Browse Source

first commit

marxjaw 5 months ago
commit
b7274f791b
100 changed files with 3798 additions and 0 deletions
  1. 12 0
      .gitignore
  2. 10 0
      AppScope/app.json5
  3. 8 0
      AppScope/resources/base/element/string.json
  4. BIN
      AppScope/resources/base/media/app_icon.png
  5. BIN
      AppScope/resources/base/media/background.png
  6. BIN
      AppScope/resources/base/media/foreground.png
  7. 7 0
      AppScope/resources/base/media/layered_image.json
  8. 77 0
      build-profile.json5
  9. 32 0
      code-linter.json5
  10. 6 0
      commons/basic/.gitignore
  11. 59 0
      commons/basic/Index.ets
  12. 28 0
      commons/basic/build-profile.json5
  13. 6 0
      commons/basic/hvigorfile.ts
  14. 23 0
      commons/basic/obfuscation-rules.txt
  15. 55 0
      commons/basic/oh-package-lock.json5
  16. 14 0
      commons/basic/oh-package.json5
  17. 49 0
      commons/basic/src/main/ets/ads/BannerAd.ets
  18. 175 0
      commons/basic/src/main/ets/ads/RewardAd.ets
  19. 109 0
      commons/basic/src/main/ets/ads/StartAd.ets
  20. 64 0
      commons/basic/src/main/ets/component/AgreePrivacy.ets
  21. 35 0
      commons/basic/src/main/ets/component/HomeSearch.ets
  22. 22 0
      commons/basic/src/main/ets/component/ReviseImgHeaderBuilder.ets
  23. 60 0
      commons/basic/src/main/ets/component/ShowBannerAd.ets
  24. 24 0
      commons/basic/src/main/ets/component/ShowJHRewardAd.ets
  25. 52 0
      commons/basic/src/main/ets/component/YTHeader.ets
  26. 29 0
      commons/basic/src/main/ets/component/YtButton.ets
  27. 49 0
      commons/basic/src/main/ets/component/YtDoubleConfirm.ets
  28. 10 0
      commons/basic/src/main/ets/enum/index.ets
  29. 28 0
      commons/basic/src/main/ets/interface/index.ets
  30. 125 0
      commons/basic/src/main/ets/model/index.ets
  31. 20 0
      commons/basic/src/main/ets/pages/Index.ets
  32. 3 0
      commons/basic/src/main/ets/utils/Calc.ets
  33. 24 0
      commons/basic/src/main/ets/utils/CopyText.ets
  34. 10 0
      commons/basic/src/main/ets/utils/DebounceLoad.ets
  35. 10 0
      commons/basic/src/main/ets/utils/FormatDate.ets
  36. 79 0
      commons/basic/src/main/ets/utils/HuaWeiAuthPlugin.ets
  37. 45 0
      commons/basic/src/main/ets/utils/PermissionControl.ets
  38. 22 0
      commons/basic/src/main/ets/utils/TakePicture.ets
  39. 70 0
      commons/basic/src/main/ets/utils/UploadFile.ets
  40. 40 0
      commons/basic/src/main/ets/utils/YTAvoid.ets
  41. 54 0
      commons/basic/src/main/ets/utils/YTBreakPoint.ets
  42. 10 0
      commons/basic/src/main/ets/utils/YTLog.ets
  43. 233 0
      commons/basic/src/main/ets/utils/YTRequest.ets
  44. 29 0
      commons/basic/src/main/ets/utils/YTRouter.ets
  45. 126 0
      commons/basic/src/main/ets/utils/YTToast.ets
  46. 14 0
      commons/basic/src/main/module.json5
  47. 40 0
      commons/basic/src/main/resources/base/element/color.json
  48. 44 0
      commons/basic/src/main/resources/base/element/float.json
  49. 8 0
      commons/basic/src/main/resources/base/element/string.json
  50. BIN
      commons/basic/src/main/resources/base/media/ic_back.png
  51. BIN
      commons/basic/src/main/resources/base/media/search.png
  52. 5 0
      commons/basic/src/main/resources/base/profile/main_pages.json
  53. 35 0
      commons/basic/src/ohosTest/ets/test/Ability.test.ets
  54. 5 0
      commons/basic/src/ohosTest/ets/test/List.test.ets
  55. 13 0
      commons/basic/src/ohosTest/module.json5
  56. 5 0
      commons/basic/src/test/List.test.ets
  57. 33 0
      commons/basic/src/test/LocalUnit.test.ets
  58. 6 0
      features/business/.gitignore
  59. 5 0
      features/business/Index.ets
  60. 28 0
      features/business/build-profile.json5
  61. 6 0
      features/business/hvigorfile.ts
  62. 23 0
      features/business/obfuscation-rules.txt
  63. 79 0
      features/business/oh-package-lock.json5
  64. 13 0
      features/business/oh-package.json5
  65. 27 0
      features/business/src/main/ets/component/YTCard.ets
  66. 31 0
      features/business/src/main/ets/component/YTChat.ets
  67. 14 0
      features/business/src/main/ets/component/YTTag.ets
  68. 201 0
      features/business/src/main/ets/pages/HomePages.ets
  69. 7 0
      features/business/src/main/ets/pages/Index.ets
  70. 3 0
      features/business/src/main/ets/utils/Calc.ets
  71. 14 0
      features/business/src/main/module.json5
  72. 8 0
      features/business/src/main/resources/base/element/float.json
  73. 8 0
      features/business/src/main/resources/base/element/string.json
  74. BIN
      features/business/src/main/resources/base/media/search.png
  75. 5 0
      features/business/src/main/resources/base/profile/main_pages.json
  76. 12 0
      features/business/src/main/resources/base/profile/router_map.json
  77. 35 0
      features/business/src/ohosTest/ets/test/Ability.test.ets
  78. 5 0
      features/business/src/ohosTest/ets/test/List.test.ets
  79. 13 0
      features/business/src/ohosTest/module.json5
  80. 5 0
      features/business/src/test/List.test.ets
  81. 33 0
      features/business/src/test/LocalUnit.test.ets
  82. 6 0
      features/user/.gitignore
  83. 1 0
      features/user/Index.ets
  84. 28 0
      features/user/build-profile.json5
  85. 6 0
      features/user/hvigorfile.ts
  86. 23 0
      features/user/obfuscation-rules.txt
  87. 68 0
      features/user/oh-package-lock.json5
  88. 12 0
      features/user/oh-package.json5
  89. 94 0
      features/user/src/main/ets/component/LoginInput.ets
  90. 152 0
      features/user/src/main/ets/component/Mine.ets
  91. 45 0
      features/user/src/main/ets/component/OtherLoginMethods.ets
  92. 66 0
      features/user/src/main/ets/component/Terms.ets
  93. 4 0
      features/user/src/main/ets/interface/index.ets
  94. 41 0
      features/user/src/main/ets/pages/AgreementPage.ets
  95. 20 0
      features/user/src/main/ets/pages/Index.ets
  96. 100 0
      features/user/src/main/ets/pages/LoginPage.ets
  97. 251 0
      features/user/src/main/ets/pages/SettingPage.ets
  98. 138 0
      features/user/src/main/ets/pages/SuggestionPage.ets
  99. 3 0
      features/user/src/main/ets/utils/Calc.ets
  100. 14 0
      features/user/src/main/module.json5

+ 12 - 0
.gitignore

@@ -0,0 +1,12 @@
+/node_modules
+/oh_modules
+/local.properties
+/.idea
+**/build
+/.hvigor
+.cxx
+/.clangd
+/.clang-format
+/.clang-tidy
+**/.test
+/.appanalyzer

+ 10 - 0
AppScope/app.json5

@@ -0,0 +1,10 @@
+{
+  "app": {
+    "bundleName": "com.ytpm.chattreasure",
+    "vendor": "example",
+    "versionCode": 1000000,
+    "versionName": "1.0.0",
+    "icon": "$media:layered_image",
+    "label": "$string:app_name"
+  }
+}

+ 8 - 0
AppScope/resources/base/element/string.json

@@ -0,0 +1,8 @@
+{
+  "string": [
+    {
+      "name": "app_name",
+      "value": "HarmonyOSChatTreasure"
+    }
+  ]
+}

BIN
AppScope/resources/base/media/app_icon.png


BIN
AppScope/resources/base/media/background.png


BIN
AppScope/resources/base/media/foreground.png


+ 7 - 0
AppScope/resources/base/media/layered_image.json

@@ -0,0 +1,7 @@
+{
+  "layered-image":
+  {
+    "background" : "$media:background",
+    "foreground" : "$media:foreground"
+  }
+}

+ 77 - 0
build-profile.json5

@@ -0,0 +1,77 @@
+{
+  "app": {
+    "signingConfigs": [],
+    "products": [
+      {
+        "name": "default",
+        "signingConfig": "default",
+        "compatibleSdkVersion": "5.0.3(15)",
+        "runtimeOS": "HarmonyOS",
+        "buildOption": {
+          "strictMode": {
+            "caseSensitiveCheck": true,
+            "useNormalizedOHMUrl": true
+          }
+        }
+      }
+    ],
+    "buildModeSet": [
+      {
+        "name": "debug",
+      },
+      {
+        "name": "release"
+      }
+    ]
+  },
+  "modules": [
+    {
+      "name": "entry",
+      "srcPath": "./products/entry",
+      "targets": [
+        {
+          "name": "default",
+          "applyToProducts": [
+            "default"
+          ]
+        }
+      ]
+    },
+    {
+      "name": "basic",
+      "srcPath": "./commons/basic",
+      "targets": [
+        {
+          "name": "default",
+          "applyToProducts": [
+            "default"
+          ]
+        }
+      ]
+    },
+    {
+      "name": "user",
+      "srcPath": "./features/user",
+      "targets": [
+        {
+          "name": "default",
+          "applyToProducts": [
+            "default"
+          ]
+        }
+      ]
+    },
+    {
+      "name": "business",
+      "srcPath": "./features/business",
+      "targets": [
+        {
+          "name": "default",
+          "applyToProducts": [
+            "default"
+          ]
+        }
+      ]
+    }
+  ]
+}

+ 32 - 0
code-linter.json5

@@ -0,0 +1,32 @@
+{
+  "files": [
+    "**/*.ets"
+  ],
+  "ignore": [
+    "**/src/ohosTest/**/*",
+    "**/src/test/**/*",
+    "**/src/mock/**/*",
+    "**/node_modules/**/*",
+    "**/oh_modules/**/*",
+    "**/build/**/*",
+    "**/.preview/**/*"
+  ],
+  "ruleSet": [
+    "plugin:@performance/recommended",
+    "plugin:@typescript-eslint/recommended"
+  ],
+  "rules": {
+    "@security/no-unsafe-aes": "error",
+    "@security/no-unsafe-hash": "error",
+    "@security/no-unsafe-mac": "warn",
+    "@security/no-unsafe-dh": "error",
+    "@security/no-unsafe-dsa": "error",
+    "@security/no-unsafe-ecdsa": "error",
+    "@security/no-unsafe-rsa-encrypt": "error",
+    "@security/no-unsafe-rsa-sign": "error",
+    "@security/no-unsafe-rsa-key": "error",
+    "@security/no-unsafe-dsa-key": "error",
+    "@security/no-unsafe-dh-key": "error",
+    "@security/no-unsafe-3des": "error"
+  }
+}

+ 6 - 0
commons/basic/.gitignore

@@ -0,0 +1,6 @@
+/node_modules
+/oh_modules
+/.preview
+/build
+/.cxx
+/.test

+ 59 - 0
commons/basic/Index.ets

@@ -0,0 +1,59 @@
+export * from '@ibestservices/ibest-ui'
+
+export { reviseImgHeaderBuilder } from './src/main/ets/component/ReviseImgHeaderBuilder'
+
+export { takePicture } from './src/main/ets/utils/TakePicture'
+
+export { copyText } from './src/main/ets/utils/CopyText'
+
+export { Log as YTLog } from '@abner/log'
+
+export { YtDebounce } from "./src/main/ets/utils/DebounceLoad"
+
+export { ShowBannerAd } from "./src/main/ets/component/ShowBannerAd"
+
+export { BannerAd } from "./src/main/ets/ads/BannerAd"
+
+export { ShowJHRewardAd } from "./src/main/ets/component/ShowJHRewardAd"
+
+export { jhStartAd } from "./src/main/ets/ads/StartAd"
+
+export { AdStatus } from "./src/main/ets/enum"
+
+export { permissionControl } from "./src/main/ets/utils/PermissionControl"
+
+export { yTRequest } from "./src/main/ets/utils/YTRequest"
+
+export { huaweiAuthPlugin } from "./src/main/ets/utils/HuaWeiAuthPlugin"
+
+export { yTToast } from "./src/main/ets/utils/YTToast"
+
+export { YtButton } from "./src/main/ets/component/YtButton"
+
+export { YTHeader } from "./src/main/ets/component/YTHeader"
+
+export { Upload } from "./src/main/ets/utils/UploadFile"
+
+export { YtAvoid } from "./src/main/ets/utils/YTAvoid"
+
+export { yTRouter } from "./src/main/ets/utils/YTRouter"
+
+export * from "./src/main/ets/utils/YTBreakPoint"
+
+export  { reqString,avoidType,BarType, ChatCard }  from './src/main/ets/interface'
+
+export * from './src/main/ets/model'
+
+export { HomeSearch } from "./src/main/ets/component/HomeSearch"
+
+
+
+
+
+
+
+
+
+
+
+

+ 28 - 0
commons/basic/build-profile.json5

@@ -0,0 +1,28 @@
+{
+  "apiType": "stageMode",
+  "buildOption": {
+  },
+  "buildOptionSet": [
+    {
+      "name": "release",
+      "arkOptions": {
+        "obfuscation": {
+          "ruleOptions": {
+            "enable": false,
+            "files": [
+              "./obfuscation-rules.txt"
+            ]
+          }
+        }
+      },
+    },
+  ],
+  "targets": [
+    {
+      "name": "default"
+    },
+    {
+      "name": "ohosTest"
+    }
+  ]
+}

+ 6 - 0
commons/basic/hvigorfile.ts

@@ -0,0 +1,6 @@
+import { hspTasks } from '@ohos/hvigor-ohos-plugin';
+
+export default {
+    system: hspTasks,  /* Built-in plugin of Hvigor. It cannot be modified. */
+    plugins:[]         /* Custom plugin to extend the functionality of Hvigor. */
+}

+ 23 - 0
commons/basic/obfuscation-rules.txt

@@ -0,0 +1,23 @@
+# Define project specific obfuscation rules here.
+# You can include the obfuscation configuration files in the current module's build-profile.json5.
+#
+# For more details, see
+#   https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5
+
+# Obfuscation options:
+# -disable-obfuscation: disable all obfuscations
+# -enable-property-obfuscation: obfuscate the property names
+# -enable-toplevel-obfuscation: obfuscate the names in the global scope
+# -compact: remove unnecessary blank spaces and all line feeds
+# -remove-log: remove all console.* statements
+# -print-namecache: print the name cache that contains the mapping from the old names to new names
+# -apply-namecache: reuse the given cache file
+
+# Keep options:
+# -keep-property-name: specifies property names that you want to keep
+# -keep-global-name: specifies names that you want to keep in the global scope
+
+-enable-property-obfuscation
+-enable-toplevel-obfuscation
+-enable-filename-obfuscation
+-enable-export-obfuscation

+ 55 - 0
commons/basic/oh-package-lock.json5

@@ -0,0 +1,55 @@
+{
+  "meta": {
+    "stableOrder": true
+  },
+  "lockfileVersion": 3,
+  "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.",
+  "specifiers": {
+    "@abner/log@^1.0.5": "@abner/log@1.0.5",
+    "@hview/dayjs@^1.11.11": "@hview/dayjs@1.11.11",
+    "@ibestservices/ibest-ui@2.0.6": "@ibestservices/ibest-ui@2.0.6",
+    "@ohos/axios@^2.2.4": "@ohos/axios@2.2.6",
+    "lunar@^1.0.0": "lunar@1.0.0"
+  },
+  "packages": {
+    "@abner/log@1.0.5": {
+      "name": "@abner/log",
+      "version": "1.0.5",
+      "integrity": "sha512-ifkzK9CKO4Rq6+BrYqT0NE5pkFfONWgBtkPTXSqJHn4vzXwEkVIWUl0v8fmgtHE+8XALi3AiBGu0ldqkrgsZ7Q==",
+      "resolved": "https://ohpm.openharmony.cn/ohpm/@abner/log/-/log-1.0.5.har",
+      "registryType": "ohpm"
+    },
+    "@hview/dayjs@1.11.11": {
+      "name": "@hview/dayjs",
+      "version": "1.11.11",
+      "integrity": "sha512-JPJlAbcCS8STHIRnesbAPWCF6eAlIkGx5rDPGT8CesSCMsB6ZJWEH+l3hl0YqtmfofCCVpWrzjBG7Z2f18olAw==",
+      "resolved": "https://ohpm.openharmony.cn/ohpm/@hview/dayjs/-/dayjs-1.11.11.har",
+      "registryType": "ohpm"
+    },
+    "@ibestservices/ibest-ui@2.0.6": {
+      "name": "@ibestservices/ibest-ui",
+      "version": "2.0.6",
+      "integrity": "sha512-xk/RTmg877QgoCJyOztk8J9oHr9ghfiZFMoij8P9CeVnaqb66L+rl82ftS+Lw8rVAju38hAqHHtqpcQOhejxKA==",
+      "resolved": "https://ohpm.openharmony.cn/ohpm/@ibestservices/ibest-ui/-/ibest-ui-2.0.6.har",
+      "registryType": "ohpm",
+      "dependencies": {
+        "@hview/dayjs": "^1.11.11",
+        "lunar": "^1.0.0"
+      }
+    },
+    "@ohos/axios@2.2.6": {
+      "name": "@ohos/axios",
+      "version": "2.2.6",
+      "integrity": "sha512-A1JqGe6XaeqWyjQETitFW4EkubQS7Fv7h0YG5a/ry3/a/vOgVGzwC4y5KAhvMzVv1tYjfY0ntMtV2kJGlmOHcQ==",
+      "resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/axios/-/axios-2.2.6.har",
+      "registryType": "ohpm"
+    },
+    "lunar@1.0.0": {
+      "name": "lunar",
+      "version": "1.0.0",
+      "integrity": "sha512-sAMOxbVr7Sn/QEzEQZHz0CwYjOROTgDLc4842CX2d7f+1D2nlHc6T5jtZJoleIMPZItNJLj0GuOaB/JR+Iwe7Q==",
+      "resolved": "https://ohpm.openharmony.cn/ohpm/lunar/-/lunar-1.0.0.har",
+      "registryType": "ohpm"
+    }
+  }
+}

