cloud-web-corejs 1.0.282 → 1.0.284

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.
Files changed (25) hide show
  1. package/package.json +1 -1
  2. package/src/components/excelExport/conditionExportConfig.js +112 -0
  3. package/src/components/excelExport/exportType.js +8 -0
  4. package/src/components/excelExport/index.vue +7 -1
  5. package/src/components/excelExport/mixins.js +410 -259
  6. package/src/components/excelImport/index.vue +1 -1
  7. package/src/components/excelImport/mixins.js +56 -8
  8. package/src/components/table/vxeFilter/mixin.js +28 -18
  9. package/src/components/xform/docs/2026-07 table/344/270/216excelExport/344/277/256/345/244/215/345/217/212/345/257/274/345/207/272/344/274/230/345/214/226.md" +11 -4
  10. package/src/components/xform/form-designer/form-widget/dialog/importDialog.vue +190 -185
  11. package/src/components/xform/form-designer/form-widget/dialog/importDialogMixin.js +2641 -1774
  12. package/src/components/xform/form-designer/form-widget/field-widget/import-button-widget.vue +18 -5
  13. package/src/components/xform/form-designer/form-widget/field-widget/select-export-item-button-widget.vue +2 -1
  14. package/src/components/xform/form-designer/form-widget/field-widget/table-export-button-widget.vue +2 -1
  15. package/src/components/xform/form-designer/setting-panel/property-editor/field-import-button/import-button-editor.vue +62 -1
  16. package/src/components/xform/form-designer/setting-panel/property-editor/field-import-button/import2-button-editor.vue +236 -96
  17. package/src/components/xform/form-designer/setting-panel/property-editor/field-table-export-button/empty-number-input.vue +51 -0
  18. package/src/components/xform/form-designer/setting-panel/property-editor/field-table-export-button/select-export-item-button-editor.vue +27 -2
  19. package/src/components/xform/form-designer/setting-panel/property-editor/field-table-export-button/table-export-button-editor.vue +33 -3
  20. package/src/components/xform/form-designer/widget-panel/widgetsConfig.js +31 -0
  21. package/src/components/xform/form-render/container-item/data-table-mixin.js +99 -11
  22. package/src/components/xform/utils/tableColumnHelper.js +46 -4
  23. package/src/utils/assetUrl.js +19 -0
  24. package/src/utils/importLimit.js +72 -0
  25. package/src/utils/vab.js +5 -1
@@ -13,8 +13,23 @@
13
13
  <el-switch v-model="optionModel.showImageAtTable" @change="changeShowImageAtTable"></el-switch>
14
14
  </el-form-item>
15
15
  <el-form-item label="导出每页查询数量" class="form-item-label-top">
16
- <base-input-number v-model="optionModel.exportPageSize"
17
- :max="optionModel.showImageAtTable?150:1000"></base-input-number>
16
+ <empty-number-input v-model="optionModel.exportPageSize"
17
+ :max="optionModel.showImageAtTable?150:1000"
18
+ :placeholder="'默认 ' + defaultPageSize"></empty-number-input>
19
+ </el-form-item>
20
+ <el-form-item label="最大查询总数" class="form-item-label-top">
21
+ <empty-number-input v-model="optionModel.maxQueryTotal"
22
+ :placeholder="'默认 ' + defaultMaxQueryTotal"></empty-number-input>
23
+ <div class="export-limit-tip">
24
+ 条件导出最多拉取的行数。留空则使用全局逻辑参数(默认 {{ defaultMaxQueryTotal }})
25
+ </div>
26
+ </el-form-item>
27
+ <el-form-item label="每个文件最大数据量" class="form-item-label-top">
28
+ <empty-number-input v-model="optionModel.limitFileSize"
29
+ :placeholder="'默认 ' + defaultLimitFileSize"></empty-number-input>
30
+ <div class="export-limit-tip">
31
+ 单个 Excel 最多行数,超出则分多个文件。留空则使用全局逻辑参数(默认 {{ defaultLimitFileSize }})
32
+ </div>
18
33
  </el-form-item>
19
34
  <el-form-item label="导出参数" label-width="150px">
20
35
  <a href="javascript:void(0);" class="a-link link-oneLind"
@@ -30,10 +45,15 @@
30
45
  import i18n from "../../../../../../components/xform/utils/i18n";
31
46
  import eventMixin
