refactor: update row density to compact in various components for improved layout
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<v-card class="bg-surface mb-4" v-bind="$attrs">
|
||||
<v-card-text>
|
||||
<v-row dense>
|
||||
<v-row density="compact">
|
||||
<!-- Dynamic Search Fields -->
|
||||
<v-col
|
||||
v-for="field in visibleFields"
|
||||
@@ -10,7 +10,7 @@
|
||||
:lg="field.meta?.lg || field.lg"
|
||||
:md="field.meta?.md || field.md"
|
||||
>
|
||||
<v-row class="ma-0" dense>
|
||||
<v-row class="ma-0" density="compact">
|
||||
<v-col class="d-flex align-center justify-start justify-md-end" cols="12" md="4">
|
||||
<span class="text-body-1">{{ field.label }}</span>
|
||||
</v-col>
|
||||
@@ -132,11 +132,11 @@ for (const field of props.fields) {
|
||||
searchState[field.key] = field.type === 'select' ? null : ''
|
||||
}
|
||||
|
||||
function handleSearch () {
|
||||
function handleSearch() {
|
||||
emit('search', { ...searchState })
|
||||
}
|
||||
|
||||
function handleReset () {
|
||||
function handleReset() {
|
||||
// Reset all fields
|
||||
for (const field of props.fields) {
|
||||
searchState[field.key] = field.type === 'select' ? null : ''
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
{{ title }}
|
||||
</v-card-title>
|
||||
<v-card-text class="pa-4">
|
||||
<v-row dense>
|
||||
<v-row density="compact">
|
||||
<v-col v-for="(nav, i) in navs" :key="i" class="text-center mb-2" cols="4">
|
||||
<v-btn
|
||||
class="mb-1"
|
||||
|
||||
@@ -8,14 +8,16 @@
|
||||
</v-chip>
|
||||
<v-chip color="info" variant="tonal">筆數 {{ filteredStudents.length }}</v-chip>
|
||||
<v-spacer />
|
||||
<v-btn flat :prepend-icon="mdiMagnify" @click="isSearchVisible = !isSearchVisible">條件搜尋</v-btn>
|
||||
<v-btn flat :prepend-icon="mdiMagnify" @click="isSearchVisible = !isSearchVisible"
|
||||
>條件搜尋</v-btn
|
||||
>
|
||||
<v-switch v-model="isBulkEditEnabled" color="primary" hide-details label="啟用編輯" />
|
||||
</v-card-title>
|
||||
|
||||
<v-divider />
|
||||
|
||||
<v-card-text class="pb-0 pt-2">
|
||||
<v-row v-if="isSearchVisible" class="mb-2" dense>
|
||||
<v-row v-if="isSearchVisible" class="mb-2" density="compact">
|
||||
<v-col cols="12" md="3">
|
||||
<div class="text-body-1 text-medium-emphasis pl-2">學號</div>
|
||||
<v-text-field
|
||||
@@ -52,7 +54,7 @@
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row v-if="isBulkEditEnabled" align="center" class="mb-2 ga-1" dense>
|
||||
<v-row v-if="isBulkEditEnabled" align="center" class="mb-2 ga-1" density="compact">
|
||||
<v-btn
|
||||
:disabled="!hasSelectedRows"
|
||||
:prepend-icon="mdiDelete"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<v-row dense>
|
||||
<v-row density="compact">
|
||||
<v-col cols="12" md="3">
|
||||
<v-text-field
|
||||
id="field-studentId"
|
||||
|
||||
@@ -5,12 +5,22 @@
|
||||
<span class="text-h6">{{ title }}</span>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
:icon="mdAndUp ? false : mdiMagnify" :prepend-icon="mdAndUp ? mdiMagnify : undefined" size="small"
|
||||
:text="mdAndUp ? '搜尋條件' : false" variant="text" @click="$emit('toggle-search')">
|
||||
:icon="mdAndUp ? false : mdiMagnify"
|
||||
:prepend-icon="mdAndUp ? mdiMagnify : undefined"
|
||||
size="small"
|
||||
:text="mdAndUp ? '搜尋條件' : false"
|
||||
variant="text"
|
||||
@click="$emit('toggle-search')"
|
||||
>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="primary" :icon="mdAndUp ? false : mdiPlus" :prepend-icon="mdAndUp ? mdiPlus : undefined"
|
||||
size="small" :text="mdAndUp ? createLabel : false" @click="$emit('create')">
|
||||
color="primary"
|
||||
:icon="mdAndUp ? false : mdiPlus"
|
||||
:prepend-icon="mdAndUp ? mdiPlus : undefined"
|
||||
size="small"
|
||||
:text="mdAndUp ? createLabel : false"
|
||||
@click="$emit('create')"
|
||||
>
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
|
||||
@@ -18,7 +28,7 @@ color="primary" :icon="mdAndUp ? false : mdiPlus" :prepend-icon="mdAndUp ? mdiPl
|
||||
<template v-if="mdAndUp">
|
||||
<v-divider />
|
||||
<v-card-text v-show="searchPanelOpen" class="px-2 py-1">
|
||||
<v-row dense>
|
||||
<v-row density="compact">
|
||||
<slot name="search-fields" />
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
@@ -29,7 +39,11 @@ color="primary" :icon="mdAndUp ? false : mdiPlus" :prepend-icon="mdAndUp ? mdiPl
|
||||
</v-sheet>
|
||||
|
||||
<!-- 手機 / Tablet:Bottom Sheet -->
|
||||
<v-bottom-sheet v-if="!mdAndUp" :model-value="searchPanelOpen" @update:model-value="$emit('toggle-search')">
|
||||
<v-bottom-sheet
|
||||
v-if="!mdAndUp"
|
||||
:model-value="searchPanelOpen"
|
||||
@update:model-value="$emit('toggle-search')"
|
||||
>
|
||||
<v-card rounded="t-xl">
|
||||
<v-card-title class="d-flex align-center py-3 px-4">
|
||||
<span class="text-subtitle-1 font-weight-medium">搜尋條件</span>
|
||||
@@ -38,7 +52,7 @@ color="primary" :icon="mdAndUp ? false : mdiPlus" :prepend-icon="mdAndUp ? mdiPl
|
||||
</v-card-title>
|
||||
<v-divider />
|
||||
<v-card-text class="px-2 py-1">
|
||||
<v-row dense>
|
||||
<v-row density="compact">
|
||||
<slot name="search-fields" />
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
|
||||
@@ -3,12 +3,19 @@
|
||||
<v-icon start :icon="mdiSchool" />
|
||||
子檔資料示範 ({{ semesters.length }})
|
||||
<v-spacer />
|
||||
<v-btn v-if="!isViewMode" color="primary" :prepend-icon="mdiPlus" size="small" variant="text" @click="$emit('add')">
|
||||
<v-btn
|
||||
v-if="!isViewMode"
|
||||
color="primary"
|
||||
:prepend-icon="mdiPlus"
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="$emit('add')"
|
||||
>
|
||||
新增學期
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<v-row dense>
|
||||
<v-row density="compact">
|
||||
<v-col v-for="semester in semesters" :key="semester.id" cols="12" :md="isMobile ? 12 : 6">
|
||||
<v-card
|
||||
class="cursor-pointer mb-2"
|
||||
@@ -18,7 +25,9 @@
|
||||
@click="$emit('select', semester.id)"
|
||||
>
|
||||
<v-list-item density="compact">
|
||||
<v-list-item-title class="text-body-2 font-weight-bold">{{ semester.semesterName }}</v-list-item-title>
|
||||
<v-list-item-title class="text-body-2 font-weight-bold">{{
|
||||
semester.semesterName
|
||||
}}</v-list-item-title>
|
||||
<v-list-item-subtitle class="text-caption">
|
||||
平均: {{ semester.average }} ・ 排名: {{ semester.rank }}
|
||||
</v-list-item-subtitle>
|
||||
@@ -30,7 +39,10 @@
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<div v-if="semesters.length === 0" class="text-caption text-center py-6 text-medium-emphasis border border-dashed rounded">
|
||||
<div
|
||||
v-if="semesters.length === 0"
|
||||
class="text-caption text-center py-6 text-medium-emphasis border border-dashed rounded"
|
||||
>
|
||||
尚無學期資料
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
<template>
|
||||
<v-card border class="d-flex flex-column h-100 rounded-0" flat width="100%">
|
||||
<v-toolbar v-if="!isDetailEditing" color="transparent" density="compact" flat>
|
||||
<v-btn :icon="isMobile ? mdiArrowLeft : mdiClose" size="small" variant="text" @click="$emit('close')" />
|
||||
<v-btn
|
||||
:icon="isMobile ? mdiArrowLeft : mdiClose"
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="$emit('close')"
|
||||
/>
|
||||
<v-toolbar-title class="text-subtitle-1 font-weight-bold">
|
||||
{{ selectedSemester ? selectedSemester.semesterName : '學期明細' }}
|
||||
</v-toolbar-title>
|
||||
@@ -29,10 +34,13 @@
|
||||
</v-toolbar>
|
||||
|
||||
<v-toolbar v-else density="compact" flat>
|
||||
<v-btn :icon="isMobile ? mdiArrowLeft : mdiClose" size="small" variant="text" @click="$emit('cancel-edit')" />
|
||||
<v-toolbar-title class="text-subtitle-1 font-weight-bold">
|
||||
編輯學期
|
||||
</v-toolbar-title>
|
||||
<v-btn
|
||||
:icon="isMobile ? mdiArrowLeft : mdiClose"
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="$emit('cancel-edit')"
|
||||
/>
|
||||
<v-toolbar-title class="text-subtitle-1 font-weight-bold"> 編輯學期 </v-toolbar-title>
|
||||
<v-spacer />
|
||||
<v-btn variant="text" @click="$emit('save-edit')">儲存</v-btn>
|
||||
</v-toolbar>
|
||||
@@ -59,7 +67,12 @@
|
||||
<v-divider />
|
||||
|
||||
<div v-if="isMobile" class="pa-3 d-flex flex-column ga-3">
|
||||
<v-card v-for="course in selectedSemester.courses" :key="course.code" class="pa-3" variant="outlined">
|
||||
<v-card
|
||||
v-for="course in selectedSemester.courses"
|
||||
:key="course.code"
|
||||
class="pa-3"
|
||||
variant="outlined"
|
||||
>
|
||||
<div class="d-flex align-start justify-space-between ga-3">
|
||||
<div>
|
||||
<div class="text-body-1 font-weight-medium">{{ course.name }}</div>
|
||||
@@ -73,7 +86,10 @@
|
||||
<div>學分 {{ course.credits }}</div>
|
||||
</div>
|
||||
</v-card>
|
||||
<div v-if="selectedSemester.courses.length === 0" class="text-center text-medium-emphasis py-6">
|
||||
<div
|
||||
v-if="selectedSemester.courses.length === 0"
|
||||
class="text-center text-medium-emphasis py-6"
|
||||
>
|
||||
尚無課程資料
|
||||
</div>
|
||||
</div>
|
||||
@@ -93,7 +109,10 @@
|
||||
<div class="text-caption text-medium-emphasis">{{ course.code }}</div>
|
||||
</td>
|
||||
<td class="text-center">{{ course.credits }}</td>
|
||||
<td class="text-right font-weight-bold" :class="course.score < 60 ? 'text-error' : 'text-success'">
|
||||
<td
|
||||
class="text-right font-weight-bold"
|
||||
:class="course.score < 60 ? 'text-error' : 'text-success'"
|
||||
>
|
||||
{{ course.score }}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -104,7 +123,7 @@
|
||||
|
||||
<v-card-text v-else class="pa-0 flex-grow-1 overflow-y-auto bg-surface">
|
||||
<div v-if="detailForm" class="pa-4 d-flex flex-column ga-4">
|
||||
<v-row dense>
|
||||
<v-row density="compact">
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
:model-value="detailForm.semesterName"
|
||||
@@ -141,17 +160,34 @@
|
||||
<div class="d-flex align-center mt-2 mb-1">
|
||||
<div class="text-subtitle-2 font-weight-bold">課程列表</div>
|
||||
<v-spacer />
|
||||
<v-btn color="primary" :prepend-icon="mdiPlus" size="small" variant="text" @click="addCourse">
|
||||
<v-btn
|
||||
color="primary"
|
||||
:prepend-icon="mdiPlus"
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="addCourse"
|
||||
>
|
||||
加入課程
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<div v-if="isMobile" class="d-flex flex-column ga-3">
|
||||
<v-card v-for="(course, idx) in detailForm.courses" :key="idx" class="pa-3" variant="outlined">
|
||||
<v-card
|
||||
v-for="(course, idx) in detailForm.courses"
|
||||
:key="idx"
|
||||
class="pa-3"
|
||||
variant="outlined"
|
||||
>
|
||||
<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" :icon="mdiDelete" size="small" variant="text" @click="removeCourse(idx)" />
|
||||
<v-btn
|
||||
color="error"
|
||||
:icon="mdiDelete"
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="removeCourse(idx)"
|
||||
/>
|
||||
</div>
|
||||
<div class="d-flex flex-column ga-3">
|
||||
<v-text-field
|
||||
@@ -190,7 +226,10 @@
|
||||
/>
|
||||
</div>
|
||||
</v-card>
|
||||
<div v-if="detailForm.courses.length === 0" class="text-center text-medium-emphasis py-4 border border-dashed rounded">
|
||||
<div
|
||||
v-if="detailForm.courses.length === 0"
|
||||
class="text-center text-medium-emphasis py-4 border border-dashed rounded"
|
||||
>
|
||||
暫無課程,請點擊上方按鈕新增
|
||||
</div>
|
||||
</div>
|
||||
@@ -207,7 +246,13 @@
|
||||
<tbody>
|
||||
<tr v-for="(course, idx) in detailForm.courses" :key="idx">
|
||||
<td class="px-0 text-center">
|
||||
<v-btn color="error" :icon="mdiDelete" size="small" variant="text" @click="removeCourse(idx)" />
|
||||
<v-btn
|
||||
color="error"
|
||||
:icon="mdiDelete"
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="removeCourse(idx)"
|
||||
/>
|
||||
</td>
|
||||
<td class="py-2">
|
||||
<v-text-field
|
||||
@@ -251,7 +296,9 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="detailForm.courses.length === 0">
|
||||
<td class="text-center text-medium-emphasis py-4" colspan="4">暫無課程,請點擊上方按鈕新增</td>
|
||||
<td class="text-center text-medium-emphasis py-4" colspan="4">
|
||||
暫無課程,請點擊上方按鈕新增
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-table>
|
||||
@@ -285,21 +332,21 @@ const emit = defineEmits<{
|
||||
(event: 'update:detail-form', value: SemesterRecord | null): void
|
||||
}>()
|
||||
|
||||
function cloneDetailForm (form: SemesterRecord): SemesterRecord {
|
||||
function cloneDetailForm(form: SemesterRecord): SemesterRecord {
|
||||
return {
|
||||
...form,
|
||||
courses: form.courses.map((course) => ({ ...course })),
|
||||
}
|
||||
}
|
||||
|
||||
function emitDetailFormUpdate (updater: (draft: SemesterRecord) => void) {
|
||||
function emitDetailFormUpdate(updater: (draft: SemesterRecord) => void) {
|
||||
if (!props.detailForm) return
|
||||
const nextForm = cloneDetailForm(props.detailForm)
|
||||
updater(nextForm)
|
||||
emit('update:detail-form', nextForm)
|
||||
}
|
||||
|
||||
function updateDetailFormField<K extends keyof SemesterRecord> (key: K, value: SemesterRecord[K]) {
|
||||
function updateDetailFormField<K extends keyof SemesterRecord>(key: K, value: SemesterRecord[K]) {
|
||||
emitDetailFormUpdate((draft) => {
|
||||
draft[key] = value
|
||||
})
|
||||
@@ -308,7 +355,7 @@ function updateDetailFormField<K extends keyof SemesterRecord> (key: K, value: S
|
||||
function updateCourseField<K extends keyof SemesterRecord['courses'][number]>(
|
||||
index: number,
|
||||
key: K,
|
||||
value: SemesterRecord['courses'][number][K],
|
||||
value: SemesterRecord['courses'][number][K]
|
||||
) {
|
||||
emitDetailFormUpdate((draft) => {
|
||||
const course = draft.courses[index]
|
||||
@@ -317,13 +364,13 @@ function updateCourseField<K extends keyof SemesterRecord['courses'][number]>(
|
||||
})
|
||||
}
|
||||
|
||||
function toNumber (value: unknown): number {
|
||||
function toNumber(value: unknown): number {
|
||||
if (typeof value === 'number') return value
|
||||
const parsed = Number(value)
|
||||
return Number.isFinite(parsed) ? parsed : 0
|
||||
}
|
||||
|
||||
function addCourse () {
|
||||
function addCourse() {
|
||||
emitDetailFormUpdate((draft) => {
|
||||
draft.courses.push({
|
||||
code: '',
|
||||
@@ -334,18 +381,18 @@ function addCourse () {
|
||||
})
|
||||
}
|
||||
|
||||
function removeCourse (index: number) {
|
||||
function removeCourse(index: number) {
|
||||
emitDetailFormUpdate((draft) => {
|
||||
draft.courses.splice(index, 1)
|
||||
})
|
||||
}
|
||||
|
||||
const totalCredits = computed(() =>
|
||||
props.selectedSemester?.courses.reduce((sum, course) => sum + course.credits, 0) ?? 0,
|
||||
const totalCredits = computed(
|
||||
() => props.selectedSemester?.courses.reduce((sum, course) => sum + course.credits, 0) ?? 0
|
||||
)
|
||||
|
||||
const statsClass = computed(() =>
|
||||
props.isMobile ? 'pa-3 d-flex flex-column ga-3 bg-surface' : 'pa-3 d-flex ga-3 bg-surface',
|
||||
props.isMobile ? 'pa-3 d-flex flex-column ga-3 bg-surface' : 'pa-3 d-flex ga-3 bg-surface'
|
||||
)
|
||||
|
||||
const statCardClass = computed(() => (props.isMobile ? '' : 'flex-grow-1'))
|
||||
|
||||
Reference in New Issue
Block a user