【HarmonyOS 】工具函数封装 持续更迭...

在鸿蒙应用开发过程中,为了提高代码复用性和可维护性,通常会将一些通用的功能封装到工具类中。本文将介绍几个常见的工具类及其方法,包括字符串、数组、对象处理、日期格式化等。

1. 字符串处理工具函数 String

1.1 根据指定的空格截取类型对字符串进行空格处理

typescript 复制代码
/**
 * 空格截取类型枚举,用于定义字符串中空格去除的方式。
 */
enum ESpaceTrimType {
  ALL, // 去除所有空格(包括前、后、中间)
  BEFORE, // 仅去除前面的空格
  AFTER, // 仅去除后面的空格
  BEFORE_AND_AFTER // 仅去除前后空格,保留中间空格
}

/**
 * @param {string} str - 需要去除空格的原始字符串。
 * @param {ESpaceTrimType} [trimType=ESpaceTrimType.BEFORE_AND_AFTER] 
 * 指定空格去除方式,默认为去除前后空格。
 *
 * @returns {string} 处理后的字符串,根据 trimType 移除了相应位置的空格。
 *
 * @example
 * SpaceTrim('  Hello World  ', ESpaceTrimType.ALL)
 * // 返回 'HelloWorld'
 *
 * SpaceTrim('  Hello World  ', ESpaceTrimType.BEFORE)
 * // 返回 'Hello World  '
 *
 * SpaceTrim('  Hello World  ', ESpaceTrimType.AFTER)
 * // 返回 '  Hello World'
 *
 * SpaceTrim('  Hello World  ', ESpaceTrimType.BEFORE_AND_AFTER)
 * // 返回 'Hello World'
 */
const SpaceTrim = (str: string, trimType: ESpaceTrimType = ESpaceTrimType.BEFORE_AND_AFTER):  string => {
   switch (trimType){
     case ESpaceTrimType.ALL:
       return str.replace(/\s+/g, '')
     case ESpaceTrimType.BEFORE:
       return str.replace(/(^\s*)/g, '')
     case ESpaceTrimType.AFTER:
       return str.replace(/(\s*$)/g, '')
     case ESpaceTrimType.BEFORE_AND_AFTER:
       return str.replace(/(^\s*)|(\s*$)/g, '')
   }
}

1.2 对目标字符串或数字进行脱敏处理,保留前后位数,中间部分用星号(*)代替

typescript 复制代码
/**
 * 适用于手机号、身份证号、银行卡号等敏感信息的显示保护。
 *
 * @param {number | string} target - 需要脱敏的目标值,可以是数字或字符串形式的数字。
 * @param {number} [prefixLength=3] - 保留的前缀字符数,默认为 3。
 * @param {number} [suffixLength=4] - 保留的后缀字符数,默认为 4。
 *
 * @returns {string | number} 脱敏后的字符串;如果原始值为空或长度不足以脱敏,则返回原值。
 *
 * @example
 * Desensitization('13800001111') 
 * // 返回 '138******1111'
 *
 * Desensitization(123456789, 2, 2)
 * // 返回 '12****89'
 *
 * Desensitization('1234', 2, 3)
 * // 返回 '1234' (因为总长度小于 prefixLength + suffixLength)
 */
 
const Desensitization = (target: number | string, prefixLength: number = 3, suffixLength: number = 4) => {
  if (target) {
    let targetStr = target.toString()
    const prefix = targetStr.substring(0, prefixLength) // 前几位
    const suffix = targetStr.substring(targetStr.length - suffixLength) // 后几位
    if (targetStr.length > prefixLength + suffixLength) {
      const maskedPart = '*'.repeat(targetStr.length - prefixLength - suffixLength) // 中间部分用 * 替换
      return `${prefix}${maskedPart}${suffix}`
    }
  }
  return target
}

1.3 生成一个模拟 UUID 的随机字符串(遵循 UUID v4 格式规范)

