|
|
@@ -12,11 +12,7 @@ import {
|
|
|
SankeyChart,
|
|
|
ThemeRiverChart
|
|
|
} from 'echarts/charts'
|
|
|
-import {
|
|
|
- DataZoomComponent,
|
|
|
- BrushComponent,
|
|
|
- RadarComponent
|
|
|
-} from 'echarts/components'
|
|
|
+import { DataZoomComponent, BrushComponent, RadarComponent } from 'echarts/components'
|
|
|
import * as echartsApi from '@/api/data-board/nine-safety'
|
|
|
import { getBuildList } from '@/api/system/badManage'
|
|
|
import { useUserStore } from '@/store/modules/user'
|
|
|
@@ -58,6 +54,7 @@ const handleBuildChange = (buildId: any) => {
|
|
|
const loading = ref(false)
|
|
|
const overview = ref<any>({})
|
|
|
const nineRiskDist = ref<any[]>([])
|
|
|
+const riskLevelRatios = ref<any[]>([])
|
|
|
const riskTrend = ref<any[]>([])
|
|
|
const floorHeatmap = ref<any>({})
|
|
|
const ageScatter = ref<any[]>([])
|
|
|
@@ -70,18 +67,31 @@ const nineRiskOptions = computed(() =>
|
|
|
[{ label: '全部', value: '' }].concat(
|
|
|
echartsApi.NINE_RISKS.map((r: any) => ({
|
|
|
label: r.name,
|
|
|
- value: r.name.replace('防', '')
|
|
|
+ value: r.name
|
|
|
}))
|
|
|
)
|
|
|
)
|
|
|
const filteredRedList = computed(() => {
|
|
|
if (!redRiskFilter.value) return redList.value
|
|
|
- return redList.value.filter((r: any) => Array.isArray(r.risks) && r.risks.includes(redRiskFilter.value))
|
|
|
+ const keyword = redRiskFilter.value.replace('防', '')
|
|
|
+ return redList.value.filter((r: any) =>
|
|
|
+ Array.isArray(r.risks) &&
|
|
|
+ r.risks.some((risk: any) => {
|
|
|
+ const s = String(risk || '').trim()
|
|
|
+ if (!s) return false
|
|
|
+ return (
|
|
|
+ s === redRiskFilter.value ||
|
|
|
+ s.replace('防', '') === keyword ||
|
|
|
+ s.includes(redRiskFilter.value) ||
|
|
|
+ redRiskFilter.value.includes(s)
|
|
|
+ )
|
|
|
+ })
|
|
|
+ )
|
|
|
})
|
|
|
const assessorWorkload = ref<any[]>([])
|
|
|
const nurseLevelRisk = ref<any[]>([])
|
|
|
+const nurseLevelRadarData = ref<any>({ indicators: [], series: [] })
|
|
|
const riskCorrelation = ref<any>({})
|
|
|
-const riskRadar = ref<any>({})
|
|
|
const bar3D = ref<any>({})
|
|
|
|
|
|
// ========================= 图表实例管理 =========================
|
|
|
@@ -222,19 +232,13 @@ const renderNineRiskDist = () => {
|
|
|
|
|
|
/** 2. 风险等级整体占比 - 玫瑰饼图 */
|
|
|
const renderOverallPie = () => {
|
|
|
- const counts = { none: 0, low: 0, medium: 0, high: 0 }
|
|
|
- nineRiskDist.value.forEach((r) => {
|
|
|
- counts.none += r.none
|
|
|
- counts.low += r.low
|
|
|
- counts.medium += r.medium
|
|
|
- counts.high += r.high
|
|
|
- })
|
|
|
- const data = [
|
|
|
- { name: '无风险', value: counts.none, color: COLORS.none },
|
|
|
- { name: '低危', value: counts.low, color: COLORS.low },
|
|
|
- { name: '中危', value: counts.medium, color: COLORS.medium },
|
|
|
- { name: '高危', value: counts.high, color: COLORS.high }
|
|
|
- ].filter((d) => d.value > 0)
|
|
|
+ const data = riskLevelRatios.value
|
|
|
+ .map((r: any) => ({
|
|
|
+ name: r.name,
|
|
|
+ value: r.value,
|
|
|
+ itemStyle: { color: r.color }
|
|
|
+ }))
|
|
|
+ .filter((d: any) => d.value > 0)
|
|
|
createChart('overallPie', {
|
|
|
backgroundColor: 'transparent',
|
|
|
tooltip: {
|
|
|
@@ -257,7 +261,7 @@ const renderOverallPie = () => {
|
|
|
itemStyle: { borderRadius: 8, borderColor: '#05132b', borderWidth: 2 },
|
|
|
label: { color: '#fff', formatter: '{b}\n{d}%', fontSize: 12 },
|
|
|
labelLine: { lineStyle: { color: '#557a9c' } },
|
|
|
- data: data.map((d) => ({ name: d.name, value: d.value, itemStyle: { color: d.color } }))
|
|
|
+ data
|
|
|
}
|
|
|
]
|
|
|
})
|
|
|
@@ -532,8 +536,8 @@ const renderNurseLevelFunnel = () => {
|
|
|
{
|
|
|
type: 'funnel',
|
|
|
left: '10%',
|
|
|
- top: 40,
|
|
|
- bottom: 20,
|
|
|
+ top: 50,
|
|
|
+ bottom: 10,
|
|
|
width: '80%',
|
|
|
label: { color: '#fff', fontSize: 12, formatter: '{b}: {c}' },
|
|
|
itemStyle: { borderColor: '#05132b', borderWidth: 1 },
|
|
|
@@ -547,15 +551,29 @@ const renderNurseLevelFunnel = () => {
|
|
|
})
|
|
|
}
|
|
|
|
|
|
-/** 9. 九防风险雷达图 */
|
|
|
+/** 9. 各护理级别 · 九防高危占比雷达 */
|
|
|
const renderRiskRadar = () => {
|
|
|
- const { indicators, series } = riskRadar.value
|
|
|
- if (!indicators || !series) return
|
|
|
- const colors = ['#ff5a3c', '#ff9966', '#ffd85a', '#00e0a7']
|
|
|
+ const { indicators, series } = nurseLevelRadarData.value
|
|
|
+ if (!indicators.length || !series.length) return
|
|
|
createChart('riskRadar', {
|
|
|
backgroundColor: 'transparent',
|
|
|
- tooltip: { textStyle: { color: '#fff' } },
|
|
|
- legend: { data: series.map((s: any) => s.name), textStyle: { color: COLORS.text }, top: 0 },
|
|
|
+ tooltip: {
|
|
|
+ textStyle: { color: '#fff' },
|
|
|
+ formatter: (p: any) => {
|
|
|
+ const s = series[p.dataIndex]
|
|
|
+ if (!s) return p.name
|
|
|
+ const lines = s.value.map((v: number, i: number) => {
|
|
|
+ return `${indicators[i].name}:${v}%(${s.count[i] ?? '-'} 人)`
|
|
|
+ })
|
|
|
+ return [`<strong>${s.name}</strong>`, ...lines].join('<br/>')
|
|
|
+ }
|
|
|
+ },
|
|
|
+ legend: {
|
|
|
+ data: series.map((s: any) => s.name),
|
|
|
+ textStyle: { color: COLORS.text },
|
|
|
+ top: 0,
|
|
|
+ type: 'scroll'
|
|
|
+ },
|
|
|
radar: {
|
|
|
indicator: indicators,
|
|
|
shape: 'polygon',
|
|
|
@@ -568,14 +586,7 @@ const renderRiskRadar = () => {
|
|
|
series: [
|
|
|
{
|
|
|
type: 'radar',
|
|
|
- areaStyle: { opacity: 0.15 },
|
|
|
- data: series.map((s: any, i: number) => ({
|
|
|
- name: s.name,
|
|
|
- value: s.value,
|
|
|
- lineStyle: { color: colors[i], width: 2 },
|
|
|
- itemStyle: { color: colors[i] },
|
|
|
- areaStyle: { color: colors[i], opacity: 0.15 }
|
|
|
- }))
|
|
|
+ data: series
|
|
|
}
|
|
|
]
|
|
|
})
|
|
|
@@ -589,12 +600,28 @@ const renderAssessorWorkload = () => {
|
|
|
backgroundColor: 'transparent',
|
|
|
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' }, textStyle: { color: '#fff' } },
|
|
|
legend: { data: ['评估人数', '发现高危'], textStyle: { color: COLORS.text }, top: 0 },
|
|
|
- grid: { left: 50, right: 30, top: 40, bottom: 30 },
|
|
|
+ grid: { left: 50, right: 30, top: 40, bottom: 90 },
|
|
|
+ dataZoom: [
|
|
|
+ {
|
|
|
+ type: 'slider',
|
|
|
+ show: true,
|
|
|
+ xAxisIndex: [0],
|
|
|
+ start: 0,
|
|
|
+ end: Math.min(40, (20 / data.length) * 100),
|
|
|
+ height: 14,
|
|
|
+ bottom: 8,
|
|
|
+ borderColor: 'transparent',
|
|
|
+ backgroundColor: 'rgba(10,47,94,0.3)',
|
|
|
+ fillerColor: 'rgba(0,170,255,0.25)',
|
|
|
+ handleStyle: { color: '#00aaff' },
|
|
|
+ textStyle: { color: COLORS.text }
|
|
|
+ }
|
|
|
+ ],
|
|
|
xAxis: {
|
|
|
type: 'category',
|
|
|
data: data.map((d) => d.name),
|
|
|
axisLine: { lineStyle: { color: COLORS.axisLine } },
|
|
|
- axisLabel: { color: COLORS.text }
|
|
|
+ axisLabel: { color: COLORS.text, rotate: 60, fontSize: 10, margin: 12 }
|
|
|
},
|
|
|
yAxis: {
|
|
|
type: 'value',
|
|
|
@@ -698,8 +725,10 @@ const render3DBar = () => {
|
|
|
createChart('bar3D', {
|
|
|
backgroundColor: 'transparent',
|
|
|
tooltip: {
|
|
|
- formatter: (p: any) =>
|
|
|
- `${floors[p.data[0]]} - ${risks[p.data[1]]}<br/>高危人数:${p.data[2]} 人`
|
|
|
+ formatter: (p: any) => {
|
|
|
+ const v = Array.isArray(p.value) ? p.value : p.data?.value || []
|
|
|
+ return `${floors[v[0]] ?? '-'} - ${risks[v[1]] ?? '-'}<br/>高危人数:${v[2] ?? '-'} 人`
|
|
|
+ }
|
|
|
},
|
|
|
visualMap: {
|
|
|
max: maxVal,
|
|
|
@@ -751,7 +780,6 @@ const render3DBar = () => {
|
|
|
})
|
|
|
}
|
|
|
|
|
|
-
|
|
|
/** 15. 评估完成情况 - 柱状图 */
|
|
|
const renderAssessCompletion = () => {
|
|
|
const data = nineRiskDist.value
|
|
|
@@ -849,41 +877,240 @@ const renderAllCharts = () => {
|
|
|
const loadAllData = async () => {
|
|
|
loading.value = true
|
|
|
disposeAll()
|
|
|
+ const tenantId = userStore.orgTenantId
|
|
|
const params = {
|
|
|
- month: filter.month || undefined,
|
|
|
+ trendMonths: 6,
|
|
|
buildId: filter.buildId || undefined,
|
|
|
- floorId: filter.floorId || undefined
|
|
|
+ floorId: filter.floorId || undefined,
|
|
|
+ tenantIds: tenantId ? (Array.isArray(tenantId) ? tenantId : [tenantId]) : undefined,
|
|
|
+ multiHighRiskThreshold: 1
|
|
|
}
|
|
|
try {
|
|
|
- const [ov, dist, trend, fhm, asc, scd, sig, red, awl, nlr, corr, radar, b3d] =
|
|
|
- await Promise.all([
|
|
|
- echartsApi.getNineSafetyOverview(params),
|
|
|
- echartsApi.getNineRiskDistribution(params),
|
|
|
- echartsApi.getRiskLevelTrend({ buildId: params.buildId }),
|
|
|
- echartsApi.getFloorRiskHeatmap({ month: params.month }),
|
|
|
- echartsApi.getAgeRiskScatter(params),
|
|
|
- echartsApi.getAssessScoreDistribution(params),
|
|
|
- echartsApi.getSignatureStatus(params),
|
|
|
- echartsApi.getHighRiskRedList(params),
|
|
|
- echartsApi.getAssessorWorkload(params),
|
|
|
- echartsApi.getRiskByNurseLevel(params),
|
|
|
- echartsApi.getNineRiskCorrelation(params),
|
|
|
- echartsApi.getRiskRadar(params),
|
|
|
- echartsApi.get3DRiskBar({ month: params.month })
|
|
|
- ])
|
|
|
- overview.value = ov
|
|
|
- nineRiskDist.value = dist
|
|
|
- riskTrend.value = trend
|
|
|
- floorHeatmap.value = fhm
|
|
|
- ageScatter.value = asc
|
|
|
- scoreDist.value = scd
|
|
|
- signature.value = sig
|
|
|
- redList.value = red
|
|
|
- assessorWorkload.value = awl
|
|
|
- nurseLevelRisk.value = nlr
|
|
|
- riskCorrelation.value = corr
|
|
|
- riskRadar.value = radar
|
|
|
- bar3D.value = b3d
|
|
|
+ // 真实接口:九防安全风险综合看板
|
|
|
+ const bd = (await echartsApi.nineRiskBoardGet(params)) || {}
|
|
|
+ const ov = bd.overview || {}
|
|
|
+
|
|
|
+ // 1. 顶部核心指标映射(保持页面模板字段名不变)
|
|
|
+ overview.value = {
|
|
|
+ totalElders: ov.disclosureTotal ?? 0,
|
|
|
+ highRiskCount: ov.highRiskTimes ?? 0,
|
|
|
+ highRiskRatio: ov.highRiskTimesRatio ?? 0,
|
|
|
+ pendingSignatures: ov.disclosureUnsignedCount ?? 0,
|
|
|
+ signedRate:
|
|
|
+ typeof ov.disclosureUnsignedRatio === 'number'
|
|
|
+ ? +(100 - ov.disclosureUnsignedRatio).toFixed(2)
|
|
|
+ : 100,
|
|
|
+ evalCompletionRate: ov.assessCompleteRate ?? 0,
|
|
|
+ assessorCount: ov.assessorCount ?? 0
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 九防风险等级分布映射(真实字段 → 图表内部字段)
|
|
|
+ nineRiskDist.value = (bd.itemRiskDistributions || []).map((item: any) => ({
|
|
|
+ name: normalizeRiskName(item.itemName),
|
|
|
+ high: item.highCount ?? 0,
|
|
|
+ medium: item.middleCount ?? 0,
|
|
|
+ low: item.lowCount ?? 0,
|
|
|
+ none: item.unknownCount ?? 0,
|
|
|
+ total: item.totalCount ?? 0
|
|
|
+ }))
|
|
|
+
|
|
|
+ // 3. 风险等级整体占比
|
|
|
+ const levelNameMap: Record<string, string> = {
|
|
|
+ none: '无风险',
|
|
|
+ low: '低危',
|
|
|
+ medium: '中危',
|
|
|
+ high: '高危',
|
|
|
+ unknown: '无风险'
|
|
|
+ }
|
|
|
+ const levelColorMap: Record<string, string> = {
|
|
|
+ none: COLORS.none,
|
|
|
+ low: COLORS.low,
|
|
|
+ medium: COLORS.medium,
|
|
|
+ high: COLORS.high,
|
|
|
+ unknown: COLORS.none
|
|
|
+ }
|
|
|
+ riskLevelRatios.value = (bd.riskLevelRatios || []).map((r: any) => ({
|
|
|
+ name: r.riskLevelName || levelNameMap[r.riskLevel] || r.riskLevel,
|
|
|
+ value: r.count ?? 0,
|
|
|
+ color: levelColorMap[r.riskLevel] || COLORS.none
|
|
|
+ }))
|
|
|
+
|
|
|
+ // 4. 风险等级月度趋势
|
|
|
+ riskTrend.value = (bd.monthlyTrends || []).map((t: any) => ({
|
|
|
+ month: t.month,
|
|
|
+ high: t.highCount ?? 0,
|
|
|
+ medium: t.middleCount ?? 0,
|
|
|
+ low: t.lowCount ?? 0,
|
|
|
+ none: t.unknownCount ?? 0
|
|
|
+ }))
|
|
|
+
|
|
|
+ // 5. 九防 × 楼栋高危人数热力图(同时供 3D 柱状图复用)
|
|
|
+ const hm = bd.highRiskHeatMap || {}
|
|
|
+ const heatItems = (hm.items || []).map((i: any) => normalizeRiskName(i.name || i.itemName || ''))
|
|
|
+ const heatBuilds = (hm.builds || []).map((b: any) => b.name || b.buildName || '')
|
|
|
+
|
|
|
+ // 新结构通过 code/id 找索引,不再依赖隐式下标
|
|
|
+ const itemCodeToIndex = new Map<string, number>()
|
|
|
+ const buildIdToIndex = new Map<string | number, number>()
|
|
|
+ ;(hm.items || []).forEach((i: any, idx: number) => {
|
|
|
+ if (i.code) itemCodeToIndex.set(String(i.code), idx)
|
|
|
+ })
|
|
|
+ ;(hm.builds || []).forEach((b: any, idx: number) => {
|
|
|
+ if (b.code) buildIdToIndex.set(String(b.code), idx)
|
|
|
+ })
|
|
|
+
|
|
|
+ const heatCells = (hm.cells || [])
|
|
|
+ .map((c: any) => {
|
|
|
+ const x =
|
|
|
+ c.itemIndex ??
|
|
|
+ itemCodeToIndex.get(String(c.itemCode ?? '')) ??
|
|
|
+ c.x ??
|
|
|
+ c.column ??
|
|
|
+ -1
|
|
|
+ const y =
|
|
|
+ c.buildIndex ??
|
|
|
+ buildIdToIndex.get(String(c.buildId ?? '')) ??
|
|
|
+ c.y ??
|
|
|
+ c.row ??
|
|
|
+ -1
|
|
|
+ if (x < 0 || y < 0) return null
|
|
|
+ const v = c.highRiskElderCount ?? c.value ?? c.count ?? c.highCount ?? 0
|
|
|
+ // 热力图与 3D 柱状图均使用 [楼栋索引, 九防索引, 值]
|
|
|
+ return [y, x, v]
|
|
|
+ })
|
|
|
+ .filter(Boolean)
|
|
|
+
|
|
|
+ floorHeatmap.value = {
|
|
|
+ floors: heatBuilds,
|
|
|
+ risks: heatItems,
|
|
|
+ data: heatCells,
|
|
|
+ maxValue: hm.maxValue ?? 0
|
|
|
+ }
|
|
|
+ bar3D.value = {
|
|
|
+ floors: heatBuilds,
|
|
|
+ risks: heatItems,
|
|
|
+ data: heatCells,
|
|
|
+ maxValue: hm.maxValue ?? 0
|
|
|
+ }
|
|
|
+
|
|
|
+ // 6. 年龄 × 高风险项散点
|
|
|
+ ageScatter.value = (bd.highRiskElders || []).map((e: any) => ({
|
|
|
+ name: e.elderName,
|
|
|
+ age: e.elderAge,
|
|
|
+ sex: e.elderSex,
|
|
|
+ highCount: e.highRiskCount ?? 0,
|
|
|
+ room: '',
|
|
|
+ nurseLevel: e.nurseLevelName
|
|
|
+ }))
|
|
|
+
|
|
|
+ // 7. 评估得分分布(真实返回 minScore/maxScore + buckets,转成箱线图五数概括)
|
|
|
+ scoreDist.value = (bd.itemScoreDistributions || []).map((item: any) => {
|
|
|
+ const buckets = [...(item.buckets || [])].sort(
|
|
|
+ (a: any, b: any) => (a.min ?? 0) - (b.min ?? 0)
|
|
|
+ )
|
|
|
+ const total = buckets.reduce((s: number, b: any) => s + (b.count ?? 0), 0)
|
|
|
+ // 在桶内做线性插值,估算四分位数
|
|
|
+ const boundary = (p: number) => {
|
|
|
+ if (total === 0 || buckets.length === 0) return item.minScore ?? 0
|
|
|
+ const target = total * p
|
|
|
+ let cumulative = 0
|
|
|
+ for (const b of buckets) {
|
|
|
+ const count = b.count ?? 0
|
|
|
+ if (count === 0) continue
|
|
|
+ if (cumulative + count >= target) {
|
|
|
+ const ratio = (target - cumulative) / count
|
|
|
+ return (b.min ?? 0) + ((b.max ?? b.min ?? 0) - (b.min ?? 0)) * ratio
|
|
|
+ }
|
|
|
+ cumulative += count
|
|
|
+ }
|
|
|
+ return buckets[buckets.length - 1].max ?? item.maxScore ?? 0
|
|
|
+ }
|
|
|
+ const round2 = (n: number) => +n.toFixed(2)
|
|
|
+ return {
|
|
|
+ name: normalizeRiskName(item.itemName),
|
|
|
+ min: round2(item.minScore ?? buckets[0]?.min ?? 0),
|
|
|
+ q1: round2(boundary(0.25)),
|
|
|
+ median: round2(boundary(0.5)),
|
|
|
+ q3: round2(boundary(0.75)),
|
|
|
+ max: round2(item.maxScore ?? buckets[buckets.length - 1]?.max ?? 0)
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ // 8. 知情书签署状态
|
|
|
+ const sr = bd.disclosureSignRate || {}
|
|
|
+ signature.value = {
|
|
|
+ signed: sr.signedCount ?? 0,
|
|
|
+ firstSignedOnly: 0,
|
|
|
+ unsigned: sr.unsignedCount ?? 0,
|
|
|
+ total: sr.total ?? 0,
|
|
|
+ signedRate: sr.signRate ?? 0
|
|
|
+ }
|
|
|
+
|
|
|
+ // 9. 按护理级别 · 高危项漏斗 / 雷达
|
|
|
+ // 后端字段为 nurseLevelHighRiskElderCounts(漏斗),优先读取;兼容旧名 nurseLevelHighRiskRecordCounts
|
|
|
+ nurseLevelRisk.value = (
|
|
|
+ bd.nurseLevelHighRiskElderCounts ||
|
|
|
+ bd.nurseLevelHighRiskRecordCounts || []
|
|
|
+ ).map((n: any) => ({
|
|
|
+ name: n.nurseLevelName,
|
|
|
+ count: n.count ?? 0,
|
|
|
+ ratio: n.ratio ?? 0,
|
|
|
+ none: 0,
|
|
|
+ low: 0,
|
|
|
+ medium: 0,
|
|
|
+ high: n.count ?? 0
|
|
|
+ }))
|
|
|
+
|
|
|
+ // 9.1 各护理级别 · 九防高危占比雷达(新结构:每项护理级别带 items 数组)
|
|
|
+ const radarRecords = bd.nurseLevelHighRiskRecordCounts || []
|
|
|
+ if (radarRecords.length && Array.isArray(radarRecords[0].items)) {
|
|
|
+ // 以第一条护理级别的 items 顺序作为雷达 indicator
|
|
|
+ const indicators = radarRecords[0].items.map((it: any) => ({
|
|
|
+ name: normalizeRiskName(it.itemName),
|
|
|
+ max: 100
|
|
|
+ }))
|
|
|
+ const colors = ['#ff5a3c', '#00e0a7', '#ffd85a', '#00aaff', '#ff9966', '#5578ff', '#b380ff']
|
|
|
+ nurseLevelRadarData.value = {
|
|
|
+ indicators,
|
|
|
+ series: radarRecords.map((n: any, idx: number) => ({
|
|
|
+ name: n.nurseLevelName,
|
|
|
+ value: n.items.map((it: any) => +(it.ratio ?? 0).toFixed(2)),
|
|
|
+ count: n.items.map((it: any) => it.count ?? 0),
|
|
|
+ lineStyle: { color: colors[idx % colors.length], width: 2 },
|
|
|
+ itemStyle: { color: colors[idx % colors.length] },
|
|
|
+ areaStyle: { color: colors[idx % colors.length], opacity: 0.1 }
|
|
|
+ }))
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 10. 评估人工作量
|
|
|
+ assessorWorkload.value = (bd.assessorStats || []).map((a: any) => ({
|
|
|
+ name: a.assessor,
|
|
|
+ count: a.assessedElderCount ?? 0,
|
|
|
+ highCount: a.highRiskElderCount ?? 0
|
|
|
+ }))
|
|
|
+
|
|
|
+ // 12. 2 项及以上高风险长者列表(红名单)
|
|
|
+ redList.value = (bd.multiHighRiskElders || []).map((e: any) => ({
|
|
|
+ id: e.elderId,
|
|
|
+ name: e.elderName,
|
|
|
+ sex: e.elderSex === 1 ? '男' : '女',
|
|
|
+ age: e.elderAge,
|
|
|
+ room: e.bedName || '',
|
|
|
+ floor: e.buildName || '',
|
|
|
+ nurseLevel: e.nurseLevelName,
|
|
|
+ assessor: e.assessor,
|
|
|
+ risks: (() => {
|
|
|
+ const raw = e.highRiskItems
|
|
|
+ const arr = Array.isArray(raw)
|
|
|
+ ? raw.map((s: any) => normalizeRiskName(String(s).trim()))
|
|
|
+ : String(raw || '').split(',').map((s: string) => normalizeRiskName(s.trim()))
|
|
|
+ return arr.filter(Boolean)
|
|
|
+ })(),
|
|
|
+ highCount: e.highRiskCount ?? 0,
|
|
|
+ assessDate: formatAssessDate(e.assessDate)
|
|
|
+ }))
|
|
|
+
|
|
|
await nextTick()
|
|
|
renderAllCharts()
|
|
|
} catch (e) {
|
|
|
@@ -906,6 +1133,25 @@ const handleReset = () => {
|
|
|
}
|
|
|
|
|
|
// ========================= 红名单风险标签颜色 =========================
|
|
|
+const formatAssessDate = (v: any): string => {
|
|
|
+ if (!v) return '-'
|
|
|
+ if (Array.isArray(v)) {
|
|
|
+ const [y, m, d] = v
|
|
|
+ if (!y) return '-'
|
|
|
+ return `${y}-${String(m ?? 1).padStart(2, '0')}-${String(d ?? 1).padStart(2, '0')}`
|
|
|
+ }
|
|
|
+ if (v instanceof Date) {
|
|
|
+ const y = v.getFullYear()
|
|
|
+ const m = v.getMonth() + 1
|
|
|
+ const d = v.getDate()
|
|
|
+ return `${y}-${String(m).padStart(2, '0')}-${String(d).padStart(2, '0')}`
|
|
|
+ }
|
|
|
+ if (typeof v === 'string') return v.split(' ')[0] || '-'
|
|
|
+ return String(v)
|
|
|
+}
|
|
|
+// 后端把「防误食」命名为「简易精神状态」,前端统一做临时映射
|
|
|
+const normalizeRiskName = (name: string): string =>
|
|
|
+ name === '简易精神状态' ? '防误食' : name
|
|
|
const riskTagColor = (name: string): string => {
|
|
|
const map: Record<string, string> = {
|
|
|
噎食: '#ff9966',
|
|
|
@@ -1142,16 +1388,16 @@ onUnmounted(() => {
|
|
|
<div :ref="setRef('riskRadar')" class="chart"></div>
|
|
|
</div>
|
|
|
<!-- 评估人工作量 -->
|
|
|
- <div class="card">
|
|
|
+ <div class="card span-2">
|
|
|
<div class="card-title"><span class="dot dot-low"></span>评估人工作量统计</div>
|
|
|
<div :ref="setRef('assessorWorkload')" class="chart"></div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 相关性矩阵 -->
|
|
|
- <div class="card">
|
|
|
- <div class="card-title"><span class="dot dot-mid"></span>九防风险相关性矩阵</div>
|
|
|
- <div :ref="setRef('riskCorrelation')" class="chart"></div>
|
|
|
- </div>
|
|
|
+ <!-- <div class="card">-->
|
|
|
+ <!-- <div class="card-title"><span class="dot dot-mid"></span>九防风险相关性矩阵</div>-->
|
|
|
+ <!-- <div :ref="setRef('riskCorrelation')" class="chart"></div>-->
|
|
|
+ <!-- </div>-->
|
|
|
<!-- 评估完成率 -->
|
|
|
<div class="card">
|
|
|
<div class="card-title"><span class="dot dot-mid"></span>九防高危占比</div>
|
|
|
@@ -1182,13 +1428,8 @@ onUnmounted(() => {
|
|
|
/>
|
|
|
<path d="M12 9v4M12 17h.01" />
|
|
|
</svg>
|
|
|
- 重点人群红名单 · 2 项及以上高风险(共 {{ filteredRedList.length }} 人)
|
|
|
- <el-select
|
|
|
- v-model="redRiskFilter"
|
|
|
- placeholder="全部"
|
|
|
- clearable
|
|
|
- class="red-risk-select"
|
|
|
- >
|
|
|
+ 重点人群红名单 · 1 项及以上高风险(共 {{ filteredRedList.length }} 人)
|
|
|
+ <el-select v-model="redRiskFilter" placeholder="全部" clearable class="red-risk-select">
|
|
|
<el-option
|
|
|
v-for="opt in nineRiskOptions"
|
|
|
:key="opt.value"
|
|
|
@@ -1201,8 +1442,8 @@ onUnmounted(() => {
|
|
|
<table class="red-table">
|
|
|
<thead>
|
|
|
<tr>
|
|
|
- <th>档案编号</th><th>姓名</th><th>性别</th><th>年龄</th> <th>楼栋</th><th>床号</th
|
|
|
- ><th>护理级别</th><th>评估人</th> <th>高风险项</th><th>评估日期</th>
|
|
|
+ <th>档案编号</th><th>姓名</th><th>性别</th><th>年龄</th> <th>床号</th><th>护理级别</th
|
|
|
+ ><th>评估人</th> <th>高风险项</th><th>评估日期</th>
|
|
|
</tr>
|
|
|
</thead>
|
|
|
<tbody>
|
|
|
@@ -1211,25 +1452,25 @@ onUnmounted(() => {
|
|
|
<td class="name">{{ r.name }}</td>
|
|
|
<td>{{ r.sex }}</td>
|
|
|
<td>{{ r.age }} 岁</td>
|
|
|
- <td>{{ r.floor }}</td>
|
|
|
+
|
|
|
<td class="mono">{{ r.room }}</td>
|
|
|
<td>{{ r.nurseLevel }}</td>
|
|
|
- <td>{{ r.assessor }}</td>
|
|
|
+ <td style="max-width: 15vw">{{ r.assessor }}</td>
|
|
|
<td>
|
|
|
<span
|
|
|
v-for="rk in r.risks"
|
|
|
:key="rk"
|
|
|
class="risk-tag"
|
|
|
:style="{
|
|
|
- background: riskTagColor(rk) + '33',
|
|
|
- color: riskTagColor(rk),
|
|
|
- borderColor: riskTagColor(rk) + '66'
|
|
|
+ background: riskTagColor(rk.replace('防', '')) + '33',
|
|
|
+ color: riskTagColor(rk.replace('防', '')),
|
|
|
+ borderColor: riskTagColor(rk.replace('防', '')) + '66'
|
|
|
}"
|
|
|
>{{ rk }}</span
|
|
|
>
|
|
|
</td>
|
|
|
<td
|
|
|
- ><span class="high-count">2022-12-12</span></td
|
|
|
+ ><span class="high-count">{{ r.assessDate }}</span></td
|
|
|
>
|
|
|
</tr>
|
|
|
</tbody>
|