This commit is contained in:
2026-06-06 23:54:11 +08:00
commit 33639129b1
58 changed files with 10309 additions and 0 deletions
+170
View File
@@ -0,0 +1,170 @@
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import {
CircleCheck,
Finished,
House,
Warning,
} from '@element-plus/icons-vue'
import AppShell from '@/components/AppShell.vue'
import { getUser } from '@/lib/auth'
import api from '@/lib/api'
import { ACTIVE_STATUSES, CLOSED_STATUSES } from '@/lib/status'
import type { OrderSummary } from '@/types'
const router = useRouter()
const user = computed(() => getUser())
const orders = ref<OrderSummary[]>([])
const loadingOrders = ref(false)
const totalOrders = computed(() => orders.value.length)
const isEmpty = computed(() => totalOrders.value === 0 && !loadingOrders.value)
const activeOrders = computed(() => orders.value.filter((order) => ACTIVE_STATUSES.has(order.status)).length)
const closedOrders = computed(() => orders.value.filter((order) => CLOSED_STATUSES.has(order.status)).length)
const currentOrders = computed(() => orders.value.filter((order) => ACTIVE_STATUSES.has(order.status)).slice(0, 3))
const recentOrders = computed(() => orders.value.slice(0, 4))
const serviceStats = computed(() => [
{ label: '累计工单', value: totalOrders.value, icon: Finished },
{ label: '处理中', value: activeOrders.value, icon: Warning },
{ label: '已办结', value: closedOrders.value, icon: CircleCheck },
])
async function loadOrders() {
loadingOrders.value = true
try {
const response = await api.get<OrderSummary[]>('/api/student/orders')
orders.value = response.data
} catch {
orders.value = []
} finally {
loadingOrders.value = false
}
}
onMounted(() => {
void loadOrders()
})
</script>
<template>
<AppShell accent="student">
<h1 class="page-title">后勤报修服务</h1>
<p class="page-subtitle">提交宿舍设施报修查看维修进度与处理结果</p>
<div class="portal-home">
<section class="portal-overview-strip">
<el-card class="portal-profile-card" shadow="never">
<div class="portal-profile-card__main">
<el-avatar :size="54">
<el-icon><House /></el-icon>
</el-avatar>
<div class="portal-profile-card__text">
<span>西海岸校区后勤报修</span>
<h1>{{ user?.display_name ?? '同学' }}需要报修从这里开始</h1>
<p>宿舍设施故障先提交报修已提交的问题在"我的工单"查看进度</p>
<div class="portal-profile-card__actions">
<button class="button" type="button" @click="router.push('/student/report')">提交报修</button>
<button class="text-button" type="button" @click="router.push('/student/orders')">查看我的工单</button>
</div>
</div>
</div>
</el-card>
</section>
<section v-if="isEmpty" class="welcome-card card">
<div class="welcome-card__content">
<h2>还没有报修记录</h2>
<p>遇到宿舍设施问题从提交第一份工单开始</p>
<div class="welcome-card__actions">
<button class="button" type="button" @click="router.push('/student/report')">提交报修</button>
</div>
</div>
</section>
<template v-else>
<section class="portal-stats-row" aria-label="报修统计">
<el-card
v-for="stat in serviceStats"
:key="stat.label"
class="portal-stat-card"
shadow="never"
>
<div class="portal-stat-card__icon">
<el-icon><component :is="stat.icon" /></el-icon>
</div>
<span>{{ stat.label }}</span>
<strong>{{ stat.value }}</strong>
</el-card>
</section>
<section class="portal-workspace">
<el-card class="portal-orders-card" shadow="never">
<template #header>
<div class="portal-card-header">
<div>
<span>当前进度</span>
<h2>需要关注的报修</h2>
</div>
<el-button type="primary" plain @click="router.push('/student/orders')">全部工单</el-button>
</div>
</template>
<el-skeleton v-if="loadingOrders" :rows="4" animated />
<el-empty v-else-if="currentOrders.length === 0" description="当前没有未办结工单" />
<el-table
v-else
class="portal-order-table"
:data="currentOrders"
:show-header="false"
@row-click="(order: OrderSummary) => router.push(`/student/orders/${order.id}`)"
>
<el-table-column min-width="220">
<template #default="{ row }">
<div class="portal-order-main">
<strong>{{ row.category }}</strong>
<span>{{ row.building }} {{ row.room }} · {{ row.order_no }}</span>
</div>
</template>
</el-table-column>
<el-table-column min-width="128">
<template #default="{ row }">
<el-tag effect="light" type="primary">{{ row.status }}</el-tag>
</template>
</el-table-column>
<el-table-column min-width="220">
<template #default="{ row }">
<span class="portal-muted">{{ row.assignee_name || '等待分配' }} / {{ row.expected_repair_time || '时间待定' }}</span>
</template>
</el-table-column>
</el-table>
</el-card>
<el-card class="portal-recent-card" shadow="never">
<template #header>
<div class="portal-card-header">
<div>
<span>最近记录</span>
<h2>报修记录</h2>
</div>
</div>
</template>
<el-empty v-if="recentOrders.length === 0" description="暂无报修记录" />
<div v-else class="portal-recent-list">
<button
v-for="order in recentOrders"
:key="order.id"
type="button"
@click="router.push(`/student/orders/${order.id}`)"
>
<span>{{ order.category }}</span>
<el-tag size="small" type="info">{{ order.status }}</el-tag>
</button>
</div>
</el-card>
</section>
</template>
</div>
</AppShell>
</template>