@skyfox2000/webui 1.4.9 → 1.4.11

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 (31) hide show
  1. package/lib/assets/modules/{baseLayout-DIny49tw.js → baseLayout-DSVT_hCt.js} +3 -3
  2. package/lib/assets/modules/{file-upload-DHUBZlX4.js → file-upload-T2kmeyGd.js} +59 -45
  3. package/lib/assets/modules/{index-CEffEFtI.js → index-C6BAsERS.js} +1 -1
  4. package/lib/assets/modules/{index-DyRjXb_O.js → index-Cwr2EDEI.js} +2 -2
  5. package/lib/assets/modules/{index-BIAdOoSj.js → index-DkMjvF98.js} +10 -10
  6. package/lib/assets/modules/{menuTabs-CCrFWIOl.js → menuTabs-D99nhxm_.js} +2 -2
  7. package/lib/assets/modules/{toolIcon-BGZR_aUI.js → toolIcon-DDp0EFRE.js} +1 -1
  8. package/lib/assets/modules/{uploadList-CMXuGRWT.js → upload-template-BK8iQQYz.js} +273 -263
  9. package/lib/assets/modules/uploadList-B8p45yS2.js +423 -0
  10. package/lib/es/AceEditor/index.js +3 -3
  11. package/lib/es/BasicLayout/index.js +2 -2
  12. package/lib/es/Error403/index.js +1 -1
  13. package/lib/es/Error404/index.js +1 -1
  14. package/lib/es/ExcelForm/index.js +16 -15
  15. package/lib/es/MenuLayout/index.js +2 -2
  16. package/lib/es/TemplateFile/index.js +62 -90
  17. package/lib/es/UploadForm/index.js +4 -4
  18. package/lib/index.d.ts +1 -0
  19. package/lib/utils/file-upload.d.ts +2 -1
  20. package/lib/utils/upload-template.d.ts +4 -0
  21. package/lib/webui.css +1 -1
  22. package/lib/webui.es.js +52 -50
  23. package/package.json +1 -1
  24. package/src/components/content/dialog/excelForm.vue +5 -0
  25. package/src/components/content/dialog/templateFile.vue +27 -79
  26. package/src/components/content/table/index.vue +1 -0
  27. package/src/components/form/upload/uploadList.vue +122 -40
  28. package/src/index.ts +2 -0
  29. package/src/utils/file-upload.ts +31 -1
  30. package/src/utils/upload-template.ts +15 -0
  31. package/lib/assets/modules/uploadList-l4q5o65m.js +0 -400
@@ -2,10 +2,10 @@
2
2
  import { watch, ref, onMounted, PropType } from 'vue';
3
3
  import { Button } from '../../common';
4
4
  import { Modal, Space } from 'ant-design-vue';
5
- import { doQuery, EditorControl, MinioFile, UploadFile, UploadStatus } from '@/index';
6
- import { AnyData, ApiResponse, IUrlInfo, ResStatus } from '@skyfox2000/fapi';
5
+ import { MinioFile, UploadFile, UploadStatus } from '@/index';
6
+ import { httpPost, IUrlInfo, ResStatus } from '@skyfox2000/fapi';
7
7
  import UploadList from '../../form/upload/uploadList.vue';
8
- import { AsyncUploader } from '@/utils/file-upload';
8
+ import { uploadTempOpener } from '../../../utils/upload-template';
9
9
 