+ 14 - 0
commons/basic/oh-package.json5

@@ -0,0 +1,14 @@
+{
+  "name": "basic",
+  "version": "1.0.0",
+  "description": "Please describe the basic information.",
+  "main": "Index.ets",
+  "author": "",
+  "license": "Apache-2.0",
+  "packageType": "InterfaceHar",
+  "dependencies": {
+    "@ohos/axios": "^2.2.4",
+    "@abner/log": "^1.0.5",
+    "@ibestservices/ibest-ui": "2.0.6"
+  }
+}

+ 49 - 0
commons/basic/src/main/ets/ads/BannerAd.ets

@@ -0,0 +1,49 @@
+import { advertising } from "@kit.AdsKit";
+
+
+interface BannerParam {
+  adWidth: number,
+  adHeight: number
+}
+
+export class BannerAd {
+  adParam: advertising.AdRequestParams = {
+    // 广告类型:横幅广告
+    adType: 8,
+    // 'testw6vs28auh3'为测试专用的广告位ID,App正式发布时需要改为正式的广告位ID
+    adId: 'testw6vs28auh3',
+    // 广告位宽
+    adWidth: 360,
+    // 广告位高
+    adHeight: 57,
+  };
+  adOptions: advertising.AdOptions = {
+    // 设置广告内容分级上限
+    adContentClassification: 'A'
+  };
+  adDisplayOptions: advertising.AdDisplayOptions = {
+    // 是否静音,默认不静音
+    mute: false,
+    // 广告轮播的时间间隔,单位ms,取值范围[30000, 120000]
+    refreshTime: 30000
+  }
+  ratio: number = 1;
+  adWidth: number = -1;
+  adHeight: number = -1;
+
+  constructor(param?: BannerParam) {
+    if (param && param.adWidth > 0 && param.adHeight > 0) {
+      this.adParam.adWidth = param.adWidth
+      this.adParam.adHeight = param.adHeight
+    }
+    if (this.adParam?.adWidth && typeof (this.adParam?.adWidth) === 'number' && this.adParam?.adWidth > 0) {
+      this.adWidth = this.adParam?.adWidth;
+    }
+    if (this.adParam?.adHeight && typeof (this.adParam?.adHeight) === 'number' && this.adParam?.adHeight > 0) {
+      this.adHeight = this.adParam?.adHeight;
+    }
+    if (this.adWidth > 0 && this.adHeight > 0) {
+      this.ratio = this.adWidth / this.adHeight;
+    }
+  }
+}

+ 175 - 0
commons/basic/src/main/ets/ads/RewardAd.ets

@@ -0,0 +1,175 @@
+import { advertising } from '@kit.AdsKit';
+import { common } from '@kit.AbilityKit';
+import { hilog } from '@kit.PerformanceAnalysisKit';
+import { BusinessError, commonEventManager, emitter } from '@kit.BasicServicesKit';
+import { AdStatus } from '../enum';
+import { YTLog, yTToast } from '../../../../Index';
+import { IBestToast } from '@ibestservices/ibest-ui';
+
+const KEY_REWARD_DATA = "reward_ad_data";
+const KEY_REWARD_STATUS = "reward_ad_status";
+
+export class RewardAd {
+  private ads: Array<advertising.Advertisement> = [];
+  private uIContext: common.UIAbilityContext
+  private load: advertising.AdLoader
+  // 用于保存创建成功的订阅者对象,后续使用其完成订阅及退订的动作
+  private subscriber: commonEventManager.CommonEventSubscriber | null = null;
+  private displayOptions: advertising.AdDisplayOptions = {
+    // 激励广告视频播放是否静音
+    mute: true
+  };
+  private isReward = false
+
+  constructor(uIContext: common.UIAbilityContext) {
+    this.uIContext = uIContext
+    this.load = new advertising.AdLoader(this.uIContext);
+    this.requestAndShowAd(this.load)
+  }
+
+  private requestAndShowAd(adLoader: advertising.AdLoader): void {
+    const adRequestParam: advertising.AdRequestParams = {
+      // 广告类型:激励广告
+      adType: 7,
+      // 'testx9dtjwj8hp'为测试专用的广告位ID,应用正式发布时需要改为正式的广告位ID
+      adId: 'testx9dtjwj8hp',
+    };
+    const adOption: advertising.AdOptions = {
+      // 设置是否请求非个性化广告
+      nonPersonalizedAd: 0,
+      // 是否允许流量下载0:不允许,1:允许,不设置以广告主设置为准
+      allowMobileTraffic: 0,
+      // 是否希望根据 COPPA 的规定将您的内容视为面向儿童的内容: -1默认值,不确定 0不希望 1希望
+      tagForChildProtection: -1,
+      // 是否希望按适合未达到法定承诺年龄的欧洲经济区 (EEA) 用户的方式处理该广告请求: -1默认值,不确定 0不希望 1希望
+      tagForUnderAgeOfPromise: -1,
+      // 设置广告内容分级上限: W: 3+,所有受众 PI: 7+,家长指导 J:12+,青少年 A: 16+/18+,成人受众
+      adContentClassification: 'A'
+    };
+    const adLoaderListener: advertising.AdLoadListener = {
+      onAdLoadFailure: (errorCode: number, errorMsg: string) => {
+        setTimeout(() => {
+          IBestToast.hide()
+          setTimeout(() => {
+            IBestToast.show({ message: '加载失败', type: 'fail' })
+          }, 100)
+        }, 1000)
+      },
+      onAdLoadSuccess: (ads: Array<advertising.Advertisement>) => {
+        hilog.info(0x0000, 'testTag', '%{public}s', `Succeeded in requesting ad`);
+        this.ads.push(...ads);
+        this.showAd()
+      },
+    };
+    adLoader.loadAd(adRequestParam, adOption, adLoaderListener);
+  }
+
+  // 订阅方法,需要在每次展示广告前调用
+  public registerPPSReceiver(): void {
+    if (this.subscriber) {
+      this.unRegisterPPSReceiver();
+    }
+    // 订阅者信息
+    const subscribeInfo: commonEventManager.CommonEventSubscribeInfo = {
+      events: ["com.huawei.hms.pps.action.PPS_REWARD_STATUS_CHANGED"],
+      // publisherBundleName被设置为"com.huawei.hms.adsservice",这意味着只有来自该包名的事件才会被订阅者接受和处理。
+      // 如果没有明确声明publisherBundleName,那么订阅者可能会收到来自其它包名的伪造事件,从而导致安全性问题或误导。
+      publisherBundleName: "com.huawei.hms.adsservice"
+    };
+    // 创建订阅者回调
+    commonEventManager.createSubscriber(subscribeInfo, (err: BusinessError, commonEventSubscriber:
+      commonEventManager.CommonEventSubscriber) => {
+      if (err) {
+        hilog.error(0x0000, 'testTag', '%{public}s',
+          `createSubscriber error, code: ${err.code}, message: ${err.message}`);
+        return;
+      }
+      hilog.info(0x0000, 'testTag', '%{public}s', `Succeeded in creating subscriber`);
+      this.subscriber = commonEventSubscriber;
+      // 订阅公共事件回调
+      if (!this.subscriber) {
+        hilog.warn(0x0000, 'testTag', '%{public}s', `Need create subscriber`);
+        return;
+      }
+      commonEventManager.subscribe(this.subscriber, (err: BusinessError, commonEventSubscriber:
+        commonEventManager.CommonEventData) => {
+        if (err) {
+          hilog.error(0x0000, 'testTag', '%{public}s', `Subscribe error, code: ${err.code}, message: ${err.message}`);
+        } else {
+          hilog.info(0x0000, 'testTag', '%{public}s', 'Subscribe data');
+          const status: string = commonEventSubscriber?.parameters?.[KEY_REWARD_STATUS];
+          switch (status) {
+            case AdStatus.AD_OPEN:
+              hilog.info(0x0000, 'testTag', '%{public}s', `onAdOpen`);
+              //关闭弹窗
+              IBestToast.hide()
+              yTToast.hide()
+              break;
+            case AdStatus.AD_CLICKED:
+              hilog.info(0x0000, 'testTag', '%{public}s', `onAdClick`);
+              break;
+            case AdStatus.AD_CLOSED:
+              hilog.info(0x0000, 'testTag', '%{public}s', `onAdClose`);
+              this.unRegisterPPSReceiver();
+
+              if (this.isReward) {
+                this.isReward = false
+                if (AppStorage.get<number>('RewardNum')! < 5) {
+                  setTimeout(() => {
+                    // yTToast.getReward({ number: 500 })
+                  }, 500)
+                }
+              }
+
+
+              break;
+            case AdStatus.AD_REWARDED:
+              const rewardData: Record<string, string | number> = commonEventSubscriber?.parameters?.[KEY_REWARD_DATA];
+              const rewardType: string = rewardData?.rewardType as string;
+              const rewardAmount: number = rewardData?.rewardAmount as number;
+              //奖励位置
+              this.isReward = true
+              YTLog.info('发送奖励')
+              emitter.emit('getReward')
+
+              break;
+            case AdStatus.AD_VIDEO_START:
+              hilog.info(0x0000, 'testTag', '%{public}s', `onAdVideoStart`);
+              break;
+            case AdStatus.AD_COMPLETED:
+              hilog.info(0x0000, 'testTag', '%{public}s', `onAdCompleted`);
+              break;
+
+            default:
+              IBestToast.hide()
+              IBestToast.show({ message: '加载失败', type: 'fail' })
+              break;
+          }
+        }
+      });
+    });
+  }
+
+  // 取消订阅
+  public unRegisterPPSReceiver(): void {
+    commonEventManager.unsubscribe(this.subscriber, (err: BusinessError) => {
+      if (err) {
+        hilog.error(0x0000, 'testTag', '%{public}s', `Unsubscribe error, code: ${err.code}, message: ${err.message}`);
+      } else {
+        hilog.info(0x0000, 'testTag', '%{public}s', `Succeeded in unsubscribing`);
+        this.subscriber = null;
+      }
+    });
+  }
+
+  //展示广告
+  private showAd() {
+    // 请在此处自行增加步骤2中的,注册激励广告状态监听器
+    this.registerPPSReceiver()
+
+    // 此处ads[0]表示请求到的第一个广告,用户根据实际情况选择
+    advertising.showAd(this.ads[0], this.displayOptions, this.uIContext);
+  }
+}
+
+

+ 109 - 0
commons/basic/src/main/ets/ads/StartAd.ets

@@ -0,0 +1,109 @@
+import { common } from '@kit.AbilityKit';
+import { advertising } from '@kit.AdsKit';
+import { router } from '@kit.ArkUI';
+import { hilog } from '@kit.PerformanceAnalysisKit';
+import { emitter } from '@kit.BasicServicesKit';
+import { YTLog } from '../../../../Index';
+
+export enum AdType {
+  // 开屏广告的类型
+  SPLASH_AD = 1
+}
+
+class JhStartAd {
+  private context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
+  private oaid: string = '';
+  private isTimeOut: boolean = false;
+  // 超时时间(单位毫秒),开发者可根据实际情况修改
+  private timeOutDuration: number = 1 * 1000;
+  // 超时index
+  private timeOutIndex: number = -1;
+  // 广告展示参数
+  declare ads: Array<advertising.Advertisement>
+  adDisplayOptions: advertising.AdDisplayOptions = {
+    // 是否静音,默认不静音
+    mute: false
+  }
+  // 广告配置
+  private adOptions: advertising.AdOptions = {
+    // 是否允许流量下载0:不允许,1:允许,不设置以广告主设置为准
+    allowMobileTraffic: 0,
+    // 是否希望根据 COPPA 的规定将您的内容视为面向儿童的内容: -1默认值,不确定 0不希望 1希望
+    tagForChildProtection: -1,
+    // 是否希望按适合未达到法定承诺年龄的欧洲经济区 (EEA) 用户的方式处理该广告请求: -1默认值,不确定 0不希望 1希望
+    tagForUnderAgeOfPromise: -1,
+    // 设置广告内容分级上限: W: 3+,所有受众 PI: 7+,家长指导 J:12+,青少年 A: 16+/18+,成人受众
+    adContentClassification: 'A'
+  }
+  // 开屏视频广告请求参数
+  private splashVideoAdReqParams: advertising.AdRequestParams = {
+    // 'testd7c5cewoj6'为测试专用的广告位ID,App正式发布时需要改为正式的广告位ID
+    adId: 'testd7c5cewoj6',
+    adType: AdType.SPLASH_AD,
+    adCount: 1,
+    oaid: this.oaid
+  }
+  // 开屏图片广告请求参数
+  private splashImageAdReqParams: advertising.AdRequestParams = {
+    // 'testq6zq98hecj'为测试专用的广告位ID,App正式发布时需要改为正式的广告位ID
+    adId: 'testq6zq98hecj',
+    adType: AdType.SPLASH_AD,
+    adCount: 1,
+    oaid: this.oaid
+  }
+
+  init(UIContext: common.UIAbilityContext, oaid: string) {
+    this.context = UIContext
+    this.oaid = oaid
+    this.requestAd(this.splashImageAdReqParams, this.adOptions)
+  }
+
+  private requestAd(adReqParams: advertising.AdRequestParams, adOptions: advertising.AdOptions): void {
+    // 广告请求回调监听
+    const adLoaderListener: advertising.AdLoadListener = {
+      // 广告请求失败回调
+      onAdLoadFailure: (errorCode: number, errorMsg: string) => {
+        clearTimeout(this.timeOutIndex);
+        YTLog.error(errorCode + ':' + errorMsg)
+
+        if (this.isTimeOut) {
+          return;
+        }
+        emitter.emit('adLoadFail')
+      },
+      // 广告请求成功回调
+      onAdLoadSuccess: (ads: Array<advertising.Advertisement>) => {
+        clearTimeout(this.timeOutIndex);
+        if (this.isTimeOut) {
+          return;
+        }
+        YTLog.info('广告加载成功')
+        if (canIUse("SystemCapability.Advertising.Ads")) {
+          this.ads = ads
+          AppStorage.setOrCreate('isLoaded', true)
+        }
+      }
+    };
+    // 创建AdLoader广告对象
+    const load: advertising.AdLoader = new advertising.AdLoader(this.context);
+    // 调用广告请求接口
+    this.timeOutHandler();
+    load.loadAd(adReqParams, adOptions, adLoaderListener);
+  }
+
+  private timeOutHandler(): void {
+    this.isTimeOut = false;
+    // 超时处理
+    this.timeOutIndex = setTimeout(() => {
+      this.isTimeOut = true;
+      const options: router.RouterOptions = {
+        // 开发者可根据项目实际情况修改超时之后要跳转的目标页面
+        url: 'pages/Index',
+      };
+      router.pushUrl(options);
+      hilog.error(0x0000, 'testTag', '%{public}s', 'load ad time out');
+    }, this.timeOutDuration);
+  }
+}
+
+export const jhStartAd = new JhStartAd()

+ 64 - 0
commons/basic/src/main/ets/component/AgreePrivacy.ets

@@ -0,0 +1,64 @@
+import { IBestToast } from '../../../../Index';
+import { BarType, reqString } from '../interface';
+import { yTRequest } from '../utils/YTRequest';
+import { yTToast } from '../utils/YTToast';
+import { YtButton } from './YtButton';
+
+@Builder
+export function AgreePrivacy(item: BarType<undefined>) {
+  Column() {
+    Text('温馨提示')
+      .fontSize(14)
+      .fontColor('#FF000000')
+      .margin({ top: 14, bottom: 8 })
+    Text('已阅读并同意《用户协议》和《隐私政策》')
+      .fontSize(13)
+      .fontColor($r('sys.color.mask_secondary'))
+      .margin({ bottom: 19 })
+
+
+    Column() {
+      YtButton({
+        btContent: '同意并登入',
+        btHeight: 37,
+        click: () => {
+          yTToast.hide()
+          const loginData: reqString = AppStorage.get<reqString>('captchaLogin')!
+
+          if (item.text == '验证码登录') {
+            if (!loginData.phonenumber || !loginData.captcha) {
+              IBestToast.show({
+                type: "warning",
+                message: "请输入手机号或验证码"
+              })
+              return
+            }
+
+            yTRequest.phonenumberLogin(AppStorage.get<reqString>('captchaLogin')!)
+          }
+          if (item.text == '华为登录') {
+            yTRequest.huaweiLogin()
+          }
+
+
+        }
+      })
+      YtButton({
+        btContent: '不同意',
+        btHeight: 37,
+        bgc: Color.White,
+        btFontColor: '#80000000',
+        click: () => {
+          yTToast.hide()
+        }
+      })
+    }
+    .padding({ left: 16, right: 16 })
+
+  }
+  .width(280)
+  .height(160)
+  .backgroundColor(Color.White)
+  .borderRadius(8)
+  .margin({ bottom: 28 })
+}

