完成consumer端同步

This commit is contained in:
2026-05-14 15:28:09 +08:00
parent 612fb3d360
commit 0ffbc53902
197 changed files with 92657 additions and 7564 deletions

View File

@@ -0,0 +1,192 @@
<template>
<scroll-view class="page" scroll-y="true">
<ServicePanel title="服务申请管理" subtitle="沿用后台列表 + 状态标签 + 操作按钮的组织方式。">
<view class="overview-row">
<view v-for="item in overview" :key="item.id" class="overview-card" :class="'overview-' + item.tone">
<text class="overview-value">{{ item.value }}</text>
<text class="overview-label">{{ item.label }}</text>
</view>
</view>
</ServicePanel>
<ServicePanel title="申请列表" subtitle="首批先展示申请受理、评估和验收前的关键字段。">
<view v-for="item in applications" :key="item.id" class="app-card">
<view class="app-row">
<view>
<text class="app-title">{{ item.elderName }} · {{ item.serviceName }}</text>
<text class="app-meta">{{ item.caseNo }}</text>
</view>
<ServiceStatusTag :text="item.statusText" :tone="item.statusTone"></ServiceStatusTag>
</view>
<text class="app-info">期望时间:{{ item.preferredTime }}</text>
<text class="app-info">评估结果:{{ item.assessmentResult }}</text>
<text class="app-info">调度员:{{ item.dispatcherName }} · 执行人员:{{ item.staffName }}</text>
<ServiceActionRow
:actions="[
{ key: 'assessment', label: '去评估', tone: 'ghost' },
{ key: 'plan', label: '编制方案', tone: 'primary' }
]"
@action="(key: string) => handlePrimaryAction(item.caseId, key)"
></ServiceActionRow>
<ServiceActionRow
:actions="[
{ key: 'rectification', label: '整改处理', tone: 'warn' },
{ key: 'settlement', label: '结算归档', tone: 'success' }
]"
:compact="true"
@action="(key: string) => handleSecondaryAction(item.caseId, key)"
></ServiceActionRow>
</view>
<view class="jump-btn" @click="goDispatch">进入派单调度</view>
</ServicePanel>
</scroll-view>
</template>
<script setup lang="uts">
import { ref } from 'vue'
import { onLoad, onShow } from '@dcloudio/uni-app'
import ServiceActionRow from '@/components/homeService/ServiceActionRow.uvue'
import ServicePanel from '@/components/homeService/ServicePanel.uvue'
import ServiceStatusTag from '@/components/homeService/ServiceStatusTag.uvue'
import { fetchAdminHomeServiceApplications, fetchAdminHomeServiceOverview } from '@/services/homeServiceService.uts'
import { HomeServiceAdminApplicationType, HomeServiceOverviewCardType } from '@/types/home-service.uts'
const applications = ref<Array<HomeServiceAdminApplicationType>>([])
const overview = ref<Array<HomeServiceOverviewCardType>>([])
async function loadData() {
applications.value = await fetchAdminHomeServiceApplications()
overview.value = await fetchAdminHomeServiceOverview()
}
function goDispatch() {
uni.navigateTo({ url: '/pages/mall/admin/home-service/dispatch-center/index' })
}
function goAssessment(caseId: string) {
uni.navigateTo({ url: '/pages/mall/admin/home-service/assessment-form/index?id=' + caseId })
}
function goPlan(caseId: string) {
uni.navigateTo({ url: '/pages/mall/admin/home-service/service-plan/index?id=' + caseId })
}
function goRectification(caseId: string) {
uni.navigateTo({ url: '/pages/mall/admin/home-service/rectification/index?id=' + caseId })
}
function goSettlement(caseId: string) {
uni.navigateTo({ url: '/pages/mall/admin/home-service/settlement-archive/index?id=' + caseId })
}
function handlePrimaryAction(caseId: string, actionKey: string) {
if (actionKey == 'assessment') {
goAssessment(caseId)
} else if (actionKey == 'plan') {
goPlan(caseId)
}
}
function handleSecondaryAction(caseId: string, actionKey: string) {
if (actionKey == 'rectification') {
goRectification(caseId)
} else if (actionKey == 'settlement') {
goSettlement(caseId)
}
}
onLoad(() => {
loadData()
})
onShow(() => {
loadData()
})
</script>
<style scoped>
.page {
min-height: 100vh;
background: #f5f7fb;
padding: 24rpx;
box-sizing: border-box;
}
.overview-row {
flex-direction: row;
flex-wrap: wrap;
gap: 16rpx;
}
.overview-card {
width: 48%;
padding: 24rpx;
border-radius: 20rpx;
}
.overview-primary {
background: #e8f2ff;
}
.overview-warning {
background: #fff4e5;
}
.overview-success {
background: #e8f7ef;
}
.overview-neutral {
background: #eef2f6;
}
.overview-value {
font-size: 40rpx;
font-weight: 700;
color: #16324f;
}
.overview-label {
margin-top: 8rpx;
font-size: 24rpx;
color: #66788a;
}
.app-card {
padding: 24rpx;
border-radius: 20rpx;
background: #f8fbfc;
margin-bottom: 20rpx;
}
.app-row {
flex-direction: row;
justify-content: space-between;
align-items: flex-start;
}
.app-title {
font-size: 30rpx;
font-weight: 700;
color: #16324f;
}
.app-meta,
.app-info {
margin-top: 8rpx;
font-size: 24rpx;
line-height: 36rpx;
color: #66788a;
}
.jump-btn {
margin-top: 8rpx;
padding: 24rpx 0;
text-align: center;
font-size: 28rpx;
font-weight: 700;
color: #ffffff;
background: #1d4ed8;
border-radius: 18rpx;
}
</style>

