瀏覽代碼

Merge remote-tracking branch 'origin/master'

# Conflicts:
#	features/feature/src/main/ets/view/MainView.ets
chenritian 3 周之前
父節點
當前提交
ac7ec45e1e

+ 3 - 0
commons/basic/Index.ets

@@ -92,10 +92,13 @@ export { YTAddressSelectorDialog, DateOption } from './src/main/ets//datepicker/
 
 export { YTDateUtil, DateFormat } from './src/main/ets/utils/YTDateUtil'
 
+export { ytOaidUtils } from './src/main/ets/utils/YtOaidUtils'
+
 export * from '@mumu/crop'
 
 export * from './src/main/ets/models'
 
+export { YTCalendarPicker } from './src/main/ets/components/generalComp/YTCalendarPicker.ets'
 
 
 

+ 14 - 1
commons/basic/src/main/ets/apis/YTRequest.ets

@@ -25,10 +25,23 @@ export const instance = axios.create({
 // 添加请求拦截器
 instance.interceptors.request.use((config: InternalAxiosRequestConfig) => {
 
-  // 对请求数据做点什么
+  // 对请求数据做点什么 - 为请求参数添加 token
   if (AppStorage.get<string>(AppStorageKeyCollect.TOKEN)) {
     config.headers.Authorization = AppStorage.get<string>(AppStorageKeyCollect.TOKEN)
   }
+
+  // 为请求参数添加 oaid
+  if ((config.method == 'get' || config.method == 'delete') && config.url) {
+    // if(config?.params) {
+    //   config.params.oaid = await ytOaidUtils.getOaid()
+    // }
+  } else {
+    // if(config?.data) {
+    //   config.data.oaid = await ytOaidUtils.getOaid()
+    // }
+  }
+
+
   YTLog.info(config.data, '请求参数')
 
   return config;

+ 112 - 75
commons/basic/src/main/ets/components/generalComp/YTCalendarPicker.ets

@@ -1,42 +1,62 @@
-import { BasicType, DateInfo } from "../../models";
+import { DateInfo } from "../../models";
 
 @Component
 @CustomDialog
 export struct YTCalendarPicker {
+  /**
+   * 外部传入
+   */
+  // 主题色
+  linearInfo: LinearGradientOptions = { colors: [['#B9FD2A', 0.01], ['#F5FD6D', 1]], angle: 110 }
+
+  // 边距
+  cPadding: Padding | Length | LocalizedPadding = { left: 0, right: 0, top: 20, bottom: 4  }
+
+  // yyyy-mm-dd 格式的数组, 符合数组内格式的日期下会有标记
+  dateList: string[] = []
+
+  // 取消
+  onCancel: () => void = () => {}
+
+  // 确定
+  onConfirm: (date: Date) => void = () => {}
+
+  // 点击某一天的回调
+  onClickItem: (date: Date) => void = () => {}
+
+  // 是否显示头部标题
+  showTitle: boolean = true
+
+  /**
+   * 内部方法
+   */
+
   // 年月
-  @State year: number = 2025;
-  @State month: number = 8;
+  @State year: number = new Date().getFullYear();
+  @State month: number = new Date().getMonth() + 1;
+
   // 开始是星期几
-  @State weekStart: number = 1;
+  @State private weekStart: number = 1;
   // 本月的天数
-  @State daysInMonth: number = 31;
+  @State private daysInMonth: number = 31;
   // 渲染用的月份数组
-  @State monthArray: DateInfo[] = [];
-  @State showDatePickerMenu: boolean = false
-  @State selectDay: Date = new Date()
+  @State private monthArray: DateInfo[] = [];
+  // 当前选中的日期
+  @State private selectDay: Date = new Date()
+  // 显示选择日期的菜单
+  @State private showDatePickerMenu: boolean = false
+
+  // 弹窗控制器 -
+  diaLogControl?: CustomDialogController
+  // 星期中文映射表
+  private readonly WEEK_MAP = ['日', '一', '二', '三', '四', '五', '六'];
+  // 日期选择器的范围
   private declare range: TextCascadePickerRangeContent[]
-  private years =
-    Array.from<number, number>({ length: new Date().getFullYear() - 1970 + 1 }, (_: number, i: number) => 1970 + i)
+  // 年份 - 日期选择器使用
+  private years = Array.from<number, number>({ length: new Date().getFullYear() - 1970 + 1 }, (_: number, i: number) => 1970 + i)
   //当前年月选择的下标
   private selectedIndex: number[] = [new Date().getFullYear() - 1970, new Date().getMonth()]
-  private linearInfo: LinearGradientOptions = {
-    colors: [['#B9FD2A', 0.01], ['#F5FD6D', 1]],
-    angle: 110
-  }
-  // 星期中文映射表
-  private readonly WEEK_MAP = ['日', '一', '二', '三', '四', '五', '六'];
-  // 取消
-  onCancel: () => void = () => {
-  }
-  // 确定
-  onConfirm: (date: Date) => void = () => {
-  }
-  // 右上角的结构
-  @BuilderParam rightTopBuild: () => void = this.buttonRow;
-  // yy-mm-dd 格式的数组, 符合数组内格式的日期下会有标记
-  @Require dateList: string[] = []
 
-  diaLogControl?: CustomDialogController
 
   /**
    * 回调
@@ -60,6 +80,14 @@ export struct YTCalendarPicker {
   // 点击单个日期
   clickItem(item: DateInfo){
     this.selectDay = item.id
+    this.onClickItem(this.selectDay)
+  }
+
+  // 更改日期选择器的显示和隐藏
+  changeDatePickerMenu(show: boolean) {
+    animateToImmediately({ duration: 200 }, () => {
+      this.showDatePickerMenu = show
+    })
   }
 
   /**
@@ -167,7 +195,7 @@ export struct YTCalendarPicker {
   }
 
   aboutToAppear(): void {
-    this.changeMonth(2025, 8);
+    this.changeMonth(this.year, this.month);
 
     const monthsRange =
       Array.from<number, number>({ length: 12 }, (_: number, i: number) => 1 + i).map(month => {
@@ -181,41 +209,50 @@ export struct YTCalendarPicker {
   build() {
     Column({ space: 20 }) {
       // 月份切换和头部组件
-      Row() {
-        // 月份切换
-        Row({ space: 8 }) {
-          Text(`${this.year}-${this.month.toString().padStart(2, '0')}`)
-            .fontSize(16)
-            .fontWeight(600)
-
-          Image($r('app.media.ic_back'))
-            .width(14)
-            .height(8)
+      if(this.showTitle) {
+        Row() {
+          // 月份切换
+          Row({ space: 8 }) {
+            Text(`${this.year}-${this.month.toString().padStart(2, '0')}`)
+              .fontSize(16)
+              .fontWeight(600)
+              .fontColor(Color.Black)
+
+            Image($r('app.media.ic_back'))
+              .width(14)
+              .height(8)
+              .rotate({angle: this.showDatePickerMenu ? 90 : 270})
+          }
+          .onClick(() => {
+            this.changeDatePickerMenu(true)
+          })
+          .bindMenu(this.showDatePickerMenu, this.dateSelectMenu, {
+            onDisappear: () => {
+              this.changeDatePickerMenu(false)
+              this.year = this.range[this.selectedIndex[0]].text.valueOf() as number
+              this.month = this.range[this.selectedIndex[0]].children![this.selectedIndex[1]].text.valueOf() as number
+              this.changeMonth(this.year, this.month)
+            },
+            placement: Placement.Bottom
+          })
+
+          // 右上角的结构
+          this.buttonRow()
         }
-        .onClick(() => {
-          this.showDatePickerMenu = true
-        })
-        .bindMenu(this.showDatePickerMenu, this.dateSelectMenu, {
-          onDisappear: () => {
-            this.showDatePickerMenu = false
-            this.year = this.range[this.selectedIndex[0]].text.valueOf() as number
-            this.month = this.range[this.selectedIndex[0]].children![this.selectedIndex[1]].text.valueOf() as number
-            this.changeMonth(this.year, this.month)
-          },
-          placement: Placement.Bottom
-        })
-
-        // 右上角的结构
-        this.rightTopBuild()
+        .width("100%")
+        .alignItems(VerticalAlign.Center)
+        .justifyContent(FlexAlign.SpaceBetween)
       }
-      .width("100%")
-      .alignItems(VerticalAlign.Center)
-      .justifyContent(FlexAlign.SpaceBetween)
 
       // 周 title 的显示
       Row() {
         ForEach(this.WEEK_MAP, (item: string, index: number) => {
-          Text(item)
+          Row(){
+            Text(item)
+              .fontColor(Color.Black)
+          }
+          .layoutWeight(1)
+          .justifyContent(FlexAlign.Center)
         })
       }
       .width("100%")
@@ -225,7 +262,7 @@ export struct YTCalendarPicker {
       Grid() {
         ForEach(this.monthArray, (item: DateInfo, index: number) => {
           GridItem() {
-            Column({ space: 3 }) {
+            Column({ space: 5 }) {
               if (item.id) {
                 // 日历-单个日期的组件
                 Row() {
@@ -233,14 +270,14 @@ export struct YTCalendarPicker {
                     .fontSize(12)
                     .fontColor(this.isSameDay(item.id, this.selectDay) ? Color.Black : '#979797')
                 }
-                .width(32)
+                .width("100%")
                 .aspectRatio(1)
                 .borderRadius(8)
+                .alignItems(VerticalAlign.Center)
+                .justifyContent(FlexAlign.Center)
                 .linearGradient(this.isSameDay(item.id, this.selectDay) ? this.linearInfo : {
                   colors: [['#F6F6F6', 1]],
                 })
-                .alignItems(VerticalAlign.Center)
-                .justifyContent(FlexAlign.Center)
                 .onClick(() => {
                   this.clickItem(item)
                 })
@@ -250,41 +287,37 @@ export struct YTCalendarPicker {
                   .width(4)
                   .aspectRatio(1)
                   .borderRadius(2)
-                  .linearGradient(this.dateList.indexOf(this.formatDateToCustomString(item.id, false)) !== -1 ?
-                  this.linearInfo : null)
+                  .linearGradient(this.dateList.indexOf(this.formatDateToCustomString(item.id, false)) !== -1 ? this.linearInfo : null)
               } else {
                 // 空白占位符
                 Text('')
               }
             }
-            .width(49)
-            .aspectRatio(1)
+            .width(32)
             .alignItems(HorizontalAlign.Center)
             .justifyContent(FlexAlign.Center)
           }
         })
       }
-      .columnsTemplate('repeat(7, 1fr)')
+      .rowsGap(10)
+      .maxCount(6)
       .width("100%")
+      .columnsTemplate('repeat(7, 1fr)')
     }
     .width("100%")
-    .height(400)
+    .padding(this.cPadding)
     .backgroundColor(Color.White)
-    .padding({
-      left: 15,
-      right: 15,
-      top: 20,
-      bottom: 4
-    })
   }
 
+  // 日期选择菜单
   @Builder
   private dateSelectMenu() {
     Stack({ alignContent: Alignment.Center }) {
       TextPicker({ range: this.range, selected: this.selectedIndex })
-        .selectedTextStyle({ color: '#FF353C46', font: { weight: 500, size: 16 } })
-        .defaultPickerItemHeight(36)
         .divider(null)
+        .defaultPickerItemHeight(36)
+        .backgroundColor(Color.White)
+        .selectedTextStyle({ color: '#FF353C46', font: { weight: 500, size: 16 } })
         .onScrollStop((value, index) => {
           console.log(`testLog ${value} ${index}`)
           this.selectedIndex = index as number[]
@@ -303,14 +336,17 @@ export struct YTCalendarPicker {
     .width(160)
     .padding(12)
     .borderRadius(8)
+    .backgroundColor(Color.White)
   }
 
+  // 右上角结构 - 确定和取消按钮
   @Builder
   private buttonRow() {
     Row({ space: 14 }) {
       Text("取消")
         .borderRadius(36)
         .backgroundColor('#F6F6F6')
+        .fontColor(Color.Black)
         .padding({
           left: 20,
           top: 5,
@@ -321,6 +357,7 @@ export struct YTCalendarPicker {
       Text("确认")
         .borderRadius(36)
         .linearGradient(this.linearInfo)
+        .fontColor(Color.Black)
         .padding({
           left: 20,
           top: 5,

+ 11 - 1
commons/basic/src/main/ets/constants/index.ets

@@ -52,7 +52,17 @@ export enum AppStorageKeyCollect {
    * @type string
    * @description 推送token
    */
-  PUSH_TOKEN = 'pushToken'
+  PUSH_TOKEN = 'pushToken',
+  /**
+   * @type boolean
+   * @description 是否首次进入APP
+   */
+  FIRST_ENTER_APP = 'firstEnterApp',
+  /**
+   * @type boolean
+   * @description 是否为单机状态下的app
+   */
+  IS_STAND_ALONE_APP = 'IsStandAloneApp'
 }
 
 export enum EmitterKeyCollection {

+ 8 - 2
commons/basic/src/main/ets/rdb/utils/RelationalStoreUtis.ts

@@ -16,10 +16,16 @@ export class RelationalStoreUtils {
   private static cloudLoadStore: relationalStore.RdbStore | undefined = undefined;
 
   //初始化数据库
-  static initReplayCloudStore(context: Context, success?: () => void) {
+  static initReplayCloudStore(context: Context, success?: () => void, isDeBug: boolean = false) {
+    let securityLevel: relationalStore.SecurityLevel = relationalStore.SecurityLevel.S3
+    // 在 debug模式下,数据库安全级别为S1。 确保数据库可视化工具可以正常访问
+    if (isDeBug) {
+      securityLevel = relationalStore.SecurityLevel.S1
+    }
+
     const REPLAY_CLOUD_STORE_CONFIG: relationalStore.StoreConfig = {
       name: 'YT_HM.db', // 数据库文件名
-      securityLevel: relationalStore.SecurityLevel.S3, // 数据库安全级别
+      securityLevel: securityLevel, // 数据库安全级别
       encrypt: false, // 可选参数,指定数据库是否加密,默认不加密
       customDir: 'customDir/subCustomDir', // 可选参数,数据库自定义路径。数据库将在如下的目录结构中被创建:context.databaseDir + '/rdb/' + customDir,其中context.databaseDir是应用沙箱对应的路径,'/rdb/'表示创建的是关系型数据库,customDir表示自定义的路径。当此参数不填时,默认在本应用沙箱目录下创建RdbStore实例。
       isReadOnly: false // 可选参数,指定数据库是否以只读方式打开。该参数默认为false,表示数据库可读可写。该参数为true时,只允许从数据库读取数据,不允许对数据库进行写操作,否则会返回错误码801。

+ 22 - 5
commons/basic/src/main/ets/utils/ContextHelper.ets

@@ -8,7 +8,9 @@ import {
   registerFontUtil,
   yTBindSheet,
   YTLog,
+  ytOaidUtils,
   yTPreferences,
+  yTRouter,
   YTToast,
   YTUserRequest
 } from '../../../../Index'
@@ -65,11 +67,26 @@ export class ContextHelper {
   }
 
 
-  //依赖上下文的工具类初始化 需要在第一个页面中设置UIContext后调用
-  static init(context: UIContext) {
+  /**
+   * 依赖上下文的工具类初始化 需要在第一个页面中设置UIContext后调用
+   * @param context 上下文对象
+   * @param isOnline 是否为在线app
+   */
+  static init(context: UIContext, isOnline: boolean = true) {
     ContextHelper.UIContext = context
-    PersistentStorage.persistProp(AppStorageKeyCollect.TOKEN, '')
-    YTUserRequest.refreshUserInfo()
+    PersistentStorage.persistProp(AppStorageKeyCollect.IS_STAND_ALONE_APP, isOnline)
+    if(isOnline) {
+      PersistentStorage.persistProp(AppStorageKeyCollect.TOKEN, '')
+      YTUserRequest.refreshUserInfo()
+    } else {
+      PersistentStorage.persistProp(AppStorageKeyCollect.FIRST_ENTER_APP, false)
+      let isFirst = AppStorage.get(AppStorageKeyCollect.FIRST_ENTER_APP)! as boolean
+      if(!isFirst) {
+        yTRouter.pushPathByName('EntryPage', null)
+      }
+    }
+
+
     permissionController.init(ContextHelper.context)
       .then(() => {
         permissionController
@@ -85,7 +102,7 @@ export class ContextHelper {
                   } else {
                     const oaid: string = data;
                     jHStartAd.init(ContextHelper.UIAbilityContext, oaid)
-                    AppStorage.setOrCreate('OAID', oaid)
+                    ytOaidUtils.setOaid(oaid)
                     YTLog.info(`succeeded in getting oaid by callback , oaid: ${oaid}`);
                   }
                 });

+ 28 - 0
commons/basic/src/main/ets/utils/YtOaidUtils.ets

@@ -0,0 +1,28 @@
+/**
+ * @description oaid 管理类
+ */
+class YtOaidUtils{
+  private messageQueue: ESObject[] =[]
+
+  getOaid(){
+    let oaid = AppStorage.get<string>('OAID')
+    if(!oaid) {
+      let p = new Promise<string>((resolve, reject) => {
+        this.messageQueue.push({resolve, reject})
+      })
+      return p
+    } else {
+      return Promise.resolve(oaid)
+    }
+  }
+
+  setOaid(oaid: string) {
+    AppStorage.setOrCreate('OAID', oaid)
+    this.messageQueue.forEach((item: ESObject) => {
+      item.resolve(oaid)
+    })
+    this.messageQueue = []
+  }
+}
+
+export const ytOaidUtils = new YtOaidUtils()

+ 6 - 1
features/feature/src/main/ets/view/MainView.ets

@@ -1,4 +1,6 @@
-import { BackgroundPageModifier, DateOption, YTAddressSelectorDialog, YTDateUtil,
+import { BackgroundPageModifier, DateOption, YTAddressSelectorDialog,
+  YTCalendarPicker,
+  YTDateUtil,
   YtProgressComp,
   yTRouter } from 'basic'
 import { UnitType } from 'basic/src/main/ets/datepicker/DatePickerEnums'
@@ -39,11 +41,14 @@ export struct MainView {
           }
           yTDateDialog.show(dateOption) //设置好配置之后打开日历的函数
         })
+
       Button('跳转页面测试')
         .onClick(()=>{
           yTRouter.pushPathByName('TestRouterPage',null)
         })
 
+      YTCalendarPicker()
+
       YtProgressComp({
         growPoint:this.point,
         v1Point:{

+ 26 - 1
hvigorfile.ts

@@ -2,6 +2,7 @@ import { appTasks } from '@ohos/hvigor-ohos-plugin';
 import { HvigorPlugin, HvigorNode } from '@ohos/hvigor';
 import fs from 'fs';
 import path from 'path';
+import { execSync } from 'child_process';
 
 function customPlugin(): HvigorPlugin {
     return {
@@ -478,10 +479,34 @@ function rDBPlugin(): HvigorPlugin {
     }
 }
 
+function forwardingPort(){
+    return {
+        pluginId: 'customPlugin',
+        apply(node: HvigorNode) {
+            try {
+                var connectKeys = execSync('hdc list targets', { encoding: 'utf-8' }).split('\n');
+
+                var targetKey = connectKeys[0].trim();
+
+                var operation = `hdc -t ${targetKey} fport rm tcp:8080 tcp:8080`
+                var result = execSync(operation, { encoding: 'utf-8' })
+                console.log(`执行命令 ${operation}, 删除端口结果:${result.trim()}`);
+
+                operation = `hdc -t ${targetKey} fport tcp:8080 tcp:8080`
+                result = execSync(operation, { encoding: 'utf-8' })
+                console.log(`执行命令 ${operation}, 转发端口结果:${result.trim()}`);
+            } catch (error) {
+                console.error(`执行失败: ${error.message}`);
+            }
+        }
+    }
+}
+
 export default {
     system: appTasks,  /* Built-in plugin of Hvigor. It cannot be modified. */
     plugins: [
         customPlugin(),  // 应用自定义Plugin
-        rDBPlugin()
+        rDBPlugin(),
+        forwardingPort()
     ]         /* Custom plugin to extend the functionality of Hvigor. */
 }

+ 0 - 28
oh-package-lock.json5

@@ -1,28 +0,0 @@
-{
-  "meta": {
-    "stableOrder": true,
-    "enableUnifiedLockfile": false
-  },
-  "lockfileVersion": 3,
-  "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.",
-  "specifiers": {
-    "@ohos/hamock@1.0.0": "@ohos/hamock@1.0.0",
-    "@ohos/hypium@1.0.21": "@ohos/hypium@1.0.21"
-  },
-  "packages": {
-    "@ohos/hamock@1.0.0": {
-      "name": "@ohos/hamock",
-      "version": "1.0.0",
-      "integrity": "sha512-K6lDPYc6VkKe6ZBNQa9aoG+ZZMiwqfcR/7yAVFSUGIuOAhPvCJAo9+t1fZnpe0dBRBPxj2bxPPbKh69VuyAtDg==",
-      "resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/hamock/-/hamock-1.0.0.har",
-      "registryType": "ohpm"
-    },
-    "@ohos/hypium@1.0.21": {
-      "name": "@ohos/hypium",
-      "version": "1.0.21",
-      "integrity": "sha512-iyKGMXxE+9PpCkqEwu0VykN/7hNpb+QOeIuHwkmZnxOpI+dFZt6yhPB7k89EgV1MiSK/ieV/hMjr5Z2mWwRfMQ==",
-      "resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/hypium/-/hypium-1.0.21.har",
-      "registryType": "ohpm"
-    }
-  }
-}

+ 1 - 0
oh-package.json5

@@ -2,6 +2,7 @@
   "modelVersion": "5.0.2",
   "description": "Please describe the basic information.",
   "dependencies": {
+    "@hadss/debug-db": "^1.0.0-rc.10"
   },
   "devDependencies": {
     "@ohos/hypium": "1.0.21",

+ 8 - 0
products/entry/build-profile.json5

@@ -1,6 +1,14 @@
 {
   "apiType": "stageMode",
   "buildOption": {
+    "arkOptions": {
+      "branchElimination": true,
+      "runtimeOnly": {
+        "packages": [
+          '@hadss/debug-db'
+        ],
+      },
+    }
   },
   "buildOptionSet": [
     {

+ 12 - 1
products/entry/src/main/ets/entryability/EntryAbility.ets

@@ -4,6 +4,7 @@ import { window } from '@kit.ArkUI';
 import {
   AppStorageKeyCollect, IBestInit, RelationalStoreUtils, YTAvoid, YTBreakPoint, YTLog
 } from 'basic';
+import BuildProfile from 'BuildProfile';
 
 const DOMAIN = 0x0000;
 
@@ -22,6 +23,17 @@ export default class EntryAbility extends UIAbility {
         hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
         return;
       }
+
+      // 动态引入: 仅在 DEBUG 模式下引入该模块
+      if (BuildProfile.DEBUG) {
+        const pkg = '@hadss/debug-db';
+        import(pkg).then(async (ns: ESObject) => {
+          ns.DebugDB.initialize(this.context, { port: 8080, defaultStart: true });
+        });
+      }
+      RelationalStoreUtils.initReplayCloudStore(this.context, null, BuildProfile.DEBUG)
+
+
       YTLog.init({
         tag: "YTLog",
         domain: 0x0000,
@@ -66,7 +78,6 @@ export default class EntryAbility extends UIAbility {
         AppStorage.setOrCreate(AppStorageKeyCollect.SCREEN_WIDTH, px2vp(size.width))
         AppStorage.setOrCreate(AppStorageKeyCollect.SCREEN_HEIGHT, px2vp(size.height))
       })
-      RelationalStoreUtils.initReplayCloudStore(this.context)
     });
 
   }

+ 69 - 0
products/entry/src/main/ets/pages/EntryPage.ets

@@ -0,0 +1,69 @@
+import { common } from '@kit.AbilityKit'
+import { RouterPage, yTRouter } from 'basic'
+
+// app 入口页面
+@Component
+@RouterPage
+struct EntryPage {
+  // 渐变色
+  linear: LinearGradientOptions = {
+    colors: [ ['#EBF7FF', 0.3], ['#A9A8FF', 1] ],
+  }
+
+  // 点击立刻体验按钮
+  _onClick() {
+    yTRouter.pushPathByName('PrivacyPage', null)
+  }
+
+  // 重写返回事件
+  _onBack() {
+    // 点击返回直接关闭 app
+    (this.getUIContext().getHostContext() as common.UIAbilityContext).terminateSelf()
+    return true
+  }
+
+  build() {
+    NavDestination() {
+      Column() {
+        Column(){
+          Column({space: 13}){
+            Image($r('app.media.app_icon'))
+              .width(100)
+              .aspectRatio(1)
+              .borderRadius(20)
+
+            Text($r('app.string.app_name'))
+              .fontSize(18)
+              .fontWeight(500)
+              .fontColor(Color.Black)
+          }
+          .backgroundColor(Color.Transparent)
+
+          Blank()
+            .height(197)
+
+          Text("立刻体验")
+            .fontSize(22)
+            .fontWeight(500)
+            .borderRadius(20)
+            .fontColor('#FAFAFA')
+            .backgroundColor('#7186F9')
+            .padding({left: 25, top: 7, right: 25, bottom: 7})
+            .onClick(() => { this._onClick() })
+        }
+        .justifyContent(FlexAlign.SpaceBetween)
+      }
+      .width('100%')
+      .height('100%')
+      .alignItems(HorizontalAlign.Center)
+      .justifyContent(FlexAlign.Center)
+      .linearGradient(this.linear)
+    }
+    .hideTitleBar(true)
+    .onBackPressed(() => { return this._onBack() })
+  }
+}
+@Builder
+function EntryBuilder() {
+  EntryPage()
+}

+ 2 - 1
products/entry/src/main/ets/pages/Index.ets

@@ -33,7 +33,8 @@ struct Index {
   tabsController: TabsController = new TabsController()
 
   aboutToAppear(): void {
-    ContextHelper.init(this.getUIContext())
+    // 设置为 false 表示为单机 app
+    ContextHelper.init(this.getUIContext(), false)
   }
 
   build() {

+ 126 - 0
products/entry/src/main/ets/pages/PrivacyPage.ets

@@ -0,0 +1,126 @@
+import { AppStorageKeyCollect, BasicType, YTAvoid, yTRouter, RouterPage } from 'basic'
+import { webview } from '@kit.ArkWeb'
+
+
+// 隐私协议 说明页面
+@Component
+@RouterPage
+struct PrivacyPage {
+  @StorageProp(YTAvoid.SAFE_TOP_KEY) safeTop: number = 0
+  // 倒计时
+  @State time: number = 5
+
+  private webviewController: WebviewController = new webview.WebviewController()
+  private forEach: Array<BasicType> = [
+    {
+      text: '隐私政策',
+      message: 'https://hm-test.ytpm.net/classAffairsPrivacy'
+    }, {
+      text: '用户协议',
+      message: 'https://hm-test.ytpm.net/classAffairsUserAgreement'
+    }
+  ]
+
+  _onBack(){
+    yTRouter.pop()
+    return true
+  }
+
+  // 同意
+  _onConfirm(){
+    AppStorage.set(AppStorageKeyCollect.FIRST_ENTER_APP, true)
+    yTRouter.clear()
+  }
+
+  aboutToAppear(): void {
+    let _c = setInterval(() => {
+      this.time--
+      if(this.time == 0) clearInterval(_c)
+    }, 1000)
+  }
+
+
+  build() {
+    NavDestination() {
+      Column({space: 40}) {
+        Text('隐私政策及用户协议')
+          .fontSize(18)
+          .fontWeight(500)
+          .fontColor(Color.Black)
+
+        // Main
+        Column({space: 10}){
+          ForEach(this.forEach, (item: BasicType) => {
+              Column({ space: 8 }) {
+                Text(item.text)
+                  .fontColor(Color.Black)
+                Row() {
+                  Web({
+                    src: item.message,
+                    controller: this.webviewController,
+                    renderMode: RenderMode.ASYNC_RENDER // 设置渲染模式
+                  })
+                }
+                .height(225)
+                .width('100%')
+                .padding(8)
+                .border({ width: 1 })
+              }
+              .alignItems(HorizontalAlign.Start)
+          })
+
+          Blank().height(10)
+
+          Row(){
+            Text('我不同意并返回')
+              .fontSize(16)
+              .fontWeight(400)
+              .fontColor('#676767')
+              .onClick(() => { this._onBack() })
+
+            Text(`我已全部了解${this.time != 0 ? ' ('+this.time+')' : '' }`)
+              .fontSize(18)
+              .fontWeight(500)
+              .borderRadius(20)
+              .fontColor('#FAFAFA')
+              .enabled(this.time == 0)
+              .backgroundColor(this.time == 0 ?'#7186F9' : '#ffacbaf3')
+              .padding({ left: 20, top: 8, right: 20, bottom: 8 })
+              .onClick(() => { this._onConfirm() })
+          }
+          .width('100%')
+          .padding({top: 5, bottom: 5})
+          .alignItems(VerticalAlign.Center)
+          .justifyContent(FlexAlign.SpaceBetween )
+
+          Row(){
+            Text('*请仔细阅读以上协议内容,点击同意表示您已充分理解并接受协议条款')
+              .fontSize(10)
+              .fontWeight(400)
+              .fontColor('#F50000')
+          }
+          .padding({top: 5})
+          .width("100%")
+          .justifyContent(FlexAlign.Center)
+        }
+        .width('100%')
+        .layoutWeight(1)
+        .alignItems(HorizontalAlign.Start)
+        .justifyContent(FlexAlign.Start)
+      }
+      .width('100%')
+      .height('100%')
+      .padding({left: 16, right: 16})
+      .alignItems(HorizontalAlign.Center)
+      .justifyContent(FlexAlign.Center)
+    }
+    .backgroundColor(Color.White)
+    .hideTitleBar(true)
+    .padding({ top: this.safeTop })
+    .onBackPressed(() => { return this._onBack() })
+  }
+}
+@Builder
+function PrivacyBuilder() {
+  PrivacyPage()
+}