Text组件的enableDataDetector如何让链接在App内打开,而不是跳转系统浏览器?思路是利用dataDetectorConfig提供的onDetectResultUpdate回调拿到链接高亮信息,然后手动设置链接的点击事件。

实现代码:

import { promptAction } from '@kit.ArkUI'

const logTag = "[LinkHighlight]"

interface RecognizeResult {
  code:number
  entity:RecognizeResultEntity[]
}
interface RecognizeResultEntity {
  start:number
  end:number
  entityContent:string
  entityType:string
}

@Entry
@ComponentV2
struct Index {
  @Local message: string = 'ArkUI 是一套构建分布式应用界面的声明式 UI 开发框架。它的官网是:https://developer.huawei.com/consumer/cn/arkui/。它使用简洁的 UI 信息语法、丰富的 UI 组件、以及实时界面预览工具,帮助你提升 HarmonyOS 应用界面开发效率。你只需使用一套 ArkTS API,就能在多个 HarmonyOS 设备上提供生动而流畅的用户界面体验。';
  @Local
  private isEnableDataDetector:boolean = true
  @Local
  private controller: TextController = new TextController()
  build() {
    RelativeContainer() {
      // 关键点0:设置TextController
      Text(this.message,{ controller:this.controller })
        .enableDataDetector(this.isEnableDataDetector)
        .dataDetectorConfig({
          types: [TextDataDetectorType.URL],
          onDetectResultUpdate: (resultJSONString:string) => {
            console.debug(logTag,"收到dataDetectorConfig的onDetectResultUpdate回调")
            // 不保证这个结构总是这样,所以要try-catch
            try {
              const structuralResult:RecognizeResult = JSON.parse(resultJSONString) as RecognizeResult
              if (structuralResult.code === 0) {
                console.debug(logTag,"识别成功")
                structuralResult.entity.forEach((entity) => {
                  if (entity.entityType === "url") {
                    const start:number = entity.start
                    const length:number = entity.end-entity.start
                    const urlString:string = entity.entityContent
                    const urlStylePartA:StyleOptions = {
                      start:start,
                      length:length,
                      styledKey: StyledStringKey.FONT,
                      styledValue: new TextStyle({
                        fontColor: "#5CBD6C"
                      })
                    }
                    const urlStylePartB:StyleOptions = {
                      start:start,
                      length:length,
                      styledKey: StyledStringKey.DECORATION,
                      styledValue: new DecorationStyle({
                        type:TextDecorationType.Underline,
                        color: "#5CBD6C",
                        style:TextDecorationStyle.SOLID
                      })
                    }
                    // 关键点1:设置链接部分的文本可以被点击
                    const urlClickAction:StyleOptions = {
                      start:start,
                      length:length,
                      styledKey: StyledStringKey.GESTURE,
                      styledValue: new GestureStyle({
                        onClick:() => {
                          promptAction.showToast({ message:`用户点击了链接(${urlString}),您这时候可以推出App内的网页浏览页面` })
                        }
                      })
                    }
                    this.controller.setStyledString(new StyledString(this.message,[urlStylePartA,urlStylePartB,urlClickAction]))
                  }
                })
              } else {
                console.debug(logTag,"链接识别失败")
              }
            } catch (error) {
              console.debug(logTag,"链接识别出错")
            }
            // 因为我们已经自行处理了链接的点击事件,我们把DataDetector的默认链接点击处理(在系统浏览器打开)给禁用掉。
            this.isEnableDataDetector = false
          }
        })
      // 注意:目前的实现中不会响应文本内容的更变,需要响应文本内容的更变,请看本文中的下一个代码块
    }
    .height('100%')
    .width('100%')
  }
}

上面的实现支持了静态文本的链接高亮(不支持更新文本内容),下面的实现支持了更改文本内容后,自动刷新文本和重新计算链接高亮。

import { promptAction } from '@kit.ArkUI'

const logTag = "[LinkHighlight]"

interface RecognizeResult {
  code: number
  entity: RecognizeResultEntity[]
}

interface RecognizeResultEntity {
  start: number
  end: number
  entityContent: string
  entityType: string
}