View File

@@ -0,0 +1,168 @@
<template>
<scroll-view class="page" scroll-y="true">
<view v-if="detail == null" class="empty-box">
<text class="empty-text">未找到评估信息</text>
</view>
<view v-else>
<ServicePanel title="上门评估" subtitle="先用 mock 表单承接风险等级、护理等级和评估结论。">
<ServiceInfoList
:items="[
{ label: '服务单号:', value: detail.caseNo },
{ label: '服务对象:', value: detail.elderName },
{ label: '申请服务:', value: detail.serviceName },
{ label: '预约上门:', value: detail.visitTime }
]"
></ServiceInfoList>
<view class="block">
<text class="label">风险等级</text>
<view class="chip-row">
<view v-for="item in riskOptions" :key="item" class="chip" :class="riskLevel == item ? 'chip-active' : ''" @click="riskLevel = item">{{ item }}</view>
</view>
</view>
<view class="block">
<text class="label">护理等级</text>
<view class="chip-row">
<view v-for="item in careOptions" :key="item" class="chip" :class="careLevel == item ? 'chip-active' : ''" @click="careLevel = item">{{ item }}</view>
</view>
</view>
<view class="block">
<text class="label">评估标签</text>
<view class="tag-row">
<view v-for="item in detail.requirementTags" :key="item" class="tag">{{ item }}</view>
</view>
</view>
<view class="block">
<text class="label">评估结论</text>
<textarea v-model="assessmentSummary" class="textarea" placeholder="填写服务对象能力状态、风险点和护理建议"></textarea>
</view>
<view class="submit-btn" @click="submitAssessment">提交评估</view>
</ServicePanel>
</view>
</scroll-view>
</template>
<script setup lang="uts">
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import ServiceInfoList from '@/components/homeService/ServiceInfoList.uvue'
import ServicePanel from '@/components/homeService/ServicePanel.uvue'
import { fetchAdminAssessmentDetail, submitAdminAssessment } from '@/services/homeServiceService.uts'
import { HomeServiceAssessmentType } from '@/types/home-service.uts'
const caseId = ref('')
const detail = ref<HomeServiceAssessmentType | null>(null)
const riskLevel = ref('中风险')
const careLevel = ref('护理二级')
const assessmentSummary = ref('行动缓慢,需重点关注晨间血压、跌倒风险和夜间陪护提醒。')
const riskOptions = ['低风险', '中风险', '高风险']
const careOptions = ['护理一级', '护理二级', '护理三级', '随访管理']
onLoad((options) => {
const id = options['id']
if (id != null) {
caseId.value = id as string
fetchAdminAssessmentDetail(caseId.value).then((res) => {
if (res != null) {
detail.value = res
riskLevel.value = res.riskLevel
careLevel.value = res.careLevel
assessmentSummary.value = res.assessmentSummary
}
})
}
})
async function submitAssessment() {
if (caseId.value == '' || assessmentSummary.value == '') {
uni.showToast({ title: '请填写评估结论', icon: 'none' })
return
}
const result = await submitAdminAssessment(caseId.value, riskLevel.value, careLevel.value, assessmentSummary.value)
if (result != null) {
uni.showToast({ title: '评估已提交', icon: 'success' })
setTimeout(() => {
uni.navigateBack()
}, 400)
}
}
</script>
<style scoped>
.page {
min-height: 100vh;
background: #f5f7fb;
padding: 24rpx;
box-sizing: border-box;
}
.label,
.chip,
.tag,
.empty-text {
font-size: 28rpx;
line-height: 40rpx;
color: #16324f;
}
.block {
margin-top: 24rpx;
}
.chip-row,
.tag-row {
margin-top: 16rpx;
flex-direction: row;
flex-wrap: wrap;
gap: 16rpx;
}
.chip,
.tag {
padding: 16rpx 20rpx;
border-radius: 18rpx;
background: #f0f4f8;
}
.chip-active {
background: #e8f2ff;
color: #1d4ed8;
}
.tag {
background: #e8f7ef;
color: #15803d;
}
.textarea {
margin-top: 16rpx;
width: 100%;
height: 260rpx;
padding: 24rpx;
box-sizing: border-box;
background: #f8fbfc;
border-radius: 20rpx;
font-size: 28rpx;
color: #23384d;
}
.submit-btn {
margin-top: 32rpx;
padding: 26rpx 0;
border-radius: 20rpx;
background: #1d4ed8;
text-align: center;
font-size: 30rpx;
font-weight: 700;
color: #ffffff;
}
.empty-box {
padding: 120rpx 0;
align-items: center;
}
</style>

