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.
642 lines
17 KiB
642 lines
17 KiB
1 month ago
|
<template>
|
||
|
<div class="consistency-content">
|
||
|
<!-- 对比文件类型选择 -->
|
||
|
<div class="section comparison-section">
|
||
|
<h3 class="section-title">选择对比文件类型</h3>
|
||
|
<p class="section-description">选择需要与合同进行一致性对比的文件类型</p>
|
||
|
|
||
|
<div class="comparison-options">
|
||
|
<div
|
||
|
class="comparison-card"
|
||
|
:class="{ selected: selectedComparisons.includes('tender') }"
|
||
|
@click="toggleComparison('tender')"
|
||
|
>
|
||
|
<div class="card-header">
|
||
|
<CheckCircleOutlined v-if="selectedComparisons.includes('tender')" class="check-icon" />
|
||
|
招标文件
|
||
|
</div>
|
||
|
<div class="card-body">
|
||
|
<h3>与招标文件对比</h3>
|
||
|
<p class="card-desc">检查合同条款是否与招标文件要求一致</p>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<div
|
||
|
class="comparison-card"
|
||
|
:class="{ selected: selectedComparisons.includes('bid') }"
|
||
|
@click="toggleComparison('bid')"
|
||
|
>
|
||
|
<div class="card-header">
|
||
|
<CheckCircleOutlined v-if="selectedComparisons.includes('bid')" class="check-icon" />
|
||
|
投标文件
|
||
|
</div>
|
||
|
<div class="card-body">
|
||
|
<h3>与投标文件对比</h3>
|
||
|
<p class="card-desc">检查合同是否履行投标承诺和要求</p>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<div
|
||
|
class="comparison-card"
|
||
|
:class="{ selected: selectedComparisons.includes('negotiation') }"
|
||
|
@click="toggleComparison('negotiation')"
|
||
|
>
|
||
|
<div class="card-header">
|
||
|
<CheckCircleOutlined v-if="selectedComparisons.includes('negotiation')" class="check-icon" />
|
||
|
谈判记录
|
||
|
</div>
|
||
|
<div class="card-body">
|
||
|
<h3>与谈判记录对比</h3>
|
||
|
<p class="card-desc">检查合同是否体现谈判确定的内容</p>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<!-- 检查维度选择 -->
|
||
|
<div class="section dimension-section">
|
||
|
<h3 class="section-title">选择检查维度</h3>
|
||
|
<p class="section-description">选择需要进行一致性检查的关键维度</p>
|
||
|
|
||
|
<div class="dimension-options">
|
||
|
<div
|
||
|
class="dimension-card"
|
||
|
:class="{ selected: selectedDimensions.includes('technical') }"
|
||
|
@click="toggleDimension('technical')"
|
||
|
>
|
||
|
<div class="card-header">
|
||
|
<CheckCircleOutlined v-if="selectedDimensions.includes('technical')" class="check-icon" />
|
||
|
技术规格
|
||
|
</div>
|
||
|
<div class="card-body">
|
||
|
<h3>技术规格一致性</h3>
|
||
|
<p class="card-desc">检查技术参数、性能指标、质量标准等</p>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<div
|
||
|
class="dimension-card"
|
||
|
:class="{ selected: selectedDimensions.includes('commercial') }"
|
||
|
@click="toggleDimension('commercial')"
|
||
|
>
|
||
|
<div class="card-header">
|
||
|
<CheckCircleOutlined v-if="selectedDimensions.includes('commercial')" class="check-icon" />
|
||
|
商务条款
|
||
|
</div>
|
||
|
<div class="card-body">
|
||
|
<h3>商务条款一致性</h3>
|
||
|
<p class="card-desc">检查价格、付款方式、结算条件等</p>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<div
|
||
|
class="dimension-card"
|
||
|
:class="{ selected: selectedDimensions.includes('service') }"
|
||
|
@click="toggleDimension('service')"
|
||
|
>
|
||
|
<div class="card-header">
|
||
|
<CheckCircleOutlined v-if="selectedDimensions.includes('service')" class="check-icon" />
|
||
|
服务要求
|
||
|
</div>
|
||
|
<div class="card-body">
|
||
|
<h3>服务要求一致性</h3>
|
||
|
<p class="card-desc">检查服务内容、服务标准、售后保障等</p>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<div
|
||
|
class="dimension-card"
|
||
|
:class="{ selected: selectedDimensions.includes('delivery') }"
|
||
|
@click="toggleDimension('delivery')"
|
||
|
>
|
||
|
<div class="card-header">
|
||
|
<CheckCircleOutlined v-if="selectedDimensions.includes('delivery')" class="check-icon" />
|
||
|
交付条件
|
||
|
</div>
|
||
|
<div class="card-body">
|
||
|
<h3>交付条件一致性</h3>
|
||
|
<p class="card-desc">检查交付时间、交付地点、验收标准等</p>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<!-- 偏离检查级别 -->
|
||
|
<div class="section deviation-section">
|
||
|
<h3 class="section-title">偏离检查级别</h3>
|
||
|
<p class="section-description">设置对偏离情况的检查严格程度</p>
|
||
|
|
||
|
<div class="deviation-options">
|
||
|
<div
|
||
|
class="deviation-card"
|
||
|
:class="{ selected: selectedDeviationLevel === 'strict' }"
|
||
|
@click="selectDeviationLevel('strict')"
|
||
|
>
|
||
|
<div class="card-icon">
|
||
|
<ExclamationCircleOutlined />
|
||
|
</div>
|
||
|
<div class="card-title">严格检查</div>
|
||
|
<div class="card-desc">任何偏离都需要标记和说明</div>
|
||
|
</div>
|
||
|
|
||
|
<div
|
||
|
class="deviation-card"
|
||
|
:class="{ selected: selectedDeviationLevel === 'standard' }"
|
||
|
@click="selectDeviationLevel('standard')"
|
||
|
>
|
||
|
<div class="card-icon">
|
||
|
<WarningOutlined />
|
||
|
</div>
|
||
|
<div class="card-title">标准检查</div>
|
||
|
<div class="card-desc">检查重要偏离和实质性变更</div>
|
||
|
</div>
|
||
|
|
||
|
<div
|
||
|
class="deviation-card"
|
||
|
:class="{ selected: selectedDeviationLevel === 'flexible' }"
|
||
|
@click="selectDeviationLevel('flexible')"
|
||
|
>
|
||
|
<div class="card-icon">
|
||
|
<InfoCircleOutlined />
|
||
|
</div>
|
||
|
<div class="card-title">灵活检查</div>
|
||
|
<div class="card-desc">主要关注关键要素的偏离</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<!-- 关键要素优先级 -->
|
||
|
<div class="section priority-section">
|
||
|
<h3 class="section-title">关键要素优先级</h3>
|
||
|
<p class="section-description">设置不同要素的检查优先级</p>
|
||
|
|
||
|
<div class="priority-settings">
|
||
|
<div class="priority-item">
|
||
|
<div class="priority-label">价格金额</div>
|
||
|
<div class="priority-selector">
|
||
|
<div class="priority-buttons">
|
||
|
<div
|
||
|
class="priority-btn"
|
||
|
:class="{ active: priorities.price === 'high' }"
|
||
|
@click="setPriority('price', 'high')"
|
||
|
>
|
||
|
高
|
||
|
</div>
|
||
|
<div
|
||
|
class="priority-btn"
|
||
|
:class="{ active: priorities.price === 'medium' }"
|
||
|
@click="setPriority('price', 'medium')"
|
||
|
>
|
||
|
中
|
||
|
</div>
|
||
|
<div
|
||
|
class="priority-btn"
|
||
|
:class="{ active: priorities.price === 'low' }"
|
||
|
@click="setPriority('price', 'low')"
|
||
|
>
|
||
|
低
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<div class="priority-item">
|
||
|
<div class="priority-label">技术参数</div>
|
||
|
<div class="priority-selector">
|
||
|
<div class="priority-buttons">
|
||
|
<div
|
||
|
class="priority-btn"
|
||
|
:class="{ active: priorities.technical === 'high' }"
|
||
|
@click="setPriority('technical', 'high')"
|
||
|
>
|
||
|
高
|
||
|
</div>
|
||
|
<div
|
||
|
class="priority-btn"
|
||
|
:class="{ active: priorities.technical === 'medium' }"
|
||
|
@click="setPriority('technical', 'medium')"
|
||
|
>
|
||
|
中
|
||
|
</div>
|
||
|
<div
|
||
|
class="priority-btn"
|
||
|
:class="{ active: priorities.technical === 'low' }"
|
||
|
@click="setPriority('technical', 'low')"
|
||
|
>
|
||
|
低
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<div class="priority-item">
|
||
|
<div class="priority-label">交付时间</div>
|
||
|
<div class="priority-selector">
|
||
|
<div class="priority-buttons">
|
||
|
<div
|
||
|
class="priority-btn"
|
||
|
:class="{ active: priorities.delivery === 'high' }"
|
||
|
@click="setPriority('delivery', 'high')"
|
||
|
>
|
||
|
高
|
||
|
</div>
|
||
|
<div
|
||
|
class="priority-btn"
|
||
|
:class="{ active: priorities.delivery === 'medium' }"
|
||
|
@click="setPriority('delivery', 'medium')"
|
||
|
>
|
||
|
中
|
||
|
</div>
|
||
|
<div
|
||
|
class="priority-btn"
|
||
|
:class="{ active: priorities.delivery === 'low' }"
|
||
|
@click="setPriority('delivery', 'low')"
|
||
|
>
|
||
|
低
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<div class="priority-item">
|
||
|
<div class="priority-label">服务承诺</div>
|
||
|
<div class="priority-selector">
|
||
|
<div class="priority-buttons">
|
||
|
<div
|
||
|
class="priority-btn"
|
||
|
:class="{ active: priorities.service === 'high' }"
|
||
|
@click="setPriority('service', 'high')"
|
||
|
>
|
||
|
高
|
||
|
</div>
|
||
|
<div
|
||
|
class="priority-btn"
|
||
|
:class="{ active: priorities.service === 'medium' }"
|
||
|
@click="setPriority('service', 'medium')"
|
||
|
>
|
||
|
中
|
||
|
</div>
|
||
|
<div
|
||
|
class="priority-btn"
|
||
|
:class="{ active: priorities.service === 'low' }"
|
||
|
@click="setPriority('service', 'low')"
|
||
|
>
|
||
|
低
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</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, reactive } from 'vue';
|
||
|
import { Input } from 'ant-design-vue';
|
||
|
import {
|
||
|
CheckCircleOutlined,
|
||
|
ExclamationCircleOutlined,
|
||
|
WarningOutlined,
|
||
|
InfoCircleOutlined
|
||
|
} from '@ant-design/icons-vue';
|
||
|
import { message } from 'ant-design-vue';
|
||
|
|
||
|
// 状态变量
|
||
|
const selectedComparisons = ref<string[]>(['tender', 'bid']); // 默认选择招标文件和投标文件
|
||
|
const selectedDimensions = ref<string[]>(['technical', 'commercial', 'delivery']); // 默认选择技术、商务、交付
|
||
|
const selectedDeviationLevel = ref<string>('standard'); // 默认选择标准检查
|
||
|
const specialFocus = ref<string>(''); // 特别关注点
|
||
|
|
||
|
// 优先级设置
|
||
|
const priorities = reactive({
|
||
|
price: 'high',
|
||
|
technical: 'high',
|
||
|
delivery: 'medium',
|
||
|
service: 'medium'
|
||
|
});
|
||
|
|
||
|
// 切换对比文件类型
|
||
|
const toggleComparison = (comparison: string) => {
|
||
|
const index = selectedComparisons.value.indexOf(comparison);
|
||
|
if (index > -1) {
|
||
|
selectedComparisons.value.splice(index, 1);
|
||
|
} else {
|
||
|
selectedComparisons.value.push(comparison);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// 切换检查维度
|
||
|
const toggleDimension = (dimension: string) => {
|
||
|
const index = selectedDimensions.value.indexOf(dimension);
|
||
|
if (index > -1) {
|
||
|
selectedDimensions.value.splice(index, 1);
|
||
|
} else {
|
||
|
selectedDimensions.value.push(dimension);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// 选择偏离检查级别
|
||
|
const selectDeviationLevel = (level: string) => {
|
||
|
selectedDeviationLevel.value = level;
|
||
|
};
|
||
|
|
||
|
// 设置优先级
|
||
|
const setPriority = (key: string, level: string) => {
|
||
|
priorities[key] = level;
|
||
|
};
|
||
|
|
||
|
// 获取数据的方法,供父组件调用
|
||
|
const getData = () => {
|
||
|
// 检查是否选择了对比文件类型
|
||
|
if (selectedComparisons.value.length === 0) {
|
||
|
message.warning('请至少选择一种对比文件类型');
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
// 检查是否选择了检查维度
|
||
|
if (selectedDimensions.value.length === 0) {
|
||
|
message.warning('请至少选择一个检查维度');
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
return {
|
||
|
type: 'consistency',
|
||
|
comparisons: selectedComparisons.value,
|
||
|
dimensions: selectedDimensions.value,
|
||
|
deviationLevel: selectedDeviationLevel.value,
|
||
|
priorities: { ...priorities },
|
||
|
specialFocus: specialFocus.value || undefined,
|
||
|
};
|
||
|
};
|
||
|
|
||
|
// 暴露getData方法
|
||
|
defineExpose({
|
||
|
getData
|
||
|
});
|
||
|
</script>
|
||
|
|
||
|
<style lang="less" scoped>
|
||
|
.consistency-content {
|
||
|
padding: 16px;
|
||
|
}
|
||
|
|
||
|
// 各部分通用样式
|
||
|
.section {
|
||
|
margin-bottom: 24px;
|
||
|
border-bottom: 1px dashed #eee;
|
||
|
padding-bottom: 16px;
|
||
|
|
||
|
&:last-child {
|
||
|
border-bottom: none;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
.section-title {
|
||
|
font-size: 16px;
|
||
|
margin-bottom: 6px;
|
||
|
font-weight: 500;
|
||
|
color: #333;
|
||
|
}
|
||
|
|
||
|
.section-description {
|
||
|
color: #666;
|
||
|
margin-bottom: 16px;
|
||
|
font-size: 14px;
|
||
|
}
|
||
|
|
||
|
// 对比文件类型选择
|
||
|
.comparison-options {
|
||
|
display: flex;
|
||
|
gap: 12px;
|
||
|
flex-wrap: wrap;
|
||
|
}
|
||
|
|
||
|
.comparison-card {
|
||
|
flex: 1;
|
||
|
min-width: 280px;
|
||
|
border: 2px solid #e8e8e8;
|
||
|
border-radius: 6px;
|
||
|
overflow: hidden;
|
||
|
transition: all 0.3s;
|
||
|
cursor: pointer;
|
||
|
|
||
|
&:hover {
|
||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||
|
border-color: #13c2c2;
|
||
|
transform: translateY(-2px);
|
||
|
}
|
||
|
|
||
|
&.selected {
|
||
|
border-color: #13c2c2;
|
||
|
box-shadow: 0 0 0 2px rgba(19, 194, 194, 0.2);
|
||
|
background-color: #e6fffb;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 检查维度选择
|
||
|
.dimension-options {
|
||
|
display: grid;
|
||
|
grid-template-columns: repeat(2, 1fr);
|
||
|
gap: 12px;
|
||
|
}
|
||
|
|
||
|
.dimension-card {
|
||
|
border: 2px solid #e8e8e8;
|
||
|
border-radius: 6px;
|
||
|
overflow: hidden;
|
||
|
transition: all 0.3s;
|
||
|
cursor: pointer;
|
||
|
|
||
|
&:hover {
|
||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||
|
border-color: #13c2c2;
|
||
|
transform: translateY(-2px);
|
||
|
}
|
||
|
|
||
|
&.selected {
|
||
|
border-color: #13c2c2;
|
||
|
box-shadow: 0 0 0 2px rgba(19, 194, 194, 0.2);
|
||
|
background-color: #e6fffb;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
.card-header {
|
||
|
background-color: #f7f7f7;
|
||
|
padding: 8px;
|
||
|
font-weight: 500;
|
||
|
font-size: 14px;
|
||
|
text-align: center;
|
||
|
color: #333;
|
||
|
display: flex;
|
||
|
align-items: center;
|
||
|
justify-content: center;
|
||
|
gap: 6px;
|
||
|
|
||
|
.comparison-card.selected &,
|
||
|
.dimension-card.selected & {
|
||
|
background-color: #13c2c2;
|
||
|
color: white;
|
||
|
}
|
||
|
|
||
|
.check-icon {
|
||
|
font-size: 16px;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
.card-body {
|
||
|
padding: 12px;
|
||
|
text-align: center;
|
||
|
|
||
|
h3 {
|
||
|
margin: 0 0 6px 0;
|
||
|
font-size: 14px;
|
||
|
font-weight: 500;
|
||
|
color: #333;
|
||
|
}
|
||
|
|
||
|
.card-desc {
|
||
|
margin: 0;
|
||
|
font-size: 12px;
|
||
|
color: #666;
|
||
|
line-height: 1.4;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 偏离检查级别
|
||
|
.deviation-options {
|
||
|
display: flex;
|
||
|
gap: 12px;
|
||
|
justify-content: center;
|
||
|
}
|
||
|
|
||
|
.deviation-card {
|
||
|
flex: 1;
|
||
|
max-width: 250px;
|
||
|
border: 2px solid #e8e8e8;
|
||
|
border-radius: 6px;
|
||
|
padding: 16px;
|
||
|
text-align: center;
|
||
|
cursor: pointer;
|
||
|
transition: all 0.3s ease;
|
||
|
|
||
|
&:hover {
|
||
|
border-color: #13c2c2;
|
||
|
box-shadow: 0 2px 8px rgba(19, 194, 194, 0.15);
|
||
|
}
|
||
|
|
||
|
&.selected {
|
||
|
border-color: #13c2c2;
|
||
|
background-color: #e6fffb;
|
||
|
box-shadow: 0 0 0 2px rgba(19, 194, 194, 0.2);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
.card-icon {
|
||
|
font-size: 24px;
|
||
|
color: #13c2c2;
|
||
|
margin-bottom: 8px;
|
||
|
}
|
||
|
|
||
|
.card-title {
|
||
|
font-size: 14px;
|
||
|
font-weight: 500;
|
||
|
margin-bottom: 6px;
|
||
|
color: #333;
|
||
|
}
|
||
|
|
||
|
.card-desc {
|
||
|
font-size: 12px;
|
||
|
color: #666;
|
||
|
line-height: 1.4;
|
||
|
}
|
||
|
|
||
|
// 优先级设置
|
||
|
.priority-settings {
|
||
|
background-color: #f9f9f9;
|
||
|
border-radius: 6px;
|
||
|
padding: 16px;
|
||
|
}
|
||
|
|
||
|
.priority-item {
|
||
|
display: flex;
|
||
|
align-items: center;
|
||
|
justify-content: space-between;
|
||
|
margin-bottom: 12px;
|
||
|
|
||
|
&:last-child {
|
||
|
margin-bottom: 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
.priority-label {
|
||
|
font-size: 14px;
|
||
|
font-weight: 500;
|
||
|
color: #333;
|
||
|
min-width: 80px;
|
||
|
}
|
||
|
|
||
|
.priority-buttons {
|
||
|
display: flex;
|
||
|
gap: 6px;
|
||
|
}
|
||
|
|
||
|
.priority-btn {
|
||
|
padding: 4px 12px;
|
||
|
border: 2px solid #e8e8e8;
|
||
|
border-radius: 16px;
|
||
|
cursor: pointer;
|
||
|
transition: all 0.3s;
|
||
|
font-size: 12px;
|
||
|
font-weight: 500;
|
||
|
color: #666;
|
||
|
|
||
|
&:hover {
|
||
|
border-color: #13c2c2;
|
||
|
color: #13c2c2;
|
||
|
}
|
||
|
|
||
|
&.active {
|
||
|
border-color: #13c2c2;
|
||
|
background-color: #13c2c2;
|
||
|
color: white;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 特别关注点
|
||
|
.focus-input {
|
||
|
margin-top: 16px;
|
||
|
|
||
|
: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>
|