添加银行后端接口,前端代码

This commit is contained in:
2025-09-23 17:57:18 +08:00
parent 325c114c38
commit bdc1b29934
67 changed files with 12682 additions and 7085 deletions

View File

@@ -6,13 +6,7 @@
<h2 v-if="!collapsed">政府管理系统</h2>
<h2 v-else>政府</h2>
</div>
<a-menu
v-model:selectedKeys="selectedKeys"
theme="dark"
mode="inline"
:items="menus"
@click="handleMenuClick"
/>
<Sidebar />
</a-layout-sider>
<!-- 主内容区 -->
@@ -70,288 +64,46 @@
</template>
<script setup>
import { ref, onMounted, h } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import {
MenuUnfoldOutlined,
MenuFoldOutlined,
UserOutlined,
DownOutlined,
LogoutOutlined,
DashboardOutlined,
UserAddOutlined,
EyeOutlined,
CheckCircleOutlined,
LineChartOutlined,
FileOutlined,
TeamOutlined,
SettingOutlined,
MedicineBoxOutlined,
ShoppingOutlined,
FolderOutlined,
BarChartOutlined,
PieChartOutlined,
ShoppingCartOutlined,
FileTextOutlined,
DatabaseOutlined,
HomeOutlined,
ShopOutlined,
MessageOutlined,
BookOutlined,
VideoCameraOutlined,
EnvironmentOutlined
LogoutOutlined
} from '@ant-design/icons-vue'
import { useUserStore } from '@/stores/user'
import Sidebar from '@/layout/Sidebar.vue'
const router = useRouter()
const route = useRoute()
const userStore = useUserStore()
const collapsed = ref(false)
const selectedKeys = ref([route.name])
const menus = ref([])
// 图标映射
const iconMap = {
DashboardOutlined: () => h(DashboardOutlined),
UserAddOutlined: () => h(UserAddOutlined),
EyeOutlined: () => h(EyeOutlined),
CheckCircleOutlined: () => h(CheckCircleOutlined),
LineChartOutlined: () => h(LineChartOutlined),
FileOutlined: () => h(FileOutlined),
TeamOutlined: () => h(TeamOutlined),
SettingOutlined: () => h(SettingOutlined),
MedicineBoxOutlined: () => h(MedicineBoxOutlined),
ShoppingOutlined: () => h(ShoppingOutlined),
FolderOutlined: () => h(FolderOutlined),
BarChartOutlined: () => h(BarChartOutlined),
PieChartOutlined: () => h(PieChartOutlined),
ShoppingCartOutlined: () => h(ShoppingCartOutlined),
FileTextOutlined: () => h(FileTextOutlined),
DatabaseOutlined: () => h(DatabaseOutlined),
HomeOutlined: () => h(HomeOutlined),
ShopOutlined: () => h(ShopOutlined),
MessageOutlined: () => h(MessageOutlined),
BookOutlined: () => h(BookOutlined),
VideoCameraOutlined: () => h(VideoCameraOutlined),
ShopOutlined: () => h(ShopOutlined),
EnvironmentOutlined: () => h(EnvironmentOutlined)
};
// 格式化菜单数据
const formatMenuItems = (menuList) => {
return menuList.map(menu => {
const menuItem = {
key: menu.key,
label: menu.label,
path: menu.path
};
// 添加图标
if (menu.icon && iconMap[menu.icon]) {
menuItem.icon = iconMap[menu.icon];
}
// 添加子菜单
if (menu.children && menu.children.length > 0) {
menuItem.children = formatMenuItems(menu.children);
}
return menuItem;
});
};
// 获取菜单数据
const fetchMenus = async () => {
try {
// 这里可以根据实际情况从API获取菜单数据
// 由于没有实际的API这里提供默认菜单作为备用
menus.value = [
{
key: 'DataCenter',
icon: 'DatabaseOutlined',
label: '数据览仓',
path: '/index/data_center'
},
{
key: 'MarketPrice',
icon: 'BarChartOutlined',
label: '市场行情',
path: '/price/price_list'
},
{
key: 'PersonnelManagement',
icon: 'TeamOutlined',
label: '人员管理',
path: '/personnel'
},
{
key: 'FarmerManagement',
icon: 'UserAddOutlined',
label: '养殖户管理',
path: '/farmer'
},
{
key: 'SmartWarehouse',
icon: 'FolderOutlined',
label: '智能仓库',
path: '/smart-warehouse'
},
{
key: 'BreedImprovement',
icon: 'SettingOutlined',
label: '品种改良管理',
path: '/breed-improvement'
},
{
key: 'PaperlessService',
icon: 'FileTextOutlined',
label: '无纸化服务',
path: '/paperless'
},
{
key: 'SlaughterHarmless',
icon: 'EnvironmentOutlined',
label: '屠宰无害化',
path: '/slaughter'
},
{
key: 'FinanceInsurance',
icon: 'ShoppingOutlined',
label: '金融保险',
path: '/finance'
},
{
key: 'ProductCertification',
icon: 'CheckCircleOutlined',
label: '生资认证',
path: '/examine/index'
},
{
key: 'ProductTrade',
icon: 'ShoppingCartOutlined',
label: '生资交易',
path: '/shengzijiaoyi'
},
{
key: 'CommunicationCommunity',
icon: 'MessageOutlined',
label: '交流社区',
path: '/community'
},
{
key: 'OnlineConsultation',
icon: 'EyeOutlined',
label: '线上问诊',
path: '/consultation'
},
{
key: 'CattleAcademy',
icon: 'BookOutlined',
label: '养牛学院',
path: '/academy'
},
{
key: 'MessageNotification',
icon: 'VideoCameraOutlined',
label: '消息通知',
path: '/notification'
},
{
key: 'UserManagement',
icon: 'UserAddOutlined',
label: '用户管理',
path: '/users'
},
{
key: 'WarehouseManagement',
icon: 'MedicineBoxOutlined',
label: '仓库管理',
path: '/warehouse'
},
{
key: 'FileManagement',
icon: 'FolderOutlined',
label: '文件管理',
path: '/files'
},
{
key: 'ServiceManagement',
icon: 'SettingOutlined',
label: '服务管理',
path: '/service'
},
{
key: 'ApprovalProcess',
icon: 'CheckCircleOutlined',
label: '审批流程',
path: '/approval'
},
{
key: 'EpidemicManagement',
icon: 'LineChartOutlined',
label: '防疫管理',
path: '/epidemic'
},
{
key: 'SupervisionDashboard',
icon: 'EyeOutlined',
label: '监管大屏',
path: '/supervision'
},
{
key: 'VisualAnalysis',
icon: 'PieChartOutlined',
label: '数据分析',
path: '/visualization'
},
{
key: 'LogManagement',
icon: 'FileOutlined',
label: '日志管理',
path: '/log'
}
];
// 应用图标映射
menus.value = formatMenuItems(menus.value);
} catch (error) {
console.error('获取菜单失败:', error);
}
};
// 菜单点击处理
const handleMenuClick = (e) => {
const menuItem = menus.value.find(item => item.key === e.key);
if (menuItem && menuItem.path) {
router.push(menuItem.path);
}
};
// 退出登录处理
// 退出登录
const handleLogout = () => {
userStore.logout();
router.push('/login');
};
// 组件挂载时获取菜单
onMounted(() => {
fetchMenus();
});
userStore.logout()
router.push('/login')
}
</script>
<style scoped>
.logo {
height: 32px;
margin: 16px;
color: white;
height: 64px;
line-height: 64px;
background: #002140;
text-align: center;
overflow: hidden;
}
.logo h2 {
color: white;
margin: 0;
font-size: 18px;
}
.trigger {
font-size: 18px;
line-height: 64px;
padding: 0 24px;
cursor: pointer;
transition: color 0.3s;
}
@@ -359,4 +111,11 @@ onMounted(() => {
.trigger:hover {
color: #1890ff;
}
.ant-dropdown-link {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
}
</style>

View File

@@ -4,198 +4,148 @@
v-model:openKeys="openKeys"
mode="inline"
theme="dark"
:root-sub-menu-open-close="false"
@select="handleMenuSelect"
@openChange="handleOpenChange"
class="sidebar-menu"
>
<!-- 首页 -->
<a-menu-item key="/" :icon="DashboardOutlined">
<span>首页</span>
<!-- 数据览仓 -->
<a-menu-item key="/index/data_center">
<template #icon><BarChartOutlined /></template>
<span>数据览仓</span>
</a-menu-item>
<!-- 监管实体管理 -->
<a-sub-menu
v-if="hasPermission('supervision')"
key="supervision"
:icon="FileOutlined"
>
<template #title>
<span>监管实体管理</span>
</template>
<a-menu-item key="/supervision/list">
<span>实体列表</span>
</a-menu-item>
<a-menu-item key="/supervision/add">
<span>新增实体</span>
</a-menu-item>
<a-menu-item key="/supervision/stats">
<span>统计分析</span>
</a-menu-item>
</a-sub-menu>
<!-- 检查记录 -->
<a-sub-menu
v-if="hasPermission('inspection')"
key="inspection"
:icon="CheckCircleOutlined"
>
<template #title>
<span>检查记录</span>
</template>
<a-menu-item key="/inspection/list">
<span>记录列表</span>
</a-menu-item>
<a-menu-item key="/inspection/add">
<span>新增记录</span>
</a-menu-item>
<a-menu-item key="/inspection/stats">
<span>统计分析</span>
</a-menu-item>
</a-sub-menu>
<!-- 违规处理 -->
<a-sub-menu
v-if="hasPermission('violation')"
key="violation"
:icon="ExclamationCircleOutlined"
>
<template #title>
<span>违规处理</span>
</template>
<a-menu-item key="/violation/list">
<span>违规记录</span>
</a-menu-item>
<a-menu-item key="/violation/add">
<span>新增违规</span>
</a-menu-item>
<a-menu-item key="/violation/process">
<span>处理流程</span>
</a-menu-item>
</a-sub-menu>
<!-- 防疫管理 -->
<a-sub-menu
v-if="hasPermission('epidemic')"
key="epidemic"
:icon="AlertOutlined"
>
<template #title>
<span>防疫管理</span>
</template>
<a-menu-item key="/epidemic/list">
<span>防疫记录</span>
</a-menu-item>
<a-menu-item key="/epidemic/add">
<span>新增记录</span>
</a-menu-item>
<a-menu-item key="/epidemic/stats">
<span>疫情统计</span>
</a-menu-item>
</a-sub-menu>
<!-- 审批管理 -->
<a-sub-menu
v-if="hasPermission('approval')"
key="approval"
:icon="FormOutlined"
>
<template #title>
<span>审批管理</span>
</template>
<a-menu-item key="/approval/list">
<span>审批列表</span>
</a-menu-item>
<a-menu-item key="/approval/pending">
<span>待我审批</span>
</a-menu-item>
<a-menu-item key="/approval/history">
<span>审批历史</span>
</a-menu-item>
</a-sub-menu>
<!-- 市场行情 -->
<a-menu-item key="/price/price_list">
<template #icon><LineChartOutlined /></template>
<span>市场行情</span>
</a-menu-item>
<!-- 人员管理 -->
<a-sub-menu
v-if="hasPermission('personnel')"
key="personnel"
:icon="UserOutlined"
>
<a-sub-menu key="personnel">
<template #icon><UserOutlined /></template>
<template #title>
<span>人员管理</span>
</template>
<a-menu-item key="/personnel/list">
<span>人员列表</span>
<a-menu-item key="/personnel/department">
<span>行政部门</span>
</a-menu-item>
<a-menu-item key="/personnel/add">
<span>新增人员</span>
</a-menu-item>
<a-menu-item key="/personnel/role">
<span>角色管理</span>
<a-menu-item key="/personnel/staff">
<span>行政人员</span>
</a-menu-item>
</a-sub-menu>
<!-- 系统设置 -->
<a-sub-menu
v-if="hasPermission('system')"
key="system"
:icon="SettingOutlined"
>
<!-- 养殖户管理 -->
<a-menu-item key="/farmer">
<template #icon><TeamOutlined /></template>
<span>养殖户管理</span>
</a-menu-item>
<!-- 智慧仓库 -->
<a-sub-menu key="smart-warehouse">
<template #icon><HddOutlined /></template>
<template #title>
<span>系统设置</span>
<span>智慧仓库</span>
</template>
<a-menu-item key="/system/config">
<span>系统配置</span>
<a-menu-item key="/smart-warehouse/smart-collar">
<span>智能项圈</span>
</a-menu-item>
<a-menu-item key="/system/logs">
<span>系统日志</span>
<a-menu-item key="/smart-warehouse/smart-earmark">
<span>智能耳标</span>
</a-menu-item>
<a-menu-item key="/system/backup">
<span>数据备份</span>
<a-menu-item key="/smart-warehouse/smart-host">
<span>智能主机</span>
</a-menu-item>
</a-sub-menu>
<!-- 帮助中心 -->
<a-menu-item key="/help" :icon="QuestionCircleOutlined">
<span>帮助中心</span>
<!-- 无纸化服务 -->
<a-menu-item key="/paperless">
<template #icon><FileTextOutlined /></template>
<span>无纸化服务</span>
</a-menu-item>
<!-- 屠宰无害化 -->
<a-menu-item key="/slaughter">
<template #icon><SafetyOutlined /></template>
<span>屠宰无害化</span>
</a-menu-item>
<!-- 生资认证 -->
<a-menu-item key="/examine/index">
<template #icon><CheckCircleOutlined /></template>
<span>生资认证</span>
</a-menu-item>
<!-- 线上问诊 -->
<a-menu-item key="/consultation">
<template #icon><MedicineBoxOutlined /></template>
<span>线上问诊</span>
</a-menu-item>
<!-- 养牛学院 -->
<a-menu-item key="/academy">
<template #icon><ReadOutlined /></template>
<span>养牛学院</span>
</a-menu-item>
<!-- 消息通知 -->
<a-menu-item key="/notification">
<template #icon><BellOutlined /></template>
<span>消息通知</span>
</a-menu-item>
</a-menu>
</template>
<script lang="js">
import { ref, onMounted, watch } from 'vue'
import { ref, onMounted, watch, h } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
import {
DashboardOutlined,
FileOutlined,
CheckCircleOutlined,
ExclamationCircleOutlined,
AlertOutlined,
FormOutlined,
BarChartOutlined,
LineChartOutlined,
UserOutlined,
SettingOutlined,
QuestionCircleOutlined
TeamOutlined,
HddOutlined,
ExperimentOutlined,
FileTextOutlined,
SafetyOutlined,
BankOutlined,
CheckCircleOutlined,
ShoppingOutlined,
CommentOutlined,
MedicineBoxOutlined,
ReadOutlined,
BellOutlined
} from '@ant-design/icons-vue'
const router = useRouter()
const route = useRoute()
const authStore = useAuthStore()
export default {
setup() {
const router = useRouter()
const route = useRoute()
const authStore = useAuthStore()
// 当前选中的菜单项
const selectedKeys = ref([])
// 当前选中的菜单项
const selectedKeys = ref([])
// 展开的菜单项
const openKeys = ref([])
// 展开的菜单项
const openKeys = ref([])
// 检查是否有权限
const hasPermission = (permission) => {
return authStore.hasPermission([permission])
}
// 检查是否有权限
const hasPermission = (permission) => {
return authStore.hasPermission([permission])
}
// 处理菜单选择
const handleMenuSelect = ({ key }) => {
router.push(key)
}
// 处理菜单选择
const handleMenuSelect = ({ key }) => {
// 确保路由存在
if (key) {
console.log('导航到路由:', key)
// 使用replace而不是push避免历史记录堆积
router.replace(key).catch(err => {
console.error('路由导航失败:', err)
})
}
}
// 处理展开/收起
const handleOpenChange = (keys) => {
@@ -211,7 +161,8 @@ const getParentMenuKey = (path) => {
'/epidemic': 'epidemic',
'/approval': 'approval',
'/personnel': 'personnel',
'/system': 'system'
'/system': 'system',
'/smart-warehouse': 'smart-warehouse'
}
for (const [prefix, key] of Object.entries(menuMap)) {
@@ -219,6 +170,12 @@ const getParentMenuKey = (path) => {
return key
}
}
// 特殊处理智慧仓库路径
if (path.includes('smart-warehouse')) {
return 'smart-warehouse'
}
return ''
}
@@ -243,10 +200,37 @@ watch(
}
)
// 返回需要在模板中使用的内容
return {
hasPermission,
handleMenuSelect,
handleOpenChange,
selectedKeys,
openKeys,
DashboardOutlined: () => h(DashboardOutlined),
BarChartOutlined: () => h(BarChartOutlined),
LineChartOutlined: () => h(LineChartOutlined),
UserOutlined: () => h(UserOutlined),
TeamOutlined: () => h(TeamOutlined),
HddOutlined: () => h(HddOutlined),
ExperimentOutlined: () => h(ExperimentOutlined),
FileTextOutlined: () => h(FileTextOutlined),
SafetyOutlined: () => h(SafetyOutlined),
BankOutlined: () => h(BankOutlined),
CheckCircleOutlined: () => h(CheckCircleOutlined),
ShoppingOutlined: () => h(ShoppingOutlined),
CommentOutlined: () => h(CommentOutlined),
MedicineBoxOutlined: () => h(MedicineBoxOutlined),
ReadOutlined: () => h(ReadOutlined),
BellOutlined: () => h(BellOutlined)
}
// 组件挂载时初始化
onMounted(() => {
updateSelectedState()
})
}
}
</script>
<style scoped>

View File

@@ -1,8 +1,9 @@
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './stores'
import { createPinia } from 'pinia'
import Antd from 'ant-design-vue'
import { ConfigProvider } from 'ant-design-vue'
import 'ant-design-vue/dist/reset.css'
import dayjs from 'dayjs'
import './mock' // 导入mock服务
@@ -21,9 +22,25 @@ globalThis.dayjs = dayjs
// 创建应用实例
const app = createApp(App)
// 创建 Pinia 实例
const pinia = createPinia()
app.use(router)
app.use(store)
app.use(pinia)
app.use(Antd)
// 配置 Ant Design Vue
app.use(ConfigProvider, {
locale: zhCN,
// 明确配置日期库为dayjs
dateFormatter: 'dayjs',
// 提供完整配置的dayjs实例确保在组件中能正确访问到配置好的dayjs
getDayjsInstance: () => {
// 确保返回一个已经正确配置了语言和插件的dayjs实例
return dayjs
},
// 安全地获取弹出层容器防止trigger为null导致的错误
getPopupContainer: (trigger) => trigger?.parentElement || document.body
})
app.mount('#app')

View File

@@ -2,16 +2,16 @@ 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 SupervisionDashboard from '@/views/SupervisionDashboard.vue'
// import UserManagement from '@/views/UserManagement.vue'
// import SupervisionDashboard from '@/views/SupervisionDashboard.vue'
import ApprovalProcess from '@/views/ApprovalProcess.vue'
import EpidemicManagement from '@/views/EpidemicManagement.vue'
import VisualAnalysis from '@/views/VisualAnalysis.vue'
import FileManagement from '@/views/FileManagement.vue'
// import EpidemicManagement from '@/views/EpidemicManagement.vue'
// import VisualAnalysis from '@/views/VisualAnalysis.vue'
// import FileManagement from '@/views/FileManagement.vue'
import PersonnelManagement from '@/views/PersonnelManagement.vue'
import ServiceManagement from '@/views/ServiceManagement.vue'
import WarehouseManagement from '@/views/WarehouseManagement.vue'
import LogManagement from '@/views/LogManagement.vue'
// import WarehouseManagement from '@/views/WarehouseManagement.vue'
// import LogManagement from '@/views/LogManagement.vue'
// 新增页面组件
import MarketPrice from '@/views/MarketPrice.vue'
import DataCenter from '@/views/DataCenter.vue'
@@ -25,6 +25,8 @@ import CommunicationCommunity from '@/views/CommunicationCommunity.vue'
import OnlineConsultation from '@/views/OnlineConsultation.vue'
import CattleAcademy from '@/views/CattleAcademy.vue'
import MessageNotification from '@/views/MessageNotification.vue'
import AdminDepartment from '@/views/AdminDepartment.vue'
import AdminStaff from '@/views/AdminStaff.vue'
const routes = [
{
@@ -59,7 +61,21 @@ const routes = [
path: 'personnel',
name: 'PersonnelManagement',
component: PersonnelManagement,
meta: { title: '人员管理' }
meta: { title: '人员管理' },
children: [
{
path: 'department',
name: 'AdminDepartment',
component: AdminDepartment,
meta: { title: '行政部门' }
},
{
path: 'staff',
name: 'AdminStaff',
component: AdminStaff,
meta: { title: '行政人员' }
}
]
},
{
path: 'farmer',
@@ -71,7 +87,27 @@ const routes = [
path: 'smart-warehouse',
name: 'SmartWarehouse',
component: SmartWarehouse,
meta: { title: '智仓库' }
meta: { title: '智仓库' },
children: [
{
path: 'smart-collar',
name: 'SmartCollar',
component: () => import('@/views/smart-warehouse/smart-hardware/SmartCollar.vue'),
meta: { title: '智能项圈' }
},
{
path: 'smart-earmark',
name: 'SmartEarmark',
component: () => import('@/views/smart-warehouse/smart-hardware/SmartEarmark.vue'),
meta: { title: '智能耳标' }
},
{
path: 'smart-host',
name: 'SmartHost',
component: () => import('@/views/smart-warehouse/smart-hardware/SmartHost.vue'),
meta: { title: '智能主机' }
}
]
},
{
path: 'breed-improvement',
@@ -133,60 +169,60 @@ const routes = [
component: MessageNotification,
meta: { title: '消息通知' }
},
{
path: 'users',
name: 'UserManagement',
component: UserManagement,
meta: { title: '用户管理' }
},
{
path: 'supervision',
name: 'SupervisionDashboard',
component: SupervisionDashboard,
meta: { title: '监管仪表板' }
},
{
path: 'approval',
name: 'ApprovalProcess',
component: ApprovalProcess,
meta: { title: '审批流程' }
},
{
path: 'file',
name: 'FileManagement',
component: FileManagement,
meta: { title: '文件管理' }
},
{
path: 'service',
name: 'ServiceManagement',
component: ServiceManagement,
meta: { title: '服务管理' }
},
{
path: 'warehouse',
name: 'WarehouseManagement',
component: WarehouseManagement,
meta: { title: '仓库管理' }
},
{
path: 'log',
name: 'LogManagement',
component: LogManagement,
meta: { title: '日志管理' }
},
{
path: 'epidemic',
name: 'EpidemicManagement',
component: EpidemicManagement,
meta: { title: '疫情管理' }
},
{
path: 'visualization',
name: 'VisualAnalysis',
component: VisualAnalysis,
meta: { title: '可视化分析' }
}
// {
// path: 'users',
// name: 'UserManagement',
// component: UserManagement,
// meta: { title: '用户管理' }
// },
// {
// path: 'supervision',
// name: 'SupervisionDashboard',
// component: SupervisionDashboard,
// meta: { title: '监管仪表板' }
// },
// {
// path: 'approval',
// name: 'ApprovalProcess',
// component: ApprovalProcess,
// meta: { title: '审批流程' }
// },
// {
// path: 'file',
// name: 'FileManagement',
// component: FileManagement,
// meta: { title: '文件管理' }
// },
// {
// path: 'service',
// name: 'ServiceManagement',
// component: ServiceManagement,
// meta: { title: '服务管理' }
// },
// {
// path: 'warehouse',
// name: 'WarehouseManagement',
// component: WarehouseManagement,
// meta: { title: '仓库管理' }
// },
// {
// path: 'log',
// name: 'LogManagement',
// component: LogManagement,
// meta: { title: '日志管理' }
// },
// {
// path: 'epidemic',
// name: 'EpidemicManagement',
// component: EpidemicManagement,
// meta: { title: '疫情管理' }
// },
// {
// path: 'visualization',
// name: 'VisualAnalysis',
// component: VisualAnalysis,
// meta: { title: '可视化分析' }
// }
]
}
]

View File

@@ -0,0 +1,244 @@
<template>
<div class="admin-department-container">
<div class="header">
<div class="title">部门防疫部门</div>
<div class="actions">
<a-button type="primary" class="action-btn">删除部门</a-button>
<a-button type="primary" class="action-btn">新增岗位</a-button>
</div>
</div>
<a-table :columns="columns" :data-source="departmentData" :pagination="false" class="department-table">
<template #headerCell="{ column }">
<span>{{ column.title }}</span>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'permission'">
<a-tag v-if="record.permission === '已设置权限'" color="success">{{ record.permission }}</a-tag>
<a-tag v-else color="warning">{{ record.permission }}</a-tag>
</template>
<template v-if="column.key === 'action'">
<a-space>
<a-button type="link" @click="editPosition(record)">编辑岗位名称</a-button>
<a-button type="link" @click="deletePosition(record)">删除岗位</a-button>
<a-button type="link" @click="setPermission(record)">设置权限</a-button>
</a-space>
</template>
</template>
</a-table>
<div class="header">
<div class="title">部门检疫部门</div>
<div class="actions">
<a-button type="primary" class="action-btn">删除部门</a-button>
<a-button type="primary" class="action-btn">新增岗位</a-button>
</div>
</div>
<a-table :columns="columns" :data-source="inspectionData" :pagination="false" class="department-table">
<template #headerCell="{ column }">
<span>{{ column.title }}</span>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'permission'">
<a-tag v-if="record.permission === '已设置权限'" color="success">{{ record.permission }}</a-tag>
<a-tag v-else color="warning">{{ record.permission }}</a-tag>
</template>
<template v-if="column.key === 'action'">
<a-space>
<a-button type="link" @click="editPosition(record)">编辑岗位名称</a-button>
<a-button type="link" @click="deletePosition(record)">删除岗位</a-button>
<a-button type="link" @click="setPermission(record)">设置权限</a-button>
</a-space>
</template>
</template>
</a-table>
<div class="header">
<div class="title">部门冷配管理部门</div>
<div class="actions">
<a-button type="primary" class="action-btn">删除部门</a-button>
<a-button type="primary" class="action-btn">新增岗位</a-button>
</div>
</div>
<a-table :columns="columns" :data-source="coldChainData" :pagination="false" class="department-table">
<template #headerCell="{ column }">
<span>{{ column.title }}</span>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'permission'">
<a-tag v-if="record.permission === '已设置权限'" color="success">{{ record.permission }}</a-tag>
<a-tag v-else color="warning">{{ record.permission }}</a-tag>
</template>
<template v-if="column.key === 'action'">
<a-space>
<a-button type="link" @click="editPosition(record)">编辑岗位名称</a-button>
<a-button type="link" @click="deletePosition(record)">删除岗位</a-button>
<a-button type="link" @click="setPermission(record)">设置权限</a-button>
</a-space>
</template>
</template>
</a-table>
<div class="header">
<div class="title">部门测试部门</div>
<div class="actions">
<a-button type="primary" class="action-btn">删除部门</a-button>
<a-button type="primary" class="action-btn">新增岗位</a-button>
</div>
</div>
<a-table :columns="columns" :data-source="testingData" :pagination="false" class="department-table">
<template #headerCell="{ column }">
<span>{{ column.title }}</span>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'permission'">
<a-tag v-if="record.permission === '已设置权限'" color="success">{{ record.permission }}</a-tag>
<a-tag v-else color="warning">{{ record.permission }}</a-tag>
</template>
<template v-if="column.key === 'action'">
<a-space>
<a-button type="link" @click="editPosition(record)">编辑岗位名称</a-button>
<a-button type="link" @click="deletePosition(record)">删除岗位</a-button>
<a-button type="link" @click="setPermission(record)">设置权限</a-button>
</a-space>
</template>
</template>
</a-table>
<div class="add-department">
<a-button type="primary" @click="showAddDepartmentModal">新增部门</a-button>
</div>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { message } from 'ant-design-vue'
// 表格列定义
const columns = [
{
title: '岗位',
dataIndex: 'position',
key: 'position',
},
{
title: '系统基础权限',
dataIndex: 'permission',
key: 'permission',
},
{
title: '操作',
key: 'action',
}
]
// 防疫部门数据
const departmentData = ref([
{
key: '1',
position: '防疫员',
permission: '已设置权限',
},
{
key: '2',
position: '防疫管理员',
permission: '已设置权限',
}
])
// 检疫部门数据
const inspectionData = ref([
{
key: '1',
position: '检疫员',
permission: '未设置权限',
}
])
// 冷配管理部门数据
const coldChainData = ref([
{
key: '1',
position: '冷配员',
permission: '已设置权限',
}
])
// 测试部门数据
const testingData = ref([
{
key: '1',
position: '内部测试账号',
permission: '已设置权限',
}
])
// 编辑岗位
const editPosition = (record) => {
message.info(`编辑岗位:${record.position}`)
}
// 删除岗位
const deletePosition = (record) => {
message.info(`删除岗位:${record.position}`)
}
// 设置权限
const setPermission = (record) => {
message.info(`设置权限:${record.position}`)
}
// 显示新增部门模态框
const showAddDepartmentModal = () => {
message.info('显示新增部门对话框')
}
</script>
<style scoped>
.admin-department-container {
padding: 20px;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.title {
font-size: 16px;
font-weight: bold;
}
.actions {
display: flex;
gap: 10px;
}
.action-btn {
margin-left: 8px;
}
.department-table {
margin-bottom: 24px;
}
.add-department {
margin-top: 24px;
display: flex;
justify-content: center;
}
</style>

View File

@@ -0,0 +1,265 @@
<template>
<div class="admin-staff-container">
<div class="header">
<a-button type="primary">新增人员</a-button>
<a-input-search
v-model:value="searchValue"
placeholder="请输入员工姓名"
style="width: 200px"
@search="onSearch"
/>
</div>
<a-table :columns="columns" :data-source="staffData" :pagination="pagination" class="staff-table">
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'status'">
<a-switch v-model:checked="record.status" />
</template>
<template v-if="column.key === 'action'">
<a-space>
<a-button type="link" @click="editStaff(record)">编辑</a-button>
<a-button type="link" @click="resetPassword(record)">修改密码</a-button>
</a-space>
</template>
</template>
</a-table>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { message } from 'ant-design-vue'
// 搜索值
const searchValue = ref('')
// 表格列定义
const columns = [
{
title: '员工姓名',
dataIndex: 'name',
key: 'name',
},
{
title: '所属部门',
dataIndex: 'department',
key: 'department',
},
{
title: '任职岗位',
dataIndex: 'position',
key: 'position',
},
{
title: '联系电话',
dataIndex: 'phone',
key: 'phone',
},
{
title: '身份证号码',
dataIndex: 'idCard',
key: 'idCard',
},
{
title: '账号使用状态',
dataIndex: 'status',
key: 'status',
},
{
title: '创建时间',
dataIndex: 'createdAt',
key: 'createdAt',
sorter: (a, b) => new Date(a.createdAt) - new Date(b.createdAt),
},
{
title: '操作',
key: 'action',
}
]
// 员工数据
const staffData = ref([
{
key: '1',
name: '内部测试账号',
department: '测试部门',
position: '内部测试账号',
phone: '187****4778',
idCard: '--',
status: true,
createdAt: '2023-11-24 17:24:32'
},
{
key: '2',
name: '扎拉嘎',
department: '冷配管理部门',
position: '冷配员',
phone: '195****9912',
idCard: '--',
status: true,
createdAt: '2023-10-10 13:59:03'
},
{
key: '3',
name: '董立鹏',
department: '防疫部',
position: '防疫管理员',
phone: '151****7022',
idCard: '--',
status: true,
createdAt: '2023-05-19 15:47:54'
},
{
key: '4',
name: '李海涛',
department: '防疫部',
position: '防疫管理员',
phone: '139****6955',
idCard: '--',
status: true,
createdAt: '2023-05-09 14:29:15'
},
{
key: '5',
name: '高凤鸣',
department: '防疫部',
position: '防疫管理员',
phone: '158****4601',
idCard: '--',
status: true,
createdAt: '2023-05-09 14:28:57'
},
{
key: '6',
name: '刘全',
department: '防疫部',
position: '防疫管理员',
phone: '139****6009',
idCard: '--',
status: true,
createdAt: '2023-05-09 14:28:39'
},
{
key: '7',
name: '李志伟',
department: '防疫部',
position: '防疫管理员',
phone: '153****0457',
idCard: '--',
status: true,
createdAt: '2023-05-09 14:28:14'
},
{
key: '8',
name: '李景学',
department: '防疫部',
position: '防疫管理员',
phone: '153****5588',
idCard: '--',
status: true,
createdAt: '2023-05-09 14:27:57'
},
{
key: '9',
name: '王海龙',
department: '防疫部',
position: '防疫管理员',
phone: '150****7222',
idCard: '--',
status: true,
createdAt: '2023-05-09 14:27:39'
},
{
key: '10',
name: '德力根',
department: '防疫部',
position: '防疫管理员',
phone: '150****2938',
idCard: '--',
status: true,
createdAt: '2023-05-09 14:27:22'
},
{
key: '11',
name: '巴日斯',
department: '防疫部',
position: '防疫管理员',
phone: '131****1366',
idCard: '--',
status: true,
createdAt: '2023-05-09 14:27:03'
},
{
key: '12',
name: '邵志勇',
department: '防疫部',
position: '防疫管理员',
phone: '139****0778',
idCard: '--',
status: true,
createdAt: '2023-05-09 14:26:26'
},
{
key: '13',
name: '张日林',
department: '防疫部',
position: '防疫管理员',
phone: '156****6300',
idCard: '--',
status: true,
createdAt: '2023-05-09 14:25:37'
},
{
key: '14',
name: '李燕',
department: '防疫部',
position: '防疫管理员',
phone: '151****1613',
idCard: '--',
status: true,
createdAt: '2023-05-09 14:25:12'
}
])
// 分页配置
const pagination = reactive({
current: 1,
pageSize: 10,
total: staffData.value.length,
showSizeChanger: true,
showTotal: (total) => `${total} 条记录`
})
// 搜索处理
const onSearch = (value) => {
message.info(`搜索: ${value}`)
}
// 编辑员工
const editStaff = (record) => {
message.info(`编辑员工: ${record.name}`)
}
// 重置密码
const resetPassword = (record) => {
message.info(`重置密码: ${record.name}`)
}
</script>
<style scoped>
.admin-staff-container {
padding: 20px;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.staff-table {
margin-top: 16px;
}
</style>

View File

@@ -1,152 +1,158 @@
<template>
<div>
<h1>数据览仓</h1>
<!-- 数据概览卡片 -->
<a-row gutter={24} style="margin-bottom: 16px;">
<a-col :span="6">
<a-card hoverable>
<div class="stat-card">
<div class="stat-icon stat-icon-primary">
<span class="iconfont icon-renyuanguanli" style="font-size: 24px;"></span>
</div>
<div class="stat-content">
<div class="data-center-container">
<!-- 顶部统计卡片 -->
<div class="stat-cards">
<a-row :gutter="16">
<a-col :span="6">
<a-card class="stat-card">
<div class="stat-header">
<div class="stat-title">养殖户总数</div>
<div class="stat-value">{{养殖户总数}}</div>
<div class="stat-change">
<span class="text-success">+5.2%</span> 较上月
<div class="stat-icon">
<span class="iconfont icon-renyuanguanli"></span>
</div>
</div>
</div>
</a-card>
</a-col>
<a-col :span="6">
<a-card hoverable>
<div class="stat-card">
<div class="stat-icon stat-icon-success">
<span class="iconfont icon-niu" style="font-size: 24px;"></span>
<div class="stat-body">
<div class="stat-value">{{ 养殖户总数 }}</div>
<div class="stat-trend">
<span class="trend-value">+5.2%</span>
<span class="trend-label">较上月</span>
</div>
</div>
<div class="stat-content">
</a-card>
</a-col>
<a-col :span="6">
<a-card class="stat-card">
<div class="stat-header">
<div class="stat-title">存栏总量</div>
<div class="stat-value">{{存栏总量}}</div>
<div class="stat-change">
<span class="text-success">+2.8%</span> 较上月
<div class="stat-icon">
<span class="iconfont icon-niu"></span>
</div>
</div>
</div>
</a-card>
</a-col>
<a-col :span="6">
<a-card hoverable>
<div class="stat-card">
<div class="stat-icon stat-icon-warning">
<span class="iconfont icon-shengzijiaoyi" style="font-size: 24px;"></span>
<div class="stat-body">
<div class="stat-value">{{ 存栏总量 }}</div>
<div class="stat-trend">
<span class="trend-value">+2.8%</span>
<span class="trend-label">较上月</span>
</div>
</div>
<div class="stat-content">
</a-card>
</a-col>
<a-col :span="6">
<a-card class="stat-card">
<div class="stat-header">
<div class="stat-title">交易量</div>
<div class="stat-value">{{交易量}}</div>
<div class="stat-change">
<span class="text-danger">-1.5%</span> 较上月
<div class="stat-icon">
<span class="iconfont icon-shengzijiaoyi"></span>
</div>
</div>
</div>
</a-card>
</a-col>
<a-col :span="6">
<a-card hoverable>
<div class="stat-card">
<div class="stat-icon stat-icon-danger">
<span class="iconfont icon-yonghu" style="font-size: 24px;"></span>
<div class="stat-body">
<div class="stat-value">{{ 交易量 }}</div>
<div class="stat-trend negative">
<span class="trend-value">-1.5%</span>
<span class="trend-label">较上月</span>
</div>
</div>
<div class="stat-content">
</a-card>
</a-col>
<a-col :span="6">
<a-card class="stat-card">
<div class="stat-header">
<div class="stat-title">活跃用户</div>
<div class="stat-value">{{活跃用户}}</div>
<div class="stat-change">
<span class="text-success">+8.3%</span> 较上月
<div class="stat-icon">
<span class="iconfont icon-yonghu"></span>
</div>
</div>
</div>
</a-card>
</a-col>
</a-row>
<div class="stat-body">
<div class="stat-value">{{ 活跃用户 }}</div>
<div class="stat-trend">
<span class="trend-value">+8.3%</span>
<span class="trend-label">较上月</span>
</div>
</div>
</a-card>
</a-col>
</a-row>
</div>
<!-- 主要图表区域 -->
<a-row gutter={24} style="margin-bottom: 16px;">
<a-col :span="12">
<a-card title="养殖区域分布" style="height: 400px;">
<div style="height: 340px;" ref="mapChartRef"></div>
</a-card>
</a-col>
<a-col :span="12">
<a-card title="养殖规模分布" style="height: 400px;">
<div style="height: 340px;" ref="scaleChartRef"></div>
</a-card>
</a-col>
</a-row>
<div class="chart-section">
<a-row :gutter="16">
<a-col :span="16">
<a-card class="chart-card" title="月度交易量趋势">
<div ref="transactionChartRef" class="chart-container"></div>
</a-card>
</a-col>
<a-col :span="8">
<a-card class="chart-card" title="品类占比分析">
<div ref="categoryChartRef" class="chart-container"></div>
</a-card>
</a-col>
</a-row>
</div>
<!-- 第二行图表 -->
<a-row gutter={24} style="margin-bottom: 16px;">
<a-col :span="12">
<a-card title="月度交易量趋势" style="height: 350px;">
<div style="height: 290px;" ref="transactionChartRef"></div>
</a-card>
</a-col>
<a-col :span="12">
<a-card title="品类占比分析" style="height: 350px;">
<div style="height: 290px;" ref="categoryChartRef"></div>
</a-card>
</a-col>
</a-row>
<div class="chart-section">
<a-row :gutter="16">
<a-col :span="12">
<a-card class="chart-card" title="养殖区域分布">
<div ref="mapChartRef" class="chart-container"></div>
</a-card>
</a-col>
<a-col :span="12">
<a-card class="chart-card" title="养殖规模分布">
<div ref="scaleChartRef" class="chart-container"></div>
</a-card>
</a-col>
</a-row>
</div>
<!-- 最近数据更新信息 -->
<a-card title="数据更新信息">
<a-timeline mode="alternate">
<a-timeline-item color="blue">
<div class="timeline-content">
<div class="timeline-title">市场行情数据更新</div>
<div class="timeline-time">2024-04-10 15:30:00</div>
<div class="timeline-desc">更新全国主要地区牛肉牛奶饲料价格数据</div>
</div>
</a-timeline-item>
<a-timeline-item color="green">
<div class="timeline-content">
<div class="timeline-title">养殖户数据同步</div>
<div class="timeline-time">2024-04-10 10:15:00</div>
<div class="timeline-desc">新增56家养殖户信息更新32家养殖户状态</div>
</div>
</a-timeline-item>
<a-timeline-item color="orange">
<div class="timeline-content">
<div class="timeline-title">产品认证数据导入</div>
<div class="timeline-time">2024-04-09 16:45:00</div>
<div class="timeline-desc">导入120条生资产品认证信息</div>
</div>
</a-timeline-item>
<a-timeline-item color="red">
<div class="timeline-content">
<div class="timeline-title">疫病监测数据更新</div>
<div class="timeline-time">2024-04-09 09:20:00</div>
<div class="timeline-desc">更新本周疫病监测数据暂无异常情况</div>
</div>
</a-timeline-item>
</a-timeline>
</a-card>
<!-- 数据导出功能 -->
<a-card title="数据导出" style="margin-top: 16px;">
<div style="display: flex; justify-content: space-between; align-items: center; padding: 16px 0;">
<div>
<a-select v-model:value="exportDataType" style="width: 200px; margin-right: 16px;">
<a-select-option value="market_price">市场行情数据</a-select-option>
<a-select-option value="farmer_info">养殖户信息</a-select-option>
<a-select-option value="transaction_data">交易数据</a-select-option>
<a-select-option value="epidemic_data">疫病监测数据</a-select-option>
</a-select>
<a-input v-model:value="exportDateRange" style="width: 250px;" placeholder="选择日期范围 (YYYY-MM-DD ~ YYYY-MM-DD)"></a-input>
</div>
<a-button type="primary" danger @click="handleExportData">导出数据</a-button>
</div>
</a-card>
<!-- 数据更新信息 -->
<div class="update-section">
<a-card class="update-card" title="数据更新信息">
<a-list>
<a-list-item>
<div class="update-item">
<div class="update-icon blue"></div>
<div class="update-content">
<div class="update-title">市场行情数据更新</div>
<div class="update-time">2024-04-10 15:30:00</div>
<div class="update-desc">更新全国主要地区牛肉牛奶饲料价格数据</div>
</div>
</div>
</a-list-item>
<a-list-item>
<div class="update-item">
<div class="update-icon green"></div>
<div class="update-content">
<div class="update-title">养殖户数据同步</div>
<div class="update-time">2024-04-10 10:15:00</div>
<div class="update-desc">新增56家养殖户信息更新32家养殖户状态</div>
</div>
</div>
</a-list-item>
<a-list-item>
<div class="update-item">
<div class="update-icon orange"></div>
<div class="update-content">
<div class="update-title">产品认证数据导入</div>
<div class="update-time">2024-04-09 16:45:00</div>
<div class="update-desc">导入120条生资产品认证信息</div>
</div>
</div>
</a-list-item>
<a-list-item>
<div class="update-item">
<div class="update-icon red"></div>
<div class="update-content">
<div class="update-title">疫病监测数据更新</div>
<div class="update-time">2024-04-09 09:20:00</div>
<div class="update-desc">更新本周疫病监测数据暂无异常情况</div>
</div>
</div>
</a-list-item>
</a-list>
</a-card>
</div>
</div>
</template>
@@ -166,9 +172,122 @@ const scaleChartRef = ref(null)
const transactionChartRef = ref(null)
const categoryChartRef = ref(null)
// 导出数据类型
const exportDataType = ref('market_price')
const exportDateRange = ref('2024-03-10 ~ 2024-04-10')
// 初始化交易量趋势图表
const initTransactionChart = () => {
if (!transactionChartRef.value) return
const chart = echarts.init(transactionChartRef.value)
const option = {
tooltip: {
trigger: 'axis'
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['1月', '2月', '3月', '4月', '5月', '6月']
},
yAxis: {
type: 'value',
name: '交易额(亿元)'
},
series: [
{
name: '交易量',
type: 'line',
stack: 'Total',
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(58, 77, 233, 0.8)' },
{ offset: 1, color: 'rgba(58, 77, 233, 0.1)' }
])
},
emphasis: {
focus: 'series'
},
data: [2.8, 3.1, 3.5, 3.26, 3.4, 3.6],
itemStyle: {
color: '#3a4de9'
},
lineStyle: {
width: 3,
color: '#3a4de9'
},
symbol: 'circle',
symbolSize: 8
}
]
}
chart.setOption(option)
window.addEventListener('resize', () => {
chart.resize()
})
}
// 初始化品类占比图表
const initCategoryChart = () => {
if (!categoryChartRef.value) return
const chart = echarts.init(categoryChartRef.value)
const option = {
tooltip: {
trigger: 'item'
},
legend: {
bottom: '5%',
left: 'center'
},
series: [
{
name: '品类占比',
type: 'pie',
radius: ['40%', '70%'],
center: ['50%', '40%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: '18',
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: [
{ value: 45, name: '肉牛养殖', itemStyle: { color: '#3a4de9' } },
{ value: 30, name: '奶牛养殖', itemStyle: { color: '#4cd137' } },
{ value: 15, name: '犊牛养殖', itemStyle: { color: '#e1b12c' } },
{ value: 10, name: '其他养殖', itemStyle: { color: '#44bd32' } }
]
}
]
}
chart.setOption(option)
window.addEventListener('resize', () => {
chart.resize()
})
}
// 初始化养殖区域分布图表
const initMapChart = () => {
@@ -182,8 +301,8 @@ const initMapChart = () => {
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
legend: {
orient: 'vertical',
left: 'left',
bottom: '5%',
left: 'center',
data: ['东北地区', '华北地区', '华东地区', '华南地区', '西南地区', '西北地区']
},
series: [
@@ -212,12 +331,12 @@ const initMapChart = () => {
show: false
},
data: [
{ value: 28, name: '东北地区' },
{ value: 22, name: '华北地区' },
{ value: 18, name: '华东地区' },
{ value: 12, name: '华南地区' },
{ value: 15, name: '西南地区' },
{ value: 5, name: '西北地区' }
{ value: 28, name: '东北地区', itemStyle: { color: '#3a4de9' } },
{ value: 22, name: '华北地区', itemStyle: { color: '#4cd137' } },
{ value: 18, name: '华东地区', itemStyle: { color: '#e1b12c' } },
{ value: 12, name: '华南地区', itemStyle: { color: '#44bd32' } },
{ value: 15, name: '西南地区', itemStyle: { color: '#0097e6' } },
{ value: 5, name: '西北地区', itemStyle: { color: '#8c7ae6' } }
]
}
]
@@ -265,9 +384,8 @@ const initScaleChart = () => {
data: [6850, 1250, 156],
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#83bff6' },
{ offset: 0.5, color: '#188df0' },
{ offset: 1, color: '#188df0' }
{ offset: 0, color: '#3a4de9' },
{ offset: 1, color: '#8c7ae6' }
])
}
}
@@ -281,210 +399,162 @@ const initScaleChart = () => {
})
}
// 初始化交易量趋势图表
const initTransactionChart = () => {
if (!transactionChartRef.value) return
const chart = echarts.init(transactionChartRef.value)
const option = {
tooltip: {
trigger: 'axis'
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['1月', '2月', '3月', '4月', '5月', '6月']
},
yAxis: {
type: 'value',
name: '交易额(亿元)'
},
series: [
{
name: '交易量',
type: 'line',
stack: 'Total',
areaStyle: {},
emphasis: {
focus: 'series'
},
data: [2.8, 3.1, 3.5, 3.26, null, null],
itemStyle: {
color: '#52c41a'
},
lineStyle: {
width: 3
},
symbol: 'circle',
symbolSize: 8
}
]
}
chart.setOption(option)
window.addEventListener('resize', () => {
chart.resize()
})
}
// 初始化品类占比图表
const initCategoryChart = () => {
if (!categoryChartRef.value) return
const chart = echarts.init(categoryChartRef.value)
const option = {
tooltip: {
trigger: 'item'
},
legend: {
top: 'bottom'
},
series: [
{
name: '品类占比',
type: 'pie',
radius: '65%',
center: ['50%', '40%'],
data: [
{ value: 45, name: '肉牛养殖' },
{ value: 30, name: '奶牛养殖' },
{ value: 15, name: '犊牛养殖' },
{ value: 10, name: '其他养殖' }
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
}
chart.setOption(option)
window.addEventListener('resize', () => {
chart.resize()
})
}
// 导出数据处理函数
const handleExportData = () => {
console.log('导出数据类型:', exportDataType.value)
console.log('导出日期范围:', exportDateRange.value)
// 这里应该有实际的导出逻辑
// 显示导出成功提示
const message = `成功导出${exportDataType.value === 'market_price' ? '市场行情' :
exportDataType.value === 'farmer_info' ? '养殖户信息' :
exportDataType.value === 'transaction_data' ? '交易数据' : '疫病监测数据'}数据`
// 在实际项目中这里应该使用Ant Design的message组件
alert(message)
}
// 组件挂载时初始化所有图表
onMounted(() => {
setTimeout(() => {
initMapChart()
initScaleChart()
initTransactionChart()
initCategoryChart()
initMapChart()
initScaleChart()
}, 100)
})
</script>
<style scoped>
.stat-card {
display: flex;
align-items: center;
padding: 16px 0;
}
.stat-icon {
width: 60px;
height: 60px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 16px;
color: white;
}
.stat-icon-primary {
background: linear-gradient(135deg, #1890ff 0%, #40a9ff 100%);
}
.stat-icon-success {
background: linear-gradient(135deg, #52c41a 0%, #73d13d 100%);
}
.stat-icon-warning {
background: linear-gradient(135deg, #fa8c16 0%, #ffa940 100%);
}
.stat-icon-danger {
background: linear-gradient(135deg, #f5222d 0%, #ff4d4f 100%);
}
.stat-content {
flex: 1;
}
.stat-title {
font-size: 14px;
color: #8c8c8c;
margin-bottom: 4px;
}
.stat-value {
font-size: 24px;
font-weight: bold;
color: #333;
margin-bottom: 4px;
}
.stat-change {
font-size: 12px;
}
.text-success {
color: #52c41a;
}
.text-danger {
color: #f5222d;
}
.timeline-content {
padding: 8px 0;
}
.timeline-title {
font-size: 14px;
font-weight: bold;
margin-bottom: 4px;
}
.timeline-time {
font-size: 12px;
color: #8c8c8c;
margin-bottom: 4px;
}
.timeline-desc {
font-size: 13px;
color: #595959;
}
.data-center-container {
padding: 20px;
}
.stat-cards {
margin-bottom: 20px;
}
.stat-card {
height: 100%;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
.stat-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px;
border-bottom: 1px solid #f0f0f0;
}
.stat-title {
font-size: 16px;
color: #666;
}
.stat-icon {
width: 40px;
height: 40px;
border-radius: 50%;
background-color: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
color: #3a4de9;
font-size: 20px;
}
.stat-body {
padding: 16px;
}
.stat-value {
font-size: 24px;
font-weight: bold;
color: #333;
margin-bottom: 8px;
}
.stat-trend {
display: flex;
align-items: center;
color: #52c41a;
}
.stat-trend.negative {
color: #f5222d;
}
.trend-value {
font-weight: bold;
margin-right: 5px;
}
.trend-label {
font-size: 12px;
color: #999;
}
.chart-section {
margin-bottom: 20px;
}
.chart-card {
height: 100%;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
.chart-container {
height: 300px;
}
.update-section {
margin-bottom: 20px;
}
.update-card {
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
.update-item {
display: flex;
align-items: flex-start;
padding: 8px 0;
}
.update-icon {
width: 12px;
height: 12px;
border-radius: 50%;
margin-right: 16px;
margin-top: 5px;
}
.update-icon.blue {
background-color: #1890ff;
}
.update-icon.green {
background-color: #52c41a;
}
.update-icon.orange {
background-color: #fa8c16;
}
.update-icon.red {
background-color: #f5222d;
}
.update-content {
flex: 1;
}
.update-title {
font-size: 14px;
font-weight: bold;
margin-bottom: 4px;
}
.update-time {
font-size: 12px;
color: #999;
margin-bottom: 4px;
}
.update-desc {
font-size: 13px;
color: #666;
}
</style>

View File

@@ -1,436 +0,0 @@
<template>
<div>
<h1>疫情管理</h1>
<!-- 数据统计卡片 -->
<a-row gutter={24}>
<a-col :span="6">
<a-card class="stat-card" :body-style="{ padding: '20px' }">
<div class="stat-content">
<div class="stat-number">{{ epidemicStats.vaccinated }}</div>
<div class="stat-label">累计接种人数</div>
</div>
</a-card>
</a-col>
<a-col :span="6">
<a-card class="stat-card" :body-style="{ padding: '20px' }">
<div class="stat-content">
<div class="stat-number">{{ epidemicStats.tested }}</div>
<div class="stat-label">累计检测次数</div>
</div>
</a-card>
</a-col>
<a-col :span="6">
<a-card class="stat-card" :body-style="{ padding: '20px' }">
<div class="stat-content">
<div class="stat-number">{{ currentCases.confirmed }}</div>
<div class="stat-label">当前确诊</div>
</div>
</a-card>
</a-col>
<a-col :span="6">
<a-card class="stat-card" :body-style="{ padding: '20px' }">
<div class="stat-content">
<div class="stat-number">{{ currentCases.observed }}</div>
<div class="stat-label">隔离观察</div>
</div>
</a-card>
</a-col>
</a-row>
<!-- 图表区域 -->
<a-row gutter={24} style="margin-top: 24px;">
<a-col :span="12">
<a-card title="疫情趋势" :body-style="{ padding: 0 }">
<div style="height: 300px; padding: 10px;">
<div id="epidemic-trend-chart" style="width: 100%; height: 100%;"></div>
</div>
</a-card>
</a-col>
<a-col :span="12">
<a-card title="区域分布" :body-style="{ padding: 0 }">
<div style="height: 300px; padding: 10px;">
<div id="area-distribution-chart" style="width: 100%; height: 100%;"></div>
</div>
</a-card>
</a-col>
</a-row>
<!-- 数据过滤器和表格 -->
<a-card title="疫情数据记录" style="margin-top: 24px;">
<div style="margin-bottom: 16px;">
<a-row gutter={16}>
<a-col :span="6">
<a-select v-model:value="filters.region" placeholder="选择地区" style="width: 100%;">
<a-select-option value="all">全部地区</a-select-option>
<a-select-option value="east">东区</a-select-option>
<a-select-option value="west">西区</a-select-option>
<a-select-option value="south">南区</a-select-option>
<a-select-option value="north">北区</a-select-option>
</a-select>
</a-col>
<a-col :span="6">
<a-select v-model:value="filters.type" placeholder="选择数据类型" style="width: 100%;">
<a-select-option value="all">全部类型</a-select-option>
<a-select-option value="confirmed">确诊病例</a-select-option>
<a-select-option value="suspected">疑似病例</a-select-option>
<a-select-option value="observed">隔离观察</a-select-option>
</a-select>
</a-col>
<a-col :span="6">
<a-range-picker v-model:value="filters.dateRange" style="width: 100%;" />
</a-col>
<a-col :span="6" style="text-align: right;">
<a-button type="primary" @click="searchData">查询</a-button>
<a-button style="margin-left: 8px;" @click="resetFilters">重置</a-button>
</a-col>
</a-row>
</div>
<a-table :columns="epidemicColumns" :data-source="epidemicData" :pagination="{ pageSize: 10 }">
<template #bodyCell:type="{ record }">
<a-tag :color="getTypeColor(record.type)">
{{ getTypeText(record.type) }}
</a-tag>
</template>
<template #bodyCell:action="{ record }">
<a-button type="link" @click="viewEpidemicDetail(record.id)">查看详情</a-button>
</template>
</a-table>
</a-card>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { message } from 'antd'
import axios from 'axios'
import * as echarts from 'echarts'
import { getEpidemicStats } from '@/mock'
const epidemicStats = ref({
vaccinated: 0,
tested: 0
})
const currentCases = ref({
confirmed: 0,
observed: 0,
suspected: 0
})
const epidemicData = ref([])
const filters = ref({
region: 'all',
type: 'all',
dateRange: []
})
let trendChart = null
let distributionChart = null
// 疫情数据表格列定义
const epidemicColumns = [
{
title: 'ID',
dataIndex: 'id',
key: 'id'
},
{
title: '地区',
dataIndex: 'region',
key: 'region'
},
{
title: '类型',
dataIndex: 'type',
key: 'type',
slots: { customRender: 'type' }
},
{
title: '数量',
dataIndex: 'count',
key: 'count'
},
{
title: '报告时间',
dataIndex: 'report_time',
key: 'report_time'
},
{
title: '报告人',
dataIndex: 'reporter',
key: 'reporter'
},
{
title: '操作',
key: 'action',
slots: { customRender: 'action' }
}
]
// 根据类型获取标签颜色
const getTypeColor = (type) => {
const colorMap = {
confirmed: 'red',
suspected: 'orange',
observed: 'blue'
}
return colorMap[type] || 'default'
}
// 根据类型获取显示文本
const getTypeText = (type) => {
const textMap = {
confirmed: '确诊病例',
suspected: '疑似病例',
observed: '隔离观察'
}
return textMap[type] || type
}
// 获取疫情统计数据
const fetchEpidemicStats = async () => {
try {
// 尝试从API获取数据
const response = await axios.get('/api/epidemic/stats')
if (response.data.code === 200) {
const data = response.data.data
epidemicStats.value.vaccinated = data.vaccinated || 0
epidemicStats.value.tested = data.tested || 0
}
} catch (error) {
console.error('获取疫情统计数据失败,使用模拟数据:', error)
// 使用模拟数据
const mockResponse = await getEpidemicStats()
if (mockResponse.code === 200) {
const data = mockResponse.data
epidemicStats.value.vaccinated = data.vaccinated || 0
epidemicStats.value.tested = data.tested || 0
}
}
// 设置其他模拟数据
currentCases.value.confirmed = Math.floor(Math.random() * 50)
currentCases.value.observed = Math.floor(Math.random() * 200) + 50
currentCases.value.suspected = Math.floor(Math.random() * 30)
}
// 获取疫情记录数据
const fetchEpidemicData = async () => {
try {
// 这里应该从API获取数据
// 由于没有实际API使用模拟数据
epidemicData.value = [
{ id: 1, region: '东区', type: 'confirmed', count: 15, report_time: '2024-01-10 09:00:00', reporter: '张三' },
{ id: 2, region: '西区', type: 'confirmed', count: 8, report_time: '2024-01-10 10:30:00', reporter: '李四' },
{ id: 3, region: '南区', type: 'suspected', count: 12, report_time: '2024-01-09 14:20:00', reporter: '王五' },
{ id: 4, region: '北区', type: 'observed', count: 60, report_time: '2024-01-09 16:45:00', reporter: '赵六' },
{ id: 5, region: '东区', type: 'observed', count: 45, report_time: '2024-01-08 08:30:00', reporter: '钱七' },
{ id: 6, region: '西区', type: 'suspected', count: 7, report_time: '2024-01-08 11:15:00', reporter: '孙八' },
{ id: 7, region: '南区', type: 'confirmed', count: 10, report_time: '2024-01-07 14:50:00', reporter: '周九' },
{ id: 8, region: '北区', type: 'observed', count: 52, report_time: '2024-01-07 17:20:00', reporter: '吴十' },
{ id: 9, region: '东区', type: 'suspected', count: 9, report_time: '2024-01-06 09:40:00', reporter: '郑一' },
{ id: 10, region: '西区', type: 'confirmed', count: 6, report_time: '2024-01-06 13:30:00', reporter: '王二' }
]
} catch (error) {
console.error('获取疫情记录数据失败:', error)
message.error('获取疫情记录数据失败,请稍后重试')
}
}
// 初始化图表
const initCharts = () => {
// 疫情趋势图
const trendChartDom = document.getElementById('epidemic-trend-chart')
if (trendChartDom) {
trendChart = echarts.init(trendChartDom)
const trendOption = {
tooltip: {
trigger: 'axis'
},
legend: {
data: ['确诊病例', '疑似病例', '隔离观察']
},
xAxis: {
type: 'category',
data: ['1月', '2月', '3月', '4月', '5月', '6月']
},
yAxis: {
type: 'value'
},
series: [
{
name: '确诊病例',
type: 'line',
data: [12, 19, 15, 10, 8, 12],
smooth: true,
lineStyle: {
color: '#ff4d4f'
}
},
{
name: '疑似病例',
type: 'line',
data: [8, 15, 10, 7, 5, 8],
smooth: true,
lineStyle: {
color: '#fa8c16'
}
},
{
name: '隔离观察',
type: 'line',
data: [45, 60, 50, 35, 40, 45],
smooth: true,
lineStyle: {
color: '#1890ff'
}
}
]
}
trendChart.setOption(trendOption)
}
// 区域分布图
const distributionChartDom = document.getElementById('area-distribution-chart')
if (distributionChartDom) {
distributionChart = echarts.init(distributionChartDom)
const distributionOption = {
tooltip: {
trigger: 'item'
},
legend: {
top: '5%',
left: 'center'
},
series: [
{
name: '区域分布',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: '18',
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: [
{
value: 30,
name: '东区'
},
{
value: 25,
name: '西区'
},
{
value: 22,
name: '南区'
},
{
value: 23,
name: '北区'
}
]
}
]
}
distributionChart.setOption(distributionOption)
}
}
// 响应窗口大小变化
const handleResize = () => {
if (trendChart) {
trendChart.resize()
}
if (distributionChart) {
distributionChart.resize()
}
}
// 搜索数据
const searchData = () => {
message.info('根据筛选条件查询数据')
// 这里应该根据筛选条件重新请求数据
// 目前使用模拟数据实际项目中需要调用API
}
// 重置筛选条件
const resetFilters = () => {
filters.value = {
region: 'all',
type: 'all',
dateRange: []
}
}
// 查看疫情详情
const viewEpidemicDetail = (id) => {
message.info(`查看ID: ${id} 的疫情详情`)
// 这里可以跳转到详情页面
// router.push(`/epidemic/detail/${id}`)
}
// 组件挂载
onMounted(() => {
fetchEpidemicStats()
fetchEpidemicData()
setTimeout(() => {
initCharts()
}, 100)
window.addEventListener('resize', handleResize)
})
// 组件卸载
onUnmounted(() => {
window.removeEventListener('resize', handleResize)
if (trendChart) {
trendChart.dispose()
}
if (distributionChart) {
distributionChart.dispose()
}
})
</script>
<style scoped>
.stat-card {
height: 100%;
transition: all 0.3s ease;
}
.stat-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
transform: translateY(-2px);
}
.stat-content {
display: flex;
flex-direction: column;
align-items: center;
}
.stat-number {
font-size: 28px;
font-weight: bold;
color: #1890ff;
margin-bottom: 8px;
}
.stat-label {
font-size: 14px;
color: #666;
}
</style>

View File

@@ -1,290 +1,147 @@
<template>
<div>
<h1>养殖户管理</h1>
<!-- 搜索和操作栏 -->
<a-card style="margin-bottom: 16px;">
<div style="display: flex; flex-wrap: wrap; gap: 16px; align-items: center;">
<a-input v-model:value="searchKeyword" placeholder="输入养殖户名称或负责人姓名" style="width: 250px;">
<template #prefix>
<span class="iconfont icon-sousuo"></span>
</template>
</a-input>
<a-select v-model:value="statusFilter" 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="closed">已关闭</a-select-option>
</a-select>
<a-select v-model:value="scaleFilter" placeholder="养殖规模" style="width: 120px;">
<a-select-option value="">全部</a-select-option>
<a-select-option value="small">小型(&lt;50)</a-select-option>
<a-select-option value="medium">中型(50-200)</a-select-option>
<a-select-option value="large">大型(&gt;200)</a-select-option>
</a-select>
<a-select v-model:value="regionFilter" placeholder="所在地区" style="width: 150px;">
<a-select-option value="">全部</a-select-option>
<a-select-option value="northeast">东北地区</a-select-option>
<a-select-option value="north">华北地区</a-select-option>
<a-select-option value="east">华东地区</a-select-option>
<a-select-option value="south">华南地区</a-select-option>
<a-select-option value="southwest">西南地区</a-select-option>
<a-select-option value="northwest">西北地区</a-select-option>
</a-select>
<a-button type="primary" @click="handleSearch" style="margin-left: auto;">
<span class="iconfont icon-sousuo"></span> 搜索
</a-button>
<a-button type="default" @click="handleReset">重置</a-button>
<a-button type="dashed" @click="handleImport">
<span class="iconfont icon-daoru"></span> 导入
</a-button>
<a-button type="dashed" @click="handleExport">
<span class="iconfont icon-daochu"></span> 导出
</a-button>
<a-button type="primary" danger @click="handleAddFarmer">
<span class="iconfont icon-tianjia"></span> 新增养殖户
</a-button>
<div style="display: flex; justify-content: space-between; margin-bottom: 16px;">
<div>
<a-button type="primary" @click="handleAddFarmer">新增养殖监管</a-button>
<a-button style="margin-left: 8px;" @click="handleExport">导出表格</a-button>
</div>
</a-card>
<!-- 数据统计卡片 -->
<a-row gutter={24} style="margin-bottom: 16px;">
<a-col :span="6">
<a-statistic title="总养殖户数" :value="totalFarmers" suffix="家" />
</a-col>
<a-col :span="6">
<a-statistic title="正常经营" :value="activeFarmers" suffix="家" :valueStyle="{ color: '#3f8600' }" />
</a-col>
<a-col :span="6">
<a-statistic title="暂停经营" :value="inactiveFarmers" suffix="家" :valueStyle="{ color: '#faad14' }" />
</a-col>
<a-col :span="6">
<a-statistic title="已关闭" :value="closedFarmers" suffix="家" :valueStyle="{ color: '#cf1322' }" />
</a-col>
</a-row>
<div>
<a-input-search
placeholder="请输入"
style="width: 200px; margin-right: 8px;"
v-model:value="searchKeyword"
@search="handleSearch"
/>
<a-select
placeholder="账号"
style="width: 120px;"
v-model:value="accountFilter"
@change="handleSearch"
>
<a-select-option value="">全部</a-select-option>
<a-select-option value="account1">账号筛选1</a-select-option>
<a-select-option value="account2">账号筛选2</a-select-option>
</a-select>
</div>
</div>
<!-- 养殖户列表 -->
<a-card>
<a-table
:columns="columns"
:data-source="farmersData"
:pagination="pagination"
row-key="id"
:row-selection="{ selectedRowKeys, onChange: onSelectChange }"
:scroll="{ x: 'max-content' }"
>
<a-table
:columns="columns"
:data-source="farmersData"
:pagination="pagination"
row-key="id"
:scroll="{ x: 'max-content' }"
>
<!-- 状态列 -->
<template #bodyCell:status="{ record }">
<a-tag :color="getStatusColor(record.status)">{{ getStatusText(record.status) }}</a-tag>
</template>
<!-- 养殖规模列 -->
<template #bodyCell:scale="{ record }">
<span>{{ getScaleText(record.scale) }}</span>
</template>
<!-- 操作列 -->
<template #bodyCell:action="{ record }">
<div style="display: flex; gap: 8px;">
<a-button size="small" @click="handleView(record)">查看</a-button>
<a-button size="small" type="primary" @click="handleEdit(record)">编辑</a-button>
<a-button size="small" danger @click="handleDelete(record.id)">删除</a-button>
</div>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'status'">
<a-tag :color="getStatusColor(record.status)">{{ getStatusText(record.status) }}</a-tag>
</template>
<template v-if="column.key === 'action'">
<div style="display: flex; gap: 8px;">
<a-button size="small" @click="handleView(record)">查看</a-button>
<a-button size="small" type="primary" @click="handleEdit(record)">编辑</a-button>
<a-button size="small" danger @click="handleDelete(record.id)">删除</a-button>
</div>
</template>
</template>
</a-table>
</a-card>
<!-- 新增/编辑养殖户模态框 -->
<a-modal
v-model:open="isAddEditModalOpen"
title="新增/编辑养殖户"
:footer="null"
width={800}
>
<a-form
:model="currentFarmer"
layout="vertical"
style="max-width: 600px; margin: 0 auto;"
>
<a-row gutter={24}>
<a-col :span="12">
<a-form-item label="养殖户名称" name="name" :rules="[{ required: true, message: '请输入养殖户名称' }]">
<a-input v-model:value="currentFarmer.name" placeholder="请输入养殖户名称" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="统一社会信用代码" name="creditCode" :rules="[{ required: true, message: '请输入统一社会信用代码' }]">
<a-input v-model:value="currentFarmer.creditCode" placeholder="请输入统一社会信用代码" />
</a-form-item>
</a-col>
</a-row>
<a-row gutter={24}>
<a-col :span="12">
<a-form-item label="负责人姓名" name="manager" :rules="[{ required: true, message: '请输入负责人姓名' }]">
<a-input v-model:value="currentFarmer.manager" placeholder="请输入负责人姓名" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="联系电话" name="phone" :rules="[{ required: true, message: '请输入联系电话' }]">
<a-input v-model:value="currentFarmer.phone" placeholder="请输入联系电话" />
</a-form-item>
</a-col>
</a-row>
<a-row gutter={24}>
<a-col :span="12">
<a-form-item label="所在地区" name="region" :rules="[{ required: true, message: '请选择所在地区' }]">
<a-select v-model:value="currentFarmer.region" placeholder="请选择所在地区">
<a-select-option value="northeast">东北地区</a-select-option>
<a-select-option value="north">华北地区</a-select-option>
<a-select-option value="east">华东地区</a-select-option>
<a-select-option value="south">华南地区</a-select-option>
<a-select-option value="southwest">西南地区</a-select-option>
<a-select-option value="northwest">西北地区</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="详细地址" name="address" :rules="[{ required: true, message: '请输入详细地址' }]">
<a-input v-model:value="currentFarmer.address" placeholder="请输入详细地址" />
</a-form-item>
</a-col>
</a-row>
<a-row gutter={24}>
<a-col :span="12">
<a-form-item label="养殖规模" name="scale" :rules="[{ required: true, message: '请选择养殖规模' }]">
<a-select v-model:value="currentFarmer.scale" placeholder="请选择养殖规模">
<a-select-option value="small">小型(&lt;50)</a-select-option>
<a-select-option value="medium">中型(50-200)</a-select-option>
<a-select-option value="large">大型(&gt;200)</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="存栏数量" name="stockQuantity" :rules="[{ required: true, message: '请输入存栏数量' }]">
<a-input-number v-model:value="currentFarmer.stockQuantity" :min="0" placeholder="请输入存栏数量" />
</a-form-item>
</a-col>
</a-row>
<a-form-item label="养殖类型" name="farmType">
<a-checkbox-group v-model:value="currentFarmer.farmType">
<a-checkbox value="beef">肉牛养殖</a-checkbox>
<a-checkbox value="milk">奶牛养殖</a-checkbox>
<a-checkbox value="calf">犊牛养殖</a-checkbox>
<a-checkbox value="other">其他养殖</a-checkbox>
</a-checkbox-group>
</a-form-item>
<a-form-item label="经营状态" name="status" :rules="[{ required: true, message: '请选择经营状态' }]">
<a-radio-group v-model:value="currentFarmer.status">
<a-radio value="active">正常经营</a-radio>
<a-radio value="inactive">暂停经营</a-radio>
<a-radio value="closed">已关闭</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="备注信息" name="remark">
<a-input.TextArea v-model:value="currentFarmer.remark" placeholder="请输入备注信息" :rows="3" />
</a-form-item>
<div style="display: flex; justify-content: flex-end; gap: 16px;">
<a-button @click="isAddEditModalOpen = false">取消</a-button>
<a-button type="primary" @click="handleSave">保存</a-button>
</div>
</a-form>
</a-modal>
<!-- 查看养殖户详情模态框 -->
<a-modal
v-model:open="isViewModalOpen"
title="养殖户详情"
:footer="null"
width={800}
>
<div v-if="viewFarmer" class="farmer-detail">
<div class="detail-row">
<div class="detail-label">养殖户名称</div>
<div class="detail-value">{{ viewFarmer.name }}</div>
</div>
<div class="detail-row">
<div class="detail-label">统一社会信用代码</div>
<div class="detail-value">{{ viewFarmer.creditCode }}</div>
</div>
<div class="detail-row">
<div class="detail-label">负责人姓名</div>
<div class="detail-value">{{ viewFarmer.manager }}</div>
</div>
<div class="detail-row">
<div class="detail-label">联系电话</div>
<div class="detail-value">{{ viewFarmer.phone }}</div>
</div>
<div class="detail-row">
<div class="detail-label">所在地区</div>
<div class="detail-value">{{ getRegionText(viewFarmer.region) }}</div>
</div>
<div class="detail-row">
<div class="detail-label">详细地址</div>
<div class="detail-value">{{ viewFarmer.address }}</div>
</div>
<div class="detail-row">
<div class="detail-label">养殖规模</div>
<div class="detail-value">{{ getScaleText(viewFarmer.scale) }}</div>
</div>
<div class="detail-row">
<div class="detail-label">存栏数量</div>
<div class="detail-value">{{ viewFarmer.stockQuantity }}</div>
</div>
<div class="detail-row">
<div class="detail-label">养殖类型</div>
<div class="detail-value">{{ getFarmTypeText(viewFarmer.farmType) }}</div>
</div>
<div class="detail-row">
<div class="detail-label">经营状态</div>
<div class="detail-value">
<a-tag :color="getStatusColor(viewFarmer.status)">{{ getStatusText(viewFarmer.status) }}</a-tag>
</div>
</div>
<div class="detail-row">
<div class="detail-label">创建时间</div>
<div class="detail-value">{{ viewFarmer.createTime }}</div>
</div>
<div class="detail-row">
<div class="detail-label">备注信息</div>
<div class="detail-value">{{ viewFarmer.remark || '无' }}</div>
</div>
</div>
<div style="text-align: center; margin-top: 24px;">
<a-button @click="isViewModalOpen = false">关闭</a-button>
</div>
</a-modal>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { ref, reactive, onMounted } from 'vue'
import { message } from 'ant-design-vue'
// 搜索条件
// 搜索关键词
const searchKeyword = ref('')
const statusFilter = ref('')
const scaleFilter = ref('')
const regionFilter = ref('')
const accountFilter = ref('')
// 统计数据
const totalFarmers = ref(1256)
const activeFarmers = ref(986)
const inactiveFarmers = ref(158)
const closedFarmers = ref(112)
// 表格列定义
const columns = [
{
title: '账号',
dataIndex: 'account',
key: 'account',
width: 150
},
{
title: '机构识别码',
dataIndex: 'orgCode',
key: 'orgCode',
width: 150
},
{
title: '账号昵称',
dataIndex: 'nickname',
key: 'nickname',
width: 150
},
{
title: '真实姓名',
dataIndex: 'realName',
key: 'realName',
width: 150
},
{
title: '养殖场名称',
dataIndex: 'farmName',
key: 'farmName',
width: 180
},
{
title: '养殖场类型',
dataIndex: 'farmType',
key: 'farmType',
width: 120
},
{
title: '养殖场种类',
dataIndex: 'animalType',
key: 'animalType',
width: 120
},
{
title: '数量',
dataIndex: 'animalCount',
key: 'animalCount',
width: 100
},
{
title: '养殖场地址',
dataIndex: 'address',
key: 'address',
width: 250
},
{
title: '登记时间',
dataIndex: 'registerTime',
key: 'registerTime',
width: 150,
sorter: (a, b) => new Date(a.registerTime) - new Date(b.registerTime)
},
{
title: '登记人',
dataIndex: 'registrar',
key: 'registrar',
width: 120
},
{
title: '更新时间',
dataIndex: 'updateTime',
key: 'updateTime',
width: 150,
sorter: (a, b) => new Date(a.updateTime) - new Date(b.updateTime)
},
{
title: '操作',
key: 'action',
fixed: 'right',
width: 180
}
]
// 分页配置
const pagination = reactive({
@@ -292,417 +149,152 @@ const pagination = reactive({
pageSize: 10,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `${total} 条记录`
})
// 选中的行
const selectedRowKeys = ref([])
const onSelectChange = (newSelectedRowKeys) => {
selectedRowKeys.value = newSelectedRowKeys
}
// 模态框状态
const isAddEditModalOpen = ref(false)
const isViewModalOpen = ref(false)
// 当前编辑的养殖户
const currentFarmer = reactive({
id: '',
name: '',
creditCode: '',
manager: '',
phone: '',
region: '',
address: '',
scale: '',
stockQuantity: 0,
farmType: [],
status: 'active',
remark: '',
createTime: ''
})
// 查看的养殖户
const viewFarmer = ref(null)
// 养殖户列表数据
// 模拟数据
const farmersData = ref([
{
id: '1',
name: '瑞丰养殖场',
creditCode: '91370100MA3C8Y6D6X',
manager: '张明',
phone: '13800138001',
region: 'north',
address: '北京市海淀区中关村南大街5号',
scale: 'large',
stockQuantity: 580,
farmType: ['beef', 'milk'],
status: 'active',
remark: '市级重点养殖企业',
createTime: '2020-01-15 10:30:00'
account: '176****4187',
orgCode: '18094-785778',
nickname: '齐五旺',
realName: '齐五旺',
farmName: '齐五旺养殖场',
farmType: '散养',
animalType: '',
animalCount: 10,
address: '内蒙古自治区通辽市扎鲁特旗鲁北镇额尔格图木塔格镇',
registerTime: '2023-05-22 08:59:44',
registrar: '齐五旺',
updateTime: '2025-08-10 06:07:41',
status: 'active'
},
{
id: '2',
name: '绿源生态养殖合作社',
creditCode: '93370100MA3C8Y6D6X',
manager: '李华',
phone: '13900139001',
region: 'east',
address: '上海市浦东新区张江高科技园区博云路2号',
scale: 'medium',
stockQuantity: 150,
farmType: ['beef'],
status: 'active',
remark: '生态养殖示范基地',
createTime: '2020-02-20 14:20:00'
account: '132****9345',
orgCode: '384708-449642',
nickname: '扎鲁特数据中心',
realName: '扎鲁特数据中心',
farmName: '扎鲁特数据中心',
farmType: '散养',
animalType: '',
animalCount: 10,
address: '内蒙古自治区通辽市扎鲁特旗',
registerTime: '2024-07-23 13:37:00',
registrar: '扎鲁特数据中心',
updateTime: '2024-10-16 11:33:02',
status: 'active'
},
{
id: '3',
name: '金牛养殖场',
creditCode: '91370100MA3C8Y6D6Y',
manager: '王强',
phone: '13700137001',
region: 'south',
address: '广州市天河区天河路385号',
scale: 'small',
stockQuantity: 30,
farmType: ['milk'],
status: 'inactive',
remark: '暂时休业整顿',
createTime: '2020-03-05 09:15:00'
account: '139****1221',
orgCode: '382987-319364',
nickname: '邵晓艳',
realName: '邵晓艳',
farmName: '扎鲁特旗百顺养殖专业合作社',
farmType: '规模',
animalType: '',
animalCount: 741,
address: '内蒙古自治区通辽市扎鲁特旗巴日合镇巴日合村委会',
registerTime: '2023-07-10 11:17:50',
registrar: '赵忠林',
updateTime: '2024-07-03 11:53:47',
status: 'active'
},
{
id: '4',
name: '草原牧歌养殖公司',
creditCode: '91370100MA3C8Y6D6Z',
manager: '赵芳',
phone: '13600136001',
region: 'northwest',
address: '西安市雁塔区科技路25号',
scale: 'large',
stockQuantity: 850,
farmType: ['beef', 'calf'],
status: 'active',
remark: '省级农业产业化龙头企业',
createTime: '2020-04-10 11:45:00'
account: '151****1013',
orgCode: '1069-899484',
nickname: '双成家庭农场',
realName: '双成',
farmName: '双成家庭农场',
farmType: '散养',
animalType: '',
animalCount: 74,
address: '内蒙古自治区通辽市扎鲁特旗鲁北镇宝力根花镇',
registerTime: '2023-04-24 10:54:22',
registrar: '双成',
updateTime: '2024-05-08 11:14:48',
status: 'active'
},
{
id: '5',
name: '祥和养殖场',
creditCode: '91370100MA3C8Y6D6A',
manager: '孙建国',
phone: '13500135001',
region: 'southwest',
address: '成都市武侯区一环路南四段1号',
scale: 'medium',
stockQuantity: 120,
farmType: ['beef'],
status: 'closed',
remark: '经营不善已关闭',
createTime: '2020-05-15 16:00:00'
},
{
id: '6',
name: '福满家养殖园',
creditCode: '91370100MA3C8Y6D6B',
manager: '周小丽',
phone: '13400134001',
region: 'northeast',
address: '沈阳市沈河区青年大街100号',
scale: 'small',
stockQuantity: 40,
farmType: ['milk', 'other'],
status: 'active',
remark: '家庭农场示范户',
createTime: '2020-06-20 13:30:00'
},
{
id: '7',
name: '康源养殖有限公司',
creditCode: '91370100MA3C8Y6D6C',
manager: '吴大山',
phone: '13300133001',
region: 'east',
address: '南京市鼓楼区中山北路1号',
scale: 'large',
stockQuantity: 620,
farmType: ['beef', 'milk', 'calf'],
status: 'active',
remark: '现代化养殖企业',
createTime: '2020-07-25 10:15:00'
},
{
id: '8',
name: '田园牧歌生态养殖',
creditCode: '91370100MA3C8Y6D6D',
manager: '郑小华',
phone: '13200132001',
region: 'north',
address: '石家庄市桥西区自强路22号',
scale: 'medium',
stockQuantity: 95,
farmType: ['beef'],
status: 'inactive',
remark: '设备升级中',
createTime: '2020-08-30 15:45:00'
},
{
id: '9',
name: '龙凤养殖场',
creditCode: '91370100MA3C8Y6D6E',
manager: '钱小红',
phone: '13100131001',
region: 'south',
address: '深圳市南山区科技园南区',
scale: 'small',
stockQuantity: 25,
farmType: ['milk'],
status: 'active',
remark: '特色奶制品供应商',
createTime: '2020-09-05 09:30:00'
},
{
id: '10',
name: '远大养殖集团',
creditCode: '91370100MA3C8Y6D6F',
manager: '陈明亮',
phone: '13000130001',
region: 'east',
address: '杭州市西湖区文三路478号',
scale: 'large',
stockQuantity: 980,
farmType: ['beef', 'calf'],
status: 'active',
remark: '国家级农业龙头企业',
createTime: '2020-10-10 14:20:00'
account: '150****1368',
orgCode: '1451-668414',
nickname: '刘超',
realName: '刘超',
farmName: '大数据中心',
farmType: '散养',
animalType: '',
animalCount: 11,
address: '内蒙古自治区通辽市扎鲁特旗鲁北镇',
registerTime: '2023-04-11 15:36:37',
registrar: '孙主任',
updateTime: '2024-03-04 11:18:24',
status: 'active'
}
])
// 表格列定义
const columns = [
{
title: '养殖户名称',
dataIndex: 'name',
key: 'name',
ellipsis: true
},
{
title: '负责人',
dataIndex: 'manager',
key: 'manager',
width: 100
},
{
title: '联系电话',
dataIndex: 'phone',
key: 'phone',
width: 120
},
{
title: '所在地区',
dataIndex: 'region',
key: 'region',
width: 100,
customRender: ({ text }) => getRegionText(text)
},
{
title: '养殖规模',
dataIndex: 'scale',
key: 'scale',
width: 120
},
{
title: '存栏数量',
dataIndex: 'stockQuantity',
key: 'stockQuantity',
width: 100,
customRender: ({ text }) => `${text}`
},
{
title: '经营状态',
dataIndex: 'status',
key: 'status',
width: 100
},
{
title: '创建时间',
dataIndex: 'createTime',
key: 'createTime',
width: 160
},
{
title: '操作',
key: 'action',
width: 180,
fixed: 'right'
}
]
// 初始化数据
onMounted(() => {
pagination.total = farmersData.value.length
})
// 处理搜索
const handleSearch = () => {
// 实际应用中这里应该调用API进行搜索
message.success('搜索条件已应用')
}
// 处理导出
const handleExport = () => {
message.success('数据导出中...')
}
// 处理添加养殖户
const handleAddFarmer = () => {
message.success('打开新增养殖监管表单')
}
// 处理查看详情
const handleView = (record) => {
message.info(`查看养殖户: ${record.farmName}`)
}
// 处理编辑
const handleEdit = (record) => {
message.info(`编辑养殖户: ${record.farmName}`)
}
// 处理删除
const handleDelete = (id) => {
message.success(`删除养殖户ID: ${id}`)
}
// 获取状态颜色
const getStatusColor = (status) => {
switch (status) {
case 'active': return 'green'
case 'inactive': return 'orange'
case 'closed': return 'red'
default: return 'default'
const statusMap = {
active: 'green',
inactive: 'orange',
closed: 'red'
}
return statusMap[status] || 'default'
}
// 获取状态文本
const getStatusText = (status) => {
switch (status) {
case 'active': return '正常经营'
case 'inactive': return '暂停经营'
case 'closed': return '关闭'
default: return status
const statusMap = {
active: '正常',
inactive: '暂停',
closed: '关闭'
}
return statusMap[status] || '未知'
}
// 获取规模文本
const getScaleText = (scale) => {
switch (scale) {
case 'small': return '小型(&lt;50头)'
case 'medium': return '中型(50-200头)'
case 'large': return '大型(&gt;200头)'
default: return scale
}
}
// 获取地区文本
const getRegionText = (region) => {
switch (region) {
case 'northeast': return '东北地区'
case 'north': return '华北地区'
case 'east': return '华东地区'
case 'south': return '华南地区'
case 'southwest': return '西南地区'
case 'northwest': return '西北地区'
default: return region
}
}
// 获取养殖类型文本
const getFarmTypeText = (farmTypes) => {
if (!farmTypes || farmTypes.length === 0) return '无'
const typeMap = {
beef: '肉牛养殖',
milk: '奶牛养殖',
calf: '犊牛养殖',
other: '其他养殖'
}
return farmTypes.map(type => typeMap[type] || type).join('、')
}
// 搜索处理
const handleSearch = () => {
console.log('搜索条件:', {
keyword: searchKeyword.value,
status: statusFilter.value,
scale: scaleFilter.value,
region: regionFilter.value
})
// 这里应该有实际的搜索逻辑
// 模拟搜索后的总数
pagination.total = farmersData.value.length
}
// 重置处理
const handleReset = () => {
searchKeyword.value = ''
statusFilter.value = ''
scaleFilter.value = ''
regionFilter.value = ''
selectedRowKeys.value = []
}
// 导入处理
const handleImport = () => {
console.log('导入养殖户数据')
// 这里应该有实际的导入逻辑
}
// 导出处理
const handleExport = () => {
console.log('导出养殖户数据')
// 这里应该有实际的导出逻辑
}
// 新增养殖户
const handleAddFarmer = () => {
// 重置当前养殖户数据
Object.assign(currentFarmer, {
id: '',
name: '',
creditCode: '',
manager: '',
phone: '',
region: '',
address: '',
scale: '',
stockQuantity: 0,
farmType: [],
status: 'active',
remark: '',
createTime: ''
})
isAddEditModalOpen.value = true
}
// 编辑养殖户
const handleEdit = (record) => {
// 复制记录数据到当前养殖户
Object.assign(currentFarmer, JSON.parse(JSON.stringify(record)))
isAddEditModalOpen.value = true
}
// 查看养殖户
const handleView = (record) => {
viewFarmer.value = JSON.parse(JSON.stringify(record))
isViewModalOpen.value = true
}
// 删除养殖户
const handleDelete = (id) => {
console.log('删除养殖户:', id)
// 这里应该有实际的删除逻辑和确认提示
// 模拟删除成功
alert(`成功删除养殖户ID: ${id}`)
}
// 保存养殖户
const handleSave = () => {
console.log('保存养殖户:', currentFarmer)
// 这里应该有实际的保存逻辑
// 模拟保存成功
isAddEditModalOpen.value = false
alert('保存成功')
}
// 初始化数据
pagination.total = farmersData.value.length
</script>
<style scoped>
.farmer-detail {
padding: 16px;
}
.detail-row {
display: flex;
margin-bottom: 16px;
align-items: flex-start;
}
.detail-label {
width: 150px;
font-weight: bold;
color: #666;
}
.detail-value {
flex: 1;
color: #333;
}
.ant-table {
margin-top: 16px;
}
</style>

View File

@@ -1,270 +0,0 @@
<template>
<div>
<h1>文件管理</h1>
<a-card style="margin-bottom: 16px;">
<a-row gutter={24}>
<a-col :span="12">
<a-upload-dragger
name="file"
:multiple="true"
:action="uploadUrl"
@change="handleUploadChange"
>
<p class="ant-upload-drag-icon">
<inbox-outlined />
</p>
<p class="ant-upload-text">点击或拖拽文件到此区域上传</p>
<p class="ant-upload-hint">
支持单个或批量上传文件大小不超过100MB
</p>
</a-upload-dragger>
</a-col>
<a-col :span="12">
<a-card title="上传设置" :body-style="{ padding: '20px' }">
<a-form layout="vertical">
<a-form-item label="文件分类">
<a-select v-model:value="fileCategory">
<a-select-option value="policy">政策文件</a-select-option>
<a-select-option value="report">报表文件</a-select-option>
<a-select-option value="notice">通知文件</a-select-option>
<a-select-option value="other">其他文件</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="文件描述">
<a-input v-model:value="fileDescription" placeholder="请输入文件描述" />
</a-form-item>
<a-form-item>
<a-button type="primary" @click="handleUploadConfirm">确认上传</a-button>
</a-form-item>
</a-form>
</a-card>
</a-col>
</a-row>
</a-card>
<!-- 文件列表 -->
<a-card title="文件列表">
<a-row gutter={24} style="margin-bottom: 16px;">
<a-col :span="6">
<a-input v-model:value="searchKeyword" placeholder="搜索文件名" allow-clear />
</a-col>
<a-col :span="6">
<a-select v-model:value="filterCategory" placeholder="筛选分类" allow-clear>
<a-select-option value="policy">政策文件</a-select-option>
<a-select-option value="report">报表文件</a-select-option>
<a-select-option value="notice">通知文件</a-select-option>
<a-select-option value="other">其他文件</a-select-option>
</a-select>
</a-col>
<a-col :span="6">
<a-date-picker v-model:value="uploadDateRange" style="width: 100%;" />
</a-col>
<a-col :span="6" style="text-align: right;">
<a-button type="primary" @click="handleSearch">搜索</a-button>
<a-button style="margin-left: 8px;" @click="handleReset">重置</a-button>
</a-col>
</a-row>
<a-table :columns="fileColumns" :data-source="fileList" :pagination="pagination" row-key="id">
<template #bodyCell:name="{ record }">
<a @click="handleFileDownload(record)">{{ record.name }}</a>
</template>
<template #bodyCell:size="{ record }">
<span>{{ formatFileSize(record.size) }}</span>
</template>
<template #bodyCell:action="{ record }">
<a-space>
<a-button type="link" @click="handleFilePreview(record)">预览</a-button>
<a-button type="link" @click="handleFileDownload(record)">下载</a-button>
<a-button type="link" danger @click="handleFileDelete(record.id)">删除</a-button>
</a-space>
</template>
</a-table>
</a-card>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { InboxOutlined } from '@ant-design/icons-vue'
import axios from 'axios'
import dayjs from 'dayjs'
// 上传配置
const uploadUrl = '/api/files/upload'
const fileCategory = ref('')
const fileDescription = ref('')
// 搜索和筛选
const searchKeyword = ref('')
const filterCategory = ref('')
const uploadDateRange = ref(null)
// 分页配置
const pagination = reactive({
current: 1,
pageSize: 10,
total: 0
})
// 文件列表数据
const fileList = ref([
{
id: '1',
name: '2024年第一季度工作报告.pdf',
size: 2097152,
type: 'pdf',
category: 'report',
uploadTime: '2024-04-01T08:30:00Z',
uploader: '张三',
downloadCount: 12
},
{
id: '2',
name: '关于加强养殖场监管的通知.docx',
size: 1048576,
type: 'docx',
category: 'notice',
uploadTime: '2024-03-28T14:15:00Z',
uploader: '李四',
downloadCount: 35
},
{
id: '3',
name: '动物防疫政策解读.pdf',
size: 3145728,
type: 'pdf',
category: 'policy',
uploadTime: '2024-03-15T10:45:00Z',
uploader: '王五',
downloadCount: 89
}
])
// 文件表格列定义
const fileColumns = [
{
title: '文件名',
dataIndex: 'name',
key: 'name',
ellipsis: true
},
{
title: '文件类型',
dataIndex: 'type',
key: 'type'
},
{
title: '分类',
dataIndex: 'category',
key: 'category',
customRender: (text) => {
const categoryMap = {
policy: '政策文件',
report: '报表文件',
notice: '通知文件',
other: '其他文件'
}
return categoryMap[text] || text
}
},
{
title: '文件大小',
dataIndex: 'size',
key: 'size',
customRender: (text) => formatFileSize(text)
},
{
title: '上传时间',
dataIndex: 'uploadTime',
key: 'uploadTime',
customRender: (text) => dayjs(text).format('YYYY-MM-DD HH:mm')
},
{
title: '上传人',
dataIndex: 'uploader',
key: 'uploader'
},
{
title: '下载次数',
dataIndex: 'downloadCount',
key: 'downloadCount'
},
{
title: '操作',
key: 'action',
width: 150,
fixed: 'right'
}
]
// 格式化文件大小
const formatFileSize = (bytes) => {
if (bytes === 0) return '0 Bytes'
const k = 1024
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}
// 上传文件变化处理
const handleUploadChange = (info) => {
console.log('文件上传变化:', info)
}
// 确认上传
const handleUploadConfirm = () => {
// 这里应该有实际的上传逻辑
console.log('确认上传', { fileCategory: fileCategory.value, fileDescription: fileDescription.value })
}
// 文件搜索
const handleSearch = () => {
// 这里应该有实际的搜索逻辑
console.log('搜索文件', { searchKeyword: searchKeyword.value, filterCategory: filterCategory.value, uploadDateRange: uploadDateRange.value })
}
// 重置搜索条件
const handleReset = () => {
searchKeyword.value = ''
filterCategory.value = ''
uploadDateRange.value = null
}
// 文件预览
const handleFilePreview = (file) => {
console.log('预览文件:', file)
}
// 文件下载
const handleFileDownload = (file) => {
console.log('下载文件:', file)
}
// 文件删除
const handleFileDelete = (id) => {
console.log('删除文件:', id)
}
</script>
<style scoped>
.stat-card {
height: 100%;
}
.stat-content {
text-align: center;
}
.stat-number {
font-size: 24px;
font-weight: bold;
color: #1890ff;
}
.stat-label {
font-size: 14px;
color: #666;
margin-top: 8px;
}
</style>

View File

@@ -1,386 +0,0 @@
<template>
<div>
<h1>日志管理</h1>
<a-card style="margin-bottom: 16px;">
<a-row gutter={24}>
<a-col :span="16">
<a-input-search
placeholder="搜索操作内容或操作人"
allow-clear
enter-button="搜索"
size="large"
style="width: 300px; margin-right: 16px;"
@search="handleSearch"
/>
<a-select v-model:value="filterOperationType" placeholder="筛选操作类型" allow-clear style="width: 150px; margin-right: 16px;">
<a-select-option value="login">登录</a-select-option>
<a-select-option value="logout">登出</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="query">查询</a-select-option>
<a-select-option value="export">导出</a-select-option>
</a-select>
<a-select v-model:value="filterModule" placeholder="筛选模块" allow-clear style="width: 150px; margin-right: 16px;">
<a-select-option value="user">用户管理</a-select-option>
<a-select-option value="epidemic">疫情管理</a-select-option>
<a-select-option value="supervision">监管管理</a-select-option>
<a-select-option value="file">文件管理</a-select-option>
<a-select-option value="warehouse">仓库管理</a-select-option>
<a-select-option value="service">服务管理</a-select-option>
<a-select-option value="personnel">人员管理</a-select-option>
</a-select>
<a-select v-model:value="filterStatus" placeholder="筛选状态" allow-clear style="width: 120px;">
<a-select-option value="success">成功</a-select-option>
<a-select-option value="fail">失败</a-select-option>
</a-select>
</a-col>
<a-col :span="8" style="text-align: right;">
<a-range-picker v-model:value="dateRange" format="YYYY-MM-DD" @change="handleDateChange" style="width: 280px; margin-right: 16px;" />
<a-button type="primary" @click="handleExportLogs">导出日志</a-button>
</a-col>
</a-row>
</a-card>
<!-- 日志统计图表 -->
<a-card title="操作统计" style="margin-bottom: 16px;">
<div style="height: 300px;" ref="chartRef"></div>
</a-card>
<!-- 日志列表 -->
<a-card title="日志列表">
<a-table :columns="logColumns" :data-source="logList" :pagination="pagination" row-key="id">
<template #bodyCell:operationType="{ record }">
<a-tag :color="getOperationTypeColor(record.operationType)">{{ getOperationTypeLabel(record.operationType) }}</a-tag>
</template>
<template #bodyCell:module="{ record }">
<a-tag color="blue">{{ getModuleLabel(record.module) }}</a-tag>
</template>
<template #bodyCell:status="{ record }">
<span :class="getStatusClass(record.status)">{{ record.status === 'success' ? '成功' : '失败' }}</span>
</template>
<template #bodyCell:time="{ record }">
{{ dayjs(record.time).format('YYYY-MM-DD HH:mm:ss') }}
</template>
</a-table>
</a-card>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import axios from 'axios'
import dayjs from 'dayjs'
import * as echarts from 'echarts'
// 日期范围
const dateRange = ref([dayjs().subtract(7, 'day'), dayjs()])
// 筛选条件
const filterOperationType = ref('')
const filterModule = ref('')
const filterStatus = ref('')
// 分页配置
const pagination = reactive({
current: 1,
pageSize: 10,
total: 0
})
// 日志列表数据
const logList = ref([
{
id: '1',
operator: 'admin',
operationType: 'login',
module: 'system',
operationContent: '用户登录系统',
ip: '192.168.1.100',
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36',
status: 'success',
time: '2024-04-10T09:00:00Z'
},
{
id: '2',
operator: 'admin',
operationType: 'update',
module: 'user',
operationContent: '更新用户信息用户ID: 1001',
ip: '192.168.1.100',
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36',
status: 'success',
time: '2024-04-10T10:15:00Z'
},
{
id: '3',
operator: 'admin',
operationType: 'create',
module: 'epidemic',
operationContent: '新增疫情记录记录ID: 2001',
ip: '192.168.1.100',
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36',
status: 'success',
time: '2024-04-10T11:30:00Z'
},
{
id: '4',
operator: 'user001',
operationType: 'query',
module: 'supervision',
operationContent: '查询监管数据',
ip: '192.168.1.101',
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Firefox/111.0 Safari/537.36',
status: 'success',
time: '2024-04-10T13:45:00Z'
},
{
id: '5',
operator: 'user002',
operationType: 'export',
module: 'file',
operationContent: '导出文件文件ID: 3001',
ip: '192.168.1.102',
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edge/112.0.0.0 Safari/537.36',
status: 'success',
time: '2024-04-10T14:20:00Z'
},
{
id: '6',
operator: 'user003',
operationType: 'delete',
module: 'warehouse',
operationContent: '删除物资物资ID: 4001',
ip: '192.168.1.103',
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36',
status: 'fail',
errorMsg: '权限不足',
time: '2024-04-10T15:00:00Z'
},
{
id: '7',
operator: 'admin',
operationType: 'logout',
module: 'system',
operationContent: '用户登出系统',
ip: '192.168.1.100',
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36',
status: 'success',
time: '2024-04-10T17:30:00Z'
}
])
// 日志表格列定义
const logColumns = [
{
title: '操作人',
dataIndex: 'operator',
key: 'operator',
width: 120
},
{
title: '操作类型',
dataIndex: 'operationType',
key: 'operationType',
width: 120
},
{
title: '所属模块',
dataIndex: 'module',
key: 'module',
width: 120
},
{
title: '操作内容',
dataIndex: 'operationContent',
key: 'operationContent'
},
{
title: 'IP地址',
dataIndex: 'ip',
key: 'ip',
width: 150
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
width: 80
},
{
title: '错误信息',
dataIndex: 'errorMsg',
key: 'errorMsg',
width: 200
},
{
title: '操作时间',
dataIndex: 'time',
key: 'time',
width: 180
}
]
// 图表引用
const chartRef = ref(null)
// 操作类型标签映射
const getOperationTypeLabel = (type) => {
const map = {
login: '登录',
logout: '登出',
create: '创建',
update: '更新',
delete: '删除',
query: '查询',
export: '导出'
}
return map[type] || type
}
// 操作类型颜色映射
const getOperationTypeColor = (type) => {
const map = {
login: 'blue',
logout: 'purple',
create: 'green',
update: 'orange',
delete: 'red',
query: 'default',
export: 'cyan'
}
return map[type] || 'default'
}
// 模块标签映射
const getModuleLabel = (module) => {
const map = {
system: '系统',
user: '用户管理',
epidemic: '疫情管理',
supervision: '监管管理',
file: '文件管理',
warehouse: '仓库管理',
service: '服务管理',
personnel: '人员管理'
}
return map[module] || module
}
// 状态样式类
const getStatusClass = (status) => {
return status === 'success' ? 'text-success' : 'text-danger'
}
// 搜索日志
const handleSearch = (keyword) => {
console.log('搜索日志:', keyword)
// 这里应该有实际的搜索逻辑
}
// 日期范围改变
const handleDateChange = (dates) => {
console.log('日期范围改变:', dates)
// 这里应该有实际的日期筛选逻辑
}
// 导出日志
const handleExportLogs = () => {
console.log('导出日志')
// 这里应该有实际的导出逻辑
}
// 初始化图表
const initChart = () => {
if (!chartRef.value) return
const chart = echarts.init(chartRef.value)
// 统计数据
const operationStats = {
login: 15,
logout: 12,
create: 8,
update: 23,
delete: 5,
query: 45,
export: 10
}
const moduleStats = {
system: 27,
user: 18,
epidemic: 15,
supervision: 20,
file: 12,
warehouse: 10,
service: 8,
personnel: 6
}
const option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
legend: {
data: ['操作类型统计', '模块统计']
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: [
{
type: 'category',
data: Object.keys(operationStats).map(key => getOperationTypeLabel(key)),
axisTick: {
alignWithLabel: true
}
}
],
yAxis: [
{
type: 'value'
}
],
series: [
{
name: '操作类型统计',
type: 'bar',
barWidth: '30%',
data: Object.values(operationStats),
itemStyle: {
color: '#1890ff'
}
}
]
}
chart.setOption(option)
// 响应式调整
window.addEventListener('resize', () => {
chart.resize()
})
}
// 组件挂载时初始化图表
onMounted(() => {
initChart()
})
</script>
<style scoped>
.text-success {
color: #52c41a;
}
.text-danger {
color: #f5222d;
}
</style>

View File

@@ -1,347 +1,198 @@
<template>
<div>
<h1>市场行情</h1>
<a-card style="margin-bottom: 16px;">
<a-row gutter={24}>
<a-col :span="8">
<a-select v-model:value="selectedProduct" placeholder="选择产品类型" allow-clear style="width: 200px;">
<a-select-option value="beef">牛肉</a-select-option>
<a-select-option value="milk">牛奶</a-select-option>
<a-select-option value="calf">牛犊</a-select-option>
<a-select-option value="feed">饲料</a-select-option>
</a-select>
</a-col>
<a-col :span="8">
<a-select v-model:value="selectedRegion" placeholder="选择地区" allow-clear style="width: 200px;">
<a-select-option value="national">全国</a-select-option>
<a-select-option value="north">北方地区</a-select-option>
<a-select-option value="south">南方地区</a-select-option>
<a-select-option value="east">东部地区</a-select-option>
<a-select-option value="west">西部地区</a-select-option>
</a-select>
</a-col>
<a-col :span="8">
<a-button type="primary" @click="handleSearch">查询</a-button>
<a-button style="margin-left: 8px;" @click="handleExport">导出数据</a-button>
</a-col>
</a-row>
</a-card>
<div class="market-price-container">
<!-- 产品类型选项卡 -->
<div class="product-tabs">
<a-tabs v-model:activeKey="activeProductTab">
<a-tab-pane key="beef" tab="育肥牛"></a-tab-pane>
<a-tab-pane key="dairy" tab="繁殖牛"></a-tab-pane>
<a-tab-pane key="milk" tab="奶牛"></a-tab-pane>
</a-tabs>
</div>
<!-- 价格走势图表 -->
<a-card title="价格走势图" style="margin-bottom: 16px;">
<div style="height: 400px;" ref="chartRef"></div>
</a-card>
<!-- 最新市场价格表 -->
<a-card title="最新市场价格表">
<a-table :columns="priceColumns" :data-source="priceData" :pagination="pagination" row-key="id">
<template #bodyCell:price="{ record }">
<span style="font-weight: bold;">{{ record.price }}</span>
</template>
<template #bodyCell:change="{ record }">
<span :class="record.change > 0 ? 'text-danger' : 'text-success'">
{{ record.change > 0 ? '+' : '' }}{{ record.change }}%
</span>
</template>
<template #bodyCell:date="{ record }">
{{ dayjs(record.date).format('YYYY-MM-DD') }}
</template>
</a-table>
</a-card>
<!-- 市场分析报告 -->
<a-card title="市场分析报告" style="margin-top: 16px;">
<div class="report-content">
<h3>近期市场行情分析</h3>
<p>根据最新数据显示近期牛肉价格呈现{{ overallTrend }}趋势主要受以下因素影响</p>
<ol>
<li>市场供需关系变化</li>
<li>养殖成本波动</li>
<li>季节性需求变化</li>
<li>政策因素影响</li>
</ol>
<p>预计未来一段时间内价格将保持{{ futureTrend }}态势建议养殖户和相关企业密切关注市场动态合理安排生产和销售计划</p>
<div class="content-layout">
<!-- 左侧内容 -->
<div class="left-content">
<!-- 本市价格信息卡片 -->
<a-card class="price-info-card">
<template #title>
<div class="card-title">
<span>本市{{ productTypeText }}价格</span>
<span class="price-date">2025-01-01</span>
</div>
</template>
<div class="price-info">
<div class="current-price">
<span class="price-value">{{ currentPrice }}</span>
<span class="price-unit">/公斤</span>
</div>
<div class="price-change">
<span class="change-value">+0.30</span>
<span class="change-percent">+1.32%</span>
</div>
</div>
</a-card>
<!-- 价格走势图表 -->
<a-card class="chart-card" title="价格走势">
<div ref="priceChartRef" class="chart-container"></div>
</a-card>
</div>
</a-card>
<!-- 右侧内容 -->
<div class="right-content">
<!-- 各省价格对比图表 -->
<a-card class="province-chart-card" title="各省价格对比">
<div ref="provinceChartRef" class="province-chart-container"></div>
</a-card>
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import axios from 'axios'
import dayjs from 'dayjs'
import { ref, computed, onMounted } from 'vue'
import * as echarts from 'echarts'
// 选择的产品和地区
const selectedProduct = ref('beef')
const selectedRegion = ref('national')
// 当前选中的产品类型
const activeProductTab = ref('beef')
// 分页配置
const pagination = reactive({
current: 1,
pageSize: 10,
total: 0
// 产品类型文本映射
const productTypeText = computed(() => {
const map = {
beef: '育肥牛',
dairy: '繁殖牛',
milk: '奶牛'
}
return map[activeProductTab.value] || '育肥牛'
})
// 当前价格
const currentPrice = ref('--')
// 图表引用
const chartRef = ref(null)
const priceChartRef = ref(null)
const provinceChartRef = ref(null)
// 整体趋势和未来趋势
const overallTrend = ref('稳中有升')
const futureTrend = ref('相对稳定')
// 价格数据
const priceData = ref([
{
id: '1',
productType: '牛肉',
spec: '一等品',
unit: '元/公斤',
price: 78.50,
change: 1.2,
region: '全国均价',
date: '2024-04-10T00:00:00Z'
},
{
id: '2',
productType: '牛肉',
spec: '二等品',
unit: '元/公斤',
price: 68.20,
change: 0.8,
region: '全国均价',
date: '2024-04-10T00:00:00Z'
},
{
id: '3',
productType: '牛奶',
spec: '生鲜乳',
unit: '元/公斤',
price: 4.30,
change: -0.5,
region: '全国均价',
date: '2024-04-10T00:00:00Z'
},
{
id: '4',
productType: '牛犊',
spec: '优良品种',
unit: '元/头',
price: 5800,
change: 2.5,
region: '全国均价',
date: '2024-04-10T00:00:00Z'
},
{
id: '5',
productType: '饲料',
spec: '精饲料',
unit: '元/吨',
price: 3200,
change: 1.8,
region: '全国均价',
date: '2024-04-10T00:00:00Z'
},
{
id: '6',
productType: '牛肉',
spec: '一等品',
unit: '元/公斤',
price: 82.30,
change: 1.5,
region: '北方地区',
date: '2024-04-10T00:00:00Z'
},
{
id: '7',
productType: '牛肉',
spec: '一等品',
unit: '元/公斤',
price: 76.80,
change: 1.0,
region: '南方地区',
date: '2024-04-10T00:00:00Z'
},
{
id: '8',
productType: '牛肉',
spec: '一等品',
unit: '元/公斤',
price: 85.50,
change: 2.0,
region: '东部地区',
date: '2024-04-10T00:00:00Z'
},
{
id: '9',
productType: '牛肉',
spec: '一等品',
unit: '元/公斤',
price: 72.10,
change: 0.5,
region: '西部地区',
date: '2024-04-10T00:00:00Z'
},
{
id: '10',
productType: '饲料',
spec: '粗饲料',
unit: '元/吨',
price: 1800,
change: 0.2,
region: '全国均价',
date: '2024-04-10T00:00:00Z'
}
])
// 价格表格列定义
const priceColumns = [
{
title: '产品类型',
dataIndex: 'productType',
key: 'productType',
width: 120
},
{
title: '规格',
dataIndex: 'spec',
key: 'spec',
width: 100
},
{
title: '单位',
dataIndex: 'unit',
key: 'unit',
width: 80
},
{
title: '价格',
dataIndex: 'price',
key: 'price',
width: 100
},
{
title: '涨跌幅',
dataIndex: 'change',
key: 'change',
width: 100
},
{
title: '地区',
dataIndex: 'region',
key: 'region',
width: 120
},
{
title: '数据日期',
dataIndex: 'date',
key: 'date',
width: 150
}
]
// 查询按钮点击事件
const handleSearch = () => {
console.log('查询条件:', { selectedProduct: selectedProduct.value, selectedRegion: selectedRegion.value })
// 这里应该有实际的查询逻辑
}
// 导出按钮点击事件
const handleExport = () => {
console.log('导出数据')
// 这里应该有实际的导出逻辑
}
// 初始化图表
const initChart = () => {
if (!chartRef.value) return
// 初始化价格走势图表
const initPriceChart = () => {
if (!priceChartRef.value) return
const chart = echarts.init(chartRef.value)
const chart = echarts.init(priceChartRef.value)
// 模拟历史价格数据
const historicalData = [
{ date: '2024-03-11', beef: 75.2, milk: 4.4, feed: 3150 },
{ date: '2024-03-18', beef: 76.5, milk: 4.4, feed: 3160 },
{ date: '2024-03-25', beef: 77.1, milk: 4.3, feed: 3180 },
{ date: '2024-04-01', beef: 77.8, milk: 4.3, feed: 3190 },
{ date: '2024-04-08', beef: 78.2, milk: 4.3, feed: 3200 },
{ date: '2024-04-15', beef: 78.5, milk: 4.3, feed: 3200 }
]
const dates = ['2024-12-19', '2024-12-20', '2024-12-21', '2024-12-22', '2024-12-23',
'2024-12-24', '2024-12-25', '2024-12-26', '2024-12-27', '2024-12-28',
'2024-12-29', '2024-12-30', '2024-12-31', '2025-01-01']
const prices = [23.10, 20.65, 24.90, 21.78, 22.15, 24.30, 23.20, 21.60, 20.35, 24.05, 23.45, 20.95, 22.50, 21.80]
const option = {
title: {
text: '产品价格走势最近6周',
left: 'center'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross'
}
},
legend: {
data: ['牛肉(元/公斤)', '牛奶(元/公斤)', '饲料(元/吨)'],
bottom: 10
},
grid: {
left: '3%',
right: '4%',
bottom: '15%',
top: '15%',
bottom: '3%',
top: '8%',
containLabel: true
},
xAxis: [
{
type: 'category',
boundaryGap: false,
data: historicalData.map(item => item.date)
xAxis: {
type: 'category',
boundaryGap: false,
data: dates,
axisLabel: {
rotate: 45,
fontSize: 10
}
],
yAxis: [
{
type: 'value',
name: '价格',
position: 'left'
}
],
},
yAxis: {
type: 'value',
min: 0,
max: 25,
interval: 5
},
series: [
{
name: '牛肉(元/公斤)',
type: 'line',
data: historicalData.map(item => item.beef),
data: prices,
symbol: 'circle',
symbolSize: 8,
symbolSize: 6,
itemStyle: {
color: '#1890ff'
},
lineStyle: {
width: 3
}
},
{
name: '牛奶(元/公斤)',
type: 'line',
data: historicalData.map(item => item.milk),
symbol: 'circle',
symbolSize: 8,
itemStyle: {
color: '#52c41a'
width: 2
},
lineStyle: {
width: 3
label: {
show: true,
position: 'top',
formatter: '{c}'
}
},
}
]
}
chart.setOption(option)
// 响应式调整
window.addEventListener('resize', () => {
chart.resize()
})
}
// 初始化各省价格对比图表
const initProvinceChart = () => {
if (!provinceChartRef.value) return
const chart = echarts.init(provinceChartRef.value)
// 模拟各省价格数据
const provinces = ['上海市', '云南省', '内蒙古自治区', '北京市', '吉林省', '四川省', '天津市', '宁夏回族自治区', '安徽省', '山东省', '山西省', '广东省', '广西壮族自治区', '新疆维吾尔自治区', '江苏省', '江西省', '河北省', '河南省', '浙江省', '海南省', '湖北省', '湖南省', '甘肃省', '福建省', '贵州省', '辽宁省', '重庆市', '陕西省', '青海省', '黑龙江省']
const prices = [36, 34.85, 34.85, 35.7, 33.26, 34.9, 35.4, 36.51, 33.5, 35.4, 35.8, 35.3, 32.6, 35.8, 34.1, 35.2, 33.2, 36, 35, 35.4, 32, 33.2, 34.8, 32.86, 32.7, 33, 32.8, 33.4, 33, 35.4]
const option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
top: '3%',
containLabel: true
},
xAxis: {
type: 'value',
min: 0,
max: 40,
interval: 10
},
yAxis: {
type: 'category',
data: provinces,
axisLabel: {
fontSize: 10
}
},
series: [
{
name: '饲料(元/吨)',
type: 'line',
data: historicalData.map(item => item.feed),
symbol: 'circle',
symbolSize: 8,
type: 'bar',
data: prices,
itemStyle: {
color: '#fa8c16'
color: '#4e7bff'
},
lineStyle: {
width: 3
label: {
show: true,
position: 'right',
formatter: '{c}'
}
}
]
@@ -357,40 +208,102 @@ const initChart = () => {
// 组件挂载时初始化图表
onMounted(() => {
initChart()
// 设置当前价格
currentPrice.value = '23.10'
// 初始化图表
setTimeout(() => {
initPriceChart()
initProvinceChart()
}, 100)
})
</script>
<style scoped>
.text-danger {
color: #f5222d;
}
.text-success {
color: #52c41a;
}
.report-content {
padding: 16px;
}
.report-content h3 {
margin-bottom: 16px;
color: #1890ff;
}
.report-content p {
margin-bottom: 12px;
line-height: 1.8;
}
.report-content ol {
margin-left: 24px;
margin-bottom: 16px;
}
.report-content li {
margin-bottom: 8px;
line-height: 1.6;
}
.market-price-container {
padding: 20px;
}
.product-tabs {
margin-bottom: 20px;
}
.content-layout {
display: flex;
gap: 20px;
}
.left-content {
flex: 1;
display: flex;
flex-direction: column;
gap: 20px;
}
.right-content {
flex: 1;
}
.price-info-card {
margin-bottom: 0;
}
.card-title {
display: flex;
justify-content: space-between;
align-items: center;
}
.price-date {
font-size: 14px;
color: #999;
}
.price-info {
display: flex;
align-items: flex-end;
gap: 20px;
padding: 10px 0;
}
.current-price {
display: flex;
align-items: baseline;
}
.price-value {
font-size: 32px;
font-weight: bold;
color: #1890ff;
line-height: 1;
}
.price-unit {
font-size: 14px;
color: #666;
margin-left: 5px;
}
.price-change {
display: flex;
flex-direction: column;
}
.change-value {
font-size: 16px;
color: #52c41a;
}
.change-percent {
font-size: 14px;
color: #52c41a;
}
.chart-container {
height: 300px;
}
.province-chart-container {
height: 600px;
}
</style>

View File

@@ -1,7 +1,11 @@
<template>
<div>
<h1>人员管理</h1>
<a-card style="margin-bottom: 16px;">
<!-- 添加router-view来渲染子路由内容 -->
<router-view />
<a-card style="margin-bottom: 16px;" v-if="!$route.path.includes('/personnel/')">
<a-row gutter={24}>
<a-col :span="8">
<a-button type="primary" @click="handleAddUser">添加人员</a-button>
@@ -22,7 +26,7 @@
</a-card>
<!-- 人员列表 -->
<a-card title="人员列表">
<a-card title="人员列表" v-if="!$route.path.includes('/personnel/')">
<a-table :columns="userColumns" :data-source="userList" :pagination="pagination" row-key="id">
<template #bodyCell:status="{ record }">
<a-badge :status="record.status === 'active' ? 'success' : 'default'" :text="record.status === 'active' ? '在职' : '离职'" />

View File

@@ -1,371 +0,0 @@
<template>
<div>
<h1>监管仪表板</h1>
<!-- 数据统计卡片 -->
<a-row gutter={24}>
<a-col :span="6">
<a-card class="stat-card" :body-style="{ padding: '20px' }">
<div class="stat-content">
<div class="stat-number">{{ stats.entityCount }}</div>
<div class="stat-label">监管实体总数</div>
</div>
</a-card>
</a-col>
<a-col :span="6">
<a-card class="stat-card" :body-style="{ padding: '20px' }">
<div class="stat-content">
<div class="stat-number">{{ stats.inspectionCount }}</div>
<div class="stat-label">检查总次数</div>
</div>
</a-card>
</a-col>
<a-col :span="6">
<a-card class="stat-card" :body-style="{ padding: '20px' }">
<div class="stat-content">
<div class="stat-number">{{ stats.pendingCount }}</div>
<div class="stat-label">待处理任务</div>
</div>
</a-card>
</a-col>
<a-col :span="6">
<a-card class="stat-card" :body-style="{ padding: '20px' }">
<div class="stat-content">
<div class="stat-number">{{ stats.abnormalCount }}</div>
<div class="stat-label">异常情况数</div>
</div>
</a-card>
</a-col>
</a-row>
<!-- 图表区域 -->
<a-row gutter={24} style="margin-top: 24px;">
<a-col :span="12">
<a-card title="监管趋势图" :body-style="{ padding: 0 }">
<div style="height: 300px; padding: 10px;">
<div id="trend-chart" style="width: 100%; height: 100%;"></div>
</div>
</a-card>
</a-col>
<a-col :span="12">
<a-card title="监管类型分布" :body-style="{ padding: 0 }">
<div style="height: 300px; padding: 10px;">
<div id="distribution-chart" style="width: 100%; height: 100%;"></div>
</div>
</a-card>
</a-col>
</a-row>
<!-- 最新监管任务 -->
<a-card title="最新监管任务" style="margin-top: 24px;">
<a-table :columns="taskColumns" :data-source="recentTasks" pagination={false}>
<template #bodyCell:status="{ record }">
<a-tag :color="getStatusColor(record.status)">
{{ getStatusText(record.status) }}
</a-tag>
</template>
<template #bodyCell:action="{ record }">
<a-button type="link" @click="viewTaskDetail(record.id)">查看详情</a-button>
</template>
</a-table>
</a-card>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { message } from 'antd'
import axios from 'axios'
import * as echarts from 'echarts'
import { getSupervisionStats } from '@/mock'
const stats = ref({
entityCount: 0,
inspectionCount: 0,
pendingCount: 0,
abnormalCount: 0
})
const recentTasks = ref([])
let trendChart = null
let distributionChart = null
// 任务表格列定义
const taskColumns = [
{
title: '任务ID',
dataIndex: 'id',
key: 'id'
},
{
title: '任务名称',
dataIndex: 'name',
key: 'name'
},
{
title: '监管对象',
dataIndex: 'target',
key: 'target'
},
{
title: '负责人',
dataIndex: 'manager',
key: 'manager'
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
slots: { customRender: 'status' }
},
{
title: '创建时间',
dataIndex: 'create_time',
key: 'create_time'
},
{
title: '操作',
key: 'action',
slots: { customRender: 'action' }
}
]
// 根据状态获取标签颜色
const getStatusColor = (status) => {
const colorMap = {
pending: 'orange',
in_progress: 'blue',
completed: 'green',
abnormal: 'red',
canceled: 'default'
}
return colorMap[status] || 'default'
}
// 根据状态获取显示文本
const getStatusText = (status) => {
const textMap = {
pending: '待处理',
in_progress: '处理中',
completed: '已完成',
abnormal: '异常',
canceled: '已取消'
}
return textMap[status] || status
}
// 获取监管统计数据
const fetchStats = async () => {
try {
// 尝试从API获取数据
const response = await axios.get('/api/supervision/stats')
if (response.data.code === 200) {
const data = response.data.data
stats.value.entityCount = data.entityCount || 0
stats.value.inspectionCount = data.inspectionCount || 0
}
} catch (error) {
console.error('获取监管统计数据失败,使用模拟数据:', error)
// 使用模拟数据
const mockResponse = await getSupervisionStats()
if (mockResponse.code === 200) {
const data = mockResponse.data
stats.value.entityCount = data.entityCount || 0
stats.value.inspectionCount = data.inspectionCount || 0
}
}
// 设置其他模拟数据
stats.value.pendingCount = Math.floor(Math.random() * 20) + 5
stats.value.abnormalCount = Math.floor(Math.random() * 10)
}
// 获取最新任务数据
const fetchRecentTasks = async () => {
try {
// 这里应该从API获取数据
// 由于没有实际API使用模拟数据
recentTasks.value = [
{ id: 1, name: '企业安全检查', target: '某食品加工厂', manager: '张三', status: 'pending', create_time: '2024-01-10 09:00:00' },
{ id: 2, name: '环境监测', target: '某化工厂', manager: '李四', status: 'in_progress', create_time: '2024-01-09 14:30:00' },
{ id: 3, name: '疫情防控检查', target: '某商场', manager: '王五', status: 'completed', create_time: '2024-01-08 11:20:00' },
{ id: 4, name: '消防安全检查', target: '某酒店', manager: '赵六', status: 'abnormal', create_time: '2024-01-07 16:40:00' },
{ id: 5, name: '质量检查', target: '某药品生产企业', manager: '钱七', status: 'pending', create_time: '2024-01-06 10:15:00' }
]
} catch (error) {
console.error('获取任务数据失败:', error)
message.error('获取任务数据失败,请稍后重试')
}
}
// 初始化图表
const initCharts = () => {
// 趋势图
const trendChartDom = document.getElementById('trend-chart')
if (trendChartDom) {
trendChart = echarts.init(trendChartDom)
const trendOption = {
tooltip: {
trigger: 'axis'
},
legend: {
data: ['检查次数', '异常情况']
},
xAxis: {
type: 'category',
data: ['1月', '2月', '3月', '4月', '5月', '6月']
},
yAxis: {
type: 'value'
},
series: [
{
name: '检查次数',
type: 'line',
data: [12, 19, 3, 5, 2, 3],
smooth: true
},
{
name: '异常情况',
type: 'line',
data: [5, 3, 2, 4, 1, 2],
smooth: true
}
]
}
trendChart.setOption(trendOption)
}
// 分布饼图
const distributionChartDom = document.getElementById('distribution-chart')
if (distributionChartDom) {
distributionChart = echarts.init(distributionChartDom)
const distributionOption = {
tooltip: {
trigger: 'item'
},
legend: {
top: '5%',
left: 'center'
},
series: [
{
name: '监管类型',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: '18',
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: [
{
value: 30,
name: '安全检查'
},
{
value: 25,
name: '环境监测'
},
{
value: 20,
name: '疫情防控'
},
{
value: 15,
name: '质量检查'
},
{
value: 10,
name: '其他'
}
]
}
]
}
distributionChart.setOption(distributionOption)
}
}
// 响应窗口大小变化
const handleResize = () => {
if (trendChart) {
trendChart.resize()
}
if (distributionChart) {
distributionChart.resize()
}
}
// 查看任务详情
const viewTaskDetail = (taskId) => {
message.info(`查看任务ID: ${taskId} 的详情`)
// 这里可以跳转到任务详情页面
// router.push(`/supervision/task/${taskId}`)
}
// 组件挂载
onMounted(() => {
fetchStats()
fetchRecentTasks()
setTimeout(() => {
initCharts()
}, 100)
window.addEventListener('resize', handleResize)
})
// 组件卸载
onUnmounted(() => {
window.removeEventListener('resize', handleResize)
if (trendChart) {
trendChart.dispose()
}
if (distributionChart) {
distributionChart.dispose()
}
})
</script>
<style scoped>
.stat-card {
height: 100%;
transition: all 0.3s ease;
}
.stat-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
transform: translateY(-2px);
}
.stat-content {
display: flex;
flex-direction: column;
align-items: center;
}
.stat-number {
font-size: 28px;
font-weight: bold;
color: #1890ff;
margin-bottom: 8px;
}
.stat-label {
font-size: 14px;
color: #666;
}
</style>

View File

@@ -1,366 +0,0 @@
<template>
<div>
<h1>用户管理</h1>
<!-- 工具栏 -->
<a-card style="margin-bottom: 16px;">
<a-row gutter={16}>
<a-col :span="8">
<a-input
v-model:value="searchText"
placeholder="请输入用户名或ID搜索"
allow-clear
style="width: 100%;"
/>
</a-col>
<a-col :span="16" style="text-align: right;">
<a-button type="primary" @click="handleAddUser">
<user-add-outlined /> 添加用户
</a-button>
</a-col>
</a-row>
</a-card>
<!-- 用户表格 -->
<a-card>
<a-table
:columns="columns"
:data-source="users"
:pagination="pagination"
row-key="id"
@change="handleTableChange"
>
<template #bodyCell:actions="{ record }">
<a-space>
<a-button type="link" @click="handleEditUser(record)">编辑</a-button>
<a-button type="link" danger @click="handleDeleteUser(record.id)">删除</a-button>
</a-space>
</template>
<template #bodyCell:status="{ record }">
<a-tag :color="record.status === 1 ? 'green' : 'red'">
{{ record.status === 1 ? '启用' : '禁用' }}
</a-tag>
</template>
</a-table>
</a-card>
<!-- 添加/编辑用户弹窗 -->
<a-modal
v-model:open="showModal"
:title="modalTitle"
footer=""
width="600px"
>
<a-form
ref="formRef"
:model="formState"
:rules="formRules"
layout="vertical"
@finish="handleSubmit"
>
<a-form-item label="用户名" name="username">
<a-input v-model:value="formState.username" placeholder="请输入用户名" />
</a-form-item>
<a-form-item label="真实姓名" name="real_name">
<a-input v-model:value="formState.real_name" placeholder="请输入真实姓名" />
</a-form-item>
<a-form-item label="手机号" name="phone">
<a-input v-model:value="formState.phone" placeholder="请输入手机号" />
</a-form-item>
<a-form-item label="邮箱" name="email">
<a-input v-model:value="formState.email" placeholder="请输入邮箱" />
</a-form-item>
<a-form-item
v-if="isAddUser"
label="密码"
name="password"
>
<a-input-password v-model:value="formState.password" placeholder="请输入密码" />
</a-form-item>
<a-form-item label="用户角色" name="role">
<a-select v-model:value="formState.role" placeholder="请选择用户角色">
<a-select-option value="admin">管理员</a-select-option>
<a-select-option value="user">普通用户</a-select-option>
<a-select-option value="guest">访客</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="状态" name="status">
<a-switch v-model:checked="formState.status" checked-children="启用" un-checked-children="禁用" />
</a-form-item>
<a-form-item>
<a-space>
<a-button @click="handleCancel">取消</a-button>
<a-button type="primary" html-type="submit">确认</a-button>
</a-space>
</a-form-item>
</a-form>
</a-modal>
</div>
</template>
<script setup>
import { ref, reactive, computed } from 'vue'
import { message, Modal } from 'antd'
import { UserAddOutlined } from '@ant-design/icons-vue'
import axios from 'axios'
// 搜索文本
const searchText = ref('')
// 用户数据
const users = ref([])
// 分页配置
const pagination = {
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: true,
showTotal: (total) => `${total} 条数据`
}
// 弹窗状态
const showModal = ref(false)
const isAddUser = ref(true)
const modalTitle = computed(() => isAddUser.value ? '添加用户' : '编辑用户')
const formRef = ref()
// 表单状态
const formState = reactive({
id: '',
username: '',
real_name: '',
phone: '',
email: '',
password: '',
role: 'user',
status: true
})
// 表单验证规则
const formRules = {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 3, max: 20, message: '用户名长度应在3到20个字符之间', trigger: 'blur' }
],
real_name: [
{ required: true, message: '请输入真实姓名', trigger: 'blur' }
],
phone: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号格式', trigger: 'blur' }
],
email: [
{ required: true, message: '请输入邮箱', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱格式', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, max: 32, message: '密码长度应在6到32个字符之间', trigger: 'blur' }
],
role: [
{ required: true, message: '请选择用户角色', trigger: 'change' }
]
}
// 表格列定义
const columns = [
{
title: '用户ID',
dataIndex: 'id',
key: 'id'
},
{
title: '用户名',
dataIndex: 'username',
key: 'username'
},
{
title: '真实姓名',
dataIndex: 'real_name',
key: 'real_name'
},
{
title: '手机号',
dataIndex: 'phone',
key: 'phone'
},
{
title: '邮箱',
dataIndex: 'email',
key: 'email'
},
{
title: '角色',
dataIndex: 'role',
key: 'role'
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
slots: { customRender: 'status' }
},
{
title: '创建时间',
dataIndex: 'created_at',
key: 'created_at'
},
{
title: '操作',
key: 'actions',
slots: { customRender: 'actions' }
}
]
// 获取用户列表
const fetchUsers = async (page = 1, pageSize = 10, keyword = '') => {
try {
// 这里应该从API获取用户数据
// 由于没有实际API使用模拟数据
// 模拟分页数据
const mockUsers = [
{ id: 1, username: 'admin', real_name: '管理员', phone: '13800138000', email: 'admin@example.com', role: 'admin', status: 1, created_at: '2024-01-01 10:00:00' },
{ id: 2, username: 'user1', real_name: '用户一', phone: '13800138001', email: 'user1@example.com', role: 'user', status: 1, created_at: '2024-01-02 11:00:00' },
{ id: 3, username: 'user2', real_name: '用户二', phone: '13800138002', email: 'user2@example.com', role: 'user', status: 0, created_at: '2024-01-03 12:00:00' },
{ id: 4, username: 'user3', real_name: '用户三', phone: '13800138003', email: 'user3@example.com', role: 'guest', status: 1, created_at: '2024-01-04 13:00:00' },
{ id: 5, username: 'user4', real_name: '用户四', phone: '13800138004', email: 'user4@example.com', role: 'user', status: 1, created_at: '2024-01-05 14:00:00' }
]
// 模拟搜索
const filteredUsers = keyword ?
mockUsers.filter(user =>
user.username.includes(keyword) ||
user.real_name.includes(keyword) ||
user.id.toString().includes(keyword)
) :
mockUsers
// 模拟分页
const start = (page - 1) * pageSize
const end = start + pageSize
const paginatedUsers = filteredUsers.slice(start, end)
users.value = paginatedUsers
pagination.total = filteredUsers.length
pagination.current = page
pagination.pageSize = pageSize
} catch (error) {
console.error('获取用户列表失败:', error)
message.error('获取用户列表失败,请稍后重试')
}
}
// 表格变化处理
const handleTableChange = (paginationObj) => {
pagination.current = paginationObj.current
pagination.pageSize = paginationObj.pageSize
fetchUsers(pagination.current, pagination.pageSize, searchText.value)
}
// 搜索用户
const handleSearch = () => {
fetchUsers(1, pagination.pageSize, searchText.value)
}
// 添加用户
const handleAddUser = () => {
isAddUser.value = true
// 重置表单
formState.id = ''
formState.username = ''
formState.real_name = ''
formState.phone = ''
formState.email = ''
formState.password = ''
formState.role = 'user'
formState.status = true
showModal.value = true
}
// 编辑用户
const handleEditUser = (record) => {
isAddUser.value = false
// 填充表单数据
formState.id = record.id
formState.username = record.username
formState.real_name = record.real_name
formState.phone = record.phone
formState.email = record.email
formState.role = record.role
formState.status = record.status === 1
showModal.value = true
}
// 删除用户
const handleDeleteUser = (userId) => {
Modal.confirm({
title: '确认删除',
content: '确定要删除这个用户吗?',
okText: '确定',
cancelText: '取消',
onOk: async () => {
try {
// 这里应该调用API删除用户
// 由于没有实际API模拟删除
console.log('删除用户:', userId)
message.success('用户删除成功')
// 重新获取用户列表
fetchUsers(pagination.current, pagination.pageSize, searchText.value)
} catch (error) {
console.error('删除用户失败:', error)
message.error('删除用户失败,请稍后重试')
}
}
})
}
// 提交表单
const handleSubmit = async () => {
try {
await formRef.value.validate()
// 准备提交数据
const submitData = {
username: formState.username,
real_name: formState.real_name,
phone: formState.phone,
email: formState.email,
role: formState.role,
status: formState.status ? 1 : 0
}
// 如果是添加用户,添加密码字段
if (isAddUser.value) {
submitData.password = formState.password
}
// 这里应该调用API提交数据
// 由于没有实际API模拟提交
console.log(isAddUser.value ? '添加用户:' : '编辑用户:', submitData)
// 显示成功消息
message.success(isAddUser.value ? '用户添加成功' : '用户编辑成功')
// 关闭弹窗
showModal.value = false
// 重新获取用户列表
fetchUsers(pagination.current, pagination.pageSize, searchText.value)
} catch (error) {
console.error(isAddUser.value ? '添加用户失败:' : '编辑用户失败:', error)
message.error(isAddUser.value ? '添加用户失败,请稍后重试' : '编辑用户失败,请稍后重试')
}
}
// 取消操作
const handleCancel = () => {
showModal.value = false
formRef.value.resetFields()
}
// 监听搜索文本变化
searchText.value = ''
fetchUsers()
</script>
<style scoped>
/* 用户管理页面样式 */
</style>

View File

@@ -1,874 +0,0 @@
<template>
<div>
<h1>可视化分析</h1>
<!-- 图表选择器 -->
<div style="margin-bottom: 16px;">
<a-select v-model:value="selectedChart" placeholder="选择图表类型" style="width: 200px;" @change="changeChart">
<a-select-option value="overview">综合概览</a-select-option>
<a-select-option value="supervision">监管数据</a-select-option>
<a-select-option value="epidemic">疫情数据</a-select-option>
<a-select-option value="approval">审批数据</a-select-option>
</a-select>
</div>
<!-- 综合概览 -->
<div v-if="selectedChart === 'overview'">
<a-row gutter={24}>
<a-col :span="16">
<a-card title="数据趋势总览" :body-style="{ padding: 0 }">
<div style="height: 400px; padding: 10px;">
<div id="overview-chart" style="width: 100%; height: 100%;"></div>
</div>
</a-card>
</a-col>
<a-col :span="8">
<a-card title="数据分类占比" :body-style="{ padding: 0 }">
<div style="height: 400px; padding: 10px;">
<div id="category-chart" style="width: 100%; height: 100%;"></div>
</div>
</a-card>
</a-col>
</a-row>
<a-row gutter={24} style="margin-top: 24px;">
<a-col :span="12">
<a-card title="月度活跃度" :body-style="{ padding: 0 }">
<div style="height: 300px; padding: 10px;">
<div id="activity-chart" style="width: 100%; height: 100%;"></div>
</div>
</a-card>
</a-col>
<a-col :span="12">
<a-card title="区域分布图" :body-style="{ padding: 0 }">
<div style="height: 300px; padding: 10px;">
<div id="region-chart" style="width: 100%; height: 100%;"></div>
</div>
</a-card>
</a-col>
</a-row>
</div>
<!-- 监管数据可视化 -->
<div v-if="selectedChart === 'supervision'">
<a-row gutter={24}>
<a-col :span="12">
<a-card title="监管类型分布" :body-style="{ padding: 0 }">
<div style="height: 300px; padding: 10px;">
<div id="supervision-type-chart" style="width: 100%; height: 100%;"></div>
</div>
</a-card>
</a-col>
<a-col :span="12">
<a-card title="异常情况趋势" :body-style="{ padding: 0 }">
<div style="height: 300px; padding: 10px;">
<div id="abnormal-trend-chart" style="width: 100%; height: 100%;"></div>
</div>
</a-card>
</a-col>
</a-row>
<a-row gutter={24} style="margin-top: 24px;">
<a-col :span="24">
<a-card title="监管覆盖率" :body-style="{ padding: 0 }">
<div style="height: 300px; padding: 10px;">
<div id="coverage-chart" style="width: 100%; height: 100%;"></div>
</div>
</a-card>
</a-col>
</a-row>
</div>
<!-- 疫情数据可视化 -->
<div v-if="selectedChart === 'epidemic'">
<a-row gutter={24}>
<a-col :span="16">
<a-card title="疫情发展趋势" :body-style="{ padding: 0 }">
<div style="height: 400px; padding: 10px;">
<div id="epidemic-trend-chart" style="width: 100%; height: 100%;"></div>
</div>
</a-card>
</a-col>
<a-col :span="8">
<a-card title="疫苗接种进度" :body-style="{ padding: 0 }">
<div style="height: 400px; padding: 10px;">
<div id="vaccine-progress-chart" style="width: 100%; height: 100%;"></div>
</div>
</a-card>
</a-col>
</a-row>
<a-row gutter={24} style="margin-top: 24px;">
<a-col :span="12">
<a-card title="区域疫情分布" :body-style="{ padding: 0 }">
<div style="height: 300px; padding: 10px;">
<div id="epidemic-region-chart" style="width: 100%; height: 100%;"></div>
</div>
</a-card>
</a-col>
<a-col :span="12">
<a-card title="检测量统计" :body-style="{ padding: 0 }">
<div style="height: 300px; padding: 10px;">
<div id="testing-stats-chart" style="width: 100%; height: 100%;"></div>
</div>
</a-card>
</a-col>
</a-row>
</div>
<!-- 审批数据可视化 -->
<div v-if="selectedChart === 'approval'">
<a-row gutter={24}>
<a-col :span="12">
<a-card title="审批类型分布" :body-style="{ padding: 0 }">
<div style="height: 300px; padding: 10px;">
<div id="approval-type-chart" style="width: 100%; height: 100%;"></div>
</div>
</a-card>
</a-col>
<a-col :span="12">
<a-card title="审批状态分布" :body-style="{ padding: 0 }">
<div style="height: 300px; padding: 10px;">
<div id="approval-status-chart" style="width: 100%; height: 100%;"></div>
</div>
</a-card>
</a-col>
</a-row>
<a-row gutter={24} style="margin-top: 24px;">
<a-col :span="24">
<a-card title="审批处理时效" :body-style="{ padding: 0 }">
<div style="height: 300px; padding: 10px;">
<div id="approval-timeline-chart" style="width: 100%; height: 100%;"></div>
</div>
</a-card>
</a-col>
</a-row>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, watch } from 'vue'
import axios from 'axios'
import * as echarts from 'echarts'
import { getVisualizationData } from '@/mock'
const selectedChart = ref('overview')
const charts = ref({})
const visualizationData = ref(null)
// 图表初始化函数
const initCharts = () => {
// 确保在切换图表类型时销毁之前的图表实例
Object.values(charts.value).forEach(chart => {
if (chart && chart.dispose) {
chart.dispose()
}
})
charts.value = {}
if (selectedChart.value === 'overview') {
initOverviewCharts()
} else if (selectedChart.value === 'supervision') {
initSupervisionCharts()
} else if (selectedChart.value === 'epidemic') {
initEpidemicCharts()
} else if (selectedChart.value === 'approval') {
initApprovalCharts()
}
}
// 初始化综合概览图表
const initOverviewCharts = () => {
// 数据趋势总览
const overviewChartDom = document.getElementById('overview-chart')
if (overviewChartDom) {
charts.value.overviewChart = echarts.init(overviewChartDom)
const overviewOption = {
tooltip: {
trigger: 'axis'
},
legend: {
data: ['监管事件', '审批数量', '疫情相关数据']
},
xAxis: {
type: 'category',
data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
},
yAxis: {
type: 'value'
},
series: [
{
name: '监管事件',
type: 'line',
data: [120, 132, 101, 134, 90, 230, 210, 180, 190, 230, 210, 250],
smooth: true
},
{
name: '审批数量',
type: 'line',
data: [220, 182, 191, 234, 290, 330, 310, 280, 290, 330, 310, 350],
smooth: true
},
{
name: '疫情相关数据',
type: 'line',
data: [150, 232, 201, 154, 190, 330, 410, 380, 390, 430, 410, 450],
smooth: true
}
]
}
charts.value.overviewChart.setOption(overviewOption)
}
// 数据分类占比
const categoryChartDom = document.getElementById('category-chart')
if (categoryChartDom) {
charts.value.categoryChart = echarts.init(categoryChartDom)
const categoryOption = {
tooltip: {
trigger: 'item'
},
legend: {
orient: 'vertical',
left: 'left'
},
series: [
{
name: '数据分类',
type: 'pie',
radius: '65%',
data: [
{
value: 30,
name: '监管数据'
},
{
value: 25,
name: '审批数据'
},
{
value: 25,
name: '疫情数据'
},
{
value: 10,
name: '用户数据'
},
{
value: 10,
name: '其他数据'
}
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
}
charts.value.categoryChart.setOption(categoryOption)
}
// 月度活跃度
const activityChartDom = document.getElementById('activity-chart')
if (activityChartDom) {
charts.value.activityChart = echarts.init(activityChartDom)
const activityOption = {
tooltip: {
trigger: 'axis'
},
xAxis: {
type: 'category',
data: ['1月', '2月', '3月', '4月', '5月', '6月']
},
yAxis: {
type: 'value'
},
series: [
{
data: [120, 200, 150, 80, 70, 110],
type: 'bar',
barWidth: '60%'
}
]
}
charts.value.activityChart.setOption(activityOption)
}
// 区域分布图
const regionChartDom = document.getElementById('region-chart')
if (regionChartDom) {
charts.value.regionChart = echarts.init(regionChartDom)
const regionOption = {
tooltip: {
trigger: 'item'
},
radar: {
indicator: [
{ name: '东区', max: 100 },
{ name: '西区', max: 100 },
{ name: '南区', max: 100 },
{ name: '北区', max: 100 },
{ name: '中区', max: 100 }
]
},
series: [
{
type: 'radar',
data: [
{
value: [80, 65, 70, 75, 60],
name: '数据分布'
}
]
}
]
}
charts.value.regionChart.setOption(regionOption)
}
}
// 初始化监管数据图表
const initSupervisionCharts = () => {
// 监管类型分布
const supervisionTypeChartDom = document.getElementById('supervision-type-chart')
if (supervisionTypeChartDom) {
charts.value.supervisionTypeChart = echarts.init(supervisionTypeChartDom)
const supervisionTypeOption = {
tooltip: {
trigger: 'item'
},
legend: {
top: '5%',
left: 'center'
},
series: [
{
name: '监管类型',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: '18',
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: [
{
value: 35,
name: '安全检查'
},
{
value: 25,
name: '质量监管'
},
{
value: 20,
name: '环保监测'
},
{
value: 15,
name: '疫情防控'
},
{
value: 5,
name: '其他'
}
]
}
]
}
charts.value.supervisionTypeChart.setOption(supervisionTypeOption)
}
// 异常情况趋势
const abnormalTrendChartDom = document.getElementById('abnormal-trend-chart')
if (abnormalTrendChartDom) {
charts.value.abnormalTrendChart = echarts.init(abnormalTrendChartDom)
const abnormalTrendOption = {
tooltip: {
trigger: 'axis'
},
xAxis: {
type: 'category',
data: ['1月', '2月', '3月', '4月', '5月', '6月']
},
yAxis: {
type: 'value'
},
series: [
{
data: [12, 19, 15, 10, 8, 12],
type: 'line',
smooth: true,
areaStyle: {}
}
]
}
charts.value.abnormalTrendChart.setOption(abnormalTrendOption)
}
// 监管覆盖率
const coverageChartDom = document.getElementById('coverage-chart')
if (coverageChartDom) {
charts.value.coverageChart = echarts.init(coverageChartDom)
const coverageOption = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
legend: {
data: ['计划覆盖率', '实际覆盖率']
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'value',
boundaryGap: [0, 0.01]
},
yAxis: {
type: 'category',
data: ['企业', '学校', '医院', '商场', '餐饮', '娱乐场所']
},
series: [
{
name: '计划覆盖率',
type: 'bar',
data: [100, 100, 100, 100, 100, 100]
},
{
name: '实际覆盖率',
type: 'bar',
data: [85, 90, 95, 80, 88, 75]
}
]
}
charts.value.coverageChart.setOption(coverageOption)
}
}
// 初始化疫情数据图表
const initEpidemicCharts = () => {
// 疫情发展趋势
const epidemicTrendChartDom = document.getElementById('epidemic-trend-chart')
if (epidemicTrendChartDom) {
charts.value.epidemicTrendChart = echarts.init(epidemicTrendChartDom)
const epidemicTrendOption = {
tooltip: {
trigger: 'axis'
},
legend: {
data: ['确诊病例', '疑似病例', '隔离观察']
},
xAxis: {
type: 'category',
data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
},
yAxis: {
type: 'value'
},
series: [
{
name: '确诊病例',
type: 'line',
data: [120, 132, 101, 134, 90, 230, 210, 180, 190, 230, 210, 250],
smooth: true,
lineStyle: {
color: '#ff4d4f'
}
},
{
name: '疑似病例',
type: 'line',
data: [80, 82, 91, 84, 70, 130, 110, 90, 100, 130, 110, 150],
smooth: true,
lineStyle: {
color: '#fa8c16'
}
},
{
name: '隔离观察',
type: 'line',
data: [150, 172, 161, 184, 160, 280, 260, 240, 250, 280, 260, 300],
smooth: true,
lineStyle: {
color: '#1890ff'
}
}
]
}
charts.value.epidemicTrendChart.setOption(epidemicTrendOption)
}
// 疫苗接种进度
const vaccineProgressChartDom = document.getElementById('vaccine-progress-chart')
if (vaccineProgressChartDom) {
charts.value.vaccineProgressChart = echarts.init(vaccineProgressChartDom)
const vaccineProgressOption = {
tooltip: {
formatter: '{a} <br/>{b} : {c}%'
},
series: [
{
name: '接种进度',
type: 'gauge',
detail: {
formatter: '{value}%'
},
data: [
{
value: 75,
name: '接种率'
}
]
}
]
}
charts.value.vaccineProgressChart.setOption(vaccineProgressOption)
}
// 区域疫情分布
const epidemicRegionChartDom = document.getElementById('epidemic-region-chart')
if (epidemicRegionChartDom) {
charts.value.epidemicRegionChart = echarts.init(epidemicRegionChartDom)
const epidemicRegionOption = {
tooltip: {
trigger: 'item'
},
legend: {
top: '5%',
left: 'center'
},
series: [
{
name: '区域分布',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: '18',
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: [
{
value: 30,
name: '东区'
},
{
value: 25,
name: '西区'
},
{
value: 22,
name: '南区'
},
{
value: 23,
name: '北区'
}
]
}
]
}
charts.value.epidemicRegionChart.setOption(epidemicRegionOption)
}
// 检测量统计
const testingStatsChartDom = document.getElementById('testing-stats-chart')
if (testingStatsChartDom) {
charts.value.testingStatsChart = echarts.init(testingStatsChartDom)
const testingStatsOption = {
tooltip: {
trigger: 'axis'
},
xAxis: {
type: 'category',
data: ['1月', '2月', '3月', '4月', '5月', '6月']
},
yAxis: {
type: 'value'
},
series: [
{
data: [220, 182, 191, 234, 290, 330],
type: 'bar',
barWidth: '60%'
}
]
}
charts.value.testingStatsChart.setOption(testingStatsOption)
}
}
// 初始化审批数据图表
const initApprovalCharts = () => {
// 审批类型分布
const approvalTypeChartDom = document.getElementById('approval-type-chart')
if (approvalTypeChartDom) {
charts.value.approvalTypeChart = echarts.init(approvalTypeChartDom)
const approvalTypeOption = {
tooltip: {
trigger: 'item'
},
legend: {
top: '5%',
left: 'center'
},
series: [
{
name: '审批类型',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: '18',
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: [
{
value: 35,
name: '企业资质'
},
{
value: 30,
name: '许可证'
},
{
value: 20,
name: '项目审批'
},
{
value: 15,
name: '其他'
}
]
}
]
}
charts.value.approvalTypeChart.setOption(approvalTypeOption)
}
// 审批状态分布
const approvalStatusChartDom = document.getElementById('approval-status-chart')
if (approvalStatusChartDom) {
charts.value.approvalStatusChart = echarts.init(approvalStatusChartDom)
const approvalStatusOption = {
tooltip: {
trigger: 'item'
},
legend: {
top: '5%',
left: 'center'
},
series: [
{
name: '审批状态',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: '18',
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: [
{
value: 40,
name: '已通过'
},
{
value: 25,
name: '待审批'
},
{
value: 20,
name: '处理中'
},
{
value: 15,
name: '已拒绝'
}
]
}
]
}
charts.value.approvalStatusChart.setOption(approvalStatusOption)
}
// 审批处理时效
const approvalTimelineChartDom = document.getElementById('approval-timeline-chart')
if (approvalTimelineChartDom) {
charts.value.approvalTimelineChart = echarts.init(approvalTimelineChartDom)
const approvalTimelineOption = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
legend: {
data: ['平均处理时间(天)', '最长处理时间(天)', '最短处理时间(天)']
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: ['企业资质', '许可证', '项目审批', '其他']
},
yAxis: {
type: 'value'
},
series: [
{
name: '平均处理时间(天)',
type: 'bar',
data: [7, 10, 15, 8]
},
{
name: '最长处理时间(天)',
type: 'bar',
data: [15, 20, 30, 16]
},
{
name: '最短处理时间(天)',
type: 'bar',
data: [3, 5, 7, 4]
}
]
}
charts.value.approvalTimelineChart.setOption(approvalTimelineOption)
}
}
// 响应窗口大小变化
const handleResize = () => {
Object.values(charts.value).forEach(chart => {
if (chart && chart.resize) {
chart.resize()
}
})
}
// 切换图表类型
const changeChart = () => {
// 延迟初始化确保DOM已经更新
setTimeout(() => {
initCharts()
}, 100)
}
// 获取可视化数据
const fetchVisualizationData = async () => {
try {
// 尝试从API获取数据
const response = await axios.get('/api/visualization/data')
if (response.data.code === 200) {
visualizationData.value = response.data.data
}
} catch (error) {
console.error('获取可视化数据失败,使用默认数据:', error)
// 使用默认数据
const mockResponse = await getVisualizationData()
if (mockResponse.code === 200) {
visualizationData.value = mockResponse.data
}
}
}
// 组件挂载
onMounted(() => {
fetchVisualizationData()
setTimeout(() => {
initCharts()
}, 100)
window.addEventListener('resize', handleResize)
})
// 组件卸载
onUnmounted(() => {
window.removeEventListener('resize', handleResize)
Object.values(charts.value).forEach(chart => {
if (chart && chart.dispose) {
chart.dispose()
}
})
})
</script>
<style scoped>
/* 样式可以根据需要进行调整 */
</style>

View File

@@ -1,504 +0,0 @@
<template>
<div>
<h1>仓库管理</h1>
<a-card style="margin-bottom: 16px;">
<a-row gutter={24}>
<a-col :span="8">
<a-button type="primary" @click="handleAddItem">添加物资</a-button>
<a-button style="margin-left: 8px;" @click="handleImportItems">导入物资</a-button>
<a-button style="margin-left: 8px;" @click="handleExportItems">导出物资</a-button>
</a-col>
<a-col :span="16" style="text-align: right;">
<a-input-search
placeholder="搜索物资名称或编号"
allow-clear
enter-button="搜索"
size="large"
style="width: 300px;"
@search="handleSearch"
/>
</a-col>
</a-row>
</a-card>
<!-- 数据概览卡片 -->
<a-row gutter={24} style="margin-bottom: 16px;">
<a-col :span="6">
<a-card class="stat-card" :body-style="{ padding: '20px' }">
<div class="stat-content">
<div class="stat-number">{{ stats.totalItems }}</div>
<div class="stat-label">物资种类</div>
</div>
</a-card>
</a-col>
<a-col :span="6">
<a-card class="stat-card" :body-style="{ padding: '20px' }">
<div class="stat-content">
<div class="stat-number">{{ stats.totalQuantity }}</div>
<div class="stat-label">总数量</div>
</div>
</a-card>
</a-col>
<a-col :span="6">
<a-card class="stat-card" :body-style="{ padding: '20px' }">
<div class="stat-content">
<div class="stat-number">{{ stats.lowStock }}</div>
<div class="stat-label">低库存</div>
</div>
</a-card>
</a-col>
<a-col :span="6">
<a-card class="stat-card" :body-style="{ padding: '20px' }">
<div class="stat-content">
<div class="stat-number">{{ stats.expiringItems }}</div>
<div class="stat-label">临期物资</div>
</div>
</a-card>
</a-col>
</a-row>
<!-- 物资列表 -->
<a-card title="物资列表">
<a-row gutter={24} style="margin-bottom: 16px;">
<a-col :span="6">
<a-select v-model:value="filterCategory" placeholder="筛选物资类型" allow-clear>
<a-select-option value="medicine">药品</a-select-option>
<a-select-option value="equipment">设备</a-select-option>
<a-select-option value="supplies">耗材</a-select-option>
<a-select-option value="other">其他</a-select-option>
</a-select>
</a-col>
<a-col :span="6">
<a-select v-model:value="filterStatus" placeholder="筛选库存状态" allow-clear>
<a-select-option value="normal">正常</a-select-option>
<a-select-option value="low">低库存</a-select-option>
<a-select-option value="out">缺货</a-select-option>
</a-select>
</a-col>
<a-col :span="6">
<a-select v-model:value="filterWarehouse" placeholder="筛选仓库" allow-clear>
<a-select-option value="main">主仓库</a-select-option>
<a-select-option value="sub1">副仓库1</a-select-option>
<a-select-option value="sub2">副仓库2</a-select-option>
</a-select>
</a-col>
</a-row>
<a-table :columns="itemColumns" :data-source="itemList" :pagination="pagination" row-key="id">
<template #bodyCell:quantity="{ record }">
<span :class="getQuantityClass(record)">{{ record.quantity }}</span>
</template>
<template #bodyCell:expiryDate="{ record }">
<span :class="getExpiryClass(record)">{{ record.expiryDate ? dayjs(record.expiryDate).format('YYYY-MM-DD') : '-' }}</span>
</template>
<template #bodyCell:action="{ record }">
<a-space>
<a-button type="link" @click="handleViewItem(record)">查看</a-button>
<a-button type="link" @click="handleEditItem(record)">编辑</a-button>
<a-button type="link" danger @click="handleDeleteItem(record.id)">删除</a-button>
<a-button type="link" @click="handleStockInOut(record, 'in')">入库</a-button>
<a-button type="link" @click="handleStockInOut(record, 'out')">出库</a-button>
</a-space>
</template>
</a-table>
</a-card>
<!-- 添加/编辑物资对话框 -->
<a-modal
v-model:open="itemModalVisible"
:title="itemModalTitle"
@ok="handleItemModalOk"
@cancel="handleItemModalCancel"
>
<a-form :model="itemForm" layout="vertical">
<a-form-item label="物资名称" name="name" :rules="[{ required: true, message: '请输入物资名称' }]">
<a-input v-model:value="itemForm.name" placeholder="请输入物资名称" />
</a-form-item>
<a-form-item label="物资编号" name="code" :rules="[{ required: true, message: '请输入物资编号' }]">
<a-input v-model:value="itemForm.code" placeholder="请输入物资编号" />
</a-form-item>
<a-form-item label="物资类型" name="category" :rules="[{ required: true, message: '请选择物资类型' }]">
<a-select v-model:value="itemForm.category" placeholder="请选择物资类型">
<a-select-option value="medicine">药品</a-select-option>
<a-select-option value="equipment">设备</a-select-option>
<a-select-option value="supplies">耗材</a-select-option>
<a-select-option value="other">其他</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="当前库存" name="quantity" :rules="[{ required: true, message: '请输入当前库存' }]">
<a-input-number v-model:value="itemForm.quantity" placeholder="请输入当前库存" :min="0" />
</a-form-item>
<a-form-item label="预警库存" name="warningStock" :rules="[{ required: true, message: '请输入预警库存' }]">
<a-input-number v-model:value="itemForm.warningStock" placeholder="请输入预警库存" :min="0" />
</a-form-item>
<a-form-item label="所属仓库" name="warehouse" :rules="[{ required: true, message: '请选择所属仓库' }]">
<a-select v-model:value="itemForm.warehouse" placeholder="请选择所属仓库">
<a-select-option value="main">主仓库</a-select-option>
<a-select-option value="sub1">副仓库1</a-select-option>
<a-select-option value="sub2">副仓库2</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="有效期至" name="expiryDate">
<a-date-picker v-model:value="itemForm.expiryDate" style="width: 100%;" />
</a-form-item>
<a-form-item label="物资描述" name="description">
<a-textarea v-model:value="itemForm.description" placeholder="请输入物资描述" rows="3" />
</a-form-item>
</a-form>
</a-modal>
<!-- 入库/出库对话框 -->
<a-modal
v-model:open="stockModalVisible"
:title="stockModalTitle"
@ok="handleStockModalOk"
@cancel="handleStockModalCancel"
>
<a-form :model="stockForm" layout="vertical">
<a-form-item label="物资名称">
<a-input :value="stockForm.itemName" disabled />
</a-form-item>
<a-form-item label="当前库存">
<a-input :value="stockForm.currentStock" disabled />
</a-form-item>
<a-form-item label="数量" name="quantity" :rules="[{ required: true, message: '请输入数量' }]">
<a-input-number v-model:value="stockForm.quantity" placeholder="请输入数量" :min="1" />
</a-form-item>
<a-form-item label="操作人" name="operator" :rules="[{ required: true, message: '请输入操作人' }]">
<a-input v-model:value="stockForm.operator" placeholder="请输入操作人" />
</a-form-item>
<a-form-item label="备注" name="remark">
<a-textarea v-model:value="stockForm.remark" placeholder="请输入备注" rows="3" />
</a-form-item>
</a-form>
</a-modal>
</div>
</template>
<script setup>
import { ref, reactive, computed } from 'vue'
import axios from 'axios'
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
import 'dayjs/locale/zh-cn'
dayjs.extend(relativeTime)
dayjs.locale('zh-cn')
// 统计数据
const stats = ref({
totalItems: 5,
totalQuantity: 1500,
lowStock: 2,
expiringItems: 1
})
// 分页配置
const pagination = reactive({
current: 1,
pageSize: 10,
total: 0
})
// 筛选条件
const filterCategory = ref('')
const filterStatus = ref('')
const filterWarehouse = ref('')
// 物资列表数据
const itemList = ref([
{
id: '1',
name: '消毒液A',
code: 'ITM001',
category: 'supplies',
quantity: 50,
warningStock: 20,
warehouse: 'main',
expiryDate: '2024-12-31T00:00:00Z',
description: '养殖场用消毒液',
createTime: '2024-01-15T09:30:00Z',
updateTime: '2024-04-10T14:20:00Z'
},
{
id: '2',
name: '疫苗B',
code: 'ITM002',
category: 'medicine',
quantity: 10,
warningStock: 50,
warehouse: 'main',
expiryDate: '2024-06-30T00:00:00Z',
description: '动物疫苗',
createTime: '2024-01-20T10:15:00Z',
updateTime: '2024-04-09T16:45:00Z'
},
{
id: '3',
name: '防护服',
code: 'ITM003',
category: 'supplies',
quantity: 200,
warningStock: 50,
warehouse: 'sub1',
expiryDate: null,
description: '防疫用防护服',
createTime: '2024-02-01T14:30:00Z',
updateTime: '2024-04-08T10:15:00Z'
},
{
id: '4',
name: '体温枪',
code: 'ITM004',
category: 'equipment',
quantity: 5,
warningStock: 3,
warehouse: 'sub2',
expiryDate: null,
description: '动物体温测量设备',
createTime: '2024-02-15T11:45:00Z',
updateTime: '2024-04-07T13:30:00Z'
},
{
id: '5',
name: '口罩',
code: 'ITM005',
category: 'supplies',
quantity: 1000,
warningStock: 100,
warehouse: 'sub1',
expiryDate: '2025-03-31T00:00:00Z',
description: '防护口罩',
createTime: '2024-01-10T09:00:00Z',
updateTime: '2024-03-01T16:20:00Z'
}
])
// 物资表格列定义
const itemColumns = [
{
title: '物资名称',
dataIndex: 'name',
key: 'name'
},
{
title: '物资编号',
dataIndex: 'code',
key: 'code'
},
{
title: '物资类型',
dataIndex: 'category',
key: 'category',
customRender: (text) => {
const categoryMap = {
medicine: '药品',
equipment: '设备',
supplies: '耗材',
other: '其他'
}
return categoryMap[text] || text
}
},
{
title: '当前库存',
dataIndex: 'quantity',
key: 'quantity'
},
{
title: '预警库存',
dataIndex: 'warningStock',
key: 'warningStock'
},
{
title: '所属仓库',
dataIndex: 'warehouse',
key: 'warehouse',
customRender: (text) => {
const warehouseMap = {
main: '主仓库',
sub1: '副仓库1',
sub2: '副仓库2'
}
return warehouseMap[text] || text
}
},
{
title: '有效期至',
dataIndex: 'expiryDate',
key: 'expiryDate'
},
{
title: '更新时间',
dataIndex: 'updateTime',
key: 'updateTime',
customRender: (text) => dayjs(text).format('YYYY-MM-DD HH:mm')
},
{
title: '操作',
key: 'action',
width: 200,
fixed: 'right'
}
]
// 模态框配置 - 物资
const itemModalVisible = ref(false)
const itemModalTitle = ref('添加物资')
const itemForm = reactive({
id: '',
name: '',
code: '',
category: '',
quantity: 0,
warningStock: 0,
warehouse: '',
expiryDate: null,
description: ''
})
// 模态框配置 - 库存操作
const stockModalVisible = ref(false)
const stockModalTitle = ref('入库')
const stockForm = reactive({
itemId: '',
itemName: '',
currentStock: 0,
quantity: 0,
operator: '',
remark: '',
type: 'in' // 'in' 入库, 'out' 出库
})
// 获取库存数量样式类
const getQuantityClass = (record) => {
if (record.quantity === 0) {
return 'text-danger'
} else if (record.quantity <= record.warningStock) {
return 'text-warning'
}
return ''
}
// 获取有效期样式类
const getExpiryClass = (record) => {
if (!record.expiryDate) return ''
const daysLeft = dayjs(record.expiryDate).diff(dayjs(), 'day')
if (daysLeft <= 30) {
return 'text-danger'
} else if (daysLeft <= 90) {
return 'text-warning'
}
return ''
}
// 搜索物资
const handleSearch = (keyword) => {
// 这里应该有实际的搜索逻辑
console.log('搜索物资:', keyword)
}
// 添加物资
const handleAddItem = () => {
itemModalTitle.value = '添加物资'
Object.keys(itemForm).forEach(key => {
itemForm[key] = key === 'quantity' || key === 'warningStock' ? 0 : ''
})
itemModalVisible.value = true
}
// 编辑物资
const handleEditItem = (item) => {
itemModalTitle.value = '编辑物资'
Object.keys(itemForm).forEach(key => {
itemForm[key] = item[key] || (key === 'quantity' || key === 'warningStock' ? 0 : '')
})
itemModalVisible.value = true
}
// 查看物资
const handleViewItem = (item) => {
console.log('查看物资:', item)
}
// 删除物资
const handleDeleteItem = (id) => {
console.log('删除物资:', id)
}
// 入库/出库
const handleStockInOut = (item, type) => {
stockModalTitle.value = type === 'in' ? '入库' : '出库'
stockForm.itemId = item.id
stockForm.itemName = item.name
stockForm.currentStock = item.quantity
stockForm.quantity = 0
stockForm.operator = ''
stockForm.remark = ''
stockForm.type = type
stockModalVisible.value = true
}
// 导入物资
const handleImportItems = () => {
console.log('导入物资')
}
// 导出物资
const handleExportItems = () => {
console.log('导出物资')
}
// 模态框确认 - 物资
const handleItemModalOk = () => {
console.log('提交表单:', itemForm)
itemModalVisible.value = false
}
// 模态框取消 - 物资
const handleItemModalCancel = () => {
itemModalVisible.value = false
}
// 模态框确认 - 库存操作
const handleStockModalOk = () => {
console.log('提交库存操作:', stockForm)
stockModalVisible.value = false
}
// 模态框取消 - 库存操作
const handleStockModalCancel = () => {
stockModalVisible.value = false
}
</script>
<style scoped>
.stat-card {
height: 100%;
}
.stat-content {
text-align: center;
}
.stat-number {
font-size: 24px;
font-weight: bold;
color: #1890ff;
}
.stat-label {
font-size: 14px;
color: #666;
margin-top: 8px;
}
.text-warning {
color: #faad14;
}
.text-danger {
color: #f5222d;
}
</style>

View File

@@ -0,0 +1,11 @@
<template>
<div>
<router-view />
</div>
</template>
<script>
export default {
name: 'SmartHardware'
}
</script>

View File

@@ -0,0 +1,422 @@
<template>
<div class="smart-collar-container">
<a-card title="智能项圈管理" :bordered="false">
<a-row :gutter="16">
<a-col :span="24">
<a-form layout="inline" :model="searchForm">
<a-form-item label="项圈编号">
<a-input v-model:value="searchForm.collarId" placeholder="请输入项圈编号" />
</a-form-item>
<a-form-item label="状态">
<a-select v-model:value="searchForm.status" placeholder="请选择状态" style="width: 120px">
<a-select-option value="active">使用中</a-select-option>
<a-select-option value="inactive">未使用</a-select-option>
<a-select-option value="maintenance">维护中</a-select-option>
</a-select>
</a-form-item>
<a-form-item>
<a-button type="primary" @click="handleSearch">查询</a-button>
<a-button style="margin-left: 8px" @click="resetSearch">重置</a-button>
</a-form-item>
</a-form>
</a-col>
</a-row>
<a-row style="margin-top: 16px">
<a-col :span="24">
<a-button type="primary" @click="showAddModal">
<template #icon><PlusOutlined /></template>
添加项圈
</a-button>
</a-col>
</a-row>
<a-table
style="margin-top: 16px"
:columns="columns"
:data-source="dataSource"
:loading="loading"
:pagination="pagination"
@change="handleTableChange"
rowKey="id"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'status'">
<a-tag :color="getStatusColor(record.status)">
{{ getStatusText(record.status) }}
</a-tag>
</template>
<template v-if="column.key === 'action'">
<a-space>
<a @click="showEditModal(record)">编辑</a>
<a-divider type="vertical" />
<a @click="showDetailModal(record)">详情</a>
<a-divider type="vertical" />
<a-popconfirm
title="确定要删除此项圈吗?"
ok-text="确定"
cancel-text="取消"
@confirm="handleDelete(record)"
>
<a class="danger-text">删除</a>
</a-popconfirm>
</a-space>
</template>
</template>
</a-table>
<!-- 添加/编辑模态框 -->
<a-modal
:title="modalTitle"
:visible="modalVisible"
:confirm-loading="modalLoading"
@ok="handleModalOk"
@cancel="handleModalCancel"
>
<a-form :model="formData" :rules="rules" ref="formRef" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
<a-form-item label="项圈编号" name="collarId">
<a-input v-model:value="formData.collarId" placeholder="请输入项圈编号" />
</a-form-item>
<a-form-item label="项圈名称" name="name">
<a-input v-model:value="formData.name" placeholder="请输入项圈名称" />
</a-form-item>
<a-form-item label="状态" name="status">
<a-select v-model:value="formData.status" placeholder="请选择状态">
<a-select-option value="active">使用中</a-select-option>
<a-select-option value="inactive">未使用</a-select-option>
<a-select-option value="maintenance">维护中</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="电池电量" name="battery">
<a-input-number v-model:value="formData.battery" :min="0" :max="100" addonAfter="%" style="width: 100%" />
</a-form-item>
<a-form-item label="备注" name="remark">
<a-textarea v-model:value="formData.remark" placeholder="请输入备注" :rows="4" />
</a-form-item>
</a-form>
</a-modal>
<!-- 详情模态框 -->
<a-modal
title="项圈详情"
:visible="detailModalVisible"
:footer="null"
@cancel="handleDetailModalCancel"
>
<a-descriptions bordered>
<a-descriptions-item label="项圈编号" span="3">{{ detailData.collarId }}</a-descriptions-item>
<a-descriptions-item label="项圈名称" span="3">{{ detailData.name }}</a-descriptions-item>
<a-descriptions-item label="状态" span="3">
<a-tag :color="getStatusColor(detailData.status)">
{{ getStatusText(detailData.status) }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="电池电量" span="3">{{ detailData.battery }}%</a-descriptions-item>
<a-descriptions-item label="创建时间" span="3">{{ detailData.createdAt }}</a-descriptions-item>
<a-descriptions-item label="更新时间" span="3">{{ detailData.updatedAt }}</a-descriptions-item>
<a-descriptions-item label="备注" span="3">{{ detailData.remark || '无' }}</a-descriptions-item>
</a-descriptions>
</a-modal>
</a-card>
</div>
</template>
<script>
import { ref, reactive, onMounted } from 'vue';
import { PlusOutlined } from '@ant-design/icons-vue';
export default {
name: 'SmartCollar',
components: {
PlusOutlined
},
setup() {
// 搜索表单
const searchForm = reactive({
collarId: '',
status: undefined
});
// 表格数据
const loading = ref(false);
const dataSource = ref([]);
const pagination = reactive({
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: true,
showTotal: (total) => `${total}`
});
// 表格列定义
const columns = [
{
title: '项圈编号',
dataIndex: 'collarId',
key: 'collarId',
sorter: true
},
{
title: '项圈名称',
dataIndex: 'name',
key: 'name'
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
filters: [
{ text: '使用中', value: 'active' },
{ text: '未使用', value: 'inactive' },
{ text: '维护中', value: 'maintenance' }
]
},
{
title: '电池电量',
dataIndex: 'battery',
key: 'battery',
customRender: ({ text }) => `${text}%`
},
{
title: '创建时间',
dataIndex: 'createdAt',
key: 'createdAt',
sorter: true
},
{
title: '操作',
key: 'action'
}
];
// 模态框相关
const modalVisible = ref(false);
const modalLoading = ref(false);
const modalTitle = ref('添加项圈');
const formRef = ref(null);
const formData = reactive({
id: null,
collarId: '',
name: '',
status: 'inactive',
battery: 100,
remark: ''
});
const rules = {
collarId: [{ required: true, message: '请输入项圈编号', trigger: 'blur' }],
name: [{ required: true, message: '请输入项圈名称', trigger: 'blur' }],
status: [{ required: true, message: '请选择状态', trigger: 'change' }]
};
// 详情模态框
const detailModalVisible = ref(false);
const detailData = reactive({});
// 模拟数据
const mockData = [
{
id: '1',
collarId: 'CL001',
name: '智能项圈001',
status: 'active',
battery: 85,
createdAt: '2023-09-15 10:00:00',
updatedAt: '2023-09-20 14:30:00',
remark: '用于示范的项圈'
},
{
id: '2',
collarId: 'CL002',
name: '智能项圈002',
status: 'inactive',
battery: 100,
createdAt: '2023-09-16 11:20:00',
updatedAt: '2023-09-16 11:20:00',
remark: ''
},
{
id: '3',
collarId: 'CL003',
name: '智能项圈003',
status: 'maintenance',
battery: 20,
createdAt: '2023-09-10 09:15:00',
updatedAt: '2023-09-21 16:45:00',
remark: '电池需要更换'
}
];
// 获取数据
const fetchData = () => {
loading.value = true;
// 模拟API请求
setTimeout(() => {
dataSource.value = mockData;
pagination.total = mockData.length;
loading.value = false;
}, 500);
};
// 状态相关
const getStatusColor = (status) => {
const statusMap = {
active: 'green',
inactive: 'blue',
maintenance: 'orange'
};
return statusMap[status] || 'default';
};
const getStatusText = (status) => {
const statusMap = {
active: '使用中',
inactive: '未使用',
maintenance: '维护中'
};
return statusMap[status] || '未知';
};
// 搜索相关
const handleSearch = () => {
pagination.current = 1;
fetchData();
};
const resetSearch = () => {
searchForm.collarId = '';
searchForm.status = undefined;
handleSearch();
};
// 表格相关
const handleTableChange = (pag, filters, sorter) => {
pagination.current = pag.current;
pagination.pageSize = pag.pageSize;
fetchData();
};
// 模态框相关
const showAddModal = () => {
modalTitle.value = '添加项圈';
formData.id = null;
formData.collarId = '';
formData.name = '';
formData.status = 'inactive';
formData.battery = 100;
formData.remark = '';
modalVisible.value = true;
};
const showEditModal = (record) => {
modalTitle.value = '编辑项圈';
formData.id = record.id;
formData.collarId = record.collarId;
formData.name = record.name;
formData.status = record.status;
formData.battery = record.battery;
formData.remark = record.remark;
modalVisible.value = true;
};
const handleModalOk = () => {
formRef.value.validate().then(() => {
modalLoading.value = true;
// 模拟API请求
setTimeout(() => {
if (formData.id) {
// 编辑
const index = mockData.findIndex(item => item.id === formData.id);
if (index !== -1) {
mockData[index] = {
...mockData[index],
...formData,
updatedAt: new Date().toLocaleString()
};
}
} else {
// 添加
mockData.push({
id: String(mockData.length + 1),
...formData,
createdAt: new Date().toLocaleString(),
updatedAt: new Date().toLocaleString()
});
}
modalLoading.value = false;
modalVisible.value = false;
fetchData();
}, 500);
});
};
const handleModalCancel = () => {
modalVisible.value = false;
};
// 详情相关
const showDetailModal = (record) => {
Object.assign(detailData, record);
detailModalVisible.value = true;
};
const handleDetailModalCancel = () => {
detailModalVisible.value = false;
};
// 删除
const handleDelete = (record) => {
loading.value = true;
// 模拟API请求
setTimeout(() => {
const index = mockData.findIndex(item => item.id === record.id);
if (index !== -1) {
mockData.splice(index, 1);
}
loading.value = false;
fetchData();
}, 500);
};
onMounted(() => {
fetchData();
});
return {
searchForm,
loading,
dataSource,
pagination,
columns,
modalVisible,
modalLoading,
modalTitle,
formRef,
formData,
rules,
detailModalVisible,
detailData,
getStatusColor,
getStatusText,
handleSearch,
resetSearch,
handleTableChange,
showAddModal,
showEditModal,
handleModalOk,
handleModalCancel,
showDetailModal,
handleDetailModalCancel,
handleDelete
};
}
};
</script>
<style scoped>
.smart-collar-container {
padding: 16px;
}
.danger-text {
color: #ff4d4f;
}
</style>

View File

@@ -0,0 +1,422 @@
<template>
<div class="smart-earmark-container">
<a-card title="智能耳标管理" :bordered="false">
<a-row :gutter="16">
<a-col :span="24">
<a-form layout="inline" :model="searchForm">
<a-form-item label="耳标编号">
<a-input v-model:value="searchForm.earmarkId" placeholder="请输入耳标编号" />
</a-form-item>
<a-form-item label="状态">
<a-select v-model:value="searchForm.status" placeholder="请选择状态" style="width: 120px">
<a-select-option value="active">使用中</a-select-option>
<a-select-option value="inactive">未使用</a-select-option>
<a-select-option value="maintenance">维护中</a-select-option>
</a-select>
</a-form-item>
<a-form-item>
<a-button type="primary" @click="handleSearch">查询</a-button>
<a-button style="margin-left: 8px" @click="resetSearch">重置</a-button>
</a-form-item>
</a-form>
</a-col>
</a-row>
<a-row style="margin-top: 16px">
<a-col :span="24">
<a-button type="primary" @click="showAddModal">
<template #icon><PlusOutlined /></template>
添加耳标
</a-button>
</a-col>
</a-row>
<a-table
style="margin-top: 16px"
:columns="columns"
:data-source="dataSource"
:loading="loading"
:pagination="pagination"
@change="handleTableChange"
rowKey="id"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'status'">
<a-tag :color="getStatusColor(record.status)">
{{ getStatusText(record.status) }}
</a-tag>
</template>
<template v-if="column.key === 'action'">
<a-space>
<a @click="showEditModal(record)">编辑</a>
<a-divider type="vertical" />
<a @click="showDetailModal(record)">详情</a>
<a-divider type="vertical" />
<a-popconfirm
title="确定要删除此耳标吗?"
ok-text="确定"
cancel-text="取消"
@confirm="handleDelete(record)"
>
<a class="danger-text">删除</a>
</a-popconfirm>
</a-space>
</template>
</template>
</a-table>
<!-- 添加/编辑模态框 -->
<a-modal
:title="modalTitle"
:visible="modalVisible"
:confirm-loading="modalLoading"
@ok="handleModalOk"
@cancel="handleModalCancel"
>
<a-form :model="formData" :rules="rules" ref="formRef" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
<a-form-item label="耳标编号" name="earmarkId">
<a-input v-model:value="formData.earmarkId" placeholder="请输入耳标编号" />
</a-form-item>
<a-form-item label="耳标名称" name="name">
<a-input v-model:value="formData.name" placeholder="请输入耳标名称" />
</a-form-item>
<a-form-item label="状态" name="status">
<a-select v-model:value="formData.status" placeholder="请选择状态">
<a-select-option value="active">使用中</a-select-option>
<a-select-option value="inactive">未使用</a-select-option>
<a-select-option value="maintenance">维护中</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="电池电量" name="battery">
<a-input-number v-model:value="formData.battery" :min="0" :max="100" addonAfter="%" style="width: 100%" />
</a-form-item>
<a-form-item label="备注" name="remark">
<a-textarea v-model:value="formData.remark" placeholder="请输入备注" :rows="4" />
</a-form-item>
</a-form>
</a-modal>
<!-- 详情模态框 -->
<a-modal
title="耳标详情"
:visible="detailModalVisible"
:footer="null"
@cancel="handleDetailModalCancel"
>
<a-descriptions bordered>
<a-descriptions-item label="耳标编号" span="3">{{ detailData.earmarkId }}</a-descriptions-item>
<a-descriptions-item label="耳标名称" span="3">{{ detailData.name }}</a-descriptions-item>
<a-descriptions-item label="状态" span="3">
<a-tag :color="getStatusColor(detailData.status)">
{{ getStatusText(detailData.status) }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="电池电量" span="3">{{ detailData.battery }}%</a-descriptions-item>
<a-descriptions-item label="创建时间" span="3">{{ detailData.createdAt }}</a-descriptions-item>
<a-descriptions-item label="更新时间" span="3">{{ detailData.updatedAt }}</a-descriptions-item>
<a-descriptions-item label="备注" span="3">{{ detailData.remark || '无' }}</a-descriptions-item>
</a-descriptions>
</a-modal>
</a-card>
</div>
</template>
<script>
import { ref, reactive, onMounted } from 'vue';
import { PlusOutlined } from '@ant-design/icons-vue';
export default {
name: 'SmartEarmark',
components: {
PlusOutlined
},
setup() {
// 搜索表单
const searchForm = reactive({
earmarkId: '',
status: undefined
});
// 表格数据
const loading = ref(false);
const dataSource = ref([]);
const pagination = reactive({
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: true,
showTotal: (total) => `${total}`
});
// 表格列定义
const columns = [
{
title: '耳标编号',
dataIndex: 'earmarkId',
key: 'earmarkId',
sorter: true
},
{
title: '耳标名称',
dataIndex: 'name',
key: 'name'
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
filters: [
{ text: '使用中', value: 'active' },
{ text: '未使用', value: 'inactive' },
{ text: '维护中', value: 'maintenance' }
]
},
{
title: '电池电量',
dataIndex: 'battery',
key: 'battery',
customRender: ({ text }) => `${text}%`
},
{
title: '创建时间',
dataIndex: 'createdAt',
key: 'createdAt',
sorter: true
},
{
title: '操作',
key: 'action'
}
];
// 模态框相关
const modalVisible = ref(false);
const modalLoading = ref(false);
const modalTitle = ref('添加耳标');
const formRef = ref(null);
const formData = reactive({
id: null,
earmarkId: '',
name: '',
status: 'inactive',
battery: 100,
remark: ''
});
const rules = {
earmarkId: [{ required: true, message: '请输入耳标编号', trigger: 'blur' }],
name: [{ required: true, message: '请输入耳标名称', trigger: 'blur' }],
status: [{ required: true, message: '请选择状态', trigger: 'change' }]
};
// 详情模态框
const detailModalVisible = ref(false);
const detailData = reactive({});
// 模拟数据
const mockData = [
{
id: '1',
earmarkId: 'EM001',
name: '智能耳标001',
status: 'active',
battery: 90,
createdAt: '2023-09-15 10:00:00',
updatedAt: '2023-09-20 14:30:00',
remark: '用于示范的耳标'
},
{
id: '2',
earmarkId: 'EM002',
name: '智能耳标002',
status: 'inactive',
battery: 100,
createdAt: '2023-09-16 11:20:00',
updatedAt: '2023-09-16 11:20:00',
remark: ''
},
{
id: '3',
earmarkId: 'EM003',
name: '智能耳标003',
status: 'maintenance',
battery: 15,
createdAt: '2023-09-10 09:15:00',
updatedAt: '2023-09-21 16:45:00',
remark: '电池需要更换'
}
];
// 获取数据
const fetchData = () => {
loading.value = true;
// 模拟API请求
setTimeout(() => {
dataSource.value = mockData;
pagination.total = mockData.length;
loading.value = false;
}, 500);
};
// 状态相关
const getStatusColor = (status) => {
const statusMap = {
active: 'green',
inactive: 'blue',
maintenance: 'orange'
};
return statusMap[status] || 'default';
};
const getStatusText = (status) => {
const statusMap = {
active: '使用中',
inactive: '未使用',
maintenance: '维护中'
};
return statusMap[status] || '未知';
};
// 搜索相关
const handleSearch = () => {
pagination.current = 1;
fetchData();
};
const resetSearch = () => {
searchForm.earmarkId = '';
searchForm.status = undefined;
handleSearch();
};
// 表格相关
const handleTableChange = (pag, filters, sorter) => {
pagination.current = pag.current;
pagination.pageSize = pag.pageSize;
fetchData();
};
// 模态框相关
const showAddModal = () => {
modalTitle.value = '添加耳标';
formData.id = null;
formData.earmarkId = '';
formData.name = '';
formData.status = 'inactive';
formData.battery = 100;
formData.remark = '';
modalVisible.value = true;
};
const showEditModal = (record) => {
modalTitle.value = '编辑耳标';
formData.id = record.id;
formData.earmarkId = record.earmarkId;
formData.name = record.name;
formData.status = record.status;
formData.battery = record.battery;
formData.remark = record.remark;
modalVisible.value = true;
};
const handleModalOk = () => {
formRef.value.validate().then(() => {
modalLoading.value = true;
// 模拟API请求
setTimeout(() => {
if (formData.id) {
// 编辑
const index = mockData.findIndex(item => item.id === formData.id);
if (index !== -1) {
mockData[index] = {
...mockData[index],
...formData,
updatedAt: new Date().toLocaleString()
};
}
} else {
// 添加
mockData.push({
id: String(mockData.length + 1),
...formData,
createdAt: new Date().toLocaleString(),
updatedAt: new Date().toLocaleString()
});
}
modalLoading.value = false;
modalVisible.value = false;
fetchData();
}, 500);
});
};
const handleModalCancel = () => {
modalVisible.value = false;
};
// 详情相关
const showDetailModal = (record) => {
Object.assign(detailData, record);
detailModalVisible.value = true;
};
const handleDetailModalCancel = () => {
detailModalVisible.value = false;
};
// 删除
const handleDelete = (record) => {
loading.value = true;
// 模拟API请求
setTimeout(() => {
const index = mockData.findIndex(item => item.id === record.id);
if (index !== -1) {
mockData.splice(index, 1);
}
loading.value = false;
fetchData();
}, 500);
};
onMounted(() => {
fetchData();
});
return {
searchForm,
loading,
dataSource,
pagination,
columns,
modalVisible,
modalLoading,
modalTitle,
formRef,
formData,
rules,
detailModalVisible,
detailData,
getStatusColor,
getStatusText,
handleSearch,
resetSearch,
handleTableChange,
showAddModal,
showEditModal,
handleModalOk,
handleModalCancel,
showDetailModal,
handleDetailModalCancel,
handleDelete
};
}
};
</script>
<style scoped>
.smart-earmark-container {
padding: 16px;
}
.danger-text {
color: #ff4d4f;
}
</style>

View File

@@ -0,0 +1,422 @@
<template>
<div class="smart-host-container">
<a-card title="智能主机管理" :bordered="false">
<a-row :gutter="16">
<a-col :span="24">
<a-form layout="inline" :model="searchForm">
<a-form-item label="主机编号">
<a-input v-model:value="searchForm.hostId" placeholder="请输入主机编号" />
</a-form-item>
<a-form-item label="状态">
<a-select v-model:value="searchForm.status" placeholder="请选择状态" style="width: 120px">
<a-select-option value="online">在线</a-select-option>
<a-select-option value="offline">离线</a-select-option>
<a-select-option value="maintenance">维护中</a-select-option>
</a-select>
</a-form-item>
<a-form-item>
<a-button type="primary" @click="handleSearch">查询</a-button>
<a-button style="margin-left: 8px" @click="resetSearch">重置</a-button>
</a-form-item>
</a-form>
</a-col>
</a-row>
<a-row style="margin-top: 16px">
<a-col :span="24">
<a-button type="primary" @click="showAddModal">
<template #icon><PlusOutlined /></template>
添加主机
</a-button>
</a-col>
</a-row>
<a-table
style="margin-top: 16px"
:columns="columns"
:data-source="dataSource"
:loading="loading"
:pagination="pagination"
@change="handleTableChange"
rowKey="id"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'status'">
<a-tag :color="getStatusColor(record.status)">
{{ getStatusText(record.status) }}
</a-tag>
</template>
<template v-if="column.key === 'action'">
<a-space>
<a @click="showEditModal(record)">编辑</a>
<a-divider type="vertical" />
<a @click="showDetailModal(record)">详情</a>
<a-divider type="vertical" />
<a-popconfirm
title="确定要删除此主机吗?"
ok-text="确定"
cancel-text="取消"
@confirm="handleDelete(record)"
>
<a class="danger-text">删除</a>
</a-popconfirm>
</a-space>
</template>
</template>
</a-table>
<!-- 添加/编辑模态框 -->
<a-modal
:title="modalTitle"
:visible="modalVisible"
:confirm-loading="modalLoading"
@ok="handleModalOk"
@cancel="handleModalCancel"
>
<a-form :model="formData" :rules="rules" ref="formRef" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
<a-form-item label="主机编号" name="hostId">
<a-input v-model:value="formData.hostId" placeholder="请输入主机编号" />
</a-form-item>
<a-form-item label="主机名称" name="name">
<a-input v-model:value="formData.name" placeholder="请输入主机名称" />
</a-form-item>
<a-form-item label="IP地址" name="ipAddress">
<a-input v-model:value="formData.ipAddress" placeholder="请输入IP地址" />
</a-form-item>
<a-form-item label="状态" name="status">
<a-select v-model:value="formData.status" placeholder="请选择状态">
<a-select-option value="online">在线</a-select-option>
<a-select-option value="offline">离线</a-select-option>
<a-select-option value="maintenance">维护中</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="备注" name="remark">
<a-textarea v-model:value="formData.remark" placeholder="请输入备注" :rows="4" />
</a-form-item>
</a-form>
</a-modal>
<!-- 详情模态框 -->
<a-modal
title="主机详情"
:visible="detailModalVisible"
:footer="null"
@cancel="handleDetailModalCancel"
>
<a-descriptions bordered>
<a-descriptions-item label="主机编号" span="3">{{ detailData.hostId }}</a-descriptions-item>
<a-descriptions-item label="主机名称" span="3">{{ detailData.name }}</a-descriptions-item>
<a-descriptions-item label="IP地址" span="3">{{ detailData.ipAddress }}</a-descriptions-item>
<a-descriptions-item label="状态" span="3">
<a-tag :color="getStatusColor(detailData.status)">
{{ getStatusText(detailData.status) }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="创建时间" span="3">{{ detailData.createdAt }}</a-descriptions-item>
<a-descriptions-item label="更新时间" span="3">{{ detailData.updatedAt }}</a-descriptions-item>
<a-descriptions-item label="备注" span="3">{{ detailData.remark || '无' }}</a-descriptions-item>
</a-descriptions>
</a-modal>
</a-card>
</div>
</template>
<script>
import { ref, reactive, onMounted } from 'vue';
import { PlusOutlined } from '@ant-design/icons-vue';
export default {
name: 'SmartHost',
components: {
PlusOutlined
},
setup() {
// 搜索表单
const searchForm = reactive({
hostId: '',
status: undefined
});
// 表格数据
const loading = ref(false);
const dataSource = ref([]);
const pagination = reactive({
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: true,
showTotal: (total) => `${total}`
});
// 表格列定义
const columns = [
{
title: '主机编号',
dataIndex: 'hostId',
key: 'hostId',
sorter: true
},
{
title: '主机名称',
dataIndex: 'name',
key: 'name'
},
{
title: 'IP地址',
dataIndex: 'ipAddress',
key: 'ipAddress'
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
filters: [
{ text: '在线', value: 'online' },
{ text: '离线', value: 'offline' },
{ text: '维护中', value: 'maintenance' }
]
},
{
title: '创建时间',
dataIndex: 'createdAt',
key: 'createdAt',
sorter: true
},
{
title: '操作',
key: 'action'
}
];
// 模态框相关
const modalVisible = ref(false);
const modalLoading = ref(false);
const modalTitle = ref('添加主机');
const formRef = ref(null);
const formData = reactive({
id: null,
hostId: '',
name: '',
ipAddress: '',
status: 'offline',
remark: ''
});
const rules = {
hostId: [{ required: true, message: '请输入主机编号', trigger: 'blur' }],
name: [{ required: true, message: '请输入主机名称', trigger: 'blur' }],
ipAddress: [{ required: true, message: '请输入IP地址', trigger: 'blur' }],
status: [{ required: true, message: '请选择状态', trigger: 'change' }]
};
// 详情模态框
const detailModalVisible = ref(false);
const detailData = reactive({});
// 模拟数据
const mockData = [
{
id: '1',
hostId: 'HOST001',
name: '智能主机001',
ipAddress: '192.168.1.100',
status: 'online',
createdAt: '2023-09-15 10:00:00',
updatedAt: '2023-09-20 14:30:00',
remark: '中心控制主机'
},
{
id: '2',
hostId: 'HOST002',
name: '智能主机002',
ipAddress: '192.168.1.101',
status: 'offline',
createdAt: '2023-09-16 11:20:00',
updatedAt: '2023-09-16 11:20:00',
remark: ''
},
{
id: '3',
hostId: 'HOST003',
name: '智能主机003',
ipAddress: '192.168.1.102',
status: 'maintenance',
createdAt: '2023-09-10 09:15:00',
updatedAt: '2023-09-21 16:45:00',
remark: '系统升级中'
}
];
// 获取数据
const fetchData = () => {
loading.value = true;
// 模拟API请求
setTimeout(() => {
dataSource.value = mockData;
pagination.total = mockData.length;
loading.value = false;
}, 500);
};
// 状态相关
const getStatusColor = (status) => {
const statusMap = {
online: 'green',
offline: 'gray',
maintenance: 'orange'
};
return statusMap[status] || 'default';
};
const getStatusText = (status) => {
const statusMap = {
online: '在线',
offline: '离线',
maintenance: '维护中'
};
return statusMap[status] || '未知';
};
// 搜索相关
const handleSearch = () => {
pagination.current = 1;
fetchData();
};
const resetSearch = () => {
searchForm.hostId = '';
searchForm.status = undefined;
handleSearch();
};
// 表格相关
const handleTableChange = (pag, filters, sorter) => {
pagination.current = pag.current;
pagination.pageSize = pag.pageSize;
fetchData();
};
// 模态框相关
const showAddModal = () => {
modalTitle.value = '添加主机';
formData.id = null;
formData.hostId = '';
formData.name = '';
formData.ipAddress = '';
formData.status = 'offline';
formData.remark = '';
modalVisible.value = true;
};
const showEditModal = (record) => {
modalTitle.value = '编辑主机';
formData.id = record.id;
formData.hostId = record.hostId;
formData.name = record.name;
formData.ipAddress = record.ipAddress;
formData.status = record.status;
formData.remark = record.remark;
modalVisible.value = true;
};
const handleModalOk = () => {
formRef.value.validate().then(() => {
modalLoading.value = true;
// 模拟API请求
setTimeout(() => {
if (formData.id) {
// 编辑
const index = mockData.findIndex(item => item.id === formData.id);
if (index !== -1) {
mockData[index] = {
...mockData[index],
...formData,
updatedAt: new Date().toLocaleString()
};
}
} else {
// 添加
mockData.push({
id: String(mockData.length + 1),
...formData,
createdAt: new Date().toLocaleString(),
updatedAt: new Date().toLocaleString()
});
}
modalLoading.value = false;
modalVisible.value = false;
fetchData();
}, 500);
});
};
const handleModalCancel = () => {
modalVisible.value = false;
};
// 详情相关
const showDetailModal = (record) => {
Object.assign(detailData, record);
detailModalVisible.value = true;
};
const handleDetailModalCancel = () => {
detailModalVisible.value = false;
};
// 删除
const handleDelete = (record) => {
loading.value = true;
// 模拟API请求
setTimeout(() => {
const index = mockData.findIndex(item => item.id === record.id);
if (index !== -1) {
mockData.splice(index, 1);
}
loading.value = false;
fetchData();
}, 500);
};
onMounted(() => {
fetchData();
});
return {
searchForm,
loading,
dataSource,
pagination,
columns,
modalVisible,
modalLoading,
modalTitle,
formRef,
formData,
rules,
detailModalVisible,
detailData,
getStatusColor,
getStatusText,
handleSearch,
resetSearch,
handleTableChange,
showAddModal,
showEditModal,
handleModalOk,
handleModalCancel,
showDetailModal,
handleDetailModalCancel,
handleDelete
};
}
};
</script>
<style scoped>
.smart-host-container {
padding: 16px;
}
.danger-text {
color: #ff4d4f;
}
</style>