@me-framework/me-sku-editor 1.0.1

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.
@@ -0,0 +1,637 @@
1
+ # 开发指南
2
+
3
+ 本指南面向需要对 SkuEditor 进行二次开发或修改的开发者。
4
+
5
+ ## 目录
6
+
7
+ - [项目结构](#项目结构)
8
+ - [核心模块说明](#核心模块说明)
9
+ - [扩展开发](#扩展开发)
10
+ - [样式定制](#样式定制)
11
+ - [常见修改场景](#常见修改场景)
12
+
13
+ ---
14
+
15
+ ## 项目结构
16
+
17
+ ```
18
+ src/
19
+ ├── components/ # 组件目录
20
+ │ └── sku/ # SKU 相关组件
21
+ │ ├── SkuEditor.vue # 主编辑器组件
22
+ │ ├── SpecSection.vue # 规格管理区域
23
+ │ ├── SkuGrid.vue # SKU 表格区域
24
+ │ └── dialogs/ # 对话框组件
25
+ │ ├── SpecDialog.vue
26
+ │ ├── AttrDialog.vue
27
+ │ └── BatchEditDialog.vue
28
+ ├── composables/ # 组合式函数
29
+ │ └── sku/ # SKU 相关逻辑
30
+ │ ├── useSkuGenerator.ts # SKU 生成逻辑
31
+ │ ├── useImportExport.ts # 导入导出逻辑
32
+ │ ├── useSortable.ts # 拖拽排序逻辑
33
+ │ ├── useMergeCells.ts # 单元格合并逻辑
34
+ │ ├── useColumnVisibility.ts # 列可见性管理
35
+ │ └── useValidation.ts # 数据校验逻辑
36
+ ├── types/ # 类型定义
37
+ │ └── sku/ # SKU 相关类型
38
+ │ ├── index.ts # 类型导出入口
39
+ │ ├── spec.ts # 规格相关类型
40
+ │ └── sku-row.ts # SKU 行相关类型
41
+ ├── constants/ # 常量定义
42
+ │ └── sku.ts # SKU 相关常量
43
+ ├── App.vue # 示例应用
44
+ └── main.ts # 入口文件
45
+ ```
46
+
47
+ ---
48
+
49
+ ## 核心模块说明
50
+
51
+ ### 1. SkuEditor.vue - 主组件
52
+
53
+ **职责:**
54
+ - 协调各子组件
55
+ - 管理数据状态(外部模式为主)
56
+ - 处理规格和属性的增删改
57
+ - 暴露公共方法
58
+ - 处理内部/外部格式转换
59
+
60
+ **关键数据:**
61
+ ```typescript
62
+ // 内部统一 SKU 数据源
63
+ const internalSkuData = ref<SkuRow[]>([])
64
+
65
+ // 同步数据到父组件
66
+ const syncDataToParent = () => {
67
+ if (isUpdatingFromInside.value) return
68
+ isUpdatingFromInside.value = true
69
+ try {
70
+ const external = toExternalFormat(internalSkuData.value, specsRef.value)
71
+ emit('update:skuData', external)
72
+ } finally {
73
+ nextTick(() => {
74
+ isUpdatingFromInside.value = false
75
+ })
76
+ }
77
+ }
78
+ ```
79
+
80
+ **关键方法:**
81
+ - `saveSpec()` - 保存规格
82
+ - `deleteSpec()` - 删除规格
83
+ - `saveAttr()` - 保存属性
84
+ - `deleteAttr()` - 删除属性
85
+ - `outputData()` - 输出数据到控制台
86
+ - `validateSkuData()` - 校验 SKU 数据
87
+
88
+ **暴露的方法:**
89
+ ```typescript
90
+ defineExpose({
91
+ exportExcel,
92
+ exportJson,
93
+ confirmImport,
94
+ confirmImportExcel,
95
+ validateSkuData
96
+ })
97
+ ```
98
+
99
+ ---
100
+
101
+ ### 2. SpecSection.vue - 规格管理区域
102
+
103
+ **职责:**
104
+ - 展示规格列表
105
+ - 提供规格和属性的操作入口
106
+ - 处理拖拽排序的 DOM 引用
107
+ - 处理文件拖放
108
+
109
+ **关键 Props:**
110
+ ```typescript
111
+ interface Props {
112
+ specs: Spec[]
113
+ }
114
+ ```
115
+
116
+ **关键 Events:**
117
+ ```typescript
118
+ interface Emits {
119
+ addSpec: []
120
+ editSpec: [spec: Spec]
121
+ deleteSpec: [spec: Spec]
122
+ addAttribute: [specId: string]
123
+ editAttribute: [spec: Spec, attr: SpecAttribute]
124
+ deleteAttribute: [spec: Spec, attr: SpecAttribute]
125
+ // ... 更多事件
126
+ }
127
+ ```
128
+
129
+ ---
130
+
131
+ ### 3. SkuGrid.vue - SKU 表格区域
132
+
133
+ **职责:**
134
+ - 展示和编辑 SKU 数据
135
+ - 处理单元格合并
136
+ - 管理列可见性
137
+ - 提供筛选功能
138
+ - 支持行高亮配置
139
+
140
+ **关键技术:**
141
+ - 使用 VXE Table 组件
142
+ - 动态生成列配置
143
+ - 虚拟滚动支持
144
+
145
+ ---
146
+
147
+ ### 4. useSkuGenerator.ts - SKU 生成逻辑
148
+
149
+ **核心函数:**
150
+
151
+ #### generateSkuData(specs, existingData?)
152
+
153
+ 生成 SKU 组合的核心算法:
154
+
155
+ 1. 过滤掉没有属性的规格
156
+ 2. 计算笛卡尔积生成所有组合
157
+ 3. 保留已有数据中匹配的组合
158
+
159
+ **笛卡尔积实现:**
160
+ ```typescript
161
+ const cartesianProduct = (lists: any[][]): any[][] => {
162
+ return lists.reduce((a, b) =>
163
+ a.flatMap(x => b.map(y => [...x, y])),
164
+ [[]]
165
+ )
166
+ }
167
+ ```
168
+
169
+ **数据匹配逻辑:**
170
+ ```typescript
171
+ // 使用规格ID和属性ID组合作为key
172
+ const generateSkuKey = (specsMap: { [key: string]: string }): string => {
173
+ const sortedSpecIds = Object.keys(specsMap).sort()
174
+ return sortedSpecIds.map(specId => `${specId}:${specsMap[specId]}`).join('|')
175
+ }
176
+ ```
177
+
178
+ #### toInternalFormat(externalSkus, specs)
179
+
180
+ 将外部格式(使用规格名称和属性名称)转换为内部格式(使用 ID)。
181
+
182
+ #### toExternalFormat(internalSkus, specs)
183
+
184
+ 将内部格式转换为外部格式。
185
+
186
+ ---
187
+
188
+ ### 5. useImportExport.ts - 导入导出逻辑
189
+
190
+ **Excel 导出格式:**
191
+ - Sheet 1:规格信息(规格序号、规格名称、规格类型、属性序号、属性名称、颜色、图片、内容)
192
+ - Sheet 2:SKU 数据(规格列 + 固定列)
193
+
194
+ **关键处理:**
195
+ - 保留字段名冲突检查(使用 `RESERVED_NAMES` 常量)
196
+ - 属性内容的 JSON 序列化/反序列化
197
+ - 兼容仅含 SKU 数据的 Excel 导入
198
+
199
+ ---
200
+
201
+ ### 6. useSortable.ts - 拖拽排序逻辑
202
+
203
+ 基于 Sortable.js 实现:
204
+
205
+ ```typescript
206
+ const initSpecSortable = () => {
207
+ specSortable = new Sortable(el, {
208
+ animation: 150,
209
+ handle: '.drag-handle',
210
+ onEnd: (evt) => { /* 处理排序 */ }
211
+ })
212
+ }
213
+ ```
214
+
215
+ **注意**:拖拽排序后会自动更新 `sort` 字段。
216
+
217
+ ---
218
+
219
+ ### 7. useValidation.ts - 数据校验逻辑
220
+
221
+ **校验规则配置:**
222
+ ```typescript
223
+ interface ValidationRules {
224
+ specs?: boolean // 校验规格
225
+ skuNoDuplication?: boolean // 校验 SKU 编号重复
226
+ requiredFields?: boolean // 校验必填字段
227
+ numericValues?: boolean // 校验数值合理性
228
+ custom?: (specs: any[], skuData: any[]) => ValidationItem[]
229
+ }
230
+ ```
231
+
232
+ **校验结果:**
233
+ ```typescript
234
+ interface ValidationResult {
235
+ valid: boolean
236
+ items: ValidationItem[] // 包含 error 和 warning 类型
237
+ }
238
+ ```
239
+
240
+ ---
241
+
242
+ ## 扩展开发
243
+
244
+ ### 添加新的 SKU 字段
245
+
246
+ #### 1. 修改类型定义
247
+
248
+ 在 `src/types/sku/sku-row.ts` 中添加字段:
249
+
250
+ ```typescript
251
+ interface SkuRow {
252
+ // ... 现有字段
253
+ customField?: string // 添加你的自定义字段
254
+ }
255
+ ```
256
+
257
+ #### 2. 更新 SkuGrid.vue
258
+
259
+ 添加列配置:
260
+
261
+ ```typescript
262
+ const skuColumns = computed(() => {
263
+ const columns: VxeGridProps['columns'] = []
264
+
265
+ // ... 规格列
266
+
267
+ // 添加自定义列
268
+ columns.push({
269
+ field: 'customField',
270
+ title: '自定义字段',
271
+ width: 150,
272
+ visible: true, // 或在 defaultColumnVisibility 中配置
273
+ slots: { default: 'customField' },
274
+ editRender: { name: 'VxeInput' } // 可编辑
275
+ })
276
+
277
+ // ... 其他列
278
+ return columns
279
+ })
280
+ ```
281
+
282
+ 添加模板插槽:
283
+
284
+ ```vue
285
+ <template #customField="{ row }">
286
+ {{ row.customField || '-' }}
287
+ </template>
288
+ ```
289
+
290
+ #### 3. 更新 useImportExport.ts
291
+
292
+ 在导出和导入逻辑中添加对新字段的处理:
293
+
294
+ ```typescript
295
+ // 导出时
296
+ rowData['自定义字段'] = row.customField
297
+
298
+ // 导入时
299
+ tempSkuData.push({
300
+ // ... 其他字段
301
+ customField: row['自定义字段']
302
+ })
303
+ ```
304
+
305
+ #### 4. 更新默认列可见性(可选)
306
+
307
+ 在 `useColumnVisibility.ts` 中:
308
+
309
+ ```typescript
310
+ export const defaultColumnVisibility: ColumnVisibilityState = {
311
+ specColumns: {},
312
+ fixedColumns: {
313
+ // ... 现有字段
314
+ customField: true // 添加新字段
315
+ }
316
+ }
317
+ ```
318
+
319
+ #### 5. 更新批量编辑对话框(可选)
320
+
321
+ 在 `BatchEditDialog.vue` 中添加批量设置选项。
322
+
323
+ #### 6. 更新数据校验(可选)
324
+
325
+ 在 `useValidation.ts` 中添加对新字段的校验。
326
+
327
+ ---
328
+
329
+ ### 添加新的规格类型
330
+
331
+ #### 1. 修改类型定义
332
+
333
+ 在 `src/types/sku/spec.ts` 中:
334
+
335
+ ```typescript
336
+ export type SpecType = 'text' | 'color' | 'image' | 'yourType'
337
+ ```
338
+
339
+ #### 2. 更新 SpecSection.vue
340
+
341
+ 在属性展示区域添加新类型的渲染:
342
+
343
+ ```vue
344
+ <div class="attr-tag-wrapper attr-drag-handle">
345
+ <!-- 已有类型 -->
346
+ <el-color-picker v-if="spec.type === 'color'" ... />
347
+ <el-image v-else-if="spec.type === 'image'" ... />
348
+
349
+ <!-- 新增类型 -->
350
+ <your-custom-component v-else-if="spec.type === 'yourType'" ... />
351
+
352
+ <el-tag ...>{{ attr.name }}</el-tag>
353
+ </div>
354
+ ```
355
+
356
+ #### 3. 更新类型标签显示
357
+
358
+ 在 `getSpecTypeLabel` 函数中:
359
+
360
+ ```typescript
361
+ const getSpecTypeLabel = (type: SpecType) => {
362
+ const map: Record<SpecType, string> = {
363
+ text: '文字',
364
+ color: '颜色',
365
+ image: '图片',
366
+ yourType: '你的类型' // 添加
367
+ }
368
+ return map[type] || type
369
+ }
370
+ ```
371
+
372
+ #### 4. 更新导入导出逻辑
373
+
374
+ 在 `useImportExport.ts` 中处理新类型的导入导出。
375
+
376
+ ---
377
+
378
+ ### 添加新的校验规则
379
+
380
+ #### 1. 在 useValidation.ts 中添加校验逻辑
381
+
382
+ ```typescript
383
+ // 在 validateSkuData 函数中添加
384
+ if (rules.yourRule !== false) {
385
+ // 添加你的校验逻辑
386
+ const errors = validateYourRule(specs, skuData)
387
+ result.items.push(...errors)
388
+ }
389
+ ```
390
+
391
+ #### 2. 更新 ValidationRules 类型
392
+
393
+ 在 `src/types/sku/index.ts` 中:
394
+
395
+ ```typescript
396
+ interface ValidationRules {
397
+ // ... 现有规则
398
+ yourRule?: boolean
399
+ }
400
+ ```
401
+
402
+ ---
403
+
404
+ ### 添加新的导出格式
405
+
406
+ #### 1. 在 useImportExport.ts 中添加函数
407
+
408
+ ```typescript
409
+ const exportCSV = () => {
410
+ // 实现 CSV 导出逻辑
411
+ }
412
+ ```
413
+
414
+ #### 2. 在 SkuEditor.vue 中暴露方法
415
+
416
+ ```typescript
417
+ defineExpose({
418
+ exportExcel,
419
+ exportJson,
420
+ exportCSV, // 添加
421
+ confirmImport,
422
+ confirmImportExcel,
423
+ validateSkuData
424
+ })
425
+ ```
426
+
427
+ #### 3. 在 SpecSection.vue 中添加入口
428
+
429
+ 在工具栏的下拉菜单中添加选项。
430
+
431
+ ---
432
+
433
+ ## 样式定制
434
+
435
+ ### 使用 CSS 变量
436
+
437
+ 组件使用 Element Plus 的 CSS 变量,可以通过覆盖变量来定制主题:
438
+
439
+ ```css
440
+ :root {
441
+ --el-color-primary: #your-color;
442
+ --el-border-radius-base: 8px;
443
+ }
444
+ ```
445
+
446
+ ### 覆盖组件样式
447
+
448
+ 各组件都有独立的 scoped 样式,可以通过深度选择器覆盖:
449
+
450
+ ```css
451
+ .sku-editor :deep(.some-class) {
452
+ /* 你的样式 */
453
+ }
454
+ ```
455
+
456
+ ### 主要样式类名
457
+
458
+ - `.sku-editor` - 主容器
459
+ - `.spec-section` - 规格区域
460
+ - `.sku-section` - SKU 表格区域
461
+ - `.spec-item` - 单个规格项
462
+ - `.attr-tag-wrapper` - 属性标签包装
463
+
464
+ ---
465
+
466
+ ## 常见修改场景
467
+
468
+ ### 1. 修改默认数据
469
+
470
+ 现在组件不再内置默认数据,需要在父组件中传入:
471
+
472
+ ```typescript
473
+ // 在 App.vue 中
474
+ const specs = ref<Spec[]>([
475
+ // 你的默认数据
476
+ ])
477
+ const skuData = ref<SkuRow[]>(generateSkuData(specs.value))
478
+ ```
479
+
480
+ ### 2. 禁用某些功能
481
+
482
+ 注释或移除模板中的对应元素:
483
+
484
+ ```vue
485
+ <!-- 移除批量编辑按钮 -->
486
+ <!-- <template #toolbarTools>
487
+ <vxe-button ...>统一设置</vxe-button>
488
+ </template> -->
489
+ ```
490
+
491
+ ### 3. 修改表格配置
492
+
493
+ 在 `SkuGrid.vue` 中修改 `skuGridOptions`:
494
+
495
+ ```typescript
496
+ const skuGridOptions = reactive<VxeGridProps>({
497
+ border: true,
498
+ stripe: false, // 改斑马线样式
499
+ resizable: true,
500
+ height: 700, // 改高度
501
+ // ...
502
+ })
503
+ ```
504
+
505
+ ### 4. 添加数据验证
506
+
507
+ 在保存前添加验证逻辑,或使用新的校验系统:
508
+
509
+ ```typescript
510
+ // 方式 1:在保存时验证
511
+ const saveSpec = (name: string, type: SpecType) => {
512
+ if (name.length > 20) {
513
+ ElMessage.error('规格名称不能超过20个字符')
514
+ return
515
+ }
516
+ // ... 原有逻辑
517
+ }
518
+
519
+ // 方式 2:使用校验系统
520
+ const result = skuEditorRef.value?.validateSkuData({
521
+ custom: (specs, skuData) => {
522
+ // 你的自定义校验
523
+ }
524
+ })
525
+ ```
526
+
527
+ ### 5. 修改 SKU 编号生成规则
528
+
529
+ 在 `generateSkuData` 函数中修改:
530
+
531
+ ```typescript
532
+ // 原逻辑
533
+ skuNo: remembered.skuNo || ''
534
+
535
+ // 修改为你的规则(在父组件中处理)
536
+ skuData.value.forEach((row, index) => {
537
+ if (!row.skuNo) {
538
+ row.skuNo = `PROD-${index + 1}`
539
+ }
540
+ })
541
+ ```
542
+
543
+ ### 6. 修改保留字段名
544
+
545
+ 在 `src/constants/sku.ts` 中修改:
546
+
547
+ ```typescript
548
+ export const RESERVED_NAMES: string[] = [
549
+ 'SKU编号',
550
+ '销售价',
551
+ // ... 添加你的保留字段
552
+ ]
553
+ ```
554
+
555
+ ---
556
+
557
+ ## 调试技巧
558
+
559
+ ### 输出数据到控制台
560
+
561
+ 点击工具栏的"输出到控制台"按钮,或调用 `handlePrintData()` 方法,可以查看当前的规格和 SKU 数据。
562
+
563
+ ### 使用 Vue DevTools
564
+
565
+ 1. 安装 Vue DevTools 浏览器扩展
566
+ 2. 在组件树中选择 SkuEditor
567
+ 3. 查看和修改组件的状态
568
+
569
+ ### 监听数据变化
570
+
571
+ 在 `SkuEditor.vue` 中添加临时的 watch 调试:
572
+
573
+ ```typescript
574
+ watch(specsRef, (newVal) => {
575
+ console.log('Specs changed:', newVal)
576
+ }, { deep: true })
577
+
578
+ watch(internalSkuData, (newVal) => {
579
+ console.log('SKU data changed:', newVal)
580
+ }, { deep: true })
581
+ ```
582
+
583
+ ---
584
+
585
+ ## 性能优化建议
586
+
587
+ ### 1. 大量 SKU 数据时
588
+
589
+ - 确保 VXE Table 的虚拟滚动开启(`virtualYConfig.enabled: true`)
590
+ - 减少不必要的列显示
591
+ - 避免在 `spanMethod` 中做复杂计算
592
+
593
+ ### 2. 频繁更新时
594
+
595
+ - 使用 `watch` 的 `flush: 'post'` 选项
596
+ - 防抖处理保存操作
597
+ - 避免在渲染期间修改数据
598
+
599
+ ### 3. 导入大文件时
600
+
601
+ - 添加加载状态提示
602
+ - 考虑分片处理
603
+ - 使用 Web Worker(需要额外配置)
604
+
605
+ ### 4. 数据校验时
606
+
607
+ - 对于大数据量,考虑只在保存时校验
608
+ - 可以只执行部分校验规则(如快速校验)
609
+
610
+ ---
611
+
612
+ ## 测试建议
613
+
614
+ ### 单元测试关注点
615
+
616
+ - `useSkuGenerator.ts` - 测试 SKU 生成逻辑、数据转换
617
+ - `useMergeCells.ts` - 测试单元格合并计算
618
+ - `useValidation.ts` - 测试数据校验逻辑
619
+ - 各种边界情况(空数据、单规格、多规格等)
620
+
621
+ ### 集成测试关注点
622
+
623
+ - 完整的增删改流程
624
+ - 导入导出功能
625
+ - 数据保留功能
626
+ - 拖拽排序功能
627
+ - 数据校验功能
628
+
629
+ ---
630
+
631
+ ## 相关资源
632
+
633
+ - [Vue 3 Composition API](https://cn.vuejs.org/guide/extras/composition-api-faq.html)
634
+ - [Element Plus 组件](https://element-plus.org/)
635
+ - [VXE Table 文档](https://vxetable.cn/)
636
+ - [Sortable.js 文档](https://sortablejs.github.io/Sortable/)
637
+ - [SheetJS (xlsx)](https://sheetjs.com/)