优化更新

This commit is contained in:
2026-01-21 14:11:42 +08:00
parent 431d2c7071
commit 638c846aed
3 changed files with 602 additions and 562 deletions
+92 -92
View File
@@ -1,231 +1,231 @@
import request from "@/utils/request"; import request from '@/utils/request'
export default { export default {
version: { version: {
url: `system/index/version`, url: `system/index/version`,
name: "获取最新版本号", name: '获取最新版本号',
get: async function () { get: async function () {
return await request.get(this.url); return await request.get(this.url)
}, },
}, },
clearcache: { clearcache: {
url: `system/index/clearcache`, url: `system/index/clearcache`,
name: "清除缓存", name: '清除缓存',
post: async function () { post: async function () {
return await request.post(this.url); return await request.post(this.url)
}, },
}, },
info: { info: {
url: `system/index/info`, url: `system/index/info`,
name: "系统信息", name: '系统信息',
get: function (data) { get: function (data) {
return request.get(this.url, data); return request.get(this.url, data)
}, },
}, },
setting: { setting: {
list: { list: {
url: `system/setting/index`, url: `system/setting/index`,
name: "获取配置信息", name: '获取配置信息',
get: function (params) { get: function (params) {
return request.get(this.url, params); return request.get(this.url, params)
}, },
}, },
fields: { fields: {
url: `system/setting/fields`, url: `system/setting/fields`,
name: "获取配置字段", name: '获取配置字段',
get: async function (params) { get: async function (params) {
return await request.get(this.url, params); return await request.get(this.url, params)
}, },
}, },
add: { add: {
url: `system/setting/add`, url: `system/setting/add`,
name: "保存配置信息", name: '保存配置信息',
post: function (data) { post: function (data) {
return request.post(this.url, data); return request.post(this.url, data)
}, },
}, },
edit: { edit: {
url: `system/setting/edit`, url: `system/setting/edit`,
name: "编辑配置信息", name: '编辑配置信息',
post: function (data) { post: function (data) {
return request.put(this.url, data); return request.put(this.url, data)
}, },
}, },
save: { save: {
url: `system/setting/save`, url: `system/setting/save`,
name: "保存配置信息", name: '保存配置信息',
post: function (data) { post: function (data) {
return request.put(this.url, data); return request.put(this.url, data)
}, },
}, },
}, },
dictionary: { dictionary: {
category: { category: {
url: `system/dict/category`, url: `system/dict/category`,
name: "获取字典树", name: '获取字典树',
get: async function (params) { get: async function (params) {
return await request.get(this.url, params); return await request.get(this.url, params)
}, },
}, },
editcate: { editcate: {
url: `system/dict/editcate`, url: `system/dict/editcate`,
name: "编辑字典树", name: '编辑字典树',
post: async function (data = {}) { post: async function (data = {}) {
return await request.put(this.url, data); return await request.put(this.url, data)
}, },
}, },
addcate: { addcate: {
url: `system/dict/addcate`, url: `system/dict/addcate`,
name: "添加字典树", name: '添加字典树',
post: async function (data = {}) { post: async function (data = {}) {
return await request.post(this.url, data); return await request.post(this.url, data)
}, },
}, },
delCate: { delCate: {
url: `system/dict/deletecate`, url: `system/dict/deletecate`,
name: "删除字典树", name: '删除字典树',
post: async function (data = {}) { post: async function (data = {}) {
return await request.delete(this.url, data); return await request.delete(this.url, data)
}, },
}, },
list: { list: {
url: `system/dict/lists`, url: `system/dict/lists`,
name: "字典明细", name: '字典明细',
get: async function (params) { get: async function (params) {
return await request.get(this.url, params); return await request.get(this.url, params)
}, },
}, },
get: { get: {
url: `system/dict/detail`, url: `system/dict/detail`,
name: "获取字典数据", name: '获取字典数据',
get: async function (params) { get: async function (params) {
return await request.get(this.url, params); return await request.get(this.url, params)
}, },
}, },
edit: { edit: {
url: `system/dict/edit`, url: `system/dict/edit`,
name: "编辑字典明细", name: '编辑字典明细',
post: async function (data = {}) { post: async function (data = {}) {
return await request.put(this.url, data); return await request.put(this.url, data)
}, },
}, },
add: { add: {
url: `system/dict/add`, url: `system/dict/add`,
name: "添加字典明细", name: '添加字典明细',
post: async function (data = {}) { post: async function (data = {}) {
return await request.post(this.url, data); return await request.post(this.url, data)
}, },
}, },
delete: { delete: {
url: `system/dict/delete`, url: `system/dict/delete`,
name: "删除字典明细", name: '删除字典明细',
post: async function (data = {}) { post: async function (data = {}) {
return await request.delete(this.url, data); return await request.delete(this.url, data)
}, },
}, },
detail: { detail: {
url: `system/dict/detail`, url: `system/dict/detail`,
name: "字典明细", name: '字典明细',
get: async function (params) { get: async function (params) {
return await request.get(this.url, params); return await request.get(this.url, params)
}, },
}, },
alldic: { alldic: {
url: `system/dict/all`, url: `system/dict/all`,
name: "全部字典", name: '全部字典',
get: async function (params) { get: async function (params) {
return await request.get(this.url, params); return await request.get(this.url, params)
}, },
}, },
}, },
area: { area: {
list: { list: {
url: `system/area/index`, url: `system/area/index`,
name: "地区列表", name: '地区列表',
get: async function (params) { get: async function (params) {
return await request.get(this.url, params); return await request.get(this.url, { params })
}, },
}, },
add: { add: {
url: `system/area/add`, url: `system/area/add`,
name: "地区添加", name: '地区添加',
post: async function (params) { post: async function (params) {
return await request.post(this.url, params); return await request.post(this.url, params)
}, },
}, },
edit: { edit: {
url: `system/area/edit`, url: `system/area/edit`,
name: "地区编辑", name: '地区编辑',
post: async function (params) { post: async function (params) {
return await request.put(this.url, params); return await request.put(this.url, params)
}, },
}, },
}, },
app: { app: {
list: { list: {
url: `system/app/list`, url: `system/app/list`,
name: "应用列表", name: '应用列表',
get: async function () { get: async function () {
return await request.get(this.url); return await request.get(this.url)
}, },
}, },
}, },
client: { client: {
list: { list: {
url: `system/client/index`, url: `system/client/index`,
name: "客户端列表", name: '客户端列表',
get: async function (params) { get: async function (params) {
return await request.get(this.url, params); return await request.get(this.url, params)
}, },
}, },
add: { add: {
url: `system/client/add`, url: `system/client/add`,
name: "客户端添加", name: '客户端添加',
post: async function (params) { post: async function (params) {
return await request.post(this.url, params); return await request.post(this.url, params)
}, },
}, },
edit: { edit: {
url: `system/client/edit`, url: `system/client/edit`,
name: "客户端编辑", name: '客户端编辑',
post: async function (params) { post: async function (params) {
return await request.put(this.url, params); return await request.put(this.url, params)
}, },
}, },
delete: { delete: {
url: `system/client/delete`, url: `system/client/delete`,
name: "客户端删除", name: '客户端删除',
post: async function (params) { post: async function (params) {
return await request.delete(this.url, params); return await request.delete(this.url, params)
}, },
}, },
menu: { menu: {
list: { list: {
url: `system/menu/index`, url: `system/menu/index`,
name: "客户端菜单列表", name: '客户端菜单列表',
get: async function (params) { get: async function (params) {
return await request.get(this.url, params); return await request.get(this.url, params)
}, },
}, },
add: { add: {
url: `system/menu/add`, url: `system/menu/add`,
name: "客户端菜单添加", name: '客户端菜单添加',
post: async function (params) { post: async function (params) {
return await request.post(this.url, params); return await request.post(this.url, params)
}, },
}, },
edit: { edit: {
url: `system/menu/edit`, url: `system/menu/edit`,
name: "客户端菜单编辑", name: '客户端菜单编辑',
post: async function (params) { post: async function (params) {
return await request.put(this.url, params); return await request.put(this.url, params)
}, },
}, },
delete: { delete: {
url: `system/menu/delete`, url: `system/menu/delete`,
name: "客户端菜单删除", name: '客户端菜单删除',
post: async function (params) { post: async function (params) {
return await request.delete(this.url, params); return await request.delete(this.url, params)
}, },
}, },
}, },
@@ -233,109 +233,109 @@ export default {
log: { log: {
list: { list: {
url: `system/log/index`, url: `system/log/index`,
name: "日志列表", name: '日志列表',
get: async function (params) { get: async function (params) {
return await request.get(this.url, params); return await request.get(this.url, params)
}, },
}, },
my: { my: {
url: `system/log/my`, url: `system/log/my`,
name: "我的日志", name: '我的日志',
get: async function (params) { get: async function (params) {
return await request.get(this.url, params); return await request.get(this.url, params)
}, },
}, },
delete: { delete: {
url: `system/log/delete`, url: `system/log/delete`,
name: "日志删除", name: '日志删除',
post: async function (params) { post: async function (params) {
return await request.delete(this.url, params); return await request.delete(this.url, params)
}, },
}, },
}, },
tasks: { tasks: {
list: { list: {
url: `system/tasks/index`, url: `system/tasks/index`,
name: "任务列表", name: '任务列表',
get: async function (params) { get: async function (params) {
return await request.get(this.url, params); return await request.get(this.url, params)
}, },
}, },
delete: { delete: {
url: `system/tasks/delete`, url: `system/tasks/delete`,
name: "任务删除", name: '任务删除',
post: async function (params) { post: async function (params) {
return await request.delete(this.url, params); return await request.delete(this.url, params)
}, },
}, },
}, },
crontab: { crontab: {
list: { list: {
url: `system/crontab/index`, url: `system/crontab/index`,
name: "定时任务列表", name: '定时任务列表',
get: async function (params) { get: async function (params) {
return await request.get(this.url, params); return await request.get(this.url, params)
}, },
}, },
add: { add: {
url: `system/crontab/add`, url: `system/crontab/add`,
name: "定时任务添加", name: '定时任务添加',
post: async function (params) { post: async function (params) {
return await request.post(this.url, params); return await request.post(this.url, params)
}, },
}, },
edit: { edit: {
url: `system/crontab/edit`, url: `system/crontab/edit`,
name: "定时任务编辑", name: '定时任务编辑',
post: async function (params) { post: async function (params) {
return await request.put(this.url, params); return await request.put(this.url, params)
}, },
}, },
delete: { delete: {
url: `system/crontab/delete`, url: `system/crontab/delete`,
name: "定时任务删除", name: '定时任务删除',
post: async function (params) { post: async function (params) {
return await request.delete(this.url, params); return await request.delete(this.url, params)
}, },
}, },
log: { log: {
url: `system/crontab/log`, url: `system/crontab/log`,
name: "定时任务日志", name: '定时任务日志',
get: async function (params) { get: async function (params) {
return await request.get(this.url, params); return await request.get(this.url, params)
}, },
}, },
reload: { reload: {
url: `system/crontab/reload`, url: `system/crontab/reload`,
name: "定时任务重载", name: '定时任务重载',
post: async function (params) { post: async function (params) {
return await request.put(this.url, params); return await request.put(this.url, params)
}, },
}, },
}, },
modules: { modules: {
list: { list: {
url: `system/modules/index`, url: `system/modules/index`,
name: "模块列表", name: '模块列表',
get: async function (params) { get: async function (params) {
return await request.get(this.url, params); return await request.get(this.url, params)
}, },
}, },
update: { update: {
url: `system/modules/update`, url: `system/modules/update`,
name: "更新模块", name: '更新模块',
post: async function (params) { post: async function (params) {
return await request.post(this.url, params); return await request.post(this.url, params)
}, },
}, },
}, },
sms: { sms: {
count: { count: {
url: `system/sms/count`, url: `system/sms/count`,
name: "短信发送统计", name: '短信发送统计',
get: async function (params) { get: async function (params) {
return await request.get(this.url, params); return await request.get(this.url, params)
}, },
}, },
}, },
}; }
+472 -401
View File
@@ -1,76 +1,126 @@
<template> <template>
<div class="sc-table-container" ref="containerRef"> <div class="sc-table">
<!-- 顶部工具操作块 --> <!-- 工具栏 -->
<div class="sc-table-toolbar"> <div v-if="showToolbar" class="sc-table-tool">
<div class="sc-table-toolbar-left"> <div class="tool-left">
<!-- 自定义操作按钮插槽 --> <!-- 左侧工具栏插槽 -->
<slot name="toolbar-left"></slot> <slot name="toolLeft"></slot>
</div> </div>
<div class="sc-table-toolbar-right"> <div class="tool-right">
<!-- 列设置 --> <!-- 右侧工具栏插槽 -->
<a-dropdown v-if="showColumnSetting" :trigger="['click']"> <slot name="toolRight"></slot>
<a-button size="small" type="text">
<SettingOutlined />
</a-button>
<template #overlay>
<div class="sc-table-column-setting">
<a-checkbox-group :model-value="visibleColumns" @change="handleColumnChange">
<a-checkbox v-for="column in columns" :key="column.key || column.dataIndex"
:value="column.key || column.dataIndex">
{{ column.title }}
</a-checkbox>
</a-checkbox-group>
</div>
</template>
</a-dropdown>
<!-- 表格尺寸设置 -->
<a-dropdown v-if="showSizeSetting" :trigger="['click']">
<a-button size="small" type="text">
<ColumnHeightOutlined />
</a-button>
<template #overlay>
<a-menu @click="handleSizeChange">
<a-menu-item key="default">
<a-radio :checked="tableSize === 'default'">默认</a-radio>
</a-menu-item>
<a-menu-item key="middle">
<a-radio :checked="tableSize === 'middle'">中等</a-radio>
</a-menu-item>
<a-menu-item key="small">
<a-radio :checked="tableSize === 'small'">紧凑</a-radio>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
<!-- 刷新按钮 --> <!-- 刷新按钮 -->
<a-button size="small" type="text" @click="handleRefresh"> <a-tooltip v-if="showRefresh" title="刷新">
<ReloadOutlined /> <a-button shape="circle" :loading="loading" @click="handleRefresh">
<template #icon>
<SyncOutlined />
</template>
</a-button> </a-button>
</a-tooltip>
<!-- 表格设置按钮 -->
<a-tooltip v-if="showColumnSetting" title="表格设置">
<a-popover v-model:open="tableSettingVisible" placement="bottomRight" trigger="click" :width="240">
<template #content>
<div class="table-setting">
<div class="table-setting-header">
<span>表格设置</span>
</div>
<div class="table-setting-body">
<!-- 边框设置 -->
<div class="setting-item">
<span class="setting-label">显示边框</span>
<a-switch v-model:checked="tableSettings.bordered" size="small" />
</div>
<!-- 表格大小 -->
<div class="setting-item">
<span class="setting-label">表格大小</span>
<a-radio-group v-model:value="tableSettings.size" size="small"
button-style="solid">
<a-radio-button value="small"></a-radio-button>
<a-radio-button value="middle"></a-radio-button>
<a-radio-button value="large"></a-radio-button>
</a-radio-group>
</div>
</div>
</div>
</template>
<a-button shape="circle">
<template #icon>
<TableOutlined />
</template>
</a-button>
</a-popover>
</a-tooltip>
<!-- 列设置按钮 -->
<a-tooltip v-if="showColumnSetting" title="列设置">
<a-popover v-model:open="columnSettingVisible" placement="bottomRight" trigger="click">
<template #content>
<div class="column-setting">
<div class="column-setting-header">
<span>显示与排序</span>
</div>
<div class="column-setting-list">
<div v-for="(colKey, index) in sortedColumns" :key="colKey"
class="column-setting-item" :class="{ dragging: draggingIndex === index }"
draggable="true" @dragstart="handleDragStart(index, $event)"
@dragover="handleDragOver(index, $event)" @dragend="handleDragEnd"
@drop="handleDrop(index)">
<HolderOutlined class="drag-handle" />
<a-checkbox :checked="visibleColumns.includes(colKey)"
@change="(e) => toggleColumn(colKey, e.target.checked)">
{{ getColumnTitle(colKey) }}
</a-checkbox>
</div>
</div>
</div>
</template>
<a-button shape="circle">
<template #icon>
<HolderOutlined />
</template>
</a-button>
</a-popover>
</a-tooltip>
</div> </div>
</div> </div>
<!-- 中间表格内容区域 --> <!-- 表格内容 -->
<div class="sc-table-content"> <div class="sc-table-content" ref="tableContent">
<a-table ref="tableRef" :columns="processedColumns" :data-source="dataSource" :loading="loading" <a-table :columns="tableColumns" :data-source="dataSource" :loading="loading" :pagination="pagination"
:row-key="rowKey" :row-selection="rowSelectionConfig" :scroll="scrollConfig" :size="tableSize" :row-key="rowKey" :row-selection="rowSelection" :scroll="scroll" :bordered="tableSettings.bordered"
:custom-row="handleRow" @change="handleTableChange" @row-click="handleRowClick" :size="tableSettings.size" :show-header="showHeader" :locale="locale" @change="handleTableChange"
@row-dblclick="handleRowDblClick"> @resizeColumn="handleResizeColumn">
<!-- 自定义列插槽 --> <!-- 自定义单元格内容 -->
<!-- 操作列插槽 --> <template #bodyCell="{ text, record, index, column }">
<template v-if="showActionColumn" #action="scope"> <!-- 序号列 -->
<slot name="action" v-bind="scope"></slot> <template v-if="column.dataIndex === '_index'">
{{ getTableIndex(index) }}
</template>
<!-- 自定义插槽 -->
<template v-else-if="column.slot">
<slot :name="column.slot || column.dataIndex" :text="text" :record="record" :index="index"
:column="column"></slot>
</template>
<!-- 操作列 -->
<template v-else-if="column.dataIndex === '_action'">
<a-space>
<a-button v-for="(action, idx) in actions" :key="idx" type="link" size="small"
:disabled="action.disabled" @click="handleAction(action, record, index)">
{{ action.label }}
</a-button>
</a-space>
</template>
</template> </template>
<!-- 展开行插槽 --> <!-- 空状态 -->
<template #expandedRowRender="scope"> <template #emptyText>
<slot name="expandedRowRender" v-bind="scope"></slot> <a-empty :image="Empty.PRESENTED_IMAGE_SIMPLE" :description="emptyText">
</template> <slot name="empty">
<span>{{ emptyText }}</span>
<!-- 空数据提示 --> </slot>
<template #empty> </a-empty>
<a-empty :description="emptyText" />
</template> </template>
</a-table> </a-table>
</div> </div>
@@ -78,444 +128,465 @@
</template> </template>
<script setup> <script setup>
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue' import { ref, computed, watch, reactive, useTemplateRef, onMounted } from 'vue'
import { Empty } from 'ant-design-vue'
import { SyncOutlined, HolderOutlined, TableOutlined } from '@ant-design/icons-vue'
defineOptions({ defineOptions({
name: 'scTable', name: 'scTable',
}) })
/**
* 通用表格组件
* 基于 Ant Design Vue Table 封装,提供增强功能和便捷使用方式
*/
// 组件属性定义
const props = defineProps({ const props = defineProps({
// 表格列配置 // 数据源
dataSource: {
type: Array,
default: () => [],
},
// 列配置
columns: { columns: {
type: Array, type: Array,
default: () => [], default: () => [],
required: true, required: true,
}, },
// 行的唯一标识
// 表格数据源 rowKey: {
dataSource: { type: [String, Function],
type: Array, default: 'id',
default: () => [],
}, },
// 加载状态 // 加载状态
loading: { loading: {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
// 行唯一标识
rowKey: {
type: [String, Function],
default: 'id',
},
// 分页配置 // 分页配置
pagination: { pagination: {
type: Object, type: [Object, Boolean],
default: () => ({ default: () => ({
current: 1, current: 1,
pageSize: 10, pageSize: 20,
total: 0, total: 0,
showSizeChanger: true, showSizeChanger: true,
showQuickJumper: true, showTotal: (total) => `${total}`,
showTotal: (total) => `${total} 条数据`, pageSizeOptions: ['20', '50', '100', '200'],
pageSizeOptions: ['10', '20', '50', '100'],
}), }),
}, },
// 行选择配置 // 行选择配置
rowSelection: { rowSelection: {
type: Object, type: Object,
default: null, default: null,
}, },
// 表格大小
// 表格尺寸
size: { size: {
type: String, type: String,
default: 'default', default: 'middle', // large, middle, small
validator: (value) => ['default', 'middle', 'small'].includes(value),
}, },
// 是否显示边框
// 滚动配置 bordered: {
scroll: {
type: Object,
default: () => ({}),
},
// 空数据提示文本
emptyText: {
type: String,
default: '暂无数据',
},
// 是否显示操作列
showActionColumn: {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
// 是否显示表头
// 操作列配置 showHeader: {
actionColumn: { type: Boolean,
type: Object, default: true,
default: () => ({ },
title: '操作', // 本地化配置
key: 'action', locale: {
width: 150, type: Object,
fixed: 'right', default: () => ({}),
}), },
// 是否显示序号列
showIndex: {
type: Boolean,
default: false,
},
// 序号列宽度
indexColumnWidth: {
type: Number,
default: 100,
},
// 序号列标题
indexTitle: {
type: String,
default: '序号',
},
// 是否显示操作列
showAction: {
type: Boolean,
default: false,
},
// 操作列配置
actions: {
type: Array,
default: () => [],
},
// 操作列宽度
actionColumnWidth: {
type: Number,
default: 200,
},
// 操作列标题
actionTitle: {
type: String,
default: '操作',
},
// 是否显示工具栏
showToolbar: {
type: Boolean,
default: true,
},
// 是否显示刷新按钮
showRefresh: {
type: Boolean,
default: true,
}, },
// 是否显示列设置 // 是否显示列设置
showColumnSetting: { showColumnSetting: {
type: Boolean, type: Boolean,
default: true, default: true,
}, },
// 空状态文字
// 是否显示尺寸设置 emptyText: {
showSizeSetting: { type: String,
type: Boolean, default: '暂无数据',
default: true,
},
// 初始可见列
visibleColumns: {
type: Array,
default: () => [],
}, },
}) })
// 事件定义 const tableContent = useTemplateRef('tableContent')
const emit = defineEmits([ let scroll = ref({
'change', // 表格变化事件 scrollToFirstRowOnChange: true,
'row-click', // 行点击事件 x: 'max-content',
'row-dblclick', // 行双击事件 y: 100,
'refresh', // 刷新事件
'column-change', // 列显示变化事件
'size-change', // 尺寸变化事件
'page-change', // 页码变化事件
'page-size-change', // 每页条数变化事件
'selection-change', // 选择变化事件
])
// 组件引用
const containerRef = ref(null)
const tableRef = ref(null)
// 响应式数据
const tableSize = ref(props.size)
const tableHeight = ref(0)
const visibleColumns = ref(props.visibleColumns.length > 0 ? props.visibleColumns : props.columns.map((col) => col.key || col.dataIndex))
// 计算属性:处理后的列配置
const processedColumns = computed(() => {
// 过滤可见列并添加scopedSlots配置
let result = props.columns
.filter((column) => {
const columnKey = column.key || column.dataIndex
return visibleColumns.value.includes(columnKey)
})
.map((column) => {
const columnKey = column.key || column.dataIndex
return {
...column,
scopedSlots: {
...column.scopedSlots,
// 如果没有自定义scopedSlots,使用columnKey作为默认插槽名
customRender: column.scopedSlots?.customRender || columnKey,
},
}
})
// 添加操作列
if (props.showActionColumn) {
result.push({
...props.actionColumn,
key: 'action',
scopedSlots: {
...props.actionColumn.scopedSlots,
customRender: 'action',
},
})
}
return result
}) })
// 计算属性:行选择配置 onMounted(() => {
const rowSelectionConfig = computed(() => { let tableHeight = 100
if (!props.rowSelection) return null if (props.pagination !== false) {
tableHeight = tableContent.value.clientHeight - 100
} else {
tableHeight = tableContent.value.clientHeight - 65
}
scroll.value.y = tableHeight
})
return { // 根据表格宽度优化横向滚动配置
...props.rowSelection, watch(
onChange: (selectedRowKeys, selectedRows) => { [() => props.columns, () => props.showIndex, () => props.showAction, tableContent],
emit('selection-change', selectedRowKeys, selectedRows) () => {
if (props.rowSelection.onChange) { // 如果列有固定宽度且总宽度较大,使用max-content
props.rowSelection.onChange(selectedRowKeys, selectedRows) // 否则使用true让表格自适应
const hasFixedColumns = props.columns.some((col) => col.width)
if (hasFixedColumns || props.showIndex || props.showAction) {
scroll.value.x = 'max-content'
} else {
scroll.value.x = true
} }
}, },
} { immediate: true, deep: true },
)
// 表格设置状态
const tableSettings = reactive({
bordered: props.bordered,
size: props.size,
}) })
// 计算属性:滚动配置 // 监听props变化
const scrollConfig = computed(() => { watch(
return { () => props.bordered,
y: tableHeight.value, (val) => {
...props.scroll, tableSettings.bordered = val
} },
)
watch(
() => props.size,
(val) => {
tableSettings.size = val
},
)
const emit = defineEmits(['refresh', 'change', 'resizeColumn', 'action', 'select', 'selectAll', 'selectNone'])
// 列设置相关
const columnSettingVisible = ref(false)
const tableSettingVisible = ref(false)
const visibleColumns = ref([])
const sortedColumns = ref([]) // 排序后的列key数组
const draggingIndex = ref(-1) // 当前拖拽的索引
// 所有列(包含序号和操作列)
const allColumns = computed(() => {
return props.columns.filter((col) => col.dataIndex && col.dataIndex !== '_index' && col.dataIndex !== '_action')
}) })
/** // 获取列标题
* 计算表格高度 const getColumnTitle = (colKey) => {
* 容器高度 - 顶部工具区高度 - 底部分页区高度 - 边距 const col = allColumns.value.find((c) => (c.dataIndex || c.key) === colKey)
*/ return col ? col.title : colKey
const calculateTableHeight = () => {
if (!containerRef.value) return 0
const containerHeight = containerRef.value.offsetHeight
const toolbarHeight = 60 // 顶部工具区固定高度
const paginationHeight = props.pagination ? 56 : 0 // 分页器高度
const margin = 16 // 边距
return containerHeight - toolbarHeight - paginationHeight - margin
} }
/** // 初始化可见列和排序
* 调整表格高度 watch(
*/ () => props.columns,
const adjustTableHeight = () => { (newColumns) => {
nextTick(() => { const columnKeys = newColumns.filter((col) => col.dataIndex && col.dataIndex !== '_index' && col.dataIndex !== '_action').map((col) => col.dataIndex || col.key)
tableHeight.value = calculateTableHeight()
})
}
/** // 如果是首次初始化,使用原始顺序
* 窗口大小变化监听 if (sortedColumns.value.length === 0) {
*/ sortedColumns.value = [...columnKeys]
const handleResize = () => { } else {
adjustTableHeight() // 保留已存在的顺序,添加新列
} const existingKeys = sortedColumns.value.filter((key) => columnKeys.includes(key))
const newKeys = columnKeys.filter((key) => !existingKeys.includes(key))
sortedColumns.value = [...existingKeys, ...newKeys]
}
/** visibleColumns.value = [...sortedColumns.value]
* 列设置变化处理 },
* @param {Array} checkedValues - 选中的列key数组 { immediate: true, deep: true },
*/ )
const handleColumnChange = (checkedValues) => {
visibleColumns.value = checkedValues
emit('column-change', checkedValues)
}
/** // 切换列的显示状态
* 表格尺寸变化处理 const toggleColumn = (colKey, checked) => {
* @param {Object} menuItem - 菜单项 if (checked) {
*/ if (!visibleColumns.value.includes(colKey)) {
const handleSizeChange = ({ key }) => { visibleColumns.value.push(colKey)
tableSize.value = key }
emit('size-change', key) } else {
} visibleColumns.value = visibleColumns.value.filter((key) => key !== colKey)
/**
* 表格变化处理
* @param {Object} pagination - 分页信息
* @param {Object} filters - 筛选信息
* @param {Object} sorter - 排序信息
* @param {Object} extra - 额外信息
*/
const handleTableChange = (pagination, filters, sorter, extra) => {
emit('change', pagination, filters, sorter, extra)
}
/**
* 行点击处理
* @param {Object} record - 行数据
* @param {Event} event - 事件对象
*/
const handleRowClick = (record, event) => {
emit('row-click', record, event)
}
/**
* 行双击处理
* @param {Object} record - 行数据
* @param {Event} event - 事件对象
*/
const handleRowDblClick = (record, event) => {
emit('row-dblclick', record, event)
}
/**
* 行配置处理
* @param {Object} record - 行数据
* @returns {Object} - 行配置
*/
const handleRow = (record) => {
return {
onClick: (event) => handleRowClick(record, event),
onDblclick: (event) => handleRowDblClick(record, event),
} }
} }
/** // 拖拽开始
* 刷新表格数据 const handleDragStart = (index, event) => {
*/ draggingIndex.value = index
event.dataTransfer.effectAllowed = 'move'
event.dataTransfer.setData('text/plain', index.toString())
}
// 拖拽经过
const handleDragOver = (index, event) => {
event.preventDefault()
event.dataTransfer.dropEffect = 'move'
}
// 拖拽结束
const handleDragEnd = () => {
draggingIndex.value = -1
}
// 拖拽放置
const handleDrop = (dropIndex) => {
if (draggingIndex.value === dropIndex) return
const draggedKey = sortedColumns.value[draggingIndex.value]
const newColumns = [...sortedColumns.value]
// 移除被拖拽的项
newColumns.splice(draggingIndex.value, 1)
// 插入到新位置
newColumns.splice(dropIndex, 0, draggedKey)
sortedColumns.value = newColumns
draggingIndex.value = -1
}
// 处理刷新
const handleRefresh = () => { const handleRefresh = () => {
emit('refresh') emit('refresh')
} }
/** // 处理表格变化(分页、排序、筛选)
* 重置表格状态 const handleTableChange = (pagination, filters, sorter, extra) => {
*/ emit('change', { pagination, filters, sorter, extra })
const resetTable = () => {
visibleColumns.value = props.columns.map((col) => col.key || col.dataIndex)
tableSize.value = props.size
adjustTableHeight()
} }
/** // 处理列宽调整
* 组件挂载时执行 const handleResizeColumn = (width, column) => {
*/ emit('resizeColumn', { width, column })
onMounted(() => { }
// 初始计算表格高度
adjustTableHeight()
// 添加窗口大小变化监听 // 获取表格序号
window.addEventListener('resize', handleResize) const getTableIndex = (index) => {
const { current = 1, pageSize = 10 } = props.pagination || {}
return (current - 1) * pageSize + index + 1
}
// 使用 ResizeObserver 监听容器大小变化(更精确) // 处理操作按钮点击
if (typeof ResizeObserver !== 'undefined') { const handleAction = (action, record, index) => {
const observer = new ResizeObserver(handleResize) if (action.onClick) {
observer.observe(containerRef.value) action.onClick(record, index)
}
emit('action', { action, record, index })
}
// 组件卸载时清理 // 表格列配置
onUnmounted(() => { const tableColumns = computed(() => {
observer.disconnect() let columns = []
// 添加序号列
if (props.showIndex) {
columns.push({
title: props.indexTitle,
dataIndex: '_index',
key: '_index',
width: props.indexColumnWidth,
align: 'center',
fixed: 'left',
}) })
} }
})
/** // 添加数据列(按排序顺序)
* 组件卸载时执行 sortedColumns.value.forEach((colKey) => {
*/ // 过滤掉未显示的列
onUnmounted(() => { if (!visibleColumns.value.includes(colKey)) {
// 移除窗口大小变化监听 return
window.removeEventListener('resize', handleResize) }
const col = props.columns.find((c) => (c.dataIndex || c.key) === colKey)
if (col) {
columns.push({
...col,
customRender: col.slot ? undefined : col.customRender,
})
}
})
// 添加操作列
if (props.showAction) {
columns.push({
title: props.actionTitle,
dataIndex: '_action',
key: '_action',
width: props.actionColumnWidth,
align: 'center',
fixed: 'right',
})
}
return columns
}) })
// 暴露方法给父组件 // 暴露方法给父组件
defineExpose({ defineExpose({
refresh: handleRefresh, refresh: handleRefresh,
reset: resetTable, getTableIndex,
adjustHeight: adjustTableHeight,
getVisibleColumns: () => visibleColumns.value,
setVisibleColumns: (columns) => {
visibleColumns.value = columns
emit('column-change', columns)
},
}) })
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.sc-table-container { .sc-table {
display: flex;
flex-direction: column;
width: 100%; width: 100%;
height: 100%; height: 100%;
overflow: hidden;
min-height: 0;
}
.sc-table-toolbar {
display: flex; display: flex;
flex-direction: column;
background: #fff;
&-tool {
height: 56px;
display: flex;
flex-direction: row;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
height: 60px;
padding: 0 16px;
border-bottom: 1px solid #f0f0f0; border-bottom: 1px solid #f0f0f0;
box-sizing: border-box;
flex-shrink: 0;
}
.sc-table-toolbar-left { .tool-left,
.tool-right {
display: flex; display: flex;
align-items: center; flex-direction: row;
gap: 8px; gap: 8px;
}
.sc-table-toolbar-right {
display: flex;
align-items: center; align-items: center;
gap: 8px; }
} }
.sc-table-content { &-content {
flex: 1; flex: 1;
overflow: hidden; overflow: hidden;
position: relative;
min-height: 0;
}
.sc-table-content :deep(.ant-table) {
height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 0; }
} }
.sc-table-content :deep(.ant-table-container) { .column-setting {
flex: 1; min-width: 200px;
display: flex;
flex-direction: column;
overflow: hidden;
min-height: 0;
}
.sc-table-content :deep(.ant-table-body) { &-header {
overflow-y: auto; padding: 8px 0;
overflow-x: auto; font-weight: 500;
min-height: 0; color: #000;
} border-bottom: 1px solid #f0f0f0;
.sc-table-content :deep(.ant-pagination) {
flex-shrink: 0;
margin-top: 16px;
padding: 0 4px;
display: flex;
justify-content: flex-end;
align-items: center;
}
.sc-table-content :deep(.ant-table-wrapper) {
height: 100%;
display: flex;
flex-direction: column;
}
.sc-table-content :deep(.ant-spin-nested-loading) {
height: 100%;
display: flex;
flex-direction: column;
}
.sc-table-content :deep(.ant-spin-container) {
height: 100%;
display: flex;
flex-direction: column;
}
.sc-table-column-setting {
padding: 12px;
max-height: 300px;
overflow-y: auto;
}
.sc-table-column-setting :deep(.ant-checkbox) {
display: block;
margin-bottom: 8px; margin-bottom: 8px;
}
&-list {
max-height: 400px;
overflow-y: auto;
}
&-item {
display: flex;
align-items: center;
padding: 6px 4px;
cursor: move;
transition: all 0.2s;
border-radius: 4px;
&:hover {
background: #f5f5f5;
}
&.dragging {
opacity: 0.5;
background: #e6f7ff;
}
.drag-handle {
margin-right: 8px;
color: #999;
cursor: grab;
flex-shrink: 0;
&:active {
cursor: grabbing;
}
}
:deep(.ant-checkbox-wrapper) {
flex: 1;
margin: 0;
}
}
}
.table-setting {
min-width: 200px;
&-header {
padding: 8px 0;
font-weight: 500;
color: #000;
border-bottom: 1px solid #f0f0f0;
margin-bottom: 12px;
}
&-body {
.setting-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 0;
&:not(:last-child) {
border-bottom: 1px dashed #f0f0f0;
}
.setting-label {
color: #666;
font-size: 14px;
}
}
}
} }
</style> </style>
+30 -61
View File
@@ -1,11 +1,10 @@
<template> <template>
<div class="system-area"> <div class="system-area">
<sc-table ref="tableRef" :columns="columns" :data-source="dataSource" :loading="loading" <sc-table ref="tableRef" :columns="columns" :data-source="dataSource" :loading="loading"
:pagination="pagination" @refresh="loadData" @change="handleTableChange" :pagination="pagination" @refresh="loadData" @change="handleTableChange" :row-selection="rowSelection"
@selection-change="handleSelectionChange" :show-action-column="true" :show-action="true" :actions="actions" :show-index="true" :show-striped="true">
:rowSelection="{ selectedRowKeys: selectedRowKeys, onChange: handleSelectionChange }">
<!-- 工具栏左侧 --> <!-- 工具栏左侧 -->
<template #toolbar-left> <template #toolLeft>
<a-button type="primary" @click="handleAdd"> <a-button type="primary" @click="handleAdd">
<template #icon> <template #icon>
<PlusOutlined /> <PlusOutlined />
@@ -31,16 +30,6 @@
<template #level="{ text }"> <template #level="{ text }">
<a-tag color="blue">{{ getLevelText(text) }}</a-tag> <a-tag color="blue">{{ getLevelText(text) }}</a-tag>
</template> </template>
<!-- 操作列 -->
<template #action="{ record }">
<a-button size="small" type="link" @click="handleEdit(record)">
{{ $t('common.edit') }}
</a-button>
<a-button size="small" type="link" danger @click="handleDelete(record)">
{{ $t('common.delete') }}
</a-button>
</template>
</sc-table> </sc-table>
<!-- 添加/编辑弹窗 --> <!-- 添加/编辑弹窗 -->
@@ -50,9 +39,9 @@
</template> </template>
<script setup> <script setup>
import { ref, reactive, onMounted } from 'vue' import { ref, reactive, onMounted, computed } from 'vue'
import { message, Modal } from 'ant-design-vue' import { message, Modal } from 'ant-design-vue'
import { EnvironmentOutlined, PlusOutlined, DeleteOutlined } from '@ant-design/icons-vue' import { PlusOutlined, DeleteOutlined } from '@ant-design/icons-vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import systemApi from '@/api/system' import systemApi from '@/api/system'
import ScTable from '@/components/scTable/index.vue' import ScTable from '@/components/scTable/index.vue'
@@ -71,15 +60,7 @@ const dataSource = ref([])
const loading = ref(false) const loading = ref(false)
// 分页 // 分页
const pagination = reactive({ const pagination = reactive(false)
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `${total}`,
pageSizeOptions: ['10', '20', '50', '100'],
})
// 选中的行 // 选中的行
const selectedRowKeys = ref([]) const selectedRowKeys = ref([])
@@ -89,8 +70,25 @@ const modalVisible = ref(false)
const isEdit = ref(false) const isEdit = ref(false)
const currentData = ref({}) const currentData = ref({})
// 行操作配置 // 行选择配置
const rowActions = ref([]) const rowSelection = computed(() => ({
selectedRowKeys: selectedRowKeys.value,
onChange: (selectedKeys) => {
selectedRowKeys.value = selectedKeys
},
}))
// 操作列配置
const actions = computed(() => [
{
label: t('common.edit'),
onClick: handleEdit,
},
{
label: t('common.delete'),
onClick: handleDelete,
},
])
// 添加 // 添加
const handleAdd = () => { const handleAdd = () => {
@@ -112,14 +110,6 @@ const getLevelText = (level) => {
// 列配置 // 列配置
const columns = ref([ const columns = ref([
{
title: 'ID',
dataIndex: 'id',
key: 'id',
width: 80,
fixed: 'left',
sorter: true,
},
{ {
title: t('common.areaName'), title: t('common.areaName'),
dataIndex: 'title', dataIndex: 'title',
@@ -165,11 +155,10 @@ const loadData = async () => {
try { try {
loading.value = true loading.value = true
const params = { const params = {
page: pagination.current, is_tree: 1,
pageSize: pagination.pageSize,
} }
const res = await systemApi.area.list.get(params) const res = await systemApi.area.list.get(params)
if (res.code === 200 || res.code === 1) { if (res.code === 1) {
dataSource.value = res.data.list || res.data || [] dataSource.value = res.data.list || res.data || []
pagination.total = res.data.total || 0 pagination.total = res.data.total || 0
} }
@@ -182,17 +171,12 @@ const loadData = async () => {
} }
// 表格变化处理 // 表格变化处理
const handleTableChange = (params) => { const handleTableChange = ({ pagination: newPagination }) => {
if (params.current) pagination.current = params.current if (newPagination.current) pagination.current = newPagination.current
if (params.pageSize) pagination.pageSize = params.pageSize if (newPagination.pageSize) pagination.pageSize = newPagination.pageSize
loadData() loadData()
} }
// 行选择变化处理
const handleSelectionChange = (selectedKeys) => {
selectedRowKeys.value = selectedKeys
}
// 编辑 // 编辑
const handleEdit = (record) => { const handleEdit = (record) => {
isEdit.value = true isEdit.value = true
@@ -225,21 +209,6 @@ const handleDelete = (record) => {
}) })
} }
// 初始化行操作配置(必须在 handleEdit 和 handleDelete 定义之后)
rowActions.value = [
{
key: 'edit',
label: t('common.edit'),
handler: handleEdit,
},
{
key: 'delete',
label: t('common.delete'),
danger: true,
handler: handleDelete,
},
]
// 批量删除 // 批量删除
const handleBatchDelete = () => { const handleBatchDelete = () => {
if (selectedRowKeys.value.length === 0) { if (selectedRowKeys.value.length === 0) {