UserCenter.ets 48 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423
  1. import { router } from '@kit.ArkUI';
  2. import { AppUtil, ToastUtil, PreferencesUtil, StrUtil } from '@pura/harmony-utils';
  3. import { BreakpointTypeEnum } from '../common/util/BreakpointSystem';
  4. import * as wxopensdk from '@tencent/wechat_open_sdk';
  5. import { OnWXResp, WXApi, WXEventHandler } from '../common/util/WXApiWrap';
  6. import { bundleManager, common, ConfigurationConstant } from '@kit.AbilityKit';
  7. import { ErrCode } from '@tencent/wechat_open_sdk';
  8. import { http } from '@kit.NetworkKit';
  9. import { authentication } from '@kit.AccountKit';
  10. import { hilog } from '@kit.PerformanceAnalysisKit';
  11. import { BusinessError, emitter } from '@kit.BasicServicesKit';
  12. import { EventConstants } from '../common/constants/EventConstants';
  13. import { util } from '@kit.ArkTS';
  14. import { DialogHelper } from '@pura/harmony-dialog';
  15. import { Pay } from '@cashier_alipay/cashiersdk';
  16. import { OrderInfoUtil } from '../alipay/OrderInfoUtil';
  17. import { RandomUtil, Base64Util } from '@pura/harmony-utils';
  18. import { CommonConstants } from '../common/constants/CommonConstants';
  19. import { PayReq } from '@tencent/wechat_open_sdk';
  20. import { cryptoFramework } from '@kit.CryptoArchitectureKit';
  21. import json from '@ohos.util.json';
  22. import UserUtil, { VipFeature, VipPlanApi, VipPlanListApiResponse, WeChatPrepayId } from '../common/util/UserUtil';
  23. import { Utility } from '../common/util/Utility';
  24. import { pinyin4js } from '@ohos/pinyin4js';
  25. import { CustomContentDialog } from '@kit.ArkUI';
  26. // 微信支付相关工具方法
  27. async function isHasWX(): Promise<boolean> {
  28. try {
  29. let link = 'weixin://';
  30. let data = bundleManager.canOpenLink(link);
  31. if (data) {
  32. return true;
  33. } else {
  34. ToastUtil.showToast('微信未安装');
  35. return false;
  36. }
  37. } catch (err) {
  38. let message = (err as BusinessError).message;
  39. console.error('canOpenLink failed: %{public}s', message);
  40. return false;
  41. }
  42. }
  43. async function createWeChatOrder(app_id: string, mch_id: string, description: string, amount: number,
  44. out_trade_no_pre: string, uid: number, plan_id: string): Promise<string> {
  45. let requestUrl = CommonConstants.WX_PAY_API
  46. + '?app_id=' + encodeURIComponent(app_id.trim())
  47. + '&mch_id=' + encodeURIComponent(mch_id.trim())
  48. + '&description=' + encodeURIComponent(description.trim())
  49. + '&amount=' + encodeURIComponent(amount)
  50. + '&uid=' + encodeURIComponent(uid)
  51. + '&plan_id=' + encodeURIComponent(plan_id)
  52. + '&out_trade_no_pre=' + encodeURIComponent(out_trade_no_pre);
  53. const httpRequest: http.HttpRequest = http.createHttp();
  54. const options: http.HttpRequestOptions = {
  55. method: http.RequestMethod.GET,
  56. readTimeout: 6000,
  57. connectTimeout: 6000,
  58. };
  59. try {
  60. const response: http.HttpResponse = await httpRequest.request(requestUrl, options);
  61. if (response.responseCode === 200) {
  62. let responseData: WeChatPrepayId;
  63. if (typeof response.result === 'string') {
  64. responseData = JSON.parse(response.result) as WeChatPrepayId;
  65. } else if (response.result instanceof Object) {
  66. responseData = response.result as WeChatPrepayId;
  67. } else {
  68. throw new Error('Unexpected response format');
  69. }
  70. const prepayId: string = responseData.prepay_id;
  71. return prepayId;
  72. } else {
  73. ToastUtil.showToast('微信下单失败');
  74. return '';
  75. }
  76. } catch (error) {
  77. ToastUtil.showToast('微信下单异常');
  78. return '';
  79. }
  80. }
  81. function constructSignatureString(appId: string, timestamp: string, nonceStr: string, prepayId: string): string {
  82. return `${appId}\n${timestamp}\n${nonceStr}\n${prepayId}\n`;
  83. }
  84. async function rsaSign2048(message: string): Promise<Uint8Array> {
  85. try {
  86. let keyGen = cryptoFramework.createAsyKeyGenerator("RSA2048")
  87. let key = keyGen.convertPemKeySync(null, CommonConstants.WX_PAY_RSA_PRIVATE_KEY)
  88. let sign = cryptoFramework.createSign('RSA2048|PKCS1|SHA256');
  89. let msgBlob: cryptoFramework.DataBlob = { data: StrUtil.strToUint8Array(message) }
  90. sign.initSync(key.priKey)
  91. let signature = await sign.sign(msgBlob)
  92. return signature.data
  93. } catch (error) {
  94. console.error(error, `onecold error code: ${error.code}`)
  95. return new Uint8Array()
  96. }
  97. }
  98. // 1. 工具函数:将 #RRGGBB 字符串和透明度转为 'rgba(r,g,b,a)' 字符串,深色模式下可返回深色变体
  99. function themeColorWithAlpha(themeColor: string, alpha: number, isDarkMode: boolean = false): string {
  100. if (isDarkMode) {
  101. // 深色模式下返回更深的灰色或半透明黑色
  102. return `rgba(34,34,34,${alpha + 0.2 > 1 ? 1 : alpha + 0.2})`;
  103. }
  104. const color = themeColor.replace('#', '');
  105. const r = parseInt(color.substring(0, 2), 16);
  106. const g = parseInt(color.substring(2, 4), 16);
  107. const b = parseInt(color.substring(4, 6), 16);
  108. return `rgba(${r},${g},${b},${alpha})`;
  109. }
  110. @Component
  111. // @Entry
  112. export struct UserCenter {
  113. @Consume isShowDrawer: boolean;
  114. @Consume offsetX: number;
  115. @State userName: string = '未登录用户';
  116. @State userId: number = 0;
  117. @State userAvatar: Resource = $r('app.media.icon_person2');
  118. @State userAvatarUrl: string = '';
  119. @State isVip: boolean = false;
  120. @State vipExpire: string = '';
  121. @State isLogin: boolean = false;
  122. @State hasActiveSubscription: boolean = false;
  123. @State subscriptionName: string = '';
  124. @State subscriptionEndDate: string = '';
  125. @State isForever: boolean = false;
  126. @StorageProp('currentBreakpoint') currentBreakpoint: string = BreakpointTypeEnum.MD;
  127. @State vipFeatures: VipFeature[] = [
  128. {
  129. icon: $r('app.media.wusunyinzhi'),
  130. title: '无损音质',
  131. description: '专享无损音乐支持,让每个音符都完美呈现',
  132. isVipOnly: true,
  133. hasCome: true
  134. },
  135. // { icon: $r('app.media.lrc'), title: '歌词下载', description: '支持歌词API在线下载LRC歌词,实现完美同步', isVipOnly: true },
  136. // { icon: $r('app.media.cover'), title: '封面API', description: '支持设置自动下载封面', isVipOnly: true },
  137. {
  138. icon: $r('app.media.theme'),
  139. title: '主题定制',
  140. description: '自定义播放器主题,打造独一无二的个性风格',
  141. isVipOnly: true,
  142. hasCome: true
  143. },
  144. {
  145. icon: $r('app.media.no_ad'),
  146. title: '无广告',
  147. description: '清爽界面,无广告播放器',
  148. isVipOnly: true,
  149. hasCome: true
  150. },
  151. {
  152. icon: $r('app.media.search'),
  153. title: '批量标签内嵌',
  154. description: '一键内嵌音乐封面和歌词',
  155. isVipOnly: true,
  156. hasCome: true
  157. },
  158. {
  159. icon: $r('app.media.skip'),
  160. title: '跳过头尾',
  161. description: '为某个歌单专门定制设置跳过头尾',
  162. isVipOnly: true,
  163. hasCome: true
  164. },
  165. {
  166. icon: $r('app.media.car'),
  167. title: 'HiCAR播放',
  168. description: '专为驾车体验优化的音乐播放模式',
  169. isVipOnly: true,
  170. hasCome: true
  171. },
  172. {
  173. icon: $r('app.media.music_wave'),
  174. title: '调音均衡器',
  175. description: '精准定制音效,带来极致听觉体验',
  176. isVipOnly: true,
  177. hasCome: false
  178. },
  179. ];
  180. @State vipPlans: VipPlanApi[] = [];
  181. @State selectedPayPlan: VipPlanApi | null = null;
  182. @State payType: number = 0; // 0: 微信, 1: 支付宝
  183. @StorageProp('themeColor') themeColor: string = CommonConstants.DEFAULT_THEME_COLOR;
  184. @State isDarkMode: boolean = false
  185. @State webUrl: string = CommonConstants.NEW_MEMBER_AGREEMENTS
  186. @State appName:string = ''
  187. @StorageProp('topRectHeight') topRectHeight: number = px2vp(AppUtil.getStatusBarHeight());
  188. @StorageProp('currentColorMode') @Watch('onColorModeChange') currentMode: number =
  189. ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT;
  190. @State showWxLogin: boolean = false;
  191. @State appIdentifier: string = '';
  192. private wxApi = WXApi
  193. private wxEventHandler = WXEventHandler
  194. // 注销功能相关
  195. deleteAccountDialogController: CustomDialogController = new CustomDialogController({
  196. builder: CustomContentDialog({
  197. primaryTitle: '注销账户',
  198. contentBuilder: () => {
  199. this.deleteAccountDialogContent();
  200. },
  201. buttons: [
  202. {
  203. value: '取消',
  204. action: () => {
  205. this.deleteAccountDialogController.close();
  206. }
  207. },
  208. {
  209. value: '确认注销',
  210. action: () => {
  211. this.handleDeleteAccount();
  212. }
  213. }
  214. ]
  215. }),
  216. autoCancel: true,
  217. alignment: DialogAlignment.Center
  218. })
  219. constructor() {
  220. super();
  221. }
  222. onColorModeChange() {
  223. this.isDarkMode = this.currentMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK
  224. }
  225. // 通过code换取用户信息
  226. async getUserInfoByCode(code: string) {
  227. await UserUtil.getUserInfoByCode(code);
  228. // 登录后同步本地会员到线上(仅本地有会员且线上无会员时)
  229. const expireDate = await Utility.getLocalNobleExpireDate();
  230. if (Utility.isNobleForOld() && !this.hasActiveSubscription && expireDate) {
  231. const success = await UserUtil.syncLocalNobleToServer(expireDate);
  232. if (success) {
  233. ToastUtil.showToast('本地会员已同步到账号');
  234. PreferencesUtil.putSync('isNoble', false);
  235. }
  236. }
  237. await this.fetchUserInfo();
  238. emitter.emit({ eventId: EventConstants.EVENT_USER_STATE_CHANGE }, {})
  239. }
  240. // 通过华为账号信息换取用户信息和会员状态
  241. async getUserInfoByHuawei(openID: string, unionID: string, nickname: string, avatarUri: string) {
  242. await UserUtil.getUserInfoByHuawei(openID, unionID, nickname, avatarUri);
  243. // 登录后同步本地会员到线上(仅本地有会员且线上无会员时)
  244. const expireDate = await Utility.getLocalNobleExpireDate();
  245. if (Utility.isNobleForOld() && !this.hasActiveSubscription && expireDate) {
  246. const success = await UserUtil.syncLocalNobleToServer(expireDate);
  247. if (success) {
  248. ToastUtil.showToast('本地会员已同步到账号');
  249. }
  250. }
  251. await this.fetchUserInfo();
  252. emitter.emit({ eventId: EventConstants.EVENT_USER_STATE_CHANGE }, {})
  253. }
  254. @Builder
  255. syncNobleDialogContentBuilder(): void {
  256. Text('检测到您是本地会员,为保障权益请登录账号,会员权益将自动同步到您的账号。')
  257. .fontSize(16)
  258. .fontColor('#222')
  259. .width('100%')
  260. .textAlign(TextAlign.Center)
  261. .padding(16)
  262. }
  263. aboutToAppear(): void {
  264. Utility.getAppName(getContext(this)).then((appName:string)=>{
  265. this.appName = appName
  266. })
  267. this.getAppIdentifier(); // 获取应用标识符
  268. this.topRectHeight = px2vp(AppUtil.getStatusBarHeight());
  269. let themeColor = PreferencesUtil.getStringSync('THEME_COLOR', CommonConstants.DEFAULT_THEME_COLOR);
  270. this.isDarkMode = this.currentMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK
  271. AppStorage.setOrCreate('themeColor', themeColor);
  272. this.themeColor = themeColor;
  273. this.isLogin = PreferencesUtil.getBooleanSync('isLogin', false);
  274. this.userId = PreferencesUtil.getNumberSync('userId', 0);
  275. this.userName = PreferencesUtil.getStringSync('userName', '未登录用户');
  276. this.userAvatarUrl = PreferencesUtil.getStringSync('userAvatarUrl', '');
  277. this.subscriptionName = PreferencesUtil.getStringSync('subscriptionName', '');
  278. this.subscriptionEndDate = PreferencesUtil.getStringSync('subscriptionEndDate', '');
  279. this.hasActiveSubscription = PreferencesUtil.getBooleanSync('hasActiveSubscription', false);
  280. this.wxEventHandler.registerOnWXRespCallback(this.onWXResp)
  281. this.fetchVipPlans();
  282. if (this.isLogin) {
  283. void this.fetchUserInfo();
  284. }
  285. console.log('Heanup isLogin:' + this.isLogin)
  286. // 只在非同步跳转时弹窗提醒
  287. let params = router.getParams() as Record<string, Object>;
  288. let fromSyncNoble = params && params['fromSyncNoble'] === true;
  289. if (fromSyncNoble) {
  290. if (this.showWxLogin) {
  291. this.showLoginDialog();
  292. } else {
  293. this.huaweiQuickLogin();
  294. }
  295. } else if (!this.isLogin && Utility.isNobleForOld()) {
  296. DialogHelper.showCustomContentDialog({
  297. dialogId: 'syncNobleDialog',
  298. title: '会员同步提示',
  299. contentBuilder: () => {
  300. this.syncNobleDialogContentBuilder();
  301. },
  302. buttons: [
  303. { value: '暂不登录' },
  304. {
  305. value: '去登录',
  306. action: () => {
  307. if (this.showWxLogin) {
  308. this.showLoginDialog();
  309. } else {
  310. this.huaweiQuickLogin();
  311. }
  312. }
  313. }
  314. ]
  315. });
  316. }
  317. }
  318. showLoginDialog() {
  319. DialogHelper.showCustomContentDialog({
  320. dialogId: 'loginDialog',
  321. title: '',
  322. autoCancel: true,
  323. contentBuilder: () => {
  324. this.loginDialogContentBuilder();
  325. },
  326. buttons: []
  327. });
  328. }
  329. // 登录方式选择弹窗内容
  330. @Builder
  331. loginDialogContentBuilder(): void {
  332. Column() {
  333. Row() {
  334. // 微信登录图标按钮
  335. Column() {
  336. Button({ type: ButtonType.Normal, stateEffect: true }) {
  337. Column() {
  338. Image($r('app.media.wx_logo'))
  339. .width(56)
  340. .height(56)
  341. .margin({ bottom: 8 })
  342. Text('微信登录')
  343. .fontSize(15)
  344. .fontWeight(FontWeight.Bold)
  345. .fontColor('#333')
  346. }
  347. .alignItems(HorizontalAlign.Center)
  348. }
  349. .width(100)
  350. .height(100)
  351. // .visibility(PreferencesUtil.getBooleanSync('isShowWX',false)?Visibility.Visible:Visibility.None)
  352. .backgroundColor(Color.White)
  353. // .border({ color: this.themeColor, width: 2 })
  354. .borderRadius(20)
  355. .onClick(async () => {
  356. this.payType = 0;
  357. // 微信登录
  358. let req = new wxopensdk.SendAuthReq
  359. req.isOption1 = false
  360. req.nonAutomatic = true
  361. req.scope = 'snsapi_userinfo,snsapi_friend,snsapi_message,snsapi_contact'
  362. req.state = 'none'
  363. req.transaction = 'test123'
  364. let finished = await this.wxApi.sendReq(getContext(this) as common.UIAbilityContext, req)
  365. console.log('send request finished: ', finished)
  366. DialogHelper.closeDialog('loginDialog');
  367. })
  368. }
  369. .margin({ right: 24 })
  370. // 华为账号登录图标按钮
  371. Column() {
  372. Button({ type: ButtonType.Normal, stateEffect: true }) {
  373. Column() {
  374. Image($r('app.media.huawei'))
  375. .width(56)
  376. .height(56)
  377. .margin({ bottom: 8 })
  378. Text('华为登录')
  379. .fontSize(15)
  380. .fontWeight(FontWeight.Bold)
  381. .fontColor('#333')
  382. }
  383. .alignItems(HorizontalAlign.Center)
  384. }
  385. .width(100)
  386. .height(100)
  387. .backgroundColor(Color.White)
  388. // .border({ color: this.themeColor, width: 2 })
  389. .borderRadius(20)
  390. .onClick(() => {
  391. this.payType = 1;
  392. this.huaweiQuickLogin();
  393. DialogHelper.closeDialog('loginDialog');
  394. })
  395. }
  396. }
  397. .justifyContent(FlexAlign.Center)
  398. .width('100%')
  399. }
  400. .padding({
  401. left: 24,
  402. right: 24,
  403. top: 8,
  404. bottom: 24
  405. })
  406. .width('100%')
  407. }
  408. aboutToDisappear() {
  409. this.wxEventHandler.unregisterOnWXRespCallback(this.onWXResp)
  410. }
  411. build() {
  412. Column() {
  413. // 顶部安全区和自定义标题栏
  414. Column() {
  415. // 顶部安全区
  416. Blank()
  417. .height(this.topRectHeight)
  418. .backgroundColor(this.isDarkMode ? $r('app.color.title_bar_bg') : this.themeColor)
  419. .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP])
  420. // 自定义标题栏(Stack实现绝对居中)
  421. Stack() {
  422. // 居中标题
  423. Text('用户中心')
  424. .fontSize(18)
  425. .fontColor(Color.White)
  426. .align(Alignment.Center)
  427. // 左右按钮
  428. Row() {
  429. Image($r('app.media.menu'))
  430. .width(26)
  431. .height(26)
  432. .margin({ left: 12, right: 8 })
  433. .onClick(() => {
  434. animateTo({ duration: 555 }, () => {
  435. // 动画闭包内控制Image组件的出现和消失
  436. this.isShowDrawer = !this.isShowDrawer
  437. this.offsetX = 0
  438. })
  439. })
  440. Blank().flexGrow(1)
  441. Blank().width(32)
  442. }
  443. .height(48)
  444. .width('100%')
  445. .alignItems(VerticalAlign.Center)
  446. }
  447. .height(48)
  448. .width('100%')
  449. .backgroundColor(this.isDarkMode ? $r('app.color.user_center_card_background') : this.themeColor)
  450. }
  451. Scroll() {
  452. Column() {
  453. this.buildUserInfoCard()
  454. if(!this.isForever){
  455. this.buildVipPlans()
  456. }
  457. this.buildVipFeatures()
  458. // 注销功能区域(仅在登录时显示)
  459. if (this.isLogin) {
  460. this.buildDeleteAccountSection()
  461. }
  462. // this.buildFunctionMenu()
  463. }
  464. .width('100%')
  465. }
  466. .scrollBar(BarState.Off)
  467. .edgeEffect(EdgeEffect.Spring)
  468. .layoutWeight(1)
  469. .padding({
  470. bottom: 45
  471. })
  472. .transition(TransitionEffect.move(TransitionEdge.BOTTOM)
  473. .animation({ duration: 380, curve: Curve.Ease,delay:50 }))
  474. }
  475. .backgroundColor($r('app.color.index_background'))
  476. .width('100%')
  477. .height('100%')
  478. .layoutWeight(1)
  479. }
  480. // 用户信息卡片
  481. @Builder
  482. buildUserInfoCard() {
  483. Column() {
  484. Row() {
  485. Stack({ alignContent: Alignment.BottomEnd }){
  486. Image(this.userAvatarUrl && this.userAvatarUrl.length > 0 ? this.userAvatarUrl : this.userAvatar)
  487. .width(55)
  488. .height(55)
  489. .margin({ left: 12 })
  490. .borderRadius('50%')
  491. .clip(true)
  492. .backgroundColor(this.isDarkMode ? Color.Black : Color.White)
  493. Column() {
  494. Text('VIP')
  495. .fontSize(9)
  496. .padding(2)
  497. .textAlign(TextAlign.Center)
  498. .fontWeight(FontWeight.Bolder)
  499. .fontColor(Color.White)
  500. }
  501. .width(28)
  502. .height(16)
  503. .visibility(Utility.isNoble()?Visibility.Visible:Visibility.None)
  504. .borderRadius(15)
  505. .backgroundColor(this.themeColor)
  506. }
  507. Column() {
  508. Row() {
  509. Text(this.isLogin ? this.userName : '未登录用户')
  510. .fontSize(14)
  511. .fontWeight(FontWeight.Bold)
  512. .padding(5)
  513. .fontColor(this.isDarkMode ? Color.White : Color.Black)
  514. .textAlign(TextAlign.Start)
  515. .maxLines(1)
  516. .width(110)
  517. .margin({ right: 12 })
  518. }
  519. .justifyContent(FlexAlign.Start)
  520. .margin({ top: 2 })
  521. if (this.isLogin) {
  522. if (this.hasActiveSubscription) {
  523. Text(this.subscriptionName)
  524. .fontSize(13)
  525. .padding(5)
  526. .fontColor(themeColorWithAlpha(this.themeColor, 0.8, false))
  527. .textAlign(TextAlign.Start)
  528. Text('到期:' + this.subscriptionEndDate)
  529. .fontSize(11)
  530. .padding(5)
  531. .fontColor(this.isDarkMode ? Color.White : Color.Gray)
  532. .textAlign(TextAlign.Start)
  533. } else {
  534. Text('普通用户')
  535. .fontSize(13)
  536. .padding(5)
  537. .fontColor(this.isDarkMode ? Color.White : Color.Grey)
  538. .textAlign(TextAlign.Start)
  539. }
  540. }
  541. }
  542. .alignItems(HorizontalAlign.Start)
  543. .width(155)
  544. .margin({ left: 10 })
  545. Column() {
  546. if (this.isLogin) {
  547. Button('退出')
  548. .fontColor(Color.White)
  549. .backgroundColor(this.isDarkMode ? $r('app.color.title_bar_bg') : this.themeColor)
  550. .height(36)
  551. .padding({ left:20,right:20,top:10,bottom:10 })
  552. .fontSize(13)
  553. .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5})
  554. .borderRadius(13)
  555. .margin({ left: 0 })
  556. .onClick(() => {
  557. this.isLogin = false
  558. this.userName = '未登录用户'
  559. this.userAvatar = $r('app.media.icon_person2')
  560. this.userAvatarUrl = ''
  561. this.isVip = false
  562. this.vipExpire = ''
  563. this.hasActiveSubscription = false;
  564. this.subscriptionName = '';
  565. this.subscriptionEndDate = '';
  566. this.isForever=false;
  567. UserUtil.logout();
  568. emitter.emit({ eventId: EventConstants.EVENT_USER_STATE_CHANGE }, {})
  569. ToastUtil.showToast('已退出登录')
  570. })
  571. } else {
  572. Button('登录')
  573. .fontColor(Color.White)
  574. .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5})
  575. .backgroundColor(this.isDarkMode ? $r('app.color.title_bar_bg') : this.themeColor)
  576. .height(36)
  577. .padding({ left:20,right:20,top:10,bottom:10 })
  578. .fontSize(13)
  579. .borderRadius(13)
  580. .margin({ right: 20 })
  581. }
  582. }
  583. .alignItems(HorizontalAlign.End)
  584. .margin({ left: 10, right: 10 })
  585. }
  586. .onClick(() => {
  587. if (!this.isLogin) {
  588. if (this.showWxLogin) {
  589. this.showLoginDialog();
  590. } else {
  591. this.huaweiQuickLogin();
  592. }
  593. }
  594. })
  595. .width('100%')
  596. .padding(10)
  597. }
  598. .backgroundColor($r('app.color.user_center_card_background'))
  599. .borderRadius(24)
  600. .margin({ left: 14, right: 14, top: 15 })
  601. .shadow({
  602. radius: 8,
  603. color: 0x11000000,
  604. offsetX: 0,
  605. offsetY: 2
  606. })
  607. }
  608. // 会员权益展示
  609. @Builder
  610. buildVipFeatures(): void {
  611. Column() {
  612. Text('会员权益')
  613. .fontSize(18)
  614. .fontWeight(FontWeight.Bold)
  615. .padding({ top: 24, bottom: 16 })
  616. .fontColor(this.isDarkMode ? Color.White : '#191919')
  617. .textAlign(TextAlign.Center);
  618. Grid() {
  619. ForEach(this.vipFeatures, (feature: VipFeature) => {
  620. GridItem() {
  621. Column() {
  622. Row() {
  623. Blank().layoutWeight(1)
  624. if (feature.hasCome === false) {
  625. Text('开发中')
  626. .fontSize(10)
  627. .fontColor(themeColorWithAlpha(this.themeColor, 0.9, false))
  628. .backgroundColor(themeColorWithAlpha(this.themeColor, 0.15, this.isDarkMode))
  629. .borderRadius(8)
  630. .padding({
  631. left: 6,
  632. right: 6,
  633. top: 2,
  634. bottom: 2
  635. })
  636. .margin({ top: 2, right: 2 })
  637. } else {
  638. Text('已开发')
  639. .fontSize(10)
  640. .fontColor(themeColorWithAlpha(this.themeColor, 0.9, false))
  641. .backgroundColor(themeColorWithAlpha(this.themeColor, 0.15, this.isDarkMode))
  642. .borderRadius(8)
  643. .padding({
  644. left: 6,
  645. right: 6,
  646. top: 2,
  647. bottom: 2
  648. })
  649. .margin({ top: 2, right: 2 })
  650. }
  651. }
  652. Row() {
  653. Blank().layoutWeight(1)
  654. Image(feature.icon)
  655. .width(32)
  656. .height(32)
  657. .fillColor(this.themeColor)
  658. Blank().layoutWeight(1)
  659. }
  660. .margin({ bottom: 8 })
  661. Text(feature.title)
  662. .fontSize(15)
  663. .fontWeight(FontWeight.Bold)
  664. .fontColor(this.isDarkMode ? Color.White : '#191919')
  665. .margin({ bottom: 2 })
  666. Text(feature.description)
  667. .fontSize(12)
  668. .fontColor(this.isDarkMode ? Color.Gray : '#888')
  669. .textAlign(TextAlign.Center)
  670. .maxLines(2)
  671. .margin({ bottom: 4 })
  672. if (feature.isVipOnly) {
  673. Text('VIP专享')
  674. .fontSize(10)
  675. .fontColor(this.isDarkMode ? Color.White : this.themeColor)
  676. .backgroundColor(this.isDarkMode ? $r('app.color.user_center_button_background') :
  677. themeColorWithAlpha(this.themeColor, 0.15, false))
  678. .borderRadius(8)
  679. .padding({
  680. left: 6,
  681. right: 6,
  682. top: 2,
  683. bottom: 2
  684. })
  685. .margin({ top: 2, bottom: 8 })
  686. }
  687. }
  688. .backgroundColor($r('app.color.user_center_card_background'))
  689. .borderRadius(16)
  690. .shadow({
  691. radius: 8,
  692. color: 0x11000000,
  693. offsetX: 0,
  694. offsetY: 2
  695. })
  696. .border({
  697. color: (this.isDarkMode ? $r('app.color.user_center_button_background') :
  698. themeColorWithAlpha(this.themeColor, 0.08, false))//未选中时的边框颜色
  699. , width: 2
  700. })
  701. .padding(12)
  702. .width('100%')
  703. .height(158)
  704. }
  705. })
  706. }
  707. .columnsTemplate('1fr 1fr')
  708. .columnsGap(16)
  709. .rowsGap(16)
  710. .margin({
  711. left: 16,
  712. right: 16,
  713. top: 0,
  714. bottom: 15
  715. })
  716. }
  717. .backgroundColor(this.isDarkMode ? Color.Black : Color.White)
  718. .borderRadius(24)
  719. .margin({
  720. left: 16,
  721. right: 16,
  722. top: 15,
  723. bottom: 15
  724. })
  725. }
  726. // 注销功能区域
  727. @Builder
  728. buildDeleteAccountSection(): void {
  729. Column() {
  730. Row() {
  731. SymbolGlyph($r('sys.symbol.exclamationmark_triangle_fill'))
  732. .fontSize(24)
  733. .fontColor([Color.Red])
  734. .margin({ left: 16 })
  735. Text('危险操作')
  736. .fontSize(18)
  737. .fontWeight(FontWeight.Bold)
  738. .fontColor(Color.Red)
  739. .margin({ left: 12 })
  740. .layoutWeight(1)
  741. }
  742. .width('100%')
  743. .margin({ bottom: 16 })
  744. Button() {
  745. Row() {
  746. SymbolGlyph($r('sys.symbol.trash'))
  747. .fontSize(20)
  748. .fontColor([Color.Red])
  749. .alignSelf(ItemAlign.Center)
  750. .margin({ left: 12 })
  751. Text('注销账户')
  752. .margin({ left: 8 })
  753. .fontSize(15)
  754. .fontColor(Color.Red)
  755. .fontWeight(FontWeight.Medium)
  756. .layoutWeight(1)
  757. Image($r('app.media.arrow_right'))
  758. .width(22)
  759. .height(22)
  760. .margin({ right: 16 })
  761. .fillColor([Color.Red])
  762. }
  763. }
  764. .backgroundColor(Color.Transparent)
  765. .height(55)
  766. .width('100%')
  767. .clickEffect({ level: ClickEffectLevel.HEAVY })
  768. .onClick(() => {
  769. this.deleteAccountDialogController.open();
  770. })
  771. }
  772. .width('100%')
  773. .backgroundColor($r('app.color.user_center_card_background'))
  774. .borderRadius(24)
  775. .margin({
  776. left: 16,
  777. right: 16,
  778. top: 10,
  779. bottom: 20
  780. })
  781. .padding({
  782. top: 20,
  783. bottom: 20
  784. })
  785. .shadow({
  786. radius: 8,
  787. color: 0x11000000,
  788. offsetX: 0,
  789. offsetY: 2
  790. })
  791. }
  792. // 支付方式选择弹窗内容
  793. @Builder
  794. payDialogContentBuilder(): void {
  795. Column() {
  796. Row() {
  797. Image($r('app.media.wx_logo'))
  798. .width(22)
  799. .height(22)
  800. .alignSelf(ItemAlign.Center)
  801. .margin({ left: 25 })
  802. Text('微信支付')
  803. .margin({ left: 10, right: 20 })
  804. .fontSize(15)
  805. .fontColor(Color.Gray)
  806. .fontWeight(480)
  807. Blank()
  808. Checkbox()
  809. .select(this.payType === 0)
  810. .selectedColor(this.themeColor)
  811. .onChange((checked: boolean) => {
  812. if (checked) {
  813. this.payType = 0
  814. }
  815. })
  816. .shape(CheckBoxShape.CIRCLE)
  817. .margin({
  818. left: 20,
  819. top: 8,
  820. bottom: 8,
  821. right: 20
  822. })
  823. .width(22)
  824. .height(22)
  825. }
  826. .onClick(() => {
  827. this.payType = 0
  828. })
  829. .width('100%')
  830. .height(50)
  831. Line().width('100%').height(0.3).backgroundColor($r('app.color.index_tab_unselected_font_color'))
  832. Row() {
  833. Image($r('app.media.alipay'))
  834. .width(22)
  835. .height(22)
  836. .alignSelf(ItemAlign.Center)
  837. .margin({ left: 25 })
  838. Text('支付宝')
  839. .margin({ left: 10, right: 20 })
  840. .fontSize(15)
  841. .fontColor(Color.Gray)
  842. .fontWeight(480)
  843. Blank()
  844. Checkbox()
  845. .select(this.payType === 1)
  846. .selectedColor(this.themeColor)
  847. .onChange((checked: boolean) => {
  848. if (checked) {
  849. this.payType = 1
  850. }
  851. })
  852. .shape(CheckBoxShape.CIRCLE)
  853. .margin({
  854. left: 20,
  855. top: 8,
  856. bottom: 8,
  857. right: 20
  858. })
  859. .width(22)
  860. .height(22)
  861. }
  862. .onClick(() => {
  863. this.payType = 1
  864. })
  865. .width('100%')
  866. .height(50)
  867. }
  868. .padding(24)
  869. .width('100%')
  870. }
  871. // 会员套餐选择
  872. @Builder
  873. buildVipPlans(): void {
  874. Column() {
  875. Text(this.subscriptionName=='永久会员'?'您还可以继续赞助支持我们':'会员套餐')
  876. .textAlign(TextAlign.Start)
  877. .padding({ left: 16, bottom: 15, top: 18 })
  878. .fontSize(18)
  879. .fontColor(this.isDarkMode ? Color.White : '#191919')
  880. .fontWeight(FontWeight.Bold)
  881. .fontFamily('鸿蒙黑体');
  882. // 横向滑动卡片区
  883. List() {
  884. ForEach(this.vipPlans, (plan: VipPlanApi, index: number) => {
  885. ListItem() {
  886. Column() {
  887. Text(this.subscriptionName=='永久会员'?"赞助套餐":plan.name)
  888. .fontSize(15)
  889. .fontWeight(FontWeight.Bold)
  890. .fontColor($r('app.color.user_center_font'))
  891. .margin({ top: 18, bottom: 8 })
  892. .textAlign(TextAlign.Center)
  893. // 价格上下排列
  894. Column() {
  895. Text('¥' + plan.price)
  896. .fontSize(24)
  897. .fontWeight(FontWeight.Bold)
  898. .fontColor(this.themeColor)
  899. .margin({ bottom: (plan.original_price && plan.discount_percent > 0) ? 2 : 0 })
  900. .textAlign(TextAlign.Center)
  901. if (plan.original_price && plan.discount_percent && plan.discount_percent > 0) {
  902. Text('¥' + plan.original_price)
  903. .fontSize(14)
  904. .fontColor(themeColorWithAlpha(this.themeColor, 0.7, false))
  905. .decoration({
  906. type: TextDecorationType.LineThrough,
  907. color: themeColorWithAlpha(this.themeColor, 0.7, false)
  908. })
  909. .fontStyle(FontStyle.Italic)
  910. .textAlign(TextAlign.Center)
  911. }
  912. }
  913. .alignItems(HorizontalAlign.Center)
  914. .margin({ bottom: 6 })
  915. Text(plan.duration === '0' ? '永久会员' : (plan.duration + '天会员'))
  916. .fontSize(13)
  917. .fontColor(this.isDarkMode ? Color.White : Color.Grey)
  918. .maxLines(1)
  919. .textAlign(TextAlign.Center)
  920. }
  921. .backgroundColor(this.selectedPayPlan && this.selectedPayPlan.id === plan.id ?
  922. themeColorWithAlpha(this.themeColor, 0.08, false) : (this.isDarkMode ? Color.Black : '#F7F7F7'))
  923. .borderRadius(18)
  924. .shadow({
  925. radius: 8,
  926. color: 0x11000000,
  927. offsetX: 0,
  928. offsetY: 2
  929. })
  930. .border({
  931. color: this.selectedPayPlan && this.selectedPayPlan.id === plan.id ?
  932. (this.isDarkMode ? Color.White : themeColorWithAlpha(this.themeColor, 0.3, this.isDarkMode))//选中时的边框颜色
  933. :
  934. (this.isDarkMode ? $r('app.color.user_center_button_background') :
  935. themeColorWithAlpha(this.themeColor, 0.08, this.isDarkMode))//未选中时的边框颜色
  936. , width: 2
  937. })
  938. .width(125)
  939. .height(125)
  940. .margin({ left: index === 0 ? 16 : 8, right: index === this.vipPlans.length - 1 ? 16 : 8 })
  941. .onClick(() => {
  942. this.selectedPayPlan = plan;
  943. })
  944. .alignItems(HorizontalAlign.Center)
  945. }
  946. })
  947. }
  948. .listDirection(Axis.Horizontal)
  949. .height(130)
  950. .edgeEffect(EdgeEffect.None)
  951. .scrollBar(BarState.Off)
  952. .margin({ bottom: 18 })
  953. Button(this.isVip ? '升级会员' : '立即开通')
  954. .borderRadius(24)
  955. .backgroundColor(this.isDarkMode ? $r('app.color.user_center_button_background') : this.themeColor)
  956. .width(200)
  957. .fontColor(Color.White)
  958. .fontSize(18)
  959. .padding(12)
  960. .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5})
  961. .fontWeight(FontWeight.Bold)
  962. .margin({ top: 1, bottom: 15 })
  963. .alignSelf(ItemAlign.Center)
  964. .onClick(() => {
  965. if (!this.isLogin) {
  966. ToastUtil.showToast('请先登录');
  967. return;
  968. }
  969. if (!this.selectedPayPlan) {
  970. ToastUtil.showToast('请选择套餐');
  971. return;
  972. }
  973. DialogHelper.showCustomContentDialog({
  974. dialogId: 'payDialog',
  975. title: '请选择支付方式',
  976. autoCancel: true,
  977. contentBuilder: () => {
  978. this.payDialogContentBuilder();
  979. },
  980. buttons: [
  981. { value: '取消' },
  982. {
  983. value: '确认支付',
  984. action: () => {
  985. if (this.selectedPayPlan) {
  986. void this.doPay(this.selectedPayPlan);
  987. }
  988. DialogHelper.closeDialog('payDialog');
  989. }
  990. }
  991. ]
  992. });
  993. });
  994. Row() {
  995. Text('购买前请阅读')
  996. .fontSize(12)
  997. .fontColor(this.isDarkMode ? Color.White : '#888')
  998. Text('《会员协议》')
  999. .fontSize(12)
  1000. .fontColor(this.isDarkMode ? Color.Blue : this.themeColor)
  1001. .onClick(() => {
  1002. router.pushUrl({
  1003. url: 'pages/WebIndex',
  1004. params: { titleName: '会员协议', webUrl: this.webUrl }
  1005. });
  1006. // router.pushUrl({ url: this.webUrl });
  1007. });
  1008. }
  1009. .justifyContent(FlexAlign.Center)
  1010. .width('100%')
  1011. .padding({ bottom: 16 });
  1012. }
  1013. .margin({ left: 14, right: 14, top: 15 })
  1014. .backgroundColor($r('app.color.user_center_card_background'))
  1015. .borderRadius(24)
  1016. }
  1017. // 功能菜单
  1018. @Builder
  1019. buildFunctionMenu(): void {
  1020. Column() {
  1021. this.menuItem($r('app.media.icon_favorite'), '我的收藏', () => {
  1022. if (!this.isLogin) {
  1023. ToastUtil.showToast('请先登录')
  1024. return
  1025. }
  1026. // 跳转到收藏页面
  1027. ToastUtil.showToast('敬请期待')
  1028. })
  1029. Line().width('90%').height(0.5).backgroundColor(0xFFEFEFEF)
  1030. this.menuItem($r('app.media.icon_history'), '播放历史', () => {
  1031. if (!this.isLogin) {
  1032. ToastUtil.showToast('请先登录')
  1033. return
  1034. }
  1035. // 跳转到历史记录页面
  1036. ToastUtil.showToast('敬请期待')
  1037. })
  1038. Line().width('90%').height(0.5).backgroundColor(0xFFEFEFEF)
  1039. this.menuItem($r('app.media.setting_red'), '设置', () => {
  1040. // 跳转到设置页面
  1041. router.pushUrl({
  1042. url: 'pages/SettingPage'
  1043. });
  1044. })
  1045. }
  1046. .backgroundColor(Color.White)
  1047. .borderRadius(24)
  1048. .margin({ left: 20, right: 20, top: 24 })
  1049. }
  1050. // 华为一键登录
  1051. async huaweiQuickLogin() {
  1052. const logTag = 'UserCenter';
  1053. const domainId = 0x0000;
  1054. try {
  1055. const authRequest = new authentication.HuaweiIDProvider().createAuthorizationWithHuaweiIDRequest();
  1056. authRequest.scopes = ['profile'];
  1057. authRequest.permissions = ['idtoken'];
  1058. authRequest.forceAuthorization = true;
  1059. authRequest.state = util.generateRandomUUID();
  1060. authRequest.idTokenSignAlgorithm = authentication.IdTokenSignAlgorithm.PS256;
  1061. const controller = new authentication.AuthenticationController(getContext(this));
  1062. const response: authentication.AuthorizationWithHuaweiIDResponse = await controller.executeRequest(authRequest);
  1063. const credential = response.data;
  1064. const nickName = credential?.nickName ?? '华为用户';
  1065. const avatarUri = credential?.avatarUri ?? '';
  1066. const openID = credential?.openID;
  1067. const unionID = credential?.unionID ?? '';
  1068. // hilog.info(domainId, logTag, `华为登录成功,昵称: ${nickName}, openID: ${openID}`);
  1069. // 调用接口获取用户信息和会员状态
  1070. if (openID) {
  1071. await this.getUserInfoByHuawei(openID, unionID, nickName, avatarUri);
  1072. } else {
  1073. ToastUtil.showToast('openID获取失败');
  1074. }
  1075. } catch (error) {
  1076. hilog.error(domainId, logTag, `华为登录失败: ${error.code}, ${error.message}`);
  1077. this.dealHuaweiLoginError(error as BusinessError);
  1078. ToastUtil.showToast('华为登录失败');
  1079. }
  1080. }
  1081. dealHuaweiLoginError(error: BusinessError): void {
  1082. if (error.code === 1001) {
  1083. ToastUtil.showToast('未登录华为账号');
  1084. } else if (error.code === 1002) {
  1085. ToastUtil.showToast('网络异常');
  1086. } else if (error.code === 1003) {
  1087. ToastUtil.showToast('内部错误');
  1088. } else if (error.code === 1004) {
  1089. ToastUtil.showToast('用户取消授权');
  1090. } else if (error.code === 1005) {
  1091. ToastUtil.showToast('系统服务异常');
  1092. } else if (error.code === 1006) {
  1093. ToastUtil.showToast('请求被拒绝');
  1094. } else if (error.code === 1007) {
  1095. ToastUtil.showToast('无权限');
  1096. } else {
  1097. ToastUtil.showToast('登录失败');
  1098. }
  1099. }
  1100. // 菜单项构建
  1101. @Builder
  1102. menuItem(icon: Resource, text: string, onClick: () => void): void {
  1103. Row() {
  1104. Image(icon)
  1105. .width(22)
  1106. .height(22)
  1107. .alignSelf(ItemAlign.Center)
  1108. .margin({ left: 22 })
  1109. Text(text)
  1110. .margin({ left: 10, right: 20 })
  1111. .fontSize(15)
  1112. .fontColor(Color.Gray)
  1113. .fontWeight(480)
  1114. Blank()
  1115. Image($r('app.media.arrow_right'))
  1116. .width(22)
  1117. .height(22)
  1118. .margin({ left: 20, right: 20 })
  1119. .align(Alignment.Center)
  1120. }
  1121. .width('100%')
  1122. .height(50)
  1123. .onClick(onClick)
  1124. }
  1125. async fetchVipPlans() {
  1126. try {
  1127. const url = 'https://pay.ss5.xyz/subscription/get_list';
  1128. const httpRequest = http.createHttp();
  1129. const options: http.HttpRequestOptions = {
  1130. method: http.RequestMethod.GET,
  1131. readTimeout: 3000,
  1132. connectTimeout: 3000,
  1133. };
  1134. const response: http.HttpResponse = await httpRequest.request(url, options);
  1135. if (response.responseCode === 200) {
  1136. let res = response.result as string;
  1137. let json: VipPlanListApiResponse = JSON.parse(res) as VipPlanListApiResponse;
  1138. if (json.code === 0 && json.data && json.data.plans) {
  1139. this.vipPlans = json.data.plans;
  1140. this.showWxLogin = !!json.data.show_wx_login;
  1141. if (this.vipPlans.length > 0) {
  1142. this.selectedPayPlan = this.vipPlans[0]; // 默认选中第一个套餐
  1143. }
  1144. } else {
  1145. ToastUtil.showToast('套餐数据解析失败');
  1146. }
  1147. } else {
  1148. ToastUtil.showToast('获取套餐失败');
  1149. }
  1150. } catch (e) {
  1151. ToastUtil.showToast('获取套餐异常');
  1152. }
  1153. }
  1154. async doPay(plan: VipPlanApi) {
  1155. if (this.payType === 0) {
  1156. await this.doWxPay(plan);
  1157. } else if (this.payType === 1) {
  1158. await this.doAliPay(plan);
  1159. }
  1160. }
  1161. async doWxPay(plan: VipPlanApi) {
  1162. if (!(await isHasWX())) {
  1163. ToastUtil.showToast('没有安装微信');
  1164. return;
  1165. }
  1166. const appName = PreferencesUtil.getStringSync('appName', 'TTMusic');
  1167. hilog.info(0x0000, 'UserCenter', `doWxPay: current this.userId is ${this.userId}`);
  1168. const prepayId = await createWeChatOrder(CommonConstants.WX_PAY_APPID, CommonConstants.WX_PAY_MCHID,
  1169. appName, Math.round(Number(plan.price) * 100), pinyin4js.getShortPinyin(this.appName), this.userId, plan.id);
  1170. if (prepayId) {
  1171. await this.wechatPay(prepayId);
  1172. } else {
  1173. ToastUtil.showToast('微信下单失败');
  1174. }
  1175. }
  1176. async wechatPay(prepayId: string) {
  1177. let nonceStr = RandomUtil.getRandomStr(16, 'abcdefghijklmnopqrstuvwxyz');
  1178. const timestamp: number = Math.floor(Date.now() / 1000);
  1179. const signString = constructSignatureString(CommonConstants.WX_PAY_APPID, timestamp + '', nonceStr, prepayId);
  1180. let signStr = await rsaSign2048(signString);
  1181. let signStrBase64 = Base64Util.encodeToStrSync(signStr);
  1182. let req = new PayReq();
  1183. req.partnerId = CommonConstants.WX_PAY_MCHID;
  1184. req.appId = CommonConstants.WX_PAY_APPID;
  1185. req.packageValue = 'Sign=WXPay';
  1186. req.prepayId = prepayId;
  1187. req.nonceStr = nonceStr;
  1188. req.timeStamp = timestamp + '';
  1189. req.sign = signStrBase64;
  1190. req.extData = 'extData';
  1191. let finished = await this.wxApi.sendReq(getContext(this) as common.UIAbilityContext, req);
  1192. hilog.info(0x0000, 'UserCenter', 'send request finished: ', finished);
  1193. }
  1194. async doAliPay(plan: VipPlanApi) {
  1195. const appName = PreferencesUtil.getStringSync('appName', 'TTMusic');
  1196. OrderInfoUtil.getOrderInfo(Number(plan.price), appName, this.userId, plan.id).then((orderInfo) => {
  1197. console.log('Heanup: alipay orderInfo = ' + json.stringify(orderInfo))
  1198. new Pay().pay(orderInfo, true).then(async (result) => {
  1199. if (result.get('resultStatus') === '9000') {
  1200. ToastUtil.showToast('支付成功!');
  1201. void this.fetchUserInfo();
  1202. // 可在此处刷新会员状态
  1203. } else {
  1204. ToastUtil.showToast('取消支付!');
  1205. }
  1206. }).catch((error: BusinessError) => {
  1207. ToastUtil.showToast('支付失败!' + error.message);
  1208. });
  1209. });
  1210. }
  1211. // 每次进入页面时刷新用户数据
  1212. async fetchUserInfo() {
  1213. // console.log('Heanup 刷新用户信息:'+token)
  1214. await UserUtil.fetchUserInfo();
  1215. // console.log("Heanup UserInfo:" + json.stringify(user_info))
  1216. this.isLogin = PreferencesUtil.getBooleanSync('isLogin', false);
  1217. this.userId = PreferencesUtil.getNumberSync('userId', 0);
  1218. this.userName = PreferencesUtil.getStringSync('userName', '未登录用户');
  1219. this.userAvatarUrl = PreferencesUtil.getStringSync('userAvatarUrl', '');
  1220. this.subscriptionName = PreferencesUtil.getStringSync('subscriptionName', '');
  1221. this.subscriptionEndDate = PreferencesUtil.getStringSync('subscriptionEndDate', '');
  1222. this.hasActiveSubscription = PreferencesUtil.getBooleanSync('hasActiveSubscription', false);
  1223. this.isForever= PreferencesUtil.getBooleanSync('isForever', false);
  1224. // 新增:同步线上会员状态到本地,便于 Utility.isNoble 全局判断
  1225. PreferencesUtil.putSync('hasActiveSubscription', this.hasActiveSubscription);
  1226. }
  1227. // 微信回调处理
  1228. private onWXResp: OnWXResp = (resp): void => {
  1229. if (resp.errCode === ErrCode.ERR_OK) {
  1230. // 判断回调类型
  1231. if (resp.type === wxopensdk.Command.kCommandSendAuth) {
  1232. // 微信登录成功回调
  1233. const authResp = resp as wxopensdk.SendAuthResp; // 转换为授权响应类型,以便访问code
  1234. this.getUserInfoByCode(resp?.['code'])
  1235. DialogHelper.closeDialog('loginDialog');
  1236. ToastUtil.showToast('登录成功!');
  1237. } else if (resp.type === wxopensdk.Command.kCommandPay) {
  1238. // 微信支付成功回调
  1239. ToastUtil.showToast('支付成功!');
  1240. // 可在此处刷新会员状态或其他支付成功后的业务逻辑
  1241. // this.fetchVipPlans(); // 刷新会员套餐信息
  1242. this.fetchUserInfo();
  1243. } else {
  1244. // 其他类型的成功回调
  1245. ToastUtil.showToast('操作成功!');
  1246. }
  1247. } else {
  1248. // 处理错误情况
  1249. if (resp.type === wxopensdk.Command.kCommandSendAuth) {
  1250. ToastUtil.showToast('微信登录失败!');
  1251. } else if (resp.type === wxopensdk.Command.kCommandPay) {
  1252. ToastUtil.showToast('支付失败!');
  1253. } else {
  1254. ToastUtil.showToast('操作失败!');
  1255. }
  1256. }
  1257. }
  1258. // 获取应用标识符
  1259. getAppIdentifier() {
  1260. let bundleFlags = bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION |
  1261. bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_SIGNATURE_INFO;
  1262. try {
  1263. bundleManager.getBundleInfoForSelf(bundleFlags).then((data) => {
  1264. hilog.info(0x0000, 'UserCenter', 'getBundleInfoForSelf successfully. Data: %{public}s', JSON.stringify(data));
  1265. let inde = JSON.stringify(data)
  1266. this.appIdentifier = inde.substring(inde.indexOf('appIdentifier'),inde.indexOf('"certificate'))
  1267. hilog.info(0x0000, 'UserCenter', 'appIdentifier Data: %{public}s', this.appIdentifier);
  1268. }).catch((err: BusinessError) => {
  1269. hilog.error(0x0000, 'UserCenter', 'getBundleInfoForSelf failed. Cause: %{public}s', err.message);
  1270. });
  1271. } catch (err) {
  1272. let message = (err as BusinessError).message;
  1273. hilog.error(0x0000, 'UserCenter', 'getBundleInfoForSelf failed: %{public}s', message);
  1274. }
  1275. }
  1276. @Builder
  1277. deleteAccountDialogContent() {
  1278. Column() {
  1279. Text('注销账户后将无法恢复,所有相关数据将被永久删除。')
  1280. .fontSize(16)
  1281. .fontColor(Color.Red)
  1282. .textAlign(TextAlign.Start)
  1283. .width('100%')
  1284. .margin({ bottom: 20 })
  1285. // Text('包括但不限于:')
  1286. // .fontSize(14)
  1287. // .fontColor(Color.Gray)
  1288. // .textAlign(TextAlign.Start)
  1289. // .width('100%')
  1290. // .margin({ bottom: 10 })
  1291. //
  1292. // Text('• 个人信息和设置(含订阅套餐)')
  1293. // .fontSize(13)
  1294. // .fontColor(Color.Gray)
  1295. // .textAlign(TextAlign.Start)
  1296. // .width('100%')
  1297. // .margin({ left: 20, bottom: 5 })
  1298. //
  1299. // Text('• 收藏和播放列表')
  1300. // .fontSize(13)
  1301. // .fontColor(Color.Gray)
  1302. // .textAlign(TextAlign.Start)
  1303. // .width('100%')
  1304. // .margin({ left: 20, bottom: 5 })
  1305. //
  1306. // Text('• 播放历史记录')
  1307. // .fontSize(13)
  1308. // .fontColor(Color.Gray)
  1309. // .textAlign(TextAlign.Start)
  1310. // .width('100%')
  1311. // .margin({ left: 20, bottom: 15 })
  1312. Text('请确认您已了解此操作的后果。')
  1313. .fontSize(14)
  1314. .fontColor($r('app.color.text_color'))
  1315. .textAlign(TextAlign.Start)
  1316. .width('100%')
  1317. }
  1318. .width('100%')
  1319. .padding(20)
  1320. }
  1321. async handleDeleteAccount() {
  1322. try {
  1323. // 显示加载提示
  1324. ToastUtil.showToast('正在处理注销请求...');
  1325. // 调用注销API
  1326. const success = await UserUtil.deleteAccount();
  1327. if (success) {
  1328. // 更新页面状态
  1329. this.isLogin = false
  1330. this.userName = '未登录用户'
  1331. this.userAvatar = $r('app.media.icon_person2')
  1332. this.userAvatarUrl = ''
  1333. this.isVip = false
  1334. this.vipExpire = ''
  1335. this.hasActiveSubscription = false;
  1336. this.subscriptionName = '';
  1337. this.subscriptionEndDate = '';
  1338. this.isForever=false;
  1339. ToastUtil.showToast('账户已成功注销');
  1340. this.deleteAccountDialogController.close();
  1341. // 发送用户状态变更事件
  1342. emitter.emit({ eventId: EventConstants.EVENT_USER_STATE_CHANGE }, {})
  1343. }
  1344. } catch (error) {
  1345. hilog.error(0x0000, 'UserCenter', '注销账户失败: %{public}s', error);
  1346. ToastUtil.showToast('注销失败,请稍后重试');
  1347. }
  1348. }
  1349. }