@Entry
@ComponentV2
struct Index {
  @Local message: string =
    'ArkUI 是一套构建分布式应用界面的声明式 UI 开发框架。它的官网是:https://developer.huawei.com/consumer/cn/arkui/。它使用简洁的 UI 信息语法、丰富的 UI 组件、以及实时界面预览工具,帮助你提升 HarmonyOS 应用界面开发效率。你只需使用一套 ArkTS API,就能在多个 HarmonyOS 设备上提供生动而流畅的用户界面体验。';
  @Local
  private isEnableDataDetector: boolean = true
  @Local
  private controller: TextController = new TextController()
  // 在文本变更的时候触发重新计算链接高亮
  @Monitor("message")
  private onMessageChange() {
    // 因为刚才setStyledString了,为确保显示的文本正常刷新,应重新设置StyledString
    this.controller.setStyledString(new StyledString(this.message))
    // 重新打开dataDetector,它会重新计算链接高亮,并再次调用onDetectResultUpdate回调
    this.isEnableDataDetector = true
  }
  build() {
    RelativeContainer() {
      // 关键点0:设置TextController
      Text(this.message, { controller: this.controller })
        .enableDataDetector(this.isEnableDataDetector)
        .dataDetectorConfig({
          types: [TextDataDetectorType.URL],
          onDetectResultUpdate: (resultJSONString: string) => {
            console.debug(logTag, "收到dataDetectorConfig的onDetectResultUpdate回调")
            // 不保证这个结构总是这样,所以要try-catch
            try {
              const structuralResult: RecognizeResult = JSON.parse(resultJSONString) as RecognizeResult
              if (structuralResult.code === 0) {
                console.debug(logTag, "识别成功")
                structuralResult.entity.forEach((entity) => {
                  if (entity.entityType === "url") {
                    const start: number = entity.start
                    const length: number = entity.end - entity.start
                    const urlString: string = entity.entityContent
                    const urlStylePartA: StyleOptions = {
                      start: start,
                      length: length,
                      styledKey: StyledStringKey.FONT,
                      styledValue: new TextStyle({
                        fontColor: "#5CBD6C"
                      })
                    }
                    const urlStylePartB: StyleOptions = {
                      start: start,
                      length: length,
                      styledKey: StyledStringKey.DECORATION,
                      styledValue: new DecorationStyle({
                        type: TextDecorationType.Underline,
                        color: "#5CBD6C",
                        style: TextDecorationStyle.SOLID
                      })
                    }
                    // 关键点1:设置链接部分的文本可以被点击
                    const urlClickAction: StyleOptions = {
                      start: start,
                      length: length,
                      styledKey: StyledStringKey.GESTURE,
                      styledValue: new GestureStyle({
                        onClick: () => {
                          promptAction.showToast({
                            message: `用户点击了链接(${urlString}),您这时候可以推出App内的网页浏览页面`
                          })
                        }
                      })
                    }
                    this.controller.setStyledString(new StyledString(this.message,
                      [urlStylePartA, urlStylePartB, urlClickAction]))
                  }
                })
              } else {
                console.debug(logTag, "链接识别失败")
              }
            } catch (error) {
              console.debug(logTag, "链接识别出错")
            }
            // 因为我们已经自行处理了链接的点击事件,我们把DataDetector的默认链接点击处理(在系统浏览器打开)给禁用掉。
            this.isEnableDataDetector = false
          }
        })
    }
    .height('100%')
    .width('100%')
    .onAppear(() => {
      // 模拟2.3秒后文本内容发生更变
      setTimeout(() => {
        console.debug(logTag, "已更改文本内容")
        this.message =
          "您可以点此查阅ArkUI的文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/application-dev-guide。感谢您的使用。"
      }, 2300)
    })
  }
}

稍作改进,也可以实现:点击链接,先弹窗确认“您即将跳转到外部网站”,再拉起浏览器。

import { promptAction } from '@kit.ArkUI'
import { common, Want } from '@kit.AbilityKit'

const logTag = "[LinkHighlight]"

interface RecognizeResult {
  code: number
  entity: RecognizeResultEntity[]
}

interface RecognizeResultEntity {
  start: number
  end: number
  entityContent: string
  entityType: string
}