View File

@@ -0,0 +1,113 @@
<template>
<scroll-view class="page" scroll-y="true">
<ServicePanel title="派单调度中心" subtitle="首批只做调度总览和待派单清单,后续再补排班与改派。">
<view v-for="item in applications" :key="item.id" class="dispatch-card">
<view class="dispatch-row">
<view>
<text class="dispatch-title">{{ item.serviceName }}</text>
<text class="dispatch-meta">{{ item.caseNo }} · {{ item.elderName }}</text>
</view>
<ServiceStatusTag :text="item.statusText" :tone="item.statusTone"></ServiceStatusTag>
</view>
<text class="dispatch-info">建议上门时间:{{ item.preferredTime }}</text>
<text class="dispatch-info">评估结论:{{ item.assessmentResult }}</text>
<text class="dispatch-info">当前执行人员:{{ item.staffName }}</text>
<view class="row-actions">
<view class="action ghost" @click="goPlan(item.caseId)">查看方案</view>
<view class="action primary" @click="toastAssign">模拟派单</view>
</view>
</view>
</ServicePanel>
</scroll-view>
</template>
<script setup lang="uts">
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import ServicePanel from '@/components/homeService/ServicePanel.uvue'
import ServiceStatusTag from '@/components/homeService/ServiceStatusTag.uvue'
import { fetchAdminHomeServiceApplications } from '@/services/homeServiceService.uts'
import { HomeServiceAdminApplicationType } from '@/types/home-service.uts'
const applications = ref<Array<HomeServiceAdminApplicationType>>([])
async function loadData() {
applications.value = await fetchAdminHomeServiceApplications()
}
function goPlan(caseId: string) {
uni.navigateTo({ url: '/pages/mall/admin/home-service/service-plan/index?id=' + caseId })
}
function toastAssign() {
uni.showToast({ title: '已完成 mock 派单演示', icon: 'none' })
}
onLoad(() => {
loadData()
})
</script>
<style scoped>
.page {
min-height: 100vh;
background: #f5f7fb;
padding: 24rpx;
box-sizing: border-box;
}
.dispatch-card {
padding: 24rpx;
border-radius: 20rpx;
background: #f8fbfc;
margin-bottom: 20rpx;
}
.dispatch-row,
.row-actions {
flex-direction: row;
justify-content: space-between;
align-items: flex-start;
}
.dispatch-title {
font-size: 30rpx;
font-weight: 700;
color: #16324f;
}
.dispatch-meta,
.dispatch-info {
margin-top: 8rpx;
font-size: 24rpx;
line-height: 36rpx;
color: #66788a;
}
.row-actions {
margin-top: 20rpx;
gap: 16rpx;
}
.action {
flex: 1;
padding: 22rpx 0;
border-radius: 16rpx;
text-align: center;
font-size: 26rpx;
font-weight: 700;
}
.ghost {
background: #ffffff;
color: #16324f;
border-width: 2rpx;
border-style: solid;
border-color: #d7e0ea;
}
.primary {
background: #1d4ed8;
color: #ffffff;
}
</style>

