完善政府端

This commit is contained in:
2025-10-09 18:01:06 +08:00
parent f88383425f
commit 1a1abf4c26
39 changed files with 4309 additions and 673 deletions

View File

@@ -85,7 +85,7 @@
</a-sub-menu>
<!-- 无纸化检疫 -->
<a-sub-menu key="/paperless/quarantine">
<!-- <a-sub-menu key="/paperless/quarantine">
<template #icon><SafetyOutlined /></template>
<template #title>
<span>无纸化检疫</span>
@@ -93,7 +93,7 @@
<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-sub-menu> -->
<!-- 生资认证 -->
<a-menu-item key="/examine/index">
@@ -108,7 +108,7 @@
</a-menu-item>
<!-- 设备预警 -->
<a-menu-item key="/device-alert">
<a-menu-item key="/device-warning">
<template #icon><ExclamationCircleOutlined /></template>
<span>设备预警</span>
</a-menu-item>

View File

@@ -0,0 +1,62 @@
import axios from 'axios'
const API_BASE_URL = 'http://localhost:5352/api/approval-process'
// 创建axios实例
const api = axios.create({
baseURL: API_BASE_URL,
timeout: 10000,
headers: {
'Content-Type': 'application/json'
}
})
// 请求拦截器
api.interceptors.request.use(
config => {
// 可以在这里添加token等认证信息
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
},
error => {
return Promise.reject(error)
}
)
// 响应拦截器
api.interceptors.response.use(
response => {
return response.data
},
error => {
console.error('API请求错误:', error)
return Promise.reject(error)
}
)
// 审批流程管理API
export default {
// 获取审批流程列表
getApprovalProcesses: (params) => api.get('/', { params }),
// 获取审批流程详情
getApprovalProcessById: (id) => api.get(`/${id}`),
// 创建审批流程
createApprovalProcess: (data) => api.post('/', data),
// 更新审批流程
updateApprovalProcess: (id, data) => api.put(`/${id}`, data),
// 删除审批流程
deleteApprovalProcess: (id) => api.delete(`/${id}`),
// 审批操作(通过/拒绝)
processApproval: (id, data) => api.post(`/${id}/process`, data),
// 更新审批状态
updateApprovalStatus: (id, status) => api.patch(`/${id}/status`, { status })
}

View File

@@ -0,0 +1,62 @@
import axios from 'axios'
const API_BASE_URL = 'http://localhost:5352/api/cattle-academy'
// 创建axios实例
const api = axios.create({
baseURL: API_BASE_URL,
timeout: 10000,
headers: {
'Content-Type': 'application/json'
}
})
// 请求拦截器
api.interceptors.request.use(
config => {
// 可以在这里添加token等认证信息
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
},
error => {
return Promise.reject(error)
}
)
// 响应拦截器
api.interceptors.response.use(
response => {
return response.data
},
error => {
console.error('API请求错误:', error)
return Promise.reject(error)
}
)
// 养牛学院资讯管理API
export default {
// 获取养牛学院资讯列表
getCattleAcademyList: (params) => api.get('/', { params }),
// 获取养牛学院资讯详情
getCattleAcademyById: (id) => api.get(`/${id}`),
// 创建养牛学院资讯
createCattleAcademy: (data) => api.post('/', data),
// 更新养牛学院资讯
updateCattleAcademy: (id, data) => api.put(`/${id}`, data),
// 删除养牛学院资讯
deleteCattleAcademy: (id) => api.delete(`/${id}`),
// 切换资讯状态
toggleCattleAcademyStatus: (id, status) => api.patch(`/${id}/status`, { status }),
// 批量更新排序
updateSort: (items) => api.patch('/sort', { items })
}

View File

@@ -0,0 +1,44 @@
import axios from 'axios';
const API_BASE_URL = 'http://localhost:5352/api/device-warning';
const api = axios.create({
baseURL: API_BASE_URL,
timeout: 10000,
headers: {
'Content-Type': 'application/json'
}
});
api.interceptors.request.use(
config => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
error => {
return Promise.reject(error);
}
);
api.interceptors.response.use(
response => {
return response.data;
},
error => {
console.error('API Error:', error.response ? error.response.data : error.message);
return Promise.reject(error);
}
);
export default {
getDeviceWarnings: (params) => api.get('/', { params }),
getDeviceWarningById: (id) => api.get(`/${id}`),
createDeviceWarning: (data) => api.post('/', data),
updateDeviceWarning: (id, data) => api.put(`/${id}`, data),
deleteDeviceWarning: (id) => api.delete(`/${id}`),
updateWarningStatus: (id, data) => api.patch(`/${id}/status`, data),
getWarningStats: () => api.get('/stats'),
};

View File

@@ -0,0 +1,73 @@
import axios from 'axios'
const API_BASE_URL = 'http://localhost:5352/api/epidemic-activity'
// 创建axios实例
const api = axios.create({
baseURL: API_BASE_URL,
timeout: 10000,
headers: {
'Content-Type': 'application/json'
}
})
// 请求拦截器
api.interceptors.request.use(
config => {
// 可以在这里添加token等认证信息
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
},
error => {
return Promise.reject(error)
}
)
// 响应拦截器
api.interceptors.response.use(
response => {
return response.data
},
error => {
console.error('API请求错误:', error)
return Promise.reject(error)
}
)
// 防疫活动管理API
export const epidemicActivityApi = {
// 获取防疫活动列表
getActivities(params = {}) {
return api.get('/', { params })
},
// 根据ID获取防疫活动详情
getActivityById(id) {
return api.get(`/${id}`)
},
// 创建防疫活动
createActivity(data) {
return api.post('/', data)
},
// 更新防疫活动
updateActivity(id, data) {
return api.put(`/${id}`, data)
},
// 删除防疫活动
deleteActivity(id) {
return api.delete(`/${id}`)
},
// 切换活动状态
toggleActivityStatus(id) {
return api.patch(`/${id}/status`)
}
}
export default epidemicActivityApi

View File

@@ -0,0 +1,59 @@
import axios from 'axios'
const API_BASE_URL = 'http://localhost:5352/api/production-material-certification'
// 创建axios实例
const api = axios.create({
baseURL: API_BASE_URL,
timeout: 10000,
headers: {
'Content-Type': 'application/json'
}
})
// 请求拦截器
api.interceptors.request.use(
config => {
// 可以在这里添加token等认证信息
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
},
error => {
return Promise.reject(error)
}
)
// 响应拦截器
api.interceptors.response.use(
response => {
return response.data
},
error => {
console.error('API请求错误:', error)
return Promise.reject(error)
}
)
// 生资认证管理API
export default {
// 获取生资认证列表
getCertifications: (params) => api.get('/', { params }),
// 获取生资认证详情
getCertificationById: (id) => api.get(`/${id}`),
// 创建生资认证
createCertification: (data) => api.post('/', data),
// 更新生资认证
updateCertification: (id, data) => api.put(`/${id}`, data),
// 删除生资认证
deleteCertification: (id) => api.delete(`/${id}`),
// 切换认证状态
toggleCertificationStatus: (id, status) => api.patch(`/${id}/status`, { status })
}

