diagnoseResult.vue 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894
  1. <template>
  2. <div class="diagnosis-page AI-Design-container">
  3. <!-- 顶部导航 -->
  4. <div class="header">
  5. <van-nav-bar title="诊断结果" left-arrow @click-left="returnPage" @click-right="toHome">
  6. <template #right>
  7. <van-icon name="wap-home-o" color="#333" size="26" />
  8. </template>
  9. </van-nav-bar>
  10. </div>
  11. <div>
  12. <!-- 历史诊断 -->
  13. <div class="history-section">
  14. <div class="history-header" @click="viewHistory">
  15. <van-icon name="clock-o" color="#EC8868" />
  16. <span>历史诊断</span>
  17. <span v-if="!readState" class="badge-dot"></span>
  18. </div>
  19. </div>
  20. <section class="infoWrapper">
  21. <div v-if="canvasImg" class="canvasImgWrapper">
  22. <img :src="canvasImg" alt="">
  23. </div>
  24. <!-- 诊断内容容器(用于生成图片) -->
  25. <div class="diagnosis-container" ref="diagnosisRef">
  26. <div class="diagnosis-img-box">
  27. <img src="../../assets/AIDesign/diagnoseTit.png" class="diagnoseTitBg">
  28. <img class="diagnosis-image" :src="UserFilePathUrl" v-show="UserFilePathUrl"
  29. @click="imgClick(UserFilePathUrl)" style="object-fit: contain;" />
  30. <p v-show="!apiLoading && StateCode == 2">该建筑的墙面诊断报告如下,长按可保存分享</p>
  31. </div>
  32. <div class="diagnosis-content" v-if="!apiLoading && wallInfo && wallInfo.wallFinishing">
  33. <div class="diagnosis-box">
  34. <h2>
  35. <van-icon name="play" color="#2484F2" size="24" class="triangle" />
  36. 墙面装修类型
  37. </h2>
  38. <div class="content-item">
  39. <p class="item-desc">{{ wallInfo.wallFinishing }}</p>
  40. </div>
  41. </div>
  42. <div class="diagnosis-box">
  43. <h2>
  44. <van-icon name="play" color="#2484F2" size="24" class="triangle" />
  45. 表面状况
  46. </h2>
  47. <div class="content-item" v-for="(item, index) in wallInfo.surfaceConditions"
  48. :key="'surfaceConditions' + index">
  49. <p class="item-desc"> {{ item }}</p>
  50. </div>
  51. </div>
  52. <div class="diagnosis-box">
  53. <h2>
  54. <van-icon name="play" color="#2484F2" size="24" class="triangle" />
  55. 损坏说明
  56. </h2>
  57. <div class="content-item" v-for="(item, index) in wallInfo.damageNotes"
  58. :key="'damageNotes' + index">
  59. <p class="item-desc">{{ item }}</p>
  60. </div>
  61. </div>
  62. <!-- 外墙-解决方案 -->
  63. <section v-if="wallType === 'outside'">
  64. <div class="diagnosis-box" v-if="wallInfo.Solutions && wallInfo.Solutions.length > 0">
  65. <h2>
  66. <van-icon name="play" color="#2484F2" size="24" class="triangle" />
  67. 解决方案
  68. </h2>
  69. <div class="content-item" v-for="(item, index) in wallInfo.Solutions"
  70. :key="'solutions' + index">
  71. <h3 class="item-title">{{ numberToChinese(index + 1) }}、{{ item.name }}</h3>
  72. <div class="item-desc" v-for="(items, indexs) in item.solution"
  73. :key="'solution' + indexs">
  74. <h4 class="items-title">{{ indexs + 1 }}.{{ items.title }}</h4>
  75. <p class="items-desc">{{ items.description }}</p>
  76. </div>
  77. </div>
  78. </div>
  79. <div class="diagnosis-box" v-if="wallInfo.DetectionDetails && wallInfo.DetectionDetails.length > 0">
  80. <h2>
  81. <van-icon name="play" color="#2484F2" size="24" class="triangle" />
  82. 墙病诊断详情
  83. </h2>
  84. <div class="content-item content-item-detectionDetails"
  85. v-for="(item, index) in wallInfo.DetectionDetails"
  86. :key="'detectionDetails' + index">
  87. <div class="reason-title">
  88. <div class="reason-title-l">{{ index + 1 }}.{{ item.Name }} </div>
  89. </div>
  90. <section>
  91. <!-- 原因分析 -->
  92. <div class="detail-title">原因分析</div>
  93. <div v-for="(cause, i) in item.Causes" :key="'causes' + i" :value="cause"
  94. class="detail-item">
  95. <van-icon name="location-o" />
  96. {{ cause }}
  97. </div>
  98. <!-- 潜在危害 -->
  99. <div class="detail-title">潜在危害</div>
  100. <div v-for="(hazard, i) in item.Hazards" :key="'hazards' + i" :value="hazard"
  101. class="detail-item hazard-item">
  102. <van-icon name="warning-o" />
  103. {{ hazard }}
  104. </div>
  105. <!-- 修复工艺 -->
  106. <div class="detail-title">推荐修复工艺</div>
  107. <div class="detail-item" v-for="(items, indexs) in item.RepairProcesses"
  108. :key="'RepairProcesses' + indexs">
  109. {{ indexs + 1 }}.{{ items }}
  110. </div>
  111. </section>
  112. </div>
  113. </div>
  114. </section>
  115. <!-- 内墙-墙病诊断详情 -->
  116. <section v-if="wallType === 'inside'">
  117. <div class="diagnosis-box">
  118. <h2>
  119. <van-icon name="play" color="#2484F2" size="24" class="triangle" />
  120. 墙病诊断详情
  121. </h2>
  122. <div class="content-item content-item-detectionDetails"
  123. v-for="(item, index) in wallInfo.detectionDetails"
  124. :key="'detectionDetails' + index">
  125. <div class="reason-title">
  126. <div class="reason-title-l">{{ index + 1 }}.{{ item.name }} </div>
  127. </div>
  128. <section>
  129. <!-- 原因分析 -->
  130. <div class="detail-title">原因分析</div>
  131. <div v-for="(cause, i) in item.causes" :key="'causes' + i" :value="cause"
  132. class="detail-item">
  133. <van-icon name="location-o" />
  134. {{ cause }}
  135. </div>
  136. <!-- 潜在危害 -->
  137. <div class="detail-title">潜在危害</div>
  138. <div v-for="(hazard, i) in item.hazards" :key="'hazards' + i" :value="hazard"
  139. class="detail-item hazard-item">
  140. <van-icon name="warning-o" />
  141. {{ hazard }}
  142. </div>
  143. <!-- 修复工艺 -->
  144. <div class="detail-title">推荐修复工艺</div>
  145. <div class="process-tags">
  146. <span v-for="(process, i) in item.repairProcesses"
  147. :key="'repairProcesses' + i" class="process-tag">
  148. {{ process }}
  149. </span>
  150. </div>
  151. </section>
  152. </div>
  153. </div>
  154. </section>
  155. <!-- 内墙-解决方案 -->
  156. <section v-if="wallType === 'inside'">
  157. <div class="diagnosis-box">
  158. <h2>
  159. <van-icon name="play" color="#2484F2" size="24" class="triangle" />
  160. 工艺说明
  161. </h2>
  162. <div class="content-item" v-for="(item, index) in wallInfo.solutions"
  163. :key="'solutions' + index">
  164. <h3 class="item-title">{{ numberToChinese(index + 1) }}、{{ item.name }}</h3>
  165. <p class="item-desc">{{ item.description }}</p>
  166. </div>
  167. </div>
  168. </section>
  169. </div>
  170. <!-- 诊断中 -->
  171. <div v-show="!apiLoading && StateCode == 1" class="diagnoseing-wrapper">
  172. <h3>正在为您飞速诊断...</h3>
  173. <ul>
  174. <li>为了提供给您更好的诊断效果,请避免上传模糊、光线不佳、非{{ wallType == "outside" ? "外墙" : "内墙" }}的图片。</li>
  175. <li>立邦AI诊断建议仅作参考,请结合专业人员现场复核。</li>
  176. <li><span class="blod">无需等待</span>,诊断结果会由<span class="blod">企微推送</span>给您。</li>
  177. </ul>
  178. </div>
  179. <!-- 诊断失败 -->
  180. <div v-show="!apiLoading && (StateCode == 3 || StateCode == 4)" class="diagnoseing-wrapper">
  181. <h3>{{ errDescription }}</h3>
  182. <ul>
  183. <li>为了提供给您更好的诊断效果,请避免上传模糊、光线不佳、非{{ wallType == "outside" ? "外墙" : "内墙" }}的图片。</li>
  184. <li>立邦诊断仅作参考,请结合现场实际情况,由施工团队提出具体落地方案。</li>
  185. </ul>
  186. <p></p>
  187. </div>
  188. </div>
  189. </section>
  190. <!-- 保存图片状态提示 -->
  191. <van-loading v-if="isSavingImage" class="save-loading" type="spinner" :text="savingText" />
  192. <!-- AI设计 -->
  193. <div class="generate-section" v-show="!apiLoading && StateCode === 2">
  194. <van-button type="primary" block @click="handleAIDesignClick" color="#2484F2">AI设计</van-button>
  195. </div>
  196. <div class="generate-section" v-show="!apiLoading && (StateCode === 3 || StateCode === 4)">
  197. <van-button type="primary" block @click="handleAgainClick" color="#2484F2">重新上传</van-button>
  198. </div>
  199. </div>
  200. <!-- 全屏加载遮罩 -->
  201. <div class="fullscreen-loading" v-if="isLoading">
  202. <van-loading type="spinner" color="#ffffff" size="50" />
  203. <p class="loading-text">诊断生成中...</p>
  204. </div>
  205. </div>
  206. </template>
  207. <script lang="ts">
  208. import { Component, Vue, Ref, Watch } from 'vue-property-decorator';
  209. import html2canvas from 'html2canvas';
  210. import { NavBar, Button, Image, Loading, Empty, Toast, ImagePreview, Tag } from 'vant';
  211. import { diagGetEntity, diagGetReadState, diagUpdateReadState } from "@/api/indexAI";
  212. import { getWecomType, toLBHome, getWxconfig, checkAndSaveUserWecomType } from '@/utils/index';
  213. import axios from "axios";
  214. declare let wx: any;
  215. @Component({
  216. components: {
  217. VanNavBar: NavBar,
  218. VanButton: Button,
  219. VanImage: Image,
  220. VanLoading: Loading,
  221. VanEmpty: Empty,
  222. VanToast: Toast,
  223. VanTag: Tag
  224. }
  225. })
  226. export default class DiagnosisPage extends Vue {
  227. @Ref('diagnosisRef') private diagnosisRef!: HTMLElement | null;
  228. private designPageApi = {
  229. outside: '/AIDesign/design',
  230. inside: '/AIDesign/insideDesign'
  231. };
  232. private agentFrom = window.localStorage.getItem('agentFromAI');
  233. private UserFilePathUrl = '';//用户原图
  234. private pollingTimer: number | null = null; // 轮询定时器引用
  235. private timer = null;
  236. private StateCode = null;
  237. private regenerateDisable = true;
  238. private readState = true;
  239. private wallType = '';
  240. private wallInfo = null;
  241. private isLoading = true; // 数据加载状态
  242. private apiLoading = true;// 接口加载状态
  243. private isSavingImage = false; // 图片保存状态
  244. private savingText = '正在保存图片...'; // 保存提示文字
  245. private errDescription = '图片模糊/光线不佳,请重新上传';
  246. private appearPosterFlag = true;
  247. private aiImg = "https://p9-flow-imagex-sign.byteimg.com/tos-cn-i-a9rns2rl98/rc_gen_image/7b6fbb743f984de4b6a2ed36e0b69c95preview.jpeg~tplv-a9rns2rl98-downsize_watermark_1_5_b.png?rcl=202511241033234D2C3B74A04216BD29F0&rk3s=8e244e95&rrcfp=ddbb2dc7&x-expires=2079311619&x-signature=LmTn71nSgdI%2F5kcqJ8L561SedVQ%3D"
  248. private longPressTimer: NodeJS.Timeout | null = null; // 长按计时器
  249. private canvasImg = null;
  250. // 页面挂载时请求数据
  251. created() {
  252. checkAndSaveUserWecomType();
  253. getWxconfig();
  254. }
  255. activated() {
  256. // 初始化数据
  257. this.initialize();
  258. this.GetReadStateFn();
  259. this.GetEntityDataFirst()
  260. }
  261. deactivated() {
  262. clearInterval(this.pollingTimer);
  263. clearTimeout(this.timer);
  264. clearTimeout(this.longPressTimer);
  265. this.pollingTimer = null;
  266. this.timer = null;
  267. this.longPressTimer = null;
  268. sessionStorage.removeItem("diagnoseSelectedImage")
  269. }
  270. // 初始化数据
  271. initialize() {
  272. clearInterval(this.pollingTimer);
  273. clearTimeout(this.timer);
  274. clearTimeout(this.longPressTimer);
  275. this.UserFilePathUrl = sessionStorage.getItem("diagnoseSelectedImage") || '';
  276. this.pollingTimer = null; // 轮询定时器引用
  277. this.timer = null;
  278. this.StateCode = null;
  279. this.regenerateDisable = true;
  280. this.readState = true;
  281. this.wallInfo = null;
  282. this.isLoading = true; // 数据加载状态
  283. this.apiLoading = true;
  284. this.isSavingImage = false; // 图片保存状态
  285. this.savingText = '正在保存图片...'; // 保存提示文字
  286. this.appearPosterFlag = true;
  287. this.longPressTimer = null; // 长按计时器
  288. this.wallType = this.$route.query.wallType || 'outside';
  289. }
  290. //进入页面首次调用获取状态
  291. GetEntityDataFirst() {
  292. let that = this;
  293. const F_ID = this.$route.query.F_id || "";
  294. const formData = new FormData();
  295. formData.append('F_id', F_ID);
  296. diagGetEntity(formData).then(response => {
  297. if (response.StatusCode == 200) {
  298. if (response.Data == null) {
  299. that.isLoading = false;
  300. that.apiLoading = false;
  301. that.regenerateDisable = true;
  302. that.StateCode = 1;
  303. if (that.timer) {
  304. clearTimeout(that.timer);
  305. that.timer = null;
  306. }
  307. that.timer = setTimeout(() => {
  308. that.startPolling();
  309. }, 20000);
  310. } else {
  311. this.StateCode = response.Data.StateCode;
  312. that.UserFilePathUrl = response.Data.F_UserFilePath;
  313. const wallInfoObj = response.Data.wallInfo || '';
  314. const WallRepairAnalysisObj = response.Data.WallRepairAnalysis || '';
  315. if (this.wallType === 'outside') {
  316. that.wallInfo = wallInfoObj;
  317. } else if (this.wallType === 'inside') {
  318. that.wallInfo = WallRepairAnalysisObj;
  319. }
  320. // that.isLoading = false;
  321. that.apiLoading = false;
  322. sessionStorage.removeItem("diagnoseSelectedImage");
  323. // 生成图片
  324. that.longPressTimer = setTimeout(() => {
  325. that.handleLongPress();
  326. }, 500);
  327. if (response.Data.StateCode == 1) {
  328. let createTime = new Date(response.Data.CreateDate);
  329. let currentTime = new Date().getTime();
  330. if (currentTime - createTime > 20000) {
  331. that.startPolling();
  332. } else {
  333. if (that.timer) {
  334. clearTimeout(that.timer);
  335. that.timer = null;
  336. }
  337. that.timer = setTimeout(() => {
  338. that.startPolling();
  339. }, currentTime - createTime);
  340. }
  341. } else {
  342. if (response.Data.StateCode == 2 || response.Data.StateCode == 3 || response.Data.StateCode == 4) {
  343. that.regenerateDisable = false;
  344. if (response.Data.Description) {
  345. that.errDescription = response.Data.Description;
  346. }
  347. } else {
  348. that.regenerateDisable = true;
  349. }
  350. }
  351. }
  352. } else {
  353. this.$toast.fail(response.Info);
  354. that.isLoading = false;
  355. that.apiLoading = false;
  356. }
  357. })
  358. }
  359. returnPage() {
  360. this.$router.push({ path: 'diagnose', query: { wallType: this.wallType } });
  361. // this.$router.back();
  362. }
  363. toHome() {
  364. toLBHome()
  365. }
  366. private startPolling(): void {
  367. // 立即执行一次检查
  368. this.GetEntityData();
  369. // 设置定时器每秒执行一次检查
  370. this.pollingTimer = window.setInterval(() => {
  371. if (!this.wallInfo) {
  372. if (this.StateCode == 3 || this.StateCode == 4) {
  373. clearInterval(this.pollingTimer);
  374. this.pollingTimer = null;
  375. } else {
  376. this.GetEntityData();
  377. }
  378. } else {
  379. if (this.pollingTimer) {
  380. clearInterval(this.pollingTimer);
  381. this.pollingTimer = null;
  382. }
  383. }
  384. }, 3000);
  385. }
  386. GetEntityData() {
  387. let that = this;
  388. const F_ID = this.$route.query.F_id || "";
  389. const formData = new FormData();
  390. formData.append('F_id', F_ID);
  391. diagGetEntity(formData).then(response => {
  392. if (response.StatusCode == 200) {
  393. if (response.Data) {
  394. this.StateCode = response.Data.StateCode;
  395. that.UserFilePathUrl = response.Data.F_UserFilePath;
  396. // const regex = /^\d+\.\s*/; // 匹配数字+.+空格
  397. if (this.wallType === 'outside') {
  398. that.wallInfo = response.Data.wallInfo || '';
  399. } else if (this.wallType === 'inside') {
  400. that.wallInfo = response.Data.WallRepairAnalysis || '';
  401. }
  402. // that.isLoading = false;
  403. that.apiLoading = false;
  404. sessionStorage.removeItem("diagnoseSelectedImage");
  405. // 生成图片
  406. that.longPressTimer = setTimeout(() => {
  407. that.handleLongPress();
  408. }, 500);
  409. if (response.Data.StateCode == 2 || response.Data.StateCode == 3 || response.Data.StateCode == 4) {
  410. that.regenerateDisable = false;
  411. if (response.Data.Description) {
  412. that.errDescription = response.Data.Description;
  413. }
  414. } else {
  415. that.regenerateDisable = true;
  416. }
  417. } else {
  418. that.regenerateDisable = true;
  419. }
  420. } else {
  421. that.isLoading = false;
  422. that.apiLoading = false;
  423. this.$toast.fail(response.Info);
  424. }
  425. })
  426. }
  427. imgClick(url) {
  428. ImagePreview([url]);
  429. }
  430. GetReadStateFn() {
  431. const formData = new FormData();
  432. // const userInfo: any = JSON.parse(window.localStorage.getItem("userInfoV1")!);
  433. // formData.append('WXuserid', userInfo.loginName);
  434. // 0外墙 1内墙
  435. const Entrytype = this.wallType === 'inside' ? 1 : 0;
  436. formData.append('Entrytype', Entrytype);
  437. diagGetReadState(formData).then(response => {
  438. if (response.StatusCode == 200) {
  439. this.readState = response.Data.readState;
  440. }
  441. });
  442. }
  443. // 跳转AI设计页面
  444. private handleAIDesignClick() {
  445. // this.$router.push({ path: this.designPageApi[this.wallType], query: { from: 'diagnoseResult', diagnoseResultImg: this.UserFilePathUrl } });
  446. this.$router.push({ path: this.designPageApi[this.wallType], query: { from: 'diagnoseResult' } });
  447. }
  448. // 重新上传
  449. private handleAgainClick() {
  450. this.$router.push({ path: '/AIDesign/diagnose', query: { F_id: this.$route.query.F_id } });
  451. }
  452. // 历史生成
  453. private viewHistory() {
  454. this.UpdateReadStateFn();
  455. // 实现查看历史生成逻辑
  456. this.$router.push({ path: '/AIDesign/diagnoseHistory', query: { wallType: this.wallType } });
  457. }
  458. // 更新未读状态
  459. UpdateReadStateFn() {
  460. const formData = new FormData();
  461. // const userInfo: any = JSON.parse(window.localStorage.getItem("userInfoV1")!);
  462. // formData.append('WXuserid', userInfo.loginName);
  463. // 0外墙 1内墙
  464. const Entrytype = this.wallType === 'inside' ? 1 : 0;
  465. formData.append('Entrytype', Entrytype);
  466. diagUpdateReadState(formData).then(response => { });
  467. }
  468. // 转化汉字排序
  469. private numberToChinese(num: number): string {
  470. const chineseNumbers = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十'];
  471. if (num <= 10) {
  472. return chineseNumbers[num];
  473. }
  474. const shi = Math.floor(num / 10);
  475. const ge = num % 10;
  476. if (ge === 0) {
  477. return chineseNumbers[shi] + '十';
  478. } else {
  479. return chineseNumbers[shi] + '十' + chineseNumbers[ge];
  480. }
  481. }
  482. // 长按诊断区域生成图片
  483. private async handleLongPress(e: Event): void {
  484. let that = this;
  485. if (!this.diagnosisRef) {
  486. this.$toast('未找到诊断内容');
  487. that.isLoading = false;
  488. return;
  489. }
  490. // this.isSavingImage = true;
  491. try {
  492. // 使用html2canvas将DOM转为canvas
  493. const canvas = await html2canvas(this.diagnosisRef, {
  494. scale: 3, // 提高清晰度(2倍缩放)
  495. useCORS: true, // 允许跨域图片
  496. allowTaint: true,
  497. logging: true, // 关闭日志false
  498. backgroundColor: '#ffffff', // 背景色
  499. });
  500. // 转为图片URL
  501. const imageUrl = canvas.toDataURL('image/png');
  502. this.canvasImg = imageUrl;
  503. that.isLoading = false;
  504. clearTimeout(this.longPressTimer);
  505. this.longPressTimer = null;
  506. // 保存图片到手机
  507. // await this.saveImageToGallery(imageUrl);
  508. // this.savingText = '保存成功';
  509. } catch (error) {
  510. that.isLoading = false;
  511. // console.error('图片生成/保存失败:', error);
  512. // this.savingText = '保存失败';
  513. // this.$toast('保存失败,请重试');
  514. } finally {
  515. that.isLoading = false;
  516. // 延迟关闭加载提示,确保用户看到结果
  517. // setTimeout(() => {
  518. // this.isSavingImage = false;
  519. // this.savingText = '正在保存图片...';
  520. // }, 1500);
  521. }
  522. }
  523. // 保存图片到手机相册(适配移动端)
  524. private async saveImageToGallery(imageUrl: string) {
  525. // 1. 若在H5环境:创建a标签触发下载
  526. if (typeof window !== 'undefined') {
  527. const link = document.createElement('a');
  528. link.href = imageUrl;
  529. link.download = '墙面诊断报告.png';
  530. link.click();
  531. return;
  532. }
  533. }
  534. }
  535. </script>
  536. <style scoped lang="scss">
  537. .van-tag {
  538. margin-left: 34px;
  539. font-size: 14px;
  540. padding: 3px 10px 4px;
  541. }
  542. .margin-top-10 {
  543. margin-top: 10px;
  544. }
  545. .diagnosis-page {
  546. background-color: #f8f9fa;
  547. min-height: 100vh;
  548. flex-direction: column;
  549. // ul {
  550. // list-style-type: disc;
  551. // }
  552. .history-section {
  553. margin: 10px 0;
  554. padding: 0 16px;
  555. height: 46px;
  556. .history-header {
  557. display: flex;
  558. align-items: center;
  559. gap: 12px;
  560. padding: 12px 16px;
  561. background-color: #ffffff;
  562. border-radius: 8px;
  563. font-size: 14px;
  564. }
  565. .icon-clock {
  566. font-size: 12px;
  567. margin-right: 8px;
  568. }
  569. .history-header span {
  570. font-weight: 500;
  571. color: #EC8868;
  572. }
  573. .history-header .badge-dot {
  574. width: 5px;
  575. height: 5px;
  576. background-color: #ff4d4f;
  577. border-radius: 50%;
  578. }
  579. }
  580. .infoWrapper {
  581. position: relative;
  582. .canvasImgWrapper {
  583. position: absolute;
  584. top: 0;
  585. left: 0;
  586. width: 100%;
  587. height: 100%;
  588. z-index: 2;
  589. opacity: 0;
  590. img {
  591. width: 100%;
  592. height: 100%;
  593. }
  594. }
  595. }
  596. // 诊断内容容器(长按区域)
  597. .diagnosis-container {
  598. margin: 0 15px;
  599. padding: 20px 0;
  600. background-color: #ffffff;
  601. border-radius: 12px;
  602. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
  603. cursor: pointer;
  604. position: relative;
  605. user-select: none;
  606. /* 可选:禁止选中文字 */
  607. // 诊断图片
  608. .diagnosis-img-box {
  609. padding: 0 20px;
  610. text-align: center;
  611. margin-bottom: 30px;
  612. pointer-events: none;
  613. display: flex;
  614. flex-direction: column;
  615. align-items: center;
  616. img {
  617. display: block;
  618. }
  619. .diagnoseTitBg {
  620. width: 228px;
  621. height: auto;
  622. margin: 0 auto 15px;
  623. }
  624. .diagnosis-image {
  625. width: 60%;
  626. height: auto;
  627. }
  628. p {
  629. color: #86909C;
  630. font-size: 14px;
  631. }
  632. }
  633. // 内容列表
  634. .diagnosis-content {
  635. .diagnosis-box {
  636. // border-bottom: 1px solid #f0f0f0;
  637. margin-bottom: 15px;
  638. padding-bottom: 15px;
  639. h2 {
  640. font-size: 20px;
  641. }
  642. .content-item {
  643. margin-top: 10px;
  644. &:last-child {
  645. border-bottom: none;
  646. margin-bottom: 0;
  647. padding-bottom: 0;
  648. }
  649. .item-title {
  650. font-size: 16px;
  651. color: #333333;
  652. margin: 15px 0 8px;
  653. font-weight: bold;
  654. }
  655. .item-desc {
  656. font-size: 14px;
  657. color: #333;
  658. line-height: 1.4;
  659. margin: 10px 0;
  660. }
  661. .item-title,
  662. .item-desc {
  663. padding: 0 34px;
  664. }
  665. .items-title {
  666. font-size: 14px;
  667. margin-bottom: 8px;
  668. }
  669. .items-desc {
  670. color: #696868;
  671. padding: 0 10px;
  672. }
  673. .items-desc1 {
  674. color: #333;
  675. }
  676. }
  677. .content-item-detectionDetails {
  678. padding: 0 34px;
  679. margin-bottom: 30px;
  680. .reason-title {
  681. color: #333;
  682. display: flex;
  683. justify-content: space-between;
  684. // align-items: center;
  685. align-items: flex-start;
  686. .reason-title-l {
  687. margin: 5px 0 0 0;
  688. font-size: 16px;
  689. font-weight: 600;
  690. color: #262626;
  691. line-height: 30px;
  692. }
  693. .confidence-badge {
  694. background: #f0f7ff;
  695. border: 1px solid #91d5ff;
  696. border-radius: 16px;
  697. padding: 2px 6px;
  698. font-size: 10px;
  699. color: #1890ff;
  700. white-space: nowrap;
  701. flex-shrink: 0;
  702. display: flex;
  703. align-items: center;
  704. gap: 4px;
  705. cursor: help;
  706. position: relative;
  707. margin-top: 12px;
  708. }
  709. }
  710. .detail-title {
  711. font-size: 14px;
  712. font-weight: 600;
  713. color: #434343;
  714. margin: 10px 0 8px 0;
  715. display: flex;
  716. align-items: center;
  717. }
  718. .detail-title::before {
  719. content: '';
  720. width: 4px;
  721. height: 14px;
  722. background: #1890ff;
  723. border-radius: 2px;
  724. margin-right: 6px;
  725. margin-left: 4px;
  726. }
  727. .detail-item {
  728. padding: 4px;
  729. position: relative;
  730. font-size: 14px;
  731. line-height: 1.5;
  732. color: #666;
  733. }
  734. .process-tags {
  735. display: flex;
  736. flex-wrap: wrap;
  737. gap: 6px;
  738. padding: 0 4px;
  739. .process-tag {
  740. background: #ffffff;
  741. border: 1px solid #EC8868;
  742. color: #EC8868;
  743. padding: 4px 12px;
  744. border-radius: 16px;
  745. font-size: 12px;
  746. font-weight: 500;
  747. }
  748. }
  749. }
  750. }
  751. }
  752. // 加载状态
  753. .loading {
  754. display: block;
  755. margin: 40px auto;
  756. }
  757. }
  758. // 保存图片加载提示
  759. .save-loading {
  760. position: fixed;
  761. top: 50%;
  762. left: 50%;
  763. transform: translate(-50%, -50%);
  764. background-color: rgba(0, 0, 0, 0.7);
  765. padding: 15px 20px;
  766. border-radius: 8px;
  767. }
  768. .generate-section {
  769. width: 90%;
  770. margin: 20px auto 50px;
  771. display: flex;
  772. flex-direction: column;
  773. align-items: center;
  774. gap: 8px;
  775. border-radius: 8px;
  776. overflow: hidden;
  777. }
  778. .fullscreen-loading {
  779. position: fixed;
  780. top: 0;
  781. left: 0;
  782. right: 0;
  783. bottom: 0;
  784. background-color: rgba(0, 0, 0, 0.7); // 半透明遮罩
  785. z-index: 9999; // 确保在最上层
  786. display: flex;
  787. flex-direction: column;
  788. align-items: center;
  789. justify-content: center;
  790. backdrop-filter: blur(7px);
  791. .loading-text {
  792. margin-top: 16px;
  793. color: #fff;
  794. font-size: 15px;
  795. }
  796. }
  797. // 诊断中
  798. .diagnoseing-wrapper {
  799. padding: 0 20px;
  800. text-align: left;
  801. h3 {
  802. font-size: 18px;
  803. font-weight: bold;
  804. }
  805. h5 {
  806. font-size: 15px;
  807. font-weight: bold;
  808. color: #333;
  809. margin-bottom: 6px;
  810. }
  811. ul {
  812. padding-top: 20px;
  813. font-size: 14px;
  814. list-style-type: disc;
  815. padding-left: 16px;
  816. li {
  817. line-height: 20px;
  818. .blod {
  819. font-weight: bold;
  820. }
  821. }
  822. }
  823. }
  824. }
  825. </style>