223 lines
6.7 KiB
TypeScript
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(/ /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<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,
|
|
}
|
|
})
|