update:文件选择优化

This commit is contained in:
2025-12-02 17:23:25 +08:00
parent aac5333174
commit 61b04d0231
3 changed files with 197 additions and 46 deletions

View File

@@ -0,0 +1,139 @@
<template>
<div class="comp-chose-file">
<el-upload
class="upload-btn"
v-model:file-list="fileList"
:multiple="multiple"
:auto-upload="false"
@change="changeFun"
>
<el-button type="primary" :disabled="disabled">选择文件</el-button>
<template #file="{ file, index }">
<div class="file-item">
<div class="file-icon">
<el-icon :size="16">
<Document />
</el-icon>
</div>
<div class="file-name">
{{ file.name }}
</div>
<div class="file-options">
<el-icon v-if="file.fileId" class="btn preview" :size="16" @click="downloadFun(file)">
<Download />
</el-icon>
<el-icon v-if="file.fileId" class="btn preview" :size="16" @click="previewFun(file)">
<View />
</el-icon>
<el-icon v-if="!disabled" class="btn del" :size="16" @click="removeFun(index)">
<Delete />
</el-icon>
</div>
</div>
</template>
</el-upload>
<FilePreview v-model="previewVisible" :fileId="fileId" />
</div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
import { Document, View, Delete, Download } from '@element-plus/icons-vue';
import FilePreview from '@/components/common/filePreview/index.vue';
import { downloadFileById } from '@/utils/file';
import { isArray } from 'lodash-es';
interface Props {
modelValue: any;
disabled?: boolean;
multiple?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
modelValue: [],
disabled: false,
multiple: true,
});
const emit = defineEmits(['update:modelValue', 'change', 'remove']);
const fileList = ref<any>([]);
const fileId = ref<any>(0);
const previewVisible = ref(false);
watch(
() => props.modelValue,
(val: any) => {
fileList.value = isArray(val) ? val : [];
},
{ deep: true, immediate: true }
);
watch(
() => fileList.value,
(val: any) => {
emit('update:modelValue', val);
},
{ deep: true, immediate: true }
);
const previewFun = (data: any) => {
fileId.value = data.fileId;
previewVisible.value = true;
};
const downloadFun = (data: any) => {
downloadFileById(data.fileId);
};
const changeFun = (data: any) => {
emit('change', data);
};
const removeFun = (index: number) => {
emit('remove', fileList.value[index]);
fileList.value.splice(index, 1);
};
</script>
<style lang="scss" scoped>
.comp-chose-file {
width: 100%;
.upload-btn {
width: 100%;
}
.file-item {
display: flex;
line-height: 18px;
padding: 0 4px;
.file-icon {
display: flex;
align-items: center;
justify-content: center;
color: var(--el-text-color-secondary);
}
.file-name {
flex: 1;
font-size: 14px;
color: var(--el-text-color-secondary);
padding-right: 5px;
word-break: break-word;
padding: 2px 4px;
}
.file-options {
display: flex;
align-items: center;
justify-content: center;
.btn {
margin-left: 8px;
cursor: pointer;
&.preview {
color: var(--el-color-primary);
}
&.del {
color: var(--el-color-danger);
}
}
}
}
}
</style>

View File

