Browse Source

liupeng code commit

liopeng 2 months ago
parent
commit
cfc37a25ec
62 changed files with 4968 additions and 1630 deletions
  1. 0 320
      README.md
  2. 80 0
      chexnet-master-MP/App.vue
  3. 31 0
      chexnet-master-MP/main.js
  4. 17 0
      chexnet-master-MP/package-lock.json
  5. 5 0
      chexnet-master-MP/package.json
  6. 179 0
      chexnet-master-MP/pages.json
  7. 642 0
      chexnet-master-MP/pages/ai/ai.vue
  8. 120 120
      chexnet-master-MP/pages/health_tip/health_tip.vue
  9. 94 94
      chexnet-master-MP/pages/health_tip/health_tip_collect/health_tip_collect.vue
  10. 386 367
      chexnet-master-MP/pages/img_process/img_process.vue
  11. 181 181
      chexnet-master-MP/pages/img_process/img_process_history/img_process_history.vue
  12. 323 0
      chexnet-master-MP/pages/index/index.vue
  13. 225 0
      chexnet-master-MP/pages/login/login.vue
  14. 110 0
      chexnet-master-MP/pages/my/family/family.vue
  15. 214 0
      chexnet-master-MP/pages/my/family/family_info/family_info.vue
  16. 207 0
      chexnet-master-MP/pages/my/my.vue
  17. 81 0
      chexnet-master-MP/pages/my/setting/setting.vue
  18. 209 0
      chexnet-master-MP/pages/my/user_info/user_info.vue
  19. 10 0
      chexnet-master-MP/uni.promisify.adaptor.js
  20. 76 0
      chexnet-master-MP/uni.scss
  21. 2 0
      chexnet-master-Server/.gitattributes
  22. 33 0
      chexnet-master-Server/.gitignore
  23. 1 0
      chexnet-master-Server/chexnet-master-img-api.ini
  24. 149 0
      chexnet-master-Server/mvnw.cmd
  25. 105 0
      chexnet-master-Server/pom.xml
  26. 15 0
      chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/ChexnetMasterApplication.java
  27. 121 0
      chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/component/MemoryUserRecordSpace.java
  28. 45 0
      chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/component/ScheduleTask.java
  29. 143 0
      chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/component/XfXhStreamClient.java
  30. 19 0
      chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/config/WebConfig.java
  31. 22 0
      chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/config/XfXhConfig.java
  32. 123 0
      chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/controller/AiController.java
  33. 42 0
      chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/controller/ContentCheck.java
  34. 43 0
      chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/controller/ImgProcessController.java
  35. 73 0
      chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/controller/UserController.java
  36. 7 0
      chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/dao/PyDao.java
  37. 73 0
      chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/dao/impl/PyDaoA.java
  38. 13 0
      chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/dto/InteractMsg.java
  39. 43 0
      chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/dto/MsgDTO.java
  40. 122 0
      chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/dto/RecordsArray.java
  41. 81 0
      chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/dto/RequestDTO.java
  42. 107 0
      chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/dto/ResponseDTO.java
  43. 57 0
      chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/listener/XfXhWebSocketListener.java
  44. 30 0
      chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/pojo/Result.java
  45. 9 0
      chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/service/ContentCheckService.java
  46. 8 0
      chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/service/ProcessImgService.java
  47. 7 0
      chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/service/PyService.java
  48. 17 0
      chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/service/UserService.java
  49. 67 0
      chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/service/impl/ContentCheckServiceA.java
  50. 50 0
      chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/service/impl/ProcessImgServiceB.java
  51. 28 0
      chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/service/impl/PyServiceA.java
  52. 123 0
      chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/service/impl/UserServiceA.java
  53. 0 0
      chexnet-master-Server/src/main/resources/application.yml
  54. 0 39
      test.md
  55. 0 330
      test/README.md
  56. 0 0
      智能胸康项目策划.md
  57. 0 82
      项目/tab2.page.html
  58. 0 17
      项目/tab2.page.ts
  59. 0 33
      项目/tab3.page.html
  60. 0 47
      项目/tab3.page.ts
  61. 0 0
      项目压缩包/前端代码.zip
  62. 0 0
      项目压缩包/后端代码.zip

+ 0 - 320
README.md

@@ -1,320 +0,0 @@
-# XXX项目策划书
-- 姓名 刘同学
-- 学号 20240001
-- 班级 软工221班
-- 手机 18600001111
-
-
-# 项目设想
-01*
-一句话描述项目设想(行业领域、用户画像、核心功能)
-(行业)社交活动规划领域,满足人们对高质量社交的需求。(用户)社交活跃者、上班族、学生等缺乏时间或创意规划社交活动的人群。(功能)利用AGI智能规划聚会、旅游等社交活动,包括推荐地点、安排行程和预订场地。
-
-02
-项目的业务流程
-1.用户信息收集:用户输入个人兴趣、社交圈子信息、空闲时间、预算等,系统也可通过分析用户过往社交数据来获取相关信息。
-2.需求明确:用户提出社交活动类型需求,如聚会、旅游、户外运动等,以及特殊要求,如主题、参与人数范围等。
-3.数据整合与分析:系统整合旅游景点、娱乐场所、餐厅等相关信息,结合用户输入和需求,利用算法分析最佳匹配方案。
-4.规划生成:根据分析结果生成社交活动规划,包括活动时间、地点推荐、行程安排、交通建议和预算分配。
-5.方案展示与调整:向用户展示规划方案,用户可提出修改意见,系统根据反馈快速调整。
-6.预订与提醒:用户确认后,系统自动预订相关服务,并在活动前适时提醒用户各项准备事宜。
-7.活动后反馈收集:活动结束后,收集用户反馈,用于改进后续规划。
-
-03
-项目的商业模式
-付费订阅模式:用户支付一定的费用,如每月或每年订阅,获取不同等级的社交活动规划服务,等级越高,规划的活动越复杂精细。
-按次收费模式:针对只偶尔需要规划社交活动的用户,每次使用服务时支付相应费用。
-商家合作分成:与旅游景点、酒店、餐厅、娱乐场所等商家合作,推荐用户消费,从用户消费金额中获取一定比例的分成。
-广告收入模式:在向用户展示规划方案时,适当展示与社交活动相关的优质产品或服务广告,收取广告商费用。
-增值服务收费:例如为用户提供活动现场的特殊布置、摄影摄像、礼品采购等增值服务,并收取额外费用。
-
-04
-相对可行的策略
-初期策略:
-聚焦特定用户群体:先针对上班族或大学生等有较强社交需求且消费能力不错的群体,开发出符合他们兴趣和预算的社交活动规划模板。
-提供免费试用与口碑营销:为新用户提供有限次数的免费试用,鼓励他们分享体验,通过口碑吸引更多潜在用户。
-与本地商家合作推广:和当地热门的餐厅、KTV、轰趴馆等商家联合举办小型活动,在活动中展示规划方案的优势。
-中期策略:
-优化用户体验:根据用户反馈不断改进规划算法,提高活动规划的精准度和个性化程度。
-拓展服务内容:增加如特殊主题派对、亲子社交活动等新的活动类型,满足更多用户需求。
-推出会员制度:设置不同等级会员,提供如优先规划、更多修改次数、专属客服等特权,激励用户升级会员。
-长期策略:
-跨地区拓展:在本地模式成熟后,将业务拓展到其他城市,与当地商家建立合作,复制成功经验。
-数据驱动发展:深入分析用户数据,挖掘用户潜在需求,开发新的功能和服务,如根据用户社交圈子变化自动推荐新的活动类型。
-打造社交平台:不仅仅是规划活动,还构建用户交流社交活动心得、分享照片等功能的平台,增强用户粘性。
-05
-用AI为项目起个好名字
-友聚蓝图
-
-
-
-# 一、项目背景
-
-## 政策背景
-> 用关键字:兴趣、社交,搜索相关的政策和扶持
-
-- 2022年1月12日,《国务院关于印发“十四五”数字经济发展规划的通知》发布,指出数字化服务是满足人民美好生活需要的重要途径。
-- 2022年12月14日,中共中央、国务院印发《扩大内需战略规划纲要(2022-2035年)》,提出扩大文化和旅游消费。完善现代文化产业体系和文化市场体系,推进优质文化资源开发,推动中华优秀传统文化创造性转化、创新性发展。鼓励文化文物单位依托馆藏文化资源,开发各类文化创意产品,扩大优质文化产品和服务供给。加快培育新型消费。支持线上线下商品消费融合发展。加快传统线下业态数字化改造和转型升级。深入发展在线文娱,鼓励传统线下文化娱乐业态线上化,支持打造数字精品内容和新兴数字资源传播平台。支持线上多样化社交、短视频平台规范有序发展,鼓励微应用、微产品、微电影等创新。
-
-## 行业背景
-
-- 《2024-2030年中国社交网络行业发展前景预测及投资策略研究报告》
-![](/img/1.png)
-![](/img/2.png)
-
-
-
-
-## 市场痛点
-- 社交环节,往往由于不敢开口,导致错过了社交的机会
-- 社交成本高,往往交了很多朋友,最后才发现性格不符
-- 活动组织,活动千篇一律,难以找不到每位参与者的喜好
-
-
-# 二、产品定位
-
-## 用户分析
-- 用户(陌生人社交+共情社交)
-    - 上班族
-    - 学生
-- 商家
-    - 餐饮
-    - 轰趴
-    - KTV
-
-## 主要功能
-- 破冰助手:开启话题,话题灵感助手
-- 友情测试:智能体代理聊天,匹配完成获得友情积分
-- 活动策划:参与成员画像、目标地点特点 => 活动策划方案
-
-
-## 商业模式
-![](/img/3.png)
-
-# 三、可行性测试
-## 破冰助手的提示词相关
-- 开场破冰的打招呼语言
-    > 您作为一名专业的社交心理情感大师,我的性格是较为内向的,我想要认识的朋友是性格外向活泼开朗的。需要您帮我想想应该用怎样的话题开始对话?
-- 根据聊天聊天内容,推荐下一步回答
-
-    ```
-    我:(参加活动迟到了,让其他人等了很久)
-    朋友A:(不耐烦),你去干什么了,怎么现在才来?
-    朋友B:我们已经等了好久了,接下来你来买单。
-    ```
-    我在一次活动中迟到了,上面的历史聊天记录,请您帮我分析下如何沟通能缓解尴尬。
-
-## 友情测试
-- 性格测评
-- 两个智能体互相聊天,到双方认可友情关系的逻辑
-    - 两个智能体会互相出三个问题,根据对方回答的结果,给予评分,最终确认友情值。
-
-## 活动策划
-- 数据搜集
-    - 所有参与成员的画像列表
-    - 所有参与成员周边商家的服务清单
-- 活动的策划
-    - 筛选风险:性格冲突、餐饮忌口、风俗习惯
-    - 活动地点选择:
-        - 给出推荐地点,有所有成员投票选择
-    - 活动流程的策划
-        - 破冰小游戏
-        - 活动参与环节
-        - 活动的奖励机制
-
-> 业务逻辑时序图
-```puml
-@startuml
-actor 参与成员
-participant 数据搜集
-participant 活动策划
-
-参与成员 -> 数据搜集 : 提供画像列表
-参与成员 -> 数据搜集 : 提供周边商家的服务清单
-数据搜集 -> 活动策划 : 提交数据
-
-活动策划 -> 活动策划 : 筛选风险
-活动策划 -> 活动策划 : 性格冲突
-活动策划 -> 活动策划 : 餐饮忌口
-活动策划 -> 活动策划 : 风俗习惯
-
-活动策划 -> 活动策划 : 活动地点选择
-活动策划 -> 参与成员 : 推荐地点
-参与成员 -> 活动策划 : 投票选择地点
-
-活动策划 -> 活动策划 : 活动流程的策划
-活动策划 -> 活动策划 : 破冰小游戏
-活动策划 -> 活动策划 : 活动参与环节
-活动策划 -> 活动策划 : 活动的奖励机制
-
-@enduml
-```
-> 参与成员
-```
-
-1. **用户A**
-   - **性格**: 外向、乐观
-   - **饮食习惯**: 素食主义者
-   - **风俗习惯**: 喜欢参与传统节日庆祝活动
-   - **兴趣爱好**: 旅行、摄影、社交活动
-
-2. **用户B**
-   - **性格**: 内向、谨慎
-   - **饮食习惯**: 不吃海鲜,偏好家常菜
-   - **风俗习惯**: 重视家庭聚会,喜欢分享家族故事
-   - **兴趣爱好**: 阅读、电影、绘画
-
-3. **用户C**
-   - **性格**: 开朗、幽默
-   - **饮食习惯**: 喜欢尝试新事物,能吃辣
-   - **风俗习惯**: 喜欢参与社区活动,热衷公益
-   - **兴趣爱好**: 音乐、运动、派对
-
-4. **用户D**
-   - **性格**: 冷静、分析型
-   - **饮食习惯**: 偏好低糖低脂饮食
-   - **风俗习惯**: 重视个人空间,不喜欢过于喧闹的场合
-   - **兴趣爱好**: 科技、科学、棋类游戏
-
-5. **用户E**
-   - **性格**: 热情、积极
-   - **饮食习惯**: 不忌口,喜欢各种美食
-   - **风俗习惯**: 喜欢参与舞蹈和音乐节
-   - **兴趣爱好**: 舞蹈、社交、烹饪
-
-6. **用户F**
-   - **性格**: 内向、细腻
-   - **饮食习惯**: 不吃牛肉,偏好清淡饮食
-   - **风俗习惯**: 喜欢安静的聚会,重视礼仪
-   - **兴趣爱好**: 写作、手工艺、瑜伽
-
-7. **用户G**
-   - **性格**: 冒险、冲动
-   - **饮食习惯**: 喜欢尝试各种异国料理
-   - **风俗习惯**: 热衷于极限运动和冒险活动
-   - **兴趣爱好**: 登山、滑雪、旅行
-
-8. **用户H**
-   - **性格**: 理性、务实
-   - **饮食习惯**: 偏好健康饮食,注重营养搭配
-   - **风俗习惯**: 喜欢参加专业研讨会,重视知识分享
-   - **兴趣爱好**: 学习、研究、健身
-
-```
-> 活动地点信息
-
-```
-
-1. **商家/地点A:素食餐厅**
-   - **适配用户**: 用户A(素食主义者)、用户B(偏好家常菜)
-   - **不适配用户**: 用户C(喜欢尝试新事物)、用户G(喜欢异国料理)
-
-2. **商家/地点B:高档酒吧**
-   - **适配用户**: 用户C(热衷社交活动)、用户E(热情积极)
-   - **不适配用户**: 用户D(重视个人空间)、用户F(喜欢安静的聚会)
-
-3. **商家/地点C:传统文化体验中心**
-   - **适配用户**: 用户A(喜欢传统节日)、用户B(重视家庭聚会)
-   - **不适配用户**: 用户G(热衷极限运动)、用户H(偏好专业研讨会)
-
-4. **商家/地点D:极限运动俱乐部**
-   - **适配用户**: 用户G(冒险、冲动)、用户C(喜欢运动)
-   - **不适配用户**: 用户D(冷静、分析型)、用户F(内向、细腻)
-
-5. **商家/地点E:艺术工作坊**
-   - **适配用户**: 用户B(喜欢绘画)、用户F(手工艺)
-   - **不适配用户**: 用户D(务实、理性)、用户H(重视知识分享)
-
-6. **商家/地点F:健身中心**
-   - **适配用户**: 用户H(健身爱好者)、用户E(热情积极)
-   - **不适配用户**: 用户B(内向、谨慎)、用户F(喜欢安静的聚会)
-
-7. **商家/地点G:社区公益活动**
-   - **适配用户**: 用户C(热衷公益)、用户A(参与传统活动)
-   - **不适配用户**: 用户D(重视个人空间)、用户F(内向、细腻)
-
-8. **商家/地点H:高科技体验馆**
-   - **适配用户**: 用户H(科技爱好者)、用户D(分析型)
-   - **不适配用户**: 用户B(偏好传统活动)、用户F(内向、细腻)
-
-```
-
-> 活动策划生成
-上面是已经搜集的数据,有所有参与成员的画像列表,还有所有参与成员周边商家的服务清单。
-请您作为一名专业的活动策划大师,根据用户和商家
-1.筛选风险:性格冲突、餐饮忌口、风俗习惯
-2.活动地点选择
-3.活动流程的策划
-4.破冰小游戏
-5.活动参与环节
-6.活动的奖励设计
-
-## 完整的活动策划方案
-根据提供的参与成员画像和活动地点信息,以下是活动策划的详细方案:
-
-### 1. 筛选风险
-在选择活动地点时,需要考虑以下风险:
-
-- **性格冲突**:
-  - 用户A(外向、乐观)与用户D(冷静、分析型)在社交活动中可能会有冲突。
-  - 用户B(内向、谨慎)与用户C(开朗、幽默)在互动中可能存在不适应。
-
-- **餐饮忌口**:
-  - 用户B不吃海鲜,用户F不吃牛肉,用户A是素食主义者。
-  - 选择餐饮时需确保适合所有人的饮食习惯。
-
-- **风俗习惯**:
-  - 用户D重视个人空间,不适合过于喧闹的场合。
-  - 用户H偏好专业研讨会,不适合过于轻松的社交活动。
-
-### 2. 活动地点选择
-综合考虑风险,推荐以下活动地点:
-
-- **商家/地点C:传统文化体验中心**
-  - 适合用户A(喜欢传统节日)、用户B(重视家庭聚会)。
-  - 适合性格较为温和的用户,能够提供较为安静的环境,避免冲突。
-
-### 3. 活动流程的策划
-- **活动时间**:周末下午2点至5点
-- **活动流程**:
-  1. **开场介绍(15分钟)**:主持人介绍活动背景和参与成员。
-  2. **破冰小游戏(30分钟)**:通过轻松的互动游戏让成员相互熟悉。
-  3. **传统文化体验(1小时)**:参与传统手工艺制作或文化讲座。
-  4. **茶歇交流(30分钟)**:提供素食茶点,成员自由交流。
-  5. **总结分享(15分钟)**:每位成员分享活动感受和收获。
-
-### 4. 破冰小游戏
-- **“真心话大冒险”**:每位成员轮流选择“真心话”或“大冒险”,通过轻松的问题或小挑战让大家熟悉彼此。
-- **“人际网络”**:每位成员用线将自己与其他人连接,分享与其他成员的共同点,增进了解。
-
-### 5. 活动参与环节
-- **传统文化体验**:安排手工艺品制作(如编织、书法等),让参与者在互动中增进了解。
-- **分享环节**:每位成员可以分享自己与传统文化的故事或经历,促进交流。
-
-### 6. 活动的奖励设计
-- **参与奖**:每位参与者都能获得小礼品(如手工艺品)。
-- **最佳分享奖**:对分享内容丰富、引发共鸣的成员给予小奖品(如文化体验券)。
-- **幸运抽奖**:设置抽奖环节,奖品可以是参与商家的优惠券或体验券,鼓励大家继续参与相关活动。
-
-### 总结
-通过以上策划,活动能够在考虑参与者的性格、饮食习惯和风俗习惯的基础上,提供一个愉快且有意义的交流平台,促进成员之间的互动和了解。
-
-# 四、产品结构
-
-## 竞品分析
-1. 寻找兴趣社交的三款App,体验其中核心功能
-    - Soul(参考兴趣交友的用户画像和资料的信息结构)
-3. 寻找活动预约和报名App至少1款,体验其中核心功能
-    - 活动行(参考活动报名的UI界面流程)
-
-## 产品结构图
-
-
-## 信息结构图
-
-
-
-
-# FAQ:项目起名
-> [color=red]项目名称的生成:
-> {{项目策划文档}},作为一名创意大师,请帮我设计本项目的名称,并且要突出XXXXXX。

+ 80 - 0
chexnet-master-MP/App.vue

@@ -0,0 +1,80 @@
+<script>
+	export default {
+		onLaunch: function() {
+			console.log('App Launch');
+		},
+		onShow: function() {
+			console.log('App Show')
+		},
+		onHide: function() {
+			console.log('App Hide')
+		}
+	}
+</script>
+
+<style>
+	/*每个页面公共css */
+	@import "style/main.css";
+	@import "style/icon.css";
+	@import "style/animation.css";
+	@import "style/base.css";
+	
+	page{
+		background-color: #ffffff;
+		font-family: "Roboto";
+		height: 100vh;
+	}
+	.top-img{
+		top: -0vw;
+		left: 0;
+		right: 0;
+		width: 100vw;
+		margin-bottom:calc(-15vw - 10px);
+	}
+
+	.button-all {
+		/* color: #5bae23; */
+		border: none;
+		border-radius: 15px;
+		/*backdrop-filter: blur(3px); /* 高斯模糊效果 */
+		/*background-color: rgba(255, 255, 255, 0.4); /* 半透明背景 */
+		/*box-shadow: 0 10px 20px rgba(0, 0, 0, 0.6); /* 添加阴影效果 */
+	}
+	.button-all:active {
+		opacity: 0.8;
+	    filter: brightness(90%);
+		transform: translateY(2px) translateX(2px);
+	}
+	.button-all::after{
+		display: none;
+	}
+	
+	.no_data{
+		width: 100%; /* 设置宽度 */
+		height: 80vh; /* 设置高度 */
+		background-image: url('@/static/img/no-data.png');
+		background-size: 75vw 75vw;
+		background-position: center 10vh;
+		background-repeat: no-repeat; /* 不重复背景图片 */
+	}
+	.divider{
+		width: 100%;
+		height: 1px;
+		background-color: #ccc;
+	}
+	
+	.bottom-button{
+		height: 50px;
+		color: #fff;
+		margin: 0 10px;
+		width: calc(100vw - 60px);
+		position: fixed; 
+		bottom: 30px;
+		border-radius: 50px;
+		background-color: #1678ff;
+	}
+	.bottom-button:active{
+		opacity: 0.7;
+		filter: brightness(80%);
+	}
+</style>

+ 31 - 0
chexnet-master-MP/main.js

@@ -0,0 +1,31 @@
+import App from './App'
+
+// #ifndef VUE3
+import Vue from 'vue'
+import './uni.promisify.adaptor'
+Vue.config.productionTip = false
+App.mpType = 'app'
+const app = new Vue({
+  ...App
+})
+app.$mount()
+// #endif
+
+// #ifdef VUE3
+import { createSSRApp } from 'vue'
+export function createApp() {
+  const app = createSSRApp(App)
+  return {
+    app
+  }
+}
+// #endif
+
+import user_api from '@/data/user_api.js';  
+Vue.prototype.$user_api = user_api;
+
+import util from '@/data/util.js';  
+Vue.prototype.$util = util;
+
+import content_check from '@/data/content_check.js'
+Vue.prototype.$content_check = content_check;

+ 17 - 0
chexnet-master-MP/package-lock.json

@@ -0,0 +1,17 @@
+{
+  "name": "test",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "dependencies": {
+        "uniapp-image-compress": "^1.0.5"
+      }
+    },
+    "node_modules/uniapp-image-compress": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/uniapp-image-compress/-/uniapp-image-compress-1.0.5.tgz",
+      "integrity": "sha512-57KuhmVQf1SNvV45gUZ9avTg0QjOzgw6XMjNe7o8Jazc9bUcftPWVpXHlrsfx+G2Rzs2zVOjxu2eW1svoj97/g=="
+    }
+  }
+}

+ 5 - 0
chexnet-master-MP/package.json

@@ -0,0 +1,5 @@
+{
+  "dependencies": {
+    "uniapp-image-compress": "^1.0.5"
+  }
+}

+ 179 - 0
chexnet-master-MP/pages.json

@@ -0,0 +1,179 @@
+{
+	"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
+		{
+			"path": "pages/index/index",
+			"style": {
+				"navigationBarTitleText": "智疗助手",
+				"enablePullDownRefresh": true,
+				"navigationStyle": "custom",
+				"navigationBarBackgroundColor": "#ffffff"
+			}
+		},
+		{
+			"path" : "pages/login/login",
+			"style" : 
+			{
+				"navigationBarTitleText" : "登陆",
+				"navigationBarBackgroundColor": "#1678ff",
+				"navigationBarTextStyle": "white"
+			}
+		},
+		{
+			"path" : "pages/my/my",
+			"style" : 
+			{
+				"navigationBarTitleText" : "我的",
+				"enablePullDownRefresh": true,
+				"navigationBarTextStyle": "white",
+				"navigationBarBackgroundColor": "#1678ff"
+			}
+		},
+
+		{
+			"path" : "pages/test/test",
+			"style" : 
+			{
+				"navigationBarTitleText" : ""
+			}
+		},
+		{
+			"path" : "pages/BMI/BMI",
+			"style" : 
+			{
+				"navigationBarTitleText" : "BMI计算",
+				// "enablePullDownRefresh": true,
+				"navigationBarBackgroundColor": "#ffffff",
+				"navigationBarTextStyle": "black"
+			}
+		},
+		{
+			"path" : "pages/img_process/img_process",
+			"style" : 
+			{
+				"navigationBarTitleText" : "病例识别"
+			}
+		},
+		{
+			"path" : "pages/BMI/BMI_output/BMI_output",
+			"style" : 
+			{
+				"navigationBarTitleText" : "BMI计算结果",
+				"navigationBarBackgroundColor": "#ffffff",
+				"navigationBarTextStyle": "black"
+			}
+		},
+		{
+			"path" : "pages/BMI/BMI_history/BMI_history",
+			"style" : 
+			{
+				"navigationBarTitleText" : "BMI 记录",
+				"enablePullDownRefresh": true,
+				"navigationBarBackgroundColor": "#f1f3f2"
+			}
+		},
+		{
+			"path" : "pages/my/user_info/user_info",
+			"style" : 
+			{
+				"navigationBarTitleText" : "个人资料",
+				"enablePullDownRefresh": true,
+				"navigationBarTextStyle": "white",
+				"navigationBarBackgroundColor": "#1678ff"
+			}
+		},
+		{
+			"path" : "pages/ai/ai",
+			"style" : 
+			{
+				"navigationBarTitleText" : "医伴 AI",
+				"enablePullDownRefresh": true,
+				"navigationBarTextStyle": "black",
+				"navigationBarBackgroundColor": "#d4f4c2"
+			}
+		},
+		{
+			"path" : "pages/my/setting/setting",
+			"style" : 
+			{
+				"navigationBarTitleText" : "设置",
+				"navigationBarBackgroundColor": "#ffffff",
+				"navigationBarTextStyle": "black"
+			}
+		},
+		{
+			"path" : "pages/health_tip/health_tip",
+			"style" : 
+			{
+				"navigationBarTitleText" : "健康小知识"
+			}
+		},
+		{
+			"path" : "pages/health_tip/health_tip_collect/health_tip_collect",
+			"style" : 
+			{
+				"navigationBarTitleText" : "健康知识收藏夹",
+				"navigationBarBackgroundColor": "#f5f5f5"
+			}
+		},
+		{
+			"path" : "pages/my/family/family",
+			"style" : 
+			{
+				"navigationBarTitleText" : "家庭成员",
+				"enablePullDownRefresh": true,
+				"navigationBarTextStyle": "white",
+				"navigationBarBackgroundColor": "#1678ff"
+			}
+		},
+		{
+			"path" : "pages/my/family/family_info/family_info",
+			"style" : 
+			{
+				"navigationBarTitleText" : "成员资料",
+				"navigationBarTextStyle": "white",
+				"navigationBarBackgroundColor": "#1678ff"
+			}
+		},
+		{
+			"path" : "pages/img_process/img_process_history/img_process_history",
+			"style" : 
+			{
+				"navigationBarTitleText" : "病例识别记录",
+				"navigationBarBackgroundColor": "#f6f6f6"
+			}
+		}
+	],
+	"globalStyle": {
+		"navigationBarTextStyle": "black",
+		"navigationBarTitleText": "首页",
+		"navigationBarBackgroundColor": "#ffffff"	
+	},
+	"tabBar": {
+		"borderStyle": "black",
+		"backgroundColor": "#fafbff",
+		"selectedColor": "#1296db",
+		"iconWidth": "20px",
+		"fontSize": "10px",
+		"list": [
+			{
+				"pagePath": "pages/ai/ai",
+				"text": "AI",
+				"iconPath": "/static/tabs/ai-icon.png",
+				"selectedIconPath": "/static/tabs/ai-icon-c.png"
+			},
+			{
+				"pagePath": "pages/index/index",
+				"text": "首页",
+				"iconPath": "/static/tabs/home-icon.png",
+				"selectedIconPath": "/static/tabs/home-icon-c.png"
+			},
+			{
+				"pagePath": "pages/my/my",
+				"text": "我的",
+				"iconPath": "/static/tabs/user-icon.png",
+				"selectedIconPath": "/static/tabs/user-icon-c.png"
+			}
+		]
+	},
+	"uniIdRouter": {}
+}

