import { defineStore } from 'pinia' import { computed, ref } from 'vue' import type { BusinessType } from '@/types/api' import type { Case } from '@/types/business/cases' import type { Banner, BannerVo, BusinessLine, HomeStats, ProjectStatusCount, ProjectSubmissionFlat, ProjectSubmissionInfo, ProjectSubmissionVo, ServiceContentBlock, ServiceType, } from '@/types/business/home' import type { PortalEntryGroup, PortalEntryVo } from '@/types/business/home' import { getCarouselList, getPortalEntryList, getProjectStatusCount, getServiceTypeDetail, getServiceTypeList, switchPortalEntryGroup as switchPortalEntryGroupApi, } from '@/api/home' import { getFeaturedCases as getFeaturedCasesApi } from '@/api/cases' import { toCase } from '@/utils/caseVo' import { formatBusinessType, toBusinessType } from '@/utils/format' import { useCasesStore } from '@/stores/modules/cases' function stripHtml(html: string): string { return html .replace(//gi, '\n') .replace(/<\/(p|div|li|h[1-6]|tr)>/gi, '\n') .replace(/<[^>]*>/g, '') .replace(/ /gi, ' ') .replace(/&/gi, '&') .replace(/</gi, '<') .replace(/>/gi, '>') .replace(/\n{3,}/g, '\n\n') .trim() } function toBanners(list: BannerVo[]): Banner[] { return list .filter((item) => item.enabled !== false && Boolean(item.imageUrl)) .sort((a, b) => (a.sort ?? 0) - (b.sort ?? 0)) .map((item) => ({ id: item.id ?? 0, imageUrl: item.imageUrl ?? '', })) } function toBusinessLine(vo: ProjectSubmissionVo): BusinessLine { const serviceTypes: ServiceType[] = (vo.serviceTypes ?? []).map((item) => ({ label: item.serviceName ?? '', desc: '', icon: item.iconUrl ?? '', })) const serviceContent = (vo.backgrounds ?? []) .map((item) => stripHtml(item.content ?? '')) .filter((text) => text.length > 0) .join('\n\n') const contents: ServiceContentBlock[] = (vo.backgrounds ?? []) .map((item) => ({ title: '', content: item.content ?? '' })) .filter((block) => block.content.trim().length > 0) const subtitle = vo.subtitle ?? '' return { id: toBusinessType(vo.businessType), name: vo.title ?? '', desc: subtitle, slogan: subtitle, icon: vo.logoUrl ?? '', serviceContent, contents, serviceTypes, video: '', } } function toBusinessLineFromInfo(info: ProjectSubmissionInfo): BusinessLine { const ps = info.projectSubmission ?? ({} as ProjectSubmissionFlat) const serviceTypes: ServiceType[] = (info.projectServiceType ?? []).map((item) => ({ label: item.serviceName ?? '', desc: '', icon: item.iconUrl ?? '', })) const serviceContent = (info.projectSubmissionCatalog ?? []) .map((item) => stripHtml(item.content ?? '')) .filter((text) => text.length > 0) .join('\n\n') const contents: ServiceContentBlock[] = (info.projectSubmissionCatalog ?? []) .map((item) => ({ title: item.title ?? '', content: item.content ?? '' })) .filter((block) => block.content.trim().length > 0) const subtitle = ps.subtitle ?? '' return { id: toBusinessType(ps.businessType), name: ps.title ?? '', desc: subtitle, slogan: subtitle, icon: ps.logoUrl ?? '', serviceContent, contents, serviceTypes, video: '', } } const EMPTY_STATUS: ProjectStatusCount = { projectCount: 0, ongoingProjectCount: 0, pendingProjectCount: 0, } export const useHomeStore = defineStore('home', () => { const casesStore = useCasesStore() const stats = ref({ pending: 0, inProgress: 0, total: 0 }) const banners = ref([]) const businessLines = ref([]) const featuredCases = ref([]) const currentBusiness = ref(null) const submissions = ref([]) const portalEntries = ref([]) const portalEntriesLoaded = ref(false) const quickEntries = computed(() => portalEntries.value.filter((item) => item.groupType === '1'), ) const otherEntries = computed(() => portalEntries.value.filter((item) => item.groupType !== '1'), ) async function fetchPortalEntries(): Promise { try { const list = await getPortalEntryList() portalEntries.value = list ?? [] portalEntriesLoaded.value = true } catch { portalEntries.value = [] portalEntriesLoaded.value = false } } async function switchEntryGroup(id: number, type: PortalEntryGroup): Promise { await switchPortalEntryGroupApi(id, type) await fetchPortalEntries() } async function ensureSubmissions(): Promise { if (submissions.value.length === 0) { submissions.value = await getServiceTypeList() } } async function fetchHomeData(): Promise { const [statusCount, submissionList, cases, carouselList] = await Promise.all([ getProjectStatusCount(), getServiceTypeList(), getFeaturedCasesApi(), getCarouselList().catch(() => [] as BannerVo[]), casesStore.ensureCategories(), ]) const status = statusCount ?? EMPTY_STATUS stats.value = { pending: status.pendingProjectCount ?? 0, inProgress: status.ongoingProjectCount ?? 0, total: status.projectCount ?? 0, } submissions.value = submissionList businessLines.value = submissionList.map(toBusinessLine) featuredCases.value = cases.map((vo) => toCase(vo, casesStore.typeNameById)) banners.value = toBanners(carouselList ?? []) await fetchPortalEntries() } async function fetchFeaturedCases(category: BusinessType | 'all' = 'all'): Promise { if (category === 'all') { featuredCases.value = (await getFeaturedCasesApi()).map((vo) => toCase(vo, casesStore.typeNameById)) return } await casesStore.ensureCategories() const name = formatBusinessType(category) const typeId = Object.entries(casesStore.typeNameById).find(([, value]) => value === name)?.[0] if (!typeId) { featuredCases.value = [] return } featuredCases.value = (await getFeaturedCasesApi(typeId)).map((vo) => toCase(vo, casesStore.typeNameById)) } async function fetchBusinessDetail(type: BusinessType): Promise { await ensureSubmissions() const target = submissions.value.find((item) => toBusinessType(item.businessType) === type) if (!target || target.id == null) { currentBusiness.value = null return } currentBusiness.value = toBusinessLineFromInfo(await getServiceTypeDetail(target.id)) } return { stats, banners, businessLines, featuredCases, currentBusiness, portalEntries, portalEntriesLoaded, quickEntries, otherEntries, fetchHomeData, fetchFeaturedCases, fetchBusinessDetail, fetchPortalEntries, switchEntryGroup, } })