@@ -4,7 +4,7 @@
v-if="item.inputMode === 'input'"
v-bind="attrs"
v-model="formData[item.key]"
:placeholder="(item.disabled && showDisabled) ? '' : '请输入'"
:placeholder="item.disabled && showDisabled ? '' : '请输入'"
clearable
:disabled="item.disabled && showDisabled"
@input="(val: any) => changeFun(item.key, val)"
@@ -15,7 +15,7 @@
v-model="formData[item.key]"
type="textarea"
autosize
:placeholder="(item.disabled && showDisabled) ? '' : '请输入'"
:placeholder="item.disabled && showDisabled ? '' : '请输入'"
:disabled="item.disabled && showDisabled"
@input="(val: any) => changeFun(item.key, val)"
/>
@@ -23,7 +23,7 @@
v-if="item.inputMode === 'inputNumber'"
v-bind="attrs"
v-model="formData[item.key]"
:placeholder="(item.disabled && showDisabled) ? '' : '请输入'"
:placeholder="item.disabled && showDisabled ? '' : '请输入'"
:min="0"
:step="item.step"
step-strictly
@@ -34,7 +34,7 @@
v-if="item.inputMode === 'select'"
v-bind="attrs"
v-model="formData[item.key]"
:placeholder="(item.disabled && showDisabled) ? '' : '请选择'"
:placeholder="item.disabled && showDisabled ? '' : '请选择'"
clearable
:options="item.options"
:disabled="item.disabled && showDisabled"
@@ -47,7 +47,12 @@
:disabled="item.disabled && showDisabled"
@change="(val: any) => changeFun(item.key, val)"
>
<el-radio v-for="(val) in item.options" :key="val.value" :label="val.label" :value="val.value" />
<el-radio
v-for="val in item.options"
:key="val.value"
:label="val.label"
:value="val.value"
/>
</el-radio-group>
<el-checkbox-group
v-if="item.inputMode === 'checkbox'"
@@ -56,7 +61,12 @@
:disabled="item.disabled && showDisabled"
@change="(val: any) => changeFun(item.key, val)"
>
<el-checkbox v-for="(val) in item.options" :key="val.value" :label="val.label" :value="val.value" />
<el-checkbox
v-for="val in item.options"
:key="val.value"
:label="val.label"
:value="val.value"
/>
</el-checkbox-group>
<MultipleSelect
v-if="item.inputMode === 'multipleSelect'"
@@ -72,7 +82,7 @@
type="datetime"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
:placeholder="(item.disabled && showDisabled) ? '' : '请选择'"
:placeholder="item.disabled && showDisabled ? '' : '请选择'"
clearable
:disabled="item.disabled && showDisabled"
@change="(val: any) => changeFun(item.key, val)"
@@ -90,20 +100,14 @@
:disabled="item.disabled && showDisabled"
@change="(val: any) => changeFun(item.key, val)"
/>
<el-upload
<ChoseFile
v-if="item.inputMode === 'choseFile'"
v-bind="attrs"
v-model:file-list="formData[item.key]"
class="upload-btn"
:limit="item.limit"
:multiple="item.limit ? (item.limit > 1) : false"
:auto-upload="false"
v-model="formData[item.key]"
:disabled="item.disabled && showDisabled"
@change="(val: any) => changeFun(item.key, val)"
@remove="(val: any) => removeFun(item.key, val)"
>
<template #trigger><el-button type="primary" :disabled="item.disabled && showDisabled">选择文件</el-button></template>
</el-upload>
/>
<UploadImg
v-if="item.inputMode === 'uploadImg'"
v-model="formData[item.key]"
@@ -150,6 +154,7 @@
import { ref, watch } from 'vue';
import MultipleSelect from './multipleSelect.vue';
import UploadImg from './uploadImg.vue';
import ChoseFile from './choseFile.vue';
import UserSelect from '@/components/common/userSelect/index.vue';
import ProjectSelect from '@/components/common/projectSelect/index.vue';
import ApproveList from '@/components/common/approveList/index.vue';
@@ -168,9 +173,12 @@ const props = withDefaults(defineProps<Props>(), {
attrs: {},
});
watch(() => props.form, (val: any) => {
formData.value = val;
});
watch(
() => props.form,
(val: any) => {
formData.value = val;
}
);
const emit = defineEmits(['change', 'remove']);
@@ -192,9 +200,3 @@ const removeFun = (key: string, val: any) => {
});
};
</script>
<style lang="scss" scoped>
.upload-btn {
width: 100%;
}
</style>

View File