+ 35 - 0
commons/basic/src/main/ets/component/HomeSearch.ets

@@ -0,0 +1,35 @@
+@Component
+export struct HomeSearch {
+  @Link searchValue: string
+  commonSearch = () => {
+
+  }
+
+  build() {
+    Row() {
+      Image($r('app.media.search'))
+        .width(24)
+        .aspectRatio(1)
+        .margin({ right: 9, left: 10 })
+      TextInput({ placeholder: '输入TA的对话,获得满分回复', text: $$this.searchValue })
+        .placeholderColor($r('app.color.main_grey'))
+        .placeholderFont({ size: 12 })
+        .fontFamily("PingFang SC")
+        .fontWeight(500)
+        .height(36)
+        .fontSize(12)
+        .layoutWeight(1)
+        .textOverflow(TextOverflow.Ellipsis)
+        .backgroundColor(Color.Transparent)
+        .padding({ left: 0 })
+        .caretColor($r('app.color.main_blue'))
+        .onSubmit(() => {
+          this.commonSearch()
+        })
+    }
+
+    .height(32)
+    .border({ width: 1, color: $r('app.color.main_ac_color_dark') })
+    .borderRadius(16)
+  }
+}

+ 22 - 0
commons/basic/src/main/ets/component/ReviseImgHeaderBuilder.ets

@@ -0,0 +1,22 @@
+import { BarType } from '../interface'
+
+@Builder
+export function reviseImgHeaderBuilder(items: BarType<undefined>[]) {
+  Column() {
+    ForEach(items, (item: BarType<undefined>, index) => {
+      Text(item.text)
+        .width('100%')
+        .height(43)
+        .textAlign(TextAlign.Center)
+        .onClick(item.click)
+      if (index < items.length - 1) {
+        Text('')
+          .width('100%')
+          .height(index == items.length - 2 ? 6 : 1)
+          .backgroundColor('#F8F8F8')
+      }
+    })
+  }
+  .height('100%')
+  .padding({ top: 4 })
+}

+ 60 - 0
commons/basic/src/main/ets/component/ShowBannerAd.ets

@@ -0,0 +1,60 @@
+import { advertising, AutoAdComponent } from '@kit.AdsKit';
+import { BannerAd } from '../ads/BannerAd';
+import { hilog } from '@kit.PerformanceAnalysisKit';
+import { AdStatus } from '../enum';
+
+@Component
+export struct ShowBannerAd {
+  private bannerAd = new BannerAd()
+  @State visibilityState: Visibility = Visibility.Hidden;
+
+  build() {
+    Column() {
+      this.buildBannerView()
+    }
+    .borderRadius(4)
+    // .backgroundColor($r('app.color.main_green'))
+    .height(54)
+    .width('100%')
+    .clip(true)
+  }
+
+  @Builder
+  buildBannerView() {
+    Column() {
+      AutoAdComponent({
+        adParam: this.bannerAd.adParam,
+        adOptions: this.bannerAd.adOptions,
+        displayOptions: this.bannerAd.adDisplayOptions,
+        interactionListener: {
+          onStatusChanged: (status: string, ad: advertising.Advertisement, data: string) => {
+            hilog.info(0x0000, 'testTag', '%{public}s', `status is ${status}`);
+            switch (status) {
+              case AdStatus.AD_OPEN:
+                hilog.info(0x0000, 'testTag', '%{public}s', 'Status is onAdOpen');
+                break;
+              case AdStatus.AD_CLICKED:
+                hilog.info(0x0000, 'testTag', '%{public}s', 'Status is onAdClick');
+                break;
+              case AdStatus.AD_CLOSED:
+                hilog.info(0x0000, 'testTag', '%{public}s', 'Status is onAdClose');
+                this.visibilityState = Visibility.None;
+                break;
+              case AdStatus.AD_LOAD:
+                hilog.info(0x0000, 'testTag', '%{public}s', 'Status is onAdLoad');
+                this.visibilityState = Visibility.Visible;
+                break;
+              case AdStatus.AD_FAIL:
+                hilog.error(0x0000, 'testTag', '%{public}s', 'Status is onAdFail');
+                this.visibilityState = Visibility.None;
+                break;
+            }
+          }
+        }
+      })
+    }
+    .width('100%')
+    .aspectRatio(this.bannerAd.ratio)
+    .visibility(this.visibilityState)
+  }
+}

+ 24 - 0
commons/basic/src/main/ets/component/ShowJHRewardAd.ets

@@ -0,0 +1,24 @@
+import { common } from '@kit.AbilityKit';
+import { RewardAd } from '../ads/RewardAd';
+import { emitter } from '@kit.BasicServicesKit';
+
+
+@Component
+export struct ShowJHRewardAd {
+  private context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
+  @BuilderParam showAdStruct: () => void
+
+  aboutToAppear(): void {
+    emitter.on('showJHAd', () => {
+      new RewardAd(this.context)
+    })
+  }
+
+  aboutToDisappear(): void {
+    emitter.off('showJHAd')
+  }
+
+  build() {
+    this.showAdStruct()
+  }
+}

+ 52 - 0
commons/basic/src/main/ets/component/YTHeader.ets

@@ -0,0 +1,52 @@
+import { yTRouter } from '../utils/YTRouter'
+
+@Component
+export struct YTHeader {
+  @BuilderParam leftComp?: () => void
+  @BuilderParam RightComp?: () => void
+  @BuilderParam CenterComp?: () => void
+  backArrow: boolean = true
+  title: string = ''
+  click = () => {
+    yTRouter.routerBack()
+  }
+
+  build() {
+    Stack() {
+      Row() {
+        if (this.backArrow && !this.leftComp) {
+          Image($r('app.media.ic_back'))
+            .width(24)
+            .margin({ left: 16 })
+            .onClick(this.click)
+        }
+        if (this.leftComp) {
+          this.leftComp()
+        }
+
+        if (this.RightComp) {
+          this.RightComp()
+        }
+      }
+      .width('100%')
+      .justifyContent(this.RightComp ? FlexAlign.SpaceBetween : FlexAlign.Start)
+
+      if (this.CenterComp || this.title)
+      Row() {
+        if (this.CenterComp) {
+          this.CenterComp()
+        }
+        if (this.title && !this.CenterComp) {
+          Text(this.title)
+            .fontSize(18)
+            .fontWeight(700)
+        }
+      }
+      .width('100%')
+      .justifyContent(FlexAlign.Center)
+      .hitTestBehavior(HitTestMode.None)
+    }
+    .height(44)
+    .backgroundColor(Color.White)
+  }
+}

+ 29 - 0
commons/basic/src/main/ets/component/YtButton.ets

@@ -0,0 +1,29 @@
+@Component
+export struct YtButton {
+  btHeight: Length = 37
+  btWidth: Length = '100%'
+  btContent: string = ''
+  click = () => {
+
+  }
+  btPadding?: Length | Padding
+  btFontSize: number = 12
+  bgc: ResourceColor = $r('app.color.main_ac_color_dark')
+  btFontColor: ResourceColor = Color.White
+  btBorder?: BorderOptions
+  btState: boolean = true
+
+  build() {
+    Button(this.btContent)
+      .buttonStyle(ButtonStyleMode.NORMAL)
+      .fontSize($r(`app.float.page_text_font_size_${this.btFontSize}`))
+      .fontColor(this.btFontColor)
+      .backgroundColor(this.bgc)
+      .height(this.btHeight)
+      .width(this.btWidth)
+      .onClick(this.click)
+      .padding(this.btPadding ?? {})
+      .border(this.btBorder)
+      .enabled(this.btState)
+  }
+}

+ 49 - 0
commons/basic/src/main/ets/component/YtDoubleConfirm.ets

@@ -0,0 +1,49 @@
+import { YtButton, yTToast } from '../../../../Index';
+import { BarType } from '../interface';
+
+@Builder
+export function yTDoubleConfirm(item: BarType<undefined>) {
+  Column() {
+    Text(item.text)
+      .fontSize(14)
+      .fontColor('#FF000000')
+      .margin({ bottom: 18 })
+    Text(item.message)
+      .fontColor($r('sys.color.mask_secondary'))
+      .lineHeight(18)
+      .fontSize(13)
+      .margin({ bottom: 18 })
+    Row() {
+      YtButton(
+        {
+          btContent: '确定',
+          btWidth: 108,
+          btHeight: 37,
+          btFontSize: 12,
+          click: item.click
+        }
+      )
+      YtButton(
+        {
+          btContent: '取消',
+          btWidth: 108,
+          btHeight: 37,
+          btFontColor: '#80000000',
+          btBorder: { width: 1, color: '#33000000' },
+          btFontSize: 12,
+          bgc: Color.White,
+          click: () => {
+            yTToast.hide()
+          }
+        }
+      )
+    }
+    .justifyContent(FlexAlign.SpaceBetween)
+    .width('100%')
+  }
+  .height(160)
+  .width(280)
+  .padding({ top: 14, left: 24, right: 24 })
+  .backgroundColor(Color.White)
+  .borderRadius(8)
+}

+ 10 - 0
commons/basic/src/main/ets/enum/index.ets

@@ -0,0 +1,10 @@
+export enum AdStatus {
+  AD_LOAD = 'onAdLoad',
+  AD_FAIL = 'onAdFail',
+  AD_OPEN = 'onAdOpen',
+  AD_CLICKED = 'onAdClick',
+  AD_CLOSED = 'onAdClose',
+  AD_REWARDED = 'onAdReward',
+  AD_VIDEO_START = 'onVideoPlayBegin',
+  AD_COMPLETED = 'onVideoPlayEnd'
+}

+ 28 - 0
commons/basic/src/main/ets/interface/index.ets

@@ -0,0 +1,28 @@
+
+export type reqString = Record<string, string>
+
+export interface avoidType {
+  top?: number,
+  bottom?: number
+}
+
+export interface BarType<T> {
+  src?: ResourceStr,
+  acSrc?: ResourceStr,
+  text?: string,
+  message?: string,
+  click?: () => void,
+  number?: number,
+  state?: T,
+  date?: string,
+  index?: number,
+  finally?: () => void
+}
+
+export interface ChatCard {
+  colorTop:ResourceColor,
+  colorBottom:ResourceColor,
+  urlTop:string,
+  urlBottom:string,
+  click?:()=> void
+}

+ 125 - 0
commons/basic/src/main/ets/model/index.ets

@@ -0,0 +1,125 @@
+import { emitter } from '@kit.BasicServicesKit'
+
+export class UserInfo {
+  KEY: string = 'UserInfo'
+  isLogin: boolean = false
+  private memberName?: string
+  private memberPhone?: string
+  private memberEmail?: string
+  private memberIcon?: string
+  private aiCount?: number
+  private userId?: number
+  private memberScore?: number
+  private token?: string
+
+  setUserInfoAndLogin(_userInfo: UserInfo) {
+    userInfo.isLogin = true
+    if (_userInfo.token) {
+      userInfo.token = _userInfo.token
+    }
+    userInfo.memberEmail = _userInfo.memberEmail
+    userInfo.memberIcon = _userInfo.memberIcon
+    userInfo.memberName = _userInfo.memberName
+    userInfo.userId = _userInfo.userId
+    userInfo.memberScore = _userInfo.memberScore
+    userInfo.aiCount = _userInfo.aiCount
+    userInfo.memberPhone = _userInfo.memberPhone
+    emitter.emit('refreshList')
+    AppStorage.setOrCreate(userInfo.KEY, userInfo)
+  }
+
+  logout() {
+    userInfo.isLogin = false
+    userInfo.token = undefined
+    userInfo.memberEmail = undefined
+    userInfo.memberIcon = undefined
+    userInfo.memberName = undefined
+    userInfo.userId = undefined
+    userInfo.memberScore = undefined
+    userInfo.aiCount = undefined
+    userInfo.memberPhone = undefined
+    emitter.emit('cleanList')
+    AppStorage.setOrCreate(userInfo.KEY, userInfo)
+    AppStorage.setOrCreate('token', undefined)
+  }
+
+  checkLogin() {
+    return userInfo.isLogin
+  }
+
+  getToken() {
+    return userInfo.token
+  }
+
+
+  setToken(token: string) {
+    userInfo.token = token
+    AppStorage.setOrCreate<string>('token', token)
+    AppStorage.setOrCreate(userInfo.KEY, userInfo)
+  }
+
+  getHeadImg() {
+    return userInfo.memberIcon
+  }
+
+  setHeadImg(headImg: string) {
+    userInfo.memberIcon = headImg
+    AppStorage.setOrCreate(userInfo.KEY, userInfo)
+  }
+
+  getEmail() {
+    return userInfo.memberEmail
+  }
+
+  setEmail(email: string) {
+    userInfo.memberEmail = email
+    AppStorage.setOrCreate(userInfo.KEY, userInfo)
+  }
+
+  getName() {
+    return userInfo.memberName
+  }
+
+  setName(name: string) {
+    userInfo.memberName = name
+    AppStorage.setOrCreate(userInfo.KEY, userInfo)
+  }
+
+  getId() {
+    return userInfo.userId
+  }
+
+  setId(id: number) {
+    userInfo.userId = id
+    AppStorage.setOrCreate(userInfo.KEY, userInfo)
+  }
+
+  getGoldNum() {
+    return userInfo.memberScore
+  }
+
+  setGoldNum(goldNum: number) {
+    userInfo.memberScore = goldNum
+    AppStorage.setOrCreate(userInfo.KEY, userInfo)
+  }
+
+  getAiNum() {
+    return userInfo.aiCount
+  }
+
+  setAiNum(aiNum: number) {
+    userInfo.aiCount = aiNum
+    AppStorage.setOrCreate(userInfo.KEY, userInfo)
+  }
+
+  getPhoneNumber() {
+    return userInfo.memberPhone
+  }
+
+  setPhoneNumber(phoneNumber: string) {
+    userInfo.memberPhone = phoneNumber
+    AppStorage.setOrCreate(userInfo.KEY, userInfo)
+  }
+}
+
+export const userInfo = new UserInfo()

+ 20 - 0
commons/basic/src/main/ets/pages/Index.ets

@@ -0,0 +1,20 @@
+@Entry
+@Component
+struct Index {
+  @State message: string = 'Hello World';
+
+  build() {
+    Row() {
+      Column() {
+        Text(this.message)
+          .fontSize(10)
+          .fontWeight(FontWeight.Bold)
+          .onClick(() => {
+            this.message = 'Welcome';
+          })
+      }
+      .width('100%')
+    }
+    .height('100%')
+  }
+}

+ 3 - 0
commons/basic/src/main/ets/utils/Calc.ets

@@ -0,0 +1,3 @@
+export function add(a: number, b: number) {
+  return a + b;
+}

+ 24 - 0
commons/basic/src/main/ets/utils/CopyText.ets

@@ -0,0 +1,24 @@
+import { IBestToast } from '@ibestservices/ibest-ui';
+import { pasteboard } from '@kit.BasicServicesKit';
+
+
+export function copyText(text: string, showToast: boolean = true) {
+  const pasteboardData = pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, text);
+  const systemPasteboard = pasteboard.getSystemPasteboard();
+  systemPasteboard.setData(pasteboardData); // 将数据放入剪切板
+  systemPasteboard.getData().then((data) => {
+    if (data) {
+      if (showToast) {
+        IBestToast.show({
+          type: "success",
+          message: "复制成功"
+        })
+      }
+    } else {
+      IBestToast.show({
+        type: "fail",
+        message: "复制失败"
+      })
+    }
+  })
+}

+ 10 - 0
commons/basic/src/main/ets/utils/DebounceLoad.ets

@@ -0,0 +1,10 @@
+// 防抖函数实现
+export function YtDebounce(del: () => void, delay: number) {
+  let timer: number | null = null;
+  return () => {
+    if (timer) {
+      clearTimeout(timer);
+    }
+    timer = setTimeout(del, delay);
+  };
+}

+ 10 - 0
commons/basic/src/main/ets/utils/FormatDate.ets

@@ -0,0 +1,10 @@
+export function formatDate(date: Date): string {
+  const year = date.getFullYear();
+  const month = (date.getMonth() + 1).toString().padStart(2, '0');
+  const day = date.getDate().toString().padStart(2, '0');
+  const hours = date.getHours().toString().padStart(2, '0');
+  const minutes = date.getMinutes().toString().padStart(2, '0');
+  const seconds = date.getSeconds().toString().padStart(2, '0');
+
+  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
+}

+ 79 - 0
commons/basic/src/main/ets/utils/HuaWeiAuthPlugin.ets

