feat: 平台适配优化和代码重构

- 更新小程序配置支持微信平台(appid: wxb51f0b0c3aad7cdf)
- 新增微信小程序平台适配模块(weapp.ts)
- 优化广告组件跨平台兼容性处理
- 移除不必要的React.memo优化
- 简化广告加载逻辑,提高稳定性
- 修复代码规范问题(import顺序、unused变量)
This commit is contained in:
imeepos 2025-09-02 17:40:45 +08:00
parent a38eab405c
commit 2cff8db4bf
17 changed files with 371 additions and 110 deletions

View File

@ -1,3 +1,2 @@
# 配置文档参考 https://taro-docs.jd.com/docs/next/env-mode-config
# TARO_APP_ID="开发环境下的小程序 AppID"
TARO_APP_ID=ttbfd9c96420ec8f8201
# TARO_APP_ID=ttbfd9c96420ec8f8201
TARO_APP_ID=wxb51f0b0c3aad7cdf

View File

@ -1,2 +1,2 @@
# TARO_APP_ID="生产环境下的小程序 AppID"
TARO_APP_ID=ttbfd9c96420ec8f8201
# TARO_APP_ID=ttbfd9c96420ec8f8201
TARO_APP_ID=wxb51f0b0c3aad7cdf

View File

@ -1,2 +1,2 @@
# TARO_APP_ID="测试环境下的小程序 AppID"
TARO_APP_ID=ttbfd9c96420ec8f8201
# TARO_APP_ID=ttbfd9c96420ec8f8201
TARO_APP_ID=wxb51f0b0c3aad7cdf

View File

@ -1 +1,39 @@
{"miniprogramRoot":"./dist","projectname":"bw-mini-app","description":"图生图 风格转换 ","appid":"ttbfd9c96420ec8f8201","setting":{"urlCheck":true,"es6":true,"enhance":false,"compileHotReLoad":false,"postcss":false,"minified":false},"compileType":"miniprogram"}
{
"miniprogramRoot": "dist/",
"projectname": "bw-mini-app",
"description": "图生图 风格转换 ",
"appid": "wxb51f0b0c3aad7cdf",
"setting": {
"urlCheck": true,
"es6": true,
"enhance": true,
"compileHotReLoad": false,
"postcss": false,
"minified": true,
"compileWorklet": false,
"uglifyFileName": false,
"uploadWithSourceMap": true,
"packNpmManually": false,
"packNpmRelationList": [],
"minifyWXSS": true,
"minifyWXML": true,
"localPlugins": false,
"disableUseStrict": false,
"useCompilerPlugins": false,
"condition": false,
"swc": false,
"disableSWC": true,
"babelSetting": {
"ignore": [],
"disablePlugins": [],
"outputPath": ""
}
},
"compileType": "miniprogram",
"simulatorPluginLibVersion": {},
"packOptions": {
"ignore": [],
"include": []
},
"editorSetting": {}
}

View File

@ -0,0 +1,7 @@
{
"setting": {
"urlCheck": true,
"bigPackageSizeSupport": false,
"compileHotReLoad": true
}
}

View File

