starfish-form-custom 1.0.46 → 1.0.48

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.
@@ -1,26 +1,49 @@
1
1
  <template>
2
- <div class="starfish-formitem"
3
- :class="{ formCover: drag, 'starfish-vertical': labelalign != 'top', [item.data.csslist?.join(' ')]: !!item.data.csslist}"
2
+ <div
3
+ class="starfish-formitem"
4
+ :class="{
5
+ formCover: drag,
6
+ 'starfish-vertical': labelalign != 'top',
7
+ [item.data.csslist?.join(' ')]: !!item.data.csslist,
8
+ }"
4
9
  :data-control-type="item.ControlType"
5
10
  :data-id="item.id"
11
+ >
12
+ <div
13
+ class="label"
14
+ :class="'label_' + labelalign"
15
+ :style="{ width: labelWidth + 'px' }"
6
16
  >
7
- <div class="label" :class="'label_' + labelalign" :style="{width: labelWidth + 'px'}">
8
- <span v-if="item.data.required && !readonly && item.data.state !== 'readonly'" class="item_require">*</span>
9
- <label>{{ item.data.label }}{{suffix}}</label>
10
- <el-tooltip v-if="item.data.tip && !readonly" class="item" effect="dark" :content="item.data.tip" placement="top">
17
+ <span
18
+ v-if="item.data.required && !readonly && item.data.state !== 'readonly'"
19
+ class="item_require"
20
+ >*</span
21
+ >
22
+ <label>{{ item.data.label }}{{ suffix }}</label>
23
+ <el-tooltip
24
+ v-if="item.data.tip && !readonly"
25
+ class="item"
26
+ effect="dark"
27
+ :content="item.data.tip"
28
+ placement="top"
29
+ >
11
30
  <span class="tip iconfontui icon-tishi"></span>
12
31
  </el-tooltip>
13
32
  </div>
14
- <div class="control" :style="{marginLeft: labelalign != 'top'?labelWidth + 'px': ''}">
33
+ <div
34
+ class="control"
35
+ :style="{ marginLeft: labelalign != 'top' ? labelWidth + 'px' : '' }"
36
+ >
15
37
  <div class="rich-text-editor" @click.stop>
16
- <QuillEditor
38
+ <QuillEditor
17
39
  ref="quillEditorRef"
18
- theme="snow"
19
- :content="content"
40
+ theme="snow"
41
+ v-model:content="internalContent"
20
42
  @update:content="handleContentChange"
21
- toolbar="full"
22
- :read-only="isReadonly"
23
- class="editor-content"
43
+ @ready="onEditorReady"
44
+ :toolbar="toolbarOptions"
45
+ :read-only="isReadonly || drag"
46
+ class="editor-content"
24
47
  />
25
48
  </div>
26
49
  </div>
@@ -28,53 +51,185 @@
28
51
  </template>
29
52
 
30
53
  <script lang="ts">
31
- import { defineComponent, ref, computed, watch } from "vue";
32
- import { QuillEditor } from '@vueup/vue-quill'
33
- import '@vueup/vue-quill/dist/vue-quill.snow.css';
54
+ import { defineComponent, ref, computed, watch, nextTick } from "vue";
55
+ import { QuillEditor } from "@vueup/vue-quill";
56
+ import "@vueup/vue-quill/dist/vue-quill.snow.css";
34
57
  import { getFormConfig } from "../../utils/fieldConfig";
35
58
  import fieldProps from "../../utils/fieldProps";
36
59
  import { useWatch } from "../../utils/customHooks";
60
+ import type { Delta } from "@vueup/vue-quill";
37
61
 
