签入版本

This commit is contained in:
2026-10-11 19:06:33 +08:00
parent 17d28de8b7
commit 4084c6bd17
674 changed files with 58855 additions and 1892 deletions
@@ -97,77 +97,83 @@ export function exampleBatchDelete(data: any) {
```typescript
// ui/src/views/tool/example/columns.ts
import { h } from 'vue';
import { ElAvatar, ElTag } from 'element-plus';
import { Tag, Avatar } from 'ant-design-vue';
export const columns = [
{
type: 'selection', // 多选框列
},
{
label: 'ID',
prop: 'id',
title: 'ID',
dataIndex: 'id',
fixed: 'left',
width: 50,
},
{
label: '名称',
prop: 'name',
title: '名称',
dataIndex: 'name',
minWidth: 120,
},
{
label: '状态',
prop: 'statusText',
title: '状态',
dataIndex: 'statusText',
minWidth: 100,
// 选项标签渲染:按值映射文字与颜色
render(record) {
customRender({ record }) {
const statusMap = {
2: { text: '停用', type: 'danger' },
1: { text: '在用', type: 'success' },
2: { text: '停用', color: 'error' },
1: { text: '在用', color: 'success' },
};
const status = statusMap[record.row.status];
return h(ElTag, { type: status?.type || 'info' }, {
default: () => status?.text || record.row.statusText,
const status = statusMap[record.status];
return h(Tag, { color: status?.color || 'default' }, {
default: () => status?.text || record.statusText,
});
},
},
{
label: '头像',
prop: 'avatar',
title: '头像',
dataIndex: 'avatar',
minWidth: 80,
// 图片渲染:以方形头像形式展示
render(record) {
return h(ElAvatar, {
customRender({ record }) {
return h(Avatar, {
size: 48,
src: record.row.avatar,
src: record.avatar,
shape: 'square',
fit: 'fill',
});
},
},
{
label: '创建人',
prop: 'createUser',
title: '创建人',
dataIndex: 'createUser',
minWidth: 100,
},
{
label: '创建时间',
prop: 'createTime',
title: '创建时间',
dataIndex: 'createTime',
width: 180,
},
{
title: '操作',
key: 'action',
fixed: 'right',
width: 180,
},
];
```
:::tip 多选列
AntDesign 表格的多选框列不在 `columns` 中定义,而是通过表格的 `:row-selection` 属性开启(见步骤四)。
:::
### 列配置字段说明
| 字段 | 类型 | 说明 |
|------|------|------|
| `type` | string | 列类型:`selection`(多选框)/ `index`(序号) |
| `label` | string | 表头标题 |
| `prop` | string | 数据字段名(camelCase,与后端响应一致) |
| `title` | string | 表头标题 |
| `dataIndex` | string | 数据字段名(camelCase,与后端响应一致) |
| `key` | string | 列标识(`#bodyCell` 插槽按 `column.key` 匹配) |
| `fixed` | string | 固定列:`left` / `right` |
| `width` | number | 固定宽度 |
| `minWidth` | number | 最小宽度(自适应) |
| `render` | function | 自定义渲染函数 |
| `customRender` | function | 自定义渲染函数(参数 `{ record }`) |
:::tip 状态值对齐
`statusMap` 的值域必须与数据库/字典一致(如 example:1=在用 2=停用)。推荐直接使用后端补全的 `statusText` 字段,仅颜色按值映射。
@@ -181,21 +187,21 @@ import { FormSchema } from '@/components/Form/index';
export const schemas: FormSchema[] = [
{
field: 'name',
name: 'name',
component: 'Input',
label: '名称',
componentProps: {
placeholder: '请输入名称',
clearable: true,
allowClear: true,
},
},
{
field: 'status',
name: 'status',
component: 'Select',
label: '状态',
componentProps: {
placeholder: '请选择状态',
clearable: true,
allowClear: true,
options: [
{ label: '在用', value: 1 },
{ label: '停用', value: 2 },
@@ -215,7 +221,7 @@ export const schemas: FormSchema[] = [
| `DatePicker` | 日期字段 | 日期选择器 |
:::tip 查询字段须与后端配置对应
`field` 必须是后端 Service `pageLikeFields`(模糊)或 `pageEqFields`(精确)中声明的字段,否则查询条件不生效(见 [8.3 分页查询规范](/zh/api-spec/pagination.html))。下拉选项也可用 `getDictItemByCode(code)` 从字典动态加载。
`name` 必须是后端 Service `pageLikeFields`(模糊)或 `pageEqFields`(精确)中声明的字段,否则查询条件不生效(见 [8.3 分页查询规范](/zh/api-spec/pagination.html))。下拉选项也可用 `getDictItemByCode(code)` 从字典动态加载。
:::
## 步骤四:创建列表页
@@ -228,42 +234,42 @@ export const schemas: FormSchema[] = [
<SearchForm :schemas="querySchemas" @search="handleSearch" @reset="handleReset" />
<!-- 操作栏 -->
<el-row :gutter="10" style="margin-bottom: 10px;">
<el-col :span="1.5">
<el-button v-perm="['sys:example:add']" type="primary" @click="handleAdd">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-perm="['sys:example:batchDelete']" type="danger" :disabled="!selectedIds.length" @click="handleBatchDelete">
批量删除
</el-button>
</el-col>
</el-row>
<a-space style="margin-bottom: 10px;">
<a-button v-perm="['sys:example:add']" type="primary" @click="handleAdd">新增</a-button>
<a-button v-perm="['sys:example:batchDelete']" danger :disabled="!selectedIds.length" @click="handleBatchDelete">
批量删除
</a-button>
</a-space>
<!-- 数据表格 -->
<el-table :data="tableData" @selection-change="handleSelectionChange" v-loading="loading">
<el-table-column v-for="col in columns" v-bind="col" :key="col.prop || col.type">
<template v-if="col.render" #default="scope">
<component :is="() => col.render(scope)" />
<a-spin :spinning="loading">
<a-table
:data-source="tableData"
:columns="columns"
:row-selection="{ selectedRowKeys: selectedIds, onChange: handleSelectionChange }"
:pagination="false"
row-key="id"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<a-button v-perm="['sys:example:update']" type="link" @click="handleEdit(record)">编辑</a-button>
<a-button v-perm="['sys:example:detail']" type="link" @click="handleDetail(record)">详情</a-button>
<a-button v-perm="['sys:example:delete']" type="link" danger @click="handleDelete(record.id)">删除</a-button>
</template>
</template>
</el-table-column>
<el-table-column label="操作" width="180" fixed="right">
<template #default="{ row }">
<el-button v-perm="['sys:example:update']" type="primary" link @click="handleEdit(row)">编辑</el-button>
<el-button v-perm="['sys:example:detail']" type="primary" link @click="handleDetail(row)">详情</el-button>
<el-button v-perm="['sys:example:delete']" type="danger" link @click="handleDelete(row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
</a-table>
</a-spin>
<!-- 分页 -->
<el-pagination
v-model:current-page="pageNo"
<a-pagination
v-model:current="pageNo"
v-model:page-size="pageSize"
:total="total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:show-size-changer="true"
:page-size-options="[10, 20, 50, 100]"
:show-total="(t) => `共 ${t} 条`"
style="margin-top: 10px; text-align: right;"
@change="fetchData"
style="margin-top: 10px; justify-content: flex-end;"
/>
<!-- 编辑弹窗 -->
@@ -278,7 +284,7 @@ export const schemas: FormSchema[] = [
import { getExamplePage, exampleDelete, exampleBatchDelete } from '@/api/tool/example';
import { columns } from './columns';
import { schemas as querySchemas } from './querySchemas';
import { ElMessage, ElMessageBox } from 'element-plus';
import { message, Modal } from 'ant-design-vue';
import EditDialog from './edit.vue';
import DetailDialog from './detail.vue';
@@ -336,24 +342,34 @@ const handleDetail = (row) => {
};
// 删除
const handleDelete = async (id) => {
await ElMessageBox.confirm('确认删除该记录?', '提示', { type: 'warning' });
await exampleDelete(id);
ElMessage.success('删除成功');
fetchData();
const handleDelete = (id) => {
Modal.confirm({
title: '提示',
content: '确认删除该记录?',
onOk: async () => {
await exampleDelete(id);
message.success('删除成功');
fetchData();
},
});
};
// 批量删除(DELETE,请求体 { ids: [...] })
const handleBatchDelete = async () => {
await ElMessageBox.confirm(`确认删除选中的 ${selectedIds.value.length} 条记录?`, '提示', { type: 'warning' });
await exampleBatchDelete({ ids: selectedIds.value });
ElMessage.success('删除成功');
fetchData();
const handleBatchDelete = () => {
Modal.confirm({
title: '提示',
content: `确认删除选中的 ${selectedIds.value.length} 条记录?`,
onOk: async () => {
await exampleBatchDelete({ ids: selectedIds.value });
message.success('删除成功');
fetchData();
},
});
};
// 多选变化
const handleSelectionChange = (rows) => {
selectedIds.value = rows.map(row => row.id);
// 多选变化(row-selection 的 onChange 返回选中行的 row-key 数组)
const handleSelectionChange = (keys) => {
selectedIds.value = keys;
};
onMounted(() => fetchData());
@@ -365,37 +381,39 @@ onMounted(() => fetchData());
```vue
<!-- ui/src/views/tool/example/edit.vue -->
<template>
<el-dialog v-model="visible" :title="formData.id ? '编辑' : '新增'" width="500px">
<el-form ref="formRef" :model="formData" :rules="rules" label-width="80px">
<el-form-item label="名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入名称" />
</el-form-item>
<el-form-item label="类型" prop="type">
<el-select v-model="formData.type" placeholder="请选择类型">
<el-option v-for="item in typeOptions" :key="item.value" :label="item.name" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio :value="1">在用</el-radio>
<el-radio :value="2">停用</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number v-model="formData.sort" :min="0" :max="9999" />
</el-form-item>
</el-form>
<a-modal v-model:visible="visible" :title="formData.id ? '编辑' : '新增'" width="500px">
<a-form ref="formRef" :model="formData" :rules="rules" :label-col="{ style: { width: '80px' } }">
<a-form-item label="名称" name="name">
<a-input v-model:value="formData.name" placeholder="请输入名称" allow-clear />
</a-form-item>
<a-form-item label="类型" name="type">
<a-select v-model:value="formData.type" placeholder="请选择类型" style="width: 100%">
<a-select-option v-for="item in typeOptions" :key="item.value" :value="item.value">
{{ item.name }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="状态" name="status">
<a-radio-group v-model:value="formData.status">
<a-radio :value="1">在用</a-radio>
<a-radio :value="2">停用</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="排序" name="sort">
<a-input-number v-model:value="formData.sort" :min="0" :max="9999" />
</a-form-item>
</a-form>
<template #footer>
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" :loading="submitting" @click="handleSubmit">确定</el-button>
<a-button @click="visible = false">取消</a-button>
<a-button type="primary" :loading="submitting" @click="handleSubmit">确定</a-button>
</template>
</el-dialog>
</a-modal>
</template>
<script setup lang="ts">
import { exampleAdd, exampleUpdate, getExampleDetail } from '@/api/tool/example';
import { getDictItemByCode } from '@/api/common/index';
import { ElMessage } from 'element-plus';
import { message } from 'ant-design-vue';
const emit = defineEmits(['success']);
@@ -413,8 +431,8 @@ const formData = ref({
});
const rules = {
name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
type: [{ required: true, message: '请选择类型', trigger: 'change' }],
name: { required: true, message: '请输入名称', trigger: 'blur' },
type: { required: true, message: '请选择类型', trigger: 'change' },
};
// 打开弹窗
@@ -439,10 +457,10 @@ const handleSubmit = async () => {
try {
if (formData.value.id) {
await exampleUpdate(formData.value);
ElMessage.success('修改成功');
message.success('修改成功');
} else {
await exampleAdd(formData.value);
ElMessage.success('新增成功');
message.success('新增成功');
}
visible.value = false;
emit('success');
@@ -464,43 +482,43 @@ defineExpose({ open });
```vue
<!-- ui/src/views/tool/example/detail.vue -->
<template>
<el-dialog v-model="props.visible" title="案例详情" width="750" :close-on-click-modal="false">
<el-descriptions column="2" border>
<el-descriptions-item label="名称:">
<a-modal v-model:visible="props.visible" title="案例详情" width="750px" :mask-closable="false">
<a-descriptions :column="2" bordered>
<a-descriptions-item label="名称">
{{ formData.name || '-' }}
</el-descriptions-item>
<el-descriptions-item label="类型:">
</a-descriptions-item>
<a-descriptions-item label="类型">
{{ formData.typeText || '-' }}
</el-descriptions-item>
<el-descriptions-item label="状态:">
<el-tag :type="formData.status === 1 ? 'success' : 'danger'">
</a-descriptions-item>
<a-descriptions-item label="状态">
<a-tag :color="formData.status === 1 ? 'success' : 'error'">
{{ formData.statusText || '-' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="排序:">
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="排序">
{{ formData.sort ?? '-' }}
</el-descriptions-item>
<el-descriptions-item label="头像:">
<el-image
</a-descriptions-item>
<a-descriptions-item label="头像">
<a-image
v-if="formData.avatar"
:src="formData.avatar"
:preview-src-list="[formData.avatar]"
fit="cover"
style="width: 80px; height: 80px"
:width="80"
:height="80"
style="object-fit: cover"
/>
<span v-else>-</span>
</el-descriptions-item>
<el-descriptions-item label="创建人:">
</a-descriptions-item>
<a-descriptions-item label="创建人">
{{ formData.createUser || '-' }}
</el-descriptions-item>
<el-descriptions-item label="创建时间:">
</a-descriptions-item>
<a-descriptions-item label="创建时间">
{{ formData.createTime || '-' }}
</el-descriptions-item>
</el-descriptions>
</a-descriptions-item>
</a-descriptions>
<template #footer>
<el-button @click="emit('update:visible', false)">关闭</el-button>
<a-button @click="emit('update:visible', false)">关闭</a-button>
</template>
</el-dialog>
</a-modal>
</template>
<script lang="ts" setup>