result.vue 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990
  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. <!-- 历史生图 -->
  12. <div class="history-section padded-lr-20" v-if="wallType === 'outside' || wallType === 'inside'">
  13. <div class="history-header" @click="viewHistory">
  14. <van-icon name="clock-o" color="#EC8868" />
  15. <span>历史生图</span>
  16. <span v-if="!readState" class="badge-dot"></span>
  17. </div>
  18. </div>
  19. <!-- 房屋效果图 -->
  20. <div class="image-container">
  21. <div v-if="imageUrl" class="comparisonWrapper">
  22. <div class="imgbox-t">
  23. <div>
  24. <p class="tit">原图</p>
  25. </div>
  26. <div>
  27. <p class="tit">AI效果图</p>
  28. </div>
  29. </div>
  30. <div class="imgbox-b">
  31. <div class="image-wrapper" @click="imgClick(0)">
  32. <img :src="UserFilePathUrl" alt="房屋效果图" class="house-image" @error="handleImageError" />
  33. <div v-on="{
  34. 'click.stop': imgHasErr ? () => reloadImage('userFile') : () => { }
  35. }"></div>
  36. </div>
  37. <div class="image-wrapper" @click="imgClick(1)">
  38. <img :src="imageUrlSmall" alt="房屋效果图" class="house-image" @error="handleImageError" />
  39. <div v-on="{
  40. 'click.stop': imgHasErr ? () => reloadImage('imageSmall') : () => { }
  41. }"></div>
  42. </div>
  43. </div>
  44. </div>
  45. <div v-else class="loading-state">
  46. <img v-if="StateCode != 3 && StateCode != 4" src="@/assets/AIDesign/loding.gif" style="width: 100px;">
  47. <p class="loading-text">{{ type || StateCode ? (StateCode == 3 ? '生成失败' : (StateCode == 4 ?
  48. '生成失败,\n' + StateInfo :
  49. '正在为您加速生成中,先喝杯茶吧~\n退出本页不影响生成')) : '加载中...' }}</p>
  50. </div>
  51. <div v-if="imageUrl" class="feedback" @click="toFeedback">
  52. <img src="@/assets/AIDesign/feedback_icon.png" class="feedback-icon" />
  53. <span v-if="F_Feedback == null">一键反馈</span>
  54. <span v-if="F_Feedback != null">查看反馈</span>
  55. </div>
  56. </div>
  57. <!-- 功能按钮组 -->
  58. <!-- 外墙 -->
  59. <div class="button-group-outside" v-if="wallType === 'outside' || wallType === 'inside'">
  60. <button class="action-button-big flex-center" @click="handleAIfun"
  61. v-if="agentFrom === 'stoneLikePaint' && allRes && allRes.F_DesignStyle != 'CHANGE_COLOR' && StateCode == 2 && wallType === 'outside'">
  62. <img src="@/assets/AIDesign/file-excel-line.png" class="icon" />
  63. <div>
  64. <div class="title">确认AI设计</div>
  65. <div class="text">生成解决方案</div>
  66. </div>
  67. </button>
  68. <div class="flex-between">
  69. <button class="action-button-middle flex-center" :disabled="regenerateDisable"
  70. :class="regenerateDisable == true ? 'save-button-disabled' : ''" @click="regenerate">
  71. <img src="@/assets/AIDesign/resetIcon.png" class="icon" />
  72. <div>
  73. <div class="title">重新生成</div>
  74. <div class="text">再来一次也不错</div>
  75. </div>
  76. </button>
  77. <!-- :class="projectDisableFlag == true ? 'save-button-disabled' : ''" :disabled="projectDisableFlag"-->
  78. <button class="action-button-middle flex-center" @click="manualDesign"
  79. v-if="showArtificial && StateCode == 2 && !projectDisableFlag && wallType === 'outside'">
  80. <img src="@/assets/AIDesign/bsIcon.png" class="iconbig" />
  81. <div>
  82. <div class="title">转人工设计
  83. </div>
  84. <div class="text">别墅之星小程序</div>
  85. </div>
  86. </button>
  87. </div>
  88. </div>
  89. <!-- 内墙 -->
  90. <!--<div class="button-group" v-if="wallType === 'inside'">-->
  91. <!--<button class="action-button" :disabled="regenerateDisable"-->
  92. <!--:class="regenerateDisable == true ? 'save-button-disabled' : ''" @click="regenerate">-->
  93. <!--<van-icon class="icon" name="replay" />-->
  94. <!--<span class="text">重新生成</span>-->
  95. <!--</button>-->
  96. <!--<button class="action-button" @click="viewHistory">-->
  97. <!--<van-icon class="icon" name="clock-o" />-->
  98. <!--<span class="text">历史生图</span>-->
  99. <!--<span v-if="!readState" class="badge-dot"></span>-->
  100. <!--</button>-->
  101. <!--</div>-->
  102. </div>
  103. <van-image-preview
  104. v-model="showImagePreview"
  105. :images="imagePreviewArray"
  106. :loop="false"
  107. :start-position="imagePreviewIndex"
  108. @change="imagePreviewOnChange">
  109. <template v-slot:index>
  110. <div class="image-preview-header">
  111. <div>{{imagePreviewIndex+1}}/{{imagePreviewArray.length}}</div>
  112. <div>{{imagePreviewIndex == 0 ? '原图' : '效果图'}}</div>
  113. </div>
  114. </template>
  115. </van-image-preview>
  116. </div>
  117. </template>
  118. <script lang="ts">
  119. import { ImagePreview, Dialog } from 'vant';
  120. import { Component, Vue } from "vue-property-decorator";
  121. import { GetEntity, GetReadState, UpdateReadState, insideGetEntity, insideGetReadState, insideUpdateReadState, GetProjectlist, GetDictList, AddTrackEvent } from "@/api/indexAI";
  122. import { getWecomType, toLBHome, toXiaoChengxu, getWxconfig, getWecomTypeName, checkAndSaveUserWecomType } from '@/utils/index';
  123. import axios from "axios";
  124. declare let wx: any;
  125. @Component
  126. export default class extends Vue {
  127. private readState = true;
  128. // 数据属性
  129. // private imageUrl = require('@/assets/AIDesign/house-image.jpg'); // 替换为实际图片路径
  130. private imageUrl = ''; // 替换为实际图片路径
  131. private imageUrlSmall = '';
  132. private UserFilePathUrl = '';//用户原图
  133. private pollingTimer: number | null = null; // 轮询定时器引用
  134. private timer = null;
  135. private StateCode = null;
  136. private projectId = null;
  137. private regenerateDisable = true;
  138. private StateInfo = '';
  139. private type = '';
  140. private wallType = '';
  141. private F_OutsideType = null;
  142. private F_Feedback = null;
  143. private defaultImg = require('@/assets/AIDesign/imgErrIcon.jpg'); // 替换为实际图片路径
  144. private imgHasErr = false;
  145. private showImagePreview = false;
  146. private imagePreviewArray = [];
  147. private imagePreviewIndex = 0
  148. // 处理内外墙api
  149. private GetEntityToApi = {
  150. outside: GetEntity,
  151. inside: insideGetEntity
  152. };
  153. private GetReadStateToApi = {
  154. outside: GetReadState,
  155. inside: insideGetReadState
  156. };
  157. private UpdateReadStateToApi = {
  158. outside: UpdateReadState,
  159. inside: insideUpdateReadState
  160. };
  161. private designPageApi = {
  162. outside: '/AIDesign/design',
  163. inside: '/AIDesign/insideDesign'
  164. };
  165. private designTimer = {
  166. outside: 30000,
  167. inside: 20000
  168. };
  169. private showArtificial = false;
  170. private agentFrom = window.localStorage.getItem('agentFromAI');
  171. private outsideDesignCount = 0;//项目下设计已用数量-接口获取
  172. private isNeedProjectFlag = true;//是否需要项目-接口获取
  173. private projectDisableFlag = true;//转人工不可点击
  174. private serviceCodeArray = [];
  175. private allRes = null;
  176. created() {
  177. getWxconfig();
  178. if (window.localStorage.getItem('agentFromAI') === 'stoneLikePaint') {
  179. this.getServiceCode();
  180. }
  181. checkAndSaveUserWecomType();
  182. }
  183. activated() {
  184. this.initialize();
  185. this.wallType = this.$route.query.wallType || 'outside';
  186. const userInfo: any = JSON.parse(window.localStorage.getItem("userInfoV1")!);
  187. const customerCode = userInfo && userInfo.sysUserExt ? userInfo.sysUserExt.customerCode : '';
  188. const salesLevel = userInfo && userInfo.sysUserExt ? userInfo.sysUserExt.salesLevel : '';
  189. // 外墙-服务商随身邦
  190. if (this.agentFrom === 'stoneLikePaint' && this.wallType === 'outside') {
  191. this.getIsNeedProjectFlag();
  192. this.showArtificial = true;
  193. }
  194. // 和部分经销商展示转人工
  195. // else if(customerCode && (salesLevel === 'customer_level' || salesLevel === 'reseller_level')){
  196. // this.showArtificial = true;
  197. // }
  198. // 从经销商随身邦进入的,显示转人工按钮,没有次数限制
  199. if ((this.agentFrom === 'ssb' || this.agentFrom === 'dg') && this.wallType === 'outside') {
  200. this.showArtificial = true;
  201. this.projectDisableFlag = false;
  202. }
  203. this.type = window.localStorage.getItem('type');
  204. this.GetReadStateFn();
  205. this.GetEntityDataFirst()
  206. }
  207. // 离开页面时清除定时器
  208. deactivated() {
  209. // console.log('resule页面销毁1')
  210. window.localStorage.removeItem('type');
  211. clearInterval(this.pollingTimer);
  212. clearTimeout(this.timer);
  213. this.pollingTimer = null;
  214. this.timer = null;
  215. }
  216. initialize() {
  217. clearInterval(this.pollingTimer);
  218. clearTimeout(this.timer);
  219. this.readState = true;
  220. this.imageUrl = ''; // 替换为实际图片路径
  221. this.imageUrlSmall = '';
  222. this.UserFilePathUrl = '';//用户原图
  223. this.pollingTimer = null; // 轮询定时器引用
  224. this.timer = null;
  225. this.StateCode = null;
  226. this.projectId = null;
  227. this.regenerateDisable = true;
  228. this.StateInfo = '';
  229. this.type = '';
  230. this.wallType = '';
  231. this.outsideDesignCount = 0;
  232. this.isNeedProjectFlag = true;
  233. this.projectDisableFlag = true;
  234. this.showArtificial = false;
  235. this.allRes = null;
  236. this.imgHasErr = false;
  237. }
  238. // 图片加载失败处理(私有方法)
  239. private handleImageError(e) {
  240. const imgElement = e.target as HTMLImageElement;
  241. // 降级显示默认占位图
  242. imgElement.src = this.defaultImg;
  243. imgElement.onerror = null;
  244. this.imgHasErr = true;
  245. }
  246. private reloadImage(imgType: 'userFile' | 'imageSmall') {
  247. let that = this;
  248. if (that.allRes && that.imgHasErr) {
  249. if (imgType === 'userFile') {
  250. if (that.allRes.F_UserFilePath) {
  251. that.UserFilePathUrl = that.allRes.BaseUrl + that.allRes.F_UserFilePath;
  252. that.imgHasErr = false;
  253. }
  254. } else {
  255. let high_Definition_img = that.allRes.F_ResultFilePath || that.allRes.F_ResultlargeFilePath || that.allRes.F_ResultSmallFilePath;
  256. if (high_Definition_img) {
  257. that.imageUrl = that.allRes.BaseUrl + high_Definition_img;
  258. that.imageUrlSmall = that.allRes.BaseUrl + that.allRes.F_ResultSmallFilePath;
  259. that.imgHasErr = false;
  260. }
  261. }
  262. }
  263. }
  264. // 是否关联了项目
  265. getIsNeedProjectFlag() {
  266. let that = this;
  267. const formData = new FormData();
  268. // const userInfo: any = JSON.parse(window.localStorage.getItem("userInfoV1")!);
  269. // let roleIdArray = [];
  270. // if (userInfo.roles.length > 0) {
  271. // userInfo.roles.forEach(item => {
  272. // roleIdArray.push(item.roleId);
  273. // })
  274. // }
  275. // formData.append('roleIds', roleIdArray.join(','));
  276. // formData.append('WXuserid', userInfo.loginName);
  277. formData.append('baseType', 0);//0外墙--这里只用查询外墙
  278. // const agentFrom = window.localStorage.getItem('agentFromAI');
  279. // const wecomType = getWecomType(agentFrom);
  280. // formData.append('wecomType', 5);
  281. GetDictList(formData).then(response => {
  282. if (response.StatusCode == 200) {
  283. if (that.serviceCodeArray.length == 0) {
  284. that.isNeedProjectFlag = false; //是否需要项目
  285. } else {
  286. that.isNeedProjectFlag = response.Data.isNeedProject;
  287. }
  288. }
  289. })
  290. }
  291. private getServiceCode() {
  292. let that = this;
  293. const userInfo: any = JSON.parse(window.localStorage.getItem("userInfoV1")!);
  294. let serviceCodeArray = [];
  295. // if (userInfo && userInfo.loginTypeList && userInfo.loginTypeList.length > 0) {
  296. // userInfo.loginTypeList.forEach(item => {
  297. // if (item.shopType == 'stoneLikePaint') {
  298. // item.shopList.forEach(childItem => {
  299. // serviceCodeArray.push(childItem.shop_code);
  300. // })
  301. // }
  302. // })
  303. // }
  304. if (userInfo && userInfo.ServiceCode) {
  305. serviceCodeArray = userInfo.ServiceCode.split(',').map(item => item.trim()).filter(item => item !== '');
  306. }
  307. that.serviceCodeArray = serviceCodeArray;
  308. }
  309. GetReadStateFn() {
  310. const formData = new FormData();
  311. // const userInfo: any = JSON.parse(window.localStorage.getItem("userInfoV1")!);
  312. // formData.append('WXuserid', userInfo.loginName);
  313. if (this.wallType === 'outside') {
  314. const outsideType_val = this.F_OutsideType || 0;
  315. formData.append('outsideType', outsideType_val);
  316. }
  317. this.GetReadStateToApi[this.wallType](formData).then(response => {
  318. if (response.StatusCode == 200) {
  319. this.readState = response.Data.readState;
  320. }
  321. });
  322. }
  323. UpdateReadStateFn() {
  324. const formData = new FormData();
  325. // const userInfo: any = JSON.parse(window.localStorage.getItem("userInfoV1")!);
  326. // formData.append('WXuserid', userInfo.loginName);
  327. const outsideType_val = this.F_OutsideType || 0;
  328. formData.append('outsideType', outsideType_val);
  329. this.UpdateReadStateToApi[this.wallType](formData).then(response => { });
  330. }
  331. returnPage() {
  332. this.$router.push({ path: this.designPageApi[this.wallType] });
  333. // this.$router.back();
  334. }
  335. toHome() {
  336. toLBHome()
  337. }
  338. imgClick(index) {
  339. this.showImagePreview = true;
  340. this.imagePreviewIndex = index;
  341. // ImagePreview([this.UserFilePathUrl,this.imageUrl],index);
  342. }
  343. imagePreviewOnChange(index){
  344. this.imagePreviewIndex = index;
  345. }
  346. private startPolling(): void {
  347. // 立即执行一次检查
  348. this.GetEntityData();
  349. // 设置定时器每秒执行一次检查
  350. this.pollingTimer = window.setInterval(() => {
  351. if (!this.imageUrl) {
  352. if (this.StateCode == 3 || this.StateCode == 4) {
  353. clearInterval(this.pollingTimer);
  354. this.pollingTimer = null;
  355. } else {
  356. this.GetEntityData();
  357. }
  358. } else {
  359. // 当 imageUrl 有值时,清除定时器
  360. if (this.pollingTimer) {
  361. clearInterval(this.pollingTimer);
  362. this.pollingTimer = null;
  363. }
  364. }
  365. }, 3000);
  366. }
  367. //进入页面首次调用获取状态
  368. GetEntityDataFirst() {
  369. let that = this;
  370. const F_ID = this.$route.query.F_id || "";
  371. const formData = new FormData();
  372. formData.append('F_id', F_ID);
  373. this.GetEntityToApi[this.wallType](formData).then(response => {
  374. // console.log(response);
  375. if (response.StatusCode == 200) {
  376. if (response.Data == null) {
  377. that.regenerateDisable = true;
  378. if (that.timer) {
  379. clearTimeout(that.timer);
  380. that.timer = null;
  381. }
  382. that.timer = setTimeout(() => {
  383. that.startPolling();
  384. }, that.designTimer[that.wallType]);
  385. } else {
  386. this.allRes = response.Data;
  387. this.StateCode = response.Data.StateCode;
  388. this.StateInfo = response.Data.Description;
  389. this.projectId = response.Data.ProjectID;
  390. this.F_OutsideType = response.Data.F_OutsideType;
  391. this.F_Feedback = response.Data.F_Feedback;
  392. let DesignCount = response.Data.DesignCount || 0;
  393. // 外墙-服务商随身邦
  394. if (this.agentFrom === 'stoneLikePaint' && this.wallType === 'outside') {
  395. if (that.isNeedProjectFlag) {
  396. if (DesignCount >= 3) {
  397. that.projectDisableFlag = false;//有项目判断>=3出现
  398. }
  399. } else {
  400. that.projectDisableFlag = false;//没有项目就可点击
  401. }
  402. }
  403. if (response.Data.F_ResultFilePath || response.Data.F_ResultlargeFilePath) {
  404. const high_Definition_img = response.Data.F_ResultFilePath || response.Data.F_ResultlargeFilePath || response.Data.F_ResultSmallFilePath;
  405. that.imageUrl = response.Data.BaseUrl + high_Definition_img;
  406. that.imageUrlSmall = response.Data.BaseUrl + response.Data.F_ResultSmallFilePath;
  407. that.UserFilePathUrl = response.Data.BaseUrl + response.Data.F_UserFilePath;
  408. that.imagePreviewArray = [that.UserFilePathUrl,that.imageUrl];
  409. }
  410. if (response.Data.StateCode == 1) {
  411. let createTime = new Date(response.Data.CreateDate);
  412. let currentTime = new Date().getTime();
  413. if (currentTime - createTime > that.designTimer[that.wallType]) {
  414. that.startPolling();
  415. } else {
  416. if (that.timer) {
  417. clearTimeout(that.timer);
  418. that.timer = null;
  419. }
  420. that.timer = setTimeout(() => {
  421. that.startPolling();
  422. }, currentTime - createTime);
  423. }
  424. } else {
  425. if (response.Data.StateCode == 2 || response.Data.StateCode == 3 || response.Data.StateCode == 4) {
  426. that.regenerateDisable = false;
  427. } else {
  428. that.regenerateDisable = true;
  429. }
  430. }
  431. }
  432. } else {
  433. this.$toast.fail(response.Info);
  434. }
  435. })
  436. }
  437. GetEntityData() {
  438. let that = this;
  439. const F_ID = this.$route.query.F_id || "";
  440. const formData = new FormData();
  441. formData.append('F_id', F_ID);
  442. this.GetEntityToApi[this.wallType](formData).then(response => {
  443. // console.log(response);
  444. if (response.StatusCode == 200) {
  445. if (response.Data) {
  446. this.allRes = response.Data;
  447. this.StateCode = response.Data.StateCode;
  448. this.StateInfo = response.Data.Description;
  449. this.F_OutsideType = response.Data.F_OutsideType;
  450. this.F_Feedback = response.Data.F_Feedback;
  451. this.projectId = response.Data.ProjectID;
  452. let DesignCount = response.Data.DesignCount || 0;
  453. // 外墙-服务商随身邦
  454. if (this.agentFrom === 'stoneLikePaint' && this.wallType === 'outside') {
  455. if (that.isNeedProjectFlag) {
  456. if (DesignCount >= 3) {
  457. that.projectDisableFlag = false;//有项目判断>=3出现
  458. }
  459. } else {
  460. that.projectDisableFlag = false;//没有项目就可点击
  461. }
  462. }
  463. if (response.Data.F_ResultFilePath || response.Data.F_ResultlargeFilePath) {
  464. const high_Definition_img = response.Data.F_ResultFilePath || response.Data.F_ResultlargeFilePath || response.Data.F_ResultlargeFilePath;
  465. that.imageUrl = response.Data.BaseUrl + high_Definition_img;
  466. that.imageUrlSmall = response.Data.BaseUrl + response.Data.F_ResultSmallFilePath;
  467. that.UserFilePathUrl = response.Data.BaseUrl + response.Data.F_UserFilePath;
  468. that.imagePreviewArray = [that.UserFilePathUrl,that.imageUrl];
  469. }
  470. if (response.Data.StateCode == 2 || response.Data.StateCode == 3 || response.Data.StateCode == 4) {
  471. that.regenerateDisable = false;
  472. } else {
  473. that.regenerateDisable = true;
  474. }
  475. } else {
  476. that.regenerateDisable = true;
  477. }
  478. } else {
  479. this.$toast.fail(response.Info);
  480. }
  481. })
  482. }
  483. // 下载图片并保存到相册
  484. downloadAndSaveImage() {
  485. const that = this;
  486. const imageUrl = this.imageUrl;
  487. wx.ready(function () {
  488. // 1. 下载OSS图片到本地临时路径
  489. wx.downloadFile({
  490. url: imageUrl,
  491. success: (downloadRes) => {
  492. // 检查下载是否成功(临时文件路径存在)
  493. if (downloadRes.statusCode === 200 && downloadRes.tempFilePath) {
  494. // 2. 保存图片到相册
  495. wx.saveImageToPhotosAlbum({
  496. filePath: downloadRes.tempFilePath,
  497. success: () => {
  498. // 提示用户
  499. that.$toast.success('图片已成功保存到相册');
  500. },
  501. fail: (saveErr) => {
  502. console.error('保存图片失败', saveErr);
  503. that.$toast.fail('保存失败,请重试');
  504. }
  505. });
  506. } else {
  507. console.error('图片下载失败', downloadRes);
  508. that.$toast.fail('图片下载失败,请检查URL');
  509. }
  510. },
  511. fail: (downloadErr) => {
  512. console.error('下载请求失败', downloadErr);
  513. that.$toast.fail('下载图片失败,请重试');
  514. }
  515. });
  516. });
  517. }
  518. // 方法
  519. private saveImage(): void {
  520. if (!this.imageUrl) {
  521. this.$toast.fail('图片尚未生成完成');
  522. return;
  523. }
  524. // // 创建一个临时的下载链接
  525. // const link = document.createElement('a');
  526. // link.href = this.imageUrl;
  527. // link.download = `ai-design-${new Date().getTime()}.png`;
  528. // link.style.display = 'none';
  529. // // 将链接添加到页面并触发点击事件
  530. // document.body.appendChild(link);
  531. // link.click();
  532. // // 清理DOM元素
  533. // document.body.removeChild(link);
  534. fetch(this.imageUrl).then(response => response.blob()).then(blob => {
  535. const blobUrl = URL.createObjectURL(blob);
  536. // 创建一个临时的下载链接
  537. const link = document.createElement('a');
  538. link.href = blobUrl;
  539. link.download = `ai-design-${new Date().getTime()}.png`;
  540. link.style.display = 'none';
  541. // 将链接添加到页面并触发点击事件
  542. document.body.appendChild(link);
  543. link.click();
  544. // 清理DOM元素
  545. document.body.removeChild(link);
  546. // 释放Blob URL
  547. URL.revokeObjectURL(blobUrl);
  548. // this.$toast.success('图片保存成功');
  549. }).catch(error => {
  550. console.error('图片下载失败:', error);
  551. this.$toast.fail('图片下载失败');
  552. });
  553. }
  554. private regenerate(): void {
  555. // console.log('重新生成');
  556. let that = this;
  557. // 实现重新生成逻辑
  558. const F_ID = this.$route.query.F_id || "";
  559. let projectId = this.allRes.WecomType == 2 && that.projectId ? that.projectId : null;
  560. this.$router.push({
  561. path: this.designPageApi[this.wallType],
  562. query: {
  563. F_id: F_ID,
  564. projectId
  565. }
  566. });
  567. }
  568. private toFeedback(){
  569. let that = this;
  570. const F_ID = this.$route.query.F_id || "";
  571. this.$router.push({ path: '/AIDesign/feedback', query: { F_id: F_ID, feedbackResult: that.F_Feedback == null ? 0 : 1, wallType: this.wallType } });
  572. }
  573. private viewHistory(): void {
  574. this.UpdateReadStateFn();
  575. // console.log('查看历史生成');
  576. // 实现查看历史生成逻辑
  577. this.$router.push({ path: '/AIDesign/history', query: { wallType: this.wallType } });
  578. }
  579. private manualDesign(): void {
  580. let that = this;
  581. const formData = new FormData();
  582. formData.append('eventname', '转人工设计');//事件名称
  583. formData.append('eventtype', 'click');//事件类型
  584. formData.append('menupath', '外墙质感/结果页/转人工设计');//完整菜单路径
  585. const eventdataObj = {
  586. wecomType: getWecomTypeName(window.localStorage.getItem('agentFromAI')),
  587. };
  588. formData.append('eventdata', JSON.stringify(eventdataObj));//事件数据
  589. AddTrackEvent(formData)//埋点
  590. // 实现转人工设计逻辑
  591. toXiaoChengxu(`${process.env.VUE_APP_BASE_DISID6}`);
  592. }
  593. private handleAIfun() {
  594. const formData = new FormData();
  595. formData.append('eventname', '确认AI设计');//事件名称
  596. formData.append('eventtype', 'click');//事件类型
  597. formData.append('menupath', '外墙质感/结果页/确认AI设计');//完整菜单路径
  598. const eventdataObj = {
  599. wecomType: getWecomTypeName(window.localStorage.getItem('agentFromAI')),
  600. };
  601. formData.append('eventdata', JSON.stringify(eventdataObj));//事件数据
  602. AddTrackEvent(formData)//埋点
  603. let allRes = JSON.stringify(this.allRes);
  604. sessionStorage.setItem("planParams", allRes);
  605. this.$router.push({ path: '/AIDesign/GeneratePlan' });
  606. }
  607. }
  608. </script>
  609. <style scoped lang="scss">
  610. .resout-container {
  611. background-color: #f8f9fa;
  612. min-height: 100vh;
  613. flex-direction: column;
  614. }
  615. .header {
  616. /*height: 50px;*/
  617. /*line-height: 50px;*/
  618. /*text-align: center;*/
  619. /*background: #fff;*/
  620. border-bottom: 1px solid #f8f8f8;
  621. .van-nav-bar__title {
  622. font-size: 20px;
  623. color: #333;
  624. }
  625. .van-icon {
  626. font-size: 20px;
  627. color: #333 !important;
  628. }
  629. }
  630. .container {
  631. padding: 0 20px;
  632. .history-section {
  633. margin: 20px 0;
  634. .history-header {
  635. display: flex;
  636. align-items: center;
  637. gap: 12px;
  638. padding: 12px 16px;
  639. background-color: #ffffff;
  640. border-radius: 18px;
  641. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
  642. border: 1px solid #e9ecef;
  643. /* margin-bottom: 10px; */
  644. }
  645. .icon-clock {
  646. font-size: 12px;
  647. margin-right: 8px;
  648. }
  649. .history-header span {
  650. font-weight: 500;
  651. color: #EC8868;
  652. }
  653. .history-header .badge-dot {
  654. width: 5px;
  655. height: 5px;
  656. background-color: #ff4d4f;
  657. border-radius: 50%;
  658. }
  659. }
  660. }
  661. .image-container {
  662. position: relative;
  663. width: 100%;
  664. border-radius: 10px;
  665. overflow: hidden;
  666. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
  667. .feedback{
  668. padding: 15px 10px 15px 10px;
  669. font-size: 16px;
  670. color: rgba(22, 93, 255, 1);
  671. .feedback-icon{
  672. width: 20px;
  673. margin-right: 5px;
  674. margin-bottom: -5px;
  675. }
  676. }
  677. .comparisonWrapper {
  678. display: flex;
  679. flex-direction: column;
  680. padding: 35px 6px;
  681. .imgbox-t {
  682. display: flex;
  683. justify-content: space-between;
  684. align-items: center;
  685. >div {
  686. .tit {
  687. width: fit-content;
  688. padding: 0 5px;
  689. height: 17px;
  690. line-height: 17px;
  691. border-radius: 4px;
  692. font-size: 10px;
  693. }
  694. }
  695. >div:first-child {
  696. width: 36%;
  697. .tit {
  698. background: rgba(204, 204, 204, 1);
  699. }
  700. }
  701. >div:last-child {
  702. width: 58%;
  703. .tit {
  704. color: #fff;
  705. background: #FF8D1A;
  706. }
  707. }
  708. }
  709. .imgbox-b {
  710. display: flex;
  711. justify-content: space-between;
  712. align-items: center;
  713. >div:first-child {
  714. width: 36%;
  715. }
  716. >div:last-child {
  717. width: 58%;
  718. }
  719. .image-wrapper {
  720. position: relative;
  721. width: 100%;
  722. margin: 5px 0;
  723. display: flex;
  724. align-items: center;
  725. justify-content: flex-start;
  726. .house-image {
  727. border-radius: 10px;
  728. display: block;
  729. width: 100%;
  730. height: auto;
  731. }
  732. .house-image+div {
  733. position: absolute;
  734. top: 0;
  735. left: 0;
  736. width: 100%;
  737. height: 100%;
  738. background: transparent;
  739. z-index: 2;
  740. }
  741. }
  742. }
  743. }
  744. }
  745. .loading-state {
  746. position: relative;
  747. width: 100%;
  748. height: 300px;
  749. display: flex;
  750. flex-direction: column;
  751. align-items: center;
  752. justify-content: center;
  753. color: #666;
  754. text-align: center;
  755. // background-color: rgba(0, 0, 0, 0.7);
  756. background-image: url('../../assets/AIDesign/bg.png');
  757. background-size: 100% 100%;
  758. /* 添加半透明黑色背景 */
  759. // border-radius: 16px;
  760. /* 圆角边框 */
  761. }
  762. .loading-text {
  763. margin-top: 10px;
  764. font-size: 14px;
  765. color: white;
  766. white-space: pre-line
  767. /* 文字颜色改为白色以提高对比度 */
  768. }
  769. .address-info {
  770. position: absolute;
  771. bottom: 10px;
  772. left: 10px;
  773. right: 10px;
  774. background-color: rgba(0, 0, 0, 0.5);
  775. color: white;
  776. padding: 8px;
  777. border-radius: 8px;
  778. font-size: 14px;
  779. text-align: center;
  780. }
  781. .project-name {
  782. margin: 0;
  783. font-weight: bold;
  784. }
  785. .address {
  786. margin: 4px 0 0;
  787. font-size: 12px;
  788. }
  789. .save-button {
  790. width: 100%;
  791. padding: 16px;
  792. margin-top: 20px;
  793. background-color: #E96337;
  794. color: white;
  795. border: none;
  796. border-radius: 12px;
  797. font-size: 16px;
  798. font-weight: bold;
  799. cursor: pointer;
  800. transition: background-color 0.3s ease;
  801. }
  802. .save-button-disabled {
  803. background-color: #cccccc !important;
  804. cursor: not-allowed !important;
  805. color: white !important;
  806. }
  807. /* .save-button:hover {
  808. background-color: #e04a1d;
  809. } */
  810. .button-group-outside {
  811. .flex-center {
  812. display: flex;
  813. align-items: center;
  814. justify-content: center;
  815. border: none;
  816. margin-top: 10px;
  817. }
  818. .flex-between {
  819. display: flex;
  820. align-items: center;
  821. border: none;
  822. }
  823. .title {
  824. font-size: 16px;
  825. color: rgb(23, 23, 23);
  826. font-weight: 600;
  827. line-height: 16px;
  828. }
  829. .text {
  830. font-size: 12px;
  831. color: rgb(134, 144, 156);
  832. line-height: 12px;
  833. margin-top: 5px;
  834. }
  835. .icon {
  836. width: 28px;
  837. height: auto;
  838. margin-right: 10px;
  839. }
  840. .iconbig {
  841. width: 48px;
  842. height: auto;
  843. margin-right: 5px;
  844. }
  845. .action-button-big {
  846. width: 100%;
  847. height: 72px;
  848. text-align: center;
  849. border-radius: 8px;
  850. background: rgba(248, 243, 204, 1);
  851. }
  852. .action-button-middle {
  853. width: 48%;
  854. height: 72px;
  855. border-radius: 8px;
  856. background: rgba(240, 240, 240, 1);
  857. margin: 8px 1%;
  858. }
  859. .action-button-small {
  860. width: fit-content;
  861. margin: 10px auto;
  862. text-align: center;
  863. text-decoration: underline;
  864. font-size: 14px;
  865. line-height: 18px;
  866. .text {
  867. font-size: 14px;
  868. color: rgb(23, 23, 23);
  869. }
  870. }
  871. }
  872. .button-group {
  873. margin-top: 20px;
  874. .action-button {
  875. width: 100%;
  876. padding: 14px;
  877. margin-bottom: 12px;
  878. background-color: white;
  879. border: 1px solid #e0e0e0;
  880. border-radius: 12px;
  881. font-size: 15px;
  882. color: #333;
  883. cursor: pointer;
  884. display: flex;
  885. align-items: center;
  886. justify-content: flex-start;
  887. transition: background-color 0.3s ease;
  888. }
  889. .action-button:hover {
  890. background-color: #f5f5f5;
  891. }
  892. .badge-dot {
  893. margin-left: 10px;
  894. width: 5px;
  895. height: 5px;
  896. background-color: #ff4d4f;
  897. border-radius: 50%;
  898. }
  899. .icon {
  900. margin-right: 10px;
  901. font-size: 18px;
  902. }
  903. .text {
  904. font-weight: 500;
  905. }
  906. }
  907. .image-preview-header{
  908. flex-direction: column;
  909. display: flex;
  910. align-items: center;
  911. }
  912. .van-nav-bar__title {
  913. font-size: 20px;
  914. color: #333;
  915. }
  916. </style>