@@ -1,13 +1,9 @@
<template>
<div class="comp-upload-list">
<div ref="dragRef" class="btn" @click="openFun" @mousedown="startDragFun"><el-icon :size="22"><Upload /></el-icon></div>
<el-drawer
title="上传列表"
v-model="listVisible"
size="400"
:modal="false"
modal-penetrable
>
<div ref="dragRef" class="btn" @click="openFun" @mousedown="startDragFun">
<el-icon :size="22"><Upload /></el-icon>
</div>
<el-drawer title="上传列表" v-model="listVisible" size="400" :modal="false" modal-penetrable>
<div class="content">
<div v-if="listData.length > 0" class="list">
<div v-for="(item, index) in listData" :key="item.file.name" class="item">
@@ -23,7 +19,7 @@
<div class="progress">
<el-progress
:show-text="false"
:percentage="Number(item.data.current / item.data.total * 100) || 0"
:percentage="Number((item.data.current / item.data.total) * 100) || 0"
:status="item.data.status === '-1' ? 'exception' : ''"
:stroke-width="5"
/>
@@ -31,11 +27,18 @@
<div class="info">
<div class="speed">{{ item.data.speed || '--' }}/s</div>
<div class="size">
{{ formatFileSize((item.data.current / item.data.total * item.file.size) || 0) || '--' }}/{{ formatFileSize(item.file.size) }}
{{
formatFileSize((item.data.current / item.data.total) * item.file.size || 0) ||
'--'
}}/{{ formatFileSize(item.file.size) }}
</div>
</div>
</div>
<div class="options"><el-icon v-if="item.data.status !== '1'" :size="16" @click="removeFun(index)"><Delete /></el-icon></div>
<div class="options">
<el-icon v-if="item.data.status !== '1'" :size="16" @click="removeFun(index)"
><Delete
/></el-icon>
</div>
</div>
</div>
<div v-else class="no-task">暂无上传任务</div>
@@ -49,14 +52,15 @@ import { ref } from 'vue';
import { Upload, Delete } from '@element-plus/icons-vue';
import { ElMessage } from 'element-plus';
import { formatFileSize } from '@/utils/file';
import { chunkUploadToMinioApi, callBackknowledgeFileApi, chunkUploadCallbackApi } from '@/api/data/data';
import { chunkUploadToMinioApi, chunkUploadCallbackApi } from '@/api/data/data';
import emitter from '@/utils/eventBus';
const taskStatusObj: any = {}; // 更具uploadTaskId和businessId记录所以任务文件的上传状态
const listVisible = ref(false);
const listData = ref<any>([]);
const chunkSize = 1024 * 1024 * 10; // 每片10MB
const UPLOAD_FILE_STATUS: any = { // TODO
const UPLOAD_FILE_STATUS: any = {
// TODO
'-1': '上传失败',
'0': '待上传',
'1': '上传中',
@@ -90,7 +94,7 @@ const initFun = (data: any) => {
};
// 分片并上传
const sliceFileFun = async(fileIndex: number) => {
const sliceFileFun = async (fileIndex: number) => {
const fileObj = listData.value[fileIndex];
const file = fileObj.file;
const fileData = fileObj.data;
@@ -119,7 +123,7 @@ const sliceFileFun = async(fileIndex: number) => {
fileObj.data.total = totalChunks;
fileObj.data.current = chunkIndex + 1;
fileObj.data.speed = res.speed;
fileObj.data.status = (chunkIndex + 1 === totalChunks) ? '2' : '1';
fileObj.data.status = chunkIndex + 1 === totalChunks ? '2' : '1';
} else {
fileObj.data.status = '-1';
}
@@ -142,9 +146,15 @@ const sliceFileFun = async(fileIndex: number) => {
const callBackFun = (data: any) => {
const { uploadTaskId, isApprove, taskType } = data;
const totalTaskNum = Object.keys(taskStatusObj[uploadTaskId]).length;
const finishTaskNum = Object.values(taskStatusObj[uploadTaskId]).filter(val => val === '2' || val === '-1').length;
const successData = Object.entries(taskStatusObj[uploadTaskId]).filter(([, value]) => value === '2').map(([key]) => key);
const failData = Object.entries(taskStatusObj[uploadTaskId]).filter(([, value]) => value === '-1').map(([key]) => key);
const finishTaskNum = Object.values(taskStatusObj[uploadTaskId]).filter(
(val) => val === '2' || val === '-1'
).length;
const successData = Object.entries(taskStatusObj[uploadTaskId])
.filter(([, value]) => value === '2')
.map(([key]) => key);
const failData = Object.entries(taskStatusObj[uploadTaskId])
.filter(([, value]) => value === '-1')
.map(([key]) => key);
if (finishTaskNum === totalTaskNum) {
const params = {
uploadTaskId: uploadTaskId,
@@ -153,14 +163,14 @@ const callBackFun = (data: any) => {
isApprove,
};
const apiObj: any = {
2: callBackknowledgeFileApi, // 知识库后处理
4: chunkUploadCallbackApi,
2: chunkUploadCallbackApi, // 知识库后处理
4: chunkUploadCallbackApi, // 交付物后处理
};
apiObj[taskType](params);
}
};
const uploadFun = async(params: any) => {
const uploadFun = async (params: any) => {
const starTime = new Date().getTime();
const res: any = await chunkUploadToMinioApi(params);
const data = res.data || {};