docs: reorganize architecture strategy documentation
Split current project diagnostics into a dedicated analysis document and trim the main architecture strategy to focus on core guidance. This makes the documentation easier to navigate and separates observed issues from recommended architectural principles.docs: reorganize architecture strategy documentation Split current project diagnostics into a dedicated analysis document and trim the main architecture strategy to focus on core guidance. This makes the documentation easier to navigate and separates observed issues from recommended architectural principles.
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
<script setup lang="ts">
|
||||
import type { StudentFormState } from '@/composables/maint/useStudentMaintenanceForm'
|
||||
|
||||
interface GradeOption {
|
||||
title: string
|
||||
value: number
|
||||
}
|
||||
|
||||
defineProps<{
|
||||
departments: string[]
|
||||
enrollYears: number[]
|
||||
fieldErrors: Record<keyof StudentFormState, string[]>
|
||||
gradeOptions: GradeOption[]
|
||||
isFormLocked: boolean
|
||||
isFormReadonly: boolean
|
||||
statuses: string[]
|
||||
}>()
|
||||
|
||||
const form = defineModel<StudentFormState>({ required: true })
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'clear-field-error', field: keyof StudentFormState): void
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-row density="compact">
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
id="field-studentId"
|
||||
v-model="form.studentId"
|
||||
density="comfortable"
|
||||
:disabled="isFormLocked"
|
||||
:error-messages="fieldErrors.studentId"
|
||||
label="學號"
|
||||
placeholder="例如:S2024008"
|
||||
:readonly="isFormReadonly"
|
||||
variant="outlined"
|
||||
@update:model-value="emit('clear-field-error', 'studentId')"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
id="field-name"
|
||||
v-model="form.name"
|
||||
density="comfortable"
|
||||
:disabled="isFormLocked"
|
||||
:error-messages="fieldErrors.name"
|
||||
label="姓名"
|
||||
placeholder="例如:陳怡君"
|
||||
:readonly="isFormReadonly"
|
||||
variant="outlined"
|
||||
@update:model-value="emit('clear-field-error', 'name')"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-select
|
||||
id="field-department"
|
||||
v-model="form.department"
|
||||
density="comfortable"
|
||||
:disabled="isFormLocked"
|
||||
:error-messages="fieldErrors.department"
|
||||
:items="departments"
|
||||
label="系所"
|
||||
:readonly="isFormReadonly"
|
||||
variant="outlined"
|
||||
@update:model-value="emit('clear-field-error', 'department')"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-select
|
||||
id="field-grade"
|
||||
v-model="form.grade"
|
||||
density="comfortable"
|
||||
:disabled="isFormLocked"
|
||||
:error-messages="fieldErrors.grade"
|
||||
item-title="title"
|
||||
item-value="value"
|
||||
:items="gradeOptions"
|
||||
label="年級"
|
||||
:readonly="isFormReadonly"
|
||||
variant="outlined"
|
||||
@update:model-value="emit('clear-field-error', 'grade')"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-select
|
||||
id="field-enrollYear"
|
||||
v-model="form.enrollYear"
|
||||
density="comfortable"
|
||||
:disabled="isFormLocked"
|
||||
:error-messages="fieldErrors.enrollYear"
|
||||
:items="enrollYears"
|
||||
label="入學年度"
|
||||
:readonly="isFormReadonly"
|
||||
variant="outlined"
|
||||
@update:model-value="emit('clear-field-error', 'enrollYear')"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
id="field-credits"
|
||||
v-model.number="form.credits"
|
||||
density="comfortable"
|
||||
:disabled="isFormLocked"
|
||||
:error-messages="fieldErrors.credits"
|
||||
label="已修學分"
|
||||
min="0"
|
||||
:readonly="isFormReadonly"
|
||||
type="number"
|
||||
variant="outlined"
|
||||
@update:model-value="emit('clear-field-error', 'credits')"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
id="field-advisor"
|
||||
v-model="form.advisor"
|
||||
density="comfortable"
|
||||
:disabled="isFormLocked"
|
||||
:error-messages="fieldErrors.advisor"
|
||||
label="指導老師"
|
||||
placeholder="例如:林教授"
|
||||
:readonly="isFormReadonly"
|
||||
variant="outlined"
|
||||
@update:model-value="emit('clear-field-error', 'advisor')"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
id="field-email"
|
||||
v-model="form.email"
|
||||
density="comfortable"
|
||||
:disabled="isFormLocked"
|
||||
:error-messages="fieldErrors.email"
|
||||
label="Email"
|
||||
placeholder="name@school.edu"
|
||||
:readonly="isFormReadonly"
|
||||
variant="outlined"
|
||||
@update:model-value="emit('clear-field-error', 'email')"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
id="field-phone"
|
||||
v-model="form.phone"
|
||||
density="comfortable"
|
||||
:disabled="isFormLocked"
|
||||
:error-messages="fieldErrors.phone"
|
||||
label="電話"
|
||||
placeholder="例如:02-2345-6789"
|
||||
:readonly="isFormReadonly"
|
||||
variant="outlined"
|
||||
@update:model-value="emit('clear-field-error', 'phone')"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-select
|
||||
id="field-status"
|
||||
v-model="form.status"
|
||||
density="comfortable"
|
||||
:disabled="isFormLocked"
|
||||
:error-messages="fieldErrors.status"
|
||||
:items="statuses"
|
||||
label="狀態"
|
||||
:readonly="isFormReadonly"
|
||||
variant="outlined"
|
||||
@update:model-value="emit('clear-field-error', 'status')"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
@@ -0,0 +1,169 @@
|
||||
<script setup lang="ts">
|
||||
import { mdiDelete, mdiEye, mdiPencil } from '@mdi/js'
|
||||
import type { StudentRecord } from '@/models/student'
|
||||
|
||||
defineProps<{
|
||||
currentPage: number
|
||||
gradeLabel: (grade: number) => string
|
||||
headers: any[]
|
||||
items: StudentRecord[]
|
||||
itemsPerPage: number
|
||||
pageCount: number
|
||||
pageSummary: string
|
||||
rowProps: (data: { item: StudentRecord }) => Record<string, string>
|
||||
statusColor: (status: string) => string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:currentPage', page: number): void
|
||||
(e: 'view', record: StudentRecord): void
|
||||
(e: 'edit', record: StudentRecord): void
|
||||
(e: 'delete', record: StudentRecord): void
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-data-table
|
||||
class="student-table"
|
||||
density="compact"
|
||||
fixed-header
|
||||
:headers="headers"
|
||||
height="100%"
|
||||
hide-default-footer
|
||||
:items="items"
|
||||
:items-per-page="itemsPerPage"
|
||||
:page="currentPage"
|
||||
:row-props="rowProps"
|
||||
@update:page="emit('update:currentPage', $event)"
|
||||
>
|
||||
<template #[`item.grade`]="{ item }">
|
||||
{{ gradeLabel(item.grade) }}
|
||||
</template>
|
||||
<template #[`item.status`]="{ item }">
|
||||
<v-chip :color="statusColor(item.status)" size="small" variant="tonal">
|
||||
{{ item.status }}
|
||||
</v-chip>
|
||||
</template>
|
||||
<template #[`item.actions`]="{ item }">
|
||||
<div class="d-flex ga-2">
|
||||
<v-btn
|
||||
color="info"
|
||||
:prepend-icon="mdiEye"
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="emit('view', item)"
|
||||
>
|
||||
檢視
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="primary"
|
||||
:prepend-icon="mdiPencil"
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="emit('edit', item)"
|
||||
>
|
||||
修改
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="error"
|
||||
:prepend-icon="mdiDelete"
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="emit('delete', item)"
|
||||
>
|
||||
刪除
|
||||
</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
<template #bottom>
|
||||
<div class="d-flex align-center justify-space-between px-4 py-3">
|
||||
<div class="text-body-2 text-medium-emphasis">
|
||||
{{ pageSummary }}
|
||||
</div>
|
||||
<div class="d-flex align-center ga-2">
|
||||
<v-btn
|
||||
:disabled="currentPage <= 1"
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="emit('update:currentPage', 1)"
|
||||
>
|
||||
第一頁
|
||||
</v-btn>
|
||||
<v-btn
|
||||
:disabled="currentPage <= 1"
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="emit('update:currentPage', currentPage - 1)"
|
||||
>
|
||||
上一頁
|
||||
</v-btn>
|
||||
<span class="text-body-2">{{ currentPage }} / {{ pageCount }}</span>
|
||||
<v-btn
|
||||
:disabled="currentPage >= pageCount"
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="emit('update:currentPage', currentPage + 1)"
|
||||
>
|
||||
下一頁
|
||||
</v-btn>
|
||||
<v-btn
|
||||
:disabled="currentPage >= pageCount"
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="emit('update:currentPage', pageCount)"
|
||||
>
|
||||
最後頁
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.student-table {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.student-table :deep(table) {
|
||||
min-width: 1400px;
|
||||
}
|
||||
|
||||
.student-table :deep(th),
|
||||
.student-table :deep(td) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.student-table :deep(.v-data-table-column--fixed),
|
||||
.student-table :deep(.v-data-table-column--fixed-end) {
|
||||
background: rgb(var(--v-theme-surface));
|
||||
}
|
||||
|
||||
.student-table :deep(.v-data-table-column--fixed-last-start)::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: -5px;
|
||||
bottom: 0;
|
||||
width: 5px;
|
||||
box-shadow: inset 10px 0 8px -8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.student-table :deep(.v-data-table-footer) {
|
||||
padding: 4px 0 0;
|
||||
}
|
||||
|
||||
tbody tr.is-highlighted {
|
||||
animation: row-highlight 1.6s ease-out;
|
||||
}
|
||||
|
||||
@keyframes row-highlight {
|
||||
0% {
|
||||
background-color: rgba(var(--v-theme-primary), 0.18);
|
||||
}
|
||||
|
||||
100% {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,282 @@
|
||||
<script setup lang="ts">
|
||||
import ConfirmDialog from '@/components/maint/CommonConfirmDialog.vue'
|
||||
import MntDialogCard from '@/components/maint/MntDialogCard.vue'
|
||||
import MntRecordNavToolbar from '@/components/maint/MntRecordNavToolbar.vue'
|
||||
import ItemFormFieldGroup from '@/components/items/ItemFormFieldGroup.vue'
|
||||
import type {
|
||||
SaveSummaryItem,
|
||||
StudentFormState,
|
||||
} from '@/composables/maint/useStudentMaintenanceForm'
|
||||
|
||||
interface FieldErrorItem {
|
||||
field: string
|
||||
message: string
|
||||
}
|
||||
|
||||
interface GradeOption {
|
||||
title: string
|
||||
value: number
|
||||
}
|
||||
|
||||
defineProps<{
|
||||
confirmCloseVisible: boolean
|
||||
confirmDeleteVisible: boolean
|
||||
confirmNavigateVisible: boolean
|
||||
confirmSaveVisible: boolean
|
||||
confirmSwitchVisible: boolean
|
||||
departments: string[]
|
||||
dialogSubtitle: string
|
||||
dialogTitle: string
|
||||
dialogVisible: boolean
|
||||
enrollYears: number[]
|
||||
errorSummary: FieldErrorItem[]
|
||||
fieldErrors: Record<keyof StudentFormState, string[]>
|
||||
gradeOptions: GradeOption[]
|
||||
hasNextRecord: boolean
|
||||
hasPrevRecord: boolean
|
||||
isDirty: boolean
|
||||
isEditMode: boolean
|
||||
isFormLocked: boolean
|
||||
isFormReadonly: boolean
|
||||
isLoading: boolean
|
||||
isSaving: boolean
|
||||
isViewMode: boolean
|
||||
pendingDeleteLabel: string
|
||||
saveSummary: SaveSummaryItem[]
|
||||
statuses: string[]
|
||||
}>()
|
||||
|
||||
const form = defineModel<StudentFormState>('form', { required: true })
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:confirmCloseVisible', value: boolean): void
|
||||
(e: 'update:confirmDeleteVisible', value: boolean): void
|
||||
(e: 'update:confirmNavigateVisible', value: boolean): void
|
||||
(e: 'update:confirmSaveVisible', value: boolean): void
|
||||
(e: 'update:confirmSwitchVisible', value: boolean): void
|
||||
(e: 'dialog-visible-change', value: boolean): void
|
||||
(e: 'clear-field-error', field: keyof StudentFormState): void
|
||||
(e: 'close'): void
|
||||
(e: 'confirm-close'): void
|
||||
(e: 'confirm-delete'): void
|
||||
(e: 'confirm-navigate'): void
|
||||
(e: 'confirm-save'): void
|
||||
(e: 'confirm-switch'): void
|
||||
(e: 'delete-current'): void
|
||||
(e: 'first'): void
|
||||
(e: 'last'): void
|
||||
(e: 'next'): void
|
||||
(e: 'prev'): void
|
||||
(e: 'save'): void
|
||||
(e: 'scroll-to-field', field: string): void
|
||||
(e: 'switch-to-edit'): void
|
||||
(e: 'switch-to-view'): void
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<teleport to="body">
|
||||
<v-overlay
|
||||
class="dialog-overlay"
|
||||
:close-on-content-click="false"
|
||||
:model-value="dialogVisible"
|
||||
scrim="rgba(0, 0, 0, 0.45)"
|
||||
scroll-strategy="block"
|
||||
@update:model-value="emit('dialog-visible-change', $event)"
|
||||
>
|
||||
<div class="dialog-panel">
|
||||
<MntDialogCard
|
||||
content-class="pa-2 flex-grow-1 overflow-y-auto"
|
||||
:dialog-subtitle="dialogSubtitle"
|
||||
:dialog-title="dialogTitle"
|
||||
:is-edit-mode="isEditMode"
|
||||
:is-view-mode="isViewMode"
|
||||
width="100%"
|
||||
>
|
||||
<template #toolbar>
|
||||
<MntRecordNavToolbar
|
||||
edit-label="進入編輯"
|
||||
first-label="第一筆"
|
||||
:has-next-record="hasNextRecord"
|
||||
:has-prev-record="hasPrevRecord"
|
||||
:is-edit-mode="isEditMode"
|
||||
:is-view-mode="isViewMode"
|
||||
last-label="最後一筆"
|
||||
view-label="回到檢視"
|
||||
@first="emit('first')"
|
||||
@last="emit('last')"
|
||||
@next="emit('next')"
|
||||
@prev="emit('prev')"
|
||||
@switch-to-edit="emit('switch-to-edit')"
|
||||
@switch-to-view="emit('switch-to-view')"
|
||||
/>
|
||||
</template>
|
||||
<template #content>
|
||||
<v-alert
|
||||
v-if="errorSummary.length > 0 && !isLoading"
|
||||
class="mb-4"
|
||||
type="error"
|
||||
variant="tonal"
|
||||
>
|
||||
<div class="text-subtitle-2 mb-2">請先修正以下問題</div>
|
||||
<div class="d-flex flex-column ga-1">
|
||||
<v-btn
|
||||
v-for="error in errorSummary"
|
||||
:key="error.field"
|
||||
color="error"
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="emit('scroll-to-field', error.field)"
|
||||
>
|
||||
{{ error.message }}
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-alert>
|
||||
|
||||
<v-skeleton-loader
|
||||
v-if="isLoading"
|
||||
class="mt-4"
|
||||
type="subtitle,paragraph"
|
||||
width="100%"
|
||||
/>
|
||||
|
||||
<v-form
|
||||
v-else
|
||||
:class="{ 'form-readonly': isFormReadonly }"
|
||||
@submit.prevent="emit('save')"
|
||||
>
|
||||
<ItemFormFieldGroup
|
||||
v-model="form"
|
||||
:departments="departments"
|
||||
:enroll-years="enrollYears"
|
||||
:field-errors="fieldErrors"
|
||||
:grade-options="gradeOptions"
|
||||
:is-form-locked="isFormLocked"
|
||||
:is-form-readonly="isFormReadonly"
|
||||
:statuses="statuses"
|
||||
@clear-field-error="emit('clear-field-error', $event)"
|
||||
/>
|
||||
</v-form>
|
||||
</template>
|
||||
<template #actions>
|
||||
<v-spacer />
|
||||
<v-btn :disabled="isSaving" variant="text" @click="emit('close')">取消</v-btn>
|
||||
<v-btn
|
||||
v-if="isEditMode"
|
||||
color="error"
|
||||
:disabled="isSaving"
|
||||
variant="tonal"
|
||||
@click="emit('delete-current')"
|
||||
>
|
||||
刪除
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-if="!isViewMode"
|
||||
color="primary"
|
||||
:disabled="!isDirty || isLoading"
|
||||
:loading="isSaving"
|
||||
variant="flat"
|
||||
@click="emit('save')"
|
||||
>
|
||||
儲存
|
||||
</v-btn>
|
||||
<v-btn v-else color="primary" variant="flat" @click="emit('close')">關閉</v-btn>
|
||||
</template>
|
||||
</MntDialogCard>
|
||||
</div>
|
||||
</v-overlay>
|
||||
</teleport>
|
||||
|
||||
<ConfirmDialog
|
||||
:model-value="confirmCloseVisible"
|
||||
confirm-color="error"
|
||||
confirm-text="關閉不儲存"
|
||||
message="目前有尚未儲存的內容,確定要關閉嗎?"
|
||||
title="未儲存變更"
|
||||
@confirm="emit('confirm-close')"
|
||||
@update:model-value="emit('update:confirmCloseVisible', $event)"
|
||||
/>
|
||||
|
||||
<ConfirmDialog
|
||||
:model-value="confirmSaveVisible"
|
||||
:confirm-loading="isSaving"
|
||||
confirm-text="確認儲存"
|
||||
max-width="520"
|
||||
title="確認儲存變更"
|
||||
@confirm="emit('confirm-save')"
|
||||
@update:model-value="emit('update:confirmSaveVisible', $event)"
|
||||
>
|
||||
<div v-if="saveSummary.length > 0" class="d-flex flex-column ga-2">
|
||||
<div v-for="item in saveSummary" :key="item.label" class="d-flex flex-column">
|
||||
<div class="text-caption text-medium-emphasis">{{ item.label }}</div>
|
||||
<div v-if="item.before !== null" class="text-body-2">
|
||||
<span class="text-medium-emphasis">原:</span>
|
||||
<span :class="{ 'text-medium-emphasis': item.before === '—' }">
|
||||
{{ item.before }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-body-2">
|
||||
<span class="text-medium-emphasis">新:</span>
|
||||
<span :class="{ 'text-medium-emphasis': item.after === '—' }">
|
||||
{{ item.after }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="text-body-2">目前沒有可儲存的變更。</div>
|
||||
</ConfirmDialog>
|
||||
|
||||
<ConfirmDialog
|
||||
:model-value="confirmDeleteVisible"
|
||||
confirm-color="error"
|
||||
confirm-text="確定刪除"
|
||||
:message="`確定要刪除 ${pendingDeleteLabel} 嗎?此操作無法復原。`"
|
||||
title="確認刪除"
|
||||
@confirm="emit('confirm-delete')"
|
||||
@update:model-value="emit('update:confirmDeleteVisible', $event)"
|
||||
/>
|
||||
|
||||
<ConfirmDialog
|
||||
:model-value="confirmSwitchVisible"
|
||||
confirm-text="確定切換"
|
||||
max-width="480"
|
||||
message="目前有尚未儲存的內容,切換為檢視模式將會捨棄變更,確定要切換嗎?"
|
||||
title="未儲存變更"
|
||||
@confirm="emit('confirm-switch')"
|
||||
@update:model-value="emit('update:confirmSwitchVisible', $event)"
|
||||
/>
|
||||
|
||||
<ConfirmDialog
|
||||
:model-value="confirmNavigateVisible"
|
||||
confirm-text="確定切換"
|
||||
max-width="480"
|
||||
message="目前有尚未儲存的內容,切換到其他資料將會捨棄變更,確定要切換嗎?"
|
||||
title="未儲存變更"
|
||||
@confirm="emit('confirm-navigate')"
|
||||
@update:model-value="emit('update:confirmNavigateVisible', $event)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.dialog-overlay :deep(.v-overlay__content) {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
width: 100vw;
|
||||
max-width: 100vw;
|
||||
}
|
||||
|
||||
.dialog-panel {
|
||||
width: 760px;
|
||||
max-width: 100%;
|
||||
height: 100vh;
|
||||
background: rgb(var(--v-theme-surface));
|
||||
padding: 12px;
|
||||
box-shadow: -12px 0 24px rgba(0, 0, 0, 0.18);
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.form-readonly :deep(.v-field) {
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,102 @@
|
||||
<script setup lang="ts">
|
||||
import { mdiBroom, mdiMagnify } from '@mdi/js'
|
||||
|
||||
interface GradeOption {
|
||||
title: string
|
||||
value: number
|
||||
}
|
||||
|
||||
interface SearchState {
|
||||
studentId: string
|
||||
name: string
|
||||
department: string
|
||||
grade: number | null
|
||||
status: string
|
||||
}
|
||||
|
||||
defineProps<{
|
||||
departments: string[]
|
||||
gradeOptions: GradeOption[]
|
||||
statuses: string[]
|
||||
}>()
|
||||
|
||||
const search = defineModel<SearchState>({ required: true })
|
||||
|
||||
defineEmits<{
|
||||
(e: 'reset'): void
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-col cols="12" md="2">
|
||||
<div id="search-student-id-label" class="text-body-2 text-medium-emphasis pl-2">學號</div>
|
||||
<v-text-field
|
||||
id="search-student-id"
|
||||
v-model="search.studentId"
|
||||
aria-labelledby="search-student-id-label"
|
||||
density="compact"
|
||||
hide-details
|
||||
name="searchStudentId"
|
||||
placeholder="例如:S2024001"
|
||||
variant="outlined"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" md="2">
|
||||
<div id="search-name-label" class="text-body-2 text-medium-emphasis pl-2">姓名</div>
|
||||
<v-text-field
|
||||
id="search-name"
|
||||
v-model="search.name"
|
||||
aria-labelledby="search-name-label"
|
||||
density="compact"
|
||||
hide-details
|
||||
name="searchName"
|
||||
placeholder="例如:王小明"
|
||||
variant="outlined"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" md="2">
|
||||
<div id="search-department-label" class="text-body-2 text-medium-emphasis pl-2">系所</div>
|
||||
<v-select
|
||||
id="search-department"
|
||||
v-model="search.department"
|
||||
aria-labelledby="search-department-label"
|
||||
density="compact"
|
||||
hide-details
|
||||
:items="departments"
|
||||
name="searchDepartment"
|
||||
variant="outlined"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" md="2">
|
||||
<div id="search-grade-label" class="text-body-2 text-medium-emphasis pl-2">年級</div>
|
||||
<v-select
|
||||
id="search-grade"
|
||||
v-model="search.grade"
|
||||
aria-labelledby="search-grade-label"
|
||||
density="compact"
|
||||
hide-details
|
||||
item-title="title"
|
||||
item-value="value"
|
||||
:items="gradeOptions"
|
||||
name="searchGrade"
|
||||
variant="outlined"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" md="2">
|
||||
<div id="search-status-label" class="text-body-2 text-medium-emphasis pl-2">狀態</div>
|
||||
<v-select
|
||||
id="search-status"
|
||||
v-model="search.status"
|
||||
aria-labelledby="search-status-label"
|
||||
density="compact"
|
||||
hide-details
|
||||
:items="statuses"
|
||||
name="searchStatus"
|
||||
variant="outlined"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col class="d-flex justify-end align-end flex-grow-1 ga-2" cols="12" md="auto">
|
||||
<v-btn :prepend-icon="mdiBroom" variant="text" @click="$emit('reset')">清除</v-btn>
|
||||
<v-btn color="primary" disabled :prepend-icon="mdiMagnify" variant="tonal">查詢</v-btn>
|
||||
</v-col>
|
||||
</template>
|
||||
Reference in New Issue
Block a user