Files
rxthinkcmf-wiki/RXThinkCMF_Laravel13_NaiveVue/zh/guide/frontend-integration.md
T
2026-10-08 17:50:29 +08:00

269 lines
8.0 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.
# 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、current、size
*/
export function getExamplePage(params?: Record<string, any>) {
return http.request({
url: '/example/page',
method: 'GET',
params,
});
}
/**
* 获取全部案例演示列表
*/
export function getExampleList(params?: Record<string, any>) {
return http.request({
url: '/example/list',
method: 'GET',
params,
});
}
/**
* 根据 ID 获取案例演示详情
*/
export function getExampleDetail(id: number) {
return http.request({
url: '/example/detail/' + id,
method: 'get',
});
}
/**
* 添加案例演示(入参 camelCase,后端 FormRequest 自动转换)
*/
export function exampleAdd(data: any) {
return http.request({
url: '/example/add',
method: 'POST',
data,
});
}
/**
* 更新案例演示(需包含主键 id)
*/
export function exampleUpdate(data: any) {
return http.request({
url: '/example/update',
method: 'PUT',
data,
});
}
/**
* 删除案例演示
*/
export function exampleDelete(id: number) {
return http.request({
url: '/example/delete/' + id,
method: 'DELETE',
});
}
/**
* 批量删除案例演示(形如 { ids: [1, 2, 3] })
*
* 注意:批量删除后端路由为 DELETE(请求体仍传 ids 数组)。
*/
export function exampleBatchDelete(data: any) {
return http.request({
url: '/example/batchDelete',
method: 'DELETE',
data,
});
}
/**
* 导入案例演示(FormData 文件)
*/
export function exampleImport(data: any) {
return http.request({
url: '/example/import',
method: 'POST',
data,
});
}
/**
* 导出案例演示(返回文件 URL)
*/
export function exampleExport(params?: Record<string, any>) {
return http.request({
url: '/example/export',
method: 'GET',
params,
});
}
```
:::tip
HTTP 封装(Token 携带、401 处理、统一错误提示、camel/snake 转换)由 `http` 实例统一完成,API 模块只描述"哪个地址、什么方法"。见 [9.4 HTTP 请求封装](/zh/frontend/http.html)。
:::
## 步骤二:创建页面组件
在 `ui/src/views/tool/example/` 下创建以下 5 个文件(与代码生成器输出结构一致):
| 文件 | 说明 |
|------|------|
| `index.vue` | 列表页(表格 + 搜索 + 分页) |
| `edit.vue` | 新增/编辑弹窗表单 |
| `detail.vue` | 详情查看页 |
| `columns.ts` | 表格列定义 |
| `querySchemas.ts` | 搜索表单字段定义 |
:::tip 关于以下示例
以下代码使用原生 `el-table` / `el-form` 简化展示核心逻辑。实际项目中应使用封装好的 `BasicTable` + `useForm` 等组合式组件,详见 [第 9 章组件文档](/zh/frontend/overview.html)。
:::
`index.vue`(列表页)示例:
```vue
<!-- ui/src/views/tool/example/index.vue -->
<template>
<div>
<!-- 搜索表单 -->
<el-form :model="searchForm" inline>
<el-form-item label="名称">
<el-input v-model="searchForm.name" placeholder="请输入名称" />
</el-form-item>
<el-form-item label="状态">
<el-select v-model="searchForm.status" placeholder="请选择" clearable>
<el-option label="启用" :value="1" />
<el-option label="停用" :value="2" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">搜索</el-button>
<el-button @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
<!-- 操作按钮(v-perm 控制显示,与后端权限码一致) -->
<el-button v-perm="['sys:example:add']" type="primary" @click="handleAdd">新增</el-button>
<el-button v-perm="['sys:example:batchDelete']" type="danger"
:disabled="!selectedIds.length" @click="handleBatchDelete">批量删除</el-button>
<!-- 数据表格 -->
<el-table :data="tableData" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" />
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="name" label="名称" />
<el-table-column prop="statusText" label="状态" width="100" />
<el-table-column prop="sort" label="排序" width="80" />
<el-table-column prop="createTime" label="创建时间" width="180" />
<el-table-column label="操作" width="200">
<template #default="{ row }">
<el-button v-perm="['sys:example:update']" size="small" @click="handleEdit(row)">编辑</el-button>
<el-button v-perm="['sys:example:delete']" size="small" type="danger"
@click="handleDelete(row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页(后端固定返回 records / total / current / size / pages) -->
<el-pagination
v-model:current-page="pageNo"
v-model:page-size="pageSize"
:total="total"
@current-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 = (rows: any[]) => { selectedIds.value = rows.map((r) => r.id); };
onMounted(() => fetchData());
</script>
```
## 步骤三:配置菜单
在后端菜单管理中,将组件路径设置为:
```text
tool/example/index
```
前端路由系统会自动将此路径映射到 `ui/src/views/tool/example/index.vue`(动态路由由后端菜单数据驱动)。
## 步骤四:测试联调
1. 确保后端服务运行:`php artisan serve`(或 `composer dev`)
2. 确保前端服务运行:`cd ui && pnpm dev`(开发端口 8001)
3. 为测试角色分配新菜单权限(见 [3.8 权限节点配置](/zh/guide/permission.html))
4. 刷新前端页面,侧边栏应出现"案例演示"菜单
5. 点击菜单,测试增删改查功能
### 联调自查顺序
```text
接口 404 → 路由未注册 / URL、HTTP 方法与 routes/api.php 不一致
接口 401 → Token 未携带或过期(看 http 封装是否注入 Authorization)
接口 403 → 权限码未配置 / 角色未分配(见 3.8)
接口 code=1 → 看 msg 与 data(FormRequest 字段错误明细)
表格不显示 → 响应结构应为 data.records / data.total,检查绑定字段
菜单不显示 → 组件路径错误 / 角色未分配菜单 / 前端 views 下无对应 .vue
```
详细前端开发内容请参考 [第 9 章](/zh/frontend/overview.html)。