Files
comclib 01e1034cc1 feat: 全系统优化 — 并发控制 + 冗余清理 + 数据流修复 + 全面测试
核心修复:
- 状态机加 SELECT FOR UPDATE 行锁,消除并发竞态
- hss_md_staff 加 role 列,登录从数据库读取真实角色
- 申请重复校验排除自身,全流程 20 步闭环通过
- 派单 SQL 修复 + 支付状态机过渡 + 完成服务 plan_item_id 修复

并发控制新增:
- RedisLockService (SET NX PX + Lua 安全解锁)
- RateLimiterService (Redis 滑动窗口 + API 拦截器)
- TransactionIsolationConfig (SERIALIZABLE for 支付回调)
- MqttPublisher (异步队列 + JDK TCP 探测)
- ObjectStorageService (AWS SigV4 预签名, 纯 JDK)

冗余清理:
- 删除 6 个死代码文件 (~620 行)
- hutool-all → JDK MessageDigest, 去 MapStruct, 去 jsr310
- haversine 提取到 GeoUtil, count/round 提取到 JdbcUtil
- 创建 platform layout 组件

前端修复:
- 登录页移除角色选择器, 由后端 JWT 返回
- 移除 ClientOnly 包裹, 页面正常渲染
- SPA fallback Nginx 配置修复

Docker: 运行时镜像 eclipse-temurin:17-jre-jammy (缩小 ~300MB)

文档: 新增系统实现与修复报告.md

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 11:48:07 +08:00

