gut-flora-detail.vue 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  1. <template>
  2. <view class="page">
  3. <view class="nav-bar">
  4. <view class="nav-back" @tap="goBack">
  5. <text class="back-text">‹ 返回</text>
  6. </view>
  7. <text class="nav-title">肠道菌群报告</text>
  8. </view>
  9. <scroll-view class="content" scroll-y>
  10. <!-- 报告头部 -->
  11. <view class="report-header">
  12. <text class="report-title">肠道菌群检测报告</text>
  13. <view class="report-meta">
  14. <text class="meta-item" v-if="report.personName">{{ report.personName }}</text>
  15. <text class="meta-item" v-if="report.gender">{{ report.gender }}</text>
  16. <text class="meta-item" v-if="report.age">{{ report.age }}岁</text>
  17. </view>
  18. <view class="report-meta">
  19. <text class="meta-item" v-if="report.reportDate">日期: {{ report.reportDate }}</text>
  20. <text class="meta-item" v-if="report.reportNumber">编号: {{ report.reportNumber }}</text>
  21. </view>
  22. <view class="badge-row" v-if="report.gutAge || report.gutType">
  23. <text class="badge" v-if="report.gutAge">肠龄: {{ report.gutAge }}</text>
  24. <text class="badge" v-if="report.gutType">肠型: {{ report.gutType }}</text>
  25. </view>
  26. <!-- 报告解读 -->
  27. <view class="interpretation-box" v-if="reportInterpretation && reportInterpretation.gutAgeAssessment">
  28. <text class="interp-text">{{ reportInterpretation.gutAgeAssessment }}</text>
  29. </view>
  30. </view>
  31. <!-- 评分圆环 -->
  32. <scroll-view class="scores-scroll" scroll-x>
  33. <view class="score-item" v-for="(score, idx) in scoreItems" :key="idx">
  34. <view class="score-ring" :style="'border-color: ' + score.color + '; color: ' + score.color">
  35. <text class="score-val">{{ score.value }}</text>
  36. </view>
  37. <text class="score-label">{{ score.label }}</text>
  38. </view>
  39. </scroll-view>
  40. <!-- 快速跳转 -->
  41. <view class="quick-nav">
  42. <view class="nav-card" @tap="goToSpecies">
  43. <text class="nav-icon">🦠</text>
  44. <text class="nav-label">菌群详情</text>
  45. <text class="nav-count">{{ floraCount }}种</text>
  46. </view>
  47. <view class="nav-card" @tap="goToPathogen">
  48. <text class="nav-icon">🔬</text>
  49. <text class="nav-label">病原菌</text>
  50. <text class="nav-count">{{ pathogenCount }}项</text>
  51. </view>
  52. <view class="nav-card" @tap="goToTaxonomy">
  53. <text class="nav-icon">📊</text>
  54. <text class="nav-label">菌群层级</text>
  55. <text class="nav-count">5级</text>
  56. </view>
  57. <view class="nav-card" @tap="goToRisks">
  58. <text class="nav-icon">⚠️</text>
  59. <text class="nav-label">疾病风险</text>
  60. <text class="nav-count">{{ riskCount }}项</text>
  61. </view>
  62. <view class="nav-card" @tap="goToFoods">
  63. <text class="nav-icon">🍽️</text>
  64. <text class="nav-label">饮食推荐</text>
  65. <text class="nav-count">{{ foodCount }}项</text>
  66. </view>
  67. <view class="nav-card" @tap="goToIndicators">
  68. <text class="nav-icon">📋</text>
  69. <text class="nav-label">全部指标</text>
  70. <text class="nav-count">{{ indicatorCount }}项</text>
  71. </view>
  72. </view>
  73. <!-- 肠道功能手风琴 -->
  74. <view class="accordion-section">
  75. <view class="accordion-header" @tap="toggleAccordion('gut')">
  76. <text class="accordion-title">肠道功能与代谢物</text>
  77. <text class="accordion-arrow">{{ accordionOpen.gut ? '收起' : '展开' }}</text>
  78. </view>
  79. <view class="accordion-body" v-if="accordionOpen.gut">
  80. <view class="indicator-card" v-for="(item, idx) in gutIndicators" :key="idx">
  81. <view class="indicator-row">
  82. <text class="indicator-name" @tap="showKnowledge(indicatorType(item), item.indicatorName)">
  83. {{ item.indicatorName }}
  84. <text class="arrow-up" v-if="item.status === '偏高' || item.status === '过多'"> ↑</text>
  85. <text class="arrow-down" v-else-if="item.status === '偏低' || item.status === '缺乏' || item.status === '不足'"> ↓</text>
  86. <text class="arrow-warn" v-else-if="item.status === '异常'"> ⚠</text>
  87. </text>
  88. <view class="indicator-value" v-if="!isEditing">
  89. <text :class="'val-text val-' + (item._cssClass || 'normal')">{{ item.indicatorValue || '--' }}</text>
  90. </view>
  91. <view class="indicator-edit" v-if="isEditing">
  92. <input class="edit-input" type="text" v-model="item.indicatorValue" />
  93. <picker :value="indStatusIndex(item.status)" :range="indStatuses" @change="onIndStatusChange($event, item)">
  94. <text class="edit-picker">{{ item.status || '正常' }}</text>
  95. </picker>
  96. </view>
  97. </view>
  98. <text class="indicator-range" v-if="item.refRange">参考: {{ item.refRange }}</text>
  99. </view>
  100. <view class="empty-hint" v-if="gutIndicators.length === 0">暂无数据</view>
  101. </view>
  102. </view>
  103. <!-- 营养指标手风琴 -->
  104. <view class="accordion-section">
  105. <view class="accordion-header" @tap="toggleAccordion('nutri')">
  106. <text class="accordion-title">营养指标</text>
  107. <text class="accordion-arrow">{{ accordionOpen.nutri ? '收起' : '展开' }}</text>
  108. </view>
  109. <view class="accordion-body" v-if="accordionOpen.nutri">
  110. <view class="sub-group" v-for="(group, gIdx) in nutritionGroups" :key="gIdx">
  111. <text class="sub-group-title">{{ group.category }}</text>
  112. <view class="indicator-card" v-for="(item, idx) in group.items" :key="idx">
  113. <view class="indicator-row">
  114. <text class="indicator-name" @tap="showKnowledge(indicatorType(item), item.indicatorName)">
  115. {{ item.indicatorName }}
  116. <text class="arrow-up" v-if="item.status === '偏高' || item.status === '过多'"> ↑</text>
  117. <text class="arrow-down" v-else-if="item.status === '偏低' || item.status === '缺乏' || item.status === '不足'"> ↓</text>
  118. <text class="arrow-warn" v-else-if="item.status === '异常'"> ⚠</text>
  119. </text>
  120. <view class="indicator-value" v-if="!isEditing">
  121. <text :class="'val-text val-' + (item._cssClass || 'normal')">{{ item.indicatorValue || '--' }}</text>
  122. </view>
  123. <view class="indicator-edit" v-if="isEditing">
  124. <input class="edit-input" type="text" v-model="item.indicatorValue" />
  125. <picker :value="indStatusIndex(item.status)" :range="indStatuses" @change="onIndStatusChange($event, item)">
  126. <text class="edit-picker">{{ item.status || '正常' }}</text>
  127. </picker>
  128. </view>
  129. </view>
  130. <text class="indicator-range" v-if="item.refRange">参考: {{ item.refRange }}</text>
  131. </view>
  132. </view>
  133. <view class="empty-hint" v-if="nutritionGroups.length === 0">暂无数据</view>
  134. </view>
  135. </view>
  136. <view class="feedback-section" @tap="openFeedback">
  137. <text class="feedback-icon">💬</text>
  138. <text class="feedback-text">对这个报告有疑问或建议?告诉我们</text>
  139. <text class="feedback-arrow">›</text>
  140. </view>
  141. <view class="bottom-spacer"></view>
  142. </scroll-view>
  143. <health-knowledge-popup :visible="knowledgeVisible" :data="knowledgeData" @close="closeKnowledge" />
  144. <feedback-popup :visible="feedbackVisible" :report-id="reportId" @close="closeFeedback" />
  145. <view class="fab" v-if="!isEditing" @tap="toggleEdit">
  146. <text class="fab-icon">✏️</text>
  147. </view>
  148. <view class="bottom-bar" v-if="isEditing">
  149. <view class="btn-cancel" @tap="cancelEdit">取消</view>
  150. <view class="btn-save" @tap="saveEdit">保存</view>
  151. </view>
  152. </view>
  153. </template>
  154. <script>
  155. import { getReportDetail, editHealthReport, queryKnowledgeBase, queryIndicatorKnowledge } from '../../utils/api.js'
  156. import HealthKnowledgePopup from '../../components/health-knowledge-popup.vue'
  157. import FeedbackPopup from '../../components/feedback-popup.vue'
  158. var INDICATOR_TYPE_MAP = {
  159. '维生素': 'vitamin',
  160. '氨基酸': 'amino_acid',
  161. '微量元素': 'indicator',
  162. '肠道屏障与代谢物': 'indicator',
  163. '短链脂肪酸': 'indicator',
  164. '神经递质与激素': 'indicator',
  165. '抗生素风险评估': 'indicator'
  166. }
  167. export default {
  168. components: { HealthKnowledgePopup, FeedbackPopup },
  169. data() {
  170. return {
  171. reportId: null,
  172. reportData: null,
  173. report: {},
  174. reportInterpretation: null,
  175. isEditing: false,
  176. accordionOpen: { gut: false, nutri: false },
  177. scoreItems: [],
  178. allIndicators: [],
  179. gutIndicators: [],
  180. nutritionGroups: [],
  181. floraCount: 0,
  182. riskCount: 0,
  183. foodCount: 0,
  184. indicatorCount: 0,
  185. pathogenCount: 0,
  186. knowledgeVisible: false,
  187. knowledgeData: {},
  188. feedbackVisible: false,
  189. indStatuses: ['正常', '偏低', '偏高', '缺乏', '不足', '过多', '异常']
  190. }
  191. },
  192. onLoad: function(options) {
  193. this.reportId = options.reportId ? parseInt(options.reportId) : null
  194. if (this.reportId) {
  195. this.loadData()
  196. }
  197. },
  198. methods: {
  199. goBack: function() { uni.navigateBack() },
  200. /** 中文状态 → CSS安全英文 */
  201. _cssForStatus: function(s) {
  202. var map = { '偏高': 'high', '过多': 'excess', '偏低': 'low', '缺乏': 'deficient', '不足': 'insufficient', '异常': 'abnormal', '注意': 'warning' }
  203. return map[s] || 'normal'
  204. },
  205. loadData: function() {
  206. var self = this
  207. getReportDetail(this.reportId).then(function(res) {
  208. if (res.code === 200 && res.data) {
  209. self.reportData = res.data
  210. self.report = res.data.report || res.data
  211. self.reportInterpretation = res.data.reportInterpretation || null
  212. self.buildScores(res.data)
  213. self.buildIndicators(res.data)
  214. self.floraCount = (res.data.gutFlora || []).length
  215. self.riskCount = (res.data.diseaseRisks || []).length
  216. self.foodCount = (res.data.foods || []).length
  217. self.indicatorCount = (res.data.indicators || []).length
  218. // 计算病原菌总数
  219. var flora = res.data.gutFlora || []
  220. self.pathogenCount = flora.filter(function(i) {
  221. return i.category === 'pathogen_genus' || i.category === 'pathogen_detection'
  222. }).length
  223. }
  224. })
  225. },
  226. buildScores: function(data) {
  227. var r = data.report || data
  228. var items = []
  229. var scoreFields = [
  230. { key: 'overallScore', label: '综合', color: '#4A9BD7' },
  231. { key: 'gutHealthScore', label: '菌群健康', color: '#10B981' },
  232. { key: 'chronicDiseaseScore', label: '慢病控制', color: '#F59E0B' },
  233. { key: 'nutritionScore', label: '营养均衡', color: '#FF8C42' },
  234. { key: 'gutBalanceScore', label: '平衡', color: '#6366F1' },
  235. { key: 'gutDiversityScore', label: '多样性', color: '#FF6B9D' },
  236. { key: 'beneficialBacteriaScore', label: '有益菌', color: '#10B981' },
  237. { key: 'harmfulBacteriaScore', label: '有害菌', color: '#C62828' },
  238. { key: 'coreSpeciesScore', label: '核心菌属', color: '#4A9BD7' }
  239. ]
  240. for (var i = 0; i < scoreFields.length; i++) {
  241. var f = scoreFields[i]
  242. if (r[f.key] != null) {
  243. items.push({ value: r[f.key], label: f.label, color: f.color })
  244. }
  245. }
  246. this.scoreItems = items
  247. },
  248. buildIndicators: function(data) {
  249. var indicators = data.indicators || []
  250. // 为每个指标添加_cssClass字段(wxss不支持中文类名)
  251. var self = this
  252. indicators.forEach(function(item) {
  253. item._cssClass = self._cssForStatus(item.status)
  254. })
  255. this.allIndicators = indicators
  256. var gutCategories = ['肠道屏障与代谢物', '短链脂肪酸', '神经递质与激素', '抗生素风险评估']
  257. this.gutIndicators = indicators.filter(function(item) {
  258. return gutCategories.indexOf(item.category) >= 0
  259. })
  260. var seen = {}
  261. var groups = []
  262. for (var i = 0; i < indicators.length; i++) {
  263. var item = indicators[i]
  264. if (gutCategories.indexOf(item.category) >= 0) continue
  265. if (!seen[item.category]) {
  266. seen[item.category] = true
  267. groups.push({ category: item.category, items: [] })
  268. }
  269. for (var g = 0; g < groups.length; g++) {
  270. if (groups[g].category === item.category) {
  271. groups[g].items.push(item)
  272. break
  273. }
  274. }
  275. }
  276. this.nutritionGroups = groups
  277. },
  278. toggleAccordion: function(key) {
  279. this.accordionOpen[key] = !this.accordionOpen[key]
  280. },
  281. indicatorType: function(item) {
  282. return INDICATOR_TYPE_MAP[item.category] || 'nutrient'
  283. },
  284. indStatusIndex: function(status) {
  285. var idx = this.indStatuses.indexOf(status)
  286. return idx >= 0 ? idx : 0
  287. },
  288. onIndStatusChange: function(e, item) {
  289. item.status = this.indStatuses[e.detail.value]
  290. },
  291. showKnowledge: function(type, name) {
  292. var self = this
  293. // 优先使用 KB v3 查询指标调整建议
  294. queryIndicatorKnowledge(name).then(function(res) {
  295. if (res.code === 200 && res.data) {
  296. var v3 = res.data
  297. var high = v3['偏高影响'] || ''
  298. var low = v3['偏低影响'] || ''
  299. self.knowledgeData = {
  300. itemName: v3['名称'] || name,
  301. category: v3['说明'] || '',
  302. description: (high ? '偏高影响:' + high + '\n' : '') + (low ? '偏低影响:' + low : ''),
  303. suggestion: v3['调整建议'] || ''
  304. }
  305. self.knowledgeVisible = true
  306. } else {
  307. // 回退旧版 DB 查询
  308. self.queryOldKnowledgeBase(type, name)
  309. }
  310. }).catch(function() {
  311. self.queryOldKnowledgeBase(type, name)
  312. })
  313. },
  314. queryOldKnowledgeBase: function(type, name) {
  315. var self = this
  316. queryKnowledgeBase(type, name).then(function(res) {
  317. if (res.code === 200 && res.data) {
  318. self.knowledgeData = res.data
  319. self.knowledgeVisible = true
  320. } else {
  321. self.knowledgeData = { itemName: name }
  322. self.knowledgeVisible = true
  323. }
  324. })
  325. },
  326. closeKnowledge: function() {
  327. this.knowledgeVisible = false
  328. this.knowledgeData = {}
  329. },
  330. openFeedback: function() {
  331. this.feedbackVisible = true
  332. },
  333. closeFeedback: function() {
  334. this.feedbackVisible = false
  335. },
  336. toggleEdit: function() {
  337. this.isEditing = true
  338. },
  339. cancelEdit: function() {
  340. this.isEditing = false
  341. this.loadData()
  342. },
  343. saveEdit: function() {
  344. var self = this
  345. var payload = {
  346. overallScore: this.report.overallScore,
  347. gutHealthScore: this.report.gutHealthScore,
  348. nutritionScore: this.report.nutritionScore,
  349. indicators: this.allIndicators
  350. }
  351. editHealthReport(this.reportId, payload, 0).then(function(res) {
  352. if (res.code === 200) {
  353. uni.showToast({ title: '已保存', icon: 'success' })
  354. self.isEditing = false
  355. self.loadData()
  356. } else {
  357. uni.showToast({ title: res.message || '保存失败', icon: 'none' })
  358. }
  359. })
  360. },
  361. goToSpecies: function() {
  362. uni.navigateTo({ url: '/pages/health/gut-flora-species-detail?reportId=' + this.reportId })
  363. },
  364. goToPathogen: function() {
  365. uni.navigateTo({ url: '/pages/health/gut-flora-species-detail?reportId=' + this.reportId + '&tab=pathogen_genus' })
  366. },
  367. goToTaxonomy: function() {
  368. uni.navigateTo({ url: '/pages/health/gut-flora-species-detail?reportId=' + this.reportId + '&tab=taxonomy_class' })
  369. },
  370. goToRisks: function() {
  371. uni.navigateTo({ url: '/pages/health/gut-flora-risks-detail?reportId=' + this.reportId })
  372. },
  373. goToFoods: function() {
  374. uni.navigateTo({ url: '/pages/health/gut-flora-foods-detail?reportId=' + this.reportId })
  375. },
  376. goToIndicators: function() {
  377. this.accordionOpen.nutri = true
  378. },
  379. // AI问答功能已屏蔽
  380. goAIChat: function() {}
  381. }
  382. }
  383. </script>
  384. <style scoped>
  385. .page { min-height: 100vh; background: #f8f8f8; }
  386. .nav-bar { display: flex; align-items: center; padding: 20rpx 30rpx; background: #fff; position: relative; }
  387. .nav-back { padding: 10rpx 0; }
  388. .back-text { font-size: 28rpx; color: #4A9BD7; }
  389. .nav-title { flex: 1; text-align: center; font-size: 32rpx; font-weight: 600; }
  390. .content { height: calc(100vh - 100rpx); }
  391. .report-header { background: linear-gradient(135deg, #4A9BD7, #6366F1); padding: 40rpx 30rpx; color: #fff; }
  392. .report-title { font-size: 36rpx; font-weight: 700; display: block; margin-bottom: 16rpx; }
  393. .report-meta { display: flex; gap: 20rpx; margin-bottom: 6rpx; }
  394. .meta-item { font-size: 24rpx; opacity: 0.9; }
  395. .badge-row { display: flex; gap: 16rpx; margin-top: 12rpx; }
  396. .badge { padding: 6rpx 20rpx; background: rgba(255,255,255,0.2); border-radius: 20rpx; font-size: 22rpx; }
  397. .scores-scroll { padding: 20rpx 0; white-space: nowrap; background: #fff; }
  398. .score-item { display: inline-flex; flex-direction: column; align-items: center; margin: 0 20rpx; }
  399. .score-ring { width: 100rpx; height: 100rpx; border-radius: 50%; border: 6rpx solid; display: flex; align-items: center; justify-content: center; font-size: 32rpx; font-weight: 700; margin-bottom: 8rpx; }
  400. .score-label { font-size: 22rpx; color: #666; }
  401. .quick-nav { display: flex; padding: 20rpx; background: #fff; margin-bottom: 16rpx; }
  402. .nav-card { flex: 1; display: flex; flex-direction: column; align-items: center; padding: 16rpx 0; }
  403. .nav-icon { font-size: 40rpx; margin-bottom: 6rpx; }
  404. .nav-label { font-size: 24rpx; color: #333; }
  405. .nav-count { font-size: 20rpx; color: #999; }
  406. .accordion-section { background: #fff; margin-bottom: 16rpx; }
  407. .accordion-header { display: flex; justify-content: space-between; padding: 30rpx; border-bottom: 1rpx solid #f0f0f0; }
  408. .accordion-title { font-size: 30rpx; font-weight: 600; color: #333; }
  409. .accordion-arrow { font-size: 24rpx; color: #999; }
  410. .accordion-body { padding: 10rpx 30rpx 20rpx; }
  411. .sub-group { margin-bottom: 16rpx; }
  412. .sub-group-title { font-size: 26rpx; color: #4A9BD7; font-weight: 600; display: block; margin: 12rpx 0 8rpx; }
  413. .indicator-card { padding: 16rpx 0; border-bottom: 1rpx solid #f5f5f5; }
  414. .indicator-row { display: flex; align-items: center; justify-content: space-between; }
  415. .indicator-name { font-size: 28rpx; color: #333; flex: 1; }
  416. .arrow-up { color: #C62828; font-size: 22rpx; }
  417. .arrow-down { color: #E65100; font-size: 22rpx; }
  418. .arrow-warn { color: #F59E0B; font-size: 22rpx; }
  419. .val-text { font-size: 28rpx; font-weight: 600; color: #333; }
  420. .val-high { color: #C62828; }
  421. .val-excess { color: #C62828; }
  422. .val-low { color: #E65100; }
  423. .val-deficient { color: #E65100; }
  424. .val-insufficient { color: #E65100; }
  425. .val-abnormal { color: #C62828; }
  426. .val-warning { color: #F59E0B; }
  427. .indicator-range { font-size: 22rpx; color: #bbb; display: block; margin-top: 4rpx; }
  428. .indicator-edit { display: flex; gap: 8rpx; align-items: center; }
  429. .edit-input { border: 1rpx solid #ddd; border-radius: 8rpx; padding: 6rpx 10rpx; font-size: 26rpx; width: 100rpx; }
  430. .edit-picker { border: 1rpx solid #ddd; border-radius: 8rpx; padding: 6rpx 10rpx; font-size: 24rpx; color: #333; }
  431. .fab { position: fixed; right: 30rpx; bottom: 40rpx; width: 100rpx; height: 100rpx; background: #4A9BD7; border-radius: 50%; display: flex; align-items: center; justify-content: center; box-shadow: 0 4rpx 16rpx rgba(74,155,215,0.4); }
  432. .fab-icon { font-size: 40rpx; color: #fff; }
  433. .bottom-bar { position: fixed; bottom: 0; left: 0; right: 0; display: flex; padding: 20rpx 30rpx; background: #fff; border-top: 1rpx solid #eee; gap: 20rpx; z-index: 100; }
  434. .btn-cancel { flex: 1; padding: 20rpx; border: 1rpx solid #ddd; border-radius: 12rpx; text-align: center; font-size: 28rpx; color: #666; }
  435. .btn-save { flex: 1; padding: 20rpx; background: #4A9BD7; border-radius: 12rpx; text-align: center; font-size: 28rpx; color: #fff; }
  436. .feedback-section {
  437. display: flex;
  438. align-items: center;
  439. padding: 24rpx 30rpx;
  440. background: #fff;
  441. margin: 16rpx;
  442. border-radius: 12rpx;
  443. border: 1rpx solid #f0f0f0;
  444. }
  445. .feedback-icon { font-size: 32rpx; margin-right: 12rpx; }
  446. .feedback-text { flex: 1; font-size: 26rpx; color: #4A9BD7; }
  447. .feedback-arrow { font-size: 28rpx; color: #ccc; }
  448. .bottom-spacer { height: 40rpx; }
  449. /* 报告解读 */
  450. .interpretation-box {
  451. background: #FFF8E1;
  452. border-radius: 12rpx;
  453. padding: 16rpx 20rpx;
  454. margin: 16rpx 0 0;
  455. border-left: 6rpx solid #FFB300;
  456. }
  457. .interp-text {
  458. font-size: 24rpx;
  459. color: #795548;
  460. line-height: 1.6;
  461. }
  462. /* AI 解读入口 */
  463. .ai-fab {
  464. position: fixed;
  465. right: 30rpx;
  466. bottom: 100rpx;
  467. display: flex;
  468. flex-direction: row;
  469. align-items: center;
  470. background: linear-gradient(135deg, #6366F1, #8B5CF6);
  471. border-radius: 40rpx;
  472. padding: 16rpx 28rpx;
  473. box-shadow: 0 4rpx 16rpx rgba(99, 102, 241, 0.4);
  474. z-index: 100;
  475. }
  476. .ai-fab .fab-icon { font-size: 32rpx; margin-right: 12rpx; }
  477. .ai-fab .fab-text { font-size: 26rpx; color: #fff; font-weight: 500; }
  478. </style>