486 lines
14 KiB
Markdown
486 lines
14 KiB
Markdown
|
|
# 交互规范
|
|||
|
|
|
|||
|
|
1、使用中文交互
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# 项目架构概览
|
|||
|
|
|
|||
|
|
## 技术栈
|
|||
|
|
|
|||
|
|
|
|||
|
|
| 层级 | 技术 |
|
|||
|
|
| --------- | ---------------------------- |
|
|||
|
|
| 框架 | Vue 3 + TypeScript 5 |
|
|||
|
|
| 构建 | Vite 4 |
|
|||
|
|
| UI 组件库 | Ant Design Vue 3 |
|
|||
|
|
| 状态管理 | Pinia 2 |
|
|||
|
|
| 路由 | Vue Router 4 |
|
|||
|
|
| HTTP | Axios(自定义封装`defHttp`) |
|
|||
|
|
| 样式 | Less + Windi CSS |
|
|||
|
|
| 国际化 | Vue I18n 9 |
|
|||
|
|
| 基础框架 | Vben Admin(深度定制) |
|
|||
|
|
| 包管理 | pnpm |
|
|||
|
|
|
|||
|
|
> 注意:本项目与 Vue 3.4.x 不兼容,请保持 Vue ~3.3.4。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 目录结构
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
src/
|
|||
|
|
├── api/ # HTTP 请求模块,按业务域分组
|
|||
|
|
│ └── <domain>/
|
|||
|
|
│ └── <module>/
|
|||
|
|
│ ├── index.ts # 请求函数
|
|||
|
|
│ └── model/ # 请求/响应 TS 类型
|
|||
|
|
├── assets/ # 静态资源、全局样式
|
|||
|
|
├── components/ # 可复用组件(BasicTable、BasicForm、Modal 等)
|
|||
|
|
├── design/ # 全局 Less 主题入口
|
|||
|
|
├── hooks/ # 组合式函数(composables)
|
|||
|
|
├── layouts/ # 布局组件(header、sider、tabs)
|
|||
|
|
├── locales/ # i18n 配置
|
|||
|
|
├── router/ # 路由配置、守卫
|
|||
|
|
├── store/ # Pinia 状态模块
|
|||
|
|
│ └── modules/ # app / user / permission / locale / lock / multipleTab
|
|||
|
|
├── utils/ # 工具函数
|
|||
|
|
│ └── http/axios/ # HTTP 封装核心
|
|||
|
|
└── views/ # 业务页面,按业务域分组
|
|||
|
|
└── <domain>/ # erp / system / workflow / contract / sales ...
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 路径别名
|
|||
|
|
|
|||
|
|
|
|||
|
|
| 别名 | 指向 |
|
|||
|
|
| --------- | --------------------------------- |
|
|||
|
|
| `/@/` | `src/` |
|
|||
|
|
| `/#/` | `types/` |
|
|||
|
|
| `/@bpmn/` | `src/views/workflow/design/bpmn/` |
|
|||
|
|
|
|||
|
|
**始终使用别名,禁止使用相对路径 `../../`。**
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# 代码规范
|
|||
|
|
|
|||
|
|
## 1. Vue 组件
|
|||
|
|
|
|||
|
|
### 新文件优先使用 `<script setup>`
|
|||
|
|
|
|||
|
|
```vue
|
|||
|
|
<script setup lang="ts">
|
|||
|
|
import { ref } from 'vue';
|
|||
|
|
import { useI18n } from '/@/hooks/web/useI18n';
|
|||
|
|
|
|||
|
|
const { t } = useI18n();
|
|||
|
|
const visible = ref(false);
|
|||
|
|
</script>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
存量代码中存在 `defineComponent` 写法,维护时保持原有风格,新增代码统一用 `<script setup>`。
|
|||
|
|
|
|||
|
|
### 文件命名
|
|||
|
|
|
|||
|
|
|
|||
|
|
| 场景 | 规范 | 示例 |
|
|||
|
|
| ----------- | ----------- | ----------------------------------- |
|
|||
|
|
| 页面入口 | `index.vue` | `src/views/system/user/index.vue` |
|
|||
|
|
| 弹窗/子组件 | PascalCase | `UserModal.vue`、`DetailDrawer.vue` |
|
|||
|
|
| 组合式函数 | camelCase | `useUserList.ts` |
|
|||
|
|
|
|||
|
|
### 事件处理函数命名
|
|||
|
|
|
|||
|
|
统一以 `handle` 开头:`handleEdit`、`handleDelete`、`handleSubmit`。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 2. API 模块
|
|||
|
|
|
|||
|
|
每个业务模块的 API 结构:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
src/api/<domain>/<module>/
|
|||
|
|
├── index.ts # 导出请求函数
|
|||
|
|
└── model/
|
|||
|
|
└── index.ts # 请求/响应类型定义
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**示例:**
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
// src/api/erp/purchase/apply/index.ts
|
|||
|
|
import { defHttp } from '/@/utils/http/axios';
|
|||
|
|
import type { ApplyListParams, ApplyListResult } from './model';
|
|||
|
|
|
|||
|
|
enum Api {
|
|||
|
|
List = '/erp/purchase/apply/list',
|
|||
|
|
Save = '/erp/purchase/apply/save',
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export const getApplyList = (params: ApplyListParams) =>
|
|||
|
|
defHttp.get<ApplyListResult>({ url: Api.List, params });
|
|||
|
|
|
|||
|
|
export const saveApply = (data: ApplyListParams) =>
|
|||
|
|
defHttp.post({ url: Api.Save, data });
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- 所有 HTTP 请求必须通过 `defHttp`,禁止直接使用 axios。
|
|||
|
|
- 接口路径统一用 `enum Api` 管理。
|
|||
|
|
- 错误提示通过 `errorMessageMode` 控制(`'modal'` | `'message'` | `'none'`)。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 3. 状态管理(Pinia)
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
// src/store/modules/xxx.ts
|
|||
|
|
import { defineStore } from 'pinia';
|
|||
|
|
import { store } from '/@/store';
|
|||
|
|
|
|||
|
|
export const useXxxStore = defineStore({
|
|||
|
|
id: 'app-xxx',
|
|||
|
|
state: () => ({ ... }),
|
|||
|
|
getters: { ... },
|
|||
|
|
actions: { ... },
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 在 setup 外使用
|
|||
|
|
export function useXxxStoreWithOut() {
|
|||
|
|
return useXxxStore(store);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- Store 命名:`useXxxStore`
|
|||
|
|
- 需要在 setup 外调用时,导出 `useXxxStoreWithOut`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 4. 表格页面(CRUD 标准模式)
|
|||
|
|
|
|||
|
|
```vue
|
|||
|
|
<template>
|
|||
|
|
<PageWrapper>
|
|||
|
|
<BasicTable @register="registerTable">
|
|||
|
|
<template #toolbar>
|
|||
|
|
<a-button type="primary" @click="handleAdd">新增</a-button>
|
|||
|
|
</template>
|
|||
|
|
<template #action="{ record }">
|
|||
|
|
<TableAction :actions="getActions(record)" />
|
|||
|
|
</template>
|
|||
|
|
</BasicTable>
|
|||
|
|
<XxxModal @register="registerModal" @success="reload" />
|
|||
|
|
</PageWrapper>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script setup lang="ts">
|
|||
|
|
import { useTable } from '/@/components/Table';
|
|||
|
|
import { useModal } from '/@/components/Modal';
|
|||
|
|
import { PageWrapper } from '/@/components/Page';
|
|||
|
|
import { TableAction } from '/@/components/Table';
|
|||
|
|
|
|||
|
|
const [registerTable, { reload }] = useTable({
|
|||
|
|
api: getXxxList,
|
|||
|
|
columns,
|
|||
|
|
useSearchForm: true,
|
|||
|
|
formConfig: { schemas },
|
|||
|
|
showTableSetting: true,
|
|||
|
|
bordered: true,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const [registerModal, { openModal }] = useModal();
|
|||
|
|
|
|||
|
|
function handleAdd() {
|
|||
|
|
openModal(true, { isUpdate: false });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function getActions(record) {
|
|||
|
|
return [
|
|||
|
|
{ label: '编辑', onClick: () => openModal(true, { record, isUpdate: true }) },
|
|||
|
|
{ label: '删除', color: 'error', popConfirm: { title: '确认删除?', confirm: () => handleDelete(record) } },
|
|||
|
|
];
|
|||
|
|
}
|
|||
|
|
</script>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 5. 样式规范
|
|||
|
|
|
|||
|
|
- 组件内样式:`<style lang="less" scoped>`
|
|||
|
|
- 全局主题变量在 `src/design/` 和 `src/assets/style/theme/` 中定义
|
|||
|
|
- 工具类优先使用 Windi CSS(如 `flex`、`items-center`、`mt-4`)
|
|||
|
|
- 项目自定义主题类前缀:`lng-`
|
|||
|
|
- 禁止在 scoped 样式中直接穿透 AntD 组件,改用 `:deep()`
|
|||
|
|
|
|||
|
|
```less
|
|||
|
|
// 正确
|
|||
|
|
:deep(.ant-table-cell) {
|
|||
|
|
padding: 8px;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 6. TypeScript 规范
|
|||
|
|
|
|||
|
|
- 严格模式已开启(`strict: true`),但项目对 `any` 较宽松
|
|||
|
|
- API 请求/响应类型放在对应 `model/` 目录
|
|||
|
|
- 通用类型放在 `types/` 目录,通过 `/#/` 别名引用
|
|||
|
|
- 变量命名:
|
|||
|
|
- `ref` 变量加 `Ref` 后缀:`visibleRef`、`loadingRef`
|
|||
|
|
- computed 可加 `ComputedRef` 后缀
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 7. 国际化
|
|||
|
|
|
|||
|
|
所有用户可见文本必须通过 i18n:
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
import { useI18n } from '/@/hooks/web/useI18n';
|
|||
|
|
const { t } = useI18n();
|
|||
|
|
|
|||
|
|
// 使用
|
|||
|
|
t('common.okText')
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 8. 权限控制
|
|||
|
|
|
|||
|
|
按钮级权限通过 `v-auth` 指令或 `usePermission` 控制:
|
|||
|
|
|
|||
|
|
```vue
|
|||
|
|
<a-button v-auth="'system:user:add'">新增</a-button>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
import { usePermission } from '/@/hooks/web/usePermission';
|
|||
|
|
const { hasPermission } = usePermission();
|
|||
|
|
if (hasPermission('system:user:edit')) { ... }
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 9. 工作流路由约定
|
|||
|
|
|
|||
|
|
特殊路由格式(在 `src/router/routes/basic.ts` 中定义):
|
|||
|
|
|
|||
|
|
|
|||
|
|
| 路由 | 用途 |
|
|||
|
|
| ----------------------------------- | ------------ |
|
|||
|
|
| `/flow/:arg1/:arg2/createFlow` | 发起流程 |
|
|||
|
|
| `/flow/:arg1/:arg2/approveFlowPage` | 审批流程 |
|
|||
|
|
| `/form/:module/:id/createForm` | 通用表单新建 |
|
|||
|
|
| `/form/:module/:id/updateForm` | 通用表单编辑 |
|
|||
|
|
| `/flowList/todo` | 待办列表 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 10. 提交规范
|
|||
|
|
|
|||
|
|
遵循 Conventional Commits:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
feat: 新增功能
|
|||
|
|
fix: 修复 bug
|
|||
|
|
refactor: 重构
|
|||
|
|
style: 样式调整
|
|||
|
|
docs: 文档更新
|
|||
|
|
chore: 构建/工具变更
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 新增页面检查清单
|
|||
|
|
|
|||
|
|
- [ ] 在 `src/views/<domain>/` 下创建目录
|
|||
|
|
- [ ] 页面入口为 `index.vue`,弹窗为 `XxxModal.vue`
|
|||
|
|
- [ ] API 模块放在 `src/api/<domain>/<module>/index.ts`
|
|||
|
|
- [ ] 类型定义放在 `model/index.ts`
|
|||
|
|
- [ ] 使用 `PageWrapper` + `BasicTable` + `useTable` + `useModal` 标准结构
|
|||
|
|
- [ ] 文本通过 `useI18n` 国际化
|
|||
|
|
- [ ] 按钮权限通过 `v-auth` 控制
|
|||
|
|
- [ ] 样式使用 `<style lang="less" scoped>`
|
|||
|
|
- [ ] 路径使用 `/@/` 别名
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# 代码生成器(generator)规范
|
|||
|
|
|
|||
|
|
## 目录结构
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
src/views/generator/
|
|||
|
|
├── codeTemplate/ # 模板管理列表(草稿增删改查)
|
|||
|
|
├── designer/ # 表单设计器演示/测试页
|
|||
|
|
├── dev/ # 代码生成器核心入口
|
|||
|
|
│ ├── choose.vue # 模式选择入口页
|
|||
|
|
│ └── components/
|
|||
|
|
│ ├── DataFirstModal.vue # 数据优先向导
|
|||
|
|
│ ├── CodeFirstModal.vue # 界面优先向导
|
|||
|
|
│ ├── SimpleTemplateModal.vue # 简易模板向导
|
|||
|
|
│ ├── PreviewCodeStep.vue # 代码预览步骤(三种模式共用)
|
|||
|
|
│ └── SelectDatabase.vue # 选择数据库表弹窗
|
|||
|
|
├── desktop/ # 桌面/首页可视化设计器
|
|||
|
|
├── order/ # 示例产物(订单模块演示)
|
|||
|
|
└── print/ # 打印模板设计器
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
步骤子组件统一放在 `/@/components/CreateCodeStep/src/` 下。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 三种生成模式
|
|||
|
|
|
|||
|
|
| 维度 | 数据优先 | 界面优先 | 简易模板 |
|
|||
|
|
|------|---------|---------|---------|
|
|||
|
|
| `designType` | `'data'` | `'code'` | `'template'` |
|
|||
|
|
| `outputConfig.type` | `0` | `1` | `2` |
|
|||
|
|
| 起点 | 选数据库表 | 设计表单 | 设计表单(固定 master 库) |
|
|||
|
|
| 表配置字段 | `tableConfigs` | `tableStructureConfigs` | `tableStructureConfigs` |
|
|||
|
|
| 生成接口 | `dataFirstGeneratorCode` | `codeFirstGeneratorCode` | `codeFirstGeneratorCode` |
|
|||
|
|
| 预览接口 | `dataFirstPreviewCode` | `codeFirstPreviewCode` | `codeFirstPreviewCode` |
|
|||
|
|
| 特殊逻辑 | 无 | 自动推导主表名 | 额外判断字段大小写(Oracle/DM 等) |
|
|||
|
|
|
|||
|
|
简易模板本质是界面优先的变体,复用同一套后端生成接口,仅 `type` 不同。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 核心设计模式
|
|||
|
|
|
|||
|
|
### 1. provide/inject 共享生成状态
|
|||
|
|
|
|||
|
|
父 Modal 通过 `provide` 下发,子步骤通过 `inject` 直接读写,禁止 props 层层传递:
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
provide('generatorConfig', generatorConfig) // 核心配置(reactive)
|
|||
|
|
provide('tableInfo', tableInfo) // 表结构信息
|
|||
|
|
provide('current', current) // 当前步骤索引
|
|||
|
|
provide('designType', 'data') // 生成模式标识
|
|||
|
|
provide('widgetForm', widgetForm) // 表单设计器状态
|
|||
|
|
provide('mainTableName', mainTableName) // 主表名(界面优先/简易模板)
|
|||
|
|
provide('isFieldUpper', isFieldUpper) // 字段大小写(简易模板)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. ref + defineExpose 控制步骤流转
|
|||
|
|
|
|||
|
|
每个步骤子组件必须暴露 `validateStep()`,父组件通过 ref 统一调用:
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
// 子组件
|
|||
|
|
defineExpose({ validateStep, initStep?, getFormData?, editFieldsValue? })
|
|||
|
|
|
|||
|
|
// 父组件
|
|||
|
|
const stepValidate = {
|
|||
|
|
0: () => tableConfigStepRef.value.validateStep(),
|
|||
|
|
1: () => formDesignStepRef.value.validateStep(),
|
|||
|
|
// ...
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. 前端本地生成 + 后端补全的混合模式
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
前端本地 buildCode() 后端接口 previewCode()
|
|||
|
|
───────────────────── ──────────────────────
|
|||
|
|
listCode(列表页) controllerCode(控制器)
|
|||
|
|
formCode(表单页) entityCode(实体类)
|
|||
|
|
apiCode(API 模块)
|
|||
|
|
modelCode(TS 类型)
|
|||
|
|
configJsonCode(路由配置)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4. hiddenComponent 生命周期
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
// 编辑模板时:从列表剔除,不在设计器中展示
|
|||
|
|
formJson.list = formJson.list.filter(x => x.type !== 'hiddenComponent')
|
|||
|
|
|
|||
|
|
// 正式生成时:重新塞回,参与代码生成
|
|||
|
|
generatorConfig.formJson.list.push(...generatorConfig.formJson.hiddenComponent)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5. 两段式下载
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
生成接口 → 返回 uuid → downloadCodes(uuid) → 下载 zip
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 核心数据结构
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
GeneratorConfig {
|
|||
|
|
databaseId: string | null
|
|||
|
|
listConfig: {
|
|||
|
|
isLeftMenu, queryConfigs, leftMenuConfig,
|
|||
|
|
columnConfigs, buttonConfigs, defaultOrder, isPage
|
|||
|
|
}
|
|||
|
|
tableConfigs: TableConfig[] // 仅数据优先
|
|||
|
|
tableStructureConfigs: [] // 界面优先/简易模板
|
|||
|
|
formJson: FormJson // 表单设计结果
|
|||
|
|
menuConfig: MenuConfig
|
|||
|
|
outputConfig: {
|
|||
|
|
creator, isMenu,
|
|||
|
|
type: 0 | 1 | 2 // 生成模式
|
|||
|
|
}
|
|||
|
|
formEventConfig: FormEventColumnConfig
|
|||
|
|
isDataAuth: boolean
|
|||
|
|
dataAuthList: []
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
类型定义文件:`src/api/system/generator/model/index.ts`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## API 接口
|
|||
|
|
|
|||
|
|
所有接口在 `src/api/system/generator/index.ts`:
|
|||
|
|
|
|||
|
|
| 函数 | 方法 | 路径 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| `dataFirstGeneratorCode` | POST | `/system/generator/generator-code/data-first` |
|
|||
|
|
| `codeFirstGeneratorCode` | POST | `/system/generator/generator-code/code-first` |
|
|||
|
|
| `dataFirstPreviewCode` | POST | `/system/generator/preview-code/data-first` |
|
|||
|
|
| `codeFirstPreviewCode` | POST | `/system/generator/preview-code/code-first` |
|
|||
|
|
| `saveDraftGeneratorCode` | POST | `/system/code-schema` |
|
|||
|
|
| `updateDraftGeneratorCode` | PUT | `/system/code-schema` |
|
|||
|
|
| `getCodeTemplateInfo` | GET | `/system/code-schema/info` |
|
|||
|
|
| `getCodeTemplateList` | GET | `/system/code-schema/page` |
|
|||
|
|
| `deleteCodeTemplate` | DELETE | `/system/code-schema` |
|
|||
|
|
| `getMasterInfo` | GET | `/system/databaselink/master-info` |
|
|||
|
|
| `batchGeneratorCode` | POST | `/system/generator/generator-code/batch` |
|
|||
|
|
| `downloadCodes` | GET | `/system/generator/downloadCodes/{uuid}` |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 功能扩展指南
|
|||
|
|
|
|||
|
|
### 新增生成模式
|
|||
|
|
|
|||
|
|
1. 复制 `CodeFirstModal.vue`,修改 `designType` 和 `outputConfig.type`
|
|||
|
|
2. 在 `choose.vue` 加入入口卡片
|
|||
|
|
3. 在 `codeTemplate/index.vue` 的路由名判断中加入新类型
|
|||
|
|
4. 后端新增对应生成/预览接口
|
|||
|
|
|
|||
|
|
### 新增向导步骤
|
|||
|
|
|
|||
|
|
1. 在 `/@/components/CreateCodeStep/src/` 下新建步骤组件,暴露 `validateStep()`
|
|||
|
|
2. 在主 Modal 的 `<a-steps>` 中加入新步骤
|
|||
|
|
3. 在 `stepValidate` 对象中加入对应索引的校验函数
|
|||
|
|
4. 在 `generatorConfig` 中加入新步骤的配置字段,并通过 `provide` 下发
|
|||
|
|
|
|||
|
|
### 扩展代码预览输出类型
|
|||
|
|
|
|||
|
|
1. 在 `FrontCode` 类型中加入新字段
|
|||
|
|
2. 在 `PreviewCodeStep.vue` 的 `<a-tabs>` 中加入新 tab
|
|||
|
|
3. 在 `validateStep()` 中加入非空校验
|
|||
|
|
4. 在 `buildCode()` 或 `previewCode()` 中补充生成逻辑
|