You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

411 lines
9.5 KiB

<template>
<div class="consistency-content">
<!-- 招投标文件上传 -->
<div class="section file-upload-section">
<h3 class="section-title">上传招投标文件</h3>
<p class="section-description">上传需要与合同进行一致性对比的招投标文件</p>
<div class="upload-box" :class="{'file-preview': bidFileList && bidFileList.length > 0}">
<template v-if="bidFileList && bidFileList.length > 0">
<!-- 文件预览 -->
<div class="file-info">
<FileTextOutlined class="file-icon" />
<div class="file-details">
<p class="file-name">{{ bidFileList[0].name }}</p>
<div class="file-progress" v-if="uploading">
<Progress :percent="uploadPercent" size="small" status="active" />
</div>
<p class="file-status" v-else>
<CheckCircleFilled class="status-icon success" /> 上传成功
<AButton type="link" class="remove-btn" @click="removeBidFile">删除</AButton>
</p>
</div>
</div>
</template>
<template v-else>
<!-- 上传区域 -->
<AUpload
:fileList="bidFileList"
:customRequest="customUploadRequest"
:beforeUpload="beforeUpload"
:showUploadList="false"
:maxCount="1"
:multiple="false"
name="file"
accept=".pdf,.doc,.docx"
draggable
>
<div class="upload-content">
<div class="upload-icon">
<UpOutlined class="upload-arrow-icon" />
</div>
<div class="upload-text-container">
<p class="upload-text">
<a class="upload-link">选择招投标文件</a>
</p>
<p class="upload-hint">支持PDF、DOC、DOCX格式文件</p>
</div>
</div>
</AUpload>
</template>
</div>
</div>
<!-- 特别关注点 -->
<!-- <div class="section focus-section">
<h3 class="section-title">特别关注点(可选)</h3>
<p class="section-description">指定需要特别关注的一致性检查要点</p>
<div class="focus-input">
<Input.TextArea
v-model:value="specialFocus"
placeholder="请输入特别关注的一致性要点,如特定技术指标、关键商务条款、重要服务承诺等..."
:rows="3"
:maxlength="500"
show-count
size="large"
/>
</div>
</div> -->
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { Input, Progress, Button, Upload } from 'ant-design-vue';
import {
UpOutlined,
FileTextOutlined,
CheckCircleFilled
} from '@ant-design/icons-vue';
import { message } from 'ant-design-vue';
import type { UploadProps } from 'ant-design-vue';
import { uploadApi } from '@/api/upload';
import { ossRemove } from '@/api/system/oss';
import { UploadFileParams } from '#/axios';
// 注册组件
const AButton = Button;
const AUpload = Upload;
// 状态变量
const bidFileList = ref<UploadProps['fileList']>([]);
const uploading = ref(false);
const uploadPercent = ref(0);
const currentBidOssId = ref<string | null>(null);
const specialFocus = ref<string>(''); // 特别关注点
// 上传文件返回的信息
interface UploadResponse {
ossId: string;
url: string;
fileName: string;
}
// 文件上传相关处理
function customUploadRequest(options: any) {
const { file, onSuccess, onError } = options;
// 设置上传状态
uploading.value = true;
uploadPercent.value = 0;
// 创建上传参数
const uploadParams: UploadFileParams = {
name: 'file',
file: file,
data: {},
};
// 显示初始文件(上传中状态)
bidFileList.value = [
{
uid: '1',
name: file.name,
status: 'uploading',
url: URL.createObjectURL(file),
} as any,
];
// 调用真实的上传API
uploadApi(
uploadParams,
(progressEvent) => {
// 处理上传进度
if (progressEvent.total) {
const percent = Math.round((progressEvent.loaded / progressEvent.total) * 100);
uploadPercent.value = percent;
}
}
).then((res) => {
// 上传成功处理
if (bidFileList.value && bidFileList.value.length > 0) {
bidFileList.value[0].status = 'done';
bidFileList.value[0].response = res;
// 保存OSS ID
if (res && res.ossId) {
currentBidOssId.value = res.ossId;
}
}
uploading.value = false;
uploadPercent.value = 100;
message.success(`${file.name} 文件上传成功`);
onSuccess(res);
}).catch((err) => {
// 上传失败处理
uploading.value = false;
bidFileList.value = [];
currentBidOssId.value = null;
message.error(`文件上传失败: ${err.message || '未知错误'}`);
onError(err);
});
}
function beforeUpload(file: File) {
const isPdf = file.type === 'application/pdf';
const isDoc = file.type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ||
file.type === 'application/msword';
if (!isPdf && !isDoc) {
message.error('只能上传 PDF/DOC/DOCX 格式的文件!');
return false;
}
// 文件大小限制(500MB)
const isLt500M = file.size / 1024 / 1024 < 500;
if (!isLt500M) {
message.error('文件大小不能超过 500MB!');
return false;
}
return true;
}
// 删除已上传的招投标文件
function removeBidFile() {
if (uploading.value) {
message.warning('文件正在上传中,请稍后再试');
return;
}
// 使用保存的OSS ID进行删除
if (currentBidOssId.value) {
ossRemove([currentBidOssId.value])
.then(() => {
bidFileList.value = [];
uploadPercent.value = 0;
currentBidOssId.value = null;
message.success('招投标文件已删除');
})
.catch((err) => {
message.error(`文件删除失败: ${err.message || '未知错误'}`);
});
} else {
// 如果没有ossId,直接清空本地状态
bidFileList.value = [];
uploadPercent.value = 0;
message.success('招投标文件已删除');
}
}
// 获取数据的方法,供父组件调用
const getData = () => {
// 检查是否上传了招投标文件
if (!currentBidOssId.value) {
message.warning('请上传招投标文件');
return null;
}
return {
type: 'consistency',
bidDocumentOssId: currentBidOssId.value, // 返回招投标文件的ossId
specialNote: specialFocus.value || undefined, // 特别说明
};
};
// 暴露getData方法
defineExpose({
getData
});
</script>
<style lang="less" scoped>
.consistency-content {
padding: 12px;
}
// 各部分通用样式
.section {
margin-bottom: 24px;
border-bottom: 1px dashed #eee;
padding-bottom: 20px;
&:last-child {
border-bottom: none;
}
}
.section-title {
font-size: 16px;
margin-bottom: 8px;
font-weight: 500;
color: #333;
}
.section-description {
color: #666;
margin-bottom: 16px;
font-size: 14px;
line-height: 1.4;
}
// 文件上传区域
.upload-box {
border: 1px dashed #ddd;
border-radius: 8px;
padding: 20px;
text-align: center;
cursor: pointer;
transition: border-color 0.3s;
width: 100%;
height: 200px;
display: flex;
align-items: center;
justify-content: center;
background-color: #f9f9f9;
&:hover {
border-color: #13c2c2;
background-color: rgba(19, 194, 194, 0.02);
}
&.file-preview {
border: 2px solid #e6fffb;
background-color: #f0f8ff;
cursor: default;
}
}
.upload-content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
}
.upload-icon {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 16px;
background-color: #e6fffb;
width: 50px;
height: 50px;
border-radius: 50%;
}
.upload-arrow-icon {
font-size: 24px;
color: #13c2c2;
}
.upload-text-container {
text-align: center;
}
.upload-text {
font-size: 16px;
color: #333;
margin-bottom: 8px;
}
.upload-link {
color: #13c2c2;
cursor: pointer;
text-decoration: none;
font-weight: 600;
}
.upload-link:hover {
text-decoration: underline;
}
.upload-hint {
color: #888;
font-size: 14px;
margin-top: 8px;
}
/* 文件预览区域 */
.file-info {
display: flex;
align-items: center;
width: 100%;
max-width: 800px;
}
.file-icon {
font-size: 32px;
color: #13c2c2;
margin-right: 16px;
}
.file-details {
flex: 1;
}
.file-name {
font-size: 16px;
font-weight: 500;
margin: 0 0 8px 0;
color: #333;
}
.file-progress {
margin: 0 0 8px 0;
width: 100%;
}
.file-status {
font-size: 14px;
color: #666;
margin: 0;
display: flex;
align-items: center;
}
.status-icon {
margin-right: 6px;
}
.status-icon.success {
color: #52c41a;
}
.remove-btn {
padding: 0;
margin-left: 12px;
font-size: 14px;
}
// 特别关注点
.focus-input {
margin-top: 12px;
:deep(.ant-input) {
font-size: 14px !important;
border-radius: 6px !important;
&:focus {
border-color: #13c2c2;
box-shadow: 0 0 0 2px rgba(19, 194, 194, 0.2);
}
}
:deep(.ant-input-data-count) {
color: #999;
font-size: 12px;
}
}
</style>