@@ -0,0 +1,79 @@
+import { authentication } from '@kit.AccountKit'
+import { util } from '@kit.ArkTS'
+import { hilog } from '@kit.PerformanceAnalysisKit'
+import { BusinessError } from '@kit.BasicServicesKit'
+import { YTLog } from '../../../../Index'
+
+class HuaweiAuthPlugin {
+  // 代替了用户输入用户名密码
+  async requestAuth() {
+    // 1. 创建一个Account Kit授权请求对象,可通过返回值设置请求参数。
+    const huaweiIdProvider = new authentication.HuaweiIDProvider()
+    const authCreateRequest = huaweiIdProvider.createAuthorizationWithHuaweiIDRequest() // 创建华为授权请求
+    // 2. 添加请求参数
+    // authCreateRequest.scopes = ['phone']
+    authCreateRequest.permissions = ['serviceauthcode']
+    authCreateRequest.forceAuthorization = true
+    // 3. 执行授权请求,获取认证码
+    const authController = new authentication.AuthenticationController(getContext())
+    const authResponse: authentication.AuthorizationWithHuaweiIDResponse =
+      await authController.executeRequest(authCreateRequest) // 执行授权请求
+    const authorizationCode = authResponse.data?.authorizationCode // 华为授权code码(有了它 有了用户信息)
+    return authorizationCode
+  }
+
+  async getPhoneNumber() {
+    // 创建授权请求,并设置参数
+    const authRequest = new authentication.HuaweiIDProvider().createAuthorizationWithHuaweiIDRequest();
+    // 获取手机号需要传如下scope,传参数之前需要先申请对应scope权限,才能返回对应数据
+    authRequest.scopes = ['phone'];
+    // 获取code需传如下permission
+    authRequest.permissions = ['serviceauthcode'];
+    //用户是否需要登录授权,该值为true且用户未登录或未授权时,会拉起用户登录或授权页面
+    authRequest.forceAuthorization = true;
+    // 用于防跨站点请求伪造
+    authRequest.state = util.generateRandomUUID();
+
+    // 执行请求
+    try {
+      const controller = new authentication.AuthenticationController(getContext(this));
+      controller.executeRequest(authRequest).then((data) => {
+        const authorizationWithHuaweiIDResponse = data as authentication.AuthorizationWithHuaweiIDResponse;
+        const state = authorizationWithHuaweiIDResponse.state;
+        if (state && authRequest.state !== state) {
+          hilog.error(0x0000, 'testTag', `Failed to authorize. The state is different, response state: ${state}`);
+          return;
+        }
+        hilog.info(0x0000, 'testTag', 'Succeeded in authentication.');
+        const authorizationWithHuaweiIDCredential = authorizationWithHuaweiIDResponse.data!;
+        const code = authorizationWithHuaweiIDCredential.authorizationCode;
+        const unionID = authorizationWithHuaweiIDCredential.unionID;
+        const openID = authorizationWithHuaweiIDCredential.openID;
+        // 开发者处理code、unionID、openID
+      }).catch((err: BusinessError) => {
+        YTLog.error(err)
+      });
+    } catch (error) {
+      YTLog.error(error)
+    }
+  }
+
+  // getHuaweiIDState  api12 支持
+
+  async cancelAuth() {
+    try {
+      // 1. 创建一个Account Kit授权请求对象,可通过返回值设置请求参数。
+      const huaweiIdProvider = new authentication.HuaweiIDProvider()
+      const authCancelRequest = huaweiIdProvider.createCancelAuthorizationRequest()
+      // 2. 取消授权
+      const authController = new authentication.AuthenticationController(getContext())
+      await authController.executeRequest(authCancelRequest)
+      return true
+    } catch (e) {
+      console.log('yt-logger', JSON.stringify(e))
+      return false
+    }
+  }
+}
+
+export const huaweiAuthPlugin = new HuaweiAuthPlugin()

+ 45 - 0
commons/basic/src/main/ets/utils/PermissionControl.ets

@@ -0,0 +1,45 @@
+import { abilityAccessCtrl, bundleManager, PermissionRequestResult, Permissions } from '@kit.AbilityKit';
+
+class PermissionControl {
+  private tokenID: number = 0
+  //记录已申请权限,方便到时候调用checkPermission时传参
+  permissions: Permissions[] = []
+  //上一次使用checkPermission校验的权限
+  private lastPermission?: Permissions
+  private context?: Context
+  private atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
+
+  //TODO 需要在windowStage中初始化
+  async init(context: Context) {
+    this.context = context
+    const res = await bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION)
+    this.tokenID = res.appInfo.accessTokenId
+  }
+
+  //申请权限
+  askPermission(permissions: Permissions[], callback: (res: PermissionRequestResult) => void) {
+    this.permissions = permissions
+    this.atManager.requestPermissionsFromUser(this.context, permissions, (err, data) => {
+      callback(data)
+    })
+  }
+
+  //二次申请权限 不传参数就是上次校验的权限
+  secondAskPermission(permission: Permissions | undefined = this.lastPermission) {
+    if (permission) {
+      this.atManager.requestPermissionOnSetting(this.context, this.permissions)
+    }
+  }
+
+  //校验权限
+  checkPermission(permission: Permissions) {
+    const isPermission = this.atManager.checkAccessTokenSync(this.tokenID, permission)
+    this.lastPermission = permission
+    if (isPermission == -1) {
+      return false
+    }
+    return true
+  }
+}
+
+export const permissionControl = new PermissionControl()

+ 22 - 0
commons/basic/src/main/ets/utils/TakePicture.ets

@@ -0,0 +1,22 @@
+import { fileIo, fileUri } from '@kit.CoreFileKit';
+import { camera, cameraPicker as picker } from '@kit.CameraKit';
+
+export async function takePicture() {
+  let pathDir = getContext().filesDir;
+  let fileName = `${new Date().getTime()}`
+  let filePath = pathDir + `/${fileName}.png`
+  fileIo.createRandomAccessFileSync(filePath, fileIo.OpenMode.CREATE);
+
+  let uri = fileUri.getUriFromPath(filePath);
+  let pickerProfile: picker.PickerProfile = {
+    cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK,
+    saveUri: uri
+  };
+  let result: picker.PickerResult =
+    await picker.pick(getContext(), [picker.PickerMediaType.PHOTO], //(如果需要录像可以添加) picker.PickerMediaType.VIDEO
+      pickerProfile);
+  if (!result.resultUri) {
+    return Promise.reject('用户未拍照')
+  }
+  return filePath
+}

+ 70 - 0
commons/basic/src/main/ets/utils/UploadFile.ets

@@ -0,0 +1,70 @@
+import { photoAccessHelper } from '@kit.MediaLibraryKit'
+import { util } from '@kit.ArkTS'
+import { fileIo } from '@kit.CoreFileKit'
+import { YTLog } from '../../../../Index'
+
+
+export class Upload {
+  // static uploadImage(context: Context) {
+  //   const option = new photoAccessHelper.PhotoSelectOptions()
+  //   option.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE
+  //   option.maxSelectNumber = 5 // 设置最多选择5张
+  //   option.isOriginalSupported = true
+  //
+  //   const picker = new photoAccessHelper.PhotoViewPicker()
+  //   picker.select(option)
+  //     .then(async res => { // 改为async/await语法
+  //       try {
+  //         const formData = new FormData()
+  //
+  //         // 并行处理多图
+  //         await Promise.all(res.photoUris.map(async (uri, index) => {
+  //           const photoName = util.generateRandomUUID()
+  //           const photoType = '.' + uri.split('.').pop()
+  //           const fullPath = context.cacheDir + '/' + photoName + photoType
+  //
+  //           const photo = fileIo.openSync(uri)
+  //           try {
+  //             await fileIo.copyFile(photo.fd, fullPath)
+  //             formData.append('file' + index, 'internal://cache/' + photoName + photoType)
+  //           } finally {
+  //             fileIo.closeSync(photo.fd) // 确保关闭文件描述符
+  //           }
+  //         }))
+  //
+  //
+  //         // 统一提交所有文件
+  //         // 这里添加实际的上传代码,例如:
+  //         // const response = await axios.post('your_api_url', formData)
+  //         // YTLog.info('Upload success:' + JSON.stringify(response.data))
+  //
+  //       } catch (err) {
+  //         YTLog.error('Upload failed:' + JSON.stringify(err))
+  //       }
+  //     })
+  // }
+
+  static selectImage(context: Context, isReady: (fullPath: string) => void) {
+    const option = new photoAccessHelper.PhotoSelectOptions()
+    option.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE
+    option.maxSelectNumber = 1
+    const picker = new photoAccessHelper.PhotoViewPicker()
+    picker.select(option)
+      .then(res => {
+        const photoName = util.generateRandomUUID()
+        const photoType = '.' + res.photoUris[0].split('.').pop()
+        const fullPath = context.cacheDir + '/' + photoName + photoType
+        const photo = fileIo.openSync(res.photoUris[0])
+        try {
+          fileIo.copyFile(photo.fd, fullPath)
+            .then(() => {
+              YTLog.info(fullPath)
+              isReady(fullPath)
+            })
+        } catch (err) {
+          YTLog.error(err)
+        }
+      })
+  }
+}
+

+ 40 - 0
commons/basic/src/main/ets/utils/YTAvoid.ets

@@ -0,0 +1,40 @@
+import { window } from '@kit.ArkUI'
+import { avoidType } from '../interface'
+
+export class YtAvoid {
+  static readonly safeTopKey: string = 'safeTop'
+  static readonly safeBottomKey: string = 'bottomTop'
+
+ static setAvoid(avoid: avoidType) {
+  if (typeof avoid.top != 'undefined') {
+    AppStorage.setOrCreate<number>(YtAvoid.safeTopKey, px2vp(avoid.top))
+  }
+  if (typeof avoid.bottom != 'undefined') {
+    AppStorage.setOrCreate<number>(YtAvoid.safeBottomKey, px2vp(avoid.bottom))
+  }
+}
+
+  static getTop() {
+    return AppStorage.get<number>(YtAvoid.safeTopKey)
+  }
+
+  static getBottom() {
+
+    return AppStorage.get<number>(YtAvoid.safeBottomKey)
+  }
+
+  static setTop(safeTop: number) {
+    AppStorage.setOrCreate<number>(YtAvoid.safeTopKey, safeTop)
+  }
+
+
+  static setBottom(safeBottom: number) {
+    AppStorage.setOrCreate<number>(YtAvoid.safeBottomKey, safeBottom)
+  }
+
+  static setStatusBarContentColor(color: string, context: Context) {
+    window.getLastWindow(context, (err, windowClass) => {
+      windowClass.setWindowSystemBarProperties({ statusBarContentColor: color })
+    })
+  }
+}

+ 54 - 0
commons/basic/src/main/ets/utils/YTBreakPoint.ets

@@ -0,0 +1,54 @@
+declare interface BreakPointTypeOption<T> {
+  xs?: T
+  sm?: T
+  md?: T
+  lg?: T
+}
+
+export class BreakPointType<T> {
+  options: BreakPointTypeOption<T>
+
+  constructor(option: BreakPointTypeOption<T>) {
+    this.options = option
+  }
+
+  getValue(currentBreakPoint: string) {
+    if (currentBreakPoint === 'xs') {
+      return this.options.xs
+    } else if (currentBreakPoint === 'sm') {
+      return this.options.sm
+    } else if (currentBreakPoint === 'md') {
+      return this.options.md
+    } else if (currentBreakPoint === 'lg') {
+      return this.options.lg
+    } else {
+      return undefined
+    }
+  }
+}
+
+
+class YTBreakPoint {
+  readonly KEY = 'bp'
+
+  setBreakPoint(width: number) {
+    const vpWidth = px2vp(width)
+    let bp = ''
+    if (vpWidth < 320) {
+      bp = 'xs'
+    } else if (vpWidth < 600) {
+      bp = 'sm'
+    } else if (vpWidth < 840) {
+      bp = 'md'
+    } else {
+      bp = 'lg'
+    }
+    AppStorage.setOrCreate<string>(this.KEY, bp)
+  }
+
+  getBreakPoint() {
+    return AppStorage.get<string>(this.KEY)
+  }
+}
+
+export const yTBreakPoint = new YTBreakPoint()

+ 10 - 0
commons/basic/src/main/ets/utils/YTLog.ets

@@ -0,0 +1,10 @@
+import { Log } from '@abner/log'
+
+Log.init({
+  tag: "YTLog",
+  domain: 0x0000,
+  close: false,
+  isHilog: true,
+  showLogLocation: true,
+  logSize: 800
+})

+ 233 - 0
commons/basic/src/main/ets/utils/YTRequest.ets

@@ -0,0 +1,233 @@
+import axios, {
+  AxiosError,
+  AxiosHeaders,
+  AxiosProgressEvent,
+  AxiosRequestConfig,
+  AxiosResponse,
+  FormData,
+  InternalAxiosRequestConfig
+} from '@ohos/axios';
+import { IBestToast, YTLog } from '../../../../Index';
+import { reqString } from '../interface';
+import { UserInfo, userInfo } from '../model/';
+import { formatDate } from './FormatDate';
+import { huaweiAuthPlugin } from './HuaWeiAuthPlugin';
+import { yTRouter } from './YTRouter';
+
+
+export const baseURL: string = 'https://hm-test.ytpm.net/prod-api'
+
+export const instance = axios.create({
+  baseURL,
+  timeout: 5000
+})
+
+// 添加请求拦截器
+instance.interceptors.request.use((config: InternalAxiosRequestConfig) => {
+
+  // 对请求数据做点什么
+  if (AppStorage.get<string>('token')) {
+    config.headers.Authorization = AppStorage.get<string>('token')
+  }
+  return config;
+}, (error: AxiosError) => {
+  // 对请求错误做些什么
+
+  return Promise.reject(error);
+});
+
+
+// 添加响应拦截器
+instance.interceptors.response.use((response: AxiosResponse) => {
+  // 对响应数据做点什么
+  YTLog.info(response)
+  // 对响应错误做点什么
+  if (response.data.code == 401) {
+
+    yTRouter.router2LoginPage()
+    return Promise.reject('401');
+  }
+
+  return response.data.data;
+}, (error: AxiosError) => {
+  YTLog.error(error)
+  setTimeout(() => {
+    IBestToast.hide()
+    setTimeout(() => {
+      IBestToast.show({ message: '请求超时,请检查网络', type: 'fail' })
+    }, 100)
+  }, 1000)
+  return Promise.reject(error);
+});
+
+
+class YtRequest {
+  get<T>(url: string, params?: Record<string, string | number | boolean>,
+    headers?: Record<string, string>): Promise<T> {
+    return instance.get<null, T, null>(url, { params, headers })
+  }
+
+  post<T, D>(url: string, data?: D, params?: Record<string, string | number | boolean>, headers?: AxiosHeaders) {
+    return instance.post<null, T, D>(url, data, { params, headers })
+  }
+
+  upPost<T, D>(url: string, data: D, configs?: AxiosRequestConfig<D>) {
+    return instance.post<string, T, D>(url, data, configs)
+  }
+
+  //获取验证码
+  getCaptcha(phonenumber: string, success: (res: string) => void, fail: (err: Error) => void) {
+    yTRequest.post<reqString, reqString>('/api/aiAccount/member/sendSmsCode',
+      { 'phonenumber': phonenumber })
+      .then(res => {
+        success(res['uuid'])
+      })
+      .catch((err: Error) => {
+        fail(err)
+      })
+  }
+
+  //手机号登录
+  phonenumberLogin(param: reqString) {
+    const uuid = AppStorage.get<string>('uuid')
+    if (uuid !== undefined) {
+      yTRequest.post<reqString, reqString>('/api/aiAccount/member/phoneLogin', {
+        'phonenumber': param['phonenumber'],
+        'smsCode': param['captcha'],
+        'uuid': uuid
+      })
+        .then(res => {
+          userInfo.setToken(res['token'])
+          yTRequest.refreshUserInfo(() => {
+            IBestToast.show({ message: '登录成功', type: 'success' })
+            yTRouter.routerBack()
+          })
+        })
+    }
+  }
+
+  //华为登录
+  huaweiLogin() {
+    try {
+      IBestToast.showLoading()
+      huaweiAuthPlugin.requestAuth()
+        .then(res => {
+          yTRequest.post<reqString, reqString>('/api/aiAccount/member/hmLogin',
+            { 'code': res } as reqString)
+            .then(data => {
+              const token = data['token']
+              userInfo.setToken(token)
+              yTRequest.refreshUserInfo((userInfo) => {
+                // YTLog.info(userInfo)
+                IBestToast.hide()
+                setTimeout(() => {
+                  IBestToast.show({ message: '登录成功', type: 'success' })
+                }, 100)
+
+                yTRouter.routerBack()
+              })
+
+            })
+            .catch((err: Error) => {
+              // AlertDialog.show({ message: JSON.stringify(err, null, 2) })
+              // IBestToast.hide()
+              YTLog.error(err)
+            })
+        })
+        .catch((e: Error) => {
+          // AlertDialog.show({ message: JSON.stringify(e, null, 2) })
+          YTLog.error(e)
+          // IBestToast.hide()
+        })
+    } catch (e) {
+      // AlertDialog.show({ message: JSON.stringify(e, null, 2) })
+      YTLog.error(e)
+      // IBestToast.hide()
+    }
+  }
+
+  //刷新用户信息
+  refreshUserInfo(success?: (res: UserInfo) => void) {
+    yTRequest.post<UserInfo, null>('/api/aiAccount/member/info')
+      .then(res => {
+        userInfo.setUserInfoAndLogin(res)
+        YTLog.info(userInfo)
+        success?.(res)
+      })
+  }
+
+  //上传文件
+  uploadFile(context: Context, fullpath: string, success: (url: string) => void) {
+    const formData = new FormData()
+    formData.append('file', fullpath)
+    yTRequest.upPost<reqString, FormData>('/common/upload', formData, {
+      headers: { 'Content-Type': 'multipart/form-data' },
+      context,
+      onUploadProgress: (progressEvent: AxiosProgressEvent): void => {
+        YTLog.info(progressEvent && progressEvent.loaded && progressEvent.total ?
+          Math.ceil(progressEvent.loaded / progressEvent.total * 100) + '%' : '0%', 'uploadFile');
+      }
+    })
+      .then(res => {
+        const url = res['url']
+        success(url)
+      })
+  }
+
+  // 修改用户头像
+  uploadHeadImg(context: Context, fullpath: string, success: () => void) {
+    const formData = new FormData()
+    formData.append('file', fullpath)
+
+    yTRequest.upPost<reqString, FormData>('/common/upload', formData, {
+      headers: { 'Content-Type': 'multipart/form-data' },
+      context,
+      onUploadProgress: (progressEvent: AxiosProgressEvent): void => {
+        YTLog.info(progressEvent && progressEvent.loaded && progressEvent.total ?
+          Math.ceil(progressEvent.loaded / progressEvent.total * 100) + '%' : '0%', 'uploadFile');
+      }
+    })
+      .then(res => {
+        const url = res['url']
+        yTRequest.post<null, reqString>('/api/aiAccount/member/modifyMemberIcon', { 'memberIcon': url })
+          .then(() => {
+            success()
+          })
+          .catch((e: Error) => {
+            YTLog.error(e)
+            // IBestToast.show({ message: '头像上传失败', type: 'fail' })
+          })
+      })
+      .catch((e: Error) => {
+        YTLog.error(e)
+      })
+  }
+
+  // 修改用户昵称
+  changeNickname(name: string, success: () => void) {
+    yTRequest.post<null, reqString>('/api/aiAccount/member/modifyMemberName', { 'memberName': name })
+      .then(() => {
+        yTRequest.refreshUserInfo(() => {
+          success()
+        })
+      })
+  }
+
+  //问题反馈
+  questionBack(des: string, createBy: string) {
+
+    yTRequest.post<null, reqString>('/api/aiAccount/question/saveQuestion', {
+      'backQuestion': des,
+      'createBy': createBy,
+      'createTime': formatDate(new Date()),
+      'id': userInfo.getId()?.toString()!
+    })
+      .then()
+      .catch((e: Error) => {
+        YTLog.error(e)
+      })
+  }
+}
+
+export const yTRequest = new YtRequest()
+

