bl-common-vue3 3.8.105 → 3.8.106

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bl-common-vue3",
3
- "version": "3.8.105",
3
+ "version": "3.8.106",
4
4
  "main": "index.js",
5
5
  "module": "index.js",
6
6
  "description": "bailing vue3 common components lib",
@@ -123,6 +123,16 @@
123
123
  </a-space>
124
124
  <span v-else>--</span>
125
125
  </template>
126
+
127
+ <template v-if="column.key === 'user_show'">
128
+ <a-switch
129
+ v-model:checked="record.user_show"
130
+ :checkedValue="1"
131
+ :unCheckedValue="0"
132
+ :disabled="userShowConfig.disabled"
133
+ @change="handleUseShowChange($event, record)"
134
+ />
135
+ </template>
126
136
  </template>
127
137
  </a-table>
128
138
  </a-card>
@@ -296,6 +306,16 @@ export default defineComponent({
296
306
  type: String,
297
307
  default: "",
298
308
  },
309
+ // 是否显示业户开放操作
310
+ userShowConfig: {
311
+ type: Object,
312
+ default: () => {
313
+ return {
314
+ show: false,
315
+ disabled: false,
316
+ };
317
+ },
318
+ }
299
319
  },
300
320
  setup(props, context) {
301
321
  const homeCrumb = { key: "home", parentId: 0, title: t('AttachmentInfo.index.559728-8') };
@@ -623,12 +643,19 @@ export default defineComponent({
623
643
  return utils.i18nTypeShow(record,'date','operate_time') || '--'
624
644
  }
625
645
  },
646
+ // 是否显示业户开放操作
647
+ {
648
+ title: t('AttchmentInfo.userShow'),
649
+ dataIndex: "user_show",
650
+ key: "user_show",
651
+ hidden: !props.userShowConfig.show,
652
+ },
626
653
  {
627
654
  title: t('common.table.operation'),
628
655
  dataIndex: "id",
629
656
  key: "action",
630
657
  },
631
- ]
658
+ ].filter(item => !item.hidden);
632
659
  });
633
660
 
634
661
  loadLanguageAsync(utils.getLang());
@@ -647,6 +674,29 @@ export default defineComponent({
647
674
  }
648
675
  })
649
676
 
677
+ // 对业户开放操作
678
+ const handleUseShowChange = (e, record) => {
679
+ let params = {
680
+ ids: [record.id],
681
+ user_show: e ? 1 : 0,
682
+ alias: props.fileAlias,
683
+ }
684
+ context.emit("request", {
685
+ params: {
686
+ method: "post",
687
+ server: `/${props.serviceFrom}`,
688
+ url: `${props.annexEditFrom}`,
689
+ extra: {
690
+ ...params,
691
+ },
692
+ },
693
+ success: (res) => {
694
+ getDataList();
695
+ message.success(res.msg || t('common.action.success'));
696
+ },
697
+ });
698
+ }
699
+
650
700
  return {
651
701
  t,
652
702
  crumbList,
@@ -677,6 +727,7 @@ export default defineComponent({
677
727
  getDataList,
678
728
  getPicList,
679
729
  attachParams,
730
+ handleUseShowChange,
680
731
  };
681
732
  },
682
733
  });
@@ -807,20 +807,39 @@
807
807
  <template
808
808
  v-for="(item, itemIndex) in rightMoreButtons"
809
809
  :key="itemIndex">
810
- <a-menu-item v-if="item?.show ?? true">
811
- <a-button
812
- v-bind="{
813
- ...(item?.props || {}),
814
- }"
815
- @click.stop="item?.onClick">
816
- <template
817
- #icon
818
- v-if="buttonIconSlots.indexOf(`${item.key}`) > -1">
810
+ <template v-if="((item?.show ?? true) && item?.children?.length)">
811
+ <!-- 配置 children 时渲染二级菜单,未配置时保持原有普通按钮行为。 -->
812
+ <a-sub-menu :key="item.key">
813
+ <template #icon v-if="buttonIconSlots.indexOf(`${item.key}`) > -1">
819
814
  <slot :name="`buttonIcon-${item.key}`"></slot>
820
815
  </template>
821
- {{ i18nMapShow(item, ["name"], ["name"]) }}
822
- </a-button>
823
- </a-menu-item>
816
+ <template #title>
817
+ {{ i18nMapShow(item, ["name"], ["name"]) }}
818
+ </template>
819
+ <template v-for="(child, childIndex) in item.children" :key="child.key">
820
+ <a-menu-item v-if="child?.show ?? true" :key="child.key">
821
+ <a-button v-bind="{ ...(child?.props || {}), }" @click.stop="child?.onClick">
822
+ <template #icon v-if="buttonIconSlots.indexOf(`${child.key}`) > -1">
823
+ <slot :name="`buttonIcon-${child.key}`"></slot>
824
+ </template>
825
+ {{ i18nMapShow(child, ["name"], ["name"]) }}
826
+ </a-button>
827
+ </a-menu-item>
828
+ </template>
829
+ </a-sub-menu>
830
+ </template>
831
+ <template v-if="((item?.show ?? true) && !item?.children?.length)">
832
+ <a-menu-item :key="item.key">
833
+ <a-button v-bind="{ ...(item?.props || {}), }" @click.stop="item?.onClick">
834
+ <template
835
+ #icon
836
+ v-if="buttonIconSlots.indexOf(`${item.key}`) > -1">
837
+ <slot :name="`buttonIcon-${item.key}`"></slot>
838
+ </template>
839
+ {{ i18nMapShow(item, ["name"], ["name"]) }}
840
+ </a-button>
841
+ </a-menu-item>
842
+ </template>
824
843
  </template>
