10 İşlemeler 2fa578d3e9 ... 675b1e7ca3

Yazar SHA1 Mesaj Tarih
  Your Name 675b1e7ca3 fix: support full http paths for scale_user avatar 1 ay önce
  Your Name 9c444f547d fix: tcm_exam详情页望诊记录和调养建议显示问题 1 ay önce
  Your Name c6d9392cbf fix: 修复status_label方法缺少end的语法错误 1 ay önce
  Your Name 8065218bf4 fix: 修复tcm_exam第五部分调养与建议不显示问题 1 ay önce
  Your Name 7a509154eb fix(scale_result): 所有数值保留2位小数显示 1 ay önce
  Your Name 893d93f697 feat(scale_result): 格式化显示测量结果,列表显示摘要,详情显示完整表格 1 ay önce
  Your Name ad52b7342e fix(scale_result): 移除不可用的created_at过滤器 1 ay önce
  Your Name c0a4735582 fix(scale_result): 使用测量结果中的timestamp显示测量时间,移除更新时间字段 1 ay önce
  Your Name 49aebda770 feat(admin): enhance tcm_exam, scale_result, scale_user display 1 ay önce
  Your Name 3a2debfac6 feat: add tcm_exam model for TCM diagnosis records 2 ay önce

+ 36 - 2
Gemfile.lock

@@ -1,5 +1,5 @@
 GEM
-  remote: http://gems.ruby-china.org/
+  remote: https://gems.ruby-china.com/
   specs:
     actionmailer (4.2.7.1)
       actionpack (= 4.2.7.1)
@@ -44,7 +44,9 @@ GEM
     arel (6.0.3)
     autoprefixer-rails (6.5.3)
       execjs
+    bcrypt (3.1.11)
     bcrypt (3.1.11-x64-mingw32)
+    bcrypt-ruby (3.0.1)
     bootstrap-sass (3.3.7)
       autoprefixer-rails (>= 5.2.1)
       sass (>= 3.3.4)
@@ -79,6 +81,7 @@ GEM
     compass-import-once (1.0.5)
       sass (>= 3.2, < 3.5)
     concurrent-ruby (1.0.2)
+    daemons (1.4.0)
     devise (4.2.0)
       bcrypt (~> 3.0)
       orm_adapter (~> 0.1)
@@ -88,7 +91,9 @@ GEM
     domain_name (0.5.20161129)
       unf (>= 0.0.5, < 1.0.0)
     erubis (2.7.0)
+    eventmachine (1.2.7)
     execjs (2.7.0)
+    ffi (1.9.14)
     ffi (1.9.14-x64-mingw32)
     font-awesome-rails (4.7.0.0)
       railties (>= 3.2, < 5.1)
@@ -120,6 +125,8 @@ GEM
     kaminari (0.17.0)
       actionpack (>= 3.0.0)
       activesupport (>= 3.0.0)
+    kgio (2.11.4)
+    libv8 (3.16.14.19-x86_64-linux)
     loofah (2.0.3)
       nokogiri (>= 1.5.9)
     macaddr (1.7.1)
@@ -131,9 +138,12 @@ GEM
     mini_portile2 (2.1.0)
     minitest (5.10.1)
     multi_json (1.12.1)
+    mysql2 (0.4.5)
     mysql2 (0.4.5-x64-mingw32)
     nested_form (0.3.2)
     netrc (0.11.0)
+    nokogiri (1.6.8.1)
+      mini_portile2 (~> 2.1.0)
     nokogiri (1.6.8.1-x64-mingw32)
       mini_portile2 (~> 2.1.0)
     orm_adapter (0.5.0)
@@ -203,15 +213,22 @@ GEM
       activesupport (= 4.2.7.1)
       rake (>= 0.8.7)
       thor (>= 0.18.1, < 2.0)
+    raindrops (0.19.2)
     rake (11.3.0)
     rb-fsevent (0.9.8)
     rb-inotify (0.9.7)
       ffi (>= 0.5.0)
     rchardet (1.6.1)
+    redis (4.3.1)
+    ref (2.0.0)
     remotipart (1.3.1)
     request_store (1.3.1)
     responders (2.3.0)
       railties (>= 4.2.0, < 5.1)
