|
@@ -0,0 +1,341 @@
|
|
|
|
+<template>
|
|
|
|
+ <div id="c-table">
|
|
|
|
+ <el-table
|
|
|
|
+ ref="table"
|
|
|
|
+ :row-key="rowKey"
|
|
|
|
+ :data="data"
|
|
|
|
+ border
|
|
|
|
+ stripe
|
|
|
|
+ :max-height="height !== null ? height : ''"
|
|
|
|
+ @select="handleSelectionChange"
|
|
|
|
+ @select-all="handleSelectAll"
|
|
|
|
+ :show-summary="useSum"
|
|
|
|
+ @row-click="rowClick"
|
|
|
|
+ :summary-method="computedSum"
|
|
|
|
+ :header-cell-style="{ background: '#F5F7FA' }"
|
|
|
|
+ >
|
|
|
|
+ <el-table-column type="selection" width="55" v-if="select" :prop="rowKey" :reserve-selection="true"> </el-table-column>
|
|
|
|
+ <template v-for="(item, index) in fields">
|
|
|
|
+ <template v-if="item.custom">
|
|
|
|
+ <el-table-column :key="index" align="center" :label="item.label" v-bind="item.options" :show-overflow-tooltip="item.showTip || true">
|
|
|
|
+ <template v-slot="{ row }">
|
|
|
|
+ <slot :name="item.model" v-bind="{ item, row }"></slot>
|
|
|
|
+ </template>
|
|
|
|
+ </el-table-column>
|
|
|
|
+ </template>
|
|
|
|
+ <template v-else>
|
|
|
|
+ <el-table-column
|
|
|
|
+ :key="index"
|
|
|
|
+ align="center"
|
|
|
|
+ :label="item.label"
|
|
|
|
+ :prop="item.model"
|
|
|
|
+ :formatter="toFormatter"
|
|
|
|
+ :sortable="true"
|
|
|
|
+ v-bind="item.options"
|
|
|
|
+ :show-overflow-tooltip="item.showTip === false ? item.showTip : true"
|
|
|
|
+ >
|
|
|
|
+ </el-table-column>
|
|
|
|
+ </template>
|
|
|
|
+ </template>
|
|
|
|
+ <template v-if="opera.length > 0">
|
|
|
|
+ <el-table-column label="操作" align="center" :width="operaWidth">
|
|
|
|
+ <template v-slot="{ row, $index }">
|
|
|
|
+ <template v-for="(item, index) in opera">
|
|
|
|
+ <template v-if="display(item, row)">
|
|
|
|
+ <template v-if="vOpera">
|
|
|
|
+ <el-link
|
|
|
|
+ v-opera="item.method"
|
|
|
|
+ :key="`${item.model}-column-${index}`"
|
|
|
|
+ :type="item.type || 'primary'"
|
|
|
|
+ :icon="item.icon || ''"
|
|
|
|
+ size="small"
|
|
|
|
+ style="padding-right: 10px"
|
|
|
|
+ :underline="false"
|
|
|
|
+ @click="handleOpera(row, item.method, item.confirm, item.methodZh, item.label, $index, item.confirmWord)"
|
|
|
|
+ >
|
|
|
|
+ {{ item.label }}
|
|
|
|
+ </el-link>
|
|
|
|
+ </template>
|
|
|
|
+ <template v-else>
|
|
|
|
+ <el-link
|
|
|
|
+ :key="`${item.model}-column-${index}`"
|
|
|
|
+ :type="item.type || 'primary'"
|
|
|
|
+ :icon="item.icon || ''"
|
|
|
|
+ size="small"
|
|
|
|
+ style="padding-right: 10px"
|
|
|
|
+ :underline="false"
|
|
|
|
+ @click="handleOpera(row, item.method, item.confirm, item.methodZh, item.label, $index, item.confirmWord)"
|
|
|
|
+ >
|
|
|
|
+ {{ item.label }}
|
|
|
|
+ </el-link>
|
|
|
|
+ </template>
|
|
|
|
+ </template>
|
|
|
|
+ </template>
|
|
|
|
+ </template>
|
|
|
|
+ </el-table-column>
|
|
|
|
+ </template>
|
|
|
|
+ </el-table>
|
|
|
|
+ <el-row type="flex" align="middle" justify="end" v-if="usePage">
|
|
|
|
+ <el-col :span="24" class="page">
|
|
|
|
+ <el-pagination
|
|
|
|
+ background
|
|
|
|
+ layout="sizes,total, prev, pager, next"
|
|
|
|
+ :page-sizes="[10, 20, 50, 100, 200]"
|
|
|
|
+ :total="total"
|
|
|
|
+ :page-size="limit"
|
|
|
|
+ v-model:current-page="currentPage"
|
|
|
|
+ @current-change="changePage"
|
|
|
|
+ @size-change="sizeChange"
|
|
|
|
+ >
|
|
|
|
+ </el-pagination>
|
|
|
|
+ </el-col>
|
|
|
|
+ </el-row>
|
|
|
|
+ </div>
|
|
|
|
+</template>
|
|
|
|
+<script setup lang="ts">
|
|
|
|
+import type { Ref } from 'vue';
|
|
|
|
+import { ref, toRefs, nextTick, getCurrentInstance } from 'vue';
|
|
|
|
+import { ElMessageBox } from 'element-plus';
|
|
|
|
+
|
|
|
|
+import _ from 'lodash';
|
|
|
|
+const { proxy } = getCurrentInstance() as any;
|
|
|
|
+
|
|
|
|
+// #region 传递
|
|
|
|
+
|
|
|
|
+interface fieldsItem {
|
|
|
|
+ custom: string;
|
|
|
|
+ label: string;
|
|
|
|
+ options: string;
|
|
|
|
+ showTip: boolean;
|
|
|
|
+ model: string;
|
|
|
|
+}
|
|
|
|
+interface operaItem {
|
|
|
|
+ method: string;
|
|
|
|
+ model: string;
|
|
|
|
+ type: string;
|
|
|
|
+ icon: string;
|
|
|
|
+ confirmWord: string;
|
|
|
|
+ label: string;
|
|
|
|
+ confirm: boolean;
|
|
|
|
+ methodZh: string;
|
|
|
|
+}
|
|
|
|
+interface dataItem {
|
|
|
|
+ _id?: string;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const props = defineProps({
|
|
|
|
+ fields: { type: Array<fieldsItem>, required: true },
|
|
|
|
+ data: { type: Array<dataItem>, required: true },
|
|
|
|
+ opera: { type: Array<operaItem>, default: () => [] },
|
|
|
|
+ rowKey: { type: String, default: '_id' },
|
|
|
|
+ select: { type: Boolean, default: false },
|
|
|
|
+ selected: { type: Array, default: () => [] },
|
|
|
|
+ usePage: { type: Boolean, default: true },
|
|
|
|
+ total: { type: Number, default: 0 },
|
|
|
|
+ useSum: { type: Boolean, default: false },
|
|
|
|
+ sumcol: { type: Array, default: () => [] },
|
|
|
|
+ sumres: { type: String, default: 'total' },
|
|
|
|
+ // limit: { type: Number, default: 10 },
|
|
|
|
+ height: null,
|
|
|
|
+ operaWidth: { type: Number, default: 200 },
|
|
|
|
+ vOpera: { type: Boolean, default: false }
|
|
|
|
+});
|
|
|
|
+const { fields } = toRefs(props);
|
|
|
|
+const { data } = toRefs(props);
|
|
|
|
+const { opera } = toRefs(props);
|
|
|
|
+const { rowKey } = toRefs(props);
|
|
|
|
+const { select } = toRefs(props);
|
|
|
|
+const { selected } = toRefs(props);
|
|
|
|
+const { usePage } = toRefs(props);
|
|
|
|
+const { total } = toRefs(props);
|
|
|
|
+const { useSum } = toRefs(props);
|
|
|
|
+const { sumcol } = toRefs(props);
|
|
|
|
+const { sumres } = toRefs(props);
|
|
|
|
+// const { limit } = toRefs(props);
|
|
|
|
+const { height } = toRefs(props);
|
|
|
|
+const { operaWidth } = toRefs(props);
|
|
|
|
+const { vOpera } = toRefs(props);
|
|
|
|
+// #endregion
|
|
|
|
+const emit = defineEmits(['method', 'handleSelect', 'query', 'rowClick']);
|
|
|
|
+
|
|
|
|
+let pageSelected: Ref<any[]> = ref([]);
|
|
|
|
+let currentPage: Ref<number> = ref(1);
|
|
|
|
+
|
|
|
|
+let limit: number = proxy.$limit;
|
|
|
|
+const toFormatter = (row: any, column: { property: string }, cellValue: string, index: string) => {
|
|
|
|
+ let this_fields = fields.value.filter((fil) => fil.model === column.property);
|
|
|
|
+ if (this_fields.length > 0) {
|
|
|
|
+ let format: any = _.get(this_fields[0], `format`, false);
|
|
|
|
+ if (format) {
|
|
|
|
+ let res;
|
|
|
|
+ if (_.isFunction(format)) {
|
|
|
|
+ res = format(cellValue);
|
|
|
|
+ } else {
|
|
|
|
+ res = toFormat({
|
|
|
|
+ model: this_fields[0].model,
|
|
|
|
+ value: cellValue
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+ return res;
|
|
|
|
+ } else {
|
|
|
|
+ return cellValue;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
+const toFormat = (e: { model: string; value: string }) => {};
|
|
|
|
+const handleOpera = (data: string, method: any, confirm = false, methodZh: string, label: string, index: string, confirmWord: string) => {
|
|
|
|
+ let self = true;
|
|
|
|
+ if (_.isFunction(methodZh)) methodZh = methodZh(data);
|
|
|
|
+ else if (!_.isString(methodZh)) {
|
|
|
|
+ methodZh = label;
|
|
|
|
+ self = false;
|
|
|
|
+ }
|
|
|
|
+ if (confirm) {
|
|
|
|
+ let word = self ? methodZh : `您确认${methodZh}该数据?`;
|
|
|
|
+ if (confirmWord) word = confirmWord;
|
|
|
|
+ ElMessageBox.confirm(word, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
|
|
|
|
+ .then(() => {
|
|
|
|
+ emit(method, data, index);
|
|
|
|
+ })
|
|
|
|
+ .catch(() => {});
|
|
|
|
+ } else emit(method, data, index);
|
|
|
|
+};
|
|
|
|
+const handleSelectionChange = (selection: string, row: never) => {
|
|
|
|
+ //根据row是否再pageSelected中,判断是添加还是删除
|
|
|
|
+ let isSelecteds = _.cloneDeep(pageSelected);
|
|
|
|
+ const is_has = isSelecteds.value.findIndex((f) => f[rowKey.value] === row[rowKey.value]);
|
|
|
|
+ if (is_has <= -1) {
|
|
|
|
+ // 没有找到,属于添加
|
|
|
|
+ isSelecteds.value.push(row);
|
|
|
|
+ } else {
|
|
|
|
+ // 找到了,删除
|
|
|
|
+ isSelecteds.value.splice(is_has, 1);
|
|
|
|
+ }
|
|
|
|
+ pageSelected.value = isSelecteds.value;
|
|
|
|
+ emit(`handleSelect`, isSelecteds);
|
|
|
|
+};
|
|
|
|
+const handleSelectAll = (selection: any) => {
|
|
|
|
+ //处于没全选状态,选择之后一定是全选,只有处于全选状态时,才会反选(全取消)
|
|
|
|
+ let res: any = [];
|
|
|
|
+ if (selection.length > 0) {
|
|
|
|
+ //全选
|
|
|
|
+ res = _.uniqBy(pageSelected.value.concat(selection), rowKey.value);
|
|
|
|
+ } else {
|
|
|
|
+ //全取消
|
|
|
|
+ res = _.differenceBy(pageSelected.value, data.value, rowKey.value);
|
|
|
|
+ }
|
|
|
|
+ pageSelected.value = res;
|
|
|
|
+ emit(`handleSelect`, res);
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+const table = ref<any>();
|
|
|
|
+
|
|
|
|
+const initSelection = (select: any, data: any) => {
|
|
|
|
+ nextTick(() => {
|
|
|
|
+ table.value.clearSelection();
|
|
|
|
+ select.forEach((info: any) => {
|
|
|
|
+ let d = data.find((p: any) => p._id == info._id);
|
|
|
|
+ console.log(d);
|
|
|
|
+ if (d) table.value.toggleRowSelection(d);
|
|
|
|
+ });
|
|
|
|
+ });
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+const selectReset = () => {
|
|
|
|
+ table.value.clearSelection();
|
|
|
|
+};
|
|
|
|
+defineExpose({ initSelection });
|
|
|
|
+
|
|
|
|
+const changePage = (page: number = currentPage.value) => {
|
|
|
|
+ emit('query', { skip: (page - 1) * limit, limit: limit });
|
|
|
|
+};
|
|
|
|
+const sizeChange = (limits: number) => {
|
|
|
|
+ limit = limits;
|
|
|
|
+ currentPage.value = 1;
|
|
|
|
+ emit('query', { skip: 0, limit: limit });
|
|
|
|
+};
|
|
|
|
+const rowClick = (row: any, column: string, event: string) => {
|
|
|
|
+ emit(`rowClick`, row);
|
|
|
|
+};
|
|
|
|
+const display = (item: operaItem, row: any) => {
|
|
|
|
+ let display: any = _.get(item, `display`, true);
|
|
|
|
+ if (display === true) return true;
|
|
|
|
+ else {
|
|
|
|
+ let res = display(row);
|
|
|
|
+ return res;
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
+// 计算合计
|
|
|
|
+const computedSum = (e: { columns: any; data: any }) => {
|
|
|
|
+ const { columns, data } = e;
|
|
|
|
+
|
|
|
|
+ if (columns.length <= 0 || data.length <= 0) return '';
|
|
|
|
+ const result = [];
|
|
|
|
+ const reg = new RegExp(/^\d+$/);
|
|
|
|
+ for (const column of columns) {
|
|
|
|
+ // 判断有没有prop属性
|
|
|
|
+ const prop = _.get(column, 'property');
|
|
|
|
+ if (!prop) {
|
|
|
|
+ result.push('');
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+ // 判断是否需要计算
|
|
|
|
+ const inlist = sumcol.value.find((f) => f == prop);
|
|
|
|
+ if (!inlist) {
|
|
|
|
+ result.push('');
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+ let res: number | unknown = 0;
|
|
|
|
+ // 整理出要计算的属性(只取出数字或者可以为数字的值)
|
|
|
|
+ const resetList = data.map((i: any) => {
|
|
|
|
+ const d = _.get(i, prop);
|
|
|
|
+ return d * 1;
|
|
|
|
+ });
|
|
|
|
+ let p1: any;
|
|
|
|
+ if (sumres.value === 'total') {
|
|
|
|
+ res = totalComputed(p1, resetList);
|
|
|
|
+ } else if (sumres.value === 'avg') {
|
|
|
|
+ res = avgComputed(resetList);
|
|
|
|
+ } else if (sumres.value === 'max') {
|
|
|
|
+ res = maxComputed(resetList);
|
|
|
|
+ } else if (sumres.value === 'min') {
|
|
|
|
+ res = minComputed(resetList);
|
|
|
|
+ }
|
|
|
|
+ result.push(res);
|
|
|
|
+ }
|
|
|
|
+ result[0] = '合计';
|
|
|
|
+ return result;
|
|
|
|
+};
|
|
|
|
+// 合计计算
|
|
|
|
+const totalComputed = (columns: any, data: any) => {
|
|
|
|
+ const total = data.reduce((p: number, n: string) => p + parseFloat(n), 0);
|
|
|
|
+ return total;
|
|
|
|
+};
|
|
|
|
+// 平均值计算
|
|
|
|
+const avgComputed = (data: any) => {
|
|
|
|
+ let p1: any;
|
|
|
|
+ const total = totalComputed(p1, data);
|
|
|
|
+ return _.round(_.divide(total, data.length), 2);
|
|
|
|
+};
|
|
|
|
+// 最大值计算
|
|
|
|
+const maxComputed = (data: any) => {
|
|
|
|
+ return _.max(data);
|
|
|
|
+};
|
|
|
|
+// 最小值计算
|
|
|
|
+const minComputed = (data: any) => {
|
|
|
|
+ return _.min(data);
|
|
|
|
+};
|
|
|
|
+</script>
|
|
|
|
+
|
|
|
|
+<style scoped>
|
|
|
|
+.page {
|
|
|
|
+ background-color: #fff;
|
|
|
|
+ padding: 8px;
|
|
|
|
+ height: 50px;
|
|
|
|
+}
|
|
|
|
+.el-pagination {
|
|
|
|
+ position: absolute;
|
|
|
|
+ right: 10px;
|
|
|
|
+ background-color: #fff;
|
|
|
|
+}
|
|
|
|
+</style>
|