修改养殖端小程序,保险前后端和小程序

This commit is contained in:
xuqiuyun
2025-09-19 18:13:07 +08:00
parent eb3c4604d3
commit 35db747d4f
89 changed files with 16231 additions and 1500 deletions

View File

@@ -11,6 +11,7 @@
"@ant-design/icons-vue": "^6.1.0",
"ant-design-vue": "^4.0.0",
"axios": "^1.4.0",
"dayjs": "^1.11.18",
"echarts": "^5.4.2",
"pinia": "^2.1.6",
"vue": "^3.3.4",

View File

@@ -10,19 +10,20 @@
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
},
"dependencies": {
"vue": "^3.3.4",
"vue-router": "^4.2.4",
"pinia": "^2.1.6",
"@ant-design/icons-vue": "^6.1.0",
"ant-design-vue": "^4.0.0",
"axios": "^1.4.0",
"@ant-design/icons-vue": "^6.1.0",
"dayjs": "^1.11.18",
"echarts": "^5.4.2",
"vue-echarts": "^6.5.3"
"pinia": "^2.1.6",
"vue": "^3.3.4",
"vue-echarts": "^6.5.3",
"vue-router": "^4.2.4"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.2.3",
"vite": "^4.4.5",
"eslint": "^8.45.0",
"eslint-plugin-vue": "^9.15.1"
"eslint-plugin-vue": "^9.15.1",
"vite": "^4.4.5"
}
}
}

View File