typescript 复制代码
/**
 * UUID v4 是基于随机数生成的唯一标识符,格式为:
 * `xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`
 * 其中:
 * - 第3段以 `4` 开头(表示 UUID 版本 4)
 * - 第4段以 `8`, `9`, `a`, 或 `b` 开头(符合 RFC 4122 规范)
 *
 * @returns {string} 返回一个模拟 UUID v4 的随机字符串。
 *
 * @example
 * toAnyString()
 * // 返回类似 '6a2e8f50-a219-4d7d-bf8c-0a9d3e1f7b5c'
 */
function toAnyString() {
  const str: string = 'xxxxx-xxxxx-4xxxx-yxxxx-xxxxx'.replace(/[xy]/g, (c: string) => {
    const r: number = (Math.random() * 16) | 0
    const v: number = c === 'x' ? r : (r & 0x3) | 0x8
    return v.toString()
  })
  return str
}

1.4 将字符串中每个单词的首字母转换为大写,其余字母保持小写

typescript 复制代码
/**
 * 适用于格式化标题、姓名、标签等需要首字母大写的场景。
 *
 * @param {string} str - 需要处理的原始字符串。
 * @returns {string} 每个单词首字母大写的新字符串。
 *
 * @example
 * firstUpperCase('hello world')
 * // 返回 'Hello World'
 *
 * firstUpperCase('THIS IS A TEST')
 * // 返回 'This Is A Test'
 */
function firstUpperCase(str: string) {
  return str.toLowerCase().replace(/( |^)[a-z]/g, (L) => L.toUpperCase())
}

1.5 将十六进制颜色字符串(HEX)转换为 RGB 数值数组

typescript 复制代码
/**
 * 支持标准格式如 `#FF5500` 和简写格式如 `#FFF`。
 * 转换后返回一个包含红、绿、蓝三个颜色通道数值的数组,每个数值范围为 0 到 255。
 *
 * @param {string} 【color】 
 *    十六进制颜色字符串。可以是 3 位(简写)或 6 位(标准),可选前缀 `#`。
 *    示例:'#FF5500'、'FF5500'、'#ABC'、'ABC'
 *
 * @returns {number[]} 
 *    包含三个数字元素的数组,分别代表红(Red)、绿(Green)、蓝(Blue)通道的值。
 *    示例:'#FF5500' → [255, 85, 0]、'#ABC' → [170, 187, 204]
 *
 * @throws {Error} 如果传入的颜色字符串不是 3 位或 6 位长度,则抛出错误。
 */
function getColorRGB(color: string): number[] {
  // 去除可能的 # 并转为小写
  let hex = color.replace(/^#/, '').toLowerCase();
  // 检查是否为合法长度(3 或 6)
  if (![3, 6].includes(hex.length)) {
    throw new Error('Invalid hex color format. Expected 3 or 6 characters.');
  }
  // 如果是3位简写,扩展为6位(例如 'f' -> 'ff')
  if (hex.length === 3) {
    hex = hex.replace(/./g, match => match + match);
  }
  // 使用正则将字符串按每两位一组分割,并转换为十进制数值
  const rgb = hex.match(/.{2}/g)?.map(channel => parseInt(`0x${channel}`)) || [];

  return rgb;
}

1.6 将十六进制颜色值转换为 RGBA 字符串格式

typescript 复制代码
/**
 * @param {string} _color - 十六进制颜色字符串,如 '#FF5733' 或 '#F5A'
 * @param {number} _opacity - 透明度(范围 0 到 1)
 * @returns {string} 转换后的 RGBA 字符串,例如 'rgba(255, 87, 51, 0.5)'
 *                   如果输入无效,则返回原始字符串或空字符串
 */
const hexToRGBA = (_color: string, _opacity: number): string => {
  let sColor = _color?.toLowerCase();

  // 定义十六进制颜色的正则表达式,支持 3 位或 6 位格式
  const reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/;

  // 检查是否为有效的十六进制颜色格式
  if (sColor && reg.test(sColor)) {
    // 如果是简写的 3 位格式(如 #F5A),将其扩展为标准的 6 位格式(如 #FF55AA)
    if (sColor.length === 4) {
      let sColorNew = "#";
      for (let i = 1; i < 4; i += 1) {
        sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1));
      }
      sColor = sColorNew;
    }

    // 处理标准的 6 位十六进制颜色值,提取 R、G、B 分量
    const sColorChange: number[] = [];
    for (let i = 1; i < 7; i += 2) {
      // 将每个两位的十六进制值转换为十进制数
      sColorChange.push(parseInt("0x" + sColor.slice(i, i + 2), 16));
    }

    // 构造并返回 RGBA 字符串
    return `rgba(${sColorChange.join(", ")}, ${_opacity})`;
  }

  // 如果输入无效,返回原始颜色值
  return sColor ?? '';
};

