1. 开方页面代煎选择交互
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CI / CI OK (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CI / CI OK (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled
This commit is contained in:
28
apps/web-antd/src/utils/prescriptionDoctorOrder.ts
Normal file
28
apps/web-antd/src/utils/prescriptionDoctorOrder.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
/** 展示用:优先顶层合并医嘱,fallback content 纯医嘱 */
|
||||
export function formatDoctorOrderText(
|
||||
item?: {
|
||||
doctor_order?: string | string[];
|
||||
content?: { doctor_order?: string };
|
||||
} | null,
|
||||
): string {
|
||||
const raw = item?.doctor_order ?? item?.content?.doctor_order ?? '';
|
||||
if (Array.isArray(raw)) {
|
||||
const list = raw.map((s) => String(s).trim()).filter(Boolean);
|
||||
return list.length ? list.join(';') : '-';
|
||||
}
|
||||
const parts = String(raw)
|
||||
.split('|')
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
return parts.length ? parts.join(';') : raw ? String(raw) : '-';
|
||||
}
|
||||
|
||||
/** 复用开方输入框:detail.doctor_order 为数组时转回字符串 */
|
||||
export function detailDoctorOrderToText(
|
||||
doctorOrder?: string | string[] | null,
|
||||
): string {
|
||||
if (Array.isArray(doctorOrder)) {
|
||||
return doctorOrder.map((s) => String(s).trim()).filter(Boolean).join('|');
|
||||
}
|
||||
return doctorOrder ?? '';
|
||||
}
|
||||
90
apps/web-antd/src/utils/processRuleCascade.ts
Normal file
90
apps/web-antd/src/utils/processRuleCascade.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
export type ProcessRuleListItem = { id?: number | string };
|
||||
|
||||
export type ProcessRuleLoadedLevel = 'root' | 'child' | 'note';
|
||||
|
||||
/**
|
||||
* 列表仅一项且当前未选时,返回该项 id
|
||||
* 用于委托调剂制剂/煎法/备注的默认选中
|
||||
*/
|
||||
export function getSingleProcessRuleId(
|
||||
list: ProcessRuleListItem[] | null | undefined,
|
||||
currentId?: number | string | null,
|
||||
): number | null {
|
||||
if (!Array.isArray(list) || list.length !== 1) {
|
||||
return null;
|
||||
}
|
||||
const cur = currentId == null || currentId === '' ? null : Number(currentId);
|
||||
if (cur != null && Number.isFinite(cur) && cur > 0) {
|
||||
return null;
|
||||
}
|
||||
const id = Number(list[0]?.id);
|
||||
return Number.isFinite(id) && id > 0 ? id : null;
|
||||
}
|
||||
|
||||
/** 根据 loadProcessRuleData 参数判断刚加载的是哪一级 */
|
||||
export function resolveProcessRuleLoadedLevel(
|
||||
pid: number,
|
||||
ruleId: number,
|
||||
): ProcessRuleLoadedLevel {
|
||||
if (ruleId !== 0) {
|
||||
return 'note';
|
||||
}
|
||||
if (pid === 0) {
|
||||
return 'root';
|
||||
}
|
||||
return 'child';
|
||||
}
|
||||
|
||||
export type ProcessRuleAutoSelectContext = {
|
||||
ruleType: number;
|
||||
disabled?: boolean;
|
||||
processRuleId?: number | string | null;
|
||||
childProcessRuleId?: number | string | null;
|
||||
processRuleNoteId?: number | string | null;
|
||||
processRuleList: ProcessRuleListItem[];
|
||||
childProcessRuleList: ProcessRuleListItem[];
|
||||
processRuleNoteList: ProcessRuleListItem[];
|
||||
load: (pid: number, ruleId: number) => Promise<void>;
|
||||
setProcessRuleId: (id: number) => void;
|
||||
setChildProcessRuleId: (id: number) => void;
|
||||
setProcessRuleNoteId: (id: number) => void;
|
||||
clearBelowProcessRule: () => void;
|
||||
clearBelowChild: () => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* 子级仅一项时自动选中,并级联 load 下一级
|
||||
* 不覆盖已有选中值(草稿恢复、用户手选、历史导入)
|
||||
*/
|
||||
export async function applySingleChildProcessRuleAutoSelect(
|
||||
loadedLevel: ProcessRuleLoadedLevel,
|
||||
ctx: ProcessRuleAutoSelectContext,
|
||||
): Promise<void> {
|
||||
if (ctx.disabled || ctx.ruleType !== 2) {
|
||||
return;
|
||||
}
|
||||
if (loadedLevel === 'root') {
|
||||
const id = getSingleProcessRuleId(ctx.processRuleList, ctx.processRuleId);
|
||||
if (id == null) {
|
||||
return;
|
||||
}
|
||||
ctx.clearBelowProcessRule();
|
||||
ctx.setProcessRuleId(id);
|
||||
await ctx.load(id, 0);
|
||||
return;
|
||||
}
|
||||
if (loadedLevel === 'child') {
|
||||
const id = getSingleProcessRuleId(ctx.childProcessRuleList, ctx.childProcessRuleId);
|
||||
if (id == null) {
|
||||
return;
|
||||
}
|
||||
ctx.clearBelowChild();
|
||||
ctx.setChildProcessRuleId(id);
|
||||
await ctx.load(0, id);
|
||||
return;
|
||||
}
|
||||
const noteId = getSingleProcessRuleId(ctx.processRuleNoteList, ctx.processRuleNoteId);
|
||||
if (noteId != null) {
|
||||
ctx.setProcessRuleNoteId(noteId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
<script setup lang="ts">
|
||||
import { MinusOutlined, PlusOutlined } from '@ant-design/icons-vue';
|
||||
import { InputNumber } from 'ant-design-vue';
|
||||
|
||||
/** 中药配置变更载荷 */
|
||||
export type ChineseMedicineConfigChange = {
|
||||
ruleType?: number;
|
||||
packageMethodId?: number | null;
|
||||
processRuleId?: number | null;
|
||||
childProcessRuleId?: number | null;
|
||||
processRuleNoteId?: number | null;
|
||||
dosage?: number;
|
||||
dayDosage?: number;
|
||||
};
|
||||
|
||||
const props = defineProps<{
|
||||
ruleType: number;
|
||||
packageMethodId?: number | null;
|
||||
processRuleId?: number | null;
|
||||
childProcessRuleId?: number | null;
|
||||
processRuleNoteId?: number | null;
|
||||
dosage: number;
|
||||
dayDosage: number;
|
||||
processRuleList?: Array<{ id: number; name: string }>;
|
||||
childProcessRuleList?: Array<{ id: number; name: string }>;
|
||||
processRuleNoteList?: Array<{ id: number; note?: string; name?: string }>;
|
||||
disabled?: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
change: [payload: ChineseMedicineConfigChange];
|
||||
}>();
|
||||
|
||||
/** 包法选项(与移动端一致) */
|
||||
const packageMethodList = [
|
||||
{ id: 1, name: '味包' },
|
||||
{ id: 2, name: '剂包' },
|
||||
];
|
||||
|
||||
/**
|
||||
* 统一向父组件抛出配置变更
|
||||
* 父组件负责级联加载制剂/煎法/备注列表
|
||||
*/
|
||||
function emitChange(payload: ChineseMedicineConfigChange) {
|
||||
emit('change', payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换制剂方式:自制剂 / 委托调剂
|
||||
*/
|
||||
function handleRuleTypeChange(value: number) {
|
||||
if (props.disabled || props.ruleType === value) return;
|
||||
if (value === 1) {
|
||||
emitChange({
|
||||
ruleType: 1,
|
||||
processRuleId: null,
|
||||
childProcessRuleId: null,
|
||||
processRuleNoteId: null,
|
||||
packageMethodId: props.packageMethodId ?? packageMethodList[1].id,
|
||||
});
|
||||
} else {
|
||||
emitChange({
|
||||
ruleType: 2,
|
||||
packageMethodId: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** 选择包法标签 */
|
||||
function handlePackageMethodClick(id: number) {
|
||||
if (props.disabled) return;
|
||||
emitChange({ packageMethodId: id });
|
||||
}
|
||||
|
||||
/** 选择制剂标签,级联清空煎法/备注 */
|
||||
function handleProcessRuleClick(item: { id: number }) {
|
||||
if (props.disabled) return;
|
||||
emitChange({
|
||||
processRuleId: item.id,
|
||||
childProcessRuleId: null,
|
||||
processRuleNoteId: null,
|
||||
});
|
||||
}
|
||||
|
||||
/** 选择煎法标签,级联清空备注 */
|
||||
function handleChildProcessRuleClick(item: { id: number }) {
|
||||
if (props.disabled) return;
|
||||
emitChange({
|
||||
childProcessRuleId: item.id,
|
||||
processRuleNoteId: null,
|
||||
});
|
||||
}
|
||||
|
||||
/** 选择备注标签 */
|
||||
function handleProcessRuleNoteClick(item: { id: number }) {
|
||||
if (props.disabled) return;
|
||||
emitChange({ processRuleNoteId: item.id });
|
||||
}
|
||||
|
||||
function updateDosage(val: number) {
|
||||
emitChange({ dosage: val });
|
||||
}
|
||||
|
||||
function updateDayDosage(val: number) {
|
||||
emitChange({ dayDosage: val });
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="chinese-medicine-config">
|
||||
<!-- 制剂方式 -->
|
||||
<div class="config-section mb-4">
|
||||
<div class="section-title mb-2 text-sm font-bold text-gray-800 dark:text-gray-200">制剂方式</div>
|
||||
<div class="pill-group">
|
||||
<span
|
||||
class="pill"
|
||||
:class="{ active: ruleType === 1, disabled }"
|
||||
@click="handleRuleTypeChange(1)"
|
||||
>自制剂</span>
|
||||
<span
|
||||
class="pill"
|
||||
:class="{ active: ruleType === 2, disabled }"
|
||||
@click="handleRuleTypeChange(2)"
|
||||
>委托调剂</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 自制剂:包法 -->
|
||||
<div v-if="ruleType === 1" class="config-section mb-4 border-t border-dashed border-gray-200 dark:border-gray-700 pt-4">
|
||||
<div class="section-title mb-2 text-sm font-bold text-gray-800 dark:text-gray-200">包法选择</div>
|
||||
<div class="pill-group">
|
||||
<span
|
||||
v-for="item in packageMethodList"
|
||||
:key="item.id"
|
||||
class="pill outline"
|
||||
:class="{ active: packageMethodId === item.id, disabled }"
|
||||
@click="handlePackageMethodClick(item.id)"
|
||||
>{{ item.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 委托调剂:制剂 / 煎法 / 备注 -->
|
||||
<template v-if="ruleType === 2">
|
||||
<div class="config-section mb-4 border-t border-dashed border-gray-200 dark:border-gray-700 pt-4">
|
||||
<div class="section-title mb-2 text-sm font-bold text-gray-800 dark:text-gray-200">制剂要求</div>
|
||||
<div class="pill-group">
|
||||
<span
|
||||
v-for="item in processRuleList"
|
||||
:key="item.id"
|
||||
class="pill outline"
|
||||
:class="{ active: processRuleId === item.id, disabled }"
|
||||
@click="handleProcessRuleClick(item)"
|
||||
>{{ item.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="childProcessRuleList?.length" class="config-section mb-4">
|
||||
<div class="section-title mb-2 text-sm font-bold text-gray-800 dark:text-gray-200">煎法说明</div>
|
||||
<div class="pill-group">
|
||||
<span
|
||||
v-for="item in childProcessRuleList"
|
||||
:key="item.id"
|
||||
class="pill outline"
|
||||
:class="{ active: childProcessRuleId === item.id, disabled }"
|
||||
@click="handleChildProcessRuleClick(item)"
|
||||
>{{ item.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="processRuleNoteList?.length" class="config-section mb-4">
|
||||
<div class="section-title mb-2 text-sm font-bold text-gray-800 dark:text-gray-200">附加备注</div>
|
||||
<div class="pill-group">
|
||||
<span
|
||||
v-for="item in processRuleNoteList"
|
||||
:key="item.id"
|
||||
class="pill outline"
|
||||
:class="{ active: processRuleNoteId === item.id, disabled }"
|
||||
@click="handleProcessRuleNoteClick(item)"
|
||||
>{{ item.note || item.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 剂数与频次 -->
|
||||
<div class="config-section border-t border-dashed border-gray-200 dark:border-gray-700 pt-4">
|
||||
<div class="mt-2 flex flex-wrap items-center gap-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm font-bold text-gray-800 dark:text-gray-200">剂数</span>
|
||||
<InputNumber
|
||||
:value="dosage"
|
||||
:disabled="disabled"
|
||||
:min="1"
|
||||
:max="100"
|
||||
class="w-24"
|
||||
@update:value="(v) => updateDosage(Number(v) || 1)"
|
||||
>
|
||||
<template #addonBefore>
|
||||
<MinusOutlined @click="!disabled && updateDosage(dosage > 1 ? dosage - 1 : 1)" />
|
||||
</template>
|
||||
<template #addonAfter>
|
||||
<PlusOutlined @click="!disabled && updateDosage(dosage < 100 ? dosage + 1 : 100)" />
|
||||
</template>
|
||||
</InputNumber>
|
||||
<span class="text-sm text-gray-500 dark:text-gray-400">剂(天)</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm font-bold text-gray-800 dark:text-gray-200">用药频次</span>
|
||||
<InputNumber
|
||||
:value="dayDosage"
|
||||
:disabled="disabled"
|
||||
:min="1"
|
||||
:max="100"
|
||||
class="w-24"
|
||||
@update:value="(v) => updateDayDosage(Number(v) || 1)"
|
||||
>
|
||||
<template #addonBefore>
|
||||
<MinusOutlined @click="!disabled && updateDayDosage(dayDosage > 1 ? dayDosage - 1 : 1)" />
|
||||
</template>
|
||||
<template #addonAfter>
|
||||
<PlusOutlined @click="!disabled && updateDayDosage(dayDosage < 100 ? dayDosage + 1 : 100)" />
|
||||
</template>
|
||||
</InputNumber>
|
||||
<span class="text-sm text-gray-500 dark:text-gray-400">次/天</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.pill-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
.pill {
|
||||
padding: 4px 16px;
|
||||
background-color: #f4f6f8;
|
||||
color: #6c7380;
|
||||
border-radius: 20px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
border: 1px solid transparent;
|
||||
transition: all 0.2s;
|
||||
user-select: none;
|
||||
}
|
||||
.pill.active {
|
||||
background-color: #00a88a;
|
||||
color: #fff;
|
||||
border-color: #00a88a;
|
||||
font-weight: 600;
|
||||
}
|
||||
.pill.outline {
|
||||
background-color: #fff;
|
||||
border-color: #e2e8f0;
|
||||
}
|
||||
.pill.outline.active {
|
||||
background-color: #e8f6f4;
|
||||
color: #00a88a;
|
||||
border-color: #00a88a;
|
||||
}
|
||||
.pill.disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
/* 暗色模式:标签背景与选中态 */
|
||||
.dark .pill {
|
||||
background-color: #2a2a2a;
|
||||
color: #a3a3a3;
|
||||
}
|
||||
.dark .pill.active {
|
||||
background-color: #00a88a;
|
||||
color: #fff;
|
||||
border-color: #00a88a;
|
||||
}
|
||||
.dark .pill.outline {
|
||||
background-color: #1f1f1f;
|
||||
border-color: #404040;
|
||||
color: #a3a3a3;
|
||||
}
|
||||
.dark .pill.outline.active {
|
||||
background-color: rgba(0, 168, 138, 0.15);
|
||||
color: #3dd6b5;
|
||||
border-color: #00a88a;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user