refactor(components): 重构表单和表格组件并更新相关引用

feat(components): 新增scForm和scTable组件替代DynamicForm
feat(pages): 添加用户管理页面到auth模块
chore(deps): 调整package.json依赖项顺序并添加vue-eslint-parser
This commit is contained in:
2026-01-20 15:51:54 +08:00
parent 8c2d943dba
commit f877097398
7 changed files with 750 additions and 841 deletions

View File

@@ -30,12 +30,13 @@
"@eslint/js": "^9.39.2",
"@vitejs/plugin-vue": "^6.0.3",
"@vue/eslint-config-prettier": "^10.2.0",
"sass-embedded": "^1.97.2",
"eslint": "^9.39.2",
"eslint-plugin-vue": "~10.6.2",
"globals": "^17.0.0",
"prettier": "3.7.4",
"sass-embedded": "^1.97.2",
"vite": "^7.3.0",
"vite-plugin-vue-devtools": "^8.0.5"
"vite-plugin-vue-devtools": "^8.0.5",
"vue-eslint-parser": "^10.2.0"
}
}

View File

@@ -0,0 +1,350 @@
<template>
<div class="sc-table-wrapper">
<!-- 表格操作栏 -->
<div class="table-toolbar" v-if="showToolbar">
<div class="toolbar-left">
<slot name="toolbar-left"></slot>
</div>
<div class="toolbar-right">
<!-- 列设置 -->
<a-dropdown v-if="showColumnSetting" :trigger="['click']">
<a-button size="small">
<SettingOutlined /> 列设置
</a-button>
<template #overlay>
<a-menu @click="handleColumnSetting">
<a-menu-item v-for="column in columns" :key="column.key || column.dataIndex">
<a-checkbox :checked="visibleColumns.includes(column.key || column.dataIndex)">
{{ column.title }}
</a-checkbox>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
<!-- 刷新按钮 -->
<a-button v-if="showRefresh" size="small" @click="handleRefresh">
<ReloadOutlined />
</a-button>
<slot name="toolbar-right"></slot>
</div>
</div>
<!-- 表格主体 -->
<a-table v-bind="tableProps" :columns="processedColumns" :data-source="dataSource"
:pagination="paginationConfig" :loading="loading" :row-selection="rowSelectionConfig" :scroll="scrollConfig"
@change="handleTableChange" @row="handleRow" @row-click="handleRowClick" @row-dblclick="handleRowDblClick">
<!-- 自定义列插槽将通过customRender在processedColumns中处理 -->
<!-- 操作列插槽 -->
<template v-if="showActionColumn" #action="scope">
<slot name="action" v-bind="scope"></slot>
</template>
<!-- 空数据提示 -->
<template #empty>
<a-empty v-if="!error" :description="emptyText" />
<div v-else class="table-error">
<span>{{ errorMessage }}</span>
<a-button size="small" type="link" @click="handleRefresh">重试</a-button>
</div>
</template>
</a-table>
</div>
</template>
<script setup>
import { computed } from 'vue'
import { SettingOutlined, ReloadOutlined } from '@ant-design/icons-vue'
const props = defineProps({
// 表格列配置
columns: {
type: Array,
default: () => [],
required: true,
},
// 数据源
dataSource: {
type: Array,
default: () => [],
},
// 加载状态
loading: {
type: Boolean,
default: false,
},
// 错误状态
error: {
type: Boolean,
default: false,
},
// 错误信息
errorMessage: {
type: String,
default: '加载失败,请稍后重试',
},
// 空数据提示
emptyText: {
type: String,
default: '暂无数据',
},
// 分页配置
pagination: {
type: Object,
default: () => ({
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: true,
pageSizeOptions: ['10', '20', '50', '100'],
showTotal: (total, range) => `${total} 条记录,第 ${range[0]}-${range[1]}`,
}),
},
// 是否显示分页
showPagination: {
type: Boolean,
default: true,
},
// 行选择配置
rowSelection: {
type: Object,
default: () => ({
type: 'checkbox',
selectedRowKeys: [],
onChange: () => { },
}),
},
// 是否显示行选择
showRowSelection: {
type: Boolean,
default: false,
},
// 滚动配置
scroll: {
type: Object,
default: () => ({}),
},
// 表格属性
tableProps: {
type: Object,
default: () => ({}),
},
// 操作列配置
actionColumn: {
type: Object,
default: () => ({
title: '操作',
key: 'action',
width: 150,
fixed: 'right',
}),
},
// 是否显示操作列
showActionColumn: {
type: Boolean,
default: false,
},
// 是否显示工具栏
showToolbar: {
type: Boolean,
default: true,
},
// 是否显示列设置
showColumnSetting: {
type: Boolean,
default: true,
},
// 是否显示刷新按钮
showRefresh: {
type: Boolean,
default: true,
},
// 可见列配置
visibleColumns: {
type: Array,
default: () => [],
},
})
const emit = defineEmits(['change', 'row-click', 'row-dblclick', 'refresh', 'column-change', 'selection-change', 'page-change', 'size-change', 'sort-change', 'filter-change'])
// 处理后的列配置
const processedColumns = computed(() => {
let result = [...props.columns]
// 过滤可见列
if (props.visibleColumns.length > 0) {
result = result.filter((column) => props.visibleColumns.includes(column.key || column.dataIndex))
}
// 为每列添加customRender支持插槽
result = result.map((column) => {
const columnKey = column.key || column.dataIndex
return {
...column,
customRender: (_, record, index) => {
// 使用渲染函数返回空内容,实际内容通过插槽渲染
return null
},
}
})
// 添加操作列
if (props.showActionColumn) {
result.push({
...props.actionColumn,
customRender: (_, record, index) => {
// 使用渲染函数返回空内容,实际内容通过插槽渲染
return null
},
})
}
return result
})
// 分页配置
const paginationConfig = computed(() => {
if (!props.showPagination) return false
return {
...props.pagination,
onChange: (page, pageSize) => {
emit('page-change', page)
emit('change', { current: page, pageSize })
},
onShowSizeChange: (current, pageSize) => {
emit('size-change', pageSize)
emit('change', { current, pageSize })
},
}
})
// 行选择配置
const rowSelectionConfig = computed(() => {
if (!props.showRowSelection) return null
return {
...props.rowSelection,
onChange: (selectedRowKeys, selectedRows) => {
emit('selection-change', selectedRowKeys, selectedRows)
},
}
})
// 滚动配置
const scrollConfig = computed(() => {
return {
...props.scroll,
}
})
// 处理表格变化
const handleTableChange = (pagination, filters, sorter, extra) => {
if (sorter.field) {
emit('sort-change', sorter)
}
if (Object.keys(filters).length > 0) {
emit('filter-change', filters)
}
emit('change', { pagination, filters, sorter, extra })
}
// 处理行点击
const handleRowClick = (record, event) => {
emit('row-click', record, event)
}
// 处理行双击
const handleRowDblClick = (record, event) => {
emit('row-dblclick', record, event)
}
// 处理行配置
const handleRow = (record) => {
return {
onClick: (event) => handleRowClick(record, event),
onDblclick: (event) => handleRowDblClick(record, event),
}
}
// 处理列设置
const handleColumnSetting = ({ key, domEvent }) => {
const checkbox = domEvent.target.closest('.ant-checkbox-wrapper').querySelector('.ant-checkbox-input')
const checked = checkbox.checked
emit('column-change', {
key,
visible: checked,
})
}
// 处理刷新
const handleRefresh = () => {
emit('refresh')
}
// 暴露方法
defineExpose({
handleRefresh,
})
</script>
<style scoped lang="scss">
.sc-table-wrapper {
width: 100%;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
.table-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
border-bottom: 1px solid #f0f0f0;
.toolbar-left,
.toolbar-right {
display: flex;
align-items: center;
gap: 8px;
}
}
.table-error {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px 0;
.error-icon {
font-size: 48px;
color: #ff4d4f;
margin-bottom: 16px;
}
}
}
</style>

