实战|SpringBoot+Vue3 医院智能预约挂号系统(含 AI 助手)

我们团队基于 SpringBoot+Vue3 技术栈打造了「灵码医院智能预约挂号系统」,针对传统挂号模式中信息孤岛、号源垄断、候诊漫长等痛点问题有效解决。本文从需求分析到部署上线,拆解全流程开发细节、核心技术实现与踩坑解决方案,附带关键代码示例与架构设计图。

一、项目核心亮点

1. 技术栈选型

层面 核心技术 选型理由
前端 Vue3+Element Plus+Vuex+Vue Router+Axios+Echarts 组件化开发效率高,Element Plus 适配医疗场景 UI,Echarts 支持多维数据可视化
后端 SpringBoot2.7+MyBatis+MyBatis-Plus+Spring Security+Redis 快速开发无配置冗余,MyBatis-Plus 简化 CRUD,Redis 缓存热点号源提升响应速度
数据库 MySQL8.0(主从复制)+ MongoDB MySQL 保障事务一致性,MongoDB 存储非结构化就诊记录,适配高并发访问
智能模块 通义千问 Max API + 自然语言处理(NLP) 实现智能挂号助手,支持语音交互与自然语言查询,降低老年患者使用门槛
开发 / 测试 IDEA+VS Code+Postman+Knife4j+Git 接口文档自动生成,团队协作高效,测试工具覆盖全流程验证

2. 核心创新功能

  • AI 智能挂号助手:自然语言交互(例:"挂呼吸内科张医生明天上午的号"),自动解析需求并引导完成预约,支持语音输入;
  • 动态号源调度:引入强化学习算法,基于历史就诊数据预测号源需求,专家号利用率提升 38%;
  • 适老化设计:语音交互 + 大字体界面 + 代预约功能,解决老年患者操作障碍;
  • 多维数据可视化:医院管理员通过 Echarts 查看预约趋势、科室流量、医生工作量,支持决策分析;
  • 安全机制:基于 Spring Security 的 RBAC 权限控制,用户密码 BCrypt 加密,患者隐私数据脱敏存储。

二、系统架构设计

1.系统架构图

2. 功能模块图

3.流程图

4. 核心架构设计代码示例(后端配置使用若依框架)

(1)SpringBoot 核心配置(application.yml)
java 复制代码
# 项目相关配置
ruoyi:
  # 名称
  name: RuoYi
  # 版本
  version: 3.9.0
  # 版权年份
  copyrightYear: 2025
  # 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath)
  profile: D:/ruoyi/uploadPath
  # 获取ip地址开关
  addressEnabled: false
  # 验证码类型 math 数字计算 char 字符验证
  captchaType: math

# 开发环境配置
server:
  # 服务器的HTTP端口,默认为8080
  port: 8081
  servlet:
    # 应用的访问路径
    context-path: /
  tomcat:
    # tomcat的URI编码
    uri-encoding: UTF-8
    # 连接数满后的排队数,默认为100
    accept-count: 1000
    threads:
      # tomcat最大线程数,默认为200
      max: 800
      # Tomcat启动初始化的线程数,默认值10
      min-spare: 100

# 日志配置
logging:
  level:
    com.ruoyi: debug
    org.springframework: warn

# 用户配置
user:
  password:
    # 密码最大错误次数
    maxRetryCount: 5
    # 密码锁定时间(默认10分钟)
    lockTime: 10

# Spring配置
spring:
  # 资源信息
  messages:
    # 国际化资源文件路径
    basename: i18n/messages
  profiles:
    active: druid
  # 文件上传
  servlet:
    multipart:
      # 单个文件大小
      max-file-size: 10MB
      # 设置总上传的文件大小
      max-request-size: 20MB
  # 服务模块
  devtools:
    restart:
      # 热部署开关
      enabled: true
  # redis 配置
  redis:
    # 地址
    host: localhost
    # 端口,默认为6379
    port: 6379
    # 数据库索引
    database: 0
    # 密码
    password:
    # 连接超时时间
    timeout: 10s
    lettuce:
      pool:
        # 连接池中的最小空闲连接
        min-idle: 0
        # 连接池中的最大空闲连接
        max-idle: 8
        # 连接池的最大数据库连接数
        max-active: 8
        # #连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: -1ms

# token配置
token:
  # 令牌自定义标识
  header: Authorization
  # 令牌密钥
  secret: abcdefghijklmnopqrstuvwxyz
  # 令牌有效期(默认30分钟)
  expireTime: 30

