diff --git a/.codex/skills/frontend-crud-coding/SKILL.md b/.codex/skills/frontend-crud-coding/SKILL.md index eb82df51..30149087 100644 --- a/.codex/skills/frontend-crud-coding/SKILL.md +++ b/.codex/skills/frontend-crud-coding/SKILL.md @@ -1,6 +1,6 @@ --- name: frontend-crud-coding -description: 在当前 plus-ui-react 前端项目中按真实 React + TypeScript + Umi Max + Ant Design ProComponents + oxlint/oxfmt 代码风格生成或修改页面、API、types、hooks 接入、样式和项目内置 gen/*.vm 代码生成模板。用于新增或修改标准 CRUD 列表页、树表页、系统管理页、监控页、workflow 页面、demo 页面,补齐与 RuoYi-Vue-Plus boot4 后端接口对应的 src/api、types 和 src/pages 代码,或维护 gen/api.ts.vm、gen/types.ts.vm、gen/index.tsx.vm、gen/index-tree.tsx.vm;触发后应先读取适用 references,再阅读目标模块真实代码和项目内置 gen 模板。 +description: 在当前 plus-ui-react 前端项目中按真实 React + TypeScript + Umi Max + Ant Design ProComponents + oxlint/oxfmt 代码风格生成或修改页面、API、types、hooks 接入、样式和项目内置 gen/*.ftl 代码生成模板。用于新增或修改标准 CRUD 列表页、树表页、系统管理页、监控页、workflow 页面、demo 页面,补齐与 RuoYi-Vue-Plus boot4 后端接口对应的 src/api、types 和 src/pages 代码,或维护 gen/api.ts.ftl、gen/types.ts.ftl、gen/index.tsx.ftl、gen/index-tree.tsx.ftl;触发后应先读取适用 references,再阅读目标模块真实代码和项目内置 gen 模板。 --- # 前端编码规范 @@ -16,10 +16,10 @@ description: 在当前 plus-ui-react 前端项目中按真实 React + TypeScript - 树表优先看 `src/pages/demo/tree/index.tsx`、`src/pages/workflow/category/index.tsx`。 - 系统复杂页优先看 `src/pages/system/user/index.tsx`、`system/role`、`system/post`、`system/config`。 - workflow 业务页优先看 `src/pages/workflow/*` 与 `src/api/workflow/*` 同类页面。 -4. 新增或维护代码生成能力时,优先阅读当前仓库 `gen/api.ts.vm`、`gen/types.ts.vm`、`gen/index.tsx.vm`、`gen/index-tree.tsx.vm`。这些是 React 版内置生成模板,页面模板必须输出当前项目 React/TSX 风格。 +4. 新增或维护代码生成能力时,优先阅读当前仓库 `gen/api.ts.ftl`、`gen/types.ts.ftl`、`gen/index.tsx.ftl`、`gen/index-tree.tsx.ftl`。这些是 React 版内置 FreeMarker 生成模板,页面模板必须输出当前项目 React/TSX 风格。 5. 需要从 Vue 版本迁移经验时,参考 `D:\git-sources\Plus相关\plus-ui-new\.codex\skills\frontend-crud-coding` 和 `.claude\agents`,只吸收任务分型、增量修改和自检原则。 -6. 新增标准页面前,可以对照后端工程 `D:\git-sources\Plus相关\RuoYi-Vue-Plus-boot4\ruoyi-modules\ruoyi-gen\src\main\resources\vm` 确认字段、权限和导出能力,但输出和 `gen/` 模板必须保持 React 项目风格。 -7. 新增代码时通常同步维护 `src/api///index.ts`、`types.ts`、`src/pages///index.tsx`;如果是生成模板优化,同步维护 `gen/` 对应 `.vm`。 +6. 新增标准页面前,可以对照后端工程 `D:\git-sources\Plus相关\RuoYi-Vue-Plus-boot4\ruoyi-modules\ruoyi-gen\src\main\resources\fm` 确认字段、权限和导出能力,但输出和 `gen/` 模板必须保持 React 项目风格。 +7. 新增代码时通常同步维护 `src/api///index.ts`、`types.ts`、`src/pages///index.tsx`;如果是生成模板优化,同步维护 `gen/` 对应 `.ftl`。 8. 增强已有页面时只做增量修改,保留原页面的树筛选、导入导出、列显隐、权限、字典、弹窗、抽屉和路由跳转能力。 9. 修改完成后按影响范围运行验证:优先 `pnpm exec tsc --noEmit` 或 `pnpm lint`;大范围页面、公共组件、构建配置变更再跑 `pnpm build`。 @@ -35,7 +35,7 @@ description: 在当前 plus-ui-react 前端项目中按真实 React + TypeScript 1. 目标目录下最近似页面、API、types 的真实实现。 2. 当前项目公共 hooks、组件、工具、样式和请求封装约定。 -3. 当前项目内置 `gen/*.vm` React 代码生成模板。 +3. 当前项目内置 `gen/*.ftl` React 代码生成模板。 4. Vue 参考项目 `.codex` / `.claude` 中的任务分型和工作流。 5. 关联后端工程 generator 模板。 6. 通用 React / Ant Design ProComponents 习惯。 @@ -108,12 +108,12 @@ workflow 目录优先参考 `src/pages/workflow/*`。流程定义、流程实例 ### 6. 维护 React 生成模板 -模板只放在当前仓库 `gen/` 下,除非用户明确要求写入 boot4 后端工程。维护时保持 Velocity 变量和文件名不变: +模板只放在当前仓库 `gen/` 下,除非用户明确要求写入 boot4 后端工程。维护时保持 FreeMarker 变量和文件名不变: -- `gen/api.ts.vm` -- `gen/types.ts.vm` -- `gen/index.tsx.vm` -- `gen/index-tree.tsx.vm` +- `gen/api.ts.ftl` +- `gen/types.ts.ftl` +- `gen/index.tsx.ftl` +- `gen/index-tree.tsx.ftl` 模板内容必须生成当前 React 项目代码,并优先使用已有公共工具:`useDateRangeQuery`、`dictOptions`、`useTreeTableExpand`、`useTableSelection`、`useTableExport`、`confirmAction`、`toPageQuery`、`toTableData`、`handleTree`、`formatDateTimeFields`、`toDayjsFields`。不要把 Vue 模板、Element Plus 组件或 `src/views` 路径写入这些模板。 diff --git a/.codex/skills/frontend-crud-coding/references/examples.md b/.codex/skills/frontend-crud-coding/references/examples.md index e64b21c9..8ecdb36b 100644 --- a/.codex/skills/frontend-crud-coding/references/examples.md +++ b/.codex/skills/frontend-crud-coding/references/examples.md @@ -14,7 +14,7 @@ - 先看 `src/api/system/client/*` 和 `src/pages/system/client/index.tsx` 是否已存在。 - 再看 `src/pages/demo/demo/index.tsx` 的标准 React CRUD 骨架。 -- 新增标准生成能力时优先对照当前仓库 `gen/index.tsx.vm`、`gen/api.ts.vm`、`gen/types.ts.vm`;boot4 generator 只用于核对字段、权限和接口。 +- 新增标准生成能力时优先对照当前仓库 `gen/index.tsx.ftl`、`gen/api.ts.ftl`、`gen/types.ts.ftl`;boot4 generator 只用于核对字段、权限和接口。 - 生成或修改 `api/index.ts`、`types.ts`、`pages/.../index.tsx`。 - 使用 `request>>`、`ProTable`、`ModalForm`、`RowActions`、`useTableSelection`、`useTableExport`。 - 日期范围、字典 options、确认框、loading 等优先使用 `useDateRangeQuery`、`dictOptions`、`confirmAction`、`useLoading` 等项目工具。 @@ -46,8 +46,8 @@ ### 期望执行方式 -- 读取 `gen/api.ts.vm`、`gen/types.ts.vm`、`gen/index.tsx.vm`、`gen/index-tree.tsx.vm`。 -- 保持 Velocity 变量、宏和文件名不变。 +- 读取 `gen/api.ts.ftl`、`gen/types.ts.ftl`、`gen/index.tsx.ftl`、`gen/index-tree.tsx.ftl`。 +- 保持 FreeMarker 变量、宏和文件名不变。 - 普通表模板继续使用 `useTableSelection`、`useTableExport`、`useDateRangeQuery`、`dictOptions`、`confirmAction`。 - 树表模板继续使用 `handleTree`、`filterTree`、`useTreeTableExpand`、`dictOptions`。 - 输出仍是 React TSX 项目代码,不写 Vue `src/views`、Element Plus 或 `AxiosPromise`。 diff --git a/.codex/skills/frontend-crud-coding/references/frontend.md b/.codex/skills/frontend-crud-coding/references/frontend.md index 061cf133..d8dcf355 100644 --- a/.codex/skills/frontend-crud-coding/references/frontend.md +++ b/.codex/skills/frontend-crud-coding/references/frontend.md @@ -3,7 +3,7 @@ ## 优先参考的代码来源 - 当前目标目录下最近似页面、API、types。 -- 当前仓库内置 React 生成模板:`gen/api.ts.vm`、`gen/types.ts.vm`、`gen/index.tsx.vm`、`gen/index-tree.tsx.vm`。 +- 当前仓库内置 React 生成模板:`gen/api.ts.ftl`、`gen/types.ts.ftl`、`gen/index.tsx.ftl`、`gen/index-tree.tsx.ftl`。 - 标准单表:`src/pages/demo/demo/index.tsx`、`src/api/demo/demo/index.ts`、`src/api/demo/demo/types.ts`。 - 树表:`src/pages/demo/tree/index.tsx`、`src/pages/workflow/category/index.tsx`。 - 复杂系统页:`src/pages/system/user/index.tsx`、`src/pages/system/role/index.tsx`、`src/pages/system/post/index.tsx`、`src/pages/system/config/index.tsx`。 @@ -127,9 +127,9 @@ ## 与 gen 模板、Vue 参考项目和后端生成器的关系 -- 当前仓库 `gen/*.vm` 是 React 版内置代码生成模板,是维护生成能力时的第一参考。 -- `gen/index.tsx.vm`、`gen/index-tree.tsx.vm` 内容必须生成 React TSX 页面。 -- 修改公共 hooks/工具后,如果它们能简化标准生成页,要同步评估 `gen/*.vm` 是否需要更新。 +- 当前仓库 `gen/*.ftl` 是 React 版内置代码生成模板,是维护生成能力时的第一参考。 +- `gen/index.tsx.ftl`、`gen/index-tree.tsx.ftl` 内容必须生成 React TSX 页面。 +- 修改公共 hooks/工具后,如果它们能简化标准生成页,要同步评估 `gen/*.ftl` 是否需要更新。 - Vue skill 和 `.claude/agents` 提供的是任务分型、优先级、增量修改、自检方式,不是 React 实现模板。 - boot4 后端 generator 模板可用于确认字段、权限、导出、状态切换、排序、日期范围等,但不是最终答案。 - 当前 React 项目的核心骨架是 `ProTable`、`ModalForm`、`RowActions`、`useTableSelection`、`useTableExport`。 @@ -137,7 +137,7 @@ ## 验证规则 -- 只改文档、skill 或 `gen/*.vm` 模板:至少运行 skill 基础校验或 `git diff --check`;如果模板改动依赖项目工具,优先再跑 `pnpm lint`。 +- 只改文档、skill 或 `gen/*.ftl` 模板:至少运行 skill 基础校验或 `git diff --check`;如果模板改动依赖项目工具,优先再跑 `pnpm lint`。 - 改前端 TS/TSX/API/types:优先运行 `pnpm exec tsc --noEmit` 或 `pnpm lint`。 - 改页面、import、权限或较多文件:运行 `pnpm lint`。 - 改公共 hooks、组件、构建相关或大范围页面:再运行 `pnpm build`。 diff --git a/gen/api.ts.vm b/gen/api.ts.ftl similarity index 77% rename from gen/api.ts.vm rename to gen/api.ts.ftl index 0ff896bd..a02ce385 100644 --- a/gen/api.ts.vm +++ b/gen/api.ts.ftl @@ -1,4 +1,4 @@ -import type { #if(!$table.tree)PageResult, #end R } from '@/api/types'; +import type { <#if !table.tree>PageResult, R } from '@/api/types'; import request from '@/api/request'; import type { ${BusinessName}Form, ${BusinessName}Query, ${BusinessName}VO } from './types'; @@ -6,7 +6,7 @@ import type { ${BusinessName}Form, ${BusinessName}Query, ${BusinessName}VO } fro * 查询${functionName}列表 */ export function list${BusinessName}(query?: ${BusinessName}Query) { - return request#end>>({ + return request${BusinessName}VO[]<#else> PageResult<${BusinessName}VO>>>({ url: '/${moduleName}/${businessName}/list', method: 'get', params: query @@ -45,13 +45,13 @@ export function update${BusinessName}(data: ${BusinessName}Form) { }); } -#if($enableStatus) +<#if enableStatus> /** * 修改${functionName}状态 */ export function change${BusinessName}Status( ${pkColumn.javaField}: string | number, - ${statusField}: #if($statusColumn.javaType == 'Boolean')boolean#elseif($statusColumn.javaType == 'Integer' || $statusColumn.javaType == 'Long')number#else string#end + ${statusField}: <#if statusColumn.javaType == 'Boolean'>boolean<#elseif statusColumn.javaType == 'Integer' || statusColumn.javaType == 'Long'>number<#else> string ) { return request({ url: '/${moduleName}/${businessName}/changeStatus', @@ -63,14 +63,14 @@ export function change${BusinessName}Status( }); } -#end -#if($enableSort) + +<#if enableSort> /** * 调整${functionName}排序 */ export function update${BusinessName}Sort( ${pkColumn.javaField}: string | number, - ${sortField}: #if($sortColumn.javaType == 'LocalDateTime' || $sortColumn.javaType == 'String')string#else number#end + ${sortField}: <#if sortColumn.javaType == 'LocalDateTime' || sortColumn.javaType == 'String'>string<#else> number ) { return request({ url: '/${moduleName}/${businessName}/updateSort', @@ -82,7 +82,7 @@ export function update${BusinessName}Sort( }); } -#end + /** * 删除${functionName} */ @@ -92,3 +92,7 @@ export function del${BusinessName}(${pkColumn.javaField}: string | number | Arra method: 'delete' }); } + + + + diff --git a/gen/index-tree.tsx.vm b/gen/index-tree.tsx.ftl similarity index 50% rename from gen/index-tree.tsx.vm rename to gen/index-tree.tsx.ftl index c3952c82..26c3cdc6 100644 --- a/gen/index-tree.tsx.vm +++ b/gen/index-tree.tsx.ftl @@ -1,66 +1,22 @@ -#set($needDict = $dicts != '') -#set($needImagePreview = false) -#set($needImageUpload = false) -#set($needFileUpload = false) -#set($needEditor = false) -#set($needCheckbox = false) -#set($needSelect = false) -#set($needTextArea = false) -#set($needDigit = false) -#set($needDateField = false) -#set($needSwitchField = false) -#foreach($column in $columns) -#if($column.list && $column.htmlType == "imageUpload") -#set($needImagePreview = true) -#end -#if(($column.insert || $column.edit) && $column.htmlType == "imageUpload") -#set($needImageUpload = true) -#end -#if(($column.insert || $column.edit) && $column.htmlType == "fileUpload") -#set($needFileUpload = true) -#end -#if(($column.insert || $column.edit) && $column.htmlType == "editor") -#set($needEditor = true) -#end -#if(($column.insert || $column.edit) && $column.htmlType == "checkbox") -#set($needCheckbox = true) -#end -#if(($column.insert || $column.edit || $column.query) && ($column.htmlType == "select" || $column.htmlType == "radio" || $column.htmlType == "switch")) -#set($needSelect = true) -#end -#if(($column.insert || $column.edit) && $column.htmlType == "textarea") -#set($needTextArea = true) -#end -#if(($column.insert || $column.edit) && $column.htmlType == "inputNumber") -#set($needDigit = true) -#end -#if(($column.insert || $column.edit) && $column.htmlType == "datetime") -#set($needDateField = true) -#end -#if(($column.insert || $column.edit || $column.list) && $column.htmlType == "switch") -#set($needSwitchField = true) -#end -#end -#macro(comment $column)#set($idx=$column.columnComment.indexOf("("))#if($idx != -1)$column.columnComment.substring(0, $idx)#else$column.columnComment#end#end -import { DeleteOutlined#if($enableExport), DownloadOutlined#end, EditOutlined, PlusOutlined, SortAscendingOutlined } from '@ant-design/icons'; +import { DeleteOutlined<#if enableExport>, DownloadOutlined, EditOutlined, PlusOutlined, SortAscendingOutlined } from '@ant-design/icons'; import { ModalForm, PageContainer, -#if($needCheckbox) +<#if needCheckbox> ProFormCheckbox, -#end -#if($needDateField) + +<#if needDateField> ProFormDateTimePicker, -#end -#if($needDigit) + +<#if needDigit> ProFormDigit, -#end -#if($needSelect) + +<#if needSelect> ProFormSelect, -#end -#if($needTextArea) + +<#if needTextArea> ProFormTextArea, -#end + ProFormText, ProFormTreeSelect, ProTable, @@ -68,62 +24,62 @@ import { type ProColumns } from '@ant-design/pro-components'; import { useBoolean } from 'ahooks'; -import { Button, Form, message#if($enableStatus || $needSwitchField), Switch#end#if($enableSort), InputNumber#end } from 'antd'; +import { Button, Form, message<#if enableStatus || needSwitchField>, Switch<#if enableSort>, InputNumber } from 'antd'; import { useMemo, useRef, useState } from 'react'; import type { ${BusinessName}Form, ${BusinessName}Query, ${BusinessName}VO } from '@/api/${moduleName}/${businessName}/types'; import { add${BusinessName}, -#if($enableStatus) +<#if enableStatus> change${BusinessName}Status, -#end + del${BusinessName}, get${BusinessName}, list${BusinessName}, -#if($enableSort) +<#if enableSort> update${BusinessName}Sort, -#end + update${BusinessName} } from '@/api/${moduleName}/${businessName}'; -#if($needDict) +<#if needDict> import DictTag from '@/components/common/DictTag'; -#end -#if($needFileUpload) + +<#if needFileUpload> import FileUpload from '@/components/common/FileUpload'; -#end -#if($needImagePreview) + +<#if needImagePreview> import ImagePreview from '@/components/common/ImagePreview'; -#end -#if($needImageUpload) + +<#if needImageUpload> import ImageUpload from '@/components/common/ImageUpload'; -#end -#if($needEditor) + +<#if needEditor> import RichTextEditor from '@/components/common/RichTextEditor'; -#end + import RowActions from '@/components/common/RowActions'; -#if($needDict) +<#if needDict> import { useDict } from '@/hooks/useDict'; -#end -#if($enableExport) + +<#if enableExport> import { useTableExport } from '@/hooks/useTableExport'; -#end + import { useTreeTableExpand } from '@/hooks/useTreeTableExpand'; import { useUserStore } from '@/stores/userStore'; -#if($needDict) +<#if needDict> import { dictOptions } from '@/utils/dict'; -#end -#if($enableStatus) + +<#if enableStatus> import { confirmAction } from '@/utils/modal'; -#end + import { hasPermi } from '@/utils/permission'; -import { filterTree#if($needDateField), formatDateTimeFields#end, handleTree#if($needDateField), toDayjsFields#end } from '@/utils/ruoyi'; +import { filterTree<#if needDateField>, formatDateTimeFields, handleTree<#if needDateField>, toDayjsFields } from '@/utils/ruoyi'; const default${BusinessName}Form: ${BusinessName}Form = { ${treeParentCode}: ${treeRootValueTsLiteral}, -#foreach($column in $columns) -#if(($column.insert || $column.edit) && !$column.pk && $column.htmlType == "checkbox") +<#list columns as column> +<#if (column.insert || column.edit) && !column.pk && column.htmlType == "checkbox"> ${column.javaField}: [], -#end -#end + + }; interface ${BusinessName}SelectNode { @@ -140,36 +96,34 @@ function toTreeSelectData(nodes: ${BusinessName}VO[]): ${BusinessName}SelectNode })); } -#macro(switchActiveValue $column)#if($column.javaType == "Boolean")true#elseif($column.javaType == "Integer" || $column.javaType == "Long")0#else'0'#end#end -#macro(switchInactiveValue $column)#if($column.javaType == "Boolean")false#elseif($column.javaType == "Integer" || $column.javaType == "Long")1#else'1'#end#end -#if($enableStatus) -const ${statusField}ActiveValue = #if($statusColumn.javaType == "Boolean")true#elseif($statusColumn.javaType == "Integer" || $statusColumn.javaType == "Long")0#else'0'#end; -const ${statusField}InactiveValue = #if($statusColumn.javaType == "Boolean")false#elseif($statusColumn.javaType == "Integer" || $statusColumn.javaType == "Long")1#else'1'#end; +<#if enableStatus> +const ${statusField}ActiveValue = <#if statusColumn.javaType == "Boolean">true<#elseif statusColumn.javaType == "Integer" || statusColumn.javaType == "Long">0<#else>'0'; +const ${statusField}InactiveValue = <#if statusColumn.javaType == "Boolean">false<#elseif statusColumn.javaType == "Integer" || statusColumn.javaType == "Long">1<#else>'1'; -#end + export default function ${BusinessName}Page() { const actionRef = useRef(undefined); const [form] = Form.useForm<${BusinessName}Form>(); const userInfo = useUserStore(state => state.userInfo); -#if($needDict) +<#if needDict> const dicts = useDict(${dicts}); -#end + const [treeOptions, setTreeOptions] = useState<${BusinessName}VO[]>([]); const [tableRows, setTableRows] = useState<${BusinessName}VO[]>([]); const { expandAll, expandedRowKeys, onExpandedRowsChange, syncExpandedRows, toggleExpandAll } = useTreeTableExpand<${BusinessName}VO>(row => row.${treeCode}); const [modalOpen, { setTrue: openModal, setFalse: closeModal }] = useBoolean(false); const [modalTitle, setModalTitle] = useState(''); -#if($enableExport) +<#if enableExport> const { updateExportParams, exportFile } = useTableExport(); -#end + const canAdd = hasPermi(userInfo, ['${permissionPrefix}:add']); const canEdit = hasPermi(userInfo, ['${permissionPrefix}:edit']); const canRemove = hasPermi(userInfo, ['${permissionPrefix}:remove']); -#if($enableExport) +<#if enableExport> const canExport = hasPermi(userInfo, ['${permissionPrefix}:export']); -#end + const treeSelectData = useMemo( () => [{ title: '顶级节点', value: ${treeRootValueTsLiteral}, children: toTreeSelectData(treeOptions) }], [treeOptions] @@ -184,7 +138,7 @@ export default function ${BusinessName}Page() { const openAdd = async (row?: ${BusinessName}VO) => { await loadTreeOptions(); form.resetFields(); - form.setFieldsValue({ ...default${BusinessName}Form, ${treeParentCode}: row?.${treeCode} || ${treeRootValueTsLiteral} }); + form.setFieldsValue({ ...default${BusinessName}Form, ${treeParentCode}: row?.${treeCode} || treeRootValueTsLiteral }); setModalTitle('添加${functionName}'); openModal(); }; @@ -192,20 +146,20 @@ export default function ${BusinessName}Page() { const openEdit = async (row: ${BusinessName}VO) => { await loadTreeOptions(row.${treeCode}); const res = await get${BusinessName}(row.${pkColumn.javaField}); - const data = #if($needDateField)toDayjsFields({ ...res.data }, [ -#foreach($column in $columns) -#if(($column.insert || $column.edit) && $column.htmlType == "datetime") + const data = <#if needDateField>toDayjsFields({ ...res.data }, [ +<#list columns as column> +<#if (column.insert || column.edit) && column.htmlType == "datetime"> '${column.javaField}', -#end -#end - ])#else{ ...res.data }#end; -#foreach($column in $columns) -#if(($column.insert || $column.edit) && $column.htmlType == "checkbox") + + + ])<#else>{ ...res.data }; +<#list columns as column> +<#if (column.insert || column.edit) && column.htmlType == "checkbox"> if (typeof data.${column.javaField} === 'string') { data.${column.javaField} = data.${column.javaField}.split(','); } -#end -#end + + form.resetFields(); form.setFieldsValue(data); setModalTitle('修改${functionName}'); @@ -213,20 +167,20 @@ export default function ${BusinessName}Page() { }; const submitForm = async (values: ${BusinessName}Form) => { - const submitValues = #if($needDateField)formatDateTimeFields({ ...values }, [ -#foreach($column in $columns) -#if(($column.insert || $column.edit) && $column.htmlType == "datetime") + const submitValues = <#if needDateField>formatDateTimeFields({ ...values }, [ +<#list columns as column> +<#if (column.insert || column.edit) && column.htmlType == "datetime"> '${column.javaField}', -#end -#end - ])#else{ ...values }#end; -#foreach($column in $columns) -#if(($column.insert || $column.edit) && $column.htmlType == "checkbox") + + + ])<#else>{ ...values }; +<#list columns as column> +<#if (column.insert || column.edit) && column.htmlType == "checkbox"> if (Array.isArray(submitValues.${column.javaField})) { submitValues.${column.javaField} = submitValues.${column.javaField}.join(','); } -#end -#end + + submitValues.${pkColumn.javaField} ? await update${BusinessName}(submitValues) : await add${BusinessName}(submitValues); message.success('操作成功'); form.resetFields(); @@ -240,15 +194,15 @@ export default function ${BusinessName}Page() { actionRef.current?.reload(); }; -#if($enableStatus) +<#if enableStatus> const handleStatusChange = async (row: ${BusinessName}VO, checked: boolean) => { const previousStatus = row.${statusField}; const status = checked ? ${statusField}ActiveValue : ${statusField}InactiveValue; const text = checked ? '启用' : '停用'; try { - await confirmAction(`确认要"${text}"吗?`); + await confirmAction(`确认要"${r'${text}'}"吗?`); await change${BusinessName}Status(row.${pkColumn.javaField}, status); - message.success(`${text}成功`); + message.success(`${r'${text}'}成功`); actionRef.current?.reload(); } catch { row.${statusField} = previousStatus; @@ -256,76 +210,74 @@ export default function ${BusinessName}Page() { } }; -#end -#if($enableSort) + +<#if enableSort> const handleSortChange = async (row: ${BusinessName}VO, value?: number | null) => { await update${BusinessName}Sort(row.${pkColumn.javaField}, value || 0); message.success('排序更新成功'); actionRef.current?.reload(); }; -#end + const columns: ProColumns<${BusinessName}VO>[] = [ -#foreach($column in $columns) -#set($javaField=$column.javaField) -#set($dictType=$column.dictType) -#if($column.list && !$column.pk) -#if($enableStatus && $statusField == $javaField) +<#list columns as column> +<#if column.list && !column.pk> +<#if enableStatus && statusField == column.javaField> { - title: '#comment($column)', - dataIndex: '${javaField}', + title: '${column.columnLabel}', + dataIndex: '${column.javaField}', valueType: 'select', width: 100, -#if("" != $dictType) - fieldProps: { options: dictOptions(dicts.${dictType}) }, -#end +<#if column.dictType?has_content> + fieldProps: { options: dictOptions(dicts.${column.dictType}) }, + render: (_, row) => ( handleStatusChange(row, checked)} /> ) }, -#elseif($enableSort && $sortField == $javaField) +<#elseif enableSort && sortField == column.javaField> { - title: '#comment($column)', - dataIndex: '${javaField}', + title: '${column.columnLabel}', + dataIndex: '${column.javaField}', search: false, width: 130, - render: (_, row) => handleSortChange(row, value)} /> + render: (_, row) => handleSortChange(row, value)} /> }, -#elseif($column.htmlType == "datetime") - { title: '#comment($column)', dataIndex: '${javaField}', valueType: 'dateTime', search: #if($column.query)true#else false#end, width: 170 }, -#elseif($column.htmlType == "imageUpload") +<#elseif column.htmlType == "datetime"> + { title: '${column.columnLabel}', dataIndex: '${column.javaField}', valueType: 'dateTime', search: <#if column.query>true<#else> false, width: 170 }, +<#elseif column.htmlType == "imageUpload"> { - title: '#comment($column)', - dataIndex: '${javaField}Url', + title: '${column.columnLabel}', + dataIndex: '${column.javaField}Url', search: false, width: 100, - render: (_, row) => + render: (_, row) => }, -#elseif($column.dictColumn) +<#elseif column.dictColumn> { - title: '#comment($column)', - dataIndex: '${javaField}', + title: '${column.columnLabel}', + dataIndex: '${column.javaField}', valueType: 'select', - fieldProps: { options: dictOptions(dicts.${dictType}) }, - render: (_, row) => + fieldProps: { options: dictOptions(dicts.${column.dictType}) }, + render: (_, row) => }, -#elseif($column.htmlType == "switch") +<#elseif column.htmlType == "switch"> { - title: '#comment($column)', - dataIndex: '${javaField}', + title: '${column.columnLabel}', + dataIndex: '${column.javaField}', valueType: 'select', width: 100, - render: (_, row) => + render: (_, row) => }, -#else - { title: '#comment($column)', dataIndex: '${javaField}'#if(!$column.query), search: false#end }, -#end -#end -#end +<#else> + { title: '${column.columnLabel}', dataIndex: '${column.javaField}'<#if !column.query>, search: false }, + + + { title: '操作', valueType: 'option', @@ -341,7 +293,7 @@ export default function ${BusinessName}Page() { label: '删除', icon: , danger: true, - confirm: `是否确认删除${functionName}编号为"#[[${row.]]#${pkColumn.javaField}#[[}]]#"的数据项?`, + confirm: `是否确认删除${functionName}编号为"${r'${row.'}${pkColumn.javaField}${r'}'}"的数据项?`, onClick: () => remove(row) } ]} @@ -368,9 +320,9 @@ export default function ${BusinessName}Page() { const rows = handleTree<${BusinessName}VO>(res.data || [], '${treeCode}', '${treeParentCode}'); setTableRows(rows); syncExpandedRows(rows, expandAll); -#if($enableExport) +<#if enableExport> updateExportParams(params); -#end + return { data: rows, total: rows.length, success: true }; }} toolbar={{ title: '${functionName}列表' }} @@ -386,16 +338,16 @@ export default function ${BusinessName}Page() { onClick={() => toggleExpandAll(tableRows)} > 展开/折叠 - #if($enableExport), + <#if enableExport>, canExport && ( - )#end + ) ]} /> @@ -411,62 +363,65 @@ export default function ${BusinessName}Page() { onFinish={submitForm} >