You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

312 regels
6.9 KiB

  1. <template>
  2. <view class="page">
  3. <scroll-view class="scroll" scroll-y>
  4. <view class="wrap">
  5. <view v-if="isLoadingState" class="state-card">
  6. <view v-if="detail.is_suc === 0" class="spinner"></view>
  7. <text class="state-title">{{ detail.is_suc === 0 ? '创作中,请耐心等待' : '生成失败' }}</text>
  8. <text v-if="detail.is_suc === 0 && countDown > 0" class="count">{{ countDown }} S</text>
  9. <text v-if="detail.is_suc < 0" class="error">{{ detail.content }}</text>
  10. </view>
  11. <view v-else class="image-card" @click="preview">
  12. <image class="work-img" :src="detail.picUrl" mode="widthFix" />
  13. <view v-if="detail.picList && detail.picList.length > 1" class="num">{{ detail.picList.length }}</view>
  14. </view>
  15. <view class="info-card">
  16. <view class="title">作品信息</view>
  17. <view v-if="detail.prompts" class="cell">
  18. <text class="cell-label">画面描述:</text>
  19. <text class="cell-content" user-select>{{ detail.prompts }}</text>
  20. </view>
  21. <view class="cell">
  22. <text class="cell-label">作者:</text>
  23. <text class="cell-content">{{ detail.author_name || '-' }}</text>
  24. </view>
  25. <view class="cell">
  26. <text class="cell-label">作品来源:</text>
  27. <text class="link" @click="createAgain">{{ detail.ai_type_name || aiTypeName }}</text>
  28. </view>
  29. </view>
  30. </view>
  31. </scroll-view>
  32. <view v-if="!isLoadingState" class="bottom-bar">
  33. <view class="bar-btn" @click="download">下载</view>
  34. <button class="bar-btn" open-type="share">分享</button>
  35. <view class="main-btn" @click="createAgain">再画一张</view>
  36. </view>
  37. </view>
  38. </template>
  39. <script setup>
  40. import { computed, ref } from 'vue'
  41. import { onLoad, onShareAppMessage, onUnload } from '@dcloudio/uni-app'
  42. import { toolApi } from '@/api/index.js'
  43. import { useApp } from '@/utils/useApp.js'
  44. const { toast } = useApp()
  45. const id = ref('')
  46. const imgIndex = ref(0)
  47. const from = ref('')
  48. const detail = ref({ picList: [], is_suc: 0 })
  49. const countDown = ref(0)
  50. let timer = 0
  51. let countTimer = 0
  52. const aiTypeName = computed(() => {
  53. const map = { 3: '动漫头像', 7: '动漫头像', 8: '动漫头像', 2: 'MJ绘画', 6: 'AI绘画' }
  54. return map[detail.value.ai_type] || 'AI绘画'
  55. })
  56. const isLoadingState = computed(() => [0, -1, -2].includes(Number(detail.value.is_suc)))
  57. onLoad((options) => {
  58. const parts = String(options.id || '').split('_')
  59. id.value = parts[0] || ''
  60. imgIndex.value = Number(parts[1] || 0)
  61. from.value = options.from || ''
  62. loadDetail()
  63. })
  64. onUnload(() => {
  65. clearTimeout(timer)
  66. clearInterval(countTimer)
  67. })
  68. onShareAppMessage(() => ({
  69. title: '推荐给你看一个 AI 绘画作品',
  70. path: `/pages/tool/work-detail?id=${id.value}_0`,
  71. imageUrl: detail.value.picUrl || ''
  72. }))
  73. async function loadDetail() {
  74. if (!id.value) return
  75. try {
  76. const api = from.value === 'anime' ? toolApi.getAnimeDetail : toolApi.getWorkDetail
  77. const res = await api({ chat_open_id: id.value })
  78. const data = res.data || {}
  79. const pics = buildPicList(data)
  80. detail.value = {
  81. ...data,
  82. picList: pics,
  83. picUrl: pics[imgIndex.value] || pics[0] || data.content || '',
  84. ai_type_name: data.ai_type_name || aiTypeName.value
  85. }
  86. if (Number(detail.value.is_suc) === 0) waitNext()
  87. } catch (e) {
  88. toast(e.msg || '作品详情加载失败')
  89. }
  90. }
  91. function buildPicList(data) {
  92. if (Array.isArray(data.picList) && data.picList.length) return data.picList
  93. if (data.imgs) return String(data.imgs).split(',').filter(Boolean)
  94. if (data.content && String(data.content).includes(',')) return String(data.content).split(',').filter(Boolean)
  95. return data.content ? [data.content] : []
  96. }
  97. function waitNext() {
  98. clearTimeout(timer)
  99. clearInterval(countTimer)
  100. countDown.value = 3
  101. countTimer = setInterval(() => {
  102. countDown.value -= 1
  103. if (countDown.value <= 0) clearInterval(countTimer)
  104. }, 1000)
  105. timer = setTimeout(loadDetail, 3000)
  106. }
  107. function preview() {
  108. const urls = detail.value.picList || []
  109. if (!urls.length) return
  110. uni.previewImage({ urls, current: detail.value.picUrl })
  111. }
  112. function download() {
  113. if (!detail.value.picUrl) return
  114. uni.previewImage({ urls: [detail.value.picUrl] })
  115. }
  116. function createAgain() {
  117. const delta = getPageStackDelta('pages/tool/anime-avatar')
  118. if (delta > 0) {
  119. if (id.value) uni.setStorageSync('anime_copy_id', id.value)
  120. uni.navigateBack({ delta })
  121. return
  122. }
  123. uni.navigateTo({ url: `/pages/tool/anime-avatar?copy_id=${id.value}` })
  124. }
  125. function getPageStackDelta(routeName) {
  126. const pages = getCurrentPages()
  127. const index = pages.findIndex((page) => page.route === routeName)
  128. return index >= 0 ? pages.length - 1 - index : -1
  129. }
  130. </script>
  131. <style lang="scss" scoped>
  132. .page {
  133. height: 100vh;
  134. background: #f4f7fb;
  135. display: flex;
  136. flex-direction: column;
  137. overflow: hidden;
  138. }
  139. .scroll {
  140. flex: 1;
  141. height: 0;
  142. }
  143. .wrap {
  144. padding: 28rpx 28rpx 150rpx;
  145. }
  146. .state-card,
  147. .image-card,
  148. .info-card {
  149. background: #fff;
  150. border-radius: 18rpx;
  151. overflow: hidden;
  152. }
  153. .state-card {
  154. min-height: 540rpx;
  155. display: flex;
  156. flex-direction: column;
  157. align-items: center;
  158. justify-content: center;
  159. color: #172033;
  160. font-size: 30rpx;
  161. }
  162. .spinner {
  163. width: 72rpx;
  164. height: 72rpx;
  165. border: 8rpx solid #e9edf5;
  166. border-top-color: #0b8cff;
  167. border-radius: 50%;
  168. animation: spin 0.8s linear infinite;
  169. margin-bottom: 28rpx;
  170. }
  171. .count,
  172. .error {
  173. margin-top: 18rpx;
  174. color: #7f8896;
  175. font-size: 26rpx;
  176. }
  177. .error {
  178. color: #f04438;
  179. padding: 0 40rpx;
  180. text-align: center;
  181. }
  182. .work-img {
  183. width: 100%;
  184. display: block;
  185. }
  186. .image-card {
  187. position: relative;
  188. }
  189. .num {
  190. position: absolute;
  191. right: 18rpx;
  192. bottom: 18rpx;
  193. min-width: 44rpx;
  194. height: 44rpx;
  195. border-radius: 22rpx;
  196. color: #fff;
  197. font-size: 24rpx;
  198. line-height: 44rpx;
  199. text-align: center;
  200. background: rgba(0, 0, 0, 0.45);
  201. }
  202. .info-card {
  203. margin-top: 24rpx;
  204. padding: 28rpx;
  205. }
  206. .title {
  207. color: #172033;
  208. font-size: 32rpx;
  209. font-weight: 700;
  210. margin-bottom: 16rpx;
  211. }
  212. .cell {
  213. display: flex;
  214. color: #7f8896;
  215. font-size: 27rpx;
  216. line-height: 1.6;
  217. padding: 10rpx 0;
  218. }
  219. .cell-label {
  220. flex-shrink: 0;
  221. }
  222. .cell-content {
  223. flex: 1;
  224. min-width: 0;
  225. color: #172033;
  226. }
  227. .link {
  228. color: #0b8cff;
  229. }
  230. .bottom-bar {
  231. position: fixed;
  232. left: 0;
  233. right: 0;
  234. bottom: 0;
  235. padding: 18rpx 28rpx calc(18rpx + env(safe-area-inset-bottom));
  236. background: #fff;
  237. display: flex;
  238. gap: 18rpx;
  239. box-shadow: 0 -8rpx 24rpx rgba(35, 55, 90, 0.08);
  240. }
  241. .bar-btn,
  242. .main-btn {
  243. height: 82rpx;
  244. border-radius: 42rpx;
  245. font-size: 29rpx;
  246. margin: 0;
  247. padding: 0;
  248. border: 0;
  249. display: flex;
  250. align-items: center;
  251. justify-content: center;
  252. }
  253. .bar-btn::after {
  254. border: 0;
  255. }
  256. .bar-btn {
  257. width: 150rpx;
  258. color: #0b8cff;
  259. background: #eef6ff;
  260. }
  261. .main-btn {
  262. flex: 1;
  263. color: #fff;
  264. background: #0b8cff;
  265. }
  266. @keyframes spin {
  267. to {
  268. transform: rotate(360deg);
  269. }
  270. }
  271. </style>