c-table.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. <template>
  2. <div id="c-table">
  3. <el-table
  4. ref="table"
  5. :row-key="rowKey"
  6. :data="data"
  7. size="small"
  8. border
  9. stripe
  10. :max-height="height !== null ? height : ''"
  11. @select="handleSelectionChange"
  12. @select-all="handleSelectAll"
  13. :show-summary="useSum"
  14. @row-click="rowClick"
  15. :summary-method="computedSum"
  16. >
  17. <el-table-column type="selection" width="55" v-if="select" :prop="rowKey" :reserve-selection="true"> </el-table-column>
  18. <template v-for="(item, index) in fields">
  19. <template v-if="item.custom">
  20. <el-table-column :key="index" align="center" :label="item.label" v-bind="item.options" :show-overflow-tooltip="item.showTip || true">
  21. <template v-slot="{ row }">
  22. <slot :name="item.model" v-bind="{ item, row }"></slot>
  23. </template>
  24. </el-table-column>
  25. </template>
  26. <template v-else>
  27. <el-table-column
  28. :key="index"
  29. align="center"
  30. :label="item.label"
  31. :prop="item.model"
  32. :formatter="toFormatter"
  33. :sortable="true"
  34. v-bind="item.options"
  35. :show-overflow-tooltip="item.showTip === false ? item.showTip : true"
  36. >
  37. </el-table-column>
  38. </template>
  39. </template>
  40. <template v-if="opera.length > 0">
  41. <el-table-column label="操作" align="center" :width="operaWidth">
  42. <template v-slot="{ row, $index }">
  43. <template v-for="(item, index) in opera">
  44. <template v-if="display(item, row)">
  45. <template v-if="vOpera">
  46. <el-link
  47. v-opera="item.method"
  48. :key="`${item.model}-column-${index}`"
  49. :type="item.type || 'primary'"
  50. :icon="item.icon || ''"
  51. size="small"
  52. style="padding-right: 10px"
  53. :underline="false"
  54. @click="handleOpera(row, item.method, item.confirm, item.methodZh, item.label, $index, item.confirmWord)"
  55. >
  56. {{ item.label }}
  57. </el-link>
  58. </template>
  59. <template v-else>
  60. <el-link
  61. :key="`${item.model}-column-${index}`"
  62. :type="item.type || 'primary'"
  63. :icon="item.icon || ''"
  64. size="small"
  65. style="padding-right: 10px"
  66. :underline="false"
  67. @click="handleOpera(row, item.method, item.confirm, item.methodZh, item.label, $index, item.confirmWord)"
  68. >
  69. {{ item.label }}
  70. </el-link>
  71. </template>
  72. </template>
  73. </template>
  74. </template>
  75. </el-table-column>
  76. </template>
  77. </el-table>
  78. <el-row type="flex" align="middle" justify="end" v-if="usePage">
  79. <el-col :span="24" class="page">
  80. <el-pagination
  81. background
  82. layout="sizes,total, prev, pager, next"
  83. :page-sizes="[10, 20, 50, 100, 200]"
  84. :total="total"
  85. :page-size="limit"
  86. v-model:current-page="currentPage"
  87. @current-change="changePage"
  88. @size-change="sizeChange"
  89. >
  90. </el-pagination>
  91. </el-col>
  92. </el-row>
  93. </div>
  94. </template>
  95. <script setup lang="ts">
  96. import type { Ref } from 'vue';
  97. import { ref, toRefs, nextTick, getCurrentInstance } from 'vue';
  98. import { ElMessageBox } from 'element-plus';
  99. import _ from 'lodash';
  100. const { proxy } = getCurrentInstance() as any;
  101. // #region 传递
  102. interface fieldsItem {
  103. custom: string;
  104. label: string;
  105. options: string;
  106. showTip: boolean;
  107. model: string;
  108. }
  109. interface operaItem {
  110. method: string;
  111. model: string;
  112. type: string;
  113. icon: string;
  114. confirmWord: string;
  115. label: string;
  116. confirm: boolean;
  117. methodZh: string;
  118. }
  119. interface dataItem {
  120. _id?: string;
  121. }
  122. const props = defineProps({
  123. fields: { type: Array<fieldsItem>, required: true },
  124. data: { type: Array<dataItem>, required: true },
  125. opera: { type: Array<operaItem>, default: () => [] },
  126. rowKey: { type: String, default: '_id' },
  127. select: { type: Boolean, default: false },
  128. selected: { type: Array, default: () => [] },
  129. usePage: { type: Boolean, default: true },
  130. total: { type: Number, default: 0 },
  131. useSum: { type: Boolean, default: false },
  132. sumcol: { type: Array, default: () => [] },
  133. sumres: { type: String, default: 'total' },
  134. // limit: { type: Number, default: 10 },
  135. height: null,
  136. operaWidth: { type: Number, default: 200 },
  137. vOpera: { type: Boolean, default: false },
  138. });
  139. const { fields } = toRefs(props);
  140. const { data } = toRefs(props);
  141. const { opera } = toRefs(props);
  142. const { rowKey } = toRefs(props);
  143. const { select } = toRefs(props);
  144. const { selected } = toRefs(props);
  145. const { usePage } = toRefs(props);
  146. const { total } = toRefs(props);
  147. const { useSum } = toRefs(props);
  148. const { sumcol } = toRefs(props);
  149. const { sumres } = toRefs(props);
  150. // const { limit } = toRefs(props);
  151. const { height } = toRefs(props);
  152. const { operaWidth } = toRefs(props);
  153. const { vOpera } = toRefs(props);
  154. // #endregion
  155. const emit = defineEmits(['method', 'handleSelect', 'query', 'rowClick']);
  156. let pageSelected: Ref<any[]> = ref([]);
  157. let currentPage: Ref<number> = ref(1);
  158. let limit: number = proxy.$limit;
  159. const toFormatter = (row: any, column: { property: string }, cellValue: string, index: string) => {
  160. let this_fields = fields.value.filter((fil) => fil.model === column.property);
  161. if (this_fields.length > 0) {
  162. let format: any = _.get(this_fields[0], `format`, false);
  163. if (format) {
  164. let res;
  165. if (_.isFunction(format)) {
  166. res = format(cellValue);
  167. } else {
  168. res = toFormat({
  169. model: this_fields[0].model,
  170. value: cellValue,
  171. });
  172. }
  173. return res;
  174. } else {
  175. return cellValue;
  176. }
  177. }
  178. };
  179. const toFormat = (e: { model: string; value: string }) => {};
  180. const handleOpera = (data: string, method: any, confirm = false, methodZh: string, label: string, index: string, confirmWord: string) => {
  181. let self = true;
  182. if (_.isFunction(methodZh)) methodZh = methodZh(data);
  183. else if (!_.isString(methodZh)) {
  184. methodZh = label;
  185. self = false;
  186. }
  187. if (confirm) {
  188. let word = self ? methodZh : `您确认${methodZh}该数据?`;
  189. if (confirmWord) word = confirmWord;
  190. ElMessageBox.confirm(word, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
  191. .then(() => {
  192. emit(method, data, index);
  193. })
  194. .catch(() => {});
  195. } else emit(method, data, index);
  196. };
  197. const handleSelectionChange = (selection: string, row: never) => {
  198. //根据row是否再pageSelected中,判断是添加还是删除
  199. let isSelecteds = _.cloneDeep(pageSelected);
  200. const is_has = isSelecteds.value.findIndex((f) => f[rowKey.value] === row[rowKey.value]);
  201. if (is_has <= -1) {
  202. // 没有找到,属于添加
  203. isSelecteds.value.push(row);
  204. } else {
  205. // 找到了,删除
  206. isSelecteds.value.splice(is_has, 1);
  207. }
  208. pageSelected.value = isSelecteds.value;
  209. emit(`handleSelect`, isSelecteds);
  210. };
  211. const handleSelectAll = (selection: any) => {
  212. //处于没全选状态,选择之后一定是全选,只有处于全选状态时,才会反选(全取消)
  213. let res: any = [];
  214. if (selection.length > 0) {
  215. //全选
  216. res = _.uniqBy(pageSelected.value.concat(selection), rowKey.value);
  217. } else {
  218. //全取消
  219. res = _.differenceBy(pageSelected.value, data.value, rowKey.value);
  220. }
  221. pageSelected.value = res;
  222. emit(`handleSelect`, res);
  223. };
  224. const table = ref<any>();
  225. const initSelection = () => {
  226. nextTick(() => {
  227. table.value.clearSelection();
  228. selected.value.forEach((info: any) => {
  229. let d = data.value.filter((p) => p._id === info._id);
  230. if (d.length > 0) table.value.toggleRowSelection(d[0]);
  231. });
  232. });
  233. };
  234. const selectReset = () => {
  235. table.value.clearSelection();
  236. };
  237. const changePage = (page: number = currentPage.value) => {
  238. emit('query', { skip: (page - 1) * limit, limit: limit });
  239. };
  240. const sizeChange = (limits: number) => {
  241. limit = limits;
  242. currentPage.value = 1;
  243. emit('query', { skip: 0, limit: limit });
  244. };
  245. const rowClick = (row: any, column: string, event: string) => {
  246. emit(`rowClick`, row);
  247. };
  248. const display = (item: operaItem, row: any) => {
  249. let display: any = _.get(item, `display`, true);
  250. if (display === true) return true;
  251. else {
  252. let res = display(row);
  253. return res;
  254. }
  255. };
  256. // 计算合计
  257. const computedSum = (columns: any, data: any) => {
  258. if (columns.length <= 0 || data.length <= 0) return '';
  259. const result = [];
  260. const reg = new RegExp(/^\d+$/);
  261. for (const column of columns) {
  262. // 判断有没有prop属性
  263. const prop = _.get(column, 'property');
  264. if (!prop) {
  265. result.push('');
  266. continue;
  267. }
  268. // 判断是否需要计算
  269. const inlist = sumcol.value.find((f) => f == prop);
  270. if (!inlist) {
  271. result.push('');
  272. continue;
  273. }
  274. let res: number | unknown = 0;
  275. // 整理出要计算的属性(只取出数字或者可以为数字的值)
  276. const resetList = data.map((i: any) => {
  277. const d = _.get(i, prop);
  278. return d * 1;
  279. });
  280. let p1: any;
  281. if (sumres.value === 'total') {
  282. res = totalComputed(p1, resetList);
  283. } else if (sumres.value === 'avg') {
  284. res = avgComputed(resetList);
  285. } else if (sumres.value === 'max') {
  286. res = maxComputed(resetList);
  287. } else if (sumres.value === 'min') {
  288. res = minComputed(resetList);
  289. }
  290. result.push(res);
  291. }
  292. result[0] = '合计';
  293. return result;
  294. };
  295. // 合计计算
  296. const totalComputed = (columns: any, data: any) => {
  297. const total = data.reduce((p: number, n: string) => p + parseFloat(n), 0);
  298. return total;
  299. };
  300. // 平均值计算
  301. const avgComputed = (data: any) => {
  302. let p1: any;
  303. const total = totalComputed(p1, data);
  304. return _.round(_.divide(total, data.length), 2);
  305. };
  306. // 最大值计算
  307. const maxComputed = (data: any) => {
  308. return _.max(data);
  309. };
  310. // 最小值计算
  311. const minComputed = (data: any) => {
  312. return _.min(data);
  313. };
  314. </script>
  315. <style scoped>
  316. .page {
  317. background-color: #fff;
  318. padding: 8px;
  319. height: 50px;
  320. }
  321. .el-pagination {
  322. position: absolute;
  323. right: 10px;
  324. background-color: #fff;
  325. }
  326. </style>