@agile-team/wl-skills-kit 1.0.0

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.
Files changed (112) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +328 -0
  3. package/bin/wl-skills.js +104 -0
  4. package/files/.github/copilot-instructions.md +211 -0
  5. package/files/.github/docs/SYS_MENU_INFO.md +247 -0
  6. package/files/.github/docs/menu-sync-design.md +265 -0
  7. package/files/.github/docs/use-skill.md +379 -0
  8. package/files/.github/docs/wl-skills-kit.md +266 -0
  9. package/files/.github/skills/api-contract/SKILL.md +247 -0
  10. package/files/.github/skills/convention-extract/SKILL.md +355 -0
  11. package/files/.github/skills/menu-sync/SKILL.md +255 -0
  12. package/files/.github/skills/menu-sync/env/guide.md +73 -0
  13. package/files/.github/skills/page-codegen/SKILL.md +825 -0
  14. package/files/.github/skills/page-codegen/TPL-CHANGE-HISTORY.md +281 -0
  15. package/files/.github/skills/page-codegen/TPL-DETAIL-TABS.md +1112 -0
  16. package/files/.github/skills/page-codegen/TPL-DRIVEN.md +124 -0
  17. package/files/.github/skills/page-codegen/TPL-FORM-ROUTE.md +441 -0
  18. package/files/.github/skills/page-codegen/TPL-LIST.md +196 -0
  19. package/files/.github/skills/page-codegen/TPL-MASTER-DETAIL.md +153 -0
  20. package/files/.github/skills/page-codegen/TPL-OPERATION-STATION.md +442 -0
  21. package/files/.github/skills/page-codegen/TPL-RECORD-FORM.md +376 -0
  22. package/files/.github/skills/page-codegen/TPL-TREE-LIST.md +191 -0
  23. package/files/.github/skills/prototype-scan/SKILL.md +414 -0
  24. package/files/demo/README.md +44 -0
  25. package/files/demo/produce/aiflow/mmwr-customer-apply-add/api.md +54 -0
  26. package/files/demo/produce/aiflow/mmwr-customer-apply-add/data.ts +346 -0
  27. package/files/demo/produce/aiflow/mmwr-customer-apply-add/index.scss +1 -0
  28. package/files/demo/produce/aiflow/mmwr-customer-apply-add/index.vue +28 -0
  29. package/files/demo/produce/aiflow/mmwr-customer-apply-add-form/data.ts +115 -0
  30. package/files/demo/produce/aiflow/mmwr-customer-apply-add-form/index.scss +44 -0
  31. package/files/demo/produce/aiflow/mmwr-customer-apply-add-form/index.vue +43 -0
  32. package/files/demo/produce/aiflow/mmwr-customer-apply-change/data.ts +338 -0
  33. package/files/demo/produce/aiflow/mmwr-customer-apply-change/index.scss +1 -0
  34. package/files/demo/produce/aiflow/mmwr-customer-apply-change/index.vue +28 -0
  35. package/files/demo/produce/aiflow/mmwr-customer-apply-change-form/data.ts +115 -0
  36. package/files/demo/produce/aiflow/mmwr-customer-apply-change-form/index.scss +44 -0
  37. package/files/demo/produce/aiflow/mmwr-customer-apply-change-form/index.vue +43 -0
  38. package/files/demo/produce/aiflow/mmwr-customer-apply-change-history/data.ts +196 -0
  39. package/files/demo/produce/aiflow/mmwr-customer-apply-change-history/index.scss +150 -0
  40. package/files/demo/produce/aiflow/mmwr-customer-apply-change-history/index.vue +79 -0
  41. package/files/demo/produce/aiflow/mmwr-customer-archive/api.md +88 -0
  42. package/files/demo/produce/aiflow/mmwr-customer-archive/data.ts +601 -0
  43. package/files/demo/produce/aiflow/mmwr-customer-archive/index.scss +1 -0
  44. package/files/demo/produce/aiflow/mmwr-customer-archive/index.vue +64 -0
  45. package/files/demo/produce/aiflow/mmwr-customer-detail/api.md +67 -0
  46. package/files/demo/produce/aiflow/mmwr-customer-detail/data.ts +286 -0
  47. package/files/demo/produce/aiflow/mmwr-customer-detail/index.scss +139 -0
  48. package/files/demo/produce/aiflow/mmwr-customer-detail/index.vue +318 -0
  49. package/files/demo/produce/aiflow/mmwr-temp-customer-archive/api.md +98 -0
  50. package/files/demo/produce/aiflow/mmwr-temp-customer-archive/data.ts +543 -0
  51. package/files/demo/produce/aiflow/mmwr-temp-customer-archive/index.scss +1 -0
  52. package/files/demo/produce/aiflow/mmwr-temp-customer-archive/index.vue +52 -0
  53. package/files/demo/sale/demo/add-demo/data.ts +518 -0
  54. package/files/demo/sale/demo/add-demo/index.scss +207 -0
  55. package/files/demo/sale/demo/add-demo/index.vue +167 -0
  56. package/files/demo/sale/demo/billet-flame-cut-plan/data.ts +524 -0
  57. package/files/demo/sale/demo/billet-flame-cut-plan/index.scss +155 -0
  58. package/files/demo/sale/demo/billet-flame-cut-plan/index.vue +117 -0
  59. package/files/demo/sale/demo/domestic-trade-order/data.ts +308 -0
  60. package/files/demo/sale/demo/domestic-trade-order/index.scss +99 -0
  61. package/files/demo/sale/demo/domestic-trade-order/index.vue +77 -0
  62. package/files/demo/sale/demo/heat-batch-return/data.ts +367 -0
  63. package/files/demo/sale/demo/heat-batch-return/index.scss +100 -0
  64. package/files/demo/sale/demo/heat-batch-return/index.vue +170 -0
  65. package/files/demo/sale/demo/heat-batch-return/meltDialog.vue +320 -0
  66. package/files/demo/sale/demo/metallurgical-spec/data.ts +825 -0
  67. package/files/demo/sale/demo/metallurgical-spec/index.scss +264 -0
  68. package/files/demo/sale/demo/metallurgical-spec/index.vue +309 -0
  69. package/files/docs/jh-date-range.md +257 -0
  70. package/files/docs/jh-date.md +222 -0
  71. package/files/docs/jh-dept-picker.md +190 -0
  72. package/files/docs/jh-drag-row.md +590 -0
  73. package/files/docs/jh-file-upload.md +216 -0
  74. package/files/docs/jh-pagination.md +505 -0
  75. package/files/docs/jh-picker.md +218 -0
  76. package/files/docs/jh-select.md +148 -0
  77. package/files/docs/jh-text.md +248 -0
  78. package/files/docs/jh-user-picker.md +197 -0
  79. package/files/docs/page-query-hook-best-practices.md +362 -0
  80. package/files/docs/request.md +925 -0
  81. package/files/src/components/global/C_ParentView/index.vue +3 -0
  82. package/files/src/components/global/C_RightToolbar/index.vue +459 -0
  83. package/files/src/components/global/C_Splitter/index.vue +195 -0
  84. package/files/src/components/global/C_SvgIcon/index.vue +61 -0
  85. package/files/src/components/global/C_SvgIcon/svgicon.js +10 -0
  86. package/files/src/components/global/C_TagStatus/README.md +264 -0
  87. package/files/src/components/global/C_TagStatus/config.ts +192 -0
  88. package/files/src/components/global/C_TagStatus/index.vue +127 -0
  89. package/files/src/components/global/C_TagStatus/types.ts +64 -0
  90. package/files/src/components/global/C_Tree/README.md +153 -0
  91. package/files/src/components/global/C_Tree/index.scss +42 -0
  92. package/files/src/components/global/C_Tree/index.vue +119 -0
  93. package/files/src/components/global/C_Tree/types.ts +59 -0
  94. package/files/src/components/local/c_formModal/README.md +235 -0
  95. package/files/src/components/local/c_formModal/data.ts +95 -0
  96. package/files/src/components/local/c_formModal/index.scss +8 -0
  97. package/files/src/components/local/c_formModal/index.vue +107 -0
  98. package/files/src/components/local/c_formSections/README.md +496 -0
  99. package/files/src/components/local/c_formSections/data.ts +175 -0
  100. package/files/src/components/local/c_formSections/index.scss +280 -0
  101. package/files/src/components/local/c_formSections/index.vue +429 -0
  102. package/files/src/components/local/c_listModal/data.ts +41 -0
  103. package/files/src/components/local/c_listModal/index.vue +136 -0
  104. package/files/src/components/local/c_spliterTitle/index.scss +25 -0
  105. package/files/src/components/local/c_spliterTitle/index.vue +21 -0
  106. package/files/src/components/remote/AGGrid/README.md +530 -0
  107. package/files/src/components/remote/BaseForm/README.md +508 -0
  108. package/files/src/components/remote/BaseQuery/README.md +865 -0
  109. package/files/src/components/remote/BaseTable/README.md +941 -0
  110. package/files/src/components/remote/BaseToolbar/README.md +496 -0
  111. package/files/src/types/page.ts +24 -0
  112. package/package.json +31 -0