233 lines
12 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useSeo } from '~/composables/useSeo'
import { useApi } from '~/composables/useApi'
import { capabilities } from '~/data/siteContent'
useSeo({ title: '平台演示', description: '智慧医养居家上门服务平台 — 功能演示与真实数据展示' })
const { get } = useApi()
const dashboard = ref<Record<string, any>>({})
const summary = ref<Record<string, any>>({})
const quality = ref<Record<string, any>>({})
const workOrders = ref<any[]>([])
const realOrders = ref<any[]>([])
const apps = ref<any[]>([])
const loading = ref(true)
const activeTab = ref('overview')
const tabs = [
{ key: 'overview', label: '管理看板' },
{ key: 'orders', label: '工单管理' },
{ key: 'dispatch', label: '派单调度' },
{ key: 'delivery', label: '移动执行' },
]
onMounted(async () => {
try {
const [d, s, q] = await Promise.all([
get<any>('/admin/dashboard').catch(() => ({})),
get<any>('/analytics/summary').catch(() => ({})),
get<any>('/analytics/quality').catch(() => ({})),
])
dashboard.value = d || {}
summary.value = s || {}
quality.value = q || {}
} catch (_) {}
loading.value = false
})
function fmt(n: any, def = '--') {
if (n === null || n === undefined) return def
if (typeof n === 'number') return n.toLocaleString()
return String(n)
}
</script>
<template>
<section class="py-16 bg-gradient-to-br from-primary-700 to-primary-900 text-white">
<div class="section-container">
<span class="text-sm text-blue-200 mb-2 block">Platform Demo</span>
<h1 class="text-4xl md:text-5xl font-bold mb-4">平台功能演示</h1>
<p class="text-lg text-blue-100 max-w-2xl">以下展示平台真实管理界面数据通过后端 API 实时获取</p>
</div>
</section>
<!-- Tab Nav -->
<div class="bg-white border-b sticky top-16 z-30">
<div class="section-container flex gap-0 overflow-x-auto">
<button v-for="t in tabs" :key="t.key" @click="activeTab = t.key"
class="px-6 py-4 text-sm font-medium border-b-2 transition-colors shrink-0"
:class="activeTab === t.key ? 'border-primary text-primary' : 'border-transparent text-text-secondary hover:text-text-primary'">
{{ t.label }}
</button>
</div>
</div>
<!-- Overview Tab -->
<section v-if="activeTab === 'overview'" class="py-12 bg-surface">
<div class="section-container space-y-8">
<div class="grid grid-cols-2 lg:grid-cols-4 gap-4">
<PlatformStatCard label="今日工单" :value="fmt(dashboard.todayOrders)" suffix="单" />
<PlatformStatCard label="进行中" :value="fmt(dashboard.inProgress)" suffix="单" />
<PlatformStatCard label="服务完成率" :value="fmt(quality.serviceCompletionRate)" suffix="%" />
<PlatformStatCard label="活跃服务人员" :value="fmt(dashboard.availableStaff)" suffix="人" />
</div>
<div class="grid lg:grid-cols-2 gap-8">
<PlatformSection title="实时工单状态分布" desc="各状态工单数量概览">
<div class="p-6 grid grid-cols-2 gap-3">
<div v-for="item in [
{ label: '待派单', v: dashboard.pendingDispatch, c: 'bg-gray-100' },
{ label: '进行中', v: dashboard.inProgress, c: 'bg-blue-50' },
{ label: '已完成', v: dashboard.completedToday, c: 'bg-green-50' },
{ label: '异常', v: dashboard.exceptions, c: 'bg-red-50' },
]" :key="item.label" :class="['rounded-xl p-4 text-center', item.c]">
<div class="text-2xl font-bold font-mono">{{ fmt(item.v) }}</div>
<div class="text-xs text-text-secondary mt-1">{{ item.label }}</div>
</div>
</div>
</PlatformSection>
<PlatformSection title="核心能力模块" desc="平台覆盖居家服务全流程">
<div class="p-6 grid grid-cols-2 gap-3">
<div v-for="c in capabilities.slice(0,8)" :key="c.title"
class="flex items-center gap-3 p-3 rounded-xl hover:bg-primary-50 transition-colors cursor-pointer group">
<div class="w-10 h-10 rounded-lg bg-primary-50 text-primary flex items-center justify-center group-hover:bg-primary group-hover:text-white transition-colors shrink-0">
<AppIcon :name="c.icon" class="w-5 h-5" />
</div>
<div class="min-w-0">
<div class="text-sm font-medium truncate">{{ c.title }}</div>
<div class="text-xs text-text-secondary truncate">{{ c.desc }}</div>
</div>
</div>
</div>
</PlatformSection>
</div>
</div>
</section>
<!-- Orders Tab (Platform UI Mockup -- 演示平台界面风格) -->
<section v-if="activeTab === 'orders'" class="py-12 bg-surface">
<div class="section-container space-y-6">
<PlatformSection title="工单管理" desc="受理、派单、接单、执行、完成全流程工单视图">
<div class="divide-y divide-gray-50">
<div class="flex items-center gap-4 px-4 py-3 bg-gray-50 text-xs font-medium text-text-secondary">
<span class="w-12">ID</span><span class="flex-1">服务对象</span><span class="w-16">类型</span><span class="w-24">日期</span><span class="w-24">状态</span><span class="w-20">人员</span>
</div>
<PlatformWorkOrderRow v-for="wo in [
{ id: 1001, patientName:'张奶奶', serviceType:'居家护理', status:'ORDER_IN_SERVICE', serviceDate:'2026-05-18', staffName:'李护理员' },
{ id: 1002, patientName:'王大爷', serviceType:'康复训练', status:'ORDER_COMPLETED', serviceDate:'2026-05-18', staffName:'张康复师' },
{ id: 1003, patientName:'赵阿姨', serviceType:'助浴服务', status:'ORDER_ASSIGNED', serviceDate:'2026-05-18', staffName:'--' },
{ id: 1004, patientName:'刘爷爷', serviceType:'健康管理', status:'ORDER_CHECKED_IN', serviceDate:'2026-05-18', staffName:'陈护理员' },
{ id: 1005, patientName:'孙奶奶', serviceType:'能力评估', status:'ORDER_EXCEPTION', serviceDate:'2026-05-17', staffName:'周评估员' },
]" :key="wo.id" v-bind="wo" />
</div>
</PlatformSection>
<PlatformSection title="工单状态流转" desc="状态机驱动的完整流转路径">
<div class="p-6">
<div class="flex flex-wrap items-center gap-2 text-xs">
<span class="px-3 py-1.5 rounded-full bg-gray-100">ORDER_CREATED</span><span class="text-gray-300">→</span>
<span class="px-3 py-1.5 rounded-full bg-blue-50 text-blue-600">ORDER_ASSIGNED</span><span class="text-gray-300">→</span>
<span class="px-3 py-1.5 rounded-full bg-indigo-50 text-indigo-600">ORDER_ACCEPTED</span><span class="text-gray-300">→</span>
<span class="px-3 py-1.5 rounded-full bg-teal-50 text-teal-600">ORDER_CHECKED_IN</span><span class="text-gray-300">→</span>
<span class="px-3 py-1.5 rounded-full bg-accent-50 text-accent-700">ORDER_IN_SERVICE</span><span class="text-gray-300">→</span>
<span class="px-3 py-1.5 rounded-full bg-green-50 text-green-600">ORDER_COMPLETED</span><span class="text-gray-300">→</span>
<span class="px-3 py-1.5 rounded-full bg-green-100 text-green-700">ACCEPTED</span>
</div>
</div>
</PlatformSection>
</div>
</section>
<!-- Dispatch Tab -->
<section v-if="activeTab === 'dispatch'" class="py-12 bg-surface">
<div class="section-container space-y-6">
<PlatformSection title="调度工作台" desc="智能推荐 + 人工确认,两阶段派单">
<div class="p-6 space-y-4">
<div class="flex items-center justify-between p-4 bg-primary-50 rounded-xl">
<div>
<div class="text-sm font-bold">#1003 赵阿姨 — 助浴服务</div>
<div class="text-xs text-text-secondary">梅江区金山街道 · 2026-05-18 09:00-10:00 · 低风险</div>
</div>
<span class="px-3 py-1.5 bg-primary text-white rounded-lg text-xs font-medium">待派单</span>
</div>
<div class="text-sm font-medium text-text-primary mt-4 mb-2">智能推荐 Top 3</div>
<div v-for="(r, i) in [
{ name:'李护理员', score:92, reasons:'资质匹配·距离1.2km·负载低·满意度4.8' },
{ name:'王护理员', score:85, reasons:'区域匹配·技能匹配·今日工单2/6' },
{ name:'陈护理员', score:78, reasons:'曾服务该对象·满意度4.6·工单量适中' },
]" :key="i"
class="flex items-center gap-4 p-4 rounded-xl border hover:border-primary hover:bg-primary-50/50 transition-colors cursor-pointer">
<span class="w-10 h-10 rounded-full bg-gradient-to-br from-primary-50 to-accent-50 text-primary flex items-center justify-center font-bold text-sm shrink-0">
{{ i + 1 }}
</span>
<div class="flex-1 min-w-0">
<div class="text-sm font-medium">{{ r.name }}</div>
<div class="text-xs text-text-secondary truncate">{{ r.reasons }}</div>
</div>
<span class="text-lg font-bold font-mono text-primary shrink-0">{{ r.score }}</span>
<button class="px-4 py-2 bg-primary text-white rounded-lg text-xs font-medium hover:bg-primary-700 transition-colors shrink-0">派单</button>
</div>
</div>
</PlatformSection>
</div>
</section>
<!-- Delivery Tab -->
<section v-if="activeTab === 'delivery'" class="py-12 bg-surface">
<div class="section-container space-y-6">
<div class="grid lg:grid-cols-3 gap-6">
<PlatformSection title="Delivery 工作台">
<div class="p-4 space-y-3">
<div class="flex items-center justify-between text-sm">
<span class="text-text-secondary">今日工单</span><span class="font-bold">6</span>
</div>
<div class="flex items-center justify-between text-sm">
<span class="text-text-secondary">待接单</span><span class="font-bold text-blue-600">2</span>
</div>
<div class="flex items-center justify-between text-sm">
<span class="text-text-secondary">待签到</span><span class="font-bold text-teal-600">1</span>
</div>
<div class="flex items-center justify-between text-sm">
<span class="text-text-secondary">服务中</span><span class="font-bold text-accent-700">1</span>
</div>
<div class="flex items-center justify-between text-sm">
<span class="text-text-secondary">已完成</span><span class="font-bold text-green-600">2</span>
</div>
</div>
</PlatformSection>
<PlatformSection title="GPS 签到">
<div class="p-4 space-y-3">
<div class="text-xs text-text-secondary">当前位置距服务地址</div>
<div class="text-3xl font-bold font-mono text-accent-700">85m</div>
<div class="text-xs text-green-600">✓ 200米范围内可签到</div>
<div class="flex gap-2 mt-3">
<div class="w-full h-20 rounded-xl bg-gray-100 flex items-center justify-center text-xs text-text-secondary">📷 现场拍照</div>
<div class="w-full h-20 rounded-xl bg-gray-100 flex items-center justify-center text-xs text-text-secondary">✍️ 对象确认</div>
</div>
<button class="w-full py-3 bg-cta text-white rounded-xl text-sm font-bold hover:bg-cta-700 transition-colors">确认签到</button>
</div>
</PlatformSection>
<PlatformSection title="项目执行记录">
<div class="p-4 space-y-3">
<div v-for="(item, i) in [
{ name:'助洁服务', status:'COMPLETED', time:'09:15-10:05', duration:'50分钟' },
{ name:'健康监测', status:'IN_PROGRESS', time:'10:10-', duration:'进行中' },
{ name:'康复指导', status:'PENDING', time:'--', duration:'待执行' },
]" :key="i" class="flex items-center gap-3 text-sm">
<span :class="['w-2 h-2 rounded-full shrink-0',
item.status === 'COMPLETED' ? 'bg-green-500' : item.status === 'IN_PROGRESS' ? 'bg-accent' : 'bg-gray-300']" />
<span class="flex-1 font-medium">{{ item.name }}</span>
<span class="text-xs text-text-secondary">{{ item.time }}</span>
<span class="text-xs text-text-secondary w-14 text-right">{{ item.duration }}</span>
</div>
</div>
</PlatformSection>
</div>
</div>
</section>
<CtaSection />
</template>