index.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565
  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. convenitentOperation: {
  47. type: Boolean,
  48. default: false,
  49. },
  50. // 是否禁止选择
  51. selectable: {
  52. type: Function,
  53. default: () => {},
  54. },
  55. //
  56. storageKey: {
  57. type: String,
  58. },
  59. showSummary:{
  60. type:Boolean,
  61. default:false,
  62. },
  63. },
  64. components: {
  65. ElDictTag: () => import("@/components/DictTag/index.vue"),
  66. ElDraggable: () => import("@/components/draggable/index.vue"),
  67. ElFilePreview: () => import("@/components/file-preview/index.vue"),
  68. ElComputedInput: () => import("@/components/computed-input/index.vue"),
  69. ElPopoverSelectV2: () => import("@/components/popover-select-v2/index.vue"),
  70. ElPopoverMultipleSelectV2: () =>
  71. import("@/components/popover-select-v2/multiple.vue"),
  72. ElComputedInputV2: () => import("@/components/computed-input-v2/index.vue"),
  73. ElPopoverTreeSelect: () =>
  74. import("@/components/popover-tree-select/index.vue"),
  75. ButtonHide: () => import("./hide.vue"),
  76. ButtonFreeze: () => import("./freeze.vue"),
  77. IconHide: () => import("./once/hide.vue"),
  78. IconSort: () => import("./once/sort.vue"),
  79. IconFreeze: () => import("./once/freeze.vue"),
  80. IconFilter: () => import("./once/filters.vue"),
  81. },
  82. data() {
  83. const { columns, storageKey } = this.$props;
  84. const localColumns = localStorage.getItem(storageKey);
  85. const innerColumns =
  86. storageKey && localColumns
  87. ? JSON.parse(localColumns)
  88. : columns.map(({ item, attr }) => ({
  89. attr,
  90. item: { hidden: true, ...item },
  91. }));
  92. return {
  93. innerColumns: innerColumns,
  94. rowKey: "id",
  95. // 选择
  96. selectData: [],
  97. selectState: false,
  98. // 过滤
  99. filterData: [],
  100. filterState: false,
  101. };
  102. },
  103. computed: {
  104. innerValue: {
  105. get() {
  106. if (this.filterState) {
  107. return this.filterData;
  108. } else if (this.selectState) {
  109. return this.selectData;
  110. } else {
  111. return this.$props.value;
  112. }
  113. },
  114. set(value) {
  115. this.$emit("input", value);
  116. },
  117. },
  118. showColumns: {
  119. get() {
  120. return this.innerColumns.filter(({ item }) => item.hidden);
  121. },
  122. set() {},
  123. },
  124. filterRules: {
  125. get() {
  126. return Object.fromEntries(
  127. this.innerColumns
  128. .filter(({ item }) => item.filter && !!item.filter.length)
  129. .map(({ item }) => [item.key, item.filter])
  130. );
  131. },
  132. set() {},
  133. },
  134. },
  135. watch: {
  136. filterRules: {
  137. handler: function (newValue) {
  138. function multiFilter(array, filters) {
  139. const filterKeys = Object.keys(filters);
  140. // filters all elements passing the criteria
  141. return array.filter((item) => {
  142. // dynamically validate all filter criteria
  143. return filterKeys.every((key) => {
  144. //ignore when the filter is empty Anne
  145. if (!filters[key].length) return true;
  146. return !!~filters[key].indexOf(item[key]);
  147. });
  148. });
  149. }
  150. this.filterState = JSON.stringify(newValue) !== "{}";
  151. this.filterData = multiFilter(this.$props.value, newValue);
  152. },
  153. },
  154. value:{
  155. handler: function (newValue) {
  156. if(this.value.length > 0){
  157. this.$refs.superTable&& this.$refs.superTable.clearSelection();
  158. }
  159. },
  160. immediate: true,
  161. deep:true
  162. }
  163. },
  164. methods: {
  165. //
  166. onSelectionChange(value) {
  167. this.selectData = value;
  168. this.$emit("row-select", this.selectData);
  169. },
  170. //
  171. onRowClick(row, column, event) {
  172. const { radio, checkbox } = this.$props;
  173. // 单选
  174. if (radio) {
  175. this.$emit("row-select", [row]);
  176. }
  177. // 多选
  178. if (checkbox) {
  179. this.$refs.superTable.toggleRowSelection(
  180. this.innerValue.find((item) => item.id === row.id)
  181. );
  182. }
  183. },
  184. // 宽度
  185. onWidth(newProp, oldProp, column) {
  186. this.innerColumns = this.innerColumns.map(({ item, attr }) => ({
  187. attr,
  188. item: {
  189. ...item,
  190. width: item.key === column.property ? newProp : item.width,
  191. },
  192. }));
  193. if (this.$props.storageKey) {
  194. localStorage.setItem(
  195. this.$props.storageKey,
  196. JSON.stringify(this.innerColumns)
  197. );
  198. }
  199. },
  200. // 冻结
  201. onHide(prop) {
  202. this.$nextTick(() => {
  203. this.$refs.superTable.doLayout();
  204. if (this.$props.storageKey) {
  205. localStorage.setItem(
  206. this.$props.storageKey,
  207. JSON.stringify(this.innerColumns)
  208. );
  209. }
  210. });
  211. },
  212. // 排序
  213. onSort(prop) {
  214. const { key, sort } = prop;
  215. this.$nextTick(() => {
  216. this.$refs.superTable.sort(key, sort);
  217. this.$refs.superTable.doLayout();
  218. if (this.$props.storageKey) {
  219. localStorage.setItem(
  220. this.$props.storageKey,
  221. JSON.stringify(this.innerColumns)
  222. );
  223. }
  224. });
  225. },
  226. // 冻结
  227. onFreeze() {
  228. this.$nextTick(() => {
  229. this.$refs.superTable.doLayout();
  230. if (this.$props.storageKey) {
  231. localStorage.setItem(
  232. this.$props.storageKey,
  233. JSON.stringify(this.innerColumns)
  234. );
  235. }
  236. });
  237. },
  238. // 过滤
  239. onFilter() {
  240. this.$nextTick(() => {
  241. this.$refs.superTable.doLayout();
  242. if (this.$props.storageKey) {
  243. localStorage.setItem(
  244. this.$props.storageKey,
  245. JSON.stringify(this.innerColumns)
  246. );
  247. }
  248. });
  249. },
  250. onFilters(value) {
  251. const {
  252. item: { key },
  253. attr: { dictName },
  254. } = value;
  255. let dataList = [];
  256. const dict = this.dict.type[dictName];
  257. dataList = Array.from(
  258. new Set(this.innerValue.map((item) => item[key]).filter((item) => item))
  259. ).map((item) => ({
  260. text: dictName
  261. ? (dict.find((dictItem) => dictItem.value == item) || {}).label
  262. : item,
  263. value: item,
  264. }));
  265. return dataList;
  266. },
  267. // 继承el-table的Method
  268. extendMethod() {
  269. const refMethod = Object.entries(this.$refs["superTable"]);
  270. for (const [key, value] of refMethod) {
  271. if (!(key.includes("$") || key.includes("_"))) {
  272. this[key] = value;
  273. }
  274. }
  275. },
  276. getSummaries(param){
  277. if(this.showSummary){
  278. const { columns, data } = param;
  279. const sums = [];
  280. columns.forEach((column, index) => {
  281. if (index === 0 && !column.property) {
  282. sums[index] = '合计';
  283. return;
  284. }
  285. const values = data.map(item => Number(item[column.property]));
  286. // if (column.property == 'businessProportion' ||column.property =='rankMagnitude' ) {
  287. let sumColumn = this.showColumns.filter(({item,attr}) => attr.isSummary && item.key === column.property);
  288. if (sumColumn.length) {
  289. sums[index] = values.reduce((prev, curr) => {
  290. const value = Number(curr);
  291. if (!isNaN(value)) {
  292. return prev + curr;
  293. } else {
  294. return prev + 0;
  295. }
  296. }, 0);
  297. sums[index] = sums[index] && sums[index].toFixed(2) // 保留2位小数,解决小数合计列;
  298. }
  299. });
  300. return sums;
  301. }
  302. }
  303. },
  304. created() {},
  305. mounted() {
  306. this.extendMethod();
  307. },
  308. updated() {
  309. this.$nextTick(()=>{
  310. this.$refs.superTable.doLayout();
  311. })
  312. },
  313. destroyed() {},
  314. };
  315. </script>
  316. <template>
  317. <div class="el-super-table">
  318. <el-table
  319. border
  320. size="mini"
  321. height="auto"
  322. ref="superTable"
  323. v-bind="$attrs"
  324. v-on="$listeners"
  325. :row-key="rowKey"
  326. :data="innerValue"
  327. :show-summary="showSummary"
  328. :summary-method="getSummaries"
  329. :highlight-current-row="radio"
  330. @row-click="onRowClick"
  331. @header-dragend="onWidth"
  332. @selection-change="onSelectionChange"
  333. style="flex: 1"
  334. >
  335. <!-- 多选 -->
  336. <el-table-column
  337. v-if="checkbox"
  338. :column-key="rowKey"
  339. fixed
  340. width="60"
  341. align="center"
  342. type="selection"
  343. reserve-selection
  344. >
  345. </el-table-column>
  346. <!-- 序号 -->
  347. <el-table-column
  348. v-if="index"
  349. :resizable="false"
  350. fixed
  351. width="50"
  352. label="序号"
  353. align="center"
  354. class="is-index"
  355. >
  356. <template slot-scope="scope">
  357. {{ scope.$index + 1 }}
  358. </template>
  359. </el-table-column>
  360. <el-table-column
  361. v-for="({ item, attr }, index) in showColumns"
  362. :key="item.key + index"
  363. :prop="item.key"
  364. :label="item.title"
  365. :fixed="item.fixed"
  366. :width="item.width || 200"
  367. show-overflow-tooltip
  368. >
  369. <template slot="header" slot-scope="scope">
  370. <template>
  371. <span v-if="item.require" style="color: #ff4949">*</span>
  372. <span
  373. :style="{
  374. color:
  375. item.sort ||
  376. item.fixed ||
  377. (item.filter && !!item.filter.length)
  378. ? '#1890ff'
  379. : '',
  380. }"
  381. >
  382. {{ item.title }}
  383. </span>
  384. <template>
  385. <icon-sort
  386. v-if="item.sortabled"
  387. v-model="item.sort"
  388. @sort="onSort(item)"
  389. ></icon-sort>
  390. <icon-freeze
  391. v-if="item.fixedabled"
  392. v-model="item.fixed"
  393. @freeze="onFreeze"
  394. ></icon-freeze>
  395. <icon-filter
  396. v-if="item.filterabled"
  397. v-model="item.filter"
  398. :filters="onFilters({ item, attr })"
  399. @filter="onFilter"
  400. ></icon-filter>
  401. <icon-hide
  402. v-if="item.hiddenabled"
  403. v-model="item.hidden"
  404. @hide="onHide"
  405. ></icon-hide>
  406. </template>
  407. </template>
  408. </template>
  409. <template slot-scope="scope">
  410. <slot :name="item.key" v-bind="scope" :item="item" :attr="attr">
  411. <template v-if="attr.is">
  412. <component
  413. v-if="attr.is === 'el-dict-tag'"
  414. v-bind="attr"
  415. :size="$attrs.size"
  416. :value="scope.row[item.key]"
  417. :options="dict.type[attr.dictName]"
  418. ></component>
  419. <component
  420. v-else-if="attr.is === 'el-popover-select-v2'"
  421. v-bind="attr"
  422. v-model="scope.row[item.key]"
  423. :title="item.title"
  424. :size="$attrs.size"
  425. :source.sync="scope.row"
  426. >
  427. </component>
  428. <component
  429. v-else-if="attr.is === 'el-popover-multiple-select-v2'"
  430. v-bind="attr"
  431. v-model="scope.row[item.key]"
  432. :title="item.title"
  433. :size="$attrs.size"
  434. :source.sync="scope.row"
  435. >
  436. </component>
  437. <component
  438. v-else-if="attr.is === 'el-select'"
  439. v-bind="attr"
  440. v-model="scope.row[item.key]"
  441. :size="$attrs.size"
  442. >
  443. <template>
  444. <el-option
  445. v-for="item in dict.type[attr.dictName]"
  446. :key="item.value"
  447. :label="item.label"
  448. :value="item.value"
  449. >
  450. </el-option>
  451. </template>
  452. </component>
  453. <component
  454. v-else
  455. v-bind="attr"
  456. v-model="scope.row[item.key]"
  457. :size="$attrs.size"
  458. style="width: 100%"
  459. >
  460. </component
  461. ></template>
  462. <template v-else>
  463. <component v-if="attr.formatter" is="span">{{
  464. attr.formatter(scope.row)
  465. }}</component>
  466. <component v-else is="span">{{
  467. scope.row[item.key] || "--"
  468. }}</component>
  469. </template>
  470. </slot>
  471. </template>
  472. </el-table-column>
  473. <slot></slot>
  474. </el-table>
  475. <div
  476. style="
  477. height: 50px;
  478. display: flex;
  479. justify-content: space-between;
  480. align-items: center;
  481. "
  482. :style="{
  483. height: checkbox || pagination ? '50px' : '0px',
  484. }"
  485. >
  486. <div class="mr-4">
  487. <!-- <template v-if="checkbox">
  488. <el-button
  489. v-if="selectState"
  490. size="mini"
  491. @click="selectState = !selectState"
  492. >
  493. 所有列
  494. </el-button>
  495. <el-button
  496. v-else
  497. :disabled="!selectData.length"
  498. size="mini"
  499. @click="selectState = !selectState"
  500. >
  501. 选择列
  502. {{ selectData.length ? ` :${selectData.length}` : "" }}
  503. </el-button>
  504. </template> -->
  505. <template v-if="convenitentOperation">
  506. <button-hide v-model="innerColumns" @change="onHide"></button-hide>
  507. </template>
  508. </div>
  509. <pagination
  510. v-if="pagination"
  511. v-show="!selectState"
  512. :total="page.total"
  513. :page.sync="page.pageNum"
  514. :limit.sync="page.pageSize"
  515. @pagination="$emit('pagination', { ...$event })"
  516. style="height: 32px; padding: 0 !important; flex: 1; overflow-x: auto"
  517. />
  518. </div>
  519. </div>
  520. </template>
  521. <style lang="scss" scoped>
  522. .el-super-table {
  523. position: relative;
  524. flex: 1;
  525. display: flex;
  526. flex-direction: column;
  527. overflow: auto;
  528. }
  529. ::v-deep.el-super-table .el-table__header .cell {
  530. word-break: keep-all;
  531. white-space: nowrap;
  532. .icon-sort {
  533. display: none;
  534. }
  535. &:hover .icon-sort {
  536. display: inline-block;
  537. }
  538. .icon-freeze {
  539. display: none;
  540. }
  541. &:hover .icon-freeze {
  542. display: inline-block;
  543. }
  544. .icon-filter {
  545. display: none;
  546. }
  547. &:hover .icon-filter {
  548. display: inline-block;
  549. }
  550. .icon-hide {
  551. display: none;
  552. }
  553. &:hover .icon-hide {
  554. display: inline-block;
  555. }
  556. }
  557. </style>