feat(kg): 医疗知识图谱全栈实现

This commit is contained in:
2026-06-19 07:41:03 +08:00
parent 554c1fe97b
commit deafee0621
38 changed files with 2379 additions and 0 deletions

View File

@@ -0,0 +1,47 @@
package com.healthlink.his.web.knowledgegraph.appservice;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.healthlink.his.web.knowledgegraph.dto.*;
public interface IKgEntityAppService {
Boolean addDisease(KgDiseaseDto dto);
Boolean updateDisease(KgDiseaseDto dto);
Boolean deleteDisease(Long id);
KgDiseaseDto getDiseaseById(Long id);
IPage<KgDiseaseDto> pageDisease(String keyword, String category, Integer pageNo, Integer pageSize);
Boolean addSymptom(KgSymptomDto dto);
Boolean updateSymptom(KgSymptomDto dto);
Boolean deleteSymptom(Long id);
KgSymptomDto getSymptomById(Long id);
IPage<KgSymptomDto> pageSymptom(String keyword, String symptomType, Integer pageNo, Integer pageSize);
Boolean addDrug(KgDrugDto dto);
Boolean updateDrug(KgDrugDto dto);
Boolean deleteDrug(Long id);
KgDrugDto getDrugById(Long id);
IPage<KgDrugDto> pageDrug(String keyword, String category, Integer pageNo, Integer pageSize);
Boolean addExamination(KgExaminationDto dto);
Boolean updateExamination(KgExaminationDto dto);
Boolean deleteExamination(Long id);
KgExaminationDto getExaminationById(Long id);
IPage<KgExaminationDto> pageExamination(String keyword, String examType, Integer pageNo, Integer pageSize);
}

View File

