285 lines
7.4 KiB
Markdown
285 lines
7.4 KiB
Markdown
# 3.9 前端联调指引
|
||
|
||
:::tip 概述
|
||
后端接口开发完成后,需要在前端创建对应的 API 模块和页面组件。本节以 example 模块为例,提供简要指引,详细内容参考[第 9 章](/zh/frontend/overview.html)。
|
||
:::
|
||
|
||
## 联调流程
|
||
|
||
```text
|
||
1. 创建 API 模块 → ui/src/api/tool/example.ts
|
||
2. 创建页面组件 → ui/src/views/tool/example/index.vue
|
||
3. 配置菜单组件路径 → 后端菜单管理中设置 component
|
||
4. 测试联调
|
||
```
|
||
|
||
## 步骤一:创建 API 模块
|
||
|
||
在 `ui/src/api/tool/` 下创建 `example.ts`(来自项目实际代码):
|
||
|
||
```typescript
|
||
// ui/src/api/tool/example.ts
|
||
import { http } from '@/utils/http/axios';
|
||
|
||
/**
|
||
* 案例演示模块接口
|
||
*
|
||
* 提供案例演示的增删改查及导入导出接口调用。
|
||
*/
|
||
|
||
/**
|
||
* 获取案例演示分页列表
|
||
*
|
||
* @param params 查询参数,包含 pageNo、pageSize 及业务筛选条件
|
||
* @returns 分页结果,含 records、total、size、current、pages
|
||
*/
|
||
export function getExamplePage(params?: Record<string, any>) {
|
||
return http.request({
|
||
url: '/example/page',
|
||
method: 'GET',
|
||
params,
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 获取全部案例演示列表
|
||
*
|
||
* @param params 查询参数
|
||
* @returns 案例演示列表
|
||
*/
|
||
export function getExampleList(params?: Record<string, any>) {
|
||
return http.request({
|
||
url: '/example/list',
|
||
method: 'GET',
|
||
params,
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 根据 ID 获取案例演示详情
|
||
*
|
||
* @param id 案例演示ID
|
||
* @returns 案例演示详情数据
|
||
*/
|
||
export function getExampleDetail(id) {
|
||
return http.request({
|
||
url: '/example/detail/' + id,
|
||
method: 'get',
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 添加案例演示
|
||
*
|
||
* @param data 案例演示数据
|
||
* @returns 新记录的 ID
|
||
*/
|
||
export function exampleAdd(data: any) {
|
||
return http.request({
|
||
url: '/example/add',
|
||
method: 'POST',
|
||
data,
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 更新案例演示
|
||
*
|
||
* @param data 案例演示数据,需包含主键 id
|
||
* @returns 操作结果
|
||
*/
|
||
export function exampleUpdate(data: any) {
|
||
return http.request({
|
||
url: '/example/update',
|
||
method: 'PUT',
|
||
data,
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 删除案例演示
|
||
*
|
||
* @param id 案例演示ID
|
||
* @returns 操作结果
|
||
*/
|
||
export function exampleDelete(id) {
|
||
return http.request({
|
||
url: '/example/delete/' + id,
|
||
method: 'DELETE',
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 批量删除案例演示
|
||
*
|
||
* @param data 含 ids 数组的对象,形如 { ids: [1, 2, 3] }
|
||
* @returns 操作结果
|
||
*/
|
||
export function exampleBatchDelete(data: any) {
|
||
return http.request({
|
||
url: '/example/batchDelete',
|
||
method: 'DELETE',
|
||
data,
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 导入案例演示
|
||
*
|
||
* @param data 文件数据(FormData)
|
||
* @returns 导入结果,含成功数量与失败明细
|
||
*/
|
||
export function exampleImport(data) {
|
||
return http.request({
|
||
url: '/example/import',
|
||
method: 'POST',
|
||
data,
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 导出案例演示
|
||
*
|
||
* @param params 查询参数
|
||
* @returns 导出文件的 URL
|
||
*/
|
||
export function exampleExport(params) {
|
||
return http.request({
|
||
url: '/example/export',
|
||
method: 'GET',
|
||
params,
|
||
});
|
||
}
|
||
```
|
||
|
||
## 步骤二:创建页面组件
|
||
|
||
在 `ui/src/views/tool/example/` 下创建以下 5 个文件:
|
||
|
||
| 文件 | 说明 |
|
||
|------|------|
|
||
| `index.vue` | 列表页(表格 + 搜索 + 分页) |
|
||
| `edit.vue` | 新增/编辑弹窗表单 |
|
||
| `detail.vue` | 详情查看页 |
|
||
| `columns.ts` | 表格列定义 |
|
||
| `querySchemas.ts` | 搜索表单字段定义 |
|
||
|
||
:::tip 关于以下示例
|
||
以下代码使用原生 `a-table` / `a-form` 简化展示核心逻辑。实际项目中应使用封装好的 `BasicTable` + `useForm` 等组合式组件,详见[第 9 章组件文档](/zh/frontend/overview.html)。
|
||
:::
|
||
|
||
`index.vue`(列表页)示例:
|
||
|
||
```vue
|
||
<!-- ui/src/views/tool/example/index.vue -->
|
||
<template>
|
||
<div>
|
||
<!-- 搜索表单 -->
|
||
<a-form :model="searchForm" layout="inline">
|
||
<a-form-item label="名称">
|
||
<a-input v-model:value="searchForm.name" placeholder="请输入名称" allow-clear />
|
||
</a-form-item>
|
||
<a-form-item label="状态">
|
||
<a-select v-model:value="searchForm.status" placeholder="请选择" allow-clear>
|
||
<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-button type="primary" @click="handleSearch">搜索</a-button>
|
||
<a-button style="margin-left: 8px" @click="handleReset">重置</a-button>
|
||
</a-form-item>
|
||
</a-form>
|
||
|
||
<!-- 操作按钮 -->
|
||
<a-space style="margin: 10px 0">
|
||
<a-button type="primary" @click="handleAdd">新增</a-button>
|
||
<a-button danger :disabled="!selectedIds.length" @click="handleBatchDelete">
|
||
批量删除
|
||
</a-button>
|
||
</a-space>
|
||
|
||
<!-- 数据表格 -->
|
||
<a-table
|
||
:data-source="tableData"
|
||
:pagination="false"
|
||
:row-key="(row) => row.id"
|
||
:row-selection="{ onChange: handleSelectionChange }"
|
||
>
|
||
<a-table-column title="ID" data-index="id" width="80" />
|
||
<a-table-column title="名称" data-index="name" />
|
||
<a-table-column title="类型" data-index="typeText" width="100" />
|
||
<a-table-column title="状态" data-index="statusText" width="100" />
|
||
<a-table-column title="排序" data-index="sort" width="80" />
|
||
<a-table-column title="创建时间" data-index="createTime" width="180" />
|
||
<a-table-column title="操作" width="200">
|
||
<template #bodyCell="{ record }">
|
||
<a-space>
|
||
<a-button size="small" type="primary" @click="handleEdit(record)">编辑</a-button>
|
||
<a-button size="small" danger @click="handleDelete(record.id)">删除</a-button>
|
||
</a-space>
|
||
</template>
|
||
</a-table-column>
|
||
</a-table>
|
||
|
||
<!-- 分页 -->
|
||
<a-pagination
|
||
style="margin-top: 10px; text-align: right"
|
||
v-model:current="pageNo"
|
||
v-model:page-size="pageSize"
|
||
:total="total"
|
||
show-total
|
||
@change="fetchData"
|
||
/>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, onMounted } from 'vue';
|
||
import { getExamplePage, exampleDelete, exampleBatchDelete } from '@/api/tool/example';
|
||
|
||
const searchForm = ref({ name: '', status: '' });
|
||
const tableData = ref([]);
|
||
const pageNo = ref(1);
|
||
const pageSize = ref(20);
|
||
const total = ref(0);
|
||
const selectedIds = ref([]);
|
||
|
||
const fetchData = async () => {
|
||
const res = await getExamplePage({
|
||
pageNo: pageNo.value,
|
||
pageSize: pageSize.value,
|
||
...searchForm.value,
|
||
});
|
||
tableData.value = res.records;
|
||
total.value = res.total;
|
||
};
|
||
|
||
const handleSearch = () => { pageNo.value = 1; fetchData(); };
|
||
const handleReset = () => { searchForm.value = { name: '', status: '' }; handleSearch(); };
|
||
const handleDelete = async (id: number) => { await exampleDelete(id); fetchData(); };
|
||
const handleBatchDelete = async () => { await exampleBatchDelete({ ids: selectedIds.value }); fetchData(); };
|
||
const handleSelectionChange = (keys: any[]) => { selectedIds.value = keys; };
|
||
|
||
onMounted(() => fetchData());
|
||
</script>
|
||
```
|
||
|
||
## 步骤三:配置菜单
|
||
|
||
在后端菜单管理中,将组件路径设置为:
|
||
|
||
```text
|
||
tool/example/index
|
||
```
|
||
|
||
前端路由系统会自动将此路径映射到 `ui/src/views/tool/example/index.vue`。
|
||
|
||
## 步骤四:测试联调
|
||
|
||
1. 确保后端服务运行:`php think run`
|
||
2. 确保前端服务运行:`pnpm dev`
|
||
3. 刷新前端页面,侧边栏应出现"案例演示"菜单
|
||
4. 点击菜单,测试增删改查功能
|
||
|
||
详细前端开发内容请参考[第 9 章](/zh/frontend/overview.html)。 |