refactor: 优化代码和添加类型检查脚本
- 统一 OWNER_ID 导入,从 lib/auth 导入而非环境变量 - 添加 type-checks 脚本用于 TypeScript 类型检查 - 对接 my 页面后端接口,使用 useTemplateGenerations - 添加 MySkeleton 骨架屏组件 - 添加下拉刷新功能 - 添加空状态提示文案 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect, useCallback } from 'react'
|
||||||
import {
|
import {
|
||||||
View,
|
View,
|
||||||
Text,
|
Text,
|
||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
Dimensions,
|
Dimensions,
|
||||||
Pressable,
|
Pressable,
|
||||||
StatusBar as RNStatusBar,
|
StatusBar as RNStatusBar,
|
||||||
|
RefreshControl,
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import { StatusBar } from 'expo-status-bar'
|
import { StatusBar } from 'expo-status-bar'
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context'
|
import { SafeAreaView } from 'react-native-safe-area-context'
|
||||||
@@ -18,41 +19,47 @@ import EditProfileDrawer from '@/components/drawer/EditProfileDrawer'
|
|||||||
import Dropdown from '@/components/ui/dropdown'
|
import Dropdown from '@/components/ui/dropdown'
|
||||||
import Toast from '@/components/ui/Toast'
|
import Toast from '@/components/ui/Toast'
|
||||||
import { signOut } from '@/lib/auth'
|
import { signOut } from '@/lib/auth'
|
||||||
|
import { useTemplateGenerations, type TemplateGeneration } from '@/hooks'
|
||||||
|
import { MySkeleton } from '@/components/skeleton/MySkeleton'
|
||||||
|
|
||||||
const { width: screenWidth } = Dimensions.get('window')
|
const { width: screenWidth } = Dimensions.get('window')
|
||||||
const GALLERY_GAP = 2
|
const GALLERY_GAP = 2
|
||||||
const GALLERY_HORIZONTAL_PADDING = 0
|
const GALLERY_HORIZONTAL_PADDING = 0
|
||||||
// 计算每个卡片的宽度:屏幕宽度 - 左右padding - 2个间距,然后除以3,使用 Math.floor 确保整数像素
|
|
||||||
const GALLERY_ITEM_SIZE = Math.floor(
|
const GALLERY_ITEM_SIZE = Math.floor(
|
||||||
(screenWidth - GALLERY_HORIZONTAL_PADDING * 2 - GALLERY_GAP * 2) / 3
|
(screenWidth - GALLERY_HORIZONTAL_PADDING * 2 - GALLERY_GAP * 2) / 3
|
||||||
)
|
)
|
||||||
// status状态有running pending completed
|
|
||||||
const works = [
|
|
||||||
{ id: 1, status: 'running' as const, count: 1 },
|
|
||||||
{ id: 2, status: 'completed' as const, count: 1 },
|
|
||||||
{ id: 3, status: 'completed' as const, count: 1 },
|
|
||||||
{ id: 4, status: 'completed' as const, count: 2 },
|
|
||||||
{ id: 5, status: 'pending' as const, count: 1 },
|
|
||||||
{ id: 6, status: 'pending' as const, count: 1 },
|
|
||||||
{ id: 7, status: 'pending' as const, count: 1 },
|
|
||||||
{ id: 8, status: 'pending' as const, count: 1 },
|
|
||||||
{ id: 9, status: 'completed' as const, count: 1 },
|
|
||||||
{ id: 10, status: 'completed' as const, count: 1 },
|
|
||||||
{ id: 11, status: 'completed' as const, count: 1 },
|
|
||||||
{ id: 12, status: 'completed' as const, count: 1 },
|
|
||||||
{ id: 13, status: 'completed' as const, count: 1 },
|
|
||||||
{ id: 14, status: 'completed' as const, count: 1 },
|
|
||||||
{ id: 15, status: 'completed' as const, count: 1 },
|
|
||||||
{ id: 16, status: 'completed' as const, count: 1 },
|
|
||||||
{ id: 17, status: 'completed' as const, count: 1 },
|
|
||||||
|
|
||||||
]
|
// 获取作品封面图
|
||||||
|
const getCoverUrl = (item: TemplateGeneration) =>
|
||||||
|
item.resultUrl?.[0] || item.template?.coverImageUrl
|
||||||
|
|
||||||
export default function My() {
|
export default function My() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { t, i18n } = useTranslation()
|
const { t, i18n } = useTranslation()
|
||||||
const [editDrawerVisible, setEditDrawerVisible] = useState(false)
|
const [editDrawerVisible, setEditDrawerVisible] = useState(false)
|
||||||
const [profileName, setProfileName] = useState('乔乔乔乔')
|
const [profileName, setProfileName] = useState('乔乔乔乔')
|
||||||
|
const [refreshing, setRefreshing] = useState(false)
|
||||||
|
|
||||||
|
// 使用 useTemplateGenerations hook 获取用户作品列表
|
||||||
|
const {
|
||||||
|
generations,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
execute: loadGenerations,
|
||||||
|
refetch,
|
||||||
|
} = useTemplateGenerations()
|
||||||
|
|
||||||
|
// 初始化加载作品列表
|
||||||
|
useEffect(() => {
|
||||||
|
loadGenerations({ page: 1, limit: 50 })
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// 处理下拉刷新
|
||||||
|
const handleRefresh = useCallback(async () => {
|
||||||
|
setRefreshing(true)
|
||||||
|
await refetch({ page: 1, limit: 50 })
|
||||||
|
setRefreshing(false)
|
||||||
|
}, [refetch])
|
||||||
|
|
||||||
// 处理设置菜单选择
|
// 处理设置菜单选择
|
||||||
const handleSettingsSelect = async (value: string) => {
|
const handleSettingsSelect = async (value: string) => {
|
||||||
@@ -172,70 +179,86 @@ export default function My() {
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* 作品九宫格 */}
|
{/* 作品九宫格 */}
|
||||||
|
{loading && !refreshing ? (
|
||||||
|
<MySkeleton />
|
||||||
|
) : (
|
||||||
<ScrollView
|
<ScrollView
|
||||||
style={styles.scrollView}
|
style={styles.scrollView}
|
||||||
contentContainerStyle={styles.scrollContent}
|
contentContainerStyle={styles.scrollContent}
|
||||||
showsVerticalScrollIndicator={false}
|
showsVerticalScrollIndicator={false}
|
||||||
|
refreshControl={
|
||||||
|
<RefreshControl
|
||||||
|
refreshing={refreshing}
|
||||||
|
onRefresh={handleRefresh}
|
||||||
|
tintColor="#FFFFFF"
|
||||||
|
colors={['#FFFFFF']}
|
||||||
|
/>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<View style={styles.galleryGrid}>
|
<View style={styles.galleryGrid}>
|
||||||
{works.map((item, index) => (
|
{generations.map((item, index) => (
|
||||||
<Pressable
|
<Pressable
|
||||||
key={item.id}
|
key={item.id}
|
||||||
style={[
|
style={[
|
||||||
styles.galleryItem,
|
styles.galleryItem,
|
||||||
// 每行的前两个item有右边距,第三个没有
|
|
||||||
index % 3 !== 2 && styles.galleryItemMarginRight,
|
index % 3 !== 2 && styles.galleryItemMarginRight,
|
||||||
// 所有item都有下边距(最后一行也会有,但影响不大)
|
|
||||||
styles.galleryItemMarginBottom,
|
styles.galleryItemMarginBottom,
|
||||||
]}
|
]}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
// 只有已完成的作品才能点击进入详情页
|
|
||||||
if (item.status === 'completed') {
|
if (item.status === 'completed') {
|
||||||
router.push({
|
router.push({
|
||||||
pathname: '/generationRecord' as any,
|
pathname: '/generationRecord' as any,
|
||||||
params: { id: item.id.toString() },
|
params: { id: item.id },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
disabled={item.status !== 'completed'}
|
disabled={item.status !== 'completed'}
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
source={require('@/assets/images/membership.png')}
|
source={getCoverUrl(item) ? { uri: getCoverUrl(item) } : require('@/assets/images/membership.png')}
|
||||||
style={styles.galleryImage}
|
style={styles.galleryImage}
|
||||||
contentFit="cover"
|
contentFit="cover"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 生成中遮罩 */}
|
{/* 遮罩:非完成状态 */}
|
||||||
{item.status != 'completed' && (
|
{item.status !== 'completed' && (
|
||||||
<View style={styles.generatingOverlay} />
|
<View style={styles.generatingOverlay} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 右上角作品数量角标 */}
|
{/* 数量角标:已完成且有结果 */}
|
||||||
{item.status === 'completed'&&<View style={styles.counterBadge}>
|
{item.status === 'completed' && (item.resultUrl?.length || 0) > 0 && (
|
||||||
|
<View style={styles.counterBadge}>
|
||||||
<Text style={styles.counterText}>
|
<Text style={styles.counterText}>
|
||||||
{item.count}
|
{item.resultUrl?.length || 1}
|
||||||
</Text>
|
</Text>
|
||||||
</View>}
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* "生成中"角标:覆盖在图片左下角 */}
|
{/* 状态角标 */}
|
||||||
{item.status === 'running' && (
|
{item.status === 'running' && (
|
||||||
<View style={styles.generatingBadge}>
|
<View style={styles.generatingBadge}>
|
||||||
<Text style={styles.generatingBadgeText}>
|
<Text style={styles.generatingBadgeText}>{t('my.generating')}</Text>
|
||||||
{t('my.generating')}
|
|
||||||
</Text>
|
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
{item.status === 'pending' && (
|
{item.status === 'pending' && (
|
||||||
<View style={styles.generatingBadge}>
|
<View style={styles.generatingBadge}>
|
||||||
<Text style={styles.generatingBadgeText}>
|
<Text style={styles.generatingBadgeText}>{t('my.queuing')}</Text>
|
||||||
{t('my.queuing')}
|
|
||||||
</Text>
|
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</Pressable>
|
</Pressable>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
{/* 空状态提示 */}
|
||||||
|
{!loading && generations.length === 0 && (
|
||||||
|
<View style={styles.emptyState}>
|
||||||
|
<Text style={styles.emptyStateText}>
|
||||||
|
{t('my.noWorks')}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* 编辑资料抽屉 */}
|
{/* 编辑资料抽屉 */}
|
||||||
<EditProfileDrawer
|
<EditProfileDrawer
|
||||||
@@ -276,75 +299,6 @@ const styles = StyleSheet.create({
|
|||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
},
|
},
|
||||||
header: {
|
|
||||||
backgroundColor: '#000000',
|
|
||||||
paddingTop: 8,
|
|
||||||
paddingBottom: 12,
|
|
||||||
},
|
|
||||||
statusBar: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
paddingHorizontal: 16,
|
|
||||||
paddingBottom: 8,
|
|
||||||
},
|
|
||||||
time: {
|
|
||||||
color: '#FFFFFF',
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: '600',
|
|
||||||
},
|
|
||||||
statusIcons: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: 4,
|
|
||||||
},
|
|
||||||
signalBars: {
|
|
||||||
width: 18,
|
|
||||||
height: 12,
|
|
||||||
backgroundColor: '#FFFFFF',
|
|
||||||
borderRadius: 2,
|
|
||||||
},
|
|
||||||
wifiIcon: {
|
|
||||||
width: 16,
|
|
||||||
height: 12,
|
|
||||||
backgroundColor: '#FFFFFF',
|
|
||||||
borderRadius: 2,
|
|
||||||
},
|
|
||||||
batteryIcon: {
|
|
||||||
width: 24,
|
|
||||||
height: 12,
|
|
||||||
backgroundColor: '#FFFFFF',
|
|
||||||
borderRadius: 2,
|
|
||||||
},
|
|
||||||
titleBar: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
paddingHorizontal: 16,
|
|
||||||
},
|
|
||||||
appTitle: {
|
|
||||||
color: '#FFFFFF',
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: '700',
|
|
||||||
},
|
|
||||||
headerRight: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: 12,
|
|
||||||
},
|
|
||||||
pointsContainer: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: 4,
|
|
||||||
},
|
|
||||||
pointsText: {
|
|
||||||
color: '#FFFFFF',
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: '600',
|
|
||||||
},
|
|
||||||
searchButton: {
|
|
||||||
padding: 4,
|
|
||||||
},
|
|
||||||
scrollView: {
|
scrollView: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: '#090A0B',
|
backgroundColor: '#090A0B',
|
||||||
@@ -384,8 +338,8 @@ const styles = StyleSheet.create({
|
|||||||
opacity: 0.7,
|
opacity: 0.7,
|
||||||
},
|
},
|
||||||
editButton: {
|
editButton: {
|
||||||
paddingHorizontal: 8, //左右各留 8 像素的内边距
|
paddingHorizontal: 8,
|
||||||
paddingVertical: 6, //上下各留 6 像素的内边距
|
paddingVertical: 6,
|
||||||
borderRadius: 6,
|
borderRadius: 6,
|
||||||
backgroundColor: '#1C1E22',
|
backgroundColor: '#1C1E22',
|
||||||
},
|
},
|
||||||
@@ -412,12 +366,6 @@ const styles = StyleSheet.create({
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
},
|
},
|
||||||
sectionMoreIcon: {
|
|
||||||
width: 10,
|
|
||||||
height: 10,
|
|
||||||
borderRadius: 5,
|
|
||||||
backgroundColor: '#FFFFFF33',
|
|
||||||
},
|
|
||||||
galleryGrid: {
|
galleryGrid: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
@@ -425,7 +373,6 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
galleryItem: {
|
galleryItem: {
|
||||||
width: GALLERY_ITEM_SIZE,
|
width: GALLERY_ITEM_SIZE,
|
||||||
// 使用等比例 1:1,保证容器永远是正方形
|
|
||||||
aspectRatio: 1,
|
aspectRatio: 1,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
backgroundColor: '#1C1E22',
|
backgroundColor: '#1C1E22',
|
||||||
@@ -439,7 +386,6 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
galleryImage: {
|
galleryImage: {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
// 高度由 aspectRatio 决定,避免拉伸
|
|
||||||
height: undefined,
|
height: undefined,
|
||||||
aspectRatio: 1,
|
aspectRatio: 1,
|
||||||
},
|
},
|
||||||
@@ -471,4 +417,14 @@ const styles = StyleSheet.create({
|
|||||||
fontSize: 9,
|
fontSize: 9,
|
||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
},
|
},
|
||||||
|
emptyState: {
|
||||||
|
width: '100%',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
paddingVertical: 60,
|
||||||
|
},
|
||||||
|
emptyStateText: {
|
||||||
|
color: '#FFFFFF80',
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
78
components/skeleton/HomeSkeleton.tsx
Normal file
78
components/skeleton/HomeSkeleton.tsx
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import { Dimensions, View, StyleSheet } from 'react-native'
|
||||||
|
import { Skeleton } from './skeleton'
|
||||||
|
|
||||||
|
const { width: screenWidth } = Dimensions.get('window')
|
||||||
|
|
||||||
|
const horizontalPadding = 8 * 2
|
||||||
|
const cardGap = 5
|
||||||
|
const cardWidth = (screenWidth - horizontalPadding - cardGap) / 2
|
||||||
|
|
||||||
|
export function HomeSkeleton() {
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
{/* 标签骨架屏 */}
|
||||||
|
<View style={styles.tabsContainer}>
|
||||||
|
{[1, 2, 3, 4].map((_, index) => (
|
||||||
|
<Skeleton
|
||||||
|
key={index}
|
||||||
|
width={60}
|
||||||
|
height={20}
|
||||||
|
borderRadius={4}
|
||||||
|
style={styles.tabSkeleton}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 卡片骨架屏 */}
|
||||||
|
<View style={styles.gridContainer}>
|
||||||
|
{[1, 2, 3, 4, 5, 6].map((_, index) => (
|
||||||
|
<View
|
||||||
|
key={index}
|
||||||
|
style={[
|
||||||
|
styles.cardSkeleton,
|
||||||
|
index % 2 === 0 ? styles.cardLeft : styles.cardRight,
|
||||||
|
{ width: cardWidth },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Skeleton
|
||||||
|
width="100%"
|
||||||
|
height={cardWidth * 1.2}
|
||||||
|
borderRadius={16}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
backgroundColor: '#090A0B',
|
||||||
|
},
|
||||||
|
tabsContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
paddingHorizontal: 16,
|
||||||
|
paddingVertical: 12,
|
||||||
|
gap: 20,
|
||||||
|
marginBottom: 18,
|
||||||
|
},
|
||||||
|
tabSkeleton: {
|
||||||
|
marginBottom: 8,
|
||||||
|
},
|
||||||
|
gridContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
paddingHorizontal: 8,
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
},
|
||||||
|
cardSkeleton: {
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
cardLeft: {
|
||||||
|
marginRight: 0,
|
||||||
|
},
|
||||||
|
cardRight: {
|
||||||
|
marginLeft: 0,
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -4,9 +4,9 @@ import { useState } from 'react'
|
|||||||
|
|
||||||
import { type ApiError } from '@/lib/types'
|
import { type ApiError } from '@/lib/types'
|
||||||
|
|
||||||
|
import { OWNER_ID } from '@/lib/auth'
|
||||||
import { handleError } from './use-error'
|
import { handleError } from './use-error'
|
||||||
|
|
||||||
const OWNER_ID = process.env.EXPO_PUBLIC_OWNER_ID || ''
|
|
||||||
|
|
||||||
export const useActivates = () => {
|
export const useActivates = () => {
|
||||||
const [loading, setLoading] = useState<boolean>(false)
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import { useState } from 'react'
|
|||||||
|
|
||||||
import { type ApiError } from '@/lib/types'
|
import { type ApiError } from '@/lib/types'
|
||||||
|
|
||||||
|
import { OWNER_ID } from '@/lib/auth'
|
||||||
import { handleError } from './use-error'
|
import { handleError } from './use-error'
|
||||||
|
|
||||||
const OWNER_ID = process.env.EXPO_PUBLIC_OWNER_ID || ''
|
|
||||||
|
|
||||||
export const useCategories = () => {
|
export const useCategories = () => {
|
||||||
const [loading, setLoading] = useState<boolean>(false)
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import { type ApiError } from '@/lib/types'
|
|||||||
|
|
||||||
import { handleError } from './use-error'
|
import { handleError } from './use-error'
|
||||||
|
|
||||||
const OWNER_ID = process.env.EXPO_PUBLIC_OWNER_ID || ''
|
|
||||||
|
|
||||||
export const useTemplateGenerations = () => {
|
export const useTemplateGenerations = () => {
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
@@ -106,3 +105,4 @@ export const useTemplateGenerations = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type { TemplateGeneration }
|
export type { TemplateGeneration }
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
"language": "Language",
|
"language": "Language",
|
||||||
"languageSwitch": "Language: 中文",
|
"languageSwitch": "Language: 中文",
|
||||||
"languageSwitchEn": "Language: English",
|
"languageSwitchEn": "Language: English",
|
||||||
"logout": "Logout"
|
"logout": "Logout",
|
||||||
|
"noWorks": "No works yet"
|
||||||
},
|
},
|
||||||
"changePassword": {
|
"changePassword": {
|
||||||
"title": "Change Password",
|
"title": "Change Password",
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
"language": "语言",
|
"language": "语言",
|
||||||
"languageSwitch": "语言: 中文",
|
"languageSwitch": "语言: 中文",
|
||||||
"languageSwitchEn": "Language: English",
|
"languageSwitchEn": "Language: English",
|
||||||
"logout": "退出登录"
|
"logout": "退出登录",
|
||||||
|
"noWorks": "暂无作品"
|
||||||
},
|
},
|
||||||
"changePassword": {
|
"changePassword": {
|
||||||
"title": "修改密码",
|
"title": "修改密码",
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
"prebuild:android": "expo prebuild --platform android",
|
"prebuild:android": "expo prebuild --platform android",
|
||||||
"reset-project": "node ./scripts/reset-project.js",
|
"reset-project": "node ./scripts/reset-project.js",
|
||||||
"claude": "claude --dangerously-skip-permissions",
|
"claude": "claude --dangerously-skip-permissions",
|
||||||
|
"type-checks": "tsc --noEmit",
|
||||||
"android": "expo run:android",
|
"android": "expo run:android",
|
||||||
"ios": "expo run:ios",
|
"ios": "expo run:ios",
|
||||||
"web": "expo start --web",
|
"web": "expo start --web",
|
||||||
|
|||||||
Reference in New Issue
Block a user