feat(ehcard): add electronic health card module (T13.3)

This commit is contained in:
2026-06-18 15:56:12 +08:00
parent 5aaa4ee883
commit f3aac08c4e
14 changed files with 505 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
package com.healthlink.his.web.ehcard.appservice;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.healthlink.his.ehcard.domain.EhcardCard;
public interface IEhcardAppService {
void apply(EhcardCard card);
IPage<EhcardCard> page(String status, String patientName, Integer pageNum, Integer pageSize);
void lock(Long id, String reason);
void unlock(Long id);
}

View File

@@ -0,0 +1,99 @@
package com.healthlink.his.web.ehcard.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.ehcard.domain.EhcardCard;
import com.healthlink.his.ehcard.domain.EhcardUsageLog;
import com.healthlink.his.ehcard.service.IEhcardCardService;
import com.healthlink.his.ehcard.service.IEhcardUsageLogService;
import com.healthlink.his.web.ehcard.appservice.IEhcardAppService;
import com.core.common.utils.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.Date;
import java.util.UUID;
@Service
public class EhcardAppServiceImpl implements IEhcardAppService {
@Autowired
private IEhcardCardService cardService;
@Autowired
private IEhcardUsageLogService usageLogService;
@Override
public void apply(EhcardCard card) {
card.setCardNo("EHC" + System.currentTimeMillis());
card.setCardType("HEALTH");
card.setStatus("ACTIVE");
card.setApplyTime(new Date());
cardService.save(card);
EhcardUsageLog log = new EhcardUsageLog();
log.setCardId(card.getId());
log.setPatientId(card.getPatientId());
log.setUsageType("APPLY");
log.setUsageDesc("申请电子健康卡");
log.setOperatorName(SecurityUtils.getUsername());
log.setUsageTime(new Date());
usageLogService.save(log);
}
@Override
public IPage<EhcardCard> page(String status, String patientName, Integer pageNum, Integer pageSize) {
LambdaQueryWrapper<EhcardCard> w = new LambdaQueryWrapper<>();
if (StringUtils.hasText(status)) {
w.eq(EhcardCard::getStatus, status);
}
if (StringUtils.hasText(patientName)) {
w.like(EhcardCard::getPatientName, patientName);
}
w.orderByDesc(EhcardCard::getCreateTime);
return cardService.page(new Page<>(pageNum, pageSize), w);
}
@Override
public void lock(Long id, String reason) {
EhcardCard card = cardService.getById(id);
if (card == null) {
throw new RuntimeException("电子健康卡不存在");
}
card.setStatus("LOCKED");
card.setLockTime(new Date());
card.setLockReason(reason);
cardService.updateById(card);
EhcardUsageLog log = new EhcardUsageLog();
log.setCardId(id);
log.setPatientId(card.getPatientId());
log.setUsageType("LOCK");
log.setUsageDesc("锁定: " + reason);
log.setOperatorName(SecurityUtils.getUsername());
log.setUsageTime(new Date());
usageLogService.save(log);
}
@Override
public void unlock(Long id) {
EhcardCard card = cardService.getById(id);
if (card == null) {
throw new RuntimeException("电子健康卡不存在");
}
card.setStatus("ACTIVE");
card.setUnlockTime(new Date());
cardService.updateById(card);
EhcardUsageLog log = new EhcardUsageLog();
log.setCardId(id);
log.setPatientId(card.getPatientId());
log.setUsageType("UNLOCK");
log.setUsageDesc("解锁");
log.setOperatorName(SecurityUtils.getUsername());
log.setUsageTime(new Date());
usageLogService.save(log);
}
}

View File

@@ -0,0 +1,53 @@
package com.healthlink.his.web.ehcard.controller;
import com.core.common.core.domain.AjaxResult;
import com.healthlink.his.ehcard.domain.EhcardCard;
import com.healthlink.his.web.ehcard.appservice.IEhcardAppService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@Tag(name = "电子健康卡管理")
@RestController
@RequestMapping("/api/v1/ehcard")
public class EhcardController {
@Autowired
private IEhcardAppService ehcardAppService;
@Operation(summary = "申请电子健康卡")
@PreAuthorize("@ss.hasPermi('basicmanage:ehcard:edit')")
@PostMapping("/apply")
public AjaxResult apply(@RequestBody EhcardCard card) {
ehcardAppService.apply(card);
return AjaxResult.success();
}
@Operation(summary = "电子健康卡分页")
@PreAuthorize("@ss.hasPermi('basicmanage:ehcard:list')")
@GetMapping("/page")
public AjaxResult page(@RequestParam(required = false) String status,
@RequestParam(required = false) String patientName,
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) {
return AjaxResult.success(ehcardAppService.page(status, patientName, pageNum, pageSize));
}
@Operation(summary = "锁定电子健康卡")
@PreAuthorize("@ss.hasPermi('basicmanage:ehcard:edit')")
@PostMapping("/lock")
public AjaxResult lock(@RequestParam Long id, @RequestParam(required = false) String reason) {
ehcardAppService.lock(id, reason);
return AjaxResult.success();
}
@Operation(summary = "解锁电子健康卡")
@PreAuthorize("@ss.hasPermi('basicmanage:ehcard:edit')")
@PostMapping("/unlock")
public AjaxResult unlock(@RequestParam Long id) {
ehcardAppService.unlock(id);
return AjaxResult.success();
}
}

