259 lines
8.3 KiB
Vue
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>
|