继续完善保险项目和养殖端小程序

This commit is contained in:
xuqiuyun
2025-09-29 18:35:03 +08:00
parent 4af8368097
commit 4e8d4dc92d
192 changed files with 4886 additions and 35384 deletions

View File

@@ -175,7 +175,7 @@ const routes = [
]
const router = createRouter({
history: createWebHistory(),
history: createWebHistory('/insurance/'),
routes
})

View File

@@ -58,6 +58,8 @@ export const applicationAPI = {
export const policyAPI = {
getList: (params) => api.get('/policies', { params }),
getDetail: (id) => api.get(`/policies/${id}`),
create: (data) => api.post('/policies', data),
update: (id, data) => api.put(`/policies/${id}`, data),
updateStatus: (id, data) => api.put(`/policies/${id}/status`, data),
delete: (id) => api.delete(`/policies/${id}`)
}

View File

@@ -164,6 +164,31 @@ const fetchRequest = async (url, options = {}) => {
// 设置默认请求头
options.headers = createHeaders(options.headers)
// ========== 请求前日志 ==========
try {
const hasAuth = !!options.headers?.Authorization
const contentType = options.headers?.['Content-Type']
const acceptType = options.headers?.['Accept']
let bodyPreview = null
if (options.body) {
try {
bodyPreview = JSON.stringify(JSON.parse(options.body))
} catch (_) {
bodyPreview = String(options.body).slice(0, 1000)
}
}
console.log('🔶 [前端] 准备发起请求', {
method: options.method || 'GET',
url: fullUrl,
hasAuthorization: hasAuth ? '是' : '否',
contentType,
acceptType,
bodyPreview
})
} catch (logError) {
console.warn('记录请求前日志失败:', logError)
}
// 设置超时
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), API_CONFIG.timeout)
@@ -172,10 +197,66 @@ const fetchRequest = async (url, options = {}) => {
try {
const response = await fetch(fullUrl, options)
clearTimeout(timeoutId)
return await handleResponse(response)
// ========== 原始响应日志 ==========
try {
console.log('🟩 [前端] 收到原始响应', {
status: response.status,
statusText: response.statusText,
url: fullUrl,
ok: response.ok,
contentType: response.headers.get('content-type')
})
} catch (respLogErr) {
console.warn('记录原始响应日志失败:', respLogErr)
}
const result = await handleResponse(response)
// ========== 处理后响应日志 ==========
try {
const safeData = (() => {
try {
return typeof result.data === 'string' ? result.data.slice(0, 1000) : JSON.stringify(result.data).slice(0, 1000)
} catch (_) {
return '[不可序列化数据]'
}
})()
console.log('✅ [前端] 请求成功', {
url: fullUrl,
method: options.method || 'GET',
status: result.status,
statusText: result.statusText,
dataPreview: safeData
})
} catch (resLogErr) {
console.warn('记录处理后响应日志失败:', resLogErr)
}
return result
} catch (error) {
clearTimeout(timeoutId)
// ========== 错误日志 ==========
try {
console.error('❌ [前端] 请求失败', {
url: fullUrl,
method: options.method || 'GET',
message: error.message,
name: error.name,
responseStatus: error.response?.status,
responseDataPreview: (() => {
try {
return JSON.stringify(error.response?.data).slice(0, 1000)
} catch (_) {
return String(error.response?.data || '')
}
})()
})
} catch (errLogErr) {
console.warn('记录错误日志失败:', errLogErr)
}
// 处理401错误
if (error.response?.status === 401) {
const errorCode = error.response?.data?.code

File diff suppressed because it is too large Load Diff

View File

@@ -20,8 +20,8 @@
</a-col>
<a-col :span="6">
<a-input
v-model:value="searchForm.farmer_name"
placeholder="农户姓名"
v-model:value="searchForm.policyholder_name"
placeholder="投保人姓名"
allow-clear
/>
</a-col>
@@ -118,6 +118,9 @@
<a-menu-item key="payment" v-if="record.payment_status === 'unpaid'">
标记已支付
</a-menu-item>
<a-menu-item key="delete" style="color: #ff4d4f;">
删除保单
</a-menu-item>
</a-menu>
</template>
<a-button type="link" size="small">
@@ -146,20 +149,20 @@
>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="农户姓名" name="farmer_name">
<a-input v-model:value="formData.farmer_name" placeholder="请输入农户姓名" />
<a-form-item label="投保人姓名" name="policyholder_name">
<a-input v-model:value="formData.policyholder_name" placeholder="请输入投保人姓名" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="农户电话" name="farmer_phone">
<a-input v-model:value="formData.farmer_phone" placeholder="请输入农户电话" />
<a-form-item label="投保人电话" name="policyholder_phone">
<a-input v-model:value="formData.policyholder_phone" placeholder="请输入投保人电话" />
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="身份证号" name="farmer_id_card">
<a-input v-model:value="formData.farmer_id_card" placeholder="请输入身份证号" />
<a-form-item label="身份证号" name="policyholder_id_card">
<a-input v-model:value="formData.policyholder_id_card" placeholder="请输入身份证号" />
</a-form-item>
</a-col>
<a-col :span="12">
@@ -180,8 +183,26 @@
</a-form-item>
</a-col>
</a-row>
<a-form-item label="农户地址" name="farmer_address">
<a-textarea v-model:value="formData.farmer_address" placeholder="请输入农户地址" :rows="2" />
<a-form-item label="投保人地址" name="policyholder_address">
<a-textarea v-model:value="formData.policyholder_address" placeholder="请输入投保人地址" :rows="2" />
</a-form-item>
<!-- 养殖场信息 -->
<a-divider orientation="left">养殖场信息</a-divider>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="养殖场名称" name="farm_name">
<a-input v-model:value="formData.farm_name" placeholder="请输入养殖场名称" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="养殖场许可证号" name="farm_license">
<a-input v-model:value="formData.farm_license" placeholder="请输入养殖场许可证号" />
</a-form-item>
</a-col>
</a-row>
<a-form-item label="养殖场地址" name="farm_address">
<a-textarea v-model:value="formData.farm_address" placeholder="请输入养殖场地址" :rows="2" />
</a-form-item>
<a-row :gutter="16">
<a-col :span="8">
@@ -208,15 +229,11 @@
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="保费费率" name="premium_rate">
<a-input-number
v-model:value="formData.premium_rate"
placeholder="请输入费率"
:min="0"
:max="1"
:precision="4"
style="width: 100%"
@change="calculateAmounts"
<a-form-item label="保费费率">
<a-input
:value="getCurrentPremiumRate()"
disabled
placeholder="自动计算"
/>
</a-form-item>
</a-col>
@@ -280,15 +297,18 @@
{{ getStatusText(currentRecord.policy_status) }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="农户姓名">{{ currentRecord.farmer_name }}</a-descriptions-item>
<a-descriptions-item label="农户电话">{{ currentRecord.farmer_phone }}</a-descriptions-item>
<a-descriptions-item label="身份证号">{{ currentRecord.farmer_id_card }}</a-descriptions-item>
<a-descriptions-item label="投保人姓名">{{ currentRecord.policyholder_name }}</a-descriptions-item>
<a-descriptions-item label="投保人电话">{{ currentRecord.policyholder_phone }}</a-descriptions-item>
<a-descriptions-item label="身份证号">{{ currentRecord.policyholder_id_card }}</a-descriptions-item>
<a-descriptions-item label="牲畜类型">{{ currentRecord.livestock_type?.name }}</a-descriptions-item>
<a-descriptions-item label="农户地址" :span="2">{{ currentRecord.farmer_address }}</a-descriptions-item>
<a-descriptions-item label="投保人地址" :span="2">{{ currentRecord.policyholder_address }}</a-descriptions-item>
<a-descriptions-item label="养殖场名称">{{ currentRecord.farm_name || '无' }}</a-descriptions-item>
<a-descriptions-item label="养殖场地址">{{ currentRecord.farm_address || '无' }}</a-descriptions-item>
<a-descriptions-item label="养殖场许可证号" :span="2">{{ currentRecord.farm_license || '无' }}</a-descriptions-item>
<a-descriptions-item label="牲畜数量">{{ currentRecord.livestock_count }}</a-descriptions-item>
<a-descriptions-item label="单头价值">¥{{ currentRecord.unit_value?.toLocaleString() }}</a-descriptions-item>
<a-descriptions-item label="总保额">¥{{ currentRecord.total_value?.toLocaleString() }}</a-descriptions-item>
<a-descriptions-item label="保费费率">{{ (currentRecord.premium_rate * 100).toFixed(2) }}%</a-descriptions-item>
<a-descriptions-item label="保费费率">{{ currentRecord.livestock_type?.premium_rate ? (currentRecord.livestock_type.premium_rate * 100).toFixed(2) + '%' : '未知' }}</a-descriptions-item>
<a-descriptions-item label="保费金额">¥{{ currentRecord.premium_amount?.toLocaleString() }}</a-descriptions-item>
<a-descriptions-item label="支付状态">
<a-tag :color="getPaymentStatusColor(currentRecord.payment_status)">
@@ -307,14 +327,14 @@
<script setup>
import { ref, reactive, onMounted, computed } from 'vue'
import { message } from 'ant-design-vue'
import { message, Modal } from 'ant-design-vue'
import dayjs from 'dayjs'
import {
PlusOutlined,
SearchOutlined,
DownOutlined
} from '@ant-design/icons-vue'
import { livestockPolicyApi } from '@/utils/api'
import { livestockPolicyApi, livestockTypeApi } from '@/utils/api'
// 响应式数据
const loading = ref(false)
@@ -328,7 +348,7 @@ const livestockTypes = ref([])
// 搜索表单
const searchForm = reactive({
policy_no: '',
farmer_name: '',
policyholder_name: '',
policy_status: undefined,
payment_status: undefined,
dateRange: []
@@ -346,37 +366,43 @@ const pagination = reactive({
// 表单数据
const formData = reactive({
farmer_name: '',
farmer_phone: '',
farmer_id_card: '',
farmer_address: '',
policy_no: '',
policyholder_name: '',
policyholder_phone: '',
policyholder_id_card: '',
policyholder_address: '',
farm_name: '',
farm_address: '',
farm_license: '',
livestock_type_id: undefined,
livestock_count: undefined,
unit_value: undefined,
premium_rate: undefined,
total_value: 0,
premium_amount: 0,
start_date: undefined,
end_date: undefined,
policy_status: 'active',
payment_status: 'unpaid',
payment_date: undefined,
policy_document_url: '',
notes: ''
})
// 表单验证规则
const formRules = {
farmer_name: [{ required: true, message: '请输入农户姓名' }],
farmer_phone: [
{ required: true, message: '请输入农户电话' },
policyholder_name: [{ required: true, message: '请输入投保人姓名' }],
policyholder_phone: [
{ required: true, message: '请输入投保人电话' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码' }
],
farmer_id_card: [
policyholder_id_card: [
{ required: true, message: '请输入身份证号' },
{ pattern: /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/, message: '请输入正确的身份证号' }
],
farmer_address: [{ required: true, message: '请输入农户地址' }],
policyholder_address: [{ required: true, message: '请输入投保人地址' }],
livestock_type_id: [{ required: true, message: '请选择牲畜类型' }],
livestock_count: [{ required: true, message: '请输入牲畜数量' }],
unit_value: [{ required: true, message: '请输入单头价值' }],
premium_rate: [{ required: true, message: '请输入保费费率' }],
start_date: [{ required: true, message: '请选择保险开始日期' }],
end_date: [{ required: true, message: '请选择保险结束日期' }]
}
@@ -395,15 +421,15 @@ const columns = [
width: 150
},
{
title: '农户姓名',
dataIndex: 'farmer_name',
key: 'farmer_name',
title: '投保人姓名',
dataIndex: 'policyholder_name',
key: 'policyholder_name',
width: 100
},
{
title: '农户电话',
dataIndex: 'farmer_phone',
key: 'farmer_phone',
title: '投保人电话',
dataIndex: 'policyholder_phone',
key: 'policyholder_phone',
width: 120
},
{
@@ -500,12 +526,13 @@ const fetchData = async () => {
const fetchLivestockTypes = async () => {
try {
const response = await livestockPolicyApi.getLivestockTypes()
const response = await livestockTypeApi.getList({ status: 'active' })
if (response.code === 200) {
livestockTypes.value = response.data.list
}
} catch (error) {
console.error('获取牲畜类型失败:', error)
message.error('获取牲畜类型失败')
}
}
@@ -579,6 +606,25 @@ const handleMenuClick = async ({ key }, record) => {
message.success('支付状态已更新')
fetchData()
break
case 'delete':
Modal.confirm({
title: '确认删除',
content: `确定要删除保单编号为 ${record.policy_no} 的保单吗?此操作不可恢复。`,
okText: '确定',
cancelText: '取消',
okType: 'danger',
onOk: async () => {
try {
await livestockPolicyApi.delete(record.id)
message.success('保单删除成功')
fetchData()
} catch (error) {
message.error('删除失败')
console.error('删除失败:', error)
}
}
})
break
}
} catch (error) {
message.error('操作失败')
@@ -627,18 +673,24 @@ const resetForm = () => {
Object.keys(formData).forEach(key => {
if (key === 'total_value' || key === 'premium_amount') {
formData[key] = 0
} else if (key === 'policy_status') {
formData[key] = 'active'
} else if (key === 'payment_status') {
formData[key] = 'unpaid'
} else if (key === 'notes' || key === 'policy_no' || key === 'policy_document_url') {
formData[key] = ''
} else {
formData[key] = undefined
}
})
formData.notes = ''
}
const handleLivestockTypeChange = (value) => {
const selectedType = livestockTypes.value.find(type => type.id === value)
if (selectedType) {
formData.unit_value = selectedType.base_value
formData.premium_rate = selectedType.premium_rate
// 设置单头价值的建议值(可以是最小值和最大值的中间值)
const suggestedValue = (selectedType.unit_price_min + selectedType.unit_price_max) / 2
formData.unit_value = suggestedValue
calculateAmounts()
}
}
@@ -647,8 +699,13 @@ const calculateAmounts = () => {
if (formData.livestock_count && formData.unit_value) {
formData.total_value = formData.livestock_count * formData.unit_value
}
if (formData.total_value && formData.premium_rate) {
formData.premium_amount = formData.total_value * formData.premium_rate
// 从选中的牲畜类型获取保费费率
if (formData.total_value && formData.livestock_type_id) {
const selectedType = livestockTypes.value.find(type => type.id === formData.livestock_type_id)
if (selectedType && selectedType.premium_rate) {
formData.premium_amount = formData.total_value * selectedType.premium_rate
}
}
}
@@ -702,6 +759,16 @@ const formatDateTime = (dateTime) => {
return dateTime ? dayjs(dateTime).format('YYYY-MM-DD HH:mm:ss') : ''
}
const getCurrentPremiumRate = () => {
if (formData.livestock_type_id) {
const selectedType = livestockTypes.value.find(type => type.id === formData.livestock_type_id)
if (selectedType && selectedType.premium_rate) {
return `${(selectedType.premium_rate * 100).toFixed(2)}%`
}
}
return '请先选择牲畜类型'
}
// 生命周期
onMounted(() => {
fetchData()

View File

@@ -192,6 +192,27 @@
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="客户选择" name="customer_id">
<a-select v-model:value="formState.customer_id" placeholder="请选择客户">
<a-select-option :value="1">张三 (客户1)</a-select-option>
<a-select-option :value="2">李四 (客户2)</a-select-option>
<a-select-option :value="3">王五 (客户3)</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="关联申请" name="application_id">
<a-select v-model:value="formState.application_id" placeholder="请选择关联申请">
<a-select-option :value="1">申请001</a-select-option>
<a-select-option :value="2">申请002</a-select-option>
<a-select-option :value="3">申请003</a-select-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="投保人姓名" name="policyholder_name">
@@ -295,8 +316,9 @@
</template>
<script setup>
import { ref, reactive, onMounted, computed } from 'vue'
import { message } from 'ant-design-vue'
import { ref, reactive, computed, onMounted } from 'vue'
import { message, Modal } from 'ant-design-vue'
import dayjs from 'dayjs'
import {
PlusOutlined,
SearchOutlined,
@@ -321,6 +343,8 @@ const searchForm = reactive({
const formState = reactive({
policy_number: '',
customer_id: null,
application_id: null,
insurance_type_id: null,
policyholder_name: '',
insured_name: '',
@@ -527,8 +551,8 @@ const handleEdit = (record) => {
insured_name: record.insured_name,
premium_amount: record.premium_amount,
coverage_amount: record.coverage_amount,
start_date: record.start_date,
end_date: record.end_date,
start_date: record.start_date ? dayjs(record.start_date) : null,
end_date: record.end_date ? dayjs(record.end_date) : null,
status: record.status,
phone: record.phone,
email: record.email,
@@ -542,18 +566,32 @@ const handleModalOk = async () => {
try {
await formRef.value.validate()
// 准备提交数据,格式化日期
const submitData = { ...formState }
if (submitData.start_date) {
submitData.start_date = submitData.start_date.format('YYYY-MM-DD')
}
if (submitData.end_date) {
submitData.end_date = submitData.end_date.format('YYYY-MM-DD')
}
if (editingId.value) {
// await policyAPI.update(editingId.value, formState)
await policyAPI.update(editingId.value, submitData)
message.success('保单更新成功')
} else {
// await policyAPI.create(formState)
await policyAPI.create(submitData)
message.success('保单创建成功')
}
modalVisible.value = false
loadPolicies()
} catch (error) {
console.log('表单验证失败', error)
if (error.errorFields) {
console.log('表单验证失败', error)
} else {
message.error(editingId.value ? '保单更新失败' : '保单创建失败')
console.error('API调用失败:', error)
}
}
}
@@ -564,11 +602,12 @@ const handleModalCancel = () => {
const handleToggleStatus = async (record) => {
try {
const newStatus = record.status === 'active' ? 'cancelled' : 'active'
// await policyAPI.update(record.id, { status: newStatus })
await policyAPI.update(record.id, { status: newStatus })
message.success('状态更新成功')
loadPolicies()
} catch (error) {
message.error('状态更新失败')
console.error('状态更新失败:', error)
}
}
@@ -581,13 +620,23 @@ const handleClaim = async (record) => {
}
const handleDelete = async (id) => {
try {
// await policyAPI.delete(id)
message.success('保单删除成功')
loadPolicies()
} catch (error) {
message.error('保单删除失败')
}
Modal.confirm({
title: '确认删除',
content: '确定要删除这个保单吗?此操作不可恢复。',
okText: '确定',
cancelText: '取消',
okType: 'danger',
onOk: async () => {
try {
await policyAPI.delete(id)
message.success('保单删除成功')
loadPolicies()
} catch (error) {
message.error('保单删除失败')
console.error('删除失败:', error)
}
}
})
}
onMounted(() => {

View File

@@ -7,6 +7,7 @@ export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd())
return {
base: '/insurance/',
plugins: [vue()],
resolve: {
alias: {