View File

@@ -0,0 +1,122 @@
<template>
<scroll-view class="page" scroll-y="true">
<view v-if="detail == null" class="empty-box">
<text class="empty-text">未找到整改信息</text>
</view>
<view v-else>
<ServicePanel title="整改处理" subtitle="用于承接家属退回后的整改闭环。">
<ServiceInfoCard
:title="detail.serviceName"
:code="detail.caseNo"
:status-text="detail.statusText"
:items="[
{ label: '责任人', value: detail.ownerName },
{ label: '整改时限', value: detail.deadline }
]"
></ServiceInfoCard>
<ServiceInfoList
:items="[
{ label: '服务对象:', value: detail.elderName },
{ label: '服务项目:', value: detail.serviceName }
]"
></ServiceInfoList>
<view class="block">
<text class="label">问题说明</text>
<textarea v-model="issueSummary" class="textarea" placeholder="填写整改动作、补充说明和复验收提示"></textarea>
</view>
<view class="submit-btn" @click="submitRectification">提交整改结果</view>
</ServicePanel>
</view>
</scroll-view>
</template>
<script setup lang="uts">
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import ServiceInfoCard from '@/components/homeService/ServiceInfoCard.uvue'
import ServiceInfoList from '@/components/homeService/ServiceInfoList.uvue'
import ServicePanel from '@/components/homeService/ServicePanel.uvue'
import { fetchAdminRectificationDetail, submitAdminRectification } from '@/services/homeServiceService.uts'
import { HomeServiceRectificationType } from '@/types/home-service.uts'
const caseId = ref('')
const detail = ref<HomeServiceRectificationType | null>(null)
const issueSummary = ref('已补充现场留痕与护理动作说明,建议家属重新确认验收。')
onLoad((options) => {
const id = options['id']
if (id != null) {
caseId.value = id as string
fetchAdminRectificationDetail(caseId.value).then((res) => {
if (res != null) {
detail.value = res
issueSummary.value = res.issueSummary
}
})
}
})
async function submitRectification() {
if (caseId.value == '' || issueSummary.value == '') {
uni.showToast({ title: '请填写整改结果', icon: 'none' })
return
}
const result = await submitAdminRectification(caseId.value, issueSummary.value)
if (result != null) {
uni.showToast({ title: '整改已提交', icon: 'success' })
setTimeout(() => {
uni.navigateBack()
}, 400)
}
}
</script>
<style scoped>
.page {
min-height: 100vh;
background: #f5f7fb;
padding: 24rpx;
box-sizing: border-box;
}
.label,
.empty-text {
font-size: 28rpx;
line-height: 40rpx;
color: #16324f;
}
.block {
margin-top: 24rpx;
}
.textarea {
margin-top: 16rpx;
width: 100%;
height: 260rpx;
padding: 24rpx;
box-sizing: border-box;
background: #f8fbfc;
border-radius: 20rpx;
font-size: 28rpx;
color: #23384d;
}
.submit-btn {
margin-top: 32rpx;
padding: 26rpx 0;
border-radius: 20rpx;
background: #b45309;
text-align: center;
font-size: 30rpx;
font-weight: 700;
color: #ffffff;
}
.empty-box {
padding: 120rpx 0;
align-items: center;
}
</style>

