@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,3 @@
1
+ <template >
2
+ <router-view />
3
+ </template>
@@ -0,0 +1,459 @@
1
+ <template>
2
+ <div class="top-right-btn" v-if="proVisible">
3
+ <el-row>
4
+ <el-tooltip
5
+ class="item"
6
+ effect="dark"
7
+ :content="showSearch ? $t('隐藏搜索') : $t('显示搜索')"
8
+ placement="top"
9
+ v-if="showSearchTool"
10
+ >
11
+ <el-button
12
+ circle
13
+ icon="Search"
14
+ @click="toggleSearch()"
15
+ :disabled="disabled"
16
+ />
17
+ </el-tooltip>
18
+ <el-tooltip
19
+ class="item"
20
+ effect="dark"
21
+ :content="$t('刷新')"
22
+ placement="top"
23
+ v-if="showRefreshTool"
24
+ >
25
+ <el-button
26
+ circle
27
+ icon="Refresh"
28
+ @click="refresh()"
29
+ :disabled="disabled"
30
+ />
31
+ </el-tooltip>
32
+ <el-tooltip
33
+ class="item"
34
+ effect="dark"
35
+ :content="$t('显隐列')"
36
+ placement="top"
37
+ v-if="columns && showColumnTool"
38
+ >
39
+ <el-button
40
+ circle
41
+ icon="Menu"
42
+ @click="showColumn()"
43
+ :disabled="disabled"
44
+ />
45
+ </el-tooltip>
46
+ </el-row>
47
+ <div class="rightToolBarDialog">
48
+ <el-dialog
49
+ :title="title"
50
+ v-model="open"
51
+ append-to-body
52
+ width="600px"
53
+ @close="closeDialog"
54
+ v-if="open"
55
+ >
56
+ <el-transfer
57
+ ref="transferRef"
58
+ :titles="[$t('显示'), $t('隐藏')]"
59
+ v-model="modelValue"
60
+ :data="data"
61
+ @change="dataChange"
62
+ target-order="push"
63
+ filterable
64
+ >
65
+ <template #left-footer class="scopeStyle">
66
+ <el-button link type="primary" size="small" @click="initColumns">{{
67
+ $t("恢复默认")
68
+ }}</el-button>
69
+ </template>
70
+ <template #right-footer class="scopeStyle">
71
+ <el-button link type="primary" size="small" @click="clearValue">{{
72
+ $t("清空")
73
+ }}</el-button>
74
+ </template>
75
+ <template #default="{ option }">
76
+ <el-tooltip
77
+ class="item"
78
+ effect="dark"
79
+ :content="option.label"
80
+ placement="bottom"
81
+ >
82
+ <span>{{ option.label }}</span>
83
+ </el-tooltip>
84
+ </template>
85
+ </el-transfer>
86
+ <template #footer>
87
+ <div class="dialog-footer">
88
+ <el-button @click="cancelBtn">{{ $t("取 消") }}</el-button>
89
+ <el-button
90
+ type="primary"
91
+ @click="submitBtn"
92
+ v-loading="submitLoading"
93
+ >{{ $t("确 定") }}</el-button
94
+ >
95
+ </div>
96
+ </template>
97
+ </el-dialog>
98
+ </div>
99
+ </div>
100
+ <C_OldVersion
101
+ v-if="!proVisible"
102
+ :columns="columnsProp"
103
+ :showSearch="showSearchProp"
104
+ />
105
+ </template>
106
+
107
+ <script setup>
108
+ import { nextTick } from "vue";
109
+ import { postAction, getAction } from "@jhlc/common-core/src/api/action";
110
+ import Sortable from "sortablejs";
111
+ import { deepClone } from "@/util";
112
+ import C_OldVersion from "./old-version";
113
+ import { useI18n } from "vue-i18n";
114
+
115
+ const { t } = useI18n();
116
+ const transferRef = ref(null);
117
+ const props = defineProps({
118
+ // 新版本rightToolbar
119
+ proVisible: {
120
+ type: Boolean,
121
+ default: false
122
+ },
123
+ showSearch: {
124
+ type: Boolean,
125
+ default: true
126
+ },
127
+ columns: {
128
+ type: Array
129
+ },
130
+ // 列表id
131
+ tableId: {
132
+ type: String
133
+ },
134
+ // 初始数据
135
+ initialColumns: {
136
+ type: Array
137
+ },
138
+ // 后端生成id,第二次后使用
139
+ id: {
140
+ type: String
141
+ },
142
+ // 穿梭框右侧数据
143
+ propsValue: {
144
+ type: Array
145
+ },
146
+ disabled: {
147
+ type: Boolean,
148
+ default: false
149
+ },
150
+ // 控制表格刷新工具按钮显示/隐藏
151
+ showRefreshTool: {
152
+ type: Boolean,
153
+ default: false
154
+ },
155
+ // 控制显隐搜索栏工具按钮显示/隐藏
156
+ showSearchTool: {
157
+ type: Boolean,
158
+ default: false
159
+ },
160
+ // 控制动态列表字段工具按钮显示/隐藏
161
+ showColumnTool: {
162
+ type: Boolean,
163
+ default: true
164
+ }
165
+ });
166
+
167
+ const modelValue = ref([]);
168
+
169
+ watch(
170
+ () => props.propsValue,
171
+ (newVal) => {
172
+ modelValue.value = newVal;
173
+ },
174
+ { immediate: true }
175
+ );
176
+
177
+ const columnsProp = computed({
178
+ get: () => props.columns
179
+ });
180
+ const showSearchProp = computed({
181
+ get: () => props.showSearch
182
+ });
183
+ const emits = defineEmits([
184
+ "update:showSearch",
185
+ "queryTable",
186
+ "update:columns",
187
+ "update:propsValue"
188
+ ]);
189
+
190
+ const data = ref([]);
191
+ // 弹出层标题
192
+ const title = ref(t("设置显示字段"));
193
+ // 是否显示弹出层
194
+ const open = ref(false);
195
+ // 显示字段拼接的字符串
196
+ const showFields = ref("");
197
+ // 隐藏字段拼接的字符串
198
+ const hideFields = ref("");
199
+ // 初始字段
200
+ const defaultColumns = ref([]);
201
+ // 保存id
202
+ const columnId = ref("");
203
+
204
+ const temp = computed(() =>
205
+ data.value.filter((item) => {
206
+ return item.visible;
207
+ })
208
+ );
209
+ const emitTemp = ref([]);
210
+ // 提交loading
211
+ const submitLoading = ref(false);
212
+
213
+ // 搜索
214
+ function toggleSearch() {
215
+ emits("update:showSearch", !props.showSearch);
216
+ }
217
+
218
+ // 刷新
219
+ function refresh() {
220
+ emits("queryTable");
221
+ }
222
+ // 关闭弹窗
223
+ const closeDialog = () => {
224
+ initColumns();
225
+ };
226
+ // 穿梭变化函数
227
+ function dataChange(val) {
228
+ if (val) {
229
+ data.value.forEach((item) => {
230
+ const index = val.findIndex((ele) => ele === item.key);
231
+ if (index !== -1) {
232
+ item.visible = false;
233
+ } else {
234
+ item.visible = true;
235
+ }
236
+ });
237
+ }
238
+ temp.value = data.value.filter((item) => {
239
+ return item.visible;
240
+ });
241
+ }
242
+ // 打开显隐列dialog
243
+ function showColumn() {
244
+ open.value = true;
245
+ data.value = [];
246
+ defaultColumns.value = [];
247
+ props.columns.forEach((item) => {
248
+ defaultColumns.value.push(deepClone(item));
249
+ data.value.push(deepClone(item));
250
+ });
251
+ // 拖动排序
252
+ nextTick(() => {
253
+ const transfer = transferRef.value.$el;
254
+ const leftPanel = transfer
255
+ .getElementsByClassName("el-transfer-panel")[0]
256
+ .getElementsByClassName("el-transfer-panel__body")[0];
257
+ const leftEl = leftPanel.getElementsByClassName(
258
+ "el-transfer-panel__list"
259
+ )[0];
260
+ Sortable.create(leftEl, {
261
+ onEnd: (evt) => {
262
+ const { oldIndex, newIndex } = evt;
263
+ temp.value = data.value.filter((item) => {
264
+ return item.visible;
265
+ });
266
+ let _arr = temp.value.splice(oldIndex, 1);
267
+ temp.value.splice(newIndex, 0, _arr[0]);
268
+ let arr = [];
269
+ temp.value.forEach((item) => arr.push(item));
270
+ data.value.forEach((item) => {
271
+ if (item.visible == false) {
272
+ arr.push(item);
273
+ }
274
+ });
275
+ data.value = [];
276
+ arr.forEach((item) => {
277
+ data.value.push(item);
278
+ });
279
+ }
280
+ });
281
+ });
282
+ }
283
+
284
+ // 恢复方法
285
+ function recoverTool(recoverData) {
286
+ let dataTemp = [];
287
+ let modelValueTemp = [];
288
+ recoverData.forEach((item) => {
289
+ dataTemp.push(deepClone(item));
290
+ if (!item.visible) {
291
+ modelValueTemp.push(item.key);
292
+ }
293
+ });
294
+ data.value = dataTemp;
295
+ modelValue.value = modelValueTemp;
296
+ }
297
+ // 穿梭框左侧恢复默认按钮
298
+ function initColumns() {
299
+ recoverTool(defaultColumns.value);
300
+ }
301
+ // 穿梭框右侧清空按钮
302
+ function clearValue() {
303
+ recoverTool(props.initialColumns);
304
+ }
305
+
306
+ function cancelBtn() {
307
+ initColumns();
308
+ open.value = false;
309
+ }
310
+
311
+ // 保存配置
312
+ function submitBtn() {
313
+ showFields.value = "";
314
+ hideFields.value = "";
315
+ temp.value.forEach((item) => {
316
+ showFields.value += `${item.key},`;
317
+ });
318
+ hideFields.value = modelValue.value.join();
319
+ let paramsList = {
320
+ id: columnId.value || props.id,
321
+ tableId: props.tableId,
322
+ showFields: showFields.value,
323
+ hideFields: hideFields.value
324
+ };
325
+ submitLoading.value = true;
326
+ postAction("system/pageShowFields/saveOrUpdate", paramsList)
327
+ .then((res) => {
328
+ temp.value.forEach((item) => {
329
+ emitTemp.value.push(item);
330
+ });
331
+ data.value.forEach((item) => {
332
+ if (!item.visible) {
333
+ emitTemp.value.push(item);
334
+ }
335
+ });
336
+ columnId.value = res.data.id;
337
+ emits("update:columns", emitTemp.value);
338
+ emits("update:propsValue", modelValue.value);
339
+ emitTemp.value = [];
340
+ })
341
+ .catch(() => initColumns())
342
+ .finally(() => {
343
+ submitLoading.value = false;
344
+ open.value = false;
345
+ });
346
+ }
347
+
348
+ // 获取配置的列表字段信息
349
+ function getColumns(columnsInfo) {
350
+ const _columnsInfo = deepClone(columnsInfo);
351
+ _columnsInfo.propsValue = [];
352
+ return getAction(
353
+ `/system/pageShowFields/getByTableIdAndUserNo?tableId=${_columnsInfo.tableId}`
354
+ ).then((res) => {
355
+ if (res.data) {
356
+ _columnsInfo.id = res.data.id;
357
+ // 显示字段
358
+ let showArr;
359
+ if (res.data.showFields) {
360
+ showArr = res.data.showFields.split(",");
361
+ }
362
+
363
+ // 隐藏字段
364
+ let hideArr;
365
+ if (res.data.hideFields) {
366
+ hideArr = res.data.hideFields.split(",");
367
+ }
368
+ // 排序
369
+ let flag = [];
370
+ flag = showArr.reduce((res, item) => {
371
+ const value = _columnsInfo.columns.find((val) => val.key === item);
372
+ if (!value) {
373
+ return res;
374
+ }
375
+ res.push(value);
376
+ return res;
377
+ }, []);
378
+ let result = [...new Set(flag.concat(_columnsInfo.columns))];
379
+ _columnsInfo.columns = [];
380
+ result.forEach((item) => {
381
+ _columnsInfo.columns.push(item);
382
+ });
383
+ _columnsInfo.columns.forEach((item) => {
384
+ if (showArr) {
385
+ const index1 = showArr.findIndex((val) => val === item.key);
386
+ if (index1 !== -1) {
387
+ item.visible = true;
388
+ }
389
+ }
390
+ if (hideArr) {
391
+ const index2 = hideArr.findIndex((val) => val === item.key);
392
+ if (index2 !== -1) {
393
+ item.visible = false;
394
+ }
395
+ }
396
+ });
397
+ // 右侧隐藏数据
398
+ if (hideArr) {
399
+ hideArr.forEach((item) => {
400
+ _columnsInfo.propsValue.push(item);
401
+ });
402
+ }
403
+
404
+ return _columnsInfo;
405
+ }
406
+ });
407
+ }
408
+
409
+ defineExpose({
410
+ getColumns
411
+ });
412
+ </script>
413
+
414
+ <style lang="scss" scoped>
415
+ :deep(.rightToolBarDialog .el-dialog-body) {
416
+ display: flex !important;
417
+ justify-content: center !important;
418
+ }
419
+ :deep(.el-transfer__button) {
420
+ display: block;
421
+ }
422
+
423
+ :deep(.el-transfer__button:first-child) {
424
+ margin-bottom: 10px;
425
+ }
426
+ :deep(.el-transfer__button:nth-child(2)) {
427
+ margin-left: 0px !important;
428
+ }
429
+
430
+ .my-el-transfer {
431
+ text-align: center;
432
+ }
433
+
434
+ :deep(
435
+ .el-transfer-panel
436
+ .el-transfer-panel__header
437
+ .el-checkbox
438
+ .el-checkbox__label
439
+ span
440
+ ) {
441
+ position: static;
442
+ }
443
+
444
+ :deep(.el-transfer-panel__footer button) {
445
+ position: absolute;
446
+ right: 5px;
447
+ top: 12px;
448
+ }
449
+
450
+ :deep(.el-transfer-panel:first-child .el-checkbox__label) {
451
+ cursor: move !important;
452
+ }
453
+ :deep(.el-transfer-panel) {
454
+ width: 40%;
455
+ }
456
+ :deep(.el-transfer__buttons) {
457
+ width: 20%;
458
+ }
459
+ </style>
@@ -0,0 +1,195 @@
1
+ <template>
2
+ <div
3
+ ref="containerRef"
4
+ class="my-splitter-container"
5
+ :class="[`is-${direction}`]"
6
+ >
7
+ <template v-for="(item, index) in vnodes" :key="index">
8
+ <div class="splitter-item" :style="getItemStyle(index)">
9
+ <component :is="item" />
10
+ </div>
11
+
12
+ <div
13
+ v-if="index < vnodes.length - 1"
14
+ class="splitter-trigger"
15
+ @mousedown="(e) => onMouseDown(e, index)"
16
+ >
17
+ <div class="trigger-line"></div>
18
+ </div>
19
+ </template>
20
+ </div>
21
+ </template>
22
+
23
+ <script setup>
24
+ import { ref, reactive, onMounted, useSlots, onUnmounted } from "vue";
25
+
26
+ const props = defineProps({
27
+ direction: {
28
+ type: String,
29
+ default: "horizontal" // horizontal | vertical
30
+ },
31
+ minSize: {
32
+ type: Number,
33
+ default: 50
34
+ }
35
+ });
36
+
37
+ const slots = useSlots();
38
+ const containerRef = ref(null);
39
+ const vnodes = ref([]); // 存储虚拟节点
40
+ const paneConfigs = ref([]); // 存储每个面板的 minSize 等配置
41
+ const sizes = reactive([]);
42
+ const isDragging = ref(false);
43
+ let currentTriggerIndex = -1;
44
+
45
+ // 初始化:获取插槽并分配初始平均尺寸
46
+ onMounted(() => {
47
+ const defaultSlots = slots.default ? slots.default() : [];
48
+ // 过滤掉注释节点(Symbol(v-cmt))和Fragment,只保留真正的元素
49
+ const children = defaultSlots.filter((v) => {
50
+ // 排除 Symbol 类型(注释节点)和 Fragment
51
+ if (typeof v.type === "symbol") return false;
52
+ // 保留 string(原生HTML元素)和 object(组件)
53
+ return typeof v.type === "string" || typeof v.type === "object";
54
+ });
55
+ vnodes.value = children;
56
+
57
+ const rect = containerRef.value.getBoundingClientRect();
58
+ const totalAvailable =
59
+ props.direction === "horizontal" ? rect.width : rect.height;
60
+ // vertical 模式 trigger 高度是 8px,horizontal 模式宽度是 4px
61
+ const triggerSize = props.direction === "horizontal" ? 4 : 8;
62
+ const triggerTotalSize = (children.length - 1) * triggerSize;
63
+
64
+ let remainingSize = totalAvailable - triggerTotalSize;
65
+ let autoCount = 0;
66
+
67
+ // 1. 第一次遍历:解析 initialSize (支持 200 或 "30%")
68
+ const tempSizes = children.map((vnode) => {
69
+ const initSize = vnode.props?.initialSize;
70
+ if (initSize === undefined || initSize === null) {
71
+ autoCount++;
72
+ return null;
73
+ }
74
+ const sizePx =
75
+ typeof initSize === "string" && initSize.endsWith("%")
76
+ ? (parseFloat(initSize) / 100) * (totalAvailable - triggerTotalSize)
77
+ : parseFloat(initSize);
78
+ remainingSize -= sizePx;
79
+ return sizePx;
80
+ });
81
+
82
+ // 2. 第二次遍历:填充 sizes 和 paneConfigs
83
+ tempSizes.forEach((size, idx) => {
84
+ sizes.push(size === null ? remainingSize / autoCount : size);
85
+ paneConfigs.value.push({
86
+ minSize: children[idx].props?.minSize || props.minSize
87
+ });
88
+ });
89
+ });
90
+
91
+ const getItemStyle = (index) => {
92
+ const prop = props.direction === "horizontal" ? "width" : "height";
93
+ return { [prop]: `${sizes[index]}px` };
94
+ };
95
+
96
+ // 拖拽逻辑
97
+ const onMouseDown = (e, index) => {
98
+ isDragging.value = true;
99
+ currentTriggerIndex = index;
100
+ document.body.style.userSelect = "none";
101
+ document.body.style.cursor =
102
+ props.direction === "horizontal" ? "col-resize" : "row-resize";
103
+
104
+ window.addEventListener("mousemove", onMouseMove);
105
+ window.addEventListener("mouseup", onMouseUp);
106
+ };
107
+
108
+ const onMouseMove = (e) => {
109
+ if (!isDragging.value) return;
110
+ const i = currentTriggerIndex;
111
+ const movement = props.direction === "horizontal" ? e.movementX : e.movementY;
112
+
113
+ const minCurr = paneConfigs.value[i].minSize;
114
+ const minNext = paneConfigs.value[i + 1].minSize;
115
+
116
+ if (sizes[i] + movement >= minCurr && sizes[i + 1] - movement >= minNext) {
117
+ sizes[i] += movement;
118
+ sizes[i + 1] -= movement;
119
+ }
120
+ };
121
+
122
+ const onMouseUp = () => {
123
+ isDragging.value = false;
124
+ document.body.style.userSelect = "";
125
+ document.body.style.cursor = "";
126
+ window.removeEventListener("mousemove", onMouseMove);
127
+ window.removeEventListener("mouseup", onMouseUp);
128
+ };
129
+
130
+ onUnmounted(() => onMouseUp());
131
+ </script>
132
+
133
+ <style scoped>
134
+ .my-splitter-container {
135
+ display: flex;
136
+ width: 100%;
137
+ height: 100%;
138
+ overflow: hidden;
139
+ }
140
+
141
+ .is-horizontal {
142
+ flex-direction: row;
143
+ }
144
+ .is-vertical {
145
+ flex-direction: column;
146
+ }
147
+
148
+ .splitter-item {
149
+ overflow: auto;
150
+ }
151
+
152
+ .splitter-trigger {
153
+ background-color: #f0f2f5;
154
+ display: flex;
155
+ align-items: center;
156
+ justify-content: center;
157
+ transition: background-color 0.2s;
158
+ z-index: 10;
159
+ }
160
+
161
+ .splitter-trigger:hover {
162
+ background-color: #409eff;
163
+ }
164
+
165
+ /* 水平线 */
166
+ .is-horizontal > .splitter-trigger {
167
+ width: 4px;
168
+ cursor: col-resize;
169
+ }
170
+ .is-horizontal .trigger-line {
171
+ width: 1px;
172
+ height: 20px;
173
+ background: #dcdfe6;
174
+ }
175
+
176
+ /* 垂直线 */
177
+ .is-vertical > .splitter-trigger {
178
+ height: 8px;
179
+ cursor: row-resize;
180
+ background-color: #f5f5f5;
181
+ flex-shrink: 0;
182
+ display: flex;
183
+ align-items: center;
184
+ justify-content: center;
185
+ }
186
+ .is-vertical .trigger-line {
187
+ width: 30px;
188
+ height: 3px;
189
+ background: #d9d9d9;
190
+ border-radius: 2px;
191
+ }
192
+ .is-vertical > .splitter-trigger:hover .trigger-line {
193
+ background: #409eff;
194
+ }
195
+ </style>
@@ -0,0 +1,61 @@
1
+ <template>
2
+ <svg :class="svgClass" aria-hidden="true">
3
+ <use :xlink:href="iconName" :fill="color" />
4
+ </svg>
5
+ </template>
6
+
7
+ <script>
8
+ import { addSymbolDelay } from '@jhlc/common-resource/src/svg/register-static-svg'
9
+
10
+ export default {
11
+ props: {
12
+ iconClass: {
13
+ type: String,
14
+ required: true
15
+ },
16
+ className: {
17
+ type: String,
18
+ default: ''
19
+ },
20
+ color: {
21
+ type: String,
22
+ default: ''
23
+ },
24
+ },
25
+ beforeCreate() {
26
+ addSymbolDelay(this.iconClass)
27
+ },
28
+ updated() {
29
+ addSymbolDelay(this.iconClass)
30
+ },
31
+ setup(props) {
32
+ return {
33
+ iconName: computed(() => `#icon-${props.iconClass}`),
34
+ svgClass: computed(() => {
35
+ if (props.className) {
36
+ return `svg-icon ${props.className}`
37
+ }
38
+ return 'svg-icon'
39
+ })
40
+ }
41
+ }
42
+ }
43
+ </script>
44
+
45
+ <style scope lang="scss">
46
+ .sub-el-icon,
47
+ .nav-icon {
48
+ display: inline-block;
49
+ font-size: 15px;
50
+ margin-right: 12px;
51
+ position: relative;
52
+ }
53
+
54
+ .svg-icon {
55
+ width: 1em;
56
+ height: 1em;
57
+ position: relative;
58
+ fill: currentColor;
59
+ vertical-align: -2px;
60
+ }
61
+ </style>