index.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  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 '';
  295. }
  296. }, 0);
  297. 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. ref="superTable"
  320. border
  321. height="auto"
  322. :row-key="rowKey"
  323. :data="innerValue"
  324. :highlight-current-row="radio"
  325. @row-click="onRowClick"
  326. @selection-change="onSelectionChange"
  327. @header-dragend="onWidth"
  328. v-bind="$attrs"
  329. v-on="$listeners"
  330. style="flex: 1"
  331. :show-summary="showSummary"
  332. :summary-method="getSummaries"
  333. >
  334. <!-- 多选 -->
  335. <el-table-column
  336. v-if="checkbox"
  337. :column-key="rowKey"
  338. fixed
  339. width="60"
  340. align="center"
  341. type="selection"
  342. reserve-selection
  343. >
  344. </el-table-column>
  345. <!-- 序号 -->
  346. <el-table-column
  347. v-if="index"
  348. :resizable="false"
  349. fixed
  350. width="50"
  351. label="序号"
  352. align="center"
  353. class="is-index"
  354. >
  355. <template slot-scope="scope">
  356. {{ scope.$index + 1 }}
  357. </template>
  358. </el-table-column>
  359. <el-table-column
  360. v-for="({ item, attr }, index) in showColumns"
  361. :key="item.key + index"
  362. :prop="item.key"
  363. :label="item.title"
  364. :fixed="item.fixed"
  365. :width="item.width || 200"
  366. show-overflow-tooltip
  367. >
  368. <template slot="header" slot-scope="scope">
  369. <template>
  370. <span v-if="item.require" style="color: #ff4949">*</span>
  371. <span
  372. :style="{
  373. color:
  374. item.sort ||
  375. item.fixed ||
  376. (item.filter && !!item.filter.length)
  377. ? '#1890ff'
  378. : '',
  379. }"
  380. >
  381. {{ item.title }}
  382. </span>
  383. <template>
  384. <icon-sort
  385. v-if="item.sortabled"
  386. v-model="item.sort"
  387. @sort="onSort(item)"
  388. ></icon-sort>
  389. <icon-freeze
  390. v-if="item.fixedabled"
  391. v-model="item.fixed"
  392. @freeze="onFreeze"
  393. ></icon-freeze>
  394. <icon-filter
  395. v-if="item.filterabled"
  396. v-model="item.filter"
  397. :filters="onFilters({ item, attr })"
  398. @filter="onFilter"
  399. ></icon-filter>
  400. <icon-hide
  401. v-if="item.hiddenabled"
  402. v-model="item.hidden"
  403. @hide="onHide"
  404. ></icon-hide>
  405. </template>
  406. </template>
  407. </template>
  408. <template slot-scope="scope">
  409. <slot :name="item.key" v-bind="scope" :item="item" :attr="attr">
  410. <template v-if="attr.is">
  411. <component
  412. v-if="attr.is === 'el-dict-tag'"
  413. v-bind="attr"
  414. :size="$attrs.size"
  415. :value="scope.row[item.key]"
  416. :options="dict.type[attr.dictName]"
  417. ></component>
  418. <component
  419. v-else-if="attr.is === 'el-popover-select-v2'"
  420. v-bind="attr"
  421. v-model="scope.row[item.key]"
  422. :title="item.title"
  423. :size="$attrs.size"
  424. :source.sync="scope.row"
  425. >
  426. </component>
  427. <component
  428. v-else-if="attr.is === 'el-popover-multiple-select-v2'"
  429. v-bind="attr"
  430. v-model="scope.row[item.key]"
  431. :title="item.title"
  432. :size="$attrs.size"
  433. :source.sync="scope.row"
  434. >
  435. </component>
  436. <component
  437. v-else-if="attr.is === 'el-select'"
  438. v-bind="attr"
  439. v-model="scope.row[item.key]"
  440. :size="$attrs.size"
  441. >
  442. <template>
  443. <el-option
  444. v-for="item in dict.type[attr.dictName]"
  445. :key="item.value"
  446. :label="item.label"
  447. :value="item.value"
  448. >
  449. </el-option>
  450. </template>
  451. </component>
  452. <component
  453. v-else
  454. v-bind="attr"
  455. v-model="scope.row[item.key]"
  456. :size="$attrs.size"
  457. style="width: 100%"
  458. >
  459. </component
  460. ></template>
  461. <template v-else>
  462. <component v-if="attr.formatter" is="span">{{
  463. attr.formatter(scope.row)
  464. }}</component>
  465. <component v-else is="span">{{
  466. scope.row[item.key] || "--"
  467. }}</component>
  468. </template>
  469. </slot>
  470. </template>
  471. </el-table-column>
  472. <slot></slot>
  473. </el-table>
  474. <div
  475. style="
  476. height: 50px;
  477. display: flex;
  478. justify-content: space-between;
  479. align-items: center;
  480. "
  481. :style="{
  482. height: checkbox || pagination ? '50px' : '0px',
  483. }"
  484. >
  485. <div class="mr-4">
  486. <!-- <template v-if="checkbox">
  487. <el-button
  488. v-if="selectState"
  489. size="mini"
  490. @click="selectState = !selectState"
  491. >
  492. 所有列
  493. </el-button>
  494. <el-button
  495. v-else
  496. :disabled="!selectData.length"
  497. size="mini"
  498. @click="selectState = !selectState"
  499. >
  500. 选择列
  501. {{ selectData.length ? ` :${selectData.length}` : "" }}
  502. </el-button>
  503. </template> -->
  504. <template v-if="convenitentOperation">
  505. <button-hide v-model="innerColumns" @change="onHide"></button-hide>
  506. </template>
  507. </div>
  508. <pagination
  509. v-if="pagination"
  510. v-show="!selectState"
  511. :total="page.total"
  512. :page.sync="page.pageNum"
  513. :limit.sync="page.pageSize"
  514. @pagination="$emit('pagination', { ...$event })"
  515. style="height: 32px; padding: 0 !important; flex: 1; overflow-x: auto"
  516. />
  517. </div>
  518. </div>
  519. </template>
  520. <style lang="scss" scoped>
  521. .el-super-table {
  522. position: relative;
  523. flex: 1;
  524. display: flex;
  525. flex-direction: column;
  526. overflow: auto;
  527. }
  528. ::v-deep.el-super-table .el-table__header .cell {
  529. word-break: keep-all;
  530. white-space: nowrap;
  531. .icon-sort {
  532. display: none;
  533. }
  534. &:hover .icon-sort {
  535. display: inline-block;
  536. }
  537. .icon-freeze {
  538. display: none;
  539. }
  540. &:hover .icon-freeze {
  541. display: inline-block;
  542. }
  543. .icon-filter {
  544. display: none;
  545. }
  546. &:hover .icon-filter {
  547. display: inline-block;
  548. }
  549. .icon-hide {
  550. display: none;
  551. }
  552. &:hover .icon-hide {
  553. display: inline-block;
  554. }
  555. }
  556. </style>