|
|
@@ -1,14 +1,23 @@
|
|
|
-import { router, ThemeControl } from '@kit.ArkUI';
|
|
|
-import { AppUtil, DeviceUtil, DisplayUtil, LogUtil, ToastUtil, PreferencesUtil } from '@pura/harmony-utils';
|
|
|
+import { router } from '@kit.ArkUI';
|
|
|
+import { AppUtil, DeviceUtil, DisplayUtil, LogUtil, ToastUtil, PreferencesUtil, StrUtil } from '@pura/harmony-utils';
|
|
|
import { resourceManager } from '@kit.LocalizationKit';
|
|
|
import { BreakpointTypeEnum } from '../common/util/BreakpointSystem';
|
|
|
import * as wxopensdk from '@tencent/wechat_open_sdk';
|
|
|
import { OnWXResp, WXApi, WXEventHandler } from '../common/util/WXApiWrap';
|
|
|
-import { common } from '@kit.AbilityKit';
|
|
|
+import { bundleManager, common, UIAbility } from '@kit.AbilityKit';
|
|
|
import { ErrCode } from '@tencent/wechat_open_sdk';
|
|
|
import { http } from '@kit.NetworkKit';
|
|
|
-import { CustomTheme } from '@kit.ArkUI';
|
|
|
-import { myTheme, DefaultTheme, TwilightTheme, ForestTheme, CoralTheme, MidnightTheme } from '../common/AppTheme';
|
|
|
+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';
|
|
|
|
|
|
|
|
|
interface WechatUserInfo {
|
|
|
@@ -59,6 +68,30 @@ interface WechatUserInfoApiResponse {
|
|
|
data: GeneratedTypeLiteralInterface_1;
|
|
|
}
|
|
|
|
|
|
+interface HuaweiUserInfo {
|
|
|
+ openid: string;
|
|
|
+ unionid?: string;
|
|
|
+ nickname: string;
|
|
|
+ headimgurl: string;
|
|
|
+}
|
|
|
+
|
|
|
+interface HuaweiSystemInfo {
|
|
|
+ user_id: number;
|
|
|
+ token: string;
|
|
|
+}
|
|
|
+
|
|
|
+interface HuaweiUserInfoApiData {
|
|
|
+ user_info: HuaweiUserInfo;
|
|
|
+ system_info?: HuaweiSystemInfo;
|
|
|
+ subscription_info?: SubscriptionInfo;
|
|
|
+}
|
|
|
+
|
|
|
+interface HuaweiUserInfoApiResponse {
|
|
|
+ code: number;
|
|
|
+ msg: string;
|
|
|
+ data: HuaweiUserInfoApiData;
|
|
|
+}
|
|
|
+
|
|
|
interface VipFeature {
|
|
|
icon: Resource;
|
|
|
title: string;
|
|
|
@@ -66,13 +99,34 @@ interface VipFeature {
|
|
|
isVipOnly: boolean;
|
|
|
}
|
|
|
|
|
|
-interface VipPlan {
|
|
|
+interface VipPlanApi {
|
|
|
+ id: string;
|
|
|
name: string;
|
|
|
+ description: string;
|
|
|
price: string;
|
|
|
- originalPrice: string;
|
|
|
duration: string;
|
|
|
- features: string[];
|
|
|
+ features: string;
|
|
|
+ sort_order: string;
|
|
|
+ status: string;
|
|
|
+ created_at: string;
|
|
|
+ updated_at: string;
|
|
|
+}
|
|
|
+
|
|
|
+interface VipPlanListApiData {
|
|
|
+ plans: VipPlanApi[];
|
|
|
+}
|
|
|
+
|
|
|
+interface VipPlanListApiResponse {
|
|
|
+ code: number;
|
|
|
+ msg: string;
|
|
|
+ data: VipPlanListApiData;
|
|
|
+}
|
|
|
+
|
|
|
+interface WeChatPrepayId {
|
|
|
+ prepay_id: string;
|
|
|
}
|
|
|
+
|
|
|
+
|
|
|
// 自定义主题颜色
|
|
|
|
|
|
// 支持传入主题,默认使用myTheme
|
|
|
@@ -80,7 +134,80 @@ export interface UserCenterParams {
|
|
|
theme?: CustomTheme;
|
|
|
}
|
|
|
|
|
|
-ThemeControl.setDefaultTheme(myTheme);
|
|
|
+// 微信支付相关工具方法
|
|
|
+async function isHasWX(): Promise<boolean> {
|
|
|
+ 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): Promise<string> {
|
|
|
+ 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)
|
|
|
+ + '&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<Uint8Array> {
|
|
|
+
|
|
|
+ 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()
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
@Component
|
|
|
@Entry
|
|
|
@@ -103,20 +230,17 @@ export struct UserCenter {
|
|
|
{ icon: $r('app.media.small_music'), title: '智能歌单', description: 'AI智能推荐歌单', isVipOnly: true },
|
|
|
{ icon: $r('app.media.small_music'), title: '主题定制', description: '自定义播放器主题', isVipOnly: true }
|
|
|
];
|
|
|
- @State vipPlans: VipPlan[] = [
|
|
|
- { name: '月度会员', price: '¥18', originalPrice: '¥30', duration: '30天', features: ['无损音质', '智能歌单', '主题定制'] },
|
|
|
- { name: '年度会员', price: '¥128', originalPrice: '¥360', duration: '365天', features: ['无损音质', '智能歌单', '主题定制', '专属客服'] }
|
|
|
- ];
|
|
|
-
|
|
|
- @State customTheme: CustomTheme = new DefaultTheme();
|
|
|
- count: number = 0;
|
|
|
+ @State vipPlans: VipPlanApi[] = [];
|
|
|
+ @State selectedPayPlan: VipPlanApi | null = null;
|
|
|
+ @State payType: number = 0; // 0: 微信, 1: 支付宝
|
|
|
+ @StorageProp('themeColor') themeColor: string = '#FF4081';
|
|
|
|
|
|
constructor() {
|
|
|
super();
|
|
|
}
|
|
|
|
|
|
// 微信回调处理
|
|
|
- private onWXResp: OnWXResp = (resp) => {
|
|
|
+ private onWXResp: OnWXResp = (resp): void => {
|
|
|
let authResultText = JSON.stringify(resp ?? {}, null, 2)
|
|
|
if (resp.errCode === ErrCode.ERR_OK) {
|
|
|
// 提取code并请求用户信息
|
|
|
@@ -180,11 +304,64 @@ export struct UserCenter {
|
|
|
ToastUtil.showToast('获取用户信息失败');
|
|
|
}
|
|
|
} catch (e) {
|
|
|
- ToastUtil.showToast('登录失败');
|
|
|
+ ToastUtil.showToast('登录失败' + (e && e.message ? (': ' + e.message) : ''));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 通过华为账号信息换取用户信息和会员状态
|
|
|
+ async getUserInfoByHuawei(openID: string, unionID: string, nickname: string, avatarUri: string) {
|
|
|
+ try {
|
|
|
+ const url = `https://pay.ss5.xyz/user/huawei?openID=${encodeURIComponent(openID)}&unionID=${encodeURIComponent(unionID)}&nickname=${encodeURIComponent(nickname)}&avatarUri=${encodeURIComponent(avatarUri)}`;
|
|
|
+ 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;
|
|
|
+ LogUtil.debug("getUserInfoByHuawei res =" + res)
|
|
|
+ let json: HuaweiUserInfoApiResponse = JSON.parse(res) as HuaweiUserInfoApiResponse;
|
|
|
+ if (json.code === 0 && json.data && json.data.user_info) {
|
|
|
+ // 登录成功处理
|
|
|
+ this.isLogin = true;
|
|
|
+ this.userName = json.data.user_info.nickname || '华为用户';
|
|
|
+ this.userAvatarUrl = json.data.user_info.headimgurl || '';
|
|
|
+ // 处理会员信息
|
|
|
+ const subInfo = json.data.subscription_info;
|
|
|
+ if (subInfo && subInfo.has_active_subscription && subInfo.subscriptions && subInfo.subscriptions.length > 0) {
|
|
|
+ this.hasActiveSubscription = true;
|
|
|
+ this.subscriptionName = subInfo.subscriptions[0].plan_name;
|
|
|
+ this.subscriptionEndDate = subInfo.subscriptions[0].end_date;
|
|
|
+ } else {
|
|
|
+ this.hasActiveSubscription = false;
|
|
|
+ this.subscriptionName = '';
|
|
|
+ this.subscriptionEndDate = '';
|
|
|
+ }
|
|
|
+ // 保存登录状态
|
|
|
+ PreferencesUtil.putSync('isLogin', true);
|
|
|
+ PreferencesUtil.putSync('userName', this.userName);
|
|
|
+ PreferencesUtil.putSync('userAvatarUrl', this.userAvatarUrl);
|
|
|
+ PreferencesUtil.putSync('subscriptionName', this.subscriptionName);
|
|
|
+ PreferencesUtil.putSync('subscriptionEndDate', this.subscriptionEndDate);
|
|
|
+ PreferencesUtil.putSync('hasActiveSubscription', this.hasActiveSubscription);
|
|
|
+ ToastUtil.showToast('华为登录成功');
|
|
|
+ } else {
|
|
|
+ ToastUtil.showToast('用户信息解析失败');
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ ToastUtil.showToast('获取用户信息失败');
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ ToastUtil.showToast('登录失败' + (e && e.message ? (': ' + e.message) : ''));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- aboutToAppear() {
|
|
|
+ aboutToAppear(): void {
|
|
|
+ let themeColor = PreferencesUtil.getStringSync('THEME_COLOR', '#FF4081');
|
|
|
+ AppStorage.setOrCreate('themeColor', themeColor);
|
|
|
+ this.themeColor = themeColor;
|
|
|
// 自动恢复本地登录状态
|
|
|
this.isLogin = PreferencesUtil.getBooleanSync('isLogin', false);
|
|
|
this.userName = PreferencesUtil.getStringSync('userName', '未登录用户');
|
|
|
@@ -193,6 +370,7 @@ export struct UserCenter {
|
|
|
this.subscriptionEndDate = PreferencesUtil.getStringSync('subscriptionEndDate', '');
|
|
|
this.hasActiveSubscription = PreferencesUtil.getBooleanSync('hasActiveSubscription', false);
|
|
|
this.wxEventHandler.registerOnWXRespCallback(this.onWXResp)
|
|
|
+ this.fetchVipPlans();
|
|
|
// 这里可以加载用户信息
|
|
|
// this.isLogin = ...
|
|
|
// this.userName = ...
|
|
|
@@ -206,85 +384,59 @@ export struct UserCenter {
|
|
|
}
|
|
|
|
|
|
build() {
|
|
|
- WithTheme({ theme: this.customTheme }) {
|
|
|
+ Column() {
|
|
|
+ // 顶部安全区和自定义标题栏
|
|
|
Column() {
|
|
|
- // 顶部安全区和自定义标题栏
|
|
|
- Column() {
|
|
|
- // 顶部安全区
|
|
|
- Blank()
|
|
|
- .height(this.isPhoneLan() ? 0 : px2vp(AppUtil.getStatusBarHeight()))
|
|
|
- .backgroundColor(this.customTheme.colors?.backgroundEmphasize)
|
|
|
- .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP])
|
|
|
- // 自定义标题栏(Stack实现绝对居中)
|
|
|
- Stack() {
|
|
|
- // 居中标题
|
|
|
- Text('用户中心')
|
|
|
- .fontSize(20)
|
|
|
- .fontColor(this.customTheme.colors?.fontPrimary)
|
|
|
- .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)
|
|
|
+ // 顶部安全区
|
|
|
+ Blank()
|
|
|
+ .height(this.isPhoneLan() ? 0 : px2vp(AppUtil.getStatusBarHeight()))
|
|
|
+ .backgroundColor(this.themeColor)
|
|
|
+ .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP])
|
|
|
+ // 自定义标题栏(Stack实现绝对居中)
|
|
|
+ Stack() {
|
|
|
+ // 居中标题
|
|
|
+ Text('用户中心')
|
|
|
+ .fontSize(20)
|
|
|
+ .fontColor($r('app.color.text_color'))
|
|
|
+ .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%')
|
|
|
- .backgroundColor(this.customTheme.colors?.backgroundEmphasize)
|
|
|
+ .alignItems(VerticalAlign.Center)
|
|
|
}
|
|
|
+ .height(48)
|
|
|
+ .width('100%')
|
|
|
+ .backgroundColor(this.themeColor)
|
|
|
+ }
|
|
|
|
|
|
- Scroll() {
|
|
|
- Column() {
|
|
|
- this.buildUserInfoCard()
|
|
|
- this.buildVipFeatures()
|
|
|
- this.buildVipPlans()
|
|
|
- this.buildFunctionMenu()
|
|
|
- this.buildLoginButton()
|
|
|
- Button('切换主题').onClick(() => {
|
|
|
- this.count++;
|
|
|
- if (this.count > 4) {
|
|
|
- this.count = 0;
|
|
|
- }
|
|
|
- switch (this.count) {
|
|
|
- case 0:
|
|
|
- this.customTheme = new DefaultTheme();
|
|
|
- break;
|
|
|
- case 1:
|
|
|
- this.customTheme = new TwilightTheme();
|
|
|
- break;
|
|
|
- case 2:
|
|
|
- this.customTheme = new ForestTheme();
|
|
|
- break;
|
|
|
- case 3:
|
|
|
- this.customTheme = new CoralTheme();
|
|
|
- break;
|
|
|
- case 4:
|
|
|
- this.customTheme = new MidnightTheme();
|
|
|
- break;
|
|
|
- }
|
|
|
- })
|
|
|
- .margin({ top: 32,bottom: 16 })
|
|
|
- }
|
|
|
- .width('100%')
|
|
|
+ Scroll() {
|
|
|
+ Column() {
|
|
|
+ this.buildUserInfoCard()
|
|
|
+ this.buildVipFeatures()
|
|
|
+ this.buildVipPlans()
|
|
|
+ this.buildFunctionMenu()
|
|
|
+ this.buildLoginButton()
|
|
|
}
|
|
|
- .scrollBar(BarState.Off)
|
|
|
- .edgeEffect(EdgeEffect.Spring)
|
|
|
- .layoutWeight(1) // 让内容区自动填满剩余空间
|
|
|
+ .width('100%')
|
|
|
}
|
|
|
- .backgroundColor(this.customTheme.colors?.backgroundSecondary)
|
|
|
- .width('100%')
|
|
|
- .height('100%')
|
|
|
+ .scrollBar(BarState.Off)
|
|
|
+ .edgeEffect(EdgeEffect.Spring)
|
|
|
+ .layoutWeight(1)
|
|
|
}
|
|
|
+ .backgroundColor(0xFFF6F7FA)
|
|
|
+ .width('100%')
|
|
|
+ .height('100%')
|
|
|
}
|
|
|
|
|
|
// 用户信息卡片
|
|
|
@@ -297,25 +449,25 @@ export struct UserCenter {
|
|
|
.height(80)
|
|
|
.borderRadius('50%')
|
|
|
.clip(true)
|
|
|
- .backgroundColor(this.customTheme.colors?.compBackgroundPrimary)
|
|
|
+ .backgroundColor(Color.White)
|
|
|
.shadow({ radius: 8, color: 0x11000000, offsetX: 0, offsetY: 2 })
|
|
|
Column() {
|
|
|
Text(this.isLogin ? this.userName : '未登录用户')
|
|
|
.fontSize(22)
|
|
|
.fontWeight(FontWeight.Bold)
|
|
|
- .fontColor(this.customTheme.colors?.fontPrimary)
|
|
|
+ .fontColor(Color.Black)
|
|
|
if (this.isLogin) {
|
|
|
if (this.hasActiveSubscription) {
|
|
|
Text(this.subscriptionName)
|
|
|
.fontSize(16)
|
|
|
- .fontColor(this.customTheme.colors?.brand)
|
|
|
+ .fontColor(Color.Red)
|
|
|
Text('到期:' + this.subscriptionEndDate)
|
|
|
.fontSize(14)
|
|
|
- .fontColor(this.customTheme.colors?.fontSecondary)
|
|
|
+ .fontColor(Color.Gray)
|
|
|
} else {
|
|
|
Text('普通用户')
|
|
|
.fontSize(15)
|
|
|
- .fontColor(this.customTheme.colors?.fontTertiary)
|
|
|
+ .fontColor(Color.Grey)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -325,7 +477,7 @@ export struct UserCenter {
|
|
|
.width('100%')
|
|
|
.padding(16)
|
|
|
}
|
|
|
- .backgroundColor(this.customTheme.colors?.compBackgroundPrimary)
|
|
|
+ .backgroundColor(Color.White)
|
|
|
.borderRadius(24)
|
|
|
.margin({ left: 20, right: 20, top: 20 })
|
|
|
.shadow({ radius: 12, color: 0x11000000, offsetX: 0, offsetY: 2 })
|
|
|
@@ -333,7 +485,7 @@ export struct UserCenter {
|
|
|
|
|
|
// 会员权益展示
|
|
|
@Builder
|
|
|
- buildVipFeatures() {
|
|
|
+ buildVipFeatures(): void {
|
|
|
Column() {
|
|
|
Text('会员权益')
|
|
|
.fontSize(18)
|
|
|
@@ -352,13 +504,13 @@ export struct UserCenter {
|
|
|
.fontWeight(FontWeight.Medium)
|
|
|
Text(feature.description)
|
|
|
.fontSize(12)
|
|
|
- .fontColor(this.customTheme.colors?.fontSecondary)
|
|
|
+ .fontColor(Color.Gray)
|
|
|
.margin({ top: 4 })
|
|
|
if (feature.isVipOnly) {
|
|
|
Text('VIP专享')
|
|
|
.fontSize(10)
|
|
|
- .fontColor(this.customTheme.colors?.brand)
|
|
|
- .backgroundColor(this.customTheme.colors?.compEmphasizeSecondary)
|
|
|
+ .fontColor(Color.Red)
|
|
|
+ .backgroundColor('#FFF0F0')
|
|
|
.borderRadius(8)
|
|
|
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
|
|
|
.margin({ top: 4 })
|
|
|
@@ -366,7 +518,7 @@ export struct UserCenter {
|
|
|
}
|
|
|
.width('100%')
|
|
|
.height(120)
|
|
|
- .backgroundColor(this.customTheme.colors?.compBackgroundPrimary)
|
|
|
+ .backgroundColor(Color.White)
|
|
|
.borderRadius(12)
|
|
|
.padding(12)
|
|
|
}
|
|
|
@@ -379,55 +531,101 @@ export struct UserCenter {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ // 支付方式选择弹窗内容
|
|
|
+ @Builder
|
|
|
+ payDialogContentBuilder(): void {
|
|
|
+ Column() {
|
|
|
+ Text('请选择支付方式')
|
|
|
+ .fontSize(18)
|
|
|
+ .fontWeight(FontWeight.Bold)
|
|
|
+ .margin({ bottom: 16 })
|
|
|
+ Row() {
|
|
|
+ Button('微信支付')
|
|
|
+ .fontColor(Color.White)
|
|
|
+ .backgroundColor('#FF4081')
|
|
|
+ .height(48)
|
|
|
+ .layoutWeight(1)
|
|
|
+ .onClick(() => {
|
|
|
+ this.payType = 0;
|
|
|
+ DialogHelper.closeDialog('payDialog');
|
|
|
+ if (this.selectedPayPlan) {
|
|
|
+ void this.doPay(this.selectedPayPlan);
|
|
|
+ }
|
|
|
+ })
|
|
|
+ Blank().width(16)
|
|
|
+ Button('支付宝')
|
|
|
+ .fontColor(Color.White)
|
|
|
+ .backgroundColor('#FF4081')
|
|
|
+ .height(48)
|
|
|
+ .layoutWeight(1)
|
|
|
+ .onClick(() => {
|
|
|
+ this.payType = 1;
|
|
|
+ DialogHelper.closeDialog('payDialog');
|
|
|
+ if (this.selectedPayPlan) {
|
|
|
+ void this.doPay(this.selectedPayPlan);
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ .margin({ top: 8, bottom: 8 })
|
|
|
+ }
|
|
|
+ .padding(24)
|
|
|
+ .width('80%')
|
|
|
+ }
|
|
|
+
|
|
|
// 会员套餐选择
|
|
|
@Builder
|
|
|
- buildVipPlans() {
|
|
|
+ buildVipPlans(): void {
|
|
|
Column() {
|
|
|
Text('会员套餐')
|
|
|
.fontSize(18)
|
|
|
.fontWeight(FontWeight.Bold)
|
|
|
.margin({ top: 24, bottom: 16 })
|
|
|
- ForEach(this.vipPlans, (plan: VipPlan) => {
|
|
|
+ ForEach(this.vipPlans, (plan: VipPlanApi) => {
|
|
|
Row() {
|
|
|
Column() {
|
|
|
Text(plan.name)
|
|
|
.fontSize(16)
|
|
|
.fontWeight(FontWeight.Bold)
|
|
|
- Text(plan.duration)
|
|
|
+ Text(plan.duration === '0' ? '永久' : plan.duration + '天')
|
|
|
.fontSize(12)
|
|
|
- .fontColor(this.customTheme.colors?.fontSecondary)
|
|
|
+ .fontColor(Color.Gray)
|
|
|
.margin({ top: 4 })
|
|
|
}
|
|
|
.layoutWeight(1)
|
|
|
Column() {
|
|
|
- Text(plan.price)
|
|
|
+ Text('¥' + plan.price)
|
|
|
.fontSize(20)
|
|
|
.fontWeight(FontWeight.Bold)
|
|
|
- .fontColor(this.customTheme.colors?.brand)
|
|
|
- Text(plan.originalPrice)
|
|
|
- .fontSize(12)
|
|
|
- .fontColor(this.customTheme.colors?.fontTertiary)
|
|
|
- .decoration({ type: TextDecorationType.LineThrough })
|
|
|
+ .fontColor(Color.Red)
|
|
|
}
|
|
|
Button('立即开通')
|
|
|
.fontSize(14)
|
|
|
- .fontColor(this.customTheme.colors?.fontOnPrimary)
|
|
|
- .backgroundColor(this.customTheme.colors?.brand)
|
|
|
+ .fontColor(Color.White)
|
|
|
+ .backgroundColor(this.themeColor)
|
|
|
.borderRadius(16)
|
|
|
.height(32)
|
|
|
- .width(80)
|
|
|
+ .width(120)
|
|
|
.margin({ left: 16 })
|
|
|
.onClick(() => {
|
|
|
if (!this.isLogin) {
|
|
|
ToastUtil.showToast('请先登录')
|
|
|
return
|
|
|
}
|
|
|
- // 处理购买逻辑
|
|
|
+ this.selectedPayPlan = plan;
|
|
|
+ DialogHelper.showCustomContentDialog({
|
|
|
+ dialogId: 'payDialog',
|
|
|
+ title: '请选择支付方式',
|
|
|
+ autoCancel: true,
|
|
|
+ contentBuilder: () => {
|
|
|
+ this.payDialogContentBuilder();
|
|
|
+ },
|
|
|
+ buttons: []
|
|
|
+ });
|
|
|
})
|
|
|
}
|
|
|
.width('100%')
|
|
|
.padding(16)
|
|
|
- .backgroundColor(this.customTheme.colors?.compBackgroundPrimary)
|
|
|
+ .backgroundColor(Color.White)
|
|
|
.borderRadius(12)
|
|
|
.margin({ bottom: 12 })
|
|
|
})
|
|
|
@@ -437,7 +635,7 @@ export struct UserCenter {
|
|
|
|
|
|
// 功能菜单
|
|
|
@Builder
|
|
|
- buildFunctionMenu() {
|
|
|
+ buildFunctionMenu(): void {
|
|
|
Column() {
|
|
|
this.menuItem($r('app.media.icon_favorite'), '我的收藏', () => {
|
|
|
if (!this.isLogin) {
|
|
|
@@ -446,7 +644,7 @@ export struct UserCenter {
|
|
|
}
|
|
|
// 跳转到收藏页面
|
|
|
})
|
|
|
- Line().width('90%').height(0.5).backgroundColor(this.customTheme.colors?.compBackgroundSecondary)
|
|
|
+ Line().width('90%').height(0.5).backgroundColor(0xFFEFEFEF)
|
|
|
this.menuItem($r('app.media.icon_history'), '播放历史', () => {
|
|
|
if (!this.isLogin) {
|
|
|
ToastUtil.showToast('请先登录')
|
|
|
@@ -454,25 +652,25 @@ export struct UserCenter {
|
|
|
}
|
|
|
// 跳转到历史记录页面
|
|
|
})
|
|
|
- Line().width('90%').height(0.5).backgroundColor(this.customTheme.colors?.compBackgroundSecondary)
|
|
|
+ Line().width('90%').height(0.5).backgroundColor(0xFFEFEFEF)
|
|
|
this.menuItem($r('app.media.setting'), '设置', () => {
|
|
|
// 跳转到设置页面
|
|
|
})
|
|
|
}
|
|
|
- .backgroundColor(this.customTheme.colors?.compBackgroundPrimary)
|
|
|
+ .backgroundColor(Color.White)
|
|
|
.borderRadius(24)
|
|
|
.margin({ left: 20, right: 20, top: 24 })
|
|
|
}
|
|
|
|
|
|
// 登录/退出按钮
|
|
|
@Builder
|
|
|
- buildLoginButton() {
|
|
|
+ buildLoginButton(): void {
|
|
|
Row() {
|
|
|
Blank().layoutWeight(1)
|
|
|
if (this.isLogin) {
|
|
|
Button('退出登录')
|
|
|
- .fontColor(this.customTheme.colors?.fontOnPrimary)
|
|
|
- .backgroundColor(this.customTheme.colors?.brand)
|
|
|
+ .fontColor(Color.White)
|
|
|
+ .backgroundColor(Color.Red)
|
|
|
.height(48)
|
|
|
.width('70%')
|
|
|
.borderRadius(24)
|
|
|
@@ -495,27 +693,88 @@ export struct UserCenter {
|
|
|
ToastUtil.showToast('已退出登录')
|
|
|
})
|
|
|
} else {
|
|
|
- Button('登录')
|
|
|
- .fontColor(this.customTheme.colors?.fontOnPrimary)
|
|
|
- // .fontColor(`$r('sys.color.font_on_primary')`)
|
|
|
- .backgroundColor(this.customTheme.colors?.brand)
|
|
|
- .height(48)
|
|
|
- .width('70%')
|
|
|
- .borderRadius(24)
|
|
|
- .onClick(async () => {
|
|
|
- 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)
|
|
|
- })
|
|
|
+ Row() {
|
|
|
+ Button('微信登录')
|
|
|
+ .height(48)
|
|
|
+ .width('45%')
|
|
|
+ .borderRadius(24)
|
|
|
+ .onClick(async () => {
|
|
|
+ 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)
|
|
|
+ })
|
|
|
+ Blank().width(16)
|
|
|
+ Button('华为登录')
|
|
|
+ .height(48)
|
|
|
+ .width('45%')
|
|
|
+ .borderRadius(24)
|
|
|
+ .onClick(() => {
|
|
|
+ this.huaweiQuickLogin();
|
|
|
+ })
|
|
|
+ }
|
|
|
+ .width('70%')
|
|
|
}
|
|
|
Blank().layoutWeight(1)
|
|
|
}
|
|
|
.margin({ top: 48, bottom: 0 })
|
|
|
+ .layoutWeight(1)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 华为一键登录
|
|
|
+ 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('登录失败');
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
isPhoneLan() {
|
|
|
@@ -531,7 +790,7 @@ export struct UserCenter {
|
|
|
|
|
|
// 菜单项构建
|
|
|
@Builder
|
|
|
- menuItem(icon: Resource, text: string, onClick: () => void) {
|
|
|
+ menuItem(icon: Resource, text: string, onClick: () => void): void {
|
|
|
Row() {
|
|
|
Image(icon)
|
|
|
.width(22)
|
|
|
@@ -541,7 +800,7 @@ export struct UserCenter {
|
|
|
Text(text)
|
|
|
.margin({ left: 10, right: 20 })
|
|
|
.fontSize(15)
|
|
|
- .fontColor(this.customTheme.colors?.fontSecondary)
|
|
|
+ .fontColor(Color.Gray)
|
|
|
.fontWeight(480)
|
|
|
Blank()
|
|
|
Image($r('app.media.arrow_right'))
|
|
|
@@ -554,4 +813,88 @@ export struct UserCenter {
|
|
|
.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;
|
|
|
+ } 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');
|
|
|
+ const prepayId = await createWeChatOrder(CommonConstants.WX_PAY_APPID, CommonConstants.WX_PAY_MCHID,
|
|
|
+ appName, Number(plan.price) * 100, CommonConstants.OUT_TRADE_NO_PRE);
|
|
|
+ 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).then((orderInfo) => {
|
|
|
+ new Pay().pay(orderInfo, true).then(async (result) => {
|
|
|
+ if (result.get('resultStatus') === '9000') {
|
|
|
+ ToastUtil.showToast('支付成功!');
|
|
|
+ // 可在此处刷新会员状态
|
|
|
+ } else {
|
|
|
+ ToastUtil.showToast('取消支付!');
|
|
|
+ }
|
|
|
+ }).catch((error: BusinessError) => {
|
|
|
+ ToastUtil.showToast('支付失败!' + error.message);
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
}
|