feat(emr): 优化病历修改留痕功能并移除医保模拟服务
- 新增分页查询修改留痕(含患者信息)功能,支持按患者、医生、操作人、病历类型筛选 - 在EmrRevisionController中移除权限校验注解,简化访问控制 - 重构病历修改留痕前端界面,采用树形结构展示病历与修订版本关系 - 添加表格列最小宽度限制和溢出省略显示,优化表格组件样式 - 更新医保配置地址从本地到云端服务器 - 移除医保模拟服务相关代码和数据库迁移文件 - 修复临床路径表缺少基础实体字段问题
This commit is contained in:
111
MD/guides/YB_MOCK_GUIDE.md
Normal file
111
MD/guides/YB_MOCK_GUIDE.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# 医保模拟接口使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
本项目提供了一个医保模拟服务器(`YbMockController`),用于在本地测试医保接口功能,无需连接真实的医保系统。
|
||||
|
||||
## 模拟的接口
|
||||
|
||||
| 接口代码 | 功能 | 请求示例 |
|
||||
|---------|------|---------|
|
||||
| 1101 | 获取参保人信息 | `{"psn_no":"P1234567890"}` |
|
||||
| 2201 | 门诊登记 | `{"psn_no":"P1234567890","org_code":"H22010402403"}` |
|
||||
| 2203 | 门诊处方上传 | `{"psn_no":"P1234567890","encounter_no":"MZ20260623001"}` |
|
||||
| 2207 | 门诊结算 | `{"psn_no":"P1234567890","encounter_no":"MZ20260623001"}` |
|
||||
| 3201 | 住院登记 | `{"psn_no":"P1234567890","org_code":"H22010402403"}` |
|
||||
| 3203 | 住院处方上传 | `{"psn_no":"P1234567890","encounter_no":"ZY20260623001"}` |
|
||||
| 3207 | 住院结算 | `{"psn_no":"P1234567890","encounter_no":"ZY20260623001"}` |
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 启动应用
|
||||
|
||||
```bash
|
||||
cd healthlink-his-server
|
||||
mvn spring-boot:run -pl healthlink-his-application
|
||||
```
|
||||
|
||||
### 2. 测试接口
|
||||
|
||||
```bash
|
||||
# 测试获取参保人信息
|
||||
curl -X POST http://localhost:18080/healthlink-his/yb/mock/1101 \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"psn_no":"P1234567890"}'
|
||||
|
||||
# 或使用测试脚本
|
||||
chmod +x scripts/test-yb-mock.sh
|
||||
./scripts/test-yb-mock.sh
|
||||
```
|
||||
|
||||
### 3. 配置医保接口地址
|
||||
|
||||
在 `application-dev.yml` 中配置医保接口地址:
|
||||
|
||||
```yaml
|
||||
ybapp:
|
||||
config:
|
||||
url: http://localhost:18080/healthlink-his/yb/mock
|
||||
```
|
||||
|
||||
## 模拟数据
|
||||
|
||||
### 参保人信息 (1101)
|
||||
|
||||
```json
|
||||
{
|
||||
"psn_no": "P1234567890",
|
||||
"psn_name": "张三",
|
||||
"sex_code": "1",
|
||||
"sex_name": "男",
|
||||
"birth_date": "1980-01-15",
|
||||
"id_card": "450123198001151234",
|
||||
"insur_type": "职工基本医疗保险",
|
||||
"insur_area": "南宁市",
|
||||
"card_no": "C2024000123456",
|
||||
"balance": "12580.50",
|
||||
"status": "正常"
|
||||
}
|
||||
```
|
||||
|
||||
### 门诊结算 (2207)
|
||||
|
||||
```json
|
||||
{
|
||||
"settle_no": "JZ20260623001",
|
||||
"total_amount": "156.80",
|
||||
"insurance_pay": "133.28",
|
||||
"self_pay": "23.52",
|
||||
"account_pay": "20.00",
|
||||
"cash_pay": "3.52",
|
||||
"settle_time": "2026-06-23 10:30:00",
|
||||
"status": "成功"
|
||||
}
|
||||
```
|
||||
|
||||
### 住院结算 (3207)
|
||||
|
||||
```json
|
||||
{
|
||||
"settle_no": "ZYJS20260623001",
|
||||
"total_amount": "15680.50",
|
||||
"insurance_pay": "14112.45",
|
||||
"self_pay": "1568.05",
|
||||
"account_pay": "1200.00",
|
||||
"cash_pay": "368.05",
|
||||
"settle_time": "2026-06-23 10:30:00",
|
||||
"status": "成功"
|
||||
}
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 模拟服务器仅用于本地测试,不模拟真实的医保业务逻辑
|
||||
2. 返回的数据是固定的测试数据,不会根据请求参数变化
|
||||
3. 生产环境请连接真实的医保接口
|
||||
4. 如需更真实的测试数据,可修改 `YbMockController` 中的响应数据
|
||||
|
||||
## 相关文件
|
||||
|
||||
- `healthlink-his-yb/src/main/java/com/healthlink/his/yb/mock/YbMockController.java`
|
||||
- `scripts/test-yb-mock.sh`
|
||||
@@ -0,0 +1,106 @@
|
||||
package com.healthlink.his.web.anesthesia.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.core.common.core.domain.R;
|
||||
import com.healthlink.his.anesthesia.domain.AnesthesiaFollowup;
|
||||
import com.healthlink.his.anesthesia.domain.AnesthesiaQualityControl;
|
||||
import com.healthlink.his.anesthesia.domain.AnesthesiaSpecimen;
|
||||
import com.healthlink.his.anesthesia.service.IAnesthesiaFollowupService;
|
||||
import com.healthlink.his.anesthesia.service.IAnesthesiaQualityControlService;
|
||||
import com.healthlink.his.anesthesia.service.IAnesthesiaSpecimenService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/anesthesia-enhanced")
|
||||
@AllArgsConstructor
|
||||
@Tag(name = "麻醉扩展-标本/随访/质控")
|
||||
public class AnesthesiaEnhancedCrudController {
|
||||
|
||||
private final IAnesthesiaSpecimenService specimenService;
|
||||
private final IAnesthesiaFollowupService followupService;
|
||||
private final IAnesthesiaQualityControlService qcService;
|
||||
|
||||
@GetMapping("/specimen/page")
|
||||
@Operation(summary = "标本分页")
|
||||
public R<?> getSpecimenPage(
|
||||
@RequestParam(defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(defaultValue = "10") Integer pageSize,
|
||||
@RequestParam(required = false) String patientName) {
|
||||
LambdaQueryWrapper<AnesthesiaSpecimen> w = new LambdaQueryWrapper<>();
|
||||
w.like(patientName != null, AnesthesiaSpecimen::getPatientName, patientName)
|
||||
.orderByDesc(AnesthesiaSpecimen::getCreateTime);
|
||||
return R.ok(specimenService.page(new Page<>(pageNo, pageSize), w));
|
||||
}
|
||||
|
||||
@PostMapping("/specimen/add")
|
||||
@Operation(summary = "新增标本")
|
||||
public R<?> addSpecimen(@RequestBody AnesthesiaSpecimen specimen) {
|
||||
specimen.setCreateTime(new Date());
|
||||
specimenService.save(specimen);
|
||||
return R.ok(specimen);
|
||||
}
|
||||
|
||||
@PostMapping("/specimen/report")
|
||||
@Operation(summary = "报告标本结果")
|
||||
public R<?> reportSpecimen(@RequestBody AnesthesiaSpecimen specimen) {
|
||||
specimen.setReportTime(new Date());
|
||||
specimenService.updateById(specimen);
|
||||
return R.ok(specimen);
|
||||
}
|
||||
|
||||
@GetMapping("/followup/page")
|
||||
@Operation(summary = "随访分页")
|
||||
public R<?> getFollowupPage(
|
||||
@RequestParam(defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(defaultValue = "10") Integer pageSize) {
|
||||
LambdaQueryWrapper<AnesthesiaFollowup> w = new LambdaQueryWrapper<>();
|
||||
w.orderByDesc(AnesthesiaFollowup::getFollowupDate);
|
||||
return R.ok(followupService.page(new Page<>(pageNo, pageSize), w));
|
||||
}
|
||||
|
||||
@PostMapping("/followup/add")
|
||||
@Operation(summary = "新增随访")
|
||||
public R<?> addFollowup(@RequestBody AnesthesiaFollowup followup) {
|
||||
followup.setCreateTime(new Date());
|
||||
followupService.save(followup);
|
||||
return R.ok(followup);
|
||||
}
|
||||
|
||||
@GetMapping("/qc/page")
|
||||
@Operation(summary = "质控分页")
|
||||
public R<?> getQcPage(
|
||||
@RequestParam(defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(defaultValue = "10") Integer pageSize) {
|
||||
LambdaQueryWrapper<AnesthesiaQualityControl> w = new LambdaQueryWrapper<>();
|
||||
w.orderByDesc(AnesthesiaQualityControl::getCreateTime);
|
||||
return R.ok(qcService.page(new Page<>(pageNo, pageSize), w));
|
||||
}
|
||||
|
||||
@PostMapping("/qc/add")
|
||||
@Operation(summary = "新增质控记录")
|
||||
public R<?> addQc(@RequestBody AnesthesiaQualityControl qc) {
|
||||
qc.setCreateTime(new Date());
|
||||
qcService.save(qc);
|
||||
return R.ok(qc);
|
||||
}
|
||||
|
||||
@GetMapping("/qc/stats")
|
||||
@Operation(summary = "质控统计")
|
||||
public R<?> getQcStats() {
|
||||
long total = qcService.count();
|
||||
long normal = qcService.count(new LambdaQueryWrapper<AnesthesiaQualityControl>()
|
||||
.eq(AnesthesiaQualityControl::getRiskLevel, "NORMAL"));
|
||||
long warning = qcService.count(new LambdaQueryWrapper<AnesthesiaQualityControl>()
|
||||
.eq(AnesthesiaQualityControl::getRiskLevel, "WARNING"));
|
||||
long critical = qcService.count(new LambdaQueryWrapper<AnesthesiaQualityControl>()
|
||||
.eq(AnesthesiaQualityControl::getRiskLevel, "CRITICAL"));
|
||||
return R.ok(Map.of("total", total, "normal", normal, "warning", warning, "critical", critical));
|
||||
}
|
||||
}
|
||||
@@ -4,13 +4,14 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.core.common.core.domain.R;
|
||||
import com.healthlink.his.emr.domain.EmrRevision;
|
||||
import com.healthlink.his.emr.dto.EmrRevisionWithPatientDto;
|
||||
import com.healthlink.his.emr.mapper.EmrRevisionMapper;
|
||||
import com.healthlink.his.emr.service.IEmrRevisionService;
|
||||
import com.healthlink.his.web.emr.appservice.IEmrRevisionAppService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Map;
|
||||
@@ -29,22 +30,21 @@ public class EmrRevisionController {
|
||||
|
||||
private final IEmrRevisionAppService emrRevisionAppService;
|
||||
|
||||
private final EmrRevisionMapper emrRevisionMapper;
|
||||
|
||||
@PostMapping("/record")
|
||||
@PreAuthorize("@ss.hasPermi('emr:edit')")
|
||||
@Operation(summary = "记录修改留痕")
|
||||
public R<EmrRevision> recordRevision(@RequestBody EmrRevision revision) {
|
||||
return R.ok(emrRevisionAppService.recordRevision(revision));
|
||||
}
|
||||
|
||||
@GetMapping("/list/{emrId}")
|
||||
@PreAuthorize("@ss.hasPermi('emr:list')")
|
||||
@Operation(summary = "获取修改历史列表")
|
||||
public R<?> getRevisions(@PathVariable Long emrId) {
|
||||
return R.ok(emrRevisionAppService.getRevisions(emrId));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@PreAuthorize("@ss.hasPermi('emr:list')")
|
||||
@Operation(summary = "分页查询修改留痕")
|
||||
public R<?> getPage(
|
||||
@RequestParam(value = "emrId", required = false) Long emrId,
|
||||
@@ -60,15 +60,35 @@ public class EmrRevisionController {
|
||||
return R.ok(revisionService.page(new Page<>(pageNo, pageSize), w));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
@PreAuthorize("@ss.hasPermi('emr:list')")
|
||||
@GetMapping("/page-with-patient")
|
||||
@Operation(summary = "分页查询修改留痕(含患者信息)")
|
||||
public R<?> getPageWithPatient(
|
||||
@RequestParam(value = "emrId", required = false) Long emrId,
|
||||
@RequestParam(value = "operatorName", required = false) String operatorName,
|
||||
@RequestParam(value = "patientName", required = false) String patientName,
|
||||
@RequestParam(value = "doctorName", required = false) String doctorName,
|
||||
@RequestParam(value = "emrType", required = false) String emrType,
|
||||
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize) {
|
||||
int offset = (pageNo - 1) * pageSize;
|
||||
long total = emrRevisionMapper.countPageWithPatient(emrId, operatorName, patientName, doctorName, emrType);
|
||||
java.util.List<EmrRevisionWithPatientDto> list = emrRevisionMapper.selectPageWithPatient(
|
||||
emrId, operatorName, patientName, doctorName, emrType, offset, pageSize);
|
||||
return R.ok(new java.util.HashMap<String, Object>() {{
|
||||
put("records", list);
|
||||
put("total", total);
|
||||
put("pageNo", pageNo);
|
||||
put("pageSize", pageSize);
|
||||
}});
|
||||
}
|
||||
|
||||
@GetMapping("/{id:\\d+}")
|
||||
@Operation(summary = "获取修订详情")
|
||||
public R<?> getById(@PathVariable Long id) {
|
||||
return R.ok(emrRevisionAppService.getRevisionDetail(id));
|
||||
}
|
||||
|
||||
@GetMapping("/compare")
|
||||
@PreAuthorize("@ss.hasPermi('emr:list')")
|
||||
@Operation(summary = "对比两个修订版本")
|
||||
public R<?> compareRevisions(
|
||||
@RequestParam("revisionId1") Long id1,
|
||||
|
||||
@@ -58,7 +58,7 @@ public class SurgerySafetyCheckController {
|
||||
return R.ok(safetyCheckService.list(w));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
@GetMapping("/{id:\\d+}")
|
||||
@Operation(summary = "获取安全核查详情")
|
||||
@PreAuthorize("@ss.hasPermi('surgery:schedule:list')")
|
||||
public R<?> getById(@PathVariable Long id) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.healthlink.his.yb.mock.service;
|
||||
package com.healthlink.his.web.ybmock.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.healthlink.his.yb.mock.domain.YbPsnInfo;
|
||||
@@ -103,3 +103,13 @@ CREATE INDEX IF NOT EXISTS idx_yb_recipe_encounter ON yb_recipe(encounter_no);
|
||||
CREATE INDEX IF NOT EXISTS idx_yb_settle_psn_no ON yb_settle_record(psn_no);
|
||||
CREATE INDEX IF NOT EXISTS idx_yb_settle_encounter ON yb_settle_record(encounter_no);
|
||||
CREATE INDEX IF NOT EXISTS idx_yb_sign_psn_no ON yb_sign_record(psn_no);
|
||||
|
||||
-- 10. 修复 clinical_pathway 表缺失列
|
||||
ALTER TABLE clinical_pathway ADD COLUMN IF NOT EXISTS create_by VARCHAR(64) DEFAULT '';
|
||||
ALTER TABLE clinical_pathway ADD COLUMN IF NOT EXISTS update_by VARCHAR(64) DEFAULT '';
|
||||
ALTER TABLE clinical_pathway ADD COLUMN IF NOT EXISTS update_time TIMESTAMP;
|
||||
|
||||
-- 11. 修复 clinical_pathway_execution 表缺失列
|
||||
ALTER TABLE clinical_pathway_execution ADD COLUMN IF NOT EXISTS create_by VARCHAR(64) DEFAULT '';
|
||||
ALTER TABLE clinical_pathway_execution ADD COLUMN IF NOT EXISTS update_by VARCHAR(64) DEFAULT '';
|
||||
ALTER TABLE clinical_pathway_execution ADD COLUMN IF NOT EXISTS update_time TIMESTAMP;
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
-- Fix Bug #748: clinical_pathway 和 clinical_pathway_execution 缺少 HisBaseEntity 列
|
||||
-- V43 和 V53 因版本号(43,53)低于已执行的迁移而被 Flyway 跳过,此处统一修复
|
||||
-- clinical_pathway_variance (V58) 已包含完整列,无需处理
|
||||
|
||||
-- 1. clinical_pathway 表: 缺失 create_by / update_by / update_time
|
||||
ALTER TABLE clinical_pathway ADD COLUMN IF NOT EXISTS create_by VARCHAR(64) DEFAULT '';
|
||||
ALTER TABLE clinical_pathway ADD COLUMN IF NOT EXISTS update_by VARCHAR(64) DEFAULT '';
|
||||
ALTER TABLE clinical_pathway ADD COLUMN IF NOT EXISTS update_time TIMESTAMP;
|
||||
|
||||
-- 2. clinical_pathway_execution 表: 缺失 create_by / update_by / update_time
|
||||
ALTER TABLE clinical_pathway_execution ADD COLUMN IF NOT EXISTS create_by VARCHAR(64) DEFAULT '';
|
||||
ALTER TABLE clinical_pathway_execution ADD COLUMN IF NOT EXISTS update_by VARCHAR(64) DEFAULT '';
|
||||
ALTER TABLE clinical_pathway_execution ADD COLUMN IF NOT EXISTS update_time TIMESTAMP;
|
||||
@@ -0,0 +1,9 @@
|
||||
-- V110: 修复 clinical_pathway_execution 表缺少 create_by 列
|
||||
-- 错误信息: ERROR: column "create_by" of relation "clinical_pathway_execution" does not exist
|
||||
|
||||
-- 添加缺失的 create_by 列
|
||||
ALTER TABLE clinical_pathway_execution ADD COLUMN IF NOT EXISTS create_by VARCHAR(64) DEFAULT '';
|
||||
|
||||
-- 确保其他 HisBaseEntity 列也存在
|
||||
ALTER TABLE clinical_pathway_execution ADD COLUMN IF NOT EXISTS update_by VARCHAR(64) DEFAULT '';
|
||||
ALTER TABLE clinical_pathway_execution ADD COLUMN IF NOT EXISTS update_time TIMESTAMP;
|
||||
@@ -1,5 +1,5 @@
|
||||
ybapp.config.url=http://localhost:18079/healthlink-his/yb/yb
|
||||
ybapp.config.url=http://api.heylihao.cloud/fsi/api
|
||||
ybapp.config.api.key=your_api_key_123
|
||||
ybapp.config.timeout=5000
|
||||
ybapp.config.fixmedinsCode=H22010402403
|
||||
ybapp.config.eleUrl=http://localhost:18079/healthlink-his/yb/ybElep
|
||||
ybapp.config.eleUrl=http://api.heylihao.cloud/fsi/api
|
||||
@@ -2,6 +2,7 @@ package com.healthlink.his.emr.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.healthlink.his.emr.domain.EmrRevision;
|
||||
import com.healthlink.his.emr.dto.EmrRevisionWithPatientDto;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
@@ -13,4 +14,20 @@ public interface EmrRevisionMapper extends BaseMapper<EmrRevision> {
|
||||
List<EmrRevision> selectByEmrId(@Param("emrId") Long emrId);
|
||||
|
||||
EmrRevision selectLatest(@Param("emrId") Long emrId);
|
||||
|
||||
List<EmrRevisionWithPatientDto> selectPageWithPatient(
|
||||
@Param("emrId") Long emrId,
|
||||
@Param("operatorName") String operatorName,
|
||||
@Param("patientName") String patientName,
|
||||
@Param("doctorName") String doctorName,
|
||||
@Param("emrType") String emrType,
|
||||
@Param("offset") int offset,
|
||||
@Param("pageSize") int pageSize);
|
||||
|
||||
long countPageWithPatient(
|
||||
@Param("emrId") Long emrId,
|
||||
@Param("operatorName") String operatorName,
|
||||
@Param("patientName") String patientName,
|
||||
@Param("doctorName") String doctorName,
|
||||
@Param("emrType") String emrType);
|
||||
}
|
||||
|
||||
@@ -17,4 +17,31 @@
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
<sql id="queryWithPatient">
|
||||
FROM emr_revision r
|
||||
LEFT JOIN emr_search_index s ON r.emr_id = s.emr_id
|
||||
<where>
|
||||
<if test="emrId != null">AND r.emr_id = #{emrId}</if>
|
||||
<if test="operatorName != null and operatorName != ''">AND r.operator_name LIKE '%' || #{operatorName} || '%'</if>
|
||||
<if test="patientName != null and patientName != ''">AND s.patient_name LIKE '%' || #{patientName} || '%'</if>
|
||||
<if test="doctorName != null and doctorName != ''">AND s.doctor_name LIKE '%' || #{doctorName} || '%'</if>
|
||||
<if test="emrType != null and emrType != ''">AND s.emr_type = #{emrType}</if>
|
||||
</where>
|
||||
</sql>
|
||||
|
||||
<select id="selectPageWithPatient" resultType="com.healthlink.his.emr.dto.EmrRevisionWithPatientDto">
|
||||
SELECT r.id, r.emr_id, r.encounter_id, r.revision_number, r.operator_id,
|
||||
r.operator_name, r.operation_type, r.diff_content, r.snapshot_content, r.create_time,
|
||||
s.patient_name, s.patient_gender, s.doctor_name, s.emr_type, s.emr_title,
|
||||
s.department_name, s.encounter_no
|
||||
<include refid="queryWithPatient"/>
|
||||
ORDER BY r.create_time DESC
|
||||
LIMIT #{pageSize} OFFSET #{offset}
|
||||
</select>
|
||||
|
||||
<select id="countPageWithPatient" resultType="long">
|
||||
SELECT COUNT(*)
|
||||
<include refid="queryWithPatient"/>
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
||||
@@ -1,174 +0,0 @@
|
||||
package com.healthlink.his.yb.mock;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 医保模拟服务器 - 用于本地测试
|
||||
*
|
||||
* 模拟广西医保电子凭证接口:
|
||||
* - 1101: 获取参保人信息
|
||||
* - 2201: 门诊登记
|
||||
* - 2203: 门诊处方上传
|
||||
* - 2207: 门诊结算
|
||||
* - 3201: 住院登记
|
||||
* - 3203: 住院处方上传
|
||||
* - 3207: 住院结算
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/yb/mock")
|
||||
@Slf4j
|
||||
public class YbMockController {
|
||||
|
||||
/**
|
||||
* 模拟医保接口入口
|
||||
*/
|
||||
@PostMapping("/{apiCode}")
|
||||
public R<?> handleApi(@PathVariable String apiCode, @RequestBody String request) {
|
||||
log.info("收到医保请求: apiCode={}, request={}", apiCode, request);
|
||||
|
||||
switch (apiCode) {
|
||||
case "1101":
|
||||
return mockGetPatientInfo(request);
|
||||
case "2201":
|
||||
return mockClinicRegister(request);
|
||||
case "2203":
|
||||
return mockClinicPrescription(request);
|
||||
case "2207":
|
||||
return mockClinicSettle(request);
|
||||
case "3201":
|
||||
return mockInpatientRegister(request);
|
||||
case "3203":
|
||||
return mockInpatientPrescription(request);
|
||||
case "3207":
|
||||
return mockInpatientSettle(request);
|
||||
default:
|
||||
return R.ok(mockDefaultResponse(apiCode));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 1101: 获取参保人信息
|
||||
*/
|
||||
private R<?> mockGetPatientInfo(String request) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("psn_no", "P1234567890");
|
||||
result.put("psn_name", "张三");
|
||||
result.put("sex_code", "1");
|
||||
result.put("sex_name", "男");
|
||||
result.put("birth_date", "1980-01-15");
|
||||
result.put("id_card", "450123198001151234");
|
||||
result.put("insur_type", "职工基本医疗保险");
|
||||
result.put("insur_area", "南宁市");
|
||||
result.put("card_no", "C2024000123456");
|
||||
result.put("balance", "12580.50");
|
||||
result.put("status", "正常");
|
||||
|
||||
return R.ok(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 2201: 门诊登记
|
||||
*/
|
||||
private R<?> mockClinicRegister(String request) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("encounter_no", "MZ20260623001");
|
||||
result.put("register_time", new Date().toString());
|
||||
result.put("status", "成功");
|
||||
|
||||
return R.ok(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 2203: 门诊处方上传
|
||||
*/
|
||||
private R<?> mockClinicPrescription(String request) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("recipe_no", "CF20260623001");
|
||||
result.put("upload_time", new Date().toString());
|
||||
result.put("total_amount", "156.80");
|
||||
result.put("self_pay", "23.52");
|
||||
result.put("insurance_pay", "133.28");
|
||||
result.put("status", "成功");
|
||||
|
||||
return R.ok(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 2207: 门诊结算
|
||||
*/
|
||||
private R<?> mockClinicSettle(String request) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("settle_no", "JZ20260623001");
|
||||
result.put("total_amount", "156.80");
|
||||
result.put("insurance_pay", "133.28");
|
||||
result.put("self_pay", "23.52");
|
||||
result.put("account_pay", "20.00");
|
||||
result.put("cash_pay", "3.52");
|
||||
result.put("settle_time", new Date().toString());
|
||||
result.put("status", "成功");
|
||||
|
||||
return R.ok(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 3201: 住院登记
|
||||
*/
|
||||
private R<?> mockInpatientRegister(String request) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("admission_no", "ZY20260623001");
|
||||
result.put("admission_time", new Date().toString());
|
||||
result.put("bed_no", "3-201-1");
|
||||
result.put("status", "成功");
|
||||
|
||||
return R.ok(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 3203: 住院处方上传
|
||||
*/
|
||||
private R<?> mockInpatientPrescription(String request) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("recipe_no", "ZYCF20260623001");
|
||||
result.put("upload_time", new Date().toString());
|
||||
result.put("total_amount", "2580.50");
|
||||
result.put("insurance_pay", "2322.45");
|
||||
result.put("self_pay", "258.05");
|
||||
result.put("status", "成功");
|
||||
|
||||
return R.ok(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 3207: 住院结算
|
||||
*/
|
||||
private R<?> mockInpatientSettle(String request) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("settle_no", "ZYJS20260623001");
|
||||
result.put("total_amount", "15680.50");
|
||||
result.put("insurance_pay", "14112.45");
|
||||
result.put("self_pay", "1568.05");
|
||||
result.put("account_pay", "1200.00");
|
||||
result.put("cash_pay", "368.05");
|
||||
result.put("settle_time", new Date().toString());
|
||||
result.put("status", "成功");
|
||||
|
||||
return R.ok(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认响应
|
||||
*/
|
||||
private Map<String, Object> mockDefaultResponse(String apiCode) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("api_code", apiCode);
|
||||
result.put("status", "成功");
|
||||
result.put("message", "模拟响应");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,21 @@ $vxe-row-height: 40px;
|
||||
$vxe-header-height: 40px;
|
||||
$vxe-radius: 4px;
|
||||
|
||||
// === 全局列宽限制:防止文字竖排 ===
|
||||
.vxe-table {
|
||||
// 表头和表体列最小宽度
|
||||
.vxe-header--column,
|
||||
.vxe-body--column {
|
||||
min-width: 80px;
|
||||
}
|
||||
// 列内容不换行,超长显示省略号
|
||||
.vxe-cell {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
// === 全局覆盖 ===
|
||||
.vxe-table {
|
||||
font-family: 'HarmonyOS Sans', 'Helvetica Neue', Helvetica, 'PingFang SC',
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
max-height="650"
|
||||
:data="ePrescribingDetailList"
|
||||
border
|
||||
:column-config="{ minWidth: 100 }"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<vxe-column
|
||||
title="处方号"
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
max-height="650"
|
||||
:data="prescriptionInfoList"
|
||||
border
|
||||
:column-config="{ minWidth: 100 }"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<vxe-column
|
||||
title="医保处方编号"
|
||||
@@ -215,6 +217,8 @@
|
||||
max-height="650"
|
||||
:data="rxdrugdetailList"
|
||||
border
|
||||
:column-config="{ minWidth: 100 }"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<vxe-column
|
||||
title="医疗目录编码"
|
||||
@@ -453,6 +457,8 @@
|
||||
max-height="650"
|
||||
:data="mdtrtinfoList"
|
||||
border
|
||||
:column-config="{ minWidth: 100 }"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<vxe-column
|
||||
title="定点医疗机构名称"
|
||||
@@ -733,6 +739,8 @@
|
||||
max-height="650"
|
||||
:data="discinfoList"
|
||||
border
|
||||
:column-config="{ minWidth: 100 }"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<vxe-column
|
||||
title="诊断类别"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import request from '@/utils/request'
|
||||
export function getRevisionPage(p){return request({url:'/emr/revision/page',method:'get',params:p})}
|
||||
export function getRevisionPage(p){return request({url:'/emr/revision/page-with-patient',method:'get',params:p})}
|
||||
export function getRevisionList(emrId){return request({url:'/emr/revision/list/'+emrId,method:'get'})}
|
||||
export function recordRevision(d){return request({url:'/emr/revision/record',method:'post',data:d})}
|
||||
export function compareRevisions(id1,id2){return request({url:'/emr/revision/compare',method:'get',params:{revisionId1:id1,revisionId2:id2}})}
|
||||
|
||||
@@ -3,82 +3,102 @@
|
||||
<div style="margin-bottom:16px">
|
||||
<span style="font-size:18px;font-weight:bold">病历修改留痕</span>
|
||||
</div>
|
||||
<div style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap">
|
||||
<el-input
|
||||
v-model="q.emrId"
|
||||
placeholder="病历ID"
|
||||
clearable
|
||||
style="width:120px"
|
||||
/>
|
||||
<el-input
|
||||
v-model="q.operatorName"
|
||||
placeholder="操作人"
|
||||
clearable
|
||||
style="width:120px"
|
||||
/>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="loadData"
|
||||
>
|
||||
查询
|
||||
</el-button>
|
||||
</div>
|
||||
<el-card shadow="never" style="margin-bottom:16px">
|
||||
<el-form inline>
|
||||
<el-form-item label="患者">
|
||||
<el-input v-model="q.patientName" placeholder="患者姓名" clearable style="width:130px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="医生">
|
||||
<el-input v-model="q.doctorName" placeholder="医生姓名" clearable style="width:130px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="操作人">
|
||||
<el-input v-model="q.operatorName" placeholder="操作人" clearable style="width:120px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="病历类型">
|
||||
<el-select v-model="q.emrType" placeholder="全部" clearable style="width:130px">
|
||||
<el-option label="入院记录" value="ADMISSION" />
|
||||
<el-option label="首次病程" value="FIRST_COURSE" />
|
||||
<el-option label="日常病程" value="DAILY_COURSE" />
|
||||
<el-option label="出院记录" value="DISCHARGE" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery">搜索</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<el-table
|
||||
:data="tableData"
|
||||
v-loading="loading"
|
||||
:data="groupedData"
|
||||
row-key="emrId"
|
||||
border
|
||||
stripe
|
||||
default-expand-all
|
||||
:tree-props="{ children: 'revisions' }"
|
||||
>
|
||||
<el-table-column
|
||||
prop="emrId"
|
||||
label="病历ID"
|
||||
width="100"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="revisionNumber"
|
||||
label="版本"
|
||||
width="70"
|
||||
align="center"
|
||||
>
|
||||
<el-table-column prop="emrTitle" label="病历标题" min-width="200">
|
||||
<template #default="{row}">
|
||||
<el-tag size="small">
|
||||
V{{ row.revisionNumber }}
|
||||
</el-tag>
|
||||
<template v-if="row.isGroup">
|
||||
<span style="font-weight:bold">{{ row.emrTitle || '病历 #' + row.emrId }}</span>
|
||||
<el-tag size="small" style="margin-left:8px">{{ row.emrType }}</el-tag>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span style="color:#909399">V{{ row.revisionNumber }}</span>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="operatorName"
|
||||
label="操作人"
|
||||
width="100"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="operationType"
|
||||
label="操作类型"
|
||||
width="100"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="diffContent"
|
||||
label="变更内容"
|
||||
min-width="200"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
prop="createTime"
|
||||
label="修改时间"
|
||||
width="170"
|
||||
/>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
width="100"
|
||||
>
|
||||
<el-table-column prop="patientName" label="患者" width="100">
|
||||
<template #default="{row}">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
size="small"
|
||||
@click="viewDetail(row)"
|
||||
>
|
||||
查看
|
||||
</el-button>
|
||||
<template v-if="row.isGroup">
|
||||
{{ row.patientName || '-' }}
|
||||
<span v-if="row.patientGender" style="color:#909399;margin-left:4px">({{ row.patientGender }})</span>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="doctorName" label="主治医生" width="100">
|
||||
<template #default="{row}">
|
||||
<template v-if="row.isGroup">{{ row.doctorName || '-' }}</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="departmentName" label="科室" width="100">
|
||||
<template #default="{row}">
|
||||
<template v-if="row.isGroup">{{ row.departmentName || '-' }}</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="encounterNo" label="就诊号" width="120">
|
||||
<template #default="{row}">
|
||||
<template v-if="row.isGroup">{{ row.encounterNo || '-' }}</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="operationType" label="操作类型" width="100">
|
||||
<template #default="{row}">
|
||||
<template v-if="!row.isGroup">
|
||||
<el-tag :type="opTypeMap[row.operationType]?.type || 'info'" size="small">
|
||||
{{ opTypeMap[row.operationType]?.label || row.operationType }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="operatorName" label="操作人" width="90">
|
||||
<template #default="{row}">
|
||||
<template v-if="!row.isGroup">{{ row.operatorName }}</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="diffContent" label="变更内容" min-width="200" show-overflow-tooltip>
|
||||
<template #default="{row}">
|
||||
<template v-if="!row.isGroup">{{ row.diffContent }}</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="时间" width="170">
|
||||
<template #default="{row}">
|
||||
<template v-if="!row.isGroup">{{ row.createTime }}</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="80">
|
||||
<template #default="{row}">
|
||||
<template v-if="!row.isGroup">
|
||||
<el-button type="primary" link size="small" @click="viewDetail(row)">详情</el-button>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -87,73 +107,105 @@
|
||||
v-model:page-size="q.pageSize"
|
||||
style="margin-top:12px;justify-content:flex-end"
|
||||
:total="total"
|
||||
layout="total,prev,pager,next"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
@size-change="loadData"
|
||||
@current-change="loadData"
|
||||
/>
|
||||
<el-dialog
|
||||
v-model="detailVisible"
|
||||
title="修订详情"
|
||||
width="700px"
|
||||
>
|
||||
<el-descriptions
|
||||
:column="2"
|
||||
border
|
||||
>
|
||||
<el-descriptions-item label="版本">
|
||||
V{{ detail.revisionNumber }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="操作人">
|
||||
{{ detail.operatorName }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="操作类型">
|
||||
{{ detail.operationType }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="时间">
|
||||
{{ detail.createTime }}
|
||||
</el-descriptions-item>
|
||||
<el-dialog v-model="detailVisible" title="修订详情" width="700px">
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="版本">V{{ detail.revisionNumber }}</el-descriptions-item>
|
||||
<el-descriptions-item label="操作人">{{ detail.operatorName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="操作类型">{{ detail.operationType }}</el-descriptions-item>
|
||||
<el-descriptions-item label="时间">{{ detail.createTime }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<div style="margin-top:12px">
|
||||
<div style="font-weight:bold;margin-bottom:8px">
|
||||
变更内容:
|
||||
</div>
|
||||
<div style="font-weight:bold;margin-bottom:8px">变更内容:</div>
|
||||
<pre style="background:#f5f7fa;padding:12px;border-radius:4px;max-height:300px;overflow:auto">{{ detail.diffContent }}</pre>
|
||||
</div>
|
||||
<div style="margin-top:12px">
|
||||
<div style="font-weight:bold;margin-bottom:8px">
|
||||
内容快照:
|
||||
</div>
|
||||
<div style="font-weight:bold;margin-bottom:8px">内容快照:</div>
|
||||
<pre style="background:#f5f7fa;padding:12px;border-radius:4px;max-height:300px;overflow:auto">{{ detail.snapshotContent }}</pre>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import {ref,onMounted} from 'vue'
|
||||
import {useRoute} from 'vue-router'
|
||||
import {getRevisionPage} from './api'
|
||||
import {ElMessage} from 'element-plus'
|
||||
const route=useRoute()
|
||||
const tableData=ref([]);const total=ref(0)
|
||||
const q=ref({pageNo:1,pageSize: 10,emrId:null,operatorName:''})
|
||||
const detailVisible=ref(false);const detail=ref({})
|
||||
const loadData=async()=>{
|
||||
try{
|
||||
// 清理空参数
|
||||
const params={pageNo:q.value.pageNo,pageSize:q.value.pageSize}
|
||||
if(q.value.emrId) params.emrId=q.value.emrId
|
||||
if(q.value.operatorName) params.operatorName=q.value.operatorName
|
||||
const r=await getRevisionPage(params)
|
||||
console.log('修订历史响应:',r)
|
||||
tableData.value=r.data?.records||r.data||[]
|
||||
total.value=r.data?.total||tableData.value.length
|
||||
}catch(e){
|
||||
console.error('加载失败:',e)
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { getRevisionPage } from './api'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const route = useRoute()
|
||||
const loading = ref(false)
|
||||
const rawData = ref([])
|
||||
const total = ref(0)
|
||||
const q = ref({ pageNo: 1, pageSize: 10, patientName: '', doctorName: '', operatorName: '', emrType: '', emrId: null })
|
||||
const detailVisible = ref(false)
|
||||
const detail = ref({})
|
||||
|
||||
const opTypeMap = {
|
||||
CREATE: { label: '创建', type: 'success' },
|
||||
EDIT: { label: '编辑', type: 'warning' },
|
||||
APPROVE: { label: '审批', type: '' },
|
||||
SIGN: { label: '签名', type: 'info' }
|
||||
}
|
||||
|
||||
const groupedData = computed(() => {
|
||||
const map = new Map()
|
||||
for (const row of rawData.value) {
|
||||
const key = row.emrId
|
||||
if (!map.has(key)) {
|
||||
map.set(key, {
|
||||
emrId: key,
|
||||
emrTitle: row.emrTitle,
|
||||
emrType: row.emrType,
|
||||
patientName: row.patientName,
|
||||
patientGender: row.patientGender,
|
||||
doctorName: row.doctorName,
|
||||
departmentName: row.departmentName,
|
||||
encounterNo: row.encounterNo,
|
||||
isGroup: true,
|
||||
revisions: []
|
||||
})
|
||||
}
|
||||
map.get(key).revisions.push({ ...row, isGroup: false })
|
||||
}
|
||||
return Array.from(map.values())
|
||||
})
|
||||
|
||||
const loadData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const params = { pageNo: q.value.pageNo, pageSize: q.value.pageSize }
|
||||
if (q.value.patientName) params.patientName = q.value.patientName
|
||||
if (q.value.doctorName) params.doctorName = q.value.doctorName
|
||||
if (q.value.operatorName) params.operatorName = q.value.operatorName
|
||||
if (q.value.emrType) params.emrType = q.value.emrType
|
||||
if (q.value.emrId) params.emrId = q.value.emrId
|
||||
const r = await getRevisionPage(params)
|
||||
rawData.value = r.data?.records || []
|
||||
total.value = r.data?.total || 0
|
||||
} catch {
|
||||
ElMessage.error('加载失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
const viewDetail=(row)=>{detail.value=row;detailVisible.value=true}
|
||||
onMounted(()=>{
|
||||
if(route.query.emrId){q.value.emrId=route.query.emrId}
|
||||
|
||||
const handleQuery = () => { q.value.pageNo = 1; loadData() }
|
||||
const resetQuery = () => {
|
||||
q.value.patientName = ''
|
||||
q.value.doctorName = ''
|
||||
q.value.operatorName = ''
|
||||
q.value.emrType = ''
|
||||
q.value.emrId = null
|
||||
q.value.pageNo = 1
|
||||
loadData()
|
||||
}
|
||||
const viewDetail = (row) => { detail.value = row; detailVisible.value = true }
|
||||
|
||||
onMounted(() => {
|
||||
if (route.query.emrId) q.value.emrId = Number(route.query.emrId)
|
||||
loadData()
|
||||
})
|
||||
</script>
|
||||
|
||||
24
scripts/kill-port-18080.ps1
Normal file
24
scripts/kill-port-18080.ps1
Normal file
@@ -0,0 +1,24 @@
|
||||
# kill-port-18080.ps1
|
||||
# 查找并杀掉占用18080端口的进程
|
||||
|
||||
$port = 18080
|
||||
Write-Host "正在查找占用端口 $port 的进程..." -ForegroundColor Yellow
|
||||
|
||||
$processes = netstat -ano | Select-String ":$port\s" | Select-String "LISTENING"
|
||||
|
||||
if ($processes) {
|
||||
foreach ($line in $processes) {
|
||||
$processId = ($line -split '\s+')[-1]
|
||||
if ($processId -match '^\d+$') {
|
||||
$process = Get-Process -Id $processId -ErrorAction SilentlyContinue
|
||||
if ($process) {
|
||||
Write-Host "找到进程: PID=$processId, 名称=$($process.ProcessName)" -ForegroundColor Cyan
|
||||
Stop-Process -Id $processId -Force
|
||||
Write-Host "已杀掉进程 $processId" -ForegroundColor Green
|
||||
}
|
||||
}
|
||||
}
|
||||
Write-Host "端口 $port 已释放" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "端口 $port 未被占用" -ForegroundColor Yellow
|
||||
}
|
||||
26
scripts/kill-port.ps1
Normal file
26
scripts/kill-port.ps1
Normal file
@@ -0,0 +1,26 @@
|
||||
param([int]$Port = 18080)
|
||||
|
||||
Write-Host "Checking port $Port..."
|
||||
|
||||
$netstatOutput = netstat -ano | findstr ":$Port " | findstr "LISTENING"
|
||||
|
||||
if ($netstatOutput) {
|
||||
foreach ($line in $netstatOutput) {
|
||||
$parts = $line -split '\s+' | Where-Object { $_ -ne '' }
|
||||
$processId = $parts[-1]
|
||||
|
||||
if ($processId -match '^\d+$') {
|
||||
try {
|
||||
$process = Get-Process -Id $processId -ErrorAction Stop
|
||||
Write-Host "Killing PID: $processId ($($process.ProcessName))"
|
||||
Stop-Process -Id $processId -Force
|
||||
Write-Host "Done"
|
||||
} catch {
|
||||
Write-Host "PID: $processId - cannot get process info"
|
||||
}
|
||||
}
|
||||
}
|
||||
Write-Host "Port $Port is now free"
|
||||
} else {
|
||||
Write-Host "Port $Port is not in use"
|
||||
}
|
||||
Reference in New Issue
Block a user