32
47
  from "../../../../../../components/xform/form-designer/setting-panel/property-editor/event-handler/eventMixin";
48
+ import { DEFAULT_CONDITION_EXPORT_CONFIG } from "@base/components/excelExport/conditionExportConfig";
49
+ import emptyNumberInput from "./empty-number-input.vue";
33
50
 
34
51
  export default {
35
52
  name: "table-export-button-editor",
36
53
  mixins: [i18n, eventMixin],
54
+ components: {
55
+ emptyNumberInput,
56
+ },
37
57
  props: {
38
58
  designer: Object,
39
59
  selectedWidget: Object,
@@ -42,6 +62,9 @@ export default {
42
62
  data() {
43
63
  return {
44
64
  eventParams: [],
65
+ defaultPageSize: DEFAULT_CONDITION_EXPORT_CONFIG.pageSize,
66
+ defaultMaxQueryTotal: DEFAULT_CONDITION_EXPORT_CONFIG.maxQueryTotal,
67
+ defaultLimitFileSize: DEFAULT_CONDITION_EXPORT_CONFIG.limitFileSize,
45
68
  };
46
69
  },
47
70
  methods: {
@@ -57,4 +80,11 @@ export default {
57
80
  };
58
81
  </script>
59
82
 
60
- <style scoped></style>
83
+ <style scoped>
84
+ .export-limit-tip {
85
+ line-height: 18px;
86
+ color: #909399;
87
+ font-size: 12px;
88
+ margin-top: 4px;
89
+ }
90
+ </style>
@@ -3254,6 +3254,9 @@ export const advancedFields = [
3254
3254
  tableRef: "",
3255
3255
  tableExportParam: "",
3256
3256
  showImageAtTable: false,
3257
+ exportPageSize: null,
3258
+ maxQueryTotal: null,
3259
+ limitFileSize: null,
3257
3260
  tableExportFlag: 1,
3258
3261
 
3259
3262
  onCreated: "",
@@ -3316,6 +3319,9 @@ export const advancedFields = [
3316
3319
  tableRef: "",
3317
3320
  tableExportParam: "",
3318
3321
  showImageAtTable: false,
3322
+ exportPageSize: null,
3323
+ maxQueryTotal: null,
3324
+ limitFileSize: null,
3319
3325
  selectExportItemFlag: 1,
3320
3326
  exportItemConditionEnabled: !1,
3321
3327
  // 按钮级明细导出配置:三项各自独立,留空的那项沿用数据表格属性上维护的
@@ -3403,12 +3409,19 @@ export const advancedFields = [
3403
3409
  ...defaultWidgetShowRuleConfig,
3404
3410
  importMultiple: false,
3405
3411
  importMultiSize: 1,
3412
+ // 批量导入时给每行补一层实体信封以承载附件列;默认关=仍发裸行数组
3413
+ importMultiAttachEnvelope: false,
3406
3414
  importFileLimitSize: 200,
3407
3415
  importEntity: "",
3408
3416
  importAttachCode: "",
3409
3417
  importAttachName: "",
3410
3418
  importScriptCode: "",
3411
3419
  onBeforeImport: "",
3420
+ // 逐行补默认值脚本:返回对象,Object.assign 到该行后再发包
3421
+ importDefaultValue: "",
3422
+ onValidateRow: "",
3423
+ // 必填校验方式:"" 默认(逐行标记失败)/ "strict" 解析期整批硬拦 / "off" 不校验
3424
+ requiredCheckMode: "",
3412
3425
  enabledImportPreHandle: false,
3413
3426
  tableRef: "",
3414
3427
  onSuccessImport: "",
@@ -3458,6 +3471,24 @@ export const advancedFields = [
3458
3471
  importAttachCode: "",
3459
3472
  importAttachName: "",
3460
3473
  tableRef: "",
3474
+ // 进度框形态:逐行校验 + 成功/失败计数 + 结果表。
3475
+ // 默认关闭 —— 关闭时是"全屏 loading + 整批入表"的老链路,存量表单无感知。
3476
+ showProgressDialog: false,
3477
+ // 进度框形态下每行怎么处理:"table" 塞入关联表格;"script" 交给 onImportRow 逐行脚本
3478
+ importMode: "table",
3479
+ // 前台校验通过后,整批一次送给后台校验并补字段;默认关=不发请求
3480
+ backendValidateEnabled: false,
3481
+ importScriptCode: "",
3482
+ onImportParams: "",
3483
+ // 内嵌图片三段式:把 Excel 里贴的图取回来填进 attachments_ 列。
3484
+ // 依赖进度框,仅在 showProgressDialog 打开时生效
3485
+ enabledImportPreHandle: false,
3486
+ onImportRow: "",
3487
+ onValidateRow: "",
3488
+ // 入表前按该字段去重,空则不去重
3489
+ dedupField: "",
3490
+ // 必填校验方式:"" 默认(有逐行通道就逐行标记,否则整批拦)/ "strict" 整批硬拦 / "off" 不校验
3491
+ requiredCheckMode: "",
3461
3492
  onConfirmImportEnabled: false,
3462
3493
  onConfirmImport: "",
3463
3494
  onSuccessImport: "",
@@ -1051,6 +1051,16 @@ modules = {
1051
1051
  return null;
1052
1052
  }
1053
1053
 
1054
+ return this.runCellsRequiredCheck(rows, rows);
1055
+ },
1056
+ /**
1057
+ * 逐单元格必填校验的核心循环。
1058
+ * @param {Array} targetRows 本次要校验的行。
1059
+ * @param {Array} indexRows 求行下标用的完整数据集(决定校验 prop 里的行下标)。
1060
+ * @returns {String|null} 首个错误提示,全部通过返回 null。
1061
+ */
1062
+ runCellsRequiredCheck(targetRows, indexRows) {
1063
+ const rows = indexRows || [];
1054
1064
  const isEditTable = this.widget.options.isEditTable || false;
1055
1065
  const tableColumns = this.widget.options.tableColumns || [];
1056
1066
  const tableFieldKeyName = this.getFieldKeyName(this.widget);
@@ -1060,8 +1070,8 @@ modules = {
1060
1070
  this.collectColumnValidateEntries(column, isEditTable, columnEntries);
1061
1071
  });
1062
1072
 
1063
- for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
1064
- const row = rows[rowIndex];
1073
+ for (let rowIndex = 0; rowIndex < targetRows.length; rowIndex++) {
1074
+ const row = targetRows[rowIndex];
1065
1075
  if (!row) {
1066
1076
  continue;
1067
1077
  }
@@ -1112,6 +1122,87 @@ modules = {
1112
1122
 
1113
1123
  return null;
1114
1124
  },
1125
+ /**
1126
+ * 行内保存用:只校验当前行的必填,不牵动其他行。
1127
+ * @param {Object} row 当前行。@returns {String|null} 错误提示,通过为 null。
1128
+ */
1129
+ validateEditRowRequired(row) {
1130
+ if (!row || this.widget.options.hidden || this.designState) {
1131
+ return null;
1132
+ }
1133
+ const rows = this.getValidationRows();
1134
+ return this.runCellsRequiredCheck([row], rows.length ? rows : [row]);
1135
+ },
1136
+ /**
1137
+ * 收集当前行所有参与校验的单元格 prop(用于原生规则的行级校验)。
1138
+ * @param {Object} row 当前行。@returns {Array<String>} 已在表单里注册的 prop 列表。
1139
+ */
1140
+ collectRowValidateProps(row) {
1141
+ const formRef = this.getFormRef?.();
1142
+ const renderForm = formRef?.$refs?.renderForm;
1143
+ if (!row || !renderForm) {
1144
+ return [];
1145
+ }
1146
+ const isEditTable = this.widget.options.isEditTable || false;
1147
+ const tableColumns = this.widget.options.tableColumns || [];
1148
+ const tableFieldKeyName = this.getFieldKeyName(this.widget);
1149
+ const rows = this.getValidationRows();
1150
+ const rowIndex = this.getValidationRowIndex(row, rows, 0);
1151
+ const columnEntries = [];
1152
+ this.loodHandleColumns(tableColumns, (column) => {
1153
+ this.collectColumnValidateEntries(column, isEditTable, columnEntries);
1154
+ });
1155
+
1156
+ // 只保留真正注册进 el-form 的 prop:validateField 传入全部未注册的 prop 会打警告
1157
+ const registered = new Set(
1158
+ (renderForm.fields || []).map((item) => item.prop)
1159
+ );
1160
+ const props = [];
1161
+ columnEntries.forEach(({ column, templateWidget }) => {
1162
+ const fieldWidget = this.resolveRowFieldWidget(row, templateWidget);
1163
+ if (shouldSkipCellValidation(fieldWidget)) {
1164
+ return;
1165
+ }
1166
+ const property = getCellFieldKey(fieldWidget, column);
1167
+ const propName = buildTableCellFormProp(
1168
+ tableFieldKeyName,
1169
+ rowIndex,
1170
+ property
1171
+ );
1172
+ if (propName && registered.has(propName) && !props.includes(propName)) {
1173
+ props.push(propName);
1174
+ }
1175
+ });
1176
+ return props;
1177
+ },
1178
+ /**
1179
+ * 行内保存的校验入口:范围收敛到当前行(原生规则 + 必填兜底),
1180
+ * 不校验其他行、也不校验表格外的表单字段。
1181
+ * @param {Object} row 当前行。@returns {Boolean} 通过为 true。
1182
+ */
1183
+ validateEditRow(row) {
1184
+ const formRef = this.getFormRef?.();
1185
+ const renderForm = formRef?.$refs?.renderForm;
1186
+ const props = this.collectRowValidateProps(row);
1187
+ let nativeError = null;
1188
+ if (renderForm && props.length) {
1189
+ renderForm.validateField(props, (errorMsg) => {
1190
+ if (!nativeError && errorMsg) {
1191
+ nativeError = errorMsg;
1192
+ }
1193
+ });
1194
+ }
1195
+ if (nativeError) {
1196
+ this.$message.error(nativeError);
1197
+ return false;
1198
+ }
1199
+ const requiredError = this.validateEditRowRequired(row);
1200
+ if (requiredError) {
1201
+ this.$message.error(requiredError);
1202
+ return false;
1203
+ }
1204
+ return true;
1205
+ },
1115
1206
  /** @param {Boolean} required 是否将整个数据表设为必填。 */
1116
1207
  setRequired(required) {
1117
1208
  this.$set(this.widget.options, "required", !!required);
@@ -2691,7 +2782,9 @@ modules = {
2691
2782
  if (
2692
2783
  customParam?.exportParam?.type !== EXPORT_TYPE.ITEM_SELECTED
2693
2784
  ) {
2694
- formData = tableOption.param ? tableOption.param() || {} : {};
2785
+ formData = customParam?.exportParam?.exportQuerySnapshot !== undefined
2786
+ ? this.$baseLodash.cloneDeep(customParam.exportParam.exportQuerySnapshot)
2787
+ : tableOption.param ? tableOption.param() || {} : {};
2695
2788
  }
2696
2789
  const queryParams = Object.assign({}, formData);
2697
2790
 
@@ -4428,14 +4521,9 @@ modules = {
4428
4521
  let logicalRowKey = obj.row?._X_ROW_KEY || null;
4429
4522
  let originalPhysicalId = null;
4430
4523
  try {
4431
- let valid = await new Promise((resolve, reject) => {
4432
- try {
4433
- formRef.validate(resolve);
4434
- } catch (error) {
4435
- reject(error);
4436
- }
4437
- });
4438
- if (!valid) return false;
4524
+ // 行内保存只提交当前行,校验范围同步收敛到当前行:其他行与表外字段
4525
+ // 的必填不该拦住单行保存(整体保存仍走 formRef.validate 的全量校验)
4526
+ if (!this.validateEditRow(obj.row)) return false;
4439
4527
 
4440
4528
  let requestRow = obj.row;
4441
4529
  if (treeContext) {
@@ -1,3 +1,47 @@
1
+ import { columnFormatMap } from "./util";
2
+
3
+ /**
4
+ * 列的某一侧(展示 / 编辑)是否真的配了组件。
5
+ *
6
+ * 判据与表单加载时的实例化口径一致(见 form-render/indexMixin 的 columnLoopDo):
7
+ * 只有 formatS / editFormatS 能映射出组件类型时该侧才会建 widget。纯格式化列
8
+ * (formatS 是 d1 这类格式码)与被改回空的列都不算。
9
+ * @param {Object} columnConfig 列配置。@param {Boolean} isEdit 是否编辑侧。
10
+ * @returns {Boolean}
11
+ */
12
+ function hasColumnWidgetSide(columnConfig, isEdit) {
13
+ if (isEdit) {
14
+ return !!(
15
+ columnConfig.editWidget || columnFormatMap[columnConfig.editFormatS]
16
+ );
17
+ }
18
+ return !!(columnConfig.widget || columnFormatMap[columnConfig.formatS]);
19
+ }
20
+
21
+ /**
22
+ * 取该侧生效的列级组件配置。
23
+ *
24
+ * 动态列走 widgetOptions / editWidgetOptions(本来就没有 formatS),直接生效;
25
+ * 设计器静态列的 columnOption / editColumnOption 必须门控:展示组件被改回空
26
+ * 之后那份 columnOption 仍会留在模板 JSON 里,无条件读会让这列凭空变必填
27
+ * (表头星号 + 拦保存),而加载时的 applyColumnMetaToFieldWidget 只读对应侧,
28
+ * 两边口径不一致正是该现象的根因。
29
+ * @param {Object} columnConfig 列配置。@param {Boolean} isEdit 是否编辑侧。
30
+ * @returns {Object|null}
31
+ */
32
+ function getEffectiveColumnOptions(columnConfig, isEdit) {
33
+ const dynamicOptions = isEdit
34
+ ? columnConfig.editWidgetOptions
35
+ : columnConfig.widgetOptions;
36
+ if (dynamicOptions) {
37
+ return dynamicOptions;
38
+ }
39
+ if (!hasColumnWidgetSide(columnConfig, isEdit)) {
40
+ return null;
41
+ }
42
+ return isEdit ? columnConfig.editColumnOption : columnConfig.columnOption;
43
+ }
44
+
1
45
  function buildColumnConfigMap(tableColumns, map = {}) {
2
46
  if (!tableColumns || !tableColumns.length) {
3
47
  return map;
@@ -56,10 +100,8 @@ export function resolveColumnRequiredFlag(columnConfig) {
56
100
  if (columnConfig.widget?.options?.required) {
57
101
  return true;
58
102
  }
59
- const widgetOptions =
60
- columnConfig.widgetOptions ?? columnConfig.columnOption;
61
- const editWidgetOptions =
62
- columnConfig.editWidgetOptions ?? columnConfig.editColumnOption;
103
+ const widgetOptions = getEffectiveColumnOptions(columnConfig, false);
104
+ const editWidgetOptions = getEffectiveColumnOptions(columnConfig, true);
63
105
  return !!(widgetOptions?.required || editWidgetOptions?.required);
64
106
  }
65
107
 
@@ -0,0 +1,19 @@
1
+ /**
2
+ * 取出 require(图片) 的真实 URL。
3
+ *
4
+ * webpack4(url-loader,vue-cli 设了 esModule:false) 下 require 直接返回 URL 字符串;
5
+ * webpack5(vue-cli5 的 asset modules) 下返回的是 ES Module 命名空间对象,且该对象
6
+ * 由 Object.create(null) 生成——没有原型链就没有 toString,直接绑到 :src 上时
7
+ * setAttribute 会抛 "Cannot convert object to primitive value" 整棵子树渲染失败
8
+ * (webpack5 的 asset modules 没有 esModule 开关,只能在取值处兜底)。
9
+ *
10
+ * 消费应用的构建配置不受本库控制,凡是把 require 结果往 DOM 属性上放的地方都要过这里。
11
+ */
12
+ export function assetUrl(mod) {
13
+ if (mod && typeof mod === "object") {
14
+ return mod.default || "";
15
+ }
16
+ return mod || "";
17
+ }
18
+
19
+ export default assetUrl;
@@ -0,0 +1,72 @@
1
+ /**
2
+ * 导入文件闸门。
3
+ *
4
+ * 导入侧是整文件 readAsBinaryString 读进内存 → XLSX.read(占用通常是文件体积的
5
+ * 5~20 倍)→ 再逐格建行数组,全程同步且没有任何让步。此前没有大小与行数校验,
6
+ * 几十 MB 的 xlsx 会直接把标签页撑爆,且崩在 FileReader / XLSX 内部,用户看到的
7
+ * 同样是"页面崩溃"而不是一句提示。
8
+ *
9
+ * 阈值都可以由导入配置覆盖(maxFileSize / maxRowNum,也支持写在 importOption 上),
10
+ * 传 0 表示不限制。
11
+ */
12
+
13
+ /** 导入文件大小上限(字节)。 */
14
+ export const DEFAULT_IMPORT_FILE_SIZE = 20 * 1024 * 1024;
15
+
16
+ /** 导入数据行数上限(不含模板前 3 行表头)。 */
17
+ export const DEFAULT_IMPORT_ROW_NUM = 20000;
18
+
19
+ const readOption = (option, key) => {
20
+ if (option && option[key] !== undefined) return option[key];
21
+ if (option && option.importOption && option.importOption[key] !== undefined) {
22
+ return option.importOption[key];
23
+ }
24
+ return undefined;
25
+ };
26
+
27
+ const resolveLimit = (value, defaultValue) => {
28
+ let num = Number(value);
29
+ if (!isFinite(num) || num < 0) return defaultValue;
30
+ return num;
31
+ };
32
+
33
+ /** @param {Object} option 导入配置。@returns {Number} 文件大小上限,0 表示不限制。 */
34
+ export function getImportFileSizeLimit(option) {
35
+ return resolveLimit(readOption(option, "maxFileSize"), DEFAULT_IMPORT_FILE_SIZE);
36
+ }
37
+
38
+ /** @param {Object} option 导入配置。@returns {Number} 行数上限,0 表示不限制。 */
39
+ export function getImportRowLimit(option) {
40
+ return resolveLimit(readOption(option, "maxRowNum"), DEFAULT_IMPORT_ROW_NUM);
41
+ }
42
+
43
+ /** @param {File} file 待导入文件。@param {Object} option 导入配置。@returns {Boolean} */
44
+ export function isImportFileOversize(file, option) {
45
+ let limit = getImportFileSizeLimit(option);
46
+ return limit > 0 && !!file && file.size > limit;
47
+ }
48
+
49
+ /** @param {Number} rowNum 数据行数。@param {Object} option 导入配置。@returns {Boolean} */
50
+ export function isImportRowsOverLimit(rowNum, option) {
51
+ let limit = getImportRowLimit(option);
52
+ return limit > 0 && rowNum > limit;
53
+ }
54
+
55
+ /** @param {Number} bytes 字节数。@returns {Number} 保留一位小数的 MB 数。 */
56
+ export function toFileSizeMb(bytes) {
57
+ return Math.round(((bytes || 0) / 1024 / 1024) * 10) / 10;
58
+ }
59
+
60
+ /**
61
+ * 取单元格文本:w(格式化文本)缺失时回退 v(原始值)。
62
+ * 模板里手工粘贴或公式生成的数值格常常只有 v,直接 w.trim() 会 TypeError,
63
+ * 而调用点外层的 catch 会把它吞成"点了导入没反应"。
64
+ * @param {Object} cell xlsx 单元格。
65
+ * @returns {String|undefined}
66
+ */
67
+ export function getImportCellText(cell) {
68
+ if (cell === undefined || cell === null) return undefined;
69
+ let text = cell.w !== undefined && cell.w !== null ? cell.w : cell.v;
70
+ if (text === undefined || text === null) return undefined;
71
+ return String(text).trim();
72
+ }
package/src/utils/vab.js CHANGED
@@ -13,6 +13,7 @@ import XEUtils from "xe-utils";
13
13
  import prefixConfig from "@/prefixConfig.js";
14
14
  import moment from "moment";
15
15
  import { openView } from "./repeatOpen";
16
+ import { assetUrl } from "./assetUrl";
16
17
 
17
18
  let imFun = {
18
19
  request,
@@ -28,7 +29,8 @@ let imFun = {
28
29
  };
29
30
 
30
31
  function reqImg(a) {
31
- return require("@/resources/images/" + a);
32
+ /* webpack5 下 require 返回的是无原型的 Module 对象,直接当 src 用会崩,统一取真实 URL */
33
+ return assetUrl(require("@/resources/images/" + a));
32
34
  }
33
35
 
34
36
  const FILE_ICON_NAME_MAP = {
@@ -398,6 +400,8 @@ install = (Vue, opts = {}) => {
398
400
  Vue.prototype.$baseLodash = imFun.lodash;
399
401
  /* 全局事件总线 */
400
402
  Vue.prototype.$baseEventBus = new Vue();
403
+ /* 模板里 :src="$assetUrl(require(...))" 用,见 utils/assetUrl.js */
404
+ Vue.prototype.$assetUrl = assetUrl;
401
405
 
402
406
  Vue.prototype.$commonFileUtil = {
403
407
  // 文件类型获取与判断接口