@qilitt-mickey/vue3-temp-skill 1.1.25 → 1.1.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SKILL.md +77 -37
- package/package.json +15 -1
- package/references/auth-pages.md +217 -0
- package/references/base-code-dict.md +7 -0
- package/references/chart-echarts.md +1 -1
- package/references/crud-pages.md +1 -1
- package/references/data-compare.md +7 -0
- package/references/data-mapping.md +7 -0
- package/references/data-screen.md +7 -0
- package/references/data-writeback.md +7 -0
- package/references/directives-advanced.md +7 -0
- package/references/download-export.md +7 -0
- package/references/feedback-loading.md +7 -0
- package/references/feedback-ui.md +7 -0
- package/references/file-management.md +7 -0
- package/references/flowchart-g6.md +7 -0
- package/references/form-advanced.md +1 -1
- package/references/graph-relation.md +7 -0
- package/references/i18n-localization.md +230 -0
- package/references/layout-theme.md +1 -1
- package/references/particles.md +7 -0
- package/references/project-inventory.md +7 -0
- package/references/qrcode-barcode.md +7 -0
- package/references/rich-text.md +7 -0
- package/references/seamless-scroll.md +7 -0
- package/references/table-vxe.md +1 -1
- package/references/tree-table.md +7 -0
- package/references/verify-captcha.md +7 -0
- package/references/websocket-realtime.md +7 -0
- package/references/workflow-bpmn.md +7 -0
- package/scripts/ai-content-audit.mjs +244 -0
- package/scripts/ai-match-audit.mjs +348 -0
- package/scripts/ai-workflow-sim.mjs +393 -0
- package/scripts/analyze-deps.mjs +278 -0
- package/scripts/check.cmd +7 -0
- package/scripts/check.mjs +95 -0
- package/scripts/cli.mjs +153 -0
- package/scripts/generate-index.mjs +214 -0
- package/scripts/match.py +323 -0
- package/scripts/modules.json +348 -0
- package/scripts/scaffold-module.mjs +232 -0
- package/scripts/validate.mjs +256 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
---
|
|
2
|
+
skill: i18n-localization
|
|
3
|
+
description: vue-i18n 国际化、locales 语言包、ReLanguageSwitch 语言切换、store/modules/lang
|
|
4
|
+
scope: project
|
|
5
|
+
tags: [国际化, 多语言, i18n, vue-i18n, locale, 语言切换, 语言包, $t, useI18n, ReLanguageSwitch]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# 国际化(i18n)
|
|
9
|
+
|
|
10
|
+
> 使用 `vue-i18n` + `src/locales/` + `ReLanguageSwitch`,勿另起一套 i18n 方案。
|
|
11
|
+
|
|
12
|
+
## 架构
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
src/
|
|
16
|
+
├── locales/
|
|
17
|
+
│ ├── index.ts # i18n 实例创建,合并 Element Plus / Vant / 自定义语言包
|
|
18
|
+
│ ├── en-US.json # 英文翻译(按模块分组)
|
|
19
|
+
│ └── zh-Cn.json # 中文翻译(按模块分组)
|
|
20
|
+
├── utils/
|
|
21
|
+
│ └── i18n.ts # getLanguage():localStorage + Cookie + navigator.language 回退
|
|
22
|
+
├── store/modules/
|
|
23
|
+
│ └── lang.ts # useLangStore:语言状态管理 + 持久化
|
|
24
|
+
├── components/
|
|
25
|
+
│ └── ReLanguageSwitch/ # 语言切换下拉组件
|
|
26
|
+
└── main.ts # app.use(i18n) + $t 全局注入
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## 创建 i18n 实例
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
// src/locales/index.ts
|
|
33
|
+
import elenLocale from 'element-plus/es/locale/lang/en'
|
|
34
|
+
import elzhLocale from 'element-plus/es/locale/lang/zh-cn'
|
|
35
|
+
import venLocale from 'vant/es/locale/lang/en-US'
|
|
36
|
+
import vzhLocale from 'vant/es/locale/lang/zh-CN'
|
|
37
|
+
|
|
38
|
+
import { createI18n } from 'vue-i18n'
|
|
39
|
+
import { getLanguage } from '@/utils/i18n'
|
|
40
|
+
import enLocale from './en-US.json'
|
|
41
|
+
import zhLocale from './zh-Cn.json'
|
|
42
|
+
|
|
43
|
+
const messages = {
|
|
44
|
+
en: {
|
|
45
|
+
...elenLocale,
|
|
46
|
+
...venLocale,
|
|
47
|
+
...enLocale,
|
|
48
|
+
},
|
|
49
|
+
zh: {
|
|
50
|
+
...elzhLocale,
|
|
51
|
+
...vzhLocale,
|
|
52
|
+
...zhLocale,
|
|
53
|
+
},
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const i18n = createI18n({
|
|
57
|
+
legacy: false,
|
|
58
|
+
locale: getLanguage(),
|
|
59
|
+
fallbackLocale: 'zh',
|
|
60
|
+
messages,
|
|
61
|
+
globalInjection: true,
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
export const $t: typeof i18n.global.t = i18n.global.t
|
|
65
|
+
export default i18n
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## 语言检测
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
// src/utils/i18n.ts
|
|
72
|
+
import { storageLocal } from '@pureadmin/utils'
|
|
73
|
+
import Cookies from 'js-cookie'
|
|
74
|
+
|
|
75
|
+
export function getLanguage() {
|
|
76
|
+
const lang = storageLocal().getItem<string>('lang') || Cookies.get('lang')
|
|
77
|
+
if (!lang) {
|
|
78
|
+
return navigator.language.startsWith('en') ? 'en' : 'zh'
|
|
79
|
+
}
|
|
80
|
+
return lang
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
优先级:`localStorage('lang')` > `Cookie('lang')` > `navigator.language` 回退。
|
|
85
|
+
|
|
86
|
+
## 语言状态管理
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
// src/store/modules/lang.ts
|
|
90
|
+
import { storageLocal } from '@pureadmin/utils'
|
|
91
|
+
import Cookies from 'js-cookie'
|
|
92
|
+
import i18n from '@/locales/index'
|
|
93
|
+
import { store } from '@/store'
|
|
94
|
+
import { getLanguage } from '@/utils/i18n'
|
|
95
|
+
|
|
96
|
+
export const useLangStore = defineStore('Lang', {
|
|
97
|
+
state: () => ({
|
|
98
|
+
lang: getLanguage(),
|
|
99
|
+
}),
|
|
100
|
+
actions: {
|
|
101
|
+
setLang(lang) {
|
|
102
|
+
this.lang = lang
|
|
103
|
+
storageLocal().setItem('lang', lang)
|
|
104
|
+
i18n.global.locale.value = lang
|
|
105
|
+
Cookies.set('lang', lang)
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
export function useLangStoreHook() {
|
|
111
|
+
return useLangStore(store)
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## 语言切换组件
|
|
116
|
+
|
|
117
|
+
```vue
|
|
118
|
+
<!-- src/components/ReLanguageSwitch/src/index.vue -->
|
|
119
|
+
<script setup lang="ts">
|
|
120
|
+
import { useGlobal } from '@pureadmin/utils'
|
|
121
|
+
import { ElDropdown, ElDropdownItem, ElDropdownMenu } from 'element-plus'
|
|
122
|
+
import { useLangStore } from '@/store/modules/lang'
|
|
123
|
+
|
|
124
|
+
defineOptions({ name: 'LanguageSwitch' })
|
|
125
|
+
|
|
126
|
+
const langStore = useLangStore()
|
|
127
|
+
const { $t } = useGlobal<GlobalPropertiesApi>()
|
|
128
|
+
|
|
129
|
+
const langSelectList = reactive([
|
|
130
|
+
{ command: 'en', name: 'English', disabled: langStore.lang === 'en' },
|
|
131
|
+
{ command: 'zh', name: $t('中文'), disabled: langStore.lang === 'zh' },
|
|
132
|
+
])
|
|
133
|
+
|
|
134
|
+
function handleSetLang(lang) {
|
|
135
|
+
langStore.setLang(lang)
|
|
136
|
+
window.location.reload()
|
|
137
|
+
}
|
|
138
|
+
</script>
|
|
139
|
+
|
|
140
|
+
<template>
|
|
141
|
+
<ElDropdown @command="handleSetLang">
|
|
142
|
+
<div class="language-trigger">
|
|
143
|
+
<div class="i-ri:translate-2 language-icon" />
|
|
144
|
+
</div>
|
|
145
|
+
<template #dropdown>
|
|
146
|
+
<ElDropdownMenu>
|
|
147
|
+
<ElDropdownItem
|
|
148
|
+
v-for="{ command, name } in langSelectList"
|
|
149
|
+
:key="command"
|
|
150
|
+
:command="command"
|
|
151
|
+
:disabled="langStore.lang === command"
|
|
152
|
+
>
|
|
153
|
+
{{ name }}
|
|
154
|
+
</ElDropdownItem>
|
|
155
|
+
</ElDropdownMenu>
|
|
156
|
+
</template>
|
|
157
|
+
</ElDropdown>
|
|
158
|
+
</template>
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## main.ts 注册
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
import i18n from '@/locales'
|
|
165
|
+
|
|
166
|
+
const app = createApp(App)
|
|
167
|
+
app.use(i18n)
|
|
168
|
+
app.config.globalProperties.$t = i18n.global.t
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## 语言包结构
|
|
172
|
+
|
|
173
|
+
```json
|
|
174
|
+
// src/locales/zh-Cn.json
|
|
175
|
+
{
|
|
176
|
+
"请填写正确的验证码": "请填写正确的验证码",
|
|
177
|
+
"错误提示": "错误提示",
|
|
178
|
+
"确定": "确定",
|
|
179
|
+
"取消": "取消",
|
|
180
|
+
"登录": "登录",
|
|
181
|
+
"退出登录": "退出登录"
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
```json
|
|
186
|
+
// src/locales/en-US.json
|
|
187
|
+
{
|
|
188
|
+
"请填写正确的验证码": "Please enter the correct verification code",
|
|
189
|
+
"错误提示": "Error",
|
|
190
|
+
"确定": "OK",
|
|
191
|
+
"取消": "Cancel",
|
|
192
|
+
"登录": "Login",
|
|
193
|
+
"退出登录": "Log Out"
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## 使用方式
|
|
198
|
+
|
|
199
|
+
### 模板中使用
|
|
200
|
+
|
|
201
|
+
```vue
|
|
202
|
+
<template>
|
|
203
|
+
<span>{{ $t('登录') }}</span>
|
|
204
|
+
<ElButton>{{ $t('确定') }}</ElButton>
|
|
205
|
+
</template>
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### 脚本中使用
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
import { useGlobal } from '@pureadmin/utils'
|
|
212
|
+
const { $t } = useGlobal<GlobalPropertiesApi>()
|
|
213
|
+
const message = $t('请填写正确的验证码')
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### 新增语言
|
|
217
|
+
|
|
218
|
+
1. 在 `src/locales/` 下新建语言包文件(如 `ja-JP.json`)
|
|
219
|
+
2. 在 `src/locales/index.ts` 中 import 并合并到 `messages`
|
|
220
|
+
3. 在 `ReLanguageSwitch` 的 `langSelectList` 中添加选项
|
|
221
|
+
4. 在 `store/modules/lang.ts` 的 `setLang` 中同步持久化
|
|
222
|
+
|
|
223
|
+
## Must-do 清单
|
|
224
|
+
|
|
225
|
+
1. **勿用中文硬编码**:所有用户可见文案用 `$t('中文 key')` 包裹
|
|
226
|
+
2. **语言包 key 用中文**:以中文原文作为 key,`$t('请填写正确的验证码')` 而非 `$t('enterCorrectCode')`
|
|
227
|
+
3. **Element Plus / Vant 语言包合并**:`createI18n` 时须合并三方库自带语言包,确保组件内置文案同步切换
|
|
228
|
+
4. **切换后 reload**:`langStore.setLang(lang)` 后调 `window.location.reload()`,确保所有组件重新渲染
|
|
229
|
+
5. **持久化双写**:`localStorage` + `Cookie` 同时写入,兼容服务端读取
|
|
230
|
+
6. **回退策略**:`fallbackLocale: 'zh'`,未翻译的 key 回退到中文
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
skill: layout-theme
|
|
3
3
|
description: 布局、主题色、暗黑模式、侧栏配色、Design Tokens、UnoCSS、响应式。修改布局/主题/颜色时加载。上游设计产出落地见 design-handoff / design-system。
|
|
4
4
|
scope: project
|
|
5
|
-
tags: [vue3, layout, theme, dark-mode, css-variables, design-tokens, element-plus, vant, dual-ui, unocss, chroma-js]
|
|
5
|
+
tags: [vue3, layout, theme, dark-mode, css-variables, design-tokens, element-plus, vant, dual-ui, unocss, chroma-js, 标签页, 面包屑, tabs, breadcrumb]
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# 设计系统、布局与主题适配
|
package/references/particles.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
skill: particles
|
|
3
|
+
description: tsParticles 登录背景粒子、@tsparticles/slim、particlesOptions
|
|
4
|
+
scope: project
|
|
5
|
+
tags: [粒子, tsparticles, 登录背景粒子, 粒子动画]
|
|
6
|
+
---
|
|
7
|
+
|
|
1
8
|
# 粒子特效(tsParticles)
|
|
2
9
|
|
|
3
10
|
> 按本文件示例编写,复用项目已有 `@tsparticles/*` 注册与 `src/utils/particles.ts` 配置,勿另起一套粒子实现,勿引入 `particles.js`。
|
package/references/rich-text.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
skill: rich-text
|
|
3
|
+
description: '@wangeditor/editor + ReRichText 富文本使用方式、图片/视频上传'
|
|
4
|
+
scope: project
|
|
5
|
+
tags: [富文本, wangeditor, ReRichText, 编辑器, Markdown]
|
|
6
|
+
---
|
|
7
|
+
|
|
1
8
|
# 富文本编辑器
|
|
2
9
|
|
|
3
10
|
> 按本文件示例编写;富文本使用选型表中的 `@wangeditor/editor` + `ReRichText`,勿改用 Quill / TinyMCE 等;新依赖须待确认。
|
package/references/table-vxe.md
CHANGED
package/references/tree-table.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
skill: tree-table
|
|
3
|
+
description: handleTree/buildHierarchyTree 和 el-table 树形表格、树与表格联动
|
|
4
|
+
scope: project
|
|
5
|
+
tags: [树表, ReTreeLine, handleTree, 树形, 树状, 层级表格, 组织架构, 部门树, 树形列表, 树状表格]
|
|
6
|
+
---
|
|
7
|
+
|
|
1
8
|
# 树形表格与树结构
|
|
2
9
|
|
|
3
10
|
> 使用 `handleTree` / `ReTreeLine` + `el-table` tree,按本示例编写。
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 参考文件 AI 可用性审计
|
|
5
|
+
*
|
|
6
|
+
* 从 AI 代码生成角度审计每个 references/*.md 文件:
|
|
7
|
+
* 1. 标准样板 — 是否有完整可抄的代码块(AI 最依赖的"整段照抄")
|
|
8
|
+
* 2. Must-do 清单 — 是否有 AI 可逐项核对的检查项
|
|
9
|
+
* 3. 完整性守则 — 是否声明了"不能只抄部分"的约束
|
|
10
|
+
* 4. 依赖信息 — 是否说明了需要什么包/组件/Hook
|
|
11
|
+
* 5. 交叉引用 — 是否标注了何时同时加载其他模块
|
|
12
|
+
* 6. 代码块完备性 — 代码块是否完整(有开始标签和结束标签)
|
|
13
|
+
* 7. 可替换标记 — 样板中是否有明确的占位符(如 Customer、customer)
|
|
14
|
+
*
|
|
15
|
+
* 核心思路:AI 生成代码时是"照抄样板 + 改名",如果样板不完整或缺约束,
|
|
16
|
+
* AI 生成的代码就会出问题。本脚本检测这些"AI 生成质量风险点"。
|
|
17
|
+
*
|
|
18
|
+
* 用法:
|
|
19
|
+
* node scripts/ai-content-audit.mjs # 全量审计
|
|
20
|
+
* node scripts/ai-content-audit.mjs crud-pages # 审计单个模块
|
|
21
|
+
* node scripts/ai-content-audit.mjs --json # JSON 输出
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import fs from "fs";
|
|
25
|
+
import path from "path";
|
|
26
|
+
import { fileURLToPath } from "url";
|
|
27
|
+
|
|
28
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
29
|
+
const ROOT = path.resolve(__dirname, "..");
|
|
30
|
+
const REFS_DIR = path.join(ROOT, "references");
|
|
31
|
+
|
|
32
|
+
const c = {
|
|
33
|
+
red: (s) => `\x1b[31m${s}\x1b[0m`,
|
|
34
|
+
green: (s) => `\x1b[32m${s}\x1b[0m`,
|
|
35
|
+
yellow: (s) => `\x1b[33m${s}\x1b[0m`,
|
|
36
|
+
cyan: (s) => `\x1b[36m${s}\x1b[0m`,
|
|
37
|
+
bold: (s) => `\x1b[1m${s}\x1b[0m`,
|
|
38
|
+
dim: (s) => `\x1b[2m${s}\x1b[0m`,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// ── 审计单个文件 ─────────────────────────────────────────
|
|
42
|
+
function auditFile(id, content) {
|
|
43
|
+
const report = {
|
|
44
|
+
moduleId: id,
|
|
45
|
+
size: content.length,
|
|
46
|
+
checks: {},
|
|
47
|
+
risks: [],
|
|
48
|
+
score: 0,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// 1. 标准样板检测
|
|
52
|
+
const hasTemplate =
|
|
53
|
+
/标准样板|强制样板|完整示例/.test(content) ||
|
|
54
|
+
/照抄|整段/.test(content);
|
|
55
|
+
const codeBlocks = content.match(/```[\s\S]*?```/g) || [];
|
|
56
|
+
const vueBlocks = codeBlocks.filter((b) => b.startsWith("```vue"));
|
|
57
|
+
const tsBlocks = codeBlocks.filter((b) => b.startsWith("```typescript") || b.startsWith("```ts"));
|
|
58
|
+
const hasCompleteCode = codeBlocks.length > 0;
|
|
59
|
+
report.checks.template = {
|
|
60
|
+
hasStandardTemplate: hasTemplate,
|
|
61
|
+
codeBlockCount: codeBlocks.length,
|
|
62
|
+
vueBlockCount: vueBlocks.length,
|
|
63
|
+
tsBlockCount: tsBlocks.length,
|
|
64
|
+
};
|
|
65
|
+
if (!hasCompleteCode) report.risks.push("无代码块 — AI 无样板可抄");
|
|
66
|
+
if (hasTemplate && codeBlocks.length === 0) report.risks.push("声明了「标准样板」但无代码块");
|
|
67
|
+
|
|
68
|
+
// 2. Must-do 清单检测
|
|
69
|
+
const hasMustDo = /Must-do|必须|清单/.test(content);
|
|
70
|
+
const checklistItems = (content.match(/^- \[ \]/gm) || []).length +
|
|
71
|
+
(content.match(/^\d+\.\s/gm) || []).length;
|
|
72
|
+
report.checks.mustDo = {
|
|
73
|
+
hasMustDoSection: hasMustDo,
|
|
74
|
+
checklistItemCount: checklistItems,
|
|
75
|
+
};
|
|
76
|
+
if (!hasMustDo) report.risks.push("无 Must-do 清单 — AI 生成后无法逐项核对");
|
|
77
|
+
|
|
78
|
+
// 3. 完整性守则检测
|
|
79
|
+
const hasCompletenessRule = /完整性守则|完整性|整段|禁止只/.test(content);
|
|
80
|
+
report.checks.completeness = {
|
|
81
|
+
hasCompletenessRule,
|
|
82
|
+
};
|
|
83
|
+
if (!hasCompletenessRule) report.risks.push("无完整性守则 — AI 可能只抄 API 签名丢掉配套段落");
|
|
84
|
+
|
|
85
|
+
// 4. 依赖信息检测
|
|
86
|
+
const hasDeps = /依赖|pnpm add|package\.json|缺则先装/.test(content);
|
|
87
|
+
const depMatches = content.match(/`([\w@/-]+)@([\d.]+)`/g) || [];
|
|
88
|
+
report.checks.dependencies = {
|
|
89
|
+
hasDepInfo: hasDeps,
|
|
90
|
+
pinnedVersions: depMatches.length,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// 5. 交叉引用检测
|
|
94
|
+
const crossRefPattern = /→\s*`?([a-z][a-z0-9-]+)`?|见\s*`?([a-z][a-z0-9-]+)`?/g;
|
|
95
|
+
let crossRefCount = 0;
|
|
96
|
+
let m;
|
|
97
|
+
while ((m = crossRefPattern.exec(content)) !== null) {
|
|
98
|
+
crossRefCount++;
|
|
99
|
+
}
|
|
100
|
+
const hasCoLoad = /同读|同批|同时加载|同读|一起/.test(content);
|
|
101
|
+
report.checks.crossRefs = {
|
|
102
|
+
crossRefCount,
|
|
103
|
+
hasCoLoadInstruction: hasCoLoad,
|
|
104
|
+
};
|
|
105
|
+
if (crossRefCount === 0) report.risks.push("无交叉引用 — AI 不知道何时还需加载其他模块");
|
|
106
|
+
|
|
107
|
+
// 6. 代码块完备性检测
|
|
108
|
+
const unclosedBlocks = (content.match(/```/g) || []).length % 2;
|
|
109
|
+
report.checks.codeBlocks = {
|
|
110
|
+
totalFences: (content.match(/```/g) || []).length,
|
|
111
|
+
isBalanced: unclosedBlocks === 0,
|
|
112
|
+
};
|
|
113
|
+
if (unclosedBlocks !== 0) report.risks.push("代码块未闭合 — AI 可能截断生成");
|
|
114
|
+
|
|
115
|
+
// 7. 可替换标记检测(样板中是否有占位符暗示 AI 需要改名)
|
|
116
|
+
const placeholders = content.match(/[A-Z][a-z]+(?:List|Detail|Edit|View|Page)/g) || [];
|
|
117
|
+
const hasPlaceholder = placeholders.length > 0;
|
|
118
|
+
report.checks.placeholders = {
|
|
119
|
+
placeholderCount: placeholders.length,
|
|
120
|
+
examples: [...new Set(placeholders)].slice(0, 5),
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// 8. AI 可执行性检测(是否有 step-by-step 指引)
|
|
124
|
+
const hasSteps = /步骤|Step|第一步|1\./.test(content);
|
|
125
|
+
const hasCodeToCopy = codeBlocks.length >= 2;
|
|
126
|
+
report.checks.executability = {
|
|
127
|
+
hasSteps,
|
|
128
|
+
hasEnoughCode: hasCodeToCopy,
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// 评分
|
|
132
|
+
let score = 0;
|
|
133
|
+
if (hasCompleteCode) score += 20;
|
|
134
|
+
if (hasTemplate) score += 15;
|
|
135
|
+
if (hasMustDo) score += 15;
|
|
136
|
+
if (hasCompletenessRule) score += 15;
|
|
137
|
+
if (hasDeps) score += 10;
|
|
138
|
+
if (crossRefCount > 0) score += 10;
|
|
139
|
+
if (unclosedBlocks === 0) score += 10;
|
|
140
|
+
if (hasCoLoad) score += 5;
|
|
141
|
+
report.score = score;
|
|
142
|
+
|
|
143
|
+
return report;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ── 主审计逻辑 ───────────────────────────────────────────
|
|
147
|
+
function auditAll(targetModule) {
|
|
148
|
+
const files = fs
|
|
149
|
+
.readdirSync(REFS_DIR)
|
|
150
|
+
.filter((f) => f.endsWith(".md"))
|
|
151
|
+
.sort();
|
|
152
|
+
|
|
153
|
+
const reports = [];
|
|
154
|
+
for (const file of files) {
|
|
155
|
+
const id = file.replace(".md", "");
|
|
156
|
+
if (targetModule && id !== targetModule) continue;
|
|
157
|
+
const content = fs.readFileSync(path.join(REFS_DIR, file), "utf-8");
|
|
158
|
+
reports.push(auditFile(id, content));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return reports;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ── 文本输出 ─────────────────────────────────────────────
|
|
165
|
+
function printReport(report) {
|
|
166
|
+
const { moduleId, checks, risks, score } = report;
|
|
167
|
+
|
|
168
|
+
console.log(c.bold(`\n${c.cyan(moduleId)} ${c.dim(`(score: ${score}/100)`)}\n`));
|
|
169
|
+
|
|
170
|
+
console.log(c.dim(" 检查项:"));
|
|
171
|
+
const items = [
|
|
172
|
+
["标准样板", checks.template?.hasStandardTemplate, `${checks.template?.codeBlockCount || 0} 代码块`],
|
|
173
|
+
["Must-do 清单", checks.mustDo?.hasMustDoSection, `${checks.mustDo?.checklistItemCount || 0} 项`],
|
|
174
|
+
["完整性守则", checks.completeness?.hasCompletenessRule, ""],
|
|
175
|
+
["依赖信息", checks.dependencies?.hasDepInfo, `${checks.dependencies?.pinnedVersions || 0} 精确版本`],
|
|
176
|
+
["交叉引用", checks.crossRefs?.crossRefCount > 0, `${checks.crossRefs?.crossRefCount || 0} 处引用`],
|
|
177
|
+
["同批加载指引", checks.crossRefs?.hasCoLoadInstruction, ""],
|
|
178
|
+
["代码块闭合", checks.codeBlocks?.isBalanced, `${checks.codeBlocks?.totalFences || 0} 围栏`],
|
|
179
|
+
["可替换标记", checks.placeholders?.placeholderCount > 0, checks.placeholders?.examples?.join(", ") || ""],
|
|
180
|
+
["步骤指引", checks.executability?.hasSteps, ""],
|
|
181
|
+
["充足代码量", checks.executability?.hasEnoughCode, ""],
|
|
182
|
+
];
|
|
183
|
+
|
|
184
|
+
for (const [name, passed, detail] of items) {
|
|
185
|
+
const mark = passed ? c.green("✓") : c.red("✗");
|
|
186
|
+
console.log(` ${mark} ${name.padEnd(16)} ${c.dim(detail)}`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (risks.length > 0) {
|
|
190
|
+
console.log(c.yellow(`\n AI 生成风险 (${risks.length}):`));
|
|
191
|
+
for (const r of risks) {
|
|
192
|
+
console.log(` ${c.yellow("!")} ${r}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function printSummary(reports) {
|
|
198
|
+
console.log(c.bold(`\n${c.cyan("AI 可用性审计汇总")} — ${reports.length} 个模块\n`));
|
|
199
|
+
|
|
200
|
+
// 按评分排序
|
|
201
|
+
const sorted = [...reports].sort((a, b) => b.score - a.score);
|
|
202
|
+
|
|
203
|
+
for (const r of sorted) {
|
|
204
|
+
const bar = "█".repeat(Math.floor(r.score / 5)) +
|
|
205
|
+
c.dim("░".repeat(20 - Math.floor(r.score / 5)));
|
|
206
|
+
const level = r.score >= 70 ? c.green("优") : r.score >= 40 ? c.yellow("中") : c.red("差");
|
|
207
|
+
const riskStr = r.risks.length > 0 ? c.red(` (${r.risks.length} 风险)`) : "";
|
|
208
|
+
console.log(` ${bar} ${r.score.toString().padStart(3)} ${c.bold(r.moduleId.padEnd(22))} ${level}${riskStr}`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// 统计
|
|
212
|
+
const totalRisks = reports.reduce((sum, r) => sum + r.risks.length, 0);
|
|
213
|
+
const avgScore = Math.round(reports.reduce((sum, r) => sum + r.score, 0) / reports.length);
|
|
214
|
+
const excellent = reports.filter((r) => r.score >= 70).length;
|
|
215
|
+
const poor = reports.filter((r) => r.score < 40).length;
|
|
216
|
+
|
|
217
|
+
console.log(c.dim(`\n 平均分: ${avgScore}/100 | 优: ${excellent} | 中: ${reports.length - excellent - poor} | 差: ${poor} | 总风险: ${totalRisks}\n`));
|
|
218
|
+
|
|
219
|
+
// 高风险模块
|
|
220
|
+
const highRisk = reports.filter((r) => r.risks.length >= 2);
|
|
221
|
+
if (highRisk.length > 0) {
|
|
222
|
+
console.log(c.bold(`高风险模块(≥2 个 AI 生成风险):\n`));
|
|
223
|
+
for (const r of highRisk) {
|
|
224
|
+
console.log(` ${c.red("!")} ${r.moduleId} ${c.dim(`(score: ${r.score})`)}`);
|
|
225
|
+
for (const risk of r.risks) {
|
|
226
|
+
console.log(` ${c.yellow("!")} ${risk}`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ── CLI 入口 ────────────────────────────────────────────────
|
|
233
|
+
const args = process.argv.slice(2);
|
|
234
|
+
const targetModule = args.find((a) => !a.startsWith("--"));
|
|
235
|
+
const reports = auditAll(targetModule);
|
|
236
|
+
|
|
237
|
+
if (args.includes("--json")) {
|
|
238
|
+
console.log(JSON.stringify(reports, null, 2));
|
|
239
|
+
} else if (targetModule) {
|
|
240
|
+
printReport(reports[0]);
|
|
241
|
+
console.log("");
|
|
242
|
+
} else {
|
|
243
|
+
printSummary(reports);
|
|
244
|
+
}
|