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 +1 -1
- package/src/components/VabUpload/index.vue +4 -1
- package/src/components/VabUpload/mixins.js +86 -3
- package/src/components/obsUpload/index.vue +4 -1
- package/src/components/obsUpload/mixins.js +68 -2
- package/src/components/xform/form-designer/form-widget/field-widget/baseAttachment-widget.vue +1 -0
- package/src/components/xform/form-designer/form-widget/field-widget/vabUpload-widget.vue +1 -0
- package/src/components/xform/form-designer/setting-panel/property-editor/field-baseAttachment/baseAttachment-fileTypes-editor.vue +23 -0
- package/src/components/xform/form-designer/setting-panel/property-editor/field-vabUpload/vabUpload-fileTypes-editor.vue +23 -0
- package/src/components/xform/form-designer/setting-panel/property-editor/fileTypesEditorMixin.js +116 -0
- package/src/components/xform/form-designer/setting-panel/property-editor/fileTypesSelect.vue +130 -0
- package/src/components/xform/form-designer/widget-panel/widgetsConfig.js +7 -3
- package/src/components/xform/form-render/indexMixin.js +38 -15
- package/src/components/xform/lang/en-US.js +13 -0
- package/src/components/xform/lang/zh-CN.js +13 -0
- package/src/utils/resolveViewComponent.js +6 -1
package/package.json
CHANGED
|
@@ -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="
|
|
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:
|
|
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:
|
|
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="
|
|
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:
|
|
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);
|
|
@@ -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>
|
package/src/components/xform/form-designer/setting-panel/property-editor/fileTypesEditorMixin.js
ADDED
|
@@ -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
|
-
|
|
2964
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
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
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
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
|
-
|
|
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;
|