添加银行后端接口,前端代码
This commit is contained in:
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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')
|
||||
@@ -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: '可视化分析' }
|
||||
// }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
244
government-admin/src/views/AdminDepartment.vue
Normal file
244
government-admin/src/views/AdminDepartment.vue
Normal 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>
|
||||
265
government-admin/src/views/AdminStaff.vue
Normal file
265
government-admin/src/views/AdminStaff.vue
Normal 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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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">小型(<50头)</a-select-option>
|
||||
<a-select-option value="medium">中型(50-200头)</a-select-option>
|
||||
<a-select-option value="large">大型(>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">小型(<50头)</a-select-option>
|
||||
<a-select-option value="medium">中型(50-200头)</a-select-option>
|
||||
<a-select-option value="large">大型(>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 '小型(<50头)'
|
||||
case 'medium': return '中型(50-200头)'
|
||||
case 'large': return '大型(>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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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' ? '在职' : '离职'" />
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
11
government-admin/src/views/smart-warehouse/SmartHardware.vue
Normal file
11
government-admin/src/views/smart-warehouse/SmartHardware.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<div>
|
||||
<router-view />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SmartHardware'
|
||||
}
|
||||
</script>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user