去掉管理员的状态

pull/174/head
ZHW 1 week ago
parent 76cd37aa1a
commit 230a58494f

@ -4,28 +4,107 @@
<h1 class="system-title">校园矿化水系统</h1>
</div>
<div class="header-right">
<div class="user-info" @click="goToProfile">
<div class="user-avatar">
<img :src="userAvatar" alt="用户头像" />
<!-- 用户信息下拉菜单 -->
<div class="user-dropdown">
<div class="user-info" @click="toggleDropdown">
<div class="user-avatar">
<img :src="userAvatar" alt="用户头像" />
</div>
<span class="user-name">{{ currentUser?.username || '未登录' }}</span>
<div class="dropdown-arrow"></div>
</div>
<!-- 下拉菜单 -->
<div v-if="showDropdown" class="dropdown-menu" @click="closeDropdown">
<div class="user-menu-header">
<div class="user-menu-avatar">
<img :src="userAvatar" alt="用户头像" />
</div>
<div class="user-menu-info">
<div class="user-menu-name">{{ currentUser?.username || '未登录' }}</div>
<div class="user-menu-role">{{ currentUser?.role || '普通用户' }}</div>
</div>
</div>
<div class="dropdown-divider"></div>
<div class="dropdown-item" @click="switchUser">
<i class="icon-switch"></i>
<span>切换用户</span>
</div>
<div class="dropdown-item" @click="logout">
<i class="icon-logout"></i>
<span>退出登录</span>
</div>
</div>
<span class="user-name">张管理员</span>
<div class="dropdown-arrow"></div>
</div>
</div>
</header>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
const userAvatar = ref('images/用户界面/u106.jpg') //
// 使
const userAvatar = ref('https://ui-avatars.com/api/?name=User&background=667eea&color=fff&size=36')
const router = useRouter()
const authStore = useAuthStore()
const showDropdown = ref(false)
//
const currentUser = computed(() => {
const user = authStore.userInfo
if (user) {
// URL
const name = user.realName || user.username || 'User'
userAvatar.value = `https://ui-avatars.com/api/?name=${encodeURIComponent(name)}&background=667eea&color=fff&size=36`
}
return user
})
//
const toggleDropdown = (event: Event) => {
event.stopPropagation()
showDropdown.value = !showDropdown.value
}
//
const closeDropdown = (event: Event) => {
event.stopPropagation()
//
}
//
const goToProfile = () => {
router.push('/home/profile')
//
const handleClickOutside = (event: Event) => {
const target = event.target as HTMLElement
if (!target.closest('.user-dropdown')) {
showDropdown.value = false
}
}
//
const switchUser = () => {
showDropdown.value = false
authStore.logout() //
router.push('/')
}
// 退
const logout = () => {
showDropdown.value = false
authStore.logout()
router.push('/')
}
//
onMounted(() => {
document.addEventListener('click', handleClickOutside)
})
//
onUnmounted(() => {
document.removeEventListener('click', handleClickOutside)
})
</script>
<style scoped>
@ -39,6 +118,7 @@ const goToProfile = () => {
padding: 0 30px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
z-index: 1000;
position: relative;
}
.system-title {
@ -47,6 +127,11 @@ const goToProfile = () => {
margin: 0;
}
.user-dropdown {
position: relative;
display: inline-block;
}
.user-info {
display: flex;
align-items: center;
@ -84,4 +169,89 @@ const goToProfile = () => {
font-size: 12px;
opacity: 0.8;
}
</style>
.dropdown-menu {
position: absolute;
top: 100%;
right: 0;
background: white;
border-radius: 8px;
box-shadow: 0 6px 16px 0 rgba(0, 0, 0, 0.12), 0 0 0 1px rgba(0, 0, 0, 0.04);
min-width: 240px;
z-index: 1001;
margin-top: 8px;
overflow: hidden;
}
.user-menu-header {
padding: 16px;
display: flex;
align-items: center;
gap: 12px;
border-bottom: 1px solid #f0f0f0;
}
.user-menu-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: #f0f0f0;
overflow: hidden;
}
.user-menu-avatar img {
width: 100%;
height: 100%;
object-fit: cover;
}
.user-menu-info {
flex: 1;
}
.user-menu-name {
font-weight: 500;
color: #333;
margin-bottom: 4px;
}
.user-menu-role {
font-size: 12px;
color: #666;
}
.dropdown-divider {
height: 1px;
background: #f0f0f0;
margin: 4px 0;
}
.dropdown-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
cursor: pointer;
transition: background-color 0.3s;
color: #333;
font-size: 14px;
}
.dropdown-item:hover {
background: #f5f5f5;
}
.dropdown-item i {
width: 16px;
height: 16px;
display: inline-block;
}
.icon-switch::before {
content: "🔄";
}
.icon-logout::before {
content: "🚪";
}
</style>

@ -44,7 +44,6 @@
<input type="checkbox" v-model="loginForm.rememberMe" />
<span class="checkbox-text">记住我</span>
</label>
<a href="#" class="forgot-password">忘记密码</a>
</div>
<button type="submit" class="login-button" :disabled="loading">

@ -1,4 +1,3 @@
<!-- src/views/personnel/MaintenanceRecord.vue -->
<template>
<div class="record-page">
<!-- 页面标题和面包屑 -->
@ -8,12 +7,11 @@
</div>
<!-- 返回按钮 -->
<div class="back-button">
<button @click="goBack" class="btn-back">
返回维修人员列表
</button>
</div>
<div class="back-button">
<button @click="goBack" class="btn-back">
返回维修人员列表
</button>
</div>
<!-- 维修人员信息 -->
<div class="repairman-info card">
@ -105,7 +103,7 @@
</td>
<td>{{ formatDate(order.createdTime) }}</td>
<td class="operation-buttons">
<button class="btn-view" @click="viewOrderDetail(order.orderId)">
<button class="btn-view" @click="showOrderDetail(order)">
查看详情
</button>
</td>
@ -137,6 +135,71 @@
下一页
</button>
</div>
<!-- 工单详情弹窗 -->
<div v-if="showDetailModal" class="modal-overlay" @click="closeDetailModal">
<div class="modal-content" @click.stop>
<div class="modal-header">
<h3>工单详情</h3>
<button class="close-btn" @click="closeDetailModal">×</button>
</div>
<div class="modal-body">
<div class="order-detail-info">
<div class="info-row">
<span class="info-label">工单号</span>
<span>{{ currentOrder.orderId }}</span>
</div>
<div class="info-row">
<span class="info-label">设备ID</span>
<span>{{ currentOrder.deviceId }}</span>
</div>
<div class="info-row">
<span class="info-label">片区</span>
<span>{{ currentOrder.areaId }}</span>
</div>
<div class="info-row">
<span class="info-label">工单类型</span>
<span>{{ formatOrderType(currentOrder.orderType) }}</span>
</div>
<div class="info-row">
<span class="info-label">状态</span>
<span :class="`status-tag ${currentOrder.status}`">
{{ formatOrderStatus(currentOrder.status) }}
</span>
</div>
<div class="info-row">
<span class="info-label">创建时间</span>
<span>{{ formatDate(currentOrder.createdTime) }}</span>
</div>
<div class="info-row" v-if="currentOrder.grabbedTime">
<span class="info-label">抢单时间</span>
<span>{{ formatDate(currentOrder.grabbedTime) }}</span>
</div>
<div class="info-row" v-if="currentOrder.completedTime">
<span class="info-label">完成时间</span>
<span>{{ formatDate(currentOrder.completedTime) }}</span>
</div>
<div class="info-row">
<span class="info-label">问题描述</span>
<div class="description-content">{{ currentOrder.description }}</div>
</div>
<div class="info-row" v-if="currentOrder.dealNote">
<span class="info-label">处理结果</span>
<div class="deal-note-content">{{ currentOrder.dealNote }}</div>
</div>
<div class="info-row" v-if="currentOrder.imgUrl">
<span class="info-label">处理图片</span>
<div class="image-container">
<img :src="currentOrder.imgUrl" alt="维修图片" class="work-image">
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn-close" @click="closeDetailModal"></button>
</div>
</div>
</div>
</div>
</template>
@ -146,7 +209,7 @@ import { useRoute, useRouter } from 'vue-router'
import { request } from '@/api/request'
import { useAuthStore } from '@/stores/auth'
import type { WorkOrder } from '@/api/types/workorder'
import type {ResultVO} from "@/api/types/auth";
import type {ResultVO} from "@/api/types/auth"
//
interface RepairmanInfo {
@ -165,10 +228,11 @@ interface MaintenanceStaff {
status: RepairmanStatus
}
//
type OrderStatus = 'pending' | 'processing' | 'reviewing' | 'completed' | 'timeout'
type RepairmanStatus = 'idle' | 'busy' | 'vacation'
type OrderType = 'repair' | 'maintenance' | 'inspection'
type OrderPriority = 'low' | 'medium' | 'high' | 'urgent'
const route = useRoute()
const router = useRouter()
@ -189,6 +253,28 @@ const currentPage = ref(1)
const pageSize = 10
const loading = ref(false)
//
const showDetailModal = ref(false)
const currentOrder = ref<WorkOrder>({
orderId: '',
deviceId: '',
areaId: '',
orderType: 'repair',
description: '',
priority: 'medium',
status: 'pending',
assignedRepairmanId: '',
createdTime: undefined,
grabbedTime: undefined,
deadline: undefined,
completedTime: undefined,
dealNote: '',
imgUrl: '',
createdBy: '',
updatedTime: undefined,
alertId: undefined
})
//
const fetchRepairmanData = async () => {
loading.value = true
@ -262,7 +348,6 @@ const fetchRepairmanData = async () => {
}
}
//
const processingOrders = computed(() => {
return allOrders.value.filter(order =>
@ -319,15 +404,36 @@ const getStatusText = (status: RepairmanStatus): string => {
//
const formatOrderStatus = (status: OrderStatus): string => {
const statusMap: Record<OrderStatus, string> = {
'pending': '待抢单',
'pending': '待处理',
'processing': '处理中',
'reviewing': '待审核',
'completed': '已完成',
'timeout': '超时未抢'
'timeout': '超时'
}
return statusMap[status] || status
}
//
const formatOrderType = (type: OrderType): string => {
const typeMap: Record<OrderType, string> = {
'repair': '维修',
'maintenance': '保养',
'inspection': '巡检'
}
return typeMap[type] || type
}
//
const formatOrderPriority = (priority: OrderPriority): string => {
const priorityMap: Record<OrderPriority, string> = {
'low': '低',
'medium': '中',
'high': '高',
'urgent': '紧急'
}
return priorityMap[priority] || priority
}
//
const formatDate = (dateString?: string): string => {
if (!dateString) return '-'
@ -335,10 +441,35 @@ const formatDate = (dateString?: string): string => {
return date.toLocaleString('zh-CN')
}
//
const viewOrderDetail = (orderId: string) => {
//
router.push(`/home/work-order/detail/${orderId}`)
//
const showOrderDetail = (order: WorkOrder) => {
currentOrder.value = { ...order } //
showDetailModal.value = true
}
//
const closeDetailModal = () => {
showDetailModal.value = false
//
currentOrder.value = {
orderId: '',
deviceId: '',
areaId: '',
orderType: 'repair',
description: '',
priority: 'medium',
status: 'pending',
assignedRepairmanId: '',
createdTime: undefined,
grabbedTime: undefined,
deadline: undefined,
completedTime: undefined,
dealNote: '',
imgUrl: '',
createdBy: '',
updatedTime: undefined,
alertId: undefined
}
}
//
@ -346,9 +477,7 @@ const goBack = () => {
router.back()
}
//
// MaintenanceRecord.vue
onMounted(() => {
const repairmanId = route.params.id as string
if (!repairmanId) {
@ -359,7 +488,6 @@ onMounted(() => {
fetchRepairmanData()
})
</script>
<style scoped>
@ -540,6 +668,183 @@ onMounted(() => {
cursor: not-allowed;
}
/* 弹窗样式 */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.modal-content {
background: white;
border-radius: 8px;
width: 600px;
max-width: 90%;
max-height: 80vh;
overflow-y: auto;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
}
.modal-header {
padding: 16px 20px;
border-bottom: 1px solid #eee;
display: flex;
justify-content: space-between;
align-items: center;
}
.modal-header h3 {
margin: 0;
font-size: 18px;
color: #333;
}
.close-btn {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #666;
padding: 4px;
}
.close-btn:hover {
color: #333;
}
.modal-body {
padding: 20px;
flex: 1;
overflow-y: auto;
}
.modal-footer {
padding: 16px 20px;
border-top: 1px solid #eee;
text-align: right;
}
.btn-close {
background: #f0f0f0;
color: #333;
border: 1px solid #ddd;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.btn-close:hover {
background: #e0e0e0;
}
.order-detail-info {
display: flex;
flex-direction: column;
gap: 12px;
}
.info-row {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
padding: 8px 0;
border-bottom: 1px solid #f5f5f5;
}
.info-label {
font-weight: 500;
color: #666;
min-width: 100px;
margin-right: 12px;
flex-shrink: 0;
}
.description-content,
.deal-note-content {
flex: 1;
padding: 8px;
background-color: #fafafa;
border-radius: 4px;
line-height: 1.6;
white-space: pre-wrap;
}
.priority-tag,
.status-tag {
display: inline-block;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
}
.priority-tag.low {
background-color: #f6ffed;
color: #52c41a;
}
.priority-tag.medium {
background-color: #e6f7ff;
color: #1890ff;
}
.priority-tag.high {
background-color: #fffbe6;
color: #d48806;
}
.priority-tag.urgent {
background-color: #fff2f0;
color: #ff4d4f;
}
.status-tag.pending {
background-color: #e6f7ff;
color: #1890ff;
}
.status-tag.processing {
background-color: #fffbe6;
color: #d48806;
}
.status-tag.reviewing {
background-color: #f6ffed;
color: #52c41a;
}
.status-tag.completed {
background-color: #f0f0f0;
color: #8c8c8c;
}
.status-tag.timeout {
background-color: #fff2f0;
color: #ff4d4f;
}
.image-container {
margin-top: 8px;
flex: 1;
}
.work-image {
max-width: 100%;
max-height: 200px;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
/* 响应式调整 */
@media (max-width: 768px) {
.order-stats {
@ -556,21 +861,33 @@ onMounted(() => {
}
.btn-back {
background: #f0f0f0;
color: #333;
border: 1px solid #ddd;
padding: 8px 16px;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
transition: all 0.3s;
}
background: #f0f0f0;
color: #333;
border: 1px solid #ddd;
padding: 8px 16px;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
transition: all 0.3s;
}
.btn-back:hover {
background: #e0e0e0;
border-color: #bbb;
}
.btn-back:hover {
background: #e0e0e0;
border-color: #bbb;
}
.modal-content {
width: 95%;
margin: 20px;
}
.info-row {
flex-direction: column;
}
.info-label {
min-width: auto;
margin-bottom: 4px;
}
}
</style>

Loading…
Cancel
Save