部署保险端项目和大屏
@@ -102,7 +102,7 @@ export const useUserStore = defineStore('user', () => {
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('http://localhost:3000/api/auth/refresh', {
|
||||
const response = await fetch('/insurance/api/auth/refresh', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
@@ -4,7 +4,7 @@ import router from '@/router'
|
||||
|
||||
// API基础配置
|
||||
const API_CONFIG = {
|
||||
baseURL: 'http://localhost:3000/api',
|
||||
baseURL: '/insurance/api',
|
||||
timeout: 10000
|
||||
}
|
||||
|
||||
|
||||
@@ -45,8 +45,8 @@
|
||||
allow-clear
|
||||
style="width: 120px"
|
||||
>
|
||||
<a-select-option value="individual">个人参保</a-select-option>
|
||||
<a-select-option value="enterprise">企业参保</a-select-option>
|
||||
<a-select-option value="养殖">养殖</a-select-option>
|
||||
<a-select-option value="畜牧养殖">畜牧养殖</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="申请状态">
|
||||
@@ -100,7 +100,7 @@
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'insurance_category'">
|
||||
{{ record.insurance_category === 'individual' ? '个人' : '企业' }}
|
||||
{{ record.insurance_category }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'insurance_amount'">
|
||||
¥{{ Number(record.insurance_amount).toLocaleString() }}
|
||||
@@ -173,7 +173,7 @@
|
||||
{{ selectedApplication.application_no }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="参保类型">
|
||||
{{ selectedApplication.insurance_category === 'individual' ? '个人参保' : '企业参保' }}
|
||||
{{ selectedApplication.insurance_category }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="参保险种">
|
||||
{{ selectedApplication.insurance_type?.name || selectedApplication.insurance_type_id }}
|
||||
@@ -273,8 +273,8 @@
|
||||
>
|
||||
<a-form-item label="参保类型" name="insurance_category" :rules="[{ required: true, message: '请选择参保类型' }]">
|
||||
<a-radio-group v-model:value="createForm.insurance_category">
|
||||
<a-radio value="individual">个人参保</a-radio>
|
||||
<a-radio value="enterprise">企业参保</a-radio>
|
||||
<a-radio value="养殖">养殖</a-radio>
|
||||
<a-radio value="畜牧养殖">畜牧养殖</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="参保险种" name="insurance_type_id" :rules="[{ required: true, message: '请选择参保险种' }]">
|
||||
@@ -466,11 +466,26 @@ const columns = [
|
||||
const loadApplications = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// 构建搜索参数,映射前端字段名到后端期望的字段名
|
||||
const params = {
|
||||
page: pagination.current,
|
||||
limit: pagination.pageSize,
|
||||
...searchForm
|
||||
// 字段名映射
|
||||
applicationNumber: searchForm.application_no || undefined,
|
||||
applicantName: searchForm.customer_name || undefined,
|
||||
insuranceType: searchForm.insuranceType || undefined,
|
||||
insuranceCategory: searchForm.insuranceCategory || undefined,
|
||||
status: searchForm.status || undefined
|
||||
}
|
||||
|
||||
// 过滤掉undefined值
|
||||
Object.keys(params).forEach(key => {
|
||||
if (params[key] === undefined) {
|
||||
delete params[key]
|
||||
}
|
||||
})
|
||||
|
||||
console.log('搜索参数:', params)
|
||||
const response = await applicationAPI.getList(params)
|
||||
console.log('申请列表API响应:', response)
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
<!-- 图表区域 -->
|
||||
<a-row :gutter="16" style="margin-top: 24px">
|
||||
<a-col :span="12">
|
||||
<a-card title="保险申请趋势" :bordered="false">
|
||||
<a-card title="申请状态分布" :bordered="false">
|
||||
<div style="height: 300px">
|
||||
<v-chart
|
||||
:option="applicationTrendOption"
|
||||
@@ -71,7 +71,7 @@
|
||||
</a-col>
|
||||
|
||||
<a-col :span="12">
|
||||
<a-card title="保单状态分布" :bordered="false">
|
||||
<a-card title="险种分布" :bordered="false">
|
||||
<div style="height: 300px">
|
||||
<v-chart
|
||||
:option="policyDistributionOption"
|
||||
@@ -122,7 +122,7 @@ import {
|
||||
BarChartOutlined,
|
||||
PieChartOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
import { dashboardAPI } from '@/utils/api'
|
||||
import { dashboardAPI, policyAPI, insuranceTypeAPI, applicationAPI } from '@/utils/api'
|
||||
import { message } from 'ant-design-vue'
|
||||
import VChart from 'vue-echarts'
|
||||
import { use } from 'echarts/core'
|
||||
@@ -279,169 +279,66 @@ const loadDashboardData = async () => {
|
||||
created_at: new Date(Date.now() - 10800000).toISOString()
|
||||
}
|
||||
]
|
||||
|
||||
// 加载图表数据
|
||||
await loadChartData()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
// 无论统计数据和活动数据是否成功,都要加载图表数据
|
||||
await loadChartData()
|
||||
}
|
||||
|
||||
// 加载图表数据
|
||||
const loadChartData = async () => {
|
||||
console.log('开始加载图表数据...')
|
||||
chartLoading.value = true
|
||||
|
||||
// 独立加载申请状态分布数据
|
||||
try {
|
||||
// 获取申请趋势数据
|
||||
console.log('正在获取申请趋势数据...')
|
||||
const applicationTrendResponse = await dashboardAPI.getChartData({
|
||||
type: 'applications',
|
||||
period: '7d'
|
||||
console.log('正在获取申请状态分布数据...')
|
||||
const applicationsResponse = await applicationAPI.getList({
|
||||
page: 1,
|
||||
limit: 100 // 获取所有申请用于状态统计
|
||||
})
|
||||
|
||||
console.log('申请趋势响应:', applicationTrendResponse)
|
||||
if (applicationTrendResponse.data && applicationTrendResponse.data.status === 'success') {
|
||||
console.log('设置申请趋势图表,数据:', applicationTrendResponse.data.data)
|
||||
setupApplicationTrendChart(applicationTrendResponse.data.data)
|
||||
console.log('申请状态分布响应:', applicationsResponse)
|
||||
if (applicationsResponse.data && applicationsResponse.data.status === 'success') {
|
||||
console.log('设置申请状态分布图表,数据:', applicationsResponse.data.data)
|
||||
setupApplicationStatusChart(applicationsResponse.data.data)
|
||||
} else {
|
||||
console.log('申请趋势响应格式错误:', applicationTrendResponse)
|
||||
}
|
||||
|
||||
// 获取保单状态分布数据
|
||||
console.log('正在获取保单状态分布数据...')
|
||||
const policyDistributionResponse = await dashboardAPI.getChartData({
|
||||
type: 'policy_status'
|
||||
})
|
||||
|
||||
console.log('保单状态分布响应:', policyDistributionResponse)
|
||||
if (policyDistributionResponse.data && policyDistributionResponse.data.status === 'success') {
|
||||
console.log('设置保单状态分布图表,数据:', policyDistributionResponse.data.data)
|
||||
setupPolicyDistributionChart(policyDistributionResponse.data.data)
|
||||
} else {
|
||||
console.log('保单状态分布响应格式错误:', policyDistributionResponse)
|
||||
console.log('申请状态分布响应格式错误:', applicationsResponse)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载图表数据失败:', error)
|
||||
console.error('错误详情:', error.response?.data || error.message)
|
||||
message.error('加载图表数据失败')
|
||||
} finally {
|
||||
chartLoading.value = false
|
||||
console.log('图表数据加载完成')
|
||||
console.error('加载申请状态分布数据失败:', error)
|
||||
}
|
||||
|
||||
// 独立加载险种分布数据
|
||||
try {
|
||||
console.log('正在获取险种分布数据...')
|
||||
const insuranceTypesResponse = await insuranceTypeAPI.getList({
|
||||
page: 1,
|
||||
pageSize: 100, // 获取所有险种用于分布统计
|
||||
name: ''
|
||||
})
|
||||
|
||||
console.log('险种分布响应:', insuranceTypesResponse)
|
||||
if (insuranceTypesResponse.data && insuranceTypesResponse.data.status === 'success') {
|
||||
console.log('设置险种分布图表,数据:', insuranceTypesResponse.data.data)
|
||||
setupInsuranceTypeDistributionChart(insuranceTypesResponse.data.data)
|
||||
} else {
|
||||
console.log('险种分布响应格式错误:', insuranceTypesResponse)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载险种分布数据失败:', error)
|
||||
message.error('加载险种分布数据失败')
|
||||
}
|
||||
|
||||
chartLoading.value = false
|
||||
console.log('图表数据加载完成')
|
||||
}
|
||||
|
||||
// 设置申请趋势图表
|
||||
const setupApplicationTrendChart = (data) => {
|
||||
console.log('设置申请趋势图表,接收数据:', data)
|
||||
if (!data || !Array.isArray(data) || data.length === 0) {
|
||||
console.warn('申请趋势数据为空或格式错误')
|
||||
return
|
||||
}
|
||||
|
||||
const dates = data.map(item => item.date)
|
||||
const counts = data.map(item => item.value || item.count)
|
||||
console.log('处理后的数据 - 日期:', dates, '数量:', counts)
|
||||
|
||||
applicationTrendOption.value = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
label: {
|
||||
backgroundColor: '#6a7985'
|
||||
}
|
||||
},
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
||||
borderColor: '#ccc',
|
||||
borderWidth: 1,
|
||||
textStyle: {
|
||||
color: '#333'
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
top: '10%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: dates,
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#d9d9d9'
|
||||
}
|
||||
},
|
||||
axisTick: {
|
||||
alignWithLabel: true
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#666'
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#d9d9d9'
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#666'
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: '#f0f0f0'
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '申请数量',
|
||||
type: 'bar',
|
||||
data: counts,
|
||||
barWidth: '60%',
|
||||
itemStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [{
|
||||
offset: 0, color: '#1890ff'
|
||||
}, {
|
||||
offset: 1, color: '#40a9ff'
|
||||
}]
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0]
|
||||
},
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
color: '#0050b3'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// 设置保单状态分布图表
|
||||
const setupPolicyDistributionChart = (data) => {
|
||||
console.log('设置保单状态分布图表,接收数据:', data)
|
||||
if (!data || !Array.isArray(data) || data.length === 0) {
|
||||
console.warn('保单状态分布数据为空或格式错误')
|
||||
return
|
||||
}
|
||||
|
||||
const chartData = data.map(item => ({
|
||||
name: getPolicyStatusLabel(item.status),
|
||||
value: item.count
|
||||
}))
|
||||
console.log('处理后的图表数据:', chartData)
|
||||
|
||||
policyDistributionOption.value = {
|
||||
// 创建饼图配置的通用函数
|
||||
const createPieChartOption = (seriesName, data) => {
|
||||
return {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{a} <br/>{b}: {c} ({d}%)',
|
||||
@@ -462,9 +359,10 @@ const setupPolicyDistributionChart = (data) => {
|
||||
},
|
||||
itemGap: 15
|
||||
},
|
||||
color: ['#1890ff', '#52c41a', '#faad14', '#f5222d', '#722ed1', '#13c2c2', '#eb2f96', '#fa8c16'],
|
||||
series: [
|
||||
{
|
||||
name: '保单数量',
|
||||
name: seriesName,
|
||||
type: 'pie',
|
||||
radius: ['45%', '75%'],
|
||||
center: ['65%', '50%'],
|
||||
@@ -489,7 +387,7 @@ const setupPolicyDistributionChart = (data) => {
|
||||
labelLine: {
|
||||
show: false
|
||||
},
|
||||
data: chartData,
|
||||
data: data,
|
||||
itemStyle: {
|
||||
borderRadius: 8,
|
||||
borderColor: '#fff',
|
||||
@@ -500,6 +398,78 @@ const setupPolicyDistributionChart = (data) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 设置申请状态分布图表
|
||||
const setupApplicationStatusChart = (data) => {
|
||||
console.log('设置申请状态分布图表,接收数据:', data)
|
||||
if (!data || !Array.isArray(data) || data.length === 0) {
|
||||
console.warn('申请状态分布数据为空或格式错误,使用默认数据')
|
||||
// 使用默认数据
|
||||
const defaultData = [
|
||||
{ name: '待审核', value: 0 },
|
||||
{ name: '已通过', value: 0 },
|
||||
{ name: '已拒绝', value: 0 }
|
||||
]
|
||||
applicationTrendOption.value = createPieChartOption('申请数量', defaultData)
|
||||
return
|
||||
}
|
||||
|
||||
// 统计各状态的申请数量
|
||||
const statusCounts = {}
|
||||
data.forEach(app => {
|
||||
const status = app.status || 'unknown'
|
||||
statusCounts[status] = (statusCounts[status] || 0) + 1
|
||||
})
|
||||
|
||||
// 转换为图表数据格式
|
||||
const chartData = Object.entries(statusCounts).map(([status, count]) => ({
|
||||
name: getApplicationStatusLabel(status),
|
||||
value: count
|
||||
}))
|
||||
|
||||
console.log('处理后的申请状态分布图表数据:', chartData)
|
||||
|
||||
applicationTrendOption.value = createPieChartOption('申请数量', chartData)
|
||||
}
|
||||
|
||||
// 设置险种分布图表
|
||||
const setupInsuranceTypeDistributionChart = (data) => {
|
||||
console.log('设置险种分布图表,接收数据:', data)
|
||||
if (!data || !Array.isArray(data) || data.length === 0) {
|
||||
console.warn('险种分布数据为空或格式错误,使用默认数据')
|
||||
// 使用默认数据
|
||||
const defaultData = [
|
||||
{ name: '人寿保险', value: 0 },
|
||||
{ name: '健康保险', value: 0 },
|
||||
{ name: '财产保险', value: 0 }
|
||||
]
|
||||
policyDistributionOption.value = createPieChartOption('险种数量', defaultData)
|
||||
return
|
||||
}
|
||||
|
||||
// 统计每个险种的数量(这里假设每个险种都有相同的权重,实际项目中可能需要根据保单数量统计)
|
||||
const chartData = data.map((item, index) => ({
|
||||
name: item.name || `险种${index + 1}`,
|
||||
value: 1 // 每个险种计数为1,实际项目中可以根据保单数量统计
|
||||
}))
|
||||
|
||||
console.log('处理后的险种分布图表数据:', chartData)
|
||||
|
||||
policyDistributionOption.value = createPieChartOption('险种数量', chartData)
|
||||
}
|
||||
|
||||
// 获取申请状态标签
|
||||
const getApplicationStatusLabel = (status) => {
|
||||
const statusMap = {
|
||||
'pending': '待审核',
|
||||
'initial_approved': '初审通过',
|
||||
'under_review': '复审中',
|
||||
'approved': '已通过',
|
||||
'rejected': '已拒绝',
|
||||
'unknown': '未知状态'
|
||||
}
|
||||
return statusMap[status] || status
|
||||
}
|
||||
|
||||
// 获取保单状态标签
|
||||
const getPolicyStatusLabel = (status) => {
|
||||
const statusMap = {
|
||||
@@ -518,29 +488,8 @@ onMounted(() => {
|
||||
policyDistributionOption: policyDistributionOption.value
|
||||
})
|
||||
|
||||
// 设置测试图表数据
|
||||
console.log('设置测试图表数据...')
|
||||
applicationTrendOption.value = {
|
||||
title: { text: '测试图表' },
|
||||
xAxis: { type: 'category', data: ['Mon', 'Tue', 'Wed'] },
|
||||
yAxis: { type: 'value' },
|
||||
series: [{ data: [120, 200, 150], type: 'bar' }]
|
||||
}
|
||||
|
||||
policyDistributionOption.value = {
|
||||
title: { text: '测试饼图' },
|
||||
series: [{
|
||||
type: 'pie',
|
||||
data: [
|
||||
{ value: 1048, name: '搜索引擎' },
|
||||
{ value: 735, name: '直接访问' },
|
||||
{ value: 580, name: '邮件营销' }
|
||||
]
|
||||
}]
|
||||
}
|
||||
|
||||
console.log('测试图表设置完成')
|
||||
|
||||
// 直接加载动态数据,不再设置测试数据
|
||||
console.log('开始加载动态图表数据...')
|
||||
loadDashboardData()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -259,12 +259,22 @@ const getStatusText = (status) => {
|
||||
const loadUsers = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// 构建搜索参数,映射前端字段名到后端期望的字段名
|
||||
const params = {
|
||||
page: pagination.current,
|
||||
pageSize: pagination.pageSize,
|
||||
...searchForm
|
||||
limit: pagination.pageSize,
|
||||
// 字段名映射
|
||||
search: searchForm.username || undefined,
|
||||
status: searchForm.status || undefined
|
||||
}
|
||||
|
||||
// 过滤掉undefined值
|
||||
Object.keys(params).forEach(key => {
|
||||
if (params[key] === undefined) {
|
||||
delete params[key]
|
||||
}
|
||||
})
|
||||
|
||||
console.log('用户管理API请求参数:', params)
|
||||
const response = await userAPI.getList(params)
|
||||
console.log('用户管理API响应:', response)
|
||||
@@ -280,6 +290,7 @@ const loadUsers = async () => {
|
||||
userList.value = []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载用户列表失败:', error)
|
||||
message.error('加载用户列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
@@ -336,17 +347,27 @@ const handleModalOk = async () => {
|
||||
await formRef.value.validate()
|
||||
|
||||
if (editingId.value) {
|
||||
// await userAPI.update(editingId.value, formState)
|
||||
// 编辑用户 - 不包含密码字段
|
||||
const updateData = { ...formState }
|
||||
delete updateData.password // 编辑时不更新密码
|
||||
|
||||
await userAPI.update(editingId.value, updateData)
|
||||
message.success('用户更新成功')
|
||||
} else {
|
||||
// await userAPI.create(formState)
|
||||
// 新增用户
|
||||
await userAPI.create(formState)
|
||||
message.success('用户创建成功')
|
||||
}
|
||||
|
||||
modalVisible.value = false
|
||||
loadUsers()
|
||||
} catch (error) {
|
||||
console.log('表单验证失败', error)
|
||||
console.error('操作失败:', error)
|
||||
if (error.response?.data?.message) {
|
||||
message.error(error.response.data.message)
|
||||
} else {
|
||||
message.error(editingId.value ? '用户更新失败' : '用户创建失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,21 +378,31 @@ const handleModalCancel = () => {
|
||||
const handleToggleStatus = async (record) => {
|
||||
try {
|
||||
const newStatus = record.status === 'active' ? 'inactive' : 'active'
|
||||
// await userAPI.update(record.id, { status: newStatus })
|
||||
message.success('状态更新成功')
|
||||
await userAPI.update(record.id, { status: newStatus })
|
||||
message.success(`用户已${newStatus === 'active' ? '启用' : '禁用'}`)
|
||||
loadUsers()
|
||||
} catch (error) {
|
||||
message.error('状态更新失败')
|
||||
console.error('状态更新失败:', error)
|
||||
if (error.response?.data?.message) {
|
||||
message.error(error.response.data.message)
|
||||
} else {
|
||||
message.error('状态更新失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleDelete = async (id) => {
|
||||
try {
|
||||
// await userAPI.delete(id)
|
||||
await userAPI.delete(id)
|
||||
message.success('用户删除成功')
|
||||
loadUsers()
|
||||
} catch (error) {
|
||||
message.error('用户删除失败')
|
||||
console.error('删除用户失败:', error)
|
||||
if (error.response?.data?.message) {
|
||||
message.error(error.response.data.message)
|
||||
} else {
|
||||
message.error('用户删除失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ const testFrontendAPI = async () => {
|
||||
console.log('开始测试前端API调用...');
|
||||
|
||||
// 模拟前端API调用
|
||||
const response = await fetch('http://localhost:3000/api/policies?page=1&pageSize=10', {
|
||||
const response = await fetch('http://49.51.70.206:3000/api/policies?page=1&pageSize=10', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
@@ -6,7 +6,7 @@ async function testApiConnection() {
|
||||
console.log('开始测试前后端通信...');
|
||||
|
||||
// 测试后端健康检查接口
|
||||
const backendResponse = await axios.get('http://localhost:3000/health', {
|
||||
const backendResponse = await axios.get('http://49.51.70.206:3000/health', {
|
||||
timeout: 5000
|
||||
});
|
||||
console.log('✅ 后端健康检查接口正常:', backendResponse.data);
|
||||
|
||||
@@ -5,7 +5,7 @@ async function testCurrentLogs() {
|
||||
console.log('🔐 先登录获取token...');
|
||||
|
||||
// 登录获取token
|
||||
const loginResponse = await axios.post('http://localhost:3000/api/auth/login', {
|
||||
const loginResponse = await axios.post('http://49.51.70.206:3000/api/auth/login', {
|
||||
username: 'admin',
|
||||
password: '123456'
|
||||
});
|
||||
@@ -16,7 +16,7 @@ async function testCurrentLogs() {
|
||||
console.log('🔍 测试当前日志API...');
|
||||
|
||||
// 测试系统日志API
|
||||
const logsResponse = await axios.get('http://localhost:3000/api/system/logs', {
|
||||
const logsResponse = await axios.get('http://49.51.70.206:3000/api/system/logs', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
@@ -32,7 +32,7 @@ async function testCurrentLogs() {
|
||||
console.log('📄 当前页日志数量:', logsResponse.data.data?.logs?.length || 0);
|
||||
|
||||
// 测试仪表板统计API
|
||||
const statsResponse = await axios.get('http://localhost:3000/api/dashboard/stats', {
|
||||
const statsResponse = await axios.get('http://49.51.70.206:3000/api/dashboard/stats', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
@@ -41,7 +41,7 @@ async function testCurrentLogs() {
|
||||
console.log('📋 统计数据:', JSON.stringify(statsResponse.data, null, 2));
|
||||
|
||||
// 测试最近活动API
|
||||
const activitiesResponse = await axios.get('http://localhost:3000/api/dashboard/recent-activities', {
|
||||
const activitiesResponse = await axios.get('http://49.51.70.206:3000/api/dashboard/recent-activities', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ export default defineConfig(({ mode }) => {
|
||||
historyApiFallback: true,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: env.VITE_API_BASE_URL || 'http://localhost:3000',
|
||||
target: env.VITE_API_BASE_URL || 'https://ad.ningmuyun.com/insurace/',
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path
|
||||
}
|
||||
|
||||
163
insurance_backend/DEPLOYMENT.md
Normal file
@@ -0,0 +1,163 @@
|
||||
# 保险系统部署指南
|
||||
|
||||
## 部署架构
|
||||
|
||||
```
|
||||
用户访问: https://ad.ningmuyun.com/insurance/
|
||||
↓
|
||||
nginx (443端口)
|
||||
↓
|
||||
前端静态文件: /var/www/insurance-admin-system/dist/
|
||||
↓
|
||||
API请求: /insurance/api/* → 代理到后端 3000端口
|
||||
```
|
||||
|
||||
## 部署步骤
|
||||
|
||||
### 1. 服务器准备
|
||||
|
||||
```bash
|
||||
# 安装必要软件
|
||||
sudo apt update
|
||||
sudo apt install nginx nodejs npm pm2 -y
|
||||
|
||||
# 创建项目目录
|
||||
sudo mkdir -p /var/www/insurance-admin-system
|
||||
sudo mkdir -p /var/www/insurance-backend
|
||||
```
|
||||
|
||||
### 2. 部署前端
|
||||
|
||||
```bash
|
||||
# 上传前端构建文件到服务器
|
||||
scp -r insurance_admin-system/dist/* user@49.51.70.206:/var/www/insurance-admin-system/
|
||||
|
||||
# 设置权限
|
||||
sudo chown -R www-data:www-data /var/www/insurance-admin-system
|
||||
sudo chmod -R 755 /var/www/insurance-admin-system
|
||||
```
|
||||
|
||||
### 3. 部署后端
|
||||
|
||||
```bash
|
||||
# 上传后端代码到服务器
|
||||
scp -r insurance_backend/* user@49.51.70.206:/var/www/insurance-backend/
|
||||
|
||||
# 安装依赖
|
||||
cd /var/www/insurance-backend
|
||||
npm install --production
|
||||
|
||||
# 创建环境变量文件
|
||||
cp .env.example .env
|
||||
# 编辑 .env 文件,设置正确的数据库连接信息
|
||||
```
|
||||
|
||||
### 4. 配置nginx
|
||||
|
||||
```bash
|
||||
# 复制nginx配置
|
||||
sudo cp /var/www/insurance-backend/nginx-ad.ningmuyun.com.conf /etc/nginx/sites-available/ad.ningmuyun.com
|
||||
|
||||
# 启用站点
|
||||
sudo ln -sf /etc/nginx/sites-available/ad.ningmuyun.com /etc/nginx/sites-enabled/
|
||||
|
||||
# 测试配置
|
||||
sudo nginx -t
|
||||
|
||||
# 重启nginx
|
||||
sudo systemctl restart nginx
|
||||
```
|
||||
|
||||
### 5. 启动后端服务
|
||||
|
||||
```bash
|
||||
cd /var/www/insurance-backend
|
||||
|
||||
# 给脚本执行权限
|
||||
chmod +x start.sh deploy.sh
|
||||
|
||||
# 启动服务
|
||||
./start.sh
|
||||
```
|
||||
|
||||
## 配置说明
|
||||
|
||||
### 前端配置
|
||||
- **baseURL**: `/insurance/api`
|
||||
- **访问地址**: `https://ad.ningmuyun.com/insurance/`
|
||||
|
||||
### 后端配置
|
||||
- **端口**: 3000
|
||||
- **进程管理**: PM2
|
||||
- **环境**: production
|
||||
|
||||
### nginx配置
|
||||
- **SSL端口**: 443
|
||||
- **前端路径**: `/insurance/` → 静态文件
|
||||
- **API路径**: `/insurance/api/` → 代理到后端3000端口
|
||||
|
||||
## 服务管理
|
||||
|
||||
### PM2命令
|
||||
```bash
|
||||
# 查看服务状态
|
||||
pm2 status
|
||||
|
||||
# 查看日志
|
||||
pm2 logs insurance-backend
|
||||
|
||||
# 重启服务
|
||||
pm2 restart insurance-backend
|
||||
|
||||
# 停止服务
|
||||
pm2 stop insurance-backend
|
||||
|
||||
# 监控面板
|
||||
pm2 monit
|
||||
```
|
||||
|
||||
### nginx命令
|
||||
```bash
|
||||
# 测试配置
|
||||
sudo nginx -t
|
||||
|
||||
# 重新加载配置
|
||||
sudo systemctl reload nginx
|
||||
|
||||
# 重启服务
|
||||
sudo systemctl restart nginx
|
||||
|
||||
# 查看状态
|
||||
sudo systemctl status nginx
|
||||
```
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 1. 前端无法访问
|
||||
- 检查nginx配置是否正确
|
||||
- 检查SSL证书是否有效
|
||||
- 检查静态文件路径是否正确
|
||||
|
||||
### 2. API请求失败
|
||||
- 检查后端服务是否运行: `pm2 status`
|
||||
- 检查nginx代理配置
|
||||
- 检查CORS设置
|
||||
- 查看后端日志: `pm2 logs insurance-backend`
|
||||
|
||||
### 3. 数据库连接问题
|
||||
- 检查数据库服务是否运行
|
||||
- 检查.env文件中的数据库配置
|
||||
- 检查防火墙设置
|
||||
|
||||
## 监控和维护
|
||||
|
||||
### 日志位置
|
||||
- nginx日志: `/var/log/nginx/`
|
||||
- 后端日志: `/var/www/insurance-backend/logs/`
|
||||
- PM2日志: `pm2 logs`
|
||||
|
||||
### 定期维护
|
||||
- 定期清理日志文件
|
||||
- 监控服务器资源使用情况
|
||||
- 更新SSL证书
|
||||
- 备份数据库
|
||||
@@ -1,27 +0,0 @@
|
||||
const { User } = require('./models');
|
||||
|
||||
async function checkAdminToken() {
|
||||
try {
|
||||
const admin = await User.findOne({
|
||||
where: { username: 'admin' },
|
||||
attributes: ['id', 'username', 'fixed_token', 'status']
|
||||
});
|
||||
|
||||
if (admin) {
|
||||
console.log('Admin用户信息:');
|
||||
console.log('ID:', admin.id);
|
||||
console.log('用户名:', admin.username);
|
||||
console.log('Token:', admin.fixed_token);
|
||||
console.log('状态:', admin.status);
|
||||
} else {
|
||||
console.log('未找到admin用户');
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error('错误:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
checkAdminToken();
|
||||
@@ -1,55 +0,0 @@
|
||||
const mysql = require('mysql2/promise');
|
||||
|
||||
async function checkDatabase() {
|
||||
const connection = await mysql.createConnection({
|
||||
host: '129.211.213.226',
|
||||
port: 9527,
|
||||
user: 'root',
|
||||
password: 'aiotAiot123!',
|
||||
database: 'insurance_data'
|
||||
});
|
||||
|
||||
try {
|
||||
console.log('=== 检查数据库表结构 ===');
|
||||
|
||||
// 查看所有表
|
||||
const [tables] = await connection.execute('SHOW TABLES');
|
||||
console.log('\n当前数据库中的表:');
|
||||
tables.forEach(table => {
|
||||
console.log(`- ${Object.values(table)[0]}`);
|
||||
});
|
||||
|
||||
// 检查每个表的结构和数据量
|
||||
for (const table of tables) {
|
||||
const tableName = Object.values(table)[0];
|
||||
console.log(`\n=== 表: ${tableName} ===`);
|
||||
|
||||
// 查看表结构
|
||||
const [structure] = await connection.execute(`DESCRIBE ${tableName}`);
|
||||
console.log('表结构:');
|
||||
structure.forEach(col => {
|
||||
console.log(` ${col.Field}: ${col.Type} ${col.Null === 'NO' ? 'NOT NULL' : 'NULL'} ${col.Key ? `(${col.Key})` : ''}`);
|
||||
});
|
||||
|
||||
// 查看数据量
|
||||
const [count] = await connection.execute(`SELECT COUNT(*) as count FROM ${tableName}`);
|
||||
console.log(`数据量: ${count[0].count} 条记录`);
|
||||
|
||||
// 如果有数据,显示前几条
|
||||
if (count[0].count > 0) {
|
||||
const [sample] = await connection.execute(`SELECT * FROM ${tableName} LIMIT 3`);
|
||||
console.log('示例数据:');
|
||||
sample.forEach((row, index) => {
|
||||
console.log(` 记录${index + 1}:`, JSON.stringify(row, null, 2));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('检查数据库错误:', error);
|
||||
} finally {
|
||||
await connection.end();
|
||||
}
|
||||
}
|
||||
|
||||
checkDatabase();
|
||||
@@ -1,93 +0,0 @@
|
||||
const { sequelize } = require('./config/database');
|
||||
|
||||
async function checkDatabaseStructure() {
|
||||
try {
|
||||
console.log('🔍 检查数据库表结构...\n');
|
||||
|
||||
// 检查数据库连接
|
||||
await sequelize.authenticate();
|
||||
console.log('✅ 数据库连接成功\n');
|
||||
|
||||
// 检查所有表
|
||||
const [tables] = await sequelize.query('SHOW TABLES');
|
||||
console.log('📋 数据库中的表:');
|
||||
tables.forEach(table => {
|
||||
const tableName = Object.values(table)[0];
|
||||
console.log(` - ${tableName}`);
|
||||
});
|
||||
|
||||
console.log('\n🏗️ 检查关键表结构:\n');
|
||||
|
||||
// 检查users表
|
||||
try {
|
||||
const [usersStructure] = await sequelize.query('DESCRIBE users');
|
||||
console.log('👥 users表结构:');
|
||||
usersStructure.forEach(column => {
|
||||
console.log(` - ${column.Field}: ${column.Type} ${column.Null === 'NO' ? '(必填)' : '(可选)'} ${column.Key ? `[${column.Key}]` : ''}`);
|
||||
});
|
||||
} catch (error) {
|
||||
console.log('❌ users表不存在');
|
||||
}
|
||||
|
||||
console.log('');
|
||||
|
||||
// 检查permissions表
|
||||
try {
|
||||
const [permissionsStructure] = await sequelize.query('DESCRIBE permissions');
|
||||
console.log('🔐 permissions表结构:');
|
||||
permissionsStructure.forEach(column => {
|
||||
console.log(` - ${column.Field}: ${column.Type} ${column.Null === 'NO' ? '(必填)' : '(可选)'} ${column.Key ? `[${column.Key}]` : ''}`);
|
||||
});
|
||||
} catch (error) {
|
||||
console.log('❌ permissions表不存在');
|
||||
}
|
||||
|
||||
console.log('');
|
||||
|
||||
// 检查roles表
|
||||
try {
|
||||
const [rolesStructure] = await sequelize.query('DESCRIBE roles');
|
||||
console.log('👑 roles表结构:');
|
||||
rolesStructure.forEach(column => {
|
||||
console.log(` - ${column.Field}: ${column.Type} ${column.Null === 'NO' ? '(必填)' : '(可选)'} ${column.Key ? `[${column.Key}]` : ''}`);
|
||||
});
|
||||
} catch (error) {
|
||||
console.log('❌ roles表不存在');
|
||||
}
|
||||
|
||||
console.log('');
|
||||
|
||||
// 检查role_permissions表
|
||||
try {
|
||||
const [rolePermissionsStructure] = await sequelize.query('DESCRIBE role_permissions');
|
||||
console.log('🔗 role_permissions表结构:');
|
||||
rolePermissionsStructure.forEach(column => {
|
||||
console.log(` - ${column.Field}: ${column.Type} ${column.Null === 'NO' ? '(必填)' : '(可选)'} ${column.Key ? `[${column.Key}]` : ''}`);
|
||||
});
|
||||
} catch (error) {
|
||||
console.log('❌ role_permissions表不存在');
|
||||
}
|
||||
|
||||
console.log('\n📊 检查数据量:');
|
||||
|
||||
// 检查各表数据量
|
||||
const tables_to_check = ['users', 'permissions', 'roles', 'role_permissions'];
|
||||
|
||||
for (const tableName of tables_to_check) {
|
||||
try {
|
||||
const [result] = await sequelize.query(`SELECT COUNT(*) as count FROM ${tableName}`);
|
||||
console.log(` - ${tableName}: ${result[0].count} 条记录`);
|
||||
} catch (error) {
|
||||
console.log(` - ${tableName}: 表不存在或查询失败`);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 检查失败:', error.message);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
console.log('\n🔚 检查完成');
|
||||
}
|
||||
}
|
||||
|
||||
checkDatabaseStructure();
|
||||
@@ -1,110 +0,0 @@
|
||||
const axios = require('axios');
|
||||
|
||||
// 模拟前端可能遇到的各种token问题
|
||||
async function checkFrontendIssues() {
|
||||
console.log('=== 前端Token问题排查 ===\n');
|
||||
|
||||
const browserAPI = axios.create({
|
||||
baseURL: 'http://localhost:3001',
|
||||
timeout: 10000
|
||||
});
|
||||
|
||||
try {
|
||||
// 1. 测试无token访问
|
||||
console.log('1. 测试无token访问数据仓库接口...');
|
||||
try {
|
||||
await browserAPI.get('/api/data-warehouse/overview');
|
||||
console.log('✅ 无token访问成功 (这不应该发生)');
|
||||
} catch (error) {
|
||||
console.log('❌ 无token访问失败:', error.response?.status, error.response?.data?.message);
|
||||
}
|
||||
|
||||
// 2. 测试错误token
|
||||
console.log('\n2. 测试错误token...');
|
||||
browserAPI.defaults.headers.common['Authorization'] = 'Bearer invalid_token';
|
||||
try {
|
||||
await browserAPI.get('/api/data-warehouse/overview');
|
||||
console.log('✅ 错误token访问成功 (这不应该发生)');
|
||||
} catch (error) {
|
||||
console.log('❌ 错误token访问失败:', error.response?.status, error.response?.data?.message);
|
||||
}
|
||||
|
||||
// 3. 测试过期token
|
||||
console.log('\n3. 测试过期token...');
|
||||
const expiredToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInJvbGVfaWQiOjEsInBlcm1pc3Npb25zIjpbXSwiaWF0IjoxNjAwMDAwMDAwLCJleHAiOjE2MDAwMDAwMDB9.invalid';
|
||||
browserAPI.defaults.headers.common['Authorization'] = `Bearer ${expiredToken}`;
|
||||
try {
|
||||
await browserAPI.get('/api/data-warehouse/overview');
|
||||
console.log('✅ 过期token访问成功 (这不应该发生)');
|
||||
} catch (error) {
|
||||
console.log('❌ 过期token访问失败:', error.response?.status, error.response?.data?.message);
|
||||
}
|
||||
|
||||
// 4. 测试正确登录流程
|
||||
console.log('\n4. 测试正确登录流程...');
|
||||
const loginResponse = await browserAPI.post('/api/auth/login', {
|
||||
username: 'admin',
|
||||
password: '123456'
|
||||
});
|
||||
|
||||
if (loginResponse.data?.code === 200) {
|
||||
const token = loginResponse.data.data.token;
|
||||
console.log('✅ 登录成功,获取token');
|
||||
|
||||
// 5. 测试正确token
|
||||
console.log('\n5. 测试正确token...');
|
||||
browserAPI.defaults.headers.common['Authorization'] = `Bearer ${token}`;
|
||||
|
||||
try {
|
||||
const response = await browserAPI.get('/api/data-warehouse/overview');
|
||||
console.log('✅ 正确token访问成功:', response.status);
|
||||
} catch (error) {
|
||||
console.log('❌ 正确token访问失败:', error.response?.status, error.response?.data?.message);
|
||||
}
|
||||
|
||||
// 6. 测试token格式问题
|
||||
console.log('\n6. 测试各种token格式问题...');
|
||||
|
||||
// 测试没有Bearer前缀
|
||||
browserAPI.defaults.headers.common['Authorization'] = token;
|
||||
try {
|
||||
await browserAPI.get('/api/data-warehouse/overview');
|
||||
console.log('✅ 无Bearer前缀访问成功 (这不应该发生)');
|
||||
} catch (error) {
|
||||
console.log('❌ 无Bearer前缀访问失败:', error.response?.status);
|
||||
}
|
||||
|
||||
// 测试错误的Bearer格式
|
||||
browserAPI.defaults.headers.common['Authorization'] = `bearer ${token}`;
|
||||
try {
|
||||
await browserAPI.get('/api/data-warehouse/overview');
|
||||
console.log('✅ 小写bearer访问成功');
|
||||
} catch (error) {
|
||||
console.log('❌ 小写bearer访问失败:', error.response?.status);
|
||||
}
|
||||
|
||||
// 测试多余空格
|
||||
browserAPI.defaults.headers.common['Authorization'] = `Bearer ${token}`;
|
||||
try {
|
||||
await browserAPI.get('/api/data-warehouse/overview');
|
||||
console.log('✅ 多余空格访问成功');
|
||||
} catch (error) {
|
||||
console.log('❌ 多余空格访问失败:', error.response?.status);
|
||||
}
|
||||
}
|
||||
|
||||
// 7. 检查中间件处理
|
||||
console.log('\n7. 检查认证中间件...');
|
||||
console.log('建议检查以下几点:');
|
||||
console.log('- 浏览器开发者工具中的Network标签页');
|
||||
console.log('- 请求头中是否包含正确的Authorization');
|
||||
console.log('- 响应头中是否有CORS相关错误');
|
||||
console.log('- localStorage中是否正确存储了token');
|
||||
console.log('- 前端代码中token获取逻辑是否正确');
|
||||
|
||||
} catch (error) {
|
||||
console.log('❌ 测试过程中出错:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
checkFrontendIssues();
|
||||
@@ -1,142 +0,0 @@
|
||||
const puppeteer = require('puppeteer');
|
||||
|
||||
async function checkInsuranceNavigation() {
|
||||
console.log('🔍 检查险种管理页面导航...');
|
||||
|
||||
const browser = await puppeteer.launch({
|
||||
headless: false,
|
||||
slowMo: 1000
|
||||
});
|
||||
|
||||
const page = await browser.newPage();
|
||||
|
||||
try {
|
||||
// 访问前端页面并登录
|
||||
await page.goto('http://localhost:3002', { waitUntil: 'networkidle2' });
|
||||
|
||||
// 登录
|
||||
await page.type('input[type="text"], input[placeholder*="用户名"], input[placeholder*="账号"]', 'admin');
|
||||
await page.type('input[type="password"], input[placeholder*="密码"]', '123456');
|
||||
|
||||
const loginSelectors = ['button[type="submit"]', '.login-btn', 'button'];
|
||||
for (const selector of loginSelectors) {
|
||||
try {
|
||||
const button = await page.$(selector);
|
||||
if (button) {
|
||||
await button.click();
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
// 继续尝试下一个选择器
|
||||
}
|
||||
}
|
||||
|
||||
// 等待登录完成
|
||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||
console.log('✅ 登录完成,当前URL:', page.url());
|
||||
|
||||
// 方法1:直接访问险种管理页面URL
|
||||
console.log('🔍 方法1:直接访问险种管理页面URL...');
|
||||
await page.goto('http://localhost:3002/#/insurance-types', { waitUntil: 'networkidle2' });
|
||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||
|
||||
let currentUrl = page.url();
|
||||
console.log('直接访问后的URL:', currentUrl);
|
||||
|
||||
if (currentUrl.includes('insurance-types')) {
|
||||
console.log('✅ 直接访问险种管理页面成功');
|
||||
|
||||
// 检查页面内容
|
||||
const pageContent = await page.content();
|
||||
const hasInsuranceContent = pageContent.includes('险种管理') ||
|
||||
pageContent.includes('保险类型') ||
|
||||
pageContent.includes('新增险种');
|
||||
console.log('页面包含险种管理内容:', hasInsuranceContent);
|
||||
|
||||
// 查找表格元素
|
||||
const tableElements = await page.evaluate(() => {
|
||||
const selectors = [
|
||||
'table',
|
||||
'.ant-table',
|
||||
'.ant-table-tbody',
|
||||
'.el-table',
|
||||
'.data-table',
|
||||
'[class*="table"]',
|
||||
'tbody tr',
|
||||
'.ant-table-row'
|
||||
];
|
||||
|
||||
const found = [];
|
||||
selectors.forEach(selector => {
|
||||
const elements = document.querySelectorAll(selector);
|
||||
if (elements.length > 0) {
|
||||
found.push({
|
||||
selector,
|
||||
count: elements.length,
|
||||
visible: elements[0] ? !elements[0].hidden && elements[0].offsetParent !== null : false
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return found;
|
||||
});
|
||||
|
||||
console.log('找到的表格元素:', JSON.stringify(tableElements, null, 2));
|
||||
|
||||
// 检查是否有数据加载
|
||||
const hasData = await page.evaluate(() => {
|
||||
const rows = document.querySelectorAll('.ant-table-tbody tr, tbody tr, .table-row');
|
||||
return rows.length > 0;
|
||||
});
|
||||
|
||||
console.log('表格是否有数据:', hasData);
|
||||
|
||||
// 检查加载状态
|
||||
const isLoading = await page.evaluate(() => {
|
||||
const loadingElements = document.querySelectorAll('.ant-spin, .loading, [class*="loading"]');
|
||||
return loadingElements.length > 0;
|
||||
});
|
||||
|
||||
console.log('页面是否在加载中:', isLoading);
|
||||
|
||||
// 等待一段时间看数据是否会加载
|
||||
console.log('等待数据加载...');
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
|
||||
const hasDataAfterWait = await page.evaluate(() => {
|
||||
const rows = document.querySelectorAll('.ant-table-tbody tr, tbody tr, .table-row');
|
||||
return rows.length > 0;
|
||||
});
|
||||
|
||||
console.log('等待后表格是否有数据:', hasDataAfterWait);
|
||||
|
||||
// 检查网络请求
|
||||
const networkLogs = [];
|
||||
page.on('response', response => {
|
||||
if (response.url().includes('insurance-types') || response.url().includes('api')) {
|
||||
networkLogs.push({
|
||||
url: response.url(),
|
||||
status: response.status(),
|
||||
statusText: response.statusText()
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 刷新页面看网络请求
|
||||
await page.reload({ waitUntil: 'networkidle2' });
|
||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||
|
||||
console.log('网络请求日志:', JSON.stringify(networkLogs, null, 2));
|
||||
|
||||
} else {
|
||||
console.log('❌ 直接访问险种管理页面失败,仍在:', currentUrl);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('检查导航失败:', error.message);
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
checkInsuranceNavigation().catch(console.error);
|
||||
@@ -1,116 +0,0 @@
|
||||
const puppeteer = require('puppeteer');
|
||||
|
||||
async function checkLoginDOM() {
|
||||
console.log('🔍 检查登录后的DOM结构...');
|
||||
|
||||
const browser = await puppeteer.launch({
|
||||
headless: false,
|
||||
slowMo: 500
|
||||
});
|
||||
|
||||
const page = await browser.newPage();
|
||||
|
||||
try {
|
||||
// 访问前端页面
|
||||
await page.goto('http://localhost:3002', { waitUntil: 'networkidle2' });
|
||||
|
||||
// 填写登录信息
|
||||
await page.type('input[type="text"], input[placeholder*="用户名"], input[placeholder*="账号"]', 'admin');
|
||||
await page.type('input[type="password"], input[placeholder*="密码"]', '123456');
|
||||
|
||||
// 点击登录按钮
|
||||
const loginSelectors = ['button[type="submit"]', '.login-btn', 'button'];
|
||||
let loginClicked = false;
|
||||
|
||||
for (const selector of loginSelectors) {
|
||||
try {
|
||||
const button = await page.$(selector);
|
||||
if (button) {
|
||||
await button.click();
|
||||
loginClicked = true;
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
// 继续尝试下一个选择器
|
||||
}
|
||||
}
|
||||
|
||||
if (!loginClicked) {
|
||||
await page.keyboard.press('Enter');
|
||||
}
|
||||
|
||||
// 等待页面加载
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
|
||||
// 获取当前URL
|
||||
const currentUrl = page.url();
|
||||
console.log('当前URL:', currentUrl);
|
||||
|
||||
// 检查页面标题
|
||||
const title = await page.title();
|
||||
console.log('页面标题:', title);
|
||||
|
||||
// 查找可能的用户信息元素
|
||||
const userElements = await page.evaluate(() => {
|
||||
const selectors = [
|
||||
'.user-info',
|
||||
'.header-user',
|
||||
'.logout-btn',
|
||||
'.user-name',
|
||||
'.username',
|
||||
'.user-avatar',
|
||||
'.header-right',
|
||||
'.navbar-user',
|
||||
'.el-dropdown',
|
||||
'[class*="user"]',
|
||||
'[class*="logout"]',
|
||||
'[class*="header"]'
|
||||
];
|
||||
|
||||
const found = [];
|
||||
selectors.forEach(selector => {
|
||||
const elements = document.querySelectorAll(selector);
|
||||
if (elements.length > 0) {
|
||||
found.push({
|
||||
selector,
|
||||
count: elements.length,
|
||||
text: Array.from(elements).map(el => el.textContent?.trim()).filter(t => t)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return found;
|
||||
});
|
||||
|
||||
console.log('找到的用户相关元素:', JSON.stringify(userElements, null, 2));
|
||||
|
||||
// 获取页面的主要结构
|
||||
const pageStructure = await page.evaluate(() => {
|
||||
const body = document.body;
|
||||
const mainElements = [];
|
||||
|
||||
// 查找主要的容器元素
|
||||
const containers = body.querySelectorAll('div[class], nav[class], header[class], main[class]');
|
||||
containers.forEach(el => {
|
||||
if (el.className) {
|
||||
mainElements.push({
|
||||
tag: el.tagName.toLowerCase(),
|
||||
className: el.className,
|
||||
text: el.textContent?.substring(0, 100) + '...'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return mainElements.slice(0, 20); // 只返回前20个
|
||||
});
|
||||
|
||||
console.log('页面主要结构:', JSON.stringify(pageStructure, null, 2));
|
||||
|
||||
} catch (error) {
|
||||
console.error('检查DOM失败:', error.message);
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
checkLoginDOM().catch(console.error);
|
||||
@@ -1,29 +0,0 @@
|
||||
const { Menu } = require('./models');
|
||||
const { sequelize } = require('./models');
|
||||
|
||||
async function checkMenus() {
|
||||
try {
|
||||
await sequelize.authenticate();
|
||||
console.log('数据库连接成功');
|
||||
|
||||
const menus = await Menu.findAll({
|
||||
order: [['order', 'ASC']]
|
||||
});
|
||||
|
||||
console.log('菜单数据总数:', menus.length);
|
||||
if (menus.length > 0) {
|
||||
console.log('前5条菜单数据:');
|
||||
menus.slice(0, 5).forEach(menu => {
|
||||
console.log(`- ID: ${menu.id}, Name: ${menu.name}, Key: ${menu.key}, Path: ${menu.path}`);
|
||||
});
|
||||
} else {
|
||||
console.log('数据库中没有菜单数据');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('检查菜单数据失败:', error.message);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
checkMenus();
|
||||
@@ -1,37 +0,0 @@
|
||||
const mysql = require('mysql2/promise');
|
||||
|
||||
async function checkTableStructure() {
|
||||
const connection = await mysql.createConnection({
|
||||
host: '129.211.213.226',
|
||||
port: 9527,
|
||||
user: 'root',
|
||||
password: 'aiotAiot123!',
|
||||
database: 'insurance_data'
|
||||
});
|
||||
|
||||
try {
|
||||
console.log('=== 检查表结构 ===');
|
||||
|
||||
const tables = ['insurance_types', 'insurance_applications', 'policies', 'claims'];
|
||||
|
||||
for (const table of tables) {
|
||||
try {
|
||||
console.log(`\n=== 表: ${table} ===`);
|
||||
const [columns] = await connection.execute(`DESCRIBE ${table}`);
|
||||
console.log('字段结构:');
|
||||
columns.forEach(col => {
|
||||
console.log(` ${col.Field}: ${col.Type} ${col.Null === 'NO' ? 'NOT NULL' : 'NULL'} ${col.Key ? `(${col.Key})` : ''}`);
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(`表 ${table} 不存在或有错误:`, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('检查表结构错误:', error);
|
||||
} finally {
|
||||
await connection.end();
|
||||
}
|
||||
}
|
||||
|
||||
checkTableStructure();
|
||||
@@ -1,136 +0,0 @@
|
||||
const puppeteer = require('puppeteer');
|
||||
|
||||
async function checkTokenStatus() {
|
||||
console.log('🔍 检查Token状态...');
|
||||
|
||||
const browser = await puppeteer.launch({
|
||||
headless: false,
|
||||
slowMo: 500
|
||||
});
|
||||
|
||||
const page = await browser.newPage();
|
||||
|
||||
try {
|
||||
// 访问前端页面并登录
|
||||
await page.goto('http://localhost:3002', { waitUntil: 'networkidle2' });
|
||||
|
||||
// 登录
|
||||
await page.type('input[type="text"], input[placeholder*="用户名"], input[placeholder*="账号"]', 'admin');
|
||||
await page.type('input[type="password"], input[placeholder*="密码"]', '123456');
|
||||
|
||||
const loginSelectors = ['button[type="submit"]', '.login-btn', 'button'];
|
||||
for (const selector of loginSelectors) {
|
||||
try {
|
||||
const button = await page.$(selector);
|
||||
if (button) {
|
||||
await button.click();
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
// 继续尝试下一个选择器
|
||||
}
|
||||
}
|
||||
|
||||
// 等待登录完成
|
||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||
console.log('✅ 登录完成,当前URL:', page.url());
|
||||
|
||||
// 检查localStorage中的Token信息
|
||||
const tokenInfo = await page.evaluate(() => {
|
||||
return {
|
||||
accessToken: localStorage.getItem('accessToken'),
|
||||
refreshToken: localStorage.getItem('refreshToken'),
|
||||
tokenExpiresAt: localStorage.getItem('tokenExpiresAt'),
|
||||
userInfo: localStorage.getItem('userInfo'),
|
||||
currentTime: Date.now()
|
||||
};
|
||||
});
|
||||
|
||||
console.log('Token信息:');
|
||||
console.log('- accessToken:', tokenInfo.accessToken ? `${tokenInfo.accessToken.substring(0, 20)}...` : 'null');
|
||||
console.log('- refreshToken:', tokenInfo.refreshToken ? `${tokenInfo.refreshToken.substring(0, 20)}...` : 'null');
|
||||
console.log('- tokenExpiresAt:', tokenInfo.tokenExpiresAt);
|
||||
console.log('- userInfo:', tokenInfo.userInfo);
|
||||
console.log('- currentTime:', tokenInfo.currentTime);
|
||||
|
||||
if (tokenInfo.tokenExpiresAt) {
|
||||
const expiresAt = parseInt(tokenInfo.tokenExpiresAt);
|
||||
const isExpired = tokenInfo.currentTime >= expiresAt;
|
||||
const timeUntilExpiry = expiresAt - tokenInfo.currentTime;
|
||||
|
||||
console.log('- Token是否过期:', isExpired);
|
||||
console.log('- 距离过期时间:', Math.round(timeUntilExpiry / 1000), '秒');
|
||||
}
|
||||
|
||||
// 测试API调用
|
||||
console.log('🔍 测试API调用...');
|
||||
const apiResponse = await page.evaluate(async () => {
|
||||
try {
|
||||
const token = localStorage.getItem('accessToken');
|
||||
const response = await fetch('http://localhost:3000/api/insurance-types', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
ok: response.ok,
|
||||
data: response.ok ? await response.json() : await response.text()
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
console.log('API响应:', JSON.stringify(apiResponse, null, 2));
|
||||
|
||||
// 现在尝试访问险种管理页面
|
||||
console.log('🔍 尝试访问险种管理页面...');
|
||||
await page.goto('http://localhost:3002/#/insurance-types', { waitUntil: 'networkidle2' });
|
||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||
|
||||
const finalUrl = page.url();
|
||||
console.log('最终URL:', finalUrl);
|
||||
|
||||
if (finalUrl.includes('insurance-types')) {
|
||||
console.log('✅ 成功访问险种管理页面');
|
||||
|
||||
// 检查页面是否有表格
|
||||
const hasTable = await page.evaluate(() => {
|
||||
const tables = document.querySelectorAll('table, .ant-table, .ant-table-tbody');
|
||||
return tables.length > 0;
|
||||
});
|
||||
|
||||
console.log('页面是否有表格:', hasTable);
|
||||
|
||||
} else if (finalUrl.includes('login')) {
|
||||
console.log('❌ 被重定向到登录页面');
|
||||
|
||||
// 检查控制台错误
|
||||
const consoleLogs = [];
|
||||
page.on('console', msg => {
|
||||
if (msg.type() === 'error') {
|
||||
consoleLogs.push(msg.text());
|
||||
}
|
||||
});
|
||||
|
||||
console.log('控制台错误:', consoleLogs);
|
||||
|
||||
} else {
|
||||
console.log('❌ 访问了其他页面:', finalUrl);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('检查Token状态失败:', error.message);
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
checkTokenStatus().catch(console.error);
|
||||
@@ -1,61 +0,0 @@
|
||||
const { User, Role } = require('./models');
|
||||
|
||||
async function checkUsers() {
|
||||
try {
|
||||
console.log('=== 检查数据库中的用户数据 ===\n');
|
||||
|
||||
// 1. 查询所有用户
|
||||
const users = await User.findAll({
|
||||
include: [{
|
||||
model: Role,
|
||||
as: 'role'
|
||||
}]
|
||||
});
|
||||
|
||||
console.log(`数据库中共有 ${users.length} 个用户:`);
|
||||
|
||||
users.forEach(user => {
|
||||
console.log(`- ID: ${user.id}, 用户名: ${user.username}, 姓名: ${user.real_name}, 状态: ${user.status}, 角色: ${user.role?.name || '无角色'}`);
|
||||
});
|
||||
|
||||
// 2. 特别检查ID为1的用户
|
||||
console.log('\n=== 检查ID为1的用户 ===');
|
||||
const user1 = await User.findByPk(1, {
|
||||
include: [{
|
||||
model: Role,
|
||||
as: 'role'
|
||||
}]
|
||||
});
|
||||
|
||||
if (user1) {
|
||||
console.log('✅ 找到ID为1的用户:');
|
||||
console.log(JSON.stringify(user1.toJSON(), null, 2));
|
||||
} else {
|
||||
console.log('❌ 没有找到ID为1的用户');
|
||||
}
|
||||
|
||||
// 3. 检查admin用户
|
||||
console.log('\n=== 检查admin用户 ===');
|
||||
const adminUser = await User.findOne({
|
||||
where: { username: 'admin' },
|
||||
include: [{
|
||||
model: Role,
|
||||
as: 'role'
|
||||
}]
|
||||
});
|
||||
|
||||
if (adminUser) {
|
||||
console.log('✅ 找到admin用户:');
|
||||
console.log(`ID: ${adminUser.id}, 用户名: ${adminUser.username}, 状态: ${adminUser.status}`);
|
||||
} else {
|
||||
console.log('❌ 没有找到admin用户');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('检查用户数据时出错:', error);
|
||||
} finally {
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
checkUsers();
|
||||
33
insurance_backend/config/config.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"development": {
|
||||
"username": "root",
|
||||
"password": "aiotAiot123!",
|
||||
"database": "insurance_data",
|
||||
"host": "129.211.213.226",
|
||||
"port": 9527,
|
||||
"dialect": "mysql",
|
||||
"timezone": "+08:00",
|
||||
"dialectOptions": {
|
||||
"dateStrings": true,
|
||||
"typeCast": true
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"username": "root",
|
||||
"password": "aiotAiot123!",
|
||||
"database": "insurance_data_test",
|
||||
"host": "129.211.213.226",
|
||||
"port": 9527,
|
||||
"dialect": "mysql",
|
||||
"timezone": "+08:00"
|
||||
},
|
||||
"production": {
|
||||
"username": "root",
|
||||
"password": "aiotAiot123!",
|
||||
"database": "insurance_data_prod",
|
||||
"host": "129.211.213.226",
|
||||
"port": 9527,
|
||||
"dialect": "mysql",
|
||||
"timezone": "+08:00"
|
||||
}
|
||||
}
|
||||
49
insurance_backend/config/database.js
Normal file
@@ -0,0 +1,49 @@
|
||||
const { Sequelize } = require('sequelize');
|
||||
require('dotenv').config();
|
||||
|
||||
// 创建Sequelize实例
|
||||
const sequelize = new Sequelize({
|
||||
dialect: process.env.DB_DIALECT || 'mysql',
|
||||
host: process.env.DB_HOST || '129.211.213.226',
|
||||
port: process.env.DB_PORT || 9527,
|
||||
database: process.env.DB_DATABASE || 'insurance_data',
|
||||
username: process.env.DB_USER || 'root',
|
||||
password: process.env.DB_PASSWORD || 'aiotAiot123!',
|
||||
logging: process.env.NODE_ENV === 'development' ? console.log : false,
|
||||
pool: {
|
||||
max: 10,
|
||||
min: 0,
|
||||
acquire: 30000,
|
||||
idle: 10000
|
||||
},
|
||||
define: {
|
||||
timestamps: true,
|
||||
underscored: true,
|
||||
charset: 'utf8mb4',
|
||||
collate: 'utf8mb4_unicode_ci'
|
||||
},
|
||||
dialectOptions: {
|
||||
// 解决MySQL严格模式问题
|
||||
dateStrings: true,
|
||||
typeCast: true,
|
||||
// 允许0000-00-00日期值
|
||||
connectAttributes: {
|
||||
sql_mode: 'TRADITIONAL'
|
||||
}
|
||||
},
|
||||
timezone: '+08:00' // 设置时区为东八区
|
||||
});
|
||||
|
||||
// 测试数据库连接
|
||||
const testConnection = async () => {
|
||||
try {
|
||||
await sequelize.authenticate();
|
||||
console.log('✅ 数据库连接成功');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('❌ 数据库连接失败:', error.message);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { sequelize, testConnection };
|
||||
42
insurance_backend/config/redis.js
Normal file
@@ -0,0 +1,42 @@
|
||||
const redis = require('redis');
|
||||
require('dotenv').config();
|
||||
|
||||
// 创建Redis客户端
|
||||
const createRedisClient = () => {
|
||||
const client = redis.createClient({
|
||||
socket: {
|
||||
host: process.env.REDIS_HOST || 'localhost',
|
||||
port: process.env.REDIS_PORT || 6379
|
||||
},
|
||||
password: process.env.REDIS_PASSWORD || '',
|
||||
legacyMode: false
|
||||
});
|
||||
|
||||
// 错误处理
|
||||
client.on('error', (err) => {
|
||||
console.error('❌ Redis连接错误:', err);
|
||||
});
|
||||
|
||||
// 连接成功
|
||||
client.on('connect', () => {
|
||||
console.log('✅ Redis连接成功');
|
||||
});
|
||||
|
||||
return client;
|
||||
};
|
||||
|
||||
// 创建并连接Redis客户端
|
||||
const redisClient = createRedisClient();
|
||||
|
||||
// 连接Redis
|
||||
const connectRedis = async () => {
|
||||
try {
|
||||
await redisClient.connect();
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('❌ Redis连接失败:', error.message);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { redisClient, connectRedis };
|
||||
244
insurance_backend/controllers/claimController.js
Normal file
@@ -0,0 +1,244 @@
|
||||
const { Claim, Policy, User, InsuranceApplication, InsuranceType } = require('../models');
|
||||
const responseFormat = require('../utils/response');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// 获取理赔列表
|
||||
const getClaims = async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
claim_no,
|
||||
customer_name,
|
||||
claim_status,
|
||||
dateRange,
|
||||
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 (dateRange && dateRange.start && dateRange.end) {
|
||||
whereClause.claim_date = {
|
||||
[Op.between]: [new Date(dateRange.start), new Date(dateRange.end)]
|
||||
};
|
||||
}
|
||||
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
const { count, rows } = await Claim.findAndCountAll({
|
||||
where: whereClause,
|
||||
include: [
|
||||
{
|
||||
model: Policy,
|
||||
as: 'policy',
|
||||
attributes: ['id', 'policy_no', 'coverage_amount'],
|
||||
include: [
|
||||
{
|
||||
model: InsuranceApplication,
|
||||
as: 'application',
|
||||
attributes: ['id', 'customer_name'],
|
||||
include: [
|
||||
{
|
||||
model: InsuranceType,
|
||||
as: 'insurance_type',
|
||||
attributes: ['id', 'name']
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
model: User,
|
||||
as: 'customer',
|
||||
attributes: ['id', 'real_name', 'username']
|
||||
},
|
||||
{
|
||||
model: User,
|
||||
as: 'reviewer',
|
||||
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 createClaim = async (req, res) => {
|
||||
try {
|
||||
const claimData = req.body;
|
||||
|
||||
// 生成理赔编号
|
||||
const claimNo = `CLM${Date.now()}${Math.random().toString(36).substr(2, 6).toUpperCase()}`;
|
||||
|
||||
const claim = await Claim.create({
|
||||
...claimData,
|
||||
claim_no: claimNo
|
||||
});
|
||||
|
||||
res.status(201).json(responseFormat.created(claim, '理赔申请创建成功'));
|
||||
} catch (error) {
|
||||
console.error('创建理赔申请错误:', error);
|
||||
res.status(500).json(responseFormat.error('创建理赔申请失败'));
|
||||
}
|
||||
};
|
||||
|
||||
// 获取单个理赔详情
|
||||
const getClaimById = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const claim = await Claim.findByPk(id, {
|
||||
include: [
|
||||
{
|
||||
model: Policy,
|
||||
as: 'policy',
|
||||
include: [
|
||||
{
|
||||
model: InsuranceApplication,
|
||||
as: 'application',
|
||||
include: [
|
||||
{
|
||||
model: InsuranceType,
|
||||
as: 'insurance_type',
|
||||
attributes: ['id', 'name', 'description']
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
model: User,
|
||||
as: 'customer',
|
||||
attributes: ['id', 'real_name', 'username', 'phone', 'email']
|
||||
},
|
||||
{
|
||||
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 reviewClaim = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { claim_status, review_notes } = req.body;
|
||||
const reviewerId = req.user.id;
|
||||
|
||||
const claim = await Claim.findByPk(id);
|
||||
if (!claim) {
|
||||
return res.status(404).json(responseFormat.error('理赔申请不存在'));
|
||||
}
|
||||
|
||||
if (!['approved', 'rejected', 'processing', 'paid'].includes(claim_status)) {
|
||||
return res.status(400).json(responseFormat.error('无效的理赔状态'));
|
||||
}
|
||||
|
||||
await claim.update({
|
||||
claim_status,
|
||||
review_notes,
|
||||
reviewer_id: reviewerId,
|
||||
review_date: new Date()
|
||||
});
|
||||
|
||||
res.json(responseFormat.success(claim, '理赔申请审核成功'));
|
||||
} catch (error) {
|
||||
console.error('审核理赔申请错误:', error);
|
||||
res.status(500).json(responseFormat.error('审核理赔申请失败'));
|
||||
}
|
||||
};
|
||||
|
||||
// 更新理赔支付状态
|
||||
const updateClaimPayment = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const claim = await Claim.findByPk(id);
|
||||
if (!claim) {
|
||||
return res.status(404).json(responseFormat.error('理赔申请不存在'));
|
||||
}
|
||||
|
||||
if (claim.claim_status !== 'approved') {
|
||||
return res.status(400).json(responseFormat.error('只有已批准的理赔才能进行支付'));
|
||||
}
|
||||
|
||||
await claim.update({
|
||||
claim_status: 'paid',
|
||||
payment_date: new Date()
|
||||
});
|
||||
|
||||
res.json(responseFormat.success(claim, '理赔支付状态更新成功'));
|
||||
} catch (error) {
|
||||
console.error('更新理赔支付状态错误:', error);
|
||||
res.status(500).json(responseFormat.error('更新理赔支付状态失败'));
|
||||
}
|
||||
};
|
||||
|
||||
// 获取理赔统计
|
||||
const getClaimStats = async (req, res) => {
|
||||
try {
|
||||
const stats = await Claim.findAll({
|
||||
attributes: [
|
||||
'claim_status',
|
||||
[Claim.sequelize.fn('COUNT', Claim.sequelize.col('id')), 'count'],
|
||||
[Claim.sequelize.fn('SUM', Claim.sequelize.col('claim_amount')), 'total_amount']
|
||||
],
|
||||
group: ['claim_status']
|
||||
});
|
||||
|
||||
const total = await Claim.count();
|
||||
const totalAmount = await Claim.sum('claim_amount');
|
||||
|
||||
res.json(responseFormat.success({
|
||||
stats,
|
||||
total,
|
||||
total_amount: totalAmount || 0
|
||||
}, '获取理赔统计成功'));
|
||||
} catch (error) {
|
||||
console.error('获取理赔统计错误:', error);
|
||||
res.status(500).json(responseFormat.error('获取理赔统计失败'));
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getClaims,
|
||||
createClaim,
|
||||
getClaimById,
|
||||
reviewClaim,
|
||||
updateClaimPayment,
|
||||
getClaimStats
|
||||
};
|
||||
435
insurance_backend/controllers/deviceController.js
Normal file
@@ -0,0 +1,435 @@
|
||||
const { Device, DeviceAlert, User } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
/**
|
||||
* 设备控制器
|
||||
*/
|
||||
class DeviceController {
|
||||
|
||||
/**
|
||||
* 获取设备列表
|
||||
*/
|
||||
static async getDeviceList(req, res) {
|
||||
try {
|
||||
const {
|
||||
page = 1,
|
||||
limit = 20,
|
||||
device_type,
|
||||
status,
|
||||
farm_id,
|
||||
pen_id,
|
||||
keyword
|
||||
} = req.query;
|
||||
|
||||
// 构建查询条件
|
||||
const whereCondition = {};
|
||||
if (device_type) {
|
||||
whereCondition.device_type = device_type;
|
||||
}
|
||||
if (status) {
|
||||
whereCondition.status = status;
|
||||
}
|
||||
if (farm_id) {
|
||||
whereCondition.farm_id = farm_id;
|
||||
}
|
||||
if (pen_id) {
|
||||
whereCondition.pen_id = pen_id;
|
||||
}
|
||||
if (keyword) {
|
||||
whereCondition[Op.or] = [
|
||||
{ device_code: { [Op.like]: `%${keyword}%` } },
|
||||
{ device_name: { [Op.like]: `%${keyword}%` } },
|
||||
{ device_model: { [Op.like]: `%${keyword}%` } },
|
||||
{ manufacturer: { [Op.like]: `%${keyword}%` } }
|
||||
];
|
||||
}
|
||||
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
const { count, rows } = await Device.findAndCountAll({
|
||||
where: whereCondition,
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: 'creator',
|
||||
attributes: ['id', 'username', 'real_name'],
|
||||
required: false
|
||||
}
|
||||
],
|
||||
order: [['created_at', 'DESC']],
|
||||
limit: parseInt(limit),
|
||||
offset: offset
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
devices: rows,
|
||||
pagination: {
|
||||
current_page: parseInt(page),
|
||||
per_page: parseInt(limit),
|
||||
total: count,
|
||||
total_pages: Math.ceil(count / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('获取设备列表失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取设备列表失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备详情
|
||||
*/
|
||||
static async getDeviceDetail(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const device = await Device.findByPk(id, {
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: 'creator',
|
||||
attributes: ['id', 'username', 'real_name'],
|
||||
required: false
|
||||
},
|
||||
{
|
||||
model: User,
|
||||
as: 'updater',
|
||||
attributes: ['id', 'username', 'real_name'],
|
||||
required: false
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (!device) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '设备不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 获取设备相关的预警统计
|
||||
const alertStats = await DeviceAlert.findAll({
|
||||
attributes: [
|
||||
'alert_level',
|
||||
[DeviceAlert.sequelize.fn('COUNT', DeviceAlert.sequelize.col('id')), 'count']
|
||||
],
|
||||
where: {
|
||||
device_id: id
|
||||
},
|
||||
group: ['alert_level'],
|
||||
raw: true
|
||||
});
|
||||
|
||||
// 获取最近的预警记录
|
||||
const recentAlerts = await DeviceAlert.findAll({
|
||||
where: {
|
||||
device_id: id
|
||||
},
|
||||
order: [['alert_time', 'DESC']],
|
||||
limit: 5,
|
||||
attributes: ['id', 'alert_type', 'alert_level', 'alert_title', 'alert_time', 'status']
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
device,
|
||||
alert_stats: alertStats,
|
||||
recent_alerts: recentAlerts
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('获取设备详情失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取设备详情失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建设备
|
||||
*/
|
||||
static async createDevice(req, res) {
|
||||
try {
|
||||
const {
|
||||
device_code,
|
||||
device_name,
|
||||
device_type,
|
||||
device_model,
|
||||
manufacturer,
|
||||
installation_location,
|
||||
installation_date,
|
||||
farm_id,
|
||||
pen_id,
|
||||
status = 'normal'
|
||||
} = req.body;
|
||||
|
||||
const userId = req.user.id;
|
||||
|
||||
// 检查设备编号是否已存在
|
||||
const existingDevice = await Device.findOne({
|
||||
where: { device_code }
|
||||
});
|
||||
|
||||
if (existingDevice) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '设备编号已存在'
|
||||
});
|
||||
}
|
||||
|
||||
const device = await Device.create({
|
||||
device_code,
|
||||
device_name,
|
||||
device_type,
|
||||
device_model,
|
||||
manufacturer,
|
||||
installation_location,
|
||||
installation_date,
|
||||
farm_id,
|
||||
pen_id,
|
||||
status,
|
||||
created_by: userId,
|
||||
updated_by: userId
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '设备创建成功',
|
||||
data: device
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('创建设备失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '创建设备失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新设备
|
||||
*/
|
||||
static async updateDevice(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const {
|
||||
device_code,
|
||||
device_name,
|
||||
device_type,
|
||||
device_model,
|
||||
manufacturer,
|
||||
installation_location,
|
||||
installation_date,
|
||||
farm_id,
|
||||
pen_id,
|
||||
status
|
||||
} = req.body;
|
||||
|
||||
const userId = req.user.id;
|
||||
|
||||
const device = await Device.findByPk(id);
|
||||
if (!device) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '设备不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 如果修改了设备编号,检查是否与其他设备重复
|
||||
if (device_code && device_code !== device.device_code) {
|
||||
const existingDevice = await Device.findOne({
|
||||
where: {
|
||||
device_code,
|
||||
id: { [Op.ne]: id }
|
||||
}
|
||||
});
|
||||
|
||||
if (existingDevice) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '设备编号已存在'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await device.update({
|
||||
device_code,
|
||||
device_name,
|
||||
device_type,
|
||||
device_model,
|
||||
manufacturer,
|
||||
installation_location,
|
||||
installation_date,
|
||||
farm_id,
|
||||
pen_id,
|
||||
status,
|
||||
updated_by: userId
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '设备更新成功',
|
||||
data: device
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('更新设备失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '更新设备失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除设备
|
||||
*/
|
||||
static async deleteDevice(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const device = await Device.findByPk(id);
|
||||
if (!device) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '设备不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查是否有关联的预警记录
|
||||
const alertCount = await DeviceAlert.count({
|
||||
where: { device_id: id }
|
||||
});
|
||||
|
||||
if (alertCount > 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '该设备存在预警记录,无法删除'
|
||||
});
|
||||
}
|
||||
|
||||
await device.destroy();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '设备删除成功'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('删除设备失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '删除设备失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备类型列表
|
||||
*/
|
||||
static async getDeviceTypes(req, res) {
|
||||
try {
|
||||
const deviceTypes = await Device.findAll({
|
||||
attributes: [
|
||||
[Device.sequelize.fn('DISTINCT', Device.sequelize.col('device_type')), 'device_type']
|
||||
],
|
||||
where: {
|
||||
device_type: {
|
||||
[Op.ne]: null
|
||||
}
|
||||
},
|
||||
raw: true
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: deviceTypes.map(item => item.device_type)
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('获取设备类型失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取设备类型失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备状态统计
|
||||
*/
|
||||
static async getDeviceStats(req, res) {
|
||||
try {
|
||||
const { farm_id } = req.query;
|
||||
|
||||
const whereCondition = {};
|
||||
if (farm_id) {
|
||||
whereCondition.farm_id = farm_id;
|
||||
}
|
||||
|
||||
// 按状态统计
|
||||
const devicesByStatus = await Device.findAll({
|
||||
attributes: [
|
||||
'status',
|
||||
[Device.sequelize.fn('COUNT', Device.sequelize.col('id')), 'count']
|
||||
],
|
||||
where: whereCondition,
|
||||
group: ['status'],
|
||||
raw: true
|
||||
});
|
||||
|
||||
// 按类型统计
|
||||
const devicesByType = await Device.findAll({
|
||||
attributes: [
|
||||
'device_type',
|
||||
[Device.sequelize.fn('COUNT', Device.sequelize.col('id')), 'count']
|
||||
],
|
||||
where: whereCondition,
|
||||
group: ['device_type'],
|
||||
raw: true
|
||||
});
|
||||
|
||||
// 总设备数
|
||||
const totalDevices = await Device.count({
|
||||
where: whereCondition
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
total_devices: totalDevices,
|
||||
devices_by_status: devicesByStatus,
|
||||
devices_by_type: devicesByType
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('获取设备统计失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取设备统计失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DeviceController;
|
||||
477
insurance_backend/controllers/installationTaskController.js
Normal file
@@ -0,0 +1,477 @@
|
||||
const InstallationTask = require('../models/InstallationTask');
|
||||
const User = require('../models/User');
|
||||
const { Op, sequelize } = require('sequelize');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
class InstallationTaskController {
|
||||
|
||||
// 获取待安装任务列表
|
||||
async getInstallationTasks(req, res) {
|
||||
try {
|
||||
const {
|
||||
page = 1,
|
||||
pageSize = 10,
|
||||
policyNumber,
|
||||
customerName,
|
||||
installationStatus,
|
||||
priority,
|
||||
keyword,
|
||||
startDate,
|
||||
endDate
|
||||
} = req.query;
|
||||
|
||||
const offset = (page - 1) * pageSize;
|
||||
const limit = parseInt(pageSize);
|
||||
|
||||
// 构建查询条件
|
||||
const whereConditions = {};
|
||||
|
||||
if (policyNumber) {
|
||||
whereConditions.policyNumber = { [Op.like]: `%${policyNumber}%` };
|
||||
}
|
||||
|
||||
if (customerName) {
|
||||
whereConditions.customerName = { [Op.like]: `%${customerName}%` };
|
||||
}
|
||||
|
||||
if (installationStatus) {
|
||||
whereConditions.installationStatus = installationStatus;
|
||||
}
|
||||
|
||||
if (priority) {
|
||||
whereConditions.priority = priority;
|
||||
}
|
||||
|
||||
// 关键字搜索(搜索申请单号、保单编号、客户姓名等)
|
||||
if (keyword) {
|
||||
whereConditions[Op.or] = [
|
||||
{ applicationNumber: { [Op.like]: `%${keyword}%` } },
|
||||
{ policyNumber: { [Op.like]: `%${keyword}%` } },
|
||||
{ customerName: { [Op.like]: `%${customerName}%` } },
|
||||
{ productName: { [Op.like]: `%${keyword}%` } }
|
||||
];
|
||||
}
|
||||
|
||||
if (startDate && endDate) {
|
||||
whereConditions.taskGeneratedTime = {
|
||||
[Op.between]: [new Date(startDate), new Date(endDate)]
|
||||
};
|
||||
}
|
||||
|
||||
const { count, rows } = await InstallationTask.findAndCountAll({
|
||||
where: whereConditions,
|
||||
order: [['taskGeneratedTime', 'DESC']],
|
||||
offset,
|
||||
limit
|
||||
});
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
status: 'success',
|
||||
message: '获取待安装任务列表成功',
|
||||
data: {
|
||||
list: rows,
|
||||
total: count,
|
||||
page: parseInt(page),
|
||||
pageSize: limit,
|
||||
totalPages: Math.ceil(count / limit)
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('获取待安装任务列表失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
status: 'error',
|
||||
message: '获取待安装任务列表失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 创建待安装任务
|
||||
async createInstallationTask(req, res) {
|
||||
try {
|
||||
const {
|
||||
applicationNumber,
|
||||
policyNumber,
|
||||
productName,
|
||||
customerName,
|
||||
idType,
|
||||
idNumber,
|
||||
livestockSupplyType,
|
||||
pendingDevices,
|
||||
installationStatus = '待安装',
|
||||
priority = '中',
|
||||
assignedTo,
|
||||
taskGeneratedTime = new Date()
|
||||
} = req.body;
|
||||
|
||||
// 验证必填字段
|
||||
if (!applicationNumber || !policyNumber || !productName || !customerName) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
status: 'error',
|
||||
message: '申请单号、保单编号、产品名称、客户姓名为必填项'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查申请单号是否已存在
|
||||
const existingTask = await InstallationTask.findOne({
|
||||
where: { applicationNumber }
|
||||
});
|
||||
|
||||
if (existingTask) {
|
||||
return res.status(409).json({
|
||||
code: 409,
|
||||
status: 'error',
|
||||
message: '该申请单号已存在待安装任务'
|
||||
});
|
||||
}
|
||||
|
||||
const installationTask = await InstallationTask.create({
|
||||
applicationNumber,
|
||||
policyNumber,
|
||||
productName,
|
||||
customerName,
|
||||
idType: idType || '身份证',
|
||||
idNumber: idNumber || '',
|
||||
livestockSupplyType,
|
||||
pendingDevices: pendingDevices ? JSON.stringify(pendingDevices) : null,
|
||||
installationStatus,
|
||||
priority,
|
||||
assignedTo,
|
||||
taskGeneratedTime,
|
||||
createdBy: req.user?.id
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
code: 201,
|
||||
status: 'success',
|
||||
message: '创建待安装任务成功',
|
||||
data: installationTask
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('创建待安装任务失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
status: 'error',
|
||||
message: '创建待安装任务失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 获取待安装任务详情
|
||||
async getInstallationTaskById(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const installationTask = await InstallationTask.findByPk(id, {
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: 'technician',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
},
|
||||
{
|
||||
model: User,
|
||||
as: 'creator',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
},
|
||||
{
|
||||
model: User,
|
||||
as: 'updater',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (!installationTask) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
status: 'error',
|
||||
message: '待安装任务不存在'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
status: 'success',
|
||||
message: '获取待安装任务详情成功',
|
||||
data: installationTask
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('获取待安装任务详情失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
status: 'error',
|
||||
message: '获取待安装任务详情失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 更新待安装任务
|
||||
async updateInstallationTask(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const updateData = { ...req.body };
|
||||
|
||||
// 添加更新人信息
|
||||
updateData.updatedBy = req.user?.id;
|
||||
|
||||
// 处理设备数据
|
||||
if (updateData.pendingDevices) {
|
||||
updateData.pendingDevices = JSON.stringify(updateData.pendingDevices);
|
||||
}
|
||||
|
||||
// 处理安装完成时间
|
||||
if (updateData.installationStatus === '已安装' && !updateData.installationCompletedTime) {
|
||||
updateData.installationCompletedTime = new Date();
|
||||
}
|
||||
|
||||
const [updatedCount] = await InstallationTask.update(updateData, {
|
||||
where: { id }
|
||||
});
|
||||
|
||||
if (updatedCount === 0) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
status: 'error',
|
||||
message: '待安装任务不存在或未做任何修改'
|
||||
});
|
||||
}
|
||||
|
||||
// 获取更新后的任务
|
||||
const updatedTask = await InstallationTask.findByPk(id);
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
status: 'success',
|
||||
message: '更新待安装任务成功',
|
||||
data: updatedTask
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('更新待安装任务失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
status: 'error',
|
||||
message: '更新待安装任务失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 删除待安装任务
|
||||
async deleteInstallationTask(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const deletedCount = await InstallationTask.destroy({
|
||||
where: { id }
|
||||
});
|
||||
|
||||
if (deletedCount === 0) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
status: 'error',
|
||||
message: '待安装任务不存在'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
status: 'success',
|
||||
message: '删除待安装任务成功'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('删除待安装任务失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
status: 'error',
|
||||
message: '删除待安装任务失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 批量操作待安装任务
|
||||
async batchOperateInstallationTasks(req, res) {
|
||||
try {
|
||||
const { ids, operation, data } = req.body;
|
||||
|
||||
if (!ids || !Array.isArray(ids) || ids.length === 0) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
status: 'error',
|
||||
message: '任务ID列表不能为空'
|
||||
});
|
||||
}
|
||||
|
||||
let result;
|
||||
switch (operation) {
|
||||
case 'assign':
|
||||
if (!data.assignedTo) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
status: 'error',
|
||||
message: '分配操作需要指定分配给的用户'
|
||||
});
|
||||
}
|
||||
result = await InstallationTask.update(
|
||||
{ assignedTo: data.assignedTo, updatedBy: req.user?.id },
|
||||
{ where: { id: ids } }
|
||||
);
|
||||
break;
|
||||
case 'updateStatus':
|
||||
if (!data.installationStatus) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
status: 'error',
|
||||
message: '状态更新操作需要指定新状态'
|
||||
});
|
||||
}
|
||||
result = await InstallationTask.update(
|
||||
{ installationStatus: data.installationStatus, updatedBy: req.user?.id },
|
||||
{ where: { id: ids } }
|
||||
);
|
||||
break;
|
||||
case 'delete':
|
||||
result = await InstallationTask.destroy({ where: { id: ids } });
|
||||
break;
|
||||
default:
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
status: 'error',
|
||||
message: '不支持的操作类型'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
status: 'success',
|
||||
message: `批量${operation}操作完成`,
|
||||
data: { affectedRows: result[0] || result }
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('批量操作待安装任务失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
status: 'error',
|
||||
message: '批量操作失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 导出待安装任务数据
|
||||
async exportInstallationTasks(req, res) {
|
||||
try {
|
||||
const { ids } = req.query;
|
||||
let where = {};
|
||||
|
||||
if (ids) {
|
||||
where.id = { [Op.in]: ids.split(',') };
|
||||
}
|
||||
|
||||
const tasks = await InstallationTask.findAll({
|
||||
where,
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: 'technician',
|
||||
attributes: ['username', 'real_name']
|
||||
},
|
||||
{
|
||||
model: User,
|
||||
as: 'creator',
|
||||
attributes: ['username', 'real_name']
|
||||
}
|
||||
],
|
||||
order: [['taskGeneratedTime', 'DESC']]
|
||||
});
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
status: 'success',
|
||||
message: '导出待安装任务数据成功',
|
||||
data: tasks
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('导出待安装任务数据失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
status: 'error',
|
||||
message: '导出数据失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 获取任务统计信息
|
||||
async getInstallationTaskStats(req, res) {
|
||||
try {
|
||||
// 按状态统计
|
||||
const statusStats = await InstallationTask.findAll({
|
||||
attributes: [
|
||||
'installationStatus',
|
||||
[sequelize.fn('COUNT', sequelize.col('id')), 'count']
|
||||
],
|
||||
group: ['installationStatus'],
|
||||
raw: true
|
||||
});
|
||||
|
||||
// 按优先级统计
|
||||
const priorityStats = await InstallationTask.findAll({
|
||||
attributes: [
|
||||
'priority',
|
||||
[sequelize.fn('COUNT', sequelize.col('id')), 'count']
|
||||
],
|
||||
group: ['priority'],
|
||||
raw: true
|
||||
});
|
||||
|
||||
// 总数统计
|
||||
const total = await InstallationTask.count();
|
||||
|
||||
// 本月新增任务
|
||||
const thisMonth = await InstallationTask.count({
|
||||
where: {
|
||||
taskGeneratedTime: {
|
||||
[Op.gte]: new Date(new Date().getFullYear(), new Date().getMonth(), 1)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
status: 'success',
|
||||
message: '获取任务统计信息成功',
|
||||
data: {
|
||||
total,
|
||||
thisMonth,
|
||||
statusStats,
|
||||
priorityStats
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('获取任务统计信息失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
status: 'error',
|
||||
message: '获取统计信息失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new InstallationTaskController();
|
||||
409
insurance_backend/controllers/insuranceController.js
Normal file
@@ -0,0 +1,409 @@
|
||||
const { InsuranceApplication, InsuranceType, User } = require('../models');
|
||||
const responseFormat = require('../utils/response');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// 获取保险申请列表
|
||||
const getApplications = async (req, res) => {
|
||||
try {
|
||||
console.log('获取保险申请列表 - 请求参数:', req.query);
|
||||
const {
|
||||
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 (applicationNumber) {
|
||||
whereClause.application_no = { [Op.like]: `%${applicationNumber}%` };
|
||||
}
|
||||
|
||||
// 投保人姓名筛选
|
||||
if (applicantName) {
|
||||
whereClause.customer_name = { [Op.like]: `%${applicantName}%` };
|
||||
}
|
||||
|
||||
// 状态筛选
|
||||
if (status) {
|
||||
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 = {
|
||||
[Op.between]: [new Date(dateRange.start), new Date(dateRange.end)]
|
||||
};
|
||||
}
|
||||
|
||||
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: 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),
|
||||
total: count
|
||||
}, '获取保险申请列表成功'));
|
||||
} catch (error) {
|
||||
console.error('获取保险申请列表错误:', error);
|
||||
res.status(500).json(responseFormat.error('获取保险申请列表失败'));
|
||||
}
|
||||
};
|
||||
|
||||
// 创建保险申请
|
||||
const createApplication = async (req, res) => {
|
||||
try {
|
||||
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 = `${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({
|
||||
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'
|
||||
});
|
||||
|
||||
// 返回创建的申请信息,包含关联数据
|
||||
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('创建保险申请失败'));
|
||||
}
|
||||
};
|
||||
|
||||
// 获取单个保险申请详情
|
||||
const getApplicationById = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const application = await InsuranceApplication.findByPk(id, {
|
||||
include: [
|
||||
{
|
||||
model: InsuranceType,
|
||||
as: 'insurance_type',
|
||||
attributes: ['id', 'name', 'description']
|
||||
},
|
||||
{
|
||||
model: User,
|
||||
as: 'reviewer',
|
||||
attributes: ['id', 'real_name', 'username']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (!application) {
|
||||
return res.status(404).json(responseFormat.error('保险申请不存在'));
|
||||
}
|
||||
|
||||
res.json(responseFormat.success(application, '获取保险申请详情成功'));
|
||||
} catch (error) {
|
||||
console.error('获取保险申请详情错误:', error);
|
||||
res.status(500).json(responseFormat.error('获取保险申请详情失败'));
|
||||
}
|
||||
};
|
||||
|
||||
// 更新保险申请
|
||||
const updateApplication = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const updateData = req.body;
|
||||
|
||||
const application = await InsuranceApplication.findByPk(id);
|
||||
if (!application) {
|
||||
return res.status(404).json(responseFormat.error('保险申请不存在'));
|
||||
}
|
||||
|
||||
await application.update(updateData);
|
||||
|
||||
res.json(responseFormat.success(application, '保险申请更新成功'));
|
||||
} catch (error) {
|
||||
console.error('更新保险申请错误:', error);
|
||||
res.status(500).json(responseFormat.error('更新保险申请失败'));
|
||||
}
|
||||
};
|
||||
|
||||
// 审核保险申请
|
||||
const reviewApplication = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { status, result, review_notes } = req.body;
|
||||
const reviewerId = req.user.id;
|
||||
|
||||
const application = await InsuranceApplication.findByPk(id);
|
||||
if (!application) {
|
||||
return res.status(404).json(responseFormat.error('保险申请不存在'));
|
||||
}
|
||||
|
||||
// 前端发送的是result字段,后端期望status字段
|
||||
const reviewStatus = status || result;
|
||||
|
||||
// 状态映射:将前端的pending_review映射为under_review
|
||||
let mappedStatus = reviewStatus;
|
||||
if (reviewStatus === 'pending_review') {
|
||||
mappedStatus = 'under_review';
|
||||
}
|
||||
|
||||
if (!['approved', 'rejected', 'under_review'].includes(mappedStatus)) {
|
||||
return res.status(400).json(responseFormat.error('无效的审核状态'));
|
||||
}
|
||||
|
||||
await application.update({
|
||||
status: mappedStatus,
|
||||
review_notes,
|
||||
reviewer_id: reviewerId,
|
||||
review_date: new Date()
|
||||
});
|
||||
|
||||
res.json(responseFormat.success(application, '保险申请审核成功'));
|
||||
} catch (error) {
|
||||
console.error('审核保险申请错误:', error);
|
||||
res.status(500).json(responseFormat.error('审核保险申请失败'));
|
||||
}
|
||||
};
|
||||
|
||||
// 删除保险申请
|
||||
const deleteApplication = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const application = await InsuranceApplication.findByPk(id);
|
||||
if (!application) {
|
||||
return res.status(404).json(responseFormat.error('保险申请不存在'));
|
||||
}
|
||||
|
||||
await application.destroy();
|
||||
|
||||
res.json(responseFormat.success(null, '保险申请删除成功'));
|
||||
} catch (error) {
|
||||
console.error('删除保险申请错误:', error);
|
||||
res.status(500).json(responseFormat.error('删除保险申请失败'));
|
||||
}
|
||||
};
|
||||
|
||||
// 获取保险申请统计
|
||||
const getApplicationStats = async (req, res) => {
|
||||
try {
|
||||
const stats = await InsuranceApplication.findAll({
|
||||
attributes: [
|
||||
'status',
|
||||
[InsuranceApplication.sequelize.fn('COUNT', InsuranceApplication.sequelize.col('id')), 'count']
|
||||
],
|
||||
group: ['status']
|
||||
});
|
||||
|
||||
const total = await InsuranceApplication.count();
|
||||
|
||||
res.json(responseFormat.success({
|
||||
stats,
|
||||
total
|
||||
}, '获取保险申请统计成功'));
|
||||
} catch (error) {
|
||||
console.error('获取保险申请统计错误:', error);
|
||||
res.status(500).json(responseFormat.error('获取保险申请统计失败'));
|
||||
}
|
||||
};
|
||||
|
||||
// 获取参保类型选项
|
||||
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,
|
||||
getApplicationById,
|
||||
updateApplication,
|
||||
reviewApplication,
|
||||
deleteApplication,
|
||||
getApplicationStats,
|
||||
getInsuranceCategories,
|
||||
exportApplications
|
||||
};
|
||||
221
insurance_backend/controllers/livestockTypeController.js
Normal 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
|
||||
};
|
||||
178
insurance_backend/controllers/menuController.js
Normal file
@@ -0,0 +1,178 @@
|
||||
const { User, Role, Menu } = require('../models');
|
||||
|
||||
/**
|
||||
* 获取菜单列表
|
||||
* @param {Object} req - Express请求对象
|
||||
* @param {Object} res - Express响应对象
|
||||
*/
|
||||
exports.getMenus = async (req, res) => {
|
||||
try {
|
||||
console.log('开始获取菜单,用户信息:', req.user);
|
||||
|
||||
// 获取用户ID(从JWT中解析或通过其他方式获取)
|
||||
const userId = req.user?.id; // 假设通过认证中间件解析后存在
|
||||
|
||||
console.log('用户ID:', userId);
|
||||
|
||||
// 如果没有用户ID,返回基础菜单
|
||||
if (!userId) {
|
||||
console.log('没有用户ID,返回基础菜单');
|
||||
const menus = await Menu.findAll({
|
||||
where: {
|
||||
parent_id: null,
|
||||
show: true,
|
||||
status: 'active'
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: Menu,
|
||||
as: 'children',
|
||||
where: {
|
||||
show: true,
|
||||
status: 'active'
|
||||
},
|
||||
required: false,
|
||||
order: [['order', 'ASC']]
|
||||
}
|
||||
],
|
||||
order: [['order', 'ASC']]
|
||||
});
|
||||
|
||||
console.log('基础菜单查询结果:', menus.length);
|
||||
|
||||
return res.json({
|
||||
code: 200,
|
||||
status: 'success',
|
||||
data: menus,
|
||||
message: '获取菜单成功'
|
||||
});
|
||||
}
|
||||
|
||||
console.log('查询用户信息...');
|
||||
// 获取用户信息和角色
|
||||
const user = await User.findByPk(userId, {
|
||||
include: [
|
||||
{
|
||||
model: Role,
|
||||
as: 'role',
|
||||
attributes: ['id', 'name', 'permissions']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
console.log('用户查询结果:', user ? '找到用户' : '用户不存在');
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
status: 'error',
|
||||
message: '用户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 获取角色的权限列表
|
||||
const userPermissions = user.role?.permissions || [];
|
||||
console.log('用户权限:', userPermissions);
|
||||
|
||||
console.log('查询菜单数据...');
|
||||
// 查询菜单,这里简化处理,实际应用中可能需要根据权限过滤
|
||||
const menus = await Menu.findAll({
|
||||
where: {
|
||||
parent_id: null,
|
||||
show: true,
|
||||
status: 'active'
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: Menu,
|
||||
as: 'children',
|
||||
where: {
|
||||
show: true,
|
||||
status: 'active'
|
||||
},
|
||||
required: false,
|
||||
order: [['order', 'ASC']]
|
||||
}
|
||||
],
|
||||
order: [['order', 'ASC']]
|
||||
});
|
||||
|
||||
console.log('菜单查询结果:', menus.length);
|
||||
|
||||
// 这里可以添加根据权限过滤菜单的逻辑
|
||||
// 简化示例,假设所有用户都能看到所有激活的菜单
|
||||
|
||||
return res.json({
|
||||
code: 200,
|
||||
status: 'success',
|
||||
data: menus,
|
||||
message: '获取菜单成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取菜单失败:', error);
|
||||
console.error('错误堆栈:', error.stack);
|
||||
return res.status(500).json({
|
||||
code: 500,
|
||||
status: 'error',
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取所有菜单(包括非激活状态,仅管理员可用)
|
||||
* @param {Object} req - Express请求对象
|
||||
* @param {Object} res - Express响应对象
|
||||
*/
|
||||
exports.getAllMenus = async (req, res) => {
|
||||
try {
|
||||
// 检查用户是否为管理员(简化示例)
|
||||
const user = await User.findByPk(req.user?.id, {
|
||||
include: [
|
||||
{
|
||||
model: Role,
|
||||
attributes: ['id', 'name', 'permissions']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (!user || !user.Role || !user.Role.permissions.includes('*:*')) {
|
||||
return res.status(403).json({
|
||||
code: 403,
|
||||
status: 'error',
|
||||
message: '没有权限查看所有菜单'
|
||||
});
|
||||
}
|
||||
|
||||
// 查询所有菜单
|
||||
const menus = await Menu.findAll({
|
||||
where: {
|
||||
parent_id: null
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: Menu,
|
||||
as: 'children',
|
||||
required: false,
|
||||
order: [['order', 'ASC']]
|
||||
}
|
||||
],
|
||||
order: [['order', 'ASC']]
|
||||
});
|
||||
|
||||
return res.json({
|
||||
code: 200,
|
||||
status: 'success',
|
||||
data: menus,
|
||||
message: '获取所有菜单成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取所有菜单失败:', error);
|
||||
return res.status(500).json({
|
||||
code: 500,
|
||||
status: 'error',
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
344
insurance_backend/controllers/operationLogController.js
Normal file
@@ -0,0 +1,344 @@
|
||||
const { OperationLog, User } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
const ExcelJS = require('exceljs');
|
||||
|
||||
/**
|
||||
* 操作日志控制器
|
||||
*/
|
||||
class OperationLogController {
|
||||
/**
|
||||
* 获取操作日志列表
|
||||
*/
|
||||
async getOperationLogs(req, res) {
|
||||
try {
|
||||
const {
|
||||
page = 1,
|
||||
limit = 20,
|
||||
user_id,
|
||||
operation_type,
|
||||
operation_module,
|
||||
status,
|
||||
start_date,
|
||||
end_date,
|
||||
keyword
|
||||
} = req.query;
|
||||
|
||||
// 构建查询条件
|
||||
const whereConditions = {};
|
||||
|
||||
if (user_id) {
|
||||
whereConditions.user_id = user_id;
|
||||
}
|
||||
|
||||
if (operation_type) {
|
||||
whereConditions.operation_type = operation_type;
|
||||
}
|
||||
|
||||
if (operation_module) {
|
||||
whereConditions.operation_module = operation_module;
|
||||
}
|
||||
|
||||
if (status) {
|
||||
whereConditions.status = status;
|
||||
}
|
||||
|
||||
// 时间范围查询
|
||||
if (start_date || end_date) {
|
||||
whereConditions.created_at = {};
|
||||
if (start_date) {
|
||||
whereConditions.created_at[Op.gte] = new Date(start_date);
|
||||
}
|
||||
if (end_date) {
|
||||
const endDateTime = new Date(end_date);
|
||||
endDateTime.setHours(23, 59, 59, 999);
|
||||
whereConditions.created_at[Op.lte] = endDateTime;
|
||||
}
|
||||
}
|
||||
|
||||
// 关键词搜索
|
||||
if (keyword) {
|
||||
whereConditions[Op.or] = [
|
||||
{ operation_content: { [Op.like]: `%${keyword}%` } },
|
||||
{ operation_target: { [Op.like]: `%${keyword}%` } },
|
||||
{ request_url: { [Op.like]: `%${keyword}%` } },
|
||||
{ ip_address: { [Op.like]: `%${keyword}%` } }
|
||||
];
|
||||
}
|
||||
|
||||
// 分页参数
|
||||
const offset = (parseInt(page) - 1) * parseInt(limit);
|
||||
|
||||
// 查询操作日志
|
||||
const { count, rows } = await OperationLog.findAndCountAll({
|
||||
where: whereConditions,
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: 'user',
|
||||
attributes: ['id', 'username', 'real_name', 'email']
|
||||
}
|
||||
],
|
||||
order: [['created_at', 'DESC']],
|
||||
limit: parseInt(limit),
|
||||
offset: offset
|
||||
});
|
||||
|
||||
const totalPages = Math.ceil(count / parseInt(limit));
|
||||
|
||||
res.json({
|
||||
status: 'success',
|
||||
message: '获取操作日志列表成功',
|
||||
data: {
|
||||
logs: rows,
|
||||
total: count,
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
totalPages: totalPages
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取操作日志列表失败:', error);
|
||||
res.status(500).json({
|
||||
status: 'error',
|
||||
message: '获取操作日志列表失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取操作日志统计信息
|
||||
*/
|
||||
async getOperationStats(req, res) {
|
||||
try {
|
||||
const {
|
||||
user_id,
|
||||
start_date,
|
||||
end_date
|
||||
} = req.query;
|
||||
|
||||
// 构建查询条件
|
||||
const whereConditions = {};
|
||||
|
||||
if (user_id) {
|
||||
whereConditions.user_id = user_id;
|
||||
}
|
||||
|
||||
// 时间范围查询
|
||||
if (start_date || end_date) {
|
||||
whereConditions.created_at = {};
|
||||
if (start_date) {
|
||||
whereConditions.created_at[Op.gte] = new Date(start_date);
|
||||
}
|
||||
if (end_date) {
|
||||
const endDateTime = new Date(end_date);
|
||||
endDateTime.setHours(23, 59, 59, 999);
|
||||
whereConditions.created_at[Op.lte] = endDateTime;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取统计数据
|
||||
const stats = await OperationLog.getOperationStats(whereConditions);
|
||||
|
||||
res.json({
|
||||
status: 'success',
|
||||
message: '获取操作日志统计成功',
|
||||
data: stats
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取操作日志统计失败:', error);
|
||||
res.status(500).json({
|
||||
status: 'error',
|
||||
message: '获取操作日志统计失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取操作日志详情
|
||||
*/
|
||||
async getOperationLogById(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const log = await OperationLog.findByPk(id, {
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: 'user',
|
||||
attributes: ['id', 'username', 'real_name', 'email']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (!log) {
|
||||
return res.status(404).json({
|
||||
status: 'error',
|
||||
message: '操作日志不存在'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
status: 'success',
|
||||
message: '获取操作日志详情成功',
|
||||
data: log
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取操作日志详情失败:', error);
|
||||
res.status(500).json({
|
||||
status: 'error',
|
||||
message: '获取操作日志详情失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出操作日志
|
||||
*/
|
||||
async exportOperationLogs(req, res) {
|
||||
try {
|
||||
const {
|
||||
user_id,
|
||||
operation_type,
|
||||
operation_module,
|
||||
status,
|
||||
start_date,
|
||||
end_date,
|
||||
keyword
|
||||
} = req.body;
|
||||
|
||||
// 构建查询条件
|
||||
const whereConditions = {};
|
||||
|
||||
if (user_id) {
|
||||
whereConditions.user_id = user_id;
|
||||
}
|
||||
|
||||
if (operation_type) {
|
||||
whereConditions.operation_type = operation_type;
|
||||
}
|
||||
|
||||
if (operation_module) {
|
||||
whereConditions.operation_module = operation_module;
|
||||
}
|
||||
|
||||
if (status) {
|
||||
whereConditions.status = status;
|
||||
}
|
||||
|
||||
// 时间范围查询
|
||||
if (start_date || end_date) {
|
||||
whereConditions.created_at = {};
|
||||
if (start_date) {
|
||||
whereConditions.created_at[Op.gte] = new Date(start_date);
|
||||
}
|
||||
if (end_date) {
|
||||
const endDateTime = new Date(end_date);
|
||||
endDateTime.setHours(23, 59, 59, 999);
|
||||
whereConditions.created_at[Op.lte] = endDateTime;
|
||||
}
|
||||
}
|
||||
|
||||
// 关键词搜索
|
||||
if (keyword) {
|
||||
whereConditions[Op.or] = [
|
||||
{ operation_content: { [Op.like]: `%${keyword}%` } },
|
||||
{ operation_target: { [Op.like]: `%${keyword}%` } },
|
||||
{ request_url: { [Op.like]: `%${keyword}%` } },
|
||||
{ ip_address: { [Op.like]: `%${keyword}%` } }
|
||||
];
|
||||
}
|
||||
|
||||
// 查询所有符合条件的日志
|
||||
const logs = await OperationLog.findAll({
|
||||
where: whereConditions,
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: 'user',
|
||||
attributes: ['id', 'username', 'real_name', 'email']
|
||||
}
|
||||
],
|
||||
order: [['created_at', 'DESC']]
|
||||
});
|
||||
|
||||
// 创建Excel工作簿
|
||||
const workbook = new ExcelJS.Workbook();
|
||||
const worksheet = workbook.addWorksheet('操作日志');
|
||||
|
||||
// 设置表头
|
||||
worksheet.columns = [
|
||||
{ header: 'ID', key: 'id', width: 10 },
|
||||
{ header: '操作用户', key: 'username', width: 15 },
|
||||
{ header: '真实姓名', key: 'real_name', width: 15 },
|
||||
{ header: '操作类型', key: 'operation_type', width: 15 },
|
||||
{ header: '操作模块', key: 'operation_module', width: 15 },
|
||||
{ header: '操作内容', key: 'operation_content', width: 30 },
|
||||
{ header: '操作目标', key: 'operation_target', width: 20 },
|
||||
{ header: '请求方法', key: 'request_method', width: 10 },
|
||||
{ header: '请求URL', key: 'request_url', width: 30 },
|
||||
{ header: 'IP地址', key: 'ip_address', width: 15 },
|
||||
{ header: '执行时间(ms)', key: 'execution_time', width: 12 },
|
||||
{ header: '状态', key: 'status', width: 10 },
|
||||
{ header: '错误信息', key: 'error_message', width: 30 },
|
||||
{ header: '创建时间', key: 'created_at', width: 20 }
|
||||
];
|
||||
|
||||
// 添加数据
|
||||
logs.forEach(log => {
|
||||
worksheet.addRow({
|
||||
id: log.id,
|
||||
username: log.user ? log.user.username : '',
|
||||
real_name: log.user ? log.user.real_name : '',
|
||||
operation_type: log.operation_type,
|
||||
operation_module: log.operation_module,
|
||||
operation_content: log.operation_content,
|
||||
operation_target: log.operation_target,
|
||||
request_method: log.request_method,
|
||||
request_url: log.request_url,
|
||||
ip_address: log.ip_address,
|
||||
execution_time: log.execution_time,
|
||||
status: log.status,
|
||||
error_message: log.error_message,
|
||||
created_at: log.created_at
|
||||
});
|
||||
});
|
||||
|
||||
// 设置响应头
|
||||
const filename = `操作日志_${new Date().toISOString().slice(0, 10)}.xlsx`;
|
||||
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
||||
res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(filename)}"`);
|
||||
|
||||
// 写入响应
|
||||
await workbook.xlsx.write(res);
|
||||
res.end();
|
||||
|
||||
// 记录导出操作日志
|
||||
await OperationLog.logOperation({
|
||||
user_id: req.user.id,
|
||||
operation_type: 'export',
|
||||
operation_module: 'operation_logs',
|
||||
operation_content: '导出操作日志',
|
||||
operation_target: `导出${logs.length}条记录`,
|
||||
request_method: 'POST',
|
||||
request_url: '/api/operation-logs/export',
|
||||
request_params: req.body,
|
||||
ip_address: req.ip,
|
||||
user_agent: req.get('User-Agent'),
|
||||
status: 'success'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('导出操作日志失败:', error);
|
||||
res.status(500).json({
|
||||
status: 'error',
|
||||
message: '导出操作日志失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new OperationLogController();
|
||||
415
insurance_backend/controllers/permissionController.js
Normal file
@@ -0,0 +1,415 @@
|
||||
const { Permission, Role, Menu, RolePermission, MenuPermission } = require('../models');
|
||||
const responseFormat = require('../utils/response');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
/**
|
||||
* 构建权限树形结构
|
||||
*/
|
||||
function buildPermissionTree(permissions, parentId = null) {
|
||||
const tree = [];
|
||||
|
||||
for (const permission of permissions) {
|
||||
if (permission.parent_id === parentId) {
|
||||
const children = buildPermissionTree(permissions, permission.id);
|
||||
const node = {
|
||||
...permission.toJSON(),
|
||||
children: children.length > 0 ? children : undefined
|
||||
};
|
||||
tree.push(node);
|
||||
}
|
||||
}
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限管理控制器
|
||||
*/
|
||||
class PermissionController {
|
||||
/**
|
||||
* 获取权限列表
|
||||
*/
|
||||
async getPermissions(req, res) {
|
||||
try {
|
||||
const {
|
||||
page = 1,
|
||||
limit = 10,
|
||||
module,
|
||||
type,
|
||||
status = 'active',
|
||||
keyword
|
||||
} = req.query;
|
||||
|
||||
const offset = (page - 1) * limit;
|
||||
const where = { status };
|
||||
|
||||
// 模块筛选
|
||||
if (module) {
|
||||
where.module = module;
|
||||
}
|
||||
|
||||
// 类型筛选
|
||||
if (type) {
|
||||
where.type = type;
|
||||
}
|
||||
|
||||
// 关键词搜索
|
||||
if (keyword) {
|
||||
where[Op.or] = [
|
||||
{ name: { [Op.like]: `%${keyword}%` } },
|
||||
{ code: { [Op.like]: `%${keyword}%` } },
|
||||
{ description: { [Op.like]: `%${keyword}%` } }
|
||||
];
|
||||
}
|
||||
|
||||
const { count, rows } = await Permission.findAndCountAll({
|
||||
where,
|
||||
include: [
|
||||
{
|
||||
model: Permission,
|
||||
as: 'parent',
|
||||
attributes: ['id', 'name', 'code']
|
||||
},
|
||||
{
|
||||
model: Permission,
|
||||
as: 'children',
|
||||
attributes: ['id', 'name', 'code', 'type']
|
||||
}
|
||||
],
|
||||
order: [['sort_order', 'ASC'], ['id', 'ASC']],
|
||||
limit: parseInt(limit),
|
||||
offset: parseInt(offset)
|
||||
});
|
||||
|
||||
res.json(responseFormat.success({
|
||||
permissions: rows,
|
||||
pagination: {
|
||||
total: count,
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
pages: Math.ceil(count / limit)
|
||||
}
|
||||
}, '获取权限列表成功'));
|
||||
} catch (error) {
|
||||
console.error('获取权限列表失败:', error);
|
||||
res.status(500).json(responseFormat.error('获取权限列表失败'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取权限树形结构
|
||||
*/
|
||||
async getPermissionTree(req, res) {
|
||||
try {
|
||||
const { module, type } = req.query;
|
||||
const where = { status: 'active' };
|
||||
|
||||
if (module) {
|
||||
where.module = module;
|
||||
}
|
||||
|
||||
if (type) {
|
||||
where.type = type;
|
||||
}
|
||||
|
||||
const permissions = await Permission.findAll({
|
||||
where,
|
||||
order: [['sort_order', 'ASC'], ['id', 'ASC']]
|
||||
});
|
||||
|
||||
// 构建树形结构
|
||||
const tree = buildPermissionTree(permissions);
|
||||
|
||||
res.json(responseFormat.success(tree, '获取权限树成功'));
|
||||
} catch (error) {
|
||||
console.error('获取权限树失败:', error);
|
||||
res.status(500).json(responseFormat.error('获取权限树失败'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单个权限详情
|
||||
*/
|
||||
async getPermissionById(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const permission = await Permission.findByPk(id, {
|
||||
include: [
|
||||
{
|
||||
model: Permission,
|
||||
as: 'parent',
|
||||
attributes: ['id', 'name', 'code']
|
||||
},
|
||||
{
|
||||
model: Permission,
|
||||
as: 'children',
|
||||
attributes: ['id', 'name', 'code', 'type']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (!permission) {
|
||||
return res.status(404).json(responseFormat.error('权限不存在'));
|
||||
}
|
||||
|
||||
res.json(responseFormat.success(permission, '获取权限详情成功'));
|
||||
} catch (error) {
|
||||
console.error('获取权限详情失败:', error);
|
||||
res.status(500).json(responseFormat.error('获取权限详情失败'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建权限
|
||||
*/
|
||||
async createPermission(req, res) {
|
||||
try {
|
||||
const {
|
||||
name,
|
||||
code,
|
||||
description,
|
||||
module,
|
||||
type = 'operation',
|
||||
parent_id,
|
||||
sort_order = 0
|
||||
} = req.body;
|
||||
|
||||
// 验证必填字段
|
||||
if (!name || !code || !module) {
|
||||
return res.status(400).json(responseFormat.error('权限名称、权限代码和所属模块为必填项'));
|
||||
}
|
||||
|
||||
// 检查权限代码是否已存在
|
||||
const existingPermission = await Permission.findOne({ where: { code } });
|
||||
if (existingPermission) {
|
||||
return res.status(400).json(responseFormat.error('权限代码已存在'));
|
||||
}
|
||||
|
||||
// 如果有父权限,验证父权限是否存在
|
||||
if (parent_id) {
|
||||
const parentPermission = await Permission.findByPk(parent_id);
|
||||
if (!parentPermission) {
|
||||
return res.status(400).json(responseFormat.error('父权限不存在'));
|
||||
}
|
||||
}
|
||||
|
||||
const permission = await Permission.create({
|
||||
name,
|
||||
code,
|
||||
description,
|
||||
module,
|
||||
type,
|
||||
parent_id,
|
||||
sort_order,
|
||||
status: 'active'
|
||||
});
|
||||
|
||||
res.status(201).json(responseFormat.created(permission, '创建权限成功'));
|
||||
} catch (error) {
|
||||
console.error('创建权限失败:', error);
|
||||
res.status(500).json(responseFormat.error('创建权限失败'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新权限
|
||||
*/
|
||||
async updatePermission(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const {
|
||||
name,
|
||||
code,
|
||||
description,
|
||||
module,
|
||||
type,
|
||||
parent_id,
|
||||
sort_order,
|
||||
status
|
||||
} = req.body;
|
||||
|
||||
const permission = await Permission.findByPk(id);
|
||||
if (!permission) {
|
||||
return res.status(404).json(responseFormat.error('权限不存在'));
|
||||
}
|
||||
|
||||
// 如果修改了权限代码,检查是否与其他权限冲突
|
||||
if (code && code !== permission.code) {
|
||||
const existingPermission = await Permission.findOne({
|
||||
where: {
|
||||
code,
|
||||
id: { [Op.ne]: id }
|
||||
}
|
||||
});
|
||||
if (existingPermission) {
|
||||
return res.status(400).json(responseFormat.error('权限代码已存在'));
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有父权限,验证父权限是否存在且不是自己
|
||||
if (parent_id) {
|
||||
if (parent_id == id) {
|
||||
return res.status(400).json(responseFormat.error('不能将自己设为父权限'));
|
||||
}
|
||||
const parentPermission = await Permission.findByPk(parent_id);
|
||||
if (!parentPermission) {
|
||||
return res.status(400).json(responseFormat.error('父权限不存在'));
|
||||
}
|
||||
}
|
||||
|
||||
await permission.update({
|
||||
name: name || permission.name,
|
||||
code: code || permission.code,
|
||||
description: description !== undefined ? description : permission.description,
|
||||
module: module || permission.module,
|
||||
type: type || permission.type,
|
||||
parent_id: parent_id !== undefined ? parent_id : permission.parent_id,
|
||||
sort_order: sort_order !== undefined ? sort_order : permission.sort_order,
|
||||
status: status || permission.status
|
||||
});
|
||||
|
||||
res.json(responseFormat.success(permission, '更新权限成功'));
|
||||
} catch (error) {
|
||||
console.error('更新权限失败:', error);
|
||||
res.status(500).json(responseFormat.error('更新权限失败'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除权限
|
||||
*/
|
||||
async deletePermission(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const permission = await Permission.findByPk(id);
|
||||
if (!permission) {
|
||||
return res.status(404).json(responseFormat.error('权限不存在'));
|
||||
}
|
||||
|
||||
// 检查是否有子权限
|
||||
const childrenCount = await Permission.count({ where: { parent_id: id } });
|
||||
if (childrenCount > 0) {
|
||||
return res.status(400).json(responseFormat.error('该权限下还有子权限,无法删除'));
|
||||
}
|
||||
|
||||
// 检查是否有角色在使用该权限
|
||||
const rolePermissionCount = await RolePermission.count({ where: { permission_id: id } });
|
||||
if (rolePermissionCount > 0) {
|
||||
return res.status(400).json(responseFormat.error('该权限正在被角色使用,无法删除'));
|
||||
}
|
||||
|
||||
// 检查是否有菜单在使用该权限
|
||||
const menuPermissionCount = await MenuPermission.count({ where: { permission_id: id } });
|
||||
if (menuPermissionCount > 0) {
|
||||
return res.status(400).json(responseFormat.error('该权限正在被菜单使用,无法删除'));
|
||||
}
|
||||
|
||||
await permission.destroy();
|
||||
|
||||
res.json(responseFormat.success(null, '删除权限成功'));
|
||||
} catch (error) {
|
||||
console.error('删除权限失败:', error);
|
||||
res.status(500).json(responseFormat.error('删除权限失败'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取角色权限
|
||||
*/
|
||||
async getRolePermissions(req, res) {
|
||||
try {
|
||||
const { roleId } = req.params;
|
||||
|
||||
const role = await Role.findByPk(roleId, {
|
||||
include: [
|
||||
{
|
||||
model: Permission,
|
||||
as: 'rolePermissions',
|
||||
through: {
|
||||
attributes: ['granted']
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (!role) {
|
||||
return res.status(404).json(responseFormat.error('角色不存在'));
|
||||
}
|
||||
|
||||
res.json(responseFormat.success({
|
||||
role: {
|
||||
id: role.id,
|
||||
name: role.name,
|
||||
description: role.description
|
||||
},
|
||||
permissions: role.rolePermissions
|
||||
}, '获取角色权限成功'));
|
||||
} catch (error) {
|
||||
console.error('获取角色权限失败:', error);
|
||||
res.status(500).json(responseFormat.error('获取角色权限失败'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分配角色权限
|
||||
*/
|
||||
async assignRolePermissions(req, res) {
|
||||
try {
|
||||
const { roleId } = req.params;
|
||||
const { permissionIds } = req.body;
|
||||
|
||||
if (!Array.isArray(permissionIds)) {
|
||||
return res.status(400).json(responseFormat.error('权限ID列表格式错误'));
|
||||
}
|
||||
|
||||
const role = await Role.findByPk(roleId);
|
||||
if (!role) {
|
||||
return res.status(404).json(responseFormat.error('角色不存在'));
|
||||
}
|
||||
|
||||
// 删除现有的角色权限关联
|
||||
await RolePermission.destroy({ where: { role_id: roleId } });
|
||||
|
||||
// 创建新的角色权限关联
|
||||
if (permissionIds.length > 0) {
|
||||
const rolePermissions = permissionIds.map(permissionId => ({
|
||||
role_id: roleId,
|
||||
permission_id: permissionId,
|
||||
granted: true
|
||||
}));
|
||||
|
||||
await RolePermission.bulkCreate(rolePermissions);
|
||||
}
|
||||
|
||||
res.json(responseFormat.success(null, '分配角色权限成功'));
|
||||
} catch (error) {
|
||||
console.error('分配角色权限失败:', error);
|
||||
res.status(500).json(responseFormat.error('分配角色权限失败'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模块列表
|
||||
*/
|
||||
async getModules(req, res) {
|
||||
try {
|
||||
const modules = await Permission.findAll({
|
||||
attributes: ['module'],
|
||||
group: ['module'],
|
||||
order: [['module', 'ASC']]
|
||||
});
|
||||
|
||||
const moduleList = modules.map(item => item.module);
|
||||
|
||||
res.json(responseFormat.success(moduleList, '获取模块列表成功'));
|
||||
} catch (error) {
|
||||
console.error('获取模块列表失败:', error);
|
||||
res.status(500).json(responseFormat.error('获取模块列表失败'));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = new PermissionController();
|
||||
374
insurance_backend/controllers/policyController.js
Normal file
@@ -0,0 +1,374 @@
|
||||
const { Policy, InsuranceApplication, InsuranceType, User } = require('../models');
|
||||
const responseFormat = require('../utils/response');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// 获取保单列表
|
||||
const getPolicies = async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
policy_number, // 前端发送的参数名
|
||||
policyholder_name, // 前端发送的参数名
|
||||
insurance_type_id, // 前端发送的参数名
|
||||
status, // 前端发送的参数名
|
||||
page = 1,
|
||||
pageSize = 10 // 前端发送的参数名
|
||||
} = req.query;
|
||||
|
||||
const whereClause = {};
|
||||
|
||||
// 保单编号筛选
|
||||
if (policy_number) {
|
||||
whereClause.policy_no = { [Op.like]: `%${policy_number}%` };
|
||||
}
|
||||
|
||||
// 保单状态筛选
|
||||
if (status) {
|
||||
whereClause.policy_status = status;
|
||||
}
|
||||
|
||||
// 保险类型筛选
|
||||
if (insurance_type_id) {
|
||||
whereClause.insurance_type_id = insurance_type_id;
|
||||
}
|
||||
|
||||
const offset = (page - 1) * pageSize;
|
||||
|
||||
const { count, rows } = await Policy.findAndCountAll({
|
||||
where: whereClause,
|
||||
include: [
|
||||
{
|
||||
model: InsuranceApplication,
|
||||
as: 'application',
|
||||
attributes: ['id', 'customer_name', 'customer_phone'],
|
||||
include: [
|
||||
{
|
||||
model: InsuranceType,
|
||||
as: 'insurance_type',
|
||||
attributes: ['id', 'name']
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
model: User,
|
||||
as: 'customer',
|
||||
attributes: ['id', 'real_name', 'username']
|
||||
},
|
||||
{
|
||||
model: InsuranceType,
|
||||
as: 'insurance_type',
|
||||
attributes: ['id', 'name']
|
||||
}
|
||||
],
|
||||
order: [['created_at', 'DESC']],
|
||||
offset,
|
||||
limit: parseInt(pageSize)
|
||||
});
|
||||
|
||||
// 处理返回数据,确保前端能够正确解析
|
||||
const processedRows = rows.map(row => {
|
||||
const policy = row.toJSON();
|
||||
return {
|
||||
id: policy.id,
|
||||
policy_number: policy.policy_no, // 映射字段名
|
||||
// 优先使用保单表中的姓名字段,如果为空则使用关联表中的数据
|
||||
policyholder_name: policy.policyholder_name || policy.application?.customer_name || policy.customer?.real_name || '',
|
||||
insured_name: policy.insured_name || policy.application?.customer_name || policy.customer?.real_name || '',
|
||||
insurance_type_id: policy.insurance_type_id,
|
||||
insurance_type_name: policy.insurance_type?.name || '',
|
||||
premium_amount: parseFloat(policy.premium_amount) || 0,
|
||||
coverage_amount: parseFloat(policy.coverage_amount) || 0,
|
||||
start_date: policy.start_date,
|
||||
end_date: policy.end_date,
|
||||
status: policy.policy_status, // 映射字段名
|
||||
// 优先使用保单表中的联系信息字段
|
||||
phone: policy.phone || policy.application?.customer_phone || '',
|
||||
email: policy.email || policy.customer?.email || '',
|
||||
address: policy.address || policy.application?.address || '',
|
||||
remarks: policy.terms_and_conditions || '',
|
||||
created_at: policy.created_at,
|
||||
updated_at: policy.updated_at
|
||||
};
|
||||
});
|
||||
|
||||
res.json(responseFormat.pagination(processedRows, {
|
||||
page: parseInt(page),
|
||||
pageSize: parseInt(pageSize),
|
||||
total: count
|
||||
}, '获取保单列表成功'));
|
||||
} catch (error) {
|
||||
console.error('获取保单列表错误:', error);
|
||||
res.status(500).json(responseFormat.error('获取保单列表失败'));
|
||||
}
|
||||
};
|
||||
|
||||
// 创建保单
|
||||
const createPolicy = async (req, res) => {
|
||||
try {
|
||||
const policyData = req.body;
|
||||
|
||||
// 生成保单编号(优先使用前端传递的policy_number,否则自动生成)
|
||||
const policyNo = policyData.policy_number || `POL${Date.now()}${Math.random().toString(36).substr(2, 6).toUpperCase()}`;
|
||||
|
||||
// 构建保单数据,优先使用前端传递的数据
|
||||
const createData = {
|
||||
// 基本信息
|
||||
policy_no: policyNo,
|
||||
insurance_type_id: policyData.insurance_type_id || 1,
|
||||
|
||||
// 客户信息 - 只有在前端没有传递时才使用默认值
|
||||
application_id: policyData.application_id || 4, // 默认关联存在的申请(id=4)
|
||||
customer_id: policyData.customer_id || 1, // 默认关联第一个客户(admin用户)
|
||||
|
||||
// 保单持有人信息(直接保存到Policy表中)
|
||||
policyholder_name: policyData.policyholder_name || '',
|
||||
insured_name: policyData.insured_name || '',
|
||||
|
||||
// 金额信息
|
||||
premium_amount: policyData.premium_amount,
|
||||
coverage_amount: policyData.coverage_amount,
|
||||
|
||||
// 日期信息
|
||||
start_date: policyData.start_date,
|
||||
end_date: policyData.end_date,
|
||||
|
||||
// 状态信息
|
||||
policy_status: policyData.status || 'active',
|
||||
payment_status: 'unpaid',
|
||||
|
||||
// 联系信息(直接保存到Policy表中)
|
||||
phone: policyData.phone || '',
|
||||
email: policyData.email || '',
|
||||
address: policyData.address || '',
|
||||
|
||||
// 备注
|
||||
terms_and_conditions: policyData.remarks,
|
||||
|
||||
// 系统字段
|
||||
created_by: req.user?.id || 1,
|
||||
updated_by: req.user?.id || 1
|
||||
};
|
||||
|
||||
const policy = await Policy.create(createData);
|
||||
|
||||
res.status(201).json(responseFormat.created(policy, '保单创建成功'));
|
||||
} catch (error) {
|
||||
console.error('创建保单错误:', error);
|
||||
res.status(500).json(responseFormat.error('创建保单失败'));
|
||||
}
|
||||
};
|
||||
|
||||
// 获取单个保单详情
|
||||
const getPolicyById = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const policy = await Policy.findByPk(id, {
|
||||
include: [
|
||||
{
|
||||
model: InsuranceApplication,
|
||||
as: 'application',
|
||||
include: [
|
||||
{
|
||||
model: InsuranceType,
|
||||
as: 'insurance_type',
|
||||
attributes: ['id', 'name', 'description', 'premium_rate']
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
model: User,
|
||||
as: 'customer',
|
||||
attributes: ['id', 'real_name', 'username', 'phone', 'email']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
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 updatePolicy = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const updateData = req.body;
|
||||
|
||||
console.log('更新保单请求 - ID:', id);
|
||||
console.log('更新保单数据:', JSON.stringify(updateData, null, 2));
|
||||
|
||||
const policy = await Policy.findByPk(id);
|
||||
if (!policy) {
|
||||
return res.status(404).json(responseFormat.error('保单不存在'));
|
||||
}
|
||||
|
||||
// 过滤掉null值的必填关联字段,避免覆盖现有数据
|
||||
const filteredUpdateData = { ...updateData };
|
||||
|
||||
// 处理日期字段格式
|
||||
if (filteredUpdateData.start_date) {
|
||||
// 确保日期格式正确
|
||||
const startDate = new Date(filteredUpdateData.start_date);
|
||||
if (isNaN(startDate.getTime())) {
|
||||
console.error('无效的开始日期格式:', filteredUpdateData.start_date);
|
||||
return res.status(400).json(responseFormat.error('开始日期格式无效'));
|
||||
}
|
||||
filteredUpdateData.start_date = startDate;
|
||||
}
|
||||
|
||||
if (filteredUpdateData.end_date) {
|
||||
// 确保日期格式正确
|
||||
const endDate = new Date(filteredUpdateData.end_date);
|
||||
if (isNaN(endDate.getTime())) {
|
||||
console.error('无效的结束日期格式:', filteredUpdateData.end_date);
|
||||
return res.status(400).json(responseFormat.error('结束日期格式无效'));
|
||||
}
|
||||
filteredUpdateData.end_date = endDate;
|
||||
}
|
||||
|
||||
// 如果这些关联字段为null或undefined,则不更新它们
|
||||
if (filteredUpdateData.customer_id === null || filteredUpdateData.customer_id === undefined) {
|
||||
delete filteredUpdateData.customer_id;
|
||||
}
|
||||
|
||||
if (filteredUpdateData.application_id === null || filteredUpdateData.application_id === undefined) {
|
||||
delete filteredUpdateData.application_id;
|
||||
}
|
||||
|
||||
if (filteredUpdateData.insurance_type_id === null || filteredUpdateData.insurance_type_id === undefined) {
|
||||
delete filteredUpdateData.insurance_type_id;
|
||||
}
|
||||
|
||||
// 验证必填字段
|
||||
if (filteredUpdateData.hasOwnProperty('policy_no') && (!filteredUpdateData.policy_no || filteredUpdateData.policy_no.trim() === '')) {
|
||||
return res.status(400).json(responseFormat.error('保单编号不能为空'));
|
||||
}
|
||||
|
||||
if (filteredUpdateData.hasOwnProperty('coverage_amount') && (filteredUpdateData.coverage_amount < 0)) {
|
||||
return res.status(400).json(responseFormat.error('保额不能小于0'));
|
||||
}
|
||||
|
||||
if (filteredUpdateData.hasOwnProperty('premium_amount') && (filteredUpdateData.premium_amount < 0)) {
|
||||
return res.status(400).json(responseFormat.error('保费金额不能小于0'));
|
||||
}
|
||||
|
||||
console.log('处理后的更新数据:', JSON.stringify(filteredUpdateData, null, 2));
|
||||
|
||||
await policy.update(filteredUpdateData);
|
||||
|
||||
console.log('保单更新成功:', policy.id);
|
||||
res.json(responseFormat.success(policy, '保单更新成功'));
|
||||
} catch (error) {
|
||||
console.error('更新保单错误详情:', {
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
name: error.name,
|
||||
sql: error.sql || 'N/A',
|
||||
parameters: error.parameters || 'N/A'
|
||||
});
|
||||
|
||||
// 根据错误类型返回更具体的错误信息
|
||||
if (error.name === 'SequelizeValidationError') {
|
||||
const validationErrors = error.errors.map(err => err.message).join(', ');
|
||||
return res.status(400).json(responseFormat.error(`数据验证失败: ${validationErrors}`));
|
||||
}
|
||||
|
||||
if (error.name === 'SequelizeUniqueConstraintError') {
|
||||
return res.status(400).json(responseFormat.error('保单编号已存在'));
|
||||
}
|
||||
|
||||
if (error.name === 'SequelizeForeignKeyConstraintError') {
|
||||
return res.status(400).json(responseFormat.error('关联数据不存在'));
|
||||
}
|
||||
|
||||
res.status(500).json(responseFormat.error('更新保单失败'));
|
||||
}
|
||||
};
|
||||
|
||||
// 更新保单状态
|
||||
const updatePolicyStatus = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { policy_status } = req.body;
|
||||
|
||||
const policy = await Policy.findByPk(id);
|
||||
if (!policy) {
|
||||
return res.status(404).json(responseFormat.error('保单不存在'));
|
||||
}
|
||||
|
||||
if (!['active', 'expired', 'cancelled', 'suspended'].includes(policy_status)) {
|
||||
return res.status(400).json(responseFormat.error('无效的保单状态'));
|
||||
}
|
||||
|
||||
await policy.update({ policy_status });
|
||||
|
||||
res.json(responseFormat.success(policy, '保单状态更新成功'));
|
||||
} catch (error) {
|
||||
console.error('更新保单状态错误:', error);
|
||||
res.status(500).json(responseFormat.error('更新保单状态失败'));
|
||||
}
|
||||
};
|
||||
|
||||
// 获取保单统计
|
||||
const getPolicyStats = async (req, res) => {
|
||||
try {
|
||||
const stats = await Policy.findAll({
|
||||
attributes: [
|
||||
'policy_status',
|
||||
[Policy.sequelize.fn('COUNT', Policy.sequelize.col('id')), 'count'],
|
||||
[Policy.sequelize.fn('SUM', Policy.sequelize.col('coverage_amount')), 'total_coverage'],
|
||||
[Policy.sequelize.fn('SUM', Policy.sequelize.col('premium_amount')), 'total_premium']
|
||||
],
|
||||
group: ['policy_status']
|
||||
});
|
||||
|
||||
const total = await Policy.count();
|
||||
const totalCoverage = await Policy.sum('coverage_amount');
|
||||
const totalPremium = await Policy.sum('premium_amount');
|
||||
|
||||
res.json(responseFormat.success({
|
||||
stats,
|
||||
total,
|
||||
total_coverage: totalCoverage || 0,
|
||||
total_premium: totalPremium || 0
|
||||
}, '获取保单统计成功'));
|
||||
} catch (error) {
|
||||
console.error('获取保单统计错误:', error);
|
||||
res.status(500).json(responseFormat.error('获取保单统计失败'));
|
||||
}
|
||||
};
|
||||
|
||||
// 删除保单
|
||||
const deletePolicy = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const policy = await Policy.findByPk(id);
|
||||
if (!policy) {
|
||||
return res.status(404).json(responseFormat.error('保单不存在'));
|
||||
}
|
||||
|
||||
await policy.destroy();
|
||||
|
||||
res.json(responseFormat.success(null, '保单删除成功'));
|
||||
} catch (error) {
|
||||
console.error('删除保单错误:', error);
|
||||
res.status(500).json(responseFormat.error('删除保单失败'));
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getPolicies,
|
||||
createPolicy,
|
||||
getPolicyById,
|
||||
updatePolicy,
|
||||
updatePolicyStatus,
|
||||
deletePolicy,
|
||||
getPolicyStats
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
459
insurance_backend/controllers/rolePermissionController.js
Normal file
@@ -0,0 +1,459 @@
|
||||
const { Role, Permission, RolePermission, User } = require('../models');
|
||||
const responseFormat = require('../utils/response');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
/**
|
||||
* 角色权限管理控制器
|
||||
* 专门处理角色权限的分配、管理和动态调用
|
||||
*/
|
||||
class RolePermissionController {
|
||||
|
||||
/**
|
||||
* 获取所有角色及其权限
|
||||
*/
|
||||
async getAllRolesWithPermissions(req, res) {
|
||||
try {
|
||||
const roles = await Role.findAll({
|
||||
order: [['id', 'ASC']]
|
||||
});
|
||||
|
||||
const rolesData = await Promise.all(roles.map(async (role) => {
|
||||
// 从RolePermission表获取权限
|
||||
const rolePermissions = await RolePermission.findAll({
|
||||
where: {
|
||||
role_id: role.id,
|
||||
granted: true
|
||||
},
|
||||
include: [{
|
||||
model: Permission,
|
||||
as: 'permission',
|
||||
attributes: ['id', 'name', 'code', 'description', 'module', 'type']
|
||||
}]
|
||||
});
|
||||
|
||||
const permissions = rolePermissions.map(rp => rp.permission.code);
|
||||
|
||||
return {
|
||||
id: role.id,
|
||||
name: role.name,
|
||||
description: role.description,
|
||||
status: role.status,
|
||||
permissions: permissions,
|
||||
permissionCount: permissions.length
|
||||
};
|
||||
}));
|
||||
|
||||
res.json(responseFormat.success({
|
||||
roles: rolesData,
|
||||
total: rolesData.length
|
||||
}, '获取角色权限列表成功'));
|
||||
} catch (error) {
|
||||
console.error('获取角色权限列表失败:', error);
|
||||
res.status(500).json(responseFormat.error('获取角色权限列表失败'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有权限
|
||||
*/
|
||||
async getAllPermissions(req, res) {
|
||||
try {
|
||||
const permissions = await Permission.findAll({
|
||||
order: [['id', 'ASC']]
|
||||
});
|
||||
|
||||
res.json(responseFormat.success(permissions, '获取权限列表成功'));
|
||||
} catch (error) {
|
||||
console.error('获取权限列表失败:', error);
|
||||
res.status(500).json(responseFormat.error('获取权限列表失败'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定角色的权限详情
|
||||
*/
|
||||
async getRolePermissionDetail(req, res) {
|
||||
try {
|
||||
const { roleId } = req.params;
|
||||
console.log('获取角色权限详情,角色ID:', roleId);
|
||||
|
||||
const role = await Role.findByPk(roleId);
|
||||
console.log('角色查询结果:', role ? role.name : '未找到');
|
||||
|
||||
if (!role) {
|
||||
return res.status(404).json(responseFormat.error('角色不存在'));
|
||||
}
|
||||
|
||||
// 获取所有权限用于对比
|
||||
const allPermissions = await Permission.findAll({
|
||||
attributes: ['id', 'name', 'code', 'description', 'module', 'type', 'parent_id'],
|
||||
order: [['module', 'ASC'], ['id', 'ASC']]
|
||||
});
|
||||
console.log('权限查询结果:', allPermissions.length, '个权限');
|
||||
|
||||
// 从RolePermission表获取角色已分配的权限
|
||||
const rolePermissions = await RolePermission.findAll({
|
||||
where: {
|
||||
role_id: roleId,
|
||||
granted: true
|
||||
},
|
||||
include: [{
|
||||
model: Permission,
|
||||
as: 'permission',
|
||||
attributes: ['id', 'name', 'code', 'description', 'module', 'type']
|
||||
}]
|
||||
});
|
||||
|
||||
const assignedPermissionCodes = rolePermissions.map(rp => rp.permission.code);
|
||||
console.log('已分配权限代码:', assignedPermissionCodes.length, '个');
|
||||
|
||||
// 暂时返回简化数据,不构建权限树
|
||||
res.json(responseFormat.success({
|
||||
role: {
|
||||
id: role.id,
|
||||
name: role.name,
|
||||
description: role.description,
|
||||
status: role.status
|
||||
},
|
||||
assignedPermissions: assignedPermissionCodes,
|
||||
allPermissions: allPermissions.map(p => ({
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
code: p.code,
|
||||
description: p.description,
|
||||
module: p.module,
|
||||
type: p.type,
|
||||
parent_id: p.parent_id,
|
||||
assigned: assignedPermissionCodes.includes(p.code)
|
||||
})),
|
||||
assignedCount: assignedPermissionCodes.length,
|
||||
totalCount: allPermissions.length
|
||||
}, '获取角色权限详情成功'));
|
||||
} catch (error) {
|
||||
console.error('获取角色权限详情失败:', error);
|
||||
console.error('错误堆栈:', error.stack);
|
||||
res.status(500).json(responseFormat.error('获取角色权限详情失败'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量分配角色权限
|
||||
*/
|
||||
async batchAssignPermissions(req, res) {
|
||||
try {
|
||||
const { roleId } = req.params;
|
||||
const { permissionIds, operation = 'replace' } = req.body;
|
||||
|
||||
console.log('=== 批量分配权限开始 ===');
|
||||
console.log('角色ID:', roleId);
|
||||
console.log('权限ID列表:', permissionIds);
|
||||
console.log('操作类型:', operation);
|
||||
console.log('请求体:', JSON.stringify(req.body, null, 2));
|
||||
|
||||
if (!Array.isArray(permissionIds)) {
|
||||
console.log('❌ 权限ID列表格式错误');
|
||||
return res.status(400).json(responseFormat.error('权限ID列表格式错误'));
|
||||
}
|
||||
|
||||
const role = await Role.findByPk(roleId);
|
||||
if (!role) {
|
||||
console.log('❌ 角色不存在');
|
||||
return res.status(404).json(responseFormat.error('角色不存在'));
|
||||
}
|
||||
|
||||
console.log('找到角色:', role.name);
|
||||
|
||||
// 验证权限ID是否存在
|
||||
const validPermissions = await Permission.findAll({
|
||||
where: { id: { [Op.in]: permissionIds } },
|
||||
attributes: ['id']
|
||||
});
|
||||
|
||||
const validPermissionIds = validPermissions.map(p => p.id);
|
||||
const invalidIds = permissionIds.filter(id => !validPermissionIds.includes(id));
|
||||
|
||||
console.log('有效权限ID:', validPermissionIds);
|
||||
console.log('无效权限ID:', invalidIds);
|
||||
|
||||
if (invalidIds.length > 0) {
|
||||
console.log('❌ 存在无效的权限ID');
|
||||
return res.status(400).json(responseFormat.error(`无效的权限ID: ${invalidIds.join(', ')}`));
|
||||
}
|
||||
|
||||
// 根据操作类型处理权限分配
|
||||
if (operation === 'replace') {
|
||||
console.log('执行替换模式权限分配');
|
||||
|
||||
// 替换模式:删除现有权限,添加新权限
|
||||
const deletedCount = await RolePermission.destroy({ where: { role_id: roleId } });
|
||||
console.log('删除现有权限数量:', deletedCount);
|
||||
|
||||
if (permissionIds.length > 0) {
|
||||
const rolePermissions = permissionIds.map(permissionId => ({
|
||||
role_id: roleId,
|
||||
permission_id: permissionId,
|
||||
granted: true
|
||||
}));
|
||||
console.log('准备创建的权限记录:', rolePermissions);
|
||||
|
||||
const createdPermissions = await RolePermission.bulkCreate(rolePermissions);
|
||||
console.log('成功创建的权限记录数量:', createdPermissions.length);
|
||||
}
|
||||
} else if (operation === 'add') {
|
||||
// 添加模式:只添加新权限
|
||||
const existingPermissions = await RolePermission.findAll({
|
||||
where: { role_id: roleId },
|
||||
attributes: ['permission_id']
|
||||
});
|
||||
const existingIds = existingPermissions.map(p => p.permission_id);
|
||||
const newPermissionIds = permissionIds.filter(id => !existingIds.includes(id));
|
||||
|
||||
if (newPermissionIds.length > 0) {
|
||||
const rolePermissions = newPermissionIds.map(permissionId => ({
|
||||
role_id: roleId,
|
||||
permission_id: permissionId,
|
||||
granted: true
|
||||
}));
|
||||
await RolePermission.bulkCreate(rolePermissions);
|
||||
}
|
||||
} else if (operation === 'remove') {
|
||||
// 移除模式:删除指定权限
|
||||
await RolePermission.destroy({
|
||||
where: {
|
||||
role_id: roleId,
|
||||
permission_id: { [Op.in]: permissionIds }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
console.log('✅ 权限分配完成');
|
||||
res.json(responseFormat.success(null, `${operation === 'replace' ? '替换' : operation === 'add' ? '添加' : '移除'}角色权限成功`));
|
||||
} catch (error) {
|
||||
console.error('❌ 批量分配权限失败:', error);
|
||||
console.error('错误堆栈:', error.stack);
|
||||
res.status(500).json(responseFormat.error('批量分配角色权限失败'));
|
||||
}
|
||||
|
||||
console.log('=== 批量分配权限结束 ===');
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制角色权限
|
||||
*/
|
||||
async copyRolePermissions(req, res) {
|
||||
try {
|
||||
const { sourceRoleId, targetRoleId } = req.body;
|
||||
|
||||
if (!sourceRoleId || !targetRoleId) {
|
||||
return res.status(400).json(responseFormat.error('源角色ID和目标角色ID不能为空'));
|
||||
}
|
||||
|
||||
if (sourceRoleId === targetRoleId) {
|
||||
return res.status(400).json(responseFormat.error('源角色和目标角色不能相同'));
|
||||
}
|
||||
|
||||
// 验证角色存在
|
||||
const [sourceRole, targetRole] = await Promise.all([
|
||||
Role.findByPk(sourceRoleId),
|
||||
Role.findByPk(targetRoleId)
|
||||
]);
|
||||
|
||||
if (!sourceRole) {
|
||||
return res.status(404).json(responseFormat.error('源角色不存在'));
|
||||
}
|
||||
if (!targetRole) {
|
||||
return res.status(404).json(responseFormat.error('目标角色不存在'));
|
||||
}
|
||||
|
||||
// 获取源角色的权限
|
||||
const sourcePermissions = await RolePermission.findAll({
|
||||
where: { role_id: sourceRoleId },
|
||||
attributes: ['permission_id']
|
||||
});
|
||||
|
||||
// 删除目标角色现有权限
|
||||
await RolePermission.destroy({ where: { role_id: targetRoleId } });
|
||||
|
||||
// 复制权限到目标角色
|
||||
if (sourcePermissions.length > 0) {
|
||||
const targetPermissions = sourcePermissions.map(p => ({
|
||||
role_id: targetRoleId,
|
||||
permission_id: p.permission_id,
|
||||
granted: true
|
||||
}));
|
||||
await RolePermission.bulkCreate(targetPermissions);
|
||||
}
|
||||
|
||||
res.json(responseFormat.success(null, `成功将 ${sourceRole.name} 的权限复制到 ${targetRole.name}`));
|
||||
} catch (error) {
|
||||
console.error('复制角色权限失败:', error);
|
||||
res.status(500).json(responseFormat.error('复制角色权限失败'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户权限
|
||||
*/
|
||||
async checkUserPermission(req, res) {
|
||||
try {
|
||||
const { userId, permissionCode } = req.params;
|
||||
|
||||
const user = await User.findByPk(userId, {
|
||||
include: [
|
||||
{
|
||||
model: Role,
|
||||
as: 'role',
|
||||
include: [
|
||||
{
|
||||
model: Permission,
|
||||
as: 'rolePermissions',
|
||||
where: { code: permissionCode },
|
||||
required: false,
|
||||
through: {
|
||||
attributes: ['granted']
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json(responseFormat.error('用户不存在'));
|
||||
}
|
||||
|
||||
const hasPermission = user.role &&
|
||||
user.role.rolePermissions &&
|
||||
user.role.rolePermissions.length > 0 &&
|
||||
user.role.rolePermissions[0].RolePermission.granted;
|
||||
|
||||
res.json(responseFormat.success({
|
||||
userId: user.id,
|
||||
username: user.username,
|
||||
roleName: user.role ? user.role.name : null,
|
||||
permissionCode,
|
||||
hasPermission,
|
||||
checkTime: new Date()
|
||||
}, '权限检查完成'));
|
||||
} catch (error) {
|
||||
console.error('检查用户权限失败:', error);
|
||||
res.status(500).json(responseFormat.error('检查用户权限失败'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取权限统计信息
|
||||
*/
|
||||
async getPermissionStats(req, res) {
|
||||
try {
|
||||
// 统计各种数据
|
||||
const [
|
||||
totalRoles,
|
||||
totalPermissions,
|
||||
moduleStats,
|
||||
roles
|
||||
] = await Promise.all([
|
||||
Role.count(),
|
||||
Permission.count(),
|
||||
Permission.findAll({
|
||||
attributes: [
|
||||
'module',
|
||||
[Permission.sequelize.fn('COUNT', Permission.sequelize.col('id')), 'count']
|
||||
],
|
||||
group: ['module'],
|
||||
order: [['module', 'ASC']]
|
||||
}),
|
||||
Role.findAll({
|
||||
attributes: ['id', 'name', 'permissions'],
|
||||
order: [['name', 'ASC']]
|
||||
})
|
||||
]);
|
||||
|
||||
// 计算总分配数和角色权限分布
|
||||
let totalAssignments = 0;
|
||||
const roleDistribution = roles.map(role => {
|
||||
let permissions = [];
|
||||
if (Array.isArray(role.permissions)) {
|
||||
permissions = role.permissions;
|
||||
} else if (typeof role.permissions === 'string') {
|
||||
try {
|
||||
permissions = JSON.parse(role.permissions);
|
||||
} catch (e) {
|
||||
permissions = [];
|
||||
}
|
||||
}
|
||||
|
||||
const permissionCount = permissions.length;
|
||||
totalAssignments += permissionCount;
|
||||
return {
|
||||
roleId: role.id,
|
||||
roleName: role.name,
|
||||
permissionCount
|
||||
};
|
||||
});
|
||||
|
||||
res.json(responseFormat.success({
|
||||
overview: {
|
||||
totalRoles,
|
||||
totalPermissions,
|
||||
totalAssignments,
|
||||
averagePermissionsPerRole: totalRoles > 0 ? Math.round(totalAssignments / totalRoles) : 0
|
||||
},
|
||||
moduleDistribution: moduleStats.map(stat => ({
|
||||
module: stat.module,
|
||||
count: parseInt(stat.dataValues.count)
|
||||
})),
|
||||
roleDistribution
|
||||
}, '获取权限统计成功'));
|
||||
} catch (error) {
|
||||
console.error('获取权限统计失败:', error);
|
||||
res.status(500).json(responseFormat.error('获取权限统计失败'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建权限树
|
||||
*/
|
||||
buildPermissionTree(permissions, parentId = null) {
|
||||
const tree = [];
|
||||
for (const permission of permissions) {
|
||||
if (permission.parent_id === parentId) {
|
||||
const children = this.buildPermissionTree(permissions, permission.id);
|
||||
const node = {
|
||||
...(permission.toJSON ? permission.toJSON() : permission),
|
||||
children: children.length > 0 ? children : undefined
|
||||
};
|
||||
tree.push(node);
|
||||
}
|
||||
}
|
||||
return tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记已分配的权限
|
||||
*/
|
||||
markAssignedPermissions(permissions, assignedIds) {
|
||||
return permissions.map(permission => ({
|
||||
...permission,
|
||||
assigned: assignedIds.includes(permission.id),
|
||||
children: permission.children ?
|
||||
this.markAssignedPermissions(permission.children, assignedIds) :
|
||||
undefined
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据权限代码标记已分配的权限
|
||||
*/
|
||||
markAssignedPermissionsByCode(permissions, assignedCodes) {
|
||||
return permissions.map(permission => ({
|
||||
...permission,
|
||||
assigned: assignedCodes.includes(permission.code),
|
||||
children: permission.children ?
|
||||
this.markAssignedPermissionsByCode(permission.children, assignedCodes) :
|
||||
undefined
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new RolePermissionController();
|
||||
316
insurance_backend/controllers/systemController.js
Normal file
@@ -0,0 +1,316 @@
|
||||
const { User, Role, InsuranceApplication, Policy, Claim } = require('../models');
|
||||
const responseFormat = require('../utils/response');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// 获取系统统计信息
|
||||
const getSystemStats = async (req, res) => {
|
||||
try {
|
||||
const [
|
||||
totalUsers,
|
||||
totalRoles,
|
||||
totalApplications,
|
||||
totalPolicies,
|
||||
totalClaims,
|
||||
activeUsers,
|
||||
pendingApplications,
|
||||
approvedApplications,
|
||||
activePolicies,
|
||||
pendingClaims,
|
||||
approvedClaims
|
||||
] = await Promise.all([
|
||||
User.count(),
|
||||
Role.count(),
|
||||
InsuranceApplication.count(),
|
||||
Policy.count(),
|
||||
Claim.count(),
|
||||
User.count({ where: { status: 'active' } }),
|
||||
InsuranceApplication.count({ where: { status: 'pending' } }),
|
||||
InsuranceApplication.count({ where: { status: 'approved' } }),
|
||||
Policy.count({ where: { policy_status: 'active' } }),
|
||||
Claim.count({ where: { claim_status: 'pending' } }),
|
||||
Claim.count({ where: { claim_status: 'approved' } })
|
||||
]);
|
||||
|
||||
// 获取最近7天的数据趋势
|
||||
const sevenDaysAgo = new Date();
|
||||
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
|
||||
|
||||
const recentStats = await Promise.all([
|
||||
User.count({ where: { created_at: { [Op.gte]: sevenDaysAgo } } }),
|
||||
InsuranceApplication.count({ where: { created_at: { [Op.gte]: sevenDaysAgo } } }),
|
||||
Policy.count({ where: { created_at: { [Op.gte]: sevenDaysAgo } } }),
|
||||
Claim.count({ where: { created_at: { [Op.gte]: sevenDaysAgo } } })
|
||||
]);
|
||||
|
||||
res.json(responseFormat.success({
|
||||
overview: {
|
||||
users: totalUsers,
|
||||
roles: totalRoles,
|
||||
applications: totalApplications,
|
||||
policies: totalPolicies,
|
||||
claims: totalClaims
|
||||
},
|
||||
status: {
|
||||
active_users: activeUsers,
|
||||
pending_applications: pendingApplications,
|
||||
approved_applications: approvedApplications,
|
||||
active_policies: activePolicies,
|
||||
pending_claims: pendingClaims,
|
||||
approved_claims: approvedClaims
|
||||
},
|
||||
recent: {
|
||||
new_users: recentStats[0],
|
||||
new_applications: recentStats[1],
|
||||
new_policies: recentStats[2],
|
||||
new_claims: recentStats[3]
|
||||
}
|
||||
}, '获取系统统计信息成功'));
|
||||
} catch (error) {
|
||||
console.error('获取系统统计信息错误:', error);
|
||||
res.status(500).json(responseFormat.error('获取系统统计信息失败'));
|
||||
}
|
||||
};
|
||||
|
||||
// 获取系统日志(模拟)
|
||||
const getSystemLogs = async (req, res) => {
|
||||
try {
|
||||
const { page = 1, limit = 50, level, start_date, end_date } = req.query;
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
// 模拟日志数据 - 扩展更多有意义的日志记录
|
||||
const mockLogs = [
|
||||
{
|
||||
id: 1,
|
||||
level: 'info',
|
||||
message: '系统启动成功',
|
||||
timestamp: new Date().toISOString(),
|
||||
user: 'system'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
level: 'info',
|
||||
message: '数据库连接成功',
|
||||
timestamp: new Date(Date.now() - 1000 * 60).toISOString(),
|
||||
user: 'system'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
level: 'warning',
|
||||
message: 'Redis连接失败,使用内存缓存',
|
||||
timestamp: new Date(Date.now() - 1000 * 120).toISOString(),
|
||||
user: 'system'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
level: 'info',
|
||||
message: '用户 admin 登录成功',
|
||||
timestamp: new Date(Date.now() - 1000 * 180).toISOString(),
|
||||
user: 'admin'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
level: 'info',
|
||||
message: '新增保险申请:车险申请 - 申请人:张三',
|
||||
timestamp: new Date(Date.now() - 1000 * 240).toISOString(),
|
||||
user: 'zhangsan'
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
level: 'info',
|
||||
message: '保单生效:保单号 POL-2024-001 - 投保人:李四',
|
||||
timestamp: new Date(Date.now() - 1000 * 300).toISOString(),
|
||||
user: 'lisi'
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
level: 'warning',
|
||||
message: '理赔申请待审核:理赔号 CLM-2024-001 - 申请人:王五',
|
||||
timestamp: new Date(Date.now() - 1000 * 360).toISOString(),
|
||||
user: 'wangwu'
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
level: 'info',
|
||||
message: '新用户注册:用户名 zhaoliu',
|
||||
timestamp: new Date(Date.now() - 1000 * 420).toISOString(),
|
||||
user: 'system'
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
level: 'error',
|
||||
message: '支付接口调用失败:订单号 ORD-2024-001',
|
||||
timestamp: new Date(Date.now() - 1000 * 480).toISOString(),
|
||||
user: 'system'
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
level: 'info',
|
||||
message: '保险类型更新:新增意外险产品',
|
||||
timestamp: new Date(Date.now() - 1000 * 540).toISOString(),
|
||||
user: 'admin'
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
level: 'info',
|
||||
message: '系统备份完成:数据库备份成功',
|
||||
timestamp: new Date(Date.now() - 1000 * 600).toISOString(),
|
||||
user: 'system'
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
level: 'warning',
|
||||
message: '磁盘空间不足警告:剩余空间 15%',
|
||||
timestamp: new Date(Date.now() - 1000 * 660).toISOString(),
|
||||
user: 'system'
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
level: 'info',
|
||||
message: '理赔审核通过:理赔号 CLM-2024-002 - 赔付金额 ¥5000',
|
||||
timestamp: new Date(Date.now() - 1000 * 720).toISOString(),
|
||||
user: 'admin'
|
||||
},
|
||||
{
|
||||
id: 14,
|
||||
level: 'info',
|
||||
message: '保单续费成功:保单号 POL-2024-002 - 续费期限 1年',
|
||||
timestamp: new Date(Date.now() - 1000 * 780).toISOString(),
|
||||
user: 'system'
|
||||
},
|
||||
{
|
||||
id: 15,
|
||||
level: 'error',
|
||||
message: '短信发送失败:手机号 138****8888',
|
||||
timestamp: new Date(Date.now() - 1000 * 840).toISOString(),
|
||||
user: 'system'
|
||||
}
|
||||
];
|
||||
|
||||
// 简单的过滤逻辑
|
||||
let filteredLogs = mockLogs;
|
||||
if (level) {
|
||||
filteredLogs = filteredLogs.filter(log => log.level === level);
|
||||
}
|
||||
if (start_date) {
|
||||
const startDate = new Date(start_date);
|
||||
filteredLogs = filteredLogs.filter(log => new Date(log.timestamp) >= startDate);
|
||||
}
|
||||
if (end_date) {
|
||||
const endDate = new Date(end_date);
|
||||
filteredLogs = filteredLogs.filter(log => new Date(log.timestamp) <= endDate);
|
||||
}
|
||||
|
||||
// 分页
|
||||
const paginatedLogs = filteredLogs.slice(offset, offset + parseInt(limit));
|
||||
|
||||
res.json(responseFormat.success({
|
||||
logs: paginatedLogs,
|
||||
pagination: {
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
total: filteredLogs.length,
|
||||
pages: Math.ceil(filteredLogs.length / parseInt(limit))
|
||||
}
|
||||
}, '获取系统日志成功'));
|
||||
} catch (error) {
|
||||
console.error('获取系统日志错误:', error);
|
||||
res.status(500).json(responseFormat.error('获取系统日志失败'));
|
||||
}
|
||||
};
|
||||
|
||||
// 获取系统配置(模拟)
|
||||
const getSystemConfig = async (req, res) => {
|
||||
try {
|
||||
const config = {
|
||||
site_name: '保险端口管理系统',
|
||||
version: '1.0.0',
|
||||
environment: process.env.NODE_ENV || 'development',
|
||||
max_file_size: '10MB',
|
||||
allowed_file_types: ['jpg', 'jpeg', 'png', 'pdf', 'doc', 'docx'],
|
||||
session_timeout: 3600,
|
||||
backup_enabled: true,
|
||||
backup_schedule: '0 2 * * *', // 每天凌晨2点
|
||||
email_notifications: true,
|
||||
sms_notifications: false,
|
||||
maintenance_mode: false
|
||||
};
|
||||
|
||||
res.json(responseFormat.success(config, '获取系统配置成功'));
|
||||
} catch (error) {
|
||||
console.error('获取系统配置错误:', error);
|
||||
res.status(500).json(responseFormat.error('获取系统配置失败'));
|
||||
}
|
||||
};
|
||||
|
||||
// 更新系统配置(模拟)
|
||||
const updateSystemConfig = async (req, res) => {
|
||||
try {
|
||||
const { maintenance_mode, email_notifications, sms_notifications } = req.body;
|
||||
|
||||
// 模拟更新配置
|
||||
const updatedConfig = {
|
||||
maintenance_mode: maintenance_mode !== undefined ? maintenance_mode : false,
|
||||
email_notifications: email_notifications !== undefined ? email_notifications : true,
|
||||
sms_notifications: sms_notifications !== undefined ? sms_notifications : false,
|
||||
updated_at: new Date().toISOString()
|
||||
};
|
||||
|
||||
res.json(responseFormat.success(updatedConfig, '系统配置更新成功'));
|
||||
} catch (error) {
|
||||
console.error('更新系统配置错误:', error);
|
||||
res.status(500).json(responseFormat.error('更新系统配置失败'));
|
||||
}
|
||||
};
|
||||
|
||||
// 备份数据库(模拟)
|
||||
const backupDatabase = async (req, res) => {
|
||||
try {
|
||||
// 模拟备份过程
|
||||
const backupInfo = {
|
||||
id: `backup_${Date.now()}`,
|
||||
filename: `backup_${new Date().toISOString().replace(/:/g, '-')}.sql`,
|
||||
size: '2.5MB',
|
||||
status: 'completed',
|
||||
created_at: new Date().toISOString(),
|
||||
download_url: '/api/system/backup/download/backup.sql'
|
||||
};
|
||||
|
||||
res.json(responseFormat.success(backupInfo, '数据库备份成功'));
|
||||
} catch (error) {
|
||||
console.error('数据库备份错误:', error);
|
||||
res.status(500).json(responseFormat.error('数据库备份失败'));
|
||||
}
|
||||
};
|
||||
|
||||
// 恢复数据库(模拟)
|
||||
const restoreDatabase = async (req, res) => {
|
||||
try {
|
||||
const { backup_id } = req.body;
|
||||
|
||||
if (!backup_id) {
|
||||
return res.status(400).json(responseFormat.error('备份ID不能为空'));
|
||||
}
|
||||
|
||||
// 模拟恢复过程
|
||||
const restoreInfo = {
|
||||
backup_id,
|
||||
status: 'completed',
|
||||
restored_at: new Date().toISOString(),
|
||||
message: '数据库恢复成功'
|
||||
};
|
||||
|
||||
res.json(responseFormat.success(restoreInfo, '数据库恢复成功'));
|
||||
} catch (error) {
|
||||
console.error('数据库恢复错误:', error);
|
||||
res.status(500).json(responseFormat.error('数据库恢复失败'));
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getSystemStats,
|
||||
getSystemLogs,
|
||||
getSystemConfig,
|
||||
updateSystemConfig,
|
||||
backupDatabase,
|
||||
restoreDatabase
|
||||
};
|
||||
@@ -1,183 +0,0 @@
|
||||
const { sequelize } = require('./config/database');
|
||||
const bcrypt = require('bcrypt');
|
||||
|
||||
async function createAdminUser() {
|
||||
try {
|
||||
// 连接数据库
|
||||
await sequelize.authenticate();
|
||||
console.log('✅ 数据库连接成功');
|
||||
|
||||
// 检查是否有users表
|
||||
const [tables] = await sequelize.query(
|
||||
"SHOW TABLES LIKE 'users'"
|
||||
);
|
||||
|
||||
if (tables.length === 0) {
|
||||
console.log('⚠️ users表不存在,开始创建必要的表结构');
|
||||
|
||||
// 创建roles表
|
||||
await sequelize.query(`
|
||||
CREATE TABLE IF NOT EXISTS roles (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '角色ID',
|
||||
name VARCHAR(50) NOT NULL UNIQUE COMMENT '角色名称',
|
||||
description VARCHAR(255) NULL COMMENT '角色描述',
|
||||
permissions JSON NOT NULL 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_roles_name (name),
|
||||
INDEX idx_roles_status (status)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='角色表';
|
||||
`);
|
||||
console.log('✅ roles表创建完成');
|
||||
|
||||
// 创建users表
|
||||
await sequelize.query(`
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '用户ID',
|
||||
username VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名',
|
||||
password VARCHAR(255) NOT NULL COMMENT '密码',
|
||||
real_name VARCHAR(50) NOT NULL COMMENT '真实姓名',
|
||||
email VARCHAR(100) NOT NULL UNIQUE COMMENT '邮箱',
|
||||
phone VARCHAR(20) NOT NULL COMMENT '手机号',
|
||||
role_id INT NOT NULL COMMENT '角色ID',
|
||||
status ENUM('active', 'inactive', 'suspended') NOT NULL DEFAULT 'active' COMMENT '状态',
|
||||
last_login TIMESTAMP NULL COMMENT '最后登录时间',
|
||||
avatar VARCHAR(255) NULL COMMENT '头像URL',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
INDEX idx_users_username (username),
|
||||
INDEX idx_users_email (email),
|
||||
INDEX idx_users_phone (phone),
|
||||
INDEX idx_users_role_id (role_id),
|
||||
INDEX idx_users_status (status),
|
||||
FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE RESTRICT
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表';
|
||||
`);
|
||||
console.log('✅ users表创建完成');
|
||||
|
||||
// 创建system_configs表
|
||||
await sequelize.query(`
|
||||
CREATE TABLE IF NOT EXISTS system_configs (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
config_key VARCHAR(50) NOT NULL UNIQUE,
|
||||
config_value TEXT NOT NULL,
|
||||
description VARCHAR(255) NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_config_key (config_key)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
`);
|
||||
console.log('✅ system_configs表创建完成');
|
||||
} else {
|
||||
console.log('✅ users表已存在');
|
||||
}
|
||||
|
||||
// 检查admin角色是否存在
|
||||
const [adminRole] = await sequelize.query(
|
||||
'SELECT id FROM roles WHERE name = ?',
|
||||
{ replacements: ['admin'] }
|
||||
);
|
||||
|
||||
let roleId;
|
||||
if (adminRole.length === 0) {
|
||||
// 创建admin角色
|
||||
await sequelize.query(
|
||||
'INSERT INTO roles (name, description, permissions, status) VALUES (?, ?, ?, ?)',
|
||||
{
|
||||
replacements: [
|
||||
'admin',
|
||||
'系统管理员',
|
||||
JSON.stringify(['*']),
|
||||
'active'
|
||||
]
|
||||
}
|
||||
);
|
||||
console.log('✅ admin角色创建完成');
|
||||
|
||||
// 获取刚创建的角色ID
|
||||
const [newRole] = await sequelize.query(
|
||||
'SELECT id FROM roles WHERE name = ?',
|
||||
{ replacements: ['admin'] }
|
||||
);
|
||||
roleId = newRole[0].id;
|
||||
} else {
|
||||
roleId = adminRole[0].id;
|
||||
console.log('✅ admin角色已存在');
|
||||
}
|
||||
|
||||
// 检查admin用户是否存在
|
||||
const [adminUser] = await sequelize.query(
|
||||
'SELECT id FROM users WHERE username = ?',
|
||||
{ replacements: ['admin'] }
|
||||
);
|
||||
|
||||
if (adminUser.length === 0) {
|
||||
// 密码为123456,使用bcrypt进行哈希处理
|
||||
const plainPassword = '123456';
|
||||
const hashedPassword = await bcrypt.hash(plainPassword, 12);
|
||||
|
||||
// 创建admin用户
|
||||
await sequelize.query(
|
||||
`INSERT INTO users (username, password, real_name, email, phone, role_id, status, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, NOW(), NOW())`,
|
||||
{
|
||||
replacements: [
|
||||
'admin',
|
||||
hashedPassword,
|
||||
'系统管理员',
|
||||
'admin@insurance.com',
|
||||
'13800138000',
|
||||
roleId,
|
||||
'active'
|
||||
]
|
||||
}
|
||||
);
|
||||
console.log('✅ admin用户创建完成,用户名: admin,密码: 123456');
|
||||
|
||||
// 插入默认系统配置
|
||||
await sequelize.query(
|
||||
`INSERT IGNORE INTO system_configs (config_key, config_value, description) VALUES
|
||||
('system_name', ?, '系统名称'),
|
||||
('company_name', ?, '公司名称'),
|
||||
('contact_email', ?, '联系邮箱'),
|
||||
('contact_phone', ?, '联系电话'),
|
||||
('max_file_size', ?, '最大文件上传大小(字节)'),
|
||||
('allowed_file_types', ?, '允许上传的文件类型')`,
|
||||
{
|
||||
replacements: [
|
||||
JSON.stringify('保险端口系统'),
|
||||
JSON.stringify('XX保险公司'),
|
||||
JSON.stringify('support@insurance.com'),
|
||||
JSON.stringify('400-123-4567'),
|
||||
'10485760',
|
||||
JSON.stringify(['jpg', 'jpeg', 'png', 'pdf', 'doc', 'docx'])
|
||||
]
|
||||
}
|
||||
);
|
||||
console.log('✅ 默认系统配置已插入');
|
||||
} else {
|
||||
console.log('⚠️ admin用户已存在,更新密码为: 123456');
|
||||
// 更新admin用户的密码
|
||||
const plainPassword = '123456';
|
||||
const hashedPassword = await bcrypt.hash(plainPassword, 12);
|
||||
|
||||
await sequelize.query(
|
||||
'UPDATE users SET password = ? WHERE username = ?',
|
||||
{ replacements: [hashedPassword, 'admin'] }
|
||||
);
|
||||
console.log('✅ admin用户密码已更新为: 123456');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 操作失败:', error.message);
|
||||
console.error(error.stack);
|
||||
} finally {
|
||||
// 关闭数据库连接
|
||||
await sequelize.close();
|
||||
console.log('🔒 数据库连接已关闭');
|
||||
}
|
||||
}
|
||||
|
||||
// 执行脚本
|
||||
createAdminUser();
|
||||
@@ -1,112 +0,0 @@
|
||||
const mysql = require('mysql2/promise');
|
||||
|
||||
async function createMissingTables() {
|
||||
const connection = await mysql.createConnection({
|
||||
host: '129.211.213.226',
|
||||
port: 9527,
|
||||
user: 'root',
|
||||
password: 'aiotAiot123!',
|
||||
database: 'insurance_data'
|
||||
});
|
||||
|
||||
try {
|
||||
console.log('=== 创建缺失的数据库表 ===');
|
||||
|
||||
// 1. 创建保险类型表
|
||||
await connection.execute(`
|
||||
CREATE TABLE IF NOT EXISTS insurance_types (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '保险类型ID',
|
||||
name VARCHAR(100) NOT NULL UNIQUE COMMENT '保险类型名称',
|
||||
description TEXT NULL COMMENT '保险类型描述',
|
||||
coverage_amount_min DECIMAL(15,2) NOT NULL DEFAULT 0.00 COMMENT '最小保额',
|
||||
coverage_amount_max DECIMAL(15,2) NOT NULL DEFAULT 1000000.00 COMMENT '最大保额',
|
||||
premium_rate DECIMAL(5,4) NOT NULL DEFAULT 0.001 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 '更新时间'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='保险类型表'
|
||||
`);
|
||||
console.log('✓ 保险类型表创建成功');
|
||||
|
||||
// 2. 创建保险申请表
|
||||
await connection.execute(`
|
||||
CREATE TABLE IF NOT EXISTS insurance_applications (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '申请ID',
|
||||
application_no VARCHAR(50) NOT NULL UNIQUE COMMENT '申请编号',
|
||||
customer_name VARCHAR(100) NOT NULL COMMENT '客户姓名',
|
||||
customer_id_card VARCHAR(18) NOT NULL COMMENT '客户身份证号',
|
||||
customer_phone VARCHAR(20) NOT NULL COMMENT '客户手机号',
|
||||
customer_address VARCHAR(255) NOT NULL COMMENT '客户地址',
|
||||
insurance_type_id INT NOT NULL COMMENT '保险类型ID',
|
||||
insurance_category VARCHAR(50) NULL COMMENT '保险类别',
|
||||
livestock_type VARCHAR(50) NULL COMMENT '牲畜类型',
|
||||
livestock_count INT NULL DEFAULT 0 COMMENT '牲畜数量',
|
||||
application_amount DECIMAL(15,2) NOT NULL COMMENT '申请金额',
|
||||
status ENUM('pending', 'approved', 'rejected', 'under_review') NOT NULL DEFAULT 'pending' COMMENT '状态',
|
||||
application_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '申请日期',
|
||||
review_notes TEXT NULL COMMENT '审核备注',
|
||||
reviewer_id INT NULL COMMENT '审核人ID',
|
||||
review_date TIMESTAMP NULL COMMENT '审核日期',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='保险申请表'
|
||||
`);
|
||||
console.log('✓ 保险申请表创建成功');
|
||||
|
||||
// 3. 创建保单表
|
||||
await connection.execute(`
|
||||
CREATE TABLE IF NOT EXISTS policies (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '保单ID',
|
||||
policy_no VARCHAR(50) NOT NULL UNIQUE COMMENT '保单编号',
|
||||
application_id INT NOT NULL COMMENT '关联的保险申请ID',
|
||||
insurance_type_id INT NOT NULL COMMENT '保险类型ID',
|
||||
customer_id INT NOT NULL COMMENT '客户ID',
|
||||
customer_name VARCHAR(100) NOT NULL COMMENT '客户姓名',
|
||||
customer_id_card VARCHAR(18) NOT NULL COMMENT '客户身份证号',
|
||||
customer_phone VARCHAR(20) NOT NULL COMMENT '客户手机号',
|
||||
coverage_amount DECIMAL(15,2) NOT NULL COMMENT '保额',
|
||||
premium_amount DECIMAL(15,2) NOT NULL COMMENT '保费金额',
|
||||
start_date DATE NOT NULL COMMENT '保险开始日期',
|
||||
end_date DATE NOT 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 '支付日期',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='保单表'
|
||||
`);
|
||||
console.log('✓ 保单表创建成功');
|
||||
|
||||
// 4. 创建理赔表
|
||||
await connection.execute(`
|
||||
CREATE TABLE IF NOT EXISTS claims (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '理赔ID',
|
||||
claim_no VARCHAR(50) NOT NULL UNIQUE COMMENT '理赔编号',
|
||||
policy_id INT NOT NULL COMMENT '关联的保单ID',
|
||||
customer_id INT NOT NULL COMMENT '客户ID',
|
||||
claim_amount DECIMAL(15,2) NOT NULL COMMENT '理赔金额',
|
||||
claim_date DATE NOT NULL COMMENT '理赔发生日期',
|
||||
incident_description TEXT NOT NULL COMMENT '事故描述',
|
||||
claim_status ENUM('pending', 'approved', 'rejected', 'processing', 'paid') NOT NULL DEFAULT 'pending' COMMENT '理赔状态',
|
||||
review_notes TEXT NULL COMMENT '审核备注',
|
||||
reviewer_id INT NULL COMMENT '审核人ID',
|
||||
review_date DATE NULL COMMENT '审核日期',
|
||||
payment_date DATE NULL COMMENT '支付日期',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='理赔表'
|
||||
`);
|
||||
console.log('✓ 理赔表创建成功');
|
||||
|
||||
console.log('\n=== 检查创建的表 ===');
|
||||
const [tables] = await connection.execute('SHOW TABLES');
|
||||
console.log('当前数据库表:', tables.map(t => Object.values(t)[0]));
|
||||
|
||||
} catch (error) {
|
||||
console.error('创建表错误:', error);
|
||||
} finally {
|
||||
await connection.end();
|
||||
}
|
||||
}
|
||||
|
||||
createMissingTables();
|
||||
@@ -1,54 +0,0 @@
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
|
||||
// 创建简单的测试应用
|
||||
const app = express();
|
||||
const PORT = 3002;
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
// 请求日志
|
||||
app.use((req, res, next) => {
|
||||
console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
|
||||
next();
|
||||
});
|
||||
|
||||
// 测试路由
|
||||
app.get('/health', (req, res) => {
|
||||
res.json({ status: 'ok', message: '测试服务器运行正常' });
|
||||
});
|
||||
|
||||
// 导入认证路由
|
||||
try {
|
||||
const authRoutes = require('./routes/auth');
|
||||
app.use('/api/auth', authRoutes);
|
||||
console.log('✅ 认证路由加载成功');
|
||||
} catch (error) {
|
||||
console.error('❌ 认证路由加载失败:', error.message);
|
||||
}
|
||||
|
||||
// 导入设备路由
|
||||
try {
|
||||
const deviceRoutes = require('./routes/devices');
|
||||
app.use('/api/devices', deviceRoutes);
|
||||
console.log('✅ 设备路由加载成功');
|
||||
} catch (error) {
|
||||
console.error('❌ 设备路由加载失败:', error.message);
|
||||
}
|
||||
|
||||
// 404处理
|
||||
app.use('*', (req, res) => {
|
||||
console.log(`404 - 未找到路由: ${req.method} ${req.originalUrl}`);
|
||||
res.status(404).json({
|
||||
code: 404,
|
||||
status: 'error',
|
||||
message: '接口不存在',
|
||||
path: req.originalUrl
|
||||
});
|
||||
});
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`🚀 调试服务器启动在端口 ${PORT}`);
|
||||
console.log(`📍 测试地址: http://localhost:${PORT}`);
|
||||
});
|
||||
@@ -1,73 +0,0 @@
|
||||
const axios = require('axios');
|
||||
|
||||
const BASE_URL = 'http://localhost:3000/api';
|
||||
const ADMIN_TOKEN = '5659725423f665a8bf5053b37e624ea86387f9113ae77ac75fc102012a349180';
|
||||
|
||||
// 创建axios实例
|
||||
const api = axios.create({
|
||||
baseURL: BASE_URL,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${ADMIN_TOKEN}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
async function debugAPIResponse() {
|
||||
console.log('调试API响应格式...\n');
|
||||
|
||||
try {
|
||||
console.log('测试获取所有角色及其权限...');
|
||||
const response = await api.get('/role-permissions/roles');
|
||||
console.log('完整响应:', JSON.stringify(response.data, null, 2));
|
||||
console.log('数据路径检查:');
|
||||
console.log('- response.data:', typeof response.data);
|
||||
console.log('- response.data.data:', typeof response.data.data);
|
||||
|
||||
// 尝试解析data字段
|
||||
let parsedData = response.data.data;
|
||||
if (typeof parsedData === 'string') {
|
||||
try {
|
||||
parsedData = JSON.parse(parsedData);
|
||||
console.log('- 解析后的data:', typeof parsedData);
|
||||
console.log('- 解析后的data.roles:', typeof parsedData?.roles);
|
||||
console.log('- 解析后的data.roles 是数组吗:', Array.isArray(parsedData?.roles));
|
||||
if (parsedData?.roles) {
|
||||
console.log('- 角色数量:', parsedData.roles.length);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('- 无法解析data字段:', e.message);
|
||||
}
|
||||
} else {
|
||||
console.log('- response.data.data.roles:', typeof response.data.data?.roles);
|
||||
console.log('- response.data.data.roles 是数组吗:', Array.isArray(response.data.data?.roles));
|
||||
if (response.data.data?.roles) {
|
||||
console.log('- 角色数量:', response.data.data.roles.length);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('错误:', {
|
||||
code: error.response?.status,
|
||||
status: error.response?.data?.status,
|
||||
data: error.response?.data?.data,
|
||||
message: error.response?.data?.message,
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('\n测试获取特定角色的详细权限...');
|
||||
const response = await api.get('/role-permissions/roles/1/permissions');
|
||||
console.log('完整响应:', JSON.stringify(response.data, null, 2));
|
||||
} catch (error) {
|
||||
console.log('错误:', error.response?.data || error.message);
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('\n测试权限统计...');
|
||||
const response = await api.get('/role-permissions/stats');
|
||||
console.log('完整响应:', JSON.stringify(response.data, null, 2));
|
||||
} catch (error) {
|
||||
console.log('错误:', error.response?.data || error.message);
|
||||
}
|
||||
}
|
||||
|
||||
debugAPIResponse().catch(console.error);
|
||||
@@ -1,77 +0,0 @@
|
||||
const axios = require('axios');
|
||||
|
||||
// 模拟前端登录和API调用流程
|
||||
async function debugFrontendToken() {
|
||||
console.log('=== 调试前端Token问题 ===\n');
|
||||
|
||||
try {
|
||||
// 1. 模拟前端登录
|
||||
console.log('1. 模拟前端登录...');
|
||||
const loginResponse = await axios.post('http://localhost:3001/api/auth/login', {
|
||||
username: 'admin',
|
||||
password: '123456'
|
||||
});
|
||||
|
||||
console.log('登录响应状态:', loginResponse.status);
|
||||
console.log('登录响应数据:', JSON.stringify(loginResponse.data, null, 2));
|
||||
|
||||
if (!loginResponse.data || loginResponse.data.code !== 200) {
|
||||
console.log('❌ 登录失败');
|
||||
return;
|
||||
}
|
||||
|
||||
const token = loginResponse.data.data.token;
|
||||
console.log('✅ 获取到Token:', token.substring(0, 50) + '...');
|
||||
|
||||
// 2. 模拟前端API调用 - 使用前端的baseURL
|
||||
console.log('\n2. 模拟前端API调用...');
|
||||
|
||||
// 前端使用的是 /api 作为baseURL,实际请求会被代理到 localhost:3000
|
||||
// 但我们直接测试 localhost:3001 的代理
|
||||
try {
|
||||
const apiResponse = await axios.get('http://localhost:3001/api/data-warehouse/overview', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
console.log('✅ API调用成功!');
|
||||
console.log('状态码:', apiResponse.status);
|
||||
console.log('响应数据:', JSON.stringify(apiResponse.data, null, 2));
|
||||
|
||||
} catch (apiError) {
|
||||
console.log('❌ API调用失败:', apiError.response?.status, apiError.response?.statusText);
|
||||
console.log('错误详情:', apiError.response?.data);
|
||||
|
||||
// 3. 尝试直接调用后端API
|
||||
console.log('\n3. 尝试直接调用后端API...');
|
||||
try {
|
||||
const directResponse = await axios.get('http://localhost:3000/api/data-warehouse/overview', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
console.log('✅ 直接调用后端成功!');
|
||||
console.log('状态码:', directResponse.status);
|
||||
console.log('响应数据:', JSON.stringify(directResponse.data, null, 2));
|
||||
|
||||
} catch (directError) {
|
||||
console.log('❌ 直接调用后端也失败:', directError.response?.status, directError.response?.statusText);
|
||||
console.log('错误详情:', directError.response?.data);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 检查前端代理配置
|
||||
console.log('\n4. 检查前端代理配置...');
|
||||
console.log('前端应该配置代理将 /api/* 请求转发到 http://localhost:3000');
|
||||
console.log('请检查 vite.config.js 或类似的代理配置文件');
|
||||
|
||||
} catch (error) {
|
||||
console.log('❌ 登录失败:', error.response?.data || error.message);
|
||||
}
|
||||
}
|
||||
|
||||
debugFrontendToken();
|
||||
132
insurance_backend/deploy.sh
Normal file
@@ -0,0 +1,132 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 保险系统部署脚本
|
||||
# 用于部署前端和后端到服务器
|
||||
|
||||
echo "🚀 开始部署保险系统..."
|
||||
|
||||
# 设置变量
|
||||
PROJECT_ROOT="/var/www/insurance-system"
|
||||
FRONTEND_DIR="/var/www/insurance-admin-system"
|
||||
BACKEND_DIR="/var/www/insurance-backend"
|
||||
NGINX_CONFIG="/etc/nginx/sites-available/ad.ningmuyun.com"
|
||||
|
||||
# 1. 创建项目目录
|
||||
echo "📁 创建项目目录..."
|
||||
sudo mkdir -p $PROJECT_ROOT
|
||||
sudo mkdir -p $FRONTEND_DIR
|
||||
sudo mkdir -p $BACKEND_DIR
|
||||
|
||||
# 2. 部署前端
|
||||
echo "🎨 部署前端..."
|
||||
cd $FRONTEND_DIR
|
||||
|
||||
# 构建前端(如果还没有构建)
|
||||
if [ ! -d "dist" ]; then
|
||||
echo "📦 构建前端项目..."
|
||||
npm install
|
||||
npm run build
|
||||
fi
|
||||
|
||||
# 设置权限
|
||||
sudo chown -R www-data:www-data $FRONTEND_DIR/dist
|
||||
sudo chmod -R 755 $FRONTEND_DIR/dist
|
||||
|
||||
# 3. 部署后端
|
||||
echo "⚙️ 部署后端..."
|
||||
cd $BACKEND_DIR
|
||||
|
||||
# 安装依赖
|
||||
npm install --production
|
||||
|
||||
# 创建环境变量文件
|
||||
if [ ! -f ".env" ]; then
|
||||
echo "📝 创建环境变量文件..."
|
||||
cat > .env << EOF
|
||||
NODE_ENV=production
|
||||
PORT=3000
|
||||
FRONTEND_URL=https://ad.ningmuyun.com
|
||||
|
||||
# 数据库配置
|
||||
DB_DIALECT=mysql
|
||||
DB_HOST=129.211.213.226
|
||||
DB_PORT=9527
|
||||
DB_DATABASE=insurance_data
|
||||
DB_USER=root
|
||||
DB_PASSWORD=aiotAiot123!
|
||||
|
||||
# JWT配置
|
||||
JWT_SECRET=insurance_super_secret_jwt_key_2024_very_long_and_secure_production
|
||||
JWT_EXPIRES_IN=24h
|
||||
|
||||
# 文件上传配置
|
||||
UPLOAD_PATH=uploads
|
||||
MAX_FILE_SIZE=10485760
|
||||
|
||||
# 日志配置
|
||||
LOG_LEVEL=info
|
||||
EOF
|
||||
fi
|
||||
|
||||
# 4. 配置nginx
|
||||
echo "🌐 配置nginx..."
|
||||
sudo cp $BACKEND_DIR/nginx-ad.ningmuyun.com.conf $NGINX_CONFIG
|
||||
|
||||
# 启用站点
|
||||
sudo ln -sf $NGINX_CONFIG /etc/nginx/sites-enabled/
|
||||
|
||||
# 测试nginx配置
|
||||
sudo nginx -t
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✅ nginx配置测试通过"
|
||||
sudo systemctl reload nginx
|
||||
else
|
||||
echo "❌ nginx配置测试失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 5. 配置PM2进程管理
|
||||
echo "🔄 配置PM2..."
|
||||
npm install -g pm2
|
||||
|
||||
# 创建PM2配置文件
|
||||
cat > ecosystem.config.js << EOF
|
||||
module.exports = {
|
||||
apps: [{
|
||||
name: 'insurance-backend',
|
||||
script: 'src/app.js',
|
||||
cwd: '$BACKEND_DIR',
|
||||
instances: 1,
|
||||
exec_mode: 'fork',
|
||||
env: {
|
||||
NODE_ENV: 'production',
|
||||
PORT: 3000
|
||||
},
|
||||
error_file: './logs/err.log',
|
||||
out_file: './logs/out.log',
|
||||
log_file: './logs/combined.log',
|
||||
time: true,
|
||||
max_memory_restart: '1G',
|
||||
restart_delay: 4000,
|
||||
max_restarts: 10,
|
||||
min_uptime: '10s'
|
||||
}]
|
||||
};
|
||||
EOF
|
||||
|
||||
# 启动应用
|
||||
pm2 start ecosystem.config.js
|
||||
pm2 save
|
||||
pm2 startup
|
||||
|
||||
echo "✅ 部署完成!"
|
||||
echo "📋 部署信息:"
|
||||
echo " 前端地址: https://ad.ningmuyun.com/insurance/"
|
||||
echo " 后端API: https://ad.ningmuyun.com/insurance/api/"
|
||||
echo " 后端端口: 3000"
|
||||
echo " 进程管理: PM2"
|
||||
|
||||
echo "🔍 检查服务状态:"
|
||||
pm2 status
|
||||
sudo systemctl status nginx
|
||||
28
insurance_backend/ecosystem.config.js
Normal file
@@ -0,0 +1,28 @@
|
||||
module.exports = {
|
||||
apps: [{
|
||||
name: 'insurance-backend',
|
||||
script: 'src/app.js',
|
||||
instances: 1,
|
||||
exec_mode: 'fork',
|
||||
env: {
|
||||
NODE_ENV: 'production',
|
||||
PORT: 3000,
|
||||
FRONTEND_URL: 'https://ad.ningmuyun.com'
|
||||
},
|
||||
env_production: {
|
||||
NODE_ENV: 'production',
|
||||
PORT: 3000,
|
||||
FRONTEND_URL: 'https://ad.ningmuyun.com'
|
||||
},
|
||||
error_file: './logs/err.log',
|
||||
out_file: './logs/out.log',
|
||||
log_file: './logs/combined.log',
|
||||
time: true,
|
||||
max_memory_restart: '1G',
|
||||
restart_delay: 4000,
|
||||
max_restarts: 10,
|
||||
min_uptime: '10s',
|
||||
watch: false,
|
||||
ignore_watch: ['node_modules', 'logs']
|
||||
}]
|
||||
};
|
||||
153
insurance_backend/fix-nginx.sh
Normal file
@@ -0,0 +1,153 @@
|
||||
#!/bin/bash
|
||||
|
||||
# nginx配置检查和修复脚本
|
||||
|
||||
echo "🌐 检查和修复nginx配置..."
|
||||
|
||||
# 检查nginx是否安装
|
||||
if ! command -v nginx &> /dev/null; then
|
||||
echo "❌ nginx未安装,正在安装..."
|
||||
sudo apt update
|
||||
sudo apt install nginx -y
|
||||
fi
|
||||
|
||||
# 检查nginx配置文件
|
||||
NGINX_CONFIG="/etc/nginx/sites-available/ad.ningmuyun.com"
|
||||
NGINX_ENABLED="/etc/nginx/sites-enabled/ad.ningmuyun.com"
|
||||
|
||||
echo "📁 检查nginx配置文件..."
|
||||
|
||||
if [ ! -f "$NGINX_CONFIG" ]; then
|
||||
echo "❌ nginx配置文件不存在,正在创建..."
|
||||
|
||||
# 创建nginx配置
|
||||
sudo tee "$NGINX_CONFIG" > /dev/null << 'EOF'
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name ad.ningmuyun.com;
|
||||
|
||||
# SSL证书配置(需要替换为实际的证书路径)
|
||||
ssl_certificate /etc/ssl/certs/ad.ningmuyun.com.crt;
|
||||
ssl_certificate_key /etc/ssl/private/ad.ningmuyun.com.key;
|
||||
|
||||
# 如果SSL证书不存在,使用自签名证书(仅用于测试)
|
||||
# ssl_certificate /etc/ssl/certs/nginx-selfsigned.crt;
|
||||
# ssl_certificate_key /etc/ssl/private/nginx-selfsigned.key;
|
||||
|
||||
# SSL配置
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;
|
||||
ssl_prefer_server_ciphers off;
|
||||
|
||||
# 前端静态文件
|
||||
location /insurance/ {
|
||||
alias /var/www/insurance-admin-system/dist/;
|
||||
try_files $uri $uri/ /insurance/index.html;
|
||||
|
||||
# 静态资源缓存
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
}
|
||||
|
||||
# 后端API代理
|
||||
location /insurance/api/ {
|
||||
proxy_pass http://127.0.0.1:3000/api/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
|
||||
# 超时设置
|
||||
proxy_connect_timeout 60s;
|
||||
proxy_send_timeout 60s;
|
||||
proxy_read_timeout 60s;
|
||||
|
||||
# CORS headers
|
||||
add_header Access-Control-Allow-Origin https://ad.ningmuyun.com;
|
||||
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
|
||||
add_header Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With";
|
||||
add_header Access-Control-Allow-Credentials true;
|
||||
|
||||
# 处理预检请求
|
||||
if ($request_method = 'OPTIONS') {
|
||||
add_header Access-Control-Allow-Origin https://ad.ningmuyun.com;
|
||||
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
|
||||
add_header Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With";
|
||||
add_header Access-Control-Allow-Credentials true;
|
||||
add_header Content-Length 0;
|
||||
add_header Content-Type text/plain;
|
||||
return 204;
|
||||
}
|
||||
}
|
||||
|
||||
# 健康检查
|
||||
location /health {
|
||||
proxy_pass http://127.0.0.1:3000/health;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
|
||||
# HTTP重定向到HTTPS
|
||||
server {
|
||||
listen 80;
|
||||
server_name ad.ningmuyun.com;
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "✅ nginx配置文件已创建"
|
||||
else
|
||||
echo "✅ nginx配置文件已存在"
|
||||
fi
|
||||
|
||||
# 启用站点
|
||||
if [ ! -L "$NGINX_ENABLED" ]; then
|
||||
echo "🔗 启用nginx站点..."
|
||||
sudo ln -sf "$NGINX_CONFIG" "$NGINX_ENABLED"
|
||||
echo "✅ nginx站点已启用"
|
||||
else
|
||||
echo "✅ nginx站点已启用"
|
||||
fi
|
||||
|
||||
# 检查前端目录
|
||||
if [ ! -d "/var/www/insurance-admin-system/dist" ]; then
|
||||
echo "⚠️ 前端目录不存在,创建目录..."
|
||||
sudo mkdir -p /var/www/insurance-admin-system/dist
|
||||
sudo chown -R www-data:www-data /var/www/insurance-admin-system
|
||||
echo "✅ 前端目录已创建"
|
||||
fi
|
||||
|
||||
# 测试nginx配置
|
||||
echo "🧪 测试nginx配置..."
|
||||
if sudo nginx -t; then
|
||||
echo "✅ nginx配置测试通过"
|
||||
|
||||
# 重新加载nginx
|
||||
echo "🔄 重新加载nginx..."
|
||||
sudo systemctl reload nginx
|
||||
|
||||
# 检查nginx状态
|
||||
echo "📊 nginx状态:"
|
||||
sudo systemctl status nginx --no-pager
|
||||
|
||||
else
|
||||
echo "❌ nginx配置测试失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "✅ nginx配置检查和修复完成!"
|
||||
echo "📋 配置信息:"
|
||||
echo " 配置文件: $NGINX_CONFIG"
|
||||
echo " 启用链接: $NGINX_ENABLED"
|
||||
echo " 前端目录: /var/www/insurance-admin-system/dist"
|
||||
echo " API代理: /insurance/api/ → http://127.0.0.1:3000/api/"
|
||||
@@ -1,4 +1,4 @@
|
||||
const bcrypt = require('bcrypt');
|
||||
const bcrypt = require('bcryptjs');
|
||||
|
||||
async function generateHash() {
|
||||
const password = 'admin123';
|
||||
|
||||
212
insurance_backend/middleware/auth.js
Normal file
@@ -0,0 +1,212 @@
|
||||
const jwt = require('jsonwebtoken');
|
||||
const User = require('../models/User');
|
||||
const Role = require('../models/Role');
|
||||
const responseFormat = require('../utils/response');
|
||||
|
||||
// JWT认证中间件
|
||||
const jwtAuth = async (req, res, next) => {
|
||||
try {
|
||||
const authHeader = req.headers.authorization;
|
||||
console.log('Authorization header:', authHeader);
|
||||
|
||||
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||
return res.status(401).json(responseFormat.error('未提供有效的认证token'));
|
||||
}
|
||||
|
||||
const token = authHeader.substring(7);
|
||||
console.log('提取的token:', token);
|
||||
console.log('token类型:', typeof token);
|
||||
console.log('token长度:', token.length);
|
||||
|
||||
// 首先尝试固定token验证
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
fixed_token: token,
|
||||
status: 'active'
|
||||
},
|
||||
include: [{
|
||||
model: Role,
|
||||
as: 'role',
|
||||
attributes: ['id', 'name', 'permissions']
|
||||
}]
|
||||
});
|
||||
|
||||
if (user) {
|
||||
// 固定token验证成功
|
||||
req.user = {
|
||||
id: user.id,
|
||||
userId: user.id,
|
||||
username: user.username,
|
||||
role_id: user.role_id,
|
||||
role: user.role,
|
||||
permissions: user.role ? user.role.permissions : [],
|
||||
type: 'fixed_token'
|
||||
};
|
||||
return next();
|
||||
}
|
||||
|
||||
// 如果固定token验证失败,尝试JWT验证
|
||||
try {
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
||||
|
||||
if (decoded.type !== 'access') {
|
||||
return res.status(401).json(responseFormat.error('Token类型错误'));
|
||||
}
|
||||
|
||||
// 验证用户是否存在且状态正常
|
||||
const jwtUser = await User.findOne({
|
||||
where: {
|
||||
id: decoded.id,
|
||||
status: 'active'
|
||||
},
|
||||
include: [{
|
||||
model: Role,
|
||||
as: 'role',
|
||||
attributes: ['id', 'name', 'permissions']
|
||||
}]
|
||||
});
|
||||
|
||||
if (!jwtUser) {
|
||||
return res.status(401).json(responseFormat.error('用户不存在或已被禁用'));
|
||||
}
|
||||
|
||||
req.user = {
|
||||
id: decoded.id,
|
||||
userId: decoded.id,
|
||||
username: decoded.username,
|
||||
role_id: decoded.role_id,
|
||||
role: jwtUser.role,
|
||||
permissions: decoded.permissions || (jwtUser.role ? jwtUser.role.permissions : []),
|
||||
type: 'jwt'
|
||||
};
|
||||
|
||||
return next();
|
||||
} catch (jwtError) {
|
||||
if (jwtError.name === 'TokenExpiredError') {
|
||||
return res.status(401).json(responseFormat.error('Token已过期', 'TOKEN_EXPIRED'));
|
||||
} else if (jwtError.name === 'JsonWebTokenError') {
|
||||
return res.status(401).json(responseFormat.error('Token无效'));
|
||||
}
|
||||
throw jwtError;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Token验证错误:', error);
|
||||
return res.status(500).json(responseFormat.error('服务器内部错误'));
|
||||
}
|
||||
};
|
||||
|
||||
// 权限检查中间件
|
||||
const checkPermission = (resource, action) => {
|
||||
return async (req, res, next) => {
|
||||
try {
|
||||
console.log(`权限检查 - 资源: ${resource}, 操作: ${action}, 用户:`, req.user);
|
||||
const user = req.user;
|
||||
|
||||
if (!user || !user.role_id) {
|
||||
console.log('权限检查失败 - 用户角色信息缺失');
|
||||
return res.status(403).json(responseFormat.error('用户角色信息缺失'));
|
||||
}
|
||||
|
||||
let permissions = [];
|
||||
|
||||
// 优先使用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;
|
||||
}
|
||||
}
|
||||
|
||||
const requiredPermission = `${resource}:${action}`;
|
||||
|
||||
// 首先检查JWT中的权限
|
||||
let hasPermission = permissions.includes(requiredPermission) ||
|
||||
permissions.includes('*:*') ||
|
||||
permissions.includes('*');
|
||||
|
||||
// 如果JWT中没有权限信息,或者JWT权限不足,从数据库查询最新权限
|
||||
if (permissions.length === 0 || !hasPermission) {
|
||||
console.log('JWT权限不足或为空,从数据库获取最新权限...');
|
||||
const { Role, RolePermission, Permission } = 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('用户角色不存在'));
|
||||
}
|
||||
|
||||
// 从RolePermission表获取权限
|
||||
const rolePermissions = await RolePermission.findAll({
|
||||
where: {
|
||||
role_id: user.role_id,
|
||||
granted: true
|
||||
},
|
||||
include: [{
|
||||
model: Permission,
|
||||
as: 'permission',
|
||||
attributes: ['code']
|
||||
}]
|
||||
});
|
||||
|
||||
permissions = rolePermissions.map(rp => rp.permission.code);
|
||||
console.log('从RolePermission表获取的最新权限:', permissions);
|
||||
|
||||
// 重新检查权限
|
||||
hasPermission = permissions.includes(requiredPermission) ||
|
||||
permissions.includes('*:*') ||
|
||||
permissions.includes('*');
|
||||
}
|
||||
|
||||
console.log('权限检查 - 用户权限:', permissions, '需要权限:', requiredPermission, '是否有权限:', hasPermission);
|
||||
|
||||
// 检查权限或超级管理员权限
|
||||
if (!hasPermission) {
|
||||
console.log('权限检查失败 - 权限不足');
|
||||
return res.status(403).json(responseFormat.error('权限不足'));
|
||||
}
|
||||
|
||||
console.log('权限检查通过');
|
||||
next();
|
||||
} catch (error) {
|
||||
return res.status(500).json(responseFormat.error('权限验证失败'));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// 可选认证中间件(不强制要求认证)
|
||||
const optionalAuth = (req, res, next) => {
|
||||
const token = req.headers.authorization?.replace('Bearer ', '');
|
||||
|
||||
if (token) {
|
||||
try {
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
||||
req.user = decoded;
|
||||
} catch (error) {
|
||||
// 令牌无效,但不阻止请求
|
||||
console.warn('可选认证令牌无效:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
// 别名导出以匹配路由中的使用
|
||||
const authenticateToken = jwtAuth;
|
||||
const requirePermission = (permission) => {
|
||||
const [resource, action] = permission.split(':');
|
||||
return checkPermission(resource, action);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
jwtAuth,
|
||||
checkPermission,
|
||||
optionalAuth,
|
||||
authenticateToken,
|
||||
requirePermission
|
||||
};
|
||||
119
insurance_backend/middleware/fixedTokenAuth.js
Normal file
@@ -0,0 +1,119 @@
|
||||
const User = require('../models/User');
|
||||
const Role = require('../models/Role');
|
||||
|
||||
/**
|
||||
* 固定Token认证中间件
|
||||
* 支持JWT token和固定token两种认证方式
|
||||
*/
|
||||
const fixedTokenAuth = async (req, res, next) => {
|
||||
try {
|
||||
const authHeader = req.headers.authorization;
|
||||
|
||||
if (!authHeader) {
|
||||
return res.status(401).json({
|
||||
status: 'error',
|
||||
message: '未提供认证token'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查是否为Bearer token格式
|
||||
if (authHeader.startsWith('Bearer ')) {
|
||||
const token = authHeader.substring(7);
|
||||
|
||||
// 首先尝试固定token验证
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
fixed_token: token,
|
||||
status: 'active'
|
||||
},
|
||||
include: [{
|
||||
model: Role,
|
||||
as: 'role',
|
||||
attributes: ['id', 'name', 'permissions']
|
||||
}]
|
||||
});
|
||||
|
||||
if (user) {
|
||||
// 固定token验证成功
|
||||
req.user = {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
role_id: user.role_id,
|
||||
role: user.role,
|
||||
permissions: user.role ? user.role.permissions : []
|
||||
};
|
||||
return next();
|
||||
}
|
||||
|
||||
// 如果固定token验证失败,尝试JWT验证
|
||||
const jwt = require('jsonwebtoken');
|
||||
try {
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
||||
|
||||
if (decoded.type !== 'access') {
|
||||
return res.status(401).json({
|
||||
status: 'error',
|
||||
message: 'Token类型错误'
|
||||
});
|
||||
}
|
||||
|
||||
// 验证用户是否存在且状态正常
|
||||
const jwtUser = await User.findOne({
|
||||
where: {
|
||||
id: decoded.userId,
|
||||
status: 'active'
|
||||
},
|
||||
include: [{
|
||||
model: Role,
|
||||
as: 'role',
|
||||
attributes: ['id', 'name', 'permissions']
|
||||
}]
|
||||
});
|
||||
|
||||
if (!jwtUser) {
|
||||
return res.status(401).json({
|
||||
status: 'error',
|
||||
message: '用户不存在或已被禁用'
|
||||
});
|
||||
}
|
||||
|
||||
req.user = {
|
||||
id: jwtUser.id,
|
||||
username: jwtUser.username,
|
||||
role_id: jwtUser.role_id,
|
||||
role: jwtUser.role,
|
||||
permissions: decoded.permissions || (jwtUser.role ? jwtUser.role.permissions : [])
|
||||
};
|
||||
|
||||
return next();
|
||||
} catch (jwtError) {
|
||||
if (jwtError.name === 'TokenExpiredError') {
|
||||
return res.status(401).json({
|
||||
status: 'error',
|
||||
code: 'TOKEN_EXPIRED',
|
||||
message: 'Token已过期'
|
||||
});
|
||||
} else if (jwtError.name === 'JsonWebTokenError') {
|
||||
return res.status(401).json({
|
||||
status: 'error',
|
||||
message: 'Token无效'
|
||||
});
|
||||
}
|
||||
throw jwtError;
|
||||
}
|
||||
} else {
|
||||
return res.status(401).json({
|
||||
status: 'error',
|
||||
message: 'Token格式错误,请使用Bearer格式'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Token验证错误:', error);
|
||||
return res.status(500).json({
|
||||
status: 'error',
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = fixedTokenAuth;
|
||||
302
insurance_backend/middleware/operationLogger.js
Normal file
@@ -0,0 +1,302 @@
|
||||
const { OperationLog } = require('../models');
|
||||
|
||||
/**
|
||||
* 操作日志记录中间件
|
||||
* 自动记录用户的操作行为
|
||||
*/
|
||||
class OperationLogger {
|
||||
/**
|
||||
* 记录操作日志的中间件
|
||||
*/
|
||||
static logOperation(options = {}) {
|
||||
return async (req, res, next) => {
|
||||
const startTime = Date.now();
|
||||
|
||||
// 保存原始的res.json方法
|
||||
const originalJson = res.json;
|
||||
|
||||
// 重写res.json方法以捕获响应数据
|
||||
res.json = function(data) {
|
||||
const endTime = Date.now();
|
||||
const executionTime = endTime - startTime;
|
||||
|
||||
// 异步记录操作日志,不阻塞响应
|
||||
setImmediate(async () => {
|
||||
try {
|
||||
await OperationLogger.recordLog(req, res, data, executionTime, options);
|
||||
} catch (error) {
|
||||
console.error('记录操作日志失败:', error);
|
||||
}
|
||||
});
|
||||
|
||||
// 调用原始的json方法
|
||||
return originalJson.call(this, data);
|
||||
};
|
||||
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录操作日志
|
||||
*/
|
||||
static async recordLog(req, res, responseData, executionTime, options) {
|
||||
try {
|
||||
// 如果用户未登录,不记录日志
|
||||
if (!req.user || !req.user.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取操作类型
|
||||
const operationType = OperationLogger.getOperationType(req.method, req.url, options);
|
||||
|
||||
// 获取操作模块
|
||||
const operationModule = OperationLogger.getOperationModule(req.url, options);
|
||||
|
||||
// 获取操作内容
|
||||
const operationContent = OperationLogger.getOperationContent(req, operationType, operationModule, options);
|
||||
|
||||
// 获取操作目标
|
||||
const operationTarget = OperationLogger.getOperationTarget(req, responseData, options);
|
||||
|
||||
// 获取操作状态
|
||||
const status = OperationLogger.getOperationStatus(res.statusCode, responseData);
|
||||
|
||||
// 获取错误信息
|
||||
const errorMessage = OperationLogger.getErrorMessage(responseData, status);
|
||||
|
||||
// 记录操作日志
|
||||
await OperationLog.logOperation({
|
||||
user_id: req.user.id,
|
||||
operation_type: operationType,
|
||||
operation_module: operationModule,
|
||||
operation_content: operationContent,
|
||||
operation_target: operationTarget,
|
||||
request_method: req.method,
|
||||
request_url: req.originalUrl || req.url,
|
||||
request_params: {
|
||||
query: req.query,
|
||||
body: OperationLogger.sanitizeRequestBody(req.body),
|
||||
params: req.params
|
||||
},
|
||||
response_status: res.statusCode,
|
||||
response_data: OperationLogger.sanitizeResponseData(responseData),
|
||||
ip_address: OperationLogger.getClientIP(req),
|
||||
user_agent: req.get('User-Agent') || '',
|
||||
execution_time: executionTime,
|
||||
status: status,
|
||||
error_message: errorMessage
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('记录操作日志时发生错误:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取操作类型
|
||||
*/
|
||||
static getOperationType(method, url, options) {
|
||||
if (options.operation_type) {
|
||||
return options.operation_type;
|
||||
}
|
||||
|
||||
// 根据URL和HTTP方法推断操作类型
|
||||
if (url.includes('/login')) return 'login';
|
||||
if (url.includes('/logout')) return 'logout';
|
||||
if (url.includes('/export')) return 'export';
|
||||
if (url.includes('/import')) return 'import';
|
||||
if (url.includes('/approve')) return 'approve';
|
||||
if (url.includes('/reject')) return 'reject';
|
||||
|
||||
switch (method.toUpperCase()) {
|
||||
case 'GET':
|
||||
return 'view';
|
||||
case 'POST':
|
||||
return 'create';
|
||||
case 'PUT':
|
||||
case 'PATCH':
|
||||
return 'update';
|
||||
case 'DELETE':
|
||||
return 'delete';
|
||||
default:
|
||||
return 'other';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取操作模块
|
||||
*/
|
||||
static getOperationModule(url, options) {
|
||||
if (options.operation_module) {
|
||||
return options.operation_module;
|
||||
}
|
||||
|
||||
// 从URL中提取模块名
|
||||
const pathSegments = url.split('/').filter(segment => segment && segment !== 'api');
|
||||
if (pathSegments.length > 0) {
|
||||
return pathSegments[0].replace(/-/g, '_');
|
||||
}
|
||||
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取操作内容
|
||||
*/
|
||||
static getOperationContent(req, operationType, operationModule, options) {
|
||||
if (options.operation_content) {
|
||||
return options.operation_content;
|
||||
}
|
||||
|
||||
const actionMap = {
|
||||
'login': '用户登录',
|
||||
'logout': '用户退出',
|
||||
'view': '查看',
|
||||
'create': '创建',
|
||||
'update': '更新',
|
||||
'delete': '删除',
|
||||
'export': '导出',
|
||||
'import': '导入',
|
||||
'approve': '审批通过',
|
||||
'reject': '审批拒绝'
|
||||
};
|
||||
|
||||
const moduleMap = {
|
||||
'users': '用户',
|
||||
'roles': '角色',
|
||||
'insurance': '保险',
|
||||
'policies': '保单',
|
||||
'claims': '理赔',
|
||||
'system': '系统',
|
||||
'operation_logs': '操作日志',
|
||||
'devices': '设备',
|
||||
'device_alerts': '设备告警'
|
||||
};
|
||||
|
||||
const action = actionMap[operationType] || operationType;
|
||||
const module = moduleMap[operationModule] || operationModule;
|
||||
|
||||
return `${action}${module}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取操作目标
|
||||
*/
|
||||
static getOperationTarget(req, responseData, options) {
|
||||
if (options.operation_target) {
|
||||
return options.operation_target;
|
||||
}
|
||||
|
||||
// 尝试从请求参数中获取ID
|
||||
if (req.params.id) {
|
||||
return `ID: ${req.params.id}`;
|
||||
}
|
||||
|
||||
// 尝试从响应数据中获取信息
|
||||
if (responseData && responseData.data) {
|
||||
if (responseData.data.id) {
|
||||
return `ID: ${responseData.data.id}`;
|
||||
}
|
||||
if (responseData.data.name) {
|
||||
return `名称: ${responseData.data.name}`;
|
||||
}
|
||||
if (responseData.data.username) {
|
||||
return `用户: ${responseData.data.username}`;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取操作状态
|
||||
*/
|
||||
static getOperationStatus(statusCode, responseData) {
|
||||
if (statusCode >= 200 && statusCode < 300) {
|
||||
return 'success';
|
||||
} else if (statusCode >= 400 && statusCode < 500) {
|
||||
return 'failed';
|
||||
} else {
|
||||
return 'error';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取错误信息
|
||||
*/
|
||||
static getErrorMessage(responseData, status) {
|
||||
if (status === 'success') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (responseData && responseData.message) {
|
||||
return responseData.message;
|
||||
}
|
||||
|
||||
if (responseData && responseData.error) {
|
||||
return responseData.error;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理请求体数据(移除敏感信息)
|
||||
*/
|
||||
static sanitizeRequestBody(body) {
|
||||
if (!body || typeof body !== 'object') {
|
||||
return body;
|
||||
}
|
||||
|
||||
const sanitized = { ...body };
|
||||
const sensitiveFields = ['password', 'token', 'secret', 'key', 'auth'];
|
||||
|
||||
sensitiveFields.forEach(field => {
|
||||
if (sanitized[field]) {
|
||||
sanitized[field] = '***';
|
||||
}
|
||||
});
|
||||
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理响应数据(移除敏感信息)
|
||||
*/
|
||||
static sanitizeResponseData(data) {
|
||||
if (!data || typeof data !== 'object') {
|
||||
return data;
|
||||
}
|
||||
|
||||
// 只保留基本的响应信息,不保存完整的响应数据
|
||||
return {
|
||||
status: data.status,
|
||||
message: data.message,
|
||||
code: data.code
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端IP地址
|
||||
*/
|
||||
static getClientIP(req) {
|
||||
return req.ip ||
|
||||
req.connection.remoteAddress ||
|
||||
req.socket.remoteAddress ||
|
||||
(req.connection.socket ? req.connection.socket.remoteAddress : null) ||
|
||||
'127.0.0.1';
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建特定操作的日志记录器
|
||||
*/
|
||||
static createLogger(operationType, operationModule, operationContent) {
|
||||
return OperationLogger.logOperation({
|
||||
operation_type: operationType,
|
||||
operation_module: operationModule,
|
||||
operation_content: operationContent
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = OperationLogger;
|
||||
@@ -0,0 +1,177 @@
|
||||
const { DataTypes } = require('sequelize');
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.createTable('supervision_tasks', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
comment: '监管任务ID'
|
||||
},
|
||||
applicationId: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '申请单号',
|
||||
field: 'application_id'
|
||||
},
|
||||
policyId: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '保单编号',
|
||||
field: 'policy_id'
|
||||
},
|
||||
productName: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '产品名称',
|
||||
field: 'product_name'
|
||||
},
|
||||
insurancePeriod: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '保险期间',
|
||||
field: 'insurance_period'
|
||||
},
|
||||
customerName: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '客户姓名',
|
||||
field: 'customer_name'
|
||||
},
|
||||
documentType: {
|
||||
type: DataTypes.STRING(20),
|
||||
allowNull: false,
|
||||
comment: '证件类型',
|
||||
field: 'document_type'
|
||||
},
|
||||
documentNumber: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '证件号码',
|
||||
field: 'document_number'
|
||||
},
|
||||
applicableAmount: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: false,
|
||||
comment: '适用金额',
|
||||
field: 'applicable_amount'
|
||||
},
|
||||
supervisionDataCount: {
|
||||
type: DataTypes.INTEGER,
|
||||
defaultValue: 0,
|
||||
comment: '监管生成数量',
|
||||
field: 'supervision_data_count'
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.ENUM('pending', 'processing', 'completed', 'rejected'),
|
||||
defaultValue: 'pending',
|
||||
comment: '状态: pending-待处理, processing-处理中, completed-已完成, rejected-已拒绝'
|
||||
},
|
||||
taskType: {
|
||||
type: DataTypes.ENUM('new_application', 'task_guidance', 'batch_operation'),
|
||||
allowNull: false,
|
||||
comment: '任务类型: new_application-新增监管任务, task_guidance-任务导入, batch_operation-批量新增',
|
||||
field: 'task_type'
|
||||
},
|
||||
assignedTo: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '分配给用户ID',
|
||||
field: 'assigned_to',
|
||||
references: {
|
||||
model: 'users',
|
||||
key: 'id'
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'SET NULL'
|
||||
},
|
||||
priority: {
|
||||
type: DataTypes.ENUM('low', 'medium', 'high', 'urgent'),
|
||||
defaultValue: 'medium',
|
||||
comment: '优先级'
|
||||
},
|
||||
dueDate: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
comment: '截止日期',
|
||||
field: 'due_date'
|
||||
},
|
||||
completedAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
comment: '完成时间',
|
||||
field: 'completed_at'
|
||||
},
|
||||
remarks: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '备注'
|
||||
},
|
||||
createdBy: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '创建人ID',
|
||||
field: 'created_by',
|
||||
references: {
|
||||
model: 'users',
|
||||
key: 'id'
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT'
|
||||
},
|
||||
updatedBy: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '更新人ID',
|
||||
field: 'updated_by',
|
||||
references: {
|
||||
model: 'users',
|
||||
key: 'id'
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'SET NULL'
|
||||
},
|
||||
createdAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.NOW,
|
||||
field: 'created_at'
|
||||
},
|
||||
updatedAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.NOW,
|
||||
field: 'updated_at'
|
||||
}
|
||||
}, {
|
||||
comment: '监管任务表',
|
||||
charset: 'utf8mb4',
|
||||
collate: 'utf8mb4_unicode_ci'
|
||||
});
|
||||
|
||||
// 添加索引
|
||||
await queryInterface.addIndex('supervision_tasks', ['application_id']);
|
||||
await queryInterface.addIndex('supervision_tasks', ['policy_id']);
|
||||
await queryInterface.addIndex('supervision_tasks', ['customer_name']);
|
||||
await queryInterface.addIndex('supervision_tasks', ['status']);
|
||||
await queryInterface.addIndex('supervision_tasks', ['task_type']);
|
||||
await queryInterface.addIndex('supervision_tasks', ['assigned_to']);
|
||||
await queryInterface.addIndex('supervision_tasks', ['created_by']);
|
||||
await queryInterface.addIndex('supervision_tasks', ['created_at']);
|
||||
|
||||
// 添加唯一索引
|
||||
await queryInterface.addIndex('supervision_tasks', ['application_id'], {
|
||||
unique: true,
|
||||
name: 'unique_application_id'
|
||||
});
|
||||
await queryInterface.addIndex('supervision_tasks', ['policy_id'], {
|
||||
unique: true,
|
||||
name: 'unique_policy_id'
|
||||
});
|
||||
},
|
||||
|
||||
down: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.dropTable('supervision_tasks');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.addColumn('users', 'fixed_token', {
|
||||
type: Sequelize.STRING(255),
|
||||
allowNull: true,
|
||||
unique: true,
|
||||
comment: '用户固定token,用于API访问验证'
|
||||
});
|
||||
},
|
||||
|
||||
down: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.removeColumn('users', 'fixed_token');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,146 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.createTable('operation_logs', {
|
||||
id: {
|
||||
type: Sequelize.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true
|
||||
},
|
||||
user_id: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: 'users',
|
||||
key: 'id'
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'CASCADE'
|
||||
},
|
||||
operation_type: {
|
||||
type: Sequelize.ENUM(
|
||||
'login', // 登录
|
||||
'logout', // 登出
|
||||
'create', // 创建
|
||||
'update', // 更新
|
||||
'delete', // 删除
|
||||
'view', // 查看
|
||||
'export', // 导出
|
||||
'import', // 导入
|
||||
'approve', // 审批
|
||||
'reject', // 拒绝
|
||||
'system_config', // 系统配置
|
||||
'user_manage', // 用户管理
|
||||
'role_manage', // 角色管理
|
||||
'other' // 其他
|
||||
),
|
||||
allowNull: false,
|
||||
comment: '操作类型'
|
||||
},
|
||||
operation_module: {
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '操作模块(如:用户管理、设备管理、预警管理等)'
|
||||
},
|
||||
operation_content: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: false,
|
||||
comment: '操作内容描述'
|
||||
},
|
||||
operation_target: {
|
||||
type: Sequelize.STRING(100),
|
||||
allowNull: true,
|
||||
comment: '操作目标(如:用户ID、设备ID等)'
|
||||
},
|
||||
request_method: {
|
||||
type: Sequelize.ENUM('GET', 'POST', 'PUT', 'DELETE', 'PATCH'),
|
||||
allowNull: true,
|
||||
comment: 'HTTP请求方法'
|
||||
},
|
||||
request_url: {
|
||||
type: Sequelize.STRING(500),
|
||||
allowNull: true,
|
||||
comment: '请求URL'
|
||||
},
|
||||
request_params: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
comment: '请求参数(JSON格式)'
|
||||
},
|
||||
response_status: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '响应状态码'
|
||||
},
|
||||
ip_address: {
|
||||
type: Sequelize.STRING(45),
|
||||
allowNull: true,
|
||||
comment: 'IP地址(支持IPv6)'
|
||||
},
|
||||
user_agent: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
comment: '用户代理信息'
|
||||
},
|
||||
execution_time: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '执行时间(毫秒)'
|
||||
},
|
||||
status: {
|
||||
type: Sequelize.ENUM('success', 'failed', 'error'),
|
||||
defaultValue: 'success',
|
||||
comment: '操作状态'
|
||||
},
|
||||
error_message: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
comment: '错误信息'
|
||||
},
|
||||
created_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
|
||||
},
|
||||
updated_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP')
|
||||
}
|
||||
}, {
|
||||
charset: 'utf8mb4',
|
||||
collate: 'utf8mb4_unicode_ci',
|
||||
comment: '系统操作日志表'
|
||||
});
|
||||
|
||||
// 创建索引
|
||||
await queryInterface.addIndex('operation_logs', ['user_id'], {
|
||||
name: 'idx_operation_logs_user_id'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('operation_logs', ['operation_type'], {
|
||||
name: 'idx_operation_logs_operation_type'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('operation_logs', ['operation_module'], {
|
||||
name: 'idx_operation_logs_operation_module'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('operation_logs', ['created_at'], {
|
||||
name: 'idx_operation_logs_created_at'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('operation_logs', ['status'], {
|
||||
name: 'idx_operation_logs_status'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('operation_logs', ['ip_address'], {
|
||||
name: 'idx_operation_logs_ip_address'
|
||||
});
|
||||
},
|
||||
|
||||
down: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.dropTable('operation_logs');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,181 @@
|
||||
/**
|
||||
* 创建监管任务表的迁移文件
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.createTable('supervisory_tasks', {
|
||||
id: {
|
||||
type: Sequelize.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
allowNull: false,
|
||||
comment: '主键ID'
|
||||
},
|
||||
applicationNumber: {
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
field: 'application_number',
|
||||
comment: '申请单号'
|
||||
},
|
||||
policyNumber: {
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: false,
|
||||
field: 'policy_number',
|
||||
comment: '保单编号'
|
||||
},
|
||||
productName: {
|
||||
type: Sequelize.STRING(100),
|
||||
allowNull: false,
|
||||
field: 'product_name',
|
||||
comment: '产品名称'
|
||||
},
|
||||
insurancePeriod: {
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: false,
|
||||
field: 'insurance_period',
|
||||
comment: '保险周期'
|
||||
},
|
||||
customerName: {
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: false,
|
||||
field: 'customer_name',
|
||||
comment: '客户姓名'
|
||||
},
|
||||
idType: {
|
||||
type: Sequelize.ENUM('身份证', '护照', '军官证', '士兵证', '港澳台居民居住证', '其他'),
|
||||
allowNull: false,
|
||||
defaultValue: '身份证',
|
||||
field: 'id_type',
|
||||
comment: '证件类型'
|
||||
},
|
||||
idNumber: {
|
||||
type: Sequelize.STRING(30),
|
||||
allowNull: false,
|
||||
field: 'id_number',
|
||||
comment: '证件号码'
|
||||
},
|
||||
applicableSupplies: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
field: 'applicable_supplies',
|
||||
comment: '适用生资(JSON格式存储)'
|
||||
},
|
||||
supervisorySuppliesQuantity: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
field: 'supervisory_supplies_quantity',
|
||||
comment: '监管生资数量'
|
||||
},
|
||||
taskStatus: {
|
||||
type: Sequelize.ENUM('待处理', '处理中', '已完成', '已取消'),
|
||||
allowNull: false,
|
||||
defaultValue: '待处理',
|
||||
field: 'task_status',
|
||||
comment: '任务状态'
|
||||
},
|
||||
priority: {
|
||||
type: Sequelize.ENUM('低', '中', '高', '紧急'),
|
||||
allowNull: false,
|
||||
defaultValue: '中',
|
||||
comment: '任务优先级'
|
||||
},
|
||||
assignedTo: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
field: 'assigned_to',
|
||||
comment: '分配给(用户ID)',
|
||||
references: {
|
||||
model: 'users',
|
||||
key: 'id'
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'SET NULL'
|
||||
},
|
||||
dueDate: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: true,
|
||||
field: 'due_date',
|
||||
comment: '截止日期'
|
||||
},
|
||||
completedAt: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: true,
|
||||
field: 'completed_at',
|
||||
comment: '完成时间'
|
||||
},
|
||||
notes: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
comment: '备注信息'
|
||||
},
|
||||
createdBy: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
field: 'created_by',
|
||||
comment: '创建人ID',
|
||||
references: {
|
||||
model: 'users',
|
||||
key: 'id'
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'SET NULL'
|
||||
},
|
||||
updatedBy: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
field: 'updated_by',
|
||||
comment: '更新人ID',
|
||||
references: {
|
||||
model: 'users',
|
||||
key: 'id'
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'SET NULL'
|
||||
},
|
||||
createdAt: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
field: 'created_at',
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
|
||||
},
|
||||
updatedAt: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
field: 'updated_at',
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP')
|
||||
}
|
||||
}, {
|
||||
comment: '监管任务表'
|
||||
});
|
||||
|
||||
// 创建索引
|
||||
await queryInterface.addIndex('supervisory_tasks', ['application_number'], {
|
||||
name: 'idx_application_number'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('supervisory_tasks', ['policy_number'], {
|
||||
name: 'idx_policy_number'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('supervisory_tasks', ['customer_name'], {
|
||||
name: 'idx_customer_name'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('supervisory_tasks', ['task_status'], {
|
||||
name: 'idx_task_status'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('supervisory_tasks', ['created_at'], {
|
||||
name: 'idx_created_at'
|
||||
});
|
||||
},
|
||||
|
||||
down: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.dropTable('supervisory_tasks');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.addColumn('policies', 'policyholder_name', {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true,
|
||||
comment: '投保人姓名'
|
||||
});
|
||||
|
||||
await queryInterface.addColumn('policies', 'insured_name', {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true,
|
||||
comment: '被保险人姓名'
|
||||
});
|
||||
|
||||
await queryInterface.addColumn('policies', 'phone', {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true,
|
||||
comment: '联系电话'
|
||||
});
|
||||
|
||||
await queryInterface.addColumn('policies', 'email', {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true,
|
||||
comment: '邮箱地址'
|
||||
});
|
||||
|
||||
await queryInterface.addColumn('policies', 'address', {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true,
|
||||
comment: '联系地址'
|
||||
});
|
||||
},
|
||||
|
||||
down: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.removeColumn('policies', 'policyholder_name');
|
||||
await queryInterface.removeColumn('policies', 'insured_name');
|
||||
await queryInterface.removeColumn('policies', 'phone');
|
||||
await queryInterface.removeColumn('policies', 'email');
|
||||
await queryInterface.removeColumn('policies', 'address');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,190 @@
|
||||
/**
|
||||
* 创建待安装任务表的迁移文件
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.createTable('installation_tasks', {
|
||||
id: {
|
||||
type: Sequelize.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
allowNull: false,
|
||||
comment: '主键ID'
|
||||
},
|
||||
applicationNumber: {
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: false,
|
||||
field: 'application_number',
|
||||
comment: '申请单号'
|
||||
},
|
||||
policyNumber: {
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: false,
|
||||
field: 'policy_number',
|
||||
comment: '保单编号'
|
||||
},
|
||||
productName: {
|
||||
type: Sequelize.STRING(100),
|
||||
allowNull: false,
|
||||
field: 'product_name',
|
||||
comment: '产品名称'
|
||||
},
|
||||
customerName: {
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: false,
|
||||
field: 'customer_name',
|
||||
comment: '客户姓名'
|
||||
},
|
||||
idType: {
|
||||
type: Sequelize.ENUM('身份证', '护照', '军官证', '士兵证', '港澳台居民居住证', '其他'),
|
||||
allowNull: false,
|
||||
defaultValue: '身份证',
|
||||
field: 'id_type',
|
||||
comment: '证件类型'
|
||||
},
|
||||
idNumber: {
|
||||
type: Sequelize.STRING(30),
|
||||
allowNull: false,
|
||||
field: 'id_number',
|
||||
comment: '证件号码'
|
||||
},
|
||||
livestockSupplyType: {
|
||||
type: Sequelize.STRING(100),
|
||||
allowNull: false,
|
||||
field: 'livestock_supply_type',
|
||||
comment: '养殖生资种类'
|
||||
},
|
||||
pendingDevices: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
field: 'pending_devices',
|
||||
comment: '待安装设备(JSON格式存储)'
|
||||
},
|
||||
installationStatus: {
|
||||
type: Sequelize.ENUM('待安装', '安装中', '已安装', '安装失败', '已取消'),
|
||||
allowNull: false,
|
||||
defaultValue: '待安装',
|
||||
field: 'installation_status',
|
||||
comment: '安装状态'
|
||||
},
|
||||
taskGeneratedTime: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
|
||||
field: 'task_generated_time',
|
||||
comment: '生成安装任务时间'
|
||||
},
|
||||
installationCompletedTime: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: true,
|
||||
field: 'installation_completed_time',
|
||||
comment: '安装完成生效时间'
|
||||
},
|
||||
priority: {
|
||||
type: Sequelize.ENUM('低', '中', '高', '紧急'),
|
||||
allowNull: false,
|
||||
defaultValue: '中',
|
||||
comment: '安装优先级'
|
||||
},
|
||||
assignedTechnician: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
field: 'assigned_technician',
|
||||
comment: '分配的技术员ID',
|
||||
references: {
|
||||
model: 'users',
|
||||
key: 'id'
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'SET NULL'
|
||||
},
|
||||
installationAddress: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
field: 'installation_address',
|
||||
comment: '安装地址'
|
||||
},
|
||||
contactPhone: {
|
||||
type: Sequelize.STRING(20),
|
||||
allowNull: true,
|
||||
field: 'contact_phone',
|
||||
comment: '联系电话'
|
||||
},
|
||||
remarks: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
comment: '备注信息'
|
||||
},
|
||||
createdBy: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
field: 'created_by',
|
||||
comment: '创建人ID',
|
||||
references: {
|
||||
model: 'users',
|
||||
key: 'id'
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'SET NULL'
|
||||
},
|
||||
updatedBy: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
field: 'updated_by',
|
||||
comment: '更新人ID',
|
||||
references: {
|
||||
model: 'users',
|
||||
key: 'id'
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'SET NULL'
|
||||
},
|
||||
createdAt: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
field: 'created_at',
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
|
||||
},
|
||||
updatedAt: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
field: 'updated_at',
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP')
|
||||
}
|
||||
}, {
|
||||
comment: '待安装任务表'
|
||||
});
|
||||
|
||||
// 创建索引
|
||||
await queryInterface.addIndex('installation_tasks', ['application_number'], {
|
||||
name: 'idx_application_number'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('installation_tasks', ['policy_number'], {
|
||||
name: 'idx_policy_number'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('installation_tasks', ['customer_name'], {
|
||||
name: 'idx_customer_name'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('installation_tasks', ['installation_status'], {
|
||||
name: 'idx_installation_status'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('installation_tasks', ['task_generated_time'], {
|
||||
name: 'idx_task_generated_time'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('installation_tasks', ['installation_completed_time'], {
|
||||
name: 'idx_installation_completed_time'
|
||||
});
|
||||
},
|
||||
|
||||
down: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.dropTable('installation_tasks');
|
||||
}
|
||||
};
|
||||
143
insurance_backend/models/Claim.js
Normal file
@@ -0,0 +1,143 @@
|
||||
const { DataTypes } = require('sequelize');
|
||||
const { sequelize } = require('../config/database');
|
||||
|
||||
const Claim = sequelize.define('Claim', {
|
||||
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: 'policies',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
customer_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '客户ID',
|
||||
references: {
|
||||
model: 'users',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
claim_amount: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: false,
|
||||
comment: '理赔金额',
|
||||
validate: {
|
||||
min: {
|
||||
args: [0],
|
||||
msg: '理赔金额不能小于0'
|
||||
}
|
||||
}
|
||||
},
|
||||
claim_date: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
comment: '理赔发生日期'
|
||||
},
|
||||
incident_description: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false,
|
||||
comment: '事故描述',
|
||||
validate: {
|
||||
notEmpty: {
|
||||
msg: '事故描述不能为空'
|
||||
}
|
||||
}
|
||||
},
|
||||
claim_status: {
|
||||
type: DataTypes.ENUM('pending', 'approved', 'rejected', 'processing', 'paid'),
|
||||
allowNull: false,
|
||||
defaultValue: 'pending',
|
||||
comment: '理赔状态:pending-待审核,approved-已批准,rejected-已拒绝,processing-处理中,paid-已支付'
|
||||
},
|
||||
review_notes: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '审核备注'
|
||||
},
|
||||
reviewer_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '审核人ID',
|
||||
references: {
|
||||
model: 'users',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
review_date: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
comment: '审核日期'
|
||||
},
|
||||
payment_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'
|
||||
},
|
||||
updated_by: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '更新人ID'
|
||||
}
|
||||
}, {
|
||||
tableName: 'claims',
|
||||
timestamps: true,
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at',
|
||||
indexes: [
|
||||
{
|
||||
name: 'idx_claim_no',
|
||||
fields: ['claim_no'],
|
||||
unique: true
|
||||
},
|
||||
{
|
||||
name: 'idx_claim_policy',
|
||||
fields: ['policy_id']
|
||||
},
|
||||
{
|
||||
name: 'idx_claim_customer',
|
||||
fields: ['customer_id']
|
||||
},
|
||||
{
|
||||
name: 'idx_claim_status',
|
||||
fields: ['claim_status']
|
||||
},
|
||||
{
|
||||
name: 'idx_claim_date',
|
||||
fields: ['claim_date']
|
||||
}
|
||||
],
|
||||
comment: '理赔表'
|
||||
});
|
||||
|
||||
module.exports = Claim;
|
||||
|
||||
87
insurance_backend/models/Device.js
Normal file
@@ -0,0 +1,87 @@
|
||||
const { DataTypes } = require('sequelize');
|
||||
const { sequelize } = require('../config/database');
|
||||
|
||||
/**
|
||||
* 设备模型
|
||||
* 用于管理保险相关的设备信息
|
||||
*/
|
||||
const Device = sequelize.define('Device', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
comment: '设备ID'
|
||||
},
|
||||
device_number: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
comment: '设备编号'
|
||||
},
|
||||
device_name: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '设备名称'
|
||||
},
|
||||
device_type: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '设备类型'
|
||||
},
|
||||
device_model: {
|
||||
type: DataTypes.STRING(100),
|
||||
comment: '设备型号'
|
||||
},
|
||||
manufacturer: {
|
||||
type: DataTypes.STRING(100),
|
||||
comment: '制造商'
|
||||
},
|
||||
installation_location: {
|
||||
type: DataTypes.STRING(200),
|
||||
comment: '安装位置'
|
||||
},
|
||||
installation_date: {
|
||||
type: DataTypes.DATE,
|
||||
comment: '安装日期'
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.ENUM('normal', 'warning', 'error', 'offline'),
|
||||
defaultValue: 'normal',
|
||||
comment: '设备状态:normal-正常,warning-警告,error-故障,offline-离线'
|
||||
},
|
||||
farm_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
comment: '所属养殖场ID'
|
||||
},
|
||||
barn_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
comment: '所属栏舍ID'
|
||||
},
|
||||
created_by: {
|
||||
type: DataTypes.INTEGER,
|
||||
comment: '创建人ID'
|
||||
},
|
||||
updated_by: {
|
||||
type: DataTypes.INTEGER,
|
||||
comment: '更新人ID'
|
||||
},
|
||||
created_at: {
|
||||
type: DataTypes.DATE,
|
||||
defaultValue: DataTypes.NOW,
|
||||
comment: '创建时间'
|
||||
},
|
||||
updated_at: {
|
||||
type: DataTypes.DATE,
|
||||
defaultValue: DataTypes.NOW,
|
||||
comment: '更新时间'
|
||||
}
|
||||
}, {
|
||||
tableName: 'devices',
|
||||
timestamps: true,
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at',
|
||||
comment: '设备信息表'
|
||||
});
|
||||
|
||||
module.exports = Device;
|
||||
|
||||
218
insurance_backend/models/LivestockPolicy.js
Normal file
@@ -0,0 +1,218 @@
|
||||
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;
|
||||
|
||||
102
insurance_backend/models/LivestockType.js
Normal file
@@ -0,0 +1,102 @@
|
||||
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;
|
||||
|
||||
85
insurance_backend/models/Menu.js
Normal file
@@ -0,0 +1,85 @@
|
||||
const { sequelize } = require('../config/database');
|
||||
const { DataTypes } = require('sequelize');
|
||||
|
||||
const Menu = sequelize.define('Menu', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
validate: {
|
||||
len: [2, 50]
|
||||
}
|
||||
},
|
||||
key: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
validate: {
|
||||
len: [2, 50]
|
||||
}
|
||||
},
|
||||
path: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
validate: {
|
||||
len: [1, 100]
|
||||
}
|
||||
},
|
||||
icon: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true
|
||||
},
|
||||
parent_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'menus',
|
||||
key: 'id'
|
||||
},
|
||||
defaultValue: null
|
||||
},
|
||||
component: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true
|
||||
},
|
||||
order: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.ENUM('active', 'inactive'),
|
||||
defaultValue: 'active'
|
||||
},
|
||||
show: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: true
|
||||
}
|
||||
}, {
|
||||
tableName: 'menus',
|
||||
timestamps: true,
|
||||
underscored: true,
|
||||
indexes: [
|
||||
{ fields: ['key'] },
|
||||
{ fields: ['parent_id'] },
|
||||
{ fields: ['status'] },
|
||||
{ fields: ['order'] }
|
||||
]
|
||||
});
|
||||
|
||||
// 设置自关联
|
||||
Menu.hasMany(Menu, {
|
||||
as: 'children',
|
||||
foreignKey: 'parent_id'
|
||||
});
|
||||
|
||||
Menu.belongsTo(Menu, {
|
||||
as: 'parent',
|
||||
foreignKey: 'parent_id'
|
||||
});
|
||||
|
||||
module.exports = Menu;
|
||||
43
insurance_backend/models/MenuPermission.js
Normal file
@@ -0,0 +1,43 @@
|
||||
const { DataTypes } = require('sequelize');
|
||||
const { sequelize } = require('../config/database');
|
||||
|
||||
const MenuPermission = sequelize.define('MenuPermission', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
comment: '关联ID'
|
||||
},
|
||||
menu_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '菜单ID'
|
||||
},
|
||||
permission_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '权限ID'
|
||||
},
|
||||
required: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: true,
|
||||
comment: '是否必需权限'
|
||||
}
|
||||
}, {
|
||||
tableName: 'menu_permissions',
|
||||
timestamps: true,
|
||||
underscored: true,
|
||||
indexes: [
|
||||
{
|
||||
fields: ['menu_id', 'permission_id'],
|
||||
unique: true,
|
||||
name: 'uk_menu_permission'
|
||||
},
|
||||
{ fields: ['menu_id'] },
|
||||
{ fields: ['permission_id'] }
|
||||
]
|
||||
});
|
||||
|
||||
module.exports = MenuPermission;
|
||||
|
||||
271
insurance_backend/models/OperationLog.js
Normal file
@@ -0,0 +1,271 @@
|
||||
const { DataTypes } = require('sequelize');
|
||||
const { sequelize } = require('../config/database');
|
||||
|
||||
const OperationLog = sequelize.define('OperationLog', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true
|
||||
},
|
||||
user_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: 'users',
|
||||
key: 'id'
|
||||
},
|
||||
comment: '操作用户ID'
|
||||
},
|
||||
operation_type: {
|
||||
type: DataTypes.ENUM(
|
||||
'login', // 登录
|
||||
'logout', // 登出
|
||||
'create', // 创建
|
||||
'update', // 更新
|
||||
'delete', // 删除
|
||||
'view', // 查看
|
||||
'export', // 导出
|
||||
'import', // 导入
|
||||
'approve', // 审批
|
||||
'reject', // 拒绝
|
||||
'system_config', // 系统配置
|
||||
'user_manage', // 用户管理
|
||||
'role_manage', // 角色管理
|
||||
'other' // 其他
|
||||
),
|
||||
allowNull: false,
|
||||
comment: '操作类型'
|
||||
},
|
||||
operation_module: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '操作模块(如:用户管理、设备管理、预警管理等)'
|
||||
},
|
||||
operation_content: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false,
|
||||
comment: '操作内容描述'
|
||||
},
|
||||
operation_target: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
comment: '操作目标(如:用户ID、设备ID等)'
|
||||
},
|
||||
request_method: {
|
||||
type: DataTypes.ENUM('GET', 'POST', 'PUT', 'DELETE', 'PATCH'),
|
||||
allowNull: true,
|
||||
comment: 'HTTP请求方法'
|
||||
},
|
||||
request_url: {
|
||||
type: DataTypes.STRING(500),
|
||||
allowNull: true,
|
||||
comment: '请求URL'
|
||||
},
|
||||
request_params: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '请求参数(JSON格式)',
|
||||
get() {
|
||||
const value = this.getDataValue('request_params');
|
||||
return value ? JSON.parse(value) : null;
|
||||
},
|
||||
set(value) {
|
||||
this.setDataValue('request_params', value ? JSON.stringify(value) : null);
|
||||
}
|
||||
},
|
||||
response_status: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '响应状态码'
|
||||
},
|
||||
ip_address: {
|
||||
type: DataTypes.STRING(45),
|
||||
allowNull: true,
|
||||
comment: 'IP地址(支持IPv6)'
|
||||
},
|
||||
user_agent: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '用户代理信息'
|
||||
},
|
||||
execution_time: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '执行时间(毫秒)'
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.ENUM('success', 'failed', 'error'),
|
||||
defaultValue: 'success',
|
||||
comment: '操作状态'
|
||||
},
|
||||
error_message: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '错误信息'
|
||||
}
|
||||
}, {
|
||||
tableName: 'operation_logs',
|
||||
timestamps: true,
|
||||
underscored: true,
|
||||
indexes: [
|
||||
{ fields: ['user_id'] },
|
||||
{ fields: ['operation_type'] },
|
||||
{ fields: ['operation_module'] },
|
||||
{ fields: ['created_at'] },
|
||||
{ fields: ['status'] },
|
||||
{ fields: ['ip_address'] }
|
||||
]
|
||||
});
|
||||
|
||||
// 定义关联关系
|
||||
OperationLog.associate = function(models) {
|
||||
// 操作日志属于用户
|
||||
OperationLog.belongsTo(models.User, {
|
||||
foreignKey: 'user_id',
|
||||
as: 'user',
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE'
|
||||
});
|
||||
};
|
||||
|
||||
// 静态方法:记录操作日志
|
||||
OperationLog.logOperation = async function(logData) {
|
||||
try {
|
||||
const log = await this.create({
|
||||
user_id: logData.userId,
|
||||
operation_type: logData.operationType,
|
||||
operation_module: logData.operationModule,
|
||||
operation_content: logData.operationContent,
|
||||
operation_target: logData.operationTarget,
|
||||
request_method: logData.requestMethod,
|
||||
request_url: logData.requestUrl,
|
||||
request_params: logData.requestParams,
|
||||
response_status: logData.responseStatus,
|
||||
ip_address: logData.ipAddress,
|
||||
user_agent: logData.userAgent,
|
||||
execution_time: logData.executionTime,
|
||||
status: logData.status || 'success',
|
||||
error_message: logData.errorMessage
|
||||
});
|
||||
return log;
|
||||
} catch (error) {
|
||||
console.error('记录操作日志失败:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// 静态方法:获取操作日志列表
|
||||
OperationLog.getLogsList = async function(options = {}) {
|
||||
const {
|
||||
page = 1,
|
||||
limit = 20,
|
||||
userId,
|
||||
operationType,
|
||||
operationModule,
|
||||
status,
|
||||
startDate,
|
||||
endDate,
|
||||
keyword
|
||||
} = options;
|
||||
|
||||
const where = {};
|
||||
|
||||
// 构建查询条件
|
||||
if (userId) where.user_id = userId;
|
||||
if (operationType) where.operation_type = operationType;
|
||||
if (operationModule) where.operation_module = operationModule;
|
||||
if (status) where.status = status;
|
||||
|
||||
// 时间范围查询
|
||||
if (startDate || endDate) {
|
||||
where.created_at = {};
|
||||
if (startDate) where.created_at[sequelize.Op.gte] = new Date(startDate);
|
||||
if (endDate) where.created_at[sequelize.Op.lte] = new Date(endDate);
|
||||
}
|
||||
|
||||
// 关键词搜索
|
||||
if (keyword) {
|
||||
where[sequelize.Op.or] = [
|
||||
{ operation_content: { [sequelize.Op.like]: `%${keyword}%` } },
|
||||
{ operation_target: { [sequelize.Op.like]: `%${keyword}%` } }
|
||||
];
|
||||
}
|
||||
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
const result = await this.findAndCountAll({
|
||||
where,
|
||||
include: [{
|
||||
model: sequelize.models.User,
|
||||
as: 'user',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
}],
|
||||
order: [['created_at', 'DESC']],
|
||||
limit: parseInt(limit),
|
||||
offset: parseInt(offset)
|
||||
});
|
||||
|
||||
return {
|
||||
logs: result.rows,
|
||||
total: result.count,
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
totalPages: Math.ceil(result.count / limit)
|
||||
};
|
||||
};
|
||||
|
||||
// 静态方法:获取操作统计
|
||||
OperationLog.getOperationStats = async function(options = {}) {
|
||||
const { startDate, endDate, userId } = options;
|
||||
|
||||
const where = {};
|
||||
if (userId) where.user_id = userId;
|
||||
|
||||
if (startDate || endDate) {
|
||||
where.created_at = {};
|
||||
if (startDate) where.created_at[sequelize.Op.gte] = new Date(startDate);
|
||||
if (endDate) where.created_at[sequelize.Op.lte] = new Date(endDate);
|
||||
}
|
||||
|
||||
// 按操作类型统计
|
||||
const typeStats = await this.findAll({
|
||||
where,
|
||||
attributes: [
|
||||
'operation_type',
|
||||
[sequelize.fn('COUNT', sequelize.col('id')), 'count']
|
||||
],
|
||||
group: ['operation_type'],
|
||||
raw: true
|
||||
});
|
||||
|
||||
// 按操作模块统计
|
||||
const moduleStats = await this.findAll({
|
||||
where,
|
||||
attributes: [
|
||||
'operation_module',
|
||||
[sequelize.fn('COUNT', sequelize.col('id')), 'count']
|
||||
],
|
||||
group: ['operation_module'],
|
||||
raw: true
|
||||
});
|
||||
|
||||
// 按状态统计
|
||||
const statusStats = await this.findAll({
|
||||
where,
|
||||
attributes: [
|
||||
'status',
|
||||
[sequelize.fn('COUNT', sequelize.col('id')), 'count']
|
||||
],
|
||||
group: ['status'],
|
||||
raw: true
|
||||
});
|
||||
|
||||
return {
|
||||
typeStats,
|
||||
moduleStats,
|
||||
statusStats
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = OperationLog;
|
||||
|
||||
94
insurance_backend/models/Permission.js
Normal file
@@ -0,0 +1,94 @@
|
||||
const { DataTypes } = require('sequelize');
|
||||
const { sequelize } = require('../config/database');
|
||||
|
||||
const Permission = sequelize.define('Permission', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
comment: '权限ID'
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '权限名称',
|
||||
validate: {
|
||||
len: [2, 100]
|
||||
}
|
||||
},
|
||||
code: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
comment: '权限代码',
|
||||
validate: {
|
||||
len: [2, 100],
|
||||
is: /^[a-zA-Z0-9_:]+$/ // 只允许字母、数字、下划线和冒号
|
||||
}
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '权限描述'
|
||||
},
|
||||
module: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '所属模块',
|
||||
validate: {
|
||||
len: [2, 50]
|
||||
}
|
||||
},
|
||||
type: {
|
||||
type: DataTypes.ENUM('menu', 'operation'),
|
||||
allowNull: false,
|
||||
defaultValue: 'operation',
|
||||
comment: '权限类型:menu-菜单权限,operation-操作权限'
|
||||
},
|
||||
parent_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '父权限ID'
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.ENUM('active', 'inactive'),
|
||||
allowNull: false,
|
||||
defaultValue: 'active',
|
||||
comment: '状态'
|
||||
},
|
||||
sort_order: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '排序'
|
||||
}
|
||||
}, {
|
||||
tableName: 'permissions',
|
||||
timestamps: true,
|
||||
underscored: true,
|
||||
indexes: [
|
||||
{ fields: ['code'], unique: true },
|
||||
{ fields: ['module'] },
|
||||
{ fields: ['type'] },
|
||||
{ fields: ['parent_id'] },
|
||||
{ fields: ['status'] }
|
||||
]
|
||||
});
|
||||
|
||||
// 定义自关联关系
|
||||
Permission.hasMany(Permission, {
|
||||
as: 'children',
|
||||
foreignKey: 'parent_id',
|
||||
onDelete: 'SET NULL',
|
||||
onUpdate: 'CASCADE'
|
||||
});
|
||||
|
||||
Permission.belongsTo(Permission, {
|
||||
as: 'parent',
|
||||
foreignKey: 'parent_id',
|
||||
onDelete: 'SET NULL',
|
||||
onUpdate: 'CASCADE'
|
||||
});
|
||||
|
||||
module.exports = Permission;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const { DataTypes } = require('sequelize');
|
||||
const { sequelize } = require('../config/database');
|
||||
const bcrypt = require('bcrypt');
|
||||
const bcrypt = require('bcryptjs');
|
||||
|
||||
const User = sequelize.define('User', {
|
||||
id: {
|
||||
|
||||
71
insurance_backend/nginx-ad.ningmuyun.com.conf
Normal file
@@ -0,0 +1,71 @@
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name ad.ningmuyun.com;
|
||||
|
||||
# SSL证书配置(需要替换为实际的证书路径)
|
||||
ssl_certificate /etc/ssl/certs/ad.ningmuyun.com.crt;
|
||||
ssl_certificate_key /etc/ssl/private/ad.ningmuyun.com.key;
|
||||
|
||||
# SSL配置
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;
|
||||
ssl_prefer_server_ciphers off;
|
||||
|
||||
# 前端静态文件
|
||||
location /insurance/ {
|
||||
alias /var/www/insurance-admin-system/dist/;
|
||||
try_files $uri $uri/ /insurance/index.html;
|
||||
|
||||
# 静态资源缓存
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
}
|
||||
|
||||
# 后端API代理
|
||||
location /insurance/api/ {
|
||||
proxy_pass http://127.0.0.1:3000/api/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
|
||||
# CORS headers
|
||||
add_header Access-Control-Allow-Origin https://ad.ningmuyun.com;
|
||||
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
|
||||
add_header Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With";
|
||||
add_header Access-Control-Allow-Credentials true;
|
||||
|
||||
# 处理预检请求
|
||||
if ($request_method = 'OPTIONS') {
|
||||
add_header Access-Control-Allow-Origin https://ad.ningmuyun.com;
|
||||
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
|
||||
add_header Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With";
|
||||
add_header Access-Control-Allow-Credentials true;
|
||||
add_header Content-Length 0;
|
||||
add_header Content-Type text/plain;
|
||||
return 204;
|
||||
}
|
||||
}
|
||||
|
||||
# 健康检查
|
||||
location /health {
|
||||
proxy_pass http://127.0.0.1:3000/health;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
|
||||
# HTTP重定向到HTTPS
|
||||
server {
|
||||
listen 80;
|
||||
server_name ad.ningmuyun.com;
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
33
insurance_backend/nginx.conf
Normal file
@@ -0,0 +1,33 @@
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name 49.51.70.206;
|
||||
|
||||
# SSL证书配置(需要申请SSL证书)
|
||||
ssl_certificate /path/to/your/certificate.crt;
|
||||
ssl_certificate_key /path/to/your/private.key;
|
||||
|
||||
# 代理到后端API
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:3000/api/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# 代理到前端
|
||||
location /insurance/ {
|
||||
proxy_pass http://127.0.0.1:3001/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
|
||||
# HTTP重定向到HTTPS
|
||||
server {
|
||||
listen 80;
|
||||
server_name 49.51.70.206;
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
29552
insurance_backend/package-lock.json
generated
62
insurance_backend/package.json
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"name": "insurance_backend",
|
||||
"version": "1.0.0",
|
||||
"description": "保险端口后端服务",
|
||||
"main": "src/app.js",
|
||||
"scripts": {
|
||||
"start": "node src/app.js",
|
||||
"dev": "nodemon src/app.js",
|
||||
"test": "jest",
|
||||
"migrate": "npx sequelize-cli db:migrate",
|
||||
"seed": "npx sequelize-cli db:seed:all",
|
||||
"migrate:undo": "npx sequelize-cli db:migrate:undo",
|
||||
"seed:undo": "npx sequelize-cli db:seed:undo:all"
|
||||
},
|
||||
"keywords": [
|
||||
"insurance",
|
||||
"backend",
|
||||
"nodejs",
|
||||
"express"
|
||||
],
|
||||
"author": "Insurance Team",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"axios": "^1.12.2",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.0.3",
|
||||
"exceljs": "^4.4.0",
|
||||
"express": "^4.18.2",
|
||||
"express-rate-limit": "^8.1.0",
|
||||
"helmet": "^7.1.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.29.4",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"mysql2": "^2.3.3",
|
||||
"redis": "^4.5.0",
|
||||
"sanitize-html": "^2.8.1",
|
||||
"sequelize": "^6.29.0",
|
||||
"swagger-jsdoc": "^6.2.8",
|
||||
"swagger-ui-express": "^5.0.1",
|
||||
"validator": "^13.9.0",
|
||||
"winston": "^3.8.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^8.32.0",
|
||||
"eslint-config-standard": "^17.0.0",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"jest": "^29.5.0",
|
||||
"nodemon": "^2.0.20",
|
||||
"puppeteer": "^19.11.1",
|
||||
"sequelize-cli": "^6.6.0",
|
||||
"supertest": "^6.3.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "16.20.2",
|
||||
"npm": ">=8.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
60
insurance_backend/restart-backend.sh
Normal file
@@ -0,0 +1,60 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 重启保险后端服务脚本
|
||||
|
||||
echo "🔄 重启保险后端服务..."
|
||||
|
||||
# 设置工作目录
|
||||
cd /var/www/insurance-backend
|
||||
|
||||
# 停止现有服务
|
||||
echo "🛑 停止现有服务..."
|
||||
pm2 stop insurance-backend 2>/dev/null || true
|
||||
pkill -f "node.*src/app.js" 2>/dev/null || true
|
||||
|
||||
# 等待进程完全停止
|
||||
sleep 3
|
||||
|
||||
# 检查端口是否被释放
|
||||
if lsof -Pi :3000 -sTCP:LISTEN -t >/dev/null ; then
|
||||
echo "⚠️ 端口3000仍被占用,强制停止..."
|
||||
sudo fuser -k 3000/tcp 2>/dev/null || true
|
||||
sleep 2
|
||||
fi
|
||||
|
||||
# 启动服务
|
||||
echo "🚀 启动服务..."
|
||||
pm2 start ecosystem.config.js --env production
|
||||
|
||||
# 等待服务启动
|
||||
echo "⏳ 等待服务启动..."
|
||||
sleep 5
|
||||
|
||||
# 检查服务状态
|
||||
echo "🔍 检查服务状态..."
|
||||
pm2 status
|
||||
|
||||
# 测试API
|
||||
echo "🧪 测试API..."
|
||||
for i in {1..3}; do
|
||||
if curl -s http://127.0.0.1:3000/health > /dev/null; then
|
||||
echo "✅ API测试成功"
|
||||
break
|
||||
else
|
||||
echo "⏳ 等待API启动... ($i/3)"
|
||||
sleep 2
|
||||
fi
|
||||
done
|
||||
|
||||
# 保存PM2配置
|
||||
pm2 save
|
||||
|
||||
echo ""
|
||||
echo "✅ 服务重启完成!"
|
||||
echo "📋 服务信息:"
|
||||
echo " 服务名称: insurance-backend"
|
||||
echo " 端口: 3000"
|
||||
echo " 环境: production"
|
||||
echo ""
|
||||
echo "🔍 查看日志: pm2 logs insurance-backend"
|
||||
echo "📊 监控面板: pm2 monit"
|
||||
75
insurance_backend/run-migration.js
Normal file
@@ -0,0 +1,75 @@
|
||||
const { sequelize } = require('./config/database.js');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
async function runMigration() {
|
||||
try {
|
||||
console.log('开始运行数据库迁移...');
|
||||
|
||||
// 测试数据库连接
|
||||
await sequelize.authenticate();
|
||||
console.log('✅ 数据库连接成功');
|
||||
|
||||
// 获取所有迁移文件
|
||||
const migrationsPath = path.join(__dirname, 'migrations');
|
||||
const migrationFiles = fs.readdirSync(migrationsPath)
|
||||
.filter(file => file.endsWith('.js'))
|
||||
.sort();
|
||||
|
||||
console.log(`找到 ${migrationFiles.length} 个迁移文件`);
|
||||
|
||||
// 确保 SequelizeMeta 表存在
|
||||
await sequelize.query(`
|
||||
CREATE TABLE IF NOT EXISTS \`SequelizeMeta\` (
|
||||
\`name\` varchar(255) COLLATE utf8mb3_unicode_ci NOT NULL,
|
||||
PRIMARY KEY (\`name\`),
|
||||
UNIQUE KEY \`name\` (\`name\`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
|
||||
`);
|
||||
|
||||
// 检查哪些迁移已经运行过
|
||||
const [executedMigrations] = await sequelize.query(
|
||||
'SELECT name FROM SequelizeMeta ORDER BY name'
|
||||
);
|
||||
const executedNames = executedMigrations.map(row => row.name);
|
||||
|
||||
// 运行未执行的迁移
|
||||
for (const file of migrationFiles) {
|
||||
if (!executedNames.includes(file)) {
|
||||
console.log(`正在运行迁移: ${file}`);
|
||||
|
||||
try {
|
||||
const migration = require(path.join(migrationsPath, file));
|
||||
await migration.up(sequelize.getQueryInterface(), sequelize.constructor);
|
||||
|
||||
// 记录迁移已执行
|
||||
await sequelize.query(
|
||||
'INSERT INTO SequelizeMeta (name) VALUES (?)',
|
||||
{ replacements: [file] }
|
||||
);
|
||||
|
||||
console.log(`✅ 迁移 ${file} 执行成功`);
|
||||
} catch (error) {
|
||||
console.error(`❌ 迁移 ${file} 执行失败:`, error);
|
||||
throw error;
|
||||
}
|
||||
} else {
|
||||
console.log(`⏭️ 迁移 ${file} 已执行,跳过`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('🎉 所有迁移执行完成!');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 迁移执行失败:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
// 运行迁移
|
||||
runMigration().catch(error => {
|
||||
console.error('迁移过程中发生错误:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -19,11 +19,42 @@ const PORT = process.env.PORT || 3000;
|
||||
// 安全中间件
|
||||
app.use(helmet());
|
||||
app.use(cors({
|
||||
origin: [
|
||||
process.env.FRONTEND_URL || 'http://localhost:3001',
|
||||
'http://localhost:3002'
|
||||
origin: function (origin, callback) {
|
||||
// 允许的域名列表
|
||||
const allowedOrigins = [
|
||||
process.env.FRONTEND_URL || 'http://localhost:3001',
|
||||
'http://127.0.0.1:3001',
|
||||
'http://localhost:3002',
|
||||
'http://127.0.0.1:3002',
|
||||
'https://ad.ningmuyun.com',
|
||||
'https://www.ningmuyun.com',
|
||||
'https://ningmuyun.com'
|
||||
];
|
||||
|
||||
// 允许没有origin的请求(如移动应用、Postman等)
|
||||
if (!origin) return callback(null, true);
|
||||
|
||||
if (allowedOrigins.indexOf(origin) !== -1) {
|
||||
callback(null, true);
|
||||
} else {
|
||||
console.log('CORS blocked origin:', origin);
|
||||
callback(new Error('Not allowed by CORS'));
|
||||
}
|
||||
},
|
||||
credentials: true,
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH'],
|
||||
allowedHeaders: [
|
||||
'Content-Type',
|
||||
'Authorization',
|
||||
'X-Requested-With',
|
||||
'Accept',
|
||||
'Cache-Control',
|
||||
'Pragma',
|
||||
'Origin',
|
||||
'Access-Control-Request-Method',
|
||||
'Access-Control-Request-Headers'
|
||||
],
|
||||
credentials: true
|
||||
optionsSuccessStatus: 200
|
||||
}));
|
||||
|
||||
// 速率限制 - 开发环境下放宽限制
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
# 测试保险申请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
|
||||
@@ -1 +0,0 @@
|
||||
UPDATE users SET password = '$2b$12$8MenY1QAy0piFa64iM6k4.85TQAPjGEJRrNWV6g4C0gMP5/LmpEHe' WHERE username = 'admin';
|
||||
8
insurance_datav/.editorconfig
Normal file
@@ -0,0 +1,8 @@
|
||||
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}]
|
||||
charset = utf-8
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
end_of_line = lf
|
||||
max_line_length = 100
|
||||
1
insurance_datav/.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
* text=auto eol=lf
|
||||
30
insurance_datav/.gitignore
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
*.tsbuildinfo
|
||||
55
insurance_datav/DASHBOARD_COLOR_UPDATE.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Dashboard颜色修改报告
|
||||
|
||||
## 修改概述
|
||||
本次修改将数据大屏Dashboard组件的颜色方案统一调整为基于Map3D组件的基础颜色,移除了所有渐变色,使用单一颜色方案。
|
||||
|
||||
## 颜色方案
|
||||
基于Map3D组件的以下颜色:
|
||||
|
||||
1. **主色调**: `#84acf0` - 地图顶部材质颜色
|
||||
2. **辅助色**: `#7af4ff` - 地图光源颜色
|
||||
3. **强调色**: `#00F6FF` - 养殖场标签颜色
|
||||
4. **深色**: `#123024` - 地图侧面材质颜色
|
||||
5. **背景色**: `#0c1426` - 深色背景
|
||||
|
||||
## 主要修改内容
|
||||
|
||||
### 1. 背景和容器
|
||||
- 移除了复杂的渐变背景,使用纯色 `#0c1426`
|
||||
- 简化背景装饰效果,只保留基础的径向渐变
|
||||
|
||||
### 2. 顶部标题栏
|
||||
- 标题颜色改为 `#84acf0`
|
||||
- 移除彩虹渐变文字效果
|
||||
- 边框和装饰线使用单色
|
||||
|
||||
### 3. 面板和卡片
|
||||
- 所有面板背景使用 `rgba(132, 172, 240, 0.05)`
|
||||
- 边框颜色统一为 `rgba(132, 172, 240, 0.3)`
|
||||
- 移除顶部装饰渐变线,使用单色
|
||||
|
||||
### 4. 数据展示
|
||||
- 统计数值颜色改为 `#84acf0`
|
||||
- 进度条背景使用 `rgba(18, 48, 36, 0.3)`
|
||||
- 进度条填充使用单色 `#84acf0`
|
||||
|
||||
### 5. 图表配置
|
||||
- 饼图使用四种基础颜色:`#84acf0`, `#7af4ff`, `#00F6FF`, `#123024`
|
||||
- 柱状图使用单色 `#84acf0`
|
||||
- 折线图使用 `#7af4ff` 或其他单色
|
||||
- 移除所有LinearGradient渐变效果
|
||||
|
||||
## 修改文件
|
||||
- `d:\1212\nxzhihui\website\src\components\Dashboard.vue`
|
||||
|
||||
## 验证方法
|
||||
1. 启动website项目: `cd website && npm run dev`
|
||||
2. 访问数据大屏页面
|
||||
3. 检查颜色是否与Map3D组件协调统一
|
||||
4. 确认没有渐变色残留
|
||||
|
||||
## 注意事项
|
||||
- 保持了原有的视觉层次和可读性
|
||||
- 所有交互效果和动画保持不变
|
||||
- 响应式布局保持不变
|
||||
- 只修改了颜色方案,未改变布局结构
|
||||
238
insurance_datav/README.md
Normal file
@@ -0,0 +1,238 @@
|
||||
# 宁夏智慧畜牧大数据平台 - 官网展示系统
|
||||
|
||||
## 项目概述
|
||||
|
||||
宁夏智慧畜牧大数据平台官网是一个现代化的数据可视化展示系统,基于Vue 3和Three.js技术栈构建。该系统提供3D地图展示、实时数据监控、养殖场管理和数据大屏可视化等功能,为宁夏地区的智慧畜牧业发展提供数字化支撑。
|
||||
|
||||
## 核心功能
|
||||
|
||||
### 🗺️ 3D地图可视化
|
||||
- 基于Three.js的3D地球和地图展示
|
||||
- 宁夏回族自治区地理边界可视化
|
||||
- 养殖场地理位置标注和信息展示
|
||||
- 交互式地图操作(缩放、旋转、点击)
|
||||
|
||||
### 📊 数据大屏展示
|
||||
- 实时数据监控大屏
|
||||
- 多维度数据图表展示
|
||||
- 养殖业态势感知
|
||||
- 预警信息集中展示
|
||||
|
||||
### 🏭 养殖场管理
|
||||
- 养殖场基本信息展示
|
||||
- 养殖规模和类型统计
|
||||
- 联系方式和建立时间管理
|
||||
- 点击弹窗详情查看
|
||||
|
||||
### 🎨 现代化UI设计
|
||||
- 科技感十足的深色主题
|
||||
- 流畅的动画效果
|
||||
- 响应式布局设计
|
||||
- 多页面导航系统
|
||||
|
||||
## 技术栈
|
||||
|
||||
### 前端框架
|
||||
- **Vue 3** - 渐进式JavaScript框架
|
||||
- **Vite** - 现代化构建工具
|
||||
- **Vue Router** - 官方路由管理器
|
||||
- **Pinia** - 状态管理库
|
||||
|
||||
### 3D可视化
|
||||
- **Three.js** - 3D图形库
|
||||
- **CSS2DRenderer** - 2D标签渲染器
|
||||
- **WebGL** - 硬件加速图形渲染
|
||||
|
||||
### 数据可视化
|
||||
- **ECharts** - 数据图表库
|
||||
- **Vue-ECharts** - Vue集成组件
|
||||
- **@jiaminghi/data-view** - 数据大屏组件
|
||||
|
||||
### 地图服务
|
||||
- **Mapbox GL** - 地图渲染引擎
|
||||
- **Cesium** - 3D地球可视化
|
||||
- **GeoJSON** - 地理数据格式
|
||||
|
||||
### 开发工具
|
||||
- **ESLint** - 代码质量检查
|
||||
- **Oxlint** - 高性能代码检查
|
||||
- **TypeScript** - 类型定义支持
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
website/
|
||||
├── public/ # 静态资源
|
||||
│ ├── data/ # 地图数据文件
|
||||
│ └── texture/ # 纹理贴图
|
||||
├── src/
|
||||
│ ├── components/ # Vue组件
|
||||
│ │ ├── Map3D.vue # 3D地图主组件
|
||||
│ │ ├── Home.vue # 首页组件
|
||||
│ │ ├── Alert.vue # 预警监测组件
|
||||
│ │ └── FarmPopup.vue # 养殖场弹窗组件
|
||||
│ ├── hooks/ # 组合式API钩子
|
||||
│ │ ├── useCoord.js # 坐标转换
|
||||
│ │ ├── useFileLoader.js # 文件加载
|
||||
│ │ └── useCSS2DRender.js # 2D渲染
|
||||
│ ├── utils/ # 工具函数
|
||||
│ ├── assets/ # 静态资源
|
||||
│ ├── App.vue # 根组件
|
||||
│ └── main.js # 应用入口
|
||||
├── index.html # HTML模板
|
||||
├── package.json # 项目配置
|
||||
├── vite.config.js # Vite配置
|
||||
└── README.md # 项目文档
|
||||
```
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 环境要求
|
||||
- Node.js >= 20.19.0 或 >= 22.12.0
|
||||
- npm 或 yarn 包管理器
|
||||
|
||||
### 安装依赖
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
### 开发环境运行
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
访问 http://localhost:5173 查看应用
|
||||
|
||||
### 生产环境构建
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
### 预览构建结果
|
||||
```bash
|
||||
npm run preview
|
||||
```
|
||||
|
||||
### 代码质量检查
|
||||
```bash
|
||||
# 运行所有代码检查
|
||||
npm run lint
|
||||
|
||||
# 仅运行ESLint检查
|
||||
npm run lint:eslint
|
||||
|
||||
# 仅运行Oxlint检查
|
||||
npm run lint:oxlint
|
||||
```
|
||||
|
||||
## 主要特性
|
||||
|
||||
### 3D地图交互
|
||||
- 鼠标拖拽旋转地球
|
||||
- 滚轮缩放地图视角
|
||||
- 点击养殖场标记查看详情
|
||||
- 平滑的相机动画过渡
|
||||
|
||||
### 数据展示
|
||||
- 实时养殖场数据更新
|
||||
- 多种图表类型支持
|
||||
- 响应式数据大屏
|
||||
- 自定义主题配置
|
||||
|
||||
### 用户体验
|
||||
- 流畅的页面切换动画
|
||||
- 现代化的UI设计
|
||||
- 移动端适配支持
|
||||
- 无障碍访问优化
|
||||
|
||||
## 开发指南
|
||||
|
||||
### 添加新的养殖场数据
|
||||
在 `src/components/Map3D.vue` 文件中的 `farmData` 数组中添加新的养殖场信息:
|
||||
|
||||
```javascript
|
||||
const farmData = [
|
||||
{
|
||||
id: 6,
|
||||
name: '新养殖场',
|
||||
position: [106.2081, 38.4681], // [经度, 纬度]
|
||||
livestock: 15000,
|
||||
area: '800亩',
|
||||
type: '肉羊养殖',
|
||||
established: '2023年',
|
||||
contact: '张经理 138****1234'
|
||||
}
|
||||
];
|
||||
```
|
||||
|
||||
### 自定义地图样式
|
||||
修改 `src/components/Map3D.vue` 中的材质和颜色配置:
|
||||
|
||||
```javascript
|
||||
// 修改地图边界线颜色
|
||||
const lineMaterial = new THREE.LineBasicMaterial({
|
||||
color: 0x00ffff, // 青色
|
||||
linewidth: 2
|
||||
});
|
||||
|
||||
// 修改养殖场标记颜色
|
||||
const markerMaterial = new THREE.MeshBasicMaterial({
|
||||
color: 0xff6b35 // 橙色
|
||||
});
|
||||
```
|
||||
|
||||
### 添加新的页面
|
||||
1. 在 `src/components/` 目录下创建新的Vue组件
|
||||
2. 在 `src/App.vue` 中注册组件和导航
|
||||
3. 更新页面组件映射和导航菜单
|
||||
|
||||
## 部署说明
|
||||
|
||||
### 静态部署
|
||||
构建完成后,将 `dist` 目录部署到任何静态文件服务器即可。
|
||||
|
||||
### Nginx配置示例
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name your-domain.com;
|
||||
root /path/to/dist;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 贡献指南
|
||||
|
||||
1. Fork 项目仓库
|
||||
2. 创建功能分支 (`git checkout -b feature/AmazingFeature`)
|
||||
3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
|
||||
4. 推送到分支 (`git push origin feature/AmazingFeature`)
|
||||
5. 创建 Pull Request
|
||||
|
||||
## 许可证
|
||||
|
||||
本项目采用 MIT 许可证 - 查看 [LICENSE](LICENSE) 文件了解详情。
|
||||
|
||||
## 联系方式
|
||||
|
||||
- 项目维护者:开发团队
|
||||
- 邮箱:dev@example.com
|
||||
- 项目地址:https://github.com/your-org/nx-smart-livestock
|
||||
|
||||
## 更新日志
|
||||
|
||||
### v1.0.0 (2025-01-18)
|
||||
- ✨ 初始版本发布
|
||||
- 🗺️ 3D地图可视化功能
|
||||
- 📊 数据大屏展示
|
||||
- 🏭 养殖场管理系统
|
||||
- 🎨 现代化UI设计
|
||||
- 📱 响应式布局支持
|
||||
26
insurance_datav/eslint.config.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import { defineConfig, globalIgnores } from 'eslint/config'
|
||||
import globals from 'globals'
|
||||
import js from '@eslint/js'
|
||||
import pluginVue from 'eslint-plugin-vue'
|
||||
import pluginOxlint from 'eslint-plugin-oxlint'
|
||||
|
||||
export default defineConfig([
|
||||
{
|
||||
name: 'app/files-to-lint',
|
||||
files: ['**/*.{js,mjs,jsx,vue}'],
|
||||
},
|
||||
|
||||
globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']),
|
||||
|
||||
{
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
js.configs.recommended,
|
||||
...pluginVue.configs['flat/essential'],
|
||||
...pluginOxlint.configs['flat/recommended'],
|
||||
])
|
||||
13
insurance_datav/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>宁夏智慧畜牧大数据平台</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
8
insurance_datav/jsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
5417
insurance_datav/package-lock.json
generated
Normal file
42
insurance_datav/package.json
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "nx",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"lint:oxlint": "oxlint . --fix -D correctness --ignore-path .gitignore",
|
||||
"lint:eslint": "eslint . --fix",
|
||||
"lint": "run-s lint:*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@jiaminghi/data-view": "^2.10.0",
|
||||
"@types/three": "^0.179.0",
|
||||
"axios": "^1.11.0",
|
||||
"cesium": "^1.133.0",
|
||||
"echarts": "^5.6.0",
|
||||
"mapbox-gl": "^3.14.0",
|
||||
"pinia": "^3.0.3",
|
||||
"three": "^0.179.1",
|
||||
"vue": "^3.5.18",
|
||||
"vue-echarts": "^7.0.3",
|
||||
"vue-router": "^4.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.31.0",
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"eslint": "^9.31.0",
|
||||
"eslint-plugin-oxlint": "~1.8.0",
|
||||
"eslint-plugin-vue": "~10.3.0",
|
||||
"globals": "^16.3.0",
|
||||
"npm-run-all2": "^8.0.4",
|
||||
"oxlint": "~1.8.0",
|
||||
"vite": "^7.0.6",
|
||||
"vite-plugin-vue-devtools": "^8.0.0"
|
||||
}
|
||||
}
|
||||
BIN
insurance_datav/public/data/map/1.png
Normal file
|
After Width: | Height: | Size: 482 KiB |
BIN
insurance_datav/public/data/map/bg.jpg
Normal file
|
After Width: | Height: | Size: 452 KiB |
BIN
insurance_datav/public/data/map/circle-point.png
Normal file
|
After Width: | Height: | Size: 470 KiB |
BIN
insurance_datav/public/data/map/dbg.webp
Normal file
|
After Width: | Height: | Size: 1.9 MiB |
BIN
insurance_datav/public/data/map/gz-map-fx.jpg
Normal file
|
After Width: | Height: | Size: 806 KiB |
BIN
insurance_datav/public/data/map/gz-map.jpg
Normal file
|
After Width: | Height: | Size: 3.6 MiB |
BIN
insurance_datav/public/data/map/rotating-point2.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
insurance_datav/public/data/map/rotatingAperture.png
Normal file
|
After Width: | Height: | Size: 277 KiB |
BIN
insurance_datav/public/data/map/sc-map.jpg
Normal file
|
After Width: | Height: | Size: 450 KiB |
BIN
insurance_datav/public/data/map/scene-bg2.png
Normal file
|
After Width: | Height: | Size: 708 KiB |
BIN
insurance_datav/public/data/map/sidebg.png
Normal file
|
After Width: | Height: | Size: 489 KiB |
BIN
insurance_datav/public/data/map/上升粒子1.png
Normal file
|
After Width: | Height: | Size: 82 KiB |
1
insurance_datav/public/data/map/中华人民共和国.json
Normal file
1
insurance_datav/public/data/map/宁夏回族自治区.json
Normal file
BIN
insurance_datav/public/data/map/牛.png
Normal file
|
After Width: | Height: | Size: 200 KiB |
24
insurance_datav/public/favicon.svg
Normal file
@@ -0,0 +1,24 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
|
||||
<!-- 背景圆形 -->
|
||||
<circle cx="16" cy="16" r="15" fill="#0c1426" stroke="#00d4ff" stroke-width="2"/>
|
||||
|
||||
<!-- 牛头轮廓 -->
|
||||
<path d="M8 18 Q8 12 12 10 Q16 8 20 10 Q24 12 24 18 L22 20 Q20 22 16 22 Q12 22 10 20 Z" fill="#00d4ff" opacity="0.8"/>
|
||||
|
||||
<!-- 牛角 -->
|
||||
<path d="M10 12 Q8 10 7 8" stroke="#00ffff" stroke-width="2" fill="none" stroke-linecap="round"/>
|
||||
<path d="M22 12 Q24 10 25 8" stroke="#00ffff" stroke-width="2" fill="none" stroke-linecap="round"/>
|
||||
|
||||
<!-- 眼睛 -->
|
||||
<circle cx="13" cy="15" r="1.5" fill="#ffffff"/>
|
||||
<circle cx="19" cy="15" r="1.5" fill="#ffffff"/>
|
||||
|
||||
<!-- 鼻子 -->
|
||||
<ellipse cx="16" cy="18" rx="2" ry="1" fill="#ffffff" opacity="0.6"/>
|
||||
|
||||
<!-- 数据点装饰 -->
|
||||
<circle cx="6" cy="8" r="1" fill="#00ffff" opacity="0.6"/>
|
||||
<circle cx="26" cy="8" r="1" fill="#00ffff" opacity="0.6"/>
|
||||
<circle cx="6" cy="24" r="1" fill="#00ffff" opacity="0.6"/>
|
||||
<circle cx="26" cy="24" r="1" fill="#00ffff" opacity="0.6"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
32
insurance_datav/public/texture/earth.svg
Normal file
@@ -0,0 +1,32 @@
|
||||
<svg width="512" height="256" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<radialGradient id="earthGradient" cx="50%" cy="30%" r="70%">
|
||||
<stop offset="0%" style="stop-color:#4a90e2;stop-opacity:1" />
|
||||
<stop offset="30%" style="stop-color:#2c5aa0;stop-opacity:1" />
|
||||
<stop offset="70%" style="stop-color:#1a365d;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#0f2027;stop-opacity:1" />
|
||||
</radialGradient>
|
||||
<pattern id="continents" patternUnits="userSpaceOnUse" width="512" height="256">
|
||||
<rect width="512" height="256" fill="url(#earthGradient)"/>
|
||||
<!-- 简化的大陆轮廓 -->
|
||||
<path d="M50 80 Q100 60 150 80 Q200 100 250 80 Q300 60 350 80 Q400 100 450 80"
|
||||
stroke="#2d5016" stroke-width="3" fill="none" opacity="0.6"/>
|
||||
<path d="M30 120 Q80 100 130 120 Q180 140 230 120 Q280 100 330 120 Q380 140 430 120"
|
||||
stroke="#2d5016" stroke-width="2" fill="none" opacity="0.4"/>
|
||||
<path d="M70 160 Q120 140 170 160 Q220 180 270 160 Q320 140 370 160 Q420 180 470 160"
|
||||
stroke="#2d5016" stroke-width="2" fill="none" opacity="0.4"/>
|
||||
<!-- 添加一些点状细节 -->
|
||||
<circle cx="100" cy="90" r="2" fill="#2d5016" opacity="0.3"/>
|
||||
<circle cx="200" cy="110" r="1.5" fill="#2d5016" opacity="0.3"/>
|
||||
<circle cx="300" cy="130" r="2" fill="#2d5016" opacity="0.3"/>
|
||||
<circle cx="400" cy="100" r="1.5" fill="#2d5016" opacity="0.3"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
|
||||
<rect width="512" height="256" fill="url(#continents)"/>
|
||||
|
||||
<!-- 添加一些云层效果 -->
|
||||
<ellipse cx="150" cy="60" rx="40" ry="15" fill="white" opacity="0.1"/>
|
||||
<ellipse cx="350" cy="80" rx="50" ry="20" fill="white" opacity="0.1"/>
|
||||
<ellipse cx="250" cy="180" rx="45" ry="18" fill="white" opacity="0.1"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
BIN
insurance_datav/public/texture/光柱.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |