Files
nodejs/exam/frontend/src/views/quote/QuoteDetail.vue
T
2026-08-21 11:19:36 +08:00

259 lines
8.3 KiB
Vue

<template>
<div class="page-container">
<el-card v-loading="loading">
<template #header>
<div class="header-row">
<span>报价详情 #{{ route.params.id }}</span>
<div>
<el-button
v-if="detail && detail.status === 'PENDING'"
type="success"
@click="handleApprove"
>
通过
</el-button>
<el-button
v-if="detail && detail.status === 'PENDING'"
type="danger"
@click="handleReject"
>
驳回
</el-button>
<el-button
v-if="detail && detail.status === 'APPROVED'"
type="primary"
@click="handleComplete"
>
完成
</el-button>
<el-button
v-if="detail && detail.status === 'REJECTED'"
type="warning"
@click="handleResubmit"
>
重新提交
</el-button>
<el-button @click="handleBack">返回</el-button>
</div>
</div>
</template>
<template v-if="detail">
<!-- 车辆信息 -->
<el-divider content-position="left">车辆信息</el-divider>
<el-descriptions :column="4" border v-if="detail.vehicle">
<el-descriptions-item label="车牌号">{{ detail.vehicle.license_plate }}</el-descriptions-item>
<el-descriptions-item label="品牌">{{ detail.vehicle.brand }}</el-descriptions-item>
<el-descriptions-item label="车型">{{ detail.vehicle.model }}</el-descriptions-item>
<el-descriptions-item label="购置价">{{ detail.vehicle.purchase_price.toLocaleString() }} 元</el-descriptions-item>
<el-descriptions-item label="注册日期">{{ detail.vehicle.reg_date }}</el-descriptions-item>
<el-descriptions-item label="座位数">{{ detail.vehicle.seat_count }}</el-descriptions-item>
</el-descriptions>
<!-- 保费信息 -->
<el-divider content-position="left">保费信息</el-divider>
<el-descriptions :column="3" border>
<el-descriptions-item label="交强险">
<span class="premium">{{ detail.compulsory_premium.toLocaleString() }} 元</span>
</el-descriptions-item>
<el-descriptions-item label="商业险">
<span class="premium">{{ detail.commercial_premium.toLocaleString() }} 元</span>
</el-descriptions-item>
<el-descriptions-item label="总保费">
<span class="total">{{ detail.total_premium.toLocaleString() }} 元</span>
</el-descriptions-item>
<el-descriptions-item label="NCD系数">{{ detail.ncd_ratio }}</el-descriptions-item>
<el-descriptions-item label="出险次数">{{ detail.accident_count }}</el-descriptions-item>
<el-descriptions-item label="状态">
<el-tag :type="statusType(detail.status)">{{ statusLabel(detail.status) }}</el-tag>
</el-descriptions-item>
<el-descriptions-item label="有效期至">{{ detail.valid_until }}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ formatDate(detail.created_at) }}</el-descriptions-item>
<el-descriptions-item label="更新时间">{{ formatDate(detail.updated_at) }}</el-descriptions-item>
<el-descriptions-item label="处理意见" :span="3">{{ detail.remark || '-' }}</el-descriptions-item>
</el-descriptions>
<!-- 状态流转历史 -->
<el-divider content-position="left">状态流转历史</el-divider>
<el-timeline v-if="detail.histories && detail.histories.length > 0">
<el-timeline-item
v-for="history in detail.histories"
:key="history.id"
:timestamp="formatDate(history.created_at)"
:type="timelineType(history.to_status)"
>
<div class="history-item">
<strong>{{ statusLabel(history.to_status) }}</strong>
<span> - {{ history.operator }}</span>
<div v-if="history.comment" class="history-comment">{{ history.comment }}</div>
</div>
</el-timeline-item>
</el-timeline>
<el-empty v-else description="暂无状态流转记录" />
</template>
</el-card>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getQuote, approveQuote, rejectQuote, completeQuote, resubmitQuote } from '../../api/quote'
import type { InsuranceQuote } from '../../types'
const router = useRouter()
const route = useRoute()
const loading = ref(false)
const detail = ref<InsuranceQuote | null>(null)
function statusLabel(status: string) {
const map: Record<string, string> = {
PENDING: '待处理',
APPROVED: '已通过',
REJECTED: '已驳回',
COMPLETED: '已完成',
}
return map[status] || status
}
function statusType(status: string) {
const map: Record<string, 'warning' | 'success' | 'danger' | 'info'> = {
PENDING: 'warning',
APPROVED: 'success',
REJECTED: 'danger',
COMPLETED: 'info',
}
return map[status] || 'info'
}
function timelineType(status: string) {
const map: Record<string, 'warning' | 'success' | 'danger' | 'info'> = {
PENDING: 'warning',
APPROVED: 'success',
REJECTED: 'danger',
COMPLETED: 'info',
}
return map[status] || 'info'
}
function formatDate(dateStr: string) {
if (!dateStr) return ''
const d = new Date(dateStr)
return d.toLocaleString('zh-CN', { hour12: false })
}
async function loadDetail() {
loading.value = true
try {
const res = await getQuote(Number(route.params.id))
detail.value = res.data
} catch (e: any) {
ElMessage.error(e.message || '获取报价详情失败')
} finally {
loading.value = false
}
}
async function handleApprove() {
try {
const { value } = await ElMessageBox.prompt('请输入处理意见', '通过报价', {
confirmButtonText: '确认通过',
cancelButtonText: '取消',
inputValidator: (val: string) => !!val || '处理意见不能为空',
})
await approveQuote(Number(route.params.id), value)
ElMessage.success('已通过')
loadDetail()
} catch (e: any) {
if (e !== 'cancel' && e !== 'close') {
ElMessage.error(e.message || '操作失败')
}
}
}
async function handleReject() {
try {
const { value } = await ElMessageBox.prompt('请输入驳回原因', '驳回报价', {
confirmButtonText: '确认驳回',
cancelButtonText: '取消',
inputValidator: (val: string) => !!val || '驳回原因不能为空',
})
await rejectQuote(Number(route.params.id), value)
ElMessage.success('已驳回')
loadDetail()
} catch (e: any) {
if (e !== 'cancel' && e !== 'close') {
ElMessage.error(e.message || '操作失败')
}
}
}
async function handleComplete() {
try {
const { value } = await ElMessageBox.prompt('请输入处理意见', '完成报价', {
confirmButtonText: '确认完成',
cancelButtonText: '取消',
inputValidator: (val: string) => !!val || '处理意见不能为空',
})
await completeQuote(Number(route.params.id), value)
ElMessage.success('已完成')
loadDetail()
} catch (e: any) {
if (e !== 'cancel' && e !== 'close') {
ElMessage.error(e.message || '操作失败')
}
}
}
async function handleResubmit() {
try {
const { value } = await ElMessageBox.prompt('请输入重新提交说明', '重新提交', {
confirmButtonText: '确认提交',
cancelButtonText: '取消',
})
await resubmitQuote(Number(route.params.id), value || '重新提交')
ElMessage.success('已重新提交')
loadDetail()
} catch (e: any) {
if (e !== 'cancel' && e !== 'close') {
ElMessage.error(e.message || '操作失败')
}
}
}
function handleBack() {
router.push('/quotes')
}
onMounted(loadDetail)
</script>
<style scoped>
.header-row {
display: flex;
justify-content: space-between;
align-items: center;
}
.premium {
font-weight: 600;
color: #409eff;
}
.total {
font-weight: 700;
color: #e6a23c;
font-size: 18px;
}
.history-item {
line-height: 1.6;
}
.history-comment {
color: #666;
font-size: 13px;
margin-top: 4px;
}
</style>