ai-clone/frontend-ai/App.vue

372 lines
11 KiB
Vue
Raw Normal View History

2026-03-05 14:29:21 +08:00
<script>
import { fetchServicePrices, showPaymentModal, SERVICE_TYPES } from '@/utils/payment.js';
import { API_BASE, API_ENDPOINTS } from '@/config/api.js';
import { cleanExpiredCache, cleanOldestCache } from '@/utils/videoCacheManager.js';
import PermissionManager from '@/utils/permission.js';
export default {
onLaunch(options) {
console.log('App Launch');
// #ifdef MP-WEIXIN
this.setupMpWeixinAuthGuard();
// #endif
// 清理过期的视频缓存
this.cleanVideoCache();
// 预加载应用配置
this.loadAppConfig();
// 预加载服务价格
this.loadServicePrices();
// 全局拦截:遇到 402需要付费/额度不足)时弹出支付弹窗
this.setupPaymentInterceptor();
// 获取小程序用户标识(用于支付)
this.getUserIdentity();
// App环境预请求麦克风权限
this.requestPermissions();
},
onShow() {
console.log('App Show');
},
onHide() {
console.log('App Hide');
},
methods: {
// #ifdef MP-WEIXIN
setupMpWeixinAuthGuard() {
const whiteList = [
'/pages/login/login',
'/pages/settings/privacy-policy',
'/pages/settings/user-agreement',
'/pages/index/index'
];
const protectedList = [
'/pages/history/history',
'/pages/revival/revival-history',
'/pages/revival/revival',
'/pages/phone-call/phone-call',
'/pages/video-call/video-call',
'/pages/video-call-new/video-call-new',
'/pages/video-gen/video-gen',
'/pages/upload-audio/upload-audio',
'/pages/my-works/my-works',
'/pages/profile/edit-profile',
'/pages/settings/settings'
];
const isLoggedIn = () => {
const token = uni.getStorageSync('token');
const userId = uni.getStorageSync('userId');
return !!(token && userId);
};
const normalizeUrlPath = (url) => {
if (!url) return '';
const qIndex = url.indexOf('?');
const path = qIndex >= 0 ? url.slice(0, qIndex) : url;
return path.startsWith('/') ? path : `/${path}`;
};
const buildLoginUrl = (originalUrl) => {
const redirect = originalUrl || '';
return `/pages/login/login?redirect=${encodeURIComponent(redirect)}`;
};
const shouldBlock = (url) => {
const path = normalizeUrlPath(url);
if (!path) return false;
if (whiteList.includes(path)) return false;
if (!protectedList.includes(path)) return false;
return !isLoggedIn();
};
const wrap = (methodName) => {
const original = uni[methodName];
if (typeof original !== 'function') return;
uni[methodName] = (options = {}) => {
try {
const url = typeof options === 'string' ? options : options.url;
if (shouldBlock(url)) {
const loginUrl = buildLoginUrl(url);
return uni.reLaunch({ url: loginUrl });
}
} catch (e) {
// ignore
}
return original.call(uni, options);
};
};
wrap('navigateTo');
wrap('redirectTo');
wrap('reLaunch');
wrap('switchTab');
},
// #endif
// 清理视频缓存
cleanVideoCache() {
try {
const expiredCount = cleanExpiredCache();
const oldCount = cleanOldestCache();
if (expiredCount > 0 || oldCount > 0) {
console.log(`[App] 视频缓存清理完成:过期${expiredCount}个,旧缓存${oldCount}`);
}
} catch (error) {
console.error('[App] 清理视频缓存失败:', error);
}
},
// 加载应用配置
async loadAppConfig() {
try {
// 临时添加:强制清除缓存,测试完成后可删除此行
uni.removeStorageSync('appConfig');
console.log('[App] 缓存已清除,重新加载配置');
const res = await uni.request({
url: `${API_BASE}${API_ENDPOINTS.config.getAppConfig}`,
method: 'GET',
header: {
'Content-Type': 'application/json'
// 不添加认证头,因为这是公开接口
}
});
// 兼容不同平台的返回格式:可能是 [error, res] 或直接是 res
const response = Array.isArray(res) ? res[1] : res;
if (response && response.data && response.data.success && response.data.data) {
const config = response.data.data;
// 保存到本地存储
uni.setStorageSync('appConfig', config);
console.log('[App] 应用配置已加载:', config);
}
} catch (error) {
console.error('[App] 加载应用配置失败:', error);
}
},
async loadServicePrices() {
try {
await fetchServicePrices();
console.log('[App] 服务价格已加载');
} catch (error) {
console.error('[App] 加载服务价格失败:', error);
}
},
// 获取小程序用户标识(用于支付)
async getUserIdentity() {
// #ifdef MP-WEIXIN
// 微信小程序获取openid
const existingOpenid = uni.getStorageSync('wx_openid');
if (existingOpenid) {
console.log('[App] 已有微信openid:', existingOpenid);
return;
}
try {
const loginRes = await uni.login();
if (loginRes[1] && loginRes[1].code) {
const code = loginRes[1].code;
console.log('[App] 微信登录成功code:', code);
// 发送code到后端换取openid
// TODO: 需要后端实现 /api/wechat/login 接口
// const res = await uni.request({
// url: `${API_BASE}/api/wechat/login`,
// method: 'POST',
// data: { code }
// });
// if (res[1] && res[1].data && res[1].data.openid) {
// uni.setStorageSync('wx_openid', res[1].data.openid);
// console.log('[App] 微信openid已保存');
// }
// 临时方案使用模拟openid仅用于测试
console.warn('[App] 使用模拟openid生产环境需要实现后端接口');
uni.setStorageSync('wx_openid', 'test_openid_' + Date.now());
}
} catch (error) {
console.error('[App] 获取微信openid失败:', error);
}
// #endif
// #ifdef MP-ALIPAY
// 支付宝小程序获取userId
const existingUserId = uni.getStorageSync('alipay_user_id');
if (existingUserId) {
console.log('[App] 已有支付宝userId:', existingUserId);
return;
}
try {
my.getAuthCode({
scopes: 'auth_user',
success: async (res) => {
const authCode = res.authCode;
console.log('[App] 支付宝授权成功authCode:', authCode);
// 发送authCode到后端换取userId
// TODO: 需要后端实现 /api/alipay/login 接口
// const loginRes = await uni.request({
// url: `${API_BASE}/api/alipay/login`,
// method: 'POST',
// data: { authCode }
// });
// if (loginRes[1] && loginRes[1].data && loginRes[1].data.userId) {
// uni.setStorageSync('alipay_user_id', loginRes[1].data.userId);
// console.log('[App] 支付宝userId已保存');
// }
// 临时方案使用模拟userId仅用于测试
console.warn('[App] 使用模拟userId生产环境需要实现后端接口');
uni.setStorageSync('alipay_user_id', '2088' + Date.now());
},
fail: (error) => {
console.error('[App] 获取支付宝userId失败:', error);
}
});
} catch (error) {
console.error('[App] 支付宝授权失败:', error);
}
// #endif
},
setupPaymentInterceptor() {
const getServiceTypeFromUrl = (url) => {
const u = String(url || '');
if (u.includes('/api/photo-revival/')) return SERVICE_TYPES.PHOTO_REVIVAL.type;
if (u.includes('/api/voice/')) return SERVICE_TYPES.VOICE_CLONE.type;
if (u.includes('/api/tts/')) return SERVICE_TYPES.TTS_SYNTHESIS.type;
if (u.includes('/api/video-call')) return SERVICE_TYPES.VIDEO_CALL.type;
if (u.includes('/api/conversation/')) return SERVICE_TYPES.CONVERSATION.type;
return '';
};
const ensureLogin = () => {
const token = uni.getStorageSync('token') || '';
const userId = uni.getStorageSync('userId') || '';
return !!(token && userId);
};
const showPay = (serviceType) => {
try {
if (!ensureLogin()) {
uni.showModal({
title: '提示',
content: '请先登录后再继续',
confirmText: '去登录',
cancelText: '取消',
success: (m) => {
if (m.confirm) {
uni.reLaunch({ url: '/pages/login/login' });
}
}
});
return;
}
const st = serviceType || SERVICE_TYPES.PHOTO_REVIVAL.type;
// 重要:支付弹窗需要页面承载(页面里渲染了 <PaymentModal/>)。
// 全局拦截在 App 实例上直接 show 会导致小程序端“无UI”。
let hostVm = this;
try {
const pages = (typeof getCurrentPages === 'function') ? getCurrentPages() : [];
const top = pages && pages.length ? pages[pages.length - 1] : null;
if (top && top.$vm) {
hostVm = top.$vm;
}
} catch (e) {
// ignore
}
showPaymentModal(hostVm, st);
} catch (e) {
console.error('[Payment] 402拦截弹窗失败:', e);
}
};
const handle402 = (options, res) => {
try {
const url = (options && options.url) ? options.url : '';
let parsed = null;
try {
parsed = res && typeof res.data === 'string' ? JSON.parse(res.data) : (res ? res.data : null);
} catch (e) {
parsed = null;
}
const backendServiceType = parsed && (parsed.serviceType || parsed.service_type);
const mapped = backendServiceType === 'CREATE_VOICE' ? SERVICE_TYPES.VOICE_CLONE.type
: backendServiceType === 'PHOTO_REVIVAL' ? SERVICE_TYPES.PHOTO_REVIVAL.type
: backendServiceType === 'VOLCENGINE_VIDEO' ? (SERVICE_TYPES.VOLCENGINE_VIDEO ? SERVICE_TYPES.VOLCENGINE_VIDEO.type : '')
: backendServiceType === 'SYNTHESIZE' ? SERVICE_TYPES.TTS_SYNTHESIS.type
: backendServiceType === 'VIDEO_CALL' ? SERVICE_TYPES.VIDEO_CALL.type
: backendServiceType === 'AI_CALL' ? SERVICE_TYPES.CONVERSATION.type
: '';
const serviceType = mapped || getServiceTypeFromUrl(url);
showPay(serviceType);
} catch (e) {
console.error('[Payment] 402拦截处理失败:', e);
}
};
const intercept = (apiName) => {
let lastOptions = null;
uni.addInterceptor(apiName, {
invoke(options) {
lastOptions = options;
return options;
},
returnValue(res) {
try {
// Promise/async 形式:在 then 里检查 402
if (res && typeof res.then === 'function') {
return res.then((out) => {
try {
const r = Array.isArray(out) ? out[1] : out;
if (r && r.statusCode === 402) {
handle402(lastOptions, r);
}
} catch (e) {
// ignore
}
return out;
});
}
// callback 形式
const r = Array.isArray(res) ? res[1] : res;
if (r && r.statusCode === 402) {
const opt = Array.isArray(res) ? res[0] : lastOptions;
handle402(opt, r);
}
} catch (e) {
// ignore
}
return res;
}
});
};
intercept('request');
intercept('uploadFile');
},
// 请求App权限
async requestPermissions() {
// #ifdef APP-PLUS
try {
console.log('[App] 开始请求App权限');
await PermissionManager.requestPermissionsOnLaunch();
} catch (error) {
console.error('[App] 请求权限失败:', error);
}
// #endif
}
}
}
</script>
<style>
/*每个页面公共css */
@import "@/common/delete-icon.css";
</style>