+    rest-client (1.8.0)
+      http-cookie (>= 1.0.2, < 2.0)
+      mime-types (>= 1.16, < 3.0)
+      netrc (~> 0.7)
     rest-client (1.8.0-x64-mingw32)
       ffi (~> 1.9)
       http-cookie (>= 1.0.2, < 2.0)
@@ -241,6 +258,13 @@ GEM
       sprockets (>= 3.0.0)
     syck (1.2.0)
     systemu (2.6.5)
+    therubyracer (0.12.3)
+      libv8 (~> 3.16.14.15)
+      ref
+    thin (1.8.1)
+      daemons (~> 1.0, >= 1.0.9)
+      eventmachine (~> 1.0, >= 1.0.4)
+      rack (>= 1, < 3)
     thor (0.19.4)
     thread_safe (0.3.5)
     tilt (2.0.5)
@@ -252,7 +276,11 @@ GEM
       execjs (>= 0.3.0, < 3)
     unf (0.1.4)
       unf_ext
+    unf_ext (0.0.7.2)
     unf_ext (0.0.7.2-x64-mingw32)
+    unicorn (6.0.0)
+      kgio (~> 2.6)
+      raindrops (~> 0.7)
     uuid (2.3.8)
       macaddr (~> 1.0)
     warden (1.2.6)
@@ -260,11 +288,13 @@ GEM
     xml-simple (1.1.5)
 
 PLATFORMS
+  ruby
   x64-mingw32
 
 DEPENDENCIES
   aliyun-sdk (~> 0.4.1)
   ancestry
+  bcrypt-ruby (~> 3.0.0)
   bootstrap-sass
   cancancan
   chinese_pinyin
@@ -274,6 +304,7 @@ DEPENDENCIES
   compass
   devise
   jquery-rails
+  minitest (= 5.10.1)
   mysql2
   paper_trail (= 5.2.3)
   paperclip
@@ -282,15 +313,18 @@ DEPENDENCIES
   rails_admin
   rails_admin_import
   rails_admin_nestable
+  redis
   rest-client (~> 1.8.0)
   safe_yaml
   sass-rails
   syck
   therubyracer
+  thin
   tzinfo-data
   uglifier
+  unicorn
   uuid
   xml-simple
 
 BUNDLED WITH
-   1.13.3
+   1.16.2

+ 150 - 20
app/models/scale_result.rb

@@ -4,7 +4,6 @@ class ScaleResult < ActiveRecord::Base
   self.table_name = "scale_result"
   belongs_to :wx_user
   belongs_to :scale_user, :foreign_key => :user_id
-  #after_create :update_paied_time
 
   def wx_user_contact
     return "-" if wx_user.blank?
@@ -15,47 +14,178 @@ class ScaleResult < ActiveRecord::Base
     contact.blank? ? "-" : contact
   end
 