View File

@@ -0,0 +1,157 @@
<template>
<scroll-view class="page" scroll-y="true">
<view v-if="detail == null" class="empty-box">
<text class="empty-text">未找到服务方案</text>
</view>
<view v-else>
<ServicePanel title="服务方案" subtitle="先用 mock 数据完成计划频次、周期和执行说明。">
<ServiceInfoList
:items="[
{ label: '服务单号:', value: detail.caseNo },
{ label: '服务对象:', value: detail.elderName },
{ label: '服务类型:', value: detail.serviceName }
]"
></ServiceInfoList>
<view class="block">
<text class="label">方案标题</text>
<input v-model="planTitle" class="input" placeholder="请输入方案标题" />
</view>
<view class="block">
<text class="label">服务频次</text>
<input v-model="serviceFrequency" class="input" placeholder="例如 每周 3 次" />
</view>
<view class="block">
<text class="label">服务周期</text>
<input v-model="serviceCycle" class="input" placeholder="例如 2026-05-14 至 2026-05-21" />
</view>
<view class="block">
<text class="label">执行建议</text>
<view class="advice-box">{{ detail.executorAdvice }}</view>
</view>
<view class="block">
<text class="label">收费说明</text>
<view class="advice-box">{{ detail.billingSummary }}</view>
</view>
<view class="block">
<text class="label">方案摘要</text>
<textarea v-model="planSummary" class="textarea" placeholder="填写服务目标、执行重点和验收口径"></textarea>
</view>
<view class="submit-btn" @click="submitPlan">提交方案</view>
</ServicePanel>
</view>
</scroll-view>
</template>
<script setup lang="uts">
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import ServiceInfoList from '@/components/homeService/ServiceInfoList.uvue'
import ServicePanel from '@/components/homeService/ServicePanel.uvue'
import { fetchAdminServicePlanDetail, submitAdminServicePlan } from '@/services/homeServiceService.uts'
import { HomeServicePlanType } from '@/types/home-service.uts'
const caseId = ref('')
const detail = ref<HomeServicePlanType | null>(null)
const planTitle = ref('')
const serviceFrequency = ref('')
const serviceCycle = ref('')
const planSummary = ref('')
onLoad((options) => {
const id = options['id']
if (id != null) {
caseId.value = id as string
fetchAdminServicePlanDetail(caseId.value).then((res) => {
if (res != null) {
detail.value = res
planTitle.value = res.planTitle
serviceFrequency.value = res.serviceFrequency
serviceCycle.value = res.serviceCycle
planSummary.value = res.planSummary
}
})
}
})
async function submitPlan() {
if (caseId.value == '' || planTitle.value == '' || serviceFrequency.value == '' || serviceCycle.value == '' || planSummary.value == '') {
uni.showToast({ title: '请补全方案信息', icon: 'none' })
return
}
const result = await submitAdminServicePlan(caseId.value, planTitle.value, serviceFrequency.value, serviceCycle.value, planSummary.value)
if (result != null) {
uni.showToast({ title: '方案已提交', icon: 'success' })
setTimeout(() => {
uni.navigateBack()
}, 400)
}
}
</script>
<style scoped>
.page {
min-height: 100vh;
background: #f5f7fb;
padding: 24rpx;
box-sizing: border-box;
}
.label,
.advice-box,
.empty-text {
font-size: 28rpx;
line-height: 40rpx;
color: #16324f;
}
.block {
margin-top: 24rpx;
}
.input,
.textarea {
margin-top: 16rpx;
width: 100%;
padding: 24rpx;
box-sizing: border-box;
background: #f8fbfc;
border-radius: 20rpx;
font-size: 28rpx;
color: #23384d;
}
.textarea {
height: 260rpx;
}
.advice-box {
margin-top: 16rpx;
padding: 22rpx 24rpx;
background: #eef2f6;
border-radius: 18rpx;
color: #66788a;
}
.submit-btn {
margin-top: 32rpx;
padding: 26rpx 0;
border-radius: 20rpx;
background: #1d4ed8;
text-align: center;
font-size: 30rpx;
font-weight: 700;
color: #ffffff;
}
.empty-box {
padding: 120rpx 0;
align-items: center;
}
</style>

