tcm_exam.rb 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. # encoding:utf-8
  2. class TcmExam < ActiveRecord::Base
  3. has_paper_trail
  4. self.table_name = "tcm_exams"
  5. belongs_to :wx_user
  6. belongs_to :scale_user, :foreign_key => :scale_user_id
  7. STATUS_ENUM = [["待处理", "pending"], ["处理中", "processing"], ["已完成", "completed"], ["失败", "failed"]]
  8. def wx_user_contact
  9. return "-" if wx_user.blank?
  10. user = wx_user.user
  11. return "-" if user.blank?
  12. contact = user.tel
  13. contact = user.email if contact.blank?
  14. contact.blank? ? "-" : contact
  15. end
  16. def scale_user_name
  17. return "-" if scale_user.blank?
  18. scale_user.nick_name || "-"
  19. end
  20. # 解析 AI 报告内容
  21. def parsed_ai_summary
  22. return {} if ai_summary.blank?
  23. begin
  24. JSON.parse(ai_summary)
  25. rescue
  26. {}
  27. end
  28. end
  29. # 解析观察图片
  30. def parsed_observation_images
  31. return {} if observation_images.blank?
  32. begin
  33. JSON.parse(observation_images)
  34. rescue
  35. {}
  36. end
  37. end
  38. # 解析问诊答案
  39. def parsed_question_answers
  40. return {} if question_answers.blank?
  41. begin
  42. JSON.parse(question_answers)
  43. rescue
  44. {}
  45. end
  46. end
  47. # 解析最新体脂秤结果
  48. def parsed_latest_scale_result
  49. return {} if latest_scale_result.blank?
  50. begin
  51. JSON.parse(latest_scale_result)
  52. rescue
  53. {}
  54. end
  55. end
  56. # 状态标签
  57. def status_label
  58. case status
  59. when "pending" then "待处理"
  60. when "processing" then "处理中"
  61. when "completed" then "已完成"
  62. when "failed" then "失败"
  63. else status || "-"
  64. end
  65. end
  66. rails_admin do
  67. navigation_label '中医诊疗记录'
  68. parent ScaleDevice
  69. weight 3
  70. list do
  71. filters [:wx_user, :scale_user, :status, :created_at]
  72. field :id do
  73. label 'ID'
  74. end
  75. field :wx_user_id do
  76. label '微信用户ID'
  77. end
  78. field :wx_user do
  79. label '微信用户'
  80. pretty_value do
  81. bindings[:object].wx_user_contact
  82. end
  83. end
  84. field :scale_user_id do
  85. label '称用户ID'
  86. end
  87. field :scale_user do
  88. label '称用户名'
  89. pretty_value do
  90. bindings[:object].scale_user_name
  91. end
  92. end
  93. field :status do
  94. label '状态'
  95. pretty_value do
  96. bindings[:object].status_label
  97. end
  98. end
  99. field :ai_summary do
  100. label '报告详情'
  101. formatted_value do
  102. exam = bindings[:object]
  103. bindings[:view].link_to('查看报告', bindings[:view].rails_admin.show_path(model_name: 'tcm_exam', id: exam.id), class: 'btn btn-info btn-sm')
  104. end
  105. end
  106. field :created_at do
  107. label '创建时间'
  108. end
  109. end
  110. show do
  111. field :id
  112. field :wx_user_id do
  113. label '微信用户ID'
  114. end
  115. field :wx_user do
  116. label '微信用户'
  117. pretty_value do
  118. bindings[:object].wx_user_contact
  119. end
  120. end
  121. field :scale_user_id do
  122. label '称用户ID'
  123. end
  124. field :scale_user do
  125. label '称用户名'
  126. pretty_value do
  127. bindings[:object].scale_user_name
  128. end
  129. end
  130. field :status do
  131. label '状态'
  132. pretty_value do
  133. bindings[:object].status_label
  134. end
  135. end
  136. # 望诊图片
  137. field :observation_images do
  138. label '望诊图片'
  139. pretty_value do
  140. exam = bindings[:object]
  141. images = exam.parsed_observation_images
  142. return "-" if images.empty?
  143. image_labels = {
  144. 'tongue' => '舌面照',
  145. 'tongueBottom' => '舌底照',
  146. 'face' => '面部照',
  147. 'body' => '全身照'
  148. }
  149. html = '<div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin: 10px 0;">'
  150. ['tongue', 'tongueBottom', 'face', 'body'].each do |key|
  151. urls = images[key]
  152. next if urls.blank? || urls.empty?
  153. url = urls.first
  154. next if url.blank?
  155. html += %Q{
  156. <div style="text-align: center;">
  157. <img src="#{url}" style="width: 100%; max-width: 200px; height: 150px; object-fit: cover; border-radius: 8px; border: 1px solid #e5e7eb; cursor: pointer;" onclick="window.open('#{url}')" />
  158. <div style="margin-top: 6px; font-size: 12px; color: #5d4037; font-weight: 600;">#{image_labels[key] || key}</div>
  159. </div>
  160. }
  161. end
  162. html += '</div>'
  163. html.html_safe
  164. end
  165. end
  166. # AI 报告内容 - 一般情况
  167. field :ai_summary do
  168. label '壹 · 一般情况'
  169. pretty_value do
  170. exam = bindings[:object]
  171. summary = exam.parsed_ai_summary
  172. content = summary['general'] || summary['summary'] || '-'
  173. %Q{<div style="background: #f9fafb; border: 1px solid #e5e7eb; border-radius: 10px; padding: 14px; margin: 10px 0; line-height: 1.7;">#{content.to_s.gsub("\n", "<br>")}</div>}.html_safe
  174. end
  175. end
  176. # 问诊
  177. field :question_answers do
  178. label '贰 · 问诊 (十问歌)'
  179. pretty_value do
  180. exam = bindings[:object]
  181. summary = exam.parsed_ai_summary
  182. content = summary['inquiry'] || '-'
  183. %Q{<div style="background: #f9fafb; border: 1px solid #e5e7eb; border-radius: 10px; padding: 14px; margin: 10px 0; line-height: 1.7;">#{content.to_s.gsub("\n", "<br>")}</div>}.html_safe
  184. end
  185. end
  186. # 望诊与闻诊
  187. field :extra_note do
  188. label '叁 · 望诊与闻诊'
  189. pretty_value do
  190. exam = bindings[:object]
  191. summary = exam.parsed_ai_summary
  192. content = summary['observation'] || '-'
  193. %Q{<div style="background: #f9fafb; border: 1px solid #e5e7eb; border-radius: 10px; padding: 14px; margin: 10px 0; line-height: 1.7;">#{content.to_s.gsub("\n", "<br>")}</div>}.html_safe
  194. end
  195. end
  196. # 总体评估与体质辨识
  197. field :latest_scale_result do
  198. label '肆 · 总体评估与体质辨识'
  199. pretty_value do
  200. exam = bindings[:object]
  201. summary = exam.parsed_ai_summary
  202. content = summary['constitution'] || '-'
  203. %Q{<div style="background: #f9fafb; border: 1px solid #e5e7eb; border-radius: 10px; padding: 14px; margin: 10px 0; line-height: 1.7;">#{content.to_s.gsub("\n", "<br>")}</div>}.html_safe
  204. end
  205. end
  206. # 调养与建议
  207. field :report_html do
  208. label '伍 · 调养与建议'
  209. pretty_value do
  210. exam = bindings[:object]
  211. summary = exam.parsed_ai_summary
  212. content = summary['advice'] || '-'
  213. %Q{<div style="background: #f9fafb; border: 1px solid #e5e7eb; border-radius: 10px; padding: 14px; margin: 10px 0; line-height: 1.7;">#{content.to_s.gsub("\n", "<br>")}</div>}.html_safe
  214. end
  215. end
  216. field :created_at do
  217. label '创建时间'
  218. end
  219. field :updated_at do
  220. label '更新时间'
  221. end
  222. end
  223. # 禁用编辑和删除,只允许查看
  224. edit do
  225. field :status, :enum do
  226. enum do
  227. STATUS_ENUM
  228. end
  229. end
  230. end
  231. end
  232. end