bl-common-vue3 3.8.131 → 3.8.133

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.133",
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">
@@ -1342,7 +1342,7 @@
1342
1342
  :url="requestInfo?.serviceUrl"
1343
1343
  :service="requestInfo?.service"
1344
1344
  :method="requestInfo?.serviceType"
1345
- :params="listParams"
1345
+ :params="exportParams"
1346
1346
  :limit="exportLimit"
1347
1347
  :columnstitle="exportColumnsTitle"
1348
1348
  :merge_options="exportMergeOptions"
@@ -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: [],
@@ -1891,6 +1892,7 @@ export default defineComponent({
1891
1892
  dictMenuAlias: "", // 后端返回的字典标识
1892
1893
  isExport: false,
1893
1894
  listParams: {},
1895
+ exportParams: {},
1894
1896
  exportResultData: [], // 导出结果处理
1895
1897
  importVisible: false,
1896
1898
  tableScrollHeight: 0,
@@ -2439,7 +2441,8 @@ export default defineComponent({
2439
2441
  let columnIndexObj = {};
2440
2442
  let haveSlot = false;
2441
2443
  cloneDeep(realColumns.value).forEach((columnItem, index) => {
2442
- // 从后往前处理
2444
+ // 不导出的展示列可能没有 dataIndex,也可能返回 VNode,不能参与数据转换。
2445
+ if (columnItem.exportHidden) return;
2443
2446
  let virtualDataIndex = replaceObjDataIndex(columnItem.dataIndex);
2444
2447
  if (columnItem.columnType) {
2445
2448
  columnIndexObj[virtualDataIndex] = {
@@ -2487,20 +2490,21 @@ export default defineComponent({
2487
2490
  localRowSelection.value?.selectedRowKeys
2488
2491
  );
2489
2492
  }
2493
+ // 导出组件会更新分页并追加选中条件,使用快照避免污染列表的后续查询。
2494
+ state.exportParams = cloneDeep(state.listParams);
2495
+ state.exportParams.filters = (state.exportParams.filters || []).filter(
2496
+ (item) => item.field !== "ids"
2497
+ );
2490
2498
  if (localRowSelection.value?.selectedRowKeys.length > 0) {
2491
- state.listParams.filters.push({
2499
+ state.exportParams.filters.push({
2492
2500
  field: "ids",
2493
2501
  type: "custom",
2494
- value: localRowSelection.value?.selectedRowKeys,
2502
+ value: [...localRowSelection.value.selectedRowKeys],
2495
2503
  });
2496
- } else {
2497
- state.listParams.filters = state.listParams.filters.filter(
2498
- (item) => item.field != "ids"
2499
- );
2500
2504
  }
2501
2505
  if (props.customExport) {
2502
2506
  /* 自定义导出 */
2503
- props.customExport(state.listParams);
2507
+ props.customExport(state.exportParams);
2504
2508
  } else {
2505
2509
  /* 默认导出 */
2506
2510
  state.isExport = true;
@@ -2814,6 +2818,12 @@ export default defineComponent({
2814
2818
  };
2815
2819
  });
2816
2820
 
2821
+ // 菜单按钮使用 stop 阻止冒泡,需要主动收起,避免弹层打开后菜单仍遮挡操作。
2822
+ const handleMoreAction = (callback, event) => {
2823
+ state.moreActionVisible = false;
2824
+ return callback?.(event);
2825
+ };
2826
+
2817
2827
  // 刷新表格数据
2818
2828
  const refresh = (isInit = false) => {
2819
2829
  state.hasDictionary = false;
@@ -2821,6 +2831,14 @@ export default defineComponent({
2821
2831
  loadData(isInit);
2822
2832
  };
2823
2833
 
2834
+ // 业务端异步取得完整列定义后更新表头,保留当前筛选、分页和勾选状态。
2835
+ const setColumns = async (columns) => {
2836
+ state.defaultColumns = cloneDeep(columns || []);
2837
+ state.initColumns = cloneDeep(state.defaultColumns);
2838
+ await getLocalColumns();
2839
+ createColumnSlots();
2840
+ };
2841
+
2824
2842
  const actionBtnShow = computed(
2825
2843
  () =>
2826
2844
  ({ item, text, record, index, column }) =>
@@ -4111,6 +4129,7 @@ export default defineComponent({
4111
4129
  expose({
4112
4130
  refresh,
4113
4131
  loadData,
4132
+ setColumns,
4114
4133
  setExportData,
4115
4134
  getFilterList,
4116
4135
  clearData,
@@ -4370,6 +4389,7 @@ export default defineComponent({
4370
4389
  columnsProcess,
4371
4390
  getDateValueFormat,
4372
4391
  handleOperationClick,
4392
+ handleMoreAction,
4373
4393
  refresh,
4374
4394
  roomClick,
4375
4395
  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>