Selaa lähdekoodia

feat: add tcm_exam model for TCM diagnosis records

Your Name 3 päivää sitten
vanhempi
commit
3a2debfac6
2 muutettua tiedostoa jossa 274 lisäystä ja 0 poistoa
  1. 253 0
      app/models/tcm_exam.rb
  2. 21 0
      config/locales/models/tcm_exam.yml

+ 253 - 0
app/models/tcm_exam.rb

@@ -0,0 +1,253 @@
+# encoding:utf-8
+class TcmExam < ActiveRecord::Base
+  has_paper_trail
+  self.table_name = "tcm_exams"
+  belongs_to :wx_user
+  belongs_to :scale_user, :foreign_key => :scale_user_id
+
+  STATUS_ENUM = [["待处理", "pending"], ["处理中", "processing"], ["已完成", "completed"], ["失败", "failed"]]
+
+  def wx_user_contact
+    return "-" if wx_user.blank?
+    user = wx_user.user
+    return "-" if user.blank?
+    contact = user.tel
+    contact = user.email if contact.blank?
+    contact.blank? ? "-" : contact
+  end
+
+  def scale_user_name
+    return "-" if scale_user.blank?
+    scale_user.nick_name || "-"
+  end
+
+  # 解析 AI 报告内容
+  def parsed_ai_summary
+    return {} if ai_summary.blank?
+    begin
+      JSON.parse(ai_summary)
+    rescue
+      {}
+    end
+  end
+
+  # 解析观察图片
+  def parsed_observation_images
+    return {} if observation_images.blank?
+    begin
+      JSON.parse(observation_images)
+    rescue
+      {}
+    end
+  end
+
+  # 解析问诊答案
+  def parsed_question_answers
+    return {} if question_answers.blank?
+    begin
+      JSON.parse(question_answers)
+    rescue
+      {}
+    end
+  end
+
+  # 解析最新体脂秤结果
+  def parsed_latest_scale_result
+    return {} if latest_scale_result.blank?
+    begin
+      JSON.parse(latest_scale_result)
+    rescue
+      {}
+    end
+  end
+
+  # 状态标签
+  def status_label
+    case status
+    when "pending" then "待处理"
+    when "processing" then "处理中"
+    when "completed" then "已完成"
+    when "failed" then "失败"
+    else status || "-"
+    end
+  end
+
+  rails_admin do
+    navigation_label '中医诊疗记录'
+    parent ScaleDevice
+    weight 3
+
+    list do
+      filters [:wx_user, :scale_user, :status, :created_at]
+      field :id do
+        label 'ID'
+      end
+      field :wx_user_id do
+        label '微信用户ID'
+      end
+      field :wx_user do
+        label '微信用户'
+        pretty_value do
+          bindings[:object].wx_user_contact
+        end
+      end
+      field :scale_user_id do
+        label '称用户ID'
+      end
+      field :scale_user do
+        label '称用户名'
+        pretty_value do
+          bindings[:object].scale_user_name
+        end
+      end
+      field :status do
+        label '状态'
+        pretty_value do
+          bindings[:object].status_label
+        end
+      end
+      field :ai_summary do
+        label '报告详情'
+        formatted_value do
+          exam = bindings[:object]
+          bindings[:view].link_to('查看报告', bindings[:view].rails_admin.show_path(model_name: 'tcm_exam', id: exam.id), class: 'btn btn-info btn-sm')
+        end
+      end
+      field :created_at do
+        label '创建时间'
+      end
+    end
+
+    show do
+      field :id
+      field :wx_user_id do
+        label '微信用户ID'
+      end
+      field :wx_user do
+        label '微信用户'
+        pretty_value do
+          bindings[:object].wx_user_contact
+        end
+      end
+      field :scale_user_id do
+        label '称用户ID'
+      end
+      field :scale_user do
+        label '称用户名'
+        pretty_value do
+          bindings[:object].scale_user_name
+        end
+      end
+      field :status do
+        label '状态'
+        pretty_value do
+          bindings[:object].status_label
+        end
+      end
+      
+      # 望诊图片
+      field :observation_images do
+        label '望诊图片'
+        pretty_value do
+          exam = bindings[:object]
+          images = exam.parsed_observation_images
+          return "-" if images.empty?
+          
+          image_labels = {
+            'tongue' => '舌面照',
+            'tongueBottom' => '舌底照',
+            'face' => '面部照',
+            'body' => '全身照'
+          }
+          
+          html = '<div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin: 10px 0;">'
+          ['tongue', 'tongueBottom', 'face', 'body'].each do |key|
+            urls = images[key]
+            next if urls.blank? || urls.empty?
+            url = urls.first
+            next if url.blank?
+            html += %Q{
+              <div style="text-align: center;">
+                <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}')" />
+                <div style="margin-top: 6px; font-size: 12px; color: #5d4037; font-weight: 600;">#{image_labels[key] || key}</div>
+              </div>
+            }
+          end
+          html += '</div>'
+          html.html_safe
+        end
+      end
+
+      # AI 报告内容 - 一般情况
+      field :ai_summary do
+        label '壹 · 一般情况'
+        pretty_value do
+          exam = bindings[:object]
+          summary = exam.parsed_ai_summary
+          content = summary['general'] || summary['summary'] || '-'
+          %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
+        end
+      end
+
+      # 问诊
+      field :question_answers do
+        label '贰 · 问诊 (十问歌)'
+        pretty_value do
+          exam = bindings[:object]
+          summary = exam.parsed_ai_summary
+          content = summary['inquiry'] || '-'
+          %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
+        end
+      end
+
+      # 望诊与闻诊
+      field :extra_note do
+        label '叁 · 望诊与闻诊'
+        pretty_value do
+          exam = bindings[:object]
+          summary = exam.parsed_ai_summary
+          content = summary['observation'] || '-'
+          %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
+        end
+      end
+
+      # 总体评估与体质辨识
+      field :latest_scale_result do
+        label '肆 · 总体评估与体质辨识'
+        pretty_value do
+          exam = bindings[:object]
+          summary = exam.parsed_ai_summary
+          content = summary['constitution'] || '-'
+          %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
+        end
+      end
+
+      # 调养与建议
+      field :report_html do
+        label '伍 · 调养与建议'
+        pretty_value do
+          exam = bindings[:object]
+          summary = exam.parsed_ai_summary
+          content = summary['advice'] || '-'
+          %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
+        end
+      end
+
+      field :created_at do
+        label '创建时间'
+      end
+      field :updated_at do
+        label '更新时间'
+      end
+    end
+
+    # 禁用编辑和删除,只允许查看
+    edit do
+      field :status, :enum do
+        enum do
+          STATUS_ENUM
+        end
+      end
+    end
+  end
+end

+ 21 - 0
config/locales/models/tcm_exam.yml

@@ -0,0 +1,21 @@
+zh-CN:
+  activerecord:
+    models:
+      tcm_exam: 中医诊疗记录
+    attributes:
+      tcm_exam:
+        id: ID
+        wx_user: 微信用户
+        wx_user_id: 微信用户ID
+        scale_user: 称用户
+        scale_user_id: 称用户ID
+        model: AI模型
+        status: 状态
+        question_answers: 问诊答案
+        observation_images: 望诊图片
+        latest_scale_result: 体脂秤结果
+        extra_note: 备注
+        ai_summary: AI分析报告
+        report_html: 报告HTML
+        created_at: 创建时间
+        updated_at: 更新时间