|
@@ -1,7 +1,4 @@
|
|
|
import request from '@/config/axios'
|
|
import request from '@/config/axios'
|
|
|
-import { getBuildList, getBuildData } from '@/api/system/badManage'
|
|
|
|
|
-import { getElderlyPage, getWeeklyOperationSummary } from '@/api/elderly/elder/elderly-Info'
|
|
|
|
|
-import { getTenantId } from '@/utils/auth'
|
|
|
|
|
import { useUserStore } from '@/store/modules/user'
|
|
import { useUserStore } from '@/store/modules/user'
|
|
|
|
|
|
|
|
export interface CampusOption {
|
|
export interface CampusOption {
|
|
@@ -12,28 +9,14 @@ export interface CampusOption {
|
|
|
export interface CampusBedStat {
|
|
export interface CampusBedStat {
|
|
|
tenantId: number
|
|
tenantId: number
|
|
|
campusName: string
|
|
campusName: string
|
|
|
- buildCount: number
|
|
|
|
|
totalBed: number
|
|
totalBed: number
|
|
|
usedBed: number
|
|
usedBed: number
|
|
|
unusedBed: number
|
|
unusedBed: number
|
|
|
tempBed: number
|
|
tempBed: number
|
|
|
|
|
+ /** 当前在住人数(periodEndCount,长者人员口径,区别于在住床位) */
|
|
|
|
|
+ inHospitalCount: number
|
|
|
/** 入住率(0-100),未配置床位时为 null */
|
|
/** 入住率(0-100),未配置床位时为 null */
|
|
|
occupancyRate: number | null
|
|
occupancyRate: number | null
|
|
|
- /** 楼栋统计接口调用失败的数量 */
|
|
|
|
|
- failed: number
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-interface BuildItem {
|
|
|
|
|
- id: number
|
|
|
|
|
- buildName: string
|
|
|
|
|
- tenantId: number
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-interface BuildData {
|
|
|
|
|
- totalBedCount?: number
|
|
|
|
|
- usedBedCount?: number
|
|
|
|
|
- unusedBedCount?: number
|
|
|
|
|
- temporaryOccupationCount?: number
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -56,102 +39,7 @@ export const getCampusOptions = (): CampusOption[] => {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 限制并发数执行任务
|
|
|
|
|
- */
|
|
|
|
|
-const runWithConcurrency = async <T, R>(
|
|
|
|
|
- items: T[],
|
|
|
|
|
- limit: number,
|
|
|
|
|
- task: (item: T, index: number) => Promise<R>
|
|
|
|
|
-): Promise<R[]> => {
|
|
|
|
|
- const results: R[] = new Array(items.length)
|
|
|
|
|
- let cursor = 0
|
|
|
|
|
- const workers = Array.from({ length: Math.min(limit, items.length) }, async () => {
|
|
|
|
|
- while (cursor < items.length) {
|
|
|
|
|
- const index = cursor
|
|
|
|
|
- cursor += 1
|
|
|
|
|
- results[index] = await task(items[index], index)
|
|
|
|
|
- }
|
|
|
|
|
- })
|
|
|
|
|
- await Promise.all(workers)
|
|
|
|
|
- return results
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * 聚合各院区的楼栋床位实时数据:
|
|
|
|
|
- * 1. 一次查询全部楼栋(后端可能返回所选院区之外的数据,这里按 tenantIds 过滤)
|
|
|
|
|
- * 2. 并发查询每栋楼的床位统计(getBuildData)
|
|
|
|
|
- * 3. 按院区汇总
|
|
|
|
|
- */
|
|
|
|
|
-export const fetchCampusBedStats = async (tenantIds: number[]): Promise<CampusBedStat[]> => {
|
|
|
|
|
- const nameMap = new Map<number, string>()
|
|
|
|
|
- getCampusOptions().forEach((item) => nameMap.set(item.value, item.label))
|
|
|
|
|
-
|
|
|
|
|
- const buildList: BuildItem[] = ((await getBuildList({ tenantIds, orgType: 1 })) || []).filter(
|
|
|
|
|
- (item: BuildItem) => tenantIds.includes(item.tenantId)
|
|
|
|
|
- )
|
|
|
|
|
-
|
|
|
|
|
- const buildDataList = await runWithConcurrency(buildList, 6, async (build) => {
|
|
|
|
|
- try {
|
|
|
|
|
- const data: BuildData = await getBuildData(build.id)
|
|
|
|
|
- return { tenantId: build.tenantId, data: data || {}, failed: false }
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- return { tenantId: build.tenantId, data: {} as BuildData, failed: true }
|
|
|
|
|
- }
|
|
|
|
|
- })
|
|
|
|
|
-
|
|
|
|
|
- const aggMap = new Map<
|
|
|
|
|
- number,
|
|
|
|
|
- {
|
|
|
|
|
- buildCount: number
|
|
|
|
|
- totalBed: number
|
|
|
|
|
- usedBed: number
|
|
|
|
|
- unusedBed: number
|
|
|
|
|
- tempBed: number
|
|
|
|
|
- failed: number
|
|
|
|
|
- }
|
|
|
|
|
- >()
|
|
|
|
|
-
|
|
|
|
|
- buildList.forEach((build, index) => {
|
|
|
|
|
- const stat = buildDataList[index]
|
|
|
|
|
- if (!aggMap.has(build.tenantId)) {
|
|
|
|
|
- aggMap.set(build.tenantId, {
|
|
|
|
|
- buildCount: 0,
|
|
|
|
|
- totalBed: 0,
|
|
|
|
|
- usedBed: 0,
|
|
|
|
|
- unusedBed: 0,
|
|
|
|
|
- tempBed: 0,
|
|
|
|
|
- failed: 0
|
|
|
|
|
- })
|
|
|
|
|
- }
|
|
|
|
|
- const agg = aggMap.get(build.tenantId)!
|
|
|
|
|
- agg.buildCount += 1
|
|
|
|
|
- agg.totalBed += Number(stat.data.totalBedCount) || 0
|
|
|
|
|
- agg.usedBed += Number(stat.data.usedBedCount) || 0
|
|
|
|
|
- agg.unusedBed += Number(stat.data.unusedBedCount) || 0
|
|
|
|
|
- agg.tempBed += Number(stat.data.temporaryOccupationCount) || 0
|
|
|
|
|
- if (stat.failed) agg.failed += 1
|
|
|
|
|
- })
|
|
|
|
|
-
|
|
|
|
|
- return tenantIds.map((tenantId) => {
|
|
|
|
|
- const agg = aggMap.get(tenantId) || {
|
|
|
|
|
- buildCount: 0,
|
|
|
|
|
- totalBed: 0,
|
|
|
|
|
- usedBed: 0,
|
|
|
|
|
- unusedBed: 0,
|
|
|
|
|
- tempBed: 0,
|
|
|
|
|
- failed: 0
|
|
|
|
|
- }
|
|
|
|
|
- return {
|
|
|
|
|
- tenantId,
|
|
|
|
|
- campusName: nameMap.get(tenantId) || `院区${tenantId}`,
|
|
|
|
|
- ...agg,
|
|
|
|
|
- occupancyRate: agg.totalBed > 0 ? Math.round((agg.usedBed / agg.totalBed) * 1000) / 10 : null
|
|
|
|
|
- }
|
|
|
|
|
- })
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * 护理等级分布项(看板接口返回的原始结构)
|
|
|
|
|
|
|
+ * 护理等级分布项
|
|
|
*/
|
|
*/
|
|
|
export interface NameCountItem {
|
|
export interface NameCountItem {
|
|
|
name: string
|
|
name: string
|
|
@@ -159,7 +47,7 @@ export interface NameCountItem {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 将看板接口中各院区自定义的护理等级名称按同名合并,返回降序排列结果
|
|
|
|
|
|
|
+ * 将各院区自定义的护理等级名称按同名合并,返回降序排列结果
|
|
|
*/
|
|
*/
|
|
|
export const mergeNameCount = (list: NameCountItem[]): NameCountItem[] => {
|
|
export const mergeNameCount = (list: NameCountItem[]): NameCountItem[] => {
|
|
|
const map = new Map<string, number>()
|
|
const map = new Map<string, number>()
|
|
@@ -200,63 +88,57 @@ const buildRecentMonths = (months: number): MonthCountItem[] => {
|
|
|
return list
|
|
return list
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/**
|
|
|
|
|
- * 在院人数:长者档案在院状态(inStatus=1)总数,按所选院区过滤
|
|
|
|
|
- */
|
|
|
|
|
-export const fetchInHospitalCount = async (tenantIds: number[]): Promise<number | null> => {
|
|
|
|
|
- try {
|
|
|
|
|
- const res: any = await getElderlyPage({
|
|
|
|
|
- tenantIds,
|
|
|
|
|
- inStatus: 1,
|
|
|
|
|
- pageNo: 1,
|
|
|
|
|
- pageSize: 1,
|
|
|
|
|
- orgType: 1
|
|
|
|
|
- })
|
|
|
|
|
- return Number(res?.total) || 0
|
|
|
|
|
- } catch {
|
|
|
|
|
- return null
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * 逐院区查询在院人数(长者档案人员口径,区别于楼栋"在住床位"口径)
|
|
|
|
|
- * 返回 tenantId -> 在院人数(查询失败院区为 null)
|
|
|
|
|
- */
|
|
|
|
|
-export const fetchCampusInHospitalMap = async (
|
|
|
|
|
- tenantIds: number[]
|
|
|
|
|
-): Promise<Map<number, number | null>> => {
|
|
|
|
|
- const values = await runWithConcurrency(tenantIds, 6, async (id) => fetchInHospitalCount([id]))
|
|
|
|
|
- const map = new Map<number, number | null>()
|
|
|
|
|
- tenantIds.forEach((id, index) => map.set(id, values[index]))
|
|
|
|
|
- return map
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * 是否为"只查看登录机构自身"的单院区场景。
|
|
|
|
|
- * 运营周期汇总接口 getWeeklyOperationSummary 只按当前登录机构返回:
|
|
|
|
|
- * 机构账号查看自身时口径正确可直接使用;集团账号(getTenantId 为集团 id)不满足。
|
|
|
|
|
- */
|
|
|
|
|
-const isOwnSingleCampus = (tenantIds: number[]): boolean => {
|
|
|
|
|
- if (!tenantIds || tenantIds.length !== 1) return false
|
|
|
|
|
- const loginTenantId = Number(getTenantId())
|
|
|
|
|
- return !!loginTenantId && loginTenantId === tenantIds[0]
|
|
|
|
|
|
|
+/** 运营周期(周/月)汇总,多院区已在前端按机构求和 */
|
|
|
|
|
+export interface OverviewWeekly {
|
|
|
|
|
+ /** 本周开始日期(上周六) */
|
|
|
|
|
+ periodBegin: string
|
|
|
|
|
+ /** 本周结束日期(本周五) */
|
|
|
|
|
+ periodEnd: string
|
|
|
|
|
+ /** 本周期初在院人数 */
|
|
|
|
|
+ periodBeginCount: number
|
|
|
|
|
+ /** 本周期末在院人数(当前在住人数) */
|
|
|
|
|
+ periodEndCount: number
|
|
|
|
|
+ weeklyCheckInCount: number
|
|
|
|
|
+ weeklyCheckOutCount: number
|
|
|
|
|
+ weeklyNetGrowthCount: number
|
|
|
|
|
+ monthlyCheckInCount: number
|
|
|
|
|
+ monthlyCheckOutCount: number
|
|
|
|
|
+ monthlyNetGrowthCount: number
|
|
|
|
|
+ /** 期末空余床位数(总床位 - 在住床位) */
|
|
|
|
|
+ periodEndEmptyBedCount: number
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/** 后端 dynamics 接口单个机构的原始返回 */
|
|
/** 后端 dynamics 接口单个机构的原始返回 */
|
|
|
interface OverviewDynamicsCampusVO {
|
|
interface OverviewDynamicsCampusVO {
|
|
|
tenantId: number
|
|
tenantId: number
|
|
|
- tenantName: string
|
|
|
|
|
|
|
+ tenantName?: string
|
|
|
newCheckInCount?: number
|
|
newCheckInCount?: number
|
|
|
checkOutCount?: number
|
|
checkOutCount?: number
|
|
|
askLeaveCount?: number
|
|
askLeaveCount?: number
|
|
|
todayAskLeaveCount?: number
|
|
todayAskLeaveCount?: number
|
|
|
waitingCount?: number
|
|
waitingCount?: number
|
|
|
reservationCount?: number
|
|
reservationCount?: number
|
|
|
|
|
+ periodBegin?: string
|
|
|
|
|
+ periodEnd?: string
|
|
|
|
|
+ periodBeginCount?: number
|
|
|
|
|
+ weeklyCheckInCount?: number
|
|
|
|
|
+ weeklyCheckOutCount?: number
|
|
|
|
|
+ periodEndCount?: number
|
|
|
|
|
+ weeklyNetGrowthCount?: number
|
|
|
|
|
+ monthlyCheckInCount?: number
|
|
|
|
|
+ monthlyCheckOutCount?: number
|
|
|
|
|
+ monthlyNetGrowthCount?: number
|
|
|
|
|
+ periodEndEmptyBedCount?: number
|
|
|
|
|
+ totalBedCount?: number
|
|
|
|
|
+ occupiedBedCount?: number
|
|
|
|
|
+ occupancyRate?: string
|
|
|
|
|
+ emptyBedCount?: number
|
|
|
|
|
+ temporaryOccupationCount?: number
|
|
|
retreatTrend?: { month: string; count: number }[]
|
|
retreatTrend?: { month: string; count: number }[]
|
|
|
nurseLevelDistribution?: { nurseLevelId?: number; nurseLevelName?: string; count?: number }[]
|
|
nurseLevelDistribution?: { nurseLevelId?: number; nurseLevelName?: string; count?: number }[]
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/** 运营总览动态指标(多院区已在前端聚合求和) */
|
|
|
|
|
|
|
+/** 运营总览聚合数据(KPI + 床位 + 周期 + 趋势 + 护理等级,多院区已聚合) */
|
|
|
export interface OverviewDynamics {
|
|
export interface OverviewDynamics {
|
|
|
/** 今日实际完成入住办理人数 */
|
|
/** 今日实际完成入住办理人数 */
|
|
|
todayCheckInCount: number
|
|
todayCheckInCount: number
|
|
@@ -270,18 +152,35 @@ export interface OverviewDynamics {
|
|
|
waitingCount: number
|
|
waitingCount: number
|
|
|
/** 预订在途人数 */
|
|
/** 预订在途人数 */
|
|
|
reservationCount: number
|
|
reservationCount: number
|
|
|
|
|
+ /** 当前在住人数(各机构 periodEndCount 求和,长者人员口径) */
|
|
|
|
|
+ inHospitalCount: number
|
|
|
|
|
+ /** 各院区床位 + 在院明细(按入参数组顺序,缺数据院区补 0) */
|
|
|
|
|
+ campusStats: CampusBedStat[]
|
|
|
|
|
+ /** 运营周期(周/月)汇总 */
|
|
|
|
|
+ weekly: OverviewWeekly
|
|
|
/** 近 12 个月已完成退住趋势(多院区按月求和) */
|
|
/** 近 12 个月已完成退住趋势(多院区按月求和) */
|
|
|
checkoutTrend: MonthCountItem[]
|
|
checkoutTrend: MonthCountItem[]
|
|
|
/** 在院长者护理等级分布(跨院区按同名合并,降序) */
|
|
/** 在院长者护理等级分布(跨院区按同名合并,降序) */
|
|
|
nursingLevels: NameCountItem[]
|
|
nursingLevels: NameCountItem[]
|
|
|
- /** 运营周期(周/月)汇总;仅登录机构查看自身时有值,多院区待后端扩展 */
|
|
|
|
|
- weekly: Record<string, any> | null
|
|
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const sumBy = (list: OverviewDynamicsCampusVO[], key: keyof OverviewDynamicsCampusVO) =>
|
|
|
|
|
+ list.reduce((acc, item) => acc + (Number(item[key]) || 0), 0)
|
|
|
|
|
+
|
|
|
|
|
+/** 后端返回的入住率为字符串(如 "75.84%"),解析为数值 */
|
|
|
|
|
+const parseRate = (rate?: string): number | null => {
|
|
|
|
|
+ if (rate === null || rate === undefined || rate === '') return null
|
|
|
|
|
+ const value = parseFloat(String(rate).replace('%', ''))
|
|
|
|
|
+ return Number.isNaN(value) ? null : value
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 运营总览动态指标聚合:GET /operation-analysis/overview/dynamics
|
|
|
|
|
- * 后端按机构分组返回(每机构一项),这里做跨院区聚合:
|
|
|
|
|
|
|
+ * 运营总览全量聚合:GET /operation-analysis/overview/dynamics
|
|
|
|
|
+ * 后端按机构分组返回(每机构一项),一次返回:
|
|
|
|
|
+ * 今日动态(新收/退院/请假/当天请假/轮候/预订)、床位统计、运营周期汇总、
|
|
|
|
|
+ * 近 12 个月退住趋势、护理等级分布。这里做跨院区聚合:
|
|
|
* - 数值指标求和;
|
|
* - 数值指标求和;
|
|
|
|
|
+ * - 各院区床位明细直接透传,入住率采用后端字符串解析值;
|
|
|
* - retreatTrend 按月求和并按近 12 个月骨架零填充;
|
|
* - retreatTrend 按月求和并按近 12 个月骨架零填充;
|
|
|
* - nurseLevelDistribution 按护理等级**名称**跨院区同名合并
|
|
* - nurseLevelDistribution 按护理等级**名称**跨院区同名合并
|
|
|
* (各院区等级字典独立、id 不通用,实测同名"一级护理"存在多个不同 id)。
|
|
* (各院区等级字典独立、id 不通用,实测同名"一级护理"存在多个不同 id)。
|
|
@@ -300,8 +199,25 @@ export const fetchOverviewDynamics = async (
|
|
|
})
|
|
})
|
|
|
if (!Array.isArray(list)) return null
|
|
if (!Array.isArray(list)) return null
|
|
|
|
|
|
|
|
- const sum = (key: keyof OverviewDynamicsCampusVO) =>
|
|
|
|
|
- list.reduce((acc, item) => acc + (Number(item[key]) || 0), 0)
|
|
|
|
|
|
|
+ const dataMap = new Map(list.map((item) => [item.tenantId, item]))
|
|
|
|
|
+ const nameMap = new Map<number, string>()
|
|
|
|
|
+ getCampusOptions().forEach((item) => nameMap.set(item.value, item.label))
|
|
|
|
|
+
|
|
|
|
|
+ // 各院区床位 + 在院明细
|
|
|
|
|
+ const campusStats: CampusBedStat[] = tenantIds.map((tenantId) => {
|
|
|
|
|
+ const item = dataMap.get(tenantId)
|
|
|
|
|
+ const totalBed = Number(item?.totalBedCount) || 0
|
|
|
|
|
+ return {
|
|
|
|
|
+ tenantId,
|
|
|
|
|
+ campusName: item?.tenantName || nameMap.get(tenantId) || `院区${tenantId}`,
|
|
|
|
|
+ totalBed,
|
|
|
|
|
+ usedBed: Number(item?.occupiedBedCount) || 0,
|
|
|
|
|
+ unusedBed: Number(item?.emptyBedCount) || 0,
|
|
|
|
|
+ tempBed: Number(item?.temporaryOccupationCount) || 0,
|
|
|
|
|
+ inHospitalCount: Number(item?.periodEndCount) || 0,
|
|
|
|
|
+ occupancyRate: totalBed > 0 ? parseRate(item?.occupancyRate) : null
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
|
|
|
// 退住趋势:按月合并
|
|
// 退住趋势:按月合并
|
|
|
const trend = buildRecentMonths(12)
|
|
const trend = buildRecentMonths(12)
|
|
@@ -323,23 +239,34 @@ export const fetchOverviewDynamics = async (
|
|
|
)
|
|
)
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
- // 运营周期汇总:仅单机构自身场景取现网接口;多院区后端尚未提供
|
|
|
|
|
- let weekly: Record<string, any> | null = null
|
|
|
|
|
- if (isOwnSingleCampus(tenantIds)) {
|
|
|
|
|
- const weeklyRes: any = await getWeeklyOperationSummary()
|
|
|
|
|
- weekly = weeklyRes?.data ?? weeklyRes ?? null
|
|
|
|
|
|
|
+ // 运营周期汇总:各机构周期日期一致,数值求和
|
|
|
|
|
+ const firstWithPeriod = list.find((item) => item.periodBegin && item.periodEnd)
|
|
|
|
|
+ const weekly: OverviewWeekly = {
|
|
|
|
|
+ periodBegin: firstWithPeriod?.periodBegin || '',
|
|
|
|
|
+ periodEnd: firstWithPeriod?.periodEnd || '',
|
|
|
|
|
+ periodBeginCount: sumBy(list, 'periodBeginCount'),
|
|
|
|
|
+ periodEndCount: sumBy(list, 'periodEndCount'),
|
|
|
|
|
+ weeklyCheckInCount: sumBy(list, 'weeklyCheckInCount'),
|
|
|
|
|
+ weeklyCheckOutCount: sumBy(list, 'weeklyCheckOutCount'),
|
|
|
|
|
+ weeklyNetGrowthCount: sumBy(list, 'weeklyNetGrowthCount'),
|
|
|
|
|
+ monthlyCheckInCount: sumBy(list, 'monthlyCheckInCount'),
|
|
|
|
|
+ monthlyCheckOutCount: sumBy(list, 'monthlyCheckOutCount'),
|
|
|
|
|
+ monthlyNetGrowthCount: sumBy(list, 'monthlyNetGrowthCount'),
|
|
|
|
|
+ periodEndEmptyBedCount: sumBy(list, 'periodEndEmptyBedCount')
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
return {
|
|
|
- todayCheckInCount: sum('newCheckInCount'),
|
|
|
|
|
- todayCheckOutCount: sum('checkOutCount'),
|
|
|
|
|
- leaveOutCount: sum('askLeaveCount'),
|
|
|
|
|
- todayLeaveCount: sum('todayAskLeaveCount'),
|
|
|
|
|
- waitingCount: sum('waitingCount'),
|
|
|
|
|
- reservationCount: sum('reservationCount'),
|
|
|
|
|
|
|
+ todayCheckInCount: sumBy(list, 'newCheckInCount'),
|
|
|
|
|
+ todayCheckOutCount: sumBy(list, 'checkOutCount'),
|
|
|
|
|
+ leaveOutCount: sumBy(list, 'askLeaveCount'),
|
|
|
|
|
+ todayLeaveCount: sumBy(list, 'todayAskLeaveCount'),
|
|
|
|
|
+ waitingCount: sumBy(list, 'waitingCount'),
|
|
|
|
|
+ reservationCount: sumBy(list, 'reservationCount'),
|
|
|
|
|
+ inHospitalCount: weekly.periodEndCount,
|
|
|
|
|
+ campusStats,
|
|
|
|
|
+ weekly,
|
|
|
checkoutTrend: trend,
|
|
checkoutTrend: trend,
|
|
|
- nursingLevels,
|
|
|
|
|
- weekly
|
|
|
|
|
|
|
+ nursingLevels
|
|
|
}
|
|
}
|
|
|
} catch {
|
|
} catch {
|
|
|
return null
|
|
return null
|