@@ -0,0 +1,256 @@
package com.healthlink.his.web.knowledgegraph.appservice.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.healthlink.his.knowledgegraph.domain.*;
import com.healthlink.his.knowledgegraph.service.*;
import com.healthlink.his.web.knowledgegraph.appservice.IKgEntityAppService;
import com.healthlink.his.web.knowledgegraph.dto.*;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
@Service
public class KgEntityAppServiceImpl implements IKgEntityAppService {
private final IKgDiseaseService kgDiseaseService;
private final IKgSymptomService kgSymptomService;
private final IKgDrugService kgDrugService;
private final IKgExaminationService kgExaminationService;
public KgEntityAppServiceImpl(IKgDiseaseService kgDiseaseService,
IKgSymptomService kgSymptomService,
IKgDrugService kgDrugService,
IKgExaminationService kgExaminationService) {
this.kgDiseaseService = kgDiseaseService;
this.kgSymptomService = kgSymptomService;
this.kgDrugService = kgDrugService;
this.kgExaminationService = kgExaminationService;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean addDisease(KgDiseaseDto dto) {
KgDisease entity = new KgDisease();
BeanUtils.copyProperties(dto, entity);
return kgDiseaseService.save(entity);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean updateDisease(KgDiseaseDto dto) {
if (dto.getId() == null) {
return false;
}
KgDisease entity = new KgDisease();
BeanUtils.copyProperties(dto, entity);
return kgDiseaseService.updateById(entity);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean deleteDisease(Long id) {
return kgDiseaseService.removeById(id);
}
@Override
public KgDiseaseDto getDiseaseById(Long id) {
KgDisease entity = kgDiseaseService.getById(id);
if (entity == null) {
return null;
}
KgDiseaseDto dto = new KgDiseaseDto();
BeanUtils.copyProperties(entity, dto);
return dto;
}
@Override
public IPage<KgDiseaseDto> pageDisease(String keyword, String category, Integer pageNo, Integer pageSize) {
Page<KgDisease> page = new Page<>(pageNo, pageSize);
LambdaQueryWrapper<KgDisease> wrapper = new LambdaQueryWrapper<>();
if (StringUtils.hasText(keyword)) {
wrapper.and(w -> w.like(KgDisease::getDiseaseName, keyword)
.or().like(KgDisease::getDiseaseCode, keyword));
}
if (StringUtils.hasText(category)) {
wrapper.eq(KgDisease::getCategory, category);
}
wrapper.orderByDesc(KgDisease::getCreateTime);
Page<KgDisease> result = kgDiseaseService.page(page, wrapper);
return result.convert(entity -> {
KgDiseaseDto dto = new KgDiseaseDto();
BeanUtils.copyProperties(entity, dto);
return dto;
});
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean addSymptom(KgSymptomDto dto) {
KgSymptom entity = new KgSymptom();
BeanUtils.copyProperties(dto, entity);
return kgSymptomService.save(entity);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean updateSymptom(KgSymptomDto dto) {
if (dto.getId() == null) {
return false;
}
KgSymptom entity = new KgSymptom();
BeanUtils.copyProperties(dto, entity);
return kgSymptomService.updateById(entity);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean deleteSymptom(Long id) {
return kgSymptomService.removeById(id);
}
@Override
public KgSymptomDto getSymptomById(Long id) {
KgSymptom entity = kgSymptomService.getById(id);
if (entity == null) {
return null;
}
KgSymptomDto dto = new KgSymptomDto();
BeanUtils.copyProperties(entity, dto);
return dto;
}
@Override
public IPage<KgSymptomDto> pageSymptom(String keyword, String symptomType, Integer pageNo, Integer pageSize) {
Page<KgSymptom> page = new Page<>(pageNo, pageSize);
LambdaQueryWrapper<KgSymptom> wrapper = new LambdaQueryWrapper<>();
if (StringUtils.hasText(keyword)) {
wrapper.and(w -> w.like(KgSymptom::getSymptomName, keyword)
.or().like(KgSymptom::getSymptomCode, keyword));
}
if (StringUtils.hasText(symptomType)) {
wrapper.eq(KgSymptom::getSymptomType, symptomType);
}
wrapper.orderByDesc(KgSymptom::getCreateTime);
Page<KgSymptom> result = kgSymptomService.page(page, wrapper);
return result.convert(entity -> {
KgSymptomDto dto = new KgSymptomDto();
BeanUtils.copyProperties(entity, dto);
return dto;
});
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean addDrug(KgDrugDto dto) {
KgDrug entity = new KgDrug();
BeanUtils.copyProperties(dto, entity);
return kgDrugService.save(entity);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean updateDrug(KgDrugDto dto) {
if (dto.getId() == null) {
return false;
}
KgDrug entity = new KgDrug();
BeanUtils.copyProperties(dto, entity);
return kgDrugService.updateById(entity);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean deleteDrug(Long id) {
return kgDrugService.removeById(id);
}
@Override
public KgDrugDto getDrugById(Long id) {
KgDrug entity = kgDrugService.getById(id);
if (entity == null) {
return null;
}
KgDrugDto dto = new KgDrugDto();
BeanUtils.copyProperties(entity, dto);
return dto;
}
@Override
public IPage<KgDrugDto> pageDrug(String keyword, String category, Integer pageNo, Integer pageSize) {
Page<KgDrug> page = new Page<>(pageNo, pageSize);
LambdaQueryWrapper<KgDrug> wrapper = new LambdaQueryWrapper<>();
if (StringUtils.hasText(keyword)) {
wrapper.and(w -> w.like(KgDrug::getDrugName, keyword)
.or().like(KgDrug::getDrugCode, keyword));
}
if (StringUtils.hasText(category)) {
wrapper.eq(KgDrug::getCategory, category);
}
wrapper.orderByDesc(KgDrug::getCreateTime);
Page<KgDrug> result = kgDrugService.page(page, wrapper);
return result.convert(entity -> {
KgDrugDto dto = new KgDrugDto();
BeanUtils.copyProperties(entity, dto);
return dto;
});
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean addExamination(KgExaminationDto dto) {
KgExamination entity = new KgExamination();
BeanUtils.copyProperties(dto, entity);
return kgExaminationService.save(entity);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean updateExamination(KgExaminationDto dto) {
if (dto.getId() == null) {
return false;
}
KgExamination entity = new KgExamination();
BeanUtils.copyProperties(dto, entity);
return kgExaminationService.updateById(entity);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean deleteExamination(Long id) {
return kgExaminationService.removeById(id);
}
@Override
public KgExaminationDto getExaminationById(Long id) {
KgExamination entity = kgExaminationService.getById(id);
if (entity == null) {
return null;
}
KgExaminationDto dto = new KgExaminationDto();
BeanUtils.copyProperties(entity, dto);
return dto;
}
@Override
public IPage<KgExaminationDto> pageExamination(String keyword, String examType, Integer pageNo, Integer pageSize) {
Page<KgExamination> page = new Page<>(pageNo, pageSize);
LambdaQueryWrapper<KgExamination> wrapper = new LambdaQueryWrapper<>();
if (StringUtils.hasText(keyword)) {
wrapper.and(w -> w.like(KgExamination::getExamName, keyword)
.or().like(KgExamination::getExamCode, keyword));
}
if (StringUtils.hasText(examType)) {
wrapper.eq(KgExamination::getExamType, examType);
}
wrapper.orderByDesc(KgExamination::getCreateTime);
Page<KgExamination> result = kgExaminationService.page(page, wrapper);
return result.convert(entity -> {
KgExaminationDto dto = new KgExaminationDto();
BeanUtils.copyProperties(entity, dto);
return dto;
});
}
}

View File

@@ -0,0 +1,301 @@
package com.healthlink.his.web.knowledgegraph.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.core.common.core.domain.R;
import com.healthlink.his.web.knowledgegraph.appservice.IKgEntityAppService;
import com.healthlink.his.web.knowledgegraph.dto.*;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@Slf4j
@Tag(name = "知识图谱-实体管理")
@RestController
@RequestMapping("/knowledgegraph")
public class KgEntityController {
private final IKgEntityAppService kgEntityAppService;
public KgEntityController(IKgEntityAppService kgEntityAppService) {
this.kgEntityAppService = kgEntityAppService;
}
@Operation(summary = "创建疾病")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:edit')")
@PostMapping("/disease")
public R<String> addDisease(@RequestBody KgDiseaseDto dto) {
try {
Boolean result = kgEntityAppService.addDisease(dto);
return result ? R.ok("创建成功") : R.fail("创建失败");
} catch (Exception e) {
log.error("创建疾病失败", e);
return R.fail("创建疾病失败: " + e.getMessage());
}
}
@Operation(summary = "疾病分页查询")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:list')")
@GetMapping("/disease/page")
public R<IPage<KgDiseaseDto>> pageDisease(
@Parameter(description = "关键词") @RequestParam(required = false) String keyword,
@Parameter(description = "分类") @RequestParam(required = false) String category,
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer pageNo,
@Parameter(description = "每页数量") @RequestParam(defaultValue = "10") Integer pageSize) {
try {
IPage<KgDiseaseDto> page = kgEntityAppService.pageDisease(keyword, category, pageNo, pageSize);
return R.ok(page);
} catch (Exception e) {
log.error("查询疾病列表失败", e);
return R.fail("查询疾病列表失败: " + e.getMessage());
}
}
@Operation(summary = "更新疾病")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:edit')")
@PutMapping("/disease")
public R<String> updateDisease(@RequestBody KgDiseaseDto dto) {
try {
Boolean result = kgEntityAppService.updateDisease(dto);
return result ? R.ok("更新成功") : R.fail("更新失败");
} catch (Exception e) {
log.error("更新疾病失败", e);
return R.fail("更新疾病失败: " + e.getMessage());
}
}
@Operation(summary = "删除疾病")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:edit')")
@DeleteMapping("/disease/{id}")
public R<String> deleteDisease(@PathVariable Long id) {
try {
Boolean result = kgEntityAppService.deleteDisease(id);
return result ? R.ok("删除成功") : R.fail("删除失败");
} catch (Exception e) {
log.error("删除疾病失败", e);
return R.fail("删除疾病失败: " + e.getMessage());
}
}
@Operation(summary = "疾病详情")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:list')")
@GetMapping("/disease/{id}")
public R<KgDiseaseDto> getDiseaseById(@PathVariable Long id) {
try {
KgDiseaseDto dto = kgEntityAppService.getDiseaseById(id);
return dto != null ? R.ok(dto) : R.fail("未找到疾病信息");
} catch (Exception e) {
log.error("获取疾病详情失败", e);
return R.fail("获取疾病详情失败: " + e.getMessage());
}
}
@Operation(summary = "创建症状")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:edit')")
@PostMapping("/symptom")
public R<String> addSymptom(@RequestBody KgSymptomDto dto) {
try {
Boolean result = kgEntityAppService.addSymptom(dto);
return result ? R.ok("创建成功") : R.fail("创建失败");
} catch (Exception e) {
log.error("创建症状失败", e);
return R.fail("创建症状失败: " + e.getMessage());
}
}
@Operation(summary = "症状分页查询")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:list')")
@GetMapping("/symptom/page")
public R<IPage<KgSymptomDto>> pageSymptom(
@Parameter(description = "关键词") @RequestParam(required = false) String keyword,
@Parameter(description = "症状类型") @RequestParam(required = false) String symptomType,
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer pageNo,
@Parameter(description = "每页数量") @RequestParam(defaultValue = "10") Integer pageSize) {
try {
IPage<KgSymptomDto> page = kgEntityAppService.pageSymptom(keyword, symptomType, pageNo, pageSize);
return R.ok(page);
} catch (Exception e) {
log.error("查询症状列表失败", e);
return R.fail("查询症状列表失败: " + e.getMessage());
}
}
@Operation(summary = "更新症状")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:edit')")
@PutMapping("/symptom")
public R<String> updateSymptom(@RequestBody KgSymptomDto dto) {
try {
Boolean result = kgEntityAppService.updateSymptom(dto);
return result ? R.ok("更新成功") : R.fail("更新失败");
} catch (Exception e) {
log.error("更新症状失败", e);
return R.fail("更新症状失败: " + e.getMessage());
}
}
@Operation(summary = "删除症状")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:edit')")
@DeleteMapping("/symptom/{id}")
public R<String> deleteSymptom(@PathVariable Long id) {
try {
Boolean result = kgEntityAppService.deleteSymptom(id);
return result ? R.ok("删除成功") : R.fail("删除失败");
} catch (Exception e) {
log.error("删除症状失败", e);
return R.fail("删除症状失败: " + e.getMessage());
}
}
@Operation(summary = "症状详情")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:list')")
@GetMapping("/symptom/{id}")
public R<KgSymptomDto> getSymptomById(@PathVariable Long id) {
try {
KgSymptomDto dto = kgEntityAppService.getSymptomById(id);
return dto != null ? R.ok(dto) : R.fail("未找到症状信息");
} catch (Exception e) {
log.error("获取症状详情失败", e);
return R.fail("获取症状详情失败: " + e.getMessage());
}
}
@Operation(summary = "创建药物")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:edit')")
@PostMapping("/drug")
public R<String> addDrug(@RequestBody KgDrugDto dto) {
try {
Boolean result = kgEntityAppService.addDrug(dto);
return result ? R.ok("创建成功") : R.fail("创建失败");
} catch (Exception e) {
log.error("创建药物失败", e);
return R.fail("创建药物失败: " + e.getMessage());
}
}
@Operation(summary = "药物分页查询")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:list')")
@GetMapping("/drug/page")
public R<IPage<KgDrugDto>> pageDrug(
@Parameter(description = "关键词") @RequestParam(required = false) String keyword,
@Parameter(description = "分类") @RequestParam(required = false) String category,
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer pageNo,
@Parameter(description = "每页数量") @RequestParam(defaultValue = "10") Integer pageSize) {
try {
IPage<KgDrugDto> page = kgEntityAppService.pageDrug(keyword, category, pageNo, pageSize);
return R.ok(page);
} catch (Exception e) {
log.error("查询药物列表失败", e);
return R.fail("查询药物列表失败: " + e.getMessage());
}
}
@Operation(summary = "更新药物")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:edit')")
@PutMapping("/drug")
public R<String> updateDrug(@RequestBody KgDrugDto dto) {
try {
Boolean result = kgEntityAppService.updateDrug(dto);
return result ? R.ok("更新成功") : R.fail("更新失败");
} catch (Exception e) {
log.error("更新药物失败", e);
return R.fail("更新药物失败: " + e.getMessage());
}
}
@Operation(summary = "删除药物")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:edit')")
@DeleteMapping("/drug/{id}")
public R<String> deleteDrug(@PathVariable Long id) {
try {
Boolean result = kgEntityAppService.deleteDrug(id);
return result ? R.ok("删除成功") : R.fail("删除失败");
} catch (Exception e) {
log.error("删除药物失败", e);
return R.fail("删除药物失败: " + e.getMessage());
}
}
@Operation(summary = "药物详情")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:list')")
@GetMapping("/drug/{id}")
public R<KgDrugDto> getDrugById(@PathVariable Long id) {
try {
KgDrugDto dto = kgEntityAppService.getDrugById(id);
return dto != null ? R.ok(dto) : R.fail("未找到药物信息");
} catch (Exception e) {
log.error("获取药物详情失败", e);
return R.fail("获取药物详情失败: " + e.getMessage());
}
}
@Operation(summary = "创建检查")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:edit')")
@PostMapping("/examination")
public R<String> addExamination(@RequestBody KgExaminationDto dto) {
try {
Boolean result = kgEntityAppService.addExamination(dto);
return result ? R.ok("创建成功") : R.fail("创建失败");
} catch (Exception e) {
log.error("创建检查失败", e);
return R.fail("创建检查失败: " + e.getMessage());
}
}
@Operation(summary = "检查分页查询")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:list')")
@GetMapping("/examination/page")
public R<IPage<KgExaminationDto>> pageExamination(
@Parameter(description = "关键词") @RequestParam(required = false) String keyword,
@Parameter(description = "检查类型") @RequestParam(required = false) String examType,
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer pageNo,
@Parameter(description = "每页数量") @RequestParam(defaultValue = "10") Integer pageSize) {
try {
IPage<KgExaminationDto> page = kgEntityAppService.pageExamination(keyword, examType, pageNo, pageSize);
return R.ok(page);
} catch (Exception e) {
log.error("查询检查列表失败", e);
return R.fail("查询检查列表失败: " + e.getMessage());
}
}
@Operation(summary = "更新检查")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:edit')")
@PutMapping("/examination")
public R<String> updateExamination(@RequestBody KgExaminationDto dto) {
try {
Boolean result = kgEntityAppService.updateExamination(dto);
return result ? R.ok("更新成功") : R.fail("更新失败");
} catch (Exception e) {
log.error("更新检查失败", e);
return R.fail("更新检查失败: " + e.getMessage());
}
}
@Operation(summary = "删除检查")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:edit')")
@DeleteMapping("/examination/{id}")
public R<String> deleteExamination(@PathVariable Long id) {
try {
Boolean result = kgEntityAppService.deleteExamination(id);
return result ? R.ok("删除成功") : R.fail("删除失败");
} catch (Exception e) {
log.error("删除检查失败", e);
return R.fail("删除检查失败: " + e.getMessage());
}
}
@Operation(summary = "检查详情")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:list')")
@GetMapping("/examination/{id}")
public R<KgExaminationDto> getExaminationById(@PathVariable Long id) {
try {
KgExaminationDto dto = kgEntityAppService.getExaminationById(id);
return dto != null ? R.ok(dto) : R.fail("未找到检查信息");
} catch (Exception e) {
log.error("获取检查详情失败", e);
return R.fail("获取检查详情失败: " + e.getMessage());
}
}
}

View File

@@ -0,0 +1,27 @@
package com.healthlink.his.web.knowledgegraph.dto;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import java.io.Serializable;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class KgDiseaseDto implements Serializable {
private static final long serialVersionUID = 1L;
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
private String diseaseCode;
private String diseaseName;
private String category;
private String department;
private String severityLevel;
}

View File

@@ -0,0 +1,29 @@
package com.healthlink.his.web.knowledgegraph.dto;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import java.io.Serializable;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class KgDrugDto implements Serializable {
private static final long serialVersionUID = 1L;
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
private String drugCode;
private String drugName;
private String genericName;
private String category;
private String dosageForm;
private String contraindications;
}

View File

@@ -0,0 +1,27 @@
package com.healthlink.his.web.knowledgegraph.dto;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import java.io.Serializable;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class KgExaminationDto implements Serializable {
private static final long serialVersionUID = 1L;
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
private String examCode;
private String examName;
private String examType;
private String department;
private String referenceRange;
}

View File

@@ -0,0 +1,25 @@
package com.healthlink.his.web.knowledgegraph.dto;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import java.io.Serializable;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class KgSymptomDto implements Serializable {
private static final long serialVersionUID = 1L;
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
private String symptomCode;
private String symptomName;
private String bodyPart;
private String symptomType;
}

View File

@@ -0,0 +1,32 @@
package com.healthlink.his.knowledgegraph.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.core.common.core.domain.HisBaseEntity;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("kg_disease")
public class KgDisease extends HisBaseEntity {
@TableId(type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
private String diseaseCode;
private String diseaseName;
private String category;
private String department;
private String severityLevel;
}

View File

@@ -0,0 +1,34 @@
package com.healthlink.his.knowledgegraph.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.core.common.core.domain.HisBaseEntity;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("kg_drug")
public class KgDrug extends HisBaseEntity {
@TableId(type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
private String drugCode;
private String drugName;
private String genericName;
private String category;
private String dosageForm;
private String contraindications;
}

View File

@@ -0,0 +1,32 @@
package com.healthlink.his.knowledgegraph.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.core.common.core.domain.HisBaseEntity;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("kg_examination")
public class KgExamination extends HisBaseEntity {
@TableId(type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
private String examCode;
private String examName;
private String examType;
private String department;
private String referenceRange;
}

View File

@@ -0,0 +1,30 @@
package com.healthlink.his.knowledgegraph.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.core.common.core.domain.HisBaseEntity;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("kg_symptom")
public class KgSymptom extends HisBaseEntity {
@TableId(type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
private String symptomCode;
private String symptomName;
private String bodyPart;
private String symptomType;
}

View File

@@ -0,0 +1,9 @@
package com.healthlink.his.knowledgegraph.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.healthlink.his.knowledgegraph.domain.KgDisease;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface KgDiseaseMapper extends BaseMapper<KgDisease> {
}

View File

@@ -0,0 +1,9 @@
package com.healthlink.his.knowledgegraph.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.healthlink.his.knowledgegraph.domain.KgDrug;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface KgDrugMapper extends BaseMapper<KgDrug> {
}

View File

@@ -0,0 +1,9 @@
package com.healthlink.his.knowledgegraph.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.healthlink.his.knowledgegraph.domain.KgExamination;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface KgExaminationMapper extends BaseMapper<KgExamination> {
}

View File

@@ -0,0 +1,9 @@
package com.healthlink.his.knowledgegraph.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.healthlink.his.knowledgegraph.domain.KgSymptom;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface KgSymptomMapper extends BaseMapper<KgSymptom> {
}

View File

@@ -0,0 +1,7 @@
package com.healthlink.his.knowledgegraph.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.healthlink.his.knowledgegraph.domain.KgDisease;
public interface IKgDiseaseService extends IService<KgDisease> {
}

View File

@@ -0,0 +1,7 @@
package com.healthlink.his.knowledgegraph.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.healthlink.his.knowledgegraph.domain.KgDrug;
public interface IKgDrugService extends IService<KgDrug> {
}

View File

@@ -0,0 +1,7 @@
package com.healthlink.his.knowledgegraph.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.healthlink.his.knowledgegraph.domain.KgExamination;
public interface IKgExaminationService extends IService<KgExamination> {
}

View File

@@ -0,0 +1,7 @@
package com.healthlink.his.knowledgegraph.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.healthlink.his.knowledgegraph.domain.KgSymptom;
public interface IKgSymptomService extends IService<KgSymptom> {
}

View File

@@ -0,0 +1,11 @@
package com.healthlink.his.knowledgegraph.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.knowledgegraph.domain.KgDisease;
import com.healthlink.his.knowledgegraph.mapper.KgDiseaseMapper;
import com.healthlink.his.knowledgegraph.service.IKgDiseaseService;
import org.springframework.stereotype.Service;
@Service
public class KgDiseaseServiceImpl extends ServiceImpl<KgDiseaseMapper, KgDisease> implements IKgDiseaseService {
}

View File

@@ -0,0 +1,11 @@
package com.healthlink.his.knowledgegraph.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.knowledgegraph.domain.KgDrug;
import com.healthlink.his.knowledgegraph.mapper.KgDrugMapper;
import com.healthlink.his.knowledgegraph.service.IKgDrugService;
import org.springframework.stereotype.Service;
@Service
public class KgDrugServiceImpl extends ServiceImpl<KgDrugMapper, KgDrug> implements IKgDrugService {
}

View File

@@ -0,0 +1,11 @@
package com.healthlink.his.knowledgegraph.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.knowledgegraph.domain.KgExamination;
import com.healthlink.his.knowledgegraph.mapper.KgExaminationMapper;
import com.healthlink.his.knowledgegraph.service.IKgExaminationService;
import org.springframework.stereotype.Service;
@Service
public class KgExaminationServiceImpl extends ServiceImpl<KgExaminationMapper, KgExamination> implements IKgExaminationService {
}

View File

@@ -0,0 +1,11 @@
package com.healthlink.his.knowledgegraph.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.knowledgegraph.domain.KgSymptom;
import com.healthlink.his.knowledgegraph.mapper.KgSymptomMapper;
import com.healthlink.his.knowledgegraph.service.IKgSymptomService;
import org.springframework.stereotype.Service;
@Service
public class KgSymptomServiceImpl extends ServiceImpl<KgSymptomMapper, KgSymptom> implements IKgSymptomService {
}

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.healthlink.his.knowledgegraph.mapper.KgDiseaseMapper">
</mapper>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.healthlink.his.knowledgegraph.mapper.KgDrugMapper">
</mapper>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.healthlink.his.knowledgegraph.mapper.KgExaminationMapper">
</mapper>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.healthlink.his.knowledgegraph.mapper.KgSymptomMapper">
</mapper>

View File

@@ -0,0 +1,105 @@
import request from '@/utils/request'
export function addDisease(data) {
return request({ url: '/knowledgegraph/disease', method: 'post', data })
}
export function getDiseasePage(params) {
return request({ url: '/knowledgegraph/disease/page', method: 'get', params })
}
export function getDiseaseById(id) {
return request({ url: '/knowledgegraph/disease/' + id, method: 'get' })
}
export function updateDisease(data) {
return request({ url: '/knowledgegraph/disease', method: 'put', data })
}
export function deleteDisease(id) {
return request({ url: '/knowledgegraph/disease/' + id, method: 'delete' })
}
export function addSymptom(data) {
return request({ url: '/knowledgegraph/symptom', method: 'post', data })
}
export function getSymptomPage(params) {
return request({ url: '/knowledgegraph/symptom/page', method: 'get', params })
}
export function getSymptomById(id) {
return request({ url: '/knowledgegraph/symptom/' + id, method: 'get' })
}
export function updateSymptom(data) {
return request({ url: '/knowledgegraph/symptom', method: 'put', data })
}
export function deleteSymptom(id) {
return request({ url: '/knowledgegraph/symptom/' + id, method: 'delete' })
}
export function addDrug(data) {
return request({ url: '/knowledgegraph/drug', method: 'post', data })
}
export function getDrugPage(params) {
return request({ url: '/knowledgegraph/drug/page', method: 'get', params })
}
export function getDrugById(id) {
return request({ url: '/knowledgegraph/drug/' + id, method: 'get' })
}
export function updateDrug(data) {
return request({ url: '/knowledgegraph/drug', method: 'put', data })
}
export function deleteDrug(id) {
return request({ url: '/knowledgegraph/drug/' + id, method: 'delete' })
}
export function addExamination(data) {
return request({ url: '/knowledgegraph/examination', method: 'post', data })
}
export function getExaminationPage(params) {
return request({ url: '/knowledgegraph/examination/page', method: 'get', params })
}
export function getExaminationById(id) {
return request({ url: '/knowledgegraph/examination/' + id, method: 'get' })
}
export function updateExamination(data) {
return request({ url: '/knowledgegraph/examination', method: 'put', data })
}
export function deleteExamination(id) {
return request({ url: '/knowledgegraph/examination/' + id, method: 'delete' })
}
export function createRelation(data) {
return request({ url: '/knowledgegraph/relation', method: 'post', data })
}
export function getRelationPage(params) {
return request({ url: '/knowledgegraph/relation/page', method: 'get', params })
}
export function getRelationGraph(entityType, entityId) {
return request({ url: `/knowledgegraph/relation/graph/${entityType}/${entityId}`, method: 'get' })
}
export function createPathway(data) {
return request({ url: '/knowledgegraph/pathway', method: 'post', data })
}
export function getPathwayPage(params) {
return request({ url: '/knowledgegraph/pathway/page', method: 'get', params })
}
export function getPathwaySteps(id) {
return request({ url: `/knowledgegraph/pathway/${id}/steps`, method: 'get' })
}

View File

@@ -0,0 +1,94 @@
<template>
<el-dialog :model-value="visible" :title="form.id ? '编辑疾病' : '新增疾病'" width="600px" @close="handleClose">
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
<el-form-item label="疾病编码" prop="diseaseCode">
<el-input v-model="form.diseaseCode" placeholder="请输入疾病编码" />
</el-form-item>
<el-form-item label="疾病名称" prop="diseaseName">
<el-input v-model="form.diseaseName" placeholder="请输入疾病名称" />
</el-form-item>
<el-form-item label="疾病分类" prop="category">
<el-input v-model="form.category" placeholder="请输入疾病分类" />
</el-form-item>
<el-form-item label="所属科室" prop="department">
<el-input v-model="form.department" placeholder="请输入所属科室" />
</el-form-item>
<el-form-item label="严重等级" prop="severityLevel">
<el-select v-model="form.severityLevel" placeholder="请选择严重等级" clearable style="width:100%">
<el-option label="轻度" value="MILD" />
<el-option label="中度" value="MODERATE" />
<el-option label="重度" value="SEVERE" />
<el-option label="危重" value="CRITICAL" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" :loading="submitting" @click="handleSubmit">确定</el-button>
</template>
</el-dialog>
</template>
<script setup>
import { ref, reactive, watch } from 'vue'
import { addDisease, updateDisease } from '@/api/knowledgegraph/api'
import { ElMessage } from 'element-plus'
const props = defineProps({
visible: Boolean,
data: { type: Object, default: () => ({}) }
})
const emit = defineEmits(['update:visible', 'success'])
const formRef = ref(null)
const submitting = ref(false)
const form = reactive({
id: undefined,
diseaseCode: '',
diseaseName: '',
category: '',
department: '',
severityLevel: ''
})
const rules = {
diseaseCode: [{ required: true, message: '请输入疾病编码', trigger: 'blur' }],
diseaseName: [{ required: true, message: '请输入疾病名称', trigger: 'blur' }]
}
watch(() => props.visible, (val) => {
if (val && props.data && props.data.id) {
Object.assign(form, props.data)
} else {
Object.assign(form, { id: undefined, diseaseCode: '', diseaseName: '', category: '', department: '', severityLevel: '' })
}
})
function handleClose() {
emit('update:visible', false)
}
async function handleSubmit() {
try {
await formRef.value.validate()
} catch {
return
}
submitting.value = true
try {
if (form.id) {
await updateDisease({ ...form })
ElMessage.success('更新成功')
} else {
await addDisease({ ...form })
ElMessage.success('创建成功')
}
emit('update:visible', false)
emit('success')
} finally {
submitting.value = false
}
}
</script>

View File

@@ -0,0 +1,121 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryFormRef" :inline="true" v-show="showSearch" label-width="80px">
<el-form-item label="关键词" prop="keyword">
<el-input v-model="queryParams.keyword" placeholder="疾病名称/编码" clearable style="width:200px" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="疾病分类" prop="category">
<el-input v-model="queryParams.category" placeholder="分类" clearable style="width:160px" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-col>
</el-row>
<el-table v-loading="loading" :data="tableData" border stripe>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="diseaseCode" label="疾病编码" width="140" show-overflow-tooltip />
<el-table-column prop="diseaseName" label="疾病名称" min-width="180" show-overflow-tooltip />
<el-table-column prop="category" label="疾病分类" width="140" show-overflow-tooltip />
<el-table-column prop="department" label="所属科室" width="120" show-overflow-tooltip />
<el-table-column prop="severityLevel" label="严重等级" width="100" align="center">
<template #default="{ row }">
<el-tag v-if="row.severityLevel" :type="severityType(row.severityLevel)" size="small">
{{ severityLabel(row.severityLevel) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="170" />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="{ row }">
<el-button link type="primary" icon="Edit" @click="handleEdit(row)">编辑</el-button>
<el-button link type="danger" icon="Delete" @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
<DiseaseForm v-model:visible="showForm" :data="currentRow" @success="getList" />
</div>
</template>
<script setup name="KgDiseaseList">
import { ref, reactive, onMounted } from 'vue'
import { getDiseasePage, deleteDisease } from '@/api/knowledgegraph/api'
import DiseaseForm from './DiseaseForm.vue'
import { ElMessage, ElMessageBox } from 'element-plus'
const { proxy } = getCurrentInstance()
const tableData = ref([])
const loading = ref(false)
const showSearch = ref(true)
const total = ref(0)
const showForm = ref(false)
const currentRow = ref({})
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
keyword: '',
category: ''
})
const severityMap = { MILD: '轻度', MODERATE: '中度', SEVERE: '重度', CRITICAL: '危重' }
const severityTypeMap = { MILD: 'info', MODERATE: '', SEVERE: 'warning', CRITICAL: 'danger' }
const severityLabel = (v) => severityMap[v] || v
const severityType = (v) => severityTypeMap[v] || 'info'
async function getList() {
loading.value = true
try {
const res = await getDiseasePage(queryParams)
if (res.code === 200) {
tableData.value = res.data?.records || []
total.value = res.data?.total || 0
}
} finally {
loading.value = false
}
}
function handleQuery() {
queryParams.pageNo = 1
getList()
}
function resetQuery() {
queryParams.keyword = ''
queryParams.category = ''
handleQuery()
}
function handleAdd() {
currentRow.value = {}
showForm.value = true
}
function handleEdit(row) {
currentRow.value = { ...row }
showForm.value = true
}
function handleDelete(row) {
ElMessageBox.confirm('确认删除该疾病吗?', '提示', { type: 'warning' })
.then(() => deleteDisease(row.id))
.then(() => {
ElMessage.success('删除成功')
getList()
})
}
onMounted(() => getList())
</script>

View File

@@ -0,0 +1,146 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryFormRef" :inline="true" v-show="showSearch" label-width="80px">
<el-form-item label="关键词" prop="keyword">
<el-input v-model="queryParams.keyword" placeholder="药物名称/编码" clearable style="width:200px" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="药物分类" prop="category">
<el-input v-model="queryParams.category" placeholder="分类" clearable style="width:160px" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-col>
</el-row>
<el-table v-loading="loading" :data="tableData" border stripe>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="drugCode" label="药物编码" width="140" show-overflow-tooltip />
<el-table-column prop="drugName" label="药物名称" min-width="160" show-overflow-tooltip />
<el-table-column prop="genericName" label="通用名" min-width="140" show-overflow-tooltip />
<el-table-column prop="category" label="药物分类" width="120" show-overflow-tooltip />
<el-table-column prop="dosageForm" label="剂型" width="100" align="center" />
<el-table-column prop="createTime" label="创建时间" width="170" />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="{ row }">
<el-button link type="primary" icon="Edit" @click="handleEdit(row)">编辑</el-button>
<el-button link type="danger" icon="Delete" @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
<el-dialog v-model="showForm" :title="form.id ? '编辑药物' : '新增药物'" width="600px" @close="resetForm">
<el-form ref="formRef" :model="form" :rules="formRules" label-width="90px">
<el-form-item label="药物编码" prop="drugCode">
<el-input v-model="form.drugCode" placeholder="请输入药物编码" />
</el-form-item>
<el-form-item label="药物名称" prop="drugName">
<el-input v-model="form.drugName" placeholder="请输入药物名称" />
</el-form-item>
<el-form-item label="通用名" prop="genericName">
<el-input v-model="form.genericName" placeholder="请输入通用名" />
</el-form-item>
<el-form-item label="药物分类" prop="category">
<el-input v-model="form.category" placeholder="请输入药物分类" />
</el-form-item>
<el-form-item label="剂型" prop="dosageForm">
<el-input v-model="form.dosageForm" placeholder="请输入剂型" />
</el-form-item>
<el-form-item label="禁忌症" prop="contraindications">
<el-input v-model="form.contraindications" type="textarea" :rows="3" placeholder="请输入禁忌症" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showForm = false">取消</el-button>
<el-button type="primary" :loading="submitting" @click="handleSubmit">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup name="KgDrugList">
import { ref, reactive, onMounted } from 'vue'
import { getDrugPage, addDrug, updateDrug, deleteDrug } from '@/api/knowledgegraph/api'
import { ElMessage, ElMessageBox } from 'element-plus'
const { proxy } = getCurrentInstance()
const tableData = ref([])
const loading = ref(false)
const showSearch = ref(true)
const total = ref(0)
const showForm = ref(false)
const submitting = ref(false)
const formRef = ref(null)
const queryParams = reactive({ pageNo: 1, pageSize: 10, keyword: '', category: '' })
const form = reactive({ id: undefined, drugCode: '', drugName: '', genericName: '', category: '', dosageForm: '', contraindications: '' })
const formRules = {
drugCode: [{ required: true, message: '请输入药物编码', trigger: 'blur' }],
drugName: [{ required: true, message: '请输入药物名称', trigger: 'blur' }]
}
async function getList() {
loading.value = true
try {
const res = await getDrugPage(queryParams)
if (res.code === 200) {
tableData.value = res.data?.records || []
total.value = res.data?.total || 0
}
} finally {
loading.value = false
}
}
function handleQuery() { queryParams.pageNo = 1; getList() }
function resetQuery() { queryParams.keyword = ''; queryParams.category = ''; handleQuery() }
function handleAdd() {
Object.assign(form, { id: undefined, drugCode: '', drugName: '', genericName: '', category: '', dosageForm: '', contraindications: '' })
showForm.value = true
}
function handleEdit(row) {
Object.assign(form, row)
showForm.value = true
}
function resetForm() {
formRef.value?.resetFields()
}
function handleDelete(row) {
ElMessageBox.confirm('确认删除该药物吗?', '提示', { type: 'warning' })
.then(() => deleteDrug(row.id))
.then(() => { ElMessage.success('删除成功'); getList() })
}
async function handleSubmit() {
try { await formRef.value.validate() } catch { return }
submitting.value = true
try {
if (form.id) {
await updateDrug({ ...form })
ElMessage.success('更新成功')
} else {
await addDrug({ ...form })
ElMessage.success('创建成功')
}
showForm.value = false
getList()
} finally {
submitting.value = false
}
}
onMounted(() => getList())
</script>

View File

@@ -0,0 +1,143 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryFormRef" :inline="true" v-show="showSearch" label-width="80px">
<el-form-item label="关键词" prop="keyword">
<el-input v-model="queryParams.keyword" placeholder="检查名称/编码" clearable style="width:200px" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="检查类型" prop="examType">
<el-input v-model="queryParams.examType" placeholder="检查类型" clearable style="width:160px" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-col>
</el-row>
<el-table v-loading="loading" :data="tableData" border stripe>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="examCode" label="检查编码" width="140" show-overflow-tooltip />
<el-table-column prop="examName" label="检查名称" min-width="180" show-overflow-tooltip />
<el-table-column prop="examType" label="检查类型" width="120" align="center" />
<el-table-column prop="department" label="所属科室" width="120" show-overflow-tooltip />
<el-table-column prop="referenceRange" label="参考范围" min-width="150" show-overflow-tooltip />
<el-table-column prop="createTime" label="创建时间" width="170" />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="{ row }">
<el-button link type="primary" icon="Edit" @click="handleEdit(row)">编辑</el-button>
<el-button link type="danger" icon="Delete" @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
<el-dialog v-model="showForm" :title="form.id ? '编辑检查' : '新增检查'" width="600px" @close="resetForm">
<el-form ref="formRef" :model="form" :rules="formRules" label-width="90px">
<el-form-item label="检查编码" prop="examCode">
<el-input v-model="form.examCode" placeholder="请输入检查编码" />
</el-form-item>
<el-form-item label="检查名称" prop="examName">
<el-input v-model="form.examName" placeholder="请输入检查名称" />
</el-form-item>
<el-form-item label="检查类型" prop="examType">
<el-input v-model="form.examType" placeholder="请输入检查类型" />
</el-form-item>
<el-form-item label="所属科室" prop="department">
<el-input v-model="form.department" placeholder="请输入所属科室" />
</el-form-item>
<el-form-item label="参考范围" prop="referenceRange">
<el-input v-model="form.referenceRange" placeholder="请输入参考范围" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showForm = false">取消</el-button>
<el-button type="primary" :loading="submitting" @click="handleSubmit">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup name="KgExaminationList">
import { ref, reactive, onMounted } from 'vue'
import { getExaminationPage, addExamination, updateExamination, deleteExamination } from '@/api/knowledgegraph/api'
import { ElMessage, ElMessageBox } from 'element-plus'
const { proxy } = getCurrentInstance()
const tableData = ref([])
const loading = ref(false)
const showSearch = ref(true)
const total = ref(0)
const showForm = ref(false)
const submitting = ref(false)
const formRef = ref(null)
const queryParams = reactive({ pageNo: 1, pageSize: 10, keyword: '', examType: '' })
const form = reactive({ id: undefined, examCode: '', examName: '', examType: '', department: '', referenceRange: '' })
const formRules = {
examCode: [{ required: true, message: '请输入检查编码', trigger: 'blur' }],
examName: [{ required: true, message: '请输入检查名称', trigger: 'blur' }]
}
async function getList() {
loading.value = true
try {
const res = await getExaminationPage(queryParams)
if (res.code === 200) {
tableData.value = res.data?.records || []
total.value = res.data?.total || 0
}
} finally {
loading.value = false
}
}
function handleQuery() { queryParams.pageNo = 1; getList() }
function resetQuery() { queryParams.keyword = ''; queryParams.examType = ''; handleQuery() }
function handleAdd() {
Object.assign(form, { id: undefined, examCode: '', examName: '', examType: '', department: '', referenceRange: '' })
showForm.value = true
}
function handleEdit(row) {
Object.assign(form, row)
showForm.value = true
}
function resetForm() {
formRef.value?.resetFields()
}
function handleDelete(row) {
ElMessageBox.confirm('确认删除该检查吗?', '提示', { type: 'warning' })
.then(() => deleteExamination(row.id))
.then(() => { ElMessage.success('删除成功'); getList() })
}
async function handleSubmit() {
try { await formRef.value.validate() } catch { return }
submitting.value = true
try {
if (form.id) {
await updateExamination({ ...form })
ElMessage.success('更新成功')
} else {
await addExamination({ ...form })
ElMessage.success('创建成功')
}
showForm.value = false
getList()
} finally {
submitting.value = false
}
}
onMounted(() => getList())
</script>

View File

@@ -0,0 +1,178 @@
<template>
<div style="padding:16px">
<div style="margin-bottom:16px;display:flex;justify-content:space-between;align-items:center">
<span style="font-size:18px;font-weight:bold">知识图谱</span>
</div>
<el-card shadow="never" style="margin-bottom:16px">
<el-form inline>
<el-form-item label="实体类型">
<el-select v-model="entityType" placeholder="请选择" style="width:130px">
<el-option label="疾病" value="disease" />
<el-option label="症状" value="symptom" />
<el-option label="药物" value="drug" />
<el-option label="检查" value="exam" />
</el-select>
</el-form-item>
<el-form-item label="实体ID">
<el-input v-model="entityId" placeholder="请输入实体ID" style="width:200px" @keyup.enter="loadGraph" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="loadGraph" :loading="loading">加载图谱</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card shadow="never">
<div ref="chartRef" style="width:100%;height:600px" />
<el-empty v-if="!hasData && !loading" description="请输入实体类型和ID点击加载图谱" />
</el-card>
<el-dialog v-model="showDetail" :title="detailTitle" width="500px" append-to-body>
<el-descriptions :column="1" border>
<el-descriptions-item label="实体类型">{{ detailNode.entityType }}</el-descriptions-item>
<el-descriptions-item label="实体ID">{{ detailNode.entityId }}</el-descriptions-item>
</el-descriptions>
<div v-if="detailRelations.length" style="margin-top:16px">
<div style="font-weight:bold;margin-bottom:8px">关联关系</div>
<el-table :data="detailRelations" border size="small">
<el-table-column prop="relationType" label="关系" width="120" />
<el-table-column prop="target" label="目标" />
<el-table-column prop="strength" label="强度" width="80" />
</el-table>
</div>
<template #footer>
<el-button @click="showDetail = false">关闭</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, nextTick } from 'vue'
import * as echarts from 'echarts'
import { getRelationGraph } from '@/api/knowledgegraph/api'
const chartRef = ref(null)
const loading = ref(false)
const hasData = ref(false)
const entityType = ref('disease')
const entityId = ref('')
let chartInstance = null
const showDetail = ref(false)
const detailTitle = ref('')
const detailNode = ref({})
const detailRelations = ref([])
const nodeColorMap = {
disease: '#f56c6c',
symptom: '#409eff',
drug: '#67c23a',
exam: '#e6a23c'
}
const nodeLabelMap = { disease: '疾病', symptom: '症状', drug: '药物', exam: '检查' }
const relationLabelMap = {
CAUSES: '导致', TREATS: '治疗', CONTRAINDICATES: '禁忌',
INTERACTS_WITH: '相互作用', REQUIRES_EXAM: '需要检查',
HAS_SYMPTOM: '具有症状', SIDE_EFFECT: '副作用', ALTERNATIVE: '替代'
}
function initChart() {
if (!chartRef.value) return
chartInstance = echarts.init(chartRef.value)
chartInstance.on('click', (params) => {
if (params.dataType === 'node') {
handleNodeClick(params.data)
}
})
}
function handleNodeClick(data) {
detailNode.value = data
detailTitle.value = `${nodeLabelMap[data.entityType] || data.entityType} - ${data.entityId}`
detailRelations.value = []
if (graphData.value) {
graphData.value.edges.forEach(e => {
if (e.source === data.id) {
detailRelations.value.push({ relationType: relationLabelMap[e.relationType] || e.relationType, target: e.target, strength: e.relationStrength })
} else if (e.target === data.id) {
detailRelations.value.push({ relationType: relationLabelMap[e.relationType] || e.relationType, target: e.source, strength: e.relationStrength })
}
})
}
showDetail.value = true
}
const graphData = ref(null)
function renderChart(data) {
if (!chartInstance) return
graphData.value = data
const nodes = (data.nodes || []).map(n => ({
id: n.id,
name: n.entityId,
symbolSize: 50,
itemStyle: { color: nodeColorMap[n.entityType] || '#909399' },
label: { show: true, fontSize: 12 },
entityType: n.entityType,
entityId: n.entityId
}))
const edges = (data.edges || []).map(e => ({
source: e.source,
target: e.target,
label: { show: true, formatter: relationLabelMap[e.relationType] || e.relationType, fontSize: 10 },
lineStyle: { curveness: 0.2 }
}))
chartInstance.setOption({
tooltip: { trigger: 'item' },
legend: {
data: Object.keys(nodeColorMap).map(k => nodeLabelMap[k]),
bottom: 10
},
series: [{
type: 'graph',
layout: 'force',
data: nodes,
links: edges,
roam: true,
draggable: true,
force: { repulsion: 400, gravity: 0.1, edgeLength: [120, 250] },
emphasis: { focus: 'adjacency', lineStyle: { width: 4 } },
categories: Object.keys(nodeColorMap).map(k => ({ name: nodeLabelMap[k] }))
}]
}, true)
}
async function loadGraph() {
if (!entityId.value) return
loading.value = true
try {
const r = await getRelationGraph(entityType.value, entityId.value)
const data = r.data || { nodes: [], edges: [] }
hasData.value = data.nodes && data.nodes.length > 0
if (hasData.value) {
await nextTick()
if (!chartInstance) initChart()
renderChart(data)
}
} finally {
loading.value = false
}
}
function handleResize() {
chartInstance && chartInstance.resize()
}
onMounted(() => {
initChart()
window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
window.removeEventListener('resize', handleResize)
chartInstance && chartInstance.dispose()
})
</script>

View File

@@ -0,0 +1,120 @@
<template>
<el-dialog v-model="visible" title="新增临床路径" width="750px" append-to-body @close="handleClose">
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
<el-form-item label="路径编码" prop="pathwayCode">
<el-input v-model="form.pathwayCode" placeholder="如: CP-DM-001" />
</el-form-item>
<el-form-item label="路径名称" prop="pathwayName">
<el-input v-model="form.pathwayName" placeholder="如: 2型糖尿病标准路径" />
</el-form-item>
<el-form-item label="疾病编码">
<el-input v-model="form.diseaseCode" placeholder="ICD-10编码" />
</el-form-item>
<el-form-item label="疾病名称">
<el-input v-model="form.diseaseName" placeholder="疾病名称" />
</el-form-item>
<el-form-item label="标准天数">
<el-input-number v-model="form.standardDays" :min="1" :max="365" />
</el-form-item>
<el-form-item label="路径描述">
<el-input v-model="form.description" type="textarea" :rows="3" placeholder="路径说明" />
</el-form-item>
</el-form>
<div style="margin-top:16px;display:flex;justify-content:space-between;align-items:center">
<span style="font-weight:bold">路径步骤</span>
<el-button size="small" type="primary" @click="addStep">添加步骤</el-button>
</div>
<div style="margin-top:8px;min-height:80px;max-height:300px;overflow-y:auto">
<div v-for="(step, idx) in steps" :key="idx"
style="display:flex;align-items:center;gap:8px;padding:8px;margin-bottom:8px;background:#f5f7fa;border-radius:4px;cursor:move"
draggable="true"
@dragstart="onDragStart(idx)"
@dragover.prevent
@drop="onDrop(idx)"
>
<span style="color:#999;cursor:grab;width:20px;text-align:center"></span>
<el-tag size="small" type="info">{{ idx + 1 }}</el-tag>
<el-select v-model="step.dayNumber" placeholder="天" style="width:80px" size="small">
<el-option v-for="d in 30" :key="d" :label="`第${d}天`" :value="d" />
</el-select>
<el-select v-model="step.stepType" placeholder="类型" style="width:100px" size="small">
<el-option label="检查" value="exam" />
<el-option label="治疗" value="treatment" />
<el-option label="护理" value="nursing" />
<el-option label="用药" value="medication" />
</el-select>
<el-input v-model="step.stepContent" placeholder="步骤内容" size="small" style="flex:1" />
<el-button link type="danger" @click="removeStep(idx)" size="small">删除</el-button>
</div>
<el-empty v-if="!steps.length" description="点击上方添加步骤" :image-size="60" />
</div>
<template #footer>
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" :loading="submitting" @click="handleSubmit">确定</el-button>
</template>
</el-dialog>
</template>
<script setup>
import { ref, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { createPathway } from '@/api/knowledgegraph/api'
const props = defineProps({ visible: Boolean })
const emit = defineEmits(['update:visible', 'success'])
const formRef = ref(null)
const submitting = ref(false)
const steps = ref([])
let dragIdx = -1
const defaultForm = () => ({
pathwayCode: '', pathwayName: '', diseaseCode: '',
diseaseName: '', standardDays: 7, description: ''
})
const form = ref(defaultForm())
const rules = {
pathwayCode: [{ required: true, message: '请输入路径编码', trigger: 'blur' }],
pathwayName: [{ required: true, message: '请输入路径名称', trigger: 'blur' }]
}
watch(() => props.visible, (v) => {
if (v) { form.value = defaultForm(); steps.value = [] }
})
function handleClose() { emit('update:visible', false) }
function addStep() {
steps.value.push({ dayNumber: 1, stepType: 'exam', stepContent: '' })
}
function removeStep(idx) {
steps.value.splice(idx, 1)
}
function onDragStart(idx) { dragIdx = idx }
function onDrop(idx) {
if (dragIdx < 0 || dragIdx === idx) return
const item = steps.value.splice(dragIdx, 1)[0]
steps.value.splice(idx, 0, item)
dragIdx = -1
}
async function handleSubmit() {
await formRef.value.validate()
submitting.value = true
try {
await createPathway({ pathway: form.value, steps: steps.value })
ElMessage.success('创建成功')
emit('update:visible', false)
emit('success')
} finally {
submitting.value = false
}
}
</script>

View File

@@ -0,0 +1,119 @@
<template>
<div style="padding:16px">
<div style="margin-bottom:16px;display:flex;justify-content:space-between;align-items:center">
<span style="font-size:18px;font-weight:bold">临床路径管理</span>
<div>
<el-button type="primary" @click="loadData">刷新</el-button>
<el-button type="success" @click="showEdit = true">新增路径</el-button>
</div>
</div>
<el-card shadow="never" style="margin-bottom:16px">
<el-form inline>
<el-form-item label="关键词">
<el-input v-model="keyword" placeholder="路径名称/疾病名称/编码" clearable style="width:220px" @keyup.enter="loadData" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="loadData">搜索</el-button>
<el-button @click="keyword = ''; loadData()">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<el-table v-loading="loading" :data="tableData" border stripe>
<el-table-column prop="pathwayCode" label="路径编码" width="140" />
<el-table-column prop="pathwayName" label="路径名称" min-width="180" show-overflow-tooltip />
<el-table-column prop="diseaseCode" label="疾病编码" width="120" />
<el-table-column prop="diseaseName" label="疾病名称" width="150" show-overflow-tooltip />
<el-table-column prop="standardDays" label="标准天数" width="90" align="center" />
<el-table-column prop="status" label="状态" width="80" align="center">
<template #default="{row}">
<el-tag :type="row.status === 'ACTIVE' ? 'success' : 'info'" size="small">
{{ row.status === 'ACTIVE' ? '启用' : '停用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="160" />
<el-table-column label="操作" width="120" fixed="right">
<template #default="{row}">
<el-button link type="primary" @click="viewSteps(row)">查看步骤</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
v-model:current-page="pageNo"
v-model:page-size="pageSize"
style="margin-top:12px;justify-content:flex-end"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="loadData"
@current-change="loadData"
/>
<PathwayEdit v-model:visible="showEdit" @success="loadData" />
<el-dialog v-model="showSteps" :title="stepsTitle" width="700px" append-to-body>
<el-timeline>
<el-timeline-item
v-for="step in stepsData"
:key="step.id"
:type="stepTypeColor(step.stepType)"
:timestamp="`第${step.dayNumber || '-'}天 - ${stepTypeName(step.stepType)}`"
placement="top"
>
<el-card shadow="never" :body-style="{padding:'10px'}">
<div style="font-size:13px">{{ step.stepContent }}</div>
</el-card>
</el-timeline-item>
</el-timeline>
<el-empty v-if="!stepsData.length" description="暂无步骤" />
<template #footer>
<el-button @click="showSteps = false">关闭</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { getPathwayPage, getPathwaySteps } from '@/api/knowledgegraph/api'
import PathwayEdit from './PathwayEdit.vue'
const loading = ref(false)
const tableData = ref([])
const total = ref(0)
const pageNo = ref(1)
const pageSize = ref(20)
const keyword = ref('')
const showEdit = ref(false)
const showSteps = ref(false)
const stepsTitle = ref('')
const stepsData = ref([])
const stepTypeMap = { exam: '检查', treatment: '治疗', nursing: '护理', medication: '用药' }
const stepColorMap = { exam: 'warning', treatment: 'primary', nursing: 'success', medication: 'danger' }
const stepTypeName = (t) => stepTypeMap[t] || t
const stepTypeColor = (t) => stepColorMap[t] || 'info'
async function loadData() {
loading.value = true
try {
const r = await getPathwayPage({ keyword: keyword.value, pageNo: pageNo.value, pageSize: pageSize.value })
tableData.value = r.data?.records || []
total.value = r.data?.total || 0
} finally {
loading.value = false
}
}
async function viewSteps(row) {
stepsTitle.value = `${row.pathwayName} - 路径步骤`
stepsData.value = []
showSteps.value = true
const r = await getPathwaySteps(row.id)
stepsData.value = r.data || []
}
onMounted(() => loadData())
</script>

View File

@@ -0,0 +1,102 @@
<template>
<el-dialog v-model="visible" title="新增实体关系" width="600px" append-to-body @close="handleClose">
<el-form ref="formRef" :model="form" :rules="rules" label-width="110px">
<el-form-item label="源实体类型" prop="sourceType">
<el-select v-model="form.sourceType" placeholder="请选择" style="width:100%">
<el-option v-for="t in entityTypes" :key="t.code" :label="t.name" :value="t.code" />
</el-select>
</el-form-item>
<el-form-item label="源实体ID" prop="sourceId">
<el-input v-model="form.sourceId" placeholder="请输入源实体ID" />
</el-form-item>
<el-form-item label="目标实体类型" prop="targetType">
<el-select v-model="form.targetType" placeholder="请选择" style="width:100%">
<el-option v-for="t in entityTypes" :key="t.code" :label="t.name" :value="t.code" />
</el-select>
</el-form-item>
<el-form-item label="目标实体ID" prop="targetId">
<el-input v-model="form.targetId" placeholder="请输入目标实体ID" />
</el-form-item>
<el-form-item label="关系类型" prop="relationType">
<el-select v-model="form.relationType" placeholder="请选择" style="width:100%">
<el-option v-for="r in relationTypes" :key="r.code" :label="r.name" :value="r.code" />
</el-select>
</el-form-item>
<el-form-item label="关系强度">
<el-slider v-model="form.relationStrength" :min="0" :max="1" :step="0.1" show-input />
</el-form-item>
<el-form-item label="描述">
<el-input v-model="form.description" type="textarea" :rows="3" placeholder="关系描述" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" :loading="submitting" @click="handleSubmit">确定</el-button>
</template>
</el-dialog>
</template>
<script setup>
import { ref, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { createRelation } from '@/api/knowledgegraph/api'
const props = defineProps({ visible: Boolean })
const emit = defineEmits(['update:visible', 'success'])
const formRef = ref(null)
const submitting = ref(false)
const entityTypes = [
{ code: 'disease', name: '疾病' },
{ code: 'symptom', name: '症状' },
{ code: 'drug', name: '药物' },
{ code: 'exam', name: '检查' }
]
const relationTypes = [
{ code: 'CAUSES', name: '导致' },
{ code: 'TREATS', name: '治疗' },
{ code: 'CONTRAINDICATES', name: '禁忌' },
{ code: 'INTERACTS_WITH', name: '相互作用' },
{ code: 'REQUIRES_EXAM', name: '需要检查' },
{ code: 'HAS_SYMPTOM', name: '具有症状' },
{ code: 'SIDE_EFFECT', name: '副作用' },
{ code: 'ALTERNATIVE', name: '替代' }
]
const defaultForm = () => ({
sourceType: '', sourceId: '', targetType: '', targetId: '',
relationType: '', relationStrength: 1.0, description: ''
})
const form = ref(defaultForm())
const rules = {
sourceType: [{ required: true, message: '请选择源实体类型', trigger: 'change' }],
sourceId: [{ required: true, message: '请输入源实体ID', trigger: 'blur' }],
targetType: [{ required: true, message: '请选择目标实体类型', trigger: 'change' }],
targetId: [{ required: true, message: '请输入目标实体ID', trigger: 'blur' }],
relationType: [{ required: true, message: '请选择关系类型', trigger: 'change' }]
}
watch(() => props.visible, (v) => {
if (v) form.value = defaultForm()
})
function handleClose() {
emit('update:visible', false)
}
async function handleSubmit() {
await formRef.value.validate()
submitting.value = true
try {
await createRelation(form.value)
ElMessage.success('创建成功')
emit('update:visible', false)
emit('success')
} finally {
submitting.value = false
}
}
</script>

View File

@@ -0,0 +1,136 @@
<template>
<div style="padding:16px">
<div style="margin-bottom:16px;display:flex;justify-content:space-between;align-items:center">
<span style="font-size:18px;font-weight:bold">实体关系管理</span>
<div>
<el-button type="primary" @click="loadData">刷新</el-button>
<el-button type="success" @click="showForm = true">新增关系</el-button>
</div>
</div>
<el-card shadow="never" style="margin-bottom:16px">
<el-form inline>
<el-form-item label="源实体类型">
<el-select v-model="filters.sourceType" clearable placeholder="全部" style="width:130px">
<el-option v-for="t in entityTypes" :key="t.code" :label="t.name" :value="t.code" />
</el-select>
</el-form-item>
<el-form-item label="目标实体类型">
<el-select v-model="filters.targetType" clearable placeholder="全部" style="width:130px">
<el-option v-for="t in entityTypes" :key="t.code" :label="t.name" :value="t.code" />
</el-select>
</el-form-item>
<el-form-item label="关系类型">
<el-select v-model="filters.relationType" clearable placeholder="全部" style="width:150px">
<el-option v-for="r in relationTypes" :key="r.code" :label="r.name" :value="r.code" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="loadData">搜索</el-button>
<el-button @click="resetFilters">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<el-table v-loading="loading" :data="tableData" border stripe>
<el-table-column prop="sourceType" label="源实体类型" width="120" align="center">
<template #default="{row}">
<el-tag size="small" :style="{background: typeColor(row.sourceType), color:'#fff', border:'none'}">
{{ typeName(row.sourceType) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="sourceId" label="源实体ID" width="140" show-overflow-tooltip />
<el-table-column prop="targetType" label="目标实体类型" width="120" align="center">
<template #default="{row}">
<el-tag size="small" :style="{background: typeColor(row.targetType), color:'#fff', border:'none'}">
{{ typeName(row.targetType) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="targetId" label="目标实体ID" width="140" show-overflow-tooltip />
<el-table-column prop="relationType" label="关系类型" width="140" align="center">
<template #default="{row}">
<el-tag size="small" type="warning">{{ relationName(row.relationType) }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="relationStrength" label="强度" width="80" align="center" />
<el-table-column prop="description" label="描述" min-width="150" show-overflow-tooltip />
<el-table-column prop="createTime" label="创建时间" width="160" />
</el-table>
<el-pagination
v-model:current-page="pageNo"
v-model:page-size="pageSize"
style="margin-top:12px;justify-content:flex-end"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="loadData"
@current-change="loadData"
/>
<RelationForm v-model:visible="showForm" @success="loadData" />
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { getRelationPage } from '@/api/knowledgegraph/api'
import RelationForm from './RelationForm.vue'
const loading = ref(false)
const tableData = ref([])
const total = ref(0)
const pageNo = ref(1)
const pageSize = ref(20)
const showForm = ref(false)
const filters = ref({ sourceType: '', targetType: '', relationType: '' })
const entityTypes = [
{ code: 'disease', name: '疾病' },
{ code: 'symptom', name: '症状' },
{ code: 'drug', name: '药物' },
{ code: 'exam', name: '检查' }
]
const relationTypes = [
{ code: 'CAUSES', name: '导致' },
{ code: 'TREATS', name: '治疗' },
{ code: 'CONTRAINDICATES', name: '禁忌' },
{ code: 'INTERACTS_WITH', name: '相互作用' },
{ code: 'REQUIRES_EXAM', name: '需要检查' },
{ code: 'HAS_SYMPTOM', name: '具有症状' },
{ code: 'SIDE_EFFECT', name: '副作用' },
{ code: 'ALTERNATIVE', name: '替代' }
]
const typeColorMap = { disease: '#f56c6c', symptom: '#409eff', drug: '#67c23a', exam: '#e6a23c' }
const typeColor = (t) => typeColorMap[t] || '#909399'
const typeName = (t) => (entityTypes.find(e => e.code === t) || {}).name || t
const relationName = (r) => (relationTypes.find(e => e.code === r) || {}).name || r
function resetFilters() {
filters.value = { sourceType: '', targetType: '', relationType: '' }
loadData()
}
async function loadData() {
loading.value = true
try {
const r = await getRelationPage({
sourceType: filters.value.sourceType,
targetType: filters.value.targetType,
relationType: filters.value.relationType,
pageNo: pageNo.value,
pageSize: pageSize.value
})
tableData.value = r.data?.records || []
total.value = r.data?.total || 0
} finally {
loading.value = false
}
}
onMounted(() => loadData())
</script>

View File

@@ -0,0 +1,139 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryFormRef" :inline="true" v-show="showSearch" label-width="80px">
<el-form-item label="关键词" prop="keyword">
<el-input v-model="queryParams.keyword" placeholder="症状名称/编码" clearable style="width:200px" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="症状类型" prop="symptomType">
<el-input v-model="queryParams.symptomType" placeholder="症状类型" clearable style="width:160px" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-col>
</el-row>
<el-table v-loading="loading" :data="tableData" border stripe>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="symptomCode" label="症状编码" width="140" show-overflow-tooltip />
<el-table-column prop="symptomName" label="症状名称" min-width="180" show-overflow-tooltip />
<el-table-column prop="bodyPart" label="所属部位" width="120" show-overflow-tooltip />
<el-table-column prop="symptomType" label="症状类型" width="120" align="center" />
<el-table-column prop="createTime" label="创建时间" width="170" />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="{ row }">
<el-button link type="primary" icon="Edit" @click="handleEdit(row)">编辑</el-button>
<el-button link type="danger" icon="Delete" @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
<el-dialog v-model="showForm" :title="form.id ? '编辑症状' : '新增症状'" width="550px" @close="resetForm">
<el-form ref="formRef" :model="form" :rules="formRules" label-width="90px">
<el-form-item label="症状编码" prop="symptomCode">
<el-input v-model="form.symptomCode" placeholder="请输入症状编码" />
</el-form-item>
<el-form-item label="症状名称" prop="symptomName">
<el-input v-model="form.symptomName" placeholder="请输入症状名称" />
</el-form-item>
<el-form-item label="所属部位" prop="bodyPart">
<el-input v-model="form.bodyPart" placeholder="请输入所属部位" />
</el-form-item>
<el-form-item label="症状类型" prop="symptomType">
<el-input v-model="form.symptomType" placeholder="请输入症状类型" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showForm = false">取消</el-button>
<el-button type="primary" :loading="submitting" @click="handleSubmit">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup name="KgSymptomList">
import { ref, reactive, onMounted } from 'vue'
import { getSymptomPage, addSymptom, updateSymptom, deleteSymptom } from '@/api/knowledgegraph/api'
import { ElMessage, ElMessageBox } from 'element-plus'
const { proxy } = getCurrentInstance()
const tableData = ref([])
const loading = ref(false)
const showSearch = ref(true)
const total = ref(0)
const showForm = ref(false)
const submitting = ref(false)
const formRef = ref(null)
const queryParams = reactive({ pageNo: 1, pageSize: 10, keyword: '', symptomType: '' })
const form = reactive({ id: undefined, symptomCode: '', symptomName: '', bodyPart: '', symptomType: '' })
const formRules = {
symptomCode: [{ required: true, message: '请输入症状编码', trigger: 'blur' }],
symptomName: [{ required: true, message: '请输入症状名称', trigger: 'blur' }]
}
async function getList() {
loading.value = true
try {
const res = await getSymptomPage(queryParams)
if (res.code === 200) {
tableData.value = res.data?.records || []
total.value = res.data?.total || 0
}
} finally {
loading.value = false
}
}
function handleQuery() { queryParams.pageNo = 1; getList() }
function resetQuery() { queryParams.keyword = ''; queryParams.symptomType = ''; handleQuery() }
function handleAdd() {
Object.assign(form, { id: undefined, symptomCode: '', symptomName: '', bodyPart: '', symptomType: '' })
showForm.value = true
}
function handleEdit(row) {
Object.assign(form, row)
showForm.value = true
}
function resetForm() {
formRef.value?.resetFields()
}
function handleDelete(row) {
ElMessageBox.confirm('确认删除该症状吗?', '提示', { type: 'warning' })
.then(() => deleteSymptom(row.id))
.then(() => { ElMessage.success('删除成功'); getList() })
}
async function handleSubmit() {
try { await formRef.value.validate() } catch { return }
submitting.value = true
try {
if (form.id) {
await updateSymptom({ ...form })
ElMessage.success('更新成功')
} else {
await addSymptom({ ...form })
ElMessage.success('创建成功')
}
showForm.value = false
getList()
} finally {
submitting.value = false
}
}
onMounted(() => getList())
</script>