parent
7bdd023c96
commit
8320bf00f8
@ -0,0 +1,50 @@
|
||||
// src/api/deviceStatus.ts
|
||||
import axios from 'axios'
|
||||
|
||||
export const DeviceStatusApi = {
|
||||
// 获取设备状态列表 - 修改为匹配后端实际接口
|
||||
getDevicesByStatus: async (status: string, areaId?: string, deviceType?: string) => {
|
||||
try {
|
||||
const params: any = { status }
|
||||
if (areaId) params.areaId = areaId
|
||||
if (deviceType) params.deviceType = deviceType
|
||||
|
||||
const response = await axios.get('/api/web/device-status/by-status', { params })
|
||||
return response.data
|
||||
} catch (error) {
|
||||
throw new Error(`获取设备列表失败: ${error}`)
|
||||
}
|
||||
},
|
||||
|
||||
// 标记设备在线
|
||||
markDeviceOnline: async (deviceId: string) => {
|
||||
try {
|
||||
const response = await axios.post(`/api/web/device-status/${deviceId}/online`)
|
||||
return response.data
|
||||
} catch (error) {
|
||||
throw new Error(`设置设备在线失败: ${error}`)
|
||||
}
|
||||
},
|
||||
|
||||
// 标记设备离线
|
||||
markDeviceOffline: async (deviceId: string, reason?: string) => {
|
||||
try {
|
||||
const params = reason ? { reason } : {}
|
||||
const response = await axios.post(`/api/web/device-status/${deviceId}/offline`, null, { params })
|
||||
return response.data
|
||||
} catch (error) {
|
||||
throw new Error(`设置设备离线失败: ${error}`)
|
||||
}
|
||||
},
|
||||
|
||||
// 标记设备故障
|
||||
markDeviceFault: async (deviceId: string, faultType: string, description: string) => {
|
||||
try {
|
||||
const params = { faultType, description }
|
||||
const response = await axios.post(`/api/web/device-status/${deviceId}/fault`, null, { params })
|
||||
return response.data
|
||||
} catch (error) {
|
||||
throw new Error(`设置设备故障失败: ${error}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
<!-- src/views/equipment/EquipmentView.vue -->
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<h1>设备监控</h1>
|
||||
<p>请选择左侧子菜单查看具体设备信息</p>
|
||||
</div>
|
||||
</template>
|
||||
@ -0,0 +1,412 @@
|
||||
<!-- src/views/equipment/WaterSupplier.vue -->
|
||||
<template>
|
||||
<div class="water-supplier-page">
|
||||
<!-- 页面标题和面包屑 -->
|
||||
<div class="page-header">
|
||||
<h2>供水机管理</h2>
|
||||
<div class="breadcrumb">校园矿化水平台 / 设备监控 / 供水机</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮区 -->
|
||||
<div class="action-bar">
|
||||
<button class="btn-add">添加供水机</button>
|
||||
|
||||
<div class="filters">
|
||||
<!-- 搜索框 -->
|
||||
<div class="search-box">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="搜索设备ID或位置..."
|
||||
v-model="searchKeyword"
|
||||
@input="handleSearch"
|
||||
>
|
||||
<button class="search-btn">搜索</button>
|
||||
</div>
|
||||
|
||||
<!-- 片区筛选 -->
|
||||
<select
|
||||
v-model="selectedArea"
|
||||
class="filter-select"
|
||||
@change="handleSearch"
|
||||
>
|
||||
<option value="">全部片区</option>
|
||||
<option value="市区">市区</option>
|
||||
<option value="校区">校区</option>
|
||||
</select>
|
||||
|
||||
<!-- 状态筛选 -->
|
||||
<select
|
||||
v-model="selectedStatus"
|
||||
class="filter-select"
|
||||
@change="handleSearch"
|
||||
>
|
||||
<option value="">全部状态</option>
|
||||
<option value="online">在线</option>
|
||||
<option value="offline">离线</option>
|
||||
<option value="warning">警告</option>
|
||||
<option value="error">故障</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 设备表格 - 新增设备机型列 -->
|
||||
<div class="card">
|
||||
<table class="equipment-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>设备ID</th>
|
||||
<th>设备机型</th> <!-- 新增机型列 -->
|
||||
<th>所属片区</th>
|
||||
<th>详细位置</th>
|
||||
<th>状态</th>
|
||||
<th>最后上传时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="device in filteredDevices" :key="device.id">
|
||||
<td>{{ device.id }}</td>
|
||||
<td>供水机</td> <!-- 固定显示供水机机型 -->
|
||||
<td>{{ device.area }}</td>
|
||||
<td>{{ device.location }}</td>
|
||||
<td>
|
||||
<span :class="`status-tag ${device.status}`">
|
||||
{{ formatStatus(device.status) }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ device.lastUploadTime }}</td>
|
||||
<td class="operation-buttons">
|
||||
<button class="btn-view" @click="viewDevice(device.id)">查看详情</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="filteredDevices.length === 0">
|
||||
<td colspan="7" class="no-data">暂无设备数据</td> <!-- colspan从6改为7 -->
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 分页控件 -->
|
||||
<div class="pagination">
|
||||
<button
|
||||
class="page-btn"
|
||||
:disabled="currentPage === 1"
|
||||
@click="currentPage--"
|
||||
>
|
||||
上一页
|
||||
</button>
|
||||
<span class="page-info">
|
||||
第 {{ currentPage }} 页 / 共 {{ totalPages }} 页
|
||||
</span>
|
||||
<button
|
||||
class="page-btn"
|
||||
:disabled="currentPage === totalPages"
|
||||
@click="currentPage++"
|
||||
>
|
||||
下一页
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
// 设备状态类型定义
|
||||
type DeviceStatus = 'online' | 'offline' | 'warning' | 'error'
|
||||
|
||||
// 设备数据接口
|
||||
interface WaterSupplierDevice {
|
||||
id: string
|
||||
area: string
|
||||
location: string
|
||||
status: DeviceStatus
|
||||
lastUploadTime: string
|
||||
}
|
||||
|
||||
// 模拟供水机设备数据
|
||||
const waterSupplierDevices: WaterSupplierDevice[] = [
|
||||
{
|
||||
id: 'WS-2023-001',
|
||||
area: '市区',
|
||||
location: '行政中心大楼1楼大厅',
|
||||
status: 'online',
|
||||
lastUploadTime: '2023-10-25 10:15:33'
|
||||
},
|
||||
{
|
||||
id: 'WS-2023-002',
|
||||
area: '校区',
|
||||
location: '研究生公寓3号楼一层',
|
||||
status: 'online',
|
||||
lastUploadTime: '2023-10-25 09:30:22'
|
||||
},
|
||||
{
|
||||
id: 'WS-2023-003',
|
||||
area: '市区',
|
||||
location: '科技园区A座大厅',
|
||||
status: 'warning',
|
||||
lastUploadTime: '2023-10-25 08:45:11'
|
||||
},
|
||||
{
|
||||
id: 'WS-2023-004',
|
||||
area: '校区',
|
||||
location: '留学生公寓1楼',
|
||||
status: 'offline',
|
||||
lastUploadTime: '2023-10-24 23:05:47'
|
||||
},
|
||||
{
|
||||
id: 'WS-2023-005',
|
||||
area: '市区',
|
||||
location: '图书馆新馆2楼',
|
||||
status: 'error',
|
||||
lastUploadTime: '2023-10-25 07:20:35'
|
||||
}
|
||||
]
|
||||
|
||||
// 响应式数据
|
||||
const devices = ref<WaterSupplierDevice[]>(waterSupplierDevices)
|
||||
const searchKeyword = ref('')
|
||||
const selectedArea = ref('') // 片区筛选值
|
||||
const selectedStatus = ref('') // 状态筛选值
|
||||
const currentPage = ref(1)
|
||||
const pageSize = 10 // 每页显示数量
|
||||
const router = useRouter()
|
||||
|
||||
// 多条件过滤设备数据
|
||||
const filteredDevices = computed(() => {
|
||||
return devices.value.filter(device => {
|
||||
const keywordMatch = searchKeyword.value.trim() === '' ||
|
||||
device.id.toLowerCase().includes(searchKeyword.value.toLowerCase()) ||
|
||||
device.location.toLowerCase().includes(searchKeyword.value.toLowerCase())
|
||||
|
||||
const areaMatch = selectedArea.value === '' || device.area === selectedArea.value
|
||||
const statusMatch = selectedStatus.value === '' || device.status === selectedStatus.value
|
||||
|
||||
return keywordMatch && areaMatch && statusMatch
|
||||
})
|
||||
})
|
||||
|
||||
// 分页计算
|
||||
const totalPages = computed(() => {
|
||||
return Math.ceil(filteredDevices.value.length / pageSize)
|
||||
})
|
||||
|
||||
// 状态格式化
|
||||
const formatStatus = (status: DeviceStatus): string => {
|
||||
const statusMap = {
|
||||
online: '在线',
|
||||
offline: '离线',
|
||||
warning: '警告',
|
||||
error: '故障'
|
||||
}
|
||||
return statusMap[status]
|
||||
}
|
||||
|
||||
// 搜索处理
|
||||
const handleSearch = () => {
|
||||
currentPage.value = 1 // 重置到第一页
|
||||
}
|
||||
|
||||
// 查看详情
|
||||
const viewDevice = (id: string) => {
|
||||
router.push(`/home/equipment/water-supplier/${id}`)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 样式与制水机页面保持一致 */
|
||||
.water-supplier-page {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.page-header h2 {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.btn-add {
|
||||
background: #42b983;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
.btn-add:hover {
|
||||
background: #359e75;
|
||||
}
|
||||
|
||||
.filters {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.search-box input {
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
width: 240px;
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.filter-select {
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
background: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.equipment-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.equipment-table th,
|
||||
.equipment-table td {
|
||||
padding: 12px 16px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.equipment-table th {
|
||||
background-color: #f8f9fa;
|
||||
font-weight: 600;
|
||||
color: #4e5969;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.equipment-table tbody tr:hover {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.status-tag {
|
||||
display: inline-block;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-tag.online {
|
||||
background-color: #e6f7ee;
|
||||
color: #00875a;
|
||||
}
|
||||
|
||||
.status-tag.offline {
|
||||
background-color: #f5f5f5;
|
||||
color: #8c8c8c;
|
||||
}
|
||||
|
||||
.status-tag.warning {
|
||||
background-color: #fff7e6;
|
||||
color: #d48806;
|
||||
}
|
||||
|
||||
.status-tag.error {
|
||||
background-color: #ffebe6;
|
||||
color: #cf1322;
|
||||
}
|
||||
|
||||
.operation-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.operation-buttons button {
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.operation-buttons button:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.btn-view {
|
||||
background-color: #e6f7ff;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.no-data {
|
||||
text-align: center;
|
||||
padding: 40px 0;
|
||||
color: #8c8c8c;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-top: 24px;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.page-btn {
|
||||
padding: 4px 12px;
|
||||
border: 1px solid #ddd;
|
||||
background: white;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.page-btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media (max-width: 768px) {
|
||||
.filters {
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-box, .filter-select {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,371 @@
|
||||
<!-- src/views/personnel/Admin.vue -->
|
||||
<template>
|
||||
<div class="admin-page">
|
||||
<!-- 页面标题和面包屑 -->
|
||||
<div class="page-header">
|
||||
<h2>管理员管理</h2>
|
||||
<div class="breadcrumb">校园矿化水平台 / 人员管理 / 管理员</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮区 -->
|
||||
<div class="action-bar">
|
||||
<button class="btn-add" @click="handleAddAdmin">新增管理员</button>
|
||||
|
||||
<div class="search-box">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="搜索姓名或账号..."
|
||||
v-model="searchKeyword"
|
||||
@input="handleSearch"
|
||||
>
|
||||
<button class="search-btn" @click="handleSearch">搜索</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 管理员表格 -->
|
||||
<div class="card">
|
||||
<table class="admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>姓名</th>
|
||||
<th>账号</th>
|
||||
<th>联系电话</th>
|
||||
<th>身份</th>
|
||||
<th>状态</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="admin in filteredAdmins" :key="admin.id">
|
||||
<td>{{ admin.name }}</td>
|
||||
<td>{{ admin.account }}</td>
|
||||
<td>{{ admin.phone }}</td>
|
||||
<td>{{ admin.role }}</td>
|
||||
<td>
|
||||
<span :class="`status-tag ${admin.status}`">
|
||||
{{ admin.status === 'active' ? '启用' : '禁用' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="operation-buttons">
|
||||
<button
|
||||
class="btn-edit"
|
||||
@click="handleEdit(admin.id)"
|
||||
>
|
||||
编辑
|
||||
</button>
|
||||
<button
|
||||
class="btn-status"
|
||||
:class="admin.status === 'active' ? 'btn-disable' : 'btn-enable'"
|
||||
@click="handleStatusChange(admin.id, admin.status)"
|
||||
>
|
||||
{{ admin.status === 'active' ? '禁用' : '启用' }}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="filteredAdmins.length === 0">
|
||||
<td colspan="6" class="no-data">暂无管理员数据</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 分页控件 -->
|
||||
<div class="pagination">
|
||||
<button
|
||||
class="page-btn"
|
||||
:disabled="currentPage === 1"
|
||||
@click="currentPage--"
|
||||
>
|
||||
上一页
|
||||
</button>
|
||||
<span class="page-info">
|
||||
第 {{ currentPage }} 页 / 共 {{ totalPages }} 页
|
||||
</span>
|
||||
<button
|
||||
class="page-btn"
|
||||
:disabled="currentPage === totalPages"
|
||||
@click="currentPage++"
|
||||
>
|
||||
下一页
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
// 管理员状态类型
|
||||
type AdminStatus = 'active' | 'disabled'
|
||||
|
||||
// 管理员数据接口
|
||||
interface Admin {
|
||||
id: string
|
||||
name: string
|
||||
account: string
|
||||
phone: string
|
||||
role: string
|
||||
status: AdminStatus
|
||||
}
|
||||
|
||||
// 模拟管理员数据
|
||||
const adminList: Admin[] = [
|
||||
{
|
||||
id: '1',
|
||||
name: '张三',
|
||||
account: 'admin01',
|
||||
phone: '13800138000',
|
||||
role: '超级管理员',
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: '李四',
|
||||
account: 'admin02',
|
||||
phone: '13900139000',
|
||||
role: '设备管理员',
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: '王五',
|
||||
account: 'admin03',
|
||||
phone: '13700137000',
|
||||
role: '系统管理员',
|
||||
status: 'disabled'
|
||||
}
|
||||
]
|
||||
|
||||
// 响应式数据
|
||||
const admins = ref<Admin[]>(adminList)
|
||||
const searchKeyword = ref('')
|
||||
const currentPage = ref(1)
|
||||
const pageSize = 10 // 每页显示数量
|
||||
const router = useRouter()
|
||||
|
||||
// 筛选后的管理员列表
|
||||
const filteredAdmins = computed(() => {
|
||||
return admins.value.filter(admin => {
|
||||
const keywordMatch = searchKeyword.value.trim() === '' ||
|
||||
admin.name.toLowerCase().includes(searchKeyword.value.toLowerCase()) ||
|
||||
admin.account.toLowerCase().includes(searchKeyword.value.toLowerCase())
|
||||
return keywordMatch
|
||||
})
|
||||
})
|
||||
|
||||
// 分页计算
|
||||
const totalPages = computed(() => {
|
||||
return Math.ceil(filteredAdmins.value.length / pageSize)
|
||||
})
|
||||
|
||||
// 搜索处理
|
||||
const handleSearch = () => {
|
||||
currentPage.value = 1 // 重置到第一页
|
||||
}
|
||||
|
||||
// 状态变更处理
|
||||
const handleStatusChange = (id: string, currentStatus: AdminStatus) => {
|
||||
const newStatus: AdminStatus = currentStatus === 'active' ? 'disabled' : 'active'
|
||||
admins.value = admins.value.map(admin =>
|
||||
admin.id === id ? { ...admin, status: newStatus } : admin
|
||||
)
|
||||
// 实际项目中这里应该调用API更新状态
|
||||
}
|
||||
|
||||
// 编辑处理
|
||||
const handleEdit = (id: string) => {
|
||||
router.push(`/home/personnel/admin/edit/${id}`)
|
||||
}
|
||||
|
||||
// 新增管理员
|
||||
const handleAddAdmin = () => {
|
||||
router.push('/home/personnel/admin/add')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.admin-page {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.page-header h2 {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.btn-add {
|
||||
background: #42b983;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
.btn-add:hover {
|
||||
background: #359e75;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.search-box input {
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
width: 240px;
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.admin-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.admin-table th,
|
||||
.admin-table td {
|
||||
padding: 12px 16px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.admin-table th {
|
||||
background-color: #f8f9fa;
|
||||
font-weight: 600;
|
||||
color: #4e5969;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.admin-table tbody tr:hover {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.status-tag {
|
||||
display: inline-block;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-tag.active {
|
||||
background-color: #e6f7ee;
|
||||
color: #00875a;
|
||||
}
|
||||
|
||||
.status-tag.disabled {
|
||||
background-color: #f5f5f5;
|
||||
color: #8c8c8c;
|
||||
}
|
||||
|
||||
.operation-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.operation-buttons button {
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.operation-buttons button:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.btn-edit {
|
||||
background-color: #e6f7ff;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.btn-enable {
|
||||
background-color: #e6f7ee;
|
||||
color: #00875a;
|
||||
}
|
||||
|
||||
.btn-disable {
|
||||
background-color: #ffebe6;
|
||||
color: #cf1322;
|
||||
}
|
||||
|
||||
.no-data {
|
||||
text-align: center;
|
||||
padding: 40px 0;
|
||||
color: #8c8c8c;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-top: 24px;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.page-btn {
|
||||
padding: 4px 12px;
|
||||
border: 1px solid #ddd;
|
||||
background: white;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.page-btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media (max-width: 768px) {
|
||||
.action-bar {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-box input {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,387 @@
|
||||
<!-- src/views/personnel/Maintenance.vue -->
|
||||
<template>
|
||||
<div class="maintenance-page">
|
||||
<!-- 页面标题和面包屑 -->
|
||||
<div class="page-header">
|
||||
<h2>维修人员管理</h2>
|
||||
<div class="breadcrumb">校园矿化水平台 / 人员管理 / 维修人员</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮区 -->
|
||||
<div class="action-bar">
|
||||
<button class="btn-add" @click="handleAddMaintenance">新增维修人员</button>
|
||||
|
||||
<div class="search-box">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="搜索姓名或账号..."
|
||||
v-model="searchKeyword"
|
||||
@input="handleSearch"
|
||||
>
|
||||
<button class="search-btn" @click="handleSearch">搜索</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 维修人员表格 -->
|
||||
<div class="card">
|
||||
<table class="maintenance-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>姓名</th>
|
||||
<th>账号</th>
|
||||
<th>联系电话</th>
|
||||
<th>维修片区</th>
|
||||
<th>状态</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="staff in filteredStaff" :key="staff.id">
|
||||
<td>{{ staff.name }}</td>
|
||||
<td>{{ staff.account }}</td>
|
||||
<td>{{ staff.phone }}</td>
|
||||
<td>{{ staff.area }}</td>
|
||||
<td>
|
||||
<span :class="`status-tag ${staff.status}`">
|
||||
{{ staff.status === 'active' ? '启用' : '禁用' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="operation-buttons">
|
||||
<button
|
||||
class="btn-view"
|
||||
@click="handleViewRecords(staff.id)"
|
||||
>
|
||||
查看维修记录
|
||||
</button>
|
||||
<button
|
||||
class="btn-edit"
|
||||
@click="handleEdit(staff.id)"
|
||||
>
|
||||
编辑
|
||||
</button>
|
||||
<button
|
||||
class="btn-status"
|
||||
:class="staff.status === 'active' ? 'btn-disable' : 'btn-enable'"
|
||||
@click="handleStatusChange(staff.id, staff.status)"
|
||||
>
|
||||
{{ staff.status === 'active' ? '禁用' : '启用' }}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="filteredStaff.length === 0">
|
||||
<td colspan="6" class="no-data">暂无维修人员数据</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 分页控件 -->
|
||||
<div class="pagination">
|
||||
<button
|
||||
class="page-btn"
|
||||
:disabled="currentPage === 1"
|
||||
@click="currentPage--"
|
||||
>
|
||||
上一页
|
||||
</button>
|
||||
<span class="page-info">
|
||||
第 {{ currentPage }} 页 / 共 {{ totalPages }} 页
|
||||
</span>
|
||||
<button
|
||||
class="page-btn"
|
||||
:disabled="currentPage === totalPages"
|
||||
@click="currentPage++"
|
||||
>
|
||||
下一页
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
// 维修人员状态类型
|
||||
type StaffStatus = 'active' | 'disabled'
|
||||
|
||||
// 维修人员数据接口
|
||||
interface MaintenanceStaff {
|
||||
id: string
|
||||
name: string
|
||||
account: string
|
||||
phone: string
|
||||
area: string
|
||||
status: StaffStatus
|
||||
}
|
||||
|
||||
// 模拟维修人员数据
|
||||
const staffList: MaintenanceStaff[] = [
|
||||
{
|
||||
id: '1',
|
||||
name: '赵六',
|
||||
account: 'repair01',
|
||||
phone: '13500135000',
|
||||
area: '市区',
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: '孙七',
|
||||
account: 'repair02',
|
||||
phone: '13600136000',
|
||||
area: '校区',
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: '周八',
|
||||
account: 'repair03',
|
||||
phone: '13400134000',
|
||||
area: '市区',
|
||||
status: 'disabled'
|
||||
}
|
||||
]
|
||||
|
||||
// 响应式数据
|
||||
const staff = ref<MaintenanceStaff[]>(staffList)
|
||||
const searchKeyword = ref('')
|
||||
const currentPage = ref(1)
|
||||
const pageSize = 10 // 每页显示数量
|
||||
const router = useRouter()
|
||||
|
||||
// 筛选后的维修人员列表
|
||||
const filteredStaff = computed(() => {
|
||||
return staff.value.filter(person => {
|
||||
const keywordMatch = searchKeyword.value.trim() === '' ||
|
||||
person.name.toLowerCase().includes(searchKeyword.value.toLowerCase()) ||
|
||||
person.account.toLowerCase().includes(searchKeyword.value.toLowerCase())
|
||||
return keywordMatch
|
||||
})
|
||||
})
|
||||
|
||||
// 分页计算
|
||||
const totalPages = computed(() => {
|
||||
return Math.ceil(filteredStaff.value.length / pageSize)
|
||||
})
|
||||
|
||||
// 搜索处理
|
||||
const handleSearch = () => {
|
||||
currentPage.value = 1 // 重置到第一页
|
||||
}
|
||||
|
||||
// 状态变更处理
|
||||
const handleStatusChange = (id: string, currentStatus: StaffStatus) => {
|
||||
const newStatus: StaffStatus = currentStatus === 'active' ? 'disabled' : 'active'
|
||||
staff.value = staff.value.map(person =>
|
||||
person.id === id ? { ...person, status: newStatus } : person
|
||||
)
|
||||
// 实际项目中这里应该调用API更新状态
|
||||
}
|
||||
|
||||
// 编辑处理
|
||||
const handleEdit = (id: string) => {
|
||||
router.push(`/home/personnel/maintenance/edit/${id}`)
|
||||
}
|
||||
|
||||
// 查看维修记录
|
||||
const handleViewRecords = (id: string) => {
|
||||
router.push(`/home/personnel/maintenance/records/${id}`)
|
||||
}
|
||||
|
||||
// 新增维修人员
|
||||
const handleAddMaintenance = () => {
|
||||
router.push('/home/personnel/maintenance/add')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.maintenance-page {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.page-header h2 {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.btn-add {
|
||||
background: #42b983;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
.btn-add:hover {
|
||||
background: #359e75;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.search-box input {
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
width: 240px;
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.maintenance-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.maintenance-table th,
|
||||
.maintenance-table td {
|
||||
padding: 12px 16px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.maintenance-table th {
|
||||
background-color: #f8f9fa;
|
||||
font-weight: 600;
|
||||
color: #4e5969;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.maintenance-table tbody tr:hover {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.status-tag {
|
||||
display: inline-block;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-tag.active {
|
||||
background-color: #e6f7ee;
|
||||
color: #00875a;
|
||||
}
|
||||
|
||||
.status-tag.disabled {
|
||||
background-color: #f5f5f5;
|
||||
color: #8c8c8c;
|
||||
}
|
||||
|
||||
.operation-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.operation-buttons button {
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.operation-buttons button:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.btn-view {
|
||||
background-color: #f6f7ff;
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
.btn-edit {
|
||||
background-color: #e6f7ff;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.btn-enable {
|
||||
background-color: #e6f7ee;
|
||||
color: #00875a;
|
||||
}
|
||||
|
||||
.btn-disable {
|
||||
background-color: #ffebe6;
|
||||
color: #cf1322;
|
||||
}
|
||||
|
||||
.no-data {
|
||||
text-align: center;
|
||||
padding: 40px 0;
|
||||
color: #8c8c8c;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-top: 24px;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.page-btn {
|
||||
padding: 4px 12px;
|
||||
border: 1px solid #ddd;
|
||||
background: white;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.page-btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media (max-width: 768px) {
|
||||
.action-bar {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-box input {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,7 @@
|
||||
<!-- src/views/workorder/WorkOrderView.vue -->
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<h1>工单管理</h1>
|
||||
<p>请选择左侧子菜单查看具体工单信息</p>
|
||||
</div>
|
||||
</template>
|
||||
Loading…
Reference in new issue