bl-common-vue3 3.8.131 → 3.8.132

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.131",
3
+ "version": "3.8.132",
4
4
  "main": "index.js",
5
5
  "module": "index.js",
6
6
  "description": "bailing vue3 common components lib",
@@ -760,11 +760,11 @@
760
760
  </a-button>
761
761
  </template>
762
762
 
763
- <a-dropdown v-if="hasMoreAction">
763
+ <a-dropdown v-if="hasMoreAction" v-model:visible="moreActionVisible">
764
764
  <template #overlay>
765
765
  <a-menu>
766
766
  <a-menu-item key="into" v-if="!excludeImport">
767
- <a-button type="text" @click.stop="handleImport">
767
+ <a-button type="text" @click.stop="handleMoreAction(handleImport, $event)">
768
768
  <template #icon>
769
769
  <bl-icon type="daoru" />
770
770
  </template>
@@ -772,7 +772,7 @@
772
772
  </a-button>
773
773
  </a-menu-item>
774
774
  <a-menu-item key="derive" v-if="!excludeExport">
775
- <a-button type="text" @click.stop="handleExport">
775
+ <a-button type="text" @click.stop="handleMoreAction(handleExport, $event)">
776
776
  <template #icon>
777
777
  <bl-icon type="daochu" />
778
778
  </template>
@@ -782,7 +782,7 @@
782
782
  <a-menu-item
783
783
  v-if="showDictSetting && dictMenuAlias"
784
784
  key="dictionary"
785
- @click.stop="handleDictionarySetting">
785
+ @click.stop="handleMoreAction(handleDictionarySetting, $event)">
786
786
  <a-button type="text">
787
787
  <template #icon>
788
788
  <bl-icon type="cidian" />
@@ -794,7 +794,7 @@
794
794
  <a-menu-item
795
795
  v-if="ApprovalProcessSettings && approvalList.length > 0"
796
796
  key="Approval_process"
797
- @click.stop="handleApprovalProcess">
797
+ @click.stop="handleMoreAction(handleApprovalProcess, $event)">
798
798
  <a-button type="text">
799
799
  <template #icon>
800
800
  <bl-icon type="shenpiliucheng" />
@@ -806,7 +806,7 @@
806
806
  <a-menu-item
807
807
  v-if="showExpandFields"
808
808
  key="expandFields"
809
- @click.stop="handleExpandFieldsSetting">
809
+ @click.stop="handleMoreAction(handleExpandFieldsSetting, $event)">
810
810
  <a-button type="text">
811
811
  <template #icon>
812
812
  <bl-icon type="tuozhanziduan" />
@@ -815,7 +815,7 @@
815
815
  </a-button>
816
816
  </a-menu-item>
817
817
  <a-menu-item key="setting" v-if="tableId">
818
- <a-button type="text" @click.stop="handleColumnSetting">
818
+ <a-button type="text" @click.stop="handleMoreAction(handleColumnSetting, $event)">
819
819
  <template #icon>
820
820
  <bl-icon type="lieshezhi" />
821
821
  </template>
@@ -823,7 +823,7 @@
823
823
  </a-button>
824
824
  </a-menu-item>
825
825
  <a-menu-item key="setting" v-if="dataMigration">
826
- <a-button type="text" @click.stop="handledataMigration">
826
+ <a-button type="text" @click.stop="handleMoreAction(handledataMigration, $event)">
827
827
  <template #icon>
828
828
  <bl-icon type="shujuqianyi" />
829
829
  </template>
@@ -844,7 +844,7 @@
844
844
  </template>
845
845
  <template v-for="(child) in item.children" :key="child.key">
846
846
  <a-menu-item v-if="child?.show ?? true" :key="child.key">
847
- <a-button v-bind="{ ...(child?.props || {}), }" @click.stop="child?.onClick">
847
+ <a-button v-bind="{ ...(child?.props || {}), }" @click.stop="handleMoreAction(child?.onClick, $event)">
848
848
  <template #icon v-if="buttonIconSlots.indexOf(`${child.key}`) > -1">
849
849
  <slot :name="`buttonIcon-${child.key}`"></slot>
850
850
  </template>
@@ -856,7 +856,7 @@
856
856
  </template>
857
857
  <template v-if="((item?.show ?? true) && !item?.children?.length)">
858
858
  <a-menu-item :key="item.key">
859
- <a-button v-bind="{ ...(item?.props || {}), }" @click.stop="item?.onClick">
859
+ <a-button v-bind="{ ...(item?.props || {}), }" @click.stop="handleMoreAction(item?.onClick, $event)">
860
860
  <template
