@agile-team/wl-skills-kit 2.11.1 → 2.11.2
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/CHANGELOG.md +13 -0
- package/README.md +1 -1
- package/bin/wl-skills.js +27 -3
- package/files/.wl-skills/docs/jh-pagination.md +505 -505
- package/files/.wl-skills/docs/request.md +940 -940
- package/files/.wl-skills/guides/architecture.md +1 -1
- package/files/.wl-skills/skills/core/convention-audit/SKILL.md +3 -3
- package/files/.wl-skills/skills/core/spec-doc-parse/SKILL.md +332 -332
- package/files/.wl-skills/skills/core/spec-doc-parse/USAGE.md +97 -97
- package/files/.wl-skills/skills/sync/permission-sync/USAGE.md +107 -107
- package/files/.wl-skills/src/components/global/C_ParentView/index.vue +3 -3
- package/files/.wl-skills/src/components/global/C_RightToolbar/index.vue +157 -157
- package/files/.wl-skills/src/components/global/C_SvgIcon/index.vue +31 -31
- package/files/.wl-skills/src/components/global/C_SvgIcon/svgicon.js +10 -10
- package/files/.wl-skills/src/components/global/C_TagStatus/README.md +264 -264
- package/files/.wl-skills/src/components/global/C_TagStatus/config.ts +192 -192
- package/files/.wl-skills/src/components/global/C_TagStatus/index.vue +106 -106
- package/files/.wl-skills/src/components/global/C_TagStatus/types.ts +64 -64
- package/files/.wl-skills/src/components/global/C_Tree/README.md +153 -153
- package/files/.wl-skills/src/components/global/C_Tree/index.scss +42 -42
- package/files/.wl-skills/src/components/global/C_Tree/index.vue +78 -78
- package/files/.wl-skills/src/components/global/C_Tree/types.ts +59 -59
- package/files/.wl-skills/src/components/local/c_formModal/README.md +235 -235
- package/files/.wl-skills/src/components/local/c_formModal/data.ts +95 -95
- package/files/.wl-skills/src/components/local/c_formModal/index.scss +8 -8
- package/files/.wl-skills/src/components/local/c_formModal/index.vue +107 -107
- package/files/.wl-skills/src/components/local/c_formSections/data.ts +175 -175
- package/files/.wl-skills/src/components/local/c_formSections/index.scss +280 -280
- package/files/.wl-skills/src/components/local/c_formSections/index.vue +429 -429
- package/files/.wl-skills/src/components/local/c_listModal/data.ts +41 -41
- package/files/.wl-skills/src/components/local/c_listModal/index.vue +136 -136
- package/files/.wl-skills/src/components/local/c_spliterTitle/index.scss +25 -25
- package/files/.wl-skills/src/components/local/c_spliterTitle/index.vue +21 -21
- package/files/.wl-skills/src/components/remote/AGGrid/README.md +530 -530
- package/files/.wl-skills/src/components/remote/BaseForm/README.md +508 -508
- package/files/.wl-skills/src/components/remote/BaseQuery/README.md +865 -865
- package/files/.wl-skills/src/components/remote/BaseTable/README.md +941 -941
- package/files/.wl-skills/src/components/remote/BaseToolbar/README.md +496 -496
- package/files/.wl-skills/src/types/page.ts +24 -24
- package/files/.wl-skills/standards/04-coding-basics.md +39 -1
- package/files/.wl-skills/standards/09-typescript.md +26 -3
- package/files/.wl-skills/standards/index.md +2 -2
- package/files/.wl-skills/templates/README.md +44 -44
- package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-apply-add/api.md +54 -54
- package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-apply-add/data.ts +346 -346
- package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-apply-add/index.scss +1 -1
- package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-apply-add/index.vue +28 -28
- package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-apply-add-form/data.ts +115 -115
- package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-apply-add-form/index.scss +44 -44
- package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-apply-add-form/index.vue +43 -43
- package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-apply-change/data.ts +338 -338
- package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-apply-change/index.scss +1 -1
- package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-apply-change/index.vue +28 -28
- package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-apply-change-form/data.ts +115 -115
- package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-apply-change-form/index.scss +44 -44
- package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-apply-change-form/index.vue +43 -43
- package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-archive/api.md +88 -88
- package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-archive/data.ts +601 -601
- package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-archive/index.scss +1 -1
- package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-archive/index.vue +64 -64
- package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-detail/api.md +67 -67
- package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-detail/data.ts +286 -286
- package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-detail/index.scss +139 -139
- package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-detail/index.vue +318 -318
- package/files/.wl-skills/templates/produce/aiflow/mmwr-temp-customer-archive/api.md +98 -98
- package/files/.wl-skills/templates/produce/aiflow/mmwr-temp-customer-archive/data.ts +543 -543
- package/files/.wl-skills/templates/produce/aiflow/mmwr-temp-customer-archive/index.scss +1 -1
- package/files/.wl-skills/templates/produce/aiflow/mmwr-temp-customer-archive/index.vue +52 -52
- package/files/.wl-skills/templates/sale/demo/add-demo/data.ts +518 -518
- package/files/.wl-skills/templates/sale/demo/billet-flame-cut-plan/data.ts +524 -524
- package/files/.wl-skills/templates/sale/demo/billet-flame-cut-plan/index.scss +154 -154
- package/files/.wl-skills/templates/sale/demo/billet-flame-cut-plan/index.vue +117 -117
- package/files/.wl-skills/templates/sale/demo/domestic-trade-order/data.ts +308 -308
- package/files/.wl-skills/templates/sale/demo/domestic-trade-order/index.scss +99 -99
- package/files/.wl-skills/templates/sale/demo/domestic-trade-order/index.vue +77 -77
- package/files/.wl-skills/templates/sale/demo/heat-batch-return/data.ts +367 -367
- package/files/.wl-skills/templates/sale/demo/heat-batch-return/index.scss +100 -100
- package/files/.wl-skills/templates/sale/demo/heat-batch-return/index.vue +170 -170
- package/files/.wl-skills/templates/sale/demo/heat-batch-return/meltDialog.vue +320 -320
- package/files/.wl-skills/templates/sale/demo/metallurgical-spec/data.ts +824 -824
- package/lib/ast-rules.js +304 -9
- package/mcp/config.js +46 -46
- package/mcp/registry.js +6 -1
- package/mcp/tools/projectTools.js +9 -1
- package/package.json +2 -2
|
@@ -1,429 +1,429 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
* @Author: ChenYu ycyplus@gmail.com
|
|
3
|
-
* @Date: 2026-01-01 13:30:00
|
|
4
|
-
* @LastEditors: ChenYu ycyplus@gmail.com
|
|
5
|
-
* @LastEditTime: 2026-01-01 16:30:00
|
|
6
|
-
* @FilePath: \cx-ui-sale\src\components\local\c_formSections\index.vue
|
|
7
|
-
* @Description: 表单区块组件 - 集成楼层导航、工具栏、布局切换等功能
|
|
8
|
-
* Copyright (c) 2026 by CHENY, All Rights Reserved 😎.
|
|
9
|
-
-->
|
|
10
|
-
<template>
|
|
11
|
-
<div class="c-form-sections-wrapper">
|
|
12
|
-
<!-- 顶部标题栏 -->
|
|
13
|
-
<div v-if="showHeader" class="c-form-header">
|
|
14
|
-
<span class="header-title">{{ headerTitle }}</span>
|
|
15
|
-
<div class="header-right">
|
|
16
|
-
<!-- 布局切换器 -->
|
|
17
|
-
<div v-if="showLayoutSwitch" class="layout-switcher">
|
|
18
|
-
<span
|
|
19
|
-
v-for="option in internalLayoutOptions"
|
|
20
|
-
:key="option.columns"
|
|
21
|
-
class="layout-icon"
|
|
22
|
-
:class="{ active: currentLayout === option.columns }"
|
|
23
|
-
:title="option.title"
|
|
24
|
-
@click="currentLayout = option.columns"
|
|
25
|
-
>
|
|
26
|
-
{{ option.icon }}
|
|
27
|
-
</span>
|
|
28
|
-
</div>
|
|
29
|
-
<el-divider
|
|
30
|
-
v-if="showLayoutSwitch && headerActions.length > 0"
|
|
31
|
-
direction="vertical"
|
|
32
|
-
/>
|
|
33
|
-
<!-- 顶部操作按钮 -->
|
|
34
|
-
<template v-for="action in headerActions" :key="action.label">
|
|
35
|
-
<el-button
|
|
36
|
-
:type="action.type || ''"
|
|
37
|
-
size="small"
|
|
38
|
-
:disabled="action.disabled"
|
|
39
|
-
@click="action.onClick"
|
|
40
|
-
>
|
|
41
|
-
<el-icon v-if="action.icon">
|
|
42
|
-
<component :is="action.icon" />
|
|
43
|
-
</el-icon>
|
|
44
|
-
{{ action.label }}
|
|
45
|
-
</el-button>
|
|
46
|
-
</template>
|
|
47
|
-
</div>
|
|
48
|
-
</div>
|
|
49
|
-
|
|
50
|
-
<!-- 工具栏 -->
|
|
51
|
-
<div v-if="showToolbar" class="c-form-toolbar">
|
|
52
|
-
<div class="toolbar-left">
|
|
53
|
-
<!-- 必填字段过滤开关 -->
|
|
54
|
-
<span v-if="showRequiredFilter" class="required-toggle">
|
|
55
|
-
<span class="label">仅显示必填字段</span>
|
|
56
|
-
<el-switch v-model="showRequiredOnly" size="small" />
|
|
57
|
-
</span>
|
|
58
|
-
<!-- 工具栏左侧插槽 -->
|
|
59
|
-
<slot name="toolbar-left"></slot>
|
|
60
|
-
</div>
|
|
61
|
-
<div class="toolbar-right">
|
|
62
|
-
<!-- 工具栏右侧插槽 -->
|
|
63
|
-
<slot name="toolbar-right"></slot>
|
|
64
|
-
</div>
|
|
65
|
-
</div>
|
|
66
|
-
|
|
67
|
-
<!-- 主体内容区域 -->
|
|
68
|
-
<div class="c-form-content">
|
|
69
|
-
<!-- 左侧楼层导航 -->
|
|
70
|
-
<el-tabs
|
|
71
|
-
v-if="showNavTabs"
|
|
72
|
-
:tab-position="navTabsPosition"
|
|
73
|
-
v-model="activeNavTab"
|
|
74
|
-
@tab-click="handleNavTabClick"
|
|
75
|
-
class="nav-tabs"
|
|
76
|
-
>
|
|
77
|
-
<el-tab-pane
|
|
78
|
-
v-for="tab in navTabs"
|
|
79
|
-
:key="tab.name"
|
|
80
|
-
:label="tab.label"
|
|
81
|
-
:name="tab.name"
|
|
82
|
-
/>
|
|
83
|
-
</el-tabs>
|
|
84
|
-
|
|
85
|
-
<!-- 表单区域 -->
|
|
86
|
-
<div class="form-container" ref="formContainerRef">
|
|
87
|
-
<el-form
|
|
88
|
-
:label-width="labelWidth"
|
|
89
|
-
:label-position="labelPosition"
|
|
90
|
-
:model="form"
|
|
91
|
-
:rules="rules"
|
|
92
|
-
>
|
|
93
|
-
<el-collapse v-model="activeNamesModel">
|
|
94
|
-
<!-- 循环渲染折叠面板 -->
|
|
95
|
-
<el-collapse-item
|
|
96
|
-
v-for="section in visibleSections"
|
|
97
|
-
:key="section.name"
|
|
98
|
-
:name="section.name"
|
|
99
|
-
:id="section.id"
|
|
100
|
-
>
|
|
101
|
-
<template #title>
|
|
102
|
-
<span class="section-title">{{ section.title }}</span>
|
|
103
|
-
<el-divider />
|
|
104
|
-
</template>
|
|
105
|
-
|
|
106
|
-
<!-- 特殊区块 - 使用具名插槽 -->
|
|
107
|
-
<slot
|
|
108
|
-
v-if="section.isSpecial"
|
|
109
|
-
:name="`special-${section.name}`"
|
|
110
|
-
:section="section"
|
|
111
|
-
>
|
|
112
|
-
<el-row :gutter="gutter">
|
|
113
|
-
<el-col :span="24">
|
|
114
|
-
<el-empty description="请通过插槽自定义特殊区块内容" />
|
|
115
|
-
</el-col>
|
|
116
|
-
</el-row>
|
|
117
|
-
</slot>
|
|
118
|
-
|
|
119
|
-
<!-- 普通区块 - 循环渲染字段 -->
|
|
120
|
-
<el-row v-else :gutter="gutter">
|
|
121
|
-
<el-col
|
|
122
|
-
v-for="field in section.fieldsConfig"
|
|
123
|
-
:key="field.prop"
|
|
124
|
-
:span="currentFieldSpan"
|
|
125
|
-
>
|
|
126
|
-
<el-form-item
|
|
127
|
-
:label="field.label"
|
|
128
|
-
:prop="field.prop"
|
|
129
|
-
:required="field.required"
|
|
130
|
-
>
|
|
131
|
-
<!-- 下拉选择框 -->
|
|
132
|
-
<el-select
|
|
133
|
-
v-if="field.type === 'select'"
|
|
134
|
-
v-model="form[field.prop]"
|
|
135
|
-
:placeholder="field.placeholder || '请选择'"
|
|
136
|
-
:clearable="field.clearable !== false"
|
|
137
|
-
>
|
|
138
|
-
<el-option
|
|
139
|
-
v-for="opt in field.options"
|
|
140
|
-
:key="opt.value"
|
|
141
|
-
:label="opt.label"
|
|
142
|
-
:value="opt.value"
|
|
143
|
-
/>
|
|
144
|
-
</el-select>
|
|
145
|
-
|
|
146
|
-
<!-- 多行文本框 -->
|
|
147
|
-
<el-input
|
|
148
|
-
v-else-if="field.type === 'textarea'"
|
|
149
|
-
v-model="form[field.prop]"
|
|
150
|
-
type="textarea"
|
|
151
|
-
:rows="field.rows || 3"
|
|
152
|
-
:placeholder="field.placeholder || '请输入'"
|
|
153
|
-
/>
|
|
154
|
-
|
|
155
|
-
<!-- 日期选择器 -->
|
|
156
|
-
<el-date-picker
|
|
157
|
-
v-else-if="field.type === 'date'"
|
|
158
|
-
v-model="form[field.prop]"
|
|
159
|
-
type="date"
|
|
160
|
-
:placeholder="field.placeholder || '选择日期'"
|
|
161
|
-
style="width: 100%"
|
|
162
|
-
/>
|
|
163
|
-
|
|
164
|
-
<!-- 日期时间选择器 -->
|
|
165
|
-
<el-date-picker
|
|
166
|
-
v-else-if="field.type === 'datetime'"
|
|
167
|
-
v-model="form[field.prop]"
|
|
168
|
-
type="datetime"
|
|
169
|
-
:placeholder="field.placeholder || '选择日期时间'"
|
|
170
|
-
style="width: 100%"
|
|
171
|
-
/>
|
|
172
|
-
|
|
173
|
-
<!-- 数字输入框 -->
|
|
174
|
-
<el-input-number
|
|
175
|
-
v-else-if="field.type === 'number'"
|
|
176
|
-
v-model="form[field.prop]"
|
|
177
|
-
:min="field.min"
|
|
178
|
-
:max="field.max"
|
|
179
|
-
:precision="field.precision"
|
|
180
|
-
:step="field.step || 1"
|
|
181
|
-
style="width: 100%"
|
|
182
|
-
/>
|
|
183
|
-
|
|
184
|
-
<!-- 自定义字段 - 使用作用域插槽 -->
|
|
185
|
-
<slot
|
|
186
|
-
v-else-if="field.type === 'custom'"
|
|
187
|
-
:name="`field-${field.prop}`"
|
|
188
|
-
:field="field"
|
|
189
|
-
:form="form"
|
|
190
|
-
>
|
|
191
|
-
<el-input
|
|
192
|
-
v-model="form[field.prop]"
|
|
193
|
-
:placeholder="field.placeholder || '请输入'"
|
|
194
|
-
/>
|
|
195
|
-
</slot>
|
|
196
|
-
|
|
197
|
-
<!-- 默认单行文本输入框 -->
|
|
198
|
-
<el-input
|
|
199
|
-
v-else
|
|
200
|
-
v-model="form[field.prop]"
|
|
201
|
-
:placeholder="field.placeholder || '请输入'"
|
|
202
|
-
/>
|
|
203
|
-
</el-form-item>
|
|
204
|
-
</el-col>
|
|
205
|
-
</el-row>
|
|
206
|
-
</el-collapse-item>
|
|
207
|
-
</el-collapse>
|
|
208
|
-
</el-form>
|
|
209
|
-
</div>
|
|
210
|
-
</div>
|
|
211
|
-
</div>
|
|
212
|
-
</template>
|
|
213
|
-
|
|
214
|
-
<script setup lang="ts">
|
|
215
|
-
import { ref, computed } from "vue";
|
|
216
|
-
import type {
|
|
217
|
-
SectionConfig,
|
|
218
|
-
FormDataType,
|
|
219
|
-
NavTabConfig,
|
|
220
|
-
HeaderAction,
|
|
221
|
-
LayoutOption
|
|
222
|
-
} from "./data";
|
|
223
|
-
|
|
224
|
-
/** 组件 Props */
|
|
225
|
-
interface Props {
|
|
226
|
-
// ===== 基础配置 =====
|
|
227
|
-
/** 表单区块配置数组 */
|
|
228
|
-
sections: SectionConfig[];
|
|
229
|
-
/** 表单数据对象 */
|
|
230
|
-
form: FormDataType;
|
|
231
|
-
/** 激活的折叠面板 name 数组 */
|
|
232
|
-
activeNames?: string[];
|
|
233
|
-
/** 表单验证规则 */
|
|
234
|
-
rules?: any;
|
|
235
|
-
/** 表单标签宽度 */
|
|
236
|
-
labelWidth?: string;
|
|
237
|
-
/** 表单标签位置 */
|
|
238
|
-
labelPosition?: "left" | "right" | "top";
|
|
239
|
-
/** 栅格间距 */
|
|
240
|
-
gutter?: number;
|
|
241
|
-
/** 每个字段占据的栅格数(外部手动控制时使用,会覆盖内部布局切换) */
|
|
242
|
-
fieldSpan?: number;
|
|
243
|
-
|
|
244
|
-
// ===== 顶部标题栏配置 =====
|
|
245
|
-
/** 是否显示顶部标题栏 */
|
|
246
|
-
showHeader?: boolean;
|
|
247
|
-
/** 标题栏标题文本 */
|
|
248
|
-
headerTitle?: string;
|
|
249
|
-
/** 顶部操作按钮 */
|
|
250
|
-
headerActions?: HeaderAction[];
|
|
251
|
-
|
|
252
|
-
// ===== 工具栏配置 =====
|
|
253
|
-
/** 是否显示工具栏 */
|
|
254
|
-
showToolbar?: boolean;
|
|
255
|
-
/** 是否显示必填字段过滤开关 */
|
|
256
|
-
showRequiredFilter?: boolean;
|
|
257
|
-
|
|
258
|
-
// ===== 布局切换配置 =====
|
|
259
|
-
/** 是否显示布局切换器 */
|
|
260
|
-
showLayoutSwitch?: boolean;
|
|
261
|
-
/** 默认布局列数 */
|
|
262
|
-
defaultLayout?: 2 | 3 | 4 | 5;
|
|
263
|
-
/** 可选布局列数 */
|
|
264
|
-
layoutOptions?: Array<2 | 3 | 4 | 5>;
|
|
265
|
-
|
|
266
|
-
// ===== 楼层导航配置 =====
|
|
267
|
-
/** 是否显示楼层导航 */
|
|
268
|
-
showNavTabs?: boolean;
|
|
269
|
-
/** 楼层导航位置 */
|
|
270
|
-
navTabsPosition?: "left" | "right";
|
|
271
|
-
/** 楼层导航配置(不传则自动从 sections 生成) */
|
|
272
|
-
navTabs?: NavTabConfig[];
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
const props = withDefaults(defineProps<Props>(), {
|
|
276
|
-
activeNames: () => [],
|
|
277
|
-
labelWidth: "100px",
|
|
278
|
-
labelPosition: "right",
|
|
279
|
-
gutter: 20,
|
|
280
|
-
fieldSpan: undefined,
|
|
281
|
-
|
|
282
|
-
showHeader: false,
|
|
283
|
-
headerTitle: "主档维护",
|
|
284
|
-
headerActions: () => [],
|
|
285
|
-
|
|
286
|
-
showToolbar: false,
|
|
287
|
-
showRequiredFilter: false,
|
|
288
|
-
|
|
289
|
-
showLayoutSwitch: false,
|
|
290
|
-
defaultLayout: 5,
|
|
291
|
-
layoutOptions: () => [2, 3, 4, 5],
|
|
292
|
-
|
|
293
|
-
showNavTabs: false,
|
|
294
|
-
navTabsPosition: "left",
|
|
295
|
-
navTabs: () => []
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
/** 组件 Emits */
|
|
299
|
-
interface Emits {
|
|
300
|
-
(e: "update:activeNames", value: string[]): void;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
const emit = defineEmits<Emits>();
|
|
304
|
-
|
|
305
|
-
// ===== 内部状态 =====
|
|
306
|
-
/** 仅显示必填字段开关 */
|
|
307
|
-
const showRequiredOnly = ref(false);
|
|
308
|
-
|
|
309
|
-
/** 当前布局列数 */
|
|
310
|
-
const currentLayout = ref<2 | 3 | 4 | 5>(props.defaultLayout);
|
|
311
|
-
|
|
312
|
-
/** 当前激活的导航标签 */
|
|
313
|
-
const activeNavTab = ref("");
|
|
314
|
-
|
|
315
|
-
/** 表单容器引用 */
|
|
316
|
-
const formContainerRef = ref<HTMLElement | null>(null);
|
|
317
|
-
|
|
318
|
-
// ===== 计算属性 =====
|
|
319
|
-
/** 激活的折叠面板(v-model) */
|
|
320
|
-
const activeNamesModel = computed({
|
|
321
|
-
get: () => props.activeNames,
|
|
322
|
-
set: (val) => emit("update:activeNames", val)
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
/**
|
|
326
|
-
* 🆕 内部布局选项配置
|
|
327
|
-
*/
|
|
328
|
-
const internalLayoutOptions = computed<LayoutOption[]>(() => {
|
|
329
|
-
return props.layoutOptions.map((col) => ({
|
|
330
|
-
columns: col,
|
|
331
|
-
icon: "|".repeat(col),
|
|
332
|
-
title: `${col}列布局`
|
|
333
|
-
}));
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
/**
|
|
337
|
-
* 🆕 当前字段 span 值
|
|
338
|
-
* 优先使用外部传入的 fieldSpan,否则根据内部布局计算
|
|
339
|
-
*/
|
|
340
|
-
const currentFieldSpan = computed(() => {
|
|
341
|
-
if (props.fieldSpan !== undefined) {
|
|
342
|
-
return props.fieldSpan;
|
|
343
|
-
}
|
|
344
|
-
return Math.floor(24 / currentLayout.value);
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
/**
|
|
348
|
-
* 🆕 过滤后的区块(根据必填字段过滤)
|
|
349
|
-
*/
|
|
350
|
-
const filteredSections = computed(() => {
|
|
351
|
-
if (!showRequiredOnly.value) {
|
|
352
|
-
return props.sections;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
return props.sections.map((section) => {
|
|
356
|
-
if (section.isSpecial) {
|
|
357
|
-
return section;
|
|
358
|
-
}
|
|
359
|
-
return {
|
|
360
|
-
...section,
|
|
361
|
-
fieldsConfig: section.fieldsConfig.filter((field) => field.required)
|
|
362
|
-
};
|
|
363
|
-
});
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
/**
|
|
367
|
-
* 过滤后的可见区块
|
|
368
|
-
* 根据 section.visible 函数和字段过滤结果判断是否显示
|
|
369
|
-
*/
|
|
370
|
-
const visibleSections = computed(() => {
|
|
371
|
-
return filteredSections.value.filter((section) => {
|
|
372
|
-
// 特殊区块:必填模式下隐藏
|
|
373
|
-
if (section.isSpecial) {
|
|
374
|
-
return !showRequiredOnly.value;
|
|
375
|
-
}
|
|
376
|
-
// 普通区块:有字段才显示
|
|
377
|
-
const hasFields = section.fieldsConfig.length > 0;
|
|
378
|
-
const visibleByFunc = section.visible ? section.visible() : true;
|
|
379
|
-
return hasFields && visibleByFunc;
|
|
380
|
-
});
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
/**
|
|
384
|
-
* 🆕 内部导航标签配置
|
|
385
|
-
* 如果外部传入则使用外部的,否则自动从 sections 生成
|
|
386
|
-
*/
|
|
387
|
-
const internalNavTabs = computed<NavTabConfig[]>(() => {
|
|
388
|
-
if (props.navTabs && props.navTabs.length > 0) {
|
|
389
|
-
return props.navTabs;
|
|
390
|
-
}
|
|
391
|
-
// 自动从 sections 生成
|
|
392
|
-
return props.sections.map((section) => ({
|
|
393
|
-
name: section.name,
|
|
394
|
-
label: section.title,
|
|
395
|
-
sectionName: section.name
|
|
396
|
-
}));
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
// ===== 方法 =====
|
|
400
|
-
/** 处理楼层导航点击 */
|
|
401
|
-
const handleNavTabClick = (tab: any) => {
|
|
402
|
-
const tabName = tab.paneName || tab.props?.name;
|
|
403
|
-
const tabConfig = internalNavTabs.value.find((t) => t.name === tabName);
|
|
404
|
-
|
|
405
|
-
if (!tabConfig?.sectionName) return;
|
|
406
|
-
|
|
407
|
-
// 展开对应的 collapse section
|
|
408
|
-
if (!activeNamesModel.value.includes(tabConfig.sectionName)) {
|
|
409
|
-
activeNamesModel.value = [...activeNamesModel.value, tabConfig.sectionName];
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
// 滚动到对应区域
|
|
413
|
-
setTimeout(() => {
|
|
414
|
-
const section = props.sections.find(
|
|
415
|
-
(s) => s.name === tabConfig.sectionName
|
|
416
|
-
);
|
|
417
|
-
if (section?.id) {
|
|
418
|
-
const sectionEl = document.getElementById(section.id);
|
|
419
|
-
if (sectionEl) {
|
|
420
|
-
sectionEl.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
}, 150);
|
|
424
|
-
};
|
|
425
|
-
</script>
|
|
426
|
-
|
|
427
|
-
<style scoped lang="scss">
|
|
428
|
-
@import "./index.scss";
|
|
429
|
-
</style>
|
|
1
|
+
<!--
|
|
2
|
+
* @Author: ChenYu ycyplus@gmail.com
|
|
3
|
+
* @Date: 2026-01-01 13:30:00
|
|
4
|
+
* @LastEditors: ChenYu ycyplus@gmail.com
|
|
5
|
+
* @LastEditTime: 2026-01-01 16:30:00
|
|
6
|
+
* @FilePath: \cx-ui-sale\src\components\local\c_formSections\index.vue
|
|
7
|
+
* @Description: 表单区块组件 - 集成楼层导航、工具栏、布局切换等功能
|
|
8
|
+
* Copyright (c) 2026 by CHENY, All Rights Reserved 😎.
|
|
9
|
+
-->
|
|
10
|
+
<template>
|
|
11
|
+
<div class="c-form-sections-wrapper">
|
|
12
|
+
<!-- 顶部标题栏 -->
|
|
13
|
+
<div v-if="showHeader" class="c-form-header">
|
|
14
|
+
<span class="header-title">{{ headerTitle }}</span>
|
|
15
|
+
<div class="header-right">
|
|
16
|
+
<!-- 布局切换器 -->
|
|
17
|
+
<div v-if="showLayoutSwitch" class="layout-switcher">
|
|
18
|
+
<span
|
|
19
|
+
v-for="option in internalLayoutOptions"
|
|
20
|
+
:key="option.columns"
|
|
21
|
+
class="layout-icon"
|
|
22
|
+
:class="{ active: currentLayout === option.columns }"
|
|
23
|
+
:title="option.title"
|
|
24
|
+
@click="currentLayout = option.columns"
|
|
25
|
+
>
|
|
26
|
+
{{ option.icon }}
|
|
27
|
+
</span>
|
|
28
|
+
</div>
|
|
29
|
+
<el-divider
|
|
30
|
+
v-if="showLayoutSwitch && headerActions.length > 0"
|
|
31
|
+
direction="vertical"
|
|
32
|
+
/>
|
|
33
|
+
<!-- 顶部操作按钮 -->
|
|
34
|
+
<template v-for="action in headerActions" :key="action.label">
|
|
35
|
+
<el-button
|
|
36
|
+
:type="action.type || ''"
|
|
37
|
+
size="small"
|
|
38
|
+
:disabled="action.disabled"
|
|
39
|
+
@click="action.onClick"
|
|
40
|
+
>
|
|
41
|
+
<el-icon v-if="action.icon">
|
|
42
|
+
<component :is="action.icon" />
|
|
43
|
+
</el-icon>
|
|
44
|
+
{{ action.label }}
|
|
45
|
+
</el-button>
|
|
46
|
+
</template>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<!-- 工具栏 -->
|
|
51
|
+
<div v-if="showToolbar" class="c-form-toolbar">
|
|
52
|
+
<div class="toolbar-left">
|
|
53
|
+
<!-- 必填字段过滤开关 -->
|
|
54
|
+
<span v-if="showRequiredFilter" class="required-toggle">
|
|
55
|
+
<span class="label">仅显示必填字段</span>
|
|
56
|
+
<el-switch v-model="showRequiredOnly" size="small" />
|
|
57
|
+
</span>
|
|
58
|
+
<!-- 工具栏左侧插槽 -->
|
|
59
|
+
<slot name="toolbar-left"></slot>
|
|
60
|
+
</div>
|
|
61
|
+
<div class="toolbar-right">
|
|
62
|
+
<!-- 工具栏右侧插槽 -->
|
|
63
|
+
<slot name="toolbar-right"></slot>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<!-- 主体内容区域 -->
|
|
68
|
+
<div class="c-form-content">
|
|
69
|
+
<!-- 左侧楼层导航 -->
|
|
70
|
+
<el-tabs
|
|
71
|
+
v-if="showNavTabs"
|
|
72
|
+
:tab-position="navTabsPosition"
|
|
73
|
+
v-model="activeNavTab"
|
|
74
|
+
@tab-click="handleNavTabClick"
|
|
75
|
+
class="nav-tabs"
|
|
76
|
+
>
|
|
77
|
+
<el-tab-pane
|
|
78
|
+
v-for="tab in navTabs"
|
|
79
|
+
:key="tab.name"
|
|
80
|
+
:label="tab.label"
|
|
81
|
+
:name="tab.name"
|
|
82
|
+
/>
|
|
83
|
+
</el-tabs>
|
|
84
|
+
|
|
85
|
+
<!-- 表单区域 -->
|
|
86
|
+
<div class="form-container" ref="formContainerRef">
|
|
87
|
+
<el-form
|
|
88
|
+
:label-width="labelWidth"
|
|
89
|
+
:label-position="labelPosition"
|
|
90
|
+
:model="form"
|
|
91
|
+
:rules="rules"
|
|
92
|
+
>
|
|
93
|
+
<el-collapse v-model="activeNamesModel">
|
|
94
|
+
<!-- 循环渲染折叠面板 -->
|
|
95
|
+
<el-collapse-item
|
|
96
|
+
v-for="section in visibleSections"
|
|
97
|
+
:key="section.name"
|
|
98
|
+
:name="section.name"
|
|
99
|
+
:id="section.id"
|
|
100
|
+
>
|
|
101
|
+
<template #title>
|
|
102
|
+
<span class="section-title">{{ section.title }}</span>
|
|
103
|
+
<el-divider />
|
|
104
|
+
</template>
|
|
105
|
+
|
|
106
|
+
<!-- 特殊区块 - 使用具名插槽 -->
|
|
107
|
+
<slot
|
|
108
|
+
v-if="section.isSpecial"
|
|
109
|
+
:name="`special-${section.name}`"
|
|
110
|
+
:section="section"
|
|
111
|
+
>
|
|
112
|
+
<el-row :gutter="gutter">
|
|
113
|
+
<el-col :span="24">
|
|
114
|
+
<el-empty description="请通过插槽自定义特殊区块内容" />
|
|
115
|
+
</el-col>
|
|
116
|
+
</el-row>
|
|
117
|
+
</slot>
|
|
118
|
+
|
|
119
|
+
<!-- 普通区块 - 循环渲染字段 -->
|
|
120
|
+
<el-row v-else :gutter="gutter">
|
|
121
|
+
<el-col
|
|
122
|
+
v-for="field in section.fieldsConfig"
|
|
123
|
+
:key="field.prop"
|
|
124
|
+
:span="currentFieldSpan"
|
|
125
|
+
>
|
|
126
|
+
<el-form-item
|
|
127
|
+
:label="field.label"
|
|
128
|
+
:prop="field.prop"
|
|
129
|
+
:required="field.required"
|
|
130
|
+
>
|
|
131
|
+
<!-- 下拉选择框 -->
|
|
132
|
+
<el-select
|
|
133
|
+
v-if="field.type === 'select'"
|
|
134
|
+
v-model="form[field.prop]"
|
|
135
|
+
:placeholder="field.placeholder || '请选择'"
|
|
136
|
+
:clearable="field.clearable !== false"
|
|
137
|
+
>
|
|
138
|
+
<el-option
|
|
139
|
+
v-for="opt in field.options"
|
|
140
|
+
:key="opt.value"
|
|
141
|
+
:label="opt.label"
|
|
142
|
+
:value="opt.value"
|
|
143
|
+
/>
|
|
144
|
+
</el-select>
|
|
145
|
+
|
|
146
|
+
<!-- 多行文本框 -->
|
|
147
|
+
<el-input
|
|
148
|
+
v-else-if="field.type === 'textarea'"
|
|
149
|
+
v-model="form[field.prop]"
|
|
150
|
+
type="textarea"
|
|
151
|
+
:rows="field.rows || 3"
|
|
152
|
+
:placeholder="field.placeholder || '请输入'"
|
|
153
|
+
/>
|
|
154
|
+
|
|
155
|
+
<!-- 日期选择器 -->
|
|
156
|
+
<el-date-picker
|
|
157
|
+
v-else-if="field.type === 'date'"
|
|
158
|
+
v-model="form[field.prop]"
|
|
159
|
+
type="date"
|
|
160
|
+
:placeholder="field.placeholder || '选择日期'"
|
|
161
|
+
style="width: 100%"
|
|
162
|
+
/>
|
|
163
|
+
|
|
164
|
+
<!-- 日期时间选择器 -->
|
|
165
|
+
<el-date-picker
|
|
166
|
+
v-else-if="field.type === 'datetime'"
|
|
167
|
+
v-model="form[field.prop]"
|
|
168
|
+
type="datetime"
|
|
169
|
+
:placeholder="field.placeholder || '选择日期时间'"
|
|
170
|
+
style="width: 100%"
|
|
171
|
+
/>
|
|
172
|
+
|
|
173
|
+
<!-- 数字输入框 -->
|
|
174
|
+
<el-input-number
|
|
175
|
+
v-else-if="field.type === 'number'"
|
|
176
|
+
v-model="form[field.prop]"
|
|
177
|
+
:min="field.min"
|
|
178
|
+
:max="field.max"
|
|
179
|
+
:precision="field.precision"
|
|
180
|
+
:step="field.step || 1"
|
|
181
|
+
style="width: 100%"
|
|
182
|
+
/>
|
|
183
|
+
|
|
184
|
+
<!-- 自定义字段 - 使用作用域插槽 -->
|
|
185
|
+
<slot
|
|
186
|
+
v-else-if="field.type === 'custom'"
|
|
187
|
+
:name="`field-${field.prop}`"
|
|
188
|
+
:field="field"
|
|
189
|
+
:form="form"
|
|
190
|
+
>
|
|
191
|
+
<el-input
|
|
192
|
+
v-model="form[field.prop]"
|
|
193
|
+
:placeholder="field.placeholder || '请输入'"
|
|
194
|
+
/>
|
|
195
|
+
</slot>
|
|
196
|
+
|
|
197
|
+
<!-- 默认单行文本输入框 -->
|
|
198
|
+
<el-input
|
|
199
|
+
v-else
|
|
200
|
+
v-model="form[field.prop]"
|
|
201
|
+
:placeholder="field.placeholder || '请输入'"
|
|
202
|
+
/>
|
|
203
|
+
</el-form-item>
|
|
204
|
+
</el-col>
|
|
205
|
+
</el-row>
|
|
206
|
+
</el-collapse-item>
|
|
207
|
+
</el-collapse>
|
|
208
|
+
</el-form>
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
</div>
|
|
212
|
+
</template>
|
|
213
|
+
|
|
214
|
+
<script setup lang="ts">
|
|
215
|
+
import { ref, computed } from "vue";
|
|
216
|
+
import type {
|
|
217
|
+
SectionConfig,
|
|
218
|
+
FormDataType,
|
|
219
|
+
NavTabConfig,
|
|
220
|
+
HeaderAction,
|
|
221
|
+
LayoutOption
|
|
222
|
+
} from "./data";
|
|
223
|
+
|
|
224
|
+
/** 组件 Props */
|
|
225
|
+
interface Props {
|
|
226
|
+
// ===== 基础配置 =====
|
|
227
|
+
/** 表单区块配置数组 */
|
|
228
|
+
sections: SectionConfig[];
|
|
229
|
+
/** 表单数据对象 */
|
|
230
|
+
form: FormDataType;
|
|
231
|
+
/** 激活的折叠面板 name 数组 */
|
|
232
|
+
activeNames?: string[];
|
|
233
|
+
/** 表单验证规则 */
|
|
234
|
+
rules?: any;
|
|
235
|
+
/** 表单标签宽度 */
|
|
236
|
+
labelWidth?: string;
|
|
237
|
+
/** 表单标签位置 */
|
|
238
|
+
labelPosition?: "left" | "right" | "top";
|
|
239
|
+
/** 栅格间距 */
|
|
240
|
+
gutter?: number;
|
|
241
|
+
/** 每个字段占据的栅格数(外部手动控制时使用,会覆盖内部布局切换) */
|
|
242
|
+
fieldSpan?: number;
|
|
243
|
+
|
|
244
|
+
// ===== 顶部标题栏配置 =====
|
|
245
|
+
/** 是否显示顶部标题栏 */
|
|
246
|
+
showHeader?: boolean;
|
|
247
|
+
/** 标题栏标题文本 */
|
|
248
|
+
headerTitle?: string;
|
|
249
|
+
/** 顶部操作按钮 */
|
|
250
|
+
headerActions?: HeaderAction[];
|
|
251
|
+
|
|
252
|
+
// ===== 工具栏配置 =====
|
|
253
|
+
/** 是否显示工具栏 */
|
|
254
|
+
showToolbar?: boolean;
|
|
255
|
+
/** 是否显示必填字段过滤开关 */
|
|
256
|
+
showRequiredFilter?: boolean;
|
|
257
|
+
|
|
258
|
+
// ===== 布局切换配置 =====
|
|
259
|
+
/** 是否显示布局切换器 */
|
|
260
|
+
showLayoutSwitch?: boolean;
|
|
261
|
+
/** 默认布局列数 */
|
|
262
|
+
defaultLayout?: 2 | 3 | 4 | 5;
|
|
263
|
+
/** 可选布局列数 */
|
|
264
|
+
layoutOptions?: Array<2 | 3 | 4 | 5>;
|
|
265
|
+
|
|
266
|
+
// ===== 楼层导航配置 =====
|
|
267
|
+
/** 是否显示楼层导航 */
|
|
268
|
+
showNavTabs?: boolean;
|
|
269
|
+
/** 楼层导航位置 */
|
|
270
|
+
navTabsPosition?: "left" | "right";
|
|
271
|
+
/** 楼层导航配置(不传则自动从 sections 生成) */
|
|
272
|
+
navTabs?: NavTabConfig[];
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
276
|
+
activeNames: () => [],
|
|
277
|
+
labelWidth: "100px",
|
|
278
|
+
labelPosition: "right",
|
|
279
|
+
gutter: 20,
|
|
280
|
+
fieldSpan: undefined,
|
|
281
|
+
|
|
282
|
+
showHeader: false,
|
|
283
|
+
headerTitle: "主档维护",
|
|
284
|
+
headerActions: () => [],
|
|
285
|
+
|
|
286
|
+
showToolbar: false,
|
|
287
|
+
showRequiredFilter: false,
|
|
288
|
+
|
|
289
|
+
showLayoutSwitch: false,
|
|
290
|
+
defaultLayout: 5,
|
|
291
|
+
layoutOptions: () => [2, 3, 4, 5],
|
|
292
|
+
|
|
293
|
+
showNavTabs: false,
|
|
294
|
+
navTabsPosition: "left",
|
|
295
|
+
navTabs: () => []
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
/** 组件 Emits */
|
|
299
|
+
interface Emits {
|
|
300
|
+
(e: "update:activeNames", value: string[]): void;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const emit = defineEmits<Emits>();
|
|
304
|
+
|
|
305
|
+
// ===== 内部状态 =====
|
|
306
|
+
/** 仅显示必填字段开关 */
|
|
307
|
+
const showRequiredOnly = ref(false);
|
|
308
|
+
|
|
309
|
+
/** 当前布局列数 */
|
|
310
|
+
const currentLayout = ref<2 | 3 | 4 | 5>(props.defaultLayout);
|
|
311
|
+
|
|
312
|
+
/** 当前激活的导航标签 */
|
|
313
|
+
const activeNavTab = ref("");
|
|
314
|
+
|
|
315
|
+
/** 表单容器引用 */
|
|
316
|
+
const formContainerRef = ref<HTMLElement | null>(null);
|
|
317
|
+
|
|
318
|
+
// ===== 计算属性 =====
|
|
319
|
+
/** 激活的折叠面板(v-model) */
|
|
320
|
+
const activeNamesModel = computed({
|
|
321
|
+
get: () => props.activeNames,
|
|
322
|
+
set: (val) => emit("update:activeNames", val)
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* 🆕 内部布局选项配置
|
|
327
|
+
*/
|
|
328
|
+
const internalLayoutOptions = computed<LayoutOption[]>(() => {
|
|
329
|
+
return props.layoutOptions.map((col) => ({
|
|
330
|
+
columns: col,
|
|
331
|
+
icon: "|".repeat(col),
|
|
332
|
+
title: `${col}列布局`
|
|
333
|
+
}));
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* 🆕 当前字段 span 值
|
|
338
|
+
* 优先使用外部传入的 fieldSpan,否则根据内部布局计算
|
|
339
|
+
*/
|
|
340
|
+
const currentFieldSpan = computed(() => {
|
|
341
|
+
if (props.fieldSpan !== undefined) {
|
|
342
|
+
return props.fieldSpan;
|
|
343
|
+
}
|
|
344
|
+
return Math.floor(24 / currentLayout.value);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* 🆕 过滤后的区块(根据必填字段过滤)
|
|
349
|
+
*/
|
|
350
|
+
const filteredSections = computed(() => {
|
|
351
|
+
if (!showRequiredOnly.value) {
|
|
352
|
+
return props.sections;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return props.sections.map((section) => {
|
|
356
|
+
if (section.isSpecial) {
|
|
357
|
+
return section;
|
|
358
|
+
}
|
|
359
|
+
return {
|
|
360
|
+
...section,
|
|
361
|
+
fieldsConfig: section.fieldsConfig.filter((field) => field.required)
|
|
362
|
+
};
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* 过滤后的可见区块
|
|
368
|
+
* 根据 section.visible 函数和字段过滤结果判断是否显示
|
|
369
|
+
*/
|
|
370
|
+
const visibleSections = computed(() => {
|
|
371
|
+
return filteredSections.value.filter((section) => {
|
|
372
|
+
// 特殊区块:必填模式下隐藏
|
|
373
|
+
if (section.isSpecial) {
|
|
374
|
+
return !showRequiredOnly.value;
|
|
375
|
+
}
|
|
376
|
+
// 普通区块:有字段才显示
|
|
377
|
+
const hasFields = section.fieldsConfig.length > 0;
|
|
378
|
+
const visibleByFunc = section.visible ? section.visible() : true;
|
|
379
|
+
return hasFields && visibleByFunc;
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* 🆕 内部导航标签配置
|
|
385
|
+
* 如果外部传入则使用外部的,否则自动从 sections 生成
|
|
386
|
+
*/
|
|
387
|
+
const internalNavTabs = computed<NavTabConfig[]>(() => {
|
|
388
|
+
if (props.navTabs && props.navTabs.length > 0) {
|
|
389
|
+
return props.navTabs;
|
|
390
|
+
}
|
|
391
|
+
// 自动从 sections 生成
|
|
392
|
+
return props.sections.map((section) => ({
|
|
393
|
+
name: section.name,
|
|
394
|
+
label: section.title,
|
|
395
|
+
sectionName: section.name
|
|
396
|
+
}));
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
// ===== 方法 =====
|
|
400
|
+
/** 处理楼层导航点击 */
|
|
401
|
+
const handleNavTabClick = (tab: any) => {
|
|
402
|
+
const tabName = tab.paneName || tab.props?.name;
|
|
403
|
+
const tabConfig = internalNavTabs.value.find((t) => t.name === tabName);
|
|
404
|
+
|
|
405
|
+
if (!tabConfig?.sectionName) return;
|
|
406
|
+
|
|
407
|
+
// 展开对应的 collapse section
|
|
408
|
+
if (!activeNamesModel.value.includes(tabConfig.sectionName)) {
|
|
409
|
+
activeNamesModel.value = [...activeNamesModel.value, tabConfig.sectionName];
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// 滚动到对应区域
|
|
413
|
+
setTimeout(() => {
|
|
414
|
+
const section = props.sections.find(
|
|
415
|
+
(s) => s.name === tabConfig.sectionName
|
|
416
|
+
);
|
|
417
|
+
if (section?.id) {
|
|
418
|
+
const sectionEl = document.getElementById(section.id);
|
|
419
|
+
if (sectionEl) {
|
|
420
|
+
sectionEl.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}, 150);
|
|
424
|
+
};
|
|
425
|
+
</script>
|
|
426
|
+
|
|
427
|
+
<style scoped lang="scss">
|
|
428
|
+
@import "./index.scss";
|
|
429
|
+
</style>
|