Files
td_official/src/views/biddingManagemen/listOfWinningBids/index copy 2.vue

525 lines
18 KiB
Vue
Raw Normal View History

2025-08-22 19:48:09 +08:00
<template>
<div class="p-4 bg-gray-50 min-h-screen">
<!-- 卡片容器控制最大宽度+居中+圆角阴影 -->
<el-card shadow="hover" class="max-w-6xl mx-auto rounded-xl overflow-hidden border-0" style="background-color: #ffffff">
<!-- 卡片头部项目信息展示区非表单布局 -->
<template #header>
<div class="bg-blue-50 px-6 rounded-t-xl" style="padding: 10px 20px">
<h3 class="el-card__header-title text-lg font-semibold text-blue-800">投标项目信息</h3>
<h4>{{ currentProject.name }}</h4>
<!-- 项目信息部分 -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<div class="project-info-item">
<span>负责人</span>
<span> {{ projectInfo.principal || '-' }}</span>
</div>
<div class="project-info-item">
<span>负责人电话</span>
<span> {{ projectInfo.principalPhone || '-' }}</span>
</div>
<div class="project-info-item">
<span>项目类型</span>
<span> {{ getDictLabel(project_type, projectInfo.projectType) || '-' }}</span>
</div>
<div class="project-info-item">
<span>项目阶段</span>
<span> {{ getDictLabel(project_stage, projectInfo.projectStage) || '-' }}</span>
</div>
<div class="project-info-item">
<span>开工时间</span>
<span> {{ projectInfo.onStreamTime || '-' }}</span>
</div>
<div class="project-info-item">
<span>经纬度</span>
<span> {{ projectInfo.lng || '-' }}{{ projectInfo.lat || '-' }}</span>
</div>
<div class="project-info-item md:col-span-2 lg:col-span-3">
<span>项目地址</span>
<span> {{ projectInfo.projectSite || '-' }}</span>
</div>
<div class="project-info-item">
<span>计划容量(M)</span>
<span> {{ projectInfo.plan || '-' }}</span>
</div>
<div class="project-info-item">
<span>实际容量(M)</span>
<span> {{ projectInfo.actual || '-' }}</span>
</div>
<div class="project-info-item">
<span>设计总量(M)</span>
<span> {{ projectInfo.designTotal || '-' }}</span>
</div>
<div class="project-info-item md:col-span-2 lg:col-span-3">
<span>备注</span>
<span> {{ projectInfo.remark || '-' }}</span>
</div>
</div>
</div>
<div class="mt-4 mb-6">
<el-button @click="isDisabled = false" type="primary" class="px-8 py-2.5 transition-all duration-300 font-medium" v-if="isDisabled">
点击编辑
</el-button>
</div>
</template>
<!-- 中标信息表单区域保持原有逻辑 -->
<el-form
:disabled="isDisabled"
ref="listOfWinningBidsFormRef"
:model="form"
:rules="rules"
label-width="150px"
class="p-6 pt-4"
style="background-color: #ffffff"
>
<el-row :gutter="32">
<el-col :span="12">
<el-form-item label="中标价(美元)" prop="winningBidOriginal" class="rounded-lg border border-gray-100 p-1 mb-5">
<el-input v-model.number="form.winningBidOriginal" type="number" placeholder="请输入中标价" @input="calculateWinningBid" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="汇率" prop="exchangeRate" class="rounded-lg border border-gray-100 p-1 mb-5">
<el-input v-model.number="form.exchangeRate" type="number" placeholder="请输入汇率" step="0.0001" @input="calculateWinningBid" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="币种" prop="currency" class="rounded-lg border border-gray-100 p-1 mb-5">
<el-input v-model="form.currency" placeholder="请输入币种" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="中标价(人民币)" prop="winningBid" class="rounded-lg border border-gray-100 p-1 mb-5">
<el-input v-model="form.winningBid" type="number" placeholder="根据美元中标价和汇率自动计算" readonly />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="中标日期" prop="bidWinningDate" class="rounded-lg border border-gray-100 p-1 mb-5">
<el-date-picker
clearable
v-model="form.bidWinningDate"
type="date"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
placeholder="请选择中标日期"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="投标保证金(人民币)" prop="bidDeposit" class="rounded-lg border border-gray-100 p-1 mb-5">
<el-input v-model="form.bidDeposit" type="number" placeholder="请输入投标保证金" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="是否退还" prop="whetherSendBack" class="rounded-lg border border-gray-100 p-1 mb-5">
<el-radio-group v-model="form.whetherSendBack">
<el-radio label="是" border></el-radio>
<el-radio label="否" border></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所属主体" prop="subject" class="rounded-lg border border-gray-100 p-1 mb-5">
<el-input v-model="form.subject" placeholder="请输入所属主体" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="建设单位" prop="construction" class="rounded-lg border border-gray-100 p-1 mb-5">
<el-input v-model="form.construction" placeholder="请输入建设单位" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="总造价" prop="totalCost" class="rounded-lg border border-gray-100 p-1 mb-5">
<el-input v-model="form.totalCost" placeholder="请输入总造价" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="立项申请人" prop="projectApplicant" class="rounded-lg border border-gray-100 p-1 mb-5">
<el-input v-model="form.projectApplicant" placeholder="请输入立项申请人" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="立项部门" prop="projectApplicantDept" class="rounded-lg border border-gray-100 p-1 mb-5">
<el-input v-model="form.projectApplicantDept" placeholder="请输入立项部门" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="立项申请日期" prop="projectApplicantTime" class="rounded-lg border border-gray-100 p-1 mb-5">
<el-date-picker
clearable
v-model="form.projectApplicantTime"
type="date"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
placeholder="请选择立项申请日期"
/>
</el-form-item>
</el-col>
<!-- <el-col :span="12">
<el-form-item label="项目编号" prop="projectNumbering" class="rounded-lg border border-gray-100 p-1 mb-5">
<el-input v-model="form.projectNumbering" placeholder="请输入项目编号" />
</el-form-item>
</el-col> -->
<el-col :span="12">
<el-form-item label="中标通知书" prop="projectNumbering" class="rounded-lg border border-gray-100 p-1 mb-5">
<file-upload v-model="form.bidFile" :limit="10" :file-type="['pdf']" :file-size="50" />
</el-form-item>
</el-col>
</el-row>
<!-- 操作按钮区域 -->
<el-row v-if="!isDisabled" class="mt-8">
<el-col :span="24" class="text-center">
<el-button
:loading="buttonLoading"
type="primary"
@click="submitForm"
v-hasPermi="['bidding:listOfWinningBids:add', 'bidding:listOfWinningBids:edit']"
class="rounded-full px-8"
size="large"
>
确认提交
</el-button>
<el-button type="default" @click="resetForm" class="ml-6 rounded-full px-8" size="large"> 重置 </el-button>
</el-col>
</el-row>
</el-form>
</el-card>
</div>
</template>
<script setup name="ListOfWinningBidsForm" lang="ts">
import { ref, reactive, toRefs, watch, onMounted, onUnmounted, getCurrentInstance, ComponentInternalInstance, computed } from 'vue';
import { addListOfWinningBids, updateListOfWinningBids, listListOfWinningBids, getListOfWinningBids } from '@/api/bidding/listOfWinningBids';
import { ListOfWinningBidsVO, ListOfWinningBidsForm } from '@/api/bidding/listOfWinningBids/types';
import { useUserStoreHook } from '@/store/modules/user';
import { ElFormInstance, ElMessage } from 'element-plus';
import { getProject, updateProject } from '@/api/project/project'; // 补充项目信息更新接口
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { project_type, project_stage } = toRefs<any>(proxy?.useDict('project_type', 'project_stage'));
// 用户状态管理
const userStore = useUserStoreHook();
// 当前选中项目从store获取
const currentProject = computed(() => userStore.selectedProject);
// 项目信息(非表单绑定,直接响应式数据)
const projectInfo = reactive({
projectName: undefined,
shortName: undefined,
pId: undefined,
status: undefined,
picUrl: undefined,
remark: undefined,
projectType: undefined,
projectCategory: undefined,
deletedAt: undefined,
projectSite: undefined,
principal: undefined,
principalPhone: undefined,
actual: undefined,
lng: undefined,
lat: undefined,
plan: undefined,
onStreamTime: undefined,
playCardStart: undefined,
playCardEnd: undefined,
designTotal: undefined,
securityAgreement: undefined,
sort: 0,
showHidden: undefined,
isDelete: undefined
});
// 表单相关引用
const listOfWinningBidsFormRef = ref<ElFormInstance>();
// 加载状态
const buttonLoading = ref(false);
// 编辑/查看状态控制
const isDisabled = ref(false);
// 表单初始数据
const initFormData: ListOfWinningBidsForm = {
id: undefined,
projectId: currentProject.value?.id,
projectStatus: undefined,
projectName: undefined,
winningBidOriginal: undefined,
exchangeRate: undefined,
currency: undefined,
subject: undefined,
winningBid: undefined,
bidWinningDate: undefined,
bidDeposit: undefined,
whetherSendBack: undefined,
construction: undefined,
totalCost: undefined,
projectApplicant: undefined,
projectApplicantDept: undefined,
projectApplicantTime: undefined,
processStatus: undefined,
projectNumbering: undefined
};
// 表单数据与验证规则
const data = reactive({
form: { ...initFormData } as ListOfWinningBidsForm,
rules: {
projectId: [{ required: true, message: '项目ID不能为空', trigger: 'blur' }],
projectName: [{ required: true, message: '请输入项目名称', trigger: 'blur' }],
winningBidOriginal: [{ required: true, message: '请输入原始中标价', trigger: 'blur' }],
exchangeRate: [
{ required: true, message: '请输入汇率', trigger: 'blur' },
{ type: 'number', min: 0.001, message: '汇率需大于0', trigger: 'blur' }
],
currency: [{ required: true, message: '请输入币种', trigger: 'blur' }],
subject: [{ required: true, message: '请输入所属主体', trigger: 'blur' }],
winningBid: [{ required: true, message: '请输入中标价', trigger: 'blur' }],
bidWinningDate: [{ required: true, message: '请选择中标日期', trigger: 'blur' }],
projectNumbering: [{ required: true, message: '请输入项目编号', trigger: 'blur' }]
} as Record<string, any>
});
// 解构响应式数据
const { form, rules } = toRefs(data);
/**
* 根据字典值获取字典标签用于项目类型/阶段的文本展示
*/
const getDictLabel = (dictList: any[], value: any) => {
if (!dictList || !value) return '';
const dictItem = dictList.find((item) => item.value === value);
return dictItem ? dictItem.label : '';
};
/**
* 计算人民币中标价
*/
const calculateWinningBid = () => {
const dollarAmount = Number(form.value.winningBidOriginal);
const rate = Number(form.value.exchangeRate);
if (isNaN(dollarAmount) || isNaN(rate) || dollarAmount <= 0 || rate <= 0) {
form.value.winningBid = undefined;
return;
}
const result = dollarAmount * rate;
form.value.winningBid = Number(result.toFixed(2));
};
/**
* 页面初始化 - 获取已有数据如存在
*/
const initData = async () => {
try {
if (currentProject.value?.id) {
const res = await listListOfWinningBids({ projectId: currentProject.value.id });
if (res.code === 200) {
resetForm();
if (!res.data) {
isDisabled.value = false;
return;
}
Object.assign(form.value, res.data);
isDisabled.value = true;
}
}
} catch (error) {
console.error('初始化中标数据失败:', error);
}
};
/**
* 提交表单含项目信息+中标信息同步提交
*/
const submitForm = () => {
listOfWinningBidsFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
try {
// 1. 计算人民币中标价
calculateWinningBid();
// 2. 绑定项目ID和项目名称从项目信息同步
form.value.projectId = currentProject.value?.id;
form.value.projectName = projectInfo.projectName;
// 3. 先更新项目信息(若项目信息有修改)
if (currentProject.value?.id) {
await updateProject({ id: currentProject.value.id, ...projectInfo });
}
// 4. 再提交中标信息(新增/编辑逻辑)
const isEdit = !!form.value.id;
if (isEdit) {
await updateListOfWinningBids(form.value);
} else {
await addListOfWinningBids(form.value);
}
// 5. 提交成功后切换为查看状态
isDisabled.value = true;
ElMessage.success(isEdit ? '编辑成功' : '提交成功');
} catch (error) {
ElMessage.error('提交失败,请重试');
console.error('提交表单失败:', error);
} finally {
buttonLoading.value = false;
}
}
});
};
/**
* 获取项目详细信息
*/
const getProjectDetail = async () => {
try {
if (currentProject.value?.id) {
const res = await getProject(currentProject.value.id);
Object.assign(projectInfo, res.data);
}
} catch (error) {
console.error('获取项目详情失败:', error);
ElMessage.error('获取项目信息失败');
}
};
/**
* 重置表单含项目信息重置
*/
const resetForm = () => {
// 重置中标表单
form.value = { ...initFormData, projectId: currentProject.value?.id };
listOfWinningBidsFormRef.value?.resetFields();
// 重置项目信息(恢复为当前项目的原始数据)
getProjectDetail();
};
/**
* 监听项目ID变化 - 重新初始化数据
*/
const projectIdWatcher = watch(
() => currentProject.value?.id,
(newId) => {
if (newId) {
form.value.projectId = newId;
getProjectDetail();
initData();
}
}
);
// 页面挂载时初始化
onMounted(() => {
getProjectDetail();
initData();
});
// 页面卸载时清除监听
onUnmounted(() => {
projectIdWatcher();
});
</script>
<style scoped>
/* 全局背景色 */
.bg-gray-50 {
background-color: #f9fafb;
}
/* 项目信息项布局样式 */
.project-info-item {
transition: all 0.2s ease;
padding: 4px 0;
color: #696969;
}
/* 输入框/选择器统一样式 */
.el-input__wrapper,
.el-date-editor .el-input__wrapper,
.el-select__wrapper {
border-radius: 6px !important;
transition: all 0.2s ease;
}
/* 输入框hover效果 */
.el-input__wrapper:hover,
.el-date-editor .el-input__wrapper:hover,
.el-select__wrapper:hover {
border-color: #409eff;
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.1);
}
/* 卡片头部文字样式 */
.el-card__header-title {
display: flex;
align-items: center;
height: 100%;
}
/* 按钮样式优化 */
.el-button {
transition: all 0.2s ease;
}
.el-button:hover {
transform: translateY(-1px);
}
/* 表单项样式优化 */
.el-form-item {
transition: all 0.2s ease;
}
.el-form-item:hover {
border-color: #e6f7ff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
/* 响应式网格布局适配 */
@media (max-width: 767px) {
.grid {
display: grid;
}
.grid-cols-1 {
grid-template-columns: repeat(1, minmax(0, 1fr));
}
}
@media (min-width: 768px) and (max-width: 1023px) {
.md\:grid-cols-2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.md\:col-span-2 {
grid-column: span 2 / span 2;
}
}
@media (min-width: 1024px) {
.lg\:grid-cols-3 {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.lg\:col-span-3 {
grid-column: span 3 / span 3;
}
}
/* 间距样式 */
.gap-6 {
gap: 1.5rem;
}
.mb-1 {
margin-bottom: 0.25rem;
}
.mb-6 {
margin-bottom: 1.5rem;
}
.mt-4 {
margin-top: 1rem;
}
.mt-6 {
margin-top: 1.5rem;
}
.mt-8 {
margin-top: 2rem;
}
</style>