8.3 KiB
8.3 KiB
3.9 前端联调指引
:::tip 概述 后端接口开发完成后,需要在前端创建对应的 API 模块和页面组件。本节以 example 模块为例,提供简要指引,详细内容参考第 9 章。 :::
联调流程
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(来自项目实际代码):
// 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 关于以下示例
以下代码来自项目实际的 example 演示模块,使用封装好的 BasicTable + BasicForm 组合式组件,详见第 9 章组件文档。
:::
index.vue(列表页)示例:
<!-- ui/src/views/tool/example/index.vue(节选,来自项目实际代码) -->
<template>
<div>
<!-- 搜索表单卡片 -->
<n-card :bordered="false" class="pt-3 mb-3 proCard">
<BasicForm @register="register" @submit="handleSubmit" @reset="handleReset" />
</n-card>
<!-- 数据表格卡片 -->
<n-card :bordered="false" class="proCard">
<BasicTable
:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.id"
ref="basicTableRef"
:actionColumn="actionColumn"
@update:checked-row-keys="onCheckedRow"
>
<!-- 表格顶部工具栏 -->
<template #tableTitle>
<n-space>
<n-button type="primary" @click="handleAdd" v-perm="['sys:example:add']">
添加案例演示
</n-button>
<n-button
type="error"
@click="handleDelete()"
:disabled="!rowKeys.length"
v-perm="['sys:example:batchDelete']"
>
批量删除
</n-button>
</n-space>
</template>
</BasicTable>
</n-card>
<!-- 新增/编辑弹窗 -->
<editDialog
ref="createModalRef"
v-if="editVisible"
v-model:visible="editVisible"
@success="reloadTable('noRefresh')"
/>
</div>
</template>
<script lang="ts" setup>
import { h, nextTick, reactive, ref } from 'vue';
import { useMessage, useDialog } from 'naive-ui';
import { BasicTable, TableAction } from '@/components/Table';
import { BasicForm, useForm } from '@/components/Form';
import {
getExamplePage,
exampleDelete,
exampleBatchDelete,
} from '@/api/tool/example';
import { columns } from './columns';
import { schemas } from './querySchemas';
import editDialog from './edit.vue';
const message = useMessage();
const dialog = useDialog();
const basicTableRef = ref();
const createModalRef = ref();
const editVisible = ref(false);
const rowKeys = ref([]);
// 查询参数(与搜索表单字段一一对应)
const formParams = reactive({ name: '', type: '', status: '' });
// 操作列(固定右侧,auth 控制按钮权限)
const actionColumn = reactive({
width: 280,
title: '操作',
key: 'action',
fixed: 'right',
render(record) {
return h(TableAction as any, {
actions: [
{ label: '编辑', type: 'info', onClick: handleEdit.bind(null, record), auth: ['sys:example:update'] },
{ label: '删除', type: 'error', onClick: handleDelete.bind(null, record), auth: ['sys:example:delete'] },
],
});
},
});
// 分页数据请求(BasicTable 内置分页)
const loadDataTable = async (params) => {
rowKeys.value = [];
return await getExamplePage({ ...formParams, ...params });
};
// 数据行选中
function onCheckedRow(keys) {
rowKeys.value = keys;
}
// 刷新表格
function reloadTable(noRefresh = '') {
basicTableRef.value.reload(noRefresh ? {} : { pageNo: 1 });
}
// 注册搜索表单
const [register] = useForm({
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
labelWidth: 80,
schemas,
});
// 提交搜索(条件写回后回第一页)
function handleSubmit(values) {
for (const key in formParams) formParams[key] = '';
Object.assign(formParams, values);
reloadTable();
}
// 重置搜索
function handleReset() {
for (const key in formParams) formParams[key] = '';
reloadTable();
}
// 新增
const handleAdd = async () => {
editVisible.value = true;
await nextTick();
createModalRef.value.openModal();
};
// 编辑
async function handleEdit(record) {
editVisible.value = true;
await nextTick();
createModalRef.value.openModal(record.id);
}
// 删除(单条 / 批量,先弹确认框)
function handleDelete(record?) {
const ids = record ? [record.id] : rowKeys.value;
if (!ids.length) {
message.warning('请选择要删除的数据');
return;
}
dialog.warning({
title: '提示',
content: record ? '确定删除该记录?' : `确定删除选中的 ${ids.length} 条记录?`,
positiveText: '确定',
negativeText: '取消',
onPositiveClick: async () => {
record ? await exampleDelete(record.id) : await exampleBatchDelete({ ids });
message.success('删除成功');
reloadTable();
},
});
}
</script>
步骤三:配置菜单
在后端菜单管理中,将组件路径设置为:
tool/example/index
前端路由系统会自动将此路径映射到 ui/src/views/tool/example/index.vue。
步骤四:测试联调
- 确保后端服务运行:
php think run - 确保前端服务运行:
pnpm dev - 刷新前端页面,侧边栏应出现"案例演示"菜单
- 点击菜单,测试增删改查功能
详细前端开发内容请参考第 9 章。