@skyfox2000/webui 1.4.9 → 1.4.10

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 (29) 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-BefTs4CH.js} +1 -1
  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-BfCPE5Gb.js +420 -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 +61 -90
  17. package/lib/es/UploadForm/index.js +4 -4
  18. package/lib/index.d.ts +1 -0
  19. package/lib/utils/upload-template.d.ts +4 -0
  20. package/lib/webui.css +1 -1
  21. package/lib/webui.es.js +52 -50
  22. package/package.json +1 -1
  23. package/src/components/content/dialog/excelForm.vue +5 -0
  24. package/src/components/content/dialog/templateFile.vue +27 -79
  25. package/src/components/content/table/index.vue +1 -0
  26. package/src/components/form/upload/uploadList.vue +106 -38
  27. package/src/index.ts +2 -0
  28. package/src/utils/upload-template.ts +15 -0
  29. 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
  /**
@@ -97,13 +97,20 @@ const props = withDefaults(defineProps<UploadListProps>(), {
97
97
  const inputFactory = useInputFactory();
98
98
  const { errInfo } = inputFactory;
99
99
 
100
- const fileList = ref<UploadFile[]>(props.fileList);
100
+
101
+ // Upload 组件的文件列表更新
102
+ const displayList = ref<UploadFile[]>(props.fileList);
103
+ // Upload 组件的内部文件列表
104
+ const uploadList = ref<UploadFile[]>([]);
105
+
101
106
  const fileUploader = ref();
102
107
  const emit = defineEmits(['update:file-list']);
103
108
  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,57 +119,119 @@ 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 = (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[];
152
- }
153
- },
154
223
  }));
155
224
 
156
225
  watch(
157
226
  () => props.fileList,
158
227
  (newVal) => {
159
- fileList.value = newVal;
228
+ displayList.value = newVal;
160
229
  },
161
230
  { deep: true, immediate: true },
162
231
  );
163
232
 
164
233
  watch(
165
- () => fileList.value,
234
+ () => displayList.value,
166
235
  (newVal) => {
167
236
  emit('update:file-list', newVal);
168
237
  },
@@ -173,7 +242,7 @@ watch(
173
242
  () => props.parentPath,
174
243
  (newVal) => {
175
244
  if (newVal) {
176
- fileList.value.forEach((file) => {
245
+ displayList.value.forEach((file) => {
177
246
  file.params.FileKey = path.join('/', newVal, file.fileName);
178
247
  });
179
248
  }
@@ -181,7 +250,7 @@ watch(
181
250
  );
182
251
 
183
252
  const downloadFile = (index: number) => {
184
- const minioFile = fileList.value[index].minioFile!;
253
+ const minioFile = displayList.value[index].minioFile!;
185
254
  const url: IUrlInfo = {
186
255
  api: props.downloadUrl!.api,
187
256
  authorize: props.downloadUrl!.authorize,
@@ -203,7 +272,7 @@ const onlineOrOffline = (file: UploadFile) => {
203
272
  };
204
273
 
205
274
  const previewFile = (index: number) => {
206
- const minioFile = fileList.value[index].minioFile!;
275
+ const minioFile = displayList.value[index].minioFile!;
207
276
  const url: IUrlInfo = {
208
277
  api: props.previewUrl!.api,
209
278
  authorize: props.previewUrl!.authorize,
@@ -219,8 +288,8 @@ const previewFile = (index: number) => {
219
288
  };
220
289
 
221
290
  const removeFile = (index: number) => {
222
- const file = fileList.value[index];
223
- if (props.deleteUrl) {
291
+ const file = displayList.value[index];
292
+ if (props.deleteUrl && file.minioFile && file.minioFile.Key) {
224
293
  httpPost<ApiResponse>(props.deleteUrl, {
225
294
  Query: {
226
295
  FileKey: file.minioFile!.Key,
@@ -228,11 +297,11 @@ const removeFile = (index: number) => {
228
297
  }).then((res) => {
229
298
  if (res && res.status === ResStatus.SUCCESS) {
230
299
  message.success('删除文件成功!');
231
- fileList.value.splice(index, 1);
300
+ displayList.value.splice(index, 1);
232
301
  }
233
302
  });
234
303
  } else {
235
- fileList.value.splice(index, 1);
304
+ displayList.value.splice(index, 1);
236
305
  }
237
306
  confirmOpen.value = false;
238
307
  };
@@ -301,11 +370,10 @@ const getStatus = (status?: UploadStatus) => {
301
370
  <div class="flex-1 text-sm text-gray-500" :class="[errInfo?.errClass + '-text']">
302
371
  {{ getPlaceholder() }}
303
372
  </div>
304
- <!-- <Button v-if="!autoUpload" @click="manualUpload" class="mr-3">开始上传</Button> -->
305
373
  </div>
306
374
 
307
375
  <div class="mt-4 px-3">
308
- <div v-for="(file, index) in fileList" :key="index" class="mb-2 pb-1">
376
+ <div v-for="(file, index) in displayList" :key="index" class="mb-2 pb-1">
309
377
  <div class="flex items-center justify-between">
310
378
  <div class="flex items-center">
311
379
  <span class="text-gray-700 mr-2"
@@ -350,7 +418,7 @@ const getStatus = (status?: UploadStatus) => {
350
418
  </div>
351
419
  <div v-if="showDelete !== false"
352
420
  class="flex items-center text-red-500 hover:text-red-700 cursor-pointer">
353
- <Popconfirm v-model:open="confirmOpen" cancelText="否" okText="是" title="确定删除该文件吗?"
421
+ <Popconfirm :open="confirmOpen" cancelText="否" okText="是" title="确定删除该文件吗?"
354
422
  :okButtonProps="{ size: 'small' }" :cancelButtonProps="{ size: 'small' }"
355
423
  @confirm="removeFile(index)" @cancel="confirmOpen = false">
356
424
  <Tooltip title="删除">
@@ -388,4 +456,4 @@ const getStatus = (status?: UploadStatus) => {
388
456
  .error-text {
389
457
  color: #ff4d4f !important;
390
458
  }
391
- </style>
459
+ </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';
@@ -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
+ };