825
844
  </a-menu>
826
845
  </template>
@@ -1345,7 +1364,8 @@
1345
1364
  @request="handleRequest"
1346
1365
  @closeModal="handleCloseExpandFields"
1347
1366
  :hasVillageSelect="expandFieldsHasVillageSelect"
1348
- :displayCompany="expandFieldsDisplayCompany" />
1367
+ :displayCompany="expandFieldsDisplayCompany"
1368
+ :customConfigItems="expandFieldsCustomConfigItems" />
1349
1369
 
1350
1370
  <!-- 选择成员 -->
1351
1371
  <ChooseOrgMember
@@ -1482,7 +1502,7 @@ export default defineComponent({
1482
1502
  return [];
1483
1503
  },
1484
1504
  },
1485
- /* 右侧更多按钮组 */
1505
+ /* 右侧更多按钮组,配置 children 时展示二级菜单 */
1486
1506
  rightMoreButtons: {
1487
1507
  type: Array,
1488
1508
  default: () => {
@@ -1753,6 +1773,11 @@ export default defineComponent({
1753
1773
  type: Function,
1754
1774
  default: () => {},
1755
1775
  },
1776
+ // 拓展字段的附加配置项会透传给拓展字段组件。
1777
+ expandFieldsCustomConfigItems: {
1778
+ type: Array,
1779
+ default: () => [],
1780
+ },
1756
1781
  },
1757
1782
  components: {
1758
1783
  PlusOutlined,
@@ -0,0 +1,58 @@
1
+ <template>
2
+ <a-col :span="config.form.span">
3
+ <a-form-item v-bind="validateInfo">
4
+ <template #label>
5
+ <span>{{ config.form.label }}</span>
6
+ <!-- 外部存在提示配置时复用统一的提示组件。 -->
7
+ <text-tip v-if="config.form.tips.length" :tips="config.form.tips" />
8
+ </template>
9
+ <a-switch
10
+ v-if="config.type === 'switch'"
11
+ v-bind="config.form.props"
12
+ :checked="modelValue"
13
+ @update:checked="handleUpdate"
14
+ />
15
+ </a-form-item>
16
+ </a-col>
17
+ </template>
18
+
19
+ <script>
20
+ import { defineComponent } from "vue";
21
+ import { Col, FormItem, Switch } from "ant-design-vue";
22
+ import TextTip from "../../TextTip/index.vue";
23
+
24
+ export default defineComponent({
25
+ name: "CustomConfigFormItem",
26
+ components: {
27
+ "a-col": Col,
28
+ "a-form-item": FormItem,
29
+ "a-switch": Switch,
30
+ TextTip,
31
+ },
32
+ props: {
33
+ config: {
34
+ type: Object,
35
+ required: true,
36
+ },
37
+ modelValue: {
38
+ type: [Boolean, Number, String],
39
+ default: undefined,
40
+ },
41
+ validateInfo: {
42
+ type: Object,
43
+ default: () => ({}),
44
+ },
45
+ },
46
+ emits: ["update:modelValue"],
47
+ setup(props, { emit }) {
48
+ // switch 事件值保持组件按外部 props 协议返回的原始类型。
49
+ const handleUpdate = (value) => {
50
+ emit("update:modelValue", value);
51
+ };
52
+
53
+ return {
54
+ handleUpdate,
55
+ };
56
+ },
57
+ });
58
+ </script>
@@ -103,6 +103,14 @@
103
103
  />
104
104
  </a-form-item>
105
105
  </a-col>
106
+ <!-- 外部配置项统一在基础设置区域渲染。 -->
107
+ <CustomConfigFormItem
108
+ v-for="item in normalizedCustomConfigItems"
109
+ :key="item.key"
110
+ v-model="formState[item.key]"
111
+ :config="item"
112
+ :validateInfo="validateInfos[item.key]"
113
+ />
106
114
  <a-col :span="24">
107
115
  <div class="text-title">{{t('components.extraManageEdit.enterSetting')}}</div>
108
116
  </a-col>
@@ -202,6 +210,14 @@ import draggable from "vuedraggable";
202
210
  import { DragOutlined , DeleteOutlined , QuestionCircleOutlined } from "@ant-design/icons-vue";
203
211
  import TextTip from "../../TextTip/index.vue";
204
212
  import BlModal from "../../BlModal/index.vue";
213
+ import CustomConfigFormItem from "./CustomConfigFormItem.vue";
214
+ import {
215
+ createCustomConfigRules,
216
+ handleCustomConfigItems,
217
+ resetCustomConfigState,
218
+ setCustomConfigDetail,
219
+ syncCustomConfigState,
220
+ } from "../utils/customConfig";
205
221
 
206
222
  export default defineComponent({
207
223
  name: "ExtraManageEdit",
@@ -251,6 +267,11 @@ export default defineComponent({
251
267
  type: Boolean,
252
268
  default: true,
253
269
  },
270
+ // 外部传入的拓展字段附加配置项,目前仅支持 switch 类型。
271
+ customConfigItems: {
272
+ type: Array,
273
+ default: () => [],
274
+ },
254
275
  },
255
276
  components: {
256
277
  BlModal,
@@ -268,9 +289,12 @@ export default defineComponent({
268
289
  DeleteOutlined,
269
290
  TextTip,
270
291
  QuestionCircleOutlined,
292
+ CustomConfigFormItem,
271
293
  },
272
294
  setup(props, {emit}) {
273
295
  const fieldsTypeList = ref([]);
296
+ // 统一补齐外部配置的回退值,表单和列表共享同一份配置约定。
297
+ const normalizedCustomConfigItems = computed(() => handleCustomConfigItems(props.customConfigItems));
274
298
  const state = reactive({
275
299
  formState: {
276
300
  fields_name: '',
@@ -307,6 +331,8 @@ export default defineComponent({
307
331
  fields_name: [{ required: true, message: t('components.ExtraManageEdit.301828-6') }],
308
332
  fields_type: [{ required: true, message: t('components.ExtraManageEdit.249040-16') }],
309
333
  sort: [{ required: true, message: t('components.ExtraManageEdit.249040-17') }],
334
+ // 自定义项按外部 required 配置动态生成校验规则。
335
+ ...createCustomConfigRules(normalizedCustomConfigItems.value, t('common.please.choose')),
310
336
  }
311
337
  });
312
338
 
@@ -401,13 +427,19 @@ export default defineComponent({
401
427
 
402
428
 
403
429
  const setModelRef = (info) => {
430
+ const customConfigKeys = new Set(normalizedCustomConfigItems.value.map((item) => item.key));
404
431
  for (let key in state.formState) {
432
+ // 自定义项由统一回填方法处理,避免覆盖配置声明的默认值。
433
+ if (customConfigKeys.has(key)) {
434
+ continue;
435
+ }
405
436
  if (['org_branch_id', 'village_id'].includes(key)) {
406
437
  state.formState[key] = info[key] || undefined;
407
438
  } else {
408
439
  state.formState[key] = info[key];
409
440
  }
410
441
  }
442
+ setCustomConfigDetail(state.formState, info, normalizedCustomConfigItems.value);
411
443
  };
412
444
 
413
445
  const getCompanyList = () => {
@@ -575,6 +607,8 @@ export default defineComponent({
575
607
  };
576
608
  const reset = () => {
577
609
  resetFields();
610
+ // resetFields 后显式恢复动态项,保证每次新增都使用最新配置默认值。
611
+ resetCustomConfigState(state.formState, normalizedCustomConfigItems.value);
578
612
  state.submitLoading = false;
579
613
  state.companyList = [];
580
614
  state.villageList = [];
@@ -591,6 +625,19 @@ export default defineComponent({
591
625
  }
592
626
  );
593
627
 
628
+ watch(
629
+ normalizedCustomConfigItems,
630
+ (items, previousItems = []) => {
631
+ // 配置运行时变化时同步动态字段,并清理已移除的配置项。
632
+ syncCustomConfigState(state.formState, items, previousItems);
633
+ if (!props.visible) {
634
+ // 弹窗关闭时应用最新默认值,避免下次新增继续使用旧配置。
635
+ resetCustomConfigState(state.formState, items);
636
+ }
637
+ },
638
+ { immediate: true, deep: true },
639
+ );
640
+
594
641
  loadLanguageAsync(utils.getLang());
595
642
 
596
643
  window.addEventListener('setBLLang', (event) => {
@@ -626,6 +673,7 @@ export default defineComponent({
626
673
  changeCompany,
627
674
  hasOptionValue,
628
675
  getContainer,
676
+ normalizedCustomConfigItems,
629
677
  };
630
678
  },
631
679
  });
@@ -20,6 +20,14 @@
20
20
  @request="handleRequest"
21
21
  :tableConfig="tableConfig"
22
22
  >
23
+ <!-- 可编辑的自定义列统一通过动态具名插槽渲染。 -->
24
+ <template v-for="item in editableCustomConfigItems" :key="item.key" #[`column-${item.key}`]="{ record }" >
25
+ <a-switch
26
+ v-if="item.type == 'switch'"
27
+ v-bind="getCustomConfigEditorProps(record, item)"
28
+ @change="handleCustomConfigChange($event, record, item)"
29
+ />
30
+ </template>
23
31
  <template #column-action="{ record }">
24
32
  <div v-if="record.is_system == 1">
25
33
  <a-button @click="handleEdit(record)" type="link" v-if="record?.can_edit">{{t('common.button.edit')}}</a-button>
@@ -47,6 +55,7 @@
47
55
  :expandFieldsExtraParams="expandFieldsExtraParams"
48
56
  :id="editId"
49
57
  :hasVillageSelect="hasVillageSelect"
58
+ :customConfigItems="customConfigItems"
50
59
  @closeModal="editVisible = false"
51
60
  @success="editSuccess"
52
61
  @request="handleRequest"
@@ -58,10 +67,17 @@
58
67
  import { cloneDeep } from "lodash";
59
68
  import {defineComponent, reactive, toRefs, defineAsyncComponent, computed} from "vue";
60
69
  import ExtraManageEdit from "./components/ExtraManageEdit.vue";
61
- import {Drawer, message,} from "ant-design-vue";
70
+ import {Drawer, message, Switch} from "ant-design-vue";
62
71
  import {t, loadLanguageAsync} from "../../locale";
63
72
  import utils from "../../common/utils/util";
64
73
  import BlDrawer from "../BlDrawer/index.vue";
74
+ import {
75
+ getCustomConfigEditorProps,
76
+ handleCustomConfigItems,
77
+ getCustomConfigEditorChange,
78
+ getColumnsVisibleCustomConfigItems,
79
+ getEditableCustomConfigItems,
80
+ } from "./utils/customConfig";
65
81
  const CustomTable = defineAsyncComponent(() =>
66
82
  import("../CustomTable/index.vue")
67
83
  );
@@ -114,11 +130,17 @@ export default defineComponent({
114
130
  type: Boolean,
115
131
  default: true,
116
132
  },
133
+ // 外部传入的拓展字段附加配置项
134
+ customConfigItems: {
135
+ type: Array,
136
+ default: () => [],
137
+ },
117
138
  },
118
139
  components: {
119
140
  BlDrawer,
120
141
  ExtraManageEdit,
121
142
  CustomTable,
143
+ "a-switch": Switch,
122
144
  },
123
145
  setup(props, {emit}) {
124
146
  const state = reactive({
@@ -126,6 +148,13 @@ export default defineComponent({
126
148
  editId: 0,
127
149
  customTable: null,
128
150
  });
151
+
152
+ // 处理后的自定义配置项
153
+ const normalizedCustomConfigItems = computed(() => handleCustomConfigItems(props.customConfigItems));
154
+
155
+ // 表格列中支持编辑的配置项
156
+ const editableCustomConfigItems = computed(() => getEditableCustomConfigItems(normalizedCustomConfigItems.value));
157
+
129
158
  const closeModal = () => {
130
159
  emit("closeModal");
131
160
  };
@@ -265,6 +294,10 @@ export default defineComponent({
265
294
  },
266
295
  })
267
296
  }
297
+ // 自定义列表项统一插入必填列之前,保留外部传入的其他表格配置。
298
+ const fillColumnIndex = _deepColumns.findIndex((column) => column.key === 'fill');
299
+ const customColumns = getColumnsVisibleCustomConfigItems(normalizedCustomConfigItems.value);
300
+ _deepColumns.splice(fillColumnIndex, 0, ...customColumns);
268
301
  return _deepColumns;
269
302
  });
270
303
 
@@ -286,6 +319,33 @@ export default defineComponent({
286
319
  loadLanguageAsync(newLang);
287
320
  });
288
321
 
322
+ // 列表快捷切换复用拓展字段编辑接口,仅提交当前配置字段及必要参数。
323
+ const handleCustomConfigChange = (value, record, item) => {
324
+ if (!record?.id) {
325
+ return;
326
+ }
327
+ const requestInfo = props.requestAllInfo?.edit || {};
328
+ const params = requestInfo?.params || props.requestAllParams?.edit || {};
329
+ const changeValue = getCustomConfigEditorChange(value, record, item);
330
+ emit("request", {
331
+ params: {
332
+ method: requestInfo.serviceType || 'put',
333
+ server: requestInfo.service || '/work_property',
334
+ url: requestInfo.serviceUrl ? requestInfo.serviceUrl.replace(/\${id}/g, record.id) : `/org/property/extra/info/field/${record.id}`,
335
+ extra: {
336
+ ...params,
337
+ ...props.expandFieldsExtraParams,
338
+ id: record.id,
339
+ [item.key]: changeValue,
340
+ },
341
+ },
342
+ success: (res) => {
343
+ state.customTable?.refresh();
344
+ message.success(res?.msg || t('common.save.success'));
345
+ },
346
+ });
347
+ };
348
+
289
349
  return {
290
350
  t,
291
351
  ...toRefs(state),
@@ -298,6 +358,9 @@ export default defineComponent({
298
358
  handleDelete,
299
359
  handleEdit,
300
360
  editSuccess,
361
+ getCustomConfigEditorProps,
362
+ handleCustomConfigChange,
363
+ editableCustomConfigItems,
301
364
  };
302
365
  },
303
366
  });
@@ -0,0 +1,195 @@
1
+ import { cloneDeep } from "lodash";
2
+
3
+ // 拓展字段列表支持编辑的配置项类型。
4
+ export const EDITABLE_CUSTOM_CONFIG_TYPES = ["switch"];
5
+
6
+ // dataIndex 未配置、空字符串或空数组时均回退到配置项 key。
7
+ const normalizeDataIndex = (dataIndex, fallbackKey) => {
8
+ if (dataIndex === undefined || dataIndex === null || dataIndex === "") {
9
+ return fallbackKey;
10
+ }
11
+ return Array.isArray(dataIndex) && dataIndex.length === 0 ? fallbackKey : dataIndex;
12
+ };
13
+
14
+ // 将外部配置补齐默认值
15
+ /* 配置项示例
16
+ const customConfigItems = [
17
+ {
18
+ // 必填且唯一,同时作为新增、编辑接口提交的字段名
19
+ key: 'user_show',
20
+ // 表单和列表标题的公共默认值
21
+ label: '用户端展示',
22
+ // 表单组件及可编辑列表组件类型
23
+ type: 'switch',
24
+ form: {
25
+ // 未配置或为空时读取外层 label
26
+ label: '用户端展示',
27
+ // 可选,未配置时不展示提示
28
+ tips: ['用户端展示1', '用户端展示2'],
29
+ // 默认 false
30
+ required: false,
31
+ // 默认值
32
+ defaultValue: 1,
33
+ // 默认 8
34
+ span: 8,
35
+ // 传递给具体表单组件
36
+ props: {
37
+ checkedValue: 1,
38
+ unCheckedValue: 0,
39
+ },
40
+ },
41
+ columns: {
42
+ // 默认 false
43
+ show: true,
44
+ // 默认 false,开启后允许在列表中快捷编辑
45
+ editable: true,
46
+ width: 100,
47
+ // 未配置或为空时读取外层 key
48
+ dataIndex: 'user_show',
49
+ // 未配置或为空时读取外层 label
50
+ title: '用户端展示',
51
+ // 其他 CustomTable 列配置
52
+ fixed: false,
53
+ },
54
+ },
55
+ ]; */
56
+ export const handleCustomConfigItems = (items = []) => {
57
+ const keys = new Set();
58
+ return (Array.isArray(items) ? items : []).reduce((result, item) => {
59
+ // 当前仅兼容已声明的编辑组件类型,避免未知类型渲染空表单项。
60
+ if (!item?.key || keys.has(item.key) || !EDITABLE_CUSTOM_CONFIG_TYPES.includes(item.type)) {
61
+ return result;
62
+ }
63
+ keys.add(item.key);
64
+ const form = item.form || {};
65
+ const columns = item.columns || {};
66
+ result.push({
67
+ ...item,
68
+ form: {
69
+ ...form,
70
+ label: form.label || item.label || "",
71
+ tips: Array.isArray(form.tips) ? form.tips.filter(Boolean) : [],
72
+ required: form.required ?? false,
73
+ defaultValue: form.defaultValue,
74
+ span: form.span ?? 8,
75
+ props: form.props || {},
76
+ },
77
+ columns: {
78
+ ...columns,
79
+ show: columns.show ?? false,
80
+ editable: columns.editable ?? false,
81
+ dataIndex: normalizeDataIndex(columns.dataIndex, item.key),
82
+ title: columns.title || item.label || "",
83
+ },
84
+ });
85
+ return result;
86
+ }, []);
87
+ };
88
+
89
+ // 配置变化时同步动态字段,并清理已经从外部配置中移除的字段。
90
+ export const syncCustomConfigState = (formState, items, previousItems = []) => {
91
+ const currentKeys = new Set(items.map((item) => item.key));
92
+ previousItems.forEach((item) => {
93
+ if (!currentKeys.has(item.key)) {
94
+ delete formState[item.key];
95
+ }
96
+ });
97
+ items.forEach((item) => {
98
+ if (!Object.prototype.hasOwnProperty.call(formState, item.key)) {
99
+ // 默认值直接使用外部配置,不根据 switch 协议转换类型。
100
+ formState[item.key] = cloneDeep(item.form.defaultValue);
101
+ }
102
+ });
103
+ };
104
+
105
+ // 新增或关闭弹窗时将全部动态字段恢复为外部配置声明的默认值。
106
+ export const resetCustomConfigState = (formState, items) => {
107
+ items.forEach((item) => {
108
+ // 重置时保持外部 defaultValue 的原始类型和值。
109
+ formState[item.key] = cloneDeep(item.form.defaultValue);
110
+ });
111
+ };
112
+
113
+ // 编辑详情存在字段时原样回填,缺少字段时才使用外部配置默认值。
114
+ export const setCustomConfigDetail = (formState, detail, items) => {
115
+ items.forEach((item) => {
116
+ const hasDetailValue = Object.prototype.hasOwnProperty.call(detail || {}, item.key);
117
+ formState[item.key] = cloneDeep(hasDetailValue ? detail[item.key] : item.form.defaultValue);
118
+ });
119
+ };
120
+
121
+ // required 只校验是否存在值,switch 的 0 和 false 都属于有效值。
122
+ export const createCustomConfigRules = (items, requiredMessage) => {
123
+ return items.reduce((rules, item) => {
124
+ if (!item.form.required) {
125
+ return rules;
126
+ }
127
+ rules[item.key] = [
128
+ {
129
+ required: true,
130
+ validator: async (rule, value) => {
131
+ const isEmpty = value === undefined || value === null || value === "";
132
+ return isEmpty ? Promise.reject(requiredMessage) : Promise.resolve();
133
+ },
134
+ },
135
+ ];
136
+ return rules;
137
+ }, {});
138
+ };
139
+
140
+ // 支持字符串或数组形式的 dataIndex,供动态列表列读取嵌套值。
141
+ export const getCustomConfigValue = (record, item) => {
142
+ const dataIndex = item?.columns?.dataIndex || item?.key;
143
+ const paths = Array.isArray(dataIndex) ? dataIndex : `${dataIndex}`.split(".");
144
+ const value = paths.reduce((result, path) => result?.[path], record);
145
+ // 列表值存在时保持接口原始类型,缺省时使用外部配置默认值。
146
+ return value === undefined ? item?.form?.defaultValue : value;
147
+ };
148
+
149
+ // 列表编辑组件组件属性
150
+ export const getCustomConfigEditorProps = (record, item) => {
151
+ let componentProps = {};
152
+ const { type } = item || {};
153
+ if(type === 'switch'){
154
+ const externalDisabled = item?.form?.props?.disabled || false;
155
+ componentProps = {
156
+ checked: getCustomConfigValue(record, item),
157
+ // 外部禁用、列不可编辑或系统字段无权限时均禁止快捷切换。
158
+ disabled: externalDisabled || !item?.columns?.editable || (record?.is_system == 1 && !record?.can_edit),
159
+ };
160
+ }
161
+ return {
162
+ ...(item?.form?.props || {}),
163
+ ...componentProps,
164
+ };
165
+ };
166
+
167
+ // 列表编辑组件change获取value
168
+ export const getCustomConfigEditorChange = (changeValue, record, item) => {
169
+ const { type } = item || {};
170
+ if(type === 'switch'){
171
+ // switch 事件值完全遵循外部 checkedValue/unCheckedValue 协议。
172
+ return changeValue;
173
+ }
174
+ return changeValue;
175
+ };
176
+
177
+ // 获取在拓展字段列表显示的配置项,组合配置项columns
178
+ export const getColumnsVisibleCustomConfigItems = (customConfigItems = []) => {
179
+ const visibleCustomConfigItems = customConfigItems.filter((item) => item.columns.show)
180
+ const customColumns = visibleCustomConfigItems.map((item) => {
181
+ const columnConfig = cloneDeep(item.columns);
182
+ delete columnConfig.show;
183
+ return {
184
+ ...columnConfig,
185
+ key: item.key,
186
+ };
187
+ });
188
+ return customColumns;
189
+ };
190
+
191
+ // 获取在拓展字段列表显示的可编辑配置项
192
+ export const getEditableCustomConfigItems = (customConfigItems = []) => {
193
+ const visibleCustomConfigItems = customConfigItems.filter((item) => item.columns.show)
194
+ return visibleCustomConfigItems.filter((item) => EDITABLE_CUSTOM_CONFIG_TYPES.includes(item.type));
195
+ };
@@ -102,6 +102,14 @@
102
102
  />
103
103
  </a-form-item>
104
104
  </a-col>
105
+ <!-- 外部配置项统一在基础设置区域渲染。 -->
106
+ <CustomConfigFormItem
107
+ v-for="item in normalizedCustomConfigItems"
108
+ :key="item.key"
109
+ v-model="formState[item.key]"
110
+ :config="item"
111
+ :validateInfo="validateInfos[item.key]"
112
+ />
105
113
  <a-col :span="24">
106
114
  <div class="text-title">{{t('components.extraManageEdit.enterSetting')}}</div>
107
115
  </a-col>
@@ -196,6 +204,14 @@ import draggable from "vuedraggable";
196
204
  import { DragOutlined , DeleteOutlined , QuestionCircleOutlined } from "@ant-design/icons-vue";
197
205
  import TextTip from "../../TextTip/index.vue";
198
206
  import BlModal from "../../BlModal/index.vue";
207
+ import CustomConfigFormItem from "../../ExtraManage/components/CustomConfigFormItem.vue";
208
+ import {
209
+ createCustomConfigRules,
210
+ handleCustomConfigItems,
211
+ resetCustomConfigState,
212
+ setCustomConfigDetail,
213
+ syncCustomConfigState,
214
+ } from "../../ExtraManage/utils/customConfig";
199
215
 
200
216
  export default defineComponent({
201
217
  name: "ExtraManageEdit",
@@ -228,7 +244,12 @@ export default defineComponent({
228
244
  server:{
229
245
  type: String,
230
246
  default: '',
231
- }
247
+ },
248
+ // 外部传入的拓展字段附加配置项,目前仅支持 switch 类型。
249
+ customConfigItems: {
250
+ type: Array,
251
+ default: () => [],
252
+ },
232
253
  },
233
254
  components: {
234
255
  BlModal,
@@ -246,9 +267,12 @@ export default defineComponent({
246
267
  DeleteOutlined,
247
268
  TextTip,
248
269
  QuestionCircleOutlined,
270
+ CustomConfigFormItem,
249
271
  },
250
272
  setup(props, {emit}) {
251
273
  const fieldsTypeList = ref([]);
274
+ // 公共拓展字段复用标准拓展字段的配置归一化规则。
275
+ const normalizedCustomConfigItems = computed(() => handleCustomConfigItems(props.customConfigItems));
252
276
  const state = reactive({
253
277
  formState: {
254
278
  fields_name: '',
@@ -281,6 +305,8 @@ export default defineComponent({
281
305
  fields_name: [{ required: true, message: t('components.ExtraManageEdit.301828-6') }],
282
306
  fields_type: [{ required: true, message: t('components.ExtraManageEdit.249040-16') }],
283
307
  sort: [{ required: true, message: t('components.ExtraManageEdit.249040-17') }],
308
+ // 自定义项按外部 required 配置动态生成校验规则。
309
+ ...createCustomConfigRules(normalizedCustomConfigItems.value, t('common.please.choose')),
284
310
  }
285
311
  });
286
312
 
@@ -366,13 +392,19 @@ export default defineComponent({
366
392
 
367
393
 
368
394
  const setModelRef = (info) => {
395
+ const customConfigKeys = new Set(normalizedCustomConfigItems.value.map((item) => item.key));
369
396
  for (let key in state.formState) {
397
+ // 自定义项由统一回填方法处理,避免覆盖配置声明的默认值。
398
+ if (customConfigKeys.has(key)) {
399
+ continue;
400
+ }
370
401
  if (['org_branch_id', 'village_id'].includes(key)) {
371
402
  state.formState[key] = info[key] || undefined;
372
403
  } else {
373
404
  state.formState[key] = info[key];
374
405
  }
375
406
  }
407
+ setCustomConfigDetail(state.formState, info, normalizedCustomConfigItems.value);
376
408
  };
377
409
 
378
410
  const getCompanyList = () => {
@@ -537,6 +569,8 @@ export default defineComponent({
537
569
  };
538
570
  const reset = () => {
539
571
  resetFields();
572
+ // resetFields 后显式恢复动态项,保证每次新增都使用最新配置默认值。
573
+ resetCustomConfigState(state.formState, normalizedCustomConfigItems.value);
540
574
  state.submitLoading = false;
541
575
  state.companyList = [];
542
576
  state.villageList = [];
@@ -553,6 +587,19 @@ export default defineComponent({
553
587
  }
554
588
  );
555
589
 
590
+ watch(
591
+ normalizedCustomConfigItems,
592
+ (items, previousItems = []) => {
593
+ // 配置运行时变化时同步动态字段,并清理已移除的配置项。
594
+ syncCustomConfigState(state.formState, items, previousItems);
595
+ if (!props.visible) {
596
+ // 弹窗关闭时应用最新默认值,避免下次新增继续使用旧配置。
597
+ resetCustomConfigState(state.formState, items);
598
+ }
599
+ },
600
+ { immediate: true, deep: true },
601
+ );
602
+
556
603
  loadLanguageAsync(utils.getLang());
557
604
 
558
605
  window.addEventListener('setBLLang', (event) => {
@@ -582,6 +629,7 @@ export default defineComponent({
582
629
  fieldsTypechage,
583
630
  i18nMapShow,
584
631
  changeCompany,
632
+ normalizedCustomConfigItems,
585
633
  };
586
634
  },
587
635
  });
@@ -20,6 +20,14 @@
20
20
  @request="handleRequest"
21
21
  :tableConfig="tableConfig"
22
22
  >
23
+ <!-- 可编辑的自定义列统一通过动态具名插槽渲染。 -->
24
+ <template v-for="item in editableCustomConfigItems" :key="item.key" #[`column-${item.key}`]="{ record }" >
25
+ <a-switch
26
+ v-if="item.type == 'switch'"
27
+ v-bind="getCustomConfigEditorProps(record, item)"
28
+ @change="handleCustomConfigChange($event, record, item)"
29
+ />
30
+ </template>
23
31
  <template #column-action="{ record }">
24
32
  <div v-if="record.is_system == 1">
25
33
  <a-tooltip>
@@ -46,6 +54,7 @@
46
54
  :alias="alias"
47
55
  :server="server"
48
56
  :hasVillageSelect="hasVillageSelect"
57
+ :customConfigItems="customConfigItems"
49
58
  @closeModal="editVisible = false"
50
59
  @success="editSuccess"
51
60
  @request="handleRequest"
@@ -57,10 +66,17 @@
57
66
  import { cloneDeep } from "lodash";
58
67
  import {defineComponent, reactive, toRefs, defineAsyncComponent, computed} from "vue";
59
68
  import ExtraManageEdit from "./components/ExtraManageEdit.vue";
60
- import {Drawer, message,} from "ant-design-vue";
69
+ import {Drawer, message, Switch} from "ant-design-vue";
61
70
  import {t, loadLanguageAsync} from "../../locale";
62
71
  import utils from "../../common/utils/util";
63
72
  import BlDrawer from "../BlDrawer/index.vue";
73
+ import {
74
+ getCustomConfigEditorProps,
75
+ handleCustomConfigItems,
76
+ getCustomConfigEditorChange,
77
+ getColumnsVisibleCustomConfigItems,
78
+ getEditableCustomConfigItems,
79
+ } from "../ExtraManage/utils/customConfig";
64
80
  const CustomTable = defineAsyncComponent(() =>
65
81
  import("../CustomTable/index.vue")
66
82
  );
@@ -96,12 +112,18 @@ export default defineComponent({
96
112
  server:{
97
113
  type: String,
98
114
  default: '',
99
- }
115
+ },
116
+ // 外部传入的拓展字段附加配置项,目前仅支持 switch 类型。
117
+ customConfigItems: {
118
+ type: Array,
119
+ default: () => [],
120
+ },
100
121
  },
101
122
  components: {
102
123
  BlDrawer,
103
124
  ExtraManageEdit,
104
125
  CustomTable,
126
+ "a-switch": Switch,
105
127
  },
106
128
  setup(props, {emit}) {
107
129
  const state = reactive({
@@ -109,6 +131,12 @@ export default defineComponent({
109
131
  editId: 0,
110
132
  customTable: null,
111
133
  });
134
+ // 公共拓展字段复用标准拓展字段的配置归一化规则。
135
+ const normalizedCustomConfigItems = computed(() => handleCustomConfigItems(props.customConfigItems));
136
+
137
+ // 表格列中支持编辑的配置项
138
+ const editableCustomConfigItems = computed(() => getEditableCustomConfigItems(normalizedCustomConfigItems.value));
139
+
112
140
  const closeModal = () => {
113
141
  emit("closeModal");
114
142
  };
@@ -237,6 +265,10 @@ export default defineComponent({
237
265
  },
238
266
  })
239
267
  }
268
+ // 自定义列表项统一插入必填列之前,保留外部传入的其他表格配置。
269
+ const fillColumnIndex = _deepColumns.findIndex((column) => column.key === 'fill');
270
+ const customColumns = getColumnsVisibleCustomConfigItems(normalizedCustomConfigItems.value);
271
+ _deepColumns.splice(fillColumnIndex, 0, ...customColumns);
240
272
  return _deepColumns;
241
273
  });
242
274
 
@@ -257,6 +289,31 @@ export default defineComponent({
257
289
  loadLanguageAsync(newLang);
258
290
  });
259
291
 
292
+ // 列表快捷切换复用公共拓展字段编辑接口,仅提交当前配置字段及必要参数。
293
+ const handleCustomConfigChange = (value, record, item) => {
294
+ if (!record?.id) {
295
+ return;
296
+ }
297
+ const changeValue = getCustomConfigEditorChange(value, record, item);
298
+ emit("request", {
299
+ params: {
300
+ method: 'put',
301
+ server: `/${props.server}`,
302
+ url: `/common/extra/field/${record.id}`,
303
+ extra: {
304
+ alias: props.alias,
305
+ ...props.expandFieldsExtraParams,
306
+ id: record.id,
307
+ [item.key]: changeValue,
308
+ },
309
+ },
310
+ success: (res) => {
311
+ state.customTable?.refresh();
312
+ message.success(res?.msg || t('common.save.success'));
313
+ },
314
+ });
315
+ };
316
+
260
317
  return {
261
318
  t,
262
319
  ...toRefs(state),
@@ -268,6 +325,9 @@ export default defineComponent({
268
325
  handleDelete,
269
326
  handleEdit,
270
327
  editSuccess,
328
+ getCustomConfigEditorProps,
329
+ handleCustomConfigChange,
330
+ editableCustomConfigItems,
271
331
  };
272
332
  },
273
333
  });
@@ -514,5 +514,6 @@
514
514
  "DepartmentUser.index.555981-0": "清除選項",
515
515
  "DepartmentUser.index.555981-1": "展開",
516
516
  "DepartmentUser.index.555981-2": "收起",
517
- "BlMobileAnnexUpload.index.676543-0": "此 QR Code 於 15 分鐘內有效"
517
+ "BlMobileAnnexUpload.index.676543-0": "此 QR Code 於 15 分鐘內有效",
518
+ "AttchmentInfo.userShow": "對業戶开放"
518
519
  }
@@ -514,5 +514,6 @@
514
514
  "DepartmentUser.index.555981-0": "Clear selection",
515
515
  "DepartmentUser.index.555981-1": "Expand",
516
516
  "DepartmentUser.index.555981-2": "Collapse",
517
- "BlMobileAnnexUpload.index.676543-0": "This QR code is valid for 15 minutes"
517
+ "BlMobileAnnexUpload.index.676543-0": "This QR code is valid for 15 minutes",
518
+ "AttchmentInfo.userShow": "Show to Users"
518
519
  }
@@ -514,5 +514,6 @@
514
514
  "DepartmentUser.index.555981-0": "選択をクリア",
515
515
  "DepartmentUser.index.555981-1": "展開",
516
516
  "DepartmentUser.index.555981-2": "折りたたむ",
517
- "BlMobileAnnexUpload.index.676543-0": "このQRコードは15分間有効です"
517
+ "BlMobileAnnexUpload.index.676543-0": "このQRコードは15分間有効です",
518
+ "AttchmentInfo.userShow": "ユーザー表示"
518
519
  }
@@ -514,5 +514,6 @@
514
514
  "CheckedHousingResources.index.074796-0": "選擇品牌",
515
515
  "BlMobileAnnexUpload.index-0": "查看二維碼",
516
516
  "BlMobileAnnexUpload.index-1": "使用手機掃碼上載",
517
- "modules.ListFieldModal.082665-0": "選項內容不能留空"
517
+ "modules.ListFieldModal.082665-0": "選項內容不能留空",
518
+ "AttchmentInfo.userShow": "對業戶开放"
518
519
  }
@@ -514,5 +514,6 @@
514
514
  "DepartmentUser.index.555981-0": "清除选项",
515
515
  "DepartmentUser.index.555981-1": "展开",
516
516
  "DepartmentUser.index.555981-2": "收起",
517
- "BlMobileAnnexUpload.index.676543-0": "该二维码十五分钟内有效"
517
+ "BlMobileAnnexUpload.index.676543-0": "该二维码十五分钟内有效",
518
+ "AttchmentInfo.userShow": "对业户开放"
518
519
  }