+  # 从测量结果中获取时间戳
+  def measurement_time
+    return nil if result.blank?
+    begin
+      data = JSON.parse(result)
+      timestamp = data['timestamp']
+      return nil if timestamp.blank?
+      Time.parse(timestamp)
+    rescue => e
+      nil
+    end
+  end
+
+  # 格式化数值,保留2位小数
+  def fmt(val)
+    return '-' if val.nil?
+    val.is_a?(Numeric) ? format('%.2f', val) : val.to_s
+  end
+
+  # 格式化显示测量结果(列表简要版)
+  def result_summary
+    return '-' if result.blank?
+    begin
+      d = JSON.parse(result)
+      "体重:#{fmt(d['weight_kg'])}kg BMI:#{fmt(d['bmi'])} 体脂:#{fmt(d['bodyFatPercent'])}%"
+    rescue
+      '-'
+    end
+  end
+
+  # 格式化显示测量结果(详情完整版)
+  def result_formatted
+    return '-' if result.blank?
+    begin
+      d = JSON.parse(result)
+      userdata = d['userdata'] || {}
+      
+      html = '<table class="table table-striped table-condensed" style="font-size:13px;">'
+      
+      # 用户信息
+      if userdata.present?
+        html += '<tr><th colspan="4" style="background:#f5f5f5;">👤 用户信息</th></tr>'
+        html += "<tr><td><b>昵称</b></td><td>#{userdata['nickName']}</td>"
+        html += "<td><b>性别</b></td><td>#{userdata['sex'] == 0 ? '男' : '女'}</td></tr>"
+        html += "<tr><td><b>年龄</b></td><td>#{userdata['age']}岁</td>"
+        html += "<td><b>身高</b></td><td>#{userdata['height']}cm</td></tr>"
+      end
+      
+      # 基础测量
+      html += '<tr><th colspan="4" style="background:#f5f5f5;">📊 基础测量</th></tr>'
+      html += "<tr><td><b>体重</b></td><td>#{fmt(d['weight_kg'])} kg</td>"
+      html += "<td><b>BMI</b></td><td>#{fmt(d['bmi'])}</td></tr>"
+      html += "<tr><td><b>身体评分</b></td><td>#{fmt(d['bodyScore'])} 分</td>"
+      html += "<td><b>身体年龄</b></td><td>#{fmt(d['physicalAge'])} 岁</td></tr>"
+      
+      # 体成分
+      html += '<tr><th colspan="4" style="background:#f5f5f5;">🏃 体成分分析</th></tr>'
+      html += "<tr><td><b>体脂率</b></td><td>#{fmt(d['bodyFatPercent'])}%</td>"
+      html += "<td><b>肌肉率</b></td><td>#{fmt(d['musclePercent'])}%</td></tr>"
+      html += "<tr><td><b>内脏脂肪</b></td><td>#{fmt(d['visceralFat'])}</td>"
+      html += "<td><b>皮下脂肪</b></td><td>#{fmt(d['subcutaneousFatPercent'])}%</td></tr>"
+      html += "<tr><td><b>水分率</b></td><td>#{fmt(d['moisturePercent'])}%</td>"
+      html += "<td><b>蛋白质</b></td><td>#{fmt(d['proteinPercent'])}%</td></tr>"
+      html += "<tr><td><b>骨量</b></td><td>#{fmt(d['boneMass'])} kg</td>"
+      html += "<td><b>基础代谢</b></td><td>#{fmt(d['bmr'])} kcal</td></tr>"
+      html += "<tr><td><b>骨骼肌率</b></td><td>#{fmt(d['smPercent'])}%</td>"
+      html += "<td><b>体型</b></td><td>#{d['bodyType']}</td></tr>"
+      
+      # 目标建议
+      html += '<tr><th colspan="4" style="background:#f5f5f5;">🎯 目标建议</th></tr>'
+      html += "<tr><td><b>目标体重</b></td><td>#{fmt(d['targetWeight'])} kg</td>"
+      html += "<td><b>体重控制</b></td><td>#{fmt(d['weightControl'])} kg</td></tr>"
+      html += "<tr><td><b>标准体重</b></td><td>#{fmt(d['weightStandard'])} kg</td>"
+      html += "<td><b>标准BMI</b></td><td>#{fmt(d['bmiStandard'])}</td></tr>"
+      
+      # 设备信息
+      html += '<tr><th colspan="4" style="background:#f5f5f5;">📱 设备信息</th></tr>'
+      html += "<tr><td><b>MAC地址</b></td><td>#{d['mac']}</td>"
+      html += "<td><b>测量时间</b></td><td>#{d['timestamp']}</td></tr>"
+      
+      html += '</table>'
+      html.html_safe
+    rescue => e
+      "解析错误: #{e.message}"
+    end
+  end
+
   rails_admin do
     navigation_label '测量记录'
     parent ScaleDevice
     weight 2
 
     list do
-      filters [:wx_user,:created_at]
-      field :id
-      field :wx_user_id
+      filters [:wx_user]
+      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 :user_id
-      field :scale_user
-      field :result
-      field :created_at
-      field :updated_at
+      field :user_id do
+        label '称用户ID'
+      end
+      field :scale_user do
+        label '称用户名'
+      end
+      field :result do
+        label '测量结果'
+        pretty_value do
+          bindings[:object].result_summary
+        end
+      end
+      field :measurement_time do
+        label '测量时间'
+        pretty_value do
+          time = bindings[:object].measurement_time
+          time.present? ? time.strftime('%Y-%m-%d %H:%M:%S') : '-'
+        end
+        sortable false
+      end
     end
 
     show do
