chendeben преди 1 година
родител
ревизия
7c57588841
променени са 3 файла, в които са добавени 496 реда и са изтрити 147 реда
  1. 6 6
      build-profile.json5
  2. 484 141
      entry/src/main/ets/pages/UserCenter.ets
  3. 6 0
      entry/src/main/module.json5

+ 6 - 6
build-profile.json5

@@ -27,13 +27,13 @@
         "name": "default",
         "type": "HarmonyOS",
         "material": {
-          "certpath": "C:\\Users\\Admin\\.ohos\\config\\default_TTMusic_4xE8H3uDhF_yQsHvKPeuQ4Ihr8SeH-FhaPc2QJXUNQQ=.cer",
-          "keyAlias": "debugKey",
-          "keyPassword": "0000001AB642934648E4D9123518000E574FF5EC00A13E807027B43264E70DDE8B55883D4BACDEA39DC0",
-          "profile": "C:\\Users\\Admin\\.ohos\\config\\default_TTMusic_4xE8H3uDhF_yQsHvKPeuQ4Ihr8SeH-FhaPc2QJXUNQQ=.p7b",
+          "storeFile": "D:/chendeben/certs/myp12.p12",
+          "storePassword": "0000001DAA9835AF419CD3787F313188F96B61EA7B785570D4C2E9A4E7AEDE6DAC5522CD6BB3C33173FEFF6C7A",
+          "keyAlias": "chendeben",
+          "keyPassword": "0000001D7D111CE4E687E42E8C20E73447BDC91E7DDD4AA833E4CE7C57CA9041B162E8EDAFDF7521D45A184B07",
           "signAlg": "SHA256withECDSA",
-          "storeFile": "C:\\Users\\Admin\\.ohos\\config\\default_TTMusic_4xE8H3uDhF_yQsHvKPeuQ4Ihr8SeH-FhaPc2QJXUNQQ=.p12",
-          "storePassword": "0000001A6A7B4454B87E22F2B10AE207891C8683F4985FB806FEA863564DC5AC0268C5DA8C17EEF45B4F"
+          "profile": "D:/chendeben/certs/chendeben-debugDebug.p7b",
+          "certpath": "D:/chendeben/certs/chendeben.cer"
         }
       }
     ]

+ 484 - 141
entry/src/main/ets/pages/UserCenter.ets

@@ -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);
+      });
+    });
+  }
 }

+ 6 - 0
entry/src/main/module.json5

@@ -144,6 +144,12 @@
 
 
 
+    ],
+    "metadata": [
+      {
+        "name": "client_id",
+        "value": "6917560202517406526"
+      }
     ]
   }
 }