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. }
  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. const userInfo: any = JSON.parse(window.localStorage.getItem("userInfoV1")!);
  175. // 0外墙 1内墙
  176. const Entrytype = this.wallType === 'inside' ? 1 : 0;
  177. const queryJson = {
  178. // WXuserid: userInfo.loginName,
  179. Entrytype
  180. };
  181. const formData = new FormData();
  182. formData.append("pagination", JSON.stringify(this.pagination));
  183. formData.append("queryJson", JSON.stringify(queryJson));
  184. diagGetDesignList(formData).then((res) => {
  185. if (res.StatusCode == 200) {
  186. // 加载更多时合并数据,否则替换数据
  187. this.images = isLoadMore ? [...this.images, ...res.Data.rows] : res.Data.rows;
  188. // 更新分页信息
  189. this.pagination.records = res.Data.records;
  190. this.pagination.total = res.Data.total;
  191. // 判断是否还有更多数据
  192. this.hasMore = this.pagination.page < this.pagination.total;
  193. } else {
  194. this.$toast.fail(res.Info);
  195. }
  196. // 重置加载状态
  197. this.isLoding = false;
  198. this.isLoadingMore = false;
  199. this.isProcessing = false;
  200. }).catch(() => {
  201. // 错误处理
  202. this.isLoding = false;
  203. this.isLoadingMore = false;
  204. this.isProcessing = false;
  205. this.$toast.fail('加载失败,请重试');
  206. });
  207. }
  208. // 其他原有方法保持不变...
  209. returnPage() {
  210. this.$router.push({ path: '/AIDesign/diagnose', query: { wallType: this.wallType } });
  211. }
  212. toHome() {
  213. toLBHome()
  214. }
  215. // 去结果页面
  216. toResultPage(image) {
  217. if (this.isLongPressing && image.StateCode == 2) {
  218. this.toggleSelect(this.getImageIndex(image));
  219. } else {
  220. this.$router.push({
  221. path: '/AIDesign/diagnoseResult',
  222. query: {
  223. F_id: image.F_ID,
  224. wallType: this.wallType
  225. }
  226. });
  227. }
  228. }
  229. getImageIndex(image: ImageItem): number {
  230. return this.images.findIndex((item: ImageItem) => item.F_ID === image.F_ID);
  231. }
  232. formatDate(dateString: string): string {
  233. const date = new Date(dateString);
  234. const today = new Date();
  235. const yesterday = new Date(today);
  236. yesterday.setDate(yesterday.getDate() - 1);
  237. if (date.toDateString() === today.toDateString()) {
  238. return '今天';
  239. }
  240. if (date.toDateString() === yesterday.toDateString()) {
  241. return '昨天';
  242. }
  243. if (date.getFullYear() === today.getFullYear()) {
  244. return `${date.getMonth() + 1}月${date.getDate()}日`;
  245. }
  246. return `${date.getFullYear()}年${date.getMonth() + 1}月${date.getDate()}日`;
  247. }
  248. selectImage() {
  249. this.selectedImages = [];
  250. this.isLongPressing = !this.isLongPressing;
  251. }
  252. handleTouchStart(index: number, event: Event): void {
  253. event.preventDefault();
  254. this.pressTimer = window.setTimeout(() => {
  255. this.isLongPressing = true;
  256. this.toggleSelect(index);
  257. }, 500);
  258. }
  259. handleTouchEnd(index: number): void {
  260. if (this.pressTimer) {
  261. clearTimeout(this.pressTimer);
  262. this.pressTimer = null;
  263. }
  264. if (!this.isLongPressing) {
  265. this.toggleSelect(index);
  266. } else {
  267. const F_ID = this.images[index].F_ID;
  268. this.$router.push({
  269. path: '/AIDesign/diagnoseResult',
  270. query: {
  271. F_id: F_ID,
  272. wallType: this.wallType
  273. }
  274. });
  275. }
  276. this.isLongPressing = false;
  277. }
  278. toggleSelect(index: number): void {
  279. const idx = this.selectedImages.indexOf(index);
  280. if (idx === -1) {
  281. this.selectedImages.push(index);
  282. } else {
  283. this.selectedImages.splice(idx, 1);
  284. }
  285. }
  286. saveImage(): void {
  287. this.selectedImages.forEach((index) => {
  288. if (index < 0 || index >= this.images.length) return;
  289. const image = this.images[index];
  290. if (!image) return;
  291. const imageUrl = image.BaseUrl + image.F_ResultFilePath;
  292. this.downloadImage(imageUrl, `ai-design-${image.F_ID}.png`);
  293. });
  294. }
  295. downloadImage(imageUrl: string, filename: string): void {
  296. fetch(imageUrl).then(response => response.blob()).then(blob => {
  297. const blobUrl = URL.createObjectURL(blob);
  298. const link = document.createElement('a');
  299. link.href = blobUrl;
  300. link.download = `ai-design-${new Date().getTime()}.png`;
  301. link.style.display = 'none';
  302. document.body.appendChild(link);
  303. link.click();
  304. document.body.removeChild(link);
  305. URL.revokeObjectURL(blobUrl);
  306. }).catch(error => {
  307. console.error('图片下载失败:', error);
  308. this.$toast.fail('图片下载失败');
  309. });
  310. }
  311. deleteImage(): void {
  312. const F_ids = this.selectedImages.map(index => this.images[index].F_ID);
  313. const formData = new FormData();
  314. formData.append("F_ids", JSON.stringify(F_ids));
  315. diagDeleteEntity(formData).then((res) => {
  316. if (res.StatusCode == 200) {
  317. this.$toast.success("删除成功");
  318. // 重新加载第一页数据
  319. this.pagination.page = 1;
  320. this.getDataInfioList();
  321. this.selectedImages = [];
  322. } else {
  323. this.$toast.fail(res.Info);
  324. }
  325. });
  326. }
  327. beforeDestroy() {
  328. if (this.pressTimer) {
  329. clearTimeout(this.pressTimer);
  330. }
  331. window.removeEventListener('scroll', this.handleScroll);
  332. }
  333. }
  334. </script>
  335. <style scoped lang="scss">
  336. /* 原有样式保持不变,增加以下样式 */
  337. // 加载更多样式
  338. .loading-more {
  339. display: flex;
  340. align-items: center;
  341. justify-content: center;
  342. padding: 20px 0;
  343. color: #666;
  344. .loading-text {
  345. margin-left: 8px;
  346. font-size: 14px;
  347. }
  348. }
  349. // 加载完成提示
  350. .load-all {
  351. text-align: center;
  352. padding: 15px 0;
  353. color: #999;
  354. font-size: 14px;
  355. }
  356. /* 其他原有样式... */
  357. .resout-container {
  358. background-color: #f8f9fa;
  359. min-height: 100vh;
  360. flex-direction: column;
  361. }
  362. .container {
  363. padding: 20px;
  364. position: relative;
  365. .selecBtn {
  366. font-size: 14px;
  367. width: 50px;
  368. position: absolute;
  369. right: 20px;
  370. top: 14px;
  371. border-radius: 8px;
  372. background-color: #2484F2;
  373. color: #fff;
  374. }
  375. }
  376. .image-groups {
  377. flex: 1;
  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>