@@ -0,0 +1,216 @@
1
+ # jh-file-upload - 文件上传组件
2
+
3
+ > 平台统一的文件上传组件,已集成平台文件系统,支持上传、回显、下载等能力,只需提供业务关联信息即可使用
4
+
5
+ ## 📦 组件位置
6
+
7
+ ```ts
8
+ import "@jhlc/common-core";
9
+ ```
10
+
11
+ 组件已全局注册,可直接在模板中使用 `<jh-file-upload />`。
12
+
13
+ ---
14
+
15
+ ## 基本用法
16
+
17
+ ### 1️⃣ 关联业务上传(最常用)
18
+
19
+ ```vue
20
+ <template>
21
+ <jh-file-upload relative-type="order" :relative-id="form.id" />
22
+ </template>
23
+
24
+ <script setup lang="ts">
25
+ import { ref } from "vue";
26
+
27
+ const form = ref({
28
+ id: "order_001"
29
+ });
30
+ </script>
31
+ ```
32
+
33
+ > 上传的文件将自动关联到对应业务数据,无需手动处理上传接口
34
+
35
+ ---
36
+
37
+ ### 2️⃣ 表单中使用(新增 / 编辑)
38
+
39
+ ```vue
40
+ <jh-file-upload
41
+ relative-type="contract"
42
+ :relative-id="form.id"
43
+ v-model="form.files"
44
+ />
45
+ ```
46
+
47
+ ---
48
+
49
+ ## Props 属性
50
+
51
+ | 参数 | 说明 | 类型 | 默认值 |
52
+ | -------------------- | ------------------ | ------------------------------------------ | ----------- |
53
+ | modelValue / v-model | 文件列表 | `FileItem[] \| string` | `[]` |
54
+ | relativeType | 业务类型标识 | `string` | - |
55
+ | relativeId | 业务主键ID | `string \| number` | - |
56
+ | limit | 最大上传数量 | `number` | `10` |
57
+ | disabled | 是否禁用 | `boolean` | `false` |
58
+ | readonly | 是否只读 | `boolean` | `false` |
59
+ | listType | 文件列表类型 | `"picture" \| "picture-card" \| "no-list"` | `"picture"` |
60
+ | accept | 接受的文件类型 | `string` | - |
61
+ | fileSizeLimit | 文件大小限制 | `string` | - |
62
+ | drag | 是否支持拖拽上传 | `boolean` | `true` |
63
+ | multiple | 是否支持多选 | `boolean` | `true` |
64
+ | autoUpload | 是否自动上传 | `boolean` | `false` |
65
+ | addable | 是否允许添加 | `boolean` | `true` |
66
+ | deletable | 是否允许删除 | `boolean` | `true` |
67
+ | downloadable | 是否允许下载 | `boolean` | `true` |
68
+ | uploadUrl | 自定义上传地址 | `string` | - |
69
+ | listUrl | 自定义列表查询地址 | `string` | - |
70
+ | removeUrl | 自定义删除地址 | `string` | - |
71
+
72
+ > **提示**: 默认使用平台文件系统,无需配置 `uploadUrl` 等参数。
73
+
74
+ ---
75
+
76
+ ## FileItem 数据结构
77
+
78
+ ```ts
79
+ interface FileItem {
80
+ id: string;
81
+ name: string;
82
+ url: string;
83
+ }
84
+ ```
85
+
86
+ ---
87
+
88
+ ## Events 事件
89
+
90
+ | 事件名 | 说明 | 回调参数 |
91
+ | ----------------- | ---------------- | --------------------------------------- |
92
+ | update:modelValue | v-model 更新 | `(value: FileItem[] \| string) => void` |
93
+ | success | 文件上传成功 | `() => void` |
94
+ | failed | 文件上传失败 | `() => void` |
95
+ | remove | 文件删除 | `() => void` |
96
+ | exceed | 超出文件数量限制 | `() => void` |
97
+
98
+ ---
99
+
100
+ ## 常见场景
101
+
102
+ ### 场景 1:订单附件上传
103
+
104
+ ```vue
105
+ <jh-file-upload relative-type="order" :relative-id="orderId" />
106
+ ```
107
+
108
+ ---
109
+
110
+ ### 场景 2:编辑页文件回显
111
+
112
+ ```vue
113
+ <jh-file-upload
114
+ relative-type="order"
115
+ :relative-id="form.id"
116
+ v-model="form.files"
117
+ />
118
+ ```
119
+
120
+ ---
121
+
122
+ ### 场景 3:详情页只读展示
123
+
124
+ ```vue
125
+ <jh-file-upload relative-type="order" :relative-id="detail.id" readonly />
126
+ ```
127
+
128
+ ---
129
+
130
+ ## 与手动实现对比
131
+
132
+ ### 使用 jh-file-upload(推荐)
133
+
134
+ ```vue
135
+ <jh-file-upload relative-type="order" :relative-id="id" />
136
+ ```
137
+
138
+ ### 手动实现(不推荐)
139
+
140
+ ```vue
141
+ <el-upload action="/api/upload" :on-success="handleSuccess" />
142
+ ```
143
+
144
+ ❌ 需要自己实现上传接口
145
+ ❌ 需要维护文件关联关系
146
+ ❌ 回显逻辑复杂
147
+
148
+ ---
149
+
150
+ ## 最佳实践
151
+
152
+ ### 1️⃣ 始终使用 relativeType + relativeId
153
+
154
+ ```vue
155
+ <jh-file-upload relative-type="order" :relative-id="form.id" />
156
+ ```
157
+
158
+ 确保文件与业务数据正确关联
159
+
160
+ ---
161
+
162
+ ### 2️⃣ 新增页与编辑页区别
163
+
164
+ - **新增页**:业务 ID 生成后再显示组件
165
+ - **编辑页**:直接传入已有 ID,自动回显
166
+
167
+ ---
168
+
169
+ ### 3️⃣ 只读与编辑分离
170
+
171
+ ```vue
172
+ <!-- 编辑 -->
173
+ <jh-file-upload ... />
174
+
175
+ <!-- 详情 -->
176
+ <jh-file-upload ... readonly />
177
+ ```
178
+
179
+ ---
180
+
181
+ ## 注意事项
182
+
183
+ 1. **无需手动处理上传接口**
184
+ - 上传、下载、回显均由组件内部完成
185
+
186
+ 2. **relativeId 必须真实存在**
187
+ - 否则文件无法正确关联
188
+
189
+ 3. **文件删除权限**
190
+ - 受平台权限控制
191
+
192
+ ---
193
+
194
+ ## 🎯 真实项目示例
195
+
196
+ ### 示例 1:合同附件
197
+
198
+ ```vue
199
+ <jh-file-upload relative-type="contract" :relative-id="form.id" />
200
+ ```
201
+
202
+ ### 示例 2:详情页附件
203
+
204
+ ```vue
205
+ <jh-file-upload relative-type="contract" :relative-id="detail.id" readonly />
206
+ ```
207
+
208
+ ---
209
+
210
+ ## 🚀 快速开始
211
+
212
+ 1. 传入 `relativeType`
213
+ 2. 传入业务主键 `relativeId`
214
+ 3. 其他逻辑无需关心
215
+
216
+ **推荐作为平台统一的文件上传组件使用!**
@@ -0,0 +1,505 @@
1
+ # jh-pagination - 分页组件
2
+
3
+ > 基于 Element Plus Pagination 封装的统一分页组件,提供标准化的分页交互和样式
4
+
5
+ ## 📦 组件位置
6
+
7
+ ```typescript
8
+ // 来自远程 common-core 包
9
+ import "@jhlc/common-core";
10
+ ```
11
+
12
+ 组件已在全局注册,无需手动导入,直接在模板中使用 `<jh-pagination>`。
13
+
14
+ ## 基本用法
15
+
16
+ ### 标准分页
17
+
18
+ ```vue
19
+ <template>
20
+ <div>
21
+ <!-- 表格数据 -->
22
+ <BaseTable :data="list" :columns="columns" />
23
+
24
+ <!-- 分页组件 -->
25
+ <jh-pagination
26
+ v-show="page.total && page.total > 0"
27
+ :total="page.total || 0"
28
+ v-model:currentPage="page.current"
29
+ v-model:pageSize="page.size"
30
+ @current-change="handlePageChange"
31
+ @size-change="handleSizeChange"
32
+ />
33
+ </div>
34
+ </template>
35
+
36
+ <script setup lang="ts">
37
+ import { ref } from "vue";
38
+
39
+ const page = ref({
40
+ current: 1,
41
+ size: 10,
42
+ total: 0
43
+ });
44
+
45
+ const list = ref([]);
46
+
47
+ // 加载数据
48
+ const loadData = async () => {
49
+ const res = await request({
50
+ url: "/api/list",
51
+ method: "get",
52
+ params: {
53
+ page: page.value.current,
54
+ size: page.value.size
55
+ }
56
+ });
57
+
58
+ list.value = res.data.records;
59
+ page.value.total = res.data.total;
60
+ };
61
+
62
+ // 页码变化
63
+ const handlePageChange = () => {
64
+ loadData();
65
+ };
66
+
67
+ // 页大小变化
68
+ const handleSizeChange = () => {
69
+ page.value.current = 1; // 重置到第一页
70
+ loadData();
71
+ };
72
+
73
+ // 初始化
74
+ onMounted(() => {
75
+ loadData();
76
+ });
77
+ </script>
78
+ ```
79
+
80
+ ## Props 属性
81
+
82
+ | 参数 | 说明 | 类型 | 默认值 |
83
+ | --------------------- | ------------------------ | ---------- | ------------------------------------------- |
84
+ | total | 总条目数 | `number` | `0` |
85
+ | currentPage (v-model) | 当前页码 | `number` | `1` |
86
+ | pageSize (v-model) | 每页显示条数 | `number` | `10` |
87
+ | pageSizes | 每页显示个数选择器的选项 | `number[]` | `[10, 20, 50, 100]` |
88
+ | layout | 组件布局 | `string` | `"total, prev, pager, next, sizes, jumper"` |
89
+ | background | 是否为分页按钮添加背景色 | `boolean` | `true` |
90
+ | disabled | 是否禁用分页 | `boolean` | `false` |
91
+
92
+ ## Events 事件
93
+
94
+ | 事件名 | 说明 | 回调参数 |
95
+ | ------------------ | --------------------- | ------------------------ |
96
+ | current-change | 页码改变时触发 | `(page: number) => void` |
97
+ | size-change | 页大小改变时触发 | `(size: number) => void` |
98
+ | update:currentPage | 页码变化(v-model) | `(page: number) => void` |
99
+ | update:pageSize | 页大小变化(v-model) | `(size: number) => void` |
100
+
101
+ ## 常见场景
102
+
103
+ ### 场景 1:列表页分页(推荐)
104
+
105
+ **适用**: 绝大多数列表页面
106
+
107
+ ```vue
108
+ <template>
109
+ <div class="app-container app-page-container">
110
+ <BaseQuery :form="queryParam" :items="queryItems" @select="select" />
111
+ <BaseToolbar :items="toolbars" />
112
+ <BaseTable :data="list" :columns="columns" />
113
+
114
+ <!-- 分页组件 -->
115
+ <jh-pagination
116
+ v-show="page.total && page.total > 0"
117
+ :total="page.total || 0"
118
+ v-model:currentPage="page.current"
119
+ v-model:pageSize="page.size"
120
+ @current-change="select"
121
+ @size-change="select"
122
+ />
123
+ </div>
124
+ </template>
125
+
126
+ <script setup lang="ts">
127
+ import { createPage } from "./data";
128
+
129
+ const Page = createPage();
130
+ const { page, list, queryParam, queryItems, toolbars, columns, select } = Page;
131
+
132
+ onMounted(() => {
133
+ select();
134
+ });
135
+ </script>
136
+ ```
137
+
138
+ **说明**:
139
+
140
+ - ✅ 使用 `v-show` 在无数据时隐藏分页
141
+ - ✅ 使用 `v-model` 双向绑定页码和页大小
142
+ - ✅ 页码/页大小变化时统一调用 `select()` 刷新数据
143
+
144
+ ---
145
+
146
+ ### 场景 2:前端分页(本地数据)
147
+
148
+ **适用**: 数据量小,一次性加载全部数据后在前端分页
149
+
150
+ ```vue
151
+ <template>
152
+ <div>
153
+ <!-- 显示分页后的数据 -->
154
+ <BaseTable :data="paginatedData" :columns="columns" />
155
+
156
+ <!-- 分页组件 -->
157
+ <jh-pagination
158
+ v-show="allData.length > 0"
159
+ :total="allData.length"
160
+ v-model:currentPage="currentPage"
161
+ v-model:pageSize="pageSize"
162
+ />
163
+ </div>
164
+ </template>
165
+
166
+ <script setup lang="ts">
167
+ import { ref, computed } from "vue";
168
+
169
+ const allData = ref([]); // 全部数据
170
+ const currentPage = ref(1);
171
+ const pageSize = ref(10);
172
+
173
+ // 计算属性:分页后的数据
174
+ const paginatedData = computed(() => {
175
+ const start = (currentPage.value - 1) * pageSize.value;
176
+ const end = start + pageSize.value;
177
+ return allData.value.slice(start, end);
178
+ });
179
+
180
+ // 加载全部数据
181
+ const loadAllData = async () => {
182
+ const res = await request({
183
+ url: "/api/all-data",
184
+ method: "get"
185
+ });
186
+ allData.value = res.data;
187
+ };
188
+
189
+ onMounted(() => {
190
+ loadAllData();
191
+ });
192
+ </script>
193
+ ```
194
+
195
+ **说明**:
196
+
197
+ - ✅ `total` 使用全部数据的长度
198
+ - ✅ 使用 `computed` 计算当前页的数据
199
+ - ✅ 不需要监听事件(响应式自动处理)
200
+
201
+ ---
202
+
203
+ ### 场景 3:子表格分页
204
+
205
+ **适用**: 新增编辑页中的项次信息表格
206
+
207
+ ```vue
208
+ <template>
209
+ <div>
210
+ <!-- 主表单区域 -->
211
+ <c_formSections :sections="sectionsConfig" :form="form" />
212
+
213
+ <!-- 项次信息表格 -->
214
+ <el-card shadow="never" class="items-card">
215
+ <BaseTable :data="itemData" :columns="itemColumns" />
216
+
217
+ <!-- 项次分页 -->
218
+ <div class="items-pagination">
219
+ <jh-pagination
220
+ v-show="itemTotal && itemTotal > 0"
221
+ :total="itemTotal || 0"
222
+ v-model:currentPage="itemPage"
223
+ v-model:pageSize="itemSize"
224
+ @current-change="loadItemData"
225
+ @size-change="loadItemData"
226
+ />
227
+ </div>
228
+ </el-card>
229
+ </div>
230
+ </template>
231
+
232
+ <script setup lang="ts">
233
+ const form = ref({});
234
+ const itemData = ref([]);
235
+ const itemTotal = ref(0);
236
+ const itemPage = ref(1);
237
+ const itemSize = ref(10);
238
+
239
+ // 加载项次数据
240
+ const loadItemData = async () => {
241
+ const res = await request({
242
+ url: "/api/items/list",
243
+ method: "get",
244
+ params: {
245
+ page: itemPage.value,
246
+ size: itemSize.value
247
+ }
248
+ });
249
+ itemData.value = res.data.records;
250
+ itemTotal.value = res.data.total;
251
+ };
252
+
253
+ onMounted(() => {
254
+ loadItemData();
255
+ });
256
+ </script>
257
+ ```
258
+
259
+ **说明**:
260
+
261
+ - ✅ 子表格使用独立的分页状态
262
+ - ✅ 与主表单数据解耦
263
+ - ✅ 支持独立刷新
264
+
265
+ ---
266
+
267
+ ### 场景 4:无数据时自动隐藏
268
+
269
+ **推荐写法**:
270
+
271
+ ```vue
272
+ <!-- ✅ 推荐:无数据时不显示 -->
273
+ <jh-pagination
274
+ v-show="page.total && page.total > 0"
275
+ :total="page.total || 0"
276
+ v-model:currentPage="page.current"
277
+ v-model:pageSize="page.size"
278
+ />
279
+
280
+ <!-- ❌ 不推荐:始终显示(体验差) -->
281
+ <jh-pagination
282
+ :total="page.total"
283
+ v-model:currentPage="page.current"
284
+ v-model:pageSize="page.size"
285
+ />
286
+ ```
287
+
288
+ **说明**:
289
+
290
+ - ✅ 使用 `v-show` 在 `total > 0` 时才显示
291
+ - ✅ 提升用户体验,避免空状态下显示分页
292
+
293
+ ---
294
+
295
+ ### 场景 5:自定义页大小选项
296
+
297
+ ```vue
298
+ <jh-pagination
299
+ :total="page.total"
300
+ :page-sizes="[5, 10, 20, 50, 200]"
301
+ v-model:currentPage="page.current"
302
+ v-model:pageSize="page.size"
303
+ />
304
+ ```
305
+
306
+ **说明**:
307
+
308
+ - 根据业务需求自定义每页条数选项
309
+ - 默认为 `[10, 20, 50, 100]`
310
+
311
+ ---
312
+
313
+ ## 与 el-pagination 对比
314
+
315
+ ### 使用 jh-pagination(推荐)
316
+
317
+ ```vue
318
+ <jh-pagination
319
+ v-show="page.total && page.total > 0"
320
+ :total="page.total || 0"
321
+ v-model:currentPage="page.current"
322
+ v-model:pageSize="page.size"
323
+ @current-change="select"
324
+ @size-change="select"
325
+ />
326
+ ```
327
+
328
+ **优势**:
329
+
330
+ - ✅ **统一规范** - 全局统一的分页样式和交互
331
+ - ✅ **简化配置** - 无需配置 layout、background 等(内置默认值)
332
+ - ✅ **驼峰命名** - 使用 `currentPage`、`pageSize`(符合 Vue 规范)
333
+ - ✅ **自动样式** - 内置统一的边距、居中、背景色
334
+
335
+ ### 使用 el-pagination(不推荐)
336
+
337
+ ```vue
338
+ <el-pagination
339
+ v-model:current-page="page.current"
340
+ v-model:page-size="page.size"
341
+ :page-sizes="[10, 20, 50, 100]"
342
+ :total="page.total"
343
+ layout="total, prev, pager, next, sizes, jumper"
344
+ background
345
+ @current-change="select"
346
+ @size-change="select"
347
+ />
348
+ ```
349
+
350
+ **劣势**:
351
+
352
+ - ❌ **配置繁琐** - 需要手动配置 layout、page-sizes、background
353
+ - ❌ **样式不统一** - 每个页面可能配置不同
354
+ - ❌ **命名不一致** - 使用 `current-page`(中划线命名)
355
+
356
+ ---
357
+
358
+ ## 💡 最佳实践
359
+
360
+ ### 1. 统一使用 jh-pagination
361
+
362
+ **推荐**:
363
+
364
+ ```vue
365
+ <jh-pagination ... />
366
+ ```
367
+
368
+ **不推荐**:
369
+
370
+ ```vue
371
+ <el-pagination ... />
372
+ ```
373
+
374
+ ### 2. 始终添加 v-show
375
+
376
+ ```vue
377
+ <!-- ✅ 推荐 -->
378
+ <jh-pagination
379
+ v-show="page.total && page.total > 0"
380
+ :total="page.total || 0"
381
+ ...
382
+ />
383
+ ```
384
+
385
+ ### 3. 使用 v-model 双向绑定
386
+
387
+ ```vue
388
+ <!-- ✅ 推荐:v-model 双向绑定 -->
389
+ <jh-pagination
390
+ v-model:currentPage="page.current"
391
+ v-model:pageSize="page.size"
392
+ />
393
+
394
+ <!-- ❌ 不推荐:单向绑定 + 事件手动更新 -->
395
+ <jh-pagination
396
+ :current-page="page.current"
397
+ :page-size="page.size"
398
+ @update:currentPage="page.current = $event"
399
+ @update:pageSize="page.size = $event"
400
+ />
401
+ ```
402
+
403
+ ### 4. 页大小变化时重置页码
404
+
405
+ ```typescript
406
+ const handleSizeChange = () => {
407
+ page.value.current = 1; // 重置到第一页
408
+ loadData();
409
+ };
410
+ ```
411
+
412
+ ### 5. 配合 Page Hook 使用
413
+
414
+ ```typescript
415
+ // data.ts
416
+ export function createPage() {
417
+ return new (class extends AbstractPageQueryHook {
418
+ // ... Hook 内置分页逻辑
419
+ })();
420
+ }
421
+
422
+ // index.vue
423
+ const Page = createPage();
424
+ const { page, select } = Page;
425
+ ```
426
+
427
+ **说明**:
428
+
429
+ - ✅ Page Hook 内置分页状态管理
430
+ - ✅ `select()` 方法自动读取 page 状态
431
+ - ✅ 减少手动管理分页的代码
432
+
433
+ ---
434
+
435
+ ## 🎯 真实项目示例
436
+
437
+ ### 示例 1:内贸订单列表页
438
+
439
+ **路径**: `src/views/sale/demo/domestic-trade-order/index.vue`
440
+
441
+ ```vue
442
+ <jh-pagination
443
+ v-show="page.total && page.total > 0"
444
+ :total="page.total || 0"
445
+ v-model:currentPage="page.current"
446
+ v-model:pageSize="page.size"
447
+ @current-change="select"
448
+ @size-change="select"
449
+ />
450
+ ```
451
+
452
+ ### 示例 2:新增编辑页项次分页
453
+
454
+ **路径**: `src/views/sale/demo/add-demo/index.vue`
455
+
456
+ ```vue
457
+ <jh-pagination
458
+ v-show="itemTotal && itemTotal > 0"
459
+ :total="itemTotal || 0"
460
+ v-model:currentPage="currentPage"
461
+ v-model:pageSize="pageSize"
462
+ @current-change="refreshItemData"
463
+ @size-change="refreshItemData"
464
+ />
465
+ ```
466
+
467
+ ---
468
+
469
+ ## ⚠️ 注意事项
470
+
471
+ 1. **组件来自远程包**
472
+
473
+ - 组件由 `@jhlc/common-core` 提供
474
+ - 已全局注册,无需手动导入
475
+ - 升级远程包时注意版本兼容性
476
+
477
+ 2. **页码从 1 开始**
478
+
479
+ - `currentPage` 最小值为 1
480
+ - 后端接口通常也是从 1 开始
481
+
482
+ 3. **total 必须准确**
483
+
484
+ - `total` 应该是数据总数,不是当前页数据量
485
+ - 后端通常返回 `{ records: [], total: 100 }`
486
+
487
+ 4. **事件处理**
488
+
489
+ - `current-change` 在页码变化时触发
490
+ - `size-change` 在页大小变化时触发
491
+ - 两个事件通常调用同一个刷新方法
492
+
493
+ 5. **样式定制**
494
+ - 如需特殊样式,可通过 CSS 覆盖
495
+ - 不要修改远程组件源码
496
+
497
+ ---
498
+
499
+ ## 🚀 快速开始
500
+
501
+ 1. **在列表页使用**:复制"场景 1"代码
502
+ 2. **在详情页使用**:复制"场景 3"代码
503
+ 3. **前端分页**:复制"场景 2"代码
504
+
505
+ **推荐作为项目统一的分页组件使用!**