diff --git a/src/components/common/ImageUpload.md b/src/components/common/ImageUpload.md
index 36f2aea..2eead57 100644
--- a/src/components/common/ImageUpload.md
+++ b/src/components/common/ImageUpload.md
@@ -9,24 +9,24 @@
- 支持上传进度显示
- 支持图片预览
- 支持多文件上传
-- 上传成功后自动回传URL
+- 上传成功后自动回传 URL
## Props
-| 参数 | 类型 | 默认值 | 说明 |
-|------|------|--------|------|
-| modelValue | UploadFile[] | [] | 文件列表,支持v-model |
-| uploadType | UploadType | - | 上传类型:1合同模版,2图片 |
-| maxSize | number | 10 * 1024 * 1024 | 最大文件大小(字节) |
-| maxCount | number | - | 最大上传数量 |
+| 参数 | 类型 | 默认值 | 说明 |
+| ---------- | ------------ | ---------------- | ---------------------------- |
+| modelValue | UploadFile[] | [] | 文件列表,支持 v-model |
+| uploadType | UploadType | - | 上传类型:1 合同模版,2 图片 |
+| maxSize | number | 10 _ 1024 _ 1024 | 最大文件大小(字节) |
+| maxCount | number | - | 最大上传数量 |
## Events
-| 事件名 | 参数 | 说明 |
-|--------|------|------|
-| update:modelValue | (value: UploadFile[]) | 文件列表更新时触发 |
-| upload-success | (file: UploadFile, response: UploadResponse) | 上传成功时触发 |
-| upload-error | (error: Error) | 上传失败时触发 |
+| 事件名 | 参数 | 说明 |
+| ----------------- | -------------------------------------------- | ------------------ |
+| update:modelValue | (value: UploadFile[]) | 文件列表更新时触发 |
+| upload-success | (file: UploadFile, response: UploadResponse) | 上传成功时触发 |
+| upload-error | (error: Error) | 上传失败时触发 |
## 类型定义
@@ -42,7 +42,8 @@ interface UploadFile {
interface UploadResponse {
filename: string;
size: number;
- url: string;
+ signedPutURL: string;
+ objectKey?: string;
}
type UploadType = 1 | 2; // 1合同模版,2图片
@@ -63,18 +64,18 @@ type UploadType = 1 | 2; // 1合同模版,2图片
```
@@ -110,6 +111,6 @@ const handleUploadError = (error) => {
## 注意事项
1. 组件会自动处理文件验证,包括文件大小和格式检查
-2. 上传成功后,文件对象会自动更新URL和状态
-3. 支持v-model双向绑定,父组件可以直接操作文件列表
-4. 上传类型必须从父组件传入,用于区分不同的上传场景
\ No newline at end of file
+2. 上传成功后,文件对象会自动更新 URL 和状态
+3. 支持 v-model 双向绑定,父组件可以直接操作文件列表
+4. 上传类型必须从父组件传入,用于区分不同的上传场景
diff --git a/src/components/common/ImageUpload.vue b/src/components/common/ImageUpload.vue
index cbc5fac..d34a584 100644
--- a/src/components/common/ImageUpload.vue
+++ b/src/components/common/ImageUpload.vue
@@ -1,7 +1,7 @@
();
const emit = defineEmits<{
- (e: 'update:modelValue', value: string[]): void;
- (e: 'upload-success', url: string): void;
+ (e: 'update:objectKeys', value: string[]): void;
+ (e: 'update:signedPutURL', value: string[]): void;
+ (
+ e: 'upload-success',
+ payload: { objectKey: string; signedPutURL: string }
+ ): void;
(e: 'upload-error', error: Error): void;
}>();
const fileList = ref([]);
-
- // 默认最大上传数:1(单图)
const maxCount = props.maxCount ?? 1;
-
- // listType 默认值
const listType = props.listType ?? 'picture-card';
- // 将外部 url[] 映射为 UploadFile[]
+ // 回显处理(编辑/刷新时)
watch(
- () => props.modelValue,
- (newVal) => {
- fileList.value = (newVal || []).map((url, index) => ({
- uid: `${index}`,
- name: `图片-${index + 1}`,
- url,
+ () => [props.objectKeys, props.signedPutURL],
+ ([keys, urls]) => {
+ fileList.value = (keys || []).map((key, idx) => ({
+ uid: `${idx}`,
+ name: key.split('/').pop() || `图片-${idx + 1}`,
+ url: urls[idx] || '',
+ objectKey: key,
status: 'done',
}));
},
- { immediate: true }
+ { immediate: true, deep: true }
);
// 自定义上传请求
@@ -80,15 +84,12 @@
'image/webp',
];
- // 文件大小校验
if (file.size > maxSize) {
onError(
new Error(`文件大小不能超过${Math.round(maxSize / 1024 / 1024)}MB`)
);
return;
}
-
- // 文件类型校验
if (!allowedTypes.includes(file.type)) {
onError(new Error('只支持 JPG、PNG、GIF、WEBP 格式的图片'));
return;
@@ -114,44 +115,54 @@
clearInterval(progressInterval);
if (code === 200 && data) {
- const response = data as UploadResponse;
-
+ const res = data as UploadResponse;
fileItem.status = 'done';
- fileItem.url = response.url;
+ fileItem.url = res.signedPutURL;
+ fileItem.objectKey = res.objectKey;
onProgress({ percent: 100 });
- onSuccess(response);
+ onSuccess(res);
- // 新的图片 url
- let updatedUrls = [...props.modelValue];
+ // 更新父组件两个数组
+ let newKeys = [...props.objectKeys];
+ let newUrls = [...props.signedPutURL];
if (maxCount === 1) {
- updatedUrls = [response.url];
+ newKeys = [res.objectKey];
+ newUrls = [res.signedPutURL];
} else {
- updatedUrls.push(response.url);
- // 截取至 maxCount
- updatedUrls = updatedUrls.slice(0, maxCount);
+ newKeys.push(res.objectKey);
+ newUrls.push(res.signedPutURL);
+ newKeys = newKeys.slice(0, maxCount);
+ newUrls = newUrls.slice(0, maxCount);
}
-
- emit('update:modelValue', updatedUrls);
- emit('upload-success', response.url);
+ emit('update:objectKeys', newKeys);
+ emit('update:signedPutURL', newUrls);
+ emit('upload-success', {
+ objectKey: res.objectKey,
+ signedPutURL: res.signedPutURL,
+ });
} else {
const error = new Error(message || '上传失败');
onError(error);
emit('upload-error', error);
}
- } catch (error) {
- const err = error instanceof Error ? error : new Error('上传失败');
+ } catch (e) {
+ const err = e instanceof Error ? e : new Error('上传失败');
onError(err);
emit('upload-error', err);
}
};
- // 同步 fileList(删除时触发)
+ // 删除文件时同步更新父组件
const handleChange = (newFileList: UploadFile[]) => {
- const validUrls = newFileList
+ const newKeys = newFileList
+ .filter((f) => f.status === 'done' && f.objectKey)
+ .map((f) => f.objectKey!);
+ const newUrls = newFileList
.filter((f) => f.status === 'done' && f.url)
.map((f) => f.url!);
- emit('update:modelValue', validUrls);
+ emit('update:objectKeys', newKeys);
+ emit('update:signedPutURL', newUrls);
};
diff --git a/src/types/business.ts b/src/types/business.ts
index 2fd5a7d..7575088 100644
--- a/src/types/business.ts
+++ b/src/types/business.ts
@@ -72,15 +72,16 @@ export type ProjectListRes = HttpResponse<{
projectsList?: ProjectItem[];
}>;
export interface CreateProjectParam {
- id?: number;
+ id?: number | null;
type: ProjectType; // 项目类型:1写字楼,2商业综合体,3园区
name: string; // 楼栋名称
province_id: number | null; // 省份id
city_id: number | null; // 城市id
district_id: number | null; // 区域id
address: string; // 详细地址
- images: string[]; // 图片数组
- buildings: BuildingItem[]; // 楼栋数据
+ objectKeys: string[]; // 存储给后端的 key
+ signedPutURL: string[]; // 存储已签名 URL
+ buildings: BuildingItem[];
}
export interface BuildingInfo extends BaseEntity {
projectId: number; // 项目id
diff --git a/src/views/business/project/drawer/project-form-drawer.vue b/src/views/business/project/drawer/project-form-drawer.vue
index 0cc9ef1..2badfc9 100644
--- a/src/views/business/project/drawer/project-form-drawer.vue
+++ b/src/views/business/project/drawer/project-form-drawer.vue
@@ -104,12 +104,13 @@
-
+
@@ -214,13 +215,15 @@
const formRef = ref();
const defaultForm: CreateProjectParam = {
+ id: null,
type: 1 as ProjectType, // 项目类型:1写字楼,2商业综合体,3园区
name: '', // 楼栋名称
province_id: null, // 省份id
city_id: null, // 城市id
district_id: null, // 区域id
address: '', // 详细地址
- images: [], // 图片数组
+ objectKeys: [], // 图片数组
+ signedPutURL: [], // 图片数组
buildings: [createEmptyBuilding()], // 楼栋数据
};
const form = reactive({ ...defaultForm });
@@ -306,7 +309,7 @@
});
}
if (record?.imageUrls?.length) {
- record.images = record?.imageUrls;
+ record.objectKeys = record?.imageUrls;
}
form.name = record.name;
Object.keys(form).forEach((key) => {
@@ -330,7 +333,6 @@
},
{ immediate: true }
);
-
const handleOk = async () => {
if (isLoading.value) return;
try {
@@ -345,9 +347,10 @@
id: item?.id,
};
});
- const params = {
+ const params: any = {
...form,
};
+ delete params.signedPutURL;
const res = props?.isEdit
? await updateProject(params)
: await createProject(params);