matrix_components 2.0.360 → 2.0.362

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,14 @@
1
1
  # **组件库2.0**
2
2
  组件使用示例参考dist/ComponentDemo
3
3
 
4
+ ```
5
+ version:2.0.362
6
+ 2025-12-01
7
+ 更新日志:
8
+ 1.重构NsForm组件,支持动态组件
9
+ 2.增加NsFormTitle动态表单标题组件,用法参考:FormDemo.vue
10
+ ```
11
+
4
12
  ```
5
13
  version:2.0.360
6
14
  2025-11-29
@@ -2,306 +2,329 @@
2
2
  <div class="demo-container">
3
3
  <div class="control-panel">
4
4
  <h3>动态表单组件演示</h3>
5
-
6
5
  <!-- 配置文件选择 -->
7
- <!-- <div class="config-section">
8
- <h4>方式一:选择配置文件</h4>
9
- <el-select
10
- v-model="selectedConfig"
11
- placeholder="请选择表单配置"
12
- @change="loadConfig"
13
- class="config-select"
14
- >
15
- <el-option
16
- v-for="config in configOptions"
17
- :key="config.value"
18
- :label="config.label"
19
- :value="config.value"
20
- />
21
- </el-select>
22
- <el-button @click="reloadConfig" :disabled="!selectedConfig">重新加载</el-button>
23
- </div> -->
24
-
25
- <!-- 文件上传方式 -->
26
- <div class="upload-section">
27
- <h4>上传配置文件</h4>
28
- <input
29
- type="file"
30
- @change="importConfig(($event.target as any)?.files?.[0])"
31
- accept=".js,.json"
32
- />
33
- <el-button @click="clearFile" :disabled="!uploadedFile">清除文件</el-button>
34
- </div>
35
-
36
- <!-- 表单操作 -->
37
- <div class="action-section">
38
- <h4>表单操作</h4>
39
- <el-button type="primary" @click="validateForm" :disabled="!formLoaded">验证表单</el-button>
40
- <el-button @click="resetForm" :disabled="!formLoaded">重置表单</el-button>
41
- <el-button type="info" @click="toggleFormData" :disabled="!formLoaded">
42
- {{ showFormData ? '隐藏' : '显示' }}表单数据
43
- </el-button>
44
- <el-button type="success" @click="exportFormData" :disabled="!formLoaded">导出数据</el-button>
45
- </div>
46
- </div>
47
-
48
- <!-- 表单容器 -->
49
- <div class="form-container" v-if="formLoaded">
50
- <NsForm
51
- ref="formRef"
52
- v-model="formData"
53
- :form-items="formItems"
54
- :rules="formRules"
55
- :options-data="optionsData"
56
- label-width="120px"
57
- :gutter="20"
58
- :clear-validate-on-init="true"
59
- @validate="handleValidate"
60
- @submit="handleSubmit"
61
- @reset="handleReset"
62
- >
63
- <!-- 自定义插槽示例 -->
64
- <template #customSlot="{ item, formData }">
65
- <el-input
66
- v-model="formData[item.prop]"
67
- placeholder="这是自定义插槽内容"
68
- prefix-icon="User"
69
- />
70
- </template>
71
-
72
- <!-- 表单操作按钮 -->
73
- <template #footer="{ validate, resetFields }">
74
- <el-form-item>
75
- <el-button type="primary" @click="validate">提交表单</el-button>
76
- <el-button @click="resetFields">重置表单</el-button>
77
- </el-form-item>
78
- </template>
79
- </NsForm>
80
- </div>
81
-
82
- <!-- 空状态 -->
83
- <div class="empty-state" v-else>
84
- <el-empty description="请选择或上传表单配置文件" />
6
+ <el-form :model="state" ref="formRef" label-position="left">
7
+ <el-button type="primary" @click="getFormData">获取表单数据</el-button>
8
+ <br>
9
+ <span style="color: red">
10
+ 结果:{{ state.formData }}
11
+ </span>
12
+ <br>
13
+ <br>
14
+ <NsFormTitle title="模型参数">
15
+ <NsForm
16
+ ref="row1Ref"
17
+ :readOnly="state.readOnly"
18
+ backgroundColor="#fff"
19
+ :model="state.model"
20
+ :rows="state.rows"
21
+ formPropKey="rows"
22
+ labelColor="#606266"
23
+ labelWidth="150"
24
+ gapH="20px"
25
+ gapV="10px"
26
+ ></NsForm>
27
+ </NsFormTitle>
28
+ <NsFormTitle title="视频配置">
29
+ <NsForm
30
+ ref="row2Ref"
31
+ :readOnly="state.readOnly"
32
+ backgroundColor="#fff"
33
+ :model="state.model"
34
+ :rows="state.rows2"
35
+ formPropKey="rows2"
36
+ labelColor="#606266"
37
+ labelWidth="150"
38
+ gapH="20px"
39
+ gapV="10px"
40
+ ></NsForm>
41
+ </NsFormTitle>
42
+ <NsFormTitle title="结果保存">
43
+ <NsForm
44
+ ref="row3Ref"
45
+ :readOnly="state.readOnly"
46
+ backgroundColor="#fff"
47
+ :model="state.model"
48
+ :rows="state.rows3"
49
+ formPropKey="rows3"
50
+ labelColor="#606266"
51
+ labelWidth="150"
52
+ gapH="20px"
53
+ gapV="10px"
54
+ ></NsForm>
55
+ </NsFormTitle>
56
+ </el-form>
85
57
  </div>
86
-
87
- <!-- 表单数据显示 -->
88
- <el-card v-if="showFormData && formLoaded" class="data-card">
89
- <template #header>
90
- <div class="card-header">
91
- <span>表单数据</span>
92
- <el-button type="text" @click="copyFormData">复制数据</el-button>
93
- </div>
94
- </template>
95
- <pre class="form-data-display">{{ JSON.stringify(formData, null, 2) }}</pre>
96
- </el-card>
97
58
  </div>
98
59
  </template>
99
60
 
100
61
  <script setup lang="ts">
101
- import { ElMessage } from 'element-plus'
102
- import { nextTick, reactive, ref } from 'vue'
103
-
104
- // 表单引用
105
- const formRef = ref()
106
-
107
- // 状态管理
108
- const formLoaded = ref(false)
109
- const showFormData = ref(false)
110
- const selectedConfig = ref('')
111
- const uploadedFile = ref()
112
-
113
- // 表单数据
114
- const formData: any = reactive({})
115
- const formItems: any = ref([])
116
- const formRules: any = ref({})
117
- const optionsData: any = reactive({})
118
-
119
- // 加载配置文件
120
- async function loadConfig() {
121
- if (!selectedConfig.value) return
122
-
123
- try {
124
- // 动态导入配置文件
125
- const configModule = (await import(`@/views/ExampleFormConfig.js`)) as { formConfig: any; default: any }
126
- const config = configModule.formConfig || configModule.default
127
-
128
- if (config) {
129
- initializeForm(config)
130
- ElMessage.success('配置加载成功')
131
- } else {
132
- ElMessage.error('配置文件格式错误')
133
- }
134
- } catch (error) {
135
- console.error('加载配置失败:', error)
136
- ElMessage.error('配置文件加载失败')
137
- }
138
- }
139
-
140
- // 导入配置文件
141
- function importConfig(file: File) {
142
- if (!file) return
143
-
144
- uploadedFile.value = file
145
- const reader = new FileReader()
146
-
147
- reader.onload = (e) => {
148
- try {
149
- const content = e.target?.result as string
150
- let config
151
-
152
- if (file.name.endsWith('.json')) {
153
- config = JSON.parse(content)
154
- } else if (file.name.endsWith('.js')) {
155
- // 简单的JS文件解析(实际项目中可能需要更复杂的处理)
156
- const configMatch = content.match(/export\s+(?:const|default)\s+\w*\s*=\s*({[\s\S]*});?/)
157
- if (configMatch) {
158
- config = eval(`(${configMatch[1]})`)
62
+ import { ElInput, ElMessage, ElRadioGroup } from "element-plus";
63
+ import { reactive, ref } from 'vue';
64
+
65
+ const props = defineProps({
66
+ readOnly: {
67
+ type: Boolean,
68
+ default: false,
69
+ },
70
+ row: {
71
+ type: Object,
72
+ default: () => ({}),
73
+ },
74
+ });
75
+
76
+ const formRef = ref();
77
+ const row1Ref = ref();
78
+ const row2Ref = ref();
79
+ const row3Ref = ref();
80
+ const state = reactive({
81
+ formData: {},
82
+ readOnly: props.readOnly,
83
+ model: props.readOnly ? "" : "vertical",
84
+ // 第一种方式,在初始化前设置数据
85
+ rows: [
86
+ [
87
+ {
88
+ key: "confidence",
89
+ label: "置信度",
90
+ value: "",
91
+ component: ElInput,
92
+ params: {
93
+ "v-length.range": {
94
+ min: 0,
95
+ max: 1,
96
+ maxLength: 3 },
97
+ rules: [
98
+ {
99
+ required: true,
100
+ message: "请输入",
101
+ },
102
+ ],
103
+ },
104
+ },
105
+ {
106
+ key: "iou",
107
+ label: "交并比",
108
+ value: "",
109
+ component: ElInput,
110
+ params: {
111
+ "v-length.range": {
112
+ min: 0,
113
+ max: 1,
114
+ maxLength: 3 },
115
+ rules: [
116
+ {
117
+ required: true,
118
+ message: "请输入",
119
+ },
120
+ ],
121
+ },
122
+ },
123
+ ],
124
+ ],
125
+ rows2: [
126
+ [
127
+ {
128
+ key: "timeInterval",
129
+ label: "时间间隔(秒)",
130
+ value: "",
131
+ component: ElInput,
132
+ params: {
133
+ "v-length.range": {
134
+ min: 0,
135
+ max: 6000,
136
+ int: true
137
+ },
138
+ rules: [
139
+ {
140
+ required: true,
141
+ message: "请输入",
142
+ },
143
+ ],
144
+ },
145
+ },
146
+ {
147
+ key: "stuck_threshold",
148
+ label: "视频帧卡顿(秒)",
149
+ value: "",
150
+ component: ElInput,
151
+ params: {
152
+ "v-length.range": {
153
+ min: 0,
154
+ max: 1000,
155
+ int: true
156
+ },
157
+ rules: [
158
+ {
159
+ required: true,
160
+ message: "请输入",
161
+ },
162
+ ],
163
+ },
164
+ },
165
+ ],
166
+ [
167
+ {
168
+ key: "max_retries",
169
+ label: "最大重连次数",
170
+ value: "",
171
+ component: ElInput,
172
+ params: {
173
+ "v-length.range": {
174
+ min: 0,
175
+ max: 100,
176
+ int: true
177
+ },
178
+ rules: [
179
+ {
180
+ required: true,
181
+ message: "请输入",
182
+ },
183
+ ],
184
+ },
185
+ },
186
+ {
187
+ value: ' '
188
+ }
189
+ ],
190
+ ],
191
+ rows3: [
192
+ [
193
+ {
194
+ key: "save_video",
195
+ label: "是否保存视频",
196
+ value: '1',
197
+ component: ElRadioGroup,
198
+ params: {
199
+ rules: [
200
+ {
201
+ required: true,
202
+ message: "请选择",
203
+ trigger: 'change'
204
+ },
205
+ ],
206
+ options: [
207
+ {
208
+ value: '1',
209
+ label: '是',
210
+ },
211
+ {
212
+ value: '0',
213
+ label: '否',
214
+ },
215
+ ]
216
+ },
217
+ },
218
+ {
219
+ key: "pre_buffer_second",
220
+ label: "帧前缓存(秒)",
221
+ value: "",
222
+ component: ElInput,
223
+ params: {
224
+ "v-length.range": {
225
+ min: 0,
226
+ max: 1000,
227
+ int: true
228
+ },
229
+ rules: [
230
+ {
231
+ required: true,
232
+ message: "请输入",
233
+ },
234
+ ],
235
+ },
236
+ },
237
+ ],
238
+ [
239
+ {
240
+ key: "det_area_mode",
241
+ label: "检测区域工作模式",
242
+ value: 'normal',
243
+ component: ElRadioGroup,
244
+ events: {
245
+ change: detAreaModeChange
246
+ },
247
+ params: {
248
+ rules: [
249
+ {
250
+ required: true,
251
+ message: "请选择",
252
+ trigger: 'change'
253
+ },
254
+ ],
255
+ options: [
256
+ {
257
+ value: 'normal',
258
+ label: '常规检测(normal)',
259
+ },
260
+ {
261
+ value: 'abnormal',
262
+ label: '非常规检测(abnormal)',
263
+ },
264
+ ]
265
+ },
266
+ },
267
+ ],
268
+ ],
269
+ });
270
+
271
+ function detAreaModeChange(value: any) {
272
+ if(value === 'abnormal') {
273
+ if(state.rows3?.length && state.rows3[state.rows3.length - 1]?.[0]?.key === 'det_area_json'){
274
+ state.rows3.pop()
159
275
  }
160
- }
161
-
162
- if (config) {
163
- initializeForm(config)
164
- selectedConfig.value = '' // 清除下拉选择
165
- ElMessage.success('配置文件导入成功')
166
- } else {
167
- ElMessage.error('配置文件格式错误')
168
- }
169
- } catch (error) {
170
- console.error('解析配置文件失败:', error)
171
- ElMessage.error('配置文件解析失败')
276
+ state.rows3.push([{
277
+ key: "det_area_json",
278
+ label: "感兴趣区域",
279
+ value: '',
280
+ component: ElInput,
281
+ span: 6,
282
+ events: {
283
+ change: (value) => getAreasHandler(value),
284
+ },
285
+ params: {
286
+ rules: [
287
+ {
288
+ required: true,
289
+ message: "请输入",
290
+ trigger: 'change'
291
+ },
292
+ ],
293
+ },
294
+ }, {value: ' '}])
295
+ }else {
296
+ state.rows3.pop()
172
297
  }
173
- }
174
-
175
- reader.readAsText(file)
176
- }
177
-
178
- // 清除上传的文件
179
- function clearFile() {
180
- uploadedFile.value = null
181
- formLoaded.value = false
182
- showFormData.value = false
183
-
184
- // 清空表单数据
185
- Object.keys(formData).forEach(key => {
186
- delete formData[key]
187
- })
188
- formItems.value = []
189
- formRules.value = {}
190
- Object.keys(optionsData).forEach(key => {
191
- delete optionsData[key]
192
- })
193
298
  }
194
299
 
195
- // 初始化表单
196
- function initializeForm(config: any) {
197
- // 清空现有数据
198
- Object.keys(formData).forEach(key => {
199
- delete formData[key]
200
- })
201
- Object.keys(optionsData).forEach(key => {
202
- delete optionsData[key]
203
- })
204
-
205
- // 设置新数据
206
- if (config.formData) {
207
- Object.assign(formData, config.formData)
208
- }
209
-
210
- if (config.formItems) {
211
- formItems.value = config.formItems
212
- }
213
-
214
- if (config.formRules) {
215
- formRules.value = config.formRules
216
- }
217
-
218
- if (config.optionsData) {
219
- Object.assign(optionsData, config.optionsData)
220
- }
221
-
222
- formLoaded.value = true
223
-
224
- // 在下一帧清除表单验证状态
225
- nextTick(() => {
226
- if (formRef.value) {
227
- formRef.value.clearValidate()
228
- }
229
- })
300
+ function getAreasHandler(value: any) {
301
+ state.rows3[state.rows3.length - 1][0].value = value
230
302
  }
231
303
 
232
- // 验证表单
233
- async function validateForm() {
234
- if (!formRef.value) return
235
-
236
- try {
237
- const valid = await formRef.value.validate()
238
- if (valid) {
239
- ElMessage.success('表单验证通过')
304
+ /**
305
+ * 保存
306
+ */
307
+ async function getFormData() {
308
+ try {
309
+ await formRef.value.validate();
310
+ } catch (error: any) {
311
+ console.log(error);
312
+ ElMessage.error('表单校验失败');
313
+ state.formData = {}
314
+ return false;
240
315
  }
241
- } catch (error) {
242
- ElMessage.error('表单验证失败')
243
- console.error('表单验证失败:', error)
244
- }
245
- }
246
-
247
- // 重置表单
248
- function resetForm() {
249
- if (!formRef.value) return
250
- formRef.value.resetFields()
251
- ElMessage.info('表单已重置')
252
- }
253
-
254
- // 切换表单数据显示
255
- function toggleFormData() {
256
- showFormData.value = !showFormData.value
257
- }
258
-
259
- // 导出表单数据
260
- function exportFormData() {
261
- const dataStr = JSON.stringify(formData, null, 2)
262
- const blob = new Blob([dataStr], { type: 'application/json' })
263
- const url = URL.createObjectURL(blob)
264
- const a = document.createElement('a')
265
- a.href = url
266
- a.download = 'form-data.json'
267
- a.click()
268
- URL.revokeObjectURL(url)
269
- ElMessage.success('数据导出成功')
270
- }
271
-
272
- // 复制表单数据
273
- async function copyFormData() {
274
- try {
275
- await navigator.clipboard.writeText(JSON.stringify(formData, null, 2))
276
- ElMessage.success('数据已复制到剪贴板')
277
- } catch (error) {
278
- ElMessage.error('复制失败')
279
- console.error('复制失败:', error)
280
- }
281
- }
282
-
283
- // 表单事件处理
284
- function handleValidate(valid: boolean, data: any) {
285
- console.log('表单验证结果:', valid, data)
286
- }
287
-
288
- function handleSubmit(data: any) {
289
- console.log('表单提交:', data)
290
- ElMessage.success('表单提交成功')
291
- }
292
-
293
- function handleReset(data: any) {
294
- console.log('表单重置:', data)
316
+ const data1 = row1Ref.value?.getKeyValuePairs?.(state.rows);
317
+ const data2 = row2Ref.value?.getKeyValuePairs?.(state.rows2);
318
+ const data3 = row3Ref.value?.getKeyValuePairs?.(state.rows3);
319
+ const data = { ...data1, ...data2, ...data3 }
320
+ state.formData = data;
321
+ ElMessage.success('表单校验成功');
322
+ return data;
295
323
  }
296
324
 
297
- // 初始化时加载默认配置
298
- nextTick(() => {
299
- selectedConfig.value = 'ExampleFormConfig'
300
- loadConfig()
301
- })
302
325
  </script>
303
326
 
304
- <style scoped lang="scss">
327
+ <style lang="scss" scoped>
305
328
  .demo-container {
306
329
  padding: 20px;
307
330
  max-width: 1400px;
@@ -90,12 +90,12 @@
90
90
  <span>当前: "{{ rangeInput4 }}%"</span>
91
91
  </div>
92
92
 
93
- <h3>整数范围模式 - v-length.range (integerOnly)</h3>
93
+ <h3>整数范围模式 - v-length.range (int)</h3>
94
94
  <div class="input-group">
95
95
  <label>整数范围 0-100 (仅整数):</label>
96
96
  <el-input
97
97
  v-model="rangeInput5"
98
- v-length.range="{ min: 0, max: 100, integerOnly: true }"
98
+ v-length.range="{ min: 0, max: 100, int: true }"
99
99
  placeholder="仅能输入0-100之间的整数"
100
100
  style="width: 300px;"
101
101
  />
@@ -106,7 +106,7 @@
106
106
  <label>年龄范围 1-120 (仅整数):</label>
107
107
  <el-input
108
108
  v-model="rangeInput6"
109
- v-length.range="{ min: 1, max: 120, integerOnly: true, maxLength: 3 }"
109
+ v-length.range="{ min: 1, max: 120, int: true, maxLength: 3 }"
110
110
  placeholder="输入年龄1-120岁"
111
111
  style="width: 300px;"
112
112
  />
@@ -117,7 +117,7 @@
117
117
  <label>负整数范围 -10到10 (仅整数):</label>
118
118
  <el-input
119
119
  v-model="rangeInput7"
120
- v-length.range="{ min: -10, max: 10, integerOnly: true }"
120
+ v-length.range="{ min: -10, max: 10, int: true }"
121
121
  placeholder="输入-10到10之间的整数"
122
122
  style="width: 300px;"
123
123
  />