View File

@@ -22,7 +22,7 @@
</div>
<!-- 审批卡片列表 -->
<div class="card-list">
<div class="card-list" v-loading="loading">
<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)">
@@ -32,15 +32,21 @@
<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>
<p><strong>审批标题:</strong> {{ item.title }}</p>
<p><strong>申请人:</strong> {{ item.applicant }}</p>
<p><strong>审批类型:</strong> {{ getTypeText(item.type) }}</p>
<p><strong>申请时间:</strong> {{ item.create_time }}</p>
<p><strong>联系电话:</strong> {{ item.phone || '未填写' }}</p>
<p><strong>养殖场名称:</strong> {{ item.farmName }}</p>
<p v-if="item.quantity"><strong>认证数量:</strong> {{ item.quantity }}</p>
</a-card>
</a-col>
</a-row>
<!-- 空状态 -->
<div v-if="!loading && approvalList.length === 0" class="empty-state">
<a-empty description="暂无审批流程数据" />
</div>
</div>
<!-- 分页 -->
@@ -137,8 +143,8 @@
<script setup>
import { ref, onMounted, computed } from 'vue'
import { message } from 'antd'
import axios from 'axios'
import { message } from 'ant-design-vue'
import approvalProcessApi from '@/utils/approvalProcessApi'
import { UploadOutlined } from '@ant-design/icons-vue'
const allData = ref([])
@@ -148,6 +154,7 @@ const currentPage = ref(1)
const pageSize = ref(9)
const totalItems = ref(0)
const searchInput = ref('')
const loading = ref(false)
const createModalVisible = ref(false)
const processModalVisible = ref(false)
@@ -206,38 +213,58 @@ const getTypeText = (type) => {
// 获取审批流程列表
const fetchApprovalList = async () => {
try {
// 这里应该从API获取数据
// 由于没有实际API使用模拟数据
approvalList.value = [
{ id: 1, title: '食品经营许可证申请', type: 'license', applicant: '张三', create_time: '2024-01-10 09:00:00', status: 'pending', description: '申请食品经营许可证' },
{ id: 2, title: '企业资质年审', type: 'enterprise', applicant: '李四', create_time: '2024-01-09 14:30:00', status: 'approved', description: '企业资质年度审核' },
{ id: 3, title: '新药品研发项目', type: 'project', applicant: '王五', create_time: '2024-01-08 11:20:00', status: 'processing', description: '新型药品研发项目审批' },
{ id: 4, title: '环保设施改造', type: 'project', applicant: '赵六', create_time: '2024-01-07 16:40:00', status: 'rejected', description: '工厂环保设施改造审批' },
{ id: 5, title: '特殊行业许可证', type: 'license', applicant: '钱七', create_time: '2024-01-06 10:15:00', status: 'pending', description: '申请特殊行业经营许可证' },
{ id: 6, title: '企业扩大经营规模', type: 'enterprise', applicant: '孙八', create_time: '2024-01-05 13:30:00', status: 'approved', description: '企业扩大生产经营规模审批' },
{ id: 7, title: '新产品上市审批', type: 'other', applicant: '周九', create_time: '2024-01-04 09:45:00', status: 'pending', description: '新产品上市销售审批' },
{ id: 8, title: '消防设施验收', type: 'project', applicant: '吴十', create_time: '2024-01-03 15:20:00', status: 'processing', description: '新建建筑消防设施验收' },
{ id: 9, title: '卫生许可证换证', type: 'license', applicant: '郑一', create_time: '2024-01-02 11:00:00', status: 'approved', description: '卫生许可证到期换证' },
{ id: 10, title: '临时占道经营', type: 'other', applicant: '王二', create_time: '2024-01-01 09:30:00', status: 'rejected', description: '临时占道经营活动审批' }
]
loading.value = true
const params = {
page: currentPage.value,
pageSize: pageSize.value,
status: activeTab.value === 'pending' ? 'pending' : activeTab.value === 'approved' ? 'approved' : undefined,
applicant: searchInput.value
}
const response = await approvalProcessApi.getApprovalProcesses(params)
if (response.code === 200) {
approvalList.value = response.data.list.map(item => ({
...item,
create_time: item.createdAt,
farmName: item.farmName || '未填写'
}))
totalItems.value = response.data.total
} else {
message.error(response.message || '获取数据失败')
}
} catch (error) {
console.error('获取审批流程列表失败:', error)
message.error('获取审批流程列表失败,请稍后重试')
} finally {
loading.value = false
}
}
// 搜索审批
const searchApproval = (value) => {
message.info(`搜索关键词: ${value}`)
// 这里应该根据搜索关键词过滤数据
// 目前使用模拟数据实际项目中需要调用API
const onSearch = (value) => {
searchInput.value = value
currentPage.value = 1
fetchApprovalList()
}
// 查看审批详情
const viewApprovalDetail = (id) => {
message.info(`查看审批ID: ${id} 的详情`)
// 这里可以跳转到详情页面
// router.push(`/approval/detail/${id}`)
const viewApprovalDetail = async (id) => {
try {
const response = await approvalProcessApi.getApprovalProcessById(id)
if (response.code === 200) {
currentApproval.value = {
...response.data,
create_time: response.data.createdAt,
farmName: response.data.farmName || '未填写'
}
processModalVisible.value = true
} else {
message.error(response.message || '获取详情失败')
}
} catch (error) {
console.error('获取审批详情失败:', error)
message.error('获取详情失败')
}
}
// 编辑审批
@@ -264,23 +291,31 @@ const processApproval = (id) => {
}
// 通过审批
const approveApproval = () => {
const approveApproval = async () => {
if (!currentApproval.value) return
message.success(`已通过审批: ${currentApproval.value.title}`)
// 这里应该调用审批通过API
// 更新本地数据
const index = approvalList.value.findIndex(item => item.id === currentApproval.value.id)
if (index !== -1) {
approvalList.value[index].status = 'approved'
try {
const response = await approvalProcessApi.processApproval(currentApproval.value.id, {
action: 'approve',
approvalComment: approvalComment.value,
approver: '当前用户' // 这里应该从用户信息中获取
})
if (response.code === 200) {
message.success(`已通过审批: ${currentApproval.value.title}`)
closeProcessModal()
fetchApprovalList() // 重新加载数据
} else {
message.error(response.message || '审批失败')
}
} catch (error) {
console.error('审批操作失败:', error)
message.error('审批失败')
}
closeProcessModal()
}
// 拒绝审批
const rejectApproval = () => {
const rejectApproval = async () => {
if (!currentApproval.value) return
if (!approvalComment.value.trim()) {
@@ -288,16 +323,24 @@ const rejectApproval = () => {
return
}
message.success(`已拒绝审批: ${currentApproval.value.title}`)
// 这里应该调用审批拒绝API
// 更新本地数据
const index = approvalList.value.findIndex(item => item.id === currentApproval.value.id)
if (index !== -1) {
approvalList.value[index].status = 'rejected'
try {
const response = await approvalProcessApi.processApproval(currentApproval.value.id, {
action: 'reject',
approvalComment: approvalComment.value,
approver: '当前用户' // 这里应该从用户信息中获取
})
if (response.code === 200) {
message.success(`已拒绝审批: ${currentApproval.value.title}`)
closeProcessModal()
fetchApprovalList() // 重新加载数据
} else {
message.error(response.message || '审批失败')
}
} catch (error) {
console.error('审批操作失败:', error)
message.error('审批失败')
}
closeProcessModal()
}
// 显示新建弹窗
@@ -333,15 +376,25 @@ const submitCreateForm = async () => {
try {
await createFormRef.value.validate()
// 这里应该调用创建审批API
message.success('新建审批流程成功')
closeCreateModal()
const response = await approvalProcessApi.createApprovalProcess({
title: createFormData.value.title,
type: createFormData.value.type,
applicant: createFormData.value.applicant,
description: createFormData.value.description,
files: createFormData.value.files
})
// 重新加载数据
fetchApprovalList()
if (response.code === 201) {
message.success('新建审批流程成功')
closeCreateModal()
fetchApprovalList() // 重新加载数据
} else {
message.error(response.message || '创建失败')
}
} catch (error) {
console.error('表单验证失败:', error)
message.error('创建失败')
}
}
@@ -362,6 +415,19 @@ const exportApprovalList = () => {
// 这里应该调用导出API
}
// 标签页切换
const handleTabChange = (key) => {
activeTab.value = key
currentPage.value = 1
fetchApprovalList()
}
// 分页变化
const handlePageChange = (page) => {
currentPage.value = page
fetchApprovalList()
}
// 组件挂载
onMounted(() => {
fetchApprovalList()
@@ -491,4 +557,56 @@ onMounted(() => {
padding-bottom: 8px;
border-bottom: 1px solid #f0f0f0;
}
/* 卡片列表样式 */
.card-list {
margin-bottom: 24px;
}
.approval-card {
cursor: pointer;
transition: all 0.3s ease;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.approval-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}
.card-title {
display: flex;
justify-content: space-between;
align-items: center;
}
.card-title span {
font-weight: 500;
color: #666;
}
.approval-card p {
margin-bottom: 8px;
line-height: 1.5;
color: #333;
}
.approval-card strong {
color: #262626;
font-weight: 500;
}
/* 空状态样式 */
.empty-state {
text-align: center;
padding: 60px 0;
}
/* 分页容器样式 */
.pagination-container {
display: flex;
justify-content: center;
margin-top: 24px;
}
</style>

View File

@@ -2,108 +2,444 @@
<div class="cattle-academy-container">
<div class="header">
<a-button type="primary" @click="handleNewInfo">新增资讯</a-button>
<a-input-search
placeholder="搜索标题或作者"
style="width: 300px; margin-left: 16px;"
@search="handleSearch"
v-model:value="searchKeyword"
/>
</div>
<a-table
:columns="columns"
:data-source="dataSource"
:pagination="pagination"
:loading="loading"
row-key="id"
@change="handleTableChange"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'coverImage'">
<img :src="record.coverImage" alt="封面图" class="cover-image" />
</template>
<template v-if="column.key === 'status'">
<a-switch :checked="record.status" />
<a-switch
:checked="record.status"
@change="(checked) => handleToggleStatus(record, checked)"
/>
</template>
<template v-if="column.key === 'operations'">
<a-space>
<a @click="handleView(record)">查看</a>
<a @click="handleEdit(record)">编辑</a>
<a @click="handleDelete(record)">删除</a>
<a @click="handleDelete(record)" style="color: #ff4d4f;">删除</a>
</a-space>
</template>
</template>
</a-table>
<!-- 新增/编辑表单 -->
<a-modal
v-model:open="isModalOpen"
:title="currentInfo.id ? '编辑资讯' : '新增资讯'"
@ok="handleSave"
@cancel="handleCancel"
:width="800"
:confirm-loading="modalLoading"
>
<a-form
:model="currentInfo"
layout="vertical"
:rules="formRules"
ref="formRef"
>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="标题" name="title">
<a-input v-model:value="currentInfo.title" placeholder="请输入标题" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="分类" name="category">
<a-select v-model:value="currentInfo.category" placeholder="请选择分类">
<a-select-option value="技术资讯">技术资讯</a-select-option>
<a-select-option value="养殖技术">养殖技术</a-select-option>
<a-select-option value="健康管理">健康管理</a-select-option>
<a-select-option value="营养管理">营养管理</a-select-option>
<a-select-option value="智能设备">智能设备</a-select-option>
<a-select-option value="环保管理">环保管理</a-select-option>
<a-select-option value="繁殖技术">繁殖技术</a-select-option>
<a-select-option value="经营管理">经营管理</a-select-option>
<a-select-option value="市场分析">市场分析</a-select-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="作者" name="author">
<a-input v-model:value="currentInfo.author" placeholder="请输入作者" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="排序" name="sort">
<a-input-number v-model:value="currentInfo.sort" placeholder="请输入排序" style="width: 100%" />
</a-form-item>
</a-col>
</a-row>
<a-form-item label="封面图URL" name="coverImage">
<a-input v-model:value="currentInfo.coverImage" placeholder="请输入封面图URL" />
</a-form-item>
<a-form-item label="摘要" name="summary">
<a-textarea v-model:value="currentInfo.summary" placeholder="请输入摘要" :rows="3" />
</a-form-item>
<a-form-item label="内容" name="content">
<a-textarea v-model:value="currentInfo.content" placeholder="请输入内容" :rows="6" />
</a-form-item>
<a-row :gutter="16">
<a-col :span="8">
<a-form-item label="是否置顶">
<a-switch v-model:checked="currentInfo.isTop" />
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="是否推荐">
<a-switch v-model:checked="currentInfo.isRecommend" />
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="状态">
<a-switch v-model:checked="currentInfo.status" />
</a-form-item>
</a-col>
</a-row>
<a-form-item label="备注" name="remarks">
<a-textarea v-model:value="currentInfo.remarks" placeholder="请输入备注" :rows="2" />
</a-form-item>
</a-form>
</a-modal>
<!-- 详情查看 -->
<a-modal
v-model:open="isDetailModalOpen"
title="资讯详情"
:footer="null"
:width="800"
>
<a-descriptions :column="2" bordered>
<a-descriptions-item label="标题">{{ detailData.title }}</a-descriptions-item>
<a-descriptions-item label="分类">{{ detailData.category }}</a-descriptions-item>
<a-descriptions-item label="作者">{{ detailData.author }}</a-descriptions-item>
<a-descriptions-item label="排序">{{ detailData.sort }}</a-descriptions-item>
<a-descriptions-item label="浏览次数">{{ detailData.viewCount }}</a-descriptions-item>
<a-descriptions-item label="发布时间">{{ detailData.publishTime }}</a-descriptions-item>
<a-descriptions-item label="是否置顶">
<a-tag :color="detailData.isTop ? 'red' : 'default'">
{{ detailData.isTop ? '是' : '否' }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="是否推荐">
<a-tag :color="detailData.isRecommend ? 'green' : 'default'">
{{ detailData.isRecommend ? '是' : '否' }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="状态">
<a-tag :color="detailData.status ? 'green' : 'red'">
{{ detailData.status ? '启用' : '禁用' }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="创建时间">{{ detailData.createdAt }}</a-descriptions-item>
<a-descriptions-item label="封面图" :span="2">
<img v-if="detailData.coverImage" :src="detailData.coverImage" alt="封面图" class="cover-image" />
</a-descriptions-item>
<a-descriptions-item label="摘要" :span="2">{{ detailData.summary }}</a-descriptions-item>
<a-descriptions-item label="内容" :span="2">{{ detailData.content }}</a-descriptions-item>
<a-descriptions-item label="备注" :span="2">{{ detailData.remarks }}</a-descriptions-item>
</a-descriptions>
</a-modal>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { ref, reactive, onMounted } from 'vue';
import { message } from 'ant-design-vue';
import cattleAcademyApi from '@/utils/cattleAcademyApi';
const columns = [
{
title: '标题',
dataIndex: 'title',
key: 'title',
width: 200,
ellipsis: true
},
{
title: '封面图',
dataIndex: 'coverImage',
key: 'coverImage',
width: 100
},
{
title: '分类',
dataIndex: 'category',
key: 'category',
width: 100
},
{
title: '作者',
dataIndex: 'author',
key: 'author',
width: 100
},
{
title: '排序',
dataIndex: 'sort',
key: 'sort',
width: 80
},
{
title: '浏览次数',
dataIndex: 'viewCount',
key: 'viewCount',
width: 100
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
width: 80
},
{
title: '创建时间',
dataIndex: 'creationTime',
key: 'creationTime',
dataIndex: 'createdAt',
key: 'createdAt',
width: 150
},
{
title: '操作',
key: 'operations',
width: 150,
fixed: 'right'
},
];
const dataSource = ref([
{
id: 1,
title: '新疆阿克苏地区电信5G数字乡村项目',
coverImage: 'https://via.placeholder.com/100x50',
sort: 100,
status: true,
creationTime: '2022-09-28 11:47:27',
},
{
id: 2,
title: '共享养牛,开启畜牧业财富之路',
coverImage: 'https://via.placeholder.com/100x50',
sort: 98,
status: true,
creationTime: '2023-08-28 15:38:35',
},
// ... more data
]);
const dataSource = ref([]);
const loading = ref(false);
const searchKeyword = ref('');
const pagination = ref({
total: dataSource.value.length,
const pagination = reactive({
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total, range) => `${range[0]}-${range[1]} 条,共 ${total}`
});
// 模态框状态
const isModalOpen = ref(false);
const isDetailModalOpen = ref(false);
const modalLoading = ref(false);
// 表单引用
const formRef = ref(null);
// 当前编辑的资讯
const currentInfo = reactive({
id: '',
title: '',
coverImage: '',
content: '',
summary: '',
category: '',
sort: 0,
status: true,
author: '',
publishTime: null,
isTop: false,
isRecommend: false,
remarks: ''
});
// 详情数据
const detailData = reactive({});
// 表单验证规则
const formRules = {
title: [
{ required: true, message: '请输入标题', trigger: 'blur' }
],
category: [
{ required: true, message: '请选择分类', trigger: 'change' }
],
author: [
{ required: true, message: '请输入作者', trigger: 'blur' }
]
};
// 获取资讯列表
const fetchCattleAcademyList = async () => {
try {
loading.value = true;
const params = {
page: pagination.current,
pageSize: pagination.pageSize,
title: searchKeyword.value,
author: searchKeyword.value
};
const response = await cattleAcademyApi.getCattleAcademyList(params);
if (response.code === 200) {
dataSource.value = response.data.list;
pagination.total = response.data.total;
} else {
message.error(response.message || '获取数据失败');
}
} catch (error) {
console.error('获取资讯列表失败:', error);
message.error('获取数据失败');
} finally {
loading.value = false;
}
};
// 搜索
const handleSearch = () => {
pagination.current = 1;
fetchCattleAcademyList();
};
// 表格变化
const handleTableChange = (pag) => {
pagination.current = pag.current;
pagination.pageSize = pag.pageSize;
fetchCattleAcademyList();
};
// 新增资讯
const handleNewInfo = () => {
console.log('New Info');
};
const handleView = (record) => {
console.log('View', record);
Object.assign(currentInfo, {
id: '',
title: '',
coverImage: '',
content: '',
summary: '',
category: '',
sort: 0,
status: true,
author: '',
publishTime: new Date(),
isTop: false,
isRecommend: false,
remarks: ''
});
isModalOpen.value = true;
};
// 编辑资讯
const handleEdit = (record) => {
console.log('Edit', record);
Object.assign(currentInfo, {
...record,
publishTime: record.publishTime ? new Date(record.publishTime) : new Date()
});
isModalOpen.value = true;
};
const handleDelete = (record) => {
console.log('Delete', record);
// 查看详情
const handleView = async (record) => {
try {
const response = await cattleAcademyApi.getCattleAcademyById(record.id);
if (response.code === 200) {
Object.assign(detailData, response.data);
isDetailModalOpen.value = true;
} else {
message.error(response.message || '获取详情失败');
}
} catch (error) {
console.error('获取详情失败:', error);
message.error('获取详情失败');
}
};
// 删除资讯
const handleDelete = async (record) => {
try {
const response = await cattleAcademyApi.deleteCattleAcademy(record.id);
if (response.code === 200) {
message.success('删除成功');
fetchCattleAcademyList();
} else {
message.error(response.message || '删除失败');
}
} catch (error) {
console.error('删除资讯失败:', error);
message.error('删除失败');
}
};
// 切换状态
const handleToggleStatus = async (record, checked) => {
try {
const response = await cattleAcademyApi.toggleCattleAcademyStatus(record.id, checked);
if (response.code === 200) {
message.success('状态更新成功');
fetchCattleAcademyList();
} else {
message.error(response.message || '状态更新失败');
}
} catch (error) {
console.error('切换状态失败:', error);
message.error('状态更新失败');
}
};
// 保存资讯
const handleSave = async () => {
try {
await formRef.value.validate();
modalLoading.value = true;
const data = {
...currentInfo,
publishTime: currentInfo.publishTime ? currentInfo.publishTime.toISOString() : new Date().toISOString()
};
let response;
if (currentInfo.id) {
response = await cattleAcademyApi.updateCattleAcademy(currentInfo.id, data);
} else {
response = await cattleAcademyApi.createCattleAcademy(data);
}
if (response.code === 200 || response.code === 201) {
message.success(currentInfo.id ? '更新成功' : '创建成功');
isModalOpen.value = false;
fetchCattleAcademyList();
} else {
message.error(response.message || '保存失败');
}
} catch (error) {
console.error('保存资讯失败:', error);
message.error('保存失败');
} finally {
modalLoading.value = false;
}
};
// 取消
const handleCancel = () => {
isModalOpen.value = false;
};
// 组件挂载时获取数据
onMounted(() => {
fetchCattleAcademyList();
});
</script>
<style scoped>

View File

@@ -3,75 +3,199 @@
<div class="header">
<div class="title">设备预警</div>
</div>
<!-- 预警统计卡片 -->
<div class="stats-container">
<div class="stat-card">
<div class="stat-value">8056</div>
<div class="stat-value">{{ warningStats.earTag }}</div>
<div class="stat-label">耳标预警</div>
</div>
<div class="stat-card">
<div class="stat-value">1</div>
<div class="stat-value">{{ warningStats.neckband }}</div>
<div class="stat-label">项圈预警</div>
</div>
<div class="stat-card">
<div class="stat-value">61</div>
<div class="stat-value">{{ warningStats.host }}</div>
<div class="stat-label">主机预警</div>
</div>
</div>
<a-card :bordered="false">
<!-- 设备类型标签页和搜索 -->
<a-card :bordered="false" class="main-card">
<div class="table-header">
<a-tabs v-model:activeKey="activeTab">
<a-tabs v-model:activeKey="activeTab" @change="handleTabChange">
<a-tab-pane key="earTag" tab="智能耳标"></a-tab-pane>
<a-tab-pane key="neckband" tab="智能项圈"></a-tab-pane>
<a-tab-pane key="host" tab="智能主机"></a-tab-pane>
</a-tabs>
<a-input v-model:value="searchKeyword" placeholder="请输入养殖户" style="width: 200px;">
<template #suffix>
<span class="iconfont icon-sousuo"></span>
</template>
</a-input>
<div class="search-container">
<a-input
v-model:value="searchKeyword"
placeholder="请输入养殖户"
style="width: 200px;"
@pressEnter="handleSearch"
>
<template #suffix>
<span class="iconfont icon-sousuo" @click="handleSearch"></span>
</template>
</a-input>
<a-button type="primary" @click="handleSearch" style="margin-left: 8px;">Q 搜索</a-button>
</div>
</div>
<!-- 数据表格 -->
<a-table
:columns="columns"
:data-source="tableData"
:data-source="filteredTableData"
:pagination="pagination"
:loading="loading"
row-key="id"
@change="handleTableChange"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'alertType'">
<a-tag :color="getAlertTypeColor(record.alertType)">
{{ record.alertType }}
</a-tag>
</template>
</template>
</a-table>
</a-card>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue';
import { ref, reactive, computed, onMounted } from 'vue';
import { message } from 'ant-design-vue';
import deviceWarningApi from '@/utils/deviceWarningApi';
const activeTab = ref('earTag');
const activeTab = ref('neckband');
const searchKeyword = ref('');
const loading = ref(false);
const tableData = ref([]);
const warningStats = ref({
earTag: 0,
neckband: 0,
host: 0
});
const columns = [
{ title: '养殖场', dataIndex: 'farm', key: 'farm' },
{ title: '养殖户', dataIndex: 'farmer', key: 'farmer' },
{ title: '联系电话', dataIndex: 'phone', key: 'phone' },
{ title: '设备类型', dataIndex: 'deviceType', key: 'deviceType' },
{ title: '设备编号', dataIndex: 'deviceNumber', key: 'deviceNumber' },
{ title: '预警类型', dataIndex: 'alertType', key: 'alertType' },
{ title: '养殖场', dataIndex: 'farmName', key: 'farmName', width: 150 },
{ title: '养殖户', dataIndex: 'farmerName', key: 'farmerName', width: 150 },
{ title: '联系电话', dataIndex: 'phone', key: 'phone', width: 120 },
{ title: '设备类型', dataIndex: 'deviceType', key: 'deviceType', width: 100 },
{ title: '设备编号', dataIndex: 'deviceNumber', key: 'deviceNumber', width: 120 },
{ title: '预警类型', dataIndex: 'alertType', key: 'alertType', width: 100 },
];
const tableData = ref([
{ id: '1', farm: '扎旗大数据中心', farmer: '扎旗大数据中心', phone: '132****9345', deviceType: '智能耳标', deviceNumber: '2407100002', alertType: '设备离线' },
{ id: '2', farm: '扎旗大数据中心', farmer: '扎旗大数据中心', phone: '132****9345', deviceType: '智能耳标', deviceNumber: '2407100001', alertType: '设备离线' },
{ id: '3', farm: '扎旗大数据中心', farmer: '扎旗大数据中心', phone: '132****9345', deviceType: '智能耳标', deviceNumber: '2407402678', alertType: '设备离线' },
{ id: '4', farm: '扎旗大数据中心', farmer: '扎旗大数据中心', phone: '132****9345', deviceType: '智能耳标', deviceNumber: '2407402675', alertType: '设备离线' },
{ id: '5', farm: '扎旗大数据中心', farmer: '扎旗大数据中心', phone: '132****9345', deviceType: '智能耳标', deviceNumber: '2407402674', alertType: '设备离线' },
{ id: '6', farm: '139****8321_养殖场', farmer: '杜云鹏', phone: '139****8321', deviceType: '智能耳标', deviceNumber: '2404412397', alertType: '设备离线' },
{ id: '7', farm: '139****8321_养殖场', farmer: '杜云鹏', phone: '139****8321', deviceType: '智能耳标', deviceNumber: '2404412404', alertType: '设备离线' },
]);
const pagination = reactive({
current: 1,
pageSize: 10,
total: tableData.value.length,
pageSize: 20,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total, range) => `${total}`
});
// 根据当前标签页过滤数据
const filteredTableData = computed(() => {
let filtered = tableData.value;
// 根据设备类型过滤
if (activeTab.value === 'earTag') {
filtered = filtered.filter(item => item.deviceType === '智能耳标');
} else if (activeTab.value === 'neckband') {
filtered = filtered.filter(item => item.deviceType === '智能项圈');
} else if (activeTab.value === 'host') {
filtered = filtered.filter(item => item.deviceType === '智能主机');
}
// 根据搜索关键词过滤
if (searchKeyword.value) {
filtered = filtered.filter(item =>
item.farmerName.includes(searchKeyword.value) ||
item.farmName.includes(searchKeyword.value)
);
}
pagination.total = filtered.length;
return filtered;
});
// 获取预警类型颜色
const getAlertTypeColor = (alertType) => {
const colorMap = {
'设备离线': 'red',
'电量不足': 'orange',
'信号异常': 'yellow',
'温度异常': 'purple',
'其他': 'default'
};
return colorMap[alertType] || 'default';
};
// 获取设备预警数据
const fetchDeviceWarnings = async () => {
try {
loading.value = true;
const params = {
page: pagination.current,
pageSize: pagination.pageSize,
deviceType: activeTab.value === 'earTag' ? '智能耳标' :
activeTab.value === 'neckband' ? '智能项圈' :
activeTab.value === 'host' ? '智能主机' : undefined,
farmerName: searchKeyword.value || undefined
};
const response = await deviceWarningApi.getDeviceWarnings(params);
if (response.code === 200) {
tableData.value = response.data.list;
pagination.total = response.data.total;
}
} catch (error) {
console.error('获取设备预警数据失败:', error);
message.error('获取设备预警数据失败');
} finally {
loading.value = false;
}
};
// 获取预警统计
const fetchWarningStats = async () => {
try {
const response = await deviceWarningApi.getWarningStats();
if (response.code === 200) {
warningStats.value = response.data;
}
} catch (error) {
console.error('获取预警统计失败:', error);
}
};
// 标签页切换
const handleTabChange = (key) => {
activeTab.value = key;
pagination.current = 1;
fetchDeviceWarnings();
};
// 搜索
const handleSearch = () => {
pagination.current = 1;
fetchDeviceWarnings();
};
// 表格变化
const handleTableChange = (pag) => {
pagination.current = pag.current;
pagination.pageSize = pag.pageSize;
fetchDeviceWarnings();
};
// 组件挂载
onMounted(() => {
fetchDeviceWarnings();
fetchWarningStats();
});
</script>
@@ -80,6 +204,7 @@ const pagination = reactive({
.page-container {
background-color: #f0f2f5;
padding: 24px;
min-height: 100vh;
}
.header {
@@ -89,6 +214,7 @@ const pagination = reactive({
.title {
font-size: 24px;
font-weight: bold;
color: #262626;
}
.stats-container {
@@ -99,22 +225,36 @@ const pagination = reactive({
.stat-card {
background-color: #fff;
padding: 20px;
padding: 24px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
text-align: center;
flex: 1;
border: 1px solid #f0f0f0;
transition: all 0.3s;
}
.stat-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
transform: translateY(-2px);
}
.stat-value {
font-size: 28px;
font-size: 32px;
font-weight: bold;
color: #1890ff;
margin-bottom: 8px;
}
.stat-label {
font-size: 14px;
font-size: 16px;
color: #595959;
font-weight: 500;
}
.main-card {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
border-radius: 8px;
}
.table-header {
@@ -122,6 +262,84 @@ const pagination = reactive({
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding-bottom: 16px;
border-bottom: 1px solid #f0f0f0;
}
.search-container {
display: flex;
align-items: center;
gap: 8px;
}
:deep(.ant-tabs-tab) {
font-size: 16px;
font-weight: 500;
padding: 12px 24px;
}
:deep(.ant-tabs-tab-active) {
color: #1890ff;
}
:deep(.ant-tabs-ink-bar) {
background-color: #1890ff;
}
:deep(.ant-table-thead > tr > th) {
background-color: #fafafa;
font-weight: 600;
color: #262626;
border-bottom: 2px solid #f0f0f0;
}
:deep(.ant-table-tbody > tr > td) {
border-bottom: 1px solid #f0f0f0;
padding: 12px 16px;
}
:deep(.ant-table-tbody > tr:hover > td) {
background-color: #f5f5f5;
}
:deep(.ant-pagination) {
margin-top: 16px;
text-align: right;
}
:deep(.ant-pagination-total-text) {
color: #666;
font-size: 14px;
}
:deep(.ant-input-affix-wrapper) {
border-radius: 6px;
}
:deep(.ant-input-affix-wrapper:focus) {
border-color: #1890ff;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
:deep(.ant-btn-primary) {
border-radius: 6px;
font-weight: 500;
}
:deep(.ant-tag) {
border-radius: 4px;
font-weight: 500;
padding: 2px 8px;
}
.icon-sousuo {
cursor: pointer;
color: #999;
font-size: 16px;
}
.icon-sousuo:hover {
color: #1890ff;
}
.important-messages {

View File

@@ -0,0 +1,626 @@
<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="materialTypeFilter" placeholder="生产资料类型" style="width: 150px;">
<a-select-option value="">全部</a-select-option>
<a-select-option value="疫苗">疫苗</a-select-option>
<a-select-option value="饲料">饲料</a-select-option>
<a-select-option value="兽药">兽药</a-select-option>
<a-select-option value="器械">器械</a-select-option>
<a-select-option value="其他">其他</a-select-option>
</a-select>
<a-select v-model:value="certificationTypeFilter" placeholder="认证类型" style="width: 150px;">
<a-select-option value="">全部</a-select-option>
<a-select-option value="兽药GMP认证">兽药GMP认证</a-select-option>
<a-select-option value="饲料生产许可证">饲料生产许可证</a-select-option>
<a-select-option value="医疗器械注册证">医疗器械注册证</a-select-option>
<a-select-option value="其他认证">其他认证</a-select-option>
</a-select>
<a-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="suspended">暂停</a-select-option>
<a-select-option value="revoked">撤销</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="handleAddCertification">
<span class="iconfont icon-tianjia"></span> 新增认证
</a-button>
</div>
</a-card>
<!-- 数据列表 -->
<a-card>
<a-table
:columns="columns"
:data-source="certificationsData"
row-key="id"
:pagination="pagination"
:loading="loading"
@change="handleTableChange"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'certificationStatus'">
<div style="display: flex; align-items: center; gap: 8px;">
<a-switch
:checked="record.certificationStatus === 'valid'"
size="small"
@change="(checked) => handleToggleStatus(record, checked)"
/>
<span>{{ getStatusText(record.certificationStatus) }}</span>
</div>
</template>
<template v-else-if="column.key === 'action'">
<div style="display: flex; gap: 8px;">
<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="handleDetail(record)">详情</a-button>
</div>
</template>
</template>
</a-table>
</a-card>
<!-- 新增/编辑表单 -->
<a-modal
v-model:open="isModalOpen"
:title="currentCertification.id ? '编辑生资认证' : '新增生资认证'"
@ok="handleSave"
@cancel="handleCancel"
:width="600"
:confirm-loading="modalLoading"
>
<a-form
:model="currentCertification"
layout="vertical"
:rules="formRules"
ref="formRef"
>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="生产资料名称" name="materialName">
<a-input v-model:value="currentCertification.materialName" placeholder="请输入生产资料名称" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="生产资料类型" name="materialType">
<a-select v-model:value="currentCertification.materialType" placeholder="请选择生产资料类型">
<a-select-option value="疫苗">疫苗</a-select-option>
<a-select-option value="饲料">饲料</a-select-option>
<a-select-option value="兽药">兽药</a-select-option>
<a-select-option value="器械">器械</a-select-option>
<a-select-option value="其他">其他</a-select-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="生产厂商" name="manufacturer">
<a-input v-model:value="currentCertification.manufacturer" placeholder="请输入生产厂商" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="认证编号" name="certificationNumber">
<a-input v-model:value="currentCertification.certificationNumber" placeholder="请输入认证编号" />
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="认证类型" name="certificationType">
<a-select v-model:value="currentCertification.certificationType" placeholder="请选择认证类型">
<a-select-option value="兽药GMP认证">兽药GMP认证</a-select-option>
<a-select-option value="饲料生产许可证">饲料生产许可证</a-select-option>
<a-select-option value="医疗器械注册证">医疗器械注册证</a-select-option>
<a-select-option value="其他认证">其他认证</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="认证机构" name="certificationAuthority">
<a-input v-model:value="currentCertification.certificationAuthority" placeholder="请输入认证机构" />
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="发证日期" name="issueDate">
<a-date-picker v-model:value="currentCertification.issueDate" style="width: 100%" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="有效期至" name="expiryDate">
<a-date-picker v-model:value="currentCertification.expiryDate" style="width: 100%" />
</a-form-item>
</a-col>
</a-row>
<a-form-item label="产品描述" name="description">
<a-textarea v-model:value="currentCertification.description" placeholder="请输入产品描述" :rows="3" />
</a-form-item>
<a-form-item label="技术规格" name="specifications">
<a-textarea v-model:value="currentCertification.specifications" placeholder="请输入技术规格" :rows="2" />
</a-form-item>
<a-form-item label="适用范围" name="applicableScope">
<a-textarea v-model:value="currentCertification.applicableScope" placeholder="请输入适用范围" :rows="2" />
</a-form-item>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="联系人" name="contactPerson">
<a-input v-model:value="currentCertification.contactPerson" placeholder="请输入联系人" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="联系电话" name="contactPhone">
<a-input v-model:value="currentCertification.contactPhone" placeholder="请输入联系电话" />
</a-form-item>
</a-col>
</a-row>
<a-form-item label="备注" name="remarks">
<a-textarea v-model:value="currentCertification.remarks" placeholder="请输入备注" :rows="2" />
</a-form-item>
</a-form>
</a-modal>
<!-- 详情查看 -->
<a-modal
v-model:open="isDetailModalOpen"
title="生资认证详情"
:footer="null"
:width="600"
>
<a-descriptions :column="2" bordered>
<a-descriptions-item label="生产资料名称">{{ detailData.materialName }}</a-descriptions-item>
<a-descriptions-item label="生产资料类型">{{ detailData.materialType }}</a-descriptions-item>
<a-descriptions-item label="生产厂商">{{ detailData.manufacturer }}</a-descriptions-item>
<a-descriptions-item label="认证编号">{{ detailData.certificationNumber }}</a-descriptions-item>
<a-descriptions-item label="认证类型">{{ detailData.certificationType }}</a-descriptions-item>
<a-descriptions-item label="认证机构">{{ detailData.certificationAuthority }}</a-descriptions-item>
<a-descriptions-item label="发证日期">{{ detailData.issueDate }}</a-descriptions-item>
<a-descriptions-item label="有效期至">{{ detailData.expiryDate }}</a-descriptions-item>
<a-descriptions-item label="认证状态">
<a-tag :color="getStatusColor(detailData.certificationStatus)">
{{ getStatusText(detailData.certificationStatus) }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="联系人">{{ detailData.contactPerson }}</a-descriptions-item>
<a-descriptions-item label="联系电话">{{ detailData.contactPhone }}</a-descriptions-item>
<a-descriptions-item label="产品描述" :span="2">{{ detailData.description }}</a-descriptions-item>
<a-descriptions-item label="技术规格" :span="2">{{ detailData.specifications }}</a-descriptions-item>
<a-descriptions-item label="适用范围" :span="2">{{ detailData.applicableScope }}</a-descriptions-item>
<a-descriptions-item label="备注" :span="2">{{ detailData.remarks }}</a-descriptions-item>
</a-descriptions>
</a-modal>
</div>
</template>
<script setup>
import { ref, reactive, computed, onMounted } from 'vue'
import { message } from 'ant-design-vue'
import productionMaterialCertificationApi from '@/utils/productionMaterialCertificationApi'
import dayjs from 'dayjs'
// 搜索条件
const searchKeyword = ref('')
const materialTypeFilter = ref('')
const certificationTypeFilter = ref('')
const statusFilter = ref('')
// 分页配置
const pagination = reactive({
current: 1,
pageSize: 20,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total, range) => `${range[0]}-${range[1]} 条,共 ${total}`
})
// 模态框状态
const isModalOpen = ref(false)
const isDetailModalOpen = ref(false)
const modalLoading = ref(false)
// 表单引用
const formRef = ref(null)
// 当前编辑的认证
const currentCertification = reactive({
id: '',
materialName: '',
materialType: '',
manufacturer: '',
certificationNumber: '',
certificationType: '',
certificationAuthority: '',
issueDate: null,
expiryDate: null,
certificationStatus: 'valid',
description: '',
specifications: '',
applicableScope: '',
contactPerson: '',
contactPhone: '',
remarks: ''
})
// 详情数据
const detailData = reactive({})
// 表单验证规则
const formRules = {
materialName: [
{ required: true, message: '请输入生产资料名称', trigger: 'blur' }
],
materialType: [
{ required: true, message: '请选择生产资料类型', trigger: 'change' }
],
manufacturer: [
{ required: true, message: '请输入生产厂商', trigger: 'blur' }
],
certificationNumber: [
{ required: true, message: '请输入认证编号', trigger: 'blur' }
],
certificationType: [
{ required: true, message: '请选择认证类型', trigger: 'change' }
],
certificationAuthority: [
{ required: true, message: '请输入认证机构', trigger: 'blur' }
],
issueDate: [
{ required: true, message: '请选择发证日期', trigger: 'change' }
],
expiryDate: [
{ required: true, message: '请选择有效期至', trigger: 'change' }
]
}
// 认证列表数据
const certificationsData = ref([])
const loading = ref(false)
// 表格列定义
const columns = [
{
title: '生产资料名称',
dataIndex: 'materialName',
key: 'materialName',
width: 150
},
{
title: '生产资料类型',
dataIndex: 'materialType',
key: 'materialType',
width: 120
},
{
title: '生产厂商',
dataIndex: 'manufacturer',
key: 'manufacturer',
width: 150
},
{
title: '认证编号',
dataIndex: 'certificationNumber',
key: 'certificationNumber',
width: 120
},
{
title: '认证类型',
dataIndex: 'certificationType',
key: 'certificationType',
width: 120
},
{
title: '认证机构',
dataIndex: 'certificationAuthority',
key: 'certificationAuthority',
width: 150
},
{
title: '发证日期',
dataIndex: 'issueDate',
key: 'issueDate',
width: 100
},
{
title: '有效期至',
dataIndex: 'expiryDate',
key: 'expiryDate',
width: 100
},
{
title: '认证状态',
dataIndex: 'certificationStatus',
key: 'certificationStatus',
width: 100
},
{
title: '更新时间',
dataIndex: 'updatedAt',
key: 'updatedAt',
width: 150
},
{
title: '操作',
key: 'action',
width: 150,
fixed: 'right'
}
]
// 获取状态文本
const getStatusText = (status) => {
const statusMap = {
'valid': '有效',
'expired': '过期',
'suspended': '暂停',
'revoked': '撤销'
}
return statusMap[status] || status
}
// 获取状态颜色
const getStatusColor = (status) => {
const colorMap = {
'valid': 'green',
'expired': 'red',
'suspended': 'orange',
'revoked': 'red'
}
return colorMap[status] || 'default'
}
// 获取认证列表
const fetchCertifications = async () => {
try {
loading.value = true
const params = {
page: pagination.current,
pageSize: pagination.pageSize,
materialName: searchKeyword.value,
materialType: materialTypeFilter.value,
certificationType: certificationTypeFilter.value,
certificationStatus: statusFilter.value
}
const response = await productionMaterialCertificationApi.getCertifications(params)
if (response.code === 200) {
certificationsData.value = response.data.list
pagination.total = response.data.total
} else {
message.error(response.message || '获取数据失败')
}
} catch (error) {
console.error('获取认证列表失败:', error)
message.error('获取数据失败')
} finally {
loading.value = false
}
}
// 搜索
const handleSearch = () => {
pagination.current = 1
fetchCertifications()
}
// 重置
const handleReset = () => {
searchKeyword.value = ''
materialTypeFilter.value = ''
certificationTypeFilter.value = ''
statusFilter.value = ''
pagination.current = 1
fetchCertifications()
}
// 表格变化
const handleTableChange = (pag) => {
pagination.current = pag.current
pagination.pageSize = pag.pageSize
fetchCertifications()
}
// 新增认证
const handleAddCertification = () => {
Object.assign(currentCertification, {
id: '',
materialName: '',
materialType: '',
manufacturer: '',
certificationNumber: '',
certificationType: '',
certificationAuthority: '',
issueDate: null,
expiryDate: null,
certificationStatus: 'valid',
description: '',
specifications: '',
applicableScope: '',
contactPerson: '',
contactPhone: '',
remarks: ''
})
isModalOpen.value = true
}
// 编辑认证
const handleEdit = (record) => {
Object.assign(currentCertification, {
...record,
issueDate: record.issueDate ? dayjs(record.issueDate) : null,
expiryDate: record.expiryDate ? dayjs(record.expiryDate) : null
})
isModalOpen.value = true
}
// 查看详情
const handleDetail = (record) => {
Object.assign(detailData, record)
isDetailModalOpen.value = true
}
// 删除认证
const handleDelete = async (id) => {
try {
const response = await productionMaterialCertificationApi.deleteCertification(id)
if (response.code === 200) {
message.success('删除成功')
fetchCertifications()
} else {
message.error(response.message || '删除失败')
}
} catch (error) {
console.error('删除认证失败:', error)
message.error('删除失败')
}
}
// 切换状态
const handleToggleStatus = async (record, checked) => {
try {
const status = checked ? 'valid' : 'suspended'
const response = await productionMaterialCertificationApi.toggleCertificationStatus(record.id, status)
if (response.code === 200) {
message.success('状态更新成功')
fetchCertifications()
} else {
message.error(response.message || '状态更新失败')
}
} catch (error) {
console.error('切换状态失败:', error)
message.error('状态更新失败')
}
}
// 保存认证
const handleSave = async () => {
try {
await formRef.value.validate()
modalLoading.value = true
const data = {
...currentCertification,
issueDate: currentCertification.issueDate ? currentCertification.issueDate.format('YYYY-MM-DD') : null,
expiryDate: currentCertification.expiryDate ? currentCertification.expiryDate.format('YYYY-MM-DD') : null
}
let response
if (currentCertification.id) {
response = await productionMaterialCertificationApi.updateCertification(currentCertification.id, data)
} else {
response = await productionMaterialCertificationApi.createCertification(data)
}
if (response.code === 200 || response.code === 201) {
message.success(currentCertification.id ? '更新成功' : '创建成功')
isModalOpen.value = false
fetchCertifications()
} else {
message.error(response.message || '保存失败')
}
} catch (error) {
console.error('保存认证失败:', error)
message.error('保存失败')
} finally {
modalLoading.value = false
}
}
// 取消
const handleCancel = () => {
isModalOpen.value = false
}
// 组件挂载时获取数据
onMounted(() => {
fetchCertifications()
})
</script>
<style scoped>
:deep(.ant-modal-content) {
border-radius: 8px;
}
:deep(.ant-modal-header) {
border-radius: 8px 8px 0 0;
padding: 16px 24px;
border-bottom: 1px solid #f0f0f0;
}
:deep(.ant-modal-body) {
padding: 24px;
}
:deep(.ant-form-item-label > label) {
font-weight: 500;
color: #262626;
}
:deep(.ant-btn-primary) {
background-color: #1890ff;
border-color: #1890ff;
border-radius: 6px;
}
:deep(.ant-btn-primary:hover) {
background-color: #40a9ff;
border-color: #40a9ff;
}
:deep(.ant-btn-dangerous) {
background-color: #ff4d4f;
border-color: #ff4d4f;
border-radius: 6px;
}
:deep(.ant-btn-dangerous:hover) {
background-color: #ff7875;
border-color: #ff7875;
}
:deep(.ant-btn) {
border-radius: 6px;
}
:deep(.ant-input) {
border-radius: 6px;
}
:deep(.ant-select-selector) {
border-radius: 6px;
}
:deep(.ant-picker) {
border-radius: 6px;
}
</style>