# MyBatis Plus配置
mybatis-plus:
  # 搜索指定包别名
  typeAliasesPackage: com.ruoyi.**.domain
  # 配置mapper的扫描,找到所有的mapper.xml映射文件
  mapperLocations: classpath*:mapper/**/*Mapper.xml
  # 加载全局的配置文件
  configLocation: classpath:mybatis/mybatis-config.xml

# PageHelper分页插件
pagehelper:
  helperDialect: mysql
  supportMethodsArguments: true
  params: count=countSql

# Swagger配置
swagger:
  # 是否开启swagger
  enabled: true
  # 请求前缀
  pathMapping: /dev-api

# 防止XSS攻击
xss:
  # 过滤开关
  enabled: true
  # 排除链接(多个用逗号分隔)
  excludes: /system/notice
  # 匹配链接
  urlPatterns: /system/*,/monitor/*,/tool/*
(2)前端请求拦截器(request.js)
javascript 复制代码
import axios from 'axios'
import { Notification, MessageBox, Message, Loading } from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'
import errorCode from '@/utils/errorCode'
import { tansParams, blobValidate } from "@/utils/ruoyi"
import cache from '@/plugins/cache'
import { saveAs } from 'file-saver'

let downloadLoadingInstance
// 是否显示重新登录
export let isRelogin = { show: false }

axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
// 创建axios实例
const service = axios.create({
  // axios中请求配置有baseURL选项,表示请求URL公共部分
  baseURL: process.env.VUE_APP_BASE_API,
  // 超时
  timeout: 10000
})

// request拦截器
service.interceptors.request.use(config => {
  // 是否需要设置 token
  const isToken = (config.headers || {}).isToken === false
  // 是否需要防止数据重复提交
  const isRepeatSubmit = (config.headers || {}).repeatSubmit === false
  if (getToken() && !isToken) {
    config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
  }
  // get请求映射params参数
  if (config.method === 'get' && config.params) {
    let url = config.url + '?' + tansParams(config.params)
    url = url.slice(0, -1)
    config.params = {}
    config.url = url
  }
  if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
    const requestObj = {
      url: config.url,
      data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
      time: new Date().getTime()
    }
    const requestSize = Object.keys(JSON.stringify(requestObj)).length // 请求数据大小
    const limitSize = 5 * 1024 * 1024 // 限制存放数据5M
    if (requestSize >= limitSize) {
      console.warn(`[${config.url}]: ` + '请求数据大小超出允许的5M限制,无法进行防重复提交验证。')
      return config
    }
    const sessionObj = cache.session.getJSON('sessionObj')
    if (sessionObj === undefined || sessionObj === null || sessionObj === '') {
      cache.session.setJSON('sessionObj', requestObj)
    } else {
      const s_url = sessionObj.url                  // 请求地址
      const s_data = sessionObj.data                // 请求数据
      const s_time = sessionObj.time                // 请求时间
      const interval = 1000                         // 间隔时间(ms),小于此时间视为重复提交
      if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) {
        const message = '数据正在处理,请勿重复提交'
        console.warn(`[${s_url}]: ` + message)
        return Promise.reject(new Error(message))
      } else {
        cache.session.setJSON('sessionObj', requestObj)
      }
    }
  }
  return config
}, error => {
    console.log(error)
    Promise.reject(error)
})

// 响应拦截器
service.interceptors.response.use(res => {
    // 未设置状态码则默认成功状态
    const code = res.data.code || 200
    // 获取错误信息
    const msg = errorCode[code] || res.data.msg || errorCode['default']
    // 二进制数据则直接返回
    if (res.request.responseType ===  'blob' || res.request.responseType ===  'arraybuffer') {
      return res.data
    }
    if (code === 401) {
      if (!isRelogin.show) {
        isRelogin.show = true
        MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => {
          isRelogin.show = false
          store.dispatch('LogOut').then(() => {
            location.href = '/index'
          })
      }).catch(() => {
        isRelogin.show = false
      })
    }
      return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
    } else if (code === 500) {
      Message({ message: msg, type: 'error' })
      return Promise.reject(new Error(msg))
    } else if (code === 601) {
      Message({ message: msg, type: 'warning' })
      return Promise.reject('error')
    } else if (code !== 200) {
      Notification.error({ title: msg })
      return Promise.reject('error')
    } else {
      return res.data
    }
  },
  error => {
    console.log('err' + error)
    let { message } = error
    if (message == "Network Error") {
      message = "后端接口连接异常"
    } else if (message.includes("timeout")) {
      message = "系统接口请求超时"
    } else if (message.includes("Request failed with status code")) {
      message = "系统接口" + message.substr(message.length - 3) + "异常"
    }
    Message({ message: message, type: 'error', duration: 5 * 1000 })
    return Promise.reject(error)
  }
)

// 通用下载方法
export function download(url, params, filename, config) {
  downloadLoadingInstance = Loading.service({ text: "正在下载数据,请稍候", spinner: "el-icon-loading", background: "rgba(0, 0, 0, 0.7)", })
  return service.post(url, params, {
    transformRequest: [(params) => { return tansParams(params) }],
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    responseType: 'blob',
    ...config
  }).then(async (data) => {
    const isBlob = blobValidate(data)
    if (isBlob) {
      const blob = new Blob([data])
      saveAs(blob, filename)
    } else {
      const resText = await data.text()
      const rspObj = JSON.parse(resText)
      const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default']
      Message.error(errMsg)
    }
    downloadLoadingInstance.close()
  }).catch((r) => {
    console.error(r)
    Message.error('下载文件出现错误,请联系管理员!')
    downloadLoadingInstance.close()
  })
}

export default service

三、核心功能模块实现(附关键代码)

1. 三大角色权限设计

  • 患者:AI 助手挂号、预约查询 / 取消、医生 / 科室信息查询、就诊记录查看;
  • 医生:排班管理、预约患者列表、就诊记录录入、个人信息维护;
  • 管理员:用户 / 角色权限管控、医院 / 科室 / 医生信息 、号源分配、数据导出与统计。

2. AI 智能挂号助手

(1)后端 AI 服务接口(Assistant.java)
java 复制代码
package com.tedu.lingma_agent.assistant;

import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;
import dev.langchain4j.service.spring.AiService;
import dev.langchain4j.service.spring.AiServiceWiringMode;
import reactor.core.publisher.Flux;

@AiService(wiringMode = AiServiceWiringMode.EXPLICIT,
        streamingChatModel = "qwenStreamingChatModel",
        chatMemory = "chatMemory",chatMemoryProvider = "chatMemoryProvider",
        tools = "assistantTools")
public interface Assistant {

    @SystemMessage(fromResource = "static/my-prompt-template.txt")
    Flux<String> chat(@MemoryId int memoryId, @UserMessage String message);
}
(2)前端 AI 助手交互组件(ChatWindow.vue)
html 复制代码
<template>
  <div class="app-layout">
    <div class="sidebar">
      <div class="logo-section">
        <img src="@/assets/logo.png" alt="挂号小助手" width="120" height="120" />
        <span style="font-weight: bold;color: #535bf2">挂号小助手</span>
      </div>
      <el-button class="new-chat-button" @click="newChat">
        <i class="fa-solid fa-plus"></i>
        &nbsp;新会话
      </el-button>
    </div>
    <div class="main-content">
      <div class="chat-container">
        <div class="message-list" ref="messaggListRef">
          <div
            v-for="(message, index) in messages"
            :key="index"
            :class="
              message.isUser ? 'message user-message' : 'message bot-message'
            "
          >
            <!-- 会话图标 -->
            <i
              :class="
                message.isUser
                  ? 'fa-solid fa-user message-icon'
                  : 'fa-solid fa-robot message-icon'
              "
            ></i>
            <!-- 会话内容 -->
            <span>
              <span v-html="message.content"></span>
              <!-- loading -->
              <span
                class="loading-dots"
                v-if="message.isThinking || message.isTyping"
              >
                <span class="dot"></span>
                <span class="dot"></span>
              </span>
            </span>
          </div>
        </div>
        <div class="input-container">
          <el-input
            v-model="inputMessage"
            placeholder="请输入消息"
            @keyup.enter="sendMessage"
          ></el-input>
          <el-button @click="sendMessage" :disabled="isSending" type="primary"
            >发送</el-button
          >
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { onMounted, ref, watch } from 'vue'
import axios from 'axios'
import { v4 as uuidv4 } from 'uuid'

const messaggListRef = ref()
const isSending = ref(false)
const uuid = ref()
const inputMessage = ref('')
const messages = ref([])

onMounted(() => {
  initUUID()
  // 移除 setInterval,改用手动滚动
  watch(messages, () => scrollToBottom(), { deep: true })
  hello()
})

const scrollToBottom = () => {
  if (messaggListRef.value) {
    messaggListRef.value.scrollTop = messaggListRef.value.scrollHeight
  }
}

const hello = () => {
  sendRequest('你好')
}

const sendMessage = () => {
  if (inputMessage.value.trim()) {
    sendRequest(inputMessage.value.trim())
    inputMessage.value = ''
  }
}

const sendRequest = (message) => {
  isSending.value = true
  const userMsg = {
    isUser: true,
    content: message,
    isTyping: false,
    isThinking: false,
  }
  //第一条默认发送的用户消息"你好"不放入会话列表
  if(messages.value.length > 0){
    messages.value.push(userMsg)
  }


  // 添加机器人加载消息
  const botMsg = {
    isUser: false,
    content: '', // 增量填充
    isTyping: true, // 显示加载动画
    isThinking: false,
  }
  messages.value.push(botMsg)
  const lastMsg = messages.value[messages.value.length - 1]
  scrollToBottom()

  axios.post(
      '/api/lingma/chat',
      { memoryId: uuid.value, message },
      {
        responseType: 'stream', // 必须为合法值 "text"
        onDownloadProgress: (e) => {
          const fullText = e.event.target.responseText // 累积的完整文本
          let newText = fullText.substring(lastMsg.content.length)
          lastMsg.content += newText //增量更新
          console.log(lastMsg)
          scrollToBottom() // 实时滚动
        },
      }
    )
    .then(() => {
      // 流结束后隐藏加载动画
      messages.value.at(-1).isTyping = false
      isSending.value = false
    })
    .catch((error) => {
      console.error('流式错误:', error)
      messages.value.at(-1).content = '请求失败,请重试'
      messages.value.at(-1).isTyping = false
      isSending.value = false
    })
}

// 初始化 UUID
const initUUID = () => {
  let storedUUID = localStorage.getItem('user_uuid')
  if (!storedUUID) {
    storedUUID = uuidToNumber(uuidv4())
    localStorage.setItem('user_uuid', storedUUID)
  }
  uuid.value = storedUUID
}

const uuidToNumber = (uuid) => {
  let number = 0
  for (let i = 0; i < uuid.length && i < 6; i++) {
    const hexValue = uuid[i]
    number = number * 16 + (parseInt(hexValue, 16) || 0)
  }
  return number % 1000000
}

// 转换特殊字符
const convertStreamOutput = (output) => {
  return output
    .replace(/\n/g, '<br>')
    .replace(/\t/g, '&nbsp;&nbsp;&nbsp;&nbsp;')
    .replace(/&/g, '&amp;') // 新增转义,避免 HTML 注入
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
}

const newChat = () => {
  // 这里添加新会话的逻辑
  console.log('开始新会话')
  localStorage.removeItem('user_uuid')
  window.location.reload()
}

</script>
<style scoped>
.app-layout {
  display: flex;
  height: 100vh;
}

.sidebar {
  width: 200px;
  background-color: #f4f4f9;
  padding: 20px;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.logo-section {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.logo-text {
  font-size: 18px;
  font-weight: bold;
  margin-top: 10px;
}

.new-chat-button {
  width: 100%;
  margin-top: 20px;
}

.main-content {
  flex: 1;
  padding: 20px;
  overflow-y: auto;
}
.chat-container {
  display: flex;
  flex-direction: column;
  height: 100%;
}

.message-list {
  flex: 1;
  overflow-y: auto;
  padding: 10px;
  border: 1px solid #e0e0e0;
  border-radius: 4px;
  background-color: #fff;
  margin-bottom: 10px;
  display: flex;
  flex-direction: column;
}

.message {
  margin-bottom: 10px;
  padding: 10px;
  border-radius: 4px;
  display: flex;
  /* align-items: center; */
}

