Files
shop-toy/src/pagesInsurance/claim-review/detail.vue
2026-01-12 18:24:25 +08:00

492 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script lang="ts" setup>
import type { ClaimApplication } from '@/api/types/insurance'
import { getClaimApplicationDetail, reviewClaimApplication } from '@/api/insurance'
definePage({
style: {
navigationBarTitleText: '理赔审核详情',
},
})
const claimId = ref('')
const claim = ref<ClaimApplication | null>(null)
const loading = ref(false)
const reviewing = ref(false)
const rejectionReason = ref('')
const payoutAmount = ref('')
// 状态映射
const statusMap: Record<string, { text: string, color: string }> = {
pending: { text: '待审核', color: '#F59E0B' },
approved: { text: '已通过', color: '#00c05a' },
rejected: { text: '已拒绝', color: '#fa4350' },
}
const statusInfo = computed(() => {
if (!claim.value)
return null
return statusMap[claim.value.status] || { text: claim.value.status, color: '#666' }
})
async function loadDetail() {
loading.value = true
try {
const res = await getClaimApplicationDetail(claimId.value)
claim.value = res
if (res.payoutAmount) {
payoutAmount.value = res.payoutAmount.toString()
}
}
finally {
loading.value = false
}
}
// 预览图片
function handlePreviewImage(url: string) {
uni.previewImage({
urls: [url],
})
}
// 理赔审核通过
async function handleApprove() {
if (!payoutAmount.value || Number(payoutAmount.value) <= 0) {
uni.showToast({ title: '请输入赔付金额', icon: 'none' })
return
}
reviewing.value = true
try {
await reviewClaimApplication(claimId.value, {
approved: true,
payoutAmount: Number(payoutAmount.value),
})
uni.showToast({ title: '审核通过', icon: 'success' })
setTimeout(() => {
uni.navigateBack()
}, 1500)
}
finally {
reviewing.value = false
}
}
// 理赔审核拒绝
async function handleReject() {
if (!rejectionReason.value) {
uni.showToast({ title: '请填写拒绝原因', icon: 'none' })
return
}
reviewing.value = true
try {
await reviewClaimApplication(claimId.value, {
approved: false,
rejectionReason: rejectionReason.value,
})
uni.showToast({ title: '已拒绝', icon: 'success' })
setTimeout(() => {
uni.navigateBack()
}, 1500)
}
finally {
reviewing.value = false
}
}
onLoad((options) => {
if (options?.id) {
claimId.value = options.id
loadDetail()
}
})
</script>
<template>
<view class="claim-review-detail-page">
<view v-if="loading" class="loading-state">
加载中...
</view>
<template v-else-if="claim">
<!-- 状态卡片 -->
<view class="status-card" :style="{ background: statusInfo?.color }">
<text class="status-text">{{ statusInfo?.text }}</text>
</view>
<!-- 银行信息 -->
<view class="section-card">
<view class="card-header">
银行信息
</view>
<view class="info-list">
<view class="info-item">
<text class="label">银行名称</text>
<text class="value">{{ claim.bankName }}</text>
</view>
<view class="info-item">
<text class="label">保险单号</text>
<text class="value">{{ claim.policyNumber }}</text>
</view>
</view>
</view>
<!-- 保险公司信息 -->
<view class="section-card">
<view class="card-header">
保险公司信息
</view>
<view class="info-list">
<view class="info-item">
<text class="label">保险公司</text>
<text class="value">{{ claim.companyName }}</text>
</view>
</view>
</view>
<!-- 理赔信息 -->
<view class="section-card">
<view class="card-header">
理赔信息
</view>
<view class="info-list">
<view class="info-item">
<text class="label">理赔金额</text>
<text class="value amount">{{ claim.claimAmount }}</text>
</view>
<view class="info-item">
<text class="label">理赔原因</text>
<text class="value reason">{{ claim.claimReason }}</text>
</view>
<view class="info-item">
<text class="label">提交时间</text>
<text class="value">{{ claim.submittedAt }}</text>
</view>
</view>
</view>
<!-- 理赔材料 -->
<view class="section-card">
<view class="card-header">
理赔材料
</view>
<view class="materials-list">
<view
v-for="material in claim.materials"
:key="material.id"
class="material-item"
@click="handlePreviewImage(material.url)"
>
<image :src="material.url" mode="aspectFill" />
<view class="material-info">
<text class="material-name">{{ material.name }}</text>
<text class="upload-time">{{ material.uploadTime }}</text>
</view>
</view>
</view>
</view>
<!-- 审核操作 -->
<view v-if="claim.status === 'pending'" class="action-card">
<view class="card-header">
理赔审核
</view>
<view class="action-buttons">
<button
class="btn approve"
:disabled="reviewing"
@click="handleApprove"
>
<text v-if="reviewing">处理中...</text>
<text v-else>通过</text>
</button>
<button
class="btn reject"
:disabled="reviewing"
@click="handleReject"
>
<text v-if="reviewing">处理中...</text>
<text v-else>拒绝</text>
</button>
</view>
<view class="payout-input">
<text class="label">赔付金额</text>
<input
v-model="payoutAmount"
type="digit"
class="input"
placeholder="请输入赔付金额"
>
</view>
<view v-if="!reviewing" class="reject-reason">
<textarea
v-model="rejectionReason"
class="textarea"
placeholder="请输入拒绝原因..."
:maxlength="500"
/>
<text class="char-count">{{ rejectionReason.length }}/500</text>
</view>
</view>
<!-- 已审核信息 -->
<view v-if="claim.status !== 'pending'" class="section-card">
<view class="card-header">
审核信息
</view>
<view class="info-list">
<view class="info-item">
<text class="label">审核时间</text>
<text class="value">{{ claim.reviewedAt }}</text>
</view>
<view class="info-item">
<text class="label">审核人员</text>
<text class="value">{{ claim.reviewedBy }}</text>
</view>
<view v-if="claim.payoutAmount" class="info-item">
<text class="label">赔付金额</text>
<text class="value payout">{{ claim.payoutAmount }}</text>
</view>
<view v-if="claim.payoutDate" class="info-item">
<text class="label">赔付日期</text>
<text class="value">{{ claim.payoutDate }}</text>
</view>
<view v-if="claim.rejectionReason" class="info-item full">
<text class="label">拒绝原因</text>
<text class="value reason">{{ claim.rejectionReason }}</text>
</view>
</view>
</view>
</template>
</view>
</template>
<style lang="scss" scoped>
.claim-review-detail-page {
min-height: 100vh;
background: #f5f7fa;
padding: 20rpx;
padding-bottom: 120rpx;
}
.loading-state {
display: flex;
align-items: center;
justify-content: center;
padding: 100rpx 0;
color: #999;
}
.status-card {
background: linear-gradient(135deg, #00c05a 0%, #34d19d 100%);
border-radius: 16rpx;
padding: 40rpx 30rpx;
color: #fff;
margin-bottom: 20rpx;
text-align: center;
.status-text {
font-size: 36rpx;
font-weight: bold;
display: block;
}
}
.section-card {
background: #fff;
border-radius: 16rpx;
padding: 0 30rpx;
margin-bottom: 20rpx;
.card-header {
padding: 30rpx 0;
border-bottom: 1rpx solid #f5f5f5;
font-size: 28rpx;
font-weight: bold;
color: #333;
}
}
.info-list {
padding: 20rpx 0;
.info-item {
display: flex;
justify-content: space-between;
padding: 16rpx 0;
font-size: 26rpx;
border-bottom: 1rpx solid #f9f9f9;
&:last-child {
border-bottom: none;
}
.label {
color: #666;
}
.value {
color: #333;
text-align: right;
&.amount {
color: #ff8f0d;
font-weight: 600;
}
&.reason {
text-align: left;
line-height: 1.5;
}
&.payout {
color: #00c05a;
font-weight: 600;
}
}
&.full {
flex-direction: column;
align-items: flex-start;
.value {
margin-top: 8rpx;
width: 100%;
}
}
}
}
.materials-list {
padding: 20rpx 0;
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16rpx;
.material-item {
position: relative;
border-radius: 12rpx;
overflow: hidden;
background: #f8f9fa;
image {
width: 100%;
height: 200rpx;
display: block;
}
.material-info {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: rgba(0, 0, 0, 0.7);
padding: 12rpx;
display: flex;
flex-direction: column;
gap: 4rpx;
.material-name {
font-size: 22rpx;
color: #fff;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.upload-time {
font-size: 20rpx;
color: rgba(255, 255, 255, 0.8);
}
}
}
}
.action-card {
background: #fff;
border-radius: 16rpx;
padding: 0 30rpx;
margin-bottom: 20rpx;
.card-header {
padding: 30rpx 0;
border-bottom: 1rpx solid #f5f5f5;
font-size: 28rpx;
font-weight: bold;
color: #333;
}
.action-buttons {
display: flex;
gap: 20rpx;
padding: 20rpx 0;
.btn {
flex: 1;
font-size: 28rpx;
height: 80rpx;
line-height: 80rpx;
border-radius: 40rpx;
&.approve {
background: #00c05a;
color: #fff;
}
&.reject {
background: #fa4350;
color: #fff;
}
&:disabled {
opacity: 0.6;
}
}
}
.payout-input {
padding: 0 0 20rpx 0;
.label {
display: block;
font-size: 26rpx;
color: #666;
margin-bottom: 12rpx;
}
.input {
width: 100%;
padding: 16rpx;
background: #f8f9fa;
border-radius: 8rpx;
font-size: 26rpx;
color: #333;
}
}
.reject-reason {
padding: 0 0 20rpx 0;
.textarea {
width: 100%;
min-height: 150rpx;
padding: 16rpx;
background: #f8f9fa;
border-radius: 8rpx;
font-size: 26rpx;
color: #333;
line-height: 1.5;
}
.char-count {
font-size: 22rpx;
color: #999;
text-align: right;
margin-top: 8rpx;
display: block;
}
}
}
</style>