refactor(friends-photo): streamline Friends Photo page components and styles

This commit is contained in:
iHeyTang 2025-09-28 12:58:45 +08:00
parent 637cfc5b95
commit 7f9168d116
15 changed files with 84 additions and 386 deletions

View File

@ -4,8 +4,8 @@ export default defineAppConfig({
pages: [
'pages/home/index', // 新的模板卡片首页
'pages/history/index', // 历史记录页面
'pages/result/index',
'pages/friends-photo/index', // 好友合照页面
'pages/result/index',
],
tabBar: {
color: '#8E9BAE',

View File

@ -21,7 +21,7 @@ function App({ children }: PropsWithChildren<any>) {
}
const authorize = createPlatformFactory().createAuthorize()
const payment = createPlatformFactory().createPayment()
// const payment = createPlatformFactory().createPayment()
try {
// 检查登录状态包括OAuth 2.0回调处理
const isLoggedIn = await authorize.checkLogin()
@ -29,8 +29,8 @@ function App({ children }: PropsWithChildren<any>) {
// 可以根据需要决定是否自动跳转登录
await authorize.login()
}
const result = await payment.pay(`character_figurine_v1`, ``)
console.log({ result })
// const result = await payment.pay(`character_figurine_v1`, ``)
// console.log({ result })
} catch (error) {
console.error('登录检查失败:', error)
}

BIN
src/assets/icons/photo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -1,63 +0,0 @@
/* 介绍卡片 */
.intro-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
margin: 20px;
border-radius: 20px;
padding: 24px;
box-shadow: 0 8px 32px rgba(102, 126, 234, 0.3);
position: relative;
overflow: hidden;
}
.intro-card::before {
content: '';
position: absolute;
top: -50%;
right: -50%;
width: 100%;
height: 100%;
background: radial-gradient(circle, rgba(255, 255, 255, 0.1) 0%, transparent 70%);
border-radius: 50%;
}
.intro-content {
display: flex;
align-items: center;
gap: 16px;
position: relative;
z-index: 1;
}
.intro-icon {
font-size: 36px;
background: rgba(255, 255, 255, 0.2);
border-radius: 16px;
padding: 12px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.3);
}
.intro-text {
flex: 1;
}
.intro-title {
font-size: 20px;
font-weight: 600;
color: white;
margin-bottom: 4px;
}
.intro-subtitle {
font-size: 14px;
color: rgba(255, 255, 255, 0.9);
line-height: 1.4;
}
/* 响应式适配 */
@media (max-width: 375px) {
.intro-card {
margin: 16px;
padding: 20px;
}
}

View File

@ -1,23 +0,0 @@
import { View } from '@tarojs/components';
import { useI18n } from '../../../../hooks/useI18n';
import './index.css';
interface IntroCardProps {
className?: string;
}
export default function IntroCard({ className = '' }: IntroCardProps) {
const { t } = useI18n();
return (
<View className={`intro-card ${className}`}>
<View className="intro-content">
<View className="intro-icon">👫</View>
<View className="intro-text">
<View className="intro-title">{t('friendsPhoto.friendsPhotoGeneration')}</View>
<View className="intro-subtitle">{t('friendsPhoto.uploadTwoPhotosDesc')}</View>
</View>
</View>
</View>
);
}

View File

@ -4,74 +4,44 @@
bottom: 0;
left: 0;
right: 0;
padding: 20px;
background: white;
border-top: 1px solid #e5e7eb;
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.08);
backdrop-filter: blur(10px);
padding: 20px 14px;
padding-bottom: calc(20px + env(safe-area-inset-bottom));
z-index: 10;
}
/* 提交按钮 */
.submit-button {
width: 100%;
height: 56px;
background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
border-radius: 28px;
height: 96px;
background: #000;
border-radius: 24px;
border: none;
color: white;
font-size: 16px;
font-weight: 600;
outline: none;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
transition: all 0.3s ease;
box-shadow: 0 6px 24px rgba(59, 130, 246, 0.4);
position: relative;
overflow: hidden;
}
.submit-button::before {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: linear-gradient(45deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transform: rotate(45deg);
transition: transform 0.6s ease;
}
.submit-button:not(.disabled):active::before {
transform: rotate(45deg) translateX(100%);
}
.submit-button.disabled {
background: #d1d5db;
box-shadow: none;
cursor: not-allowed;
}
.submit-button:not(.disabled):active {
transform: translateY(2px);
box-shadow: 0 3px 16px rgba(59, 130, 246, 0.4);
}
.submit-icon {
font-size: 18px;
transition: all 0.2s ease;
}
.submit-text {
color: white;
font-size: 24px;
font-weight: 600;
}
.submit-button[disabled] {
background-color: #ccc;
cursor: not-allowed;
border: none;
outline: none;
}
/* 加载动画 */
.loading-spinner {
width: 16px;
height: 16px;
border: 2px solid rgba(255, 255, 255, 0.3);
border: 2px solid rgb(255 255 255 / 30%);
border-top: 2px solid white;
border-radius: 50%;
animation: spin 1s linear infinite;
@ -81,22 +51,8 @@
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
/* 响应式适配 */
@media (max-width: 375px) {
.submit-section {
padding: 16px;
}
}
/* 深色模式适配 */
@media (prefers-color-scheme: dark) {
.submit-section {
background: #2a2a2a;
border-color: #333;
}
}

View File

@ -21,10 +21,7 @@ export default function SubmitButton({ disabled, loading, onSubmit, className =
<View className="submit-text">{t('friendsPhoto.generating')}</View>
</>
) : (
<>
<View className="submit-icon"></View>
<View className="submit-text">{t('friendsPhoto.startGenerating')}</View>
</>
<View className="submit-text">{t('friendsPhoto.startGenerating')}</View>
)}
</Button>
</View>

View File

@ -1,60 +1,29 @@
/* 上传卡片 */
.upload-card {
background: white;
border-radius: 16px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
overflow: hidden;
transition: all 0.3s ease;
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
align-items: center;
cursor: pointer;
transition: all 0.2s ease;
}
.upload-card:active {
transform: scale(0.98);
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.12);
}
.upload-card-content {
padding: 20px;
}
/* 上传信息 */
.upload-info {
margin-bottom: 16px;
}
.upload-title {
font-size: 16px;
font-weight: 600;
color: #1d1f22;
margin-bottom: 4px;
}
.upload-description {
font-size: 14px;
color: #8e9bae;
}
/* 上传区域 */
.upload-area {
/* 上传内容区域 */
.upload-content {
width: 100%;
height: 160px;
border: 2px dashed #e5e7eb;
border-radius: 12px;
aspect-ratio: 3/4;
background: #f6f7f9;
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
background: #fafbfc;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.upload-area:active {
transform: scale(0.98);
}
.upload-area.has-image {
border: 2px solid #3b82f6;
background: transparent;
position: relative;
}
/* 上传占位符 */
@ -62,18 +31,16 @@
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
justify-content: center;
width: 100%;
height: 100%;
gap: 24px;
}
.upload-icon {
font-size: 32px;
color: #8e9bae;
}
.upload-hint {
font-size: 14px;
color: #8e9bae;
font-weight: 500;
width: 25%;
aspect-ratio: 1;
height: auto;
}
/* 上传的图片 */
@ -81,61 +48,13 @@
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 10px;
}
/* 图片覆盖层 */
.image-overlay {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.3s ease;
backdrop-filter: blur(4px);
}
.upload-area.has-image:active .image-overlay {
opacity: 1;
}
.overlay-content {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.change-icon {
font-size: 24px;
color: white;
}
.change-text {
color: white;
/* 标题 */
.upload-title {
font-size: 14px;
font-weight: 500;
}
/* 深色模式适配 */
@media (prefers-color-scheme: dark) {
.upload-card {
background: #2a2a2a;
border: 1px solid #333;
}
.upload-title {
color: #fff;
}
.upload-description {
color: #999;
}
.upload-area {
background: #333;
border-color: #444;
}
color: #666;
text-align: center;
line-height: 1.2;
}

View File

@ -1,43 +1,25 @@
import { View, Image } from '@tarojs/components';
import { useI18n } from '../../../../hooks/useI18n';
import './index.css';
interface UploadCardProps {
imageUrl: string;
title: string;
description: string;
onUpload: () => void;
className?: string;
}
export default function UploadCard({ imageUrl, title, description, onUpload, className = '' }: UploadCardProps) {
const { t } = useI18n();
export default function UploadCard({ imageUrl, title, onUpload, className = '' }: UploadCardProps) {
return (
<View className={`upload-card ${className}`}>
<View className="upload-card-content">
<View className="upload-info">
<View className="upload-title">{title}</View>
<View className="upload-description">{description}</View>
</View>
<View className={`upload-area ${imageUrl ? 'has-image' : ''}`} onClick={onUpload}>
{imageUrl ? (
<>
<Image src={imageUrl} className="upload-image" mode="aspectFill" />
<View className="image-overlay">
<View className="overlay-content">
<View className="change-icon">🔄</View>
<View className="change-text">{t('friendsPhoto.clickToChange')}</View>
</View>
</View>
</>
) : (
<View className="upload-placeholder">
<View className="upload-icon">📷</View>
<View className="upload-hint">{t('friendsPhoto.clickToUpload')}</View>
</View>
)}
</View>
<View className={`upload-card ${className}`} onClick={onUpload}>
<View className="upload-content">
{imageUrl ? (
<Image src={imageUrl} className="upload-image" mode="aspectFill" />
) : (
<View className="upload-placeholder">
<Image src="/assets/icons/photo.png" className="upload-icon" mode="aspectFit" />
<View className="upload-title">{title}</View>
</View>
)}
</View>
</View>
);

View File

@ -1,3 +1,2 @@
export { default as IntroCard } from './IntroCard';
export { default as UploadCard } from './UploadCard';
export { default as SubmitButton } from './SubmitButton';

View File

@ -1,2 +1 @@
export { useUsageGuide } from './useUsageGuide';
export { useImageUpload } from './useImageUpload';

View File

@ -1,31 +0,0 @@
import { useState, useEffect } from 'react';
import Taro from '@tarojs/taro';
import { useI18n } from '../../../hooks/useI18n';
export function useUsageGuide() {
const [showGuide, setShowGuide] = useState(true);
const { t } = useI18n();
useEffect(() => {
if (!showGuide) return;
const timer = setTimeout(() => {
Taro.showModal({
title: t('friendsPhoto.usageTips'),
content: t('friendsPhoto.usageTipsContent'),
confirmText: t('friendsPhoto.iKnow'),
showCancel: false,
success: () => {
setShowGuide(false);
},
});
}, 1000);
return () => clearTimeout(timer);
}, [showGuide, t]);
return {
showGuide,
setShowGuide,
};
}

View File

@ -1,45 +1,20 @@
/* 页面容器 */
.friends-photo {
height: 100vh;
background: #f5f6f8;
background: #f8f9fa;
display: flex;
flex-direction: column;
position: relative;
}
/* 滚动容器 */
.friends-photo-scroll {
flex: 1;
height: 100%;
padding-bottom: 100px; /* 为固定底部按钮留空间 */
}
/* 上传区域 */
.upload-section {
padding: 0 20px;
flex: 1;
padding: 40px 14px 88px;
display: flex;
flex-direction: column;
gap: 16px;
}
/* 底部间距 */
.bottom-spacer {
height: 20px;
}
/* 响应式适配 */
@media (max-width: 375px) {
.upload-section {
padding: 0 16px;
}
}
/* 深色模式适配 */
@media (prefers-color-scheme: dark) {
.friends-photo {
background: #1a1a1a;
}
flex-direction: row;
align-items: center;
gap: 12px;
min-height: 0;
overflow: hidden;
}

View File

@ -1,4 +1,4 @@
import { View, ScrollView } from '@tarojs/components';
import { View } from '@tarojs/components';
import { useState, useEffect } from 'react';
import Taro, { navigateTo, useRouter } from '@tarojs/taro';
import { useServerSdk } from '../../hooks/index';
@ -6,10 +6,10 @@ import { useI18n } from '../../hooks/useI18n';
import { i18nManager } from '../../i18n/manager';
// 导入组件
import { IntroCard, UploadCard, SubmitButton } from './components';
import { UploadCard, SubmitButton } from './components';
// 导入hooks
import { useUsageGuide, useImageUpload } from './hooks';
import { useImageUpload } from './hooks';
import './index.css';
@ -20,8 +20,6 @@ export default function FriendsPhoto() {
const [loading, setLoading] = useState(false);
// 使用自定义hooks
useUsageGuide(); // 自动处理使用指南显示
const { image1, image2, uploadSingleImage } = useImageUpload();
// 设置页面标题
@ -89,29 +87,19 @@ export default function FriendsPhoto() {
return (
<View className="friends-photo">
<ScrollView className="friends-photo-scroll" scrollY enhanced showScrollbar={false} enablePassive bounces>
{/* 顶部介绍卡片 */}
<IntroCard />
{/* 上传区域 */}
<View className="upload-section">
<UploadCard
imageUrl={image1}
title={t('friendsPhoto.firstPhoto')}
description={t('friendsPhoto.selectYourPhoto')}
onUpload={() => uploadSingleImage(1)}
/>
<UploadCard
imageUrl={image2}
title={t('friendsPhoto.secondPhoto')}
description={t('friendsPhoto.selectFriendPhoto')}
onUpload={() => uploadSingleImage(2)}
/>
</View>
{/* 底部间距 */}
<View className="bottom-spacer" />
</ScrollView>
{/* 上传区域 */}
<View className="upload-section">
<UploadCard
imageUrl={image1}
title={t('friendsPhoto.selectYourPhoto')}
onUpload={() => uploadSingleImage(1)}
/>
<UploadCard
imageUrl={image2}
title={t('friendsPhoto.selectFriendPhoto')}
onUpload={() => uploadSingleImage(2)}
/>
</View>
{/* 固定底部按钮 */}
<SubmitButton disabled={!image1 || !image2} loading={loading} onSubmit={handleSubmit} />

View File

@ -9,4 +9,4 @@ export class H5Payment extends Payment {
}
throw new Error(`payment error: ${response}`)
}
}
}