--- name: cross-plugin-web-development description: Madong 插件 Web 端开发规范,覆盖 Web 模板源、运行目录同步、页面路由、Web 菜单、API、认证、Element Plus 页面实现、验证与重装防回退。 globs: - "backend/plugin/**/resource/menu/web.php" - "backend/plugin/**/resource/template/web/**/*" - "frontend/web/app/apps/**/pages/**/*.vue" - "frontend/web/app/apps/**/api/**/*.ts" - "frontend/web/app/apps/**/pages/routes.ts" - ".windsurf/skills/**/*.md" --- # Madong 插件 Web 端开发技能 用于开发和维护插件的 Web 前台功能。重点解决 Web 页面模板源、安装复制、插件路由、Web 菜单、API 调用和卸载重装后的防回退问题。 ## 适用场景 - 为已有 Madong 插件增加 Web 页面。 - 将移动端或后台已有业务迁移为 Web 页面。 - 为插件补齐 Web API、路由和 Web 菜单。 - 修复插件卸载重装后 Web 页面、路由或菜单回退。 - 维护插件 Web 模板源与安装后的 Web 运行目录一致性。 ## 总原则 1. 先核对项目真实实现,再编写或修改 Web 插件代码。 2. 插件 Web 的权威模板源是 `backend/plugin/{plugin}/resource/template/web`。 3. 安装后的 Web 运行目录是 `frontend/web/app/apps/{plugin}`,属于模板复制产物。 4. 正式修改优先落在模板源,完成后再同步到 Web 运行目录。 5. 页面、API、路由、菜单和后端接口的路径必须一致。 6. 优先复用当前项目的 `~/utils/request`、Element Plus、Pinia 和既有页面风格,不直接照搬移动端 UI。 7. 先解决业务根因,不通过页面层伪造数据或绕过后端校验。 8. 不删除未经确认的内容;新增页面时不要覆盖已有业务页面。 ## 目录与复制关系 ### 模板源 ```text backend/plugin/{plugin}/resource/template/web/ ├── api/ │ ├── data-entry.ts │ └── exception.ts ├── pages/ │ ├── routes.ts │ └── {module}/ │ ├── index.vue │ ├── tasks.vue │ └── components/ └── .gitkeep ``` ### 安装后的运行目录 ```text frontend/web/app/apps/{plugin}/ ├── api/ ├── pages/ │ ├── routes.ts │ └── {module}/ └── ... ``` 当前项目的实际对应关系: ```text backend/plugin/produce/resource/template/web/ frontend/web/app/apps/produce/ ``` 修改 Web 插件时,必须核对以下文件是否同步: - `api/**/*.ts` - `pages/**/*.vue` - `pages/routes.ts` - 页面使用的组件和辅助文件 ## 开发前检查 开始开发前至少核对: 1. 插件名和 Web 运行目录:`frontend/web/app/apps/{plugin}`。 2. 模板源是否存在:`backend/plugin/{plugin}/resource/template/web`。 3. Web 主应用是否通过 `router.options.ts` 加载插件路由。 4. 后端接口的真实路径、请求方法、参数和响应结构。 5. 登录用户信息来源和认证请求头。 6. `resource/menu/web.php` 的真实菜单字段格式。 7. 可参考的 Web 页面,例如: - `frontend/web/app/apps/produce/pages/data-entry/index.vue` - `frontend/web/app/apps/produce/pages/data-entry/components/OrderEntryPanel.vue` 8. 移动端页面仅作为业务流程参考,不作为 Web 布局和组件规范来源。 ## Web 路由规范 ### 1. 插件路由入口 当前 Web 主应用通过以下方式加载插件路由: ```text frontend/web/app/router.options.ts ``` 该文件使用 `import.meta.glob('~/apps/**/pages/routes.ts', { eager: true })` 加载插件路由。因此插件页面应在以下文件注册: ```text backend/plugin/{plugin}/resource/template/web/pages/routes.ts frontend/web/app/apps/{plugin}/pages/routes.ts ``` ### 2. 路由写法 ```typescript export default [ { path: '/{plugin}/{module}', component: () => import('~/apps/{plugin}/pages/{module}/index.vue'), meta: { title: '页面标题', layout: 'default', }, }, ] ``` 要求: - 页面路径使用业务语义,保持与 Web 菜单 URL 一致。 - 组件路径使用 `~/apps/{plugin}/pages/...`。 - 默认使用 `layout: 'default'`,除非已核对项目存在其他布局。 - 一个页面只能注册一次,避免主站路由和插件路由重复。 - 带参数页面使用 Nuxt 约定的 `[param].vue`,但当前插件也可按项目路由表显式注册页面。 ### 3. 认证与权限 Web 路由是否需要登录由当前项目权限中间件和路由元信息共同决定。涉及用户数据、异常提报、维修提交等业务时: - 页面必须使用真实的登录用户信息。 - 不要在前端硬编码用户工号作为提交值。 - 需要登录的页面应按当前项目实际权限配置 `meta.permissions`,先核对现有菜单和中间件实现。 - 如果后端接口本身标记为公共接口,也不能因此在页面中跳过业务必填校验。 ## Web 菜单规范 ### 1. 菜单文件 插件 Web 菜单文件: ```text backend/plugin/{plugin}/resource/menu/web.php ``` Web 菜单不是 Admin 菜单,不能直接复制 `resource/menu/admin.php` 的结构。当前项目 Web 菜单使用记录数组,例如: ```php return [ [ 'app' => 'web', 'category' => 1, 'source' => 'plugin', 'code' => null, 'name' => '业务页面', 'url' => '/{plugin}/{module}', 'icon' => 'ri:apps-line', 'level' => 1, 'type' => 2, 'sort' => 10, 'target' => 1, 'is_show' => 1, 'enabled' => 1, 'created_at' => time(), 'updated_at' => time(), 'deleted_at' => null, ], ]; ``` 字段要求以当前项目和菜单导入实现为准,至少核对:`app`、`source`、`name`、`url`、`type`、`sort`、`is_show`、`enabled`。 ### 2. 菜单与路由一致 新增 Web 页面时必须同时检查: - `resource/menu/web.php` 是否有对应菜单。 - 菜单 `url` 是否等于路由 `path`。 - 路由组件是否真实存在。 - 菜单是否需要显示为一级菜单或子菜单。 - 图标是否使用项目已有图标格式。 不要只添加路由而不添加菜单,也不要只添加菜单而不注册路由。 ### 3. 菜单安装行为 菜单通常在插件安装时导入系统菜单。修改 `resource/menu/web.php` 后,当前数据库中已存在的菜单不一定会自动刷新。验证菜单时应区分: - 文件配置是否正确。 - 安装导入后的系统菜单是否已更新。 - 当前用户是否有访问该菜单的权限。 不要通过前端硬编码菜单代替插件菜单导入机制。 ## Web API 规范 ### 1. API 文件位置 插件 Web API 正式放在模板源: ```text backend/plugin/{plugin}/resource/template/web/api/ ``` 运行目录对应: ```text frontend/web/app/apps/{plugin}/api/ ``` 当前项目的实际写法是业务 API 直接放在插件目录,例如: ```typescript import request from '~/utils/request' export function getExampleList(params: Record = {}) { return request.get('/{plugin}/{resource}', params) } ``` ### 2. 请求约定 - 使用 `~/utils/request`,不要在业务 API 中直接调用 axios 或 fetch。 - 传入后端业务路径,例如 `/produce/process`,不要手动拼接 `/api/`。 - GET 参数作为第二个参数传入。 - POST 数据作为第二个参数传入。 - 默认响应已经解包为 `data`。 - 只有确实需要状态码、消息或完整响应时,才使用 `returnFullResponse: true`。 - 上传使用 `request.upload` 和 `FormData`,统一由 API 层封装。 - API 层负责类型声明、路径和参数;页面负责交互状态和展示。 - 错误提示优先使用请求层统一处理,页面避免重复弹出相同错误。 ### 3. API 类型 应根据后端真实返回结构定义接口,例如: ```typescript export interface ExampleItem { id: string | number name?: string status?: number } export function getExampleDetail(id: string | number) { return request.get(`/example/${id}`) } ``` 列表接口要确认返回的是数组还是 `{ items, total }`,不能凭页面名称猜测。 ### 4. API 与页面解耦 页面中不要重复实现: - 基础 URL 拼接。 - Authorization 认证头。 - 响应解包。 - 上传认证。 - 通用错误弹窗。 如果多个页面共享业务请求,应提取到插件 Web API 文件,而不是复制请求代码。 ## 页面开发规范 ### 1. 技术栈 当前项目 Web 端使用: - Vue 3 `