export class RichTextParser {
  static parse(str: string): MyCustomSpan[] {
    // 只解析话题和@,不解析“xxx的视频”
    const regex = /#([^#\s,。!?、,.!?:;【】\[\]\(\)]+)#|#([^#\s,。!?、,.!?:;【】\[\]\(\)]+)|(@[\u4e00-\u9fa5A-Za-z0-9_]+)/g;
    const result: MyCustomSpan[] = [];
    let lastIndex = 0;
    let id = 0;

    const matches = Array.from(str.matchAll(regex));
    for (const match of matches) {
      const offset = match.index ?? 0;
      // 普通文本
      if (offset > lastIndex) {
        result.push(new MyCustomSpan(id++, MyCustomSpanType.Normal, str.slice(lastIndex, offset)));
      }
      // 话题
      if (match[1] || match[2]) {
        const topic = '#' + (match[1] || match[2]);
        result.push(new MyCustomSpan(id++, MyCustomSpanType.Hashtag, topic));
      }
      // @提及
      else if (match[3]) {
        result.push(new MyCustomSpan(id++, MyCustomSpanType.Mention, match[3]));
      }
      lastIndex = offset + match[0].length;
    }
    // 结尾剩余普通文本
    if (lastIndex < str.length) {
      result.push(new MyCustomSpan(id++, MyCustomSpanType.Normal, str.slice(lastIndex)));
    }
    return result;
  }
}

/**
 * 自定义Message类
 */
export class CustomMessage {
  id: number | string;
  profile: string | Resource; // 头像
  userName: string; // 用户名
  subTitle: string; // 副标题
  spans: MyCustomSpan[]; // Text组件中展示的 Span 列表
  media?: Resource; // 内容

  constructor(id: number | string, profile: string | Resource, userName: string, subTitle: string,
    spans: MyCustomSpan[], media?: Resource) {
    this.id = id;
    this.profile = profile;
    this.userName = userName;
    this.subTitle = subTitle;
    this.spans = spans;
    if (media) {
      this.media = media;
    }
  }
}

/**
 * 自定义Span类
 */
export class MyCustomSpan {
  id: number | string; // 文本id
  type: MyCustomSpanType; // 文本类型
  content: string; // 文本内容
  url?: string; // 跳转的链接地址

  constructor(id: number | string, type: MyCustomSpanType = MyCustomSpanType.Normal, content: string, url?: string) {
    this.id = id;
    this.type = type;
    this.content = content;
    if (url) {
      this.url = url;
    }
  }
}

/**
 * 定义Span类型的枚举
 */
export enum MyCustomSpanType {
  Normal, // 普通文本,不含任何特殊格式或标记
  Mention, // @提及
  Hashtag, // 话题标签
  VideoLink, // 视频链接
  DetailLink // 正文详情
}

/*驴友动态详情文章详情*/
@Entry
@ComponentV2
struct DEDetailsComponent {
  @Local text: string =
    '🌞来中东最地道体验,怎么能少了#沙漠骆驼 呢!开车进入瓦希巴沙漠深处的星空营地,住奢华的帐篷酒店,骑骆驼是必不可少的贝都因体验!~\n' +
      '📌位置:在阿曼的中部腹地,距马斯喀特约2小时车程,绵延千里,需四驱越野穿越流动沙丘\n' +
      '🔥【必体验】贝都因三部曲:骆驼、沙丘与星空这里是贝都因人传承千年的家园,更是追寻原汁原味阿曼风情的终极目的地。\n' +
      '🐫骑骆驼:千年驼铃重响跨上单峰骆驼,跟随贝都因向导的节奏穿越沙海,感受丝绸之路上商队的历史回响。驼铃声中,学习用星辰定位的古老导航智慧。\n' +
      '🖼️登顶沙丘:#追逐光的艺术 傍晚时分,乘坐越野车翻越沙丘。登顶后在沙脊线上看夕阳将沙海染成焦糖色一勃艮第红一薰衣草紫的三重渐变。@遇到困难睡大觉'
  // "【准备回家!#神十七乘组4月30日回地球】#神十八太空过国12314125rara#按计划,在轨驻留期间,神十八乘组将迎来天舟八号货运飞船、神舟十九号载人飞船的来访,计划于今年10月下旬返回东风着陆场。祝一切顺利!#神十八新闻发布会#神十七乘组在与神十八乘组完成在轨轮换后,计划于本月30日返回东风着陆场。祝一切顺利!"
  // '【#消防员作证这娃真是从石缝钻出来的#】近日,河南郑州,男童在碎石堆上玩耍,脚下一滑不慎掉进石缝中,刚好被牢牢卡住无法出来。@郑州消防 赶赴,利用撬棍支撑固定石块,搬开压在男孩身上的石头将其成功救出,并飞奔送上救护车。中国消防的视频';
  @Local clickSpanId: number | string = ''; // 当前点击span的id
  @Local parsedData: MyCustomSpan[] = []

  aboutToAppear(): void {
    this.parsedData = this.parsed()
  }

  parsed(): MyCustomSpan[] {
    return RichTextParser.parse(this.text);
  }

  // 仅文本的超链接话题Span组件
  @Builder
  hyperlinkSpanBuilder(item: MyCustomSpan) {
    Span(item.content)
      .fontColor('#0FD7B8')
      .textBackgroundStyle({
        color: this.clickSpanId === item.id ? "#85bce5" :
        Color.Transparent
      }).onClick(() => {

    })

  }

  // 仅文本的@提及Span组件
  @Builder
  mentionSpanBuilder(item: MyCustomSpan) {
    Span(item.content)
      .fontColor('#175CD3')
      .textBackgroundStyle({
        color: this.clickSpanId === item.id ? "#85bce5" :
        Color.Transparent
      }).onClick(() => {

    })

  }

  build() {
    Column({ space: 12 }) {
      Text() {
        ForEach(this.parsedData, (item: MyCustomSpan) => {
          if (item.type === MyCustomSpanType.Normal) {
            Span(item.content)
          } else if (item.type === MyCustomSpanType.Hashtag ||
            item.type === MyCustomSpanType.DetailLink) {
            this.hyperlinkSpanBuilder(item)
          } else if (item.type === MyCustomSpanType.Mention) {
            this.mentionSpanBuilder(item)
          }
        }, (item: MyCustomSpan, index: number) => `${index}_${JSON.stringify(item)}`)
      }
      .width('100%')
      .fontColor('#3F3F46')
      .fontSize(16)
      .lineHeight(24)
      .textAlign(TextAlign.JUSTIFY)
      .wordBreak(WordBreak.BREAK_ALL)
    }.width('100%').padding({ left: 14, right: 14, top: 16 })

  }
}

Logo

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

更多推荐