View File

@@ -0,0 +1,42 @@
CREATE TABLE IF NOT EXISTS ehcard_card (
id BIGINT PRIMARY KEY,
patient_id BIGINT NOT NULL,
patient_name VARCHAR(64),
id_card VARCHAR(32),
phone VARCHAR(20),
card_no VARCHAR(64) NOT NULL,
card_type VARCHAR(20) NOT NULL DEFAULT 'HEALTH',
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
qr_code TEXT,
apply_time TIMESTAMP,
lock_time TIMESTAMP,
unlock_time TIMESTAMP,
lock_reason VARCHAR(256),
create_by VARCHAR(64),
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_by VARCHAR(64),
update_time TIMESTAMP,
tenant_id INT DEFAULT 1,
delete_flag VARCHAR(1) DEFAULT '0'
);
CREATE TABLE IF NOT EXISTS ehcard_usage_log (
id BIGINT PRIMARY KEY,
card_id BIGINT NOT NULL,
patient_id BIGINT,
usage_type VARCHAR(32) NOT NULL,
usage_desc VARCHAR(256),
operator_name VARCHAR(64),
usage_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
create_by VARCHAR(64),
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_by VARCHAR(64),
update_time TIMESTAMP,
tenant_id INT DEFAULT 1,
delete_flag VARCHAR(1) DEFAULT '0'
);
CREATE INDEX idx_ehcard_card_patient ON ehcard_card(patient_id);
CREATE INDEX idx_ehcard_card_no ON ehcard_card(card_no);
CREATE INDEX idx_ehcard_card_status ON ehcard_card(status);
CREATE INDEX idx_ehcard_log_card ON ehcard_usage_log(card_id);

View File

