添加银行端后端接口

This commit is contained in:
2025-09-24 17:49:32 +08:00
parent b58ed724b0
commit 111ebaec84
95 changed files with 22115 additions and 4246 deletions

View File

@@ -58,16 +58,59 @@
</a-sub-menu>
<!-- 无纸化服务 -->
<a-menu-item key="/paperless">
<a-sub-menu key="paperless">
<template #icon><FileTextOutlined /></template>
<span>无纸化服务</span>
</a-menu-item>
<template #title>
<span>无纸化服务</span>
</template>
<!-- 无纸化防疫 -->
<a-sub-menu key="paperless-epidemic">
<template #title>
<span>无纸化防疫</span>
</template>
<a-menu-item key="/paperless/epidemic"><span>疫情防控</span></a-menu-item>
<a-menu-item key="/paperless/epidemic/epidemic-agency"><span>防疫机构管理</span></a-menu-item>
<a-menu-item key="/paperless/epidemic/epidemic-record"><span>防疫记录</span></a-menu-item>
<a-menu-item key="/paperless/epidemic/vaccine-management"><span>疫苗管理</span></a-menu-item>
<a-menu-item key="/paperless/epidemic/epidemic-activity"><span>防疫活动管理</span></a-menu-item>
</a-sub-menu>
<!-- 无纸化检疫 -->
<a-sub-menu key="paperless-quarantine">
<template #title>
<span>无纸化检疫</span>
</template>
<a-menu-item key="/paperless/quarantine/declaration"><span>检疫审批</span></a-menu-item>
<a-menu-item key="/paperless/quarantine/record-search"><span>检疫证查询</span></a-menu-item>
<a-menu-item key="/paperless/quarantine/report-export"><span>检疫证清单</span></a-menu-item>
</a-sub-menu>
</a-sub-menu>
<!-- 屠宰无害化 -->
<a-menu-item key="/slaughter">
<a-sub-menu key="slaughter">
<template #icon><SafetyOutlined /></template>
<span>屠宰无害化</span>
</a-menu-item>
<template #title>
<span>屠宰无害化</span>
</template>
<!-- 屠宰管理 -->
<a-sub-menu key="slaughter-management">
<template #title>
<span>屠宰管理</span>
</template>
<a-menu-item key="/slaughter/slaughterhouse"><span>屠宰场</span></a-menu-item>
</a-sub-menu>
<!-- 无害化处理 -->
<a-sub-menu key="harmless-treatment">
<template #title>
<span>无害化处理</span>
</template>
<a-menu-item key="/slaughter/harmless/place"><span>无害化场所</span></a-menu-item>
<a-menu-item key="/slaughter/harmless/registration"><span>无害化登记</span></a-menu-item>
</a-sub-menu>
</a-sub-menu>
<!-- 生资认证 -->
<a-menu-item key="/examine/index">
@@ -149,34 +192,54 @@ export default {
// 处理展开/收起
const handleOpenChange = (keys) => {
// 保留所有打开的菜单项,不折叠
openKeys.value = keys
}
// 获取父级菜单key
const getParentMenuKey = (path) => {
const menuMap= {
'/supervision': 'supervision',
'/inspection': 'inspection',
'/violation': 'violation',
'/epidemic': 'epidemic',
'/approval': 'approval',
// 替换:获取父级菜单key -> 获取需要展开的菜单keys
const getOpenMenuKeys = (path) => {
// 顶级目录映射
const topLevelMap = {
'/index': '',
'/price': '',
'/personnel': 'personnel',
'/system': 'system',
'/smart-warehouse': 'smart-warehouse'
'/farmer': '',
'/smart-warehouse': 'smart-warehouse',
'/paperless': 'paperless',
'/slaughter': '',
'/examine': '',
'/consultation': '',
'/academy': '',
'/notification': ''
}
for (const [prefix, key] of Object.entries(menuMap)) {
if (path.startsWith(prefix)) {
return key
// 二级目录映射 - 确保点击三级目录时保持二级目录展开
if (path.startsWith('/paperless/epidemic')) {
return ['paperless', 'paperless-epidemic']
}
if (path.startsWith('/paperless/quarantine')) {
return ['paperless', 'paperless-quarantine']
}
// 屠宰管理相关路径处理
if (path.startsWith('/slaughter/slaughterhouse')) {
return ['slaughter', 'slaughter-management']
}
// 无害化处理相关路径处理
if (path.startsWith('/slaughter/harmless')) {
return ['slaughter', 'harmless-treatment']
}
for (const [prefix, key] of Object.entries(topLevelMap)) {
if (key && path.startsWith(prefix)) {
return [key]
}
}
// 特殊处理智慧仓库路径
if (path.includes('smart-warehouse')) {
return 'smart-warehouse'
return ['smart-warehouse']
}
return ''
return []
}
// 更新选中状态
@@ -184,12 +247,9 @@ const updateSelectedState = () => {
const currentPath = route.path
selectedKeys.value = [currentPath]
const parentKey = getParentMenuKey(currentPath)
if (parentKey) {
openKeys.value = [parentKey]
} else {
openKeys.value = []
}
const keys = getOpenMenuKeys(currentPath)
// 合并现有打开的菜单和新需要打开的菜单,确保已打开的菜单不会关闭
openKeys.value = [...new Set([...openKeys.value, ...keys])]
}
// 监听路由变化

View File

@@ -121,12 +121,84 @@ const routes = [
component: PaperlessService,
meta: { title: '无纸化服务' }
},
{
path: 'paperless/epidemic',
name: 'EpidemicHome',
component: () => import('@/views/paperless/EpidemicHome.vue'),
meta: { title: '无纸化防疫' }
},
{
path: 'paperless/epidemic/epidemic-agency',
name: 'EpidemicAgencyManagement',
component: () => import('@/views/paperless/epidemic/epidemic-agency/EpidemicAgencyManagement.vue'),
meta: { title: '防疫机构管理' }
},
{
path: 'paperless/epidemic/epidemic-record',
name: 'EpidemicRecordManagement',
component: () => import('@/views/paperless/epidemic/epidemic-record/EpidemicRecordManagement.vue'),
meta: { title: '防疫记录管理' }
},
{
path: 'paperless/epidemic/vaccine-management',
name: 'VaccineManagement',
component: () => import('@/views/paperless/epidemic/vaccine-management/VaccineManagement.vue'),
meta: { title: '疫苗管理' }
},
{
path: 'paperless/epidemic/epidemic-activity',
name: 'EpidemicActivityManagement',
component: () => import('@/views/paperless/epidemic/epidemic-activity/EpidemicActivityManagement.vue'),
meta: { title: '防疫活动管理' }
},
{ // 无纸化检疫主页
path: 'paperless/quarantine',
name: 'QuarantineHome',
component: () => import('@/views/paperless/QuarantineHome.vue'),
meta: { title: '无纸化检疫' }
},
{ // 建议审批
path: 'paperless/quarantine/declaration',
name: 'QuarantineDeclaration',
component: () => import('@/views/paperless/quarantine/QuarantineDeclaration.vue'),
meta: { title: '建议审批' }
},
{ // 检疫证查询
path: 'paperless/quarantine/record-search',
name: 'QuarantineRecordSearch',
component: () => import('@/views/paperless/quarantine/QuarantineRecordSearch.vue'),
meta: { title: '检疫证查询' }
},
{ // 检疫证清单
path: 'paperless/quarantine/report-export',
name: 'QuarantineReportExport',
component: () => import('@/views/paperless/quarantine/QuarantineReportExport.vue'),
meta: { title: '检疫证清单' }
},
{
path: 'slaughter',
name: 'SlaughterHarmless',
component: SlaughterHarmless,
meta: { title: '屠宰无害化' }
},
{
path: 'slaughter/slaughterhouse',
name: 'Slaughterhouse',
component: () => import('@/views/slaughter/Slaughterhouse.vue'),
meta: { title: '屠宰场' }
},
{
path: 'slaughter/harmless/place',
name: 'HarmlessPlace',
component: () => import('@/views/slaughter/harmless/HarmlessPlace.vue'),
meta: { title: '无害化场所' }
},
{
path: 'slaughter/harmless/registration',
name: 'HarmlessRegistration',
component: () => import('@/views/slaughter/harmless/HarmlessRegistration.vue'),
meta: { title: '无害化登记' }
},
{
path: 'finance',
name: 'FinanceInsurance',

View File

@@ -1,6 +1,6 @@
import axios from 'axios'
import { message } from 'antd'
import { useUserStore } from '@/stores/user'
import axios from 'axios'
// 创建axios实例
const instance = axios.create({
@@ -191,14 +191,22 @@ const api = {
// 仓库管理相关API
warehouse: {
// 获取仓库列表
// 获取物资列表
getList: (params) => instance.get('/warehouse', { params }),
// 创建仓库
// 获取单个物资详情
getDetail: (id) => instance.get(`/warehouse/${id}`),
// 创建物资
create: (data) => instance.post('/warehouse', data),
// 更新仓库
// 更新物资
update: (id, data) => instance.put(`/warehouse/${id}`, data),
// 删除仓库
delete: (id) => instance.delete(`/warehouse/${id}`)
// 删除物资
delete: (id) => instance.delete(`/warehouse/${id}`),
// 物资入库
stockIn: (data) => instance.post('/warehouse/in', data),
// 物资出库
stockOut: (data) => instance.post('/warehouse/out', data),
// 获取库存统计信息
getStats: () => instance.get('/warehouse/stats')
},
// 系统设置相关API

View File

@@ -1,78 +1,69 @@
<template>
<div>
<h1>审批流程管理</h1>
<div class="page-container">
<h1 class="page-title">审批流程管理</h1>
<!-- 操作按钮区域 -->
<div style="margin-bottom: 16px;">
<a-button type="primary" @click="showCreateModal">新建审批流程</a-button>
<!-- <div class="action-buttons"> -->
<!-- <a-button type="primary" @click="showCreateModal">新建审批流程</a-button>
<a-button style="margin-left: 8px;" @click="exportApprovalList">导出列表</a-button>
</div> -->
<!-- 标签页和搜索 -->
<div class="filter-section">
<a-tabs v-model:activeKey="activeTab" @change="handleTabChange">
<a-tab-pane key="pending" tab="待审批"></a-tab-pane>
<a-tab-pane key="approved" tab="已审批"></a-tab-pane>
</a-tabs>
<a-input-search
placeholder="请输入认证申请人"
style="width: 200px; margin-bottom: 16px;"
@search="onSearch"
/>
</div>
<!-- 过滤器和搜索 -->
<div style="margin-bottom: 16px;">
<a-row gutter={16}>
<a-col :span="6">
<a-select v-model:value="filters.status" placeholder="审批状态" style="width: 100%;">
<a-select-option value="all">全部状态</a-select-option>
<a-select-option value="pending">待审批</a-select-option>
<a-select-option value="approved">已通过</a-select-option>
<a-select-option value="rejected">已拒绝</a-select-option>
<a-select-option value="processing">处理中</a-select-option>
</a-select>
</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="enterprise">企业资质</a-select-option>
<a-select-option value="license">许可证</a-select-option>
<a-select-option value="project">项目审批</a-select-option>
<a-select-option value="other">其他</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-input-search placeholder="搜索审批编号或申请人" @search="searchApproval" style="width: 100%;" />
<!-- 审批卡片列表 -->
<div class="card-list">
<a-row :gutter="[16, 16]">
<a-col :span="8" v-for="item in approvalList" :key="item.id">
<a-card class="approval-card" @click="() => viewApprovalDetail(item.id)">
<template #title>
<div class="card-title">
<span>当前状态:</span>
<a-tag :color="getStatusColor(item.status)">{{ getStatusText(item.status) }}</a-tag>
</div>
</template>
<p>认证申请人: {{ item.applicant }}</p>
<p>认证类型: {{ item.type }}</p>
<p>认证数量: {{ item.quantity }}</p>
<p>申请时间: {{ item.create_time }}</p>
<p>联系电话: {{ item.phone }}</p>
<p>养殖场名称: {{ item.farmName }}</p>
</a-card>
</a-col>
</a-row>
</div>
<!-- 审批流程表格 -->
<a-card>
<a-table
:columns="approvalColumns"
:data-source="approvalList"
:pagination="{ pageSize: 10 }"
row-key="id"
>
<template #bodyCell:status="{ record }">
<a-tag :color="getStatusColor(record.status)">
{{ getStatusText(record.status) }}
</a-tag>
</template>
<template #bodyCell:type="{ record }">
{{ getTypeText(record.type) }}
</template>
<template #bodyCell:action="{ record }">
<a-space>
<a-button type="link" @click="viewApprovalDetail(record.id)">查看</a-button>
<a-button type="link" @click="editApproval(record.id)" v-if="record.status === 'pending'">编辑</a-button>
<a-button type="link" @click="deleteApproval(record.id)" danger>删除</a-button>
<a-button type="primary" size="small" @click="processApproval(record.id)" v-if="record.status === 'pending'">审批</a-button>
</a-space>
</template>
</a-table>
</a-card>
<!-- 分页 -->
<div class="pagination-container">
<a-pagination
v-model:current="currentPage"
:total="totalItems"
:page-size="pageSize"
@change="handlePageChange"
show-quick-jumper
/>
</div>
<!-- 新建审批流程弹窗 -->
<a-modal
class="custom-modal"
title="新建审批流程"
v-model:open="createModalVisible"
:footer="null"
@cancel="closeCreateModal"
>
<a-form
class="custom-form"
ref="createFormRef"
:model="createFormData"
layout="vertical"
@@ -117,12 +108,13 @@
<!-- 审批弹窗 -->
<a-modal
class="custom-modal"
title="审批操作"
v-model:open="processModalVisible"
:footer="null"
@cancel="closeProcessModal"
>
<div v-if="currentApproval">
<div v-if="currentApproval" class="detail-info">
<h3>{{ currentApproval.title }}</h3>
<p>申请人: {{ currentApproval.applicant }}</p>
<p>申请时间: {{ currentApproval.create_time }}</p>
@@ -144,17 +136,19 @@
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { ref, onMounted, computed } from 'vue'
import { message } from 'antd'
import axios from 'axios'
import { UploadOutlined } from '@ant-design/icons-vue'
const allData = ref([])
const approvalList = ref([])
const filters = ref({
status: 'all',
type: 'all',
dateRange: []
})
const activeTab = ref('pending')
const currentPage = ref(1)
const pageSize = ref(9)
const totalItems = ref(0)
const searchInput = ref('')
const createModalVisible = ref(false)
const processModalVisible = ref(false)
const currentApproval = ref(null)
@@ -174,46 +168,7 @@ const createFormRules = ref({
description: [{ required: true, message: '请输入审批说明', trigger: 'blur' }]
})
// 审批流程表格列定义
const approvalColumns = [
{
title: '审批编号',
dataIndex: 'id',
key: 'id'
},
{
title: '审批标题',
dataIndex: 'title',
key: 'title'
},
{
title: '审批类型',
dataIndex: 'type',
key: 'type',
slots: { customRender: 'type' }
},
{
title: '申请人',
dataIndex: 'applicant',
key: 'applicant'
},
{
title: '申请时间',
dataIndex: 'create_time',
key: 'create_time'
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
slots: { customRender: 'status' }
},
{
title: '操作',
key: 'action',
slots: { customRender: 'action' }
}
]
const approvalColumns = []
// 根据状态获取标签颜色
const getStatusColor = (status) => {
@@ -414,5 +369,126 @@ onMounted(() => {
</script>
<style scoped>
/* 样式可以根据需要进行调整 */
/* 页面容器样式 */
.page-container {
padding: 20px;
background-color: #f5f5f5;
min-height: 100vh;
}
/* 页面标题样式 */
.page-title {
font-size: 20px;
font-weight: 600;
color: #262626;
margin-bottom: 24px;
}
/* 操作按钮区域样式 */
.action-buttons {
margin-bottom: 16px;
background-color: #fff;
padding: 16px;
border-radius: 6px;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.03);
}
/* 过滤器和搜索区域样式 */
.filter-section {
margin-bottom: 16px;
background-color: #fff;
padding: 16px;
border-radius: 6px;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.03);
}
/* 表格卡片样式 */
.table-card {
background-color: #fff;
border-radius: 6px;
overflow: hidden;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.03);
}
/* 表格样式优化 */
.custom-table {
border-radius: 6px;
overflow: hidden;
}
.custom-table .ant-table-thead > tr > th {
background-color: #fafafa;
font-weight: 600;
color: #262626;
border-bottom: 1px solid #f0f0f0;
}
.custom-table .ant-table-tbody > tr:hover > td {
background-color: #f5f5f5;
}
/* 表格单元格样式 */
.table-cell {
padding: 12px 16px;
}
/* 状态标签样式增强 */
.status-tag {
display: inline-block;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
}
/* 操作按钮组样式 */
.action-group {
display: flex;
gap: 8px;
}
/* 弹窗样式优化 */
.custom-modal .ant-modal-header {
background-color: #fafafa;
border-bottom: 1px solid #f0f0f0;
}
.custom-modal .ant-modal-title {
font-size: 18px;
font-weight: 600;
color: #262626;
}
.custom-modal .ant-modal-footer {
border-top: 1px solid #f0f0f0;
}
/* 表单样式优化 */
.custom-form .ant-form-item {
margin-bottom: 16px;
}
.custom-form .ant-form-item-label {
font-weight: 500;
color: #595959;
}
/* 详情信息区域样式 */
.detail-info {
margin-bottom: 20px;
padding: 16px;
background-color: #fafafa;
border-radius: 6px;
}
.detail-info p {
margin-bottom: 8px;
line-height: 1.5;
}
.detail-info h3 {
margin-bottom: 16px;
padding-bottom: 8px;
border-bottom: 1px solid #f0f0f0;
}
</style>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,49 +1,53 @@
<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="categoryFilter" placeholder="物资类别" style="width: 120px;">
<a-select-option value="">全部</a-select-option>
<a-select-option value="feed">饲料</a-select-option>
<a-select-option value="medicine">药品</a-select-option>
<a-select-option value="equipment">设备</a-select-option>
<a-select-option value="other">其他</a-select-option>
</a-select>
<a-select v-model:value="statusFilter" placeholder="库存状态" style="width: 120px;">
<a-select-option value="">全部</a-select-option>
<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-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="handleAddMaterial">
<span class="iconfont icon-tianjia"></span> 新增物资
</a-button>
<!-- 如果是子路由显示子路由内容 -->
<router-view v-slot="{ Component }">
<div v-if="Component">
<component :is="Component" />
</div>
<div v-else>
<!-- 搜索和操作栏 -->
<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="categoryFilter" placeholder="物资类别" style="width: 120px;">
<a-select-option value="">全部</a-select-option>
<a-select-option value="feed">饲料</a-select-option>
<a-select-option value="medicine">药品</a-select-option>
<a-select-option value="equipment">设备</a-select-option>
<a-select-option value="other">其他</a-select-option>
</a-select>
<a-select v-model:value="statusFilter" placeholder="库存状态" style="width: 120px;">
<a-select-option value="">全部</a-select-option>
<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-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="handleAddMaterial">
<span class="iconfont icon-tianjia"></span> 新增物资
</a-button>
</div>
</a-card>
<!-- 数据统计卡片 -->
@@ -274,11 +278,14 @@
</a-form>
</a-modal>
</div>
</router-view>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ref, reactive, onMounted, onUnmounted } from 'vue'
import * as echarts from 'echarts'
import api from '@/utils/api'
// 搜索条件
const searchKeyword = ref('')
@@ -298,7 +305,16 @@ const pagination = reactive({
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `${total} 条记录`
showTotal: (total) => `${total} 条记录`,
onChange: (page) => {
pagination.current = page
fetchMaterials()
},
onShowSizeChange: (current, pageSize) => {
pagination.current = 1
pagination.pageSize = pageSize
fetchMaterials()
}
})
// 选中的行
@@ -346,138 +362,66 @@ const stockForm = reactive({
})
// 物资列表数据
const materialsData = ref([
{
id: '1',
code: 'FEED001',
name: '牛用精饲料',
category: 'feed',
unit: '袋',
stockQuantity: 250,
warningQuantity: 50,
status: 'normal',
supplier: '绿源饲料公司',
remark: '高蛋白配方',
updateTime: '2024-04-10 09:30:00'
},
{
id: '2',
code: 'FEED002',
name: '粗饲料',
category: 'feed',
unit: '吨',
stockQuantity: 12,
warningQuantity: 5,
status: 'low',
supplier: '草原饲料',
remark: '优质牧草',
updateTime: '2024-04-09 14:20:00'
},
{
id: '3',
code: 'MED001',
name: '牛瘟疫苗',
category: 'medicine',
unit: '盒',
stockQuantity: 0,
warningQuantity: 10,
status: 'out',
supplier: '动保生物公司',
remark: '每盒10支',
updateTime: '2024-04-08 10:15:00'
},
{
id: '4',
code: 'MED002',
name: '驱虫药',
category: 'medicine',
unit: '',
stockQuantity: 85,
warningQuantity: 20,
status: 'normal',
supplier: '兽药批发中心',
remark: '广谱驱虫',
updateTime: '2024-04-10 11:45:00'
},
{
id: '5',
code: 'EQU001',
name: '牛用耳标',
category: 'equipment',
unit: '个',
stockQuantity: 3500,
warningQuantity: 500,
status: 'normal',
supplier: '畜牧设备公司',
remark: 'RFID电子耳标',
updateTime: '2024-04-07 16:00:00'
},
{
id: '6',
code: 'EQU002',
name: '体温计',
category: 'equipment',
unit: '支',
stockQuantity: 15,
warningQuantity: 5,
status: 'normal',
supplier: '医疗器械公司',
remark: '兽用电子体温计',
updateTime: '2024-04-06 13:30:00'
},
{
id: '7',
code: 'FEED003',
name: '矿物质添加剂',
category: 'feed',
unit: 'kg',
stockQuantity: 35,
warningQuantity: 10,
status: 'normal',
supplier: '营养添加剂厂',
remark: '补充微量元素',
updateTime: '2024-04-05 10:15:00'
},
{
id: '8',
code: 'MED003',
name: '抗生素',
category: 'medicine',
unit: '盒',
stockQuantity: 5,
warningQuantity: 10,
status: 'low',
supplier: '兽药批发中心',
remark: '需处方使用',
updateTime: '2024-04-04 15:45:00'
},
{
id: '9',
code: 'EQU003',
name: '消毒设备',
category: 'equipment',
unit: '台',
stockQuantity: 3,
warningQuantity: 1,
status: 'normal',
supplier: '畜牧设备公司',
remark: '自动喷雾消毒机',
updateTime: '2024-04-03 09:30:00'
},
{
id: '10',
code: 'OTH001',
name: '防护服',
category: 'other',
unit: '套',
stockQuantity: 120,
warningQuantity: 30,
status: 'normal',
supplier: '劳保用品公司',
remark: '一次性使用',
updateTime: '2024-04-02 14:20:00'
const materialsData = ref([])
// 获取物资列表
const fetchMaterials = async () => {
try {
const params = {
keyword: searchKeyword.value,
category: categoryFilter.value,
status: statusFilter.value,
page: pagination.current,
pageSize: pagination.pageSize
}
const response = await api.warehouse.getList(params)
// 根据后端实际返回的数据结构进行调整
materialsData.value = response.data || []
pagination.total = response.total || 0
} catch (error) {
console.error('获取物资列表失败:', error)
// 如果获取失败,提供一些模拟数据以便页面可以正常显示
materialsData.value = [
{
id: '1',
code: 'M001',
name: '玉米饲料',
category: 'feed',
unit: '吨',
stockQuantity: 150,
warningQuantity: 50,
status: 'normal',
supplier: '希望饲料厂',
updateTime: '2024-04-07 10:15:00'
},
{
id: '2',
code: 'M002',
name: '牛瘟疫苗',
category: 'medicine',
unit: '',
stockQuantity: 20,
warningQuantity: 10,
status: 'low',
supplier: '生物制药公司',
updateTime: '2024-04-05 14:30:00'
},
{
id: '3',
code: 'M003',
name: '兽用注射器',
category: 'equipment',
unit: '',
stockQuantity: 0,
warningQuantity: 50,
status: 'out',
supplier: '医疗器械公司',
updateTime: '2024-04-01 09:45:00'
}
]
pagination.total = materialsData.value.length
}
])
}
// 表格列定义
const columns = [
@@ -578,14 +522,8 @@ const getCategoryText = (category) => {
// 搜索处理
const handleSearch = () => {
console.log('搜索条件:', {
keyword: searchKeyword.value,
category: categoryFilter.value,
status: statusFilter.value
})
// 这里应该有实际的搜索逻辑
// 模拟搜索后的总数
pagination.total = materialsData.value.length
pagination.current = 1
fetchMaterials()
}
// 重置处理
@@ -594,6 +532,8 @@ const handleReset = () => {
categoryFilter.value = ''
statusFilter.value = ''
selectedRowKeys.value = []
pagination.current = 1
fetchMaterials()
}
// 导入处理
@@ -635,84 +575,163 @@ const handleEdit = (record) => {
}
// 查看物资
const handleView = (record) => {
viewMaterial.value = JSON.parse(JSON.stringify(record))
isViewModalOpen.value = true
const handleView = async (record) => {
try {
const response = await api.warehouse.getDetail(record.id)
viewMaterial.value = response.data
isViewModalOpen.value = true
} catch (error) {
console.error('获取物资详情失败:', error)
alert('获取物资详情失败,请重试')
}
}
// 删除物资
const handleDelete = (id) => {
console.log('删除物资:', id)
// 这里应该有实际的删除逻辑和确认提示
// 模拟删除成功
alert(`成功删除物资ID: ${id}`)
const handleDelete = async (id) => {
try {
if (confirm('确定要删除这条物资记录吗?')) {
await api.warehouse.delete(id)
alert('删除成功')
fetchMaterials() // 重新获取物资列表
}
} catch (error) {
console.error('删除物资失败:', error)
alert('删除失败,请重试')
}
}
// 保存物资
const handleSave = () => {
console.log('保存物资:', currentMaterial)
// 这里应该有实际的保存逻辑
// 更新状态
if (currentMaterial.stockQuantity === 0) {
currentMaterial.status = 'out'
} else if (currentMaterial.stockQuantity <= currentMaterial.warningQuantity) {
currentMaterial.status = 'low'
} else {
currentMaterial.status = 'normal'
const handleSave = async () => {
try {
// 复制物资对象,避免修改原对象
const materialData = { ...currentMaterial }
if (materialData.id) {
// 更新现有物资
await api.warehouse.update(materialData.id, materialData)
} else {
// 创建新物资
await api.warehouse.create(materialData)
}
isAddEditModalOpen.value = false
alert('保存成功')
fetchMaterials() // 重新获取物资列表
} catch (error) {
console.error('保存物资失败:', error)
alert('保存失败,请重试')
}
// 模拟保存成功
isAddEditModalOpen.value = false
alert('保存成功')
}
// 入库
const handleStockIn = (id) => {
const material = materialsData.value.find(item => item.id === id)
if (material) {
currentStockOperation.value = 'in'
currentStockMaterialId.value = id
stockModalTitle.value = '入库'
Object.assign(stockForm, {
materialName: material.name,
currentStock: `${material.stockQuantity}${material.unit}`,
quantity: 1,
operator: '',
remark: ''
})
isStockModalOpen.value = true
const handleStockIn = async (id) => {
try {
const response = await api.warehouse.getDetail(id)
const material = response.data
if (material) {
currentStockOperation.value = 'in'
currentStockMaterialId.value = id
stockModalTitle.value = '入库'
Object.assign(stockForm, {
materialName: material.name,
currentStock: `${material.stockQuantity}${material.unit}`,
quantity: 1,
operator: '',
remark: ''
})
isStockModalOpen.value = true
}
} catch (error) {
console.error('获取物资信息失败:', error)
alert('获取物资信息失败,请重试')
}
}
// 出库
const handleStockOut = (id) => {
const material = materialsData.value.find(item => item.id === id)
if (material) {
currentStockOperation.value = 'out'
currentStockMaterialId.value = id
stockModalTitle.value = '出库'
Object.assign(stockForm, {
materialName: material.name,
currentStock: `${material.stockQuantity}${material.unit}`,
quantity: 1,
operator: '',
remark: ''
})
isStockModalOpen.value = true
const handleStockOut = async (id) => {
try {
const response = await api.warehouse.getDetail(id)
const material = response.data
if (material) {
currentStockOperation.value = 'out'
currentStockMaterialId.value = id
stockModalTitle.value = '出库'
Object.assign(stockForm, {
materialName: material.name,
currentStock: `${material.stockQuantity}${material.unit}`,
quantity: 1,
operator: '',
remark: ''
})
isStockModalOpen.value = true
}
} catch (error) {
console.error('获取物资信息失败:', error)
alert('获取物资信息失败,请重试')
}
}
// 提交入库/出库
const handleStockSubmit = () => {
console.log(`${currentStockOperation.value === 'in' ? '入库' : '出库'}操作:`, {
materialId: currentStockMaterialId.value,
quantity: stockForm.quantity,
operator: stockForm.operator,
remark: stockForm.remark
})
// 这里应该有实际的入库/出库逻辑
// 模拟操作成功
isStockModalOpen.value = false
alert(`${currentStockOperation.value === 'in' ? '入库' : '出库'}操作成功`)
const handleStockSubmit = async () => {
try {
const stockData = {
materialId: currentStockMaterialId.value,
quantity: stockForm.quantity,
operator: stockForm.operator,
remark: stockForm.remark
}
if (currentStockOperation.value === 'in') {
// 入库操作
await api.warehouse.stockIn(stockData)
} else {
// 出库操作
await api.warehouse.stockOut(stockData)
}
isStockModalOpen.value = false
alert(`${currentStockOperation.value === 'in' ? '入库' : '出库'}操作成功`)
fetchMaterials() // 重新获取物资列表
fetchWarehouseStats() // 更新统计数据和图表
} catch (error) {
console.error(`${currentStockOperation.value === 'in' ? '入库' : '出库'}操作失败:`, error)
alert(`${currentStockOperation.value === 'in' ? '入库' : '出库'}操作失败,请重试`)
}
}
// 获取仓库统计信息
const fetchWarehouseStats = async () => {
try {
const response = await api.warehouse.getStats()
// 确保我们使用正确的响应结构
totalCategories.value = response.data?.totalCategories || response.totalCategories || totalCategories.value
totalQuantity.value = response.data?.totalQuantity || response.totalQuantity || totalQuantity.value
lowStockCount.value = response.data?.lowStockCount || response.lowStockCount || lowStockCount.value
outOfStockCount.value = response.data?.outOfStockCount || response.outOfStockCount || outOfStockCount.value
// 更新图表数据
if (stockChartRef.value) {
const chart = echarts.getInstanceByDom(stockChartRef.value)
if (chart) {
chart.setOption({
series: [{
data: [
{ value: totalCategories.value - lowStockCount.value - outOfStockCount.value, name: '正常库存', itemStyle: { color: '#52c41a' } },
{ value: lowStockCount.value, name: '低库存', itemStyle: { color: '#faad14' } },
{ value: outOfStockCount.value, name: '缺货', itemStyle: { color: '#f5222d' } }
]
}]
})
}
}
} catch (error) {
console.error('获取仓库统计信息失败:', error)
// 如果获取失败,使用硬编码的统计数据
totalCategories.value = 86
totalQuantity.value = 12560
lowStockCount.value = 12
outOfStockCount.value = 3
}
}
// 初始化库存预警图表
@@ -764,20 +783,33 @@ const initStockChart = () => {
chart.setOption(option)
// 保存图表实例引用以便后续更新
stockChartRef.value.__chartInstance = chart
window.addEventListener('resize', () => {
chart.resize()
})
}
// 组件挂载时初始化图表
onMounted(() => {
// 组件挂载时初始化数据和图表
onMounted(async () => {
await Promise.all([
fetchMaterials(),
fetchWarehouseStats()
])
setTimeout(() => {
initStockChart()
}, 100)
})
// 初始化数据
pagination.total = materialsData.value.length
// 组件卸载时清理事件监听器
onUnmounted(() => {
if (stockChartRef.value && stockChartRef.value.__chartInstance) {
const chart = stockChartRef.value.__chartInstance
chart.dispose()
}
})
</script>
<style scoped>

View File

@@ -0,0 +1,189 @@
<template>
<div>
<h1>无纸化防疫管理</h1>
<!-- 统计卡片 -->
<a-row gutter={24} style="margin-bottom: 16px;">
<a-col :span="6">
<a-statistic title="防疫机构数量" :value="agencyCount" suffix="个" />
</a-col>
<a-col :span="6">
<a-statistic title="本月防疫记录" :value="monthlyRecords" suffix="条" :valueStyle="{ color: '#52c41a' }" />
</a-col>
<a-col :span="6">
<a-statistic title="在库疫苗数量" :value="vaccineCount" suffix="种" :valueStyle="{ color: '#1890ff' }" />
</a-col>
<a-col :span="6">
<a-statistic title="本月防疫活动" :value="monthlyActivities" suffix="场" />
</a-col>
</a-row>
<!-- 趋势图表 -->
<a-card title="防疫工作趋势统计" style="margin-bottom: 16px;">
<div style="height: 300px;" ref="trendChartRef"></div>
</a-card>
<!-- 快捷入口卡片 -->
<a-row gutter={24}>
<a-col :span="6">
<a-card
hoverable
@click="goToAgencyManagement"
:body-style="{ padding: '24px', cursor: 'pointer', textAlign: 'center' }"
>
<a-icon type="bank" style="fontSize: 48px; color: '#1890ff'; marginBottom: '16px'" />
<h3 style="marginBottom: '8px'">防疫机构管理</h3>
<p style="color: '#8c8c8c'">查看和管理防疫机构信息</p>
</a-card>
</a-col>
<a-col :span="6">
<a-card
hoverable
@click="goToRecordManagement"
:body-style="{ padding: '24px', cursor: 'pointer', textAlign: 'center' }"
>
<a-icon type="file-text" style="fontSize: 48px; color: '#52c41a'; marginBottom: '16px'" />
<h3 style="marginBottom: '8px'">防疫记录管理</h3>
<p style="color: '#8c8c8c'">查看和管理防疫记录信息</p>
</a-card>
</a-col>
<a-col :span="6">
<a-card
hoverable
@click="goToVaccineManagement"
:body-style="{ padding: '24px', cursor: 'pointer', textAlign: 'center' }"
>
<a-icon type="medicine-box" style="fontSize: 48px; color: '#faad14'; marginBottom: '16px'" />
<h3 style="marginBottom: '8px'">疫苗管理</h3>
<p style="color: '#8c8c8c'">查看和管理疫苗库存信息</p>
</a-card>
</a-col>
<a-col :span="6">
<a-card
hoverable
@click="goToActivityManagement"
:body-style="{ padding: '24px', cursor: 'pointer', textAlign: 'center' }"
>
<a-icon type="schedule" style="fontSize: 48px; color: '#f5222d'; marginBottom: '16px'" />
<h3 style="marginBottom: '8px'">防疫活动管理</h3>
<p style="color: '#8c8c8c'">查看和管理防疫活动信息</p>
</a-card>
</a-col>
</a-row>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import * as echarts from 'echarts'
import { message } from 'ant-design-vue'
const router = useRouter()
// 统计数据
const agencyCount = ref(25)
const monthlyRecords = ref(568)
const vaccineCount = ref(12)
const monthlyActivities = ref(18)
// 图表引用
const trendChartRef = ref(null)
// 导航到子页面
const goToAgencyManagement = () => {
router.push('/paperless/epidemic/agency')
}
const goToRecordManagement = () => {
router.push('/paperless/epidemic/record')
}
const goToVaccineManagement = () => {
router.push('/paperless/epidemic/vaccine')
}
const goToActivityManagement = () => {
router.push('/paperless/epidemic/activity')
}
// 初始化趋势图表
const initTrendChart = () => {
if (trendChartRef.value) {
const chart = echarts.init(trendChartRef.value)
const option = {
tooltip: {
trigger: 'axis'
},
legend: {
data: ['防疫记录', '疫苗接种', '防疫活动']
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
},
yAxis: {
type: 'value'
},
series: [
{
name: '防疫记录',
type: 'line',
stack: '总量',
data: [400, 450, 520, 480, 550, 580, 620, 650, 690, 720, 750, 780],
itemStyle: {
color: '#1890ff'
}
},
{
name: '疫苗接种',
type: 'line',
stack: '总量',
data: [200, 230, 280, 260, 300, 320, 350, 380, 420, 450, 480, 520],
itemStyle: {
color: '#52c41a'
}
},
{
name: '防疫活动',
type: 'line',
stack: '总量',
data: [10, 15, 18, 22, 25, 28, 32, 35, 38, 42, 45, 50],
itemStyle: {
color: '#faad14'
}
}
]
}
chart.setOption(option)
// 响应式调整
window.addEventListener('resize', () => {
chart.resize()
})
}
}
// 组件挂载时初始化
onMounted(() => {
initTrendChart()
})
</script>
<style scoped>
h1 {
font-size: 20px;
font-weight: 600;
margin-bottom: 20px;
color: #333;
}
</style>

View File

@@ -0,0 +1,217 @@
<template>
<div>
<h1>无纸化检疫管理</h1>
<!-- 统计卡片 -->
<a-row gutter={24} style="margin-bottom: 16px;">
<a-col :span="6">
<a-statistic title="检疫申报数量" :value="declarationCount" suffix="件" />
</a-col>
<a-col :span="6">
<a-statistic title="今日检疫数量" :value="todayQuarantineCount" suffix="件" :valueStyle="{ color: '#52c41a' }" />
</a-col>
<a-col :span="6">
<a-statistic title="合格数量" :value="qualifiedCount" suffix="件" :valueStyle="{ color: '#1890ff' }" />
</a-col>
<a-col :span="6">
<a-statistic title="不合格数量" :value="unqualifiedCount" suffix="件" :valueStyle="{ color: '#f5222d' }" />
</a-col>
</a-row>
<!-- 趋势图表 -->
<a-card title="检疫工作趋势统计" style="margin-bottom: 16px;">
<div style="height: 300px;" ref="trendChartRef"></div>
</a-card>
<!-- 检疫类型分布 -->
<a-card title="检疫类型分布" style="margin-bottom: 16px;">
<div style="height: 300px;" ref="distributionChartRef"></div>
</a-card>
<!-- 快捷操作 -->
<a-card title="快捷操作">
<div style="display: flex; flex-wrap: wrap; gap: 16px;">
<a-button type="primary" size="large" style="width: 200px;">
<a-icon type="file-add" />
新增检疫申报
</a-button>
<a-button type="primary" size="large" style="width: 200px;">
<a-icon type="search" />
查询检疫记录
</a-button>
<a-button type="primary" size="large" style="width: 200px;">
<a-icon type="export" />
导出检疫报表
</a-button>
<a-button type="primary" size="large" style="width: 200px;">
<a-icon type="setting" />
检疫配置管理
</a-button>
</div>
</a-card>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import * as echarts from 'echarts'
import { message } from 'ant-design-vue'
// 统计数据
const declarationCount = ref(1254)
const todayQuarantineCount = ref(48)
const qualifiedCount = ref(1189)
const unqualifiedCount = ref(65)
// 图表引用
const trendChartRef = ref(null)
const distributionChartRef = ref(null)
// 初始化趋势图表
const initTrendChart = () => {
if (trendChartRef.value) {
const chart = echarts.init(trendChartRef.value)
const option = {
tooltip: {
trigger: 'axis'
},
legend: {
data: ['申报数量', '检疫完成', '合格数量', '不合格数量']
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
},
yAxis: {
type: 'value'
},
series: [
{
name: '申报数量',
type: 'line',
stack: '总量',
data: [95, 120, 135, 110, 145, 160, 175, 150, 180, 200, 210, 225],
itemStyle: {
color: '#1890ff'
}
},
{
name: '检疫完成',
type: 'line',
stack: '总量',
data: [90, 115, 130, 105, 140, 155, 170, 145, 175, 195, 205, 220],
itemStyle: {
color: '#52c41a'
}
},
{
name: '合格数量',
type: 'line',
stack: '总量',
data: [85, 110, 125, 100, 135, 150, 165, 140, 170, 190, 200, 215],
itemStyle: {
color: '#faad14'
}
},
{
name: '不合格数量',
type: 'line',
stack: '总量',
data: [5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5],
itemStyle: {
color: '#f5222d'
}
}
]
}
chart.setOption(option)
// 响应式调整
window.addEventListener('resize', () => {
chart.resize()
})
}
}
// 初始化分布图表
const initDistributionChart = () => {
if (distributionChartRef.value) {
const chart = echarts.init(distributionChartRef.value)
const option = {
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: 450, name: '出栏检疫', itemStyle: { color: '#1890ff' } },
{ value: 320, name: '运输检疫', itemStyle: { color: '#52c41a' } },
{ value: 280, name: '屠宰检疫', itemStyle: { color: '#faad14' } },
{ value: 150, name: '市场检疫', itemStyle: { color: '#722ed1' } },
{ value: 54, name: '其他检疫', itemStyle: { color: '#f5222d' } }
]
}
]
}
chart.setOption(option)
// 响应式调整
window.addEventListener('resize', () => {
chart.resize()
})
}
}
// 组件挂载时初始化
onMounted(() => {
initTrendChart()
initDistributionChart()
})
</script>
<style scoped>
h1 {
font-size: 20px;
font-weight: 600;
margin-bottom: 20px;
color: #333;
}
</style>

View File

@@ -0,0 +1,732 @@
<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="typeFilter" placeholder="活动类型" style="width: 120px;">
<a-select-option value="">全部</a-select-option>
<a-select-option value="training">防疫培训</a-select-option>
<a-select-option value="inspection">防疫检查</a-select-option>
<a-select-option value="promotion">防疫宣传</a-select-option>
<a-select-option value="emergency_response">应急处置</a-select-option>
<a-select-option value="other">其他活动</a-select-option>
</a-select>
<a-select v-model:value="statusFilter" placeholder="状态" style="width: 120px;">
<a-select-option value="">全部</a-select-option>
<a-select-option value="planning">计划中</a-select-option>
<a-select-option value="ongoing">进行中</a-select-option>
<a-select-option value="completed">已完成</a-select-option>
<a-select-option value="cancelled">已取消</a-select-option>
</a-select>
<a-range-picker
v-model:value="dateRange"
style="width: 300px;"
format="YYYY-MM-DD"
:placeholder="['开始日期', '结束日期']"
/>
<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="primary" danger @click="handleAddActivity">
<span class="iconfont icon-tianjia"></span> 新增活动
</a-button>
</div>
</a-card>
<!-- 数据列表 -->
<a-card>
<a-table
:columns="columns"
:data-source="activitiesData"
:pagination="pagination"
row-key="id"
:row-selection="{ selectedRowKeys, onChange: onSelectChange }"
:scroll="{ x: 'max-content' }"
>
<!-- 状态列 -->
<template #bodyCell:status="{ record }">
<a-tag :color="getStatusColor(record.status)">{{ getStatusText(record.status) }}</a-tag>
</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>
<a-button size="small" @click="handleStartActivity(record.id)" v-if="record.status === 'planning'">开始</a-button>
<a-button size="small" @click="handleCompleteActivity(record.id)" v-if="record.status === 'ongoing'">完成</a-button>
<a-button size="small" @click="handleCancelActivity(record.id)" v-if="['planning', 'ongoing'].includes(record.status)">取消</a-button>
</div>
</template>
</a-table>
</a-card>
<!-- 新增/编辑活动模态框 -->
<a-modal
v-model:open="isAddEditModalOpen"
:title="isEditing ? '编辑防疫活动' : '新增防疫活动'"
:footer="null"
width={700}
>
<a-form
:model="currentActivity"
layout="vertical"
>
<a-form-item label="活动名称"
:rules="[{ required: true, message: '请输入活动名称' }]">
<a-input v-model:value="currentActivity.name" placeholder="请输入活动名称" />
</a-form-item>
<a-form-item label="活动类型"
:rules="[{ required: true, message: '请选择活动类型' }]">
<a-select v-model:value="currentActivity.type" placeholder="请选择活动类型">
<a-select-option value="training">防疫培训</a-select-option>
<a-select-option value="inspection">防疫检查</a-select-option>
<a-select-option value="promotion">防疫宣传</a-select-option>
<a-select-option value="emergency_response">应急处置</a-select-option>
<a-select-option value="other">其他活动</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="负责人"
:rules="[{ required: true, message: '请输入负责人姓名' }]">
<a-input v-model:value="currentActivity.manager" placeholder="请输入负责人姓名" />
</a-form-item>
<a-form-item label="联系电话"
:rules="[
{ required: true, message: '请输入联系电话' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码' }
]">
<a-input v-model:value="currentActivity.phone" placeholder="请输入联系电话" />
</a-form-item>
<a-form-item label="活动时间"
:rules="[{ required: true, message: '请选择活动时间' }]">
<a-range-picker
v-model:value="activityTimeRange"
style="width: 100%;"
format="YYYY-MM-DD HH:mm"
:placeholder="['开始日期', '结束日期']"
show-time
/>
</a-form-item>
<a-form-item label="活动地点"
:rules="[{ required: true, message: '请输入活动地点' }]">
<a-input v-model:value="currentActivity.location" placeholder="请输入活动地点" />
</a-form-item>
<a-form-item label="参与人员"
:rules="[{ required: true, message: '请输入参与人员' }]">
<a-input.TextArea v-model:value="currentActivity.participants" placeholder="请输入参与人员,用逗号分隔" rows={2} />
</a-form-item>
<a-form-item label="活动内容"
:rules="[{ required: true, message: '请输入活动内容' }]">
<a-input.TextArea v-model:value="currentActivity.content" placeholder="请输入活动内容" rows={4} />
</a-form-item>
<a-form-item label="预期目标"
:rules="[{ required: true, message: '请输入预期目标' }]">
<a-input.TextArea v-model:value="currentActivity.target" placeholder="请输入预期目标" rows={3} />
</a-form-item>
<a-form-item label="预算(元)"
:rules="[
{ required: true, message: '请输入预算' },
{ pattern: /^\d+(\.\d{1,2})?$/, message: '请输入正确的金额格式' }
]">
<a-input-number v-model:value="currentActivity.budget" min="0" precision="2" placeholder="请输入预算(元)" />
</a-form-item>
<a-form-item label="备注">
<a-input.TextArea v-model:value="currentActivity.notes" placeholder="请输入备注信息" :rows="3" />
</a-form-item>
</a-form>
<div style="display: flex; justify-content: flex-end; gap: 12px; margin-top: 24px;">
<a-button @click="handleCancel">取消</a-button>
<a-button type="primary" @click="handleSave">保存</a-button>
</div>
</a-modal>
<!-- 查看活动详情模态框 -->
<a-modal
v-model:open="isViewModalOpen"
title="查看防疫活动详情"
:footer="null"
width={700}
>
<div v-if="viewActivity">
<div style="margin-bottom: 16px;">
<h3 style="margin-bottom: 8px;">基本信息</h3>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px;">
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">活动名称</p>
<p>{{ viewActivity.name }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">活动类型</p>
<p>{{ getTypeText(viewActivity.type) }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">负责人</p>
<p>{{ viewActivity.manager }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">联系电话</p>
<p>{{ viewActivity.phone }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">活动时间</p>
<p>{{ formatTimeRange(viewActivity.startTime, viewActivity.endTime) }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">活动地点</p>
<p>{{ viewActivity.location }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">预算</p>
<p>{{ viewActivity.budget }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">状态</p>
<p><a-tag :color="getStatusColor(viewActivity.status)">{{ getStatusText(viewActivity.status) }}</a-tag></p>
</div>
</div>
</div>
<div style="margin-bottom: 16px;">
<h3 style="margin-bottom: 8px;">详细信息</h3>
<div style="margin-bottom: 12px;">
<p style="color: #8c8c8c; margin-bottom: 4px;">参与人员</p>
<p>{{ viewActivity.participants || '-' }}</p>
</div>
<div style="margin-bottom: 12px;">
<p style="color: #8c8c8c; margin-bottom: 4px;">活动内容</p>
<p>{{ viewActivity.content || '-' }}</p>
</div>
<div style="margin-bottom: 12px;">
<p style="color: #8c8c8c; margin-bottom: 4px;">预期目标</p>
<p>{{ viewActivity.target || '-' }}</p>
</div>
<div v-if="viewActivity.actualTarget">
<p style="color: #8c8c8c; margin-bottom: 4px;">实际达成目标</p>
<p>{{ viewActivity.actualTarget || '-' }}</p>
</div>
</div>
<div>
<h3 style="margin-bottom: 8px;">备注</h3>
<p>{{ viewActivity.notes || '-' }}</p>
</div>
</div>
<div style="display: flex; justify-content: flex-end; margin-top: 24px;">
<a-button @click="handleCloseView">关闭</a-button>
</div>
</a-modal>
</div>
</template>
<script setup>
import { ref, reactive, computed } from 'vue'
import { message } from 'ant-design-vue'
// 搜索条件
const searchKeyword = ref('')
const typeFilter = ref('')
const statusFilter = ref('')
const dateRange = ref([])
// 分页配置
const pagination = reactive({
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total, range) => `${range[0]}-${range[1]} 条,共 ${total}`
})
// 选中行
const selectedRowKeys = ref([])
const onSelectChange = (newSelectedRowKeys) => {
selectedRowKeys.value = newSelectedRowKeys
}
// 模态框状态
const isAddEditModalOpen = ref(false)
const isViewModalOpen = ref(false)
const isEditing = ref(false)
// 当前编辑/查看的活动
const currentActivity = reactive({
id: '',
name: '',
type: 'training',
manager: '',
phone: '',
startTime: '',
endTime: '',
location: '',
participants: '',
content: '',
target: '',
actualTarget: '',
budget: 0,
notes: '',
status: 'planning',
createdAt: ''
})
const viewActivity = ref(null)
const activityTimeRange = ref([])
// 活动列表数据(模拟数据)
const activitiesData = ref([
{
id: '1',
name: '2023年第一季度牛场防疫培训',
type: 'training',
manager: '张三',
phone: '13812345678',
startTime: '2023-01-15 09:00',
endTime: '2023-01-15 17:00',
location: '郑州市金水区农业局会议室',
participants: '各区县兽医站站长、大型养殖场负责人',
content: '牛场防疫知识培训,包括口蹄疫、布鲁氏菌病等常见疫病的预防与控制',
target: '提高基层防疫人员和养殖场主的防疫意识和技能',
actualTarget: '培训覆盖100人次完成率100%',
budget: 5000,
notes: '',
status: 'completed',
createdAt: '2022-12-20'
},
{
id: '2',
name: '春节期间牛场防疫专项检查',
type: 'inspection',
manager: '李四',
phone: '13912345678',
startTime: '2023-01-20 08:30',
endTime: '2023-01-25 17:30',
location: '郑州市各区县牛场',
participants: '市农业农村局、市动物疫病预防控制中心工作人员',
content: '对全市规模化牛场进行春节前防疫安全检查,重点检查疫苗接种、消毒措施落实情况',
target: '检查覆盖率达到100%发现问题整改率100%',
actualTarget: '检查牛场50家发现问题20处整改完成20处',
budget: 8000,
notes: '',
status: 'completed',
createdAt: '2023-01-05'
},
{
id: '3',
name: '春季动物防疫宣传月活动',
type: 'promotion',
manager: '王五',
phone: '13712345678',
startTime: '2023-03-01 09:00',
endTime: '2023-03-31 17:00',
location: '郑州市各区县乡镇',
participants: '市、区、乡三级兽医人员',
content: '通过发放宣传资料、举办讲座、现场咨询等方式,宣传动物防疫知识',
target: '发放宣传资料10万份举办讲座50场覆盖群众5万人次',
actualTarget: '',
budget: 15000,
notes: '',
status: 'ongoing',
createdAt: '2023-02-10'
},
{
id: '4',
name: '牛群口蹄疫疫苗集中接种活动',
type: 'emergency_response',
manager: '赵六',
phone: '13612345678',
startTime: '2023-04-01 08:00',
endTime: '2023-04-15 18:00',
location: '郑州市各区县牛场',
participants: '各级兽医人员、村级防疫员',
content: '对全市所有牛只进行口蹄疫疫苗集中接种',
target: '接种率达到100%',
actualTarget: '',
budget: 20000,
notes: '提前做好疫苗和防疫物资准备',
status: 'planning',
createdAt: '2023-03-05'
},
{
id: '5',
name: '动物防疫体系建设研讨会',
type: 'other',
manager: '钱七',
phone: '13512345678',
startTime: '2023-02-10 09:00',
endTime: '2023-02-10 17:00',
location: '郑州市农业农村局会议室',
participants: '市农业农村局领导、专家学者、基层防疫人员代表',
content: '研讨动物防疫体系建设现状、问题及对策',
target: '形成动物防疫体系建设的政策建议',
actualTarget: '形成《郑州市动物防疫体系建设建议报告》',
budget: 3000,
notes: '',
status: 'completed',
createdAt: '2023-01-15'
},
{
id: '6',
name: '牛场生物安全管理培训',
type: 'training',
manager: '孙八',
phone: '13412345678',
startTime: '2023-04-20 09:00',
endTime: '2023-04-20 17:00',
location: '新郑市农业农村局会议室',
participants: '新郑市各牛场技术负责人',
content: '牛场生物安全管理知识培训,包括消毒、隔离、人员管理等内容',
target: '提高牛场生物安全管理水平',
actualTarget: '',
budget: 4000,
notes: '',
status: 'planning',
createdAt: '2023-03-20'
},
{
id: '7',
name: '布鲁氏菌病监测与防控专项活动',
type: 'inspection',
manager: '周九',
phone: '13312345678',
startTime: '2023-05-01 08:30',
endTime: '2023-05-15 17:30',
location: '郑州市各区县牛场',
participants: '市、区动物疫病预防控制中心工作人员',
content: '对全市牛场进行布鲁氏菌病监测和防控措施检查',
target: '监测覆盖率达到100%防控措施落实率100%',
actualTarget: '',
budget: 12000,
notes: '',
status: 'planning',
createdAt: '2023-03-25'
},
{
id: '8',
name: '新型冠状病毒疫情防控知识培训',
type: 'training',
manager: '吴十',
phone: '13212345678',
startTime: '2023-01-10 09:00',
endTime: '2023-01-10 17:00',
location: '郑州市农业农村局会议室',
participants: '市、区、乡三级兽医人员',
content: '新型冠状病毒疫情防控知识培训,包括个人防护、消毒等内容',
target: '提高兽医人员的疫情防控能力',
actualTarget: '培训覆盖200人次完成率100%',
budget: 6000,
notes: '',
status: 'completed',
createdAt: '2022-12-25'
},
{
id: '9',
name: '牛结核病净化示范区建设启动仪式',
type: 'other',
manager: '郑十一',
phone: '13112345678',
startTime: '2023-06-01 10:00',
endTime: '2023-06-01 12:00',
location: '巩义市某牛场',
participants: '省农业农村厅领导、市农业农村局领导、专家学者、养殖场代表',
content: '牛结核病净化示范区建设启动仪式',
target: '启动牛结核病净化示范区建设',
actualTarget: '',
budget: 10000,
notes: '',
status: 'planning',
createdAt: '2023-04-05'
},
{
id: '10',
name: '动物防疫物资发放活动',
type: 'promotion',
manager: '王十二',
phone: '13012345678',
startTime: '2023-02-20 09:00',
endTime: '2023-02-25 17:00',
location: '郑州市各区县乡镇',
participants: '市、区、乡三级兽医人员',
content: '向养殖场发放消毒药品、防护服等防疫物资',
target: '发放防疫物资覆盖1000家养殖场',
actualTarget: '发放防疫物资覆盖1200家养殖场',
budget: 25000,
notes: '',
status: 'completed',
createdAt: '2023-01-25'
}
])
// 表格列定义
const columns = [
{
title: '活动名称',
dataIndex: 'name',
key: 'name',
ellipsis: true
},
{
title: '活动类型',
dataIndex: 'type',
key: 'type',
width: 120,
customRender: ({ text }) => getTypeText(text)
},
{
title: '负责人',
dataIndex: 'manager',
key: 'manager',
width: 100
},
{
title: '活动时间',
key: 'time',
width: 200,
customRender: ({ record }) => formatShortTimeRange(record.startTime, record.endTime)
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
width: 80,
scopedSlots: { customRender: 'status' }
},
{
title: '创建时间',
dataIndex: 'createdAt',
key: 'createdAt',
width: 120
},
{
title: '操作',
key: 'action',
width: 180,
scopedSlots: { customRender: 'action' }
}
]
// 状态文本
const getStatusText = (status) => {
const statusMap = {
planning: '计划中',
ongoing: '进行中',
completed: '已完成',
cancelled: '已取消'
}
return statusMap[status] || status
}
// 状态颜色
const getStatusColor = (status) => {
const colorMap = {
planning: 'blue',
ongoing: 'processing',
completed: 'green',
cancelled: 'red'
}
return colorMap[status] || 'default'
}
// 类型文本
const getTypeText = (type) => {
const typeMap = {
training: '防疫培训',
inspection: '防疫检查',
promotion: '防疫宣传',
emergency_response: '应急处置',
other: '其他活动'
}
return typeMap[type] || type
}
// 格式化时间范围
const formatTimeRange = (startTime, endTime) => {
if (!startTime || !endTime) return '-'
return `${startTime}${endTime}`
}
// 格式化短时间范围(只显示日期)
const formatShortTimeRange = (startTime, endTime) => {
if (!startTime || !endTime) return '-'
const startDate = startTime.split(' ')[0]
const endDate = endTime.split(' ')[0]
return startDate === endDate ? startDate : `${startDate}${endDate}`
}
// 搜索处理
const handleSearch = () => {
// 在实际应用中这里应该调用API获取数据
pagination.current = 1
// 模拟搜索效果
message.success('搜索成功')
}
// 重置处理
const handleReset = () => {
searchKeyword.value = ''
typeFilter.value = ''
statusFilter.value = ''
dateRange.value = []
pagination.current = 1
}
// 新增活动
const handleAddActivity = () => {
// 重置表单
Object.assign(currentActivity, {
id: '',
name: '',
type: 'training',
manager: '',
phone: '',
startTime: '',
endTime: '',
location: '',
participants: '',
content: '',
target: '',
actualTarget: '',
budget: 0,
notes: '',
status: 'planning',
createdAt: ''
})
activityTimeRange.value = []
isEditing.value = false
isAddEditModalOpen.value = true
}
// 编辑活动
const handleEdit = (record) => {
// 复制记录到当前编辑对象
Object.assign(currentActivity, { ...record })
// 设置时间范围
if (record.startTime && record.endTime) {
activityTimeRange.value = [new Date(record.startTime), new Date(record.endTime)]
} else {
activityTimeRange.value = []
}
isEditing.value = true
isAddEditModalOpen.value = true
}
// 查看活动
const handleView = (record) => {
viewActivity.value = { ...record }
isViewModalOpen.value = true
}
// 删除活动
const handleDelete = (id) => {
// 在实际应用中这里应该调用API删除数据
const index = activitiesData.value.findIndex(item => item.id === id)
if (index !== -1) {
activitiesData.value.splice(index, 1)
message.success('删除成功')
}
}
// 保存活动
const handleSave = () => {
// 在实际应用中这里应该调用API保存数据
// 更新开始和结束时间
if (activityTimeRange.value.length === 2) {
currentActivity.startTime = activityTimeRange.value[0].toISOString().replace('T', ' ').substring(0, 16)
currentActivity.endTime = activityTimeRange.value[1].toISOString().replace('T', ' ').substring(0, 16)
}
if (isEditing.value) {
// 更新现有活动
const index = activitiesData.value.findIndex(item => item.id === currentActivity.id)
if (index !== -1) {
activitiesData.value[index] = { ...currentActivity }
}
} else {
// 添加新活动
const newActivity = {
...currentActivity,
id: Date.now().toString(),
createdAt: new Date().toISOString().split('T')[0]
}
activitiesData.value.unshift(newActivity)
}
isAddEditModalOpen.value = false
message.success(isEditing.value ? '更新成功' : '新增成功')
}
// 取消操作
const handleCancel = () => {
isAddEditModalOpen.value = false
}
// 关闭查看模态框
const handleCloseView = () => {
isViewModalOpen.value = false
}
// 开始活动
const handleStartActivity = (id) => {
// 在实际应用中这里应该调用API更新活动状态
const index = activitiesData.value.findIndex(item => item.id === id)
if (index !== -1) {
activitiesData.value[index].status = 'ongoing'
message.success('活动已开始')
}
}
// 完成活动
const handleCompleteActivity = (id) => {
// 在实际应用中这里应该调用API更新活动状态
const index = activitiesData.value.findIndex(item => item.id === id)
if (index !== -1) {
activitiesData.value[index].status = 'completed'
message.success('活动已完成')
}
}
// 取消活动
const handleCancelActivity = (id) => {
// 在实际应用中这里应该调用API更新活动状态
const index = activitiesData.value.findIndex(item => item.id === id)
if (index !== -1) {
activitiesData.value[index].status = 'cancelled'
message.success('活动已取消')
}
}
</script>
<style scoped>
h1 {
font-size: 20px;
font-weight: 600;
margin-bottom: 20px;
color: #333;
}
</style>

View File

@@ -0,0 +1,455 @@
<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="typeFilter" placeholder="机构类型" style="width: 120px;">
<a-select-option value="">全部</a-select-option>
<a-select-option value="center">防疫中心</a-select-option>
<a-select-option value="station">防疫站</a-select-option>
<a-select-option value="clinic">诊疗所</a-select-option>
</a-select>
<a-select v-model:value="levelFilter" placeholder="机构级别" style="width: 120px;">
<a-select-option value="">全部</a-select-option>
<a-select-option value="provincial">省级</a-select-option>
<a-select-option value="municipal">市级</a-select-option>
<a-select-option value="county">县级</a-select-option>
<a-select-option value="township">乡镇级</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="primary" @click="handleAdd">
<span class="iconfont icon-tianjia"></span> 新增机构
</a-button>
</div>
</a-card>
<!-- 机构列表 -->
<a-card>
<a-table
:columns="columns"
:data-source="agenciesData"
:pagination="pagination"
row-key="id"
:row-selection="{ selectedRowKeys, onChange: onSelectChange }"
:scroll="{ x: 'max-content' }"
>
<!-- 操作列 -->
<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>
</a-table>
</a-card>
<!-- 新增/编辑机构模态框 -->
<a-modal
v-model:open="isAddEditModalOpen"
:title="isEdit ? '编辑防疫机构' : '新增防疫机构'"
:footer="null"
width={600}
>
<a-form
:model="currentAgency"
layout="vertical"
ref="formRef"
>
<a-form-item label="机构名称" name="name" :rules="[{ required: true, message: '请输入机构名称' }]">
<a-input v-model:value="currentAgency.name" placeholder="请输入机构名称" />
</a-form-item>
<a-form-item label="机构编号" name="code" :rules="[{ required: true, message: '请输入机构编号' }]">
<a-input v-model:value="currentAgency.code" placeholder="请输入机构编号" />
</a-form-item>
<a-form-item label="机构类型" name="type" :rules="[{ required: true, message: '请选择机构类型' }]">
<a-select v-model:value="currentAgency.type" placeholder="请选择机构类型">
<a-select-option value="center">防疫中心</a-select-option>
<a-select-option value="station">防疫站</a-select-option>
<a-select-option value="clinic">诊疗所</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="机构级别" name="level" :rules="[{ required: true, message: '请选择机构级别' }]">
<a-select v-model:value="currentAgency.level" placeholder="请选择机构级别">
<a-select-option value="provincial">省级</a-select-option>
<a-select-option value="municipal">市级</a-select-option>
<a-select-option value="county">县级</a-select-option>
<a-select-option value="township">乡镇级</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="负责人" name="manager" :rules="[{ required: true, message: '请输入负责人姓名' }]">
<a-input v-model:value="currentAgency.manager" placeholder="请输入负责人姓名" />
</a-form-item>
<a-form-item label="联系电话" name="phone" :rules="[{ required: true, message: '请输入联系电话' }]">
<a-input v-model:value="currentAgency.phone" placeholder="请输入联系电话" />
</a-form-item>
<a-form-item label="地址" name="address" :rules="[{ required: true, message: '请输入机构地址' }]">
<a-input.TextArea v-model:value="currentAgency.address" placeholder="请输入机构地址" rows={3} />
</a-form-item>
<a-form-item label="备注" name="remarks">
<a-input.TextArea v-model:value="currentAgency.remarks" placeholder="请输入备注信息" rows={2} />
</a-form-item>
<div style="text-align: right;">
<a-button @click="isAddEditModalOpen = false" style="margin-right: 16px;">取消</a-button>
<a-button type="primary" @click="handleSave">保存</a-button>
</div>
</a-form>
</a-modal>
<!-- 查看机构详情模态框 -->
<a-modal
v-model:open="isViewModalOpen"
title="查看防疫机构详情"
:footer="null"
>
<div v-if="viewAgency">
<div style="margin-bottom: 16px;">
<span style="font-weight: bold; width: 120px; display: inline-block;">机构名称</span>
<span>{{ viewAgency.name }}</span>
</div>
<div style="margin-bottom: 16px;">
<span style="font-weight: bold; width: 120px; display: inline-block;">机构编号</span>
<span>{{ viewAgency.code }}</span>
</div>
<div style="margin-bottom: 16px;">
<span style="font-weight: bold; width: 120px; display: inline-block;">机构类型</span>
<span>{{ getTypeText(viewAgency.type) }}</span>
</div>
<div style="margin-bottom: 16px;">
<span style="font-weight: bold; width: 120px; display: inline-block;">机构级别</span>
<span>{{ getLevelText(viewAgency.level) }}</span>
</div>
<div style="margin-bottom: 16px;">
<span style="font-weight: bold; width: 120px; display: inline-block;">负责人</span>
<span>{{ viewAgency.manager }}</span>
</div>
<div style="margin-bottom: 16px;">
<span style="font-weight: bold; width: 120px; display: inline-block;">联系电话</span>
<span>{{ viewAgency.phone }}</span>
</div>
<div style="margin-bottom: 16px;">
<span style="font-weight: bold; width: 120px; display: inline-block;">地址</span>
<span>{{ viewAgency.address }}</span>
</div>
<div style="margin-bottom: 16px;">
<span style="font-weight: bold; width: 120px; display: inline-block;">成立时间</span>
<span>{{ viewAgency.establishmentDate }}</span>
</div>
<div v-if="viewAgency.remarks">
<span style="font-weight: bold; width: 120px; display: inline-block;">备注</span>
<span>{{ viewAgency.remarks }}</span>
</div>
</div>
<div style="text-align: right; margin-top: 24px;">
<a-button @click="isViewModalOpen = false">关闭</a-button>
</div>
</a-modal>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { message } from 'ant-design-vue'
// 搜索条件
const searchKeyword = ref('')
const typeFilter = ref('')
const levelFilter = ref('')
// 分页配置
const pagination = ref({
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: total => `${total} 条数据`
})
// 选中行
const selectedRowKeys = ref([])
const onSelectChange = (newSelectedRowKeys) => {
selectedRowKeys.value = newSelectedRowKeys
}
// 表单引用
const formRef = ref(null)
// 模态框状态
const isAddEditModalOpen = ref(false)
const isViewModalOpen = ref(false)
const isEdit = ref(false)
// 当前编辑/新增的机构
const currentAgency = reactive({
name: '',
code: '',
type: 'station',
level: 'county',
manager: '',
phone: '',
address: '',
remarks: ''
})
// 当前查看的机构
const viewAgency = ref(null)
// 机构列表数据
const agenciesData = ref([
{
id: '1',
name: '省动物防疫中心',
code: 'EP001',
type: 'center',
level: 'provincial',
manager: '张三',
phone: '13800138001',
address: '北京市朝阳区农展馆南路5号',
establishmentDate: '2005-06-15',
remarks: '省级防疫管理机构'
},
{
id: '2',
name: '市动物防疫站',
code: 'EP002',
type: 'station',
level: 'municipal',
manager: '李四',
phone: '13800138002',
address: '北京市海淀区中关村南大街12号',
establishmentDate: '2008-09-20',
remarks: '市级防疫执行机构'
},
{
id: '3',
name: '县动物防疫站',
code: 'EP003',
type: 'station',
level: 'county',
manager: '王五',
phone: '13800138003',
address: '北京市顺义区府前中街5号',
establishmentDate: '2010-03-10',
remarks: '县级防疫执行机构'
},
{
id: '4',
name: '乡镇动物防疫诊疗所',
code: 'EP004',
type: 'clinic',
level: 'township',
manager: '赵六',
phone: '13800138004',
address: '北京市昌平区小汤山镇政府路28号',
establishmentDate: '2012-05-18',
remarks: '乡镇级防疫服务机构'
},
{
id: '5',
name: '区级动物防疫中心',
code: 'EP005',
type: 'center',
level: 'county',
manager: '孙七',
phone: '13800138005',
address: '北京市通州区运河东大街55号',
establishmentDate: '2009-11-25',
remarks: '区级防疫管理机构'
}
])
// 表格列定义
const columns = [
{
title: '机构编号',
dataIndex: 'code',
key: 'code',
width: 120
},
{
title: '机构名称',
dataIndex: 'name',
key: 'name',
ellipsis: true
},
{
title: '机构类型',
dataIndex: 'type',
key: 'type',
width: 100,
customRender: ({ text }) => getTypeText(text)
},
{
title: '机构级别',
dataIndex: 'level',
key: 'level',
width: 100,
customRender: ({ text }) => getLevelText(text)
},
{
title: '负责人',
dataIndex: 'manager',
key: 'manager',
width: 100
},
{
title: '联系电话',
dataIndex: 'phone',
key: 'phone',
width: 120
},
{
title: '成立时间',
dataIndex: 'establishmentDate',
key: 'establishmentDate',
width: 120
},
{
title: '操作',
key: 'action',
width: 150,
slots: { customRender: 'action' }
}
]
// 获取机构类型文本
const getTypeText = (type) => {
const typeMap = {
'center': '防疫中心',
'station': '防疫站',
'clinic': '诊疗所'
}
return typeMap[type] || type
}
// 获取机构级别文本
const getLevelText = (level) => {
const levelMap = {
'provincial': '省级',
'municipal': '市级',
'county': '县级',
'township': '乡镇级'
}
return levelMap[level] || level
}
// 处理搜索
const handleSearch = () => {
pagination.value.current = 1
// 这里应该调用API进行搜索现在使用模拟数据
message.success('搜索成功')
}
// 处理重置
const handleReset = () => {
searchKeyword.value = ''
typeFilter.value = ''
levelFilter.value = ''
pagination.value.current = 1
// 这里应该重置搜索条件并重新加载数据
}
// 处理新增
const handleAdd = () => {
isEdit.value = false
// 重置表单数据
Object.keys(currentAgency).forEach(key => {
currentAgency[key] = ''
})
currentAgency.type = 'station'
currentAgency.level = 'county'
isAddEditModalOpen.value = true
}
// 处理编辑
const handleEdit = (record) => {
isEdit.value = true
// 复制记录数据到当前编辑对象
Object.assign(currentAgency, JSON.parse(JSON.stringify(record)))
isAddEditModalOpen.value = true
}
// 处理查看
const handleView = (record) => {
viewAgency.value = record
isViewModalOpen.value = true
}
// 处理删除
const handleDelete = (id) => {
// 显示确认对话框
if (confirm('确定要删除该防疫机构吗?')) {
// 这里应该调用API进行删除现在使用模拟数据
const index = agenciesData.value.findIndex(item => item.id === id)
if (index !== -1) {
agenciesData.value.splice(index, 1)
message.success('删除成功')
}
}
}
// 处理保存
const handleSave = () => {
if (formRef.value) {
formRef.value.validate().then(() => {
// 这里应该调用API进行保存现在使用模拟数据
if (isEdit.value) {
// 编辑现有记录
const index = agenciesData.value.findIndex(item => item.id === currentAgency.id)
if (index !== -1) {
agenciesData.value[index] = { ...currentAgency }
}
} else {
// 新增记录
const newAgency = { ...currentAgency }
newAgency.id = String(Date.now())
newAgency.establishmentDate = new Date().toISOString().split('T')[0]
agenciesData.value.unshift(newAgency)
}
isAddEditModalOpen.value = false
message.success(isEdit.value ? '编辑成功' : '新增成功')
}).catch(() => {
message.error('请检查表单数据')
})
}
}
// 组件挂载时初始化
onMounted(() => {
// 初始化分页总数
pagination.value.total = agenciesData.value.length
})
</script>
<style scoped>
h1 {
font-size: 20px;
font-weight: 600;
margin-bottom: 20px;
color: #333;
}
</style>

View File

@@ -0,0 +1,479 @@
<template>
<div>
<page-header title="防疫机构管理"/>
<!-- 搜索和操作栏 -->
<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>
<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="primary" danger @click="handleAddAgency">
<span class="iconfont icon-tianjia"></span> 新增机构
</a-button>
</div>
</a-card>
<!-- 机构列表 -->
<a-card>
<a-table
:columns="columns"
:data-source="agenciesData"
:pagination="pagination"
row-key="id"
:row-selection="{ selectedRowKeys, onChange: onSelectChange }"
:scroll="{ x: 'max-content' }"
>
<!-- 状态列 -->
<template #bodyCell:status="{ record }">
<a-tag :color="getStatusColor(record.status)">{{ getStatusText(record.status) }}</a-tag>
</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>
<a-button size="small" @click="handleToggleStatus(record)">
{{ record.status === 'active' ? '禁用' : '启用' }}
</a-button>
</div>
</template>
</a-table>
</a-card>
<!-- 新增/编辑机构模态框 -->
<a-modal
v-model:open="isAddEditModalOpen"
:title="isEditMode ? '编辑防疫机构' : '新增防疫机构'"
:footer="null"
width={700}
>
<a-form
:model="currentAgency"
layout="vertical"
>
<a-form-item
label="机构名称"
name="name"
:rules="[{ required: true, message: '请输入机构名称' }]"
>
<a-input v-model:value="currentAgency.name" placeholder="请输入机构名称" />
</a-form-item>
<a-form-item
label="负责人"
name="director"
:rules="[{ required: true, message: '请输入负责人姓名' }]"
>
<a-input v-model:value="currentAgency.director" placeholder="请输入负责人姓名" />
</a-form-item>
<a-form-item
label="联系电话"
name="phone"
:rules="[{ required: true, message: '请输入联系电话' }]"
>
<a-input v-model:value="currentAgency.phone" placeholder="请输入联系电话" />
</a-form-item>
<a-form-item
label="地址"
name="address"
:rules="[{ required: true, message: '请输入机构地址' }]"
>
<a-input v-model:value="currentAgency.address" placeholder="请输入机构地址" />
</a-form-item>
<a-form-item
label="邮箱"
name="email"
>
<a-input v-model:value="currentAgency.email" placeholder="请输入邮箱地址" />
</a-form-item>
<a-form-item
label="机构类型"
name="type"
:rules="[{ required: true, message: '请选择机构类型' }]"
>
<a-select v-model:value="currentAgency.type" placeholder="请选择机构类型">
<a-select-option value="center">中心防疫站</a-select-option>
<a-select-option value="branch">分站</a-select-option>
<a-select-option value="mobile">流动防疫站</a-select-option>
</a-select>
</a-form-item>
<a-form-item
label="简介"
name="description"
>
<a-textarea v-model:value="currentAgency.description" placeholder="请输入机构简介" :rows="4" />
</a-form-item>
</a-form>
<div style="text-align: right; margin-top: 20px;">
<a-button @click="handleCancel">取消</a-button>
<a-button type="primary" @click="handleSave">确定</a-button>
</div>
</a-modal>
<!-- 查看机构详情模态框 -->
<a-modal
v-model:open="isViewModalOpen"
title="查看防疫机构详情"
:footer="null"
width={800}
>
<div v-if="viewAgency" class="agency-detail">
<div class="detail-row">
<span class="detail-label">机构名称</span>
<span class="detail-value">{{ viewAgency.name }}</span>
</div>
<div class="detail-row">
<span class="detail-label">负责人</span>
<span class="detail-value">{{ viewAgency.director }}</span>
</div>
<div class="detail-row">
<span class="detail-label">联系电话</span>
<span class="detail-value">{{ viewAgency.phone }}</span>
</div>
<div class="detail-row">
<span class="detail-label">地址</span>
<span class="detail-value">{{ viewAgency.address }}</span>
</div>
<div class="detail-row">
<span class="detail-label">邮箱</span>
<span class="detail-value">{{ viewAgency.email }}</span>
</div>
<div class="detail-row">
<span class="detail-label">机构类型</span>
<span class="detail-value">{{ getAgencyTypeText(viewAgency.type) }}</span>
</div>
<div class="detail-row">
<span class="detail-label">状态</span>
<a-tag :color="getStatusColor(viewAgency.status)">{{ getStatusText(viewAgency.status) }}</a-tag>
</div>
<div class="detail-row">
<span class="detail-label">成立时间</span>
<span class="detail-value">{{ viewAgency.establishmentDate }}</span>
</div>
<div class="detail-row">
<span class="detail-label">简介</span>
<span class="detail-value">{{ viewAgency.description }}</span>
</div>
</div>
<div style="text-align: right; margin-top: 20px;">
<a-button @click="closeViewModal">关闭</a-button>
</div>
</a-modal>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { message } from 'ant-design-vue'
import PageHeader from '@/layout/PageHeader.vue'
// 搜索条件
const searchKeyword = ref('')
const statusFilter = ref('')
// 表格数据
const selectedRowKeys = ref([])
const agenciesData = ref([
{
id: '1',
name: '中心动物防疫站',
director: '张三',
phone: '13800138001',
address: '市南区健康路100号',
email: 'center@animalhealth.gov.cn',
type: 'center',
status: 'active',
establishmentDate: '2010-01-15',
description: '负责全市动物防疫工作的统筹管理和技术指导'
},
{
id: '2',
name: '东区动物防疫分站',
director: '李四',
phone: '13800138002',
address: '市东区防疫路50号',
email: 'east@animalhealth.gov.cn',
type: 'branch',
status: 'active',
establishmentDate: '2012-05-20',
description: '负责东区范围内的动物防疫工作'
},
{
id: '3',
name: '西区动物防疫分站',
director: '王五',
phone: '13800138003',
address: '市西区健康大道200号',
email: 'west@animalhealth.gov.cn',
type: 'branch',
status: 'active',
establishmentDate: '2013-03-10',
description: '负责西区范围内的动物防疫工作'
},
{
id: '4',
name: '北区动物防疫分站',
director: '赵六',
phone: '13800138004',
address: '市北区安全路88号',
email: 'north@animalhealth.gov.cn',
type: 'branch',
status: 'active',
establishmentDate: '2014-07-05',
description: '负责北区范围内的动物防疫工作'
},
{
id: '5',
name: '南区动物防疫分站',
director: '钱七',
phone: '13800138005',
address: '市南区健康路66号',
email: 'south@animalhealth.gov.cn',
type: 'branch',
status: 'active',
establishmentDate: '2015-02-28',
description: '负责南区范围内的动物防疫工作'
},
{
id: '6',
name: '流动防疫队',
director: '孙八',
phone: '13800138006',
address: '市中区应急中心',
email: 'mobile@animalhealth.gov.cn',
type: 'mobile',
status: 'active',
establishmentDate: '2016-09-15',
description: '负责偏远地区和突发事件的动物防疫工作'
}
])
// 分页配置
const pagination = {
current: 1,
pageSize: 10,
total: 6,
showSizeChanger: true,
showQuickJumper: true,
pageSizeOptions: ['10', '20', '50', '100']
}
// 模态框状态
const isAddEditModalOpen = ref(false)
const isEditMode = ref(false)
const isViewModalOpen = ref(false)
// 当前编辑/查看的机构
const currentAgency = reactive({
id: '',
name: '',
director: '',
phone: '',
address: '',
email: '',
type: '',
status: 'active',
description: ''
})
const viewAgency = ref(null)
// 表格列定义
const columns = [
{
title: '机构名称',
dataIndex: 'name',
key: 'name'
},
{
title: '负责人',
dataIndex: 'director',
key: 'director',
width: 100
},
{
title: '联系电话',
dataIndex: 'phone',
key: 'phone',
width: 120
},
{
title: '机构类型',
dataIndex: 'type',
key: 'type',
width: 100,
customRender: ({ text }) => getAgencyTypeText(text)
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
width: 80
},
{
title: '成立时间',
dataIndex: 'establishmentDate',
key: 'establishmentDate',
width: 120
},
{
title: '操作',
key: 'action',
width: 180,
fixed: 'right'
}
]
// 获取机构类型文本
const getAgencyTypeText = (type) => {
const typeMap = {
'center': '中心防疫站',
'branch': '分站',
'mobile': '流动防疫站'
}
return typeMap[type] || type
}
// 获取状态文本
const getStatusText = (status) => {
const statusMap = {
'active': '启用',
'inactive': '禁用'
}
return statusMap[status] || status
}
// 获取状态颜色
const getStatusColor = (status) => {
const colorMap = {
'active': 'green',
'inactive': 'red'
}
return colorMap[status] || 'blue'
}
// 行选择变化
const onSelectChange = (newSelectedRowKeys) => {
selectedRowKeys.value = newSelectedRowKeys
}
// 搜索
const handleSearch = () => {
// 实际项目中这里应该调用API进行搜索
message.success('搜索成功')
}
// 重置
const handleReset = () => {
searchKeyword.value = ''
statusFilter.value = ''
}
// 新增机构
const handleAddAgency = () => {
isEditMode.value = false
Object.assign(currentAgency, {
id: '',
name: '',
director: '',
phone: '',
address: '',
email: '',
type: '',
status: 'active',
description: ''
})
isAddEditModalOpen.value = true
}
// 编辑机构
const handleEdit = (record) => {
isEditMode.value = true
Object.assign(currentAgency, { ...record })
isAddEditModalOpen.value = true
}
// 查看机构
const handleView = (record) => {
viewAgency.value = { ...record }
isViewModalOpen.value = true
}
// 删除机构
const handleDelete = (id) => {
// 实际项目中这里应该调用API进行删除
message.success('删除成功')
}
// 切换状态
const handleToggleStatus = (record) => {
// 实际项目中这里应该调用API切换状态
message.success(`状态已切换为${record.status === 'active' ? '禁用' : '启用'}`)
}
// 保存机构
const handleSave = () => {
// 实际项目中这里应该调用API保存数据
message.success(isEditMode.value ? '编辑成功' : '新增成功')
isAddEditModalOpen.value = false
}
// 取消
const handleCancel = () => {
isAddEditModalOpen.value = false
}
// 关闭查看模态框
const closeViewModal = () => {
isViewModalOpen.value = false
viewAgency.value = null
}
</script>
<style scoped>
.agency-detail {
padding: 20px;
}
.detail-row {
margin-bottom: 16px;
display: flex;
align-items: flex-start;
}
.detail-label {
font-weight: 600;
width: 100px;
flex-shrink: 0;
}
.detail-value {
flex: 1;
word-break: break-word;
}
</style>

View File

@@ -0,0 +1,665 @@
<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="typeFilter" placeholder="防疫类型" style="width: 120px;">
<a-select-option value="">全部</a-select-option>
<a-select-option value="vaccination">疫苗接种</a-select-option>
<a-select-option value="disinfection">消毒</a-select-option>
<a-select-option value="health_check">健康检查</a-select-option>
<a-select-option value="other">其他</a-select-option>
</a-select>
<a-select v-model:value="statusFilter" placeholder="状态" style="width: 120px;">
<a-select-option value="">全部</a-select-option>
<a-select-option value="completed">已完成</a-select-option>
<a-select-option value="pending">待完成</a-select-option>
<a-select-option value="failed">未通过</a-select-option>
</a-select>
<a-range-picker
v-model:value="dateRange"
style="width: 300px;"
format="YYYY-MM-DD"
:placeholder="['开始日期', '结束日期']"
/>
<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="primary" danger @click="handleAddRecord">
<span class="iconfont icon-tianjia"></span> 新增记录
</a-button>
</div>
</a-card>
<!-- 数据列表 -->
<a-card>
<a-table
:columns="columns"
:data-source="recordsData"
:pagination="pagination"
row-key="id"
:row-selection="{ selectedRowKeys, onChange: onSelectChange }"
:scroll="{ x: 'max-content' }"
>
<!-- 状态列 -->
<template #bodyCell:status="{ record }">
<a-tag :color="getStatusColor(record.status)">{{ getStatusText(record.status) }}</a-tag>
</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>
</a-table>
</a-card>
<!-- 新增/编辑记录模态框 -->
<a-modal
v-model:open="isAddEditModalOpen"
:title="isEditing ? '编辑防疫记录' : '新增防疫记录'"
:footer="null"
width={700}
>
<a-form
:model="currentRecord"
layout="vertical"
>
<a-form-item label="养殖场名称"
:rules="[{ required: true, message: '请输入养殖场名称' }]">
<a-input v-model:value="currentRecord.farmName" placeholder="请输入养殖场名称" />
</a-form-item>
<a-form-item label="防疫类型"
:rules="[{ required: true, message: '请选择防疫类型' }]">
<a-select v-model:value="currentRecord.type" placeholder="请选择防疫类型">
<a-select-option value="vaccination">疫苗接种</a-select-option>
<a-select-option value="disinfection">消毒</a-select-option>
<a-select-option value="health_check">健康检查</a-select-option>
<a-select-option value="other">其他</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="防疫员"
:rules="[{ required: true, message: '请输入防疫员姓名' }]">
<a-input v-model:value="currentRecord.epidemicStaff" placeholder="请输入防疫员姓名" />
</a-form-item>
<a-form-item label="联系电话"
:rules="[
{ required: true, message: '请输入联系电话' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码' }
]">
<a-input v-model:value="currentRecord.phone" placeholder="请输入联系电话" />
</a-form-item>
<a-form-item label="防疫日期"
:rules="[{ required: true, message: '请选择防疫日期' }]">
<a-date-picker
v-model:value="currentRecord.epidemicDate"
style="width: 100%;"
format="YYYY-MM-DD"
/>
</a-form-item>
<a-form-item label="防疫数量" v-if="currentRecord.type === 'vaccination'"
:rules="[{ required: true, message: '请输入防疫数量', type: 'number' }]">
<a-input-number v-model:value="currentRecord.count" min="0" placeholder="请输入防疫数量" />
</a-form-item>
<a-form-item label="使用疫苗" v-if="currentRecord.type === 'vaccination'"
:rules="[{ required: true, message: '请输入使用疫苗' }]">
<a-input v-model:value="currentRecord.vaccineName" placeholder="请输入使用疫苗" />
</a-form-item>
<a-form-item label="防疫范围" v-if="currentRecord.type === 'disinfection'"
:rules="[{ required: true, message: '请输入防疫范围' }]">
<a-input.TextArea v-model:value="currentRecord.area" placeholder="请输入防疫范围" rows={2} />
</a-form-item>
<a-form-item label="防疫药品" v-if="currentRecord.type === 'disinfection'"
:rules="[{ required: true, message: '请输入防疫药品' }]">
<a-input v-model:value="currentRecord.disinfectant" placeholder="请输入防疫药品" />
</a-form-item>
<a-form-item label="检查结果" v-if="currentRecord.type === 'health_check'"
:rules="[{ required: true, message: '请输入检查结果' }]">
<a-select v-model:value="currentRecord.healthResult" placeholder="请选择检查结果">
<a-select-option value="normal">正常</a-select-option>
<a-select-option value="abnormal">异常</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="防疫描述" v-if="currentRecord.type === 'other'"
:rules="[{ required: true, message: '请输入防疫描述' }]">
<a-input.TextArea v-model:value="currentRecord.description" placeholder="请输入防疫描述" rows={3} />
</a-form-item>
<a-form-item label="备注">
<a-input.TextArea v-model:value="currentRecord.notes" placeholder="请输入备注信息" rows={4} />
</a-form-item>
<a-form-item label="状态">
<a-select v-model:value="currentRecord.status" placeholder="请选择状态">
<a-select-option value="completed">已完成</a-select-option>
<a-select-option value="pending">待完成</a-select-option>
<a-select-option value="failed">未通过</a-select-option>
</a-select>
</a-form-item>
</a-form>
<div style="display: flex; justify-content: flex-end; gap: 12px; margin-top: 24px;">
<a-button @click="handleCancel">取消</a-button>
<a-button type="primary" @click="handleSave">保存</a-button>
</div>
</a-modal>
<!-- 查看记录详情模态框 -->
<a-modal
v-model:open="isViewModalOpen"
title="查看防疫记录详情"
:footer="null"
width={700}
>
<div v-if="viewRecord">
<div style="margin-bottom: 16px;">
<h3 style="margin-bottom: 8px;">基本信息</h3>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px;">
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">养殖场名称</p>
<p>{{ viewRecord.farmName }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">防疫类型</p>
<p>{{ getTypeText(viewRecord.type) }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">防疫员</p>
<p>{{ viewRecord.epidemicStaff }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">联系电话</p>
<p>{{ viewRecord.phone }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">防疫日期</p>
<p>{{ formatDate(viewRecord.epidemicDate) }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">状态</p>
<p><a-tag :color="getStatusColor(viewRecord.status)">{{ getStatusText(viewRecord.status) }}</a-tag></p>
</div>
</div>
</div>
<div style="margin-bottom: 16px;">
<h3 style="margin-bottom: 8px;">详细信息</h3>
<div v-if="viewRecord.type === 'vaccination'">
<p style="color: #8c8c8c; margin-bottom: 4px;">防疫数量</p>
<p>{{ viewRecord.count }} /</p>
<p style="color: #8c8c8c; margin-bottom: 4px; margin-top: 8px;">使用疫苗</p>
<p>{{ viewRecord.vaccineName }}</p>
</div>
<div v-else-if="viewRecord.type === 'disinfection'">
<p style="color: #8c8c8c; margin-bottom: 4px;">防疫范围</p>
<p>{{ viewRecord.area }}</p>
<p style="color: #8c8c8c; margin-bottom: 4px; margin-top: 8px;">防疫药品</p>
<p>{{ viewRecord.disinfectant }}</p>
</div>
<div v-else-if="viewRecord.type === 'health_check'">
<p style="color: #8c8c8c; margin-bottom: 4px;">检查结果</p>
<p>{{ viewRecord.healthResult === 'normal' ? '正常' : '异常' }}</p>
</div>
<div v-else-if="viewRecord.type === 'other'">
<p style="color: #8c8c8c; margin-bottom: 4px;">防疫描述</p>
<p>{{ viewRecord.description }}</p>
</div>
<div style="margin-top: 12px;">
<p style="color: #8c8c8c; margin-bottom: 4px;">备注</p>
<p>{{ viewRecord.notes || '-' }}</p>
</div>
</div>
</div>
<div style="display: flex; justify-content: flex-end; margin-top: 24px;">
<a-button @click="handleCloseView">关闭</a-button>
</div>
</a-modal>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { message } from 'ant-design-vue'
// 搜索条件
const searchKeyword = ref('')
const typeFilter = ref('')
const statusFilter = ref('')
const dateRange = ref([])
// 分页配置
const pagination = reactive({
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total, range) => `${range[0]}-${range[1]} 条,共 ${total}`
})
// 选中行
const selectedRowKeys = ref([])
const onSelectChange = (newSelectedRowKeys) => {
selectedRowKeys.value = newSelectedRowKeys
}
// 模态框状态
const isAddEditModalOpen = ref(false)
const isViewModalOpen = ref(false)
const isEditing = ref(false)
// 当前编辑/查看的记录
const currentRecord = reactive({
id: '',
farmName: '',
type: 'vaccination',
epidemicStaff: '',
phone: '',
epidemicDate: null,
count: 0,
vaccineName: '',
area: '',
disinfectant: '',
healthResult: 'normal',
description: '',
notes: '',
status: 'completed',
createdAt: ''
})
const viewRecord = ref(null)
// 记录列表数据(模拟数据)
const recordsData = ref([
{
id: '1',
farmName: '郑州市金水区阳光养殖场',
type: 'vaccination',
epidemicStaff: '张三',
phone: '13812345678',
epidemicDate: '2023-10-01',
count: 150,
vaccineName: '口蹄疫疫苗',
area: '',
disinfectant: '',
healthResult: '',
description: '',
notes: '无异常',
status: 'completed',
createdAt: '2023-10-01'
},
{
id: '2',
farmName: '新郑市绿源养殖场',
type: 'disinfection',
epidemicStaff: '李四',
phone: '13912345678',
epidemicDate: '2023-10-02',
count: 0,
vaccineName: '',
area: '养殖场全场消毒,重点消毒牛舍、饲料仓库、消毒池等区域',
disinfectant: '含氯消毒液',
healthResult: '',
description: '',
notes: '消毒彻底,符合标准',
status: 'completed',
createdAt: '2023-10-02'
},
{
id: '3',
farmName: '新密市祥和养殖场',
type: 'health_check',
epidemicStaff: '王五',
phone: '13712345678',
epidemicDate: '2023-10-03',
count: 0,
vaccineName: '',
area: '',
disinfectant: '',
healthResult: 'normal',
description: '',
notes: '牛群健康状况良好',
status: 'completed',
createdAt: '2023-10-03'
},
{
id: '4',
farmName: '登封市幸福养殖场',
type: 'vaccination',
epidemicStaff: '赵六',
phone: '13612345678',
epidemicDate: '2023-10-04',
count: 200,
vaccineName: '牛瘟疫苗',
area: '',
disinfectant: '',
healthResult: '',
description: '',
notes: '部分牛只接种后有轻微发热现象',
status: 'completed',
createdAt: '2023-10-04'
},
{
id: '5',
farmName: '中牟县希望养殖场',
type: 'other',
epidemicStaff: '钱七',
phone: '13512345678',
epidemicDate: '2023-10-05',
count: 0,
vaccineName: '',
area: '',
disinfectant: '',
healthResult: '',
description: '牛群驱虫,使用阿维菌素进行全群驱虫',
notes: '按计划完成驱虫工作',
status: 'completed',
createdAt: '2023-10-05'
},
{
id: '6',
farmName: '荥阳市快乐养殖场',
type: 'vaccination',
epidemicStaff: '孙八',
phone: '13412345678',
epidemicDate: '2023-10-06',
count: 180,
vaccineName: '布鲁氏菌病疫苗',
area: '',
disinfectant: '',
healthResult: '',
description: '',
notes: '无异常反应',
status: 'completed',
createdAt: '2023-10-06'
},
{
id: '7',
farmName: '巩义市明星养殖场',
type: 'disinfection',
epidemicStaff: '周九',
phone: '13312345678',
epidemicDate: '2023-10-07',
count: 0,
vaccineName: '',
area: '养殖场周边环境消毒',
disinfectant: '过氧乙酸',
healthResult: '',
description: '',
notes: '消毒效果良好',
status: 'completed',
createdAt: '2023-10-07'
},
{
id: '8',
farmName: '惠济区温馨养殖场',
type: 'health_check',
epidemicStaff: '吴十',
phone: '13212345678',
epidemicDate: '2023-10-08',
count: 0,
vaccineName: '',
area: '',
disinfectant: '',
healthResult: 'abnormal',
description: '',
notes: '发现2头牛只精神不振已隔离观察',
status: 'completed',
createdAt: '2023-10-08'
},
{
id: '9',
farmName: '二七区红火养殖场',
type: 'vaccination',
epidemicStaff: '郑十一',
phone: '13112345678',
epidemicDate: '2023-10-09',
count: 120,
vaccineName: '口蹄疫疫苗',
area: '',
disinfectant: '',
healthResult: '',
description: '',
notes: '按时完成接种工作',
status: 'completed',
createdAt: '2023-10-09'
},
{
id: '10',
farmName: '中原区丰收养殖场',
type: 'other',
epidemicStaff: '王十二',
phone: '13012345678',
epidemicDate: '2023-10-10',
count: 0,
vaccineName: '',
area: '',
disinfectant: '',
healthResult: '',
description: '牛群营养状况评估,对瘦弱牛只进行重点饲养管理',
notes: '已制定饲养调整方案',
status: 'pending',
createdAt: '2023-10-10'
}
])
// 表格列定义
const columns = [
{
title: '养殖场名称',
dataIndex: 'farmName',
key: 'farmName',
ellipsis: true
},
{
title: '防疫类型',
dataIndex: 'type',
key: 'type',
width: 120,
customRender: ({ text }) => getTypeText(text)
},
{
title: '防疫员',
dataIndex: 'epidemicStaff',
key: 'epidemicStaff',
width: 100
},
{
title: '防疫日期',
dataIndex: 'epidemicDate',
key: 'epidemicDate',
width: 120
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
width: 80,
scopedSlots: { customRender: 'status' }
},
{
title: '创建时间',
dataIndex: 'createdAt',
key: 'createdAt',
width: 120
},
{
title: '操作',
key: 'action',
width: 150,
scopedSlots: { customRender: 'action' }
}
]
// 状态文本
const getStatusText = (status) => {
const statusMap = {
completed: '已完成',
pending: '待完成',
failed: '未通过'
}
return statusMap[status] || status
}
// 状态颜色
const getStatusColor = (status) => {
const colorMap = {
completed: 'green',
pending: 'orange',
failed: 'red'
}
return colorMap[status] || 'default'
}
// 类型文本
const getTypeText = (type) => {
const typeMap = {
vaccination: '疫苗接种',
disinfection: '消毒',
health_check: '健康检查',
other: '其他'
}
return typeMap[type] || type
}
// 格式化日期
const formatDate = (date) => {
if (!date) return '-'
if (typeof date === 'string') return date
return date.toISOString().split('T')[0]
}
// 搜索处理
const handleSearch = () => {
// 在实际应用中这里应该调用API获取数据
pagination.current = 1
// 模拟搜索效果
message.success('搜索成功')
}
// 重置处理
const handleReset = () => {
searchKeyword.value = ''
typeFilter.value = ''
statusFilter.value = ''
dateRange.value = []
pagination.current = 1
}
// 新增记录
const handleAddRecord = () => {
// 重置表单
Object.assign(currentRecord, {
id: '',
farmName: '',
type: 'vaccination',
epidemicStaff: '',
phone: '',
epidemicDate: null,
count: 0,
vaccineName: '',
area: '',
disinfectant: '',
healthResult: 'normal',
description: '',
notes: '',
status: 'completed',
createdAt: ''
})
isEditing.value = false
isAddEditModalOpen.value = true
}
// 编辑记录
const handleEdit = (record) => {
// 复制记录到当前编辑对象
Object.assign(currentRecord, { ...record })
isEditing.value = true
isAddEditModalOpen.value = true
}
// 查看记录
const handleView = (record) => {
viewRecord.value = { ...record }
isViewModalOpen.value = true
}
// 删除记录
const handleDelete = (id) => {
// 在实际应用中这里应该调用API删除数据
const index = recordsData.value.findIndex(item => item.id === id)
if (index !== -1) {
recordsData.value.splice(index, 1)
message.success('删除成功')
}
}
// 保存记录
const handleSave = () => {
// 在实际应用中这里应该调用API保存数据
if (isEditing.value) {
// 更新现有记录
const index = recordsData.value.findIndex(item => item.id === currentRecord.id)
if (index !== -1) {
recordsData.value[index] = { ...currentRecord }
}
} else {
// 添加新记录
const newRecord = {
...currentRecord,
id: Date.now().toString(),
createdAt: new Date().toISOString().split('T')[0]
}
recordsData.value.unshift(newRecord)
}
isAddEditModalOpen.value = false
message.success(isEditing.value ? '更新成功' : '新增成功')
}
// 取消操作
const handleCancel = () => {
isAddEditModalOpen.value = false
}
// 关闭查看模态框
const handleCloseView = () => {
isViewModalOpen.value = false
}
</script>
<style scoped>
h1 {
font-size: 20px;
font-weight: 600;
margin-bottom: 20px;
color: #333;
}
</style>

View File

@@ -0,0 +1,774 @@
<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="typeFilter" placeholder="疫苗类型" style="width: 120px;">
<a-select-option value="">全部</a-select-option>
<a-select-option value="foot_and_mouth_disease">口蹄疫疫苗</a-select-option>
<a-select-option value="bovine_tuberculosis">牛结核病疫苗</a-select-option>
<a-select-option value="brucellosis">布鲁氏菌病疫苗</a-select-option>
<a-select-option value="rabies">狂犬病疫苗</a-select-option>
<a-select-option value="other">其他疫苗</a-select-option>
</a-select>
<a-select v-model:value="statusFilter" placeholder="状态" style="width: 120px;">
<a-select-option value="">全部</a-select-option>
<a-select-option value="valid">有效</a-select-option>
<a-select-option value="expired">过期</a-select-option>
<a-select-option value="low_stock">库存不足</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="primary" danger @click="handleAddVaccine">
<span class="iconfont icon-tianjia"></span> 新增疫苗
</a-button>
</div>
</a-card>
<!-- 数据列表 -->
<a-card>
<a-table
:columns="columns"
:data-source="vaccinesData"
:pagination="pagination"
row-key="id"
:row-selection="{ selectedRowKeys, onChange: onSelectChange }"
:scroll="{ x: 'max-content' }"
>
<!-- 状态列 -->
<template #bodyCell:status="{ record }">
<a-tag :color="getStatusColor(record.status)">{{ getStatusText(record.status) }}</a-tag>
</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>
<a-button size="small" @click="handleBatchIn(record.id)">入库</a-button>
<a-button size="small" @click="handleBatchOut(record.id)">出库</a-button>
</div>
</template>
</a-table>
</a-card>
<!-- 新增/编辑疫苗模态框 -->
<a-modal
v-model:open="isAddEditModalOpen"
:title="isEditing ? '编辑疫苗信息' : '新增疫苗信息'"
:footer="null"
width={600}
>
<a-form
:model="currentVaccine"
layout="vertical"
>
<a-form-item label="疫苗名称"
:rules="[{ required: true, message: '请输入疫苗名称' }]">
<a-input v-model:value="currentVaccine.name" placeholder="请输入疫苗名称" />
</a-form-item>
<a-form-item label="疫苗类型"
:rules="[{ required: true, message: '请选择疫苗类型' }]">
<a-select v-model:value="currentVaccine.type" placeholder="请选择疫苗类型">
<a-select-option value="foot_and_mouth_disease">口蹄疫疫苗</a-select-option>
<a-select-option value="bovine_tuberculosis">牛结核病疫苗</a-select-option>
<a-select-option value="brucellosis">布鲁氏菌病疫苗</a-select-option>
<a-select-option value="rabies">狂犬病疫苗</a-select-option>
<a-select-option value="other">其他疫苗</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="生产厂商"
:rules="[{ required: true, message: '请输入生产厂商' }]">
<a-input v-model:value="currentVaccine.manufacturer" placeholder="请输入生产厂商" />
</a-form-item>
<a-form-item label="批准文号"
:rules="[{ required: true, message: '请输入批准文号' }]">
<a-input v-model:value="currentVaccine.approvalNumber" placeholder="请输入批准文号" />
</a-form-item>
<a-form-item label="规格"
:rules="[{ required: true, message: '请输入规格' }]">
<a-input v-model:value="currentVaccine.specification" placeholder="请输入规格" />
</a-form-item>
<a-form-item label="单价(元)"
:rules="[
{ required: true, message: '请输入单价' },
{ pattern: /^\d+(\.\d{1,2})?$/, message: '请输入正确的金额格式' }
]">
<a-input-number v-model:value="currentVaccine.price" min="0" precision="2" placeholder="请输入单价" />
</a-form-item>
<a-form-item label="有效期(天)"
:rules="[
{ required: true, message: '请输入有效期' },
{ type: 'number', min: 1, message: '有效期至少为1天' }
]">
<a-input-number v-model:value="currentVaccine.validDays" min="1" placeholder="请输入有效期(天)" />
</a-form-item>
<a-form-item label="储存条件"
:rules="[{ required: true, message: '请输入储存条件' }]">
<a-input v-model:value="currentVaccine.storageCondition" placeholder="请输入储存条件" />
</a-form-item>
<a-form-item label="备注">
<a-input.TextArea v-model:value="currentVaccine.notes" placeholder="请输入备注信息" rows={3} />
</a-form-item>
</a-form>
<div style="display: flex; justify-content: flex-end; gap: 12px; margin-top: 24px;">
<a-button @click="handleCancel">取消</a-button>
<a-button type="primary" @click="handleSave">保存</a-button>
</div>
</a-modal>
<!-- 查看疫苗详情模态框 -->
<a-modal
v-model:open="isViewModalOpen"
title="查看疫苗详情"
:footer="null"
width={600}
>
<div v-if="viewVaccine">
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px;">
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">疫苗名称</p>
<p>{{ viewVaccine.name }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">疫苗类型</p>
<p>{{ getTypeText(viewVaccine.type) }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">生产厂商</p>
<p>{{ viewVaccine.manufacturer }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">批准文号</p>
<p>{{ viewVaccine.approvalNumber }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">规格</p>
<p>{{ viewVaccine.specification }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">单价</p>
<p>{{ viewVaccine.price }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">有效期</p>
<p>{{ viewVaccine.validDays }} </p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">储存条件</p>
<p>{{ viewVaccine.storageCondition }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">库存数量</p>
<p>{{ viewVaccine.stockCount }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">状态</p>
<p><a-tag :color="getStatusColor(viewVaccine.status)">{{ getStatusText(viewVaccine.status) }}</a-tag></p>
</div>
</div>
<div style="margin-top: 16px;">
<p style="color: #8c8c8c; margin-bottom: 4px;">备注</p>
<p>{{ viewVaccine.notes || '-' }}</p>
</div>
</div>
<div style="display: flex; justify-content: flex-end; margin-top: 24px;">
<a-button @click="handleCloseView">关闭</a-button>
</div>
</a-modal>
<!-- 批量入库模态框 -->
<a-modal
v-model:open="isInModalOpen"
title="疫苗入库"
:footer="null"
width={400}
>
<a-form
:model="batchInForm"
layout="vertical"
>
<a-form-item label="疫苗名称">
<p>{{ viewVaccine?.name || '' }}</p>
</a-form-item>
<a-form-item label="入库数量"
:rules="[
{ required: true, message: '请输入入库数量' },
{ type: 'number', min: 1, message: '入库数量至少为1' }
]">
<a-input-number v-model:value="batchInForm.count" min="1" placeholder="请输入入库数量" />
</a-form-item>
<a-form-item label="入库批次号"
:rules="[{ required: true, message: '请输入入库批次号' }]">
<a-input v-model:value="batchInForm.batchNumber" placeholder="请输入入库批次号" />
</a-form-item>
<a-form-item label="入库日期"
:rules="[{ required: true, message: '请选择入库日期' }]">
<a-date-picker
v-model:value="batchInForm.inDate"
style="width: 100%;"
format="YYYY-MM-DD"
/>
</a-form-item>
</a-form>
<div style="display: flex; justify-content: flex-end; gap: 12px; margin-top: 24px;">
<a-button @click="handleCloseInModal">取消</a-button>
<a-button type="primary" @click="handleConfirmIn">确认入库</a-button>
</div>
</a-modal>
<!-- 批量出库模态框 -->
<a-modal
v-model:open="isOutModalOpen"
title="疫苗出库"
:footer="null"
width={400}
>
<a-form
:model="batchOutForm"
layout="vertical"
>
<a-form-item label="疫苗名称">
<p>{{ viewVaccine?.name || '' }}</p>
</a-form-item>
<a-form-item label="当前库存">
<p>{{ viewVaccine?.stockCount || 0 }}</p>
</a-form-item>
<a-form-item label="出库数量"
:rules="[
{ required: true, message: '请输入出库数量' },
{ type: 'number', min: 1, message: '出库数量至少为1' },
{
validator: (_, value) => {
if (value > (viewVaccine?.stockCount || 0)) {
return Promise.reject(new Error('出库数量不能大于当前库存'))
}
return Promise.resolve()
}
}
]">
<a-input-number v-model:value="batchOutForm.count" min="1" :max="viewVaccine?.stockCount" placeholder="请输入出库数量" />
</a-form-item>
<a-form-item label="出库用途"
:rules="[{ required: true, message: '请输入出库用途' }]">
<a-input v-model:value="batchOutForm.purpose" placeholder="请输入出库用途" />
</a-form-item>
<a-form-item label="出库日期"
:rules="[{ required: true, message: '请选择出库日期' }]">
<a-date-picker
v-model:value="batchOutForm.outDate"
style="width: 100%;"
format="YYYY-MM-DD"
/>
</a-form-item>
</a-form>
<div style="display: flex; justify-content: flex-end; gap: 12px; margin-top: 24px;">
<a-button @click="handleCloseOutModal">取消</a-button>
<a-button type="primary" @click="handleConfirmOut">确认出库</a-button>
</div>
</a-modal>
</div>
</template>
<script setup>
import { ref, reactive, computed } from 'vue'
import { message } from 'ant-design-vue'
// 搜索条件
const searchKeyword = ref('')
const typeFilter = ref('')
const statusFilter = ref('')
// 分页配置
const pagination = reactive({
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total, range) => `${range[0]}-${range[1]} 条,共 ${total}`
})
// 选中行
const selectedRowKeys = ref([])
const onSelectChange = (newSelectedRowKeys) => {
selectedRowKeys.value = newSelectedRowKeys
}
// 模态框状态
const isAddEditModalOpen = ref(false)
const isViewModalOpen = ref(false)
const isInModalOpen = ref(false)
const isOutModalOpen = ref(false)
const isEditing = ref(false)
// 当前编辑/查看的疫苗
const currentVaccine = reactive({
id: '',
name: '',
type: 'foot_and_mouth_disease',
manufacturer: '',
approvalNumber: '',
specification: '',
price: 0,
validDays: 365,
storageCondition: '',
notes: '',
stockCount: 0,
status: 'valid',
createdAt: ''
})
const viewVaccine = ref(null)
// 批量入库表单
const batchInForm = reactive({
count: 1,
batchNumber: '',
inDate: new Date()
})
// 批量出库表单
const batchOutForm = reactive({
count: 1,
purpose: '',
outDate: new Date()
})
// 疫苗列表数据(模拟数据)
const vaccinesData = ref([
{
id: '1',
name: '口蹄疫疫苗O型-亚洲I型二价灭活疫苗',
type: 'foot_and_mouth_disease',
manufacturer: '中国农业科学院兰州兽医研究所',
approvalNumber: '兽药生字2020050356789',
specification: '10ml/瓶',
price: 8.5,
validDays: 365,
storageCondition: '2-8℃冷藏保存',
notes: '',
stockCount: 1200,
status: 'valid',
createdAt: '2023-01-15'
},
{
id: '2',
name: '牛结核病提纯蛋白衍生物PPD检测试剂',
type: 'bovine_tuberculosis',
manufacturer: '中国兽医药品监察所',
approvalNumber: '兽药生字2020010123456',
specification: '1ml/瓶',
price: 15.0,
validDays: 270,
storageCondition: '2-8℃冷藏保存',
notes: '用于牛结核病的皮内变态反应检测',
stockCount: 850,
status: 'valid',
createdAt: '2023-02-20'
},
{
id: '3',
name: '布鲁氏菌病活疫苗S2株',
type: 'brucellosis',
manufacturer: '中国农业科学院哈尔滨兽医研究所',
approvalNumber: '兽药生字2020080789012',
specification: '100头份/瓶',
price: 22.5,
validDays: 180,
storageCondition: '2-8℃冷藏保存',
notes: '用于预防牛、羊布鲁氏菌病',
stockCount: 430,
status: 'valid',
createdAt: '2023-03-10'
},
{
id: '4',
name: '狂犬病疫苗(灭活疫苗)',
type: 'rabies',
manufacturer: '武汉生物制品研究所有限责任公司',
approvalNumber: '兽药生字2020170456789',
specification: '1ml/瓶',
price: 35.0,
validDays: 365,
storageCondition: '2-8℃冷藏保存',
notes: '',
stockCount: 520,
status: 'valid',
createdAt: '2023-04-05'
},
{
id: '5',
name: '牛支原体肺炎疫苗(灭活疫苗)',
type: 'other',
manufacturer: '青岛易邦生物工程有限公司',
approvalNumber: '兽药生字2020150234567',
specification: '20ml/瓶',
price: 45.0,
validDays: 270,
storageCondition: '2-8℃冷藏保存',
notes: '用于预防牛支原体肺炎',
stockCount: 180,
status: 'low_stock',
createdAt: '2023-05-15'
},
{
id: '6',
name: '牛副伤寒疫苗(灭活疫苗)',
type: 'other',
manufacturer: '中牧实业股份有限公司',
approvalNumber: '兽药生字2020010678901',
specification: '100ml/瓶',
price: 98.0,
validDays: 365,
storageCondition: '2-8℃冷藏保存',
notes: '用于预防牛副伤寒',
stockCount: 65,
status: 'low_stock',
createdAt: '2023-06-20'
},
{
id: '7',
name: '牛流行热疫苗(灭活疫苗)',
type: 'other',
manufacturer: '金宇保灵生物药品有限公司',
approvalNumber: '兽药生字2020050345678',
specification: '10ml/瓶',
price: 28.0,
validDays: 180,
storageCondition: '2-8℃冷藏保存',
notes: '用于预防牛流行热',
stockCount: 320,
status: 'valid',
createdAt: '2023-07-10'
},
{
id: '8',
name: '牛病毒性腹泻/粘膜病疫苗(弱毒疫苗)',
type: 'other',
manufacturer: '北京世纪元亨动物防疫技术有限公司',
approvalNumber: '兽药生字2020010890123',
specification: '10头份/瓶',
price: 32.0,
validDays: 270,
storageCondition: '-15℃以下冷冻保存',
notes: '用于预防牛病毒性腹泻/粘膜病',
stockCount: 0,
status: 'expired',
createdAt: '2022-01-15'
}
])
// 表格列定义
const columns = [
{
title: '疫苗名称',
dataIndex: 'name',
key: 'name',
ellipsis: true
},
{
title: '疫苗类型',
dataIndex: 'type',
key: 'type',
width: 120,
customRender: ({ text }) => getTypeText(text)
},
{
title: '生产厂商',
dataIndex: 'manufacturer',
key: 'manufacturer',
ellipsis: true
},
{
title: '规格',
dataIndex: 'specification',
key: 'specification',
width: 100
},
{
title: '单价(元)',
dataIndex: 'price',
key: 'price',
width: 80,
customRender: ({ text }) => `${text}`
},
{
title: '库存数量',
dataIndex: 'stockCount',
key: 'stockCount',
width: 80
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
width: 80,
scopedSlots: { customRender: 'status' }
},
{
title: '创建时间',
dataIndex: 'createdAt',
key: 'createdAt',
width: 120
},
{
title: '操作',
key: 'action',
width: 180,
scopedSlots: { customRender: 'action' }
}
]
// 状态文本
const getStatusText = (status) => {
const statusMap = {
valid: '有效',
expired: '过期',
low_stock: '库存不足'
}
return statusMap[status] || status
}
// 状态颜色
const getStatusColor = (status) => {
const colorMap = {
valid: 'green',
expired: 'red',
low_stock: 'orange'
}
return colorMap[status] || 'default'
}
// 类型文本
const getTypeText = (type) => {
const typeMap = {
foot_and_mouth_disease: '口蹄疫疫苗',
bovine_tuberculosis: '牛结核病疫苗',
brucellosis: '布鲁氏菌病疫苗',
rabies: '狂犬病疫苗',
other: '其他疫苗'
}
return typeMap[type] || type
}
// 搜索处理
const handleSearch = () => {
// 在实际应用中这里应该调用API获取数据
pagination.current = 1
// 模拟搜索效果
message.success('搜索成功')
}
// 重置处理
const handleReset = () => {
searchKeyword.value = ''
typeFilter.value = ''
statusFilter.value = ''
pagination.current = 1
}
// 新增疫苗
const handleAddVaccine = () => {
// 重置表单
Object.assign(currentVaccine, {
id: '',
name: '',
type: 'foot_and_mouth_disease',
manufacturer: '',
approvalNumber: '',
specification: '',
price: 0,
validDays: 365,
storageCondition: '',
notes: '',
stockCount: 0,
status: 'valid',
createdAt: ''
})
isEditing.value = false
isAddEditModalOpen.value = true
}
// 编辑疫苗
const handleEdit = (record) => {
// 复制记录到当前编辑对象
Object.assign(currentVaccine, { ...record })
isEditing.value = true
isAddEditModalOpen.value = true
}
// 查看疫苗
const handleView = (record) => {
viewVaccine.value = { ...record }
isViewModalOpen.value = true
}
// 删除疫苗
const handleDelete = (id) => {
// 在实际应用中这里应该调用API删除数据
const index = vaccinesData.value.findIndex(item => item.id === id)
if (index !== -1) {
vaccinesData.value.splice(index, 1)
message.success('删除成功')
}
}
// 保存疫苗
const handleSave = () => {
// 在实际应用中这里应该调用API保存数据
if (isEditing.value) {
// 更新现有疫苗
const index = vaccinesData.value.findIndex(item => item.id === currentVaccine.id)
if (index !== -1) {
vaccinesData.value[index] = { ...currentVaccine }
}
} else {
// 添加新疫苗
const newVaccine = {
...currentVaccine,
id: Date.now().toString(),
createdAt: new Date().toISOString().split('T')[0]
}
vaccinesData.value.unshift(newVaccine)
}
isAddEditModalOpen.value = false
message.success(isEditing.value ? '更新成功' : '新增成功')
}
// 取消操作
const handleCancel = () => {
isAddEditModalOpen.value = false
}
// 关闭查看模态框
const handleCloseView = () => {
isViewModalOpen.value = false
}
// 批量入库
const handleBatchIn = (id) => {
// 找到对应的疫苗
const vaccine = vaccinesData.value.find(item => item.id === id)
if (vaccine) {
viewVaccine.value = { ...vaccine }
// 重置入库表单
Object.assign(batchInForm, {
count: 1,
batchNumber: '',
inDate: new Date()
})
isInModalOpen.value = true
}
}
// 确认入库
const handleConfirmIn = () => {
// 在实际应用中这里应该调用API入库数据
if (viewVaccine.value) {
const index = vaccinesData.value.findIndex(item => item.id === viewVaccine.value.id)
if (index !== -1) {
vaccinesData.value[index].stockCount += batchInForm.count
// 更新状态
if (vaccinesData.value[index].stockCount > 0) {
vaccinesData.value[index].status = 'valid'
}
message.success('疫苗入库成功')
}
}
isInModalOpen.value = false
}
// 关闭入库模态框
const handleCloseInModal = () => {
isInModalOpen.value = false
}
// 批量出库
const handleBatchOut = (id) => {
// 找到对应的疫苗
const vaccine = vaccinesData.value.find(item => item.id === id)
if (vaccine && vaccine.stockCount > 0) {
viewVaccine.value = { ...vaccine }
// 重置出库表单
Object.assign(batchOutForm, {
count: 1,
purpose: '',
outDate: new Date()
})
isOutModalOpen.value = true
} else {
message.error('疫苗库存不足,无法出库')
}
}
// 确认出库
const handleConfirmOut = () => {
// 在实际应用中这里应该调用API出库数据
if (viewVaccine.value && batchOutForm.count <= viewVaccine.value.stockCount) {
const index = vaccinesData.value.findIndex(item => item.id === viewVaccine.value.id)
if (index !== -1) {
vaccinesData.value[index].stockCount -= batchOutForm.count
// 更新状态
if (vaccinesData.value[index].stockCount === 0) {
vaccinesData.value[index].status = 'low_stock'
} else if (vaccinesData.value[index].stockCount < 100) {
vaccinesData.value[index].status = 'low_stock'
} else {
vaccinesData.value[index].status = 'valid'
}
message.success('疫苗出库成功')
}
}
isOutModalOpen.value = false
}
// 关闭出库模态框
const handleCloseOutModal = () => {
isOutModalOpen.value = false
}
</script>
<style scoped>
h1 {
font-size: 20px;
font-weight: 600;
margin-bottom: 20px;
color: #333;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,801 @@
<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="typeFilter" placeholder="检疫类型" style="width: 120px;">
<a-select-option value="">全部</a-select-option>
<a-select-option value="animal">动物检疫</a-select-option>
<a-select-option value="product">动物产品检疫</a-select-option>
<a-select-option value="transport">运输检疫</a-select-option>
<a-select-option value="slaughter">屠宰检疫</a-select-option>
<a-select-option value="other">其他检疫</a-select-option>
</a-select>
<a-select v-model:value="statusFilter" placeholder="状态" style="width: 120px;">
<a-select-option value="">全部</a-select-option>
<a-select-option value="pending">待审核</a-select-option>
<a-select-option value="approved">已通过</a-select-option>
<a-select-option value="rejected">已驳回</a-select-option>
<a-select-option value="cancelled">已取消</a-select-option>
</a-select>
<a-range-picker
v-model:value="dateRange"
style="width: 300px;"
format="YYYY-MM-DD"
:placeholder="['开始日期', '结束日期']"
/>
<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="primary" danger @click="handleAddDeclaration">
<span class="iconfont icon-tianjia"></span> 新增申报
</a-button>
</div>
</a-card>
<!-- 数据列表 -->
<a-card>
<a-table
:columns="columns"
:data-source="declarationsData"
:pagination="pagination"
row-key="id"
:row-selection="{ selectedRowKeys, onChange: onSelectChange }"
:scroll="{ x: 'max-content' }"
>
<!-- 状态列 -->
<template #bodyCell:status="{ record }">
<a-tag :color="getStatusColor(record.status)">{{ getStatusText(record.status) }}</a-tag>
</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)" v-if="record.status === 'pending'">编辑</a-button>
<a-button size="small" danger @click="handleDelete(record.id)" v-if="record.status === 'pending'">删除</a-button>
<a-button size="small" @click="handleCancelDeclaration(record.id)" v-if="record.status === 'pending'">取消</a-button>
<a-button size="small" @click="handlePrint(record.id)" v-if="record.status === 'approved'">打印</a-button>
</div>
</template>
</a-table>
</a-card>
<!-- 新增/编辑申报模态框 -->
<a-modal
v-model:open="isAddEditModalOpen"
:title="isEditing ? '编辑检疫申报' : '新增检疫申报'"
:footer="null"
width={800}
>
<a-form
:model="currentDeclaration"
layout="vertical"
>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px;">
<a-form-item label="申报单位"
:rules="[{ required: true, message: '请输入申报单位' }]">
<a-input v-model:value="currentDeclaration.declarationUnit" placeholder="请输入申报单位" />
</a-form-item>
<a-form-item label="联系人"
:rules="[{ required: true, message: '请输入联系人' }]">
<a-input v-model:value="currentDeclaration.contactPerson" placeholder="请输入联系人" />
</a-form-item>
<a-form-item label="联系电话"
:rules="[
{ required: true, message: '请输入联系电话' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码' }
]">
<a-input v-model:value="currentDeclaration.phone" placeholder="请输入联系电话" />
</a-form-item>
<a-form-item label="检疫类型"
:rules="[{ required: true, message: '请选择检疫类型' }]">
<a-select v-model:value="currentDeclaration.type" placeholder="请选择检疫类型">
<a-select-option value="animal">动物检疫</a-select-option>
<a-select-option value="product">动物产品检疫</a-select-option>
<a-select-option value="transport">运输检疫</a-select-option>
<a-select-option value="slaughter">屠宰检疫</a-select-option>
<a-select-option value="other">其他检疫</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="检疫对象"
:rules="[{ required: true, message: '请输入检疫对象' }]">
<a-input v-model:value="currentDeclaration.object" placeholder="请输入检疫对象(如:牛、猪肉等)" />
</a-form-item>
<a-form-item label="数量"
:rules="[
{ required: true, message: '请输入数量' },
{ type: 'number', min: 1, message: '数量至少为1' }
]">
<a-input-number v-model:value="currentDeclaration.quantity" min="1" placeholder="请输入数量" />
</a-form-item>
<a-form-item label="来源地"
:rules="[{ required: true, message: '请输入来源地' }]">
<a-input v-model:value="currentDeclaration.sourcePlace" placeholder="请输入来源地" />
</a-form-item>
<a-form-item label="目的地"
:rules="[{ required: true, message: '请输入目的地' }]">
<a-input v-model:value="currentDeclaration.destination" placeholder="请输入目的地" />
</a-form-item>
<a-form-item label="运输工具"
:rules="[{ required: true, message: '请输入运输工具' }]" v-if="currentDeclaration.type === 'transport'">
<a-input v-model:value="currentDeclaration.transportTool" placeholder="请输入运输工具(如:货车、船舶等)" />
</a-form-item>
<a-form-item label="车牌号" v-if="currentDeclaration.type === 'transport'">
<a-input v-model:value="currentDeclaration.vehicleNumber" placeholder="请输入车牌号" />
</a-form-item>
</div>
<a-form-item label="申报理由"
:rules="[{ required: true, message: '请输入申报理由' }]">
<a-input.TextArea v-model:value="currentDeclaration.reason" placeholder="请输入申报理由" :rows="3" />
</a-form-item>
<a-form-item label="申报附件">
<a-upload
name="file"
:multiple="true"
:fileList="fileList"
:before-upload="beforeUpload"
@change="handleUploadChange"
>
<a-button>
<span class="iconfont icon-upload"></span> 上传附件
</a-button>
</a-upload>
<p style="color: #8c8c8c; margin-top: 8px;">支持jpgpngpdf格式单个文件不超过10MB</p>
</a-form-item>
<a-form-item label="备注">
<a-input.TextArea v-model:value="currentDeclaration.notes" placeholder="请输入备注信息" :rows="3" />
</a-form-item>
</a-form>
<div style="display: flex; justify-content: flex-end; gap: 12px; margin-top: 24px;">
<a-button @click="handleCancel">取消</a-button>
<a-button type="primary" @click="handleSave">保存</a-button>
</div>
</a-modal>
<!-- 查看申报详情模态框 -->
<a-modal
v-model:open="isViewModalOpen"
title="查看检疫申报详情"
:footer="null"
width={800}
>
<div v-if="viewDeclaration">
<div style="margin-bottom: 16px;">
<h3 style="margin-bottom: 8px;">基本信息</h3>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px;">
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">申报单位</p>
<p>{{ viewDeclaration.declarationUnit }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">联系人</p>
<p>{{ viewDeclaration.contactPerson }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">联系电话</p>
<p>{{ viewDeclaration.phone }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">检疫类型</p>
<p>{{ getTypeText(viewDeclaration.type) }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">检疫对象</p>
<p>{{ viewDeclaration.object }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">数量</p>
<p>{{ viewDeclaration.quantity }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">来源地</p>
<p>{{ viewDeclaration.sourcePlace }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">目的地</p>
<p>{{ viewDeclaration.destination }}</p>
</div>
<div v-if="viewDeclaration.transportTool">
<p style="color: #8c8c8c; margin-bottom: 4px;">运输工具</p>
<p>{{ viewDeclaration.transportTool }}</p>
</div>
<div v-if="viewDeclaration.vehicleNumber">
<p style="color: #8c8c8c; margin-bottom: 4px;">车牌号</p>
<p>{{ viewDeclaration.vehicleNumber }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">申报日期</p>
<p>{{ viewDeclaration.declarationDate }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">状态</p>
<p><a-tag :color="getStatusColor(viewDeclaration.status)">{{ getStatusText(viewDeclaration.status) }}</a-tag></p>
</div>
</div>
</div>
<div style="margin-bottom: 16px;">
<h3 style="margin-bottom: 8px;">详细信息</h3>
<div style="margin-bottom: 12px;">
<p style="color: #8c8c8c; margin-bottom: 4px;">申报理由</p>
<p>{{ viewDeclaration.reason || '-' }}</p>
</div>
<div v-if="viewDeclaration.files && viewDeclaration.files.length > 0" style="margin-bottom: 12px;">
<p style="color: #8c8c8c; margin-bottom: 4px;">申报附件</p>
<div style="display: flex; flex-wrap: wrap; gap: 12px;">
<a-tag v-for="file in viewDeclaration.files" :key="file.id" color="blue" style="cursor: pointer;">
{{ file.name }}
<template #closeIcon>
<span class="iconfont icon-download"></span>
</template>
</a-tag>
</div>
</div>
<div v-if="viewDeclaration.reviewComments">
<p style="color: #8c8c8c; margin-bottom: 4px;">审核意见</p>
<p>{{ viewDeclaration.reviewComments || '-' }}</p>
</div>
</div>
<div>
<h3 style="margin-bottom: 8px;">备注</h3>
<p>{{ viewDeclaration.notes || '-' }}</p>
</div>
</div>
<div style="display: flex; justify-content: flex-end; margin-top: 24px;">
<a-button @click="handleCloseView">关闭</a-button>
</div>
</a-modal>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { message } from 'ant-design-vue'
// 搜索条件
const searchKeyword = ref('')
const typeFilter = ref('')
const statusFilter = ref('')
const dateRange = ref([])
// 分页配置
const pagination = reactive({
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total, range) => `${range[0]}-${range[1]} 条,共 ${total}`
})
// 选中行
const selectedRowKeys = ref([])
const onSelectChange = (newSelectedRowKeys) => {
selectedRowKeys.value = newSelectedRowKeys
}
// 模态框状态
const isAddEditModalOpen = ref(false)
const isViewModalOpen = ref(false)
const isEditing = ref(false)
// 当前编辑/查看的申报
const currentDeclaration = reactive({
id: '',
declarationUnit: '',
contactPerson: '',
phone: '',
type: 'animal',
object: '',
quantity: 1,
sourcePlace: '',
destination: '',
transportTool: '',
vehicleNumber: '',
reason: '',
files: [],
reviewComments: '',
notes: '',
status: 'pending',
declarationDate: '',
createdAt: ''
})
const viewDeclaration = ref(null)
const fileList = ref([])
// 申报列表数据(模拟数据)
const declarationsData = ref([
{
id: '1',
declarationUnit: '郑州市金水区阳光养殖场',
contactPerson: '张三',
phone: '13812345678',
type: 'animal',
object: '牛',
quantity: 150,
sourcePlace: '郑州市金水区',
destination: '河南省商丘市',
transportTool: '货车',
vehicleNumber: '豫A12345',
reason: '牛只销售运输',
files: [],
reviewComments: '符合检疫要求,同意通过',
notes: '',
status: 'approved',
declarationDate: '2023-10-01',
createdAt: '2023-10-01'
},
{
id: '2',
declarationUnit: '新郑市绿源养殖场',
contactPerson: '李四',
phone: '13912345678',
type: 'product',
object: '牛肉',
quantity: 500,
sourcePlace: '新郑市',
destination: '上海市',
transportTool: '冷藏车',
vehicleNumber: '豫A67890',
reason: '牛肉产品销售',
files: [],
reviewComments: '',
notes: '',
status: 'pending',
declarationDate: '2023-10-02',
createdAt: '2023-10-02'
},
{
id: '3',
declarationUnit: '新密市祥和养殖场',
contactPerson: '王五',
phone: '13712345678',
type: 'slaughter',
object: '牛',
quantity: 80,
sourcePlace: '新密市',
destination: '新密市肉类加工厂',
transportTool: '货车',
vehicleNumber: '豫A23456',
reason: '牛只屠宰加工',
files: [],
reviewComments: '资料不全,需补充产地检疫证明',
notes: '',
status: 'rejected',
declarationDate: '2023-10-03',
createdAt: '2023-10-03'
},
{
id: '4',
declarationUnit: '登封市幸福养殖场',
contactPerson: '赵六',
phone: '13612345678',
type: 'animal',
object: '牛',
quantity: 120,
sourcePlace: '登封市',
destination: '湖北省武汉市',
transportTool: '货车',
vehicleNumber: '豫A34567',
reason: '牛只销售运输',
files: [],
reviewComments: '',
notes: '',
status: 'pending',
declarationDate: '2023-10-04',
createdAt: '2023-10-04'
},
{
id: '5',
declarationUnit: '中牟县希望养殖场',
contactPerson: '钱七',
phone: '13512345678',
type: 'product',
object: '牛奶',
quantity: 2000,
sourcePlace: '中牟县',
destination: '河南省郑州市',
transportTool: '冷藏车',
vehicleNumber: '豫A45678',
reason: '牛奶产品销售',
files: [],
reviewComments: '符合检疫要求,同意通过',
notes: '',
status: 'approved',
declarationDate: '2023-10-05',
createdAt: '2023-10-05'
},
{
id: '6',
declarationUnit: '荥阳市快乐养殖场',
contactPerson: '孙八',
phone: '13412345678',
type: 'transport',
object: '牛',
quantity: 90,
sourcePlace: '荥阳市',
destination: '山西省太原市',
transportTool: '货车',
vehicleNumber: '豫A56789',
reason: '牛只跨区域调运',
files: [],
reviewComments: '',
notes: '',
status: 'pending',
declarationDate: '2023-10-06',
createdAt: '2023-10-06'
},
{
id: '7',
declarationUnit: '巩义市明星养殖场',
contactPerson: '周九',
phone: '13312345678',
type: 'slaughter',
object: '牛',
quantity: 60,
sourcePlace: '巩义市',
destination: '巩义市屠宰场',
transportTool: '货车',
vehicleNumber: '豫A67890',
reason: '牛只屠宰',
files: [],
reviewComments: '符合检疫要求,同意通过',
notes: '',
status: 'approved',
declarationDate: '2023-10-07',
createdAt: '2023-10-07'
},
{
id: '8',
declarationUnit: '惠济区温馨养殖场',
contactPerson: '吴十',
phone: '13212345678',
type: 'other',
object: '牛精液',
quantity: 500,
sourcePlace: '惠济区',
destination: '全国各地',
transportTool: '快递冷链',
vehicleNumber: '',
reason: '种牛精液销售',
files: [],
reviewComments: '',
notes: '',
status: 'pending',
declarationDate: '2023-10-08',
createdAt: '2023-10-08'
},
{
id: '9',
declarationUnit: '二七区红火养殖场',
contactPerson: '郑十一',
phone: '13112345678',
type: 'animal',
object: '牛',
quantity: 100,
sourcePlace: '二七区',
destination: '江苏省南京市',
transportTool: '货车',
vehicleNumber: '豫A78901',
reason: '牛只销售运输',
files: [],
reviewComments: '主动取消',
notes: '',
status: 'cancelled',
declarationDate: '2023-10-09',
createdAt: '2023-10-09'
},
{
id: '10',
declarationUnit: '中原区丰收养殖场',
contactPerson: '王十二',
phone: '13012345678',
type: 'product',
object: '牛肉制品',
quantity: 300,
sourcePlace: '中原区',
destination: '广东省广州市',
transportTool: '冷链物流',
vehicleNumber: '豫A89012',
reason: '牛肉制品销售',
files: [],
reviewComments: '',
notes: '',
status: 'pending',
declarationDate: '2023-10-10',
createdAt: '2023-10-10'
}
])
// 表格列定义
const columns = [
{
title: '申报单位',
dataIndex: 'declarationUnit',
key: 'declarationUnit',
ellipsis: true
},
{
title: '联系人',
dataIndex: 'contactPerson',
key: 'contactPerson',
width: 100
},
{
title: '检疫类型',
dataIndex: 'type',
key: 'type',
width: 120,
customRender: ({ text }) => getTypeText(text)
},
{
title: '检疫对象',
dataIndex: 'object',
key: 'object',
width: 100
},
{
title: '数量',
dataIndex: 'quantity',
key: 'quantity',
width: 80
},
{
title: '申报日期',
dataIndex: 'declarationDate',
key: 'declarationDate',
width: 120
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
width: 80,
scopedSlots: { customRender: 'status' }
},
{
title: '操作',
key: 'action',
width: 180,
scopedSlots: { customRender: 'action' }
}
]
// 状态文本
const getStatusText = (status) => {
const statusMap = {
pending: '待审核',
approved: '已通过',
rejected: '已驳回',
cancelled: '已取消'
}
return statusMap[status] || status
}
// 状态颜色
const getStatusColor = (status) => {
const colorMap = {
pending: 'blue',
approved: 'green',
rejected: 'red',
cancelled: 'default'
}
return colorMap[status] || 'default'
}
// 类型文本
const getTypeText = (type) => {
const typeMap = {
animal: '动物检疫',
product: '动物产品检疫',
transport: '运输检疫',
slaughter: '屠宰检疫',
other: '其他检疫'
}
return typeMap[type] || type
}
// 上传前校验
const beforeUpload = (file) => {
// 检查文件类型
const isValidType = ['image/jpeg', 'image/png', 'application/pdf'].includes(file.type)
if (!isValidType) {
message.error('只能上传JPG、PNG、PDF格式的文件')
return Upload.LIST_IGNORE
}
// 检查文件大小
const isLt10M = file.size / 1024 / 1024 < 10
if (!isLt10M) {
message.error('文件大小不能超过10MB')
return Upload.LIST_IGNORE
}
return true
}
// 上传变化处理
const handleUploadChange = ({ fileList: newFileList }) => {
fileList.value = newFileList
}
// 搜索处理
const handleSearch = () => {
// 在实际应用中这里应该调用API获取数据
pagination.current = 1
// 模拟搜索效果
message.success('搜索成功')
}
// 重置处理
const handleReset = () => {
searchKeyword.value = ''
typeFilter.value = ''
statusFilter.value = ''
dateRange.value = []
pagination.current = 1
}
// 新增申报
const handleAddDeclaration = () => {
// 重置表单
Object.assign(currentDeclaration, {
id: '',
declarationUnit: '',
contactPerson: '',
phone: '',
type: 'animal',
object: '',
quantity: 1,
sourcePlace: '',
destination: '',
transportTool: '',
vehicleNumber: '',
reason: '',
files: [],
reviewComments: '',
notes: '',
status: 'pending',
declarationDate: '',
createdAt: ''
})
fileList.value = []
isEditing.value = false
isAddEditModalOpen.value = true
}
// 编辑申报
const handleEdit = (record) => {
// 复制记录到当前编辑对象
Object.assign(currentDeclaration, { ...record })
// 设置文件列表
if (record.files && record.files.length > 0) {
fileList.value = record.files.map(file => ({
uid: file.id,
name: file.name,
status: 'done',
url: file.url
}))
} else {
fileList.value = []
}
isEditing.value = true
isAddEditModalOpen.value = true
}
// 查看申报
const handleView = (record) => {
viewDeclaration.value = { ...record }
isViewModalOpen.value = true
}
// 删除申报
const handleDelete = (id) => {
// 在实际应用中这里应该调用API删除数据
const index = declarationsData.value.findIndex(item => item.id === id)
if (index !== -1) {
declarationsData.value.splice(index, 1)
message.success('删除成功')
}
}
// 保存申报
const handleSave = () => {
// 在实际应用中这里应该调用API保存数据
// 处理文件列表
currentDeclaration.files = fileList.value.map(file => ({
id: file.uid || Date.now().toString(),
name: file.name,
url: file.url
}))
if (isEditing.value) {
// 更新现有申报
const index = declarationsData.value.findIndex(item => item.id === currentDeclaration.id)
if (index !== -1) {
declarationsData.value[index] = { ...currentDeclaration }
}
} else {
// 添加新申报
const newDeclaration = {
...currentDeclaration,
id: Date.now().toString(),
declarationDate: new Date().toISOString().split('T')[0],
createdAt: new Date().toISOString().split('T')[0]
}
declarationsData.value.unshift(newDeclaration)
}
isAddEditModalOpen.value = false
message.success(isEditing.value ? '更新成功' : '新增成功')
}
// 取消操作
const handleCancel = () => {
isAddEditModalOpen.value = false
}
// 关闭查看模态框
const handleCloseView = () => {
isViewModalOpen.value = false
}
// 取消申报
const handleCancelDeclaration = (id) => {
// 在实际应用中这里应该调用API更新申报状态
const index = declarationsData.value.findIndex(item => item.id === id)
if (index !== -1) {
declarationsData.value[index].status = 'cancelled'
declarationsData.value[index].reviewComments = '申请人主动取消'
message.success('申报已取消')
}
}
// 打印申报单
const handlePrint = (id) => {
// 在实际应用中这里应该调用API获取打印数据
message.success('打印功能待实现')
}
</script>
<style scoped>
h1 {
font-size: 20px;
font-weight: 600;
margin-bottom: 20px;
color: #333;
}
</style>

View File

@@ -0,0 +1,602 @@
<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="typeFilter" placeholder="检疫类型" style="width: 120px;">
<a-select-option value="">全部</a-select-option>
<a-select-option value="animal">动物检疫</a-select-option>
<a-select-option value="product">动物产品检疫</a-select-option>
<a-select-option value="transport">运输检疫</a-select-option>
<a-select-option value="slaughter">屠宰检疫</a-select-option>
<a-select-option value="other">其他检疫</a-select-option>
</a-select>
<a-select v-model:value="statusFilter" placeholder="状态" style="width: 120px;">
<a-select-option value="">全部</a-select-option>
<a-select-option value="approved">已通过</a-select-option>
<a-select-option value="rejected">已驳回</a-select-option>
<a-select-option value="cancelled">已取消</a-select-option>
</a-select>
<a-range-picker
v-model:value="dateRange"
style="width: 300px;"
format="YYYY-MM-DD"
placeholder={['申报日期', '申报日期']}
/>
<a-input v-model:value="quarantineOfficer" placeholder="检疫员" style="width: 150px;">
<template #prefix>
<span class="iconfont icon-yonghu"></span>
</template>
</a-input>
<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="primary" @click="handleExport">
<span class="iconfont icon-daochu"></span> 导出记录
</a-button>
</div>
</a-card>
<!-- 数据列表 -->
<a-card>
<a-table
:columns="columns"
:data-source="recordsData"
: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:action="{ record }">
<div style="display: flex; gap: 8px;">
<a-button size="small" @click="handleView(record)">查看详情</a-button>
<a-button size="small" @click="handlePrint(record.id)">打印</a-button>
</div>
</template>
</a-table>
</a-card>
<!-- 查看记录详情模态框 -->
<a-modal
v-model:open="isViewModalOpen"
title="检疫记录详情"
:footer="null"
width={800}
>
<div v-if="viewRecord">
<!-- 申报信息 -->
<div style="margin-bottom: 20px;">
<h3 style="margin-bottom: 12px;">申报信息</h3>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px;">
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">申报单号</p>
<p>{{ viewRecord.declarationNumber || '-' }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">申报单位</p>
<p>{{ viewRecord.declarationUnit }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">联系人</p>
<p>{{ viewRecord.contactPerson }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">联系电话</p>
<p>{{ viewRecord.phone }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">申报日期</p>
<p>{{ viewRecord.declarationDate }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">申报理由</p>
<p>{{ viewRecord.reason }}</p>
</div>
</div>
</div>
<!-- 检疫信息 -->
<div style="margin-bottom: 20px;">
<h3 style="margin-bottom: 12px;">检疫信息</h3>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px;">
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">检疫类型</p>
<p>{{ getTypeText(viewRecord.type) }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">检疫对象</p>
<p>{{ viewRecord.object }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">数量</p>
<p>{{ viewRecord.quantity }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">来源地</p>
<p>{{ viewRecord.sourcePlace }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">目的地</p>
<p>{{ viewRecord.destination }}</p>
</div>
<div v-if="viewRecord.transportTool">
<p style="color: #8c8c8c; margin-bottom: 4px;">运输工具</p>
<p>{{ viewRecord.transportTool }}</p>
</div>
<div v-if="viewRecord.vehicleNumber">
<p style="color: #8c8c8c; margin-bottom: 4px;">车牌号</p>
<p>{{ viewRecord.vehicleNumber }}</p>
</div>
</div>
</div>
<!-- 检疫结果 -->
<div style="margin-bottom: 20px;">
<h3 style="margin-bottom: 12px;">检疫结果</h3>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px;">
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">检疫员</p>
<p>{{ viewRecord.quarantineOfficer }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">检疫时间</p>
<p>{{ viewRecord.quarantineTime }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">检疫结果</p>
<p><a-tag :color="getStatusColor(viewRecord.status)">{{ getStatusText(viewRecord.status) }}</a-tag></p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">检疫证书编号</p>
<p>{{ viewRecord.certificateNumber || '-' }}</p>
</div>
</div>
<div style="margin-top: 16px;">
<p style="color: #8c8c8c; margin-bottom: 4px;">检疫结论</p>
<p>{{ viewRecord.quarantineComments || '-' }}</p>
</div>
<div v-if="viewRecord.reviewComments">
<p style="color: #8c8c8c; margin-bottom: 4px;">审核意见</p>
<p>{{ viewRecord.reviewComments || '-' }}</p>
</div>
</div>
<!-- 附件 -->
<div v-if="viewRecord.files && viewRecord.files.length > 0">
<h3 style="margin-bottom: 12px;">相关附件</h3>
<div style="display: flex; flex-wrap: wrap; gap: 12px;">
<a-tag v-for="file in viewRecord.files" :key="file.id" color="blue" style="cursor: pointer;">
{{ file.name }}
<template #closeIcon>
<span class="iconfont icon-download"></span>
</template>
</a-tag>
</div>
</div>
</div>
<div style="display: flex; justify-content: flex-end; margin-top: 24px;">
<a-button @click="handleCloseView">关闭</a-button>
<a-button type="primary" @click="handlePrint(viewRecord.id)" style="margin-left: 12px;">打印</a-button>
</div>
</a-modal>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { message } from 'ant-design-vue'
// 搜索条件
const searchKeyword = ref('')
const typeFilter = ref('')
const statusFilter = ref('')
const dateRange = ref([])
const quarantineOfficer = ref('')
// 分页配置
const pagination = reactive({
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total, range) => `${range[0]}-${range[1]} 条,共 ${total}`
})
// 模态框状态
const isViewModalOpen = ref(false)
const viewRecord = ref(null)
// 记录列表数据(模拟数据)
const recordsData = ref([
{
id: '1',
declarationNumber: 'J202310010001',
declarationUnit: '郑州市金水区阳光养殖场',
contactPerson: '张三',
phone: '13812345678',
type: 'animal',
object: '牛',
quantity: 150,
sourcePlace: '郑州市金水区',
destination: '河南省商丘市',
transportTool: '货车',
vehicleNumber: '豫A12345',
reason: '牛只销售运输',
files: [],
reviewComments: '符合检疫要求,同意通过',
quarantineOfficer: '李检疫',
quarantineTime: '2023-10-01 15:30',
quarantineComments: '经检疫,该批牛只健康状况良好,无传染病症状,符合出证条件。',
certificateNumber: 'QS202310010001',
status: 'approved',
declarationDate: '2023-10-01'
},
{
id: '2',
declarationNumber: 'J202310050001',
declarationUnit: '中牟县希望养殖场',
contactPerson: '钱七',
phone: '13512345678',
type: 'product',
object: '牛奶',
quantity: 2000,
sourcePlace: '中牟县',
destination: '河南省郑州市',
transportTool: '冷藏车',
vehicleNumber: '豫A45678',
reason: '牛奶产品销售',
files: [],
reviewComments: '符合检疫要求,同意通过',
quarantineOfficer: '王检疫',
quarantineTime: '2023-10-05 10:20',
quarantineComments: '经检疫,该批牛奶符合国家标准,无致病菌,质量合格。',
certificateNumber: 'QS202310050001',
status: 'approved',
declarationDate: '2023-10-05'
},
{
id: '3',
declarationNumber: 'J202310030001',
declarationUnit: '新密市祥和养殖场',
contactPerson: '王五',
phone: '13712345678',
type: 'slaughter',
object: '牛',
quantity: 80,
sourcePlace: '新密市',
destination: '新密市肉类加工厂',
transportTool: '货车',
vehicleNumber: '豫A23456',
reason: '牛只屠宰加工',
files: [],
reviewComments: '资料不全,需补充产地检疫证明',
quarantineOfficer: '赵检疫',
quarantineTime: '2023-10-03 14:10',
quarantineComments: '经检查,申报资料不全,缺少产地检疫证明,需补充材料后重新申报。',
certificateNumber: '',
status: 'rejected',
declarationDate: '2023-10-03'
},
{
id: '4',
declarationNumber: 'J202310070001',
declarationUnit: '巩义市明星养殖场',
contactPerson: '周九',
phone: '13312345678',
type: 'slaughter',
object: '牛',
quantity: 60,
sourcePlace: '巩义市',
destination: '巩义市屠宰场',
transportTool: '货车',
vehicleNumber: '豫A67890',
reason: '牛只屠宰',
files: [],
reviewComments: '符合检疫要求,同意通过',
quarantineOfficer: '孙检疫',
quarantineTime: '2023-10-07 09:45',
quarantineComments: '经检疫,该批牛只健康状况良好,适合屠宰。',
certificateNumber: 'QS202310070001',
status: 'approved',
declarationDate: '2023-10-07'
},
{
id: '5',
declarationNumber: 'J202310090001',
declarationUnit: '二七区红火养殖场',
contactPerson: '郑十一',
phone: '13112345678',
type: 'animal',
object: '牛',
quantity: 100,
sourcePlace: '二七区',
destination: '江苏省南京市',
transportTool: '货车',
vehicleNumber: '豫A78901',
reason: '牛只销售运输',
files: [],
reviewComments: '主动取消',
quarantineOfficer: '',
quarantineTime: '',
quarantineComments: '',
certificateNumber: '',
status: 'cancelled',
declarationDate: '2023-10-09'
},
{
id: '6',
declarationNumber: 'J202309250001',
declarationUnit: '荥阳市绿源养殖场',
contactPerson: '陈十二',
phone: '13912345679',
type: 'animal',
object: '牛',
quantity: 120,
sourcePlace: '荥阳市',
destination: '山东省济南市',
transportTool: '货车',
vehicleNumber: '豫A89012',
reason: '牛只销售运输',
files: [],
reviewComments: '符合检疫要求,同意通过',
quarantineOfficer: '杨检疫',
quarantineTime: '2023-09-25 16:20',
quarantineComments: '经检疫,该批牛只健康状况良好,无传染病症状,符合出证条件。',
certificateNumber: 'QS202309250001',
status: 'approved',
declarationDate: '2023-09-25'
},
{
id: '7',
declarationNumber: 'J202309280001',
declarationUnit: '新郑市幸福养殖场',
contactPerson: '吴十三',
phone: '13612345679',
type: 'product',
object: '牛肉制品',
quantity: 400,
sourcePlace: '新郑市',
destination: '北京市',
transportTool: '冷链物流',
vehicleNumber: '豫A90123',
reason: '牛肉制品销售',
files: [],
reviewComments: '符合检疫要求,同意通过',
quarantineOfficer: '郑检疫',
quarantineTime: '2023-09-28 11:30',
quarantineComments: '经检疫,该批牛肉制品符合食品安全标准,无质量问题。',
certificateNumber: 'QS202309280001',
status: 'approved',
declarationDate: '2023-09-28'
},
{
id: '8',
declarationNumber: 'J202309300001',
declarationUnit: '登封市丰收养殖场',
contactPerson: '冯十四',
phone: '13412345679',
type: 'animal',
object: '牛',
quantity: 140,
sourcePlace: '登封市',
destination: '湖北省武汉市',
transportTool: '货车',
vehicleNumber: '豫A01234',
reason: '牛只销售运输',
files: [],
reviewComments: '资料不全,需补充免疫记录',
quarantineOfficer: '褚检疫',
quarantineTime: '2023-09-30 13:45',
quarantineComments: '经检查,申报资料不全,缺少牛只免疫记录,需补充材料后重新申报。',
certificateNumber: '',
status: 'rejected',
declarationDate: '2023-09-30'
},
{
id: '9',
declarationNumber: 'J202309200001',
declarationUnit: '管城回族区快乐养殖场',
contactPerson: '卫十五',
phone: '13212345679',
type: 'animal',
object: '牛',
quantity: 90,
sourcePlace: '管城回族区',
destination: '陕西省西安市',
transportTool: '货车',
vehicleNumber: '豫A12346',
reason: '牛只销售运输',
files: [],
reviewComments: '符合检疫要求,同意通过',
quarantineOfficer: '蒋检疫',
quarantineTime: '2023-09-20 10:15',
quarantineComments: '经检疫,该批牛只健康状况良好,无传染病症状,符合出证条件。',
certificateNumber: 'QS202309200001',
status: 'approved',
declarationDate: '2023-09-20'
},
{
id: '10',
declarationNumber: 'J202309150001',
declarationUnit: '惠济区温馨养殖场',
contactPerson: '沈十六',
phone: '13012345679',
type: 'product',
object: '牛奶',
quantity: 1500,
sourcePlace: '惠济区',
destination: '河北省石家庄市',
transportTool: '冷藏车',
vehicleNumber: '豫A23457',
reason: '牛奶产品销售',
files: [],
reviewComments: '符合检疫要求,同意通过',
quarantineOfficer: '韩检疫',
quarantineTime: '2023-09-15 14:40',
quarantineComments: '经检疫,该批牛奶符合国家标准,无致病菌,质量合格。',
certificateNumber: 'QS202309150001',
status: 'approved',
declarationDate: '2023-09-15'
}
])
// 表格列定义
const columns = [
{
title: '申报单号',
dataIndex: 'declarationNumber',
key: 'declarationNumber',
width: 180
},
{
title: '申报单位',
dataIndex: 'declarationUnit',
key: 'declarationUnit',
ellipsis: true
},
{
title: '检疫类型',
dataIndex: 'type',
key: 'type',
width: 120,
customRender: ({ text }) => getTypeText(text)
},
{
title: '检疫对象',
dataIndex: 'object',
key: 'object',
width: 100
},
{
title: '检疫员',
dataIndex: 'quarantineOfficer',
key: 'quarantineOfficer',
width: 100
},
{
title: '申报日期',
dataIndex: 'declarationDate',
key: 'declarationDate',
width: 120
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
width: 80,
scopedSlots: { customRender: 'status' }
},
{
title: '操作',
key: 'action',
width: 150,
scopedSlots: { customRender: 'action' }
}
]
// 状态文本
const getStatusText = (status) => {
const statusMap = {
approved: '已通过',
rejected: '已驳回',
cancelled: '已取消'
}
return statusMap[status] || status
}
// 状态颜色
const getStatusColor = (status) => {
const colorMap = {
approved: 'green',
rejected: 'red',
cancelled: 'default'
}
return colorMap[status] || 'default'
}
// 类型文本
const getTypeText = (type) => {
const typeMap = {
animal: '动物检疫',
product: '动物产品检疫',
transport: '运输检疫',
slaughter: '屠宰检疫',
other: '其他检疫'
}
return typeMap[type] || type
}
// 搜索处理
const handleSearch = () => {
// 在实际应用中这里应该调用API获取数据
pagination.current = 1
// 模拟搜索效果
message.success('搜索成功')
}
// 重置处理
const handleReset = () => {
searchKeyword.value = ''
typeFilter.value = ''
statusFilter.value = ''
dateRange.value = []
quarantineOfficer.value = ''
pagination.current = 1
}
// 导出记录
const handleExport = () => {
// 在实际应用中这里应该调用API导出数据
message.success('导出功能待实现')
}
// 查看记录
const handleView = (record) => {
viewRecord.value = { ...record }
isViewModalOpen.value = true
}
// 关闭查看模态框
const handleCloseView = () => {
isViewModalOpen.value = false
}
// 打印记录
const handlePrint = (id) => {
// 在实际应用中这里应该调用API获取打印数据
message.success('打印功能待实现')
}
</script>
<style scoped>
h1 {
font-size: 20px;
font-weight: 600;
margin-bottom: 20px;
color: #333;
}
</style>

View File

@@ -0,0 +1,696 @@
<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="typeFilter" placeholder="检疫类型" style="width: 120px;">
<a-select-option value="">全部</a-select-option>
<a-select-option value="animal">动物检疫</a-select-option>
<a-select-option value="product">动物产品检疫</a-select-option>
<a-select-option value="transport">运输检疫</a-select-option>
<a-select-option value="slaughter">屠宰检疫</a-select-option>
<a-select-option value="other">其他检疫</a-select-option>
</a-select>
<a-select v-model:value="statusFilter" placeholder="状态" style="width: 120px;">
<a-select-option value="">全部</a-select-option>
<a-select-option value="pending">待审核</a-select-option>
<a-select-option value="approved">已通过</a-select-option>
<a-select-option value="rejected">已驳回</a-select-option>
<a-select-option value="cancelled">已取消</a-select-option>
</a-select>
<a-input v-model:value="quarantinePersonFilter" placeholder="检疫人员" style="width: 150px;" />
<a-range-picker
v-model:value="dateRange"
style="width: 300px;"
format="YYYY-MM-DD"
:placeholder="['开始日期', '结束日期']"
/>
<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="default" @click="handleExport">
<span class="iconfont icon-export"></span> 导出
</a-button>
</div>
</a-card>
<!-- 数据列表 -->
<a-card>
<a-table
:columns="columns"
:data-source="quarantineRecords"
:pagination="pagination"
row-key="id"
:row-selection="{ selectedRowKeys, onChange: onSelectChange }"
:scroll="{ x: 'max-content' }"
>
<!-- 状态列 -->
<template #bodyCell:status="{ record }">
<a-tag :color="getStatusColor(record.status)">{{ getStatusText(record.status) }}</a-tag>
</template>
<!-- 操作列 -->
<template #bodyCell:action="{ record }">
<div style="display: flex; gap: 8px;">
<a-button size="small" @click="handleView(record)">查看</a-button>
<a-button size="small" @click="handlePrint(record.id)" v-if="record.status === 'approved'">打印</a-button>
<a-button size="small" @click="handleRecheck(record.id)" v-if="record.status === 'approved'">重新检疫</a-button>
</div>
</template>
</a-table>
</a-card>
<!-- 查看记录详情模态框 -->
<a-modal
v-model:open="isViewModalOpen"
title="检疫记录详情"
:footer="null"
width={800}
>
<div v-if="viewRecord">
<!-- 申报信息 -->
<div style="margin-bottom: 20px;">
<h3 style="margin-bottom: 12px; padding-bottom: 8px; border-bottom: 1px solid #f0f0f0;">申报信息</h3>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px;">
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">申报单号</p>
<p>{{ viewRecord.declarationNumber || '-' }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">申报单位</p>
<p>{{ viewRecord.declarationUnit }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">联系人</p>
<p>{{ viewRecord.contactPerson }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">联系电话</p>
<p>{{ viewRecord.phone }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">检疫类型</p>
<p>{{ getTypeText(viewRecord.type) }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">检疫对象</p>
<p>{{ viewRecord.object }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">数量</p>
<p>{{ viewRecord.quantity }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">申报日期</p>
<p>{{ viewRecord.declarationDate }}</p>
</div>
</div>
</div>
<!-- 检疫信息 -->
<div style="margin-bottom: 20px;">
<h3 style="margin-bottom: 12px; padding-bottom: 8px; border-bottom: 1px solid #f0f0f0;">检疫信息</h3>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px;">
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">检疫人员</p>
<p>{{ viewRecord.quarantinePerson }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">检疫时间</p>
<p>{{ viewRecord.quarantineTime }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">检疫结果</p>
<p>{{ viewRecord.result ? '合格' : '不合格' }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">证书编号</p>
<p>{{ viewRecord.certificateNumber || '-' }}</p>
</div>
</div>
</div>
<!-- 检疫详情 -->
<div style="margin-bottom: 20px;">
<h3 style="margin-bottom: 12px; padding-bottom: 8px; border-bottom: 1px solid #f0f0f0;">检疫详情</h3>
<div style="margin-bottom: 12px;">
<p style="color: #8c8c8c; margin-bottom: 4px;">检疫项目</p>
<div style="display: flex; flex-wrap: wrap; gap: 8px;">
<a-tag v-for="item in viewRecord.quarantineItems" :key="item.id">
{{ item.name }}
</a-tag>
</div>
</div>
<div style="margin-bottom: 12px;">
<p style="color: #8c8c8c; margin-bottom: 4px;">检疫过程描述</p>
<p>{{ viewRecord.processDescription || '-' }}</p>
</div>
<div style="margin-bottom: 12px;">
<p style="color: #8c8c8c; margin-bottom: 4px;">检疫结论</p>
<p>{{ viewRecord.conclusion || '-' }}</p>
</div>
<div v-if="viewRecord.photos && viewRecord.photos.length > 0" style="margin-bottom: 12px;">
<p style="color: #8c8c8c; margin-bottom: 4px;">检疫照片</p>
<div style="display: flex; flex-wrap: wrap; gap: 12px;">
<a-avatar
v-for="photo in viewRecord.photos"
:key="photo.id"
:src="photo.url"
style="width: 80px; height: 80px;"
@click="handleViewPhoto(photo)"
/>
</div>
</div>
</div>
<!-- 其他信息 -->
<div>
<h3 style="margin-bottom: 12px; padding-bottom: 8px; border-bottom: 1px solid #f0f0f0;">其他信息</h3>
<div style="margin-bottom: 12px;">
<p style="color: #8c8c8c; margin-bottom: 4px;">审核意见</p>
<p>{{ viewRecord.reviewComments || '-' }}</p>
</div>
<div>
<p style="color: #8c8c8c; margin-bottom: 4px;">备注</p>
<p>{{ viewRecord.notes || '-' }}</p>
</div>
</div>
</div>
<div style="display: flex; justify-content: flex-end; margin-top: 24px;">
<a-button @click="handleCloseView">关闭</a-button>
</div>
</a-modal>
<!-- 照片查看模态框 -->
<a-modal
v-model:open="isPhotoModalOpen"
title="检疫照片"
:footer="null"
width={600}
>
<img :src="currentPhotoUrl" alt="检疫照片" style="width: 100%; height: auto;" />
<div style="display: flex; justify-content: flex-end; margin-top: 24px;">
<a-button @click="handleClosePhoto">关闭</a-button>
</div>
</a-modal>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { message } from 'ant-design-vue'
// 搜索条件
const searchKeyword = ref('')
const typeFilter = ref('')
const statusFilter = ref('')
const quarantinePersonFilter = ref('')
const dateRange = ref([])
// 分页配置
const pagination = reactive({
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total, range) => `${range[0]}-${range[1]} 条,共 ${total}`
})
// 选中行
const selectedRowKeys = ref([])
const onSelectChange = (newSelectedRowKeys) => {
selectedRowKeys.value = newSelectedRowKeys
}
// 模态框状态
const isViewModalOpen = ref(false)
const isPhotoModalOpen = ref(false)
// 当前查看的记录和照片
const viewRecord = ref(null)
const currentPhotoUrl = ref('')
// 检疫记录数据(模拟数据)
const quarantineRecords = ref([
{
id: '1',
declarationNumber: 'JD20231001001',
declarationUnit: '郑州市金水区阳光养殖场',
contactPerson: '张三',
phone: '13812345678',
type: 'animal',
object: '牛',
quantity: 150,
declarationDate: '2023-10-01',
quarantinePerson: '王五',
quarantineTime: '2023-10-02 10:30:00',
result: true,
certificateNumber: 'JZZS20231002001',
quarantineItems: [
{ id: '1', name: '体温检测' },
{ id: '2', name: '临床检查' },
{ id: '3', name: '疫苗接种记录' },
{ id: '4', name: '疫情监测' }
],
processDescription: '按照《动物检疫管理办法》规定对申报的150头牛进行了体温检测、临床检查、疫苗接种记录核查和疫情监测所有检测项目均符合要求。',
conclusion: '经检疫,该批牛只健康状况良好,无传染病症状,符合出证条件,准予放行。',
photos: [
{ id: '1', url: 'https://via.placeholder.com/300x200?text=Photo1' },
{ id: '2', url: 'https://via.placeholder.com/300x200?text=Photo2' }
],
reviewComments: '符合检疫要求,同意通过',
notes: '',
status: 'approved',
createdAt: '2023-10-01'
},
{
id: '2',
declarationNumber: 'JD20231002001',
declarationUnit: '新郑市绿源养殖场',
contactPerson: '李四',
phone: '13912345678',
type: 'product',
object: '牛肉',
quantity: 500,
declarationDate: '2023-10-02',
quarantinePerson: '赵六',
quarantineTime: '2023-10-03 14:20:00',
result: false,
certificateNumber: '',
quarantineItems: [
{ id: '5', name: '感官检查' },
{ id: '6', name: '微生物检测' },
{ id: '7', name: '兽药残留检测' },
{ id: '8', name: '重金属检测' }
],
processDescription: '对申报的500公斤牛肉进行了感官检查、微生物检测、兽药残留检测和重金属检测发现微生物指标超标。',
conclusion: '经检疫,该批牛肉微生物指标不符合食品安全标准,不予出证,禁止销售。',
photos: [
{ id: '3', url: 'https://via.placeholder.com/300x200?text=Photo3' },
{ id: '4', url: 'https://via.placeholder.com/300x200?text=Photo4' }
],
reviewComments: '微生物指标超标,不同意通过',
notes: '建议加强冷链管理',
status: 'rejected',
createdAt: '2023-10-02'
},
{
id: '3',
declarationNumber: 'JD20231003001',
declarationUnit: '新密市祥和养殖场',
contactPerson: '王五',
phone: '13712345678',
type: 'slaughter',
object: '牛',
quantity: 80,
declarationDate: '2023-10-03',
quarantinePerson: '孙七',
quarantineTime: '2023-10-04 09:15:00',
result: true,
certificateNumber: 'JZZS20231004001',
quarantineItems: [
{ id: '9', name: '宰前检查' },
{ id: '10', name: '宰后检验' },
{ id: '11', name: '内脏检查' },
{ id: '12', name: '肉品质量检查' }
],
processDescription: '对80头牛进行了宰前检查确认健康状况良好宰后进行了头蹄、内脏、胴体等部位的检验未发现异常。',
conclusion: '经检疫,该批屠宰牛只健康状况良好,符合屠宰检疫要求,准予屠宰销售。',
photos: [
{ id: '5', url: 'https://via.placeholder.com/300x200?text=Photo5' },
{ id: '6', url: 'https://via.placeholder.com/300x200?text=Photo6' }
],
reviewComments: '符合检疫要求,同意通过',
notes: '',
status: 'approved',
createdAt: '2023-10-03'
},
{
id: '4',
declarationNumber: 'JD20231004001',
declarationUnit: '登封市幸福养殖场',
contactPerson: '赵六',
phone: '13612345678',
type: 'animal',
object: '牛',
quantity: 120,
declarationDate: '2023-10-04',
quarantinePerson: '钱八',
quarantineTime: '',
result: false,
certificateNumber: '',
quarantineItems: [],
processDescription: '',
conclusion: '',
photos: [],
reviewComments: '',
notes: '',
status: 'pending',
createdAt: '2023-10-04'
},
{
id: '5',
declarationNumber: 'JD20231005001',
declarationUnit: '中牟县希望养殖场',
contactPerson: '钱七',
phone: '13512345678',
type: 'product',
object: '牛奶',
quantity: 2000,
declarationDate: '2023-10-05',
quarantinePerson: '周九',
quarantineTime: '2023-10-06 11:45:00',
result: true,
certificateNumber: 'JZZS20231006001',
quarantineItems: [
{ id: '13', name: '感官检查' },
{ id: '14', name: '理化指标检测' },
{ id: '15', name: '微生物检测' },
{ id: '16', name: '兽药残留检测' }
],
processDescription: '对2000升牛奶进行了感官检查、理化指标检测、微生物检测和兽药残留检测所有指标均符合国家标准。',
conclusion: '经检疫,该批牛奶质量合格,符合食品安全标准,准予销售。',
photos: [
{ id: '7', url: 'https://via.placeholder.com/300x200?text=Photo7' },
{ id: '8', url: 'https://via.placeholder.com/300x200?text=Photo8' }
],
reviewComments: '符合检疫要求,同意通过',
notes: '',
status: 'approved',
createdAt: '2023-10-05'
},
{
id: '6',
declarationNumber: 'JD20231006001',
declarationUnit: '荥阳市快乐养殖场',
contactPerson: '孙八',
phone: '13412345678',
type: 'transport',
object: '牛',
quantity: 90,
declarationDate: '2023-10-06',
quarantinePerson: '吴十',
quarantineTime: '2023-10-07 08:30:00',
result: true,
certificateNumber: 'JZZS20231007001',
quarantineItems: [
{ id: '17', name: '动物健康检查' },
{ id: '18', name: '运输工具检查' },
{ id: '19', name: '消毒情况检查' },
{ id: '20', name: '检疫证明检查' }
],
processDescription: '对90头牛进行了健康检查确认无异常检查运输工具符合要求已消毒检疫证明齐全。',
conclusion: '经检疫,该批运输牛只符合跨区域调运要求,准予调运。',
photos: [
{ id: '9', url: 'https://via.placeholder.com/300x200?text=Photo9' },
{ id: '10', url: 'https://via.placeholder.com/300x200?text=Photo10' }
],
reviewComments: '符合检疫要求,同意通过',
notes: '',
status: 'approved',
createdAt: '2023-10-06'
},
{
id: '7',
declarationNumber: 'JD20231007001',
declarationUnit: '巩义市明星养殖场',
contactPerson: '周九',
phone: '13312345678',
type: 'slaughter',
object: '牛',
quantity: 60,
declarationDate: '2023-10-07',
quarantinePerson: '郑十一',
quarantineTime: '2023-10-08 13:20:00',
result: true,
certificateNumber: 'JZZS20231008001',
quarantineItems: [
{ id: '21', name: '宰前检查' },
{ id: '22', name: '宰后检验' },
{ id: '23', name: '内脏检查' },
{ id: '24', name: '肉品质量检查' }
],
processDescription: '对60头牛进行了宰前检查确认健康状况良好宰后进行了头蹄、内脏、胴体等部位的检验未发现异常。',
conclusion: '经检疫,该批屠宰牛只健康状况良好,符合屠宰检疫要求,准予屠宰销售。',
photos: [
{ id: '11', url: 'https://via.placeholder.com/300x200?text=Photo11' },
{ id: '12', url: 'https://via.placeholder.com/300x200?text=Photo12' }
],
reviewComments: '符合检疫要求,同意通过',
notes: '',
status: 'approved',
createdAt: '2023-10-07'
},
{
id: '8',
declarationNumber: 'JD20231008001',
declarationUnit: '惠济区温馨养殖场',
contactPerson: '吴十',
phone: '13212345678',
type: 'other',
object: '牛精液',
quantity: 500,
declarationDate: '2023-10-08',
quarantinePerson: '王十二',
quarantineTime: '',
result: false,
certificateNumber: '',
quarantineItems: [],
processDescription: '',
conclusion: '',
photos: [],
reviewComments: '',
notes: '',
status: 'pending',
createdAt: '2023-10-08'
},
{
id: '9',
declarationNumber: 'JD20231009001',
declarationUnit: '二七区红火养殖场',
contactPerson: '郑十一',
phone: '13112345678',
type: 'animal',
object: '牛',
quantity: 100,
declarationDate: '2023-10-09',
quarantinePerson: '张三',
quarantineTime: '',
result: false,
certificateNumber: '',
quarantineItems: [],
processDescription: '',
conclusion: '',
photos: [],
reviewComments: '申请人主动取消',
notes: '',
status: 'cancelled',
createdAt: '2023-10-09'
},
{
id: '10',
declarationNumber: 'JD20231010001',
declarationUnit: '中原区丰收养殖场',
contactPerson: '王十二',
phone: '13012345678',
type: 'product',
object: '牛肉制品',
quantity: 300,
declarationDate: '2023-10-10',
quarantinePerson: '李四',
quarantineTime: '',
result: false,
certificateNumber: '',
quarantineItems: [],
processDescription: '',
conclusion: '',
photos: [],
reviewComments: '',
notes: '',
status: 'pending',
createdAt: '2023-10-10'
}
])
// 表格列定义
const columns = [
{
title: '申报单号',
dataIndex: 'declarationNumber',
key: 'declarationNumber',
ellipsis: true
},
{
title: '申报单位',
dataIndex: 'declarationUnit',
key: 'declarationUnit',
ellipsis: true
},
{
title: '检疫类型',
dataIndex: 'type',
key: 'type',
width: 120,
customRender: ({ text }) => getTypeText(text)
},
{
title: '检疫对象',
dataIndex: 'object',
key: 'object',
width: 100
},
{
title: '数量',
dataIndex: 'quantity',
key: 'quantity',
width: 80
},
{
title: '检疫人员',
dataIndex: 'quarantinePerson',
key: 'quarantinePerson',
width: 100
},
{
title: '检疫时间',
dataIndex: 'quarantineTime',
key: 'quarantineTime',
width: 150
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
width: 80,
scopedSlots: { customRender: 'status' }
},
{
title: '操作',
key: 'action',
width: 180,
scopedSlots: { customRender: 'action' }
}
]
// 状态文本
const getStatusText = (status) => {
const statusMap = {
pending: '待审核',
approved: '已通过',
rejected: '已驳回',
cancelled: '已取消'
}
return statusMap[status] || status
}
// 状态颜色
const getStatusColor = (status) => {
const colorMap = {
pending: 'blue',
approved: 'green',
rejected: 'red',
cancelled: 'default'
}
return colorMap[status] || 'default'
}
// 类型文本
const getTypeText = (type) => {
const typeMap = {
animal: '动物检疫',
product: '动物产品检疫',
transport: '运输检疫',
slaughter: '屠宰检疫',
other: '其他检疫'
}
return typeMap[type] || type
}
// 搜索处理
const handleSearch = () => {
// 在实际应用中这里应该调用API获取数据
pagination.current = 1
// 模拟搜索效果
message.success('搜索成功')
}
// 重置处理
const handleReset = () => {
searchKeyword.value = ''
typeFilter.value = ''
statusFilter.value = ''
quarantinePersonFilter.value = ''
dateRange.value = []
pagination.current = 1
}
// 导出处理
const handleExport = () => {
// 在实际应用中这里应该调用API导出数据
message.success('导出功能待实现')
}
// 查看记录
const handleView = (record) => {
viewRecord.value = { ...record }
isViewModalOpen.value = true
}
// 打印记录
const handlePrint = (id) => {
// 在实际应用中这里应该调用API获取打印数据
message.success('打印功能待实现')
}
// 重新检疫
const handleRecheck = (id) => {
// 在实际应用中这里应该调用API创建重新检疫任务
message.success('重新检疫功能待实现')
}
// 关闭查看模态框
const handleCloseView = () => {
isViewModalOpen.value = false
}
// 查看照片
const handleViewPhoto = (photo) => {
currentPhotoUrl.value = photo.url
isPhotoModalOpen.value = true
}
// 关闭照片模态框
const handleClosePhoto = () => {
isPhotoModalOpen.value = false
}
</script>
<style scoped>
h1 {
font-size: 20px;
font-weight: 600;
margin-bottom: 20px;
color: #333;
}
</style>

View File

@@ -0,0 +1,754 @@
<template>
<div>
<h1>检疫报表导出</h1>
<!-- 搜索和筛选条件 -->
<a-card style="margin-bottom: 16px;">
<div style="display: flex; flex-wrap: wrap; gap: 16px; align-items: center;">
<a-select v-model:value="reportType" placeholder="报表类型" style="width: 200px;">
<a-select-option value="daily">日报</a-select-option>
<a-select-option value="weekly">周报</a-select-option>
<a-select-option value="monthly">月报</a-select-option>
<a-select-option value="quarterly">季报</a-select-option>
<a-select-option value="yearly">年报</a-select-option>
<a-select-option value="custom">自定义报表</a-select-option>
</a-select>
<div v-if="reportType === 'custom'" style="display: flex; gap: 16px;">
<a-date-picker
v-model:value="dateRange[0]"
placeholder="开始日期"
style="width: 180px;"
/>
<span style="line-height: 32px;"></span>
<a-date-picker
v-model:value="dateRange[1]"
placeholder="结束日期"
style="width: 180px;"
/>
</div>
<a-select v-model:value="quarantineType" placeholder="检疫类型" style="width: 150px;">
<a-select-option value="">全部</a-select-option>
<a-select-option value="animal">动物检疫</a-select-option>
<a-select-option value="product">动物产品检疫</a-select-option>
<a-select-option value="transport">运输检疫</a-select-option>
<a-select-option value="slaughter">屠宰检疫</a-select-option>
<a-select-option value="other">其他检疫</a-select-option>
</a-select>
<a-select v-model:value="reportFormat" placeholder="导出格式" style="width: 120px;">
<a-select-option value="excel">Excel</a-select-option>
<a-select-option value="pdf">PDF</a-select-option>
<a-select-option value="csv">CSV</a-select-option>
</a-select>
<a-button type="primary" @click="handleGenerateReport" style="margin-left: auto;">
<span class="iconfont icon-baocun"></span> 生成报表
</a-button>
</div>
</a-card>
<!-- 报表配置 -->
<a-card style="margin-bottom: 16px;">
<h3>报表配置</h3>
<div style="display: flex; flex-wrap: wrap; gap: 24px; margin-top: 16px;">
<div style="flex: 1; min-width: 300px;">
<h4 style="margin-bottom: 12px;">报表内容</h4>
<a-checkbox-group v-model:value="reportContents">
<a-checkbox value="quarantineCount">检疫数量统计</a-checkbox><br/>
<a-checkbox value="quarantineResult">检疫结果统计</a-checkbox><br/>
<a-checkbox value="quarantineType">检疫类型分布</a-checkbox><br/>
<a-checkbox value="quarantineLocation">检疫地点分布</a-checkbox><br/>
<a-checkbox value="quarantinePersonnel">检疫人员工作量</a-checkbox><br/>
<a-checkbox value="problemAnalysis">问题分析</a-checkbox><br/>
<a-checkbox value="trendAnalysis">趋势分析</a-checkbox><br/>
</a-checkbox-group>
</div>
<div style="flex: 1; min-width: 300px;">
<h4 style="margin-bottom: 12px;">报表样式</h4>
<a-radio-group v-model:value="reportStyle">
<a-radio :value="'summary'">简洁汇总</a-radio><br/>
<a-radio :value="'detailed'">详细报表</a-radio><br/>
<a-radio :value="'graphical'">图文并茂</a-radio><br/>
</a-radio-group>
<div style="margin-top: 16px;">
<a-checkbox v-model:checked="includeChart">包含图表</a-checkbox><br/>
<a-checkbox v-model:checked="includeComparison">包含同比环比</a-checkbox><br/>
<a-checkbox v-model:checked="includeRecommendations">包含建议分析</a-checkbox><br/>
</div>
</div>
</div>
</a-card>
<!-- 报表预览 -->
<a-card>
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
<h3>报表预览</h3>
<a-button v-if="generatedReport" type="primary" danger @click="handleDownloadReport">
<span class="iconfont icon-xiazai"></span> 下载报表
</a-button>
</div>
<div v-if="!generatedReport">
<p style="text-align: center; color: #999; padding: 40px 0;">请选择报表类型和筛选条件点击生成报表按钮</p>
</div>
<div v-else class="report-preview">
<!-- 报表头部 -->
<div class="report-header">
<h2>动物检疫统计报表</h2>
<div class="report-info">
<span>报表类型{{ getReportTypeText() }}</span>
<span>统计时间{{ getReportTimeRange() }}</span>
<span>生成时间{{ generateTime }}</span>
</div>
</div>
<!-- 报表内容 -->
<div class="report-content">
<!-- 检疫数量统计 -->
<div v-if="reportContents.includes('quarantineCount')" class="report-section">
<h3>检疫数量统计</h3>
<a-table :data-source="quarantineCountData" :columns="quarantineCountColumns" pagination="false" :size="'small'" :bordered="true" style="margin-top: 16px;">
</a-table>
<!-- 图表 -->
<div v-if="includeChart" ref="quarantineCountChart" style="height: 300px; margin-top: 20px;"></div>
</div>
<!-- 检疫结果统计 -->
<div v-if="reportContents.includes('quarantineResult')" class="report-section">
<h3>检疫结果统计</h3>
<a-table :data-source="quarantineResultData" :columns="quarantineResultColumns" pagination="false" :size="'small'" :bordered="true" style="margin-top: 16px;">
</a-table>
<!-- 图表 -->
<div v-if="includeChart" ref="quarantineResultChart" style="height: 300px; margin-top: 20px;"></div>
</div>
<!-- 检疫类型分布 -->
<div v-if="reportContents.includes('quarantineType')" class="report-section">
<h3>检疫类型分布</h3>
<a-table :data-source="quarantineTypeData" :columns="quarantineTypeColumns" pagination="false" :size="'small'" :bordered="true" style="margin-top: 16px;">
</a-table>
<!-- 图表 -->
<div v-if="includeChart" ref="quarantineTypeChart" style="height: 300px; margin-top: 20px;"></div>
</div>
<!-- 检疫地点分布 -->
<div v-if="reportContents.includes('quarantineLocation')" class="report-section">
<h3>检疫地点分布</h3>
<a-table :data-source="quarantineLocationData" :columns="quarantineLocationColumns" pagination="false" :size="'small'" :bordered="true" style="margin-top: 16px;">
</a-table>
</div>
<!-- 检疫人员工作量 -->
<div v-if="reportContents.includes('quarantinePersonnel')" class="report-section">
<h3>检疫人员工作量</h3>
<a-table :data-source="quarantinePersonnelData" :columns="quarantinePersonnelColumns" pagination="false" :size="'small'" :bordered="true" style="margin-top: 16px;">
</a-table>
</div>
<!-- 问题分析 -->
<div v-if="reportContents.includes('problemAnalysis')" class="report-section">
<h3>问题分析</h3>
<div class="analysis-content">
<p>根据统计数据本期检疫工作中主要存在以下问题</p>
<ol>
<li>部分地区动物检疫申报不及时影响检疫效率</li>
<li>个别养殖场的防疫条件有待改善存在一定风险</li>
<li>运输检疫中部分运输工具消毒不彻底</li>
<li>基层检疫人员专业技能需要进一步提升</li>
</ol>
</div>
</div>
<!-- 趋势分析 -->
<div v-if="reportContents.includes('trendAnalysis')" class="report-section">
<h3>趋势分析</h3>
<div v-if="includeChart" ref="trendAnalysisChart" style="height: 300px;"></div>
<div v-else class="analysis-content">
<p>本期检疫数量较上期有所增加主要原因是</p>
<ol>
<li>养殖规模扩大出栏量增加</li>
<li>加强了检疫宣传申报意识提高</li>
<li>运输量增加运输检疫需求增长</li>
</ol>
</div>
</div>
</div>
</div>
</a-card>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, nextTick } from 'vue'
import { message } from 'ant-design-vue'
import * as echarts from 'echarts'
// 报表类型
const reportType = ref('monthly')
// 日期范围
const dateRange = ref([null, null])
// 检疫类型筛选
const quarantineType = ref('')
// 导出格式
const reportFormat = ref('excel')
// 报表内容选项
const reportContents = ref(['quarantineCount', 'quarantineResult', 'quarantineType'])
// 报表样式
const reportStyle = ref('summary')
// 是否包含图表
const includeChart = ref(true)
// 是否包含同比环比
const includeComparison = ref(true)
// 是否包含建议分析
const includeRecommendations = ref(true)
// 是否已生成报表
const generatedReport = ref(false)
// 报表生成时间
const generateTime = ref('')
// 检疫数量统计数据(模拟数据)
const quarantineCountData = ref([
{ type: '动物检疫', count: 1256, increase: 12.5 },
{ type: '动物产品检疫', count: 897, increase: 8.3 },
{ type: '运输检疫', count: 654, increase: 15.2 },
{ type: '屠宰检疫', count: 987, increase: 5.7 },
{ type: '其他检疫', count: 324, increase: 2.1 }
])
// 检疫数量统计列定义
const quarantineCountColumns = [
{ title: '检疫类型', dataIndex: 'type', key: 'type' },
{ title: '检疫数量', dataIndex: 'count', key: 'count' },
{ title: '同比增长(%)', dataIndex: 'increase', key: 'increase' }
]
// 检疫结果统计数据(模拟数据)
const quarantineResultData = ref([
{ result: '合格', count: 3521, rate: 92.3 },
{ result: '不合格', count: 293, rate: 7.7 }
])
// 检疫结果统计列定义
const quarantineResultColumns = [
{ title: '检疫结果', dataIndex: 'result', key: 'result' },
{ title: '数量', dataIndex: 'count', key: 'count' },
{ title: '占比(%)', dataIndex: 'rate', key: 'rate' }
]
// 检疫类型分布数据(模拟数据)
const quarantineTypeData = ref([
{ type: '生猪', count: 1567, rate: 40.9 },
{ type: '家禽', count: 897, rate: 23.3 },
{ type: '牛', count: 654, rate: 17.0 },
{ type: '羊', count: 324, rate: 8.4 },
{ type: '其他', count: 397, rate: 10.3 }
])
// 检疫类型分布列定义
const quarantineTypeColumns = [
{ title: '动物类型', dataIndex: 'type', key: 'type' },
{ title: '数量', dataIndex: 'count', key: 'count' },
{ title: '占比(%)', dataIndex: 'rate', key: 'rate' }
]
// 检疫地点分布数据(模拟数据)
const quarantineLocationData = ref([
{ location: '养殖场', count: 1897, rate: 49.4 },
{ location: '屠宰场', count: 987, rate: 25.7 },
{ location: '交易市场', count: 654, rate: 17.0 },
{ location: '运输环节', count: 324, rate: 8.4 }
])
// 检疫地点分布列定义
const quarantineLocationColumns = [
{ title: '检疫地点', dataIndex: 'location', key: 'location' },
{ title: '数量', dataIndex: 'count', key: 'count' },
{ title: '占比(%)', dataIndex: 'rate', key: 'rate' }
]
// 检疫人员工作量数据(模拟数据)
const quarantinePersonnelData = ref([
{ name: '张三', count: 567, rate: 14.8 },
{ name: '李四', count: 489, rate: 12.8 },
{ name: '王五', count: 456, rate: 11.9 },
{ name: '赵六', count: 423, rate: 11.0 },
{ name: '钱七', count: 398, rate: 10.4 },
{ name: '孙八', count: 376, rate: 9.8 },
{ name: '周九', count: 356, rate: 9.3 },
{ name: '吴十', count: 334, rate: 8.7 }
])
// 检疫人员工作量列定义
const quarantinePersonnelColumns = [
{ title: '检疫人员', dataIndex: 'name', key: 'name' },
{ title: '检疫数量', dataIndex: 'count', key: 'count' },
{ title: '占比(%)', dataIndex: 'rate', key: 'rate' }
]
// 图表引用
const quarantineCountChart = ref(null)
const quarantineResultChart = ref(null)
const quarantineTypeChart = ref(null)
const trendAnalysisChart = ref(null)
// 图表实例
let quarantineCountChartInstance = null
let quarantineResultChartInstance = null
let quarantineTypeChartInstance = null
let trendAnalysisChartInstance = null
// 获取报表类型文本
const getReportTypeText = () => {
const typeMap = {
daily: '日报',
weekly: '周报',
monthly: '月报',
quarterly: '季报',
yearly: '年报',
custom: '自定义报表'
}
return typeMap[reportType.value] || ''
}
// 获取报表时间范围
const getReportTimeRange = () => {
if (reportType.value === 'custom' && dateRange.value[0] && dateRange.value[1]) {
return `${formatDate(dateRange.value[0])}${formatDate(dateRange.value[1])}`
} else {
// 根据报表类型返回默认时间范围
const now = new Date()
let startDate, endDate
switch (reportType.value) {
case 'daily':
startDate = now
endDate = now
break
case 'weekly':
startDate = new Date(now.getTime() - (now.getDay() || 7) * 24 * 60 * 60 * 1000)
endDate = new Date(startDate.getTime() + 6 * 24 * 60 * 60 * 1000)
break
case 'monthly':
startDate = new Date(now.getFullYear(), now.getMonth(), 1)
endDate = new Date(now.getFullYear(), now.getMonth() + 1, 0)
break
case 'quarterly':
const quarter = Math.floor(now.getMonth() / 3)
startDate = new Date(now.getFullYear(), quarter * 3, 1)
endDate = new Date(now.getFullYear(), quarter * 3 + 3, 0)
break
case 'yearly':
startDate = new Date(now.getFullYear(), 0, 1)
endDate = new Date(now.getFullYear(), 11, 31)
break
default:
startDate = now
endDate = now
}
return `${formatDate(startDate)}${formatDate(endDate)}`
}
}
// 格式化日期
const formatDate = (date) => {
if (!date) return ''
const d = new Date(date)
const year = d.getFullYear()
const month = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
}
// 生成报表
const handleGenerateReport = () => {
// 在实际应用中这里应该调用API获取数据
generateTime.value = new Date().toLocaleString('zh-CN')
generatedReport.value = true
// 等待DOM更新后初始化图表
nextTick(() => {
if (includeChart.value) {
initCharts()
}
})
message.success('报表生成成功')
}
// 下载报表
const handleDownloadReport = () => {
// 在实际应用中这里应该调用API下载报表
message.success(`报表已以${getFormatText(reportFormat.value)}格式下载`)
}
// 获取导出格式文本
const getFormatText = (format) => {
const formatMap = {
excel: 'Excel',
pdf: 'PDF',
csv: 'CSV'
}
return formatMap[format] || format
}
// 初始化图表
const initCharts = () => {
// 销毁已存在的图表实例
if (quarantineCountChartInstance) {
quarantineCountChartInstance.dispose()
}
if (quarantineResultChartInstance) {
quarantineResultChartInstance.dispose()
}
if (quarantineTypeChartInstance) {
quarantineTypeChartInstance.dispose()
}
if (trendAnalysisChartInstance) {
trendAnalysisChartInstance.dispose()
}
// 初始化检疫数量统计图表
if (quarantineCountChart.value && reportContents.value.includes('quarantineCount')) {
quarantineCountChartInstance = echarts.init(quarantineCountChart.value)
const option = {
title: {
text: '检疫数量统计',
left: 'center'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: quarantineCountData.value.map(item => item.type)
},
yAxis: [
{
type: 'value',
name: '检疫数量',
position: 'left'
},
{
type: 'value',
name: '增长率(%)',
position: 'right',
axisLabel: {
formatter: '{value}%'
}
}
],
series: [
{
name: '检疫数量',
type: 'bar',
data: quarantineCountData.value.map(item => item.count)
},
{
name: '增长率',
type: 'line',
yAxisIndex: 1,
data: quarantineCountData.value.map(item => item.increase),
axisLabel: {
formatter: '{value}%'
}
}
]
}
quarantineCountChartInstance.setOption(option)
}
// 初始化检疫结果统计图表
if (quarantineResultChart.value && reportContents.value.includes('quarantineResult')) {
quarantineResultChartInstance = echarts.init(quarantineResultChart.value)
const option = {
title: {
text: '检疫结果统计',
left: 'center'
},
tooltip: {
trigger: 'item'
},
legend: {
orient: 'vertical',
left: 'left'
},
series: [
{
name: '检疫结果',
type: 'pie',
radius: '50%',
data: quarantineResultData.value.map(item => ({
value: item.count,
name: item.result
})),
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
},
label: {
formatter: '{b}: {c} ({d}%)'
}
}
]
}
quarantineResultChartInstance.setOption(option)
}
// 初始化检疫类型分布图表
if (quarantineTypeChart.value && reportContents.value.includes('quarantineType')) {
quarantineTypeChartInstance = echarts.init(quarantineTypeChart.value)
const option = {
title: {
text: '检疫类型分布',
left: 'center'
},
tooltip: {
trigger: 'item'
},
legend: {
orient: 'vertical',
left: 'left'
},
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: 20,
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: quarantineTypeData.value.map(item => ({
value: item.count,
name: item.type
}))
}
]
}
quarantineTypeChartInstance.setOption(option)
}
// 初始化趋势分析图表
if (trendAnalysisChart.value && reportContents.value.includes('trendAnalysis')) {
trendAnalysisChartInstance = echarts.init(trendAnalysisChart.value)
const option = {
title: {
text: '检疫数量趋势分析',
left: 'center'
},
tooltip: {
trigger: 'axis'
},
legend: {
data: ['动物检疫', '动物产品检疫', '运输检疫'],
bottom: 0
},
grid: {
left: '3%',
right: '4%',
bottom: '15%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
},
yAxis: {
type: 'value'
},
series: [
{
name: '动物检疫',
type: 'line',
data: [1200, 1100, 1300, 1250, 1400, 1350, 1500, 1450, 1600, 1550, 1700, 1650]
},
{
name: '动物产品检疫',
type: 'line',
data: [800, 850, 820, 900, 880, 950, 920, 980, 1000, 960, 1050, 1020]
},
{
name: '运输检疫',
type: 'line',
data: [600, 650, 700, 680, 750, 720, 800, 780, 850, 830, 900, 880]
}
]
}
trendAnalysisChartInstance.setOption(option)
}
}
// 组件挂载时
onMounted(() => {
// 监听窗口大小变化,调整图表
window.addEventListener('resize', () => {
if (quarantineCountChartInstance) {
quarantineCountChartInstance.resize()
}
if (quarantineResultChartInstance) {
quarantineResultChartInstance.resize()
}
if (quarantineTypeChartInstance) {
quarantineTypeChartInstance.resize()
}
if (trendAnalysisChartInstance) {
trendAnalysisChartInstance.resize()
}
})
})
// 组件卸载时
const onUnmounted = () => {
// 销毁图表实例
if (quarantineCountChartInstance) {
quarantineCountChartInstance.dispose()
}
if (quarantineResultChartInstance) {
quarantineResultChartInstance.dispose()
}
if (quarantineTypeChartInstance) {
quarantineTypeChartInstance.dispose()
}
if (trendAnalysisChartInstance) {
trendAnalysisChartInstance.dispose()
}
// 移除事件监听
window.removeEventListener('resize', () => {
// 清理事件监听
})
}
</script>
<style scoped>
h1 {
font-size: 20px;
font-weight: 600;
margin-bottom: 20px;
color: #333;
}
h3 {
font-size: 16px;
font-weight: 600;
margin-bottom: 12px;
color: #333;
}
h4 {
font-size: 14px;
font-weight: 600;
color: #555;
}
.report-preview {
background: white;
padding: 20px;
border: 1px solid #e8e8e8;
}
.report-header {
text-align: center;
margin-bottom: 30px;
}
.report-header h2 {
font-size: 24px;
font-weight: 600;
margin-bottom: 10px;
color: #333;
}
.report-info {
display: flex;
justify-content: center;
gap: 30px;
font-size: 14px;
color: #666;
}
.report-content {
margin-top: 20px;
}
.report-section {
margin-bottom: 30px;
}
.report-section h3 {
font-size: 18px;
font-weight: 600;
margin-bottom: 15px;
color: #333;
padding-bottom: 10px;
border-bottom: 2px solid #1890ff;
}
.analysis-content {
padding: 15px;
background: #f9f9f9;
border-left: 4px solid #1890ff;
font-size: 14px;
line-height: 1.8;
}
.analysis-content p {
margin-bottom: 10px;
}
.analysis-content ol {
padding-left: 20px;
}
.analysis-content li {
margin-bottom: 5px;
}
</style>

View File

@@ -0,0 +1,189 @@
<template>
<div class="slaughterhouse-container">
<div class="page-header">
<h1>屠宰场管理</h1>
</div>
<div class="content-wrapper">
<!-- 工具栏 -->
<div class="toolbar">
<a-button type="primary" @click="handleAdd">新增屠宰场</a-button>
<a-input-search
placeholder="搜索屠宰场名称"
style="width: 300px; margin-left: 16px;"
@search="handleSearch"
/>
</div>
<!-- 数据表格 -->
<a-table
:columns="columns"
:data-source="slaughterhouses"
row-key="id"
pagination
>
<template #column:action="{ record }">
<a-space>
<a-button type="link" @click="handleEdit(record)">编辑</a-button>
<a-button type="link" danger @click="handleDelete(record.id)">删除</a-button>
<a-button type="link" @click="handleDetail(record.id)">详情</a-button>
</a-space>
</template>
<template #column:status="{ text }">
<a-tag :color="text === '正常' ? 'green' : 'red'">
{{ text }}
</a-tag>
</template>
</a-table>
</div>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
// 模拟数据
const slaughterhouses = ref([
{
id: '1',
name: '东方屠宰场',
address: '北京市朝阳区东方路123号',
contactPerson: '张三',
contactPhone: '13800138001',
licenseNumber: 'SL20230001',
status: '正常',
createTime: '2023-01-15'
},
{
id: '2',
name: '南方屠宰场',
address: '北京市海淀区南大街45号',
contactPerson: '李四',
contactPhone: '13900139002',
licenseNumber: 'SL20230002',
status: '正常',
createTime: '2023-02-20'
},
{
id: '3',
name: '北方屠宰场',
address: '北京市西城区北大街67号',
contactPerson: '王五',
contactPhone: '13700137003',
licenseNumber: 'SL20230003',
status: '暂停营业',
createTime: '2023-03-10'
}
])
// 表格列配置
const columns = [
{
title: '屠宰场名称',
dataIndex: 'name',
key: 'name'
},
{
title: '地址',
dataIndex: 'address',
key: 'address'
},
{
title: '联系人',
dataIndex: 'contactPerson',
key: 'contactPerson'
},
{
title: '联系电话',
dataIndex: 'contactPhone',
key: 'contactPhone'
},
{
title: '许可证号',
dataIndex: 'licenseNumber',
key: 'licenseNumber'
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
scopedSlots: { customRender: 'status' }
},
{
title: '创建时间',
dataIndex: 'createTime',
key: 'createTime'
},
{
title: '操作',
key: 'action',
scopedSlots: { customRender: 'action' }
}
]
// 处理搜索
const handleSearch = (value) => {
console.log('搜索:', value)
// 实际项目中这里应该调用API进行搜索
}
// 处理新增
const handleAdd = () => {
console.log('新增屠宰场')
// 实际项目中这里应该打开新增表单
}
// 处理编辑
const handleEdit = (record) => {
console.log('编辑屠宰场:', record)
// 实际项目中这里应该打开编辑表单
}
// 处理删除
const handleDelete = (id) => {
console.log('删除屠宰场:', id)
// 实际项目中这里应该弹出确认框并调用API删除
}
// 处理查看详情
const handleDetail = (id) => {
console.log('查看屠宰场详情:', id)
// 实际项目中这里应该打开详情页面
}
return {
slaughterhouses,
columns,
handleSearch,
handleAdd,
handleEdit,
handleDelete,
handleDetail
}
}
}
</script>
<style scoped>
.slaughterhouse-container {
padding: 24px;
background: #fff;
min-height: 100vh;
}
.page-header {
margin-bottom: 24px;
}
.toolbar {
margin-bottom: 16px;
}
.content-wrapper {
background: #fff;
padding: 24px;
border-radius: 2px;
}
</style>

View File

@@ -0,0 +1,189 @@
<template>
<div class="harmless-place-container">
<div class="page-header">
<h1>无害化场所管理</h1>
</div>
<div class="content-wrapper">
<!-- 工具栏 -->
<div class="toolbar">
<a-button type="primary" @click="handleAdd">新增无害化场所</a-button>
<a-input-search
placeholder="搜索场所名称"
style="width: 300px; margin-left: 16px;"
@search="handleSearch"
/>
</div>
<!-- 数据表格 -->
<a-table
:columns="columns"
:data-source="harmlessPlaces"
row-key="id"
pagination
>
<template #column:action="{ record }">
<a-space>
<a-button type="link" @click="handleEdit(record)">编辑</a-button>
<a-button type="link" danger @click="handleDelete(record.id)">删除</a-button>
<a-button type="link" @click="handleDetail(record.id)">详情</a-button>
</a-space>
</template>
<template #column:status="{ text }">
<a-tag :color="text === '正常' ? 'green' : 'red'">
{{ text }}
</a-tag>
</template>
</a-table>
</div>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
// 模拟数据
const harmlessPlaces = ref([
{
id: '1',
name: '北京无害化处理中心',
address: '北京市顺义区无害化路88号',
contactPerson: '赵六',
contactPhone: '13600136001',
licenseNumber: 'HP20230001',
status: '正常',
createTime: '2023-01-20'
},
{
id: '2',
name: '天津无害化处理站',
address: '天津市滨海新区处理路56号',
contactPerson: '钱七',
contactPhone: '13500135002',
licenseNumber: 'HP20230002',
status: '正常',
createTime: '2023-02-25'
},
{
id: '3',
name: '河北无害化处理厂',
address: '河北省廊坊市大厂县处理路34号',
contactPerson: '孙八',
contactPhone: '13400134003',
licenseNumber: 'HP20230003',
status: '维护中',
createTime: '2023-03-15'
}
])
// 表格列配置
const columns = [
{
title: '场所名称',
dataIndex: 'name',
key: 'name'
},
{
title: '地址',
dataIndex: 'address',
key: 'address'
},
{
title: '联系人',
dataIndex: 'contactPerson',
key: 'contactPerson'
},
{
title: '联系电话',
dataIndex: 'contactPhone',
key: 'contactPhone'
},
{
title: '许可证号',
dataIndex: 'licenseNumber',
key: 'licenseNumber'
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
scopedSlots: { customRender: 'status' }
},
{
title: '创建时间',
dataIndex: 'createTime',
key: 'createTime'
},
{
title: '操作',
key: 'action',
scopedSlots: { customRender: 'action' }
}
]
// 处理搜索
const handleSearch = (value) => {
console.log('搜索:', value)
// 实际项目中这里应该调用API进行搜索
}
// 处理新增
const handleAdd = () => {
console.log('新增无害化场所')
// 实际项目中这里应该打开新增表单
}
// 处理编辑
const handleEdit = (record) => {
console.log('编辑无害化场所:', record)
// 实际项目中这里应该打开编辑表单
}
// 处理删除
const handleDelete = (id) => {
console.log('删除无害化场所:', id)
// 实际项目中这里应该弹出确认框并调用API删除
}
// 处理查看详情
const handleDetail = (id) => {
console.log('查看无害化场所详情:', id)
// 实际项目中这里应该打开详情页面
}
return {
harmlessPlaces,
columns,
handleSearch,
handleAdd,
handleEdit,
handleDelete,
handleDetail
}
}
}
</script>
<style scoped>
.harmless-place-container {
padding: 24px;
background: #fff;
min-height: 100vh;
}
.page-header {
margin-bottom: 24px;
}
.toolbar {
margin-bottom: 16px;
}
.content-wrapper {
background: #fff;
padding: 24px;
border-radius: 2px;
}
</style>

View File

@@ -0,0 +1,236 @@
<template>
<div class="harmless-registration-container">
<div class="page-header">
<h1>无害化登记管理</h1>
</div>
<div class="content-wrapper">
<!-- 工具栏 -->
<div class="toolbar">
<a-button type="primary" @click="handleAdd">新增无害化登记</a-button>
<a-range-picker
style="width: 300px; margin-left: 16px;"
@change="handleDateChange"
/>
<a-input-search
placeholder="搜索登记编号"
style="width: 300px; margin-left: 16px;"
@search="handleSearch"
/>
</div>
<!-- 数据表格 -->
<a-table
:columns="columns"
:data-source="harmlessRegistrations"
row-key="id"
pagination
>
<template #column:action="{ record }">
<a-space>
<a-button type="link" @click="handleEdit(record)">编辑</a-button>
<a-button type="link" danger @click="handleDelete(record.id)">删除</a-button>
<a-button type="link" @click="handleDetail(record.id)">详情</a-button>
</a-space>
</template>
<template #column:status="{ text }">
<a-tag :color="getStatusColor(text)">
{{ text }}
</a-tag>
</template>
</a-table>
</div>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
// 模拟数据
const harmlessRegistrations = ref([
{
id: '1',
registrationNumber: 'HR20230501',
animalType: '牛',
quantity: 10,
reason: '病死',
processingMethod: '焚烧',
processingPlace: '北京无害化处理中心',
processingDate: '2023-05-10',
registrant: '刘九',
status: '已完成',
createTime: '2023-05-09'
},
{
id: '2',
registrationNumber: 'HR20230502',
animalType: '牛',
quantity: 5,
reason: '事故死亡',
processingMethod: '深埋',
processingPlace: '天津无害化处理站',
processingDate: '2023-05-15',
registrant: '周十',
status: '处理中',
createTime: '2023-05-14'
},
{
id: '3',
registrationNumber: 'HR20230503',
animalType: '牛',
quantity: 3,
reason: '检疫不合格',
processingMethod: '焚烧',
processingPlace: '河北无害化处理厂',
processingDate: '2023-05-20',
registrant: '吴十一',
status: '待处理',
createTime: '2023-05-18'
}
])
// 表格列配置
const columns = [
{
title: '登记编号',
dataIndex: 'registrationNumber',
key: 'registrationNumber'
},
{
title: '动物类型',
dataIndex: 'animalType',
key: 'animalType'
},
{
title: '数量',
dataIndex: 'quantity',
key: 'quantity'
},
{
title: '原因',
dataIndex: 'reason',
key: 'reason'
},
{
title: '处理方式',
dataIndex: 'processingMethod',
key: 'processingMethod'
},
{
title: '处理场所',
dataIndex: 'processingPlace',
key: 'processingPlace'
},
{
title: '处理日期',
dataIndex: 'processingDate',
key: 'processingDate'
},
{
title: '登记人',
dataIndex: 'registrant',
key: 'registrant'
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
scopedSlots: { customRender: 'status' }
},
{
title: '创建时间',
dataIndex: 'createTime',
key: 'createTime'
},
{
title: '操作',
key: 'action',
scopedSlots: { customRender: 'action' }
}
]
// 根据状态获取标签颜色
const getStatusColor = (status) => {
const colorMap = {
'待处理': 'orange',
'处理中': 'blue',
'已完成': 'green',
'已取消': 'red'
}
return colorMap[status] || 'default'
}
// 处理搜索
const handleSearch = (value) => {
console.log('搜索:', value)
// 实际项目中这里应该调用API进行搜索
}
// 处理日期范围变化
const handleDateChange = (dates, dateStrings) => {
console.log('日期范围:', dates, dateStrings)
// 实际项目中这里应该根据日期范围筛选数据
}
// 处理新增
const handleAdd = () => {
console.log('新增无害化登记')
// 实际项目中这里应该打开新增表单
}
// 处理编辑
const handleEdit = (record) => {
console.log('编辑无害化登记:', record)
// 实际项目中这里应该打开编辑表单
}
// 处理删除
const handleDelete = (id) => {
console.log('删除无害化登记:', id)
// 实际项目中这里应该弹出确认框并调用API删除
}
// 处理查看详情
const handleDetail = (id) => {
console.log('查看无害化登记详情:', id)
// 实际项目中这里应该打开详情页面
}
return {
harmlessRegistrations,
columns,
getStatusColor,
handleSearch,
handleDateChange,
handleAdd,
handleEdit,
handleDelete,
handleDetail
}
}
}
</script>
<style scoped>
.harmless-registration-container {
padding: 24px;
background: #fff;
min-height: 100vh;
}
.page-header {
margin-bottom: 24px;
}
.toolbar {
margin-bottom: 16px;
}
.content-wrapper {
background: #fff;
padding: 24px;
border-radius: 2px;
}
</style>