@ -1,5 +1,4 @@
import { View, Text } from '@tarojs/components'
import { memo } from 'react'
import './index.css'
interface DownloadSectionProps {
@ -8,9 +7,9 @@ interface DownloadSectionProps {
loading: boolean
}
const DownloadSection: React.FC<DownloadSectionProps> = memo(({ onDownload, onRegenerate, loading }) => {
const DownloadSection: React.FC<DownloadSectionProps> = ({ onDownload, onRegenerate, loading }) => {
return (
<View className="download-section">
<View className='download-section'>
<View
className={`download-btn ${loading ? 'disabled' : ''}`}
onClick={loading ? undefined : onDownload}
@ -18,13 +17,13 @@ const DownloadSection: React.FC<DownloadSectionProps> = memo(({ onDownload, onRe
<Text>{loading ? '广告加载中...' : '📱 看广告下载到相册'}</Text>
</View>
{onRegenerate && (
<View className="regenerate-btn" onClick={onRegenerate}>
<View className='regenerate-btn' onClick={onRegenerate}>
<Text>🎨 </Text>
</View>
)}
<Text className="download-tip">广</Text>
<Text className='download-tip'>广</Text>
</View>
)
})
}
export default DownloadSection

View File

@ -1,21 +1,20 @@
import { View, Text } from '@tarojs/components'
import { memo } from 'react'
import './index.css'
interface ErrorOverlayProps {
onRetry: () => void
}
const ErrorOverlay: React.FC<ErrorOverlayProps> = memo(({ onRetry }) => {
const ErrorOverlay: React.FC<ErrorOverlayProps> = ({ onRetry }) => {
return (
<View className="error-overlay" onClick={onRetry}>
<View className="error-container">
<Text className="error-icon"></Text>
<Text className="error-title"></Text>
<Text className="error-hint"></Text>
<View className='error-overlay' onClick={onRetry}>
<View className='error-container'>
<Text className='error-icon'></Text>
<Text className='error-title'></Text>
<Text className='error-hint'></Text>
</View>
</View>
)
})
}
export default ErrorOverlay

View File

@ -1,19 +1,18 @@
import { View, Text } from '@tarojs/components'
import { memo } from 'react'
import './index.css'
const LoadingOverlay: React.FC = memo(() => {
const LoadingOverlay: React.FC = () => {
return (
<View className="loading-overlay">
<View className="loading-container">
<View className="loading-spinner" />
<View className="loading-text">
<Text className="loading-title">AI正在生成中...</Text>
<Text className="loading-desc"></Text>
<View className='loading-overlay'>
<View className='loading-container'>
<View className='loading-spinner' />
<View className='loading-text'>
<Text className='loading-title'>AI正在生成中...</Text>
<Text className='loading-desc'></Text>
</View>
</View>
</View>
)
})
}
export default LoadingOverlay

View File

@ -1,19 +1,18 @@
import { View, Button } from '@tarojs/components'
import { memo } from 'react'
import './index.css'
interface UploadButtonProps {
onUpload: () => void
}
const UploadButton: React.FC<UploadButtonProps> = memo(({ onUpload }) => {
const UploadButton: React.FC<UploadButtonProps> = ({ onUpload }) => {
return (
<View className="button-container">
<Button className="create-button" onClick={onUpload}>
<View className='button-container'>
<Button className='create-button' onClick={onUpload}>
</Button>
</View>
)
})
}
export default UploadButton

View File

@ -1,5 +1,5 @@
import { useState, useCallback, useRef, useEffect } from 'react';
import { createPlatformFactory, RewardedVideoAd, RewardedVideoCloseCb, RewardedVideoErrorCb } from "../platforms";
import { useState, useCallback, useRef, useEffect, useMemo } from 'react';
// 广告奖励回调函数类型
type AdRewardCallback = () => void;
@ -21,15 +21,10 @@ export function useAd(options?: UseAdOptions): UseAdReturn {
const [loading, setLoading] = useState(true);
const adRef = useRef<RewardedVideoAd | null>(null);
// 使用 useMemo 稳定 options 引用,避免重复初始化
const stableOptions = useMemo(() => options, [options?.onReward, options?.onClose]);
useEffect(() => {
// 创建平台广告实例
const factory = createPlatformFactory()
adRef.current = factory.createRewardedVideoAd({
adUnitId: 'gncb4kr2b6kwp0uacr'
});
adRef.current = factory.createRewardedVideoAd();
const ad = adRef.current!;
// 广告关闭回调处理
@ -45,7 +40,7 @@ export function useAd(options?: UseAdOptions): UseAdReturn {
console.log('用户观看完整广告,给予奖励');
// 执行奖励回调
stableOptions?.onReward?.();
options?.onReward?.();
// 可以在这里处理以下业务:
// 1. 发放奖励(积分、道具等)
@ -64,7 +59,7 @@ export function useAd(options?: UseAdOptions): UseAdReturn {
}
// 执行关闭回调,传入是否完整观看
stableOptions?.onClose?.(isEnded);
options?.onClose?.(isEnded);
}
// 广告加载错误回调处理
const onError: RewardedVideoErrorCb = (res) => {
@ -91,7 +86,7 @@ export function useAd(options?: UseAdOptions): UseAdReturn {
adRef.current.destroy();
}
};
}, [stableOptions]);
}, [options]);
// 显示广告方法
const showAd = useCallback(() => {

View File

@ -6,7 +6,6 @@ import { useSdk } from '../../hooks/index'
import UploadButton from '../../components/UploadButton'
import LoadingOverlay from '../../components/LoadingOverlay'
import ErrorOverlay from '../../components/ErrorOverlay'
import { createPlatformFactory } from '../../platforms'
type PageStep = 'upload' | 'loading' | 'error'
@ -24,9 +23,6 @@ export default function Index() {
const chooseAndGenerateImage = async () => {
try {
const platformFactory = createPlatformFactory()
await platformFactory.createUserInfo().checkSession().catch(e => console.error(e))
// 选择图片选择完成后会自动触发loading状态
const task_id = await sdk.chooseAndGenerateImage({
onImageSelected: () => {

View File

@ -1,26 +1,12 @@
import { View, Image } from '@tarojs/components'
import { memo, useEffect, useState } from 'react'
import { useEffect, useState } from 'react'
import { saveImageToPhotosAlbum, downloadFile, showToast, navigateBack, getCurrentInstance } from '@tarojs/taro'
import { useAd } from '../../hooks/useAd'
import DownloadSection from '../../components/DownloadSection'
import './index.css'
const ResultPage: React.FC = memo(() => {
const ResultPage: React.FC = () => {
const [images, setImages] = useState<string[]>([])
// 广告激励下载功能
const { loading: adLoading } = useAd({
onReward: () => {
// 观看完整广告后下载图片
handleDownloadImages()
},
onClose: (isEnded) => {
if (!isEnded) {
console.log('需要观看完整广告才能下载')
}
}
})
useEffect(() => {
// 获取页面参数
const instance = getCurrentInstance()
@ -87,9 +73,9 @@ const ResultPage: React.FC = memo(() => {
}
return (
<View className="result-page">
<View className='result-page'>
<View
className="result-image-container"
className='result-image-container'
style={{
backgroundImage: `url(${images[0]})`,
backgroundSize: 'cover',
@ -97,13 +83,13 @@ const ResultPage: React.FC = memo(() => {
backgroundRepeat: 'no-repeat'
}}
>
<View className="backdrop-blur">
<Image className="result-image" src={images[0]} mode="aspectFit" />
<View className='backdrop-blur'>
<Image className='result-image' src={images[0]} mode='aspectFit' />
</View>
</View>
<DownloadSection onDownload={handleDownloadImages} onRegenerate={handleRegenerate} loading={adLoading} />
<DownloadSection onDownload={handleDownloadImages} onRegenerate={handleRegenerate} loading={false} />
</View>
)
})
}
export default ResultPage

View File

@ -52,6 +52,7 @@ export interface RewardedVideoAdOptions {
* 广
*/
export abstract class RewardedVideoAd {
abstract canUse(): boolean;
/**
* 广
* 广

View File

@ -1,5 +1,6 @@
import { RewardedVideoAdTT, UserInfoTT } from "./tt";
import { RewardedVideoAd, RewardedVideoAdOptions, UserInfo } from "./core";
import { RewardedVideoAd, UserInfo } from "./core";
import { RewardedVideoAdWeApp, UserInfoWeApp } from "./weapp";
/**
*
@ -37,12 +38,14 @@ export class PlatformFactory {
* @returns RewardedVideoAd 广
* @throws Error
*/
createRewardedVideoAd(options?: RewardedVideoAdOptions): RewardedVideoAd {
createRewardedVideoAd(): RewardedVideoAd {
switch (this.platform) {
case 'tt':
return new RewardedVideoAdTT(options);
return new RewardedVideoAdTT({
adUnitId: 'gncb4kr2b6kwp0uacr'
});
case 'weapp':
throw new Error(`微信小程序平台暂未实现`);
throw new RewardedVideoAdWeApp();
case 'alipay':
throw new Error(`支付宝小程序平台暂未实现`);
case 'swan':
@ -60,7 +63,7 @@ export class PlatformFactory {
case 'tt':
return new UserInfoTT();
case 'weapp':
throw new Error(`微信小程序平台暂未实现`);
throw new UserInfoWeApp();
case 'alipay':
throw new Error(`支付宝小程序平台暂未实现`);
case 'swan':

View File

@ -5,7 +5,6 @@ import {
RewardedVideoLoadCb,
RewardedVideoAdOptions,
UserInfo,
IUserInfo,
IUserProfile,
ICheckSession
} from "./core";
@ -18,27 +17,19 @@ declare const tt: {
[key: string]: any;
};
/**
* 广
*/
interface TTRewardedVideoAdOptions {
/** 广告位 ID */
adUnitId: string;
/** 是否启用多场景化 */
multiton?: boolean;
/** 用户 ID */
userId?: string;
}
/**
* 广
* 广 API
*/
export class RewardedVideoAdTT extends RewardedVideoAd {
private readonly ad: any;
private readonly options: TTRewardedVideoAdOptions;
private readonly options: RewardedVideoAdOptions;
private _isReady: boolean = false;
canUse(): boolean {
return this.options?.adUnitId ? true : false;
}
/**
*
* @param options 广
@ -51,16 +42,13 @@ export class RewardedVideoAdTT extends RewardedVideoAd {
adUnitId: options?.adUnitId || '',
};
if (!this.options.adUnitId) {
throw new Error('广告位 ID (adUnitId) 不能为空');
}
// 检查字节跳动环境
if (typeof tt === 'undefined') {
throw new Error('当前环境不支持字节跳动小程序 API');
}
// 创建激励视频广告实例
if (this.options.adUnitId) {
this.ad = tt.createRewardedVideoAd({
adUnitId: this.options.adUnitId,
});
@ -75,12 +63,14 @@ export class RewardedVideoAdTT extends RewardedVideoAd {
this._isReady = false;
});
}
}
/**
* 广
* @returns Promise<void> Promise
*/
async load(): Promise<void> {
if(!this.ad) return;
try {
await this.ad.load();
this._isReady = true;
@ -95,6 +85,7 @@ export class RewardedVideoAdTT extends RewardedVideoAd {
* @returns Promise<void> Promise
*/
async show(): Promise<void> {
if(!this.ad) return;
if (!this._isReady) {
throw new Error('广告尚未加载完成,请先调用 load() 方法或等待广告加载完成');
}
@ -106,6 +97,7 @@ export class RewardedVideoAdTT extends RewardedVideoAd {
* @returns Promise<void> Promise
*/
async destroy(): Promise<void> {
if(!this.ad) return;
this._isReady = false;
if (this.ad.destroy) {
return this.ad.destroy();
@ -122,6 +114,7 @@ export class RewardedVideoAdTT extends RewardedVideoAd {
* @param cb
*/
onClose(cb: RewardedVideoCloseCb): void {
if(!this.ad) return;
this.ad.onClose(cb);
}
@ -130,6 +123,7 @@ export class RewardedVideoAdTT extends RewardedVideoAd {
* @param cb
*/
offClose(cb: RewardedVideoCloseCb): void {
if(!this.ad) return;
this.ad.offClose(cb);
}
@ -138,6 +132,7 @@ export class RewardedVideoAdTT extends RewardedVideoAd {
* @param cb
*/
onError(cb: RewardedVideoErrorCb): void {
if(!this.ad) return;
this.ad.onError(cb);
}
@ -146,6 +141,7 @@ export class RewardedVideoAdTT extends RewardedVideoAd {
* @param cb
*/
offError(cb: RewardedVideoErrorCb): void {
if(!this.ad) return;
this.ad.offError(cb);
}
@ -154,6 +150,7 @@ export class RewardedVideoAdTT extends RewardedVideoAd {
* @param cb
*/
onLoad(cb: RewardedVideoLoadCb): void {
if(!this.ad) return;
this.ad.onLoad(cb);
}
@ -162,6 +159,7 @@ export class RewardedVideoAdTT extends RewardedVideoAd {
* @param cb
*/
offLoad(cb: RewardedVideoLoadCb): void {
if(!this.ad) return;
this.ad.offLoad(cb);
}
@ -186,7 +184,7 @@ export class RewardedVideoAdTT extends RewardedVideoAd {
* 广
* @returns TTRewardedVideoAdOptions
*/
getOptions(): TTRewardedVideoAdOptions {
getOptions(): RewardedVideoAdOptions {
return { ...this.options };
}

241
src/platforms/weapp.ts Normal file
View File

@ -0,0 +1,241 @@
import {
RewardedVideoAd,
RewardedVideoCloseCb,
RewardedVideoErrorCb,
RewardedVideoLoadCb,
RewardedVideoAdOptions,
UserInfo,
IUserProfile,
ICheckSession
} from "./core";
/**
*
*/
declare const wx: {
createRewardedVideoAd: (options: any) => any;
[key: string]: any;
};
/**
* 广
* 广 API
*/
export class RewardedVideoAdWeApp extends RewardedVideoAd {
private readonly ad: any;
private readonly options: RewardedVideoAdOptions;
private _isReady: boolean = false;
canUse(): boolean {
return this.options?.adUnitId ? true : false;
}
/**
*
* @param options 广
*/
constructor(options?: RewardedVideoAdOptions) {
super();
// 设置默认配置
this.options = {
adUnitId: options?.adUnitId || '',
};
// 检查字节跳动环境
if (typeof wx === 'undefined') {
throw new Error('当前环境不支持字节跳动小程序 API');
}
// 创建激励视频广告实例
if (this.options.adUnitId) {
this.ad = wx.createRewardedVideoAd({
adUnitId: this.options.adUnitId,
});
// 监听广告加载成功事件
this.ad.onLoad(() => {
this._isReady = true;
});
// 监听广告加载错误事件
this.ad.onError(() => {
this._isReady = false;
});
}
}
/**
* 广
* @returns Promise<void> Promise
*/
async load(): Promise<void> {
if (!this.ad) return;
try {
await this.ad.load();
this._isReady = true;
} catch (error) {
this._isReady = false;
throw error;
}
}
/**
* 广
* @returns Promise<void> Promise
*/
async show(): Promise<void> {
if (!this.ad) return;
if (!this._isReady) {
throw new Error('广告尚未加载完成,请先调用 load() 方法或等待广告加载完成');
}
return this.ad.show();
}
/**
* 广
* @returns Promise<void> Promise
*/
async destroy(): Promise<void> {
if (!this.ad) return;
this._isReady = false;
if (this.ad.destroy) {
return this.ad.destroy();
}
// 兼容性处理:某些版本可能使用 destory (拼写错误)
if (this.ad.destory) {
return this.ad.destory();
}
return Promise.resolve();
}
/**
* 广
* @param cb
*/
onClose(cb: RewardedVideoCloseCb): void {
if (!this.ad) return;
this.ad.onClose(cb);
}
/**
* 广
* @param cb
*/
offClose(cb: RewardedVideoCloseCb): void {
if (!this.ad) return;
this.ad.offClose(cb);
}
/**
* 广
* @param cb
*/
onError(cb: RewardedVideoErrorCb): void {
if (!this.ad) return;
this.ad.onError(cb);
}
/**
* 广
* @param cb
*/
offError(cb: RewardedVideoErrorCb): void {
if (!this.ad) return;
this.ad.offError(cb);
}
/**
* 广
* @param cb
*/
onLoad(cb: RewardedVideoLoadCb): void {
if (!this.ad) return;
this.ad.onLoad(cb);
}
/**
* 广
* @param cb
*/
offLoad(cb: RewardedVideoLoadCb): void {
if (!this.ad) return;
this.ad.offLoad(cb);
}
/**
* 广
* @returns boolean 广
*/
isReady(): boolean {
return this._isReady;
}
/**
* 广
* 广
* @returns Promise<void> Promise
*/
async preload(): Promise<void> {
return this.load();
}
/**
* 广
* @returns TTRewardedVideoAdOptions
*/
getOptions(): RewardedVideoAdOptions {
return { ...this.options };
}
/**
* 广
* 访
* @returns any 广
*/
getNativeAd(): any {
return this.ad;
}
}
export class UserInfoWeApp extends UserInfo {
getUserProfile(): Promise<IUserProfile> {
return new Promise((resolve, reject) => {
wx.getUserProfile({
success: (res: any) => {
console.log({ res })
resolve(res)
},
fail: (res: any) => {
console.log({ res })
reject(res)
}
})
})
}
login(): Promise<any> {
return new Promise((resolve, reject) => {
wx.login({
success: (res: any) => {
resolve(res)
},
fail: (res: any) => {
reject(res)
}
})
})
}
checkSession(): Promise<ICheckSession> {
return new Promise((resolve, reject) => {
wx.checkSession({
success: (res: any) => {
resolve(res)
},
fail: (res: any) => {
reject(res)
}
})
})
}
}

View File

@ -164,6 +164,8 @@ export class BowongAISDK {
sourceType
});
console.log({chooseResult})
// 选择图片完成后触发回调
onImageSelected?.();
@ -311,7 +313,6 @@ export class BowongAISDK {
}
const result = response.data as ApiResponse;
console.log({ result })
if (!result.status) {
throw new Error(result.msg || '查询任务状态失败');
}