.user-message {
  max-width: 70%;
  background-color: #e1f5fe;
  align-self: flex-end;
  flex-direction: row-reverse;
}

.bot-message {
  max-width: 100%;
  background-color: #f1f8e9;
  align-self: flex-start;
}

.message-icon {
  margin: 0 10px;
  font-size: 1.2em;
}

.loading-dots {
  padding-left: 5px;
}

.dot {
  display: inline-block;
  margin-left: 5px;
  width: 8px;
  height: 8px;
  background-color: #000000;
  border-radius: 50%;
  animation: pulse 1.2s infinite ease-in-out both;
}

.dot:nth-child(2) {
  animation-delay: -0.6s;
}

@keyframes pulse {
  0%,
  100% {
    transform: scale(0.6);
    opacity: 0.4;
  }

  50% {
    transform: scale(1);
    opacity: 1;
  }
}
.input-container {
  display: flex;
}

.input-container .el-input {
  flex: 1;
  margin-right: 10px;
}

/* 媒体查询,当设备宽度小于等于 768px 时应用以下样式 */
@media (max-width: 768px) {
  .main-content {
    padding: 10px 0 10px 0;
  }
  .app-layout {
    flex-direction: column;
  }

  .sidebar {
    /* display: none; */
    width: 100%;
    flex-direction: row;
    justify-content: space-between;
    align-items: center;
    padding: 10px;
  }

  .logo-section {
    flex-direction: row;
    align-items: center;
  }

  .logo-text {
    font-size: 20px;
  }

  .logo-section img {
    width: 40px;
    height: 40px;
  }

  .new-chat-button {
    margin-right: 30px;
    width: auto;
    margin-top: 5px;
  }
}

