完善保险端的前后端

This commit is contained in:
shenquanyi
2025-09-23 18:29:24 +08:00
parent e7a0cd4aa3
commit b58ed724b0
69 changed files with 9728 additions and 769 deletions

View File

@@ -216,7 +216,7 @@ const fetchMenus = async () => {
key: 'CompletedTask',
icon: () => h(FileDoneOutlined),
label: '监管任务已结项',
path: '/dashboard' // 重定向到仪表板
path: '/completed-tasks'
},
{
key: 'InsuredCustomers',
@@ -239,6 +239,11 @@ const fetchMenus = async () => {
key: 'PolicyManagement',
label: '生资保单列表',
path: '/policies'
},
{
key: 'LivestockPolicyManagement',
label: '生资保单管理',
path: '/livestock-policies'
}
]
},

View File

@@ -7,6 +7,27 @@ import 'ant-design-vue/dist/reset.css'
// Ant Design Vue的中文语言包
import antdZhCN from 'ant-design-vue/es/locale/zh_CN'
// 抑制ResizeObserver警告
const resizeObserverErrorHandler = (e) => {
if (e.message === 'ResizeObserver loop completed with undelivered notifications.' ||
e.message.includes('ResizeObserver loop')) {
e.preventDefault()
e.stopImmediatePropagation()
return false
}
}
// 处理未捕获的错误
window.addEventListener('error', resizeObserverErrorHandler)
// 处理未捕获的Promise错误
window.addEventListener('unhandledrejection', (e) => {
if (e.reason && e.reason.message && e.reason.message.includes('ResizeObserver loop')) {
e.preventDefault()
return false
}
})
// Ant Design Vue 4.x配置
// 使用Ant Design Vue内置的dayjs实例避免版本冲突
const antdConfig = {

View File

@@ -18,6 +18,7 @@ import AntdConfigDebug from '@/views/AntdConfigDebug.vue'
import SimpleDayjsTest from '@/views/SimpleDayjsTest.vue'
import RangePickerTest from '@/views/RangePickerTest.vue'
import LoginTest from '@/views/LoginTest.vue'
import LivestockPolicyManagement from '@/views/LivestockPolicyManagement.vue'
const routes = [
{
@@ -79,6 +80,12 @@ const routes = [
component: SupervisionTaskManagement,
meta: { title: '监管任务管理' }
},
{
path: 'completed-tasks',
name: 'CompletedTasks',
component: () => import('@/views/CompletedTaskManagement.vue'),
meta: { requiresAuth: true, title: '监管任务结项' }
},
{
path: 'installation-tasks',
alias: ['pending-installation', 'installation-task'], // 添加别名兼容不同形式
@@ -86,6 +93,12 @@ const routes = [
component: InstallationTaskManagement,
meta: { title: '待安装任务管理' }
},
{
path: 'livestock-policies',
name: 'LivestockPolicyManagement',
component: LivestockPolicyManagement,
meta: { title: '生资保单管理' }
},
{
path: 'date-picker-test',
name: 'DatePickerTest',

View File

@@ -66,8 +66,14 @@ export const insuranceTypeAPI = {
export const applicationAPI = {
getList: (params) => api.get('/insurance/applications', { params }),
getDetail: (id) => api.get(`/insurance/applications/${id}`),
create: (data) => api.post('/insurance/applications', data),
update: (id, data) => api.put(`/insurance/applications/${id}`, data),
review: (id, data) => api.patch(`/insurance/applications/${id}/review`, data),
updateStatus: (id, data) => api.put(`/insurance/applications/${id}/status`, data),
delete: (id) => api.delete(`/insurance/applications/${id}`)
delete: (id) => api.delete(`/insurance/applications/${id}`),
getStats: () => api.get('/insurance/applications-stats'),
getCategories: () => api.get('/insurance/categories'),
export: (params) => api.get('/insurance/applications/export', { params, responseType: 'blob' })
}
export const policyAPI = {
@@ -113,13 +119,48 @@ export const supervisionTaskApi = {
export const installationTaskApi = {
getList: (params) => api.get('/installation-tasks', { params }),
create: (data) => api.post('/installation-tasks', data),
update: (data) => api.put(`/installation-tasks/${data.id}`, data),
update: (id, data) => api.put(`/installation-tasks/${id}`, data),
delete: (id) => api.delete(`/installation-tasks/${id}`),
getDetail: (id) => api.get(`/installation-tasks/${id}`),
batchDelete: (ids) => api.post('/installation-tasks/batch-delete', { ids }),
batchUpdateStatus: (data) => api.post('/installation-tasks/batch-update-status', data),
export: (params) => api.get('/installation-tasks/export/excel', { params, responseType: 'blob' }),
getStats: () => api.get('/installation-tasks/statistics/summary')
getById: (id) => api.get(`/installation-tasks/${id}`),
updateStatus: (id, data) => api.patch(`/installation-tasks/${id}/status`, data),
getStats: () => api.get('/installation-tasks/stats'),
assign: (id, data) => api.post(`/installation-tasks/${id}/assign`, data),
complete: (id, data) => api.post(`/installation-tasks/${id}/complete`, data),
getHistory: (id) => api.get(`/installation-tasks/${id}/history`)
}
// 生资保险相关API
export const livestockTypeApi = {
getList: (params) => api.get('/livestock-types', { params }),
create: (data) => api.post('/livestock-types', data),
update: (id, data) => api.put(`/livestock-types/${id}`, data),
delete: (id, data) => api.delete(`/livestock-types/${id}`, data),
getById: (id) => api.get(`/livestock-types/${id}`),
updateStatus: (id, data) => api.patch(`/livestock-types/${id}/status`, data),
batchUpdateStatus: (data) => api.patch('/livestock-types/batch-status', data)
}
export const livestockPolicyApi = {
getList: (params) => api.get('/livestock-policies', { params }),
create: (data) => api.post('/livestock-policies', data),
update: (id, data) => api.put(`/livestock-policies/${id}`, data),
delete: (id) => api.delete(`/livestock-policies/${id}`),
getById: (id) => api.get(`/livestock-policies/${id}`),
updateStatus: (id, data) => api.patch(`/livestock-policies/${id}/status`, data),
getStats: () => api.get('/livestock-policies/stats'),
getLivestockTypes: () => api.get('/livestock-types?status=active')
}
export const livestockClaimApi = {
getList: (params) => api.get('/livestock-claims', { params }),
create: (data) => api.post('/livestock-claims', data),
update: (id, data) => api.put(`/livestock-claims/${id}`, data),
delete: (id) => api.delete(`/livestock-claims/${id}`),
getById: (id) => api.get(`/livestock-claims/${id}`),
approve: (id, data) => api.post(`/livestock-claims/${id}/approve`, data),
reject: (id, data) => api.post(`/livestock-claims/${id}/reject`, data),
updatePaymentStatus: (id, data) => api.patch(`/livestock-claims/${id}/payment`, data),
getStats: () => api.get('/livestock-claims/stats')
}
export default api

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,509 @@
<template>
<div class="completed-task-management">
<div class="page-header">
<h1>监管任务结项管理</h1>
<p>查看和管理已完成的监管任务</p>
</div>
<!-- 搜索和筛选区域 -->
<div class="search-section">
<a-row :gutter="16">
<a-col :span="6">
<a-input
v-model:value="searchForm.taskName"
placeholder="请输入任务名称"
allow-clear
>
<template #prefix>
<SearchOutlined />
</template>
</a-input>
</a-col>
<a-col :span="6">
<a-select
v-model:value="searchForm.status"
placeholder="请选择状态"
allow-clear
style="width: 100%"
>
<a-select-option value="completed">已完成</a-select-option>
<a-select-option value="archived">已归档</a-select-option>
</a-select>
</a-col>
<a-col :span="6">
<a-range-picker
v-model:value="searchForm.dateRange"
style="width: 100%"
placeholder="['开始日期', '结束日期']"
/>
</a-col>
<a-col :span="6">
<a-space>
<a-button type="primary" @click="handleSearch">
<SearchOutlined />
搜索
</a-button>
<a-button @click="handleReset">
<ReloadOutlined />
重置
</a-button>
</a-space>
</a-col>
</a-row>
</div>
<!-- 统计卡片 -->
<div class="stats-section">
<a-row :gutter="16">
<a-col :span="6">
<a-card>
<a-statistic
title="总结项任务"
:value="stats.total"
:value-style="{ color: '#3f8600' }"
>
<template #prefix>
<CheckCircleOutlined />
</template>
</a-statistic>
</a-card>
</a-col>
<a-col :span="6">
<a-card>
<a-statistic
title="本月结项"
:value="stats.thisMonth"
:value-style="{ color: '#1890ff' }"
>
<template #prefix>
<CalendarOutlined />
</template>
</a-statistic>
</a-card>
</a-col>
<a-col :span="6">
<a-card>
<a-statistic
title="已归档任务"
:value="stats.archived"
:value-style="{ color: '#722ed1' }"
>
<template #prefix>
<FolderOutlined />
</template>
</a-statistic>
</a-card>
</a-col>
<a-col :span="6">
<a-card>
<a-statistic
title="平均处理时长"
:value="stats.avgDuration"
suffix="天"
:value-style="{ color: '#fa8c16' }"
>
<template #prefix>
<ClockCircleOutlined />
</template>
</a-statistic>
</a-card>
</a-col>
</a-row>
</div>
<!-- 任务列表 -->
<div class="table-section">
<a-table
:columns="columns"
:data-source="taskList"
:loading="loading"
:pagination="pagination"
@change="handleTableChange"
row-key="id"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'status'">
<a-tag :color="getStatusColor(record.status)">
{{ getStatusText(record.status) }}
</a-tag>
</template>
<template v-else-if="column.key === 'priority'">
<a-tag :color="getPriorityColor(record.priority)">
{{ getPriorityText(record.priority) }}
</a-tag>
</template>
<template v-else-if="column.key === 'duration'">
{{ record.duration }}
</template>
<template v-else-if="column.key === 'action'">
<a-space>
<a-button type="link" size="small" @click="handleView(record)">
<EyeOutlined />
查看
</a-button>
<a-button type="link" size="small" @click="handleDownload(record)">
<DownloadOutlined />
下载报告
</a-button>
<a-dropdown>
<template #overlay>
<a-menu>
<a-menu-item key="archive" @click="handleArchive(record)">
<FolderOutlined />
归档
</a-menu-item>
<a-menu-item key="export" @click="handleExport(record)">
<ExportOutlined />
导出
</a-menu-item>
</a-menu>
</template>
<a-button type="link" size="small">
更多
<DownOutlined />
</a-button>
</a-dropdown>
</a-space>
</template>
</template>
</a-table>
</div>
<!-- 任务详情模态框 -->
<a-modal
v-model:open="detailModalVisible"
title="任务详情"
width="800px"
:footer="null"
>
<div v-if="selectedTask" class="task-detail">
<a-descriptions :column="2" bordered>
<a-descriptions-item label="任务名称">
{{ selectedTask.taskName }}
</a-descriptions-item>
<a-descriptions-item label="任务编号">
{{ selectedTask.taskCode }}
</a-descriptions-item>
<a-descriptions-item label="优先级">
<a-tag :color="getPriorityColor(selectedTask.priority)">
{{ getPriorityText(selectedTask.priority) }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="状态">
<a-tag :color="getStatusColor(selectedTask.status)">
{{ getStatusText(selectedTask.status) }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="创建时间">
{{ selectedTask.createdAt }}
</a-descriptions-item>
<a-descriptions-item label="完成时间">
{{ selectedTask.completedAt }}
</a-descriptions-item>
<a-descriptions-item label="负责人">
{{ selectedTask.assignee }}
</a-descriptions-item>
<a-descriptions-item label="处理时长">
{{ selectedTask.duration }}
</a-descriptions-item>
<a-descriptions-item label="任务描述" :span="2">
{{ selectedTask.description }}
</a-descriptions-item>
<a-descriptions-item label="完成备注" :span="2">
{{ selectedTask.completionNotes }}
</a-descriptions-item>
</a-descriptions>
</div>
</a-modal>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { message } from 'ant-design-vue'
import {
SearchOutlined,
ReloadOutlined,
CheckCircleOutlined,
CalendarOutlined,
FolderOutlined,
ClockCircleOutlined,
EyeOutlined,
DownloadOutlined,
ExportOutlined,
DownOutlined
} from '@ant-design/icons-vue'
// 响应式数据
const loading = ref(false)
const taskList = ref([])
const detailModalVisible = ref(false)
const selectedTask = ref(null)
// 搜索表单
const searchForm = reactive({
taskName: '',
status: undefined,
dateRange: undefined
})
// 统计数据
const stats = reactive({
total: 0,
thisMonth: 0,
archived: 0,
avgDuration: 0
})
// 分页配置
const pagination = reactive({
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total, range) => `${range[0]}-${range[1]} 条,共 ${total}`
})
// 表格列配置
const columns = [
{
title: '任务编号',
dataIndex: 'taskCode',
key: 'taskCode',
width: 120
},
{
title: '任务名称',
dataIndex: 'taskName',
key: 'taskName',
ellipsis: true
},
{
title: '优先级',
dataIndex: 'priority',
key: 'priority',
width: 100
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
width: 100
},
{
title: '负责人',
dataIndex: 'assignee',
key: 'assignee',
width: 120
},
{
title: '完成时间',
dataIndex: 'completedAt',
key: 'completedAt',
width: 150
},
{
title: '处理时长',
dataIndex: 'duration',
key: 'duration',
width: 100
},
{
title: '操作',
key: 'action',
width: 200,
fixed: 'right'
}
]
// 获取状态颜色
const getStatusColor = (status) => {
const colorMap = {
completed: 'green',
archived: 'purple'
}
return colorMap[status] || 'default'
}
// 获取状态文本
const getStatusText = (status) => {
const textMap = {
completed: '已完成',
archived: '已归档'
}
return textMap[status] || status
}
// 获取优先级颜色
const getPriorityColor = (priority) => {
const colorMap = {
high: 'red',
medium: 'orange',
low: 'blue'
}
return colorMap[priority] || 'default'
}
// 获取优先级文本
const getPriorityText = (priority) => {
const textMap = {
high: '高',
medium: '中',
low: '低'
}
return textMap[priority] || priority
}
// 搜索处理
const handleSearch = () => {
pagination.current = 1
fetchTaskList()
}
// 重置搜索
const handleReset = () => {
Object.keys(searchForm).forEach(key => {
searchForm[key] = key === 'status' ? undefined : ''
})
pagination.current = 1
fetchTaskList()
}
// 表格变化处理
const handleTableChange = (pag) => {
pagination.current = pag.current
pagination.pageSize = pag.pageSize
fetchTaskList()
}
// 查看详情
const handleView = (record) => {
selectedTask.value = record
detailModalVisible.value = true
}
// 下载报告
const handleDownload = (record) => {
message.info(`正在下载 ${record.taskName} 的报告...`)
// 这里实现下载逻辑
}
// 归档任务
const handleArchive = (record) => {
message.success(`任务 ${record.taskName} 已归档`)
fetchTaskList()
}
// 导出任务
const handleExport = (record) => {
message.info(`正在导出 ${record.taskName} 的数据...`)
// 这里实现导出逻辑
}
// 获取任务列表
const fetchTaskList = async () => {
loading.value = true
try {
// 模拟API调用
await new Promise(resolve => setTimeout(resolve, 1000))
// 模拟数据
const mockData = [
{
id: 1,
taskCode: 'RT001',
taskName: '农场设备安全检查',
priority: 'high',
status: 'completed',
assignee: '张三',
createdAt: '2024-01-15',
completedAt: '2024-01-20',
duration: 5,
description: '对农场所有设备进行安全检查,确保设备正常运行',
completionNotes: '检查完成发现3处需要维修的设备已安排维修'
},
{
id: 2,
taskCode: 'RT002',
taskName: '环境监测数据审核',
priority: 'medium',
status: 'archived',
assignee: '李四',
createdAt: '2024-01-10',
completedAt: '2024-01-18',
duration: 8,
description: '审核上月环境监测数据,确保数据准确性',
completionNotes: '数据审核完成,所有数据符合标准'
}
]
taskList.value = mockData
pagination.total = mockData.length
// 更新统计数据
stats.total = 156
stats.thisMonth = 23
stats.archived = 89
stats.avgDuration = 6.5
} catch (error) {
message.error('获取任务列表失败')
} finally {
loading.value = false
}
}
// 组件挂载时获取数据
onMounted(() => {
fetchTaskList()
})
</script>
<style scoped>
.completed-task-management {
padding: 24px;
}
.page-header {
margin-bottom: 24px;
}
.page-header h1 {
margin: 0 0 8px 0;
font-size: 24px;
font-weight: 600;
}
.page-header p {
margin: 0;
color: #666;
}
.search-section {
background: #fff;
padding: 24px;
border-radius: 8px;
margin-bottom: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.stats-section {
margin-bottom: 16px;
}
.stats-section .ant-card {
text-align: center;
}
.table-section {
background: #fff;
padding: 24px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.task-detail .ant-descriptions {
margin-top: 16px;
}
</style>

View File

@@ -67,24 +67,19 @@
size="middle"
:scroll="{ x: 1800 }"
>
<!-- 安装状态列 -->
<template #installationStatus="{ record }">
<a-tag :color="getInstallationStatusColor(record.installationStatus)">
{{ record.installationStatus }}
</a-tag>
</template>
<!-- 时间列格式化 -->
<template #taskGeneratedTime="{ record }">
<span>{{ formatDateTime(record.taskGeneratedTime) }}</span>
</template>
<template #installationCompletedTime="{ record }">
<span>{{ formatDateTime(record.installationCompletedTime) }}</span>
</template>
<!-- 操作列 -->
<template #action="{ record }">
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'installationStatus'">
<a-tag :color="getInstallationStatusColor(record.installationStatus)">
{{ record.installationStatus }}
</a-tag>
</template>
<template v-else-if="column.key === 'taskGeneratedTime'">
<span>{{ formatDateTime(record.taskGeneratedTime) }}</span>
</template>
<template v-else-if="column.key === 'installationCompletedTime'">
<span>{{ formatDateTime(record.installationCompletedTime) }}</span>
</template>
<template v-else-if="column.key === 'action'">
<a-space>
<a-button type="link" size="small" @click="handleView(record)">
查看
@@ -101,6 +96,7 @@
</a-button>
</a-popconfirm>
</a-space>
</template>
</template>
</a-table>
</div>
@@ -205,29 +201,25 @@ const columns = [
title: '安装状态',
dataIndex: 'installationStatus',
key: 'installationStatus',
width: 100,
slots: { customRender: 'installationStatus' }
width: 100
},
{
title: '生成安装任务时间',
dataIndex: 'taskGeneratedTime',
key: 'taskGeneratedTime',
width: 160,
slots: { customRender: 'taskGeneratedTime' }
width: 160
},
{
title: '安装完成生效时间',
dataIndex: 'installationCompletedTime',
key: 'installationCompletedTime',
width: 160,
slots: { customRender: 'installationCompletedTime' }
width: 160
},
{
title: '操作',
key: 'action',
width: 200,
fixed: 'right',
slots: { customRender: 'action' }
fixed: 'right'
}
];

View File

@@ -1,13 +1,13 @@
<template>
<div class="insurance-type-management">
<a-page-header
title="保险类型管理"
sub-title="管理系统支持的保险产品类型"
title="险种管理"
sub-title="管理系统支持的保险产品险种"
>
<template #extra>
<a-button type="primary" @click="showModal">
<plus-outlined />
新增类型
新增险种
</a-button>
</template>
</a-page-header>
@@ -15,22 +15,36 @@
<!-- 搜索区域 -->
<a-card style="margin-top: 16px">
<a-form layout="inline" :model="searchForm">
<a-form-item label="类型名称">
<a-form-item label="险种名称">
<a-input
v-model:value="searchForm.name"
placeholder="请输入类型名称"
placeholder="请输入险种名称"
@pressEnter="handleSearch"
/>
</a-form-item>
<a-form-item label="状态">
<a-form-item label="适用范围">
<a-input
v-model:value="searchForm.applicable_scope"
placeholder="请输入适用范围"
@pressEnter="handleSearch"
/>
</a-form-item>
<a-form-item label="服务区域">
<a-input
v-model:value="searchForm.service_area"
placeholder="请输入服务区域"
@pressEnter="handleSearch"
/>
</a-form-item>
<a-form-item label="在线状态">
<a-select
v-model:value="searchForm.status"
placeholder="请选择状态"
v-model:value="searchForm.online_status"
placeholder="请选择在线状态"
style="width: 120px"
>
<a-select-option value="">全部</a-select-option>
<a-select-option value="active">启用</a-select-option>
<a-select-option value="inactive">禁用</a-select-option>
<a-select-option :value="true">在线</a-select-option>
<a-select-option :value="false">离线</a-select-option>
</a-select>
</a-form-item>
<a-form-item>
@@ -46,7 +60,7 @@
</a-form>
</a-card>
<!-- 类型表格 -->
<!-- 险种表格 -->
<a-card style="margin-top: 16px">
<a-table
:columns="columns"
@@ -54,28 +68,35 @@
:loading="loading"
:pagination="pagination"
@change="handleTableChange"
:scroll="{ x: 1500 }"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'status'">
<a-tag :color="record.status === 'active' ? 'green' : 'red'">
{{ record.status === 'active' ? '启用' : '禁用' }}
</a-tag>
<template v-if="column.key === 'online_status'">
<a-switch
:checked="record.online_status"
@change="(checked) => handleToggleOnlineStatus(record, checked)"
checked-children="在线"
un-checked-children="离线"
/>
</template>
<template v-else-if="column.key === 'coverage_type'">
<span>{{ getCoverageTypeText(record.coverage_type) }}</span>
<template v-else-if="column.key === 'premium_price'">
<span>{{ record.premium_price ? `¥${record.premium_price}` : '-' }}</span>
</template>
<template v-else-if="column.key === 'experience_period'">
<span>{{ record.experience_period ? `${record.experience_period}个月` : '-' }}</span>
</template>
<template v-else-if="column.key === 'insurance_period'">
<span>{{ record.insurance_period ? `${record.insurance_period}个月` : '-' }}</span>
</template>
<template v-else-if="column.key === 'product_time'">
<span>{{ record.product_time ? formatDate(record.product_time) : '-' }}</span>
</template>
<template v-else-if="column.key === 'action'">
<a-space>
<a-button size="small" @click="handleEdit(record)">编辑</a-button>
<a-button
size="small"
:type="record.status === 'active' ? 'danger' : 'primary'"
@click="handleToggleStatus(record)"
>
{{ record.status === 'active' ? '禁用' : '启用' }}
</a-button>
<a-button size="small" @click="handleView(record)">详情</a-button>
<a-popconfirm
title="确定要删除这个保险类型吗?"
title="确定要删除这个险种吗?"
@confirm="handleDelete(record.id)"
>
<a-button size="small" danger>删除</a-button>
@@ -88,11 +109,13 @@
<!-- 新增/编辑模态框 -->
<a-modal
v-model:visible="modalVisible"
:title="modalTitle"
width="600px"
@ok="handleModalOk"
@cancel="handleModalCancel"
v-model:open="modalVisible"
:title="isEdit ? '编辑险种' : '新增险种'"
:ok-text="isEdit ? '更新' : '创建'"
cancel-text="取消"
@ok="handleSubmit"
@cancel="handleCancel"
width="1000px"
>
<a-form
ref="formRef"
@@ -102,76 +125,107 @@
>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="类型名称" name="name">
<a-input v-model:value="formState.name" placeholder="请输入类型名称" />
<a-form-item label="险种名称" name="name">
<a-input v-model:value="formState.name" placeholder="请输入险种名称" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="类型代码" name="code">
<a-input v-model:value="formState.code" placeholder="请输入类型代码" />
<a-form-item label="适用范围" name="applicable_scope">
<a-input v-model:value="formState.applicable_scope" placeholder="请输入适用范围" />
</a-form-item>
</a-col>
</a-row>
<a-form-item label="描述" name="description">
<a-form-item label="险种描述" name="description">
<a-textarea
v-model:value="formState.description"
placeholder="请输入类型描述"
placeholder="请输入险种描述"
:rows="3"
/>
</a-form-item>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="保障类型" name="coverage_type">
<a-select v-model:value="formState.coverage_type" placeholder="请选择保障类型">
<a-select-option value="life">人寿保险</a-select-option>
<a-select-option value="health">健康保险</a-select-option>
<a-select-option value="property">财产保险</a-select-option>
<a-select-option value="accident">意外保险</a-select-option>
<a-select-option value="travel">旅行保险</a-select-option>
</a-select>
<a-col :span="8">
<a-form-item label="体验期" name="experience_period">
<a-input-number
v-model:value="formState.experience_period"
placeholder="请输入体验期(月)"
:min="0"
style="width: 100%"
addon-after="个月"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="状态" name="status">
<a-select v-model:value="formState.status" placeholder="请选择状态">
<a-select-option value="active">启用</a-select-option>
<a-select-option value="inactive">禁用</a-select-option>
</a-select>
<a-col :span="8">
<a-form-item label="保险期间" name="insurance_period">
<a-input-number
v-model:value="formState.insurance_period"
placeholder="请输入保险期间(月)"
:min="0"
style="width: 100%"
addon-after="个月"
/>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="保费价格" name="premium_price">
<a-input-number
v-model:value="formState.premium_price"
placeholder="请输入保费价格"
:min="0"
:precision="2"
style="width: 100%"
addon-before="¥"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="最小保额" name="min_coverage">
<a-input-number
v-model:value="formState.min_coverage"
:min="0"
:step="1000"
style="width: 100%"
placeholder="最小保额"
/>
<a-form-item label="服务区域" name="service_area">
<a-input v-model:value="formState.service_area" placeholder="请输入服务区域" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="最大保额" name="max_coverage">
<a-input-number
v-model:value="formState.max_coverage"
:min="0"
:step="1000"
<a-form-item label="上架时间" name="product_time">
<a-date-picker
v-model:value="formState.product_time"
placeholder="请选择上架时间"
style="width: 100%"
placeholder="最大保额"
format="YYYY-MM-DD HH:mm:ss"
show-time
/>
</a-form-item>
</a-col>
</a-row>
<a-form-item label="保费计算规则" name="premium_rules">
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="在线状态" name="online_status">
<a-switch
v-model:checked="formState.online_status"
checked-children="在线"
un-checked-children="离线"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="排序" name="sort_order">
<a-input-number
v-model:value="formState.sort_order"
placeholder="请输入排序值"
:min="0"
style="width: 100%"
/>
</a-form-item>
</a-col>
</a-row>
<a-form-item label="备注" name="remarks">
<a-textarea
v-model:value="formState.premium_rules"
placeholder="请输入保费计算规则JSON格式"
v-model:value="formState.remarks"
placeholder="请输入备注信息"
:rows="3"
/>
</a-form-item>
@@ -183,6 +237,8 @@
<script setup>
import { ref, reactive, onMounted, computed } from 'vue'
import { message } from 'ant-design-vue'
import dayjs from 'dayjs'
import { insuranceTypeAPI } from '@/utils/api'
import {
PlusOutlined,
SearchOutlined,
@@ -191,24 +247,30 @@ import {
const loading = ref(false)
const modalVisible = ref(false)
const editingId = ref(null)
const isEdit = ref(false)
const typeList = ref([])
const formRef = ref()
const searchForm = reactive({
name: '',
status: ''
applicable_scope: '',
service_area: '',
online_status: ''
})
const formState = reactive({
id: null,
name: '',
code: '',
description: '',
coverage_type: '',
status: 'active',
min_coverage: null,
max_coverage: null,
premium_rules: ''
applicable_scope: '',
experience_period: null,
insurance_period: null,
premium_price: null,
service_area: '',
product_time: null,
online_status: true,
sort_order: 0,
remarks: ''
})
const pagination = reactive({
@@ -222,49 +284,54 @@ const pagination = reactive({
const columns = [
{
title: 'ID',
dataIndex: 'id',
key: 'id',
width: 80
},
{
title: '类型名称',
title: '险种名称',
dataIndex: 'name',
key: 'name'
key: 'name',
width: 150,
fixed: 'left'
},
{
title: '类型代码',
dataIndex: 'code',
key: 'code'
title: '适用范围',
dataIndex: 'applicable_scope',
key: 'applicable_scope',
width: 150
},
{
title: '保障类型',
key: 'coverage_type',
dataIndex: 'coverage_type'
title: '体验期',
dataIndex: 'experience_period',
key: 'experience_period',
width: 100
},
{
title: '最小保额',
dataIndex: 'min_coverage',
key: 'min_coverage',
render: (text) => text ? `¥${text.toLocaleString()}` : '-'
title: '保险期间',
dataIndex: 'insurance_period',
key: 'insurance_period',
width: 100
},
{
title: '最大保额',
dataIndex: 'max_coverage',
key: 'max_coverage',
render: (text) => text ? `¥${text.toLocaleString()}` : '-'
title: '保费价格',
dataIndex: 'premium_price',
key: 'premium_price',
width: 120
},
{
title: '状态',
key: 'status',
dataIndex: 'status'
title: '服务区域',
dataIndex: 'service_area',
key: 'service_area',
width: 150
},
{
title: '创建时间',
dataIndex: 'created_at',
key: 'created_at',
title: '上架时间',
dataIndex: 'product_time',
key: 'product_time',
width: 180
},
{
title: '在线状态',
dataIndex: 'online_status',
key: 'online_status',
width: 100
},
{
title: '操作',
key: 'action',
@@ -274,25 +341,18 @@ const columns = [
]
const rules = {
name: [{ required: true, message: '请输入类型名称' }],
code: [{ required: true, message: '请输入类型代码' }],
coverage_type: [{ required: true, message: '请选择保障类型' }],
status: [{ required: true, message: '请选择状态' }]
name: [{ required: true, message: '请输入险种名称' }],
applicable_scope: [{ required: true, message: '请输入适用范围' }],
experience_period: [{ required: true, message: '请输入体验期' }],
insurance_period: [{ required: true, message: '请输入保险期间' }],
premium_price: [{ required: true, message: '请输入保费价格' }],
service_area: [{ required: true, message: '请输入服务区域' }]
}
const modalTitle = computed(() => {
return editingId.value ? '编辑保险类型' : '新增保险类型'
})
const getCoverageTypeText = (type) => {
const types = {
life: '人寿保险',
health: '健康保险',
property: '财产保险',
accident: '意外保险',
travel: '旅行保险'
}
return types[type] || type
// 格式化日期
const formatDate = (date) => {
if (!date) return '-'
return dayjs(date).format('YYYY-MM-DD HH:mm:ss')
}
const loadInsuranceTypes = async () => {
@@ -304,41 +364,17 @@ const loadInsuranceTypes = async () => {
...searchForm
}
// 这里应该是实际的API调用
// const response = await insuranceTypeAPI.getList(params)
// typeList.value = response.data.list
// pagination.total = response.data.total
const response = await insuranceTypeAPI.getList(params)
// 模拟数据
typeList.value = [
{
id: 1,
name: '综合意外险',
code: 'ACCIDENT_001',
description: '提供全面的意外伤害保障',
coverage_type: 'accident',
status: 'active',
min_coverage: 100000,
max_coverage: 500000,
premium_rules: '{\"base_rate\": 0.001, \"age_factor\": 1.2}',
created_at: '2024-01-01 10:00:00'
},
{
id: 2,
name: '终身寿险',
code: 'LIFE_001',
description: '提供终身的人寿保障',
coverage_type: 'life',
status: 'active',
min_coverage: 500000,
max_coverage: 2000000,
premium_rules: '{\"base_rate\": 0.0005, \"age_factor\": 1.5}',
created_at: '2024-01-02 14:30:00'
}
]
pagination.total = 2
if (response.status === 'success') {
typeList.value = response.data.list
pagination.total = response.data.total
} else {
message.error(response.message || '加载险种列表失败')
}
} catch (error) {
message.error('加载保险类型列表失败')
message.error('加载险种列表失败')
console.error('Error loading insurance types:', error)
} finally {
loading.value = false
}
@@ -350,8 +386,12 @@ const handleSearch = () => {
}
const resetSearch = () => {
searchForm.name = ''
searchForm.status = ''
Object.assign(searchForm, {
name: '',
applicable_scope: '',
service_area: '',
online_status: ''
})
handleSearch()
}
@@ -362,76 +402,119 @@ const handleTableChange = (pag) => {
}
const showModal = () => {
editingId.value = null
isEdit.value = false
Object.assign(formState, {
id: null,
name: '',
code: '',
description: '',
coverage_type: '',
status: 'active',
min_coverage: null,
max_coverage: null,
premium_rules: ''
applicable_scope: '',
experience_period: null,
insurance_period: null,
premium_price: null,
service_area: '',
product_time: null,
online_status: true,
sort_order: 0,
remarks: ''
})
modalVisible.value = true
}
const handleEdit = (record) => {
editingId.value = record.id
isEdit.value = true
Object.assign(formState, {
id: record.id,
name: record.name,
code: record.code,
description: record.description,
coverage_type: record.coverage_type,
status: record.status,
min_coverage: record.min_coverage,
max_coverage: record.max_coverage,
premium_rules: record.premium_rules
applicable_scope: record.applicable_scope,
experience_period: record.experience_period,
insurance_period: record.insurance_period,
premium_price: record.premium_price,
service_area: record.service_area,
product_time: record.product_time ? dayjs(record.product_time) : null,
online_status: record.online_status,
sort_order: record.sort_order,
remarks: record.remarks
})
modalVisible.value = true
}
const handleModalOk = async () => {
const handleView = (record) => {
// 查看详情功能
message.info('查看详情功能待实现')
}
const handleSubmit = async () => {
try {
await formRef.value.validate()
if (editingId.value) {
// await insuranceTypeAPI.update(editingId.value, formState)
message.success('保险类型更新成功')
const submitData = {
...formState,
product_time: formState.product_time ? formState.product_time.format('YYYY-MM-DD HH:mm:ss') : null
}
if (isEdit.value) {
const response = await insuranceTypeAPI.update(formState.id, submitData)
if (response.status === 'success') {
message.success('险种更新成功')
} else {
message.error(response.message || '险种更新失败')
}
} else {
// await insuranceTypeAPI.create(formState)
message.success('保险类型创建成功')
const response = await insuranceTypeAPI.create(submitData)
if (response.status === 'success') {
message.success('险种创建成功')
} else {
message.error(response.message || '险种创建失败')
}
}
modalVisible.value = false
loadInsuranceTypes()
} catch (error) {
console.log('表单验证失败', error)
if (error.response?.data?.message) {
message.error(error.response.data.message)
} else {
console.log('表单验证失败', error)
}
}
}
const handleModalCancel = () => {
const handleCancel = () => {
modalVisible.value = false
}
const handleToggleStatus = async (record) => {
const handleToggleOnlineStatus = async (record, checked) => {
try {
const newStatus = record.status === 'active' ? 'inactive' : 'active'
// await insuranceTypeAPI.update(record.id, { status: newStatus })
message.success('状态更新成功')
loadInsuranceTypes()
const response = await insuranceTypeAPI.updateStatus(record.id, {
online_status: checked
})
if (response.status === 'success') {
message.success('在线状态更新成功')
loadInsuranceTypes()
} else {
message.error(response.message || '在线状态更新失败')
}
} catch (error) {
message.error('状态更新失败')
message.error('在线状态更新失败')
console.error('Error updating online status:', error)
}
}
const handleDelete = async (id) => {
try {
// await insuranceTypeAPI.delete(id)
message.success('保险类型删除成功')
loadInsuranceTypes()
const response = await insuranceTypeAPI.delete(id)
if (response.status === 'success') {
message.success('险种删除成功')
loadInsuranceTypes()
} else {
message.error(response.message || '险种删除失败')
}
} catch (error) {
message.error('保险类型删除失败')
message.error('险种删除失败')
console.error('Error deleting insurance type:', error)
}
}

View File

@@ -0,0 +1,747 @@
<template>
<div class="livestock-policy-management">
<div class="page-header">
<h2>生资保单管理</h2>
<a-button type="primary" @click="showCreateModal">
<PlusOutlined />
新建保单
</a-button>
</div>
<!-- 搜索筛选区域 -->
<div class="search-section">
<a-row :gutter="16">
<a-col :span="6">
<a-input
v-model:value="searchForm.policy_no"
placeholder="保单编号"
allow-clear
/>
</a-col>
<a-col :span="6">
<a-input
v-model:value="searchForm.farmer_name"
placeholder="农户姓名"
allow-clear
/>
</a-col>
<a-col :span="6">
<a-select
v-model:value="searchForm.policy_status"
placeholder="保单状态"
allow-clear
style="width: 100%"
>
<a-select-option value="draft">草稿</a-select-option>
<a-select-option value="active">生效</a-select-option>
<a-select-option value="expired">过期</a-select-option>
<a-select-option value="cancelled">已取消</a-select-option>
</a-select>
</a-col>
<a-col :span="6">
<a-select
v-model:value="searchForm.payment_status"
placeholder="支付状态"
allow-clear
style="width: 100%"
>
<a-select-option value="unpaid">未支付</a-select-option>
<a-select-option value="paid">已支付</a-select-option>
<a-select-option value="partial">部分支付</a-select-option>
</a-select>
</a-col>
</a-row>
<a-row :gutter="16" style="margin-top: 16px">
<a-col :span="8">
<a-range-picker
v-model:value="searchForm.dateRange"
placeholder="['开始日期', '结束日期']"
style="width: 100%"
/>
</a-col>
<a-col :span="8">
<a-button type="primary" @click="handleSearch">
<SearchOutlined />
搜索
</a-button>
<a-button style="margin-left: 8px" @click="handleReset">
重置
</a-button>
</a-col>
</a-row>
</div>
<!-- 数据表格 -->
<div class="table-section">
<a-table
:columns="columns"
:data-source="tableData"
:loading="loading"
:pagination="pagination"
@change="handleTableChange"
row-key="id"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'policy_status'">
<a-tag :color="getStatusColor(record.policy_status)">
{{ getStatusText(record.policy_status) }}
</a-tag>
</template>
<template v-else-if="column.key === 'payment_status'">
<a-tag :color="getPaymentStatusColor(record.payment_status)">
{{ getPaymentStatusText(record.payment_status) }}
</a-tag>
</template>
<template v-else-if="column.key === 'total_value'">
¥{{ record.total_value?.toLocaleString() }}
</template>
<template v-else-if="column.key === 'premium_amount'">
¥{{ record.premium_amount?.toLocaleString() }}
</template>
<template v-else-if="column.key === 'action'">
<a-space>
<a-button type="link" size="small" @click="viewDetail(record)">
查看
</a-button>
<a-button type="link" size="small" @click="editRecord(record)">
编辑
</a-button>
<a-dropdown>
<template #overlay>
<a-menu @click="handleMenuClick($event, record)">
<a-menu-item key="activate" v-if="record.policy_status === 'draft'">
激活保单
</a-menu-item>
<a-menu-item key="cancel" v-if="['draft', 'active'].includes(record.policy_status)">
取消保单
</a-menu-item>
<a-menu-item key="payment" v-if="record.payment_status === 'unpaid'">
标记已支付
</a-menu-item>
</a-menu>
</template>
<a-button type="link" size="small">
更多 <DownOutlined />
</a-button>
</a-dropdown>
</a-space>
</template>
</template>
</a-table>
</div>
<!-- 创建/编辑保单弹窗 -->
<a-modal
v-model:open="modalVisible"
:title="modalTitle"
width="800px"
@ok="handleSubmit"
@cancel="handleCancel"
>
<a-form
ref="formRef"
:model="formData"
:rules="formRules"
layout="vertical"
>
<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>
</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>
</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>
</a-col>
<a-col :span="12">
<a-form-item label="牲畜类型" name="livestock_type_id">
<a-select
v-model:value="formData.livestock_type_id"
placeholder="请选择牲畜类型"
@change="handleLivestockTypeChange"
>
<a-select-option
v-for="type in livestockTypes"
:key="type.id"
:value="type.id"
>
{{ type.name }}
</a-select-option>
</a-select>
</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>
<a-row :gutter="16">
<a-col :span="8">
<a-form-item label="牲畜数量" name="livestock_count">
<a-input-number
v-model:value="formData.livestock_count"
placeholder="请输入数量"
:min="1"
style="width: 100%"
@change="calculateAmounts"
/>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="单头价值" name="unit_value">
<a-input-number
v-model:value="formData.unit_value"
placeholder="请输入单头价值"
:min="0"
:precision="2"
style="width: 100%"
@change="calculateAmounts"
/>
</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>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="8">
<a-form-item label="总保额">
<a-input
:value="`¥${formData.total_value?.toLocaleString() || '0'}`"
disabled
/>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="保费金额">
<a-input
:value="`¥${formData.premium_amount?.toLocaleString() || '0'}`"
disabled
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="保险开始日期" name="start_date">
<a-date-picker
v-model:value="formData.start_date"
placeholder="请选择开始日期"
style="width: 100%"
@change="handleStartDateChange"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="保险结束日期" name="end_date">
<a-date-picker
v-model:value="formData.end_date"
placeholder="请选择结束日期"
style="width: 100%"
/>
</a-form-item>
</a-col>
</a-row>
<a-form-item label="备注" name="notes">
<a-textarea v-model:value="formData.notes" placeholder="请输入备注" :rows="3" />
</a-form-item>
</a-form>
</a-modal>
<!-- 详情查看弹窗 -->
<a-modal
v-model:open="detailVisible"
title="保单详情"
width="800px"
:footer="null"
>
<a-descriptions :column="2" bordered v-if="currentRecord">
<a-descriptions-item label="保单编号">{{ currentRecord.policy_no }}</a-descriptions-item>
<a-descriptions-item label="保单状态">
<a-tag :color="getStatusColor(currentRecord.policy_status)">
{{ 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.livestock_type?.name }}</a-descriptions-item>
<a-descriptions-item label="农户地址" :span="2">{{ currentRecord.farmer_address }}</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.premium_amount?.toLocaleString() }}</a-descriptions-item>
<a-descriptions-item label="支付状态">
<a-tag :color="getPaymentStatusColor(currentRecord.payment_status)">
{{ getPaymentStatusText(currentRecord.payment_status) }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="保险开始日期">{{ currentRecord.start_date }}</a-descriptions-item>
<a-descriptions-item label="保险结束日期">{{ currentRecord.end_date }}</a-descriptions-item>
<a-descriptions-item label="支付时间">{{ currentRecord.payment_date || '未支付' }}</a-descriptions-item>
<a-descriptions-item label="创建时间">{{ formatDateTime(currentRecord.created_at) }}</a-descriptions-item>
<a-descriptions-item label="备注" :span="2">{{ currentRecord.notes || '无' }}</a-descriptions-item>
</a-descriptions>
</a-modal>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, computed } from 'vue'
import { message } from 'ant-design-vue'
import dayjs from 'dayjs'
import {
PlusOutlined,
SearchOutlined,
DownOutlined
} from '@ant-design/icons-vue'
import { livestockPolicyApi } from '@/utils/api'
// 响应式数据
const loading = ref(false)
const tableData = ref([])
const modalVisible = ref(false)
const detailVisible = ref(false)
const currentRecord = ref(null)
const formRef = ref()
const livestockTypes = ref([])
// 搜索表单
const searchForm = reactive({
policy_no: '',
farmer_name: '',
policy_status: undefined,
payment_status: undefined,
dateRange: []
})
// 分页配置
const pagination = reactive({
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `${total} 条记录`
})
// 表单数据
const formData = reactive({
farmer_name: '',
farmer_phone: '',
farmer_id_card: '',
farmer_address: '',
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,
notes: ''
})
// 表单验证规则
const formRules = {
farmer_name: [{ required: true, message: '请输入农户姓名' }],
farmer_phone: [
{ required: true, message: '请输入农户电话' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码' }
],
farmer_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: '请输入农户地址' }],
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: '请选择保险结束日期' }]
}
// 计算属性
const modalTitle = computed(() => {
return currentRecord.value ? '编辑保单' : '新建保单'
})
// 表格列配置
const columns = [
{
title: '保单编号',
dataIndex: 'policy_no',
key: 'policy_no',
width: 150
},
{
title: '农户姓名',
dataIndex: 'farmer_name',
key: 'farmer_name',
width: 100
},
{
title: '农户电话',
dataIndex: 'farmer_phone',
key: 'farmer_phone',
width: 120
},
{
title: '牲畜类型',
dataIndex: ['livestock_type', 'name'],
key: 'livestock_type',
width: 100
},
{
title: '数量',
dataIndex: 'livestock_count',
key: 'livestock_count',
width: 80,
customRender: ({ text }) => `${text}`
},
{
title: '总保额',
dataIndex: 'total_value',
key: 'total_value',
width: 120
},
{
title: '保费金额',
dataIndex: 'premium_amount',
key: 'premium_amount',
width: 120
},
{
title: '保单状态',
dataIndex: 'policy_status',
key: 'policy_status',
width: 100
},
{
title: '支付状态',
dataIndex: 'payment_status',
key: 'payment_status',
width: 100
},
{
title: '保险期间',
key: 'insurance_period',
width: 180,
customRender: ({ record }) => `${record.start_date}${record.end_date}`
},
{
title: '操作',
key: 'action',
width: 150,
fixed: 'right'
}
]
// 方法
const fetchData = async () => {
try {
loading.value = true
const params = {
page: pagination.current,
limit: pagination.pageSize,
...searchForm
}
// 处理日期范围
if (searchForm.dateRange && searchForm.dateRange.length === 2) {
params.start_date = dayjs(searchForm.dateRange[0]).format('YYYY-MM-DD')
params.end_date = dayjs(searchForm.dateRange[1]).format('YYYY-MM-DD')
}
delete params.dateRange
const response = await livestockPolicyApi.getList(params)
if (response.code === 200) {
tableData.value = response.data.list
pagination.total = response.data.total
}
} catch (error) {
message.error('获取数据失败')
console.error('获取数据失败:', error)
} finally {
loading.value = false
}
}
const fetchLivestockTypes = async () => {
try {
const response = await livestockPolicyApi.getLivestockTypes()
if (response.code === 200) {
livestockTypes.value = response.data.list
}
} catch (error) {
console.error('获取牲畜类型失败:', error)
}
}
const handleSearch = () => {
pagination.current = 1
fetchData()
}
const handleReset = () => {
Object.keys(searchForm).forEach(key => {
if (key === 'dateRange') {
searchForm[key] = []
} else {
searchForm[key] = key.includes('status') ? undefined : ''
}
})
pagination.current = 1
fetchData()
}
const handleTableChange = (pag) => {
pagination.current = pag.current
pagination.pageSize = pag.pageSize
fetchData()
}
const showCreateModal = () => {
currentRecord.value = null
resetForm()
modalVisible.value = true
}
const viewDetail = (record) => {
currentRecord.value = record
detailVisible.value = true
}
const editRecord = (record) => {
currentRecord.value = record
Object.keys(formData).forEach(key => {
if (key === 'start_date' || key === 'end_date') {
formData[key] = record[key] ? dayjs(record[key]) : undefined
} else {
formData[key] = record[key]
}
})
modalVisible.value = true
}
const handleMenuClick = async ({ key }, record) => {
try {
switch (key) {
case 'activate':
await livestockPolicyApi.updateStatus(record.id, {
policy_status: 'active'
})
message.success('保单已激活')
fetchData()
break
case 'cancel':
await livestockPolicyApi.updateStatus(record.id, {
policy_status: 'cancelled'
})
message.success('保单已取消')
fetchData()
break
case 'payment':
await livestockPolicyApi.updateStatus(record.id, {
payment_status: 'paid'
})
message.success('支付状态已更新')
fetchData()
break
}
} catch (error) {
message.error('操作失败')
console.error('操作失败:', error)
}
}
const handleSubmit = async () => {
try {
await formRef.value.validate()
const submitData = { ...formData }
if (submitData.start_date) {
submitData.start_date = dayjs(submitData.start_date).format('YYYY-MM-DD')
}
if (submitData.end_date) {
submitData.end_date = dayjs(submitData.end_date).format('YYYY-MM-DD')
}
if (currentRecord.value) {
await livestockPolicyApi.update(currentRecord.value.id, submitData)
message.success('保单更新成功')
} else {
await livestockPolicyApi.create(submitData)
message.success('保单创建成功')
}
modalVisible.value = false
fetchData()
} catch (error) {
if (error.errorFields) {
message.error('请检查表单填写')
} else {
message.error('操作失败')
console.error('操作失败:', error)
}
}
}
const handleCancel = () => {
modalVisible.value = false
resetForm()
}
const resetForm = () => {
Object.keys(formData).forEach(key => {
if (key === 'total_value' || key === 'premium_amount') {
formData[key] = 0
} 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
calculateAmounts()
}
}
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
}
}
const handleStartDateChange = (date) => {
if (date) {
// 自动设置结束日期为一年后
formData.end_date = dayjs(date).add(1, 'year').subtract(1, 'day')
}
}
// 状态相关方法
const getStatusColor = (status) => {
const colors = {
draft: 'default',
active: 'success',
expired: 'warning',
cancelled: 'error'
}
return colors[status] || 'default'
}
const getStatusText = (status) => {
const texts = {
draft: '草稿',
active: '生效',
expired: '过期',
cancelled: '已取消'
}
return texts[status] || status
}
const getPaymentStatusColor = (status) => {
const colors = {
unpaid: 'error',
paid: 'success',
partial: 'warning'
}
return colors[status] || 'default'
}
const getPaymentStatusText = (status) => {
const texts = {
unpaid: '未支付',
paid: '已支付',
partial: '部分支付'
}
return texts[status] || status
}
const formatDateTime = (dateTime) => {
return dateTime ? dayjs(dateTime).format('YYYY-MM-DD HH:mm:ss') : ''
}
// 生命周期
onMounted(() => {
fetchData()
fetchLivestockTypes()
})
</script>
<style scoped>
.livestock-policy-management {
padding: 24px;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
}
.page-header h2 {
margin: 0;
font-size: 20px;
font-weight: 600;
}
.search-section {
background: #fff;
padding: 24px;
border-radius: 8px;
margin-bottom: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.table-section {
background: #fff;
padding: 24px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
:deep(.ant-table) {
font-size: 14px;
}
:deep(.ant-table-thead > tr > th) {
background: #fafafa;
font-weight: 600;
}
:deep(.ant-descriptions-item-label) {
font-weight: 600;
background: #fafafa;
}
</style>

View File

@@ -64,34 +64,18 @@
size="middle"
:scroll="{ x: 1500 }"
>
<!-- 状态列 -->
<template #status="{ record }">
<a-tag :color="getStatusColor(record.status)">
{{ getStatusText(record.status) }}
</a-tag>
</template>
<!-- 任务类型列 -->
<template #taskType="{ record }">
<a-tag :color="getTaskTypeColor(record.taskType)">
{{ getTaskTypeText(record.taskType) }}
</a-tag>
</template>
<!-- 优先级列 -->
<template #priority="{ record }">
<a-tag :color="getPriorityColor(record.priority)">
{{ getPriorityText(record.priority) }}
</a-tag>
</template>
<!-- 金额列 -->
<template #amount="{ record }">
<span>{{ formatAmount(record.applicableAmount) }}</span>
</template>
<!-- 操作列 -->
<template #action="{ record }">
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'taskStatus'">
<a-tag :color="getStatusColor(record.taskStatus)">
{{ record.taskStatus }}
</a-tag>
</template>
<template v-else-if="column.key === 'priority'">
<a-tag :color="getPriorityColor(record.priority)">
{{ record.priority }}
</a-tag>
</template>
<template v-else-if="column.key === 'action'">
<a-space>
<a-button type="link" size="small" @click="handleView(record)">
查看
@@ -108,6 +92,7 @@
</a-button>
</a-popconfirm>
</a-space>
</template>
</template>
</a-table>
</div>
@@ -365,22 +350,19 @@ export default {
title: '任务状态',
dataIndex: 'taskStatus',
key: 'taskStatus',
width: 100,
slots: { customRender: 'status' }
width: 100
},
{
title: '优先级',
dataIndex: 'priority',
key: 'priority',
width: 80,
slots: { customRender: 'priority' }
width: 80
},
{
title: '操作',
key: 'action',
width: 150,
fixed: 'right',
slots: { customRender: 'action' }
fixed: 'right'
}
]

View File

@@ -54,26 +54,21 @@
row-key="id"
size="middle"
>
<!-- 任务状态列 -->
<template #taskStatus="{ record }">
<a-tag
:color="getStatusColor(record.taskStatus)"
>
{{ record.taskStatus }}
</a-tag>
</template>
<!-- 优先级列 -->
<template #priority="{ record }">
<a-tag
:color="getPriorityColor(record.priority)"
>
{{ record.priority }}
</a-tag>
</template>
<!-- 操作列 -->
<template #action="{ record }">
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'applicableSupplies'">
{{ formatApplicableSupplies(record.applicableSupplies) }}
</template>
<template v-else-if="column.key === 'taskStatus'">
<a-tag :color="getStatusColor(record.taskStatus)">
{{ record.taskStatus }}
</a-tag>
</template>
<template v-else-if="column.key === 'priority'">
<a-tag :color="getPriorityColor(record.priority)">
{{ record.priority }}
</a-tag>
</template>
<template v-else-if="column.key === 'action'">
<a-space>
<a-button
type="link"
@@ -102,6 +97,7 @@
</a-button>
</a-popconfirm>
</a-space>
</template>
</template>
</a-table>
</div>
@@ -378,15 +374,7 @@ const columns = [
title: '适用生资',
dataIndex: 'applicableSupplies',
key: 'applicableSupplies',
width: 120,
customRender: ({ record }) => {
try {
const supplies = JSON.parse(record.applicableSupplies || '[]')
return Array.isArray(supplies) ? supplies.join(', ') : record.applicableSupplies
} catch {
return record.applicableSupplies || '-'
}
}
width: 120
},
{
title: '监管生资数量',
@@ -397,7 +385,6 @@ const columns = [
{
title: '操作',
key: 'action',
slots: { customRender: 'action' },
width: 200,
fixed: 'right'
}
@@ -646,6 +633,16 @@ const downloadTemplate = () => {
message.info('模板下载功能开发中')
}
// 格式化适用生资
const formatApplicableSupplies = (supplies) => {
try {
const parsed = JSON.parse(supplies || '[]')
return Array.isArray(parsed) ? parsed.join(', ') : supplies || ''
} catch {
return supplies || ''
}
}
// 组件挂载时获取数据
onMounted(() => {
fetchTaskList()

View File

@@ -17,7 +17,7 @@ export default defineConfig(({ mode }) => {
port: parseInt(env.VITE_PORT) || 3004,
proxy: {
'/api': {
target: env.VITE_API_BASE_URL || 'http://localhost:3002',
target: env.VITE_API_BASE_URL || 'http://localhost:3000',
changeOrigin: true,
rewrite: (path) => path
}