完善保险端前后端和养殖端小程序

This commit is contained in:
xuqiuyun
2025-09-22 19:09:45 +08:00
parent 02a25515a9
commit 325c114c38
256 changed files with 48348 additions and 4444 deletions

View File

@@ -1,11 +1,9 @@
<template>
<a-config-provider :locale="zhCN">
<router-view />
</a-config-provider>
<router-view />
</template>
<script setup>
import zhCN from 'ant-design-vue/es/locale/zh_CN'
// 语言配置已在main.js中通过全局ConfigProvider设置
</script>
<style>

View File

@@ -10,9 +10,40 @@
v-model:selectedKeys="selectedKeys"
theme="dark"
mode="inline"
:items="menus"
@click="handleMenuClick"
/>
>
<template v-for="menu in menus" :key="menu.key">
<a-menu-item
v-if="!menu.children || menu.children.length === 0"
:key="menu.key"
:path="menu.path"
>
<template #icon v-if="menu.icon">
<component :is="menu.icon" />
</template>
{{ menu.label }}
</a-menu-item>
<a-sub-menu
v-else
:key="menu.key"
:title="menu.label"
>
<template #icon v-if="menu.icon">
<component :is="menu.icon" />
</template>
<template v-for="subMenu in menu.children" :key="subMenu.key">
<a-menu-item
:path="subMenu.path"
>
<template #icon v-if="subMenu.icon">
<component :is="subMenu.icon" />
</template>
{{ subMenu.label }}
</a-menu-item>
</template>
</a-sub-menu>
</template>
</a-menu>
</a-layout-sider>
<!-- 主内容区 -->
@@ -105,25 +136,25 @@ const collapsed = ref(false)
const selectedKeys = ref([route.name])
const menus = ref([])
// 图标映射,根据后端返回的icon名称返回对应的组件
// 图标映射,直接返回图标组件而不是渲染函数
const iconMap = {
DashboardOutlined: () => h(DashboardOutlined),
DatabaseOutlined: () => h(DatabaseOutlined),
CheckCircleOutlined: () => h(CheckCircleOutlined),
FileTextOutlined: () => h(FileTextOutlined),
FileDoneOutlined: () => h(FileDoneOutlined),
SafetyCertificateOutlined: () => h(SafetyCertificateOutlined),
ShopOutlined: () => h(ShopOutlined),
FileProtectOutlined: () => h(FileProtectOutlined),
MedicineBoxOutlined: () => h(MedicineBoxOutlined),
InsuranceOutlined: () => h(InsuranceOutlined),
BellOutlined: () => h(BellOutlined),
UserAddOutlined: () => h(UserAddOutlined),
SettingOutlined: () => h(SettingOutlined),
UserSwitchOutlined: () => h(UserSwitchOutlined)
DashboardOutlined: DashboardOutlined,
DatabaseOutlined: DatabaseOutlined,
CheckCircleOutlined: CheckCircleOutlined,
FileTextOutlined: FileTextOutlined,
FileDoneOutlined: FileDoneOutlined,
SafetyCertificateOutlined: SafetyCertificateOutlined,
ShopOutlined: ShopOutlined,
FileProtectOutlined: FileProtectOutlined,
MedicineBoxOutlined: MedicineBoxOutlined,
InsuranceOutlined: InsuranceOutlined,
BellOutlined: BellOutlined,
UserAddOutlined: UserAddOutlined,
SettingOutlined: SettingOutlined,
UserSwitchOutlined: UserSwitchOutlined
};
// 格式化菜单数据为Ant Design Vue的Menu组件所需格式
// 格式化菜单数据
const formatMenuItems = (menuList) => {
return menuList.map(menu => {
const menuItem = {
@@ -132,7 +163,7 @@ const formatMenuItems = (menuList) => {
path: menu.path
};
// 添加图标
// 添加图标组件引用
if (menu.icon && iconMap[menu.icon]) {
menuItem.icon = iconMap[menu.icon];
}
@@ -173,13 +204,13 @@ const fetchMenus = async () => {
key: 'SupervisionTask',
icon: () => h(CheckCircleOutlined),
label: '监管任务',
path: '/dashboard' // 重定向到仪表板
path: '/supervision-tasks' // 使用正确的复数路径
},
{
key: 'PendingInstallationTask',
icon: () => h(ExclamationCircleOutlined),
label: '待安装任务',
path: '/dashboard' // 重定向到仪表板
path: '/pending-installation' // 使用正确的路径
},
{
key: 'CompletedTask',
@@ -265,7 +296,21 @@ const fetchMenus = async () => {
// 菜单点击处理
const handleMenuClick = (e) => {
const menuItem = menus.value.find(item => item.key === e.key);
// 递归查找菜单项
const findMenuByKey = (menuList, key) => {
for (const item of menuList) {
if (item.key === key) {
return item;
}
if (item.children) {
const found = findMenuByKey(item.children, key);
if (found) return found;
}
}
return null;
};
const menuItem = findMenuByKey(menus.value, e.key);
if (menuItem && menuItem.path) {
router.push(menuItem.path);
}

View File

@@ -4,28 +4,22 @@ import router from './router'
import store from './stores'
import Antd from 'ant-design-vue'
import 'ant-design-vue/dist/reset.css'
import dayjs from 'dayjs'
import 'dayjs/locale/zh-cn'
import relativeTime from 'dayjs/plugin/relativeTime'
import duration from 'dayjs/plugin/duration'
import zhCN from 'ant-design-vue/es/locale/zh_CN'
// Ant Design Vue的中文语言包
import antdZhCN from 'ant-design-vue/es/locale/zh_CN'
// 配置 dayjs
dayjs.extend(relativeTime)
dayjs.extend(duration)
dayjs.locale('zh-cn')
// 为 Ant Design Vue 配置日期库
globalThis.dayjs = dayjs
// Ant Design Vue 4.x 中不再支持通过 Antd.ConfigProvider.config 配置全局属性
// 日期库配置已通过 globalThis.dayjs 完成
// Ant Design Vue 4.x配置
// 使用Ant Design Vue内置的dayjs实例避免版本冲突
const antdConfig = {
locale: antdZhCN,
getPopupContainer: (trigger) => trigger.parentNode
};
// 创建应用实例
const app = createApp(App)
// 安装Vue插件
app.use(router)
app.use(store)
app.use(Antd)
app.use(Antd, antdConfig)
app.mount('#app')

View File

@@ -1,4 +1,5 @@
import { createRouter, createWebHistory } from 'vue-router'
import { useUserStore } from '@/stores/user'
import Layout from '@/components/Layout.vue'
import Login from '@/views/Login.vue'
import Dashboard from '@/views/Dashboard.vue'
@@ -8,6 +9,15 @@ import ApplicationManagement from '@/views/ApplicationManagement.vue'
import PolicyManagement from '@/views/PolicyManagement.vue'
import ClaimManagement from '@/views/ClaimManagement.vue'
import DataWarehouse from '@/views/DataWarehouse.vue'
import SupervisionTaskManagement from '@/views/SupervisionTaskManagement.vue'
import InstallationTaskManagement from '@/views/InstallationTaskManagement.vue'
import DatePickerTest from '@/views/DatePickerTest.vue'
import DatePickerDebug from '@/views/DatePickerDebug.vue'
import DayjsDebug from '@/views/DayjsDebug.vue'
import AntdConfigDebug from '@/views/AntdConfigDebug.vue'
import SimpleDayjsTest from '@/views/SimpleDayjsTest.vue'
import RangePickerTest from '@/views/RangePickerTest.vue'
import LoginTest from '@/views/LoginTest.vue'
const routes = [
{
@@ -61,6 +71,62 @@ const routes = [
name: 'DataWarehouse',
component: DataWarehouse,
meta: { title: '数据览仓' }
},
{
path: 'supervision-tasks',
alias: 'supervision-task', // 添加别名兼容单数形式
name: 'SupervisionTaskManagement',
component: SupervisionTaskManagement,
meta: { title: '监管任务管理' }
},
{
path: 'installation-tasks',
alias: ['pending-installation', 'installation-task'], // 添加别名兼容不同形式
name: 'InstallationTaskManagement',
component: InstallationTaskManagement,
meta: { title: '待安装任务管理' }
},
{
path: 'date-picker-test',
name: 'DatePickerTest',
component: DatePickerTest,
meta: { title: '日期选择器测试' }
},
{
path: 'date-picker-debug',
name: 'DatePickerDebug',
component: DatePickerDebug,
meta: { title: '日期选择器调试' }
},
{
path: 'dayjs-debug',
name: 'DayjsDebug',
component: DayjsDebug,
meta: { title: 'Dayjs配置调试' }
},
{
path: 'antd-config-debug',
name: 'AntdConfigDebug',
component: AntdConfigDebug,
meta: { title: 'Antd配置调试' }
},
{
path: 'simple-dayjs-test',
name: 'SimpleDayjsTest',
component: SimpleDayjsTest,
meta: { title: '简单Dayjs测试' }
},
{
path: 'range-picker-test',
name: 'RangePickerTest',
component: RangePickerTest,
meta: { title: '范围选择器测试' }
},
{
path: 'login-test',
name: 'LoginTest',
component: LoginTest,
meta: { title: '登录和API测试' }
}
]
}
@@ -71,4 +137,23 @@ const router = createRouter({
routes
})
// 路由守卫
router.beforeEach((to, from, next) => {
const userStore = useUserStore()
// 如果访问登录页面且已登录,重定向到仪表板
if (to.path === '/login' && userStore.token) {
next('/dashboard')
return
}
// 如果访问受保护的路由但未登录,重定向到登录页
if (to.path !== '/login' && !userStore.token) {
next('/login')
return
}
next()
})
export default router

View File

@@ -98,4 +98,28 @@ export const dataWarehouseAPI = {
getClaimStats: () => api.get('/data-warehouse/claim-stats')
}
// 监管任务API
export const supervisionTaskApi = {
getList: (params) => api.get('/supervision-tasks', { params }),
create: (data) => api.post('/supervision-tasks', data),
update: (id, data) => api.put(`/supervision-tasks/${id}`, data),
delete: (id) => api.delete(`/supervision-tasks/${id}`),
getDetail: (id) => api.get(`/supervision-tasks/${id}`),
batchOperate: (data) => api.post('/supervision-tasks/batch/operate', data),
getStats: () => api.get('/supervision-tasks/stats')
}
// 待安装任务API
export const installationTaskApi = {
getList: (params) => api.get('/installation-tasks', { params }),
create: (data) => api.post('/installation-tasks', data),
update: (data) => api.put(`/installation-tasks/${data.id}`, data),
delete: (id) => api.delete(`/installation-tasks/${id}`),
getDetail: (id) => api.get(`/installation-tasks/${id}`),
batchDelete: (ids) => api.post('/installation-tasks/batch-delete', { ids }),
batchUpdateStatus: (data) => api.post('/installation-tasks/batch-update-status', data),
export: (params) => api.get('/installation-tasks/export/excel', { params, responseType: 'blob' }),
getStats: () => api.get('/installation-tasks/statistics/summary')
}
export default api

View File

@@ -0,0 +1,49 @@
import { installationTaskApi } from './api'
// 待安装任务API接口封装
export default {
// 获取安装任务列表
getInstallationTasks: (params) => {
return installationTaskApi.getList(params)
},
// 创建安装任务
createInstallationTask: (data) => {
return installationTaskApi.create(data)
},
// 更新安装任务
updateInstallationTask: (id, data) => {
return installationTaskApi.update({ ...data, id })
},
// 删除安装任务
deleteInstallationTask: (id) => {
return installationTaskApi.delete(id)
},
// 获取安装任务详情
getInstallationTaskDetail: (id) => {
return installationTaskApi.getDetail(id)
},
// 批量删除安装任务
batchDeleteInstallationTasks: (ids) => {
return installationTaskApi.batchDelete(ids)
},
// 批量更新安装任务状态
batchUpdateInstallationTaskStatus: (data) => {
return installationTaskApi.batchUpdateStatus(data)
},
// 导出安装任务
exportInstallationTasks: (params) => {
return installationTaskApi.export(params)
},
// 获取安装任务统计
getInstallationTaskStats: () => {
return installationTaskApi.getStats()
}
}

View File

@@ -0,0 +1,197 @@
<template>
<div class="debug-container">
<h2>Ant Design Vue 配置调试页面</h2>
<div class="test-section">
<h3>1. Ant Design Vue 版本信息</h3>
<p>Ant Design Vue 版本: {{ antdVersion }}</p>
<p>Vue 版本: {{ vueVersion }}</p>
</div>
<div class="test-section">
<h3>2. 日期选择器配置测试</h3>
<div class="test-group">
<h4>单个日期选择器</h4>
<a-date-picker
v-model:value="singleDate"
@change="logSingleChange"
style="width: 200px; margin-right: 16px;"
/>
<p>选择的值: {{ singleDate ? singleDate.format('YYYY-MM-DD') : '未选择' }}</p>
<p>值类型: {{ singleDate ? typeof singleDate : 'null' }}</p>
</div>
<div class="test-group">
<h4>范围选择器 (range属性)</h4>
<a-date-picker
v-model:value="rangeDate"
range
@change="logRangeChange"
style="width: 300px; margin-right: 16px;"
/>
<p>选择的值: {{ rangeDate ? rangeDate.map(d => d.format('YYYY-MM-DD')).join(' ~ ') : '未选择' }}</p>
<p>值类型: {{ Array.isArray(rangeDate) ? 'array' : typeof rangeDate }}</p>
<p v-if="Array.isArray(rangeDate)">数组元素类型: {{ rangeDate.map(d => typeof d).join(', ') }}</p>
</div>
<div class="test-group">
<h4>RangePicker 组件</h4>
<a-range-picker
v-model:value="rangePickerDate"
@change="logRangePickerChange"
style="width: 300px;"
/>
<p>选择的值: {{ rangePickerDate ? rangePickerDate.map(d => d.format('YYYY-MM-DD')).join(' ~ ') : '未选择' }}</p>
<p>值类型: {{ Array.isArray(rangePickerDate) ? 'array' : typeof rangePickerDate }}</p>
<p v-if="Array.isArray(rangePickerDate)">数组元素类型: {{ rangePickerDate.map(d => typeof d).join(', ') }}</p>
</div>
</div>
<div class="test-section">
<h3>3. 手动创建dayjs实例测试</h3>
<button @click="testDayjsCreation" style="margin-right: 16px;">测试dayjs实例创建</button>
<p>测试结果: {{ dayjsTestResult }}</p>
</div>
<div class="test-section">
<h3>4. 全局配置检查</h3>
<p>全局$dayjs存在: {{ hasGlobalDayjs }}</p>
<p>Antd配置中的getDayjsInstance类型: {{ getDayjsInstanceType }}</p>
<p>Antd配置中的getDayjsInstance返回值类型: {{ getDayjsInstanceReturnType }}</p>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, getCurrentInstance } from 'vue';
import dayjs from 'dayjs';
const antdVersion = ref('未知');
const vueVersion = ref('未知');
const singleDate = ref(dayjs());
const rangeDate = ref([dayjs().subtract(7, 'day'), dayjs()]);
const rangePickerDate = ref([dayjs().subtract(7, 'day'), dayjs()]);
const dayjsTestResult = ref('');
const instance = getCurrentInstance();
const hasGlobalDayjs = ref(!!(instance?.appContext.config.globalProperties.$dayjs));
const getDayjsInstanceType = ref('未知');
const getDayjsInstanceReturnType = ref('未知');
const logSingleChange = (date) => {
console.log('单个日期选择器变化:', date, '类型:', typeof date);
if (date && typeof date.locale === 'function') {
console.log('日期对象有locale方法');
} else {
console.log('日期对象缺少locale方法');
}
};
const logRangeChange = (dates) => {
console.log('范围选择器变化:', dates, '类型:', Array.isArray(dates) ? 'array' : typeof dates);
if (Array.isArray(dates)) {
dates.forEach((date, index) => {
console.log(`日期 ${index + 1}:`, date, '类型:', typeof date);
if (date && typeof date.locale === 'function') {
console.log(`日期 ${index + 1} 有locale方法`);
} else {
console.log(`日期 ${index + 1} 缺少locale方法`);
}
});
}
};
const logRangePickerChange = (dates) => {
console.log('RangePicker变化:', dates, '类型:', Array.isArray(dates) ? 'array' : typeof dates);
if (Array.isArray(dates)) {
dates.forEach((date, index) => {
console.log(`日期 ${index + 1}:`, date, '类型:', typeof date);
if (date && typeof date.locale === 'function') {
console.log(`日期 ${index + 1} 有locale方法`);
} else {
console.log(`日期 ${index + 1} 缺少locale方法`);
}
});
}
};
const testDayjsCreation = () => {
try {
const testDate = dayjs('2023-01-01');
const hasLocale = typeof testDate.locale === 'function';
const hasFormat = typeof testDate.format === 'function';
dayjsTestResult.value = `创建成功! 有locale方法: ${hasLocale}, 有format方法: ${hasFormat}`;
console.log('手动创建dayjs测试:', testDate, '有locale:', hasLocale, '有format:', hasFormat);
} catch (error) {
dayjsTestResult.value = `创建失败: ${error.message}`;
console.error('手动创建dayjs错误:', error);
}
};
onMounted(() => {
// 获取版本信息
try {
antdVersion.value = instance?.appContext.config.globalProperties?.$ANTD?.version || '未知';
vueVersion.value = instance?.appContext.config.globalProperties?.$vue?.version || '未知';
} catch (error) {
console.log('获取版本信息失败:', error);
}
// 检查Antd配置
try {
const antdConfig = instance?.appContext.config.globalProperties?.$ANTD?.config;
if (antdConfig && antdConfig.getDayjsInstance) {
getDayjsInstanceType.value = typeof antdConfig.getDayjsInstance;
const result = antdConfig.getDayjsInstance();
getDayjsInstanceReturnType.value = typeof result;
console.log('Antd getDayjsInstance配置:', {
functionType: typeof antdConfig.getDayjsInstance,
returnType: typeof result,
returnValue: result
});
}
} catch (error) {
console.log('检查Antd配置失败:', error);
}
console.log('Antd配置调试页面加载完成');
});
</script>
<style scoped>
.debug-container {
padding: 20px;
}
.test-section {
margin-bottom: 30px;
padding: 20px;
border: 1px solid #d9d9d9;
border-radius: 6px;
background-color: #fafafa;
}
.test-section h3 {
color: #1890ff;
margin-bottom: 15px;
}
.test-group {
margin-bottom: 20px;
padding: 15px;
border: 1px solid #e8e8e8;
border-radius: 4px;
background-color: white;
}
.test-group h4 {
color: #52c41a;
margin-bottom: 10px;
}
.test-group p {
margin: 5px 0;
font-family: monospace;
}
</style>

View File

@@ -3,12 +3,23 @@
<div class="page-header">
<h1>数据览仓</h1>
<div class="filters">
<a-date-picker
v-model:value="dateRange"
range
@change="handleDateChange"
style="width: 300px; margin-right: 16px;"
/>
<a-input-group compact style="width: 300px; margin-right: 16px;">
<a-input
v-model:value="dateRange[0]"
placeholder="开始日期"
style="width: 45%"
/>
<a-input
style="width: 10%; text-align: center; pointer-events: none; background-color: #f5f5f5;"
placeholder="~"
disabled
/>
<a-input
v-model:value="dateRange[1]"
placeholder="结束日期"
style="width: 45%"
/>
</a-input-group>
<a-button type="primary" @click="refreshData">刷新数据</a-button>
</div>
</div>
@@ -71,7 +82,59 @@ import { ref, onMounted, onUnmounted } from 'vue';
import * as echarts from 'echarts';
import { message } from 'ant-design-vue';
import { dataWarehouseAPI } from '@/utils/api';
import dayjs from 'dayjs';
// 安全获取dayjs实例
const getSafeDayjs = () => {
try {
// 尝试使用Ant Design Vue提供的dayjs实例
if (window.dayjs && typeof window.dayjs === 'function') {
return window.dayjs;
}
// 如果Ant Design Vue没有提供尝试导入我们自己的dayjs
// 但需要确保版本兼容性
const dayjsModule = require('dayjs');
if (dayjsModule && typeof dayjsModule === 'function') {
return dayjsModule;
}
// 如果都失败,使用简单的兼容实现
console.warn('使用简单的日期处理兼容实现');
return createSimpleDayjs();
} catch (error) {
console.warn('获取dayjs失败使用兼容实现:', error);
return createSimpleDayjs();
}
};
// 创建简单的dayjs兼容实现
const createSimpleDayjs = () => {
const simpleDayjs = (date) => {
const d = date ? new Date(date) : new Date();
return {
subtract: (num, unit) => {
const ms = unit === 'day' ? 86400000 : 3600000;
return simpleDayjs(new Date(d.getTime() - num * ms));
},
format: (fmt = 'YYYY-MM-DD') => {
const year = d.getFullYear();
const month = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
return fmt.replace('YYYY', year).replace('MM', month).replace('DD', day);
},
// 添加locale方法以兼容Ant Design Vue
locale: () => simpleDayjs
};
};
// 添加静态方法
simpleDayjs.locale = () => simpleDayjs;
return simpleDayjs;
};
const dayjs = getSafeDayjs();
// 数据状态
const overview = ref({
@@ -84,8 +147,11 @@ const overview = ref({
pendingClaims: 0
});
// 正确初始化日期范围为 dayjs 对象
const dateRange = ref([dayjs().subtract(7, 'day'), dayjs()]);
// 初始化日期范围为字符串格式
const dateRange = ref([
dayjs().subtract(7, 'day').format('YYYY-MM-DD'),
dayjs().format('YYYY-MM-DD')
]);
const loading = ref(false);
// 图表引用

View File

@@ -0,0 +1,91 @@
<template>
<div class="debug-container">
<h2>日期选择器调试页面</h2>
<div class="test-section">
<h3>1. 普通日期选择器</h3>
<a-date-picker
v-model:value="singleDate"
@change="handleSingleChange"
style="width: 200px; margin-right: 16px;"
/>
<p>当前值: {{ singleDate ? singleDate.format('YYYY-MM-DD') : '未选择' }}</p>
</div>
<div class="test-section">
<h3>2. 范围选择器 (range模式)</h3>
<a-date-picker
v-model:value="rangeDate"
:range="true"
@change="handleRangeChange"
style="width: 300px; margin-right: 16px;"
/>
<p>当前值: {{ rangeDate ? rangeDate.map(d => d.format('YYYY-MM-DD')).join(' ~ ') : '未选择' }}</p>
</div>
<div class="test-section">
<h3>3. 范围选择器 (使用range属性)</h3>
<a-date-picker
v-model:value="rangeDate2"
range
@change="handleRangeChange2"
style="width: 300px; margin-right: 16px;"
/>
<p>当前值: {{ rangeDate2 ? rangeDate2.map(d => d.format('YYYY-MM-DD')).join(' ~ ') : '未选择' }}</p>
</div>
<div class="test-section">
<h3>4. 使用RangePicker组件</h3>
<a-range-picker
v-model:value="rangePickerDate"
@change="handleRangePickerChange"
style="width: 300px; margin-right: 16px;"
/>
<p>当前值: {{ rangePickerDate ? rangePickerDate.map(d => d.format('YYYY-MM-DD')).join(' ~ ') : '未选择' }}</p>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import dayjs from 'dayjs';
const singleDate = ref(dayjs());
const rangeDate = ref([dayjs().subtract(7, 'day'), dayjs()]);
const rangeDate2 = ref([dayjs().subtract(7, 'day'), dayjs()]);
const rangePickerDate = ref([dayjs().subtract(7, 'day'), dayjs()]);
const handleSingleChange = (date) => {
console.log('单日期选择:', date);
};
const handleRangeChange = (dates) => {
console.log('范围选择:', dates);
};
const handleRangeChange2 = (dates) => {
console.log('范围选择2:', dates);
};
const handleRangePickerChange = (dates) => {
console.log('RangePicker选择:', dates);
};
</script>
<style scoped>
.debug-container {
padding: 20px;
}
.test-section {
margin-bottom: 30px;
padding: 20px;
border: 1px solid #d9d9d9;
border-radius: 6px;
}
.test-section h3 {
color: #1890ff;
margin-bottom: 15px;
}
</style>

View File

@@ -0,0 +1,88 @@
<template>
<div class="date-picker-test">
<a-page-header
title="日期组件测试"
sub-title="测试dayjs与Ant Design Vue日期组件的集成"
/>
<a-card style="margin-top: 16px">
<h3>基础日期选择器</h3>
<a-date-picker
v-model:value="dateValue"
placeholder="请选择日期"
style="width: 200px"
/>
<p style="margin-top: 10px">当前选择的值: {{ dateValue }}</p>
</a-card>
<a-card style="margin-top: 16px">
<h3>范围日期选择器</h3>
<a-range-picker
v-model:value="rangeValue"
placeholder="请选择日期范围"
/>
<p style="margin-top: 10px">当前选择的范围: {{ rangeValue }}</p>
</a-card>
<a-card style="margin-top: 16px">
<h3>日期时间选择器</h3>
<a-date-picker
v-model:value="dateTimeValue"
show-time
placeholder="请选择日期时间"
style="width: 250px"
/>
<p style="margin-top: 10px">当前选择的时间: {{ dateTimeValue }}</p>
</a-card>
<a-card style="margin-top: 16px">
<h3>手动测试dayjs</h3>
<p>当前日期: {{ formattedToday }}</p>
<p>5天后: {{ formattedFuture }}</p>
<p>5天前: {{ formattedPast }}</p>
</a-card>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
const dateValue = ref(null)
const rangeValue = ref(null)
const dateTimeValue = ref(null)
// 使用我们配置的dayjs实例
// 手动创建一个函数来获取全局配置的dayjs实例
const getGlobalDayjs = () => {
// 访问全局配置的dayjs实例
const { $dayjs } = window.app.config.globalProperties
return $dayjs
}
// 格式化日期进行显示
const formattedToday = computed(() => {
const dayjs = getGlobalDayjs()
return dayjs().format('YYYY年MM月DD日')
})
const formattedFuture = computed(() => {
const dayjs = getGlobalDayjs()
return dayjs().add(5, 'day').format('YYYY年MM月DD日')
})
const formattedPast = computed(() => {
const dayjs = getGlobalDayjs()
return dayjs().subtract(5, 'day').format('YYYY年MM月DD日')
})
onMounted(() => {
console.log('日期组件测试页面已加载')
// 这里可以添加更多测试逻辑
})
</script>
<style scoped>
.date-picker-test {
padding: 0;
}
</style>

View File

@@ -0,0 +1,97 @@
<template>
<div class="debug-container">
<h2>Dayjs 配置调试页面</h2>
<div class="test-section">
<h3>1. Dayjs 基本功能测试</h3>
<p>当前时间: {{ currentTime }}</p>
<p>格式化测试: {{ formattedTime }}</p>
<p>相对时间测试: {{ relativeTime }}</p>
<p>语言设置: {{ localeInfo }}</p>
</div>
<div class="test-section">
<h3>2. Dayjs 实例检查</h3>
<p>dayjs 函数类型: {{ typeof dayjs }}</p>
<p>dayjs.locale 类型: {{ typeof dayjs.locale }}</p>
<p>dayjs.extend 类型: {{ typeof dayjs.extend }}</p>
<p>当前语言: {{ currentLocale }}</p>
</div>
<div class="test-section">
<h3>3. 全局属性检查</h3>
<p>$dayjs 是否存在: {{ hasGlobalDayjs }}</p>
<p>$dayjs 类型: {{ globalDayjsType }}</p>
<p v-if="hasGlobalDayjs">$dayjs.locale 类型: {{ globalDayjsLocaleType }}</p>
</div>
<div class="test-section">
<h3>4. 手动创建日期选择器</h3>
<a-date-picker
v-model:value="testDate"
@change="handleTestChange"
style="width: 200px; margin-right: 16px;"
/>
<p>测试日期: {{ testDate ? testDate.format('YYYY-MM-DD') : '未选择' }}</p>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, getCurrentInstance } from 'vue';
import dayjs from 'dayjs';
const currentTime = ref(dayjs().format());
const formattedTime = ref(dayjs().format('YYYY-MM-DD HH:mm:ss'));
const relativeTime = ref(dayjs().subtract(2, 'hour').fromNow());
const localeInfo = ref(dayjs.locale());
const currentLocale = ref(dayjs.locale());
const testDate = ref(dayjs());
const instance = getCurrentInstance();
const hasGlobalDayjs = ref(!!(instance?.appContext.config.globalProperties.$dayjs));
const globalDayjsType = ref(hasGlobalDayjs.value ? typeof instance.appContext.config.globalProperties.$dayjs : 'N/A');
const globalDayjsLocaleType = ref(hasGlobalDayjs.value ? typeof instance.appContext.config.globalProperties.$dayjs.locale : 'N/A');
const handleTestChange = (date) => {
console.log('测试日期选择:', date);
};
// 检查dayjs插件是否正常
onMounted(() => {
console.log('Dayjs 调试信息:');
console.log('dayjs:', dayjs);
console.log('dayjs.locale:', dayjs.locale);
console.log('dayjs.extend:', dayjs.extend);
console.log('当前语言:', dayjs.locale());
if (hasGlobalDayjs.value) {
console.log('$dayjs:', instance.appContext.config.globalProperties.$dayjs);
console.log('$dayjs.locale:', instance.appContext.config.globalProperties.$dayjs.locale);
}
});
</script>
<style scoped>
.debug-container {
padding: 20px;
}
.test-section {
margin-bottom: 30px;
padding: 20px;
border: 1px solid #d9d9d9;
border-radius: 6px;
background-color: #fafafa;
}
.test-section h3 {
color: #1890ff;
margin-bottom: 15px;
}
.test-section p {
margin: 5px 0;
font-family: monospace;
}
</style>

View File

@@ -0,0 +1,454 @@
<template>
<div class="installation-task-page">
<!-- 页面标题 -->
<div class="page-header">
<h1 class="page-title">待安装任务</h1>
<div class="header-actions">
<a-button type="primary" @click="handleExportTasks">
安装任务导出
</a-button>
</div>
</div>
<!-- 搜索区域 -->
<div class="search-area">
<a-form layout="inline" :model="searchForm" @finish="handleSearch">
<a-form-item>
<a-input
v-model:value="searchForm.policyNumber"
placeholder="保单编号"
allowClear
style="width: 150px;"
/>
</a-form-item>
<a-form-item>
<a-input
v-model:value="searchForm.keyword"
placeholder="关键字搜索"
allowClear
style="width: 150px;"
/>
</a-form-item>
<a-form-item>
<a-select
v-model:value="searchForm.installationStatus"
placeholder="安装状态"
allowClear
style="width: 120px;"
>
<a-select-option value="待安装">待安装</a-select-option>
<a-select-option value="安装中">安装中</a-select-option>
<a-select-option value="已安装">已安装</a-select-option>
<a-select-option value="安装失败">安装失败</a-select-option>
<a-select-option value="已取消">已取消</a-select-option>
</a-select>
</a-form-item>
<a-form-item>
<a-button type="primary" html-type="submit">
<SearchOutlined />
搜索
</a-button>
</a-form-item>
<a-form-item>
<a-button @click="handleReset">重置</a-button>
</a-form-item>
</a-form>
</div>
<!-- 数据表格 -->
<div class="table-container">
<a-table
:columns="columns"
:data-source="tableData"
:pagination="pagination"
:loading="loading"
row-key="id"
@change="handleTableChange"
size="middle"
:scroll="{ x: 1800 }"
>
<!-- 安装状态列 -->
<template #installationStatus="{ record }">
<a-tag :color="getInstallationStatusColor(record.installationStatus)">
{{ record.installationStatus }}
</a-tag>
</template>
<!-- 时间列格式化 -->
<template #taskGeneratedTime="{ record }">
<span>{{ formatDateTime(record.taskGeneratedTime) }}</span>
</template>
<template #installationCompletedTime="{ record }">
<span>{{ formatDateTime(record.installationCompletedTime) }}</span>
</template>
<!-- 操作列 -->
<template #action="{ record }">
<a-space>
<a-button type="link" size="small" @click="handleView(record)">
查看
</a-button>
<a-button type="link" size="small" @click="handleEdit(record)">
编辑
</a-button>
<a-popconfirm
title="确定要删除这条安装任务吗?"
@confirm="handleDelete(record.id)"
>
<a-button type="link" size="small" danger>
删除
</a-button>
</a-popconfirm>
</a-space>
</template>
</a-table>
</div>
<!-- 数据为空提示 -->
<div v-if="!loading && tableData.length === 0" class="empty-data">
<a-empty description="暂无数据" />
</div>
<!-- 分页信息 -->
<div class="pagination-info">
<span> {{ pagination.total }} 条记录</span>
</div>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue';
import { message } from 'ant-design-vue';
import { SearchOutlined } from '@ant-design/icons-vue';
import dayjs from 'dayjs';
import installationTaskApi from '@/utils/installationTaskApi';
// 响应式数据
const loading = ref(false);
const tableData = ref([]);
// 搜索表单
const searchForm = reactive({
policyNumber: '',
keyword: '',
installationStatus: undefined,
});
// 分页配置
const pagination = reactive({
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total, range) => `${range[0]}-${range[1]} 条,共 ${total}`
});
// 表格列定义
const columns = [
{
title: '申请单号',
dataIndex: 'applicationNumber',
key: 'applicationNumber',
width: 120,
ellipsis: true
},
{
title: '保单编号',
dataIndex: 'policyNumber',
key: 'policyNumber',
width: 120,
ellipsis: true
},
{
title: '产品名称',
dataIndex: 'productName',
key: 'productName',
width: 150,
ellipsis: true
},
{
title: '客户姓名',
dataIndex: 'customerName',
key: 'customerName',
width: 100
},
{
title: '证件类型',
dataIndex: 'idType',
key: 'idType',
width: 100
},
{
title: '证件号码',
dataIndex: 'idNumber',
key: 'idNumber',
width: 160,
ellipsis: true
},
{
title: '养殖生资种类',
dataIndex: 'livestockSupplyType',
key: 'livestockSupplyType',
width: 150,
ellipsis: true
},
{
title: '待安装设备',
dataIndex: 'pendingDevices',
key: 'pendingDevices',
width: 150,
ellipsis: true
},
{
title: '安装状态',
dataIndex: 'installationStatus',
key: 'installationStatus',
width: 100,
slots: { customRender: 'installationStatus' }
},
{
title: '生成安装任务时间',
dataIndex: 'taskGeneratedTime',
key: 'taskGeneratedTime',
width: 160,
slots: { customRender: 'taskGeneratedTime' }
},
{
title: '安装完成生效时间',
dataIndex: 'installationCompletedTime',
key: 'installationCompletedTime',
width: 160,
slots: { customRender: 'installationCompletedTime' }
},
{
title: '操作',
key: 'action',
width: 200,
fixed: 'right',
slots: { customRender: 'action' }
}
];
// 生命周期钩子
onMounted(() => {
fetchInstallationTasks();
});
// 方法定义
const fetchInstallationTasks = async () => {
loading.value = true;
try {
const params = {
page: pagination.current,
pageSize: pagination.pageSize,
...searchForm
};
const response = await installationTaskApi.getInstallationTasks(params);
if (response.code === 200) {
tableData.value = response.data.rows || response.data.list || [];
pagination.total = response.data.total || 0;
} else {
message.error(response.message || '获取安装任务列表失败');
}
} catch (error) {
console.error('获取安装任务列表失败:', error);
message.error('获取安装任务列表失败');
} finally {
loading.value = false;
}
};
const handleSearch = () => {
pagination.current = 1;
fetchInstallationTasks();
};
const handleReset = () => {
Object.assign(searchForm, {
policyNumber: '',
keyword: '',
installationStatus: undefined,
});
handleSearch();
};
const handleTableChange = (paginationInfo) => {
pagination.current = paginationInfo.current;
pagination.pageSize = paginationInfo.pageSize;
fetchInstallationTasks();
};
const handleView = (record) => {
console.log('查看记录:', record);
message.info('查看功能开发中');
};
const handleEdit = (record) => {
console.log('编辑记录:', record);
message.info('编辑功能开发中');
};
const handleDelete = async (id) => {
try {
const response = await installationTaskApi.deleteInstallationTask(id);
if (response.code === 200) {
message.success('删除成功');
fetchInstallationTasks();
} else {
message.error(response.message || '删除失败');
}
} catch (error) {
console.error('删除失败:', error);
message.error('删除失败');
}
};
const handleExportTasks = async () => {
try {
const response = await installationTaskApi.exportInstallationTasks(searchForm);
if (response) {
// 处理文件下载
const blob = new Blob([response], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
});
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `安装任务导出_${dayjs().format('YYYY-MM-DD_HH-mm-ss')}.xlsx`;
link.click();
window.URL.revokeObjectURL(url);
message.success('导出成功');
} else {
message.error('导出失败');
}
} catch (error) {
console.error('导出失败:', error);
message.error('导出失败');
}
};
// 工具方法
const formatDateTime = (dateTime) => {
return dateTime ? dayjs(dateTime).format('YYYY-MM-DD HH:mm:ss') : '-';
};
const getInstallationStatusColor = (status) => {
const colorMap = {
'待安装': 'orange',
'安装中': 'blue',
'已安装': 'green',
'安装失败': 'red',
'已取消': 'default'
};
return colorMap[status] || 'default';
};
</script>
<style scoped>
.installation-task-container {
padding: 20px;
background: #fff;
min-height: 100vh;
}
.page-header {
margin-bottom: 20px;
padding-bottom: 16px;
border-bottom: 1px solid #f0f0f0;
}
.page-title {
margin: 0;
font-size: 20px;
font-weight: 600;
color: #262626;
}
.search-section {
margin-bottom: 20px;
padding: 16px;
background: #fafafa;
border-radius: 6px;
}
.search-row {
align-items: center;
}
.text-right {
text-align: right;
}
.table-section {
background: #fff;
border-radius: 6px;
}
:deep(.ant-table-thead > tr > th) {
background: #fafafa;
font-weight: 600;
}
:deep(.ant-pagination) {
margin-top: 16px;
text-align: right;
}
</style>
<style scoped>
.installation-task-page {
padding: 24px;
background: #fff;
min-height: 100vh;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
border-bottom: 1px solid #f0f0f0;
padding-bottom: 16px;
}
.page-title {
font-size: 24px;
font-weight: 600;
color: #262626;
margin: 0;
}
.header-actions {
display: flex;
gap: 8px;
}
.search-area {
margin-bottom: 16px;
padding: 16px;
background: #fafafa;
border-radius: 6px;
}
.table-container {
margin-bottom: 16px;
}
.empty-data {
text-align: center;
padding: 40px 0;
}
.pagination-info {
text-align: right;
padding: 16px 0;
color: #666;
font-size: 14px;
}
</style>

View File

@@ -0,0 +1,122 @@
<template>
<div style="padding: 20px;">
<h2>登录和API测试</h2>
<div style="margin-bottom: 20px;">
<h3>当前状态</h3>
<p>Token: {{ userStore.token ? '已设置' : '未设置' }}</p>
<p>用户信息: {{ JSON.stringify(userStore.userInfo) }}</p>
</div>
<div style="margin-bottom: 20px;">
<h3>快速登录</h3>
<a-button type="primary" @click="quickLogin" :loading="loginLoading">
使用 admin/123456 登录
</a-button>
</div>
<div style="margin-bottom: 20px;">
<h3>测试API调用</h3>
<a-button @click="testStatsAPI" :loading="statsLoading" style="margin-right: 10px;">
测试系统统计API
</a-button>
<a-button @click="testAuthAPI" :loading="authLoading">
测试认证API
</a-button>
</div>
<div v-if="apiResults.length">
<h3>API调用结果</h3>
<div v-for="(result, index) in apiResults" :key="index" style="margin-bottom: 10px; padding: 10px; border: 1px solid #ddd;">
<strong>{{ result.name }}:</strong>
<pre>{{ result.data }}</pre>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { message } from 'ant-design-vue'
import { useUserStore } from '@/stores/user'
import { authAPI, dashboardAPI } from '@/utils/api'
const userStore = useUserStore()
const loginLoading = ref(false)
const statsLoading = ref(false)
const authLoading = ref(false)
const apiResults = ref([])
const quickLogin = async () => {
loginLoading.value = true
try {
const response = await authAPI.login({
username: 'admin',
password: '123456'
})
if (response.status === 'success') {
userStore.setToken(response.data.token)
userStore.setUserInfo(response.data.user)
message.success('登录成功!')
apiResults.value.unshift({
name: '登录API',
data: JSON.stringify(response, null, 2)
})
} else {
message.error(response.message || '登录失败')
}
} catch (error) {
message.error(error.response?.data?.message || '登录失败')
apiResults.value.unshift({
name: '登录API (错误)',
data: JSON.stringify(error.response?.data || error.message, null, 2)
})
} finally {
loginLoading.value = false
}
}
const testStatsAPI = async () => {
statsLoading.value = true
try {
const response = await dashboardAPI.getStats()
message.success('系统统计API调用成功')
apiResults.value.unshift({
name: '系统统计API',
data: JSON.stringify(response, null, 2)
})
} catch (error) {
message.error('系统统计API调用失败')
apiResults.value.unshift({
name: '系统统计API (错误)',
data: JSON.stringify(error.response?.data || error.message, null, 2)
})
} finally {
statsLoading.value = false
}
}
const testAuthAPI = async () => {
authLoading.value = true
try {
const response = await authAPI.getProfile()
message.success('认证API调用成功')
apiResults.value.unshift({
name: '用户资料API',
data: JSON.stringify(response, null, 2)
})
} catch (error) {
message.error('认证API调用失败')
apiResults.value.unshift({
name: '用户资料API (错误)',
data: JSON.stringify(error.response?.data || error.message, null, 2)
})
} finally {
authLoading.value = false
}
}
</script>

View File

@@ -0,0 +1,121 @@
<template>
<div class="range-test">
<h2>范围选择器测试</h2>
<div class="test-section">
<h3>1. 使用 range 属性的日期选择器</h3>
<a-date-picker
v-model:value="rangeDate1"
range
@change="logRangeChange1"
style="width: 300px; margin-bottom: 20px;"
/>
<p>选择的值: {{ rangeDate1 ? rangeDate1.map(d => d && d.format ? d.format('YYYY-MM-DD') : String(d)).join(' ~ ') : '未选择' }}</p>
<p>值类型: {{ Array.isArray(rangeDate1) ? 'array' : typeof rangeDate1 }}</p>
<p v-if="Array.isArray(rangeDate1)">数组元素类型: {{ rangeDate1.map(d => typeof d).join(', ') }}</p>
<p v-if="Array.isArray(rangeDate1)">元素有locale方法: {{ rangeDate1.map(d => d && typeof d.locale === 'function').join(', ') }}</p>
</div>
<div class="test-section">
<h3>2. 使用 a-range-picker 组件</h3>
<a-range-picker
v-model:value="rangeDate2"
@change="logRangeChange2"
style="width: 300px; margin-bottom: 20px;"
/>
<p>选择的值: {{ rangeDate2 ? rangeDate2.map(d => d && d.format ? d.format('YYYY-MM-DD') : String(d)).join(' ~ ') : '未选择' }}</p>
<p>值类型: {{ Array.isArray(rangeDate2) ? 'array' : typeof rangeDate2 }}</p>
<p v-if="Array.isArray(rangeDate2)">数组元素类型: {{ rangeDate2.map(d => typeof d).join(', ') }}</p>
<p v-if="Array.isArray(rangeDate2)">元素有locale方法: {{ rangeDate2.map(d => d && typeof d.locale === 'function').join(', ') }}</p>
</div>
<div class="test-section">
<h3>3. 手动验证dayjs实例</h3>
<button @click="testDayjsInstances" style="margin-right: 16px;">测试dayjs实例</button>
<p>测试结果: {{ testResult }}</p>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import dayjs from 'dayjs';
const rangeDate1 = ref([dayjs().subtract(7, 'day'), dayjs()]);
const rangeDate2 = ref([dayjs().subtract(7, 'day'), dayjs()]);
const testResult = ref('');
const logRangeChange1 = (dates) => {
console.log('range属性选择器变化:', dates);
if (Array.isArray(dates)) {
dates.forEach((date, index) => {
console.log(`日期 ${index + 1}:`, date, '类型:', typeof date);
if (date && typeof date.locale === 'function') {
console.log(`日期 ${index + 1} 有locale方法`);
} else {
console.log(`日期 ${index + 1} 缺少locale方法`);
}
});
}
};
const logRangeChange2 = (dates) => {
console.log('RangePicker组件变化:', dates);
if (Array.isArray(dates)) {
dates.forEach((date, index) => {
console.log(`日期 ${index + 1}:`, date, '类型:', typeof date);
if (date && typeof date.locale === 'function') {
console.log(`日期 ${index + 1} 有locale方法`);
} else {
console.log(`日期 ${index + 1} 缺少locale方法`);
}
});
}
};
const testDayjsInstances = () => {
try {
// 测试当前使用的dayjs实例
const testDate1 = dayjs('2023-01-01');
const testDate2 = dayjs('2023-12-31');
const hasLocale1 = testDate1 && typeof testDate1.locale === 'function';
const hasLocale2 = testDate2 && typeof testDate2.locale === 'function';
testResult.value = `测试成功! 日期1有locale: ${hasLocale1}, 日期2有locale: ${hasLocale2}`;
console.log('手动dayjs实例测试:', {
date1: testDate1,
date2: testDate2,
hasLocale1,
hasLocale2
});
} catch (error) {
testResult.value = `测试失败: ${error.message}`;
console.error('手动dayjs实例测试错误:', error);
}
};
</script>
<style scoped>
.range-test {
padding: 20px;
}
.test-section {
margin-bottom: 30px;
padding: 20px;
border: 1px solid #d9d9d9;
border-radius: 6px;
background-color: #fafafa;
}
.test-section h3 {
color: #1890ff;
margin-bottom: 15px;
}
.test-section p {
margin: 5px 0;
font-family: monospace;
}
</style>

View File

@@ -0,0 +1,67 @@
<template>
<div class="simple-test">
<h2>简单Dayjs测试</h2>
<div class="test-section">
<h3>1. 直接使用dayjs</h3>
<p>当前时间: {{ currentTime }}</p>
<p>格式化测试: {{ formattedTime }}</p>
<p>dayjs实例类型: {{ dayjsType }}</p>
<p>dayjs.locale方法存在: {{ hasLocaleMethod }}</p>
</div>
<div class="test-section">
<h3>2. 简单日期选择器</h3>
<a-date-picker
v-model:value="testDate"
style="width: 200px;"
/>
<p>选择的值: {{ testDate ? testDate.format('YYYY-MM-DD') : '未选择' }}</p>
<p>值类型: {{ testDate ? typeof testDate : 'null' }}</p>
<p v-if="testDate">有locale方法: {{ testDate && typeof testDate.locale === 'function' }}</p>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import dayjs from 'dayjs';
const currentTime = ref(dayjs().format());
const formattedTime = ref(dayjs().format('YYYY-MM-DD HH:mm:ss'));
const dayjsType = ref(typeof dayjs);
const hasLocaleMethod = ref(typeof dayjs.locale === 'function');
const testDate = ref(dayjs());
onMounted(() => {
console.log('简单Dayjs测试:');
console.log('dayjs:', dayjs);
console.log('dayjs.locale:', dayjs.locale);
console.log('dayjs实例:', dayjs());
console.log('dayjs实例locale方法:', dayjs().locale);
});
</script>
<style scoped>
.simple-test {
padding: 20px;
}
.test-section {
margin-bottom: 30px;
padding: 20px;
border: 1px solid #d9d9d9;
border-radius: 6px;
background-color: #fafafa;
}
.test-section h3 {
color: #1890ff;
margin-bottom: 15px;
}
.test-section p {
margin: 5px 0;
font-family: monospace;
}
</style>

View File

@@ -0,0 +1,669 @@
<template>
<div class="supervision-task-page">
<!-- 页面标题 -->
<div class="page-header">
<h1 class="page-title">监管任务导入</h1>
</div>
<!-- 操作按钮区 -->
<div class="action-buttons">
<a-space>
<a-button type="primary" @click="handleCreateTask">
新增监管任务
</a-button>
<a-button @click="handleTaskGuidance">
任务导入
</a-button>
<a-button @click="handleBatchCreate">
批量新增
</a-button>
</a-space>
</div>
<!-- 搜索区域 -->
<div class="search-area">
<a-form layout="inline" :model="searchForm" @finish="handleSearch">
<a-form-item>
<a-input
v-model:value="searchForm.policyNumber"
placeholder="保单编号"
allowClear
style="width: 120px;"
/>
</a-form-item>
<a-form-item>
<a-input
v-model:value="searchForm.customerName"
placeholder="客户姓名"
allowClear
style="width: 120px;"
/>
</a-form-item>
<a-form-item>
<a-button type="primary" html-type="submit">
<SearchOutlined />
搜索
</a-button>
</a-form-item>
<a-form-item>
<a-button @click="handleReset">重置</a-button>
</a-form-item>
</a-form>
</div>
<!-- 数据表格 -->
<div class="table-container">
<a-table
:columns="columns"
:data-source="tableData"
:pagination="pagination"
:loading="loading"
row-key="id"
:row-selection="rowSelection"
@change="handleTableChange"
size="middle"
:scroll="{ x: 1500 }"
>
<!-- 状态列 -->
<template #status="{ record }">
<a-tag :color="getStatusColor(record.status)">
{{ getStatusText(record.status) }}
</a-tag>
</template>
<!-- 任务类型列 -->
<template #taskType="{ record }">
<a-tag :color="getTaskTypeColor(record.taskType)">
{{ getTaskTypeText(record.taskType) }}
</a-tag>
</template>
<!-- 优先级列 -->
<template #priority="{ record }">
<a-tag :color="getPriorityColor(record.priority)">
{{ getPriorityText(record.priority) }}
</a-tag>
</template>
<!-- 金额列 -->
<template #amount="{ record }">
<span>{{ formatAmount(record.applicableAmount) }}</span>
</template>
<!-- 操作列 -->
<template #action="{ record }">
<a-space>
<a-button type="link" size="small" @click="handleView(record)">
查看
</a-button>
<a-button type="link" size="small" @click="handleEdit(record)">
编辑
</a-button>
<a-popconfirm
title="确定要删除这条监管任务吗?"
@confirm="handleDelete(record.id)"
>
<a-button type="link" size="small" danger>
删除
</a-button>
</a-popconfirm>
</a-space>
</template>
</a-table>
</div>
<!-- 数据为空提示 -->
<div v-if="!loading && tableData.length === 0" class="empty-data">
<a-empty description="暂无数据" />
</div>
<!-- 分页信息 -->
<div class="pagination-info">
<span> {{ pagination.total }} </span>
<span>页共 0 </span>
</div>
<!-- 创建/编辑监管任务弹窗 -->
<a-modal
:title="modalTitle"
:open="modalVisible"
:width="800"
@ok="handleModalOk"
@cancel="handleModalCancel"
:confirmLoading="modalLoading"
>
<a-form
ref="modalFormRef"
:model="modalForm"
:rules="modalRules"
layout="vertical"
>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="申请单号" name="applicationNumber">
<a-input v-model:value="modalForm.applicationNumber" placeholder="请输入申请单号" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="保单编号" name="policyNumber">
<a-input v-model:value="modalForm.policyNumber" placeholder="请输入保单编号" />
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="产品名称" name="productName">
<a-input v-model:value="modalForm.productName" placeholder="请输入产品名称" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="保险期间" name="insurancePeriod">
<a-input v-model:value="modalForm.insurancePeriod" placeholder="请输入保险期间" />
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="客户姓名" name="customerName">
<a-input v-model:value="modalForm.customerName" placeholder="请输入客户姓名" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="证件类型" name="idType">
<a-select v-model:value="modalForm.idType" placeholder="请选择证件类型">
<a-select-option value="身份证">身份证</a-select-option>
<a-select-option value="护照">护照</a-select-option>
<a-select-option value="军官证">军官证</a-select-option>
<a-select-option value="其他">其他</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="idNumber">
<a-input v-model:value="modalForm.idNumber" placeholder="请输入证件号码" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="适用金额" name="applicableAmount">
<a-input-number
v-model:value="modalForm.applicableAmount"
placeholder="请输入适用金额"
:min="0"
:precision="2"
style="width: 100%"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="任务类型" name="taskType">
<a-select v-model:value="modalForm.taskType" placeholder="请选择任务类型">
<a-select-option value="new_application">新增监管任务</a-select-option>
<a-select-option value="task_guidance">任务导入</a-select-option>
<a-select-option value="batch_operation">批量新增</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="优先级" name="priority">
<a-select v-model:value="modalForm.priority" placeholder="请选择优先级">
<a-select-option value="low"></a-select-option>
<a-select-option value="medium"></a-select-option>
<a-select-option value="high"></a-select-option>
<a-select-option value="urgent">紧急</a-select-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-form-item label="备注" name="remarks">
<a-textarea
v-model:value="modalForm.remarks"
placeholder="请输入备注信息"
:rows="3"
/>
</a-form-item>
</a-form>
</a-modal>
</div>
</template>
<script>
import { ref, reactive, computed, onMounted } from 'vue'
import { message } from 'ant-design-vue'
import { SearchOutlined } from '@ant-design/icons-vue'
import { supervisionTaskApi } from '@/utils/api'
export default {
name: 'SupervisionTaskManagement',
components: {
SearchOutlined
},
setup() {
const loading = ref(false)
const tableData = ref([])
const selectedRowKeys = ref([])
const modalVisible = ref(false)
const modalLoading = ref(false)
const modalFormRef = ref()
const editingId = ref(null)
// 搜索表单
const searchForm = reactive({
policyNumber: '',
customerName: ''
})
// 分页配置
const pagination = reactive({
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total, range) => `${range[0]}-${range[1]} 条,共 ${total}`
})
// 弹窗表单
const modalForm = reactive({
applicationNumber: '',
policyNumber: '',
productName: '',
insurancePeriod: '',
customerName: '',
idType: '',
idNumber: '',
supervisorySuppliesQuantity: null,
taskStatus: '',
priority: '中',
notes: ''
})
// 表单验证规则
const modalRules = {
applicationNumber: [
{ required: true, message: '请输入申请单号', trigger: 'blur' }
],
policyNumber: [
{ required: true, message: '请输入保单编号', trigger: 'blur' }
],
productName: [
{ required: true, message: '请输入产品名称', trigger: 'blur' }
],
customerName: [
{ required: true, message: '请输入客户姓名', trigger: 'blur' }
],
idNumber: [
{ required: true, message: '请输入证件号码', trigger: 'blur' }
]
}
// 计算属性
const modalTitle = computed(() => {
return editingId.value ? '编辑监管任务' : '新增监管任务'
})
// 表格列配置
const columns = [
{
title: '申请单号',
dataIndex: 'applicationNumber',
key: 'applicationNumber',
width: 120,
fixed: 'left'
},
{
title: '保单编号',
dataIndex: 'policyNumber',
key: 'policyNumber',
width: 120
},
{
title: '产品名称',
dataIndex: 'productName',
key: 'productName',
width: 150
},
{
title: '保险期间',
dataIndex: 'insurancePeriod',
key: 'insurancePeriod',
width: 120
},
{
title: '客户姓名',
dataIndex: 'customerName',
key: 'customerName',
width: 100
},
{
title: '证件类型',
dataIndex: 'idType',
key: 'idType',
width: 100
},
{
title: '证件号码',
dataIndex: 'idNumber',
key: 'idNumber',
width: 150
},
{
title: '监管生资数量',
dataIndex: 'supervisorySuppliesQuantity',
key: 'supervisorySuppliesQuantity',
width: 120
},
{
title: '任务状态',
dataIndex: 'taskStatus',
key: 'taskStatus',
width: 100,
slots: { customRender: 'status' }
},
{
title: '优先级',
dataIndex: 'priority',
key: 'priority',
width: 80,
slots: { customRender: 'priority' }
},
{
title: '操作',
key: 'action',
width: 150,
fixed: 'right',
slots: { customRender: 'action' }
}
]
// 行选择配置
const rowSelection = {
selectedRowKeys: selectedRowKeys,
onChange: (keys) => {
selectedRowKeys.value = keys
}
}
// 状态相关方法
const getStatusColor = (status) => {
const colorMap = {
'待处理': 'orange',
'处理中': 'blue',
'已完成': 'green',
'已取消': 'red'
}
return colorMap[status] || 'default'
}
const getStatusText = (status) => {
return status || '待处理'
}
const getTaskTypeColor = (taskType) => {
const colorMap = {
new_application: 'blue',
task_guidance: 'green',
batch_operation: 'purple'
}
return colorMap[taskType] || 'default'
}
const getTaskTypeText = (taskType) => {
const textMap = {
new_application: '新增监管任务',
task_guidance: '任务导入',
batch_operation: '批量新增'
}
return textMap[taskType] || taskType
}
const getPriorityColor = (priority) => {
const colorMap = {
'低': 'green',
'中': 'blue',
'高': 'orange',
'紧急': 'red'
}
return colorMap[priority] || 'default'
}
const getPriorityText = (priority) => {
return priority || '中'
}
const formatAmount = (amount) => {
if (!amount) return '0.00'
return new Intl.NumberFormat('zh-CN', {
style: 'currency',
currency: 'CNY'
}).format(amount)
}
// 数据加载
const loadData = async () => {
loading.value = true
try {
const params = {
page: pagination.current,
limit: pagination.pageSize,
policyNumber: searchForm.policyNumber,
customerName: searchForm.customerName
}
const response = await supervisionTaskApi.getList(params)
if (response.code === 200) {
tableData.value = response.data.list
pagination.total = response.data.total
} else {
message.error(response.message || '获取数据失败')
}
} catch (error) {
console.error('加载数据失败:', error)
message.error('获取数据失败')
} finally {
loading.value = false
}
}
// 事件处理方法
const handleSearch = () => {
pagination.current = 1
loadData()
}
const handleReset = () => {
searchForm.policyNumber = ''
searchForm.customerName = ''
pagination.current = 1
loadData()
}
const handleTableChange = (paginationConfig) => {
pagination.current = paginationConfig.current
pagination.pageSize = paginationConfig.pageSize
loadData()
}
const handleCreateTask = () => {
editingId.value = null
resetModalForm()
modalVisible.value = true
}
const handleTaskGuidance = () => {
message.info('任务导入功能开发中...')
}
const handleBatchCreate = () => {
message.info('批量新增功能开发中...')
}
const handleView = (record) => {
console.log('查看详情:', record)
message.info('查看功能开发中...')
}
const handleEdit = (record) => {
editingId.value = record.id
Object.assign(modalForm, record)
modalVisible.value = true
}
const handleDelete = async (id) => {
try {
const response = await supervisionTaskApi.delete(id)
if (response.code === 200) {
message.success('删除成功')
loadData()
} else {
message.error(response.message || '删除失败')
}
} catch (error) {
console.error('删除失败:', error)
message.error('删除失败')
}
}
const resetModalForm = () => {
Object.assign(modalForm, {
applicationNumber: '',
policyNumber: '',
productName: '',
insurancePeriod: '',
customerName: '',
idType: '',
idNumber: '',
supervisorySuppliesQuantity: null,
taskStatus: '',
priority: '中',
notes: ''
})
}
const handleModalOk = async () => {
try {
await modalFormRef.value.validate()
modalLoading.value = true
const apiMethod = editingId.value ?
supervisionTaskApi.update.bind(null, editingId.value) :
supervisionTaskApi.create
const response = await supervisionTaskApi.create(modalForm)
if (response.code === 201) {
message.success(editingId.value ? '更新成功' : '创建成功')
modalVisible.value = false
loadData()
} else {
message.error(response.message || '操作失败')
}
} catch (error) {
console.error('提交失败:', error)
message.error('操作失败')
} finally {
modalLoading.value = false
}
}
const handleModalCancel = () => {
modalVisible.value = false
resetModalForm()
}
// 生命周期
onMounted(() => {
loadData()
})
return {
loading,
tableData,
selectedRowKeys,
searchForm,
pagination,
columns,
rowSelection,
modalVisible,
modalLoading,
modalForm,
modalFormRef,
modalRules,
modalTitle,
getStatusColor,
getStatusText,
getTaskTypeColor,
getTaskTypeText,
getPriorityColor,
getPriorityText,
formatAmount,
handleSearch,
handleReset,
handleTableChange,
handleCreateTask,
handleTaskGuidance,
handleBatchCreate,
handleView,
handleEdit,
handleDelete,
handleModalOk,
handleModalCancel
}
}
}
</script>
<style scoped>
.supervision-task-page {
padding: 24px;
background: #fff;
min-height: 100vh;
}
.page-header {
margin-bottom: 24px;
}
.page-title {
font-size: 24px;
font-weight: 600;
color: #262626;
margin: 0;
}
.action-buttons {
margin-bottom: 16px;
}
.search-area {
margin-bottom: 16px;
padding: 16px;
background: #fafafa;
border-radius: 6px;
}
.table-container {
margin-bottom: 16px;
}
.empty-data {
text-align: center;
padding: 40px 0;
}
.pagination-info {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 14px;
color: #666;
}
</style>

View File

@@ -0,0 +1,729 @@
<template>
<div class="supervisory-task-management">
<!-- 页面标题 -->
<div class="page-header">
<h2 class="page-title">监管任务导入</h2>
</div>
<!-- 操作栏 -->
<div class="action-bar">
<div class="left-actions">
<a-button type="primary" @click="showCreateModal" :icon="h(PlusOutlined)">
新增监管任务
</a-button>
<a-button @click="handleExport" :icon="h(ExportOutlined)">
任务导出
</a-button>
<a-button @click="showBulkCreateModal" :icon="h(CloudUploadOutlined)">
批量新增
</a-button>
</div>
<div class="right-actions">
<a-input-group compact>
<a-select
v-model:value="searchType"
placeholder="搜索类型"
style="width: 120px"
>
<a-select-option value="policyNumber">保单号</a-select-option>
<a-select-option value="customerName">承保人分类</a-select-option>
</a-select>
<a-input
v-model:value="searchValue"
placeholder="请输入搜索内容"
style="width: 200px"
@press-enter="handleSearch"
/>
</a-input-group>
<a-button type="primary" @click="handleSearch" :icon="h(SearchOutlined)">
搜索
</a-button>
<a-button @click="resetSearch">重置</a-button>
</div>
</div>
<!-- 数据表格 -->
<div class="table-container">
<a-table
:columns="columns"
:data-source="taskList"
:loading="loading"
:pagination="pagination"
@change="handleTableChange"
row-key="id"
size="middle"
>
<!-- 任务状态列 -->
<template #taskStatus="{ record }">
<a-tag
:color="getStatusColor(record.taskStatus)"
>
{{ record.taskStatus }}
</a-tag>
</template>
<!-- 优先级列 -->
<template #priority="{ record }">
<a-tag
:color="getPriorityColor(record.priority)"
>
{{ record.priority }}
</a-tag>
</template>
<!-- 操作列 -->
<template #action="{ record }">
<a-space>
<a-button
type="link"
size="small"
@click="showViewModal(record)"
>
查看
</a-button>
<a-button
type="link"
size="small"
@click="showEditModal(record)"
>
编辑
</a-button>
<a-popconfirm
title="确定要删除这个监管任务吗?"
@confirm="handleDelete(record.id)"
>
<a-button
type="link"
size="small"
danger
>
删除
</a-button>
</a-popconfirm>
</a-space>
</template>
</a-table>
</div>
<!-- 创建/编辑任务弹窗 -->
<a-modal
v-model:open="taskModalVisible"
:title="modalMode === 'create' ? '新增监管任务' : modalMode === 'edit' ? '编辑监管任务' : '查看监管任务'"
:width="800"
:footer="modalMode === 'view' ? null : undefined"
@ok="handleModalOk"
@cancel="handleModalCancel"
>
<a-form
ref="taskFormRef"
:model="taskForm"
:rules="taskFormRules"
:label-col="{ span: 6 }"
:wrapper-col="{ span: 18 }"
:disabled="modalMode === 'view'"
>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="申请单号" name="applicationNumber">
<a-input v-model:value="taskForm.applicationNumber" placeholder="请输入申请单号" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="保单编号" name="policyNumber">
<a-input v-model:value="taskForm.policyNumber" placeholder="请输入保单编号" />
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="产品名称" name="productName">
<a-input v-model:value="taskForm.productName" placeholder="请输入产品名称" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="保险周期" name="insurancePeriod">
<a-input v-model:value="taskForm.insurancePeriod" placeholder="请输入保险周期" />
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="客户姓名" name="customerName">
<a-input v-model:value="taskForm.customerName" placeholder="请输入客户姓名" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="证件类型" name="idType">
<a-select v-model:value="taskForm.idType" placeholder="请选择证件类型">
<a-select-option value="身份证">身份证</a-select-option>
<a-select-option value="护照">护照</a-select-option>
<a-select-option value="军官证">军官证</a-select-option>
<a-select-option value="士兵证">士兵证</a-select-option>
<a-select-option value="港澳台居民居住证">港澳台居民居住证</a-select-option>
<a-select-option value="其他">其他</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="idNumber">
<a-input v-model:value="taskForm.idNumber" placeholder="请输入证件号码" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="监管生资数量" name="supervisorySuppliesQuantity">
<a-input-number
v-model:value="taskForm.supervisorySuppliesQuantity"
placeholder="请输入监管生资数量"
:min="0"
style="width: 100%"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="任务状态" name="taskStatus">
<a-select v-model:value="taskForm.taskStatus" placeholder="请选择任务状态">
<a-select-option value="待处理">待处理</a-select-option>
<a-select-option value="处理中">处理中</a-select-option>
<a-select-option value="已完成">已完成</a-select-option>
<a-select-option value="已取消">已取消</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="优先级" name="priority">
<a-select v-model:value="taskForm.priority" placeholder="请选择优先级">
<a-select-option value="低"></a-select-option>
<a-select-option value="中"></a-select-option>
<a-select-option value="高"></a-select-option>
<a-select-option value="紧急">紧急</a-select-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-form-item label="适用生资" name="applicableSupplies">
<a-textarea
v-model:value="taskForm.applicableSuppliesText"
placeholder="请输入适用生资信息"
:rows="3"
/>
</a-form-item>
<a-form-item label="备注信息" name="notes">
<a-textarea
v-model:value="taskForm.notes"
placeholder="请输入备注信息"
:rows="3"
/>
</a-form-item>
</a-form>
</a-modal>
<!-- 批量新增弹窗 -->
<a-modal
v-model:open="bulkCreateModalVisible"
title="批量新增监管任务"
:width="600"
@ok="handleBulkCreateOk"
@cancel="handleBulkCreateCancel"
>
<div class="bulk-create-content">
<a-upload-dragger
:file-list="fileList"
:before-upload="beforeUpload"
@remove="handleRemove"
accept=".xlsx,.xls,.csv"
>
<p class="ant-upload-drag-icon">
<CloudUploadOutlined />
</p>
<p class="ant-upload-text">点击或拖拽文件到此区域上传</p>
<p class="ant-upload-hint">
支持 Excel(.xlsx, .xls) CSV 文件格式
</p>
</a-upload-dragger>
<div class="template-download" style="margin-top: 16px;">
<a-button type="link" @click="downloadTemplate">
下载导入模板
</a-button>
</div>
</div>
</a-modal>
</div>
</template>
<script setup>
import { ref, reactive, computed, onMounted, h } from 'vue'
import { message } from 'ant-design-vue'
import {
PlusOutlined,
SearchOutlined,
ExportOutlined,
CloudUploadOutlined
} from '@ant-design/icons-vue'
import { supervisoryTaskApi } from '@/utils/api'
// 响应式数据
const loading = ref(false)
const taskList = ref([])
const taskModalVisible = ref(false)
const bulkCreateModalVisible = ref(false)
const modalMode = ref('create') // create, edit, view
const taskFormRef = ref()
const searchType = ref('policyNumber')
const searchValue = ref('')
const fileList = ref([])
// 表单数据
const taskForm = reactive({
id: null,
applicationNumber: '',
policyNumber: '',
productName: '',
insurancePeriod: '',
customerName: '',
idType: '身份证',
idNumber: '',
supervisorySuppliesQuantity: 0,
taskStatus: '待处理',
priority: '中',
applicableSuppliesText: '',
notes: ''
})
// 表单验证规则
const taskFormRules = {
applicationNumber: [
{ required: true, message: '请输入申请单号', trigger: 'blur' }
],
policyNumber: [
{ required: true, message: '请输入保单编号', trigger: 'blur' }
],
productName: [
{ required: true, message: '请输入产品名称', trigger: 'blur' }
],
customerName: [
{ required: true, message: '请输入客户姓名', trigger: 'blur' }
],
idNumber: [
{ required: true, message: '请输入证件号码', trigger: 'blur' }
]
}
// 分页配置
const pagination = reactive({
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `${total} 条数据`
})
// 表格列配置
const columns = [
{
title: '申请单号',
dataIndex: 'applicationNumber',
key: 'applicationNumber',
width: 150
},
{
title: '保单编号',
dataIndex: 'policyNumber',
key: 'policyNumber',
width: 150
},
{
title: '产品名称',
dataIndex: 'productName',
key: 'productName',
width: 120
},
{
title: '保险周期',
dataIndex: 'insurancePeriod',
key: 'insurancePeriod',
width: 100
},
{
title: '客户姓名',
dataIndex: 'customerName',
key: 'customerName',
width: 100
},
{
title: '证件类型',
dataIndex: 'idType',
key: 'idType',
width: 100
},
{
title: '证件号码',
dataIndex: 'idNumber',
key: 'idNumber',
width: 150
},
{
title: '适用生资',
dataIndex: 'applicableSupplies',
key: 'applicableSupplies',
width: 120,
customRender: ({ record }) => {
try {
const supplies = JSON.parse(record.applicableSupplies || '[]')
return Array.isArray(supplies) ? supplies.join(', ') : record.applicableSupplies
} catch {
return record.applicableSupplies || '-'
}
}
},
{
title: '监管生资数量',
dataIndex: 'supervisorySuppliesQuantity',
key: 'supervisorySuppliesQuantity',
width: 120
},
{
title: '操作',
key: 'action',
slots: { customRender: 'action' },
width: 200,
fixed: 'right'
}
]
// 状态颜色映射
const getStatusColor = (status) => {
const colorMap = {
'待处理': 'orange',
'处理中': 'blue',
'已完成': 'green',
'已取消': 'red'
}
return colorMap[status] || 'default'
}
// 优先级颜色映射
const getPriorityColor = (priority) => {
const colorMap = {
'低': 'green',
'中': 'blue',
'高': 'orange',
'紧急': 'red'
}
return colorMap[priority] || 'default'
}
// 获取任务列表
const fetchTaskList = async () => {
try {
loading.value = true
const params = {
page: pagination.current,
limit: pagination.pageSize
}
// 添加搜索条件
if (searchValue.value) {
params[searchType.value] = searchValue.value
}
const response = await supervisoryTaskApi.getList(params)
if (response.code === 200) {
taskList.value = response.data.list
pagination.total = response.data.total
}
} catch (error) {
console.error('获取任务列表失败:', error)
message.error('获取任务列表失败')
} finally {
loading.value = false
}
}
// 表格变化处理
const handleTableChange = (pag) => {
pagination.current = pag.current
pagination.pageSize = pag.pageSize
fetchTaskList()
}
// 搜索处理
const handleSearch = () => {
pagination.current = 1
fetchTaskList()
}
// 重置搜索
const resetSearch = () => {
searchValue.value = ''
pagination.current = 1
fetchTaskList()
}
// 显示创建弹窗
const showCreateModal = () => {
modalMode.value = 'create'
resetTaskForm()
taskModalVisible.value = true
}
// 显示编辑弹窗
const showEditModal = (record) => {
modalMode.value = 'edit'
fillTaskForm(record)
taskModalVisible.value = true
}
// 显示查看弹窗
const showViewModal = (record) => {
modalMode.value = 'view'
fillTaskForm(record)
taskModalVisible.value = true
}
// 填充表单数据
const fillTaskForm = (record) => {
Object.keys(taskForm).forEach(key => {
if (key === 'applicableSuppliesText') {
try {
const supplies = JSON.parse(record.applicableSupplies || '[]')
taskForm[key] = Array.isArray(supplies) ? supplies.join('\n') : record.applicableSupplies || ''
} catch {
taskForm[key] = record.applicableSupplies || ''
}
} else {
taskForm[key] = record[key]
}
})
}
// 重置表单
const resetTaskForm = () => {
Object.keys(taskForm).forEach(key => {
if (key === 'idType') {
taskForm[key] = '身份证'
} else if (key === 'taskStatus') {
taskForm[key] = '待处理'
} else if (key === 'priority') {
taskForm[key] = '中'
} else if (key === 'supervisorySuppliesQuantity') {
taskForm[key] = 0
} else {
taskForm[key] = key === 'id' ? null : ''
}
})
}
// 弹窗确定处理
const handleModalOk = async () => {
try {
await taskFormRef.value.validate()
const submitData = { ...taskForm }
// 处理适用生资数据
if (submitData.applicableSuppliesText) {
submitData.applicableSupplies = submitData.applicableSuppliesText.split('\n').filter(item => item.trim())
}
delete submitData.applicableSuppliesText
if (modalMode.value === 'create') {
delete submitData.id
const response = await supervisoryTaskApi.create(submitData)
if (response.code === 201) {
message.success('创建监管任务成功')
taskModalVisible.value = false
fetchTaskList()
}
} else if (modalMode.value === 'edit') {
const response = await supervisoryTaskApi.update(submitData.id, submitData)
if (response.code === 200) {
message.success('更新监管任务成功')
taskModalVisible.value = false
fetchTaskList()
}
}
} catch (error) {
console.error('操作失败:', error)
if (error.response?.data?.message) {
message.error(error.response.data.message)
} else {
message.error('操作失败')
}
}
}
// 弹窗取消处理
const handleModalCancel = () => {
taskModalVisible.value = false
resetTaskForm()
}
// 删除任务
const handleDelete = async (id) => {
try {
const response = await supervisoryTaskApi.delete(id)
if (response.code === 200) {
message.success('删除监管任务成功')
fetchTaskList()
}
} catch (error) {
console.error('删除失败:', error)
message.error('删除失败')
}
}
// 导出任务
const handleExport = async () => {
try {
const response = await supervisoryTaskApi.export()
if (response.code === 200) {
// 这里可以实现文件下载逻辑
message.success('导出成功')
}
} catch (error) {
console.error('导出失败:', error)
message.error('导出失败')
}
}
// 显示批量创建弹窗
const showBulkCreateModal = () => {
bulkCreateModalVisible.value = true
fileList.value = []
}
// 文件上传前处理
const beforeUpload = (file) => {
fileList.value = [file]
return false // 阻止自动上传
}
// 移除文件
const handleRemove = () => {
fileList.value = []
}
// 批量创建确定
const handleBulkCreateOk = async () => {
if (fileList.value.length === 0) {
message.warning('请选择要上传的文件')
return
}
try {
// 这里应该实现文件解析和批量创建逻辑
message.success('批量创建成功')
bulkCreateModalVisible.value = false
fetchTaskList()
} catch (error) {
console.error('批量创建失败:', error)
message.error('批量创建失败')
}
}
// 批量创建取消
const handleBulkCreateCancel = () => {
bulkCreateModalVisible.value = false
fileList.value = []
}
// 下载模板
const downloadTemplate = () => {
// 实现模板下载逻辑
message.info('模板下载功能开发中')
}
// 组件挂载时获取数据
onMounted(() => {
fetchTaskList()
})
</script>
<style scoped>
.supervisory-task-management {
padding: 24px;
background: #f5f5f5;
min-height: 100vh;
}
.page-header {
margin-bottom: 24px;
}
.page-title {
margin: 0;
font-size: 24px;
font-weight: 600;
color: #262626;
}
.action-bar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding: 16px;
background: #fff;
border-radius: 6px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.left-actions {
display: flex;
gap: 8px;
}
.right-actions {
display: flex;
gap: 8px;
align-items: center;
}
.table-container {
background: #fff;
border-radius: 6px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
overflow: hidden;
}
.bulk-create-content {
padding: 16px 0;
}
.template-download {
text-align: center;
padding-top: 16px;
border-top: 1px solid #f0f0f0;
}
/* 响应式设计 */
@media (max-width: 768px) {
.action-bar {
flex-direction: column;
gap: 16px;
}
.left-actions,
.right-actions {
width: 100%;
justify-content: center;
}
.right-actions {
flex-wrap: wrap;
}
}
</style>