cloud-web-corejs 1.0.54-dev.780 → 1.0.54-dev.781

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,7 +1,7 @@
1
1
  {
2
2
  "name": "cloud-web-corejs",
3
3
  "private": false,
4
- "version": "1.0.54-dev.780",
4
+ "version": "1.0.54-dev.781",
5
5
  "scripts": {
6
6
  "dev": "vue-cli-service serve",
7
7
  "dev:micro": "vue-cli-service serve --port 17527",
@@ -63,6 +63,9 @@
63
63
  {{
64
64
  $t2('注意:当前所有文件上传完,才允许确定操作,单个文件上传大小,请尽量小于100MB。', 'components.VabUpload.tip2')
65
65
  }}
66
+ <template v-if="allowedFileTypesText">{{
67
+ $t2('仅支持上传以下格式:', 'components.VabUpload.fileTypesTip')
68
+ }}{{ allowedFileTypesText }}</template>
66
69
  <div class="upload-count fr">
67
70
  <div>{{ $t2('总数', 'components.VabUpload.total') }}:<span>{{ totalQuantity }}</span></div>
68
71
  <div>{{ $t2('成功', 'components.VabUpload.success') }}:<span>{{ successQuantity }}</span></div>
@@ -90,7 +93,7 @@
90
93
  :on-remove="handleRemove"
91
94
  :on-success="handleSuccess"
92
95
  :before-upload="handleBeforeUpload"
93
- :accept="accept"
96
+ :accept="uploadAccept"
94
97
  class="upload-demo"
95
98
  list-type="picture"
96
99
  >
@@ -28,14 +28,22 @@ tmixins = {
28
28
  type: Number,
29
29
  default: () => 100000,
30
30
  },
31
+ /* 单个文件大小限制(MB),0 表示不限制 */
31
32
  size: {
32
33
  type: Number,
33
- default: 200,
34
+ default: () => {
35
+ return settingConfig.uploadFileSize ?? 200;
36
+ },
34
37
  },
35
38
  accept: {
36
39
  type: String,
37
40
  default: "file",
38
41
  },
42
+ /* 允许上传的文件格式,数组或逗号分隔字符串,如 ['doc','xlsx'] / 'doc,xlsx';为空表示不限制 */
43
+ fileTypes: {
44
+ type: [Array, String],
45
+ default: () => [],
46
+ },
39
47
  chunkSize: {
40
48
  type: Number,
41
49
  default: 5, //5MB
@@ -147,8 +155,55 @@ tmixins = {
147
155
  }
148
156
  return isMulti;
149
157
  },
158
+ /* 允许上传的文件后缀(小写、去掉点号),为空数组表示不限制 */
159
+ allowedFileTypes() {
160
+ let types = this.fileTypes;
161
+ if (typeof types === "string") {
162
+ types = types.split(",");
163
+ }
164
+ if (!Array.isArray(types)) {
165
+ return [];
166
+ }
167
+ return types
168
+ .map((type) => String(type).trim().replace(/^\./, "").toLowerCase())
169
+ .filter((type) => !!type);
170
+ },
171
+ /* 限制了上传格式时用后缀列表覆盖 accept,未限制则沿用外部传入的 accept */
172
+ uploadAccept() {
173
+ let types = this.allowedFileTypes;
174
+ if (!types.length) {
175
+ return this.accept;
176
+ }
177
+ return types.map((type) => "." + type).join(",");
178
+ },
179
+ allowedFileTypesText() {
180
+ return this.allowedFileTypes.join("、");
181
+ },
150
182
  },
151
183
  methods: {
184
+ /* 校验文件后缀是否在允许的上传格式内,未配置格式时一律通过 */
185
+ checkFileType(file) {
186
+ let types = this.allowedFileTypes;
187
+ if (!types.length) {
188
+ return true;
189
+ }
190
+ let suffix = this.$commonFileUtil.getFileSuffix(file.name || "");
191
+ return types.indexOf(suffix) > -1;
192
+ },
193
+ showFileTypeError(fileName) {
194
+ this.$message({
195
+ message: this.$t2(
196
+ "文件[" + fileName + "]格式不支持,仅允许" + this.allowedFileTypesText,
197
+ "components.VabUpload.warnMsg4",
198
+ {
199
+ fileName: fileName,
200
+ fileTypes: this.allowedFileTypesText,
201
+ }
202
+ ),
203
+ type: "error",
204
+ duration: 3 * 1000,
205
+ });
206
+ },
152
207
  openFileLibraryDialog() {
153
208
  this.$openFileLibraryDialog({
154
209
  prefix: this.storeAreaPrefix,
@@ -220,8 +275,19 @@ tmixins = {
220
275
  // this.show = true
221
276
  },
222
277
  handleChange(file, fileList) {
278
+ /* 格式校验只针对仍在列表中的文件,removeFile 移除后也会回调本方法,不能重复报错 */
279
+ if (fileList.indexOf(file) > -1 && !this.checkFileType(file)) {
280
+ fileList.splice(fileList.indexOf(file), 1);
281
+ this.fileList = fileList;
282
+ this.showFileTypeError(file.name);
283
+ if (file.raw) {
284
+ file.raw.status = "fail";
285
+ }
286
+ return false;
287
+ }
288
+ /* size 为 0(或未配置限制)时不做大小校验 */
223
289
  let maxSize = this.size * 1024 * 1024;
224
- if (file.size > maxSize) {
290
+ if (maxSize > 0 && file.size > maxSize) {
225
291
  fileList.map((item, index) => {
226
292
  if (item === file) {
227
293
  fileList.splice(index, 1);
@@ -1034,6 +1100,14 @@ tmixins = {
1034
1100
 
1035
1101
  let newFiles = rows
1036
1102
  .filter((row) => !row.dirs && !sourceSns.includes(row.sn))
1103
+ .filter((row) => {
1104
+ /* 文件库/个人文档选来的文件不走 el-upload,需在此处补做格式校验 */
1105
+ if (this.checkFileType({ name: row.fileName })) {
1106
+ return true;
1107
+ }
1108
+ this.showFileTypeError(row.fileName);
1109
+ return false;
1110
+ })
1037
1111
  .map((row) => {
1038
1112
  let item = {
1039
1113
  status: "success",
@@ -1251,14 +1325,22 @@ tViewMixins = {
1251
1325
  type: Number,
1252
1326
  default: 100000,
1253
1327
  },
1328
+ /* 单个文件大小限制(MB),0 表示不限制 */
1254
1329
  size: {
1255
1330
  type: Number,
1256
- default: 200,
1331
+ default: () => {
1332
+ return settingConfig.uploadFileSize ?? 200;
1333
+ },
1257
1334
  },
1258
1335
  accept: {
1259
1336
  type: String,
1260
1337
  default: "file",
1261
1338
  },
1339
+ /* 允许上传的文件格式,数组或逗号分隔字符串,如 ['doc','xlsx'] / 'doc,xlsx';为空表示不限制 */
1340
+ fileTypes: {
1341
+ type: [Array, String],
1342
+ default: () => [],
1343
+ },
1262
1344
  chunkSize: {
1263
1345
  type: Number,
1264
1346
  default: 5, //5MB
@@ -1415,6 +1497,7 @@ tViewMixins = {
1415
1497
  limit: this.limit,
1416
1498
  multi: this.multi,
1417
1499
  accept: this.accept ? this.accept : "file",
1500
+ fileTypes: this.fileTypes,
1418
1501
  auto: this.auto,
1419
1502
  size: this.fileMaxSize,
1420
1503
  chunkSize: this.chunkSize,
@@ -63,6 +63,9 @@
63
63
  {{
64
64
  $t2('注意:当前所有文件上传完,才允许确定操作,单个文件上传大小,请尽量小于100MB。', 'components.VabUpload.tip2')
65
65
  }}
66
+ <template v-if="allowedFileTypesText">{{
67
+ $t2('仅支持上传以下格式:', 'components.VabUpload.fileTypesTip')
68
+ }}{{ allowedFileTypesText }}</template>
66
69
  <div class="upload-count fr">
67
70
  <div>{{ $t2('总数', 'components.VabUpload.total') }}:<span>{{ totalQuantity }}</span></div>
68
71
  <div>{{ $t2('成功', 'components.VabUpload.success') }}:<span>{{ successQuantity }}</span></div>
@@ -90,7 +93,7 @@
90
93
  :on-remove="handleRemove"
91
94
  :on-success="handleSuccess"
92
95
  :before-upload="handleBeforeUpload"
93
- :accept="accept"
96
+ :accept="uploadAccept"
94
97
  class="upload-demo"
95
98
  list-type="picture"
96
99
  >
@@ -28,14 +28,22 @@ tmixins = {
28
28
  type: Number,
29
29
  default: () => 100000,
30
30
  },
31
+ /* 单个文件大小限制(MB),0 表示不限制 */
31
32
  size: {
32
33
  type: Number,
33
- default: 200,
34
+ default: () => {
35
+ return settingConfig.uploadFileSize ?? 200;
36
+ },
34
37
  },
35
38
  accept: {
36
39
  type: String,
37
40
  default: "file",
38
41
  },
42
+ /* 允许上传的文件格式,数组或逗号分隔字符串,如 ['doc','xlsx'] / 'doc,xlsx';为空表示不限制 */
43
+ fileTypes: {
44
+ type: [Array, String],
45
+ default: () => [],
46
+ },
39
47
  chunkSize: {
40
48
  type: Number,
41
49
  default: 5, //5MB
@@ -138,8 +146,55 @@ tmixins = {
138
146
  }
139
147
  return isMulti;
140
148
  },
149
+ /* 允许上传的文件后缀(小写、去掉点号),为空数组表示不限制 */
150
+ allowedFileTypes() {
151
+ let types = this.fileTypes;
152
+ if (typeof types === "string") {
153
+ types = types.split(",");
154
+ }
155
+ if (!Array.isArray(types)) {
156
+ return [];
157
+ }
158
+ return types
159
+ .map((type) => String(type).trim().replace(/^\./, "").toLowerCase())
160
+ .filter((type) => !!type);
161
+ },
162
+ /* 限制了上传格式时用后缀列表覆盖 accept,未限制则沿用外部传入的 accept */
163
+ uploadAccept() {
164
+ let types = this.allowedFileTypes;
165
+ if (!types.length) {
166
+ return this.accept;
167
+ }
168
+ return types.map((type) => "." + type).join(",");
169
+ },
170
+ allowedFileTypesText() {
171
+ return this.allowedFileTypes.join("、");
172
+ },
141
173
  },
142
174
  methods: {
175
+ /* 校验文件后缀是否在允许的上传格式内,未配置格式时一律通过 */
176
+ checkFileType(file) {
177
+ let types = this.allowedFileTypes;
178
+ if (!types.length) {
179
+ return true;
180
+ }
181
+ let suffix = this.$commonFileUtil.getFileSuffix(file.name || "");
182
+ return types.indexOf(suffix) > -1;
183
+ },
184
+ showFileTypeError(fileName) {
185
+ this.$message({
186
+ message: this.$t2(
187
+ "文件[" + fileName + "]格式不支持,仅允许" + this.allowedFileTypesText,
188
+ "components.VabUpload.warnMsg4",
189
+ {
190
+ fileName: fileName,
191
+ fileTypes: this.allowedFileTypesText,
192
+ }
193
+ ),
194
+ type: "error",
195
+ duration: 3 * 1000,
196
+ });
197
+ },
143
198
  updateUploadingStatus() {
144
199
  var target = this.$refs.upload;
145
200
  let uploading, hasReadyFile;
@@ -185,8 +240,19 @@ tmixins = {
185
240
  // this.show = true
186
241
  },
187
242
  handleChange(file, fileList) {
243
+ /* 格式校验只针对仍在列表中的文件,removeFile 移除后也会回调本方法,不能重复报错 */
244
+ if (fileList.indexOf(file) > -1 && !this.checkFileType(file)) {
245
+ fileList.splice(fileList.indexOf(file), 1);
246
+ this.fileList = fileList;
247
+ this.showFileTypeError(file.name);
248
+ if (file.raw) {
249
+ file.raw.status = "fail";
250
+ }
251
+ return false;
252
+ }
253
+ /* size 为 0(或未配置限制)时不做大小校验 */
188
254
  let maxSize = this.size * 1024 * 1024;
189
- if (file.size > maxSize) {
255
+ if (maxSize > 0 && file.size > maxSize) {
190
256
  fileList.map((item, index) => {
191
257
  if (item === file) {
192
258
  fileList.splice(index, 1);
@@ -138,6 +138,7 @@ export default {
138
138
  showViewButton: this.field.options.showViewButton !== false,
139
139
  uploadConfig: {
140
140
  limit: this.field.options.limit,
141
+ fileTypes: this.field.options.fileTypes || [],
141
142
  resultType: "Array"
142
143
  },
143
144
  rows: (done) => {
@@ -29,6 +29,7 @@
29
29
  :pickPrivateProfile="false"
30
30
  :multi="field.options.limit !== 1"
31
31
  :limit="field.options.limit"
32
+ :fileTypes="field.options.fileTypes || []"
32
33
  resultType="Array"
33
34
  :edit="!field.options.disabled"
34
35
  :remove="!field.options.hideRemoveButton"
@@ -0,0 +1,23 @@
1
+ <template>
2
+ <file-types-select :option-model="optionModel"></file-types-select>
3
+ </template>
4
+
5
+ <script>
6
+ import FileTypesSelect from "../fileTypesSelect.vue"
7
+
8
+ export default {
9
+ name: "baseAttachment-fileTypes-editor",
10
+ components: {
11
+ FileTypesSelect,
12
+ },
13
+ props: {
14
+ designer: Object,
15
+ selectedWidget: Object,
16
+ optionModel: Object,
17
+ },
18
+ }
19
+ </script>
20
+
21
+ <style scoped>
22
+
23
+ </style>
@@ -0,0 +1,23 @@
1
+ <template>
2
+ <file-types-select :option-model="optionModel"></file-types-select>
3
+ </template>
4
+
5
+ <script>
6
+ import FileTypesSelect from "../fileTypesSelect.vue"
7
+
8
+ export default {
9
+ name: "vabUpload-fileTypes-editor",
10
+ components: {
11
+ FileTypesSelect,
12
+ },
13
+ props: {
14
+ designer: Object,
15
+ selectedWidget: Object,
16
+ optionModel: Object,
17
+ },
18
+ }
19
+ </script>
20
+
21
+ <style scoped>
22
+
23
+ </style>
@@ -0,0 +1,116 @@
1
+ /**
2
+ * 上传格式属性编辑器的共用逻辑。
3
+ *
4
+ * 存储格式仍是扁平的后缀数组(如 ['jpg','png','mp4']),分类只是勾选时的快捷方式:
5
+ * 勾"图片"等于把该分类下的后缀一次性并入,取消勾选则整组移除。这样运行期的格式
6
+ * 校验与 accept 生成都不必感知分类,已保存的老表单里的后缀数组也能原样沿用。
7
+ */
8
+
9
+ /* 每个分类内的后缀顺序即下拉分组里的展示顺序 */
10
+ export const FILE_TYPE_CATEGORIES = [
11
+ {
12
+ value: "image",
13
+ labelKey: "designer.setting.fileTypeCategory.image",
14
+ types: ["jpg", "jpeg", "png", "gif", "bmp", "webp", "svg"],
15
+ },
16
+ {
17
+ value: "video",
18
+ labelKey: "designer.setting.fileTypeCategory.video",
19
+ types: ["mp4", "avi", "mov", "wmv", "flv", "mkv", "rmvb"],
20
+ },
21
+ {
22
+ value: "audio",
23
+ labelKey: "designer.setting.fileTypeCategory.audio",
24
+ types: ["mp3", "wav", "wma", "aac", "flac"],
25
+ },
26
+ {
27
+ value: "word",
28
+ labelKey: "designer.setting.fileTypeCategory.word",
29
+ types: ["doc", "docx"],
30
+ },
31
+ {
32
+ value: "excel",
33
+ labelKey: "designer.setting.fileTypeCategory.excel",
34
+ types: ["xls", "xlsx", "csv"],
35
+ },
36
+ {
37
+ value: "ppt",
38
+ labelKey: "designer.setting.fileTypeCategory.ppt",
39
+ types: ["ppt", "pptx"],
40
+ },
41
+ {
42
+ value: "pdf",
43
+ labelKey: "designer.setting.fileTypeCategory.pdf",
44
+ types: ["pdf"],
45
+ },
46
+ {
47
+ value: "text",
48
+ labelKey: "designer.setting.fileTypeCategory.text",
49
+ types: ["txt"],
50
+ },
51
+ {
52
+ value: "archive",
53
+ labelKey: "designer.setting.fileTypeCategory.archive",
54
+ types: ["zip", "rar", "7z", "tar", "gz"],
55
+ },
56
+ ];
57
+
58
+ export default {
59
+ computed: {
60
+ fileTypeCategories() {
61
+ return FILE_TYPE_CATEGORIES.map((category) => {
62
+ return {
63
+ ...category,
64
+ label: this.i18nt(category.labelKey),
65
+ };
66
+ });
67
+ },
68
+ selectedFileTypes: {
69
+ get() {
70
+ return this.optionModel.fileTypes || [];
71
+ },
72
+ set(value) {
73
+ /* 老表单可能没有 fileTypes 键,用 $set 保证可响应 */
74
+ this.$set(this.optionModel, "fileTypes", this.normalizeFileTypes(value));
75
+ },
76
+ },
77
+ },
78
+ methods: {
79
+ /* 允许手工输入,统一成小写、去点号、去重的后缀 */
80
+ normalizeFileTypes(value) {
81
+ let result = [];
82
+ (value || []).forEach((item) => {
83
+ let fileType = String(item).trim().replace(/^\./, "").toLowerCase();
84
+ if (!!fileType && result.indexOf(fileType) < 0) {
85
+ result.push(fileType);
86
+ }
87
+ });
88
+ return result;
89
+ },
90
+ isCategoryChecked(category) {
91
+ let selected = this.selectedFileTypes;
92
+ return category.types.every((fileType) => {
93
+ return selected.indexOf(fileType) > -1;
94
+ });
95
+ },
96
+ isCategoryIndeterminate(category) {
97
+ if (this.isCategoryChecked(category)) {
98
+ return false;
99
+ }
100
+ let selected = this.selectedFileTypes;
101
+ return category.types.some((fileType) => {
102
+ return selected.indexOf(fileType) > -1;
103
+ });
104
+ },
105
+ toggleCategory(category, checked) {
106
+ let selected = this.selectedFileTypes;
107
+ if (checked) {
108
+ this.selectedFileTypes = [...selected, ...category.types];
109
+ } else {
110
+ this.selectedFileTypes = selected.filter((fileType) => {
111
+ return category.types.indexOf(fileType) < 0;
112
+ });
113
+ }
114
+ },
115
+ },
116
+ };
@@ -0,0 +1,130 @@
1
+ <template>
2
+ <!-- 分类勾选行放不进 120px 标签剩下的宽度里,故整块用 label-width="0" 占满属性面板 -->
3
+ <el-form-item class="file-types-item" label-width="0">
4
+ <div class="file-types-title">{{i18nt('designer.setting.fileTypes')}}
5
+ <el-tooltip effect="light" :content="i18nt('designer.setting.fileTypesCategoryHelp')">
6
+ <i class="el-icon-info"></i></el-tooltip>
7
+ </div>
8
+ <div class="file-types-category">
9
+ <el-checkbox v-for="category in fileTypeCategories"
10
+ :key="category.value"
11
+ :value="isCategoryChecked(category)"
12
+ :indeterminate="isCategoryIndeterminate(category)"
13
+ @change="toggleCategory(category, $event)">{{ category.label }}</el-checkbox>
14
+ </div>
15
+ <div class="file-types-select">
16
+ <el-select ref="fileTypesSelect" multiple allow-create filterable default-first-option clearable
17
+ :placeholder="i18nt('designer.setting.fileTypesPlaceholder')"
18
+ v-model="selectedFileTypes" style="width: 100%">
19
+ <el-option-group v-for="category in fileTypeCategories"
20
+ :key="category.value"
21
+ :label="category.label">
22
+ <el-option v-for="fileType in category.types"
23
+ :key="fileType"
24
+ :label="fileType"
25
+ :value="fileType">
26
+ </el-option>
27
+ </el-option-group>
28
+ </el-select>
29
+ </div>
30
+ </el-form-item>
31
+ </template>
32
+
33
+ <script>
34
+ import i18n from "../../../../../components/xform/utils/i18n"
35
+ import fileTypesEditorMixin from "./fileTypesEditorMixin"
36
+
37
+ export default {
38
+ name: "file-types-select",
39
+ mixins: [i18n, fileTypesEditorMixin],
40
+ props: {
41
+ optionModel: Object,
42
+ },
43
+ mounted() {
44
+ /*
45
+ * el-select 标签区的 max-width 是挂载那一刻按控件宽度算出来的内联样式,
46
+ * 这个编辑器常常挂载在还没完成布局的属性弹窗里,算出来的值偏小,
47
+ * 标签就会溢出到输入框外面。等布局稳定后让它重算一次。
48
+ */
49
+ this.$nextTick(() => {
50
+ let select = this.$refs.fileTypesSelect
51
+ if (!!select && !!select.resetInputWidth) {
52
+ select.resetInputWidth()
53
+ }
54
+ })
55
+ },
56
+ }
57
+ </script>
58
+
59
+ <style scoped>
60
+ .file-types-title {
61
+ color: #606266;
62
+ line-height: 22px;
63
+ }
64
+
65
+ .file-types-category {
66
+ margin-bottom: 4px;
67
+ line-height: 22px;
68
+ }
69
+
70
+ /* 独立的 el-checkbox 默认没有间距,且标签会跟着换行错位 */
71
+ .file-types-category .el-checkbox {
72
+ margin-right: 10px;
73
+ }
74
+
75
+ /*
76
+ * 全局样式表 src/resources/css/style.scss 里对 .el-form-item__content / .el-input
77
+ * 有大量固定宽度和 display 的 !important 覆盖,属性面板这一处正好被波及:
78
+ * 外面的 el-select 撑开了,里面画边框的 .el-input__inner 却还是窄的,
79
+ * 于是标签溢出到框外。这里从内容区一路 !important 压到最内层的 input。
80
+ */
81
+ .file-types-item ::v-deep .el-form-item__content {
82
+ display: block !important;
83
+ width: auto !important;
84
+ margin-left: 0 !important;
85
+ float: none;
86
+ }
87
+
88
+ .file-types-select {
89
+ display: block;
90
+ width: auto;
91
+ }
92
+
93
+ .file-types-select ::v-deep .el-select {
94
+ display: block !important;
95
+ width: 100% !important;
96
+ }
97
+
98
+ .file-types-select ::v-deep .el-select .el-input,
99
+ .file-types-select ::v-deep .el-select .el-input__inner {
100
+ width: 100% !important;
101
+ }
102
+
103
+ /*
104
+ * el-select 的标签区是绝对定位盖在输入框上的兄弟节点,max-width 为挂载时按控件
105
+ * 宽度算出的内联值;弹窗里挂载时控件还没有宽度,这个值会偏小导致标签溢出到框外。
106
+ * 这里用 !important 盖掉内联值,但必须和 element 一样留出右侧 32px:
107
+ * 清空按钮的显隐挂在 el-input 的 mouseenter/mouseleave 上,标签层一旦盖住图标区,
108
+ * 鼠标移过去就会触发 el-input 的 mouseleave,图标随即消失。
109
+ */
110
+ .file-types-select ::v-deep .el-select__tags {
111
+ max-width: calc(100% - 32px) !important;
112
+ /*
113
+ * 标签层整体对鼠标透明:清空按钮的显隐挂在 el-input 的 mouseenter/mouseleave 上,
114
+ * 而标签层是盖在输入框上的兄弟节点,凡是它盖住的地方 hover 都算不到 el-input 头上,
115
+ * 清空按钮就只在没被盖住的图标区才出现。
116
+ */
117
+ pointer-events: none;
118
+ }
119
+
120
+ /* 标签上的删除按钮和手工输入框仍要能点 */
121
+ .file-types-select ::v-deep .el-select__tags .el-tag__close,
122
+ .file-types-select ::v-deep .el-select__input {
123
+ pointer-events: auto;
124
+ }
125
+
126
+ /* 手工输入用的内联 input 同样带着按旧宽度算出的内联 max-width,同样留出图标区 */
127
+ .file-types-select ::v-deep .el-select__input {
128
+ max-width: calc(100% - 42px) !important;
129
+ }
130
+ </style>
@@ -2942,6 +2942,7 @@ export const advancedFields = [
2942
2942
  labelWidth: null,
2943
2943
  labelHidden: !0,
2944
2944
  limit: null,
2945
+ fileTypes: [],
2945
2946
  hideRemoveButton: false,
2946
2947
  accessType: "1",
2947
2948
  entityTableCode: null,
@@ -2960,12 +2961,14 @@ export const advancedFields = [
2960
2961
  showRuleEnabled: 1,
2961
2962
  showRules: [],
2962
2963
 
2963
- hiddenFileInfo: false,
2964
- hiddenFileName: false,
2964
+ /* 键名须与 field-vabUpload-editor、vabUpload-widget 读取的一致;
2965
+ 早期的 hiddenFileInfo/hiddenFileName/widgetSize 无处读取,已改名 */
2966
+ hideFileInfo: false,
2967
+ hideFileName: false,
2965
2968
  showFileSize: false,
2966
2969
  showFileCreateBy: false,
2967
2970
  showFileCreateDate: false,
2968
- widgetSize: 2,
2971
+ fileShowImageSize: 2,
2969
2972
 
2970
2973
  customUploadEnabled: false,
2971
2974
  uploadUrl: null,
@@ -3468,6 +3471,7 @@ export const advancedFields = [
3468
3471
  labelWidth: null,
3469
3472
  labelHidden: !0,
3470
3473
  limit: null,
3474
+ fileTypes: [],
3471
3475
  showViewButton: true,
3472
3476
  accessType: "1",
3473
3477
  entityTableCode: null,
@@ -4020,30 +4020,53 @@ modules = {
4020
4020
  let treeTaBm = newFormData.treeTaBm || [];
4021
4021
  treeTaBm.push(formTemplateTableDTO.tableAlias);
4022
4022
  newFormData.treeTaBm = treeTaBm;
4023
+ // 树的主键以 treeConfig.rowField 为准:transform 模式下 vxe 是用它
4024
+ // (xe-utils toArrayTree 的 key) 建树的,根节点判定必须与之同源,
4025
+ // 否则判出来的顶层节点和表格里的实际层级对不上。
4026
+ let idField = $grid.treeConfig.rowField || "id";
4023
4027
  let tableDatas = this.$baseLodash.cloneDeep(
4024
4028
  formDataModel[formTemplateTableDTO.name] || []
4025
4029
  );
4030
+ // 根节点判定必须在下面清空临时 id 之前完成:新增子行的父 id 是
4031
+ // "row_xxx",清空后变成空值,之后再判定就会被误判成根节点,既提交成
4032
+ // 顶层节点又已嵌在父行的 children 里,造成重复与层级错乱。
4033
+ // 判定口径与 toArrayTree 一致:父值为空(null/undefined/""/0/"0")视为
4034
+ // 根;父值非空但父行不在本表数据里(过滤/查询后的孤儿行)也要收进顶层
4035
+ // 数组,否则整棵子树被静默丢弃——它的 parentField 原值保留不清空,
4036
+ // 后端据此重新挂接到既有父节点。
4037
+ let idSet = new Set();
4026
4038
  tableDatas.forEach((tableData) => {
4027
- if (
4028
- tableData.id &&
4029
- tableData.id.startsWith &&
4030
- tableData.id.startsWith("row_")
4031
- ) {
4032
- tableData.id = null;
4039
+ let id = tableData[idField];
4040
+ if (id !== null && id !== undefined && id !== "") {
4041
+ idSet.add(String(id));
4033
4042
  }
4034
- if (
4035
- tableData[parentField] &&
4036
- tableData[parentField].startsWith &&
4037
- tableData[parentField].startsWith("row_")
4038
- ) {
4039
- tableData[parentField] = null;
4043
+ });
4044
+ let isRootFlags = tableDatas.map((tableData) => {
4045
+ let parentValue = tableData[parentField];
4046
+ if ([null, undefined, "", 0, "0"].includes(parentValue)) {
4047
+ return true;
4040
4048
  }
4049
+ return !idSet.has(String(parentValue));
4050
+ });
4051
+ // 临时 id 要连字面量 id 一起清:新增行(addTreeRow)把 "row_xxx" 写在
4052
+ // row.id 上,复制(handleCopyTreeData)写在 rowField 上,来源不同,
4053
+ // 只清一个会把临时 id 提交给后端。rowField 缺省时两者是同一字段。
4054
+ let clearTempValue = (tableData, field) => {
4055
+ let value = tableData[field];
4056
+ if (value && value.startsWith && value.startsWith("row_")) {
4057
+ tableData[field] = null;
4058
+ }
4059
+ };
4060
+ tableDatas.forEach((tableData) => {
4061
+ clearTempValue(tableData, idField);
4062
+ if (idField !== "id") {
4063
+ clearTempValue(tableData, "id");
4064
+ }
4065
+ clearTempValue(tableData, parentField);
4041
4066
  if (tableData[parentField] === undefined)
4042
4067
  tableData[parentField] = null;
4043
4068
  });
4044
- tableDatas = tableDatas.filter((item) => {
4045
- return item[parentField] === 0 || item[parentField] === null;
4046
- });
4069
+ tableDatas = tableDatas.filter((item, index) => isRootFlags[index]);
4047
4070
  newFormData[tableAlias] = tableDatas;
4048
4071
  } else {
4049
4072
  newFormData[tableAlias] =
@@ -239,6 +239,19 @@ export default {
239
239
  fileMaxSize: "Max Size(MB)",
240
240
  fileTypes: "Upload File Types",
241
241
  fileTypesHelp: "Allows to add more file types",
242
+ fileTypesCategoryHelp: "Check a category to select all its formats at once, or type in other extensions; empty means no restriction",
243
+ fileTypesPlaceholder: "No format restriction",
244
+ fileTypeCategory: {
245
+ image: "Image",
246
+ video: "Video",
247
+ audio: "Audio",
248
+ word: "Word",
249
+ excel: "Excel",
250
+ ppt: "PPT",
251
+ pdf: "PDF",
252
+ text: "Text",
253
+ archive: "Archive",
254
+ },
242
255
  headers: "Request Headers",
243
256
  cellWidth: "Width",
244
257
  cellHeight: "Height",
@@ -394,6 +394,19 @@ export default {
394
394
  fileMaxSize: "文件大小限制(MB)",
395
395
  fileTypes: "上传文件类型",
396
396
  fileTypesHelp: "支持添加其他文件类型",
397
+ fileTypesCategoryHelp: "勾选分类可批量选择该类下的全部格式,也支持手工输入其他后缀;留空表示不限制",
398
+ fileTypesPlaceholder: "不限制上传格式",
399
+ fileTypeCategory: {
400
+ image: "图片",
401
+ video: "视频",
402
+ audio: "音频",
403
+ word: "Word",
404
+ excel: "Excel",
405
+ ppt: "PPT",
406
+ pdf: "PDF",
407
+ text: "文本",
408
+ archive: "压缩包",
409
+ },
397
410
  headers: "上传请求头",
398
411
  formulaEnabled: "启用计算公式",
399
412
  formula: "计算公式",
@@ -156,7 +156,12 @@ function requireAppView(rel) {
156
156
  function getRendererAppCode() {
157
157
  const webPrefix = (window.WEB_PREFIX || "").replace(/^\/+/, "");
158
158
  try {
159
- const settings = require("@/settings").default;
159
+ // settings.js CommonJS(module.exports),取 .default 会拿到 undefined,
160
+ // 必须兼容两种形态,否则 appCode 永远读不到、退化成 WEB_PREFIX 派生值,
161
+ // 与 store/modules/permission.js 的 getOwnAppCode 口径不一致(appCode 与
162
+ // WEB_PREFIX 不同名的应用会误判归属)。
163
+ const mod = require("@/settings");
164
+ const settings = (mod && mod.default) || mod;
160
165
  return (settings && settings.appCode) || webPrefix;
161
166
  } catch (e) {
162
167
  return webPrefix;