refactor: 搜尋欄位

This commit is contained in:
skytek_xinliang
2026-06-10 11:44:08 +08:00
parent afbdea6b13
commit aa49d78a84
10 changed files with 170 additions and 202 deletions
+1 -1
View File
@@ -130,7 +130,7 @@ src/
├── components/ ├── components/
│ ├── sections/ ← 新增:Section / Shelf 層 │ ├── sections/ ← 新增:Section / Shelf 層
│ │ ├── SectionSearchPanel.vue │ │ ├
│ │ ├── SectionDataTable.vue │ │ ├── SectionDataTable.vue
│ │ └── SectionFormPanel.vue │ │ └── SectionFormPanel.vue
│ │ │ │
-1
View File
@@ -114,7 +114,6 @@ router -> App.vue -> AppShell -> layout -> view -> page component -> section ->
`components/sections` 是頁面區塊容器: `components/sections` 是頁面區塊容器:
- [SectionSearchPanel.vue](../src/components/sections/SectionSearchPanel.vue)
- [SectionDataTable.vue](../src/components/sections/SectionDataTable.vue) - [SectionDataTable.vue](../src/components/sections/SectionDataTable.vue)
- [SectionFormPanel.vue](../src/components/sections/SectionFormPanel.vue) - [SectionFormPanel.vue](../src/components/sections/SectionFormPanel.vue)
- [SectionFormPage.vue](../src/components/sections/SectionFormPage.vue) - [SectionFormPage.vue](../src/components/sections/SectionFormPage.vue)
+16 -19
View File
@@ -9,7 +9,7 @@
<v-chip color="info" variant="tonal">筆數 {{ filteredStudents.length }}</v-chip> <v-chip color="info" variant="tonal">筆數 {{ filteredStudents.length }}</v-chip>
<v-spacer /> <v-spacer />
<v-btn flat :prepend-icon="mdiMagnify" @click="isSearchVisible = !isSearchVisible" <v-btn flat :prepend-icon="mdiMagnify" @click="isSearchVisible = !isSearchVisible"
>條件搜尋</v-btn >顯示條件搜尋</v-btn
> >
<v-switch v-model="isBulkEditEnabled" color="primary" hide-details label="啟用編輯" /> <v-switch v-model="isBulkEditEnabled" color="primary" hide-details label="啟用編輯" />
</v-card-title> </v-card-title>
@@ -19,37 +19,31 @@
<v-card-text class="pb-0 pt-2"> <v-card-text class="pb-0 pt-2">
<v-row v-if="isSearchVisible" class="mb-2" density="compact"> <v-row v-if="isSearchVisible" class="mb-2" density="compact">
<v-col cols="12" md="3"> <v-col cols="12" md="3">
<div class="text-body-1 text-medium-emphasis pl-2">學號</div> <BaseFormTextField
<v-text-field
v-model="search.studentId" v-model="search.studentId"
clearable clearable
density="compact" label="學號"
hide-details :label-char-count="2"
placeholder="例如:S2024001" placeholder="例如:S2024001"
variant="outlined"
/> />
</v-col> </v-col>
<v-col cols="12" md="3"> <v-col cols="12" md="3">
<div class="text-body-1 text-medium-emphasis pl-2">姓名</div> <BaseFormTextField
<v-text-field
v-model="search.name" v-model="search.name"
clearable clearable
density="compact" label="姓名"
hide-details :label-char-count="2"
placeholder="例如:王小明" placeholder="例如:王小明"
variant="outlined"
/> />
</v-col> </v-col>
<v-col cols="12" md="3"> <v-col cols="12" md="3">
<div class="text-body-1 text-medium-emphasis pl-2">系所</div> <BaseFormSelect
<v-select
v-model="search.department" v-model="search.department"
:class="{ 'select-hide-arrow': !isBulkEditEnabled }" :class="{ 'select-hide-arrow': !isBulkEditEnabled }"
clearable clearable
density="compact" label="系所"
hide-details :label-char-count="2"
:items="departments" :items="departments"
variant="outlined"
/> />
</v-col> </v-col>
</v-row> </v-row>
@@ -374,6 +368,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { mdiContentSave, mdiDelete, mdiMagnify, mdiRestore } from '@mdi/js' import { mdiContentSave, mdiDelete, mdiMagnify, mdiRestore } from '@mdi/js'
import { computed, ref, watch } from 'vue' import { computed, ref, watch } from 'vue'
import BaseFormSelect from '@/components/base/BaseFormSelect.vue'
import BaseFormTextField from '@/components/base/BaseFormTextField.vue'
import ConfirmDialog from '@/components/maint/CommonConfirmDialog.vue' import ConfirmDialog from '@/components/maint/CommonConfirmDialog.vue'
import { useEditableStudentGrid } from '@/composables/maint/useEditableStudentGrid' import { useEditableStudentGrid } from '@/composables/maint/useEditableStudentGrid'
@@ -414,7 +410,9 @@ const {
const itemsPerPage = 10 const itemsPerPage = 10
const currentPage = ref(1) const currentPage = ref(1)
const pageCount = computed(() => Math.max(1, Math.ceil(filteredStudents.value.length / itemsPerPage))) const pageCount = computed(() =>
Math.max(1, Math.ceil(filteredStudents.value.length / itemsPerPage))
)
const pageSummary = computed(() => { const pageSummary = computed(() => {
const total = filteredStudents.value.length const total = filteredStudents.value.length
if (total === 0) return '第 0-0 筆 / 共 0 筆' if (total === 0) return '第 0-0 筆 / 共 0 筆'
@@ -443,8 +441,7 @@ const singleDeleteMessage = computed(() => {
}) })
const selectedDeleteMessage = computed( const selectedDeleteMessage = computed(
() => () => `確定要刪除目前選取的 ${selectedRowIds.value.length} 筆資料嗎?此操作會在儲存後正式生效。`
`確定要刪除目前選取的 ${selectedRowIds.value.length} 筆資料嗎?此操作會在儲存後正式生效。`
) )
watch(pageCount, (value) => { watch(pageCount, (value) => {
+1 -1
View File
@@ -8,7 +8,7 @@
:icon="mdAndUp ? false : mdiMagnify" :icon="mdAndUp ? false : mdiMagnify"
:prepend-icon="mdAndUp ? mdiMagnify : undefined" :prepend-icon="mdAndUp ? mdiMagnify : undefined"
size="small" size="small"
:text="mdAndUp ? '搜尋條件' : false" :text="mdAndUp ? '顯示搜尋條件' : false"
variant="text" variant="text"
@click="$emit('toggle-search')" @click="$emit('toggle-search')"
> >
@@ -1,102 +0,0 @@
<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>
+2 -2
View File
@@ -5,7 +5,7 @@ export const routes: RouteRecordRaw[] = [
path: '/', path: '/',
name: 'home', name: 'home',
component: () => import('@/views/Home.vue'), component: () => import('@/views/Home.vue'),
meta: { layout: 'default', requiresAuth: true }, meta: { layout: 'default', requiresAuth: false },
}, },
{ {
path: '/settings', path: '/settings',
@@ -17,7 +17,7 @@ export const routes: RouteRecordRaw[] = [
path: '/login', path: '/login',
name: 'login', name: 'login',
component: () => import('@/views/Login.vue'), component: () => import('@/views/Login.vue'),
meta: { layout: 'none', guestOnly: true }, meta: { layout: 'none', guestOnly: false },
}, },
{ {
path: '/single-record-maintenance', path: '/single-record-maintenance',
+58 -8
View File
@@ -1,4 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import BaseFormSelect from '@/components/base/BaseFormSelect.vue'
import BaseFormTextField from '@/components/base/BaseFormTextField.vue'
import ConfirmDialog from '@/components/maint/CommonConfirmDialog.vue' import ConfirmDialog from '@/components/maint/CommonConfirmDialog.vue'
import DetailNavigation from '@/components/maint/master-detail/DetailNavigation.vue' import DetailNavigation from '@/components/maint/master-detail/DetailNavigation.vue'
import DetailSidePanel from '@/components/maint/master-detail/DetailSidePanel.vue' import DetailSidePanel from '@/components/maint/master-detail/DetailSidePanel.vue'
@@ -7,7 +9,6 @@ import MntDialogCard from '@/components/maint/MntDialogCard.vue'
import MntRecordNavToolbar from '@/components/maint/MntRecordNavToolbar.vue' import MntRecordNavToolbar from '@/components/maint/MntRecordNavToolbar.vue'
import MaintShell from '@/components/maint/MaintShell.vue' import MaintShell from '@/components/maint/MaintShell.vue'
import SectionDataTable from '@/components/sections/SectionDataTable.vue' import SectionDataTable from '@/components/sections/SectionDataTable.vue'
import SectionSearchPanel from '@/components/sections/SectionSearchPanel.vue'
import { useMasterDetailAMaintenancePage } from '@/composables/page-drivers/useMasterDetailAMaintenancePage' import { useMasterDetailAMaintenancePage } from '@/composables/page-drivers/useMasterDetailAMaintenancePage'
const { const {
@@ -27,13 +28,62 @@ const {
@create="openAddDialog" @create="openAddDialog"
> >
<template #search-fields> <template #search-fields>
<SectionSearchPanel <v-col cols="12" md="2">
v-model="search" <BaseFormTextField
:departments="masterDetailProps.departments" id="search-student-id"
:grade-options="masterDetailProps.gradeOptions" v-model="search.studentId"
:statuses="masterDetailProps.statuses" label="學號"
@reset="resetSearch" :label-char-count="2"
/> name="searchStudentId"
placeholder="例如:S2024001"
/>
</v-col>
<v-col cols="12" md="2">
<BaseFormTextField
id="search-name"
v-model="search.name"
label="姓名"
:label-char-count="2"
name="searchName"
placeholder="例如:王小明"
/>
</v-col>
<v-col cols="12" md="2">
<BaseFormSelect
id="search-department"
v-model="search.department"
label="系所"
:label-char-count="2"
:items="departments"
name="searchDepartment"
/>
</v-col>
<v-col cols="12" md="2">
<BaseFormSelect
id="search-grade"
v-model="search.grade"
label="年級"
:label-char-count="2"
item-title="title"
item-value="value"
:items="gradeOptions"
name="searchGrade"
/>
</v-col>
<v-col cols="12" md="2">
<BaseFormSelect
id="search-status"
v-model="search.status"
label="狀態"
:label-char-count="2"
:items="statuses"
name="searchStatus"
/>
</v-col>
<v-col class="d-flex justify-end align-end flex-grow-1 ga-2" cols="12" md="auto">
<v-btn variant="text" @click="resetSearch">清除</v-btn>
<v-btn color="primary" disabled variant="tonal">查詢</v-btn>
</v-col>
</template> </template>
<template #table> <template #table>
<SectionDataTable <SectionDataTable
+17 -30
View File
@@ -9,70 +9,55 @@
<!-- 搜尋欄位放在 MaintShell search-fields slot讓外殼固定欄位由頁面決定 --> <!-- 搜尋欄位放在 MaintShell search-fields slot讓外殼固定欄位由頁面決定 -->
<template #search-fields> <template #search-fields>
<v-col cols="12" md="2"> <v-col cols="12" md="2">
<div id="search-student-id-label" class="text-body-2 text-medium-emphasis pl-2">學號</div> <BaseFormTextField
<v-text-field
id="search-student-id" id="search-student-id"
v-model="search.studentId" v-model="search.studentId"
aria-labelledby="search-student-id-label" label="學號"
density="compact" :label-char-count="2"
hide-details
name="searchStudentId" name="searchStudentId"
placeholder="例如:S2024001" placeholder="例如:S2024001"
variant="outlined"
/> />
</v-col> </v-col>
<v-col cols="12" md="2"> <v-col cols="12" md="2">
<div id="search-name-label" class="text-body-2 text-medium-emphasis pl-2">姓名</div> <BaseFormTextField
<v-text-field
id="search-name" id="search-name"
v-model="search.name" v-model="search.name"
aria-labelledby="search-name-label" label="姓名"
density="compact" :label-char-count="2"
hide-details
name="searchName" name="searchName"
placeholder="例如:王小明" placeholder="例如:王小明"
variant="outlined"
/> />
</v-col> </v-col>
<v-col cols="12" md="2"> <v-col cols="12" md="2">
<div id="search-department-label" class="text-body-2 text-medium-emphasis pl-2">系所</div> <BaseFormSelect
<v-select
id="search-department" id="search-department"
v-model="search.department" v-model="search.department"
aria-labelledby="search-department-label" label="系所"
density="compact" :label-char-count="2"
hide-details
:items="departments" :items="departments"
name="searchDepartment" name="searchDepartment"
variant="outlined"
/> />
</v-col> </v-col>
<v-col cols="12" md="2"> <v-col cols="12" md="2">
<div id="search-grade-label" class="text-body-2 text-medium-emphasis pl-2">年級</div> <BaseFormSelect
<v-select
id="search-grade" id="search-grade"
v-model="search.grade" v-model="search.grade"
aria-labelledby="search-grade-label" label="年級"
density="compact" :label-char-count="2"
hide-details
item-title="title" item-title="title"
item-value="value" item-value="value"
:items="gradeOptions" :items="gradeOptions"
name="searchGrade" name="searchGrade"
variant="outlined"
/> />
</v-col> </v-col>
<v-col cols="12" md="2"> <v-col cols="12" md="2">
<div id="search-status-label" class="text-body-2 text-medium-emphasis pl-2">狀態</div> <BaseFormSelect
<v-select
id="search-status" id="search-status"
v-model="search.status" v-model="search.status"
aria-labelledby="search-status-label" label="狀態"
density="compact" :label-char-count="2"
hide-details
:items="statuses" :items="statuses"
name="searchStatus" name="searchStatus"
variant="outlined"
/> />
</v-col> </v-col>
<v-col class="d-flex justify-end align-end flex-grow-1 ga-2" cols="12" md="auto"> <v-col class="d-flex justify-end align-end flex-grow-1 ga-2" cols="12" md="auto">
@@ -505,6 +490,8 @@ import { mdiBookPlus, mdiBroom, mdiDelete, mdiEye, mdiMagnify, mdiPencil } from
import { computed, nextTick, ref, watch } from 'vue' import { computed, nextTick, ref, watch } from 'vue'
import { useDisplay } from 'vuetify' import { useDisplay } from 'vuetify'
import BaseFormSelect from '@/components/base/BaseFormSelect.vue'
import BaseFormTextField from '@/components/base/BaseFormTextField.vue'
import ConfirmDialog from '@/components/maint/CommonConfirmDialog.vue' import ConfirmDialog from '@/components/maint/CommonConfirmDialog.vue'
import DetailCollapseGropus from '@/components/maint/master-detail/DetailCollapseGropus.vue' import DetailCollapseGropus from '@/components/maint/master-detail/DetailCollapseGropus.vue'
import DetailFullHeightPanel from '@/components/maint/master-detail/DetailFullHeightPanel.vue' import DetailFullHeightPanel from '@/components/maint/master-detail/DetailFullHeightPanel.vue'
+17 -30
View File
@@ -9,70 +9,55 @@
<!-- 搜尋欄位放在 MaintShell search-fields slot讓外殼固定欄位由頁面決定 --> <!-- 搜尋欄位放在 MaintShell search-fields slot讓外殼固定欄位由頁面決定 -->
<template #search-fields> <template #search-fields>
<v-col cols="12" md="2"> <v-col cols="12" md="2">
<div id="search-student-id-label" class="text-body-2 text-medium-emphasis pl-2">學號</div> <BaseFormTextField
<v-text-field
id="search-student-id" id="search-student-id"
v-model="search.studentId" v-model="search.studentId"
aria-labelledby="search-student-id-label" label="學號"
density="compact" :label-char-count="2"
hide-details
name="searchStudentId" name="searchStudentId"
placeholder="例如:S2024001" placeholder="例如:S2024001"
variant="outlined"
/> />
</v-col> </v-col>
<v-col cols="12" md="2"> <v-col cols="12" md="2">
<div id="search-name-label" class="text-body-2 text-medium-emphasis pl-2">姓名</div> <BaseFormTextField
<v-text-field
id="search-name" id="search-name"
v-model="search.name" v-model="search.name"
aria-labelledby="search-name-label" label="姓名"
density="compact" :label-char-count="2"
hide-details
name="searchName" name="searchName"
placeholder="例如:王小明" placeholder="例如:王小明"
variant="outlined"
/> />
</v-col> </v-col>
<v-col cols="12" md="2"> <v-col cols="12" md="2">
<div id="search-department-label" class="text-body-2 text-medium-emphasis pl-2">系所</div> <BaseFormSelect
<v-select
id="search-department" id="search-department"
v-model="search.department" v-model="search.department"
aria-labelledby="search-department-label" label="系所"
density="compact" :label-char-count="2"
hide-details
:items="departments" :items="departments"
name="searchDepartment" name="searchDepartment"
variant="outlined"
/> />
</v-col> </v-col>
<v-col cols="12" md="2"> <v-col cols="12" md="2">
<div id="search-grade-label" class="text-body-2 text-medium-emphasis pl-2">年級</div> <BaseFormSelect
<v-select
id="search-grade" id="search-grade"
v-model="search.grade" v-model="search.grade"
aria-labelledby="search-grade-label" label="年級"
density="compact" :label-char-count="2"
hide-details
item-title="title" item-title="title"
item-value="value" item-value="value"
:items="gradeOptions" :items="gradeOptions"
name="searchGrade" name="searchGrade"
variant="outlined"
/> />
</v-col> </v-col>
<v-col cols="12" md="2"> <v-col cols="12" md="2">
<div id="search-status-label" class="text-body-2 text-medium-emphasis pl-2">狀態</div> <BaseFormSelect
<v-select
id="search-status" id="search-status"
v-model="search.status" v-model="search.status"
aria-labelledby="search-status-label" label="狀態"
density="compact" :label-char-count="2"
hide-details
:items="statuses" :items="statuses"
name="searchStatus" name="searchStatus"
variant="outlined"
/> />
</v-col> </v-col>
<v-col class="d-flex justify-end align-end flex-grow-1 ga-2" cols="12" md="auto"> <v-col class="d-flex justify-end align-end flex-grow-1 ga-2" cols="12" md="auto">
@@ -492,6 +477,8 @@ import { mdiBroom, mdiDelete, mdiEye, mdiMagnify, mdiPencil, mdiSchool } from '@
import { computed, nextTick, ref, watch } from 'vue' import { computed, nextTick, ref, watch } from 'vue'
import { useDisplay } from 'vuetify' import { useDisplay } from 'vuetify'
import BaseFormSelect from '@/components/base/BaseFormSelect.vue'
import BaseFormTextField from '@/components/base/BaseFormTextField.vue'
import ConfirmDialog from '@/components/maint/CommonConfirmDialog.vue' import ConfirmDialog from '@/components/maint/CommonConfirmDialog.vue'
import MasterDetailCCourseMobilePanel from '@/components/maint/master-detail/CourseMobilePanel.vue' import MasterDetailCCourseMobilePanel from '@/components/maint/master-detail/CourseMobilePanel.vue'
import DetailSimpleList from '@/components/maint/master-detail/DetailSimpleList.vue' import DetailSimpleList from '@/components/maint/master-detail/DetailSimpleList.vue'
+58 -8
View File
@@ -1,8 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import BaseFormSelect from '@/components/base/BaseFormSelect.vue'
import BaseFormTextField from '@/components/base/BaseFormTextField.vue'
import MaintShell from '@/components/maint/MaintShell.vue' import MaintShell from '@/components/maint/MaintShell.vue'
import SectionDataTable from '@/components/sections/SectionDataTable.vue' import SectionDataTable from '@/components/sections/SectionDataTable.vue'
import SectionFormPanel from '@/components/sections/SectionFormPanel.vue' import SectionFormPanel from '@/components/sections/SectionFormPanel.vue'
import SectionSearchPanel from '@/components/sections/SectionSearchPanel.vue'
import { useSingleRecordMaintenancePage } from '@/composables/page-drivers/useSingleRecordMaintenancePage' import { useSingleRecordMaintenancePage } from '@/composables/page-drivers/useSingleRecordMaintenancePage'
const { const {
@@ -21,13 +22,62 @@ const {
@create="commands.openAddDialog" @create="commands.openAddDialog"
> >
<template #search-fields> <template #search-fields>
<SectionSearchPanel <v-col cols="12" md="2">
v-model="search" <BaseFormTextField
:departments="departments" id="search-student-id"
:grade-options="gradeOptions" v-model="search.studentId"
:statuses="statuses" label="學號"
@reset="resetSearch" :label-char-count="2"
/> name="searchStudentId"
placeholder="例如:S2024001"
/>
</v-col>
<v-col cols="12" md="2">
<BaseFormTextField
id="search-name"
v-model="search.name"
label="姓名"
:label-char-count="2"
name="searchName"
placeholder="例如:王小明"
/>
</v-col>
<v-col cols="12" md="2">
<BaseFormSelect
id="search-department"
v-model="search.department"
label="系所"
:label-char-count="2"
:items="departments"
name="searchDepartment"
/>
</v-col>
<v-col cols="12" md="2">
<BaseFormSelect
id="search-grade"
v-model="search.grade"
label="年級"
:label-char-count="2"
item-title="title"
item-value="value"
:items="gradeOptions"
name="searchGrade"
/>
</v-col>
<v-col cols="12" md="2">
<BaseFormSelect
id="search-status"
v-model="search.status"
label="狀態"
:label-char-count="2"
:items="statuses"
name="searchStatus"
/>
</v-col>
<v-col class="d-flex justify-end align-end flex-grow-1 ga-2" cols="12" md="auto">
<v-btn variant="text" @click="resetSearch">清除</v-btn>
<v-btn color="primary" disabled variant="tonal">查詢</v-btn>
</v-col>
</template> </template>
<template #table> <template #table>
<SectionDataTable <SectionDataTable