@qilitt-mickey/vue3-temp-skill 1.1.1 → 1.1.3

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/README.md CHANGED
@@ -59,6 +59,7 @@ Vue 3 企业级中后台项目开发规范技能包 — 让 AI 按团队规范
59
59
  | graph-relation / workflow-bpmn / flowchart-g6 / data-mapping | 图谱与工作流 |
60
60
  | websocket-realtime / mobile-h5 / wechat-js | 实时、移动、微信 |
61
61
  | verify-captcha / qrcode-barcode / feedback-ui / seamless-scroll | 验证码、码、反馈 UI |
62
+ | particles | 粒子特效(tsParticles 登录/氛围背景) |
62
63
  | data-compare / data-screen / directives-advanced | 对比、大屏、指令 |
63
64
 
64
65
  ## 安装
package/SKILL.md CHANGED
@@ -39,7 +39,8 @@ tags: [vue3, typescript, element-plus, vant, pinia, vite, unocss, crud, componen
39
39
  - 选型表已指定三方库(如图谱、流程图、富文本)→ **用指定库,勿擅自换成同类其他库**;确需新库须「待确认」
40
40
  3. **按示例编写**:按本包 `references/` 中的**完整示例**与约定写法编写(命名、结构、调用方式以示例为准)。
41
41
  4. **无则上报**:清单与 Skill 均无对应能力时,标注「待确认」新依赖,未经用户同意不得引入。
42
- 5. **真相源优先级**:`仓库现有实现` > `本 Skill references 示例` > 通用 Vue 习惯。
42
+ 5. **真相源优先级**:`仓库已有可复用封装(Re* / Hook / utils)` > `本 Skill references 完整示例` > 通用 Vue 习惯。
43
+ **禁止**把仓库里的 views 演示页当作规范来源(业务项目可能没有演示页);列表布局、弹窗写法等以 Skill 内黄金样板为准。
43
44
  命名以代码为准:列表方法 **`onSearch` / `reset`**;响应类型全局 **`Result<T>`**(`types/global.d.ts`);HTTP 为 **`import { http } from '@/utils/http'`**。
44
45
  6. **依赖 / 插件安装(addon 必做)**:命中专项能力时,先读业务项目 `package.json`。
45
46
  - 已装齐 → 直接写代码
@@ -98,6 +99,7 @@ tags: [vue3, typescript, element-plus, vant, pinia, vite, unocss, crud, componen
98
99
  | **qrcode-barcode** | 二维码/条形码 | 二维码、条形码、qrcode、barcode |
99
100
  | **feedback-ui** | 通知栏/水印/时间轴等 | 通知栏、水印、时间轴、打字机 |
100
101
  | **seamless-scroll** | 无缝滚动 | 无缝滚动、跑马灯 |
102
+ | **particles** | 粒子特效(登录/氛围背景) | 粒子、粒子特效、tsparticles、登录背景粒子、粒子动画、连线粒子 |
101
103
  | **directives-advanced** | 文本一键复制、长标题字号随宽度变小等体验(用户说效果即可,无需知指令名) | 一键复制、点一下复制、不好抄、标题太长、显示不全、字号跟着变小、文字别截断、挤得下;内部映射 v-copy / v-auto-font |
102
104
 
103
105
  ### 匹配规则
@@ -114,6 +116,7 @@ tags: [vue3, typescript, element-plus, vant, pinia, vite, unocss, crud, componen
114
116
  - 「编辑标出改了哪些 / 变更汇总」→ `data-compare`
115
117
  - 「导出 Excel / 下载文件」→ `download-export`
116
118
  - 「菜单加图标 / 换图标 / 图标选择器 / 本地 SVG 图标」→ `icons`
119
+ - 「粒子背景 / 登录粒子 / 粒子特效」→ `particles`
117
120
  - 只说「自定义指令」未说明效果 → 先追问,禁止默认塞模块
118
121
  6. **HTTP**:方法跟后端文档,**允许 GET/POST**;POST→`data`,GET→`params`;真实 URL 用 `VITE_API_BASE_URL`;`/mock` 仅本地 Fake Server;无单请求 `crypto: true`。
119
122
 
@@ -171,7 +174,7 @@ export function getXxxListApi(data: XxxQuery) {
171
174
  ### 列表页
172
175
 
173
176
  ```typescript
174
- const { content, pagination, loading, onSearch, reset, formRef, tableData, ... } =
177
+ const { content, pagination, loading, onSearch, reset, formRef, tableData, tableHeight, ... } =
175
178
  useTableSearch(formQuery, getXxxListApi, `${import.meta.env.VITE_API_BASE_URL}/console/xxx/list`);
176
179
 
177
180
  onSearch(); // 查询
@@ -180,6 +183,10 @@ reset(); // 重置(配合 formRef.resetFields)
180
183
 
181
184
  - 方法名必须是 **`onSearch` / `reset`**,不是 handleSearch/handleReset。
182
185
  - 配合 `ReGrid` / `ReGridItem` / `ReTableBar` / `RePagination` / `ReTableOperate`。
186
+ - **布局必须按 `core-kernel`「列表黄金样板」完整输出**(技能自包含,不依赖仓库 views 演示页):
187
+ - `el-card ref="content"` + 固定 `height`(含 `headerHeight`),禁止只用 `minHeight` / `ref` 挂 `div`
188
+ - 查询区 `bg-bg_color p-5` + `ReGrid` `:gap="[0, 20]"` + 表单项 `w-[100%] !mr-0`;与 `ReTableBar` 展示区分开
189
+ - 表格绑 `tableHeight`;分页放在 `ReTableBar` 内,避免无数据时贴查询区
183
190
 
184
191
  ### 路由
185
192
 
@@ -245,6 +252,7 @@ reset(); // 重置(配合 formRef.resetFields)
245
252
  **3. API** — `@/utils/http`;方法跟后端(允许 GET/POST);封装在 `src/api/`;`VITE_API_BASE_URL`;无虚构 `/api` / 无 `crypto: true`
246
253
  **4. 安全** — 无危险 v-html;无硬编码密钥
247
254
  **5. 业务** — `onSearch`/`reset`;提交 loading;权限;未二次 Layout
255
+ **5b. 列表布局** — `el-card`+`content`+固定 height;查询/展示分区;`tableHeight`;分页在 ReTableBar 内
248
256
  **6. 风格** — 无多余 console;`$t('中文')`;`--vts-*` / UnoCSS;图标走 `svg-icon` / `icons` 约定
249
257
 
250
258
  完整项见 `references/code-quality.md`。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qilitt-mickey/vue3-temp-skill",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "description": "Vue 3 企业级中后台项目开发规范技能包 — core-kernel 架构、按需功能模块",
5
5
  "bin": {
6
6
  "vue3-temp-skill": "./bin/cli.js"
@@ -22,6 +22,7 @@ tags: [code-review, quality, checklist, lint, typescript, vue3, security, perfor
22
22
  - [ ] HTTP 为 `import { http } from '@/utils/http'` + 全局 `Result<T>`
23
23
  - [ ] 业务路由未二次挂 Layout
24
24
  - [ ] 使用对应 `references/<模块>.md` 示例与约定写法
25
+ - [ ] **查询列表布局**:`el-card ref="content"` + 固定 `height`(含 `headerHeight`);查询区 `bg-bg_color p-5` + `ReGrid` `gap`/`cols`;表格绑 `tableHeight`;分页在 `ReTableBar` 内
25
26
 
26
27
 
27
28
  ## 一、TypeScript 类型检查
@@ -226,6 +227,18 @@ const fullName = computed(() => `${user.firstName} ${user.lastName}`);
226
227
  - [ ] 路由跳转使用了正确的参数传递方式(`query` 而非 `params`)。
227
228
  - [ ] 分页切换正确触发了数据加载。
228
229
 
230
+ ### 查询列表页布局(命中 crud-pages / 列表需求时必查)
231
+
232
+ - [ ] `ref="content"` 挂在 **`el-card`**(组件),不是原生 `div`。
233
+ - [ ] 卡片样式为固定 **`height: calc(100vh - 2 * var(--vts-margin) - ${headerHeight}px)`**,不是 `minHeight`。
234
+ - [ ] 查询 `el-form`:`inline` + `class="bg-bg_color p-5"`;`ReGrid` 有 `:gap="[0, 20]"` 与响应式 `cols`。
235
+ - [ ] 表单项有 `class="w-[100%] !mr-0"`;操作按钮用 `<ReGridItem suffix>`。
236
+ - [ ] 折叠事件为 `@should-show-collapse`(不是 `@show-collapse`)。
237
+ - [ ] 展示区用 `ReTableBar class="bg-bg_color"`,与查询区视觉分离。
238
+ - [ ] 表格 `:style="{ height: \`${tableHeight}px\` }"`;`paginationRef` 在表格下方且同属 `ReTableBar` `#default`。
239
+ - [ ] `RePagination` 使用 `:pagination` + `@handle-current-change` / `@handle-size-change`。
240
+ - [ ] `ReTableOperate` 使用 `:list`(不是 `:buttons`)。
241
+
229
242
  ## 八、代码风格检查
230
243
 
231
244
  ### 必须通过
@@ -28,6 +28,7 @@ tags: [vue3, kernel, conventions, layout, router, http, crud, inventory]
28
28
  | 8 | i18n:`$t('中文key')`,key 即中文文案 |
29
29
  | 9 | 禁止用 `v-html` 渲染不可信内容 |
30
30
  | 10 | 新三方依赖必须用户确认;已有封装须复用,选型表指定库勿擅自替换 |
31
+ | 11 | **查询列表页布局硬约束**(见下方黄金样板):`el-card` + `ref="content"` + **固定 `height`**(非 `minHeight`);查询区与表格区分区;表格用 `tableHeight`;分页放在 `ReTableBar` 内 |
31
32
  | 11 | addon 缺失时按 `project-inventory` 第九节**精确版本**安装;禁止 `@latest` / 无版本号;Vite 插件与 `build/plugins/*` 成套核对 |
32
33
 
33
34
  ---
@@ -116,8 +117,23 @@ ReAuth、ReDialog、ReGrid、ReGridItem、RePagination、ReTableBar、ReTableOpe
116
117
 
117
118
  ### 列表黄金样板
118
119
 
120
+ > **布局真相源即本样板**(技能自包含,不依赖业务仓库是否存在演示页)。生成查询列表页必须完整按下方结构输出,禁止简化成「表单 + 裸表格 + 裸分页」——否则会出现字段挤在一起、查询与展示区分不清、无数据时分页贴查询区、页面高度不饱满。
121
+
122
+ #### 布局硬约束(违反即不合格)
123
+
124
+ | # | 必须 | 说明 |
125
+ |---|------|------|
126
+ | L1 | `el-card` + `ref="content"` | `useTableSearch` 通过 `content.$el` 算高度;`ref` 必须挂在 **组件**(`el-card`)上,禁止挂在原生 `div` |
127
+ | L2 | 卡片用固定 **`height`** | `height: calc(100vh - 2 * var(--vts-margin) - ${headerHeight}px)`;**禁止**只写 `minHeight`(否则 `tableHeight` 算不准,表格无法撑满) |
128
+ | L3 | 查询区与展示区分开 | 查询:`el-form` + `class="bg-bg_color p-5"`;展示:`ReTableBar class="bg-bg_color"`;两区视觉分离 |
129
+ | L4 | `ReGrid` 间距与栅格 | `:gap="[0, 20]"` + `:cols="{ xs: 1, sm: 2, md: 2, lg: 3, xl: 4 }"`;表单项 `class="w-[100%] !mr-0"`;事件为 `@should-show-collapse` |
130
+ | L5 | 操作按钮用 `suffix` | `<ReGridItem suffix>`,勿用随意 `index=99` 冒充后缀列 |
131
+ | L6 | 表格动态高度 | `:style="{ height: \`${tableHeight}px\` }"`;分页 **必须在** `ReTableBar` 的 `#default` 内、表格下方,保证空数据时仍占满剩余高度 |
132
+ | L7 | `RePagination` 真实 API | `:pagination="pagination"` + `@handle-current-change` / `@handle-size-change`;**禁止**虚构的 `v-model:current-page` |
133
+
119
134
  ```vue
120
135
  <script setup lang="ts">
136
+ import type { OperateButton } from "@/components/ReTableOperate";
121
137
  import type { Customer, CustomerQuery } from "@/api/customer";
122
138
  import { getCustomerList } from "@/api/customer";
123
139
  import { ReGrid } from "@/components/ReGrid";
@@ -159,6 +175,15 @@ const {
159
175
 
160
176
  const tableList = computed(() => tableData.value.tableList as Customer[]);
161
177
 
178
+ const operateButtons: OperateButton[] = [
179
+ {
180
+ label: $t("删除"),
181
+ type: "danger",
182
+ icon: "i-ep:delete",
183
+ onClick: (row) => handleDelete(row as Customer),
184
+ },
185
+ ];
186
+
162
187
  onMounted(() => {
163
188
  onSearch();
164
189
  });
@@ -167,91 +192,147 @@ function handleDelete(row: Customer) {
167
192
  message($t("删除成功"), { type: "success" });
168
193
  onSearch();
169
194
  }
195
+
196
+ function handleCurrentChange(cur: number) {
197
+ pagination.currentPage = cur;
198
+ onSearch();
199
+ }
200
+
201
+ function handleSizeChange(size: number) {
202
+ pagination.pageSize = size;
203
+ onSearch();
204
+ }
170
205
  </script>
171
206
 
172
207
  <template>
173
- <div
174
- ref="content"
175
- :style="{ minHeight: `calc(100vh - 2 * var(--vts-margin) - ${headerHeight}px)` }"
176
- class="bg-[var(--el-bg-color)] p-2"
177
- >
178
- <el-form ref="formRef" :model="formQuery" @keyup.enter="onSearch">
179
- <ReGrid ref="gridRef" :collapsed="collapsed" @show-collapse="showCollapse = $event">
180
- <ReGridItem :index="0">
181
- <el-form-item :label="$t('关键词')" prop="keyword">
182
- <el-input v-model="formQuery.keyword" clearable :placeholder="$t('请输入')" />
183
- </el-form-item>
184
- </ReGridItem>
185
- <ReGridItem :index="1">
186
- <el-form-item :label="$t('状态')" prop="status">
187
- <el-select v-model="formQuery.status" clearable :placeholder="$t('请选择')">
188
- <el-option :label="$t('启用')" :value="1" />
189
- <el-option :label="$t('禁用')" :value="0" />
190
- </el-select>
191
- </el-form-item>
192
- </ReGridItem>
193
- <ReGridItem :index="99" :collapsed="false">
194
- <el-form-item>
195
- <el-button type="primary" :loading="loading" @click="onSearch">{{ $t("查询") }}</el-button>
196
- <el-button @click="reset()">{{ $t("重置") }}</el-button>
197
- <el-button
198
- v-if="showCollapse"
199
- text
200
- type="primary"
201
- @click="collapsedToggle"
202
- >
203
- {{ collapsed ? $t("展开") : $t("收起") }}
204
- <component :is="collapsed ? downBold : upBold" class="ml-1" />
205
- </el-button>
206
- </el-form-item>
207
- </ReGridItem>
208
- </ReGrid>
209
- </el-form>
210
-
211
- <ReTableBar
212
- :columns="[]"
213
- @refresh="onSearch"
214
- >
215
- <template #buttons>
216
- <el-button type="primary" v-auth="['admin']">{{ $t("新增") }}</el-button>
217
- </template>
218
- </ReTableBar>
219
-
220
- <el-table
221
- ref="multipleTableRef"
222
- v-loading="loading"
223
- :element-loading-svg="svgLoading"
224
- :data="tableList"
225
- :height="tableHeight"
226
- border
227
- row-key="id"
208
+ <div>
209
+ <!-- L1/L2:ref=content 必须在 el-card;用 height 非 minHeight -->
210
+ <el-card
211
+ ref="content"
212
+ shadow="never"
213
+ :style="{
214
+ height: `calc(100vh - 2 * var(--vts-margin) - ${headerHeight}px)`,
215
+ }"
228
216
  >
229
- <el-table-column prop="code" :label="$t('编号')" min-width="120" />
230
- <el-table-column prop="name" :label="$t('名称')" min-width="140" />
231
- <el-table-column :label="$t('操作')" width="160" fixed="right">
232
- <template #default="{ row }">
233
- <ReTableOperate
234
- :buttons="[
235
- { label: $t('编辑'), onClick: () => {} },
236
- { label: $t('删除'), type: 'danger', onClick: () => handleDelete(row) },
237
- ]"
238
- />
217
+ <!-- L3:查询区独立背景与内边距 -->
218
+ <div>
219
+ <el-form
220
+ ref="formRef"
221
+ :inline="true"
222
+ :model="formQuery"
223
+ class="bg-bg_color p-5"
224
+ >
225
+ <!-- L4:gap + cols;事件名 should-show-collapse -->
226
+ <ReGrid
227
+ ref="gridRef"
228
+ :collapsed="collapsed"
229
+ :gap="[0, 20]"
230
+ :cols="{ xs: 1, sm: 2, md: 2, lg: 3, xl: 4 }"
231
+ style="max-height: 300px; overflow-y: auto;"
232
+ @should-show-collapse="(val) => (showCollapse = val)"
233
+ >
234
+ <ReGridItem index="0">
235
+ <el-form-item :label="$t('关键词')" prop="keyword" class="w-[100%] !mr-0">
236
+ <el-input
237
+ v-model="formQuery.keyword"
238
+ clearable
239
+ :placeholder="$t('请输入')"
240
+ />
241
+ </el-form-item>
242
+ </ReGridItem>
243
+ <ReGridItem index="1">
244
+ <el-form-item :label="$t('状态')" prop="status" class="w-[100%] !mr-0">
245
+ <el-select v-model="formQuery.status" clearable :placeholder="$t('请选择')">
246
+ <el-option :label="$t('启用')" :value="1" />
247
+ <el-option :label="$t('禁用')" :value="0" />
248
+ </el-select>
249
+ </el-form-item>
250
+ </ReGridItem>
251
+ <!-- L5:操作列用 suffix -->
252
+ <ReGridItem suffix>
253
+ <div class="flex flex-end">
254
+ <el-button
255
+ type="primary"
256
+ size="small"
257
+ :loading="loading"
258
+ @click="pagination.currentPage = 1; pagination.pageSize = 10; onSearch()"
259
+ >
260
+ {{ $t("查询") }}
261
+ </el-button>
262
+ <el-button size="small" @click="reset">
263
+ {{ $t("重置") }}
264
+ </el-button>
265
+ <el-button
266
+ v-if="showCollapse"
267
+ type="primary"
268
+ link
269
+ class="!font-size-12"
270
+ @click="collapsedToggle"
271
+ >
272
+ {{ collapsed ? $t("展开") : $t("收起") }}
273
+ <el-icon class="el-icon--right">
274
+ <component :is="collapsed ? downBold : upBold" />
275
+ </el-icon>
276
+ </el-button>
277
+ </div>
278
+ </ReGridItem>
279
+ </ReGrid>
280
+ </el-form>
281
+ </div>
282
+
283
+ <!-- L3/L6:展示区在 ReTableBar 内;表格+分页同槽,保证空数据也撑满 -->
284
+ <ReTableBar class="bg-bg_color" :columns="[]" @refresh="onSearch">
285
+ <template #title>
286
+ <span class="truncate font-size-16 font-bold">{{ $t("展示列表") }}</span>
287
+ </template>
288
+ <template #buttons>
289
+ <el-button type="primary" size="small" v-auth="['admin']">
290
+ {{ $t("新增") }}
291
+ </el-button>
292
+ </template>
293
+ <template #default="{ size }">
294
+ <div class="vl-parent">
295
+ <el-table
296
+ ref="multipleTableRef"
297
+ v-loading.lock="loading"
298
+ :element-loading-text="$t('加载中')"
299
+ :element-loading-spinner="svgLoading"
300
+ element-loading-svg-view-box="0, 0, 20, 20"
301
+ :style="{ height: `${tableHeight}px` }"
302
+ :size="size"
303
+ :data="tableList"
304
+ :header-cell-style="{
305
+ background: 'var(--el-fill-color-light)',
306
+ color: 'var(--el-text-color-primary)',
307
+ }"
308
+ row-key="id"
309
+ >
310
+ <el-table-column prop="code" :label="$t('编号')" min-width="120" />
311
+ <el-table-column prop="name" :label="$t('名称')" min-width="140" />
312
+ <el-table-column :label="$t('操作')" width="160" fixed="right">
313
+ <template #default="{ row, $index }">
314
+ <ReTableOperate :list="operateButtons" :row="row" :index="$index" />
315
+ </template>
316
+ </el-table-column>
317
+ </el-table>
318
+
319
+ <!-- L7:真实分页 API;ref=paginationRef 参与 tableHeight 计算 -->
320
+ <RePagination
321
+ ref="paginationRef"
322
+ :size="size"
323
+ :pagination="pagination"
324
+ @handle-current-change="handleCurrentChange"
325
+ @handle-size-change="handleSizeChange"
326
+ />
327
+ </div>
239
328
  </template>
240
- </el-table-column>
241
- </el-table>
242
-
243
- <RePagination
244
- ref="paginationRef"
245
- v-model:current-page="pagination.currentPage"
246
- v-model:page-size="pagination.pageSize"
247
- :total="pagination.total"
248
- @change="onSearch"
249
- />
329
+ </ReTableBar>
330
+ </el-card>
250
331
  </div>
251
332
  </template>
252
333
  ```
253
334
 
254
- `useTableSearch(formQuery, searchFn, url)`:`searchFn` 签名为 `(query, url) => Promise<Result<T>>`,分页字段为 `pageNo` / `pageSize`,列表数据取 `data.content`,总数 `data.totalCount`。
335
+ `useTableSearch(formQuery, searchFn, url)`:`searchFn` 签名为 `(query, url) => Promise<Result<T>>`,分页字段为 `pageNo` / `pageSize`,列表数据取 `data.content`,总数 `data.totalCount`。返回值中的 `content` / `tableHeight` / `paginationRef` / `formRef` 必须按样板绑定,否则动态高度失效。
255
336
 
256
337
  ---
257
338
 
@@ -270,7 +351,7 @@ function handleDelete(row: Customer) {
270
351
 
271
352
  ### 附加 Utils
272
353
 
273
- `uploadManager` / `imageCompression` → file;`changeSummaryService` → data-compare;`websocket/**` / `wechat/**` / `vantRules` / `particles` / `motion` → 对应专项。
354
+ `uploadManager` / `imageCompression` → file;`changeSummaryService` → data-compare;`websocket/**` / `wechat/**` / `vantRules` 对应专项;`particles` / `particlesOptions` → **particles**;`motion` → feedback-ui / 登录页入场动画。
274
355
 
275
356
  ---
276
357
 
@@ -1,14 +1,15 @@
1
1
  ---
2
2
  skill: crud-pages
3
- description: 查询列表页、表单页、标准 CRUD 流程。含 useTableSearch(onSearch/reset)、ReGrid 查询区、表格工具栏。务必按照本文件完整示例编写。
3
+ description: 查询列表页、表单页、标准 CRUD 流程。含 useTableSearch(onSearch/reset)、ReGrid 查询区、表格工具栏与动态高度布局。务必按照本文件与 core-kernel 完整示例编写。
4
4
  scope: project
5
- tags: [vue3, crud, list, form, detail, table, useTableSearch, page]
5
+ tags: [vue3, crud, list, form, detail, table, useTableSearch, page, layout]
6
6
  ---
7
7
 
8
8
  # CRUD 页面开发规范
9
9
 
10
10
  > 以下示例即规范真相。
11
11
  > 须同时遵守 `core-kernel` / `http-api`。详情跳转见 `detail-page`,回显见 `data-writeback`。
12
+ > **查询列表页完整模板以 `core-kernel.md`「列表黄金样板」为准**(技能内完整示例,勿到仓库里找 views 演示页对照)。
12
13
 
13
14
  ## 查询列表页
14
15
 
@@ -17,23 +18,40 @@ tags: [vue3, crud, list, form, detail, table, useTableSearch, page]
17
18
  ```typescript
18
19
  useTableSearch(formQuery, searchFn, url)
19
20
  // searchFn: (query: CombinedQueryData, url: string) => Promise<Result<T>>
20
- // 返回含:content, tableData, pagination, loading, formRef, onSearch, reset, tableHeight, ...
21
+ // 返回含:content, tableData, pagination, loading, formRef, onSearch, reset,
22
+ // tableHeight, paginationRef, gridRef, showCollapse, collapsed,
23
+ // collapsedToggle, upBold, downBold, multipleTableRef, ...
21
24
  ```
22
25
 
23
26
  - 查询:`onSearch()`
24
27
  - 重置:`reset()`(内部 `formRef.resetFields()`)
25
28
  - 列表:`tableData.value.tableList`(或经 computed 断言)
26
29
  - 分页:请求带 `pageNo` / `pageSize`;响应 `data.content` / `data.totalCount`
30
+ - **高度**:`tableHeight` 由 ResizeObserver 根据 `content` / `formRef` / `paginationRef` 计算;三者必须按黄金样板绑定
31
+
32
+ ### 布局硬约束(生成列表页必须全部满足)
33
+
34
+ | 区域 | 正确写法 | 禁止 |
35
+ |------|---------|------|
36
+ | 外层容器 | `<el-card ref="content" shadow="never" :style="{ height: \`calc(100vh - 2 * var(--vts-margin) - ${headerHeight}px)\` }">` | `ref` 挂原生 `div`;只用 `minHeight`;漏 `headerHeight` |
37
+ | 查询区 | `el-form` `:inline="true"` + `class="bg-bg_color p-5"` | 无内边距、无背景、字段横向挤成一团 |
38
+ | 栅格 | `ReGrid` `:gap="[0, 20]"` + `:cols="{ xs: 1, sm: 2, md: 2, lg: 3, xl: 4 }"`;`@should-show-collapse` | 无 `gap`;事件写成 `@show-collapse` |
39
+ | 表单项 | `el-form-item` 加 `class="w-[100%] !mr-0"`;`ReGridItem` 用 `index="0"` 字符串 | 表单项无宽度、彼此贴死 |
40
+ | 查询按钮 | `<ReGridItem suffix>` + `flex flex-end` | 用 `index="99"` 冒充后缀 |
41
+ | 展示区 | `ReTableBar class="bg-bg_color"`,与查询区分离 | 查询与表格糊成一块、无标题栏 |
42
+ | 表格高度 | `:style="{ height: \`${tableHeight}px\` }"` | 不绑 `tableHeight`(空数据时分页贴查询区) |
43
+ | 分页 | 放在 `ReTableBar` `#default` 内、表格下方;`RePagination` + `:pagination` + `@handle-current-change` / `@handle-size-change` | 分页在卡片外;虚构 `v-model:current-page` |
27
44
 
28
45
  ### 完整列表示例
29
46
 
30
47
  见 `core-kernel.md`「列表黄金样板」。本模块补充约定:
31
48
 
32
49
  1. 查询区用 **`ReGrid` + `ReGridItem`**,折叠与 `collapsed` / `showCollapse` / `collapsedToggle` 配套。
33
- 2. 工具栏用 **`ReTableBar`**,行操作用 **`ReTableOperate`**,分页用 **`RePagination`**。
34
- 3. 查询 / 重置按钮文案 `$t('查询')` / `$t('重置')`,点击绑定 **`onSearch` / `reset`**。
50
+ 2. 工具栏用 **`ReTableBar`**,行操作用 **`ReTableOperate`**(`:list` 非 `:buttons`),分页用 **`RePagination`**。
51
+ 3. 查询 / 重置按钮文案 `$t('查询')` / `$t('重置')`(或模板一致的「搜索」),点击绑定 **`onSearch` / `reset`**。
35
52
  4. `defineOptions({ name })` 与路由 `name` 一致(keep-alive / 标签)。
36
53
  5. 需要权限的按钮加 `v-auth` / `ReAuth`。
54
+ 6. 从 `useApp()` 取 **`headerHeight`**、`svgLoading`(及按需 `filterValue` / `baseCodeGet`)。
37
55
 
38
56
  ### API 配套
39
57
 
@@ -45,11 +63,12 @@ export function getCustomerList(data: CustomerQuery, url: string) {
45
63
  ```
46
64
 
47
65
  ```typescript
48
- const { onSearch, reset, tableData, loading, pagination, ... } = useTableSearch(
49
- formQuery,
50
- getCustomerList,
51
- `${import.meta.env.VITE_API_BASE_URL}/console/customer/list`,
52
- );
66
+ const { onSearch, reset, tableData, loading, pagination, content, tableHeight, ... } =
67
+ useTableSearch(
68
+ formQuery,
69
+ getCustomerList,
70
+ `${import.meta.env.VITE_API_BASE_URL}/console/customer/list`,
71
+ );
53
72
  onMounted(() => onSearch());
54
73
  ```
55
74
 
@@ -140,3 +159,11 @@ async function onSubmit() {
140
159
  - 提交无 loading
141
160
  - 业务路由二次 Layout
142
161
  - ReSelectQuery 写成不存在的 `:api` / `label-key` API(真实 props 见 form-advanced)
162
+ - **`ref="content"` 挂在 `div` 上**(`content.$el` 为空 → `tableHeight` 恒为 0)
163
+ - 卡片只用 **`minHeight`**,不用固定 **`height`**(页面不饱满、表格高度算错)
164
+ - 查询表单无 `p-5` / 无 `gap` / 表单项无 `w-[100%] !mr-0`(字段挤在一起)
165
+ - 查询区与 `ReTableBar` 无分区(条件与展示糊成一块)
166
+ - 表格不绑 `tableHeight`,分页放在卡片外(无数据时分页直接贴查询条件)
167
+ - `RePagination` 写成 `v-model:current-page`(组件真实 API 为 `:pagination` + handle 事件)
168
+ - `ReTableOperate` 写成 `:buttons`(真实 prop 为 `:list`)
169
+ - `ReGrid` 监听 `@show-collapse`(真实事件为 `@should-show-collapse`)
@@ -203,7 +203,7 @@ function parseAddress(text: string) {
203
203
  ## ReDialog 通用弹窗(必用)
204
204
 
205
205
  > 落点:`src/components/ReDialog`。基于 `el-dialog` 封装:居中、可拖拽、`destroy-on-close`、默认禁止点遮罩关闭、可选全屏切换、内容区 `el-scrollbar`。
206
- > 样板参考:`src/views/table/base/index.vue`、`src/views/form/moreForm/MultiFormDialog.vue`。
206
+ > 写法以**本模块下方黄金样板**为准(技能自包含,勿依赖仓库 views 演示页)。
207
207
 
208
208
  ### Props / 插槽
209
209
 
@@ -154,7 +154,7 @@ export default {
154
154
 
155
155
  ## 五、ReIconPicker 图标选择器
156
156
 
157
- 路径:`src/components/ReIconPicker`。演示页:`src/views/form/iconPicker`(`/form/icon-picker`)。
157
+ 路径:`src/components/ReIconPicker`。用法以本模块示例为准。
158
158
 
159
159
  ### 默认图标源
160
160
 
@@ -0,0 +1,143 @@
1
+ # 粒子特效(tsParticles)
2
+
3
+ > 按本文件示例编写,复用项目已有 `@tsparticles/*` 注册与 `src/utils/particles.ts` 配置,勿另起一套粒子实现,勿引入 `particles.js`。
4
+
5
+ 规范登录页 / 氛围页粒子背景的依赖、全局注册、配置与页面用法。在需要粒子背景、登录特效、连线粒子时参照。
6
+
7
+ ## 依赖(缺则先装)
8
+
9
+ | 包 | 版本 | 用途 |
10
+ |---|---|---|
11
+ | `@tsparticles/slim` | `4.3.1` | 轻量引擎(`loadSlim`) |
12
+ | `@tsparticles/vue3` | `4.3.1` | Vue3 组件 `vue-particles` |
13
+
14
+ ```bash
15
+ pnpm add @tsparticles/slim@4.3.1 @tsparticles/vue3@4.3.1
16
+ ```
17
+
18
+ 完整模板一般为 **core** 已装齐;裁剪项目缺失时按上表精确版本安装。构建侧若有 `tsparticles-vendor` 拆包(见 `build/chunks.ts`),模板已接线则勿改。
19
+
20
+ ## 全局注册(`main.ts`)
21
+
22
+ 必须在应用挂载前注册,并用 `loadSlim` 初始化引擎:
23
+
24
+ ```typescript
25
+ import type { Plugin } from "vue";
26
+ import { loadSlim } from "@tsparticles/slim";
27
+ import Particles from "@tsparticles/vue3";
28
+
29
+ app.use(Particles as unknown as Plugin, {
30
+ init: async (engine: unknown) => {
31
+ await loadSlim(engine as Parameters<typeof loadSlim>[0]);
32
+ },
33
+ });
34
+ ```
35
+
36
+ - 使用 **slim** 包即可覆盖登录背景常见能力;勿默认换成 `@tsparticles/engine` 全量包。
37
+ - 类型断言以仓库现有写法为准(`as unknown as Plugin`)。
38
+
39
+ ## 配置(`src/utils/particles.ts`)
40
+
41
+ 导出名为 **`particlesOptions`**(以仓库为准)。v4 已移除 `particles.color` / `particles.stroke`,必须用 **paint** 系统:
42
+
43
+ | 旧(勿再用) | 新(v4) |
44
+ |---|---|
45
+ | `particles.color` | `particles.paint[].fill` |
46
+ | `particles.stroke` | `particles.paint[].stroke` |
47
+ | `links.color` 直接传数组/字符串 | `links.color: { value: '...' }`(`IRangeColor`) |
48
+
49
+ 关键约定:
50
+
51
+ 1. **登录背景**:`background.color.value` 用 `transparent`,避免挡住背景图。
52
+ 2. **非全屏铺层**:`fullScreen.enable: false`,由页面 CSS 控制 `#tsparticles` 尺寸(见 `src/styles/login.css`)。
53
+ 3. **彩色粒子**:用 `paint` 数组(多组 fill)实现;勿写回旧 `color` 字段。
54
+ 4. **交互**:项目默认 hover `repulse`、click `push`;业务可按场景改 `interactivity`,但保持配置集中在 `particles.ts`。
55
+
56
+ 精简示例(完整默认值以仓库 `src/utils/particles.ts` 为准):
57
+
58
+ ```typescript
59
+ export const particlesOptions = {
60
+ background: {
61
+ color: { value: "transparent" },
62
+ },
63
+ fullScreen: { enable: false },
64
+ fpsLimit: 120,
65
+ interactivity: {
66
+ events: {
67
+ onClick: { enable: true, mode: "push" },
68
+ onHover: { enable: true, mode: "repulse" },
69
+ },
70
+ modes: {
71
+ push: { quantity: 4 },
72
+ repulse: { distance: 200, duration: 0.4 },
73
+ },
74
+ },
75
+ particles: {
76
+ paint: [
77
+ {
78
+ fill: {
79
+ enable: true,
80
+ color: { value: "#4a5568" },
81
+ opacity: 0.95,
82
+ },
83
+ stroke: { enable: false },
84
+ },
85
+ ],
86
+ links: {
87
+ enable: true,
88
+ color: { value: "#4a5568" },
89
+ distance: 150,
90
+ opacity: 0.8,
91
+ width: 1,
92
+ },
93
+ move: {
94
+ direction: "none",
95
+ enable: true,
96
+ outModes: "bounce",
97
+ speed: 5,
98
+ },
99
+ number: {
100
+ density: { enable: true },
101
+ value: 80,
102
+ },
103
+ shape: { type: "circle" },
104
+ size: { value: { min: 2.5, max: 6 } },
105
+ },
106
+ detectRetina: true,
107
+ };
108
+ ```
109
+
110
+ ## 页面用法(登录页)
111
+
112
+ ```vue
113
+ <script setup lang="ts">
114
+ import { particlesOptions } from "@/utils/particles";
115
+ </script>
116
+
117
+ <template>
118
+ <vue-particles id="tsparticles" :options="particlesOptions" />
119
+ <!-- 业务内容 -->
120
+ </template>
121
+ ```
122
+
123
+ 配套样式(登录场景):
124
+
125
+ ```css
126
+ #tsparticles {
127
+ position: absolute;
128
+ width: 100%;
129
+ height: 100%;
130
+ }
131
+ ```
132
+
133
+ - `id` 与 CSS 选择器保持一致(默认 `#tsparticles`)。
134
+ - 粒子层放在页面内容之下,避免挡住表单点击;必要时用 `z-index` / 容器定位调整。
135
+ - 其他氛围页可复用同一配置,或在 `particles.ts` 中导出第二套 options(如 `particlesOptionsDark`),勿在页面内联大段配置。
136
+
137
+ ## 选型与禁止项
138
+
139
+ 1. **只用** `@tsparticles/slim` + `@tsparticles/vue3`,禁止 `particles.js` / `vue-particles`(旧包)等替代库。
140
+ 2. **配置集中**在 `src/utils/particles.ts`,页面只引用 `particlesOptions`。
141
+ 3. **v4 API**:禁止写 `particles.color`;必须用 `paint.fill` / `paint.stroke`。
142
+ 4. 低端设备或弱网场景可降低 `number.value` / `fpsLimit`,勿为特效再引入第二套动画库。
143
+ 5. 中后台业务列表页默认不加粒子;仅登录、营销壳、氛围背景等场景使用。
@@ -221,7 +221,7 @@
221
221
  | **useApp** | 全局应用状态(布局、字典、登出等) | `baseCodeGet(codeType)`、`filterValue()`、`headerHeight`、`svgLoading` |
222
222
  | **useDetail** | 详情页跳转与参数传递 | `toDetail({ id, mark, text })`、`initToDetail()`、`getParameter()` |
223
223
  | **useFormCheck** | 多表单校验聚合 | 追踪各字段校验状态,emit `form-validate` 事件 |
224
- | **useTableSearch** | 列表页搜索/分页/Loading | `content`、`tableData`、`pagination`、`loading`、`onSearch()`、`reset()`、`formRef` |
224
+ | **useTableSearch** | 列表页搜索/分页/Loading/动态表高 | `content`(须绑 `el-card`)、`tableHeight`、`paginationRef`、`tableData`、`pagination`、`loading`、`onSearch()`、`reset()`、`formRef`、`gridRef`、`collapsed` / `showCollapse` / `collapsedToggle` |
225
225
  | **useTags** | 多标签页管理 | 右键菜单(刷新/关闭/关闭其他/全屏) |
226
226
  | **useTheme** | 主题切换(暗色/亮色) | 主题色生成、布局预设、缓存重置 |
227
227
  | **useWebSocket** | WebSocket 通信 | `subscribe()`、`send()`、`sendRaw()`、`isConnected` |
@@ -245,7 +245,7 @@
245
245
  | `message.ts` | `message()` | 消息提示封装(ElMessage 包装) |
246
246
  | `mitt.ts` | `mitt()` | 全局事件总线 |
247
247
  | `motion.tsx` | `MotionComponent` | 入场动画 TSX 组件 |
248
- | `particles.ts` | `particlesConfig` | 粒子动画配置 |
248
+ | `particles.ts` | `particlesOptions` | 粒子动画配置(tsParticles v4 paint) |
249
249
  | `progress/` | `NProgress` | 路由进度条配置 |
250
250
  | `responsive.ts` | `ResponsiveStorage` | 响应式存储注入 |
251
251
  | `router.ts` | `hasAuth()`、路由扁平化、权限过滤 | 路由工具 |
@@ -308,7 +308,7 @@
308
308
  | 文件预览 | `@file-viewer/*` + `ReFileViewer` | 禁止引入 pdf.js / mammoth 等 |
309
309
  | 滑块验证 | `vue3-puzzle-vcode` + `ReVerify` | 禁止引入其他验证码库 |
310
310
  | 打字机动画 | `typeit` + `ReTypeit` | 禁止引入其他打字机库 |
311
- | 粒子动画 | `@tsparticles/slim` | 禁止引入 particles.js |
311
+ | 粒子动画 | `@tsparticles/slim` + `@tsparticles/vue3` + `particlesOptions` | 禁止引入 particles.js 等;配置见 `particles` 模块 |
312
312
  | 日期处理 | `dayjs` | 禁止引入 moment / date-fns 等 |
313
313
  | HTTP 请求 | 项目封装的 `src/utils/http.ts`(基于 axios) | 禁止直接使用 axios / fetch / ky 等 |
314
314
  | 状态管理 | `pinia` + `pinia-plugin-persistedstate` | 禁止引入 vuex / zustand 等 |
@@ -362,6 +362,7 @@
362
362
  | **verify-captcha** | `vue3-puzzle-vcode@1.1.7` | — | — | `pnpm add vue3-puzzle-vcode@1.1.7` |
363
363
  | **qrcode-barcode** | `qrcode@1.5.4` `jsbarcode@3.12.3` | `@types/qrcode@1.5.6` | — | `pnpm add qrcode@1.5.4 jsbarcode@3.12.3` + `pnpm add -D @types/qrcode@1.5.6` |
364
364
  | **feedback-ui**(打字机) | `typeit@8.8.7` | — | — | `pnpm add typeit@8.8.7` |
365
+ | **particles** | `@tsparticles/slim@4.3.1` `@tsparticles/vue3@4.3.1` | — | `main.ts` 中 `Particles` + `loadSlim`;可选 `build/chunks.ts` 的 `tsparticles-vendor` | `pnpm add @tsparticles/slim@4.3.1 @tsparticles/vue3@4.3.1` |
365
366
  | **api-check** / Mock | —(运行时无额外包) | `vite-plugin-fake-server@2.2.4`(core 通常已有) | `build/plugins/ViteMockServe.ts`;`VITE_USE_MOCK` | 缺失时:`pnpm add -D vite-plugin-fake-server@2.2.4` |
366
367
  | **websocket-realtime** / **data-compare** / **seamless-scroll** / **directives-advanced** / **download-export** / **tree-table** | 无额外 npm 包(用项目封装) | — | 见各模块 | 无需安装 |
367
368