|
@@ -0,0 +1,585 @@
|
|
|
+<script>
|
|
|
+export default {
|
|
|
+ name: "SuperTable",
|
|
|
+ props: {
|
|
|
+ // 数据
|
|
|
+ value: {
|
|
|
+ type: [Array],
|
|
|
+ require: true,
|
|
|
+ },
|
|
|
+ // 字典
|
|
|
+ dict: {
|
|
|
+ type: [Object],
|
|
|
+ require: true,
|
|
|
+ },
|
|
|
+ // 分页
|
|
|
+ page: {
|
|
|
+ type: [Object],
|
|
|
+ require: false,
|
|
|
+ },
|
|
|
+ // 模板
|
|
|
+ columns: {
|
|
|
+ type: [Array],
|
|
|
+ require: true,
|
|
|
+ },
|
|
|
+ // 是否显示序号
|
|
|
+ index: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false,
|
|
|
+ },
|
|
|
+ // 是否显示单选
|
|
|
+ radio: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false,
|
|
|
+ },
|
|
|
+ // 是否显示多选
|
|
|
+ checkbox: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false,
|
|
|
+ },
|
|
|
+ // 是否显示分页
|
|
|
+ pagination: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false,
|
|
|
+ },
|
|
|
+ // 是否列操作
|
|
|
+ convenitentOperation: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false,
|
|
|
+ },
|
|
|
+ // 是否禁止选择
|
|
|
+ selectable: {
|
|
|
+ type: Function,
|
|
|
+ default: () => {},
|
|
|
+ },
|
|
|
+ //
|
|
|
+ storageKey: {
|
|
|
+ type: String,
|
|
|
+ },
|
|
|
+ showSummary:{
|
|
|
+ type:Boolean,
|
|
|
+ default:false,
|
|
|
+ },
|
|
|
+
|
|
|
+ },
|
|
|
+ components: {
|
|
|
+ ElDictTag: () => import("@/components/DictTag/index.vue"),
|
|
|
+ ElDraggable: () => import("@/components/draggable/index.vue"),
|
|
|
+ ElFilePreview: () => import("@/components/file-preview/index.vue"),
|
|
|
+ ElComputedInput: () => import("@/components/computed-input/index.vue"),
|
|
|
+ ElPopoverSelectV2: () => import("@/components/popover-select-v2/index.vue"),
|
|
|
+ ElPopoverMultipleSelectV2: () =>
|
|
|
+ import("@/components/popover-select-v2/multiple.vue"),
|
|
|
+ ElComputedInputV2: () => import("@/components/computed-input-v2/index.vue"),
|
|
|
+ ElPopoverTreeSelect: () =>
|
|
|
+ import("@/components/popover-tree-select/index.vue"),
|
|
|
+ ButtonHide: () => import("./hide.vue"),
|
|
|
+ ButtonFreeze: () => import("./freeze.vue"),
|
|
|
+ IconHide: () => import("./once/hide.vue"),
|
|
|
+ IconSort: () => import("./once/sort.vue"),
|
|
|
+ IconFreeze: () => import("./once/freeze.vue"),
|
|
|
+ IconFilter: () => import("./once/filters.vue"),
|
|
|
+ },
|
|
|
+ data() {
|
|
|
+ const { columns, storageKey } = this.$props;
|
|
|
+ const localColumns = localStorage.getItem(storageKey);
|
|
|
+ const innerColumns =
|
|
|
+ storageKey && localColumns
|
|
|
+ ? JSON.parse(localColumns)
|
|
|
+ : columns.map(({ item, attr }) => ({
|
|
|
+ attr,
|
|
|
+ item: { hidden: true, ...item },
|
|
|
+ }));
|
|
|
+ return {
|
|
|
+ innerColumns: innerColumns,
|
|
|
+ rowKey: "id",
|
|
|
+ // 选择
|
|
|
+ selectData: [],
|
|
|
+ selectState: false,
|
|
|
+ // 过滤
|
|
|
+ filterData: [],
|
|
|
+ filterState: false,
|
|
|
+ };
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ innerValue: {
|
|
|
+ get() {
|
|
|
+ if (this.filterState) {
|
|
|
+ return this.filterData;
|
|
|
+ } else if (this.selectState) {
|
|
|
+ return this.selectData;
|
|
|
+ } else {
|
|
|
+ return this.$props.value;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ set(value) {
|
|
|
+ this.$emit("input", value);
|
|
|
+ },
|
|
|
+ },
|
|
|
+ showColumns: {
|
|
|
+ get() {
|
|
|
+ return this.innerColumns.filter(({ item }) => item.hidden);
|
|
|
+ },
|
|
|
+ set() {},
|
|
|
+ },
|
|
|
+ filterRules: {
|
|
|
+ get() {
|
|
|
+ return Object.fromEntries(
|
|
|
+ this.innerColumns
|
|
|
+ .filter(({ item }) => item.filter && !!item.filter.length)
|
|
|
+ .map(({ item }) => [item.key, item.filter])
|
|
|
+ );
|
|
|
+ },
|
|
|
+ set() {},
|
|
|
+ },
|
|
|
+ },
|
|
|
+ watch: {
|
|
|
+ filterRules: {
|
|
|
+ handler: function (newValue) {
|
|
|
+ function multiFilter(array, filters) {
|
|
|
+ const filterKeys = Object.keys(filters);
|
|
|
+ // filters all elements passing the criteria
|
|
|
+ return array.filter((item) => {
|
|
|
+ // dynamically validate all filter criteria
|
|
|
+ return filterKeys.every((key) => {
|
|
|
+ //ignore when the filter is empty Anne
|
|
|
+ if (!filters[key].length) return true;
|
|
|
+ return !!~filters[key].indexOf(item[key]);
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
+ this.filterState = JSON.stringify(newValue) !== "{}";
|
|
|
+ this.filterData = multiFilter(this.$props.value, newValue);
|
|
|
+ },
|
|
|
+ },
|
|
|
+ value:{
|
|
|
+ handler: function (newValue) {
|
|
|
+ if(this.value.length > 0){
|
|
|
+ this.$refs.superTable&& this.$refs.superTable.clearSelection();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ immediate: true,
|
|
|
+ deep:true
|
|
|
+ }
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ //
|
|
|
+ onSelectionChange(value) {
|
|
|
+ this.selectData = value;
|
|
|
+ this.$emit("row-select", this.selectData);
|
|
|
+ },
|
|
|
+ //
|
|
|
+ onRowClick(row, column, event) {
|
|
|
+ const { radio, checkbox } = this.$props;
|
|
|
+ // 单选
|
|
|
+ if (radio) {
|
|
|
+ this.$emit("row-select", [row]);
|
|
|
+ }
|
|
|
+ // 多选
|
|
|
+ if (checkbox) {
|
|
|
+ this.$refs.superTable.toggleRowSelection(
|
|
|
+ this.innerValue.find((item) => item.id === row.id)
|
|
|
+ );
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 宽度
|
|
|
+ onWidth(newProp, oldProp, column) {
|
|
|
+ this.innerColumns = this.innerColumns.map(({ item, attr }) => ({
|
|
|
+ attr,
|
|
|
+ item: {
|
|
|
+ ...item,
|
|
|
+ width: item.key === column.property ? newProp : item.width,
|
|
|
+ },
|
|
|
+ }));
|
|
|
+ if (this.$props.storageKey) {
|
|
|
+ localStorage.setItem(
|
|
|
+ this.$props.storageKey,
|
|
|
+ JSON.stringify(this.innerColumns)
|
|
|
+ );
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 冻结
|
|
|
+ onHide(prop) {
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.$refs.superTable.doLayout();
|
|
|
+ if (this.$props.storageKey) {
|
|
|
+ localStorage.setItem(
|
|
|
+ this.$props.storageKey,
|
|
|
+ JSON.stringify(this.innerColumns)
|
|
|
+ );
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ // 排序
|
|
|
+ onSort(prop) {
|
|
|
+ const { key, sort } = prop;
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.$refs.superTable.sort(key, sort);
|
|
|
+ this.$refs.superTable.doLayout();
|
|
|
+ if (this.$props.storageKey) {
|
|
|
+ localStorage.setItem(
|
|
|
+ this.$props.storageKey,
|
|
|
+ JSON.stringify(this.innerColumns)
|
|
|
+ );
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ // 冻结
|
|
|
+ onFreeze() {
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.$refs.superTable.doLayout();
|
|
|
+ if (this.$props.storageKey) {
|
|
|
+ localStorage.setItem(
|
|
|
+ this.$props.storageKey,
|
|
|
+ JSON.stringify(this.innerColumns)
|
|
|
+ );
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ // 过滤
|
|
|
+ onFilter() {
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.$refs.superTable.doLayout();
|
|
|
+ if (this.$props.storageKey) {
|
|
|
+ localStorage.setItem(
|
|
|
+ this.$props.storageKey,
|
|
|
+ JSON.stringify(this.innerColumns)
|
|
|
+ );
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ onFilters(value) {
|
|
|
+ const {
|
|
|
+ item: { key },
|
|
|
+ attr: { dictName },
|
|
|
+ } = value;
|
|
|
+ let dataList = [];
|
|
|
+ const dict = this.dict.type[dictName];
|
|
|
+ dataList = Array.from(
|
|
|
+ new Set(this.innerValue.map((item) => item[key]).filter((item) => item))
|
|
|
+ ).map((item) => ({
|
|
|
+ text: dictName
|
|
|
+ ? (dict.find((dictItem) => dictItem.value == item) || {}).label
|
|
|
+ : item,
|
|
|
+ value: item,
|
|
|
+ }));
|
|
|
+ return dataList;
|
|
|
+ },
|
|
|
+ // 继承el-table的Method
|
|
|
+ extendMethod() {
|
|
|
+ const refMethod = Object.entries(this.$refs["superTable"]);
|
|
|
+ for (const [key, value] of refMethod) {
|
|
|
+ if (!(key.includes("$") || key.includes("_"))) {
|
|
|
+ this[key] = value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ getSummaries({ columns, data }){
|
|
|
+
|
|
|
+ const means = [] // 合计
|
|
|
+ columns.forEach((column, columnIndex) => {
|
|
|
+ if (columnIndex === 0) {
|
|
|
+ means.push('合计')
|
|
|
+ } else {
|
|
|
+ const values = data.map(item => Number(item[column.property]));
|
|
|
+
|
|
|
+ let sumColumn = this.showColumns.filter(({item,attr}) => attr.isSummary && item.key === column.property);
|
|
|
+
|
|
|
+ // 合计
|
|
|
+ // if (!values.every(value => isNaN(value))) {
|
|
|
+ if (sumColumn.length) {
|
|
|
+ means[columnIndex] = values.reduce((prev, curr) => {
|
|
|
+ const value = Number(curr);
|
|
|
+ if (!isNaN(value)) {
|
|
|
+ return prev + curr;
|
|
|
+ } else {
|
|
|
+ return prev;
|
|
|
+ }
|
|
|
+ }, 0);
|
|
|
+ means[columnIndex] = means[columnIndex].toFixed(2);
|
|
|
+ } else {
|
|
|
+ means[columnIndex] = '';
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ // sums[index] = sums[index] && sums[index].toFixed(2); // 保留2位小数,解决小数合计列
|
|
|
+ return [means]
|
|
|
+
|
|
|
+ }
|
|
|
+ },
|
|
|
+ created() {},
|
|
|
+ mounted() {
|
|
|
+ this.extendMethod();
|
|
|
+ },
|
|
|
+ updated() {
|
|
|
+ this.$nextTick(()=>{
|
|
|
+ this.$refs.superTable.doLayout();
|
|
|
+ })
|
|
|
+ },
|
|
|
+ destroyed() {},
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<template>
|
|
|
+ <div class="el-super-table">
|
|
|
+ <ux-grid
|
|
|
+ border
|
|
|
+ row-key
|
|
|
+ use-virtual
|
|
|
+ show-overflow
|
|
|
+ keep-source
|
|
|
+ height="auto"
|
|
|
+ ref="superTable"
|
|
|
+ v-bind="$attrs"
|
|
|
+ v-on="$listeners"
|
|
|
+ :data="innerValue"
|
|
|
+ :show-summary="showSummary"
|
|
|
+ :summary-method="getSummaries"
|
|
|
+ :highlight-current-row="radio"
|
|
|
+ @row-click="onRowClick"
|
|
|
+ @header-dragend="onWidth"
|
|
|
+ @selection-change="onSelectionChange"
|
|
|
+ style="flex: 1"
|
|
|
+ >
|
|
|
+ <!-- <el-table
|
|
|
+ border
|
|
|
+ height="auto"
|
|
|
+ ref="superTable"
|
|
|
+ v-bind="$attrs"
|
|
|
+ v-on="$listeners"
|
|
|
+ :row-key="rowKey"
|
|
|
+ :data="innerValue"
|
|
|
+ :show-summary="showSummary"
|
|
|
+ :summary-method="getSummaries"
|
|
|
+ :highlight-current-row="radio"
|
|
|
+ @row-click="onRowClick"
|
|
|
+ @header-dragend="onWidth"
|
|
|
+ @selection-change="onSelectionChange"
|
|
|
+ style="flex: 1"
|
|
|
+ > -->
|
|
|
+ <!-- 多选 -->
|
|
|
+ <ux-table-column
|
|
|
+ v-if="checkbox"
|
|
|
+ :column-key="rowKey"
|
|
|
+ fixed
|
|
|
+ width="60"
|
|
|
+ align="center"
|
|
|
+ type="checkbox"
|
|
|
+ reserve-selection
|
|
|
+ >
|
|
|
+ </ux-table-column>
|
|
|
+ <!-- 序号 -->
|
|
|
+ <ux-table-column
|
|
|
+ v-if="index"
|
|
|
+ :resizable="false"
|
|
|
+ fixed
|
|
|
+ width="50"
|
|
|
+ title="序号"
|
|
|
+ align="center"
|
|
|
+ class="is-index"
|
|
|
+ type="index"
|
|
|
+ >
|
|
|
+ <!-- <template slot-scope="scope">
|
|
|
+ {{ scope.$index + 1 }}
|
|
|
+ </template> -->
|
|
|
+ </ux-table-column>
|
|
|
+ <ux-table-column
|
|
|
+ v-for="({ item, attr }, index) in showColumns"
|
|
|
+ :key="item.key + index"
|
|
|
+ :field="item.key"
|
|
|
+ :title="item.title"
|
|
|
+ :fixed="item.fixed"
|
|
|
+ :width="item.width || 200"
|
|
|
+ show-overflow-tooltip
|
|
|
+ >
|
|
|
+ <template slot="header" slot-scope="scope">
|
|
|
+ <template>
|
|
|
+ <span v-if="item.require" style="color: #ff4949">*</span>
|
|
|
+ <span
|
|
|
+ :style="{
|
|
|
+ color:
|
|
|
+ item.sort ||
|
|
|
+ item.fixed ||
|
|
|
+ (item.filter && !!item.filter.length)
|
|
|
+ ? '#1890ff'
|
|
|
+ : '',
|
|
|
+ }"
|
|
|
+ >
|
|
|
+ {{ item.title }}
|
|
|
+ </span>
|
|
|
+ <template>
|
|
|
+ <icon-sort
|
|
|
+ v-if="item.sortabled"
|
|
|
+ v-model="item.sort"
|
|
|
+ @sort="onSort(item)"
|
|
|
+ ></icon-sort>
|
|
|
+ <icon-freeze
|
|
|
+ v-if="item.fixedabled"
|
|
|
+ v-model="item.fixed"
|
|
|
+ @freeze="onFreeze"
|
|
|
+ ></icon-freeze>
|
|
|
+ <icon-filter
|
|
|
+ v-if="item.filterabled"
|
|
|
+ v-model="item.filter"
|
|
|
+ :filters="onFilters({ item, attr })"
|
|
|
+ @filter="onFilter"
|
|
|
+ ></icon-filter>
|
|
|
+ <icon-hide
|
|
|
+ v-if="item.hiddenabled"
|
|
|
+ v-model="item.hidden"
|
|
|
+ @hide="onHide"
|
|
|
+ ></icon-hide>
|
|
|
+ </template>
|
|
|
+ </template>
|
|
|
+ </template>
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <slot :name="item.key" v-bind="scope" :item="item" :attr="attr">
|
|
|
+ <template v-if="attr.is">
|
|
|
+ <component
|
|
|
+ v-if="attr.is === 'el-dict-tag'"
|
|
|
+ v-bind="attr"
|
|
|
+ :size="$attrs.size"
|
|
|
+ :value="scope.row[item.key]"
|
|
|
+ :options="dict.type[attr.dictName]"
|
|
|
+ ></component>
|
|
|
+ <component
|
|
|
+ v-else-if="attr.is === 'el-popover-select-v2'"
|
|
|
+ v-bind="attr"
|
|
|
+ v-model="scope.row[item.key]"
|
|
|
+ :title="item.title"
|
|
|
+ :size="$attrs.size"
|
|
|
+ :source.sync="scope.row"
|
|
|
+ >
|
|
|
+ </component>
|
|
|
+ <component
|
|
|
+ v-else-if="attr.is === 'el-popover-multiple-select-v2'"
|
|
|
+ v-bind="attr"
|
|
|
+ v-model="scope.row[item.key]"
|
|
|
+ :title="item.title"
|
|
|
+ :size="$attrs.size"
|
|
|
+ :source.sync="scope.row"
|
|
|
+ >
|
|
|
+ </component>
|
|
|
+ <component
|
|
|
+ v-else-if="attr.is === 'el-select'"
|
|
|
+ v-bind="attr"
|
|
|
+ v-model="scope.row[item.key]"
|
|
|
+ :size="$attrs.size"
|
|
|
+ >
|
|
|
+ <template>
|
|
|
+ <el-option
|
|
|
+ v-for="item in dict.type[attr.dictName]"
|
|
|
+ :key="item.value"
|
|
|
+ :label="item.label"
|
|
|
+ :value="item.value"
|
|
|
+ >
|
|
|
+ </el-option>
|
|
|
+ </template>
|
|
|
+ </component>
|
|
|
+ <component
|
|
|
+ v-else
|
|
|
+ v-bind="attr"
|
|
|
+ v-model="scope.row[item.key]"
|
|
|
+ :size="$attrs.size"
|
|
|
+ style="width: 100%"
|
|
|
+ >
|
|
|
+ </component
|
|
|
+ ></template>
|
|
|
+ <template v-else>
|
|
|
+ <component v-if="attr.formatter" is="span">{{
|
|
|
+ attr.formatter(scope.row)
|
|
|
+ }}</component>
|
|
|
+ <component v-else is="span">{{
|
|
|
+ scope.row[item.key] || "--"
|
|
|
+ }}</component>
|
|
|
+ </template>
|
|
|
+ </slot>
|
|
|
+ </template>
|
|
|
+ </ux-table-column>
|
|
|
+ <slot></slot>
|
|
|
+ <!-- </el-table> -->
|
|
|
+ </ux-grid>
|
|
|
+ <div
|
|
|
+ style="
|
|
|
+ height: 50px;
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ "
|
|
|
+ :style="{
|
|
|
+ height: checkbox || pagination ? '50px' : '0px',
|
|
|
+ }"
|
|
|
+ >
|
|
|
+ <div class="mr-4">
|
|
|
+ <!-- <template v-if="checkbox">
|
|
|
+ <el-button
|
|
|
+ v-if="selectState"
|
|
|
+ size="mini"
|
|
|
+ @click="selectState = !selectState"
|
|
|
+ >
|
|
|
+ 所有列
|
|
|
+ </el-button>
|
|
|
+ <el-button
|
|
|
+ v-else
|
|
|
+ :disabled="!selectData.length"
|
|
|
+ size="mini"
|
|
|
+ @click="selectState = !selectState"
|
|
|
+ >
|
|
|
+ 选择列
|
|
|
+ {{ selectData.length ? ` :${selectData.length}` : "" }}
|
|
|
+ </el-button>
|
|
|
+ </template> -->
|
|
|
+ <template v-if="convenitentOperation">
|
|
|
+ <button-hide v-model="innerColumns" @change="onHide"></button-hide>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+ <pagination
|
|
|
+ v-if="pagination"
|
|
|
+ v-show="!selectState"
|
|
|
+ :total="page.total"
|
|
|
+ :page.sync="page.pageNum"
|
|
|
+ :limit.sync="page.pageSize"
|
|
|
+ @pagination="$emit('pagination', { ...$event })"
|
|
|
+ style="height: 32px; padding: 0 !important; flex: 1; overflow-x: auto"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.el-super-table {
|
|
|
+ position: relative;
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ overflow: auto;
|
|
|
+}
|
|
|
+::v-deep.el-super-table .el-table__header .cell {
|
|
|
+ word-break: keep-all;
|
|
|
+ white-space: nowrap;
|
|
|
+ .icon-sort {
|
|
|
+ display: none;
|
|
|
+ }
|
|
|
+ &:hover .icon-sort {
|
|
|
+ display: inline-block;
|
|
|
+ }
|
|
|
+ .icon-freeze {
|
|
|
+ display: none;
|
|
|
+ }
|
|
|
+ &:hover .icon-freeze {
|
|
|
+ display: inline-block;
|
|
|
+ }
|
|
|
+ .icon-filter {
|
|
|
+ display: none;
|
|
|
+ }
|
|
|
+ &:hover .icon-filter {
|
|
|
+ display: inline-block;
|
|
|
+ }
|
|
|
+ .icon-hide {
|
|
|
+ display: none;
|
|
|
+ }
|
|
|
+ &:hover .icon-hide {
|
|
|
+ display: inline-block;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|