861
861
  #icon
862
862
  v-if="buttonIconSlots.indexOf(`${item.key}`) > -1">
@@ -1878,6 +1878,7 @@ export default defineComponent({
1878
1878
  },
1879
1879
  setup(props, { slots, expose, emit, attrs }) {
1880
1880
  const state = reactive({
1881
+ moreActionVisible: false,
1881
1882
  localLoading: false,
1882
1883
  selectedRowKeys: [],
1883
1884
  selectedRows: [],
@@ -2814,6 +2815,12 @@ export default defineComponent({
2814
2815
  };
2815
2816
  });
2816
2817
 
2818
+ // 菜单按钮使用 stop 阻止冒泡,需要主动收起,避免弹层打开后菜单仍遮挡操作。
2819
+ const handleMoreAction = (callback, event) => {
2820
+ state.moreActionVisible = false;
2821
+ return callback?.(event);
2822
+ };
2823
+
2817
2824
  // 刷新表格数据
2818
2825
  const refresh = (isInit = false) => {
2819
2826
  state.hasDictionary = false;
@@ -2821,6 +2828,14 @@ export default defineComponent({
2821
2828
  loadData(isInit);
2822
2829
  };
2823
2830
 
2831
+ // 业务端异步取得完整列定义后更新表头,保留当前筛选、分页和勾选状态。
2832
+ const setColumns = async (columns) => {
2833
+ state.defaultColumns = cloneDeep(columns || []);
2834
+ state.initColumns = cloneDeep(state.defaultColumns);
2835
+ await getLocalColumns();
2836
+ createColumnSlots();
2837
+ };
2838
+
2824
2839
  const actionBtnShow = computed(
2825
2840
  () =>
2826
2841
  ({ item, text, record, index, column }) =>
@@ -4111,6 +4126,7 @@ export default defineComponent({
4111
4126
  expose({
4112
4127
  refresh,
4113
4128
  loadData,
4129
+ setColumns,
4114
4130
  setExportData,
4115
4131
  getFilterList,
4116
4132
  clearData,
@@ -4370,6 +4386,7 @@ export default defineComponent({
4370
4386
  columnsProcess,
4371
4387
  getDateValueFormat,
4372
4388
  handleOperationClick,
4389
+ handleMoreAction,
4373
4390
  refresh,
4374
4391
  roomClick,
4375
4392
  toOwnerDetail,
@@ -0,0 +1,19 @@
1
+ import dayjs from 'dayjs';
2
+ import customParseFormat from 'dayjs/plugin/customParseFormat';
3
+
4
+ dayjs.extend(customParseFormat);
5
+
6
+ export const EXTRA_DATE_FORMATS = Object.freeze({
7
+ 1: 'YYYY-MM-DD',
8
+ 2: 'YYYY-MM-DD HH:mm',
9
+ 3: 'HH:mm',
10
+ });
11
+
12
+ // 兼容历史秒级值及日期类型调整;展示和再次保存均统一为分钟精度。
13
+ export const formatExtraDate = (value, dateType) => {
14
+ if (value == null || value === '') return value;
15
+ const format = EXTRA_DATE_FORMATS[dateType];
16
+ if (!format) return value;
17
+ const parsed = dayjs(value, ['YYYY-MM-DD HH:mm', 'YYYY-MM-DD HH:mm:ss', 'YYYY-MM-DD', 'HH:mm', 'HH:mm:ss'], true);
18
+ return parsed.isValid() ? parsed.format(format) : value;
19
+ };
@@ -65,10 +65,10 @@
65
65
  <a-date-picker v-model:value="value" :disabled="disabled" style="width: 100%" @change="changediv" format="YYYY-MM-DD" valueFormat="YYYY-MM-DD" />
66
66
  </div>
67
67
  <div v-if="data?.date_type == 2">
68
- <a-date-picker show-time v-model:value="value" :disabled="disabled" style="width: 100%" @change="changediv" format="YYYY-MM-DD HH:mm" valueFormat="YYYY-MM-DD HH:mm" />
68
+ <a-date-picker :show-time="{ format: 'HH:mm', showSecond: false }" v-model:value="value" :disabled="disabled" style="width: 100%" @change="changediv" format="YYYY-MM-DD HH:mm" valueFormat="YYYY-MM-DD HH:mm" />
69
69
  </div>
70
70
  <div v-if="data?.date_type == 3">
71
- <a-time-picker v-model:value="value" style="width: 100%" :disabled="disabled" @change="changediv" format="HH:mm" valueFormat="HH:mm" />
71
+ <a-time-picker :show-second="false" v-model:value="value" style="width: 100%" :disabled="disabled" @change="changediv" format="HH:mm" valueFormat="HH:mm" />
72
72
  </div>
73
73
  </div>
74
74
  <!-- 下拉单选 -->
@@ -78,6 +78,7 @@
78
78
  v-model:value="value"
79
79
  style="width: 100%"
80
80
  :options="data?.option_value"
81
+ :allowClear="!data?.fill"
81
82
  @change="changediv"
82
83
  :disabled="disabled"
83
84
  ></a-select>
@@ -90,6 +91,7 @@
90
91
  v-model:value="value"
91
92
  style="width: 100%"
92
93
  :options="data?.option_value"
94
+ :allowClear="!data?.fill"
93
95
  @change="changediv"
94
96
  :disabled="disabled"
95
97
  ></a-select>
@@ -137,7 +139,7 @@
137
139
  :before-upload="beforeUpload"
138
140
  :accept="accept"
139
141
  >
140
- <a-button v-if="value.length < data?.option_max">
142
+ <a-button v-if="!data?.option_max || value.length < data?.option_max">
141
143
  <UploadOutlined />
142
144
  {{ t('ExtraFieldsItem.index.831918-0') }}
143
145
  </a-button>
@@ -170,7 +172,8 @@
170
172
  </div>
171
173
  <!-- 数字 -->
172
174
  <div v-if="data?.fields_type == 'number'">
173
- {{data?.value || '--'}}
175
+ <!-- 数字 0 是有效字段值,只有未填写时才显示占位符。 -->
176
+ {{data?.value === '' ? '--' : (data?.value ?? '--')}}
174
177
  </div>
175
178
  <!-- 单选 -->
176
179
  <div v-if="data?.fields_type == 'radio'">
@@ -196,7 +199,7 @@
196
199
  </div>
197
200
  <!-- 日期时间 -->
198
201
  <div v-if="data?.fields_type == 'date'">
199
- {{data?.value || '--'}}
202
+ {{formatExtraDate(data?.value, data?.date_type) || '--'}}
200
203
  </div>
201
204
  <!-- 下拉单选 -->
202
205
  <div v-if="data?.fields_type == 'select_single'">
@@ -257,10 +260,11 @@
257
260
  </template>
258
261
 
259
262
  <script>
260
- import { cloneDeep } from "lodash";
263
+ import { cloneDeep } from "lodash";
261
264
  import {
262
265
  defineComponent,
263
266
  reactive,
267
+ onUnmounted,
264
268
  ref,
265
269
  toRefs,
266
270
  } from "vue";
@@ -268,9 +272,11 @@ import { message } from "ant-design-vue";
268
272
  import {PlusOutlined,DownloadOutlined,UploadOutlined,DeleteOutlined} from "@ant-design/icons-vue";
269
273
  import {t, loadLanguageAsync} from "../../locale";
270
274
  import utils from "../../common/utils/util";
275
+ import { formatExtraDate } from './date';
271
276
 
272
277
  export default defineComponent({
273
278
  name: "ExtraFieldsItem",
279
+ emits: ["close", "uploading"],
274
280
  components: {PlusOutlined , DownloadOutlined , UploadOutlined , DeleteOutlined},
275
281
  props: {
276
282
  Objectdata: {
@@ -306,6 +312,7 @@ export default defineComponent({
306
312
  }
307
313
 
308
314
  const handleInventoryUploadChange = (info) => {
315
+ emit('uploading', info.fileList.some((file) => file.status === 'uploading'));
309
316
  let resFileList = [...info.fileList];
310
317
  if (resFileList?.length) {
311
318
  resFileList.map((file, index) => {
@@ -359,19 +366,16 @@ export default defineComponent({
359
366
  // 上传视频
360
367
  const fileListVideo = ref([]);
361
368
  const handleChange = (info) => {
362
- let resFileList = [...info.fileList];
363
- let result = resFileList.slice(-1);
364
- result = result[0];
365
- if (result && result.response) {
369
+ emit('uploading', info.fileList.some((file) => file.status === 'uploading'));
370
+ // 连续选择附件会并行上传,应处理本次变化的文件,并保留其他未完成任务。
371
+ const result = info.file;
372
+ if (result?.status === 'done' && result.response) {
366
373
  if (result.response.code == 1000) {
367
- let list = {}
368
- list.fileUrl = result.response?.data?.fileUrl
369
- list.name = resFileList.slice(-1)[0].name
370
- state.value.push(list)
371
- fileListVideo.value = []
374
+ state.value.push({fileUrl: result.response.data?.fileUrl, name: result.name});
372
375
  } else {
373
376
  message.warning(result.response.msg);
374
377
  }
378
+ fileListVideo.value = info.fileList.filter((file) => file.uid !== result.uid);
375
379
  }
376
380
  changediv()
377
381
  };
@@ -398,16 +402,18 @@ export default defineComponent({
398
402
  changediv()
399
403
  }
400
404
  if(state.data.fields_type == 'radio'){
401
- state.value = state.data.value || ''
405
+ state.value = state.data.value ?? ''
402
406
  }
403
407
  if(state.data.fields_type == 'multiple_choice'){
404
- state.value = state.data.value || ''
408
+ state.value = state.data.value || []
405
409
  }
406
410
  if(state.data.fields_type == 'date'){
407
- state.value = state.data.value || ''
411
+ state.value = formatExtraDate(state.data.value, state.data.date_type) || ''
412
+ // 历史秒值同步到表单模型,保证未操作日期控件时再次保存也使用分钟精度。
413
+ if (state.data.value && state.value !== state.data.value) changediv()
408
414
  }
409
415
  if(state.data.fields_type == 'select_single'){
410
- state.value = state.data.value || null
416
+ state.value = state.data.value ?? null
411
417
  }
412
418
  if(state.data.fields_type == 'select_multiple'){
413
419
  state.value = state.data.value || []
@@ -440,12 +446,15 @@ export default defineComponent({
440
446
 
441
447
  loadLanguageAsync(utils.getLang());
442
448
 
443
- window.addEventListener('setBLLang', (event) => {
444
- const newLang = event.newValue; // 获取新的语言值
445
- loadLanguageAsync(newLang);
446
- });
449
+ // 列表单元格会频繁卸载,必须同步释放语言事件监听。
450
+ const handleLanguageChange = (event) => {
451
+ loadLanguageAsync(event.newValue);
452
+ };
453
+ window.addEventListener('setBLLang', handleLanguageChange);
454
+ onUnmounted(() => window.removeEventListener('setBLLang', handleLanguageChange));
447
455
 
448
456
  return {
457
+ formatExtraDate,
449
458
  t,
450
459
  ...toRefs(state),
451
460
  init,
@@ -0,0 +1,86 @@
1
+ <template>
2
+ <aside class="extra-field-preview">
3
+ <div class="preview-heading"><span class="live-dot"></span>{{ t('PublicExtensionField.previewTitle') }}</div>
4
+ <div class="preview-card">
5
+ <div class="preview-section">{{ t('PublicExtensionField.previewSection') }}</div>
6
+ <label class="preview-label"><span v-if="field.fill" class="required-mark">*</span>{{ field.fields_name || t('PublicExtensionField.unnamed') }}</label>
7
+ <a-input v-if="field.fields_type === 'single_text'" v-model:value="value" :placeholder="placeholder" :maxlength="maxLength" />
8
+ <a-textarea v-else-if="field.fields_type === 'multiple_text'" v-model:value="value" :placeholder="placeholder" :maxlength="maxLength" :rows="3" />
9
+ <a-input-number v-else-if="field.fields_type === 'number'" v-model:value="value" :placeholder="placeholder" :min="minValue" :max="maxValue" class="preview-control" />
10
+ <a-radio-group v-else-if="field.fields_type === 'radio'" v-model:value="value" :options="options" />
11
+ <a-checkbox-group v-else-if="field.fields_type === 'multiple_choice'" v-model:value="value" :options="options" />
12
+ <a-select v-else-if="field.fields_type === 'select_single' || field.fields_type === 'select_multiple'"
13
+ v-model:value="value" :mode="field.fields_type === 'select_multiple' ? 'multiple' : undefined"
14
+ :options="options" :placeholder="t('common.please.choose')" :allowClear="!field.fill" class="preview-control" />
15
+ <template v-else-if="field.fields_type === 'date'">
16
+ <a-time-picker v-if="field.date_type == 3" v-model:value="value" format="HH:mm" value-format="HH:mm" :show-second="false" class="preview-control" />
17
+ <a-date-picker v-else v-model:value="value" :format="dateFormat" :value-format="dateFormat"
18
+ :show-time="field.date_type == 2 ? { format: 'HH:mm', showSecond: false } : false" class="preview-control" />
19
+ </template>
20
+ <!-- 维护预览不上传文件,避免试填配置时产生业务附件。 -->
21
+ <div v-else-if="field.fields_type === 'image' || field.fields_type === 'attachment'" class="upload-preview">
22
+ <PictureOutlined v-if="field.fields_type === 'image'" /><UploadOutlined v-else />
23
+ <div>{{ t(field.fields_type === 'image' ? 'ExtraFieldsItem.index.243488-0' : 'ExtraFieldsItem.index.831918-0') }}</div>
24
+ </div>
25
+ </div>
26
+ <p class="preview-description">{{ t('PublicExtensionField.previewHint') }}</p>
27
+ <div class="preview-note">{{ t(field.fill ? 'PublicExtensionField.requiredHint' : 'PublicExtensionField.optionalHint') }}</div>
28
+ </aside>
29
+ </template>
30
+
31
+ <script>
32
+ import { computed, defineComponent, ref, watch } from 'vue';
33
+ import { Input, InputNumber, Radio, Checkbox, Select, DatePicker, TimePicker } from 'ant-design-vue';
34
+ import { PictureOutlined, UploadOutlined } from '@ant-design/icons-vue';
35
+ import { t } from '../../../locale';
36
+ import { EXTRA_DATE_FORMATS } from '../../ExtraFieldsItem/date';
37
+
38
+ export default defineComponent({
39
+ name: 'ExtraFieldPreview',
40
+ components: {
41
+ 'a-input': Input, 'a-textarea': Input.TextArea, 'a-input-number': InputNumber,
42
+ 'a-radio-group': Radio.Group, 'a-checkbox-group': Checkbox.Group,
43
+ 'a-select': Select, 'a-date-picker': DatePicker, 'a-time-picker': TimePicker,
44
+ PictureOutlined, UploadOutlined,
45
+ },
46
+ props: { field: { type: Object, required: true } },
47
+ setup(props) {
48
+ const value = ref();
49
+ const options = computed(() => (props.field.option_value || []).filter((item) => item.label));
50
+ const placeholder = computed(() => props.field.placeholder || t('common.please.enter'));
51
+ const dateFormat = computed(() => EXTRA_DATE_FORMATS[props.field.date_type] || EXTRA_DATE_FORMATS[1]);
52
+ const toLimit = (limit) => limit === '' || limit == null ? undefined : Number(limit);
53
+ const minValue = computed(() => toLimit(props.field.min_length));
54
+ const maxValue = computed(() => toLimit(props.field.max_length));
55
+ const maxLength = computed(() => maxValue.value > 0 ? maxValue.value : undefined);
56
+ watch(() => [props.field.fields_type, props.field.date_type, props.field.default_value], () => {
57
+ if (['single_text', 'multiple_text', 'number'].includes(props.field.fields_type)) {
58
+ value.value = props.field.default_value ?? '';
59
+ } else {
60
+ value.value = ['select_multiple', 'multiple_choice'].includes(props.field.fields_type) ? [] : undefined;
61
+ }
62
+ }, { immediate: true });
63
+ watch(() => [props.field.fill, options.value], () => {
64
+ if (props.field.fields_type === 'select_single' && props.field.fill && options.value.length === 1) {
65
+ value.value = options.value[0].value;
66
+ }
67
+ }, { immediate: true, deep: true });
68
+ return { t, value, options, placeholder, dateFormat, minValue, maxValue, maxLength };
69
+ },
70
+ });
71
+ </script>
72
+
73
+ <style lang="less" scoped>
74
+ .extra-field-preview { padding: 24px; background: #f7f9fc; border-left: 1px solid #edf0f4; }
75
+ .preview-heading { display: flex; align-items: center; gap: 8px; margin-bottom: 20px; color: #758499; font-size: 12px; }
76
+ .live-dot { width: 6px; height: 6px; border-radius: 50%; background: #52b89a; }
77
+ .preview-card { padding: 20px 18px 24px; border: 1px solid #e4eaf2; border-radius: 6px; background: #fff; }
78
+ .preview-section { padding-bottom: 14px; margin-bottom: 18px; border-bottom: 1px solid #edf0f4; color: #718095; font-size: 13px; }
79
+ .preview-label { display: block; margin-bottom: 10px; overflow-wrap: anywhere; }
80
+ .required-mark { margin-right: 4px; color: #ff4d4f; }
81
+ .preview-control { width: 100%; }
82
+ .preview-description { margin: 16px 0; color: #8b97a8; line-height: 1.8; font-size: 12px; }
83
+ .preview-note { padding: 9px 12px; border-radius: 4px; background: #edf3fb; color: #6584a9; font-size: 12px; }
84
+ .upload-preview { padding: 24px 10px; text-align: center; border: 1px dashed #cfd9e6; border-radius: 4px; color: #8295ae; }
85
+ @media (max-width: 767px) { .extra-field-preview { border-left: 0; border-top: 1px solid #edf0f4; } }
86
+ </style>