Files
djangoadmin-wiki/DjangoAdmin_Flask_AntdVue/zh/volume7/fe-component.md
T
2026-09-13 21:53:16 +08:00

373 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 前端页面规范
## 概述
前端基于 Vue3 + AntDesign Vue + 自定义组件库构建。页面统一使用 `PageWrapper` + `BasicForm` + `BasicTable` + `bodyCell` 插槽四件套,分页由 `BasicTable` 内置处理,无需单独引入分页组件。
:::tip 技术栈
- Vue3(Composition API + `<script setup>`)
- AntDesign Vue 4.x
- 自定义组件(BasicTable / BasicForm / PageWrapper)
- Pinia 状态管理
- Vue Router 路由管理
:::
## 目录结构
```
ui/src/
├── api/ # API 接口定义
│ ├── data/ # 数据模块 API(param、dict、config、notice 等)
│ │ ├── param.ts
│ │ ├── dictionary.ts
│ │ └── ...
│ ├── system/ # 系统模块 API(user、role、dept、position、level)
│ │ ├── user.ts
│ │ ├── role.ts
│ │ └── ...
│ ├── content/ # 内容模块 API(article、category)
│ ├── common/ # 公共 API(city、upload、dict 按编码查询)
│ └── monitor/ # 监控模块 API(job、loginLog、operLog)
├── components/ # 公共组件(详见组件库文档)
├── views/ # 页面视图(按模块分目录)
│ ├── data/
│ │ ├── param/ # 参数管理
│ │ │ ├── index.vue # 列表页
│ │ │ ├── edit.vue # 编辑弹窗
│ │ │ ├── columns.ts # 表格列定义
│ │ │ └── querySchemas.ts # 搜索表单配置
│ │ ├── dict/
│ │ └── config/
│ ├── system/
│ │ ├── user/
│ │ ├── role/
│ │ ├── dept/
│ │ ├── position/
│ │ └── level/
│ ├── content/
│ │ ├── article/
│ │ └── category/
│ └── monitor/
│ ├── job/
│ ├── loginLog/
│ └── operLog/
├── store/ # Pinia 状态管理
├── router/ # 路由配置
├── utils/ # 工具函数
├── hooks/ # 组合式函数
├── styles/ # 全局样式
├── plugins/ # 插件注册(AntDesign Vue、自定义组件、指令)
└── main.ts # 入口文件
```
## 文件组织规范
每个业务模块统一包含以下文件:
| 文件 | 说明 | 示例 |
|------|------|------|
| `index.vue` | 列表页(搜索 + 表格 + 操作) | `views/data/param/index.vue` |
| `edit.vue` | 编辑弹窗(新增/编辑共用) | `views/data/param/edit.vue` |
| `columns.ts` | 表格列定义 | `views/data/param/columns.ts` |
| `querySchemas.ts` | 搜索表单配置 | `views/data/param/querySchemas.ts` |
## 页面标准结构
以参数管理(`views/data/param/index.vue`)为标准模板:
```vue
<template>
<PageWrapper>
<!-- 搜索表单 -->
<a-card :bordered="false" class="pt-3 mb-3 proCard">
<BasicForm @register="register" @submit="handleSubmit" @reset="handleReset" />
</a-card>
<!-- 数据表格 -->
<a-card :bordered="false" class="proCard">
<BasicTable
:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.id"
ref="tableRef"
scroll-x="1200"
:row-selection="{ onChange: onSelectionChange }"
>
<template #tableTitle>
<a-space>
<a-button type="primary" @click="handleAdd" v-perm="['sys:param:add']">
<template #icon><PlusOutlined /></template>
新建
</a-button>
<a-button type="danger" @click="handleDelete()" :disabled="!selectionData.length" v-perm="['sys:param:batchDelete']">
<template #icon><DeleteOutlined /></template>
删除
</a-button>
</a-space>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<a-space>
<a-button type="primary" @click="handleEdit(record.id)" v-perm="['sys:param:update']">编辑</a-button>
<a-button type="primary" danger @click="handleDelete(record.id)" v-perm="['sys:param:delete']">删除</a-button>
</a-space>
</template>
</template>
</BasicTable>
</a-card>
<!-- 编辑弹窗(懒加载) -->
<editDialog
v-if="editVisible"
:paramId="paramId"
v-model:visible="editVisible"
@success="reloadTable('noRefresh')"
/>
</PageWrapper>
</template>
```
:::warning 注意
- 分页由 `BasicTable` 内置处理,无需引入 `<pagination>` 组件
- `:request="loadDataTable"` 自动管理分页参数(pageNo / pageSize)和 loading 状态
- `tableRef.value.reload({ pageNo: 1 })` 用于搜索/刷新时重置到第一页
:::
## 表格列定义(columns.ts)
```typescript
// views/data/param/columns.ts
import { h } from 'vue';
import { Tag } from 'ant-design-vue';
export const columns = [
{ title: 'ID', dataIndex: 'id', width: 50, fixed: 'left' },
{ title: '参数名称', dataIndex: 'name', width: 200 },
{ title: '参数编码', dataIndex: 'code', width: 250 },
{ title: '参数值', dataIndex: 'value', width: 200 },
{
title: '参数类型',
dataIndex: 'type',
width: 100,
customRender({ record }) {
return h(Tag,
{ color: record.type == 0 ? 'processing' : 'warning' },
{ default: () => (record.type == 0 ? '系统' : '业务') }
);
},
},
{
title: '参数状态',
dataIndex: 'status',
width: 100,
customRender({ record }) {
return h(Tag,
{ color: record.status == 1 ? 'success' : 'error' },
{ default: () => (record.status == 1 ? '正常' : '禁用') }
);
},
},
{ title: '排序', dataIndex: 'sort', width: 100 },
{ title: '备注', dataIndex: 'note', width: 200 },
{ title: '创建人', dataIndex: 'createUser', width: 100 },
{ title: '创建时间', dataIndex: 'createTime', width: 180 },
{ title: '操作', fixed: 'right', dataIndex: 'action', key: 'action', width: 200 },
];
```
### customRender 函数自定义渲染
```typescript
// 状态标签
customRender({ record }) {
return h(Tag,
{ color: record.status == 1 ? 'success' : 'error' },
{ default: () => record.statusText }
);
}
// 链接
customRender({ record }) {
return h('a',
{ href: record.url, target: '_blank', class: 'text-blue-500' },
{ default: () => record.url }
);
}
```
## 搜索表单配置(querySchemas.ts)
```typescript
// views/data/param/querySchemas.ts
import { FormSchema } from '@/components/Form/index';
export const schemas: FormSchema[] = [
{
name: 'name',
component: 'Input',
label: '参数名称',
componentProps: { placeholder: '请输入参数名称' },
},
{
name: 'type',
component: 'Select',
label: '参数类型',
componentProps: {
placeholder: '请选择参数类型',
clearable: true,
options: [
{ label: '系统', value: '0' },
{ label: '业务', value: '1' },
],
},
},
{
name: 'status',
component: 'Select',
label: '状态',
componentProps: {
placeholder: '请选择状态',
clearable: true,
options: [
{ label: '正常', value: '1' },
{ label: '禁用', value: '2' },
],
},
},
];
```
## API 接口定义
```typescript
// api/data/param.ts
import { http } from '@/utils/http/axios';
export function getParamList(params?) {
return http.request({ url: '/param/page', method: 'GET', params });
}
export function getParamDetail(id) {
return http.request({ url: '/param/detail/' + id, method: 'get' });
}
export function paramAdd(data: any) {
return http.request({ url: '/param/add', method: 'POST', data });
}
export function paramUpdate(data: any) {
return http.request({ url: '/param/update', method: 'PUT', data });
}
export function paramDelete(id) {
return http.request({ url: '/param/delete/' + id, method: 'DELETE' });
}
export function paramBatchDelete(data: any) {
return http.request({ url: '/param/batchDelete', method: 'DELETE', data });
}
```
## 数据加载与刷新
```typescript
// 加载数据(BasicTable 的 :request 回调,自动传入分页参数)
const loadDataTable = async (res: any) => {
const result = await getParamList({ ...formParams, ...res });
return result;
};
// 刷新表格
function reloadTable(noRefresh = '') {
tableRef.value.reload(noRefresh ? {} : { pageNo: 1 });
}
// 搜索提交
function handleSubmit(values: Recordable) {
for (const key in formParams) {
formParams[key] = '';
}
for (const key in values) {
formParams[key] = values[key];
}
reloadTable();
}
// 重置搜索
function handleReset() {
for (const key in formParams) {
formParams[key] = '';
}
reloadTable();
}
```
## 编辑弹窗(edit.vue)
编辑弹窗通过 `defineAsyncComponent` 懒加载,使用 `v-model:visible` 控制显示:
```vue
<!-- index.vue 中引用 -->
<editDialog
v-if="editVisible"
:paramId="paramId"
v-model:visible="editVisible"
@success="reloadTable('noRefresh')"
/>
```
```typescript
// 懒加载编辑组件
const editDialog = defineAsyncComponent(() => import('./edit.vue'));
// 新增
const handleAdd = async () => {
paramId.value = 0;
await nextTick();
editVisible.value = true;
};
// 编辑
async function handleEdit(id) {
paramId.value = id;
await nextTick();
editVisible.value = true;
}
```
## 删除(单条 + 批量)
```typescript
import { Modal, message } from 'ant-design-vue';
async function handleDelete(id) {
Modal.confirm({
title: '提示',
content: '确定要删除?',
onOk: async () => {
id ? await paramDelete(id) : await paramBatchDelete(selectionData.value);
message.success('删除成功');
reloadTable();
},
});
}
// 多选变化
function onSelectionChange(value) {
selectionData.value = value;
}
```
## 方法命名规范
| 前缀 | 用途 | 示例 |
|------|------|------|
| `handle` | 事件处理 | `handleAdd`、`handleEdit`、`handleDelete`、`handleSubmit`、`handleReset` |
| `load` | 数据加载 | `loadDataTable` |
| `reload` | 刷新数据 | `reloadTable` |
| `on` | 组件事件回调 | `onSelectionChange`、`onSuccess`、`onError` |
## 总结
前端页面统一使用 `PageWrapper` + `BasicForm` + `BasicTable` + `bodyCell` 插槽四件套。分页由 `BasicTable` 内置处理,无需单独引入分页组件。每个模块包含 `index.vue`(列表页)、`edit.vue`(编辑弹窗)、`columns.ts`(列定义)、`querySchemas.ts`(搜索配置)四个文件。API 接口按模块分文件定义在 `api/` 目录下。