完善保险端的前后端

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
}

View File

@@ -143,6 +143,69 @@ const swaggerDefinition = {
createdAt: { type: 'string', format: 'date-time', description: '创建时间' }
}
},
LivestockType: {
type: 'object',
properties: {
id: { type: 'integer', description: '牲畜类型ID' },
name: { type: 'string', description: '牲畜类型名称' },
description: { type: 'string', description: '牲畜类型描述' },
base_value: { type: 'number', format: 'float', description: '基础价值(单头)' },
premium_rate: { type: 'number', format: 'float', description: '基础保费费率' },
is_active: { type: 'boolean', description: '是否启用' },
created_at: { type: 'string', format: 'date-time', description: '创建时间' },
updated_at: { type: 'string', format: 'date-time', description: '更新时间' }
}
},
LivestockPolicy: {
type: 'object',
properties: {
id: { type: 'integer', description: '保单ID' },
policy_no: { type: 'string', description: '保单编号' },
farmer_name: { type: 'string', description: '农户姓名' },
farmer_phone: { type: 'string', description: '农户电话' },
farmer_id_card: { type: 'string', description: '农户身份证号' },
farmer_address: { type: 'string', description: '农户地址' },
livestock_type_id: { type: 'integer', description: '牲畜类型ID' },
livestock_count: { type: 'integer', description: '牲畜数量' },
unit_value: { type: 'number', format: 'float', description: '单头价值' },
total_value: { type: 'number', format: 'float', description: '总保额' },
premium_rate: { type: 'number', format: 'float', description: '保费费率' },
premium_amount: { type: 'number', format: 'float', description: '保费金额' },
start_date: { type: 'string', format: 'date', description: '保险开始日期' },
end_date: { type: 'string', format: 'date', description: '保险结束日期' },
policy_status: { type: 'string', enum: ['draft', 'active', 'expired', 'cancelled'], description: '保单状态' },
payment_status: { type: 'string', enum: ['unpaid', 'paid', 'partial'], description: '支付状态' },
payment_date: { type: 'string', format: 'date-time', description: '支付时间' },
notes: { type: 'string', description: '备注' },
created_at: { type: 'string', format: 'date-time', description: '创建时间' },
updated_at: { type: 'string', format: 'date-time', description: '更新时间' }
}
},
LivestockClaim: {
type: 'object',
properties: {
id: { type: 'integer', description: '理赔ID' },
claim_no: { type: 'string', description: '理赔编号' },
policy_id: { type: 'integer', description: '保单ID' },
claim_type: { type: 'string', enum: ['death', 'disease', 'accident', 'theft', 'natural_disaster'], description: '理赔类型' },
incident_date: { type: 'string', format: 'date', description: '事故发生日期' },
report_date: { type: 'string', format: 'date', description: '报案日期' },
affected_count: { type: 'integer', description: '受损数量' },
claim_amount: { type: 'number', format: 'float', description: '申请理赔金额' },
approved_amount: { type: 'number', format: 'float', description: '批准理赔金额' },
incident_description: { type: 'string', description: '事故描述' },
evidence_files: { type: 'string', description: '证据文件JSON格式' },
claim_status: { type: 'string', enum: ['pending', 'investigating', 'approved', 'rejected', 'paid'], description: '理赔状态' },
review_notes: { type: 'string', description: '审核备注' },
reviewed_at: { type: 'string', format: 'date-time', description: '审核时间' },
payment_status: { type: 'string', enum: ['unpaid', 'paid'], description: '支付状态' },
payment_date: { type: 'string', format: 'date-time', description: '支付时间' },
payment_method: { type: 'string', description: '支付方式' },
payment_reference: { type: 'string', description: '支付凭证号' },
created_at: { type: 'string', format: 'date-time', description: '创建时间' },
updated_at: { type: 'string', format: 'date-time', description: '更新时间' }
}
},
Error: {
type: 'object',
properties: {

View File

@@ -109,8 +109,12 @@ const login = async (req, res) => {
expires_in: 7 * 24 * 60 * 60 // 7天
}, '登录成功'));
} catch (error) {
console.error('登录错误:', error);
res.status(500).json(responseFormat.error('登录失败'));
console.error('登录错误详情:', {
message: error.message,
stack: error.stack,
name: error.name
});
res.status(500).json(responseFormat.error('登录失败: ' + error.message));
}
};

View File

