diff --git a/frontend/uni-app/docs/命名规范.md b/frontend/uni-app/docs/命名规范.md index 054cbe232..8bb54856b 100644 --- a/frontend/uni-app/docs/命名规范.md +++ b/frontend/uni-app/docs/命名规范.md @@ -16,6 +16,7 @@ |---|---|---|---| | `src/` 顶层目录 | 全小写单词或 kebab-case | `api/`、`dynamic-pages/` | 已合规 | | `pages/` 页面目录 | **snake_case**,页面入口固定 `index.vue` | `pages/users/user_address/index.vue` | 存量路径冻结(见 §3-1);新页面必须遵守 | +| `pages-modules/` 插件模块(免注册分包) | 模块目录 **snake_case**(=插件标识);页面 = snake_case 子目录 + `index.vue`;细则与营销插件对照见 §2.1 | `pages-modules/seckill/goods_details/index.vue` | 新层,无存量 | | `.vue` 组件(`components/`、`dynamic-pages/`、页面内 `components/`) | **PascalCase.vue**;easycom 组件目录与文件同名 | `components/BaseMoney/`、`LogisticsTimeline.vue`、局部组件 `InvoiceModal.vue` | 存量 camelCase(`emptyPage.vue` 等)冻结 | | `subpackage/diyComponents/` DIY 组件 | **契约冻结层,不适用本规范**;新 DIY 组件文件名必须与数据库 `eb_diy.value` 下发的 `name` 字段一致(camelCase) | `homeComb.vue` ↔ name `homeComb` | 冻结 | | `composables/` | `useXxx.js`(camelCase) | `useColorTheme.js` | 已合规 | @@ -28,6 +29,28 @@ - **页面内局部组件**放在页面目录旁的 `components/` 子目录(复数,勿用单数 `component/`),命名 PascalCase。 - 第三方/移植库目录(`mp-html`、`easy-loadimage`、`zb-code`、verify 验证码树、`*.min.js`)保持原名,不适用本规范。 +### 2.1 `pages-modules/` 插件模块命名(免注册分包) + +营销/插件页面(秒杀类)统一投放 `src/pages-modules/`,丢入即自动注册为分包(机制见 `frontend/uni-app/AGENTS.md` 专节)。命名细则: + +1. **模块目录 = 插件标识**:snake_case 英文全拼,与后端模块/插件标识及自有表前缀 `eb_{插件标识}_*` 对齐;一个插件一个模块目录,禁止多插件混放。 +2. **页面 = snake_case 子目录 + `index.vue`**(`lint:naming` 强制,PascalCase 文件会被扫描器误注册为路由);模块根 `index.vue` 即模块首页。页面目录**不重复模块名**——路径已含前缀,写 `seckill/goods_details/index.vue` 而非 `seckill/seckill_goods_details/`。 +3. **语义后缀惯例**(对齐存量 `goods_seckill_details` 风格):入口/会场 `index`、子列表 `*_list`、详情 `*_details`、结果/状态 `*_status`、记录 `*_record`、规则说明 `rule`。 +4. **模块固定/常用文件**:`routes.js` 路由常量(必备,模块内外跳转统一引用,禁止硬编码路径);可选 `api.js`(接口封装,camelCase)、`components/`(局部组件 PascalCase)、`static/`(图片 kebab-case,同 §2 静态资源行)。 +5. 常用营销插件模块名对照(新增插件参考命名,存量系统页面不受影响): + +| 功能 | 模块目录 | 典型页面(模块内) | +|---|---|---| +| 秒杀 | `seckill` | `index`(会场)、`goods_details`、`record` | +| 拼团 | `combination` | `index`、`goods_details`、`order_status` | +| 砍价 | `bargain` | `index`、`goods_details` | +| 预售 | `presell` | `index`、`goods_details` | +| 积分商城 | `integral` | `index`、`goods_list`、`exchange_record` | +| 优惠券 | `coupon` | `index`、`receive_record` | +| 签到 | `sign` | `index`、`record` | +| 抽奖 | `lottery` | `index`、`record` | +| 分销 | `distribution` | `index`、`poster`、`record` | + ## 3. 红线:契约冻结清单(改名即断线上功能,禁止触碰) 1. **pages.json 已注册的所有页面 path 与分包 root**。 diff --git a/frontend/uni-app/scripts/check-naming.mjs b/frontend/uni-app/scripts/check-naming.mjs index 8e4dbde70..57f544cc0 100644 --- a/frontend/uni-app/scripts/check-naming.mjs +++ b/frontend/uni-app/scripts/check-naming.mjs @@ -75,6 +75,14 @@ for (const f of files) { const stem = name.slice(0, name.length - ext.length); if (!IMG_RE.test(stem) || !/[a-z]/.test(stem)) addViolation(relPath, '图片须 kebab-case 全小写且含字母(禁下划线/大写/拼音/纯数字)'); } + // pages-modules 页面入口唯一形态:snake_case 目录 + index.vue(PascalCase 文件会被误注册为路由) + if (ext === '.vue' && name !== 'index.vue' && relPath.startsWith('src/pages-modules/')) { + const segs = relPath.split('/'); + const inner = segs.slice(2, -1); // ['pages-modules', 模块名, ...页面目录祖先] + if (!inner.some((s) => ROLE_DIRS.has(s) || s.startsWith('_'))) { + addViolation(relPath, 'pages-modules/ 页面必须为 snake_case 目录 + index.vue(局部组件放 components/,细则见 docs/命名规范.md §2.1)'); + } + } } for (const d of dirs) { @@ -83,10 +91,14 @@ for (const d of dirs) { const name = segs[segs.length - 1]; if (segs.length === 2) { if (!TOP_DIR_RE.test(name)) addViolation(relPath, 'src 顶层目录须全小写或 kebab-case'); - } else if (segs[1] === 'pages') { + } else if (segs[1] === 'pages' || segs[1] === 'pages-modules') { const inner = segs.slice(2, -1); // 该目录的所有祖先段 - if (!inner.some((s) => ROLE_DIRS.has(s)) && !PAGE_DIR_RE.test(name)) { - addViolation(relPath, 'pages/ 页面目录须 snake_case 或全小写单词'); + // pages-modules 下 `_` 前缀目录为扫描器排除区(_template 模板等),不按页面目录规则校验 + const exempt = + inner.some((s) => ROLE_DIRS.has(s)) || + (segs[1] === 'pages-modules' && (name.startsWith('_') || inner.some((s) => s.startsWith('_')))); + if (!exempt && !PAGE_DIR_RE.test(name)) { + addViolation(relPath, 'pages/(及 pages-modules/)页面目录须 snake_case 或全小写单词'); } } else if (segs[1] === 'components' && segs.length === 3 && !PASCAL_DIR_RE.test(name)) { addViolation(relPath, 'components/ 组件目录须 PascalCase');