1.7 将对象参数转换为查询字符串并拼接到 URL 上

typescript 复制代码
/**
 * @param baseUrl - 基础 URL
 * @param obj - 要转换为查询参数的对象
 * @returns 拼接后的完整 URL
 *
 * 示例:
 * const url = setObjToUrlParams("https://api.example.com", { a: 1, b: "hello" });
 * // 结果: "https://api.example.com?a=1&b=hello"
 *
 * const urlWithParams = setObjToUrlParams("https://api.example.com?a=1", { b: "test" });
 * // 结果: "https://api.example.com?a=1&b=test"
 */
function setObjToUrlParams(baseUrl: string, obj: object): string {
  const params = Object.keys(obj).map(key => {
    return `${key}=${encodeURIComponent(obj[key])}`;
  }).join('&');

  const separator = baseUrl.includes('?') ? '' : '?';
  const cleanUrl = baseUrl.endsWith('?') || baseUrl.endsWith('&') ? baseUrl : baseUrl;

  return `${cleanUrl}${separator}${params}`;
}

1.8 判断传入的字符串是否为 Base64 编码的图片数据 URI

typescript 复制代码
/**
 * 支持格式如:
 * - data:image/png;base64,iVBORw0KG...
 * - data:image/jpeg;base64,/9j/4AAQ...
 *
 * @param str - 需要判断的字符串
 * @returns 如果是 Base64 格式的图片数据,返回 `true`;否则返回 `false`
 *
 * 示例:
 * isBase64Image('data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...')
 * // true
 *
 * isBase64Image('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFQQMEDQsKDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwN/wAARCAABAAEDASIAAhEBAxEB/2gAMAwEAAhEDEQA/APf6ACiiigD//Z')
 * // true
 *
 * isBase64Image('https://example.com/image.png')
 * // false
 *
 * isBase64Image('not-a-base64-string')
 * // false
 */
function isBase64Image(str: string): boolean {
  // 判断字符串是否以 "data:image/" 开头并且包含 "base64"
  const base64PrefixRegex = /^data:image/([a-zA-Z]*);base64,/;
  return base64PrefixRegex.test(str);
}

1.9 移除 Base64 图片数据的前缀部分(如 "data:image/png;base64,")

typescript 复制代码
/**
 * @param base64String - 包含前缀的完整 Base64 图片字符串
 * @returns 去除前缀后的纯 Base64 编码字符串
 *
 * ### 示例
 * removeBase64Prefix('data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...')
 * // 返回: 'iVBORw0KGgoAAAANSUhEUg...'
 *
 * removeBase64Prefix('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFQQMEDQsKDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwN/wAARCAABAAEDASIAAhEBAxEB/2gAMAwEAAhEDEQA/APf6ACiiigD//Z')
 * // 返回: '/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFQQMEDQsKDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwMDQwN/wAARCAABAAEDASIAAhEBAxEB/2gAMAwEAAhEDEQA/APf6ACiiigD//Z'
 *
 * removeBase64Prefix('iVBORw0KGgoAAAANSUhEUg...')
 * // 返回: 'iVBORw0KGgoAAAANSUhEUg...'
 */
function removeBase64Prefix(base64String: string) {
  const regex = /^data:image\/[^;]+;base64,/;
  return base64String.replace(regex, "");
}

2. 数组|对象工具函数 Array | Object

2.1 根据指定的键对对象数组进行分组,返回一个以组合键为索引、对应元素数组为值的对象