+ 642 - 0
chexnet-master-MP/pages/ai/ai.vue

@@ -0,0 +1,642 @@
+<template>
+	<view>
+		<!-- <view class="tips color_fff size_12 align_c" :class="{ 'show':ajax.loading }" @tap="getHistoryMsg">{{ajax.loadText}}</view> -->
+		<!-- <image @click="openpopup" class="history" src="/static/img/ai.png"></image> -->
+		<view class="box-1">
+			<view class="talk-list">
+				<view v-for="(item,index) in talkList" :key="index" :id="`msg-${item.id}`">
+					<view class="item flex_col" :class=" item.type == 1 ? 'push':'pull' ">
+						<image v-if="item.pic" :src="item.pic" mode="aspectFill" class="pic"></image>
+						<view class="content">{{item.content}}</view>
+					</view>
+				</view>
+			</view>
+		</view>
+		
+		<view class="box-2">
+			<view class="flex_col" :style="{position: 'relative',bottom:inputHeight+'px'}">
+				<image @click="openpopup" class="history" src="/static/img/ai.png"></image>
+				<view class="flex_grow">
+					<input @confirm="send" :adjust-position="false" @focus="inputBindFocus" 
+						@blur="inputBindBlur" type="text" class="content" v-model="content" 
+						placeholder="请输入聊天内容" placeholder-style="color:#DDD;" 
+						:cursor-spacing="6" @click="$content_check.checkLogin" :disabled="$content_check.isDisabled"
+					/>
+				</view>
+				<button class="send" @tap="send">发送</button>
+			</view>
+		</view>
+		
+		<uv-popup ref="popup" mode="left" bgColor="#f5f5f5" style="overflow-y: scroll;" >
+			<scroll-view class="popup scroll-Y" scroll-y="true">
+				<view class="chart-con">
+					<view class="chart-wrap">
+						<view v-if="ai_talk_history && Object.keys(ai_talk_history).length > 0" class="flex justify-between">
+							<view class="title">
+								<view class="ver-line"></view>
+								<text>AI 问诊记录</text>
+							</view>
+							<button @click="check_delAllHistoryMsg" class="del-button button-all center">删除所有记录</button>
+						</view>
+						
+						<view v-else class="no-data center">暂无数据</view>
+						
+						<uv-gap height="10"></uv-gap>
+						
+						<view @longpress="check_delHistoryMsg(key)" @click="getHistoryMsg(key)" class="history_item" v-if="refresh" v-for="(value, key) in ai_talk_history" :key="key">
+							<view>
+								<text class="block">{{value['timeString']}}</text>
+								<div class="block talk-text"><text style="color: #99d7ff;">{{name}}:</text>{{value['talkList'][1]['content']}}</div>
+								<view class="divider"></view>
+								<div class="block talk-text"><text style="color: #adecaf;">AI:</text>{{value['talkList'][2]['content']}}</div>
+								<view class="divider"></view>
+							</view>
+						</view>
+						<view v-else style="height: 3000px;"></view>
+						<!-- {{ai_talk_history}} -->
+					</view>
+				</view>
+				
+				<button @click="new_talk" class="button-all center new-button">新建对话</button>
+			</scroll-view>
+		</uv-popup>
+	</view>
+</template>
+
+<script>
+	export default {
+		onPullDownRefresh() {
+			setTimeout(() => {uni.stopPullDownRefresh();}, 1000);
+		},
+		onShow() {
+			// 获取全局变量
+			const ai_history = uni.getStorageSync('ai_history');
+			if(ai_history){
+				uni.setStorageSync('ai_history', false);
+				this.openpopup();
+			}
+			
+			// 设置医生性别
+			let doctor_gender = uni.getStorageSync('doctor_gender');
+			if(doctor_gender === 0) this.doctor_gender = 0;
+			else this.doctor_gender = 1;
+			
+			// 设置用户名
+			this.name = uni.getStorageSync("name");
+		},
+		data() {
+			return {
+				
+				doctor_woman: '/static/img/女医生.png',
+				doctor_man: '/static/img/男医生.png',
+				doctor_gender: 1,
+				
+				name: '微信用户',
+				content: '',
+				talkList: [],
+				ai_talk_history: null,
+				timestamp: null,
+				
+				request: 1,
+				// ajax:{
+				// 	rows:20,	//每页数量
+				// 	page:1,	//页码
+				// 	flag:true,	// 请求开关
+				// 	loading:true,	// 加载中
+				// 	loadText:'正在获取消息'
+				// },
+				
+				inputHeight: 0,
+				refresh: true,
+				
+			}
+		},
+		mounted() {
+			this.new_talk();
+			// this.$nextTick(()=>{
+			// 	this.getHistoryMsg();
+			// });
+		},
+		onPageScroll(e){
+			// if(e.scrollTop<5){
+			// 	this.getHistoryMsg();
+			// }
+		},
+		methods: {
+			new_talk(){
+				this.timestamp = Date.now();
+				this.talkList = [{"id":1,"content":"请问有什么需要帮助的吗?"
+				,"type":0,"pic":this.doctor_gender == 1 ? this.doctor_woman : this.doctor_man}];
+			},
+			openpopup(){
+				this.$refs.popup.open();
+				this.ai_talk_history = this.$util.reversedObject(uni.getStorageSync('ai_talk_history'));
+			},
+			inputBindFocus(e) {
+				if(e.detail.height) {
+					this.inputHeight = e.detail.height - this.getBottomBarHeight(); //这个高度就是软键盘的高度
+					console.log('this.inputHeight: ',this.inputHeight);
+				}
+			},
+			inputBindBlur(){
+				this.inputHeight = 0;
+				this.$content_check.textCheck(this.content, this);
+			},
+			getBottomBarHeight() {
+				let res = uni.getSystemInfoSync();
+				const windowHeight = res.windowHeight; // 屏幕可用高度
+				const screenHeight = res.screenHeight; // 屏幕总高度
+				let bottomBarHeight = screenHeight - windowHeight - getTopHeight();
+				bottomBarHeight = bottomBarHeight > 0 ? bottomBarHeight : 0;
+				
+				console.log('Bottom Bar Height:',bottomBarHeight);
+				return bottomBarHeight;
+				
+				function getTopHeight(){
+					// 状态栏高度
+					const statusBarHeight = uni.getSystemInfoSync().statusBarHeight
+					
+					// #ifdef MP-WEIXIN
+					// 获取微信胶囊的位置信息 width,height,top,right,left,bottom
+					const custom = wx.getMenuButtonBoundingClientRect()
+					// console.log(custom)
+					
+					// 导航栏高度(标题栏高度) = 胶囊高度 + (顶部距离 - 状态栏高度) * 2
+					const navigationBarHeight = custom.height + (custom.top - statusBarHeight) * 2
+					// console.log("导航栏高度:"+navigationBarHeight)
+					
+					// 总体高度 = 状态栏高度 + 导航栏高度
+					const navHeight = navigationBarHeight + statusBarHeight
+					
+					// #endif
+					
+					console.log(navHeight);
+					return navHeight;
+				}
+			},
+			check_delAllHistoryMsg(){
+				uni.showModal({
+					title: '清除历史纪录',
+					content: '是否要清除全部历史纪录?',
+					success: (res) => { 
+						if (res.confirm) 
+							this.delAllHistoryMsg(); 
+					}  
+				})
+			},
+			delAllHistoryMsg(){
+				this.new_talk();
+				
+				this.ai_talk_history = null;
+				
+				uni.setStorageSync('ai_talk_history', {});
+			},
+			check_delHistoryMsg(key){
+				uni.showModal({
+					title: '删除历史纪录',
+					content: '是否要删除此历史纪录?',
+					success: (res) => { 
+						if (res.confirm) 
+							this.delHistoryMsg(key); 
+					}  
+				})	
+			},
+			delHistoryMsg(key){
+				if(this.timestamp == key) this.new_talk();
+				
+				let ai_talk_history = uni.getStorageSync('ai_talk_history');
+				delete ai_talk_history[key];
+				
+				this.ai_talk_history = ai_talk_history;
+				uni.setStorageSync('ai_talk_history', ai_talk_history);
+			},
+			// 获取历史消息
+			getHistoryMsg(key){
+				console.log('key: ',key);
+				this.timestamp = key;
+				this.talkList = this.ai_talk_history[key]['talkList'];
+				this.$refs.popup.close();
+				
+				
+			},
+			// 设置页面滚动位置
+			setPageScrollTo(selector){
+				let view = uni.createSelectorQuery().in(this).select(selector);
+				view.boundingClientRect((res) => {
+					uni.pageScrollTo({
+					    scrollTop:res.top - 30,	// -30 为多显示出大半个消息的高度,示意上面还有信息。
+					    duration: 0
+					});
+				}).exec();
+			},
+			// 隐藏加载提示
+			hideLoadTips(flag){
+				if(flag){
+					this.ajax.loadText = '消息获取成功';
+					setTimeout(()=>{
+						this.ajax.loading = false;
+					},300);
+				}else{
+					this.ajax.loading = true;
+					this.ajax.loadText = '正在获取消息';
+				}
+			},
+			// 发送信息
+			async send(){
+				if(!this.content){
+					uni.showToast({
+						title:'请输入有效的内容',
+						icon:'none'
+					});
+					return ;
+				}
+				
+				if(this.request == 0){
+					uni.showToast({duration:1000,icon:'none',title: '消息发送中,请稍等...'});
+					setTimeout(() => uni.showLoading({title:'正在发送...'}), 1500);
+					return ;
+				}
+				
+				let isCheck = await this.$content_check.textCheck(this.content, this);
+				console.log('isCheck: ',isCheck);
+				if(isCheck !== true) return ;
+				
+				this.request = 0;
+				uni.showLoading({title:'正在发送...'});
+				
+				// 将当前发送信息 添加到消息列表。
+				let data = {
+					"id":new Date().getTime(),
+					"content":this.content,
+					"type":1,
+					"pic":null
+				}
+				this.talkList.push(data);
+				
+				let content = this.content;
+				this.$nextTick(()=>{
+					// 清空内容框中的内容
+					this.content = '';
+					uni.pageScrollTo({
+						scrollTop: 999999,	// 设置一个超大值,以保证滚动条滚动到底部
+						duration: 500
+					});
+				});
+				
+				send_message_ai(content).then(res => {
+					console.log('send: res: ',res);
+					let data = {
+						"id":new Date().getTime(),
+						"content":res,
+						"type":0,
+						"pic":this.doctor_gender == 1 ? this.doctor_woman : this.doctor_man
+					}
+					this.talkList.push(data);
+					uni.hideLoading();
+					uni.pageScrollTo({
+						scrollTop: 999999,	// 设置一个超大值,以保证滚动条滚动到底部
+						duration: 500
+					});
+					this.request = 1;
+					
+					this.save_talk(this.talkList, this.timestamp);
+				}).catch(err => {
+					console.log('send: err: ',err);
+					uni.hideLoading();
+					this.imgshow = false;
+					this.request = 1;
+				});
+			},
+			save_talk(talkList, timestamp){
+				let timeString = this.$util.formatDateTime(timestamp);
+				let talkData = {talkList, timeString};
+				
+				let ai_talk_history = uni.getStorageSync('ai_talk_history');
+				if(ai_talk_history) ai_talk_history[timestamp] = talkData;
+				else{
+					ai_talk_history = {};
+					ai_talk_history[timestamp] = talkData;
+				}
+				uni.setStorageSync('ai_talk_history', ai_talk_history);
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	@import "talk.scss";
+	page{
+		background-color: #f5f5f5;
+		font-size: 28rpx;
+	}
+	.scroll-Y {
+		/* #ifndef MP */
+		height: calc(100vh - 155px);
+		/* #endif */
+		
+		/* #ifdef MP */
+		height: calc(100vh - 65px);
+		/* #endif */
+	}
+	.popup{
+		padding: 10px;
+		width: 75vw;
+		
+		.new-button{
+			color: #666;
+			position: fixed;
+			bottom: 15px;
+			background-color: #d4f4c2;
+			height: 50px;
+			width: calc(100% - 20px);
+			font-size: 15px;
+			font-weight: 550;
+			border-radius: 25px;
+		}
+		
+		.history_item{
+			margin-bottom: 10px;
+			background-color: #fff;
+			border-radius: 20px;
+			padding: 10px 20px 20px;
+			color: #666;
+			font-size: 14px;
+			
+			text{
+				margin: 10px 0;
+			}
+			
+			.talk-text{
+				margin: 10px 0 5px;
+				color: #565;
+				font-weight: 550;
+				font-size: 18px;
+				overflow-wrap: break-word;
+				text-overflow: ellipsis;
+				overflow: hidden;
+				white-space: nowrap;
+			}
+		}
+		.history_item:active{
+			opacity: 0.5;
+		}
+	}
+	.history{
+		width: 32px;
+		height: 32px;
+		margin-right: 5px;
+	}
+	.history:active {
+		opacity: 0.5;
+	}
+	
+	/* 加载数据提示 */
+	.tips{
+		position: fixed;
+		left: 0;
+		top:var(--window-top);
+		width: 100%;
+		z-index: 9;
+		color: #333;
+		// background-color: rgba(0,0,0,0.15);
+		height: 72rpx;
+		line-height: 72rpx;
+		transform:translateY(-80rpx);
+		transition: transform 0.3s ease-in-out 0s;
+		
+		&.show{
+			transform:translateY(0);
+		}
+	}
+	
+	.box-1{
+		width: 100%;
+		height: auto;
+		padding-bottom: 100rpx;
+		box-sizing: content-box;
+		
+		/* 兼容iPhoneX */
+		margin-bottom: 0;  
+		margin-bottom: constant(safe-area-inset-bottom);  
+		margin-bottom: env(safe-area-inset-bottom);  
+	}
+	.box-2{
+		position: fixed;
+		left: 0;
+		width: 100%;
+		bottom: 0;
+		height: auto;
+		z-index: 2;
+		// border-top: #e5e5e5 solid 1px;
+		box-sizing: content-box;
+		background-color: #f5f5f5;
+		
+		/* 兼容iPhoneX */
+		padding-bottom: 0;  
+		padding-bottom: constant(safe-area-inset-bottom);  
+		padding-bottom: env(safe-area-inset-bottom);  
+		
+		>view{
+			padding: 0 20rpx;
+			height: 100rpx;
+		}
+		
+		.content{
+			background-color: #fff;
+			height: 64rpx;
+			padding: 0 20rpx;
+			border-radius: 32rpx;
+			font-size: 28rpx;
+		}
+		
+		.send{
+			background-color: #b7d7a5;
+			color: #fff;
+			height: 64rpx;
+			margin-left: 20rpx;
+			border-radius: 32rpx;
+			padding: 0;
+			width: 120rpx;
+			line-height: 62rpx;
+			
+			&:active{
+				opacity: 1;
+				filter: brightness(80%);
+				transform: translateY(1px) translateX(1px);
+			}
+		}
+	}
+	
+	.talk-list{
+		padding-bottom: 20rpx;
+		
+		/* 消息项,基础类 */
+		.item{
+			padding: 20rpx 20rpx 0 20rpx;
+			align-items:flex-start;
+			align-content:flex-start;
+			color: #333;
+			
+			.pic{
+				width: 92rpx;
+				height: 92rpx;
+				border-radius: 50%;
+				border: #fff solid 1px;
+			}
+			
+			.content{
+				padding: 20rpx;
+				border-radius: 4px;
+				max-width: 500rpx;
+				word-break: break-all;
+				line-height: 52rpx;
+				position: relative;
+			}
+			
+			/* 收到的消息 */
+			&.pull{
+				.content{
+					margin-left: 32rpx;
+					background-color: #fff;
+					
+					&::after{
+						content: '';
+						display: block;
+						width: 0;
+						height: 0;
+						border-top: 16rpx solid transparent;
+						border-bottom: 16rpx solid transparent;
+						border-right: 20rpx solid #fff;
+						position: absolute;
+						top: 30rpx;
+						left: -18rpx;
+					}
+				}
+			}
+			
+			/* 发出的消息 */
+			&.push{
+				/* 主轴为水平方向,起点在右端。使不修改DOM结构,也能改变元素排列顺序 */
+				flex-direction: row-reverse;
+				
+				.content{
+					margin-right: 32rpx;
+					background-color: #c7e7b5;
+				}
+			}
+		}
+	}
+</style>
+
+<style lang="scss" scoped>
+	.no-data{
+		height: 30vw;
+		font-size: 24px;
+		color: #777;
+		padding: 10px;
+	}
+	.del-button{
+		height: 30px;
+		width: 250px;
+		margin-right: 10px;
+		color: #666;
+		background-color: #ffffff;
+	}
+	.chart-con {
+		width: 100%;
+		box-sizing: border-box;
+
+		.chart-wrap {
+			width: 100%;
+			box-sizing: border-box;
+			background-color: #f5f5f5;
+			padding: 32rpx 0rpx;
+			border-radius: 20rpx;
+
+			.title {
+				box-sizing: border-box;
+				width: 100%;
+				padding: 0rpx 28rpx;
+				display: flex;
+				flex-direction: row;
+				justify-content: flex-start;
+				align-items: center;
+			}
+
+			.ver-line {
+				height: 30rpx;
+				width: 8rpx;
+				border-radius: 10rpx;
+				background-color: #4e9d77;
+				margin-right: 5px;
+			}
+
+			.title-desc {
+				font-size: 30rpx;
+				color: #222222;
+				margin-left: 22rpx;
+				font-weight: bold;
+			}
+
+			
+			.line-chart-con {
+				width: 100%;
+				box-sizing: border-box;
+				padding: 0rpx 28rpx;
+
+				.fun-tabs {
+					margin-top: 42rpx;
+					display: flex;
+					flex-direction: row;
+					justify-content: space-between;
+					align-self: center;
+					width: 100%;
+					box-sizing: border-box;
+
+					.tab-item {
+						width: 200rpx;
+						height: 120rpx;
+						border-radius: 10rpx;
+						padding-left: 20rpx;
+						background: #ffffff;
+						border: 1rpx solid #ececec;
+						display: flex;
+						flex-direction: column;
+						justify-content: center;
+						align-items: flex-start;
+						box-sizing: border-box;
+
+						.item-name {
+							color: #6e6e6e;
+							font-size: 20rpx;
+						}
+
+						.item-val {
+							color: #222222;
+							font-size: 24rpx;
+							font-weight: bold;
+							margin-top: 20rpx;
+						}
+					}
+
+					.selected {
+						background: #edf5f1 !important;
+						border: 1rpx solid #4e9d77 !important;
+
+						.item-name {
+							color: #4e9d77 !important;
+						}
+
+						.item-val {
+							color: #4e9d77 !important;
+						}
+					}
+				}
+				.line-chart {
+					margin-top: 30rpx;
+					height: 380rpx;
+				}
+			}
+		}
+	}
+</style>

+ 120 - 120
项目/health_tip.vue → chexnet-master-MP/pages/health_tip/health_tip.vue

@@ -1,141 +1,141 @@
 <template>
-	<view v-if="health_tip" class="content">
-		<view>
-			<text class="title">健康小知识</text>
-		</view>
-		
-		<uv-gap height="50"></uv-gap>
-		
-		<view class="tip center">
-			<div class="long-text center" v-html="health_tip"></div>
-		</view>
-		
-		<view @click="change_favor" class="flex justify-end relative" style="top: -55vw;">
-			<text v-if="favor" class="favor cuIcon-favorfill text-red"></text>
-			<text v-else class="favor cuIcon-favor"></text>
-		</view>
-		
-		<uv-gap height="50"></uv-gap>
-		
-		<view class="flex justify-between">
-			<button @click="get_health_tip(index - 1)" class="button-all">上一条</button>
-			<button @click="get_health_tip(index + 1)" class="button-all">下一条</button>
+	<view v-if="health_tip" class="content">
+		<view>
+			<text class="title">健康小知识</text>
 		</view>
-	</view>
+		
+		<uv-gap height="50"></uv-gap>
+		
+		<view class="tip center">
+			<div class="long-text center" v-html="health_tip"></div>
+		</view>
+		
+		<view @click="change_favor" class="flex justify-end relative" style="top: -55vw;">
+			<text v-if="favor" class="favor cuIcon-favorfill text-red"></text>
+			<text v-else class="favor cuIcon-favor"></text>
+		</view>
+		
+		<uv-gap height="50"></uv-gap>
+		
+		<view class="flex justify-between">
+			<button @click="get_health_tip(index - 1)" class="button-all">上一条</button>
+			<button @click="get_health_tip(index + 1)" class="button-all">下一条</button>
+		</view>
+	</view>
 	<view v-else class="no_data"></view>
 </template>
 
-<script>
+<script>
 	import health_tips from '@/static/health_tip.js'
-	export default {
-		onShow() {
-			this.get_random_health_tip();
+	export default {
+		onShow() {
+			this.get_random_health_tip();
 		},
 		data() {
 			return {
-				health_tip: null,
-				index: null,
+				health_tip: null,
+				index: null,
 				favor: false
 			}
 		},
-		methods: {
-			change_favor(){
-				let favor_health_list = uni.getStorageSync('favor_health_list');
-				if(favor_health_list){
-					if(favor_health_list.includes(this.index)) 
-						favor_health_list.splice(favor_health_list.indexOf(this.index), 1);
-					else favor_health_list.unshift(this.index);
-				}
-				else favor_health_list = [this.index];
-				
-				uni.setStorageSync('favor_health_list',favor_health_list);
-				
-				this.favor = !this.favor;
-			},
-			get_health_tip(index){
-				if(index < 0 || index >= health_tips.length){
-					uni.showToast({duration:1000,icon:'none',title: '到顶了'});
-					return ;
-				}
-				const health_tip = health_tips[index];
-				// 输出随机选取的元素
-				console.log(health_tip);
-				
-				this.favor = false;
-				let favor_health_list = uni.getStorageSync('favor_health_list');
-				if(favor_health_list){
-					if(favor_health_list.includes(index)) this.favor = true;
-				}
-				
-				this.index = index;
-				
-				this.health_tip = formatHealthTip(health_tip);
-				
-				function formatHealthTip(health_tip) {
-				    const totalLength = health_tip.length;
-				    const numRows = Math.ceil(totalLength / 10); // 计算总行数
-				    const charsPerRow = Math.ceil(totalLength / numRows); // 计算每行字符数
-				
-				    let formattedTip = '';
-				    for (let i = 0; i < totalLength; i += charsPerRow) {
-				        formattedTip += health_tip.slice(i, i + charsPerRow) + '<br>';
-				    }
-				
-				    // 去掉最后一行多余的 <br>
-				    formattedTip = formattedTip.slice(0, -4);
-				
-				    return formattedTip;
-				}
+		methods: {
+			change_favor(){
+				let favor_health_list = uni.getStorageSync('favor_health_list');
+				if(favor_health_list){
+					if(favor_health_list.includes(this.index)) 
+						favor_health_list.splice(favor_health_list.indexOf(this.index), 1);
+					else favor_health_list.unshift(this.index);
+				}
+				else favor_health_list = [this.index];
+				
+				uni.setStorageSync('favor_health_list',favor_health_list);
+				
+				this.favor = !this.favor;
 			},
-			get_random_health_tip(){
-				// 获取数组的长度
-				const length = health_tips.length;
-				
-				// 使用 Math.random() 生成一个 [0, length) 范围内的随机索引值
-				const randomIndex = Math.floor(Math.random() * length);
-				
-				this.get_health_tip(randomIndex);
+			get_health_tip(index){
+				if(index < 0 || index >= health_tips.length){
+					uni.showToast({duration:1000,icon:'none',title: '到顶了'});
+					return ;
+				}
+				const health_tip = health_tips[index];
+				// 输出随机选取的元素
+				console.log(health_tip);
+				
+				this.favor = false;
+				let favor_health_list = uni.getStorageSync('favor_health_list');
+				if(favor_health_list){
+					if(favor_health_list.includes(index)) this.favor = true;
+				}
+				
+				this.index = index;
+				
+				this.health_tip = formatHealthTip(health_tip);
+				
+				function formatHealthTip(health_tip) {
+				    const totalLength = health_tip.length;
+				    const numRows = Math.ceil(totalLength / 10); // 计算总行数
+				    const charsPerRow = Math.ceil(totalLength / numRows); // 计算每行字符数
+				
+				    let formattedTip = '';
+				    for (let i = 0; i < totalLength; i += charsPerRow) {
+				        formattedTip += health_tip.slice(i, i + charsPerRow) + '<br>';
+				    }
+				
+				    // 去掉最后一行多余的 <br>
+				    formattedTip = formattedTip.slice(0, -4);
+				
+				    return formattedTip;
+				}
+			},
+			get_random_health_tip(){
+				// 获取数组的长度
+				const length = health_tips.length;
+				
+				// 使用 Math.random() 生成一个 [0, length) 范围内的随机索引值
+				const randomIndex = Math.floor(Math.random() * length);
+				
+				this.get_health_tip(randomIndex);
 			}
 		}
 	}
 </script>
 
-<style>
-	page{
-		background-color: #fff;
-	}
-	button{
-		background-color: aliceblue;
-	}
-	.content{
-		padding: 20px;
-	}
-	.tip{
-		height: 55vw;
-		background-color: #f1f3f2;
-		border: none;
-		border-radius: 30px;
-		padding: 20px;
-		letter-spacing: 2px;
-		margin-bottom: 20px;
-		font-weight: 1000;
-		color: #666;
-		font-size: 27px;
+<style>
+	page{
+		background-color: #fff;
+	}
+	button{
+		background-color: aliceblue;
+	}
+	.content{
+		padding: 20px;
+	}
+	.tip{
+		height: 55vw;
+		background-color: #f1f3f2;
+		border: none;
+		border-radius: 30px;
+		padding: 20px;
+		letter-spacing: 2px;
+		margin-bottom: 20px;
+		font-weight: 1000;
+		color: #666;
+		font-size: 27px;
+	}
+	.title{
+		color: #777;
+		font-size: 20px;
+		font-weight: 600;
+	}
+	.favor{
+		font-size: 24px;
+		margin-right: 5vw;
 	}
-	.title{
-		color: #777;
-		font-size: 20px;
-		font-weight: 600;
-	}
-	.favor{
-		font-size: 24px;
-		margin-right: 5vw;
-	}
-	.long-text{
-		width: 100vw;
-		overflow: scroll;
-		height: calc(55vw - 40px);
-		overflow-wrap: break-word;
-		text-align: center;
+	.long-text{
+		width: 100vw;
+		overflow: scroll;
+		height: calc(55vw - 40px);
+		overflow-wrap: break-word;
+		text-align: center;
 	}
 </style>

+ 94 - 94
项目/health_tip_collect.vue → chexnet-master-MP/pages/health_tip/health_tip_collect/health_tip_collect.vue

@@ -1,116 +1,116 @@
 <template>
 	<view v-if="favor_health_list" class="content">
-		<view v-for="(value, key) in favor_health_list" :key="value" class="tip-group">
-			<view class="tip-item flex justify-between">
-				<div class="center" v-html="formatHealthTip(health_tips[value])"></div>
-				<view @click="change_favor(value,key)" class="flex justify-end relative">
-					<text v-if="change_favor_list[key]" class="favor cuIcon-favorfill text-red"></text>
-					<text v-else class="favor cuIcon-favor"></text>
-				</view>
-			</view>
+		<view v-for="(value, key) in favor_health_list" :key="value" class="tip-group">
+			<view class="tip-item flex justify-between">
+				<div class="center" v-html="formatHealthTip(health_tips[value])"></div>
+				<view @click="change_favor(value,key)" class="flex justify-end relative">
+					<text v-if="change_favor_list[key]" class="favor cuIcon-favorfill text-red"></text>
+					<text v-else class="favor cuIcon-favor"></text>
+				</view>
+			</view>
 		</view>
-	</view>
+	</view>
 	<view v-else class="no_data"></view>
 </template>
 
-<script>
+<script>
 	import health_tips from '@/static/health_tip.js'
-	export default {
-		onLoad() {
-			let favor_health_list = uni.getStorageSync('favor_health_list');
-			if(favor_health_list && Object.keys(favor_health_list).length != 0) this.favor_health_list = favor_health_list;
-			
-			this.change_favor_list = new Array(favor_health_list.length).fill(1);
+	export default {
+		onLoad() {
+			let favor_health_list = uni.getStorageSync('favor_health_list');
+			if(favor_health_list && Object.keys(favor_health_list).length != 0) this.favor_health_list = favor_health_list;
+			
+			this.change_favor_list = new Array(favor_health_list.length).fill(1);
 		},
 		data() {
-			return {
+			return {
 				health_tips,
-				favor_health_list: null,
+				favor_health_list: null,
 				change_favor_list: null
 			}
 		},
-		methods: {
-			formatHealthTip(health_tip) {
-			    const totalLength = health_tip.length;
-			    const numRows = Math.ceil(totalLength / 15); // 计算总行数
-			    const charsPerRow = Math.ceil(totalLength / numRows); // 计算每行字符数
-			
-			    let formattedTip = '';
-			    for (let i = 0; i < totalLength; i += charsPerRow) {
-			        formattedTip += health_tip.slice(i, i + charsPerRow) + '<br>';
-			    }
-			
-			    // 去掉最后一行多余的 <br>
-			    formattedTip = formattedTip.slice(0, -4);
-			
-			    return formattedTip;
+		methods: {
+			formatHealthTip(health_tip) {
+			    const totalLength = health_tip.length;
+			    const numRows = Math.ceil(totalLength / 15); // 计算总行数
+			    const charsPerRow = Math.ceil(totalLength / numRows); // 计算每行字符数
+			
+			    let formattedTip = '';
+			    for (let i = 0; i < totalLength; i += charsPerRow) {
+			        formattedTip += health_tip.slice(i, i + charsPerRow) + '<br>';
+			    }
+			
+			    // 去掉最后一行多余的 <br>
+			    formattedTip = formattedTip.slice(0, -4);
+			
+			    return formattedTip;
 			},
-			change_favor(index,key){
-				console.log('index: ',index);
-				console.log('key: ',key);
-				let favor_health_list = uni.getStorageSync('favor_health_list');
-				
-				if(favor_health_list){
-					if(favor_health_list.includes(index)) 
-						favor_health_list.splice(favor_health_list.indexOf(index), 1);
-					else favor_health_list.unshift(index);
-				}
-				else favor_health_list = [index];
-				
-				uni.setStorageSync('favor_health_list',favor_health_list);
-				
-				if(this.change_favor_list[key] == 1) this.change_favor_list[key] = 0;
-				else this.change_favor_list[key] = 1;
-				this.$forceUpdate();
+			change_favor(index,key){
+				console.log('index: ',index);
+				console.log('key: ',key);
+				let favor_health_list = uni.getStorageSync('favor_health_list');
+				
+				if(favor_health_list){
+					if(favor_health_list.includes(index)) 
+						favor_health_list.splice(favor_health_list.indexOf(index), 1);
+					else favor_health_list.unshift(index);
+				}
+				else favor_health_list = [index];
+				
+				uni.setStorageSync('favor_health_list',favor_health_list);
+				
+				if(this.change_favor_list[key] == 1) this.change_favor_list[key] = 0;
+				else this.change_favor_list[key] = 1;
+				this.$forceUpdate();
 			},
 		}
 	}
 </script>
 
 <style lang="less">
-	page{
-		background-color: #f5f5f5;
-	}
-	text{
-		font-size: 18px;
-		display: flex;
-		justify-content: center; /* 水平居中 */
-		align-items: center;    /* 垂直居中 */
-	}
-	
-	.content {
-		padding:5px 20px;
-		border: none;
-	}
-	.tip-group{
-		border: none;
-		border-radius: 15px;
-		width: 100%;
-		background-color: #ffffff;
-		margin-bottom: 10px;
-		
-		:first-child{
-			border-radius: 15px 15px 0% 0%;
-		}
-		:last-child{
-			border-radius:0% 0% 15px 15px;
-		}
-		:only-child {
-		    border-radius: 15px;
-		}
-	}
-	.tip-item{
-		font-size: 18px;
-		border: 0px;
-		height: 14vw;
-		padding: 0 10px;
-		background-color: #ffffff;
-	}
-	.tip-item::after {
-	    display: none;
-	}
-	.favor{
-		font-size: 24px;
-		margin-right: 5px;
+	page{
+		background-color: #f5f5f5;
+	}
+	text{
+		font-size: 18px;
+		display: flex;
+		justify-content: center; /* 水平居中 */
+		align-items: center;    /* 垂直居中 */
+	}
+	
+	.content {
+		padding:5px 20px;
+		border: none;
+	}
+	.tip-group{
+		border: none;
+		border-radius: 15px;
+		width: 100%;
+		background-color: #ffffff;
+		margin-bottom: 10px;
+		
+		:first-child{
+			border-radius: 15px 15px 0% 0%;
+		}
+		:last-child{
+			border-radius:0% 0% 15px 15px;
+		}
+		:only-child {
+		    border-radius: 15px;
+		}
+	}
+	.tip-item{
+		font-size: 18px;
+		border: 0px;
+		height: 14vw;
+		padding: 0 10px;
+		background-color: #ffffff;
+	}
+	.tip-item::after {
+	    display: none;
+	}
+	.favor{
+		font-size: 24px;
+		margin-right: 5px;
 	}
 </style>

+ 386 - 367
项目/img_process.vue → chexnet-master-MP/pages/img_process/img_process.vue

@@ -1,373 +1,392 @@
 <template>
-	<view class="content">
-		<text v-if="process_show" class="title">x胸腔图病症识别器</text>
-		<text v-else style="color: #666;">{{timeString}}</text>
-		<view class="history">
-			<image @click="$util.navigateTo('/pages/img_process/img_process_history/img_process_history')" src="@/static/img/history_bmi.png"></image>
-			<p style="text-align: center;" class="head-text">历史</p>
-		</view>
-		<uv-gap height="10"></uv-gap>
-		<view v-if="process_show" class="flex justify-center" style="align-items: center; padding: 20px;">
-			<view class="flex justify-between align-center">
-				<uv-upload
-					@oversize="tip()"
-					maxSize=2097152
-					:compressed='false'
-					:fileList="fileList1" 
-					name="1" 
-					:maxCount="1" 
-					@afterRead="afterRead"
-					@delete="deletePic"
-					:previewFullImage="true"
-					width="100" 
-					height="100">
-				</uv-upload>
-				
-				<image v-if="img_url" class="arrow" src="/static/img/右箭头.png" mode="widthFix" style="background-color: #fff;"></image>
-				<image v-if="img_url" :src="img_url" mode="widthFix"></image>
-				
-			</view>
-		</view>
-		
-		<uv-gap v-if="process_show" height="10"></uv-gap>
-		
-		<view v-if="process_show" class="flex justify-center">
-			<uv-button @click="processImg()" color="#1678ff" text="确定" customStyle="width:85vw" customTextStyle="font-weight: 1000;"></uv-button>
-		</view>
-		
-		<uv-gap height="10"></uv-gap>
-		
-		<view v-if="resInfo">
-			<view v-if="resInfo['code']">
-				<h3 v-if="process_show" style="font-weight: 1000;color: #53c21d;">处理成功:</h3>
-				
-				<uv-gap height="5"></uv-gap>
-				
-				<view style="font-size: 16px;">
-					<text style="color: #666;">识别结果:</text>
-					<text style="color: #f56c6c;margin-right: 10px;" v-for="(diseaseValue, diseaseKey) in resInfo['data']['disease']" :key="diseaseKey">{{diseaseValue}}</text>
-				</view>
-				
-				<uv-gap v-if="!process_show" height="20"></uv-gap>
-				
-				<uv-gap height="10"></uv-gap>
-				
-				<view class="diseaseItem" v-for="(diseaseValue, diseaseKey) in resInfo['data']['disease']" :key="diseaseKey">
-				    <text>{{diseaseValue}}:</text>
-					<view>
-				        <text>饮食建议:</text>
-				        <view v-for="(dietValue, dietKey) in resInfo['data'][diseaseValue]['饮食建议']" :key="dietKey">
-				            <text>{{dietValue}}</text>
-				        </view>
-				    </view>
-				    <view>
-				        <text>锻炼建议:</text>
-				        <view v-for="(exerciseValue, exerciseKey) in resInfo['data'][diseaseValue]['锻炼建议']" :key="exerciseKey">
-				            <text>{{exerciseValue}}</text>
-				        </view>
-				    </view>
-				    <view>
-				        <text>睡眠建议:</text>
-				        <view v-for="(sleepValue, sleepKey) in resInfo['data'][diseaseValue]['睡眠建议']" :key="sleepKey">
-				            <text>{{sleepValue}}</text>
-				        </view>
-				    </view>
-				    <view>
-				        <text>用药建议:</text>
-				        <view v-for="(medicationValue, medicationKey) in resInfo['data'][diseaseValue]['用药建议']" :key="medicationKey">
-				            <text>{{medicationValue}}</text>
-				        </view>
-				    </view>
-				</view>
-				
-				<view class="aiSuggest" v-if="aiSuggest">
-					<text>AI 建议</text>
-					<view>
-						<text>{{aiSuggest}}</text>
-					</view>
-				</view>
-				<view v-else class="aiSuggest button-all center" style="height: 50vw;" @click="getAiSuggest">获得ai建议</view>
-				<uv-gap height="30"></uv-gap>
-				<uv-divider text="我是有底线的"></uv-divider>
-				<uv-gap height="30"></uv-gap>
-			</view>
-			<view v-if="!resInfo['code']">
-				<p style="color: #f56c6c; font-size: 18px;">处理失败</p>
-			</view>
+	<view class="content">
+		<text v-if="process_show" class="title">x胸腔图病症识别器</text>
+		<text v-else style="color: #666;">{{timeString}}</text>
+		<view class="history">
+			<image @click="$util.navigateTo('/pages/img_process/img_process_history/img_process_history')" src="@/static/img/history_bmi.png"></image>
+			<p style="text-align: center;" class="head-text">历史</p>
+		</view>
+		<uv-gap height="10"></uv-gap>
+		<view v-if="process_show" class="flex justify-center" style="align-items: center; padding: 20px;">
+			<view class="flex justify-between align-center">
+				<uv-upload
+					@oversize="tip()"
+					maxSize=1048576
+					:compressed='false'
+					:fileList="fileList1" 
+					name="1" 
+					:maxCount="1" 
+					@afterRead="afterRead"
+					@delete="deletePic"
+					:previewFullImage="true"
+					width="100" 
+					height="100">
+				</uv-upload>
+				
+				<image v-if="img_url" class="arrow" src="/static/img/右箭头.png" mode="widthFix" style="background-color: #fff;"></image>
+				<image v-if="img_url" :src="img_url" mode="widthFix"></image>
+				
+			</view>
+		</view>
+		
+		<uv-gap v-if="process_show" height="10"></uv-gap>
+		
+		<view v-if="process_show && !processed" class="flex justify-center">
+			<uv-button @click="processImg" color="#1678ff" text="确定" customStyle="width:85vw" customTextStyle="font-weight: 1000;"></uv-button>
+		</view>
+		
+		<uv-gap height="10"></uv-gap>
+		
+		<view v-if="resInfo">
+			<view v-if="resInfo['code']">
+				<h3 v-if="process_show" style="font-weight: 1000;color: #53c21d;">处理成功:</h3>
+				
+				<uv-gap height="5"></uv-gap>
+				
+				<view style="font-size: 16px;">
+					<text style="color: #666;">识别结果:</text>
+					<text style="color: #f56c6c;margin-right: 10px;" v-for="(diseaseValue, diseaseKey) in resInfo['data']['disease']" :key="diseaseKey">{{diseaseValue}}</text>
+				</view>
+				
+				<uv-gap v-if="!process_show" height="20"></uv-gap>
+				
+				<uv-gap height="10"></uv-gap>
+				
+				<view class="diseaseItem" v-for="(diseaseValue, diseaseKey) in resInfo['data']['disease']" :key="diseaseKey">
+				    <text>{{diseaseValue}}:</text>
+					<view>
+				        <text>饮食建议:</text>
+				        <view v-for="(dietValue, dietKey) in resInfo['data'][diseaseValue]['饮食建议']" :key="dietKey">
+				            <text>{{dietValue}}</text>
+				        </view>
+				    </view>
+				    <view>
+				        <text>锻炼建议:</text>
+				        <view v-for="(exerciseValue, exerciseKey) in resInfo['data'][diseaseValue]['锻炼建议']" :key="exerciseKey">
+				            <text>{{exerciseValue}}</text>
+				        </view>
+				    </view>
+				    <view>
+				        <text>睡眠建议:</text>
+				        <view v-for="(sleepValue, sleepKey) in resInfo['data'][diseaseValue]['睡眠建议']" :key="sleepKey">
+				            <text>{{sleepValue}}</text>
+				        </view>
+				    </view>
+				    <view>
+				        <text>用药建议:</text>
+				        <view v-for="(medicationValue, medicationKey) in resInfo['data'][diseaseValue]['用药建议']" :key="medicationKey">
+				            <text>{{medicationValue}}</text>
+				        </view>
+				    </view>
+				</view>
+				
+				<view class="aiSuggest" v-if="aiSuggest">
+					<text>AI 建议</text>
+					<view>
+						<text>{{aiSuggest}}</text>
+					</view>
+				</view>
+				<view v-else class="aiSuggest button-all center" style="height: 50vw;" @click="getAiSuggest">获得ai建议</view>
+				<uv-gap height="30"></uv-gap>
+				<uv-divider text="我是有底线的"></uv-divider>
+				<uv-gap height="30"></uv-gap>
+			</view>
+			<view v-if="!resInfo['code']">
+				<p style="color: #f56c6c; font-size: 18px;">处理失败</p>
+			</view>
 		</view>
 	</view>
 </template>
 
-<script>
-	import img_api from '@/data/img_api';
-	import send_message_ai from "@/data/medical_ai.js"
-	import disease_suggest from '@/static/disease_suggest.js'
-	
-	export default {
-		onLoad(options) {
-			// 获取传递的对象参数,使用decodeURIComponent解码,并转为对象
-			if ('timestamp' in options) {
-				this.process_show = false;
-				
-				let timestamp = JSON.parse(decodeURIComponent(options.timestamp));
-				this.timestamp = timestamp;
-				console.log('timestamp: ',timestamp);
-				
-				let img_process_history = uni.getStorageSync('img_process_history');
-				
-				let img_process = img_process_history[timestamp];
-				let resInfo = {code: 1, data: {disease: img_process['disease']}};
-				
-				for (let disease of img_process['disease']) {
-					resInfo['data'][disease] = disease_suggest[disease];
-				}
-				this.resInfo = resInfo;
-				
-				this.timeString = img_process['time_string'];
-				
-				if(img_process['ai_suggest']) this.aiSuggest = img_process['ai_suggest'];
-				
-			}
-		},
-		data() {
-			return {
-				process_show: true,
-				uploading: false,
-				style:null,
-				fileList1: [],
-				resInfo: null,
-				img_url: null,
-				request: 1,
-				aiSuggest: null,
-				timestamp: null,
-				timeString: null
-			}
-		},
-		methods: {
-			getAiSuggest(){
-				let disease = '';
-				for (let value of this.resInfo['data']['disease']) disease += '、' + value;
-				
-				// 去掉第一个多余的 "、"
-				disease = disease.substring(1);
-				
-				const content = '通过x胸腔图,已知患者有' + disease + "症状,请给出一些建议";
-				console.log('content: ',content);
-				
-				if(this.request == 0){
-					uni.showToast({duration:1000,icon:'none',title: '消息发送中,请稍等...'});
-					setTimeout(() => uni.showLoading({title:'正在发送...'}), 1500);
-					return ;
-				}
-				
-				this.request = 0;
-				uni.showLoading({title:'正在发送...'});
-				
-				send_message_ai(content).then(res => {
-					console.log('send: res: ',res);
-					this.aiSuggest = res;
-					uni.hideLoading();
-					this.request = 1;
-					
-					let img_process_history = uni.getStorageSync('img_process_history');
-					img_process_history[this.timestamp]['ai_suggest'] = res;
-					uni.setStorageSync('img_process_history', img_process_history);
-				}).catch(err => {
-					console.log('send: err: ',err);
-					uni.hideLoading();
-					uni.showToast({duration:1500,icon:'error',title: '获得失败'});
-					this.request = 1;
-				});
-			},
-			// 删除图片
-			deletePic(event) {
-				uni.showModal({
-					title: '提示',
-					content: '确定要删除这个照片吗?',
-					cancelText: '再看看',
-					confirmText: '删除',
-					success: res => {
-						if (res.confirm) {
-							this[`fileList${event.name}`].splice(event.index, 1)
-						}
-					}
-				})
-			}, 
-			// 新增图片
-			async afterRead(event) {
-				// 当设置 multiple 为 true 时, file 为数组格式,否则为对象格式
-				let lists = [].concat(event.file)
-				let fileListLen = this[`fileList${event.name}`].length
-				lists.map((item) => {
-					this[`fileList${event.name}`].push({
-						...item,
-						status: 'uploading',
-						message: '上传中'
-					})
-				})
-				for (let i = 0; i < lists.length; i++) {
-					const result = '';
-					let item = this[`fileList${event.name}`][fileListLen]
-					this[`fileList${event.name}`].splice(fileListLen, 1, Object.assign(item, {
-						status: 'success',
-						message: '',
-						url: result
-					}))
-					fileListLen++;
-				}
-			},
-			tip(){
-				uni.showToast({
-					icon:"error",
-					title: '图片不能超过2M',
-				})
-			},
-			processImg() {
-				if(!this.request){
-					uni.showToast({
-						icon:"none",
-						title: '图片处理中,请稍后...',
-					});
-				}
-				else if(!this.fileList1.length){
-					uni.showToast({
-						icon:"error",
-						title: '请选择图片',
-					})
-				}
-				else{
-					this.request = 0;
-					uni.showLoading({title: '图片处理中...'});
-					img_api(this.fileList1[0]['thumb']).then(res => {
-						console.info('processImg: res: ',res);
-						this.request = 1;
-						this.resInfo = res;
-						this.img_url = 'data:image/png;base64,' + res['data']['image'];
-						uni.hideLoading();
-						uni.showToast({icon:'success',title: '处理成功',duration:1500});
-						
-						let img_process_history = uni.getStorageSync('img_process_history');
-						const timestamp = Date.now();
-						this.timestamp = timestamp;
-						
-						if(!img_process_history || Object.keys(img_process_history).length === 0) img_process_history = {};
-						
-						img_process_history[timestamp] = {disease: res['data']['disease'], time_string: this.$util.formatDateTime(timestamp)};
-						
-						uni.setStorageSync('img_process_history', img_process_history);
-						
-					}).catch(err => {
-						this.request = 1;
-						this.resInfo = err;
-						
-						console.error('processImg: err: ',err);
-						uni.hideLoading();
-						if(err === 'img_api: 没有token') uni.showToast({icon:'error',title: '未登陆',duration:1500});
-						else uni.showToast({icon:'error',title: '处理失败',duration:1500});
-					});
-				}
-			}
-		}
-	}
-</script>
-
-<style lang="less">
-	image{
-		height: 100px;
-		width: 100px;
-		background-color: #fff;
-	}
-	.history{
-		position: fixed;
-		top: 10px;
-		right: 20px;
-		background-color: rgba(0, 0, 0, 0);
-		font-size: 12px;
-		color: #666;
-		
-		image{
-			width: 35px;
-			height: 35px;
-			background-color: rgba(0, 0, 0, 0);
-		}
-	}
-	.history:active{
-		opacity: 0.6;
-	}
-	.content{
-		padding:20px;
-	}
-	.arrow{
-		height: 30px;
-		width: 30px;
-		margin: 20px;
-	}
-	.title{
-		color: #777;
-		font-size: 20px;
-		font-weight: 600;
-	}
-	.diseaseItem{
-		background-color: #f1f3f2;
-		border-radius: 30px;
-		padding: 20px;
-		color: #333;
-		letter-spacing: 1px;
-		margin-bottom: 20px;
-		
-		> text{
-			color: #000;
-			font-size: 18px;
-			font-weight: 600;
-		}
-		
-		> view{
-			border-radius: 20px;
-			margin: 15px 0 5px;
-			background-color: #ffffff;
-			border-radius: 20px;
-			padding: 15px;
-			
-			> text{
-				font-size: 16px;
-				color: #222;
-			}
-			view{
-				padding: 1px 0;
-				text{
-					font-size: 15px;
-				}
-			}
-		}
-	}
-	
-	.aiSuggest{
-		background-color: #f1f3f2;
-		border: none;
-		border-radius: 30px;
-		padding: 20px;
-		letter-spacing: 1px;
-		margin-bottom: 20px;
-		font-weight: 1000;
-		color: #666;
-		font-size: 28px;
-		
-		> text{
-			color: #000;
-			font-size: 18px;
-			font-weight: 600;
-		}
-		
-		> view{
-			border-radius: 20px;
-			margin: 10px 0;
-			background-color: #ffffff;
-			border-radius: 20px;
-			padding: 15px;
-			
-			> text{
-				font-size: 15px;
-			}
-		}
-	}
+<script>
+	import img_api from '@/data/img_api';
+	import send_message_ai from "@/data/medical_ai.js"
+	import disease_suggest from '@/static/disease_suggest.js'
+	
+	export default {
+		onLoad(options) {
+			// 获取传递的对象参数,使用decodeURIComponent解码,并转为对象
+			if ('timestamp' in options) {
+				this.process_show = false;
+				
+				let timestamp = JSON.parse(decodeURIComponent(options.timestamp));
+				this.timestamp = timestamp;
+				console.log('timestamp: ',timestamp);
+				
+				let img_process_history = uni.getStorageSync('img_process_history');
+				
+				let img_process = img_process_history[timestamp];
+				let resInfo = {code: 1, data: {disease: img_process['disease']}};
+				
+				for (let disease of img_process['disease']) {
+					resInfo['data'][disease] = disease_suggest[disease];
+				}
+				this.resInfo = resInfo;
+				
+				this.timeString = img_process['time_string'];
+				
+				if(img_process['ai_suggest']) this.aiSuggest = img_process['ai_suggest'];
+				
+			}
+		},
+		data() {
+			return {
+				process_show: true,
+				uploading: false,
+				style:null,
+				fileList1: [],
+				resInfo: null,
+				img_url: null,
+				aiSuggest: null,
+				timestamp: null,
+				timeString: null,
+				
+				request: 1,
+				processed: false
+			}
+		},
+		methods: {
+			getAiSuggest(){
+				let disease = '';
+				for (let value of this.resInfo['data']['disease']) disease += '、' + value;
+				
+				// 去掉第一个多余的 "、"
+				disease = disease.substring(1);
+				
+				const content = '通过x胸腔图,已知患者有' + disease + "症状,请给出一些建议";
+				console.log('content: ',content);
+				
+				if(this.request == 0){
+					uni.showToast({duration:1000,icon:'none',title: '消息发送中,请稍等...'});
+					setTimeout(() => uni.showLoading({title:'正在发送...'}), 1500);
+					return ;
+				}
+				
+				this.request = 0;
+				uni.showLoading({title:'正在发送...'});
+				
+				send_message_ai(content).then(res => {
+					console.log('send: res: ',res);
+					this.aiSuggest = res;
+					uni.hideLoading();
+					this.request = 1;
+					
+					let img_process_history = uni.getStorageSync('img_process_history');
+					img_process_history[this.timestamp]['ai_suggest'] = res;
+					uni.setStorageSync('img_process_history', img_process_history);
+				}).catch(err => {
+					console.log('send: err: ',err);
+					uni.hideLoading();
+					uni.showToast({duration:1500,icon:'error',title: '获得失败'});
+					this.request = 1;
+				});
+			},
+			// 删除图片
+			deletePic(event) {
+				uni.showModal({
+					title: '提示',
+					content: '确定要删除这个照片吗?',
+					cancelText: '再看看',
+					confirmText: '删除',
+					success: res => {
+						if (res.confirm) {
+							this[`fileList${event.name}`].splice(event.index, 1);
+							this.processed = false;
+							this.aiSuggest = null;
+							this.resInfo = null;
+							this.img_url = null;
+						}
+					}
+				})
+			}, 
+			// 新增图片
+			async afterRead(event) {
+				// 当设置 multiple 为 true 时, file 为数组格式,否则为对象格式
+				let lists = [].concat(event.file)
+				let fileListLen = this[`fileList${event.name}`].length
+				lists.map((item) => {
+					this[`fileList${event.name}`].push({
+						...item,
+						status: 'uploading',
+						message: '上传中'
+					})
+				});
+				
+				// 使用 Promise.all 来并行检查所有添加的文件
+				await Promise.all(lists.map(async (item, index) => {
+					let addedItem = this[`fileList${event.name}`][fileListLen + index];
+					
+					let isEnable = await this.$content_check.imgCheck(addedItem.thumb);
+					console.log('isEnable: ',isEnable);
+					
+					if(isEnable === true) {
+						addedItem.status = 'success';
+						addedItem.message = '';
+					}
+					else{
+						let indexToRemove = this[`fileList${event.name}`].findIndex(file => file === addedItem);
+						if (indexToRemove !== -1) {
+							this[`fileList${event.name}`].splice(indexToRemove, 1);
+						}
+					}
+				}));
+			},
+			
+			tip(){
+				uni.showToast({
+					icon:"error",
+					title: '图片不能超过1MB',
+				})
+			},
+			processImg() {
+				if(!this.fileList1.length){
+					uni.showToast({
+						icon:"error",
+						title: '请选择图片',
+					})
+				}
+				
+				if(!this.request || this.fileList1[0]['status'] != 'success'){
+					uni.showToast({
+						icon:"none",
+						title: '图片处理中,请稍后...',
+					});
+					
+					return ;
+				}
+				
+				this.request = 0;
+				uni.showLoading({title: '图片处理中...'});
+				img_api(this.fileList1[0]['thumb']).then(res => {
+					console.info('processImg: res: ',res);
+					this.request = 1;
+					this.processed = true;
+					this.resInfo = res;
+					this.img_url = 'data:image/png;base64,' + res['data']['image'];
+					uni.hideLoading();
+					uni.showToast({icon:'success',title: '处理成功',duration:1500});
+					
+					let img_process_history = uni.getStorageSync('img_process_history');
+					const timestamp = Date.now();
+					this.timestamp = timestamp;
+					
+					if(!img_process_history || Object.keys(img_process_history).length === 0) img_process_history = {};
+					
+					img_process_history[timestamp] = {disease: res['data']['disease'], time_string: this.$util.formatDateTime(timestamp)};
+					
+					uni.setStorageSync('img_process_history', img_process_history);
+					
+				}).catch(err => {
+					this.request = 1;
+					this.resInfo = err;
+					
+					console.error('processImg: err: ',err);
+					uni.hideLoading();
+					if(err === 'img_api: 没有token') uni.showToast({icon:'error',title: '未登陆',duration:1500});
+					else uni.showToast({icon:'error',title: '处理失败',duration:1500});
+				});
+			}
+		}
+	}
+</script>
+
+<style lang="less">
+	image{
+		height: 100px;
+		width: 100px;
+		background-color: #fff;
+	}
+	.history{
+		position: fixed;
+		top: 10px;
+		right: 20px;
+		background-color: rgba(0, 0, 0, 0);
+		font-size: 12px;
+		color: #666;
+		
+		image{
+			width: 35px;
+			height: 35px;
+			background-color: rgba(0, 0, 0, 0);
+		}
+	}
+	.history:active{
+		opacity: 0.6;
+	}
+	.content{
+		padding:20px;
+	}
+	.arrow{
+		height: 30px;
+		width: 30px;
+		margin: 20px;
+	}
+	.title{
+		color: #777;
+		font-size: 20px;
+		font-weight: 600;
+	}
+	.diseaseItem{
+		background-color: #f1f3f2;
+		border-radius: 30px;
+		padding: 20px;
+		color: #333;
+		letter-spacing: 1px;
+		margin-bottom: 20px;
+		
+		> text{
+			color: #000;
+			font-size: 18px;
+			font-weight: 600;
+		}
+		
+		> view{
+			border-radius: 20px;
+			margin: 15px 0 5px;
+			background-color: #ffffff;
+			border-radius: 20px;
+			padding: 15px;
+			
+			> text{
+				font-size: 16px;
+				color: #222;
+			}
+			view{
+				padding: 1px 0;
+				text{
+					font-size: 15px;
+				}
+			}
+		}
+	}
+	
+	.aiSuggest{
+		background-color: #f1f3f2;
+		border: none;
+		border-radius: 30px;
+		padding: 20px;
+		letter-spacing: 1px;
+		margin-bottom: 20px;
+		font-weight: 1000;
+		color: #666;
+		font-size: 28px;
+		
+		> text{
+			color: #000;
+			font-size: 18px;
+			font-weight: 600;
+		}
+		
+		> view{
+			border-radius: 20px;
+			margin: 10px 0;
+			background-color: #ffffff;
+			border-radius: 20px;
+			padding: 15px;
+			
+			> text{
+				font-size: 15px;
+			}
+		}
+	}
 </style>

+ 181 - 181
项目/img_process_history.vue → chexnet-master-MP/pages/img_process/img_process_history/img_process_history.vue

@@ -1,201 +1,201 @@
 <template>
-	<view class="content">
-		<view v-if="!img_process_history || Object.keys(img_process_history).length === 0" class="no_data"></view>
-		<view v-else v-for="(value, key) in img_process_history" :key="key" class="img-history-item">
-			<view @longpress="check_del_img_history(key)" @click="$util.navigateTo(`/pages/img_process/img_process?timestamp=${key}`)" class="flex justify-between">
-				<view>
-					<view class="flex justify-start margin-bottom-xs">
-						<text class="title center">检测结果:</text>
-						<text class="result center" v-for="(value, key) in value['disease']">{{value}}</text>
-					</view>
-					
-					<view class="time align-center">{{value['time_string']}}</view>
-				</view>
-				
-				<view @click.stop="openpopup(key)" class="center relationship">
-					<text v-if="value['relationship']">{{family_list[value['relationship']]['name']}}</text>
-					<text v-else style="text-decoration: underline; color: #666;">关联成员</text>
-				</view>
-			</view>
-		</view>
-		<uv-popup ref="popup" mode="right" bgColor="#f5f5f5" style="overflow-y: scroll;" >
-			<scroll-view class="popup scroll-Y" scroll-y="true">
-				<view v-if="!family_list" class="no-data center">暂无成员</view>
-				<view v-else v-for="(value, key) in family_list" :key="key" class="family-item">
-					<view @click="addrelationship(key)" class="flex justify-between">
-						<view>
-							<view class="flex justify-start margin-bottom-xs">
-								<text class="name center">{{value['name']}}</text>
-								<text v-if="value['relationship']" class="relationship center">{{value['relationship']}}</text>
-							</view>
-							
-							<text class="gender">性别:{{ value['gender'] === 0 ? '男' : (value['gender'] === 1 ? '女' : '未知') }}</text>
-							
-							<text v-if="value['age']" class="age">年龄:{{value['age']}}岁</text>
-						</view>
-						<text class="cuIcon-right center"></text>
-					</view>
-				</view>
-			</scroll-view>
+	<view class="content">
+		<view v-if="!img_process_history || Object.keys(img_process_history).length === 0" class="no_data"></view>
+		<view v-else v-for="(value, key) in img_process_history" :key="key" class="img-history-item">
+			<view @longpress="check_del_img_history(key)" @click="$util.navigateTo(`/pages/img_process/img_process?timestamp=${key}`)" class="flex justify-between">
+				<view>
+					<view class="flex justify-start margin-bottom-xs">
+						<text class="title center">检测结果:</text>
+						<text class="result center" v-for="(value, key) in value['disease']">{{value}}</text>
+					</view>
+					
+					<view class="time align-center">{{value['time_string']}}</view>
+				</view>
+				
+				<view @click.stop="openpopup(key)" class="center relationship">
+					<text v-if="value['relationship']">{{family_list[value['relationship']]['name']}}</text>
+					<text v-else style="text-decoration: underline; color: #666;">关联成员</text>
+				</view>
+			</view>
+		</view>
+		<uv-popup ref="popup" mode="right" bgColor="#f5f5f5" style="overflow-y: scroll;" >
+			<scroll-view class="popup scroll-Y" scroll-y="true">
+				<view v-if="!family_list" class="no-data center">暂无成员</view>
+				<view v-else v-for="(value, key) in family_list" :key="key" class="family-item">
+					<view @click="addrelationship(key)" class="flex justify-between">
+						<view>
+							<view class="flex justify-start margin-bottom-xs">
+								<text class="name center">{{value['name']}}</text>
+								<text v-if="value['relationship']" class="relationship center">{{value['relationship']}}</text>
+							</view>
+							
+							<text class="gender">性别:{{ value['gender'] === 0 ? '男' : (value['gender'] === 1 ? '女' : '未知') }}</text>
+							
+							<text v-if="value['age']" class="age">年龄:{{value['age']}}岁</text>
+						</view>
+						<text class="cuIcon-right center"></text>
+					</view>
+				</view>
+			</scroll-view>
 		</uv-popup>
 	</view>
 </template>
 
 <script>
-	export default {
-		onShow() {
-			let img_process_history = uni.getStorageSync('img_process_history');
-			this.img_process_history = this.$util.reversedObject(img_process_history);
-			
-			this.family_list = uni.getStorageSync('family_list');
-		},
-		onPullDownRefresh() {
-			let img_process_history = uni.getStorageSync('img_process_history');
-			this.img_process_history = this.$util.reversedObject(img_process_history);
-			
-			this.family_list = uni.getStorageSync('family_list');
+	export default {
+		onShow() {
+			let img_process_history = uni.getStorageSync('img_process_history');
+			this.img_process_history = this.$util.reversedObject(img_process_history);
+			
+			this.family_list = uni.getStorageSync('family_list');
+		},
+		onPullDownRefresh() {
+			let img_process_history = uni.getStorageSync('img_process_history');
+			this.img_process_history = this.$util.reversedObject(img_process_history);
+			
+			this.family_list = uni.getStorageSync('family_list');
 		},
 		data() {
 			return {
-				img_process_history: null,
-				family_list: null,
+				img_process_history: null,
+				family_list: null,
 				selectitem: null,
 			}
 		},
-		methods: {
-			addrelationship(key){
-				let img_process_history = uni.getStorageSync('img_process_history');
-				
-				img_process_history[this.selectitem]['relationship'] = key;
-				
-				uni.setStorageSync('img_process_history', img_process_history);
-				this.img_process_history = this.$util.reversedObject(img_process_history);
-				
-				this.$refs.popup.close();
-			},
-			openpopup(selectitem){
-				this.selectitem = selectitem;
-				this.$refs.popup.open();
+		methods: {
+			addrelationship(key){
+				let img_process_history = uni.getStorageSync('img_process_history');
+				
+				img_process_history[this.selectitem]['relationship'] = key;
+				
+				uni.setStorageSync('img_process_history', img_process_history);
+				this.img_process_history = this.$util.reversedObject(img_process_history);
+				
+				this.$refs.popup.close();
+			},
+			openpopup(selectitem){
+				this.selectitem = selectitem;
+				this.$refs.popup.open();
 			},
-			check_del_img_history(key){
-				uni.showModal({
-					title: '删除记录',
-					content: ' 是否要删除此记录?',
-					success: (res) => { 
-						if (res.confirm) this.del_img_history(key); 
-					}  
-				})	
-			},
-			del_img_history(key){
-				let img_process_history = uni.getStorageSync('img_process_history');
-				delete img_process_history[key];
-				
-				this.img_process_history = this.$util.reversedObject(img_process_history);
-				uni.setStorageSync('img_process_history', img_process_history);
-				
-				this.$forceUpdate();
+			check_del_img_history(key){
+				uni.showModal({
+					title: '删除记录',
+					content: ' 是否要删除此记录?',
+					success: (res) => { 
+						if (res.confirm) this.del_img_history(key); 
+					}  
+				})	
+			},
+			del_img_history(key){
+				let img_process_history = uni.getStorageSync('img_process_history');
+				delete img_process_history[key];
+				
+				this.img_process_history = this.$util.reversedObject(img_process_history);
+				uni.setStorageSync('img_process_history', img_process_history);
+				
+				this.$forceUpdate();
 			},
 		}
 	}
 </script>
 
-<style lang="less" scoped>
-	.popup{
-		padding: 10px;
-		width: 75vw;
-		
-		.family-item{
-			background-color: #fff;
-			border-radius: 20px;
-			height: 80px;
-			margin-bottom: 10px;
-			padding: 10px 18px;
-			
-			color: #666;
-			letter-spacing: 1px;
-			
-			.name{
-				letter-spacing: 2px;
-				font-size: 20px;
-				font-weight: 550;
-				color: #555;
-			}
-			
-			.gender{
-				margin-right: 20px;
-			}
-			
-			.relationship{
-				color: #555;
-				font-size: 16px;
-				height: 24px;
-				width: 60px;
-				border-radius: 10px;
-				letter-spacing: 0px;
-				margin-left: 20px;
-				background-color: #ebf6ff;
-			}
-		}
-	}
-	.no-data{
-		height: 30vw;
-		font-size: 24px;
-		color: #777;
-		padding: 10px;
-		background-color: #fff;
-		border-radius: 20px;
-	}
-	.content{
-		padding: 10px 20px;
-		border: none;
-		background-color: #f6f6f6;
-		height: 100vh;
-	}
-	.scroll-Y {
-		/* #ifndef MP */
-		height: calc(100vh - 155px);
-		/* #endif */
-		
-		/* #ifdef MP */
-		height: calc(100vh - 65px);
-		/* #endif */
-	}
-	.relationship{
-		z-index: 100;
-		color: #666;
-		font-size: 16px;
-		margin: 5px 0;
-		width: 85px;
-		border-radius: 10px;
-		letter-spacing: 0px;
-		background-color: #ebf6ff;
-	}
-	.img-history-item{
-		background-color: #fff;
-		border-radius: 20px;
-		height: 80px;
-		margin-bottom: 20px;
-		padding: 15px;
-		
-		color: #666;
-		letter-spacing: 1px;
-		
-		.title{
-			letter-spacing: 2px;
-			font-size: 18px;
-			font-weight: 550;
-			color: #555;
-		}
-		
-		.result{
-			font-size: 17px;
-			font-weight: 550;
-			color: #f56c6c;
-			margin-right: 10px;
-		}
-		
-		.time{
-			margin-top: 8px;
-		}
+<style lang="less" scoped>
+	.popup{
+		padding: 10px;
+		width: 75vw;
+		
+		.family-item{
+			background-color: #fff;
+			border-radius: 20px;
+			height: 80px;
+			margin-bottom: 10px;
+			padding: 10px 18px;
+			
+			color: #666;
+			letter-spacing: 1px;
+			
+			.name{
+				letter-spacing: 2px;
+				font-size: 20px;
+				font-weight: 550;
+				color: #555;
+			}
+			
+			.gender{
+				margin-right: 20px;
+			}
+			
+			.relationship{
+				color: #555;
+				font-size: 16px;
+				height: 24px;
+				width: 60px;
+				border-radius: 10px;
+				letter-spacing: 0px;
+				margin-left: 20px;
+				background-color: #ebf6ff;
+			}
+		}
+	}
+	.no-data{
+		height: 30vw;
+		font-size: 24px;
+		color: #777;
+		padding: 10px;
+		background-color: #fff;
+		border-radius: 20px;
+	}
+	.content{
+		padding: 10px 20px;
+		border: none;
+		background-color: #f6f6f6;
+		height: 100vh;
+	}
+	.scroll-Y {
+		/* #ifndef MP */
+		height: calc(100vh - 155px);
+		/* #endif */
+		
+		/* #ifdef MP */
+		height: calc(100vh - 65px);
+		/* #endif */
+	}
+	.relationship{
+		z-index: 100;
+		color: #666;
+		font-size: 16px;
+		margin: 5px 0;
+		width: 85px;
+		border-radius: 10px;
+		letter-spacing: 0px;
+		background-color: #ebf6ff;
+	}
+	.img-history-item{
+		background-color: #fff;
+		border-radius: 20px;
+		height: 80px;
+		margin-bottom: 20px;
+		padding: 15px;
+		
+		color: #666;
+		letter-spacing: 1px;
+		
+		.title{
+			letter-spacing: 2px;
+			font-size: 18px;
+			font-weight: 550;
+			color: #555;
+		}
+		
+		.result{
+			font-size: 17px;
+			font-weight: 550;
+			color: #f56c6c;
+			margin-right: 10px;
+		}
+		
+		.time{
+			margin-top: 8px;
+		}
 	}
 </style>

+ 323 - 0
chexnet-master-MP/pages/index/index.vue

@@ -0,0 +1,323 @@
+<template>
+	<view>
+		
+		<image class="top-img" src="@/static/img/lbt1.jpg"></image>
+		
+		<view class="container">
+			<!-- <view class="search-bar">
+				<input type="text" placeholder="请输入关键词搜索" />
+			</view> -->
+			<view @click="to_ai()" class="large-button button-all" style="background-color: #d4f4c2;"><!-- blanchedalmond -->
+				<view class="align-center flex justify-between">
+					<image style="height: 22vw; width: 22vw; margin-left: -3vw;" src="@/static/img/ai-talk.png"></image>
+					<p style="color: #5c6a7c; font-size: 30px; margin-left: 4vw;">AI 问诊</p>
+				</view>
+			</view>
+			
+			<view @click="$util.navigateTo('/pages/img_process/img_process')" class="large-button button-all" style="background-color: blanchedalmond;"><!-- blanchedalmond -->
+				<view class="align-center flex justify-between">
+					<image style="height: 20vw; width: 20vw; margin-left: 2vw;" src="@/static/img/识别.png"></image>
+					<p style="color: #5c6a7c; font-size: 28px; margin-left: 4vw;">病例识别</p>
+				</view>
+			</view>
+			
+			<view @click="$util.navigateTo('/pages/health_tip/health_tip')" class="large-button button-all" style="background-color: aliceblue;">
+				<view class="align-center flex justify-between">
+					<image style="height: 22vw; width: 22vw; margin-left: 5vw;" src="@/static/img/health-tip.png"></image>
+					<p style="color: #5c6a7c; font-size: 26px; margin-left: 4vw;">健康小知识</p>
+				</view>
+			</view>
+			
+			<view @click="$util.navigateTo('/pages/BMI/BMI')" class="large-button button-all" style="background-color: #fff1f1;"> <!-- #fffae8 #fff0ff-->
+				<view class="align-center flex justify-between">
+					<image style="height: 22vw; width: 22vw; margin-left: 0vw;" src="@/static/img/bmi.png"></image>
+					<p style="color: #5c6a7c; font-size: 30px; margin-left: 10vw;">B M I</p>
+				</view>
+			</view>
+			
+			<uv-gap height="20vw"></uv-gap>
+			
+			<view v-if="windowHeight">
+				<image @click="to_ai()" :style="{top:`calc(${windowHeight}px - 30vw)`}" class="doctor" :src="doctor_gender == 1 ? doctor_woman : doctor_man"></image>
+				<view v-if="isTalking" :style="{ width: `${talkWidth}`, top:`calc(${windowHeight}px - 18vw)` }" class="talk">{{message}}</view>
+			</view>
+			<!-- <uv-button @click="$util.navigateTo('/pages/test/test')"></uv-button> -->
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		onShow() {
+			let doctor_gender = uni.getStorageSync('doctor_gender');
+			if(doctor_gender === 0) this.doctor_gender = 0;
+			else this.doctor_gender = 1;
+			console.log('doctor_gender: ',this.doctor_gender);
+			
+		},
+		onReady() {
+			const windowHeight = uni.getSystemInfoSync().windowHeight - getBottomBarHeight();
+			this.windowHeight = windowHeight;
+			console.log('windowHeight-init: ',windowHeight);
+			
+			function getBottomBarHeight() {
+				let res = uni.getSystemInfoSync();
+				const windowHeight = res.windowHeight; // 屏幕可用高度
+				const screenHeight = res.screenHeight; // 屏幕总高度
+				
+				// 状态栏高度
+				const statusBarHeight = res.statusBarHeight
+				
+				// #ifdef MP-WEIXIN
+				// 获取微信胶囊的位置信息 width,height,top,right,left,bottom
+				const custom = wx.getMenuButtonBoundingClientRect()
+				// console.log(custom)
+				
+				// 导航栏高度(标题栏高度) = 胶囊高度 + (顶部距离 - 状态栏高度) * 2
+				const navigationBarHeight = custom.height + (custom.top - statusBarHeight) * 2
+				// console.log("导航栏高度:"+navigationBarHeight)
+				
+				// 总体高度 = 状态栏高度 + 导航栏高度
+				const navHeight = navigationBarHeight + statusBarHeight
+				
+				// #endif
+				
+				let bottomBarHeight = screenHeight - windowHeight - navHeight;
+				bottomBarHeight = bottomBarHeight > 0 ? bottomBarHeight : 0;
+				
+				console.log('Bottom Bar Height:',bottomBarHeight);
+				
+				return bottomBarHeight;
+			}
+		},
+		onLoad() {
+			this.$user_api.check_login();
+		},
+		mounted(){
+			this.talk();
+		},
+		onPullDownRefresh() {
+			setTimeout(() => {uni.stopPullDownRefresh();}, 1000);
+		},
+		data() {
+			return {
+				windowHeight: null,
+				
+				doctor_woman: '/static/img/女医生.png',
+				doctor_man: '/static/img/男医生.png',
+				doctor_gender: 1,
+				name: null,
+				time: 1,
+				message: '您好 !',
+				message_num: 0,
+				talkWidth: '30vw',
+				isTalking: true
+				// list: [
+				// 	'../../static/img/lbt1.jpg',
+				// 	'../../static/img/lbt2.jpg',
+				// 	'../../static/img/lbt3.jpg'
+				// ]
+			};
+		},
+		methods: {
+			to_ai(){
+				uni.switchTab({
+					url: '/pages/ai/ai'
+				})
+			},
+			to_home(){
+				uni.switchTab({
+					url: '/pages/my/my'
+				})
+			},
+			talk(){
+				return new Promise((resolve, reject) => {
+					if(this.message_num < 6){
+						if(this.message_num == 1) {
+							this.talkWidth = '30vw';
+							const hour = new Date().getHours();
+							console.log('hour: ',hour);
+							if (hour >= 4 && hour < 8) {
+								this.message = '早上好 !'; // 早上
+							} else if (hour >= 8 && hour < 12) {
+								this.message = '上午好 !'; // 上午
+							} else if (hour >= 12 && hour < 18) {
+								this.message = '下午好 !'; // 下午
+							} else {
+								this.message = '晚上好 !'; // 晚上
+							}
+							this.applyAnimation();
+						}
+						if(this.message_num == 2) {
+							this.talkWidth = '60vw';
+							this.message = '希望您今天过得愉快。';
+							this.applyAnimation();
+						}
+						if(this.message_num == 3) {
+							this.talkWidth = '65vw';
+							this.message = '有什么我可以帮您的吗?';
+							this.applyAnimation();
+						}
+						if(this.message_num == 4) {
+							this.talkWidth = '60vw';
+							this.message = '注意休息,保持健康。';
+							this.applyAnimation();
+						}
+						
+						this.message_num = this.message_num + 1;
+						// console.log('this.message_num: ',this.message_num);
+						setTimeout(() => this.talk(), 3000);
+					}
+					else{
+						this.message_num = 0;
+						setTimeout(() => this.talk(), 3000);
+					}
+				})
+			},
+			applyAnimation() {
+				this.isTalking = false;
+				setTimeout(() => this.isTalking = true, 100);
+			},
+		}
+	};
+</script>
+
+<style>
+	page {
+		height: 100vh;
+		background-color: #fff;
+	}
+	
+	.container {
+		margin: 0 20px;
+		display: flex;
+		flex-direction: column;
+		height: 80vh;
+	}
+
+	.header-index {
+	  width: 100%;
+	  padding: 10px;
+	  display: flex;
+	  justify-content: space-between;
+	  align-items: center;
+	  color: #ee4863;
+	  font-weight: 1000;
+	  backdrop-filter: blur(10px); /* 高斯模糊效果 */
+	  background-color: #f8df72; /* 半透明背景 */
+	  border: none;
+	}
+
+	.header-title {
+	  font-size: 20px;
+	}
+
+	.header-login {
+	  color: white;
+	  text-decoration: none;
+	  font-size: 16px;
+	}
+
+	.search-bar {
+	  width: 100%;
+	}
+
+	.search-bar input {
+	  font-size: 16px;
+	  width: 100%;
+	  height: 5vh;
+	  padding: 10px;
+	  box-sizing: border-box;
+	  border-radius: 5px;
+	}
+
+	.button-group {
+	  font-weight: 1000;
+	  margin-top: 20px;
+	  width: 100%;
+	  display: flex;
+	  justify-content: space-between;
+	  gap: 20px;
+	}
+
+	.square-button {
+	  width: 100%;
+	  height: 18vh;
+	  line-height: 100px;
+	  text-align: center;
+	  color: #5bae23;
+	  border: none;
+	  border-radius: 5px;
+	}
+
+	.large-button {
+		color: #fff;
+		z-index: 0;
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		width: 100%;
+		height: 40vw;
+		margin-top: 20px;
+		width: 100%;
+		font-weight: 1000;
+	}
+	
+	.doctor{
+		height: 30vw;
+		width: 30vw;
+		margin-top: auto;
+		position: fixed;
+		left: -2vw;
+	}
+	
+	.talk {
+		position: fixed;
+		left: 30vw;
+		width: 30vw;
+		height: 40px;
+		color: #5c6a7c;
+		font-size: 20px;
+		font-weight: 1000;
+		border-radius: 8px;
+		background-color: #ffece3;
+		display: flex; /* 使用flex布局来实现居中 */
+		justify-content: center; /* 水平居中 */
+		align-items: center; /* 垂直居中 */
+	}
+	
+	.talk::before {
+		content: "";
+		position: absolute;
+		top: 50%; /* 三角形位于元素下方 */
+		right: 100%; /* 水平居中 */
+		width: 0;
+		height: 0;
+		border-top: 6px solid transparent;
+		border-bottom: 6px solid transparent;
+		border-right: 10px solid #ffece3; /* 使用变量 */
+		transform: translateY(-50%); /* 确保三角形的中心点位于 .active 元素的中心 */
+	}
+	
+	.talk {
+	    opacity: 0; /* 初始透明度 */
+		animation: fadeInLeft 1s ease-in-out forwards;
+	}
+	
+	/* 箭头淡入动画 */
+	@keyframes fadeInLeft {
+		0% {
+			opacity: 0;
+			transform: translateX(100%);
+		}
+		100% {
+			opacity: 1;
+			transform: translateX(0);
+		}
+	}
+
+</style>
+
+
+
+

+ 225 - 0
chexnet-master-MP/pages/login/login.vue

@@ -0,0 +1,225 @@
+<template>
+	<view>
+		<view v-if="register" style="font-size: 15px; font-weight:550;">
+			<view class="top-back"></view>
+			<view class="container">
+				<view class="content">	
+					<view class="margin-top" >
+						<uv-gap height="10" ></uv-gap>
+						
+						<!-- <p style="font-size: 25px;">个人信息</p> -->
+						<uv-divider class="head-text" text="个人资料" textSize="26" textColor="#000"></uv-divider>
+						
+						<uv-gap height="10" ></uv-gap>
+						
+						<view class="margin-top"><!-- 昵称 -->
+							<view class="title">昵称</view>
+							<uv-gap height="10" ></uv-gap>
+							
+							<view class="flex justify-between">
+								<input maxlength=20 class="" type="nickname" style="width: 60%;" v-model="user.name" 
+									placeholder-style="color: #cccccc; font-size: 24px; font-weight: 100;" 
+									placeholder="请输入昵称"
+								/>
+								<image src="../../static/img/user.png" style="background-color: #fff;width: 15vw;height: 15vw;margin: 0 30px 5px;"></image>
+							</view>
+						</view>
+						<uv-divider customStyle="margin: 0 0"></uv-divider>
+						
+						<uv-gap height="20" ></uv-gap>
+						
+						<view><!-- 性别 -->
+							<view class="">
+								<view class="title">性别</view>
+								<uv-gap height="10" ></uv-gap>
+								<view class="flex justify-between">
+									<input placeholder="请选择性别" disabled="disabled" style="width: 60%;" name="input" v-model="gender" placeholder-style="color: #cccccc; font-size: 24px; font-weight: 100;" @click="changegender"></input>
+									<image @click="confirm({'indexs': [0]})" src="../../static/img/man.png" style="background-color: #fff;width: 15vw;height: 15vw;"></image>
+									<image @click="confirm({'indexs': [1]})" src="../../static/img/woman.png" style="background-color: #fff;width: 15vw;height: 15vw;"></image>
+								</view>
+							</view>
+							<uv-picker ref="picker" :columns="columns" @confirm="confirm"></uv-picker>
+						</view>
+						<uv-divider customStyle="margin: 0 0"></uv-divider>
+						
+						<uv-gap height="20" ></uv-gap>
+						
+						<view class="" style="margin-bottom: -20upx;"><!-- 年龄 -->
+							<view class="title">年龄</view>
+							<uv-gap height="10" ></uv-gap>
+							<input maxlength=3 class="weui-input" type="number" v-model="user.age" placeholder-style="color: #cccccc; font-size: 24px; font-weight: 100;" placeholder="请输入年龄" />
+						</view>
+						<uv-divider customStyle="margin: 0 0"></uv-divider>
+						
+						<uv-gap height="10" ></uv-gap>
+						
+					</view>	
+					<uv-modal width=260 ref="modal" title="请将信息填写完整"  @confirm="confirm"></uv-modal>				
+				</view>
+				
+				<uv-button @click="update()" customStyle="position: fixed; bottom: 40px;width: calc(100vw - 30px);" shape="circle" size="large" color="#1296db">保存</uv-button>
+			</view>
+		</view>
+		<view v-else>
+			<button style="background-color:#07c160; width: 65%; color: white; font-size: 20px; font-weight:550;
+			margin-top: 50%;" @click="login()">
+			<text style="font-size: 23px; padding-right: 5px;" class="cuIcon-weixin"></text>微信一键登陆
+			</button>	
+		</view>
+	</view>
+</template>
+
+<script>
+	import website from '@/data/website.js';
+	export default {
+		data() {
+			return {
+				user:{
+					name: '',
+					gender: '',
+					age: '',
+				},
+				gender: '',
+				columns: [['男','女']],
+				register:0,
+				request:1,
+			}
+		},
+		methods: {	
+			// inputBindBlur(){
+			// 	this.$content_check.textCheck(this.user.name, this, 'user.name');
+			// },
+			changegender() {
+				this.$refs.picker.open();
+			},
+			confirm(e) {
+				this.user.gender = e['indexs'][0];
+				this.gender = this.user.gender === 0 ? '男' : '女';
+			},
+			bindblur(e) {
+				console.log('获取微信昵称', e);
+				this.name = e.detail.value; // 获取微信昵称
+			},
+			login(){
+				if(this.request){
+					this.request = 0;
+					
+					uni.showLoading({title: '加载中...'});
+					console.log('this.user: ',this.user);
+					this.$user_api.user_login()
+						.then((res) => {
+							uni.hideLoading();
+							if(res == 'new'){
+								this.register = 1;
+								this.request = 1;
+							}	
+							if(res == 'old'){
+								uni.showToast({duration:1000,icon:'success',title: '登陆成功 !'});
+								setTimeout(() => {this.request = 1; uni.navigateBack();}, 1300);
+							}
+						}).catch((err) => {  
+							this.request = 1;
+							uni.hideLoading();
+							uni.showToast({icon:"error",title: '登陆失败 !'});
+						});
+				}
+			},
+			async update(){
+				if(this.user.name.length > 0){
+					//用户名必须由1到20个字符组成,并且只能包含字母和数字。
+					const regex = /^[a-zA-Z0-9\u4e00-\u9fa5]{1,20}$/;
+					const isValid = regex.test(this.user.name);
+					
+					if (isValid) {
+						console.log("用户名有效");
+						
+						if(this.request){
+							
+							let isCheck = await this.$content_check.textCheck(this.user.name, this, 'user.name');
+							console.log('isCheck: ',isCheck);
+							if(isCheck !== true) return ;
+							
+							this.request = 0;
+							uni.showLoading({title: '加载中...'});
+							this.$user_api.user_update(this.$util.removeNullValues(this.user))
+								.then((res) => {
+									uni.hideLoading();
+									uni.showToast({duration:1000,icon:'success',title: '保存成功 !'});
+									setTimeout(() => {this.request = 1; uni.navigateBack();}, 1300);
+									
+								}).catch((err) => {  
+									this.request = 1;
+									uni.hideLoading();
+									uni.showToast({icon:"error",title: '保存失败 !'});
+								});
+						}
+						
+					} else {
+						console.log("用户名无效");
+						uni.showToast({duration:1500,icon:"none",title: '用户名只能包含字母和数字'});
+					}
+				}
+				else{
+					uni.showToast({duration:1500,icon:"none",title: '请输入用户名'});
+				}
+			}
+		}
+	}
+</script>
+
+<style>
+	page {
+		height: 100vh;
+	}
+	.top-back{
+		height: 25px;
+		background-color: #1296db;
+		color: #fff;
+		margin-bottom: -5vh;
+	}
+	.top-back p{
+		height: 7.5vh;
+		display: flex; /* 使用flex布局来实现居中 */
+		justify-content: center; /* 水平居中 */
+		align-items: center; /* 垂直居中 */
+		font-size: 20px;
+		letter-spacing: 10px;
+		margin-left: 10px;
+		font-weight: 600;
+	}
+	
+	.container {
+	  padding: 0 15px;
+	  border-radius: 25px;
+	  border: none;
+	  background-color: #ffffff;
+	}
+	.head-text{
+		letter-spacing: 5px;
+		font-weight: 600;
+	}
+	.title{
+		letter-spacing: 2px;
+		color: #8c92ad;
+		font-size: 18px;
+	}
+	input {
+		letter-spacing: 1px;
+	    width: 100%;
+		height: 8vh;
+	    padding-bottom: 5px;
+	    font-size: 24px;
+		font-weight: 600;
+		font-weight: 400;
+		color: #000;
+	    outline: none; /* 移除聚焦时的默认边框 */
+	}
+	.content {
+	  padding: 0 20rpx;
+	  border-radius: 20rpx;
+	}
+	.content image:active {
+		transform: translateY(2px) translateX(2px);
+	}
+
+</style>

+ 110 - 0
chexnet-master-MP/pages/my/family/family.vue

@@ -0,0 +1,110 @@
+<template>
+	<view>
+		<view class="top-back"></view>
+		<view class="content">
+			<view v-if="!family_list || Object.keys(family_list).length === 0" class="no_data"></view>
+			<view v-else v-for="(value, key) in family_list" :key="key" class="family-item">
+				<view @longpress="check_del_family(key)" @click="$util.navigateTo(`/pages/my/family/family_info/family_info?id=${key}`)" class="flex justify-between">
+					<view>
+						<view class="flex justify-start margin-bottom-xs">
+							<text class="name center">{{value['name']}}</text>
+							<text v-if="value['relationship']" class="relationship center">{{value['relationship']}}</text>
+						</view>
+						
+						<text class="gender">性别:{{ value['gender'] === 0 ? '男' : (value['gender'] === 1 ? '女' : '未知') }}</text>
+						
+						<text v-if="value['age']" class="age">年龄:{{value['age']}}岁</text>
+					</view>
+					<text class="cuIcon-right center"></text>
+				</view>
+			</view>
+			
+			<uv-gap height="60"></uv-gap>
+			<button @click="$util.navigateTo('/pages/my/family/family_info/family_info')" class="bottom-button center">新增成员</button>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		onShow() {
+			this.family_list = uni.getStorageSync('family_list');
+		},
+		onPullDownRefresh() {
+			this.family_list = uni.getStorageSync('family_list');
+		},
+		data() {
+			return {
+				family_list: null,
+			}
+		},
+		methods: {
+			check_del_family(key){
+				uni.showModal({
+					title: '删除成员',
+					content: ' 是否要删除此成员?',
+					success: (res) => { 
+						if (res.confirm) this.del_family(key); 
+					}  
+				})	
+			},
+			del_family(key){
+				let family_list = uni.getStorageSync('family_list');
+				delete family_list[key];
+				
+				this.family_list = family_list;
+				uni.setStorageSync('family_list', family_list);
+				
+				this.$forceUpdate();
+			},
+		}
+	}
+</script>
+
+<style lang="less" scoped>
+	.family-item{
+		background-color: #fff;
+		border-radius: 20px;
+		height: 80px;
+		margin-top: 20px;
+		padding: 15px;
+		
+		color: #666;
+		letter-spacing: 1px;
+		
+		.name{
+			letter-spacing: 2px;
+			font-size: 20px;
+			font-weight: 550;
+			color: #555;
+		}
+		
+		.gender{
+			margin-right: 20px;
+		}
+		
+		.relationship{
+			color: #555;
+			font-size: 17px;
+			height: 28px;
+			width: 75px;
+			border-radius: 10px;
+			letter-spacing: 0px;
+			margin-left: 20px;
+			background-color: #ebf6ff;
+		}
+	}
+	.top-back{
+		height: 35px;
+		background-color: #1678ff;
+		color: #fff;
+		margin-bottom: -25px;
+	}
+	.content{
+		padding: 10px 20px;
+		border-radius: 25px 25px 0 0;
+		border: none;
+		background-color: #f6f6f6;
+		height: 100vh;
+	}
+</style>

+ 214 - 0
chexnet-master-MP/pages/my/family/family_info/family_info.vue

@@ -0,0 +1,214 @@
+<template>
+	<view>
+		<view class="top-back"></view>
+		<view class="content">
+			<view class="margin-top" >
+				<uv-gap height="10" ></uv-gap>
+				
+				<!-- <p style="font-size: 25px;">个人信息</p> -->
+				<uv-divider class="head-text" :text="title" textSize="26" textColor="#000"></uv-divider>
+				
+				<uv-gap height="10" ></uv-gap>
+				
+				<view class="margin-top"><!-- 昵称 -->
+					<view class="title">昵称</view>
+					<uv-gap height="10" ></uv-gap>
+					
+					<view class="flex justify-between">
+						<input maxlength=20 type="text" style="width: 60%;" v-model="family.name" 
+							placeholder-style="color: #cccccc; font-size: 24px; font-weight: 100;" placeholder="请输入昵称" 
+							@click="$content_check.checkLogin" :disabled="$content_check.isDisabled"
+							@blur="inputBindBlur('name')"
+						/>
+						<image src="@/static/img/user.png" style="background-color: #fff;width: 15vw;height: 15vw;margin: 0 30px 5px;"></image>
+					</view>
+				</view>
+				<uv-divider customStyle="margin: 0 0"></uv-divider>
+				
+				<uv-gap height="20" ></uv-gap>
+				
+				<view style="margin-bottom: -20upx;"><!-- 关系 -->
+					<view class="title">关系</view>
+					<uv-gap height="10" ></uv-gap>
+					
+					<view class="flex justify-between">
+						<input maxlength=10 type="text" style="width: 60%;" v-model="family.relationship" 
+							placeholder-style="color: #cccccc; font-size: 24px; font-weight: 100;" placeholder="请输入成员关系" 
+							@click="$content_check.checkLogin" :disabled="$content_check.isDisabled"
+							@blur="inputBindBlur('relationship')"
+						/>
+						<image src="@/static/img/family.png" style="background-color: #fff;width: 15vw;height: 15vw;margin: 0 30px 15px;"></image>
+					</view>
+				</view>
+				<uv-divider customStyle="margin: 0 0"></uv-divider>
+				
+				<uv-gap height="20" ></uv-gap>
+				
+				<view><!-- 性别 -->
+					<view>
+						<view class="title">性别</view>
+						<uv-gap height="10" ></uv-gap>
+						<view class="flex justify-between">
+							<input placeholder="请选择性别" disabled="disabled" style="width: 60%;" name="input" v-model="gender" placeholder-style="color: #cccccc; font-size: 24px; font-weight: 100;" @click="changegender"></input>
+							<image @click="confirm({'indexs': [0]})" src="@/static/img/man.png" style="background-color: #fff;width: 15vw;height: 15vw;"></image>
+							<image @click="confirm({'indexs': [1]})" src="@/static/img/woman.png" style="background-color: #fff;width: 15vw;height: 15vw;"></image>
+						</view>
+					</view>
+					<uv-picker ref="picker" :columns="columns" @confirm="confirm"></uv-picker>
+				</view>
+				<uv-divider customStyle="margin: 0 0"></uv-divider>
+				
+				<uv-gap height="20" ></uv-gap>
+				
+				<view style="margin-bottom: -20upx;"><!-- 年龄 -->
+					<view class="title">年龄</view>
+					<uv-gap height="10" ></uv-gap>
+					<input maxlength=3 type="number" v-model="family.age" placeholder-style="color: #cccccc; font-size: 24px; font-weight: 100;" placeholder="请输入年龄" />
+				</view>
+				<uv-divider customStyle="margin: 0 0"></uv-divider>
+				
+				<uv-gap height="10" ></uv-gap>
+				
+			</view>	
+			<uv-modal width=260 ref="modal" title="请将信息填写完整"  @confirm="confirm"></uv-modal>				
+		</view>
+		
+		<uv-button @click="update()" customStyle="position: fixed; bottom: 40px;width: calc(100vw - 60px);margin: 0 30px" shape="circle" size="large" color="#1678ff">{{button_text}}</uv-button>
+	</view>
+</template>
+
+<script>
+	export default {
+		onLoad(options) {
+			// 获取传递的对象参数,使用decodeURIComponent解码,并转为对象
+			if ('id' in options) {
+				let id = JSON.parse(decodeURIComponent(options.id));
+				this.family.id = id;
+				this.family = uni.getStorageSync("family_list")[id];
+				this.gender = this.family.gender === 0 ? '男' : '女';
+			}
+			else{
+				let title = '新增成员';
+				let button_text = '新增';
+				uni.setNavigationBarTitle({title});
+				this.title = title;
+				this.button_text = button_text;
+				
+				let family_list = uni.getStorageSync('family_list');
+				if(!family_list || Object.keys(family_list).length == 0) this.family.id = 1;
+				else{
+					let keys = Object.keys(family_list);
+					this.family.id = Number(keys[keys.length - 1]) + 1;
+				}
+			}
+		},
+		data() {
+			return {
+				title: '成员资料',
+				button_text: '保存',
+				
+				family:{
+					id: null,
+					name: '',
+					relationship: '',
+					gender: '',
+					age: '',
+				},
+				gender: '',
+				columns: [['男','女']],
+			}
+		},
+		methods: {
+			inputBindBlur(type){
+				this.$content_check.textCheck(this.family[type], this, 'family.' + type);
+			},
+			changegender() {
+				this.$refs.picker.open();
+			},
+			confirm(e) {
+				this.family.gender = e['indexs'][0];
+				this.gender = this.family.gender === 0 ? '男' : '女';
+			},
+			async update(){
+				if(this.family.name.length == 0){
+					console.log("用户名无效");
+					uni.showToast({duration:1500,icon:"none",title: '请输入用户名'});
+					return ;
+				}
+				
+				//用户名必须由1到20个字符组成,并且只能包含字母、数字、汉字。
+				const regex = /^[a-zA-Z0-9\u4e00-\u9fa5]{1,20}$/;
+				const isValid = regex.test(this.family.name);
+				if (!isValid) {
+					console.log("用户名无效");
+					uni.showToast({duration:1500,icon:"none",title: '用户名只能包含字母和数字'});
+					return ;
+				}
+				console.log("用户名有效");
+				
+				let isCheck = await this.$content_check.textCheck(this.family.name, this, 'family.name');
+				console.log('isCheck: ',isCheck);
+				if(isCheck !== true) return ;
+				
+				isCheck = await this.$content_check.textCheck(this.family.relationship, this, 'family.relationship');
+				console.log('isCheck: ',isCheck);
+				if(isCheck !== true) return ;
+				
+				let family_list = uni.getStorageSync('family_list');
+				if(!family_list || Object.keys(family_list).length == 0) family_list = {};
+				
+				family_list[this.family.id] = this.family;
+
+				console.log('family_list: ',family_list);
+				
+				uni.setStorageSync('family_list', family_list);
+				
+				uni.showToast({duration:1000,icon:'success',title: '保存成功 !'});
+				setTimeout(() => uni.navigateBack(), 1300);
+			}
+		}
+	}
+</script>
+
+<style>
+	page {
+		height: 100vh;
+	}
+	.top-back{
+		height: 30px;
+		background-color: #1678ff;
+		color: #fff;
+		margin-bottom: -40px;
+	}
+	
+	.head-text{
+		letter-spacing: 5px;
+		font-weight: 600;
+	}
+	.title{
+		letter-spacing: 2px;
+		color: #8c92ad;
+		font-size: 18px;
+	}
+	input {
+		letter-spacing: 1px;
+	    width: 100%;
+		height: 8vh;
+	    padding-bottom: 5px;
+	    font-size: 24px;
+		font-weight: 600;
+		font-weight: 400;
+		color: #000;
+	    outline: none; /* 移除聚焦时的默认边框 */
+	}
+	.content {
+	  padding: 0 35px;
+	  border-radius: 25px;
+	  border: none;
+	  background-color: #ffffff;
+	}
+	.content image:active {
+		transform: translateY(2px) translateX(2px);
+	}
+
+</style>

+ 207 - 0
chexnet-master-MP/pages/my/my.vue

@@ -0,0 +1,207 @@
+<template>
+	<view>
+		<view class="top-back">
+			<view @click="$util.navigateTo('/pages/my/user_info/user_info')" class="flex justify-between">
+				<text v-if="name" class="head-text">您好:{{name}}</text>
+				<text v-else class="head-text">登陆/注册</text>
+				<text class="cuIcon-right"></text>
+			</view>
+		</view>
+		<view class="content margin-top">
+			<uv-gap height="10" ></uv-gap>
+			
+			<view class="button-group">
+				<button @click="$util.navigateTo('/pages/my/user_info/user_info')" class="button-item flex justify-between">
+					<view class="align-center flex justify-between">
+						<image style="background-color: #fff;" class="icon" src="@/static/img/user.png"></image>
+						<text>个人资料</text>
+					</view>
+					<text class="cuIcon-right"></text>
+				</button>
+				
+				<uv-divider customStyle="margin:0px;width:92%;margin:0 auto;"></uv-divider>
+				
+				<button @click="$util.navigateTo('/pages/my/family/family')" class="button-item flex justify-between">
+					<view class="align-center flex justify-between">
+						<image style="background-color: #fff;" src="@/static/img/family.png"></image>
+						<text>家庭成员</text>
+					</view>
+					<text class="cuIcon-right"></text>
+				</button>
+			</view>
+			
+			<view class="button-group">
+				<button @click="to_ai()" class="button-item flex justify-between">
+					<view class="align-center flex justify-between">
+						<image style="background-color: #fff;" src="@/static/img/ai.png"></image>
+						<text>AI 问诊记录</text>
+					</view>
+					<text class="cuIcon-right"></text>
+				</button>
+				
+				<uv-divider customStyle="margin:0px;width:92%;margin:0 auto;"></uv-divider>
+				
+				<button @click="$util.navigateTo('/pages/img_process/img_process_history/img_process_history')" class="button-item flex justify-between">
+					<view class="align-center flex justify-between">
+						<image style="background-color: #fff;" src="@/static/img/识别.png"></image>
+						<text>病例识别记录</text>
+					</view>
+					<text class="cuIcon-right"></text>
+				</button>
+				
+				<uv-divider customStyle="margin:0px;width:92%;margin:0 auto;"></uv-divider>
+				
+				<button @click="$util.navigateTo('/pages/health_tip/health_tip_collect/health_tip_collect')" class="button-item flex justify-between">
+					<view class="align-center flex justify-between">
+						<image style="background-color: #fff;" src="@/static/img/health-tip.png"></image>
+						<text>健康知识收藏夹</text>
+					</view>
+					<text class="cuIcon-right"></text>
+				</button>
+				
+				<uv-divider customStyle="margin:0px;width:92%;margin:0 auto;"></uv-divider>
+				
+				<button @click="$util.navigateTo('/pages/BMI/BMI_history/BMI_history')" class="button-item flex justify-between">
+					<view class="align-center flex justify-between">
+						<image style="background-color: #fff;" src="@/static/img/bmi.png"></image>
+						<text>BMI 记录</text>
+					</view>
+					<text class="cuIcon-right"></text>
+				</button>
+				
+			</view>
+			
+			<view class="button-group">
+				<button open-type="share" class="button-item flex justify-between">
+					<view class="align-center flex justify-between">
+						<text style="margin:0 12px 0 3px;font-size: 25px;" class="cuIcon-share"></text>
+						<text>分享小程序</text>
+					</view>
+					<text class="cuIcon-right"></text>
+				</button>
+				
+				<uv-divider customStyle="margin:0px;width:92%;margin:0 auto;"></uv-divider>
+				
+				<button open-type="feedback" class="button-item flex justify-between">
+					<view class="align-center flex justify-between">
+						<image style="background-color: #ffffff;" src="@/static/img/feedback.png"></image>
+						<text>问题反馈</text>
+					</view>
+					<text class="cuIcon-right"></text>
+				</button>
+				
+				<uv-divider customStyle="margin:0px;width:92%;margin:0 auto;"></uv-divider>
+				
+				<button @click="$util.navigateTo('/pages/my/setting/setting')" class="button-item flex justify-between">
+					<view class="align-center flex justify-between">
+						<text style="margin:0 12px 0 3px;font-size: 25px;" class="cuIcon-settings"></text>
+						<text>设置</text>
+					</view>
+					<text class="cuIcon-right"></text>
+				</button>
+			</view>
+			
+			<uv-gap height="30"></uv-gap>
+			<uv-divider text="我是有底线的"></uv-divider>
+			
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		onShow() {
+			this.name = uni.getStorageSync("name");
+		},
+		onPullDownRefresh(){
+			this.name = uni.getStorageSync("name");
+			setTimeout(() => {uni.stopPullDownRefresh();}, 1000);
+		},
+		data() {
+			return {
+				name: null,
+				request: 1,
+			}
+		},
+		methods: {	
+			to_ai(){
+				uni.setStorageSync('ai_history', true);
+				uni.switchTab({
+					url: '/pages/ai/ai'
+				})
+			},
+		}
+	}
+</script>
+
+<style lang="less">
+	page{
+		background-color: #f5f5f5;
+	}
+	text{
+		font-size: 18px;
+		display: flex;
+		justify-content: center; /* 水平居中 */
+		align-items: center;    /* 垂直居中 */
+	}
+	image{
+		margin-right: 10px;
+		width: 30px;
+		height: 30px;
+		background-color: #ffffff;
+	}
+
+	.content {
+		padding: 0 10px;
+		border-radius: 25px;
+		border: none;
+		background-color: #f5f5f5;
+	}
+	.button-group{
+		border: none;
+		border-radius: 15px;
+		width: 100%;
+		background-color: #ffffff;
+		margin-bottom: 15px;
+		
+		:first-child{
+			border-radius: 15px 15px 0% 0%;
+		}
+		:last-child{
+			border-radius:0% 0% 15px 15px;
+		}
+		:only-child {
+		    border-radius: 15px;
+		}
+	}
+	.button-item{
+		border: 0px;
+		height: 12vw;
+		padding: 0 10px;
+		background-color: #ffffff;
+	}
+	.button-item::after {
+	    display: none;
+	}
+	.button-item:active {
+		filter: brightness(80%);
+		opacity: 0.5;
+	}
+	.header{
+		width: 10vw;
+		height: 10vw;
+	}
+	.top-back{
+		padding: 5px 20px;
+		height: 20vw;
+		background-color: #1678ff;
+		color: #fff;
+		margin-bottom: -5vh;
+		
+		.head-text{
+			letter-spacing: 2px;
+			font-size: 20px;
+			font-weight: 500;
+		}
+	}
+</style>

+ 81 - 0
chexnet-master-MP/pages/my/setting/setting.vue

@@ -0,0 +1,81 @@
+<template>
+	<view>
+		<view class="button-group">
+			<button @click="radioChange">
+				<view class="align-center flex justify-between">
+					<image style="background-color: #ffffff;" :src="doctor_gender == 1 ? doctor_woman : doctor_man"></image>
+					<text>主页医生</text>
+				</view>
+				<radio-group class="center">
+					<label class="margin-right-sm"><radio class="margin-right-xs" value="r1" :checked="doctor_gender == 0" />男</label>
+					<label><radio class="margin-right-xs" value="r2" :checked="doctor_gender == 1" />女</label>
+				</radio-group>
+			</button>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		onLoad() {
+			let doctor_gender = uni.getStorageSync('doctor_gender');
+			if(doctor_gender === 0) this.doctor_gender = 0;
+			else this.doctor_gender = 1;
+		},
+		data() {
+			return {
+				doctor_woman: '/static/img/女医生.png',
+				doctor_man: '/static/img/男医生.png',
+				doctor_gender: 1
+			}
+		},
+		methods: {
+			radioChange() {
+				if(this.doctor_gender === 1) this.doctor_gender = 0;
+				else this.doctor_gender = 1;
+				uni.removeStorageSync('doctor_gender');
+				uni.setStorageSync('doctor_gender',this.doctor_gender);
+			}
+		}
+	}
+</script>
+
+<style lang="less">
+	page{
+		background-color: #f5f5f5;
+	}
+	text{
+		font-size: 18px;
+		display: flex;
+		justify-content: center; /* 水平居中 */
+		align-items: center;    /* 垂直居中 */
+	}
+	image{
+		margin-right: 10px;
+		width: 30px;
+		height: 30px;
+		background-color: #ffffff;
+	}
+	.button-group{
+		border: none;
+		width: 100%;
+		background-color: #ffffff;
+		margin-bottom: 15px;
+		border-radius: 0px;
+		
+		button{
+			border: 1px;
+			border-color: #000;
+			border-radius: 0px;
+			height: 12vw;
+			padding: 0 10px;
+			background-color: #ffffff;
+			display: flex;
+			justify-content: space-between;
+		}
+		button:active {
+			filter: brightness(80%);
+			opacity: 0.8;
+		}
+	}
+</style>

+ 209 - 0
chexnet-master-MP/pages/my/user_info/user_info.vue

@@ -0,0 +1,209 @@
+<template>
+	<view>
+		<view class="top-back"></view>
+		<view class="content">
+			<view class="margin-top" >
+				<uv-gap height="10" ></uv-gap>
+				
+				<!-- <p style="font-size: 25px;">个人信息</p> -->
+				<uv-divider class="head-text" text="个人资料" textSize="26" textColor="#000"></uv-divider>
+				
+				<uv-gap height="10" ></uv-gap>
+				
+				<view class="margin-top"><!-- 昵称 -->
+					<view class="title">昵称</view>
+					<uv-gap height="10" ></uv-gap>
+					
+					<view class="flex justify-between">
+						<input maxlength=20 class="" type="nickname" style="width: 60%;" v-model="user.name" 
+							placeholder-style="color: #cccccc; font-size: 24px; font-weight: 100;" placeholder="请输入昵称" 
+						/>
+						<image src="@/static/img/user.png" style="background-color: #fff;width: 15vw;height: 15vw;margin: 0 30px 5px;"></image>
+					</view>
+				</view>
+				<uv-divider customStyle="margin: 0 0"></uv-divider>
+				
+				<uv-gap height="20" ></uv-gap>
+				
+				<view><!-- 性别 -->
+					<view class="">
+						<view class="title">性别</view>
+						<uv-gap height="10" ></uv-gap>
+						<view class="flex justify-between">
+							<input placeholder="请选择性别" disabled="disabled" style="width: 60%;" name="input" v-model="gender" placeholder-style="color: #cccccc; font-size: 24px; font-weight: 100;" @click="changegender"></input>
+							<image @click="confirm({'indexs': [0]})" src="@/static/img/man.png" style="background-color: #fff;width: 15vw;height: 15vw;"></image>
+							<image @click="confirm({'indexs': [1]})" src="@/static/img/woman.png" style="background-color: #fff;width: 15vw;height: 15vw;"></image>
+						</view>
+					</view>
+					<uv-picker ref="picker" :columns="columns" @confirm="confirm"></uv-picker>
+				</view>
+				<uv-divider customStyle="margin: 0 0"></uv-divider>
+				
+				<uv-gap height="20" ></uv-gap>
+				
+				<view class="" style="margin-bottom: -20upx;"><!-- 年龄 -->
+					<view class="title">年龄</view>
+					<uv-gap height="10" ></uv-gap>
+					<input maxlength=3 class="weui-input" type="number" v-model="user.age" placeholder-style="color: #cccccc; font-size: 24px; font-weight: 100;" placeholder="请输入年龄" />
+				</view>
+				<uv-divider customStyle="margin: 0 0"></uv-divider>
+				
+				<uv-gap height="10" ></uv-gap>
+				
+			</view>	
+			<uv-modal width=260 ref="modal" title="请将信息填写完整"  @confirm="confirm"></uv-modal>				
+		</view>
+		
+		<uv-button @click="update()" customStyle="position: fixed; bottom: 40px;width: calc(100vw - 60px);margin: 0 30px" shape="circle" size="large" color="#1678ff">保存</uv-button>
+	</view>
+</template>
+
+<script>
+	export default {
+		onLoad() {
+			this.user.name = uni.getStorageSync("name");
+			this.$user_api.user_get().then(res => {
+				this.user = this.$util.removeNullValues(res);
+				this.gender = this.user.gender === 0 ? '男' : '女';
+				uni.setStorageSync("name",res.name);
+				uni.setStorageSync("user",this.user);
+			});
+		},
+		onPullDownRefresh(){
+			this.user.name = uni.getStorageSync("name");
+			this.$user_api.user_get().then(res => {
+				this.user = this.$util.removeNullValues(res);
+				this.gender = this.user.gender === 0 ? '男' : '女';
+				uni.setStorageSync("name",res.name);
+				uni.setStorageSync("user",this.user);
+			});
+			setTimeout(() => {uni.stopPullDownRefresh();}, 1000);
+		},
+		data() {
+			return {
+				user:{
+					name: '',
+					gender: '',
+					age: '',
+				},
+				gender: '',
+				columns: [['男','女']],
+				request: 1,
+			}
+		},
+		methods: {
+			// inputBindBlur(){
+			// 	this.$content_check.textCheck(this.user.name, this, 'user.name');
+			// },
+			changegender() {
+				this.$refs.picker.open();
+			},
+			confirm(e) {
+				this.user.gender = e['indexs'][0];
+				this.gender = this.user.gender === 0 ? '男' : '女';
+			},
+			async update(){
+				if(this.user.name.length > 0){
+					//用户名必须由1到20个字符组成,并且只能包含字母、数字、汉字。
+					const regex = /^[a-zA-Z0-9\u4e00-\u9fa5]{1,20}$/;
+					const isValid = regex.test(this.user.name);
+					
+					if (isValid) {
+						console.log("用户名有效");
+						
+						if(this.request){
+							let user = uni.getStorageSync("user");
+							
+							if(!this.$util.ObjectEqual(user,this.user)){
+								
+								let isCheck = await this.$content_check.textCheck(this.user.name, this, 'user.name');
+								console.log('isCheck: ',isCheck);
+								if(isCheck !== true) return ;
+								
+								this.request = 0;
+								
+								uni.showLoading({title: '加载中...'});
+								console.log('this.user: ',this.user);
+								this.$user_api.user_update(this.$util.removeNullValues(this.user))
+									.then((res) => {
+										uni.setStorageSync("name",this.user.name);
+										uni.setStorageSync("user",this.user);
+										this.request = 1;
+										uni.hideLoading();
+										uni.showToast({duration:1000,icon:'success',title: '保存成功 !'});
+									}).catch((err) => {  
+										this.request = 1;
+										uni.hideLoading();
+										uni.showToast({icon:"error",title: '保存失败 !'});
+									});
+							}
+							else{
+								console.log("信息未改变");
+								uni.showToast({duration:1500,icon:"error",title: '信息未发生改变'});
+							}
+						}
+					} else {
+						console.log("用户名无效");
+						uni.showToast({duration:1500,icon:"none",title: '用户名只能包含字母和数字'});
+					}
+				}
+				else{
+					uni.showToast({duration:1500,icon:"none",title: '请输入用户名'});
+				}
+			}
+		}
+	}
+</script>
+
+<style>
+	page {
+		height: 100vh;
+	}
+	.top-back{
+		height: 30px;
+		background-color: #1678ff;
+		color: #fff;
+		margin-bottom: -40px;
+	}
+	.top-back p{
+		height: 7.5vh;
+		display: flex; /* 使用flex布局来实现居中 */
+		justify-content: center; /* 水平居中 */
+		align-items: center; /* 垂直居中 */
+		font-size: 20px;
+		letter-spacing: 10px;
+		margin-left: 10px;
+		font-weight: 600;
+	}
+	
+	.head-text{
+		letter-spacing: 5px;
+		font-weight: 600;
+	}
+	.title{
+		letter-spacing: 2px;
+		color: #8c92ad;
+		font-size: 18px;
+	}
+	input {
+		letter-spacing: 1px;
+	    width: 100%;
+		height: 8vh;
+	    padding-bottom: 5px;
+	    font-size: 24px;
+		font-weight: 600;
+		font-weight: 400;
+		color: #000;
+	    outline: none; /* 移除聚焦时的默认边框 */
+	}
+	.content {
+	  padding: 0 35px;
+	  border-radius: 25px;
+	  border: none;
+	  background-color: #ffffff;
+	}
+	.content image:active {
+		transform: translateY(2px) translateX(2px);
+	}
+
+</style>

+ 10 - 0
chexnet-master-MP/uni.promisify.adaptor.js

@@ -0,0 +1,10 @@
+uni.addInterceptor({
+  returnValue (res) {
+    if (!(!!res && (typeof res === "object" || typeof res === "function") && typeof res.then === "function")) {
+      return res;
+    }
+    return new Promise((resolve, reject) => {
+      res.then((res) => res[0] ? reject(res[0]) : resolve(res[1]));
+    });
+  },
+});

+ 76 - 0
chexnet-master-MP/uni.scss

@@ -0,0 +1,76 @@
+/**
+ * 这里是uni-app内置的常用样式变量
+ *
+ * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
+ * 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
+ *
+ */
+
+/**
+ * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
+ *
+ * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
+ */
+
+/* 颜色变量 */
+
+/* 行为相关颜色 */
+$uni-color-primary: #007aff;
+$uni-color-success: #4cd964;
+$uni-color-warning: #f0ad4e;
+$uni-color-error: #dd524d;
+
+/* 文字基本颜色 */
+$uni-text-color:#333;//基本色
+$uni-text-color-inverse:#fff;//反色
+$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息
+$uni-text-color-placeholder: #808080;
+$uni-text-color-disable:#c0c0c0;
+
+/* 背景颜色 */
+$uni-bg-color:#ffffff;
+$uni-bg-color-grey:#f8f8f8;
+$uni-bg-color-hover:#f1f1f1;//点击状态颜色
+$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色
+
+/* 边框颜色 */
+$uni-border-color:#c8c7cc;
+
+/* 尺寸变量 */
+
+/* 文字尺寸 */
+$uni-font-size-sm:12px;
+$uni-font-size-base:14px;
+$uni-font-size-lg:16px;
+
+/* 图片尺寸 */
+$uni-img-size-sm:20px;
+$uni-img-size-base:26px;
+$uni-img-size-lg:40px;
+
+/* Border Radius */
+$uni-border-radius-sm: 2px;
+$uni-border-radius-base: 3px;
+$uni-border-radius-lg: 6px;
+$uni-border-radius-circle: 50%;
+
+/* 水平间距 */
+$uni-spacing-row-sm: 5px;
+$uni-spacing-row-base: 10px;
+$uni-spacing-row-lg: 15px;
+
+/* 垂直间距 */
+$uni-spacing-col-sm: 4px;
+$uni-spacing-col-base: 8px;
+$uni-spacing-col-lg: 12px;
+
+/* 透明度 */
+$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
+
+/* 文章场景相关 */
+$uni-color-title: #2C405A; // 文章标题颜色
+$uni-font-size-title:20px;
+$uni-color-subtitle: #555555; // 二级标题颜色
+$uni-font-size-subtitle:26px;
+$uni-color-paragraph: #3F536E; // 文章段落颜色
+$uni-font-size-paragraph:15px;

+ 2 - 0
chexnet-master-Server/.gitattributes

@@ -0,0 +1,2 @@
+/mvnw text eol=lf
+*.cmd text eol=crlf

+ 33 - 0
chexnet-master-Server/.gitignore

@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/

+ 1 - 0
chexnet-master-Server/chexnet-master-img-api.ini

@@ -0,0 +1 @@
+http://[240e:460:64:6c6:83da:f61d:3179:23f9]:12315/chexnet-master/processImg

+ 149 - 0
chexnet-master-Server/mvnw.cmd

@@ -0,0 +1,149 @@
+<# : batch portion
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements.  See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership.  The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License.  You may obtain a copy of the License at
+@REM
+@REM    http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied.  See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Apache Maven Wrapper startup batch script, version 3.3.2
+@REM
+@REM Optional ENV vars
+@REM   MVNW_REPOURL - repo url base for downloading maven distribution
+@REM   MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+@REM   MVNW_VERBOSE - true: enable verbose log; others: silence the output
+@REM ----------------------------------------------------------------------------
+
+@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
+@SET __MVNW_CMD__=
+@SET __MVNW_ERROR__=
+@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
+@SET PSModulePath=
+@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
+  IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
+)
+@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
+@SET __MVNW_PSMODULEP_SAVE=
+@SET __MVNW_ARG0_NAME__=
+@SET MVNW_USERNAME=
+@SET MVNW_PASSWORD=
+@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*)
+@echo Cannot start maven from wrapper >&2 && exit /b 1
+@GOTO :EOF
+: end batch / begin powershell #>
+
+$ErrorActionPreference = "Stop"
+if ($env:MVNW_VERBOSE -eq "true") {
+  $VerbosePreference = "Continue"
+}
+
+# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
+$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
+if (!$distributionUrl) {
+  Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
+}
+
+switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
+  "maven-mvnd-*" {
+    $USE_MVND = $true
+    $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
+    $MVN_CMD = "mvnd.cmd"
+    break
+  }
+  default {
+    $USE_MVND = $false
+    $MVN_CMD = $script -replace '^mvnw','mvn'
+    break
+  }
+}
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
+if ($env:MVNW_REPOURL) {
+  $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" }
+  $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')"
+}
+$distributionUrlName = $distributionUrl -replace '^.*/',''
+$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
+$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain"
+if ($env:MAVEN_USER_HOME) {
+  $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain"
+}
+$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
+$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
+
+if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
+  Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+  Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
+  exit $?
+}
+
+if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
+  Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
+}
+
+# prepare tmp dir
+$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
+$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
+$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
+trap {
+  if ($TMP_DOWNLOAD_DIR.Exists) {
+    try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+    catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+  }
+}
+
+New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
+
+# Download and Install Apache Maven
+Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+Write-Verbose "Downloading from: $distributionUrl"
+Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+$webclient = New-Object System.Net.WebClient
+if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
+  $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
+}
+[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
+if ($distributionSha256Sum) {
+  if ($USE_MVND) {
+    Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
+  }
+  Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
+  if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
+    Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
+  }
+}
+
+# unzip and move
+Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
+Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null
+try {
+  Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
+} catch {
+  if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
+    Write-Error "fail to move MAVEN_HOME"
+  }
+} finally {
+  try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+  catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+}
+
+Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"

+ 105 - 0
chexnet-master-Server/pom.xml

@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>3.3.4</version>
+        <relativePath/> <!-- lookup parent from repository -->
+    </parent>
+    <groupId>cn.flea</groupId>
+    <artifactId>chexnet-master</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <name>chexnet-master</name>
+    <description>chexnet-master</description>
+    <url/>
+    <licenses>
+        <license/>
+    </licenses>
+    <developers>
+        <developer/>
+    </developers>
+    <scm>
+        <connection/>
+        <developerConnection/>
+        <tag/>
+        <url/>
+    </scm>
+    <properties>
+        <java.version>17</java.version>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.mybatis.spring.boot</groupId>
+            <artifactId>mybatis-spring-boot-starter</artifactId>
+            <version>3.0.3</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.mysql</groupId>
+            <artifactId>mysql-connector-j</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.mybatis.spring.boot</groupId>
+            <artifactId>mybatis-spring-boot-starter-test</artifactId>
+            <version>3.0.3</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt</artifactId>
+            <version>0.9.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>javax.xml.bind</groupId>
+            <artifactId>jaxb-api</artifactId>
+            <version>2.3.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.google.code.gson</groupId>
+            <artifactId>gson</artifactId>
+            <version>2.8.8</version>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <configuration>
+                    <excludes>
+                        <exclude>
+                            <groupId>org.projectlombok</groupId>
+                            <artifactId>lombok</artifactId>
+                        </exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 15 - 0
chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/ChexnetMasterApplication.java

@@ -0,0 +1,15 @@
+package cn.flea.chexnetmaster;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+@EnableScheduling
+@SpringBootApplication
+public class ChexnetMasterApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(ChexnetMasterApplication.class, args);
+    }
+
+}

+ 121 - 0
chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/component/MemoryUserRecordSpace.java

@@ -0,0 +1,121 @@
+package org.lp.medicalai.component;
+
+import org.lp.medicalai.config.XfXhConfig;
+import org.lp.medicalai.dto.InteractMsg;
+import org.lp.medicalai.dto.MsgDTO;
+import org.lp.medicalai.dto.RecordsArray;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+
+@Component
+public class MemoryUserRecordSpace {
+    @Autowired
+    private XfXhConfig xfXhConfig;
+
+    private final ConcurrentHashMap<Long, RecordsArray> userRecordMap = new ConcurrentHashMap<>();
+
+    public ConcurrentHashMap<Long, RecordsArray> getUserRecordMap() {
+        return userRecordMap;
+    }
+
+    public List<MsgDTO> getAllInteractMsg(Integer id) {
+        RecordsArray recordsArray = userRecordMap.get(id);
+        if (recordsArray == null) {
+            return new ArrayList<>(1);
+        }
+        return recordsArray.getAllInteractMsg();
+    }
+
+    /**
+     * 尝试加锁
+     *
+     * @param id
+     * @return true-加锁成功,false-加锁失败
+     */
+    public boolean tryLock(Integer id) {
+        synchronized (id.toString().intern()) {
+            RecordsArray recordsArray = userRecordMap.get(id);
+            // 如果查不到用户或者没有加锁,则允许操作
+            if (recordsArray == null) {
+                // 用户信息保存到内存
+                RecordsArray newRecordsArray = storeUserRecord(id);
+                // 加锁
+                newRecordsArray.setLock(true);
+                return true;
+            } else if (!recordsArray.isLock()) {
+                // 加锁
+                recordsArray.setLock(true);
+                // 返回
+                return true;
+            } else {
+                return false;
+            }
+        }
+    }
+
+
+    /**
+     * 释放锁
+     *
+     * @param id
+     */
+    public void unLock(Integer id) {
+        RecordsArray recordsArray = userRecordMap.get(id);
+        if (recordsArray != null) {
+            recordsArray.setLock(false);
+        }
+    }
+
+    public void storeInteractMsg(Integer id, InteractMsg interactMsg) {
+        RecordsArray recordsArray = userRecordMap.get(id);
+        // 为空说明不存在该用户的记录
+        if (recordsArray == null) {
+            storeUserRecord(id,interactMsg);
+            return;
+        }
+
+        // 不为空的处理
+        recordsArray.addInteractMsg(interactMsg);
+        // 刷新状态为最新
+        recordsArray.setStatus(0);
+    }
+
+    private RecordsArray storeUserRecord(Integer id, InteractMsg interactMsg) {
+        // 判断是否满了
+        while (userRecordMap.size() >= xfXhConfig.getMaxUserCount()) {
+            // 需要移除状态最差(status 最高)的用户
+            int maxStatus = -1;
+            Long userId = 0L;
+            for (Map.Entry<Long, RecordsArray> entry : userRecordMap.entrySet()) {
+                RecordsArray recordsArray = entry.getValue();
+                // 已锁用户不进行处理
+                if (!recordsArray.isLock()) {
+                    // 获取当前用户的状态
+                    int userStatus = recordsArray.getStatus();
+                    if (maxStatus < userStatus) {
+                        maxStatus = userStatus;
+                        userId = entry.getKey();
+                    }
+                }
+            }
+            userRecordMap.remove(userId);
+        }
+        RecordsArray newRecordArray = new RecordsArray(xfXhConfig.getMaxInteractCount());
+        if (interactMsg != null) {
+            newRecordArray.addInteractMsg(interactMsg);
+        }
+        userRecordMap.put(Long.valueOf(id), newRecordArray);
+        return newRecordArray;
+    }
+
+    private RecordsArray storeUserRecord(Integer id){
+        return storeUserRecord(id,null);
+    }
+
+}

+ 45 - 0
chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/component/ScheduleTask.java

@@ -0,0 +1,45 @@
+package org.lp.medicalai.component;
+
+import org.lp.medicalai.config.XfXhConfig;
+import org.lp.medicalai.dto.RecordsArray;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+
+
+@EnableScheduling   // 1.开启定时任务
+@EnableAsync        // 2.开启多线程
+@Component
+public class ScheduleTask {
+    @Autowired
+    private MemoryUserRecordSpace memoryUserRecordSpace;
+
+    @Autowired
+    private XfXhConfig xfXhConfig;
+
+    @Scheduled(initialDelayString = "${xfxh.scheduled.updateUserStatusFixedDelay}"
+            ,fixedDelayString  = "${xfxh.scheduled.updateUserStatusFixedDelay}")
+    @Async
+    public void updateUserStatusTask(){
+        ConcurrentHashMap<Long, RecordsArray> userRecordMap = memoryUserRecordSpace.getUserRecordMap();
+        for (Map.Entry<Long, RecordsArray> userRecord : userRecordMap.entrySet()) {
+            RecordsArray recordsArray = userRecord.getValue();
+            // 如果已经加锁,说明正在交互消息中,直接跳过
+            if (recordsArray.isLock()){
+                continue;
+            }
+            // 将状态+1,当超过 xfXhConfig.userRecordMaxStatus 则从空间中移除该用户交互信息
+            recordsArray.setStatus(recordsArray.getStatus()+1);
+            if (recordsArray.getStatus()>xfXhConfig.getUserRecordMaxStatus()){
+                userRecordMap.remove(userRecord.getKey());
+            }
+        }
+    }
+}

+ 143 - 0
chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/component/XfXhStreamClient.java

@@ -0,0 +1,143 @@
+package org.lp.medicalai.component;
+
+import com.alibaba.fastjson.JSONObject;
+import org.lp.medicalai.config.XfXhConfig;
+import org.lp.medicalai.dto.MsgDTO;
+import org.lp.medicalai.dto.RequestDTO;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+
+@Component
+@Slf4j
+public class XfXhStreamClient {
+    @Autowired
+    private XfXhConfig xfXhConfig;
+
+    @Value("${xfxh.QPS}")
+    private int connectionTokenCount;
+
+    /**
+     * 获取令牌
+     */
+    public static int GET_TOKEN_STATUS = 0;
+    /**
+     * 归还令牌
+     */
+    public static int BACK_TOKEN_STATUS = 1;
+
+    /**
+     * 操作令牌
+     *
+     * @param status 0-获取令牌 1-归还令牌
+     * @return 是否操作成功
+     */
+    public synchronized boolean operateToken(int status) {
+        if (status == GET_TOKEN_STATUS) {
+            // 获取令牌
+            if (connectionTokenCount != 0) {
+                // 说明还有令牌,将令牌数减一
+                connectionTokenCount-=1;
+                return true;
+            }else{
+                return false;
+            }
+        } else {
+            // 放回令牌
+            connectionTokenCount += 1;
+            return true;
+        }
+    }
+
+    /**
+     * 发送消息
+     *
+     * @param uid     每个用户的id,用于区分不同用户
+     * @param msgList 发送给大模型的消息,可以包含上下文内容
+     * @return 获取websocket连接,以便于我们在获取完整大模型回复后手动关闭连接
+     */
+    public WebSocket sendMsg(String uid, List<MsgDTO> msgList, WebSocketListener listener) {
+        // 获取鉴权url
+        String authUrl = this.getAuthUrl();
+        // 鉴权方法生成失败,直接返回 null
+        if (authUrl == null) {
+            return null;
+        }
+        OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
+        // 将 https/http 连接替换为 ws/wss 连接
+        String url = authUrl.replace("http://", "ws://").replace("https://", "wss://");
+        Request request = new Request.Builder().url(url).build();
+        // 建立 wss 连接
+        WebSocket webSocket = okHttpClient.newWebSocket(request, listener);
+        // 组装请求参数
+        RequestDTO requestDTO = getRequestParam(uid, msgList);
+        // 发送请求
+        webSocket.send(JSONObject.toJSONString(requestDTO));
+        return webSocket;
+    }
+
+    /**
+     * 生成鉴权方法,具体实现不用关心,这是讯飞官方定义的鉴权方式
+     *
+     * @return 鉴权访问大模型的路径
+     */
+    public String getAuthUrl() {
+        try {
+            URL url = new URL(xfXhConfig.getHostUrl());
+            // 时间
+            SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
+            format.setTimeZone(TimeZone.getTimeZone("GMT"));
+            String date = format.format(new Date());
+            // 拼接
+            String preStr = "host: " + url.getHost() + "\n" +
+                    "date: " + date + "\n" +
+                    "GET " + url.getPath() + " HTTP/1.1";
+            // SHA256加密
+            Mac mac = Mac.getInstance("hmacsha256");
+            SecretKeySpec spec = new SecretKeySpec(xfXhConfig.getApiSecret().getBytes(StandardCharsets.UTF_8), "hmacsha256");
+            mac.init(spec);
+
+            byte[] hexDigits = mac.doFinal(preStr.getBytes(StandardCharsets.UTF_8));
+            // Base64加密
+            String sha = Base64.getEncoder().encodeToString(hexDigits);
+            // 拼接
+            String authorizationOrigin = String.format("api_key=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"", xfXhConfig.getApiKey(), "hmac-sha256", "host date request-line", sha);
+            // 拼接地址
+            HttpUrl httpUrl = Objects.requireNonNull(HttpUrl.parse("https://" + url.getHost() + url.getPath())).newBuilder().
+                    addQueryParameter("authorization", Base64.getEncoder().encodeToString(authorizationOrigin.getBytes(StandardCharsets.UTF_8))).
+                    addQueryParameter("date", date).
+                    addQueryParameter("host", url.getHost()).
+                    build();
+
+            return httpUrl.toString();
+        } catch (Exception e) {
+            log.error("鉴权方法中发生错误:" + e.getMessage());
+            return null;
+        }
+    }
+
+    /**
+     * 获取请求参数
+     *
+     * @param uid     每个用户的id,用于区分不同用户
+     * @param msgList 发送给大模型的消息,可以包含上下文内容
+     * @return 请求DTO,该 DTO 转 json 字符串后生成的格式参考 resources/demo-json/request.json
+     */
+    public RequestDTO getRequestParam(String uid, List<MsgDTO> msgList) {
+        RequestDTO requestDTO = new RequestDTO();
+        requestDTO.setHeader(new RequestDTO.HeaderDTO(xfXhConfig.getAppId(), uid));
+        requestDTO.setParameter(new RequestDTO.ParameterDTO(new RequestDTO.ParameterDTO.ChatDTO(xfXhConfig.getDomain(), xfXhConfig.getTemperature(), xfXhConfig.getMaxTokens())));
+        requestDTO.setPayload(new RequestDTO.PayloadDTO(new RequestDTO.PayloadDTO.MessageDTO(msgList)));
+        return requestDTO;
+    }
+}

+ 19 - 0
chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/config/WebConfig.java

@@ -0,0 +1,19 @@
+package org.lp.medicalai.config;
+
+import org.lp.medicalai.interceptor.LoginCheckInterceptor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class WebConfig implements WebMvcConfigurer {
+
+    @Autowired
+    private LoginCheckInterceptor loginCheckInterceptor;
+
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/medical-ai/**");
+    }
+}

+ 22 - 0
chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/config/XfXhConfig.java

@@ -0,0 +1,22 @@
+package org.lp.medicalai.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Data
+@Configuration
+@ConfigurationProperties(prefix = "xfxh")
+public class XfXhConfig {
+    private String hostUrl;
+    private String domain;
+    private Float temperature;
+    private Integer maxTokens;
+    private String appId;
+    private String apiKey;
+    private String apiSecret;
+    private Integer maxInteractCount;
+    private Integer maxUserCount;
+    private Integer userRecordMaxStatus;
+    private Integer maxResponseTime;
+}

+ 123 - 0
chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/controller/AiController.java

@@ -0,0 +1,123 @@
+package org.lp.medicalai.controller;
+
+import cn.hutool.core.util.StrUtil;
+import com.alibaba.fastjson.JSONObject;
+import org.lp.medicalai.component.MemoryUserRecordSpace;
+import org.lp.medicalai.component.XfXhStreamClient;
+import org.lp.medicalai.config.XfXhConfig;
+import org.lp.medicalai.dto.InteractMsg;
+import org.lp.medicalai.dto.MsgDTO;
+import org.lp.medicalai.dto.RecordsArray;
+import org.lp.medicalai.listener.XfXhWebSocketListener;
+import okhttp3.WebSocket;
+import org.lp.medicalai.pojo.Result;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+@RestController
+@RequestMapping("/medical-ai")
+public class AiController {
+    @Autowired
+    private XfXhConfig xfXhConfig;
+
+    @Autowired
+    private XfXhStreamClient xfXhStreamClient;
+
+    @Autowired
+    private MemoryUserRecordSpace memoryUserRecordSpace;
+
+    // 使用 id 作为唯一用户的标识(区分不同用户)
+    @GetMapping("/sendQuestion")
+    public Result question(@RequestAttribute("id") Integer id,String question) throws InterruptedException {
+
+        if (StrUtil.isBlank(question)) {
+            return Result.error("NO_QUESTION");//无效问题,请重新输入
+        }
+
+        // 尝试锁定用户
+        if (!memoryUserRecordSpace.tryLock(id)) {
+            return Result.error("WAIT");//正在处理上次问题,请稍后再试
+        }
+
+        // 获取连接令牌
+        if(!xfXhStreamClient.operateToken(XfXhStreamClient.GET_TOKEN_STATUS)){
+            // 释放锁
+            memoryUserRecordSpace.unLock(id);
+            return Result.error("BUSY");//当前大模型连接数过多,请稍后再试
+        }
+
+        MsgDTO msgDTO = MsgDTO.createUserMsg(question);
+        String tip = "你是一位专业且耐心的医生,专注于提供医疗建议。非医疗问题请礼貌拒绝。";
+
+        MsgDTO tipDTO = MsgDTO.createSystemMsg(tip);
+
+        XfXhWebSocketListener listener = new XfXhWebSocketListener();
+
+        // 组装上下文内容发送
+        List<MsgDTO> msgList = memoryUserRecordSpace.getAllInteractMsg(id);
+
+        msgList.add(tipDTO);
+        msgList.add(msgDTO);
+
+        WebSocket webSocket = xfXhStreamClient.sendMsg(UUID.randomUUID().toString().substring(0, 10), msgList, listener);
+        if (webSocket == null) {
+            // 归还令牌
+            xfXhStreamClient.operateToken(XfXhStreamClient.BACK_TOKEN_STATUS);
+            // 释放锁
+            memoryUserRecordSpace.unLock(id);
+            return Result.error("ERROR");//系统内部错误,请联系管理员
+        }
+        try {
+            int count = 0;
+            // 为了避免死循环,设置循环次数来定义超时时长
+            int maxCount = xfXhConfig.getMaxResponseTime() * 5;
+            while (count <= maxCount) {
+                Thread.sleep(200);
+                if (listener.isWsCloseFlag()) {
+                    break;
+                }
+                count++;
+            }
+            if (count > maxCount) {
+                return Result.error("ERROR");//大模型响应超时,请联系管理员
+            }
+            // 将记录添加到 memoryUserRecordSpace
+            String answer = listener.getAnswer().toString();
+            memoryUserRecordSpace.storeInteractMsg(id, new InteractMsg(MsgDTO.createUserMsg(question), MsgDTO.createAssistantMsg(answer)));
+            return Result.success(answer);//相应成功
+        } finally {
+            // 关闭连接
+            webSocket.close(1000, "");
+            // 释放锁
+            memoryUserRecordSpace.unLock(id);
+            // 归还令牌
+            xfXhStreamClient.operateToken(XfXhStreamClient.BACK_TOKEN_STATUS);
+        }
+    }
+
+    // 测试使用,查看内存空间中所有的用户记录信息
+    @GetMapping("/spaceInfo")
+    public List<JSONObject> spaceInfo(@RequestAttribute("id") Integer id){
+        if(id == 1){
+            ConcurrentHashMap<Long, RecordsArray> userRecordMap = memoryUserRecordSpace.getUserRecordMap();
+            ArrayList<JSONObject> infoList = new ArrayList<>(userRecordMap.size());
+            for (Map.Entry<Long, RecordsArray> entry : userRecordMap.entrySet()) {
+                RecordsArray recordsArray = entry.getValue();
+                JSONObject data = new JSONObject();
+                data.put("id",entry.getKey());
+                data.put("allInteractMsg",recordsArray.getAllInteractMsg());
+                data.put("status",recordsArray.getStatus());
+                data.put("lock",recordsArray.isLock());
+                infoList.add(data);
+            }
+            return infoList;
+        }
+        return null;
+    }
+}

+ 42 - 0
chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/controller/ContentCheck.java

@@ -0,0 +1,42 @@
+package cn.flea.chexnetmaster.controller;
+
+import cn.flea.chexnetmaster.pojo.Result;
+import cn.flea.chexnetmaster.service.ContentCheckService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+@Slf4j
+@RestController
+@RequestMapping("/contentCheck")
+public class ContentCheck {
+
+    @Autowired
+    private ContentCheckService contentCheckService;
+
+    @GetMapping("/textCheck")
+    public Result textCheck(String text, @RequestAttribute("openid") String openid){
+        log.info("待检查文本: " + text);
+
+        Integer res = contentCheckService.textCheck(text,openid);
+        log.info("文本审核结果: " + res);
+
+        if (res == 1) return Result.success("PASS");
+        if (res == 2) return Result.success("NO_PASS");
+
+        return Result.error("文本审核失败");
+    }
+
+    @PostMapping("/imgCheck")
+    public Result imgCheck(MultipartFile img, @RequestAttribute("openid") String openid){
+
+        Integer res = contentCheckService.imgCheck(img,openid);
+        log.info("图片审核结果: " + res);
+
+        if (res == 1) return Result.success("PASS");
+        if (res == 2) return Result.success("NO_PASS");
+
+        return Result.error("图片审核失败");
+    }
+}

+ 43 - 0
chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/controller/ImgProcessController.java

@@ -0,0 +1,43 @@
+package cn.flea.chexnetmaster.controller;
+
+import cn.flea.chexnetmaster.pojo.Result;
+import cn.flea.chexnetmaster.service.ProcessImgService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+@Slf4j
+@RestController
+@RequestMapping("/chexnet-master")
+@CrossOrigin(origins = "*")
+public class ImgProcessController {
+
+    private String chexnetMasterImgApiUrl = null;
+
+    @Autowired
+    private ProcessImgService processImgService;
+
+    @PostMapping("/processImg")
+    public Result processImg(MultipartFile img) {
+        if (chexnetMasterImgApiUrl == null) {
+            log.error("未设置chexnetMasterImgApiUrl");
+            return Result.error("未设置chexnetMasterImgApiUrl");
+        }
+        return processImgService.processImg(img,chexnetMasterImgApiUrl);
+    }
+
+
+    @PutMapping("/processImg")
+    public Result saveImg(String chexnetMasterImgApiUrl) {
+        if (chexnetMasterImgApiUrl != null || !chexnetMasterImgApiUrl.isEmpty()){
+            this.chexnetMasterImgApiUrl = chexnetMasterImgApiUrl;
+            log.info("更新chexnetMasterImgApiUrl为:{}", chexnetMasterImgApiUrl);
+            return Result.success();
+        }
+        log.error("参数错误");
+        return Result.error("参数错误");
+    }
+
+
+}

+ 73 - 0
chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/controller/UserController.java

@@ -0,0 +1,73 @@
+package cn.flea.chexnetmaster.controller;
+
+import cn.flea.chexnetmaster.pojo.Result;
+import cn.flea.chexnetmaster.pojo.User;
+import cn.flea.chexnetmaster.service.UserService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.HashMap;
+
+@Slf4j
+@RestController
+@CrossOrigin(origins = "*")
+public class UserController {
+
+    @Autowired
+    UserService userService;
+
+    @PostMapping("/user")
+    public Result Login(String code, String type) {
+        log.info("用户登陆,数据: " + "code: " + code + "type" + type);
+        Result res = userService.login(code);
+        log.info("登陆操作完成: " + res);
+        return res;
+    }
+
+    @PutMapping("/user")
+    public Result updateUserById(User user,@RequestAttribute("id") Integer id, String type) {
+        log.info("更新用户,数据: " + "user: " + user + "id: " + id + "type" + type);
+        user.setId(id);
+        Integer res = userService.updateUserById(user);
+
+        if (res == 0) return Result.error("更新用户数据失败");
+
+        log.info("更新用户数据成功");
+        return Result.success("更新用户数据成功");
+    }
+
+//    @DeleteMapping("/user")
+//    public Result deleteUser(@PathVariable String openid) {
+//        log.info("openid: " + openid);
+//        return userService.deleteUser(openid);
+//    }
+
+    @GetMapping("/user")
+    public Result getUserById(@RequestAttribute("id") Integer id) {
+        log.info("获取用户信息,id: " + id);
+
+        User user = userService.getUserById(id);
+
+        if (user == null) return Result.error("用户不存在");
+
+        log.info("用户信息: " + user);
+
+        return Result.success(user);
+    }
+
+    @GetMapping("/user/check_token")
+    public Result checkToken(@RequestHeader("token") String token,@RequestAttribute("id") Integer id,@RequestAttribute("openid") String openid,@RequestAttribute("exp") Integer exp) {
+        log.info("校验token: " + token + "id:" + id + "exp:" + exp);
+
+        HashMap<String, Object> map = new HashMap<String, Object>();
+        map.put("id", id);
+        map.put("openid", openid);
+
+        String newToken = userService.checkToken(map, exp);
+
+        if (newToken == null) return Result.success(token);
+
+        return Result.success(newToken);
+    }
+}

+ 7 - 0
chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/dao/PyDao.java

@@ -0,0 +1,7 @@
+package cn.flea.chexnetmaster.dao;
+
+import cn.flea.chexnetmaster.pojo.Result;
+
+public interface PyDao {
+    public Result processImg(String InputImage, String OutputImage);
+}

+ 73 - 0
chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/dao/impl/PyDaoA.java

@@ -0,0 +1,73 @@
+package cn.flea.chexnetmaster.dao.impl;
+
+import cn.flea.chexnetmaster.dao.PyDao;
+import cn.flea.chexnetmaster.pojo.Result;
+import cn.flea.chexnetmaster.util.FilePathUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.stereotype.Repository;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.IOException;
+import java.util.List;
+
+@Slf4j
+@Repository
+public class PyDaoA implements PyDao {
+    @Autowired
+    private ResourceLoader resourceLoader;
+
+    @Autowired
+    private FilePathUtil filePathUtil;
+
+    private String pyPath = "visual.py";
+    private String imgPath = "test\\";
+    private String workingDir = "\\PythonApp\\";
+    @Override
+    public Result processImg(String InputImage, String OutputImage) {
+        String InputWinImage = ".\\test\\" + InputImage;
+        String OutputWinImage = ".\\test\\" + OutputImage;
+
+        try {
+            // 使用 Runtime 类执行 Python 脚本
+            String cmd = "cmd /c cd /d " + filePathUtil.getProjectPath() + this.workingDir + " && python " + pyPath + " " + InputWinImage + " " + OutputWinImage;
+
+            log.info("执行 Python 脚本:" + cmd);
+
+            Process process = Runtime.getRuntime().exec(cmd);
+
+            // 使用 try-with-resources 确保资源被正确关闭
+            try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
+                 BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
+
+                // 读取 Python 脚本的标准输出
+                String line;
+                StringBuilder pyOut = new StringBuilder();
+                while ((line = reader.readLine()) != null) {
+                    log.debug(line);
+                    pyOut.append(line + "<br>");
+                }
+                log.info("Python 脚本输出:" + pyOut);
+                // 读取 Python 脚本的错误输出
+                while ((line = errorReader.readLine()) != null) {
+                    System.err.println(line);
+                }
+
+                // 等待子进程结束
+                int exitCode = process.waitFor();
+                if (exitCode != 0) {
+                    return Result.error("Python script failed with exit code: " + exitCode);
+                }
+                return Result.success(List.of(pyOut.toString()));
+//                String OutputImageWinPath = filePathUtil.getFileWinPath(imgPath + OutputImage);
+//                return Result.success(List.of(pyOut.toString(),OutputImage));
+            }
+
+        } catch (IOException | InterruptedException e) {
+            e.printStackTrace();
+            return Result.error("Error occurred: " + e.getMessage());
+        }
+    }
+
+}

+ 13 - 0
chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/dto/InteractMsg.java

@@ -0,0 +1,13 @@
+package org.lp.medicalai.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class InteractMsg {
+    private MsgDTO userMsg;
+    private MsgDTO assistantMsg;
+}

+ 43 - 0
chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/dto/MsgDTO.java

@@ -0,0 +1,43 @@
+package org.lp.medicalai.dto;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class MsgDTO {
+    /**
+     * 角色
+     */
+    private String role;
+    /**
+     * 消息内容
+     */
+    private String content;
+    /**
+     * 响应结果字段:结果序号,取值为[0,10]; 当前为保留字段,开发者可忽略
+     */
+    private Integer index;
+
+    public static final String ROLE_USER = "user";
+    public static final String ROLE_System = "system";
+    public static final String ROLE_ASSISTANT = "assistant";
+
+
+    public static MsgDTO createSystemMsg(String tip){
+        return new MsgDTO(ROLE_System,tip,null);
+    }
+
+    public static MsgDTO createUserMsg(String content){
+        return new MsgDTO(ROLE_USER,content,null);
+    }
+
+    public static MsgDTO createAssistantMsg(String content){
+        return new MsgDTO(ROLE_ASSISTANT,content,null);
+    }
+
+}

+ 122 - 0
chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/dto/RecordsArray.java

@@ -0,0 +1,122 @@
+package org.lp.medicalai.dto;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+public class RecordsArray {
+
+    /**
+     * 操作锁,默认 false 即未锁
+     * true-已锁
+     * false-未锁
+     */
+    private boolean lock;
+
+    /**
+     * 记录状态,默认为 0
+     * 0: 最新记录
+     * 1: 最近一次交互在 10 分钟前
+     * 2: 最近一次交互在 20 分钟前
+     * ...
+     * 设置最大在 60 分钟后消息被销毁
+     */
+    private int status;
+
+    /**
+     * 表示数组的最大容量,始终留一个空间作为 rear 标记点,实际的允许的交互记录大小为 arraySize-1
+     */
+    private final int arrayMaxSize;
+
+    /**
+     * front 初始值为 0,指向队列的第一个元素, 也就是说 interactMsgArr[front] 就是队列的第一个元素
+     */
+    private int front;
+
+    /**
+     * 队列尾,rear的初始值 = 0,指向队列的最后一个元素的后一个位置,因为希望空出一个空间做为约定
+     */
+    private int rear;
+
+    /**
+     * 存放交互记录,模拟队列
+     */
+    private final InteractMsg[] interactMsgArr;
+
+    public RecordsArray(int maxInteractCount) {
+        // +1 留出一个空间作为 rear 标记点
+        this.arrayMaxSize = maxInteractCount + 1;
+        this.interactMsgArr = new InteractMsg[arrayMaxSize];
+    }
+
+    /**
+     * 判断队列是否满
+     *
+     * @return true-队满,false-未满
+     */
+    public boolean isFull() {
+        return (rear + 1) % arrayMaxSize == front;
+    }
+
+
+    /**
+     * 添加交互消息
+     */
+    public void addInteractMsg(InteractMsg interactMsg) {
+        // 判断队列是否满
+        if (isFull()) {
+            // 队满,front 向后移动相当于将最旧的数据出队列
+            front = (front + 1) % arrayMaxSize;
+        }
+        // 队列未满,无需将最旧的数据出队列,即 front 无需移动
+
+        // 无论是否队满,新的交互信息还是要添加的,因此 rear 处设置为最新的交互消息,并向后移动
+        interactMsgArr[rear] = interactMsg;
+        rear = (rear + 1) % arrayMaxSize;
+    }
+
+    /**
+     * 获取存储的交互消息
+     */
+    public List<MsgDTO> getAllInteractMsg() {
+        int realSize = size();
+        ArrayList<MsgDTO> msgList = new ArrayList<>(realSize * 2);
+        for (int i = front; i < front + realSize; i++) {
+            InteractMsg interactMsg = interactMsgArr[i % arrayMaxSize];
+            if (interactMsg.getUserMsg() != null) {
+                msgList.add(interactMsg.getUserMsg());
+            }
+            if (interactMsg.getAssistantMsg() != null) {
+                msgList.add(interactMsg.getAssistantMsg());
+            }
+        }
+        return msgList;
+    }
+
+    /**
+     * 求出当前队列有效数据的个数
+     *
+     * @return 有效个数
+     */
+    public int size() {
+        //当rear>=front时,有效个数为rear-front
+        //当rear<front时,有效个数为(maxSize-front)+rear
+        return (rear + arrayMaxSize - front) % arrayMaxSize;
+    }
+
+    public int getStatus() {
+        return status;
+    }
+
+    public void setStatus(int status) {
+        this.status = status;
+    }
+
+    public boolean isLock() {
+        return lock;
+    }
+
+    public void setLock(boolean lock) {
+        this.lock = lock;
+    }
+}

+ 81 - 0
chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/dto/RequestDTO.java

@@ -0,0 +1,81 @@
+package org.lp.medicalai.dto;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@NoArgsConstructor
+@Data
+public class RequestDTO {
+
+    @JsonProperty("header")
+    private HeaderDTO header;
+    @JsonProperty("parameter")
+    private ParameterDTO parameter;
+    @JsonProperty("payload")
+    private PayloadDTO payload;
+
+    @NoArgsConstructor
+    @Data
+    @AllArgsConstructor
+    public static class HeaderDTO {
+        /**
+         * 应用appid,从开放平台控制台创建的应用中获取
+         */
+        @JSONField(name = "app_id")
+        private String appId;
+        /**
+         * 应用appid,从开放平台控制台创建的应用中获取
+         */
+        @JSONField(name = "uid")
+        private String uid;
+    }
+
+    @NoArgsConstructor
+    @Data
+    @AllArgsConstructor
+    public static class ParameterDTO {
+        private ChatDTO chat;
+
+        @NoArgsConstructor
+        @Data
+        @AllArgsConstructor
+        public static class ChatDTO {
+            /**
+             * 指定访问的领域,general指向V1.5版本 generalv2指向V2版本。注意:不同的取值对应的url也不一样!
+             */
+            @JsonProperty("domain")
+            private String domain;
+            /**
+             * 核采样阈值。用于决定结果随机性,取值越高随机性越强即相同的问题得到的不同答案的可能性越高
+             */
+            @JsonProperty("temperature")
+            private Float temperature;
+            /**
+             * 模型回答的tokens的最大长度
+             */
+            @JSONField(name = "max_tokens")
+            private Integer maxTokens;
+        }
+    }
+
+    @NoArgsConstructor
+    @Data
+    @AllArgsConstructor
+    public static class PayloadDTO {
+        @JsonProperty("message")
+        private MessageDTO message;
+
+        @NoArgsConstructor
+        @Data
+        @AllArgsConstructor
+        public static class MessageDTO {
+            @JsonProperty("text")
+            private List<MsgDTO> text;
+        }
+    }
+}

+ 107 - 0
chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/dto/ResponseDTO.java

@@ -0,0 +1,107 @@
+package org.lp.medicalai.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@NoArgsConstructor
+@Data
+public class ResponseDTO {
+
+    @JsonProperty("header")
+    private HeaderDTO header;
+    @JsonProperty("payload")
+    private PayloadDTO payload;
+
+    @NoArgsConstructor
+    @Data
+    public static class HeaderDTO {
+        /**
+         * 错误码,0表示正常,非0表示出错
+         */
+        @JsonProperty("code")
+        private Integer code;
+        /**
+         * 会话是否成功的描述信息
+         */
+        @JsonProperty("message")
+        private String message;
+        /**
+         * 会话的唯一id,用于讯飞技术人员查询服务端会话日志使用,出现调用错误时建议留存该字段
+         */
+        @JsonProperty("sid")
+        private String sid;
+        /**
+         * 会话状态,取值为[0,1,2];0代表首次结果;1代表中间结果;2代表最后一个结果
+         */
+        @JsonProperty("status")
+        private Integer status;
+    }
+
+    @NoArgsConstructor
+    @Data
+    public static class PayloadDTO {
+        @JsonProperty("choices")
+        private ChoicesDTO choices;
+        /**
+         * 在最后一次结果返回
+         */
+        @JsonProperty("usage")
+        private UsageDTO usage;
+
+        @NoArgsConstructor
+        @Data
+        public static class ChoicesDTO {
+            /**
+             * 文本响应状态,取值为[0,1,2]; 0代表首个文本结果;1代表中间文本结果;2代表最后一个文本结果
+             */
+            @JsonProperty("status")
+            private Integer status;
+            /**
+             * 返回的数据序号,取值为[0,9999999]
+             */
+            @JsonProperty("seq")
+            private Integer seq;
+            /**
+             * 响应文本
+             */
+            @JsonProperty("text")
+            private List<MsgDTO> text;
+
+        }
+
+        @NoArgsConstructor
+        @Data
+        public static class UsageDTO {
+            @JsonProperty("text")
+            private TextDTO text;
+
+            @NoArgsConstructor
+            @Data
+            public static class TextDTO {
+                /**
+                 * 保留字段,可忽略
+                 */
+                @JsonProperty("question_tokens")
+                private Integer questionTokens;
+                /**
+                 * 包含历史问题的总tokens大小
+                 */
+                @JsonProperty("prompt_tokens")
+                private Integer promptTokens;
+                /**
+                 * 回答的tokens大小
+                 */
+                @JsonProperty("completion_tokens")
+                private Integer completionTokens;
+                /**
+                 * prompt_tokens和completion_tokens的和,也是本次交互计费的tokens大小
+                 */
+                @JsonProperty("total_tokens")
+                private Integer totalTokens;
+            }
+        }
+    }
+}

+ 57 - 0
chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/listener/XfXhWebSocketListener.java

@@ -0,0 +1,57 @@
+package org.lp.medicalai.listener;
+
+import com.alibaba.fastjson.JSONObject;
+import org.lp.medicalai.dto.MsgDTO;
+import org.lp.medicalai.dto.ResponseDTO;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.Response;
+import okhttp3.WebSocket;
+import okhttp3.WebSocketListener;
+import org.jetbrains.annotations.NotNull;
+import org.springframework.lang.Nullable;
+
+@Data
+@Slf4j
+public class XfXhWebSocketListener extends WebSocketListener {
+    private StringBuilder answer = new StringBuilder();
+
+    private boolean wsCloseFlag = false;
+
+    @Override
+    public void onOpen(@NotNull WebSocket webSocket, @NotNull Response response) {
+        super.onOpen(webSocket, response);
+    }
+
+    @Override
+    public void onMessage(@NotNull WebSocket webSocket, @NotNull String text) {
+        super.onMessage(webSocket, text);
+        ResponseDTO responseData = JSONObject.parseObject(text, ResponseDTO.class);
+        if (responseData.getHeader().getCode() != 0) {
+            // 日志记录
+            log.error("发生错误,错误码为:" + responseData.getHeader().getCode() + "; " + "信息:" + responseData.getHeader().getMessage());
+            // 记录信息
+            this.answer = new StringBuilder("大模型响应错误,请稍后再试");
+            wsCloseFlag = true;
+            return;
+        }
+        // 将回答进行拼接
+        for (MsgDTO msgDTO : responseData.getPayload().getChoices().getText()) {
+            this.answer.append(msgDTO.getContent());
+        }
+        // 对最后一个文本结果进行处理
+        if (2 == responseData.getHeader().getStatus()) {
+            wsCloseFlag = true;
+        }
+    }
+
+    @Override
+    public void onFailure(@NotNull WebSocket webSocket, @NotNull Throwable t, @Nullable Response response) {
+        super.onFailure(webSocket, t, response);
+    }
+
+    @Override
+    public void onClosed(@NotNull WebSocket webSocket, int code, @NotNull String reason) {
+        super.onClosed(webSocket, code, reason);
+    }
+}

+ 30 - 0
chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/pojo/Result.java

@@ -0,0 +1,30 @@
+package cn.flea.chexnetmaster.pojo;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+
+public class Result {
+    private Integer code;//1 成功 , 0 失败
+    private String msg;  //提示信息
+    private Object data; //数据 data
+
+    public static Result success(String msg, Object data) {
+        return new Result(1, msg, data);
+    }
+    public static Result success(Object data) {
+        return new Result(1, "success", data);
+    }
+
+    public static Result success() {
+        return new Result(1, "success", null);
+    }
+
+    public static Result error(String msg) {
+        return new Result(0, msg, null);
+    }
+}

+ 9 - 0
chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/service/ContentCheckService.java

@@ -0,0 +1,9 @@
+package cn.flea.chexnetmaster.service;
+
+import org.springframework.web.multipart.MultipartFile;
+
+public interface ContentCheckService {
+    Integer textCheck(String text, String openid);
+
+    Integer imgCheck(MultipartFile img, String openid);
+}

+ 8 - 0
chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/service/ProcessImgService.java

@@ -0,0 +1,8 @@
+package cn.flea.chexnetmaster.service;
+
+import cn.flea.chexnetmaster.pojo.Result;
+import org.springframework.web.multipart.MultipartFile;
+
+public interface ProcessImgService {
+    public Result processImg(MultipartFile img, String chexnetMasterImgApiUrl);
+}

+ 7 - 0
chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/service/PyService.java

@@ -0,0 +1,7 @@
+package cn.flea.chexnetmaster.service;
+
+import cn.flea.chexnetmaster.pojo.Result;
+
+public interface PyService {
+    public Result processImg(String InputImage);
+}

+ 17 - 0
chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/service/UserService.java

@@ -0,0 +1,17 @@
+package cn.flea.chexnetmaster.service;
+
+import cn.flea.chexnetmaster.pojo.Result;
+import cn.flea.chexnetmaster.pojo.User;
+
+import java.util.HashMap;
+
+public interface UserService {
+    public Result login(String code);
+    public int addUser(User user);
+    public int deleteUser(String wechatOpenid);
+    public int updateUserById(User user);
+    public int updateUserByOpenid(User user);
+    public User getUserById(Integer id);
+    public User getUserByOpenid(String wechatOpenid);
+    public String checkToken(HashMap<String, Object> map, Integer exp);
+}

+ 67 - 0
chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/service/impl/ContentCheckServiceA.java

@@ -0,0 +1,67 @@
+package cn.flea.chexnetmaster.service.impl;
+
+import cn.flea.chexnetmaster.service.ContentCheckService;
+import cn.flea.chexnetmaster.util.WeiXinUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.Map;
+
+@Slf4j
+@Service
+public class ContentCheckServiceA implements ContentCheckService {
+    @Autowired
+    private WeiXinUtil weiXinUtil;
+
+    /**
+     * 文本检测, 返回0为检测失败,1为文本正常,2为文本违规
+     *
+     * @param text
+     * @param openid
+     * @return
+     */
+    @Override
+    public Integer textCheck(String text, String openid) {
+        Map<String, Object> map = weiXinUtil.textCheck(text, openid);
+        log.info(text + "文本检测结果: " + map);
+
+        try {
+            Integer res = ((Double) map.get("errcode")).intValue();
+            if (res == 0) {
+                map = (Map<String, Object>) map.get("result");
+                if (map.get("suggest").equals("pass")) return 1;
+                return 2;
+            }
+        } catch (Exception e) {
+
+        }
+
+        return 0;
+    }
+
+    /**
+     * 图片检测, 返回0为检测失败,1为图片正常,2为图片违规
+     *
+     * @param img
+     * @param openid
+     * @return
+     */
+
+    @Override
+    public Integer imgCheck(MultipartFile img, String openid) {
+        Map<String, Object> map = weiXinUtil.imgCheck(img);
+        log.info("图片检测结果: " + map);
+
+        try {
+            Integer res = ((Double) map.get("errcode")).intValue();
+            if (res == 0) return 1;
+            if (res == 87014) return 2;
+        } catch (Exception e) {
+
+        }
+
+        return 0;
+    }
+}

+ 50 - 0
chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/service/impl/ProcessImgServiceB.java

@@ -0,0 +1,50 @@
+package cn.flea.chexnetmaster.service.impl;
+
+import cn.flea.chexnetmaster.pojo.Result;
+import cn.flea.chexnetmaster.service.ProcessImgService;
+import cn.flea.chexnetmaster.util.FileUploadUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.Map;
+
+@Slf4j
+@Service
+public class ProcessImgServiceB implements ProcessImgService {
+
+//    @Autowired
+//    private GetChexnetMasterImgApiUrl getChexnetMasterImgApiUrl;
+//
+//    @PostConstruct
+//    public void init() {
+//        chexnetMasterImgApiUrl = getChexnetMasterImgApiUrl.getChexnetMasterImgApiUrl();
+//    }
+
+    @Override
+    public Result processImg(MultipartFile img, String chexnetMasterImgApiUrl) {
+        try {
+            Map<String, Object> res = FileUploadUtil.uploadFileMap(chexnetMasterImgApiUrl, img, "img");
+            log.info("图片处理结果:" + res.get("code") + res.get("msg"));
+            Result result = new Result();
+            // 检查并转换 code 值
+            Object codeObj = res.get("code");
+            if (codeObj instanceof Integer) {
+                result.setCode((Integer) codeObj);
+            } else if (codeObj instanceof Double) {
+                result.setCode(((Double) codeObj).intValue());
+            } else {
+                throw new IllegalArgumentException("Unexpected type for code: " + codeObj.getClass().getName());
+            }
+            result.setMsg((String) res.get("msg"));
+            result.setData(res.get("data"));
+
+            log.info("处理图片完成");
+            return result;
+
+        } catch (Exception e){
+            log.error("图片处理失败");
+            return Result.error("图片处理失败");
+        }
+    }
+}

+ 28 - 0
chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/service/impl/PyServiceA.java

@@ -0,0 +1,28 @@
+package cn.flea.chexnetmaster.service.impl;
+
+import cn.flea.chexnetmaster.dao.PyDao;
+import cn.flea.chexnetmaster.pojo.Result;
+import cn.flea.chexnetmaster.service.PyService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class PyServiceA implements PyService {
+    @Autowired
+    private PyDao pyDao;
+    @Override
+    public Result processImg(String InputImage) {
+        String OutputImage = this.modifyFileName(InputImage);
+        return pyDao.processImg(InputImage, OutputImage);
+    }
+
+    public static String modifyFileName(String fileName) {
+        int dotIndex = fileName.lastIndexOf(".");
+        if (dotIndex == -1) {
+            return fileName + "_out"; // 如果没有扩展名,直接添加_out
+        }
+        String namePart = fileName.substring(0, dotIndex);
+        String extensionPart = fileName.substring(dotIndex);
+        return namePart + "_out" + extensionPart;
+    }
+}

+ 123 - 0
chexnet-master-Server/src/main/java/cn/flea/chexnetmaster/service/impl/UserServiceA.java

@@ -0,0 +1,123 @@
+package cn.flea.chexnetmaster.service.impl;
+
+import cn.flea.chexnetmaster.mapper.UserMapper;
+import cn.flea.chexnetmaster.pojo.Result;
+import cn.flea.chexnetmaster.pojo.User;
+import cn.flea.chexnetmaster.service.UserService;
+import cn.flea.chexnetmaster.util.JwtUtil;
+import cn.flea.chexnetmaster.util.WeiXinUtil;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.HashMap;
+
+@Slf4j
+@Service
+public class UserServiceA implements UserService {
+    @Autowired
+    WeiXinUtil weiXinUtil;
+
+    @Autowired
+    UserMapper userMapper;
+
+    @Override
+    public Result login(String code) {
+        String res = weiXinUtil.getOpenid(code);
+        log.info(res);
+        try {
+            ObjectMapper objectMapper = new ObjectMapper();
+            // 将String解析为JsonNode
+            JsonNode jsonNode = objectMapper.readTree(res);
+
+            String openid = jsonNode.get("openid").asText();
+            String sessionKey = jsonNode.get("session_key").asText();
+            String unionid = jsonNode.get("unionid").asText();
+
+            User user = userMapper.getUserByOpenid(openid);
+
+            if (user == null) {
+                log.info("用户不存在,创建新用户");
+
+                user = new User();
+                user.setName("微信用户");
+                user.setSessionKey(sessionKey);
+                user.setWechatOpenid(openid);
+                user.setWechatUnionid(unionid);
+                Integer id = addUser(user);
+
+                HashMap<String, Object> map = new HashMap<String, Object>();
+                map.put("id", id);
+                map.put("openid", openid);
+
+                HashMap<String, String> info = new HashMap<String, String>();
+                info.put("name", user.getName());
+                info.put("token",JwtUtil.getJwt(map));
+
+                return Result.success("new", info);
+            } else {
+                log.info("用户已存在,更新用户session_key");
+                user.setSessionKey(sessionKey);
+                Integer id = updateUserByOpenid(user);
+
+                HashMap<String, Object> map = new HashMap<String, Object>();
+                map.put("id", id);
+                map.put("openid", openid);
+
+                HashMap<String, String> info = new HashMap<String, String>();
+                info.put("name", user.getName());
+                info.put("token",JwtUtil.getJwt(map));
+
+                return Result.success("old", info);
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return Result.error("登录失败");
+    }
+
+    public int addUser(User user) {
+        userMapper.addUser(user);
+        int id = user.getId();
+        log.info("用户添加成功:" + id);
+        return id;
+    }
+
+    @Override
+    public int deleteUser(String wechatOpenid) {
+        return userMapper.deleteUserByOpenid(wechatOpenid);
+    }
+
+    @Override
+    public int updateUserByOpenid(User user) {
+        userMapper.updateUserByOpenid(user);
+        int id = user.getId();
+        log.info("用户更新成功:" + id);
+        return id;
+    }
+
+    @Override
+    public int updateUserById(User user) {
+        return userMapper.updateUserById(user);
+    }
+
+    @Override
+    public User getUserByOpenid(String wechatOpenid) {
+        return userMapper.getUserByOpenid(wechatOpenid);
+    }
+
+    @Override
+    public String checkToken(HashMap<String, Object> map, Integer exp) {
+
+        if (exp < 24 * 60 * 60 * 1000) return JwtUtil.getJwt(map);
+
+        return null;
+    }
+
+    @Override
+    public User getUserById(Integer id) {
+        return userMapper.getUserById(id);
+    }
+}

+ 0 - 0
chexnet-master-Server/src/main/resources/application.yml


+ 0 - 39
test.md

@@ -1,39 +0,0 @@
-# Markdown
-
-<h1>项目标题1</h1>
-<h1>项目标题2</h1>
-
-<ul>
-<li>项目名称</li>
-<li>项目名称</li>
-<li>项目名称</li>
-</ul>
-
-# 项目标题1
-- 项目名称
-- 项目作者
-- 项目类型
-
-# 项目标题2
-
-```plantuml
-@startuml
-abstract        abstract
-abstract class  "abstract class"
-annotation      annotation
-circle          circle
-()              circle_short_form
-class           class
-class           class_stereo  <<stereotype>>
-diamond         diamond
-<>              diamond_short_form
-entity          entity
-enum            enum
-exception       exception
-interface       interface
-metaclass       metaclass
-protocol        protocol
-stereotype      stereotype
-struct          struct
-@enduml
-```

+ 0 - 330
test/README.md

@@ -1,330 +0,0 @@
-# XXX项目策划书
-- 姓名 刘同学
-- 学号 20240001
-- 班级 软工221班
-- 手机 18600001111
-
-# 智疗项目策划书
-
-- 姓名:龚灵兮
-- 学号:202226701022
-- 班级:22级大数据班
-- 联系电话:18720299917
-
-## 项目设想
-
-针对医疗健康领域,为忙碌的人群提供便捷的线上问诊服务。通过智能诊断系统快速分析病理,实现精准医疗咨询。
-
-# 项目设想
-01*
-一句话描述项目设想(行业领域、用户画像、核心功能)
-(行业)社交活动规划领域,满足人们对高质量社交的需求。(用户)社交活跃者、上班族、学生等缺乏时间或创意规划社交活动的人群。(功能)利用AGI智能规划聚会、旅游等社交活动,包括推荐地点、安排行程和预订场地。
-
-02
-项目的业务流程
-1.用户信息收集:用户输入个人兴趣、社交圈子信息、空闲时间、预算等,系统也可通过分析用户过往社交数据来获取相关信息。
-2.需求明确:用户提出社交活动类型需求,如聚会、旅游、户外运动等,以及特殊要求,如主题、参与人数范围等。
-3.数据整合与分析:系统整合旅游景点、娱乐场所、餐厅等相关信息,结合用户输入和需求,利用算法分析最佳匹配方案。
-4.规划生成:根据分析结果生成社交活动规划,包括活动时间、地点推荐、行程安排、交通建议和预算分配。
-5.方案展示与调整:向用户展示规划方案,用户可提出修改意见,系统根据反馈快速调整。
-6.预订与提醒:用户确认后,系统自动预订相关服务,并在活动前适时提醒用户各项准备事宜。
-7.活动后反馈收集:活动结束后,收集用户反馈,用于改进后续规划。
-
-03
-项目的商业模式
-付费订阅模式:用户支付一定的费用,如每月或每年订阅,获取不同等级的社交活动规划服务,等级越高,规划的活动越复杂精细。
-按次收费模式:针对只偶尔需要规划社交活动的用户,每次使用服务时支付相应费用。
-商家合作分成:与旅游景点、酒店、餐厅、娱乐场所等商家合作,推荐用户消费,从用户消费金额中获取一定比例的分成。
-广告收入模式:在向用户展示规划方案时,适当展示与社交活动相关的优质产品或服务广告,收取广告商费用。
-增值服务收费:例如为用户提供活动现场的特殊布置、摄影摄像、礼品采购等增值服务,并收取额外费用。
-
-04
-相对可行的策略
-初期策略:
-聚焦特定用户群体:先针对上班族或大学生等有较强社交需求且消费能力不错的群体,开发出符合他们兴趣和预算的社交活动规划模板。
-提供免费试用与口碑营销:为新用户提供有限次数的免费试用,鼓励他们分享体验,通过口碑吸引更多潜在用户。
-与本地商家合作推广:和当地热门的餐厅、KTV、轰趴馆等商家联合举办小型活动,在活动中展示规划方案的优势。
-中期策略:
-优化用户体验:根据用户反馈不断改进规划算法,提高活动规划的精准度和个性化程度。
-拓展服务内容:增加如特殊主题派对、亲子社交活动等新的活动类型,满足更多用户需求。
-推出会员制度:设置不同等级会员,提供如优先规划、更多修改次数、专属客服等特权,激励用户升级会员。
-长期策略:
-跨地区拓展:在本地模式成熟后,将业务拓展到其他城市,与当地商家建立合作,复制成功经验。
-数据驱动发展:深入分析用户数据,挖掘用户潜在需求,开发新的功能和服务,如根据用户社交圈子变化自动推荐新的活动类型。
-打造社交平台:不仅仅是规划活动,还构建用户交流社交活动心得、分享照片等功能的平台,增强用户粘性。
-05
-用AI为项目起个好名字
-友聚蓝图
-
-
-
-# 一、项目背景
-
-## 政策背景
-> 用关键字:兴趣、社交,搜索相关的政策和扶持
-
-- 2022年1月12日,《国务院关于印发“十四五”数字经济发展规划的通知》发布,指出数字化服务是满足人民美好生活需要的重要途径。
-- 2022年12月14日,中共中央、国务院印发《扩大内需战略规划纲要(2022-2035年)》,提出扩大文化和旅游消费。完善现代文化产业体系和文化市场体系,推进优质文化资源开发,推动中华优秀传统文化创造性转化、创新性发展。鼓励文化文物单位依托馆藏文化资源,开发各类文化创意产品,扩大优质文化产品和服务供给。加快培育新型消费。支持线上线下商品消费融合发展。加快传统线下业态数字化改造和转型升级。深入发展在线文娱,鼓励传统线下文化娱乐业态线上化,支持打造数字精品内容和新兴数字资源传播平台。支持线上多样化社交、短视频平台规范有序发展,鼓励微应用、微产品、微电影等创新。
-
-## 行业背景
-
-- 《2024-2030年中国社交网络行业发展前景预测及投资策略研究报告》
-![](/img/1.png)
-![](/img/2.png)
-
-
-
-
-## 市场痛点
-- 社交环节,往往由于不敢开口,导致错过了社交的机会
-- 社交成本高,往往交了很多朋友,最后才发现性格不符
-- 活动组织,活动千篇一律,难以找不到每位参与者的喜好
-
-
-# 二、产品定位
-
-## 用户分析
-- 用户(陌生人社交+共情社交)
-    - 上班族
-    - 学生
-- 商家
-    - 餐饮
-    - 轰趴
-    - KTV
-
-## 主要功能
-- 破冰助手:开启话题,话题灵感助手
-- 友情测试:智能体代理聊天,匹配完成获得友情积分
-- 活动策划:参与成员画像、目标地点特点 => 活动策划方案
-
-
-## 商业模式
-![](/img/3.png)
-
-# 三、可行性测试
-## 破冰助手的提示词相关
-- 开场破冰的打招呼语言
-    > 您作为一名专业的社交心理情感大师,我的性格是较为内向的,我想要认识的朋友是性格外向活泼开朗的。需要您帮我想想应该用怎样的话题开始对话?
-- 根据聊天聊天内容,推荐下一步回答
-
-    ```
-    我:(参加活动迟到了,让其他人等了很久)
-    朋友A:(不耐烦),你去干什么了,怎么现在才来?
-    朋友B:我们已经等了好久了,接下来你来买单。
-    ```
-    我在一次活动中迟到了,上面的历史聊天记录,请您帮我分析下如何沟通能缓解尴尬。
-
-## 友情测试
-- 性格测评
-- 两个智能体互相聊天,到双方认可友情关系的逻辑
-    - 两个智能体会互相出三个问题,根据对方回答的结果,给予评分,最终确认友情值。
-
-## 活动策划
-- 数据搜集
-    - 所有参与成员的画像列表
-    - 所有参与成员周边商家的服务清单
-- 活动的策划
-    - 筛选风险:性格冲突、餐饮忌口、风俗习惯
-    - 活动地点选择:
-        - 给出推荐地点,有所有成员投票选择
-    - 活动流程的策划
-        - 破冰小游戏
-        - 活动参与环节
-        - 活动的奖励机制
-
-> 业务逻辑时序图
-```puml
-@startuml
-actor 参与成员
-participant 数据搜集
-participant 活动策划
-
-参与成员 -> 数据搜集 : 提供画像列表
-参与成员 -> 数据搜集 : 提供周边商家的服务清单
-数据搜集 -> 活动策划 : 提交数据
-
-活动策划 -> 活动策划 : 筛选风险
-活动策划 -> 活动策划 : 性格冲突
-活动策划 -> 活动策划 : 餐饮忌口
-活动策划 -> 活动策划 : 风俗习惯
-
-活动策划 -> 活动策划 : 活动地点选择
-活动策划 -> 参与成员 : 推荐地点
-参与成员 -> 活动策划 : 投票选择地点
-
-活动策划 -> 活动策划 : 活动流程的策划
-活动策划 -> 活动策划 : 破冰小游戏
-活动策划 -> 活动策划 : 活动参与环节
-活动策划 -> 活动策划 : 活动的奖励机制
-
-@enduml
-```
-> 参与成员
-```
-
-1. **用户A**
-   - **性格**: 外向、乐观
-   - **饮食习惯**: 素食主义者
-   - **风俗习惯**: 喜欢参与传统节日庆祝活动
-   - **兴趣爱好**: 旅行、摄影、社交活动
-
-2. **用户B**
-   - **性格**: 内向、谨慎
-   - **饮食习惯**: 不吃海鲜,偏好家常菜
-   - **风俗习惯**: 重视家庭聚会,喜欢分享家族故事
-   - **兴趣爱好**: 阅读、电影、绘画
-
-3. **用户C**
-   - **性格**: 开朗、幽默
-   - **饮食习惯**: 喜欢尝试新事物,能吃辣
-   - **风俗习惯**: 喜欢参与社区活动,热衷公益
-   - **兴趣爱好**: 音乐、运动、派对
-
-4. **用户D**
-   - **性格**: 冷静、分析型
-   - **饮食习惯**: 偏好低糖低脂饮食
-   - **风俗习惯**: 重视个人空间,不喜欢过于喧闹的场合
-   - **兴趣爱好**: 科技、科学、棋类游戏
-
-5. **用户E**
-   - **性格**: 热情、积极
-   - **饮食习惯**: 不忌口,喜欢各种美食
-   - **风俗习惯**: 喜欢参与舞蹈和音乐节
-   - **兴趣爱好**: 舞蹈、社交、烹饪
-
-6. **用户F**
-   - **性格**: 内向、细腻
-   - **饮食习惯**: 不吃牛肉,偏好清淡饮食
-   - **风俗习惯**: 喜欢安静的聚会,重视礼仪
-   - **兴趣爱好**: 写作、手工艺、瑜伽
-
-7. **用户G**
-   - **性格**: 冒险、冲动
-   - **饮食习惯**: 喜欢尝试各种异国料理
-   - **风俗习惯**: 热衷于极限运动和冒险活动
-   - **兴趣爱好**: 登山、滑雪、旅行
-
-8. **用户H**
-   - **性格**: 理性、务实
-   - **饮食习惯**: 偏好健康饮食,注重营养搭配
-   - **风俗习惯**: 喜欢参加专业研讨会,重视知识分享
-   - **兴趣爱好**: 学习、研究、健身
-
-```
-> 活动地点信息
-
-```
-
-1. **商家/地点A:素食餐厅**
-   - **适配用户**: 用户A(素食主义者)、用户B(偏好家常菜)
-   - **不适配用户**: 用户C(喜欢尝试新事物)、用户G(喜欢异国料理)
-
-2. **商家/地点B:高档酒吧**
-   - **适配用户**: 用户C(热衷社交活动)、用户E(热情积极)
-   - **不适配用户**: 用户D(重视个人空间)、用户F(喜欢安静的聚会)
-
-3. **商家/地点C:传统文化体验中心**
-   - **适配用户**: 用户A(喜欢传统节日)、用户B(重视家庭聚会)
-   - **不适配用户**: 用户G(热衷极限运动)、用户H(偏好专业研讨会)
-
-4. **商家/地点D:极限运动俱乐部**
-   - **适配用户**: 用户G(冒险、冲动)、用户C(喜欢运动)
-   - **不适配用户**: 用户D(冷静、分析型)、用户F(内向、细腻)
-
-5. **商家/地点E:艺术工作坊**
-   - **适配用户**: 用户B(喜欢绘画)、用户F(手工艺)
-   - **不适配用户**: 用户D(务实、理性)、用户H(重视知识分享)
-
-6. **商家/地点F:健身中心**
-   - **适配用户**: 用户H(健身爱好者)、用户E(热情积极)
-   - **不适配用户**: 用户B(内向、谨慎)、用户F(喜欢安静的聚会)
-
-7. **商家/地点G:社区公益活动**
-   - **适配用户**: 用户C(热衷公益)、用户A(参与传统活动)
-   - **不适配用户**: 用户D(重视个人空间)、用户F(内向、细腻)
-
-8. **商家/地点H:高科技体验馆**
-   - **适配用户**: 用户H(科技爱好者)、用户D(分析型)
-   - **不适配用户**: 用户B(偏好传统活动)、用户F(内向、细腻)
-
-```
-
-> 活动策划生成
-上面是已经搜集的数据,有所有参与成员的画像列表,还有所有参与成员周边商家的服务清单。
-请您作为一名专业的活动策划大师,根据用户和商家
-1.筛选风险:性格冲突、餐饮忌口、风俗习惯
-2.活动地点选择
-3.活动流程的策划
-4.破冰小游戏
-5.活动参与环节
-6.活动的奖励设计
-
-## 完整的活动策划方案
-根据提供的参与成员画像和活动地点信息,以下是活动策划的详细方案:
-
-### 1. 筛选风险
-在选择活动地点时,需要考虑以下风险:
-
-- **性格冲突**:
-  - 用户A(外向、乐观)与用户D(冷静、分析型)在社交活动中可能会有冲突。
-  - 用户B(内向、谨慎)与用户C(开朗、幽默)在互动中可能存在不适应。
-
-- **餐饮忌口**:
-  - 用户B不吃海鲜,用户F不吃牛肉,用户A是素食主义者。
-  - 选择餐饮时需确保适合所有人的饮食习惯。
-
-- **风俗习惯**:
-  - 用户D重视个人空间,不适合过于喧闹的场合。
-  - 用户H偏好专业研讨会,不适合过于轻松的社交活动。
-
-### 2. 活动地点选择
-综合考虑风险,推荐以下活动地点:
-
-- **商家/地点C:传统文化体验中心**
-  - 适合用户A(喜欢传统节日)、用户B(重视家庭聚会)。
-  - 适合性格较为温和的用户,能够提供较为安静的环境,避免冲突。
-
-### 3. 活动流程的策划
-- **活动时间**:周末下午2点至5点
-- **活动流程**:
-  1. **开场介绍(15分钟)**:主持人介绍活动背景和参与成员。
-  2. **破冰小游戏(30分钟)**:通过轻松的互动游戏让成员相互熟悉。
-  3. **传统文化体验(1小时)**:参与传统手工艺制作或文化讲座。
-  4. **茶歇交流(30分钟)**:提供素食茶点,成员自由交流。
-  5. **总结分享(15分钟)**:每位成员分享活动感受和收获。
-
-### 4. 破冰小游戏
-- **“真心话大冒险”**:每位成员轮流选择“真心话”或“大冒险”,通过轻松的问题或小挑战让大家熟悉彼此。
-- **“人际网络”**:每位成员用线将自己与其他人连接,分享与其他成员的共同点,增进了解。
-
-### 5. 活动参与环节
-- **传统文化体验**:安排手工艺品制作(如编织、书法等),让参与者在互动中增进了解。
-- **分享环节**:每位成员可以分享自己与传统文化的故事或经历,促进交流。
-
-### 6. 活动的奖励设计
-- **参与奖**:每位参与者都能获得小礼品(如手工艺品)。
-- **最佳分享奖**:对分享内容丰富、引发共鸣的成员给予小奖品(如文化体验券)。
-- **幸运抽奖**:设置抽奖环节,奖品可以是参与商家的优惠券或体验券,鼓励大家继续参与相关活动。
-
-### 总结
-通过以上策划,活动能够在考虑参与者的性格、饮食习惯和风俗习惯的基础上,提供一个愉快且有意义的交流平台,促进成员之间的互动和了解。
-
-# 四、产品结构
-
-## 竞品分析
-1. 寻找兴趣社交的三款App,体验其中核心功能
-    - Soul(参考兴趣交友的用户画像和资料的信息结构)
-3. 寻找活动预约和报名App至少1款,体验其中核心功能
-    - 活动行(参考活动报名的UI界面流程)
-
-## 产品结构图
-
-
-## 信息结构图
-
-
-
-
-# FAQ:项目起名
-> [color=red]项目名称的生成:
-> {{项目策划文档}},作为一名创意大师,请帮我设计本项目的名称,并且要突出XXXXXX。

+ 0 - 0
# 智能胸康项目策划.md → 智能胸康项目策划.md


+ 0 - 82
项目/tab2.page.html

@@ -1,82 +0,0 @@
-<ion-header>
-  <ion-toolbar>
-    <!-- 左侧 Logo -->
-    <ion-title>
-      <ion-icon name="logo-ionic" slot="start"></ion-icon>
-      <span>品牌名称</span>
-    </ion-title>
-    
-    <!-- 右侧 用户头像 -->
-    <ion-buttons slot="end">
-      <ion-avatar>
-        <img src="path_to_avatar.jpg" />
-      </ion-avatar>
-    </ion-buttons>
-  </ion-toolbar>
-</ion-header>
-
-<ion-content>
-  <!-- 主功能区 -->
-  <div class="welcome-section">
-    <h2>欢迎回来,Gorilla!</h2>
-  </div>
-
-  <!-- AI问诊入口 -->
-  <ion-card>
-    <ion-card-header>
-      <ion-card-title>AI问诊</ion-card-title>
-    </ion-card-header>
-    <ion-card-content>
-      <ion-button expand="full" routerLink="/ai-consultation">
-        开始AI问诊
-      </ion-button>
-      <p>根据您的症状,AI将为您提供健康管理建议。</p>
-    </ion-card-content>
-  </ion-card>
-
-  <!-- 病例识别入口 -->
-  <ion-card>
-    <ion-card-header>
-      <ion-card-title>病例识别</ion-card-title>
-    </ion-card-header>
-    <ion-card-content>
-      <ion-button expand="full" routerLink="/case-recognition">
-        上传病例图像
-      </ion-button>
-      <p>上传您的胸部CT图片,AI帮助您分析潜在病症。</p>
-    </ion-card-content>
-  </ion-card>
-
-  <!-- BMI记录入口 -->
-  <ion-card>
-    <ion-card-header>
-      <ion-card-title>BMI记录</ion-card-title>
-    </ion-card-header>
-    <ion-card-content>
-      <ion-button expand="full" routerLink="/bmi-record">
-        记录BMI数据
-      </ion-button>
-      <p>输入您的体重和身高,查看健康曲线。</p>
-    </ion-card-content>
-  </ion-card>
-</ion-content>
-
-<!-- 底部导航栏 -->
-<ion-footer>
-  <ion-toolbar>
-    <ion-buttons>
-      <ion-button routerLink="/home">
-        <ion-icon name="home" slot="icon-only"></ion-icon>
-      </ion-button>
-      <ion-button routerLink="/records">
-        <ion-icon name="document-text" slot="icon-only"></ion-icon>
-      </ion-button>
-      <ion-button routerLink="/settings">
-        <ion-icon name="settings" slot="icon-only"></ion-icon>
-      </ion-button>
-      <ion-button routerLink="/health-data">
-        <ion-icon name="bar-chart" slot="icon-only"></ion-icon>
-      </ion-button>
-    </ion-buttons>
-  </ion-toolbar>
-</ion-footer>

+ 0 - 17
项目/tab2.page.ts

@@ -1,17 +0,0 @@
-import { Component } from '@angular/core';
-import { IonCardTitle,IonAvatar ,IonCardContent,IonIcon ,IonButtons,IonFooter,IonButton, IonCard ,IonHeader, IonToolbar, IonTitle, IonContent, IonCardHeader } from '@ionic/angular/standalone';
-import { ExploreContainerComponent } from '../explore-container/explore-container.component';
-
-
-@Component({
-  selector: 'app-tab2',
-  templateUrl: 'tab2.page.html',
-  styleUrls: ['tab2.page.scss'],
-  standalone: true,
-  imports: [IonCardContent,IonHeader,IonAvatar,IonCardContent,IonCardHeader,IonCardTitle,IonFooter,IonButtons, IonToolbar, IonTitle, IonContent, ExploreContainerComponent,IonHeader,IonCard,IonButton,IonIcon]
-})
-export class Tab2Page {
-
-  constructor() {}
-
-}

+ 0 - 33
项目/tab3.page.html

@@ -1,33 +0,0 @@
-<!-- src/app/tab3/tab3.page.html -->
-<ion-header>
-  <ion-toolbar>
-    <ion-title>BMI记录</ion-title>
-  </ion-toolbar>
-</ion-header>
-
-<ion-content class="ion-padding">
-  <!-- 身高输入 -->
-  <ion-item>
-    <ion-label position="floating">身高 (cm)</ion-label>
-    <ion-input type="number" (ionInput)="onHeightInput($event)"></ion-input>
-  </ion-item>
-
-  <!-- 体重输入 -->
-  <ion-item>
-    <ion-label position="floating">体重 (kg)</ion-label>
-    <ion-input type="number" (ionInput)="onWeightInput($event)"></ion-input>
-  </ion-item>
-
-  <!-- 计算BMI按钮 -->
-  <ion-button expand="full" (click)="calculateBMI()">计算BMI</ion-button>
-
-  <!-- 显示BMI结果 -->
-  <ion-card *ngIf="bmi !== null">
-    <ion-card-header>
-      <ion-card-title>您的BMI</ion-card-title>
-    </ion-card-header>
-    <ion-card-content>
-      <p>您的BMI为{{ bmi | number:'1.1-1' }},属于‘{{ healthStatus }}’范围。</p>
-    </ion-card-content>
-  </ion-card>
-</ion-content>

+ 0 - 47
项目/tab3.page.ts

@@ -1,47 +0,0 @@
-import { Component } from '@angular/core';
-import {IonItem,IonInput, IonCardTitle, IonAvatar, IonCardContent, IonIcon, IonButtons, IonFooter, IonButton, IonCard, IonHeader, IonToolbar, IonTitle, IonContent, IonCardHeader, IonLabel} from '@ionic/angular/standalone';
-import { ExploreContainerComponent } from '../explore-container/explore-container.component';
-import { CommonModule } from '@angular/common';
-
-@Component({
-  selector: 'app-tab3',
-  templateUrl: 'tab3.page.html',
-  styleUrls: ['tab3.page.scss'],
-  standalone: true,
-  imports: [CommonModule,IonInput,IonItem,IonLabel,IonCardContent,IonHeader,IonAvatar,IonCardContent,IonCardHeader,IonCardTitle,IonFooter,IonButtons, IonToolbar, IonTitle, IonContent, ExploreContainerComponent,IonHeader,IonCard,IonButton,IonIcon]
-})
-export class Tab3Page {
-  height: number | null = null;
-  weight: number | null = null;
-  bmi: number | null = null;
-  healthStatus: string = '';
-
-  // 更新身高
-  onHeightInput(event: any) {
-    this.height = parseFloat(event.target.value);
-  }
-
-  // 更新体重
-  onWeightInput(event: any) {
-    this.weight = parseFloat(event.target.value);
-  }
-
-  // 计算BMI
-  calculateBMI() {
-    if (this.height !== null && this.weight !== null && this.height > 0 && this.weight > 0) {
-      this.bmi = this.weight / ((this.height / 100) ** 2);
-      if (this.bmi < 18.5) {
-        this.healthStatus = '偏瘦';
-      } else if (this.bmi >= 18.5 && this.bmi < 24.9) {
-        this.healthStatus = '正常';
-      } else if (this.bmi >= 25 && this.bmi < 29.9) {
-        this.healthStatus = '偏重';
-      } else {
-        this.healthStatus = '超重';
-      }
-    } else {
-      this.bmi = null;
-      this.healthStatus = '请输入有效的身高和体重。';
-    }
-  }
-}

+ 0 - 0
总项目/前端代码.zip → 项目压缩包/前端代码.zip


+ 0 - 0
总项目/后端代码.zip → 项目压缩包/后端代码.zip