Files
xh-medical-sign-web/clinical-web/src/views/management/documents/components/DocumentTemplateCard.vue
T

339 lines
8.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { computed } from 'vue'
import type { TemplateVersionAction } from '@/api/management/documents'
import {
canInitiateSigning,
canToggleStatus,
DOCUMENT_STATUS_DOT,
isGeneralOwnership,
readLibraryStatus,
readStatusTone,
} from '../library'
import { DOC_TEMPLATE_PERMISSIONS, workflowPermission } from '../permissions'
import type { DocumentTemplate } from '../types'
const props = defineProps<{
template: DocumentTemplate
/**
* 演示模式。
*
* 真实模式下卡片只有「停用 / 启用」这一个状态动作:后端菜单资源树里的
* 「发布 / 提交审核」按钮虽在,但原型 V10.2 已经抛弃那条工作流,
* 科室侧不该再看到「提交审核」这一步(草稿的送审改由属性/正文弹窗完成,
* 或直接由医务管理放行)。
*/
isMock: boolean
}>()
const emit = defineEmits<{
preview: [template: DocumentTemplate]
'edit-props': [template: DocumentTemplate]
'edit-content': [template: DocumentTemplate]
history: [template: DocumentTemplate]
'toggle-status': [template: DocumentTemplate]
'submit-review': [template: DocumentTemplate]
initiate: [template: DocumentTemplate]
}>()
const status = computed(() => readLibraryStatus(props.template))
const tone = computed(() => readStatusTone(status.value))
const dotColor = computed(() => DOCUMENT_STATUS_DOT[tone.value])
const isGeneral = computed(() => isGeneralOwnership(props.template))
const ownershipLabel = computed(() =>
isGeneral.value ? '🌐 全院通用' : `🏥 ${props.template.departmentName ?? '未知科室'}`,
)
/**
* 归属徽标上的「·+N 科室」。
*
* sharedDepartments 只在演示文库里有值(后端 CreateTemplateRequest 只接受单个
* departmentId),所以真实模式下这里自然为 0、标签不显示后缀——
* 不给真实数据编一个后端没存的共享清单。
*/
const sharedCount = computed(() => props.template.sharedDepartments?.length ?? 0)
/** 只有「启用中」能发起签署;其余状态要么没定稿,要么已下线 */
const canInitiate = computed(() => canInitiateSigning(status.value))
const initiateTitle = computed(() =>
canInitiate.value ? '用此文书发起签署' : '仅「启用中」的文书可发起签署',
)
const canToggle = computed(() => canToggleStatus(status.value))
const toggleLabel = computed(() => (status.value === '停用' ? '启用' : '停用'))
/** 草稿的「提交审核」只在演示模式露出,真实模式不给这个入口 */
const showSubmitReview = computed(() => props.isMock && status.value === '草稿')
/** 停用/启用走的是后端版本动作:停用 → disable,启用 → publish */
const toggleAction = computed<TemplateVersionAction>(() =>
status.value === '停用' ? 'publish' : 'disable',
)
// 按钮文案与权限码的对应关系集中在模板里,避免多处各写一份
const permissions = DOC_TEMPLATE_PERMISSIONS
const togglePermission = computed(() => workflowPermission(toggleAction.value))
</script>
<template>
<article class="tpl-card">
<div class="tpl-hd">
<h3 class="tpl-title" :title="template.name">{{ template.name }}</h3>
<span class="tpl-status" :class="'tone-' + tone">
<i :style="{ background: dotColor }" aria-hidden="true" />
{{ status }}
</span>
</div>
<p class="tpl-meta">{{ template.code || '未编号' }} · {{ template.version }}</p>
<div class="tpl-tags">
<span class="exam-tag" :class="isGeneral ? 'is-general' : 'is-department'">
{{ ownershipLabel }}<template v-if="!isGeneral && sharedCount"> ·+{{ sharedCount }} 科室</template>
</span>
</div>
<p class="tpl-desc">{{ template.description || '—' }}</p>
<div class="tpl-ft">
<button type="button" class="card-button" @click="emit('preview', template)">预览</button>
<button
v-permission="permissions.props"
type="button"
class="card-button"
@click="emit('edit-props', template)"
>
编辑属性
</button>
<button
v-permission="permissions.edit"
type="button"
class="card-button"
@click="emit('edit-content', template)"
>
编辑正文
</button>
<button
v-permission="permissions.history"
type="button"
class="card-button"
@click="emit('history', template)"
>
历史记录
</button>
<span class="ft-spacer" />
<button
v-if="canToggle"
v-permission="togglePermission"
type="button"
class="card-button"
:title="
toggleLabel === '停用'
? '停用后各科室不可见、不可发起签署'
: '启用后各科室可见并可发起签署'
"
@click="emit('toggle-status', template)"
>
{{ toggleLabel }}
</button>
<button
v-if="showSubmitReview"
v-permission="permissions.publish"
type="button"
class="card-button"
title="提交审核,审核通过后自动发布"
@click="emit('submit-review', template)"
>
提交审核
</button>
<button
v-if="canInitiate"
v-permission="permissions.initiate"
type="button"
class="card-button primary"
:title="initiateTitle"
@click="emit('initiate', template)"
>
发起
</button>
</div>
</article>
</template>
<style scoped>
.tpl-card {
display: flex;
flex-direction: column;
gap: 10px;
min-width: 0;
padding: 16px;
background: var(--card);
border: 1px solid transparent;
border-radius: var(--r);
box-shadow: var(--sh);
transition:
border-color 0.15s ease,
box-shadow 0.15s ease;
}
.tpl-card:hover {
border-color: var(--brand);
box-shadow: 0 6px 18px rgb(14 110 140 / 10%);
}
.tpl-hd {
display: flex;
gap: 10px;
align-items: flex-start;
justify-content: space-between;
}
.tpl-title {
display: -webkit-box;
flex: 1;
margin: 0;
overflow: hidden;
color: var(--ink);
font-size: 14px;
line-height: 1.4;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
.tpl-status {
display: inline-flex;
flex-shrink: 0;
gap: 5px;
align-items: center;
padding: 3px 9px;
font-size: 11px;
font-weight: 700;
white-space: nowrap;
border-radius: 12px;
}
.tpl-status i {
width: 6px;
height: 6px;
border-radius: 50%;
}
/* 徽标底色沿用原型 tg-* 那套色调;圆点颜色由内联样式给出(见 DOCUMENT_STATUS_DOT) */
.tone-enabled {
color: var(--ok);
background: var(--ok-l);
}
.tone-review {
color: var(--warn);
background: var(--warn-l);
}
.tone-draft,
.tone-archived,
.tone-stopped {
color: #8a9aa3;
background: #eceef0;
}
.tpl-meta {
margin: 0;
overflow: hidden;
color: var(--mut);
font-size: 11.5px;
text-overflow: ellipsis;
white-space: nowrap;
}
.tpl-tags {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
/* 归属徽标:全院通用用蓝、科室用绿,与原型 tpl-card 的取色一致 */
.exam-tag {
display: inline-flex;
align-items: center;
padding: 2px 8px;
font-size: 10.5px;
font-weight: 700;
white-space: nowrap;
border-radius: 10px;
}
.exam-tag.is-general {
color: #3a7bd5;
background: #e7effb;
}
.exam-tag.is-department {
color: #0b7a4b;
background: #e2f5ec;
}
.tpl-desc {
display: -webkit-box;
flex: 1;
min-height: 58px;
margin: 0;
overflow: hidden;
color: var(--mut);
font-size: 12px;
line-height: 1.6;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
}
.tpl-ft {
display: flex;
flex-wrap: wrap;
gap: 6px;
align-items: center;
padding-top: 10px;
margin-top: 4px;
border-top: 1px solid var(--line);
}
/* 把状态动作与发起推到右侧,左侧四个入口(预览/属性/正文/历史)成一组 */
.ft-spacer {
flex: 1 1 auto;
}
.card-button {
padding: 4px 9px;
color: var(--ink);
font-size: 12px;
background: #fff;
border: 1px solid var(--line);
border-radius: 5px;
}
.card-button:hover {
color: var(--brand);
border-color: var(--brand);
}
.card-button.primary {
color: #fff;
background: var(--brand);
border-color: var(--brand);
}
.card-button.primary:hover {
color: #fff;
background: var(--brand-d);
border-color: var(--brand-d);
}
@media (max-width: 680px) {
.ft-spacer {
display: none;
}
}
</style>