+ 29 - 0
commons/basic/src/main/ets/utils/YTRouter.ets

@@ -0,0 +1,29 @@
+class YTRouter extends NavPathStack {
+
+
+  router2SettingPage() {
+    yTRouter.pushPathByName('SettingPage', '')
+  }
+
+  router2SuggestionPage() {
+    yTRouter.pushPathByName('SuggestionPage', '')
+  }
+
+  router2AgreementPage(param: '关于我们' | '隐私政策' | '用户协议') {
+    yTRouter.pushPathByName('AgreementPage', param)
+  }
+
+  getAgreementPageParam() {
+    return yTRouter.getParamByName('AgreementPage').pop() as '关于我们' | '隐私政策' | '用户协议'
+  }
+
+  router2LoginPage() {
+    yTRouter.pushPathByName('LoginPage', '')
+  }
+
+  routerBack() {
+    yTRouter.pop()
+  }
+}
+
+export const yTRouter = new YTRouter()

+ 126 - 0
commons/basic/src/main/ets/utils/YTToast.ets

@@ -0,0 +1,126 @@
+import { BusinessError } from '@kit.BasicServicesKit';
+import { ComponentContent, promptAction } from '@kit.ArkUI';
+import { UIContext } from '@ohos.arkui.UIContext';
+import { BarType,  YTLog } from '../../../../Index';
+import { yTDoubleConfirm } from '../component/YtDoubleConfirm';
+import { AgreePrivacy } from '../component/AgreePrivacy';
+
+
+interface InitOption {
+  context: UIContext;
+  options: promptAction.BaseDialogOptions;
+}
+
+class Params<T> implements BarType<T> {
+  src?: ResourceStr
+  acSrc?: ResourceStr
+  text?: string
+  index?: number
+  number?: number | undefined
+  message?: string
+  click?: () => void
+
+  constructor(item: BarType<undefined>) {
+    this.src = item.src;
+    this.text = item.text;
+    this.index = item.index;
+    this.number = item.number;
+    this.click = item.click;
+    this.message = item.message;
+    this.acSrc = item.acSrc;
+  }
+}
+
+export class YTToast {
+  private declare ctx: UIContext;
+  private declare contentNode: ComponentContent<Object> | null;
+  private declare options: promptAction.BaseDialogOptions;
+
+  init(initOption: InitOption) {
+    this.ctx = initOption.context;
+    this.options = initOption.options;
+  }
+
+  setContext(context: UIContext) {
+    this.ctx = context;
+  }
+
+  setContentNode(node: ComponentContent<Object>) {
+    this.contentNode = node;
+  }
+
+  setOptions(options: promptAction.BaseDialogOptions) {
+    this.options = options;
+  }
+
+  openToast(builder: WrappedBuilder<[
+    BarType<undefined>
+  ]>, item: BarType<undefined>) {
+    this.contentNode =
+      new ComponentContent(this.ctx, builder, new Params<undefined>(item));
+    this.ctx.getPromptAction()
+      .openCustomDialog(this.contentNode, this.options)
+      .then(() => {
+        console.info('OpenCustomDialog complete.')
+      })
+      .catch((error: BusinessError) => {
+        let message = (error as BusinessError).message;
+        let code = (error as BusinessError).code;
+        console.error(`OpenCustomDialog args error code is ${code}, message is ${message}`);
+      })
+  }
+
+  doubleConfirm(item: BarType<undefined>) {
+    this.openToast(wrapBuilder(yTDoubleConfirm), item)
+  }
+  //
+  // getReward(item: BarType<undefined>) {
+  //   this.openToast(wrapBuilder(rewardTreasure), item)
+  // }
+  //
+  agreePrivacy(item: BarType<undefined>) {
+    this.openToast(wrapBuilder(AgreePrivacy), item)
+  }
+  //
+  // showBillImage(item: BarType<undefined>) {
+  //   this.openToast(wrapBuilder(showBillImage), item)
+  // }
+
+
+  hide() {
+    try {
+      if (this.contentNode !== null) {
+        this.ctx.getPromptAction()
+          .closeCustomDialog(this.contentNode)
+          .then(() => {
+            this.contentNode = null
+            console.info('CloseCustomDialog complete.')
+          })
+          .catch((error: BusinessError) => {
+            let message = (error as BusinessError).message;
+            let code = (error as BusinessError).code;
+            console.error(`CloseCustomDialog args error code is ${code}, message is ${message}`);
+          })
+      }
+    } catch (e) {
+      YTLog.warn(e)
+    }
+  }
+
+  updateDialog(options: promptAction.BaseDialogOptions) {
+    if (this.contentNode !== null) {
+      this.ctx.getPromptAction()
+        .updateCustomDialog(this.contentNode, options)
+        .then(() => {
+          console.info('UpdateCustomDialog complete.')
+        })
+        .catch((error: BusinessError) => {
+          let message = (error as BusinessError).message;
+          let code = (error as BusinessError).code;
+          console.error(`UpdateCustomDialog args error code is ${code}, message is ${message}`);
+        })
+    }
+  }
+}
+
+export const yTToast = new YTToast()

+ 14 - 0
commons/basic/src/main/module.json5

@@ -0,0 +1,14 @@
+{
+  "module": {
+    "name": "basic",
+    "type": "shared",
+    "description": "$string:shared_desc",
+    "deviceTypes": [
+      "phone",
+      "tablet",
+      "2in1"
+    ],
+    "deliveryWithInstall": true,
+    "pages": "$profile:main_pages"
+  }
+}

+ 40 - 0
commons/basic/src/main/resources/base/element/color.json

@@ -0,0 +1,40 @@
+{
+  "color": [
+    {
+      "name": "main_ac_color_light",
+      "value": "#FFE4943C"
+    },
+    {
+      "name": "main_ac_color_dark",
+      "value": "#0DB5F7"
+    },
+    {
+      "name": "main_na_color",
+      "value": "#FFC4C4C4"
+    },
+    {
+      "name": "mask_color",
+      "value": "#80000000"
+    },
+    {
+      "name": "main_blank",
+      "value": "#FF161718"
+    },
+    {
+      "name": "main_blue",
+      "value": "#0DB5F7"
+    },
+    {
+      "name": "main_green",
+      "value": "#95BFBF"
+    },
+    {
+      "name": "main_grey",
+      "value": "#C4C4C4"
+    },
+    {
+      "name": "bar_back_color",
+      "value": "#f4f4f4"
+    }
+  ]
+}

+ 44 - 0
commons/basic/src/main/resources/base/element/float.json

@@ -0,0 +1,44 @@
+{
+  "float": [
+    {
+      "name": "page_text_font_size_8",
+      "value": "8vp"
+    },
+    {
+      "name": "page_text_font_size_9",
+      "value": "8.8vp"
+    },
+    {
+      "name": "page_text_font_size_10",
+      "value": "10vp"
+    },
+    {
+      "name": "page_text_font_size_11",
+      "value": "11vp"
+    },
+    {
+      "name": "page_text_font_size_12",
+      "value": "12vp"
+    },
+    {
+      "name": "page_text_font_size_13",
+      "value": "13vp"
+    },
+    {
+      "name": "page_text_font_size_14",
+      "value": "14vp"
+    },
+    {
+      "name": "page_text_font_size_15",
+      "value": "15vp"
+    },
+    {
+      "name": "page_text_font_size_16",
+      "value": "16vp"
+    },
+    {
+      "name": "page_text_font_size_20",
+      "value": "20vp"
+    }
+  ]
+}

+ 8 - 0
commons/basic/src/main/resources/base/element/string.json

@@ -0,0 +1,8 @@
+{
+  "string": [
+    {
+      "name": "shared_desc",
+      "value": "公共模块存储公用组件"
+    }
+  ]
+}

BIN
commons/basic/src/main/resources/base/media/ic_back.png


BIN
commons/basic/src/main/resources/base/media/search.png


+ 5 - 0
commons/basic/src/main/resources/base/profile/main_pages.json

@@ -0,0 +1,5 @@
+{
+  "src": [
+    "pages/Index"
+  ]
+}

+ 35 - 0
commons/basic/src/ohosTest/ets/test/Ability.test.ets

@@ -0,0 +1,35 @@
+import { hilog } from '@kit.PerformanceAnalysisKit';
+import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium';
+
+export default function abilityTest() {
+  describe('ActsAbilityTest', () => {
+    // Defines a test suite. Two parameters are supported: test suite name and test suite function.
+    beforeAll(() => {
+      // Presets an action, which is performed only once before all test cases of the test suite start.
+      // This API supports only one parameter: preset action function.
+    })
+    beforeEach(() => {
+      // Presets an action, which is performed before each unit test case starts.
+      // The number of execution times is the same as the number of test cases defined by **it**.
+      // This API supports only one parameter: preset action function.
+    })
+    afterEach(() => {
+      // Presets a clear action, which is performed after each unit test case ends.
+      // The number of execution times is the same as the number of test cases defined by **it**.
+      // This API supports only one parameter: clear action function.
+    })
+    afterAll(() => {
+      // Presets a clear action, which is performed after all test cases of the test suite end.
+      // This API supports only one parameter: clear action function.
+    })
+    it('assertContain', 0, () => {
+      // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function.
+      hilog.info(0x0000, 'testTag', '%{public}s', 'it begin');
+      let a = 'abc';
+      let b = 'b';
+      // Defines a variety of assertion methods, which are used to declare expected boolean conditions.
+      expect(a).assertContain(b);
+      expect(a).assertEqual(a);
+    })
+  })
+}

+ 5 - 0
commons/basic/src/ohosTest/ets/test/List.test.ets

@@ -0,0 +1,5 @@
+import abilityTest from './Ability.test';
+
+export default function testsuite() {
+  abilityTest();
+}

+ 13 - 0
commons/basic/src/ohosTest/module.json5

@@ -0,0 +1,13 @@
+{
+  "module": {
+    "name": "basic_test",
+    "type": "feature",
+    "deviceTypes": [
+      "phone",
+      "tablet",
+      "2in1"
+    ],
+    "deliveryWithInstall": true,
+    "installationFree": false
+  }
+}

+ 5 - 0
commons/basic/src/test/List.test.ets

@@ -0,0 +1,5 @@
+import localUnitTest from './LocalUnit.test';
+
+export default function testsuite() {
+  localUnitTest();
+}

+ 33 - 0
commons/basic/src/test/LocalUnit.test.ets

@@ -0,0 +1,33 @@
+import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium';
+
+export default function localUnitTest() {
+  describe('localUnitTest', () => {
+    // Defines a test suite. Two parameters are supported: test suite name and test suite function.
+    beforeAll(() => {
+      // Presets an action, which is performed only once before all test cases of the test suite start.
+      // This API supports only one parameter: preset action function.
+    });
+    beforeEach(() => {
+      // Presets an action, which is performed before each unit test case starts.
+      // The number of execution times is the same as the number of test cases defined by **it**.
+      // This API supports only one parameter: preset action function.
+    });
+    afterEach(() => {
+      // Presets a clear action, which is performed after each unit test case ends.
+      // The number of execution times is the same as the number of test cases defined by **it**.
+      // This API supports only one parameter: clear action function.
+    });
+    afterAll(() => {
+      // Presets a clear action, which is performed after all test cases of the test suite end.
+      // This API supports only one parameter: clear action function.
+    });
+    it('assertContain', 0, () => {
+      // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function.
+      let a = 'abc';
+      let b = 'b';
+      // Defines a variety of assertion methods, which are used to declare expected boolean conditions.
+      expect(a).assertContain(b);
+      expect(a).assertEqual(a);
+    });
+  });
+}

+ 6 - 0
features/business/.gitignore

@@ -0,0 +1,6 @@
+/node_modules
+/oh_modules
+/.preview
+/build
+/.cxx
+/.test

+ 5 - 0
features/business/Index.ets

@@ -0,0 +1,5 @@
+export { add } from './src/main/ets/utils/Calc';
+export  { HomePages } from './src/main/ets/pages/HomePages';
+export {YTChat} from './src/main/ets/component/YTChat'
+export {YTCard} from './src/main/ets/component/YTCard'
+export {YTTag} from './src/main/ets/component/YTTag'

+ 28 - 0
features/business/build-profile.json5

@@ -0,0 +1,28 @@
+{
+  "apiType": "stageMode",
+  "buildOption": {
+  },
+  "buildOptionSet": [
+    {
+      "name": "release",
+      "arkOptions": {
+        "obfuscation": {
+          "ruleOptions": {
+            "enable": false,
+            "files": [
+              "./obfuscation-rules.txt"
+            ]
+          }
+        }
+      },
+    },
+  ],
+  "targets": [
+    {
+      "name": "default"
+    },
+    {
+      "name": "ohosTest"
+    }
+  ]
+}

+ 6 - 0
features/business/hvigorfile.ts

@@ -0,0 +1,6 @@
+import { hspTasks } from '@ohos/hvigor-ohos-plugin';
+
+export default {
+    system: hspTasks,  /* Built-in plugin of Hvigor. It cannot be modified. */
+    plugins:[]         /* Custom plugin to extend the functionality of Hvigor. */
+}

+ 23 - 0
features/business/obfuscation-rules.txt

@@ -0,0 +1,23 @@
+# Define project specific obfuscation rules here.
+# You can include the obfuscation configuration files in the current module's build-profile.json5.
+#
+# For more details, see
+#   https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5
+
+# Obfuscation options:
+# -disable-obfuscation: disable all obfuscations
+# -enable-property-obfuscation: obfuscate the property names
+# -enable-toplevel-obfuscation: obfuscate the names in the global scope
+# -compact: remove unnecessary blank spaces and all line feeds
+# -remove-log: remove all console.* statements
+# -print-namecache: print the name cache that contains the mapping from the old names to new names
+# -apply-namecache: reuse the given cache file
+
+# Keep options:
+# -keep-property-name: specifies property names that you want to keep
+# -keep-global-name: specifies names that you want to keep in the global scope
+
+-enable-property-obfuscation
+-enable-toplevel-obfuscation
+-enable-filename-obfuscation
+-enable-export-obfuscation

+ 79 - 0
features/business/oh-package-lock.json5

@@ -0,0 +1,79 @@
+{
+  "meta": {
+    "stableOrder": true
+  },
+  "lockfileVersion": 3,
+  "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.",
+  "specifiers": {
+    "@abner/log@^1.0.5": "@abner/log@1.0.5",
+    "@hview/dayjs@^1.11.11": "@hview/dayjs@1.11.11",
+    "@ibestservices/ibest-ui@2.0.6": "@ibestservices/ibest-ui@2.0.6",
+    "@ohos/axios@^2.2.4": "@ohos/axios@2.2.6",
+    "basic@../../commons/basic": "basic@../../commons/basic",
+    "lunar@^1.0.0": "lunar@1.0.0",
+    "user@../user": "user@../user"
+  },
+  "packages": {
+    "@abner/log@1.0.5": {
+      "name": "@abner/log",
+      "version": "1.0.5",
+      "integrity": "sha512-ifkzK9CKO4Rq6+BrYqT0NE5pkFfONWgBtkPTXSqJHn4vzXwEkVIWUl0v8fmgtHE+8XALi3AiBGu0ldqkrgsZ7Q==",
+      "resolved": "https://ohpm.openharmony.cn/ohpm/@abner/log/-/log-1.0.5.har",
+      "registryType": "ohpm"
+    },
+    "@hview/dayjs@1.11.11": {
+      "name": "@hview/dayjs",
+      "version": "1.11.11",
+      "integrity": "sha512-JPJlAbcCS8STHIRnesbAPWCF6eAlIkGx5rDPGT8CesSCMsB6ZJWEH+l3hl0YqtmfofCCVpWrzjBG7Z2f18olAw==",
+      "resolved": "https://ohpm.openharmony.cn/ohpm/@hview/dayjs/-/dayjs-1.11.11.har",
+      "registryType": "ohpm"
+    },
+    "@ibestservices/ibest-ui@2.0.6": {
+      "name": "@ibestservices/ibest-ui",
+      "version": "2.0.6",
+      "integrity": "sha512-xk/RTmg877QgoCJyOztk8J9oHr9ghfiZFMoij8P9CeVnaqb66L+rl82ftS+Lw8rVAju38hAqHHtqpcQOhejxKA==",
+      "resolved": "https://ohpm.openharmony.cn/ohpm/@ibestservices/ibest-ui/-/ibest-ui-2.0.6.har",
+      "registryType": "ohpm",
+      "dependencies": {
+        "@hview/dayjs": "^1.11.11",
+        "lunar": "^1.0.0"
+      }
+    },
+    "@ohos/axios@2.2.6": {
+      "name": "@ohos/axios",
+      "version": "2.2.6",
+      "integrity": "sha512-A1JqGe6XaeqWyjQETitFW4EkubQS7Fv7h0YG5a/ry3/a/vOgVGzwC4y5KAhvMzVv1tYjfY0ntMtV2kJGlmOHcQ==",
+      "resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/axios/-/axios-2.2.6.har",
+      "registryType": "ohpm"
+    },
+    "basic@../../commons/basic": {
+      "name": "basic",
+      "version": "1.0.0",
+      "resolved": "../../commons/basic",
+      "registryType": "local",
+      "dependencies": {
+        "@ohos/axios": "^2.2.4",
+        "@abner/log": "^1.0.5",
+        "@ibestservices/ibest-ui": "2.0.6"
+      },
+      "packageType": "InterfaceHar"
+    },
+    "lunar@1.0.0": {
+      "name": "lunar",
+      "version": "1.0.0",
+      "integrity": "sha512-sAMOxbVr7Sn/QEzEQZHz0CwYjOROTgDLc4842CX2d7f+1D2nlHc6T5jtZJoleIMPZItNJLj0GuOaB/JR+Iwe7Q==",
+      "resolved": "https://ohpm.openharmony.cn/ohpm/lunar/-/lunar-1.0.0.har",
+      "registryType": "ohpm"
+    },
+    "user@../user": {
+      "name": "user",
+      "version": "1.0.0",
+      "resolved": "../user",
+      "registryType": "local",
+      "dependencies": {
+        "basic": "file:../../commons/basic"
+      },
+      "packageType": "InterfaceHar"
+    }
+  }
+}

