<!-- 顶部状态栏 -->

This commit is contained in:
2026-01-12 18:24:25 +08:00
parent 725aa1819a
commit 7adfd27f3f
33 changed files with 9401 additions and 309 deletions

View File

@@ -0,0 +1,491 @@
<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>