This commit is contained in:
王子琦
2026-02-09 21:14:30 +08:00
parent 09028d97f7
commit f9bfb8556b
54 changed files with 6963 additions and 461 deletions

View File

@@ -0,0 +1,307 @@
# 2026届网络工程毕业设计
# 论文撰写规范(应用开发类)
中文摘要 (3-5 个关键词)
# 英文摘要
# 目录
# 第1章绪论
1.1 课题来源及意义
1.2 课题研究现状
1.3当前存在的问题
1.4 课题研究目标
# 第2章主要技术和框架
2.1 主要技术 (开发系统或平台所需相关技术的介绍)
# 2.2 框架和开发模式(开发系统或平台所使用的框架和开发模式的介绍)
# 第3章XXXXX的系统分析
# 3.1需求分析(包括功能需求分析和性能需求分析)
3.2可行性分析(包括技术可行性、操作可行性、经济可行性,社会可行性等)
3.3 用例图(根据系统角色划分用例图,若有两个角色就有两个用例图)
3.3.1 用户用例图(需先对用例图进行描述,其实也就是对该系统的用户角色进行权限分析,再画出具体用户用例图,用例图如下所示)
![image](https://cdn-mineru.openxlab.org.cn/result/2026-01-30/cb195f07-5e0a-4b42-8211-f649b1c1b2df/fedda2aecd9c17cf7f678bd9dfce3c4005225a98e76f2b3c551c72ab00f31ab0.jpg)
图3.1用户用例图
3.3.2 管理员用例图(需先对用例图进行描述,其实也就是对该系统的用户角色进行权限分析,再画出具体用户用例图,用例图如下所示)
![image](https://cdn-mineru.openxlab.org.cn/result/2026-01-30/cb195f07-5e0a-4b42-8211-f649b1c1b2df/85b57ee2a2135507c52a9eaab015e3bbf5218d259fefa8256bf6efc10fa0b42a.jpg)
图3.2管理员用例图
3.4 用例描述(用例描述是对用例图中用例的详细说明,它详细阐述了用例的功
能实现过程、输入输出、前置条件、后置条件以及可能的异常情况等,是对用例图
的补充和细化使系统功能更加清晰具体。需分别对每个用例列举3个左右的用
例描述。)
1用户管理地址用例描述如下表3.1所示。
表 3.1 用户管理地址用例描述
<table><tr><td>用例名称:管理地址</td></tr><tr><td>执行者:用户</td></tr><tr><td>简要说明:用户对收货地址进行添加、修改、删除等管理操作</td></tr><tr><td>基本事件流:
1.用户登录平台,进入地址管理界面
2.系统显示已有地址列表
3.用户选择添加新地址,输入详细地址信息,验证通过后存储地址
4.若修改地址,系统更新后提示成功,若删除地址,确认操作后数据库移除该地址记录</td></tr><tr><td>(2)用户查看商品用例描述如下表3.2所示。
表3.2用户查看商品用例</td></tr><tr><td>用例名称:查看商品</td></tr><tr><td>执行者:用户</td></tr><tr><td>简要说明:用户分类浏览平台上的闲置商品</td></tr><tr><td>基本事件流:
1.用户登录平台,进入查看商品界面
2.选择商品分类,如闲置书籍、衣服、手机等;
3.系统加载并显示该分类下的商品列表
4.用户可点击具体商品,查看详细信息,如介绍、图片、价格等</td></tr></table>
# 3.5 系统性能分析
# 第4章XXXXX的系统设计
4.1系统功能设计(先总体概述一段该系统的主要角色有哪几个,分别包含什么主
要功能,再给出功能结构图。)
具体如图4.1和图4.2所示。
![image](https://cdn-mineru.openxlab.org.cn/result/2026-01-30/cb195f07-5e0a-4b42-8211-f649b1c1b2df/eac455914323329b18745df5545040771c017cdc8a0d678f5569589c30bf1f4e.jpg)
图4.1用户功能结构图
![image](https://cdn-mineru.openxlab.org.cn/result/2026-01-30/cb195f07-5e0a-4b42-8211-f649b1c1b2df/18177e942ba24fd2a163cebfaf31c07e24ad1f0c3c3bb57deb63dff73bc44a22.jpg)
图4.2管理员功能结构图
4.2 类图(根据给定的初步类图,详细描述每个类的属性、方法以及类之间的具体关系。)
![image](https://cdn-mineru.openxlab.org.cn/result/2026-01-30/cb195f07-5e0a-4b42-8211-f649b1c1b2df/e677faab545e5c9b75f847b7d786df1e0c2e764b6475056e4ed0f2b769dcf25f.jpg)
图4.3系统类图
4.3 序列图(展示了对象之间随时间顺序发生的消息传递,通常用于描述用例的实现过程或系统中某个功能的动态行为。至少需列举系统角色各一个序列图,在给出序列图之前还需将过程的详细步骤写出。如:用户登录序列图、管理员删除账号序列图。)
![image](https://cdn-mineru.openxlab.org.cn/result/2026-01-30/cb195f07-5e0a-4b42-8211-f649b1c1b2df/b707fad3bc4d4d52a6b9da497d9bfdc7c365eac728f93991ea0c2488b2ccb653.jpg)
图4.4用户购买商品功能序列图
4.4 活动图(活动图可以清晰地描述系统的动态行为和工作流程。例如,在描述一个在线购物系统的订单处理流程时,可以通过活动图展示从用户下单到订单完成的各个步骤。论文中至少需描述各个角色各一个活动的活动图,如用户更新个人信息活动图,管理员删除用户信息活动图。)
# 4.4.1 用户填写个人信息活动图
用户填写个人信息过程可分为以下几步:
(1) 用户输入验证账号密码。
(2) 数据库查询用户数据,并判断用户是否存在。
(3) 登录成功后,用户填写个人信息并提交。
(4) 系统接收提交的个人信息后,将其发送至数据库进行更新保存。
(5) 用户确认信息更新无误后,更新界面。
用户填写个人信息活动图如下图4.7所示。
![image](https://cdn-mineru.openxlab.org.cn/result/2026-01-30/cb195f07-5e0a-4b42-8211-f649b1c1b2df/1167b27131dd2f56dcf570a6d1f2ad3888a8cb31390877f532d47161d49aaabc.jpg)
图4.7 用户填写个人信息活动图
# 4.5 数据库设计
撰写说明:
本章是重点章节很多同学搞不清概念设计、逻辑设计和物理设计的关系请参见另外一个文档《2026届网络工程毕业论文第4章-数据库设计撰写规范(应用开发类补充说明).docx》
4.5.1 概念设计概念设计包括两部分实体属性图不少于8个实体每个实体
标明属性图的主码和总体E-R图
# (1) 管理员实体
管理员实体属性图如图4.9所示。
![image](https://cdn-mineru.openxlab.org.cn/result/2026-01-30/cb195f07-5e0a-4b42-8211-f649b1c1b2df/f0139dd7d92cc9b47066ec9de102f1667c1ce1e48206094d7a2bdef274205425.jpg)
图4.9管理员实体属性图
# (2用户实体
用户实体属性图如图4.10所示。
![image](https://cdn-mineru.openxlab.org.cn/result/2026-01-30/cb195f07-5e0a-4b42-8211-f649b1c1b2df/6d6a6cb8e810b97076d80cd79891215ff2119fc737339c35edd15ad884bba839.jpg)
图4.10用户实体属性图
![image](https://cdn-mineru.openxlab.org.cn/result/2026-01-30/cb195f07-5e0a-4b42-8211-f649b1c1b2df/0f95a66d6d06fcc3c2727444cdea2f4793ac489748efa04d7bfa75c3578d1d9c.jpg)
图4.系统E-R图标明各个实体之间的关联
4.5.2 逻辑设计将E-R图中的实体关系转换为逻辑结构设计即关系模式。
其中,一个实体转换为 1 个单独的关系模式。
实体间联系有3种1:11:nn:m。
1:n 联系要将 1 端实体如用户的主码加入到 n 端如评论实体的关系模式中。
n:m 联系要将联系如上图中的购买转换成一个单独的关系模式,并将两端的主
码放入该关系模式作为属性。)关系模式中需标明主外码
上图转换为逻辑结构如下所示:
(1) 用户表:(用户 id, 用户账号, 密码, 用户姓名, 性别, 联系电话, 头像, 积分,余额)
(2) 管理员表: (管理员 id, 创建时间, 管理员名称, 密码, 管理员类型)
(3) 收货地址表收货地址id用户id地址收货人电话是否为默认地址
(4) 购物车表购物车id商品表名商品商品名称图片购买数量单价会员价商品类型
(5) 订单表订单id订单编号用户id规格上架时间商品详情团购价拼团人数
(6) 团购商品表团购商品id购买数量价格折扣价总价商品类型id支付类型物流购物车id
(7) 商品类型表:(商品类型 id创建时间类型数量
(8) 评论表评论id用户id头像用户名评论内容回复内容
(9) 公告表公告id创建时间标题简介图片内容管理员id
(10) 购买表:(用户 id团购商品 id购买日期购买时间购买表的外键为
用户id团购商品id)
4.5.3 物理设计将上述关系模式转换为一个个物理表结构一个关系模式转换为一个物理表至少包含8张物理表
1系统根据MySQL数据库数据存储的特性设计数据库关系表
表 4.1 管理员表 (admin 表)
<table><tr><td>字段名称</td><td>字段意义</td><td>数据类型</td><td>长度</td><td>完整性约束</td></tr><tr><td>admin_id</td><td>管理员 id</td><td>int</td><td>10</td><td>主键</td></tr><tr><td>admin_name</td><td>管理员姓名</td><td>varchar</td><td>10</td><td>非空</td></tr><tr><td>admin_password</td><td>管理员密码</td><td>varchar</td><td>20</td><td>非空</td></tr><tr><td>admin_email</td><td>管理员邮箱</td><td>varchar</td><td>20</td><td>非空</td></tr><tr><td>admin_phone</td><td>管理员手机号</td><td>varchar</td><td>11</td><td>非空</td></tr></table>
表 4.2 用户表 (user 表)
<table><tr><td>字段名称</td><td>字段意义</td><td>数据类型</td><td>长度</td><td>完整性约束</td></tr><tr><td>user_id</td><td>用户id</td><td>int</td><td>10</td><td>主键</td></tr><tr><td>user_name</td><td>用户昵称</td><td>varchar</td><td>10</td><td>非空</td></tr><tr><td>user_password</td><td>用户密码</td><td>varchar</td><td>20</td><td>非空</td></tr><tr><td>user/mobile</td><td>用户电话</td><td>varchar</td><td>11</td><td>非空</td></tr><tr><td>user_realname</td><td>用户真实姓名</td><td>varchar</td><td>10</td><td>非空</td></tr><tr><td>user_score</td><td>信誉分</td><td>int</td><td>4</td><td>非空</td></tr></table>
表4.3商品表 (product表)
<table><tr><td>字段名称</td><td>字段意义</td><td>数据类型</td><td>长度</td><td>完整性约束</td></tr><tr><td>product_id</td><td>商品id</td><td>int</td><td>10</td><td>主键</td></tr><tr><td>product_name</td><td>商品名称</td><td>varchar</td><td>10</td><td>非空</td></tr><tr><td>product_title</td><td>商品概要</td><td>varchar</td><td>50</td><td>非空</td></tr><tr><td>product_intro</td><td>商品详情</td><td>varchar</td><td>100</td><td>非空</td></tr></table>
4.6图形界面设计实现某一个具体功能的界面设计至少系统中各角色各列举1
# 个功能界面设计)
如:删除购物车界面设计
![image](https://cdn-mineru.openxlab.org.cn/result/2026-01-30/cb195f07-5e0a-4b42-8211-f649b1c1b2df/5f2eaca38a994385483bc816a9956ee51e2a41baf561d9e08f613b9938c1de46.jpg)
图4. 删除购物车界面设计图
# 第5章XXXXX的系统实现
只写出主要功能的实现结果即可,并给出运行截图(不写用户注册、登录),
运行截图中需输入数据的地方要有数据输入,且输入的数据必须真实,不可输入
类似 1111 类数据。可按系统角色进行实现,再进行角色详细功能实现。如:
# 5.1 用户功能模块
# 5.1.1 购物车
# 5.1.2 我的订单
··
# 5.2 管理员功能模块
# 5.2.1 账号管理
# 5.2.2 订单管理
··
# 第6章XXXXX的系统测试
# 6.1 系统测试概述
6.1.1 测试的背景
6.1.2 测试的意义
6.1.3 测试的环境
# 6.2 系统测试用例设计
6.2.1XXXX功能测试
6.2.2XXXX功能测试
6.2.3XXXX功能测试
6.2.4 安全性测试
6.2.5 兼容性测试
# 第7章结论
参考文献
致谢
附录

43
AGENTS.md Normal file
View File

@@ -0,0 +1,43 @@
# Repository Guidelines
## Project Structure & Module Organization
- `backend/` is a Spring Boot 2.7 (Java 17) service with MyBatis-Plus and JWT security.
- Main code: `backend/src/main/java/com/gpf/pethospital`
- Config: `backend/src/main/resources/application.yml` and `application-dev.yml`
- `frontend/` is a Vue 3 + Vite app with Pinia and TDesign.
- Source: `frontend/src/` (pages, router, store, layouts, styles)
- Static assets: `frontend/src/assets/`
- `docs/` and `*.drawio` contain documentation and UML/ER diagrams.
- `frp/` and `start_frp.sh` include FRP configuration/scripts.
## Build, Test, and Development Commands
Backend (from `backend/`):
- `mvn spring-boot:run` — run the API locally.
- `mvn clean package` — build a runnable jar.
- `mvn test` — run Spring Boot tests (if present).
Frontend (from `frontend/`):
- `npm install` — install dependencies.
- `npm run dev` — start Vite dev server.
- `npm run build` — production build.
- `npm run preview` — preview the production build.
## Coding Style & Naming Conventions
- Java: 4-space indentation, standard Spring Boot conventions, packages under `com.gpf.pethospital`.
- Vue/TypeScript: ES module syntax, semicolons used; follow existing file structure.
- Use descriptive, domain-specific names (e.g., `AppointmentController`, `appointmentService`).
## Testing Guidelines
- No dedicated test directories are present yet.
- Backend tests should go in `backend/src/test/java` using Spring Boot Test.
- Frontend has no test runner configured; if adding, include a script in `frontend/package.json`.
## Commit & Pull Request Guidelines
- Current commit history uses short Chinese descriptions like “添加…” and “将…更改为…”.
- Keep messages short, imperative, and meaningful; avoid placeholder commits like “1”.
- PRs should include: a clear summary, testing performed (or “not run”), and screenshots for UI changes.
## Configuration & Security Notes
- `application-dev.yml` contains local MySQL defaults (user `root`, password `password`).
Update credentials via environment or local overrides; do not commit real secrets.
- JWT secrets are currently hardcoded in config; rotate for real deployments.

View File

@@ -0,0 +1,195 @@
<mxfile host="app.diagrams.net" modified="2026-01-30T13:55:00.000Z" agent="codex" version="24.7.7">
<diagram name="admin activity diagram"><mxGraphModel dx="1426" dy="769" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" background="#ffffff" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<!-- 开始节点 -->
<mxCell id="2" value="" style="ellipse;html=1;shape=startState;fillColor=#000000;strokeColor=#000000;" vertex="1" parent="1">
<mxGeometry x="360" y="40" width="30" height="30" as="geometry"/>
</mxCell>
<!-- 活动节点 -->
<mxCell id="3" value="管理员登录系统" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="335" y="110" width="80" height="40" as="geometry"/>
</mxCell>
<!-- 状态节点 -->
<mxCell id="4" value="系统验证管理员身份" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="335" y="180" width="80" height="40" as="geometry"/>
</mxCell>
<!-- 判断节点 -->
<mxCell id="5" value="" style="rhombus;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="360" y="250" width="30" height="30" as="geometry"/>
</mxCell>
<!-- 活动节点 -->
<mxCell id="6" value="显示错误信息" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="460" y="245" width="80" height="40" as="geometry"/>
</mxCell>
<!-- 活动节点 -->
<mxCell id="7" value="进入用户管理模块" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="335" y="320" width="80" height="40" as="geometry"/>
</mxCell>
<!-- 活动节点 -->
<mxCell id="8" value="查看用户列表" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="335" y="390" width="80" height="40" as="geometry"/>
</mxCell>
<!-- 活动节点 -->
<mxCell id="9" value="选择操作用户" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="335" y="460" width="80" height="40" as="geometry"/>
</mxCell>
<!-- 活动节点 -->
<mxCell id="10" value="选择操作类型" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="335" y="530" width="80" height="40" as="geometry"/>
</mxCell>
<!-- 判断节点 -->
<mxCell id="11" value="" style="rhombus;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="360" y="600" width="30" height="30" as="geometry"/>
</mxCell>
<!-- 活动节点 -->
<mxCell id="12" value="编辑用户信息" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="460" y="595" width="80" height="40" as="geometry"/>
</mxCell>
<!-- 活动节点 -->
<mxCell id="13" value="禁用/启用用户" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="460" y="665" width="80" height="40" as="geometry"/>
</mxCell>
<!-- 活动节点 -->
<mxCell id="14" value="删除用户" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="460" y="735" width="80" height="40" as="geometry"/>
</mxCell>
<!-- 活动节点 -->
<mxCell id="15" value="保存更改" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="335" y="735" width="80" height="40" as="geometry"/>
</mxCell>
<!-- 活动节点 -->
<mxCell id="16" value="确认操作" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="335" y="805" width="80" height="40" as="geometry"/>
</mxCell>
<!-- 活动节点 -->
<mxCell id="17" value="退出系统" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="335" y="875" width="80" height="40" as="geometry"/>
</mxCell>
<!-- 结束节点 -->
<mxCell id="18" value="" style="ellipse;html=1;shape=endState;fillColor=#000000;strokeColor=#000000;" vertex="1" parent="1">
<mxGeometry x="360" y="945" width="30" height="30" as="geometry"/>
</mxCell>
<!-- 结束节点 -->
<mxCell id="19" value="" style="ellipse;html=1;shape=endState;fillColor=#000000;strokeColor=#000000;" vertex="1" parent="1">
<mxGeometry x="500" y="315" width="30" height="30" as="geometry"/>
</mxCell>
<!-- 连接线 -->
<mxCell id="20" value="" style="endArrow=classic;html=1;exitX=0.5;exitY=1;exitPerimeter=0;entryX=0.5;entryY=0;" edge="1" parent="1" source="2" target="3">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="210" y="140" as="sourcePoint"/>
<mxPoint x="260" y="90" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="21" value="" style="endArrow=classic;html=1;exitX=0.5;exitY=1;entryX=0.5;entryY=0;" edge="1" parent="1" source="3" target="4">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="220" y="150" as="sourcePoint"/>
<mxPoint x="270" y="100" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="22" value="" style="endArrow=classic;html=1;exitX=0.5;exitY=1;entryX=0.5;entryY=0;" edge="1" parent="1" source="4" target="5">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="230" y="160" as="sourcePoint"/>
<mxPoint x="280" y="110" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="23" value="是" style="endArrow=classic;html=1;exitX=0.5;exitY=1;entryX=0.5;entryY=0;" edge="1" parent="1" source="5" target="7">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="240" y="170" as="sourcePoint"/>
<mxPoint x="290" y="120" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="24" value="否" style="endArrow=classic;html=1;exitX=1;exitY=0.5;entryX=0;entryY=0.5;" edge="1" parent="1" source="5" target="6">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="250" y="180" as="sourcePoint"/>
<mxPoint x="300" y="130" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="25" value="" style="endArrow=classic;html=1;exitX=0.5;exitY=1;entryX=0.5;entryY=0;" edge="1" parent="1" source="7" target="8">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="260" y="290" as="sourcePoint"/>
<mxPoint x="310" y="240" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="26" value="" style="endArrow=classic;html=1;exitX=0.5;exitY=1;entryX=0.5;entryY=0;" edge="1" parent="1" source="8" target="9">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="270" y="300" as="sourcePoint"/>
<mxPoint x="320" y="250" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="27" value="" style="endArrow=classic;html=1;exitX=0.5;exitY=1;entryX=0.5;entryY=0;" edge="1" parent="1" source="9" target="10">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="280" y="310" as="sourcePoint"/>
<mxPoint x="330" y="260" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="28" value="" style="endArrow=classic;html=1;exitX=0.5;exitY=1;entryX=0.5;entryY=0;" edge="1" parent="1" source="10" target="11">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="290" y="320" as="sourcePoint"/>
<mxPoint x="340" y="270" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="29" value="编辑信息" style="endArrow=classic;html=1;exitX=1;exitY=0.5;entryX=0;entryY=0.5;" edge="1" parent="1" source="11" target="12">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="300" y="330" as="sourcePoint"/>
<mxPoint x="350" y="280" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="30" value="禁用/启用" style="endArrow=classic;html=1;exitX=1;exitY=0.5;entryX=0;entryY=0.5;" edge="1" parent="1" source="11" target="13">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="310" y="340" as="sourcePoint"/>
<mxPoint x="360" y="290" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="31" value="删除用户" style="endArrow=classic;html=1;exitX=1;exitY=0.5;entryX=0;entryY=0.5;" edge="1" parent="1" source="11" target="14">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="320" y="350" as="sourcePoint"/>
<mxPoint x="370" y="300" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="32" value="" style="endArrow=classic;html=1;exitX=0.5;exitY=1;entryX=0.5;entryY=0;" edge="1" parent="1" source="12" target="15">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="330" y="360" as="sourcePoint"/>
<mxPoint x="380" y="310" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="33" value="" style="endArrow=classic;html=1;exitX=0.5;exitY=1;entryX=0.5;entryY=0;" edge="1" parent="1" source="13" target="15">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="340" y="370" as="sourcePoint"/>
<mxPoint x="390" y="320" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="34" value="" style="endArrow=classic;html=1;exitX=0.5;exitY=1;entryX=0.5;entryY=0;" edge="1" parent="1" source="14" target="15">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="350" y="380" as="sourcePoint"/>
<mxPoint x="400" y="330" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="35" value="" style="endArrow=classic;html=1;exitX=0.5;exitY=1;entryX=0.5;entryY=0;" edge="1" parent="1" source="15" target="16">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="360" y="390" as="sourcePoint"/>
<mxPoint x="410" y="340" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="36" value="" style="endArrow=classic;html=1;exitX=0.5;exitY=1;entryX=0.5;entryY=0;" edge="1" parent="1" source="16" target="17">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="370" y="400" as="sourcePoint"/>
<mxPoint x="420" y="350" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="37" value="" style="endArrow=classic;html=1;exitX=0.5;exitY=1;entryX=0.5;entryY=0;" edge="1" parent="1" source="17" target="18">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="380" y="410" as="sourcePoint"/>
<mxPoint x="430" y="360" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="38" value="" style="endArrow=classic;html=1;exitX=0.5;exitY=1;entryX=0.5;entryY=0;" edge="1" parent="1" source="6" target="19">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="500" y="430" as="sourcePoint"/>
<mxPoint x="550" y="380" as="targetPoint"/>
</mxGeometry>
</mxCell>
</root>
</mxGraphModel></diagram>
</mxfile>

14
admin_entity.drawio Normal file
View File

@@ -0,0 +1,14 @@
<mxfile host="app.diagrams.net" modified="2026-01-30T13:55:00.000Z" agent="codex" version="24.7.7">
<diagram name="????????"><mxGraphModel dx="1426" dy="769" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" background="#ffffff" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="2" value="???(Admin)" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=26;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;swimlaneFillColor=#ffffff;rounded=0;shadow=0;labelBackgroundColor=none;strokeColor=#000000;strokeWidth=1;fillColor=none;fontFamily=Verdana;fontSize=12;fontColor=#000000;" vertex="1" parent="1">
<mxGeometry x="200" y="120" width="240" height="260" as="geometry"/>
</mxCell>
<mxCell id="3" value="+ admin_id: BIGINT&#xa;+ admin_name: VARCHAR(50)&#xa;+ admin_username: VARCHAR(50)&#xa;+ admin_password: VARCHAR(255)&#xa;+ phone: VARCHAR(20)&#xa;+ role: VARCHAR(20)&#xa;+ status: INT&#xa;+ create_time: TIMESTAMP&#xa;+ update_time: TIMESTAMP" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;" vertex="1" parent="2">
<mxGeometry y="26" width="240" height="234" as="geometry"/>
</mxCell>
</root>
</mxGraphModel></diagram>
</mxfile>

View File

@@ -0,0 +1,107 @@
<mxfile host="app.diagrams.net" modified="2026-01-30T14:00:00.000Z" agent="codex" version="24.7.7">
<diagram name="管理员功能结构图"><mxGraphModel dx="1426" dy="769" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" background="#ffffff" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="2" value="管理员功能结构" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#333333;" vertex="1" parent="1">
<mxGeometry x="310" y="40" width="180" height="50" as="geometry"/>
</mxCell>
<mxCell id="10" value="账号管理" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#333333;" vertex="1" parent="1">
<mxGeometry x="100" y="140" width="180" height="40" as="geometry"/>
</mxCell>
<mxCell id="100" value="" style="endArrow=classic;html=1;" edge="1" parent="1" source="2" target="10">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="11" value="员工账号管理" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#666666;" vertex="1" parent="1">
<mxGeometry x="360" y="130" width="180" height="36" as="geometry"/>
</mxCell>
<mxCell id="101" value="" style="endArrow=classic;html=1;" edge="1" parent="1" source="10" target="11">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="12" value="顾客账号管理" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#666666;" vertex="1" parent="1">
<mxGeometry x="360" y="176" width="180" height="36" as="geometry"/>
</mxCell>
<mxCell id="102" value="" style="endArrow=classic;html=1;" edge="1" parent="1" source="11" target="12">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="13" value="药品库存" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#333333;" vertex="1" parent="1">
<mxGeometry x="100" y="230" width="180" height="40" as="geometry"/>
</mxCell>
<mxCell id="103" value="" style="endArrow=classic;html=1;" edge="1" parent="1" source="2" target="13">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="14" value="药品入库/出库" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#666666;" vertex="1" parent="1">
<mxGeometry x="360" y="220" width="180" height="36" as="geometry"/>
</mxCell>
<mxCell id="104" value="" style="endArrow=classic;html=1;" edge="1" parent="1" source="13" target="14">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="15" value="库存查询" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#666666;" vertex="1" parent="1">
<mxGeometry x="360" y="266" width="180" height="36" as="geometry"/>
</mxCell>
<mxCell id="105" value="" style="endArrow=classic;html=1;" edge="1" parent="1" source="14" target="15">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="16" value="预约管理" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#333333;" vertex="1" parent="1">
<mxGeometry x="100" y="320" width="180" height="40" as="geometry"/>
</mxCell>
<mxCell id="106" value="" style="endArrow=classic;html=1;" edge="1" parent="1" source="2" target="16">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="17" value="预约审核" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#666666;" vertex="1" parent="1">
<mxGeometry x="360" y="310" width="180" height="36" as="geometry"/>
</mxCell>
<mxCell id="107" value="" style="endArrow=classic;html=1;" edge="1" parent="1" source="16" target="17">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="18" value="预约调整" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#666666;" vertex="1" parent="1">
<mxGeometry x="360" y="356" width="180" height="36" as="geometry"/>
</mxCell>
<mxCell id="108" value="" style="endArrow=classic;html=1;" edge="1" parent="1" source="17" target="18">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="19" value="门诊与病历" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#333333;" vertex="1" parent="1">
<mxGeometry x="100" y="410" width="180" height="40" as="geometry"/>
</mxCell>
<mxCell id="109" value="" style="endArrow=classic;html=1;" edge="1" parent="1" source="2" target="19">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="20" value="门诊管理" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#666666;" vertex="1" parent="1">
<mxGeometry x="360" y="400" width="180" height="36" as="geometry"/>
</mxCell>
<mxCell id="110" value="" style="endArrow=classic;html=1;" edge="1" parent="1" source="19" target="20">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="21" value="病历管理" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#666666;" vertex="1" parent="1">
<mxGeometry x="360" y="446" width="180" height="36" as="geometry"/>
</mxCell>
<mxCell id="111" value="" style="endArrow=classic;html=1;" edge="1" parent="1" source="20" target="21">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="22" value="处方管理" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#666666;" vertex="1" parent="1">
<mxGeometry x="360" y="492" width="180" height="36" as="geometry"/>
</mxCell>
<mxCell id="112" value="" style="endArrow=classic;html=1;" edge="1" parent="1" source="21" target="22">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="23" value="公告与统计" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#333333;" vertex="1" parent="1">
<mxGeometry x="100" y="500" width="180" height="40" as="geometry"/>
</mxCell>
<mxCell id="113" value="" style="endArrow=classic;html=1;" edge="1" parent="1" source="2" target="23">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="24" value="公告管理" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#666666;" vertex="1" parent="1">
<mxGeometry x="360" y="490" width="180" height="36" as="geometry"/>
</mxCell>
<mxCell id="114" value="" style="endArrow=classic;html=1;" edge="1" parent="1" source="23" target="24">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="25" value="统计报表" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#666666;" vertex="1" parent="1">
<mxGeometry x="360" y="536" width="180" height="36" as="geometry"/>
</mxCell>
<mxCell id="115" value="" style="endArrow=classic;html=1;" edge="1" parent="1" source="24" target="25">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
</root>
</mxGraphModel></diagram>
</mxfile>

View File

@@ -0,0 +1,152 @@
<mxfile host="app.diagrams.net" modified="2026-01-30T14:02:00.000Z" agent="codex" version="24.7.7">
<diagram name="admin sequence diagram"><mxGraphModel dx="1426" dy="769" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" background="#ffffff" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="2" value="管理员" style="shape=umlLifeline;perimeter=lifelinePerimeter;whiteSpace=wrap;html=1;container=1;collapsible=0;recursiveResize=0;outlineConnect=0;" vertex="1" parent="1">
<mxGeometry x="100" y="80" width="100" height="520" as="geometry" />
</mxCell>
<mxCell id="3" value="" style="html=1;points=[];perimeter=orthogonalPerimeter;" vertex="1" parent="2">
<mxGeometry x="45" y="70" width="10" height="450" as="geometry" />
</mxCell>
<mxCell id="4" value="系统" style="shape=umlLifeline;perimeter=lifelinePerimeter;whiteSpace=wrap;html=1;container=1;collapsible=0;recursiveResize=0;outlineConnect=0;" vertex="1" parent="1">
<mxGeometry x="270" y="80" width="100" height="520" as="geometry" />
</mxCell>
<mxCell id="5" value="" style="html=1;points=[];perimeter=orthogonalPerimeter;" vertex="1" parent="4">
<mxGeometry x="45" y="80" width="10" height="440" as="geometry" />
</mxCell>
<mxCell id="6" value="药品管理模块" style="shape=umlLifeline;perimeter=lifelinePerimeter;whiteSpace=wrap;html=1;container=1;collapsible=0;recursiveResize=0;outlineConnect=0;" vertex="1" parent="1">
<mxGeometry x="440" y="80" width="100" height="520" as="geometry" />
</mxCell>
<mxCell id="7" value="" style="html=1;points=[];perimeter=orthogonalPerimeter;" vertex="1" parent="6">
<mxGeometry x="45" y="100" width="10" height="420" as="geometry" />
</mxCell>
<mxCell id="8" value="数据库" style="shape=umlLifeline;perimeter=lifelinePerimeter;whiteSpace=wrap;html=1;container=1;collapsible=0;recursiveResize=0;outlineConnect=0;" vertex="1" parent="1">
<mxGeometry x="610" y="80" width="100" height="520" as="geometry" />
</mxCell>
<mxCell id="9" value="" style="html=1;points=[];perimeter=orthogonalPerimeter;" vertex="1" parent="8">
<mxGeometry x="45" y="110" width="10" height="410" as="geometry" />
</mxCell>
<mxCell id="10" value="" style="html=1;points=[];perimeter=orthogonalPerimeter;" vertex="1" parent="1">
<mxGeometry x="155" y="170" width="10" height="40" as="geometry" />
</mxCell>
<mxCell id="11" value="登录系统" style="html=1;verticalAlign=bottom;endArrow=block;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;" edge="1" parent="1" target="10">
<mxGeometry relative="1" as="geometry">
<mxPoint x="160" y="150" as="sourcePoint" />
</mxGeometry>
</mxCell>
<mxCell id="12" value="验证管理员身份" style="html=1;verticalAlign=bottom;endArrow=block;entryX=0;entryY=0;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;" edge="1" parent="1" target="5">
<mxGeometry relative="1" as="geometry">
<mxPoint x="320" y="200" as="sourcePoint" />
<mxPoint x="470" y="200" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="13" value="返回验证结果" style="html=1;verticalAlign=bottom;endArrow=block;endSize=8;startArrow=classic;startSize=8;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;" edge="1" parent="1">
<mxGeometry x="0.16" y="-20" relative="1" as="geometry">
<mxPoint x="320" y="230" as="targetPoint" />
<mxPoint x="320" y="230" as="sourcePoint" />
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="14" value="登录成功" style="html=1;verticalAlign=bottom;endArrow=block;endSize=8;startArrow=classic;startSize=8;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;" edge="1" parent="1">
<mxGeometry x="0.08" y="-20" relative="1" as="geometry">
<mxPoint x="160" y="260" as="targetPoint" />
<mxPoint x="160" y="260" as="sourcePoint" />
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="15" value="进入药品管理" style="html=1;verticalAlign=bottom;endArrow=block;entryX=0;entryY=0;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;" edge="1" parent="1" target="5">
<mxGeometry relative="1" as="geometry">
<mxPoint x="160" y="290" as="sourcePoint" />
<mxPoint x="470" y="290" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="16" value="调用药品管理模块" style="html=1;verticalAlign=bottom;endArrow=block;entryX=0;entryY=0;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;" edge="1" parent="1" target="7">
<mxGeometry relative="1" as="geometry">
<mxPoint x="320" y="320" as="sourcePoint" />
<mxPoint x="470" y="320" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="17" value="获取药品列表" style="html=1;verticalAlign=bottom;endArrow=block;entryX=0;entryY=0;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;" edge="1" parent="1" target="9">
<mxGeometry relative="1" as="geometry">
<mxPoint x="490" y="350" as="sourcePoint" />
<mxPoint x="470" y="350" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="18" value="返回药品列表" style="html=1;verticalAlign=bottom;endArrow=block;endSize=8;startArrow=classic;startSize=8;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;" edge="1" parent="1">
<mxGeometry x="0.1" y="-20" relative="1" as="geometry">
<mxPoint x="490" y="380" as="targetPoint" />
<mxPoint x="490" y="380" as="sourcePoint" />
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="19" value="返回药品列表" style="html=1;verticalAlign=bottom;endArrow=block;endSize=8;startArrow=classic;startSize=8;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;" edge="1" parent="1">
<mxGeometry x="0.1" y="-20" relative="1" as="geometry">
<mxPoint x="320" y="410" as="targetPoint" />
<mxPoint x="320" y="410" as="sourcePoint" />
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="20" value="显示药品列表" style="html=1;verticalAlign=bottom;endArrow=block;endSize=8;startArrow=classic;startSize=8;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;" edge="1" parent="1">
<mxGeometry x="0.08" y="-20" relative="1" as="geometry">
<mxPoint x="160" y="440" as="targetPoint" />
<mxPoint x="160" y="440" as="sourcePoint" />
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="21" value="选择添加药品" style="html=1;verticalAlign=bottom;endArrow=block;entryX=0;entryY=0;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;" edge="1" parent="1" target="5">
<mxGeometry relative="1" as="geometry">
<mxPoint x="160" y="470" as="sourcePoint" />
<mxPoint x="470" y="470" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="22" value="调用添加药品功能" style="html=1;verticalAlign=bottom;endArrow=block;entryX=0;entryY=0;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;" edge="1" parent="1" target="7">
<mxGeometry relative="1" as="geometry">
<mxPoint x="320" y="500" as="sourcePoint" />
<mxPoint x="470" y="500" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="23" value="输入药品信息" style="html=1;verticalAlign=bottom;endArrow=block;entryX=0;entryY=0;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;" edge="1" parent="1" target="7">
<mxGeometry relative="1" as="geometry">
<mxPoint x="160" y="530" as="sourcePoint" />
<mxPoint x="470" y="530" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="24" value="验证药品信息" style="html=1;verticalAlign=bottom;endArrow=block;entryX=0;entryY=0;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;" edge="1" parent="1" target="7">
<mxGeometry relative="1" as="geometry">
<mxPoint x="490" y="560" as="sourcePoint" />
<mxPoint x="470" y="560" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="25" value="保存药品信息" style="html=1;verticalAlign=bottom;endArrow=block;entryX=0;entryY=0;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;" edge="1" parent="1" target="9">
<mxGeometry relative="1" as="geometry">
<mxPoint x="490" y="590" as="sourcePoint" />
<mxPoint x="470" y="590" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="26" value="保存成功" style="html=1;verticalAlign=bottom;endArrow=block;endSize=8;startArrow=classic;startSize=8;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;" edge="1" parent="1">
<mxGeometry x="0.1" y="-20" relative="1" as="geometry">
<mxPoint x="490" y="620" as="targetPoint" />
<mxPoint x="490" y="620" as="sourcePoint" />
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="27" value="返回添加结果" style="html=1;verticalAlign=bottom;endArrow=block;endSize=8;startArrow=classic;startSize=8;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;" edge="1" parent="1">
<mxGeometry x="0.1" y="-20" relative="1" as="geometry">
<mxPoint x="320" y="650" as="targetPoint" />
<mxPoint x="320" y="650" as="sourcePoint" />
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="28" value="药品添加成功" style="html=1;verticalAlign=bottom;endArrow=block;endSize=8;startArrow=classic;startSize=8;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;" edge="1" parent="1">
<mxGeometry x="0.08" y="-20" relative="1" as="geometry">
<mxPoint x="160" y="680" as="targetPoint" />
<mxPoint x="160" y="680" as="sourcePoint" />
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
</root>
</mxGraphModel></diagram>
</mxfile>

68
admin_use_case.drawio Normal file
View File

@@ -0,0 +1,68 @@
<mxfile host="app.diagrams.net" modified="2026-01-30T14:00:00.000Z" agent="codex" version="24.7.7">
<diagram name="管理员用例图"><mxGraphModel dx="1426" dy="769" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" background="#ffffff" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="2" value="爱维宠物医院管理系统" style="html=1;fontStyle=1;verticalAlign=top;align=left;spacingTop=8;spacingLeft=6;spacingRight=12;shape=cube;size=10;direction=south;fontSize=14;" vertex="1" parent="1">
<mxGeometry x="20" y="20" width="760" height="520" as="geometry"/>
</mxCell>
<mxCell id="3" value="管理员" style="shape=umlActor;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;html=1;outlineConnect=0;" vertex="1" parent="1">
<mxGeometry x="80" y="220" width="30" height="60" as="geometry"/>
</mxCell>
<mxCell id="20" value="管理员工账号" style="ellipse;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="220" y="80" width="120" height="50" as="geometry"/>
</mxCell>
<mxCell id="21" value="管理顾客账号" style="ellipse;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="220" y="150" width="120" height="50" as="geometry"/>
</mxCell>
<mxCell id="22" value="药品库存管理" style="ellipse;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="220" y="220" width="120" height="50" as="geometry"/>
</mxCell>
<mxCell id="23" value="公告管理" style="ellipse;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="220" y="290" width="120" height="50" as="geometry"/>
</mxCell>
<mxCell id="24" value="统计报表" style="ellipse;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="420" y="80" width="120" height="50" as="geometry"/>
</mxCell>
<mxCell id="25" value="预约管理" style="ellipse;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="420" y="150" width="120" height="50" as="geometry"/>
</mxCell>
<mxCell id="26" value="门诊管理" style="ellipse;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="420" y="220" width="120" height="50" as="geometry"/>
</mxCell>
<mxCell id="27" value="病历管理" style="ellipse;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="420" y="290" width="120" height="50" as="geometry"/>
</mxCell>
<mxCell id="28" value="处方管理" style="ellipse;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="420" y="360" width="120" height="50" as="geometry"/>
</mxCell>
<mxCell id="100" value="" style="endArrow=none;html=1;entryX=0;entryY=0.5;exitX=1;exitY=0.5;exitPerimeter=0;" edge="1" parent="1" source="3" target="20">
<mxGeometry width="50" height="50" relative="1" as="geometry"/>
</mxCell>
<mxCell id="101" value="" style="endArrow=none;html=1;entryX=0;entryY=0.5;exitX=1;exitY=0.5;exitPerimeter=0;" edge="1" parent="1" source="3" target="21">
<mxGeometry width="50" height="50" relative="1" as="geometry"/>
</mxCell>
<mxCell id="102" value="" style="endArrow=none;html=1;entryX=0;entryY=0.5;exitX=1;exitY=0.5;exitPerimeter=0;" edge="1" parent="1" source="3" target="22">
<mxGeometry width="50" height="50" relative="1" as="geometry"/>
</mxCell>
<mxCell id="103" value="" style="endArrow=none;html=1;entryX=0;entryY=0.5;exitX=1;exitY=0.5;exitPerimeter=0;" edge="1" parent="1" source="3" target="23">
<mxGeometry width="50" height="50" relative="1" as="geometry"/>
</mxCell>
<mxCell id="104" value="" style="endArrow=none;html=1;entryX=0;entryY=0.5;exitX=1;exitY=0.5;exitPerimeter=0;" edge="1" parent="1" source="3" target="24">
<mxGeometry width="50" height="50" relative="1" as="geometry"/>
</mxCell>
<mxCell id="105" value="" style="endArrow=none;html=1;entryX=0;entryY=0.5;exitX=1;exitY=0.5;exitPerimeter=0;" edge="1" parent="1" source="3" target="25">
<mxGeometry width="50" height="50" relative="1" as="geometry"/>
</mxCell>
<mxCell id="106" value="" style="endArrow=none;html=1;entryX=0;entryY=0.5;exitX=1;exitY=0.5;exitPerimeter=0;" edge="1" parent="1" source="3" target="26">
<mxGeometry width="50" height="50" relative="1" as="geometry"/>
</mxCell>
<mxCell id="107" value="" style="endArrow=none;html=1;entryX=0;entryY=0.5;exitX=1;exitY=0.5;exitPerimeter=0;" edge="1" parent="1" source="3" target="27">
<mxGeometry width="50" height="50" relative="1" as="geometry"/>
</mxCell>
<mxCell id="108" value="" style="endArrow=none;html=1;entryX=0;entryY=0.5;exitX=1;exitY=0.5;exitPerimeter=0;" edge="1" parent="1" source="3" target="28">
<mxGeometry width="50" height="50" relative="1" as="geometry"/>
</mxCell>
</root>
</mxGraphModel></diagram>
</mxfile>

View File

@@ -26,6 +26,8 @@ public class AppointmentController {
AuthUser user = SecurityUtils.currentUser();
if (user != null && "CUSTOMER".equals(user.getRole())) {
appointment.setCustomerId(user.getId());
} else if (appointment.getCustomerId() == null) {
return ApiResponse.error(400, "customerId required");
}
if (appointment.getStatus() == null) {
appointment.setStatus("PENDING");

View File

@@ -4,7 +4,9 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.gpf.pethospital.common.ApiResponse;
import com.gpf.pethospital.entity.Notice;
import com.gpf.pethospital.security.AuthUser;
import com.gpf.pethospital.service.NoticeService;
import com.gpf.pethospital.util.SecurityUtils;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@@ -42,6 +44,15 @@ public class NoticeController {
@PreAuthorize("hasRole('ADMIN')")
@PostMapping("/notices")
public ApiResponse<?> create(@RequestBody Notice notice) {
if (notice.getPublisherId() == null) {
AuthUser user = SecurityUtils.currentUser();
if (user != null) {
notice.setPublisherId(user.getId());
}
}
if (notice.getPublisherId() == null) {
return ApiResponse.error(400, "publisherId required");
}
if (notice.getStatus() == null) {
notice.setStatus(1);
}

View File

@@ -38,6 +38,8 @@ public class PetController {
AuthUser user = SecurityUtils.currentUser();
if (user != null && "CUSTOMER".equals(user.getRole())) {
pet.setOwnerId(user.getId());
} else if (pet.getOwnerId() == null) {
return ApiResponse.error(400, "ownerId required");
}
petService.save(pet);
return ApiResponse.success("created", null);

View File

@@ -55,8 +55,11 @@ public class StatsController {
wrapper.select("SUM(amount) AS total");
List<Map<String, Object>> result = orderService.listMaps(wrapper);
BigDecimal total = BigDecimal.ZERO;
if (!result.isEmpty() && result.get(0).get("total") != null) {
total = new BigDecimal(result.get(0).get("total").toString());
if (!result.isEmpty()) {
Map<String, Object> row = result.get(0);
if (row != null && row.get("total") != null) {
total = new BigDecimal(row.get("total").toString());
}
}
data.put("orderAmountTotal", total);
return ApiResponse.success(data);

View File

@@ -46,7 +46,8 @@ public class PrescriptionItem {
/**
* 用法用量
*/
private String usage;
@TableField("usage_desc")
private String usageDesc;
/**
* 用药天数

View File

@@ -9,9 +9,9 @@ spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/pet_hospital_dev?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8
url: jdbc:mysql://localhost:3307/pet_hospital_dev?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8
username: root
password: password
password: qq5211314
hikari:
maximum-pool-size: 20
minimum-idle: 10
@@ -30,7 +30,7 @@ spring:
sql:
init:
mode: always
mode: never
schema-locations: classpath*:schema.sql
data-locations: classpath*:data.sql

View File

@@ -58,7 +58,13 @@ CREATE TABLE IF NOT EXISTS prescription_item (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
prescription_id BIGINT NOT NULL,
drug_id BIGINT NOT NULL,
drug_name VARCHAR(100),
specification VARCHAR(100),
quantity INT NOT NULL,
usage_desc VARCHAR(200),
days INT,
unit_price DECIMAL(10,2),
subtotal DECIMAL(10,2),
dosage VARCHAR(100),
frequency VARCHAR(50),
duration VARCHAR(50),
@@ -92,6 +98,12 @@ CREATE TABLE IF NOT EXISTS drug (
specification VARCHAR(100),
unit_price DECIMAL(10,2),
stock_quantity INT DEFAULT 0,
stock INT DEFAULT 0,
alert_threshold INT,
purchase_price DECIMAL(10,2),
sale_price DECIMAL(10,2),
approval_number VARCHAR(100),
expiry_date TIMESTAMP NULL,
unit VARCHAR(20),
status INT DEFAULT 1,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
@@ -150,6 +162,14 @@ CREATE TABLE IF NOT EXISTS `user` (
CREATE TABLE IF NOT EXISTS medical_record (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
visit_id BIGINT NOT NULL,
chief_complaint TEXT,
present_illness TEXT,
physical_examination TEXT,
examination_results TEXT,
diagnosis TEXT,
treatment_plan TEXT,
advice TEXT,
status VARCHAR(20),
record_type VARCHAR(50), -- CHECKUP体检, DIAGNOSIS诊断, TREATMENT治疗
content TEXT,
attachment_urls TEXT,
@@ -162,11 +182,18 @@ CREATE TABLE IF NOT EXISTS medical_record (
-- 检查并创建message表
CREATE TABLE IF NOT EXISTS message (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT,
user_name VARCHAR(100),
contact VARCHAR(100),
title VARCHAR(200),
sender_id BIGINT,
receiver_id BIGINT NOT NULL,
content TEXT NOT NULL,
type VARCHAR(20) DEFAULT 'NOTICE', -- NOTICE通知, CHAT聊天
status VARCHAR(20) DEFAULT 'UNREAD', -- UNREAD未读, READ已读
reply TEXT,
reply_time TIMESTAMP NULL,
reply_user_id BIGINT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
@@ -177,6 +204,7 @@ CREATE TABLE IF NOT EXISTS notice (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(200) NOT NULL,
content TEXT NOT NULL,
is_top INT DEFAULT 0,
publisher_id BIGINT NOT NULL,
publish_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
status INT DEFAULT 1,
@@ -188,6 +216,13 @@ CREATE TABLE IF NOT EXISTS notice (
-- 检查并创建report表
CREATE TABLE IF NOT EXISTS report (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
visit_id BIGINT,
pet_id BIGINT,
type VARCHAR(50),
title VARCHAR(200),
summary TEXT,
attachment_url VARCHAR(255),
doctor_id BIGINT,
report_type VARCHAR(50) NOT NULL, -- REVENUE收入, CUSTOMER客户, PET宠物, DRUG药品
report_data JSON,
period_start DATE,
@@ -206,6 +241,7 @@ CREATE TABLE IF NOT EXISTS stock_in (
unit_price DECIMAL(10,2),
supplier VARCHAR(100),
operator_id BIGINT,
stock_in_time TIMESTAMP NULL,
remark TEXT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
@@ -216,10 +252,12 @@ CREATE TABLE IF NOT EXISTS stock_in (
CREATE TABLE IF NOT EXISTS stock_out (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
drug_id BIGINT NOT NULL,
prescription_id BIGINT,
quantity INT NOT NULL,
unit_price DECIMAL(10,2),
purpose VARCHAR(100), -- 用途:销售、损耗等
operator_id BIGINT,
stock_out_time TIMESTAMP NULL,
remark TEXT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
@@ -230,6 +268,10 @@ CREATE TABLE IF NOT EXISTS stock_out (
CREATE TABLE IF NOT EXISTS vaccine_record (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
pet_id BIGINT NOT NULL,
type VARCHAR(20), -- VACCINE疫苗, DEWORMING驱虫
item_name VARCHAR(100),
execute_date DATE,
next_reminder_date DATE,
vaccine_name VARCHAR(100) NOT NULL,
dose_number INT,
injection_date DATE,
@@ -239,4 +281,4 @@ CREATE TABLE IF NOT EXISTS vaccine_record (
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
);

View File

@@ -8,33 +8,30 @@ spring:
name: pet-hospital
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;MODE=MYSQL;
username: sa
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3307/pet_hospital_dev?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8
username: root
password: qq5211314
hikari:
maximum-pool-size: 10
minimum-idle: 5
maximum-pool-size: 20
minimum-idle: 10
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
jackson:
time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss
h2:
console:
enabled: true
path: /h2-console
jpa:
hibernate:
ddl-auto: create-drop
ddl-auto: update
show-sql: true
sql:
init:
mode: always
schema-locations: classpath*:schema-h2.sql
mode: never
schema-locations: classpath*:schema.sql
data-locations: classpath*:data.sql
mybatis-plus:

View File

@@ -25,6 +25,10 @@ mybatis-plus:
logic-not-delete-value: 0
mapper-locations: classpath*:mapper/**/*.xml
# 数据库配置
database:
type: mysql
# JWT配置
jwt:
secret: pet-hospital-secret-key-2024-guanpengfei-graduate-design

View File

@@ -1,242 +0,0 @@
-- 如果表不存在则创建表,如果存在则添加缺失的列
-- 检查并创建appointment表或添加缺失列
CREATE TABLE IF NOT EXISTS appointment (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
customer_id BIGINT NOT NULL,
pet_id BIGINT NOT NULL,
doctor_id BIGINT,
department VARCHAR(50),
appointment_date DATE,
time_slot VARCHAR(20), -- 添加这个缺失的列
status VARCHAR(20) DEFAULT 'PENDING',
remark TEXT,
cancel_time TIMESTAMP NULL,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 检查并创建visit表
CREATE TABLE IF NOT EXISTS visit (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
appointment_id BIGINT,
customer_id BIGINT NOT NULL,
pet_id BIGINT NOT NULL,
doctor_id BIGINT NOT NULL,
symptoms TEXT,
diagnosis TEXT,
treatment_plan TEXT,
status VARCHAR(20) DEFAULT 'PENDING',
total_amount DECIMAL(10,2),
payment_status VARCHAR(20) DEFAULT 'UNPAID',
payment_method VARCHAR(20),
payment_time TIMESTAMP NULL,
start_time TIMESTAMP,
end_time TIMESTAMP,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 检查并创建prescription表
CREATE TABLE IF NOT EXISTS prescription (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
visit_id BIGINT,
doctor_id BIGINT,
customer_id BIGINT,
total_amount DECIMAL(10,2),
status VARCHAR(20) DEFAULT 'DRAFT',
remark TEXT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 检查并创建prescription_item表
CREATE TABLE IF NOT EXISTS prescription_item (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
prescription_id BIGINT NOT NULL,
drug_id BIGINT NOT NULL,
quantity INT NOT NULL,
dosage VARCHAR(100),
frequency VARCHAR(50),
duration VARCHAR(50),
usage_instructions TEXT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 检查并创建order_info表
CREATE TABLE IF NOT EXISTS order_info (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
visit_id BIGINT,
customer_id BIGINT NOT NULL,
amount DECIMAL(10,2),
status VARCHAR(20) DEFAULT 'UNPAID',
payment_method VARCHAR(20),
payment_time TIMESTAMP NULL,
remark TEXT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 检查并创建drug表
CREATE TABLE IF NOT EXISTS drug (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
category VARCHAR(50),
manufacturer VARCHAR(100),
specification VARCHAR(100),
unit_price DECIMAL(10,2),
stock_quantity INT DEFAULT 0,
unit VARCHAR(20),
status INT DEFAULT 1,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 检查并创建pet表
CREATE TABLE IF NOT EXISTS pet (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
owner_id BIGINT NOT NULL,
name VARCHAR(50) NOT NULL,
species VARCHAR(50),
breed VARCHAR(100),
gender VARCHAR(10), -- 修改为VARCHAR以支持MALE/FEMALE
birthday DATE, -- 添加birthday字段而不是age
weight DOUBLE, -- 添加weight字段
photo VARCHAR(255), -- 添加photo字段
remark TEXT, -- 添加remark字段
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 检查并创建doctor表
CREATE TABLE IF NOT EXISTS doctor (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
department VARCHAR(50),
title VARCHAR(50),
phone VARCHAR(20),
email VARCHAR(100),
avatar VARCHAR(255),
status INT DEFAULT 1,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 检查并创建user表
CREATE TABLE IF NOT EXISTS `user` (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
phone VARCHAR(20),
email VARCHAR(100),
password VARCHAR(255) NOT NULL,
role VARCHAR(20) DEFAULT 'CUSTOMER',
status INT DEFAULT 1,
avatar VARCHAR(255),
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 检查并创建medical_record表
CREATE TABLE IF NOT EXISTS medical_record (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
visit_id BIGINT NOT NULL,
record_type VARCHAR(50), -- CHECKUP体检, DIAGNOSIS诊断, TREATMENT治疗
content TEXT,
attachment_urls TEXT,
doctor_id BIGINT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 检查并创建message表
CREATE TABLE IF NOT EXISTS message (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
sender_id BIGINT,
receiver_id BIGINT NOT NULL,
content TEXT NOT NULL,
type VARCHAR(20) DEFAULT 'NOTICE', -- NOTICE通知, CHAT聊天
status VARCHAR(20) DEFAULT 'UNREAD', -- UNREAD未读, READ已读
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 检查并创建notice表
CREATE TABLE IF NOT EXISTS notice (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(200) NOT NULL,
content TEXT NOT NULL,
publisher_id BIGINT NOT NULL,
publish_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
status INT DEFAULT 1,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 检查并创建report表
CREATE TABLE IF NOT EXISTS report (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
report_type VARCHAR(50) NOT NULL, -- REVENUE收入, CUSTOMER客户, PET宠物, DRUG药品
report_data JSON,
period_start DATE,
period_end DATE,
generated_by BIGINT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 检查并创建stock_in表
CREATE TABLE IF NOT EXISTS stock_in (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
drug_id BIGINT NOT NULL,
quantity INT NOT NULL,
unit_price DECIMAL(10,2),
supplier VARCHAR(100),
operator_id BIGINT,
remark TEXT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 检查并创建stock_out表
CREATE TABLE IF NOT EXISTS stock_out (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
drug_id BIGINT NOT NULL,
quantity INT NOT NULL,
unit_price DECIMAL(10,2),
purpose VARCHAR(100), -- 用途:销售、损耗等
operator_id BIGINT,
remark TEXT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 检查并创建vaccine_record表
CREATE TABLE IF NOT EXISTS vaccine_record (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
pet_id BIGINT NOT NULL,
vaccine_name VARCHAR(100) NOT NULL,
dose_number INT,
injection_date DATE,
next_appointment_date DATE,
doctor_id BIGINT,
remark TEXT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);

View File

@@ -1,84 +1,70 @@
-- 用户表
CREATE TABLE IF NOT EXISTS `user` (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
phone VARCHAR(20),
email VARCHAR(100),
password VARCHAR(255) NOT NULL,
role VARCHAR(20) DEFAULT 'CUSTOMER',
status INT DEFAULT 1,
avatar VARCHAR(255),
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 如果表不存在则创建表,如果存在则添加缺失的列
-- 宠物表
CREATE TABLE IF NOT EXISTS pet (
-- 检查并创建appointment表或添加缺失列
CREATE TABLE IF NOT EXISTS appointment (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
species VARCHAR(50),
breed VARCHAR(100),
gender CHAR(1),
age INT,
owner_id BIGINT NOT NULL,
health_status VARCHAR(100),
vaccination_status VARCHAR(100),
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 医生表
CREATE TABLE IF NOT EXISTS doctor (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
customer_id BIGINT NOT NULL,
pet_id BIGINT NOT NULL,
doctor_id BIGINT,
department VARCHAR(50),
title VARCHAR(50),
phone VARCHAR(20),
email VARCHAR(100),
avatar VARCHAR(255),
status INT DEFAULT 1,
appointment_date DATE,
time_slot VARCHAR(20), -- 添加这个缺失的列
status VARCHAR(20) DEFAULT 'PENDING',
remark TEXT,
cancel_time TIMESTAMP NULL,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 就诊记录
-- 检查并创建visit
CREATE TABLE IF NOT EXISTS visit (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
appointment_id BIGINT,
customer_id BIGINT NOT NULL,
pet_id BIGINT NOT NULL,
doctor_id BIGINT NOT NULL,
customer_id BIGINT NOT NULL,
symptoms TEXT,
diagnosis TEXT,
treatment_plan TEXT,
visit_date DATE,
status VARCHAR(20) DEFAULT 'PENDING',
total_amount DECIMAL(10,2),
payment_status VARCHAR(20) DEFAULT 'UNPAID',
payment_method VARCHAR(20),
payment_time TIMESTAMP NULL,
start_time TIMESTAMP,
end_time TIMESTAMP,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 处方
-- 检查并创建prescription
CREATE TABLE IF NOT EXISTS prescription (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
visit_id BIGINT NOT NULL,
doctor_id BIGINT NOT NULL,
customer_id BIGINT NOT NULL,
visit_id BIGINT,
doctor_id BIGINT,
customer_id BIGINT,
total_amount DECIMAL(10,2),
status VARCHAR(20) DEFAULT 'ACTIVE',
status VARCHAR(20) DEFAULT 'DRAFT',
remark TEXT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 处方明细
-- 检查并创建prescription_item
CREATE TABLE IF NOT EXISTS prescription_item (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
prescription_id BIGINT NOT NULL,
drug_id BIGINT NOT NULL,
drug_name VARCHAR(100),
specification VARCHAR(100),
quantity INT NOT NULL,
usage_desc VARCHAR(200),
days INT,
unit_price DECIMAL(10,2),
subtotal DECIMAL(10,2),
dosage VARCHAR(100),
frequency VARCHAR(50),
duration VARCHAR(50),
@@ -88,23 +74,7 @@ CREATE TABLE IF NOT EXISTS prescription_item (
deleted INT DEFAULT 0
);
-- 药品
CREATE TABLE IF NOT EXISTS drug (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
category VARCHAR(50),
manufacturer VARCHAR(100),
specification VARCHAR(100),
unit_price DECIMAL(10,2),
stock_quantity INT DEFAULT 0,
unit VARCHAR(20),
status INT DEFAULT 1,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 订单表
-- 检查并创建order_info
CREATE TABLE IF NOT EXISTS order_info (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
visit_id BIGINT,
@@ -119,25 +89,189 @@ CREATE TABLE IF NOT EXISTS order_info (
deleted INT DEFAULT 0
);
-- 预约
CREATE TABLE IF NOT EXISTS appointment (
-- 检查并创建drug
CREATE TABLE IF NOT EXISTS drug (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
pet_id BIGINT NOT NULL,
doctor_id BIGINT NOT NULL,
customer_id BIGINT NOT NULL,
appointment_date DATE NOT NULL,
appointment_time TIME NOT NULL,
reason TEXT,
status VARCHAR(20) DEFAULT 'PENDING',
name VARCHAR(100) NOT NULL,
category VARCHAR(50),
manufacturer VARCHAR(100),
specification VARCHAR(100),
unit_price DECIMAL(10,2),
stock_quantity INT DEFAULT 0,
stock INT DEFAULT 0,
alert_threshold INT,
purchase_price DECIMAL(10,2),
sale_price DECIMAL(10,2),
approval_number VARCHAR(100),
expiry_date TIMESTAMP NULL,
unit VARCHAR(20),
status INT DEFAULT 1,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 疫苗接种记录
-- 检查并创建pet
CREATE TABLE IF NOT EXISTS pet (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
owner_id BIGINT NOT NULL,
name VARCHAR(50) NOT NULL,
species VARCHAR(50),
breed VARCHAR(100),
gender VARCHAR(10), -- 修改为VARCHAR以支持MALE/FEMALE
birthday DATE, -- 添加birthday字段而不是age
weight DOUBLE, -- 添加weight字段
photo VARCHAR(255), -- 添加photo字段
remark TEXT, -- 添加remark字段
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 检查并创建doctor表
CREATE TABLE IF NOT EXISTS doctor (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
department VARCHAR(50),
title VARCHAR(50),
phone VARCHAR(20),
email VARCHAR(100),
avatar VARCHAR(255),
status INT DEFAULT 1,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 检查并创建user表
CREATE TABLE IF NOT EXISTS `user` (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
phone VARCHAR(20),
email VARCHAR(100),
password VARCHAR(255) NOT NULL,
role VARCHAR(20) DEFAULT 'CUSTOMER',
status INT DEFAULT 1,
avatar VARCHAR(255),
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 检查并创建medical_record表
CREATE TABLE IF NOT EXISTS medical_record (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
visit_id BIGINT NOT NULL,
chief_complaint TEXT,
present_illness TEXT,
physical_examination TEXT,
examination_results TEXT,
diagnosis TEXT,
treatment_plan TEXT,
advice TEXT,
status VARCHAR(20),
record_type VARCHAR(50), -- CHECKUP体检, DIAGNOSIS诊断, TREATMENT治疗
content TEXT,
attachment_urls TEXT,
doctor_id BIGINT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 检查并创建message表
CREATE TABLE IF NOT EXISTS message (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT,
user_name VARCHAR(100),
contact VARCHAR(100),
title VARCHAR(200),
sender_id BIGINT,
receiver_id BIGINT NOT NULL,
content TEXT NOT NULL,
type VARCHAR(20) DEFAULT 'NOTICE', -- NOTICE通知, CHAT聊天
status VARCHAR(20) DEFAULT 'UNREAD', -- UNREAD未读, READ已读
reply TEXT,
reply_time TIMESTAMP NULL,
reply_user_id BIGINT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 检查并创建notice表
CREATE TABLE IF NOT EXISTS notice (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(200) NOT NULL,
content TEXT NOT NULL,
is_top INT DEFAULT 0,
publisher_id BIGINT NOT NULL,
publish_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
status INT DEFAULT 1,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 检查并创建report表
CREATE TABLE IF NOT EXISTS report (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
visit_id BIGINT,
pet_id BIGINT,
type VARCHAR(50),
title VARCHAR(200),
summary TEXT,
attachment_url VARCHAR(255),
doctor_id BIGINT,
report_type VARCHAR(50) NOT NULL, -- REVENUE收入, CUSTOMER客户, PET宠物, DRUG药品
report_data JSON,
period_start DATE,
period_end DATE,
generated_by BIGINT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 检查并创建stock_in表
CREATE TABLE IF NOT EXISTS stock_in (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
drug_id BIGINT NOT NULL,
quantity INT NOT NULL,
unit_price DECIMAL(10,2),
supplier VARCHAR(100),
operator_id BIGINT,
stock_in_time TIMESTAMP NULL,
remark TEXT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 检查并创建stock_out表
CREATE TABLE IF NOT EXISTS stock_out (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
drug_id BIGINT NOT NULL,
prescription_id BIGINT,
quantity INT NOT NULL,
unit_price DECIMAL(10,2),
purpose VARCHAR(100), -- 用途:销售、损耗等
operator_id BIGINT,
stock_out_time TIMESTAMP NULL,
remark TEXT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 检查并创建vaccine_record表
CREATE TABLE IF NOT EXISTS vaccine_record (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
pet_id BIGINT NOT NULL,
type VARCHAR(20), -- VACCINE疫苗, DEWORMING驱虫
item_name VARCHAR(100),
execute_date DATE,
next_reminder_date DATE,
vaccine_name VARCHAR(100) NOT NULL,
dose_number INT,
injection_date DATE,
@@ -148,83 +282,3 @@ CREATE TABLE IF NOT EXISTS vaccine_record (
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 库存入库表
CREATE TABLE IF NOT EXISTS stock_in (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
drug_id BIGINT NOT NULL,
quantity INT NOT NULL,
unit_price DECIMAL(10,2),
supplier VARCHAR(100),
operator_id BIGINT,
remark TEXT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 库存出库表
CREATE TABLE IF NOT EXISTS stock_out (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
drug_id BIGINT NOT NULL,
quantity INT NOT NULL,
unit_price DECIMAL(10,2),
purpose VARCHAR(100), -- 用途:销售、损耗等
operator_id BIGINT,
remark TEXT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 消息表
CREATE TABLE IF NOT EXISTS message (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
sender_id BIGINT,
receiver_id BIGINT NOT NULL,
content TEXT NOT NULL,
type VARCHAR(20) DEFAULT 'NOTICE', -- NOTICE通知, CHAT聊天
status VARCHAR(20) DEFAULT 'UNREAD', -- UNREAD未读, READ已读
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 公告表
CREATE TABLE IF NOT EXISTS notice (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(200) NOT NULL,
content TEXT NOT NULL,
publisher_id BIGINT NOT NULL,
publish_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
status INT DEFAULT 1,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 医疗记录表
CREATE TABLE IF NOT EXISTS medical_record (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
visit_id BIGINT NOT NULL,
record_type VARCHAR(50), -- CHECKUP体检, DIAGNOSIS诊断, TREATMENT治疗
content TEXT,
attachment_urls TEXT,
doctor_id BIGINT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 报表统计表
CREATE TABLE IF NOT EXISTS report (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
report_type VARCHAR(50) NOT NULL, -- REVENUE收入, CUSTOMER客户, PET宠物, DRUG药品
report_data JSON,
period_start DATE,
period_end DATE,
generated_by BIGINT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);

67
class_diagram.drawio Normal file
View File

@@ -0,0 +1,67 @@
<mxfile host="app.diagrams.net" modified="2026-01-30T13:55:00.000Z" agent="codex" version="24.7.7">
<diagram name="class diagram"><mxGraphModel dx="1426" dy="769" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" background="#ffffff" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="40" value="User" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=26;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;swimlaneFillColor=#ffffff;rounded=0;shadow=0;labelBackgroundColor=none;strokeColor=#000000;strokeWidth=1;fillColor=none;fontFamily=Verdana;fontSize=12;fontColor=#000000;" vertex="1" parent="1">
<mxGeometry x="40" y="80" width="180" height="230" as="geometry"/>
</mxCell>
<mxCell id="41" value="+ id: Long
+ username: String
+ phone: String
+ email: String
+ password: String
+ role: String
+ status: Integer" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;" vertex="1" parent="40">
<mxGeometry y="26" width="180" height="204" as="geometry"/>
</mxCell>
<mxCell id="43" value="Doctor" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=26;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;swimlaneFillColor=#ffffff;rounded=0;shadow=0;labelBackgroundColor=none;strokeColor=#000000;strokeWidth=1;fillColor=none;fontFamily=Verdana;fontSize=12;fontColor=#000000;" vertex="1" parent="1">
<mxGeometry x="260" y="80" width="180" height="230" as="geometry"/>
</mxCell>
<mxCell id="44" value="+ id: Long
+ name: String
+ department: String
+ title: String
+ phone: String" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;" vertex="1" parent="43">
<mxGeometry y="26" width="180" height="204" as="geometry"/>
</mxCell>
<mxCell id="46" value="Pet" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=26;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;swimlaneFillColor=#ffffff;rounded=0;shadow=0;labelBackgroundColor=none;strokeColor=#000000;strokeWidth=1;fillColor=none;fontFamily=Verdana;fontSize=12;fontColor=#000000;" vertex="1" parent="1">
<mxGeometry x="480" y="80" width="180" height="230" as="geometry"/>
</mxCell>
<mxCell id="47" value="+ id: Long
+ ownerId: Long
+ name: String
+ species: String
+ breed: String
+ gender: String
+ birthday: Date" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;" vertex="1" parent="46">
<mxGeometry y="26" width="180" height="204" as="geometry"/>
</mxCell>
<mxCell id="49" value="Appointment" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=26;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;swimlaneFillColor=#ffffff;rounded=0;shadow=0;labelBackgroundColor=none;strokeColor=#000000;strokeWidth=1;fillColor=none;fontFamily=Verdana;fontSize=12;fontColor=#000000;" vertex="1" parent="1">
<mxGeometry x="720" y="80" width="180" height="230" as="geometry"/>
</mxCell>
<mxCell id="50" value="+ id: Long
+ customerId: Long
+ petId: Long
+ doctorId: Long
+ appointmentDate: Date
+ timeSlot: String
+ status: String" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;" vertex="1" parent="49">
<mxGeometry y="26" width="180" height="204" as="geometry"/>
</mxCell>
<mxCell id="52" value="owns" style="endArrow=open;endSize=12;startArrow=diamondThin;startSize=14;startFill=0;edgeStyle=orthogonalEdgeStyle;align=left;verticalAlign=bottom;" edge="1" parent="1" source="40" target="46">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="53" value="makes" style="endArrow=open;endSize=12;startArrow=diamondThin;startSize=14;startFill=0;edgeStyle=orthogonalEdgeStyle;align=left;verticalAlign=bottom;" edge="1" parent="1" source="40" target="49">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="54" value="handles" style="endArrow=open;endSize=12;startArrow=diamondThin;startSize=14;startFill=0;edgeStyle=orthogonalEdgeStyle;align=left;verticalAlign=bottom;" edge="1" parent="1" source="43" target="49">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="55" value="involves" style="endArrow=open;endSize=12;startArrow=diamondThin;startSize=14;startFill=0;edgeStyle=orthogonalEdgeStyle;align=left;verticalAlign=bottom;" edge="1" parent="1" source="46" target="49">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

View File

@@ -1429,6 +1429,7 @@
"version": "5.9.3",
"devOptional": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -1448,6 +1449,7 @@
"version": "5.4.21",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "^0.21.3",
"postcss": "^8.4.43",
@@ -1520,6 +1522,7 @@
"node_modules/vue": {
"version": "3.5.27",
"license": "MIT",
"peer": true,
"dependencies": {
"@vue/compiler-dom": "3.5.27",
"@vue/compiler-sfc": "3.5.27",

View File

@@ -14,6 +14,9 @@
</t-button>
</div>
<t-table :data="list" :columns="columns" row-key="id" bordered stripe hover>
<template #petId="{ row }">
{{ getPetName(row.petId) }}
</template>
<template #statusSlot="{ row }">
<span class="status-badge" :class="getStatusClass(row.status)">
{{ getStatusText(row.status) }}
@@ -33,6 +36,9 @@
<t-dialog v-model:visible="dialogVisible" header="新增预约" :on-confirm="submitCreate" width="500">
<t-form :data="form" layout="vertical">
<t-form-item v-if="!isCustomer" label="顾客" required>
<t-select v-model="form.customerId" :options="customerOptions" placeholder="请选择顾客" clearable />
</t-form-item>
<t-form-item label="宠物" required>
<t-select v-model="form.petId" :options="petOptions" placeholder="请选择宠物" clearable />
</t-form-item>
@@ -40,7 +46,7 @@
<t-date-picker v-model="form.appointmentDate" placeholder="请选择预约日期" />
</t-form-item>
<t-form-item label="时间段" required>
<t-input v-model="form.timeSlot" placeholder="请输入时间段09:00-10:00" />
<t-select v-model="form.timeSlot" :options="timeSlotOptions" placeholder="请选择时间段" />
</t-form-item>
<t-form-item label="备注">
<t-textarea v-model="form.remark" placeholder="请输入备注信息" :maxlength="200" />
@@ -54,12 +60,17 @@
import { onMounted, reactive, ref, computed } from 'vue';
import { MessagePlugin } from 'tdesign-vue-next';
import { api } from '../api';
import { useAuthStore } from '../store/auth';
const list = ref([] as any[]);
const pets = ref([] as any[]);
const customers = ref([] as any[]);
const dialogVisible = ref(false);
const query = reactive({ status: '' });
const form = reactive({ petId: '', appointmentDate: '', timeSlot: '' });
const form = reactive({ customerId: '', petId: '', appointmentDate: '', timeSlot: '' });
const auth = useAuthStore();
const isCustomer = computed(() => auth.user?.role === 'CUSTOMER');
const statusOptions = [
{ label: '待确认', value: 'PENDING' },
@@ -69,8 +80,29 @@ const statusOptions = [
{ label: '爽约', value: 'NO_SHOW' },
];
const timeSlotOptions = [
{ label: '09:00-10:00', value: '09:00-10:00' },
{ label: '10:00-11:00', value: '10:00-11:00' },
{ label: '11:00-12:00', value: '11:00-12:00' },
{ label: '14:00-15:00', value: '14:00-15:00' },
{ label: '15:00-16:00', value: '15:00-16:00' },
{ label: '16:00-17:00', value: '16:00-17:00' },
{ label: '17:00-18:00', value: '17:00-18:00' },
];
const customerOptions = computed(() => {
return customers.value.map((customer: any) => ({
label: customer.username || `顾客${customer.id}`,
value: customer.id,
}));
});
const petOptions = computed(() => {
return pets.value.map((pet: any) => ({
const activeCustomerId = isCustomer.value ? auth.user?.userId : form.customerId;
const filtered = activeCustomerId
? pets.value.filter((pet: any) => pet.ownerId === activeCustomerId)
: pets.value;
return filtered.map((pet: any) => ({
label: `${pet.name} (${pet.breed})`,
value: pet.id
}));
@@ -87,6 +119,12 @@ const getStatusText = (status: string) => {
return statusMap[status] || status;
};
const getPetName = (petId: number) => {
const pet = pets.value.find((item: any) => item.id === petId);
if (!pet) return petId ? `宠物ID ${petId}` : '-';
return `${pet.name}${pet.breed ? ` (${pet.breed})` : ''}`;
};
const getStatusClass = (status: string) => {
const classMap: Record<string, string> = {
'PENDING': 'status-pending',
@@ -100,7 +138,7 @@ const getStatusClass = (status: string) => {
const columns = [
{ colKey: 'id', title: 'ID', width: 80 },
{ colKey: 'petId', title: '宠物ID' },
{ colKey: 'petId', title: '宠物' },
{ colKey: 'appointmentDate', title: '预约日期' },
{ colKey: 'timeSlot', title: '时段' },
{ colKey: 'statusSlot', title: '状态' },
@@ -132,6 +170,7 @@ const updateStatus = async (id: number, status: string) => {
};
const openCreate = () => {
form.customerId = isCustomer.value ? auth.user?.userId || '' : '';
form.petId = '';
form.appointmentDate = '';
form.timeSlot = '';
@@ -152,8 +191,26 @@ const loadPets = async () => {
}
};
const loadCustomers = async () => {
try {
const res = await api.users({ page: 1, size: 100, role: 'CUSTOMER' });
if (res.code === 0) {
customers.value = res.data?.records || [];
} else {
MessagePlugin.error(res.message || '获取顾客列表失败');
}
} catch (error) {
console.error('获取顾客列表失败:', error);
MessagePlugin.error('获取顾客列表失败');
}
};
const submitCreate = async () => {
const res = await api.createAppointment({ ...form });
const payload = { ...form } as any;
if (isCustomer.value) {
payload.customerId = auth.user?.userId || payload.customerId;
}
const res = await api.createAppointment(payload);
if (res.code === 0) {
MessagePlugin.success('创建成功');
dialogVisible.value = false;
@@ -166,5 +223,8 @@ const submitCreate = async () => {
onMounted(() => {
load();
loadPets(); // 加载宠物列表
if (!isCustomer.value) {
loadCustomers();
}
});
</script>

View File

@@ -8,6 +8,12 @@
<t-button variant="outline" @click="openCreate">新增病历</t-button>
</div>
<t-table :data="list" :columns="columns" row-key="id">
<template #visitId="{ row }">
{{ getVisitLabel(row.visitId) }}
</template>
<template #status="{ row }">
{{ getStatusText(row.status) }}
</template>
<template #op="{ row }">
<div class="table-actions">
<t-button size="small" variant="text" @click="openEdit(row)">编辑</t-button>
@@ -21,7 +27,14 @@
<t-dialog v-model:visible="dialogVisible" :header="dialogTitle" :on-confirm="submit">
<t-form :data="form">
<t-form-item label="就诊ID"><t-input v-model="form.visitId" /></t-form-item>
<t-form-item label="就诊ID">
<t-select
v-model="form.visitId"
:options="visitOptions"
placeholder="请选择就诊记录"
clearable
/>
</t-form-item>
<t-form-item label="主诉"><t-input v-model="form.chiefComplaint" /></t-form-item>
<t-form-item label="诊断"><t-input v-model="form.diagnosis" /></t-form-item>
<t-form-item label="状态"><t-select v-model="form.status" :options="statusOptions" /></t-form-item>
@@ -31,11 +44,14 @@
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue';
import { computed, onMounted, reactive, ref } from 'vue';
import { MessagePlugin } from 'tdesign-vue-next';
import { api } from '../api';
const list = ref([] as any[]);
const visits = ref([] as any[]);
const pets = ref([] as any[]);
const doctors = ref([] as any[]);
const dialogVisible = ref(false);
const dialogTitle = ref('新增病历');
const editingId = ref<number | null>(null);
@@ -47,9 +63,37 @@ const statusOptions = [
{ label: '已完成', value: 'COMPLETED' },
];
const getStatusText = (status: string) => {
const map: Record<string, string> = {
DRAFT: '草稿',
COMPLETED: '已完成',
};
return map[status] || status || '-';
};
const visitOptions = computed(() => {
return visits.value.map((visit: any) => {
const pet = pets.value.find((item: any) => item.id === visit.petId);
const doctor = doctors.value.find((item: any) => item.id === visit.doctorId);
const labelParts: string[] = [];
if (pet) labelParts.push(`宠物 ${pet.name || '未命名'}`);
if (doctor) labelParts.push(`医生 ${doctor.name || '未知'}`);
return {
label: labelParts.length ? labelParts.join(' / ') : '就诊记录',
value: visit.id,
};
});
});
const getVisitLabel = (visitId: number) => {
const visit = visits.value.find((item: any) => item.id === visitId);
if (!visit) return visitId ? `就诊ID ${visitId}` : '-';
return `就诊#${visit.id}`;
};
const columns = [
{ colKey: 'id', title: 'ID', width: 80 },
{ colKey: 'visitId', title: '就诊ID' },
{ colKey: 'visitId', title: '就诊' },
{ colKey: 'chiefComplaint', title: '主诉' },
{ colKey: 'diagnosis', title: '诊断' },
{ colKey: 'status', title: '状态' },
@@ -65,7 +109,7 @@ const load = async () => {
const openCreate = () => {
dialogTitle.value = '新增病历';
editingId.value = null;
form.visitId = query.visitId;
form.visitId = query.visitId ? Number(query.visitId) : '';
form.chiefComplaint = '';
form.diagnosis = '';
form.status = 'DRAFT';
@@ -82,6 +126,23 @@ const openEdit = (row: any) => {
dialogVisible.value = true;
};
const loadVisits = async () => {
const res = await api.visits({ page: 1, size: 100 });
if (res.code === 0) {
visits.value = res.data?.records || res.data || [];
}
};
const loadPets = async () => {
const res = await api.pets({ page: 1, size: 200 });
if (res.code === 0) pets.value = res.data?.records || [];
};
const loadDoctors = async () => {
const res = await api.users({ page: 1, size: 200, role: 'DOCTOR' });
if (res.code === 0) doctors.value = res.data?.records || [];
};
const submit = async () => {
const payload = { ...form };
const res = editingId.value
@@ -105,4 +166,10 @@ const remove = async (id: number) => {
MessagePlugin.error(res.message || '删除失败');
}
};
onMounted(() => {
loadVisits();
loadPets();
loadDoctors();
});
</script>

View File

@@ -7,6 +7,9 @@
<t-button theme="primary" @click="load">查询</t-button>
</div>
<t-table :data="list" :columns="columns" row-key="id">
<template #status="{ row }">
{{ getStatusText(row.status) }}
</template>
<template #op="{ row }">
<t-button size="small" variant="text" @click="openReply(row)">回复</t-button>
</template>
@@ -39,6 +42,14 @@ const statusOptions = [
{ label: '已处理', value: 'PROCESSED' },
];
const getStatusText = (status: string) => {
const map: Record<string, string> = {
PENDING: '待处理',
PROCESSED: '已处理',
};
return map[status] || status || '-';
};
const columns = [
{ colKey: 'id', title: 'ID', width: 80 },
{ colKey: 'title', title: '标题' },

View File

@@ -7,12 +7,15 @@
<template #icon><t-icon name="refresh" /></template>
刷新
</t-button>
<t-button variant="outline" @click="openCreate">
<t-button v-if="canEdit" variant="outline" @click="openCreate">
<template #icon><t-icon name="add" /></template>
新增订单
</t-button>
</div>
<t-table :data="list" :columns="columns" row-key="id" bordered stripe hover>
<template #visitId="{ row }">
{{ getVisitLabel(row.visitId) }}
</template>
<template #status="{ row }">
<span class="status-badge" :class="getStatusClass(row.status)">
{{ getStatusText(row.status) }}
@@ -23,7 +26,7 @@
</template>
<template #op="{ row }">
<div class="table-actions">
<t-button size="small" variant="outline" @click="openEdit(row)">
<t-button v-if="canEdit" size="small" variant="outline" @click="openEdit(row)">
<template #icon><t-icon name="edit" /></template>
编辑
</t-button>
@@ -65,6 +68,7 @@
import { onMounted, reactive, ref, computed } from 'vue';
import { MessagePlugin } from 'tdesign-vue-next';
import { api } from '../api';
import { useAuthStore } from '../store/auth';
const list = ref([] as any[]);
const visits = ref([] as any[]);
@@ -72,6 +76,9 @@ const dialogVisible = ref(false);
const dialogTitle = ref('新增订单');
const editingId = ref<number | null>(null);
const form = reactive({ visitId: '', amount: '', paymentMethod: 'OFFLINE', status: 'UNPAID' });
const auth = useAuthStore();
const canEdit = computed(() => auth.user?.role === 'ADMIN');
const visitOptions = computed(() => {
return visits.value.map((visit: any) => ({
@@ -80,6 +87,12 @@ const visitOptions = computed(() => {
}));
});
const getVisitLabel = (visitId: number) => {
const visit = visits.value.find((item: any) => item.id === visitId);
if (!visit) return visitId ? `就诊ID ${visitId}` : '-';
return `就诊#${visit.id}`;
};
const getStatusText = (status: string) => {
const statusMap: Record<string, string> = {
'UNPAID': '未支付',
@@ -127,7 +140,7 @@ const statusOptions = [
const columns = [
{ colKey: 'id', title: 'ID', width: 80 },
{ colKey: 'visitId', title: '就诊ID' },
{ colKey: 'visitId', title: '就诊' },
{ colKey: 'amount', title: '金额' },
{ colKey: 'status', title: '状态' },
{ colKey: 'paymentMethod', title: '支付方式' },
@@ -140,6 +153,7 @@ const load = async () => {
};
const openCreate = () => {
if (!canEdit.value) return;
dialogTitle.value = '新增订单';
editingId.value = null;
form.visitId = '';
@@ -150,6 +164,7 @@ const openCreate = () => {
};
const openEdit = (row: any) => {
if (!canEdit.value) return;
dialogTitle.value = '编辑订单';
editingId.value = row.id;
form.visitId = row.visitId || '';
@@ -174,6 +189,7 @@ const loadVisits = async () => {
};
const submit = async () => {
if (!canEdit.value) return;
const payload = { ...form };
const res = editingId.value
? await api.updateOrder(editingId.value, payload)

View File

@@ -3,11 +3,14 @@
<h2 class="page-title">宠物档案</h2>
<div class="panel">
<div class="inline-form">
<t-input v-model="query.ownerId" placeholder="主人ID" style="width: 160px" />
<t-input v-if="!isCustomer" v-model="query.ownerId" placeholder="主人ID" style="width: 160px" />
<t-button theme="primary" @click="load">查询</t-button>
<t-button variant="outline" @click="openCreate">新增宠物</t-button>
</div>
<t-table :data="list" :columns="columns" row-key="id">
<template #gender="{ row }">
{{ getGenderText(row.gender) }}
</template>
<template #op="{ row }">
<div class="table-actions">
<t-button size="small" variant="text" @click="openEdit(row)">编辑</t-button>
@@ -21,6 +24,9 @@
<t-dialog v-model:visible="dialogVisible" :header="dialogTitle" :on-confirm="submit">
<t-form :data="form">
<t-form-item v-if="!isCustomer" label="主人" required>
<t-select v-model="form.ownerId" :options="customerOptions" placeholder="请选择主人" clearable />
</t-form-item>
<t-form-item label="名称"><t-input v-model="form.name" /></t-form-item>
<t-form-item label="品种"><t-input v-model="form.breed" /></t-form-item>
<t-form-item label="性别"><t-select v-model="form.gender" :options="genderOptions" /></t-form-item>
@@ -31,16 +37,28 @@
</template>
<script setup lang="ts">
import { onMounted, reactive, ref } from 'vue';
import { computed, onMounted, reactive, ref } from 'vue';
import { MessagePlugin } from 'tdesign-vue-next';
import { api } from '../api';
import { useAuthStore } from '../store/auth';
const query = reactive({ ownerId: '' });
const list = ref([] as any[]);
const customers = ref([] as any[]);
const dialogVisible = ref(false);
const dialogTitle = ref('新增宠物');
const editingId = ref<number | null>(null);
const form = reactive({ name: '', breed: '', gender: 'MALE', weight: '' });
const form = reactive({ ownerId: '', name: '', breed: '', gender: 'MALE', weight: '' });
const auth = useAuthStore();
const isCustomer = computed(() => auth.user?.role === 'CUSTOMER');
const customerOptions = computed(() => {
return customers.value.map((customer: any) => ({
label: customer.username || `顾客${customer.id}`,
value: customer.id,
}));
});
const columns = [
{ colKey: 'id', title: 'ID', width: 80 },
@@ -56,6 +74,14 @@ const genderOptions = [
{ label: '雌性', value: 'FEMALE' },
];
const getGenderText = (gender: string) => {
const map: Record<string, string> = {
MALE: '雄性',
FEMALE: '雌性',
};
return map[gender] || gender || '-';
};
const load = async () => {
const params: any = { page: 1, size: 20 };
if (query.ownerId) params.ownerId = query.ownerId;
@@ -66,6 +92,9 @@ const load = async () => {
const openCreate = () => {
dialogTitle.value = '新增宠物';
editingId.value = null;
form.ownerId = isCustomer.value
? auth.user?.userId || ''
: query.ownerId ? Number(query.ownerId) : '';
form.name = '';
form.breed = '';
form.gender = 'MALE';
@@ -76,6 +105,7 @@ const openCreate = () => {
const openEdit = (row: any) => {
dialogTitle.value = '编辑宠物';
editingId.value = row.id;
form.ownerId = row.ownerId || '';
form.name = row.name || '';
form.breed = row.breed || '';
form.gender = row.gender || 'MALE';
@@ -83,8 +113,18 @@ const openEdit = (row: any) => {
dialogVisible.value = true;
};
const loadCustomers = async () => {
const res = await api.users({ page: 1, size: 100, role: 'CUSTOMER' });
if (res.code === 0) {
customers.value = res.data?.records || [];
}
};
const submit = async () => {
const payload = { ...form };
const payload = { ...form } as any;
if (isCustomer.value) {
payload.ownerId = auth.user?.userId || payload.ownerId;
}
const res = editingId.value
? await api.updatePet(editingId.value, payload)
: await api.createPet(payload);
@@ -107,5 +147,10 @@ const remove = async (id: number) => {
}
};
onMounted(load);
onMounted(() => {
load();
if (!isCustomer.value) {
loadCustomers();
}
});
</script>

View File

@@ -14,6 +14,12 @@
</t-button>
</div>
<t-table :data="list" :columns="columns" row-key="id" bordered stripe hover>
<template #visitId="{ row }">
{{ getVisitLabel(row.visitId) }}
</template>
<template #doctorId="{ row }">
{{ getDoctorName(row.doctorId) }}
</template>
<template #status="{ row }">
<span class="status-badge" :class="getStatusClass(row.status)">
{{ getStatusText(row.status) }}
@@ -71,6 +77,18 @@ const visitOptions = computed(() => {
}));
});
const getVisitLabel = (visitId: number) => {
const visit = visits.value.find((item: any) => item.id === visitId);
if (!visit) return visitId ? `就诊ID ${visitId}` : '-';
return `就诊#${visit.id}`;
};
const getDoctorName = (doctorId: number) => {
const doctor = doctors.value.find((item: any) => item.id === doctorId);
if (!doctor) return doctorId ? `医生ID ${doctorId}` : '-';
return doctor.name || `医生${doctor.id}`;
};
const doctorOptions = computed(() => {
return doctors.value.map((doctor: any) => ({
label: doctor.name,
@@ -107,8 +125,8 @@ const statusOptions = [
const columns = [
{ colKey: 'id', title: 'ID', width: 80 },
{ colKey: 'visitId', title: '就诊ID' },
{ colKey: 'doctorId', title: '医生ID' },
{ colKey: 'visitId', title: '就诊' },
{ colKey: 'doctorId', title: '医生' },
{ colKey: 'status', title: '状态' },
{ colKey: 'op', title: '操作', width: 100 },
];

View File

@@ -3,15 +3,18 @@
<h2 class="page-title">检查报告</h2>
<div class="panel">
<div class="inline-form">
<t-input v-model="query.petId" placeholder="宠物ID" style="width: 160px" />
<t-select v-model="query.petId" :options="petOptions" placeholder="宠物" style="width: 200px" clearable />
<t-button theme="primary" @click="load">查询</t-button>
<t-button variant="outline" @click="openCreate">新增报告</t-button>
<t-button v-if="canEdit" variant="outline" @click="openCreate">新增报告</t-button>
</div>
<t-table :data="list" :columns="columns" row-key="id">
<template #petId="{ row }">
{{ getPetName(row.petId) }}
</template>
<template #op="{ row }">
<div class="table-actions">
<t-button size="small" variant="text" @click="openEdit(row)">编辑</t-button>
<t-popconfirm content="确认删除?" @confirm="remove(row.id)">
<t-button v-if="canEdit" size="small" variant="text" @click="openEdit(row)">编辑</t-button>
<t-popconfirm v-if="canEdit" content="确认删除?" @confirm="remove(row.id)">
<t-button size="small" theme="danger" variant="text">删除</t-button>
</t-popconfirm>
</div>
@@ -21,8 +24,12 @@
<t-dialog v-model:visible="dialogVisible" :header="dialogTitle" :on-confirm="submit">
<t-form :data="form">
<t-form-item label="就诊ID"><t-input v-model="form.visitId" /></t-form-item>
<t-form-item label="宠物ID"><t-input v-model="form.petId" /></t-form-item>
<t-form-item label="就诊记录" required>
<t-select v-model="form.visitId" :options="visitOptions" placeholder="请选择就诊记录" clearable />
</t-form-item>
<t-form-item label="宠物" required>
<t-select v-model="form.petId" :options="petOptions" placeholder="请选择宠物" clearable />
</t-form-item>
<t-form-item label="类型"><t-input v-model="form.type" /></t-form-item>
<t-form-item label="标题"><t-input v-model="form.title" /></t-form-item>
<t-form-item label="摘要"><t-textarea v-model="form.summary" /></t-form-item>
@@ -32,20 +39,51 @@
</template>
<script setup lang="ts">
import { onMounted, reactive, ref } from 'vue';
import { computed, onMounted, reactive, ref } from 'vue';
import { MessagePlugin } from 'tdesign-vue-next';
import { api } from '../api';
import { useAuthStore } from '../store/auth';
const list = ref([] as any[]);
const visits = ref([] as any[]);
const pets = ref([] as any[]);
const dialogVisible = ref(false);
const dialogTitle = ref('新增报告');
const editingId = ref<number | null>(null);
const query = reactive({ petId: '' });
const form = reactive({ visitId: '', petId: '', type: '', title: '', summary: '' });
const auth = useAuthStore();
const canEdit = computed(() => ['ADMIN', 'DOCTOR'].includes(auth.user?.role || ''));
const visitOptions = computed(() => {
return visits.value.map((visit: any) => {
const labelParts = [`就诊ID ${visit.id}`];
if (visit.petId) labelParts.push(`宠物ID ${visit.petId}`);
if (visit.customerId) labelParts.push(`顾客ID ${visit.customerId}`);
return {
label: labelParts.join(' / '),
value: visit.id,
};
});
});
const petOptions = computed(() => {
return pets.value.map((pet: any) => ({
label: `${pet.name || '宠物'}${pet.breed ? ` (${pet.breed})` : ''}`,
value: pet.id,
}));
});
const getPetName = (petId: number) => {
const pet = pets.value.find((item: any) => item.id === petId);
if (!pet) return petId ? `宠物ID ${petId}` : '-';
return `${pet.name || '宠物'}${pet.breed ? ` (${pet.breed})` : ''}`;
};
const columns = [
{ colKey: 'id', title: 'ID', width: 80 },
{ colKey: 'petId', title: '宠物ID' },
{ colKey: 'petId', title: '宠物' },
{ colKey: 'type', title: '类型' },
{ colKey: 'title', title: '标题' },
{ colKey: 'op', title: '操作', width: 140 },
@@ -59,10 +97,11 @@ const load = async () => {
};
const openCreate = () => {
if (!canEdit.value) return;
dialogTitle.value = '新增报告';
editingId.value = null;
form.visitId = '';
form.petId = query.petId;
form.petId = query.petId ? Number(query.petId) : '';
form.type = '';
form.title = '';
form.summary = '';
@@ -70,6 +109,7 @@ const openCreate = () => {
};
const openEdit = (row: any) => {
if (!canEdit.value) return;
dialogTitle.value = '编辑报告';
editingId.value = row.id;
form.visitId = row.visitId || '';
@@ -80,7 +120,18 @@ const openEdit = (row: any) => {
dialogVisible.value = true;
};
const loadVisits = async () => {
const res = await api.visits({ page: 1, size: 100 });
if (res.code === 0) visits.value = res.data?.records || [];
};
const loadPets = async () => {
const res = await api.pets({ page: 1, size: 100 });
if (res.code === 0) pets.value = res.data?.records || [];
};
const submit = async () => {
if (!canEdit.value) return;
const payload = { ...form };
const res = editingId.value
? await api.updateReport(editingId.value, payload)
@@ -95,6 +146,7 @@ const submit = async () => {
};
const remove = async (id: number) => {
if (!canEdit.value) return;
const res = await api.deleteReport(id);
if (res.code === 0) {
MessagePlugin.success('删除成功');
@@ -104,5 +156,9 @@ const remove = async (id: number) => {
}
};
onMounted(load);
onMounted(() => {
load();
loadVisits();
loadPets();
});
</script>

View File

@@ -6,12 +6,18 @@
<t-button theme="primary" @click="load">刷新</t-button>
<t-button variant="outline" @click="openCreate">新增入库</t-button>
</div>
<t-table :data="list" :columns="columns" row-key="id" />
<t-table :data="list" :columns="columns" row-key="id">
<template #drugId="{ row }">
{{ getDrugName(row.drugId) }}
</template>
</t-table>
</div>
<t-dialog v-model:visible="dialogVisible" header="新增入库" :on-confirm="submitCreate">
<t-form :data="form">
<t-form-item label="药品ID"><t-input v-model="form.drugId" /></t-form-item>
<t-form-item label="药品" required>
<t-select v-model="form.drugId" :options="drugOptions" placeholder="请选择药品" clearable />
</t-form-item>
<t-form-item label="数量"><t-input v-model="form.quantity" /></t-form-item>
</t-form>
</t-dialog>
@@ -19,17 +25,31 @@
</template>
<script setup lang="ts">
import { onMounted, reactive, ref } from 'vue';
import { computed, onMounted, reactive, ref } from 'vue';
import { MessagePlugin } from 'tdesign-vue-next';
import { api } from '../api';
const list = ref([] as any[]);
const dialogVisible = ref(false);
const form = reactive({ drugId: '', quantity: '' });
const drugs = ref([] as any[]);
const drugOptions = computed(() => {
return drugs.value.map((drug: any) => ({
label: drug.name,
value: drug.id,
}));
});
const getDrugName = (drugId: number) => {
const drug = drugs.value.find((item: any) => item.id === drugId);
if (!drug) return drugId ? `药品ID ${drugId}` : '-';
return drug.name || `药品${drug.id}`;
};
const columns = [
{ colKey: 'id', title: 'ID', width: 80 },
{ colKey: 'drugId', title: '药品ID' },
{ colKey: 'drugId', title: '药品' },
{ colKey: 'quantity', title: '数量' },
{ colKey: 'stockInTime', title: '入库时间' },
];
@@ -39,6 +59,11 @@ const load = async () => {
if (res.code === 0) list.value = res.data.records || [];
};
const loadDrugs = async () => {
const res = await api.drugs({ page: 1, size: 200 });
if (res.code === 0) drugs.value = res.data?.records || [];
};
const openCreate = () => {
form.drugId = '';
form.quantity = '';
@@ -56,5 +81,8 @@ const submitCreate = async () => {
}
};
onMounted(load);
onMounted(() => {
load();
loadDrugs();
});
</script>

View File

@@ -6,12 +6,18 @@
<t-button theme="primary" @click="load">刷新</t-button>
<t-button variant="outline" @click="openCreate">新增出库</t-button>
</div>
<t-table :data="list" :columns="columns" row-key="id" />
<t-table :data="list" :columns="columns" row-key="id">
<template #drugId="{ row }">
{{ getDrugName(row.drugId) }}
</template>
</t-table>
</div>
<t-dialog v-model:visible="dialogVisible" header="新增出库" :on-confirm="submitCreate">
<t-form :data="form">
<t-form-item label="药品ID"><t-input v-model="form.drugId" /></t-form-item>
<t-form-item label="药品" required>
<t-select v-model="form.drugId" :options="drugOptions" placeholder="请选择药品" clearable />
</t-form-item>
<t-form-item label="数量"><t-input v-model="form.quantity" /></t-form-item>
</t-form>
</t-dialog>
@@ -19,17 +25,31 @@
</template>
<script setup lang="ts">
import { onMounted, reactive, ref } from 'vue';
import { computed, onMounted, reactive, ref } from 'vue';
import { MessagePlugin } from 'tdesign-vue-next';
import { api } from '../api';
const list = ref([] as any[]);
const dialogVisible = ref(false);
const form = reactive({ drugId: '', quantity: '' });
const drugs = ref([] as any[]);
const drugOptions = computed(() => {
return drugs.value.map((drug: any) => ({
label: drug.name,
value: drug.id,
}));
});
const getDrugName = (drugId: number) => {
const drug = drugs.value.find((item: any) => item.id === drugId);
if (!drug) return drugId ? `药品ID ${drugId}` : '-';
return drug.name || `药品${drug.id}`;
};
const columns = [
{ colKey: 'id', title: 'ID', width: 80 },
{ colKey: 'drugId', title: '药品ID' },
{ colKey: 'drugId', title: '药品' },
{ colKey: 'quantity', title: '数量' },
{ colKey: 'stockOutTime', title: '出库时间' },
];
@@ -39,6 +59,11 @@ const load = async () => {
if (res.code === 0) list.value = res.data.records || [];
};
const loadDrugs = async () => {
const res = await api.drugs({ page: 1, size: 200 });
if (res.code === 0) drugs.value = res.data?.records || [];
};
const openCreate = () => {
form.drugId = '';
form.quantity = '';
@@ -56,5 +81,8 @@ const submitCreate = async () => {
}
};
onMounted(load);
onMounted(() => {
load();
loadDrugs();
});
</script>

View File

@@ -8,6 +8,12 @@
<t-button variant="outline" @click="openCreate">新增账号</t-button>
</div>
<t-table :data="list" :columns="columns" row-key="id">
<template #role="{ row }">
{{ getRoleText(row.role) }}
</template>
<template #status="{ row }">
{{ getStatusText(row.status) }}
</template>
<template #op="{ row }">
<div class="table-actions">
<t-button size="small" variant="text" @click="toggleStatus(row)">
@@ -54,6 +60,23 @@ const roleOptions = [
{ label: '顾客', value: 'CUSTOMER' },
];
const getRoleText = (role: string) => {
const map: Record<string, string> = {
ADMIN: '管理员',
DOCTOR: '医生',
CUSTOMER: '顾客',
};
return map[role] || role || '-';
};
const getStatusText = (status: number) => {
const map: Record<number, string> = {
0: '禁用',
1: '启用',
};
return map[status] ?? '-';
};
const columns = [
{ colKey: 'id', title: 'ID', width: 80 },
{ colKey: 'username', title: '用户名' },

View File

@@ -13,6 +13,15 @@
</t-button>
</div>
<t-table :data="list" :columns="columns" row-key="id" bordered stripe hover>
<template #customerId="{ row }">
{{ getCustomerName(row.customerId) }}
</template>
<template #petId="{ row }">
{{ getPetName(row.petId) }}
</template>
<template #doctorId="{ row }">
{{ getDoctorName(row.doctorId) }}
</template>
<template #status="{ row }">
<span class="status-badge" :class="getStatusClass(row.status)">
{{ getStatusText(row.status) }}
@@ -90,6 +99,7 @@
import { onMounted, reactive, ref, computed } from 'vue';
import { MessagePlugin } from 'tdesign-vue-next';
import { api } from '../api';
import { useAuthStore } from '../store/auth';
const list = ref([] as any[]);
const customers = ref([] as any[]);
@@ -98,6 +108,9 @@ const doctors = ref([] as any[]);
const dialogVisible = ref(false);
const dialogTitle = ref('新增就诊');
const editingId = ref<number | null>(null);
const auth = useAuthStore();
const isAdmin = computed(() => auth.user?.role === 'ADMIN');
const form = reactive({
customerId: '',
petId: '',
@@ -123,7 +136,7 @@ const petOptions = computed(() => {
const doctorOptions = computed(() => {
return doctors.value.map((doctor: any) => ({
label: doctor.name,
label: doctor.username || doctor.name,
value: doctor.id
}));
});
@@ -137,6 +150,24 @@ const getStatusText = (status: string) => {
return statusMap[status] || status;
};
const getCustomerName = (customerId: number) => {
const customer = customers.value.find((item: any) => item.id === customerId);
if (!customer) return customerId ? `顾客ID ${customerId}` : '-';
return customer.username || `顾客${customer.id}`;
};
const getPetName = (petId: number) => {
const pet = pets.value.find((item: any) => item.id === petId);
if (!pet) return petId ? `宠物ID ${petId}` : '-';
return `${pet.name}${pet.breed ? ` (${pet.breed})` : ''}`;
};
const getDoctorName = (doctorId: number) => {
const doctor = doctors.value.find((item: any) => item.id === doctorId);
if (!doctor) return doctorId ? `医生ID ${doctorId}` : '-';
return doctor.username || doctor.name || `医生${doctor.id}`;
};
const getStatusClass = (status: string) => {
const classMap: Record<string, string> = {
'IN_PROGRESS': 'status-pending',
@@ -188,9 +219,9 @@ const payMethodOptions = [
const columns = [
{ colKey: 'id', title: 'ID', width: 80 },
{ colKey: 'customerId', title: '顾客ID' },
{ colKey: 'petId', title: '宠物ID' },
{ colKey: 'doctorId', title: '医生ID' },
{ colKey: 'customerId', title: '顾客' },
{ colKey: 'petId', title: '宠物' },
{ colKey: 'doctorId', title: '医生' },
{ colKey: 'status', title: '就诊状态' },
{ colKey: 'paymentStatus', title: '支付状态' },
{ colKey: 'totalAmount', title: '总金额', width: 100 },
@@ -207,7 +238,7 @@ const openCreate = () => {
editingId.value = null;
form.customerId = '';
form.petId = '';
form.doctorId = '';
form.doctorId = isAdmin.value ? '' : auth.user?.userId || '';
form.status = 'IN_PROGRESS';
form.paymentStatus = 'UNPAID';
form.paymentMethod = 'OFFLINE';
@@ -227,6 +258,10 @@ const openEdit = (row: any) => {
};
const loadCustomers = async () => {
if (!isAdmin.value) {
customers.value = [];
return;
}
try {
const res = await api.users({ page: 1, size: 100, role: 'CUSTOMER' }); // 获取所有顾客
if (res.code === 0) {
@@ -255,6 +290,12 @@ const loadPets = async () => {
};
const loadDoctors = async () => {
if (!isAdmin.value) {
doctors.value = auth.user?.userId
? [{ id: auth.user.userId, username: auth.user.username }]
: [];
return;
}
try {
const res = await api.users({ page: 1, size: 100, role: 'DOCTOR' }); // 获取所有医生
if (res.code === 0) {

View File

@@ -0,0 +1,527 @@
@startuml
!theme plain
skinparam backgroundColor #FFFFFF
skinparam classAttributeIconSize 0
skinparam linetype ortho
package "com.gpf.pethospital.entity" {
class User {
- Long id
- String username
- String phone
- String email
- String password
- String role
- Integer status
- String avatar
- LocalDateTime createTime
- LocalDateTime updateTime
- Integer deleted
}
class Appointment {
- Long id
- Long userId
- Long petId
- Long doctorId
- LocalDateTime appointmentTime
- String reason
- String status
- LocalDateTime createTime
- LocalDateTime updateTime
- Integer deleted
}
class Pet {
- Long id
- Long ownerId
- String name
- String species
- String breed
- String gender
- LocalDate birthDate
- String medicalHistory
- LocalDateTime createTime
- LocalDateTime updateTime
- Integer deleted
}
class Drug {
- Long id
- String name
- String category
- String manufacturer
- String description
- BigDecimal price
- Integer stock
- LocalDateTime createTime
- LocalDateTime updateTime
- Integer deleted
}
class MedicalRecord {
- Long id
- Long appointmentId
- Long doctorId
- Long petId
- String symptoms
- String diagnosis
- String treatment
- LocalDateTime createTime
- LocalDateTime updateTime
- Integer deleted
}
class Prescription {
- Long id
- Long medicalRecordId
- String description
- LocalDateTime createTime
- LocalDateTime updateTime
- Integer deleted
}
class PrescriptionItem {
- Long id
- Long prescriptionId
- Long drugId
- Integer quantity
- String usage
- LocalDateTime createTime
- LocalDateTime updateTime
- Integer deleted
}
class Visit {
- Long id
- Long appointmentId
- Long doctorId
- Long petId
- String chiefComplaint
- String physicalExamination
- String diagnosis
- String treatmentPlan
- LocalDateTime visitTime
- LocalDateTime createTime
- LocalDateTime updateTime
- Integer deleted
}
class VaccineRecord {
- Long id
- Long petId
- Long vaccineId
- String vaccineName
- LocalDate vaccinationDate
- LocalDate nextDueDate
- String administeredBy
- LocalDateTime createTime
- LocalDateTime updateTime
- Integer deleted
}
class Order {
- Long id
- Long userId
- String orderNo
- String status
- BigDecimal totalAmount
- LocalDateTime createTime
- LocalDateTime updateTime
- Integer deleted
}
class StockIn {
- Long id
- Long drugId
- Integer quantity
- BigDecimal unitPrice
- String supplier
- String operator
- LocalDateTime createTime
- LocalDateTime updateTime
- Integer deleted
}
class StockOut {
- Long id
- Long drugId
- Integer quantity
- String purpose
- String operator
- LocalDateTime createTime
- LocalDateTime updateTime
- Integer deleted
}
class Message {
- Long id
- Long senderId
- Long receiverId
- String title
- String content
- String status
- LocalDateTime createTime
- LocalDateTime updateTime
- Integer deleted
}
class Notice {
- Long id
- String title
- String content
- String publisher
- LocalDateTime publishTime
- LocalDateTime createTime
- LocalDateTime updateTime
- Integer deleted
}
class Report {
- Long id
- String reportType
- String title
- String content
- LocalDate reportDate
- String generatedBy
- LocalDateTime createTime
- LocalDateTime updateTime
- Integer deleted
}
}
package "com.gpf.pethospital.mapper" {
interface BaseMapper<T> {
+ insert(T entity): int
+ deleteById(Serializable id): int
+ updateById(T entity): int
+ selectById(Serializable id): T
+ selectList(Wrapper<T> queryWrapper): List<T>
}
interface UserMapper {
}
interface AppointmentMapper {
}
interface PetMapper {
}
interface DrugMapper {
}
interface MedicalRecordMapper {
}
interface PrescriptionMapper {
}
interface PrescriptionItemMapper {
}
interface VisitMapper {
}
interface VaccineRecordMapper {
}
interface OrderMapper {
}
interface StockInMapper {
}
interface StockOutMapper {
}
interface MessageMapper {
}
interface NoticeMapper {
}
interface ReportMapper {
}
}
package "com.gpf.pethospital.service" {
interface IService<T> {
+ save(T entity): boolean
+ removeById(Serializable id): boolean
+ updateById(T entity): boolean
+ getById(Serializable id): T
+ list(): List<T>
+ page(IPage<T> page, Wrapper<T> queryWrapper): IPage<T>
}
interface UserService {
+ register(User user): boolean
+ login(String username, String password): AuthUser
}
interface AppointmentService {
+ book(Appointment appointment): boolean
+ cancel(Long id): boolean
+ getByUserId(Long userId): List<Appointment>
}
interface PetService {
+ getPetsByOwnerId(Long ownerId): List<Pet>
+ updatePet(Pet pet): boolean
}
interface DrugService {
+ getLowStockDrugs(): List<Drug>
+ updateStock(Long drugId, Integer quantity): boolean
}
interface MedicalRecordService {
+ createRecord(MedicalRecord record): boolean
+ getRecordsByPetId(Long petId): List<MedicalRecord>
}
interface PrescriptionService {
+ createPrescription(Prescription prescription): boolean
+ getPrescriptionsByRecordId(Long recordId): List<Prescription>
}
interface PrescriptionItemService {
+ addItems(Long prescriptionId, List<PrescriptionItem> items): boolean
}
interface VisitService {
+ createVisit(Visit visit): boolean
+ getVisitsByAppointmentId(Long appointmentId): Visit
}
interface VaccineRecordService {
+ addVaccineRecord(VaccineRecord record): boolean
+ getVaccineRecordsByPetId(Long petId): List<VaccineRecord>
}
interface OrderService {
+ createOrder(Order order): boolean
+ updateOrderStatus(Long orderId, String status): boolean
}
interface StockInService {
+ addStock(StockIn stockIn): boolean
}
interface StockOutService {
+ consumeStock(StockOut stockOut): boolean
}
interface MessageService {
+ sendMessage(Message message): boolean
+ getMessagesByUserId(Long userId): List<Message>
}
interface NoticeService {
+ publishNotice(Notice notice): boolean
+ getPublishedNotices(): List<Notice>
}
interface ReportService {
+ generateReport(String type, LocalDate startDate, LocalDate endDate): Report
+ getReportsByType(String type): List<Report>
}
}
package "com.gpf.pethospital.service.impl" {
class UserServiceImpl {
}
class AppointmentServiceImpl {
}
class PetServiceImpl {
}
class DrugServiceImpl {
}
class MedicalRecordServiceImpl {
}
class PrescriptionServiceImpl {
}
class PrescriptionItemServiceImpl {
}
class VisitServiceImpl {
}
class VaccineRecordServiceImpl {
}
class OrderServiceImpl {
}
class StockInServiceImpl {
}
class StockOutServiceImpl {
}
class MessageServiceImpl {
}
class NoticeServiceImpl {
}
class ReportServiceImpl {
}
}
package "com.gpf.pethospital.controller" {
class UserController {
- UserService userService
- PasswordEncoder passwordEncoder
+ me(): ApiResponse<?>
+ updateMe(User payload): ApiResponse<?>
+ list(long page, long size, String role): ApiResponse<?>
+ create(User user): ApiResponse<?>
+ updateStatus(Long id, Integer status): ApiResponse<?>
+ resetPassword(Long id, String newPassword): ApiResponse<?>
+ stats(): ApiResponse<?>
}
class AppointmentController {
- AppointmentService appointmentService
+ book(Appointment appointment): ApiResponse<?>
+ cancel(Long id): ApiResponse<?>
+ list(Long userId, String status): ApiResponse<?>
}
class PetController {
- PetService petService
+ listByOwner(Long ownerId): ApiResponse<?>
+ create(Pet pet): ApiResponse<?>
+ update(Pet pet): ApiResponse<?>
}
class DrugController {
- DrugService drugService
+ list(String category, String keyword): ApiResponse<?>
+ create(Drug drug): ApiResponse<?>
+ update(Drug drug): ApiResponse<?>
}
class MedicalRecordController {
- MedicalRecordService medicalRecordService
+ create(MedicalRecord record): ApiResponse<?>
+ getByPet(Long petId): ApiResponse<?>
}
class PrescriptionController {
- PrescriptionService prescriptionService
+ create(Prescription prescription): ApiResponse<?>
+ getByRecord(Long recordId): ApiResponse<?>
}
class PrescriptionItemController {
- PrescriptionItemService prescriptionItemService
+ addItems(Long prescriptionId, List<PrescriptionItem> items): ApiResponse<?>
}
class VaccineRecordController {
- VaccineRecordService vaccineRecordService
+ addRecord(VaccineRecord record): ApiResponse<?>
+ getByPet(Long petId): ApiResponse<?>
}
class OrderController {
- OrderService orderService
+ create(Order order): ApiResponse<?>
+ updateStatus(Long orderId, String status): ApiResponse<?>
}
class StockInController {
- StockInService stockInService
+ addStock(StockIn stockIn): ApiResponse<?>
}
class StockOutController {
- StockOutService stockOutService
+ consume(StockOut stockOut): ApiResponse<?>
}
class MessageController {
- MessageService messageService
+ send(Message message): ApiResponse<?>
+ listByUser(Long userId): ApiResponse<?>
}
class NoticeController {
- NoticeService noticeService
+ publish(Notice notice): ApiResponse<?>
+ list(): ApiResponse<?>
}
class ReportController {
- ReportService reportService
+ generate(String type, LocalDate startDate, LocalDate endDate): ApiResponse<?>
+ list(String type): ApiResponse<?>
}
class AuthController {
- UserService userService
- JwtTokenUtil jwtTokenUtil
+ register(User user): ApiResponse<?>
+ login(UserLoginDto loginDto): ApiResponse<?>
}
}
package "com.gpf.pethospital.config" {
class SecurityConfig {
}
class WebConfig {
}
class MybatisPlusConfig {
}
}
package "com.gpf.pethospital.common" {
class ApiResponse {
- Integer code
- String message
- Object data
+ success(Object data): ApiResponse
+ error(Integer code, String message): ApiResponse
}
}
' 关系映射
User ||--o{ Appointment : "makes"
User ||--o{ Pet : "owns"
User ||--o{ MedicalRecord : "creates"
User ||--o{ Message : "sends/receives"
User ||--o{ Order : "places"
Pet ||--o{ Appointment : "has"
Pet ||--o{ MedicalRecord : "has"
Pet ||--o{ VaccineRecord : "has"
Pet ||--o{ Visit : "has"
Appointment ||--|| MedicalRecord : "generates"
Appointment ||--|| Visit : "generates"
Drug }o--o{ PrescriptionItem : "included in"
Prescription ||--o{ PrescriptionItem : "contains"
MedicalRecord ||--o{ Prescription : "has"
MedicalRecord ||--o{ Visit : "has"
StockIn ||--|| Drug : "affects"
StockOut ||--|| Drug : "affects"
Order ||--o{ PrescriptionItem : "contains"
User ||--o{ VaccineRecord : "administers"
@enduml

View File

@@ -0,0 +1,64 @@
@startuml
!theme plain
skinparam backgroundColor #FFFFFF
skinparam sequence {
ArrowColor #333333
LifeLineBorderColor #777777
LifeLineBackgroundColor #FFFFFF
LifeLineBorderColor #AAAAAA
ParticipantBorderColor #AAAAAA
ParticipantBackgroundColor #F5F5F5
ParticipantFontColor #333333
ParticipantFontSize 12
}
title Pet Hospital Appointment Booking Process
actor Customer as C
participant "UserController" as UC
participant "UserService" as US
participant "AppointmentService" as AS
participant "AppointmentMapper" as AM
database "Database" as DB
C -> UC: Login(username, password)
UC -> US: login(username, password)
US -> DB: SELECT user FROM user WHERE username=?
activate US
return AuthUser
return JWT Token
deactivate US
C -> UC: View available pets
UC -> US: getPetsByOwnerId(userId)
activate US
US -> DB: SELECT pets FROM pet WHERE ownerId=?
return List<Pet>
deactivate US
C -> UC: Book appointment(Appointment)
activate UC
UC -> AS: book(Appointment)
activate AS
AS -> DB: INSERT appointment
activate AM
AM -> DB: Insert appointment record
return Success
deactivate AM
return Appointment ID
deactivate AS
return Success Response
deactivate UC
C -> UC: View appointments
UC -> AS: getByUserId(userId)
activate AS
AS -> DB: SELECT appointments FROM appointment WHERE userId=?
return List<Appointment>
deactivate AS
return Appointment List
deactivate UC
note right: Customer receives confirmation\nof successful booking
@enduml

View File

@@ -0,0 +1,92 @@
@startuml
!theme plain
left to right direction
skinparam backgroundColor #FFFFFF
skinparam actorStyle awesome
skinparam linetype ortho
actor "Customer" as Customer
actor "Doctor" as Doctor
actor "Admin" as Admin
package "Pet Hospital System" {
usecase "Register Account" as UC1
usecase "Login" as UC2
usecase "View Profile" as UC3
usecase "Update Profile" as UC4
usecase "View Pets" as UC5
usecase "Add Pet" as UC6
usecase "Update Pet Info" as UC7
usecase "View Pet Records" as UC8
usecase "Book Appointment" as UC9
usecase "Cancel Appointment" as UC10
usecase "View Appointments" as UC11
usecase "View Prescriptions" as UC12
usecase "View Medical Records" as UC13
usecase "View Vaccination History" as UC14
usecase "View Available Time Slots" as UC15
usecase "Confirm Appointment" as UC16
usecase "Manage Users" as UC17
usecase "Manage Drugs" as UC18
usecase "Manage Appointments" as UC19
usecase "Generate Reports" as UC20
usecase "Create Medical Record" as UC21
usecase "Create Prescription" as UC22
usecase "Add Vaccination Record" as UC23
usecase "Perform Visit" as UC24
usecase "View Statistics" as UC25
usecase "View System Notices" as UC26
usecase "Send Messages" as UC27
}
Customer --> UC1
Customer --> UC2
Customer --> UC3
Customer --> UC4
Customer --> UC5
Customer --> UC6
Customer --> UC7
Customer --> UC8
Customer --> UC9
Customer --> UC10
Customer --> UC11
Customer --> UC12
Customer --> UC13
Customer --> UC14
Customer --> UC15
Customer --> UC16
Customer --> UC26
Customer --> UC27
Doctor --> UC2
Doctor --> UC3
Doctor --> UC11
Doctor --> UC13
Doctor --> UC14
Doctor --> UC21
Doctor --> UC22
Doctor --> UC23
Doctor --> UC24
Doctor --> UC26
Doctor --> UC27
Admin --> UC17
Admin --> UC18
Admin --> UC19
Admin --> UC20
Admin --> UC25
Admin --> UC26
UC9 ..> UC15 : includes
UC21 ..> UC9 : extends
UC22 ..> UC21 : extends
UC24 ..> UC9 : extends
@enduml

236
system_er_diagram.drawio Normal file
View File

@@ -0,0 +1,236 @@
<mxfile host="app.diagrams.net" modified="2026-01-30T13:55:00.000Z" agent="codex" version="24.7.7">
<diagram name="system er diagram"><mxGraphModel dx="1426" dy="769" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" background="#ffffff" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<!-- User实体 -->
<mxCell id="2" value="User" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=26;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;swimlaneFillColor=#ffffff;rounded=0;shadow=0;comic=0;labelBackgroundColor=none;strokeColor=#000000;strokeWidth=1;fillColor=none;fontFamily=Verdana;fontSize=12;fontColor=#000000;" vertex="1" parent="1">
<mxGeometry x="100" y="80" width="160" height="230" as="geometry"/>
</mxCell>
<mxCell id="3" value="+ id: BIGINT + username: VARCHAR(50) + phone: VARCHAR(20) + email: VARCHAR(100) + password: VARCHAR(255) + role: VARCHAR(20) + status: INT + avatar: VARCHAR(255) + create_time: TIMESTAMP + update_time: TIMESTAMP + deleted: INT" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;" vertex="1" parent="2">
<mxGeometry y="26" width="160" height="204" as="geometry"/>
</mxCell>
<mxCell id="4" value="" style="line;strokeWidth=1;fillColor=none;align=left;verticalAlign=middle;spacingTop=-1;spacingLeft=3;spacingRight=3;rotatable=0;labelPosition=right;points=[];portConstraint=eastwest;" vertex="1" parent="2">
<mxGeometry y="230" width="160" height="30" as="geometry"/>
</mxCell>
<!-- Doctor实体 -->
<mxCell id="5" value="Doctor" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=26;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;swimlaneFillColor=#ffffff;rounded=0;shadow=0;comic=0;labelBackgroundColor=none;strokeColor=#000000;strokeWidth=1;fillColor=none;fontFamily=Verdana;fontSize=12;fontColor=#000000;" vertex="1" parent="1">
<mxGeometry x="320" y="80" width="160" height="230" as="geometry"/>
</mxCell>
<mxCell id="6" value="+ id: BIGINT + name: VARCHAR(50) + department: VARCHAR(50) + title: VARCHAR(50) + phone: VARCHAR(20) + email: VARCHAR(100) + avatar: VARCHAR(255) + status: INT + create_time: TIMESTAMP + update_time: TIMESTAMP + deleted: INT" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;" vertex="1" parent="5">
<mxGeometry y="26" width="160" height="204" as="geometry"/>
</mxCell>
<mxCell id="7" value="" style="line;strokeWidth=1;fillColor=none;align=left;verticalAlign=middle;spacingTop=-1;spacingLeft=3;spacingRight=3;rotatable=0;labelPosition=right;points=[];portConstraint=eastwest;" vertex="1" parent="5">
<mxGeometry y="230" width="160" height="30" as="geometry"/>
</mxCell>
<!-- Pet实体 -->
<mxCell id="8" value="Pet" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=26;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;swimlaneFillColor=#ffffff;rounded=0;shadow=0;comic=0;labelBackgroundColor=none;strokeColor=#000000;strokeWidth=1;fillColor=none;fontFamily=Verdana;fontSize=12;fontColor=#000000;" vertex="1" parent="1">
<mxGeometry x="540" y="80" width="160" height="230" as="geometry"/>
</mxCell>
<mxCell id="9" value="+ id: BIGINT + owner_id: BIGINT + name: VARCHAR(50) + species: VARCHAR(50) + breed: VARCHAR(100) + gender: VARCHAR(10) + birthday: DATE + weight: DOUBLE + photo: VARCHAR(255) + remark: TEXT + create_time: TIMESTAMP + update_time: TIMESTAMP + deleted: INT" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;" vertex="1" parent="8">
<mxGeometry y="26" width="160" height="204" as="geometry"/>
</mxCell>
<mxCell id="10" value="" style="line;strokeWidth=1;fillColor=none;align=left;verticalAlign=middle;spacingTop=-1;spacingLeft=3;spacingRight=3;rotatable=0;labelPosition=right;points=[];portConstraint=eastwest;" vertex="1" parent="8">
<mxGeometry y="230" width="160" height="30" as="geometry"/>
</mxCell>
<!-- Appointment实体 -->
<mxCell id="11" value="Appointment" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=26;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;swimlaneFillColor=#ffffff;rounded=0;shadow=0;comic=0;labelBackgroundColor=none;strokeColor=#000000;strokeWidth=1;fillColor=none;fontFamily=Verdana;fontSize=12;fontColor=#000000;" vertex="1" parent="1">
<mxGeometry x="100" y="380" width="160" height="230" as="geometry"/>
</mxCell>
<mxCell id="12" value="+ id: BIGINT + customer_id: BIGINT + pet_id: BIGINT + doctor_id: BIGINT + department: VARCHAR(50) + appointment_date: DATE + time_slot: VARCHAR(20) + status: VARCHAR(20) + remark: TEXT + cancel_time: TIMESTAMP + create_time: TIMESTAMP + update_time: TIMESTAMP + deleted: INT" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;" vertex="1" parent="11">
<mxGeometry y="26" width="160" height="204" as="geometry"/>
</mxCell>
<mxCell id="13" value="" style="line;strokeWidth=1;fillColor=none;align=left;verticalAlign=middle;spacingTop=-1;spacingLeft=3;spacingRight=3;rotatable=0;labelPosition=right;points=[];portConstraint=eastwest;" vertex="1" parent="11">
<mxGeometry y="230" width="160" height="30" as="geometry"/>
</mxCell>
<!-- Visit实体 -->
<mxCell id="14" value="Visit" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=26;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;swimlaneFillColor=#ffffff;rounded=0;shadow=0;comic=0;labelBackgroundColor=none;strokeColor=#000000;strokeWidth=1;fillColor=none;fontFamily=Verdana;fontSize=12;fontColor=#000000;" vertex="1" parent="1">
<mxGeometry x="320" y="380" width="160" height="230" as="geometry"/>
</mxCell>
<mxCell id="15" value="+ id: BIGINT + appointment_id: BIGINT + customer_id: BIGINT + pet_id: BIGINT + doctor_id: BIGINT + symptoms: TEXT + diagnosis: TEXT + treatment_plan: TEXT + status: VARCHAR(20) + total_amount: DECIMAL(10,2) + payment_status: VARCHAR(20) + payment_method: VARCHAR(20) + payment_time: TIMESTAMP + start_time: TIMESTAMP + end_time: TIMESTAMP + create_time: TIMESTAMP + update_time: TIMESTAMP + deleted: INT" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;" vertex="1" parent="14">
<mxGeometry y="26" width="160" height="204" as="geometry"/>
</mxCell>
<mxCell id="16" value="" style="line;strokeWidth=1;fillColor=none;align=left;verticalAlign=middle;spacingTop=-1;spacingLeft=3;spacingRight=3;rotatable=0;labelPosition=right;points=[];portConstraint=eastwest;" vertex="1" parent="14">
<mxGeometry y="230" width="160" height="30" as="geometry"/>
</mxCell>
<!-- Medical Record实体 -->
<mxCell id="17" value="Medical Record" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=26;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;swimlaneFillColor=#ffffff;rounded=0;shadow=0;comic=0;labelBackgroundColor=none;strokeColor=#000000;strokeWidth=1;fillColor=none;fontFamily=Verdana;fontSize=12;fontColor=#000000;" vertex="1" parent="1">
<mxGeometry x="540" y="380" width="160" height="230" as="geometry"/>
</mxCell>
<mxCell id="18" value="+ id: BIGINT + visit_id: BIGINT + record_type: VARCHAR(50) + content: TEXT + attachment_urls: TEXT + doctor_id: BIGINT + create_time: TIMESTAMP + update_time: TIMESTAMP + deleted: INT" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;" vertex="1" parent="17">
<mxGeometry y="26" width="160" height="204" as="geometry"/>
</mxCell>
<mxCell id="19" value="" style="line;strokeWidth=1;fillColor=none;align=left;verticalAlign=middle;spacingTop=-1;spacingLeft=3;spacingRight=3;rotatable=0;labelPosition=right;points=[];portConstraint=eastwest;" vertex="1" parent="17">
<mxGeometry y="230" width="160" height="30" as="geometry"/>
</mxCell>
<!-- Prescription实体 -->
<mxCell id="20" value="Prescription" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=26;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;swimlaneFillColor=#ffffff;rounded=0;shadow=0;comic=0;labelBackgroundColor=none;strokeColor=#000000;strokeWidth=1;fillColor=none;fontFamily=Verdana;fontSize=12;fontColor=#000000;" vertex="1" parent="1">
<mxGeometry x="100" y="680" width="160" height="230" as="geometry"/>
</mxCell>
<mxCell id="21" value="+ id: BIGINT + visit_id: BIGINT + doctor_id: BIGINT + customer_id: BIGINT + total_amount: DECIMAL(10,2) + status: VARCHAR(20) + remark: TEXT + create_time: TIMESTAMP + update_time: TIMESTAMP + deleted: INT" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;" vertex="1" parent="20">
<mxGeometry y="26" width="160" height="204" as="geometry"/>
</mxCell>
<mxCell id="22" value="" style="line;strokeWidth=1;fillColor=none;align=left;verticalAlign=middle;spacingTop=-1;spacingLeft=3;spacingRight=3;rotatable=0;labelPosition=right;points=[];portConstraint=eastwest;" vertex="1" parent="20">
<mxGeometry y="230" width="160" height="30" as="geometry"/>
</mxCell>
<!-- Drug实体 -->
<mxCell id="23" value="Drug" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=26;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;swimlaneFillColor=#ffffff;rounded=0;shadow=0;comic=0;labelBackgroundColor=none;strokeColor=#000000;strokeWidth=1;fillColor=none;fontFamily=Verdana;fontSize=12;fontColor=#000000;" vertex="1" parent="1">
<mxGeometry x="320" y="680" width="160" height="230" as="geometry"/>
</mxCell>
<mxCell id="24" value="+ id: BIGINT + name: VARCHAR(100) + category: VARCHAR(50) + manufacturer: VARCHAR(100) + specification: VARCHAR(100) + unit_price: DECIMAL(10,2) + stock_quantity: INT + unit: VARCHAR(20) + status: INT + create_time: TIMESTAMP + update_time: TIMESTAMP + deleted: INT" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;" vertex="1" parent="23">
<mxGeometry y="26" width="160" height="204" as="geometry"/>
</mxCell>
<mxCell id="25" value="" style="line;strokeWidth=1;fillColor=none;align=left;verticalAlign=middle;spacingTop=-1;spacingLeft=3;spacingRight=3;rotatable=0;labelPosition=right;points=[];portConstraint=eastwest;" vertex="1" parent="23">
<mxGeometry y="230" width="160" height="30" as="geometry"/>
</mxCell>
<!-- 关系连线 -->
<mxCell id="26" value="1" style="endArrow=open;endSize=12;startArrow=diamondThin;startSize=14;startFill=0;edgeStyle=orthogonalEdgeStyle;align=left;verticalAlign=bottom;" edge="1" parent="1" source="2" target="8">
<mxGeometry x="-1" y="3" relative="1" as="geometry">
<mxPoint x="280" y="200" as="sourcePoint"/>
<mxPoint x="520" y="200" as="targetPoint"/>
<Array as="points">
<mxPoint x="260" y="200"/>
<mxPoint x="540" y="200"/>
</Array>
</mxGeometry>
</mxCell>
<mxCell id="27" value="*" style="text;html=1;resizable=0;points=[];align=center;verticalAlign=middle;labelBackgroundColor=#ffffff;" vertex="1" connectable="0" parent="26">
<mxGeometry x="0.7692" y="-1" relative="1" as="geometry">
<mxPoint x="-11" y="-13" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="28" value="1" style="endArrow=open;endSize=12;startArrow=diamondThin;startSize=14;startFill=0;edgeStyle=orthogonalEdgeStyle;align=left;verticalAlign=bottom;" edge="1" parent="1" source="2" target="11">
<mxGeometry x="-1" y="3" relative="1" as="geometry">
<mxPoint x="280" y="210" as="sourcePoint"/>
<mxPoint x="520" y="210" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="29" value="*" style="text;html=1;resizable=0;points=[];align=center;verticalAlign=middle;labelBackgroundColor=#ffffff;" vertex="1" connectable="0" parent="28">
<mxGeometry x="0.7692" y="-1" relative="1" as="geometry">
<mxPoint x="-11" y="-13" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="30" value="1" style="endArrow=open;endSize=12;startArrow=diamondThin;startSize=14;startFill=0;edgeStyle=orthogonalEdgeStyle;align=left;verticalAlign=bottom;" edge="1" parent="1" source="5" target="11">
<mxGeometry x="-1" y="3" relative="1" as="geometry">
<mxPoint x="290" y="220" as="sourcePoint"/>
<mxPoint x="530" y="220" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="31" value="*" style="text;html=1;resizable=0;points=[];align=center;verticalAlign=middle;labelBackgroundColor=#ffffff;" vertex="1" connectable="0" parent="30">
<mxGeometry x="0.7692" y="-1" relative="1" as="geometry">
<mxPoint x="-11" y="-13" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="32" value="1" style="endArrow=open;endSize=12;startArrow=diamondThin;startSize=14;startFill=0;edgeStyle=orthogonalEdgeStyle;align=left;verticalAlign=bottom;" edge="1" parent="1" source="8" target="11">
<mxGeometry x="-1" y="3" relative="1" as="geometry">
<mxPoint x="300" y="230" as="sourcePoint"/>
<mxPoint x="540" y="230" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="33" value="*" style="text;html=1;resizable=0;points=[];align=center;verticalAlign=middle;labelBackgroundColor=#ffffff;" vertex="1" connectable="0" parent="32">
<mxGeometry x="0.7692" y="-1" relative="1" as="geometry">
<mxPoint x="-11" y="-13" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="34" value="1" style="endArrow=open;endSize=12;startArrow=diamondThin;startSize=14;startFill=0;edgeStyle=orthogonalEdgeStyle;align=left;verticalAlign=bottom;" edge="1" parent="1" source="11" target="14">
<mxGeometry x="-1" y="3" relative="1" as="geometry">
<mxPoint x="310" y="240" as="sourcePoint"/>
<mxPoint x="550" y="240" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="35" value="1" style="text;html=1;resizable=0;points=[];align=center;verticalAlign=middle;labelBackgroundColor=#ffffff;" vertex="1" connectable="0" parent="34">
<mxGeometry x="0.7692" y="-1" relative="1" as="geometry">
<mxPoint x="-11" y="-13" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="36" value="1" style="endArrow=open;endSize=12;startArrow=diamondThin;startSize=14;startFill=0;edgeStyle=orthogonalEdgeStyle;align=left;verticalAlign=bottom;" edge="1" parent="1" source="2" target="14">
<mxGeometry x="-1" y="3" relative="1" as="geometry">
<mxPoint x="320" y="250" as="sourcePoint"/>
<mxPoint x="560" y="250" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="37" value="*" style="text;html=1;resizable=0;points=[];align=center;verticalAlign=middle;labelBackgroundColor=#ffffff;" vertex="1" connectable="0" parent="36">
<mxGeometry x="0.7692" y="-1" relative="1" as="geometry">
<mxPoint x="-11" y="-13" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="38" value="1" style="endArrow=open;endSize=12;startArrow=diamondThin;startSize=14;startFill=0;edgeStyle=orthogonalEdgeStyle;align=left;verticalAlign=bottom;" edge="1" parent="1" source="8" target="14">
<mxGeometry x="-1" y="3" relative="1" as="geometry">
<mxPoint x="330" y="260" as="sourcePoint"/>
<mxPoint x="570" y="260" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="39" value="*" style="text;html=1;resizable=0;points=[];align=center;verticalAlign=middle;labelBackgroundColor=#ffffff;" vertex="1" connectable="0" parent="38">
<mxGeometry x="0.7692" y="-1" relative="1" as="geometry">
<mxPoint x="-11" y="-13" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="40" value="1" style="endArrow=open;endSize=12;startArrow=diamondThin;startSize=14;startFill=0;edgeStyle=orthogonalEdgeStyle;align=left;verticalAlign=bottom;" edge="1" parent="1" source="5" target="14">
<mxGeometry x="-1" y="3" relative="1" as="geometry">
<mxPoint x="340" y="270" as="sourcePoint"/>
<mxPoint x="580" y="270" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="41" value="*" style="text;html=1;resizable=0;points=[];align=center;verticalAlign=middle;labelBackgroundColor=#ffffff;" vertex="1" connectable="0" parent="40">
<mxGeometry x="0.7692" y="-1" relative="1" as="geometry">
<mxPoint x="-11" y="-13" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="42" value="1" style="endArrow=open;endSize=12;startArrow=diamondThin;startSize=14;startFill=0;edgeStyle=orthogonalEdgeStyle;align=left;verticalAlign=bottom;" edge="1" parent="1" source="14" target="17">
<mxGeometry x="-1" y="3" relative="1" as="geometry">
<mxPoint x="350" y="280" as="sourcePoint"/>
<mxPoint x="590" y="280" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="43" value="*" style="text;html=1;resizable=0;points=[];align=center;verticalAlign=middle;labelBackgroundColor=#ffffff;" vertex="1" connectable="0" parent="42">
<mxGeometry x="0.7692" y="-1" relative="1" as="geometry">
<mxPoint x="-11" y="-13" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="44" value="1" style="endArrow=open;endSize=12;startArrow=diamondThin;startSize=14;startFill=0;edgeStyle=orthogonalEdgeStyle;align=left;verticalAlign=bottom;" edge="1" parent="1" source="5" target="17">
<mxGeometry x="-1" y="3" relative="1" as="geometry">
<mxPoint x="360" y="290" as="sourcePoint"/>
<mxPoint x="600" y="290" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="45" value="*" style="text;html=1;resizable=0;points=[];align=center;verticalAlign=middle;labelBackgroundColor=#ffffff;" vertex="1" connectable="0" parent="44">
<mxGeometry x="0.7692" y="-1" relative="1" as="geometry">
<mxPoint x="-11" y="-13" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="46" value="1" style="endArrow=open;endSize=12;startArrow=diamondThin;startSize=14;startFill=0;edgeStyle=orthogonalEdgeStyle;align=left;verticalAlign=bottom;" edge="1" parent="1" source="14" target="20">
<mxGeometry x="-1" y="3" relative="1" as="geometry">
<mxPoint x="370" y="300" as="sourcePoint"/>
<mxPoint x="610" y="300" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="47" value="*" style="text;html=1;resizable=0;points=[];align=center;verticalAlign=middle;labelBackgroundColor=#ffffff;" vertex="1" connectable="0" parent="46">
<mxGeometry x="0.7692" y="-1" relative="1" as="geometry">
<mxPoint x="-11" y="-13" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="48" value="1" style="endArrow=open;endSize=12;startArrow=diamondThin;startSize=14;startFill=0;edgeStyle=orthogonalEdgeStyle;align=left;verticalAlign=bottom;" edge="1" parent="1" source="5" target="20">
<mxGeometry x="-1" y="3" relative="1" as="geometry">
<mxPoint x="380" y="310" as="sourcePoint"/>
<mxPoint x="620" y="310" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="49" value="*" style="text;html=1;resizable=0;points=[];align=center;verticalAlign=middle;labelBackgroundColor=#ffffff;" vertex="1" connectable="0" parent="48">
<mxGeometry x="0.7692" y="-1" relative="1" as="geometry">
<mxPoint x="-11" y="-13" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="50" value="1" style="endArrow=open;endSize=12;startArrow=diamondThin;startSize=14;startFill=0;edgeStyle=orthogonalEdgeStyle;align=left;verticalAlign=bottom;" edge="1" parent="1" source="2" target="20">
<mxGeometry x="-1" y="3" relative="1" as="geometry">
<mxPoint x="390" y="320" as="sourcePoint"/>
<mxPoint x="630" y="320" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="51" value="*" style="text;html=1;resizable=0;points=[];align=center;verticalAlign=middle;labelBackgroundColor=#ffffff;" vertex="1" connectable="0" parent="50">
<mxGeometry x="0.7692" y="-1" relative="1" as="geometry">
<mxPoint x="-11" y="-13" as="offset"/>
</mxGeometry>
</mxCell>
</root>
</mxGraphModel></diagram>
</mxfile>

2059
thesis_with_diagrams.md Normal file

File diff suppressed because it is too large Load Diff

56
uml_diagrams_readme.md Normal file
View File

@@ -0,0 +1,56 @@
# 爱维宠物医院管理系统 - UML图说明文档
本目录包含为爱维宠物医院管理系统设计的各种UML图均按照《2026届网络工程毕业设计论文撰写规范应用开发类》要求制作。
## 文件说明
### 1. pet_hospital_uml.drawio
- **类型**: 用例图和类图
- **内容**:
- 系统用例图,展示三个角色(顾客、医生、管理员)的功能用例
- 系统类图,展示主要实体类及其关系
### 2. sequence_diagram.drawio
- **类型**: 序列图
- **内容**: 预约挂号流程的序列图,展示顾客、系统、预约服务和数据库之间的交互
### 3. activity_diagram.drawio
- **类型**: 活动图
- **内容**: 医生接诊流程的活动图,展示从登录到结束就诊的完整流程
### 4. er_diagram.drawio
- **类型**: 实体关系图
- **内容**: 数据库实体关系图,展示系统各实体及它们之间的关系
## 如何使用
1. 安装draw.io (现在称为 diagrams.net)
- 访问 https://app.diagrams.net/
- 或下载桌面版
2. 打开文件
- 在draw.io中选择"文件" → "打开"
- 选择对应的.drawio文件
3. 编辑和导出
- 可以根据需要调整图形样式
- 选择"文件" → "导出为" → 选择所需格式PNG、PDF、SVG等
## 论文中应用位置
根据撰写规范要求,这些图表可用于:
- 第3章 系统分析: 用例图
- 第3.4节 用例描述: 用例图
- 第4章 系统设计: 类图、序列图、活动图、ER图
- 第4.2节 类图: 系统类图
- 第4.3节 序列图: 序列图
- 第4.4节 活动图: 活动图
- 第4.5节 数据库设计: ER图
## 注意事项
1. 所有图表均已按照规范要求设计,包含足够的实体和关系
2. 图表命名遵循章节编号规则
3. 可根据实际需要调整图表细节
4. 导出图像时建议使用高分辨率以保证打印质量

View File

@@ -0,0 +1,72 @@
# 爱维宠物医院管理系统 - UML图说明文档
本目录包含为爱维宠物医院管理系统设计的各种UML图均按照《2026届网络工程毕业设计论文撰写规范应用开发类》要求制作。
## 文件说明
### 1. pet_hospital_uml.drawio
- **类型**: 用例图和类图
- **内容**:
- 系统用例图,展示三个角色(顾客、医生、管理员)的功能用例
- 系统类图,展示主要实体类及其关系
### 2. sequence_diagram.drawio
- **类型**: 序列图
- **内容**: 预约挂号流程的序列图,展示医生、系统、预约服务和数据库之间的交互
### 3. customer_sequence_diagram.drawio
- **类型**: 序列图
- **内容**: 顾客预约流程的序列图,展示顾客、系统、预约服务和数据库之间的交互
### 4. admin_sequence_diagram.drawio
- **类型**: 序列图
- **内容**: 管理员管理药品流程的序列图,展示管理员、系统、药品管理模块和数据库之间的交互
### 5. activity_diagram.drawio
- **类型**: 活动图
- **内容**: 医生接诊流程的活动图,展示从登录到结束就诊的完整流程
### 6. customer_activity_diagram.drawio
- **类型**: 活动图
- **内容**: 顾客查看病历流程的活动图,展示从登录到查看病历再到退出的完整流程
### 7. admin_activity_diagram.drawio
- **类型**: 活动图
- **内容**: 管理员管理用户流程的活动图,展示从登录到管理用户再到退出的完整流程
### 8. er_diagram.drawio
- **类型**: 实体关系图
- **内容**: 数据库实体关系图,展示系统各实体及它们之间的关系
## 如何使用
1. 安装draw.io (现在称为 diagrams.net)
- 访问 https://app.diagrams.net/
- 或下载桌面版
2. 打开文件
- 在draw.io中选择"文件" → "打开"
- 选择对应的.drawio文件
3. 编辑和导出
- 可以根据需要调整图形样式
- 选择"文件" → "导出为" → 选择所需格式PNG、PDF、SVG等
## 论文中应用位置
根据撰写规范要求,这些图表可用于:
- 第3章 系统分析: 用例图
- 第3.4节 用例描述: 用例图
- 第4章 系统设计: 类图、序列图、活动图、ER图
- 第4.2节 类图: 系统类图
- 第4.3节 序列图: 医生预约序列图、顾客预约序列图、管理员管理药品序列图
- 第4.4节 活动图: 医生接诊活动图、顾客查看病历活动图、管理员管理用户活动图
- 第4.5节 数据库设计: ER图
## 注意事项
1. 所有图表均已按照规范要求设计,包含足够的实体和关系
2. 图表命名遵循章节编号规则
3. 可根据实际需要调整图表细节
4. 导出图像时建议使用高分辨率以保证打印质量

View File

@@ -0,0 +1,189 @@
<mxfile host="app.diagrams.net" modified="2026-01-30T13:55:00.000Z" agent="codex" version="24.7.7">
<diagram name="user activity diagram"><mxGraphModel dx="1426" dy="769" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" background="#ffffff" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<!-- 开始节点 -->
<mxCell id="2" value="" style="ellipse;html=1;shape=startState;fillColor=#000000;strokeColor=#000000;" vertex="1" parent="1">
<mxGeometry x="360" y="40" width="30" height="30" as="geometry"/>
</mxCell>
<!-- 活动节点 -->
<mxCell id="3" value="顾客登录系统" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="335" y="110" width="80" height="40" as="geometry"/>
</mxCell>
<!-- 状态节点 -->
<mxCell id="4" value="系统验证顾客身份" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="335" y="180" width="80" height="40" as="geometry"/>
</mxCell>
<!-- 判断节点 -->
<mxCell id="5" value="" style="rhombus;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="360" y="250" width="30" height="30" as="geometry"/>
</mxCell>
<!-- 活动节点 -->
<mxCell id="6" value="显示错误信息" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="460" y="245" width="80" height="40" as="geometry"/>
</mxCell>
<!-- 活动节点 -->
<mxCell id="7" value="进入个人中心" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="335" y="320" width="80" height="40" as="geometry"/>
</mxCell>
<!-- 活动节点 -->
<mxCell id="8" value="选择宠物" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="335" y="390" width="80" height="40" as="geometry"/>
</mxCell>
<!-- 活动节点 -->
<mxCell id="9" value="查看病历记录" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="335" y="460" width="80" height="40" as="geometry"/>
</mxCell>
<!-- 活动节点 -->
<mxCell id="10" value="选择查看时间范围" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="335" y="530" width="80" height="40" as="geometry"/>
</mxCell>
<!-- 活动节点 -->
<mxCell id="11" value="系统查询病历数据" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="335" y="600" width="80" height="40" as="geometry"/>
</mxCell>
<!-- 活动节点 -->
<mxCell id="12" value="显示病历信息" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="335" y="670" width="80" height="40" as="geometry"/>
</mxCell>
<!-- 活动节点 -->
<mxCell id="13" value="查看病历详情" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="335" y="740" width="80" height="40" as="geometry"/>
</mxCell>
<!-- 判断节点 -->
<mxCell id="14" value="" style="rhombus;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="360" y="810" width="30" height="30" as="geometry"/>
</mxCell>
<!-- 活动节点 -->
<mxCell id="15" value="下载病历" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="460" y="805" width="80" height="40" as="geometry"/>
</mxCell>
<!-- 活动节点 -->
<mxCell id="16" value="继续浏览其他病历" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="335" y="880" width="80" height="40" as="geometry"/>
</mxCell>
<!-- 活动节点 -->
<mxCell id="17" value="退出系统" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="335" y="950" width="80" height="40" as="geometry"/>
</mxCell>
<!-- 结束节点 -->
<mxCell id="18" value="" style="ellipse;html=1;shape=endState;fillColor=#000000;strokeColor=#000000;" vertex="1" parent="1">
<mxGeometry x="360" y="1020" width="30" height="30" as="geometry"/>
</mxCell>
<!-- 结束节点 -->
<mxCell id="19" value="" style="ellipse;html=1;shape=endState;fillColor=#000000;strokeColor=#000000;" vertex="1" parent="1">
<mxGeometry x="500" y="315" width="30" height="30" as="geometry"/>
</mxCell>
<!-- 连接线 -->
<mxCell id="20" value="" style="endArrow=classic;html=1;exitX=0.5;exitY=1;exitPerimeter=0;entryX=0.5;entryY=0;" edge="1" parent="1" source="2" target="3">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="210" y="140" as="sourcePoint"/>
<mxPoint x="260" y="90" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="21" value="" style="endArrow=classic;html=1;exitX=0.5;exitY=1;entryX=0.5;entryY=0;" edge="1" parent="1" source="3" target="4">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="220" y="150" as="sourcePoint"/>
<mxPoint x="270" y="100" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="22" value="" style="endArrow=classic;html=1;exitX=0.5;exitY=1;entryX=0.5;entryY=0;" edge="1" parent="1" source="4" target="5">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="230" y="160" as="sourcePoint"/>
<mxPoint x="280" y="110" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="23" value="是" style="endArrow=classic;html=1;exitX=0.5;exitY=1;entryX=0.5;entryY=0;" edge="1" parent="1" source="5" target="7">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="240" y="170" as="sourcePoint"/>
<mxPoint x="290" y="120" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="24" value="否" style="endArrow=classic;html=1;exitX=1;exitY=0.5;entryX=0;entryY=0.5;" edge="1" parent="1" source="5" target="6">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="250" y="180" as="sourcePoint"/>
<mxPoint x="300" y="130" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="25" value="" style="endArrow=classic;html=1;exitX=0.5;exitY=1;entryX=0.5;entryY=0;" edge="1" parent="1" source="7" target="8">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="260" y="290" as="sourcePoint"/>
<mxPoint x="310" y="240" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="26" value="" style="endArrow=classic;html=1;exitX=0.5;exitY=1;entryX=0.5;entryY=0;" edge="1" parent="1" source="8" target="9">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="270" y="300" as="sourcePoint"/>
<mxPoint x="320" y="250" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="27" value="" style="endArrow=classic;html=1;exitX=0.5;exitY=1;entryX=0.5;entryY=0;" edge="1" parent="1" source="9" target="10">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="280" y="310" as="sourcePoint"/>
<mxPoint x="330" y="260" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="28" value="" style="endArrow=classic;html=1;exitX=0.5;exitY=1;entryX=0.5;entryY=0;" edge="1" parent="1" source="10" target="11">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="290" y="320" as="sourcePoint"/>
<mxPoint x="340" y="270" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="29" value="" style="endArrow=classic;html=1;exitX=0.5;exitY=1;entryX=0.5;entryY=0;" edge="1" parent="1" source="11" target="12">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="300" y="330" as="sourcePoint"/>
<mxPoint x="350" y="280" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="30" value="" style="endArrow=classic;html=1;exitX=0.5;exitY=1;entryX=0.5;entryY=0;" edge="1" parent="1" source="12" target="13">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="310" y="340" as="sourcePoint"/>
<mxPoint x="360" y="290" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="31" value="" style="endArrow=classic;html=1;exitX=0.5;exitY=1;entryX=0.5;entryY=0;" edge="1" parent="1" source="13" target="14">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="320" y="350" as="sourcePoint"/>
<mxPoint x="370" y="300" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="32" value="是" style="endArrow=classic;html=1;exitX=1;exitY=0.5;entryX=0;entryY=0.5;" edge="1" parent="1" source="14" target="15">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="340" y="370" as="sourcePoint"/>
<mxPoint x="390" y="320" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="33" value="否" style="endArrow=classic;html=1;exitX=0.5;exitY=1;entryX=0.5;entryY=0;" edge="1" parent="1" source="14" target="16">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="350" y="380" as="sourcePoint"/>
<mxPoint x="400" y="330" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="34" value="" style="endArrow=classic;html=1;exitX=0.5;exitY=1;entryX=0.5;entryY=0;" edge="1" parent="1" source="15" target="16">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="360" y="390" as="sourcePoint"/>
<mxPoint x="410" y="340" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="35" value="" style="endArrow=classic;html=1;exitX=0.5;exitY=1;entryX=0.5;entryY=0;" edge="1" parent="1" source="16" target="17">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="370" y="400" as="sourcePoint"/>
<mxPoint x="420" y="350" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="36" value="" style="endArrow=classic;html=1;exitX=0.5;exitY=1;entryX=0.5;entryY=0;" edge="1" parent="1" source="17" target="18">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="380" y="410" as="sourcePoint"/>
<mxPoint x="430" y="360" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="37" value="" style="endArrow=classic;html=1;exitX=0.5;exitY=1;entryX=0.5;entryY=0;" edge="1" parent="1" source="6" target="19">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="500" y="430" as="sourcePoint"/>
<mxPoint x="550" y="380" as="targetPoint"/>
</mxGeometry>
</mxCell>
</root>
</mxGraphModel></diagram>
</mxfile>

14
user_entity.drawio Normal file
View File

@@ -0,0 +1,14 @@
<mxfile host="app.diagrams.net" modified="2026-01-30T13:55:00.000Z" agent="codex" version="24.7.7">
<diagram name="???????"><mxGraphModel dx="1426" dy="769" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" background="#ffffff" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="2" value="??(User)" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=26;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;swimlaneFillColor=#ffffff;rounded=0;shadow=0;labelBackgroundColor=none;strokeColor=#000000;strokeWidth=1;fillColor=none;fontFamily=Verdana;fontSize=12;fontColor=#000000;" vertex="1" parent="1">
<mxGeometry x="200" y="120" width="240" height="260" as="geometry"/>
</mxCell>
<mxCell id="3" value="+ user_id: BIGINT&#xa;+ username: VARCHAR(50)&#xa;+ phone: VARCHAR(20)&#xa;+ email: VARCHAR(100)&#xa;+ password: VARCHAR(255)&#xa;+ role: VARCHAR(20)&#xa;+ status: INT&#xa;+ avatar: VARCHAR(255)&#xa;+ create_time: TIMESTAMP&#xa;+ update_time: TIMESTAMP&#xa;+ deleted: INT" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;" vertex="1" parent="2">
<mxGeometry y="26" width="240" height="234" as="geometry"/>
</mxCell>
</root>
</mxGraphModel></diagram>
</mxfile>

View File

@@ -0,0 +1,89 @@
<mxfile host="app.diagrams.net" modified="2026-01-30T14:00:00.000Z" agent="codex" version="24.7.7">
<diagram name="用户功能结构图"><mxGraphModel dx="1426" dy="769" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" background="#ffffff" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="2" value="用户功能结构" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#333333;" vertex="1" parent="1">
<mxGeometry x="310" y="40" width="180" height="50" as="geometry"/>
</mxCell>
<mxCell id="10" value="账户与资料" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#333333;" vertex="1" parent="1">
<mxGeometry x="100" y="140" width="180" height="40" as="geometry"/>
</mxCell>
<mxCell id="100" value="" style="endArrow=classic;html=1;" edge="1" parent="1" source="2" target="10">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="11" value="注册/登录" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#666666;" vertex="1" parent="1">
<mxGeometry x="360" y="130" width="180" height="36" as="geometry"/>
</mxCell>
<mxCell id="101" value="" style="endArrow=classic;html=1;" edge="1" parent="1" source="10" target="11">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="12" value="个人资料维护" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#666666;" vertex="1" parent="1">
<mxGeometry x="360" y="176" width="180" height="36" as="geometry"/>
</mxCell>
<mxCell id="102" value="" style="endArrow=classic;html=1;" edge="1" parent="1" source="11" target="12">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="13" value="宠物管理" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#333333;" vertex="1" parent="1">
<mxGeometry x="100" y="230" width="180" height="40" as="geometry"/>
</mxCell>
<mxCell id="103" value="" style="endArrow=classic;html=1;" edge="1" parent="1" source="2" target="13">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="14" value="宠物档案维护" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#666666;" vertex="1" parent="1">
<mxGeometry x="360" y="220" width="180" height="36" as="geometry"/>
</mxCell>
<mxCell id="104" value="" style="endArrow=classic;html=1;" edge="1" parent="1" source="13" target="14">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="15" value="预约与就诊" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#333333;" vertex="1" parent="1">
<mxGeometry x="100" y="320" width="180" height="40" as="geometry"/>
</mxCell>
<mxCell id="105" value="" style="endArrow=classic;html=1;" edge="1" parent="1" source="2" target="15">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="16" value="预约挂号" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#666666;" vertex="1" parent="1">
<mxGeometry x="360" y="310" width="180" height="36" as="geometry"/>
</mxCell>
<mxCell id="106" value="" style="endArrow=classic;html=1;" edge="1" parent="1" source="15" target="16">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="17" value="查看预约记录" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#666666;" vertex="1" parent="1">
<mxGeometry x="360" y="356" width="180" height="36" as="geometry"/>
</mxCell>
<mxCell id="107" value="" style="endArrow=classic;html=1;" edge="1" parent="1" source="16" target="17">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="18" value="记录与查询" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#333333;" vertex="1" parent="1">
<mxGeometry x="100" y="410" width="180" height="40" as="geometry"/>
</mxCell>
<mxCell id="108" value="" style="endArrow=classic;html=1;" edge="1" parent="1" source="2" target="18">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="19" value="病历/处方查看" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#666666;" vertex="1" parent="1">
<mxGeometry x="360" y="400" width="180" height="36" as="geometry"/>
</mxCell>
<mxCell id="109" value="" style="endArrow=classic;html=1;" edge="1" parent="1" source="18" target="19">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="20" value="报告查询" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#666666;" vertex="1" parent="1">
<mxGeometry x="360" y="446" width="180" height="36" as="geometry"/>
</mxCell>
<mxCell id="110" value="" style="endArrow=classic;html=1;" edge="1" parent="1" source="19" target="20">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="21" value="费用支付" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#333333;" vertex="1" parent="1">
<mxGeometry x="100" y="500" width="180" height="40" as="geometry"/>
</mxCell>
<mxCell id="111" value="" style="endArrow=classic;html=1;" edge="1" parent="1" source="2" target="21">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="22" value="费用结算与支付" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#666666;" vertex="1" parent="1">
<mxGeometry x="360" y="490" width="180" height="36" as="geometry"/>
</mxCell>
<mxCell id="112" value="" style="endArrow=classic;html=1;" edge="1" parent="1" source="21" target="22">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
</root>
</mxGraphModel></diagram>
</mxfile>

View File

@@ -0,0 +1,180 @@
<mxfile host="app.diagrams.net" modified="2026-01-30T14:02:00.000Z" agent="codex" version="24.7.7">
<diagram name="user sequence diagram"><mxGraphModel dx="1426" dy="769" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" background="#ffffff" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="2" value="顾客" style="shape=umlLifeline;perimeter=lifelinePerimeter;whiteSpace=wrap;html=1;container=1;collapsible=0;recursiveResize=0;outlineConnect=0;" vertex="1" parent="1">
<mxGeometry x="80" y="60" width="80" height="700" as="geometry" />
</mxCell>
<mxCell id="3" value="" style="html=1;points=[];perimeter=orthogonalPerimeter;" vertex="1" parent="2">
<mxGeometry x="35" y="60" width="10" height="640" as="geometry" />
</mxCell>
<mxCell id="4" value="系统" style="shape=umlLifeline;perimeter=lifelinePerimeter;whiteSpace=wrap;html=1;container=1;collapsible=0;recursiveResize=0;outlineConnect=0;" vertex="1" parent="1">
<mxGeometry x="220" y="60" width="80" height="700" as="geometry" />
</mxCell>
<mxCell id="5" value="" style="html=1;points=[];perimeter=orthogonalPerimeter;" vertex="1" parent="4">
<mxGeometry x="35" y="70" width="10" height="630" as="geometry" />
</mxCell>
<mxCell id="6" value="预约服务" style="shape=umlLifeline;perimeter=lifelinePerimeter;whiteSpace=wrap;html=1;container=1;collapsible=0;recursiveResize=0;outlineConnect=0;" vertex="1" parent="1">
<mxGeometry x="360" y="60" width="80" height="700" as="geometry" />
</mxCell>
<mxCell id="7" value="" style="html=1;points=[];perimeter=orthogonalPerimeter;" vertex="1" parent="6">
<mxGeometry x="35" y="90" width="10" height="610" as="geometry" />
</mxCell>
<mxCell id="8" value="数据库" style="shape=umlLifeline;perimeter=lifelinePerimeter;whiteSpace=wrap;html=1;container=1;collapsible=0;recursiveResize=0;outlineConnect=0;" vertex="1" parent="1">
<mxGeometry x="500" y="60" width="80" height="700" as="geometry" />
</mxCell>
<mxCell id="9" value="" style="html=1;points=[];perimeter=orthogonalPerimeter;" vertex="1" parent="8">
<mxGeometry x="35" y="110" width="10" height="590" as="geometry" />
</mxCell>
<mxCell id="10" value="" style="html=1;points=[];perimeter=orthogonalPerimeter;" vertex="1" parent="1">
<mxGeometry x="125" y="140" width="10" height="30" as="geometry" />
</mxCell>
<mxCell id="11" value="登录系统" style="html=1;verticalAlign=bottom;endArrow=block;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;exitX=1;exitY=0.5;entryX=0;entryY=0.5;" edge="1" parent="1" source="3" target="10">
<mxGeometry relative="1" as="geometry">
<mxPoint x="140" y="155" as="sourcePoint" />
<mxPoint x="225" y="155" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="12" value="验证用户身份" style="html=1;verticalAlign=bottom;endArrow=block;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;exitX=1;exitY=0.5;entryX=0;entryY=0.5;" edge="1" parent="1" source="5" target="7">
<mxGeometry relative="1" as="geometry">
<mxPoint x="260" y="180" as="sourcePoint" />
<mxPoint x="365" y="180" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="13" value="返回验证结果" style="html=1;verticalAlign=bottom;endArrow=block;endSize=8;startArrow=classic;startSize=8;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;exitX=0;exitY=0.5;entryX=1;entryY=0.5;" edge="1" parent="1" source="7" target="5">
<mxGeometry x="0.16" y="-20" relative="1" as="geometry">
<mxPoint x="260" y="210" as="targetPoint" />
<mxPoint x="320" y="210" as="sourcePoint" />
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="14" value="登录成功" style="html=1;verticalAlign=bottom;endArrow=block;endSize=8;startArrow=classic;startSize=8;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;exitX=1;exitY=0.5;entryX=0;entryY=0.5;" edge="1" parent="1" source="10" target="3">
<mxGeometry x="0.08" y="-20" relative="1" as="geometry">
<mxPoint x="140" y="240" as="targetPoint" />
<mxPoint x="200" y="240" as="sourcePoint" />
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="15" value="请求可预约时间段" style="html=1;verticalAlign=bottom;endArrow=block;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;exitX=1;exitY=0.5;entryX=0;entryY=0.5;" edge="1" parent="1" source="3" target="5">
<mxGeometry relative="1" as="geometry">
<mxPoint x="140" y="270" as="sourcePoint" />
<mxPoint x="350" y="270" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="16" value="查询可用时间段" style="html=1;verticalAlign=bottom;endArrow=block;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;exitX=1;exitY=0.5;entryX=0;entryY=0.5;" edge="1" parent="1" source="5" target="7">
<mxGeometry relative="1" as="geometry">
<mxPoint x="260" y="300" as="sourcePoint" />
<mxPoint x="365" y="300" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="17" value="查询预约数据" style="html=1;verticalAlign=bottom;endArrow=block;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;exitX=1;exitY=0.5;entryX=0;entryY=0.5;" edge="1" parent="1" source="7" target="9">
<mxGeometry relative="1" as="geometry">
<mxPoint x="370" y="330" as="sourcePoint" />
<mxPoint x="450" y="330" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="18" value="返回可用时间段" style="html=1;verticalAlign=bottom;endArrow=block;endSize=8;startArrow=classic;startSize=8;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;exitX=0;exitY=0.5;entryX=1;entryY=0.5;" edge="1" parent="1" source="9" target="7">
<mxGeometry x="0.1" y="-20" relative="1" as="geometry">
<mxPoint x="370" y="360" as="targetPoint" />
<mxPoint x="430" y="360" as="sourcePoint" />
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="19" value="返回可用时间段" style="html=1;verticalAlign=bottom;endArrow=block;endSize=8;startArrow=classic;startSize=8;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;exitX=0;exitY=0.5;entryX=1;entryY=0.5;" edge="1" parent="1" source="7" target="5">
<mxGeometry x="0.1" y="-20" relative="1" as="geometry">
<mxPoint x="260" y="390" as="targetPoint" />
<mxPoint x="320" y="390" as="sourcePoint" />
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="20" value="显示可预约时间段" style="html=1;verticalAlign=bottom;endArrow=block;endSize=8;startArrow=classic;startSize=8;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;exitX=0;exitY=0.5;entryX=1;entryY=0.5;" edge="1" parent="1" source="5" target="3">
<mxGeometry x="0.08" y="-20" relative="1" as="geometry">
<mxPoint x="140" y="420" as="targetPoint" />
<mxPoint x="200" y="420" as="sourcePoint" />
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="21" value="选择医生、时间段和宠物" style="html=1;verticalAlign=bottom;endArrow=block;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;exitX=1;exitY=0.5;entryX=0;entryY=0.5;" edge="1" parent="1" source="3" target="5">
<mxGeometry relative="1" as="geometry">
<mxPoint x="140" y="450" as="sourcePoint" />
<mxPoint x="350" y="450" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="22" value="验证时段可用性" style="html=1;verticalAlign=bottom;endArrow=block;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;exitX=1;exitY=0.5;entryX=0;entryY=0.5;" edge="1" parent="1" source="5" target="7">
<mxGeometry relative="1" as="geometry">
<mxPoint x="260" y="480" as="sourcePoint" />
<mxPoint x="365" y="480" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="23" value="检查时段是否已被预约" style="html=1;verticalAlign=bottom;endArrow=block;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;exitX=1;exitY=0.5;entryX=0;entryY=0.5;" edge="1" parent="1" source="7" target="9">
<mxGeometry relative="1" as="geometry">
<mxPoint x="370" y="510" as="sourcePoint" />
<mxPoint x="450" y="510" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="24" value="返回验证结果" style="html=1;verticalAlign=bottom;endArrow=block;endSize=8;startArrow=classic;startSize=8;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;exitX=0;exitY=0.5;entryX=1;entryY=0.5;" edge="1" parent="1" source="9" target="7">
<mxGeometry x="0.1" y="-20" relative="1" as="geometry">
<mxPoint x="370" y="540" as="targetPoint" />
<mxPoint x="430" y="540" as="sourcePoint" />
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="25" value="返回验证结果" style="html=1;verticalAlign=bottom;endArrow=block;endSize=8;startArrow=classic;startSize=8;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;exitX=0;exitY=0.5;entryX=1;entryY=0.5;" edge="1" parent="1" source="7" target="5">
<mxGeometry x="0.1" y="-20" relative="1" as="geometry">
<mxPoint x="260" y="570" as="targetPoint" />
<mxPoint x="320" y="570" as="sourcePoint" />
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="26" value="验证通过" style="html=1;verticalAlign=bottom;endArrow=block;endSize=8;startArrow=classic;startSize=8;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;exitX=0;exitY=0.5;entryX=1;entryY=0.5;" edge="1" parent="1" source="5" target="3">
<mxGeometry x="0.08" y="-20" relative="1" as="geometry">
<mxPoint x="140" y="600" as="targetPoint" />
<mxPoint x="200" y="600" as="sourcePoint" />
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="27" value="确认预约信息" style="html=1;verticalAlign=bottom;endArrow=block;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;exitX=1;exitY=0.5;entryX=0;entryY=0.5;" edge="1" parent="1" source="3" target="5">
<mxGeometry relative="1" as="geometry">
<mxPoint x="140" y="630" as="sourcePoint" />
<mxPoint x="350" y="630" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="28" value="创建预约记录" style="html=1;verticalAlign=bottom;endArrow=block;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;exitX=1;exitY=0.5;entryX=0;entryY=0.5;" edge="1" parent="1" source="5" target="7">
<mxGeometry relative="1" as="geometry">
<mxPoint x="260" y="660" as="sourcePoint" />
<mxPoint x="365" y="660" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="29" value="保存预约信息" style="html=1;verticalAlign=bottom;endArrow=block;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;exitX=1;exitY=0.5;entryX=0;entryY=0.5;" edge="1" parent="1" source="7" target="9">
<mxGeometry relative="1" as="geometry">
<mxPoint x="370" y="690" as="sourcePoint" />
<mxPoint x="450" y="690" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="30" value="保存成功" style="html=1;verticalAlign=bottom;endArrow=block;endSize=8;startArrow=classic;startSize=8;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;exitX=0;exitY=0.5;entryX=1;entryY=0.5;" edge="1" parent="1" source="9" target="7">
<mxGeometry x="0.1" y="-20" relative="1" as="geometry">
<mxPoint x="370" y="720" as="targetPoint" />
<mxPoint x="430" y="720" as="sourcePoint" />
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="31" value="预约创建成功" style="html=1;verticalAlign=bottom;endArrow=block;endSize=8;startArrow=classic;startSize=8;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;exitX=0;exitY=0.5;entryX=1;entryY=0.5;" edge="1" parent="1" source="7" target="5">
<mxGeometry x="0.1" y="-20" relative="1" as="geometry">
<mxPoint x="260" y="750" as="targetPoint" />
<mxPoint x="320" y="750" as="sourcePoint" />
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="32" value="预约确认信息" style="html=1;verticalAlign=bottom;endArrow=block;endSize=8;startArrow=classic;startSize=8;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;exitX=0;exitY=0.5;entryX=1;entryY=0.5;" edge="1" parent="1" source="5" target="3">
<mxGeometry x="0.08" y="-20" relative="1" as="geometry">
<mxPoint x="140" y="780" as="targetPoint" />
<mxPoint x="200" y="780" as="sourcePoint" />
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
</root>
</mxGraphModel></diagram>
</mxfile>

62
user_use_case.drawio Normal file
View File

@@ -0,0 +1,62 @@
<mxfile host="app.diagrams.net" modified="2026-01-30T14:00:00.000Z" agent="codex" version="24.7.7">
<diagram name="用户用例图"><mxGraphModel dx="1426" dy="769" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" background="#ffffff" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="2" value="爱维宠物医院管理系统" style="html=1;fontStyle=1;verticalAlign=top;align=left;spacingTop=8;spacingLeft=6;spacingRight=12;shape=cube;size=10;direction=south;fontSize=14;" vertex="1" parent="1">
<mxGeometry x="20" y="20" width="760" height="520" as="geometry"/>
</mxCell>
<mxCell id="3" value="用户" style="shape=umlActor;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;html=1;outlineConnect=0;" vertex="1" parent="1">
<mxGeometry x="80" y="220" width="30" height="60" as="geometry"/>
</mxCell>
<mxCell id="10" value="注册/登录" style="ellipse;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="220" y="80" width="120" height="50" as="geometry"/>
</mxCell>
<mxCell id="11" value="个人资料管理" style="ellipse;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="220" y="150" width="120" height="50" as="geometry"/>
</mxCell>
<mxCell id="12" value="宠物档案管理" style="ellipse;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="220" y="220" width="120" height="50" as="geometry"/>
</mxCell>
<mxCell id="13" value="预约挂号" style="ellipse;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="220" y="290" width="120" height="50" as="geometry"/>
</mxCell>
<mxCell id="14" value="查看预约记录" style="ellipse;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="420" y="80" width="120" height="50" as="geometry"/>
</mxCell>
<mxCell id="15" value="查看病历与处方" style="ellipse;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="420" y="150" width="120" height="50" as="geometry"/>
</mxCell>
<mxCell id="16" value="查询报告" style="ellipse;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="420" y="220" width="120" height="50" as="geometry"/>
</mxCell>
<mxCell id="17" value="费用支付" style="ellipse;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="420" y="290" width="120" height="50" as="geometry"/>
</mxCell>
<mxCell id="100" value="" style="endArrow=none;html=1;entryX=0;entryY=0.5;exitX=1;exitY=0.5;exitPerimeter=0;" edge="1" parent="1" source="3" target="10">
<mxGeometry width="50" height="50" relative="1" as="geometry"/>
</mxCell>
<mxCell id="101" value="" style="endArrow=none;html=1;entryX=0;entryY=0.5;exitX=1;exitY=0.5;exitPerimeter=0;" edge="1" parent="1" source="3" target="11">
<mxGeometry width="50" height="50" relative="1" as="geometry"/>
</mxCell>
<mxCell id="102" value="" style="endArrow=none;html=1;entryX=0;entryY=0.5;exitX=1;exitY=0.5;exitPerimeter=0;" edge="1" parent="1" source="3" target="12">
<mxGeometry width="50" height="50" relative="1" as="geometry"/>
</mxCell>
<mxCell id="103" value="" style="endArrow=none;html=1;entryX=0;entryY=0.5;exitX=1;exitY=0.5;exitPerimeter=0;" edge="1" parent="1" source="3" target="13">
<mxGeometry width="50" height="50" relative="1" as="geometry"/>
</mxCell>
<mxCell id="104" value="" style="endArrow=none;html=1;entryX=0;entryY=0.5;exitX=1;exitY=0.5;exitPerimeter=0;" edge="1" parent="1" source="3" target="14">
<mxGeometry width="50" height="50" relative="1" as="geometry"/>
</mxCell>
<mxCell id="105" value="" style="endArrow=none;html=1;entryX=0;entryY=0.5;exitX=1;exitY=0.5;exitPerimeter=0;" edge="1" parent="1" source="3" target="15">
<mxGeometry width="50" height="50" relative="1" as="geometry"/>
</mxCell>
<mxCell id="106" value="" style="endArrow=none;html=1;entryX=0;entryY=0.5;exitX=1;exitY=0.5;exitPerimeter=0;" edge="1" parent="1" source="3" target="16">
<mxGeometry width="50" height="50" relative="1" as="geometry"/>
</mxCell>
<mxCell id="107" value="" style="endArrow=none;html=1;entryX=0;entryY=0.5;exitX=1;exitY=0.5;exitPerimeter=0;" edge="1" parent="1" source="3" target="17">
<mxGeometry width="50" height="50" relative="1" as="geometry"/>
</mxCell>
</root>
</mxGraphModel></diagram>
</mxfile>

File diff suppressed because it is too large Load Diff