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.

369 lines
10 KiB

1 month ago
<template>
1 month ago
<div class="container">
<div class="header">
<h2 class="title">编辑合同产品信息</h2>
<div class="actions">
<el-button type="primary" @click="handleAddProduct">新增产品</el-button>
1 month ago
<el-button @click="handleClose">关闭</el-button>
</div>
</div>
1 month ago
<div class="content">
1 month ago
<!-- 左侧PDF显示区域 -->
1 month ago
<div class="pdf-area">
<embed :src="pdfUrl" type="application/pdf" width="100%" height="100%" />
1 month ago
</div>
<!-- 右侧编辑表单区域 -->
1 month ago
<div class="form-area">
<!-- 产品列表 -->
<div v-for="(product, index) in productList" :key="product.id || index" class="product-item">
<div class="product-header">
<h3>产品 #{{ index + 1 }}</h3>
<div class="product-actions">
<el-button type="primary" size="small" @click="handleSubmitSingle(index)">保存</el-button>
<el-button type="danger" size="small" @click="handleRemoveProduct(index)" v-if="productList.length > 1">
删除
</el-button>
</div>
</div>
<el-form
:ref="el => (formRefs[index] = el)"
:model="product"
label-width="100px"
class="edit-form"
>
<el-form-item label="品牌" prop="brand">
<el-input v-model="product.brand" placeholder="请输入品牌" />
</el-form-item>
<el-form-item label="型号(版本号)" prop="versionStr">
<el-input v-model="product.versionStr" placeholder="请输入型号" />
</el-form-item>
<el-form-item label="CPU型号" prop="cpuModel">
<el-input v-model="product.cpuModel" placeholder="请输入CPU型号" />
</el-form-item>
<el-form-item label="类型" prop="type">
<el-input v-model="product.type" placeholder="请输入类型" />
</el-form-item>
<el-form-item label="单价" prop="unitPrice">
<el-input-number
v-model="product.unitPrice"
:precision="0"
:step="1"
:min="0"
placeholder="请输入单价"
/>
</el-form-item>
<el-form-item label="数量" prop="quantity">
<el-input-number
v-model="product.quantity"
:min="0"
placeholder="请输入数量"
/>
</el-form-item>
<el-form-item label="总价" prop="totalPrice">
<el-input-number
v-model="product.totalPrice"
:precision="0"
:step="1"
:min="0"
placeholder="总价"
disabled
/>
</el-form-item>
</el-form>
<el-divider v-if="index < productList.length - 1" />
</div>
1 month ago
</div>
</div>
</div>
</template>
<script setup lang="ts">
1 month ago
import { ref, reactive, onMounted, nextTick, watch, computed } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { updateContractualProductInfo, addContractualProductInfo, delContractualProductInfo } from '@/api/contractReview/ContractualProductInfo';
1 month ago
import { getPdfFile } from '@/api/contractReview/JyjcontractualTaskBatch';
defineOptions({ name: 'ContractualProductInfoEdit' });
1 month ago
// 定义产品类型接口
interface ProductInfo {
id: string | number;
taskId: string | number;
fileName: string;
unitName: string;
brand: string;
versionStr: string;
cpuModel: string;
type: string;
unitPrice: number;
quantity: number;
totalPrice: number;
}
1 month ago
// 定义Props
const props = defineProps({
record: {
1 month ago
type: Array as () => ProductInfo[],
1 month ago
required: true,
1 month ago
default: () => []
1 month ago
}
});
// 定义Emits
const emit = defineEmits(['close', 'success']);
1 month ago
// 创建空白产品模板
const createEmptyProduct = (): ProductInfo => {
return {
id: '',
taskId: props.record.length > 0 ? props.record[0].taskId : '',
fileName: props.record.length > 0 ? props.record[0].fileName : '',
unitName: '',
brand: '',
versionStr: '',
cpuModel: '',
type: '',
unitPrice: 0,
quantity: 0,
totalPrice: 0
};
1 month ago
};
1 month ago
// 产品列表
const productList = ref<ProductInfo[]>([]);
// 表单引用数组
const formRefs = ref<any[]>([]);
1 month ago
// PDF文件URL
const pdfUrl = ref<string>('');
const loading = ref(false);
1 month ago
// 监听每个产品的单价和数量变化,计算总价
watch(productList, () => {
productList.value.forEach(product => {
product.totalPrice = product.unitPrice * product.quantity;
});
}, { deep: true });
1 month ago
// 初始化数据
onMounted(async () => {
1 month ago
if (props.record && props.record.length) {
// 填充产品列表数据
productList.value = props.record.map((item: ProductInfo) => {
const product = createEmptyProduct();
Object.keys(product).forEach(key => {
if (item[key as keyof ProductInfo] !== undefined) {
// @ts-ignore
product[key as keyof ProductInfo] = item[key as keyof ProductInfo];
}
});
return product;
1 month ago
});
1 month ago
// 如果没有产品,添加一个空白产品
if (productList.value.length === 0) {
productList.value.push(createEmptyProduct());
}
1 month ago
// 尝试加载PDF文件
try {
1 month ago
if (props.record.length > 0 && props.record[0].taskId) {
// @ts-ignore
const response = await getPdfFile(props.record[0].taskId);
1 month ago
if (response instanceof Blob) {
pdfUrl.value = URL.createObjectURL(response);
// 文件加载完成后自动适应宽度
nextTick(() => {
setTimeout(() => {
// 如果需要特殊处理PDF显示可以在这里添加
}, 500);
});
} else {
console.error('获取PDF文件失败:返回的不是Blob对象');
}
}
} catch (error) {
console.error('加载PDF文件失败:', error);
}
1 month ago
} else {
// 如果没有记录,添加一个空白产品
productList.value.push(createEmptyProduct());
1 month ago
}
});
1 month ago
// 添加产品
const handleAddProduct = () => {
productList.value.push(createEmptyProduct());
};
// 删除产品
const handleRemoveProduct = async (index: number) => {
const currentProduct = productList.value[index];
// 如果产品已有ID(已保存到数据库),则调用删除API
if (currentProduct.id) {
try {
// 添加用户确认
await ElMessageBox.confirm('确定要删除该产品记录吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
loading.value = true;
const res = await delContractualProductInfo(currentProduct.id);
if (res.code === 200) {
ElMessage.success('删除成功');
productList.value.splice(index, 1);
emit('success', null); // 通知父组件刷新数据
} else {
ElMessage.error(res.msg || '删除失败');
}
} catch (error: any) {
if (error !== 'cancel') {
console.error('删除失败', error);
ElMessage.error('删除失败');
}
} finally {
loading.value = false;
}
} else {
// 如果产品没有ID(未保存到数据库),直接从列表中移除
productList.value.splice(index, 1);
}
1 month ago
};
// 提交单个产品
const handleSubmitSingle = async (index: number) => {
1 month ago
try {
1 month ago
if (formRefs.value[index]) {
await formRefs.value[index].validate();
}
1 month ago
loading.value = true;
1 month ago
// 获取当前产品
const currentProduct = productList.value[index];
// 根据产品是否有id决定调用哪个API
let res;
if (currentProduct.id) {
// 已有产品,调用更新接口
res = await updateContractualProductInfo(currentProduct);
} else {
// 新增产品,调用新增接口
res = await addContractualProductInfo(currentProduct);
// 如果新增成功,更新产品id
if (res.code === 200 && res.data) {
currentProduct.id = res.data.id;
}
}
1 month ago
if (res.code === 200) {
1 month ago
ElMessage.success(`产品 #${index + 1} ${currentProduct.id ? '更新' : '新增'}成功`);
emit('success', currentProduct);
1 month ago
} else {
ElMessage.error(res.msg || '保存失败');
}
} catch (error) {
console.error('提交失败', error);
ElMessage.error('表单验证失败,请检查输入');
} finally {
loading.value = false;
}
};
// 关闭页面
const handleClose = () => {
emit('close');
};
</script>
<style scoped>
1 month ago
.container {
display: flex;
flex-direction: column;
height: 100%;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
1 month ago
padding: 16px 24px;
background-color: #f5f7fa;
border-bottom: 1px solid #e4e7ed;
}
1 month ago
.title {
1 month ago
margin: 0;
font-size: 18px;
color: #303133;
}
1 month ago
.actions {
display: flex;
gap: 12px;
1 month ago
}
1 month ago
.content {
display: flex;
flex: 1;
overflow: hidden;
1 month ago
}
1 month ago
.pdf-area {
width: 50%;
height: 100%;
border-right: 1px solid #e4e7ed;
overflow: hidden;
1 month ago
}
1 month ago
.pdf-area embed {
width: 100%;
height: 100%;
border: none;
1 month ago
}
1 month ago
.form-area {
width: 50%;
1 month ago
height: 100%;
overflow-y: auto;
1 month ago
padding: 16px;
1 month ago
}
1 month ago
.product-item {
margin-bottom: 20px;
background-color: #f9fafc;
border-radius: 4px;
padding: 16px;
1 month ago
}
1 month ago
.product-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.product-header h3 {
margin: 0;
font-size: 16px;
font-weight: 500;
}
.product-actions {
display: flex;
gap: 8px;
}
.edit-form {
padding: 8px 0;
1 month ago
}
</style>