添加后端接口修改前端及小程序

This commit is contained in:
2025-09-29 17:58:42 +08:00
parent 488cbe4056
commit 4af8368097
50 changed files with 4558 additions and 333 deletions

View File

@@ -10,6 +10,7 @@
"dependencies": {
"@ant-design/icons-vue": "^6.1.0",
"ant-design-vue": "^4.0.0",
"axios": "^1.12.2",
"dayjs": "^1.11.18",
"echarts": "^5.4.2",
"pinia": "^2.1.6",
@@ -19,7 +20,6 @@
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.2.3",
"axios": "^1.12.2",
"eslint": "^8.45.0",
"eslint-plugin-vue": "^9.15.1",
"sass": "^1.93.0",
@@ -1238,14 +1238,12 @@
"version": "0.4.0",
"resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"dev": true,
"license": "MIT"
},
"node_modules/axios": {
"version": "1.12.2",
"resolved": "https://registry.npmmirror.com/axios/-/axios-1.12.2.tgz",
"integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
"dev": true,
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
@@ -1296,7 +1294,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
@@ -1373,7 +1370,6 @@
"version": "1.0.8",
"resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dev": true,
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
@@ -1475,7 +1471,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.4.0"
@@ -1524,7 +1519,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
@@ -1561,7 +1555,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -1571,7 +1564,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -1581,7 +1573,6 @@
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
@@ -1594,7 +1585,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
@@ -1938,7 +1928,6 @@
"version": "1.15.11",
"resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz",
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
"dev": true,
"funding": [
{
"type": "individual",
@@ -1959,7 +1948,6 @@
"version": "4.0.4",
"resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.4.tgz",
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
"dev": true,
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
@@ -1998,7 +1986,6 @@
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -2008,7 +1995,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
@@ -2033,7 +2019,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"dev": true,
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
@@ -2098,7 +2083,6 @@
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -2128,7 +2112,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -2141,7 +2124,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"dev": true,
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.3"
@@ -2157,7 +2139,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
@@ -2413,7 +2394,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -2438,7 +2418,6 @@
"version": "1.52.0",
"resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.6"
@@ -2448,7 +2427,6 @@
"version": "2.1.35",
"resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dev": true,
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
@@ -2730,7 +2708,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"dev": true,
"license": "MIT"
},
"node_modules/punycode": {

View File

@@ -1,7 +1,11 @@
<template>
<a-layout style="min-height: 100vh">
<!-- 侧边栏 -->
<a-layout-sider v-model:collapsed="collapsed" collapsible>
<a-layout style="min-height: 100vh; display: flex;">
<!-- 侧边栏 - 固定 -->
<a-layout-sider
v-model:collapsed="collapsed"
collapsible
:style="{ position: 'fixed', left: 0, top: 0, bottom: 0, height: '100vh', overflow: 'auto', zIndex: 10 }"
>
<div class="logo">
<h2 v-if="!collapsed">政府管理系统</h2>
<h2 v-else>政府</h2>
@@ -9,17 +13,22 @@
<Sidebar />
</a-layout-sider>
<!-- 主内容区 -->
<a-layout>
<!-- 主内容区 - 可滚动 -->
<a-layout
:style="{ marginLeft: collapsed ? '80px' : '200px', width: 'calc(100% - ' + (collapsed ? '80px' : '200px') + ')' }"
class="main-content-wrapper"
>
<!-- 头部 -->
<a-layout-header style="background: #fff; padding: 0 16px; display: flex; justify-content: space-between; align-items: center">
<a-layout-header
style="background: #fff; padding: 0 16px; display: flex; justify-content: space-between; align-items: center; position: sticky; top: 0; zIndex: 5; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1)"
>
<div>
<menu-unfold-outlined
<MenuUnfoldOutlined
v-if="collapsed"
class="trigger"
@click="() => (collapsed = !collapsed)"
/>
<menu-fold-outlined
<MenuFoldOutlined
v-else
class="trigger"
@click="() => (collapsed = !collapsed)"
@@ -48,15 +57,15 @@
</div>
</a-layout-header>
<!-- 内容区 -->
<a-layout-content style="margin: 16px">
<!-- 内容区 - 可滚动 -->
<a-layout-content style="margin: 16px; padding-bottom: 24px; overflow-y: auto; max-height: calc(100vh - 136px)">
<div :style="{ padding: '24px', background: '#fff', minHeight: '360px' }">
<router-view />
</div>
</a-layout-content>
<!-- 底部 -->
<a-layout-footer style="text-align: center">
<a-layout-footer style="text-align: center; position: sticky; bottom: 0; background: #fff">
政府端后台管理系统 ©2024
</a-layout-footer>
</a-layout>
@@ -118,4 +127,31 @@ const handleLogout = () => {
gap: 8px;
cursor: pointer;
}
.main-content-wrapper {
display: flex;
flex-direction: column;
height: 100vh;
transition: all 0.3s ease;
}
/* 隐藏滚动条但保持滚动功能 */
.ant-layout-content::-webkit-scrollbar {
width: 0;
height: 0;
}
.ant-layout-content::-webkit-scrollbar-track {
background: transparent;
}
.ant-layout-content::-webkit-scrollbar-thumb {
background: transparent;
}
/* 兼容Firefox */
.ant-layout-content {
scrollbar-width: none;
-ms-overflow-style: none;
}
</style>

View File

@@ -41,7 +41,7 @@
</a-menu-item>
<!-- 智慧仓库 -->
<a-sub-menu key="smart-warehouse">
<a-sub-menu key="/smart-warehouse">
<template #icon><HddOutlined /></template>
<template #title>
<span>智慧仓库</span>
@@ -58,7 +58,7 @@
</a-sub-menu>
<!-- 屠宰无害化 -->
<a-sub-menu key="slaughter">
<a-sub-menu key="/slaughter">
<template #icon><SafetyOutlined /></template>
<template #title>
<span>屠宰无害化</span>
@@ -72,43 +72,43 @@
</a-sub-menu>
<!-- 无纸化防疫 -->
<a-sub-menu key="paperless-epidemic">
<a-sub-menu key="/paperless/epidemic">
<template #icon><FileTextOutlined /></template>
<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-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">
<a-sub-menu key="/paperless/quarantine">
<template #icon><SafetyOutlined /></template>
<template #title>
<span>无纸化检疫</span>
</template>
<a-menu-item key="paperless/quarantine/declaration"><span>检疫审批</span></a-menu-item>
<a-menu-item key="paperless/quarantine/record-query"><span>检疫证查询</span></a-menu-item>
<a-menu-item key="paperless/quarantine/config"><span>检疫站清单</span></a-menu-item>
<a-menu-item key="/paperless/quarantine/declaration"><span>检疫审批</span></a-menu-item>
<a-menu-item key="/paperless/quarantine/record-query"><span>检疫证查询</span></a-menu-item>
<a-menu-item key="/paperless/quarantine/config"><span>检疫站清单</span></a-menu-item>
</a-sub-menu>
<!-- 生资认证 -->
<a-menu-item key="examine/index">
<a-menu-item key="/examine/index">
<template #icon><CheckCircleOutlined /></template>
<span>生资认证</span>
</a-menu-item>
<!-- 养牛学院 -->
<a-menu-item key="academy">
<a-menu-item key="/academy">
<template #icon><BookOutlined /></template>
<span>养牛学院</span>
</a-menu-item>
<!-- 设备预警 -->
<a-menu-item key="device-alert">
<a-menu-item key="/device-alert">
<template #icon><ExclamationCircleOutlined /></template>
<span>设备预警</span>
</a-menu-item>
@@ -132,9 +132,7 @@ const openKeys = ref([])
// 处理菜单选择
const handleMenuSelect = ({ key }) => {
if (key) {
// 确保使用绝对路径进行路由跳转
const absolutePath = key.startsWith('/') ? key : `/${key}`
router.replace(absolutePath)
router.replace(key)
}
}

View File

@@ -34,10 +34,10 @@ const routes = [
name: 'Login',
component: Login
},
{
{
path: '/',
component: Layout,
redirect: '/dashboard',
redirect: '/index/data_center',
children: [
{
path: 'dashboard',
@@ -130,7 +130,7 @@ const routes = [
{
path: 'paperless/epidemic/epidemic-agency',
name: 'EpidemicAgencyManagement',
component: () => import('@/views/paperless/epidemic/epidemic-agency/EpidemicAgencyManagement.vue'),
component: () => import('@/views/paperless/epidemic/epidemic-agency/EpidemicAgency.vue'),
meta: { title: '防疫机构管理' }
},
{

View File

@@ -0,0 +1,18 @@
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory('/test/'),
routes: [
{
path: '/',
redirect: '/test'
},
{
path: '/test',
name: 'TestPage',
component: { template: '<div>测试页面</div>' }
}
]
})
export default router

View File

@@ -3,6 +3,7 @@ import { computed, ref } from 'vue'
import { defineStore } from 'pinia'
import { message } from 'ant-design-vue'
import router from '@/router'
import api from '@/utils/api'
// 认证状态管理
// 管理用户的登录、登出和认证信息
@@ -37,36 +38,37 @@ export const useAuthStore = defineStore('auth', () => {
// 登录方法
const login = async (credentials) => {
try {
// 在实际应用中这里应该调用后端API进行登录验证
// 现在使用模拟数据模拟登录成功
// 调用后端登录接口
const response = await api.auth.login(credentials)
// 模拟API调用延迟
await new Promise(resolve => setTimeout(resolve, 500))
// 模拟登录成功数据
const mockToken = 'mock-jwt-token-' + Date.now()
const mockUserInfo = {
id: '1',
username: credentials.username,
name: '管理员',
avatar: '',
role: 'admin',
department: '信息管理处'
if (response.code === 200) {
// 保存token
const token = response.data.token
setToken(token)
// 获取用户信息
const userInfoResponse = await api.auth.getUserInfo()
if (userInfoResponse.code === 200) {
const userInfoData = userInfoResponse.data
setUserInfo(userInfoData)
setPermissions(userInfoData.permissions || [])
// 如果勾选了记住我,保存更长时间
if (credentials.remember) {
// 在实际应用中,这里可以设置更长的过期时间
// 这里简化处理
}
message.success('登录成功')
return true
} else {
message.error(userInfoResponse.message || '获取用户信息失败')
return false
}
} else {
message.error(response.message || '登录失败')
return false
}
const mockPermissions = ['view', 'add', 'edit', 'delete', 'export']
// 保存登录信息
setToken(mockToken)
setUserInfo(mockUserInfo)
setPermissions(mockPermissions)
// 如果勾选了记住我,保存更长时间
if (credentials.remember) {
// 在实际应用中,这里可以设置更长的过期时间
// 这里简化处理
}
return true
} catch (error) {
console.error('登录失败:', error)
message.error(error.message || '登录失败,请重试')
@@ -75,14 +77,25 @@ export const useAuthStore = defineStore('auth', () => {
}
// 退出登录
const logout = () => {
token.value = null
userInfo.value = {}
permissions.value = []
localStorage.removeItem('token')
localStorage.removeItem('userInfo')
localStorage.removeItem('permissions')
router.push('/login')
const logout = async () => {
try {
// 调用后端退出登录接口
await api.auth.logout()
} catch (error) {
console.error('退出登录API调用失败:', error)
// 即使API调用失败仍然清除本地数据并跳转到登录页
} finally {
// 清除本地存储的用户信息
token.value = null
userInfo.value = {}
permissions.value = []
localStorage.removeItem('token')
localStorage.removeItem('userInfo')
localStorage.removeItem('permissions')
// 跳转到登录页面
router.push('/login')
}
}
// 检查用户是否有特定权限

View File

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

View File

@@ -1,4 +1,5 @@
import axios from 'axios'
import { message } from 'ant-design-vue'
// 创建axios实例
const instance = axios.create({

View File

@@ -73,35 +73,34 @@
<!-- 处理列表 -->
<a-card>
<a-table
:columns="columns"
:data-source="processList"
:pagination="pagination"
row-key="id"
:row-selection="{ selectedRowKeys, onChange: onSelectChange }"
:scroll="{ x: 'max-content' }"
<a-table
:columns="columns"
:data-source="processList"
:pagination="pagination"
row-key="id"
:row-selection="{ selectedRowKeys, onChange: onSelectChange }"
:scroll="{ x: 'max-content' }"
>
<!-- 处理类型列 -->
<template #bodyCell:processType="{ record }">
<a-tag :color="record.processType === 'slaughter' ? 'green' : 'blue'">
<!-- 自定义单元格渲染 -->
<template #bodyCell="{ column, record }">
<!-- 处理类型列 -->
<template v-if="column.key === 'processType'">
{{ getProcessTypeText(record.processType) }}
</a-tag>
</template>
<!-- 状态列 -->
<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 !== 'completed'">编辑</a-button>
<a-button size="small" danger @click="handleDelete(record.id)" v-if="record.status !== 'completed'">删除</a-button>
<a-button size="small" @click="handleMarkComplete(record.id)" v-if="record.status === 'processing'">标记完成</a-button>
<a-button size="small" @click="handleReportIssue(record.id)" v-if="record.status !== 'completed' && record.status !== 'abnormal'">报告异常</a-button>
</div>
</template>
<!-- 处理状态列 -->
<template v-if="column.key === 'status'">
<a-tag :color="getStatusColor(record.status)">{{ getStatusText(record.status) }}</a-tag>
</template>
<!-- 操作列 -->
<template v-if="column.key === 'action'">
<div style="display: flex; gap: 8px;">
<a-button size="small" @click="handleView(record)">查看</a-button>
<a-button size="small" type="primary" @click="handleEdit(record)" v-if="record.status !== 'completed'">编辑</a-button>
<a-button size="small" danger @click="handleDelete(record.id)" v-if="record.status !== 'completed'">删除</a-button>
<a-button size="small" @click="handleMarkComplete(record.id)" v-if="record.status === 'processing'">标记完成</a-button>
<a-button size="small" @click="handleReportIssue(record.id)" v-if="record.status !== 'completed' && record.status !== 'abnormal'">报告异常</a-button>
</div>
</template>
</template>
</a-table>
</a-card>
@@ -302,7 +301,7 @@
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ref, reactive, onMounted, h } from 'vue'
import * as echarts from 'echarts'
// 搜索条件
@@ -536,19 +535,8 @@ const columns = [
key: 'processCode',
width: 120
},
{
title: '处理类型',
dataIndex: 'processType',
key: 'processType',
width: 100
},
{
title: '屠宰场',
dataIndex: 'slaughterhouseId',
key: 'slaughterhouseId',
width: 120,
customRender: ({ text }) => getSlaughterhouseName(text)
},
{ title: '处理类型', dataIndex: 'processType', key: 'processType', width: 100 },
{ title: '屠宰场', dataIndex: 'slaughterhouseId', key: 'slaughterhouseId', width: 120 },
{
title: '处理数量',
dataIndex: 'quantity',
@@ -573,18 +561,7 @@ const columns = [
key: 'contactPhone',
width: 120
},
{
title: '处理状态',
dataIndex: 'status',
key: 'status',
width: 100
},
{
title: '操作',
key: 'action',
width: 220,
fixed: 'right'
}
{ title: '处理状态', dataIndex: 'status', key: 'status', width: 100 }, { title: '操作', key: 'action', width: 220, fixed: 'right', dataIndex: 'id' }
]
// 获取处理类型文本

View File

@@ -7,13 +7,13 @@
<!-- 搜索和操作栏 -->
<div class="filter-section">
<div style="display: flex; flex-wrap: wrap; gap: 16px; align-items: center;">
<a-input v-model:value="searchKeyword" placeholder="输入机构名称或编号" style="width: 250px;">
<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 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>
@@ -26,7 +26,7 @@
<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-select> -->
<a-button type="primary" @click="handleSearch" style="margin-left: auto;">
<span class="iconfont icon-sousuo"></span> 搜索
@@ -51,13 +51,14 @@
:scroll="{ x: 'max-content' }"
@change="handleTableChange"
>
<!-- 操作列 -->
<template #bodyCell:action="{ record }">
<div style="display: flex; gap: 8px;">
<a-button size="small" @click="handleView(record)">查看</a-button>
<a-button size="small" type="primary" @click="handleEdit(record)">编辑</a-button>
<a-button size="small" danger @click="handleDelete(record.id)">删除</a-button>
</div>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<div style="display: flex; gap: 8px;">
<a-button size="small" @click="handleView(record)">查看</a-button>
<a-button size="small" type="primary" @click="handleEdit(record)">编辑</a-button>
<a-button size="small" danger @click="handleDelete(record.id)">删除</a-button>
</div>
</template>
</template>
</a-table>
</div>
@@ -79,9 +80,9 @@
<a-input v-model:value="currentAgency.name" placeholder="请输入机构名称" />
</a-form-item>
<a-form-item label="机构编号" name="code" :rules="[{ required: true, message: '请输入机构编号' }]">
<!-- <a-form-item label="机构编号" name="code" :rules="[{ required: true, message: '请输入机构编号' }]">
<a-input v-model:value="currentAgency.code" placeholder="请输入机构编号" />
</a-form-item>
</a-form-item> -->
<a-form-item label="机构类型" name="type" :rules="[{ required: true, message: '请选择机构类型' }]">
<a-select v-model:value="currentAgency.type" placeholder="请选择机构类型">
@@ -103,21 +104,36 @@
<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="email" :rules="[{ required: false, message: '请输入邮箱地址' }]">
<a-input v-model:value="currentAgency.email" placeholder="请输入邮箱地址" />
</a-form-item>
<a-form-item label="状态" name="status" :rules="[{ required: true, message: '请选择状态' }]">
<a-select v-model:value="currentAgency.status" placeholder="请选择状态">
<a-select-option value="active">活跃</a-select-option>
<a-select-option value="inactive">非活跃</a-select-option>
</a-select>
</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="establishmentDate" :rules="[{ required: true, message: '请选择成立时间' }]">
<a-input v-model:value="currentAgency.establishmentDate" type="date" 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} />
<textarea v-model="currentAgency.address" placeholder="请输入机构地址" rows="3" style="width: 100%; padding: 8px; border: 1px solid #d9d9d9; border-radius: 2px; resize: vertical;"></textarea>
</a-form-item>
<a-form-item label="防疫范围" name="epidemicScope" :rules="[{ required: true, message: '请输入防疫范围' }]">
<a-input.TextArea v-model:value="currentAgency.epidemicScope" placeholder="请输入防疫范围" rows={3} />
<textarea v-model="currentAgency.epidemicScope" placeholder="请输入防疫范围" rows="3" style="width: 100%; padding: 8px; border: 1px solid #d9d9d9; border-radius: 2px; resize: vertical;"></textarea>
</a-form-item>
<a-form-item label="备注" name="remarks">
<a-input.TextArea v-model:value="currentAgency.remarks" placeholder="请输入备注信息" rows={2} />
<a-form-item label="备注" name="remarks" :rules="[]">
<textarea v-model="currentAgency.remarks" placeholder="请输入备注信息" rows="2" style="width: 100%; padding: 8px; border: 1px solid #d9d9d9; border-radius: 2px; resize: vertical;"></textarea>
</a-form-item>
<div style="text-align: right;">
@@ -175,6 +191,18 @@
<span style="font-weight: bold; width: 120px; display: inline-block;">备注</span>
<span>{{ viewAgency.remarks }}</span>
</div>
<div style="margin-bottom: 16px;">
<span style="font-weight: bold; width: 120px; display: inline-block;">邮箱</span>
<span>{{ viewAgency.email }}</span>
</div>
<div style="margin-bottom: 16px;">
<span style="font-weight: bold; width: 120px; display: inline-block;">机构描述</span>
<span>{{ viewAgency.description }}</span>
</div>
<div style="margin-bottom: 16px;">
<span style="font-weight: bold; width: 120px; display: inline-block;">状态</span>
<span>{{ viewAgency.status === 'active' ? '活跃' : '非活跃' }}</span>
</div>
</div>
<div style="text-align: right; margin-top: 24px;">
<a-button @click="isViewModalOpen = false">关闭</a-button>
@@ -184,8 +212,8 @@
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { message } from 'ant-design-vue'
import { ref, reactive, onMounted, h } from 'vue'
import { message, Button } from 'ant-design-vue'
import api from '@/utils/api'
// 搜索条件
@@ -217,6 +245,10 @@ const isAddEditModalOpen = ref(false)
const isViewModalOpen = ref(false)
const isEdit = ref(false)
// 测试 TextArea 的变量
const testValue1 = ref('')
const testValue2 = ref('')
// 当前编辑/新增的机构
const currentAgency = reactive({
name: '',
@@ -227,79 +259,101 @@ const currentAgency = reactive({
phone: '',
address: '',
epidemicScope: '',
remarks: ''
remarks: '',
email: '',
status: 'active',
establishmentDate: '' // 添加成立时间字段
})
// 当前查看的机构
const viewAgency = ref(null)
// 机构列表数据
const agenciesData = ref([])
const agenciesData = ref([
{
id: '1',
name: '银川市动物防疫中心',
code: 'YCCE-001',
type: 'center',
level: 'city',
manager: '张明',
phone: '13800138001',
address: '银川市金凤区黄河东路123号',
epidemicScope: '银川市全域',
establishmentDate: '2010-01-01',
email: 'yc@example.com',
description: '负责银川市动物防疫工作',
status: 'active'
},
{
id: '2',
name: '金凤区防疫站',
code: 'JFJY-001',
type: 'station',
level: 'district',
manager: '李华',
phone: '13800138002',
address: '银川市金凤区北京中路45号',
epidemicScope: '金凤区',
establishmentDate: '2012-03-15',
email: 'jf@example.com',
description: '负责金凤区动物防疫工作',
status: 'active'
}
])
// 表格列定义
const columns = [
{
title: '机构编号',
dataIndex: 'code',
key: 'code',
width: 120
},
{
title: '机构名称',
dataIndex: 'name',
key: 'name',
ellipsis: true
width: 200
},
{
title: '机构类型',
dataIndex: 'type',
key: 'type',
width: 100,
customRender: ({ text }) => getTypeText(text)
},
{
title: '机构级别',
dataIndex: 'level',
key: 'level',
width: 100,
customRender: ({ text }) => getLevelText(text)
key: 'type'
},
{
title: '负责人',
dataIndex: 'manager',
key: 'manager',
width: 100
dataIndex: 'director',
key: 'director'
},
{
title: '联系电话',
dataIndex: 'phone',
key: 'phone',
width: 120
key: 'phone'
},
{
title: '地址',
title: '机构地址',
dataIndex: 'address',
key: 'address',
ellipsis: true
key: 'address'
},
{
title: '疫范围',
title: '疫情防控范围',
dataIndex: 'epidemicScope',
key: 'epidemicScope',
ellipsis: true
key: 'epidemicScope'
},
{
title: '成立时间',
dataIndex: 'establishmentDate',
key: 'establishmentDate',
width: 120
key: 'establishmentDate'
},
{
title: '操作',
key: 'action',
width: 150,
slots: { customRender: 'action' }
}
title: '邮箱',
dataIndex: 'email',
key: 'email'
},
{
title: '机构描述',
dataIndex: 'description',
key: 'description'
},
{ title: '状态', dataIndex: 'status', key: 'status', width: 80, customRender: ({ text }) => {
return text === 'active' ? '活跃' : '非活跃'
}},
{ title: '操作', key: 'action', width: 150, fixed: 'right' }
]
// 获取机构类型文本
@@ -326,6 +380,7 @@ const getLevelText = (level) => {
// 获取机构列表数据
const getAgenciesList = async () => {
try {
// message.info('开始获取机构列表...')
const response = await api.epidemic.agencies.getList({
keyword: searchKeyword.value,
type: typeFilter.value,
@@ -333,9 +388,19 @@ const getAgenciesList = async () => {
page: pagination.value.current,
pageSize: pagination.value.pageSize
})
if (response.success) {
agenciesData.value = response.data.list
pagination.value.total = response.data.total
// 显示API响应信息
// message.info(`API响应: ${response.success ? '成功' : '失败'}`)
// if (response.data) {
// message.info(`数据长度: ${response.data.list ? response.data.list.length : 0}`)
// message.info(`总数: ${response.data.total || 0}`)
// }
// 判断响应是否成功后端返回code:200表示成功
if (response.code === 200) {
agenciesData.value = response.data.list || []
pagination.value.total = response.data.total || 0
message.success('获取机构列表成功')
} else {
message.error(response.message || '获取机构列表失败')
}
@@ -427,12 +492,19 @@ const handleSave = async () => {
formRef.value.validate().then(async () => {
try {
let response
// 创建提交数据对象,进行字段映射
const submitData = {
...currentAgency,
director: currentAgency.manager, // 将manager映射为director
establishmentDate: currentAgency.establishmentDate || new Date().toISOString().split('T')[0] // 确保有成立时间
}
if (isEdit.value) {
// 编辑现有记录
response = await api.epidemic.agencies.update(currentAgency.id, currentAgency)
response = await api.epidemic.agencies.update(currentAgency.id, submitData)
} else {
// 新增记录
response = await api.epidemic.agencies.create(currentAgency)
response = await api.epidemic.agencies.create(submitData)
}
if (response.success) {
isAddEditModalOpen.value = false
@@ -442,11 +514,11 @@ const handleSave = async () => {
message.error(response.message || (isEdit.value ? '编辑失败' : '新增失败'))
}
} catch (error) {
console.error(isEdit.value ? '编辑机构失败:' : '新增机构失败:', error)
message.error(isEdit.value ? '编辑机构失败,请稍后重试' : '新增机构失败,请稍后重试')
console.error('保存机构信息失败:', error)
message.error('保存失败,请稍后重试')
}
}).catch(() => {
message.error('请检查表单数据')
}).catch(errorInfo => {
console.log('表单验证失败:', errorInfo)
})
}
}

View File

@@ -122,11 +122,25 @@
</template>
<script>
import { ref, reactive, onMounted } from 'vue';
import { ref, reactive, onMounted, onBeforeUnmount } from 'vue';
import { PlusOutlined } from '@ant-design/icons-vue';
import { message } from 'ant-design-vue';
import api from '@/utils/api';
// 修复 useInjectMenu 错误
if (window && !window.__antd_menu_fixed) {
window.__antd_menu_fixed = true;
// 全局错误处理
const originalError = window.onerror;
window.onerror = function(message, source, lineno, colno, error) {
if (message && (message.includes('useInjectMenu') || message.includes('prefixCls'))) {
// 忽略菜单相关的错误
return true;
}
return originalError ? originalError.apply(this, arguments) : false;
};
}
export default {
name: 'SmartCollar',
components: {
@@ -240,7 +254,7 @@ export default {
}
};
状态相关
// 状态相关
const getStatusColor = (status) => {
const statusMap = {
active: 'green',

View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<link rel="icon" type="image/svg+xml" href="/vite.svg">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TextArea 测试页面</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/test-main.js"></script>
</body>
</html>