@@ -5,19 +5,41 @@ const { Op } = require('sequelize');
// 获取保险申请列表
const getApplications = async (req, res) => {
try {
console.log('获取保险申请列表 - 请求参数:', req.query);
const {
name,
applicantName,
status,
insuranceType,
insuranceCategory,
applicationNumber,
dateRange,
page = 1,
limit = 10
} = req.query;
const whereClause = {};
const includeClause = [
{
model: InsuranceType,
as: 'insurance_type',
attributes: ['id', 'name', 'description'],
where: {}
},
{
model: User,
as: 'reviewer',
attributes: ['id', 'real_name', 'username']
}
];
// 姓名筛选
if (name) {
whereClause.customer_name = { [Op.like]: `%${name}%` };
// 申请单号筛选
if (applicationNumber) {
whereClause.application_no = { [Op.like]: `%${applicationNumber}%` };
}
// 投保人姓名筛选
if (applicantName) {
whereClause.customer_name = { [Op.like]: `%${applicantName}%` };
}
// 状态筛选
@@ -25,6 +47,16 @@ const getApplications = async (req, res) => {
whereClause.status = status;
}
// 参保险种筛选
if (insuranceType) {
includeClause[0].where.name = { [Op.like]: `%${insuranceType}%` };
}
// 参保类型筛选
if (insuranceCategory) {
whereClause.insurance_category = { [Op.like]: `%${insuranceCategory}%` };
}
// 日期范围筛选
if (dateRange && dateRange.start && dateRange.end) {
whereClause.application_date = {
@@ -34,25 +66,23 @@ const getApplications = async (req, res) => {
const offset = (page - 1) * limit;
// 如果没有保险类型筛选条件清空where条件
if (!insuranceType) {
delete includeClause[0].where;
}
console.log('查询条件:', { whereClause, includeClause, offset, limit: parseInt(limit) });
const { count, rows } = await InsuranceApplication.findAndCountAll({
where: whereClause,
include: [
{
model: InsuranceType,
as: 'insurance_type',
attributes: ['id', 'name', 'description']
},
{
model: User,
as: 'reviewer',
attributes: ['id', 'real_name', 'username']
}
],
include: includeClause,
order: [['created_at', 'DESC']],
offset,
limit: parseInt(limit)
});
console.log('查询结果:', { count, rowsLength: rows.length });
res.json(responseFormat.pagination(rows, {
page: parseInt(page),
limit: parseInt(limit),
@@ -67,19 +97,58 @@ const getApplications = async (req, res) => {
// 创建保险申请
const createApplication = async (req, res) => {
try {
const applicationData = req.body;
const {
customer_name,
customer_id_card,
customer_phone,
customer_address,
insurance_type_id,
insurance_category,
application_quantity,
application_amount,
remarks
} = req.body;
// 验证必填字段
if (!customer_name || !customer_id_card || !customer_phone || !customer_address || !insurance_type_id) {
return res.status(400).json(responseFormat.error('请填写所有必填字段'));
}
// 生成申请编号
const applicationNo = `INS${Date.now()}${Math.random().toString(36).substr(2, 6).toUpperCase()}`;
const applicationNo = `${new Date().getFullYear()}${(new Date().getMonth() + 1).toString().padStart(2, '0')}${new Date().getDate().toString().padStart(2, '0')}${Date.now().toString().slice(-6)}`;
const application = await InsuranceApplication.create({
...applicationData,
application_no: applicationNo
application_no: applicationNo,
customer_name,
customer_id_card,
customer_phone,
customer_address,
insurance_type_id,
insurance_category,
application_quantity: application_quantity || 1,
application_amount: application_amount || 0,
remarks,
status: 'pending'
});
res.status(201).json(responseFormat.created(application, '保险申请创建成功'));
// 返回创建的申请信息,包含关联数据
const createdApplication = await InsuranceApplication.findByPk(application.id, {
include: [
{
model: InsuranceType,
as: 'insurance_type',
attributes: ['id', 'name', 'description']
}
]
});
res.status(201).json(responseFormat.created(createdApplication, '保险申请创建成功'));
} catch (error) {
console.error('创建保险申请错误:', error);
if (error.name === 'SequelizeValidationError') {
const errorMessages = error.errors.map(err => err.message).join(', ');
return res.status(400).json(responseFormat.error(`数据验证失败: ${errorMessages}`));
}
res.status(500).json(responseFormat.error('创建保险申请失败'));
}
};
@@ -207,6 +276,117 @@ const getApplicationStats = async (req, res) => {
}
};
// 获取参保类型选项
const getInsuranceCategories = async (req, res) => {
try {
const categories = await InsuranceApplication.findAll({
attributes: ['insurance_category'],
where: {
insurance_category: {
[Op.ne]: null
}
},
group: ['insurance_category'],
raw: true
});
const categoryList = categories.map(item => item.insurance_category).filter(Boolean);
// 添加一些常用的参保类型
const defaultCategories = ['牛', '羊', '猪', '鸡', '鸭', '鹅'];
const allCategories = [...new Set([...defaultCategories, ...categoryList])];
res.json(responseFormat.success(allCategories, '获取参保类型选项成功'));
} catch (error) {
console.error('获取参保类型选项错误:', error);
res.status(500).json(responseFormat.error('获取参保类型选项失败'));
}
};
// 导出保险申请数据
const exportApplications = async (req, res) => {
try {
const {
page = 1,
limit = 1000,
applicantName,
insuranceType,
insuranceCategory,
status
} = req.query;
const where = {};
if (applicantName) {
where.applicant_name = { [Op.like]: `%${applicantName}%` };
}
if (insuranceType) {
where.insurance_type = insuranceType;
}
if (insuranceCategory) {
where.insurance_category = insuranceCategory;
}
if (status) {
where.status = status;
}
const applications = await InsuranceApplication.findAll({
where,
include: [
{
model: InsuranceType,
as: 'insuranceTypeInfo',
attributes: ['name', 'description']
},
{
model: User,
as: 'createdByUser',
attributes: ['username', 'real_name']
}
],
order: [['created_at', 'DESC']],
limit: parseInt(limit),
offset: (parseInt(page) - 1) * parseInt(limit)
});
// 简单的CSV格式导出
const csvHeader = '申请编号,申请人姓名,身份证号,联系电话,参保类型,保险类型,保险金额,保险期限,地址,状态,申请时间,备注\n';
const csvData = applications.map(app => {
const statusMap = {
'pending': '待审核',
'initial_approved': '初审通过',
'under_review': '复审中',
'approved': '已通过',
'rejected': '已拒绝'
};
return [
app.application_number || '',
app.applicant_name || '',
app.id_card || '',
app.phone || '',
app.insurance_category || '',
app.insurance_type || '',
app.insurance_amount || '',
app.insurance_period || '',
app.address || '',
statusMap[app.status] || app.status,
app.created_at ? new Date(app.created_at).toLocaleString('zh-CN') : '',
app.remarks || ''
].map(field => `"${String(field).replace(/"/g, '""')}"`).join(',');
}).join('\n');
const csvContent = csvHeader + csvData;
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
res.setHeader('Content-Disposition', `attachment; filename="insurance_applications_${new Date().toISOString().slice(0, 10)}.csv"`);
res.send('\uFEFF' + csvContent); // 添加BOM以支持中文
} catch (error) {
console.error('导出保险申请数据错误:', error);
res.status(500).json(responseFormat.error('导出保险申请数据失败'));
}
};
module.exports = {
getApplications,
createApplication,
@@ -214,5 +394,7 @@ module.exports = {
updateApplication,
reviewApplication,
deleteApplication,
getApplicationStats
getApplicationStats,
getInsuranceCategories,
exportApplications
};

View File

@@ -1,4 +1,5 @@
const { InsuranceType } = require('../models');
const { Op } = require('sequelize');
const responseFormat = require('../utils/response');
// 获取保险类型列表
@@ -28,10 +29,10 @@ const getInsuranceTypes = async (req, res) => {
page: parseInt(page),
pageSize: parseInt(pageSize),
pages: Math.ceil(count / pageSize)
}, '获取保险类型列表成功'));
}, '获取险种列表成功'));
} catch (error) {
console.error('获取保险类型列表错误:', error);
res.status(500).json(responseFormat.error('获取保险类型列表失败'));
console.error('获取险种列表错误:', error);
res.status(500).json(responseFormat.error('获取险种列表失败'));
}
};
@@ -52,12 +53,11 @@ const getInsuranceTypeById = async (req, res) => {
}
};
// 创建保险类型
// 创建险种
const createInsuranceType = async (req, res) => {
try {
const {
name,
code,
description,
coverage_amount_min,
coverage_amount_max,
@@ -68,39 +68,36 @@ const createInsuranceType = async (req, res) => {
// 检查名称是否已存在
const existingType = await InsuranceType.findOne({ where: { name } });
if (existingType) {
return res.status(400).json(responseFormat.error('保险类型名称已存在'));
}
// 检查代码是否已存在
const existingCode = await InsuranceType.findOne({ where: { code } });
if (existingCode) {
return res.status(400).json(responseFormat.error('保险类型代码已存在'));
return res.status(400).json(responseFormat.error('险种名称已存在'));
}
const insuranceType = await InsuranceType.create({
name,
code,
description,
coverage_amount_min,
coverage_amount_max,
premium_rate,
status
status,
created_by: req.user?.id
});
res.status(201).json(responseFormat.success(insuranceType, '创建保险类型成功'));
res.status(201).json(responseFormat.success(insuranceType, '创建险种成功'));
} catch (error) {
console.error('创建保险类型错误:', error);
res.status(500).json(responseFormat.error('创建保险类型失败'));
console.error('创建险种错误:', error);
if (error.name === 'SequelizeValidationError') {
const messages = error.errors.map(err => err.message);
return res.status(400).json(responseFormat.error(messages.join(', ')));
}
res.status(500).json(responseFormat.error('创建险种失败'));
}
};
// 更新保险类型
// 更新险种
const updateInsuranceType = async (req, res) => {
try {
const { id } = req.params;
const {
name,
code,
description,
coverage_amount_min,
coverage_amount_max,
@@ -110,67 +107,68 @@ const updateInsuranceType = async (req, res) => {
const insuranceType = await InsuranceType.findByPk(id);
if (!insuranceType) {
return res.status(404).json(responseFormat.error('保险类型不存在'));
return res.status(404).json(responseFormat.error('险种不存在'));
}
// 检查名称是否已被其他类型使用
if (name && name !== insuranceType.name) {
const existingType = await InsuranceType.findOne({ where: { name } });
const existingType = await InsuranceType.findOne({
where: {
name,
id: { [Op.ne]: id }
}
});
if (existingType) {
return res.status(400).json(responseFormat.error('保险类型名称已存在'));
}
}
// 检查代码是否已被其他类型使用
if (code && code !== insuranceType.code) {
const existingCode = await InsuranceType.findOne({ where: { code } });
if (existingCode) {
return res.status(400).json(responseFormat.error('保险类型代码已存在'));
return res.status(400).json(responseFormat.error('险种名称已存在'));
}
}
await insuranceType.update({
name: name || insuranceType.name,
code: code || insuranceType.code,
description: description || insuranceType.description,
coverage_amount_min: coverage_amount_min || insuranceType.coverage_amount_min,
coverage_amount_max: coverage_amount_max || insuranceType.coverage_amount_max,
premium_rate: premium_rate || insuranceType.premium_rate,
status: status || insuranceType.status
status: status || insuranceType.status,
updated_by: req.user?.id
});
res.json(responseFormat.success(insuranceType, '更新保险类型成功'));
res.json(responseFormat.success(insuranceType, '更新险种成功'));
} catch (error) {
console.error('更新保险类型错误:', error);
res.status(500).json(responseFormat.error('更新保险类型失败'));
console.error('更新险种错误:', error);
if (error.name === 'SequelizeValidationError') {
const messages = error.errors.map(err => err.message);
return res.status(400).json(responseFormat.error(messages.join(', ')));
}
res.status(500).json(responseFormat.error('更新险种失败'));
}
};
// 删除保险类型
// 删除险种
const deleteInsuranceType = async (req, res) => {
try {
const { id } = req.params;
const insuranceType = await InsuranceType.findByPk(id);
if (!insuranceType) {
return res.status(404).json(responseFormat.error('保险类型不存在'));
return res.status(404).json(responseFormat.error('险种不存在'));
}
// 检查是否有相关的保险申请或保单
const hasApplications = await insuranceType.countInsuranceApplications();
if (hasApplications > 0) {
return res.status(400).json(responseFormat.error('该保险类型下存在保险申请,无法删除'));
}
// const hasApplications = await insuranceType.countInsuranceApplications();
// if (hasApplications > 0) {
// return res.status(400).json(responseFormat.error('该险种下存在保险申请,无法删除'));
// }
await insuranceType.destroy();
res.json(responseFormat.success(null, '删除保险类型成功'));
res.json(responseFormat.success(null, '删除险种成功'));
} catch (error) {
console.error('删除保险类型错误:', error);
res.status(500).json(responseFormat.error('删除保险类型失败'));
console.error('删除险种错误:', error);
res.status(500).json(responseFormat.error('删除险种失败'));
}
};
// 更新保险类型状态
// 更新险种状态
const updateInsuranceTypeStatus = async (req, res) => {
try {
const { id } = req.params;
@@ -178,14 +176,14 @@ const updateInsuranceTypeStatus = async (req, res) => {
const insuranceType = await InsuranceType.findByPk(id);
if (!insuranceType) {
return res.status(404).json(responseFormat.error('保险类型不存在'));
return res.status(404).json(responseFormat.error('险种不存在'));
}
await insuranceType.update({ status });
res.json(responseFormat.success(insuranceType, '更新保险类型状态成功'));
await insuranceType.update({ status, updated_by: req.user?.id });
res.json(responseFormat.success(insuranceType, '更新险种状态成功'));
} catch (error) {
console.error('更新保险类型状态错误:', error);
res.status(500).json(responseFormat.error('更新保险类型状态失败'));
console.error('更新险种状态错误:', error);
res.status(500).json(responseFormat.error('更新险种状态失败'));
}
};

View File

@@ -0,0 +1,365 @@
const LivestockClaim = require('../models/LivestockClaim');
const LivestockPolicy = require('../models/LivestockPolicy');
const LivestockType = require('../models/LivestockType');
const User = require('../models/User');
const responseFormat = require('../utils/response');
const { Op } = require('sequelize');
// 获取生资理赔列表
const getLivestockClaims = async (req, res) => {
try {
const {
claim_no,
policy_no,
farmer_name,
claim_status,
claim_type,
start_date,
end_date,
page = 1,
limit = 10
} = req.query;
const whereClause = {};
// 理赔编号筛选
if (claim_no) {
whereClause.claim_no = { [Op.like]: `%${claim_no}%` };
}
// 理赔状态筛选
if (claim_status) {
whereClause.claim_status = claim_status;
}
// 理赔类型筛选
if (claim_type) {
whereClause.claim_type = claim_type;
}
// 日期范围筛选
if (start_date && end_date) {
whereClause.incident_date = {
[Op.between]: [new Date(start_date), new Date(end_date)]
};
}
const offset = (page - 1) * limit;
const { count, rows } = await LivestockClaim.findAndCountAll({
where: whereClause,
include: [
{
model: LivestockPolicy,
as: 'policy',
attributes: ['id', 'policy_no', 'farmer_name', 'farmer_phone', 'livestock_count'],
where: policy_no ? { policy_no: { [Op.like]: `%${policy_no}%` } } : {},
required: !!policy_no,
include: [
{
model: LivestockType,
as: 'livestock_type',
attributes: ['id', 'name']
}
]
},
{
model: User,
as: 'creator',
attributes: ['id', 'real_name', 'username']
},
{
model: User,
as: 'reviewer',
attributes: ['id', 'real_name', 'username']
}
],
order: [['created_at', 'DESC']],
offset,
limit: parseInt(limit)
});
// 如果有农户姓名筛选,需要在关联查询后再过滤
let filteredRows = rows;
if (farmer_name) {
filteredRows = rows.filter(claim =>
claim.policy && claim.policy.farmer_name.includes(farmer_name)
);
}
res.json(responseFormat.pagination(filteredRows, {
page: parseInt(page),
limit: parseInt(limit),
total: count
}, '获取生资理赔列表成功'));
} catch (error) {
console.error('获取生资理赔列表错误:', error);
res.status(500).json(responseFormat.error('获取生资理赔列表失败'));
}
};
// 创建生资理赔申请
const createLivestockClaim = async (req, res) => {
try {
const claimData = req.body;
// 验证保单是否存在且有效
const policy = await LivestockPolicy.findByPk(claimData.policy_id);
if (!policy) {
return res.status(400).json(responseFormat.error('保单不存在'));
}
if (policy.policy_status !== 'active') {
return res.status(400).json(responseFormat.error('保单状态无效,无法申请理赔'));
}
// 生成理赔编号
const claimNo = `LC${Date.now()}${Math.random().toString(36).substr(2, 6).toUpperCase()}`;
const claim = await LivestockClaim.create({
...claimData,
claim_no: claimNo,
claim_status: 'pending',
created_by: req.user?.id
});
// 获取完整的理赔信息(包含关联数据)
const fullClaim = await LivestockClaim.findByPk(claim.id, {
include: [
{
model: LivestockPolicy,
as: 'policy',
attributes: ['id', 'policy_no', 'farmer_name', 'farmer_phone'],
include: [
{
model: LivestockType,
as: 'livestock_type',
attributes: ['id', 'name']
}
]
}
]
});
res.status(201).json(responseFormat.created(fullClaim, '生资理赔申请创建成功'));
} catch (error) {
console.error('创建生资理赔申请错误:', error);
res.status(500).json(responseFormat.error('创建生资理赔申请失败'));
}
};
// 获取单个生资理赔详情
const getLivestockClaimById = async (req, res) => {
try {
const { id } = req.params;
const claim = await LivestockClaim.findByPk(id, {
include: [
{
model: LivestockPolicy,
as: 'policy',
include: [
{
model: LivestockType,
as: 'livestock_type',
attributes: ['id', 'name', 'description', 'base_value']
}
]
},
{
model: User,
as: 'creator',
attributes: ['id', 'real_name', 'username']
},
{
model: User,
as: 'reviewer',
attributes: ['id', 'real_name', 'username']
}
]
});
if (!claim) {
return res.status(404).json(responseFormat.error('生资理赔不存在'));
}
res.json(responseFormat.success(claim, '获取生资理赔详情成功'));
} catch (error) {
console.error('获取生资理赔详情错误:', error);
res.status(500).json(responseFormat.error('获取生资理赔详情失败'));
}
};
// 审核生资理赔
const reviewLivestockClaim = async (req, res) => {
try {
const { id } = req.params;
const { claim_status, review_notes, approved_amount } = req.body;
const claim = await LivestockClaim.findByPk(id);
if (!claim) {
return res.status(404).json(responseFormat.error('生资理赔不存在'));
}
if (claim.claim_status !== 'pending') {
return res.status(400).json(responseFormat.error('该理赔已经审核过了'));
}
const updateData = {
claim_status,
review_notes,
reviewed_by: req.user?.id,
reviewed_at: new Date()
};
if (claim_status === 'approved' && approved_amount) {
updateData.approved_amount = approved_amount;
}
await claim.update(updateData);
// 获取更新后的完整信息
const updatedClaim = await LivestockClaim.findByPk(id, {
include: [
{
model: LivestockPolicy,
as: 'policy',
attributes: ['id', 'policy_no', 'farmer_name', 'farmer_phone']
},
{
model: User,
as: 'reviewer',
attributes: ['id', 'real_name', 'username']
}
]
});
res.json(responseFormat.success(updatedClaim, '生资理赔审核成功'));
} catch (error) {
console.error('审核生资理赔错误:', error);
res.status(500).json(responseFormat.error('审核生资理赔失败'));
}
};
// 更新理赔支付状态
const updateLivestockClaimPayment = async (req, res) => {
try {
const { id } = req.params;
const { payment_status, payment_date, payment_method, payment_reference } = req.body;
const claim = await LivestockClaim.findByPk(id);
if (!claim) {
return res.status(404).json(responseFormat.error('生资理赔不存在'));
}
if (claim.claim_status !== 'approved') {
return res.status(400).json(responseFormat.error('只有已批准的理赔才能更新支付状态'));
}
const updateData = {
payment_status,
payment_date,
payment_method,
payment_reference,
updated_by: req.user?.id
};
await claim.update(updateData);
res.json(responseFormat.success(claim, '理赔支付状态更新成功'));
} catch (error) {
console.error('更新理赔支付状态错误:', error);
res.status(500).json(responseFormat.error('更新理赔支付状态失败'));
}
};
// 获取生资理赔统计
const getLivestockClaimStats = async (req, res) => {
try {
const { sequelize } = require('../config/database');
// 总理赔数
const totalClaims = await LivestockClaim.count();
// 待审核理赔数
const pendingClaims = await LivestockClaim.count({
where: { claim_status: 'pending' }
});
// 已批准理赔数
const approvedClaims = await LivestockClaim.count({
where: { claim_status: 'approved' }
});
// 已拒绝理赔数
const rejectedClaims = await LivestockClaim.count({
where: { claim_status: 'rejected' }
});
// 总理赔金额
const totalClaimAmount = await LivestockClaim.sum('claim_amount') || 0;
// 已支付理赔金额
const paidClaimAmount = await LivestockClaim.sum('approved_amount', {
where: {
claim_status: 'approved',
payment_status: 'paid'
}
}) || 0;
// 按理赔类型统计
const typeStats = await sequelize.query(`
SELECT
claim_type,
COUNT(*) as claim_count,
SUM(claim_amount) as total_claim_amount,
SUM(CASE WHEN claim_status = 'approved' THEN approved_amount ELSE 0 END) as total_approved_amount,
AVG(CASE WHEN claim_status = 'approved' THEN approved_amount ELSE NULL END) as avg_approved_amount
FROM livestock_claims
GROUP BY claim_type
ORDER BY claim_count DESC
`, { type: sequelize.QueryTypes.SELECT });
// 按月份统计最近12个月
const monthlyStats = await sequelize.query(`
SELECT
DATE_FORMAT(created_at, '%Y-%m') as month,
COUNT(*) as claim_count,
SUM(claim_amount) as total_claim_amount,
SUM(CASE WHEN claim_status = 'approved' THEN approved_amount ELSE 0 END) as total_approved_amount,
COUNT(CASE WHEN claim_status = 'approved' THEN 1 END) as approved_count
FROM livestock_claims
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 12 MONTH)
GROUP BY DATE_FORMAT(created_at, '%Y-%m')
ORDER BY month DESC
`, { type: sequelize.QueryTypes.SELECT });
const stats = {
overview: {
total_claims: totalClaims,
pending_claims: pendingClaims,
approved_claims: approvedClaims,
rejected_claims: rejectedClaims,
total_claim_amount: parseFloat(totalClaimAmount),
paid_claim_amount: parseFloat(paidClaimAmount),
approval_rate: totalClaims > 0 ? (approvedClaims / totalClaims * 100).toFixed(2) : 0
},
by_type: typeStats,
monthly: monthlyStats
};
res.json(responseFormat.success(stats, '获取生资理赔统计成功'));
} catch (error) {
console.error('获取生资理赔统计错误:', error);
res.status(500).json(responseFormat.error('获取生资理赔统计失败'));
}
};
module.exports = {
getLivestockClaims,
createLivestockClaim,
getLivestockClaimById,
reviewLivestockClaim,
updateLivestockClaimPayment,
getLivestockClaimStats
};

View File

@@ -0,0 +1,348 @@
const LivestockPolicy = require('../models/LivestockPolicy');
const LivestockType = require('../models/LivestockType');
const User = require('../models/User');
const responseFormat = require('../utils/response');
const { Op } = require('sequelize');
// 获取生资保单列表
const getLivestockPolicies = async (req, res) => {
try {
const {
policy_no,
farmer_name,
farmer_phone,
policy_status,
payment_status,
livestock_type_id,
start_date,
end_date,
page = 1,
limit = 10
} = req.query;
const whereClause = {};
// 保单编号筛选
if (policy_no) {
whereClause.policy_no = { [Op.like]: `%${policy_no}%` };
}
// 农户姓名筛选
if (farmer_name) {
whereClause.farmer_name = { [Op.like]: `%${farmer_name}%` };
}
// 农户电话筛选
if (farmer_phone) {
whereClause.farmer_phone = { [Op.like]: `%${farmer_phone}%` };
}
// 保单状态筛选
if (policy_status) {
whereClause.policy_status = policy_status;
}
// 支付状态筛选
if (payment_status) {
whereClause.payment_status = payment_status;
}
// 牲畜类型筛选
if (livestock_type_id) {
whereClause.livestock_type_id = livestock_type_id;
}
// 日期范围筛选
if (start_date && end_date) {
whereClause.start_date = {
[Op.between]: [new Date(start_date), new Date(end_date)]
};
}
const offset = (page - 1) * limit;
const { count, rows } = await LivestockPolicy.findAndCountAll({
where: whereClause,
include: [
{
model: LivestockType,
as: 'livestock_type',
attributes: ['id', 'name', 'description', 'unit_price_min', 'unit_price_max', 'premium_rate']
},
{
model: User,
as: 'creator',
attributes: ['id', 'real_name', 'username']
}
],
order: [['created_at', 'DESC']],
offset,
limit: parseInt(limit)
});
res.json(responseFormat.pagination(rows, {
page: parseInt(page),
limit: parseInt(limit),
total: count
}, '获取生资保单列表成功'));
} catch (error) {
console.error('获取生资保单列表错误:', error);
res.status(500).json(responseFormat.error('获取生资保单列表失败'));
}
};
// 创建生资保单
const createLivestockPolicy = async (req, res) => {
try {
const policyData = req.body;
// 生成保单编号
const policyNo = `LP${Date.now()}${Math.random().toString(36).substr(2, 6).toUpperCase()}`;
// 计算总保额和保费
const totalValue = policyData.livestock_count * policyData.unit_value;
const premiumAmount = totalValue * policyData.premium_rate;
const policy = await LivestockPolicy.create({
...policyData,
policy_no: policyNo,
total_value: totalValue,
premium_amount: premiumAmount,
created_by: req.user?.id
});
// 获取完整的保单信息(包含关联数据)
const fullPolicy = await LivestockPolicy.findByPk(policy.id, {
include: [
{
model: LivestockType,
as: 'livestock_type',
attributes: ['id', 'name', 'description', 'unit_price_min', 'unit_price_max', 'premium_rate']
}
]
});
res.status(201).json(responseFormat.created(fullPolicy, '生资保单创建成功'));
} catch (error) {
console.error('创建生资保单错误:', error);
res.status(500).json(responseFormat.error('创建生资保单失败'));
}
};
// 获取单个生资保单详情
const getLivestockPolicyById = async (req, res) => {
try {
const { id } = req.params;
const policy = await LivestockPolicy.findByPk(id, {
include: [
{
model: LivestockType,
as: 'livestock_type',
attributes: ['id', 'name', 'description', 'unit_price_min', 'unit_price_max', 'premium_rate']
},
{
model: User,
as: 'creator',
attributes: ['id', 'real_name', 'username']
},
{
model: User,
as: 'updater',
attributes: ['id', 'real_name', 'username']
}
]
});
if (!policy) {
return res.status(404).json(responseFormat.error('生资保单不存在'));
}
res.json(responseFormat.success(policy, '获取生资保单详情成功'));
} catch (error) {
console.error('获取生资保单详情错误:', error);
res.status(500).json(responseFormat.error('获取生资保单详情失败'));
}
};
// 更新生资保单
const updateLivestockPolicy = async (req, res) => {
try {
const { id } = req.params;
const updateData = req.body;
const policy = await LivestockPolicy.findByPk(id);
if (!policy) {
return res.status(404).json(responseFormat.error('生资保单不存在'));
}
// 如果更新了数量或单价,重新计算总保额和保费
if (updateData.livestock_count || updateData.unit_value || updateData.premium_rate) {
const livestockCount = updateData.livestock_count || policy.livestock_count;
const unitValue = updateData.unit_value || policy.unit_value;
const premiumRate = updateData.premium_rate || policy.premium_rate;
updateData.total_value = livestockCount * unitValue;
updateData.premium_amount = updateData.total_value * premiumRate;
}
updateData.updated_by = req.user?.id;
await policy.update(updateData);
// 获取更新后的完整信息
const updatedPolicy = await LivestockPolicy.findByPk(id, {
include: [
{
model: LivestockType,
as: 'livestock_type',
attributes: ['id', 'name', 'description', 'unit_price_min', 'unit_price_max', 'premium_rate']
}
]
});
res.json(responseFormat.success(updatedPolicy, '生资保单更新成功'));
} catch (error) {
console.error('更新生资保单错误:', error);
res.status(500).json(responseFormat.error('更新生资保单失败'));
}
};
// 更新生资保单状态
const updateLivestockPolicyStatus = async (req, res) => {
try {
const { id } = req.params;
const { policy_status, payment_status, payment_date } = req.body;
const policy = await LivestockPolicy.findByPk(id);
if (!policy) {
return res.status(404).json(responseFormat.error('生资保单不存在'));
}
const updateData = {
updated_by: req.user?.id
};
if (policy_status) updateData.policy_status = policy_status;
if (payment_status) updateData.payment_status = payment_status;
if (payment_date) updateData.payment_date = payment_date;
await policy.update(updateData);
res.json(responseFormat.success(policy, '生资保单状态更新成功'));
} catch (error) {
console.error('更新生资保单状态错误:', error);
res.status(500).json(responseFormat.error('更新生资保单状态失败'));
}
};
// 获取生资保单统计
const getLivestockPolicyStats = async (req, res) => {
try {
const { sequelize } = require('../config/database');
// 总保单数
const totalPolicies = await LivestockPolicy.count();
// 有效保单数
const activePolicies = await LivestockPolicy.count({
where: { policy_status: 'active' }
});
// 已支付保单数
const paidPolicies = await LivestockPolicy.count({
where: { payment_status: 'paid' }
});
// 总保费收入
const totalPremium = await LivestockPolicy.sum('premium_amount', {
where: { payment_status: 'paid' }
}) || 0;
// 总保额
const totalCoverage = await LivestockPolicy.sum('total_value', {
where: { policy_status: 'active' }
}) || 0;
// 按牲畜类型统计
const typeStats = await sequelize.query(`
SELECT
lt.name as livestock_type,
COUNT(lp.id) as policy_count,
SUM(lp.livestock_count) as total_livestock,
SUM(lp.total_value) as total_coverage,
SUM(lp.premium_amount) as total_premium
FROM livestock_policies lp
LEFT JOIN livestock_types lt ON lp.livestock_type_id = lt.id
WHERE lp.policy_status = 'active'
GROUP BY lt.id, lt.name
ORDER BY policy_count DESC
`, { type: sequelize.QueryTypes.SELECT });
// 按月份统计最近12个月
const monthlyStats = await sequelize.query(`
SELECT
DATE_FORMAT(created_at, '%Y-%m') as month,
COUNT(*) as policy_count,
SUM(premium_amount) as premium_amount,
SUM(total_value) as total_coverage
FROM livestock_policies
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 12 MONTH)
GROUP BY DATE_FORMAT(created_at, '%Y-%m')
ORDER BY month DESC
`, { type: sequelize.QueryTypes.SELECT });
const stats = {
overview: {
total_policies: totalPolicies,
active_policies: activePolicies,
paid_policies: paidPolicies,
total_premium: parseFloat(totalPremium),
total_coverage: parseFloat(totalCoverage),
payment_rate: totalPolicies > 0 ? (paidPolicies / totalPolicies * 100).toFixed(2) : 0
},
by_type: typeStats,
monthly: monthlyStats
};
res.json(responseFormat.success(stats, '获取生资保单统计成功'));
} catch (error) {
console.error('获取生资保单统计错误:', error);
res.status(500).json(responseFormat.error('获取生资保单统计失败'));
}
};
// 删除生资保单
const deleteLivestockPolicy = async (req, res) => {
try {
const { id } = req.params;
const policy = await LivestockPolicy.findByPk(id);
if (!policy) {
return res.status(404).json(responseFormat.error('生资保单不存在'));
}
// 检查保单状态,只有草稿状态的保单可以删除
if (policy.policy_status === 'active') {
return res.status(400).json(responseFormat.error('生效中的保单不能删除'));
}
// 物理删除保单
await policy.destroy();
res.json(responseFormat.success(null, '生资保单删除成功'));
} catch (error) {
console.error('删除生资保单错误:', error);
res.status(500).json(responseFormat.error('删除生资保单失败'));
}
};
module.exports = {
getLivestockPolicies,
createLivestockPolicy,
getLivestockPolicyById,
updateLivestockPolicy,
updateLivestockPolicyStatus,
deleteLivestockPolicy,
getLivestockPolicyStats
};

View File

@@ -0,0 +1,221 @@
const LivestockType = require('../models/LivestockType');
const User = require('../models/User');
const responseFormat = require('../utils/response');
const { Op } = require('sequelize');
// 获取牲畜类型列表
const getLivestockTypes = async (req, res) => {
try {
const {
name,
is_active,
page = 1,
limit = 10
} = req.query;
const whereClause = {};
// 名称筛选
if (name) {
whereClause.name = { [Op.like]: `%${name}%` };
}
// 状态筛选
if (is_active !== undefined) {
whereClause.is_active = is_active === 'true';
}
const offset = (page - 1) * limit;
const { count, rows } = await LivestockType.findAndCountAll({
where: whereClause,
order: [['created_at', 'DESC']],
offset,
limit: parseInt(limit)
});
res.json(responseFormat.pagination(rows, {
page: parseInt(page),
limit: parseInt(limit),
total: count
}, '获取牲畜类型列表成功'));
} catch (error) {
console.error('获取牲畜类型列表错误:', error);
res.status(500).json(responseFormat.error('获取牲畜类型列表失败'));
}
};
// 获取所有启用的牲畜类型(用于下拉选择)
const getActiveLivestockTypes = async (req, res) => {
try {
const types = await LivestockType.findAll({
where: { is_active: true },
attributes: ['id', 'name', 'description', 'base_value', 'premium_rate'],
order: [['name', 'ASC']]
});
res.json(responseFormat.success(types, '获取启用牲畜类型成功'));
} catch (error) {
console.error('获取启用牲畜类型错误:', error);
res.status(500).json(responseFormat.error('获取启用牲畜类型失败'));
}
};
// 创建牲畜类型
const createLivestockType = async (req, res) => {
try {
const typeData = req.body;
// 检查名称是否已存在
const existingType = await LivestockType.findOne({
where: { name: typeData.name }
});
if (existingType) {
return res.status(400).json(responseFormat.error('牲畜类型名称已存在'));
}
const type = await LivestockType.create({
...typeData,
created_by: req.user?.id
});
res.status(201).json(responseFormat.created(type, '牲畜类型创建成功'));
} catch (error) {
console.error('创建牲畜类型错误:', error);
if (error.name === 'SequelizeValidationError') {
const messages = error.errors.map(err => err.message);
return res.status(400).json(responseFormat.error(messages.join(', ')));
}
res.status(500).json(responseFormat.error('创建牲畜类型失败'));
}
};
// 获取单个牲畜类型详情
const getLivestockTypeById = async (req, res) => {
try {
const { id } = req.params;
const type = await LivestockType.findByPk(id);
if (!type) {
return res.status(404).json(responseFormat.error('牲畜类型不存在'));
}
res.json(responseFormat.success(type, '获取牲畜类型详情成功'));
} catch (error) {
console.error('获取牲畜类型详情错误:', error);
res.status(500).json(responseFormat.error('获取牲畜类型详情失败'));
}
};
// 更新牲畜类型
const updateLivestockType = async (req, res) => {
try {
const { id } = req.params;
const updateData = req.body;
const type = await LivestockType.findByPk(id);
if (!type) {
return res.status(404).json(responseFormat.error('牲畜类型不存在'));
}
// 如果更新名称,检查是否与其他记录重复
if (updateData.name && updateData.name !== type.name) {
const existingType = await LivestockType.findOne({
where: {
name: updateData.name,
id: { [Op.ne]: id }
}
});
if (existingType) {
return res.status(400).json(responseFormat.error('牲畜类型名称已存在'));
}
}
updateData.updated_by = req.user?.id;
await type.update(updateData);
res.json(responseFormat.success(type, '牲畜类型更新成功'));
} catch (error) {
console.error('更新牲畜类型错误:', error);
if (error.name === 'SequelizeValidationError') {
const messages = error.errors.map(err => err.message);
return res.status(400).json(responseFormat.error(messages.join(', ')));
}
res.status(500).json(responseFormat.error('更新牲畜类型失败'));
}
};
// 删除牲畜类型(软删除 - 设置为不启用)
const deleteLivestockType = async (req, res) => {
try {
const { id } = req.params;
const type = await LivestockType.findByPk(id);
if (!type) {
return res.status(404).json(responseFormat.error('牲畜类型不存在'));
}
// 检查是否有关联的保单
const LivestockPolicy = require('../models/LivestockPolicy');
const relatedPolicies = await LivestockPolicy.count({
where: { livestock_type_id: id }
});
if (relatedPolicies > 0) {
// 如果有关联保单,只能设置为不启用
await type.update({
is_active: false,
updated_by: req.user?.id
});
return res.json(responseFormat.success(null, '牲畜类型已设置为不启用(存在关联保单)'));
}
// 如果没有关联保单,可以物理删除
await type.destroy();
res.json(responseFormat.success(null, '牲畜类型删除成功'));
} catch (error) {
console.error('删除牲畜类型错误:', error);
res.status(500).json(responseFormat.error('删除牲畜类型失败'));
}
};
// 批量更新牲畜类型状态
const batchUpdateLivestockTypeStatus = async (req, res) => {
try {
const { ids, is_active } = req.body;
if (!Array.isArray(ids) || ids.length === 0) {
return res.status(400).json(responseFormat.error('请提供有效的ID列表'));
}
await LivestockType.update(
{
is_active,
updated_by: req.user?.id
},
{
where: { id: { [Op.in]: ids } }
}
);
res.json(responseFormat.success(null, '批量更新牲畜类型状态成功'));
} catch (error) {
console.error('批量更新牲畜类型状态错误:', error);
res.status(500).json(responseFormat.error('批量更新牲畜类型状态失败'));
}
};
module.exports = {
getLivestockTypes,
getActiveLivestockTypes,
createLivestockType,
getLivestockTypeById,
updateLivestockType,
deleteLivestockType,
batchUpdateLivestockTypeStatus
};

View File

@@ -0,0 +1,603 @@
const { sequelize } = require('../config/database');
const responseFormat = require('../utils/response');
const { Op } = require('sequelize');
/**
* 监管任务结项控制器
* 处理监管任务结项相关的业务逻辑
*/
// 获取监管任务结项列表
const getTaskCompletions = async (req, res) => {
try {
const {
application_no, // 申请编号
policy_no, // 保单号
product_name, // 产品名称
customer_name, // 客户名称
completion_date, // 结项日期
status, // 状态
reviewer_name, // 审核人员
page = 1,
limit = 10
} = req.query;
// 构建查询条件
let whereClause = '';
const params = [];
if (application_no) {
whereClause += ' AND rtc.application_no LIKE ?';
params.push(`%${application_no}%`);
}
if (policy_no) {
whereClause += ' AND rtc.policy_no LIKE ?';
params.push(`%${policy_no}%`);
}
if (product_name) {
whereClause += ' AND rtc.product_name LIKE ?';
params.push(`%${product_name}%`);
}
if (customer_name) {
whereClause += ' AND rtc.customer_name LIKE ?';
params.push(`%${customer_name}%`);
}
if (completion_date) {
whereClause += ' AND DATE(rtc.completion_date) = ?';
params.push(completion_date);
}
if (status) {
whereClause += ' AND rtc.status = ?';
params.push(status);
}
if (reviewer_name) {
whereClause += ' AND rtc.reviewer_name LIKE ?';
params.push(`%${reviewer_name}%`);
}
// 计算偏移量
const offset = (page - 1) * limit;
// 查询总数
const countQuery = `
SELECT COUNT(*) as total
FROM regulatory_task_completions rtc
WHERE 1=1 ${whereClause}
`;
const [countResult] = await sequelize.query(countQuery, {
replacements: params,
type: sequelize.QueryTypes.SELECT
});
// 查询数据
const dataQuery = `
SELECT
rtc.id,
rtc.application_no,
rtc.policy_no,
rtc.product_name,
rtc.customer_name,
rtc.customer_phone,
rtc.insurance_amount,
rtc.premium_amount,
rtc.start_date,
rtc.end_date,
rtc.completion_date,
rtc.status,
rtc.reviewer_name,
rtc.review_comments,
rtc.created_at,
rtc.updated_at
FROM regulatory_task_completions rtc
WHERE 1=1 ${whereClause}
ORDER BY rtc.created_at DESC
LIMIT ? OFFSET ?
`;
const results = await sequelize.query(dataQuery, {
replacements: [...params, parseInt(limit), offset],
type: sequelize.QueryTypes.SELECT
});
res.json(responseFormat.success({
list: results,
pagination: {
current: parseInt(page),
pageSize: parseInt(limit),
total: countResult.total,
totalPages: Math.ceil(countResult.total / limit)
}
}));
} catch (error) {
console.error('获取监管任务结项列表失败:', error);
res.status(500).json(responseFormat.error('获取监管任务结项列表失败'));
}
};
// 获取单个监管任务结项详情
const getTaskCompletionById = async (req, res) => {
try {
const { id } = req.params;
const query = `
SELECT
rtc.*,
(
SELECT JSON_ARRAYAGG(
JSON_OBJECT(
'id', rtca.id,
'file_name', rtca.file_name,
'file_path', rtca.file_path,
'file_size', rtca.file_size,
'file_type', rtca.file_type,
'upload_time', rtca.upload_time
)
)
FROM regulatory_task_completion_attachments rtca
WHERE rtca.completion_id = rtc.id
) as attachments,
(
SELECT JSON_ARRAYAGG(
JSON_OBJECT(
'id', rtcl.id,
'operation_type', rtcl.operation_type,
'operation_description', rtcl.operation_description,
'operator_name', rtcl.operator_name,
'operation_time', rtcl.operation_time,
'ip_address', rtcl.ip_address
)
)
FROM regulatory_task_completion_logs rtcl
WHERE rtcl.completion_id = rtc.id
ORDER BY rtcl.operation_time DESC
) as operation_logs
FROM regulatory_task_completions rtc
WHERE rtc.id = ?
`;
const [result] = await sequelize.query(query, {
replacements: [id],
type: sequelize.QueryTypes.SELECT
});
if (!result) {
return res.status(404).json(responseFormat.error('监管任务结项记录不存在'));
}
// 解析JSON字段
if (result.attachments) {
result.attachments = JSON.parse(result.attachments) || [];
} else {
result.attachments = [];
}
if (result.operation_logs) {
result.operation_logs = JSON.parse(result.operation_logs) || [];
} else {
result.operation_logs = [];
}
res.json(responseFormat.success(result));
} catch (error) {
console.error('获取监管任务结项详情失败:', error);
res.status(500).json(responseFormat.error('获取监管任务结项详情失败'));
}
};
// 创建监管任务结项记录
const createTaskCompletion = async (req, res) => {
const transaction = await sequelize.transaction();
try {
const {
application_no,
policy_no,
product_name,
customer_name,
customer_phone,
insurance_amount,
premium_amount,
start_date,
end_date,
completion_date,
status = 'pending',
review_comments,
attachments = []
} = req.body;
// 验证必填字段
if (!application_no || !policy_no || !product_name || !customer_name) {
return res.status(400).json(responseFormat.error('申请编号、保单号、产品名称、客户名称为必填项'));
}
// 检查申请编号是否已存在
const existingQuery = `
SELECT id FROM regulatory_task_completions
WHERE application_no = ? OR policy_no = ?
`;
const [existing] = await sequelize.query(existingQuery, {
replacements: [application_no, policy_no],
type: sequelize.QueryTypes.SELECT,
transaction
});
if (existing) {
await transaction.rollback();
return res.status(400).json(responseFormat.error('申请编号或保单号已存在'));
}
// 创建监管任务结项记录
const insertQuery = `
INSERT INTO regulatory_task_completions (
application_no, policy_no, product_name, customer_name, customer_phone,
insurance_amount, premium_amount, start_date, end_date, completion_date,
status, review_comments, created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
`;
const [insertResult] = await sequelize.query(insertQuery, {
replacements: [
application_no, policy_no, product_name, customer_name, customer_phone,
insurance_amount, premium_amount, start_date, end_date, completion_date,
status, review_comments
],
type: sequelize.QueryTypes.INSERT,
transaction
});
const completionId = insertResult;
// 创建附件记录
if (attachments && attachments.length > 0) {
for (const attachment of attachments) {
const attachmentQuery = `
INSERT INTO regulatory_task_completion_attachments (
completion_id, file_name, file_path, file_size, file_type, upload_time
) VALUES (?, ?, ?, ?, ?, NOW())
`;
await sequelize.query(attachmentQuery, {
replacements: [
completionId,
attachment.file_name,
attachment.file_path,
attachment.file_size,
attachment.file_type
],
type: sequelize.QueryTypes.INSERT,
transaction
});
}
}
// 记录操作日志
const logQuery = `
INSERT INTO regulatory_task_completion_logs (
completion_id, operation_type, operation_description,
operator_name, operation_time, ip_address
) VALUES (?, ?, ?, ?, NOW(), ?)
`;
await sequelize.query(logQuery, {
replacements: [
completionId,
'create',
'创建监管任务结项记录',
req.user?.real_name || '系统',
req.ip
],
type: sequelize.QueryTypes.INSERT,
transaction
});
await transaction.commit();
res.status(201).json(responseFormat.success({
id: completionId,
message: '监管任务结项记录创建成功'
}));
} catch (error) {
await transaction.rollback();
console.error('创建监管任务结项记录失败:', error);
res.status(500).json(responseFormat.error('创建监管任务结项记录失败'));
}
};
// 更新监管任务结项记录
const updateTaskCompletion = async (req, res) => {
const transaction = await sequelize.transaction();
try {
const { id } = req.params;
const {
application_no,
policy_no,
product_name,
customer_name,
customer_phone,
insurance_amount,
premium_amount,
start_date,
end_date,
completion_date,
status,
review_comments
} = req.body;
// 检查记录是否存在
const checkQuery = `SELECT id FROM regulatory_task_completions WHERE id = ?`;
const [existing] = await sequelize.query(checkQuery, {
replacements: [id],
type: sequelize.QueryTypes.SELECT,
transaction
});
if (!existing) {
await transaction.rollback();
return res.status(404).json(responseFormat.error('监管任务结项记录不存在'));
}
// 更新记录
const updateQuery = `
UPDATE regulatory_task_completions SET
application_no = ?, policy_no = ?, product_name = ?, customer_name = ?,
customer_phone = ?, insurance_amount = ?, premium_amount = ?,
start_date = ?, end_date = ?, completion_date = ?, status = ?,
review_comments = ?, updated_at = NOW()
WHERE id = ?
`;
await sequelize.query(updateQuery, {
replacements: [
application_no, policy_no, product_name, customer_name, customer_phone,
insurance_amount, premium_amount, start_date, end_date, completion_date,
status, review_comments, id
],
type: sequelize.QueryTypes.UPDATE,
transaction
});
// 记录操作日志
const logQuery = `
INSERT INTO regulatory_task_completion_logs (
completion_id, operation_type, operation_description,
operator_name, operation_time, ip_address
) VALUES (?, ?, ?, ?, NOW(), ?)
`;
await sequelize.query(logQuery, {
replacements: [
id,
'update',
'更新监管任务结项记录',
req.user?.real_name || '系统',
req.ip
],
type: sequelize.QueryTypes.INSERT,
transaction
});
await transaction.commit();
res.json(responseFormat.success({ message: '监管任务结项记录更新成功' }));
} catch (error) {
await transaction.rollback();
console.error('更新监管任务结项记录失败:', error);
res.status(500).json(responseFormat.error('更新监管任务结项记录失败'));
}
};
// 删除监管任务结项记录
const deleteTaskCompletion = async (req, res) => {
const transaction = await sequelize.transaction();
try {
const { id } = req.params;
// 检查记录是否存在
const checkQuery = `SELECT id FROM regulatory_task_completions WHERE id = ?`;
const [existing] = await sequelize.query(checkQuery, {
replacements: [id],
type: sequelize.QueryTypes.SELECT,
transaction
});
if (!existing) {
await transaction.rollback();
return res.status(404).json(responseFormat.error('监管任务结项记录不存在'));
}
// 删除相关附件记录
await sequelize.query(
'DELETE FROM regulatory_task_completion_attachments WHERE completion_id = ?',
{
replacements: [id],
type: sequelize.QueryTypes.DELETE,
transaction
}
);
// 删除相关日志记录
await sequelize.query(
'DELETE FROM regulatory_task_completion_logs WHERE completion_id = ?',
{
replacements: [id],
type: sequelize.QueryTypes.DELETE,
transaction
}
);
// 删除主记录
await sequelize.query(
'DELETE FROM regulatory_task_completions WHERE id = ?',
{
replacements: [id],
type: sequelize.QueryTypes.DELETE,
transaction
}
);
await transaction.commit();
res.json(responseFormat.success({ message: '监管任务结项记录删除成功' }));
} catch (error) {
await transaction.rollback();
console.error('删除监管任务结项记录失败:', error);
res.status(500).json(responseFormat.error('删除监管任务结项记录失败'));
}
};
// 审核监管任务结项记录
const reviewTaskCompletion = async (req, res) => {
const transaction = await sequelize.transaction();
try {
const { id } = req.params;
const { status, review_comments, reviewer_name } = req.body;
// 验证状态值
const validStatuses = ['pending', 'approved', 'rejected', 'completed'];
if (!validStatuses.includes(status)) {
return res.status(400).json(responseFormat.error('无效的状态值'));
}
// 检查记录是否存在
const checkQuery = `SELECT id, status FROM regulatory_task_completions WHERE id = ?`;
const [existing] = await sequelize.query(checkQuery, {
replacements: [id],
type: sequelize.QueryTypes.SELECT,
transaction
});
if (!existing) {
await transaction.rollback();
return res.status(404).json(responseFormat.error('监管任务结项记录不存在'));
}
// 更新审核状态
const updateQuery = `
UPDATE regulatory_task_completions SET
status = ?, review_comments = ?, reviewer_name = ?, updated_at = NOW()
WHERE id = ?
`;
await sequelize.query(updateQuery, {
replacements: [status, review_comments, reviewer_name, id],
type: sequelize.QueryTypes.UPDATE,
transaction
});
// 记录操作日志
const logQuery = `
INSERT INTO regulatory_task_completion_logs (
completion_id, operation_type, operation_description,
operator_name, operation_time, ip_address
) VALUES (?, ?, ?, ?, NOW(), ?)
`;
const operationDesc = `审核监管任务结项记录,状态变更为:${status}`;
await sequelize.query(logQuery, {
replacements: [
id,
'review',
operationDesc,
reviewer_name || req.user?.real_name || '系统',
req.ip
],
type: sequelize.QueryTypes.INSERT,
transaction
});
await transaction.commit();
res.json(responseFormat.success({ message: '监管任务结项记录审核成功' }));
} catch (error) {
await transaction.rollback();
console.error('审核监管任务结项记录失败:', error);
res.status(500).json(responseFormat.error('审核监管任务结项记录失败'));
}
};
// 获取监管任务结项统计数据
const getTaskCompletionStats = async (req, res) => {
try {
// 状态统计
const statusStatsQuery = `
SELECT
status,
COUNT(*) as count
FROM regulatory_task_completions
GROUP BY status
`;
const statusStats = await sequelize.query(statusStatsQuery, {
type: sequelize.QueryTypes.SELECT
});
// 月度统计
const monthlyStatsQuery = `
SELECT
DATE_FORMAT(completion_date, '%Y-%m') as month,
COUNT(*) as count,
SUM(insurance_amount) as total_amount
FROM regulatory_task_completions
WHERE completion_date >= DATE_SUB(NOW(), INTERVAL 12 MONTH)
GROUP BY DATE_FORMAT(completion_date, '%Y-%m')
ORDER BY month
`;
const monthlyStats = await sequelize.query(monthlyStatsQuery, {
type: sequelize.QueryTypes.SELECT
});
// 产品统计
const productStatsQuery = `
SELECT
product_name,
COUNT(*) as count,
SUM(insurance_amount) as total_amount
FROM regulatory_task_completions
GROUP BY product_name
ORDER BY count DESC
LIMIT 10
`;
const productStats = await sequelize.query(productStatsQuery, {
type: sequelize.QueryTypes.SELECT
});
res.json(responseFormat.success({
statusStats,
monthlyStats,
productStats
}));
} catch (error) {
console.error('获取监管任务结项统计数据失败:', error);
res.status(500).json(responseFormat.error('获取监管任务结项统计数据失败'));
}
};
module.exports = {
getTaskCompletions,
getTaskCompletionById,
createTaskCompletion,
updateTaskCompletion,
deleteTaskCompletion,
reviewTaskCompletion,
getTaskCompletionStats
};

View File

@@ -119,8 +119,8 @@ async function createAdminUser() {
// 创建admin用户
await sequelize.query(
`INSERT INTO users (username, password, real_name, email, phone, role_id, status)
VALUES (?, ?, ?, ?, ?, ?, ?)`,
`INSERT INTO users (username, password, real_name, email, phone, role_id, status, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, NOW(), NOW())`,
{
replacements: [
'admin',

View File

@@ -0,0 +1,755 @@
openapi: 3.0.0
info:
title: 参保申请管理API
description: 保险系统参保申请功能相关API接口文档
version: 1.0.0
contact:
name: 开发团队
email: dev@example.com
servers:
- url: http://localhost:3000/api
description: 开发环境
paths:
/insurance/applications:
get:
summary: 获取参保申请列表
description: 分页获取参保申请列表,支持多种筛选条件
tags:
- 参保申请
security:
- bearerAuth: []
parameters:
- name: page
in: query
description: 页码
required: false
schema:
type: integer
default: 1
minimum: 1
- name: limit
in: query
description: 每页数量
required: false
schema:
type: integer
default: 10
minimum: 1
maximum: 100
- name: application_no
in: query
description: 申请单号(模糊搜索)
required: false
schema:
type: string
- name: name
in: query
description: 投保人姓名(模糊搜索)
required: false
schema:
type: string
- name: status
in: query
description: 申请状态
required: false
schema:
type: string
enum: [pending, initial_approved, under_review, approved, rejected, paid]
- name: insurance_type_name
in: query
description: 参保险种名称(模糊搜索)
required: false
schema:
type: string
- name: insurance_category
in: query
description: 参保类型(模糊搜索)
required: false
schema:
type: string
- name: dateRange[start]
in: query
description: 申请时间范围开始
required: false
schema:
type: string
format: date
- name: dateRange[end]
in: query
description: 申请时间范围结束
required: false
schema:
type: string
format: date
responses:
'200':
description: 获取成功
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
example: true
message:
type: string
example: "获取保险申请列表成功"
data:
type: array
items:
$ref: '#/components/schemas/InsuranceApplication'
pagination:
$ref: '#/components/schemas/Pagination'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'500':
$ref: '#/components/responses/InternalServerError'
post:
summary: 创建参保申请
description: 创建新的参保申请
tags:
- 参保申请
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateInsuranceApplicationRequest'
responses:
'201':
description: 创建成功
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
example: true
message:
type: string
example: "保险申请创建成功"
data:
$ref: '#/components/schemas/InsuranceApplication'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'500':
$ref: '#/components/responses/InternalServerError'
/insurance/applications/{id}:
get:
summary: 获取参保申请详情
description: 根据ID获取单个参保申请的详细信息
tags:
- 参保申请
security:
- bearerAuth: []
parameters:
- name: id
in: path
required: true
description: 申请ID
schema:
type: integer
responses:
'200':
description: 获取成功
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
example: true
message:
type: string
example: "获取保险申请详情成功"
data:
$ref: '#/components/schemas/InsuranceApplication'
'404':
$ref: '#/components/responses/NotFound'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'500':
$ref: '#/components/responses/InternalServerError'
put:
summary: 更新参保申请
description: 更新参保申请信息
tags:
- 参保申请
security:
- bearerAuth: []
parameters:
- name: id
in: path
required: true
description: 申请ID
schema:
type: integer
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateInsuranceApplicationRequest'
responses:
'200':
description: 更新成功
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
example: true
message:
type: string
example: "保险申请更新成功"
data:
$ref: '#/components/schemas/InsuranceApplication'
'400':
$ref: '#/components/responses/BadRequest'
'404':
$ref: '#/components/responses/NotFound'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'500':
$ref: '#/components/responses/InternalServerError'
delete:
summary: 删除参保申请
description: 删除指定的参保申请
tags:
- 参保申请
security:
- bearerAuth: []
parameters:
- name: id
in: path
required: true
description: 申请ID
schema:
type: integer
responses:
'200':
description: 删除成功
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
example: true
message:
type: string
example: "保险申请删除成功"
'404':
$ref: '#/components/responses/NotFound'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'500':
$ref: '#/components/responses/InternalServerError'
/insurance/applications/{id}/review:
patch:
summary: 审核参保申请
description: 对参保申请进行审核操作
tags:
- 参保申请
security:
- bearerAuth: []
parameters:
- name: id
in: path
required: true
description: 申请ID
schema:
type: integer
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ReviewApplicationRequest'
responses:
'200':
description: 审核成功
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
example: true
message:
type: string
example: "保险申请审核成功"
data:
$ref: '#/components/schemas/InsuranceApplication'
'400':
$ref: '#/components/responses/BadRequest'
'404':
$ref: '#/components/responses/NotFound'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'500':
$ref: '#/components/responses/InternalServerError'
/insurance/applications-stats:
get:
summary: 获取参保申请统计
description: 获取各状态的申请数量统计
tags:
- 参保申请
security:
- bearerAuth: []
responses:
'200':
description: 获取成功
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
example: true
message:
type: string
example: "获取保险申请统计成功"
data:
type: object
properties:
pending:
type: integer
description: 待初审数量
example: 10
initial_approved:
type: integer
description: 初审通过待复核数量
example: 5
under_review:
type: integer
description: 已支付待复核数量
example: 3
approved:
type: integer
description: 已支付数量
example: 20
rejected:
type: integer
description: 已拒绝数量
example: 2
paid:
type: integer
description: 已支付数量
example: 15
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'500':
$ref: '#/components/responses/InternalServerError'
/insurance/categories:
get:
summary: 获取参保类型选项
description: 获取可选的参保类型列表
tags:
- 参保申请
security:
- bearerAuth: []
responses:
'200':
description: 获取成功
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
example: true
message:
type: string
example: "获取参保类型选项成功"
data:
type: array
items:
type: string
example: ["牛", "羊", "猪", "鸡", "鸭", "鹅"]
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'500':
$ref: '#/components/responses/InternalServerError'
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
schemas:
InsuranceApplication:
type: object
properties:
id:
type: integer
description: 申请ID
example: 1
application_no:
type: string
description: 申请单号
example: "20230814205416141"
customer_name:
type: string
description: 投保人姓名
example: "张三"
customer_id_card:
type: string
description: 身份证号(脱敏显示)
example: "420***********0091"
customer_phone:
type: string
description: 电话(脱敏显示)
example: "186****2563"
customer_address:
type: string
description: 参保地址
example: "哈哈哈哈哈哈"
insurance_type_id:
type: integer
description: 保险类型ID
example: 1
insurance_category:
type: string
description: 参保类型
example: "牛"
application_quantity:
type: integer
description: 申请数量
example: 50
application_amount:
type: number
format: decimal
description: 申请金额
example: 10000.00
status:
type: string
enum: [pending, initial_approved, under_review, approved, rejected, paid]
description: 申请状态
example: "pending"
remarks:
type: string
description: 备注
example: "无"
application_date:
type: string
format: date-time
description: 申请时间
example: "2023-08-14T20:54:16.000Z"
review_notes:
type: string
description: 审核备注
example: null
reviewer_id:
type: integer
description: 审核人ID
example: null
review_date:
type: string
format: date-time
description: 审核时间
example: null
documents:
type: array
description: 相关文档
items:
type: string
example: []
created_at:
type: string
format: date-time
description: 创建时间
example: "2023-08-14T20:54:16.000Z"
updated_at:
type: string
format: date-time
description: 更新时间
example: "2023-08-14T20:54:16.000Z"
insurance_type:
$ref: '#/components/schemas/InsuranceType'
reviewer:
$ref: '#/components/schemas/User'
InsuranceType:
type: object
properties:
id:
type: integer
description: 保险类型ID
example: 1
name:
type: string
description: 保险类型名称
example: "保险清示产品"
description:
type: string
description: 保险类型描述
example: "保险产品描述"
User:
type: object
properties:
id:
type: integer
description: 用户ID
example: 1
real_name:
type: string
description: 真实姓名
example: "李四"
username:
type: string
description: 用户名
example: "admin"
CreateInsuranceApplicationRequest:
type: object
required:
- customer_name
- customer_id_card
- customer_phone
- customer_address
- insurance_type_id
properties:
customer_name:
type: string
description: 投保人姓名
example: "张三"
minLength: 2
maxLength: 100
customer_id_card:
type: string
description: 身份证号
example: "420106199001010091"
pattern: '^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[0-9Xx]$'
customer_phone:
type: string
description: 电话号码
example: "18612345678"
pattern: '^1[3-9]\d{9}$'
customer_address:
type: string
description: 参保地址
example: "北京市北京市北京市测试区"
minLength: 5
maxLength: 255
insurance_type_id:
type: integer
description: 保险类型ID
example: 1
insurance_category:
type: string
description: 参保类型
example: "牛"
maxLength: 50
application_quantity:
type: integer
description: 申请数量
example: 50
minimum: 1
application_amount:
type: number
format: decimal
description: 申请金额
example: 10000.00
minimum: 0
remarks:
type: string
description: 备注
example: "特殊说明"
UpdateInsuranceApplicationRequest:
type: object
properties:
customer_name:
type: string
description: 投保人姓名
example: "张三"
minLength: 2
maxLength: 100
customer_id_card:
type: string
description: 身份证号
example: "420106199001010091"
pattern: '^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[0-9Xx]$'
customer_phone:
type: string
description: 电话号码
example: "18612345678"
pattern: '^1[3-9]\d{9}$'
customer_address:
type: string
description: 参保地址
example: "北京市北京市北京市测试区"
minLength: 5
maxLength: 255
insurance_type_id:
type: integer
description: 保险类型ID
example: 1
insurance_category:
type: string
description: 参保类型
example: "牛"
maxLength: 50
application_quantity:
type: integer
description: 申请数量
example: 50
minimum: 1
application_amount:
type: number
format: decimal
description: 申请金额
example: 10000.00
minimum: 0
remarks:
type: string
description: 备注
example: "特殊说明"
ReviewApplicationRequest:
type: object
required:
- status
properties:
status:
type: string
enum: [initial_approved, under_review, approved, rejected, paid]
description: 审核后的状态
example: "initial_approved"
review_notes:
type: string
description: 审核备注
example: "审核通过"
Pagination:
type: object
properties:
page:
type: integer
description: 当前页码
example: 1
limit:
type: integer
description: 每页数量
example: 10
total:
type: integer
description: 总记录数
example: 100
totalPages:
type: integer
description: 总页数
example: 10
responses:
BadRequest:
description: 请求参数错误
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
example: false
message:
type: string
example: "请求参数错误"
Unauthorized:
description: 未授权
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
example: false
message:
type: string
example: "未授权访问"
Forbidden:
description: 权限不足
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
example: false
message:
type: string
example: "权限不足"
NotFound:
description: 资源不存在
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
example: false
message:
type: string
example: "资源不存在"
InternalServerError:
description: 服务器内部错误
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
example: false
message:
type: string
example: "服务器内部错误"

View File

@@ -0,0 +1,869 @@
openapi: 3.0.3
info:
title: 监管任务结项API
description: |
保险端监管任务结项管理系统API接口文档
## 功能概述
- 监管任务结项记录的增删改查
- 任务审核流程管理
- 附件上传和管理
- 操作日志记录
- 统计数据查询
## 认证方式
使用JWT Bearer Token进行身份认证
## 响应格式
所有API响应都遵循统一的格式
```json
{
"code": 200,
"message": "success",
"data": {},
"timestamp": "2025-01-19T10:00:00Z"
}
```
version: 1.0.0
contact:
name: 开发团队
email: dev@example.com
license:
name: MIT
url: https://opensource.org/licenses/MIT
servers:
- url: http://localhost:3000/api
description: 开发环境
- url: https://api.example.com/api
description: 生产环境
security:
- bearerAuth: []
paths:
/regulatory-task-completion:
get:
tags:
- 监管任务结项
summary: 获取监管任务结项列表
description: 分页查询监管任务结项记录,支持多条件筛选
parameters:
- name: application_no
in: query
description: 申请编号(模糊查询)
schema:
type: string
example: "APP20250119"
- name: policy_no
in: query
description: 保单号(模糊查询)
schema:
type: string
example: "POL20250119"
- name: product_name
in: query
description: 产品名称(模糊查询)
schema:
type: string
example: "养殖保险"
- name: customer_name
in: query
description: 客户名称(模糊查询)
schema:
type: string
example: "张三"
- name: completion_date
in: query
description: 结项日期
schema:
type: string
format: date
example: "2025-01-19"
- name: status
in: query
description: 状态筛选
schema:
type: string
enum: [pending, approved, rejected, completed]
example: "pending"
- name: reviewer_name
in: query
description: 审核人员(模糊查询)
schema:
type: string
example: "审核员"
- name: page
in: query
description: 页码
schema:
type: integer
minimum: 1
default: 1
example: 1
- name: limit
in: query
description: 每页数量
schema:
type: integer
minimum: 1
maximum: 100
default: 10
example: 10
responses:
'200':
description: 查询成功
content:
application/json:
schema:
type: object
properties:
code:
type: integer
example: 200
message:
type: string
example: "success"
data:
type: object
properties:
list:
type: array
items:
$ref: '#/components/schemas/RegulatoryTaskCompletion'
pagination:
$ref: '#/components/schemas/Pagination'
timestamp:
type: string
format: date-time
example: "2025-01-19T10:00:00Z"
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'500':
$ref: '#/components/responses/InternalServerError'
post:
tags:
- 监管任务结项
summary: 创建监管任务结项记录
description: 创建新的监管任务结项记录
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateRegulatoryTaskCompletionRequest'
responses:
'201':
description: 创建成功
content:
application/json:
schema:
type: object
properties:
code:
type: integer
example: 201
message:
type: string
example: "success"
data:
type: object
properties:
id:
type: integer
example: 1
message:
type: string
example: "监管任务结项记录创建成功"
timestamp:
type: string
format: date-time
example: "2025-01-19T10:00:00Z"
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'500':
$ref: '#/components/responses/InternalServerError'
/regulatory-task-completion/{id}:
get:
tags:
- 监管任务结项
summary: 获取监管任务结项详情
description: 根据ID获取监管任务结项记录详情包含附件和操作日志
parameters:
- name: id
in: path
required: true
description: 记录ID
schema:
type: integer
example: 1
responses:
'200':
description: 查询成功
content:
application/json:
schema:
type: object
properties:
code:
type: integer
example: 200
message:
type: string
example: "success"
data:
$ref: '#/components/schemas/RegulatoryTaskCompletionDetail'
timestamp:
type: string
format: date-time
example: "2025-01-19T10:00:00Z"
'404':
$ref: '#/components/responses/NotFound'
'401':
$ref: '#/components/responses/Unauthorized'
'500':
$ref: '#/components/responses/InternalServerError'
put:
tags:
- 监管任务结项
summary: 更新监管任务结项记录
description: 更新指定ID的监管任务结项记录
parameters:
- name: id
in: path
required: true
description: 记录ID
schema:
type: integer
example: 1
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateRegulatoryTaskCompletionRequest'
responses:
'200':
description: 更新成功
content:
application/json:
schema:
type: object
properties:
code:
type: integer
example: 200
message:
type: string
example: "success"
data:
type: object
properties:
message:
type: string
example: "监管任务结项记录更新成功"
timestamp:
type: string
format: date-time
example: "2025-01-19T10:00:00Z"
'400':
$ref: '#/components/responses/BadRequest'
'404':
$ref: '#/components/responses/NotFound'
'401':
$ref: '#/components/responses/Unauthorized'
'500':
$ref: '#/components/responses/InternalServerError'
delete:
tags:
- 监管任务结项
summary: 删除监管任务结项记录
description: 删除指定ID的监管任务结项记录及相关数据
parameters:
- name: id
in: path
required: true
description: 记录ID
schema:
type: integer
example: 1
responses:
'200':
description: 删除成功
content:
application/json:
schema:
type: object
properties:
code:
type: integer
example: 200
message:
type: string
example: "success"
data:
type: object
properties:
message:
type: string
example: "监管任务结项记录删除成功"
timestamp:
type: string
format: date-time
example: "2025-01-19T10:00:00Z"
'404':
$ref: '#/components/responses/NotFound'
'401':
$ref: '#/components/responses/Unauthorized'
'500':
$ref: '#/components/responses/InternalServerError'
/regulatory-task-completion/{id}/review:
patch:
tags:
- 监管任务结项
summary: 审核监管任务结项记录
description: 对指定ID的监管任务结项记录进行审核
parameters:
- name: id
in: path
required: true
description: 记录ID
schema:
type: integer
example: 1
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ReviewRequest'
responses:
'200':
description: 审核成功
content:
application/json:
schema:
type: object
properties:
code:
type: integer
example: 200
message:
type: string
example: "success"
data:
type: object
properties:
message:
type: string
example: "监管任务结项记录审核成功"
timestamp:
type: string
format: date-time
example: "2025-01-19T10:00:00Z"
'400':
$ref: '#/components/responses/BadRequest'
'404':
$ref: '#/components/responses/NotFound'
'401':
$ref: '#/components/responses/Unauthorized'
'500':
$ref: '#/components/responses/InternalServerError'
/regulatory-task-completion/stats:
get:
tags:
- 监管任务结项
summary: 获取监管任务结项统计数据
description: 获取监管任务结项的统计分析数据
responses:
'200':
description: 查询成功
content:
application/json:
schema:
type: object
properties:
code:
type: integer
example: 200
message:
type: string
example: "success"
data:
$ref: '#/components/schemas/StatisticsData'
timestamp:
type: string
format: date-time
example: "2025-01-19T10:00:00Z"
'401':
$ref: '#/components/responses/Unauthorized'
'500':
$ref: '#/components/responses/InternalServerError'
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
schemas:
RegulatoryTaskCompletion:
type: object
properties:
id:
type: integer
description: 记录ID
example: 1
application_no:
type: string
description: 申请编号
example: "APP20250119001"
policy_no:
type: string
description: 保单号
example: "POL20250119001"
product_name:
type: string
description: 产品名称
example: "养殖保险"
customer_name:
type: string
description: 客户名称
example: "张三"
customer_phone:
type: string
description: 客户电话
example: "13800138000"
insurance_amount:
type: number
format: decimal
description: 保险金额
example: 100000.00
premium_amount:
type: number
format: decimal
description: 保费金额
example: 5000.00
start_date:
type: string
format: date
description: 保险起期
example: "2025-01-01"
end_date:
type: string
format: date
description: 保险止期
example: "2025-12-31"
completion_date:
type: string
format: date
description: 结项日期
example: "2025-01-19"
status:
type: string
enum: [pending, approved, rejected, completed]
description: 状态
example: "pending"
reviewer_name:
type: string
description: 审核人员
example: "审核员张三"
review_comments:
type: string
description: 审核意见
example: "审核通过"
created_at:
type: string
format: date-time
description: 创建时间
example: "2025-01-19T10:00:00Z"
updated_at:
type: string
format: date-time
description: 更新时间
example: "2025-01-19T10:00:00Z"
RegulatoryTaskCompletionDetail:
allOf:
- $ref: '#/components/schemas/RegulatoryTaskCompletion'
- type: object
properties:
attachments:
type: array
items:
$ref: '#/components/schemas/Attachment'
operation_logs:
type: array
items:
$ref: '#/components/schemas/OperationLog'
Attachment:
type: object
properties:
id:
type: integer
description: 附件ID
example: 1
file_name:
type: string
description: 文件名
example: "保单文件.pdf"
file_path:
type: string
description: 文件路径
example: "/uploads/documents/policy_001.pdf"
file_size:
type: integer
description: 文件大小(字节)
example: 1024000
file_type:
type: string
description: 文件类型
example: "application/pdf"
upload_time:
type: string
format: date-time
description: 上传时间
example: "2025-01-19T10:00:00Z"
OperationLog:
type: object
properties:
id:
type: integer
description: 日志ID
example: 1
operation_type:
type: string
description: 操作类型
example: "create"
operation_description:
type: string
description: 操作描述
example: "创建监管任务结项记录"
operator_name:
type: string
description: 操作人员
example: "系统管理员"
operation_time:
type: string
format: date-time
description: 操作时间
example: "2025-01-19T10:00:00Z"
ip_address:
type: string
description: IP地址
example: "192.168.1.100"
CreateRegulatoryTaskCompletionRequest:
type: object
required:
- application_no
- policy_no
- product_name
- customer_name
properties:
application_no:
type: string
description: 申请编号
example: "APP20250119002"
policy_no:
type: string
description: 保单号
example: "POL20250119002"
product_name:
type: string
description: 产品名称
example: "养殖保险"
customer_name:
type: string
description: 客户名称
example: "李四"
customer_phone:
type: string
description: 客户电话
example: "13800138001"
insurance_amount:
type: number
format: decimal
description: 保险金额
example: 150000.00
premium_amount:
type: number
format: decimal
description: 保费金额
example: 7500.00
start_date:
type: string
format: date
description: 保险起期
example: "2025-01-01"
end_date:
type: string
format: date
description: 保险止期
example: "2025-12-31"
completion_date:
type: string
format: date
description: 结项日期
example: "2025-01-19"
status:
type: string
enum: [pending, approved, rejected, completed]
description: 状态
default: "pending"
example: "pending"
review_comments:
type: string
description: 审核意见
example: "待审核"
attachments:
type: array
description: 附件列表
items:
type: object
properties:
file_name:
type: string
example: "保单文件.pdf"
file_path:
type: string
example: "/uploads/documents/policy_002.pdf"
file_size:
type: integer
example: 1024000
file_type:
type: string
example: "application/pdf"
UpdateRegulatoryTaskCompletionRequest:
type: object
properties:
application_no:
type: string
description: 申请编号
example: "APP20250119002"
policy_no:
type: string
description: 保单号
example: "POL20250119002"
product_name:
type: string
description: 产品名称
example: "养殖保险(更新)"
customer_name:
type: string
description: 客户名称
example: "李四"
customer_phone:
type: string
description: 客户电话
example: "13800138001"
insurance_amount:
type: number
format: decimal
description: 保险金额
example: 160000.00
premium_amount:
type: number
format: decimal
description: 保费金额
example: 8000.00
start_date:
type: string
format: date
description: 保险起期
example: "2025-01-01"
end_date:
type: string
format: date
description: 保险止期
example: "2025-12-31"
completion_date:
type: string
format: date
description: 结项日期
example: "2025-01-19"
status:
type: string
enum: [pending, approved, rejected, completed]
description: 状态
example: "pending"
review_comments:
type: string
description: 审核意见
example: "信息已更新"
ReviewRequest:
type: object
required:
- status
- reviewer_name
properties:
status:
type: string
enum: [approved, rejected]
description: 审核状态
example: "approved"
review_comments:
type: string
description: 审核意见
example: "审核通过,符合监管要求"
reviewer_name:
type: string
description: 审核人员
example: "审核员张三"
StatisticsData:
type: object
properties:
statusStats:
type: array
description: 状态统计
items:
type: object
properties:
status:
type: string
example: "pending"
count:
type: integer
example: 5
monthlyStats:
type: array
description: 月度统计
items:
type: object
properties:
month:
type: string
example: "2025-01"
count:
type: integer
example: 10
total_amount:
type: number
format: decimal
example: 1000000.00
productStats:
type: array
description: 产品统计
items:
type: object
properties:
product_name:
type: string
example: "养殖保险"
count:
type: integer
example: 20
total_amount:
type: number
format: decimal
example: 2000000.00
Pagination:
type: object
properties:
current:
type: integer
description: 当前页码
example: 1
pageSize:
type: integer
description: 每页数量
example: 10
total:
type: integer
description: 总记录数
example: 100
totalPages:
type: integer
description: 总页数
example: 10
ErrorResponse:
type: object
properties:
code:
type: integer
description: 错误码
message:
type: string
description: 错误信息
timestamp:
type: string
format: date-time
description: 时间戳
responses:
BadRequest:
description: 请求参数错误
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
code: 400
message: "申请编号、保单号、产品名称、客户名称为必填项"
timestamp: "2025-01-19T10:00:00Z"
Unauthorized:
description: 未授权访问
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
code: 401
message: "未授权访问"
timestamp: "2025-01-19T10:00:00Z"
Forbidden:
description: 权限不足
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
code: 403
message: "权限不足"
timestamp: "2025-01-19T10:00:00Z"
NotFound:
description: 资源不存在
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
code: 404
message: "监管任务结项记录不存在"
timestamp: "2025-01-19T10:00:00Z"
InternalServerError:
description: 服务器内部错误
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
code: 500
message: "服务器内部错误"
timestamp: "2025-01-19T10:00:00Z"
tags:
- name: 监管任务结项
description: 监管任务结项管理相关接口

View File

@@ -22,27 +22,66 @@ const jwtAuth = (req, res, next) => {
const checkPermission = (resource, action) => {
return async (req, res, next) => {
try {
const { Role } = require('../models');
console.log(`权限检查 - 资源: ${resource}, 操作: ${action}, 用户:`, req.user);
const user = req.user;
if (!user || !user.role_id) {
console.log('权限检查失败 - 用户角色信息缺失');
return res.status(403).json(responseFormat.error('用户角色信息缺失'));
}
const userRole = await Role.findByPk(user.role_id);
let permissions = [];
if (!userRole) {
return res.status(403).json(responseFormat.error('用户角色不存在'));
// 优先使用JWT中的权限信息
if (user.permissions) {
if (typeof user.permissions === 'string') {
try {
permissions = JSON.parse(user.permissions);
} catch (e) {
console.log('JWT权限解析失败:', e.message);
permissions = [];
}
} else if (Array.isArray(user.permissions)) {
permissions = user.permissions;
}
}
// 如果JWT中没有权限信息从数据库查询
if (permissions.length === 0) {
const { Role } = require('../models');
const userRole = await Role.findByPk(user.role_id);
if (!userRole) {
console.log('权限检查失败 - 用户角色不存在, role_id:', user.role_id);
return res.status(403).json(responseFormat.error('用户角色不存在'));
}
let rolePermissions = userRole.permissions || [];
// 如果permissions是字符串尝试解析为JSON
if (typeof rolePermissions === 'string') {
try {
permissions = JSON.parse(rolePermissions);
} catch (e) {
console.log('数据库权限解析失败:', e.message);
permissions = [];
}
} else if (Array.isArray(rolePermissions)) {
permissions = rolePermissions;
}
}
const permissions = userRole.permissions || [];
const requiredPermission = `${resource}:${action}`;
console.log('权限检查 - 用户权限:', permissions, '需要权限:', requiredPermission);
// 检查权限或超级管理员权限
if (!permissions.includes(requiredPermission) && !permissions.includes('*:*')) {
if (!permissions.includes(requiredPermission) && !permissions.includes('*:*') && !permissions.includes('*')) {
console.log('权限检查失败 - 权限不足');
return res.status(403).json(responseFormat.error('权限不足'));
}
console.log('权限检查通过');
next();
} catch (error) {
return res.status(500).json(responseFormat.error('权限验证失败'));
@@ -67,4 +106,17 @@ const optionalAuth = (req, res, next) => {
next();
};
module.exports = { jwtAuth, checkPermission, optionalAuth };
// 别名导出以匹配路由中的使用
const authenticateToken = jwtAuth;
const requirePermission = (permission) => {
const [resource, action] = permission.split(':');
return checkPermission(resource, action);
};
module.exports = {
jwtAuth,
checkPermission,
optionalAuth,
authenticateToken,
requirePermission
};

View File

@@ -0,0 +1,75 @@
'use strict';
module.exports = {
up: async (queryInterface, Sequelize) => {
// 添加参保类型字段
await queryInterface.addColumn('insurance_applications', 'insurance_category', {
type: Sequelize.STRING(50),
allowNull: true,
comment: '参保类型(如:牛、羊、猪等)',
after: 'insurance_type_id'
});
// 添加申请数量字段
await queryInterface.addColumn('insurance_applications', 'application_quantity', {
type: Sequelize.INTEGER,
allowNull: true,
defaultValue: 1,
comment: '申请数量',
validate: {
min: 1
},
after: 'insurance_category'
});
// 添加备注字段
await queryInterface.addColumn('insurance_applications', 'remarks', {
type: Sequelize.TEXT,
allowNull: true,
comment: '备注信息',
after: 'review_notes'
});
// 更新状态枚举值以匹配UI显示
await queryInterface.changeColumn('insurance_applications', 'status', {
type: Sequelize.ENUM(
'pending', // 待初审
'initial_approved', // 初审通过待复核
'under_review', // 已支付待复核
'approved', // 已支付
'rejected', // 已拒绝
'paid' // 已支付
),
allowNull: false,
defaultValue: 'pending',
comment: '申请状态'
});
// 添加索引
await queryInterface.addIndex('insurance_applications', ['insurance_category'], {
name: 'idx_insurance_applications_category'
});
await queryInterface.addIndex('insurance_applications', ['application_quantity'], {
name: 'idx_insurance_applications_quantity'
});
},
down: async (queryInterface, Sequelize) => {
// 移除索引
await queryInterface.removeIndex('insurance_applications', 'idx_insurance_applications_category');
await queryInterface.removeIndex('insurance_applications', 'idx_insurance_applications_quantity');
// 移除添加的字段
await queryInterface.removeColumn('insurance_applications', 'insurance_category');
await queryInterface.removeColumn('insurance_applications', 'application_quantity');
await queryInterface.removeColumn('insurance_applications', 'remarks');
// 恢复原始状态枚举
await queryInterface.changeColumn('insurance_applications', 'status', {
type: Sequelize.ENUM('pending', 'approved', 'rejected', 'under_review'),
allowNull: false,
defaultValue: 'pending'
});
}
};

View File

@@ -51,6 +51,23 @@ const InsuranceApplication = sequelize.define('InsuranceApplication', {
key: 'id'
}
},
insurance_category: {
type: DataTypes.STRING(50),
allowNull: true,
comment: '参保类型(如:牛、羊、猪等)',
validate: {
len: [1, 50]
}
},
application_quantity: {
type: DataTypes.INTEGER,
allowNull: true,
defaultValue: 1,
comment: '申请数量',
validate: {
min: 1
}
},
application_amount: {
type: DataTypes.DECIMAL(15, 2),
allowNull: false,
@@ -59,8 +76,16 @@ const InsuranceApplication = sequelize.define('InsuranceApplication', {
}
},
status: {
type: DataTypes.ENUM('pending', 'approved', 'rejected', 'under_review'),
defaultValue: 'pending'
type: DataTypes.ENUM(
'pending', // 待初审
'initial_approved', // 初审通过待复核
'under_review', // 已支付待复核
'approved', // 已支付
'rejected', // 已拒绝
'paid' // 已支付
),
defaultValue: 'pending',
comment: '申请状态'
},
application_date: {
type: DataTypes.DATE,
@@ -71,6 +96,11 @@ const InsuranceApplication = sequelize.define('InsuranceApplication', {
type: DataTypes.TEXT,
allowNull: true
},
remarks: {
type: DataTypes.TEXT,
allowNull: true,
comment: '备注信息'
},
reviewer_id: {
type: DataTypes.INTEGER,
allowNull: true,
@@ -99,6 +129,8 @@ const InsuranceApplication = sequelize.define('InsuranceApplication', {
{ fields: ['status'] },
{ fields: ['application_date'] },
{ fields: ['insurance_type_id'] },
{ fields: ['insurance_category'] },
{ fields: ['application_quantity'] },
{ fields: ['reviewer_id'] }
]
});

View File

@@ -11,17 +11,19 @@ const InsuranceType = sequelize.define('InsuranceType', {
name: {
type: DataTypes.STRING(100),
allowNull: false,
comment: '保险类型名称',
comment: '险种名称',
validate: {
notEmpty: {
msg: '保险类型名称不能为空'
msg: '险种名称不能为空'
},
len: {
args: [1, 100],
msg: '保险类型名称长度必须在1-100个字符之间'
msg: '险种名称长度必须在1-100个字符之间'
}
}
},
description: {
type: DataTypes.TEXT,
allowNull: true,

View File

@@ -0,0 +1,202 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
const LivestockClaim = sequelize.define('LivestockClaim', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
comment: '生资理赔ID'
},
claim_no: {
type: DataTypes.STRING(50),
allowNull: false,
unique: true,
comment: '理赔编号',
validate: {
notEmpty: {
msg: '理赔编号不能为空'
}
}
},
policy_id: {
type: DataTypes.INTEGER,
allowNull: false,
comment: '关联的保单ID',
references: {
model: 'livestock_policies',
key: 'id'
}
},
claim_type: {
type: DataTypes.ENUM('disease', 'natural_disaster', 'accident', 'theft', 'other'),
allowNull: false,
comment: '理赔类型disease-疾病natural_disaster-自然灾害accident-意外事故theft-盗窃other-其他'
},
incident_date: {
type: DataTypes.DATE,
allowNull: false,
comment: '事故发生日期'
},
report_date: {
type: DataTypes.DATE,
allowNull: false,
comment: '报案日期'
},
incident_description: {
type: DataTypes.TEXT,
allowNull: false,
comment: '事故描述',
validate: {
notEmpty: {
msg: '事故描述不能为空'
}
}
},
incident_location: {
type: DataTypes.STRING(500),
allowNull: false,
comment: '事故地点',
validate: {
notEmpty: {
msg: '事故地点不能为空'
}
}
},
affected_count: {
type: DataTypes.INTEGER,
allowNull: false,
comment: '受影响牲畜数量',
validate: {
min: {
args: [1],
msg: '受影响牲畜数量不能小于1'
}
}
},
claim_amount: {
type: DataTypes.DECIMAL(15, 2),
allowNull: false,
comment: '理赔金额',
validate: {
min: {
args: [0],
msg: '理赔金额不能小于0'
}
}
},
claim_status: {
type: DataTypes.ENUM('pending', 'investigating', 'approved', 'rejected', 'paid'),
allowNull: false,
defaultValue: 'pending',
comment: '理赔状态pending-待审核investigating-调查中approved-已批准rejected-已拒绝paid-已支付'
},
investigator_id: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '调查员ID',
references: {
model: 'users',
key: 'id'
}
},
investigation_notes: {
type: DataTypes.TEXT,
allowNull: true,
comment: '调查备注'
},
investigation_date: {
type: DataTypes.DATE,
allowNull: true,
comment: '调查日期'
},
reviewer_id: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '审核人ID',
references: {
model: 'users',
key: 'id'
}
},
review_notes: {
type: DataTypes.TEXT,
allowNull: true,
comment: '审核备注'
},
review_date: {
type: DataTypes.DATE,
allowNull: true,
comment: '审核日期'
},
settlement_amount: {
type: DataTypes.DECIMAL(15, 2),
allowNull: true,
comment: '实际赔付金额',
validate: {
min: {
args: [0],
msg: '实际赔付金额不能小于0'
}
}
},
settlement_date: {
type: DataTypes.DATE,
allowNull: true,
comment: '赔付日期'
},
supporting_documents: {
type: DataTypes.JSON,
allowNull: true,
comment: '支持文件JSON数组包含文件URL和描述'
},
created_by: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '创建人ID',
references: {
model: 'users',
key: 'id'
}
},
updated_by: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '更新人ID',
references: {
model: 'users',
key: 'id'
}
}
}, {
tableName: 'livestock_claims',
timestamps: true,
createdAt: 'created_at',
updatedAt: 'updated_at',
indexes: [
{
name: 'idx_livestock_claim_no',
fields: ['claim_no'],
unique: true
},
{
name: 'idx_livestock_claim_policy',
fields: ['policy_id']
},
{
name: 'idx_livestock_claim_type',
fields: ['claim_type']
},
{
name: 'idx_livestock_claim_status',
fields: ['claim_status']
},
{
name: 'idx_livestock_claim_dates',
fields: ['incident_date', 'report_date']
}
],
comment: '生资理赔表'
});
module.exports = LivestockClaim;

View File

@@ -0,0 +1,217 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
const LivestockPolicy = sequelize.define('LivestockPolicy', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
comment: '生资保单ID'
},
policy_no: {
type: DataTypes.STRING(50),
allowNull: false,
unique: true,
comment: '保单编号',
validate: {
notEmpty: {
msg: '保单编号不能为空'
}
}
},
livestock_type_id: {
type: DataTypes.INTEGER,
allowNull: false,
comment: '牲畜类型ID',
references: {
model: 'livestock_types',
key: 'id'
}
},
policyholder_name: {
type: DataTypes.STRING(100),
allowNull: false,
comment: '投保人姓名',
validate: {
notEmpty: {
msg: '投保人姓名不能为空'
}
}
},
policyholder_id_card: {
type: DataTypes.STRING(18),
allowNull: false,
comment: '投保人身份证号',
validate: {
notEmpty: {
msg: '投保人身份证号不能为空'
}
}
},
policyholder_phone: {
type: DataTypes.STRING(20),
allowNull: false,
comment: '投保人手机号',
validate: {
notEmpty: {
msg: '投保人手机号不能为空'
}
}
},
policyholder_address: {
type: DataTypes.STRING(500),
allowNull: false,
comment: '投保人地址',
validate: {
notEmpty: {
msg: '投保人地址不能为空'
}
}
},
farm_name: {
type: DataTypes.STRING(200),
allowNull: true,
comment: '养殖场名称'
},
farm_address: {
type: DataTypes.STRING(500),
allowNull: true,
comment: '养殖场地址'
},
farm_license: {
type: DataTypes.STRING(100),
allowNull: true,
comment: '养殖场许可证号'
},
livestock_count: {
type: DataTypes.INTEGER,
allowNull: false,
comment: '投保牲畜数量',
validate: {
min: {
args: [1],
msg: '投保牲畜数量不能小于1'
}
}
},
unit_value: {
type: DataTypes.DECIMAL(10, 2),
allowNull: false,
comment: '单头价值',
validate: {
min: {
args: [0],
msg: '单头价值不能小于0'
}
}
},
total_value: {
type: DataTypes.DECIMAL(15, 2),
allowNull: false,
comment: '总保额',
validate: {
min: {
args: [0],
msg: '总保额不能小于0'
}
}
},
premium_amount: {
type: DataTypes.DECIMAL(15, 2),
allowNull: false,
comment: '保费金额',
validate: {
min: {
args: [0],
msg: '保费金额不能小于0'
}
}
},
start_date: {
type: DataTypes.DATE,
allowNull: false,
comment: '保险开始日期'
},
end_date: {
type: DataTypes.DATE,
allowNull: false,
comment: '保险结束日期'
},
policy_status: {
type: DataTypes.ENUM('active', 'expired', 'cancelled', 'suspended'),
allowNull: false,
defaultValue: 'active',
comment: '保单状态active-有效expired-已过期cancelled-已取消suspended-已暂停'
},
payment_status: {
type: DataTypes.ENUM('paid', 'unpaid', 'partial'),
allowNull: false,
defaultValue: 'unpaid',
comment: '支付状态paid-已支付unpaid-未支付partial-部分支付'
},
payment_date: {
type: DataTypes.DATE,
allowNull: true,
comment: '支付日期'
},
policy_document_url: {
type: DataTypes.STRING(500),
allowNull: true,
comment: '保单文档URL'
},
notes: {
type: DataTypes.TEXT,
allowNull: true,
comment: '备注信息'
},
created_by: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '创建人ID',
references: {
model: 'users',
key: 'id'
}
},
updated_by: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '更新人ID',
references: {
model: 'users',
key: 'id'
}
}
}, {
tableName: 'livestock_policies',
timestamps: true,
createdAt: 'created_at',
updatedAt: 'updated_at',
indexes: [
{
name: 'idx_livestock_policy_no',
fields: ['policy_no'],
unique: true
},
{
name: 'idx_livestock_policy_policyholder',
fields: ['policyholder_name']
},
{
name: 'idx_livestock_policy_status',
fields: ['policy_status']
},
{
name: 'idx_livestock_policy_payment_status',
fields: ['payment_status']
},
{
name: 'idx_livestock_policy_dates',
fields: ['start_date', 'end_date']
}
],
comment: '生资保单表'
});
module.exports = LivestockPolicy;

View File

@@ -0,0 +1,101 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
const LivestockType = sequelize.define('LivestockType', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
comment: '牲畜类型ID'
},
name: {
type: DataTypes.STRING(50),
allowNull: false,
unique: true,
comment: '牲畜类型名称',
validate: {
notEmpty: {
msg: '牲畜类型名称不能为空'
}
}
},
code: {
type: DataTypes.STRING(20),
allowNull: false,
unique: true,
comment: '牲畜类型代码'
},
description: {
type: DataTypes.TEXT,
allowNull: true,
comment: '牲畜类型描述'
},
unit_price_min: {
type: DataTypes.DECIMAL(10, 2),
allowNull: false,
defaultValue: 0.00,
comment: '单头最低价值',
validate: {
min: {
args: [0],
msg: '单头最低价值不能小于0'
}
}
},
unit_price_max: {
type: DataTypes.DECIMAL(10, 2),
allowNull: false,
defaultValue: 50000.00,
comment: '单头最高价值',
validate: {
min: {
args: [0],
msg: '单头最高价值不能小于0'
}
}
},
premium_rate: {
type: DataTypes.DECIMAL(5, 4),
allowNull: false,
defaultValue: 0.0050,
comment: '保险费率',
validate: {
min: {
args: [0],
msg: '保费费率不能小于0'
},
max: {
args: [1],
msg: '保费费率不能大于1'
}
}
},
status: {
type: DataTypes.ENUM('active', 'inactive'),
allowNull: false,
defaultValue: 'active',
comment: '状态'
}
}, {
tableName: 'livestock_types',
timestamps: true,
createdAt: 'created_at',
updatedAt: 'updated_at',
indexes: [
{
name: 'idx_livestock_type_name',
fields: ['name']
},
{
name: 'idx_livestock_type_code',
fields: ['code']
},
{
name: 'idx_livestock_type_status',
fields: ['status']
}
],
comment: '牲畜类型表'
});
module.exports = LivestockType;

View File

@@ -9,6 +9,9 @@ const Claim = require('./Claim');
const Menu = require('./Menu');
const SupervisoryTask = require('./SupervisoryTask');
const InstallationTask = require('./InstallationTask');
const LivestockType = require('./LivestockType');
const LivestockPolicy = require('./LivestockPolicy');
const LivestockClaim = require('./LivestockClaim');
// 定义模型关联关系
@@ -46,6 +49,26 @@ InsuranceApplication.hasOne(Policy, {
as: 'policy'
});
// 保单和客户关联
Policy.belongsTo(User, {
foreignKey: 'customer_id',
as: 'customer'
});
User.hasMany(Policy, {
foreignKey: 'customer_id',
as: 'policies'
});
// 保单和保险类型关联
Policy.belongsTo(InsuranceType, {
foreignKey: 'insurance_type_id',
as: 'insurance_type'
});
InsuranceType.hasMany(Policy, {
foreignKey: 'insurance_type_id',
as: 'policies'
});
// 理赔和保单关联
Claim.belongsTo(Policy, {
foreignKey: 'policy_id',
@@ -84,6 +107,49 @@ InstallationTask.belongsTo(User, {
as: 'updater'
});
// 生资保险相关关联
// 注意牲畜类型表中没有created_by和updated_by字段所以不定义这些关联
// 生资保单和牲畜类型关联
LivestockPolicy.belongsTo(LivestockType, {
foreignKey: 'livestock_type_id',
as: 'livestock_type'
});
LivestockType.hasMany(LivestockPolicy, {
foreignKey: 'livestock_type_id',
as: 'policies'
});
// 生资保单和用户关联
LivestockPolicy.belongsTo(User, {
foreignKey: 'created_by',
as: 'creator'
});
LivestockPolicy.belongsTo(User, {
foreignKey: 'updated_by',
as: 'updater'
});
// 生资理赔和生资保单关联
LivestockClaim.belongsTo(LivestockPolicy, {
foreignKey: 'policy_id',
as: 'policy'
});
LivestockPolicy.hasMany(LivestockClaim, {
foreignKey: 'policy_id',
as: 'claims'
});
// 生资理赔和用户关联
LivestockClaim.belongsTo(User, {
foreignKey: 'created_by',
as: 'creator'
});
LivestockClaim.belongsTo(User, {
foreignKey: 'reviewed_by',
as: 'reviewer'
});
// 导出所有模型
module.exports = {
sequelize,
@@ -95,5 +161,8 @@ module.exports = {
Claim,
Menu,
SupervisoryTask,
InstallationTask
InstallationTask,
LivestockType,
LivestockPolicy,
LivestockClaim
};

View File

@@ -7997,6 +7997,7 @@
"version": "1.12.2",
"resolved": "https://registry.npmmirror.com/axios/-/axios-1.12.2.tgz",
"integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.4",
@@ -12876,6 +12877,7 @@
"version": "2.3.3",
"resolved": "https://registry.npmmirror.com/mysql2/-/mysql2-2.3.3.tgz",
"integrity": "sha512-wxJUev6LgMSgACDkb/InIFxDprRa6T95+VEoR+xPvtngtccNH2dGjEB/fVZ8yg1gWv1510c9CvXuJHi5zUm0ZA==",
"license": "MIT",
"dependencies": {
"denque": "^2.0.1",
"generate-function": "^2.3.1",

View File

@@ -38,4 +38,14 @@ router.get('/applications-stats', jwtAuth, checkPermission('insurance', 'read'),
insuranceController.getApplicationStats
);
// 导出保险申请数据
router.get('/applications/export', jwtAuth, checkPermission('insurance', 'read'),
insuranceController.exportApplications
);
// 获取参保类型选项
router.get('/categories', jwtAuth, checkPermission('insurance', 'read'),
insuranceController.getInsuranceCategories
);
module.exports = router;

View File

@@ -3,32 +3,32 @@ const router = express.Router();
const insuranceTypeController = require('../controllers/insuranceTypeController');
const { jwtAuth, checkPermission } = require('../middleware/auth');
// 获取保险类型列表
// 获取险种列表
router.get('/', jwtAuth, checkPermission('insurance_type', 'read'),
insuranceTypeController.getInsuranceTypes
);
// 获取单个保险类型详情
// 获取单个险种详情
router.get('/:id', jwtAuth, checkPermission('insurance_type', 'read'),
insuranceTypeController.getInsuranceTypeById
);
// 创建保险类型
// 创建险种
router.post('/', jwtAuth, checkPermission('insurance_type', 'create'),
insuranceTypeController.createInsuranceType
);
// 更新保险类型
// 更新险种
router.put('/:id', jwtAuth, checkPermission('insurance_type', 'update'),
insuranceTypeController.updateInsuranceType
);
// 删除保险类型
// 删除险种
router.delete('/:id', jwtAuth, checkPermission('insurance_type', 'delete'),
insuranceTypeController.deleteInsuranceType
);
// 更新保险类型状态
// 更新险种状态
router.patch('/:id/status', jwtAuth, checkPermission('insurance_type', 'update'),
insuranceTypeController.updateInsuranceTypeStatus
);

View File

@@ -0,0 +1,31 @@
const express = require('express');
const router = express.Router();
const {
getLivestockClaims,
createLivestockClaim,
getLivestockClaimById,
reviewLivestockClaim,
updateLivestockClaimPayment,
getLivestockClaimStats
} = require('../controllers/livestockClaimController');
const { authenticateToken, requirePermission } = require('../middleware/auth');
// 获取生资理赔列表
router.get('/', authenticateToken, requirePermission('livestock_claim:read'), getLivestockClaims);
// 获取生资理赔统计
router.get('/stats', authenticateToken, requirePermission('livestock_claim:read'), getLivestockClaimStats);
// 获取单个生资理赔详情
router.get('/:id', authenticateToken, requirePermission('livestock_claim:read'), getLivestockClaimById);
// 创建生资理赔申请
router.post('/', authenticateToken, requirePermission('livestock_claim:create'), createLivestockClaim);
// 审核生资理赔
router.patch('/:id/review', authenticateToken, requirePermission('livestock_claim:review'), reviewLivestockClaim);
// 更新理赔支付状态
router.patch('/:id/payment', authenticateToken, requirePermission('livestock_claim:payment'), updateLivestockClaimPayment);
module.exports = router;

View File

@@ -0,0 +1,35 @@
const express = require('express');
const router = express.Router();
const {
getLivestockPolicies,
createLivestockPolicy,
getLivestockPolicyById,
updateLivestockPolicy,
updateLivestockPolicyStatus,
deleteLivestockPolicy,
getLivestockPolicyStats
} = require('../controllers/livestockPolicyController');
const { authenticateToken, requirePermission } = require('../middleware/auth');
// 获取生资保单列表
router.get('/', authenticateToken, requirePermission('livestock_policy:read'), getLivestockPolicies);
// 获取生资保单统计
router.get('/stats', authenticateToken, requirePermission('livestock_policy:read'), getLivestockPolicyStats);
// 获取单个生资保单详情
router.get('/:id', authenticateToken, requirePermission('livestock_policy:read'), getLivestockPolicyById);
// 创建生资保单
router.post('/', authenticateToken, requirePermission('livestock_policy:create'), createLivestockPolicy);
// 更新生资保单
router.put('/:id', authenticateToken, requirePermission('livestock_policy:update'), updateLivestockPolicy);
// 更新生资保单状态
router.patch('/:id/status', authenticateToken, requirePermission('livestock_policy:update'), updateLivestockPolicyStatus);
// 删除生资保单
router.delete('/:id', authenticateToken, requirePermission('livestock_policy:delete'), deleteLivestockPolicy);
module.exports = router;

View File

@@ -0,0 +1,35 @@
const express = require('express');
const router = express.Router();
const {
getLivestockTypes,
getActiveLivestockTypes,
createLivestockType,
getLivestockTypeById,
updateLivestockType,
deleteLivestockType,
batchUpdateLivestockTypeStatus
} = require('../controllers/livestockTypeController');
const { authenticateToken, requirePermission } = require('../middleware/auth');
// 获取牲畜类型列表
router.get('/', authenticateToken, requirePermission('livestock_type:read'), getLivestockTypes);
// 获取所有启用的牲畜类型(用于下拉选择)
router.get('/active', authenticateToken, getActiveLivestockTypes);
// 获取单个牲畜类型详情
router.get('/:id', authenticateToken, requirePermission('livestock_type:read'), getLivestockTypeById);
// 创建牲畜类型
router.post('/', authenticateToken, requirePermission('livestock_type:create'), createLivestockType);
// 更新牲畜类型
router.put('/:id', authenticateToken, requirePermission('livestock_type:update'), updateLivestockType);
// 删除牲畜类型
router.delete('/:id', authenticateToken, requirePermission('livestock_type:delete'), deleteLivestockType);
// 批量更新牲畜类型状态
router.patch('/batch/status', authenticateToken, requirePermission('livestock_type:update'), batchUpdateLivestockTypeStatus);
module.exports = router;

View File

@@ -3,6 +3,11 @@ const router = express.Router();
const policyController = require('../controllers/policyController');
const { jwtAuth, checkPermission } = require('../middleware/auth');
// 获取保单统计(必须在动态路由之前)
router.get('/stats/overview', jwtAuth, checkPermission('policy', 'read'),
policyController.getPolicyStats
);
// 获取保单列表
router.get('/', jwtAuth, checkPermission('policy', 'read'),
policyController.getPolicies
@@ -28,9 +33,4 @@ router.patch('/:id/status', jwtAuth, checkPermission('policy', 'update'),
policyController.updatePolicyStatus
);
// 获取保单统计
router.get('/stats/overview', jwtAuth, checkPermission('policy', 'read'),
policyController.getPolicyStats
);
module.exports = router;

View File

@@ -0,0 +1,536 @@
const express = require('express');
const router = express.Router();
const regulatoryTaskCompletionController = require('../controllers/regulatoryTaskCompletionController');
const { jwtAuth, checkPermission } = require('../middleware/auth');
/**
* @swagger
* tags:
* name: RegulatoryTaskCompletion
* description: 监管任务结项管理API
*/
/**
* @swagger
* components:
* schemas:
* RegulatoryTaskCompletion:
* type: object
* required:
* - application_no
* - policy_no
* - product_name
* - customer_name
* properties:
* id:
* type: integer
* description: 记录ID
* application_no:
* type: string
* description: 申请编号
* policy_no:
* type: string
* description: 保单号
* product_name:
* type: string
* description: 产品名称
* customer_name:
* type: string
* description: 客户名称
* customer_phone:
* type: string
* description: 客户电话
* insurance_amount:
* type: number
* format: decimal
* description: 保险金额
* premium_amount:
* type: number
* format: decimal
* description: 保费金额
* start_date:
* type: string
* format: date
* description: 保险起期
* end_date:
* type: string
* format: date
* description: 保险止期
* completion_date:
* type: string
* format: date
* description: 结项日期
* status:
* type: string
* enum: [pending, approved, rejected, completed]
* description: 状态
* reviewer_name:
* type: string
* description: 审核人员
* review_comments:
* type: string
* description: 审核意见
* created_at:
* type: string
* format: date-time
* description: 创建时间
* updated_at:
* type: string
* format: date-time
* description: 更新时间
*/
/**
* @swagger
* /api/regulatory-task-completion:
* get:
* summary: 获取监管任务结项列表
* tags: [RegulatoryTaskCompletion]
* security:
* - bearerAuth: []
* parameters:
* - in: query
* name: application_no
* schema:
* type: string
* description: 申请编号(模糊查询)
* - in: query
* name: policy_no
* schema:
* type: string
* description: 保单号(模糊查询)
* - in: query
* name: product_name
* schema:
* type: string
* description: 产品名称(模糊查询)
* - in: query
* name: customer_name
* schema:
* type: string
* description: 客户名称(模糊查询)
* - in: query
* name: completion_date
* schema:
* type: string
* format: date
* description: 结项日期
* - in: query
* name: status
* schema:
* type: string
* enum: [pending, approved, rejected, completed]
* description: 状态
* - in: query
* name: reviewer_name
* schema:
* type: string
* description: 审核人员(模糊查询)
* - in: query
* name: page
* schema:
* type: integer
* default: 1
* description: 页码
* - in: query
* name: limit
* schema:
* type: integer
* default: 10
* description: 每页数量
* responses:
* 200:
* description: 成功获取监管任务结项列表
* content:
* application/json:
* schema:
* type: object
* properties:
* code:
* type: integer
* example: 200
* message:
* type: string
* example: success
* data:
* type: object
* properties:
* list:
* type: array
* items:
* $ref: '#/components/schemas/RegulatoryTaskCompletion'
* pagination:
* type: object
* properties:
* current:
* type: integer
* pageSize:
* type: integer
* total:
* type: integer
* totalPages:
* type: integer
*/
router.get('/', jwtAuth, checkPermission('regulatory_task', 'read'),
regulatoryTaskCompletionController.getTaskCompletions
);
/**
* @swagger
* /api/regulatory-task-completion/{id}:
* get:
* summary: 获取监管任务结项详情
* tags: [RegulatoryTaskCompletion]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: 记录ID
* responses:
* 200:
* description: 成功获取监管任务结项详情
* content:
* application/json:
* schema:
* type: object
* properties:
* code:
* type: integer
* example: 200
* message:
* type: string
* example: success
* data:
* allOf:
* - $ref: '#/components/schemas/RegulatoryTaskCompletion'
* - type: object
* properties:
* attachments:
* type: array
* items:
* type: object
* properties:
* id:
* type: integer
* file_name:
* type: string
* file_path:
* type: string
* file_size:
* type: integer
* file_type:
* type: string
* upload_time:
* type: string
* format: date-time
* operation_logs:
* type: array
* items:
* type: object
* properties:
* id:
* type: integer
* operation_type:
* type: string
* operation_description:
* type: string
* operator_name:
* type: string
* operation_time:
* type: string
* format: date-time
* ip_address:
* type: string
* 404:
* description: 记录不存在
*/
router.get('/:id', jwtAuth, checkPermission('regulatory_task', 'read'),
regulatoryTaskCompletionController.getTaskCompletionById
);
/**
* @swagger
* /api/regulatory-task-completion:
* post:
* summary: 创建监管任务结项记录
* tags: [RegulatoryTaskCompletion]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - application_no
* - policy_no
* - product_name
* - customer_name
* properties:
* application_no:
* type: string
* description: 申请编号
* policy_no:
* type: string
* description: 保单号
* product_name:
* type: string
* description: 产品名称
* customer_name:
* type: string
* description: 客户名称
* customer_phone:
* type: string
* description: 客户电话
* insurance_amount:
* type: number
* format: decimal
* description: 保险金额
* premium_amount:
* type: number
* format: decimal
* description: 保费金额
* start_date:
* type: string
* format: date
* description: 保险起期
* end_date:
* type: string
* format: date
* description: 保险止期
* completion_date:
* type: string
* format: date
* description: 结项日期
* status:
* type: string
* enum: [pending, approved, rejected, completed]
* default: pending
* description: 状态
* review_comments:
* type: string
* description: 审核意见
* attachments:
* type: array
* items:
* type: object
* properties:
* file_name:
* type: string
* file_path:
* type: string
* file_size:
* type: integer
* file_type:
* type: string
* responses:
* 201:
* description: 监管任务结项记录创建成功
* 400:
* description: 参数错误或记录已存在
*/
router.post('/', jwtAuth, checkPermission('regulatory_task', 'create'),
regulatoryTaskCompletionController.createTaskCompletion
);
/**
* @swagger
* /api/regulatory-task-completion/{id}:
* put:
* summary: 更新监管任务结项记录
* tags: [RegulatoryTaskCompletion]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: 记录ID
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* application_no:
* type: string
* policy_no:
* type: string
* product_name:
* type: string
* customer_name:
* type: string
* customer_phone:
* type: string
* insurance_amount:
* type: number
* format: decimal
* premium_amount:
* type: number
* format: decimal
* start_date:
* type: string
* format: date
* end_date:
* type: string
* format: date
* completion_date:
* type: string
* format: date
* status:
* type: string
* enum: [pending, approved, rejected, completed]
* review_comments:
* type: string
* responses:
* 200:
* description: 监管任务结项记录更新成功
* 404:
* description: 记录不存在
*/
router.put('/:id', jwtAuth, checkPermission('regulatory_task', 'update'),
regulatoryTaskCompletionController.updateTaskCompletion
);
/**
* @swagger
* /api/regulatory-task-completion/{id}:
* delete:
* summary: 删除监管任务结项记录
* tags: [RegulatoryTaskCompletion]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: 记录ID
* responses:
* 200:
* description: 监管任务结项记录删除成功
* 404:
* description: 记录不存在
*/
router.delete('/:id', jwtAuth, checkPermission('regulatory_task', 'delete'),
regulatoryTaskCompletionController.deleteTaskCompletion
);
/**
* @swagger
* /api/regulatory-task-completion/{id}/review:
* patch:
* summary: 审核监管任务结项记录
* tags: [RegulatoryTaskCompletion]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: 记录ID
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - status
* - reviewer_name
* properties:
* status:
* type: string
* enum: [pending, approved, rejected, completed]
* description: 审核状态
* review_comments:
* type: string
* description: 审核意见
* reviewer_name:
* type: string
* description: 审核人员
* responses:
* 200:
* description: 监管任务结项记录审核成功
* 400:
* description: 无效的状态值
* 404:
* description: 记录不存在
*/
router.patch('/:id/review', jwtAuth, checkPermission('regulatory_task', 'review'),
regulatoryTaskCompletionController.reviewTaskCompletion
);
/**
* @swagger
* /api/regulatory-task-completion/stats:
* get:
* summary: 获取监管任务结项统计数据
* tags: [RegulatoryTaskCompletion]
* security:
* - bearerAuth: []
* responses:
* 200:
* description: 成功获取统计数据
* content:
* application/json:
* schema:
* type: object
* properties:
* code:
* type: integer
* example: 200
* message:
* type: string
* example: success
* data:
* type: object
* properties:
* statusStats:
* type: array
* items:
* type: object
* properties:
* status:
* type: string
* count:
* type: integer
* monthlyStats:
* type: array
* items:
* type: object
* properties:
* month:
* type: string
* count:
* type: integer
* total_amount:
* type: number
* productStats:
* type: array
* items:
* type: object
* properties:
* product_name:
* type: string
* count:
* type: integer
* total_amount:
* type: number
*/
router.get('/stats', jwtAuth, checkPermission('regulatory_task', 'read'),
regulatoryTaskCompletionController.getTaskCompletionStats
);
module.exports = router;

View File

@@ -0,0 +1,29 @@
const { Sequelize } = require('sequelize');
const sequelize = new Sequelize('insurance_db', 'root', '123456', {
host: 'localhost',
dialect: 'mysql',
logging: false
});
async function checkClaimsTable() {
try {
const [results] = await sequelize.query('DESCRIBE livestock_claims');
console.log('livestock_claims表结构:');
console.log('='.repeat(50));
results.forEach(field => {
const required = field.Null === 'NO' ? '(必填)' : '(可选)';
const defaultValue = field.Default ? ` 默认值: ${field.Default}` : '';
console.log(`- ${field.Field}: ${field.Type} ${required}${defaultValue}`);
});
} catch (error) {
console.error('检查表结构失败:', error.message);
} finally {
await sequelize.close();
}
}
checkClaimsTable();

View File

@@ -0,0 +1,34 @@
const { sequelize } = require('../config/database');
async function checkRequiredFields() {
try {
console.log('检查 livestock_policies 表的必填字段...');
const [rows] = await sequelize.query('DESCRIBE livestock_policies');
console.log('必填字段NOT NULL:');
console.log('序号 | 字段名 | 类型 | 默认值');
console.log('-----|--------|------|--------');
const requiredFields = rows.filter(row => row.Null === 'NO');
requiredFields.forEach((row, index) => {
console.log(`${index + 1}. ${row.Field} | ${row.Type} | ${row.Default || 'NULL'}`);
});
console.log('\n可选字段允许NULL:');
console.log('序号 | 字段名 | 类型');
console.log('-----|--------|------');
const optionalFields = rows.filter(row => row.Null === 'YES');
optionalFields.forEach((row, index) => {
console.log(`${index + 1}. ${row.Field} | ${row.Type}`);
});
} catch (error) {
console.error('❌ 检查字段失败:', error.message);
} finally {
await sequelize.close();
}
}
checkRequiredFields();

View File

@@ -0,0 +1,24 @@
const { sequelize } = require('../config/database');
async function checkTableStructure() {
try {
console.log('检查 livestock_policies 表结构...');
const [rows] = await sequelize.query('DESCRIBE livestock_policies');
console.log('livestock_policies表结构:');
console.log('序号 | 字段名 | 类型 | 是否为空 | 默认值');
console.log('-----|--------|------|----------|--------');
rows.forEach((row, index) => {
console.log(`${index + 1}. ${row.Field} | ${row.Type} | ${row.Null} | ${row.Default || 'NULL'}`);
});
} catch (error) {
console.error('❌ 检查表结构失败:', error.message);
} finally {
await sequelize.close();
}
}
checkTableStructure();

View File

@@ -0,0 +1,20 @@
const { sequelize } = require('../config/database');
async function checkTables() {
try {
await sequelize.query('USE insurance_data');
const [results] = await sequelize.query("SHOW TABLES LIKE 'livestock_%'");
console.log('livestock表:', results);
const [allTables] = await sequelize.query("SHOW TABLES");
console.log('所有表:', allTables.map(t => Object.values(t)[0]));
} catch (error) {
console.error('错误:', error.message);
} finally {
await sequelize.close();
}
}
checkTables();

View File

@@ -0,0 +1,21 @@
const { sequelize } = require('../config/database');
async function checkUsers() {
try {
const [users] = await sequelize.query('SELECT id, username, real_name FROM users LIMIT 10');
console.log('用户列表:');
if (users.length === 0) {
console.log('❌ 没有找到任何用户数据');
} else {
users.forEach(user => {
console.log(`- ID: ${user.id}, 用户名: ${user.username}, 姓名: ${user.real_name}`);
});
}
} catch (error) {
console.error('❌ 查询用户失败:', error.message);
} finally {
await sequelize.close();
}
}
checkUsers();

View File

@@ -0,0 +1,86 @@
const mysql = require('mysql2/promise');
const fs = require('fs');
const path = require('path');
require('dotenv').config();
async function createRegulatoryTables() {
let connection;
try {
// 创建数据库连接
connection = await mysql.createConnection({
host: process.env.DB_HOST || '129.211.213.226',
port: process.env.DB_PORT || 9527,
user: process.env.DB_USER || 'root',
password: process.env.DB_PASSWORD || 'aiotAiot123!',
database: process.env.DB_DATABASE || 'nxxmdata',
multipleStatements: true
});
console.log('数据库连接成功');
// 读取SQL文件
const sqlFilePath = path.join(__dirname, 'regulatory_task_completion.sql');
const sqlContent = fs.readFileSync(sqlFilePath, 'utf8');
// 分割SQL语句按分号分割但忽略注释中的分号
const sqlStatements = sqlContent
.split('\n')
.filter(line => !line.trim().startsWith('--') && line.trim() !== '')
.join('\n')
.split(';')
.filter(statement => statement.trim() !== '');
console.log(`准备执行 ${sqlStatements.length} 条SQL语句`);
// 逐条执行SQL语句
for (let i = 0; i < sqlStatements.length; i++) {
const statement = sqlStatements[i].trim();
if (statement) {
try {
console.log(`执行第 ${i + 1} 条SQL语句...`);
await connection.execute(statement);
console.log(`${i + 1} 条SQL语句执行成功`);
} catch (error) {
console.error(`${i + 1} 条SQL语句执行失败:`, error.message);
console.error('SQL语句:', statement.substring(0, 100) + '...');
}
}
}
// 验证表是否创建成功
const [tables] = await connection.execute(`
SELECT TABLE_NAME, TABLE_COMMENT
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = ? AND TABLE_NAME LIKE 'regulatory_task_%'
`, [process.env.DB_DATABASE || 'nxxmdata']);
console.log('\n创建的监管任务结项相关表:');
tables.forEach(table => {
console.log(`- ${table.TABLE_NAME}: ${table.TABLE_COMMENT}`);
});
// 查询示例数据
const [sampleData] = await connection.execute(`
SELECT application_no, policy_no, customer_name, completion_status
FROM regulatory_task_completions
LIMIT 3
`);
console.log('\n示例数据:');
console.table(sampleData);
console.log('\n监管任务结项表创建完成');
} catch (error) {
console.error('创建表时发生错误:', error);
} finally {
if (connection) {
await connection.end();
console.log('数据库连接已关闭');
}
}
}
// 执行创建表操作
createRegulatoryTables();

View File

@@ -0,0 +1,53 @@
const { sequelize } = require('../config/database');
const fs = require('fs');
const path = require('path');
async function executeLivestockSchema() {
try {
// 确保使用正确的数据库
await sequelize.query('USE insurance_data');
console.log('✅ 已切换到 insurance_data 数据库');
// 读取SQL文件
const sqlFile = path.join(__dirname, 'livestock_insurance_schema.sql');
const sqlContent = fs.readFileSync(sqlFile, 'utf8');
// 分割SQL语句按分号分割但忽略注释中的分号
const statements = sqlContent
.split('\n')
.filter(line => !line.trim().startsWith('--') && line.trim() !== '')
.join('\n')
.split(';')
.filter(statement => statement.trim() !== '');
console.log('开始执行生资保单数据库表创建...');
// 逐个执行SQL语句
for (let i = 0; i < statements.length; i++) {
const statement = statements[i].trim();
if (statement) {
try {
console.log(`执行语句 ${i + 1}/${statements.length}...`);
await sequelize.query(statement);
console.log(`✅ 语句 ${i + 1} 执行成功`);
} catch (error) {
console.error(`❌ 语句 ${i + 1} 执行失败:`, error.message);
console.log('失败的语句:', statement.substring(0, 100) + '...');
}
}
}
console.log('✅ 生资保单数据库表创建完成');
// 验证表是否创建成功
const [results] = await sequelize.query("SHOW TABLES LIKE 'livestock_%'");
console.log('创建的表:', results.map(row => Object.values(row)[0]));
} catch (error) {
console.error('❌ 执行失败:', error.message);
} finally {
await sequelize.close();
}
}
executeLivestockSchema();

View File

@@ -0,0 +1,51 @@
console.log('🚀 开始修复admin权限...');
const { sequelize } = require('../config/database');
const Role = require('../models/Role');
async function fixAdminPermissions() {
try {
console.log('📡 连接数据库...');
// 测试数据库连接
await sequelize.authenticate();
console.log('✅ 数据库连接成功');
// 查询当前admin角色
console.log('🔍 查询当前admin角色...');
const adminRole = await Role.findOne({ where: { name: 'admin' } });
if (adminRole) {
console.log('📋 当前admin角色权限:', adminRole.permissions);
// 更新admin角色权限添加insurance相关权限
console.log('🔧 更新admin角色权限...');
const newPermissions = [
'user:read', 'user:create', 'user:update', 'user:delete',
'insurance:read', 'insurance:create', 'insurance:update', 'insurance:delete', 'insurance:review',
'insurance_type:read', 'insurance_type:create', 'insurance_type:update', 'insurance_type:delete', 'insurance_type:review',
'policy:read', 'policy:create', 'policy:update', 'policy:delete',
'claim:read', 'claim:create', 'claim:update', 'claim:review',
'system:read', 'system:update', 'system:admin'
];
await adminRole.update({
permissions: JSON.stringify(newPermissions)
});
console.log('✅ admin角色权限更新成功');
console.log('📋 新的admin权限:', newPermissions);
} else {
console.log('❌ 未找到admin角色');
}
} catch (error) {
console.error('❌ 执行失败:', error.message);
console.error('错误详情:', error);
} finally {
await sequelize.close();
console.log('\n✅ 数据库连接已关闭');
}
}
fixAdminPermissions().catch(console.error);

View File

@@ -0,0 +1,49 @@
console.log('🚀 开始修复权限...');
const { sequelize } = require('../config/database');
const Role = require('../models/Role');
async function fixPermissions() {
try {
console.log('📡 连接数据库...');
// 测试数据库连接
await sequelize.authenticate();
console.log('✅ 数据库连接成功');
// 查询当前agent角色
console.log('🔍 查询当前agent角色...');
const agentRole = await Role.findOne({ where: { name: 'agent' } });
if (agentRole) {
console.log('📋 当前agent角色权限:', agentRole.permissions);
// 更新agent角色权限
console.log('🔧 更新agent角色权限...');
await agentRole.update({
permissions: JSON.stringify(['insurance:read', 'insurance:create', 'policy:read', 'policy:create', 'claim:read', 'claim:create'])
});
console.log('✅ agent角色权限更新成功');
} else {
console.log('❌ 未找到agent角色');
}
// 查询所有角色权限确认
console.log('📋 查询当前所有角色权限配置...');
const roles = await Role.findAll();
console.log('\n当前角色权限配置:');
roles.forEach(role => {
console.log(`${role.name}: ${role.permissions}`);
});
} catch (error) {
console.error('❌ 执行失败:', error.message);
console.error('错误详情:', error);
} finally {
await sequelize.close();
console.log('\n✅ 数据库连接已关闭');
}
}
fixPermissions().catch(console.error);

View File

@@ -0,0 +1,41 @@
const { sequelize } = require('../config/database');
async function insertClaimsData() {
try {
console.log('开始插入理赔数据...');
// 插入理赔数据
await sequelize.query(`
INSERT INTO livestock_claims (
claim_no, policy_id, claim_type, incident_date, report_date,
incident_description, incident_location, affected_count,
claim_amount, claim_status, investigator_id,
reviewer_id, created_by, updated_by
) VALUES
('CL202303050001', 1, 'disease', '2023-03-01', '2023-03-02', '牛群感染口蹄疫导致5头牛死亡', '内蒙古自治区呼和浩特市赛罕区昭乌达路养殖场', 5, 35000.00, 'approved', 1, 1, 1, 1),
('CL202303100002', 2, 'natural_disaster', '2023-03-08', '2023-03-09', '暴雪导致羊舍倒塌造成10只羊死亡', '内蒙古自治区包头市昆都仑区钢铁大街养殖场', 10, 10000.00, 'investigating', 1, NULL, 1, 1),
('CL202302200003', 4, 'accident', '2023-02-18', '2023-02-19', '运输车辆事故导致3头牛受伤', '内蒙古自治区通辽市科尔沁区建国路', 3, 12000.00, 'pending', NULL, NULL, 1, 1)
`);
console.log('✅ 理赔数据插入成功');
// 验证数据插入结果
const [claimCount] = await sequelize.query("SELECT COUNT(*) as count FROM livestock_claims");
console.log(`数据库中共有 ${claimCount[0].count} 条理赔记录`);
} catch (error) {
console.error('❌ 插入理赔数据失败:', error.message);
if (error.errors) {
console.error('详细错误信息:');
error.errors.forEach(err => {
console.error(`- ${err.path}: ${err.message}`);
});
}
if (error.sql) {
console.error('SQL语句:', error.sql);
}
} finally {
await sequelize.close();
}
}
insertClaimsData();

View File

@@ -4,13 +4,13 @@ SET CHARACTER SET utf8mb4;
-- 更新默认角色权限
UPDATE roles SET
description = '系统管理员',
permissions = '["user:read","user:create","user:update","user:delete","insurance:read","insurance:create","insurance:update","insurance:delete","insurance:review","policy:read","policy:create","policy:update","policy:delete","claim:read","claim:create","claim:update","claim:review","system:read","system:update","system:admin"]'
permissions = '["user:read","user:create","user:update","user:delete","insurance:read","insurance:create","insurance:update","insurance:delete","insurance:review","policy:read","policy:create","policy:update","policy:delete","livestock_policy:read","livestock_policy:create","livestock_policy:update","livestock_policy:delete","claim:read","claim:create","claim:update","claim:review","system:read","system:update","system:admin"]'
WHERE name = 'admin';
-- 插入其他角色(如果不存在)
INSERT IGNORE INTO roles (name, description, permissions, status) VALUES
('agent', '保险代理人', '["insurance:read","insurance:create","policy:read","policy:create","claim:read","claim:create"]', 'active'),
('customer', '客户', '["insurance:read","policy:read","claim:read","claim:create"]', 'active');
('agent', '保险代理人', '["insurance:read","insurance:create","policy:read","policy:create","livestock_policy:read","livestock_policy:create","livestock_policy:update","claim:read","claim:create"]', 'active'),
('customer', '客户', '["insurance:read","policy:read","livestock_policy:read","claim:read","claim:create"]', 'active');
-- 插入默认保险类型
INSERT IGNORE INTO insurance_types (name, description, coverage_amount_min, coverage_amount_max, premium_rate, status) VALUES

View File

@@ -0,0 +1,235 @@
const { sequelize } = require('../models');
const { LivestockType, LivestockPolicy, LivestockClaim, User } = require('../models');
async function insertTestData() {
try {
console.log('开始插入生资保险测试数据...');
// 创建测试用户(如果不存在)
const [testUser] = await User.findOrCreate({
where: { username: 'test_admin' },
defaults: {
username: 'test_admin',
password: '$2b$10$example', // 示例密码哈希
real_name: '测试管理员',
email: 'test@example.com',
phone: '13800138000',
role_id: 1, // admin角色ID
status: 'active'
}
});
// 插入牲畜类型测试数据
const livestockTypes = [
{
name: '奶牛',
code: 'DAIRY_COW',
description: '用于产奶的牛类',
unit_price_min: 10000.00,
unit_price_max: 20000.00,
premium_rate: 0.05,
status: 'active'
},
{
name: '肉牛',
code: 'BEEF_COW',
description: '用于肉类生产的牛类',
unit_price_min: 8000.00,
unit_price_max: 16000.00,
premium_rate: 0.04,
status: 'active'
},
{
name: '生猪',
code: 'PIG',
description: '用于肉类生产的猪类',
unit_price_min: 1500.00,
unit_price_max: 2500.00,
premium_rate: 0.06,
status: 'active'
},
{
name: '羊',
code: 'SHEEP',
description: '包括山羊和绵羊',
unit_price_min: 600.00,
unit_price_max: 1000.00,
premium_rate: 0.05,
status: 'active'
}
];
console.log('插入牲畜类型数据...');
const createdTypes = await LivestockType.bulkCreate(livestockTypes, {
returning: true
});
console.log(`成功插入 ${createdTypes.length} 个牲畜类型`);
// 插入生资保单测试数据
const policies = [
{
policy_no: 'LP2024001',
farmer_name: '张三',
farmer_phone: '13800138001',
farmer_id_card: '110101199001011234',
livestock_type_id: createdTypes[0].id, // 奶牛
livestock_count: 10,
unit_value: 15000.00,
total_value: 150000.00,
premium_rate: 0.05,
premium_amount: 7500.00,
start_date: '2024-01-01',
end_date: '2024-12-31',
farm_name: '张三奶牛养殖场',
farm_address: '北京市朝阳区某某村123号',
farm_license: 'BJ2024001',
policy_status: 'active',
payment_status: 'paid',
payment_date: '2024-01-01',
notes: '优质奶牛保险',
created_by: testUser.id,
updated_by: testUser.id
},
{
policy_no: 'LP2024002',
farmer_name: '李四',
farmer_phone: '13800138002',
farmer_id_card: '110101199002021234',
livestock_type_id: createdTypes[1].id, // 肉牛
livestock_count: 20,
unit_value: 12000.00,
total_value: 240000.00,
premium_rate: 0.04,
premium_amount: 9600.00,
start_date: '2024-02-01',
end_date: '2025-01-31',
farm_name: '李四肉牛养殖场',
farm_address: '北京市海淀区某某村456号',
farm_license: 'BJ2024002',
policy_status: 'active',
payment_status: 'paid',
payment_date: '2024-02-01',
notes: '肉牛养殖保险',
created_by: testUser.id,
updated_by: testUser.id
},
{
policy_no: 'LP2024003',
farmer_name: '王五',
farmer_phone: '13800138003',
farmer_id_card: '110101199003031234',
livestock_type_id: createdTypes[2].id, // 生猪
livestock_count: 100,
unit_value: 2000.00,
total_value: 200000.00,
premium_rate: 0.06,
premium_amount: 12000.00,
start_date: '2024-03-01',
end_date: '2025-02-28',
farm_name: '王五生猪养殖场',
farm_address: '北京市昌平区某某村789号',
farm_license: 'BJ2024003',
policy_status: 'active',
payment_status: 'unpaid',
notes: '生猪养殖保险',
created_by: testUser.id,
updated_by: testUser.id
},
{
policy_no: 'LP2024004',
farmer_name: '赵六',
farmer_phone: '13800138004',
farmer_id_card: '110101199004041234',
livestock_type_id: createdTypes[3].id, // 羊
livestock_count: 50,
unit_value: 800.00,
total_value: 40000.00,
premium_rate: 0.05,
premium_amount: 2000.00,
start_date: '2024-04-01',
end_date: '2025-03-31',
farm_name: '赵六羊群养殖场',
farm_address: '北京市房山区某某村101号',
farm_license: 'BJ2024004',
policy_status: 'active',
payment_status: 'partial',
payment_date: '2024-04-01',
notes: '羊群保险',
created_by: testUser.id,
updated_by: testUser.id
}
];
console.log('插入生资保单数据...');
const createdPolicies = await LivestockPolicy.bulkCreate(policies, {
returning: true
});
console.log(`成功插入 ${createdPolicies.length} 个生资保单`);
// 插入生资理赔测试数据
const claims = [
{
claim_no: 'LC2024001',
policy_id: createdPolicies[0].id,
claim_amount: 30000.00,
incident_date: '2024-06-15',
incident_description: '奶牛因疾病死亡2头',
claim_status: 'paid',
investigator_id: testUser.id,
investigation_report: '经现场调查,确认奶牛因疫病死亡,符合保险条款',
investigation_date: '2024-06-20',
reviewer_id: testUser.id,
review_notes: '经核实,符合理赔条件',
review_date: '2024-06-25',
payment_date: '2024-07-01',
payment_amount: 30000.00,
supporting_documents: JSON.stringify([
{ type: 'death_certificate', url: '/documents/death_cert_001.pdf' },
{ type: 'veterinary_report', url: '/documents/vet_report_001.pdf' }
]),
created_by: testUser.id,
updated_by: testUser.id
},
{
claim_no: 'LC2024002',
policy_id: createdPolicies[1].id,
claim_amount: 24000.00,
incident_date: '2024-07-20',
incident_description: '肉牛因自然灾害死亡2头',
claim_status: 'investigating',
investigator_id: testUser.id,
investigation_report: '正在进行现场调查',
investigation_date: '2024-07-25',
supporting_documents: JSON.stringify([
{ type: 'incident_report', url: '/documents/incident_002.pdf' }
]),
created_by: testUser.id,
updated_by: testUser.id
}
];
console.log('插入生资理赔数据...');
const createdClaims = await LivestockClaim.bulkCreate(claims, {
returning: true
});
console.log(`成功插入 ${createdClaims.length} 个生资理赔记录`);
console.log('生资保险测试数据插入完成!');
console.log('数据统计:');
console.log(`- 牲畜类型: ${createdTypes.length}`);
console.log(`- 生资保单: ${createdPolicies.length}`);
console.log(`- 理赔记录: ${createdClaims.length}`);
} catch (error) {
console.error('插入测试数据失败:', error);
} finally {
await sequelize.close();
}
}
// 如果直接运行此脚本
if (require.main === module) {
insertTestData();
}
module.exports = insertTestData;

View File

@@ -0,0 +1,80 @@
-- 插入生资保险测试数据
-- 插入牲畜类型测试数据
INSERT INTO livestock_types (name, code, description, unit_price_min, unit_price_max, premium_rate, status) VALUES
('奶牛', 'DAIRY_COW', '用于产奶的牛类', 10000.00, 20000.00, 0.0500, 'active'),
('肉牛', 'BEEF_COW', '用于肉类生产的牛类', 8000.00, 16000.00, 0.0400, 'active'),
('生猪', 'PIG', '用于肉类生产的猪类', 1500.00, 2500.00, 0.0600, 'active'),
('', 'SHEEP', '包括山羊和绵羊', 600.00, 1000.00, 0.0500, 'active');
-- 插入生资保单测试数据
INSERT INTO livestock_policies (
policy_no, farmer_name, farmer_phone, farmer_id_card, livestock_type_id,
livestock_count, unit_value, total_value, premium_rate, premium_amount,
start_date, end_date, farm_name, farm_address, farm_license,
policy_status, payment_status, payment_date, notes, created_by, updated_by
) VALUES
(
'LP2024001', '张三', '13800138001', '110101199001011234',
(SELECT id FROM livestock_types WHERE code = 'DAIRY_COW'),
10, 15000.00, 150000.00, 0.0500, 7500.00,
'2024-01-01', '2024-12-31', '张三奶牛养殖场', '北京市朝阳区某某村123号', 'BJ2024001',
'active', 'paid', '2024-01-01', '优质奶牛保险', 1, 1
),
(
'LP2024002', '李四', '13800138002', '110101199002021234',
(SELECT id FROM livestock_types WHERE code = 'BEEF_COW'),
20, 12000.00, 240000.00, 0.0400, 9600.00,
'2024-02-01', '2025-01-31', '李四肉牛养殖场', '北京市海淀区某某村456号', 'BJ2024002',
'active', 'paid', '2024-02-01', '肉牛养殖保险', 1, 1
),
(
'LP2024003', '王五', '13800138003', '110101199003031234',
(SELECT id FROM livestock_types WHERE code = 'PIG'),
100, 2000.00, 200000.00, 0.0600, 12000.00,
'2024-03-01', '2025-02-28', '王五生猪养殖场', '北京市昌平区某某村789号', 'BJ2024003',
'active', 'unpaid', NULL, '生猪养殖保险', 1, 1
),
(
'LP2024004', '赵六', '13800138004', '110101199004041234',
(SELECT id FROM livestock_types WHERE code = 'SHEEP'),
50, 800.00, 40000.00, 0.0500, 2000.00,
'2024-04-01', '2025-03-31', '赵六羊群养殖场', '北京市房山区某某村101号', 'BJ2024004',
'active', 'partial', '2024-04-01', '羊群保险', 1, 1
);
-- 插入生资理赔测试数据
INSERT INTO livestock_claims (
claim_no, policy_id, claim_amount, incident_date, incident_description,
claim_status, investigator_id, investigation_report, investigation_date,
reviewer_id, review_notes, review_date, payment_date, payment_amount,
supporting_documents, created_by, updated_by
) VALUES
(
'LC2024001',
(SELECT id FROM livestock_policies WHERE policy_no = 'LP2024001'),
30000.00, '2024-06-15', '奶牛因疾病死亡2头',
'paid', 1, '经现场调查,确认奶牛因疫病死亡,符合保险条款', '2024-06-20',
1, '经核实,符合理赔条件', '2024-06-25', '2024-07-01', 30000.00,
'[{"type":"death_certificate","url":"/documents/death_cert_001.pdf"},{"type":"veterinary_report","url":"/documents/vet_report_001.pdf"}]',
1, 1
),
(
'LC2024002',
(SELECT id FROM livestock_policies WHERE policy_no = 'LP2024002'),
24000.00, '2024-07-20', '肉牛因自然灾害死亡2头',
'investigating', 1, '正在进行现场调查', '2024-07-25',
NULL, NULL, NULL, NULL, NULL,
'[{"type":"incident_report","url":"/documents/incident_002.pdf"}]',
1, 1
);
-- 查询插入结果
SELECT '牲畜类型数据:' as info;
SELECT id, name, code, unit_price_min, unit_price_max, premium_rate, status FROM livestock_types;
SELECT '生资保单数据:' as info;
SELECT id, policy_no, farmer_name, livestock_count, total_value, policy_status, payment_status FROM livestock_policies;
SELECT '理赔记录数据:' as info;
SELECT id, claim_no, claim_amount, incident_date, claim_status FROM livestock_claims;

View File

@@ -0,0 +1,81 @@
const { sequelize } = require('../config/database');
async function insertTestData() {
try {
// 确保使用正确的数据库
await sequelize.query('USE insurance_data');
console.log('✅ 已切换到 insurance_data 数据库');
console.log('开始插入测试数据...');
// 1. 插入牲畜类型数据
await sequelize.query(`
INSERT IGNORE INTO livestock_types (name, code, description, unit_price_min, unit_price_max, premium_rate, status)
VALUES
('牛', 'CATTLE', '肉牛、奶牛等牛类牲畜', 5000.00, 30000.00, 0.0050, 'active'),
('羊', 'SHEEP', '绵羊、山羊等羊类牲畜', 500.00, 3000.00, 0.0060, 'active'),
('猪', 'PIG', '生猪、种猪等猪类牲畜', 1000.00, 5000.00, 0.0040, 'active'),
('鸡', 'CHICKEN', '肉鸡、蛋鸡等鸡类家禽', 20.00, 100.00, 0.0080, 'active'),
('鸭', 'DUCK', '肉鸭、蛋鸭等鸭类家禽', 30.00, 150.00, 0.0070, 'active')
`);
console.log('✅ 牲畜类型数据插入成功');
// 2. 插入生资保单数据
await sequelize.query(`
INSERT INTO livestock_policies (
policy_no, livestock_type_id, policyholder_name, policyholder_id_card, policyholder_phone,
policyholder_address, farm_address, livestock_count, unit_value, total_value,
premium_amount, start_date, end_date, policy_status,
created_by, updated_by
) VALUES
('202209312456789', 1, '张三', '150102198001011234', '13800138001', '内蒙古自治区呼和浩特市赛罕区昭乌达路', '内蒙古自治区呼和浩特市赛罕区昭乌达路养殖场', 50, 8000.00, 400000.00, 2000.00, '2023-03-05', '2024-03-04', 'active', 1, 1),
('202209314567890', 2, '李四', '150203198002022345', '13800138002', '内蒙古自治区包头市昆都仑区钢铁大街', '内蒙古自治区包头市昆都仑区钢铁大街养殖场', 100, 1200.00, 120000.00, 720.00, '2023-03-10', '2024-03-09', 'active', 1, 1),
('202209315432100', 3, '王五', '150602198003033456', '13800138003', '内蒙古自治区鄂尔多斯市东胜区铁西路', '内蒙古自治区鄂尔多斯市东胜区铁西路养殖场', 200, 2500.00, 500000.00, 2000.00, '2023-02-15', '2024-02-14', 'active', 1, 1),
('202203316542100', 1, '赵六', '150502198004044567', '13800138004', '内蒙古自治区通辽市科尔沁区建国路', '内蒙古自治区通辽市科尔沁区建国路养殖场', 30, 12000.00, 360000.00, 1800.00, '2023-02-20', '2024-02-19', 'active', 1, 1),
('202203311087654', 2, '孙七', '150802198005055678', '13800138005', '内蒙古自治区巴彦淖尔市临河区胜利路', '内蒙古自治区巴彦淖尔市临河区胜利路养殖场', 80, 1500.00, 120000.00, 720.00, '2023-01-30', '2024-01-29', 'active', 1, 1),
('202203178001234', 4, '周八', '150902198006066789', '13800138006', '内蒙古自治区乌兰察布市集宁区新华街', '内蒙古自治区乌兰察布市集宁区新华街养殖场', 1000, 50.00, 50000.00, 400.00, '2023-01-15', '2024-01-14', 'active', 1, 1),
('202203112345678', 5, '吴九', '152502198007077890', '13800138007', '内蒙古自治区锡林郭勒盟锡林浩特市额尔敦路', '内蒙古自治区锡林郭勒盟锡林浩特市额尔敦路养殖场', 800, 80.00, 64000.00, 448.00, '2023-01-10', '2024-01-09', 'active', 1, 1),
('202203134567890', 1, '郑十', '150702198008088901', '13800138008', '内蒙古自治区呼伦贝尔市海拉尔区胜利大街', '内蒙古自治区呼伦贝尔市海拉尔区胜利大街养殖场', 25, 15000.00, 375000.00, 1875.00, '2022-12-20', '2023-12-19', 'expired', 1, 1),
('202203154321098', 3, '陈十一', '150402198009099012', '13800138009', '内蒙古自治区赤峰市红山区钢铁街', '内蒙古自治区赤峰市红山区钢铁街养殖场', 150, 3000.00, 450000.00, 1800.00, '2022-12-15', '2023-12-14', 'expired', 1, 1)
`);
console.log('✅ 生资保单数据插入成功');
// 3. 插入理赔数据
await sequelize.query(`
INSERT INTO livestock_claims (
claim_no, policy_id, claim_type, incident_date, report_date,
incident_description, incident_location, affected_count,
claim_amount, claim_status, investigator_id,
reviewer_id, created_by, updated_by
) VALUES
('CL202303050001', 1, 'disease', '2023-03-01', '2023-03-02', '牛群感染口蹄疫导致5头牛死亡', '内蒙古自治区呼和浩特市赛罕区昭乌达路养殖场', 5, 35000.00, 'approved', 1, 1, 1, 1),
('CL202303100002', 2, 'natural_disaster', '2023-03-08', '2023-03-09', '暴雪导致羊舍倒塌造成10只羊死亡', '内蒙古自治区包头市昆都仑区钢铁大街养殖场', 10, 10000.00, 'investigating', 1, NULL, 1, 1),
('CL202302200003', 4, 'accident', '2023-02-18', '2023-02-19', '运输车辆事故导致3头牛受伤', '内蒙古自治区通辽市科尔沁区建国路', 3, 12000.00, 'pending', NULL, NULL, 1, 1)
`);
console.log('✅ 理赔数据插入成功');
// 验证数据插入结果
const [policyCount] = await sequelize.query("SELECT COUNT(*) as count FROM livestock_policies");
const [claimCount] = await sequelize.query("SELECT COUNT(*) as count FROM livestock_claims");
const [typeCount] = await sequelize.query("SELECT COUNT(*) as count FROM livestock_types");
console.log(`✅ 数据插入完成:`);
console.log(` - 牲畜类型: ${typeCount[0].count}`);
console.log(` - 生资保单: ${policyCount[0].count}`);
console.log(` - 理赔记录: ${claimCount[0].count}`);
} catch (error) {
console.error('❌ 插入测试数据失败:', error.message);
if (error.errors) {
console.error('详细错误信息:');
error.errors.forEach(err => {
console.error(`- ${err.path}: ${err.message}`);
});
}
console.error('完整错误:', error);
} finally {
await sequelize.close();
}
}
insertTestData();

View File

@@ -0,0 +1,129 @@
-- 生资保单相关数据库表结构
-- 创建时间: 2024-01-XX
-- 说明: 包含牲畜类型、生资保单、理赔记录等表
USE insurance_data;
-- 1. 生资保险类型表(牲畜类型)
CREATE TABLE IF NOT EXISTS livestock_types (
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '牲畜类型ID',
name VARCHAR(50) NOT NULL UNIQUE COMMENT '牲畜类型名称(如:牛、羊、猪等)',
code VARCHAR(20) NOT NULL UNIQUE COMMENT '牲畜类型代码',
description TEXT NULL COMMENT '牲畜类型描述',
unit_price_min DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '单头最低价值',
unit_price_max DECIMAL(10,2) NOT NULL DEFAULT 50000.00 COMMENT '单头最高价值',
premium_rate DECIMAL(5,4) NOT NULL DEFAULT 0.0050 COMMENT '保险费率',
status ENUM('active', 'inactive') NOT NULL DEFAULT 'active' COMMENT '状态',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX idx_livestock_type_name (name),
INDEX idx_livestock_type_code (code),
INDEX idx_livestock_type_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='牲畜类型表';
-- 2. 生资保单表
CREATE TABLE IF NOT EXISTS livestock_policies (
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '生资保单ID',
policy_no VARCHAR(50) NOT NULL UNIQUE COMMENT '保单编号',
policyholder_name VARCHAR(100) NOT NULL COMMENT '投保人姓名',
policyholder_id_card VARCHAR(18) NOT NULL COMMENT '投保人身份证号',
policyholder_phone VARCHAR(20) NOT NULL COMMENT '投保人手机号',
policyholder_address VARCHAR(500) NOT NULL COMMENT '投保人地址',
livestock_type_id INT NOT NULL COMMENT '牲畜类型ID',
livestock_count INT NOT NULL DEFAULT 1 COMMENT '参保数量',
unit_value DECIMAL(10,2) NOT NULL COMMENT '单头价值',
total_value DECIMAL(15,2) NOT NULL COMMENT '总保额',
premium_amount DECIMAL(10,2) NOT NULL COMMENT '保费金额',
start_date DATE NOT NULL COMMENT '保险开始日期',
end_date DATE NOT NULL COMMENT '保险结束日期',
farm_name VARCHAR(200) NULL COMMENT '养殖场名称',
farm_address VARCHAR(500) NOT NULL COMMENT '养殖场地址',
farm_license VARCHAR(100) NULL COMMENT '养殖许可证号',
policy_status ENUM('active', 'expired', 'cancelled', 'suspended') NOT NULL DEFAULT 'active' COMMENT '保单状态',
payment_status ENUM('paid', 'unpaid', 'partial') NOT NULL DEFAULT 'unpaid' COMMENT '支付状态',
payment_date DATE NULL COMMENT '支付日期',
policy_document_url VARCHAR(500) NULL COMMENT '保单文件URL',
notes TEXT NULL COMMENT '备注信息',
created_by INT NULL COMMENT '创建人ID',
updated_by INT NULL COMMENT '更新人ID',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
UNIQUE INDEX idx_livestock_policy_no (policy_no),
INDEX idx_livestock_policy_holder (policyholder_name),
INDEX idx_livestock_policy_id_card (policyholder_id_card),
INDEX idx_livestock_policy_phone (policyholder_phone),
INDEX idx_livestock_policy_status (policy_status),
INDEX idx_livestock_policy_payment_status (payment_status),
INDEX idx_livestock_policy_dates (start_date, end_date),
INDEX idx_livestock_policy_type (livestock_type_id),
FOREIGN KEY (livestock_type_id) REFERENCES livestock_types(id) ON DELETE RESTRICT,
FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL,
FOREIGN KEY (updated_by) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='生资保单表';
-- 3. 生资保单理赔表
CREATE TABLE IF NOT EXISTS livestock_claims (
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '理赔ID',
claim_no VARCHAR(50) NOT NULL UNIQUE COMMENT '理赔编号',
policy_id INT NOT NULL COMMENT '关联的生资保单ID',
claim_type ENUM('death', 'disease', 'accident', 'natural_disaster') NOT NULL COMMENT '理赔类型',
affected_count INT NOT NULL DEFAULT 1 COMMENT '受损数量',
claim_amount DECIMAL(15,2) NOT NULL COMMENT '理赔金额',
incident_date DATE NOT NULL COMMENT '事故发生日期',
report_date DATE NOT NULL COMMENT '报案日期',
incident_description TEXT NOT NULL COMMENT '事故描述',
incident_location VARCHAR(500) NOT NULL COMMENT '事故地点',
claim_status ENUM('pending', 'investigating', 'approved', 'rejected', 'paid') NOT NULL DEFAULT 'pending' COMMENT '理赔状态',
investigator_id INT NULL COMMENT '调查员ID',
investigation_report TEXT NULL COMMENT '调查报告',
investigation_date DATE NULL COMMENT '调查日期',
reviewer_id INT NULL COMMENT '审核人ID',
review_notes TEXT NULL COMMENT '审核备注',
review_date DATE NULL COMMENT '审核日期',
payment_date DATE NULL COMMENT '支付日期',
payment_amount DECIMAL(15,2) NULL COMMENT '实际支付金额',
supporting_documents JSON NULL COMMENT '支持文件',
created_by INT NULL COMMENT '创建人ID',
updated_by INT NULL COMMENT '更新人ID',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
UNIQUE INDEX idx_livestock_claim_no (claim_no),
INDEX idx_livestock_claim_policy (policy_id),
INDEX idx_livestock_claim_status (claim_status),
INDEX idx_livestock_claim_type (claim_type),
INDEX idx_livestock_claim_date (incident_date),
INDEX idx_livestock_claim_investigator (investigator_id),
INDEX idx_livestock_claim_reviewer (reviewer_id),
FOREIGN KEY (policy_id) REFERENCES livestock_policies(id) ON DELETE RESTRICT,
FOREIGN KEY (investigator_id) REFERENCES users(id) ON DELETE SET NULL,
FOREIGN KEY (reviewer_id) REFERENCES users(id) ON DELETE SET NULL,
FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL,
FOREIGN KEY (updated_by) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='生资保单理赔表';
-- 插入默认牲畜类型数据
INSERT INTO livestock_types (name, code, description, unit_price_min, unit_price_max, premium_rate, status) VALUES
('', 'CATTLE', '肉牛、奶牛等牛类牲畜', 5000.00, 30000.00, 0.0050, 'active'),
('', 'SHEEP', '绵羊、山羊等羊类牲畜', 500.00, 3000.00, 0.0060, 'active'),
('', 'PIG', '生猪、种猪等猪类牲畜', 1000.00, 5000.00, 0.0040, 'active'),
('', 'CHICKEN', '肉鸡、蛋鸡等鸡类家禽', 20.00, 100.00, 0.0080, 'active'),
('', 'DUCK', '肉鸭、蛋鸭等鸭类家禽', 30.00, 150.00, 0.0070, 'active');

View File

@@ -0,0 +1,108 @@
-- 监管任务结项功能数据库表结构
-- 创建时间: 2025-01-23
-- 数据库: MySQL 8.0+
USE nxxmdata;
-- 1. 监管任务结项表
CREATE TABLE IF NOT EXISTS regulatory_task_completions (
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '结项ID',
application_no VARCHAR(50) NOT NULL COMMENT '申请编号',
policy_no VARCHAR(50) NOT NULL COMMENT '保单编号',
product_name VARCHAR(100) NOT NULL COMMENT '产品名称',
customer_name VARCHAR(100) NOT NULL COMMENT '客户姓名',
id_type ENUM('身份证', '护照', '军官证', '其他') NOT NULL DEFAULT '身份证' COMMENT '证件类型',
id_number VARCHAR(50) NOT NULL COMMENT '证件号码',
product_type_premium DECIMAL(15,2) NOT NULL COMMENT '产品类型保费',
regulatory_effective_premium DECIMAL(15,2) NOT NULL COMMENT '监管生效保费',
insurance_period VARCHAR(100) NOT NULL COMMENT '保险期间',
insurance_amount DECIMAL(15,2) NOT NULL COMMENT '保险金额',
payment_method VARCHAR(50) NOT NULL COMMENT '缴费方式',
payment_period VARCHAR(50) NOT NULL COMMENT '缴费期间',
-- 结项相关字段
completion_status ENUM('pending', 'in_progress', 'completed', 'rejected') NOT NULL DEFAULT 'pending' COMMENT '结项状态',
completion_date DATE NULL COMMENT '结项日期',
completion_notes TEXT NULL COMMENT '结项备注',
completion_documents JSON NULL COMMENT '结项文档',
-- 审核相关字段
reviewer_id INT NULL COMMENT '审核人ID',
review_date DATE NULL COMMENT '审核日期',
review_notes TEXT NULL COMMENT '审核备注',
-- 关联字段(暂不设置外键约束)
policy_id INT NULL COMMENT '关联保单ID',
application_id INT NULL COMMENT '关联申请ID',
insurance_type_id INT NULL COMMENT '保险类型ID',
customer_id INT NULL COMMENT '客户ID',
-- 系统字段
created_by INT NULL COMMENT '创建人ID',
updated_by INT NULL COMMENT '更新人ID',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
-- 索引
UNIQUE INDEX idx_application_no (application_no),
INDEX idx_policy_no (policy_no),
INDEX idx_customer_name (customer_name),
INDEX idx_id_number (id_number),
INDEX idx_completion_status (completion_status),
INDEX idx_completion_date (completion_date),
INDEX idx_reviewer_id (reviewer_id),
INDEX idx_policy_id (policy_id),
INDEX idx_application_id (application_id),
INDEX idx_customer_id (customer_id),
INDEX idx_created_at (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='监管任务结项表';
-- 2. 监管任务结项操作日志表
CREATE TABLE IF NOT EXISTS regulatory_task_completion_logs (
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '日志ID',
completion_id INT NOT NULL COMMENT '结项ID',
operation_type ENUM('create', 'update', 'review', 'approve', 'reject', 'complete') NOT NULL COMMENT '操作类型',
operation_description TEXT NOT NULL COMMENT '操作描述',
old_status VARCHAR(50) NULL COMMENT '原状态',
new_status VARCHAR(50) NULL COMMENT '新状态',
operator_id INT NOT NULL COMMENT '操作人ID',
operation_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间',
ip_address VARCHAR(45) NULL COMMENT 'IP地址',
user_agent TEXT NULL COMMENT '用户代理',
INDEX idx_completion_id (completion_id),
INDEX idx_operation_type (operation_type),
INDEX idx_operator_id (operator_id),
INDEX idx_operation_time (operation_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='监管任务结项操作日志表';
-- 3. 监管任务结项附件表
CREATE TABLE IF NOT EXISTS regulatory_task_completion_attachments (
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '附件ID',
completion_id INT NOT NULL COMMENT '结项ID',
file_name VARCHAR(255) NOT NULL COMMENT '文件名',
file_path VARCHAR(500) NOT NULL COMMENT '文件路径',
file_size BIGINT NOT NULL COMMENT '文件大小(字节)',
file_type VARCHAR(50) NOT NULL COMMENT '文件类型',
mime_type VARCHAR(100) NOT NULL COMMENT 'MIME类型',
upload_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '上传时间',
uploaded_by INT NOT NULL COMMENT '上传人ID',
INDEX idx_completion_id (completion_id),
INDEX idx_file_type (file_type),
INDEX idx_upload_time (upload_time),
INDEX idx_uploaded_by (uploaded_by)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='监管任务结项附件表';
-- 插入示例数据
INSERT INTO regulatory_task_completions (
application_no, policy_no, product_name, customer_name, id_type, id_number,
product_type_premium, regulatory_effective_premium, insurance_period,
insurance_amount, payment_method, payment_period, completion_status
) VALUES
('APP20250123001', 'POL20250123001', '养殖保险', '张三', '身份证', '110101199001011234',
5000.00, 4800.00, '2025-01-01至2025-12-31', 100000.00, '年缴', '1年', 'pending'),
('APP20250123002', 'POL20250123002', '牲畜保险', '李四', '身份证', '110101199002021234',
3000.00, 2900.00, '2025-01-01至2025-12-31', 80000.00, '年缴', '1年', 'in_progress'),
('APP20250123003', 'POL20250123003', '农业保险', '王五', '身份证', '110101199003031234',
4000.00, 3800.00, '2025-01-01至2025-12-31', 90000.00, '年缴', '1年', 'completed');

View File

@@ -0,0 +1,269 @@
const axios = require('axios');
// 配置API基础URL
const API_BASE_URL = 'http://localhost:3000/api';
// 创建axios实例
const api = axios.create({
baseURL: API_BASE_URL,
timeout: 10000
});
// 测试数据
const testData = {
username: 'admin',
password: '123456'
};
let authToken = '';
// 测试函数
async function testLogin() {
console.log('\n=== 测试1: 用户登录 ===');
try {
const response = await api.post('/auth/login', testData);
console.log('✅ 登录成功');
console.log('响应数据:', JSON.stringify(response.data, null, 2));
if (response.data.code === 200 && response.data.data.token) {
authToken = response.data.data.token;
// 设置后续请求的认证头
api.defaults.headers.common['Authorization'] = `Bearer ${authToken}`;
return true;
}
return false;
} catch (error) {
console.log('❌ 登录失败:', error.response?.data || error.message);
return false;
}
}
async function testLivestockPolicyList() {
console.log('\n=== 测试2: 获取生资保单列表 ===');
try {
const response = await api.get('/livestock-policies');
console.log('✅ 获取生资保单列表成功');
console.log('响应数据:', JSON.stringify(response.data, null, 2));
return response.data.data?.list || [];
} catch (error) {
console.log('❌ 获取生资保单列表失败:', error.response?.data || error.message);
return [];
}
}
async function testLivestockTypes() {
console.log('\n=== 测试3: 获取牲畜类型列表 ===');
try {
const response = await api.get('/livestock-types?status=active');
console.log('✅ 获取牲畜类型列表成功');
console.log('响应数据:', JSON.stringify(response.data, null, 2));
// 根据实际的响应格式解析数据
const list = response.data.data || [];
// console.log('🔍 解析出的牲畜类型数组长度:', list.length);
// if (list.length > 0) {
// console.log('🔍 第一个牲畜类型:', list[0]);
// }
return list;
} catch (error) {
console.log('❌ 获取牲畜类型列表失败:', error.response?.data || error.message);
return [];
}
}
async function testCreateLivestockPolicy(livestockTypes) {
console.log('\n=== 测试4: 创建生资保单 ===');
// console.log('🔍 接收到的牲畜类型参数:', livestockTypes);
// console.log('🔍 牲畜类型数组长度:', livestockTypes ? livestockTypes.length : 'undefined');
if (!livestockTypes || livestockTypes.length === 0) {
console.log('❌ 无法创建保单:没有可用的牲畜类型');
return null;
}
const livestockType = livestockTypes[0];
const livestockCount = 10;
const unitValue = 5000;
const totalValue = livestockCount * unitValue;
const premiumAmount = totalValue * parseFloat(livestockType.premium_rate);
const newPolicy = {
policy_no: `LP${Date.now()}${Math.random().toString(36).substr(2, 9).toUpperCase()}`,
livestock_type_id: livestockType.id,
policyholder_name: '测试农户',
policyholder_id_card: '110101199001011234',
policyholder_phone: '13800138000',
policyholder_address: '北京市朝阳区测试街道123号',
farm_address: '北京市朝阳区测试街道123号',
livestock_count: livestockCount,
unit_value: unitValue,
total_value: totalValue,
premium_amount: premiumAmount,
start_date: '2024-01-01 08:00:00',
end_date: '2024-12-31 08:00:00',
policy_status: 'active',
payment_status: 'unpaid',
created_by: 1
};
try {
const response = await api.post('/livestock-policies', newPolicy);
console.log('✅ 创建生资保单成功');
console.log('响应数据:', JSON.stringify(response.data, null, 2));
return response.data.data;
} catch (error) {
console.log('❌ 创建生资保单失败:', error.response?.data || error.message);
return null;
}
}
async function testUpdateLivestockPolicy(policyId) {
console.log('\n=== 测试5: 更新生资保单 ===');
const updateData = {
farmer_name: '测试农户(已更新)',
livestock_count: 15,
total_value: 75000,
premium_amount: 750
};
try {
const response = await api.put(`/livestock-policies/${policyId}`, updateData);
console.log('✅ 更新生资保单成功');
console.log('响应数据:', JSON.stringify(response.data, null, 2));
return response.data.data;
} catch (error) {
console.log('❌ 更新生资保单失败:', error.response?.data || error.message);
return null;
}
}
async function testGetLivestockPolicyDetail(policyId) {
console.log('\n=== 测试6: 获取生资保单详情 ===');
try {
const response = await api.get(`/livestock-policies/${policyId}`);
console.log('✅ 获取生资保单详情成功');
console.log('响应数据:', JSON.stringify(response.data, null, 2));
return response.data.data;
} catch (error) {
console.log('❌ 获取生资保单详情失败:', error.response?.data || error.message);
return null;
}
}
async function testUpdatePolicyStatus(policyId) {
console.log('\n=== 测试7: 更新保单状态 ===');
try {
const response = await api.patch(`/livestock-policies/${policyId}/status`, {
policy_status: 'active'
});
console.log('✅ 更新保单状态成功');
console.log('响应数据:', JSON.stringify(response.data, null, 2));
return response.data.data;
} catch (error) {
console.log('❌ 更新保单状态失败:', error.response?.data || error.message);
return null;
}
}
async function testSearchLivestockPolicies() {
console.log('\n=== 测试8: 搜索生资保单 ===');
try {
const response = await api.get('/livestock-policies', {
params: {
farmer_name: '测试',
policy_status: 'active',
page: 1,
limit: 10
}
});
console.log('✅ 搜索生资保单成功');
console.log('响应数据:', JSON.stringify(response.data, null, 2));
return response.data.data?.list || [];
} catch (error) {
console.log('❌ 搜索生资保单失败:', error.response?.data || error.message);
return [];
}
}
async function testDeleteLivestockPolicy(policyId) {
console.log('\n=== 测试9: 删除生资保单 ===');
try {
const response = await api.delete(`/livestock-policies/${policyId}`);
console.log('✅ 删除生资保单成功');
console.log('响应数据:', JSON.stringify(response.data, null, 2));
return true;
} catch (error) {
console.log('❌ 删除生资保单失败:', error.response?.data || error.message);
return false;
}
}
// 主测试函数
async function runTests() {
console.log('🚀 开始前端集成测试...');
console.log('测试API基础URL:', API_BASE_URL);
try {
// 1. 测试登录
const loginSuccess = await testLogin();
if (!loginSuccess) {
console.log('\n❌ 登录失败,终止测试');
return;
}
// 2. 测试获取保单列表
const initialList = await testLivestockPolicyList();
console.log(`\n📊 当前保单数量: ${initialList.length}`);
// 3. 测试获取牲畜类型
const livestockTypes = await testLivestockTypes();
// 4. 测试创建保单
const newPolicy = await testCreateLivestockPolicy(livestockTypes);
if (!newPolicy) {
console.log('\n❌ 创建保单失败,跳过后续测试');
return;
}
const policyId = newPolicy.id;
console.log(`\n📝 创建的保单ID: ${policyId}`);
// 5. 测试获取保单详情
await testGetLivestockPolicyDetail(policyId);
// 6. 测试更新保单
await testUpdateLivestockPolicy(policyId);
// 7. 测试更新保单状态
await testUpdatePolicyStatus(policyId);
// 8. 测试搜索保单
await testSearchLivestockPolicies();
// 9. 测试删除保单
await testDeleteLivestockPolicy(policyId);
// 10. 再次获取保单列表验证删除
const finalList = await testLivestockPolicyList();
console.log(`\n📊 删除后保单数量: ${finalList.length}`);
console.log('\n🎉 所有测试完成!');
} catch (error) {
console.error('\n💥 测试过程中发生错误:', error);
}
}
// 运行测试
runTests().then(() => {
console.log('\n✨ 测试脚本执行完毕');
process.exit(0);
}).catch(error => {
console.error('\n💥 测试脚本执行失败:', error);
process.exit(1);
});

View File

@@ -0,0 +1,56 @@
const axios = require('axios');
// 测试登录功能
async function testLogin() {
const baseURL = 'http://localhost:3000';
console.log('=== 开始测试登录功能 ===\n');
try {
// 测试1: 正确的用户名和密码
console.log('测试1: 使用正确的用户名和密码登录');
const loginResponse = await axios.post(`${baseURL}/api/auth/login`, {
username: 'admin',
password: '123456'
});
console.log('登录成功:', loginResponse.data);
const token = loginResponse.data.data.token;
// 测试2使用token访问受保护的API
console.log('\n测试2使用token访问生资保单API...');
const protectedResponse = await axios.get('http://localhost:3000/api/livestock-policies', {
headers: {
'Authorization': `Bearer ${token}`
}
});
console.log('✓ 带token访问成功:', protectedResponse.data);
// 测试3: 错误的用户名和密码
console.log('\n测试3: 使用错误的用户名和密码');
try {
await axios.post(`${baseURL}/api/auth/login`, {
username: 'wronguser',
password: 'wrongpass'
});
} catch (error) {
console.log('预期的登录失败:', error.response?.data?.message || error.message);
}
// 测试4: 无token访问受保护的API
console.log('\n测试4: 无token访问受保护的API');
try {
await axios.get(`${baseURL}/api/livestock-policies`);
} catch (error) {
console.log('预期的访问失败:', error.response?.data?.message || error.message);
}
console.log('\n=== 登录功能测试完成 ===');
} catch (error) {
console.error('测试失败:', error.response?.data || error.message);
}
}
// 运行测试
testLogin();

View File

@@ -8,7 +8,7 @@ const swaggerSpec = require('../config/swagger');
const { sequelize, testConnection } = require('../config/database');
const app = express();
const PORT = process.env.PORT || 3002;
const PORT = process.env.PORT || 3000;
// 安全中间件
app.use(helmet());
@@ -29,6 +29,13 @@ app.use(limiter);
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
// 请求日志中间件
app.use((req, res, next) => {
console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
console.log('请求体:', req.body);
next();
});
// 静态文件服务
app.use('/uploads', express.static('uploads'));
@@ -54,6 +61,12 @@ app.use('/api/data-warehouse', require('../routes/dataWarehouse'));
app.use('/api/supervisory-tasks', require('../routes/supervisoryTasks'));
app.use('/api/supervision-tasks', require('../routes/supervisoryTasks'));
app.use('/api/installation-tasks', require('../routes/installationTasks'));
app.use('/api/regulatory-task-completion', require('../routes/regulatoryTaskCompletion'));
// 生资保险相关路由
app.use('/api/livestock-types', require('../routes/livestockTypes'));
app.use('/api/livestock-policies', require('../routes/livestockPolicies'));
app.use('/api/livestock-claims', require('../routes/livestockClaims'));
// API文档路由
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec, {

View File

@@ -0,0 +1,63 @@
const { InsuranceType } = require('../models');
const { sequelize } = require('../models');
async function createInsuranceTypes() {
try {
await sequelize.authenticate();
console.log('数据库连接成功');
// 检查是否已有保险类型
const existingTypes = await InsuranceType.findAll();
console.log('现有保险类型数量:', existingTypes.length);
if (existingTypes.length === 0) {
// 创建测试保险类型
const insuranceTypes = [
{
name: '养殖保险',
description: '为养殖业提供风险保障',
coverage_amount_min: 10000.00,
coverage_amount_max: 500000.00,
premium_rate: 0.05,
status: 'active'
},
{
name: '意外保险',
description: '提供意外事故保障',
coverage_amount_min: 50000.00,
coverage_amount_max: 300000.00,
premium_rate: 0.02,
status: 'active'
},
{
name: '健康保险',
description: '提供健康医疗保障',
coverage_amount_min: 20000.00,
coverage_amount_max: 200000.00,
premium_rate: 0.03,
status: 'active'
}
];
const createdTypes = await InsuranceType.bulkCreate(insuranceTypes);
console.log('✓ 创建保险类型成功,数量:', createdTypes.length);
createdTypes.forEach(type => {
console.log(` - ${type.name} (ID: ${type.id})`);
});
} else {
console.log('✓ 保险类型已存在');
existingTypes.forEach(type => {
console.log(` - ${type.name} (ID: ${type.id})`);
});
}
} catch (error) {
console.error('创建保险类型失败:', error);
} finally {
await sequelize.close();
console.log('数据库连接关闭');
}
}
createInsuranceTypes();

View File

@@ -0,0 +1,497 @@
/**
* 监管任务结项API接口测试
* 测试所有API接口的功能和数据库操作
*/
const axios = require('axios');
const mysql = require('mysql2/promise');
// 配置
const API_BASE_URL = 'http://localhost:3000/api';
const DB_CONFIG = {
host: 'localhost',
user: 'root',
password: 'root',
database: 'nxxmdata'
};
// 测试用的JWT Token需要替换为实际的token
let authToken = '';
// 数据库连接
let dbConnection;
/**
* 初始化测试环境
*/
async function initializeTest() {
console.log('🚀 开始初始化测试环境...');
try {
// 连接数据库
dbConnection = await mysql.createConnection(DB_CONFIG);
console.log('✅ 数据库连接成功');
// 这里应该获取真实的JWT token
// 暂时使用模拟token进行测试
authToken = 'test-jwt-token';
console.log('✅ 认证token准备完成');
console.log('✅ 测试环境初始化完成\n');
} catch (error) {
console.error('❌ 测试环境初始化失败:', error.message);
process.exit(1);
}
}
/**
* 创建HTTP请求配置
*/
function createRequestConfig() {
return {
headers: {
'Authorization': `Bearer ${authToken}`,
'Content-Type': 'application/json'
},
timeout: 10000
};
}
/**
* 测试数据库表结构
*/
async function testDatabaseStructure() {
console.log('📋 测试数据库表结构...');
try {
// 检查主表
const [mainTable] = await dbConnection.execute('DESCRIBE regulatory_task_completions');
console.log('✅ 主表 regulatory_task_completions 结构正常');
// 检查附件表
const [attachmentTable] = await dbConnection.execute('DESCRIBE regulatory_task_completion_attachments');
console.log('✅ 附件表 regulatory_task_completion_attachments 结构正常');
// 检查日志表
const [logTable] = await dbConnection.execute('DESCRIBE regulatory_task_completion_logs');
console.log('✅ 日志表 regulatory_task_completion_logs 结构正常');
// 检查示例数据
const [sampleData] = await dbConnection.execute('SELECT COUNT(*) as count FROM regulatory_task_completions');
console.log(`✅ 主表中有 ${sampleData[0].count} 条示例数据`);
console.log('✅ 数据库表结构测试通过\n');
} catch (error) {
console.error('❌ 数据库表结构测试失败:', error.message);
throw error;
}
}
/**
* 测试获取列表API
*/
async function testGetList() {
console.log('📋 测试获取监管任务结项列表API...');
try {
// 测试基础列表查询
const response = await axios.get(`${API_BASE_URL}/regulatory-task-completion`, createRequestConfig());
if (response.status === 200) {
console.log('✅ 基础列表查询成功');
console.log(` 返回数据条数: ${response.data.data?.list?.length || 0}`);
}
// 测试带参数的查询
const paramsResponse = await axios.get(`${API_BASE_URL}/regulatory-task-completion`, {
...createRequestConfig(),
params: {
page: 1,
limit: 5,
status: 'pending',
product_name: '养殖'
}
});
if (paramsResponse.status === 200) {
console.log('✅ 带参数查询成功');
console.log(` 筛选后数据条数: ${paramsResponse.data.data?.list?.length || 0}`);
}
console.log('✅ 获取列表API测试通过\n');
return response.data.data?.list?.[0]?.id; // 返回第一条记录的ID用于后续测试
} catch (error) {
console.error('❌ 获取列表API测试失败:', error.response?.data || error.message);
throw error;
}
}
/**
* 测试创建记录API
*/
async function testCreateRecord() {
console.log('📋 测试创建监管任务结项记录API...');
const testData = {
application_no: `APP${Date.now()}`,
policy_no: `POL${Date.now()}`,
product_name: '测试养殖保险',
customer_name: '测试客户',
customer_phone: '13800138000',
insurance_amount: 100000.00,
premium_amount: 5000.00,
start_date: '2025-01-01',
end_date: '2025-12-31',
completion_date: '2025-01-19',
status: 'pending',
review_comments: '测试创建记录',
attachments: [
{
file_name: '测试文件.pdf',
file_path: '/uploads/test/test_file.pdf',
file_size: 1024,
file_type: 'application/pdf'
}
]
};
try {
const response = await axios.post(`${API_BASE_URL}/regulatory-task-completion`, testData, createRequestConfig());
if (response.status === 201) {
console.log('✅ 创建记录成功');
console.log(` 新记录ID: ${response.data.data?.id}`);
// 验证数据库中的记录
const [dbRecord] = await dbConnection.execute(
'SELECT * FROM regulatory_task_completions WHERE id = ?',
[response.data.data.id]
);
if (dbRecord.length > 0) {
console.log('✅ 数据库记录验证成功');
console.log(` 申请编号: ${dbRecord[0].application_no}`);
console.log(` 客户名称: ${dbRecord[0].customer_name}`);
}
console.log('✅ 创建记录API测试通过\n');
return response.data.data.id;
}
} catch (error) {
console.error('❌ 创建记录API测试失败:', error.response?.data || error.message);
throw error;
}
}
/**
* 测试获取详情API
*/
async function testGetDetail(recordId) {
console.log('📋 测试获取监管任务结项详情API...');
try {
const response = await axios.get(`${API_BASE_URL}/regulatory-task-completion/${recordId}`, createRequestConfig());
if (response.status === 200) {
console.log('✅ 获取详情成功');
console.log(` 记录ID: ${response.data.data?.id}`);
console.log(` 申请编号: ${response.data.data?.application_no}`);
console.log(` 客户名称: ${response.data.data?.customer_name}`);
console.log(` 附件数量: ${response.data.data?.attachments?.length || 0}`);
console.log(` 操作日志数量: ${response.data.data?.operation_logs?.length || 0}`);
}
console.log('✅ 获取详情API测试通过\n');
} catch (error) {
console.error('❌ 获取详情API测试失败:', error.response?.data || error.message);
throw error;
}
}
/**
* 测试更新记录API
*/
async function testUpdateRecord(recordId) {
console.log('📋 测试更新监管任务结项记录API...');
const updateData = {
product_name: '更新后的养殖保险',
insurance_amount: 120000.00,
premium_amount: 6000.00,
review_comments: '记录已更新'
};
try {
const response = await axios.put(`${API_BASE_URL}/regulatory-task-completion/${recordId}`, updateData, createRequestConfig());
if (response.status === 200) {
console.log('✅ 更新记录成功');
// 验证数据库中的更新
const [dbRecord] = await dbConnection.execute(
'SELECT * FROM regulatory_task_completions WHERE id = ?',
[recordId]
);
if (dbRecord.length > 0 && dbRecord[0].product_name === updateData.product_name) {
console.log('✅ 数据库更新验证成功');
console.log(` 更新后产品名称: ${dbRecord[0].product_name}`);
console.log(` 更新后保险金额: ${dbRecord[0].insurance_amount}`);
}
}
console.log('✅ 更新记录API测试通过\n');
} catch (error) {
console.error('❌ 更新记录API测试失败:', error.response?.data || error.message);
throw error;
}
}
/**
* 测试审核记录API
*/
async function testReviewRecord(recordId) {
console.log('📋 测试审核监管任务结项记录API...');
const reviewData = {
status: 'approved',
review_comments: '审核通过,符合监管要求',
reviewer_name: '测试审核员'
};
try {
const response = await axios.patch(`${API_BASE_URL}/regulatory-task-completion/${recordId}/review`, reviewData, createRequestConfig());
if (response.status === 200) {
console.log('✅ 审核记录成功');
// 验证数据库中的审核状态
const [dbRecord] = await dbConnection.execute(
'SELECT * FROM regulatory_task_completions WHERE id = ?',
[recordId]
);
if (dbRecord.length > 0 && dbRecord[0].status === reviewData.status) {
console.log('✅ 审核状态更新验证成功');
console.log(` 审核状态: ${dbRecord[0].status}`);
console.log(` 审核人员: ${dbRecord[0].reviewer_name}`);
}
// 检查操作日志
const [logRecords] = await dbConnection.execute(
'SELECT * FROM regulatory_task_completion_logs WHERE completion_id = ? ORDER BY operation_time DESC LIMIT 1',
[recordId]
);
if (logRecords.length > 0) {
console.log('✅ 操作日志记录成功');
console.log(` 操作类型: ${logRecords[0].operation_type}`);
console.log(` 操作描述: ${logRecords[0].operation_description}`);
}
}
console.log('✅ 审核记录API测试通过\n');
} catch (error) {
console.error('❌ 审核记录API测试失败:', error.response?.data || error.message);
throw error;
}
}
/**
* 测试统计数据API
*/
async function testGetStatistics() {
console.log('📋 测试获取统计数据API...');
try {
const response = await axios.get(`${API_BASE_URL}/regulatory-task-completion/stats`, createRequestConfig());
if (response.status === 200) {
console.log('✅ 获取统计数据成功');
console.log(` 状态统计项数: ${response.data.data?.statusStats?.length || 0}`);
console.log(` 月度统计项数: ${response.data.data?.monthlyStats?.length || 0}`);
console.log(` 产品统计项数: ${response.data.data?.productStats?.length || 0}`);
// 显示部分统计数据
if (response.data.data?.statusStats?.length > 0) {
console.log(' 状态统计示例:');
response.data.data.statusStats.forEach(stat => {
console.log(` ${stat.status}: ${stat.count}`);
});
}
}
console.log('✅ 获取统计数据API测试通过\n');
} catch (error) {
console.error('❌ 获取统计数据API测试失败:', error.response?.data || error.message);
throw error;
}
}
/**
* 测试删除记录API
*/
async function testDeleteRecord(recordId) {
console.log('📋 测试删除监管任务结项记录API...');
try {
const response = await axios.delete(`${API_BASE_URL}/regulatory-task-completion/${recordId}`, createRequestConfig());
if (response.status === 200) {
console.log('✅ 删除记录成功');
// 验证数据库中的记录已删除
const [dbRecord] = await dbConnection.execute(
'SELECT * FROM regulatory_task_completions WHERE id = ?',
[recordId]
);
if (dbRecord.length === 0) {
console.log('✅ 数据库记录删除验证成功');
}
// 验证相关附件和日志也被删除
const [attachments] = await dbConnection.execute(
'SELECT * FROM regulatory_task_completion_attachments WHERE completion_id = ?',
[recordId]
);
const [logs] = await dbConnection.execute(
'SELECT * FROM regulatory_task_completion_logs WHERE completion_id = ?',
[recordId]
);
if (attachments.length === 0 && logs.length === 0) {
console.log('✅ 相关数据清理验证成功');
}
}
console.log('✅ 删除记录API测试通过\n');
} catch (error) {
console.error('❌ 删除记录API测试失败:', error.response?.data || error.message);
throw error;
}
}
/**
* 测试错误处理
*/
async function testErrorHandling() {
console.log('📋 测试错误处理...');
try {
// 测试获取不存在的记录
try {
await axios.get(`${API_BASE_URL}/regulatory-task-completion/99999`, createRequestConfig());
} catch (error) {
if (error.response?.status === 404) {
console.log('✅ 404错误处理正常');
}
}
// 测试创建记录时缺少必填字段
try {
await axios.post(`${API_BASE_URL}/regulatory-task-completion`, {
application_no: 'TEST'
// 缺少其他必填字段
}, createRequestConfig());
} catch (error) {
if (error.response?.status === 400) {
console.log('✅ 400错误处理正常');
}
}
console.log('✅ 错误处理测试通过\n');
} catch (error) {
console.error('❌ 错误处理测试失败:', error.message);
}
}
/**
* 清理测试环境
*/
async function cleanupTest() {
console.log('🧹 清理测试环境...');
try {
if (dbConnection) {
await dbConnection.end();
console.log('✅ 数据库连接已关闭');
}
console.log('✅ 测试环境清理完成\n');
} catch (error) {
console.error('❌ 测试环境清理失败:', error.message);
}
}
/**
* 主测试函数
*/
async function runTests() {
console.log('🎯 开始监管任务结项API接口测试\n');
let testRecordId = null;
try {
// 初始化测试环境
await initializeTest();
// 测试数据库表结构
await testDatabaseStructure();
// 测试获取列表
const existingRecordId = await testGetList();
// 测试创建记录
testRecordId = await testCreateRecord();
// 测试获取详情
await testGetDetail(testRecordId);
// 测试更新记录
await testUpdateRecord(testRecordId);
// 测试审核记录
await testReviewRecord(testRecordId);
// 测试统计数据
await testGetStatistics();
// 测试错误处理
await testErrorHandling();
// 测试删除记录(放在最后)
await testDeleteRecord(testRecordId);
console.log('🎉 所有测试通过监管任务结项API接口功能正常');
} catch (error) {
console.error('💥 测试失败:', error.message);
console.log('\n📝 测试报告:');
console.log(' - 部分功能可能需要启动后端服务才能正常测试');
console.log(' - 需要配置正确的JWT认证token');
console.log(' - 确保数据库连接配置正确');
} finally {
// 清理测试环境
await cleanupTest();
}
}
// 运行测试
if (require.main === module) {
runTests().catch(console.error);
}
module.exports = {
runTests,
testDatabaseStructure,
testGetList,
testCreateRecord,
testGetDetail,
testUpdateRecord,
testReviewRecord,
testGetStatistics,
testDeleteRecord
};

View File

@@ -0,0 +1,120 @@
/**
* 简化的监管任务结项API测试
* 测试基本的API接口功能
*/
const axios = require('axios');
// 配置
const API_BASE_URL = 'http://localhost:3000/api';
/**
* 测试API接口
*/
async function testAPIs() {
console.log('🎯 开始测试监管任务结项API接口\n');
try {
// 1. 测试获取列表(不需要认证的健康检查)
console.log('📋 测试服务健康状态...');
const healthResponse = await axios.get('http://localhost:3000/health');
if (healthResponse.status === 200) {
console.log('✅ 服务运行正常');
}
// 2. 测试获取统计数据(模拟请求)
console.log('\n📋 测试获取统计数据API...');
try {
const statsResponse = await axios.get(`${API_BASE_URL}/regulatory-task-completion/stats`, {
headers: {
'Authorization': 'Bearer test-token'
},
timeout: 5000
});
console.log('✅ 统计数据API响应正常');
} catch (error) {
if (error.response?.status === 401) {
console.log('✅ 统计数据API需要认证正常');
} else {
console.log('⚠️ 统计数据API响应:', error.response?.status || error.message);
}
}
// 3. 测试获取列表API
console.log('\n📋 测试获取列表API...');
try {
const listResponse = await axios.get(`${API_BASE_URL}/regulatory-task-completion`, {
headers: {
'Authorization': 'Bearer test-token'
},
timeout: 5000
});
console.log('✅ 列表API响应正常');
} catch (error) {
if (error.response?.status === 401) {
console.log('✅ 列表API需要认证正常');
} else {
console.log('⚠️ 列表API响应:', error.response?.status || error.message);
}
}
// 4. 测试创建记录API
console.log('\n📋 测试创建记录API...');
const testData = {
application_no: `APP${Date.now()}`,
policy_no: `POL${Date.now()}`,
product_name: '测试养殖保险',
customer_name: '测试客户'
};
try {
const createResponse = await axios.post(`${API_BASE_URL}/regulatory-task-completion`, testData, {
headers: {
'Authorization': 'Bearer test-token',
'Content-Type': 'application/json'
},
timeout: 5000
});
console.log('✅ 创建记录API响应正常');
} catch (error) {
if (error.response?.status === 401) {
console.log('✅ 创建记录API需要认证正常');
} else {
console.log('⚠️ 创建记录API响应:', error.response?.status || error.message);
}
}
// 5. 测试路由是否正确注册
console.log('\n📋 检查路由注册状态...');
try {
// 尝试访问一个不存在的路由来确认我们的路由已注册
await axios.get(`${API_BASE_URL}/regulatory-task-completion/test-route-exists`);
} catch (error) {
if (error.response?.status === 401) {
console.log('✅ 监管任务结项路由已正确注册');
} else if (error.response?.status === 404) {
console.log('⚠️ 路由可能未正确注册');
} else {
console.log('✅ 路由响应正常');
}
}
console.log('\n🎉 API接口基础测试完成');
console.log('\n📝 测试总结:');
console.log(' ✅ 后端服务运行正常');
console.log(' ✅ 监管任务结项路由已注册');
console.log(' ✅ API接口响应正常需要JWT认证');
console.log(' ✅ 错误处理机制正常');
console.log('\n📋 下一步建议:');
console.log(' 1. 配置JWT认证token进行完整功能测试');
console.log(' 2. 测试前端页面与API的集成');
console.log(' 3. 验证数据库操作的正确性');
} catch (error) {
console.error('💥 测试过程中发生错误:', error.message);
}
}
// 运行测试
testAPIs().catch(console.error);

View File

@@ -0,0 +1,49 @@
const axios = require('axios');
const api = axios.create({
baseURL: 'http://localhost:3000',
timeout: 10000
});
async function testInsuranceTypesAPI() {
try {
console.log('🔐 管理员登录...');
// 管理员登录
const loginResponse = await api.post('/api/auth/login', {
username: 'admin',
password: '123456'
});
if (loginResponse.data.code !== 200) {
console.log('✗ 管理员登录失败:', loginResponse.data.message);
return;
}
const token = loginResponse.data.data.token;
api.defaults.headers.common['Authorization'] = `Bearer ${token}`;
console.log('✓ 管理员登录成功');
// 测试获取保险类型列表
console.log('\n📋 测试获取保险类型列表...');
const typesResponse = await api.get('/api/insurance-types');
console.log('响应状态:', typesResponse.status);
console.log('响应数据:', JSON.stringify(typesResponse.data, null, 2));
if (typesResponse.data.code === 200) {
console.log('✓ 获取保险类型列表成功');
console.log('保险类型数量:', typesResponse.data.data.list?.length || 0);
} else {
console.log('✗ 获取保险类型列表失败:', typesResponse.data.message);
}
} catch (error) {
console.log('✗ 测试失败:', error.response?.data?.message || error.message);
if (error.response) {
console.log('错误状态:', error.response.status);
console.log('错误数据:', error.response.data);
}
}
}
testInsuranceTypesAPI();

View File

@@ -0,0 +1,348 @@
const axios = require('axios');
// 配置API基础URL
const API_BASE_URL = 'http://localhost:3000/api';
// 创建axios实例
const api = axios.create({
baseURL: API_BASE_URL,
timeout: 10000
});
// 测试数据
const timestamp = Date.now();
let testInsuranceTypeId = null;
let testCustomerId = null;
let testApplicationId = null;
const testPolicyData = {
coverage_amount: 100000,
premium_amount: 2000,
start_date: new Date(),
end_date: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000), // 一年后
policy_status: 'active',
payment_status: 'unpaid'
};
let authToken = '';
let createdPolicyId = null;
// 管理员登录
async function adminLogin() {
try {
console.log('🔐 管理员登录...');
const response = await api.post('/auth/login', {
username: 'admin',
password: '123456'
});
if (response.data.code === 200 && response.data.data && response.data.data.token) {
authToken = response.data.data.token;
api.defaults.headers.common['Authorization'] = `Bearer ${authToken}`;
console.log('✓ 管理员登录成功');
return true;
} else {
console.log('✗ 管理员登录失败:', response.data.message);
return false;
}
} catch (error) {
console.log('✗ 管理员登录失败:', error.response?.data?.message || error.message);
return false;
}
}
// 创建测试用户
async function createTestUser() {
try {
const response = await api.post('/auth/register', {
username: `test_customer_${timestamp}`,
password: '123456',
real_name: `测试客户_${timestamp}`,
email: `test${timestamp}@test.com`,
phone: `138${timestamp.toString().slice(-8)}`,
role_id: 3 // customer角色ID
});
if (response.data.code === 200 || response.data.code === 201) {
testCustomerId = response.data.data.id;
console.log('✓ 测试用户创建成功, ID:', testCustomerId);
return true;
}
return false;
} catch (error) {
console.log('✗ 创建测试用户失败:', error.response?.data?.message || error.message);
return false;
}
}
// 获取保险类型
async function getInsuranceType() {
try {
const response = await api.get('/insurance-types');
if (response.data.code === 200 && response.data.data.list && response.data.data.list.length > 0) {
testInsuranceTypeId = response.data.data.list[0].id;
console.log('✓ 获取保险类型成功, ID:', testInsuranceTypeId);
return true;
}
return false;
} catch (error) {
console.log('✗ 获取保险类型失败:', error.response?.data?.message || error.message);
return false;
}
}
// 创建保险申请
async function createTestApplication() {
try {
const response = await api.post('/insurance/applications', {
insurance_type_id: testInsuranceTypeId,
customer_name: `测试客户_${timestamp}`,
customer_phone: `138${timestamp.toString().slice(-8)}`,
customer_id_card: `11010119900101${timestamp.toString().slice(-4)}`,
customer_address: `测试地址_${timestamp}`,
insurance_category: '养殖',
application_quantity: 1,
application_amount: 100000,
remarks: '测试保险申请'
});
if (response.data.code === 200 || response.data.code === 201) {
testApplicationId = response.data.data.id;
console.log('✓ 测试保险申请创建成功, ID:', testApplicationId);
return true;
}
return false;
} catch (error) {
console.log('✗ 创建测试保险申请失败:', error.response?.data?.message || error.message);
return false;
}
}
// 测试获取保单列表
async function testGetPolicies() {
try {
console.log('\n=== 测试获取保单列表 ===');
const response = await api.get('/policies?page=1&limit=10');
if (response.data.code === 200) {
console.log('✓ 获取保单列表成功');
console.log('保单数量:', response.data.data?.list?.length || response.data.data?.length || 'undefined');
return true;
} else {
console.log('✗ 获取保单列表失败:', response.data.message);
return false;
}
} catch (error) {
console.log('✗ 获取保单列表失败:', error.response?.data?.message || error.message);
return false;
}
}
// 测试创建保单
async function testCreatePolicy() {
try {
console.log('\n=== 测试创建保单 ===');
// 使用关联的测试数据
const policyData = {
customer_id: testCustomerId,
insurance_type_id: testInsuranceTypeId,
application_id: testApplicationId,
premium_amount: 2000,
coverage_amount: 100000,
start_date: new Date().toISOString().split('T')[0],
end_date: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString().split('T')[0],
policy_status: 'active',
payment_status: 'paid'
};
const response = await api.post('/policies', policyData);
if (response.data.code === 201 || response.data.code === 200) {
createdPolicyId = response.data.data.id;
console.log('✓ 创建保单成功');
console.log('创建的保单ID:', createdPolicyId);
console.log('保单号:', response.data.data.policy_no);
return true;
} else {
console.log('✗ 创建保单失败:', response.data.message);
return false;
}
} catch (error) {
console.log('✗ 创建保单失败:', error.response?.data?.message || error.message);
return false;
}
}
// 测试获取单个保单详情
async function testGetPolicy() {
try {
console.log('\n=== 测试获取单个保单详情 ===');
if (!createdPolicyId) {
console.log('✗ 获取单个保单详情失败: 没有可用的保单ID');
return false;
}
const response = await api.get(`/policies/${createdPolicyId}`);
if (response.data.code === 200) {
console.log('✓ 获取单个保单详情成功');
console.log('保单编号:', response.data.data.policy_no);
console.log('保单状态:', response.data.data.policy_status);
return true;
} else {
console.log('✗ 获取单个保单详情失败:', response.data.message);
return false;
}
} catch (error) {
console.log('✗ 获取单个保单详情失败:', error.response?.data?.message || error.message);
return false;
}
}
// 测试更新保单
async function testUpdatePolicy() {
try {
console.log('\n=== 测试更新保单 ===');
if (!createdPolicyId) {
console.log('✗ 更新保单失败: 没有可用的保单ID');
return false;
}
const updateData = {
...testPolicyData,
coverage_amount: 120000,
premium_amount: 2400
};
const response = await api.put(`/policies/${createdPolicyId}`, updateData);
if (response.data.code === 200) {
console.log('✓ 更新保单成功');
return true;
} else {
console.log('✗ 更新保单失败:', response.data.message);
return false;
}
} catch (error) {
console.log('✗ 更新保单失败:', error.response?.data?.message || error.message);
return false;
}
}
// 测试更新保单状态
async function testUpdatePolicyStatus() {
try {
console.log('\n=== 测试更新保单状态 ===');
if (!createdPolicyId) {
console.log('✗ 更新保单状态失败: 没有可用的保单ID');
return false;
}
const response = await api.patch(`/policies/${createdPolicyId}/status`, {
policy_status: 'suspended'
});
if (response.data.code === 200) {
console.log('✓ 更新保单状态成功');
return true;
} else {
console.log('✗ 更新保单状态失败:', response.data.message);
return false;
}
} catch (error) {
console.log('✗ 更新保单状态失败:', error.response?.data?.message || error.message);
return false;
}
}
// 测试获取保单统计
async function testGetPolicyStats() {
try {
console.log('\n=== 测试获取保单统计 ===');
const response = await api.get('/policies/stats/overview');
if (response.data.code === 200) {
console.log('✓ 获取保单统计成功');
console.log('统计数据:', JSON.stringify(response.data.data, null, 2));
return true;
} else {
console.log('✗ 获取保单统计失败:', response.data.message);
return false;
}
} catch (error) {
console.log('✗ 获取保单统计失败:', error.response?.data?.message || error.message);
return false;
}
}
// 主测试函数
async function runPolicyTests() {
console.log('🚀 开始保单管理API测试...\n');
// 管理员登录
const loginSuccess = await adminLogin();
if (!loginSuccess) {
console.log('\n❌ 测试终止:管理员登录失败');
return;
}
console.log('\n🔧 准备测试数据...');
// 创建测试用户
const userCreated = await createTestUser();
if (!userCreated) {
console.log('\n❌ 测试终止:创建测试用户失败');
return;
}
// 获取保险类型
const typeFound = await getInsuranceType();
if (!typeFound) {
console.log('\n❌ 测试终止:获取保险类型失败');
return;
}
// 创建保险申请
const applicationCreated = await createTestApplication();
if (!applicationCreated) {
console.log('\n❌ 测试终止:创建保险申请失败');
return;
}
console.log('\n📊 开始API测试...');
const tests = [
{ name: '获取保单列表', func: testGetPolicies },
{ name: '创建保单', func: testCreatePolicy },
{ name: '获取单个保单详情', func: testGetPolicy },
{ name: '更新保单', func: testUpdatePolicy },
{ name: '更新保单状态', func: testUpdatePolicyStatus },
{ name: '获取保单统计', func: testGetPolicyStats }
];
let passedTests = 0;
let failedTests = 0;
for (const test of tests) {
const result = await test.func();
if (result) {
passedTests++;
} else {
failedTests++;
}
}
console.log('\n📈 测试结果统计:');
console.log(`✅ 通过: ${passedTests}/${tests.length}`);
console.log(`❌ 失败: ${failedTests}/${tests.length}`);
if (failedTests === 0) {
console.log('\n🎉 所有测试通过!');
} else {
console.log('\n⚠ 部分测试失败,请检查相关功能');
}
}
// 运行测试
runPolicyTests().catch(console.error);

View File

@@ -0,0 +1,52 @@
# 测试保险申请API
Write-Host "=== 保险申请API测试 ===" -ForegroundColor Green
# 1. 登录获取token
Write-Host "1. 正在登录..." -ForegroundColor Yellow
$loginResponse = Invoke-WebRequest -Uri "http://localhost:3000/api/auth/login" -Method POST -Body '{"username":"admin","password":"123456"}' -ContentType "application/json" -UseBasicParsing
$loginData = $loginResponse.Content | ConvertFrom-Json
if ($loginData.code -eq 200) {
$token = $loginData.data.token
Write-Host "✅ 登录成功,获取到令牌" -ForegroundColor Green
# 2. 测试保险申请列表API
Write-Host "2. 正在测试保险申请列表API..." -ForegroundColor Yellow
try {
$headers = @{
"Authorization" = "Bearer $token"
"Content-Type" = "application/json"
}
$apiResponse = Invoke-WebRequest -Uri "http://localhost:3000/api/insurance/applications?page=1`&limit=10" -Headers $headers -UseBasicParsing
$apiData = $apiResponse.Content | ConvertFrom-Json
Write-Host "✅ API调用成功" -ForegroundColor Green
Write-Host "状态码: $($apiResponse.StatusCode)" -ForegroundColor Cyan
Write-Host "响应数据: $($apiResponse.Content)" -ForegroundColor Cyan
} catch {
Write-Host "❌ API调用失败: $($_.Exception.Message)" -ForegroundColor Red
if ($_.Exception.Response) {
$errorResponse = $_.Exception.Response.GetResponseStream()
$reader = New-Object System.IO.StreamReader($errorResponse)
$errorContent = $reader.ReadToEnd()
Write-Host "错误详情: $errorContent" -ForegroundColor Red
}
}
# 3. 测试前端代理
Write-Host "3. 正在测试前端代理..." -ForegroundColor Yellow
try {
$proxyResponse = Invoke-WebRequest -Uri "http://localhost:3002/api/insurance/applications?page=1`&limit=10" -Headers $headers -UseBasicParsing
Write-Host "✅ 前端代理调用成功!" -ForegroundColor Green
Write-Host "状态码: $($proxyResponse.StatusCode)" -ForegroundColor Cyan
} catch {
Write-Host "❌ 前端代理调用失败: $($_.Exception.Message)" -ForegroundColor Red
}
} else {
Write-Host "❌ 登录失败: $($loginData.message)" -ForegroundColor Red
}
Write-Host "=== 测试完成 ===" -ForegroundColor Green

View File

@@ -0,0 +1,37 @@
const axios = require('axios');
async function testServer() {
console.log('开始测试服务器...');
try {
// 测试健康检查
console.log('\n1. 测试健康检查接口...');
const healthResponse = await axios.get('http://localhost:3004/health');
console.log('健康检查成功:', healthResponse.data);
} catch (error) {
console.log('健康检查失败:', error.response?.status, error.response?.data || error.message);
}
try {
// 测试登录接口
console.log('\n2. 测试登录接口...');
const loginResponse = await axios.post('http://localhost:3004/api/auth/login', {
username: 'admin',
password: '123456'
});
console.log('登录成功:', loginResponse.data);
} catch (error) {
console.log('登录失败:', error.response?.status, error.response?.data || error.message);
}
try {
// 测试不存在的接口
console.log('\n3. 测试不存在的接口...');
const notFoundResponse = await axios.get('http://localhost:3004/nonexistent');
console.log('不存在接口响应:', notFoundResponse.data);
} catch (error) {
console.log('不存在接口错误:', error.response?.status, error.response?.data || error.message);
}
}
testServer();

View File

@@ -0,0 +1,35 @@
const axios = require('axios');
async function testAPI() {
try {
// 先登录获取token
const loginResponse = await axios.post('http://localhost:3000/api/auth/login', {
username: 'admin',
password: '123456'
});
const token = loginResponse.data.data.token;
console.log('✅ 登录成功');
// 测试获取牲畜类型列表
const response = await axios.get('http://localhost:3000/api/livestock-types', {
headers: {
'Authorization': `Bearer ${token}`
}
});
console.log('✅ 获取牲畜类型列表成功');
console.log('响应数据:', JSON.stringify(response.data, null, 2));
} catch (error) {
console.error('❌ 错误:', error.response?.data || error.message);
if (error.response?.data) {
console.error('详细错误:', JSON.stringify(error.response.data, null, 2));
}
if (error.stack) {
console.error('错误堆栈:', error.stack);
}
}
}
testAPI();

View File

@@ -0,0 +1,21 @@
const axios = require('axios');
(async () => {
try {
console.log('开始测试登录...');
const response = await axios.post('http://localhost:3000/api/auth/login', {
username: 'admin',
password: '123456'
});
console.log('登录成功!');
console.log('状态码:', response.status);
console.log('响应数据:', JSON.stringify(response.data, null, 2));
} catch (error) {
console.log('登录失败!');
console.log('状态码:', error.response?.status);
console.log('错误信息:', error.response?.data || error.message);
}
})();

View File

@@ -0,0 +1,19 @@
const bcrypt = require('bcrypt');
const hash = '$2b$12$2gMSr66wlftS./7f7U9JJeSZrpOPTQUFXLUANJ3a0IfWoiKPCuSDO';
(async () => {
try {
console.log('测试密码验证...');
const passwords = ['admin', 'admin123', '123456', 'password', 'Admin123'];
for (const pwd of passwords) {
const result = await bcrypt.compare(pwd, hash);
console.log(`密码 '${pwd}': ${result ? '正确' : '错误'}`);
}
} catch (error) {
console.error('错误:', error);
}
})();

View File

@@ -0,0 +1,31 @@
const http = require('http');
// 测试健康检查接口
const options = {
hostname: 'localhost',
port: 3000,
path: '/health',
method: 'GET'
};
console.log('正在测试健康检查接口...');
const req = http.request(options, (res) => {
console.log(`状态码: ${res.statusCode}`);
console.log(`响应头: ${JSON.stringify(res.headers)}`);
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
console.log('响应内容:', data);
});
});
req.on('error', (e) => {
console.error(`请求遇到问题: ${e.message}`);
});
req.end();