保险前后端,养殖端和保险端小程序
This commit is contained in:
13
insurance_admin-system/index.html
Normal file
13
insurance_admin-system/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<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>
|
||||
4380
insurance_admin-system/package-lock.json
generated
Normal file
4380
insurance_admin-system/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
28
insurance_admin-system/package.json
Normal file
28
insurance_admin-system/package.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "insurance-admin-system",
|
||||
"version": "1.0.0",
|
||||
"description": "保险端口后台管理系统",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"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-vue": "^4.0.0",
|
||||
"axios": "^1.4.0",
|
||||
"@ant-design/icons-vue": "^6.1.0",
|
||||
"echarts": "^5.4.2",
|
||||
"vue-echarts": "^6.5.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"vite": "^4.4.5",
|
||||
"eslint": "^8.45.0",
|
||||
"eslint-plugin-vue": "^9.15.1"
|
||||
}
|
||||
}
|
||||
1
insurance_admin-system/public/vite.svg
Normal file
1
insurance_admin-system/public/vite.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
26
insurance_admin-system/src/App.vue
Normal file
26
insurance_admin-system/src/App.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<a-config-provider :locale="zhCN">
|
||||
<router-view />
|
||||
</a-config-provider>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import zhCN from 'ant-design-vue/es/locale/zh_CN'
|
||||
</script>
|
||||
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
#app {
|
||||
min-height: 100vh;
|
||||
}
|
||||
</style>
|
||||
165
insurance_admin-system/src/components/Layout.vue
Normal file
165
insurance_admin-system/src/components/Layout.vue
Normal file
@@ -0,0 +1,165 @@
|
||||
<template>
|
||||
<a-layout style="min-height: 100vh">
|
||||
<!-- 侧边栏 -->
|
||||
<a-layout-sider v-model:collapsed="collapsed" collapsible>
|
||||
<div class="logo">
|
||||
<h2 v-if="!collapsed">保险管理系统</h2>
|
||||
<h2 v-else>保险</h2>
|
||||
</div>
|
||||
<a-menu
|
||||
v-model:selectedKeys="selectedKeys"
|
||||
theme="dark"
|
||||
mode="inline"
|
||||
:items="menuItems"
|
||||
@click="handleMenuClick"
|
||||
/>
|
||||
</a-layout-sider>
|
||||
|
||||
<!-- 主内容区 -->
|
||||
<a-layout>
|
||||
<!-- 头部 -->
|
||||
<a-layout-header style="background: #fff; padding: 0 16px; display: flex; justify-content: space-between; align-items: center">
|
||||
<div>
|
||||
<menu-unfold-outlined
|
||||
v-if="collapsed"
|
||||
class="trigger"
|
||||
@click="() => (collapsed = !collapsed)"
|
||||
/>
|
||||
<menu-fold-outlined
|
||||
v-else
|
||||
class="trigger"
|
||||
@click="() => (collapsed = !collapsed)"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<a-dropdown>
|
||||
<a class="ant-dropdown-link" @click.prevent>
|
||||
<user-outlined />
|
||||
{{ userStore.userInfo.real_name || '管理员' }}
|
||||
<down-outlined />
|
||||
</a>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="profile">
|
||||
<user-outlined />
|
||||
个人中心
|
||||
</a-menu-item>
|
||||
<a-menu-item key="logout" @click="handleLogout">
|
||||
<logout-outlined />
|
||||
退出登录
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
</a-layout-header>
|
||||
|
||||
<!-- 内容区 -->
|
||||
<a-layout-content style="margin: 16px">
|
||||
<div :style="{ padding: '24px', background: '#fff', minHeight: '360px' }">
|
||||
<router-view />
|
||||
</div>
|
||||
</a-layout-content>
|
||||
|
||||
<!-- 底部 -->
|
||||
<a-layout-footer style="text-align: center">
|
||||
保险端口后台管理系统 ©2024
|
||||
</a-layout-footer>
|
||||
</a-layout>
|
||||
</a-layout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, h } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import {
|
||||
MenuUnfoldOutlined,
|
||||
MenuFoldOutlined,
|
||||
UserOutlined,
|
||||
DownOutlined,
|
||||
LogoutOutlined,
|
||||
DashboardOutlined,
|
||||
UserSwitchOutlined,
|
||||
InsuranceOutlined,
|
||||
FileTextOutlined,
|
||||
FileDoneOutlined,
|
||||
SafetyCertificateOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const collapsed = ref(false)
|
||||
const selectedKeys = ref([route.name])
|
||||
|
||||
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: '理赔管理'
|
||||
}
|
||||
])
|
||||
|
||||
const handleMenuClick = ({ key }) => {
|
||||
router.push({ name: key })
|
||||
}
|
||||
|
||||
const handleLogout = () => {
|
||||
userStore.logout()
|
||||
router.push('/login')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.logo {
|
||||
height: 32px;
|
||||
margin: 16px;
|
||||
color: white;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.trigger {
|
||||
font-size: 18px;
|
||||
line-height: 64px;
|
||||
padding: 0 24px;
|
||||
cursor: pointer;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
.trigger:hover {
|
||||
color: #1890ff;
|
||||
}
|
||||
</style>
|
||||
14
insurance_admin-system/src/main.js
Normal file
14
insurance_admin-system/src/main.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import store from './stores'
|
||||
import Antd from 'ant-design-vue'
|
||||
import 'ant-design-vue/dist/reset.css'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(router)
|
||||
app.use(store)
|
||||
app.use(Antd)
|
||||
|
||||
app.mount('#app')
|
||||
67
insurance_admin-system/src/router/index.js
Normal file
67
insurance_admin-system/src/router/index.js
Normal file
@@ -0,0 +1,67 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import Layout from '@/components/Layout.vue'
|
||||
import Login from '@/views/Login.vue'
|
||||
import Dashboard from '@/views/Dashboard.vue'
|
||||
import UserManagement from '@/views/UserManagement.vue'
|
||||
import InsuranceTypeManagement from '@/views/InsuranceTypeManagement.vue'
|
||||
import ApplicationManagement from '@/views/ApplicationManagement.vue'
|
||||
import PolicyManagement from '@/views/PolicyManagement.vue'
|
||||
import ClaimManagement from '@/views/ClaimManagement.vue'
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/login',
|
||||
name: 'Login',
|
||||
component: Login
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
component: Layout,
|
||||
redirect: '/dashboard',
|
||||
children: [
|
||||
{
|
||||
path: 'dashboard',
|
||||
name: 'Dashboard',
|
||||
component: Dashboard,
|
||||
meta: { title: '仪表板' }
|
||||
},
|
||||
{
|
||||
path: 'users',
|
||||
name: 'UserManagement',
|
||||
component: UserManagement,
|
||||
meta: { title: '用户管理' }
|
||||
},
|
||||
{
|
||||
path: 'insurance-types',
|
||||
name: 'InsuranceTypeManagement',
|
||||
component: InsuranceTypeManagement,
|
||||
meta: { title: '保险类型管理' }
|
||||
},
|
||||
{
|
||||
path: 'applications',
|
||||
name: 'ApplicationManagement',
|
||||
component: ApplicationManagement,
|
||||
meta: { title: '保险申请管理' }
|
||||
},
|
||||
{
|
||||
path: 'policies',
|
||||
name: 'PolicyManagement',
|
||||
component: PolicyManagement,
|
||||
meta: { title: '保单管理' }
|
||||
},
|
||||
{
|
||||
path: 'claims',
|
||||
name: 'ClaimManagement',
|
||||
component: ClaimManagement,
|
||||
meta: { title: '理赔管理' }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes
|
||||
})
|
||||
|
||||
export default router
|
||||
3
insurance_admin-system/src/stores/index.js
Normal file
3
insurance_admin-system/src/stores/index.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import { createPinia } from 'pinia'
|
||||
|
||||
export default createPinia()
|
||||
32
insurance_admin-system/src/stores/user.js
Normal file
32
insurance_admin-system/src/stores/user.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
export const useUserStore = defineStore('user', () => {
|
||||
const token = ref(localStorage.getItem('token'))
|
||||
const userInfo = ref(JSON.parse(localStorage.getItem('userInfo') || '{}'))
|
||||
|
||||
const setToken = (newToken) => {
|
||||
token.value = newToken
|
||||
localStorage.setItem('token', newToken)
|
||||
}
|
||||
|
||||
const setUserInfo = (info) => {
|
||||
userInfo.value = info
|
||||
localStorage.setItem('userInfo', JSON.stringify(info))
|
||||
}
|
||||
|
||||
const logout = () => {
|
||||
token.value = null
|
||||
userInfo.value = {}
|
||||
localStorage.removeItem('token')
|
||||
localStorage.removeItem('userInfo')
|
||||
}
|
||||
|
||||
return {
|
||||
token,
|
||||
userInfo,
|
||||
setToken,
|
||||
setUserInfo,
|
||||
logout
|
||||
}
|
||||
})
|
||||
87
insurance_admin-system/src/utils/api.js
Normal file
87
insurance_admin-system/src/utils/api.js
Normal file
@@ -0,0 +1,87 @@
|
||||
import axios from 'axios'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
||||
// 创建axios实例
|
||||
const api = axios.create({
|
||||
baseURL: '/api',
|
||||
timeout: 10000
|
||||
})
|
||||
|
||||
// 请求拦截器
|
||||
api.interceptors.request.use(
|
||||
(config) => {
|
||||
const userStore = useUserStore()
|
||||
if (userStore.token) {
|
||||
config.headers.Authorization = `Bearer ${userStore.token}`
|
||||
}
|
||||
return config
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
// 响应拦截器
|
||||
api.interceptors.response.use(
|
||||
(response) => {
|
||||
return response.data
|
||||
},
|
||||
(error) => {
|
||||
if (error.response?.status === 401) {
|
||||
const userStore = useUserStore()
|
||||
userStore.logout()
|
||||
window.location.href = '/login'
|
||||
}
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
// API接口
|
||||
export const authAPI = {
|
||||
login: (data) => api.post('/auth/login', data),
|
||||
logout: () => api.post('/auth/logout'),
|
||||
getProfile: () => api.get('/auth/profile')
|
||||
}
|
||||
|
||||
export const userAPI = {
|
||||
getList: (params) => api.get('/users', { params }),
|
||||
create: (data) => api.post('/users', data),
|
||||
update: (id, data) => api.put(`/users/${id}`, data),
|
||||
delete: (id) => api.delete(`/users/${id}`)
|
||||
}
|
||||
|
||||
export const insuranceTypeAPI = {
|
||||
getList: (params) => api.get('/insurance-types', { params }),
|
||||
create: (data) => api.post('/insurance-types', data),
|
||||
update: (id, data) => api.put(`/insurance-types/${id}`, data),
|
||||
delete: (id) => api.delete(`/insurance-types/${id}`),
|
||||
updateStatus: (id, data) => api.patch(`/insurance-types/${id}/status`, data)
|
||||
}
|
||||
|
||||
export const applicationAPI = {
|
||||
getList: (params) => api.get('/insurance/applications', { params }),
|
||||
getDetail: (id) => api.get(`/insurance/applications/${id}`),
|
||||
updateStatus: (id, data) => api.put(`/insurance/applications/${id}/status`, data),
|
||||
delete: (id) => api.delete(`/insurance/applications/${id}`)
|
||||
}
|
||||
|
||||
export const policyAPI = {
|
||||
getList: (params) => api.get('/policies', { params }),
|
||||
getDetail: (id) => api.get(`/policies/${id}`),
|
||||
updateStatus: (id, data) => api.put(`/policies/${id}/status`, data),
|
||||
delete: (id) => api.delete(`/policies/${id}`)
|
||||
}
|
||||
|
||||
export const claimAPI = {
|
||||
getList: (params) => api.get('/claims', { params }),
|
||||
getDetail: (id) => api.get(`/claims/${id}`),
|
||||
updateStatus: (id, data) => api.put(`/claims/${id}/status`, data),
|
||||
delete: (id) => api.delete(`/claims/${id}`)
|
||||
}
|
||||
|
||||
export const dashboardAPI = {
|
||||
getStats: () => api.get('/system/stats'),
|
||||
getRecentActivities: () => api.get('/system/logs?limit=10')
|
||||
}
|
||||
|
||||
export default api
|
||||
570
insurance_admin-system/src/views/ApplicationManagement.vue
Normal file
570
insurance_admin-system/src/views/ApplicationManagement.vue
Normal file
@@ -0,0 +1,570 @@
|
||||
<template>
|
||||
<div class="application-management">
|
||||
<a-page-header
|
||||
title="投保申请管理"
|
||||
sub-title="管理用户的保险投保申请"
|
||||
/>
|
||||
|
||||
<a-card>
|
||||
<!-- 搜索区域 -->
|
||||
<a-form layout="inline" :model="searchForm" @finish="handleSearch">
|
||||
<a-form-item label="申请人">
|
||||
<a-input
|
||||
v-model:value="searchForm.applicant_name"
|
||||
placeholder="请输入申请人姓名"
|
||||
style="width: 120px"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="保险类型">
|
||||
<a-select
|
||||
v-model:value="searchForm.insurance_type_id"
|
||||
placeholder="请选择保险类型"
|
||||
style="width: 120px"
|
||||
allow-clear
|
||||
>
|
||||
<a-select-option v-for="type in insuranceTypes" :key="type.id" :value="type.id">
|
||||
{{ type.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="申请状态">
|
||||
<a-select
|
||||
v-model:value="searchForm.status"
|
||||
placeholder="请选择状态"
|
||||
style="width: 120px"
|
||||
allow-clear
|
||||
>
|
||||
<a-select-option value="pending">待审核</a-select-option>
|
||||
<a-select-option value="approved">已通过</a-select-option>
|
||||
<a-select-option value="rejected">已拒绝</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="申请时间">
|
||||
<a-range-picker
|
||||
v-model:value="searchForm.timeRange"
|
||||
:show-time="{ format: 'HH:mm' }"
|
||||
format="YYYY-MM-DD HH:mm"
|
||||
style="width: 320px"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-space>
|
||||
<a-button type="primary" html-type="submit">查询</a-button>
|
||||
<a-button @click="resetSearch">重置</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<a-card style="margin-top: 16px">
|
||||
<!-- 操作按钮 -->
|
||||
<div style="margin-bottom: 16px">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="exportApplications" :loading="exportLoading">
|
||||
导出申请
|
||||
</a-button>
|
||||
<a-button @click="refreshApplications">刷新</a-button>
|
||||
<a-tag color="blue">
|
||||
共 {{ pagination.total }} 条申请
|
||||
</a-tag>
|
||||
</a-space>
|
||||
</div>
|
||||
|
||||
<!-- 申请表格 -->
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="applicationList"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
@change="handleTableChange"
|
||||
:scroll="{ x: 1200 }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<!-- 申请状态 -->
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="getStatusColor(record.status)">
|
||||
{{ getStatusText(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<!-- 保险类型 -->
|
||||
<template v-else-if="column.key === 'insurance_type_id'">
|
||||
{{ getInsuranceTypeName(record.insurance_type_id) }}
|
||||
</template>
|
||||
|
||||
<!-- 申请时间 -->
|
||||
<template v-else-if="column.key === 'created_at'">
|
||||
{{ formatDateTime(record.created_at) }}
|
||||
</template>
|
||||
|
||||
<!-- 操作 -->
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button
|
||||
size="small"
|
||||
@click="showApplicationDetail(record)"
|
||||
>
|
||||
详情
|
||||
</a-button>
|
||||
<a-button
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="approveApplication(record)"
|
||||
v-if="record.status === 'pending'"
|
||||
>
|
||||
通过
|
||||
</a-button>
|
||||
<a-button
|
||||
size="small"
|
||||
danger
|
||||
@click="rejectApplication(record)"
|
||||
v-if="record.status === 'pending'"
|
||||
>
|
||||
拒绝
|
||||
</a-button>
|
||||
<a-popconfirm
|
||||
title="确定要删除这个申请吗?"
|
||||
@confirm="deleteApplication(record)"
|
||||
>
|
||||
<a-button size="small" danger>删除</a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 申请详情模态框 -->
|
||||
<a-modal
|
||||
v-model:visible="detailVisible"
|
||||
:title="`申请详情 - ${currentApplication?.application_number || ''}`"
|
||||
width="800px"
|
||||
:footer="null"
|
||||
>
|
||||
<a-descriptions bordered :column="2" v-if="currentApplication">
|
||||
<a-descriptions-item label="申请编号">
|
||||
{{ currentApplication.application_number }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="申请人">
|
||||
{{ currentApplication.applicant_name }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="联系电话">
|
||||
{{ currentApplication.phone }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="电子邮箱">
|
||||
{{ currentApplication.email }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="保险类型">
|
||||
{{ getInsuranceTypeName(currentApplication.insurance_type_id) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="保额">
|
||||
{{ currentApplication.coverage_amount }} 元
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="保险期限">
|
||||
{{ currentApplication.insurance_period }} 年
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="申请状态">
|
||||
<a-tag :color="getStatusColor(currentApplication.status)">
|
||||
{{ getStatusText(currentApplication.status) }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="申请时间">
|
||||
{{ formatDateTime(currentApplication.created_at) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="审核时间" v-if="currentApplication.reviewed_at">
|
||||
{{ formatDateTime(currentApplication.reviewed_at) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="审核人" v-if="currentApplication.reviewer">
|
||||
{{ currentApplication.reviewer }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="拒绝原因" v-if="currentApplication.reject_reason">
|
||||
{{ currentApplication.reject_reason }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="备注信息" :span="2">
|
||||
{{ currentApplication.notes || '无' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="申请人信息" :span="2">
|
||||
<pre style="background: #f5f5f5; padding: 8px; border-radius: 4px; overflow: auto;">
|
||||
{{ JSON.stringify(JSON.parse(currentApplication.applicant_info || '{}'), null, 2) }}
|
||||
</pre>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-modal>
|
||||
|
||||
<!-- 审核模态框 -->
|
||||
<a-modal
|
||||
v-model:visible="reviewVisible"
|
||||
:title="`审核申请 - ${currentApplication?.application_number || ''}`"
|
||||
width="500px"
|
||||
:confirm-loading="reviewLoading"
|
||||
@ok="handleReview"
|
||||
>
|
||||
<a-form layout="vertical" :model="reviewForm" v-if="currentApplication">
|
||||
<a-form-item
|
||||
label="审核结果"
|
||||
name="status"
|
||||
:rules="[{ required: true, message: '请选择审核结果' }]"
|
||||
>
|
||||
<a-radio-group v-model:value="reviewForm.status">
|
||||
<a-radio value="approved">通过</a-radio>
|
||||
<a-radio value="rejected">拒绝</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
label="拒绝原因"
|
||||
name="reject_reason"
|
||||
v-if="reviewForm.status === 'rejected'"
|
||||
:rules="[{ required: reviewForm.status === 'rejected', message: '请输入拒绝原因' }]"
|
||||
>
|
||||
<a-textarea
|
||||
v-model:value="reviewForm.reject_reason"
|
||||
placeholder="请输入拒绝原因"
|
||||
:rows="3"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="备注信息" name="notes">
|
||||
<a-textarea
|
||||
v-model:value="reviewForm.notes"
|
||||
placeholder="请输入备注信息"
|
||||
:rows="2"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
|
||||
const searchForm = reactive({
|
||||
applicant_name: '',
|
||||
insurance_type_id: undefined,
|
||||
status: undefined,
|
||||
timeRange: []
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
const exportLoading = ref(false)
|
||||
const detailVisible = ref(false)
|
||||
const reviewVisible = ref(false)
|
||||
const reviewLoading = ref(false)
|
||||
const currentApplication = ref(null)
|
||||
|
||||
const applicationList = ref([
|
||||
{
|
||||
id: 1,
|
||||
application_number: 'APP202401200001',
|
||||
applicant_name: '张三',
|
||||
phone: '13800138000',
|
||||
email: 'zhangsan@example.com',
|
||||
insurance_type_id: 1,
|
||||
coverage_amount: 100000,
|
||||
insurance_period: 10,
|
||||
status: 'pending',
|
||||
created_at: '2024-01-20 10:30:25',
|
||||
applicant_info: '{"age": 30, "gender": "male", "occupation": "engineer"}',
|
||||
notes: '首次申请'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
application_number: 'APP202401200002',
|
||||
applicant_name: '李四',
|
||||
phone: '13900139000',
|
||||
email: 'lisi@example.com',
|
||||
insurance_type_id: 2,
|
||||
coverage_amount: 500000,
|
||||
insurance_period: 20,
|
||||
status: 'approved',
|
||||
created_at: '2024-01-20 09:15:18',
|
||||
reviewed_at: '2024-01-20 10:00:00',
|
||||
reviewer: 'admin',
|
||||
applicant_info: '{"age": 35, "gender": "female", "occupation": "teacher"}',
|
||||
notes: '优质客户'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
application_number: 'APP202401200003',
|
||||
applicant_name: '王五',
|
||||
phone: '13700137000',
|
||||
email: 'wangwu@example.com',
|
||||
insurance_type_id: 1,
|
||||
coverage_amount: 200000,
|
||||
insurance_period: 15,
|
||||
status: 'rejected',
|
||||
created_at: '2024-01-20 08:45:30',
|
||||
reviewed_at: '2024-01-20 09:30:00',
|
||||
reviewer: 'admin',
|
||||
reject_reason: '健康状况不符合要求',
|
||||
applicant_info: '{"age": 45, "gender": "male", "occupation": "business"}',
|
||||
notes: '健康告知不完整'
|
||||
}
|
||||
])
|
||||
|
||||
const insuranceTypes = ref([
|
||||
{ id: 1, name: '人寿保险', code: 'life' },
|
||||
{ id: 2, name: '健康保险', code: 'health' },
|
||||
{ id: 3, name: '意外保险', code: 'accident' },
|
||||
{ id: 4, name: '财产保险', code: 'property' }
|
||||
])
|
||||
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 3,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total, range) =>
|
||||
`第 ${range[0]}-${range[1]} 条,共 ${total} 条`
|
||||
})
|
||||
|
||||
const reviewForm = reactive({
|
||||
status: 'approved',
|
||||
reject_reason: '',
|
||||
notes: ''
|
||||
})
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '申请编号',
|
||||
dataIndex: 'application_number',
|
||||
key: 'application_number',
|
||||
width: 140
|
||||
},
|
||||
{
|
||||
title: '申请人',
|
||||
dataIndex: 'applicant_name',
|
||||
key: 'applicant_name',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '联系电话',
|
||||
dataIndex: 'phone',
|
||||
key: 'phone',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '保险类型',
|
||||
key: 'insurance_type_id',
|
||||
dataIndex: 'insurance_type_id',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '保额(元)',
|
||||
dataIndex: 'coverage_amount',
|
||||
key: 'coverage_amount',
|
||||
width: 100,
|
||||
align: 'right'
|
||||
},
|
||||
{
|
||||
title: '保险期限',
|
||||
dataIndex: 'insurance_period',
|
||||
key: 'insurance_period',
|
||||
width: 100,
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '申请状态',
|
||||
key: 'status',
|
||||
dataIndex: 'status',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '申请时间',
|
||||
key: 'created_at',
|
||||
dataIndex: 'created_at',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 200,
|
||||
fixed: 'right'
|
||||
}
|
||||
]
|
||||
|
||||
const getStatusColor = (status) => {
|
||||
const colors = {
|
||||
pending: 'orange',
|
||||
approved: 'green',
|
||||
rejected: 'red'
|
||||
}
|
||||
return colors[status] || 'default'
|
||||
}
|
||||
|
||||
const getStatusText = (status) => {
|
||||
const texts = {
|
||||
pending: '待审核',
|
||||
approved: '已通过',
|
||||
rejected: '已拒绝'
|
||||
}
|
||||
return texts[status] || status
|
||||
}
|
||||
|
||||
const getInsuranceTypeName = (typeId) => {
|
||||
const type = insuranceTypes.value.find(t => t.id === typeId)
|
||||
return type ? type.name : '未知类型'
|
||||
}
|
||||
|
||||
const formatDateTime = (datetime) => {
|
||||
if (!datetime) return ''
|
||||
return datetime.replace('T', ' ').substring(0, 19)
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
pagination.current = 1
|
||||
loadApplications()
|
||||
}
|
||||
|
||||
const resetSearch = () => {
|
||||
Object.assign(searchForm, {
|
||||
applicant_name: '',
|
||||
insurance_type_id: undefined,
|
||||
status: undefined,
|
||||
timeRange: []
|
||||
})
|
||||
pagination.current = 1
|
||||
loadApplications()
|
||||
}
|
||||
|
||||
const handleTableChange = (pag) => {
|
||||
pagination.current = pag.current
|
||||
pagination.pageSize = pag.pageSize
|
||||
loadApplications()
|
||||
}
|
||||
|
||||
const loadApplications = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// const params = {
|
||||
// page: pagination.current,
|
||||
// pageSize: pagination.pageSize,
|
||||
// ...searchForm,
|
||||
// start_time: searchForm.timeRange?.[0]?.format('YYYY-MM-DD HH:mm:ss'),
|
||||
// end_time: searchForm.timeRange?.[1]?.format('YYYY-MM-DD HH:mm:ss')
|
||||
// }
|
||||
// const response = await applicationAPI.getApplications(params)
|
||||
// applicationList.value = response.data.list
|
||||
// pagination.total = response.data.total
|
||||
|
||||
// 模拟数据过滤
|
||||
const filteredApps = applicationList.value.filter(app => {
|
||||
if (searchForm.applicant_name && !app.applicant_name.includes(searchForm.applicant_name)) return false
|
||||
if (searchForm.insurance_type_id && app.insurance_type_id !== searchForm.insurance_type_id) return false
|
||||
if (searchForm.status && app.status !== searchForm.status) return false
|
||||
return true
|
||||
})
|
||||
|
||||
applicationList.value = filteredApps
|
||||
pagination.total = filteredApps.length
|
||||
} catch (error) {
|
||||
message.error('加载申请列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const refreshApplications = () => {
|
||||
loadApplications()
|
||||
}
|
||||
|
||||
const exportApplications = async () => {
|
||||
exportLoading.value = true
|
||||
try {
|
||||
// await applicationAPI.exportApplications(searchForm)
|
||||
message.success('导出任务已开始,请稍后查看下载')
|
||||
} catch (error) {
|
||||
message.error('导出失败')
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const showApplicationDetail = (application) => {
|
||||
currentApplication.value = application
|
||||
detailVisible.value = true
|
||||
}
|
||||
|
||||
const approveApplication = (application) => {
|
||||
currentApplication.value = application
|
||||
reviewForm.status = 'approved'
|
||||
reviewForm.reject_reason = ''
|
||||
reviewForm.notes = ''
|
||||
reviewVisible.value = true
|
||||
}
|
||||
|
||||
const rejectApplication = (application) => {
|
||||
currentApplication.value = application
|
||||
reviewForm.status = 'rejected'
|
||||
reviewForm.reject_reason = ''
|
||||
reviewForm.notes = ''
|
||||
reviewVisible.value = true
|
||||
}
|
||||
|
||||
const handleReview = async () => {
|
||||
reviewLoading.value = true
|
||||
try {
|
||||
// await applicationAPI.reviewApplication(currentApplication.value.id, reviewForm)
|
||||
message.success('审核完成')
|
||||
reviewVisible.value = false
|
||||
loadApplications()
|
||||
} catch (error) {
|
||||
message.error('审核失败')
|
||||
} finally {
|
||||
reviewLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const deleteApplication = async (application) => {
|
||||
try {
|
||||
// await applicationAPI.deleteApplication(application.id)
|
||||
message.success('删除成功')
|
||||
loadApplications()
|
||||
} catch (error) {
|
||||
message.error('删除失败')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadApplications()
|
||||
// 加载保险类型
|
||||
loadInsuranceTypes()
|
||||
})
|
||||
|
||||
const loadInsuranceTypes = async () => {
|
||||
try {
|
||||
// const response = await insuranceTypeAPI.getInsuranceTypes()
|
||||
// insuranceTypes.value = response.data
|
||||
} catch (error) {
|
||||
message.error('加载保险类型失败')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.application-management {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
:deep(.ant-descriptions-item-label) {
|
||||
width: 100px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
:deep(.ant-descriptions-item-content) {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
:deep(pre) {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
</style>
|
||||
751
insurance_admin-system/src/views/ClaimManagement.vue
Normal file
751
insurance_admin-system/src/views/ClaimManagement.vue
Normal file
@@ -0,0 +1,751 @@
|
||||
<template>
|
||||
<div class="claim-management">
|
||||
<a-page-header
|
||||
title="理赔管理"
|
||||
sub-title="管理系统所有理赔申请"
|
||||
>
|
||||
<template #extra>
|
||||
<a-button type="primary" @click="showModal">
|
||||
<plus-outlined />
|
||||
新增理赔
|
||||
</a-button>
|
||||
</template>
|
||||
</a-page-header>
|
||||
|
||||
<!-- 搜索区域 -->
|
||||
<a-card style="margin-top: 16px">
|
||||
<a-form layout="inline" :model="searchForm">
|
||||
<a-form-item label="理赔单号">
|
||||
<a-input
|
||||
v-model:value="searchForm.claim_number"
|
||||
placeholder="请输入理赔单号"
|
||||
@pressEnter="handleSearch"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="保单号">
|
||||
<a-input
|
||||
v-model:value="searchForm.policy_number"
|
||||
placeholder="请输入保单号"
|
||||
@pressEnter="handleSearch"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="申请人">
|
||||
<a-input
|
||||
v-model:value="searchForm.applicant_name"
|
||||
placeholder="请输入申请人姓名"
|
||||
@pressEnter="handleSearch"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="状态">
|
||||
<a-select
|
||||
v-model:value="searchForm.status"
|
||||
placeholder="请选择状态"
|
||||
style="width: 120px"
|
||||
>
|
||||
<a-select-option value="">全部</a-select-option>
|
||||
<a-select-option value="pending">待审核</a-select-option>
|
||||
<a-select-option value="approved">已通过</a-select-option>
|
||||
<a-select-option value="rejected">已拒绝</a-select-option>
|
||||
<a-select-option value="processing">处理中</a-select-option>
|
||||
<a-select-option value="completed">已完成</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="handleSearch">
|
||||
<search-outlined />
|
||||
搜索
|
||||
</a-button>
|
||||
<a-button style="margin-left: 8px" @click="resetSearch">
|
||||
<redo-outlined />
|
||||
重置
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<!-- 理赔表格 -->
|
||||
<a-card style="margin-top: 16px">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="claimList"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
@change="handleTableChange"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="getStatusColor(record.status)">
|
||||
{{ getStatusText(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'claim_amount'">
|
||||
<span>¥{{ record.claim_amount?.toLocaleString() }}</span>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'approved_amount'">
|
||||
<span v-if="record.approved_amount">¥{{ record.approved_amount?.toLocaleString() }}</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button size="small" @click="handleView(record)">查看</a-button>
|
||||
<a-button
|
||||
size="small"
|
||||
:type="record.status === 'pending' ? 'primary' : 'default'"
|
||||
@click="handleProcess(record)"
|
||||
:disabled="record.status !== 'pending'"
|
||||
>
|
||||
处理
|
||||
</a-button>
|
||||
<a-dropdown>
|
||||
<a-button size="small">更多</a-button>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item @click="handleEdit(record)">编辑</a-menu-item>
|
||||
<a-menu-item @click="handleApprove(record)" :disabled="record.status !== 'pending'">
|
||||
通过
|
||||
</a-menu-item>
|
||||
<a-menu-item @click="handleReject(record)" :disabled="record.status !== 'pending'">
|
||||
拒绝
|
||||
</a-menu-item>
|
||||
<a-menu-item danger @click="handleDelete(record.id)">删除</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 查看详情模态框 -->
|
||||
<a-modal
|
||||
v-model:visible="detailVisible"
|
||||
title="理赔详情"
|
||||
width="800px"
|
||||
:footer="null"
|
||||
>
|
||||
<a-descriptions
|
||||
v-if="currentClaim"
|
||||
title="基本信息"
|
||||
bordered
|
||||
:column="2"
|
||||
>
|
||||
<a-descriptions-item label="理赔单号">{{ currentClaim.claim_number }}</a-descriptions-item>
|
||||
<a-descriptions-item label="保单号">{{ currentClaim.policy_number }}</a-descriptions-item>
|
||||
<a-descriptions-item label="申请人">{{ currentClaim.applicant_name }}</a-descriptions-item>
|
||||
<a-descriptions-item label="联系电话">{{ currentClaim.phone }}</a-descriptions-item>
|
||||
<a-descriptions-item label="申请金额">¥{{ currentClaim.claim_amount?.toLocaleString() }}</a-descriptions-item>
|
||||
<a-descriptions-item label="审核金额" v-if="currentClaim.approved_amount">
|
||||
¥{{ currentClaim.approved_amount?.toLocaleString() }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="申请时间">{{ currentClaim.apply_date }}</a-descriptions-item>
|
||||
<a-descriptions-item label="事故时间">{{ currentClaim.accident_date }}</a-descriptions-item>
|
||||
<a-descriptions-item label="状态">
|
||||
<a-tag :color="getStatusColor(currentClaim.status)">
|
||||
{{ getStatusText(currentClaim.status) }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="审核人">{{ currentClaim.reviewer_name || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="审核时间">{{ currentClaim.review_date || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="拒绝原因" :span="2" v-if="currentClaim.reject_reason">
|
||||
{{ currentClaim.reject_reason }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
|
||||
<a-divider />
|
||||
|
||||
<a-descriptions
|
||||
title="事故详情"
|
||||
bordered
|
||||
:column="1"
|
||||
>
|
||||
<a-descriptions-item label="事故描述">
|
||||
{{ currentClaim.accident_description }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="处理过程">
|
||||
{{ currentClaim.process_description || '暂无处理过程' }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
|
||||
<a-divider />
|
||||
|
||||
<a-descriptions
|
||||
title="相关文档"
|
||||
bordered
|
||||
:column="1"
|
||||
>
|
||||
<a-descriptions-item label="附件列表">
|
||||
<div v-if="currentClaim.documents && currentClaim.documents.length > 0">
|
||||
<a-space direction="vertical" style="width: 100%">
|
||||
<div v-for="(doc, index) in currentClaim.documents" :key="index" class="document-item">
|
||||
<file-text-outlined />
|
||||
<a :href="doc.url" target="_blank">{{ doc.name }}</a>
|
||||
<span class="document-size">({{ doc.size }})</span>
|
||||
</div>
|
||||
</a-space>
|
||||
</div>
|
||||
<span v-else>暂无附件</span>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-modal>
|
||||
|
||||
<!-- 处理理赔模态框 -->
|
||||
<a-modal
|
||||
v-model:visible="processVisible"
|
||||
title="处理理赔申请"
|
||||
width="600px"
|
||||
@ok="handleProcessOk"
|
||||
@cancel="handleProcessCancel"
|
||||
>
|
||||
<a-form
|
||||
ref="processFormRef"
|
||||
:model="processForm"
|
||||
:rules="processRules"
|
||||
layout="vertical"
|
||||
>
|
||||
<a-form-item label="审核金额" name="approved_amount">
|
||||
<a-input-number
|
||||
v-model:value="processForm.approved_amount"
|
||||
:min="0"
|
||||
:max="currentClaim?.claim_amount"
|
||||
:step="100"
|
||||
style="width: 100%"
|
||||
placeholder="请输入审核金额"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="处理结果" name="status">
|
||||
<a-radio-group v-model:value="processForm.status">
|
||||
<a-radio value="approved">通过</a-radio>
|
||||
<a-radio value="rejected">拒绝</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
v-if="processForm.status === 'rejected'"
|
||||
label="拒绝原因"
|
||||
name="reject_reason"
|
||||
>
|
||||
<a-textarea
|
||||
v-model:value="processForm.reject_reason"
|
||||
placeholder="请输入拒绝原因"
|
||||
:rows="3"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="处理说明" name="process_description">
|
||||
<a-textarea
|
||||
v-model:value="processForm.process_description"
|
||||
placeholder="请输入处理说明"
|
||||
:rows="3"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
|
||||
<!-- 新增/编辑模态框 -->
|
||||
<a-modal
|
||||
v-model:visible="modalVisible"
|
||||
:title="modalTitle"
|
||||
width="800px"
|
||||
@ok="handleModalOk"
|
||||
@cancel="handleModalCancel"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="formState"
|
||||
:rules="rules"
|
||||
layout="vertical"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="理赔单号" name="claim_number">
|
||||
<a-input v-model:value="formState.claim_number" placeholder="请输入理赔单号" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="保单号" name="policy_number">
|
||||
<a-input v-model:value="formState.policy_number" placeholder="请输入保单号" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="申请人姓名" name="applicant_name">
|
||||
<a-input v-model:value="formState.applicant_name" placeholder="请输入申请人姓名" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="联系电话" name="phone">
|
||||
<a-input v-model:value="formState.phone" placeholder="请输入联系电话" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="申请金额" name="claim_amount">
|
||||
<a-input-number
|
||||
v-model:value="formState.claim_amount"
|
||||
:min="0"
|
||||
:step="100"
|
||||
style="width: 100%"
|
||||
placeholder="请输入申请金额"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="申请日期" name="apply_date">
|
||||
<a-date-picker
|
||||
v-model:value="formState.apply_date"
|
||||
style="width: 100%"
|
||||
placeholder="请选择申请日期"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="事故日期" name="accident_date">
|
||||
<a-date-picker
|
||||
v-model:value="formState.accident_date"
|
||||
style="width: 100%"
|
||||
placeholder="请选择事故日期"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="状态" name="status">
|
||||
<a-select v-model:value="formState.status" placeholder="请选择状态">
|
||||
<a-select-option value="pending">待审核</a-select-option>
|
||||
<a-select-option value="processing">处理中</a-select-option>
|
||||
<a-select-option value="approved">已通过</a-select-option>
|
||||
<a-select-option value="rejected">已拒绝</a-select-option>
|
||||
<a-select-option value="completed">已完成</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item label="事故描述" name="accident_description">
|
||||
<a-textarea
|
||||
v-model:value="formState.accident_description"
|
||||
placeholder="请输入事故详细描述"
|
||||
:rows="4"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="处理说明" name="process_description">
|
||||
<a-textarea
|
||||
v-model:value="formState.process_description"
|
||||
placeholder="请输入处理说明"
|
||||
:rows="3"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="拒绝原因" name="reject_reason">
|
||||
<a-textarea
|
||||
v-model:value="formState.reject_reason"
|
||||
placeholder="请输入拒绝原因(如适用)"
|
||||
:rows="2"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import {
|
||||
PlusOutlined,
|
||||
SearchOutlined,
|
||||
RedoOutlined,
|
||||
FileTextOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
|
||||
const loading = ref(false)
|
||||
const modalVisible = ref(false)
|
||||
const detailVisible = ref(false)
|
||||
const processVisible = ref(false)
|
||||
const editingId = ref(null)
|
||||
const currentClaim = ref(null)
|
||||
const claimList = ref([])
|
||||
const formRef = ref()
|
||||
const processFormRef = ref()
|
||||
|
||||
const searchForm = reactive({
|
||||
claim_number: '',
|
||||
policy_number: '',
|
||||
applicant_name: '',
|
||||
status: ''
|
||||
})
|
||||
|
||||
const formState = reactive({
|
||||
claim_number: '',
|
||||
policy_number: '',
|
||||
applicant_name: '',
|
||||
phone: '',
|
||||
claim_amount: null,
|
||||
apply_date: null,
|
||||
accident_date: null,
|
||||
status: 'pending',
|
||||
accident_description: '',
|
||||
process_description: '',
|
||||
reject_reason: '',
|
||||
approved_amount: null
|
||||
})
|
||||
|
||||
const processForm = reactive({
|
||||
approved_amount: null,
|
||||
status: 'approved',
|
||||
reject_reason: '',
|
||||
process_description: ''
|
||||
})
|
||||
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total) => `共 ${total} 条记录`
|
||||
})
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '理赔单号',
|
||||
dataIndex: 'claim_number',
|
||||
key: 'claim_number'
|
||||
},
|
||||
{
|
||||
title: '保单号',
|
||||
dataIndex: 'policy_number',
|
||||
key: 'policy_number'
|
||||
},
|
||||
{
|
||||
title: '申请人',
|
||||
dataIndex: 'applicant_name',
|
||||
key: 'applicant_name'
|
||||
},
|
||||
{
|
||||
title: '申请金额',
|
||||
key: 'claim_amount',
|
||||
dataIndex: 'claim_amount'
|
||||
},
|
||||
{
|
||||
title: '审核金额',
|
||||
key: 'approved_amount',
|
||||
dataIndex: 'approved_amount'
|
||||
},
|
||||
{
|
||||
title: '申请日期',
|
||||
dataIndex: 'apply_date',
|
||||
key: 'apply_date',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
dataIndex: 'status'
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'created_at',
|
||||
key: 'created_at',
|
||||
width: 180
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 250,
|
||||
fixed: 'right'
|
||||
}
|
||||
]
|
||||
|
||||
const rules = {
|
||||
claim_number: [{ required: true, message: '请输入理赔单号' }],
|
||||
policy_number: [{ required: true, message: '请输入保单号' }],
|
||||
applicant_name: [{ required: true, message: '请输入申请人姓名' }],
|
||||
claim_amount: [{ required: true, message: '请输入申请金额' }],
|
||||
apply_date: [{ required: true, message: '请选择申请日期' }],
|
||||
accident_date: [{ required: true, message: '请选择事故日期' }],
|
||||
status: [{ required: true, message: '请选择状态' }],
|
||||
accident_description: [{ required: true, message: '请输入事故描述' }]
|
||||
}
|
||||
|
||||
const processRules = {
|
||||
approved_amount: [{ required: true, message: '请输入审核金额' }],
|
||||
status: [{ required: true, message: '请选择处理结果' }],
|
||||
reject_reason: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入拒绝原因',
|
||||
trigger: 'change',
|
||||
validator: (_, value) => {
|
||||
if (processForm.status === 'rejected' && !value) {
|
||||
return Promise.reject('拒绝时必须填写原因')
|
||||
}
|
||||
return Promise.resolve()
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const modalTitle = computed(() => {
|
||||
return editingId.value ? '编辑理赔' : '新增理赔'
|
||||
})
|
||||
|
||||
const getStatusColor = (status) => {
|
||||
const colors = {
|
||||
pending: 'orange',
|
||||
approved: 'green',
|
||||
rejected: 'red',
|
||||
processing: 'blue',
|
||||
completed: 'purple'
|
||||
}
|
||||
return colors[status] || 'default'
|
||||
}
|
||||
|
||||
const getStatusText = (status) => {
|
||||
const texts = {
|
||||
pending: '待审核',
|
||||
approved: '已通过',
|
||||
rejected: '已拒绝',
|
||||
processing: '处理中',
|
||||
completed: '已完成'
|
||||
}
|
||||
return texts[status] || '未知'
|
||||
}
|
||||
|
||||
const loadClaims = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {
|
||||
page: pagination.current,
|
||||
pageSize: pagination.pageSize,
|
||||
...searchForm
|
||||
}
|
||||
|
||||
// 这里应该是实际的API调用
|
||||
// const response = await claimAPI.getList(params)
|
||||
// claimList.value = response.data.list
|
||||
// pagination.total = response.data.total
|
||||
|
||||
// 模拟数据
|
||||
claimList.value = [
|
||||
{
|
||||
id: 1,
|
||||
claim_number: 'CLM20240001',
|
||||
policy_number: 'POL20240001',
|
||||
applicant_name: '张三',
|
||||
phone: '13800138000',
|
||||
claim_amount: 50000,
|
||||
approved_amount: 45000,
|
||||
apply_date: '2024-01-15',
|
||||
accident_date: '2024-01-10',
|
||||
status: 'approved',
|
||||
accident_description: '交通事故,车辆前部受损',
|
||||
process_description: '已核实事故情况,符合理赔条件',
|
||||
reject_reason: '',
|
||||
reviewer_name: '李审核员',
|
||||
review_date: '2024-01-16',
|
||||
documents: [
|
||||
{ name: '事故照片.jpg', size: '2.5MB', url: '#' },
|
||||
{ name: '维修报价单.pdf', size: '1.2MB', url: '#' }
|
||||
],
|
||||
created_at: '2024-01-15 10:00:00',
|
||||
updated_at: '2024-01-16 14:30:00'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
claim_number: 'CLM20240002',
|
||||
policy_number: 'POL20240002',
|
||||
applicant_name: '李四',
|
||||
phone: '13800138001',
|
||||
claim_amount: 100000,
|
||||
approved_amount: null,
|
||||
apply_date: '2024-01-20',
|
||||
accident_date: '2024-01-18',
|
||||
status: 'pending',
|
||||
accident_description: '家庭财产损失,水管爆裂',
|
||||
process_description: '',
|
||||
reject_reason: '',
|
||||
reviewer_name: null,
|
||||
review_date: null,
|
||||
documents: [
|
||||
{ name: '损失评估报告.pdf', size: '3.1MB', url: '#' }
|
||||
],
|
||||
created_at: '2024-01-20 14:30:00',
|
||||
updated_at: '2024-01-20 14:30:00'
|
||||
}
|
||||
]
|
||||
pagination.total = 2
|
||||
} catch (error) {
|
||||
message.error('加载理赔列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
pagination.current = 1
|
||||
loadClaims()
|
||||
}
|
||||
|
||||
const resetSearch = () => {
|
||||
searchForm.claim_number = ''
|
||||
searchForm.policy_number = ''
|
||||
searchForm.applicant_name = ''
|
||||
searchForm.status = ''
|
||||
handleSearch()
|
||||
}
|
||||
|
||||
const handleTableChange = (pag) => {
|
||||
pagination.current = pag.current
|
||||
pagination.pageSize = pag.pageSize
|
||||
loadClaims()
|
||||
}
|
||||
|
||||
const showModal = () => {
|
||||
editingId.value = null
|
||||
Object.assign(formState, {
|
||||
claim_number: '',
|
||||
policy_number: '',
|
||||
applicant_name: '',
|
||||
phone: '',
|
||||
claim_amount: null,
|
||||
apply_date: null,
|
||||
accident_date: null,
|
||||
status: 'pending',
|
||||
accident_description: '',
|
||||
process_description: '',
|
||||
reject_reason: '',
|
||||
approved_amount: null
|
||||
})
|
||||
modalVisible.value = true
|
||||
}
|
||||
|
||||
const handleView = (record) => {
|
||||
currentClaim.value = record
|
||||
detailVisible.value = true
|
||||
}
|
||||
|
||||
const handleProcess = (record) => {
|
||||
currentClaim.value = record
|
||||
Object.assign(processForm, {
|
||||
approved_amount: record.claim_amount,
|
||||
status: 'approved',
|
||||
reject_reason: '',
|
||||
process_description: ''
|
||||
})
|
||||
processVisible.value = true
|
||||
}
|
||||
|
||||
const handleEdit = (record) => {
|
||||
editingId.value = record.id
|
||||
Object.assign(formState, {
|
||||
claim_number: record.claim_number,
|
||||
policy_number: record.policy_number,
|
||||
applicant_name: record.applicant_name,
|
||||
phone: record.phone,
|
||||
claim_amount: record.claim_amount,
|
||||
apply_date: record.apply_date,
|
||||
accident_date: record.accident_date,
|
||||
status: record.status,
|
||||
accident_description: record.accident_description,
|
||||
process_description: record.process_description,
|
||||
reject_reason: record.reject_reason,
|
||||
approved_amount: record.approved_amount
|
||||
})
|
||||
modalVisible.value = true
|
||||
}
|
||||
|
||||
const handleApprove = async (record) => {
|
||||
try {
|
||||
// await claimAPI.approve(record.id, { approved_amount: record.claim_amount })
|
||||
message.success('理赔申请已通过')
|
||||
loadClaims()
|
||||
} catch (error) {
|
||||
message.error('操作失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleReject = async (record) => {
|
||||
message.info('请使用处理功能填写拒绝原因')
|
||||
}
|
||||
|
||||
const handleProcessOk = async () => {
|
||||
try {
|
||||
await processFormRef.value.validate()
|
||||
|
||||
// await claimAPI.process(currentClaim.value.id, processForm)
|
||||
message.success('处理完成')
|
||||
processVisible.value = false
|
||||
loadClaims()
|
||||
} catch (error) {
|
||||
console.log('表单验证失败', error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleProcessCancel = () => {
|
||||
processVisible.value = false
|
||||
}
|
||||
|
||||
const handleModalOk = async () => {
|
||||
try {
|
||||
await formRef.value.validate()
|
||||
|
||||
if (editingId.value) {
|
||||
// await claimAPI.update(editingId.value, formState)
|
||||
message.success('理赔更新成功')
|
||||
} else {
|
||||
// await claimAPI.create(formState)
|
||||
message.success('理赔创建成功')
|
||||
}
|
||||
|
||||
modalVisible.value = false
|
||||
loadClaims()
|
||||
} catch (error) {
|
||||
console.log('表单验证失败', error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleModalCancel = () => {
|
||||
modalVisible.value = false
|
||||
}
|
||||
|
||||
const handleDelete = async (id) => {
|
||||
try {
|
||||
// await claimAPI.delete(id)
|
||||
message.success('理赔删除成功')
|
||||
loadClaims()
|
||||
} catch (error) {
|
||||
message.error('理赔删除失败')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadClaims()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.claim-management {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.document-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.document-size {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
285
insurance_admin-system/src/views/Dashboard.vue
Normal file
285
insurance_admin-system/src/views/Dashboard.vue
Normal file
@@ -0,0 +1,285 @@
|
||||
<template>
|
||||
<div class="dashboard">
|
||||
<a-page-header
|
||||
title="仪表板"
|
||||
sub-title="系统概览和统计数据"
|
||||
/>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<a-row :gutter="16" style="margin-top: 24px">
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<div class="stat-card">
|
||||
<user-outlined style="color: #1890ff; font-size: 24px" />
|
||||
<div class="stat-content">
|
||||
<div class="stat-number">{{ stats.totalUsers || 0 }}</div>
|
||||
<div class="stat-label">总用户数</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<div class="stat-card">
|
||||
<insurance-outlined style="color: #52c41a; font-size: 24px" />
|
||||
<div class="stat-content">
|
||||
<div class="stat-number">{{ stats.totalApplications || 0 }}</div>
|
||||
<div class="stat-label">保险申请</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<div class="stat-card">
|
||||
<file-done-outlined style="color: #faad14; font-size: 24px" />
|
||||
<div class="stat-content">
|
||||
<div class="stat-number">{{ stats.totalPolicies || 0 }}</div>
|
||||
<div class="stat-label">有效保单</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<div class="stat-card">
|
||||
<safety-certificate-outlined style="color: #f5222d; font-size: 24px" />
|
||||
<div class="stat-content">
|
||||
<div class="stat-number">{{ stats.totalClaims || 0 }}</div>
|
||||
<div class="stat-label">理赔申请</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 图表区域 -->
|
||||
<a-row :gutter="16" style="margin-top: 24px">
|
||||
<a-col :span="12">
|
||||
<a-card title="保险申请趋势" :bordered="false">
|
||||
<div style="height: 300px">
|
||||
<!-- 这里可以放置ECharts图表 -->
|
||||
<div style="text-align: center; padding: 60px 0; color: #999">
|
||||
<bar-chart-outlined style="font-size: 48px" />
|
||||
<p>图表区域 - 申请趋势</p>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="12">
|
||||
<a-card title="保单状态分布" :bordered="false">
|
||||
<div style="height: 300px">
|
||||
<!-- 这里可以放置ECharts饼图 -->
|
||||
<div style="text-align: center; padding: 60px 0; color: #999">
|
||||
<pie-chart-outlined style="font-size: 48px" />
|
||||
<p>图表区域 - 状态分布</p>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 最近活动 -->
|
||||
<a-card title="最近活动" style="margin-top: 24px">
|
||||
<a-list
|
||||
item-layout="horizontal"
|
||||
:data-source="recentActivities"
|
||||
:loading="loading"
|
||||
>
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item>
|
||||
<a-list-item-meta
|
||||
:description="item.description"
|
||||
>
|
||||
<template #title>
|
||||
<span :style="{ color: getActivityColor(item.type) }">{{ item.title }}</span>
|
||||
</template>
|
||||
<template #avatar>
|
||||
<a-avatar :style="{ backgroundColor: getActivityColor(item.type) }">
|
||||
{{ getActivityIcon(item.type) }}
|
||||
</a-avatar>
|
||||
</template>
|
||||
</a-list-item-meta>
|
||||
<div>{{ formatTime(item.created_at) }}</div>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import {
|
||||
UserOutlined,
|
||||
InsuranceOutlined,
|
||||
FileDoneOutlined,
|
||||
SafetyCertificateOutlined,
|
||||
BarChartOutlined,
|
||||
PieChartOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
import { dashboardAPI } from '@/utils/api'
|
||||
import { message } from 'ant-design-vue'
|
||||
|
||||
const loading = ref(false)
|
||||
const stats = ref({})
|
||||
const recentActivities = ref([])
|
||||
|
||||
const getActivityColor = (type) => {
|
||||
const colors = {
|
||||
user: '#1890ff',
|
||||
application: '#52c41a',
|
||||
policy: '#faad14',
|
||||
claim: '#f5222d'
|
||||
}
|
||||
return colors[type] || '#666'
|
||||
}
|
||||
|
||||
const getActivityIcon = (type) => {
|
||||
const icons = {
|
||||
user: 'U',
|
||||
application: 'A',
|
||||
policy: 'P',
|
||||
claim: 'C'
|
||||
}
|
||||
return icons[type] || '?'
|
||||
}
|
||||
|
||||
const formatTime = (time) => {
|
||||
return new Date(time).toLocaleString()
|
||||
}
|
||||
|
||||
const getLogType = (message) => {
|
||||
if (message.includes('用户') || message.includes('注册') || message.includes('登录')) {
|
||||
return 'user'
|
||||
} else if (message.includes('申请') || message.includes('投保')) {
|
||||
return 'application'
|
||||
} else if (message.includes('保单') || message.includes('合同')) {
|
||||
return 'policy'
|
||||
} else if (message.includes('理赔') || message.includes('赔偿')) {
|
||||
return 'claim'
|
||||
}
|
||||
return 'system'
|
||||
}
|
||||
|
||||
const getLogTitle = (level, message) => {
|
||||
if (level === 'info') {
|
||||
if (message.includes('启动') || message.includes('连接')) {
|
||||
return '系统操作'
|
||||
}
|
||||
return '信息通知'
|
||||
} else if (level === 'warning') {
|
||||
return '警告信息'
|
||||
} else if (level === 'error') {
|
||||
return '错误报告'
|
||||
}
|
||||
return '系统消息'
|
||||
}
|
||||
|
||||
const loadDashboardData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// 获取统计信息
|
||||
const statsResponse = await dashboardAPI.getStats()
|
||||
if (statsResponse.status === 'success') {
|
||||
stats.value = {
|
||||
totalUsers: statsResponse.data.overview?.users || 0,
|
||||
totalApplications: statsResponse.data.overview?.applications || 0,
|
||||
totalPolicies: statsResponse.data.overview?.policies || 0,
|
||||
totalClaims: statsResponse.data.overview?.claims || 0
|
||||
}
|
||||
}
|
||||
|
||||
// 获取最近活动(使用系统日志作为活动记录)
|
||||
const activitiesResponse = await dashboardAPI.getRecentActivities()
|
||||
if (activitiesResponse.status === 'success') {
|
||||
recentActivities.value = activitiesResponse.data.logs?.slice(0, 10).map(log => ({
|
||||
id: log.id,
|
||||
type: getLogType(log.message),
|
||||
title: getLogTitle(log.level, log.message),
|
||||
description: log.message,
|
||||
created_at: log.timestamp
|
||||
})) || []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载仪表板数据失败:', error)
|
||||
message.error('加载数据失败')
|
||||
|
||||
// 如果API调用失败,使用模拟数据
|
||||
stats.value = {
|
||||
totalUsers: 128,
|
||||
totalApplications: 356,
|
||||
totalPolicies: 289,
|
||||
totalClaims: 45
|
||||
}
|
||||
|
||||
recentActivities.value = [
|
||||
{
|
||||
id: 1,
|
||||
type: 'application',
|
||||
title: '新的保险申请',
|
||||
description: '张三提交了车险申请',
|
||||
created_at: new Date().toISOString()
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: 'policy',
|
||||
title: '保单生效',
|
||||
description: '李四的寿险保单已生效',
|
||||
created_at: new Date(Date.now() - 3600000).toISOString()
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
type: 'claim',
|
||||
title: '理赔申请',
|
||||
description: '王五提交了医疗险理赔',
|
||||
created_at: new Date(Date.now() - 7200000).toISOString()
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
type: 'user',
|
||||
title: '新用户注册',
|
||||
description: '新用户赵六完成注册',
|
||||
created_at: new Date(Date.now() - 10800000).toISOString()
|
||||
}
|
||||
]
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadDashboardData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.stat-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.stat-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.dashboard {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
447
insurance_admin-system/src/views/InsuranceTypeManagement.vue
Normal file
447
insurance_admin-system/src/views/InsuranceTypeManagement.vue
Normal file
@@ -0,0 +1,447 @@
|
||||
<template>
|
||||
<div class="insurance-type-management">
|
||||
<a-page-header
|
||||
title="保险类型管理"
|
||||
sub-title="管理系统支持的保险产品类型"
|
||||
>
|
||||
<template #extra>
|
||||
<a-button type="primary" @click="showModal">
|
||||
<plus-outlined />
|
||||
新增类型
|
||||
</a-button>
|
||||
</template>
|
||||
</a-page-header>
|
||||
|
||||
<!-- 搜索区域 -->
|
||||
<a-card style="margin-top: 16px">
|
||||
<a-form layout="inline" :model="searchForm">
|
||||
<a-form-item label="类型名称">
|
||||
<a-input
|
||||
v-model:value="searchForm.name"
|
||||
placeholder="请输入类型名称"
|
||||
@pressEnter="handleSearch"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="状态">
|
||||
<a-select
|
||||
v-model:value="searchForm.status"
|
||||
placeholder="请选择状态"
|
||||
style="width: 120px"
|
||||
>
|
||||
<a-select-option value="">全部</a-select-option>
|
||||
<a-select-option value="active">启用</a-select-option>
|
||||
<a-select-option value="inactive">禁用</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="handleSearch">
|
||||
<search-outlined />
|
||||
搜索
|
||||
</a-button>
|
||||
<a-button style="margin-left: 8px" @click="resetSearch">
|
||||
<redo-outlined />
|
||||
重置
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<!-- 类型表格 -->
|
||||
<a-card style="margin-top: 16px">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="typeList"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
@change="handleTableChange"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="record.status === 'active' ? 'green' : 'red'">
|
||||
{{ record.status === 'active' ? '启用' : '禁用' }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'coverage_type'">
|
||||
<span>{{ getCoverageTypeText(record.coverage_type) }}</span>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button size="small" @click="handleEdit(record)">编辑</a-button>
|
||||
<a-button
|
||||
size="small"
|
||||
:type="record.status === 'active' ? 'danger' : 'primary'"
|
||||
@click="handleToggleStatus(record)"
|
||||
>
|
||||
{{ record.status === 'active' ? '禁用' : '启用' }}
|
||||
</a-button>
|
||||
<a-popconfirm
|
||||
title="确定要删除这个保险类型吗?"
|
||||
@confirm="handleDelete(record.id)"
|
||||
>
|
||||
<a-button size="small" danger>删除</a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 新增/编辑模态框 -->
|
||||
<a-modal
|
||||
v-model:visible="modalVisible"
|
||||
:title="modalTitle"
|
||||
width="600px"
|
||||
@ok="handleModalOk"
|
||||
@cancel="handleModalCancel"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="formState"
|
||||
:rules="rules"
|
||||
layout="vertical"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="类型名称" name="name">
|
||||
<a-input v-model:value="formState.name" placeholder="请输入类型名称" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="类型代码" name="code">
|
||||
<a-input v-model:value="formState.code" placeholder="请输入类型代码" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item label="描述" name="description">
|
||||
<a-textarea
|
||||
v-model:value="formState.description"
|
||||
placeholder="请输入类型描述"
|
||||
:rows="3"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="保障类型" name="coverage_type">
|
||||
<a-select v-model:value="formState.coverage_type" placeholder="请选择保障类型">
|
||||
<a-select-option value="life">人寿保险</a-select-option>
|
||||
<a-select-option value="health">健康保险</a-select-option>
|
||||
<a-select-option value="property">财产保险</a-select-option>
|
||||
<a-select-option value="accident">意外保险</a-select-option>
|
||||
<a-select-option value="travel">旅行保险</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="状态" name="status">
|
||||
<a-select v-model:value="formState.status" placeholder="请选择状态">
|
||||
<a-select-option value="active">启用</a-select-option>
|
||||
<a-select-option value="inactive">禁用</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="最小保额" name="min_coverage">
|
||||
<a-input-number
|
||||
v-model:value="formState.min_coverage"
|
||||
:min="0"
|
||||
:step="1000"
|
||||
style="width: 100%"
|
||||
placeholder="最小保额"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="最大保额" name="max_coverage">
|
||||
<a-input-number
|
||||
v-model:value="formState.max_coverage"
|
||||
:min="0"
|
||||
:step="1000"
|
||||
style="width: 100%"
|
||||
placeholder="最大保额"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item label="保费计算规则" name="premium_rules">
|
||||
<a-textarea
|
||||
v-model:value="formState.premium_rules"
|
||||
placeholder="请输入保费计算规则(JSON格式)"
|
||||
:rows="3"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import {
|
||||
PlusOutlined,
|
||||
SearchOutlined,
|
||||
RedoOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
|
||||
const loading = ref(false)
|
||||
const modalVisible = ref(false)
|
||||
const editingId = ref(null)
|
||||
const typeList = ref([])
|
||||
const formRef = ref()
|
||||
|
||||
const searchForm = reactive({
|
||||
name: '',
|
||||
status: ''
|
||||
})
|
||||
|
||||
const formState = reactive({
|
||||
name: '',
|
||||
code: '',
|
||||
description: '',
|
||||
coverage_type: '',
|
||||
status: 'active',
|
||||
min_coverage: null,
|
||||
max_coverage: null,
|
||||
premium_rules: ''
|
||||
})
|
||||
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total) => `共 ${total} 条记录`
|
||||
})
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '类型名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name'
|
||||
},
|
||||
{
|
||||
title: '类型代码',
|
||||
dataIndex: 'code',
|
||||
key: 'code'
|
||||
},
|
||||
{
|
||||
title: '保障类型',
|
||||
key: 'coverage_type',
|
||||
dataIndex: 'coverage_type'
|
||||
},
|
||||
{
|
||||
title: '最小保额',
|
||||
dataIndex: 'min_coverage',
|
||||
key: 'min_coverage',
|
||||
render: (text) => text ? `¥${text.toLocaleString()}` : '-'
|
||||
},
|
||||
{
|
||||
title: '最大保额',
|
||||
dataIndex: 'max_coverage',
|
||||
key: 'max_coverage',
|
||||
render: (text) => text ? `¥${text.toLocaleString()}` : '-'
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
dataIndex: 'status'
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'created_at',
|
||||
key: 'created_at',
|
||||
width: 180
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 200,
|
||||
fixed: 'right'
|
||||
}
|
||||
]
|
||||
|
||||
const rules = {
|
||||
name: [{ required: true, message: '请输入类型名称' }],
|
||||
code: [{ required: true, message: '请输入类型代码' }],
|
||||
coverage_type: [{ required: true, message: '请选择保障类型' }],
|
||||
status: [{ required: true, message: '请选择状态' }]
|
||||
}
|
||||
|
||||
const modalTitle = computed(() => {
|
||||
return editingId.value ? '编辑保险类型' : '新增保险类型'
|
||||
})
|
||||
|
||||
const getCoverageTypeText = (type) => {
|
||||
const types = {
|
||||
life: '人寿保险',
|
||||
health: '健康保险',
|
||||
property: '财产保险',
|
||||
accident: '意外保险',
|
||||
travel: '旅行保险'
|
||||
}
|
||||
return types[type] || type
|
||||
}
|
||||
|
||||
const loadInsuranceTypes = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {
|
||||
page: pagination.current,
|
||||
pageSize: pagination.pageSize,
|
||||
...searchForm
|
||||
}
|
||||
|
||||
// 这里应该是实际的API调用
|
||||
// const response = await insuranceTypeAPI.getList(params)
|
||||
// typeList.value = response.data.list
|
||||
// pagination.total = response.data.total
|
||||
|
||||
// 模拟数据
|
||||
typeList.value = [
|
||||
{
|
||||
id: 1,
|
||||
name: '综合意外险',
|
||||
code: 'ACCIDENT_001',
|
||||
description: '提供全面的意外伤害保障',
|
||||
coverage_type: 'accident',
|
||||
status: 'active',
|
||||
min_coverage: 100000,
|
||||
max_coverage: 500000,
|
||||
premium_rules: '{\"base_rate\": 0.001, \"age_factor\": 1.2}',
|
||||
created_at: '2024-01-01 10:00:00'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '终身寿险',
|
||||
code: 'LIFE_001',
|
||||
description: '提供终身的人寿保障',
|
||||
coverage_type: 'life',
|
||||
status: 'active',
|
||||
min_coverage: 500000,
|
||||
max_coverage: 2000000,
|
||||
premium_rules: '{\"base_rate\": 0.0005, \"age_factor\": 1.5}',
|
||||
created_at: '2024-01-02 14:30:00'
|
||||
}
|
||||
]
|
||||
pagination.total = 2
|
||||
} catch (error) {
|
||||
message.error('加载保险类型列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
pagination.current = 1
|
||||
loadInsuranceTypes()
|
||||
}
|
||||
|
||||
const resetSearch = () => {
|
||||
searchForm.name = ''
|
||||
searchForm.status = ''
|
||||
handleSearch()
|
||||
}
|
||||
|
||||
const handleTableChange = (pag) => {
|
||||
pagination.current = pag.current
|
||||
pagination.pageSize = pag.pageSize
|
||||
loadInsuranceTypes()
|
||||
}
|
||||
|
||||
const showModal = () => {
|
||||
editingId.value = null
|
||||
Object.assign(formState, {
|
||||
name: '',
|
||||
code: '',
|
||||
description: '',
|
||||
coverage_type: '',
|
||||
status: 'active',
|
||||
min_coverage: null,
|
||||
max_coverage: null,
|
||||
premium_rules: ''
|
||||
})
|
||||
modalVisible.value = true
|
||||
}
|
||||
|
||||
const handleEdit = (record) => {
|
||||
editingId.value = record.id
|
||||
Object.assign(formState, {
|
||||
name: record.name,
|
||||
code: record.code,
|
||||
description: record.description,
|
||||
coverage_type: record.coverage_type,
|
||||
status: record.status,
|
||||
min_coverage: record.min_coverage,
|
||||
max_coverage: record.max_coverage,
|
||||
premium_rules: record.premium_rules
|
||||
})
|
||||
modalVisible.value = true
|
||||
}
|
||||
|
||||
const handleModalOk = async () => {
|
||||
try {
|
||||
await formRef.value.validate()
|
||||
|
||||
if (editingId.value) {
|
||||
// await insuranceTypeAPI.update(editingId.value, formState)
|
||||
message.success('保险类型更新成功')
|
||||
} else {
|
||||
// await insuranceTypeAPI.create(formState)
|
||||
message.success('保险类型创建成功')
|
||||
}
|
||||
|
||||
modalVisible.value = false
|
||||
loadInsuranceTypes()
|
||||
} catch (error) {
|
||||
console.log('表单验证失败', error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleModalCancel = () => {
|
||||
modalVisible.value = false
|
||||
}
|
||||
|
||||
const handleToggleStatus = async (record) => {
|
||||
try {
|
||||
const newStatus = record.status === 'active' ? 'inactive' : 'active'
|
||||
// await insuranceTypeAPI.update(record.id, { status: newStatus })
|
||||
message.success('状态更新成功')
|
||||
loadInsuranceTypes()
|
||||
} catch (error) {
|
||||
message.error('状态更新失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleDelete = async (id) => {
|
||||
try {
|
||||
// await insuranceTypeAPI.delete(id)
|
||||
message.success('保险类型删除成功')
|
||||
loadInsuranceTypes()
|
||||
} catch (error) {
|
||||
message.error('保险类型删除失败')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadInsuranceTypes()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.insurance-type-management {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
144
insurance_admin-system/src/views/Login.vue
Normal file
144
insurance_admin-system/src/views/Login.vue
Normal file
@@ -0,0 +1,144 @@
|
||||
<template>
|
||||
<div class="login-container">
|
||||
<div class="login-form">
|
||||
<div class="login-header">
|
||||
<h2>保险端口后台管理系统</h2>
|
||||
<p>请登录您的账户</p>
|
||||
</div>
|
||||
|
||||
<a-form
|
||||
:model="formState"
|
||||
name="login"
|
||||
autocomplete="off"
|
||||
@finish="onFinish"
|
||||
@finishFailed="onFinishFailed"
|
||||
>
|
||||
<a-form-item
|
||||
name="username"
|
||||
:rules="[{ required: true, message: '请输入用户名!' }]"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="formState.username"
|
||||
placeholder="用户名"
|
||||
size="large"
|
||||
>
|
||||
<template #prefix>
|
||||
<user-outlined />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
name="password"
|
||||
:rules="[{ required: true, message: '请输入密码!' }]"
|
||||
>
|
||||
<a-input-password
|
||||
v-model:value="formState.password"
|
||||
placeholder="密码"
|
||||
size="large"
|
||||
>
|
||||
<template #prefix>
|
||||
<lock-outlined />
|
||||
</template>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-button
|
||||
type="primary"
|
||||
html-type="submit"
|
||||
size="large"
|
||||
:loading="loading"
|
||||
block
|
||||
>
|
||||
登录
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<div class="login-footer">
|
||||
<p>© 2024 保险端口系统 - 后台管理系统</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { UserOutlined, LockOutlined } from '@ant-design/icons-vue'
|
||||
import { authAPI } from '@/utils/api'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
const loading = ref(false)
|
||||
|
||||
const formState = reactive({
|
||||
username: '',
|
||||
password: ''
|
||||
})
|
||||
|
||||
const onFinish = async (values) => {
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await authAPI.login(values)
|
||||
if (response.status === 'success') {
|
||||
userStore.setToken(response.data.token)
|
||||
userStore.setUserInfo(response.data.user)
|
||||
message.success('登录成功')
|
||||
router.push('/dashboard')
|
||||
} else {
|
||||
message.error(response.message || '登录失败')
|
||||
}
|
||||
} catch (error) {
|
||||
message.error(error.response?.data?.message || '登录失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const onFinishFailed = (errorInfo) => {
|
||||
console.log('Failed:', errorInfo)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.login-container {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
}
|
||||
|
||||
.login-form {
|
||||
width: 400px;
|
||||
padding: 40px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.login-header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.login-header h2 {
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.login-header p {
|
||||
color: #666;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.login-footer {
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
623
insurance_admin-system/src/views/PolicyManagement.vue
Normal file
623
insurance_admin-system/src/views/PolicyManagement.vue
Normal file
@@ -0,0 +1,623 @@
|
||||
<template>
|
||||
<div class="policy-management">
|
||||
<a-page-header
|
||||
title="保单管理"
|
||||
sub-title="管理系统所有保单信息"
|
||||
>
|
||||
<template #extra>
|
||||
<a-button type="primary" @click="showModal">
|
||||
<plus-outlined />
|
||||
新增保单
|
||||
</a-button>
|
||||
</template>
|
||||
</a-page-header>
|
||||
|
||||
<!-- 搜索区域 -->
|
||||
<a-card style="margin-top: 16px">
|
||||
<a-form layout="inline" :model="searchForm">
|
||||
<a-form-item label="保单号">
|
||||
<a-input
|
||||
v-model:value="searchForm.policy_number"
|
||||
placeholder="请输入保单号"
|
||||
@pressEnter="handleSearch"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="投保人">
|
||||
<a-input
|
||||
v-model:value="searchForm.policyholder_name"
|
||||
placeholder="请输入投保人姓名"
|
||||
@pressEnter="handleSearch"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="保险类型">
|
||||
<a-select
|
||||
v-model:value="searchForm.insurance_type_id"
|
||||
placeholder="请选择保险类型"
|
||||
style="width: 150px"
|
||||
>
|
||||
<a-select-option value="">全部类型</a-select-option>
|
||||
<a-select-option :value="1">综合意外险</a-select-option>
|
||||
<a-select-option :value="2">终身寿险</a-select-option>
|
||||
<a-select-option :value="3">健康医疗保险</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="状态">
|
||||
<a-select
|
||||
v-model:value="searchForm.status"
|
||||
placeholder="请选择状态"
|
||||
style="width: 120px"
|
||||
>
|
||||
<a-select-option value="">全部</a-select-option>
|
||||
<a-select-option value="active">有效</a-select-option>
|
||||
<a-select-option value="pending">待审核</a-select-option>
|
||||
<a-select-option value="expired">已过期</a-select-option>
|
||||
<a-select-option value="cancelled">已取消</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="handleSearch">
|
||||
<search-outlined />
|
||||
搜索
|
||||
</a-button>
|
||||
<a-button style="margin-left: 8px" @click="resetSearch">
|
||||
<redo-outlined />
|
||||
重置
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<!-- 保单表格 -->
|
||||
<a-card style="margin-top: 16px">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="policyList"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
@change="handleTableChange"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="getStatusColor(record.status)">
|
||||
{{ getStatusText(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'premium_amount'">
|
||||
<span>¥{{ record.premium_amount?.toLocaleString() }}</span>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'coverage_amount'">
|
||||
<span>¥{{ record.coverage_amount?.toLocaleString() }}</span>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button size="small" @click="handleView(record)">查看</a-button>
|
||||
<a-button size="small" @click="handleEdit(record)">编辑</a-button>
|
||||
<a-button
|
||||
size="small"
|
||||
:type="record.status === 'active' ? 'danger' : 'primary'"
|
||||
@click="handleToggleStatus(record)"
|
||||
>
|
||||
{{ record.status === 'active' ? '停用' : '启用' }}
|
||||
</a-button>
|
||||
<a-dropdown>
|
||||
<a-button size="small">更多</a-button>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item @click="handleRenew(record)">续保</a-menu-item>
|
||||
<a-menu-item @click="handleClaim(record)">理赔</a-menu-item>
|
||||
<a-menu-item danger @click="handleDelete(record.id)">删除</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 查看详情模态框 -->
|
||||
<a-modal
|
||||
v-model:visible="detailVisible"
|
||||
title="保单详情"
|
||||
width="800px"
|
||||
:footer="null"
|
||||
>
|
||||
<a-descriptions
|
||||
v-if="currentPolicy"
|
||||
title="基本信息"
|
||||
bordered
|
||||
:column="2"
|
||||
>
|
||||
<a-descriptions-item label="保单号">{{ currentPolicy.policy_number }}</a-descriptions-item>
|
||||
<a-descriptions-item label="保险类型">{{ currentPolicy.insurance_type_name }}</a-descriptions-item>
|
||||
<a-descriptions-item label="投保人">{{ currentPolicy.policyholder_name }}</a-descriptions-item>
|
||||
<a-descriptions-item label="被保险人">{{ currentPolicy.insured_name }}</a-descriptions-item>
|
||||
<a-descriptions-item label="保费金额">¥{{ currentPolicy.premium_amount?.toLocaleString() }}</a-descriptions-item>
|
||||
<a-descriptions-item label="保额">¥{{ currentPolicy.coverage_amount?.toLocaleString() }}</a-descriptions-item>
|
||||
<a-descriptions-item label="保险期间">
|
||||
{{ currentPolicy.start_date }} 至 {{ currentPolicy.end_date }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="状态">
|
||||
<a-tag :color="getStatusColor(currentPolicy.status)">
|
||||
{{ getStatusText(currentPolicy.status) }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="创建时间">{{ currentPolicy.created_at }}</a-descriptions-item>
|
||||
<a-descriptions-item label="最后更新">{{ currentPolicy.updated_at }}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
|
||||
<a-divider />
|
||||
|
||||
<a-descriptions
|
||||
title="联系信息"
|
||||
bordered
|
||||
:column="2"
|
||||
>
|
||||
<a-descriptions-item label="联系电话">{{ currentPolicy.phone }}</a-descriptions-item>
|
||||
<a-descriptions-item label="电子邮箱">{{ currentPolicy.email }}</a-descriptions-item>
|
||||
<a-descriptions-item label="联系地址" :span="2">
|
||||
{{ currentPolicy.address }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-modal>
|
||||
|
||||
<!-- 新增/编辑模态框 -->
|
||||
<a-modal
|
||||
v-model:visible="modalVisible"
|
||||
:title="modalTitle"
|
||||
width="800px"
|
||||
@ok="handleModalOk"
|
||||
@cancel="handleModalCancel"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="formState"
|
||||
:rules="rules"
|
||||
layout="vertical"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="保单号" name="policy_number">
|
||||
<a-input v-model:value="formState.policy_number" placeholder="请输入保单号" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="保险类型" name="insurance_type_id">
|
||||
<a-select v-model:value="formState.insurance_type_id" placeholder="请选择保险类型">
|
||||
<a-select-option :value="1">综合意外险</a-select-option>
|
||||
<a-select-option :value="2">终身寿险</a-select-option>
|
||||
<a-select-option :value="3">健康医疗保险</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="投保人姓名" name="policyholder_name">
|
||||
<a-input v-model:value="formState.policyholder_name" placeholder="请输入投保人姓名" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="被保险人姓名" name="insured_name">
|
||||
<a-input v-model:value="formState.insured_name" placeholder="请输入被保险人姓名" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="保费金额" name="premium_amount">
|
||||
<a-input-number
|
||||
v-model:value="formState.premium_amount"
|
||||
:min="0"
|
||||
:step="100"
|
||||
style="width: 100%"
|
||||
placeholder="请输入保费金额"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="保额" name="coverage_amount">
|
||||
<a-input-number
|
||||
v-model:value="formState.coverage_amount"
|
||||
:min="0"
|
||||
:step="1000"
|
||||
style="width: 100%"
|
||||
placeholder="请输入保额"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="保险开始日期" name="start_date">
|
||||
<a-date-picker
|
||||
v-model:value="formState.start_date"
|
||||
style="width: 100%"
|
||||
placeholder="请选择开始日期"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="保险结束日期" name="end_date">
|
||||
<a-date-picker
|
||||
v-model:value="formState.end_date"
|
||||
style="width: 100%"
|
||||
placeholder="请选择结束日期"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item label="状态" name="status">
|
||||
<a-select v-model:value="formState.status" placeholder="请选择状态">
|
||||
<a-select-option value="active">有效</a-select-option>
|
||||
<a-select-option value="pending">待审核</a-select-option>
|
||||
<a-select-option value="expired">已过期</a-select-option>
|
||||
<a-select-option value="cancelled">已取消</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="联系信息">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item name="phone">
|
||||
<a-input v-model:value="formState.phone" placeholder="联系电话" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item name="email">
|
||||
<a-input v-model:value="formState.email" placeholder="电子邮箱" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item name="address">
|
||||
<a-textarea
|
||||
v-model:value="formState.address"
|
||||
placeholder="联系地址"
|
||||
:rows="2"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="备注" name="remarks">
|
||||
<a-textarea
|
||||
v-model:value="formState.remarks"
|
||||
placeholder="请输入备注信息"
|
||||
:rows="3"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import {
|
||||
PlusOutlined,
|
||||
SearchOutlined,
|
||||
RedoOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
|
||||
const loading = ref(false)
|
||||
const modalVisible = ref(false)
|
||||
const detailVisible = ref(false)
|
||||
const editingId = ref(null)
|
||||
const currentPolicy = ref(null)
|
||||
const policyList = ref([])
|
||||
const formRef = ref()
|
||||
|
||||
const searchForm = reactive({
|
||||
policy_number: '',
|
||||
policyholder_name: '',
|
||||
insurance_type_id: '',
|
||||
status: ''
|
||||
})
|
||||
|
||||
const formState = reactive({
|
||||
policy_number: '',
|
||||
insurance_type_id: null,
|
||||
policyholder_name: '',
|
||||
insured_name: '',
|
||||
premium_amount: null,
|
||||
coverage_amount: null,
|
||||
start_date: null,
|
||||
end_date: null,
|
||||
status: 'active',
|
||||
phone: '',
|
||||
email: '',
|
||||
address: '',
|
||||
remarks: ''
|
||||
})
|
||||
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total) => `共 ${total} 条记录`
|
||||
})
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '保单号',
|
||||
dataIndex: 'policy_number',
|
||||
key: 'policy_number'
|
||||
},
|
||||
{
|
||||
title: '保险类型',
|
||||
dataIndex: 'insurance_type_name',
|
||||
key: 'insurance_type_name'
|
||||
},
|
||||
{
|
||||
title: '投保人',
|
||||
dataIndex: 'policyholder_name',
|
||||
key: 'policyholder_name'
|
||||
},
|
||||
{
|
||||
title: '被保险人',
|
||||
dataIndex: 'insured_name',
|
||||
key: 'insured_name'
|
||||
},
|
||||
{
|
||||
title: '保费金额',
|
||||
key: 'premium_amount',
|
||||
dataIndex: 'premium_amount'
|
||||
},
|
||||
{
|
||||
title: '保额',
|
||||
key: 'coverage_amount',
|
||||
dataIndex: 'coverage_amount'
|
||||
},
|
||||
{
|
||||
title: '保险期间',
|
||||
key: 'period',
|
||||
render: (_, record) => `${record.start_date} 至 ${record.end_date}`
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
dataIndex: 'status'
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'created_at',
|
||||
key: 'created_at',
|
||||
width: 180
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 250,
|
||||
fixed: 'right'
|
||||
}
|
||||
]
|
||||
|
||||
const rules = {
|
||||
policy_number: [{ required: true, message: '请输入保单号' }],
|
||||
insurance_type_id: [{ required: true, message: '请选择保险类型' }],
|
||||
policyholder_name: [{ required: true, message: '请输入投保人姓名' }],
|
||||
insured_name: [{ required: true, message: '请输入被保险人姓名' }],
|
||||
premium_amount: [{ required: true, message: '请输入保费金额' }],
|
||||
coverage_amount: [{ required: true, message: '请输入保额' }],
|
||||
start_date: [{ required: true, message: '请选择开始日期' }],
|
||||
end_date: [{ required: true, message: '请选择结束日期' }],
|
||||
status: [{ required: true, message: '请选择状态' }]
|
||||
}
|
||||
|
||||
const modalTitle = computed(() => {
|
||||
return editingId.value ? '编辑保单' : '新增保单'
|
||||
})
|
||||
|
||||
const getStatusColor = (status) => {
|
||||
const colors = {
|
||||
active: 'green',
|
||||
pending: 'orange',
|
||||
expired: 'default',
|
||||
cancelled: 'red'
|
||||
}
|
||||
return colors[status] || 'default'
|
||||
}
|
||||
|
||||
const getStatusText = (status) => {
|
||||
const texts = {
|
||||
active: '有效',
|
||||
pending: '待审核',
|
||||
expired: '已过期',
|
||||
cancelled: '已取消'
|
||||
}
|
||||
return texts[status] || '未知'
|
||||
}
|
||||
|
||||
const loadPolicies = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {
|
||||
page: pagination.current,
|
||||
pageSize: pagination.pageSize,
|
||||
...searchForm
|
||||
}
|
||||
|
||||
// 这里应该是实际的API调用
|
||||
// const response = await policyAPI.getList(params)
|
||||
// policyList.value = response.data.list
|
||||
// pagination.total = response.data.total
|
||||
|
||||
// 模拟数据
|
||||
policyList.value = [
|
||||
{
|
||||
id: 1,
|
||||
policy_number: 'POL20240001',
|
||||
insurance_type_id: 1,
|
||||
insurance_type_name: '综合意外险',
|
||||
policyholder_name: '张三',
|
||||
insured_name: '张三',
|
||||
premium_amount: 1200,
|
||||
coverage_amount: 500000,
|
||||
start_date: '2024-01-01',
|
||||
end_date: '2025-01-01',
|
||||
status: 'active',
|
||||
phone: '13800138000',
|
||||
email: 'zhangsan@example.com',
|
||||
address: '北京市朝阳区',
|
||||
created_at: '2024-01-01 10:00:00',
|
||||
updated_at: '2024-01-01 10:00:00'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
policy_number: 'POL20240002',
|
||||
insurance_type_id: 2,
|
||||
insurance_type_name: '终身寿险',
|
||||
policyholder_name: '李四',
|
||||
insured_name: '李四',
|
||||
premium_amount: 5000,
|
||||
coverage_amount: 1000000,
|
||||
start_date: '2024-01-02',
|
||||
end_date: '2074-01-02',
|
||||
status: 'active',
|
||||
phone: '13800138001',
|
||||
email: 'lisi@example.com',
|
||||
address: '上海市浦东新区',
|
||||
created_at: '2024-01-02 14:30:00',
|
||||
updated_at: '2024-01-02 14:30:00'
|
||||
}
|
||||
]
|
||||
pagination.total = 2
|
||||
} catch (error) {
|
||||
message.error('加载保单列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
pagination.current = 1
|
||||
loadPolicies()
|
||||
}
|
||||
|
||||
const resetSearch = () => {
|
||||
searchForm.policy_number = ''
|
||||
searchForm.policyholder_name = ''
|
||||
searchForm.insurance_type_id = ''
|
||||
searchForm.status = ''
|
||||
handleSearch()
|
||||
}
|
||||
|
||||
const handleTableChange = (pag) => {
|
||||
pagination.current = pag.current
|
||||
pagination.pageSize = pag.pageSize
|
||||
loadPolicies()
|
||||
}
|
||||
|
||||
const showModal = () => {
|
||||
editingId.value = null
|
||||
Object.assign(formState, {
|
||||
policy_number: '',
|
||||
insurance_type_id: null,
|
||||
policyholder_name: '',
|
||||
insured_name: '',
|
||||
premium_amount: null,
|
||||
coverage_amount: null,
|
||||
start_date: null,
|
||||
end_date: null,
|
||||
status: 'active',
|
||||
phone: '',
|
||||
email: '',
|
||||
address: '',
|
||||
remarks: ''
|
||||
})
|
||||
modalVisible.value = true
|
||||
}
|
||||
|
||||
const handleView = (record) => {
|
||||
currentPolicy.value = record
|
||||
detailVisible.value = true
|
||||
}
|
||||
|
||||
const handleEdit = (record) => {
|
||||
editingId.value = record.id
|
||||
Object.assign(formState, {
|
||||
policy_number: record.policy_number,
|
||||
insurance_type_id: record.insurance_type_id,
|
||||
policyholder_name: record.policyholder_name,
|
||||
insured_name: record.insured_name,
|
||||
premium_amount: record.premium_amount,
|
||||
coverage_amount: record.coverage_amount,
|
||||
start_date: record.start_date,
|
||||
end_date: record.end_date,
|
||||
status: record.status,
|
||||
phone: record.phone,
|
||||
email: record.email,
|
||||
address: record.address,
|
||||
remarks: record.remarks
|
||||
})
|
||||
modalVisible.value = true
|
||||
}
|
||||
|
||||
const handleModalOk = async () => {
|
||||
try {
|
||||
await formRef.value.validate()
|
||||
|
||||
if (editingId.value) {
|
||||
// await policyAPI.update(editingId.value, formState)
|
||||
message.success('保单更新成功')
|
||||
} else {
|
||||
// await policyAPI.create(formState)
|
||||
message.success('保单创建成功')
|
||||
}
|
||||
|
||||
modalVisible.value = false
|
||||
loadPolicies()
|
||||
} catch (error) {
|
||||
console.log('表单验证失败', error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleModalCancel = () => {
|
||||
modalVisible.value = false
|
||||
}
|
||||
|
||||
const handleToggleStatus = async (record) => {
|
||||
try {
|
||||
const newStatus = record.status === 'active' ? 'cancelled' : 'active'
|
||||
// await policyAPI.update(record.id, { status: newStatus })
|
||||
message.success('状态更新成功')
|
||||
loadPolicies()
|
||||
} catch (error) {
|
||||
message.error('状态更新失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleRenew = async (record) => {
|
||||
message.info('续保功能开发中')
|
||||
}
|
||||
|
||||
const handleClaim = async (record) => {
|
||||
message.info('理赔功能开发中')
|
||||
}
|
||||
|
||||
const handleDelete = async (id) => {
|
||||
try {
|
||||
// await policyAPI.delete(id)
|
||||
message.success('保单删除成功')
|
||||
loadPolicies()
|
||||
} catch (error) {
|
||||
message.error('保单删除失败')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadPolicies()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.policy-management {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
521
insurance_admin-system/src/views/SystemLogs.vue
Normal file
521
insurance_admin-system/src/views/SystemLogs.vue
Normal file
@@ -0,0 +1,521 @@
|
||||
<template>
|
||||
<div class="system-logs">
|
||||
<a-page-header
|
||||
title="系统日志"
|
||||
sub-title="查看系统操作和运行日志"
|
||||
/>
|
||||
|
||||
<a-card>
|
||||
<!-- 搜索区域 -->
|
||||
<a-form layout="inline" :model="searchForm" @finish="handleSearch">
|
||||
<a-form-item label="操作类型">
|
||||
<a-select
|
||||
v-model:value="searchForm.log_type"
|
||||
placeholder="请选择操作类型"
|
||||
style="width: 120px"
|
||||
allow-clear
|
||||
>
|
||||
<a-select-option value="login">登录</a-select-option>
|
||||
<a-select-option value="create">新增</a-select-option>
|
||||
<a-select-option value="update">修改</a-select-option>
|
||||
<a-select-option value="delete">删除</a-select-option>
|
||||
<a-select-option value="export">导出</a-select-option>
|
||||
<a-select-option value="system">系统操作</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="操作模块">
|
||||
<a-select
|
||||
v-model:value="searchForm.module"
|
||||
placeholder="请选择操作模块"
|
||||
style="width: 120px"
|
||||
allow-clear
|
||||
>
|
||||
<a-select-option value="user">用户管理</a-select-option>
|
||||
<a-select-option value="policy">保单管理</a-select-option>
|
||||
<a-select-option value="claim">理赔管理</a-select-option>
|
||||
<a-select-option value="type">保险类型</a-select-option>
|
||||
<a-select-option value="system">系统设置</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="操作人">
|
||||
<a-input
|
||||
v-model:value="searchForm.operator"
|
||||
placeholder="请输入操作人"
|
||||
style="width: 120px"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="操作时间">
|
||||
<a-range-picker
|
||||
v-model:value="searchForm.timeRange"
|
||||
:show-time="{ format: 'HH:mm' }"
|
||||
format="YYYY-MM-DD HH:mm"
|
||||
style="width: 320px"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="IP地址">
|
||||
<a-input
|
||||
v-model:value="searchForm.ip_address"
|
||||
placeholder="请输入IP地址"
|
||||
style="width: 120px"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-space>
|
||||
<a-button type="primary" html-type="submit">查询</a-button>
|
||||
<a-button @click="resetSearch">重置</a-button>
|
||||
<a-button @click="exportLogs" :loading="exportLoading">导出</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<a-card style="margin-top: 16px">
|
||||
<!-- 操作按钮 -->
|
||||
<div style="margin-bottom: 16px">
|
||||
<a-space>
|
||||
<a-button
|
||||
type="primary"
|
||||
danger
|
||||
@click="clearLogs"
|
||||
:loading="clearLoading"
|
||||
>
|
||||
清空日志
|
||||
</a-button>
|
||||
<a-button @click="refreshLogs">刷新</a-button>
|
||||
<a-tag color="blue">
|
||||
共 {{ pagination.total }} 条记录
|
||||
</a-tag>
|
||||
</a-space>
|
||||
</div>
|
||||
|
||||
<!-- 日志表格 -->
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="logList"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
@change="handleTableChange"
|
||||
:scroll="{ x: 1200 }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<!-- 操作类型 -->
|
||||
<template v-if="column.key === 'log_type'">
|
||||
<a-tag :color="getLogTypeColor(record.log_type)">
|
||||
{{ getLogTypeText(record.log_type) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<!-- 操作模块 -->
|
||||
<template v-else-if="column.key === 'module'">
|
||||
<a-tag color="blue">
|
||||
{{ getModuleText(record.module) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<!-- 操作结果 -->
|
||||
<template v-else-if="column.key === 'result'">
|
||||
<a-tag :color="record.result === 'success' ? 'green' : 'red'">
|
||||
{{ record.result === 'success' ? '成功' : '失败' }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<!-- 操作内容 -->
|
||||
<template v-else-if="column.key === 'content'">
|
||||
<a-tooltip :title="record.content">
|
||||
<span class="log-content">{{ record.content }}</span>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
|
||||
<!-- 操作时间 -->
|
||||
<template v-else-if="column.key === 'created_at'">
|
||||
{{ formatDateTime(record.created_at) }}
|
||||
</template>
|
||||
|
||||
<!-- 操作 -->
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button
|
||||
size="small"
|
||||
@click="showLogDetail(record)"
|
||||
>
|
||||
详情
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 日志详情模态框 -->
|
||||
<a-modal
|
||||
v-model:visible="detailVisible"
|
||||
title="日志详情"
|
||||
width="600px"
|
||||
:footer="null"
|
||||
>
|
||||
<a-descriptions bordered :column="1" v-if="currentLog">
|
||||
<a-descriptions-item label="操作类型">
|
||||
<a-tag :color="getLogTypeColor(currentLog.log_type)">
|
||||
{{ getLogTypeText(currentLog.log_type) }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="操作模块">
|
||||
<a-tag color="blue">
|
||||
{{ getModuleText(currentLog.module) }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="操作人">
|
||||
{{ currentLog.operator }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="操作时间">
|
||||
{{ formatDateTime(currentLog.created_at) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="IP地址">
|
||||
{{ currentLog.ip_address }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="操作结果">
|
||||
<a-tag :color="currentLog.result === 'success' ? 'green' : 'red'">
|
||||
{{ currentLog.result === 'success' ? '成功' : '失败' }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="操作内容">
|
||||
<div style="word-break: break-all; white-space: pre-wrap;">
|
||||
{{ currentLog.content }}
|
||||
</div>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="请求参数" v-if="currentLog.request_params">
|
||||
<pre style="background: #f5f5f5; padding: 8px; border-radius: 4px; overflow: auto;">
|
||||
{{ JSON.stringify(JSON.parse(currentLog.request_params), null, 2) }}
|
||||
</pre>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="错误信息" v-if="currentLog.error_message">
|
||||
<div style="color: #ff4d4f; word-break: break-all; white-space: pre-wrap;">
|
||||
{{ currentLog.error_message }}
|
||||
</div>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
|
||||
const searchForm = reactive({
|
||||
log_type: undefined,
|
||||
module: undefined,
|
||||
operator: '',
|
||||
timeRange: [],
|
||||
ip_address: ''
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
const exportLoading = ref(false)
|
||||
const clearLoading = ref(false)
|
||||
const detailVisible = ref(false)
|
||||
const currentLog = ref(null)
|
||||
|
||||
const logList = ref([
|
||||
{
|
||||
id: 1,
|
||||
log_type: 'login',
|
||||
module: 'system',
|
||||
operator: 'admin',
|
||||
operator_id: 1,
|
||||
content: '用户登录系统',
|
||||
result: 'success',
|
||||
ip_address: '192.168.1.100',
|
||||
created_at: '2024-01-20 10:30:25',
|
||||
request_params: '{"username":"admin"}',
|
||||
error_message: ''
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
log_type: 'create',
|
||||
module: 'user',
|
||||
operator: 'admin',
|
||||
operator_id: 1,
|
||||
content: '新增用户:张三',
|
||||
result: 'success',
|
||||
ip_address: '192.168.1.100',
|
||||
created_at: '2024-01-20 10:35:18',
|
||||
request_params: '{"username":"zhangsan","role":"user"}',
|
||||
error_message: ''
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
log_type: 'update',
|
||||
module: 'policy',
|
||||
operator: 'admin',
|
||||
operator_id: 1,
|
||||
content: '修改保单状态:P202401200001',
|
||||
result: 'success',
|
||||
ip_address: '192.168.1.100',
|
||||
created_at: '2024-01-20 11:20:45',
|
||||
request_params: '{"policy_id":"P202401200001","status":"active"}',
|
||||
error_message: ''
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
log_type: 'delete',
|
||||
module: 'claim',
|
||||
operator: 'admin',
|
||||
operator_id: 1,
|
||||
content: '删除理赔记录:C202401200001',
|
||||
result: 'success',
|
||||
ip_address: '192.168.1.100',
|
||||
created_at: '2024-01-20 14:15:30',
|
||||
request_params: '{"claim_id":"C202401200001"}',
|
||||
error_message: ''
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
log_type: 'login',
|
||||
module: 'system',
|
||||
operator: 'user001',
|
||||
operator_id: 2,
|
||||
content: '用户登录失败',
|
||||
result: 'fail',
|
||||
ip_address: '192.168.1.101',
|
||||
created_at: '2024-01-20 15:40:12',
|
||||
request_params: '{"username":"user001"}',
|
||||
error_message: '密码错误'
|
||||
}
|
||||
])
|
||||
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 5,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total, range) =>
|
||||
`第 ${range[0]}-${range[1]} 条,共 ${total} 条`
|
||||
})
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
width: 60
|
||||
},
|
||||
{
|
||||
title: '操作类型',
|
||||
key: 'log_type',
|
||||
dataIndex: 'log_type',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '操作模块',
|
||||
key: 'module',
|
||||
dataIndex: 'module',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '操作人',
|
||||
dataIndex: 'operator',
|
||||
key: 'operator',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '操作内容',
|
||||
key: 'content',
|
||||
dataIndex: 'content',
|
||||
width: 200
|
||||
},
|
||||
{
|
||||
title: '操作结果',
|
||||
key: 'result',
|
||||
dataIndex: 'result',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: 'IP地址',
|
||||
dataIndex: 'ip_address',
|
||||
key: 'ip_address',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '操作时间',
|
||||
key: 'created_at',
|
||||
dataIndex: 'created_at',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 80,
|
||||
fixed: 'right'
|
||||
}
|
||||
]
|
||||
|
||||
const getLogTypeColor = (type) => {
|
||||
const colors = {
|
||||
login: 'blue',
|
||||
create: 'green',
|
||||
update: 'orange',
|
||||
delete: 'red',
|
||||
export: 'purple',
|
||||
system: 'cyan'
|
||||
}
|
||||
return colors[type] || 'default'
|
||||
}
|
||||
|
||||
const getLogTypeText = (type) => {
|
||||
const texts = {
|
||||
login: '登录',
|
||||
create: '新增',
|
||||
update: '修改',
|
||||
delete: '删除',
|
||||
export: '导出',
|
||||
system: '系统操作'
|
||||
}
|
||||
return texts[type] || type
|
||||
}
|
||||
|
||||
const getModuleText = (module) => {
|
||||
const texts = {
|
||||
user: '用户管理',
|
||||
policy: '保单管理',
|
||||
claim: '理赔管理',
|
||||
type: '保险类型',
|
||||
system: '系统设置'
|
||||
}
|
||||
return texts[module] || module
|
||||
}
|
||||
|
||||
const formatDateTime = (datetime) => {
|
||||
if (!datetime) return ''
|
||||
return datetime.replace('T', ' ').substring(0, 19)
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
pagination.current = 1
|
||||
loadLogs()
|
||||
}
|
||||
|
||||
const resetSearch = () => {
|
||||
Object.assign(searchForm, {
|
||||
log_type: undefined,
|
||||
module: undefined,
|
||||
operator: '',
|
||||
timeRange: [],
|
||||
ip_address: ''
|
||||
})
|
||||
pagination.current = 1
|
||||
loadLogs()
|
||||
}
|
||||
|
||||
const handleTableChange = (pag) => {
|
||||
pagination.current = pag.current
|
||||
pagination.pageSize = pag.pageSize
|
||||
loadLogs()
|
||||
}
|
||||
|
||||
const loadLogs = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// const params = {
|
||||
// page: pagination.current,
|
||||
// pageSize: pagination.pageSize,
|
||||
// ...searchForm,
|
||||
// start_time: searchForm.timeRange?.[0]?.format('YYYY-MM-DD HH:mm:ss'),
|
||||
// end_time: searchForm.timeRange?.[1]?.format('YYYY-MM-DD HH:mm:ss')
|
||||
// }
|
||||
// const response = await systemAPI.getLogs(params)
|
||||
// logList.value = response.data.list
|
||||
// pagination.total = response.data.total
|
||||
|
||||
// 模拟数据
|
||||
const filteredLogs = logList.value.filter(log => {
|
||||
if (searchForm.log_type && log.log_type !== searchForm.log_type) return false
|
||||
if (searchForm.module && log.module !== searchForm.module) return false
|
||||
if (searchForm.operator && !log.operator.includes(searchForm.operator)) return false
|
||||
if (searchForm.ip_address && !log.ip_address.includes(searchForm.ip_address)) return false
|
||||
return true
|
||||
})
|
||||
|
||||
logList.value = filteredLogs
|
||||
pagination.total = filteredLogs.length
|
||||
} catch (error) {
|
||||
message.error('加载日志失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const refreshLogs = () => {
|
||||
loadLogs()
|
||||
}
|
||||
|
||||
const exportLogs = async () => {
|
||||
exportLoading.value = true
|
||||
try {
|
||||
// await systemAPI.exportLogs(searchForm)
|
||||
message.success('导出任务已开始,请稍后查看下载')
|
||||
} catch (error) {
|
||||
message.error('导出失败')
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const clearLogs = async () => {
|
||||
clearLoading.value = true
|
||||
try {
|
||||
// await systemAPI.clearLogs()
|
||||
message.success('日志已清空')
|
||||
loadLogs()
|
||||
} catch (error) {
|
||||
message.error('清空日志失败')
|
||||
} finally {
|
||||
clearLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const showLogDetail = (log) => {
|
||||
currentLog.value = log
|
||||
detailVisible.value = true
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadLogs()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.system-logs {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.log-content {
|
||||
display: inline-block;
|
||||
max-width: 180px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
:deep(.ant-descriptions-item-label) {
|
||||
width: 100px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
:deep(.ant-descriptions-item-content) {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
:deep(pre) {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
</style>
|
||||
707
insurance_admin-system/src/views/SystemSettings.vue
Normal file
707
insurance_admin-system/src/views/SystemSettings.vue
Normal file
@@ -0,0 +1,707 @@
|
||||
<template>
|
||||
<div class="system-settings">
|
||||
<a-page-header
|
||||
title="系统设置"
|
||||
sub-title="管理系统配置和参数"
|
||||
/>
|
||||
|
||||
<a-tabs v-model:activeKey="activeTab" type="card">
|
||||
<!-- 基本设置 -->
|
||||
<a-tab-pane key="general" tab="基本设置">
|
||||
<a-card title="系统基本信息">
|
||||
<a-form
|
||||
ref="generalFormRef"
|
||||
:model="generalForm"
|
||||
:rules="generalRules"
|
||||
layout="vertical"
|
||||
>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="系统名称" name="system_name">
|
||||
<a-input v-model:value="generalForm.system_name" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="系统版本" name="system_version">
|
||||
<a-input v-model:value="generalForm.system_version" disabled />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item label="系统描述" name="system_description">
|
||||
<a-textarea
|
||||
v-model:value="generalForm.system_description"
|
||||
:rows="3"
|
||||
placeholder="请输入系统描述"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="客服电话" name="customer_service_phone">
|
||||
<a-input v-model:value="generalForm.customer_service_phone" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="客服邮箱" name="customer_service_email">
|
||||
<a-input v-model:value="generalForm.customer_service_email" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item label="公司地址" name="company_address">
|
||||
<a-textarea
|
||||
v-model:value="generalForm.company_address"
|
||||
:rows="2"
|
||||
placeholder="请输入公司地址"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="saveGeneralSettings">保存设置</a-button>
|
||||
<a-button style="margin-left: 8px" @click="resetGeneralForm">重置</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<a-card title="系统状态" style="margin-top: 16px">
|
||||
<a-descriptions bordered :column="2">
|
||||
<a-descriptions-item label="运行时间">{{ systemStatus.uptime }}</a-descriptions-item>
|
||||
<a-descriptions-item label="内存使用">{{ systemStatus.memory_usage }}</a-descriptions-item>
|
||||
<a-descriptions-item label="数据库状态">
|
||||
<a-tag :color="systemStatus.database_status === '正常' ? 'green' : 'red'">
|
||||
{{ systemStatus.database_status }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="最后备份">{{ systemStatus.last_backup }}</a-descriptions-item>
|
||||
<a-descriptions-item label="用户数量">{{ systemStatus.user_count }}</a-descriptions-item>
|
||||
<a-descriptions-item label="保单数量">{{ systemStatus.policy_count }}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-card>
|
||||
</a-tab-pane>
|
||||
|
||||
<!-- 邮件设置 -->
|
||||
<a-tab-pane key="email" tab="邮件设置">
|
||||
<a-card title="邮件服务器配置">
|
||||
<a-form
|
||||
ref="emailFormRef"
|
||||
:model="emailForm"
|
||||
:rules="emailRules"
|
||||
layout="vertical"
|
||||
>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="SMTP服务器" name="smtp_host">
|
||||
<a-input v-model:value="emailForm.smtp_host" placeholder="smtp.example.com" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-form-item label="端口" name="smtp_port">
|
||||
<a-input-number
|
||||
v-model:value="emailForm.smtp_port"
|
||||
:min="1"
|
||||
:max="65535"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-form-item label="加密方式" name="smtp_secure">
|
||||
<a-select v-model:value="emailForm.smtp_secure">
|
||||
<a-select-option value="">无</a-select-option>
|
||||
<a-select-option value="ssl">SSL</a-select-option>
|
||||
<a-select-option value="tls">TLS</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="发件邮箱" name="from_email">
|
||||
<a-input v-model:value="emailForm.from_email" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="发件人名称" name="from_name">
|
||||
<a-input v-model:value="emailForm.from_name" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="用户名" name="smtp_username">
|
||||
<a-input v-model:value="emailForm.smtp_username" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="密码" name="smtp_password">
|
||||
<a-input-password v-model:value="emailForm.smtp_password" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="saveEmailSettings">保存设置</a-button>
|
||||
<a-button style="margin-left: 8px" @click="testEmailSettings">测试连接</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<a-card title="邮件模板" style="margin-top: 16px">
|
||||
<a-tabs type="card">
|
||||
<a-tab-pane key="welcome" tab="欢迎邮件">
|
||||
<a-form layout="vertical">
|
||||
<a-form-item label="邮件主题">
|
||||
<a-input v-model:value="emailTemplates.welcome.subject" />
|
||||
</a-form-item>
|
||||
<a-form-item label="邮件内容">
|
||||
<a-textarea
|
||||
v-model:value="emailTemplates.welcome.content"
|
||||
:rows="6"
|
||||
placeholder="可使用变量:{username}, {system_name}"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="saveEmailTemplate('welcome')">保存模板</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="policy" tab="保单通知">
|
||||
<a-form layout="vertical">
|
||||
<a-form-item label="邮件主题">
|
||||
<a-input v-model:value="emailTemplates.policy.subject" />
|
||||
</a-form-item>
|
||||
<a-form-item label="邮件内容">
|
||||
<a-textarea
|
||||
v-model:value="emailTemplates.policy.content"
|
||||
:rows="6"
|
||||
placeholder="可使用变量:{policy_number}, {policyholder_name}, {coverage_amount}"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="saveEmailTemplate('policy')">保存模板</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="claim" tab="理赔通知">
|
||||
<a-form layout="vertical">
|
||||
<a-form-item label="邮件主题">
|
||||
<a-input v-model:value="emailTemplates.claim.subject" />
|
||||
</a-form-item>
|
||||
<a-form-item label="邮件内容">
|
||||
<a-textarea
|
||||
v-model:value="emailTemplates.claim.content"
|
||||
:rows="6"
|
||||
placeholder="可使用变量:{claim_number}, {applicant_name}, {claim_amount}"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="saveEmailTemplate('claim')">保存模板</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-card>
|
||||
</a-tab-pane>
|
||||
|
||||
<!-- 通知设置 -->
|
||||
<a-tab-pane key="notification" tab="通知设置">
|
||||
<a-card title="系统通知配置">
|
||||
<a-form layout="vertical">
|
||||
<a-form-item label="启用邮件通知">
|
||||
<a-switch v-model:checked="notificationSettings.email_enabled" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="启用短信通知">
|
||||
<a-switch v-model:checked="notificationSettings.sms_enabled" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="启用站内通知">
|
||||
<a-switch v-model:checked="notificationSettings.in_app_enabled" />
|
||||
</a-form-item>
|
||||
|
||||
<a-divider />
|
||||
|
||||
<h3>通知类型</h3>
|
||||
|
||||
<a-form-item label="新用户注册">
|
||||
<a-checkbox-group v-model:value="notificationSettings.types.new_user">
|
||||
<a-checkbox value="email">邮件</a-checkbox>
|
||||
<a-checkbox value="sms">短信</a-checkbox>
|
||||
<a-checkbox value="in_app">站内</a-checkbox>
|
||||
</a-checkbox-group>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="新保单创建">
|
||||
<a-checkbox-group v-model:value="notificationSettings.types.new_policy">
|
||||
<a-checkbox value="email">邮件</a-checkbox>
|
||||
<a-checkbox value="sms">短信</a-checkbox>
|
||||
<a-checkbox value="in_app">站内</a-checkbox>
|
||||
</a-checkbox-group>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="理赔申请提交">
|
||||
<a-checkbox-group v-model:value="notificationSettings.types.new_claim">
|
||||
<a-checkbox value="email">邮件</a-checkbox>
|
||||
<a-checkbox value="sms">短信</a-checkbox>
|
||||
<a-checkbox value="in_app">站内</a-checkbox>
|
||||
</a-checkbox-group>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="系统告警">
|
||||
<a-checkbox-group v-model:value="notificationSettings.types.system_alert">
|
||||
<a-checkbox value="email">邮件</a-checkbox>
|
||||
<a-checkbox value="sms">短信</a-checkbox>
|
||||
<a-checkbox value="in_app">站内</a-checkbox>
|
||||
</a-checkbox-group>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="saveNotificationSettings">保存设置</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<a-card title="短信服务配置" style="margin-top: 16px">
|
||||
<a-form layout="vertical">
|
||||
<a-form-item label="短信服务商">
|
||||
<a-select v-model:value="smsSettings.provider">
|
||||
<a-select-option value="aliyun">阿里云</a-select-option>
|
||||
<a-select-option value="tencent">腾讯云</a-select-option>
|
||||
<a-select-option value="other">其他</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="Access Key">
|
||||
<a-input v-model:value="smsSettings.access_key" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="Access Secret">
|
||||
<a-input-password v-model:value="smsSettings.access_secret" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item label="签名">
|
||||
<a-input v-model:value="smsSettings.sign_name" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="模板ID">
|
||||
<a-input v-model:value="smsSettings.template_id" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="saveSmsSettings">保存设置</a-button>
|
||||
<a-button style="margin-left: 8px" @click="testSmsSettings">测试发送</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</a-tab-pane>
|
||||
|
||||
<!-- 备份设置 -->
|
||||
<a-tab-pane key="backup" tab="备份设置">
|
||||
<a-card title="数据备份配置">
|
||||
<a-form layout="vertical">
|
||||
<a-form-item label="自动备份">
|
||||
<a-switch v-model:checked="backupSettings.auto_backup" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="备份频率">
|
||||
<a-select v-model:value="backupSettings.frequency" :disabled="!backupSettings.auto_backup">
|
||||
<a-select-option value="daily">每天</a-select-option>
|
||||
<a-select-option value="weekly">每周</a-select-option>
|
||||
<a-select-option value="monthly">每月</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="备份时间" v-if="backupSettings.auto_backup">
|
||||
<a-time-picker
|
||||
v-model:value="backupSettings.backup_time"
|
||||
format="HH:mm"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="保留备份天数">
|
||||
<a-input-number
|
||||
v-model:value="backupSettings.retention_days"
|
||||
:min="1"
|
||||
:max="365"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="备份路径">
|
||||
<a-input
|
||||
v-model:value="backupSettings.backup_path"
|
||||
placeholder="/path/to/backup"
|
||||
/>
|
||||
<a-button
|
||||
type="link"
|
||||
style="padding-left: 0"
|
||||
@click="browseBackupPath"
|
||||
>
|
||||
选择路径
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="saveBackupSettings">保存设置</a-button>
|
||||
<a-button
|
||||
style="margin-left: 8px"
|
||||
@click="manualBackup"
|
||||
:loading="backupLoading"
|
||||
>
|
||||
立即备份
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<a-card title="备份记录" style="margin-top: 16px">
|
||||
<a-table
|
||||
:columns="backupColumns"
|
||||
:data-source="backupList"
|
||||
:loading="backupLoading"
|
||||
:pagination="{ pageSize: 5 }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'size'">
|
||||
<span>{{ formatFileSize(record.size) }}</span>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button size="small" @click="downloadBackup(record)">下载</a-button>
|
||||
<a-button size="small" @click="restoreBackup(record)">恢复</a-button>
|
||||
<a-popconfirm
|
||||
title="确定要删除这个备份吗?"
|
||||
@confirm="deleteBackup(record)"
|
||||
>
|
||||
<a-button size="small" danger>删除</a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
|
||||
const activeTab = ref('general')
|
||||
|
||||
// 基本设置
|
||||
const generalFormRef = ref()
|
||||
const generalForm = reactive({
|
||||
system_name: '保险管理系统',
|
||||
system_version: '1.0.0',
|
||||
system_description: '专业的保险业务管理系统',
|
||||
customer_service_phone: '400-123-4567',
|
||||
customer_service_email: 'service@insurance.com',
|
||||
company_address: '北京市朝阳区某某大厦A座1001室'
|
||||
})
|
||||
|
||||
const generalRules = {
|
||||
system_name: [{ required: true, message: '请输入系统名称' }],
|
||||
customer_service_phone: [{ required: true, message: '请输入客服电话' }],
|
||||
customer_service_email: [
|
||||
{ required: true, message: '请输入客服邮箱' },
|
||||
{ type: 'email', message: '邮箱格式不正确' }
|
||||
]
|
||||
}
|
||||
|
||||
const systemStatus = reactive({
|
||||
uptime: '15天2小时',
|
||||
memory_usage: '45%',
|
||||
database_status: '正常',
|
||||
last_backup: '2024-01-20 02:00:00',
|
||||
user_count: '128',
|
||||
policy_count: '456'
|
||||
})
|
||||
|
||||
// 邮件设置
|
||||
const emailFormRef = ref()
|
||||
const emailForm = reactive({
|
||||
smtp_host: 'smtp.example.com',
|
||||
smtp_port: 587,
|
||||
smtp_secure: 'tls',
|
||||
from_email: 'noreply@insurance.com',
|
||||
from_name: '保险管理系统',
|
||||
smtp_username: 'user@example.com',
|
||||
smtp_password: '********'
|
||||
})
|
||||
|
||||
const emailRules = {
|
||||
smtp_host: [{ required: true, message: '请输入SMTP服务器地址' }],
|
||||
smtp_port: [{ required: true, message: '请输入端口号' }],
|
||||
from_email: [
|
||||
{ required: true, message: '请输入发件邮箱' },
|
||||
{ type: 'email', message: '邮箱格式不正确' }
|
||||
],
|
||||
from_name: [{ required: true, message: '请输入发件人名称' }]
|
||||
}
|
||||
|
||||
const emailTemplates = reactive({
|
||||
welcome: {
|
||||
subject: '欢迎加入{system_name}',
|
||||
content: '尊敬的{username},欢迎您使用{system_name}!'
|
||||
},
|
||||
policy: {
|
||||
subject: '您的保单已创建成功',
|
||||
content: '尊敬的{policyholder_name},您的保单{policy_number}已创建成功,保额{coverage_amount}元。'
|
||||
},
|
||||
claim: {
|
||||
subject: '理赔申请已受理',
|
||||
content: '尊敬的{applicant_name},您的理赔申请{claim_number}已受理,申请金额{claim_amount}元。'
|
||||
}
|
||||
})
|
||||
|
||||
// 通知设置
|
||||
const notificationSettings = reactive({
|
||||
email_enabled: true,
|
||||
sms_enabled: false,
|
||||
in_app_enabled: true,
|
||||
types: {
|
||||
new_user: ['email', 'in_app'],
|
||||
new_policy: ['email'],
|
||||
new_claim: ['email', 'in_app'],
|
||||
system_alert: ['email', 'sms']
|
||||
}
|
||||
})
|
||||
|
||||
const smsSettings = reactive({
|
||||
provider: 'aliyun',
|
||||
access_key: '',
|
||||
access_secret: '',
|
||||
sign_name: '保险服务',
|
||||
template_id: ''
|
||||
})
|
||||
|
||||
// 备份设置
|
||||
const backupSettings = reactive({
|
||||
auto_backup: true,
|
||||
frequency: 'daily',
|
||||
backup_time: null,
|
||||
retention_days: 30,
|
||||
backup_path: '/backups'
|
||||
})
|
||||
|
||||
const backupLoading = ref(false)
|
||||
const backupList = ref([
|
||||
{
|
||||
id: 1,
|
||||
name: 'backup_20240120_020000.sql',
|
||||
size: 1024000,
|
||||
create_time: '2024-01-20 02:00:00',
|
||||
type: '自动'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'backup_20240119_020000.sql',
|
||||
size: 1023000,
|
||||
create_time: '2024-01-19 02:00:00',
|
||||
type: '自动'
|
||||
}
|
||||
])
|
||||
|
||||
const backupColumns = [
|
||||
{
|
||||
title: '备份文件',
|
||||
dataIndex: 'name',
|
||||
key: 'name'
|
||||
},
|
||||
{
|
||||
title: '大小',
|
||||
key: 'size',
|
||||
dataIndex: 'size'
|
||||
},
|
||||
{
|
||||
title: '备份时间',
|
||||
dataIndex: 'create_time',
|
||||
key: 'create_time'
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
dataIndex: 'type',
|
||||
key: 'type'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 200
|
||||
}
|
||||
]
|
||||
|
||||
const saveGeneralSettings = async () => {
|
||||
try {
|
||||
await generalFormRef.value.validate()
|
||||
// await systemAPI.saveGeneralSettings(generalForm)
|
||||
message.success('基本设置保存成功')
|
||||
} catch (error) {
|
||||
console.log('表单验证失败', error)
|
||||
}
|
||||
}
|
||||
|
||||
const resetGeneralForm = () => {
|
||||
generalFormRef.value.resetFields()
|
||||
}
|
||||
|
||||
const saveEmailSettings = async () => {
|
||||
try {
|
||||
await emailFormRef.value.validate()
|
||||
// await systemAPI.saveEmailSettings(emailForm)
|
||||
message.success('邮件设置保存成功')
|
||||
} catch (error) {
|
||||
console.log('表单验证失败', error)
|
||||
}
|
||||
}
|
||||
|
||||
const testEmailSettings = async () => {
|
||||
try {
|
||||
// await systemAPI.testEmailSettings(emailForm)
|
||||
message.success('邮件测试发送成功')
|
||||
} catch (error) {
|
||||
message.error('邮件测试失败')
|
||||
}
|
||||
}
|
||||
|
||||
const saveEmailTemplate = async (type) => {
|
||||
try {
|
||||
// await systemAPI.saveEmailTemplate(type, emailTemplates[type])
|
||||
message.success('邮件模板保存成功')
|
||||
} catch (error) {
|
||||
message.error('保存失败')
|
||||
}
|
||||
}
|
||||
|
||||
const saveNotificationSettings = async () => {
|
||||
try {
|
||||
// await systemAPI.saveNotificationSettings(notificationSettings)
|
||||
message.success('通知设置保存成功')
|
||||
} catch (error) {
|
||||
message.error('保存失败')
|
||||
}
|
||||
}
|
||||
|
||||
const saveSmsSettings = async () => {
|
||||
try {
|
||||
// await systemAPI.saveSmsSettings(smsSettings)
|
||||
message.success('短信设置保存成功')
|
||||
} catch (error) {
|
||||
message.error('保存失败')
|
||||
}
|
||||
}
|
||||
|
||||
const testSmsSettings = async () => {
|
||||
try {
|
||||
// await systemAPI.testSmsSettings(smsSettings)
|
||||
message.success('短信测试发送成功')
|
||||
} catch (error) {
|
||||
message.error('短信测试失败')
|
||||
}
|
||||
}
|
||||
|
||||
const saveBackupSettings = async () => {
|
||||
try {
|
||||
// await systemAPI.saveBackupSettings(backupSettings)
|
||||
message.success('备份设置保存成功')
|
||||
} catch (error) {
|
||||
message.error('保存失败')
|
||||
}
|
||||
}
|
||||
|
||||
const browseBackupPath = () => {
|
||||
message.info('路径选择功能开发中')
|
||||
}
|
||||
|
||||
const manualBackup = async () => {
|
||||
backupLoading.value = true
|
||||
try {
|
||||
// await systemAPI.manualBackup()
|
||||
message.success('备份任务已启动')
|
||||
} catch (error) {
|
||||
message.error('备份失败')
|
||||
} finally {
|
||||
backupLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const downloadBackup = (record) => {
|
||||
message.info(`开始下载备份: ${record.name}`)
|
||||
}
|
||||
|
||||
const restoreBackup = (record) => {
|
||||
message.info(`开始恢复备份: ${record.name}`)
|
||||
}
|
||||
|
||||
const deleteBackup = (record) => {
|
||||
message.success(`备份 ${record.name} 已删除`)
|
||||
}
|
||||
|
||||
const formatFileSize = (bytes) => {
|
||||
if (bytes === 0) return '0 Bytes'
|
||||
const k = 1024
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB']
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 加载系统设置
|
||||
loadSystemSettings()
|
||||
})
|
||||
|
||||
const loadSystemSettings = async () => {
|
||||
try {
|
||||
// const settings = await systemAPI.getSettings()
|
||||
// Object.assign(generalForm, settings.general)
|
||||
// Object.assign(emailForm, settings.email)
|
||||
// Object.assign(notificationSettings, settings.notification)
|
||||
// Object.assign(smsSettings, settings.sms)
|
||||
// Object.assign(backupSettings, settings.backup)
|
||||
} catch (error) {
|
||||
message.error('加载系统设置失败')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.system-settings {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
:deep(.ant-tabs-card) .ant-tabs-content {
|
||||
margin-top: -16px;
|
||||
}
|
||||
|
||||
:deep(.ant-tabs-card) .ant-tabs-content .ant-tabs-tabpane {
|
||||
background: #fff;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
:deep(.ant-tabs-card) .ant-tabs-bar {
|
||||
border-color: #fff;
|
||||
}
|
||||
|
||||
:deep(.ant-tabs-card) .ant-tabs-bar .ant-tabs-tab {
|
||||
border-color: transparent;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
:deep(.ant-tabs-card) .ant-tabs-bar .ant-tabs-tab-active {
|
||||
border-color: #fff;
|
||||
background: #fff;
|
||||
}
|
||||
</style>
|
||||
402
insurance_admin-system/src/views/UserManagement.vue
Normal file
402
insurance_admin-system/src/views/UserManagement.vue
Normal file
@@ -0,0 +1,402 @@
|
||||
<template>
|
||||
<div class="user-management">
|
||||
<a-page-header
|
||||
title="用户管理"
|
||||
sub-title="管理系统用户和权限"
|
||||
>
|
||||
<template #extra>
|
||||
<a-button type="primary" @click="showModal">
|
||||
<plus-outlined />
|
||||
新增用户
|
||||
</a-button>
|
||||
</template>
|
||||
</a-page-header>
|
||||
|
||||
<!-- 搜索区域 -->
|
||||
<a-card style="margin-top: 16px">
|
||||
<a-form layout="inline" :model="searchForm">
|
||||
<a-form-item label="用户名">
|
||||
<a-input
|
||||
v-model:value="searchForm.username"
|
||||
placeholder="请输入用户名"
|
||||
@pressEnter="handleSearch"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="状态">
|
||||
<a-select
|
||||
v-model:value="searchForm.status"
|
||||
placeholder="请选择状态"
|
||||
style="width: 120px"
|
||||
>
|
||||
<a-select-option value="">全部</a-select-option>
|
||||
<a-select-option value="active">活跃</a-select-option>
|
||||
<a-select-option value="inactive">禁用</a-select-option>
|
||||
<a-select-option value="suspended">暂停</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="handleSearch">
|
||||
<search-outlined />
|
||||
搜索
|
||||
</a-button>
|
||||
<a-button style="margin-left: 8px" @click="resetSearch">
|
||||
<redo-outlined />
|
||||
重置
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<!-- 用户表格 -->
|
||||
<a-card style="margin-top: 16px">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="userList"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
@change="handleTableChange"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="getStatusColor(record.status)">
|
||||
{{ getStatusText(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button size="small" @click="handleEdit(record)">编辑</a-button>
|
||||
<a-button
|
||||
size="small"
|
||||
:type="record.status === 'active' ? 'danger' : 'primary'"
|
||||
@click="handleToggleStatus(record)"
|
||||
>
|
||||
{{ record.status === 'active' ? '禁用' : '启用' }}
|
||||
</a-button>
|
||||
<a-popconfirm
|
||||
title="确定要删除这个用户吗?"
|
||||
@confirm="handleDelete(record.id)"
|
||||
>
|
||||
<a-button size="small" danger>删除</a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 新增/编辑模态框 -->
|
||||
<a-modal
|
||||
v-model:visible="modalVisible"
|
||||
:title="modalTitle"
|
||||
@ok="handleModalOk"
|
||||
@cancel="handleModalCancel"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="formState"
|
||||
:rules="rules"
|
||||
layout="vertical"
|
||||
>
|
||||
<a-form-item label="用户名" name="username">
|
||||
<a-input v-model:value="formState.username" />
|
||||
</a-form-item>
|
||||
<a-form-item label="真实姓名" name="real_name">
|
||||
<a-input v-model:value="formState.real_name" />
|
||||
</a-form-item>
|
||||
<a-form-item label="邮箱" name="email">
|
||||
<a-input v-model:value="formState.email" />
|
||||
</a-form-item>
|
||||
<a-form-item label="手机号" name="phone">
|
||||
<a-input v-model:value="formState.phone" />
|
||||
</a-form-item>
|
||||
<a-form-item label="角色" name="role_id">
|
||||
<a-select v-model:value="formState.role_id" placeholder="请选择角色">
|
||||
<a-select-option :value="1">管理员</a-select-option>
|
||||
<a-select-option :value="2">保险顾问</a-select-option>
|
||||
<a-select-option :value="3">客服人员</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="状态" name="status">
|
||||
<a-select v-model:value="formState.status" placeholder="请选择状态">
|
||||
<a-select-option value="active">活跃</a-select-option>
|
||||
<a-select-option value="inactive">禁用</a-select-option>
|
||||
<a-select-option value="suspended">暂停</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="!editingId" label="密码" name="password">
|
||||
<a-input-password v-model:value="formState.password" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import {
|
||||
PlusOutlined,
|
||||
SearchOutlined,
|
||||
RedoOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
import { userAPI } from '@/utils/api'
|
||||
|
||||
const loading = ref(false)
|
||||
const modalVisible = ref(false)
|
||||
const editingId = ref(null)
|
||||
const userList = ref([])
|
||||
const formRef = ref()
|
||||
|
||||
const searchForm = reactive({
|
||||
username: '',
|
||||
status: ''
|
||||
})
|
||||
|
||||
const formState = reactive({
|
||||
username: '',
|
||||
real_name: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
role_id: null,
|
||||
status: 'active',
|
||||
password: ''
|
||||
})
|
||||
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total) => `共 ${total} 条记录`
|
||||
})
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '用户名',
|
||||
dataIndex: 'username',
|
||||
key: 'username'
|
||||
},
|
||||
{
|
||||
title: '真实姓名',
|
||||
dataIndex: 'real_name',
|
||||
key: 'real_name'
|
||||
},
|
||||
{
|
||||
title: '邮箱',
|
||||
dataIndex: 'email',
|
||||
key: 'email'
|
||||
},
|
||||
{
|
||||
title: '手机号',
|
||||
dataIndex: 'phone',
|
||||
key: 'phone'
|
||||
},
|
||||
{
|
||||
title: '角色',
|
||||
dataIndex: 'role_name',
|
||||
key: 'role_name'
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
dataIndex: 'status'
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'created_at',
|
||||
key: 'created_at',
|
||||
width: 180
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 200,
|
||||
fixed: 'right'
|
||||
}
|
||||
]
|
||||
|
||||
const rules = {
|
||||
username: [{ required: true, message: '请输入用户名' }],
|
||||
real_name: [{ required: true, message: '请输入真实姓名' }],
|
||||
email: [
|
||||
{ required: true, message: '请输入邮箱' },
|
||||
{ type: 'email', message: '邮箱格式不正确' }
|
||||
],
|
||||
phone: [{ required: true, message: '请输入手机号' }],
|
||||
role_id: [{ required: true, message: '请选择角色' }],
|
||||
password: [{ required: true, message: '请输入密码' }]
|
||||
}
|
||||
|
||||
const modalTitle = computed(() => {
|
||||
return editingId.value ? '编辑用户' : '新增用户'
|
||||
})
|
||||
|
||||
const getStatusColor = (status) => {
|
||||
const colors = {
|
||||
active: 'green',
|
||||
inactive: 'red',
|
||||
suspended: 'orange'
|
||||
}
|
||||
return colors[status] || 'default'
|
||||
}
|
||||
|
||||
const getStatusText = (status) => {
|
||||
const texts = {
|
||||
active: '活跃',
|
||||
inactive: '禁用',
|
||||
suspended: '暂停'
|
||||
}
|
||||
return texts[status] || '未知'
|
||||
}
|
||||
|
||||
const loadUsers = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {
|
||||
page: pagination.current,
|
||||
pageSize: pagination.pageSize,
|
||||
...searchForm
|
||||
}
|
||||
|
||||
// 这里应该是实际的API调用
|
||||
// const response = await userAPI.getList(params)
|
||||
// userList.value = response.data.list
|
||||
// pagination.total = response.data.total
|
||||
|
||||
// 模拟数据
|
||||
userList.value = [
|
||||
{
|
||||
id: 1,
|
||||
username: 'admin',
|
||||
real_name: '管理员',
|
||||
email: 'admin@example.com',
|
||||
phone: '13800138000',
|
||||
role_name: '管理员',
|
||||
status: 'active',
|
||||
created_at: '2024-01-01 10:00:00'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
username: 'advisor1',
|
||||
real_name: '张顾问',
|
||||
email: 'advisor1@example.com',
|
||||
phone: '13800138001',
|
||||
role_name: '保险顾问',
|
||||
status: 'active',
|
||||
created_at: '2024-01-02 14:30:00'
|
||||
}
|
||||
]
|
||||
pagination.total = 2
|
||||
} catch (error) {
|
||||
message.error('加载用户列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
pagination.current = 1
|
||||
loadUsers()
|
||||
}
|
||||
|
||||
const resetSearch = () => {
|
||||
searchForm.username = ''
|
||||
searchForm.status = ''
|
||||
handleSearch()
|
||||
}
|
||||
|
||||
const handleTableChange = (pag) => {
|
||||
pagination.current = pag.current
|
||||
pagination.pageSize = pag.pageSize
|
||||
loadUsers()
|
||||
}
|
||||
|
||||
const showModal = () => {
|
||||
editingId.value = null
|
||||
Object.assign(formState, {
|
||||
username: '',
|
||||
real_name: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
role_id: null,
|
||||
status: 'active',
|
||||
password: ''
|
||||
})
|
||||
modalVisible.value = true
|
||||
}
|
||||
|
||||
const handleEdit = (record) => {
|
||||
editingId.value = record.id
|
||||
Object.assign(formState, {
|
||||
username: record.username,
|
||||
real_name: record.real_name,
|
||||
email: record.email,
|
||||
phone: record.phone,
|
||||
role_id: record.role_id,
|
||||
status: record.status,
|
||||
password: ''
|
||||
})
|
||||
modalVisible.value = true
|
||||
}
|
||||
|
||||
const handleModalOk = async () => {
|
||||
try {
|
||||
await formRef.value.validate()
|
||||
|
||||
if (editingId.value) {
|
||||
// await userAPI.update(editingId.value, formState)
|
||||
message.success('用户更新成功')
|
||||
} else {
|
||||
// await userAPI.create(formState)
|
||||
message.success('用户创建成功')
|
||||
}
|
||||
|
||||
modalVisible.value = false
|
||||
loadUsers()
|
||||
} catch (error) {
|
||||
console.log('表单验证失败', error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleModalCancel = () => {
|
||||
modalVisible.value = false
|
||||
}
|
||||
|
||||
const handleToggleStatus = async (record) => {
|
||||
try {
|
||||
const newStatus = record.status === 'active' ? 'inactive' : 'active'
|
||||
// await userAPI.update(record.id, { status: newStatus })
|
||||
message.success('状态更新成功')
|
||||
loadUsers()
|
||||
} catch (error) {
|
||||
message.error('状态更新失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleDelete = async (id) => {
|
||||
try {
|
||||
// await userAPI.delete(id)
|
||||
message.success('用户删除成功')
|
||||
loadUsers()
|
||||
} catch (error) {
|
||||
message.error('用户删除失败')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadUsers()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.user-management {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
21
insurance_admin-system/vite.config.js
Normal file
21
insurance_admin-system/vite.config.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import { resolve } from 'path'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve(__dirname, 'src')
|
||||
}
|
||||
},
|
||||
server: {
|
||||
port: 3001,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:3000',
|
||||
changeOrigin: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user