feat: add SingleRecord component for student maintenance with CRUD functionality
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
<template>
|
||||
<v-card border class="d-flex flex-column h-100 rounded-0" flat width="100%">
|
||||
<v-toolbar color="transparent" density="compact" flat>
|
||||
<v-btn :icon="mdiArrowLeft" size="small" variant="text" @click="$emit('close')" />
|
||||
<v-toolbar-title class="text-subtitle-1 font-weight-bold">
|
||||
{{ semester?.semesterName || '課程明細' }}
|
||||
</v-toolbar-title>
|
||||
</v-toolbar>
|
||||
|
||||
<v-divider />
|
||||
|
||||
<v-card-text class="pa-0 flex-grow-1 overflow-y-auto bg-grey-lighten-5">
|
||||
<div v-if="semester" class="pa-4 d-flex flex-column ga-4">
|
||||
<div class="d-flex flex-column ga-3">
|
||||
<v-card class="py-2 px-3" variant="outlined">
|
||||
<div class="text-caption text-medium-emphasis">學期平均</div>
|
||||
<div class="text-h6 font-weight-bold text-primary">{{ semester.average }}</div>
|
||||
</v-card>
|
||||
<v-card class="py-2 px-3" variant="outlined">
|
||||
<div class="text-caption text-medium-emphasis">班級排名</div>
|
||||
<div class="text-h6 font-weight-bold">{{ semester.rank }}</div>
|
||||
</v-card>
|
||||
<v-card class="py-2 px-3" variant="outlined">
|
||||
<div class="text-caption text-medium-emphasis">總學分</div>
|
||||
<div class="text-h6 font-weight-bold">{{ totalCredits }}</div>
|
||||
</v-card>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-center">
|
||||
<div>
|
||||
<div class="text-subtitle-2 font-weight-bold">課程列表</div>
|
||||
<div class="text-caption text-medium-emphasis">
|
||||
手機版改用卡片式維護,不使用扁平表格
|
||||
</div>
|
||||
</div>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
v-if="!isViewMode"
|
||||
color="primary"
|
||||
:disabled="isFormLocked"
|
||||
:prepend-icon="mdiPlus"
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="$emit('add-course', semester.id)"
|
||||
>
|
||||
加入課程
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<template v-if="semester.courses.length > 0">
|
||||
<v-card
|
||||
v-for="(course, idx) in semester.courses"
|
||||
:key="`${course.code}-${idx}`"
|
||||
class="pa-3"
|
||||
variant="outlined"
|
||||
>
|
||||
<template v-if="isViewMode">
|
||||
<div class="d-flex align-start justify-space-between ga-3">
|
||||
<div>
|
||||
<div class="text-body-1 font-weight-medium">{{ course.name }}</div>
|
||||
<div class="text-caption text-medium-emphasis">{{ course.code }}</div>
|
||||
</div>
|
||||
<v-chip
|
||||
:color="course.score < 60 ? 'error' : 'success'"
|
||||
size="small"
|
||||
variant="tonal"
|
||||
>
|
||||
{{ course.score }} 分
|
||||
</v-chip>
|
||||
</div>
|
||||
<div class="d-flex ga-4 mt-3 text-body-2">
|
||||
<div>學分 {{ course.credits }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<div class="d-flex align-center mb-3">
|
||||
<div class="text-subtitle-2 font-weight-bold">課程 {{ idx + 1 }}</div>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
color="error"
|
||||
:disabled="isFormLocked"
|
||||
:icon="mdiDelete"
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="$emit('delete-course', semester.id, idx)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-column ga-3">
|
||||
<v-text-field
|
||||
density="comfortable"
|
||||
:disabled="isFormLocked"
|
||||
hide-details
|
||||
label="課程名稱"
|
||||
:model-value="course.name"
|
||||
variant="outlined"
|
||||
@update:model-value="(value) => updateCourse(idx, { name: String(value) })"
|
||||
/>
|
||||
<v-text-field
|
||||
density="comfortable"
|
||||
:disabled="isFormLocked"
|
||||
hide-details
|
||||
label="代碼"
|
||||
:model-value="course.code"
|
||||
variant="outlined"
|
||||
@update:model-value="(value) => updateCourse(idx, { code: String(value) })"
|
||||
/>
|
||||
<v-text-field
|
||||
density="comfortable"
|
||||
:disabled="isFormLocked"
|
||||
hide-details
|
||||
label="學分"
|
||||
:model-value="course.credits"
|
||||
type="number"
|
||||
variant="outlined"
|
||||
@update:model-value="
|
||||
(value) => updateCourse(idx, { credits: Number(value) || 0 })
|
||||
"
|
||||
/>
|
||||
<v-text-field
|
||||
density="comfortable"
|
||||
:disabled="isFormLocked"
|
||||
hide-details
|
||||
label="分數"
|
||||
:model-value="course.score"
|
||||
type="number"
|
||||
variant="outlined"
|
||||
@update:model-value="(value) => updateCourse(idx, { score: Number(value) || 0 })"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<div v-else class="text-center text-medium-emphasis py-6 border border-dashed rounded">
|
||||
尚無課程資料
|
||||
</div>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { CourseRecord, SemesterRecord } from '@/stores/semesters'
|
||||
import { mdiArrowLeft, mdiDelete, mdiPlus } from '@mdi/js'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
semester: SemesterRecord | null
|
||||
isViewMode: boolean
|
||||
isFormLocked: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'close'): void
|
||||
(event: 'add-course', semesterId: number): void
|
||||
(
|
||||
event: 'update-course',
|
||||
semesterId: number,
|
||||
courseIndex: number,
|
||||
payload: Partial<CourseRecord>
|
||||
): void
|
||||
(event: 'delete-course', semesterId: number, courseIndex: number): void
|
||||
}>()
|
||||
|
||||
const totalCredits = computed(
|
||||
() => props.semester?.courses.reduce((sum, course) => sum + course.credits, 0) ?? 0
|
||||
)
|
||||
|
||||
function updateCourse(courseIndex: number, payload: Partial<CourseRecord>) {
|
||||
if (!props.semester) return
|
||||
emit('update-course', props.semester.id, courseIndex, payload)
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user