签入版本

This commit is contained in:
2026-10-10 12:24:24 +08:00
parent 49b04a1ec4
commit 3423dea03f
11 changed files with 2372 additions and 43 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,4 @@
allowBuilds:
'@parcel/watcher': set this to true or false
esbuild: set this to true or false
vue-demi: set this to true or false
@@ -53,6 +53,8 @@
```typescript ```typescript
// Axios 响应拦截器 // Axios 响应拦截器
import { message } from 'ant-design-vue';
axios.interceptors.response.use(response => { axios.interceptors.response.use(response => {
const { code, msg, data } = response.data; const { code, msg, data } = response.data;
@@ -67,12 +69,12 @@ axios.interceptors.response.use(response => {
} }
if (code === 403) { if (code === 403) {
ElMessage.error(msg || '无权限'); message.error(msg || '无权限');
return; return;
} }
// 其他错误 // 其他错误
ElMessage.error(msg || '操作失败'); message.error(msg || '操作失败');
return Promise.reject(new Error(msg)); return Promise.reject(new Error(msg));
}); });
``` ```
@@ -253,6 +253,7 @@ $this->fail('缺少id参数', 1);
```typescript ```typescript
// src/utils/http/axios/index.ts // src/utils/http/axios/index.ts
import { message } from 'ant-design-vue';
axios.interceptors.response.use( axios.interceptors.response.use(
response => { response => {
@@ -272,16 +273,16 @@ axios.interceptors.response.use(
// 权限不足 // 权限不足
if (code === 403) { if (code === 403) {
ElMessage.error(msg || '无访问权限'); message.error(msg || '无访问权限');
return Promise.reject(new Error(msg)); return Promise.reject(new Error(msg));
} }
// 其他业务错误 // 其他业务错误
ElMessage.error(msg || '操作失败'); message.error(msg || '操作失败');
return Promise.reject(new Error(msg)); return Promise.reject(new Error(msg));
}, },
error => { error => {
ElMessage.error('网络错误'); message.error('网络错误');
return Promise.reject(error); return Promise.reject(error);
} }
); );
@@ -305,9 +306,9 @@ const handleImport = async (file) => {
const res = await importUsers(file); const res = await importUsers(file);
// res = { count: 8, errors: [{ row: 3, reason: "..." }] } // res = { count: 8, errors: [{ row: 3, reason: "..." }] }
if (res.errors.length === 0) { if (res.errors.length === 0) {
ElMessage.success(`成功导入 ${res.count} 条`); message.success(`成功导入 ${res.count} 条`);
} else { } else {
ElMessage.warning(`成功 ${res.count} 条,失败 ${res.errors.length} 条`); message.warning(`成功 ${res.count} 条,失败 ${res.errors.length} 条`);
} }
}; };
``` ```
@@ -104,7 +104,7 @@ export function exampleBatchDelete(data: any) {
```vue ```vue
<script setup lang="ts"> <script setup lang="ts">
import { getExamplePage, exampleDelete } from '@/api/tool/example'; import { getExamplePage, exampleDelete } from '@/api/tool/example';
import { ElMessage, ElMessageBox } from 'element-plus'; import { Modal, message } from 'ant-design-vue';
const tableData = ref([]); const tableData = ref([]);
@@ -116,10 +116,15 @@ const fetchData = async () => {
// 删除 // 删除
const handleDelete = async (id) => { const handleDelete = async (id) => {
await ElMessageBox.confirm('确认删除?', '提示', { type: 'warning' }); Modal.confirm({
await exampleDelete(id); title: '提示',
ElMessage.success('删除成功'); content: '确认删除?',
fetchData(); onOk: async () => {
await exampleDelete(id);
message.success('删除成功');
fetchData();
},
});
}; };
</script> </script>
``` ```
@@ -30,6 +30,8 @@ config.headers['Authorization'] = `Bearer ${token}`;
### 响应拦截器(统一错误处理) ### 响应拦截器(统一错误处理)
```typescript ```typescript
import { message } from 'ant-design-vue';
// 响应处理逻辑 // 响应处理逻辑
const { code, data, msg } = res.data; const { code, data, msg } = res.data;
@@ -43,7 +45,7 @@ if (code === 401) {
} }
// 其他错误,显示提示 // 其他错误,显示提示
ElMessage.error(msg || '操作失败'); message.error(msg || '操作失败');
``` ```
:::tip 响应处理规则 :::tip 响应处理规则
@@ -80,8 +80,8 @@ export default {
<template> <template>
<div> <div>
<!-- 基础翻译 --> <!-- 基础翻译 -->
<el-button>{{ $t('common.search') }}</el-button> <a-button>{{ $t('common.search') }}</a-button>
<el-button>{{ $t('common.reset') }}</el-button> <a-button>{{ $t('common.reset') }}</a-button>
<!-- 带参数翻译 --> <!-- 带参数翻译 -->
<p>{{ $t('message.welcome', { name: 'Admin' }) }}</p> <p>{{ $t('message.welcome', { name: 'Admin' }) }}</p>
@@ -134,15 +134,17 @@ const columns = computed(() => [
```vue ```vue
<!-- ui/src/components/LangSwitch/index.vue --> <!-- ui/src/components/LangSwitch/index.vue -->
<template> <template>
<el-dropdown @command="switchLang"> <a-dropdown>
<el-button :icon="Icon" circle /> <a-button>
<template #dropdown> <GlobeOutlined />
<el-dropdown-menu> </a-button>
<el-dropdown-item command="zh-CN">中文</el-dropdown-item> <template #overlay>
<el-dropdown-item command="en-US">English</el-dropdown-item> <a-menu @click="({ key }) => switchLang(key)">
</el-dropdown-menu> <a-menu-item key="zh-CN">中文</a-menu-item>
<a-menu-item key="en-US">English</a-menu-item>
</a-menu>
</template> </template>
</el-dropdown> </a-dropdown>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -23,7 +23,7 @@
| `ui/src/views/{app}/{name}/index.vue` | index.vue.tpl | 列表页(含搜索、表格、新增/编辑/详情弹窗、导入导出按钮) | | `ui/src/views/{app}/{name}/index.vue` | index.vue.tpl | 列表页(含搜索、表格、新增/编辑/详情弹窗、导入导出按钮) |
| `ui/src/views/{app}/{name}/edit.vue` | edit.vue.tpl | 编辑弹窗(新增/复用) | | `ui/src/views/{app}/{name}/edit.vue` | edit.vue.tpl | 编辑弹窗(新增/复用) |
| `ui/src/views/{app}/{name}/detail.vue` | detail.vue.tpl | 详情弹窗 | | `ui/src/views/{app}/{name}/detail.vue` | detail.vue.tpl | 详情弹窗 |
| `ui/src/views/{app}/{name}/columns.ts` | columns.ts.tpl | 表格列配置(含图片 ElAvatar、选项 ElTag、链接等渲染) | | `ui/src/views/{app}/{name}/columns.ts` | columns.ts.tpl | 表格列配置(含图片 Avatar、选项 Tag、链接等渲染) |
| `ui/src/views/{app}/{name}/querySchemas.ts` | querySchemas.ts.tpl | 查询表单配置(按字段类型自动选 Input/Select/InputNumber/DatePicker) | | `ui/src/views/{app}/{name}/querySchemas.ts` | querySchemas.ts.tpl | 查询表单配置(按字段类型自动选 Input/Select/InputNumber/DatePicker) |
### 前端文件(ui2/ 树形模板 — 4 个文件) ### 前端文件(ui2/ 树形模板 — 4 个文件)
@@ -64,7 +64,7 @@ public function getTableList(): array
| 类型 | VARCHAR | | 类型 | VARCHAR |
| 匹配 | 注释或字段名匹配 `/图片\|封面\|头像\|logo\|icon\|image\|avatar\|photo/i` | | 匹配 | 注释或字段名匹配 `/图片\|封面\|头像\|logo\|icon\|image\|avatar\|photo/i` |
识别后:`is_image = true`,归入 `file_fields`,前端用 ElAvatar 渲染。 识别后:`is_image = true`,归入 `file_fields`,前端用 Avatar 渲染。
### 富文本字段 ### 富文本字段
@@ -102,12 +102,12 @@ public function getTableList(): array
标签颜色映射: 标签颜色映射:
| 选项值 | 标签颜色 | Element Plus type | | 选项值 | 标签颜色 | Ant Design color |
|--------|---------|-------------------| |--------|---------|-------------------|
| 1 | 绿色 | `success` | | 1 | 绿色 | `success` |
| 2 | 红色 | `danger` | | 2 | 红色 | `error` |
| 3 | 橙色 | `warning` | | 3 | 橙色 | `warning` |
| 其他 | 灰色 | `info` | | 其他 | 灰色 | `default` |
识别后:`choices` 数组非空,自动归入 `eq_fields`(精确匹配)和 `serialize_maps`(字典映射)。 识别后:`choices` 数组非空,自动归入 `eq_fields`(精确匹配)和 `serialize_maps`(字典映射)。
@@ -107,7 +107,7 @@ $field = [
'default' => 0, // 默认值 'default' => 0, // 默认值
'is_image' => false, // 是否为图片字段 'is_image' => false, // 是否为图片字段
'is_rich_text' => false, // 是否为富文本字段 'is_rich_text' => false, // 是否为富文本字段
'choices' => [ // 选项列表(有值时可生成 ElTag 渲染) 'choices' => [ // 选项列表(有值时可生成 Tag 渲染)
['value' => 0, 'label' => '正常', 'type' => 'success'], ['value' => 0, 'label' => '正常', 'type' => 'success'],
['value' => 1, 'label' => '禁用', 'type' => 'danger'], ['value' => 1, 'label' => '禁用', 'type' => 'danger'],
], ],
@@ -159,28 +159,37 @@ class <?php echo $model_class_name ?>Logic extends BaseLogic
```typescript ```typescript
import { h } from 'vue'; import { h } from 'vue';
import { ElAvatar, ElTag } from 'element-plus'; import { Tag, Avatar } from 'ant-design-vue';
export const columns = [ export const columns = [
{ type: 'selection' }, {
{ label: 'ID', prop: 'id', fixed: 'left', width: 50 }, title: 'ID',
dataIndex: '<?php echo $primary_key ?>',
fixed: 'left',
width: 50,
},
<?php foreach ($list_fields as $field): ?> <?php foreach ($list_fields as $field): ?>
{ {
label: '<?php echo $field['label'] ?>', title: '<?php echo $field['label'] ?>',
prop: '<?php echo $field['camel_name'] ?>', dataIndex: '<?php echo $field['camel_name'] ?>',
key: '<?php echo $field['camel_name'] ?>',
<?php if ($field['is_image']): ?> <?php if ($field['is_image']): ?>
render(record) { customRender({ record }) {
return h(ElAvatar, { size: 48, src: record.row.<?php echo $field['camel_name'] ?> }); return h(Avatar, {
size: 48,
src: record.<?php echo $field['camel_name'] ?>,
shape: 'square',
});
}, },
<?php elseif ($field['choices']): ?> <?php elseif ($field['choices']): ?>
render(record) { customRender({ record }) {
const map = { const statusMap = {
<?php foreach ($field['choices'] as $choice): ?> <?php foreach ($field['choices'] as $choice): ?>
<?php echo $choice['value'] ?>: { text: '<?php echo $choice['label'] ?>', type: '<?php echo $choice['type'] ?>' }, <?php echo $choice['value'] ?>: { text: '<?php echo $choice['label'] ?>', color: '<?php echo ($choice['type'] === 'danger') ? 'error' : (($choice['type'] === 'info') ? 'default' : $choice['type']) ?>' },
<?php endforeach; ?> <?php endforeach; ?>
}; };
const s = map[record.row.<?php echo $field['camel_name'] ?>]; const status = statusMap[record.<?php echo $field['camel_name'] ?>];
return h(ElTag, { type: s?.type || 'info' }, { default: () => s?.text }); return h(Tag, { color: status?.color || 'default' }, { default: () => status?.text });
}, },
<?php endif; ?> <?php endif; ?>
}, },
@@ -221,7 +230,7 @@ export const columns = [
| `index.vue` | 列表页 | 导入 columns.ts、querySchemas.ts | | `index.vue` | 列表页 | 导入 columns.ts、querySchemas.ts |
| `edit.vue` | 编辑弹窗 | 被 index.vue 动态加载 | | `edit.vue` | 编辑弹窗 | 被 index.vue 动态加载 |
| `detail.vue` | 详情弹窗 | 被 index.vue 动态加载 | | `detail.vue` | 详情弹窗 | 被 index.vue 动态加载 |
| `columns.ts` | 表格列配置 | 被 index.vue 导入,含 ElTag/ElAvatar 渲染 | | `columns.ts` | 表格列配置 | 被 index.vue 导入,含 Tag/Avatar 渲染 |
| `querySchemas.ts` | 查询表单配置 | 被 index.vue 导入,含 Select/Input/DatePicker 组件 | | `querySchemas.ts` | 查询表单配置 | 被 index.vue 导入,含 Select/Input/DatePicker 组件 |
## 相关文档 ## 相关文档
@@ -57,7 +57,7 @@ feat(generator): 支持树形表格模板生成
- 新增 ui2/ 树形模板目录 - 新增 ui2/ 树形模板目录
- 自动识别 parent_id/pid 字段 - 自动识别 parent_id/pid 字段
- 支持 el-tree-select 组件 - 支持 a-tree-select 组件
Closes #123 Closes #123
``` ```