@Entry
@ComponentV2
struct Index {
  @Local message: string =
    'ArkUI 是一套构建分布式应用界面的声明式 UI 开发框架。它的官网是:https://developer.huawei.com/consumer/cn/arkui/。它使用简洁的 UI 信息语法、丰富的 UI 组件、以及实时界面预览工具,帮助你提升 HarmonyOS 应用界面开发效率。你只需使用一套 ArkTS API,就能在多个 HarmonyOS 设备上提供生动而流畅的用户界面体验。';
  @Local
  private isEnableDataDetector: boolean = true
  @Local
  private controller: TextController = new TextController()
  // 在文本变更的时候触发重新计算链接高亮
  @Monitor("message")
  private onMessageChange() {
    // 因为刚才setStyledString了,为确保显示的文本正常刷新,应重新设置StyledString
    this.controller.setStyledString(new StyledString(this.message))
    // 重新打开dataDetector,它会重新计算链接高亮,并再次调用onDetectResultUpdate回调
    this.isEnableDataDetector = true
  }
  @Local
  private selectedURL:string | null = null
  @Local
  private showConfirm:boolean = false
  build() {
    RelativeContainer() {
      // 关键点0:设置TextController
      Text(this.message, { controller: this.controller })
        .enableDataDetector(this.isEnableDataDetector)
        .dataDetectorConfig({
          types: [TextDataDetectorType.URL],
          onDetectResultUpdate: (resultJSONString: string) => {
            console.debug(logTag, "收到dataDetectorConfig的onDetectResultUpdate回调")
            // 不保证这个结构总是这样,所以要try-catch
            try {
              const structuralResult: RecognizeResult = JSON.parse(resultJSONString) as RecognizeResult
              if (structuralResult.code === 0) {
                console.debug(logTag, "识别成功")
                structuralResult.entity.forEach((entity) => {
                  if (entity.entityType === "url") {
                    const start: number = entity.start
                    const length: number = entity.end - entity.start
                    const urlString: string = entity.entityContent
                    const urlStylePartA: StyleOptions = {
                      start: start,
                      length: length,
                      styledKey: StyledStringKey.FONT,
                      styledValue: new TextStyle({
                        fontColor: "#5CBD6C"
                      })
                    }
                    const urlStylePartB: StyleOptions = {
                      start: start,
                      length: length,
                      styledKey: StyledStringKey.DECORATION,
                      styledValue: new DecorationStyle({
                        type: TextDecorationType.Underline,
                        color: "#5CBD6C",
                        style: TextDecorationStyle.SOLID
                      })
                    }
                    // 关键点1:设置链接部分的文本可以被点击
                    const urlClickAction: StyleOptions = {
                      start: start,
                      length: length,
                      styledKey: StyledStringKey.GESTURE,
                      styledValue: new GestureStyle({
                        onClick: () => {
                          this.selectedURL = urlString
                          this.showConfirm = true
                        }
                      })
                    }
                    this.controller.setStyledString(new StyledString(this.message,
                      [urlStylePartA, urlStylePartB, urlClickAction]))
                  }
                })
              } else {
                console.debug(logTag, "链接识别失败")
              }
            } catch (error) {
              console.debug(logTag, "链接识别出错")
            }
            // 因为我们已经自行处理了链接的点击事件,我们把DataDetector的默认链接点击处理(在系统浏览器打开)给禁用掉。
            this.isEnableDataDetector = false
          }
        })
    }
    .height('100%')
    .width('100%')
    .bindMenu(!!this.showConfirm,[{
      value:"是",
      action:() => {
        // 拉起系统浏览器
        let context = getContext(this) as common.UIAbilityContext;
        let wantInfo: Want = {
          action: 'ohos.want.action.viewData',
          entities: ['entity.system.browsable'],
          uri: this.selectedURL
        }
        context.startAbility(wantInfo).then(() => {
          console.debug(logTag,"已打开")
        }).catch((err:Error) => {
          console.debug(logTag,"出错了")
        })
      }
    },{
      value:"否",
      action:() => {}
    }],{title:`是否跳转到外部链接${this.selectedURL}`,onDisappear:() => { this.showConfirm = false }})
    .onAppear(() => {
      // 模拟2.3秒后文本内容发生更变
      setTimeout(() => {
        console.debug(logTag, "已更改文本内容")
        this.message =
          "我们可以点此查阅ArkUI的文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/application-dev-guide。感谢您的使用。"
      }, 2300)
    })
  }
}

实现:点击部分链接可以直接在App内打开(比如来自“mydomain.cn”的链接),其它链接则在外部浏览器打开。