-      field :wx_user_id
+      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 :user_id
-      field :scale_user
-      field :result
-      field :created_at
-      field :updated_at
+      field :user_id do
+        label '称用户ID'
+      end
+      field :scale_user do
+        label '称用户名'
+      end
+      field :result do
+        label '测量结果'
+        pretty_value do
+          bindings[:object].result_formatted
+        end
+      end
+      field :measurement_time do
+        label '测量时间'
+        pretty_value do
+          time = bindings[:object].measurement_time
+          time.present? ? time.strftime('%Y-%m-%d %H:%M:%S') : '-'
+        end
+      end
     end
 
     edit do
-      field :wx_user
-      field :scale_user
-      field :result
-      field :created_at
-      field :updated_at
+      field :wx_user do
+        label '微信用户'
+      end
+      field :scale_user do
+        label '称用户名'
+      end
+      field :result do
+        label '测量结果'
+      end
     end
   end
 end

+ 92 - 0
app/models/scale_user.rb

@@ -9,6 +9,36 @@ class ScaleUser < ActiveRecord::Base
   #after_create :update_paied_time
   SEX_ENUM = [["男",0],["女",1]]
 
+  # 国家选项
+  COUNTRY_ENUM = [
+    ["中国", "china"],
+    ["美国", "usa"],
+    ["日本", "japan"],
+    ["韩国", "korea"],
+    ["马来西亚", "malaysia"],
+    ["其他", "other"]
+  ]
+
+  # 种族选项
+  RACE_ENUM = [
+    ["亚洲人", "asian"],
+    ["白人", "caucasian"],
+    ["黑人", "black"],
+    ["拉丁裔", "latino"],
+    ["其他", "other"]
+  ]
+
+  # 职业选项
+  OCCUPATION_ENUM = [
+    ["IT/程序员", "it_programmer"],
+    ["教师", "teacher"],
+    ["医生", "doctor"],
+    ["学生", "student"],
+    ["自由职业", "freelancer"],
+    ["退休", "retired"],
+    ["其他", "other"]
+  ]
+
   def wx_user_contact
     return "-" if wx_user.blank?
     user = wx_user.user
@@ -20,9 +50,26 @@ class ScaleUser < ActiveRecord::Base
 
   def get_avatar_img
     return "https://x-encrpt-bucket.s3.ap-southeast-2.amazonaws.com/encript/1766455493300.jpeg" if self.avatar.blank?
+    return self.avatar if self.avatar.to_s.start_with?('http')
     url = "http://#{Ali::Oss::CDN_URL_FOR_HOST}/#{self.avatar}"
     return url
   end
+
+  # 获取国家显示名称
+  def country_label
+    COUNTRY_ENUM.find { |label, value| value == country }&.first || country || "-"
+  end
+
+  # 获取种族显示名称
+  def race_label
+    RACE_ENUM.find { |label, value| value == race }&.first || race || "-"
+  end
+
+  # 获取职业显示名称
+  def occupation_label
+    OCCUPATION_ENUM.find { |label, value| value == occupation }&.first || occupation || "-"
+  end
+
   rails_admin do
     navigation_label '体脂秤用户'
     parent ScaleDevice
@@ -55,6 +102,21 @@ class ScaleUser < ActiveRecord::Base
       field :age
       field :target_weight
       field :birthday
+      field :country do
+        pretty_value do
+          bindings[:object].country_label
+        end
+      end
+      field :race do
+        pretty_value do
+          bindings[:object].race_label
+        end
+      end
+      field :occupation do
+        pretty_value do
+          bindings[:object].occupation_label
+        end
+      end
       field :created_at
       field :updated_at
     end
@@ -84,6 +146,21 @@ class ScaleUser < ActiveRecord::Base
       field :age
       field :target_weight
       field :birthday
+      field :country do
+        pretty_value do
+          bindings[:object].country_label
+        end
+      end
+      field :race do
+        pretty_value do
+          bindings[:object].race_label
+        end
+      end
+      field :occupation do
+        pretty_value do
+          bindings[:object].occupation_label
+        end
+      end
       field :created_at
       field :updated_at
     end
@@ -107,6 +184,21 @@ class ScaleUser < ActiveRecord::Base
       field :age
       field :target_weight
       field :birthday
+      field :country, :enum do
+        enum do
+          COUNTRY_ENUM
+        end
+      end
+      field :race, :enum do
+        enum do
+          RACE_ENUM
+        end
+      end
+      field :occupation, :enum do
+        enum do
+          OCCUPATION_ENUM
+        end
+      end
       field :created_at
       field :updated_at
     end

