diagnoseHistory.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
  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. }
  125. // 处理滚动事件
  126. private handleScroll() {
  127. // 防止重复触发加载
  128. if (this.isProcessing || !this.hasMore || this.isLoadingMore) return;
  129. // 获取滚动相关参数
  130. const scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
  131. const clientHeight = document.documentElement.clientHeight || window.innerHeight;
  132. const scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
  133. // 当滚动到距离底部200px时触发加载
  134. if (scrollTop + clientHeight >= scrollHeight - 200) {
  135. this.loadMoreData();
  136. }
  137. }
  138. // 加载更多数据
  139. private loadMoreData() {
  140. if (this.pagination.page >= this.pagination.total) {
  141. this.hasMore = false;
  142. return;
  143. }
  144. this.isProcessing = true;
  145. this.isLoadingMore = true;
  146. this.pagination.page += 1;
  147. // 延迟执行,优化用户体验
  148. setTimeout(() => {
  149. this.getDataInfioList(true);
  150. }, 300);
  151. }
  152. // 按日期分组逻辑
  153. get groupedImages(): Record<string, ImageItem[]> {
  154. if (!this.images || this.images.length === 0) return {};
  155. const groups: Record<string, ImageItem[]> = {};
  156. this.images.forEach(image => {
  157. const date = image.CreateDate.split('T')[0];
  158. if (!groups[date]) {
  159. groups[date] = [];
  160. }
  161. groups[date].push(image);
  162. });
  163. // 按日期倒序排列
  164. const sortedGroups: Record<string, ImageItem[]> = {};
  165. Object.keys(groups)
  166. .sort((a, b) => new Date(b).getTime() - new Date(a).getTime())
  167. .forEach(key => {
  168. sortedGroups[key] = groups[key];
  169. });
  170. return sortedGroups;
  171. }
  172. // 获取数据列表 - 支持加载更多
  173. getDataInfioList(isLoadMore = false): void {
  174. // 0外墙 1内墙
  175. const Entrytype = this.wallType === 'inside' ? 1 : 0;
  176. const queryJson = {
  177. Entrytype
  178. };
  179. const formData = new FormData();
  180. formData.append("pagination", JSON.stringify(this.pagination));
  181. formData.append("queryJson", JSON.stringify(queryJson));
  182. diagGetDesignList(formData).then((res) => {
  183. if (res.StatusCode == 200) {
  184. // 加载更多时合并数据,否则替换数据
  185. this.images = isLoadMore ? [...this.images, ...res.Data.rows] : res.Data.rows;
  186. // 更新分页信息
  187. this.pagination.records = res.Data.records;
  188. this.pagination.total = res.Data.total;
  189. // 判断是否还有更多数据
  190. this.hasMore = this.pagination.page < this.pagination.total;
  191. } else {
  192. this.$toast.fail(res.Info);
  193. }
  194. // 重置加载状态
  195. this.isLoding = false;
  196. this.isLoadingMore = false;
  197. this.isProcessing = false;
  198. }).catch(() => {
  199. // 错误处理
  200. this.isLoding = false;
  201. this.isLoadingMore = false;
  202. this.isProcessing = false;
  203. this.$toast.fail('加载失败,请重试');
  204. });
  205. }
  206. // 其他原有方法保持不变...
  207. returnPage() {
  208. this.$router.push({ path: '/AIDesign/diagnose', query: { wallType: this.wallType } });
  209. }
  210. toHome() {
  211. toLBHome()
  212. }
  213. // 去结果页面
  214. toResultPage(image) {
  215. if (this.isLongPressing && image.StateCode == 2) {
  216. this.toggleSelect(this.getImageIndex(image));
  217. } else {
  218. this.$router.push({
  219. path: '/AIDesign/diagnoseResult',
  220. query: {
  221. F_id: image.F_ID,
  222. wallType: this.wallType
  223. }
  224. });
  225. }
  226. }
  227. getImageIndex(image: ImageItem): number {
  228. return this.images.findIndex((item: ImageItem) => item.F_ID === image.F_ID);
  229. }
  230. formatDate(dateString: string): string {
  231. const date = new Date(dateString);
  232. const today = new Date();
  233. const yesterday = new Date(today);
  234. yesterday.setDate(yesterday.getDate() - 1);
  235. if (date.toDateString() === today.toDateString()) {
  236. return '今天';
  237. }
  238. if (date.toDateString() === yesterday.toDateString()) {
  239. return '昨天';
  240. }
  241. if (date.getFullYear() === today.getFullYear()) {
  242. return `${date.getMonth() + 1}月${date.getDate()}日`;
  243. }
  244. return `${date.getFullYear()}年${date.getMonth() + 1}月${date.getDate()}日`;
  245. }
  246. selectImage() {
  247. this.selectedImages = [];
  248. this.isLongPressing = !this.isLongPressing;
  249. }
  250. handleTouchStart(index: number, event: Event): void {
  251. event.preventDefault();
  252. this.pressTimer = window.setTimeout(() => {
  253. this.isLongPressing = true;
  254. this.toggleSelect(index);
  255. }, 500);
  256. }
  257. handleTouchEnd(index: number): void {
  258. if (this.pressTimer) {
  259. clearTimeout(this.pressTimer);
  260. this.pressTimer = null;
  261. }
  262. if (!this.isLongPressing) {
  263. this.toggleSelect(index);
  264. } else {
  265. const F_ID = this.images[index].F_ID;
  266. this.$router.push({
  267. path: '/AIDesign/diagnoseResult',
  268. query: {
  269. F_id: F_ID,
  270. wallType: this.wallType
  271. }
  272. });
  273. }
  274. this.isLongPressing = false;
  275. }
  276. toggleSelect(index: number): void {
  277. const idx = this.selectedImages.indexOf(index);
  278. if (idx === -1) {
  279. this.selectedImages.push(index);
  280. } else {
  281. this.selectedImages.splice(idx, 1);
  282. }
  283. }
  284. saveImage(): void {
  285. this.selectedImages.forEach((index) => {
  286. if (index < 0 || index >= this.images.length) return;
  287. const image = this.images[index];
  288. if (!image) return;
  289. const imageUrl = image.BaseUrl + image.F_ResultFilePath;
  290. this.downloadImage(imageUrl, `ai-design-${image.F_ID}.png`);
  291. });
  292. }
  293. downloadImage(imageUrl: string, filename: string): void {
  294. fetch(imageUrl).then(response => response.blob()).then(blob => {
  295. const blobUrl = URL.createObjectURL(blob);
  296. const link = document.createElement('a');
  297. link.href = blobUrl;
  298. link.download = `ai-design-${new Date().getTime()}.png`;
  299. link.style.display = 'none';
  300. document.body.appendChild(link);
  301. link.click();
  302. document.body.removeChild(link);
  303. URL.revokeObjectURL(blobUrl);
  304. }).catch(error => {
  305. console.error('图片下载失败:', error);
  306. this.$toast.fail('图片下载失败');
  307. });
  308. }
  309. deleteImage(): void {
  310. const F_ids = this.selectedImages.map(index => this.images[index].F_ID);
  311. const formData = new FormData();
  312. formData.append("F_ids", JSON.stringify(F_ids));
  313. diagDeleteEntity(formData).then((res) => {
  314. if (res.StatusCode == 200) {
  315. this.$toast.success("删除成功");
  316. // 重新加载第一页数据
  317. this.pagination.page = 1;
  318. this.getDataInfioList();
  319. this.selectedImages = [];
  320. } else {
  321. this.$toast.fail(res.Info);
  322. }
  323. });
  324. }
  325. beforeDestroy() {
  326. if (this.pressTimer) {
  327. clearTimeout(this.pressTimer);
  328. }
  329. window.removeEventListener('scroll', this.handleScroll);
  330. }
  331. }
  332. </script>
  333. <style scoped lang="scss">
  334. /* 原有样式保持不变,增加以下样式 */
  335. // 加载更多样式
  336. .loading-more {
  337. display: flex;
  338. align-items: center;
  339. justify-content: center;
  340. padding: 20px 0;
  341. color: #666;
  342. .loading-text {
  343. margin-left: 8px;
  344. font-size: 14px;
  345. }
  346. }
  347. // 加载完成提示
  348. .load-all {
  349. text-align: center;
  350. padding: 15px 0;
  351. color: #999;
  352. font-size: 14px;
  353. }
  354. /* 其他原有样式... */
  355. .resout-container {
  356. background-color: #f8f9fa;
  357. min-height: 100vh;
  358. flex-direction: column;
  359. }
  360. .container {
  361. padding: 20px;
  362. position: relative;
  363. .selecBtn {
  364. font-size: 14px;
  365. width: 50px;
  366. position: absolute;
  367. right: 20px;
  368. top: 14px;
  369. border-radius: 8px;
  370. background-color: #2484F2;
  371. color: #fff;
  372. }
  373. }
  374. .image-groups {
  375. flex: 1;
  376. }
  377. .image-group {
  378. margin-bottom: 24px;
  379. }
  380. .group-header {
  381. font-size: 16px;
  382. font-weight: 500;
  383. color: #333;
  384. margin-bottom: 12px;
  385. padding-left: 8px;
  386. }
  387. .image-grid {
  388. display: grid;
  389. //grid-template-columns: repeat(2, 1fr);
  390. //gap: 16px;
  391. }
  392. .image-item {
  393. position: relative;
  394. cursor: pointer;
  395. margin-bottom: 20px;
  396. .result-image-box {
  397. display: flex;
  398. align-items: center;
  399. .house-image {
  400. height: 90px;
  401. width: 90px;
  402. margin-right: 16px;
  403. }
  404. .WallFinishing {
  405. flex: calc(100% - 100px);
  406. display: -webkit-box;
  407. -webkit-line-clamp: 3;
  408. -webkit-box-orient: vertical;
  409. overflow: hidden;
  410. text-overflow: ellipsis;
  411. }
  412. }
  413. }
  414. .loading-state {
  415. width: 90px;
  416. height: 90px;
  417. display: flex;
  418. flex-direction: column;
  419. align-items: center;
  420. justify-content: center;
  421. color: #666;
  422. text-align: center;
  423. background-image: url('../../assets/AIDesign/bg.png');
  424. background-size: 100% 100%;
  425. border-radius: 5px;
  426. }
  427. .loading-text {
  428. margin-top: 10px;
  429. font-size: 14px;
  430. color: white;
  431. line-height: 15px;
  432. font-size: 11px;
  433. }
  434. .select-indicator {
  435. position: absolute;
  436. top: 5px;
  437. left: 5px;
  438. }
  439. .select-indicator-circle {
  440. width: 24px;
  441. height: 24px;
  442. border-radius: 50%;
  443. background-color: #B2C4DB;
  444. display: flex;
  445. align-items: center;
  446. justify-content: center;
  447. }
  448. .icon-select {
  449. width: 10px;
  450. height: 10px;
  451. border-radius: 50%;
  452. display: flex;
  453. align-items: center;
  454. justify-content: center;
  455. }
  456. .icon-select.selected {
  457. background-color: #FFFFFF;
  458. }
  459. .delete-button {
  460. position: fixed;
  461. bottom: 20px;
  462. left: 5%;
  463. width: 90%;
  464. padding: 12px 16px;
  465. background-color: #E96337;
  466. border: none;
  467. border-radius: 12px;
  468. color: white;
  469. font-weight: 600;
  470. cursor: pointer;
  471. transition: all 0.2s ease;
  472. text-align: center;
  473. font-size: 16px;
  474. font-weight: normal;
  475. }
  476. .delete-button:active {
  477. transform: scale(0.98);
  478. }
  479. .header {
  480. border-bottom: 1px solid #f8f8f8;
  481. .van-nav-bar__title {
  482. font-size: 20px;
  483. color: #333;
  484. }
  485. .van-icon {
  486. font-size: 20px;
  487. color: #333 !important;
  488. }
  489. }
  490. </style>