import { promptAction } from '@kit.ArkUI'
import { common, Want } from '@kit.AbilityKit'

const logTag = "[LinkHighlight]"

interface RecognizeResult {
  code: number
  entity: RecognizeResultEntity[]
}

interface RecognizeResultEntity {
  start: number
  end: number
  entityContent: string
  entityType: string
}



@Entry
@ComponentV2
struct Index {
  @Local message: string =
    'ArkUI 是一套构建分布式应用界面的声明式 UI 开发框架。它的官网是:https://developer.huawei.com/consumer/cn/arkui/。它使用简洁的 UI 信息语法、丰富的 UI 组件、以及实时界面预览工具,帮助你提升 HarmonyOS 应用界面开发效率。你只需使用一套 ArkTS API,就能在多个 HarmonyOS 设备上提供生动而流畅的用户界面体验。';
  @Local
  private isEnableDataDetector: boolean = true
  @Local
  private controller: TextController = new TextController()
  // 在文本变更的时候触发重新计算链接高亮
  @Monitor("message")
  private onMessageChange() {
    // 因为刚才setStyledString了,为确保显示的文本正常刷新,应重新设置StyledString
    this.controller.setStyledString(new StyledString(this.message))
    // 重新打开dataDetector,它会重新计算链接高亮,并再次调用onDetectResultUpdate回调
    this.isEnableDataDetector = true
  }
  build() {
    RelativeContainer() {
      // 关键点0:设置TextController
      Text(this.message, { controller: this.controller })
        .enableDataDetector(this.isEnableDataDetector)
        .dataDetectorConfig({
          types: [TextDataDetectorType.URL],
          onDetectResultUpdate: (resultJSONString: string) => {
            console.debug(logTag, "收到dataDetectorConfig的onDetectResultUpdate回调")
            // 不保证这个结构总是这样,所以要try-catch
            try {
              const structuralResult: RecognizeResult = JSON.parse(resultJSONString) as RecognizeResult
              if (structuralResult.code === 0) {
                console.debug(logTag, "识别成功")
                structuralResult.entity.forEach((entity) => {
                  if (entity.entityType === "url") {
                    const start: number = entity.start
                    const length: number = entity.end - entity.start
                    const urlString: string = entity.entityContent
                    const urlStylePartA: StyleOptions = {
                      start: start,
                      length: length,
                      styledKey: StyledStringKey.FONT,
                      styledValue: new TextStyle({
                        fontColor: "#5CBD6C"
                      })
                    }
                    const urlStylePartB: StyleOptions = {
                      start: start,
                      length: length,
                      styledKey: StyledStringKey.DECORATION,
                      styledValue: new DecorationStyle({
                        type: TextDecorationType.Underline,
                        color: "#5CBD6C",
                        style: TextDecorationStyle.SOLID
                      })
                    }
                    // 关键点1:设置链接部分的文本可以被点击
                    const urlClickAction: StyleOptions = {
                      start: start,
                      length: length,
                      styledKey: StyledStringKey.GESTURE,
                      styledValue: new GestureStyle({
                        onClick: () => {
                          if (isMyDomain(urlString)) {
                            // 是本App域名,在App内打开
                            promptAction.showToast({ message:"用户点击了本App域名,在App内打开:"+urlString })
                          } else {
                            // 非本App域名,拉起系统浏览器
                            let context = getContext(this) as common.UIAbilityContext;
                            let wantInfo: Want = {
                              action: 'ohos.want.action.viewData',
                              entities: ['entity.system.browsable'],
                              uri: urlString
                            }
                            context.startAbility(wantInfo).then(() => {
                              console.debug(logTag,"已打开")
                            }).catch((err:Error) => {
                              console.debug(logTag,"出错了")
                            })
                          }
                        }
                      })
                    }
                    this.controller.setStyledString(new StyledString(this.message,
                      [urlStylePartA, urlStylePartB, urlClickAction]))
                  }
                })
              } else {
                console.debug(logTag, "链接识别失败")
              }
            } catch (error) {
              console.debug(logTag, "链接识别出错")
            }
            // 因为我们已经自行处理了链接的点击事件,我们把DataDetector的默认链接点击处理(在系统浏览器打开)给禁用掉。
            this.isEnableDataDetector = false
          }
        })
    }
    .height('100%')
    .width('100%')
    .onAppear(() => {
      // 模拟2.3秒后文本内容发生更变
      setTimeout(() => {
        console.debug(logTag, "已更改文本内容")
        this.message =
          "如果是本App的域名:https://mydomain.cn/hello-world。那我们就在App内打开。"
      }, 2300)
    })
  }
}