View File

@@ -9,41 +9,28 @@
</template>
<div class="search-form">
<a-form :model="searchForm" layout="inline">
<a-form-item label="用户名">
<a-input v-model:value="searchForm.username" placeholder="请输入用户名" />
</a-form-item>
<a-form-item label="状态">
<a-select v-model:value="searchForm.status" placeholder="请选择状态" style="width: 120px">
<a-select-option value="">全部</a-select-option>
<a-select-option :value="1">正常</a-select-option>
<a-select-option :value="0">禁用</a-select-option>
</a-select>
</a-form-item>
<a-form-item>
<a-space>
<a-button type="primary" @click="handleSearch">查询</a-button>
<a-button @click="handleReset">重置</a-button>
</a-space>
</a-form-item>
</a-form>
<sc-form :form-items="formItems" :initial-values="searchForm" :show-actions="true" submit-text="查询"
reset-text="重置" @finish="handleSearch" @reset="handleReset" layout="inline" />
</div>
<a-table :columns="columns" :data-source="dataSource" :loading="loading" :pagination="pagination">
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'status'">
<sc-table :columns="columns" :data-source="dataSource" :loading="loading" :pagination="pagination"
:show-action-column="true" :action-column="{
title: '操作',
key: 'action',
width: 150,
}">
<template #status="{ record }">
<a-tag :color="record.status === 1 ? 'green' : 'red'">
{{ record.status === 1 ? '正常' : '禁用' }}
</a-tag>
</template>
<template v-else-if="column.key === 'action'">
<template #action="{ record }">
<a-space>
<a-button type="link" size="small" @click="handleEdit(record)">编辑</a-button>
<a-button type="link" size="small" danger @click="handleDelete(record)">删除</a-button>
</a-space>
</template>
</template>
</a-table>
</sc-table>
</a-card>
</div>
</template>
@@ -52,45 +39,66 @@
import { ref, onMounted } from 'vue'
import { message } from 'ant-design-vue'
import { PlusOutlined } from '@ant-design/icons-vue'
import ScTable from '@/components/scTable/index.vue'
import ScForm from '@/components/scForm/index.vue'
const columns = [
{
title: 'ID',
dataIndex: 'id',
key: 'id',
width: 80
width: 80,
},
{
title: '用户名',
dataIndex: 'username',
key: 'username'
key: 'username',
},
{
title: '昵称',
dataIndex: 'nickname',
key: 'nickname'
key: 'nickname',
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
width: 100
width: 100,
},
{
title: '创建时间',
dataIndex: 'createTime',
key: 'createTime'
key: 'createTime',
},
]
//
const formItems = [
{
field: 'username',
label: '用户名',
type: 'input',
placeholder: '请输入用户名',
allowClear: true,
},
{
title: '操作',
key: 'action',
width: 150
}
field: 'status',
label: '状态',
type: 'select',
placeholder: '请选择状态',
options: [
{ label: '全部', value: '' },
{ label: '正常', value: 1 },
{ label: '禁用', value: 0 },
],
allowClear: true,
style: 'width: 120px',
},
]
const searchForm = ref({
username: '',
status: ''
status: '',
})
const dataSource = ref([])
@@ -100,7 +108,7 @@ const pagination = ref({
pageSize: 10,
total: 0,
showSizeChanger: true,
showTotal: (total) => `${total}`
showTotal: (total) => `${total}`,
})
//
@@ -110,15 +118,15 @@ const mockData = [
username: 'admin',
nickname: '管理员',
status: 1,
createTime: '2024-01-01 10:00:00'
createTime: '2024-01-01 10:00:00',
},
{
id: 2,
username: 'user',
nickname: '普通用户',
status: 1,
createTime: '2024-01-02 10:00:00'
}
createTime: '2024-01-02 10:00:00',
},
]
const loadData = () => {
@@ -138,7 +146,7 @@ const handleSearch = () => {
const handleReset = () => {
searchForm.value = {
username: '',
status: ''
status: '',
}
loadData()
}

View File

@@ -1,12 +1,12 @@
<template>
<DynamicForm :form-items="formItems" :initial-values="initialValues" :loading="loading" @finish="handleFinish"
<scForm :form-items="formItems" :initial-values="initialValues" :loading="loading" @finish="handleFinish"
@reset="handleReset" />
</template>
<script setup>
import { ref, computed } from 'vue'
import { message } from 'ant-design-vue'
import DynamicForm from '@/components/DynamicForm.vue'
import scForm from '@/components/scForm/index.vue'
const props = defineProps({
userInfo: {

View File

@@ -1,12 +1,12 @@
<template>
<DynamicForm :form-items="formItems" :initial-values="initialValues" :loading="loading" submit-text="修改密码"
<scForm :form-items="formItems" :initial-values="initialValues" :loading="loading" submit-text="修改密码"
@finish="handleFinish" @reset="handleReset" />
</template>
<script setup>
import { ref } from 'vue'
import { message } from 'ant-design-vue'
import DynamicForm from '@/components/DynamicForm.vue'
import scForm from '@/components/scForm/index.vue'
const emit = defineEmits(['success'])

1126
yarn.lock

File diff suppressed because it is too large Load Diff