feat: 实现异步文件上传前端功能
- 添加批量上传API接口 - 扩展UploadData组件,添加批量上传弹窗 - 添加统计卡片展示(上传中、解析中、成功、失败) - 添加文件上传记录列表 - 实现轮询机制自动刷新状态 - 支持文件数量、格式、大小校验 - 支持手动刷新和状态筛选 - 添加响应式布局支持
This commit is contained in:
@@ -79,3 +79,63 @@ export function getImportStatus(taskId) {
|
|||||||
method: 'get'
|
method: 'get'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========== 批量文件上传相关接口 ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量上传文件
|
||||||
|
* @param {Number} projectId 项目ID
|
||||||
|
* @param {Array<File>} files 文件数组
|
||||||
|
* @returns {Promise} 返回 batchId
|
||||||
|
*/
|
||||||
|
export function batchUploadFiles(projectId, files) {
|
||||||
|
const formData = new FormData()
|
||||||
|
files.forEach(file => {
|
||||||
|
formData.append('files', file)
|
||||||
|
})
|
||||||
|
formData.append('projectId', projectId)
|
||||||
|
|
||||||
|
return request({
|
||||||
|
url: '/ccdi/file-upload/batch',
|
||||||
|
method: 'post',
|
||||||
|
data: formData,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data'
|
||||||
|
},
|
||||||
|
timeout: 300000 // 5分钟超时
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询文件上传记录列表
|
||||||
|
* @param {Object} params 查询参数
|
||||||
|
*/
|
||||||
|
export function getFileUploadList(params) {
|
||||||
|
return request({
|
||||||
|
url: '/ccdi/file-upload/list',
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询文件上传统计
|
||||||
|
* @param {Number} projectId 项目ID
|
||||||
|
*/
|
||||||
|
export function getFileUploadStatistics(projectId) {
|
||||||
|
return request({
|
||||||
|
url: `/ccdi/file-upload/statistics/${projectId}`,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询文件上传详情
|
||||||
|
* @param {Number} id 记录ID
|
||||||
|
*/
|
||||||
|
export function getFileUploadDetail(id) {
|
||||||
|
return request({
|
||||||
|
url: `/ccdi/file-upload/detail/${id}`,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -46,6 +46,126 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 统计卡片区域 -->
|
||||||
|
<div class="statistics-section">
|
||||||
|
<div class="stat-card" @click="handleStatusFilter('uploading')">
|
||||||
|
<div class="stat-icon uploading">
|
||||||
|
<i class="el-icon-upload"></i>
|
||||||
|
</div>
|
||||||
|
<div class="stat-content">
|
||||||
|
<div class="stat-label">上传中</div>
|
||||||
|
<div class="stat-value">{{ statistics.uploading }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stat-card" @click="handleStatusFilter('parsing')">
|
||||||
|
<div class="stat-icon parsing">
|
||||||
|
<i class="el-icon-loading"></i>
|
||||||
|
</div>
|
||||||
|
<div class="stat-content">
|
||||||
|
<div class="stat-label">解析中</div>
|
||||||
|
<div class="stat-value">{{ statistics.parsing }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stat-card" @click="handleStatusFilter('parsed_success')">
|
||||||
|
<div class="stat-icon success">
|
||||||
|
<i class="el-icon-success"></i>
|
||||||
|
</div>
|
||||||
|
<div class="stat-content">
|
||||||
|
<div class="stat-label">解析成功</div>
|
||||||
|
<div class="stat-value">{{ statistics.parsed_success }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stat-card" @click="handleStatusFilter('parsed_failed')">
|
||||||
|
<div class="stat-icon failed">
|
||||||
|
<i class="el-icon-error"></i>
|
||||||
|
</div>
|
||||||
|
<div class="stat-content">
|
||||||
|
<div class="stat-label">解析失败</div>
|
||||||
|
<div class="stat-value">{{ statistics.parsed_failed }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 文件上传记录列表 -->
|
||||||
|
<div class="file-list-section">
|
||||||
|
<div class="list-toolbar">
|
||||||
|
<div class="filter-group">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.fileStatus"
|
||||||
|
placeholder="文件状态"
|
||||||
|
clearable
|
||||||
|
@change="loadFileList"
|
||||||
|
style="width: 150px"
|
||||||
|
>
|
||||||
|
<el-option label="上传中" value="uploading"></el-option>
|
||||||
|
<el-option label="解析中" value="parsing"></el-option>
|
||||||
|
<el-option label="解析成功" value="parsed_success"></el-option>
|
||||||
|
<el-option label="解析失败" value="parsed_failed"></el-option>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-button icon="el-icon-refresh" @click="handleManualRefresh">刷新</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-table :data="fileUploadList" v-loading="listLoading" stripe border>
|
||||||
|
<el-table-column prop="fileName" label="文件名" min-width="200"></el-table-column>
|
||||||
|
<el-table-column prop="fileSize" label="文件大小" width="120">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
{{ formatFileSize(scope.row.fileSize) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="fileStatus" label="状态" width="120">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-tag :type="getStatusType(scope.row.fileStatus)" size="small">
|
||||||
|
{{ getStatusText(scope.row.fileStatus) }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="enterpriseNames" label="主体名称" min-width="150">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
{{ scope.row.enterpriseNames || '-' }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="uploadTime" label="上传时间" width="160"></el-table-column>
|
||||||
|
<el-table-column prop="uploadUser" label="上传人" width="100"></el-table-column>
|
||||||
|
<el-table-column label="操作" width="120" fixed="right">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-button
|
||||||
|
v-if="scope.row.fileStatus === 'parsed_success'"
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
@click="handleViewFlow(scope.row)"
|
||||||
|
>
|
||||||
|
查看流水
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-if="scope.row.fileStatus === 'parsed_failed'"
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
@click="handleViewError(scope.row)"
|
||||||
|
>
|
||||||
|
查看错误
|
||||||
|
</el-button>
|
||||||
|
<span v-if="scope.row.fileStatus === 'uploading' || scope.row.fileStatus === 'parsing'">
|
||||||
|
-
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<el-pagination
|
||||||
|
@current-change="handlePageChange"
|
||||||
|
:current-page="queryParams.pageNum"
|
||||||
|
:page-size="queryParams.pageSize"
|
||||||
|
:total="total"
|
||||||
|
layout="total, prev, pager, next, jumper"
|
||||||
|
style="margin-top: 16px; text-align: right"
|
||||||
|
></el-pagination>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 数据质量检查
|
<!-- 数据质量检查
|
||||||
<div class="quality-check-section">
|
<div class="quality-check-section">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
@@ -149,6 +269,63 @@
|
|||||||
>
|
>
|
||||||
</span>
|
</span>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 批量上传弹窗 -->
|
||||||
|
<el-dialog
|
||||||
|
title="批量上传流水文件"
|
||||||
|
:visible.sync="batchUploadDialogVisible"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
width="700px"
|
||||||
|
>
|
||||||
|
<el-upload
|
||||||
|
class="batch-upload-area"
|
||||||
|
drag
|
||||||
|
action="#"
|
||||||
|
multiple
|
||||||
|
:auto-upload="false"
|
||||||
|
:on-change="handleBatchFileChange"
|
||||||
|
:file-list="selectedFiles"
|
||||||
|
>
|
||||||
|
<i class="el-icon-upload"></i>
|
||||||
|
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||||
|
<div class="el-upload__tip" slot="tip">
|
||||||
|
支持 .xlsx, .xls 格式文件,最多100个文件,单个文件不超过50MB
|
||||||
|
</div>
|
||||||
|
</el-upload>
|
||||||
|
|
||||||
|
<div v-if="selectedFiles.length > 0" class="selected-files">
|
||||||
|
<div class="files-header">
|
||||||
|
<span>已选择 {{ selectedFiles.length }} 个文件</span>
|
||||||
|
</div>
|
||||||
|
<div class="files-list">
|
||||||
|
<div
|
||||||
|
v-for="(file, index) in selectedFiles"
|
||||||
|
:key="index"
|
||||||
|
class="file-item"
|
||||||
|
>
|
||||||
|
<i class="el-icon-document"></i>
|
||||||
|
<span class="file-name">{{ file.name }}</span>
|
||||||
|
<span class="file-size">{{ formatFileSize(file.size) }}</span>
|
||||||
|
<el-button
|
||||||
|
type="text"
|
||||||
|
icon="el-icon-close"
|
||||||
|
@click="handleRemoveFile(index)"
|
||||||
|
></el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span slot="footer">
|
||||||
|
<el-button @click="batchUploadDialogVisible = false">取消</el-button>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
:loading="uploadLoading"
|
||||||
|
:disabled="selectedFiles.length === 0"
|
||||||
|
@click="handleBatchUpload"
|
||||||
|
>开始上传</el-button
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -160,6 +337,9 @@ import {
|
|||||||
pullBankInfo,
|
pullBankInfo,
|
||||||
updateNameListSelection,
|
updateNameListSelection,
|
||||||
uploadFile,
|
uploadFile,
|
||||||
|
batchUploadFiles,
|
||||||
|
getFileUploadList,
|
||||||
|
getFileUploadStatistics,
|
||||||
} from "@/api/ccdiProjectUpload";
|
} from "@/api/ccdiProjectUpload";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -261,6 +441,34 @@ export default {
|
|||||||
level: "info",
|
level: "info",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
// === 批量上传相关 ===
|
||||||
|
batchUploadDialogVisible: false,
|
||||||
|
selectedFiles: [],
|
||||||
|
uploadLoading: false,
|
||||||
|
|
||||||
|
// === 统计数据 ===
|
||||||
|
statistics: {
|
||||||
|
uploading: 0,
|
||||||
|
parsing: 0,
|
||||||
|
parsed_success: 0,
|
||||||
|
parsed_failed: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
// === 文件列表相关 ===
|
||||||
|
fileUploadList: [],
|
||||||
|
listLoading: false,
|
||||||
|
queryParams: {
|
||||||
|
projectId: null,
|
||||||
|
fileStatus: null,
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
},
|
||||||
|
total: 0,
|
||||||
|
|
||||||
|
// === 轮询相关 ===
|
||||||
|
pollingTimer: null,
|
||||||
|
pollingEnabled: false,
|
||||||
|
pollingInterval: 5000,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
@@ -272,6 +480,20 @@ export default {
|
|||||||
mounted() {
|
mounted() {
|
||||||
// 组件挂载后监听项目ID变化
|
// 组件挂载后监听项目ID变化
|
||||||
this.$watch("projectId", this.loadInitialData);
|
this.$watch("projectId", this.loadInitialData);
|
||||||
|
|
||||||
|
// 加载统计数据和文件列表
|
||||||
|
this.loadStatistics();
|
||||||
|
this.loadFileList();
|
||||||
|
|
||||||
|
// 检查是否需要启动轮询
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (this.statistics.uploading > 0 || this.statistics.parsing > 0) {
|
||||||
|
this.startPolling();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.stopPolling();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
/** 加载初始数据 */
|
/** 加载初始数据 */
|
||||||
@@ -361,13 +583,19 @@ export default {
|
|||||||
const card = this.uploadCards.find((c) => c.key === key);
|
const card = this.uploadCards.find((c) => c.key === key);
|
||||||
if (!card) return;
|
if (!card) return;
|
||||||
|
|
||||||
if (key === "namelist") {
|
if (key === "transaction") {
|
||||||
this.showNameListDialog = true;
|
// 流水导入 - 打开批量上传弹窗
|
||||||
} else {
|
this.batchUploadDialogVisible = true;
|
||||||
|
this.selectedFiles = [];
|
||||||
|
} else if (key === "credit") {
|
||||||
|
// 征信导入 - 保持现有逻辑
|
||||||
this.uploadFileType = key;
|
this.uploadFileType = key;
|
||||||
this.uploadDialogTitle = `上传${card.title}`;
|
this.uploadDialogTitle = `上传${card.title}`;
|
||||||
this.uploadFileTypes = card.desc.replace(/.*支持|上传/g, "").trim();
|
this.uploadFileTypes = card.desc.replace(/.*支持|上传/g, "").trim();
|
||||||
this.showUploadDialog = true;
|
this.showUploadDialog = true;
|
||||||
|
} else if (key === "namelist") {
|
||||||
|
// 名单库选择 - 保持现有逻辑
|
||||||
|
this.showNameListDialog = true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
/** 文件选择变化 */
|
/** 文件选择变化 */
|
||||||
@@ -601,6 +829,223 @@ export default {
|
|||||||
};
|
};
|
||||||
return statusMap[status] || "未知";
|
return statusMap[status] || "未知";
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// === 批量上传相关方法 ===
|
||||||
|
|
||||||
|
/** 批量上传的文件选择变化 */
|
||||||
|
handleBatchFileChange(file, fileList) {
|
||||||
|
if (fileList.length > 100) {
|
||||||
|
this.$message.warning("最多上传100个文件");
|
||||||
|
fileList = fileList.slice(0, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
const validTypes = [".xlsx", ".xls"];
|
||||||
|
const invalidFiles = fileList.filter((f) => {
|
||||||
|
const ext = f.name.substring(f.name.lastIndexOf(".")).toLowerCase();
|
||||||
|
return !validTypes.includes(ext);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (invalidFiles.length > 0) {
|
||||||
|
this.$message.error("仅支持 .xlsx, .xls 格式文件");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const oversizedFiles = fileList.filter((f) => f.size > 50 * 1024 * 1024);
|
||||||
|
if (oversizedFiles.length > 0) {
|
||||||
|
this.$message.error("单个文件不能超过50MB");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selectedFiles = fileList;
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 删除已选文件 */
|
||||||
|
handleRemoveFile(index) {
|
||||||
|
this.selectedFiles.splice(index, 1);
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 开始批量上传 */
|
||||||
|
async handleBatchUpload() {
|
||||||
|
if (this.selectedFiles.length === 0) {
|
||||||
|
this.$message.warning("请选择要上传的文件");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.uploadLoading = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await batchUploadFiles(
|
||||||
|
this.projectId,
|
||||||
|
this.selectedFiles.map((f) => f.raw)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.uploadLoading = false;
|
||||||
|
this.batchUploadDialogVisible = false;
|
||||||
|
|
||||||
|
this.$message.success("上传任务已提交,请查看处理进度");
|
||||||
|
|
||||||
|
// 刷新数据并启动轮询
|
||||||
|
await Promise.all([this.loadStatistics(), this.loadFileList()]);
|
||||||
|
|
||||||
|
this.startPolling();
|
||||||
|
} catch (error) {
|
||||||
|
this.uploadLoading = false;
|
||||||
|
this.$message.error("上传失败:" + (error.msg || "未知错误"));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// === 统计和列表相关方法 ===
|
||||||
|
|
||||||
|
/** 加载统计数据 */
|
||||||
|
async loadStatistics() {
|
||||||
|
try {
|
||||||
|
const res = await getFileUploadStatistics(this.projectId);
|
||||||
|
this.statistics = res.data || {
|
||||||
|
uploading: 0,
|
||||||
|
parsing: 0,
|
||||||
|
parsed_success: 0,
|
||||||
|
parsed_failed: 0,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("加载统计数据失败:", error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 加载文件列表 */
|
||||||
|
async loadFileList() {
|
||||||
|
this.listLoading = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
projectId: this.projectId,
|
||||||
|
fileStatus: this.queryParams.fileStatus,
|
||||||
|
pageNum: this.queryParams.pageNum,
|
||||||
|
pageSize: this.queryParams.pageSize,
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await getFileUploadList(params);
|
||||||
|
this.fileUploadList = res.rows || [];
|
||||||
|
this.total = res.total || 0;
|
||||||
|
} catch (error) {
|
||||||
|
this.$message.error("加载文件列表失败");
|
||||||
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
this.listLoading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// === 轮询相关方法 ===
|
||||||
|
|
||||||
|
/** 启动轮询 */
|
||||||
|
startPolling() {
|
||||||
|
if (this.pollingEnabled) return;
|
||||||
|
this.pollingEnabled = true;
|
||||||
|
|
||||||
|
const poll = () => {
|
||||||
|
if (!this.pollingEnabled) return;
|
||||||
|
|
||||||
|
Promise.all([this.loadStatistics(), this.loadFileList()])
|
||||||
|
.then(() => {
|
||||||
|
if (
|
||||||
|
this.statistics.uploading === 0 &&
|
||||||
|
this.statistics.parsing === 0
|
||||||
|
) {
|
||||||
|
this.stopPolling();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.pollingTimer = setTimeout(poll, this.pollingInterval);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("轮询失败:", error);
|
||||||
|
this.pollingTimer = setTimeout(poll, this.pollingInterval);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
poll();
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 停止轮询 */
|
||||||
|
stopPolling() {
|
||||||
|
this.pollingEnabled = false;
|
||||||
|
if (this.pollingTimer) {
|
||||||
|
clearTimeout(this.pollingTimer);
|
||||||
|
this.pollingTimer = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 手动刷新 */
|
||||||
|
async handleManualRefresh() {
|
||||||
|
await Promise.all([this.loadStatistics(), this.loadFileList()]);
|
||||||
|
|
||||||
|
this.$message.success("刷新成功");
|
||||||
|
|
||||||
|
if (this.statistics.uploading > 0 || this.statistics.parsing > 0) {
|
||||||
|
this.startPolling();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// === 辅助方法 ===
|
||||||
|
|
||||||
|
/** 状态筛选 */
|
||||||
|
handleStatusFilter(status) {
|
||||||
|
this.queryParams.fileStatus = status;
|
||||||
|
this.queryParams.pageNum = 1;
|
||||||
|
this.loadFileList();
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 分页变化 */
|
||||||
|
handlePageChange(pageNum) {
|
||||||
|
this.queryParams.pageNum = pageNum;
|
||||||
|
this.loadFileList();
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 查看流水 */
|
||||||
|
handleViewFlow(record) {
|
||||||
|
this.$emit("menu-change", {
|
||||||
|
key: "detail",
|
||||||
|
route: "detail",
|
||||||
|
params: { logId: record.logId },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 查看错误 */
|
||||||
|
handleViewError(record) {
|
||||||
|
this.$alert(record.errorMessage || "未知错误", "错误信息", {
|
||||||
|
confirmButtonText: "确定",
|
||||||
|
type: "error",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 状态文本映射 */
|
||||||
|
getStatusText(status) {
|
||||||
|
const map = {
|
||||||
|
uploading: "上传中",
|
||||||
|
parsing: "解析中",
|
||||||
|
parsed_success: "解析成功",
|
||||||
|
parsed_failed: "解析失败",
|
||||||
|
};
|
||||||
|
return map[status] || status;
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 状态标签类型映射 */
|
||||||
|
getStatusType(status) {
|
||||||
|
const map = {
|
||||||
|
uploading: "primary",
|
||||||
|
parsing: "warning",
|
||||||
|
parsed_success: "success",
|
||||||
|
parsed_failed: "danger",
|
||||||
|
};
|
||||||
|
return map[status] || "info";
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 格式化文件大小 */
|
||||||
|
formatFileSize(bytes) {
|
||||||
|
if (bytes === 0) return "0 B";
|
||||||
|
const k = 1024;
|
||||||
|
const sizes = ["B", "KB", "MB", "GB"];
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -876,6 +1321,97 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 统计卡片区域
|
||||||
|
.statistics-section {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
.stat-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-icon {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 24px;
|
||||||
|
|
||||||
|
&.uploading {
|
||||||
|
background: rgba(64, 158, 255, 0.1);
|
||||||
|
color: #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.parsing {
|
||||||
|
background: rgba(230, 162, 60, 0.1);
|
||||||
|
color: #e6a23c;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.success {
|
||||||
|
background: rgba(103, 194, 58, 0.1);
|
||||||
|
color: #67c23a;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.failed {
|
||||||
|
background: rgba(245, 108, 108, 0.1);
|
||||||
|
color: #f56c6c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-content {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #909399;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文件列表区域
|
||||||
|
.file-list-section {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
|
.list-toolbar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
.filter-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 上传弹窗样式
|
// 上传弹窗样式
|
||||||
::v-deep .el-dialog__wrapper {
|
::v-deep .el-dialog__wrapper {
|
||||||
.upload-area {
|
.upload-area {
|
||||||
@@ -898,6 +1434,83 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 批量上传弹窗样式
|
||||||
|
.batch-upload-area {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
::v-deep .el-upload {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.el-upload-dragger {
|
||||||
|
width: 100%;
|
||||||
|
height: 180px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-files {
|
||||||
|
margin-top: 16px;
|
||||||
|
border: 1px solid #ebeef5;
|
||||||
|
border-radius: 4px;
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
.files-header {
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: #f5f7fa;
|
||||||
|
border-bottom: 1px solid #ebeef5;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
|
.files-list {
|
||||||
|
padding: 8px;
|
||||||
|
|
||||||
|
.file-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: background 0.3s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #f5f7fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 18px;
|
||||||
|
color: #1890ff;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-name {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #303133;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-size {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #909399;
|
||||||
|
margin: 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-button {
|
||||||
|
padding: 4px;
|
||||||
|
color: #909399;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #f56c6c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 响应式
|
// 响应式
|
||||||
@media (max-width: 1200px) {
|
@media (max-width: 1200px) {
|
||||||
.upload-section .upload-cards {
|
.upload-section .upload-cards {
|
||||||
@@ -908,6 +1521,10 @@ export default {
|
|||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(3, 1fr);
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.statistics-section {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
@@ -932,5 +1549,15 @@ export default {
|
|||||||
.quality-check-section .metrics {
|
.quality-check-section .metrics {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.statistics-section {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-list-section .list-toolbar {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user