Compare commits
2 Commits
4e481dad5f
...
7802cfdf80
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7802cfdf80 | ||
|
|
532a8cc76c |
@ -15,6 +15,12 @@ export interface Agency {
|
||||
contactPhone: string
|
||||
bedCount: number | null
|
||||
signDate: number | null
|
||||
/** 质控时限(小时)。 */
|
||||
qcThreshold: number | null
|
||||
/** 对接档位:A/B/C。 */
|
||||
dockingTier: string | null
|
||||
/** 国家新增项通知开关。 */
|
||||
newNationalNotify: boolean
|
||||
createdAt: number
|
||||
createdBy: string | null
|
||||
updatedAt: number
|
||||
@ -35,6 +41,9 @@ export type AgencyUpdateData = Partial<
|
||||
| 'contactPhone'
|
||||
| 'bedCount'
|
||||
| 'signDate'
|
||||
| 'qcThreshold'
|
||||
| 'dockingTier'
|
||||
| 'newNationalNotify'
|
||||
>
|
||||
>
|
||||
|
||||
|
||||
@ -27,7 +27,7 @@ export interface SvcNational {
|
||||
version: number
|
||||
}
|
||||
|
||||
/** L4 机构项目库记录(机构勾选 + 配价)。 */
|
||||
/** L4 机构项目库记录(机构勾选 + 配价 + 拍照分档)。 */
|
||||
export interface SvcOrg {
|
||||
id: string
|
||||
tenantCode: string
|
||||
@ -36,6 +36,8 @@ export interface SvcOrg {
|
||||
isEnabled: boolean
|
||||
price: string | null
|
||||
priceEffective: number | null
|
||||
/** 拍照分档:must/free/optional。 */
|
||||
photoTier: string
|
||||
isNewNational: boolean
|
||||
createdAt: number
|
||||
createdBy: string | null
|
||||
@ -69,3 +71,22 @@ export function configSvcOrg(data: ConfigSvcOrgData): Promise<SvcOrg> {
|
||||
export function listNewNational(signal?: AbortSignal): Promise<SvcNational[]> {
|
||||
return http.get<SvcNational[]>('/svc/org/new-national', { signal })
|
||||
}
|
||||
|
||||
/** 拍照分档批量更新项。 */
|
||||
export interface UpdatePhotoTierItem {
|
||||
id: string
|
||||
/** must/free/optional。 */
|
||||
photoTier: string
|
||||
}
|
||||
|
||||
/** 拍照分档批量更新结果:must→free/optional 视为「放宽」,收集进 warnings。 */
|
||||
export interface UpdatePhotoTierResult {
|
||||
updated: number
|
||||
warnings: { id: string; from: string; to: string }[]
|
||||
message?: string
|
||||
}
|
||||
|
||||
/** 批量更新拍照分档。 */
|
||||
export function updatePhotoTier(items: UpdatePhotoTierItem[]): Promise<UpdatePhotoTierResult> {
|
||||
return http.post<UpdatePhotoTierResult>('/svc/org/photo-tier/batch', { items })
|
||||
}
|
||||
|
||||
103
src/api/document.ts
Normal file
103
src/api/document.ts
Normal file
@ -0,0 +1,103 @@
|
||||
import { http } from '@/api/http'
|
||||
|
||||
/**
|
||||
* 文书档案(一人一档)导出 API。
|
||||
* 后端返回结构化 JSON(身份证已掩码),前端负责组装下载文件。
|
||||
* 字段锚点见 shared/data-model spec(document 导出契约)。
|
||||
*/
|
||||
|
||||
/** 导出水印头。 */
|
||||
export interface DocumentWatermark {
|
||||
orgName: string
|
||||
exportTime: string
|
||||
operator: string
|
||||
notice: string
|
||||
}
|
||||
|
||||
/** 老人基本信息(@db.Date 字段已转 YYYY-MM-DD,idCardNo 已掩码)。 */
|
||||
export interface DocumentElderInfo {
|
||||
name: string
|
||||
gender: string
|
||||
birthDate: string | null
|
||||
idCardNo: string
|
||||
phone: string | null
|
||||
address: string | null
|
||||
insurNo: string | null
|
||||
careLevel: string | null
|
||||
treatStatus: string
|
||||
benefitStart: string | null
|
||||
benefitEnd: string | null
|
||||
}
|
||||
|
||||
/** 计划项(项目名快照)。 */
|
||||
export interface DocumentPlanItem {
|
||||
svcId: string
|
||||
svcName: string
|
||||
freqType: string | null
|
||||
freqValue: number | null
|
||||
durationMin: number | null
|
||||
photoTier: string | null
|
||||
}
|
||||
|
||||
/** 服务计划。 */
|
||||
export interface DocumentPlan {
|
||||
id: string
|
||||
planPeriodStart: string | null
|
||||
planPeriodEnd: string | null
|
||||
careLevel: string
|
||||
careMode: string
|
||||
planStatus: string
|
||||
items: DocumentPlanItem[]
|
||||
}
|
||||
|
||||
/** 留痕项目明细。 */
|
||||
export interface DocumentRecordItem {
|
||||
svcId: string
|
||||
svcName: string
|
||||
execResult: string
|
||||
undoReason: string | null
|
||||
extraFlag: boolean
|
||||
}
|
||||
|
||||
/** 履约留痕。 */
|
||||
export interface DocumentRecord {
|
||||
id: string
|
||||
checkinTime: string | null
|
||||
checkoutTime: string | null
|
||||
durationMin: number | null
|
||||
recordStatus: string
|
||||
items: DocumentRecordItem[]
|
||||
}
|
||||
|
||||
/** 异常上报。 */
|
||||
export interface DocumentAbnormal {
|
||||
id: string
|
||||
type: string
|
||||
desc: string | null
|
||||
handleStatus: string
|
||||
}
|
||||
|
||||
/** 一人一档结构化导出。 */
|
||||
export interface DocumentExportData {
|
||||
watermark: DocumentWatermark
|
||||
elder: DocumentElderInfo
|
||||
plans: DocumentPlan[]
|
||||
records: DocumentRecord[]
|
||||
abnormals: DocumentAbnormal[]
|
||||
}
|
||||
|
||||
export interface ExportDocumentQuery {
|
||||
elderId: string
|
||||
/** 留痕起始日期(YYYY-MM-DD)。 */
|
||||
startDate?: string
|
||||
/** 留痕结束日期(YYYY-MM-DD)。 */
|
||||
endDate?: string
|
||||
}
|
||||
|
||||
/** 按老人(+可选时间范围)导出一人一档。 */
|
||||
export function exportDocument(query: ExportDocumentQuery, signal?: AbortSignal): Promise<DocumentExportData> {
|
||||
const params: Record<string, unknown> = { elderId: query.elderId }
|
||||
if (query.startDate) params.startDate = query.startDate
|
||||
if (query.endDate) params.endDate = query.endDate
|
||||
return http.get<DocumentExportData>('/document/export', { params, signal })
|
||||
}
|
||||
@ -1,7 +1,109 @@
|
||||
<template>
|
||||
<div>服务项目与配价(C1 实现)</div>
|
||||
<div class="catalog-page">
|
||||
<el-card shadow="never">
|
||||
<div class="mb-md text-xs text-text-placeholder">
|
||||
机构只能勾选「是否开展」与「价格」,目录项目名称与来源由属地/国家库决定,不可修改。
|
||||
</div>
|
||||
<el-table v-loading="loading" :data="rows" border>
|
||||
<el-table-column prop="name" label="项目" min-width="180">
|
||||
<template #default="{ row }">
|
||||
{{ row.name }}
|
||||
<el-tag v-if="row.isNewNational" type="warning" size="small" class="ml-xs">国家新增</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="来源" width="90" align="center">
|
||||
<template #default="{ row }">{{ row.srcType === 'local' ? '属地' : '国家' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="是否开展" width="110" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-switch
|
||||
v-model="row.isEnabled"
|
||||
:loading="togglingId === row.id"
|
||||
@change="toggleEnabled(row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="价格" width="160">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.price" placeholder="预留,本期不参与结算" clearable />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="100" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
v-permission="'svc-org:config'"
|
||||
link
|
||||
type="primary"
|
||||
:loading="savingId === row.id"
|
||||
@click="savePrice(row)"
|
||||
>
|
||||
保存
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ApiError } from '@/api/http'
|
||||
import { configSvcOrg } from '@/api/catalog'
|
||||
import { loadL4Projects, type L4ProjectRow } from '../l4-projects'
|
||||
|
||||
defineOptions({ name: 'svc-config-catalog' })
|
||||
|
||||
const rows = ref<L4ProjectRow[]>([])
|
||||
const loading = ref(false)
|
||||
const togglingId = ref<string | null>(null)
|
||||
const savingId = ref<string | null>(null)
|
||||
|
||||
async function load(): Promise<void> {
|
||||
loading.value = true
|
||||
try {
|
||||
rows.value = await loadL4Projects()
|
||||
} catch (e) {
|
||||
if (e instanceof ApiError) ElMessage.error(e.message)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 开关即存:只改 isEnabled。失败回滚开关。 */
|
||||
async function toggleEnabled(row: Record<string, any>): Promise<void> {
|
||||
togglingId.value = row.id
|
||||
const before = row.isEnabled
|
||||
try {
|
||||
await configSvcOrg({ id: row.id, isEnabled: row.isEnabled })
|
||||
ElMessage.success('已保存')
|
||||
} catch (e) {
|
||||
row.isEnabled = !before
|
||||
if (e instanceof ApiError) ElMessage.error(e.message)
|
||||
} finally {
|
||||
togglingId.value = null
|
||||
}
|
||||
}
|
||||
|
||||
/** 配价:只改 price。 */
|
||||
async function savePrice(row: Record<string, any>): Promise<void> {
|
||||
savingId.value = row.id
|
||||
try {
|
||||
await configSvcOrg({ id: row.id, price: row.price ?? undefined })
|
||||
ElMessage.success('已保存')
|
||||
} catch (e) {
|
||||
if (e instanceof ApiError) ElMessage.error(e.message)
|
||||
} finally {
|
||||
savingId.value = null
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.catalog-page {
|
||||
@apply flex h-full flex-col;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,7 +1,73 @@
|
||||
<template>
|
||||
<div>对接档位(C1 实现)</div>
|
||||
<div class="docking-page">
|
||||
<el-card shadow="never">
|
||||
<div v-loading="loading">
|
||||
<div class="mb-md">
|
||||
<div class="text-sm text-text-regular">当前对接档位</div>
|
||||
<div class="mt-sm flex items-center gap-sm">
|
||||
<el-tag v-if="dockingTier" :type="dockingTier === 'C' ? 'info' : 'success'" size="large">
|
||||
{{ dockingTier }} 档
|
||||
</el-tag>
|
||||
<span v-else class="text-text-placeholder">未设置</span>
|
||||
<span v-if="dockingTier === 'C'" class="text-xs text-text-placeholder">待接口发布</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-divider content-position="left">切换档位</el-divider>
|
||||
<el-radio-group v-model="dockingTier" :disabled="saving" @change="save">
|
||||
<el-radio-button value="A">A 档</el-radio-button>
|
||||
<el-radio-button value="B">B 档</el-radio-button>
|
||||
<el-radio-button value="C" disabled>C 档(待接口发布)</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ApiError } from '@/api/http'
|
||||
import { getAgency, updateAgency } from '@/api/agency'
|
||||
|
||||
defineOptions({ name: 'svc-config-docking' })
|
||||
|
||||
const dockingTier = ref<string>('')
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
|
||||
async function load(): Promise<void> {
|
||||
loading.value = true
|
||||
try {
|
||||
const agency = await getAgency()
|
||||
dockingTier.value = agency.dockingTier ?? ''
|
||||
} catch (e) {
|
||||
if (e instanceof ApiError) ElMessage.error(e.message)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function save(value: string | number | boolean | undefined): Promise<void> {
|
||||
if (value !== 'A' && value !== 'B') return
|
||||
saving.value = true
|
||||
try {
|
||||
await updateAgency({ dockingTier: value })
|
||||
ElMessage.success('对接档位已切换')
|
||||
await load()
|
||||
} catch (e) {
|
||||
if (e instanceof ApiError) ElMessage.error(e.message)
|
||||
await load()
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.docking-page {
|
||||
@apply flex h-full flex-col;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,7 +1,130 @@
|
||||
<template>
|
||||
<div>机构信息(C1 实现)</div>
|
||||
<div class="info-page">
|
||||
<el-card shadow="never">
|
||||
<div v-loading="loading">
|
||||
<el-form label-width="120px" class="info-page__form">
|
||||
<el-form-item label="机构名称">
|
||||
<el-input v-model="form.orgName" placeholder="请输入机构名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="地址">
|
||||
<el-input v-model="form.address" placeholder="请输入地址" />
|
||||
</el-form-item>
|
||||
<el-form-item label="法人/联系人">
|
||||
<el-input v-model="form.contactName" placeholder="请输入法人/联系人" />
|
||||
</el-form-item>
|
||||
<el-form-item label="联系方式">
|
||||
<el-input v-model="form.contactPhone" placeholder="请输入联系方式" />
|
||||
</el-form-item>
|
||||
<el-form-item label="参保地">
|
||||
<el-input :model-value="locLabel" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="质控阈值(小时)">
|
||||
<el-input-number v-model="form.qcThreshold" :min="0" :step="1" step-strictly />
|
||||
<span class="ml-xs text-xs text-text-placeholder">留痕提交后超过该时限未质控即触发提醒</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="对接档位">
|
||||
<el-select v-model="form.dockingTier" placeholder="请选择" style="width: 200px">
|
||||
<el-option label="A 档" value="A" />
|
||||
<el-option label="B 档" value="B" />
|
||||
<el-option label="C 档(待接口发布)" value="C" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="国家新增项提示">
|
||||
<el-switch v-model="form.newNationalNotify" />
|
||||
<span class="ml-xs text-xs text-text-placeholder">出现国家新增服务项目时提示</span>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button v-permission="'agency:update'" type="primary" :loading="saving" @click="save">保存</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ApiError } from '@/api/http'
|
||||
import { getAgency, updateAgency } from '@/api/agency'
|
||||
|
||||
defineOptions({ name: 'svc-config-info' })
|
||||
|
||||
interface FormModel {
|
||||
orgName: string
|
||||
address: string
|
||||
contactName: string
|
||||
contactPhone: string
|
||||
locCode: string
|
||||
locName: string
|
||||
qcThreshold: number | null
|
||||
dockingTier: string | null
|
||||
newNationalNotify: boolean
|
||||
}
|
||||
|
||||
const form = reactive<FormModel>({
|
||||
orgName: '',
|
||||
address: '',
|
||||
contactName: '',
|
||||
contactPhone: '',
|
||||
locCode: '',
|
||||
locName: '',
|
||||
qcThreshold: null,
|
||||
dockingTier: null,
|
||||
newNationalNotify: false,
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
|
||||
const locLabel = computed(() => form.locName || form.locCode || '—')
|
||||
|
||||
async function load(): Promise<void> {
|
||||
loading.value = true
|
||||
try {
|
||||
const agency = await getAgency()
|
||||
form.orgName = agency.orgName ?? ''
|
||||
form.address = agency.address ?? ''
|
||||
form.contactName = agency.contactName ?? ''
|
||||
form.contactPhone = agency.contactPhone ?? ''
|
||||
form.locCode = agency.locCode ?? ''
|
||||
form.locName = agency.locName ?? ''
|
||||
form.qcThreshold = agency.qcThreshold ?? null
|
||||
form.dockingTier = agency.dockingTier ?? null
|
||||
form.newNationalNotify = agency.newNationalNotify ?? false
|
||||
} catch (e) {
|
||||
if (e instanceof ApiError) ElMessage.error(e.message)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function save(): Promise<void> {
|
||||
saving.value = true
|
||||
try {
|
||||
await updateAgency({
|
||||
orgName: form.orgName,
|
||||
address: form.address,
|
||||
contactName: form.contactName,
|
||||
contactPhone: form.contactPhone,
|
||||
qcThreshold: form.qcThreshold,
|
||||
dockingTier: form.dockingTier,
|
||||
newNationalNotify: form.newNationalNotify,
|
||||
})
|
||||
ElMessage.success('机构信息已保存')
|
||||
await load()
|
||||
} catch (e) {
|
||||
if (e instanceof ApiError) ElMessage.error(e.message)
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.info-page__form {
|
||||
@apply max-w-[640px];
|
||||
}
|
||||
</style>
|
||||
|
||||
29
src/pages/ltc/config/l4-projects.ts
Normal file
29
src/pages/ltc/config/l4-projects.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { getAgency } from '@/api/agency'
|
||||
import { listLocal, listNewNational, listOrg, type SvcOrg } from '@/api/catalog'
|
||||
|
||||
/** L4 机构项目 + 解析后的展示名(join L2 属地目录 / L1 国家目录)。 */
|
||||
export interface L4ProjectRow extends SvcOrg {
|
||||
name: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载当前机构 L4 项目库,并按 srcType/srcId join 目录解析展示名。
|
||||
* L4(svc_org)只有 srcType/srcId,无名称,需按来源 join L2/L1 目录。
|
||||
*/
|
||||
export async function loadL4Projects(): Promise<L4ProjectRow[]> {
|
||||
const [agency, orgs, nationals] = await Promise.all([getAgency(), listOrg(), listNewNational()])
|
||||
const locals = await listLocal(agency.locCode)
|
||||
|
||||
const localMap = new Map<string, string>()
|
||||
const nationalMap = new Map<string, string>()
|
||||
for (const it of locals) {
|
||||
if ('localItemName' in it) localMap.set(it.id, it.localItemName)
|
||||
else if ('natName' in it) nationalMap.set(it.id, it.natName)
|
||||
}
|
||||
for (const it of nationals) nationalMap.set(it.id, it.natName)
|
||||
|
||||
return orgs.map((o) => {
|
||||
const name = o.srcType === 'local' ? localMap.get(o.srcId) : nationalMap.get(o.srcId)
|
||||
return { ...o, name: name ?? o.srcId }
|
||||
})
|
||||
}
|
||||
@ -1,7 +1,171 @@
|
||||
<template>
|
||||
<div>属地与目录(C1 实现)</div>
|
||||
<div class="loc-page">
|
||||
<el-card class="mb-md" shadow="never">
|
||||
<div v-loading="agencyLoading" class="flex items-center">
|
||||
<span class="mr-sm">国家新增项提示</span>
|
||||
<el-switch
|
||||
v-model="newNationalNotify"
|
||||
:loading="notifySaving"
|
||||
@change="saveNotify"
|
||||
/>
|
||||
<span class="ml-xs text-xs text-text-placeholder">出现国家新增服务项目时提示</span>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never">
|
||||
<el-tabs v-model="activeTab">
|
||||
<el-tab-pane label="国家基准库(L1)" name="national">
|
||||
<el-table v-loading="loading" :data="nationals" border size="small">
|
||||
<el-table-column prop="natName" label="项目" min-width="160" />
|
||||
<el-table-column prop="natCategory" label="分类" width="120" />
|
||||
<el-table-column prop="natSubCategory" label="子分类" min-width="140">
|
||||
<template #default="{ row }">{{ row.natSubCategory ?? '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="建议人员" min-width="120">
|
||||
<template #default="{ row }">{{ row.recommendStaff ?? '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="建议时长" width="100">
|
||||
<template #default="{ row }">{{ row.recommendDuration ?? '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="医疗项目" width="90" align="center">
|
||||
<template #default="{ row }">{{ row.isMedical ? '是' : '—' }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="属地目录(L2)" name="local">
|
||||
<el-table v-loading="loading" :data="locals" border size="small">
|
||||
<el-table-column prop="localItemName" label="项目" min-width="160" />
|
||||
<el-table-column prop="localCategory" label="分类" min-width="120">
|
||||
<template #default="{ row }">{{ row.localCategory ?? '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="服务时长" width="100">
|
||||
<template #default="{ row }">{{ row.svcTime ?? '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="最低频次" width="100">
|
||||
<template #default="{ row }">{{ row.minFreq ?? '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="人员资质" min-width="120">
|
||||
<template #default="{ row }">{{ row.staffQual ?? '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="医疗项目" width="90" align="center">
|
||||
<template #default="{ row }">{{ row.isMedical ? '是' : '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="映射状态" width="100">
|
||||
<template #default="{ row }">{{ row.mapStatus ?? '—' }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="!loading && locals.length === 0" description="本参保地暂无属地目录" :image-size="60" />
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="机构项目(L4)" name="org">
|
||||
<el-table v-loading="loading" :data="orgs" border size="small">
|
||||
<el-table-column prop="name" label="项目" min-width="160" />
|
||||
<el-table-column label="来源" width="90" align="center">
|
||||
<template #default="{ row }">{{ row.srcType === 'local' ? '属地' : '国家' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="是否开展" width="90" align="center">
|
||||
<template #default="{ row }">{{ row.isEnabled ? '是' : '否' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="价格" width="100" align="right">
|
||||
<template #default="{ row }">{{ row.price ?? '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="拍照分档" width="100" align="center">
|
||||
<template #default="{ row }">{{ photoTierLabel(row.photoTier) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="国家新增" width="90" align="center">
|
||||
<template #default="{ row }">{{ row.isNewNational ? '是' : '—' }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ApiError } from '@/api/http'
|
||||
import { getAgency, updateAgency } from '@/api/agency'
|
||||
import { listLocal, listNewNational, type SvcLocal, type SvcNational } from '@/api/catalog'
|
||||
import { loadL4Projects, type L4ProjectRow } from '../l4-projects'
|
||||
|
||||
defineOptions({ name: 'svc-config-loc' })
|
||||
|
||||
const activeTab = ref('national')
|
||||
|
||||
const nationals = ref<SvcNational[]>([])
|
||||
const locals = ref<SvcLocal[]>([])
|
||||
const orgs = ref<L4ProjectRow[]>([])
|
||||
|
||||
const loading = ref(false)
|
||||
const agencyLoading = ref(false)
|
||||
const notifySaving = ref(false)
|
||||
const newNationalNotify = ref(false)
|
||||
|
||||
const PHOTO_TIER_LABELS: Record<string, string> = {
|
||||
must: '必拍',
|
||||
free: '不拍',
|
||||
optional: '选拍',
|
||||
}
|
||||
|
||||
function photoTierLabel(v: string): string {
|
||||
return PHOTO_TIER_LABELS[v] ?? v
|
||||
}
|
||||
|
||||
async function loadAgency(): Promise<void> {
|
||||
agencyLoading.value = true
|
||||
try {
|
||||
const agency = await getAgency()
|
||||
newNationalNotify.value = agency.newNationalNotify ?? false
|
||||
} catch (e) {
|
||||
if (e instanceof ApiError) ElMessage.error(e.message)
|
||||
} finally {
|
||||
agencyLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function load(): Promise<void> {
|
||||
loading.value = true
|
||||
try {
|
||||
const [agency, nationalRes, orgRes] = await Promise.all([
|
||||
getAgency(),
|
||||
listNewNational(),
|
||||
loadL4Projects(),
|
||||
])
|
||||
nationals.value = nationalRes
|
||||
orgs.value = orgRes
|
||||
|
||||
const localRes = await listLocal(agency.locCode)
|
||||
locals.value = localRes.filter((it): it is SvcLocal => 'localItemName' in it)
|
||||
} catch (e) {
|
||||
if (e instanceof ApiError) ElMessage.error(e.message)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function saveNotify(): Promise<void> {
|
||||
notifySaving.value = true
|
||||
try {
|
||||
await updateAgency({ newNationalNotify: newNationalNotify.value })
|
||||
ElMessage.success('已保存')
|
||||
} catch (e) {
|
||||
if (e instanceof ApiError) ElMessage.error(e.message)
|
||||
} finally {
|
||||
notifySaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
void loadAgency()
|
||||
void load()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.loc-page {
|
||||
@apply flex h-full flex-col;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,7 +1,141 @@
|
||||
<template>
|
||||
<div>拍照分档配置(C1 实现)</div>
|
||||
<div class="photo-page">
|
||||
<el-card shadow="never">
|
||||
<div class="mb-md flex items-center gap-sm">
|
||||
<el-select v-model="targetTier" placeholder="目标分档" style="width: 160px">
|
||||
<el-option label="必拍(must)" value="must" />
|
||||
<el-option label="选拍(optional)" value="optional" />
|
||||
<el-option label="不拍(free)" value="free" />
|
||||
</el-select>
|
||||
<el-button
|
||||
v-permission="'svc-org:config'"
|
||||
type="primary"
|
||||
:disabled="selectedRows.length === 0"
|
||||
:loading="saving"
|
||||
@click="applyBatch"
|
||||
>
|
||||
批量应用(已选 {{ selectedRows.length }} 项)
|
||||
</el-button>
|
||||
<span class="text-xs text-text-placeholder">必拍项目放宽为选拍/不拍会增加漏拍风险,不建议放宽。</span>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="rows"
|
||||
border
|
||||
@selection-change="onSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="48" />
|
||||
<el-table-column prop="name" label="项目" min-width="180">
|
||||
<template #default="{ row }">
|
||||
{{ row.name }}
|
||||
<el-tag v-if="row.isNewNational" type="warning" size="small" class="ml-xs">国家新增</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="当前分档" width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="PHOTO_TIER_TAG_MAP[row.photoTier]?.type ?? 'info'" size="small">
|
||||
{{ PHOTO_TIER_TAG_MAP[row.photoTier]?.label ?? row.photoTier }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="是否开展" width="100" align="center">
|
||||
<template #default="{ row }">{{ row.isEnabled ? '是' : '否' }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import type { TagType } from '@/types/crud'
|
||||
import { ApiError } from '@/api/http'
|
||||
import { updatePhotoTier } from '@/api/catalog'
|
||||
import { loadL4Projects, type L4ProjectRow } from '../l4-projects'
|
||||
|
||||
defineOptions({ name: 'svc-config-photo' })
|
||||
|
||||
const PHOTO_TIER_TAG_MAP: Record<string, { label: string; type: TagType }> = {
|
||||
must: { label: '必拍', type: 'danger' },
|
||||
free: { label: '不拍', type: 'info' },
|
||||
optional: { label: '选拍', type: 'warning' },
|
||||
}
|
||||
|
||||
const rows = ref<L4ProjectRow[]>([])
|
||||
const selectedRows = ref<L4ProjectRow[]>([])
|
||||
const targetTier = ref('')
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
|
||||
const selectedIds = computed(() => selectedRows.value.map((r) => r.id))
|
||||
|
||||
function onSelectionChange(rows: L4ProjectRow[]): void {
|
||||
selectedRows.value = rows
|
||||
}
|
||||
|
||||
async function load(): Promise<void> {
|
||||
loading.value = true
|
||||
try {
|
||||
rows.value = await loadL4Projects()
|
||||
selectedRows.value = []
|
||||
} catch (e) {
|
||||
if (e instanceof ApiError) ElMessage.error(e.message)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 应用前预检:must→free/optional 视为「放宽」,需用户确认(不建议放宽)。 */
|
||||
async function applyBatch(): Promise<void> {
|
||||
if (!targetTier.value) {
|
||||
ElMessage.warning('请选择目标分档')
|
||||
return
|
||||
}
|
||||
const relaxCount = selectedRows.value.filter(
|
||||
(r) => r.photoTier === 'must' && targetTier.value !== 'must',
|
||||
).length
|
||||
if (relaxCount > 0) {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`已放宽 ${relaxCount} 项,请确认(必拍项目不建议放宽)。`,
|
||||
'确认批量调整',
|
||||
{ type: 'warning', confirmButtonText: '确认调整', cancelButtonText: '取消' },
|
||||
)
|
||||
} catch {
|
||||
return // 用户取消
|
||||
}
|
||||
}
|
||||
|
||||
saving.value = true
|
||||
try {
|
||||
const result = await updatePhotoTier(
|
||||
selectedIds.value.map((id) => ({ id, photoTier: targetTier.value })),
|
||||
)
|
||||
if (result.warnings.length > 0) {
|
||||
ElMessageBox.alert(
|
||||
`已放宽 ${result.warnings.length} 项,请确认(不建议放宽)。`,
|
||||
'放宽提醒',
|
||||
{ type: 'warning' },
|
||||
)
|
||||
} else {
|
||||
ElMessage.success(`已更新 ${result.updated} 项`)
|
||||
}
|
||||
targetTier.value = ''
|
||||
await load()
|
||||
} catch (e) {
|
||||
if (e instanceof ApiError) ElMessage.error(e.message)
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.photo-page {
|
||||
@apply flex h-full flex-col;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,7 +1,69 @@
|
||||
<template>
|
||||
<div>质控提醒配置(C1 实现)</div>
|
||||
<div class="qc-page">
|
||||
<el-card shadow="never">
|
||||
<div v-loading="loading">
|
||||
<el-form label-width="140px" class="qc-page__form">
|
||||
<el-form-item label="质控时限(小时)">
|
||||
<el-input-number v-model="qcThreshold" :min="0" :step="1" step-strictly />
|
||||
<span class="ml-xs text-xs text-text-placeholder">留痕提交后超过该时限未质控即触发提醒</span>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button v-permission="'agency:update'" type="primary" :loading="saving" @click="save">
|
||||
保存
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ApiError } from '@/api/http'
|
||||
import { getAgency, updateAgency } from '@/api/agency'
|
||||
|
||||
defineOptions({ name: 'svc-config-qc' })
|
||||
|
||||
const qcThreshold = ref<number | null>(null)
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
|
||||
async function load(): Promise<void> {
|
||||
loading.value = true
|
||||
try {
|
||||
const agency = await getAgency()
|
||||
qcThreshold.value = agency.qcThreshold ?? null
|
||||
} catch (e) {
|
||||
if (e instanceof ApiError) ElMessage.error(e.message)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function save(): Promise<void> {
|
||||
if (qcThreshold.value == null) {
|
||||
ElMessage.warning('请设置质控时限')
|
||||
return
|
||||
}
|
||||
saving.value = true
|
||||
try {
|
||||
await updateAgency({ qcThreshold: qcThreshold.value })
|
||||
ElMessage.success('质控时限已保存')
|
||||
await load()
|
||||
} catch (e) {
|
||||
if (e instanceof ApiError) ElMessage.error(e.message)
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.qc-page__form {
|
||||
@apply max-w-[560px];
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,7 +1,327 @@
|
||||
<template>
|
||||
<div>文书档案(C1–C4 实现)</div>
|
||||
<div class="document-page">
|
||||
<el-card class="mb-md" shadow="never">
|
||||
<ty-search-form :schema="searchSchema" :model-value="query" @search="handleSearch" @reset="handleReset" />
|
||||
</el-card>
|
||||
|
||||
<el-card class="document-page__table" shadow="never">
|
||||
<ty-table
|
||||
:columns="columns"
|
||||
:data="list"
|
||||
:loading="loading"
|
||||
:total="total"
|
||||
v-model:page="page"
|
||||
v-model:page-size="pageSize"
|
||||
:actions-width="100"
|
||||
>
|
||||
<template #actions="{ row }">
|
||||
<el-button v-permission="'export:export'" link type="primary" @click="openDetail(row)">查看</el-button>
|
||||
</template>
|
||||
</ty-table>
|
||||
</el-card>
|
||||
|
||||
<el-dialog v-model="detailVisible" :title="`一人一档(${currentName})`" width="920px" top="6vh">
|
||||
<div v-loading="detailLoading" class="document-page__detail">
|
||||
<template v-if="detail">
|
||||
<el-alert
|
||||
v-if="detail.watermark"
|
||||
:title="`${detail.watermark.orgName} · 导出时间 ${formatDateTime(detail.watermark.exportTime)} · ${detail.watermark.notice}`"
|
||||
type="info"
|
||||
:closable="false"
|
||||
class="mb-md"
|
||||
/>
|
||||
|
||||
<el-divider content-position="left">基本信息</el-divider>
|
||||
<el-descriptions :column="3" border size="small">
|
||||
<el-descriptions-item label="姓名">{{ detail.elder.name }}</el-descriptions-item>
|
||||
<el-descriptions-item label="性别">{{ labelOf(GENDER_OPTIONS, detail.elder.gender) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="身份证号">{{ detail.elder.idCardNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="出生日期">{{ detail.elder.birthDate ?? '—' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="联系电话">{{ detail.elder.phone ?? '—' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="居住地址">{{ detail.elder.address ?? '—' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="参保号">{{ detail.elder.insurNo ?? '—' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="护理等级">{{ labelOf(CARE_LEVEL_OPTIONS, detail.elder.careLevel) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="待遇状态">
|
||||
<el-tag :type="TREAT_STATUS_TAG_MAP[detail.elder.treatStatus]?.type ?? 'info'" size="small">
|
||||
{{ TREAT_STATUS_TAG_MAP[detail.elder.treatStatus]?.label ?? detail.elder.treatStatus }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="待遇起">{{ detail.elder.benefitStart ?? '—' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="待遇止">{{ detail.elder.benefitEnd ?? '—' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<el-divider content-position="left">服务计划</el-divider>
|
||||
<div v-if="detail.plans.length > 0">
|
||||
<div v-for="plan in detail.plans" :key="plan.id" class="mb-md">
|
||||
<div class="mb-xs text-sm text-text-regular">
|
||||
计划 {{ plan.id }} · {{ plan.planPeriodStart ?? '—' }} ~ {{ plan.planPeriodEnd ?? '—' }}
|
||||
· {{ labelOf(CARE_MODE_OPTIONS, plan.careMode) }} · {{ planStatusLabel(plan.planStatus) }}
|
||||
</div>
|
||||
<el-table :data="plan.items" border size="small">
|
||||
<el-table-column prop="svcName" label="项目" min-width="160" />
|
||||
<el-table-column label="频次" min-width="120">
|
||||
<template #default="{ row }">{{ freqLabel(row) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="时长(分)" width="90" align="center">
|
||||
<template #default="{ row }">{{ row.durationMin ?? '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="拍照分档" width="100" align="center">
|
||||
<template #default="{ row }">{{ photoTierLabel(row.photoTier) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
<el-empty v-else description="暂无服务计划" :image-size="60" />
|
||||
|
||||
<el-divider content-position="left">留痕记录</el-divider>
|
||||
<el-table v-if="detail.records.length > 0" :data="detail.records" border size="small">
|
||||
<el-table-column label="签到时间" width="160">
|
||||
<template #default="{ row }">{{ formatDateTime(row.checkinTime) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="签退时间" width="160">
|
||||
<template #default="{ row }">{{ formatDateTime(row.checkoutTime) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="时长(分)" width="90" align="center">
|
||||
<template #default="{ row }">{{ row.durationMin ?? '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="90" align="center">
|
||||
<template #default="{ row }">{{ recordStatusLabel(row.recordStatus) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="项目执行" min-width="200">
|
||||
<template #default="{ row }">
|
||||
<div v-for="it in row.items" :key="it.svcId" class="text-xs">
|
||||
{{ it.svcName }}:{{ execResultLabel(it.execResult) }}{{ it.undoReason ? `(${it.undoReason})` : '' }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-else description="暂无留痕记录" :image-size="60" />
|
||||
|
||||
<el-divider content-position="left">异常上报</el-divider>
|
||||
<el-table v-if="detail.abnormals.length > 0" :data="detail.abnormals" border size="small">
|
||||
<el-table-column label="分类" width="130">
|
||||
<template #default="{ row }">{{ abnormalTypeLabel(row.type) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="描述" min-width="180">
|
||||
<template #default="{ row }">{{ row.desc ?? '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="处理状态" width="100" align="center">
|
||||
<template #default="{ row }">{{ handleStatusLabel(row.handleStatus) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-else description="暂无异常上报" :image-size="60" />
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="flex w-full items-center gap-sm">
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
type="daterange"
|
||||
value-format="YYYY-MM-DD"
|
||||
start-placeholder="留痕起始"
|
||||
end-placeholder="留痕结束"
|
||||
range-separator="至"
|
||||
class="flex-1"
|
||||
/>
|
||||
<el-button v-permission="'export:export'" type="primary" :loading="exporting" @click="download">
|
||||
导出 JSON
|
||||
</el-button>
|
||||
<el-button @click="detailVisible = false">关闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ApiError } from '@/api/http'
|
||||
import type { JsonSchema, TableColumn } from '@/types/crud'
|
||||
import { useRequest } from '@/composables/useRequest'
|
||||
import { listElders, type ElderListQuery } from '@/api/elder'
|
||||
import {
|
||||
exportDocument,
|
||||
type DocumentExportData,
|
||||
} from '@/api/document'
|
||||
import { ABNORMAL_TYPE_LABEL_MAP, HANDLE_STATUS_TAG_MAP } from '@/api/abnormal'
|
||||
import { EXEC_RESULT_TAG_MAP } from '@/api/record'
|
||||
import {
|
||||
CARE_LEVEL_OPTIONS,
|
||||
CARE_MODE_OPTIONS,
|
||||
GENDER_OPTIONS,
|
||||
TREAT_STATUS_TAG_MAP,
|
||||
formatDateTime,
|
||||
labelOf,
|
||||
} from '@/pages/ltc/elder/constants'
|
||||
|
||||
defineOptions({ name: 'document' })
|
||||
|
||||
const columns: TableColumn[] = [
|
||||
{ prop: 'name', label: '姓名', minWidth: 100 },
|
||||
{ prop: 'gender', label: '性别', width: 70, align: 'center', formatter: (v) => labelOf(GENDER_OPTIONS, v) },
|
||||
{ prop: 'careLevel', label: '护理等级', minWidth: 140, formatter: (v) => labelOf(CARE_LEVEL_OPTIONS, v) },
|
||||
{ prop: 'treatStatus', label: '待遇状态', width: 120, align: 'center', type: 'tag', tagMap: TREAT_STATUS_TAG_MAP },
|
||||
{ prop: 'phone', label: '联系电话', width: 130, formatter: (v) => (v == null || v === '' ? '—' : String(v)) },
|
||||
]
|
||||
|
||||
const searchSchema: JsonSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', title: '姓名', placeholder: '请输入姓名' },
|
||||
},
|
||||
}
|
||||
|
||||
const query = reactive<Record<string, any>>({ name: '' })
|
||||
const list = ref<Record<string, any>[]>([])
|
||||
const total = ref(0)
|
||||
const page = ref(1)
|
||||
const pageSize = ref(10)
|
||||
|
||||
const str = (v: unknown): string | undefined => (typeof v === 'string' && v !== '' ? v : undefined)
|
||||
|
||||
function toListQuery(): ElderListQuery {
|
||||
return { page: page.value, pageSize: pageSize.value, name: str(query.name) }
|
||||
}
|
||||
|
||||
const { loading, run: fetchList } = useRequest((signal: AbortSignal) => listElders(toListQuery(), signal), {
|
||||
manual: true,
|
||||
onSuccess: (result) => {
|
||||
list.value = result.list.map((e) => ({ ...e }))
|
||||
total.value = result.total
|
||||
},
|
||||
})
|
||||
|
||||
watch([page, pageSize], () => fetchList())
|
||||
|
||||
function handleSearch(): void {
|
||||
if (page.value === 1) fetchList()
|
||||
else page.value = 1
|
||||
}
|
||||
|
||||
function handleReset(): void {
|
||||
if (page.value === 1) fetchList()
|
||||
else page.value = 1
|
||||
}
|
||||
|
||||
// ---- 一人一档详情 ----
|
||||
const detailVisible = ref(false)
|
||||
const detailLoading = ref(false)
|
||||
const detail = ref<DocumentExportData | null>(null)
|
||||
const currentElderId = ref('')
|
||||
const currentName = ref('')
|
||||
const dateRange = ref<[string, string] | null>(null)
|
||||
const exporting = ref(false)
|
||||
|
||||
const PHOTO_TIER_LABELS: Record<string, string> = {
|
||||
must: '必拍',
|
||||
free: '不拍',
|
||||
optional: '选拍',
|
||||
}
|
||||
const PLAN_STATUS_LABELS: Record<string, string> = {
|
||||
active: '生效中',
|
||||
changed: '已变更',
|
||||
}
|
||||
|
||||
function photoTierLabel(v: string | null): string {
|
||||
return v ? PHOTO_TIER_LABELS[v] ?? v : '—'
|
||||
}
|
||||
|
||||
function planStatusLabel(v: string): string {
|
||||
return PLAN_STATUS_LABELS[v] ?? v
|
||||
}
|
||||
|
||||
function freqLabel(item: { freqType?: string | null; freqValue?: number | null }): string {
|
||||
const v = item.freqValue
|
||||
switch (item.freqType) {
|
||||
case 'daily':
|
||||
return '每日'
|
||||
case 'weekly_n':
|
||||
return `每周 ${v ?? '—'} 次`
|
||||
case 'monthly_n':
|
||||
return `每月 ${v ?? '—'} 次`
|
||||
case 'as_needed':
|
||||
return '按需'
|
||||
default:
|
||||
return item.freqType ?? '—'
|
||||
}
|
||||
}
|
||||
|
||||
function recordStatusLabel(v: string): string {
|
||||
return v === 'submitted' ? '已提交' : v === 'serving' ? '服务中' : v
|
||||
}
|
||||
|
||||
function execResultLabel(v: string): string {
|
||||
return EXEC_RESULT_TAG_MAP[v]?.label ?? v
|
||||
}
|
||||
|
||||
function abnormalTypeLabel(v: string): string {
|
||||
return ABNORMAL_TYPE_LABEL_MAP[v] ?? v
|
||||
}
|
||||
|
||||
function handleStatusLabel(v: string): string {
|
||||
return HANDLE_STATUS_TAG_MAP[v]?.label ?? v
|
||||
}
|
||||
|
||||
async function openDetail(row: Record<string, any>): Promise<void> {
|
||||
currentElderId.value = String(row.id)
|
||||
currentName.value = String(row.name ?? '')
|
||||
dateRange.value = null
|
||||
detailVisible.value = true
|
||||
detail.value = null
|
||||
detailLoading.value = true
|
||||
try {
|
||||
detail.value = await exportDocument({ elderId: currentElderId.value })
|
||||
} catch (e) {
|
||||
if (e instanceof ApiError) ElMessage.error(e.message)
|
||||
} finally {
|
||||
detailLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 按当前时间范围导出并下载 JSON。 */
|
||||
async function download(): Promise<void> {
|
||||
if (!currentElderId.value) return
|
||||
exporting.value = true
|
||||
try {
|
||||
const data = await exportDocument({
|
||||
elderId: currentElderId.value,
|
||||
startDate: dateRange.value?.[0],
|
||||
endDate: dateRange.value?.[1],
|
||||
})
|
||||
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = `文书档案_${currentName.value}_${new Date().toISOString().slice(0, 10)}.json`
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
ElMessage.success('已导出')
|
||||
} catch (e) {
|
||||
if (e instanceof ApiError) ElMessage.error(e.message)
|
||||
} finally {
|
||||
exporting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => fetchList())
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.document-page {
|
||||
@apply flex h-full flex-col;
|
||||
}
|
||||
|
||||
.document-page__table {
|
||||
@apply flex min-h-0 flex-1 flex-col;
|
||||
|
||||
:deep(.el-card__body) {
|
||||
@apply flex min-h-0 flex-1 flex-col;
|
||||
}
|
||||
}
|
||||
|
||||
.document-page__detail {
|
||||
@apply max-h-[62vh] overflow-auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user