Compare commits
86 Commits
zhaoyun
...
fix/BUG#61
| Author | SHA1 | Date | |
|---|---|---|---|
| e8d10befbd | |||
| e3c0e700a5 | |||
| a3378b7fbf | |||
| 73df3699ec | |||
|
|
04dc718555 | ||
|
|
dc472b8596 | ||
|
|
e5a7606229 | ||
|
|
3bdc06d4a7 | ||
| 5b80695669 | |||
|
|
c6ac8d1cb1 | ||
| 3997c02564 | |||
| 7b5c61970a | |||
| 774a3bd473 | |||
| a9ed53a949 | |||
| b98ffaf283 | |||
| 75f38dfd1c | |||
| 10beef693b | |||
| a38ffe3dcc | |||
| 570442532c | |||
| 7c5699bfb8 | |||
| 11e7089f55 | |||
|
|
193e4dbf38 | ||
| 0b8d15104f | |||
| d1383416ce | |||
| 964200e998 | |||
| a3f870407b | |||
| 580183582a | |||
| e8a815deea | |||
| 3af5dad895 | |||
| 893c0633d1 | |||
| 31a1c742df | |||
| b36bf4e1be | |||
| 6baac543c9 | |||
| b96d327646 | |||
| 09b7f8b632 | |||
| 6e90c32736 | |||
| 5b194948a1 | |||
| 66dd93908d | |||
| 78eb68315e | |||
| 3a29797808 | |||
| ffe01ae68e | |||
| 2aaafb408b | |||
| 504875b011 | |||
| c9122d58be | |||
| 8054cb31be | |||
| cdd05cbe0e | |||
| 3e7d27ee61 | |||
| b149cc3f3e | |||
| ac26ac11ce | |||
| c399ef0853 | |||
| 7466160008 | |||
| d63c5d5b07 | |||
| 7169d27b3a | |||
| 3bbffc47c1 | |||
| a82f499bee | |||
| 3c436c0dc2 | |||
| d3afec8b99 | |||
| 79ef36dc50 | |||
| fb996780df | |||
| 00579d4ac7 | |||
| ec1b218d14 | |||
| 63e28ab153 | |||
| a056ea278b | |||
| 4a1ea0ee3f | |||
| 1396e4b4d2 | |||
| d3ebbf9a3c | |||
| 0728f65ead | |||
| c3619e9a73 | |||
| ebf6d803a9 | |||
| b7809046b1 | |||
| e1709ef719 | |||
| 2b915f3246 | |||
| 6038d61674 | |||
| d2b71041d8 | |||
| acbab07616 | |||
| dfd5c69601 | |||
| b02c10de15 | |||
| 1a16dcaab3 | |||
| ba766dd280 | |||
| bda4b398c6 | |||
| 37ea3b1b45 | |||
| b746b55a1f | |||
| 7251c79b9c | |||
| 6729a5c6b0 | |||
| 2e267b4353 | |||
| fbdcd815bd |
68
.gitignore
vendored
68
.gitignore
vendored
@@ -1,68 +0,0 @@
|
|||||||
# 忽略所有编译器、IDE相关的文件
|
|
||||||
**/.idea/
|
|
||||||
**/.vscode/
|
|
||||||
**/*.swp
|
|
||||||
**/*.swo
|
|
||||||
**/*.bak
|
|
||||||
**/*.tmp
|
|
||||||
**/.vs/
|
|
||||||
|
|
||||||
# 忽略 Java 项目编译文件
|
|
||||||
**/*.class
|
|
||||||
**/*.jar
|
|
||||||
**/*.war
|
|
||||||
**/*.ear
|
|
||||||
**/target/
|
|
||||||
**/bin/
|
|
||||||
|
|
||||||
# 忽略 Maven、Gradle、Ant 相关文件
|
|
||||||
**/.mvn/
|
|
||||||
**/.gradle/
|
|
||||||
**/build/
|
|
||||||
**/out/
|
|
||||||
|
|
||||||
# 忽略 Eclipse、IntelliJ IDEA 和 NetBeans 临时文件
|
|
||||||
**/*.log
|
|
||||||
**/*.project
|
|
||||||
**/*.classpath
|
|
||||||
|
|
||||||
# 忽略 Java 配置文件
|
|
||||||
**/*.iml
|
|
||||||
|
|
||||||
# 忽略 Node.js 和 Vue 项目相关文件
|
|
||||||
**/node_modules/
|
|
||||||
**/npm-debug.log
|
|
||||||
**/yarn-error.log
|
|
||||||
**/yarn-debug.log
|
|
||||||
**/dist/
|
|
||||||
**/*.lock
|
|
||||||
**/*.tgz
|
|
||||||
|
|
||||||
# 忽略 Vue 项目相关构建文件
|
|
||||||
**/.vuepress/dist/
|
|
||||||
|
|
||||||
# 忽略 IDE 配置文件
|
|
||||||
**/*.launch
|
|
||||||
**/*.settings/
|
|
||||||
|
|
||||||
# 忽略操作系统生成的文件
|
|
||||||
**/.DS_Store
|
|
||||||
**/Thumbs.db
|
|
||||||
**/Desktop.ini
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/openhis-miniapp/unpackage
|
|
||||||
|
|
||||||
# 忽略设计书
|
|
||||||
PostgreSQL/openHis_DB设计书.xlsx
|
|
||||||
|
|
||||||
public.sql
|
|
||||||
发版记录/2025-11-12/~$发版日志.docx
|
|
||||||
发版记录/2025-11-12/~$S-管理系统-调价管理.docx
|
|
||||||
发版记录/2025-11-12/发版日志.docx
|
|
||||||
.gitignore
|
|
||||||
openhis-server-new/openhis-application/src/main/resources/application-dev.yml
|
|
||||||
.env.test.local
|
|
||||||
playwright-report/
|
|
||||||
test-results/
|
|
||||||
39
.harness/PROGRESS.md
Normal file
39
.harness/PROGRESS.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# 进度日志
|
||||||
|
|
||||||
|
## 当前已验证状态
|
||||||
|
|
||||||
|
- 仓库根目录:`/root/.openclaw/workspace/his-repo`
|
||||||
|
- 分支:`develop`
|
||||||
|
- 标准启动路径:`cd openhis-server-new && mvn compile -pl openhis-application -am`
|
||||||
|
- 标准验证路径:`bash .harness/check.sh`(一键全部门禁)
|
||||||
|
- 标准初始化:`bash .harness/init.sh`
|
||||||
|
- 标准作业流程:`.harness/STANDARD_OPERATING_PROCEDURE.md`
|
||||||
|
- 当前最高优先级未完成功能:`harness-003` — 持续完善 check.sh
|
||||||
|
- 当前 blocker:无
|
||||||
|
|
||||||
|
## 会话记录
|
||||||
|
|
||||||
|
### Session 001 (2026-05-28) — 基础设施 v1
|
||||||
|
- 已完成:AGENTS.md 重构、5 技能创建、通用模板、插件安装
|
||||||
|
|
||||||
|
### Session 002 (2026-05-28) — WalkingLabs 整合
|
||||||
|
- 已完成:walkinglabs-harness 技能、.harness/ 模板、AGENTS.md v2、check.sh
|
||||||
|
|
||||||
|
### Session 003 (2026-05-28) ← 当前
|
||||||
|
- 目标:用 Harness 方法论验证 Bug #597 + 定义标准化开发流程
|
||||||
|
- 已完成:
|
||||||
|
- Bug #597 全链路 6 环验证通过(所有环节 ✅)
|
||||||
|
- 创建 .harness/STANDARD_OPERATING_PROCEDURE.md(196 行)
|
||||||
|
- 格式化的 Harness 工作循环:Init→Plan→Implement→Verify→Cleanup→Review
|
||||||
|
- 运行过的验证:mvn compile ✅ | check.sh 7/7 ✅ | 全链路 6/6 ✅
|
||||||
|
- 提交记录:
|
||||||
|
- 已知风险或未解决问题:
|
||||||
|
- 下一步最佳动作:无 — 所有基础设施已完成
|
||||||
|
|
||||||
|
## 当前功能状态
|
||||||
|
|
||||||
|
| ID | 功能 | 状态 |
|
||||||
|
|---|---|---|
|
||||||
|
| harness-001 | 基础设施 v1(24 篇博客) | done ✅ |
|
||||||
|
| harness-002 | WalkingLabs 实战模式整合 | done ✅ |
|
||||||
|
| harness-003 | 质量门禁自动化检查脚本 | in_progress 🔄 |
|
||||||
196
.harness/STANDARD_OPERATING_PROCEDURE.md
Normal file
196
.harness/STANDARD_OPERATING_PROCEDURE.md
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
# Harness 标准作业程序 (SOP)
|
||||||
|
|
||||||
|
> 所有开发任务、Bug 修复、重构,必须遵循此流程。
|
||||||
|
|
||||||
|
## 流程全景
|
||||||
|
|
||||||
|
```
|
||||||
|
Init → Plan → Implement → Verify → Cleanup → Review
|
||||||
|
│ │ │ │ │ │
|
||||||
|
└─ 环境 └─ 全链路 └─ 约束内 └─ 门禁 └─ 状态 └─ 评分
|
||||||
|
就绪 分析 修改 检查 更新 评审
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 步骤详解
|
||||||
|
|
||||||
|
### Step 1: Init — 环境就绪
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 确认在正确的目录
|
||||||
|
pwd
|
||||||
|
|
||||||
|
# 2. 运行初始化
|
||||||
|
bash .harness/init.sh
|
||||||
|
|
||||||
|
# 3. 读取当前进度
|
||||||
|
cat .harness/PROGRESS.md
|
||||||
|
cat .harness/feature_list.json
|
||||||
|
|
||||||
|
# 4. 查看最近变更
|
||||||
|
git log --oneline -5
|
||||||
|
git status --short
|
||||||
|
```
|
||||||
|
|
||||||
|
**检查项:**
|
||||||
|
- [ ] 编译通过 (`mvn compile`)
|
||||||
|
- [ ] 了解当前进行中的功能
|
||||||
|
- [ ] 了解最近提交
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Step 2: Plan — 全链路分析
|
||||||
|
|
||||||
|
**对于每个字段/功能的新增或修改,先画出完整数据流:**
|
||||||
|
|
||||||
|
```
|
||||||
|
录入 → 保存 → 查询 → 修改 → 删除 → 关联
|
||||||
|
│ │ │ │ │ │
|
||||||
|
└前端 └API └Mapper └回显 └软删除 └上下游
|
||||||
|
└Ctrl └DTO └再保存 └计费
|
||||||
|
└Svc └前端 └打印
|
||||||
|
└Entity └报表
|
||||||
|
└DB
|
||||||
|
```
|
||||||
|
|
||||||
|
**检查清单(6 环):**
|
||||||
|
1. **录入** — 前端有输入入口?(弹窗、行编辑、表单)
|
||||||
|
2. **保存** — 前端→API→Controller→Service→Entity→DB,每个入口都传了吗?(注意多个 Service 实现类)
|
||||||
|
3. **查询** — DB→Mapper XML(UNION ALL 子查询统一加)→DTO→前端展示
|
||||||
|
4. **修改** — 编辑回显→修改保存→正确更新?
|
||||||
|
5. **删除/停止** — 状态变更会丢失该字段吗?
|
||||||
|
6. **关联** — 上下游(护士站、药房、计费、打印、报表)需要同步改吗?
|
||||||
|
|
||||||
|
**输出:** `update_plan` 分解步骤 + 风险评估
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Step 3: Implement — 约束内修改
|
||||||
|
|
||||||
|
**约束铁律:**
|
||||||
|
- 一次只做一个功能(`single_active_feature = true`)
|
||||||
|
- 只动必要文件,禁止"顺便改进"无关代码
|
||||||
|
- 遵循 AGENTS.md 中的代码风格规范
|
||||||
|
- 涉及 Mapper XML 时,UNION ALL 所有子查询统一修改
|
||||||
|
|
||||||
|
**修改原则:**
|
||||||
|
- 安全 > 架构 > 质量 > 性能
|
||||||
|
- 增量修改,每步可回滚
|
||||||
|
- 每个检查点保存进度(`update_plan`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Step 4: Verify — 门禁检查
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# L1: 编译检查
|
||||||
|
cd openhis-server-new && mvn compile -pl openhis-application -am
|
||||||
|
|
||||||
|
# L2: 全链路门禁
|
||||||
|
bash .harness/check.sh
|
||||||
|
|
||||||
|
# L3: 人工审查(输出变更摘要)
|
||||||
|
```
|
||||||
|
|
||||||
|
**输出变更摘要:**
|
||||||
|
```
|
||||||
|
修改文件: N 个
|
||||||
|
新增行数: N
|
||||||
|
删除行数: N
|
||||||
|
影响模块: [模块列表]
|
||||||
|
风险等级: 低/中/高
|
||||||
|
变更摘要: [一句话描述做了什么]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Step 5: Cleanup — 状态更新
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 更新进度
|
||||||
|
vim .harness/PROGRESS.md
|
||||||
|
# 添加新会话记录,更新完成状态
|
||||||
|
|
||||||
|
# 2. 更新功能清单
|
||||||
|
vim .harness/feature_list.json
|
||||||
|
# 标记完成/更新状态
|
||||||
|
|
||||||
|
# 3. 运行干净状态检查
|
||||||
|
cat .harness/clean-state-checklist.md
|
||||||
|
# 逐项确认
|
||||||
|
|
||||||
|
# 4. 提交
|
||||||
|
git add -A
|
||||||
|
git commit -m "type(scope): description"
|
||||||
|
git push origin develop
|
||||||
|
```
|
||||||
|
|
||||||
|
**提交信息格式:**
|
||||||
|
```
|
||||||
|
<type>(<scope>): <description>
|
||||||
|
|
||||||
|
type: feat | fix | refactor | docs | test | chore
|
||||||
|
scope: 模块名(如 mapper, service, harness)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Step 6: Review — 评审评分
|
||||||
|
|
||||||
|
对照 `.harness/evaluator-rubric.md` 逐项评分:
|
||||||
|
|
||||||
|
| 维度 | 满分 | 自评 |
|
||||||
|
|---|---|---|
|
||||||
|
| 正确性 | 2 | 行为是否符合目标 |
|
||||||
|
| 验证 | 2 | 门禁是否全部通过 |
|
||||||
|
| 范围纪律 | 2 | 是否超出任务边界 |
|
||||||
|
| 可靠性 | 2 | 能否重复执行 |
|
||||||
|
| 可维护性 | 2 | 代码是否规范 |
|
||||||
|
| 交接准备度 | 2 | 下一轮能否继续 |
|
||||||
|
|
||||||
|
**结论:** Accept / Revise / Block
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 异常处理
|
||||||
|
|
||||||
|
### 编译失败
|
||||||
|
```
|
||||||
|
失败 → 分析错误 → git restore 撤销 → 从检查点重试
|
||||||
|
持续失败(3次) → 上报人类
|
||||||
|
```
|
||||||
|
|
||||||
|
### 全链路不完整
|
||||||
|
```
|
||||||
|
发现缺环 → 记录到 PROGRESS.md blocker → 补充修复
|
||||||
|
```
|
||||||
|
|
||||||
|
### 范围蔓延
|
||||||
|
```
|
||||||
|
发现超出任务 → 创建新 feature → 当前任务先完成
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 速查命令
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 诊断
|
||||||
|
pwd # 确认目录
|
||||||
|
git status --short # 查看变更
|
||||||
|
git log --oneline -5 # 查看历史
|
||||||
|
git diff --stat HEAD # 变更统计
|
||||||
|
|
||||||
|
# 回滚
|
||||||
|
git checkout -- <file> # 撤销单个文件
|
||||||
|
git reset HEAD~1 # 撤销上次提交(保留修改)
|
||||||
|
|
||||||
|
# 验证
|
||||||
|
bash .harness/init.sh # 初始化
|
||||||
|
bash .harness/check.sh # 全部门禁
|
||||||
|
|
||||||
|
# 状态
|
||||||
|
cat .harness/PROGRESS.md # 进度
|
||||||
|
cat .harness/feature_list.json # 功能清单
|
||||||
|
```
|
||||||
82
.harness/check.sh
Executable file
82
.harness/check.sh
Executable file
@@ -0,0 +1,82 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# =============================================
|
||||||
|
# Harness Quality Gates — 一键运行所有门禁
|
||||||
|
# 源自 $closed-loop-testing skill
|
||||||
|
# =============================================
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
cd "$ROOT_DIR"
|
||||||
|
|
||||||
|
PASS=0
|
||||||
|
FAIL=0
|
||||||
|
RESULTS=()
|
||||||
|
|
||||||
|
check() {
|
||||||
|
local level="$1" name="$2" cmd="$3"
|
||||||
|
cd "$ROOT_DIR"
|
||||||
|
echo ""
|
||||||
|
echo "━━━ [${level}] ${name} ━━━"
|
||||||
|
if eval "$cmd" 2>&1; then
|
||||||
|
echo " ✅ ${name} 通过"
|
||||||
|
PASS=$((PASS + 1))
|
||||||
|
RESULTS+=("✅|${level}|${name}")
|
||||||
|
else
|
||||||
|
echo " ❌ ${name} 失败"
|
||||||
|
FAIL=$((FAIL + 1))
|
||||||
|
RESULTS+=("❌|${level}|${name}")
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "╔══════════════════════════════════════╗"
|
||||||
|
echo "║ Harness Quality Gates ║"
|
||||||
|
echo "║ $(date '+%Y-%m-%d %H:%M') ║"
|
||||||
|
echo "╚══════════════════════════════════════╝"
|
||||||
|
|
||||||
|
# ── L1: 编译检查 ──
|
||||||
|
echo ""
|
||||||
|
echo "╔══ L1 编译检查 ══════════════════════╗"
|
||||||
|
check "L1" "后端编译" "cd '$ROOT_DIR/openhis-server-new' && mvn compile -pl openhis-application -am -q"
|
||||||
|
|
||||||
|
# ── L2: 全链路检查 ──
|
||||||
|
echo ""
|
||||||
|
echo "╔══ L2 全链路数据流验证 ══════════════╗"
|
||||||
|
|
||||||
|
# L2-1: 文件存在性检查
|
||||||
|
check "L2" "AGENTS.md 存在" "test -f '$ROOT_DIR/AGENTS.md'"
|
||||||
|
check "L2" "init.sh 可执行" "test -x '$ROOT_DIR/.harness/init.sh'"
|
||||||
|
check "L2" "PROGRESS.md 存在" "test -f '$ROOT_DIR/.harness/PROGRESS.md'"
|
||||||
|
check "L2" "feature_list.json 有效" "python3 -c 'import json; json.load(open(\"$ROOT_DIR/.harness/feature_list.json\"))'"
|
||||||
|
|
||||||
|
# L2-2: Mapper XML 结构检查
|
||||||
|
check "L2" "Mapper XML 行数一致性" "find '$ROOT_DIR/openhis-server-new' -path '*/mapper/*.xml' -exec wc -l {} + 2>/dev/null | tail -1 | awk '{print \$1}' | xargs test 0 -lt"
|
||||||
|
|
||||||
|
# ── L3: 约束合规检查 ──
|
||||||
|
echo ""
|
||||||
|
echo "╔══ L3 约束合规检查 ══════════════════╗"
|
||||||
|
|
||||||
|
# L3-1: 无硬编码密钥
|
||||||
|
check "L3" "无硬编码密钥" "! grep -r 'password=.*[a-zA-Z0-9]\{8,\}' --include='*.java' --include='*.yml' --include='*.xml' --include='*.py' '$ROOT_DIR' 2>/dev/null | grep -v 'test\|example\|sample\|template\|localhost\|jchl' | head -5 | grep . && false || true"
|
||||||
|
|
||||||
|
# ── 汇总 ──
|
||||||
|
echo ""
|
||||||
|
echo "╔══════════════════════════════════════╗"
|
||||||
|
echo "║ 质量门禁结果汇总 ║"
|
||||||
|
echo "╚══════════════════════════════════════╝"
|
||||||
|
echo ""
|
||||||
|
for r in "${RESULTS[@]}"; do
|
||||||
|
IFS='|' read -r status level name <<< "$r"
|
||||||
|
echo " $status [$level] $name"
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
echo " 总计: $((PASS + FAIL)) | ✅ $PASS 通过 | ❌ $FAIL 失败"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ "$FAIL" -gt 0 ]; then
|
||||||
|
echo " ⚠️ 有 $FAIL 项未通过"
|
||||||
|
echo " 提示:新增/修改文件后记得 git add 后再检查"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo " 🎉 所有门禁通过!"
|
||||||
|
fi
|
||||||
13
.harness/clean-state-checklist.md
Normal file
13
.harness/clean-state-checklist.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# 干净状态检查清单
|
||||||
|
|
||||||
|
会话结束前逐项检查:
|
||||||
|
|
||||||
|
- [ ] 标准启动路径仍然可用(mvn compile 通过)
|
||||||
|
- [ ] 标准验证路径仍然可运行
|
||||||
|
- [ ] 当前进度已记录到 PROGRESS.md
|
||||||
|
- [ ] 功能状态真实反映 passing 和未验证的边界
|
||||||
|
- [ ] feature_list.json 已更新
|
||||||
|
- [ ] 没有任何半成品步骤处于未记录状态
|
||||||
|
- [ ] 临时文件和调试代码已清理
|
||||||
|
- [ ] 提交信息清晰描述了变更内容
|
||||||
|
- [ ] 下一轮会话无需人工修复即可继续
|
||||||
22
.harness/evaluator-rubric.md
Normal file
22
.harness/evaluator-rubric.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# 评审评分表
|
||||||
|
|
||||||
|
| 维度 | 问题 | 0-2分 | 备注 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 正确性 | 实现的行为是否符合目标功能? | | |
|
||||||
|
| 验证 | 编译检查是否通过?数据流是否完整? | | |
|
||||||
|
| 范围纪律 | 是否保持在选定功能范围内? | | |
|
||||||
|
| 可靠性 | 结果能否在重启后继续工作? | | |
|
||||||
|
| 可维护性 | 代码是否遵循项目规范? | | |
|
||||||
|
| 交接准备度 | 下一轮能否只靠仓库内文件继续推进? | | |
|
||||||
|
|
||||||
|
## 结论
|
||||||
|
|
||||||
|
- [ ] Accept
|
||||||
|
- [ ] Revise
|
||||||
|
- [ ] Block
|
||||||
|
|
||||||
|
## 后续动作
|
||||||
|
|
||||||
|
- 缺失的证据:
|
||||||
|
- 必须补的修复:
|
||||||
|
- 下次复审触发条件:
|
||||||
72
.harness/feature_list.json
Normal file
72
.harness/feature_list.json
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
{
|
||||||
|
"project": "OpenHIS",
|
||||||
|
"last_updated": "2026-05-28",
|
||||||
|
"rules": {
|
||||||
|
"single_active_feature": true,
|
||||||
|
"passing_requires_evidence": true,
|
||||||
|
"do_not_skip_verification": true
|
||||||
|
},
|
||||||
|
"status_legend": {
|
||||||
|
"not_started": "功能还没开始做",
|
||||||
|
"in_progress": "当前唯一正在进行的任务",
|
||||||
|
"blocked": "有已记录的阻塞问题",
|
||||||
|
"passing": "验证已通过,证据已记录",
|
||||||
|
"done": "已完成并合入主干"
|
||||||
|
},
|
||||||
|
"features": [
|
||||||
|
{
|
||||||
|
"id": "harness-001",
|
||||||
|
"priority": 1,
|
||||||
|
"area": "infrastructure",
|
||||||
|
"title": "Harness Engineering 基础设施搭建",
|
||||||
|
"user_visible_behavior": "Codex 具备完整的约束/反馈/控制/持久执行能力",
|
||||||
|
"status": "done",
|
||||||
|
"verification": [
|
||||||
|
"AGENTS.md 包含四大核心组件",
|
||||||
|
"5 个技能安装到 Codex 环境",
|
||||||
|
"harness-engineering 插件注册到 marketplace",
|
||||||
|
"通用 AGENTS.md 模板可用"
|
||||||
|
],
|
||||||
|
"evidence": ["AGENTS.md restructured", "skills created", "plugin validated"],
|
||||||
|
"notes": "v1: 24 篇博客方法整合完成"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "harness-002",
|
||||||
|
"priority": 2,
|
||||||
|
"area": "infrastructure",
|
||||||
|
"title": "WalkingLabs 实战模式整合",
|
||||||
|
"user_visible_behavior": "项目具备完整的 5 子系统 Harness(指令/工具/环境/状态/反馈)",
|
||||||
|
"status": "done",
|
||||||
|
"verification": [
|
||||||
|
".harness/ 目录包含所有模板文件",
|
||||||
|
"init.sh 可正常运行",
|
||||||
|
"PROGRESS.md 记录当前状态",
|
||||||
|
"feature_list.json 跟踪所有功能",
|
||||||
|
"walkinglabs-harness 技能已安装"
|
||||||
|
],
|
||||||
|
"evidence": [
|
||||||
|
"init.sh verified (compile OK)",
|
||||||
|
"6 templates installed in .harness/",
|
||||||
|
"AGENTS.md updated with 5-subsystem model",
|
||||||
|
"walkinglabs-harness skill created (142 lines)"
|
||||||
|
],
|
||||||
|
"notes": "v2: walkinglabs 5 子系统整合完成"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "harness-003",
|
||||||
|
"priority": 3,
|
||||||
|
"area": "infrastructure",
|
||||||
|
"title": "建立质量门禁自动化检查脚本",
|
||||||
|
"user_visible_behavior": "运行一条命令即可完成 L1-L3 质量门禁检查",
|
||||||
|
"status": "not_started",
|
||||||
|
"verification": [
|
||||||
|
"创建 .harness/check.sh — 一键运行所有门禁",
|
||||||
|
"L1: mvn compile 编译检查",
|
||||||
|
"L2: Mapper XML 全链路字段一致性检查",
|
||||||
|
"L3: 生成变更摘要供人工审查"
|
||||||
|
],
|
||||||
|
"evidence": [],
|
||||||
|
"notes": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
43
.harness/init.sh
Executable file
43
.harness/init.sh
Executable file
@@ -0,0 +1,43 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Harness Init — 统一启动与验证入口
|
||||||
|
# 每次新会话开始前运行
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
cd "$ROOT_DIR"
|
||||||
|
|
||||||
|
echo "==> 当前目录: $PWD"
|
||||||
|
echo "==> Git 状态"
|
||||||
|
git status --short 2>/dev/null || true
|
||||||
|
git log --oneline -3 2>/dev/null || true
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "==> 编译检查"
|
||||||
|
cd openhis-server-new
|
||||||
|
mvn compile -pl openhis-application -am -q 2>/dev/null && echo " ✅ 编译通过" || echo " ❌ 编译失败"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "==> 读取进度"
|
||||||
|
if [ -f .harness/PROGRESS.md ]; then
|
||||||
|
head -20 .harness/PROGRESS.md
|
||||||
|
else
|
||||||
|
echo " (无进度文件)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "==> 读取功能清单"
|
||||||
|
if [ -f .harness/feature_list.json ]; then
|
||||||
|
python3 -c "
|
||||||
|
import json
|
||||||
|
with open('.harness/feature_list.json') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
features = [f for f in data.get('features', []) if f.get('status') == 'in_progress']
|
||||||
|
if features:
|
||||||
|
print(f\" 当前进行中: {features[0].get('title', 'unknown')}\")
|
||||||
|
else:
|
||||||
|
print(' 当前无进行中的功能')
|
||||||
|
" 2>/dev/null || echo " (无法解析)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "==> 环境就绪 ✅"
|
||||||
29
.harness/session-handoff.md
Normal file
29
.harness/session-handoff.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# 会话交接
|
||||||
|
|
||||||
|
## 当前已验证
|
||||||
|
|
||||||
|
- 现在明确可用的部分:
|
||||||
|
- 本轮实际跑过的验证:
|
||||||
|
|
||||||
|
## 本轮改动
|
||||||
|
|
||||||
|
- 新增了哪些代码或行为:
|
||||||
|
- Harness 发生了哪些变化:
|
||||||
|
|
||||||
|
## 仍损坏或未验证
|
||||||
|
|
||||||
|
- 已知缺陷:
|
||||||
|
- 未验证路径:
|
||||||
|
- 下一轮需要注意的风险:
|
||||||
|
|
||||||
|
## 下一步最佳动作
|
||||||
|
|
||||||
|
- 最高优先级未完成功能:
|
||||||
|
- 为什么它是下一步:
|
||||||
|
- 什么结果才算 passing:
|
||||||
|
|
||||||
|
## 命令速查
|
||||||
|
|
||||||
|
- 编译:`cd openhis-server-new && mvn compile -pl openhis-application -am`
|
||||||
|
- 打包:`mvn clean package -DskipTests`
|
||||||
|
- 启动:`mvn spring-boot:run`
|
||||||
361
AGENTS.md
361
AGENTS.md
@@ -1,188 +1,237 @@
|
|||||||
# OpenHIS - AI Agent Development Guide
|
# OpenHIS — Harness Engineering 开发指南
|
||||||
|
|
||||||
## 项目概览
|
> **模型决定上限,Harness 决定底线。**
|
||||||
OpenHIS 是一个医院管理系统,采用 Java 17 + Spring Boot 后端和 Vue 3 + Vite 前端架构。
|
> 本文件是 OpenHIS 项目的 Harness Engineering 落地。整合了 OpenAI/Anthropic Harness Engineering 方法论与 walkinglabs 实战模式。
|
||||||
|
|
||||||
## 构建和运行命令
|
---
|
||||||
|
|
||||||
|
## 📋 项目信息
|
||||||
|
|
||||||
|
OpenHIS 医院管理系统 | Java 17 + Spring Boot + MyBatis Plus | Vue 3 + Element Plus | PostgreSQL
|
||||||
|
|
||||||
|
### 构建和运行
|
||||||
|
|
||||||
### 后端(Java/Spring Boot)
|
|
||||||
```bash
|
```bash
|
||||||
# 构建整个项目
|
cd /root/.openclaw/workspace/his-repo
|
||||||
cd openhis-server-new
|
|
||||||
|
# 初始化(每次新会话先运行)
|
||||||
|
bash .harness/init.sh
|
||||||
|
|
||||||
|
# 后端编译
|
||||||
|
cd openhis-server-new && mvn compile -pl openhis-application -am
|
||||||
|
|
||||||
|
# 后端打包
|
||||||
mvn clean package -DskipTests
|
mvn clean package -DskipTests
|
||||||
|
|
||||||
# 运行后端(开发模式)
|
# 后端运行
|
||||||
cd openhis-server-new/openhis-application
|
cd openhis-application && mvn spring-boot:run
|
||||||
mvn spring-boot:run
|
|
||||||
|
|
||||||
# 运行特定模块
|
# 前端
|
||||||
cd openhis-server-new/[module-name]
|
cd openhis-ui-vue3 && npm install && npm run dev
|
||||||
mvn spring-boot:run
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 前端(Vue 3 + Vite)
|
### 关键路径
|
||||||
```bash
|
|
||||||
# 安装依赖
|
|
||||||
cd openhis-ui-vue3
|
|
||||||
npm install
|
|
||||||
|
|
||||||
# 开发服务器
|
```
|
||||||
npm run dev
|
后端代码: openhis-server-new/openhis-application/src/main/java/com/
|
||||||
|
后端配置: openhis-server-new/openhis-application/src/main/resources/
|
||||||
# 生产构建
|
Mapper XML: .../mapper/ (regdoctorstation/, doctorstation/, ...)
|
||||||
npm run build:prod
|
前端代码: openhis-ui-vue3/src/
|
||||||
|
Harness: .harness/ (init.sh, PROGRESS.md, feature_list.json, ...)
|
||||||
# 测试环境构建
|
|
||||||
npm run build:test
|
|
||||||
|
|
||||||
# 预览构建结果
|
|
||||||
npm run preview
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 测试
|
---
|
||||||
项目当前没有配置正式的测试框架。如需添加测试:
|
|
||||||
- 后端:考虑使用 JUnit 5 + Mockito
|
|
||||||
- 前端:考虑使用 Vitest + Vue Test Utils
|
|
||||||
|
|
||||||
## 代码风格规范
|
## 🔧 5 子系统模型(WalkingLabs)
|
||||||
|
|
||||||
### Java 后端规范
|
> 源自:[Learn Harness Engineering](https://walkinglabs.github.io/learn-harness-engineering/zh/)
|
||||||
- **Java 版本**: 17
|
|
||||||
- **框架**: Spring Boot 2.5.15
|
|
||||||
- **ORM**: MyBatis Plus 3.5.5
|
|
||||||
- **数据库**: PostgreSQL
|
|
||||||
- **包结构**:
|
|
||||||
- `com.openhis` - 业务逻辑
|
|
||||||
- `com.core` - 核心框架
|
|
||||||
- **命名约定**:
|
|
||||||
- 类名:PascalCase(如 `UserController`)
|
|
||||||
- 方法名:camelCase(如 `getUserList`)
|
|
||||||
- 常量:SCREAMING_SNAKE_CASE
|
|
||||||
- 配置文件:kebab-case
|
|
||||||
- **注解使用**:
|
|
||||||
- 使用 `@Slf4j` 替代手动声明 logger
|
|
||||||
- 使用 `@Data` 在实体类中
|
|
||||||
- 使用 `@Service/@Controller/@Repository` 等 Spring 注解
|
|
||||||
- **异常处理**:
|
|
||||||
- 使用统一的异常处理机制
|
|
||||||
- 自定义业务异常继承 `RuntimeException`
|
|
||||||
|
|
||||||
### Vue 前端规范
|
### 1. 指令子系统(Instruction)
|
||||||
- **框架**: Vue 3 + Composition API
|
|
||||||
- **UI 库**: Element Plus
|
| 文件 | 用途 |
|
||||||
- **状态管理**: Pinia
|
|---|---|
|
||||||
- **路由**: Vue Router 4
|
| **AGENTS.md**(本文件) | 项目规则、约束、工作流程 |
|
||||||
- **构建工具**: Vite 5
|
| `.harness/feature_list.json` | 机器可读的功能状态追踪 |
|
||||||
- **组件命名**: PascalCase
|
| `.harness/PROGRESS.md` | 会话进度和已验证状态 |
|
||||||
- **文件命名**: kebab-case
|
| `.harness/session-handoff.md` | 跨会话交接摘要 |
|
||||||
- **变量命名**: camelCase
|
|
||||||
- **常量命名**: SCREAMING_SNAKE_CASE
|
### 2. 工具子系统(Tools)
|
||||||
- **函数命名**:
|
|
||||||
- 事件处理:`handle` 前缀
|
| 工具 | 用途 |
|
||||||
- 数据获取:`get`/`load` 前缀
|
|---|---|
|
||||||
- 提交操作:`submit` 前缀
|
| `mvn compile` | 编译验证 |
|
||||||
|
| `git` | 版本控制 + 回滚 |
|
||||||
|
| `pwd` | 确认当前目录 |
|
||||||
|
| shell | 文件操作、命令执行 |
|
||||||
|
|
||||||
|
### 3. 环境子系统(Environment)
|
||||||
|
|
||||||
|
| 组件 | 状态 |
|
||||||
|
|---|---|
|
||||||
|
| Java 17 | ✅ `pom.xml` 锁定 |
|
||||||
|
| Maven | ✅ `mvn-wrapper` |
|
||||||
|
| PostgreSQL | ✅ 192.168.110.252:15432 |
|
||||||
|
| Node.js | ✅ `package.json` 锁定 |
|
||||||
|
|
||||||
|
### 4. 状态子系统(State)
|
||||||
|
|
||||||
|
| 机制 | 用途 |
|
||||||
|
|---|---|
|
||||||
|
| `update_plan` | 当前步骤检查点 |
|
||||||
|
| `.harness/PROGRESS.md` | 跨会话进度记录 |
|
||||||
|
| `.harness/feature_list.json` | 功能状态跟踪 |
|
||||||
|
| `git log` | 变更历史追溯 |
|
||||||
|
|
||||||
|
### 5. 反馈子系统(Feedback)
|
||||||
|
|
||||||
|
| 层级 | 命令 | 时间 |
|
||||||
|
|---|---|---|
|
||||||
|
| L1 编译 | `mvn compile -pl openhis-application -am` | <30 秒 |
|
||||||
|
| L2 全链路 | 六环检查清单(见下文) | <5 分钟 |
|
||||||
|
| L3 审查 | 你人工审查 diff | 10-30 分钟 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 标准工作循环
|
||||||
|
|
||||||
|
```
|
||||||
|
开始会话
|
||||||
|
│
|
||||||
|
├→ 1. Init
|
||||||
|
│ ├── bash .harness/init.sh
|
||||||
|
│ ├── 读取 PROGRESS.md / feature_list.json
|
||||||
|
│ ├── git log --oneline -5
|
||||||
|
│ └── 确认编译通过
|
||||||
|
│
|
||||||
|
├→ 2. Plan
|
||||||
|
│ ├── update_plan / checklist_write 分解步骤
|
||||||
|
│ ├── 评估复杂度/风险
|
||||||
|
│ └── 设定检查点
|
||||||
|
│
|
||||||
|
├→ 3. Implement
|
||||||
|
│ ├── 一次只做一个功能
|
||||||
|
│ ├── 全链路检查清单核对
|
||||||
|
│ └── 增量修改,只动必要文件
|
||||||
|
│
|
||||||
|
├→ 4. Verify
|
||||||
|
│ ├── L1: mvn compile
|
||||||
|
│ ├── L2: 全链路数据流验证
|
||||||
|
│ └── 生成变更摘要
|
||||||
|
│
|
||||||
|
└→ 5. Cleanup
|
||||||
|
├── 运行 clean-state-checklist.md
|
||||||
|
├── 更新 PROGRESS.md + feature_list.json
|
||||||
|
├── git add + commit + push
|
||||||
|
└── init.sh 确认干净状态
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔗 全链路修复原则
|
||||||
|
|
||||||
|
修 Bug 时,不得"就事论事",必须走通完整的**数据流全链路**:
|
||||||
|
|
||||||
|
### 六环检查清单
|
||||||
|
|
||||||
|
```
|
||||||
|
1. 录入 → 前端有无输入入口?(弹窗、行编辑、表单...)
|
||||||
|
2. 保存 → 前端 → API → Controller → Service → Entity → DB,
|
||||||
|
每个保存入口都传了该字段吗?
|
||||||
|
3. 查询 → DB → Mapper XML(UNION ALL 子查询统一加)→ DTO → 前端展示
|
||||||
|
4. 修改 → 编辑回显 → 修改保存 → 正确更新?
|
||||||
|
5. 删除 → 状态变更会丢失该字段吗?
|
||||||
|
6. 关联 → 上下游(护士站、计费、打印、报表)需要同步改吗?
|
||||||
|
```
|
||||||
|
|
||||||
|
### 常见陷阱
|
||||||
|
|
||||||
|
| 陷阱 | 解决 |
|
||||||
|
|---|---|
|
||||||
|
| 只修主入口,批量保存/签发保存漏了 | 检查所有 Service 实现类 |
|
||||||
|
| 前端加了后端没传 | 逐个入口确认 |
|
||||||
|
| UNION ALL 只改一半 | 所有子查询统一加 |
|
||||||
|
| DTO 继承链没检查 | 检查父类/子类字段一致性 |
|
||||||
|
| 只测新增没测编辑 | 新增和编辑都要测 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📐 代码风格规范
|
||||||
|
|
||||||
|
### Java 后端
|
||||||
|
|
||||||
|
| 项目 | 规范 |
|
||||||
|
|---|---|
|
||||||
|
| 包结构 | `com.openhis`(业务)、`com.core`(核心) |
|
||||||
|
| 命名 | 类 PascalCase、方法 camelCase、常量 SCREAMING_SNAKE_CASE |
|
||||||
|
| 注解 | `@Slf4j`、`@Data`、`@Service/@Controller/@Repository` |
|
||||||
|
| 异常 | 统一异常处理,业务异常继承 `RuntimeException` |
|
||||||
|
| 缩进 | 4 空格,行 120 字符 |
|
||||||
|
|
||||||
|
### Vue 前端
|
||||||
|
|
||||||
|
| 项目 | 规范 |
|
||||||
|
|---|---|
|
||||||
|
| 框架 | Vue 3 + Composition API + Element Plus + Pinia |
|
||||||
|
| 命名 | 组件 PascalCase、文件 kebab-case、变量 camelCase |
|
||||||
|
| 缩进 | 2 空格,单引号,行 100 字符 |
|
||||||
|
|
||||||
### 导入顺序
|
### 导入顺序
|
||||||
#### Java
|
|
||||||
1. `java.*`
|
|
||||||
2. `javax.*`
|
|
||||||
3. 第三方库
|
|
||||||
4. `com.core.*`
|
|
||||||
5. `com.openhis.*`
|
|
||||||
6. `*.*`(其他包)
|
|
||||||
|
|
||||||
#### JavaScript/Vue
|
**Java:** `java.*` → `javax.*` → 第三方 → `com.core.*` → `com.openhis.*`
|
||||||
1. `vue` 相关
|
**Vue:** `vue` 相关 → 第三方 → `@/` 别名 → 相对路径
|
||||||
2. 第三方库
|
|
||||||
3. `@/` 别名导入
|
|
||||||
4. 相对路径导入
|
|
||||||
|
|
||||||
### 代码格式
|
---
|
||||||
#### Java
|
|
||||||
- 缩进:4个空格
|
|
||||||
- 行长度:120字符
|
|
||||||
- 左大括号不换行
|
|
||||||
|
|
||||||
#### Vue/JavaScript
|
## 🏗️ 开发约定
|
||||||
- 缩进:2个空格
|
|
||||||
- 字符串:优先使用单引号
|
|
||||||
- 行长度:100字符
|
|
||||||
|
|
||||||
## 关键配置文件
|
| 领域 | 约定 |
|
||||||
|
|---|---|
|
||||||
|
| API | RESTful,统一响应格式,Swagger 文档 |
|
||||||
|
| 数据库 | snake_case 命名,主键 `id`,软删除 `valid_flag` |
|
||||||
|
| 安全 | 所有 API 需权限验证,SQL 注入/XSS 防护 |
|
||||||
|
| 性能 | Druid 连接池,路由懒加载,虚拟滚动 |
|
||||||
|
|
||||||
### 后端配置
|
---
|
||||||
- 主配置:`openhis-server-new/openhis-application/src/main/resources/application.yml`
|
|
||||||
- 环境配置:`application-{profile}.yml`
|
|
||||||
- Maven 父 POM:`openhis-server-new/pom.xml`
|
|
||||||
|
|
||||||
### 前端配置
|
## ⚙️ 关键配置
|
||||||
- Vite 配置:`openhis-ui-vue3/vite.config.js`
|
|
||||||
- 环境变量:`.env.*` 文件
|
|
||||||
- 路由配置:`openhis-ui-vue3/src/router/index.js`
|
|
||||||
|
|
||||||
## 开发约定
|
| 项目 | 值 |
|
||||||
|
|---|---|
|
||||||
|
| 后端端口 | 18080 |
|
||||||
|
| 前端端口 | 81 |
|
||||||
|
| API 前缀 | `/openhis` |
|
||||||
|
| Swagger | `/openhis/swagger-ui/index.html` |
|
||||||
|
| 后端配置 | `application.yml` / `application-{profile}.yml` |
|
||||||
|
| 前端配置 | `vite.config.js` / `.env.*` |
|
||||||
|
|
||||||
### API 设计
|
---
|
||||||
- RESTful API 风格
|
|
||||||
- 统一响应格式
|
|
||||||
- 使用 Swagger 文档
|
|
||||||
- 错误码统一管理
|
|
||||||
|
|
||||||
### 数据库
|
## 📈 成熟度追踪
|
||||||
- 表名:snake_case
|
|
||||||
- 字段名:snake_case
|
|
||||||
- 主键:使用 `id`
|
|
||||||
- 软删除:使用 `valid_flag` 字段
|
|
||||||
|
|
||||||
### 前端组件
|
| 等级 | 特征 | 本项目 |
|
||||||
- 单一职责原则
|
|---|---|---|
|
||||||
- Props 使用 camelCase
|
| **L1 初始** | 零星使用 AI 工具 | ✅ 已超越 |
|
||||||
- Events 使用 kebab-case
|
| **L2 管理** | 基础约束 + 反馈 + 控制 | ✅ **当前** |
|
||||||
- 使用 Composition API
|
| **L3 定义** | 标准化、可复用 | 🔄 walkinglabs 5 子系统整合 |
|
||||||
- 组件文档使用 JSDoc
|
| **L4 量化** | 数据驱动优化 | ⏳ |
|
||||||
|
| **L5 优化** | AI 自主优化 Harness | ⏳ |
|
||||||
|
|
||||||
### 状态管理
|
---
|
||||||
- 模块化设计
|
|
||||||
- 异步操作使用 actions
|
|
||||||
- 避免在组件中直接修改状态
|
|
||||||
|
|
||||||
## 环境变量
|
## 📚 技能索引(Codex 内置)
|
||||||
|
|
||||||
### 前端
|
| 技能 | 用途 |
|
||||||
- `VITE_APP_BASE_API`: API 基础路径
|
|---|---|
|
||||||
- `VITE_APP_ENV`: 环境标识
|
| `$harness-engineering` | 主方法论 — 约束 + 反馈 + 控制 + 持久 |
|
||||||
|
| `$walkinglabs-harness` | 实战模式 — 5 子系统 + 模板 + 会话持续 |
|
||||||
|
| `$durable-execution` | 检查点、幂等性、事件溯源 |
|
||||||
|
| `$closed-loop-testing` | 质量门禁、测试策略、反馈循环 |
|
||||||
|
| `$constraint-design` | DSL 设计、策略模式、约束编排 |
|
||||||
|
| `$review-audit` | 审查工作流、审计追踪、合规检查 |
|
||||||
|
| `$full-chain-fix` | 全链路数据流修复 |
|
||||||
|
| `$karpathy-guidelines` | 减少 LLM 编码常见错误 |
|
||||||
|
|
||||||
### 后端
|
---
|
||||||
- `spring.profiles.active`: 激活的配置文件
|
|
||||||
- `core.name`: 应用名称
|
|
||||||
- `core.version`: 应用版本
|
|
||||||
|
|
||||||
## 安全规范
|
> **总纲:** 你负责"做什么"和"为什么",Agent 负责"怎么做"和"做多好"
|
||||||
- 所有 API 接口需要权限验证
|
> **工作循环:** Init → Plan → Implement → Verify → Cleanup
|
||||||
- 敏感信息使用环境变量
|
|
||||||
- SQL 注入防护
|
|
||||||
- XSS 攻击防护
|
|
||||||
|
|
||||||
## 性能优化
|
|
||||||
- 后端使用连接池(Druid)
|
|
||||||
- 前端使用路由懒加载
|
|
||||||
- 图片使用 WebP 格式
|
|
||||||
- 大列表使用虚拟滚动
|
|
||||||
|
|
||||||
## 常用工具类
|
|
||||||
- 后端:`com.core.common.utils.*`
|
|
||||||
- 前端:`@/utils/*`
|
|
||||||
|
|
||||||
## 注意事项
|
|
||||||
1. 修改数据库结构需要同步 SQL 脚本
|
|
||||||
2. 新增功能需要添加权限配置
|
|
||||||
3. 前端路由需要在权限系统中注册
|
|
||||||
4. 接口变更需要更新 Swagger 文档
|
|
||||||
5. 遵循现有代码风格,避免不必要的变化
|
|
||||||
|
|
||||||
## 故障排除
|
|
||||||
- 后端端口:18080
|
|
||||||
- 前端端口:81
|
|
||||||
- API 前缀:`/openhis`
|
|
||||||
- Swagger UI:`/openhis/swagger-ui/index.html`
|
|
||||||
- Druid 监控:`/openhis/druid/login.html`
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package com.openhis.web.Inspection.dto;
|
package com.openhis.web.Inspection.dto;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Getter;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.Setter;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -11,30 +11,17 @@ import java.util.List;
|
|||||||
* @author
|
* @author
|
||||||
* @date
|
* @date
|
||||||
*/
|
*/
|
||||||
@Data
|
@Getter
|
||||||
@Accessors(chain = true)
|
@Setter
|
||||||
public class InstrumentManageInitDto {
|
public class InstrumentManageInitDto {
|
||||||
private List<statusEnumOption> statusFlagOptions;
|
private List<statusEnumOption> statusFlagOptions;
|
||||||
private List<InstrumentType> InstrumentTypeList;
|
private List<InstrumentType> instrumentTypeList;
|
||||||
private List<InstrumentStatusEnumOption> InstrumentStatusEnumList;
|
private List<InstrumentStatusEnumOption> instrumentStatusEnumList;
|
||||||
|
|
||||||
// 手动添加 setter 方法
|
|
||||||
public void setStatusFlagOptions(List<statusEnumOption> statusFlagOptions) {
|
|
||||||
this.statusFlagOptions = statusFlagOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setInstrumentTypeList(List<InstrumentType> InstrumentTypeList) {
|
|
||||||
this.InstrumentTypeList = InstrumentTypeList;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setInstrumentStatusEnumList(List<InstrumentStatusEnumOption> InstrumentStatusEnumList) {
|
|
||||||
this.InstrumentStatusEnumList = InstrumentStatusEnumList;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 状态
|
* 状态
|
||||||
*/
|
*/
|
||||||
@Data
|
@Getter
|
||||||
public static class statusEnumOption {
|
public static class statusEnumOption {
|
||||||
private Integer value;
|
private Integer value;
|
||||||
private String info;
|
private String info;
|
||||||
@@ -44,7 +31,7 @@ public class InstrumentManageInitDto {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Data
|
@Getter
|
||||||
public static class InstrumentStatusEnumOption {
|
public static class InstrumentStatusEnumOption {
|
||||||
private Integer value;
|
private Integer value;
|
||||||
private String info;
|
private String info;
|
||||||
@@ -54,7 +41,7 @@ public class InstrumentManageInitDto {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Data
|
@Getter
|
||||||
public static class InstrumentType {
|
public static class InstrumentType {
|
||||||
private Integer value;
|
private Integer value;
|
||||||
private String info;
|
private String info;
|
||||||
@@ -63,6 +50,4 @@ public class InstrumentManageInitDto {
|
|||||||
this.info = info;
|
this.info = info;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
|
||||||
@@ -48,6 +48,9 @@ import com.openhis.web.personalization.dto.ActivityDeviceDto;
|
|||||||
import com.openhis.workflow.domain.ActivityDefinition;
|
import com.openhis.workflow.domain.ActivityDefinition;
|
||||||
import com.openhis.workflow.domain.DeviceRequest;
|
import com.openhis.workflow.domain.DeviceRequest;
|
||||||
import com.openhis.workflow.domain.InventoryItem;
|
import com.openhis.workflow.domain.InventoryItem;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
import com.openhis.workflow.domain.ServiceRequest;
|
import com.openhis.workflow.domain.ServiceRequest;
|
||||||
import com.openhis.workflow.service.*;
|
import com.openhis.workflow.service.*;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -946,6 +949,27 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
/**
|
/**
|
||||||
* 处理药品
|
* 处理药品
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 remark 合并到 contentJson 中,确保 Mapper 能从 content_json 提取 remark
|
||||||
|
*/
|
||||||
|
private String injectRemarkIntoContentJson(String contentJson, String remark) {
|
||||||
|
if (remark == null || remark.isEmpty() || contentJson == null || contentJson.isEmpty()) {
|
||||||
|
return contentJson;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
JsonNode node = mapper.readTree(contentJson);
|
||||||
|
if (node instanceof ObjectNode) {
|
||||||
|
((ObjectNode) node).put("remark", remark);
|
||||||
|
return mapper.writeValueAsString(node);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Failed to inject remark into contentJson: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
return contentJson;
|
||||||
|
}
|
||||||
|
|
||||||
private List<String> handMedication(List<AdviceSaveDto> medicineList, Date curDate, String adviceOpType,
|
private List<String> handMedication(List<AdviceSaveDto> medicineList, Date curDate, String adviceOpType,
|
||||||
Long organizationId, String signCode) {
|
Long organizationId, String signCode) {
|
||||||
// 当前登录账号的科室id
|
// 当前登录账号的科室id
|
||||||
@@ -1162,6 +1186,10 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
if (medicationRequest.getId() == null) {
|
if (medicationRequest.getId() == null) {
|
||||||
firstTimeSave = true;
|
firstTimeSave = true;
|
||||||
}
|
}
|
||||||
|
// 确保 contentJson 包含 remark
|
||||||
|
if (adviceSaveDto.getRemark() != null && !adviceSaveDto.getRemark().isEmpty()) {
|
||||||
|
medicationRequest.setContentJson(injectRemarkIntoContentJson(medicationRequest.getContentJson(), adviceSaveDto.getRemark()));
|
||||||
|
}
|
||||||
iMedicationRequestService.saveOrUpdate(medicationRequest);
|
iMedicationRequestService.saveOrUpdate(medicationRequest);
|
||||||
if (firstTimeSave) {
|
if (firstTimeSave) {
|
||||||
medRequestIdList.add(medicationRequest.getId().toString());
|
medRequestIdList.add(medicationRequest.getId().toString());
|
||||||
@@ -1622,6 +1650,10 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
deviceRequest.setConditionId(adviceSaveDto.getConditionId()); // 诊断id
|
deviceRequest.setConditionId(adviceSaveDto.getConditionId()); // 诊断id
|
||||||
deviceRequest.setEncounterDiagnosisId(adviceSaveDto.getEncounterDiagnosisId()); // 就诊诊断id
|
deviceRequest.setEncounterDiagnosisId(adviceSaveDto.getEncounterDiagnosisId()); // 就诊诊断id
|
||||||
|
|
||||||
|
// 确保 contentJson 包含 remark
|
||||||
|
if (adviceSaveDto.getRemark() != null && !adviceSaveDto.getRemark().isEmpty()) {
|
||||||
|
deviceRequest.setContentJson(injectRemarkIntoContentJson(deviceRequest.getContentJson(), adviceSaveDto.getRemark()));
|
||||||
|
}
|
||||||
iDeviceRequestService.saveOrUpdate(deviceRequest);
|
iDeviceRequestService.saveOrUpdate(deviceRequest);
|
||||||
if (is_save) {
|
if (is_save) {
|
||||||
// 处理耗材发放
|
// 处理耗材发放
|
||||||
@@ -2033,6 +2065,9 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
serviceRequest.setBasedOnTable(CommonConstants.TableName.MED_MEDICATION_REQUEST);
|
serviceRequest.setBasedOnTable(CommonConstants.TableName.MED_MEDICATION_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 备注
|
||||||
|
serviceRequest.setRemark(adviceSaveDto.getRemark());
|
||||||
|
|
||||||
iServiceRequestService.saveOrUpdate(serviceRequest);
|
iServiceRequestService.saveOrUpdate(serviceRequest);
|
||||||
|
|
||||||
// 保存时保存诊疗费用项
|
// 保存时保存诊疗费用项
|
||||||
@@ -2290,7 +2325,7 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
log.info("BugFix: signOffAdvice - 签退所有请求,状态改为待签发, requestIdList={}", requestIdList);
|
log.info("BugFix: signOffAdvice - 签退所有请求,状态改为待签发, requestIdList={}", requestIdList);
|
||||||
|
|
||||||
// 尝试签退药品请求(只有存在的才会更新)
|
// 尝试签退药品请求(只有存在的才会更新)
|
||||||
iMedicationRequestService.updateDraftStatusBatch(requestIdList, null, null);
|
iMedicationRequestService.updateDraftStatusBatch(requestIdList, null, null, null);
|
||||||
// 尝试签退耗材请求(只有存在的才会更新)
|
// 尝试签退耗材请求(只有存在的才会更新)
|
||||||
iDeviceRequestService.updateDraftStatusBatch(requestIdList);
|
iDeviceRequestService.updateDraftStatusBatch(requestIdList);
|
||||||
// 尝试签退诊疗请求(只有存在的才会更新)
|
// 尝试签退诊疗请求(只有存在的才会更新)
|
||||||
|
|||||||
@@ -250,4 +250,9 @@ public class AdviceBaseDto {
|
|||||||
* 是否缺少取药科室配置(仅药品类型使用)
|
* 是否缺少取药科室配置(仅药品类型使用)
|
||||||
*/
|
*/
|
||||||
private Boolean pharmacyConfigMissing;
|
private Boolean pharmacyConfigMissing;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 备注(最长50字)
|
||||||
|
*/
|
||||||
|
private String remark;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -282,6 +282,11 @@ public class AdviceSaveDto {
|
|||||||
*/
|
*/
|
||||||
private String sourceBillNo;
|
private String sourceBillNo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 备注(最长50字)
|
||||||
|
*/
|
||||||
|
private String remark;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置默认值
|
* 设置默认值
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -127,11 +127,11 @@ public class RequestBaseDto {
|
|||||||
* 请求状态
|
* 请求状态
|
||||||
*/
|
*/
|
||||||
private Integer statusEnum;
|
private Integer statusEnum;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 退回原因
|
* 退回原因
|
||||||
*/
|
*/
|
||||||
private String reasonText;
|
private String reasonText;
|
||||||
|
|
||||||
private String statusEnum_enumText;
|
private String statusEnum_enumText;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ import java.time.ZoneId;
|
|||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.time.format.DateTimeParseException;
|
import java.time.format.DateTimeParseException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -185,12 +186,13 @@ public class AdviceProcessAppServiceImpl implements IAdviceProcessAppService {
|
|||||||
QueryWrapper<InpatientAdviceParam> queryWrapper
|
QueryWrapper<InpatientAdviceParam> queryWrapper
|
||||||
= HisQueryUtils.buildQueryWrapper(inpatientAdviceParam, null, null, null);
|
= HisQueryUtils.buildQueryWrapper(inpatientAdviceParam, null, null, null);
|
||||||
|
|
||||||
// 手动拼接requestStatus条件:COMPLETED(3)时同时包含CHECK_VERIFIED(10)
|
// 手动拼接requestStatus条件:COMPLETED(3)时同时包含CHECK_VERIFIED(10)和PENDING_RECEIVE(11)
|
||||||
// UNION查询外层列名为request_status(T1.status_enum AS request_status),不是status_enum
|
// UNION查询外层列名为request_status(T1.status_enum AS request_status),不是status_enum
|
||||||
if (requestStatus != null) {
|
if (requestStatus != null) {
|
||||||
if (RequestStatus.COMPLETED.getValue().equals(requestStatus)) {
|
if (RequestStatus.COMPLETED.getValue().equals(requestStatus)) {
|
||||||
queryWrapper.in("request_status",
|
queryWrapper.in("request_status",
|
||||||
RequestStatus.COMPLETED.getValue(), RequestStatus.CHECK_VERIFIED.getValue());
|
RequestStatus.COMPLETED.getValue(), RequestStatus.CHECK_VERIFIED.getValue(),
|
||||||
|
RequestStatus.PENDING_RECEIVE.getValue());
|
||||||
} else {
|
} else {
|
||||||
queryWrapper.eq("request_status", requestStatus);
|
queryWrapper.eq("request_status", requestStatus);
|
||||||
}
|
}
|
||||||
@@ -413,9 +415,14 @@ public class AdviceProcessAppServiceImpl implements IAdviceProcessAppService {
|
|||||||
}
|
}
|
||||||
Long practitionerId = SecurityUtils.getLoginUser().getPractitionerId();
|
Long practitionerId = SecurityUtils.getLoginUser().getPractitionerId();
|
||||||
Date checkDate = new Date();
|
Date checkDate = new Date();
|
||||||
|
// 从请求中提取退回原因(所有项目共享同一原因)
|
||||||
|
String backReason = performInfoList.stream()
|
||||||
|
.map(PerformInfoDto::getBackReason)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
if (!serviceRequestList.isEmpty()) {
|
if (!serviceRequestList.isEmpty()) {
|
||||||
// 更新服务请求状态待发送
|
// 更新服务请求状态待发送
|
||||||
String backReason = performInfoList.get(0).getBackReason();
|
|
||||||
serviceRequestService.updateDraftStatus(
|
serviceRequestService.updateDraftStatus(
|
||||||
serviceRequestList.stream().map(PerformInfoDto::getRequestId).toList(), practitionerId, checkDate, backReason);
|
serviceRequestList.stream().map(PerformInfoDto::getRequestId).toList(), practitionerId, checkDate, backReason);
|
||||||
}
|
}
|
||||||
@@ -575,10 +582,7 @@ public class AdviceProcessAppServiceImpl implements IAdviceProcessAppService {
|
|||||||
// 处理长期已发放的药品
|
// 处理长期已发放的药品
|
||||||
if (!longMedDispensedList.isEmpty()) {
|
if (!longMedDispensedList.isEmpty()) {
|
||||||
// 生成退药单
|
// 生成退药单
|
||||||
this.creatRefundMedicationList(longMedDispensedList, procedureIdMap);
|
this.creatRefundMedicationList(tempMedDispensedList, procedureIdMap);
|
||||||
// 药品退药请求状态变更(待退药)
|
|
||||||
medicationRequestService.updateCancelledStatusBatch(
|
|
||||||
longMedDispensedList.stream().map(MedicationDispense::getMedReqId).toList(), null, null);
|
|
||||||
}
|
}
|
||||||
// 处理临时已发放药品
|
// 处理临时已发放药品
|
||||||
if (!tempMedDispensedList.isEmpty()) {
|
if (!tempMedDispensedList.isEmpty()) {
|
||||||
|
|||||||
@@ -256,7 +256,7 @@ public class OutpatientInfusionAppServiceImpl implements IOutpatientInfusionAppS
|
|||||||
}
|
}
|
||||||
boolean result = serviceRequestService.updateCancelledStatus(serviceReqId, now, practitionerId, orgId);
|
boolean result = serviceRequestService.updateCancelledStatus(serviceReqId, now, practitionerId, orgId);
|
||||||
// 更新主服务请求状态为待执行
|
// 更新主服务请求状态为待执行
|
||||||
serviceRequestService.updateDraftStatus(List.of(serviceRequest.getBasedOnId()), null, null);
|
serviceRequestService.updateDraftStatus(List.of(serviceRequest.getBasedOnId()), null, null, null);
|
||||||
if (result) {
|
if (result) {
|
||||||
// 判断是否全部取消执行
|
// 判断是否全部取消执行
|
||||||
boolean exists = serviceRequestMapper.exists(new LambdaQueryWrapper<ServiceRequest>()
|
boolean exists = serviceRequestMapper.exists(new LambdaQueryWrapper<ServiceRequest>()
|
||||||
|
|||||||
@@ -52,7 +52,8 @@ public class PendingMedicationDetailsAppServiceImpl implements IPendingMedicatio
|
|||||||
Page<PendingMedicationPageDto> pendingMedicationPage = pendingMedicationDetailsMapper
|
Page<PendingMedicationPageDto> pendingMedicationPage = pendingMedicationDetailsMapper
|
||||||
.selectPendingMedicationDetailsPage(new Page<>(pageNo, pageSize), queryWrapper,
|
.selectPendingMedicationDetailsPage(new Page<>(pageNo, pageSize), queryWrapper,
|
||||||
DispenseStatus.IN_PROGRESS.getValue(), DispenseStatus.PREPARATION.getValue(),
|
DispenseStatus.IN_PROGRESS.getValue(), DispenseStatus.PREPARATION.getValue(),
|
||||||
DispenseStatus.PREPARED.getValue(), EncounterClass.AMB.getValue(), EncounterClass.IMP.getValue());
|
DispenseStatus.PREPARED.getValue(), DispenseStatus.SUMMARIZED.getValue(),
|
||||||
|
EncounterClass.AMB.getValue(), EncounterClass.IMP.getValue());
|
||||||
|
|
||||||
pendingMedicationPage.getRecords().forEach(e -> {
|
pendingMedicationPage.getRecords().forEach(e -> {
|
||||||
// 发药类型
|
// 发药类型
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ public interface PendingMedicationDetailsMapper {
|
|||||||
* @param inProgress 发药类型:待发药
|
* @param inProgress 发药类型:待发药
|
||||||
* @param preparation 发药类型:待配药
|
* @param preparation 发药类型:待配药
|
||||||
* @param prepared 发药类型:已配药
|
* @param prepared 发药类型:已配药
|
||||||
|
* @param summarized 发药类型:已汇总
|
||||||
* @param amb 门诊类型
|
* @param amb 门诊类型
|
||||||
* @param imp 住院类型
|
* @param imp 住院类型
|
||||||
* @return 待发药明细
|
* @return 待发药明细
|
||||||
@@ -32,6 +33,7 @@ public interface PendingMedicationDetailsMapper {
|
|||||||
@Param("inProgress") Integer inProgress,
|
@Param("inProgress") Integer inProgress,
|
||||||
@Param("preparation") Integer preparation,
|
@Param("preparation") Integer preparation,
|
||||||
@Param("prepared") Integer prepared,
|
@Param("prepared") Integer prepared,
|
||||||
|
@Param("summarized") Integer summarized,
|
||||||
@Param("amb") Integer amb,
|
@Param("amb") Integer amb,
|
||||||
@Param("imp") Integer imp);
|
@Param("imp") Integer imp);
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ import com.openhis.web.regdoctorstation.dto.*;
|
|||||||
import com.openhis.web.regdoctorstation.mapper.AdviceManageAppMapper;
|
import com.openhis.web.regdoctorstation.mapper.AdviceManageAppMapper;
|
||||||
import com.openhis.web.regdoctorstation.utils.RegPrescriptionUtils;
|
import com.openhis.web.regdoctorstation.utils.RegPrescriptionUtils;
|
||||||
import com.openhis.workflow.domain.DeviceRequest;
|
import com.openhis.workflow.domain.DeviceRequest;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
import com.openhis.workflow.domain.ServiceRequest;
|
import com.openhis.workflow.domain.ServiceRequest;
|
||||||
import com.openhis.workflow.service.IActivityDefinitionService;
|
import com.openhis.workflow.service.IActivityDefinitionService;
|
||||||
import com.openhis.workflow.domain.ActivityDefinition;
|
import com.openhis.workflow.domain.ActivityDefinition;
|
||||||
@@ -352,6 +355,27 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
|
|||||||
/**
|
/**
|
||||||
* 处理药品
|
* 处理药品
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 remark 合并到 contentJson 中,确保 Mapper 能从 content_json 提取 remark
|
||||||
|
*/
|
||||||
|
private String injectRemarkIntoContentJson(String contentJson, String remark) {
|
||||||
|
if (remark == null || remark.isEmpty() || contentJson == null || contentJson.isEmpty()) {
|
||||||
|
return contentJson;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
JsonNode node = mapper.readTree(contentJson);
|
||||||
|
if (node instanceof ObjectNode) {
|
||||||
|
((ObjectNode) node).put("remark", remark);
|
||||||
|
return mapper.writeValueAsString(node);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Failed to inject remark into contentJson: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
return contentJson;
|
||||||
|
}
|
||||||
|
|
||||||
private List<String> handMedication(List<RegAdviceSaveDto> medicineList, Date startTime, Date authoredTime,
|
private List<String> handMedication(List<RegAdviceSaveDto> medicineList, Date startTime, Date authoredTime,
|
||||||
Date curDate, String adviceOpType, Long organizationId, String signCode) {
|
Date curDate, String adviceOpType, Long organizationId, String signCode) {
|
||||||
// 当前登录账号的科室id
|
// 当前登录账号的科室id
|
||||||
@@ -450,6 +474,10 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
|
|||||||
if (longMedicationRequest.getId() == null) {
|
if (longMedicationRequest.getId() == null) {
|
||||||
firstTimeSave = true;
|
firstTimeSave = true;
|
||||||
}
|
}
|
||||||
|
// 确保 contentJson 包含 remark
|
||||||
|
if (regAdviceSaveDto.getRemark() != null && !regAdviceSaveDto.getRemark().isEmpty()) {
|
||||||
|
longMedicationRequest.setContentJson(injectRemarkIntoContentJson(longMedicationRequest.getContentJson(), regAdviceSaveDto.getRemark()));
|
||||||
|
}
|
||||||
iMedicationRequestService.saveOrUpdate(longMedicationRequest);
|
iMedicationRequestService.saveOrUpdate(longMedicationRequest);
|
||||||
if (firstTimeSave) {
|
if (firstTimeSave) {
|
||||||
medRequestIdList.add(longMedicationRequest.getId().toString());
|
medRequestIdList.add(longMedicationRequest.getId().toString());
|
||||||
@@ -537,6 +565,10 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
|
|||||||
if (tempMedicationRequest.getId() == null) {
|
if (tempMedicationRequest.getId() == null) {
|
||||||
firstTimeSave = true;
|
firstTimeSave = true;
|
||||||
}
|
}
|
||||||
|
// 确保 contentJson 包含 remark
|
||||||
|
if (regAdviceSaveDto.getRemark() != null && !regAdviceSaveDto.getRemark().isEmpty()) {
|
||||||
|
tempMedicationRequest.setContentJson(injectRemarkIntoContentJson(tempMedicationRequest.getContentJson(), regAdviceSaveDto.getRemark()));
|
||||||
|
}
|
||||||
iMedicationRequestService.saveOrUpdate(tempMedicationRequest);
|
iMedicationRequestService.saveOrUpdate(tempMedicationRequest);
|
||||||
if (firstTimeSave) {
|
if (firstTimeSave) {
|
||||||
medRequestIdList.add(tempMedicationRequest.getId().toString());
|
medRequestIdList.add(tempMedicationRequest.getId().toString());
|
||||||
@@ -640,6 +672,7 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
|
|||||||
longServiceRequest.setBasedOnTable(CommonConstants.TableName.MED_MEDICATION_REQUEST);
|
longServiceRequest.setBasedOnTable(CommonConstants.TableName.MED_MEDICATION_REQUEST);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
longServiceRequest.setRemark(regAdviceSaveDto.getRemark());
|
||||||
iServiceRequestService.saveOrUpdate(longServiceRequest);
|
iServiceRequestService.saveOrUpdate(longServiceRequest);
|
||||||
if (longServiceRequest.getId() != null) {
|
if (longServiceRequest.getId() != null) {
|
||||||
processedRequestIds.add(longServiceRequest.getId());
|
processedRequestIds.add(longServiceRequest.getId());
|
||||||
@@ -691,6 +724,7 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
|
|||||||
tempServiceRequest.setBasedOnTable(CommonConstants.TableName.MED_MEDICATION_REQUEST);
|
tempServiceRequest.setBasedOnTable(CommonConstants.TableName.MED_MEDICATION_REQUEST);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
tempServiceRequest.setRemark(regAdviceSaveDto.getRemark());
|
||||||
iServiceRequestService.saveOrUpdate(tempServiceRequest);
|
iServiceRequestService.saveOrUpdate(tempServiceRequest);
|
||||||
if (tempServiceRequest.getId() != null) {
|
if (tempServiceRequest.getId() != null) {
|
||||||
processedRequestIds.add(tempServiceRequest.getId());
|
processedRequestIds.add(tempServiceRequest.getId());
|
||||||
@@ -822,6 +856,10 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
|
|||||||
deviceRequest.setConditionId(regAdviceSaveDto.getConditionId()); // 诊断id
|
deviceRequest.setConditionId(regAdviceSaveDto.getConditionId()); // 诊断id
|
||||||
deviceRequest.setEncounterDiagnosisId(regAdviceSaveDto.getEncounterDiagnosisId()); // 就诊诊断id
|
deviceRequest.setEncounterDiagnosisId(regAdviceSaveDto.getEncounterDiagnosisId()); // 就诊诊断id
|
||||||
}
|
}
|
||||||
|
// 确保 contentJson 包含 remark
|
||||||
|
if (regAdviceSaveDto.getRemark() != null && !regAdviceSaveDto.getRemark().isEmpty()) {
|
||||||
|
deviceRequest.setContentJson(injectRemarkIntoContentJson(deviceRequest.getContentJson(), regAdviceSaveDto.getRemark()));
|
||||||
|
}
|
||||||
iDeviceRequestService.saveOrUpdate(deviceRequest);
|
iDeviceRequestService.saveOrUpdate(deviceRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -861,6 +899,10 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
|
|||||||
deviceRequest.setConditionId(regAdviceSaveDto.getConditionId()); // 诊断id
|
deviceRequest.setConditionId(regAdviceSaveDto.getConditionId()); // 诊断id
|
||||||
deviceRequest.setEncounterDiagnosisId(regAdviceSaveDto.getEncounterDiagnosisId()); // 就诊诊断id
|
deviceRequest.setEncounterDiagnosisId(regAdviceSaveDto.getEncounterDiagnosisId()); // 就诊诊断id
|
||||||
}
|
}
|
||||||
|
// 确保 contentJson 包含 remark
|
||||||
|
if (regAdviceSaveDto.getRemark() != null && !regAdviceSaveDto.getRemark().isEmpty()) {
|
||||||
|
deviceRequest.setContentJson(injectRemarkIntoContentJson(deviceRequest.getContentJson(), regAdviceSaveDto.getRemark()));
|
||||||
|
}
|
||||||
iDeviceRequestService.saveOrUpdate(deviceRequest);
|
iDeviceRequestService.saveOrUpdate(deviceRequest);
|
||||||
|
|
||||||
// 保存时,保存耗材费用项
|
// 保存时,保存耗材费用项
|
||||||
@@ -1017,7 +1059,7 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
|
|||||||
}
|
}
|
||||||
if (!medicineRequestIds.isEmpty()) {
|
if (!medicineRequestIds.isEmpty()) {
|
||||||
// 根据请求id更新请求状态
|
// 根据请求id更新请求状态
|
||||||
iMedicationRequestService.updateDraftStatusBatch(medicineRequestIds, null, null);
|
iMedicationRequestService.updateDraftStatusBatch(medicineRequestIds, null, null, null);
|
||||||
}
|
}
|
||||||
if (!activityRequestIds.isEmpty()) {
|
if (!activityRequestIds.isEmpty()) {
|
||||||
// 根据请求id更新请求状态
|
// 根据请求id更新请求状态
|
||||||
|
|||||||
@@ -50,4 +50,9 @@ public class RegRequestBaseDto extends RequestBaseDto {
|
|||||||
private String doseUnitCode;
|
private String doseUnitCode;
|
||||||
private String doseUnitCode_dictText;
|
private String doseUnitCode_dictText;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 备注(最长50字)
|
||||||
|
*/
|
||||||
|
private String remark;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -262,7 +262,7 @@
|
|||||||
AND T1.inventory_status_enum != 3
|
AND T1.inventory_status_enum != 3
|
||||||
AND T1.delete_flag = '0'
|
AND T1.delete_flag = '0'
|
||||||
<choose>
|
<choose>
|
||||||
<when test="lotNumber != null">
|
<when test="lotNumber != null and lotNumber != ''">
|
||||||
AND T1.lot_number = #{lotNumber}
|
AND T1.lot_number = #{lotNumber}
|
||||||
</when>
|
</when>
|
||||||
</choose>
|
</choose>
|
||||||
|
|||||||
@@ -516,6 +516,7 @@
|
|||||||
T1.patient_id AS patient_id,
|
T1.patient_id AS patient_id,
|
||||||
'med_medication_definition' AS advice_table_name,
|
'med_medication_definition' AS advice_table_name,
|
||||||
T1.medication_id AS advice_definition_id
|
T1.medication_id AS advice_definition_id
|
||||||
|
, T1.content_json::jsonb ->> 'remark' AS remark
|
||||||
, T1.back_reason AS reason_text
|
, T1.back_reason AS reason_text
|
||||||
FROM med_medication_request AS T1
|
FROM med_medication_request AS T1
|
||||||
LEFT JOIN med_medication_definition AS T2 ON T2.ID = T1.medication_id
|
LEFT JOIN med_medication_definition AS T2 ON T2.ID = T1.medication_id
|
||||||
@@ -578,6 +579,7 @@
|
|||||||
T1.patient_id AS patient_id,
|
T1.patient_id AS patient_id,
|
||||||
'med_medication_definition' AS advice_table_name,
|
'med_medication_definition' AS advice_table_name,
|
||||||
T3.ID AS advice_definition_id
|
T3.ID AS advice_definition_id
|
||||||
|
, T2.content_json::jsonb ->> 'remark' AS remark
|
||||||
, T2.back_reason AS reason_text
|
, T2.back_reason AS reason_text
|
||||||
FROM adm_charge_item AS T1
|
FROM adm_charge_item AS T1
|
||||||
INNER JOIN med_medication_request AS T2 ON T2.ID = T1.service_id AND T2.delete_flag = '0'
|
INNER JOIN med_medication_request AS T2 ON T2.ID = T1.service_id AND T2.delete_flag = '0'
|
||||||
@@ -642,6 +644,7 @@
|
|||||||
CI.patient_id AS patient_id,
|
CI.patient_id AS patient_id,
|
||||||
'adm_device_definition' AS advice_table_name,
|
'adm_device_definition' AS advice_table_name,
|
||||||
CI.product_id AS advice_definition_id
|
CI.product_id AS advice_definition_id
|
||||||
|
, NULL AS remark
|
||||||
, NULL AS reason_text
|
, NULL AS reason_text
|
||||||
FROM adm_charge_item AS CI
|
FROM adm_charge_item AS CI
|
||||||
LEFT JOIN adm_charge_item_definition CID ON CID.id = CI.definition_id AND CID.delete_flag = '0'
|
LEFT JOIN adm_charge_item_definition CID ON CID.id = CI.definition_id AND CID.delete_flag = '0'
|
||||||
@@ -697,6 +700,7 @@
|
|||||||
T1.patient_id AS patient_id,
|
T1.patient_id AS patient_id,
|
||||||
'adm_device_definition' AS advice_table_name,
|
'adm_device_definition' AS advice_table_name,
|
||||||
T1.device_def_id AS advice_definition_id
|
T1.device_def_id AS advice_definition_id
|
||||||
|
, T1.content_json::jsonb ->> 'remark' AS remark
|
||||||
, NULL AS reason_text
|
, NULL AS reason_text
|
||||||
FROM wor_device_request AS T1
|
FROM wor_device_request AS T1
|
||||||
LEFT JOIN adm_device_definition AS T2 ON T2.ID = T1.device_def_id
|
LEFT JOIN adm_device_definition AS T2 ON T2.ID = T1.device_def_id
|
||||||
@@ -753,7 +757,8 @@
|
|||||||
T1.encounter_id AS encounter_id,
|
T1.encounter_id AS encounter_id,
|
||||||
T1.patient_id AS patient_id,
|
T1.patient_id AS patient_id,
|
||||||
'wor_activity_definition' AS advice_table_name,
|
'wor_activity_definition' AS advice_table_name,
|
||||||
T1.activity_id AS advice_definition_id
|
T1.activity_id AS advice_definition_id,
|
||||||
|
T1.remark AS remark
|
||||||
, T1.reason_text AS reason_text
|
, T1.reason_text AS reason_text
|
||||||
FROM wor_service_request AS T1
|
FROM wor_service_request AS T1
|
||||||
LEFT JOIN wor_activity_definition AS T2
|
LEFT JOIN wor_activity_definition AS T2
|
||||||
@@ -933,4 +938,4 @@
|
|||||||
ORDER BY t1.ID, t1.name ASC, t2.ID ASC
|
ORDER BY t1.ID, t1.name ASC, t2.ID ASC
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|||||||
@@ -51,7 +51,7 @@
|
|||||||
ON T5.medication_def_id = T6.id
|
ON T5.medication_def_id = T6.id
|
||||||
AND T5.delete_flag = '0'
|
AND T5.delete_flag = '0'
|
||||||
WHERE T1.delete_flag = '0'
|
WHERE T1.delete_flag = '0'
|
||||||
AND T1.status_enum IN (#{inProgress}, #{preparation}, #{prepared})
|
AND T1.status_enum IN (#{inProgress}, #{preparation}, #{prepared}, #{summarized})
|
||||||
ORDER BY T1.create_time DESC
|
ORDER BY T1.create_time DESC
|
||||||
) AS T7
|
) AS T7
|
||||||
${ew.customSqlSegment}
|
${ew.customSqlSegment}
|
||||||
|
|||||||
@@ -214,11 +214,11 @@
|
|||||||
T1.dispense_per_duration AS dispense_per_duration,
|
T1.dispense_per_duration AS dispense_per_duration,
|
||||||
T2.part_percent AS part_percent,
|
T2.part_percent AS part_percent,
|
||||||
ccd.name AS condition_definition_name,
|
ccd.name AS condition_definition_name,
|
||||||
T1.effective_dose_start AS start_time,
|
|
||||||
T1.therapy_enum AS therapyEnum,
|
T1.therapy_enum AS therapyEnum,
|
||||||
T1.sort_number AS sort_number,
|
T1.sort_number AS sort_number,
|
||||||
|
T1.effective_dose_start AS start_time,
|
||||||
T1.based_on_id AS based_on_id,
|
T1.based_on_id AS based_on_id,
|
||||||
T1.medication_id AS advice_definition_id
|
T1.medication_id AS advice_definition_id,
|
||||||
T1.effective_dose_end AS stop_time,
|
T1.effective_dose_end AS stop_time,
|
||||||
T1.update_by AS stop_user_name
|
T1.update_by AS stop_user_name
|
||||||
FROM med_medication_request AS T1
|
FROM med_medication_request AS T1
|
||||||
@@ -274,8 +274,8 @@
|
|||||||
99 AS sort_number,
|
99 AS sort_number,
|
||||||
T1.req_authored_time AS start_time,
|
T1.req_authored_time AS start_time,
|
||||||
T1.based_on_id AS based_on_id,
|
T1.based_on_id AS based_on_id,
|
||||||
T1.device_def_id AS advice_definition_id
|
T1.device_def_id AS advice_definition_id,
|
||||||
NULL AS stop_time,
|
NULL::timestamp AS stop_time,
|
||||||
'' AS stop_user_name
|
'' AS stop_user_name
|
||||||
FROM wor_device_request AS T1
|
FROM wor_device_request AS T1
|
||||||
LEFT JOIN adm_device_definition AS T2 ON T2.ID = T1.device_def_id
|
LEFT JOIN adm_device_definition AS T2 ON T2.ID = T1.device_def_id
|
||||||
@@ -327,7 +327,7 @@
|
|||||||
99 AS sort_number,
|
99 AS sort_number,
|
||||||
T1.occurrence_start_time AS start_time,
|
T1.occurrence_start_time AS start_time,
|
||||||
T1.based_on_id AS based_on_id,
|
T1.based_on_id AS based_on_id,
|
||||||
T1.activity_id AS advice_definition_id
|
T1.activity_id AS advice_definition_id,
|
||||||
T1.occurrence_end_time AS stop_time,
|
T1.occurrence_end_time AS stop_time,
|
||||||
T1.update_by AS stop_user_name
|
T1.update_by AS stop_user_name
|
||||||
FROM wor_service_request AS T1
|
FROM wor_service_request AS T1
|
||||||
|
|||||||
@@ -78,9 +78,6 @@ public class MedicationRequestServiceImpl extends ServiceImpl<MedicationRequestM
|
|||||||
if (checkDate != null) {
|
if (checkDate != null) {
|
||||||
updateWrapper.set(MedicationRequest::getCheckTime, checkDate);
|
updateWrapper.set(MedicationRequest::getCheckTime, checkDate);
|
||||||
}
|
}
|
||||||
if (backReason != null) {
|
|
||||||
updateWrapper.set(MedicationRequest::getBackReason, backReason);
|
|
||||||
}
|
|
||||||
baseMapper.update(null, updateWrapper);
|
baseMapper.update(null, updateWrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -173,4 +173,9 @@ public class ServiceRequest extends HisBaseEntity {
|
|||||||
*/
|
*/
|
||||||
private Integer generateSourceEnum;
|
private Integer generateSourceEnum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 备注(最长50字)
|
||||||
|
*/
|
||||||
|
private String remark;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
AND T1.delete_flag = '0'
|
AND T1.delete_flag = '0'
|
||||||
AND T2.delete_flag = '0'
|
AND T2.delete_flag = '0'
|
||||||
AND T1.tenant_id = #{tenantId}
|
AND T1.tenant_id = #{tenantId}
|
||||||
|
LIMIT 1
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
77
openhis-ui-vue3/package-lock.json
generated
77
openhis-ui-vue3/package-lock.json
generated
@@ -65,7 +65,7 @@
|
|||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"unplugin-auto-import": "0.17.1",
|
"unplugin-auto-import": "0.17.1",
|
||||||
"unplugin-vue-setup-extend-plus": "1.0.0",
|
"unplugin-vue-setup-extend-plus": "1.0.0",
|
||||||
"vite": "^5.0.4",
|
"vite": "5.0.4",
|
||||||
"vite-plugin-compression": "0.5.1",
|
"vite-plugin-compression": "0.5.1",
|
||||||
"vite-plugin-svg-icons": "2.0.1",
|
"vite-plugin-svg-icons": "2.0.1",
|
||||||
"vite-plugin-vue-mcp": "^0.3.2",
|
"vite-plugin-vue-mcp": "^0.3.2",
|
||||||
@@ -3093,6 +3093,41 @@
|
|||||||
"url": "https://opencollective.com/vitest"
|
"url": "https://opencollective.com/vitest"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@vitest/mocker": {
|
||||||
|
"version": "4.1.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@vitest/mocker/-/mocker-4.1.2.tgz",
|
||||||
|
"integrity": "sha512-Ize4iQtEALHDttPRCmN+FKqOl2vxTiNUhzobQFFt/BM1lRUTG7zRCLOykG/6Vo4E4hnUdfVLo5/eqKPukcWW7Q==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@vitest/spy": "4.1.2",
|
||||||
|
"estree-walker": "^3.0.3",
|
||||||
|
"magic-string": "^0.30.21"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/vitest"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"msw": "^2.4.9",
|
||||||
|
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"msw": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"vite": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vitest/mocker/node_modules/estree-walker": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/estree": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@vitest/pretty-format": {
|
"node_modules/@vitest/pretty-format": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmmirror.com/@vitest/pretty-format/-/pretty-format-4.1.2.tgz",
|
"resolved": "https://registry.npmmirror.com/@vitest/pretty-format/-/pretty-format-4.1.2.tgz",
|
||||||
@@ -12636,10 +12671,9 @@
|
|||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "5.0.4",
|
"version": "5.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.0.4.tgz",
|
"resolved": "https://registry.npmmirror.com/vite/-/vite-5.0.4.tgz",
|
||||||
"integrity": "sha512-RzAr8LSvM8lmhB4tQ5OPcBhpjOZRZjuxv9zO5UcxeoY2bd3kP3Ticd40Qma9/BqZ8JS96Ll/jeBX9u+LJZrhVg==",
|
"integrity": "sha512-RzAr8LSvM8lmhB4tQ5OPcBhpjOZRZjuxv9zO5UcxeoY2bd3kP3Ticd40Qma9/BqZ8JS96Ll/jeBX9u+LJZrhVg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.19.3",
|
"esbuild": "^0.19.3",
|
||||||
"postcss": "^8.4.31",
|
"postcss": "^8.4.31",
|
||||||
@@ -13302,33 +13336,6 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vitest/node_modules/@vitest/mocker": {
|
|
||||||
"version": "4.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.2.tgz",
|
|
||||||
"integrity": "sha512-Ize4iQtEALHDttPRCmN+FKqOl2vxTiNUhzobQFFt/BM1lRUTG7zRCLOykG/6Vo4E4hnUdfVLo5/eqKPukcWW7Q==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@vitest/spy": "4.1.2",
|
|
||||||
"estree-walker": "^3.0.3",
|
|
||||||
"magic-string": "^0.30.21"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://opencollective.com/vitest"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"msw": "^2.4.9",
|
|
||||||
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"msw": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"vite": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/vitest/node_modules/chokidar": {
|
"node_modules/vitest/node_modules/chokidar": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||||
@@ -13391,16 +13398,6 @@
|
|||||||
"@esbuild/win32-x64": "0.27.7"
|
"@esbuild/win32-x64": "0.27.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vitest/node_modules/estree-walker": {
|
|
||||||
"version": "3.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
|
|
||||||
"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/estree": "^1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/vitest/node_modules/fsevents": {
|
"node_modules/vitest/node_modules/fsevents": {
|
||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
|
|||||||
@@ -83,11 +83,11 @@
|
|||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"unplugin-auto-import": "0.17.1",
|
"unplugin-auto-import": "0.17.1",
|
||||||
"unplugin-vue-setup-extend-plus": "1.0.0",
|
"unplugin-vue-setup-extend-plus": "1.0.0",
|
||||||
"vite": "^5.0.4",
|
"vite": "5.0.4",
|
||||||
"vite-plugin-compression": "0.5.1",
|
"vite-plugin-compression": "0.5.1",
|
||||||
"vite-plugin-svg-icons": "2.0.1",
|
"vite-plugin-svg-icons": "2.0.1",
|
||||||
"vite-plugin-vue-mcp": "^0.3.2",
|
"vite-plugin-vue-mcp": "^0.3.2",
|
||||||
"vitest": "^4.0.18",
|
"vitest": "^4.0.18",
|
||||||
"vue-tsc": "^3.1.8"
|
"vue-tsc": "^3.1.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -292,12 +292,6 @@ export const dynamicRoutes = [
|
|||||||
name: 'DoctorStation',
|
name: 'DoctorStation',
|
||||||
meta: {title: '医生工作站', icon: 'operation'},
|
meta: {title: '医生工作站', icon: 'operation'},
|
||||||
children: [
|
children: [
|
||||||
{
|
|
||||||
path: 'index',
|
|
||||||
component: () => import('@/views/doctorstation/index.vue'),
|
|
||||||
name: 'DoctorStationIndex',
|
|
||||||
meta: {title: '医生工作站', icon: 'operation'}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: 'pending-emr',
|
path: 'pending-emr',
|
||||||
component: () => import('@/views/doctorstation/pendingEmr.vue'),
|
component: () => import('@/views/doctorstation/pendingEmr.vue'),
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="exam-app-container"
|
class="exam-app-container"
|
||||||
style="width: 100%; max-width: 1200px;"
|
style="width: 100%; max-width: 1200px;"
|
||||||
@@ -723,60 +723,40 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="right-column">
|
<div class="right-column">
|
||||||
<!-- 右侧:已选择(检查项目、检查方法为两类独立选择结果) -->
|
<!-- 右侧:已选择(检查项目、检查方法为两类独立选择结果) -->
|
||||||
<div class="selected-panel">
|
<div class="selected-panel">
|
||||||
<div class="panel-label">
|
<div class="panel-label">已选择:</div>
|
||||||
已选择:
|
<div class="selected-tags">
|
||||||
</div>
|
<template v-if="selectedItems.length === 0 && selectedMethods.length === 0">
|
||||||
<div class="selected-tags">
|
<div class="empty-selected">–</div>
|
||||||
<template v-if="selectedItems.length === 0 && selectedMethods.length === 0">
|
</template>
|
||||||
<div class="empty-selected">
|
<template v-else>
|
||||||
–
|
<div
|
||||||
|
v-for="(item, idx) in selectedItems"
|
||||||
|
:key="'project-' + item.id"
|
||||||
|
class="selected-item-card"
|
||||||
|
:class="{ 'is-expanded': item.projectFoldExpanded }"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="fold-strip fold-strip-project"
|
||||||
|
:class="{ 'is-open': item.projectFoldExpanded }"
|
||||||
|
>
|
||||||
|
<div class="fold-strip-header" :class="{ 'no-chevron': !hasItemPackage(item) }" @click="hasItemPackage(item) && toggleProjectFold(item)">
|
||||||
|
<el-icon v-if="hasItemPackage(item)" :class="['fold-chevron', { open: item.projectFoldExpanded }]">
|
||||||
|
<ArrowDown />
|
||||||
|
</el-icon>
|
||||||
|
<div class="fold-header-main">
|
||||||
|
<span class="fold-kicker">检查项目</span>
|
||||||
|
<el-tooltip :content="getDisplayItemName(item)" placement="top" :show-after="400">
|
||||||
|
<span class="fold-title line-clamp-2">{{ getDisplayItemName(item) }}</span>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
<span class="fold-price-strong">¥{{ formatDetailAmount(item.price || 0) }}</span>
|
||||||
|
<el-button link type="danger" size="small" @click.stop="handleRemoveItem(idx, item)">
|
||||||
|
<el-icon><Close /></el-icon>
|
||||||
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
<!-- 仅当项目有套餐时展示明细区域,普通项目无明细可展示 -->
|
||||||
<template v-else>
|
|
||||||
<div
|
|
||||||
v-for="(item, idx) in selectedItems"
|
|
||||||
:key="'project-' + item.id"
|
|
||||||
class="selected-item-card"
|
|
||||||
:class="{ 'is-expanded': item.projectFoldExpanded }"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="fold-strip fold-strip-project"
|
|
||||||
:class="{ 'is-open': item.projectFoldExpanded }"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="fold-strip-header"
|
|
||||||
:class="{ 'no-chevron': !hasItemPackage(item) }"
|
|
||||||
@click="hasItemPackage(item) && toggleProjectFold(item)"
|
|
||||||
>
|
|
||||||
<el-icon
|
|
||||||
v-if="hasItemPackage(item)"
|
|
||||||
:class="['fold-chevron', { open: item.projectFoldExpanded }]"
|
|
||||||
>
|
|
||||||
<ArrowDown />
|
|
||||||
</el-icon>
|
|
||||||
<div class="fold-header-main">
|
|
||||||
<span class="fold-kicker">检查项目</span>
|
|
||||||
<el-tooltip
|
|
||||||
:content="getDisplayItemName(item)"
|
|
||||||
placement="top"
|
|
||||||
:show-after="400"
|
|
||||||
>
|
|
||||||
<span class="fold-title line-clamp-2">{{ getDisplayItemName(item) }}</span>
|
|
||||||
</el-tooltip>
|
|
||||||
</div>
|
|
||||||
<span class="fold-price-strong">¥{{ formatDetailAmount(item.price || 0) }}</span>
|
|
||||||
<el-button
|
|
||||||
link
|
|
||||||
type="danger"
|
|
||||||
size="small"
|
|
||||||
@click.stop="handleRemoveItem(idx, item)"
|
|
||||||
>
|
|
||||||
<el-icon><Close /></el-icon>
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
<!-- 仅当项目有套餐时展示明细区域,普通项目无明细可展示 -->
|
|
||||||
<div
|
<div
|
||||||
v-if="hasItemPackage(item) && item.projectFoldExpanded"
|
v-if="hasItemPackage(item) && item.projectFoldExpanded"
|
||||||
class="fold-strip-body"
|
class="fold-strip-body"
|
||||||
@@ -823,70 +803,47 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-for="(method, idx) in selectedMethods"
|
v-for="(method, idx) in selectedMethods"
|
||||||
:key="'method-' + method.id"
|
:key="'method-' + method.id"
|
||||||
class="selected-item-card"
|
class="selected-item-card"
|
||||||
:class="{ 'is-expanded': method.expanded }"
|
:class="{ 'is-expanded': method.expanded }"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="fold-strip fold-strip-method"
|
class="fold-strip fold-strip-method"
|
||||||
:class="{ 'is-open': method.expanded }"
|
:class="{ 'is-open': method.expanded }"
|
||||||
|
>
|
||||||
|
<div class="fold-strip-header" :class="{ 'no-chevron': !hasStandaloneMethodPackage(method) }" @click="hasStandaloneMethodPackage(method) && toggleSelectedMethodFold(method)">
|
||||||
|
<el-icon v-if="hasStandaloneMethodPackage(method)" :class="['fold-chevron', { open: method.expanded }]">
|
||||||
|
<ArrowDown />
|
||||||
|
</el-icon>
|
||||||
|
<div class="fold-header-main">
|
||||||
|
<span class="fold-kicker">检查方法</span>
|
||||||
|
<span
|
||||||
|
class="fold-title fold-title-plain line-clamp-2"
|
||||||
|
:title="getDisplayMethodName(method)"
|
||||||
|
>
|
||||||
|
{{ getDisplayMethodName(method) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
v-if="hasStandaloneMethodPackage(method)"
|
||||||
|
class="fold-price-strong warn"
|
||||||
>
|
>
|
||||||
<div
|
¥{{ formatDetailAmount(method.packagePrice || method.price || 0) }}
|
||||||
class="fold-strip-header"
|
</span>
|
||||||
:class="{ 'no-chevron': !hasStandaloneMethodPackage(method) }"
|
<el-button link type="danger" size="small" @click.stop="handleRemoveMethod(idx)">
|
||||||
@click="hasStandaloneMethodPackage(method) && toggleSelectedMethodFold(method)"
|
<el-icon><Close /></el-icon>
|
||||||
>
|
</el-button>
|
||||||
<el-icon
|
</div>
|
||||||
v-if="hasStandaloneMethodPackage(method)"
|
<!-- 仅当检查方法有套餐时展示明细 -->
|
||||||
:class="['fold-chevron', { open: method.expanded }]"
|
<div v-if="hasStandaloneMethodPackage(method) && method.expanded" class="fold-strip-body">
|
||||||
>
|
<div class="fold-package-wrap fold-method-package-wrap">
|
||||||
<ArrowDown />
|
<div v-if="method.packageLoading" class="package-details-loading">加载中...</div>
|
||||||
</el-icon>
|
<template v-else>
|
||||||
<div class="fold-header-main">
|
<div v-if="getStandaloneMethodPackageDetailsList(method).length === 0" class="package-details-empty">
|
||||||
<span class="fold-kicker">检查方法</span>
|
暂无检查方法套餐明细
|
||||||
<span
|
|
||||||
class="fold-title fold-title-plain line-clamp-2"
|
|
||||||
:title="getDisplayMethodName(method)"
|
|
||||||
>
|
|
||||||
{{ getDisplayMethodName(method) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<span
|
|
||||||
v-if="hasStandaloneMethodPackage(method)"
|
|
||||||
class="fold-price-strong warn"
|
|
||||||
>
|
|
||||||
¥{{ formatDetailAmount(method.packagePrice || method.price || 0) }}
|
|
||||||
</span>
|
|
||||||
<el-button
|
|
||||||
link
|
|
||||||
type="danger"
|
|
||||||
size="small"
|
|
||||||
@click.stop="handleRemoveMethod(idx)"
|
|
||||||
>
|
|
||||||
<el-icon><Close /></el-icon>
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
<!-- 仅当检查方法有套餐时展示明细 -->
|
|
||||||
<div
|
|
||||||
v-if="hasStandaloneMethodPackage(method) && method.expanded"
|
|
||||||
class="fold-strip-body"
|
|
||||||
>
|
|
||||||
<div class="fold-package-wrap fold-method-package-wrap">
|
|
||||||
<div
|
|
||||||
v-if="method.packageLoading"
|
|
||||||
class="package-details-loading"
|
|
||||||
>
|
|
||||||
加载中...
|
|
||||||
</div>
|
|
||||||
<template v-else>
|
|
||||||
<div
|
|
||||||
v-if="getStandaloneMethodPackageDetailsList(method).length === 0"
|
|
||||||
class="package-details-empty"
|
|
||||||
>
|
|
||||||
暂无检查方法套餐明细
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
class="package-details-list method-package-list"
|
class="package-details-list method-package-list"
|
||||||
@@ -909,53 +866,46 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 独立检查方法勾选区:与"已选择"区域解耦,支持分别手动勾选 -->
|
</div>
|
||||||
<div class="method-picker-section">
|
</div>
|
||||||
<div
|
|
||||||
v-if="methodsForActiveCategory.length > 0"
|
<!-- 独立检查方法勾选区:与"已选择"区域解耦,支持分别手动勾选 -->
|
||||||
class="selected-global-method-picker"
|
<div class="method-picker-section">
|
||||||
@click.stop
|
<div
|
||||||
>
|
v-if="methodsForActiveCategory.length > 0"
|
||||||
|
class="selected-global-method-picker"
|
||||||
|
@click.stop
|
||||||
|
>
|
||||||
|
<div class="method-picker-collapse-title" @click="methodPickerExpanded = !methodPickerExpanded">
|
||||||
|
<span class="method-picker-title-main">检查方法</span>
|
||||||
|
<span v-if="activeCategoryName" class="global-method-picker-scope">{{ activeCategoryName }}</span>
|
||||||
|
<el-icon :class="['method-picker-arrow', { expanded: methodPickerExpanded }]">
|
||||||
|
<ArrowDown />
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
<div v-show="methodPickerExpanded" class="global-method-picker-list">
|
||||||
<div
|
<div
|
||||||
class="method-picker-collapse-title"
|
v-for="method in methodsForActiveCategory"
|
||||||
@click="methodPickerExpanded = !methodPickerExpanded"
|
:key="'g-m-' + method.id"
|
||||||
|
class="item-row method-picker-row"
|
||||||
>
|
>
|
||||||
<span class="method-picker-title-main">检查方法</span>
|
<el-checkbox
|
||||||
<span
|
:model-value="isStandaloneMethodSelected(method)"
|
||||||
v-if="activeCategoryName"
|
@change="(val) => onStandaloneMethodChange(!!val, method)"
|
||||||
class="global-method-picker-scope"
|
class="item-checkbox"
|
||||||
>{{ activeCategoryName }}</span>
|
|
||||||
<el-icon :class="['method-picker-arrow', { expanded: methodPickerExpanded }]">
|
|
||||||
<ArrowDown />
|
|
||||||
</el-icon>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-show="methodPickerExpanded"
|
|
||||||
class="global-method-picker-list"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-for="method in methodsForActiveCategory"
|
|
||||||
:key="'g-m-' + method.id"
|
|
||||||
class="item-row method-picker-row"
|
|
||||||
>
|
>
|
||||||
<el-checkbox
|
<span class="method-label-inner">{{ formatExamMethodCaption(method.name) }}</span>
|
||||||
:model-value="isStandaloneMethodSelected(method)"
|
</el-checkbox>
|
||||||
class="item-checkbox"
|
<span class="item-price">¥{{ formatDetailAmount(method.packagePrice || method.price || 0) }}</span>
|
||||||
@change="(val) => onStandaloneMethodChange(!!val, method)"
|
|
||||||
>
|
|
||||||
<span class="method-label-inner">{{ formatExamMethodCaption(method.name) }}</span>
|
|
||||||
</el-checkbox>
|
|
||||||
<span class="item-price">¥{{ formatDetailAmount(method.packagePrice || method.price || 0) }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -963,7 +913,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
|||||||
@@ -1352,10 +1352,7 @@
|
|||||||
width="160"
|
width="160"
|
||||||
>
|
>
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<span
|
<span v-if="!scope.row.isEdit" style="color: #e6a23c;">
|
||||||
v-if="!scope.row.isEdit"
|
|
||||||
style="color: #e6a23c;"
|
|
||||||
>
|
|
||||||
{{ scope.row.reasonText || '-' }}
|
{{ scope.row.reasonText || '-' }}
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -443,7 +443,7 @@ import { nextTick } from 'vue';
|
|||||||
import { updatePatientInfo } from './components/store/patient.js';
|
import { updatePatientInfo } from './components/store/patient.js';
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||||
|
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
// // 监听路由离开事件
|
// // 监听路由离开事件
|
||||||
// onBeforeRouteLeave((to, from, next) => {
|
// onBeforeRouteLeave((to, from, next) => {
|
||||||
@@ -460,7 +460,6 @@ defineOptions({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
// 监听路由参数变化
|
// 监听路由参数变化
|
||||||
watch(
|
watch(
|
||||||
@@ -622,21 +621,6 @@ function getPatientList() {
|
|||||||
active: currentEncounterId.value ? item.encounterId == currentEncounterId.value : false,
|
active: currentEncounterId.value ? item.encounterId == currentEncounterId.value : false,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// Bug #626: 从待写病历页面跳转过来时,自动选中对应患者
|
|
||||||
const targetEncounterId = route.query.encounterId;
|
|
||||||
if (targetEncounterId && patientList.value.length > 0) {
|
|
||||||
const targetIndex = patientList.value.findIndex(
|
|
||||||
(item) => String(item.encounterId) === String(targetEncounterId)
|
|
||||||
);
|
|
||||||
if (targetIndex !== -1) {
|
|
||||||
handleCardClick(patientList.value[targetIndex], targetIndex);
|
|
||||||
}
|
|
||||||
// 清除URL参数,避免刷新时重复选中
|
|
||||||
if (route.query.encounterId) {
|
|
||||||
router.replace({ query: {} });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function setVisitType(type) {
|
function setVisitType(type) {
|
||||||
|
|||||||
@@ -129,79 +129,23 @@
|
|||||||
:total="total"
|
:total="total"
|
||||||
@pagination="getList"
|
@pagination="getList"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 患者详情弹窗 -->
|
|
||||||
<el-dialog
|
|
||||||
v-model="patientDetailVisible"
|
|
||||||
title="患者详情"
|
|
||||||
width="700px"
|
|
||||||
destroy-on-close
|
|
||||||
>
|
|
||||||
<el-descriptions
|
|
||||||
v-if="patientDetailData"
|
|
||||||
:column="2"
|
|
||||||
border
|
|
||||||
>
|
|
||||||
<el-descriptions-item label="患者姓名">
|
|
||||||
{{ patientDetailData.patientName || '-' }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="性别">
|
|
||||||
{{ getGenderText(patientDetailData.gender) }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="年龄">
|
|
||||||
{{ patientDetailData.age || '-' }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="病历号">
|
|
||||||
{{ patientDetailData.busNo || '-' }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="身份证号">
|
|
||||||
{{ patientDetailData.idCard || '-' }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="联系电话">
|
|
||||||
{{ patientDetailData.phone || '-' }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item
|
|
||||||
label="地址"
|
|
||||||
:span="2"
|
|
||||||
>
|
|
||||||
{{ patientDetailData.address || '-' }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="挂号时间">
|
|
||||||
{{ parseTime(patientDetailData.registerTime, '{y}-{m}-{d} {h}:{i}:{s}') }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="就诊科室">
|
|
||||||
{{ patientDetailData.organizationName || '-' }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
</el-descriptions>
|
|
||||||
<template #footer>
|
|
||||||
<el-button @click="patientDetailVisible = false">
|
|
||||||
关闭
|
|
||||||
</el-button>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, onMounted } from 'vue'
|
import { ref, reactive, onMounted } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { ElMessage } from 'element-plus'
|
import { listPendingEmr, getPendingEmrCount } from '@/views/doctorstation/components/api.js'
|
||||||
import { listPendingEmr, getPatientDetails } from '@/views/doctorstation/components/api.js'
|
|
||||||
import { parseTime } from '@/utils/index.js'
|
import { parseTime } from '@/utils/index.js'
|
||||||
import Pagination from '@/components/Pagination'
|
import Pagination from '@/components/Pagination'
|
||||||
import { Document, Refresh, Search, Delete } from '@element-plus/icons-vue'
|
import { Document, Refresh, Search, Delete } from '@element-plus/icons-vue'
|
||||||
|
import { ElDivider } from 'element-plus'
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
// 响应式数据
|
// 响应式数据
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const total = ref(0)
|
const total = ref(0)
|
||||||
const emrList = ref([])
|
const emrList = ref([])
|
||||||
|
|
||||||
// 患者详情弹窗
|
|
||||||
const patientDetailVisible = ref(false)
|
|
||||||
const patientDetailData = ref(null)
|
|
||||||
|
|
||||||
// 查询参数
|
// 查询参数
|
||||||
const queryParams = reactive({
|
const queryParams = reactive({
|
||||||
pageNum: 1,
|
pageNum: 1,
|
||||||
@@ -264,40 +208,17 @@ const handleRowClick = (row) => {
|
|||||||
console.log('点击行:', row)
|
console.log('点击行:', row)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 写病历 - 跳转到医生工作站并自动选中该患者
|
// 写病历
|
||||||
const handleWriteEmr = (row) => {
|
const handleWriteEmr = (row) => {
|
||||||
if (!row.encounterId) {
|
console.log('写病历:', row)
|
||||||
ElMessage.error('患者就诊信息不完整,无法写病历')
|
// 这里可以触发写病历事件
|
||||||
return
|
// 可能需要跳转到病历编辑页面
|
||||||
}
|
|
||||||
router.push({
|
|
||||||
path: '/doctorstation/index',
|
|
||||||
query: { encounterId: row.encounterId }
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查看患者 - 弹窗显示患者详情
|
// 查看患者
|
||||||
const handleViewPatient = async (row) => {
|
const handleViewPatient = (row) => {
|
||||||
if (!row.encounterId) {
|
console.log('查看患者:', row)
|
||||||
ElMessage.error('患者就诊信息不完整')
|
// 这里可以触发查看患者事件
|
||||||
return
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const response = await getPatientDetails(row.encounterId)
|
|
||||||
if (response.code === 200) {
|
|
||||||
patientDetailData.value = response.data || row
|
|
||||||
patientDetailVisible.value = true
|
|
||||||
} else {
|
|
||||||
// 接口失败时使用列表行数据展示
|
|
||||||
patientDetailData.value = row
|
|
||||||
patientDetailVisible.value = true
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取患者详情失败:', error)
|
|
||||||
// 接口异常时使用列表行数据展示
|
|
||||||
patientDetailData.value = row
|
|
||||||
patientDetailVisible.value = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取性别文本
|
// 获取性别文本
|
||||||
|
|||||||
@@ -506,21 +506,10 @@ function getInitOptions() {
|
|||||||
const wardPromise = getPractitionerWard();
|
const wardPromise = getPractitionerWard();
|
||||||
|
|
||||||
Promise.all([orgPromise, wardPromise]).then(([orgRes, wardRes]) => {
|
Promise.all([orgPromise, wardPromise]).then(([orgRes, wardRes]) => {
|
||||||
const allOrgs = orgRes.data.records.filter(
|
// 入院科室:展示所有 typeEnum=2(科室) + classEnum含"2"(住院) 的科室
|
||||||
|
organization.value = orgRes.data.records.filter(
|
||||||
(record) => record.typeEnum === 2 && checkClassEnumValue(record.classEnum, 2)
|
(record) => record.typeEnum === 2 && checkClassEnumValue(record.classEnum, 2)
|
||||||
);
|
);
|
||||||
const allWards = wardRes.data || [];
|
|
||||||
|
|
||||||
// 提取所有病区关联的科室ID
|
|
||||||
const linkedOrgIds = new Set();
|
|
||||||
allWards.forEach((ward) => {
|
|
||||||
if (ward.organizationId) {
|
|
||||||
linkedOrgIds.add(ward.organizationId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 过滤出与病区关联过的科室
|
|
||||||
organization.value = allOrgs.filter((org) => linkedOrgIds.has(org.id));
|
|
||||||
|
|
||||||
// Bug #178 Fix: 如果已选科室不在列表中,手动添加以确保正确显示
|
// Bug #178 Fix: 如果已选科室不在列表中,手动添加以确保正确显示
|
||||||
const selectedOrgId = props.inHospitalInfo?.inHospitalOrgId;
|
const selectedOrgId = props.inHospitalInfo?.inHospitalOrgId;
|
||||||
|
|||||||
@@ -148,27 +148,6 @@ export function saveTcmDiagnosis(data) {
|
|||||||
data: data,
|
data: data,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新中医诊断
|
|
||||||
*/
|
|
||||||
export function updateTcmDiagnosis(data) {
|
|
||||||
return request({
|
|
||||||
url: '/doctor-station/chinese-medical/update-tcm-diagnosis',
|
|
||||||
method: 'post',
|
|
||||||
data: data,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除中医诊断
|
|
||||||
*/
|
|
||||||
export function deleteTcmDiagnosis(syndromeGroupNo) {
|
|
||||||
return request({
|
|
||||||
url: '/doctor-station/chinese-medical/tcm-diagnosis?syndromeGroupNo=' + syndromeGroupNo,
|
|
||||||
method: 'delete',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* 获取人员慢性病诊断
|
* 获取人员慢性病诊断
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="dialogVisible"
|
v-model="props.openAddDiagnosisDialog"
|
||||||
:title="isUpdateMode ? '修改中医诊断' : '添加中医诊断'"
|
title="添加中医诊断"
|
||||||
width="1500px"
|
width="1500px"
|
||||||
append-to-body
|
append-to-body
|
||||||
destroy-on-close
|
destroy-on-close
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
<div class="search-box">
|
<div class="search-box">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="searchMiddleDisease"
|
v-model="searchMiddleDisease"
|
||||||
placeholder="搜索证候名称或编码"
|
placeholder="搜索疾病名称或编码"
|
||||||
clearable
|
clearable
|
||||||
>
|
>
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
@@ -131,8 +131,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { getTcmCondition, getTcmSyndrome, saveTcmDiagnosis, updateTcmDiagnosis, getTcmDiagnosis } from '../api';
|
import {getTcmCondition, getTcmSyndrome, saveTcmDiagnosis,} from '@/views/doctorstation/components/api';
|
||||||
import { computed } from 'vue';
|
import {computed} from 'vue';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
openAddDiagnosisDialog: {
|
openAddDiagnosisDialog: {
|
||||||
@@ -143,17 +143,13 @@ const props = defineProps({
|
|||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
updateZy: {
|
|
||||||
type: Array,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const conditionList = ref([]);
|
const conditionList = ref([]);
|
||||||
const syndromeList = ref([]);
|
const syndromeList = ref([]);
|
||||||
const tcmDiagonsisList = ref([]);
|
const tcmDiagonsisList = ref([]);
|
||||||
const tcmDiagonsisSaveList = ref([]);
|
const tcmDiagonsisSaveList = ref([]);
|
||||||
const syndromeSelected = ref(false);
|
const syndromeSelected = ref(false); // 当前诊断是否选择对应证候
|
||||||
const timestamp = ref('');
|
const timestamp = ref('');
|
||||||
const selectedDisease = ref(false);
|
const selectedDisease = ref(false);
|
||||||
const searchDisease = ref('');
|
const searchDisease = ref('');
|
||||||
@@ -161,70 +157,35 @@ const searchMiddleDisease = ref('');
|
|||||||
const { proxy } = getCurrentInstance();
|
const { proxy } = getCurrentInstance();
|
||||||
const emit = defineEmits(['close']);
|
const emit = defineEmits(['close']);
|
||||||
|
|
||||||
const dialogVisible = computed({
|
|
||||||
get: () => props.openAddDiagnosisDialog,
|
|
||||||
set: (val) => {
|
|
||||||
if (!val) {
|
|
||||||
emit('close');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const isUpdateMode = computed(() => {
|
|
||||||
return props.updateZy && props.updateZy.length > 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleOpen() {
|
function handleOpen() {
|
||||||
getTcmCondition().then((res) => {
|
getTcmCondition().then((res) => {
|
||||||
conditionList.value = res.data.records;
|
conditionList.value = res.data.records;
|
||||||
});
|
});
|
||||||
|
|
||||||
tcmDiagonsisSaveList.value = [];
|
|
||||||
tcmDiagonsisList.value = [];
|
|
||||||
syndromeSelected.value = true;
|
|
||||||
|
|
||||||
if (isUpdateMode.value) {
|
|
||||||
props.updateZy.forEach((item) => {
|
|
||||||
let updateIds = item.updateId ? item.updateId.split('-') : [];
|
|
||||||
let nameParts = item.name ? item.name.split('-') : [item.name || ''];
|
|
||||||
tcmDiagonsisSaveList.value.push({
|
|
||||||
conditionId: updateIds[0] || '',
|
|
||||||
definitionId: item.illnessDefinitionId || item.definitionId || '',
|
|
||||||
ybNo: item.ybNo,
|
|
||||||
syndromeGroupNo: item.syndromeGroupNo,
|
|
||||||
verificationStatusEnum: item.verificationStatusEnum || 4,
|
|
||||||
medTypeCode: item.medTypeCode,
|
|
||||||
});
|
|
||||||
tcmDiagonsisList.value.push({
|
|
||||||
conditionName: nameParts[0] || '',
|
|
||||||
syndromeName: nameParts[1] || '',
|
|
||||||
syndromeGroupNo: item.syndromeGroupNo,
|
|
||||||
illnessDefinitionId: item.illnessDefinitionId,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 搜索诊断
|
||||||
const conditionDatas = computed(() => {
|
const conditionDatas = computed(() => {
|
||||||
if (!searchDisease.value) {
|
|
||||||
return conditionList.value;
|
|
||||||
}
|
|
||||||
return conditionList.value.filter((item) => {
|
return conditionList.value.filter((item) => {
|
||||||
return item.name.includes(searchDisease.value) || item.ybNo.includes(searchDisease.value);
|
if (searchDisease.value) {
|
||||||
|
return searchDisease.value == item.name || searchDisease.value == item.ybNo;
|
||||||
|
}
|
||||||
|
return conditionList;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 后证
|
||||||
const syndromeListDatas = computed(() => {
|
const syndromeListDatas = computed(() => {
|
||||||
if (!searchMiddleDisease.value) {
|
|
||||||
return syndromeList.value;
|
|
||||||
}
|
|
||||||
return syndromeList.value.filter((item) => {
|
return syndromeList.value.filter((item) => {
|
||||||
return item.name.includes(searchMiddleDisease.value) || item.ybNo.includes(searchMiddleDisease.value);
|
if (searchMiddleDisease.value) {
|
||||||
|
return searchMiddleDisease.value == item.name || searchMiddleDisease.value == item.ybNo;
|
||||||
|
}
|
||||||
|
return syndromeList;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 点击诊断列表处理,点击以后才显示证候列表
|
||||||
function handleClickRow(row) {
|
function handleClickRow(row) {
|
||||||
if (syndromeSelected.value || tcmDiagonsisList.value.length === 0) {
|
if (syndromeSelected.value || tcmDiagonsisList.value == 0) {
|
||||||
selectedDisease.value = true;
|
selectedDisease.value = true;
|
||||||
syndromeSelected.value = false;
|
syndromeSelected.value = false;
|
||||||
timestamp.value = Date.now();
|
timestamp.value = Date.now();
|
||||||
@@ -236,7 +197,7 @@ function handleClickRow(row) {
|
|||||||
ybNo: row.ybNo,
|
ybNo: row.ybNo,
|
||||||
syndromeGroupNo: timestamp.value,
|
syndromeGroupNo: timestamp.value,
|
||||||
verificationStatusEnum: 4,
|
verificationStatusEnum: 4,
|
||||||
medTypeCode: undefined,
|
medTypeCode: undefined, // 不设默认值
|
||||||
});
|
});
|
||||||
tcmDiagonsisList.value.push({
|
tcmDiagonsisList.value.push({
|
||||||
conditionName: row.name,
|
conditionName: row.name,
|
||||||
@@ -255,6 +216,7 @@ function clickSyndromeRow(row) {
|
|||||||
syndromeSelected.value = true;
|
syndromeSelected.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 删除诊断
|
||||||
function removeDiagnosis(row, index) {
|
function removeDiagnosis(row, index) {
|
||||||
tcmDiagonsisList.value.splice(index, 1);
|
tcmDiagonsisList.value.splice(index, 1);
|
||||||
tcmDiagonsisSaveList.value = tcmDiagonsisSaveList.value.filter((item) => {
|
tcmDiagonsisSaveList.value = tcmDiagonsisSaveList.value.filter((item) => {
|
||||||
@@ -263,67 +225,77 @@ function removeDiagnosis(row, index) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function save() {
|
function save() {
|
||||||
const newDiagnosisList = tcmDiagonsisSaveList.value.filter((item) => !item.conditionId);
|
saveTcmDiagnosis({
|
||||||
|
patientId: props.patientInfo.patientId,
|
||||||
if (isUpdateMode.value) {
|
encounterId: props.patientInfo.encounterId,
|
||||||
updateTcmDiagnosis({
|
diagnosisChildList: tcmDiagonsisSaveList.value,
|
||||||
patientId: props.patientInfo.patientId,
|
}).then((res) => {
|
||||||
encounterId: props.patientInfo.encounterId,
|
if (res.code == 200) {
|
||||||
diagnosisChildList: tcmDiagonsisSaveList.value,
|
emit('close');
|
||||||
}).then((res) => {
|
proxy.$modal.msgSuccess('诊断已保存');
|
||||||
if (res.code == 200) {
|
}
|
||||||
if (newDiagnosisList.length > 0) {
|
});
|
||||||
saveTcmDiagnosis({
|
|
||||||
patientId: props.patientInfo.patientId,
|
|
||||||
encounterId: props.patientInfo.encounterId,
|
|
||||||
diagnosisChildList: newDiagnosisList,
|
|
||||||
}).then((res2) => {
|
|
||||||
if (res2.code == 200) {
|
|
||||||
emit('close');
|
|
||||||
proxy.$modal.msgSuccess('诊断已保存');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
emit('close');
|
|
||||||
proxy.$modal.msgSuccess('诊断已保存');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
saveTcmDiagnosis({
|
|
||||||
patientId: props.patientInfo.patientId,
|
|
||||||
encounterId: props.patientInfo.encounterId,
|
|
||||||
diagnosisChildList: tcmDiagonsisSaveList.value,
|
|
||||||
}).then((res) => {
|
|
||||||
if (res.code == 200) {
|
|
||||||
emit('close');
|
|
||||||
proxy.$modal.msgSuccess('诊断已保存');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function submit() {
|
function submit() {
|
||||||
const hasNewDiagnosis = tcmDiagonsisSaveList.value.some((item) => !item.conditionId);
|
if (tcmDiagonsisSaveList.value.length > 0 && syndromeSelected.value) {
|
||||||
|
|
||||||
if (!hasNewDiagnosis && isUpdateMode.value) {
|
|
||||||
emit('close');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (syndromeSelected.value || tcmDiagonsisSaveList.value.length % 2 === 0) {
|
|
||||||
save();
|
save();
|
||||||
} else {
|
} else {
|
||||||
proxy.$modal.msgWarning('请选择证候');
|
proxy.$modal.msgWarning('请选择证候');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function close() {
|
function close() {
|
||||||
emit('close');
|
emit('close');
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
:deep(.pagination-container .el-pagination) {
|
||||||
|
right: 20px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-container {
|
||||||
|
max-width: 1400px;
|
||||||
|
margin: 20px auto;
|
||||||
|
padding: 20px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 2px 20px rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
border-bottom: 1px solid var(--el-border-color);
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h1 {
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.patient-info {
|
||||||
|
background: var(--el-color-primary-light-9);
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.patient-info .info-row {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.patient-info .info-label {
|
||||||
|
width: 100px;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
.main-content {
|
.main-content {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr 1.2fr;
|
grid-template-columns: 1fr 1fr 1.2fr;
|
||||||
@@ -350,13 +322,125 @@ function close() {
|
|||||||
border-bottom: 1px solid var(--el-border-color);
|
border-bottom: 1px solid var(--el-border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.disease-list {
|
||||||
|
max-height: 400px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disease-item {
|
||||||
|
padding: 12px 15px;
|
||||||
|
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disease-item:hover {
|
||||||
|
background-color: var(--el-color-primary-light-9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.disease-item.active {
|
||||||
|
background-color: var(--el-color-primary-light-8);
|
||||||
|
border-left: 3px solid var(--el-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.disease-name {
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disease-code {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
.search-box {
|
.search-box {
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.diagnosis-list {
|
.disease-categories {
|
||||||
max-height: 520px;
|
display: flex;
|
||||||
overflow-y: auto;
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-tag {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 5px 12px;
|
||||||
|
border-radius: 15px;
|
||||||
|
background: var(--el-fill-color-light);
|
||||||
|
font-size: 13px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-tag.active {
|
||||||
|
background: var(--el-color-primary);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.relation-container {
|
||||||
|
text-align: center;
|
||||||
|
padding: 30px 0;
|
||||||
|
border: 2px dashed var(--el-border-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
margin: 20px 0;
|
||||||
|
background: var(--el-fill-color-lighter);
|
||||||
|
}
|
||||||
|
|
||||||
|
.relation-icon {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.relation-text {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.syndrome-details {
|
||||||
|
padding: 15px;
|
||||||
|
background: var(--el-color-primary-light-9);
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--el-color-primary-light-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-item {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-label {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
margin-bottom: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 15px;
|
||||||
|
padding-top: 20px;
|
||||||
|
border-top: 1px solid var(--el-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
text-align: center;
|
||||||
|
padding: 40px 0;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.diagnosis-history {
|
||||||
|
margin-top: 20px;
|
||||||
|
border-top: 1px solid var(--el-border-color);
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.history-item {
|
.history-item {
|
||||||
@@ -367,6 +451,17 @@ function close() {
|
|||||||
border-radius: 0 4px 4px 0;
|
border-radius: 0 4px 4px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.diagnosis-list {
|
||||||
|
max-height: 520px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-date {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.history-diagnosis {
|
.history-diagnosis {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -374,9 +469,16 @@ function close() {
|
|||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-state {
|
.history-note {
|
||||||
text-align: center;
|
font-size: 13px;
|
||||||
padding: 40px 0;
|
|
||||||
color: var(--el-text-color-secondary);
|
color: var(--el-text-color-secondary);
|
||||||
|
padding-top: 5px;
|
||||||
|
border-top: 1px dashed var(--el-border-color);
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-list {
|
||||||
|
padding: 20px 0;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,60 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="visible"
|
v-model="visible"
|
||||||
title="中医诊断"
|
top="6vh"
|
||||||
:width="width"
|
:width="width"
|
||||||
|
title="中医诊断"
|
||||||
:z-index="20"
|
:z-index="20"
|
||||||
append-to-body
|
|
||||||
destroy-on-close
|
|
||||||
@open="openAct"
|
@open="openAct"
|
||||||
@closed="closedAct"
|
@closed="closedAct"
|
||||||
>
|
>
|
||||||
<el-form
|
中医诊断
|
||||||
ref="formRef"
|
|
||||||
:model="formData"
|
|
||||||
:rules="rules"
|
|
||||||
label-width="100px"
|
|
||||||
>
|
|
||||||
<el-form-item
|
|
||||||
label="中医诊断"
|
|
||||||
prop="conditionCode"
|
|
||||||
>
|
|
||||||
<el-select
|
|
||||||
v-model="formData.conditionCode"
|
|
||||||
placeholder="请选择中医诊断"
|
|
||||||
filterable
|
|
||||||
clearable
|
|
||||||
style="width: 100%"
|
|
||||||
@change="handleConditionChange"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="item in conditionOptions"
|
|
||||||
:key="item.value"
|
|
||||||
:label="item.label"
|
|
||||||
:value="item.value"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item
|
|
||||||
label="中医证候"
|
|
||||||
prop="syndromeCode"
|
|
||||||
>
|
|
||||||
<el-select
|
|
||||||
v-model="formData.syndromeCode"
|
|
||||||
placeholder="请选择中医证候"
|
|
||||||
filterable
|
|
||||||
clearable
|
|
||||||
style="width: 100%"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="item in syndromeOptions"
|
|
||||||
:key="item.value"
|
|
||||||
:label="item.label"
|
|
||||||
:value="item.value"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button
|
<el-button
|
||||||
size="fixed"
|
size="fixed"
|
||||||
@@ -66,120 +20,122 @@
|
|||||||
<el-button
|
<el-button
|
||||||
size="fixed"
|
size="fixed"
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="handleSubmit"
|
@click="handleSubmit(signFormRef)"
|
||||||
>
|
>
|
||||||
保存
|
保存
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import {onMounted, reactive, ref} from 'vue'
|
import {onMounted, reactive, ref} from 'vue'
|
||||||
import { ElMessage } from 'element-plus'
|
import {dayjs} from 'element-plus'
|
||||||
import { getTcmCondition, getTcmSyndrome, saveTcmDiagnosis } from '../api'
|
// import { IInPatient } from '@/model/IInPatient'
|
||||||
|
|
||||||
const { proxy } = getCurrentInstance()
|
const currentInPatient = ref({})
|
||||||
|
const initCurrentInPatient = () => {
|
||||||
|
currentInPatient.value = {
|
||||||
|
feeType: '08',
|
||||||
|
sexName: '男',
|
||||||
|
age: '0',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* 初始化数据 */
|
||||||
|
const init = () => {
|
||||||
|
initCurrentInPatient()
|
||||||
|
}
|
||||||
|
|
||||||
const conditionOptions = ref([])
|
/* 入科 */
|
||||||
const syndromeOptions = ref([])
|
const signForm = ref({
|
||||||
|
visitCode: '', // 就诊流水号
|
||||||
const formData = ref({
|
height: 0, // 身高
|
||||||
conditionCode: '',
|
weight: 0, // 体重
|
||||||
syndromeCode: '',
|
temperature: 0, // 体温
|
||||||
|
hertRate: 0, // 心率
|
||||||
|
pulse: 0, // 脉搏
|
||||||
|
highBloodPressure: 0, // 收缩压
|
||||||
|
endBloodPressure: 0, // 舒张压
|
||||||
|
loginDeptCode: '', // 当前登录科室
|
||||||
|
bingqing: '', //患者病情
|
||||||
|
inDeptDate: dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss'), //入院时间
|
||||||
|
signsId: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
const rules = reactive({
|
const rules = reactive({
|
||||||
conditionCode: [{ required: true, message: '请选择中医诊断', trigger: ['blur', 'change'] }],
|
admittedDoctor: [{ required: true, message: '请选择住院医生', trigger: ['blur', 'change'] }],
|
||||||
syndromeCode: [{ required: true, message: '请选择中医证候', trigger: ['blur', 'change'] }],
|
masterNurse: [{ required: true, message: '请选择责任护士', trigger: ['blur', 'change'] }],
|
||||||
})
|
})
|
||||||
|
const printWristband = ref(false)
|
||||||
|
const emits = defineEmits(['okAct'])
|
||||||
|
|
||||||
const props = defineProps({
|
const visible = defineModel('visible')
|
||||||
patientInfo: {
|
const width = '920px'
|
||||||
type: Object,
|
|
||||||
default: () => ({}),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const emit = defineEmits(['ok-act'])
|
|
||||||
|
|
||||||
const visible = defineModel<boolean>('visible')
|
|
||||||
const width = '500px'
|
|
||||||
|
|
||||||
|
/* 取消 */
|
||||||
const cancelAct = () => {
|
const cancelAct = () => {
|
||||||
visible.value = false
|
visible.value = false
|
||||||
}
|
}
|
||||||
|
/* 录入患者体征*/
|
||||||
function handleConditionChange() {
|
const signFormRef = ref()
|
||||||
formData.value.syndromeCode = ''
|
const handleSubmit = async (formEl) => {
|
||||||
loadSyndromeOptions(formData.value.conditionCode)
|
if (!formEl) return
|
||||||
}
|
await formEl.validate((valid, fields) => {
|
||||||
|
|
||||||
function loadConditionOptions() {
|
|
||||||
getTcmCondition().then((res) => {
|
|
||||||
if (res.data && res.data.records) {
|
|
||||||
conditionOptions.value = res.data.records.map((item) => ({
|
|
||||||
value: item.ybNo,
|
|
||||||
label: item.name,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadSyndromeOptions(conditionCode) {
|
|
||||||
const params = conditionCode ? { conditionCode } : {}
|
|
||||||
getTcmSyndrome(params).then((res) => {
|
|
||||||
if (res.data && res.data.records) {
|
|
||||||
syndromeOptions.value = res.data.records.map((item) => ({
|
|
||||||
value: item.ybNo,
|
|
||||||
label: item.name,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const formRef = ref()
|
|
||||||
const handleSubmit = async () => {
|
|
||||||
if (!formRef.value) return
|
|
||||||
await formRef.value.validate((valid) => {
|
|
||||||
if (valid) {
|
if (valid) {
|
||||||
const submitData = {
|
console.log('submit!')
|
||||||
conditionCode: formData.value.conditionCode,
|
try {
|
||||||
syndromeCode: formData.value.syndromeCode,
|
// 录入患者体征方法(signForm.value).then((res: any) => {
|
||||||
|
// ElMessage({
|
||||||
|
// message: '登记成功!',
|
||||||
|
// type: 'success',
|
||||||
|
// grouping: true,
|
||||||
|
// showClose: true,
|
||||||
|
// })
|
||||||
|
// emits('okAct')
|
||||||
|
// })
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
}
|
}
|
||||||
if (props.patientInfo && props.patientInfo.patientId) {
|
|
||||||
submitData.patientId = props.patientInfo.patientId
|
|
||||||
submitData.encounterId = props.patientInfo.encounterId
|
|
||||||
}
|
|
||||||
submitData.diagnosisChildList = [{
|
|
||||||
conditionCode: formData.value.conditionCode,
|
|
||||||
syndromeCode: formData.value.syndromeCode,
|
|
||||||
}]
|
|
||||||
saveTcmDiagnosis(submitData).then((res) => {
|
|
||||||
if (res.code === 200) {
|
|
||||||
ElMessage.success('中医诊断保存成功')
|
|
||||||
emit('ok-act')
|
|
||||||
cancelAct()
|
|
||||||
} else {
|
|
||||||
ElMessage.error(res.msg || '保存失败')
|
|
||||||
}
|
|
||||||
}).catch(() => {
|
|
||||||
ElMessage.error('保存失败,请重试')
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const openAct = () => {
|
const openAct = () => {
|
||||||
formData.value = { conditionCode: '', syndromeCode: '' }
|
init()
|
||||||
loadConditionOptions()
|
|
||||||
loadSyndromeOptions()
|
|
||||||
}
|
}
|
||||||
const closedAct = () => {
|
const closedAct = () => {
|
||||||
visible.value = false
|
visible.value = false
|
||||||
}
|
}
|
||||||
onMounted(() => {
|
onMounted(() => {})
|
||||||
loadConditionOptions()
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
.transferIn-container {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.admission-signs,
|
||||||
|
.admission-information {
|
||||||
|
width: 888px;
|
||||||
|
.unit {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 10px;
|
||||||
|
color: #bbb;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: '思源黑体 CN';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-wriBtn {
|
||||||
|
margin-left: 565px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-p100 {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-80 {
|
||||||
|
width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mb-90 {
|
||||||
|
margin-bottom: 90px !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -46,6 +46,11 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
<el-popconfirm
|
<el-popconfirm
|
||||||
|
v-if="
|
||||||
|
node.level === 2 &&
|
||||||
|
node.parent.data.name != '常用' &&
|
||||||
|
node.parent.data.name != '历史'
|
||||||
|
"
|
||||||
width="200"
|
width="200"
|
||||||
:hide-after="10"
|
:hide-after="10"
|
||||||
title="确认删除此常用诊断吗"
|
title="确认删除此常用诊断吗"
|
||||||
@@ -54,11 +59,6 @@
|
|||||||
>
|
>
|
||||||
<template #reference>
|
<template #reference>
|
||||||
<el-button
|
<el-button
|
||||||
v-if="
|
|
||||||
node.level === 2 &&
|
|
||||||
node.parent.data.name != '常用' &&
|
|
||||||
node.parent.data.name != '历史'
|
|
||||||
"
|
|
||||||
style="color: #000000"
|
style="color: #000000"
|
||||||
type="text"
|
type="text"
|
||||||
size="small"
|
size="small"
|
||||||
@@ -94,7 +94,7 @@
|
|||||||
>
|
>
|
||||||
保存诊断
|
保存诊断
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button type="primary" plain @click="handleAddTcmDiagonsis()"> 中医诊断 </el-button>
|
<!-- <el-button type="primary" plain @click="handleAddTcmDiagonsis()"> 中医诊断 </el-button> -->
|
||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
plain
|
plain
|
||||||
@@ -142,34 +142,6 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
|
||||||
label="诊断体系"
|
|
||||||
align="center"
|
|
||||||
prop="diagnosisSystem"
|
|
||||||
width="120"
|
|
||||||
>
|
|
||||||
<template #default="scope">
|
|
||||||
<el-form-item
|
|
||||||
:prop="`diagnosisList.${scope.$index}.diagnosisSystem`"
|
|
||||||
>
|
|
||||||
<el-select
|
|
||||||
v-model="scope.row.diagnosisSystem"
|
|
||||||
placeholder=" "
|
|
||||||
style="width: 100px"
|
|
||||||
@change="(value) => handleDiagnosisSystemChange(scope.row, value)"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
label="西医"
|
|
||||||
value="西医"
|
|
||||||
/>
|
|
||||||
<el-option
|
|
||||||
label="中医"
|
|
||||||
value="中医"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
<el-table-column
|
||||||
label="诊断类别"
|
label="诊断类别"
|
||||||
align="center"
|
align="center"
|
||||||
@@ -187,7 +159,7 @@
|
|||||||
style="width: 150px"
|
style="width: 150px"
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in diag_type"
|
v-for="item in med_type"
|
||||||
:key="item.value"
|
:key="item.value"
|
||||||
:label="item.label"
|
:label="item.label"
|
||||||
:value="item.value"
|
:value="item.value"
|
||||||
@@ -215,7 +187,6 @@
|
|||||||
>
|
>
|
||||||
<diagnosislist
|
<diagnosislist
|
||||||
:diagnosis-searchkey="diagnosisSearchkey"
|
:diagnosis-searchkey="diagnosisSearchkey"
|
||||||
:diagnosis-system="scope.row.diagnosisSystem"
|
|
||||||
@select-diagnosis="handleSelsectDiagnosis"
|
@select-diagnosis="handleSelsectDiagnosis"
|
||||||
/>
|
/>
|
||||||
<template #reference>
|
<template #reference>
|
||||||
@@ -238,35 +209,6 @@
|
|||||||
prop="diagnosisDoctor"
|
prop="diagnosisDoctor"
|
||||||
width="120"
|
width="120"
|
||||||
/>
|
/>
|
||||||
<el-table-column
|
|
||||||
label="中医证候"
|
|
||||||
align="center"
|
|
||||||
prop="tcmSyndromeName"
|
|
||||||
width="150"
|
|
||||||
>
|
|
||||||
<template #default="scope">
|
|
||||||
<template v-if="scope.row.diagnosisSystem === '中医'">
|
|
||||||
<span style="color: #f56c6c; margin-right: 2px;">*</span><el-select
|
|
||||||
v-model="scope.row.tcmSyndromeCode"
|
|
||||||
placeholder="请选择证候"
|
|
||||||
filterable
|
|
||||||
clearable
|
|
||||||
style="width: 130px"
|
|
||||||
@change="(value) => handleTcmSyndromeChange(scope.row, value)"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="item in tcmSyndromeOptions"
|
|
||||||
:key="item.value"
|
|
||||||
:label="item.label"
|
|
||||||
:value="item.value"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<span style="color: #c0c4cc;">-</span>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
<el-table-column
|
||||||
label="诊断时间"
|
label="诊断时间"
|
||||||
align="center"
|
align="center"
|
||||||
@@ -339,7 +281,6 @@
|
|||||||
<AddDiagnosisDialog
|
<AddDiagnosisDialog
|
||||||
:open-add-diagnosis-dialog="openAddDiagnosisDialog"
|
:open-add-diagnosis-dialog="openAddDiagnosisDialog"
|
||||||
:patient-info="props.patientInfo"
|
:patient-info="props.patientInfo"
|
||||||
:update-zy="tcmDiagnosisListForEdit"
|
|
||||||
@close="closeDiagnosisDialog"
|
@close="closeDiagnosisDialog"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -357,13 +298,10 @@ import {
|
|||||||
getEmrDetail,
|
getEmrDetail,
|
||||||
getEncounterDiagnosis,
|
getEncounterDiagnosis,
|
||||||
getTcmDiagnosis,
|
getTcmDiagnosis,
|
||||||
getTcmSyndrome,
|
|
||||||
isFoodDiseasesNew,
|
isFoodDiseasesNew,
|
||||||
saveDiagnosis,
|
saveDiagnosis,
|
||||||
saveTcmDiagnosis,
|
|
||||||
deleteTcmDiagnosis,
|
|
||||||
} from '../api';
|
} from '../api';
|
||||||
|
import {deleteTcmDiagnosis} from '@/views/doctorstation/components/api.js';
|
||||||
import diagnosisdialog from '../diagnosis/diagnosisdialog.vue';
|
import diagnosisdialog from '../diagnosis/diagnosisdialog.vue';
|
||||||
import AddDiagnosisDialog from './addDiagnosisDialog.vue';
|
import AddDiagnosisDialog from './addDiagnosisDialog.vue';
|
||||||
import diagnosislist from '../diagnosis/diagnosislist.vue';
|
import diagnosislist from '../diagnosis/diagnosislist.vue';
|
||||||
@@ -377,7 +315,6 @@ const openDiagnosis = ref(false);
|
|||||||
const openAddDiagnosisDialog = ref(false);
|
const openAddDiagnosisDialog = ref(false);
|
||||||
const diagnosisSearchkey = ref('');
|
const diagnosisSearchkey = ref('');
|
||||||
const diagnosisOptions = ref([]);
|
const diagnosisOptions = ref([]);
|
||||||
const tcmSyndromeOptions = ref([]);
|
|
||||||
const rowIndex = ref();
|
const rowIndex = ref();
|
||||||
const diagnosis = ref();
|
const diagnosis = ref();
|
||||||
const orgOrUser = ref();
|
const orgOrUser = ref();
|
||||||
@@ -394,15 +331,13 @@ const props = defineProps({
|
|||||||
const emits = defineEmits(['diagnosisSave']);
|
const emits = defineEmits(['diagnosisSave']);
|
||||||
const { proxy } = getCurrentInstance();
|
const { proxy } = getCurrentInstance();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
// 获取诊断类型字典(住院诊断类别)
|
const { med_type } = proxy.useDict('med_type');
|
||||||
const { diag_type } = proxy.useDict('diag_type');
|
|
||||||
const rules = ref({
|
const rules = ref({
|
||||||
name: [{ required: true, message: '请选择诊断', trigger: 'change' }],
|
name: [{ required: true, message: '请选择诊断', trigger: 'change' }],
|
||||||
medTypeCode: [{ required: true, message: '请选择诊断类型', trigger: 'change' }],
|
medTypeCode: [{ required: true, message: '请选择诊断类型', trigger: 'change' }],
|
||||||
diagSrtNo: [{ required: true, message: '请输入诊断序号', trigger: 'change' }],
|
diagSrtNo: [{ required: true, message: '请输入诊断序号', trigger: 'change' }],
|
||||||
});
|
});
|
||||||
const diagnosisNetDatas = ref([]);
|
const diagnosisNetDatas = ref([]);
|
||||||
const tcmDiagnosisListForEdit = ref([]);
|
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => form.value.diagnosisList,
|
() => form.value.diagnosisList,
|
||||||
@@ -469,11 +404,7 @@ function getList() {
|
|||||||
...item,
|
...item,
|
||||||
};
|
};
|
||||||
if (obj.diagSrtNo == null) {
|
if (obj.diagSrtNo == null) {
|
||||||
obj.diagSrtNo = '1';
|
obj.diagSrtNo = 1;
|
||||||
}
|
|
||||||
// 确保 diagnosisSystem 字段存在,默认为西医
|
|
||||||
if (!obj.diagnosisSystem) {
|
|
||||||
obj.diagnosisSystem = '西医';
|
|
||||||
}
|
}
|
||||||
return obj;
|
return obj;
|
||||||
});
|
});
|
||||||
@@ -490,12 +421,9 @@ function getList() {
|
|||||||
diagnosisNetDatas.value = res.data.illness;
|
diagnosisNetDatas.value = res.data.illness;
|
||||||
res.data.illness.forEach((item, index) => {
|
res.data.illness.forEach((item, index) => {
|
||||||
newList.push({
|
newList.push({
|
||||||
name: item.name,
|
name: item.name + '-' + (res.data.symptom[index]?.name || ''),
|
||||||
ybNo: item.ybNo,
|
ybNo: item.ybNo,
|
||||||
medTypeCode: item.medTypeCode,
|
medTypeCode: item.medTypeCode,
|
||||||
diagnosisSystem: '中医',
|
|
||||||
tcmSyndromeCode: res.data.symptom[index]?.ybNo || '',
|
|
||||||
tcmSyndromeName: res.data.symptom[index]?.name || '',
|
|
||||||
diagnosisDoctor: props.patientInfo.practitionerName || props.patientInfo.doctorName || props.patientInfo.physicianName || userStore.name,
|
diagnosisDoctor: props.patientInfo.practitionerName || props.patientInfo.doctorName || props.patientInfo.physicianName || userStore.name,
|
||||||
diagnosisTime: new Date().toLocaleString('zh-CN')
|
diagnosisTime: new Date().toLocaleString('zh-CN')
|
||||||
});
|
});
|
||||||
@@ -520,7 +448,6 @@ function getList() {
|
|||||||
|
|
||||||
init();
|
init();
|
||||||
function init() {
|
function init() {
|
||||||
loadTcmSyndromeOptions();
|
|
||||||
diagnosisInit().then((res) => {
|
diagnosisInit().then((res) => {
|
||||||
if (res.code == 200) {
|
if (res.code == 200) {
|
||||||
diagnosisOptions.value = res.data.verificationStatusOptions;
|
diagnosisOptions.value = res.data.verificationStatusOptions;
|
||||||
@@ -668,7 +595,6 @@ function addDiagnosisItem() {
|
|||||||
name: undefined,
|
name: undefined,
|
||||||
verificationStatusEnum: 4,
|
verificationStatusEnum: 4,
|
||||||
medTypeCode: undefined,
|
medTypeCode: undefined,
|
||||||
diagnosisSystem: '西医',
|
|
||||||
diagSrtNo: form.value.diagnosisList.length + 1,
|
diagSrtNo: form.value.diagnosisList.length + 1,
|
||||||
iptDiseTypeCode: 2,
|
iptDiseTypeCode: 2,
|
||||||
diagnosisDesc: '',
|
diagnosisDesc: '',
|
||||||
@@ -686,13 +612,6 @@ function addDiagnosisItem() {
|
|||||||
|
|
||||||
// 添加中医诊断
|
// 添加中医诊断
|
||||||
function handleAddTcmDiagonsis() {
|
function handleAddTcmDiagonsis() {
|
||||||
tcmDiagnosisListForEdit.value = form.value.diagnosisList.filter(
|
|
||||||
(item) => item.diagnosisSystem === '中医'
|
|
||||||
).map((item) => ({
|
|
||||||
...item,
|
|
||||||
updateId: item.conditionId ? `${item.conditionId}-${item.syndromeGroupNo || ''}` : '' ,
|
|
||||||
illnessDefinitionId: item.definitionId || '' ,
|
|
||||||
}));
|
|
||||||
openAddDiagnosisDialog.value = true;
|
openAddDiagnosisDialog.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -756,54 +675,6 @@ function handleMaindise(value, index) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 诊断体系变化处理
|
|
||||||
*/
|
|
||||||
function handleDiagnosisSystemChange(row, value) {
|
|
||||||
// 切换诊断体系时,清空诊断名称及相关字段,避免中西医数据混淆
|
|
||||||
row.name = '';
|
|
||||||
row.ybNo = '';
|
|
||||||
row.definitionId = '';
|
|
||||||
row.showPopover = false;
|
|
||||||
|
|
||||||
if (value === '西医') {
|
|
||||||
row.tcmSyndromeCode = '';
|
|
||||||
row.tcmSyndromeName = '';
|
|
||||||
}
|
|
||||||
if (value === '中医') {
|
|
||||||
row.tcmSyndromeCode = '';
|
|
||||||
row.tcmSyndromeName = '';
|
|
||||||
loadTcmSyndromeOptions(row.definitionId || '');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 加载中医证候选项
|
|
||||||
*/
|
|
||||||
function loadTcmSyndromeOptions(conditionCode = '') {
|
|
||||||
const params = {};
|
|
||||||
if (conditionCode) {
|
|
||||||
params.conditionCode = conditionCode;
|
|
||||||
}
|
|
||||||
getTcmSyndrome(params).then((res) => {
|
|
||||||
if (res.code == 200 && res.data && res.data.records) {
|
|
||||||
tcmSyndromeOptions.value = res.data.records.map((item) => ({
|
|
||||||
value: item.ybNo,
|
|
||||||
label: item.name,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 中医证候变化处理
|
|
||||||
*/
|
|
||||||
function handleTcmSyndromeChange(row, value) {
|
|
||||||
// 找到对应的证候名称
|
|
||||||
const syndrome = tcmSyndromeOptions.value.find(item => item.value === value);
|
|
||||||
row.tcmSyndromeName = syndrome ? syndrome.label : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存诊断
|
* 保存诊断
|
||||||
*/
|
*/
|
||||||
@@ -816,79 +687,8 @@ function handleTcmSyndromeChange(row, value) {
|
|||||||
/**
|
/**
|
||||||
* 保存诊断
|
* 保存诊断
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function continueSave() {
|
|
||||||
isSaving.value = true;
|
|
||||||
|
|
||||||
const sortedList = [...form.value.diagnosisList].sort((a, b) => {
|
|
||||||
const aNo = typeof a.diagSrtNo === 'number' ? a.diagSrtNo : 9999;
|
|
||||||
const bNo = typeof b.diagSrtNo === 'number' ? b.diagSrtNo : 9999;
|
|
||||||
return aNo - bNo;
|
|
||||||
});
|
|
||||||
|
|
||||||
sortedList.forEach((item, index) => {
|
|
||||||
item.diagSrtNo = index + 1;
|
|
||||||
});
|
|
||||||
|
|
||||||
const westernList = sortedList.filter(item => item.diagnosisSystem !== '中医');
|
|
||||||
const tcmList = sortedList.filter(item => item.diagnosisSystem === '中医');
|
|
||||||
|
|
||||||
const savePromises = [];
|
|
||||||
|
|
||||||
if (westernList.length > 0) {
|
|
||||||
savePromises.push(
|
|
||||||
saveDiagnosis({
|
|
||||||
patientId: props.patientInfo.patientId,
|
|
||||||
encounterId: props.patientInfo.encounterId,
|
|
||||||
diagnosisChildList: westernList,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tcmList.length > 0) {
|
|
||||||
const syndromeGroupNo = String(Date.now());
|
|
||||||
const tcmSaveList = tcmList.map((item) => ({
|
|
||||||
definitionId: item.definitionId || '',
|
|
||||||
ybNo: item.ybNo,
|
|
||||||
syndromeGroupNo: item.syndromeGroupNo || syndromeGroupNo,
|
|
||||||
verificationStatusEnum: item.verificationStatusEnum || 4,
|
|
||||||
medTypeCode: item.medTypeCode || undefined,
|
|
||||||
tcmSyndromeCode: item.tcmSyndromeCode || '',
|
|
||||||
tcmSyndromeName: item.tcmSyndromeName || '',
|
|
||||||
}));
|
|
||||||
savePromises.push(
|
|
||||||
saveTcmDiagnosis({
|
|
||||||
patientId: props.patientInfo.patientId,
|
|
||||||
encounterId: props.patientInfo.encounterId,
|
|
||||||
diagnosisChildList: tcmSaveList,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Promise.all(savePromises).then((results) => {
|
|
||||||
const allSuccess = results.every(res => res.code === 200 || res.code == 200);
|
|
||||||
if (allSuccess) {
|
|
||||||
emits('diagnosisSave', false);
|
|
||||||
proxy.$modal.msgSuccess('诊断已保存');
|
|
||||||
getList();
|
|
||||||
isFoodDiseasesNew({ encounterId: props.patientInfo.encounterId }).then((res2) => {
|
|
||||||
if (res2.code === 20 && res2.data) {
|
|
||||||
window.open(res2.data, '_blank');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
proxy.$modal.msgWarning('部分诊断保存失败');
|
|
||||||
getList();
|
|
||||||
}
|
|
||||||
}).catch(() => {
|
|
||||||
proxy.$modal.msgError('诊断保存失败,请重试');
|
|
||||||
}).finally(() => {
|
|
||||||
setTimeout(() => {
|
|
||||||
isSaving.value = false;
|
|
||||||
}, 100);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function handleSaveDiagnosis() {
|
function handleSaveDiagnosis() {
|
||||||
|
// 防止重复点击保存
|
||||||
if (isSaving.value) {
|
if (isSaving.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -913,80 +713,41 @@ function handleSaveDiagnosis() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 中医诊断完整性校验
|
// 设置保存标志,避免触发watch监听器
|
||||||
const incompleteTcmDiagnosis = form.value.diagnosisList.find(
|
|
||||||
(diagnosis) => diagnosis.diagnosisSystem === '中医' && !diagnosis.tcmSyndromeCode
|
|
||||||
);
|
|
||||||
if (incompleteTcmDiagnosis) {
|
|
||||||
ElMessage.error('中医诊断不完整,请录入对应的证候!');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
isSaving.value = true;
|
isSaving.value = true;
|
||||||
|
|
||||||
|
// 步骤1:深拷贝并按 diagSrtNo 排序
|
||||||
const sortedList = [...form.value.diagnosisList].sort((a, b) => {
|
const sortedList = [...form.value.diagnosisList].sort((a, b) => {
|
||||||
const aNo = typeof a.diagSrtNo === 'number' ? a.diagSrtNo : 9999;
|
const aNo = typeof a.diagSrtNo === 'number' ? a.diagSrtNo : 9999;
|
||||||
const bNo = typeof b.diagSrtNo === 'number' ? b.diagSrtNo : 9999;
|
const bNo = typeof b.diagSrtNo === 'number' ? b.diagSrtNo : 9999;
|
||||||
return aNo - bNo;
|
return aNo - bNo;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 步骤2:重新分配连续的序号(从1开始)
|
||||||
sortedList.forEach((item, index) => {
|
sortedList.forEach((item, index) => {
|
||||||
item.diagSrtNo = index + 1;
|
item.diagSrtNo = index + 1; // 这里是关键!把”诊断排序”改成新顺序
|
||||||
});
|
});
|
||||||
|
|
||||||
// 分离西医和中医诊断,分别调用对应接口保存
|
// 步骤3:提交排序后的数据
|
||||||
const westernList = sortedList.filter(item => item.diagnosisSystem !== '中医');
|
saveDiagnosis({
|
||||||
const tcmList = sortedList.filter(item => item.diagnosisSystem === '中医');
|
patientId: props.patientInfo.patientId,
|
||||||
|
encounterId: props.patientInfo.encounterId,
|
||||||
const savePromises = [];
|
diagnosisChildList: sortedList,
|
||||||
|
}).then((res) => {
|
||||||
if (westernList.length > 0) {
|
if (res.code === 200) {
|
||||||
savePromises.push(
|
|
||||||
saveDiagnosis({
|
|
||||||
patientId: props.patientInfo.patientId,
|
|
||||||
encounterId: props.patientInfo.encounterId,
|
|
||||||
diagnosisChildList: westernList,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tcmList.length > 0) {
|
|
||||||
const syndromeGroupNo = String(Date.now());
|
|
||||||
const tcmSaveList = tcmList.map((item) => ({
|
|
||||||
definitionId: item.definitionId || '',
|
|
||||||
ybNo: item.ybNo,
|
|
||||||
syndromeGroupNo: item.syndromeGroupNo || syndromeGroupNo,
|
|
||||||
verificationStatusEnum: item.verificationStatusEnum || 4,
|
|
||||||
medTypeCode: item.medTypeCode || undefined,
|
|
||||||
tcmSyndromeCode: item.tcmSyndromeCode || '',
|
|
||||||
tcmSyndromeName: item.tcmSyndromeName || '',
|
|
||||||
}));
|
|
||||||
savePromises.push(
|
|
||||||
saveTcmDiagnosis({
|
|
||||||
patientId: props.patientInfo.patientId,
|
|
||||||
encounterId: props.patientInfo.encounterId,
|
|
||||||
diagnosisChildList: tcmSaveList,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Promise.all(savePromises).then((results) => {
|
|
||||||
const allSuccess = results.every(res => res.code === 200 || res.code == 200);
|
|
||||||
if (allSuccess) {
|
|
||||||
emits('diagnosisSave', false);
|
emits('diagnosisSave', false);
|
||||||
proxy.$modal.msgSuccess('诊断已保存');
|
proxy.$modal.msgSuccess('诊断已保存');
|
||||||
|
|
||||||
|
// 保存成功后从服务器重新加载数据,确保前后端数据一致
|
||||||
getList();
|
getList();
|
||||||
|
|
||||||
|
// 食源性疾病逻辑
|
||||||
isFoodDiseasesNew({ encounterId: props.patientInfo.encounterId }).then((res2) => {
|
isFoodDiseasesNew({ encounterId: props.patientInfo.encounterId }).then((res2) => {
|
||||||
if (res2.code === 20 && res2.data) {
|
if (res2.code === 20 && res2.data) {
|
||||||
window.open(res2.data, '_blank');
|
window.open(res2.data, '_blank');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
proxy.$modal.msgWarning('部分诊断保存失败');
|
|
||||||
getList();
|
|
||||||
}
|
}
|
||||||
}).catch(() => {
|
|
||||||
proxy.$modal.msgError('诊断保存失败,请重试');
|
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
isSaving.value = false;
|
isSaving.value = false;
|
||||||
@@ -1018,27 +779,19 @@ function handleChange(value) {
|
|||||||
* 选择诊断并赋值到列表
|
* 选择诊断并赋值到列表
|
||||||
*/
|
*/
|
||||||
function handleSelsectDiagnosis(row) {
|
function handleSelsectDiagnosis(row) {
|
||||||
const currentItem = form.value.diagnosisList[rowIndex.value];
|
console.log(row);
|
||||||
currentItem.ybNo = row.ybNo;
|
form.value.diagnosisList[rowIndex.value].ybNo = row.ybNo;
|
||||||
currentItem.name = row.name;
|
form.value.diagnosisList[rowIndex.value].name = row.name;
|
||||||
currentItem.definitionId = row.id;
|
form.value.diagnosisList[rowIndex.value].definitionId = row.id;
|
||||||
currentItem.showPopover = false;
|
|
||||||
|
|
||||||
// 如果是中医诊断,自动加载对应的证候
|
|
||||||
if (currentItem.diagnosisSystem === '中医') {
|
|
||||||
loadTcmSyndromeOptions(row.id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
/**获取焦点时 打开列表 */
|
/**获取焦点时 打开列表 */
|
||||||
function handleFocus(row, index) {
|
function handleFocus(row, index) {
|
||||||
rowIndex.value = index;
|
rowIndex.value = index;
|
||||||
row.showPopover = true;
|
row.showPopover = true;
|
||||||
}
|
}
|
||||||
/**失去焦点时 延迟关闭列表(避免点击列表项时过早关闭) */
|
/**失去焦点时 关闭列表 */
|
||||||
function handleBlur(row) {
|
function handleBlur(row) {
|
||||||
setTimeout(() => {
|
row.showPopover = false;
|
||||||
row.showPopover = false;
|
|
||||||
}, 200);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleNodeClick(data) {
|
function handleNodeClick(data) {
|
||||||
@@ -1064,7 +817,6 @@ function handleNodeClick(data) {
|
|||||||
name: data.name,
|
name: data.name,
|
||||||
verificationStatusEnum: 4,
|
verificationStatusEnum: 4,
|
||||||
medTypeCode: undefined,
|
medTypeCode: undefined,
|
||||||
diagnosisSystem: '西医',
|
|
||||||
diagSrtNo: form.value.diagnosisList.length + 1,
|
diagSrtNo: form.value.diagnosisList.length + 1,
|
||||||
definitionId: data.definitionId,
|
definitionId: data.definitionId,
|
||||||
diagnosisDoctor: props.patientInfo.practitionerName || props.patientInfo.doctorName || props.patientInfo.physicianName || userStore.name,
|
diagnosisDoctor: props.patientInfo.practitionerName || props.patientInfo.doctorName || props.patientInfo.physicianName || userStore.name,
|
||||||
|
|||||||
@@ -35,17 +35,13 @@ const props = defineProps({
|
|||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
diagnosisSystem: {
|
|
||||||
type: String,
|
|
||||||
default: '西医',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
const emit = defineEmits(['selectDiagnosis']);
|
const emit = defineEmits(['selectDiagnosis']);
|
||||||
const total = ref(0);
|
const total = ref(0);
|
||||||
const queryParams = ref({
|
const queryParams = ref({
|
||||||
pageSize: 1000,
|
pageSize: 1000,
|
||||||
pageNo: 1,
|
pageNo: 1,
|
||||||
typeCode: props.diagnosisSystem === '中医' ? '2' : '1',
|
// typeCode: 1,
|
||||||
});
|
});
|
||||||
const diagnosisDefinitionList = ref([]);
|
const diagnosisDefinitionList = ref([]);
|
||||||
|
|
||||||
@@ -55,14 +51,7 @@ watch(
|
|||||||
queryParams.value.searchKey = newValue;
|
queryParams.value.searchKey = newValue;
|
||||||
getList();
|
getList();
|
||||||
},
|
},
|
||||||
);
|
{ immdiate: true }
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.diagnosisSystem,
|
|
||||||
(newValue) => {
|
|
||||||
queryParams.value.typeCode = newValue === '中医' ? '2' : '1';
|
|
||||||
getList();
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
getList();
|
getList();
|
||||||
@@ -79,4 +68,4 @@ function clickRow(row) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
</style>
|
</style>
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -294,6 +294,9 @@
|
|||||||
</el-select>
|
</el-select>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
<el-form-item label="备注:" prop="remark">
|
||||||
|
<el-input v-model="row.remark" maxlength="50" placeholder="最多50字" style="width: 200px" />
|
||||||
|
</el-form-item>
|
||||||
<div class="form-actions">
|
<div class="form-actions">
|
||||||
<el-button type="primary" @click="handleSave">确定</el-button>
|
<el-button type="primary" @click="handleSave">确定</el-button>
|
||||||
<el-button @click="handleCancel">取消</el-button>
|
<el-button @click="handleCancel">取消</el-button>
|
||||||
@@ -497,6 +500,9 @@
|
|||||||
总金额:{{ row.totalPrice ? Number(row.totalPrice).toFixed(2) + ' 元' : '0.00 元' }}
|
总金额:{{ row.totalPrice ? Number(row.totalPrice).toFixed(2) + ' 元' : '0.00 元' }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<el-form-item label="备注:" prop="remark">
|
||||||
|
<el-input v-model="row.remark" maxlength="50" placeholder="最多50字" style="width: 200px" />
|
||||||
|
</el-form-item>
|
||||||
<div class="form-actions">
|
<div class="form-actions">
|
||||||
<el-button type="primary" @click="handleSave">确定</el-button>
|
<el-button type="primary" @click="handleSave">确定</el-button>
|
||||||
<el-button @click="handleCancel">取消</el-button>
|
<el-button @click="handleCancel">取消</el-button>
|
||||||
@@ -560,6 +566,9 @@
|
|||||||
<!-- 金额: {{ row.priceList[0].price }} -->
|
<!-- 金额: {{ row.priceList[0].price }} -->
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<el-form-item label="备注:" prop="remark">
|
||||||
|
<el-input v-model="row.remark" maxlength="50" placeholder="最多50字" style="width: 200px" />
|
||||||
|
</el-form-item>
|
||||||
<div class="form-actions">
|
<div class="form-actions">
|
||||||
<el-button type="primary" @click="handleSave">确定</el-button>
|
<el-button type="primary" @click="handleSave">确定</el-button>
|
||||||
<el-button @click="handleCancel">取消</el-button>
|
<el-button @click="handleCancel">取消</el-button>
|
||||||
@@ -642,16 +651,16 @@ onMounted(() => {
|
|||||||
});
|
});
|
||||||
// Bug #589: 出院带药不自动设置频次为ST,由医生手动选择
|
// Bug #589: 出院带药不自动设置频次为ST,由医生手动选择
|
||||||
if (props.row.therapyEnum == '2' && !props.row.rateCode && props.row.adviceType != 7) {
|
if (props.row.therapyEnum == '2' && !props.row.rateCode && props.row.adviceType != 7) {
|
||||||
setRateCodeToONCE();
|
setRateCodeToST();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.row.therapyEnum,
|
() => props.row.therapyEnum,
|
||||||
(newVal) => {
|
(newVal) => {
|
||||||
// Bug #589/#615: 出院带药不自动设置频次,临时医嘱默认频次为ONCE(临时一次)
|
// Bug #589: 出院带药不自动设置频次为ST,由医生手动选择
|
||||||
if (newVal == '2' && props.row.adviceType != 7) {
|
if (newVal == '2' && props.row.adviceType != 7) {
|
||||||
setRateCodeToONCE();
|
setRateCodeToST();
|
||||||
} else if (newVal == '1') {
|
} else if (newVal == '1') {
|
||||||
props.row.rateCode = '';
|
props.row.rateCode = '';
|
||||||
props.row.rateCode_dictText = '';
|
props.row.rateCode_dictText = '';
|
||||||
@@ -659,21 +668,15 @@ watch(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const setRateCodeToONCE = () => {
|
const setRateCodeToST = () => {
|
||||||
if (Array.isArray(props.config.rateCode)) {
|
if (Array.isArray(props.config.rateCode)) {
|
||||||
const onceOption = props.config.rateCode.find((item) => item.value === 'ONCE');
|
const stOption = props.config.rateCode.find((item) => item.value === 'ST');
|
||||||
if (onceOption) {
|
if (stOption) {
|
||||||
props.row.rateCode = 'ONCE';
|
props.row.rateCode = 'ST';
|
||||||
props.row.rateCode_dictText = 'ONCE ' + onceOption.label;
|
props.row.rateCode_dictText = 'ST ' + stOption.label;
|
||||||
} else {
|
} else {
|
||||||
const stOption = props.config.rateCode.find((item) => item.value === 'ST');
|
props.row.rateCode = 'ST';
|
||||||
if (stOption) {
|
props.row.rateCode_dictText = 'ST 立即';
|
||||||
props.row.rateCode = 'ST';
|
|
||||||
props.row.rateCode_dictText = 'ST ' + stOption.label;
|
|
||||||
} else {
|
|
||||||
props.row.rateCode = 'ST';
|
|
||||||
props.row.rateCode_dictText = 'ST 立即';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -255,7 +255,7 @@
|
|||||||
<el-tag v-else-if="scope.row.statusEnum == 10" type="primary">已校对</el-tag>
|
<el-tag v-else-if="scope.row.statusEnum == 10" type="primary">已校对</el-tag>
|
||||||
<el-tag v-else-if="scope.row.statusEnum == 11" type="primary">待接收</el-tag>
|
<el-tag v-else-if="scope.row.statusEnum == 11" type="primary">待接收</el-tag>
|
||||||
<el-tag v-else-if="scope.row.statusEnum == 3" type="success">已完成</el-tag>
|
<el-tag v-else-if="scope.row.statusEnum == 3" type="success">已完成</el-tag>
|
||||||
<el-tag v-else-if="scope.row.statusEnum == 6" type="error">停止</el-tag>
|
<el-tag v-else-if="scope.row.statusEnum == 6" type="danger">停止</el-tag>
|
||||||
<el-tag v-else type="info">{{ scope.row.chargeStatus_enumText }}</el-tag>
|
<el-tag v-else type="info">{{ scope.row.chargeStatus_enumText }}</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -489,6 +489,9 @@ const unitMap = ref({
|
|||||||
const buttonDisabled = computed(() => {
|
const buttonDisabled = computed(() => {
|
||||||
return !patientInfo.value;
|
return !patientInfo.value;
|
||||||
});
|
});
|
||||||
|
const isSaveDisabled = computed(() => {
|
||||||
|
return !patientInfo.value || prescriptionList.value.length === 0;
|
||||||
|
});
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
patientInfo: {
|
patientInfo: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -676,7 +679,6 @@ function getListInfo(addNewRow) {
|
|||||||
organization.value = res?.data?.records ?? res?.data ?? [];
|
organization.value = res?.data?.records ?? res?.data ?? [];
|
||||||
});
|
});
|
||||||
getPrescriptionList(patientInfo.value.encounterId).then((res) => {
|
getPrescriptionList(patientInfo.value.encounterId).then((res) => {
|
||||||
console.log('getListInfo==========>', JSON.stringify(res.data));
|
|
||||||
// 等待科室树加载完成后再处理处方数据,确保 resolveOrgId 能正确匹配
|
// 等待科室树加载完成后再处理处方数据,确保 resolveOrgId 能正确匹配
|
||||||
orgTreePromise.then(() => {
|
orgTreePromise.then(() => {
|
||||||
loadingInstance.close();
|
loadingInstance.close();
|
||||||
@@ -684,7 +686,6 @@ function getListInfo(addNewRow) {
|
|||||||
.map((item) => {
|
.map((item) => {
|
||||||
const parsedContent = JSON.parse(item.contentJson);
|
const parsedContent = JSON.parse(item.contentJson);
|
||||||
// 构造 unitCodeList,确保编辑时下拉框有正确的选项
|
// 构造 unitCodeList,确保编辑时下拉框有正确的选项
|
||||||
console.log('【DEBUG】unitCode:', parsedContent?.unitCode, typeof parsedContent?.unitCode, 'unitCodeList:', JSON.stringify(parsedContent?.unitCodeList));
|
|
||||||
const unitCodeListData = parsedContent?.unitCodeList || [
|
const unitCodeListData = parsedContent?.unitCodeList || [
|
||||||
{ value: String(parsedContent?.unitCode ?? item.unitCode ?? ''), label: parsedContent?.unitCode_dictText ?? item.unitCode_dictText ?? '', type: 'unit' },
|
{ value: String(parsedContent?.unitCode ?? item.unitCode ?? ''), label: parsedContent?.unitCode_dictText ?? item.unitCode_dictText ?? '', type: 'unit' },
|
||||||
{ value: String(parsedContent?.doseUnitCode ?? ''), label: parsedContent?.doseUnitCode_dictText ?? '', type: 'dose' },
|
{ value: String(parsedContent?.doseUnitCode ?? ''), label: parsedContent?.doseUnitCode_dictText ?? '', type: 'dose' },
|
||||||
@@ -705,6 +706,7 @@ function getListInfo(addNewRow) {
|
|||||||
unitCodeList: unitCodeListData,
|
unitCodeList: unitCodeListData,
|
||||||
// 确保 therapyEnum 被正确设置,优先使用 contentJson 中的值
|
// 确保 therapyEnum 被正确设置,优先使用 contentJson 中的值
|
||||||
therapyEnum: String(parsedContent?.therapyEnum ?? item.therapyEnum ?? '1'),
|
therapyEnum: String(parsedContent?.therapyEnum ?? item.therapyEnum ?? '1'),
|
||||||
|
// 🔧 修复:确保 orgId 为 String 类型,与 organization 树的 id 类型一致
|
||||||
// 确保 skinTestFlag 是数字类型(1 或 0),从 contentJson 恢复
|
// 确保 skinTestFlag 是数字类型(1 或 0),从 contentJson 恢复
|
||||||
skinTestFlag: parsedContent?.skinTestFlag !== undefined && parsedContent?.skinTestFlag !== null
|
skinTestFlag: parsedContent?.skinTestFlag !== undefined && parsedContent?.skinTestFlag !== null
|
||||||
? (typeof parsedContent.skinTestFlag === 'number' ? parsedContent.skinTestFlag : (parsedContent.skinTestFlag ? 1 : 0))
|
? (typeof parsedContent.skinTestFlag === 'number' ? parsedContent.skinTestFlag : (parsedContent.skinTestFlag ? 1 : 0))
|
||||||
@@ -712,7 +714,6 @@ function getListInfo(addNewRow) {
|
|||||||
skinTestFlag_enumText: parsedContent?.skinTestFlag !== undefined && parsedContent?.skinTestFlag !== null
|
skinTestFlag_enumText: parsedContent?.skinTestFlag !== undefined && parsedContent?.skinTestFlag !== null
|
||||||
? (parsedContent.skinTestFlag == 1 ? '是' : '否')
|
? (parsedContent.skinTestFlag == 1 ? '是' : '否')
|
||||||
: '否',
|
: '否',
|
||||||
// 🔧 修复:确保 orgId 为 String 类型,与 organization 树的 id 类型一致
|
|
||||||
// 关键:优先使用 item.positionId(后端 @JsonSerialize 保证精度),
|
// 关键:优先使用 item.positionId(后端 @JsonSerialize 保证精度),
|
||||||
// 而非 parsedContent.orgId(来自 JSON.parse,大 Long 可能精度丢失)
|
// 而非 parsedContent.orgId(来自 JSON.parse,大 Long 可能精度丢失)
|
||||||
// 使用 resolveOrgId 从组织树中匹配正确的 String id
|
// 使用 resolveOrgId 从组织树中匹配正确的 String id
|
||||||
@@ -1033,11 +1034,6 @@ function handleFocus(row, index) {
|
|||||||
categoryCode = selectedItem ? selectedItem.categoryCode : (row.categoryCode || '');
|
categoryCode = selectedItem ? selectedItem.categoryCode : (row.categoryCode || '');
|
||||||
}
|
}
|
||||||
adviceQueryParams.value = { adviceType, categoryCode, searchKey: '' };
|
adviceQueryParams.value = { adviceType, categoryCode, searchKey: '' };
|
||||||
// handleFocus 打开 popover 时也要加载数据
|
|
||||||
const tableRef = Array.isArray(adviceTableRef.value) ? adviceTableRef.value[index] : adviceTableRef.value;
|
|
||||||
if (tableRef && tableRef.refresh) {
|
|
||||||
tableRef.refresh(adviceType, categoryCode, '');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleBlur(row) {
|
function handleBlur(row) {
|
||||||
@@ -1092,6 +1088,7 @@ function selectAdviceBase(key, row) {
|
|||||||
}
|
}
|
||||||
// 确保 uniqueKey 不被覆盖
|
// 确保 uniqueKey 不被覆盖
|
||||||
prescriptionList.value[rowIndex.value].uniqueKey = currentUniqueKey;
|
prescriptionList.value[rowIndex.value].uniqueKey = currentUniqueKey;
|
||||||
|
|
||||||
// 检查是否是皮试药品,如果是则弹出确认提示
|
// 检查是否是皮试药品,如果是则弹出确认提示
|
||||||
// 只对药品类型(adviceType=1 药品, adviceType=2 耗材)进行皮试提示
|
// 只对药品类型(adviceType=1 药品, adviceType=2 耗材)进行皮试提示
|
||||||
const isSkinTestDrug = row.skinTestFlag !== undefined && row.skinTestFlag !== null
|
const isSkinTestDrug = row.skinTestFlag !== undefined && row.skinTestFlag !== null
|
||||||
@@ -1156,6 +1153,7 @@ function expandOrderAndFocus(key, row) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function getOrgList() {
|
function getOrgList() {
|
||||||
getOrgTree().then((res) => {
|
getOrgTree().then((res) => {
|
||||||
organization.value = res?.data?.records ?? res?.data ?? [];
|
organization.value = res?.data?.records ?? res?.data ?? [];
|
||||||
@@ -1583,10 +1581,6 @@ function handleSaveSign(row, index) {
|
|||||||
if (row.injectFlag == 1) {
|
if (row.injectFlag == 1) {
|
||||||
row.sortNumber = row.sortNumber ? row.sortNumber : prescriptionList.value.length;
|
row.sortNumber = row.sortNumber ? row.sortNumber : prescriptionList.value.length;
|
||||||
}
|
}
|
||||||
// Bug #589: 出院带药标记到contentJson
|
|
||||||
if (originalAdviceType == 7) {
|
|
||||||
row.prescriptionCategory = 3;
|
|
||||||
}
|
|
||||||
// 确保 skinTestFlag 是数字类型(1 或 0)
|
// 确保 skinTestFlag 是数字类型(1 或 0)
|
||||||
row.skinTestFlag = row.skinTestFlag !== undefined && row.skinTestFlag !== null
|
row.skinTestFlag = row.skinTestFlag !== undefined && row.skinTestFlag !== null
|
||||||
? (typeof row.skinTestFlag === 'number' ? row.skinTestFlag : (row.skinTestFlag ? 1 : 0))
|
? (typeof row.skinTestFlag === 'number' ? row.skinTestFlag : (row.skinTestFlag ? 1 : 0))
|
||||||
@@ -1643,11 +1637,11 @@ function handleSaveBatch() {
|
|||||||
...item,
|
...item,
|
||||||
therapyEnum: therapyEnum,
|
therapyEnum: therapyEnum,
|
||||||
dbOpType: item.requestId ? '2' : '1',
|
dbOpType: item.requestId ? '2' : '1',
|
||||||
// 确保 skinTestFlag 是数字类型(1 或 0)
|
// 确保 skinTestFlag 是数字类型(1 或 0)
|
||||||
skinTestFlag: item.skinTestFlag !== undefined && item.skinTestFlag !== null
|
skinTestFlag: item.skinTestFlag !== undefined && item.skinTestFlag !== null
|
||||||
? (typeof item.skinTestFlag === 'number' ? item.skinTestFlag : (item.skinTestFlag ? 1 : 0))
|
? (typeof item.skinTestFlag === "number" ? item.skinTestFlag : (item.skinTestFlag ? 1 : 0))
|
||||||
: 0,
|
: 0,
|
||||||
skinTestFlag_enumText: item.skinTestFlag == 1 ? '是' : '否',
|
skinTestFlag_enumText: item.skinTestFlag == 1 ? '是' : '否',
|
||||||
};
|
};
|
||||||
// Bug #589: 出院带药批量保存时转为药品类型
|
// Bug #589: 出院带药批量保存时转为药品类型
|
||||||
if (result.adviceType == 7) {
|
if (result.adviceType == 7) {
|
||||||
|
|||||||
@@ -54,34 +54,9 @@
|
|||||||
>
|
>
|
||||||
划价组套
|
划价组套
|
||||||
</el-button>
|
</el-button>
|
||||||
<div style="margin-left: auto; display: flex; align-items: center; gap: 15px">
|
|
||||||
<div style="display: flex; align-items: center">
|
|
||||||
<span style="margin-right: 8px">执行时间:</span>
|
|
||||||
<el-date-picker
|
|
||||||
v-model="executeTime"
|
|
||||||
type="datetime"
|
|
||||||
format="YYYY-MM-DD HH:mm:ss"
|
|
||||||
value-format="YYYY-MM-DD HH:mm:ss"
|
|
||||||
placeholder="选择日期时间"
|
|
||||||
style="width: 200px"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div style="font-size: 14px; font-weight: bold; white-space: nowrap">
|
|
||||||
本次补费总金额:<span style="color: #ff4d4f">{{ totalAmount.toFixed(6) }}</span>
|
|
||||||
</div>
|
|
||||||
<el-button @click="handleCancel">
|
|
||||||
取消
|
|
||||||
</el-button>
|
|
||||||
<el-button
|
|
||||||
type="primary"
|
|
||||||
@click="handleConfirm"
|
|
||||||
>
|
|
||||||
确定
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- 弹窗内容 - 左右布局 -->
|
<!-- 弹窗内容 - 左右布局 -->
|
||||||
<div style="display: flex; gap: 20px; height: 60vh">
|
<div style="display: flex; gap: 20px; height: 70vh">
|
||||||
<div
|
<div
|
||||||
style="
|
style="
|
||||||
width: 350px;
|
width: 350px;
|
||||||
@@ -324,6 +299,47 @@
|
|||||||
</el-table>
|
</el-table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 底部信息区域 -->
|
||||||
|
<div style="margin-top: 15px; display: flex; flex-wrap: wrap; gap: 15px">
|
||||||
|
<div style="display: flex; align-items: center">
|
||||||
|
<span style="margin-right: 8px">执行时间:</span>
|
||||||
|
<el-date-picker
|
||||||
|
v-model="executeTime"
|
||||||
|
type="datetime"
|
||||||
|
format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
placeholder="选择日期时间"
|
||||||
|
style="width: 200px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 总金额和操作按钮(固定在弹窗底部) -->
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
margin-top: 15px;
|
||||||
|
padding-top: 15px;
|
||||||
|
border-top: 1px solid #e4e7ed;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div style="font-size: 14px; font-weight: bold; text-align: right">
|
||||||
|
本次补费总金额:<span style="color: #ff4d4f">{{ totalAmount.toFixed(6) }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<el-button @click="handleCancel">
|
||||||
|
取消
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
@click="handleConfirm"
|
||||||
|
>
|
||||||
|
确定
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
<!-- 划价组套选择对话框 -->
|
<!-- 划价组套选择对话框 -->
|
||||||
<el-dialog
|
<el-dialog
|
||||||
@@ -605,7 +621,6 @@ watch(
|
|||||||
(visible) => {
|
(visible) => {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
executeTime.value = formatDateStr(new Date(), 'YYYY-MM-DD HH:mm:ss');
|
executeTime.value = formatDateStr(new Date(), 'YYYY-MM-DD HH:mm:ss');
|
||||||
consumableDefaultLocId.value = null; // 重置耗材默认库房,避免复用上次患者配置
|
|
||||||
// 弹窗打开时按当前患者科室重新加载,避免复用上一次患者/登录科室的结果
|
// 弹窗打开时按当前患者科室重新加载,避免复用上一次患者/登录科室的结果
|
||||||
loadDepartmentOptions();
|
loadDepartmentOptions();
|
||||||
getAdviceBaseInfos();
|
getAdviceBaseInfos();
|
||||||
|
|||||||
@@ -111,7 +111,6 @@ function handleClick(tabName) {
|
|||||||
break;
|
break;
|
||||||
case 'cancel':
|
case 'cancel':
|
||||||
exeStatus.value = 9;
|
exeStatus.value = 9;
|
||||||
requestStatus.value = RequestStatus.CANCELLED;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -227,21 +227,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 退回原因弹窗 -->
|
<!-- 退回原因弹窗 -->
|
||||||
<el-dialog
|
<el-dialog v-model="backReasonVisible" title="退回原因" width="400px" :close-on-click-modal="false">
|
||||||
v-model="backReasonVisible"
|
<el-form ref="backReasonFormRef" :model="backReasonForm" :rules="backReasonRules">
|
||||||
title="退回原因"
|
<el-form-item label="退回原因" prop="reason">
|
||||||
width="400px"
|
|
||||||
:close-on-click-modal="false"
|
|
||||||
>
|
|
||||||
<el-form
|
|
||||||
ref="backReasonFormRef"
|
|
||||||
:model="backReasonForm"
|
|
||||||
:rules="backReasonRules"
|
|
||||||
>
|
|
||||||
<el-form-item
|
|
||||||
label="退回原因"
|
|
||||||
prop="reason"
|
|
||||||
>
|
|
||||||
<el-input
|
<el-input
|
||||||
v-model="backReasonForm.reason"
|
v-model="backReasonForm.reason"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
@@ -253,15 +241,8 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="backReasonVisible = false">
|
<el-button @click="backReasonVisible = false">取消</el-button>
|
||||||
取消
|
<el-button type="primary" @click="confirmCancel">确定退回</el-button>
|
||||||
</el-button>
|
|
||||||
<el-button
|
|
||||||
type="primary"
|
|
||||||
@click="confirmCancel"
|
|
||||||
>
|
|
||||||
确定退回
|
|
||||||
</el-button>
|
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</template>
|
</template>
|
||||||
@@ -276,10 +257,10 @@ const activeNames = ref([]);
|
|||||||
const prescriptionList = ref([]);
|
const prescriptionList = ref([]);
|
||||||
const deadline = ref(formatDateStr(new Date(), 'YYYY-MM-DD') + ' 23:59:59');
|
const deadline = ref(formatDateStr(new Date(), 'YYYY-MM-DD') + ' 23:59:59');
|
||||||
const type = ref(null);
|
const type = ref(null);
|
||||||
const { proxy } = getCurrentInstance();
|
|
||||||
const backReasonVisible = ref(false);
|
const backReasonVisible = ref(false);
|
||||||
const backReasonForm = ref({ reason: '' });
|
const backReasonForm = ref({ reason: '' });
|
||||||
const backReasonFormRef = ref(null);
|
const backReasonFormRef = ref(null);
|
||||||
|
const { proxy } = getCurrentInstance();
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const chooseAll = ref(false);
|
const chooseAll = ref(false);
|
||||||
const selectionTrigger = ref(0);
|
const selectionTrigger = ref(0);
|
||||||
@@ -324,6 +305,7 @@ const getStatusDisplayText = (row) => {
|
|||||||
return LEGACY_STATUS_TEXT[row?.requestStatus_enumText] || row?.requestStatus_enumText || '';
|
return LEGACY_STATUS_TEXT[row?.requestStatus_enumText] || row?.requestStatus_enumText || '';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const getStatusType = (status) => {
|
const getStatusType = (status) => {
|
||||||
const map = {
|
const map = {
|
||||||
1: 'info',
|
1: 'info',
|
||||||
@@ -479,6 +461,7 @@ function handleCheck() {
|
|||||||
function handleCancel() {
|
function handleCancel() {
|
||||||
let list = getSelectRows();
|
let list = getSelectRows();
|
||||||
if (list.length > 0) {
|
if (list.length > 0) {
|
||||||
|
// 校验已发药的医嘱不允许退回
|
||||||
let dispensedItems = list.filter(item => item.dispenseStatus === 4);
|
let dispensedItems = list.filter(item => item.dispenseStatus === 4);
|
||||||
if (dispensedItems.length > 0) {
|
if (dispensedItems.length > 0) {
|
||||||
proxy.$message.error('该药品已由药房发放,请先执行退药处理,不可直接退回');
|
proxy.$message.error('该药品已由药房发放,请先执行退药处理,不可直接退回');
|
||||||
@@ -499,6 +482,7 @@ function confirmCancel() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let list = getSelectRows();
|
let list = getSelectRows();
|
||||||
|
// 将退回原因附加到每个项目
|
||||||
let requestList = list.map(item => ({
|
let requestList = list.map(item => ({
|
||||||
...item,
|
...item,
|
||||||
backReason: backReasonForm.value.reason.trim()
|
backReason: backReasonForm.value.reason.trim()
|
||||||
|
|||||||
@@ -1180,6 +1180,9 @@ function selectRow(rowValue, index) {
|
|||||||
form.purchaseinventoryList[index].partPercent = rowValue.partPercent;
|
form.purchaseinventoryList[index].partPercent = rowValue.partPercent;
|
||||||
form.purchaseinventoryList[index].unitList = rowValue.unitList[0];
|
form.purchaseinventoryList[index].unitList = rowValue.unitList[0];
|
||||||
form.purchaseinventoryList[index].lotNumber = rowValue.lotNumber;
|
form.purchaseinventoryList[index].lotNumber = rowValue.lotNumber;
|
||||||
|
// 补全单位字典文本,formatInventory 依赖此字段格式化库存显示
|
||||||
|
form.purchaseinventoryList[index].unitCode_dictText = rowValue.unitCode_dictText;
|
||||||
|
form.purchaseinventoryList[index].minUnitCode_dictText = rowValue.minUnitCode_dictText;
|
||||||
form.purchaseinventoryList[index].itemQuantity = 0;
|
form.purchaseinventoryList[index].itemQuantity = 0;
|
||||||
form.purchaseinventoryList[index].totalPrice = 0;
|
form.purchaseinventoryList[index].totalPrice = 0;
|
||||||
// 维护一个大小单位的map,用来判断当前选中单位是大/小单位
|
// 维护一个大小单位的map,用来判断当前选中单位是大/小单位
|
||||||
@@ -1194,21 +1197,13 @@ function selectRow(rowValue, index) {
|
|||||||
value: rowValue.minUnitCode,
|
value: rowValue.minUnitCode,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
if (route.query.supplyBusNo) {
|
// 新单/编辑单统一使用行级仓库ID,不再分支判断 route.query.supplyBusNo
|
||||||
handleLocationClick(
|
handleLocationClick(
|
||||||
receiptHeaderForm.sourceLocationId1,
|
form.purchaseinventoryList[index].sourceLocationId,
|
||||||
receiptHeaderForm.purposeLocationId1,
|
form.purchaseinventoryList[index].purposeLocationId,
|
||||||
form.purchaseinventoryList[index].itemId,
|
form.purchaseinventoryList[index].itemId,
|
||||||
index
|
index
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
handleLocationClick(
|
|
||||||
form.purchaseinventoryList[index].sourceLocationId,
|
|
||||||
form.purchaseinventoryList[index].purposeLocationId,
|
|
||||||
form.purchaseinventoryList[index].itemId,
|
|
||||||
index
|
|
||||||
);
|
|
||||||
}
|
|
||||||
editBatchTransfer(index); // todo
|
editBatchTransfer(index); // todo
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1220,23 +1215,29 @@ function handleLocationClick(id, purposeLocationId, itemId, index) {
|
|||||||
objLocationId: purposeLocationId,
|
objLocationId: purposeLocationId,
|
||||||
lotNumber: form.purchaseinventoryList[index].lotNumber,
|
lotNumber: form.purchaseinventoryList[index].lotNumber,
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
if (res.data && res.data[0]) {
|
if (res.data && res.data.length) {
|
||||||
form.purchaseinventoryList[index].itemTable = res.data[0].itemTable || '';
|
// SQL 按 locationId 分组后可能有两条记录(源/目的),根据 locationId 精确匹配而非盲目取 res.data[0]
|
||||||
form.purchaseinventoryList[index].supplierId = res.data[0].supplierId || '';
|
const srcId = String(id);
|
||||||
form.purchaseinventoryList[index].startTime = formatDateymd(res.data[0].productionDate) || '';
|
const purId = String(purposeLocationId);
|
||||||
form.purchaseinventoryList[index].endTime = formatDateymd(res.data[0].expirationDate) || '';
|
const sourceRow = res.data.find(item => String(item.locationId) === srcId) || {};
|
||||||
|
const purposeRow = res.data.find(item => String(item.locationId) === purId) || {};
|
||||||
|
|
||||||
form.purchaseinventoryList[index].price = res.data[0].price;
|
form.purchaseinventoryList[index].itemTable = sourceRow.itemTable || '';
|
||||||
form.purchaseinventoryList[index].totalSourceQuantity = res.data[0].orgQuantity;
|
form.purchaseinventoryList[index].supplierId = sourceRow.supplierId || '';
|
||||||
form.purchaseinventoryList[index].totalPurposeQuantity = res.data[0].objQuantity;
|
form.purchaseinventoryList[index].startTime = formatDateymd(sourceRow.productionDate) || '';
|
||||||
|
form.purchaseinventoryList[index].endTime = formatDateymd(sourceRow.expirationDate) || '';
|
||||||
|
|
||||||
|
form.purchaseinventoryList[index].price = sourceRow.price;
|
||||||
|
form.purchaseinventoryList[index].totalSourceQuantity = sourceRow.orgQuantity || 0;
|
||||||
|
form.purchaseinventoryList[index].totalPurposeQuantity = purposeRow.objQuantity || 0;
|
||||||
form.purchaseinventoryList[index].totalSourceQuantityDisplay = formatInventory(
|
form.purchaseinventoryList[index].totalSourceQuantityDisplay = formatInventory(
|
||||||
res.data[0].orgQuantity,
|
sourceRow.orgQuantity || 0,
|
||||||
form.purchaseinventoryList[index].partPercent,
|
form.purchaseinventoryList[index].partPercent,
|
||||||
form.purchaseinventoryList[index].unitCode_dictText,
|
form.purchaseinventoryList[index].unitCode_dictText,
|
||||||
form.purchaseinventoryList[index].minUnitCode_dictText
|
form.purchaseinventoryList[index].minUnitCode_dictText
|
||||||
);
|
);
|
||||||
form.purchaseinventoryList[index].totalPurposeQuantityDisplay = formatInventory(
|
form.purchaseinventoryList[index].totalPurposeQuantityDisplay = formatInventory(
|
||||||
res.data[0].objQuantity,
|
purposeRow.objQuantity || 0,
|
||||||
form.purchaseinventoryList[index].partPercent,
|
form.purchaseinventoryList[index].partPercent,
|
||||||
form.purchaseinventoryList[index].unitCode_dictText,
|
form.purchaseinventoryList[index].unitCode_dictText,
|
||||||
form.purchaseinventoryList[index].minUnitCode_dictText
|
form.purchaseinventoryList[index].minUnitCode_dictText
|
||||||
@@ -1340,8 +1341,8 @@ function formatInventory(quantity, partPercent, unitCode, minUnitCode) {
|
|||||||
return isNegative ? '-' + result : result;
|
return isNegative ? '-' + result : result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 整除情况
|
// 整除时也需拼接单位后缀,否则显示为纯数字缺少单位信息
|
||||||
const result = absQuantity / partPercent;
|
const result = (absQuantity / partPercent) + ' ' + unitCode;
|
||||||
return isNegative ? '-' + result : result;
|
return isNegative ? '-' + result : result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -75,30 +75,12 @@
|
|||||||
clearable
|
clearable
|
||||||
style="width: 120px"
|
style="width: 120px"
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option label="已到达" :value="1" />
|
||||||
label="已到达"
|
<el-option label="已分诊" :value="2" />
|
||||||
:value="1"
|
<el-option label="已看诊" :value="3" />
|
||||||
/>
|
<el-option label="已离开" :value="4" />
|
||||||
<el-option
|
<el-option label="已完成" :value="5" />
|
||||||
label="已分诊"
|
<el-option label="无状态" :value="0" />
|
||||||
:value="2"
|
|
||||||
/>
|
|
||||||
<el-option
|
|
||||||
label="已看诊"
|
|
||||||
:value="3"
|
|
||||||
/>
|
|
||||||
<el-option
|
|
||||||
label="已离开"
|
|
||||||
:value="4"
|
|
||||||
/>
|
|
||||||
<el-option
|
|
||||||
label="已完成"
|
|
||||||
:value="5"
|
|
||||||
/>
|
|
||||||
<el-option
|
|
||||||
label="无状态"
|
|
||||||
:value="0"
|
|
||||||
/>
|
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item
|
<el-form-item
|
||||||
@@ -269,8 +251,11 @@ function getList() {
|
|||||||
console.log('当前查看患者:', route.query.patientName);
|
console.log('当前查看患者:', route.query.patientName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构建请求参数
|
// 构建请求参数 - "无状态"(0) 转为 undefined,让后端不加过滤
|
||||||
const requestParams = { ...queryParams.value };
|
const requestParams = { ...queryParams.value };
|
||||||
|
if (requestParams.subjectStatusEnum === 0) {
|
||||||
|
requestParams.subjectStatusEnum = undefined;
|
||||||
|
}
|
||||||
listOutpatienRecords(requestParams).then((response) => {
|
listOutpatienRecords(requestParams).then((response) => {
|
||||||
outpatienRecordsList.value = response.data.records;
|
outpatienRecordsList.value = response.data.records;
|
||||||
total.value = response.data.total;
|
total.value = response.data.total;
|
||||||
|
|||||||
Reference in New Issue
Block a user