report-upload.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592
  1. <template>
  2. <view class="upload-container">
  3. <!-- 说明提示 -->
  4. <view class="tip-banner">
  5. <text class="tip-icon">📋</text>
  6. <text class="tip-text">上传体检/菌群报告、认知测评、心理测评或情绪打卡记录,为您生成个性化成长方案</text>
  7. </view>
  8. <!-- 表单 -->
  9. <view class="form-section">
  10. <!-- 上传方式:并排卡片 -->
  11. <view class="upload-methods">
  12. <view class="upload-card card-file" @click="pickFile">
  13. <view class="upload-card-icon">📄</view>
  14. <text class="upload-card-title">从聊天选择文件</text>
  15. <text class="upload-card-desc">PDF / Word / 图片<br/>体检报告、测评报告、情绪打卡</text>
  16. </view>
  17. <view class="upload-card card-photo" @click="takePhoto">
  18. <view class="upload-card-icon">📷</view>
  19. <text class="upload-card-title">拍照上传</text>
  20. <text class="upload-card-desc">拍摄舌象照片<br/>或单页纸质报告</text>
  21. </view>
  22. </view>
  23. <!-- 责任声明 -->
  24. <view class="form-item disclaimer-box">
  25. <view class="disclaimer-check" @click="toggleAgree">
  26. <view class="disclaimer-checkbox" :class="{ 'disclaimer-checked': agreedToTerms }">
  27. <text class="disclaimer-checkmark" v-if="agreedToTerms">✓</text>
  28. </view>
  29. <text class="disclaimer-label">我已阅读并同意以下声明</text>
  30. </view>
  31. <view class="disclaimer-content">
  32. <text class="disclaimer-text">我同意平台使用本报告数据进行分析,为我提供个性化健康解决方案;未经我授权,平台不会以任何形式将我的报告内容提供给其他任何个人或机构。</text>
  33. </view>
  34. </view>
  35. <!-- 上传中 -->
  36. <view class="form-item" v-if="uploading">
  37. <text class="form-label">上传中...</text>
  38. <view class="analyzing-status">
  39. <text class="analyzing-text">正在上传文件,即将进入分析页面...</text>
  40. </view>
  41. </view>
  42. </view>
  43. <!-- 上传说明 -->
  44. <view class="guide-section">
  45. <view class="guide-header">
  46. <text class="guide-header-icon">📖</text>
  47. <text class="guide-header-title">可上传的报告类型</text>
  48. </view>
  49. <view class="guide-list">
  50. <view class="guide-item">
  51. <view class="guide-icon-box guide-icon-blue"><text class="guide-icon">📄</text></view>
  52. <view class="guide-info">
  53. <text class="guide-name">体检报告</text>
  54. <text class="guide-desc">血常规、尿常规、生化检验等,支持 PDF 或清晰照片</text>
  55. </view>
  56. </view>
  57. <view class="guide-item">
  58. <view class="guide-icon-box guide-icon-green"><text class="guide-icon">🦠</text></view>
  59. <view class="guide-info">
  60. <text class="guide-name">肠道菌群检测报告</text>
  61. <text class="guide-desc">支持 PDF 或清晰照片,AI 自动解读菌群健康</text>
  62. </view>
  63. </view>
  64. <view class="guide-item">
  65. <view class="guide-icon-box guide-icon-orange"><text class="guide-icon">👅</text></view>
  66. <view class="guide-info">
  67. <text class="guide-name">舌象照片</text>
  68. <text class="guide-desc">拍摄舌象照片,AI 辅助舌诊分析</text>
  69. </view>
  70. </view>
  71. <view class="guide-item">
  72. <view class="guide-icon-box guide-icon-purple"><text class="guide-icon">🧠</text></view>
  73. <view class="guide-info">
  74. <text class="guide-name">认知能力测评报告</text>
  75. <text class="guide-desc">韦氏、瑞文等标准化认知测评,以及双培强基工程DAN认知能力测评,AI 解读认知结构与发展建议</text>
  76. </view>
  77. </view>
  78. <view class="guide-item">
  79. <view class="guide-icon-box guide-icon-teal"><text class="guide-icon">💭</text></view>
  80. <view class="guide-info">
  81. <text class="guide-name">心理测评报告</text>
  82. <text class="guide-desc">气质类型、情绪智力、人格特征等心理测评,以及双培强基工程DAN人格、情绪、自我概念和自驱力测评,辅助家庭教育决策</text>
  83. </view>
  84. </view>
  85. <view class="guide-item">
  86. <view class="guide-icon-box guide-icon-pink"><text class="guide-icon">📝</text></view>
  87. <view class="guide-info">
  88. <text class="guide-name">情绪打卡记录</text>
  89. <text class="guide-desc">手机端情绪打卡截图或记录文件,追踪情绪变化趋势</text>
  90. </view>
  91. </view>
  92. <view class="guide-item" @click="goBodyFatScale">
  93. <view class="guide-icon-box guide-icon-teal"><text class="guide-icon">⚖️</text></view>
  94. <view class="guide-info">
  95. <text class="guide-name">体脂秤报告</text>
  96. <text class="guide-desc">拍摄体脂秤显示屏照片,AI 自动提取体重、BMI、体脂率等指标</text>
  97. </view>
  98. </view>
  99. </view>
  100. <view class="guide-tips">
  101. <text class="guide-tips-title">温馨提示</text>
  102. <text class="guide-tips-item">· 单次上传 1 个文件,支持 PDF / JPG / PNG 格式</text>
  103. <text class="guide-tips-item">· 拍照时请保持光线充足,确保报告内容完整清晰</text>
  104. <text class="guide-tips-item">· 上传后由 AI 自动解析,解析结果可在线编辑确认</text>
  105. </view>
  106. </view>
  107. </view>
  108. </template>
  109. <script>
  110. import { uploadReportOnly } from '../../utils/api.js'
  111. export default {
  112. data() {
  113. return {
  114. memberId: null,
  115. uploading: false,
  116. selectedFile: null,
  117. photoPath: null,
  118. agreedToTerms: false,
  119. currentMemberId: null,
  120. extractedMetrics: []
  121. }
  122. },
  123. onLoad: function(options) {
  124. this.memberId = options.memberId ? parseInt(options.memberId) : null
  125. if (options.bodyFatMetrics) {
  126. try {
  127. this.extractedMetrics = JSON.parse(decodeURIComponent(options.bodyFatMetrics))
  128. } catch (e) {}
  129. }
  130. },
  131. methods: {
  132. // 切换同意声明
  133. toggleAgree() {
  134. this.agreedToTerms = !this.agreedToTerms
  135. },
  136. // 未勾选声明时弹窗确认
  137. ensureAgreed(isPhoto) {
  138. if (this.agreedToTerms) {
  139. this.startPick(isPhoto)
  140. return
  141. }
  142. uni.showModal({
  143. title: '数据使用声明',
  144. content: '上传报告即代表您同意平台使用本报告数据进行分析,为您提供个性化健康解决方案;未经您授权,平台不会以任何形式将您的报告内容提供给其他任何个人或机构。点击「同意继续」即表示您已阅读并同意。',
  145. confirmText: '同意继续',
  146. cancelText: '暂不',
  147. success: (res) => {
  148. if (res.confirm) {
  149. this.agreedToTerms = true
  150. this.startPick(isPhoto)
  151. }
  152. }
  153. })
  154. },
  155. startPick(isPhoto) {
  156. if (isPhoto) {
  157. uni.chooseImage({
  158. count: 1,
  159. sourceType: ['camera', 'album'],
  160. success: (res) => {
  161. this.photoPath = res.tempFilePaths[0]
  162. this.uploadAndGoConfirm(this.photoPath, 'image')
  163. }
  164. })
  165. } else {
  166. uni.chooseMessageFile({
  167. count: 1,
  168. type: 'file',
  169. extension: ['pdf'],
  170. success: (res) => {
  171. var files = res.tempFiles
  172. if (files && files.length > 0) {
  173. this.selectedFile = files[0]
  174. this.uploadAndGoConfirm(files[0].path, '')
  175. }
  176. },
  177. fail: (e) => {
  178. console.log('选择文件取消或失败', e)
  179. }
  180. })
  181. }
  182. },
  183. // ===== 拍照上传 =====
  184. takePhoto() {
  185. if (this.agreedToTerms) {
  186. this.startPick(true)
  187. return
  188. }
  189. uni.showModal({
  190. title: '数据使用声明',
  191. content: '上传报告即代表您同意平台使用本报告数据进行分析,为您提供个性化健康解决方案;未经您授权,平台不会以任何形式将您的报告内容提供给其他任何个人或机构。点击「同意继续」即表示您已阅读并同意。',
  192. confirmText: '同意继续',
  193. cancelText: '暂不',
  194. success: (res) => {
  195. if (res.confirm) {
  196. this.agreedToTerms = true
  197. this.startPick(true)
  198. }
  199. }
  200. })
  201. },
  202. // ===== 文件上传 =====
  203. pickFile() {
  204. if (this.agreedToTerms) {
  205. this.startPick(false)
  206. return
  207. }
  208. uni.showModal({
  209. title: '数据使用声明',
  210. content: '上传报告即代表您同意平台使用本报告数据进行分析,为您提供个性化健康解决方案;未经您授权,平台不会以任何形式将您的报告内容提供给其他任何个人或机构。点击「同意继续」即表示您已阅读并同意。',
  211. confirmText: '同意继续',
  212. cancelText: '暂不',
  213. success: (res) => {
  214. if (res.confirm) {
  215. this.agreedToTerms = true
  216. this.startPick(false)
  217. }
  218. }
  219. })
  220. },
  221. // 上传文件 → 自动跳转确认页(确认页按 draftId 触发解析并自动刷新显示结果)
  222. uploadAndGoConfirm(filePath, type) {
  223. if (!filePath) return
  224. this.uploading = true
  225. uni.showLoading({ title: '上传中...' })
  226. uploadReportOnly(filePath, type).then((res) => {
  227. uni.hideLoading()
  228. this.uploading = false
  229. var draftId = res.data && res.data.draftId
  230. if (!draftId) {
  231. uni.showToast({ title: (res && res.message) || '上传失败', icon: 'none' })
  232. return
  233. }
  234. uni.redirectTo({ url: '/pages/health/gut-flora-detail?draftId=' + draftId })
  235. }).catch((err) => {
  236. uni.hideLoading()
  237. this.uploading = false
  238. var msg = (err && err.message) || '上传失败,请重试'
  239. uni.showToast({ title: msg, icon: 'none' })
  240. })
  241. },
  242. // ===== 体脂秤专用上传 =====
  243. goBodyFatScale() {
  244. if (!this.agreedToTerms) {
  245. uni.showModal({
  246. title: '数据使用声明',
  247. content: '上传体脂秤照片即代表您同意平台使用本报告数据进行分析。点击「同意继续」即表示您已阅读并同意。',
  248. confirmText: '同意继续',
  249. cancelText: '暂不',
  250. success: (res) => {
  251. if (res.confirm) {
  252. this.agreedToTerms = true
  253. this.startBodyFatUpload()
  254. }
  255. }
  256. })
  257. return
  258. }
  259. this.startBodyFatUpload()
  260. },
  261. startBodyFatUpload() {
  262. var self = this
  263. uni.chooseImage({
  264. count: 1,
  265. sourceType: ['camera', 'album'],
  266. success: function(res) {
  267. self.photoPath = res.tempFilePaths[0]
  268. self.uploadBodyFat(self.photoPath)
  269. }
  270. })
  271. },
  272. uploadBodyFat(filePath) {
  273. var self = this
  274. self.uploading = true
  275. uni.showLoading({ title: 'AI 识别中...' })
  276. var token = uni.getStorageSync('token')
  277. uni.uploadFile({
  278. url: config.API_BASE_URL + '/api/health/report/bodyfat-upload',
  279. filePath: filePath,
  280. name: 'file',
  281. header: { 'Authorization': 'Bearer ' + token },
  282. success: function(uploadRes) {
  283. var data
  284. try { data = JSON.parse(uploadRes.data) } catch (e) { data = uploadRes.data }
  285. self.uploading = false
  286. uni.hideLoading()
  287. if (data && data.code === 200) {
  288. self.currentMemberId = data.data && data.data.memberId
  289. self.extractedMetrics = data.data && data.data.metrics || []
  290. uni.navigateTo({ url: '/pages/health/bodyfat-confirm' })
  291. } else {
  292. uni.showToast({ title: (data && data.message) || '上传失败', icon: 'none' })
  293. }
  294. },
  295. fail: function() {
  296. self.uploading = false
  297. uni.hideLoading()
  298. uni.showToast({ title: '网络错误', icon: 'none' })
  299. }
  300. })
  301. }
  302. }
  303. }
  304. </script>
  305. <style scoped>
  306. .upload-container {
  307. min-height: 100vh;
  308. background: #F5F7FA;
  309. }
  310. /* ===== 导航栏 ===== */
  311. .nav-placeholder {
  312. width: 80rpx;
  313. }
  314. /* ===== 提示条 ===== */
  315. .tip-banner {
  316. display: flex;
  317. flex-direction: row;
  318. align-items: center;
  319. background: #E3F2FD;
  320. margin: 20rpx 30rpx;
  321. padding: 20rpx 24rpx;
  322. border-radius: 16rpx;
  323. }
  324. .tip-icon {
  325. font-size: 32rpx;
  326. margin-right: 16rpx;
  327. flex-shrink: 0;
  328. }
  329. .tip-text {
  330. font-size: 24rpx;
  331. color: #1565C0;
  332. line-height: 1.5;
  333. }
  334. /* ===== 表单 ===== */
  335. .form-section {
  336. margin: 0 30rpx;
  337. }
  338. .form-item {
  339. background: #fff;
  340. border-radius: 16rpx;
  341. padding: 24rpx;
  342. margin-bottom: 20rpx;
  343. }
  344. .form-label {
  345. font-size: 26rpx;
  346. font-weight: bold;
  347. color: #333;
  348. display: block;
  349. margin-bottom: 16rpx;
  350. }
  351. /* 上传中状态 */
  352. .analyzing-status {
  353. display: flex;
  354. flex-direction: row;
  355. align-items: center;
  356. }
  357. .analyzing-text {
  358. font-size: 26rpx;
  359. color: #5B9BD5;
  360. }
  361. /* ===== 底部 ===== */
  362. .bottom-spacer {
  363. height: 60rpx;
  364. }
  365. /* ===== 责任声明 ===== */
  366. .disclaimer-box {
  367. background: #FFF8F0;
  368. border: 2rpx solid #FDE68A;
  369. }
  370. .disclaimer-check {
  371. display: flex;
  372. flex-direction: row;
  373. align-items: center;
  374. margin-bottom: 12rpx;
  375. }
  376. .disclaimer-checkbox {
  377. width: 36rpx;
  378. height: 36rpx;
  379. border-radius: 6rpx;
  380. border: 2rpx solid #CBD5E1;
  381. background: #fff;
  382. display: flex;
  383. align-items: center;
  384. justify-content: center;
  385. flex-shrink: 0;
  386. margin-right: 12rpx;
  387. }
  388. .disclaimer-checked {
  389. background: #5B9BD5;
  390. border-color: #5B9BD5;
  391. }
  392. .disclaimer-checkmark {
  393. font-size: 24rpx;
  394. color: #fff;
  395. font-weight: bold;
  396. }
  397. .disclaimer-label {
  398. font-size: 24rpx;
  399. color: #333;
  400. font-weight: 500;
  401. }
  402. .disclaimer-content {
  403. background: #FFFDF5;
  404. border-radius: 10rpx;
  405. padding: 16rpx;
  406. margin-left: 48rpx;
  407. }
  408. .disclaimer-text {
  409. font-size: 22rpx;
  410. color: #92400E;
  411. line-height: 1.6;
  412. }
  413. /* ===== 上传方式:并排卡片 ===== */
  414. .upload-methods {
  415. display: flex;
  416. flex-direction: row;
  417. gap: 20rpx;
  418. margin-bottom: 20rpx;
  419. }
  420. .upload-card {
  421. flex: 1;
  422. display: flex;
  423. flex-direction: column;
  424. align-items: center;
  425. justify-content: center;
  426. padding: 40rpx 20rpx;
  427. border-radius: 20rpx;
  428. text-align: center;
  429. min-height: 240rpx;
  430. }
  431. .upload-card:active {
  432. opacity: 0.85;
  433. }
  434. .upload-card-icon {
  435. width: 96rpx;
  436. height: 96rpx;
  437. border-radius: 24rpx;
  438. font-size: 48rpx;
  439. display: flex;
  440. align-items: center;
  441. justify-content: center;
  442. margin-bottom: 20rpx;
  443. }
  444. .upload-card-title {
  445. font-size: 28rpx;
  446. font-weight: 600;
  447. color: #333;
  448. margin-bottom: 10rpx;
  449. }
  450. .upload-card-desc {
  451. font-size: 22rpx;
  452. color: #94A3B8;
  453. line-height: 1.5;
  454. }
  455. .card-file {
  456. background: #F0F7FF;
  457. border: 2rpx dashed #93C5FD;
  458. }
  459. .card-file .upload-card-icon {
  460. background: linear-gradient(135deg, #3B82F6, #1D4ED8);
  461. color: #fff;
  462. box-shadow: 0 4rpx 12rpx rgba(59, 130, 246, 0.3);
  463. }
  464. .card-photo {
  465. background: #FFF7ED;
  466. border: 2rpx solid #FED7AA;
  467. }
  468. .card-photo .upload-card-icon {
  469. background: linear-gradient(135deg, #FB923C, #EA580C);
  470. color: #fff;
  471. box-shadow: 0 4rpx 12rpx rgba(234, 88, 12, 0.25);
  472. }
  473. /* ===== 上传说明 ===== */
  474. .guide-section {
  475. margin: 0 30rpx 30rpx;
  476. background: #fff;
  477. border-radius: 20rpx;
  478. padding: 28rpx 24rpx;
  479. box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
  480. }
  481. .guide-header {
  482. display: flex;
  483. flex-direction: row;
  484. align-items: center;
  485. margin-bottom: 20rpx;
  486. }
  487. .guide-header-icon {
  488. font-size: 30rpx;
  489. margin-right: 10rpx;
  490. }
  491. .guide-header-title {
  492. font-size: 28rpx;
  493. font-weight: bold;
  494. color: #333;
  495. }
  496. .guide-list {
  497. display: flex;
  498. flex-direction: column;
  499. }
  500. .guide-item {
  501. display: flex;
  502. flex-direction: row;
  503. align-items: center;
  504. padding: 16rpx 0;
  505. border-bottom: 1rpx solid #F0F0F0;
  506. }
  507. .guide-item:last-of-type {
  508. border-bottom: none;
  509. }
  510. .guide-icon-box {
  511. width: 72rpx;
  512. height: 72rpx;
  513. border-radius: 16rpx;
  514. display: flex;
  515. align-items: center;
  516. justify-content: center;
  517. flex-shrink: 0;
  518. margin-right: 20rpx;
  519. }
  520. .guide-icon {
  521. font-size: 36rpx;
  522. }
  523. .guide-icon-blue {
  524. background: #E3F2FD;
  525. }
  526. .guide-icon-green {
  527. background: #E8F5E9;
  528. }
  529. .guide-icon-orange {
  530. background: #FFF3E0;
  531. }
  532. .guide-icon-purple {
  533. background: #F3E5F5;
  534. }
  535. .guide-icon-teal {
  536. background: #E0F2F1;
  537. }
  538. .guide-icon-pink {
  539. background: #FCE4EC;
  540. }
  541. .guide-info {
  542. flex: 1;
  543. min-width: 0;
  544. display: flex;
  545. flex-direction: column;
  546. }
  547. .guide-name {
  548. font-size: 26rpx;
  549. font-weight: 600;
  550. color: #333;
  551. margin-bottom: 4rpx;
  552. }
  553. .guide-desc {
  554. font-size: 22rpx;
  555. color: #94A3B8;
  556. line-height: 1.4;
  557. }
  558. .guide-tips {
  559. background: #F8FAFC;
  560. border-radius: 12rpx;
  561. padding: 16rpx 20rpx;
  562. margin-top: 16rpx;
  563. display: flex;
  564. flex-direction: column;
  565. }
  566. .guide-tips-title {
  567. font-size: 24rpx;
  568. font-weight: 600;
  569. color: #5B9BD5;
  570. margin-bottom: 8rpx;
  571. }
  572. .guide-tips-item {
  573. font-size: 22rpx;
  574. color: #64748B;
  575. line-height: 1.7;
  576. }
  577. </style>