Refactor MasterDetailMntC.vue for improved readability and consistency

This commit is contained in:
skytek_xinliang
2026-03-30 09:18:55 +08:00
parent 7591ecd062
commit 16b58fbf7a
66 changed files with 2071 additions and 777 deletions
+6 -6
View File
@@ -107,7 +107,7 @@ const dialogModel = computed({
set: (v: boolean) => emit('update:modelValue', v),
})
function normalizeOptions (options: Array<Option | string | number>) {
function normalizeOptions(options: Array<Option | string | number>) {
return options.map((o) => {
if (typeof o === 'string' || typeof o === 'number') {
return { title: String(o), value: o }
@@ -121,7 +121,7 @@ const normalizedPermissionOptions = computed(() => normalizeOptions(props.permis
const form = reactive<GenericRecord>({})
function resetForm (next: GenericRecord) {
function resetForm(next: GenericRecord) {
for (const key of Object.keys(form)) {
delete form[key]
}
@@ -129,7 +129,7 @@ function resetForm (next: GenericRecord) {
}
const getDefaultStatus = (): OptionValue | '' => normalizedStatusOptions.value[0]?.value ?? ''
function getDefaultPermission (): OptionValue | '' {
function getDefaultPermission(): OptionValue | '' {
return normalizedPermissionOptions.value[0]?.value ?? ''
}
@@ -153,7 +153,7 @@ const formPermission = computed<OptionValue | ''>({
},
})
function syncFromItem () {
function syncFromItem() {
const item = props.item ?? {}
resetForm({ ...item })
@@ -182,12 +182,12 @@ watch(
}
)
function handleCancel () {
function handleCancel() {
emit('cancel')
dialogModel.value = false
}
function handleSubmit () {
function handleSubmit() {
emit('submit', { ...form })
if (props.closeOnSubmit) {
+9 -3
View File
@@ -30,7 +30,13 @@
<v-tooltip :disabled="!refreshTooltipText" location="top" :text="refreshTooltipText">
<template #activator="{ props: activatorProps }">
<v-btn v-bind="activatorProps" density="comfortable" icon variant="text" @click="$emit('refresh')">
<v-btn
v-bind="activatorProps"
density="comfortable"
icon
variant="text"
@click="$emit('refresh')"
>
<v-icon :icon="mdiRefresh" />
</v-btn>
</template>
@@ -146,7 +152,7 @@ const selectAllIndeterminate = computed(() => {
return selectedCount > 0 && selectedCount < allSettingsKeys.value.length
})
function toggleSelectAll (checked: unknown) {
function toggleSelectAll(checked: unknown) {
const current = Array.isArray(settingsSelectedKeys.value) ? settingsSelectedKeys.value : []
const nonSettingsKeys = current.filter((k) => !allSettingsKeys.value.includes(k))
@@ -156,7 +162,7 @@ function toggleSelectAll (checked: unknown) {
)
}
function updateSettingsSelectedKeys (value: unknown) {
function updateSettingsSelectedKeys(value: unknown) {
emit('update:settingsSelectedKeys', Array.isArray(value) ? value : [])
}
</script>
+1 -1
View File
@@ -85,7 +85,7 @@ watch(
{ immediate: true }
)
function toggleExpand (id: string | number) {
function toggleExpand(id: string | number) {
if (expandedIds.value.has(id)) {
expandedIds.value.delete(id)
} else {
@@ -36,7 +36,7 @@ const props = defineProps({
const emit = defineEmits(['click'])
function handleClick (e: MouseEvent) {
function handleClick(e: MouseEvent) {
emit('click', e)
if (!props.href) {
e.preventDefault()
@@ -1,6 +1,8 @@
<template>
<v-card class="w-100 h-100 d-flex flex-column bg-transparent pa-2 pa-lg-4" elevation="3">
<v-card-title class="text-h6 text-lg-h5 font-weight-bold text-accent mb-4">{{ title }}</v-card-title>
<v-card-title class="text-h6 text-lg-h5 font-weight-bold text-accent mb-4">{{
title
}}</v-card-title>
<v-tabs v-model="activeTab" class="mb-3" color="primary" density="comfortable">
<v-tab v-for="tab in normalizedTabs" :key="tab.value" :value="tab.value">
@@ -23,8 +25,11 @@
<td class="text-no-wrap">{{ item.school }}</td>
<td>
<v-btn
class="px-0 text-none justify-start" color="primary" variant="text"
@click="emit('select-announcement', item)">
class="px-0 text-none justify-start"
color="primary"
variant="text"
@click="emit('select-announcement', item)"
>
{{ item.title }}
</v-btn>
</td>
@@ -41,17 +46,18 @@ class="px-0 text-none justify-start" color="primary" variant="text"
{{ item.content }}
</v-list-item-title>
<v-list-item-subtitle v-if="item.title || item.createdAt">
{{ item.title }}<span v-if="item.title && item.createdAt"> </span>{{ item.createdAt }}
{{ item.title }}<span v-if="item.title && item.createdAt"> </span
>{{ item.createdAt }}
</v-list-item-subtitle>
</v-list-item>
<v-list-item v-if="systemPageItems.length === 0" class="h-100">
<v-list-item-title class="text-center text-medium-emphasis">{{ emptyText }}</v-list-item-title>
<v-list-item-title class="text-center text-medium-emphasis">{{
emptyText
}}</v-list-item-title>
</v-list-item>
</v-list>
</div>
<div class="d-flex justify-space-between align-center mt-auto pt-3">
<span class="text-caption text-medium-emphasis">
{{ paginationLabel }} {{ totalItems }}
@@ -125,7 +131,8 @@ const systemTab = computed<AnnouncementTab>(() => ({
}))
const normalizedTabs = computed<AnnouncementTab[]>(() => {
const baseTabs = props.tabs.length > 0 ? props.tabs : [{ label: props.allTabLabel, value: allTabValue }]
const baseTabs =
props.tabs.length > 0 ? props.tabs : [{ label: props.allTabLabel, value: allTabValue }]
if (baseTabs.some((tab) => tab.value === systemTabValue)) return baseTabs
return [...baseTabs, systemTab.value]
})
+41 -11
View File
@@ -1,29 +1,59 @@
<template>
<v-form @submit.prevent="$emit('submit', { username, password, rememberMe })">
<v-text-field
v-model="username" bg-color="surface" class="mb-6 mb-md-4" color="primary"
density="comfortable" hide-details :placeholder="props.accPlaceholder" variant="outlined"></v-text-field>
v-model="username"
bg-color="surface"
class="mb-6 mb-md-4"
color="primary"
density="comfortable"
hide-details
:placeholder="props.accPlaceholder"
variant="outlined"
></v-text-field>
<v-text-field
v-model="password" :append-inner-icon="showPassword ? mdiEyeOff : mdiEye" bg-color="surface"
class="mb-6 mb-md-4" color="primary" density="comfortable" hide-details :placeholder="props.passwPlaceholder" :type="showPassword ? 'text' : 'password'"
v-model="password"
:append-inner-icon="showPassword ? mdiEyeOff : mdiEye"
bg-color="surface"
class="mb-6 mb-md-4"
color="primary"
density="comfortable"
hide-details
:placeholder="props.passwPlaceholder"
:type="showPassword ? 'text' : 'password'"
variant="outlined"
@click:append-inner="showPassword = !showPassword"></v-text-field>
@click:append-inner="showPassword = !showPassword"
></v-text-field>
<slot name="verify"></slot>
<div class="d-flex align-center justify-space-between mb-6 mb-md-4">
<v-checkbox
v-model="rememberMe" color="primary" density="compact" hide-details
:label="props.rememberMeLabel"></v-checkbox>
v-model="rememberMe"
color="primary"
density="compact"
hide-details
:label="props.rememberMeLabel"
></v-checkbox>
<a
class="text-body-2 text-primary text-decoration-none" :href="props.forgotPasswordHref || '#'"
:target="props.forgotPasswordTarget" @click="handleForgotPasswordClick">
class="text-body-2 text-primary text-decoration-none"
:href="props.forgotPasswordHref || '#'"
:target="props.forgotPasswordTarget"
@click="handleForgotPasswordClick"
>
{{ props.forgotPasswordText }}
</a>
</div>
<v-btn block class="mb-6 font-weight-bold" color="primary" elevation="0" height="48" size="large" type="submit">
<v-btn
block
class="mb-6 font-weight-bold"
color="primary"
elevation="0"
height="48"
size="large"
type="submit"
>
{{ props.submitText }}
</v-btn>
</v-form>
@@ -96,7 +126,7 @@ watch([rememberMe, username], ([nextRemember, nextUsername]) => {
localStorage.setItem(props.rememberStorageKey, nextUsername)
})
function handleForgotPasswordClick (e: MouseEvent) {
function handleForgotPasswordClick(e: MouseEvent) {
emit('forgot-password', e)
if (!props.forgotPasswordHref) {
e.preventDefault()
+4 -4
View File
@@ -6,7 +6,8 @@
:icon="mdiPaletteOutline"
size="small"
variant="text"
@click="toggleTheme"></v-btn>
@click="toggleTheme"
></v-btn>
<v-menu location="bottom end">
<template #activator="{ props: menuActivatorProps }">
<v-btn
@@ -60,7 +61,7 @@ const availableThemeNames = computed(() =>
Object.keys(theme.themes.value ?? {}).filter((name) => name.startsWith('theme'))
)
function toggleTheme () {
function toggleTheme() {
const names = availableThemeNames.value
if (names.length === 0) return
@@ -74,9 +75,8 @@ const localeOptions = computed(() =>
props.locales.length > 0 ? props.locales : ['zh-TW', 'en-US']
)
function handleSelectLocale (locale: string) {
function handleSelectLocale(locale: string) {
if (locale === props.locale) return
emit('change-locale', locale)
}
</script>
+17 -6
View File
@@ -7,17 +7,28 @@
<div v-else class="d-flex align-center gap-2">
<!-- Captcha Image and Refresh -->
<div
class="captcha-wrapper d-flex align-center cursor-pointer me-1 me-md-2" :title="props.refreshTitle"
@click="handleRefresh">
class="captcha-wrapper d-flex align-center cursor-pointer me-1 me-md-2"
:title="props.refreshTitle"
@click="handleRefresh"
>
<img v-if="captchaImage" alt="Captcha" class="captcha-img" :src="captchaImage" />
<v-icon class="ms-2" color="grey" :icon="mdiRefresh"></v-icon>
</div>
<!-- Input and Verify -->
<v-text-field
v-model="inputCode" :append-inner-icon="props.verified ? mdiCheckCircle : undefined" bg-color="surface" class="flex-grow-1"
color="primary" density="compact" :disabled="props.verified" :error="!!errorMsg" hide-details
:placeholder="props.captchaPlaceholder" variant="outlined">
v-model="inputCode"
:append-inner-icon="props.verified ? mdiCheckCircle : undefined"
bg-color="surface"
class="flex-grow-1"
color="primary"
density="compact"
:disabled="props.verified"
:error="!!errorMsg"
hide-details
:placeholder="props.captchaPlaceholder"
variant="outlined"
>
<template v-if="props.verified" #append-inner>
<v-icon color="success" :icon="mdiCheckCircle" />
</template>
@@ -78,7 +89,7 @@ const errorMsg = computed(() => props.errorMessage)
const loading = computed(() => props.loading)
function handleRefresh () {
function handleRefresh() {
if (props.verified) return
emit('refresh')
}