@@ -0,0 +1,39 @@
package com.healthlink.his.ehcard.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.annotation.JsonFormat;
import lombok.Data;
import lombok.EqualsAndHashCode;
import tools.jackson.databind.annotation.JsonSerialize;
import tools.jackson.databind.ser.std.ToStringSerializer;
import java.util.Date;
@Data
@TableName("ehcard_card")
@EqualsAndHashCode(callSuper = true)
public class EhcardCard extends HisBaseEntity {
@TableId(value = "id", type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
@JsonSerialize(using = ToStringSerializer.class)
private Long patientId;
private String patientName;
private String idCard;
private String phone;
private String cardNo;
private String cardType;
private String status;
private String qrCode;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date applyTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date lockTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date unlockTime;
private String lockReason;
}

View File

@@ -0,0 +1,30 @@
package com.healthlink.his.ehcard.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 lombok.Data;
import lombok.EqualsAndHashCode;
import tools.jackson.databind.annotation.JsonSerialize;
import tools.jackson.databind.ser.std.ToStringSerializer;
import java.util.Date;
@Data
@TableName("ehcard_usage_log")
@EqualsAndHashCode(callSuper = true)
public class EhcardUsageLog extends HisBaseEntity {
@TableId(value = "id", type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
@JsonSerialize(using = ToStringSerializer.class)
private Long cardId;
@JsonSerialize(using = ToStringSerializer.class)
private Long patientId;
private String usageType;
private String usageDesc;
private String operatorName;
private Date usageTime;
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,11 @@
package com.healthlink.his.ehcard.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.ehcard.domain.EhcardCard;
import com.healthlink.his.ehcard.mapper.EhcardCardMapper;
import com.healthlink.his.ehcard.service.IEhcardCardService;
import org.springframework.stereotype.Service;
@Service
public class EhcardCardServiceImpl extends ServiceImpl<EhcardCardMapper, EhcardCard> implements IEhcardCardService {
}

View File

@@ -0,0 +1,11 @@
package com.healthlink.his.ehcard.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.ehcard.domain.EhcardUsageLog;
import com.healthlink.his.ehcard.mapper.EhcardUsageLogMapper;
import com.healthlink.his.ehcard.service.IEhcardUsageLogService;
import org.springframework.stereotype.Service;
@Service
public class EhcardUsageLogServiceImpl extends ServiceImpl<EhcardUsageLogMapper, EhcardUsageLog> implements IEhcardUsageLogService {
}

View File

@@ -0,0 +1,160 @@
<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>
<el-button type="primary" @click="handleApply">申请健康卡</el-button>
</div>
<el-card shadow="never" style="margin-bottom: 16px">
<div style="display: flex; gap: 8px; flex-wrap: wrap; align-items: center">
<el-input v-model="query.patientName" placeholder="患者姓名" clearable style="width: 160px" />
<el-select v-model="query.status" placeholder="状态" clearable style="width: 120px">
<el-option label="正常" value="ACTIVE" />
<el-option label="已锁定" value="LOCKED" />
</el-select>
<el-button type="primary" @click="loadPage">查询</el-button>
<el-button @click="resetQuery">重置</el-button>
</div>
</el-card>
<el-card shadow="never">
<el-table :data="tableData" v-loading="loading" border stripe>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="cardNo" label="卡号" width="180" />
<el-table-column prop="patientName" label="患者姓名" width="120" />
<el-table-column prop="idCard" label="身份证号" width="180" />
<el-table-column prop="phone" label="手机号" width="140" />
<el-table-column prop="cardType" label="卡类型" width="100">
<template #default="{ row }">
<el-tag v-if="row.cardType === 'HEALTH'" type="primary">健康卡</el-tag>
<el-tag v-else>{{ row.cardType }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="100">
<template #default="{ row }">
<el-tag v-if="row.status === 'ACTIVE'" type="success">正常</el-tag>
<el-tag v-else-if="row.status === 'LOCKED'" type="danger">已锁定</el-tag>
<el-tag v-else>{{ row.status }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="applyTime" label="申请时间" width="170" />
<el-table-column label="操作" width="180" fixed="right">
<template #default="{ row }">
<el-button v-if="row.status === 'ACTIVE'" type="danger" size="small" @click="handleLock(row)">锁定</el-button>
<el-button v-if="row.status === 'LOCKED'" type="success" size="small" @click="handleUnlock(row)">解锁</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
v-model:current-page="query.pageNum"
v-model:page-size="query.pageSize"
:page-sizes="[10, 20, 50]"
:total="total"
layout="total, sizes, prev, pager, next"
style="margin-top: 16px; justify-content: flex-end"
@size-change="loadPage"
@current-change="loadPage"
/>
</el-card>
<el-dialog v-model="applyVisible" title="申请电子健康卡" width="500px">
<el-form :model="applyForm" label-width="80px">
<el-form-item label="患者ID">
<el-input v-model="applyForm.patientId" placeholder="患者ID" />
</el-form-item>
<el-form-item label="姓名">
<el-input v-model="applyForm.patientName" placeholder="患者姓名" />
</el-form-item>
<el-form-item label="身份证号">
<el-input v-model="applyForm.idCard" placeholder="身份证号" />
</el-form-item>
<el-form-item label="手机号">
<el-input v-model="applyForm.phone" placeholder="手机号" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="applyVisible = false">取消</el-button>
<el-button type="primary" @click="submitApply">确定</el-button>
</template>
</el-dialog>
<el-dialog v-model="lockVisible" title="锁定健康卡" width="400px">
<el-form label-width="80px">
<el-form-item label="锁定原因">
<el-input v-model="lockReason" type="textarea" :rows="3" placeholder="请输入锁定原因" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="lockVisible = false">取消</el-button>
<el-button type="primary" @click="confirmLock">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import { applyEhcard, getEhcardPage, lockEhcard, unlockEhcard } from './api'
const loading = ref(false)
const tableData = ref([])
const total = ref(0)
const query = ref({ patientName: '', status: '', pageNum: 1, pageSize: 10 })
const applyVisible = ref(false)
const applyForm = ref({ patientId: '', patientName: '', idCard: '', phone: '' })
const lockVisible = ref(false)
const lockReason = ref('')
const lockRow = ref(null)
async function loadPage() {
loading.value = true
try {
const res = await getEhcardPage(query.value)
tableData.value = res.data?.records || []
total.value = res.data?.total || 0
} finally {
loading.value = false
}
}
function resetQuery() {
query.value = { patientName: '', status: '', pageNum: 1, pageSize: 10 }
loadPage()
}
function handleApply() {
applyForm.value = { patientId: '', patientName: '', idCard: '', phone: '' }
applyVisible.value = true
}
async function submitApply() {
await applyEhcard(applyForm.value)
ElMessage.success('申请成功')
applyVisible.value = false
loadPage()
}
function handleLock(row) {
lockRow.value = row
lockReason.value = ''
lockVisible.value = true
}
async function confirmLock() {
await lockEhcard({ id: lockRow.value.id, reason: lockReason.value })
ElMessage.success('锁定成功')
lockVisible.value = false
loadPage()
}
async function handleUnlock(row) {
await unlockEhcard({ id: row.id })
ElMessage.success('解锁成功')
loadPage()
}
onMounted(() => loadPage())
</script>

View File

@@ -0,0 +1,17 @@
import request from '@/utils/request'
export function applyEhcard(data) {
return request({ url: '/api/v1/ehcard/apply', method: 'post', data })
}
export function getEhcardPage(params) {
return request({ url: '/api/v1/ehcard/page', method: 'get', params })
}
export function lockEhcard(params) {
return request({ url: '/api/v1/ehcard/lock', method: 'post', params })
}
export function unlockEhcard(params) {
return request({ url: '/api/v1/ehcard/unlock', method: 'post', params })
}