feat: 初始化居家上门服务系统完整项目代码
- Spring Boot 后端服务 (hss-home-service) - delivery-miniapp 配送小程序 - website 官网 (Nuxt) - docs 架构设计文档 - Docker 容器化部署配置 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
40
hss-home-service/website/pages/about.vue
Normal file
40
hss-home-service/website/pages/about.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<script setup lang="ts">
|
||||
import { useSeo } from '~/composables/useSeo'
|
||||
import { siteName, siteTagline } from '~/data/siteContent'
|
||||
useSeo({ title: '关于我们', description: siteTagline })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="py-20 bg-gradient-to-br from-primary-700 to-primary-900 text-white">
|
||||
<div class="section-container text-center">
|
||||
<h1 class="text-4xl md:text-5xl font-bold mb-4">关于我们</h1>
|
||||
<p class="text-xl text-blue-100 max-w-2xl mx-auto">{{ siteName }}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="py-20 bg-white">
|
||||
<div class="section-container max-w-3xl">
|
||||
<div class="prose prose-lg mx-auto">
|
||||
<h2 class="text-3xl font-bold mb-6">平台愿景</h2>
|
||||
<p class="text-text-secondary leading-relaxed mb-8">
|
||||
我们致力于为政府、医院、养老机构和社区服务中心提供专业、可信、智能的居家上门服务闭环管理平台。
|
||||
通过数字化手段,让每一个服务请求都有始有终、每一步操作都可追溯、每一个异常都有处理、每一笔费用都有结算。
|
||||
</p>
|
||||
|
||||
<h2 class="text-3xl font-bold mb-6">核心团队</h2>
|
||||
<p class="text-text-secondary leading-relaxed mb-8">
|
||||
团队由医疗信息化、养老服务运营、企业级 SaaS 平台开发等领域的资深专家组成,
|
||||
具有丰富的医养结合数字化平台建设和落地经验。
|
||||
</p>
|
||||
|
||||
<h2 class="text-3xl font-bold mb-6">行业标准</h2>
|
||||
<p class="text-text-secondary leading-relaxed mb-8">
|
||||
平台设计严格遵循《居家养老上门服务基本规范》GB/T 43153-2023 以及智慧健康养老产业发展相关指导文件,
|
||||
确保服务流程、数据安全、质量评价和用户隐私保护符合国家标准。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<CtaSection />
|
||||
</template>
|
||||
31
hss-home-service/website/pages/capabilities.vue
Normal file
31
hss-home-service/website/pages/capabilities.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<script setup lang="ts">
|
||||
import { useSeo } from '~/composables/useSeo'
|
||||
import { capabilities } from '~/data/siteContent'
|
||||
useSeo({ title: '核心能力', description: '八大核心能力覆盖居家上门服务完整业务链路:需求受理、能力评估、方案制定、智能派单、上门执行、过程监管、验收评价、结算归档。' })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="py-20 bg-gradient-to-br from-primary-700 to-primary-900 text-white">
|
||||
<div class="section-container text-center">
|
||||
<h1 class="text-4xl md:text-5xl font-bold mb-4">核心能力</h1>
|
||||
<p class="text-xl text-blue-100 max-w-2xl mx-auto">八大能力模块,覆盖居家上门服务完整业务链路</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="py-20 bg-surface">
|
||||
<div class="section-container">
|
||||
<div class="grid md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
<CapabilityCard v-for="c in capabilities" :key="c.title" v-bind="c" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="py-20 bg-white">
|
||||
<div class="section-container">
|
||||
<h2 class="section-title">三端协同</h2>
|
||||
<TriEndDisplay class="mt-12" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<CtaSection />
|
||||
</template>
|
||||
30
hss-home-service/website/pages/contact.vue
Normal file
30
hss-home-service/website/pages/contact.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
import { useSeo } from '~/composables/useSeo'
|
||||
useSeo({ title: '联系我们', description: '预约演示、获取方案、合作咨询。填写表单,我们将尽快与您联系。' })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="py-20 bg-gradient-to-br from-primary-700 to-primary-900 text-white">
|
||||
<div class="section-container text-center">
|
||||
<h1 class="text-4xl md:text-5xl font-bold mb-4">联系我们</h1>
|
||||
<p class="text-xl text-blue-100 max-w-2xl mx-auto">预约演示、获取方案或合作咨询</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="py-20 bg-surface">
|
||||
<div class="section-container">
|
||||
<div class="grid lg:grid-cols-2 gap-16 items-start">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold mb-6">预约演示</h2>
|
||||
<p class="text-text-secondary mb-8">填写表单,我们的产品顾问将在 1 个工作日内与您联系,为您安排专属演示。</p>
|
||||
<DemoForm />
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold mb-6">合作咨询</h2>
|
||||
<p class="text-text-secondary mb-8">如果您有政府项目合作、医院对接、机构采购或其他合作需求,请填写下方表单。</p>
|
||||
<ContactForm />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
231
hss-home-service/website/pages/demo.vue
Normal file
231
hss-home-service/website/pages/demo.vue
Normal file
@@ -0,0 +1,231 @@
|
||||
<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 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 -->
|
||||
<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>
|
||||
99
hss-home-service/website/pages/index.vue
Normal file
99
hss-home-service/website/pages/index.vue
Normal file
@@ -0,0 +1,99 @@
|
||||
<script setup lang="ts">
|
||||
import { useSeo } from '~/composables/useSeo'
|
||||
import { painPoints, capabilities, scenarios } from '~/data/siteContent'
|
||||
|
||||
useSeo({ title: '首页', description: '智慧医养居家上门服务闭环管理平台,覆盖申请、评估、方案、派单、执行、监管、验收、结算全流程。' })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<HeroSection />
|
||||
|
||||
<section class="py-20 bg-surface">
|
||||
<div class="section-container">
|
||||
<h2 class="section-title">居家上门服务,为什么这么难管?</h2>
|
||||
<p class="section-subtitle">申请流程乱、评估标准不一、派单靠经验、上门难监管、质量难追溯、结算不规范</p>
|
||||
<div class="grid md:grid-cols-3 gap-8 mt-12">
|
||||
<PainPointCard v-for="p in painPoints" :key="p.title" v-bind="p" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="py-20 bg-white">
|
||||
<div class="section-container">
|
||||
<h2 class="section-title">一套平台,打通居家服务全流程</h2>
|
||||
<p class="section-subtitle">从服务申请到结算归档,每个环节都可监管、可追溯、可评价</p>
|
||||
<img src="https://loremflickr.com/1024/400/technology" alt="平台概览示意图(示意素材,待替换)"
|
||||
class="mt-12 rounded-2xl shadow-lg w-full" width="1024" height="400" loading="lazy" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="py-20 bg-surface">
|
||||
<div class="section-container">
|
||||
<h2 class="section-title">完整服务闭环</h2>
|
||||
<p class="section-subtitle">8 个阶段无缝衔接,每个节点都可监管、可追溯</p>
|
||||
<ServiceLoopFlow class="mt-12" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="py-20 bg-white">
|
||||
<div class="section-container">
|
||||
<h2 class="section-title">八大核心能力</h2>
|
||||
<p class="section-subtitle">覆盖居家上门服务完整业务链路</p>
|
||||
<div class="grid md:grid-cols-2 lg:grid-cols-4 gap-6 mt-12">
|
||||
<CapabilityCard v-for="c in capabilities" :key="c.title" v-bind="c" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="py-20 bg-surface">
|
||||
<div class="section-container">
|
||||
<h2 class="section-title">三端协同,角色分明</h2>
|
||||
<p class="section-subtitle">家属端、服务端、管理端各司其职</p>
|
||||
<TriEndDisplay class="mt-12" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="py-20 bg-white">
|
||||
<div class="section-container">
|
||||
<h2 class="section-title">覆盖五大应用场景</h2>
|
||||
<p class="section-subtitle">适配不同机构类型的业务需求</p>
|
||||
<div class="grid md:grid-cols-2 lg:grid-cols-3 gap-6 mt-12">
|
||||
<ScenarioCard v-for="s in scenarios" :key="s.title" v-bind="s" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="py-20 bg-surface">
|
||||
<div class="section-container">
|
||||
<h2 class="section-title">实时监管,数据驱动</h2>
|
||||
<p class="section-subtitle">运营数据一目了然,异常预警即时通知</p>
|
||||
<DataDashboard class="mt-12" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="py-20 bg-white">
|
||||
<div class="section-container">
|
||||
<h2 class="section-title">医疗级安全合规</h2>
|
||||
<p class="section-subtitle">数据安全与合规体系,满足政企医疗行业标准</p>
|
||||
<SecurityGrid class="mt-12" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="py-20 bg-surface">
|
||||
<div class="section-container">
|
||||
<h2 class="section-title">平台建设价值</h2>
|
||||
<ValueMetrics class="mt-12" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="py-20 bg-white">
|
||||
<div class="section-container text-center">
|
||||
<h2 class="section-title">获取完整方案</h2>
|
||||
<p class="section-subtitle">下载解决方案 PDF、白皮书和产品介绍资料</p>
|
||||
<DownloadForm class="mt-8" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<CtaSection />
|
||||
<MobileBottomCTA />
|
||||
</template>
|
||||
87
hss-home-service/website/pages/platform/applications.vue
Normal file
87
hss-home-service/website/pages/platform/applications.vue
Normal file
@@ -0,0 +1,87 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
|
||||
const apps = ref<any[]>([])
|
||||
const loading = ref(true)
|
||||
const ready = ref(false)
|
||||
const showCreate = ref(false)
|
||||
const submitting = ref(false)
|
||||
const form = ref({ patientId: '', serviceType: 'HOME_CARE', channel: 'WECHAT', contactName: '', contactPhone: '', address: '', regionCode: '441402001', notes: '' })
|
||||
const serviceTypes = [{ v:'HOME_CARE',label:'居家护理' },{ v:'REHABILITATION',label:'康复训练' },{ v:'BATH_ASSIST',label:'助浴服务' },{ v:'ASSESSMENT',label:'能力评估' }]
|
||||
const channels = ['WECHAT','APP','PHONE','COMMUNITY','HOSPITAL']
|
||||
const statusMap: Record<string,string> = { DRAFT:'草稿',PENDING_ACCEPTANCE:'待受理',PENDING_ASSESSMENT:'待评估',ASSESSING:'评估中',ASSESSMENT_PASSED:'已评估',RETURNED:'已退回',CANCELLED:'已取消',REVIEWING:'复核中' }
|
||||
const statusCls: Record<string,string> = { DRAFT:'bg-gray-100',PENDING_ACCEPTANCE:'bg-yellow-50 text-yellow-700',PENDING_ASSESSMENT:'bg-blue-50 text-blue-600',ASSESSING:'bg-indigo-50 text-indigo-600',ASSESSMENT_PASSED:'bg-green-50 text-green-600',RETURNED:'bg-red-50 text-red-600' }
|
||||
|
||||
function getAuthHeaders(): Record<string,string> {
|
||||
const h: Record<string,string> = { 'Content-Type':'application/json','X-Tenant-Id':'1','X-Org-Id':'1','X-User-Role':'ADMIN','X-User-Id':'1' }
|
||||
try { const u = JSON.parse(localStorage.getItem('hss_platform_user')||'{}'); if(u.userRole){h['X-User-Role']=u.userRole;h['X-User-Id']=u.userId} } catch {}
|
||||
return h
|
||||
}
|
||||
|
||||
async function apiFetch(path: string, opts: any = {}) {
|
||||
const h = { ...getAuthHeaders(), 'Idempotency-Key': 'web-'+Date.now()+'-'+Math.random().toString(36).slice(2,6) }
|
||||
return await $fetch('/api/hss' + path, { ...opts, headers: { ...h, ...(opts.headers||{}) } })
|
||||
}
|
||||
|
||||
async function loadApps() {
|
||||
try { const res = await apiFetch('/applications?page=1&size=30'); apps.value = res?.data?.records || res?.data || [] } catch {}
|
||||
}
|
||||
|
||||
async function createApp() {
|
||||
submitting.value = true
|
||||
try { await apiFetch('/applications', { method:'POST', body: JSON.stringify(form.value) }); showCreate.value = false; await loadApps() }
|
||||
catch(e: any) { alert(e?.data?.message || e?.message || '创建失败') }
|
||||
finally { submitting.value = false }
|
||||
}
|
||||
|
||||
async function doAction(id: number, action: string, body?: any) {
|
||||
try { await apiFetch(`/applications/${id}/${action}`, { method:'POST', body: body ? JSON.stringify(body) : undefined }); await loadApps() }
|
||||
catch(e: any) { alert(e?.data?.message || e?.message || '操作失败') }
|
||||
}
|
||||
|
||||
onMounted(() => { ready.value = true; loadApps().finally(() => loading.value = false) })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ClientOnly>
|
||||
<div v-if="!ready" class="min-h-screen bg-surface flex items-center justify-center"><p class="text-text-secondary">加载中...</p></div>
|
||||
<div v-else class="min-h-screen bg-surface flex">
|
||||
<aside class="hidden lg:flex flex-col w-56 bg-white border-r shrink-0">
|
||||
<div class="p-4 border-b"><a href="/platform" class="font-bold text-primary text-sm">← 返回工作台</a></div>
|
||||
<div class="p-3"><div class="text-xs text-text-secondary">角色: {{ getAuthHeaders()['X-User-Role'] }}</div></div>
|
||||
</aside>
|
||||
<main class="flex-1 p-4 lg:p-8 overflow-auto">
|
||||
<div class="flex items-center justify-between mb-6"><h2 class="text-xl font-bold">服务申请管理</h2><button @click="showCreate=!showCreate" class="px-4 py-2 bg-primary text-white rounded-lg text-sm font-medium hover:bg-primary-700">+ 新建申请</button></div>
|
||||
<div v-if="showCreate" class="bg-white rounded-2xl shadow-sm border p-6 mb-6 space-y-4">
|
||||
<h3 class="font-bold">新建服务申请</h3>
|
||||
<div class="grid md:grid-cols-2 gap-4">
|
||||
<div><label class="text-xs text-text-secondary block mb-1">服务对象ID</label><input v-model="form.patientId" class="w-full px-3 py-2 border rounded-lg text-sm outline-none focus:border-primary" placeholder="如 2001"/></div>
|
||||
<div><label class="text-xs text-text-secondary block mb-1">服务类型</label><select v-model="form.serviceType" class="w-full px-3 py-2 border rounded-lg text-sm outline-none focus:border-primary"><option v-for="s in serviceTypes" :key="s.v" :value="s.v">{{s.label}}</option></select></div>
|
||||
<div><label class="text-xs text-text-secondary block mb-1">渠道</label><select v-model="form.channel" class="w-full px-3 py-2 border rounded-lg text-sm outline-none focus:border-primary"><option v-for="c in channels" :key="c" :value="c">{{c}}</option></select></div>
|
||||
<div><label class="text-xs text-text-secondary block mb-1">联系人</label><input v-model="form.contactName" class="w-full px-3 py-2 border rounded-lg text-sm outline-none focus:border-primary" placeholder="姓名"/></div>
|
||||
<div><label class="text-xs text-text-secondary block mb-1">电话</label><input v-model="form.contactPhone" class="w-full px-3 py-2 border rounded-lg text-sm outline-none focus:border-primary" placeholder="手机号"/></div>
|
||||
<div><label class="text-xs text-text-secondary block mb-1">区域</label><input v-model="form.regionCode" class="w-full px-3 py-2 border rounded-lg text-sm outline-none focus:border-primary" placeholder="441402001"/></div>
|
||||
<div class="md:col-span-2"><label class="text-xs text-text-secondary block mb-1">地址</label><input v-model="form.address" class="w-full px-3 py-2 border rounded-lg text-sm outline-none focus:border-primary" placeholder="详细地址"/></div>
|
||||
</div>
|
||||
<div class="flex gap-3"><button @click="createApp" :disabled="submitting" class="px-6 py-2 bg-primary text-white rounded-lg text-sm font-medium disabled:opacity-50">{{submitting?'提交中...':'提交申请'}}</button><button @click="showCreate=false" class="px-6 py-2 border rounded-lg text-sm">取消</button></div>
|
||||
</div>
|
||||
<div class="bg-white rounded-2xl shadow-sm border overflow-hidden">
|
||||
<div v-if="loading" class="p-8 text-center text-text-secondary">加载中...</div>
|
||||
<table v-else class="w-full text-sm"><thead><tr class="bg-gray-50 text-left text-xs text-text-secondary"><th class="px-4 py-3">ID</th><th class="px-4 py-3">对象</th><th class="px-4 py-3">类型</th><th class="px-4 py-3">状态</th><th class="px-4 py-3">联系人</th><th class="px-4 py-3">时间</th><th class="px-4 py-3">操作</th></tr></thead>
|
||||
<tbody><tr v-for="a in apps" :key="a.id" class="border-t border-gray-50 hover:bg-gray-50">
|
||||
<td class="px-4 py-3 font-mono text-xs">#{{a.id}}</td><td class="px-4 py-3">{{a.patientId}}</td><td class="px-4 py-3 text-xs">{{a.serviceType}}</td>
|
||||
<td class="px-4 py-3"><span class="px-2 py-0.5 rounded-full text-xs font-medium" :class="statusCls[a.status]||'bg-gray-100'">{{statusMap[a.status]||a.status}}</span></td>
|
||||
<td class="px-4 py-3 text-xs">{{a.contactName||'-'}}</td><td class="px-4 py-3 text-xs text-text-secondary">{{a.createdAt?.substring(0,16)||'-'}}</td>
|
||||
<td class="px-4 py-3"><div class="flex gap-1 flex-wrap">
|
||||
<button v-if="a.status==='DRAFT'" @click="doAction(a.id,'submit')" class="px-2 py-1 bg-blue-50 text-blue-600 rounded text-xs hover:bg-blue-100">提交</button>
|
||||
<button v-if="a.status==='PENDING_ACCEPTANCE'" @click="doAction(a.id,'accept')" class="px-2 py-1 bg-green-50 text-green-600 rounded text-xs hover:bg-green-100">受理</button>
|
||||
<button v-if="a.status==='PENDING_ACCEPTANCE'" @click="doAction(a.id,'return',{reason:'退回原因'})" class="px-2 py-1 bg-red-50 text-red-600 rounded text-xs hover:bg-red-100">退回</button>
|
||||
<button v-if="!['CANCELLED','RETURNED'].includes(a.status)" @click="doAction(a.id,'cancel')" class="px-2 py-1 bg-gray-50 text-gray-500 rounded text-xs hover:bg-gray-100">取消</button>
|
||||
</div></td>
|
||||
</tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
120
hss-home-service/website/pages/platform/index.vue
Normal file
120
hss-home-service/website/pages/platform/index.vue
Normal file
@@ -0,0 +1,120 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { usePlatformAuth } from '~/composables/usePlatformAuth'
|
||||
import { useApi } from '~/composables/useApi'
|
||||
|
||||
definePageMeta({ layout: false })
|
||||
|
||||
const { isLoggedIn, user, logout, switchRole, getAuthHeaders, ROLES } = usePlatformAuth()
|
||||
const { get, post } = useApi()
|
||||
|
||||
const stats = ref<Record<string,any>>({})
|
||||
const recentOrders = ref<any[]>([])
|
||||
const activeMenu = ref('dashboard')
|
||||
|
||||
if (!isLoggedIn.value) { await navigateTo('/platform/login') }
|
||||
|
||||
onMounted(async () => {
|
||||
try { stats.value = await get<any>('/admin/dashboard').catch(() => ({})) } catch {}
|
||||
})
|
||||
|
||||
const menuItems = [
|
||||
{ key: 'dashboard', label: '工作台', icon: 'chart' },
|
||||
{ key: 'applications', label: '服务申请', icon: 'clipboard', href: '/platform/applications' },
|
||||
{ key: 'work-orders', label: '工单管理', icon: 'document', href: '/platform/work-orders' },
|
||||
]
|
||||
|
||||
const roleMenus: Record<string, string[]> = {
|
||||
RECEPTIONIST: ['applications'],
|
||||
ASSESSOR: ['applications'],
|
||||
DISPATCHER: ['work-orders'],
|
||||
STAFF: ['work-orders'],
|
||||
SETTLER: ['dashboard'],
|
||||
SUPERVISOR: ['dashboard'],
|
||||
ADMIN: ['applications', 'work-orders'],
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-screen bg-surface flex">
|
||||
<!-- Sidebar -->
|
||||
<aside class="hidden lg:flex flex-col w-56 bg-white border-r shrink-0">
|
||||
<div class="p-4 border-b">
|
||||
<NuxtLink to="/platform" class="flex items-center gap-2 font-bold text-primary">
|
||||
<div class="w-8 h-8 rounded-lg bg-primary flex items-center justify-center text-white text-xs font-mono">H</div>
|
||||
<span class="text-sm">智慧医养平台</span>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
<nav class="flex-1 p-3 space-y-1">
|
||||
<NuxtLink v-for="m in menuItems" :key="m.key" :to="m.href || '/platform'"
|
||||
class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm transition-colors"
|
||||
:class="activeMenu === m.key ? 'bg-primary-50 text-primary font-medium' : 'text-text-secondary hover:bg-gray-50'">
|
||||
<AppIcon :name="m.icon" class="w-4 h-4" /> {{ m.label }}
|
||||
</NuxtLink>
|
||||
</nav>
|
||||
<div class="p-3 border-t">
|
||||
<div class="flex items-center gap-2 px-3 py-2 text-sm">
|
||||
<div class="w-7 h-7 rounded-full bg-primary-50 text-primary flex items-center justify-center text-xs font-bold">{{ user?.userName?.charAt(0) }}</div>
|
||||
<div class="min-w-0">
|
||||
<div class="text-xs font-medium truncate">{{ user?.userName }}</div>
|
||||
<select @change="switchRole(($event.target as HTMLSelectElement).value)"
|
||||
class="text-xs text-text-secondary bg-transparent border-none outline-none cursor-pointer">
|
||||
<option v-for="r in ROLES" :key="r.key" :value="r.key" :selected="user?.userRole === r.key">{{ r.label }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<button @click="logout(); navigateTo('/platform/login')"
|
||||
class="w-full mt-2 px-3 py-2 text-xs text-text-secondary hover:text-red-500 transition-colors text-left rounded-lg hover:bg-red-50">退出登录</button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="flex-1 overflow-auto">
|
||||
<!-- Mobile header -->
|
||||
<div class="lg:hidden bg-white border-b px-4 py-3 flex items-center justify-between">
|
||||
<NuxtLink to="/platform" class="font-bold text-primary text-sm">智慧医养平台</NuxtLink>
|
||||
<div class="flex items-center gap-2">
|
||||
<select @change="switchRole(($event.target as HTMLSelectElement).value)"
|
||||
class="text-xs border rounded-lg px-2 py-1 outline-none">
|
||||
<option v-for="r in ROLES" :key="r.key" :value="r.key" :selected="user?.userRole === r.key">{{ r.label }}</option>
|
||||
</select>
|
||||
<button @click="logout(); navigateTo('/platform/login')" class="text-xs text-red-500">退出</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-4 lg:p-8">
|
||||
<h2 class="text-xl font-bold mb-6">工作台 — {{ ROLES.find(r=>r.key===user?.userRole)?.label || user?.userRole }}</h2>
|
||||
|
||||
<div class="grid grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
|
||||
<div class="bg-white rounded-xl p-4 shadow-sm border"><div class="text-2xl font-bold font-mono text-primary">{{ stats.todayOrders || '--' }}</div><div class="text-xs text-text-secondary mt-1">今日工单</div></div>
|
||||
<div class="bg-white rounded-xl p-4 shadow-sm border"><div class="text-2xl font-bold font-mono text-accent-700">{{ stats.inProgress || '--' }}</div><div class="text-xs text-text-secondary mt-1">进行中</div></div>
|
||||
<div class="bg-white rounded-xl p-4 shadow-sm border"><div class="text-2xl font-bold font-mono text-green-600">{{ stats.completedToday || '--' }}</div><div class="text-xs text-text-secondary mt-1">已完成</div></div>
|
||||
<div class="bg-white rounded-xl p-4 shadow-sm border"><div class="text-2xl font-bold font-mono text-red-500">{{ stats.exceptions || '--' }}</div><div class="text-xs text-text-secondary mt-1">异常</div></div>
|
||||
</div>
|
||||
|
||||
<!-- Quick actions based on role -->
|
||||
<div class="grid lg:grid-cols-2 gap-6">
|
||||
<div class="bg-white rounded-2xl shadow-sm border p-6">
|
||||
<h3 class="font-bold mb-4">快捷操作</h3>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<NuxtLink v-if="['RECEPTIONIST','ADMIN'].includes(user?.userRole||'')" to="/platform/applications"
|
||||
class="p-4 rounded-xl bg-primary-50 text-primary text-sm font-medium hover:bg-primary hover:text-white transition-colors text-center">受理新申请</NuxtLink>
|
||||
<NuxtLink v-if="['DISPATCHER','ADMIN'].includes(user?.userRole||'')" to="/platform/work-orders"
|
||||
class="p-4 rounded-xl bg-accent-50 text-accent-700 text-sm font-medium hover:bg-accent hover:text-white transition-colors text-center">查看工单</NuxtLink>
|
||||
<NuxtLink to="/demo" class="p-4 rounded-xl bg-gray-50 text-text-secondary text-sm font-medium hover:bg-gray-100 transition-colors text-center">平台演示</NuxtLink>
|
||||
<NuxtLink to="/" class="p-4 rounded-xl bg-gray-50 text-text-secondary text-sm font-medium hover:bg-gray-100 transition-colors text-center">返回官网</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white rounded-2xl shadow-sm border p-6">
|
||||
<h3 class="font-bold mb-4">当前角色权限</h3>
|
||||
<div class="space-y-2 text-sm">
|
||||
<div class="flex justify-between py-2 border-b border-gray-50"><span class="text-text-secondary">角色</span><span class="font-medium">{{ ROLES.find(r=>r.key===user?.userRole)?.label }}</span></div>
|
||||
<div class="flex justify-between py-2 border-b border-gray-50"><span class="text-text-secondary">可操作模块</span><span class="font-medium">{{ (roleMenus[user?.userRole||'']||['dashboard']).join(', ') }}</span></div>
|
||||
<div class="flex justify-between py-2"><span class="text-text-secondary">数据范围</span><span class="font-medium">本机构</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
59
hss-home-service/website/pages/platform/login.vue
Normal file
59
hss-home-service/website/pages/platform/login.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
definePageMeta({ ssr: false })
|
||||
import { ref } from 'vue'
|
||||
import { usePlatformAuth } from '~/composables/usePlatformAuth'
|
||||
const { login, PRESET_USERS } = usePlatformAuth()
|
||||
const selected = ref('admin')
|
||||
const loading = ref(false)
|
||||
const error = ref('')
|
||||
|
||||
async function doLogin() {
|
||||
loading.value = true; error.value = ''
|
||||
const u = login(selected.value)
|
||||
if (u) {
|
||||
await navigateTo('/platform')
|
||||
} else {
|
||||
error.value = '登录失败'
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-screen bg-surface flex items-center justify-center p-4">
|
||||
<div class="w-full max-w-md">
|
||||
<div class="text-center mb-8">
|
||||
<div class="w-16 h-16 mx-auto mb-4 rounded-2xl bg-gradient-to-br from-primary to-accent flex items-center justify-center">
|
||||
<span class="text-white font-bold text-2xl font-mono">H</span>
|
||||
</div>
|
||||
<h1 class="text-2xl font-bold">智慧医养居家上门服务平台</h1>
|
||||
<p class="text-text-secondary text-sm mt-2">演示环境 — 选择角色即可登录</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-2xl shadow-sm border p-6 space-y-4">
|
||||
<label class="block text-sm font-medium text-text-secondary">选择登录角色</label>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<button v-for="(u, k) in PRESET_USERS" :key="k"
|
||||
@click="selected = k"
|
||||
class="text-left px-4 py-3 rounded-xl border-2 transition-all text-sm"
|
||||
:class="selected === k ? 'border-primary bg-primary-50 text-primary' : 'border-gray-100 hover:border-gray-200'">
|
||||
<div class="font-medium">{{ u.userName }}</div>
|
||||
<div class="text-xs text-text-secondary">{{ u.userRole }}</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p v-if="error" class="text-red-500 text-sm text-center">{{ error }}</p>
|
||||
|
||||
<button @click="doLogin" :disabled="loading"
|
||||
class="w-full py-3 bg-primary text-white rounded-xl font-semibold hover:bg-primary-700 transition-colors disabled:opacity-50">
|
||||
{{ loading ? '登录中...' : '进入平台' }}
|
||||
</button>
|
||||
|
||||
<p class="text-xs text-text-secondary text-center">
|
||||
演示环境使用 Header 认证,无需密码。选择角色后直接进入对应工作台。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
80
hss-home-service/website/pages/platform/work-orders.vue
Normal file
80
hss-home-service/website/pages/platform/work-orders.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
|
||||
const orders = ref<any[]>([])
|
||||
const loading = ref(true)
|
||||
const ready = ref(false)
|
||||
const statusFilter = ref('')
|
||||
|
||||
const statusMap: Record<string,{label:string;cls:string}> = {
|
||||
ORDER_CREATED: { label:'待派单', cls:'bg-gray-100 text-gray-600' },
|
||||
ORDER_ASSIGNED: { label:'已派单', cls:'bg-blue-50 text-blue-600' },
|
||||
ORDER_ACCEPTED: { label:'已接单', cls:'bg-indigo-50 text-indigo-600' },
|
||||
ORDER_CHECKED_IN: { label:'已签到', cls:'bg-teal-50 text-teal-600' },
|
||||
ORDER_IN_SERVICE: { label:'服务中', cls:'bg-accent-50 text-accent-700' },
|
||||
ORDER_COMPLETED: { label:'已完成', cls:'bg-green-50 text-green-600' },
|
||||
ORDER_EXCEPTION: { label:'异常', cls:'bg-red-50 text-red-600' },
|
||||
ACCEPTED: { label:'已验收', cls:'bg-green-100 text-green-700' },
|
||||
}
|
||||
|
||||
function getAuthHeaders(): Record<string,string> {
|
||||
const h: Record<string,string> = { 'Content-Type':'application/json','X-Tenant-Id':'1','X-Org-Id':'1','X-User-Role':'ADMIN','X-User-Id':'1' }
|
||||
try { const u = JSON.parse(localStorage.getItem('hss_platform_user')||'{}'); if(u.userRole){h['X-User-Role']=u.userRole;h['X-User-Id']=u.userId} } catch {}
|
||||
return h
|
||||
}
|
||||
|
||||
async function apiFetch(path: string, opts: any = {}) {
|
||||
const h = { ...getAuthHeaders(), 'Idempotency-Key': 'web-'+Date.now() }
|
||||
return await $fetch('/api/hss' + path, { ...opts, headers: { ...h, ...(opts.headers||{}) } })
|
||||
}
|
||||
|
||||
async function loadOrders() {
|
||||
try { const res = await apiFetch('/admin/work-orders?page=1&size=30'); orders.value = res?.data || [] } catch {}
|
||||
}
|
||||
|
||||
async function doAction(id: number, action: string, body?: any) {
|
||||
try { await apiFetch(`/work-orders/${id}/${action}`, { method:'POST', body: body ? JSON.stringify(body) : undefined }); await loadOrders() }
|
||||
catch(e: any) { alert(e?.data?.message || e?.message || '操作失败') }
|
||||
}
|
||||
|
||||
onMounted(() => { ready.value = true; loadOrders().finally(() => loading.value = false) })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ClientOnly>
|
||||
<div v-if="!ready" class="min-h-screen bg-surface flex items-center justify-center"><p class="text-text-secondary">加载中...</p></div>
|
||||
<div v-else class="min-h-screen bg-surface flex">
|
||||
<aside class="hidden lg:flex flex-col w-56 bg-white border-r shrink-0">
|
||||
<div class="p-4 border-b"><a href="/platform" class="font-bold text-primary text-sm">← 返回工作台</a></div>
|
||||
<div class="p-3 space-y-1">
|
||||
<button v-for="s in ['', ...Object.keys(statusMap)]" :key="s" @click="statusFilter = s"
|
||||
class="block w-full text-left px-3 py-2 rounded-lg text-xs transition-colors"
|
||||
:class="statusFilter===s ? 'bg-primary-50 text-primary font-medium' : 'text-text-secondary hover:bg-gray-50'">
|
||||
{{ s ? statusMap[s]?.label : '全部工单' }}
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
<main class="flex-1 p-4 lg:p-8 overflow-auto">
|
||||
<h2 class="text-xl font-bold mb-6">工单管理</h2>
|
||||
<div class="bg-white rounded-2xl shadow-sm border overflow-hidden">
|
||||
<div v-if="loading" class="p-8 text-center text-text-secondary">加载中...</div>
|
||||
<table v-else class="w-full text-sm"><thead><tr class="bg-gray-50 text-left text-xs text-text-secondary"><th class="px-4 py-3">ID</th><th class="px-4 py-3">服务日期</th><th class="px-4 py-3">服务对象</th><th class="px-4 py-3">状态</th><th class="px-4 py-3">人员</th><th class="px-4 py-3">操作</th></tr></thead>
|
||||
<tbody><tr v-for="o in orders.filter((x:any) => !statusFilter || x.status===statusFilter)" :key="o.id" class="border-t border-gray-50 hover:bg-gray-50">
|
||||
<td class="px-4 py-3 font-mono text-xs">#{{o.id}}</td><td class="px-4 py-3 text-xs">{{o.serviceDate}}</td><td class="px-4 py-3 text-xs">{{o.patientId}}</td>
|
||||
<td class="px-4 py-3"><span class="px-2 py-0.5 rounded-full text-xs font-medium" :class="statusMap[o.status]?.cls||'bg-gray-100'">{{statusMap[o.status]?.label||o.status}}</span></td>
|
||||
<td class="px-4 py-3 text-xs">{{o.staffId||'-'}}</td>
|
||||
<td class="px-4 py-3"><div class="flex gap-1 flex-wrap">
|
||||
<button v-if="o.status==='ORDER_CREATED'" @click="doAction(o.id,'assign',{staffId:1,reason:'派单'})" class="px-2 py-1 bg-blue-50 text-blue-600 rounded text-xs hover:bg-blue-100">派单</button>
|
||||
<button v-if="o.status==='ORDER_ASSIGNED'" @click="doAction(o.id,'accept')" class="px-2 py-1 bg-green-50 text-green-600 rounded text-xs hover:bg-green-100">接单</button>
|
||||
<button v-if="o.status==='ORDER_ACCEPTED'" @click="doAction(o.id,'check-in',{latitude:24.2878,longitude:116.1271,photoFileId:'test',patientConfirmed:true})" class="px-2 py-1 bg-teal-50 text-teal-600 rounded text-xs hover:bg-teal-100">签到</button>
|
||||
<button v-if="o.status==='ORDER_CHECKED_IN'" @click="doAction(o.id,'start-service')" class="px-2 py-1 bg-accent-50 text-accent-600 rounded text-xs hover:bg-accent-100">开始</button>
|
||||
<button v-if="o.status==='ORDER_IN_SERVICE'" @click="doAction(o.id,'finish',{executionRecords:[{planItemId:1,status:'COMPLETED',notes:'完成'}],serviceSummary:'完成',signOffLatitude:24.2878,signOffLongitude:116.1271})" class="px-2 py-1 bg-green-50 text-green-600 rounded text-xs hover:bg-green-100">完成</button>
|
||||
<button v-if="o.status==='ORDER_IN_SERVICE'" @click="doAction(o.id,'report-exception',{exceptionType:'PATIENT_ABSENT',description:'对象不在家'})" class="px-2 py-1 bg-red-50 text-red-600 rounded text-xs hover:bg-red-100">异常</button>
|
||||
</div></td>
|
||||
</tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
36
hss-home-service/website/pages/resources.vue
Normal file
36
hss-home-service/website/pages/resources.vue
Normal file
@@ -0,0 +1,36 @@
|
||||
<script setup lang="ts">
|
||||
import { useSeo } from '~/composables/useSeo'
|
||||
useSeo({ title: '资源中心', description: '下载解决方案 PDF、白皮书、产品介绍资料,获取最新政策解读和行业洞察。' })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="py-20 bg-gradient-to-br from-primary-700 to-primary-900 text-white">
|
||||
<div class="section-container text-center">
|
||||
<h1 class="text-4xl md:text-5xl font-bold mb-4">资源中心</h1>
|
||||
<p class="text-xl text-blue-100 max-w-2xl mx-auto">下载解决方案、白皮书和产品资料</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="py-20 bg-surface">
|
||||
<div class="section-container">
|
||||
<div class="grid md:grid-cols-3 gap-6">
|
||||
<ResourceCard v-for="r in resources" :key="r.title" v-bind="r" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="py-20 bg-white">
|
||||
<div class="section-container text-center">
|
||||
<h2 class="section-title">获取完整方案资料</h2>
|
||||
<DownloadForm class="mt-8" />
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
const resources = [
|
||||
{ title: '平台解决方案', desc: '完整的平台能力介绍、服务闭环说明和技术架构概览', type: 'PDF' },
|
||||
{ title: '产品介绍白皮书', desc: '行业背景、平台定位、核心功能和建设价值详细说明', type: 'PDF' },
|
||||
{ title: '部署与对接指南', desc: '技术部署方案、API 对接说明和运维要求', type: 'PDF' },
|
||||
]
|
||||
</script>
|
||||
24
hss-home-service/website/pages/scenarios.vue
Normal file
24
hss-home-service/website/pages/scenarios.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
import { useSeo } from '~/composables/useSeo'
|
||||
import { scenarios } from '~/data/siteContent'
|
||||
useSeo({ title: '应用场景', description: '覆盖政府监管、医院延续护理、养老机构、社区服务中心、长护险管理五大应用场景。' })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="py-20 bg-gradient-to-br from-primary-700 to-primary-900 text-white">
|
||||
<div class="section-container text-center">
|
||||
<h1 class="text-4xl md:text-5xl font-bold mb-4">应用场景</h1>
|
||||
<p class="text-xl text-blue-100 max-w-2xl mx-auto">适配不同机构类型的业务需求</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="py-20 bg-surface">
|
||||
<div class="section-container">
|
||||
<div class="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<ScenarioCard v-for="s in scenarios" :key="s.title" v-bind="s" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<CtaSection />
|
||||
</template>
|
||||
21
hss-home-service/website/pages/security.vue
Normal file
21
hss-home-service/website/pages/security.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import { useSeo } from '~/composables/useSeo'
|
||||
useSeo({ title: '安全合规', description: 'RBAC 权限体系、数据分类分级、授权同意管理、审计日志追溯、脱敏展示、合规留痕。' })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="py-20 bg-gradient-to-br from-primary-700 to-primary-900 text-white">
|
||||
<div class="section-container text-center">
|
||||
<h1 class="text-4xl md:text-5xl font-bold mb-4">安全合规</h1>
|
||||
<p class="text-xl text-blue-100 max-w-2xl mx-auto">医疗级安全合规体系,满足政企、医疗行业标准</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="py-20 bg-surface">
|
||||
<div class="section-container">
|
||||
<SecurityGrid />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<CtaSection />
|
||||
</template>
|
||||
32
hss-home-service/website/pages/service-loop.vue
Normal file
32
hss-home-service/website/pages/service-loop.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<script setup lang="ts">
|
||||
import { useSeo } from '~/composables/useSeo'
|
||||
useSeo({ title: '服务闭环', description: '8 个阶段无缝衔接:需求受理→能力评估→方案制定→智能派单→上门执行→过程监管→验收评价→结算归档。' })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="py-20 bg-gradient-to-br from-primary-700 to-primary-900 text-white">
|
||||
<div class="section-container text-center">
|
||||
<h1 class="text-4xl md:text-5xl font-bold mb-4">服务闭环</h1>
|
||||
<p class="text-xl text-blue-100 max-w-2xl mx-auto">从申请到归档,每个环节都可监管、可追溯、可评价</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="py-20 bg-surface">
|
||||
<div class="section-container">
|
||||
<ServiceLoopFlow />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="py-20 bg-white">
|
||||
<div class="section-container">
|
||||
<h2 class="section-title">数据完整性保障</h2>
|
||||
<div class="grid md:grid-cols-3 gap-6 mt-12 text-center">
|
||||
<div class="p-6"><div class="w-12 h-12 mx-auto mb-3 rounded-xl bg-primary-50 text-primary flex items-center justify-center"><AppIcon name="database" class="w-6 h-6"/></div><h3 class="font-bold mb-2">环节联动</h3><p class="text-sm text-text-secondary">上游数据自动驱动下游,评估结果 → 方案制定,签署生效 → 服务计划</p></div>
|
||||
<div class="p-6"><div class="w-12 h-12 mx-auto mb-3 rounded-xl bg-accent-50 text-accent flex items-center justify-center"><AppIcon name="check" class="w-6 h-6"/></div><h3 class="font-bold mb-2">状态校验</h3><p class="text-sm text-text-secondary">每步流转必须满足前置条件,未签署方案不能生成工单,未验收工单不能结算</p></div>
|
||||
<div class="p-6"><div class="w-12 h-12 mx-auto mb-3 rounded-xl bg-cta/10 text-cta flex items-center justify-center"><AppIcon name="clipboard" class="w-6 h-6"/></div><h3 class="font-bold mb-2">版本可追溯</h3><p class="text-sm text-text-secondary">评估报告、方案快照、价格规则、签署记录全部版本化管理,不可覆盖</p></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<CtaSection />
|
||||
</template>
|
||||
51
hss-home-service/website/pages/solution.vue
Normal file
51
hss-home-service/website/pages/solution.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<script setup lang="ts">
|
||||
import { useSeo } from '~/composables/useSeo'
|
||||
import { painPoints } from '~/data/siteContent'
|
||||
useSeo({ title: '解决方案', description: '一套平台打通居家服务全流程,解决申请受理难、派单调度难、过程监管难等核心痛点。' })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="py-20 bg-gradient-to-br from-primary-700 to-primary-900 text-white">
|
||||
<div class="section-container text-center">
|
||||
<h1 class="text-4xl md:text-5xl font-bold mb-4">解决方案</h1>
|
||||
<p class="text-xl text-blue-100 max-w-2xl mx-auto">一套平台打通居家服务全流程,解决行业核心痛点</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="py-20 bg-surface">
|
||||
<div class="section-container">
|
||||
<h2 class="section-title">行业痛点</h2>
|
||||
<div class="grid md:grid-cols-3 gap-8 mt-12">
|
||||
<PainPointCard v-for="p in painPoints" :key="p.title" v-bind="p" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="py-20 bg-white">
|
||||
<div class="section-container">
|
||||
<h2 class="section-title">平台如何解决</h2>
|
||||
<div class="grid md:grid-cols-2 gap-8 mt-12">
|
||||
<div v-for="s in solutions" :key="s.title" class="flex gap-4">
|
||||
<div class="w-12 h-12 rounded-xl bg-primary-50 text-primary flex items-center justify-center shrink-0">
|
||||
<AppIcon :name="s.icon" class="w-6 h-6" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-bold text-xl mb-2">{{ s.title }}</h3>
|
||||
<p class="text-text-secondary">{{ s.desc }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<CtaSection />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
const solutions = [
|
||||
{ icon: 'database', title: '全流程打通', desc: '从申请到归档,一套平台覆盖所有业务环节,消除信息孤岛。' },
|
||||
{ icon: 'cog', title: '智能调度', desc: '算法匹配推荐,人工确认兜底,提升派单效率和公平性。' },
|
||||
{ icon: 'phone', title: '移动端执行', desc: '服务人员通过 Delivery 端完成接单、签到、执行、异常上报。' },
|
||||
{ icon: 'chart', title: '数据监管', desc: '实时看板、异常预警、质量分析,数据驱动管理决策。' },
|
||||
]
|
||||
</script>
|
||||
Reference in New Issue
Block a user