/* 媒体查询,当设备宽度大于 768px 时应用原来的样式 */
@media (min-width: 769px) {
  .main-content {
    padding: 0 0 10px 10px;
  }

  .app-layout {
    display: flex;
    height: 100vh;
  }

  .sidebar {
    width: 200px;
    background-color: #f4f4f9;
    padding: 20px;
    display: flex;
    flex-direction: column;
    align-items: center;
  }

  .logo-section {
    display: flex;
    flex-direction: column;
    align-items: center;
  }

  .logo-text {
    font-size: 18px;
    font-weight: bold;
    margin-top: 10px;
  }

  .new-chat-button {
    width: 100%;
    margin-top: 20px;
  }
}
</style>

四、数据库设计

1. 核心表结构(MySQL)

(1)医院表(hospitals)
sql 复制代码
CREATE TABLE hospitals
(
    hospital_id    INT PRIMARY KEY AUTO_INCREMENT,
    hospital_name  VARCHAR(100) NOT NULL COMMENT '医院名称',
    hospital_level ENUM ('三级甲等', '三级乙等', '二级甲等', '一级', '其他') NOT NULL COMMENT '医院等级',
    address        VARCHAR(200) NOT NULL COMMENT '地址',
    phone          VARCHAR(20) COMMENT '联系电话',
    description    TEXT COMMENT '医院简介',
    create_time    DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    update_time    DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    deleted        TINYINT(1) DEFAULT 0 COMMENT '删除标志(0=未删除,1=已删除)'
) COMMENT '医院信息表';
(2)科室表(departments)
sql 复制代码
CREATE TABLE departments
(
    dept_id     INT PRIMARY KEY AUTO_INCREMENT,
    dept_name   VARCHAR(50) NOT NULL COMMENT '科室名称',
    hospital_id INT         NOT NULL COMMENT '所属医院ID',
    description TEXT COMMENT '科室简介',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    deleted     TINYINT(1) DEFAULT 0 COMMENT '删除标志(0=未删除,1=已删除)'
) COMMENT '科室信息表';
(3)医生表(doctors)
sql 复制代码
CREATE TABLE doctors
(
    doctor_id        INT PRIMARY KEY AUTO_INCREMENT,
    dept_id          INT            NOT NULL COMMENT '所属科室ID',
    title            ENUM ('主任医师', '副主任医师', '主治医师', '住院医师') NOT NULL COMMENT '职称',
    specialty        VARCHAR(100) COMMENT '擅长领域',
    work_years       INT COMMENT '从业年限',
    consultation_fee DECIMAL(10, 2) NOT NULL COMMENT '挂号费(元)',
    introduction     TEXT COMMENT '医生简介',
    create_time      DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    update_time      DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    deleted          TINYINT(1) DEFAULT 0 COMMENT '删除标志(0=未删除,1=已删除)'
) COMMENT '医生信息表';
(4)医生排班表(schedules)
sql 复制代码
CREATE TABLE schedules
(
    schedule_id         INT PRIMARY KEY AUTO_INCREMENT,
    doctor_id           INT         NOT NULL COMMENT '医生ID',
    date                VARCHAR(20) NOT NULL COMMENT '排班日期',
    morning_start       TIME COMMENT '上午开始时间',
    morning_end         TIME COMMENT '上午结束时间',
    afternoon_start     TIME COMMENT '下午开始时间',
    afternoon_end       TIME COMMENT '下午结束时间',
    night_start         TIME COMMENT '夜间开始时间',
    night_end           TIME COMMENT '夜间结束时间',
    morning_quota       INT      DEFAULT 0 COMMENT '上午号源数量',
    afternoon_quota     INT      DEFAULT 0 COMMENT '下午号源数量',
    night_quota         INT      DEFAULT 0 COMMENT '夜间号源数量',
    morning_remaining   INT      DEFAULT 0 COMMENT '上午剩余号源',
    afternoon_remaining INT      DEFAULT 0 COMMENT '下午剩余号源',
    night_remaining     INT      DEFAULT 0 COMMENT '夜间剩余号源',
    create_time         DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    update_time         DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    deleted             TINYINT(1) DEFAULT 0 COMMENT '删除标志(0=未删除,1=已删除)'
) COMMENT '医生排班表';
(5)预约表(appointments)
sql 复制代码
CREATE TABLE appointments
(
    appointment_id VARCHAR(32) PRIMARY KEY COMMENT '预约表(UUID生成)',
    patient_name   VARCHAR(20) COMMENT '患者名称',
    id_card        VARCHAR(20) COMMENT '身份证号',
    doctor_name    VARCHAR(20) COMMENT '医生名称',
    hospital_name  VARCHAR(20) COMMENT '医院名称',
    dept_name      VARCHAR(20) COMMENT '科室名称',
    time_slot      ENUM ('上午', '下午', '夜间') NOT NULL COMMENT '就诊时段',
    cancel_reason  VARCHAR(200) COMMENT '取消原因',
    create_time    DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '预约时间',
    update_time    DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
) COMMENT '预约订单表';
(6)就诊记录表(medical_records)
sql 复制代码
CREATE TABLE medical_records
(
    record_id         INT PRIMARY KEY AUTO_INCREMENT,
    appointment_id    VARCHAR(32) NOT NULL COMMENT '关联预约订单号',
    patient_name      INT         NOT NULL COMMENT '患者名称',
    doctor_id         INT         NOT NULL COMMENT '医生ID',
    diagnosis         TEXT COMMENT '诊断结果',
    treatment_plan    TEXT COMMENT '治疗方案',
    prescription      TEXT COMMENT '处方内容',
    examination_items TEXT COMMENT '检查项目',
    visit_time        DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '就诊时间',
    create_time       DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '记录时间'
) COMMENT '就诊记录表';

