feat: 轮播图
This commit is contained in:
parent
a8c5117cb7
commit
0db4c0c74e
@ -4,21 +4,19 @@ import StoriesSvg from '@/assets/icons/svg/stories.svg';
|
||||
import UsedStorageSvg from '@/assets/icons/svg/usedStorage.svg';
|
||||
import AskNavbar from '@/components/layout/ask';
|
||||
import AlbumComponent from '@/components/owner/album';
|
||||
import CategoryComponent from '@/components/owner/category';
|
||||
import CountComponent from '@/components/owner/count';
|
||||
import CarouselComponent from '@/components/owner/carousel';
|
||||
import CreateCountComponent from '@/components/owner/createCount';
|
||||
import Ranking from '@/components/owner/ranking';
|
||||
import ResourceComponent from '@/components/owner/resource';
|
||||
import SettingModal from '@/components/owner/setting';
|
||||
import UserInfo from '@/components/owner/userName';
|
||||
import { formatDuration } from '@/components/utils/time';
|
||||
import { checkAuthStatus } from '@/lib/auth';
|
||||
import { fetchApi } from '@/lib/server-api-util';
|
||||
import { CountData, UserInfoDetails } from '@/types/user';
|
||||
import { useRouter } from 'expo-router';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FlatList, ScrollView, StyleSheet, View } from 'react-native';
|
||||
import { FlatList, StyleSheet, View } from 'react-native';
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
|
||||
export default function OwnerPage() {
|
||||
@ -93,27 +91,8 @@ export default function OwnerPage() {
|
||||
<ResourceComponent title={t("generalSetting.remainingPoints", { ns: "personal" })} data={{ all: userInfoDetails.total_points, used: userInfoDetails.remain_points }} icon={<PointsSvg />} />
|
||||
</View>
|
||||
</View>
|
||||
{/* 数据统计 */}
|
||||
<CountComponent
|
||||
data={[{ title: t("generalSetting.totalVideo", { ns: "personal" }), number: countData?.counter?.total_count?.video_count || 0 }, { title: t("generalSetting.totalPhoto", { ns: "personal" }), number: countData?.counter?.total_count?.photo_count || 0 }, { title: t("generalSetting.live", { ns: "personal" }), number: countData?.counter?.total_count?.live_count || 0 }, { title: t("generalSetting.videoLength", { ns: "personal" }), number: formatDuration(countData?.counter?.total_count?.video_length || 0) }]}
|
||||
/>
|
||||
|
||||
{/* 分类 */}
|
||||
<View style={{ height: 145 }}>
|
||||
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{ gap: 16 }} >
|
||||
{countData?.counter?.category_count && Object.entries(countData?.counter?.category_count).map(([key, value], index) => {
|
||||
return (
|
||||
<CategoryComponent
|
||||
key={index}
|
||||
title={key}
|
||||
data={[{ title: 'Video', number: value.video_count }, { title: 'Photo', number: value.photo_count }, { title: 'Length', number: formatDuration(value.video_length || 0) }]}
|
||||
bgSvg={value.cover_url}
|
||||
style={{ aspectRatio: 1, flex: 1 }}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</ScrollView>
|
||||
</View>
|
||||
<CarouselComponent data={userInfoDetails?.material_counter} />
|
||||
|
||||
{/* 作品数据 */}
|
||||
<View className='flex flex-row justify-between gap-[1rem]'>
|
||||
|
||||
127
components/owner/carousel.tsx
Normal file
127
components/owner/carousel.tsx
Normal file
@ -0,0 +1,127 @@
|
||||
import { Counter, UserCountData } from "@/types/user";
|
||||
import * as React from "react";
|
||||
import { Dimensions, StyleSheet, View } from "react-native";
|
||||
import { useSharedValue } from "react-native-reanimated";
|
||||
import Carousel, {
|
||||
ICarouselInstance
|
||||
} from "react-native-reanimated-carousel";
|
||||
import { ThemedText } from "../ThemedText";
|
||||
import { formatDuration } from "../utils/time";
|
||||
import CategoryComponent from "./category";
|
||||
interface Props {
|
||||
data: Counter
|
||||
}
|
||||
|
||||
interface CarouselData {
|
||||
key: string,
|
||||
value: UserCountData
|
||||
|
||||
}[]
|
||||
const width = Dimensions.get("window").width;
|
||||
|
||||
function CarouselComponent(props: Props) {
|
||||
const { data } = props;
|
||||
|
||||
const ref = React.useRef<ICarouselInstance>(null);
|
||||
const progress = useSharedValue<number>(0);
|
||||
const [carouselDataValue, setCarouselDataValue] = React.useState<CarouselData[]>([]);
|
||||
const dataHandle = () => {
|
||||
const carouselData = { ...data?.category_count, total_count: data?.total_count }
|
||||
// 1. 转换为数组并过滤掉 'total'
|
||||
const entries = Object?.entries(carouselData)
|
||||
?.filter(([key]) => key !== 'total_count')
|
||||
?.map(([key, value]) => ({ key, value }));
|
||||
|
||||
// 2. 找到 total 数据
|
||||
const totalEntry = {
|
||||
key: 'total_count',
|
||||
value: carouselData?.total_count
|
||||
};
|
||||
|
||||
// 3. 插入到中间位置
|
||||
const middleIndex = Math.floor((entries || [])?.length / 2);
|
||||
entries?.splice(middleIndex, 0, totalEntry);
|
||||
setCarouselDataValue(entries)
|
||||
return entries;
|
||||
}
|
||||
|
||||
const totleItem = (data: UserCountData) => {
|
||||
return <View style={[styles.container]}>
|
||||
{Object?.entries(data)?.filter(([key]) => key !== 'cover_url')?.map((item, index) => (
|
||||
<View style={styles.item} key={index}>
|
||||
<ThemedText style={styles.title}>{item[0]}</ThemedText>
|
||||
<ThemedText style={styles.number}>{item[1]}</ThemedText>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
if (data) {
|
||||
dataHandle()
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
|
||||
<Carousel
|
||||
ref={ref}
|
||||
width={width * 0.8}
|
||||
height={width * 0.8}
|
||||
data={carouselDataValue || []}
|
||||
mode="parallax"
|
||||
onProgressChange={progress}
|
||||
defaultIndex={carouselDataValue?.findIndex((item) => item?.key === 'total_count') - 1 || 0}
|
||||
renderItem={({ item }) => {
|
||||
if (item?.key === 'total_count') {
|
||||
return totleItem(item.value)
|
||||
}
|
||||
return CategoryComponent(
|
||||
{
|
||||
title: item?.key,
|
||||
data:
|
||||
[
|
||||
{ title: 'Video', number: item?.value?.video_count },
|
||||
{ title: 'Photo', number: item?.value?.photo_count },
|
||||
{ title: 'Length', number: formatDuration(item?.value?.video_length || 0) }
|
||||
],
|
||||
bgSvg: item?.value?.cover_url,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
backgroundColor: "#FFB645",
|
||||
padding: 16,
|
||||
borderRadius: 20,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "space-between",
|
||||
height: '100%',
|
||||
},
|
||||
item: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
paddingVertical: 8
|
||||
},
|
||||
title: {
|
||||
color: "#4C320C",
|
||||
fontWeight: "700",
|
||||
fontSize: 14,
|
||||
},
|
||||
number: {
|
||||
color: "#fff",
|
||||
fontWeight: "700",
|
||||
fontSize: 32,
|
||||
textAlign: 'right',
|
||||
flex: 1,
|
||||
paddingTop: 8
|
||||
}
|
||||
})
|
||||
|
||||
export default CarouselComponent;
|
||||
@ -14,8 +14,11 @@ const CategoryComponent = ({ title, data, bgSvg, style }: CategoryProps) => {
|
||||
<View style={styles.backgroundContainer}>
|
||||
<Image
|
||||
source={bgSvg !== "" && bgSvg !== null ? { uri: bgSvg } : require('@/assets/images/png/owner/animals.png')}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
resizeMode="cover"
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
resizeMode: "cover"
|
||||
}}
|
||||
/>
|
||||
<View style={styles.overlay} />
|
||||
</View>
|
||||
@ -37,11 +40,12 @@ const styles = StyleSheet.create({
|
||||
borderRadius: 32,
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
aspectRatio: 1,
|
||||
},
|
||||
backgroundContainer: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
},
|
||||
overlay: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
|
||||
13
package-lock.json
generated
13
package-lock.json
generated
@ -60,6 +60,7 @@
|
||||
"react-native-picker-select": "^9.3.1",
|
||||
"react-native-progress": "^5.0.1",
|
||||
"react-native-reanimated": "~3.17.4",
|
||||
"react-native-reanimated-carousel": "^4.0.2",
|
||||
"react-native-render-html": "^6.3.4",
|
||||
"react-native-safe-area-context": "5.4.0",
|
||||
"react-native-screens": "~4.11.1",
|
||||
@ -14826,6 +14827,18 @@
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-reanimated-carousel": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-native-reanimated-carousel/-/react-native-reanimated-carousel-4.0.2.tgz",
|
||||
"integrity": "sha512-vNpCfPlFoOVKHd+oB7B0luoJswp+nyz0NdJD8+LCrf25JiNQXfM22RSJhLaksBHqk3fm8R4fKWPNcfy5w7wL1Q==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": ">=18.0.0",
|
||||
"react-native": ">=0.70.3",
|
||||
"react-native-gesture-handler": ">=2.9.0",
|
||||
"react-native-reanimated": ">=3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-reanimated/node_modules/react-native-is-edge-to-edge": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.1.7.tgz",
|
||||
|
||||
@ -25,6 +25,7 @@
|
||||
"expo-audio": "~0.4.8",
|
||||
"expo-background-task": "^0.2.8",
|
||||
"expo-blur": "~14.1.5",
|
||||
"expo-clipboard": "~7.1.5",
|
||||
"expo-constants": "~17.1.6",
|
||||
"expo-dev-client": "~5.2.4",
|
||||
"expo-device": "~7.1.4",
|
||||
@ -33,6 +34,7 @@
|
||||
"expo-haptics": "~14.1.4",
|
||||
"expo-image-manipulator": "~13.1.7",
|
||||
"expo-image-picker": "~16.1.4",
|
||||
"expo-linear-gradient": "~14.1.5",
|
||||
"expo-linking": "~7.1.7",
|
||||
"expo-localization": "^16.1.5",
|
||||
"expo-location": "~18.1.5",
|
||||
@ -64,6 +66,7 @@
|
||||
"react-native-picker-select": "^9.3.1",
|
||||
"react-native-progress": "^5.0.1",
|
||||
"react-native-reanimated": "~3.17.4",
|
||||
"react-native-reanimated-carousel": "^4.0.2",
|
||||
"react-native-render-html": "^6.3.4",
|
||||
"react-native-safe-area-context": "5.4.0",
|
||||
"react-native-screens": "~4.11.1",
|
||||
@ -72,9 +75,7 @@
|
||||
"react-native-uuid": "^2.0.3",
|
||||
"react-native-web": "~0.20.0",
|
||||
"react-native-webview": "13.13.5",
|
||||
"react-redux": "^9.2.0",
|
||||
"expo-clipboard": "~7.1.5",
|
||||
"expo-linear-gradient": "~14.1.5"
|
||||
"react-redux": "^9.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.25.2",
|
||||
|
||||
@ -12,7 +12,7 @@ export interface User {
|
||||
avatar_file_url?: string
|
||||
}
|
||||
|
||||
interface UserCountData {
|
||||
export interface UserCountData {
|
||||
video_count: number,
|
||||
photo_count: number,
|
||||
live_count: number,
|
||||
@ -31,7 +31,7 @@ export interface CountData {
|
||||
}
|
||||
}
|
||||
|
||||
interface Counter {
|
||||
export interface Counter {
|
||||
user_id: number,
|
||||
total_count: UserCountData,
|
||||
category_count: {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user