You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
shop-admin/src/components/TableList.vue

622 lines
21 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<component :is="render" />
</template>
<script setup lang="jsx">
import SortableTable from './SortableTable.vue';
import { ElTable, ElTableColumn } from 'element-plus/es/components/table/index';
import 'element-plus/es/components/table/style/css';
const props = defineProps({
code: {
type: String,
default: '',
},
title: {
type: String,
default: '',
},
sortable: {
type: Boolean,
default: false,
},
config: {
type: Object,
required: true,
},
operation: {
type: Array,
default() {
return ['create', 'remove', 'search'];
},
},
total: {
type: Number,
default: 0,
},
});
const emits = defineEmits([
'create',
'save',
'remove',
'search',
'select',
'selectAll',
'selectionChange',
'currentChange',
'reset',
'template',
'import',
'export',
]);
const attrs = useAttrs();
const slots = useSlots();
const { proxy } = getCurrentInstance();
const store = useStore();
// 获取只包含对象基础属性的副本
const getObjectProps = (obj) => {
return _.clone(Object.fromEntries(Object.entries(obj).filter((entry) => typeof entry[1] !== 'object')));
};
// 初始化设置
const settings = computed(() => store.state.local.listSettings);
const handleInit = (clear = false) => {
// 修改过表格配置源码需要清空本地缓存配置
if (props.code && clear) {
store.commit('local/setListSettings', {
...unref(settings),
[props.code]: null,
});
console.info('[list] reset:' + props.code);
}
let config = null;
if (
!props.code ||
!unref(settings) ||
!unref(settings)[props.code] ||
!(unref(settings)[props.code] instanceof Array)
) {
// 如果找不到本地缓存配置则重新初始化
config = props.config.columns
.map((item, index) => {
return {
_sort: index + 1,
_index: index,
_visible: true,
width: '',
fixed: false,
...getObjectProps(item),
};
})
.sort((prev, next) => prev._sort - next._sort);
if (props.code) {
// 保存本地缓存配置
store.commit('local/setListSettings', {
...unref(settings),
[props.code]: config,
});
}
} else {
config = unref(settings)[props.code];
}
// 配置源码修改过的标识
let needReset = false;
config = props.config.columns.map((item, index) => {
//
let old = config.find((temp) => temp._index === index);
if (
old &&
Object.entries(getObjectProps(item)).every((entry) => {
return old[entry[0]] === entry[1];
})
) {
item = {
...old,
...item,
};
} else {
needReset = true;
}
return item;
});
return needReset ? handleInit(true) : config;
};
const setting = ref(handleInit());
console.info('[list] setting:' + props.code, unref(setting));
// 设置窗口
let rollback = ref([]);
const settingVisible = ref(false);
const handleSetting = () => {
rollback.value = _.cloneDeep(unref(setting));
settingVisible.value = true;
};
const handleCancel = () => {
settingVisible.value = false;
};
const handleSave = () => {
rollback.value = null;
store.commit('local/setListSettings', {
...unref(settings),
[props.code]: unref(setting).map((item) => getObjectProps(item)),
});
settingVisible.value = false;
};
const handleCloseSetting = () => {
if (unref(rollback)) {
setting.value = unref(rollback);
}
};
// 导入窗口
const refsUpload = ref(null);
const importVisible = ref(false);
const handleShowImport = () => {
importVisible.value = true;
};
const handleCancelImport = () => {
importVisible.value = false;
};
const handleSaveImport = (file) => {
const files = unref(refsUpload).refsUpload.uploadFiles;
if (!files.length) {
proxy.$baseMessage('请上传要导入的文件', 'error');
} else {
if (file === false) {
unref(refsUpload).refsUpload.submit();
} else {
handleImport(file);
importVisible.value = false;
}
}
};
// 选中数据
const refTable = ref(null);
const checked = ref(null);
const selection = ref([]);
const handleSelect = (arr, row) => {
setTimeout(() => {
let res = [],
selected = arr.indexOf(row) !== -1;
(function deep(item) {
res.push(item);
(item.children || []).forEach(deep);
})(row);
res.forEach((item) => {
toggleRowExpansion(item, selected);
toggleRowSelection(item, selected);
});
}, 0);
emits('select', arr, row);
};
// 分页查询
const pages = computed(() => store.state.local.listPage);
const search = ref(
props.code
? store.state.local.listPage[props.code]
: {
pageIndex: 1,
length: 10,
}
);
const resetPage = (
page = {
pageIndex: 1,
length: 10,
}
) => {
if (props.code) {
store.commit('local/setListPage', {
...unref(pages),
[props.code]: page,
});
}
search.value = page;
};
if (!unref(search)) {
resetPage();
}
watch(
() => props.total,
(value) => {
if (!value) {
resetPage();
}
}
);
// 事件封装
const handleSearch = () => {
emits('search', unref(search));
};
const handleReset = () => {
emits('reset');
search.value = { pageIndex: 1, length: 10 };
handleSearch();
};
const handleTemplate = () => {
emits('template');
};
const handleImport = (files) => {
emits('import', files);
};
const handleExport = () => {
emits('export');
};
const handleCreate = () => {
emits('create', unref(checked));
};
const handleUpdate = () => {
emits('save', unref(selection));
};
const handleRemove = () => {
emits('remove', unref(selection));
};
const listeners = {
onSelect: handleSelect,
onSelectAll: (arr) => {
setTimeout(() => {
attrs.data.forEach((item) => handleSelect(arr, item));
}, 0);
emits('selectAll', arr);
},
onSelectionChange: (arr) => {
selection.value = arr;
emits('selectionChange', arr);
},
onCurrentChange: (row) => {
checked.value = row;
emits('currentChange', row);
},
};
// 自动查询
watch(
search,
(value) => {
if (value && props.code) {
resetPage(value);
}
handleSearch();
},
{ deep: true, immediate: props.config.autoSearch !== false }
);
// 代理原生函数
const handleProxy = (fnName, args) => {
return unref(refTable)[fnName]?.apply(unref(refTable), args);
};
const clearSelection = function () {
return handleProxy('clearSelection', arguments);
};
const toggleRowSelection = function () {
return handleProxy('toggleRowSelection', arguments);
};
const toggleAllSelection = function () {
return handleProxy('toggleAllSelection', arguments);
};
const toggleRowExpansion = function () {
return handleProxy('toggleRowExpansion', arguments);
};
const setCurrentRow = function () {
return handleProxy('setCurrentRow', arguments);
};
const clearSort = function () {
return handleProxy('clearSort', arguments);
};
const clearFilter = function () {
return handleProxy('clearFilter', arguments);
};
const doLayout = function () {
return handleProxy('doLayout', arguments);
};
const sort = function () {
return handleProxy('sort', arguments);
};
// 抛出
defineExpose({
search: handleSearch,
selection,
refTable,
clearSelection,
toggleRowSelection,
toggleAllSelection,
toggleRowExpansion,
setCurrentRow,
clearSort,
clearFilter,
doLayout,
sort,
});
const Component = props.sortable ? SortableTable : ElTable;
const render = () => (
<div class="common-list">
{slots.search ? <div class="search-box">{slots.search()}</div> : ''}
<div class="operation-box">
{props.operation.includes('create') ? (
<ElButton type="primary" onClick={() => handleCreate()}>
<ElIcon name="Plus" />
<span>新增{props.title}</span>
</ElButton>
) : (
''
)}
{props.operation.includes('save') ? (
<ElButton type="primary" onClick={() => handleUpdate()}>
<ElIcon name="Tickets" />
<span>批量保存</span>
</ElButton>
) : (
''
)}
{props.operation.includes('remove') ? (
<ElButton type="danger" onClick={() => handleRemove()}>
<ElIcon name="Delete" />
<span>批量移除</span>
</ElButton>
) : (
''
)}
{props.operation.includes('search') ? (
<ElButton type="success" onClick={() => handleSearch()}>
<ElIcon name="Search" />
<span>查询</span>
</ElButton>
) : (
''
)}
{props.operation.includes('search') ? (
<ElButton onClick={() => handleReset()}>
<ElIcon name="MagicStick" />
<span>重置</span>
</ElButton>
) : (
''
)}
{props.operation.includes('import') ? (
<ElButton type="warning" onClick={() => handleShowImport()}>
<ElIcon name="Document" />
<span>模板导入</span>
</ElButton>
) : (
''
)}
{props.operation.includes('export') ? (
<ElButton type="warning" onClick={() => handleExport()}>
<ElIcon name="Download" />
<span>导出</span>
</ElButton>
) : (
''
)}
{slots.operation?.()}
{props.operation.includes('import') ? (
<ElDialog
title={'模板导入 - ' + props.title}
v-model={importVisible.value}
width="765px"
v-slots={{
footer: () => {
return (
<div>
<ElButton type="danger" onClick={() => handleCancelImport()}>
取消
</ElButton>
<ElButton type="primary" onClick={() => handleSaveImport(false)}>
导入
</ElButton>
</div>
);
},
}}
>
<ElButton icon="Document" type="warning" onClick={() => handleTemplate()}>
下载模板
</ElButton>
<br />
<br />
<ElUpload
ref={refsUpload}
auto-upload={false}
before-upload={(e) => {
handleSaveImport(e);
return false;
}}
/>
</ElDialog>
) : (
''
)}
{props.code ? (
<ElButton class="setting-btn" icon="setting" type="text" onClick={() => handleSetting()}>
设置
</ElButton>
) : (
''
)}
<ElDialog
title={'表格设置 - ' + props.code}
v-model={settingVisible.value}
width="765px"
onClose={() => handleCloseSetting()}
v-slots={{
footer: () => {
return (
<div>
<ElButton type="danger" onClick={() => handleCancel()}>
取消
</ElButton>
<ElButton type="primary" onClick={() => handleSave()}>
保存
</ElButton>
</div>
);
},
}}
>
<p>修改后如果显示异常刷新页面即可恢复正常</p>
<br />
<ElTable border stripe highlightCurrentRow data={setting.value} height="50vh">
<ElTableColumn
headerAlign="center"
align="center"
label="排序"
prop="_sort"
width="72px"
v-slots={{
default: ({ row }) => (
<ElInputNumber controls={false} v-model={row._sort} style="width:48px;" />
),
}}
/>
<ElTableColumn
headerAlign="center"
align="center"
label="列名"
prop="label"
width="200px"
v-slots={{
default: ({ row }) =>
row.type === 'index' ? (
'序号'
) : row.type === 'selection' ? (
'选择框'
) : (
<ElInput
v-model={row.label}
placeholder={props.config.columns[row._index].label}
/>
),
}}
/>
<ElTableColumn
headerAlign="center"
align="center"
label="宽度"
prop="width"
width="140px"
v-slots={{
default: ({ row }) => <ElInput v-model={row.width} style="width:104px;" />,
}}
/>
<ElTableColumn
headerAlign="center"
align="center"
label="固定"
prop="fixed"
width="240px"
v-slots={{
default: ({ row }) => (
<ElRadioGroup
v-model={row.fixed}
button
opts={[
{ label: '左侧', value: 'left' },
{ label: '取消', value: false },
{ label: '右侧', value: 'right' },
]}
></ElRadioGroup>
),
}}
/>
<ElTableColumn
headerAlign="center"
align="center"
label="显示"
prop="_visible"
width="72px"
v-slots={{
default: ({ row }) => <ElSwitch v-model={row._visible} />,
}}
/>
</ElTable>
</ElDialog>
</div>
<div class="content-box">
<Component
ref={refTable}
border
stripe
highlightCurrentRow={false}
rowKey="id"
height="100%"
{...listeners}
{...attrs}
{...props.config.table}
v-slots={{
empty: () => (
<div class="empty-content">
<ElEmpty description="没有查询到符合条件的数据" />
</div>
),
...props.config.table?.slots,
}}
>
{unref(setting).map((col) =>
col._visible ? (
<ElTableColumn
showOverflowTooltip={!col.slots?.default}
headerAlign="center"
align="center"
selectable={(row) => !row.baseData}
v-slots={{ ...col.slots }}
{...col}
></ElTableColumn>
) : (
''
)
)}
</Component>
</div>
<div class="pagination-box">
{props.config.page === false ? (
''
) : (
<ElPagination
background
hide-on-single-page={false}
page-sizes={props.config.page?.sizes || [10, 15, 20, 30, 50, 100]}
layout={props.config.page?.layout || 'total, sizes, prev, pager, next, jumper'}
total={props.total}
v-model:current-page={unref(search).pageIndex}
v-model:page-size={unref(search).length}
/>
)}
</div>
</div>
);
</script>
<style lang="less" scoped>
.common-list {
width: 100%;
height: 100%;
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
:deep(.search-box) {
border-radius: 5px;
}
:deep(.operation-box) {
border-radius: 5px;
padding-bottom: 10px;
display: flex;
align-items: center;
.setting-btn {
margin-left: auto;
}
}
:deep(.content-box) {
flex: 1;
overflow: hidden;
margin-bottom: 10px;
display: flex;
flex-direction: column;
.el-table {
max-height: 100%;
flex: 1;
}
}
:deep(.pagination-box) {
display: flex;
justify-content: center;
.el-pagination {
margin: 0;
}
}
}
</style>