@ -0,0 +1,2 @@
|
||||
VITE_API_BASE_URL=http://localhost:8080
|
||||
VITE_APP_ORIGIN=http://localhost:5173
|
||||
@ -1,58 +1,61 @@
|
||||
import type { LoginRequest, LoginVO } from './types/auth'
|
||||
// 替换原文件内容
|
||||
import type { LoginRequest, LoginResponse, LoginVO } from './types/auth'
|
||||
|
||||
// 真实的登录API调用
|
||||
export const realLoginApi = async (data: LoginRequest): Promise<LoginVO> => {
|
||||
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080'
|
||||
|
||||
console.log('🌐 调用登录接口:', `${API_BASE_URL}/api/common/login`)
|
||||
console.log('📤 请求数据:', data)
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/api/common/login`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
|
||||
console.log('📥 响应状态:', response.status, response.statusText)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`网络请求失败: ${response.status} ${response.statusText}`)
|
||||
export const realLoginApi = async (data: LoginRequest): Promise<LoginResponse> => {
|
||||
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080'
|
||||
|
||||
console.log('🌐 调用登录接口:', `${API_BASE_URL}/api/common/login`)
|
||||
console.log('📤 请求数据:', data)
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/api/common/login`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
|
||||
console.log('📥 响应状态:', response.status, response.statusText)
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
console.error('❌ 响应内容:', errorText)
|
||||
throw new Error(`网络请求失败: ${response.status} ${response.statusText}`)
|
||||
}
|
||||
|
||||
const result: LoginResponse = await response.json()
|
||||
console.log('✅ 登录响应:', result)
|
||||
|
||||
return result
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('❌ 登录接口调用失败:', error)
|
||||
throw new Error(`登录失败: ${error.message}`)
|
||||
}
|
||||
|
||||
const result: LoginVO = await response.json()
|
||||
console.log('✅ 登录响应:', result)
|
||||
|
||||
return result
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('❌ 登录接口调用失败:', error)
|
||||
throw new Error(`登录失败: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
// 备用模拟登录
|
||||
export const mockLoginApi = async (data: LoginRequest): Promise<LoginVO> => {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
|
||||
if (data.username === 'admin' && data.password === '123456') {
|
||||
return {
|
||||
code: 200,
|
||||
message: '登录成功',
|
||||
data: {
|
||||
token: 'mock-jwt-token-' + Date.now(),
|
||||
userInfo: {
|
||||
id: 1,
|
||||
username: 'admin',
|
||||
realName: '张管理员',
|
||||
role: 'admin',
|
||||
avatar: ''
|
||||
export const mockLoginApi = async (data: LoginRequest): Promise<LoginResponse> => {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
|
||||
if (data.username === 'admin' && data.password === '123456') {
|
||||
return {
|
||||
code: 200,
|
||||
message: '登录成功',
|
||||
data: {
|
||||
token: 'mock-jwt-token-' + Date.now(),
|
||||
userInfo: {
|
||||
id: 1,
|
||||
username: 'admin',
|
||||
realName: '张管理员',
|
||||
role: 'admin',
|
||||
avatar: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Error('用户名或密码错误')
|
||||
}
|
||||
} else {
|
||||
throw new Error('用户名或密码错误')
|
||||
}
|
||||
}
|
||||
@ -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,15 @@
|
||||
// src/api/modules/auth.ts
|
||||
import { api } from '../request'
|
||||
import type { LoginRequest, LoginVO, ResultVO } from '../types/auth'
|
||||
|
||||
class AuthApi {
|
||||
/**
|
||||
* 用户登录
|
||||
*/
|
||||
async login(data: LoginRequest): Promise<ResultVO<LoginVO>> {
|
||||
console.log('🚀 调用登录接口:', data)
|
||||
return api.post<ResultVO<LoginVO>>('/api/common/login', data)
|
||||
}
|
||||
}
|
||||
|
||||
export const authApi = new AuthApi()
|
||||
@ -1,22 +1,29 @@
|
||||
// 与后端 LoginRequest 对应的类型
|
||||
// src/api/types/auth.ts
|
||||
|
||||
// 登录请求参数 - 匹配后端的 LoginRequest
|
||||
export interface LoginRequest {
|
||||
username: string
|
||||
password: string
|
||||
rememberMe?: boolean
|
||||
username: string
|
||||
password: string
|
||||
userType: string // 添加这个属性
|
||||
rememberMe?: boolean
|
||||
}
|
||||
|
||||
// 通用响应结构
|
||||
export interface ResultVO<T = any> {
|
||||
code: number
|
||||
message: string
|
||||
data: T
|
||||
}
|
||||
|
||||
// 与后端 LoginVO 对应的类型
|
||||
// 登录响应数据 - 匹配后端的 LoginVO
|
||||
export interface LoginVO {
|
||||
code: number
|
||||
message: string
|
||||
data: {
|
||||
token: string
|
||||
userInfo: {
|
||||
id: number
|
||||
username: string
|
||||
realName: string
|
||||
role: string
|
||||
avatar?: string
|
||||
id: number
|
||||
username: string
|
||||
realName?: string // 根据后端字段调整
|
||||
role?: string // 根据后端字段调整
|
||||
userType?: string // 添加这个字段
|
||||
avatar?: string
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,33 +1,251 @@
|
||||
// src/router/index.ts
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import { createRouter, createWebHistory, type NavigationGuardNext, type RouteLocationNormalized } from 'vue-router'
|
||||
import LoginView from '../views/LoginView.vue'
|
||||
import MainLayout from '../components/layout/MainLayout.vue' // 导入布局组件
|
||||
import MainLayout from '../components/layout/MainLayout.vue'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
name: 'login',
|
||||
component: LoginView
|
||||
},
|
||||
{
|
||||
path: '/home',
|
||||
component: MainLayout, // 使用 MainLayout 作为布局
|
||||
children: [
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: [
|
||||
{
|
||||
path: '',
|
||||
name: 'home',
|
||||
component: () => import('../views/Dashboard.vue') // Dashboard 作为子路由
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/about',
|
||||
name: 'about',
|
||||
component: () => import('../views/AboutView.vue')
|
||||
},
|
||||
],
|
||||
path: '/',
|
||||
name: 'login',
|
||||
component: LoginView,
|
||||
meta: {
|
||||
title: '登录',
|
||||
requiresAuth: false // 不需要认证
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/home',
|
||||
component: MainLayout,
|
||||
meta: {
|
||||
requiresAuth: true // 需要认证
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'home',
|
||||
component: () => import('../views/Dashboard.vue'),
|
||||
meta: {
|
||||
title: '首页'
|
||||
}
|
||||
},
|
||||
// 设备监控相关路由
|
||||
{
|
||||
path: 'equipment',
|
||||
name: 'equipment',
|
||||
component: () => import('../views/equipment/EquipmentView.vue'),
|
||||
meta: {
|
||||
title: '设备监控'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'equipment/water-maker',
|
||||
name: 'water-maker',
|
||||
component: () => import('../views/equipment/WaterMaker.vue'),
|
||||
meta: {
|
||||
title: '制水设备'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'equipment/water-supplier',
|
||||
name: 'water-supplier',
|
||||
component: () => import('../views/equipment/WaterSupplier.vue'),
|
||||
meta: {
|
||||
title: '供水设备'
|
||||
}
|
||||
},
|
||||
// 工单管理相关路由
|
||||
{
|
||||
path: 'work-order',
|
||||
name: 'work-order',
|
||||
component: () => import('../views/workorder/WorkOrderView.vue'),
|
||||
meta: {
|
||||
title: '工单管理'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'work-order/pending',
|
||||
name: 'work-order-pending',
|
||||
component: () => import('../views/workorder/Pending.vue'),
|
||||
meta: {
|
||||
title: '待处理工单'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'work-order/timeout',
|
||||
name: 'work-order-timeout',
|
||||
component: () => import('../views/workorder/Timeout.vue'),
|
||||
meta: {
|
||||
title: '超时工单'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'work-order/processing',
|
||||
name: 'work-order-processing',
|
||||
component: () => import('../views/workorder/Processing.vue'),
|
||||
meta: {
|
||||
title: '处理中工单'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'work-order/review',
|
||||
name: 'work-order-review',
|
||||
component: () => import('../views/workorder/Review.vue'),
|
||||
meta: {
|
||||
title: '待审核工单'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'work-order/review/:id',
|
||||
name: 'ReviewDetail',
|
||||
component: () => import('../views/workorder/ReviewDetail.vue'),
|
||||
meta: {
|
||||
title: '工单审核详情'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'work-order/completed',
|
||||
name: 'work-order-completed',
|
||||
component: () => import('../views/workorder/Completed.vue'),
|
||||
meta: {
|
||||
title: '已完成工单'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/home/work-order/completed/:id',
|
||||
name: 'CompletedDetail',
|
||||
component: () => import('@/views/workorder/CompletedDetail.vue'),
|
||||
meta: {
|
||||
title: '结单信息'
|
||||
}
|
||||
},
|
||||
// 人员管理相关路由
|
||||
{
|
||||
path: 'personnel/admin',
|
||||
name: 'personnel-admin',
|
||||
component: () => import('../views/personnel/Admin.vue'),
|
||||
meta: {
|
||||
title: '管理员管理'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'personnel/maintenance',
|
||||
name: 'personnel-maintenance',
|
||||
component: () => import('../views/personnel/Maintenance.vue'),
|
||||
meta: {
|
||||
title: '运维人员管理'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'personnel/user',
|
||||
name: 'personnel-user',
|
||||
component: () => import('../views/personnel/User.vue'),
|
||||
meta: {
|
||||
title: '用户管理'
|
||||
}
|
||||
},
|
||||
// 片区相关路由
|
||||
{
|
||||
path: 'area/urban',
|
||||
name: 'area-urban',
|
||||
component: () => import('../views/area/Urban.vue'),
|
||||
meta: {
|
||||
title: '城市片区'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'equipment/water-maker/:id',
|
||||
name: 'water-maker-detail',
|
||||
component: () => import('../views/equipment/WaterMakerDetail.vue'),
|
||||
meta: {
|
||||
title: '制水设备详情'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'area/campus',
|
||||
name: 'area-campus',
|
||||
component: () => import('../views/area/Campus.vue'),
|
||||
meta: {
|
||||
title: '校园片区'
|
||||
}
|
||||
},
|
||||
// 个人信息路由
|
||||
{
|
||||
path: 'profile',
|
||||
name: 'profile',
|
||||
component: () => import('../views/Profile.vue'),
|
||||
meta: {
|
||||
title: '个人信息',
|
||||
requiresAuth: true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/about',
|
||||
name: 'about',
|
||||
component: () => import('../views/AboutView.vue'),
|
||||
meta: {
|
||||
title: '关于',
|
||||
requiresAuth: false
|
||||
}
|
||||
},
|
||||
/* // 404 页面
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
name: 'NotFound',
|
||||
component: () => import('../views/NotFound.vue'),
|
||||
meta: {
|
||||
title: '页面不存在',
|
||||
requiresAuth: false
|
||||
}
|
||||
}*/
|
||||
]
|
||||
})
|
||||
|
||||
// 路由守卫
|
||||
router.beforeEach(async (to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) => {
|
||||
// 设置页面标题
|
||||
const title = to.meta?.title as string || '校园直饮水管理系统'
|
||||
document.title = title
|
||||
|
||||
// 获取认证状态
|
||||
const authStore = useAuthStore()
|
||||
|
||||
// 初始化登录状态
|
||||
if (!authStore.isLoggedIn) {
|
||||
authStore.initialize()
|
||||
}
|
||||
|
||||
// 判断是否需要认证
|
||||
const requiresAuth = to.matched.some(record => record.meta.requiresAuth)
|
||||
|
||||
// 如果路由需要认证但用户未登录
|
||||
if (requiresAuth && !authStore.isLoggedIn) {
|
||||
// 重定向到登录页面,并保存当前想要访问的路径
|
||||
next({
|
||||
name: 'login',
|
||||
query: { redirect: to.fullPath }
|
||||
})
|
||||
}
|
||||
// 如果用户已登录但访问登录页面
|
||||
else if (to.name === 'login' && authStore.isLoggedIn) {
|
||||
// 检查是否有重定向路径
|
||||
const redirect = from.query.redirect as string || '/home'
|
||||
next(redirect)
|
||||
}
|
||||
// 其他情况正常放行
|
||||
else {
|
||||
next()
|
||||
}
|
||||
})
|
||||
|
||||
// 路由后置守卫 - 可在这里添加一些统计或清理工作
|
||||
router.afterEach((to: RouteLocationNormalized, from: RouteLocationNormalized) => {
|
||||
// 可以在这里添加页面访问统计等
|
||||
console.log(`路由跳转: ${from.fullPath} -> ${to.fullPath}`)
|
||||
})
|
||||
|
||||
export default router
|
||||
@ -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