feat: 公告開關
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
<LoginBrand :title="props.branding.title" />
|
||||
</div>
|
||||
<v-sheet
|
||||
v-if="props.withAnnouncement"
|
||||
class="board-wrapper pa-2 pa-lg-0"
|
||||
color="rgba(var(--v-theme-surface), 0.8)"
|
||||
elevation="0"
|
||||
@@ -195,7 +196,11 @@
|
||||
</v-card>
|
||||
</v-row>
|
||||
|
||||
<v-bottom-sheet v-model="mobileAnnouncementSheetVisible" class="d-sm-none">
|
||||
<v-bottom-sheet
|
||||
v-if="props.withAnnouncement"
|
||||
v-model="mobileAnnouncementSheetVisible"
|
||||
class="d-sm-none"
|
||||
>
|
||||
<v-card rounded="t-xl">
|
||||
<v-card-title class="text-subtitle-1 font-weight-bold">
|
||||
{{ props.mobileAnnouncement.listTitle }}
|
||||
@@ -330,6 +335,7 @@ interface ToolBarConfig {
|
||||
|
||||
interface Props {
|
||||
layout: 'side-left' | 'side-right' | 'card'
|
||||
withAnnouncement?: boolean
|
||||
branding: BrandingConfig
|
||||
illustration: IllustrationConfig
|
||||
announcementBoard: AnnouncementBoardConfig
|
||||
@@ -341,6 +347,7 @@ interface Props {
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
layout: 'side-left',
|
||||
withAnnouncement: true,
|
||||
branding: () => ({
|
||||
title: 'Skyteck Login',
|
||||
organization: 'school',
|
||||
@@ -430,6 +437,7 @@ const mobileAnnouncementSheetVisible = ref(false)
|
||||
const mobileAnnouncementItems = computed(() => props.mobileAnnouncement.items ?? [])
|
||||
|
||||
const showMobileAnnouncementBanner = computed(() => {
|
||||
if (!props.withAnnouncement) return false
|
||||
if (props.mobileAnnouncement.show === false) return false
|
||||
return mobileAnnouncementItems.value.length > 0
|
||||
})
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { computed, ref, toValue, watch, type MaybeRefOrGetter } from 'vue'
|
||||
|
||||
export interface LoginAnnouncementItem {
|
||||
id: string | number
|
||||
@@ -25,6 +24,10 @@ export interface LoginMobileAnnouncementItem {
|
||||
createdAt?: string
|
||||
}
|
||||
|
||||
interface UseLoginAnnouncementsOptions {
|
||||
enabled: MaybeRefOrGetter<boolean>
|
||||
}
|
||||
|
||||
const storageKey = 'sk_playground_login_announcements'
|
||||
|
||||
const defaultItems: LoginAnnouncementItem[] = [
|
||||
@@ -110,10 +113,11 @@ async function mockFetchMobileAnnouncementsApi(): Promise<LoginMobileAnnouncemen
|
||||
]
|
||||
}
|
||||
|
||||
export const useLoginAnnouncementsStore = defineStore('loginAnnouncements', () => {
|
||||
const items = ref<LoginAnnouncementItem[]>(readItems())
|
||||
export function useLoginAnnouncements(options: UseLoginAnnouncementsOptions) {
|
||||
const items = ref<LoginAnnouncementItem[]>([])
|
||||
const selectedId = ref<string | number | null>(null)
|
||||
const mobileAnnouncements = ref<LoginMobileAnnouncementItem[]>([])
|
||||
const enabled = computed(() => toValue(options.enabled))
|
||||
|
||||
const listItems = computed<LoginAnnouncementListItem[]>(() =>
|
||||
items.value.map((item) => ({
|
||||
@@ -132,8 +136,8 @@ export const useLoginAnnouncementsStore = defineStore('loginAnnouncements', () =
|
||||
{ label: '國中', value: 'junior' },
|
||||
{ label: '高中', value: 'senior' },
|
||||
],
|
||||
items: listItems.value,
|
||||
systemAnnouncements: mobileAnnouncements.value,
|
||||
items: enabled.value ? listItems.value : [],
|
||||
systemAnnouncements: enabled.value ? mobileAnnouncements.value : [],
|
||||
itemsPerPage: 5,
|
||||
dateHeader: '公告時間',
|
||||
schoolHeader: '公告學校',
|
||||
@@ -142,7 +146,7 @@ export const useLoginAnnouncementsStore = defineStore('loginAnnouncements', () =
|
||||
}))
|
||||
|
||||
const selectedAnnouncement = computed(() => {
|
||||
if (selectedId.value === null) return null
|
||||
if (!enabled.value || selectedId.value === null) return null
|
||||
return items.value.find((item) => item.id === selectedId.value) ?? null
|
||||
})
|
||||
|
||||
@@ -151,59 +155,54 @@ export const useLoginAnnouncementsStore = defineStore('loginAnnouncements', () =
|
||||
})
|
||||
|
||||
const mobileAnnouncementConfig = computed(() => ({
|
||||
items: mobileAnnouncements.value,
|
||||
show: mobileAnnouncements.value.length > 0,
|
||||
items: enabled.value ? mobileAnnouncements.value : [],
|
||||
show: enabled.value && mobileAnnouncements.value.length > 0,
|
||||
viewAllText: '查看全部',
|
||||
listTitle: '系統公告',
|
||||
closeText: '關閉',
|
||||
emptyText: '目前沒有公告',
|
||||
}))
|
||||
|
||||
const hydrate = () => {
|
||||
function hydrate() {
|
||||
if (!enabled.value) return
|
||||
|
||||
items.value = readItems()
|
||||
}
|
||||
|
||||
const replaceAll = (nextItems: LoginAnnouncementItem[]) => {
|
||||
items.value = Array.isArray(nextItems) ? nextItems : []
|
||||
}
|
||||
async function fetchMobileAnnouncements() {
|
||||
if (!enabled.value) return
|
||||
|
||||
const selectById = (id: string | number) => {
|
||||
selectedId.value = id
|
||||
}
|
||||
|
||||
const clearSelection = () => {
|
||||
selectedId.value = null
|
||||
}
|
||||
|
||||
const fetchMobileAnnouncements = async () => {
|
||||
const result = await mockFetchMobileAnnouncementsApi()
|
||||
mobileAnnouncements.value = Array.isArray(result) ? result : []
|
||||
}
|
||||
|
||||
const fetchMobileAnnouncement = async () => {
|
||||
async function load() {
|
||||
hydrate()
|
||||
await fetchMobileAnnouncements()
|
||||
}
|
||||
|
||||
function selectById(id: string | number) {
|
||||
if (!enabled.value) return
|
||||
|
||||
selectedId.value = id
|
||||
}
|
||||
|
||||
watch(
|
||||
items,
|
||||
(val) => {
|
||||
if (!enabled.value) return
|
||||
|
||||
writeItems(val)
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
return {
|
||||
items,
|
||||
listItems,
|
||||
boardConfig,
|
||||
mobileAnnouncementConfig,
|
||||
selectedAnnouncement,
|
||||
selectedAnnouncementDetail,
|
||||
hydrate,
|
||||
replaceAll,
|
||||
load,
|
||||
selectById,
|
||||
clearSelection,
|
||||
fetchMobileAnnouncements,
|
||||
fetchMobileAnnouncement,
|
||||
}
|
||||
})
|
||||
}
|
||||
+16
-15
@@ -8,6 +8,7 @@
|
||||
:layout="formPositionLayout"
|
||||
:mobile-announcement="mobileAnnouncement"
|
||||
:toolbar="toolbar"
|
||||
:with-announcement="withAnnouncement"
|
||||
@captcha-change="handleCaptchaChange"
|
||||
@captcha-refresh="handleCaptchaRefresh"
|
||||
@change-locale="handleChangeLocale"
|
||||
@@ -52,18 +53,17 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import HyakkaouAcademyImage from '@/assets/logo.png'
|
||||
import PageLogin from '@/components/PageLogin.vue'
|
||||
import { useLoginCaptcha } from '@/composables/useLoginCaptcha'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import {
|
||||
type LoginAnnouncementListItem,
|
||||
useLoginAnnouncementsStore,
|
||||
} from '@/stores/loginAnnouncements'
|
||||
useLoginAnnouncements,
|
||||
} from '@/composables/useLoginAnnouncements'
|
||||
import { useLoginCaptcha } from '@/composables/useLoginCaptcha'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { useMenuStore } from '@/stores/menu'
|
||||
import { useSnackbarStore } from '@/stores/snackbar'
|
||||
|
||||
@@ -74,15 +74,8 @@ const { t, locale } = useI18n()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const authStore = useAuthStore()
|
||||
const loginAnnouncementsStore = useLoginAnnouncementsStore()
|
||||
const menuStore = useMenuStore()
|
||||
const snackbarStore = useSnackbarStore()
|
||||
const {
|
||||
boardConfig: announcementBoard,
|
||||
mobileAnnouncementConfig: mobileAnnouncement,
|
||||
selectedAnnouncement,
|
||||
selectedAnnouncementDetail,
|
||||
} = storeToRefs(loginAnnouncementsStore)
|
||||
|
||||
// 語系選項
|
||||
const locales = ['zh-TW', 'en-US']
|
||||
@@ -92,10 +85,19 @@ const illustrationImage = ref(HyakkaouAcademyImage)
|
||||
|
||||
// 功能開關與版型
|
||||
const formPositionLayout = ref<LayoutType>('side-left')
|
||||
// 是否啟用公告
|
||||
const withAnnouncement = ref(true)
|
||||
|
||||
// 功能開關:是否啟用驗證碼
|
||||
const withCaptcha = ref(true)
|
||||
const loginCaptcha = useLoginCaptcha({ enabled: withCaptcha })
|
||||
const loginAnnouncements = useLoginAnnouncements({ enabled: withAnnouncement })
|
||||
const {
|
||||
boardConfig: announcementBoard,
|
||||
mobileAnnouncementConfig: mobileAnnouncement,
|
||||
selectedAnnouncement,
|
||||
selectedAnnouncementDetail,
|
||||
} = loginAnnouncements
|
||||
|
||||
// 文字內容(i18n)
|
||||
const title = computed(() => t('pages.login.title'))
|
||||
@@ -200,7 +202,7 @@ function handleToggleLayout() {
|
||||
}
|
||||
|
||||
function handleSelectAnnouncement(item: LoginAnnouncementListItem) {
|
||||
loginAnnouncementsStore.selectById(item.id)
|
||||
loginAnnouncements.selectById(item.id)
|
||||
announcementDialogVisible.value = true
|
||||
}
|
||||
|
||||
@@ -252,8 +254,7 @@ async function onLogin(data: Record<string, unknown>) {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loginAnnouncementsStore.hydrate()
|
||||
loginAnnouncementsStore.fetchMobileAnnouncements()
|
||||
void loginAnnouncements.load()
|
||||
void loginCaptcha.loadCaptcha().catch(() => undefined)
|
||||
})
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user