+ 334 - 0
app/models/tcm_exam.rb

@@ -0,0 +1,334 @@
+# 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
+
+  # 虚拟方法:调养与建议
+  def advice_section
+    parsed_ai_summary["advice"] || "-"
+  end
+
+  # 虚拟方法:望诊记录
+  def observation_section
+    parsed_ai_summary["observation"] || "-"
+  end
+
+  # 格式化内容,处理换行和 markdown 格式
+  def format_content(content)
+    return "-" if content.blank?
+    # 处理换行符
+    formatted = content.to_s
+      .gsub(/\\n/, "<br>")
+      .gsub(/\n/, "<br>")
+      .gsub(/\*\*([^*]+)\*\*/, '<strong>\1</strong>')  # **粗体**
+      .gsub(/\*([^*]+)\*/, '<em>\1</em>')  # *斜体*
+    formatted
+  end
+
+  # 格式化调养与建议的嵌套 JSON 结构
+  def format_advice(advice)
+    return "-" if advice.blank?
+    
+    # 如果是字符串,尝试解析为 JSON
+    if advice.is_a?(String)
+      begin
+        advice = JSON.parse(advice)
+      rescue
+        return format_content(advice)
+      end
+    end
+    
+    # 如果不是 Hash,直接格式化
+    return format_content(advice.to_s) unless advice.is_a?(Hash)
+    
+    # 定义分类标签和图标
+    category_info = {
+      '饮食' => { icon: '🍵', color: '#059669' },
+      '运动' => { icon: '🏃', color: '#2563eb' },
+      '作息' => { icon: '🌙', color: '#7c3aed' },
+      '情志' => { icon: '💆', color: '#db2777' }
+    }
+    
+    html = ''
+    advice.each do |category, items|
+      info = category_info[category] || { icon: '📌', color: '#6b7280' }
+      html += %Q{<div style="margin-bottom: 16px;">}
+      html += %Q{<div style="font-weight: 600; color: #{info[:color]}; margin-bottom: 8px; font-size: 15px;">#{info[:icon]} #{category}</div>}
+      html += %Q{<ul style="margin: 0; padding-left: 20px; list-style-type: disc;">}
+      
+      if items.is_a?(Array)
+        items.each do |item|
+          html += %Q{<li style="margin-bottom: 6px; line-height: 1.6;">#{format_content(item)}</li>}
+        end
+      else
+        html += %Q{<li style="margin-bottom: 6px; line-height: 1.6;">#{format_content(items)}</li>}
+      end
+      
+      html += '</ul></div>'
+    end
+    
+    html
+  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;">#{exam.format_content(content)}</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;">#{exam.format_content(content)}</div>}.html_safe
+        end
+      end
+
+      # 望诊与闻诊 - 使用虚拟字段确保显示
+      field :observation_section 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;">#{exam.format_content(content)}</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;">#{exam.format_content(content)}</div>}.html_safe
+        end
+      end
+
+      # 调养与建议 - 格式化嵌套 JSON
+      field :advice_section do
+        label '伍 · 调养与建议'
+        pretty_value do
+          exam = bindings[:object]
+          summary = exam.parsed_ai_summary
+          advice = summary['advice']
+          return "-" if advice.blank?
+          formatted = exam.format_advice(advice)
+          %Q{<div style="background: #f9fafb; border: 1px solid #e5e7eb; border-radius: 10px; padding: 14px; margin: 10px 0; line-height: 1.7;">#{formatted}</div>}.html_safe
+        end
+      end
+
+      # 总结
+      field :model do
+        label '陆 · 总结'
+        pretty_value do
+          exam = bindings[:object]
+          summary = exam.parsed_ai_summary
+          content = summary['summary']
+          return "-" if content.blank?
+          %Q{<div style="background: #fff7ed; border: 1px solid #fed7aa; border-radius: 10px; padding: 14px; margin: 10px 0; line-height: 1.7; color: #9a3412;">#{exam.format_content(content)}</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

+ 4 - 1
config/locales/models/scale_user.yml

@@ -14,5 +14,8 @@ zh-CN:
         age: 年龄
         birthday: 生日
         target_weight: 目标体重
+        country: 国家
+        race: 种族
+        occupation: 职业
         created_at: 创建时间
-        updated_at: 更新时间
+        updated_at: 更新时间

+ 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: 更新时间