View File

@@ -0,0 +1,121 @@
<template>
<scroll-view class="page" scroll-y="true">
<view v-if="detail == null" class="empty-box">
<text class="empty-text">未找到结算信息</text>
</view>
<view v-else>
<ServicePanel title="结算归档" subtitle="承接已完成服务单的费用确认与档案归集。">
<ServiceInfoCard
:title="detail.serviceName"
:code="detail.caseNo"
:status-text="detail.archiveStatusText"
:items="[
{ label: '账期', value: detail.billingPeriod }
]"
></ServiceInfoCard>
<ServiceInfoList
:items="[
{ label: '服务对象:', value: detail.elderName }
]"
></ServiceInfoList>
<view class="amount-card">
<text class="amount-line">服务总额:{{ detail.totalAmount }}</text>
<text class="amount-line">长护险/补贴:{{ detail.insuranceAmount }}</text>
<text class="amount-line">个人自付:{{ detail.selfPayAmount }}</text>
</view>
<view class="archive-box">
<text class="archive-text">已包含执行记录、验收反馈、结算单与电子档案索引,提交后将标记为已归档。</text>
</view>
<view class="submit-btn" @click="submitArchive">确认归档</view>
</ServicePanel>
</view>
</scroll-view>
</template>
<script setup lang="uts">
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import ServiceInfoCard from '@/components/homeService/ServiceInfoCard.uvue'
import ServiceInfoList from '@/components/homeService/ServiceInfoList.uvue'
import ServicePanel from '@/components/homeService/ServicePanel.uvue'
import { fetchAdminSettlementDetail, submitAdminSettlementArchive } from '@/services/homeServiceService.uts'
import { HomeServiceSettlementType } from '@/types/home-service.uts'
const caseId = ref('')
const detail = ref<HomeServiceSettlementType | null>(null)
onLoad((options) => {
const id = options['id']
if (id != null) {
caseId.value = id as string
fetchAdminSettlementDetail(caseId.value).then((res) => {
if (res != null) {
detail.value = res
}
})
}
})
async function submitArchive() {
if (caseId.value == '') {
return
}
const result = await submitAdminSettlementArchive(caseId.value)
if (result != null) {
detail.value = result
uni.showToast({ title: '已完成归档', icon: 'success' })
}
}
</script>
<style scoped>
.page {
min-height: 100vh;
background: #f5f7fb;
padding: 24rpx;
box-sizing: border-box;
}
.amount-line,
.archive-text,
.empty-text {
font-size: 28rpx;
line-height: 40rpx;
color: #16324f;
}
.amount-card,
.archive-box {
margin-top: 24rpx;
padding: 24rpx;
border-radius: 20rpx;
background: #f8fbfc;
}
.amount-line + .amount-line {
margin-top: 12rpx;
}
.archive-text {
color: #66788a;
}
.submit-btn {
margin-top: 32rpx;
padding: 26rpx 0;
border-radius: 20rpx;
background: #15803d;
text-align: center;
font-size: 30rpx;
font-weight: 700;
color: #ffffff;
}
.empty-box {
padding: 120rpx 0;
align-items: center;
}
</style>