+ 13 - 0
features/business/oh-package.json5

@@ -0,0 +1,13 @@
+{
+  "name": "business",
+  "version": "1.0.0",
+  "description": "业务模块",
+  "main": "Index.ets",
+  "author": "",
+  "license": "Apache-2.0",
+  "packageType": "InterfaceHar",
+  "dependencies": {
+    "user": "file:../user",
+    "basic": "file:../../commons/basic"
+  }
+}

+ 27 - 0
features/business/src/main/ets/component/YTCard.ets

@@ -0,0 +1,27 @@
+
+@Component
+export struct YTCard {
+  desc: string = ''
+  @BuilderParam tag?: (tagDesc:string) => void
+  tagArr: string[] = []
+
+  build() {
+    Column(){
+      Text(this.desc).fontColor('#ffffffff').padding(12)
+      Divider()
+        .width('80%')
+        .height(0)
+        .border({ width: {left:0,right:0,top:0,bottom:2}, color:Color.White, style: BorderStyle.Dashed })
+      ForEach(this.tagArr, (tagDesc:string)=>{
+        this.tag?.(tagDesc)
+      })
+    }
+    .height(214)
+    .width('100%')
+    .flexShrink(0)
+    .borderRadius(8)
+    .padding({ left: 18, right: 18, top: 8})
+    .backgroundColor(Color.Pink)
+    .linearGradient({angle: 133,colors: [['#ffe77575',0], ['#ffff97d9',1.4225]]})
+  }
+}

+ 31 - 0
features/business/src/main/ets/component/YTChat.ets

@@ -0,0 +1,31 @@
+import { YTHeader } from "basic"
+
+@Component
+export struct YTChat{
+  @BuilderParam card?: () => void
+  @BuilderParam cardTag?: () => void
+  @BuilderParam chatTag?: () => void
+  @BuilderParam chatInput?: () => void
+  @BuilderParam backTop?: () => void
+  title:string = ''
+  cardDesc:string = ''
+  chatInputTips:string = ''
+
+  build() {
+    Column(){
+      YTHeader({title: this.title})
+      Column() {
+        this.card?.()
+        Row(){
+          this.chatInput?.()
+          this.backTop?.()
+        }
+        this.chatTag?.()
+      }
+      .width('100%')
+      .padding({left:18, right:18})
+    }
+    .height('100%')
+    .width('100%')
+  }
+}

+ 14 - 0
features/business/src/main/ets/component/YTTag.ets

@@ -0,0 +1,14 @@
+@Component
+export struct YTTag {
+  desc: string = ''
+
+  build() {
+    Row() {
+      Text(this.desc)
+    }
+    .height(36)
+    .width('100%')
+    .padding(5)
+    .backgroundColor(Color.Orange)
+  }
+}

+ 201 - 0
features/business/src/main/ets/pages/HomePages.ets

@@ -0,0 +1,201 @@
+import { ChatCard, HomeSearch, YtAvoid } from 'basic'
+
+@Builder
+function HomeBuilder(){
+  NavDestination() {
+    HomePages()
+  }
+  .hideTitleBar(true)
+}
+
+@Component
+export struct HomePages {
+  //用户输入的搜索内容
+  @State searchValue: string = ''
+
+  @StorageProp(YtAvoid.safeTopKey) safeTop: number = 0
+  @StorageProp(YtAvoid.safeBottomKey) safeBottom: number = 0
+
+  aboutToAppear(): void {
+
+  }
+
+  delContent(item: string) {
+    return item.length > 25 ? item.slice(0, 25) + '...' : item
+  }
+
+  doSearch(param: string) {
+
+  }
+  // 搜索
+  commonSearch() {
+
+  }
+
+  build() {
+    Scroll(){
+      Column() {
+        Row() {
+          HomeSearch({
+            searchValue: this.searchValue, commonSearch: () => {
+              this.commonSearch()
+            }
+          })
+            .layoutWeight(1)
+        }
+        .margin({top:15})
+        .width('100%')
+        .height(32)
+        .padding({ left: 18, right: 18 })
+
+        Row() {
+          Stack({alignContent: Alignment.Start}){
+            Column(){}.width(10).height(10).flexShrink(0).borderRadius(10).backgroundColor('#FF6F7F').zIndex(100)
+            Column(){}.width(18).height(10).flexShrink(0).borderRadius(10).backgroundColor('#0DB5F7')
+          }.height(20).margin({ left: 18})
+          Text('聊天话术').fontColor('#333333').fontFamily("MiSans VF").fontSize(16).fontWeight(500).fontStyle(FontStyle.Normal).margin({left: 10})
+        }.alignItems(VerticalAlign.Center)
+        .width('100%')
+        .margin({top: 10})
+        //聊天话术六个卡片
+        Row() {
+          this.chatCard({colorTop:'#FF718EFF',urlTop: '',colorBottom:'#FFFF8285', urlBottom: ''})
+          this.chatCard({colorTop:'#FFA78BFA',urlTop: '',colorBottom:'#FFFFAE3C', urlBottom: ''})
+          this.chatCard({colorTop:'#FF64BFFE',urlTop: '',colorBottom:'#FF718EFF', urlBottom: ''})
+        }
+        .height(163)
+        .width('100%')
+        .padding({left:5, right: 5})
+
+        Row() {
+          Row() {
+            Stack({alignContent: Alignment.Start}){
+              Column(){}.width(10).height(10).flexShrink(0).borderRadius(10).backgroundColor('#FF6F7F').zIndex(100)
+              Column(){}.width(18).height(10).flexShrink(0).borderRadius(10).backgroundColor('#0DB5F7')
+            }.height(20).margin({ left: 18})
+            Text('高质量情书').fontColor('#333333').fontFamily("MiSans VF").fontSize(16).fontWeight(500).fontStyle(FontStyle.Normal).margin({left: 10})
+          }
+          Text('更多 >').fontColor('#80000000').fontFamily('MiSans VF').fontSize(12).fontWeight(500).fontStyle(FontStyle.Normal).margin({right: 10})
+        }
+        .justifyContent(FlexAlign.SpaceBetween)
+        .margin({top: 10})
+        .alignItems(VerticalAlign.Center)
+        .width('100%')
+
+        //高质量情书内容
+        Row() {
+          Row(){
+            Image('').width(56).height(56).backgroundColor(Color.Pink).borderRadius(4)
+            Column(){
+              Text('世间所有美好,皆是你').width(165).fontFamily('MiSans VF').fontSize(14).fontColor('#FF333333')
+              Text('情不知何起,岁不问长短。只知道喜欢你情不知何起,岁不问长短…').width(165).fontFamily('MiSans VF').fontSize(10).fontColor('#991F1E1F').margin({top:5})
+            }
+            .width(165)
+            .margin({left: 8})
+            Button('阅读全文')
+              .borderRadius(12)
+              .backgroundColor('#0DB5F7')
+              .width(64).height(24).fontColor('#ffffff').fontFamily('MiSans VF').fontSize(12).fontWeight(500).fontStyle(FontStyle.Normal)
+              .padding(4)
+              .margin({left: 18})
+          }
+          .width('100%')
+          .alignItems(VerticalAlign.Center)
+        }
+        .margin({top: 10})
+        .height(80)
+        .width('100%')
+        .padding({left: 18, right: 18})
+
+        Row(){
+          Image('').width(16).height(12).flexShrink(0).fillColor('#80333333').backgroundColor(Color.Gray)
+          Text('1026').margin({left: 10}).fontColor('#80333333')
+          Image('').width(12).height(13).flexShrink(0).fillColor('#80333333').backgroundColor(Color.Gray).margin({left: 20})
+          Text('1719').margin({left: 10}).fontColor('#80333333')
+        }
+        .margin({top: 10})
+        .height(24)
+        .width('100%')
+        .padding({left: 10, right: 10})
+
+        Row(){
+          Button('我也写一篇')
+            .width('60%')
+            .backgroundColor('#FF0DB5F7')
+            .fontColor('#FFFFFFFF')
+            .align(Alignment.Center)
+            .fontFamily('Source Han Sans CN')
+            .fontSize(18)
+            .fontWeight(400)
+            .fontStyle(FontStyle.Normal)
+        }
+        .margin({top: 10})
+        .justifyContent(FlexAlign.Center)
+        .width('100%')
+        .height(48)
+
+        Row() {
+          Stack({alignContent: Alignment.Start}){
+            Column(){}.width(10).height(10).flexShrink(0).borderRadius(10).backgroundColor('#FF6F7F').zIndex(100)
+            Column(){}.width(18).height(10).flexShrink(0).borderRadius(10).backgroundColor('#0DB5F7')
+          }.height(20).margin({ left: 18})
+          Text('扩列搭子').fontColor('#333333').fontFamily("MiSans VF").fontSize(16).fontWeight(500).fontStyle(FontStyle.Normal).margin({left: 10})
+        }
+        .margin({top: 10})
+        .alignItems(VerticalAlign.Center)
+        .width('100%')
+        .padding({top: 20})
+
+        //扩列搭子内容
+        Row() {
+          this.ytNote()
+          this.ytNote()
+        }
+        .width('100%')
+
+        Row() {
+          Column(){
+            Image('').width('30%').backgroundColor('#527C9AFF').borderRadius(32).height(94)
+            Text('丢瓶子(1)').fontColor('#FF68A3FF').fontSize(12).fontFamily('MiSans VF').fontWeight(500).fontStyle(FontStyle.Normal).margin({top:10})
+          }
+          .alignItems(HorizontalAlign.Center)
+          Column(){
+            Image('').width('30%').backgroundColor('#52AF91F9').borderRadius(32).height(94)
+            Text('捞瓶子(3)').fontColor('#FFB3A3FF').fontSize(12).fontFamily('MiSans VF').fontWeight(500).fontStyle(FontStyle.Normal).margin({top:10})
+          }
+          .alignItems(HorizontalAlign.Center)
+        }
+        .justifyContent(FlexAlign.SpaceBetween)
+        .margin({top: 10})
+        .width('100%')
+        .padding({left: 51,right:51, top: 15, bottom: 15})
+      }
+      .padding({top: this.safeTop})
+      .width('100%')
+    }
+  }
+
+  @Builder
+  ytNote(){
+    Row(){
+      Image('').height(17.72).width(8).backgroundColor(Color.Orange)
+      Text('180******88 丢出了瓶子').fontSize(11).fontFamily('MiSans VF').fontWeight(500).fontColor('#FF333333').margin({left: 5})
+    }
+    .width('40%')
+    .height(36)
+    .borderRadius(8)
+    .border({width: 1, color: '#FFEAEAEA'})
+    .margin({left: 18,top:10})
+    .padding({left: 10, right:10})
+  }
+
+  @Builder
+  chatCard(chatCard:ChatCard){
+    Column(){
+      Image(chatCard.urlTop).width('100%').height(67).flexShrink(0).borderRadius(8).backgroundColor(chatCard.colorTop)
+      Image(chatCard.urlBottom).width('100%').height(67).flexShrink(0).borderRadius(8).backgroundColor(chatCard.colorBottom).margin({top:8})
+    }.height(134)
+    .width('30%')
+    .margin({left:5,right:5})
+  }
+}

+ 7 - 0
features/business/src/main/ets/pages/Index.ets

@@ -0,0 +1,7 @@
+@Entry
+@Component
+struct Index {
+  build() {
+
+  }
+}

+ 3 - 0
features/business/src/main/ets/utils/Calc.ets

@@ -0,0 +1,3 @@
+export function add(a: number, b: number) {
+  return a + b;
+}

+ 14 - 0
features/business/src/main/module.json5

@@ -0,0 +1,14 @@
+{
+  "module": {
+    "name": "business",
+    "type": "shared",
+    "description": "$string:shared_desc",
+    "deviceTypes": [
+      "phone",
+      "tablet",
+      "2in1"
+    ],
+    "deliveryWithInstall": true,
+    "pages": "$profile:main_pages"
+  }
+}

+ 8 - 0
features/business/src/main/resources/base/element/float.json

@@ -0,0 +1,8 @@
+{
+  "float": [
+    {
+      "name": "page_text_font_size",
+      "value": "50fp"
+    }
+  ]
+}

+ 8 - 0
features/business/src/main/resources/base/element/string.json

@@ -0,0 +1,8 @@
+{
+  "string": [
+    {
+      "name": "shared_desc",
+      "value": "业务模块-主要业务功能"
+    }
+  ]
+}

BIN
features/business/src/main/resources/base/media/search.png


+ 5 - 0
features/business/src/main/resources/base/profile/main_pages.json

@@ -0,0 +1,5 @@
+{
+  "src": [
+    "pages/Index"
+  ]
+}

+ 12 - 0
features/business/src/main/resources/base/profile/router_map.json

@@ -0,0 +1,12 @@
+{
+  "routerMap": [
+    {
+      "name": "HomePages",
+      "pageSourceFile": "src/main/ets/pages/HomePages.ets",
+      "buildFunction": "HomeBuilder",
+      "data": {
+        "description": "首页"
+      }
+    }
+  ]
+}

+ 35 - 0
features/business/src/ohosTest/ets/test/Ability.test.ets

@@ -0,0 +1,35 @@
+import { hilog } from '@kit.PerformanceAnalysisKit';
+import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium';
+
+export default function abilityTest() {
+  describe('ActsAbilityTest', () => {
+    // Defines a test suite. Two parameters are supported: test suite name and test suite function.
+    beforeAll(() => {
+      // Presets an action, which is performed only once before all test cases of the test suite start.
+      // This API supports only one parameter: preset action function.
+    })
+    beforeEach(() => {
+      // Presets an action, which is performed before each unit test case starts.
+      // The number of execution times is the same as the number of test cases defined by **it**.
+      // This API supports only one parameter: preset action function.
+    })
+    afterEach(() => {
+      // Presets a clear action, which is performed after each unit test case ends.
+      // The number of execution times is the same as the number of test cases defined by **it**.
+      // This API supports only one parameter: clear action function.
+    })
+    afterAll(() => {
+      // Presets a clear action, which is performed after all test cases of the test suite end.
+      // This API supports only one parameter: clear action function.
+    })
+    it('assertContain', 0, () => {
+      // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function.
+      hilog.info(0x0000, 'testTag', '%{public}s', 'it begin');
+      let a = 'abc';
+      let b = 'b';
+      // Defines a variety of assertion methods, which are used to declare expected boolean conditions.
+      expect(a).assertContain(b);
+      expect(a).assertEqual(a);
+    })
+  })
+}

+ 5 - 0
features/business/src/ohosTest/ets/test/List.test.ets

@@ -0,0 +1,5 @@
+import abilityTest from './Ability.test';
+
+export default function testsuite() {
+  abilityTest();
+}

+ 13 - 0
features/business/src/ohosTest/module.json5

@@ -0,0 +1,13 @@
+{
+  "module": {
+    "name": "business_test",
+    "type": "feature",
+    "deviceTypes": [
+      "phone",
+      "tablet",
+      "2in1"
+    ],
+    "deliveryWithInstall": true,
+    "installationFree": false
+  }
+}

+ 5 - 0
features/business/src/test/List.test.ets

@@ -0,0 +1,5 @@
+import localUnitTest from './LocalUnit.test';
+
+export default function testsuite() {
+  localUnitTest();
+}

+ 33 - 0
features/business/src/test/LocalUnit.test.ets

@@ -0,0 +1,33 @@
+import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium';
+
+export default function localUnitTest() {
+  describe('localUnitTest', () => {
+    // Defines a test suite. Two parameters are supported: test suite name and test suite function.
+    beforeAll(() => {
+      // Presets an action, which is performed only once before all test cases of the test suite start.
+      // This API supports only one parameter: preset action function.
+    });
+    beforeEach(() => {
+      // Presets an action, which is performed before each unit test case starts.
+      // The number of execution times is the same as the number of test cases defined by **it**.
+      // This API supports only one parameter: preset action function.
+    });
+    afterEach(() => {
+      // Presets a clear action, which is performed after each unit test case ends.
+      // The number of execution times is the same as the number of test cases defined by **it**.
+      // This API supports only one parameter: clear action function.
+    });
+    afterAll(() => {
+      // Presets a clear action, which is performed after all test cases of the test suite end.
+      // This API supports only one parameter: clear action function.
+    });
+    it('assertContain', 0, () => {
+      // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function.
+      let a = 'abc';
+      let b = 'b';
+      // Defines a variety of assertion methods, which are used to declare expected boolean conditions.
+      expect(a).assertContain(b);
+      expect(a).assertEqual(a);
+    });
+  });
+}

