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
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>
|