Files
cz-web/src/stores/modules/home.ts
2026-10-01 17:24:57 +08:00

223 lines
6.7 KiB
TypeScript

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(/<br\s*\/?>/gi, '\n')
.replace(/<\/(p|div|li|h[1-6]|tr)>/gi, '\n')
.replace(/<[^>]*>/g, '')
.replace(/&nbsp;/gi, ' ')
.replace(/&amp;/gi, '&')
.replace(/&lt;/gi, '<')
.replace(/&gt;/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<HomeStats>({ pending: 0, inProgress: 0, total: 0 })
const banners = ref<Banner[]>([])
const businessLines = ref<BusinessLine[]>([])
const featuredCases = ref<Case[]>([])
const currentBusiness = ref<BusinessLine | null>(null)
const submissions = ref<ProjectSubmissionVo[]>([])
const portalEntries = ref<PortalEntryVo[]>([])
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<void> {
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<void> {
await switchPortalEntryGroupApi(id, type)
await fetchPortalEntries()
}
async function ensureSubmissions(): Promise<void> {
if (submissions.value.length === 0) {
submissions.value = await getServiceTypeList()
}
}
async function fetchHomeData(): Promise<void> {
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<void> {
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<void> {
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,
}
})