Refactor MasterDetailMntC.vue for improved readability and consistency
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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]
|
||||
})
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user