diagnoseHistory.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562
  1. <template>
  2. <div class="resout-container AI-Design-container">
  3. <div class="header">
  4. <van-nav-bar title="历史诊断" left-arrow @click-left="returnPage" @click-right="toHome">
  5. <template #right>
  6. <van-icon name="wap-home-o" color="#333" size="26" />
  7. </template>
  8. </van-nav-bar>
  9. </div>
  10. <div class="container">
  11. <van-button v-show="Object.keys(groupedImages).length > 0" class="selecBtn" size="small"
  12. @click="selectImage">
  13. {{ isLongPressing ? "取消" : "选择" }}
  14. </van-button>
  15. <!-- 图片列表 -->
  16. <div class="image-groups" v-if="Object.keys(groupedImages).length > 0">
  17. <div v-for="(group, date) in groupedImages" :key="date" class="image-group">
  18. <div class="group-header">{{ formatDate(date) }}</div>
  19. <div class="image-grid">
  20. <div v-for="image in group" :key="image.F_ID" class="image-item" @click="toResultPage(image)">
  21. <div v-if="image.StateCode == 2" class="result-image-box">
  22. <van-image lazy-load :src="image.F_UserFilePath" class="house-image" alt="AI设计图"
  23. fit="cover" radius="5px" />
  24. <p class="WallFinishing" v-if="image.F_WallFinishing">{{ image.F_WallFinishing }}</p>
  25. <p class="WallFinishing"
  26. v-else-if="image.WallRepairAnalysis && image.WallRepairAnalysis.wallFinishing">{{
  27. image.WallRepairAnalysis.wallFinishing }}</p>
  28. </div>
  29. <div v-else class="loading-state">
  30. <img v-if="image.StateCode != 3 && image.StateCode != 4"
  31. src="@/assets/AIDesign/loding.gif" style="width: 50px;">
  32. <p class="loading-text" v-if="image.StateCode == 3 || image.StateCode == 4">生成失败</p>
  33. <p class="loading-text" v-else>图片正在生成中</p>
  34. </div>
  35. <div class="select-indicator" v-if="isLongPressing && image.StateCode == 2">
  36. <div class="select-indicator-circle">
  37. <span v-if="selectedImages.includes(getImageIndex(image))"
  38. class="icon-select selected"></span>
  39. </div>
  40. </div>
  41. </div>
  42. </div>
  43. </div>
  44. </div>
  45. <!-- 加载状态 -->
  46. <div v-if="isLoadingMore" class="loading-more">
  47. <van-loading size="20px" />
  48. <span class="loading-text">加载更多...</span>
  49. </div>
  50. <van-empty v-else-if="!isLoding && Object.keys(groupedImages).length === 0">
  51. <p>暂无数据</p>
  52. </van-empty>
  53. <!-- 已加载全部数据提示 -->
  54. <div v-if="!hasMore && !isLoding && Object.keys(groupedImages).length !== 0" class="load-all">
  55. 已加载全部数据
  56. </div>
  57. <!-- 删除按钮 -->
  58. <button class="delete-button" @click="deleteImage" v-if="selectedImages.length > 0">
  59. 删除图片({{ selectedImages.length }})
  60. </button>
  61. </div>
  62. </div>
  63. </template>
  64. <script lang="ts">
  65. import { Component, Vue } from "vue-property-decorator";
  66. import { diagGetDesignList, diagDeleteEntity } from "@/api/indexAI";
  67. import { Lazyload, Loading } from 'vant';
  68. import { toLBHome } from '@/utils/index';
  69. Vue.use(Lazyload);
  70. Vue.use(Loading);
  71. interface ImageItem {
  72. F_ID: string;
  73. BaseUrl: string;
  74. F_ResultFilePath: string;
  75. CreateDate: string;
  76. StateCode: number;
  77. }
  78. @Component
  79. export default class Resout extends Vue {
  80. private images: ImageItem[] = [];
  81. private selectedImages: number[] = [];
  82. private pressTimer: number | null = null;
  83. private isLongPressing = false;
  84. private isLoding = true;
  85. // 分页参数
  86. private pagination = {
  87. rows: 10, // 每页加载10条,适合移动端
  88. page: 1, // 当前页码
  89. sidx: "CreateDate",
  90. sord: "desc",
  91. records: 0, // 总记录数
  92. total: 0 // 总页数
  93. };
  94. // 滚动加载相关状态
  95. private isLoadingMore = false; // 是否正在加载更多
  96. private hasMore = true; // 是否还有更多数据
  97. private isProcessing = false; // 防止重复加载的锁
  98. private wallType = '';
  99. activated() {
  100. // 初始化数据
  101. this.initialize();
  102. this.getDataInfioList();
  103. // 监听滚动事件
  104. window.addEventListener('scroll', this.handleScroll);
  105. }
  106. deactivated() {
  107. clearTimeout(this.pressTimer);
  108. this.pressTimer = null;
  109. // 移除滚动监听
  110. window.removeEventListener('scroll', this.handleScroll);
  111. }
  112. initialize() {
  113. clearTimeout(this.pressTimer);
  114. this.images = [];
  115. this.wallType = this.$route.query.wallType || 'outside';
  116. this.pagination.page = 1;
  117. this.selectedImages = [];
  118. this.pressTimer = null;
  119. this.isLongPressing = false;
  120. this.isLoding = true;
  121. this.isLoadingMore = false;
  122. this.hasMore = true;
  123. this.isProcessing = false; // 防止重复加载的锁
  124. this.isLongPressing = false;
  125. }
  126. // 处理滚动事件
  127. private handleScroll() {
  128. // 防止重复触发加载
  129. if (this.isProcessing || !this.hasMore || this.isLoadingMore) return;
  130. // 获取滚动相关参数
  131. const scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
  132. const clientHeight = document.documentElement.clientHeight || window.innerHeight;
  133. const scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
  134. // 当滚动到距离底部200px时触发加载
  135. if (scrollTop + clientHeight >= scrollHeight - 200) {
  136. this.loadMoreData();
  137. }
  138. }
  139. // 加载更多数据
  140. private loadMoreData() {
  141. if (this.pagination.page >= this.pagination.total) {
  142. this.hasMore = false;
  143. return;
  144. }
  145. this.isProcessing = true;
  146. this.isLoadingMore = true;
  147. this.pagination.page += 1;
  148. // 延迟执行,优化用户体验
  149. setTimeout(() => {
  150. this.getDataInfioList(true);
  151. }, 300);
  152. }
  153. // 按日期分组逻辑
  154. get groupedImages(): Record<string, ImageItem[]> {
  155. if (!this.images || this.images.length === 0) return {};
  156. const groups: Record<string, ImageItem[]> = {};
  157. this.images.forEach(image => {
  158. const date = image.CreateDate.split('T')[0];
  159. if (!groups[date]) {
  160. groups[date] = [];
  161. }
  162. groups[date].push(image);
  163. });
  164. // 按日期倒序排列
  165. const sortedGroups: Record<string, ImageItem[]> = {};
  166. Object.keys(groups)
  167. .sort((a, b) => new Date(b).getTime() - new Date(a).getTime())
  168. .forEach(key => {
  169. sortedGroups[key] = groups[key];
  170. });
  171. return sortedGroups;
  172. }
  173. // 获取数据列表 - 支持加载更多
  174. getDataInfioList(isLoadMore = false): void {
  175. // 0外墙 1内墙
  176. const Entrytype = this.wallType === 'inside' ? 1 : 0;
  177. const queryJson = {
  178. Entrytype
  179. };
  180. const formData = new FormData();
  181. formData.append("pagination", JSON.stringify(this.pagination));
  182. formData.append("queryJson", JSON.stringify(queryJson));
  183. diagGetDesignList(formData).then((res) => {
  184. if (res.StatusCode == 200) {
  185. // 加载更多时合并数据,否则替换数据
  186. this.images = isLoadMore ? [...this.images, ...res.Data.rows] : res.Data.rows;
  187. // 更新分页信息
  188. this.pagination.records = res.Data.records;
  189. this.pagination.total = res.Data.total;
  190. // 判断是否还有更多数据
  191. this.hasMore = this.pagination.page < this.pagination.total;
  192. } else {
  193. this.$toast.fail(res.Info);
  194. }
  195. // 重置加载状态
  196. this.isLoding = false;
  197. this.isLoadingMore = false;
  198. this.isProcessing = false;
  199. }).catch(() => {
  200. // 错误处理
  201. this.isLoding = false;
  202. this.isLoadingMore = false;
  203. this.isProcessing = false;
  204. this.$toast.fail('加载失败,请重试');
  205. });
  206. }
  207. // 其他原有方法保持不变...
  208. returnPage() {
  209. this.$router.push({ path: '/AIDesign/diagnose', query: { wallType: this.wallType } });
  210. }
  211. toHome() {
  212. toLBHome()
  213. }
  214. // 去结果页面
  215. toResultPage(image) {
  216. if (this.isLongPressing && image.StateCode == 2) {
  217. this.toggleSelect(this.getImageIndex(image));
  218. } else {
  219. this.$router.push({
  220. path: '/AIDesign/diagnoseResult',
  221. query: {
  222. F_id: image.F_ID,
  223. wallType: this.wallType
  224. }
  225. });
  226. }
  227. }
  228. getImageIndex(image: ImageItem): number {
  229. return this.images.findIndex((item: ImageItem) => item.F_ID === image.F_ID);
  230. }
  231. formatDate(dateString: string): string {
  232. const date = new Date(dateString);
  233. const today = new Date();
  234. const yesterday = new Date(today);
  235. yesterday.setDate(yesterday.getDate() - 1);
  236. if (date.toDateString() === today.toDateString()) {
  237. return '今天';
  238. }
  239. if (date.toDateString() === yesterday.toDateString()) {
  240. return '昨天';
  241. }
  242. if (date.getFullYear() === today.getFullYear()) {
  243. return `${date.getMonth() + 1}月${date.getDate()}日`;
  244. }
  245. return `${date.getFullYear()}年${date.getMonth() + 1}月${date.getDate()}日`;
  246. }
  247. selectImage() {
  248. this.selectedImages = [];
  249. this.isLongPressing = !this.isLongPressing;
  250. }
  251. handleTouchStart(index: number, event: Event): void {
  252. event.preventDefault();
  253. this.pressTimer = window.setTimeout(() => {
  254. this.isLongPressing = true;
  255. this.toggleSelect(index);
  256. }, 500);
  257. }
  258. handleTouchEnd(index: number): void {
  259. if (this.pressTimer) {
  260. clearTimeout(this.pressTimer);
  261. this.pressTimer = null;
  262. }
  263. if (!this.isLongPressing) {
  264. this.toggleSelect(index);
  265. } else {
  266. const F_ID = this.images[index].F_ID;
  267. this.$router.push({
  268. path: '/AIDesign/diagnoseResult',
  269. query: {
  270. F_id: F_ID,
  271. wallType: this.wallType
  272. }
  273. });
  274. }
  275. this.isLongPressing = false;
  276. }
  277. toggleSelect(index: number): void {
  278. const idx = this.selectedImages.indexOf(index);
  279. if (idx === -1) {
  280. this.selectedImages.push(index);
  281. } else {
  282. this.selectedImages.splice(idx, 1);
  283. }
  284. }
  285. saveImage(): void {
  286. this.selectedImages.forEach((index) => {
  287. if (index < 0 || index >= this.images.length) return;
  288. const image = this.images[index];
  289. if (!image) return;
  290. const imageUrl = image.BaseUrl + image.F_ResultFilePath;
  291. this.downloadImage(imageUrl, `ai-design-${image.F_ID}.png`);
  292. });
  293. }
  294. downloadImage(imageUrl: string, filename: string): void {
  295. fetch(imageUrl).then(response => response.blob()).then(blob => {
  296. const blobUrl = URL.createObjectURL(blob);
  297. const link = document.createElement('a');
  298. link.href = blobUrl;
  299. link.download = `ai-design-${new Date().getTime()}.png`;
  300. link.style.display = 'none';
  301. document.body.appendChild(link);
  302. link.click();
  303. document.body.removeChild(link);
  304. URL.revokeObjectURL(blobUrl);
  305. }).catch(error => {
  306. console.error('图片下载失败:', error);
  307. this.$toast.fail('图片下载失败');
  308. });
  309. }
  310. deleteImage(): void {
  311. const F_ids = this.selectedImages.map(index => this.images[index].F_ID);
  312. const formData = new FormData();
  313. formData.append("F_ids", JSON.stringify(F_ids));
  314. diagDeleteEntity(formData).then((res) => {
  315. if (res.StatusCode == 200) {
  316. this.$toast.success("删除成功");
  317. // 重新加载第一页数据
  318. this.pagination.page = 1;
  319. this.getDataInfioList();
  320. this.selectedImages = [];
  321. } else {
  322. this.$toast.fail(res.Info);
  323. }
  324. });
  325. }
  326. beforeDestroy() {
  327. if (this.pressTimer) {
  328. clearTimeout(this.pressTimer);
  329. }
  330. window.removeEventListener('scroll', this.handleScroll);
  331. }
  332. }
  333. </script>
  334. <style scoped lang="scss">
  335. /* 原有样式保持不变,增加以下样式 */
  336. // 加载更多样式
  337. .loading-more {
  338. display: flex;
  339. align-items: center;
  340. justify-content: center;
  341. padding: 20px 0;
  342. color: #666;
  343. .loading-text {
  344. margin-left: 8px;
  345. font-size: 14px;
  346. }
  347. }
  348. // 加载完成提示
  349. .load-all {
  350. text-align: center;
  351. padding: 15px 0;
  352. color: #999;
  353. font-size: 14px;
  354. }
  355. /* 其他原有样式... */
  356. .resout-container {
  357. background-color: #f8f9fa;
  358. min-height: 100vh;
  359. flex-direction: column;
  360. }
  361. .container {
  362. padding: 20px;
  363. position: relative;
  364. .selecBtn {
  365. font-size: 14px;
  366. width: 50px;
  367. position: absolute;
  368. right: 20px;
  369. top: 56px;
  370. border-radius: 8px;
  371. background-color: #2484F2;
  372. color: #fff;
  373. }
  374. }
  375. .image-groups {
  376. flex: 1;
  377. padding-top:15px;
  378. }
  379. .image-group {
  380. margin-bottom: 24px;
  381. }
  382. .group-header {
  383. font-size: 16px;
  384. font-weight: 500;
  385. color: #333;
  386. margin-bottom: 12px;
  387. padding-left: 8px;
  388. }
  389. .image-grid {
  390. display: grid;
  391. //grid-template-columns: repeat(2, 1fr);
  392. //gap: 16px;
  393. }
  394. .image-item {
  395. position: relative;
  396. cursor: pointer;
  397. margin-bottom: 20px;
  398. .result-image-box {
  399. display: flex;
  400. align-items: center;
  401. .house-image {
  402. height: 90px;
  403. width: 90px;
  404. margin-right: 16px;
  405. }
  406. .WallFinishing {
  407. flex: calc(100% - 100px);
  408. display: -webkit-box;
  409. -webkit-line-clamp: 3;
  410. -webkit-box-orient: vertical;
  411. overflow: hidden;
  412. text-overflow: ellipsis;
  413. }
  414. }
  415. }
  416. .loading-state {
  417. width: 90px;
  418. height: 90px;
  419. display: flex;
  420. flex-direction: column;
  421. align-items: center;
  422. justify-content: center;
  423. color: #666;
  424. text-align: center;
  425. background-image: url('../../assets/AIDesign/bg.png');
  426. background-size: 100% 100%;
  427. border-radius: 5px;
  428. }
  429. .loading-text {
  430. margin-top: 10px;
  431. font-size: 14px;
  432. color: white;
  433. line-height: 15px;
  434. font-size: 11px;
  435. }
  436. .select-indicator {
  437. position: absolute;
  438. top: 5px;
  439. left: 5px;
  440. }
  441. .select-indicator-circle {
  442. width: 24px;
  443. height: 24px;
  444. border-radius: 50%;
  445. background-color: #B2C4DB;
  446. display: flex;
  447. align-items: center;
  448. justify-content: center;
  449. }
  450. .icon-select {
  451. width: 10px;
  452. height: 10px;
  453. border-radius: 50%;
  454. display: flex;
  455. align-items: center;
  456. justify-content: center;
  457. }
  458. .icon-select.selected {
  459. background-color: #FFFFFF;
  460. }
  461. .delete-button {
  462. position: fixed;
  463. bottom: 20px;
  464. left: 5%;
  465. width: 90%;
  466. padding: 12px 16px;
  467. background-color: #E96337;
  468. border: none;
  469. border-radius: 12px;
  470. color: white;
  471. font-weight: 600;
  472. cursor: pointer;
  473. transition: all 0.2s ease;
  474. text-align: center;
  475. font-size: 16px;
  476. font-weight: normal;
  477. }
  478. .delete-button:active {
  479. transform: scale(0.98);
  480. }
  481. .header {
  482. border-bottom: 1px solid #f8f8f8;
  483. .van-nav-bar__title {
  484. font-size: 20px;
  485. color: #333;
  486. }
  487. .van-icon {
  488. font-size: 20px;
  489. color: #333 !important;
  490. }
  491. }
  492. </style>