index.tsx 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. import { useEffect, useRef, useState } from "react";
  2. import { Image, View, ScrollView, Video } from "@tarojs/components";
  3. import { formatSeconds, saveMediaFile } from "@/utils/index";
  4. import PageCustom from "@/components/page-custom/index";
  5. import NavBarNormal from "@/components/NavBarNormal/index";
  6. import { checkPermission, showAuthModal } from "@/utils/auth";
  7. import { uploadAndNavToGenNewAvatar } from "@/utils/avatar";
  8. import IconPlusBig from "@/components/icon/icon-plus-big";
  9. import IconPlayWhite24 from '@/components/icon/IconPlayWhite20'
  10. import { deleteAvatar, fetchMyAvatars } from "@/service/storage";
  11. import style from "./index.module.less";
  12. import { TAvatarItem } from "@/service/storage";
  13. import { useAgentStore, useAgentStoreActions } from "@/store/agentStore";
  14. import Taro, { useDidShow, useRouter, useUnload } from "@tarojs/taro";
  15. import { isSuccess } from "@/utils";
  16. import WemetaRadio from "@/components/WemetaRadio/index";
  17. import { useModalStore } from "@/store/modalStore";
  18. import { useLoadMoreInfinite, createKey } from "@/utils/loadMoreInfinite";
  19. import BottomBar from "@/components/BottomBar";
  20. import WemetaButton from "@/components/buttons/WemetaButton";
  21. import { getNavigationDelta} from '@/utils/index'
  22. export default function Index() {
  23. const agent = useAgentStore((state) => state.agent);
  24. const { setCurrentEditAvatar } = useAgentStoreActions()
  25. const [scrollTop, setScrollTop] = useState(0);
  26. const scrollPositionRef = useRef(0);
  27. const { showModal } = useModalStore((state) => state.actions);
  28. const fetcher = async ([_url, { pageIndex, pageSize }]) => {
  29. const res = await fetchMyAvatars({ pageIndex: pageIndex, pageSize });
  30. return res.data;
  31. };
  32. const { list, loadMore, mutate } = useLoadMoreInfinite<TAvatarItem[]>(
  33. createKey("fetchMyAvatars"),
  34. fetcher
  35. );
  36. const delta = getNavigationDelta('pages/agent/index')
  37. console.log(delta)
  38. const [current, setCurrent] = useState<TAvatarItem | null>(null);
  39. // 选择形象
  40. const handleSelect = async (e: any, item: TAvatarItem) => {
  41. e.stopPropagation()
  42. console.log(item);
  43. if (current?.avatarId === item.avatarId) {
  44. setCurrent(null)
  45. } else {
  46. setCurrent(item);
  47. }
  48. };
  49. const handleSetBackground = () => {
  50. current && setCurrentEditAvatar(current)
  51. Taro.navigateTo({ url: '/pages/agent-avatar-confirm/index' });
  52. }
  53. const onScrollToLower = () => {
  54. loadMore();
  55. console.log("lower");
  56. };
  57. const handleCreate = () => {
  58. uploadAndNavToGenNewAvatar();
  59. };
  60. const handleDelete = async (e: any) => {
  61. e.stopPropagation()
  62. if (!current || !current.canDel) {
  63. return
  64. }
  65. showModal({
  66. content: <>确定删除该形象?</>,
  67. async onConfirm() {
  68. const response = await deleteAvatar(current.avatarId);
  69. if (isSuccess(response.status)) {
  70. setCurrent(null)
  71. mutate();
  72. }
  73. },
  74. });
  75. };
  76. const handlePreview = (index: number) => {
  77. Taro.previewMedia({
  78. current: index,
  79. //@ts-ignore
  80. sources: list.map((item) => {
  81. return {
  82. url: item.avatarUrl,
  83. type: item.isVideo ? 'video' : 'image',
  84. }
  85. }),
  86. })
  87. }
  88. const saveMedia = async (tmpPath: string) => {
  89. const res = await saveMediaFile(tmpPath, current?.isVideo);
  90. if (res) {
  91. Taro.showToast({
  92. title: '保存成功'
  93. })
  94. return
  95. }
  96. Taro.showToast({
  97. title: '保存失败'
  98. })
  99. }
  100. const handleDownload = async () => {
  101. if (!current?.avatarUrl) {
  102. return
  103. }
  104. // 保存至相册
  105. Taro.showLoading();
  106. const authed = await checkPermission("scope.writePhotosAlbum");
  107. if (!authed) {
  108. Taro.hideLoading();
  109. showAuthModal("需要您相册权限");
  110. return;
  111. }
  112. Taro.downloadFile({
  113. url: current.avatarUrl,
  114. success: (res) => {
  115. if (res.statusCode === 200) {
  116. saveMedia(res.tempFilePath)
  117. }
  118. },
  119. fail: () => {
  120. Taro.hideLoading();
  121. },
  122. })
  123. };
  124. useDidShow(() => {
  125. mutate()
  126. })
  127. useUnload(()=> {
  128. Taro.hideLoading()
  129. })
  130. const renderMedia = (avatar: TAvatarItem, index: number) => {
  131. if (avatar.isVideo) {
  132. return <>
  133. <View className={style.videoContainer} onClick={() => handlePreview(index)}>
  134. <Video
  135. controls={false}
  136. showCenterPlayBtn={false}
  137. loop={true}
  138. muted={true}
  139. objectFit="cover"
  140. src={avatar.avatarUrl}
  141. className="w-full h-full"
  142. />
  143. <View className={style.blurBg}></View>
  144. <View className={style.durationStatus}>
  145. <IconPlayWhite24 />
  146. <View className="text-10">{formatSeconds(Math.round(avatar.videoSeconds))}</View>
  147. </View>
  148. </View>
  149. </>
  150. }
  151. return <Image src={`${avatar.avatarUrl}?x-oss-process=image/quality,Q_60/format,jpg`} mode="widthFix" className="w-full" onClick={() => handlePreview(index)} />;
  152. };
  153. const renderList = () => {
  154. if (!list.length) {
  155. return (
  156. <>
  157. {/* <EmptyData type={"search"}></EmptyData> */}
  158. </>
  159. );
  160. }
  161. return list.map((avatar, index) => {
  162. const currentUsed = agent?.avatarUrl === avatar.avatarUrl;
  163. const isCurrentSelected = current?.avatarId === avatar.avatarId;
  164. return (
  165. <View
  166. className={`${currentUsed ? style.gridItemActived : style.gridItem}`}
  167. >
  168. <View
  169. className={style.selected}
  170. onClick={(e) => handleSelect(e, avatar)}
  171. >
  172. <WemetaRadio theme="light" checkbox checked={isCurrentSelected} />
  173. </View>
  174. {currentUsed && <View className={style.gridItemCurrentUsedMark}></View>}
  175. {/* <View className={style.gridItemCurrentUsedMark}></View> */}
  176. {!avatar.isOriginal && <View className={style.aiTips}>图片由AI生成</View>}
  177. {renderMedia(avatar, index)}
  178. </View>
  179. );
  180. });
  181. };
  182. return (
  183. <PageCustom>
  184. <NavBarNormal>历史形象</NavBarNormal>
  185. <View className={style.container}>
  186. <ScrollView
  187. scrollY
  188. onScrollToLower={onScrollToLower}
  189. scrollTop={scrollTop}
  190. onScroll={(e) => {
  191. scrollPositionRef.current = e.detail.scrollTop;
  192. }}
  193. style={{
  194. flex: 1,
  195. height: "100%", // 高度自适应
  196. }}
  197. >
  198. <View className="w-full p-16 pb-120">
  199. <View className={style.grid}>
  200. <View className={style.gridItemCreateBtn} onClick={handleCreate}>
  201. <View className={style.icon}>
  202. <IconPlusBig></IconPlusBig>
  203. </View>
  204. <View className="pt-8 text-12 leading-20 text-gray-4">
  205. 创建新形象
  206. </View>
  207. </View>
  208. {renderList()}
  209. </View>
  210. </View>
  211. </ScrollView>
  212. <BottomBar className="pt-12 px-16">
  213. <WemetaButton type='danger' className="w-88 text-red" disabled={!current?.canDel} onClick={handleDelete}>删除</WemetaButton>
  214. <WemetaButton type='normal' className="w-102" disabled={!current} onClick={handleDownload}>下载</WemetaButton>
  215. <WemetaButton type="primary" className="flex-1" disabled={!current} onClick={handleSetBackground}>设置为聊天背景</WemetaButton>
  216. </BottomBar>
  217. </View>
  218. </PageCustom>
  219. );
  220. }