typescript 复制代码
/**
 * @template T - 数组中每个元素的类型,必须是一个对象(object)
 * @template K - 指定分组依据的键的类型,必须是 T 的 key
 * @param {T[]} arr - 要分组的对象数组
 * @param {K[]} keys - 用于分组的一个或多个键(属性名)
 * @returns {Record<string, T[]>} 分组后的结果,键为组合键字符串,值为对应的元素数组
 *
 * @example
 * const testData1 = [
 *   { name: 'Alice', age: 25, city: 'Beijing' },
 *   { name: 'Bob', age: 30, city: 'Shanghai' },
 *   { name: 'Charlie', age: 25, city: 'Beijing' }
 * ];
 *
 * groupByKeys(testData1, ['city']);
 * // 返回:
 * // {
 * //   'Beijing': [{ name: 'Alice', ... }, { name: 'Charlie', ... }],
 * //   'Shanghai': [{ name: 'Bob', ... }]
 * // }
 */
const groupByKeys = <T extends object, K extends keyof T>(arr: T[], keys: K[]): Record<string, T[]> => {
  return arr.reduce<Record<string, T[]>>((acc, current) => {
    const groupKey = keys.map((key) => Reflect.get(current, key) as T).join('-')
    if (!acc[groupKey]) {
      acc[groupKey] = []
    }
    acc[groupKey].push(current)
    return acc
  }, {} as Record<string, T[]>)
}

2.2 深拷贝函数,用于复制对象或数组及其嵌套结构,避免引用共享

typescript 复制代码
/**
 * @param {ESObject} obj - 要深拷贝的对象或数组
 * @returns {ESObject} 返回一个与原对象结构相同但内存独立的新对象
 */
function deepCopy(obj: ESObject): ESObject {
  if (typeof obj !== 'object' || obj === null) {
    return obj;
  }
  let copy: ESObject;
  if (Array.isArray(obj)) {
    copy = [];
    for (let i = 0; i < obj.length; i++) {
      copy[i] = deepCopy(obj[i]);
    }
  } else {
    copy = {};
    for (let i = 0; i < obj.length(); i++) {
      let key: ESObject = obj[i];
      if (obj.hasOwnProperty(key)) {
        copy[key] = deepCopy(obj[key]);
      }
    }
  }
  return copy;
}

2.3 对两个数组进行对比,删除数组 A 中与数组 B 相同的数据(根据指定的唯一标识字段)

typescript 复制代码
/**
 * @param dataA - 数组 A,从中移除匹配项
 * @param dataB - 数组 B,包含需要匹配的项
 * @param id - 用于比较的唯一标识字段名称(必须是对象中的键)
 * @returns 过滤后的数组 A,不包含在数组 B 中出现过的数据
 *
 * @typeParam T - 泛型类型,表示数组中对象的类型
 * @typeParam K - 泛型类型,表示用于比较的字段名,必须是 `T` 的键
 *
 * 示例:
 * const arrayA = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];
 * const arrayB = [{ id: 2, name: 'Bob' }];
 * const result = removeMatchingItemsById(arrayA, arrayB, 'id');
 * // result => [{ id: 1, name: 'Alice' }]
 */

function removeMatchingItemsById<T extends Record<K, string | number>, K extends keyof T>(
  dataA: T[],
  dataB: T[],
  id: K
): T[] {
  if (!Array.isArray(dataA) || !Array.isArray(dataB)) {
    throw new Error('dataA and dataB must be arrays');
  }

  const idsInB = new Set(dataB.map(item => item[id]));

  return dataA.filter(item => !idsInB.has(item[id]));
}

3. 日期处理工具类 Date

3.1 获取当前时间戳(毫秒数)

typescript 复制代码
/**
 * 如果传入一个有效的 Date 对象,则返回该对象对应的时间戳;
 * 否则返回当前系统时间的时间戳。
 *
 * @param {Date} [date] - 可选参数,一个有效的 Date 对象
 * @returns {number} 表示时间戳的数字(单位:毫秒)
 *
 * @example
 * getCurrentTimestamp(); // 返回当前时间戳,例如 1712345678901
 * getCurrentTimestamp(new Date(2023, 0, 1)); // 返回 2023 年 1 月 1 日对应的时间戳
 */
function getCurrentTimestamp(date?: Date): number {
  if (date instanceof Date && !isNaN(date.getTime())) {
    return date.getTime();
  }
  return Date.now();
}

