884 lines
22 KiB
Vue
884 lines
22 KiB
Vue
<script setup lang="ts">
|
||
/**
|
||
* 医生端 PC 传方入口:顶栏按钮 + 子窗口列表 + 状态操作
|
||
* 权限:有 doctor_id 且绑定 store_id 时由外层 v-if 控制,本组件只负责展示与操作
|
||
*/
|
||
import {
|
||
computed,
|
||
onBeforeUnmount,
|
||
onMounted,
|
||
ref,
|
||
watch,
|
||
} from 'vue';
|
||
|
||
import {
|
||
FileTextOutlined,
|
||
ReloadOutlined,
|
||
ZoomInOutlined,
|
||
ZoomOutOutlined,
|
||
} from '@ant-design/icons-vue';
|
||
import { usePreferences } from '@vben/preferences';
|
||
import { useUserStore } from '@vben/stores';
|
||
|
||
import {
|
||
Badge,
|
||
Button,
|
||
Dropdown,
|
||
Image,
|
||
Input,
|
||
Menu,
|
||
Modal,
|
||
Select,
|
||
Spin,
|
||
Tag,
|
||
Textarea,
|
||
Tooltip,
|
||
message,
|
||
} from 'ant-design-vue';
|
||
|
||
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
|
||
import {
|
||
DEFAULT_SUB_WINDOW_Z_INDEX,
|
||
getSubWindowPopupZIndex,
|
||
} from '#/components/sub-window/constants';
|
||
import SubWindow from '#/components/sub-window/SubWindow.vue';
|
||
import {
|
||
getTransferPrescriptionListByStoreApi,
|
||
updateTransferPrescriptionHandleStatusApi,
|
||
} from '#/views/business/salesperson-transfer-prescription/api';
|
||
import { invokeDoctorTransferImport } from '#/views/doctor/doctor-reception/composables/useDoctorReceptionTransferImport';
|
||
|
||
const REFRESH_STORAGE_KEY = 'pc_doctor_transfer_refresh_sec';
|
||
const DEFAULT_AVATAR = '/img/user-default-avatar.png';
|
||
const IMAGE_SCALE_MIN = 0.5;
|
||
const IMAGE_SCALE_MAX = 3;
|
||
const IMAGE_SCALE_STEP = 0.25;
|
||
|
||
const userStore = useUserStore();
|
||
const { isDark } = usePreferences();
|
||
|
||
/** 与子窗口 z-index 对齐的弹出层层级 */
|
||
const popupZIndex = computed(() => getSubWindowPopupZIndex(DEFAULT_SUB_WINDOW_Z_INDEX));
|
||
|
||
const windowOpen = ref(false);
|
||
const loading = ref(false);
|
||
const list = ref<any[]>([]);
|
||
const pollTimer = ref<ReturnType<typeof setInterval> | null>(null);
|
||
const inlineImageUrl = ref('');
|
||
const imageScale = ref(1);
|
||
const imagePanX = ref(0);
|
||
const imagePanY = ref(0);
|
||
const imageDragging = ref(false);
|
||
const imageDragStartX = ref(0);
|
||
const imageDragStartY = ref(0);
|
||
const imagePanOriginX = ref(0);
|
||
const imagePanOriginY = ref(0);
|
||
|
||
const salespersonKeyword = ref('');
|
||
const patientName = ref('');
|
||
const refreshIntervalSec = ref(30);
|
||
|
||
const exceptionModalOpen = ref(false);
|
||
const exceptionMessage = ref('');
|
||
const pendingStatusItem = ref<any>(null);
|
||
|
||
const storeId = computed(() => Number(userStore.userInfo?.store_id || 0));
|
||
|
||
const pendingCount = computed(
|
||
() => list.value.filter((item) => Number(item.status) === 0).length,
|
||
);
|
||
|
||
const refreshOptions = [
|
||
{ value: 0, label: '不刷新' },
|
||
{ value: 10, label: '10秒' },
|
||
{ value: 30, label: '30秒' },
|
||
{ value: 60, label: '1分钟' },
|
||
];
|
||
|
||
const statusOptions = [
|
||
{ value: 0, label: '未处理' },
|
||
{ value: 1, label: '处理中' },
|
||
{ value: 2, label: '已完成' },
|
||
{ value: 3, label: '异常' },
|
||
];
|
||
|
||
const imageTransformStyle = computed(() => ({
|
||
transform: `translate(${imagePanX.value}px, ${imagePanY.value}px) scale(${imageScale.value})`,
|
||
transformOrigin: 'center top',
|
||
}));
|
||
|
||
/** Dropdown 挂载到 body,避免被 overflow 裁剪 */
|
||
function getPopupContainer() {
|
||
return document.body;
|
||
}
|
||
|
||
function statusColor(status: number) {
|
||
const map: Record<number, string> = {
|
||
0: 'warning',
|
||
1: 'processing',
|
||
2: 'success',
|
||
3: 'error',
|
||
};
|
||
return map[Number(status)] || 'default';
|
||
}
|
||
|
||
/** 重置内嵌看图缩放与平移 */
|
||
function resetImageView() {
|
||
imageScale.value = 1;
|
||
imagePanX.value = 0;
|
||
imagePanY.value = 0;
|
||
}
|
||
|
||
/** 从 localStorage 读取刷新间隔配置 */
|
||
function loadRefreshSetting() {
|
||
const cached = localStorage.getItem(REFRESH_STORAGE_KEY);
|
||
if (cached !== null) {
|
||
const val = Number(cached);
|
||
if ([0, 10, 30, 60].includes(val)) {
|
||
refreshIntervalSec.value = val;
|
||
}
|
||
}
|
||
}
|
||
|
||
function saveRefreshSetting() {
|
||
localStorage.setItem(REFRESH_STORAGE_KEY, String(refreshIntervalSec.value));
|
||
}
|
||
|
||
/** 加载当前门店传方列表 */
|
||
async function loadList(silent = false) {
|
||
if (!storeId.value) return;
|
||
if (!silent) loading.value = true;
|
||
try {
|
||
const params: Record<string, any> = {
|
||
store_id: storeId.value,
|
||
page: 1,
|
||
pageSize: 50,
|
||
};
|
||
if (salespersonKeyword.value.trim()) {
|
||
params.salesperson_keyword = salespersonKeyword.value.trim();
|
||
}
|
||
if (patientName.value.trim()) {
|
||
params.patient_name = patientName.value.trim();
|
||
}
|
||
const res = await getTransferPrescriptionListByStoreApi(params);
|
||
list.value = res?.items ?? [];
|
||
} catch (e) {
|
||
console.error(e);
|
||
if (!silent) list.value = [];
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
}
|
||
|
||
function handleSearch() {
|
||
loadList();
|
||
}
|
||
|
||
function handleManualRefresh() {
|
||
loadList();
|
||
}
|
||
|
||
/** 按配置间隔启动轮询(0 表示不自动刷新) */
|
||
function restartPoll() {
|
||
stopPoll();
|
||
const sec = refreshIntervalSec.value;
|
||
if (sec <= 0) return;
|
||
pollTimer.value = setInterval(() => loadList(true), sec * 1000);
|
||
}
|
||
|
||
function stopPoll() {
|
||
if (pollTimer.value) {
|
||
clearInterval(pollTimer.value);
|
||
pollTimer.value = null;
|
||
}
|
||
}
|
||
|
||
function openWindow() {
|
||
windowOpen.value = true;
|
||
inlineImageUrl.value = '';
|
||
resetImageView();
|
||
loadList();
|
||
}
|
||
|
||
function openImageInWindow(url: string) {
|
||
inlineImageUrl.value = url;
|
||
resetImageView();
|
||
}
|
||
|
||
function closeInlineImage() {
|
||
inlineImageUrl.value = '';
|
||
resetImageView();
|
||
}
|
||
|
||
function zoomIn() {
|
||
imageScale.value = Math.min(
|
||
IMAGE_SCALE_MAX,
|
||
Math.round((imageScale.value + IMAGE_SCALE_STEP) * 100) / 100,
|
||
);
|
||
}
|
||
|
||
function zoomOut() {
|
||
imageScale.value = Math.max(
|
||
IMAGE_SCALE_MIN,
|
||
Math.round((imageScale.value - IMAGE_SCALE_STEP) * 100) / 100,
|
||
);
|
||
}
|
||
|
||
function resetImageZoom() {
|
||
resetImageView();
|
||
}
|
||
|
||
/** 滚轮缩放图片 */
|
||
function onImageWheel(e: WheelEvent) {
|
||
e.preventDefault();
|
||
if (e.deltaY < 0) zoomIn();
|
||
else zoomOut();
|
||
}
|
||
|
||
/** 内嵌看图:按住拖拽平移 */
|
||
function onImageMouseDown(e: MouseEvent) {
|
||
if (e.button !== 0) return;
|
||
imageDragging.value = true;
|
||
imageDragStartX.value = e.clientX;
|
||
imageDragStartY.value = e.clientY;
|
||
imagePanOriginX.value = imagePanX.value;
|
||
imagePanOriginY.value = imagePanY.value;
|
||
document.addEventListener('mousemove', onImageMouseMove);
|
||
document.addEventListener('mouseup', onImageMouseUp);
|
||
}
|
||
|
||
function onImageMouseMove(e: MouseEvent) {
|
||
if (!imageDragging.value) return;
|
||
imagePanX.value = imagePanOriginX.value + (e.clientX - imageDragStartX.value);
|
||
imagePanY.value = imagePanOriginY.value + (e.clientY - imageDragStartY.value);
|
||
}
|
||
|
||
function onImageMouseUp() {
|
||
imageDragging.value = false;
|
||
document.removeEventListener('mousemove', onImageMouseMove);
|
||
document.removeEventListener('mouseup', onImageMouseUp);
|
||
}
|
||
|
||
/** 选择处理状态 */
|
||
async function handleStatusSelect(item: any, status: number) {
|
||
if (status === 3) {
|
||
pendingStatusItem.value = item;
|
||
exceptionMessage.value = item.exception_message || '';
|
||
exceptionModalOpen.value = true;
|
||
return;
|
||
}
|
||
await submitStatus(item, status, '');
|
||
}
|
||
|
||
async function submitStatus(item: any, status: number, exceptionMsg: string) {
|
||
if (!storeId.value) return;
|
||
try {
|
||
await updateTransferPrescriptionHandleStatusApi({
|
||
id: item.id,
|
||
store_id: storeId.value,
|
||
status,
|
||
exception_message: status === 3 ? exceptionMsg : '',
|
||
});
|
||
message.success('状态已更新');
|
||
exceptionModalOpen.value = false;
|
||
pendingStatusItem.value = null;
|
||
loadList(true);
|
||
} catch (e: any) {
|
||
message.error(e?.message || '更新失败');
|
||
}
|
||
}
|
||
|
||
async function confirmException() {
|
||
if (!exceptionMessage.value.trim()) {
|
||
message.warning('请填写异常说明');
|
||
return;
|
||
}
|
||
if (pendingStatusItem.value) {
|
||
await submitStatus(pendingStatusItem.value, 3, exceptionMessage.value.trim());
|
||
}
|
||
}
|
||
|
||
/** 是否可导入:手动输入且有药品 */
|
||
function canImportItem(item: any) {
|
||
if (Number(item.transfer_mode ?? 1) === 2) return false;
|
||
return Boolean(item.drug_list?.length || item.drug_text_lines?.length);
|
||
}
|
||
|
||
/** 当前接诊页选中的挂号 ID */
|
||
function getCurrentRegisterId() {
|
||
return Number.parseInt(localStorage.getItem('doctorReception-id') || '0', 10);
|
||
}
|
||
|
||
/** 导入传方药品到当前接诊患者 */
|
||
function handleImportToCurrentPatient(item: any) {
|
||
const registerId = getCurrentRegisterId();
|
||
if (!registerId) {
|
||
message.warning('请先在接诊页选择患者');
|
||
return;
|
||
}
|
||
if (!canImportItem(item)) {
|
||
message.warning('仅支持手动输入药品的传方');
|
||
return;
|
||
}
|
||
const ok = invokeDoctorTransferImport({ transferId: item.id, registerId });
|
||
if (!ok) {
|
||
message.warning('请先打开接诊页面');
|
||
}
|
||
}
|
||
|
||
watch(refreshIntervalSec, () => {
|
||
saveRefreshSetting();
|
||
restartPoll();
|
||
});
|
||
|
||
watch(windowOpen, (v) => {
|
||
if (!v) {
|
||
inlineImageUrl.value = '';
|
||
resetImageView();
|
||
}
|
||
});
|
||
|
||
watch(storeId, () => {
|
||
if (windowOpen.value) loadList(true);
|
||
});
|
||
|
||
onBeforeUnmount(() => {
|
||
stopPoll();
|
||
document.removeEventListener('mousemove', onImageMouseMove);
|
||
document.removeEventListener('mouseup', onImageMouseUp);
|
||
});
|
||
|
||
onMounted(() => {
|
||
loadRefreshSetting();
|
||
loadList(true);
|
||
restartPoll();
|
||
});
|
||
|
||
defineExpose({ loadList });
|
||
</script>
|
||
|
||
<template>
|
||
<div class="doctor-transfer-header-entry">
|
||
<Tooltip title="门店传方">
|
||
<Badge :count="pendingCount" :overflow-count="99" size="small">
|
||
<Button
|
||
type="text"
|
||
class="transfer-header-btn"
|
||
@click="openWindow"
|
||
>
|
||
<FileTextOutlined />
|
||
<span class="transfer-header-btn__text">传方</span>
|
||
</Button>
|
||
</Badge>
|
||
</Tooltip>
|
||
</div>
|
||
<SubWindow
|
||
v-model:open="windowOpen"
|
||
title="门店传方"
|
||
storage-key="pc_doctor_transfer_window"
|
||
:width="440"
|
||
:height="560"
|
||
:min-width="360"
|
||
:min-height="320"
|
||
:z-index="DEFAULT_SUB_WINDOW_Z_INDEX"
|
||
>
|
||
<div class="panel" :class="{ 'is-dark': isDark }">
|
||
<div v-if="inlineImageUrl" class="inline-image-layer">
|
||
<div class="inline-image-toolbar">
|
||
<Button type="link" size="small" @click="closeInlineImage">返回列表</Button>
|
||
<Button type="link" size="small" @click="zoomIn">
|
||
<ZoomInOutlined /> 放大
|
||
</Button>
|
||
<Button type="link" size="small" @click="zoomOut">
|
||
<ZoomOutOutlined /> 缩小
|
||
</Button>
|
||
<Button type="link" size="small" @click="resetImageZoom">还原</Button>
|
||
<span class="zoom-percent">{{ Math.round(imageScale * 100) }}%</span>
|
||
</div>
|
||
<div
|
||
class="inline-image-body"
|
||
:class="{ dragging: imageDragging }"
|
||
@wheel="onImageWheel"
|
||
@mousedown="onImageMouseDown"
|
||
>
|
||
<img
|
||
:src="inlineImageUrl"
|
||
class="inline-image"
|
||
:style="imageTransformStyle"
|
||
alt="处方图"
|
||
draggable="false"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="toolbar">
|
||
<div class="toolbar-row">
|
||
<Input.Search
|
||
v-model:value="salespersonKeyword"
|
||
allow-clear
|
||
placeholder="推广员姓名/手机"
|
||
size="small"
|
||
class="toolbar-input"
|
||
@search="handleSearch"
|
||
/>
|
||
<Input.Search
|
||
v-model:value="patientName"
|
||
allow-clear
|
||
placeholder="收货人姓名"
|
||
size="small"
|
||
class="toolbar-input"
|
||
@search="handleSearch"
|
||
/>
|
||
</div>
|
||
<div class="toolbar-row toolbar-row-bottom">
|
||
<span class="toolbar-label">刷新</span>
|
||
<Select
|
||
v-model:value="refreshIntervalSec"
|
||
size="small"
|
||
class="toolbar-select"
|
||
:options="refreshOptions"
|
||
:get-popup-container="getPopupContainer"
|
||
:dropdown-style="{ zIndex: popupZIndex }"
|
||
/>
|
||
<Button size="small" :loading="loading" @click="handleManualRefresh">
|
||
<ReloadOutlined />
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="panel-body">
|
||
<div class="panel-scroll">
|
||
<Spin :spinning="loading">
|
||
<div v-if="!loading && !list.length" class="empty-tip">暂无传方记录</div>
|
||
<div v-for="item in list" :key="item.id" class="transfer-card">
|
||
<div class="card-head">
|
||
<span class="store-name">{{ item.store_name || '—' }}</span>
|
||
<Tag :color="statusColor(item.status)">
|
||
{{ item.status_text || '未处理' }}
|
||
</Tag>
|
||
</div>
|
||
<div class="salesperson-row">
|
||
<Image
|
||
:src="item.salesperson_avatar || DEFAULT_AVATAR"
|
||
:width="32"
|
||
:height="32"
|
||
class="salesperson-avatar"
|
||
:preview="false"
|
||
/>
|
||
<div class="salesperson-info">
|
||
<div class="salesperson-name">{{ item.salesperson_name || '—' }}</div>
|
||
<div v-if="item.salesperson_phone" class="salesperson-phone">
|
||
{{ item.salesperson_phone }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="card-row">
|
||
<span class="label">收货人</span>
|
||
<span class="value">
|
||
{{ item.patient_name }}
|
||
<SensitiveText :record="item" field="patient_mobile" :show-eye="false" />
|
||
</span>
|
||
</div>
|
||
<div v-if="item.patient_address" class="card-row">
|
||
<span class="label">地址</span>
|
||
<span class="value ellipsis">{{ item.patient_address }}</span>
|
||
</div>
|
||
<div v-if="item.remark" class="card-row">
|
||
<span class="label">备注</span>
|
||
<span class="value">{{ item.remark }}</span>
|
||
</div>
|
||
<div
|
||
v-if="item.drug_text_lines?.length"
|
||
class="drug-block drug-block--importable"
|
||
@click="canImportItem(item) && handleImportToCurrentPatient(item)"
|
||
>
|
||
<span class="label">药方</span>
|
||
<div class="drug-lines">
|
||
<div
|
||
v-for="(line, idx) in item.drug_text_lines"
|
||
:key="'d-' + idx"
|
||
class="drug-line"
|
||
>
|
||
{{ line }}
|
||
</div>
|
||
</div>
|
||
<span v-if="canImportItem(item)" class="import-hint">点击导入到当前患者</span>
|
||
</div>
|
||
<div v-if="item.prescription_images?.length" class="img-block">
|
||
<span class="label">处方图</span>
|
||
<div class="img-row">
|
||
<Image
|
||
v-for="(img, idx) in item.prescription_images"
|
||
:key="'img-' + idx"
|
||
:src="img"
|
||
:width="64"
|
||
:height="64"
|
||
class="thumb"
|
||
:preview="{ zIndex: popupZIndex }"
|
||
/>
|
||
</div>
|
||
<Button
|
||
type="link"
|
||
size="small"
|
||
class="link-btn"
|
||
@click="openImageInWindow(item.prescription_images[0])"
|
||
>
|
||
在窗口查看
|
||
</Button>
|
||
</div>
|
||
<div
|
||
v-if="item.status === 3 && item.exception_message"
|
||
class="exception-box"
|
||
>
|
||
异常:{{ item.exception_message }}
|
||
</div>
|
||
<div class="card-foot">
|
||
<span class="time">{{ item.transfer_time_text }}</span>
|
||
<div class="card-foot-actions">
|
||
<Button
|
||
v-if="canImportItem(item)"
|
||
size="small"
|
||
type="primary"
|
||
@click="handleImportToCurrentPatient(item)"
|
||
>
|
||
导入到当前患者
|
||
</Button>
|
||
<Dropdown
|
||
:trigger="['click']"
|
||
:get-popup-container="getPopupContainer"
|
||
:overlay-style="{ zIndex: popupZIndex }"
|
||
>
|
||
<Button size="small" type="primary" ghost>操作</Button>
|
||
<template #overlay>
|
||
<Menu @click="({ key }) => handleStatusSelect(item, Number(key))">
|
||
<Menu.Item v-for="opt in statusOptions" :key="String(opt.value)">
|
||
{{ opt.label }}
|
||
</Menu.Item>
|
||
</Menu>
|
||
</template>
|
||
</Dropdown>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</Spin>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</SubWindow>
|
||
<Modal
|
||
v-model:open="exceptionModalOpen"
|
||
title="填写异常说明"
|
||
ok-text="确认"
|
||
cancel-text="取消"
|
||
:z-index="popupZIndex"
|
||
@ok="confirmException"
|
||
>
|
||
<Textarea
|
||
v-model:value="exceptionMessage"
|
||
:rows="4"
|
||
placeholder="请描述异常原因"
|
||
/>
|
||
</Modal>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.panel {
|
||
flex: 1;
|
||
position: relative;
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-height: 0;
|
||
}
|
||
.toolbar {
|
||
flex-shrink: 0;
|
||
padding: 12px;
|
||
border-bottom: 1px solid #eee;
|
||
}
|
||
.panel.is-dark .toolbar {
|
||
border-bottom-color: #424242;
|
||
}
|
||
.toolbar-row {
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
.toolbar-row-bottom {
|
||
margin-top: 8px;
|
||
align-items: center;
|
||
}
|
||
.toolbar-input {
|
||
flex: 1;
|
||
}
|
||
.toolbar-select {
|
||
flex: 1;
|
||
}
|
||
.toolbar-label {
|
||
flex-shrink: 0;
|
||
font-size: 12px;
|
||
color: #86909c;
|
||
}
|
||
.panel.is-dark .toolbar-label {
|
||
color: rgba(255, 255, 255, 0.45);
|
||
}
|
||
/* 内部滚动:panel-body 限定高度,panel-scroll 为唯一滚动容器 */
|
||
.panel-body {
|
||
flex: 1 1 0;
|
||
min-height: 0;
|
||
overflow: hidden;
|
||
}
|
||
.panel-scroll {
|
||
height: 100%;
|
||
overflow-y: auto;
|
||
overscroll-behavior: contain;
|
||
padding: 12px;
|
||
}
|
||
.empty-tip {
|
||
text-align: center;
|
||
color: #999;
|
||
padding: 40px 0;
|
||
}
|
||
.panel.is-dark .empty-tip {
|
||
color: rgba(255, 255, 255, 0.45);
|
||
}
|
||
.transfer-card {
|
||
background: #f9fafb;
|
||
border-radius: 8px;
|
||
padding: 12px;
|
||
margin-bottom: 12px;
|
||
border: 1px solid #eee;
|
||
}
|
||
.panel.is-dark .transfer-card {
|
||
background: #262626;
|
||
border-color: #424242;
|
||
}
|
||
.card-head {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 8px;
|
||
}
|
||
.store-name {
|
||
font-weight: 600;
|
||
font-size: 14px;
|
||
color: #1d2129;
|
||
}
|
||
.panel.is-dark .store-name {
|
||
color: rgba(255, 255, 255, 0.85);
|
||
}
|
||
.salesperson-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
margin-bottom: 8px;
|
||
}
|
||
.salesperson-avatar {
|
||
flex-shrink: 0;
|
||
border-radius: 50%;
|
||
object-fit: cover;
|
||
}
|
||
.salesperson-info {
|
||
min-width: 0;
|
||
flex: 1;
|
||
}
|
||
.salesperson-name {
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
color: #1d2129;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.panel.is-dark .salesperson-name {
|
||
color: rgba(255, 255, 255, 0.85);
|
||
}
|
||
.salesperson-phone {
|
||
font-size: 12px;
|
||
color: #86909c;
|
||
}
|
||
.panel.is-dark .salesperson-phone {
|
||
color: rgba(255, 255, 255, 0.45);
|
||
}
|
||
.card-row {
|
||
display: flex;
|
||
font-size: 13px;
|
||
margin-bottom: 6px;
|
||
line-height: 1.5;
|
||
}
|
||
.label {
|
||
color: #86909c;
|
||
width: 56px;
|
||
flex-shrink: 0;
|
||
}
|
||
.panel.is-dark .label {
|
||
color: rgba(255, 255, 255, 0.45);
|
||
}
|
||
.value {
|
||
color: #4e5969;
|
||
flex: 1;
|
||
}
|
||
.panel.is-dark .value {
|
||
color: rgba(255, 255, 255, 0.65);
|
||
}
|
||
.ellipsis {
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.drug-block {
|
||
margin: 6px 0;
|
||
display: flex;
|
||
font-size: 13px;
|
||
}
|
||
.drug-block--importable {
|
||
flex-wrap: wrap;
|
||
cursor: pointer;
|
||
padding: 6px 8px;
|
||
border-radius: 4px;
|
||
transition: background 0.15s;
|
||
}
|
||
.drug-block--importable:hover {
|
||
background: rgba(0, 135, 113, 0.08);
|
||
}
|
||
.panel.is-dark .drug-block--importable:hover {
|
||
background: rgba(52, 211, 153, 0.12);
|
||
}
|
||
.import-hint {
|
||
flex-basis: 100%;
|
||
font-size: 12px;
|
||
color: #008771;
|
||
margin-top: 4px;
|
||
margin-left: 56px;
|
||
}
|
||
.panel.is-dark .import-hint {
|
||
color: #34d399;
|
||
}
|
||
.drug-lines {
|
||
flex: 1;
|
||
}
|
||
.drug-line {
|
||
color: #008771;
|
||
margin-bottom: 2px;
|
||
}
|
||
.panel.is-dark .drug-line {
|
||
color: #34d399;
|
||
}
|
||
.img-block {
|
||
margin-top: 6px;
|
||
}
|
||
.img-row {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
margin: 6px 0 6px 56px;
|
||
}
|
||
.thumb {
|
||
object-fit: cover;
|
||
border-radius: 4px;
|
||
}
|
||
.link-btn {
|
||
padding-left: 56px;
|
||
}
|
||
.exception-box {
|
||
background: #fef2f2;
|
||
color: #991b1b;
|
||
font-size: 12px;
|
||
padding: 8px;
|
||
border-radius: 4px;
|
||
margin-top: 6px;
|
||
}
|
||
.panel.is-dark .exception-box {
|
||
background: rgba(127, 29, 29, 0.35);
|
||
color: #fca5a5;
|
||
}
|
||
.card-foot {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-top: 8px;
|
||
padding-top: 8px;
|
||
border-top: 1px dashed #e5e7eb;
|
||
}
|
||
.card-foot-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
.panel.is-dark .card-foot {
|
||
border-top-color: #424242;
|
||
}
|
||
.time {
|
||
font-size: 12px;
|
||
color: #999;
|
||
}
|
||
.panel.is-dark .time {
|
||
color: rgba(255, 255, 255, 0.45);
|
||
}
|
||
.inline-image-layer {
|
||
position: absolute;
|
||
inset: 0;
|
||
background: #fff;
|
||
z-index: 10;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.panel.is-dark .inline-image-layer {
|
||
background: #1f1f1f;
|
||
}
|
||
.inline-image-toolbar {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 8px 12px;
|
||
border-bottom: 1px solid #eee;
|
||
flex-shrink: 0;
|
||
}
|
||
.panel.is-dark .inline-image-toolbar {
|
||
border-bottom-color: #424242;
|
||
}
|
||
.zoom-percent {
|
||
font-size: 12px;
|
||
color: #86909c;
|
||
}
|
||
.panel.is-dark .zoom-percent {
|
||
color: rgba(255, 255, 255, 0.45);
|
||
}
|
||
.inline-image-body {
|
||
flex: 1;
|
||
overflow: auto;
|
||
overscroll-behavior: contain;
|
||
padding: 12px;
|
||
cursor: grab;
|
||
}
|
||
.inline-image-body.dragging {
|
||
cursor: grabbing;
|
||
}
|
||
.inline-image {
|
||
width: 100%;
|
||
height: auto;
|
||
display: block;
|
||
margin: 0 auto;
|
||
user-select: none;
|
||
}
|
||
.doctor-transfer-header-entry {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-right: 4px;
|
||
}
|
||
.transfer-header-btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
height: 32px;
|
||
padding: 0 8px;
|
||
color: hsl(var(--foreground));
|
||
}
|
||
.transfer-header-btn__text {
|
||
font-size: 13px;
|
||
}
|
||
@media (max-width: 640px) {
|
||
.transfer-header-btn__text {
|
||
display: none;
|
||
}
|
||
}
|
||
</style>
|