2. ER 图核心关系

五、系统效果与性能指标

1.后端登录页面设计

2.后端系统首页设计

3.用户管理页面

4.医院信息管理页面

5.科室信息管理页面

6.医生信息管理页面

7.医生排班信息管理页面

8.预约信息管理页面

9.AI助手挂号页面

六、未来优化方向

  1. 技术升级:引入微服务架构拆分核心模块,支持多医院接入;
  2. 功能拓展:集成医保支付、远程问诊、语音识别方便特殊群体功能、电子健康档案功能;
  3. 智能化深化:基于用户就诊历史推荐医生,通过 AI 算法预测疾病风险;
  4. 跨平台适配:开发小程序 / APP 版本,提升用户触达率。

尚未修改完善,持续修改完善中。

相关推荐
silver98862 小时前
docker容器和分布式事务
后端
九年义务漏网鲨鱼2 小时前
【多模态大模型面经】 BERT 专题面经
人工智能·深度学习·bert
q***3752 小时前
Spring Boot 从 2.7.x 升级到 3.3注意事项
数据库·hive·spring boot
YDS8292 小时前
苍穹外卖 —— Spring Task和WebSocket的运用以及订单统一处理、订单的提醒和催单功能的实现
java·spring boot·后端·websocket·spring
q***31832 小时前
Spring Boot(快速上手)
java·spring boot·后端
q***09803 小时前
Spring Boot 3.3.4 升级导致 Logback 之前回滚策略配置不兼容问题解决
java·spring boot·logback
爱打球的白师傅3 小时前
python机器学习工程化demo(包含训练模型,预测数据,模型列表,模型详情,删除模型)支持线性回归、逻辑回归、决策树、SVC、随机森林等模型
人工智能·python·深度学习·机器学习·flask·逻辑回归·线性回归
爱分享的鱼鱼3 小时前
Java进阶(二:Maven——Java项目管理工具)
后端
鹏北海3 小时前
TypeScript 类型工具与 NestJS Mapped Types
前端·后端·typescript