10
10
  const props = defineProps({
11
11
  /**
@@ -33,10 +33,10 @@ const props = defineProps({
33
33
  /**
34
34
  * 弹窗控制器
35
35
  */
36
- uploadForm: {
37
- type: Object as PropType<EditorControl<AnyData>>,
38
- required: true,
39
- },
36
+ // open: {
37
+ // type: Boolean as PropType<boolean>,
38
+ // required: true,
39
+ // },
40
40
  /**
41
41
  * 文件列表地址
42
42
  */
@@ -81,59 +81,44 @@ const props = defineProps({
81
81
  required: false,
82
82
  },
83
83
  });
84
+
85
+ const api = props.api ?? "FILE_API";
84
86
  /**
85
87
  * minio桶名称template
86
88
  *
87
89
  * 子目录templateType
88
90
  */
89
91
  const fileListUrl = ref(props.fileListUrl || {
90
- api: props.api,
92
+ api: api,
91
93
  authorize: true,
92
94
  url: '/api/TemplateFileSrv/list',
93
95
  });
94
96
  const uploadUrl = ref(props.uploadUrl || {
95
- api: props.api,
97
+ api: api,
96
98
  authorize: true,
97
99
  url: '/api/TemplateFileSrv/upload',
98
100
  });
99
101
  const downloadUrl = ref(props.downloadUrl || {
100
- api: props.api,
102
+ api: api,
101
103
  authorize: true,
102
104
  url: '/api/TemplateFileSrv/download',
103
105
  });
104
106
  const deleteUrl = ref(props.deleteUrl || {
105
- api: props.api,
107
+ api: api,
106
108
  authorize: true,
107
109
  url: '/api/TemplateFileSrv/delete',
108
110
  });
109
111
 
110
- const uploadFormCtrl = props.uploadForm;
111
112
  const open = ref<boolean>(false);
112
113
 
113
114
  const fileList = ref<UploadFile[]>([]);
114
- const emit = defineEmits<{
115
- /**
116
- * 显示预处理
117
- */
118
- 'before:file-list': [EditorControl<AnyData>, UploadFile[]];
119
- /**
120
- * 上传前预处理
121
- */
122
- 'before:upload': [UploadFile[]];
123
- /**
124
- * 上传结束,处理上传后的文件
125
- */
126
- 'after:upload': [boolean, UploadFile[]];
127
- /**
128
- * 上传进度
129
- */
130
- 'on:progress': [UploadFile];
131
- }>();
115
+ // const emit = defineEmits<{
116
+ // }>();
132
117
 
133
118
  watch(
134
- () => uploadFormCtrl.visible.value,
119
+ () => uploadTempOpener.value,
135
120
  () => {
136
- open.value = uploadFormCtrl.visible.value;
121
+ open.value = uploadTempOpener.value;
137
122
  },
138
123
  );
139
124
 
@@ -142,14 +127,10 @@ watch(
142
127
  * 加载现有模板文件列表
143
128
  */
144
129
  const loadFileList = async () => {
145
- const res: ApiResponse<MinioFile[]> | null = await doQuery<MinioFile[]>(props.uploadForm, {
146
- urlKey: 'list',
147
- url: fileListUrl.value,
148
- params: {
149
- Query: {
150
- PrefixPath: props.templateType,
151
- }
152
- },
130
+ const res = await httpPost<MinioFile[]>(fileListUrl.value, {
131
+ Query: {
132
+ PrefixPath: props.templateType,
133
+ }
153
134
  });
154
135
  if (res?.status === ResStatus.SUCCESS && res.data) {
155
136
  const files = res.data as MinioFile[];
@@ -166,66 +147,33 @@ const loadFileList = async () => {
166
147
  }
167
148
  return uploadFile;
168
149
  })
169
- emit('before:file-list', uploadFormCtrl, list);
150
+ // emit('before:file-list', uploadFormCtrl, list);
170
151
  fileList.value.push(...list);
171
152
  }
172
153
  };
173
154
 
174
- const dialogSave = async () => {
175
- emit('before:upload', fileList.value);
176
- const uploader = new AsyncUploader(uploadUrl.value, 3);
177
- await uploader.doUpload(
178
- fileList.value,
179
- uploadFormCtrl.isFormLoading,
180
- true,
181
- async (result, files) => {
182
- emit('after:upload', result, files);
183
- if (result) {
184
- uploadFormCtrl.visible.value = false;
185
- }
186
- },
187
- (file) => {
188
- emit('on:progress', file);
189
- },
190
- );
191
- };
192
-
193
155
  onMounted(() => {
194
- if (!fileListUrl.value.api) {
195
- fileListUrl.value.api = uploadFormCtrl.page.api;
196
- }
197
- if (!uploadUrl.value.api) {
198
- uploadUrl.value.api = uploadFormCtrl.page.api;
199
- }
200
- if (!downloadUrl.value.api) {
201
- downloadUrl.value.api = uploadFormCtrl.page.api;
202
- }
203
- if (!deleteUrl.value.api) {
204
- deleteUrl.value.api = uploadFormCtrl.page.api;
205
- }
206
156
  loadFileList();
207
157
 
208
- open.value = uploadFormCtrl.visible.value;
158
+ open.value = uploadTempOpener.value;
209
159
  });
210
160
 
211
161
  const dialogClose = () => {
212
- uploadFormCtrl.visible.value = false;
162
+ open.value = false;
163
+ uploadTempOpener.value = false;
213
164
  };
214
165
  </script>
215
166
  <template>
216
167
  <Modal title="模板文件管理" v-model:open="open"
217
168
  :wrapClassName="'modal mx-auto ' + ($attrs.width ? 'w-[' + $attrs.width + ']' : 'w-[500px]')"
218
169
  @close="dialogClose">
219
- <UploadList v-model:file-list="fileList" :parent-path="props.templateType" :upload-url="uploadUrl"
170
+ <!-- 改为选择后自动上传 -->
171
+ <UploadList v-model:file-list="fileList" auto-upload :parent-path="props.templateType" :upload-url="uploadUrl"
220
172
  :download-url="downloadUrl" :delete-url="deleteUrl" :max-count="maxCount" :file-ext="fileExt"
221
173
  :show-delete="showDelete" />
222
174
  <template #footer>
223
175
  <Space>
224
176
  <Button @click="dialogClose">关闭</Button>
225
- <Button @click="dialogSave" type="primary" :loading="uploadFormCtrl.isFormSaving.value"
226
- :disabled="fileList.length === 0">
227
- 上传保存
228
- </Button>
229
177
  </Space>
230
178
  </template>
231
179
  </Modal>
@@ -28,6 +28,7 @@ const props = withDefaults(defineProps<{
28
28
  primaryKey?: string;
29
29
  /**
30
30
  * 自定义表格数据
31
+ * - 使用gridCtrl实现显示
31
32
  */
32
33
  tableData?: Record<string, AnyData>[];
33
34
  /**
@@ -7,6 +7,7 @@ import { Upload, Progress, Tag, Popconfirm } from 'ant-design-vue';
7
7
  import { UploadFile, UploadStatus, donwloadFromMinio, path, Switch, previewFromMinio } from '@/index';
8
8
  import { useInputFactory } from '@/utils/form-validate';
9
9
  import { ApiResponse, httpPost, IUrlInfo, ResStatus } from '@skyfox2000/fapi';
10
+ import { fastUpload } from '@/utils/file-upload';
10
11
 
11
12
  export interface UploadListProps {
12
13
  /**
@@ -97,13 +98,19 @@ const props = withDefaults(defineProps<UploadListProps>(), {
97
98
  const inputFactory = useInputFactory();
98
99
  const { errInfo } = inputFactory;
99
100
 
100
- const fileList = ref<UploadFile[]>(props.fileList);
101
+
102
+ // Upload 组件的文件列表更新
103
+ const displayList = ref<UploadFile[]>(props.fileList);
104
+ // Upload 组件的内部文件列表
105
+ const uploadList = ref<UploadFile[]>([]);
106
+
101
107
  const fileUploader = ref();
102
108
  const emit = defineEmits(['update:file-list']);
103
- const confirmOpen = ref(false);
104
109
  const acceptString = computed(() => (props.fileExt?.length ? props.fileExt.map((ext) => `.${ext}`).join(',') : ''));
105
110
 
106
- const beforeUpload: UploadProps['beforeUpload'] = (file) => {
111
+ // 统一的文件验证函数
112
+ const validateFile = (file: File): boolean => {
113
+ // 文件类型验证
107
114
  if (props.fileExt && props.fileExt.length > 0) {
108
115
  const extension = file.name.split('.').pop()?.toLowerCase() || '';
109
116
  if (!props.fileExt.includes(extension)) {
@@ -112,43 +119,114 @@ const beforeUpload: UploadProps['beforeUpload'] = (file) => {
112
119
  }
113
120
  }
114
121
 
122
+ // 文件大小验证
115
123
  if (file.size / 1024 / 1024 > props.maxFileSize) {
116
124
  message.error(`文件大小超过 ${props.maxFileSize}MB 限制`);
117
125
  return false;
118
126
  }
119
127
 
120
- if (props.maxCount > 1 && fileList.value.length >= props.maxCount) {
121
- message.error(`最多上传 ${props.maxCount} 个文件`);
122
- return false;
128
+ return true;
129
+ };
130
+
131
+ // 检查是否达到最大数量限制
132
+ const isMaxCountReached = (): boolean => {
133
+ return props.maxCount > 1 && displayList.value.length >= props.maxCount;
134
+ };
135
+
136
+ // 准备文件信息
137
+ const prepareFileInfo = (file: any): UploadFile<any> => {
138
+ const fileInfo = file as UploadFile<any>;
139
+ if (!fileInfo.params) fileInfo.params = {};
140
+ fileInfo.params.FileKey = fileInfo.name;
141
+ if (props.parentPath) {
142
+ fileInfo.params.FileKey = path.join('/', props.parentPath, fileInfo.name);
123
143
  }
144
+ fileInfo.status = UploadStatus.Pending;
145
+ return fileInfo;
146
+ };
124
147
 
125
- confirmOpen.value = false;
126
- return props.autoUpload;
148
+ // 简化的 beforeUpload - 只做基本验证,不处理业务逻辑
149
+ const beforeUpload: UploadProps['beforeUpload'] = (file) => {
150
+ return validateFile(file) && props.autoUpload;
127
151
  };
128
152
 
129
- const updateFileList: UploadProps['onUpdate:fileList'] = (fileList) => {
130
- fileList.forEach((file) => {
131
- const fileInfo = file as UploadFile<any>;
132
- if (!fileInfo.fileName) fileInfo.fileName = fileInfo.name;
133
- if (props.parentPath) fileInfo.name = path.join('/', props.parentPath, fileInfo.fileName);
134
- if (!fileInfo.params) fileInfo.params = {};
135
- fileInfo.params.FileKey = fileInfo.name;
136
- fileInfo.status = UploadStatus.Pending;
137
- });
153
+ // 处理文件选择(核心逻辑)
154
+ const handleFileSelect = async (newFiles: any[]) => {
155
+ if (newFiles.length === 0) return;
156
+
157
+ const tempFiles = [...displayList.value];
158
+ let hasError = false;
159
+
160
+ for (const file of newFiles) {
161
+ // 验证文件
162
+ if (!validateFile(file)) {
163
+ hasError = true;
164
+ continue;
165
+ }
166
+
167
+ const fileInfo = prepareFileInfo(file);
168
+
169
+ // 如果maxCount=1,直接替换
170
+ if (props.maxCount === 1) {
171
+ tempFiles.length = 0;
172
+ tempFiles.push(fileInfo);
173
+ break;
174
+ }
175
+
176
+ // 检查是否达到最大数量限制
177
+ if (isMaxCountReached()) {
178
+ message.error(`最多上传 ${props.maxCount} 个文件`);
179
+ hasError = true;
180
+ break;
181
+ }
182
+
183
+ // 检查是否为同名文件(严格判断)
184
+ const existingIndex = tempFiles.findIndex(f =>
185
+ (f.name === fileInfo.name)
186
+ );
187
+
188
+ if (existingIndex > -1) {
189
+ // 同名文件:替换
190
+ tempFiles[existingIndex] = fileInfo;
191
+ } else {
192
+ // 新文件:添加到列表
193
+ tempFiles.push(fileInfo);
194
+ }
195
+ }
196
+
197
+ // 更新显示列表
198
+ if (!hasError || tempFiles.length > 0) {
199
+ displayList.value = tempFiles;
200
+ }
201
+
202
+ // 清空 Upload 组件的内部列表
203
+ uploadList.value = [];
204
+ };
205
+
206
+ // Upload 组件的文件列表更新
207
+ const updateUploadList: UploadProps['onUpdate:fileList'] = (newFiles) => {
208
+ // 只更新 Upload 内部列表
209
+ uploadList.value = newFiles as unknown as UploadFile[];
210
+
211
+ // 处理新选择的文件
212
+ handleFileSelect(newFiles);
138
213
  };
139
214
 
140
215
  const uploadProps = computed<UploadProps>(() => ({
141
216
  accept: acceptString.value,
142
- multiple: true,
143
- fileList: fileList.value as UploadProps['fileList'],
144
- 'onUpdate:fileList': updateFileList,
217
+ multiple: props.maxCount !== 1,
218
+ fileList: uploadList.value as UploadProps['fileList'],
219
+ 'onUpdate:fileList': updateUploadList,
145
220
  beforeUpload: beforeUpload,
146
221
  listType: 'text',
147
- maxCount: props.maxCount,
148
222
  showUploadList: false,
149
- onChange: (info) => {
150
- if (!props.autoUpload) {
151
- fileList.value = info.fileList as unknown as UploadFile[];
223
+ customRequest: async () => {
224
+ if (props.autoUpload && props.uploadUrl) {
225
+ for (const file of displayList.value) {
226
+ if (file.percent === 0 && file.status === UploadStatus.Pending) {
227
+ await fastUpload(props.uploadUrl, file);
228
+ }
229
+ }
152
230
  }
153
231
  },
154
232
  }));
@@ -156,13 +234,13 @@ const uploadProps = computed<UploadProps>(() => ({
156
234
  watch(
157
235
  () => props.fileList,
158
236
  (newVal) => {
159
- fileList.value = newVal;
237
+ displayList.value = newVal;
160
238
  },
161
239
  { deep: true, immediate: true },
162
240
  );
163
241
 
164
242
  watch(
165
- () => fileList.value,
243
+ () => displayList.value,
166
244
  (newVal) => {
167
245
  emit('update:file-list', newVal);
168
246
  },
@@ -173,7 +251,7 @@ watch(
173
251
  () => props.parentPath,
174
252
  (newVal) => {
175
253
  if (newVal) {
176
- fileList.value.forEach((file) => {
254
+ displayList.value.forEach((file) => {
177
255
  file.params.FileKey = path.join('/', newVal, file.fileName);
178
256
  });
179
257
  }
@@ -181,7 +259,7 @@ watch(
181
259
  );
182
260
 
183
261
  const downloadFile = (index: number) => {
184
- const minioFile = fileList.value[index].minioFile!;
262
+ const minioFile = displayList.value[index].minioFile!;
185
263
  const url: IUrlInfo = {
186
264
  api: props.downloadUrl!.api,
187
265
  authorize: props.downloadUrl!.authorize,
@@ -203,7 +281,7 @@ const onlineOrOffline = (file: UploadFile) => {
203
281
  };
204
282
 
205
283
  const previewFile = (index: number) => {
206
- const minioFile = fileList.value[index].minioFile!;
284
+ const minioFile = displayList.value[index].minioFile!;
207
285
  const url: IUrlInfo = {
208
286
  api: props.previewUrl!.api,
209
287
  authorize: props.previewUrl!.authorize,
@@ -219,8 +297,8 @@ const previewFile = (index: number) => {
219
297
  };
220
298
 
221
299
  const removeFile = (index: number) => {
222
- const file = fileList.value[index];
223
- if (props.deleteUrl) {
300
+ const file = displayList.value[index];
301
+ if (props.deleteUrl && file.minioFile && file.minioFile.Key) {
224
302
  httpPost<ApiResponse>(props.deleteUrl, {
225
303
  Query: {
226
304
  FileKey: file.minioFile!.Key,
@@ -228,23 +306,28 @@ const removeFile = (index: number) => {
228
306
  }).then((res) => {
229
307
  if (res && res.status === ResStatus.SUCCESS) {
230
308
  message.success('删除文件成功!');
231
- fileList.value.splice(index, 1);
309
+ displayList.value.splice(index, 1);
232
310
  }
233
311
  });
234
312
  } else {
235
- fileList.value.splice(index, 1);
313
+ displayList.value.splice(index, 1);
236
314
  }
237
- confirmOpen.value = false;
315
+ delIndex.value = -1;
238
316
  };
239
317
 
318
+ const delIndex = ref(-1);
240
319
  const confirmDelFile = (index: number, status?: UploadStatus) => {
320
+ delIndex.value = index;
241
321
  if (status === UploadStatus.Pending) {
242
322
  removeFile(index);
243
323
  return;
244
324
  }
245
- confirmOpen.value = true;
246
325
  };
247
326
 
327
+ const confirmIndex = (index: number) => {
328
+ return index === delIndex.value;
329
+ }
330
+
248
331
  const getPlaceholder = (): string => {
249
332
  const typeMsg =
250
333
  props.fileExt && props.fileExt.length && props.fileExtTip ? `文件必须为 ${props.fileExt.join('/')}` : '';
@@ -301,11 +384,10 @@ const getStatus = (status?: UploadStatus) => {
301
384
  <div class="flex-1 text-sm text-gray-500" :class="[errInfo?.errClass + '-text']">
302
385
  {{ getPlaceholder() }}
303
386
  </div>
304
- <!-- <Button v-if="!autoUpload" @click="manualUpload" class="mr-3">开始上传</Button> -->
305
387
  </div>
306
388
 
307
389
  <div class="mt-4 px-3">
308
- <div v-for="(file, index) in fileList" :key="index" class="mb-2 pb-1">
390
+ <div v-for="(file, index) in displayList" :key="index" class="mb-2 pb-1">
309
391
  <div class="flex items-center justify-between">
310
392
  <div class="flex items-center">
311
393
  <span class="text-gray-700 mr-2"
@@ -350,9 +432,9 @@ const getStatus = (status?: UploadStatus) => {
350
432
  </div>
351
433
  <div v-if="showDelete !== false"
352
434
  class="flex items-center text-red-500 hover:text-red-700 cursor-pointer">
353
- <Popconfirm v-model:open="confirmOpen" cancelText="否" okText="是" title="确定删除该文件吗?"
435
+ <Popconfirm :open="confirmIndex(index)" cancelText="否" okText="是" title="确定删除该文件吗?"
354
436
  :okButtonProps="{ size: 'small' }" :cancelButtonProps="{ size: 'small' }"
355
- @confirm="removeFile(index)" @cancel="confirmOpen = false">
437
+ @confirm="removeFile(index)" @cancel="delIndex = -1">
356
438
  <Tooltip title="删除">
357
439
  <div @click="confirmDelFile(index, file.status)">
358
440
  <ToolIcon icon="icon-new" :angle="45"
@@ -388,4 +470,4 @@ const getStatus = (status?: UploadStatus) => {
388
470
  .error-text {
389
471
  color: #ff4d4f !important;
390
472
  }
391
- </style>
473
+ </style>
package/src/index.ts CHANGED
@@ -279,3 +279,5 @@ export {
279
279
  Menu,
280
280
  MenuTabs,
281
281
  } from '@/components/index';
282
+
283
+ export { uploadTempOpener, uploadTempBtn } from './utils/upload-template';
@@ -111,7 +111,7 @@ export class AsyncUploader {
111
111
  * @param onProgress 上传进度回调
112
112
  * @param onComplete 上传完成回调
113
113
  */
114
- private async uploadFiles(
114
+ public async uploadFiles(
115
115
  fileList: UploadFile[],
116
116
  onProgress?: (file: UploadFile) => void,
117
117
  onComplete?: (files: UploadFile[]) => void,
@@ -329,6 +329,36 @@ export class AsyncUploader {
329
329
  }
330
330
  }
331
331
  }
332
+
333
+ /**
334
+ * 单文件快速上传
335
+ * @param uploadUrl 上传地址
336
+ * @param file
337
+ * @returns
338
+ */
339
+ export const fastUpload = async (uploadUrl: IUrlInfo, uploadFile: UploadFile): Promise<void> => {
340
+ const uploader = new AsyncUploader(uploadUrl!, 1);
341
+ const fileList = [uploadFile];
342
+ await uploader.uploadFiles(
343
+ fileList,
344
+ (file) => {
345
+ uploadFile.percent = file.percent;
346
+ },
347
+ (files) => {
348
+ const file = files[0];
349
+ uploadFile.percent = file.percent;
350
+
351
+ if (file.status === UploadStatus.Success) {
352
+ uploadFile.status = UploadStatus.Online;
353
+ message.success('文件上传成功!');
354
+ } else {
355
+ uploadFile.status = UploadStatus.Error;
356
+ message.error('上传文件失败!');
357
+ }
358
+ },
359
+ );
360
+ };
361
+
332
362
  // // 使用示例
333
363
  // (async () => {
334
364
  // // 创建上传器实例,指定 API 配置和最大并发数
@@ -0,0 +1,15 @@
1
+ import { ButtonTool } from '@/typings/tools';
2
+ import { ref } from 'vue';
3
+
4
+ export const uploadTempOpener = ref<boolean>(false);
5
+
6
+ // 添加上传模板按钮
7
+ export const uploadTempBtn: ButtonTool = {
8
+ label: '模板管理',
9
+ key: 'uploadTemp',
10
+ type: 'primary',
11
+ icon: 'icon-download',
12
+ click: () => {
13
+ uploadTempOpener.value = true;
14
+ },
15
+ };