Files
Home-Care/hss-home-service/website/pages/platform/work-orders.vue
comclib c02029a5f3 feat: 初始化居家上门服务系统完整项目代码
- Spring Boot 后端服务 (hss-home-service)
- delivery-miniapp 配送小程序
- website 官网 (Nuxt)
- docs 架构设计文档
- Docker 容器化部署配置

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 09:04:49 +08:00

81 lines
5.6 KiB
Vue

<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>