3.2 计算两个日期之间的天数差(向上取整)

typescript 复制代码
/**
 * @param {Date} startDate - 开始日期对象
 * @param {Date} endDate - 结束日期对象
 * @returns {number} 两个日期之间的天数差(向上取整)
 *
 * @example
 * const start = new Date('2024-04-01');
 * const end = new Date('2024-04-03');
 * getDaysBetweenDates(start, end); // 返回 2
 */
function getDaysBetweenDates(startDate: Date, endDate: Date): number {
    const diffTime = Math.abs(endDate.getTime() - startDate.getTime());
    return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
}

3.3 计算两个日期之间的年数和剩余月份数,结果不进行四舍五入

typescript 复制代码
interface YearsAndMonths {
  years: number;
  months: number;
}

/**
 * @param {Date} startDate - 起始日期
 * @param {Date} endDate - 结束日期
 * @returns {YearsAndMonths} 返回包含年份差和剩余月份的对象
 * @throws {Error} 如果传入的日期无效或结束日期早于开始日期
 *
 * @example
 * const start = new Date('2020-05-15');
 * const end = new Date('2023-07-20');
 * CalculateYearsAndMonths(start, end); // 返回 { years: 3, months: 2 }
 */
const CalculateYearsAndMonths = (startDate: Date, endDate: Date) => {
  const start = new Date(startDate);
  const end = new Date(endDate);

  // 校验是否为有效日期
  if (isNaN(start.getTime()) || isNaN(end.getTime())) {
    throw new Error('Invalid date input');
  }

  // 确保结束日期不早于开始日期
  if (end < start) {
    throw new Error('End date cannot be earlier than start date');
  }

  let years = end.getFullYear() - start.getFullYear();
  let months = end.getMonth() - start.getMonth();

  // 调整年份和月份:如果月份为负,则借位一年并加12个月
  if (months < 0) {
    years--;
    months += 12;
  }

  return { years, months } as YearsAndMonths;
};

3.4 将时间格式化为指定字符串格式

typescript 复制代码
interface TimeFormatObject {
  y: number; // 年
  m: number; // 月(1-12)
  d: number; // 日
  h: number; // 时
  i: number; // 分
  s: number; // 秒
  a: number; // 星期几(0=周日,1=周一,... 6=周六)
}


/**
 * 支持传入 Date 对象、时间戳(10位或13位)或日期字符串(如 '2024-01-01')
 * 可自定义输出格式,支持年(y)、月(m)、日(d)、时(h)、分(i)、秒(s)、星期(a)等占位符
 *
 * 示例:
 *   parseTime(new Date(), '{y}-{m}-{d} {h}:{i}:{s}') => "2024-05-30 15:30:45"
 *   parseTime(1717029200, '{y}年{m}月{d}日 星期{a}') => "2024年5月30日 星期四"
 *
 * @param {Date | string | number} [time]
 *    要格式化的时间,支持 Date / 时间戳 / 日期字符串
 * @param {string} [cFormat]
 *。  格式化模板,默认为 '{y}-{m}-{d} {h}:{i}:{s}'
 *    支持字段:{y}年,{m}月,{d}日,{h}时,{i}分,{s}秒,{a}星期
 * @returns {string} 格式化后的时间字符串,若时间无效则返回空字符串
 */
 
function parseTime<K extends keyof TimeFormatObject>(time: Date | string | number, cFormat?: string): string {
  if (arguments.length === 0) {
    return ''
  }

  if (time == null || time === 'null') {
    return ''
  }

  let date: Date

  if (time instanceof Date) {
    date = time
  } else {
    if (typeof time === 'string' && /^[0-9]+$/.test(time)) {
      time = parseInt(time)
    }

    if (typeof time === 'number') {
      const floorTime = Math.floor(time)
      if (floorTime.toString().length === 10) {
        time = time * 1000
      }
    }

    date = new Date(time as number | string)

    if (isNaN(date.getTime())) {
      return ''
    }
  }

  const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
  const formatObj: TimeFormatObject = {
    y: date.getFullYear(),
    m: date.getMonth() + 1,
    d: date.getDate(),
    h: date.getHours(),
    i: date.getMinutes(),
    s: date.getSeconds(),
    a: date.getDay()
  }

  const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key: K) => {
    const value = Reflect.get(formatObj, key) ?? ''

    if (key === 'a') {
      return ['日', '一', '二', '三', '四', '五', '六'][value as number] || ''
    }

    if (result.length > 0 && typeof value === 'number' && value < 10) {
      return '0' + value
    }

    return value.toString()
  })

  return time_str
}