+ 6 - 0
features/user/.gitignore

@@ -0,0 +1,6 @@
+/node_modules
+/oh_modules
+/.preview
+/build
+/.cxx
+/.test

+ 1 - 0
features/user/Index.ets

@@ -0,0 +1 @@
+export { Mine } from './src/main/ets/component/Mine';

+ 28 - 0
features/user/build-profile.json5

@@ -0,0 +1,28 @@
+{
+  "apiType": "stageMode",
+  "buildOption": {
+  },
+  "buildOptionSet": [
+    {
+      "name": "release",
+      "arkOptions": {
+        "obfuscation": {
+          "ruleOptions": {
+            "enable": false,
+            "files": [
+              "./obfuscation-rules.txt"
+            ]
+          }
+        }
+      },
+    },
+  ],
+  "targets": [
+    {
+      "name": "default"
+    },
+    {
+      "name": "ohosTest"
+    }
+  ]
+}

+ 6 - 0
features/user/hvigorfile.ts

@@ -0,0 +1,6 @@
+import { hspTasks } from '@ohos/hvigor-ohos-plugin';
+
+export default {
+    system: hspTasks,  /* Built-in plugin of Hvigor. It cannot be modified. */
+    plugins:[]         /* Custom plugin to extend the functionality of Hvigor. */
+}

+ 23 - 0
features/user/obfuscation-rules.txt

@@ -0,0 +1,23 @@
+# Define project specific obfuscation rules here.
+# You can include the obfuscation configuration files in the current module's build-profile.json5.
+#
+# For more details, see
+#   https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5
+
+# Obfuscation options:
+# -disable-obfuscation: disable all obfuscations
+# -enable-property-obfuscation: obfuscate the property names
+# -enable-toplevel-obfuscation: obfuscate the names in the global scope
+# -compact: remove unnecessary blank spaces and all line feeds
+# -remove-log: remove all console.* statements
+# -print-namecache: print the name cache that contains the mapping from the old names to new names
+# -apply-namecache: reuse the given cache file
+
+# Keep options:
+# -keep-property-name: specifies property names that you want to keep
+# -keep-global-name: specifies names that you want to keep in the global scope
+
+-enable-property-obfuscation
+-enable-toplevel-obfuscation
+-enable-filename-obfuscation
+-enable-export-obfuscation

+ 68 - 0
features/user/oh-package-lock.json5

@@ -0,0 +1,68 @@
+{
+  "meta": {
+    "stableOrder": true
+  },
+  "lockfileVersion": 3,
+  "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.",
+  "specifiers": {
+    "@abner/log@^1.0.5": "@abner/log@1.0.5",
+    "@hview/dayjs@^1.11.11": "@hview/dayjs@1.11.11",
+    "@ibestservices/ibest-ui@2.0.6": "@ibestservices/ibest-ui@2.0.6",
+    "@ohos/axios@^2.2.4": "@ohos/axios@2.2.6",
+    "basic@../../commons/basic": "basic@../../commons/basic",
+    "lunar@^1.0.0": "lunar@1.0.0"
+  },
+  "packages": {
+    "@abner/log@1.0.5": {
+      "name": "@abner/log",
+      "version": "1.0.5",
+      "integrity": "sha512-ifkzK9CKO4Rq6+BrYqT0NE5pkFfONWgBtkPTXSqJHn4vzXwEkVIWUl0v8fmgtHE+8XALi3AiBGu0ldqkrgsZ7Q==",
+      "resolved": "https://ohpm.openharmony.cn/ohpm/@abner/log/-/log-1.0.5.har",
+      "registryType": "ohpm"
+    },
+    "@hview/dayjs@1.11.11": {
+      "name": "@hview/dayjs",
+      "version": "1.11.11",
+      "integrity": "sha512-JPJlAbcCS8STHIRnesbAPWCF6eAlIkGx5rDPGT8CesSCMsB6ZJWEH+l3hl0YqtmfofCCVpWrzjBG7Z2f18olAw==",
+      "resolved": "https://ohpm.openharmony.cn/ohpm/@hview/dayjs/-/dayjs-1.11.11.har",
+      "registryType": "ohpm"
+    },
+    "@ibestservices/ibest-ui@2.0.6": {
+      "name": "@ibestservices/ibest-ui",
+      "version": "2.0.6",
+      "integrity": "sha512-xk/RTmg877QgoCJyOztk8J9oHr9ghfiZFMoij8P9CeVnaqb66L+rl82ftS+Lw8rVAju38hAqHHtqpcQOhejxKA==",
+      "resolved": "https://ohpm.openharmony.cn/ohpm/@ibestservices/ibest-ui/-/ibest-ui-2.0.6.har",
+      "registryType": "ohpm",
+      "dependencies": {
+        "@hview/dayjs": "^1.11.11",
+        "lunar": "^1.0.0"
+      }
+    },
+    "@ohos/axios@2.2.6": {
+      "name": "@ohos/axios",
+      "version": "2.2.6",
+      "integrity": "sha512-A1JqGe6XaeqWyjQETitFW4EkubQS7Fv7h0YG5a/ry3/a/vOgVGzwC4y5KAhvMzVv1tYjfY0ntMtV2kJGlmOHcQ==",
+      "resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/axios/-/axios-2.2.6.har",
+      "registryType": "ohpm"
+    },
+    "basic@../../commons/basic": {
+      "name": "basic",
+      "version": "1.0.0",
+      "resolved": "../../commons/basic",
+      "registryType": "local",
+      "dependencies": {
+        "@ohos/axios": "^2.2.4",
+        "@abner/log": "^1.0.5",
+        "@ibestservices/ibest-ui": "2.0.6"
+      },
+      "packageType": "InterfaceHar"
+    },
+    "lunar@1.0.0": {
+      "name": "lunar",
+      "version": "1.0.0",
+      "integrity": "sha512-sAMOxbVr7Sn/QEzEQZHz0CwYjOROTgDLc4842CX2d7f+1D2nlHc6T5jtZJoleIMPZItNJLj0GuOaB/JR+Iwe7Q==",
+      "resolved": "https://ohpm.openharmony.cn/ohpm/lunar/-/lunar-1.0.0.har",
+      "registryType": "ohpm"
+    }
+  }
+}

+ 12 - 0
features/user/oh-package.json5

@@ -0,0 +1,12 @@
+{
+  "name": "user",
+  "version": "1.0.0",
+  "description": "用户模块-涵盖登录注册",
+  "main": "Index.ets",
+  "author": "",
+  "license": "Apache-2.0",
+  "packageType": "InterfaceHar",
+  "dependencies": {
+    "basic": "file:../../commons/basic"
+  }
+}

+ 94 - 0
features/user/src/main/ets/component/LoginInput.ets

@@ -0,0 +1,94 @@
+import { IBestToast, yTRequest } from 'basic'
+import { CodeInputType } from '../interface'
+
+@Component
+export struct LoginInput {
+  item?: CodeInputType
+  mgBottom: number = 24
+  @Link inputData: string
+  @Prop phoneNumber: string
+  code: string = ''
+  @State time: number = 61
+  private declare sa: number
+  needCode: boolean = true
+
+  aboutToDisappear(): void {
+    clearInterval(this.sa)
+  }
+
+  build() {
+    Row({ space: 8 }) {
+      Text(this.item?.txt)
+        .width(73)
+        .height(31)
+        .fontSize(12)
+        .border({ width: { bottom: 1 }, color: $r('[basic].color.main_na_color') })
+        .textAlign(TextAlign.Start)
+      TextInput({ placeholder: this.item?.placeHolder, text: $$this.inputData })
+        .layoutWeight(1)
+        .type(InputType.PhoneNumber)
+        .borderRadius(0)
+        .border({ width: { bottom: 1 }, color: $r('[basic].color.main_na_color') })
+        .textAlign(TextAlign.Start)
+        .height(31)
+        .placeholderFont({ size: 12 })
+        .placeholderColor($r('[basic].color.main_na_color'))
+        .backgroundColor(Color.Transparent)
+        .padding(0)
+        .caretColor($r('[basic].color.main_ac_color_dark'))
+      if (this.needCode) {
+        Text(this.time == 61 ? '获取验证码' : this.time + '后重新发送')
+          .position({ right: 0 })
+          .height(31)
+          .fontSize(12)
+          .fontColor($r('[basic].color.main_blue'))
+          .onClick(() => {
+            const rep = new RegExp('^1(3[0-9]|4[01456879]|5[0-35-9]|6[2567]|7[0-8]|8[0-9]|9[0-35-9])\\d{8}$');
+            if (this.phoneNumber.match(rep) && this.time == 61) {
+              this.time = 60
+              this.sa = setInterval(() => {
+                this.time--
+                if (this.time < 1) {
+                  this.time = 61
+                  clearInterval(this.sa)
+                }
+              }, 1000)
+              // TODO  此处放置验证码获取逻辑
+              yTRequest.getCaptcha(this.phoneNumber, (res) => {
+                if (!res) {
+                  IBestToast.show({ type: 'warning', message: '请勿重复发送验证码' })
+                  return
+                }
+
+                AppStorage.setOrCreate<string>('uuid', res)
+              }, (err) => {
+                IBestToast.show({ type: 'fail', message: '验证码发送失败' })
+                this.time = 61
+              })
+              // yTRequest.post<CodeType, AskCodeNum>('/api/friendcase/member/sendSmsCode',
+              //   { phonenumber: this.phoneNumber })
+              //   .then(res => {
+              //     if (!res) {
+              //       IBestToast.show({ type: 'warning', message: '请勿重复发送验证码' })
+              //       return
+              //     }
+              //
+              //     AppStorage.setOrCreate<CodeType>('smsCode', res)
+              //   })
+              //   .catch((err: Error) => {
+              //     IBestToast.show({ type: 'fail', message: '验证码发送失败' })
+              //     this.time = 61
+              //   })
+            } else {
+              if (!this.phoneNumber.match(rep)) {
+                IBestToast.show({ message: '请输入正确的手机号', type: 'warning' })
+              }
+            }
+          })
+          .enabled(this.time == 61)
+      }
+
+    }
+    .margin({ bottom: this.mgBottom })
+  }
+}

+ 152 - 0
features/user/src/main/ets/component/Mine.ets

@@ -0,0 +1,152 @@
+import { BarType, copyText, ShowBannerAd, UserInfo, userInfo, YtAvoid, yTRouter } from 'basic'
+
+@Component
+export struct Mine {
+  @StorageProp(YtAvoid.safeTopKey) safeTop: number = 0
+  @StorageProp(userInfo.KEY) userInfo: UserInfo = new UserInfo()
+  setArr: Array<BarType<undefined>> = [
+    // {
+    //   text: 'AI次数兑换',
+    //   click: () => {
+    //     yTRouter.router2AIExchangePage()
+    //     if (this.userInfo.checkLogin()) {
+    //
+    //     } else {
+    //       //TODO 去登录页
+    //     }
+    //
+    //   },
+    //   src: $r('app.media.right_arrow')
+    // },
+    {
+      text: '意见反馈',
+      click: () => {
+        yTRouter.router2SuggestionPage()
+        if (this.userInfo.checkLogin()) {
+
+        } else {
+          //TODO 去登录页
+        }
+
+      },
+      src: $r('app.media.right_arrow')
+    },
+    {
+      text: '给个好评',
+      click: () => {
+
+      },
+      src: $r('app.media.right_arrow')
+    },
+    {
+      text: '关于我们',
+      click: () => {
+        yTRouter.router2AgreementPage('关于我们')
+      },
+      src: $r('app.media.right_arrow')
+    },
+    {
+      text: '隐私政策',
+      click: () => {
+        yTRouter.router2AgreementPage('隐私政策')
+      },
+      src: $r('app.media.right_arrow')
+    }
+  ]
+
+  aboutToAppear(): void {
+
+  }
+
+  build() {
+    Column() {
+      Row() {
+        Row() {
+          if (this.userInfo.getHeadImg()) {
+            Image(this.userInfo.getHeadImg())
+              .aspectRatio(1)
+              .height(40)
+              .borderRadius(20)
+              .margin({ right: 9 })
+          } else {
+            Image($r('app.media.app_icon'))
+              .width(50)
+              .height(40)
+              .margin({ right: 9 })
+          }
+
+          Column({ space: 7 }) {
+            Text(this.userInfo.getName() ?? this.userInfo.getPhoneNumber() ?? this.userInfo.getId()?.toString() ??
+              '未登录')
+              .fontSize($r('[basic].float.page_text_font_size_16'))
+              .fontColor('#FF000000')
+            Text() {
+              Span('ID:' + (this.userInfo.getId()?.toString().padStart(8, '0') ?? '00000000'))
+              ImageSpan($r('app.media.copy'))
+                .width(7)
+                .height(8)
+                .margin({ left: 4 })
+                .onClick(() => {
+                  copyText((this.userInfo.getId()?.toString().padStart(8, '0') ?? '00000000'))
+                })
+                .offset({ bottom: 4 })
+            }
+            .fontColor('#80000000')
+            .fontSize($r('[basic].float.page_text_font_size_10'))
+          }
+          .alignItems(HorizontalAlign.Start)
+        }
+        .onClick(() => {
+
+          if (this.userInfo.checkLogin()) {
+            yTRouter.router2SettingPage()
+            return
+          }
+          yTRouter.router2LoginPage()
+        })
+      }
+      .width('100%')
+      .margin({ bottom: 30 })
+
+      ShowBannerAd()
+
+      List() {
+        ForEach(this.setArr, (item: BarType<undefined>, index) => {
+          ListItem() {
+            Row() {
+              Text(item.text)
+                .fontColor('#80000000')
+                .fontSize(12)
+              if (!index) {
+                Row() {
+                  Text(this.userInfo.getAiNum()?.toString() ?? '')
+                    .fontWeight(600)
+                    .fontSize($r('[basic].float.page_text_font_size_14'))
+                  Image($r('app.media.right_arrow'))
+                    .width(24)
+                    .aspectRatio(1)
+                }
+              } else {
+                Image($r('app.media.right_arrow'))
+                  .width(24)
+                  .aspectRatio(1)
+              }
+            }
+            .width('100%')
+            .height(36)
+            .justifyContent(FlexAlign.SpaceBetween)
+            .onClick(() => {
+              item.click?.()
+            })
+          }
+        })
+      }
+      .padding({ left: 12, right: 4 })
+      .divider({ strokeWidth: 1, color: '#0A000000', endMargin: 8 })
+      .margin({ top: 30 })
+    }
+    .padding({ top: this.safeTop + 22, left: 16, right: 16 })
+
+    .height('100%')
+  }
+}

+ 45 - 0
features/user/src/main/ets/component/OtherLoginMethods.ets

@@ -0,0 +1,45 @@
+import { BarType } from 'basic'
+
+
+@Component
+export struct OtherLoginMethods {
+  loginMethodArr: BarType<undefined>[] = []
+
+  build() {
+    Column() {
+      Text('其他登入方式')
+        .fontSize($r('[basic].float.page_text_font_size_10'))
+        .margin({ bottom: 24 })
+      Column({ space: 16 }) {
+        ForEach(this.loginMethodArr, (item: BarType<undefined>) => {
+          LoginMethod(item)
+        })
+      }
+
+    }
+    .alignItems(HorizontalAlign.Start)
+  }
+}
+
+
+@Builder
+function LoginMethod(item: BarType<undefined>) {
+  Stack({ alignContent: Alignment.Start }) {
+    Button(item.text)
+      .border({ width: 1, color: $r('[basic].color.main_ac_color_dark') })
+      .backgroundColor(Color.Transparent)
+      .width('100%')
+      .height(37)
+      .fontSize($r('[basic].float.page_text_font_size_12'))
+      .type(ButtonType.Normal)
+      .fontColor($r('[basic].color.main_ac_color_dark'))
+      .borderRadius(32)
+    Image(item.src)
+      .width(24)
+      .height(24)
+      .offset({ left: 12 })
+
+  }
+  .onClick(item.click)
+
+}

+ 66 - 0
features/user/src/main/ets/component/Terms.ets

@@ -0,0 +1,66 @@
+import { yTRouter } from 'basic'
+
+@Extend(Span)
+function spanEx(click: () => void) {
+  .decoration({ type: TextDecorationType.Underline, color: $r('[basic].color.main_ac_color_dark') })
+  .fontColor($r('[basic].color.main_ac_color_dark'))
+  .onClick(click)
+}
+
+@Component
+export struct Terms {
+  @Link isAgree: boolean
+  mg: number = 39
+
+  build() {
+    Column() {
+      Row() {
+        Text() {
+          Span('我已阅读并同意')
+          // Span('《中国认证服务条款》')
+          //   .spanEx(() => {
+          //
+          //   })
+          //
+          // Span('以及')
+          Span('"用户协议"')
+            .spanEx(() => {
+              yTRouter.router2AgreementPage('用户协议')
+
+            })
+          Span('和')
+          Span('"隐私政策"')
+            .spanEx(() => {
+              yTRouter.router2AgreementPage('隐私政策')
+            })
+          Span('。')
+        }
+        .fontColor('#4D000000')
+        .fontSize($r('[basic].float.page_text_font_size_10'))
+        .margin({ bottom: this.mg })
+      }
+      .width('100%')
+
+      Row() {
+        Text()
+          .height('100%')
+          .aspectRatio(1)
+          .backgroundColor(Color.White)
+          .borderRadius(18)
+      }
+      .padding(1)
+      .alignSelf(ItemAlign.End)
+      .width(39)
+      .aspectRatio(2)
+      .borderRadius(18)
+      .justifyContent(this.isAgree ? FlexAlign.End : FlexAlign.Start)
+      .backgroundColor(this.isAgree ? $r('[basic].color.main_ac_color_dark') : '#D9D9D9')
+      .onClick(() => {
+        animateTo({ duration: 200 }, () => {
+          this.isAgree = !this.isAgree
+        })
+      })
+    }
+
+  }
+}

