Files
hci_work/frontend/src/views/StudentHomeView.vue
T
2026-06-06 23:54:11 +08:00

171 lines
6.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>