// console.log(isMyDomain("https://mydomain.cn")); // true
// console.log(isMyDomain("http://www.mydomain.cn")); // true
// console.log(isMyDomain("api.mydomain.cn:8080")); // true
// console.log(isMyDomain("mydomain.cn/path/to/page")); // true
// console.log(isMyDomain("http://notmydomain.cn")); // false
// console.log(isMyDomain("http://fakemydomain.cn")); // false
// console.log(isMyDomain("http://mydomain.cn.evil.com")); // false
// console.log(isMyDomain("http://a.b.mydomain.cn")); // true
// console.log(isMyDomain("evil.mydomain.cn.evil.com")); // false
// console.log(isMyDomain("HTTP://WWW.MYDOMAIN.CN")); // true
function isMyDomain(rawUrl: string): boolean {
  const domain = 'mydomain.cn';

  // 去掉协议(http://, https://)
  let url = rawUrl.trim().toLowerCase();
  url = url.replace(/^https?:\/\//, '');

  // 只取主机部分,去掉端口、路径、查询参数
  // 找第一个 `/`,只保留前面
  const slashIndex = url.indexOf('/');
  if (slashIndex !== -1) {
    url = url.substring(0, slashIndex);
  }
  // 去掉端口
  const colonIndex = url.indexOf(':');
  if (colonIndex !== -1) {
    url = url.substring(0, colonIndex);
  }

  // 现在 url 应该就是个 hostname
  // 先检查是不是 mydomain.cn 本身
  if (url === domain) {
    return true;
  }

  // 再检查是不是以 .mydomain.cn 结尾
  if (url.endsWith('.' + domain)) {
    // 额外防御:防止 evil.mydomain.cn.evil.com 这种
    const rest = url.slice(0, url.length - domain.length - 1); // 去掉 .mydomain.cn
    if (rest.indexOf('.') === -1) {
      // rest 不能是空,但也不能包含更多的点(防止 a.b.mydomain.cn)
      return true;
    }
    if (rest.length > 0) {
      return true;
    }
  }

  return false;
}

实现:只对白名单中的链接高亮。

import { promptAction } from '@kit.ArkUI'
import { common, Want } from '@kit.AbilityKit'

const logTag = "[LinkHighlight]"

interface RecognizeResult {
  code: number
  entity: RecognizeResultEntity[]
}

interface RecognizeResultEntity {
  start: number
  end: number
  entityContent: string
  entityType: string
}



@Entry
@ComponentV2
struct Index {
  @Local message: string = "这是一个不可信域名:http://nottrusted.cn。我们不会高亮它。"
  @Local
  private isEnableDataDetector: boolean = true
  @Local
  private controller: TextController = new TextController()
  // 在文本变更的时候触发重新计算链接高亮
  @Monitor("message")
  private onMessageChange() {
    // 因为刚才setStyledString了,为确保显示的文本正常刷新,应重新设置StyledString
    this.controller.setStyledString(new StyledString(this.message))
    // 重新打开dataDetector,它会重新计算链接高亮,并再次调用onDetectResultUpdate回调
    this.isEnableDataDetector = true
  }
  @Local
  private trustedDomains:string[] = ['mydomain.cn', 'trusted.cn', 'secure.site']

  build() {
    RelativeContainer() {
      // 关键点0:设置TextController
      Text(this.message, { controller: this.controller })
        .enableDataDetector(this.isEnableDataDetector)
        .dataDetectorConfig({
          types: [TextDataDetectorType.URL],
          onDetectResultUpdate: (resultJSONString: string) => {
            console.debug(logTag, "收到dataDetectorConfig的onDetectResultUpdate回调")
            // 不保证这个结构总是这样,所以要try-catch
            try {
              const structuralResult: RecognizeResult = JSON.parse(resultJSONString) as RecognizeResult
              if (structuralResult.code === 0) {
                console.debug(logTag, "识别成功")
                structuralResult.entity.forEach((entity) => {
                  if (entity.entityType === "url") {
                    const urlString: string = entity.entityContent
                    if (isTrustedDomain(urlString,this.trustedDomains)) {
                      // 可信域名,高亮
                      const start: number = entity.start
                      const length: number = entity.end - entity.start
                      const urlStylePartA: StyleOptions = {
                        start: start,
                        length: length,
                        styledKey: StyledStringKey.FONT,
                        styledValue: new TextStyle({
                          fontColor: "#5CBD6C"
                        })
                      }
                      const urlStylePartB: StyleOptions = {
                        start: start,
                        length: length,
                        styledKey: StyledStringKey.DECORATION,
                        styledValue: new DecorationStyle({
                          type: TextDecorationType.Underline,
                          color: "#5CBD6C",
                          style: TextDecorationStyle.SOLID
                        })
                      }
                      // 关键点1:设置链接部分的文本可以被点击
                      const urlClickAction: StyleOptions = {
                        start: start,
                        length: length,
                        styledKey: StyledStringKey.GESTURE,
                        styledValue: new GestureStyle({
                          onClick: () => {
                            // 是可信域名,拉起系统浏览器
                            let context = getContext(this) as common.UIAbilityContext;
                            let wantInfo: Want = {
                              action: 'ohos.want.action.viewData',
                              entities: ['entity.system.browsable'],
                              uri: urlString
                            }
                            context.startAbility(wantInfo).then(() => {
                              console.debug(logTag,"已打开")
                            }).catch((err:Error) => {
                              console.debug(logTag,"出错了")
                            })
                          }
                        })
                      }
                      this.controller.setStyledString(new StyledString(this.message,
                        [urlStylePartA, urlStylePartB, urlClickAction]))
                    } else {
                      // 不可信域名,不高亮
                    }
                  }
                })
              } else {
                console.debug(logTag, "链接识别失败")
              }
            } catch (error) {
              console.debug(logTag, "链接识别出错")
            }
            // 因为我们已经自行处理了链接的点击事件,我们把DataDetector的默认链接点击处理(在系统浏览器打开)给禁用掉。
            this.isEnableDataDetector = false
          }
        })
    }
    .height('100%')
    .width('100%')
    .onAppear(() => {
      // 模拟2.3秒后文本内容发生更变
      setTimeout(() => {
        console.debug(logTag, "已更改文本内容")
        this.message =
          "如果是可信域名:https://mydomain.cn/hello-world。那我们就高亮。"
      }, 2300)
    })
  }
}

// const trustedDomains = ['mydomain.cn', 'trusted.cn', 'secure.site'];
//
// console.log(isTrustedDomain("https://mydomain.cn", trustedDomains)); // true
// console.log(isTrustedDomain("http://www.mydomain.cn", trustedDomains)); // true
// console.log(isTrustedDomain("api.trusted.cn:8080", trustedDomains)); // true
// console.log(isTrustedDomain("secure.site/path?query=123", trustedDomains)); // true
// console.log(isTrustedDomain("http://nottrusted.cn", trustedDomains)); // false
// console.log(isTrustedDomain("http://evil.mydomain.cn.evil.com", trustedDomains)); // false
// console.log(isTrustedDomain("evil.secure.site.evil.com", trustedDomains)); // false
// console.log(isTrustedDomain("HTTP://WWW.TRUSTED.CN", trustedDomains)); // true
function isTrustedDomain(rawUrl: string, trustedDomains: string[]): boolean {
  // 预处理一下,把可信域名都转小写,方便比对
  const lowerTrustedDomains = trustedDomains.map(d => d.toLowerCase());

  let url = rawUrl.trim().toLowerCase();
  url = url.replace(/^https?:\/\//, '');

  // 只取主机部分,去掉端口、路径、查询参数
  const slashIndex = url.indexOf('/');
  if (slashIndex !== -1) {
    url = url.substring(0, slashIndex);
  }
  const colonIndex = url.indexOf(':');
  if (colonIndex !== -1) {
    url = url.substring(0, colonIndex);
  }

  for (const domain of lowerTrustedDomains) {
    if (url === domain) {
      return true;
    }
    if (url.endsWith('.' + domain)) {
      // 防止 evil.domain.com
      const rest = url.slice(0, url.length - domain.length - 1);
      if (rest.length > 0) {
        return true;
      }
    }
  }

  return false;
}
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