@@ -10,7 +10,7 @@
v-model:selectedKeys="selectedKeys"
theme="dark"
mode="inline"
:items="menuItems"
:items="menus"
@click="handleMenuClick"
/>
</a-layout-sider>
@@ -70,7 +70,7 @@
</template>
<script setup>
import { ref, computed, h } from 'vue'
import { ref, computed, onMounted, h } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import {
MenuUnfoldOutlined,
@@ -79,13 +79,23 @@ import {
DownOutlined,
LogoutOutlined,
DashboardOutlined,
UserSwitchOutlined,
InsuranceOutlined,
FileTextOutlined,
FileDoneOutlined,
SafetyCertificateOutlined
SafetyCertificateOutlined,
DatabaseOutlined,
CheckCircleOutlined,
ExclamationCircleOutlined,
BellOutlined,
SettingOutlined,
UserAddOutlined,
ShopOutlined,
FileProtectOutlined,
MedicineBoxOutlined,
UserSwitchOutlined,
InsuranceOutlined
} from '@ant-design/icons-vue'
import { useUserStore } from '@/stores/user'
import { menuAPI } from '@/utils/api'
const router = useRouter()
const route = useRoute()
@@ -93,54 +103,184 @@ const userStore = useUserStore()
const collapsed = ref(false)
const selectedKeys = ref([route.name])
const menus = ref([])
const menuItems = computed(() => [
{
key: 'Dashboard',
icon: () => h(DashboardOutlined),
label: '仪表板',
title: '仪表板'
},
{
key: 'UserManagement',
icon: () => h(UserSwitchOutlined),
label: '用户管理',
title: '用户管理'
},
{
key: 'InsuranceTypeManagement',
icon: () => h(InsuranceOutlined),
label: '保险类型管理',
title: '保险类型管理'
},
{
key: 'ApplicationManagement',
icon: () => h(FileTextOutlined),
label: '保险申请管理',
title: '保险申请管理'
},
{
key: 'PolicyManagement',
icon: () => h(FileDoneOutlined),
label: '保单管理',
title: '保单管理'
},
{
key: 'ClaimManagement',
icon: () => h(SafetyCertificateOutlined),
label: '理赔管理',
title: '理赔管理'
// 图标映射根据后端返回的icon名称返回对应的组件
const iconMap = {
DashboardOutlined: () => h(DashboardOutlined),
DatabaseOutlined: () => h(DatabaseOutlined),
CheckCircleOutlined: () => h(CheckCircleOutlined),
FileTextOutlined: () => h(FileTextOutlined),
FileDoneOutlined: () => h(FileDoneOutlined),
SafetyCertificateOutlined: () => h(SafetyCertificateOutlined),
ShopOutlined: () => h(ShopOutlined),
FileProtectOutlined: () => h(FileProtectOutlined),
MedicineBoxOutlined: () => h(MedicineBoxOutlined),
InsuranceOutlined: () => h(InsuranceOutlined),
BellOutlined: () => h(BellOutlined),
UserAddOutlined: () => h(UserAddOutlined),
SettingOutlined: () => h(SettingOutlined),
UserSwitchOutlined: () => h(UserSwitchOutlined)
};
// 格式化菜单数据为Ant Design Vue的Menu组件所需格式
const formatMenuItems = (menuList) => {
return menuList.map(menu => {
const menuItem = {
key: menu.key,
label: menu.name,
path: menu.path
};
// 添加图标
if (menu.icon && iconMap[menu.icon]) {
menuItem.icon = iconMap[menu.icon];
}
// 添加子菜单
if (menu.children && menu.children.length > 0) {
menuItem.children = formatMenuItems(menu.children);
}
return menuItem;
});
};
// 获取动态菜单数据
const fetchMenus = async () => {
try {
const response = await menuAPI.getMenus();
if (response.status === 'success') {
menus.value = formatMenuItems(response.data);
}
} catch (error) {
console.error('获取菜单失败:', error);
// 提供默认菜单作为备用
menus.value = [
{
key: 'Dashboard',
icon: () => h(DashboardOutlined),
label: '仪表板',
path: '/dashboard'
},
{
key: 'DataWarehouse',
icon: () => h(DatabaseOutlined),
label: '数据览仓',
path: '/dashboard' // 重定向到仪表板
},
{
key: 'SupervisionTask',
icon: () => h(CheckCircleOutlined),
label: '监管任务',
path: '/dashboard' // 重定向到仪表板
},
{
key: 'PendingInstallationTask',
icon: () => h(ExclamationCircleOutlined),
label: '待安装任务',
path: '/dashboard' // 重定向到仪表板
},
{
key: 'CompletedTask',
icon: () => h(FileDoneOutlined),
label: '监管任务已结项',
path: '/dashboard' // 重定向到仪表板
},
{
key: 'InsuredCustomers',
icon: () => h(ShopOutlined),
label: '投保客户单',
children: [
{
key: 'ApplicationManagement',
label: '参保申请',
path: '/applications'
}
]
},
{
key: 'AgriculturalInsurance',
icon: () => h(FileProtectOutlined),
label: '生资保单',
children: [
{
key: 'PolicyManagement',
label: '生资保单列表',
path: '/policies'
}
]
},
{
key: 'InsuranceTypeManagement',
icon: () => h(MedicineBoxOutlined),
label: '险种管理',
children: [
{
key: 'InsuranceTypeList',
label: '险种管理',
path: '/insurance-types'
}
]
},
{
key: 'CustomerClaims',
icon: () => h(ExclamationCircleOutlined),
label: '客户理赔',
children: [
{
key: 'ClaimManagement',
label: '客户理赔',
path: '/claims'
}
]
},
{
key: 'Notifications',
icon: () => h(BellOutlined),
label: '消息通知',
path: '/dashboard' // 重定向到仪表板
},
{
key: 'UserManagement',
icon: () => h(UserAddOutlined),
label: '子账号管理',
path: '/users'
},
{
key: 'SystemSettings',
icon: () => h(SettingOutlined),
label: '系统设置',
path: '/dashboard' // 重定向到仪表板
},
{
key: 'UserProfile',
icon: () => h(UserSwitchOutlined),
label: '个人中心',
path: '/dashboard' // 重定向到仪表板
}
];
}
])
};
const handleMenuClick = ({ key }) => {
router.push({ name: key })
}
// 菜单点击处理
const handleMenuClick = (e) => {
const menuItem = menus.value.find(item => item.key === e.key);
if (menuItem && menuItem.path) {
router.push(menuItem.path);
}
};
// 退出登录处理
const handleLogout = () => {
userStore.logout()
router.push('/login')
}
userStore.logout();
router.push('/login');
};
// 组件挂载时获取菜单
onMounted(() => {
fetchMenus();
});
</script>
<style scoped>

View File

@@ -4,7 +4,24 @@ import router from './router'
import store from './stores'
import Antd from 'ant-design-vue'
import 'ant-design-vue/dist/reset.css'
import dayjs from 'dayjs'
import 'dayjs/locale/zh-cn'
import relativeTime from 'dayjs/plugin/relativeTime'
import duration from 'dayjs/plugin/duration'
import zhCN from 'ant-design-vue/es/locale/zh_CN'
// 配置 dayjs
dayjs.extend(relativeTime)
dayjs.extend(duration)
dayjs.locale('zh-cn')
// 为 Ant Design Vue 配置日期库
globalThis.dayjs = dayjs
// Ant Design Vue 4.x 中不再支持通过 Antd.ConfigProvider.config 配置全局属性
// 日期库配置已通过 globalThis.dayjs 完成
// 创建应用实例
const app = createApp(App)
app.use(router)

View File

@@ -7,6 +7,7 @@ import InsuranceTypeManagement from '@/views/InsuranceTypeManagement.vue'
import ApplicationManagement from '@/views/ApplicationManagement.vue'
import PolicyManagement from '@/views/PolicyManagement.vue'
import ClaimManagement from '@/views/ClaimManagement.vue'
import DataWarehouse from '@/views/DataWarehouse.vue'
const routes = [
{
@@ -54,6 +55,12 @@ const routes = [
name: 'ClaimManagement',
component: ClaimManagement,
meta: { title: '理赔管理' }
},
{
path: 'data-warehouse',
name: 'DataWarehouse',
component: DataWarehouse,
meta: { title: '数据览仓' }
}
]
}

View File

@@ -48,6 +48,11 @@ export const userAPI = {
create: (data) => api.post('/users', data),
update: (id, data) => api.put(`/users/${id}`, data),
delete: (id) => api.delete(`/users/${id}`)
};
export const menuAPI = {
getMenus: () => api.get('/menus/public'),
getAllMenus: () => api.get('/menus/all')
}
export const insuranceTypeAPI = {
@@ -84,4 +89,13 @@ export const dashboardAPI = {
getRecentActivities: () => api.get('/system/logs?limit=10')
}
// 数据览仓API
export const dataWarehouseAPI = {
getOverview: () => api.get('/data-warehouse/overview'),
getInsuranceTypeDistribution: () => api.get('/data-warehouse/insurance-types'),
getApplicationStatusDistribution: () => api.get('/data-warehouse/application-status'),
getTrendData: () => api.get('/data-warehouse/trend'),
getClaimStats: () => api.get('/data-warehouse/claim-stats')
}
export default api

View File

@@ -0,0 +1,540 @@
<template>
<div class="data-warehouse">
<div class="page-header">
<h1>数据览仓</h1>
<div class="filters">
<a-date-picker
v-model:value="dateRange"
range
@change="handleDateChange"
style="width: 300px; margin-right: 16px;"
/>
<a-button type="primary" @click="refreshData">刷新数据</a-button>
</div>
</div>
<!-- 概览卡片 -->
<div class="overview-cards">
<a-card class="card-item" hoverable>
<div class="card-content">
<div class="card-title">总用户数</div>
<div class="card-value">{{ overview.totalUsers }}</div>
</div>
</a-card>
<a-card class="card-item" hoverable>
<div class="card-content">
<div class="card-title">保险申请总数</div>
<div class="card-value">{{ overview.totalApplications }}</div>
</div>
</a-card>
<a-card class="card-item" hoverable>
<div class="card-content">
<div class="card-title">保单总数</div>
<div class="card-value">{{ overview.totalPolicies }}</div>
</div>
</a-card>
<a-card class="card-item" hoverable>
<div class="card-content">
<div class="card-title">理赔总数</div>
<div class="card-value">{{ overview.totalClaims }}</div>
</div>
</a-card>
</div>
<!-- 图表区域 -->
<div class="charts-container">
<!-- 保险类型分布 -->
<a-card title="保险类型分布" class="chart-item">
<div ref="typeDistributionChart" class="chart"></div>
</a-card>
<!-- 申请状态分布 -->
<a-card title="申请状态分布" class="chart-item">
<div ref="statusDistributionChart" class="chart"></div>
</a-card>
<!-- 趋势图 -->
<a-card title="近7天业务趋势" class="chart-item full-width">
<div ref="trendChart" class="chart"></div>
</a-card>
<!-- 赔付统计 -->
<a-card title="按保险类型赔付统计" class="chart-item full-width">
<div ref="claimStatsChart" class="chart"></div>
</a-card>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import * as echarts from 'echarts';
import { message } from 'ant-design-vue';
import { dataWarehouseAPI } from '@/utils/api';
import dayjs from 'dayjs';
// 数据状态
const overview = ref({
totalUsers: 0,
totalApplications: 0,
totalPolicies: 0,
totalClaims: 0,
activePolicies: 0,
approvedClaims: 0,
pendingClaims: 0
});
// 正确初始化日期范围为 dayjs 对象
const dateRange = ref([dayjs().subtract(7, 'day'), dayjs()]);
const loading = ref(false);
// 图表引用
const typeDistributionChart = ref(null);
const statusDistributionChart = ref(null);
const trendChart = ref(null);
const claimStatsChart = ref(null);
// 图表实例
let typeChartInstance = null;
let statusChartInstance = null;
let trendChartInstance = null;
let claimChartInstance = null;
// 获取概览数据
const fetchOverview = async () => {
try {
const result = await dataWarehouseAPI.getOverview();
if (result.status === 'success') {
overview.value = result.data;
} else {
message.error('获取概览数据失败');
}
} catch (error) {
message.error('获取概览数据失败');
console.error('获取概览数据错误:', error);
}
};
// 获取保险类型分布数据
const fetchTypeDistribution = async () => {
try {
const result = await dataWarehouseAPI.getInsuranceTypeDistribution();
if (result.status === 'success') {
renderTypeDistributionChart(result.data);
} else {
message.error('获取保险类型分布数据失败');
}
} catch (error) {
message.error('获取保险类型分布数据失败');
console.error('获取保险类型分布数据错误:', error);
}
};
// 获取申请状态分布数据
const fetchStatusDistribution = async () => {
try {
const result = await dataWarehouseAPI.getApplicationStatusDistribution();
if (result.status === 'success') {
renderStatusDistributionChart(result.data);
} else {
message.error('获取申请状态分布数据失败');
}
} catch (error) {
message.error('获取申请状态分布数据失败');
console.error('获取申请状态分布数据错误:', error);
}
};
// 获取趋势数据
const fetchTrendData = async () => {
try {
const result = await dataWarehouseAPI.getTrendData();
if (result.status === 'success') {
renderTrendChart(result.data);
} else {
message.error('获取趋势数据失败');
}
} catch (error) {
message.error('获取趋势数据失败');
console.error('获取趋势数据错误:', error);
}
};
// 获取赔付统计数据
const fetchClaimStats = async () => {
try {
const result = await dataWarehouseAPI.getClaimStats();
if (result.status === 'success') {
renderClaimStatsChart(result.data);
} else {
message.error('获取赔付统计数据失败');
}
} catch (error) {
message.error('获取赔付统计数据失败');
console.error('获取赔付统计数据错误:', error);
}
};
// 刷新所有数据
const refreshData = async () => {
loading.value = true;
try {
await Promise.all([
fetchOverview(),
fetchTypeDistribution(),
fetchStatusDistribution(),
fetchTrendData(),
fetchClaimStats()
]);
message.success('数据刷新成功');
} finally {
loading.value = false;
}
};
// 处理日期范围变化
const handleDateChange = (dates) => {
dateRange.value = dates;
// 这里可以根据日期范围筛选数据
// refreshData();
};
// 渲染保险类型分布图表
const renderTypeDistributionChart = (data) => {
if (!typeChartInstance) {
typeChartInstance = echarts.init(typeDistributionChart.value);
}
const option = {
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
legend: {
orient: 'vertical',
left: 'left',
data: data.map(item => item.name)
},
series: [
{
name: '保险类型',
type: 'pie',
radius: '50%',
center: ['50%', '50%'],
data: data.map(item => ({ value: item.count, name: item.name })),
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
},
label: {
formatter: '{b}\n{c} ({d}%)'
}
}
]
};
typeChartInstance.setOption(option);
};
// 渲染申请状态分布图表
const renderStatusDistributionChart = (data) => {
if (!statusChartInstance) {
statusChartInstance = echarts.init(statusDistributionChart.value);
}
const option = {
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
legend: {
orient: 'vertical',
left: 'left',
data: data.map(item => item.name)
},
series: [
{
name: '申请状态',
type: 'pie',
radius: '50%',
center: ['50%', '50%'],
data: data.map(item => ({ value: item.count, name: item.name })),
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
},
label: {
formatter: '{b}\n{c} ({d}%)'
}
}
]
};
statusChartInstance.setOption(option);
};
// 渲染趋势图表
const renderTrendChart = (data) => {
if (!trendChartInstance) {
trendChartInstance = echarts.init(trendChart.value);
}
const dates = data.map(item => item.date);
const newApplications = data.map(item => item.newApplications);
const newPolicies = data.map(item => item.newPolicies);
const newClaims = data.map(item => item.newClaims);
const option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#6a7985'
}
}
},
legend: {
data: ['新增申请', '新增保单', '新增理赔']
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: dates
},
yAxis: {
type: 'value'
},
series: [
{
name: '新增申请',
type: 'line',
stack: '总量',
areaStyle: {},
emphasis: {
focus: 'series'
},
data: newApplications
},
{
name: '新增保单',
type: 'line',
stack: '总量',
areaStyle: {},
emphasis: {
focus: 'series'
},
data: newPolicies
},
{
name: '新增理赔',
type: 'line',
stack: '总量',
areaStyle: {},
emphasis: {
focus: 'series'
},
data: newClaims
}
]
};
trendChartInstance.setOption(option);
};
// 渲染赔付统计图表
const renderClaimStatsChart = (data) => {
if (!claimChartInstance) {
claimChartInstance = echarts.init(claimStatsChart.value);
}
const option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
legend: {
data: ['赔付金额']
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: data.map(item => item.name),
axisLabel: {
rotate: 45
}
},
yAxis: {
type: 'value',
name: '金额(元)',
axisLabel: {
formatter: '{value} 元'
}
},
series: [
{
name: '赔付金额',
type: 'bar',
data: data.map(item => ({
value: item.totalAmount,
label: {
show: true,
position: 'top',
formatter: '{c} 元'
}
}))
}
]
};
claimChartInstance.setOption(option);
};
// 处理窗口大小变化,重新调整图表
const handleResize = () => {
typeChartInstance?.resize();
statusChartInstance?.resize();
trendChartInstance?.resize();
claimChartInstance?.resize();
};
// 组件挂载时初始化
onMounted(() => {
refreshData();
window.addEventListener('resize', handleResize);
});
// 组件卸载时清理
onUnmounted(() => {
window.removeEventListener('resize', handleResize);
typeChartInstance?.dispose();
statusChartInstance?.dispose();
trendChartInstance?.dispose();
claimChartInstance?.dispose();
});
</script>
<style scoped>
.data-warehouse {
padding: 20px;
background-color: #f5f5f5;
min-height: 100vh;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.page-header h1 {
margin: 0;
font-size: 24px;
font-weight: 500;
}
.filters {
display: flex;
align-items: center;
}
.overview-cards {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
margin-bottom: 20px;
}
.card-item {
height: 120px;
}
.card-content {
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.card-title {
font-size: 14px;
color: #666;
margin-bottom: 8px;
}
.card-value {
font-size: 28px;
font-weight: 600;
color: #1890ff;
}
.charts-container {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
}
.chart-item {
height: 400px;
}
.chart-item.full-width {
grid-column: span 2;
}
.chart {
width: 100%;
height: calc(100% - 50px);
}
@media (max-width: 1200px) {
.overview-cards {
grid-template-columns: repeat(2, 1fr);
}
.charts-container {
grid-template-columns: 1fr;
}
.chart-item.full-width {
grid-column: span 1;
}
}
@media (max-width: 768px) {
.overview-cards {
grid-template-columns: 1fr;
}
.page-header {
flex-direction: column;
align-items: flex-start;
gap: 16px;
}
.filters {
width: 100%;
}
}
</style>