241 lines
8.6 KiB
Vue
241 lines
8.6 KiB
Vue
<template>
|
|
<el-drawer
|
|
:model-value="visible"
|
|
title="合同详情"
|
|
size="680px"
|
|
:close-on-click-modal="false"
|
|
@update:model-value="emit('update:visible', $event)"
|
|
>
|
|
<div v-loading="loading" class="contract-detail">
|
|
<template v-if="detail.id">
|
|
<div class="detail-head">
|
|
<div class="detail-title">
|
|
<span class="name">{{ detail.contract_name }}</span>
|
|
<el-tag :type="contractStatusTag(detail.status)" size="small">
|
|
{{ contractStatusText(detail.status) }}
|
|
</el-tag>
|
|
<el-tag type="primary" size="small" effect="plain">
|
|
我方·{{ ourRoleText(detail.our_role) }}
|
|
</el-tag>
|
|
</div>
|
|
<div class="detail-sub">
|
|
{{ detail.contract_no }} · {{ contractCategoryText(detail.contract_category) }}
|
|
</div>
|
|
</div>
|
|
|
|
<el-descriptions :column="2" border style="margin-top: 16px">
|
|
<el-descriptions-item label="所属项目" :span="2">
|
|
<template v-if="detail.project_id">
|
|
<el-tag size="small" type="primary" effect="light">项目合同</el-tag>
|
|
<span style="margin-left: 8px">{{ detail.project_name }}</span>
|
|
</template>
|
|
<template v-else>
|
|
<el-tag size="small" type="warning" effect="light">无头合同</el-tag>
|
|
</template>
|
|
</el-descriptions-item>
|
|
<el-descriptions-item label="签订日期">{{ formatDateOnly(detail.sign_date) }}</el-descriptions-item>
|
|
<el-descriptions-item label="项目负责人">{{ detail.owner_name || "-" }}</el-descriptions-item>
|
|
<el-descriptions-item label="生效日期">{{ formatDateOnly(detail.effective_date) }}</el-descriptions-item>
|
|
<el-descriptions-item label="结束日期">{{ formatDateOnly(detail.expire_date) }}</el-descriptions-item>
|
|
<el-descriptions-item label="备注" :span="2">{{ detail.remark || "-" }}</el-descriptions-item>
|
|
</el-descriptions>
|
|
|
|
<el-divider content-position="left">各方签约主体</el-divider>
|
|
<el-table :data="detail.parties || []" border size="small">
|
|
<el-table-column label="角色" width="80" align="center">
|
|
<template #default="{ row }">{{ partyLabel(row.role) }}</template>
|
|
</el-table-column>
|
|
<el-table-column label="签约主体" min-width="160" show-overflow-tooltip>
|
|
<template #default="{ row }">{{ row.ref_name || "-" }}</template>
|
|
</el-table-column>
|
|
<el-table-column label="来源" width="90" align="center">
|
|
<template #default="{ row }">
|
|
<el-tag v-if="Number(row.ref_type) === 1" size="small" type="success" effect="plain">客户</el-tag>
|
|
<el-tag v-else-if="Number(row.ref_type) === 2" size="small" type="warning" effect="plain">供应商</el-tag>
|
|
<span v-else>本公司</span>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="signer_name" label="签约人" width="100">
|
|
<template #default="{ row }">{{ row.signer_name || "-" }}</template>
|
|
</el-table-column>
|
|
<el-table-column prop="signer_phone" label="联系电话" width="120">
|
|
<template #default="{ row }">{{ row.signer_phone || "-" }}</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
|
|
<el-divider content-position="left">产品清单</el-divider>
|
|
<el-table :data="detail.products || []" border size="small">
|
|
<el-table-column type="index" label="#" width="45" align="center" />
|
|
<el-table-column prop="name" label="产品名称" min-width="140" show-overflow-tooltip />
|
|
<el-table-column label="类别" width="100" align="center">
|
|
<template #default="{ row }">
|
|
<el-tag :type="productCategoryTag(row.category)" size="small" effect="plain">
|
|
{{ productCategoryText(row.category) }}
|
|
</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="unit" label="单位" width="60" align="center" />
|
|
<el-table-column label="数量" width="80" align="right">
|
|
<template #default="{ row }">{{ row.quantity ?? "-" }}</template>
|
|
</el-table-column>
|
|
<el-table-column label="单价" width="100" align="right">
|
|
<template #default="{ row }">{{ formatMoney(row.price) }}</template>
|
|
</el-table-column>
|
|
<el-table-column label="小计" width="110" align="right">
|
|
<template #default="{ row }">
|
|
{{ formatMoney((Number(row.quantity) || 0) * (Number(row.price) || 0)) }}
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="remark" label="备注" min-width="100" show-overflow-tooltip>
|
|
<template #default="{ row }">{{ row.remark || "-" }}</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
|
|
<el-divider content-position="left">金额汇总</el-divider>
|
|
<div class="summary-grid">
|
|
<div class="summary-card">
|
|
<span class="label">合同总金额</span>
|
|
<span class="value primary">¥{{ formatMoney(summary.total_amount) }}</span>
|
|
</div>
|
|
<div class="summary-card">
|
|
<span class="label">产品总成本</span>
|
|
<span class="value">¥{{ formatMoney(summary.total_cost) }}</span>
|
|
</div>
|
|
<div class="summary-card">
|
|
<span class="label">合同总利润</span>
|
|
<span class="value success">¥{{ formatMoney(summary.total_profit) }}</span>
|
|
</div>
|
|
<div class="summary-card">
|
|
<span class="label">硬件部分金额</span>
|
|
<span class="value warning">¥{{ formatMoney(summary.hardware_amount) }}</span>
|
|
</div>
|
|
<div class="summary-card">
|
|
<span class="label">软件部分金额</span>
|
|
<span class="value primary">¥{{ formatMoney(summary.software_amount) }}</span>
|
|
</div>
|
|
<div class="summary-card">
|
|
<span class="label">其他部分金额</span>
|
|
<span class="value">¥{{ formatMoney(summary.other_amount) }}</span>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<el-empty v-else-if="!loading" description="暂无数据" />
|
|
</div>
|
|
</el-drawer>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, computed, watch } from "vue";
|
|
import { getContractDetail } from "@/api/crmContract";
|
|
import { PARTY_ROLES, buildSummary } from "./utils";
|
|
import {
|
|
formatMoney,
|
|
formatDateOnly,
|
|
contractCategoryText,
|
|
ourRoleText,
|
|
contractStatusText,
|
|
contractStatusTag,
|
|
productCategoryText,
|
|
productCategoryTag,
|
|
} from "../../dict";
|
|
|
|
const props = defineProps({
|
|
visible: { type: Boolean, default: false },
|
|
/** 行数据(含 id),详情从接口拉取完整数据 */
|
|
contract: { type: Object, default: null },
|
|
});
|
|
|
|
const emit = defineEmits(["update:visible"]);
|
|
|
|
const loading = ref(false);
|
|
const detail = ref({});
|
|
|
|
const summary = computed(() => {
|
|
const s = detail.value.summary;
|
|
if (s && Object.keys(s).length) return s;
|
|
return buildSummary(detail.value.products || []);
|
|
});
|
|
|
|
const partyLabel = (role) => PARTY_ROLES.find((r) => r.key === role)?.label || role || "-";
|
|
|
|
watch(
|
|
() => props.visible,
|
|
async (val) => {
|
|
if (!val) return;
|
|
detail.value = {};
|
|
if (!props.contract?.id) return;
|
|
loading.value = true;
|
|
try {
|
|
const res = await getContractDetail(props.contract.id);
|
|
detail.value = res?.data || props.contract;
|
|
} catch {
|
|
detail.value = props.contract;
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
}
|
|
);
|
|
</script>
|
|
|
|
<style lang="less" scoped>
|
|
.contract-detail {
|
|
padding: 0 4px;
|
|
}
|
|
|
|
.detail-head {
|
|
.detail-title {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
|
|
.name {
|
|
font-size: 17px;
|
|
font-weight: 600;
|
|
color: var(--el-text-color-primary);
|
|
}
|
|
}
|
|
|
|
.detail-sub {
|
|
margin-top: 6px;
|
|
font-size: 13px;
|
|
color: var(--el-text-color-secondary);
|
|
}
|
|
}
|
|
|
|
.summary-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: 12px;
|
|
|
|
.summary-card {
|
|
padding: 12px 16px;
|
|
border: 1px solid var(--el-border-color-lighter);
|
|
border-radius: 8px;
|
|
background: var(--el-fill-color-extra-light);
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 6px;
|
|
|
|
.label {
|
|
font-size: 12px;
|
|
color: var(--el-text-color-secondary);
|
|
}
|
|
|
|
.value {
|
|
font-size: 17px;
|
|
font-weight: 700;
|
|
|
|
&.primary {
|
|
color: var(--el-color-primary);
|
|
}
|
|
&.success {
|
|
color: var(--el-color-success);
|
|
}
|
|
&.warning {
|
|
color: var(--el-color-warning);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</style>
|