|
| 1 | +<script setup lang="ts"> |
| 2 | +import { format, formatDistanceToNow, parse } from 'date-fns' |
| 3 | +import { zhCN } from 'date-fns/locale' |
| 4 | +import { LoaderCircle } from 'lucide-vue-next' |
| 5 | +import { VueElement, onMounted } from 'vue' |
| 6 | +import { enums } from '~/components/custom/enum2str' |
| 7 | +import { useToast } from '@/components/ui/toast/use-toast' |
| 8 | +import Toaster from '@/components/ui/toast/Toaster.vue' |
| 9 | +
|
| 10 | +definePageMeta({ |
| 11 | + middleware: ['auth'], |
| 12 | +}) |
| 13 | +
|
| 14 | +const { toast } = useToast() |
| 15 | +
|
| 16 | +enum Statuses { |
| 17 | + LOADING, |
| 18 | + ERROR, |
| 19 | + READY, |
| 20 | +} |
| 21 | +
|
| 22 | +const status = ref(Statuses.LOADING) |
| 23 | +let content: any[] = [] |
| 24 | +
|
| 25 | +async function load() { |
| 26 | + status.value = Statuses.LOADING |
| 27 | + try { |
| 28 | + const data = await $fetch('/api/reservation/all-admin') |
| 29 | + if (!data) { |
| 30 | + status.value = Statuses.ERROR |
| 31 | + } |
| 32 | + else { |
| 33 | + status.value = Statuses.READY |
| 34 | + const response = JSON.parse(data) |
| 35 | + content = response.data |
| 36 | + } |
| 37 | + } |
| 38 | + catch (error) { |
| 39 | + status.value = Statuses.ERROR |
| 40 | + } |
| 41 | +} |
| 42 | +
|
| 43 | +const dlgOpen = ref(false) |
| 44 | +const alertContent = { |
| 45 | + title: '是否确认操作?', |
| 46 | + message: '', |
| 47 | + id: -1, |
| 48 | + action: 'UNKNOWN', |
| 49 | +} |
| 50 | +const managePending = ref(false) |
| 51 | +
|
| 52 | +function confirmManage(id: number, action: string, currentStatus = '') { |
| 53 | + if (action === 'DELALL') { |
| 54 | + alertContent.message = '将会删除全部预约记录,此操作不可撤销。' |
| 55 | + } |
| 56 | + else { |
| 57 | + alertContent.message = `将对 预约#${id} 执行以下操作: ` |
| 58 | + if (action === 'DELETE') { |
| 59 | + alertContent.message += '撤销' |
| 60 | + } |
| 61 | + } |
| 62 | + alertContent.id = id |
| 63 | + alertContent.action = action |
| 64 | + dlgOpen.value = true |
| 65 | +} |
| 66 | +
|
| 67 | +async function manage() { |
| 68 | + managePending.value = true |
| 69 | + try { |
| 70 | + const data = await $fetch('/api/reservation/manage', { |
| 71 | + query: { |
| 72 | + id: alertContent.id, |
| 73 | + action: alertContent.action, |
| 74 | + admin: true, |
| 75 | + key: '', |
| 76 | + }, |
| 77 | + }) |
| 78 | + await load() |
| 79 | + } |
| 80 | + catch (error: any) { |
| 81 | + toast({ |
| 82 | + description: error.data.message, |
| 83 | + variant: 'destructive', |
| 84 | + }) |
| 85 | + } |
| 86 | + managePending.value = false |
| 87 | + dlgOpen.value = false |
| 88 | +} |
| 89 | +
|
| 90 | +onMounted(async () => { |
| 91 | + await load() |
| 92 | +}) |
| 93 | +</script> |
| 94 | + |
| 95 | +<template> |
| 96 | + <Alert variant="destructive"> |
| 97 | + <AlertDescription> |
| 98 | + 警告:这是一个管理员页面。此页面中的所有操作均有最高权限,你可以删除任何存在的记录。进行操作前务必再次确认,操作不可撤销! |
| 99 | + </AlertDescription> |
| 100 | + </Alert> |
| 101 | + <AlertDialog v-model:open="dlgOpen"> |
| 102 | + <AlertDialogContent> |
| 103 | + <AlertDialogHeader> |
| 104 | + <AlertDialogTitle>{{ alertContent.title }}</AlertDialogTitle> |
| 105 | + <AlertDialogDescription>{{ alertContent.message }}</AlertDialogDescription> |
| 106 | + </AlertDialogHeader> |
| 107 | + <AlertDialogFooter> |
| 108 | + <AlertDialogCancel :disabled="managePending"> |
| 109 | + 取消 |
| 110 | + </AlertDialogCancel> |
| 111 | + <Button :disabled="managePending" @click="manage()"> |
| 112 | + <LoaderCircle v-if="managePending" class="animate-spin mr-2" /> |
| 113 | + <span v-if="!managePending">确认</span> |
| 114 | + <span v-if="managePending">处理中...</span> |
| 115 | + </Button> |
| 116 | + </AlertDialogFooter> |
| 117 | + </AlertDialogContent> |
| 118 | + </AlertDialog> |
| 119 | + <Table class="mt-4"> |
| 120 | + <TableHeader> |
| 121 | + <TableRow> |
| 122 | + <TableHead class="w-16"> |
| 123 | + 编号 |
| 124 | + </TableHead> |
| 125 | + <TableHead>提交时间</TableHead> |
| 126 | + <TableHead>申请者</TableHead> |
| 127 | + <TableHead>社团</TableHead> |
| 128 | + <TableHead>日期</TableHead> |
| 129 | + <TableHead>时间</TableHead> |
| 130 | + <TableHead>教室</TableHead> |
| 131 | + <TableHead>备注</TableHead> |
| 132 | + <TableHead> |
| 133 | + <Skeleton v-if="status === Statuses.LOADING" class="h-5 my-2" /> |
| 134 | + <!-- TODO: This is very dangerous and its behavior must be changed in the future --> |
| 135 | + <Button v-if="status === Statuses.READY" variant="link" class="p-0 text-red-500" @click="confirmManage(-1, 'DELALL')"> |
| 136 | + 清空全部 |
| 137 | + </Button> |
| 138 | + </TableHead> |
| 139 | + </TableRow> |
| 140 | + </TableHeader> |
| 141 | + <TableBody v-if="status === Statuses.LOADING"> |
| 142 | + <TableRow v-for="i in 5" :key="i"> |
| 143 | + <TableCell v-for="j in 9" :key="j"> |
| 144 | + <Skeleton class="h-5 my-2" /> |
| 145 | + </TableCell> |
| 146 | + </TableRow> |
| 147 | + </TableBody> |
| 148 | + <TableBody v-if="status === Statuses.READY"> |
| 149 | + <TableRow v-for="record in content" :key="record.id"> |
| 150 | + <TableCell class="text-muted-foreground"> |
| 151 | + #{{ record.id }} |
| 152 | + </TableCell> |
| 153 | + <TableCell>{{ formatDistanceToNow(new Date(record.creationTimestamp), { locale: zhCN, addSuffix: true }) }}</TableCell> |
| 154 | + <TableCell>{{ record.user.name }}</TableCell> |
| 155 | + <TableCell>{{ record.club.name.zh }}</TableCell> |
| 156 | + <TableCell>{{ enums.days.map[record.day] }}</TableCell> |
| 157 | + <TableCell>{{ enums.periods.map[record.period] }}</TableCell> |
| 158 | + <TableCell>{{ record.classroom.name }}</TableCell> |
| 159 | + <TableCell> |
| 160 | + {{ record.note ? '' : '–' }} |
| 161 | + <HoverCard v-if="record.note"> |
| 162 | + <HoverCardTrigger> |
| 163 | + <Button variant="link" class="text-blue-500"> |
| 164 | + 查看 |
| 165 | + </Button> |
| 166 | + </HoverCardTrigger> |
| 167 | + <HoverCardContent> |
| 168 | + {{ record.note }} |
| 169 | + </HoverCardContent> |
| 170 | + </HoverCard> |
| 171 | + </TableCell> |
| 172 | + <TableCell class="text-left"> |
| 173 | + <div v-if="true" class="text-red-500"> |
| 174 | + <Button variant="link" class="p-0 text-red-500" @click="confirmManage(record.id, 'DELETE')"> |
| 175 | + 撤销 |
| 176 | + </Button> |
| 177 | + </div> |
| 178 | + </TableCell> |
| 179 | + </TableRow> |
| 180 | + </TableBody> |
| 181 | + </Table> |
| 182 | + <div v-if="status === Statuses.READY && !content.length" class="w-1/3 my-2 mx-auto text-center"> |
| 183 | + <Alert> |
| 184 | + 暂无记录 |
| 185 | + </Alert> |
| 186 | + </div> |
| 187 | + <div v-if="status === Statuses.ERROR" class="w-1/3 my-2 mx-auto text-center"> |
| 188 | + <Alert variant="destructive"> |
| 189 | + 加载失败 |
| 190 | + </Alert> |
| 191 | + </div> |
| 192 | + <Toaster /> |
| 193 | +</template> |
0 commit comments