38
62
  export default defineComponent({
39
63
  ControlType: "RichText",
40
64
  nameCn: "富文本",
41
65
  icon: "icon-textEdit",
42
- formConfig: getFormConfig("RichText", [{ fieldName: "state", component: "Radio" }]),
66
+ formConfig: getFormConfig("RichText", [
67
+ { fieldName: "state", component: "Radio" },
68
+ ]),
43
69
  props: {
44
70
  ...fieldProps,
45
71
  },
46
72
  components: {
47
- QuillEditor
73
+ QuillEditor,
48
74
  },
49
75
  setup(props) {
50
76
  useWatch(props);
51
- const quillEditorRef = ref();
52
- const content = ref(props.data[props.item.data.fieldName] || props.item.data.default || '');
53
-
54
- // 计算只读状态
55
- const isReadonly = computed(() =>
56
- props.readonly || props.item.data.state === 'readonly'
77
+ const quillEditorRef = ref<InstanceType<typeof QuillEditor>>();
78
+ const internalContent = ref<string | Delta | null>(null);
79
+ const isEditorReady = ref(false);
80
+
81
+ const toolbarOptions = [
82
+ ["bold", "italic", "underline", "strike"],
83
+ ["blockquote", "code-block"],
84
+ ["link", "image", "formula"],
85
+ [{ header: 1 }, { header: 2 }],
86
+ [{ list: "ordered" }, { list: "bullet" }, { list: "check" }],
87
+ [{ script: "sub" }, { script: "super" }],
88
+ [{ indent: "-1" }, { indent: "+1" }],
89
+ [{ direction: "rtl" }],
90
+ [{ size: ["small", false, "large", "huge"] }],
91
+ [{ header: [1, 2, 3, 4, 5, 6, false] }],
92
+ [{ color: [] }, { background: [] }],
93
+ [{ font: [] }],
94
+ [{ align: [] }],
95
+ ["clean"],
96
+ ];
97
+
98
+ const isReadonly = computed(
99
+ () => props.readonly || props.item.data.state === "readonly"
57
100
  );
58
-
101
+
102
+ // 编辑器就绪回调
103
+ const onEditorReady = () => {
104
+ isEditorReady.value = true;
105
+ initContentFromProps();
106
+ };
107
+
108
+ // 从 props 初始化内容(支持字符串和 Delta 对象)
109
+ const initContentFromProps = () => {
110
+ if (!isEditorReady.value) return;
111
+
112
+ const fieldName = props.item.data.fieldName;
113
+ const rawValue = props.data[fieldName];
114
+ const defaultValue = props.item.data.default || "";
115
+
116
+ // 处理空值
117
+ if (rawValue === undefined || rawValue === null || rawValue === "") {
118
+ internalContent.value = defaultValue;
119
+ return;
120
+ }
121
+
122
+ // 情况1:如果是 Delta 对象({ ops: [...] })
123
+ if (
124
+ rawValue &&
125
+ typeof rawValue === "object" &&
126
+ Array.isArray(rawValue.ops)
127
+ ) {
128
+ internalContent.value = rawValue;
129
+ return;
130
+ }
131
+
132
+ // 情况2:如果是字符串
133
+ if (typeof rawValue === "string") {
134
+ // 尝试解析是否为 JSON 字符串的 Delta
135
+ if (rawValue.trim().startsWith("{") || rawValue.trim().startsWith("[")) {
136
+ try {
137
+ const parsed = JSON.parse(rawValue);
138
+ if (parsed && Array.isArray(parsed.ops)) {
139
+ internalContent.value = parsed;
140
+ } else {
141
+ // 不是 Delta JSON,当作普通 HTML 字符串
142
+ internalContent.value = rawValue;
143
+ }
144
+ } catch {
145
+ // 解析失败,当作普通 HTML 字符串
146
+ internalContent.value = rawValue;
147
+ }
148
+ } else {
149
+ // 普通字符串
150
+ internalContent.value = rawValue;
151
+ }
152
+ return;
153
+ }
154
+
155
+ // 情况3:其他类型,使用默认值
156
+ internalContent.value = defaultValue;
157
+ };
158
+
59
159
  // 处理内容变化
60
- const handleContentChange = (value: string) => {
61
- content.value = value;
62
- // 更新父组件数据
63
- props.data[props.item.data.fieldName] = value;
160
+ const handleContentChange = (value: string | Delta) => {
161
+ internalContent.value = value;
162
+
163
+ if (!quillEditorRef.value) return;
164
+
165
+ const quill = quillEditorRef.value.getQuill();
166
+ if (!quill) return;
167
+
168
+ const fieldName = props.item.data.fieldName;
169
+ const htmlContent = quill.root.innerHTML;
170
+ props.data[fieldName] = htmlContent;
64
171
  };
65
-
66
- // 监听外部数据变化
67
- watch(() => props.data[props.item.data.fieldName], (newValue) => {
68
- if (newValue !== content.value) {
69
- content.value = newValue || '';
172
+
173
+ // 监听外部数据变化(父组件更新时)
174
+ watch(
175
+ () => props.data[props.item.data.fieldName],
176
+ (newValue) => {
177
+ // 避免循环更新
178
+ if (!isEditorReady.value) return;
179
+
180
+ // 转换为内部表示
181
+ let newInternalValue: string | Delta | null = null;
182
+
183
+ if (newValue === undefined || newValue === null || newValue === "") {
184
+ newInternalValue = props.item.data.default || "";
185
+ } else if (
186
+ newValue &&
187
+ typeof newValue === "object" &&
188
+ Array.isArray(newValue.ops)
189
+ ) {
190
+ // Delta 对象
191
+ newInternalValue = newValue;
192
+ } else if (typeof newValue === "string") {
193
+ // 字符串(可能是 HTML 或 JSON 字符串)
194
+ if (newValue.trim().startsWith("{") || newValue.trim().startsWith("[")) {
195
+ try {
196
+ const parsed = JSON.parse(newValue);
197
+ if (parsed && Array.isArray(parsed.ops)) {
198
+ newInternalValue = parsed;
199
+ } else {
200
+ newInternalValue = newValue;
201
+ }
202
+ } catch {
203
+ newInternalValue = newValue;
204
+ }
205
+ } else {
206
+ newInternalValue = newValue;
207
+ }
208
+ }
209
+ // 只有值真正变化时才更新
210
+ if (JSON.stringify(newInternalValue) !== JSON.stringify(internalContent.value)) {
211
+ internalContent.value = newInternalValue;
212
+ }
213
+ },
214
+ { immediate: true }
215
+ );
216
+
217
+ // 监听编辑器就绪状态
218
+ watch(isEditorReady, (ready) => {
219
+ if (ready) {
220
+ nextTick(() => {
221
+ initContentFromProps();
222
+ });
70
223
  }
71
224
  });
72
-
225
+
73
226
  return {
74
- content,
227
+ content: internalContent,
75
228
  quillEditorRef,
76
229
  handleContentChange,
77
- isReadonly
230
+ onEditorReady,
231
+ toolbarOptions,
232
+ isReadonly,
78
233
  };
79
234
  },
80
235
  });
@@ -87,11 +242,9 @@ export default defineComponent({
87
242
  z-index: 1;
88
243
  }
89
244
 
90
- // 确保编辑器层级
91
245
  :deep(.editor-content) {
92
246
  min-height: 200px;
93
-
94
- // 确保工具栏正常显示
247
+
95
248
  .ql-toolbar {
96
249
  z-index: 100;
97
250
  background: white;
@@ -100,7 +253,7 @@ export default defineComponent({
100
253
  border-top-right-radius: 4px;
101
254
  position: relative;
102
255
  }
103
-
256
+
104
257
  .ql-container {
105
258
  border: 1px solid #ccc;
106
259
  border-top: none;
@@ -111,7 +264,6 @@ export default defineComponent({
111
264
  }
112
265
  }
113
266
 
114
- // 防止拖拽区域覆盖编辑器
115
267
  :deep(.shape) {
116
268
  .rich-text-editor {
117
269
  pointer-events: auto !important;
@@ -23,9 +23,14 @@ const validateNumberD2 = `(rule, value, callback) => {
23
23
  // 电话号码校验规则
24
24
  const validatePhone = `(rule, value, callback) => {
25
25
  if (value === "" || value == null) {
26
- callback(new Error("请输入"));
27
- } else if (!/^1(?:3\d|4[4-9]|5[0-35-9]|6[67]|7[013-8]|8\d|9\d)\d{8}$/.test(value)) {
28
- callback(new Error("请输入正确的电话号码"));
26
+ callback(new Error("请输入电话号码"));
27
+ return;
28
+ }
29
+ var re = /^1[3,4,5,6,7,8,9][0-9]{9}$/;
30
+
31
+ if (!re.test(value)) {
32
+ callback(new Error("请输入正确的11位手机号码"));
33
+ return;
29
34
  }
30
35
  callback();
31
36
  }`;
@@ -34,7 +39,7 @@ const validatePhone = `(rule, value, callback) => {
34
39
  const validateIdCard = `(rule, value, callback) => {
35
40
  if (value === "" || value == null) {
36
41
  callback(new Error("请输入"));
37
- } else if (!/^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/.test(value)) {
42
+ } else if (!/^[1-9]\\d{5}(18|19|20)\\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$/.test(value)) {
38
43
  callback(new Error("请输入正确的身份证号"));
39
44
  }
40
45
  callback();
@@ -33,6 +33,7 @@
33
33
  style="width: 320px;"
34
34
  :placeholder="item.data.placeholder"
35
35
  v-if="drag"
36
+ clearable
36
37
  :size="size"
37
38
  :disabled="item.data.state === 'disabled' || item.data.state === 'readonly'"
38
39
  >
@@ -43,13 +44,17 @@
43
44
  :value="items.value"
44
45
  />
45
46
  </el-select>
46
- <span v-if="!drag && (item.data.state === 'readonly' || readonly)">{{ data[item.data.fieldName] || '--' }}</span>
47
+ <!-- 只读状态显示对应的 label -->
48
+ <span v-if="!drag && (item.data.state === 'readonly' || readonly)">
49
+ {{ getDisplayText() }}
50
+ </span>
47
51
  <el-select
48
52
  v-model="data[item.data.fieldName]"
49
53
  style="width: 320px;"
50
54
  :placeholder="item.data.placeholder"
51
55
  v-else-if="!drag"
52
56
  :size="size"
57
+ clearable
53
58
  :disabled="item.data.state === 'disabled' || item.data.state === 'readonly'"
54
59
  @focus="execFunc('onFocus')"
55
60
  @blur="execFunc('onBlur')"
@@ -64,6 +69,7 @@
64
69
  </div>
65
70
  </div>
66
71
  </template>
72
+
67
73
  <script lang="ts">
68
74
  import {
69
75
  defineComponent,
@@ -73,6 +79,7 @@ import {
73
79
  import { getFormConfig } from "../../utils/fieldConfig";
74
80
  import fieldProps from "../../utils/fieldProps";
75
81
  import { useWatch } from "../../utils/customHooks";
82
+
76
83
  export default defineComponent({
77
84
  ControlType: "Selected", // 必须与文件名匹配
78
85
  nameCn: "选择器",
@@ -99,5 +106,22 @@ export default defineComponent({
99
106
  },
100
107
  };
101
108
  },
109
+ methods: {
110
+ getDisplayText() {
111
+ const fieldValue = this.data[this.item.data.fieldName];
112
+ const items = this.item.data.itemConfig?.items || [];
113
+
114
+ // 如果没有值,显示默认的 "--"
115
+ if (fieldValue === undefined || fieldValue === null || fieldValue === '') {
116
+ return this.item.data.placeholder || '--';
117
+ }
118
+
119
+ // 查找对应的 label
120
+ const selectedItem = items.find(item => item.value === fieldValue);
121
+
122
+ // 如果找到对应的 label,显示 label,否则显示原始值
123
+ return selectedItem ? selectedItem.label : fieldValue;
124
+ }
125
+ }
102
126
  });
103
- </script>
127
+ </script>
@@ -44,8 +44,9 @@
44
44
  :value="items.value"
45
45
  />
46
46
  </el-select>
47
+ <!-- 只读状态显示对应的 label -->
47
48
  <span v-else-if="!drag && (item.data.state === 'readonly' || readonly)">
48
- {{ getReadonlyDisplayValue() }}
49
+ {{ getDisplayText() }}
49
50
  </span>
50
51
  <el-select
51
52
  v-else
@@ -78,6 +79,7 @@ import {
78
79
  import { getFormConfig } from "../../utils/fieldConfig";
79
80
  import fieldProps from "../../utils/fieldProps";
80
81
  import { useWatch } from "../../utils/customHooks";
82
+
81
83
  export default defineComponent({
82
84
  ControlType: "Selecteds", // 必须与文件名匹配
83
85
  nameCn: "多选择器",
@@ -95,27 +97,6 @@ export default defineComponent({
95
97
  const vm = getCurrentInstance() as ComponentInternalInstance;
96
98
  useWatch(props);
97
99
 
98
- const getReadonlyDisplayValue = () => {
99
- try {
100
- // 优先使用 data 中的值
101
- const fieldValue = props.data[props.item.data.fieldName];
102
- if (Array.isArray(fieldValue)) {
103
- return fieldValue.length > 0 ? fieldValue.join(',') : '--';
104
- }
105
-
106
- // 如果 data 中没有值,使用 itemConfig 中的默认值
107
- const configValue = props.item.data.itemConfig?.value;
108
- if (Array.isArray(configValue)) {
109
- return configValue.length > 0 ? configValue.join(',') : '--';
110
- }
111
-
112
- return '--';
113
- } catch (error) {
114
- console.error('Error getting readonly display value:', error);
115
- return '--';
116
- }
117
- };
118
-
119
100
  return {
120
101
  execFunc(type: string) {
121
102
  if (props.item.data.action && props.item.data.action[type]) {
@@ -124,8 +105,27 @@ export default defineComponent({
124
105
  ]);
125
106
  }
126
107
  },
127
- getReadonlyDisplayValue
128
108
  };
129
109
  },
110
+ methods: {
111
+ getDisplayText() {
112
+ const fieldValue = this.data[this.item.data.fieldName];
113
+ const items = this.item.data.itemConfig?.items || [];
114
+
115
+ // 如果没有值,显示默认的 "--"
116
+ if (!fieldValue || !Array.isArray(fieldValue) || fieldValue.length === 0) {
117
+ return this.item.data.placeholder || '--';
118
+ }
119
+
120
+ // 根据选中的 value 数组查找对应的 label
121
+ const selectedLabels = fieldValue.map(value => {
122
+ const item = items.find(item => item.value === value);
123
+ return item ? item.label : value;
124
+ });
125
+
126
+ // 返回用逗号分隔的 label 字符串
127
+ return selectedLabels.join(', ');
128
+ }
129
+ }
130
130
  });
131
131
  </script>
@@ -28,26 +28,32 @@
28
28
  class="control"
29
29
  :style="{ marginLeft: labelalign != 'top' ? labelWidth + 'px' : '' }"
30
30
  >
31
+ <!-- 只读状态显示是或否 -->
32
+ <span v-if="!drag && (item.data.state === 'readonly' || readonly)">
33
+ {{ getDisplayText() }}
34
+ </span>
31
35
  <el-switch
36
+ v-else-if="drag"
32
37
  v-model="item.data.default"
33
- v-if="drag"
34
38
  :size="size"
35
39
  :disabled="item.data.state === 'disabled' || item.data.state === 'readonly'"
36
40
  />
37
41
  <el-switch
42
+ v-else
38
43
  v-model="data[item.data.fieldName]"
39
- v-if="!drag"
40
44
  :size="size"
41
45
  :disabled="item.data.state === 'disabled' || item.data.state === 'readonly'"
42
46
  />
43
47
  </div>
44
48
  </div>
45
49
  </template>
50
+
46
51
  <script lang="ts">
47
52
  import { defineComponent } from "vue";
48
53
  import { getFormConfig } from "../../utils/fieldConfig";
49
54
  import fieldProps from "../../utils/fieldProps";
50
55
  import { useWatch } from "../../utils/customHooks";
56
+
51
57
  export default defineComponent({
52
58
  ControlType: "Switch", // 必须与文件名匹配
53
59
  nameCn: "开关",
@@ -63,5 +69,18 @@ export default defineComponent({
63
69
  setup(props) {
64
70
  useWatch(props);
65
71
  },
72
+ methods: {
73
+ getDisplayText() {
74
+ const fieldValue = this.data[this.item.data.fieldName];
75
+
76
+ // 如果没有值,显示默认的 "--"
77
+ if (fieldValue === undefined || fieldValue === null) {
78
+ return '--';
79
+ }
80
+
81
+ // 根据布尔值返回是或否
82
+ return fieldValue ? '是' : '否';
83
+ }
84
+ }
66
85
  });
67
- </script>
86
+ </script>