index.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. <script>
  2. export default {
  3. name: "SuperTable",
  4. props: {
  5. // 数据
  6. value: {
  7. type: [Array],
  8. require: true,
  9. },
  10. // 字典
  11. dict: {
  12. type: [Object],
  13. require: true,
  14. },
  15. // 分页
  16. page: {
  17. type: [Object],
  18. require: false,
  19. },
  20. // 模板
  21. columns: {
  22. type: [Array],
  23. require: true,
  24. },
  25. // 是否显示序号
  26. index: {
  27. type: Boolean,
  28. default: false,
  29. },
  30. // 是否显示单选
  31. radio: {
  32. type: Boolean,
  33. default: false,
  34. },
  35. // 是否显示多选
  36. checkbox: {
  37. type: Boolean,
  38. default: false,
  39. },
  40. // 是否显示分页
  41. pagination: {
  42. type: Boolean,
  43. default: false,
  44. },
  45. // 是否列显示
  46. hiddenColumns: {
  47. type: Boolean,
  48. default: false,
  49. },
  50. // 是否列过滤
  51. filterColumns: {
  52. type: Boolean,
  53. default: false,
  54. },
  55. // 是否禁止选择
  56. selectable: {
  57. type: Function,
  58. default: () => {},
  59. },
  60. },
  61. components: {
  62. ElDictTag: () => import("@/components/DictTag/index.vue"),
  63. ElDraggable: () => import("@/components/draggable/index.vue"),
  64. ElFilePreview: () => import("@/components/file-preview/index.vue"),
  65. ElComputedInput: () => import("@/components/computed-input/index.vue"),
  66. ElPopoverSelectV2: () => import("@/components/popover-select-v2/index.vue"),
  67. ElComputedInputV2: () => import("@/components/computed-input-v2/index.vue"),
  68. ButtonHide: () => import("./hide.vue"),
  69. ButtonFreeze: () => import("./freeze.vue"),
  70. IconHide: () => import("./once/hide.vue"),
  71. IconSort: () => import("./once/sort.vue"),
  72. IconFilter: () => import("./once/filter.vue"),
  73. IconFreeze: () => import("./once/freeze.vue"),
  74. },
  75. data() {
  76. const { columns } = this.$props;
  77. const innerColumns = columns.map(({ item, attr }) => ({
  78. attr,
  79. item: { ...item, hidden: true },
  80. }));
  81. return {
  82. innerColumns: innerColumns,
  83. rowKey: "id",
  84. // 选择
  85. selectData: [],
  86. selectState: false,
  87. // 过滤
  88. filterData: [],
  89. filterState: false,
  90. };
  91. },
  92. computed: {
  93. innerValue: {
  94. get() {
  95. if (this.filterState) {
  96. return this.filterData;
  97. } else if (this.selectState) {
  98. return this.selectData;
  99. } else {
  100. return this.$props.value;
  101. }
  102. },
  103. set(value) {
  104. this.$emit("input", value);
  105. },
  106. },
  107. showColumns: {
  108. get() {
  109. return this.innerColumns.filter(({ item }) => item.hidden);
  110. },
  111. set() {},
  112. },
  113. filterRules: {
  114. get() {
  115. return Object.fromEntries(
  116. this.innerColumns
  117. .filter(({ item }) => item.filter && !!item.filter.length)
  118. .map(({ item }) => [item.key, item.filter])
  119. );
  120. },
  121. set() {},
  122. },
  123. },
  124. watch: {
  125. filterRules: {
  126. handler: function (newValue) {
  127. function multiFilter(array, filters) {
  128. const filterKeys = Object.keys(filters);
  129. // filters all elements passing the criteria
  130. return array.filter((item) => {
  131. // dynamically validate all filter criteria
  132. return filterKeys.every((key) => {
  133. //ignore when the filter is empty Anne
  134. if (!filters[key].length) return true;
  135. return !!~filters[key].indexOf(item[key]);
  136. });
  137. });
  138. }
  139. this.filterState = JSON.stringify(newValue) !== "{}";
  140. this.filterData = multiFilter(this.$props.value, newValue);
  141. },
  142. },
  143. },
  144. methods: {
  145. //
  146. onSelectionChange(value) {
  147. this.selectData = value;
  148. },
  149. //
  150. onRowClick(row, column, event) {
  151. const { radio, checkbox } = this.$props;
  152. // 单选
  153. if (radio) {
  154. this.selectData = [row];
  155. this.innerValue = this.innerValue.map((item) => ({
  156. ...item,
  157. isChecked: item.id === row.id ? true : false,
  158. }));
  159. this.$emit("row-select", this.selectData);
  160. }
  161. // 多选
  162. if (checkbox) {
  163. this.$refs.superTable.toggleRowSelection(
  164. this.innerValue.find((item) => item.id === row.id)
  165. );
  166. this.$emit("row-select", this.selectData);
  167. }
  168. },
  169. // 冻结
  170. onHide() {
  171. this.$nextTick(() => {
  172. this.$refs.superTable.doLayout();
  173. });
  174. },
  175. // 排序
  176. onSort(prop) {
  177. const { key, sort } = prop;
  178. this.$nextTick(() => {
  179. this.$refs.superTable.sort(key, sort);
  180. this.$refs.superTable.doLayout();
  181. });
  182. },
  183. // 冻结
  184. onFreeze() {
  185. this.$nextTick(() => {
  186. this.$refs.superTable.doLayout();
  187. });
  188. },
  189. // 过滤
  190. onFilter() {
  191. this.$nextTick(() => {
  192. this.$refs.superTable.doLayout();
  193. });
  194. },
  195. onFilters(value) {
  196. const {
  197. item: { key },
  198. attr: { dictName },
  199. } = value;
  200. let dataList = [];
  201. const dict = this.dict.type[dictName];
  202. dataList = Array.from(
  203. new Set(this.innerValue.map((item) => item[key]).filter((item) => item))
  204. ).map((item) => ({
  205. text: dictName
  206. ? (dict.find((dictItem) => dictItem.value == item) || {}).label
  207. : item,
  208. value: item,
  209. }));
  210. return dataList;
  211. },
  212. },
  213. created() {},
  214. mounted() {},
  215. destroyed() {},
  216. };
  217. </script>
  218. <template>
  219. <div class="el-super-table">
  220. <el-table
  221. ref="superTable"
  222. border
  223. :row-key="rowKey"
  224. :data="innerValue"
  225. @row-click="onRowClick"
  226. @selection-change="onSelectionChange"
  227. v-bind="$attrs"
  228. v-on="$listeners"
  229. >
  230. <!-- 序号 -->
  231. <el-table-column
  232. v-if="index"
  233. :resizable="false"
  234. fixed
  235. width="50"
  236. label="序号"
  237. align="center"
  238. class="is-index"
  239. >
  240. <template slot-scope="scope">
  241. {{ scope.$index + 1 }}
  242. </template>
  243. </el-table-column>
  244. <!-- 多选 -->
  245. <el-table-column
  246. v-if="checkbox"
  247. :column-key="rowKey"
  248. fixed
  249. width="50"
  250. align="center"
  251. type="selection"
  252. reserve-selection
  253. >
  254. </el-table-column>
  255. <el-table-column
  256. v-for="({ item, attr }, index) in showColumns"
  257. :key="index"
  258. :prop="item.key"
  259. :label="item.title"
  260. :fixed="item.fixed"
  261. :width="item.width || 200"
  262. show-overflow-tooltip
  263. >
  264. <template slot="header" slot-scope="scope">
  265. <template>
  266. <span v-if="item.require" style="color: #ff4949">*</span>
  267. <span
  268. :style="{
  269. color:
  270. item.sort ||
  271. item.fixed ||
  272. (item.filter && !!item.filter.length)
  273. ? '#1890ff'
  274. : '',
  275. }"
  276. >
  277. {{ item.title }}
  278. </span>
  279. <icon-sort
  280. v-model="item.sort"
  281. class="icon-sort"
  282. @sort="onSort(item)"
  283. :style="{
  284. color: item.sort ? '#1890ff' : '',
  285. display: item.sort ? 'inline-block' : '',
  286. }"
  287. ></icon-sort>
  288. <icon-freeze
  289. v-model="item.fixed"
  290. class="icon-freeze"
  291. @freeze="onFreeze"
  292. :style="{
  293. color: item.fixed ? '#1890ff' : '',
  294. display: item.fixed ? 'inline-block' : '',
  295. }"
  296. ></icon-freeze>
  297. <icon-filter
  298. v-if="filterColumns"
  299. v-model="item.filter"
  300. class="icon-filter"
  301. :filters="onFilters({ item, attr })"
  302. @filter="onFilter"
  303. :style="{
  304. color: item.filter && item.filter.length ? '#1890ff' : '',
  305. display:
  306. item.filter && item.filter.length ? 'inline-block' : '',
  307. }"
  308. ></icon-filter>
  309. <icon-hide
  310. v-if="hiddenColumns"
  311. v-model="item.hidden"
  312. class="icon-hide"
  313. @hide="onHide"
  314. ></icon-hide>
  315. </template>
  316. </template>
  317. <template slot-scope="scope">
  318. <slot :name="item.key" v-bind="scope" :item="item" :attr="attr">
  319. <template v-if="attr.is">
  320. <component
  321. v-if="attr.is === 'el-dict-tag'"
  322. v-bind="attr"
  323. :size="$attrs.size"
  324. :value="scope.row[item.key]"
  325. :options="dict.type[attr.dictName]"
  326. ></component>
  327. <component
  328. v-else-if="attr.is === 'el-popover-select-v2'"
  329. v-bind="attr"
  330. v-model="scope.row[item.key]"
  331. :size="$attrs.size"
  332. :source.sync="scope.row"
  333. >
  334. </component>
  335. <component
  336. v-else-if="attr.is === 'el-select'"
  337. v-bind="attr"
  338. v-model="scope.row[item.key]"
  339. :size="$attrs.size"
  340. >
  341. <template>
  342. <el-option
  343. v-for="item in dict.type[attr.dictName]"
  344. :key="item.value"
  345. :label="item.label"
  346. :value="item.value"
  347. >
  348. </el-option>
  349. </template>
  350. </component>
  351. <component
  352. v-else
  353. v-bind="attr"
  354. v-model="scope.row[item.key]"
  355. :size="$attrs.size"
  356. style="width: 100%"
  357. >
  358. </component
  359. ></template>
  360. <template v-else>
  361. <component v-if="attr.formatter" is="span">{{
  362. attr.formatter(scope.row)
  363. }}</component>
  364. <component v-else is="span">{{
  365. scope.row[item.key] || "--"
  366. }}</component>
  367. </template>
  368. </slot>
  369. </template>
  370. </el-table-column>
  371. <slot></slot>
  372. </el-table>
  373. <div
  374. style="
  375. height: auto;
  376. display: flex;
  377. justify-content: space-between;
  378. align-items: center;
  379. "
  380. >
  381. <div>
  382. <template v-if="checkbox">
  383. <el-button
  384. v-if="selectState"
  385. size="mini"
  386. @click="selectState = !selectState"
  387. >
  388. 所有列
  389. </el-button>
  390. <el-button
  391. v-else
  392. :disabled="!selectData.length"
  393. size="mini"
  394. @click="selectState = !selectState"
  395. >
  396. 选择列
  397. {{ selectData.length ? ` :${selectData.length}` : "" }}
  398. </el-button>
  399. </template>
  400. <template v-if="hiddenColumns">
  401. <button-hide v-model="innerColumns" @hide="onHide"></button-hide>
  402. <button-freeze
  403. v-model="showColumns"
  404. @freeze="onFreeze"
  405. ></button-freeze>
  406. </template>
  407. </div>
  408. <pagination
  409. v-if="pagination"
  410. v-show="!selectState"
  411. :total="page.total"
  412. :page.sync="page.pageNum"
  413. :limit.sync="page.pageSize"
  414. @pagination="$emit('pagination', { ...$event })"
  415. style="height: 32px; padding: 0 !important"
  416. />
  417. </div>
  418. </div>
  419. </template>
  420. <style lang="scss" scoped>
  421. .el-super-table {
  422. position: relative;
  423. }
  424. .el-super-table .el-table__header .cell {
  425. display: flex;
  426. .icon-sort {
  427. display: none;
  428. }
  429. &:hover .icon-sort {
  430. display: inline-block;
  431. }
  432. .icon-freeze {
  433. display: none;
  434. }
  435. &:hover .icon-freeze {
  436. display: inline-block;
  437. }
  438. .icon-filter {
  439. display: none;
  440. }
  441. &:hover .icon-filter {
  442. display: inline-block;
  443. }
  444. .icon-hide {
  445. display: none;
  446. }
  447. &:hover .icon-hide {
  448. display: inline-block;
  449. }
  450. }
  451. </style>