import { router } from '@kit.ArkUI'; import { AppUtil, ToastUtil, PreferencesUtil, StrUtil } from '@pura/harmony-utils'; import { BreakpointTypeEnum } from '../common/util/BreakpointSystem'; import * as wxopensdk from '@tencent/wechat_open_sdk'; import { OnWXResp, WXApi, WXEventHandler } from '../common/util/WXApiWrap'; import { bundleManager, common, ConfigurationConstant } from '@kit.AbilityKit'; import { ErrCode } from '@tencent/wechat_open_sdk'; import { http } from '@kit.NetworkKit'; import { authentication } from '@kit.AccountKit'; import { hilog } from '@kit.PerformanceAnalysisKit'; import { BusinessError } from '@kit.BasicServicesKit'; import { util } from '@kit.ArkTS'; import { DialogHelper } from '@pura/harmony-dialog'; import { Pay } from '@cashier_alipay/cashiersdk'; import { OrderInfoUtil } from '../alipay/OrderInfoUtil'; import { RandomUtil, Base64Util } from '@pura/harmony-utils'; import { CommonConstants } from '../common/constants/CommonConstants'; import { PayReq } from '@tencent/wechat_open_sdk'; import { cryptoFramework } from '@kit.CryptoArchitectureKit'; import json from '@ohos.util.json'; import UserUtil, { VipFeature, VipPlanApi, VipPlanListApiResponse, WeChatPrepayId } from '../common/util/UserUtil'; import { Utility } from '../common/util/Utility'; // 微信支付相关工具方法 async function isHasWX(): Promise { try { let link = 'weixin://'; let data = bundleManager.canOpenLink(link); if (data) { return true; } else { ToastUtil.showToast('微信未安装'); return false; } } catch (err) { let message = (err as BusinessError).message; console.error('canOpenLink failed: %{public}s', message); return false; } } async function createWeChatOrder(app_id: string, mch_id: string, description: string, amount: number, out_trade_no_pre: string, uid: number, plan_id: string): Promise { let requestUrl = CommonConstants.WX_PAY_API + '?app_id=' + encodeURIComponent(app_id.trim()) + '&mch_id=' + encodeURIComponent(mch_id.trim()) + '&description=' + encodeURIComponent(description.trim()) + '&amount=' + encodeURIComponent(amount) + '&uid=' + encodeURIComponent(uid) + '&plan_id=' + encodeURIComponent(plan_id) + '&out_trade_no_pre=' + encodeURIComponent(out_trade_no_pre); const httpRequest: http.HttpRequest = http.createHttp(); const options: http.HttpRequestOptions = { method: http.RequestMethod.GET, readTimeout: 6000, connectTimeout: 6000, }; try { const response: http.HttpResponse = await httpRequest.request(requestUrl, options); if (response.responseCode === 200) { let responseData: WeChatPrepayId; if (typeof response.result === 'string') { responseData = JSON.parse(response.result) as WeChatPrepayId; } else if (response.result instanceof Object) { responseData = response.result as WeChatPrepayId; } else { throw new Error('Unexpected response format'); } const prepayId: string = responseData.prepay_id; return prepayId; } else { ToastUtil.showToast('微信下单失败'); return ''; } } catch (error) { ToastUtil.showToast('微信下单异常'); return ''; } } function constructSignatureString(appId: string, timestamp: string, nonceStr: string, prepayId: string): string { return `${appId}\n${timestamp}\n${nonceStr}\n${prepayId}\n`; } async function rsaSign2048(message: string): Promise { try { let keyGen = cryptoFramework.createAsyKeyGenerator("RSA2048") let key = keyGen.convertPemKeySync(null, CommonConstants.WX_PAY_RSA_PRIVATE_KEY) let sign = cryptoFramework.createSign('RSA2048|PKCS1|SHA256'); let msgBlob: cryptoFramework.DataBlob = {data: StrUtil.strToUint8Array(message)} sign.initSync(key.priKey) let signature = await sign.sign(msgBlob) return signature.data } catch (error) { console.error(error, `onecold error code: ${error.code}`) return new Uint8Array() } } // 1. 工具函数:将 #RRGGBB 字符串和透明度转为 'rgba(r,g,b,a)' 字符串,深色模式下可返回深色变体 function themeColorWithAlpha(themeColor: string, alpha: number, isDarkMode: boolean = false): string { if (isDarkMode) { // 深色模式下返回更深的灰色或半透明黑色 return `rgba(34,34,34,${alpha + 0.2 > 1 ? 1 : alpha + 0.2})`; } const color = themeColor.replace('#', ''); const r = parseInt(color.substring(0, 2), 16); const g = parseInt(color.substring(2, 4), 16); const b = parseInt(color.substring(4, 6), 16); return `rgba(${r},${g},${b},${alpha})`; } @Component @Entry export struct UserCenter { @State userName: string = '未登录用户'; @State userId: number = 0; @State userAvatar: Resource = $r('app.media.icon_person2'); @State userAvatarUrl: string = ''; @State isVip: boolean = false; @State vipExpire: string = ''; @State isLogin: boolean = false; @State hasActiveSubscription: boolean = false; @State subscriptionName: string = ''; @State subscriptionEndDate: string = ''; private wxApi = WXApi private wxEventHandler = WXEventHandler @StorageProp('currentBreakpoint') currentBreakpoint: string = BreakpointTypeEnum.MD; @State vipFeatures: VipFeature[] = [ { icon: $r('app.media.wusunyinzhi'), title: '无损音质', description: '专享无损音乐支持,让每个音符都完美呈现', isVipOnly: true,hasCome:true }, // { icon: $r('app.media.lrc'), title: '歌词下载', description: '支持歌词API在线下载LRC歌词,实现完美同步', isVipOnly: true }, // { icon: $r('app.media.cover'), title: '封面API', description: '支持设置自动下载封面', isVipOnly: true }, { icon: $r('app.media.theme'), title: '主题定制', description: '自定义播放器主题,打造独一无二的个性风格', isVipOnly: true,hasCome:true }, { icon: $r('app.media.no_ad'), title: '无广告', description: '清爽界面,无广告播放器', isVipOnly: true ,hasCome:true}, { icon: $r('app.media.skip'), title: '跳过头尾', description: '为某个歌单专门定制设置跳过头尾', isVipOnly: true ,hasCome:true}, { icon: $r('app.media.car'), title: 'HiCAR播放', description: '专为驾车体验优化的音乐播放模式', isVipOnly: true ,hasCome:false}, { icon: $r('app.media.music_wave'), title: '调音均衡器', description: '精准定制音效,带来极致听觉体验', isVipOnly: true ,hasCome:false}, ]; @State vipPlans: VipPlanApi[] = []; @State selectedPayPlan: VipPlanApi | null = null; @State payType: number = 0; // 0: 微信, 1: 支付宝 @StorageProp('themeColor') themeColor: string = CommonConstants.DEFAULT_THEME_COLOR; @State isDarkMode: boolean = false @State webUrl: string = CommonConstants.NEW_MEMBER_AGREEMENTS @StorageProp('topRectHeight') topRectHeight: number = px2vp(AppUtil.getStatusBarHeight()); @StorageProp('currentColorMode') @Watch('onColorModeChange') currentMode: number = ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT; onColorModeChange() { this.isDarkMode = this.currentMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK } constructor() { super(); } // 微信回调处理 private onWXResp: OnWXResp = (resp): void => { if (resp.errCode === ErrCode.ERR_OK) { // 判断回调类型 if (resp.type === wxopensdk.Command.kCommandSendAuth) { // 微信登录成功回调 const authResp = resp as wxopensdk.SendAuthResp; // 转换为授权响应类型,以便访问code this.getUserInfoByCode(resp?.['code']) DialogHelper.closeDialog('loginDialog'); ToastUtil.showToast('登录成功!'); } else if (resp.type === wxopensdk.Command.kCommandPay) { // 微信支付成功回调 ToastUtil.showToast('支付成功!'); // 可在此处刷新会员状态或其他支付成功后的业务逻辑 // this.fetchVipPlans(); // 刷新会员套餐信息 this.fetchUserInfo(); } else { // 其他类型的成功回调 ToastUtil.showToast('操作成功!'); } } else { // 处理错误情况 if (resp.type === wxopensdk.Command.kCommandSendAuth) { ToastUtil.showToast('微信登录失败!'); } else if (resp.type === wxopensdk.Command.kCommandPay) { ToastUtil.showToast('支付失败!'); } else { ToastUtil.showToast('操作失败!'); } } } // 通过code换取用户信息 async getUserInfoByCode(code: string) { await UserUtil.getUserInfoByCode(code).then(async ()=>{ // 登录后同步本地会员到线上(仅本地有会员且线上无会员时) Utility.getLocalNobleExpireDate().then((expireDate)=>{ if (Utility.isNobleForOld() && !this.hasActiveSubscription && expireDate) { UserUtil.syncLocalNobleToServer(expireDate).then((success)=>{ if (success) { ToastUtil.showToast('本地会员已同步到账号'); PreferencesUtil.putSync('isNoble', false); this.fetchUserInfo(); } else { this.fetchUserInfo(); } }); } else { this.fetchUserInfo(); } }); }); } // 通过华为账号信息换取用户信息和会员状态 async getUserInfoByHuawei(openID: string, unionID: string, nickname: string, avatarUri: string) { await UserUtil.getUserInfoByHuawei(openID, unionID, nickname, avatarUri).then(async (data)=>{ // 登录后同步本地会员到线上(仅本地有会员且线上无会员时) Utility.getLocalNobleExpireDate().then((expireDate)=>{ if (Utility.isNobleForOld() && !this.hasActiveSubscription && expireDate) { UserUtil.syncLocalNobleToServer(expireDate).then((success)=>{ if (success) { ToastUtil.showToast('本地会员已同步到账号'); // 关键:同步成功后再刷新用户信息 this.fetchUserInfo(); } else { // 同步失败也刷新一次 this.fetchUserInfo(); } }); } else { // 不需要同步会员,直接刷新 this.fetchUserInfo(); } }); }) } @Builder syncNobleDialogContentBuilder(): void { Text('检测到您是本地会员,为保障权益请登录账号,会员权益将自动同步到您的账号。') .fontSize(16) .fontColor('#222') .width('100%') .textAlign(TextAlign.Center) .padding(16) } aboutToAppear(): void { this.topRectHeight = px2vp(AppUtil.getStatusBarHeight()); let themeColor = PreferencesUtil.getStringSync('THEME_COLOR', CommonConstants.DEFAULT_THEME_COLOR); this.isDarkMode = this.currentMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK AppStorage.setOrCreate('themeColor', themeColor); this.themeColor = themeColor; this.isLogin = PreferencesUtil.getBooleanSync('isLogin', false); this.userId = PreferencesUtil.getNumberSync('userId', 0); this.userName = PreferencesUtil.getStringSync('userName', '未登录用户'); this.userAvatarUrl = PreferencesUtil.getStringSync('userAvatarUrl', ''); this.subscriptionName = PreferencesUtil.getStringSync('subscriptionName', ''); this.subscriptionEndDate = PreferencesUtil.getStringSync('subscriptionEndDate', ''); this.hasActiveSubscription = PreferencesUtil.getBooleanSync('hasActiveSubscription', false); this.wxEventHandler.registerOnWXRespCallback(this.onWXResp) this.fetchVipPlans(); if (this.isLogin) { void this.fetchUserInfo(); } console.log('Heanup isLogin:'+this.isLogin) // 只在非同步跳转时弹窗提醒 let params = router.getParams() as Record; let fromSyncNoble = params && params['fromSyncNoble'] === true; if (fromSyncNoble) { // 自动弹出登录框 this.showLoginDialog(); } else if (!this.isLogin && Utility.isNobleForOld()) { DialogHelper.showCustomContentDialog({ dialogId: 'syncNobleDialog', title: '会员同步提示', contentBuilder: () => { this.syncNobleDialogContentBuilder(); }, buttons: [ { value: '暂不登录' }, { value: '去登录', action: () => { this.showLoginDialog(); } } ] }); } } aboutToDisappear() { this.wxEventHandler.unregisterOnWXRespCallback(this.onWXResp) } build() { Column() { // 顶部安全区和自定义标题栏 Column() { // 顶部安全区 Blank() .height(this.topRectHeight) .backgroundColor(this.isDarkMode?$r('app.color.title_bar_bg'):this.themeColor) .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP]) // 自定义标题栏(Stack实现绝对居中) Stack() { // 居中标题 Text('用户中心') .fontSize(18) .fontColor(Color.White) .align(Alignment.Center) // 左右按钮 Row() { Image($r('app.media.left_back_white')) .width(32) .height(32) .margin({ left: 12, right: 8 }) .onClick(() => { router.back(); }) Blank().flexGrow(1) Blank().width(32) } .height(48) .width('100%') .alignItems(VerticalAlign.Center) } .height(48) .width('100%') .backgroundColor(this.isDarkMode?$r('app.color.user_center_card_background'):this.themeColor) } Scroll() { Column() { this.buildUserInfoCard() this.buildVipPlans() this.buildVipFeatures() // this.buildFunctionMenu() } .width('100%') } .scrollBar(BarState.Off) .edgeEffect(EdgeEffect.Spring) .layoutWeight(1) .padding({ bottom:45 }) } .backgroundColor($r('app.color.index_background')) .width('100%') .height('100%') .layoutWeight(1) } // 用户信息卡片 @Builder buildUserInfoCard() { Column() { Row() { Image(this.userAvatarUrl && this.userAvatarUrl.length > 0 ? this.userAvatarUrl : this.userAvatar) .width(56) .height(56) .borderRadius('50%') .clip(true) .backgroundColor(this.isDarkMode ? Color.Black : Color.White) .shadow({ radius: 8, color: 0x11000000, offsetX: 0, offsetY: 2 }) Column() { Row() { Text(this.isLogin ? this.userName : '未登录用户') .fontSize(17) .fontWeight(FontWeight.Bold) .fontColor(this.isDarkMode ? Color.White : Color.Black) .textAlign(TextAlign.Start) .maxLines(1) .width(110) .margin({ right: 12 }) } .justifyContent(FlexAlign.Start) .margin({ top: 2 }) if (this.isLogin) { if (this.hasActiveSubscription) { Text(this.subscriptionName) .fontSize(13) .fontColor( themeColorWithAlpha(this.themeColor, 0.8, false)) .textAlign(TextAlign.Start) Text('到期:' + this.subscriptionEndDate) .fontSize(11) .fontColor(this.isDarkMode ? Color.White : Color.Gray) .textAlign(TextAlign.Start) } else { Text('普通用户') .fontSize(12) .fontColor(this.isDarkMode ? Color.White : Color.Grey) .textAlign(TextAlign.Start) } } } .alignItems(HorizontalAlign.Start) .width(155) .margin({ left: 10 }) Column(){ if (this.isLogin) { Button('退出') .fontColor( Color.White) .backgroundColor(this.isDarkMode ? $r('app.color.title_bar_bg') : this.themeColor) .height(36) .width(66) .fontSize(13) .borderRadius(13) .margin({ left: 0 }) .onClick(() => { this.isLogin = false this.userName = '未登录用户' this.userAvatar = $r('app.media.icon_person2') this.userAvatarUrl = '' this.isVip = false this.vipExpire = '' this.hasActiveSubscription = false; this.subscriptionName = ''; this.subscriptionEndDate = ''; PreferencesUtil.putSync('isLogin', false); PreferencesUtil.putSync('userName', '未登录用户'); PreferencesUtil.putSync('userAvatarUrl', ''); PreferencesUtil.putSync('subscriptionName', ''); PreferencesUtil.putSync('subscriptionEndDate', ''); PreferencesUtil.putSync('hasActiveSubscription', false); ToastUtil.showToast('已退出登录') }) }else { Button('登录') .fontColor( Color.White) .backgroundColor(this.isDarkMode ? $r('app.color.title_bar_bg') : this.themeColor) .height(36) .width(66) .fontSize(13) .borderRadius(13) .margin({ left: 0 }) } } .alignItems(HorizontalAlign.End) .margin({left:30, right: 10 }) } .onClick(() => { if (!this.isLogin) { this.showLoginDialog(); } }) .width('100%') .padding(10) } .backgroundColor($r('app.color.user_center_card_background')) .borderRadius(24) .margin({ left: 14, right: 14, top: 15 }) .shadow({ radius: 8, color: 0x11000000, offsetX: 0, offsetY: 2 }) } // 登录方式选择弹窗内容 @Builder loginDialogContentBuilder(): void { Column() { Row() { // 微信登录图标按钮 Column() { Button({ type: ButtonType.Normal, stateEffect: true }) { Column() { Image($r('app.media.wx_logo')) .width(56) .height(56) .margin({ bottom: 8 }) Text('微信登录') .fontSize(15) .fontWeight(FontWeight.Bold) .fontColor('#333') } .alignItems(HorizontalAlign.Center) } .width(100) .height(100) .backgroundColor(Color.White) // .border({ color: this.themeColor, width: 2 }) .borderRadius(20) .onClick(async () => { this.payType = 0; // 微信登录 let req = new wxopensdk.SendAuthReq req.isOption1 = false req.nonAutomatic = true req.scope = 'snsapi_userinfo,snsapi_friend,snsapi_message,snsapi_contact' req.state = 'none' req.transaction = 'test123' let finished = await this.wxApi.sendReq(getContext(this) as common.UIAbilityContext, req) console.log('send request finished: ', finished) DialogHelper.closeDialog('loginDialog'); }) } .margin({ right: 24 }) // 华为账号登录图标按钮 Column() { Button({ type: ButtonType.Normal, stateEffect: true }) { Column() { Image($r('app.media.huawei')) .width(56) .height(56) .margin({ bottom: 8 }) Text('华为登录') .fontSize(15) .fontWeight(FontWeight.Bold) .fontColor('#333') } .alignItems(HorizontalAlign.Center) } .width(100) .height(100) .backgroundColor(Color.White) // .border({ color: this.themeColor, width: 2 }) .borderRadius(20) .onClick(() => { this.payType = 1; this.huaweiQuickLogin(); DialogHelper.closeDialog('loginDialog'); }) } } .justifyContent(FlexAlign.Center) .width('100%') } .padding({ left: 24, right: 24, top: 8, bottom: 24 }) .width('100%') } showLoginDialog() { DialogHelper.showCustomContentDialog({ dialogId: 'loginDialog', title: '', autoCancel: true, contentBuilder: () => { this.loginDialogContentBuilder(); }, buttons: [ ] }); } // 会员权益展示 @Builder buildVipFeatures(): void { Column() { Text('会员权益') .fontSize(18) .fontWeight(FontWeight.Bold) .padding({ top: 24, bottom: 16 }) .fontColor(this.isDarkMode ? Color.White : '#191919') .textAlign(TextAlign.Center); Grid() { ForEach(this.vipFeatures, (feature: VipFeature) => { GridItem() { Column() { Row() { Blank().layoutWeight(1) if (feature.hasCome === false) { Text('开发中') .fontSize(10) .fontColor(themeColorWithAlpha(this.themeColor, 0.9, false)) .backgroundColor(themeColorWithAlpha(this.themeColor, 0.15, this.isDarkMode)) .borderRadius(8) .padding({ left: 6, right: 6, top: 2, bottom: 2 }) .margin({ top: 2, right: 2 }) }else { Text('已开发') .fontSize(10) .fontColor(themeColorWithAlpha(this.themeColor, 0.9, false)) .backgroundColor(themeColorWithAlpha(this.themeColor, 0.15, this.isDarkMode)) .borderRadius(8) .padding({ left: 6, right: 6, top: 2, bottom: 2 }) .margin({ top: 2, right: 2 }) } } Row() { Blank().layoutWeight(1) Image(feature.icon) .width(32) .height(32) .fillColor(this.themeColor) Blank().layoutWeight(1) } .margin({ bottom: 8 }) Text(feature.title) .fontSize(15) .fontWeight(FontWeight.Bold) .fontColor(this.isDarkMode ? Color.White : '#191919') .margin({ bottom: 2 }) Text(feature.description) .fontSize(12) .fontColor(this.isDarkMode ? Color.Gray : '#888') .textAlign(TextAlign.Center) .maxLines(2) .margin({ bottom: 4 }) if (feature.isVipOnly) { Text('VIP专享') .fontSize(10) .fontColor(this.isDarkMode ? Color.White : this.themeColor) .backgroundColor(this.isDarkMode?$r('app.color.user_center_button_background'):themeColorWithAlpha(this.themeColor, 0.15, false)) .borderRadius(8) .padding({ left: 6, right: 6, top: 2, bottom: 2 }) .margin({ top: 2,bottom:6 }) } } .backgroundColor($r('app.color.user_center_card_background')) .borderRadius(16) .shadow({ radius: 8, color: 0x11000000, offsetX: 0, offsetY: 2 }) .border({ color:(this.isDarkMode?$r('app.color.user_center_button_background') :themeColorWithAlpha(this.themeColor, 0.08, false))//未选中时的边框颜色 , width: 2 }) .padding(12) .width('100%') .height(145) } }) } .columnsTemplate('1fr 1fr') .columnsGap(16) .rowsGap(16) .margin({ left: 16, right: 16,top:0, bottom: 15 }) } .backgroundColor(this.isDarkMode ? Color.Black : Color.White) .borderRadius(24) .margin({ left: 16, right: 16,top:15, bottom: 15 }) } // 支付方式选择弹窗内容 @Builder payDialogContentBuilder(): void { Column() { Row() { Image($r('app.media.wx_logo')) .width(22) .height(22) .alignSelf(ItemAlign.Center) .margin({ left: 25 }) Text('微信支付') .margin({ left: 10, right: 20 }) .fontSize(15) .fontColor(Color.Gray) .fontWeight(480) Blank() Checkbox() .select(this.payType === 0) .selectedColor(this.themeColor) .onChange((checked: boolean) => { if (checked) { this.payType = 0 } }) .shape(CheckBoxShape.CIRCLE) .margin({ left: 20, top: 8, bottom: 8, right: 20 }) .width(22) .height(22) } .onClick(() => { this.payType = 0 }) .width('100%') .height(50) Line().width('100%').height(0.3).backgroundColor($r('app.color.index_tab_unselected_font_color')) Row() { Image($r('app.media.alipay')) .width(22) .height(22) .alignSelf(ItemAlign.Center) .margin({ left: 25 }) Text('支付宝') .margin({ left: 10, right: 20 }) .fontSize(15) .fontColor(Color.Gray) .fontWeight(480) Blank() Checkbox() .select(this.payType === 1) .selectedColor(this.themeColor) .onChange((checked: boolean) => { if (checked) { this.payType = 1 } }) .shape(CheckBoxShape.CIRCLE) .margin({ left: 20, top: 8, bottom: 8, right: 20 }) .width(22) .height(22) } .onClick(() => { this.payType = 1 }) .width('100%') .height(50) } .padding(24) .width('100%') } // 会员套餐选择 @Builder buildVipPlans(): void { Column() { Text('会员套餐') .textAlign(TextAlign.Start) .padding({ left: 16, bottom: 15, top: 18 }) .fontSize(18) .fontColor(this.isDarkMode ? Color.White : '#191919') .fontWeight(FontWeight.Bold) .fontFamily('鸿蒙黑体'); // 横向滑动卡片区 List() { ForEach(this.vipPlans, (plan: VipPlanApi, index: number) => { ListItem() { Column() { Text(plan.name) .fontSize(15) .fontWeight(FontWeight.Bold) .fontColor($r('app.color.user_center_font')) .margin({ top: 18, bottom: 8 }) .textAlign(TextAlign.Center) // 价格上下排列 Column() { Text('¥' + plan.price) .fontSize(24) .fontWeight(FontWeight.Bold) .fontColor(this.themeColor) .margin({ bottom: (plan.original_price && plan.discount_percent > 0) ? 2 : 0 }) .textAlign(TextAlign.Center) if (plan.original_price && plan.discount_percent && plan.discount_percent > 0) { Text('¥' + plan.original_price) .fontSize(14) .fontColor(themeColorWithAlpha(this.themeColor, 0.7,false)) .decoration({ type: TextDecorationType.LineThrough, color: themeColorWithAlpha(this.themeColor, 0.7, false) }) .fontStyle(FontStyle.Italic) .textAlign(TextAlign.Center) } } .alignItems(HorizontalAlign.Center) .margin({ bottom: 6 }) Text(plan.duration === '0' ? '永久会员' : (plan.duration + '天会员')) .fontSize(13) .fontColor(this.isDarkMode ? Color.White : Color.Grey) .maxLines(1) .textAlign(TextAlign.Center) } .backgroundColor(this.selectedPayPlan && this.selectedPayPlan.id === plan.id ? themeColorWithAlpha(this.themeColor, 0.08, false) : (this.isDarkMode ? Color.Black : '#F7F7F7')) .borderRadius(18) .shadow({ radius: 8, color: 0x11000000, offsetX: 0, offsetY: 2 }) .border({ color: this.selectedPayPlan && this.selectedPayPlan.id === plan.id ? (this.isDarkMode ? Color.White :themeColorWithAlpha(this.themeColor, 0.3, this.isDarkMode))//选中时的边框颜色 : (this.isDarkMode?$r('app.color.user_center_button_background') :themeColorWithAlpha(this.themeColor, 0.08, this.isDarkMode))//未选中时的边框颜色 , width: 2 }) .width(125) .height(125) .margin({ left: index === 0 ? 16 : 8, right: index === this.vipPlans.length - 1 ? 16 : 8 }) .onClick(() => { this.selectedPayPlan = plan; }) .alignItems(HorizontalAlign.Center) } }) } .listDirection(Axis.Horizontal) .height(130) .edgeEffect(EdgeEffect.None) .scrollBar(BarState.Off) .margin({ bottom: 18 }) Button(this.isVip ? '升级会员' : '立即开通') .borderRadius(24) .backgroundColor(this.isDarkMode ? $r('app.color.user_center_button_background') : this.themeColor) .width(this.currentBreakpoint !== BreakpointTypeEnum.SM?'25%':'60%') .fontColor(Color.White) .fontSize(18) .padding(12) .fontWeight(FontWeight.Bold) .margin({ top: 1, bottom: 15 }) .alignSelf(ItemAlign.Center) .onClick(() => { if (!this.isLogin) { ToastUtil.showToast('请先登录'); return; } if (!this.selectedPayPlan) { ToastUtil.showToast('请选择套餐'); return; } DialogHelper.showCustomContentDialog({ dialogId: 'payDialog', title: '请选择支付方式', autoCancel: true, contentBuilder: () => { this.payDialogContentBuilder(); }, buttons: [ { value: '取消' }, { value: '确认支付', action: () => { if (this.selectedPayPlan) { void this.doPay(this.selectedPayPlan); } DialogHelper.closeDialog('payDialog'); } } ] }); }); Row() { Text('购买前请阅读') .fontSize(12) .fontColor(this.isDarkMode ? Color.White : '#888') Text('《会员协议》') .fontSize(12) .fontColor(this.isDarkMode ? Color.Blue : this.themeColor) .onClick(() => { router.pushUrl({ url: 'pages/WebIndex', params: { titleName: '会员协议', webUrl: this.webUrl } }); // router.pushUrl({ url: this.webUrl }); }); } .justifyContent(FlexAlign.Center) .width('100%') .padding({ bottom: 16 }); } .margin({ left: 14, right: 14, top: 15 }) .backgroundColor($r('app.color.user_center_card_background')) .borderRadius(24) } // 功能菜单 @Builder buildFunctionMenu(): void { Column() { this.menuItem($r('app.media.icon_favorite'), '我的收藏', () => { if (!this.isLogin) { ToastUtil.showToast('请先登录') return } // 跳转到收藏页面 ToastUtil.showToast('敬请期待') }) Line().width('90%').height(0.5).backgroundColor(0xFFEFEFEF) this.menuItem($r('app.media.icon_history'), '播放历史', () => { if (!this.isLogin) { ToastUtil.showToast('请先登录') return } // 跳转到历史记录页面 ToastUtil.showToast('敬请期待') }) Line().width('90%').height(0.5).backgroundColor(0xFFEFEFEF) this.menuItem($r('app.media.setting_red'), '设置', () => { // 跳转到设置页面 router.pushUrl({ url: 'pages/SettingPage' }); }) } .backgroundColor(Color.White) .borderRadius(24) .margin({ left: 20, right: 20, top: 24 }) } // 华为一键登录 async huaweiQuickLogin() { const logTag = 'UserCenter'; const domainId = 0x0000; try { const authRequest = new authentication.HuaweiIDProvider().createAuthorizationWithHuaweiIDRequest(); authRequest.scopes = ['profile']; authRequest.permissions = ['idtoken']; authRequest.forceAuthorization = true; authRequest.state = util.generateRandomUUID(); authRequest.idTokenSignAlgorithm = authentication.IdTokenSignAlgorithm.PS256; const controller = new authentication.AuthenticationController(getContext(this)); const response: authentication.AuthorizationWithHuaweiIDResponse = await controller.executeRequest(authRequest); const credential = response.data; const nickName = credential?.nickName ?? '华为用户'; const avatarUri = credential?.avatarUri ?? ''; const openID = credential?.openID; const unionID = credential?.unionID ?? ''; hilog.info(domainId, logTag, `华为登录成功,昵称: ${nickName}, openID: ${openID}`); // 调用接口获取用户信息和会员状态 if (openID) { await this.getUserInfoByHuawei(openID, unionID, nickName, avatarUri); } else { ToastUtil.showToast('openID获取失败'); } } catch (error) { hilog.error(domainId, logTag, `华为登录失败: ${error.code}, ${error.message}`); this.dealHuaweiLoginError(error as BusinessError); ToastUtil.showToast('华为登录失败'); } } dealHuaweiLoginError(error: BusinessError): void { if (error.code === 1001) { ToastUtil.showToast('未登录华为账号'); } else if (error.code === 1002) { ToastUtil.showToast('网络异常'); } else if (error.code === 1003) { ToastUtil.showToast('内部错误'); } else if (error.code === 1004) { ToastUtil.showToast('用户取消授权'); } else if (error.code === 1005) { ToastUtil.showToast('系统服务异常'); } else if (error.code === 1006) { ToastUtil.showToast('请求被拒绝'); } else if (error.code === 1007) { ToastUtil.showToast('无权限'); } else { ToastUtil.showToast('登录失败'); } } // 菜单项构建 @Builder menuItem(icon: Resource, text: string, onClick: () => void): void { Row() { Image(icon) .width(22) .height(22) .alignSelf(ItemAlign.Center) .margin({ left: 22 }) Text(text) .margin({ left: 10, right: 20 }) .fontSize(15) .fontColor(Color.Gray) .fontWeight(480) Blank() Image($r('app.media.arrow_right')) .width(22) .height(22) .margin({ left: 20, right: 20 }) .align(Alignment.Center) } .width('100%') .height(50) .onClick(onClick) } async fetchVipPlans() { try { const url = 'https://pay.ss5.xyz/subscription/get_list'; const httpRequest = http.createHttp(); const options: http.HttpRequestOptions = { method: http.RequestMethod.GET, readTimeout: 3000, connectTimeout: 3000, }; const response: http.HttpResponse = await httpRequest.request(url, options); if (response.responseCode === 200) { let res = response.result as string; let json: VipPlanListApiResponse = JSON.parse(res) as VipPlanListApiResponse; if (json.code === 0 && json.data && json.data.plans) { this.vipPlans = json.data.plans; if (this.vipPlans.length > 0) { this.selectedPayPlan = this.vipPlans[0]; // 默认选中第一个套餐 } } else { ToastUtil.showToast('套餐数据解析失败'); } } else { ToastUtil.showToast('获取套餐失败'); } } catch (e) { ToastUtil.showToast('获取套餐异常'); } } async doPay(plan: VipPlanApi) { if (this.payType === 0) { await this.doWxPay(plan); } else if (this.payType === 1) { await this.doAliPay(plan); } } async doWxPay(plan: VipPlanApi) { if (!(await isHasWX())) { ToastUtil.showToast('没有安装微信'); return; } const appName = PreferencesUtil.getStringSync('appName', 'TTMusic'); hilog.info(0x0000, 'UserCenter', `doWxPay: current this.userId is ${this.userId}`); const prepayId = await createWeChatOrder(CommonConstants.WX_PAY_APPID, CommonConstants.WX_PAY_MCHID, appName, Number(plan.price) * 100, CommonConstants.OUT_TRADE_NO_PRE, this.userId, plan.id); if (prepayId) { await this.wechatPay(prepayId); } else { ToastUtil.showToast('微信下单失败'); } } async wechatPay(prepayId: string) { let nonceStr = RandomUtil.getRandomStr(16, 'abcdefghijklmnopqrstuvwxyz'); const timestamp: number = Math.floor(Date.now() / 1000); const signString = constructSignatureString(CommonConstants.WX_PAY_APPID, timestamp + '', nonceStr, prepayId); let signStr = await rsaSign2048(signString); let signStrBase64 = Base64Util.encodeToStrSync(signStr); let req = new PayReq(); req.partnerId = CommonConstants.WX_PAY_MCHID; req.appId = CommonConstants.WX_PAY_APPID; req.packageValue = 'Sign=WXPay'; req.prepayId = prepayId; req.nonceStr = nonceStr; req.timeStamp = timestamp + ''; req.sign = signStrBase64; req.extData = 'extData'; let finished = await this.wxApi.sendReq(getContext(this) as common.UIAbilityContext, req); hilog.info(0x0000, 'UserCenter', 'send request finished: ', finished); } async doAliPay(plan: VipPlanApi) { const appName = PreferencesUtil.getStringSync('appName', 'TTMusic'); OrderInfoUtil.getOrderInfo(Number(plan.price), appName, this.userId, plan.id).then((orderInfo) => { console.log('Heanup: alipay orderInfo = '+json.stringify(orderInfo)) new Pay().pay(orderInfo, true).then(async (result) => { if (result.get('resultStatus') === '9000') { ToastUtil.showToast('支付成功!'); void this.fetchUserInfo(); // 可在此处刷新会员状态 } else { ToastUtil.showToast('取消支付!'); } }).catch((error: BusinessError) => { ToastUtil.showToast('支付失败!' + error.message); }); }); } // 每次进入页面时刷新用户数据 async fetchUserInfo() { // console.log('Heanup 刷新用户信息:'+token) await UserUtil.fetchUserInfo().then((user_info)=>{ console.log("Heanup UserInfo:"+json.stringify(user_info)) this.isLogin = PreferencesUtil.getBooleanSync('isLogin', false); this.userId = PreferencesUtil.getNumberSync('userId', 0); this.userName = PreferencesUtil.getStringSync('userName', '未登录用户'); this.userAvatarUrl = PreferencesUtil.getStringSync('userAvatarUrl', ''); this.subscriptionName = PreferencesUtil.getStringSync('subscriptionName', ''); this.subscriptionEndDate = PreferencesUtil.getStringSync('subscriptionEndDate', ''); this.hasActiveSubscription = PreferencesUtil.getBooleanSync('hasActiveSubscription', false); console.log('Heanup hasActiveSubscription:'+this.hasActiveSubscription) // 新增:同步线上会员状态到本地,便于 Utility.isNoble 全局判断 PreferencesUtil.putSync('hasActiveSubscription', this.hasActiveSubscription); }); } }