This commit is contained in:
biss committed 2026-08-18 21:26:16 +08:00
1 parent f106973d62
commit cf64d836d0
1 file changed
+12 -4
+12 -4
View File
@@ -24,6 +24,8 @@ const equipmentVisible = ref(false)
const equipmentClassroomId = ref('') const equipmentClassroomId = ref('')
const equipmentRows = ref<any[]>([]) const equipmentRows = ref<any[]>([])
const equipmentForm = ref({ name: '', category: 1, notes: '' }) const equipmentForm = ref({ name: '', category: 1, notes: '' })
const detailVisible = ref(false)
const detail = ref<any>()
const isManager = computed(() => auth.user?.roles.some(role => ['SuperAdmin', 'AcademicAdmin', 'MaintenanceAdmin'].includes(role)) ?? false) const isManager = computed(() => auth.user?.roles.some(role => ['SuperAdmin', 'AcademicAdmin', 'MaintenanceAdmin'].includes(role)) ?? false)
async function load() { async function load() {
@@ -46,8 +48,13 @@ async function showQr(ticket: any) { qrUrl.value = `${location.origin}${import.m
async function showClassroomQr() { if (!qrClassroomId.value) return ElMessage.warning('请选择教室。'); await showQr({ classroomId: qrClassroomId.value }) } async function showClassroomQr() { if (!qrClassroomId.value) return ElMessage.warning('请选择教室。'); await showQr({ classroomId: qrClassroomId.value }) }
async function loadEquipment() { if (!equipmentClassroomId.value) return; equipmentRows.value = (await http.get(`/classroom-repairs/classrooms/${equipmentClassroomId.value}/equipment`)).data } async function loadEquipment() { if (!equipmentClassroomId.value) return; equipmentRows.value = (await http.get(`/classroom-repairs/classrooms/${equipmentClassroomId.value}/equipment`)).data }
async function addEquipment() { if (!equipmentForm.value.name) return ElMessage.warning('请填写设备名称。'); await http.post(`/classroom-repairs/classrooms/${equipmentClassroomId.value}/equipment`, equipmentForm.value); equipmentForm.value = { name: '', category: 1, notes: '' }; await loadEquipment() } async function addEquipment() { if (!equipmentForm.value.name) return ElMessage.warning('请填写设备名称。'); await http.post(`/classroom-repairs/classrooms/${equipmentClassroomId.value}/equipment`, equipmentForm.value); equipmentForm.value = { name: '', category: 1, notes: '' }; await loadEquipment() }
function statusName(value: number) { return ['待分派', '已分派', '维修中', '已完成'][value - 1] } function statusValue(value: number | string) {
function statusType(value: number) { return value === 4 ? 'success' : value === 3 ? 'warning' : value === 2 ? 'primary' : 'info' } if (typeof value === 'number') return value
return ({ Submitted: 1, Assigned: 2, InProgress: 3, Completed: 4 } as Record<string, number>)[value] ?? 1
}
function statusName(value: number | string) { return ['待分派', '已分派', '维修中', '已完成'][statusValue(value) - 1] }
function statusType(value: number | string) { const normalized = statusValue(value); return normalized === 4 ? 'success' : normalized === 3 ? 'warning' : normalized === 2 ? 'primary' : 'info' }
function openDetail(ticket: any) { detail.value = ticket; detailVisible.value = true }
onMounted(load) onMounted(load)
</script> </script>
@@ -55,7 +62,8 @@ onMounted(load)
<section class="page-shell"> <section class="page-shell">
<div class="page-heading"><div><p class="eyebrow">FACILITY CARE</p><h1>教室设备与故障闭环</h1><p>扫码报修、维修分派、状态恢复与受影响教师提醒。</p></div><div class="heading-actions"><el-button v-if="isManager" @click="equipmentVisible = true">设备档案</el-button><el-button v-if="isManager" @click="qrDataUrl = ''; qrVisible = true">生成场地报修码</el-button><el-button @click="load">刷新</el-button></div></div> <div class="page-heading"><div><p class="eyebrow">FACILITY CARE</p><h1>教室设备与故障闭环</h1><p>扫码报修、维修分派、状态恢复与受影响教师提醒。</p></div><div class="heading-actions"><el-button v-if="isManager" @click="equipmentVisible = true">设备档案</el-button><el-button v-if="isManager" @click="qrDataUrl = ''; qrVisible = true">生成场地报修码</el-button><el-button @click="load">刷新</el-button></div></div>
<el-card shadow="never"><div class="toolbar"><el-select v-model="status" clearable placeholder="全部状态" @change="load"><el-option v-for="item in [1,2,3,4]" :key="item" :label="statusName(item)" :value="item" /></el-select></div> <el-card shadow="never"><div class="toolbar"><el-select v-model="status" clearable placeholder="全部状态" @change="load"><el-option v-for="item in [1,2,3,4]" :key="item" :label="statusName(item)" :value="item" /></el-select></div>
<el-table :data="tickets" v-loading="loading" stripe><el-table-column label="场地" min-width="160"><template #default="{ row }"><b>{{ row.buildingName }} · {{ row.classroomName }}</b><br><span>{{ row.equipmentName || row.faultItem }}</span></template></el-table-column><el-table-column prop="description" label="故障说明" min-width="240" show-overflow-tooltip /><el-table-column label="状态" width="100"><template #default="{ row }"><el-tag :type="statusType(row.status)">{{ statusName(row.status) }}</el-tag></template></el-table-column><el-table-column label="维修员" width="120"><template #default="{ row }">{{ row.assigneeName || '未分派' }}</template></el-table-column><el-table-column label="报修时间" width="170"><template #default="{ row }">{{ new Date(row.createdAt).toLocaleString() }}</template></el-table-column><el-table-column fixed="right" label="操作" width="220"><template #default="{ row }"><el-button link @click="showQr(row)">报修码</el-button><el-button v-if="isManager && row.status !== 4" link type="primary" @click="assign(row)">分派</el-button><el-button v-if="row.status === 2" link type="warning" @click="start(row)">开始</el-button><el-button v-if="row.status === 3" link type="success" @click="openComplete(row)">完成</el-button></template></el-table-column></el-table></el-card> <el-table :data="tickets" v-loading="loading" stripe @row-click="openDetail"><el-table-column label="场地" min-width="160"><template #default="{ row }"><b>{{ row.buildingName }} · {{ row.classroomName }}</b><br><span>{{ row.equipmentName || row.faultItem }}</span></template></el-table-column><el-table-column prop="description" label="故障说明" min-width="240" show-overflow-tooltip /><el-table-column label="状态" width="110"><template #default="{ row }"><el-tag :type="statusType(row.status)">{{ statusName(row.status) }}</el-tag></template></el-table-column><el-table-column label="维修员" width="120"><template #default="{ row }">{{ row.assigneeName || '未分派' }}</template></el-table-column><el-table-column label="报修时间" width="170"><template #default="{ row }">{{ new Date(row.createdAt).toLocaleString() }}</template></el-table-column><el-table-column fixed="right" label="操作" width="270"><template #default="{ row }"><el-button link @click.stop="openDetail(row)">详情</el-button><el-button link @click.stop="showQr(row)">报修码</el-button><el-button v-if="isManager && statusValue(row.status) !== 4" link type="primary" @click.stop="assign(row)">分派</el-button><el-button v-if="statusValue(row.status) === 2" link type="warning" @click.stop="start(row)">开始维修</el-button><el-button v-if="statusValue(row.status) === 3" link type="success" @click.stop="openComplete(row)">完成维修</el-button></template></el-table-column></el-table></el-card>
<el-dialog v-model="detailVisible" title="维修工单详情" width="min(680px, calc(100vw - 32px))"><template v-if="detail"><div class="ticket-state"><el-tag :type="statusType(detail.status)">{{ statusName(detail.status) }}</el-tag><span>{{ detail.buildingName }} · {{ detail.classroomName }}</span></div><el-descriptions :column="1" border><el-descriptions-item label="故障设备/项目">{{ detail.equipmentName || detail.faultItem }}</el-descriptions-item><el-descriptions-item label="详细说明">{{ detail.description }}</el-descriptions-item><el-descriptions-item label="报修人">{{ detail.reporterName }} · {{ detail.reporterContact }}</el-descriptions-item><el-descriptions-item label="维修员">{{ detail.assigneeName || '尚未分派' }}</el-descriptions-item><el-descriptions-item label="报修时间">{{ new Date(detail.createdAt).toLocaleString() }}</el-descriptions-item><el-descriptions-item v-if="detail.startedAt" label="开始维修">{{ new Date(detail.startedAt).toLocaleString() }}</el-descriptions-item><el-descriptions-item v-if="detail.completedAt" label="完成时间">{{ new Date(detail.completedAt).toLocaleString() }}</el-descriptions-item><el-descriptions-item v-if="detail.repairResult" label="维修结果">{{ detail.repairResult }}</el-descriptions-item></el-descriptions></template></el-dialog>
<el-dialog v-model="assignVisible" title="分派维修员" width="420px"><el-select v-model="assigneeId" placeholder="选择维修员" style="width:100%"><el-option v-for="worker in workers" :key="worker.id" :label="worker.displayName" :value="worker.id" /></el-select><template #footer><el-button @click="assignVisible=false">取消</el-button><el-button type="primary" @click="submitAssign">确认分派</el-button></template></el-dialog> <el-dialog v-model="assignVisible" title="分派维修员" width="420px"><el-select v-model="assigneeId" placeholder="选择维修员" style="width:100%"><el-option v-for="worker in workers" :key="worker.id" :label="worker.displayName" :value="worker.id" /></el-select><template #footer><el-button @click="assignVisible=false">取消</el-button><el-button type="primary" @click="submitAssign">确认分派</el-button></template></el-dialog>
<el-dialog v-model="completeVisible" title="填写维修结果" width="520px"><el-input v-model="result" type="textarea" :rows="5" maxlength="2000" show-word-limit placeholder="说明处理方式、已恢复项目或遗留事项。" /><template #footer><el-button @click="completeVisible=false">取消</el-button><el-button type="success" @click="submitComplete">完成工单</el-button></template></el-dialog> <el-dialog v-model="completeVisible" title="填写维修结果" width="520px"><el-input v-model="result" type="textarea" :rows="5" maxlength="2000" show-word-limit placeholder="说明处理方式、已恢复项目或遗留事项。" /><template #footer><el-button @click="completeVisible=false">取消</el-button><el-button type="success" @click="submitComplete">完成工单</el-button></template></el-dialog>
<el-dialog v-model="equipmentVisible" title="教室设备档案" width="620px"><el-select v-model="equipmentClassroomId" filterable placeholder="选择教室" style="width:100%" @change="loadEquipment"><el-option v-for="room in classrooms" :key="room.id" :label="`${room.buildingName} · ${room.name}`" :value="room.id" /></el-select><template v-if="equipmentClassroomId"><el-table :data="equipmentRows" size="small" class="equipment-table"><el-table-column prop="name" label="设备/设施"/><el-table-column label="可用状态" width="100"><template #default="{ row }"><el-tag :type="row.isAvailable ? 'success' : 'danger'">{{ row.isAvailable ? '可用' : '不可用' }}</el-tag></template></el-table-column><el-table-column prop="notes" label="备注"/></el-table><div class="equipment-add"><el-input v-model="equipmentForm.name" placeholder="新增:投影仪、幕布、电子白板、座位、水电暖、空调等"/><el-select v-model="equipmentForm.category"><el-option label="多媒体" :value="1"/><el-option label="课桌椅" :value="2"/><el-option label="水电暖" :value="3"/><el-option label="空调" :value="4"/><el-option label="其他" :value="5"/></el-select><el-button type="primary" @click="addEquipment">添加</el-button></div></template></el-dialog> <el-dialog v-model="equipmentVisible" title="教室设备档案" width="620px"><el-select v-model="equipmentClassroomId" filterable placeholder="选择教室" style="width:100%" @change="loadEquipment"><el-option v-for="room in classrooms" :key="room.id" :label="`${room.buildingName} · ${room.name}`" :value="room.id" /></el-select><template v-if="equipmentClassroomId"><el-table :data="equipmentRows" size="small" class="equipment-table"><el-table-column prop="name" label="设备/设施"/><el-table-column label="可用状态" width="100"><template #default="{ row }"><el-tag :type="row.isAvailable ? 'success' : 'danger'">{{ row.isAvailable ? '可用' : '不可用' }}</el-tag></template></el-table-column><el-table-column prop="notes" label="备注"/></el-table><div class="equipment-add"><el-input v-model="equipmentForm.name" placeholder="新增:投影仪、幕布、电子白板、座位、水电暖、空调等"/><el-select v-model="equipmentForm.category"><el-option label="多媒体" :value="1"/><el-option label="课桌椅" :value="2"/><el-option label="水电暖" :value="3"/><el-option label="空调" :value="4"/><el-option label="其他" :value="5"/></el-select><el-button type="primary" @click="addEquipment">添加</el-button></div></template></el-dialog>
@@ -64,5 +72,5 @@ onMounted(load)
</template> </template>
<style scoped> <style scoped>
.page-shell { padding: 24px; }.page-heading { display:flex; justify-content:space-between; gap:20px; align-items:start; margin-bottom:20px; }.heading-actions { display:flex; gap:10px; }.page-heading h1 { margin:4px 0; }.page-heading p { margin:0; color:#738399; }.eyebrow { color:#2b72c8 !important; font-size:12px; font-weight:800; letter-spacing:.11em; }.toolbar { margin-bottom:16px; }.toolbar .el-select { width:150px; }.qr { display:block; width:280px; margin:18px auto 0; }.qr-generate { width:100%; margin-top:14px; }.qr-hint { color:#728197; line-height:1.6; text-align:center; }.equipment-table { margin:16px 0; }.equipment-add { display:grid; grid-template-columns:1fr 110px auto; gap:10px; } @media(max-width:600px){.page-shell{padding:16px}.page-heading{display:block}.heading-actions{margin-top:14px}.equipment-add{grid-template-columns:1fr}} .page-shell { padding: 24px; }.page-heading { display:flex; justify-content:space-between; gap:20px; align-items:start; margin-bottom:20px; }.heading-actions { display:flex; gap:10px; }.page-heading h1 { margin:4px 0; }.page-heading p { margin:0; color:#738399; }.eyebrow { color:#2b72c8 !important; font-size:12px; font-weight:800; letter-spacing:.11em; }.toolbar { margin-bottom:16px; }.toolbar .el-select { width:150px; }.qr { display:block; width:280px; margin:18px auto 0; }.qr-generate { width:100%; margin-top:14px; }.qr-hint { color:#728197; line-height:1.6; text-align:center; }.equipment-table { margin:16px 0; }.equipment-add { display:grid; grid-template-columns:1fr 110px auto; gap:10px; }.ticket-state{display:flex;align-items:center;gap:12px;margin-bottom:18px;color:#274a6c;font-weight:700}.page-shell :deep(.el-table__row){cursor:pointer} @media(max-width:600px){.page-shell{padding:16px}.page-heading{display:block}.heading-actions{margin-top:14px}.equipment-add{grid-template-columns:1fr}}
</style> </style>