+ 4 - 0
features/user/src/main/ets/interface/index.ets

@@ -0,0 +1,4 @@
+export interface CodeInputType {
+  txt: string,
+  placeHolder: string
+}

+ 41 - 0
features/user/src/main/ets/pages/AgreementPage.ets

@@ -0,0 +1,41 @@
+import {YtAvoid, YTHeader, yTRouter } from 'basic'
+import { webview } from '@kit.ArkWeb'
+
+@Builder
+function agreementBuilder() {
+  NavDestination() {
+    AgreementPage()
+  }
+  .hideTitleBar(true)
+}
+
+@Component
+struct AgreementPage {
+  @State title: '隐私政策' | '关于我们' | '用户协议' = '隐私政策'
+  @StorageProp(YtAvoid.safeTopKey) safeTop: number = 0
+  @StorageProp(YtAvoid.safeBottomKey) safeBottom: number = 0
+  controller: webview.WebviewController = new webview.WebviewController()
+
+  aboutToAppear(): void {
+    this.title = yTRouter.getAgreementPageParam()
+    // IBestToast.showLoading({
+    //   message: '加载中...'
+    // })
+  }
+
+  build() {
+    Column({ space: 12 }) {
+      YTHeader({ title: this.title })
+      Column() {
+        Web({ src: 'https://www.baidu.com', controller: this.controller })
+          .width('100%')
+          .layoutWeight(1)
+          .onPageEnd(() => {
+            // IBestToast.hide()
+          })
+      }
+      .padding({ left: 16, right: 16 })
+    }
+    .padding({ top: this.safeTop, bottom: this.safeBottom })
+  }
+}

+ 20 - 0
features/user/src/main/ets/pages/Index.ets

@@ -0,0 +1,20 @@
+@Entry
+@Component
+struct Index {
+  @State message: string = 'Hello World';
+
+  build() {
+    Row() {
+      Column() {
+        Text(this.message)
+          .fontSize($r('app.float.page_text_font_size'))
+          .fontWeight(FontWeight.Bold)
+          .onClick(() => {
+            this.message = 'Welcome';
+          })
+      }
+      .width('100%')
+    }
+    .height('100%')
+  }
+}

+ 100 - 0
features/user/src/main/ets/pages/LoginPage.ets

@@ -0,0 +1,100 @@
+import { BarType, YtAvoid, YtButton, yTRequest, yTRouter, yTToast } from 'basic'
+import { CodeInputType } from '../interface'
+import { LoginInput } from '../component/LoginInput'
+import { Terms } from '../component/Terms'
+import { OtherLoginMethods } from '../component/OtherLoginMethods'
+
+// import { OtherLoginMethods } from '../views/OtherLoginMethods'
+
+
+@Builder
+function LoginBuilder() {
+  NavDestination() {
+    LoginPage()
+  }
+  .hideTitleBar(true)
+}
+
+@Component
+struct LoginPage {
+  @State isAgree: boolean = false
+  @StorageProp(YtAvoid.safeTopKey) safeTop: number = 0
+  @StorageProp(YtAvoid.safeBottomKey) safeBottom: number = 0
+  @State phoneNumber: string = ''
+  @State Captcha: string = ''
+  inputPhoneNumberType: CodeInputType = {
+    txt: '+86',
+    placeHolder: '请输入手机号'
+  }
+  inputCaptchaType: CodeInputType = {
+    txt: '验证码',
+    placeHolder: '请输入验证码'
+  }
+  loginMethodArr: BarType<undefined>[] = [
+    {
+      // src: $r('app.media.hua_wei'),
+      text: '华为登录',
+      click: () => {
+        if (!this.isAgree) {
+          // yTToast.agreePrivacy({ text: '华为登录' })
+          return
+        }
+        yTRequest.huaweiLogin()
+      }
+    }
+  ]
+
+  aboutToAppear(): void {
+
+  }
+
+  build() {
+    Column() {
+      Image($r('[basic].media.ic_back'))
+        .width(24)
+        .aspectRatio(1)
+        .margin({ bottom: 66 })
+        .onClick(() => {
+          yTRouter.routerBack()
+        })
+      Text('使用电话号码访问')
+        .fontSize($r('[basic].float.page_text_font_size_10'))
+        .margin({ bottom: 39 })
+
+      LoginInput({ item: this.inputPhoneNumberType, inputData: this.phoneNumber, needCode: false })
+      LoginInput({ item: this.inputCaptchaType, inputData: this.Captcha, phoneNumber: this.phoneNumber })
+
+
+      Terms({ isAgree: this.isAgree, mg: 13 })
+        .margin({ bottom: 74 })
+
+      YtButton({
+        btContent: '登录', click: () => {
+          if (!this.isAgree) {
+            AppStorage.setOrCreate<Record<string, string>>('captchaLogin',
+              {
+                'phonenumber': this.phoneNumber,
+                'captcha': this.Captcha
+              })
+            // yTToast.agreePrivacy({ text: '验证码登录' })
+            return
+          }
+          yTRequest.phonenumberLogin({
+            'phonenumber': this.phoneNumber,
+            'captcha': this.Captcha
+          })
+        }
+      })
+        .margin({ bottom: 81 })
+      OtherLoginMethods({ loginMethodArr: this.loginMethodArr })
+    }
+    .padding({
+      top: 10 + this.safeTop,
+      left: 16,
+      right: 16,
+      bottom: this.safeBottom
+    })
+    .alignItems(HorizontalAlign.Start)
+  }
+}
+

+ 251 - 0
features/user/src/main/ets/pages/SettingPage.ets

@@ -0,0 +1,251 @@
+import {
+  BarType,
+  IBestToast,
+  reviseImgHeaderBuilder,
+  takePicture,
+  Upload,
+  UserInfo,
+  userInfo,
+  YtAvoid,
+  YtButton,
+  YTHeader,
+  YTLog,
+  yTRequest,
+  yTRouter,
+  yTToast
+} from 'basic'
+
+@Builder
+function settingBuilder() {
+  NavDestination() {
+    SettingPage()
+  }
+  .hideTitleBar(true)
+}
+
+@Component
+struct SettingPage {
+  @StorageProp(YtAvoid.safeTopKey) safeTop: number = 0
+  @StorageProp(YtAvoid.safeBottomKey) safeBottom: number = 0
+  @StorageProp(userInfo.KEY) userInfo: UserInfo = new UserInfo()
+  @State showReviseName: boolean = false
+  @State showHeaderImgRevise: boolean = false
+  @State value: string = ''
+  reviseBuilderArr: Array<BarType<undefined>> = [
+    {
+      text: '头像修改',
+      src: '',
+      click: () => {
+        this.showHeaderImgRevise = true
+      }
+    },
+    {
+      text: '昵称修改',
+      click: () => {
+        this.showReviseName = true
+      }
+    }
+  ]
+  options: BarType<undefined>[] = [
+    {
+      text: '拍照',
+      click: async () => {
+        try {
+          const fullpath = await takePicture()
+          this.showHeaderImgRevise = false
+          // yTRouter.router2DelPhotoPage({ src: fullpath, type: 'header' })
+        } catch (e) {
+          YTLog.warn(e)
+        }
+      }
+    },
+    {
+      text: '从相册中选择',
+      click: () => {
+        Upload.selectImage(getContext(), (fullpath) => {
+          this.showHeaderImgRevise = false
+          IBestToast.showLoading({ message: '上传中...' })
+          yTRequest.uploadHeadImg(getContext(), fullpath, () => {
+            yTRequest.refreshUserInfo(() => {
+              IBestToast.hide()
+              setTimeout(() => {
+                IBestToast.show({ message: '头像修改成功', type: 'success' })
+              }, 100)
+            })
+          })
+        })
+      }
+    },
+    {
+      text: '取消',
+      click: () => {
+        this.showHeaderImgRevise = false
+      }
+    },
+  ]
+
+  build() {
+    Column() {
+      YTHeader({ title: '用户设置' })
+      Column() {
+        ForEach(this.reviseBuilderArr, (item: BarType<undefined>, index) => {
+          this.reviseBuilder(item)
+          if (index < this.reviseBuilderArr.length - 1) {
+            Column() {
+              Text('')
+                .height(1)
+                .width('100%')
+                .backgroundColor('#0A000000')
+            }
+            .padding({ left: 12, right: 8 })
+            .bindSheet($$this.showHeaderImgRevise, this.changeBuilder, {
+              height: 204,
+              backgroundColor: Color.White,
+              showClose: false
+            })
+          }
+        })
+        YtButton({
+          btContent: '退出登录', click: () => {
+            this.userInfo.logout()
+            yTRouter.routerBack()
+            IBestToast.show({ message: '退出登录成功', type: 'success' })
+          }
+        })
+          .margin({ top: 426, bottom: 12 })
+        YtButton({
+          btContent: '注销用户',
+          btFontColor: Color.Red,
+          bgc: Color.White,
+          click: () => {
+            // yTToast.doubleConfirm({
+            //   text: '警告⚠', message: '确定要注销吗?\n注销后数据丢失无法恢复!', click: () => {
+            //     AlertDialog.show({ message: JSON.stringify('点击了确定', null, 2) })
+            //   }
+            // })
+          }
+        })
+      }
+      .bindSheet($$this.showReviseName, this.reviseNameBuilder, {
+        height: 275,
+        showClose: false,
+        backgroundColor: Color.White
+      })
+      .padding({
+        top: 29,
+        left: 16,
+        right: 16,
+      })
+    }
+    .padding({ top: this.safeTop, bottom: this.safeBottom })
+
+  }
+
+  @Builder
+  reviseBuilder(item: BarType<undefined>) {
+    Row() {
+      Text(item.text)
+        .fontSize($r('[basic].float.page_text_font_size_12'))
+        .fontColor('#80000000')
+      Row() {
+        if (typeof item.src == 'string') {
+          Image(this.userInfo.getHeadImg() ? this.userInfo.getHeadImg() : $r('app.media.default_img'))
+            .height(24)
+            .width(24)
+            .borderRadius(12)
+        } else {
+          Text(this.userInfo.getName() ?? this.userInfo.getPhoneNumber() ?? this.userInfo.getId()?.toString())
+            .fontSize($r('[basic].float.page_text_font_size_12'))
+            .fontColor('#CC000000')
+        }
+        Image($r('app.media.right_arrow'))
+          .width(24)
+          .width(24)
+      }
+    }
+    .width('100%')
+    .justifyContent(FlexAlign.SpaceBetween)
+    .padding({ left: 12, right: 4 })
+    .height(36)
+    .onClick(item.click)
+  }
+
+  @Builder
+  reviseNameBuilder() {
+    Column() {
+
+      Text('昵称')
+        .width('100%')
+        .height(36)
+        .textAlign(TextAlign.Start)
+        .fontSize(12)
+
+      TextInput({ text: $$this.value, placeholder: '请输入昵称' })
+        .borderRadius(4)
+        .fontSize(12)
+        .placeholderFont({ size: 12 })
+        .placeholderColor('#14000000')
+        .height(36)
+        .width('100%')
+        .backgroundColor(Color.White)
+        .border({ width: 1, color: '#14000000' })
+        .margin({ bottom: 25 })
+
+      Row({ space: 32 }) {
+        YtButton({
+          btContent: '取消',
+          btHeight: 25,
+          btWidth: 78,
+          btBorder: { width: 1, color: '#1A000000' },
+          btPadding: {
+            left: 26,
+            right: 26,
+            top: 4,
+            bottom: 4
+          },
+          bgc: Color.White,
+          btFontColor: $r('sys.color.mask_secondary'),
+          click: () => {
+            this.showReviseName = false
+            this.value = ''
+
+          }
+        })
+        YtButton({
+          btContent: '完成',
+          btHeight: 25,
+          btWidth: 78,
+          btPadding: {
+            left: 26,
+            right: 26,
+            top: 4,
+            bottom: 4
+          },
+          click: () => {
+            if (!this.value) {
+              IBestToast.show({
+                type: "warning",
+                message: "昵称不能为空"
+              })
+              return
+            }
+            yTRequest.changeNickname(this.value, () => {
+              IBestToast.show({ message: '名称修改成功', type: 'success' })
+            })
+            //TODO 发送请求后关闭弹窗
+            this.showReviseName = false
+          }
+        })
+      }
+      .justifyContent(FlexAlign.Center)
+      .width('100%')
+
+    }
+    .padding({ left: 22, right: 22, top: 8 })
+  }
+
+  @Builder
+  changeBuilder() {
+    reviseImgHeaderBuilder(this.options)
+  }
+}

+ 138 - 0
features/user/src/main/ets/pages/SuggestionPage.ets

@@ -0,0 +1,138 @@
+import { IBestToast, YtAvoid, YtButton, YTHeader, yTRequest } from 'basic'
+
+@Builder
+function suggestionBuilder() {
+  NavDestination() {
+    SuggestionPage()
+  }
+  .hideTitleBar(true)
+}
+
+@Component
+struct SuggestionPage {
+  @StorageProp(YtAvoid.safeTopKey) safeTop: number = 0
+  @StorageProp(YtAvoid.safeBottomKey) safeBottom: number = 0
+  @State description: string = ''
+  @State contact: string = ''
+
+  build() {
+    Column() {
+      YTHeader({ title: '意见反馈' })
+      Row() {
+        Row() {
+          Text()
+            .width(3)
+            .height(10)
+            .backgroundColor('#FF95BFBF')
+            .margin({ right: 3 })
+          Text() {
+            Span('问题描述 ')
+              .fontColor('#CC000000')
+
+            Span('(必填)')
+              .fontColor('#FFFD4437')
+          }
+          .fontSize($r('[basic].float.page_text_font_size_12'))
+          .fontWeight(500)
+        }
+      }
+      .width('100%')
+      .alignItems(VerticalAlign.Bottom)
+      .margin({ bottom: 4, top: 26 })
+      .justifyContent(FlexAlign.SpaceBetween)
+      .padding({ left: 16, right: 20 })
+
+
+      Text('请尽量将问题描述详细')
+        .fontSize($r('[basic].float.page_text_font_size_10'))
+        .fontColor('#4D000000')
+        .margin({ bottom: 8 })
+        .width('100%')
+        .textAlign(TextAlign.Start)
+        .padding({ left: 22 })
+
+      Column() {
+        TextArea({ text: $$this.description })
+          .width('100%')
+          .padding(5)
+          .fontSize($r('[basic].float.page_text_font_size_12'))
+          .fontColor('#CC000000')
+          .borderRadius(4)
+          .border({ width: 1, color: '#14000000' })
+          .height(155)
+          .backgroundColor(Color.White)
+      }
+      .padding({ left: 22, right: 22 })
+      .margin({ bottom: 23 })
+
+      Row() {
+        Row() {
+          Text()
+            .width(3)
+            .height(10)
+            .backgroundColor('#FF95BFBF')
+            .margin({ right: 3 })
+          Text() {
+            Span('联系方式 ')
+              .fontColor('#CC000000')
+
+            Span('(选填)')
+              .fontColor('#CC000000')
+          }
+          .fontSize($r('[basic].float.page_text_font_size_12'))
+          .fontWeight(500)
+        }
+      }
+      .width('100%')
+      .alignItems(VerticalAlign.Bottom)
+      .margin({ bottom: 4, top: 26 })
+      .justifyContent(FlexAlign.SpaceBetween)
+      .padding({ left: 16, right: 20 })
+
+
+      Text('请输入手机号或QQ号')
+        .fontSize($r('[basic].float.page_text_font_size_10'))
+        .fontColor('#4D000000')
+        .margin({ bottom: 8 })
+        .width('100%')
+        .textAlign(TextAlign.Start)
+        .padding({ left: 22 })
+
+      Column() {
+        TextArea({ text: $$this.contact })
+          .width('100%')
+          .borderRadius(4)
+          .padding(5)
+          .fontSize($r('[basic].float.page_text_font_size_12'))
+          .fontColor('#CC000000')
+          .border({ width: 1, color: '#14000000' })
+          .height(87)
+          .backgroundColor(Color.White)
+      }
+      .padding({ left: 22, right: 22 })
+      .margin({ bottom: 160 })
+
+      Column() {
+        YtButton({
+          btContent: '提交反馈',
+          click: () => {
+            if (!this.description) {
+              IBestToast.show({
+                type: 'warning',
+                message: '请输入问题描述'
+              })
+              return
+            }
+            yTRequest.questionBack(this.description, this.contact)
+            // //TODO 发请求提交反馈
+            // YTLog.info(this.description + this.contact + '反馈提交了')
+          }
+        })
+      }
+      .padding({ left: 18, right: 18 })
+
+    }
+    .padding({ top: this.safeTop, bottom: this.safeBottom })
+
+  }
+}

+ 3 - 0
features/user/src/main/ets/utils/Calc.ets

@@ -0,0 +1,3 @@
+export function add(a: number, b: number) {
+  return a + b;
+}

+ 14 - 0
features/user/src/main/module.json5

@@ -0,0 +1,14 @@
+{
+  "module": {
+    "name": "user",
+    "type": "shared",
+    "description": "$string:shared_desc",
+    "deviceTypes": [
+      "phone",
+      "tablet",
+      "2in1"
+    ],
+    "deliveryWithInstall": true,
+    "pages": "$profile:main_pages"
+  }
+}

Some files were not shown because too many files changed in this diff