3.4.1 格式化时间戳为可读性更强的时间描述

  • parsedTime需要用到上面封装的时间格式化工具函数
typescript 复制代码
/**
 * @param {number} 【time】 - 时间戳(秒或毫秒)。
 * @param {string | undefined} 【option】
 * 可自定义输出格式,支持年(y)、月(m)、日(d)、时(h)、分(i)、秒(s)、星期(a)等占位符
 * @returns {string} - 格式化后的时间描述。
 *
 * 该函数首先检查时间戳的长度是否为10位(即是否是秒级时间戳),如果是,则将其转换为毫秒级时间戳。
 * 然后创建一个 Date 对象,并计算当前时间和给定时间之间的差异(以秒为单位)。
 * 根据时间差,返回不同的时间描述:
 * - 如果时间差小于30秒,返回"刚刚"。
 * - 如果时间差小于1小时,返回"X分钟前"。
 * - 如果时间差小于24小时,返回"X小时前"。
 * - 如果时间差小于2天,返回"1天前"。
 * 如果提供了格式字符串,则使用 parseTime 函数按照指定格式返回时间。
 * 否则,返回默认格式的时间字符串,包括月份、日期、小时和分钟。
 */
function formatTime(time: number, option?: string): string {
  let parsedTime = time;

  if (('' + parsedTime).length === 10) {
    parsedTime = parseInt('' + parsedTime) * 1000;
  } else {
    parsedTime = +parsedTime;
  }

  const d = new Date(parsedTime);
  const now = Date.now();

  const diff = (now - d.getTime()) / 1000;

  if (diff < 30) {
    return '刚刚';
  } else if (diff < 3600) {
    // less 1 hour
    return Math.ceil(diff / 60) + '分钟前';
  } else if (diff < 3600 * 24) {
    return Math.ceil(diff / 3600) + '小时前';
  } else if (diff < 3600 * 24 * 2) {
    return '1天前';
  }

  if (option) {
    return parseTime(parsedTime, option);
  } else {
    return (
      d.getMonth() +
        1 +
        '月' +
      d.getDate() +
        '日' +
      d.getHours() +
        '时' +
      d.getMinutes() +
        '分'
    );
  }
}

总结

以上是一些常用的工具类和方法,涵盖了字符串处理、日期格式化、数组、对象等方面。通过这些工具类的封装,可以大大提升开发效率并保持代码的一致性。在实际开发中,可以根据具体需求进一步扩展和完善这些工具类。

相关推荐
前端世界7 小时前
鸿蒙UI开发全解:JS与Java双引擎实战指南
javascript·ui·harmonyos
阿巴~阿巴~10 小时前
操作系统核心技术剖析:从Android驱动模型到鸿蒙微内核的国产化实践
android·华为·harmonyos
iMerryou10 小时前
鸿蒙的动态渐变背景实现
harmonyos
Keya14 小时前
在HarmonyOS(鸿蒙)中H5页面中的视频不会自动播放
app·harmonyos·arkts
HMS Core17 小时前
用AI重塑游戏体验:《诛仙2》携手HarmonyOS SDK实现性能与功耗双赢
人工智能·游戏·harmonyos
儿歌八万首17 小时前
HarmonyOS中各种动画的使用介绍
华为·harmonyos·arkts·arkui
dilvx17 小时前
配置鸿蒙 fastboot
华为·harmonyos
儿歌八万首17 小时前
HarmonyOS 中状态管理 V2和 V1 的区别
harmonyos·component·arkui
鸿蒙小林17 小时前
HarmonyOS应用开发者高级试题2025年7月部分单选题
harmonyos·开发者认证
zkmall17 小时前
鸿蒙商城开发:ZKmall开源商城系统特性适配与性能优化
性能优化·开源·harmonyos