签入版本

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
@@ -147,92 +147,165 @@ HTTP 封装(Token 携带、401 处理、统一错误提示、camel/snake 转
| `querySchemas.ts` | 搜索表单字段定义 |
:::tip 关于以下示例
以下代码使用原生 `el-table` / `el-form` 简化展示核心逻辑。实际项目中应使用封装好的 `BasicTable` + `useForm` 等组合式组件,详见 [第 9 章组件文档](/zh/frontend/overview.html)。
以下代码来自项目实际的 `example` 演示模块,使用封装好的 `BasicTable` + `BasicForm` 组合式组件(n-card 布局、`v-perm` 按钮权限、`$message`/`$dialog` 反馈),详见 [第 9 章组件文档](/zh/frontend/overview.html)。
:::
`index.vue`(列表页)示例:
```vue
<!-- ui/src/views/tool/example/index.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>
<!-- 搜索表单卡片 -->
<n-card :bordered="false" class="pt-3 mb-3 proCard">
<BasicForm @register="register" @submit="handleSubmit" @reset="handleReset" />
</n-card>
<!-- 操作按钮(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>
<!-- 数据表格卡片 -->
<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"
>
<!-- 表格顶部工具栏(v-perm 控制显示,与后端权限码一致) -->
<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>
</el-table-column>
</el-table>
</BasicTable>
</n-card>
<!-- 分页(后端固定返回 records / total / current / size / pages) -->
<el-pagination
v-model:current-page="pageNo"
v-model:page-size="pageSize"
:total="total"
@current-change="fetchData"
<!-- 新增/编辑弹窗,主键为 0 时表示新增 -->
<editDialog
ref="createModalRef"
v-if="editVisible"
:id="currentId"
v-model:visible="editVisible"
@success="reloadTable('noRefresh')"
/>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
<script lang="ts" setup>
import { h, nextTick, reactive, ref } from 'vue';
import { useMessage } from '@/hooks/web/useMessage';
import { useDialog } from '@/hooks/web/useDialog';
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 searchForm = ref({ name: '', status: '' });
const tableData = ref([]);
const pageNo = ref(1);
const pageSize = ref(20);
const total = ref(0);
const selectedIds = ref([]);
const $message = useMessage();
const $dialog = useDialog();
const basicTableRef = ref();
const createModalRef = ref();
const editVisible = ref(false);
const currentId = ref(0);
const rowKeys = 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 formParams = reactive({ name: '', type: '', status: '' });
// 操作列(固定右侧,auth 声明权限码,配合 v-perm 控制显隐)
const actionColumn = reactive({
width: 280,
title: '操作',
key: 'action',
fixed: 'right',
render(record) {
return h(TableAction as any, {
style: 'button',
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 内置分页,后端固定返回 records / total / current / size / pages)
const loadDataTable = async (params) => {
rowKeys.value = [];
return await getExamplePage({ ...formParams, ...params });
};
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); };
// 数据行选中(记录勾选行的 ID,供批量删除使用)
function onCheckedRow(keys) {
rowKeys.value = keys;
}
onMounted(() => fetchData());
// 刷新表格(不传参回第一页,传 'noRefresh' 保持当前页)
function reloadTable(noRefresh = '') {
basicTableRef.value.reload(noRefresh ? {} : { pageNo: 1 });
}
// 注册搜索表单(字段来自 querySchemas.ts)
const [register] = useForm({
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
labelWidth: 80,
schemas,
});
// 提交搜索(条件写回 formParams 后回第一页)
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();
}
// 新增(主键置 0 表示新增)
const handleAdd = async () => {
currentId.value = 0;
editVisible.value = true;
await nextTick();
createModalRef.value.openModal();
};
// 编辑
async function handleEdit(record) {
currentId.value = record.id;
editVisible.value = true;
await nextTick();
createModalRef.value.openModal();
}
// 删除(单条 / 批量,先弹确认框,成功后 $message 提示并刷新)
async function handleDelete(record?) {
$dialog.warning({
title: '提示',
content: '确定要删除选中的案例演示吗?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: async () => {
record?.id ? await exampleDelete(record.id) : await exampleBatchDelete({ ids: rowKeys.value });
$message.success('删除成功');
reloadTable();
},
});
}
</script>
```
@@ -85,7 +85,7 @@ app/Http/Requests/ExampleRequest.php # 表单请求验证
app/Services/ExampleService.php # 业务服务
app/Http/Controllers/Api/ExampleController.php # 控制器
routes/api.php # 路由(example 模块 9 条)
# 前端(独立 EleVue 仓库):
# 前端(本仓库 ui/ 目录):
# ui/src/api/tool/example.ts # 前端 API 模块
# ui/src/views/tool/example/ # 前端页面
```
@@ -123,5 +123,5 @@ Route::post('{模块}/import', [Api\XxxController::class, 'import']); //
php artisan route:list
# 测试路由是否正确(需先登录获取 token,见 2.7 第一个接口调试)
curl http://localhost:8000/api/example/page -H "Authorization: Bearer <token>"
curl http://admin.laravel13.naivevue/api/example/page -H "Authorization: Bearer <token>"
```