-
【引言】本应用的主要功能是将用户输入的数字转换为中文的小写、大写及大写金额形式。用户可以在输入框中输入任意数字,点击“示例”按钮可以快速填充预设的数字,点击“清空”按钮则会清除当前输入。转换结果显示在下方的结果区域,每个结果旁边都有一个“复制”按钮,方便用户将结果复制到剪贴板。【环境准备】• 操作系统:Windows 10• 开发工具:DevEco Studio NEXT Beta1 Build Version: 5.0.3.806• 目标设备:华为Mate60 Pro• 开发语言:ArkTS• 框架:ArkUI• API版本:API 12• 三方库:chinese-number-format(数字转中文)、chinese-finance-number(将数字转换成财务用的中文大写数字)12ohpm install @nutpi/chinese-finance-numberohpm install @nutpi/chinese-number-format【功能实现】• 输入监听:通过 @Watch 装饰器监听输入框的变化,一旦输入发生变化,即调用 inputChanged 方法更新转换结果。• 转换逻辑:利用 @nutpi/chinese-number-format 和 @nutpi/chinese-finance-number 库提供的方法完成数字到中文的各种转换。• 复制功能:使用 pasteboard 模块将结果显示的中文文本复制到剪贴板,通过 promptAction.showToast 提示用户复制成功。【完整代码】// 导入必要的模块 import { promptAction } from '@kit.ArkUI'; // 用于显示提示信息 import { pasteboard } from '@kit.BasicServicesKit'; // 用于处理剪贴板操作 import { toChineseNumber } from '@nutpi/chinese-finance-number'; // 将数字转换为中文大写金额 import { toChineseWithUnits, // 将数字转换为带单位的中文 toUpperCase, // 将中文小写转换为大写 } from '@nutpi/chinese-number-format'; @Entry // 标记此组件为入口点 @Component // 定义一个组件 struct NumberToChineseConverter { @State private exampleNumber: number = 88.8; // 示例数字 @State private textColor: string = "#2e2e2e"; // 文本颜色 @State private lineColor: string = "#d5d5d5"; // 分割线颜色 @State private basePadding: number = 30; // 基础内边距 @State private chineseLowercase: string = ""; // 转换后的小写中文 @State private chineseUppercase: string = ""; // 转换后的中文大写 @State private chineseUppercaseAmount: string = ""; // 转换后的中文大写金额 @State @Watch('inputChanged') private inputText: string = ""; // 监听输入文本变化 // 当输入文本改变时触发的方法 inputChanged() { this.chineseLowercase = toChineseWithUnits(Number(this.inputText), 'zh-CN'); // 转换为小写中文并带上单位 this.chineseUppercase = toUpperCase(this.chineseLowercase, 'zh-CN'); // 将小写中文转换为大写 this.chineseUppercaseAmount = toChineseNumber(Number(this.inputText)); // 转换为大写金额 } // 复制文本到剪贴板的方法 private copyToClipboard(text: string): void { const pasteboardData = pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, text); // 创建剪贴板数据 const systemPasteboard = pasteboard.getSystemPasteboard(); // 获取系统剪贴板 systemPasteboard.setData(pasteboardData); // 设置剪贴板数据 promptAction.showToast({ message: '已复制' }); // 显示复制成功的提示 } // 构建用户界面的方法 build() { Column() { // 主列容器 // 页面标题 Text('数字转中文大小写') .fontColor(this.textColor) // 设置字体颜色 .fontSize(18) // 设置字体大小 .width('100%') // 设置宽度 .height(50) // 设置高度 .textAlign(TextAlign.Center) // 文本居中对齐 .backgroundColor(Color.White) // 设置背景颜色 .shadow({ // 添加阴影效果 radius: 2, // 阴影半径 color: this.lineColor, // 阴影颜色 offsetX: 0, // X轴偏移量 offsetY: 5 // Y轴偏移量 }); Scroll() { // 滚动视图 Column() { // 内部列容器 // 工具介绍部分 Column() { Text('工具介绍').fontSize(20).fontWeight(600).fontColor(this.textColor); // 设置介绍文字样式 Text('将数字转换为中文格式,适用于票据填写、合同文书、财务报表等多种场景。支持从最小单位“分”到最大单位“千兆”的数字转换。') .textAlign(TextAlign.JUSTIFY) .fontSize(18).fontColor(this.textColor).margin({ top: `${this.basePadding / 2}lpx` }); // 设置介绍详情文字样式 } .alignItems(HorizontalAlign.Start) // 对齐方式 .width('650lpx') // 设置宽度 .padding(`${this.basePadding}lpx`) // 设置内边距 .margin({ top: `${this.basePadding}lpx` }) // 设置外边距 .borderRadius(10) // 设置圆角 .backgroundColor(Color.White) // 设置背景颜色 .shadow({ // 添加阴影效果 radius: 10, // 阴影半径 color: this.lineColor, // 阴影颜色 offsetX: 0, // X轴偏移量 offsetY: 0 // Y轴偏移量 }); // 输入区 Column() { Row() { // 行容器 Text('示例') .fontColor("#5871ce") // 设置字体颜色 .fontSize(18) // 设置字体大小 .padding(`${this.basePadding / 2}lpx`) // 设置内边距 .backgroundColor("#f2f1fd") // 设置背景颜色 .borderRadius(5) // 设置圆角 .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.8 }) // 设置点击效果 .onClick(() => { // 点击事件 this.inputText = `${this.exampleNumber}`; // 设置输入框文本为示例数字 }); Blank(); // 占位符 Text('清空') .fontColor("#e48742") // 设置字体颜色 .fontSize(18) // 设置字体大小 .padding(`${this.basePadding / 2}lpx`) // 设置内边距 .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.8 }) // 设置点击效果 .backgroundColor("#ffefe6") // 设置背景颜色 .borderRadius(5) // 设置圆角 .onClick(() => { // 点击事件 this.inputText = ""; // 清空输入框 }); }.height(45) // 设置高度 .justifyContent(FlexAlign.SpaceBetween) // 子元素水平分布方式 .width('100%'); // 设置宽度 Divider().margin({ top: 5, bottom: 5 }); // 分割线 TextInput({ text: $$this.inputText, placeholder: `请输入数字,例如:${this.exampleNumber}` }) // 输入框 .width('100%') // 设置宽度 .fontSize(18) // 设置字体大小 .caretColor(this.textColor) // 设置光标颜色 .fontColor(this.textColor) // 设置字体颜色 .margin({ top: `${this.basePadding}lpx` }) // 设置外边距 .padding(0) // 设置内边距 .backgroundColor(Color.Transparent) // 设置背景颜色 .borderRadius(0) // 设置圆角 .type(InputType.NUMBER_DECIMAL); // 设置输入类型为数字 } .alignItems(HorizontalAlign.Start) // 对齐方式 .width('650lpx') // 设置宽度 .padding(`${this.basePadding}lpx`) // 设置内边距 .margin({ top: `${this.basePadding}lpx` }) // 设置外边距 .borderRadius(10) // 设置圆角 .backgroundColor(Color.White) // 设置背景颜色 .shadow({ // 添加阴影效果 radius: 10, // 阴影半径 color: this.lineColor, // 阴影颜色 offsetX: 0, // X轴偏移量 offsetY: 0 // Y轴偏移量 }); // 结果区 Column() { Row() { Text(`小写:${this.chineseLowercase}`).fontColor(this.textColor).fontSize(18).layoutWeight(1); // 显示小写结果 Text('复制') .fontColor(Color.White) // 设置字体颜色 .fontSize(18) // 设置字体大小 .padding(`${this.basePadding / 2}lpx`) // 设置内边距 .backgroundColor("#0052d9") // 设置背景颜色 .borderRadius(5) // 设置圆角 .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.8 }) // 设置点击效果 .onClick(() => { // 点击事件 this.copyToClipboard(this.chineseLowercase); // 复制小写结果到剪贴板 }); }.constraintSize({ minHeight: 45 }) // 最小高度 .justifyContent(FlexAlign.SpaceBetween) // 子元素水平分布方式 .width('100%'); // 设置宽度 Divider().margin({ top: 5, bottom: 5 }); // 分割线 Row() { Text(`大写:${this.chineseUppercase}`).fontColor(this.textColor).fontSize(18).layoutWeight(1); // 显示大写结果 Text('复制') .fontColor(Color.White) // 设置字体颜色 .fontSize(18) // 设置字体大小 .padding(`${this.basePadding / 2}lpx`) // 设置内边距 .backgroundColor("#0052d9") // 设置背景颜色 .borderRadius(5) // 设置圆角 .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.8 }) // 设置点击效果 .onClick(() => { // 点击事件 this.copyToClipboard(this.chineseUppercase); // 复制大写结果到剪贴板 }); }.constraintSize({ minHeight: 45 }) // 最小高度 .justifyContent(FlexAlign.SpaceBetween) // 子元素水平分布方式 .width('100%'); // 设置宽度 Divider().margin({ top: 5, bottom: 5 }); // 分割线 Row() { Text(`大写金额:${this.chineseUppercaseAmount}`).fontColor(this.textColor).fontSize(18).layoutWeight(1); // 显示大写金额结果 Text('复制') .fontColor(Color.White) // 设置字体颜色 .fontSize(18) // 设置字体大小 .padding(`${this.basePadding / 2}lpx`) // 设置内边距 .backgroundColor("#0052d9") // 设置背景颜色 .borderRadius(5) // 设置圆角 .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.8 }) // 设置点击效果 .onClick(() => { // 点击事件 this.copyToClipboard(this.chineseUppercaseAmount); // 复制大写金额结果到剪贴板 }); }.constraintSize({ minHeight: 45 }) // 最小高度 .justifyContent(FlexAlign.SpaceBetween) // 子元素水平分布方式 .width('100%'); // 设置宽度 } .alignItems(HorizontalAlign.Start) // 对齐方式 .width('650lpx') // 设置宽度 .padding(`${this.basePadding}lpx`) // 设置内边距 .margin({ top: `${this.basePadding}lpx` }) // 设置外边距 .borderRadius(10) // 设置圆角 .backgroundColor(Color.White) // 设置背景颜色 .shadow({ // 添加阴影效果 radius: 10, // 阴影半径 color: this.lineColor, // 阴影颜色 offsetX: 0, // X轴偏移量 offsetY: 0 // Y轴偏移量 }); } }.scrollBar(BarState.Off).clip(false); // 关闭滚动条,不允许裁剪 } .height('100%') // 设置高度 .width('100%') // 设置宽度 .backgroundColor("#f4f8fb"); // 设置页面背景颜色 } } 转载自https://www.cnblogs.com/zhongcx/p/18562512
-
【引言】本文将通过一个具体的案例——“字数统计”组件,来探讨如何在鸿蒙NEXT框架下实现这一功能。此组件不仅能够统计用户输入文本中的汉字、中文标点、数字、以及英文字符的数量,还具有良好的用户界面设计,使用户能够直观地了解输入文本的各种统计数据。【环境准备】• 操作系统:Windows 10• 开发工具:DevEco Studio NEXT Beta1 Build Version: 5.0.3.806• 目标设备:华为Mate60 Pro• 开发语言:ArkTS• 框架:ArkUI• API版本:API 12【组件概述】“字数统计”组件基于鸿蒙NEXT框架构建,旨在提供一个简洁而强大的文本统计工具。组件的主要功能包括:• 文本输入:用户可以在提供的文本区域内输入或粘贴任何文本。• 实时统计:当用户输入或修改文本时,组件会实时更新并显示文本中汉字、中文标点、数字、英文字符等的具体数量。• 示例与清空:提供了“示例”按钮,点击后会自动填充预设的文本内容;“清空”按钮则用于清空当前的输入内容。【技术实现】1. 状态管理:使用@State装饰器来管理组件的状态,如输入文本、各种字符的数量统计等。通过@Watch装饰器监听输入文本的变化,触发相应的计算逻辑。2. 文本解析:当检测到输入文本发生变化时,组件会遍历文本中的每一个字符,根据正则表达式判断字符类型,并分别统计汉字、中文标点、数字、英文字符的数量。特别地,对于汉字和中文标点,每个字符被视为两个单位进行统计。3. 用户界面:组件的UI设计遵循了鸿蒙NEXT的设计规范,使用了Column、Row、Text、TextArea等基础组件来构建布局。通过设置字体颜色、大小、背景色、边距等属性,实现了美观且易于使用的界面。此外,组件还利用了阴影效果和圆角设计来提升视觉体验。4. 交互设计:为了增强用户体验,组件中加入了“示例”和“清空”按钮,用户可以通过简单的点击操作快速测试组件的功能或清空输入框。同时,组件支持文本输入区域的实时更新,保证了用户操作的即时反馈。【完整代码】// 定义一个组件,用于数字和文本统计 @Entry @Component struct NumberToChineseConverter { // 定义一个状态变量,存储示例数字字符串 @State private exampleNumber: string = '自从盘古破鸿蒙,开辟从兹清浊辨。\nare you ok?\n1234\n+-*/'; // 定义文本颜色的状态变量 @State private textColor: string = "#2e2e2e"; // 定义阴影边框颜色的状态变量 @State private shadowColor: string = "#d5d5d5"; // 定义基础内边距的状态变量 @State private basePadding: number = 30; // 定义汉字数量的状态变量 @State private chineseCharCount: string = "0"; // 定义中文标点数量的状态变量 @State private chinesePunctuationCount: string = "0"; // 定义汉字加中文标点总数的状态变量 @State private totalChineseCount: string = "0"; // 定义英文字符数量的状态变量 @State private englishCharCount: string = "0"; // 定义数字数量的状态变量 @State private digitCount: string = "0"; // 定义总字符数的状态变量 @State private charTotalCount: string = "0"; // 定义监听输入文本变化的状态变量 @State @Watch('inputChanged') private inputText: string = ""; // 当输入文本发生变化时调用的方法 inputChanged() { // 初始化计数器 let chineseChars = 0; // 汉字数量 let chinesePunctuation = 0; // 中文标点数量 let englishChars = 0; // 英文字符数量 let digits = 0; // 数字数量 let count = 0; // 总字符数 // 遍历输入文本的每个字符 for (let i = 0; i < this.inputText.length; i++) { let char = this.inputText.charAt(i); // 获取当前字符 count++; // 计数器加一 // 如果字符是数字,则数字计数器加一 if (/\d/.test(char)) { digits++; } // 如果字符是汉字,则汉字计数器加一,同时总字符数加二 if (/[\u4e00-\u9fa5]/.test(char)) { chineseChars++; count++; // 汉字和中文标点算两个字符,所以这里多+1 } // 如果字符是中文标点,则中文标点计数器加一,同时总字符数加二 if (/[\u3001-\u3002\uff01-\uff1a]/.test(char)) { chinesePunctuation++; count++; // 汉字和中文标点算两个字符,所以这里多+1 } // 如果字符是英文字符或英文标点,则英文字符计数器加一 if (/[a-zA-Z0-9\s!-/:-@[-`{-~]/.test(char)) { englishChars++; } } // 更新状态变量 this.chineseCharCount = `${chineseChars}`; this.chinesePunctuationCount = `${chinesePunctuation}`; this.totalChineseCount = `${chineseChars + chinesePunctuation}`; this.englishCharCount = `${englishChars}`; this.digitCount = `${digits}`; this.charTotalCount = `${count}`; } // 构建UI界面的方法 build() { // 创建一个列布局容器 Column() { // 添加标题 Text('字数统计') .fontColor(this.textColor) // 设置字体颜色 .fontSize(18) // 设置字体大小 .width('100%') // 设置宽度为100% .height(50) // 设置高度为50 .textAlign(TextAlign.Center) // 设置文本居中对齐 .backgroundColor(Color.White) // 设置背景色为白色 .shadow({ // 设置阴影效果 radius: 2, // 阴影半径 color: this.shadowColor, // 阴影颜色 offsetX: 0, // X轴偏移量 offsetY: 5 // Y轴偏移量 }); // 创建可滚动的容器 Scroll() { // 在可滚动容器内部创建列布局 Column() { // 添加工具介绍 Column() { Text('工具介绍').fontSize(18).fontWeight(600).fontColor(this.textColor); Text('本工具能够快速统计输入文本中的汉字、中文标点、数字、英文字符等的数量。具体规则如下:\n•汉字和中文标点各算作两个字符。\n•数字、空格、英文字母及英文标点各算作一个字符。') .textAlign(TextAlign.JUSTIFY) // 设置文本两端对齐 .fontSize(13) // 设置字体大小 .fontColor(this.textColor) // 设置字体颜色 .margin({ top: `${this.basePadding / 2}lpx` }); // 设置上边距 } // 设置样式 .alignItems(HorizontalAlign.Start) // 设置水平对齐方式 .width('650lpx') // 设置宽度 .padding(`${this.basePadding}lpx`) // 设置内边距 .margin({ top: `${this.basePadding}lpx` }) // 设置外边距 .borderRadius(10) // 设置圆角 .backgroundColor(Color.White) // 设置背景色 .shadow({ // 设置阴影效果 radius: 10, // 阴影半径 color: this.shadowColor, // 阴影颜色 offsetX: 0, // X轴偏移量 offsetY: 0 // Y轴偏移量 }); // 添加示例和清空按钮 Column() { Row() { Text('示例') .fontColor("#5871ce") // 设置字体颜色 .fontSize(16) // 设置字体大小 .padding(`${this.basePadding / 2}lpx`) // 设置内边距 .backgroundColor("#f2f1fd") // 设置背景色 .borderRadius(5) // 设置圆角 .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.8 }) // 设置点击效果 .onClick(() => { // 设置点击事件 this.inputText = this.exampleNumber; // 将示例文本赋值给输入框 }); Blank(); // 添加空白间隔 Text('清空') .fontColor("#e48742") // 设置字体颜色 .fontSize(16) // 设置字体大小 .padding(`${this.basePadding / 2}lpx`) // 设置内边距 .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.8 }) // 设置点击效果 .backgroundColor("#ffefe6") // 设置背景色 .borderRadius(5) // 设置圆角 .onClick(() => { // 设置点击事件 this.inputText = ""; // 清空输入框 }); } .height(45) // 设置高度 .justifyContent(FlexAlign.SpaceBetween) // 设置子项之间间距均匀分布 .width('100%'); // 设置宽度 Divider(); // 添加分割线 // 添加文本输入区域 TextArea({ text: $$this.inputText, placeholder: `请输入内容` }) .width(`${650 - this.basePadding * 2}lpx`) // 设置宽度 .height(100) // 设置高度 .fontSize(16) // 设置字体大小 .caretColor(this.textColor) // 设置光标颜色 .fontColor(this.textColor) // 设置字体颜色 .margin({ top: `${this.basePadding}lpx` }) // 设置上边距 .padding(0) // 设置内边距 .backgroundColor(Color.Transparent) // 设置背景色 .borderRadius(0) // 设置圆角 .textAlign(TextAlign.JUSTIFY); // 设置文本两端对齐 } // 设置样式 .alignItems(HorizontalAlign.Start) // 设置水平对齐方式 .width('650lpx') // 设置宽度 .padding(`${this.basePadding}lpx`) // 设置内边距 .margin({ top: `${this.basePadding}lpx` }) // 设置外边距 .borderRadius(10) // 设置圆角 .backgroundColor(Color.White) // 设置背景色 .shadow({ // 设置阴影效果 radius: 10, // 阴影半径 color: this.shadowColor, // 阴影颜色 offsetX: 0, // X轴偏移量 offsetY: 0 // Y轴偏移量 }); // 添加统计结果展示 Column() { // 汉字数量 Row() { Text() { Span(`汉字:`) Span(`${this.chineseCharCount} `).fontColor(Color.Red) // 汉字数量以红色显示 Span('个') } .fontColor(this.textColor) // 设置字体颜色 .fontSize(16) // 设置字体大小 .layoutWeight(1); // 设置布局权重 } .constraintSize({ minHeight: 45 }) // 设置最小高度 .justifyContent(FlexAlign.SpaceBetween) // 设置子项之间间距均匀分布 .width('100%'); // 设置宽度 Divider(); // 添加分割线 // 中文标点数量 Row() { Text() { Span(`中文标点:`) Span(`${this.chinesePunctuationCount} `).fontColor(Color.Red) // 中文标点数量以红色显示 Span('个') } .fontColor(this.textColor) // 设置字体颜色 .fontSize(16) // 设置字体大小 .layoutWeight(1); // 设置布局权重 } .constraintSize({ minHeight: 45 }) // 设置最小高度 .justifyContent(FlexAlign.SpaceBetween) // 设置子项之间间距均匀分布 .width('100%'); // 设置宽度 Divider(); // 添加分割线 // 汉字加中文标点总数 Row() { Text() { Span(`汉字+中文标点:`) Span(`${this.totalChineseCount} `).fontColor(Color.Red) // 汉字加中文标点总数以红色显示 Span('个') } .fontColor(this.textColor) // 设置字体颜色 .fontSize(16) // 设置字体大小 .layoutWeight(1); // 设置布局权重 } .constraintSize({ minHeight: 45 }) // 设置最小高度 .justifyContent(FlexAlign.SpaceBetween) // 设置子项之间间距均匀分布 .width('100%'); // 设置宽度 Divider(); // 添加分割线 // 英文字符数量 Row() { Text() { Span(`英文:`) Span(`${this.englishCharCount} `).fontColor(Color.Red) // 英文字符数量以红色显示 Span('个') Span('(含英文状态下的数字、符号、标点)').fontSize(13) // 附加说明 } .fontColor(this.textColor) // 设置字体颜色 .fontSize(16) // 设置字体大小 .layoutWeight(1); // 设置布局权重 } .constraintSize({ minHeight: 45 }) // 设置最小高度 .justifyContent(FlexAlign.SpaceBetween) // 设置子项之间间距均匀分布 .width('100%'); // 设置宽度 Divider(); // 添加分割线 // 数字数量 Row() { Text() { Span(`数字:`) Span(`${this.digitCount} `).fontColor(Color.Red) // 数字数量以红色显示 Span('个') } .fontColor(this.textColor) // 设置字体颜色 .fontSize(16) // 设置字体大小 .layoutWeight(1); // 设置布局权重 } .constraintSize({ minHeight: 45 }) // 设置最小高度 .justifyContent(FlexAlign.SpaceBetween) // 设置子项之间间距均匀分布 .width('100%'); // 设置宽度 Divider(); // 添加分割线 // 总字符数 Row() { Text() { Span(`字符总数:`) Span(`${this.charTotalCount} `).fontColor(Color.Red) // 总字符数以红色显示 Span('个字符') } .fontColor(this.textColor) // 设置字体颜色 .fontSize(16) // 设置字体大小 .layoutWeight(1); // 设置布局权重 } .constraintSize({ minHeight: 45 }) // 设置最小高度 .justifyContent(FlexAlign.SpaceBetween) // 设置子项之间间距均匀分布 .width('100%'); // 设置宽度 } // 设置样式 .alignItems(HorizontalAlign.Start) // 设置水平对齐方式 .width('650lpx') // 设置宽度 .padding(`${this.basePadding}lpx`) // 设置内边距 .margin({ top: `${this.basePadding}lpx` }) // 设置外边距 .borderRadius(10) // 设置圆角 .backgroundColor(Color.White) // 设置背景色 .shadow({ // 设置阴影效果 radius: 10, // 阴影半径 color: this.shadowColor, // 阴影颜色 offsetX: 0, // X轴偏移量 offsetY: 0 // Y轴偏移量 }); } } .scrollBar(BarState.Off) // 关闭滚动条 .clip(false); // 不裁剪超出部分,这里是允许内部组件阴影可以向父布局外扩散 } .height('100%') // 设置高度为100% .width('100%') // 设置宽度为100% .backgroundColor("#f4f8fb"); // 设置背景色 } }转载自https://www.cnblogs.com/zhongcx/p/18563304
-
【引言】在本篇文章中,我们将探讨如何在鸿蒙NEXT平台上实现二维码的生成与识别功能。通过使用ArkUI组件库和相关的媒体库,我们将创建一个简单的应用程序,用户可以生成二维码并扫描识别。【环境准备】• 操作系统:Windows 10• 开发工具:DevEco Studio NEXT Beta1 Build Version: 5.0.3.806• 目标设备:华为Mate60 Pro• 开发语言:ArkTS• 框架:ArkUI• API版本:API 12• 权限:ohos.permission.WRITE_IMAGEVIDEO(为实现将图片保存至相册功能)【项目介绍】1. 项目结构我们首先定义一个名为QrCodeGeneratorAndScanner的组件,使用@Component装饰器进行标记。该组件包含多个状态变量和方法,用于处理二维码的生成、识别和剪贴板操作。2. 组件状态组件的状态包括:buttonOptions: 定义分段按钮的选项,用于切换生成和识别二维码的功能。inputText: 用户输入的文本,用于生成二维码。scanResult: 扫描结果文本。scanResultObject: 存储扫描结果的对象。3. 用户界面构建在build方法中,我们使用Column和Row布局来构建用户界面。主要包含以下部分:分段按钮:用户可以选择生成二维码或识别二维码。输入区域:用户可以输入文本并生成二维码。二维码显示:根据输入文本生成二维码。扫描区域:用户可以通过相机扫描二维码或从图库选择图片进行识别。4. 二维码生成二维码生成使用QRCode组件,输入文本通过this.inputText传递。用户输入后,二维码会实时更新。5. 二维码识别二维码识别功能通过scanBarcode模块实现。用户可以点击“扫一扫”按钮,启动相机进行扫描,或选择图库中的图片进行识别。识别结果将显示在界面上,并提供复制功能。6. 剪贴板操作用户可以将扫描结果复制到剪贴板,使用pasteboard模块实现。点击“复制”按钮后,扫描结果将被复制,用户会收到提示。【完整代码】填写权限使用声明字符串:src/main/resources/base/element/string.json{ "string": [ { "name": "module_desc", "value": "module description" }, { "name": "EntryAbility_desc", "value": "description" }, { "name": "EntryAbility_label", "value": "label" }, { "name": "WRITE_IMAGEVIDEO_info", "value": "保存功能需要该权限" } ] }配置权限:src/main/module.json5{ "module": { "requestPermissions": [ { "name": 'ohos.permission.WRITE_IMAGEVIDEO', "reason": "$string:WRITE_IMAGEVIDEO_info", "usedScene": { } } ], //... ...示例代码:src/main/ets/pages/Index.etsimport { componentSnapshot, // 组件快照 promptAction, // 提示操作 SegmentButton, // 分段按钮 SegmentButtonItemTuple, // 分段按钮项元组 SegmentButtonOptions // 分段按钮选项 } from '@kit.ArkUI'; // 引入 ArkUI 组件库 import { photoAccessHelper } from '@kit.MediaLibraryKit'; // 引入 MediaLibraryKit 中的照片访问助手 import { common } from '@kit.AbilityKit'; // 引入 AbilityKit 中的通用功能 import { fileIo as fs } from '@kit.CoreFileKit'; // 引入 CoreFileKit 中的文件 I/O 模块 import { image } from '@kit.ImageKit'; // 引入 ImageKit 中的图像处理模块 import { BusinessError, pasteboard } from '@kit.BasicServicesKit'; // 引入 BasicServicesKit 中的业务错误和剪贴板操作 import { hilog } from '@kit.PerformanceAnalysisKit'; // 引入 PerformanceAnalysisKit 中的性能分析模块 import { detectBarcode, scanBarcode } from '@kit.ScanKit'; // 引入 ScanKit 中的条形码识别模块 @Entry // 入口标记 @Component // 组件标记 struct QrCodeGeneratorAndScanner { // 定义二维码生成与识别组件 @State private buttonOptions: SegmentButtonOptions = SegmentButtonOptions.capsule({ // 定义分段按钮选项 buttons: [{ text: '生成二维码' }, { text: '识别二维码' }] as SegmentButtonItemTuple, // 按钮文本 multiply: false, // 不允许多选 fontColor: Color.White, // 字体颜色为白色 selectedFontColor: Color.White, // 选中字体颜色为白色 selectedBackgroundColor: Color.Orange, // 选中背景颜色为橙色 backgroundColor: "#d5d5d5", // 背景颜色 backgroundBlurStyle: BlurStyle.BACKGROUND_THICK // 背景模糊样式 }) @State private sampleText: string = 'hello world'; // 示例文本 @State private inputText: string = ""; // 输入文本 @State private scanResult: string = ""; // 扫描结果 @State @Watch('selectIndexChanged') selectIndex: number = 0 // 选择索引 @State @Watch('selectedIndexesChanged') selectedIndexes: number[] = [0]; // 选中索引数组 private qrCodeId: string = "qrCodeId" // 二维码 ID @State private scanResultObject: scanBarcode.ScanResult = ({} as scanBarcode.ScanResult) // 扫描结果对象 @State private textColor: string = "#2e2e2e"; // 文本颜色 @State private shadowColor: string = "#d5d5d5"; // 阴影颜色 @State private basePadding: number = 30; // 基础内边距 selectedIndexesChanged() { // 选中索引改变事件 console.info(`this.selectedIndexes[0]:${this.selectedIndexes[0]}`) this.selectIndex = this.selectedIndexes[0] } selectIndexChanged() { // 选择索引改变事件 console.info(`selectIndex:${this.selectIndex}`) this.selectedIndexes[0] = this.selectIndex } private copyToClipboard(text: string): void { // 复制文本到剪贴板 const pasteboardData = pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, text); // 创建剪贴板数据 const systemPasteboard = pasteboard.getSystemPasteboard(); // 获取系统剪贴板 systemPasteboard.setData(pasteboardData); // 设置数据 promptAction.showToast({ message: '已复制' }); // 弹出提示消息 } build() { // 构建界面 Column() { // 列布局 SegmentButton({ // 分段按钮 options: this.buttonOptions, // 选项 selectedIndexes: this.selectedIndexes // 选中索引 }).width('400lpx').margin({ top: 20 }) // 设置宽度和外边距 Tabs({ index: this.selectIndex }) { // 选项卡 TabContent() { // 选项卡内容 Scroll() { // 滚动视图 Column() { // 列布局 Column() { // 列布局 Row() { // 行布局 Text('示例') // 文本 .fontColor("#5871ce") // 字体颜色 .fontSize(16) // 字体大小 .padding(`${this.basePadding / 2}lpx`) // 内边距 .backgroundColor("#f2f1fd") // 背景颜色 .borderRadius(5) // 边框圆角 .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.8 }) // 点击效果 .onClick(() => { // 点击事件 this.inputText = this.sampleText; // 设置输入文本为示例文本 }); Blank(); // 空白占位 Text('清空') // 清空按钮 .fontColor("#e48742") // 字体颜色 .fontSize(16) // 字体大小 .padding(`${this.basePadding / 2}lpx`) // 内边距 .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.8 }) // 点击效果 .backgroundColor("#ffefe6") // 背景颜色 .borderRadius(5) // 边框圆角 .onClick(() => { // 点击事件 this.inputText = ""; // 清空输入文本 }); } .height(45) // 设置高度 .justifyContent(FlexAlign.SpaceBetween) // 主轴对齐方式 .width('100%'); // 设置宽度 Divider(); // 分隔线 TextArea({ text: $$this.inputText, placeholder: `请输入内容` }) // 文本输入框 .width(`${650 - this.basePadding * 2}lpx`) // 设置宽度 .height(100) // 设置高度 .fontSize(16) // 字体大小 .caretColor(this.textColor) // 光标颜色 .fontColor(this.textColor) // 字体颜色 .margin({ top: `${this.basePadding}lpx` }) // 外边距 .padding(0) // 内边距 .backgroundColor(Color.Transparent) // 背景颜色 .borderRadius(0) // 边框圆角 .textAlign(TextAlign.JUSTIFY); // 文本对齐方式 } .alignItems(HorizontalAlign.Start) // 交叉轴对齐方式 .width('650lpx') // 设置宽度 .padding(`${this.basePadding}lpx`) // 内边距 .borderRadius(10) // 边框圆角 .backgroundColor(Color.White) // 背景颜色 .shadow({ // 阴影 radius: 10, // 阴影半径 color: this.shadowColor, // 阴影颜色 offsetX: 0, // X 轴偏移 offsetY: 0 // Y 轴偏移 }); Row() { // 行布局 QRCode(this.inputText) // 二维码组件 .width('300lpx') // 设置宽度 .aspectRatio(1) // 设置宽高比 .id(this.qrCodeId) // 设置 ID SaveButton() // 保存按钮 .onClick(async (_event: ClickEvent, result: SaveButtonOnClickResult) => { // 点击事件 if (result === SaveButtonOnClickResult.SUCCESS) { // 如果保存成功 const context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; // 获取上下文 let helper = photoAccessHelper.getPhotoAccessHelper(context); // 获取照片访问助手 try { // 尝试 let uri = await helper.createAsset(photoAccessHelper.PhotoType.IMAGE, 'jpg'); // 创建图片资源 let file = await fs.open(uri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE); // 打开文件 componentSnapshot.get(this.qrCodeId).then((pixelMap) => { // 获取二维码快照 let packOpts: image.PackingOption = { format: 'image/png', quality: 100 } // 打包选项 const imagePacker: image.ImagePacker = image.createImagePacker(); // 创建图像打包器 return imagePacker.packToFile(pixelMap, file.fd, packOpts).finally(() => { // 打包并保存文件 imagePacker.release(); // 释放打包器 fs.close(file.fd); // 关闭文件 promptAction.showToast({ // 弹出提示消息 message: '图片已保存至相册', // 提示内容 duration: 2000 // 持续时间 }); }); }) } catch (error) { // 捕获错误 const err: BusinessError = error as BusinessError; // 转换为业务错误 console.error(`Failed to save photo. Code is ${err.code}, message is ${err.message}`); // 打印错误信息 } } else { // 如果保存失败 promptAction.showToast({ // 弹出提示消息 message: '设置权限失败!', // 提示内容 duration: 2000 // 持续时间 }); } }) } .visibility(this.inputText ? Visibility.Visible : Visibility.Hidden) // 根据输入文本设置可见性 .justifyContent(FlexAlign.SpaceBetween) // 主轴对齐方式 .width('650lpx') // 设置宽度 .padding(`${this.basePadding}lpx`) // 内边距 .margin({ top: `${this.basePadding}lpx` }) // 外边距 .borderRadius(10) // 边框圆角 .backgroundColor(Color.White) // 背景颜色 .shadow({ // 阴影 radius: 10, // 阴影半径 color: this.shadowColor, // 阴影颜色 offsetX: 0, // X 轴偏移 offsetY: 0 // Y 轴偏移 }); }.padding({ top: 20, bottom: 20 }) // 设置内边距 .width('100%') // 设置宽度 }.scrollBar(BarState.Off) // 禁用滚动条 .align(Alignment.Top) // 顶部对齐 .height('100%') // 设置高度 } TabContent() { // 第二个选项卡内容 Scroll() { // 滚动视图 Column() { // 列布局 Row() { // 行布局 Text('扫一扫') // 扫一扫文本 .fontSize(20) // 字体大小 .textAlign(TextAlign.Center) // 文本居中对齐 .fontColor("#5871ce") // 字体颜色 .backgroundColor("#f2f1fd") // 背景颜色 .clickEffect({ scale: 0.8, level: ClickEffectLevel.LIGHT }) // 点击效果 .borderRadius(10) // 边框圆角 .height('250lpx') // 设置高度 .layoutWeight(1) // 布局权重 .onClick(() => { // 点击事件 if (canIUse('SystemCapability.Multimedia.Scan.ScanBarcode')) { // 检查是否支持扫描 try { // 尝试 scanBarcode.startScanForResult(getContext(this), { // 开始扫描 enableMultiMode: true, // 启用多模式 enableAlbum: true // 启用相册选择 }, (error: BusinessError, result: scanBarcode.ScanResult) => { // 扫描结果回调 if (error) { // 如果发生错误 hilog.error(0x0001, '[Scan CPSample]', // 记录错误日志 `Failed to get ScanResult by callback with options. Code: ${error.code}, message: ${error.message}`); return; // 退出 } hilog.info(0x0001, '[Scan CPSample]', // 记录成功日志 `Succeeded in getting ScanResult by callback with options, result is ${JSON.stringify(result)}`); this.scanResultObject = result; // 设置扫描结果对象 this.scanResult = result.originalValue ? result.originalValue : '无法识别'; // 设置扫描结果文本 }); } catch (error) { // 捕获错误 hilog.error(0x0001, '[Scan CPSample]', // 记录错误日志 `Failed to start the scanning service. Code:${error.code}, message: ${error.message}`); } } else { // 如果不支持扫描 promptAction.showToast({ message: '当前设备不支持二维码扫描' }); // 弹出提示消息 } }); Line().width(`${this.basePadding}lpx`).aspectRatio(1); // 分隔线 Text('图库选') // 图库选择文本 .fontSize(20) // 字体大小 .textAlign(TextAlign.Center) // 文本居中对齐 .fontColor("#e48742") // 字体颜色 .backgroundColor("#ffefe6") // 背景颜色 .borderRadius(10) // 边框圆角 .clickEffect({ scale: 0.8, level: ClickEffectLevel.LIGHT }) // 点击效果 .height('250lpx') // 设置高度 .layoutWeight(1) // 布局权重 .onClick(() => { // 点击事件 if (canIUse('SystemCapability.Multimedia.Scan.ScanBarcode')) { // 检查是否支持扫描 let photoOption = new photoAccessHelper.PhotoSelectOptions(); // 创建照片选择选项 photoOption.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE; // 设置 MIME 类型 photoOption.maxSelectNumber = 1; // 设置最大选择数量 let photoPicker = new photoAccessHelper.PhotoViewPicker(); // 创建照片选择器 photoPicker.select(photoOption).then((result) => { // 选择照片 let inputImage: detectBarcode.InputImage = { uri: result.photoUris[0] }; // 获取选中的图片 URI try { // 尝试 detectBarcode.decode(inputImage, // 解码条形码 (error: BusinessError, result: Array<scanBarcode.ScanResult>) => { // 解码结果回调 if (error && error.code) { // 如果发生错误 hilog.error(0x0001, '[Scan Sample]', // 记录错误日志 `Failed to get ScanResult by callback. Code: ${error.code}, message: ${error.message}`); return; // 退出 } hilog.info(0x0001, '[Scan Sample]', // 记录成功日志 `Succeeded in getting ScanResult by callback, result is ${JSON.stringify(result, null, '\u00A0\u00A0')}`); if (result.length > 0) { // 如果有结果 this.scanResultObject = result[0]; // 设置扫描结果对象 this.scanResult = result[0].originalValue ? result[0].originalValue : '无法识别'; // 设置扫描结果文本 } else { // 如果没有结果 this.scanResult = '不存在二维码'; // 设置结果文本 } }); } catch (error) { // 捕获错误 hilog.error(0x0001, '[Scan Sample]', // 记录错误日志 `Failed to detect Barcode. Code: ${error.code}, message: ${error.message}`); } }); } else { // 如果不支持扫描 promptAction.showToast({ message: '当前设备不支持二维码扫描' }); // 弹出提示消息 } }); } .justifyContent(FlexAlign.SpaceEvenly) // 主轴对齐方式 .width('650lpx') // 设置宽度 .padding(`${this.basePadding}lpx`) // 内边距 .borderRadius(10) // 边框圆角 .backgroundColor(Color.White) // 背景颜色 .shadow({ // 阴影 radius: 10, // 阴影半径 color: this.shadowColor, // 阴影颜色 offsetX: 0, // X 轴偏移 offsetY: 0 // Y 轴偏移 }); Column() { // 列布局 Row() { // 行布局 Text(`解析结果:\n${this.scanResult}`) // 显示解析结果文本 .fontColor(this.textColor) // 设置字体颜色 .fontSize(18) // 设置字体大小 .layoutWeight(1) // 设置布局权重 Text('复制') // 复制按钮文本 .fontColor(Color.White) // 设置字体颜色为白色 .fontSize(16) // 设置字体大小 .padding(`${this.basePadding / 2}lpx`) // 设置内边距 .backgroundColor("#0052d9") // 设置背景颜色 .borderRadius(5) // 设置边框圆角 .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.8 }) // 设置点击效果 .onClick(() => { // 点击事件 this.copyToClipboard(this.scanResult); // 复制扫描结果到剪贴板 }); }.constraintSize({ minHeight: 45 }) // 设置最小高度约束 .justifyContent(FlexAlign.SpaceBetween) // 设置主轴对齐方式 .width('100%'); // 设置宽度为100% } .visibility(this.scanResult ? Visibility.Visible : Visibility.Hidden) // 根据扫描结果设置可见性 .alignItems(HorizontalAlign.Start) // 设置交叉轴对齐方式 .width('650lpx') // 设置宽度 .padding(`${this.basePadding}lpx`) // 设置内边距 .margin({ top: `${this.basePadding}lpx` }) // 设置外边距 .borderRadius(10) // 设置边框圆角 .backgroundColor(Color.White) // 设置背景颜色为白色 .shadow({ // 设置阴影 radius: 10, // 设置阴影半径 color: this.shadowColor, // 设置阴影颜色 offsetX: 0, // 设置X轴偏移 offsetY: 0 // 设置Y轴偏移 }); Column() { // 列布局 Row() { // 行布局 Text(`完整结果:`).fontColor(this.textColor).fontSize(18).layoutWeight(1) // 显示完整结果文本 }.constraintSize({ minHeight: 45 }) // 设置最小高度约束 .justifyContent(FlexAlign.SpaceBetween) // 设置主轴对齐方式 .width('100%'); // 设置宽度为100% Divider().margin({ top: 2, bottom: 15 }); // 添加分隔线并设置外边距 Row() { // 行布局 Text(`${JSON.stringify(this.scanResultObject, null, '\u00A0\u00A0')}`) // 显示完整扫描结果 .fontColor(this.textColor) // 设置字体颜色 .fontSize(18) // 设置字体大小 .layoutWeight(1) // 设置布局权重 .copyOption(CopyOptions.LocalDevice); // 设置复制选项 }.constraintSize({ minHeight: 45 }) // 设置最小高度约束 .justifyContent(FlexAlign.SpaceBetween) // 设置主轴对齐方式 .width('100%'); // 设置宽度为100% } .visibility(this.scanResult ? Visibility.Visible : Visibility.Hidden) // 根据扫描结果设置可见性 .alignItems(HorizontalAlign.Start) // 设置交叉轴对齐方式 .width('650lpx') // 设置宽度 .padding(`${this.basePadding}lpx`) // 设置内边距 .margin({ top: `${this.basePadding}lpx` }) // 设置外边距 .borderRadius(10) // 设置边框圆角 .backgroundColor(Color.White) // 设置背景颜色为白色 .shadow({ // 设置阴影 radius: 10, // 设置阴影半径 color: this.shadowColor, // 设置阴影颜色 offsetX: 0, // 设置X轴偏移 offsetY: 0 // 设置Y轴偏移 }); } .padding({ top: 20, bottom: 20 }) // 设置内边距 .width('100%') // 设置宽度为100% }.scrollBar(BarState.Off) // 禁用滚动条 .align(Alignment.Top) // 顶部对齐 .height('100%') // 设置高度为100% } } .barHeight(0) // 设置选项卡条高度为0 .tabIndex(this.selectIndex) // 设置当前选中的索引 .width('100%') // 设置宽度为100% .layoutWeight(1) // 设置布局权重 .onChange((index: number) => { // 选项卡变化事件 this.selectIndex = index; // 更新选择的索引 }); } .height('100%') // 设置高度为100% .width('100%') // 设置宽度为100% .backgroundColor("#f4f8fb"); // 设置背景颜色 } }转载自https://www.cnblogs.com/zhongcx/p/18566067
-
【引言】在快节奏的现代生活中,人们往往因为忙碌而忽略了与亲戚间的互动,特别是在春节期间,面对众多的长辈和晚辈时,很多人会感到困惑,不知道该如何正确地称呼每一位亲戚。针对这一问题,我们开发了一款基于鸿蒙NEXT平台的“亲戚关系计算器”应用,旨在帮助用户快速、准确地识别和称呼他们的亲戚。【环境准备】• 操作系统:Windows 10• 开发工具:DevEco Studio NEXT Beta1 Build Version: 5.0.3.806• 目标设备:华为Mate60 Pro• 开发语言:ArkTS• 框架:ArkUI• API版本:API 12• 三方库:@nutpi/relationship(核心算法)【应用背景】中国社会有着深厚的家庭观念,亲属关系复杂多样。从血缘到姻亲,从直系到旁系,每一种关系都有其独特的称呼方式。然而,随着社会的发展,家庭成员之间的联系逐渐变得疏远,尤其是对于年轻人来说,准确地称呼每一位亲戚成了一项挑战。为了应对这一挑战,“亲戚关系计算器”应运而生。【核心功能】1. 关系输入:用户可以通过界面输入或选择具体的亲戚关系描述,例如“爸爸的哥哥的儿子”。2. 性别及称呼选择:考虑到不同地区的习俗差异,应用允许用户选择自己的性别和希望使用的称呼方式,比如“哥哥”、“姐夫”等。3. 关系计算:利用@nutpi/relationship库,根据用户提供的信息,精确计算出正确的亲戚称呼。4. 示例与清空:提供示例按钮供用户测试应用功能,同时也设有清空按钮方便用户重新开始。5. 个性化设置:支持多种方言和地方习惯的称呼方式,让应用更加贴近用户的实际需求。【用户界面】应用的用户界面简洁明了,主要由以下几个部分组成:• 选择性别:通过分段按钮让用户选择自己的性别。• 选择称呼方式:另一个分段按钮让用户选择希望的称呼方式。• 输入关系描述:提供一个文本输入框,用户可以在此处输入具体的关系描述。• 结果显示区:在用户提交信息后,这里会显示出正确的亲戚称呼。• 操作按钮:包括示例按钮、清空按钮等,方便用户操作。【完整代码】导包1ohpm install @nutpi/relationship代码// 导入关系计算模块 import relationship from "@nutpi/relationship" // 导入分段按钮组件及配置类型 import { SegmentButton, SegmentButtonItemTuple, SegmentButtonOptions } from '@kit.ArkUI'; // 使用 @Entry 和 @Component 装饰器标记这是一个应用入口组件 @Entry @Component // 定义一个名为 RelationshipCalculator 的结构体,作为组件主体 struct RelationshipCalculator { // 用户输入的关系描述,默认值为“爸爸的堂弟” @State private userInputRelation: string = "爸爸的堂弟"; // 应用的主题颜色,设置为橙色 @State private themeColor: string | Color = Color.Orange; // 文字颜色 @State private textColor: string = "#2e2e2e"; // 边框颜色 @State private lineColor: string = "#d5d5d5"; // 基础内边距大小 @State private paddingBase: number = 30; // 性别选项数组 @State private genderOptions: object[] = [Object({ text: '男' }), Object({ text: '女' })]; // 称呼方式选项数组 @State private callMethodOptions: object[] = [Object({ text: '我叫ta' }), Object({ text: 'ta叫我' })]; // 性别选择按钮的配置 @State private genderButtonOptions: SegmentButtonOptions | undefined = undefined; // 称呼方式选择按钮的配置 @State private callMethodButtonOptions: SegmentButtonOptions | undefined = undefined; // 当前选中的性别索引 @State @Watch('updateSelections') selectedGenderIndex: number[] = [0]; // 当前选中的称呼方式索引 @State @Watch('updateSelections') selectedCallMethodIndex: number[] = [0]; // 用户输入的关系描述 @State @Watch('updateSelections') userInput: string = ""; // 计算结果显示 @State calculationResult: string = ""; // 输入框是否获得焦点 @State isInputFocused: boolean = false; // 当选择发生改变时,更新关系计算 updateSelections() { // 根据索引获取选中的性别(0为男,1为女) const gender = this.selectedGenderIndex[0] === 0 ? 1 : 0; // 判断是否需要反转称呼方向 const reverse = this.selectedCallMethodIndex[0] === 0 ? false : true; // 调用关系计算模块进行计算 const result: string[] = relationship({ text: this.userInput, reverse: reverse, sex: gender }) as string[]; // 如果有计算结果,则更新显示;否则显示默认提示 if (result && result.length > 0) { this.calculationResult = `${reverse ? '对方称呼我' : '我称呼对方'}:${result[0]}`; } else { this.calculationResult = this.userInput ? '当前信息未查到关系' : ''; } } // 组件即将显示时,初始化性别和称呼方式选择按钮的配置 aboutToAppear(): void { this.genderButtonOptions = SegmentButtonOptions.capsule({ buttons: this.genderOptions as SegmentButtonItemTuple, multiply: false, fontColor: Color.White, selectedFontColor: Color.White, selectedBackgroundColor: this.themeColor, backgroundColor: this.lineColor, backgroundBlurStyle: BlurStyle.BACKGROUND_THICK }); this.callMethodButtonOptions = SegmentButtonOptions.capsule({ buttons: this.callMethodOptions as SegmentButtonItemTuple, multiply: false, fontColor: Color.White, selectedFontColor: Color.White, selectedBackgroundColor: this.themeColor, backgroundColor: this.lineColor, backgroundBlurStyle: BlurStyle.BACKGROUND_THICK }); } // 构建组件界面 build() { // 创建主列布局 Column() { // 标题栏 Text('亲戚关系计算器') .fontColor(this.textColor) .fontSize(18) .width('100%') .height(50) .textAlign(TextAlign.Center) .backgroundColor(Color.White) .shadow({ radius: 2, color: this.lineColor, offsetX: 0, offsetY: 5 }); // 内部列布局 Column() { // 性别选择行 Row() { Text('我的性别').fontColor(this.textColor).fontSize(18); // 性别选择按钮 SegmentButton({ options: this.genderButtonOptions, selectedIndexes: this.selectedGenderIndex }).width('400lpx'); }.height(45).justifyContent(FlexAlign.SpaceBetween).width('100%'); // 称呼方式选择行 Row() { Text('称呼方式').fontColor(this.textColor).fontSize(18); // 称呼方式选择按钮 SegmentButton({ options: this.callMethodButtonOptions, selectedIndexes: this.selectedCallMethodIndex }).width('400lpx'); }.height(45).justifyContent(FlexAlign.SpaceBetween).width('100%'); // 示例与清空按钮行 Row() { // 示例按钮 Text('示例') .fontColor("#5871ce") .fontSize(18) .padding(`${this.paddingBase / 2}lpx`) .backgroundColor("#f2f1fd") .borderRadius(5) .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.8 }) .onClick(() => { this.userInput = this.userInputRelation; }); // 空白间隔 Blank(); // 清空按钮 Text('清空') .fontColor("#e48742") .fontSize(18) .padding(`${this.paddingBase / 2}lpx`) .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.8 }) .backgroundColor("#ffefe6") .borderRadius(5) .onClick(() => { this.userInput = ""; }); }.height(45) .justifyContent(FlexAlign.SpaceBetween) .width('100%'); // 用户输入框 TextInput({ text: $$this.userInput, placeholder: !this.isInputFocused ? `请输入称呼。如:${this.userInputRelation}` : '' }) .placeholderColor(this.isInputFocused ? this.themeColor : Color.Gray) .fontColor(this.isInputFocused ? this.themeColor : this.textColor) .borderColor(this.isInputFocused ? this.themeColor : Color.Gray) .caretColor(this.themeColor) .borderWidth(1) .borderRadius(10) .onBlur(() => this.isInputFocused = false) .onFocus(() => this.isInputFocused = true) .height(45) .width('100%') .margin({ top: `${this.paddingBase / 2}lpx` }); } .alignItems(HorizontalAlign.Start) .width('650lpx') .padding(`${this.paddingBase}lpx`) .margin({ top: `${this.paddingBase}lpx` }) .borderRadius(10) .backgroundColor(Color.White) .shadow({ radius: 10, color: this.lineColor, offsetX: 0, offsetY: 0 }); // 结果显示区 Column() { Row() { // 显示计算结果 Text(this.calculationResult).fontColor(this.textColor).fontSize(18); }.justifyContent(FlexAlign.SpaceBetween).width('100%'); } .visibility(this.calculationResult ? Visibility.Visible : Visibility.None) .alignItems(HorizontalAlign.Start) .width('650lpx') .padding(`${this.paddingBase}lpx`) .margin({ top: `${this.paddingBase}lpx` }) .borderRadius(10) .backgroundColor(Color.White) .shadow({ radius: 10, color: this.lineColor, offsetX: 0, offsetY: 0 }); } .height('100%') .width('100%') .backgroundColor("#f4f8fb"); } } 转载自https://www.cnblogs.com/zhongcx/p/18568197
-
【引言】在鸿蒙NEXT开发中,文字转拼音是一个常见的需求,本文将介绍如何利用鸿蒙系统和pinyin-pro库实现文字转拼音的功能。【环境准备】• 操作系统:Windows 10• 开发工具:DevEco Studio NEXT Beta1 Build Version: 5.0.3.806• 目标设备:华为Mate60 Pro• 开发语言:ArkTS• 框架:ArkUI• API版本:API 12• 三方库:pinyin-pro@3.18.3(核心算法)【开始步骤】首先,我们引入pinyin-pro库中的pinyin函数,用于将中文转换为拼音。然后定义一个PinyinBean类来存储字符和其对应的拼音,以便后续展示转换结果。接着,我们使用装饰器定义一个PinyinConverter组件,该组件实现了文字转拼音的功能。通过用户输入文本,调用convertToPinyin方法将文本转换成拼音数组,并将拼音和字符对应存储在conversionResult数组中。在UI方面,我们通过鸿蒙系统提供的布局组件和样式设置,构建了一个用户友好的界面。用户可以输入文本,点击示例按钮填充默认文本,点击清空按钮清空输入内容。转换结果会以拼音和字符的形式展示在界面上。整个开发案例涵盖了鸿蒙NEXT开发中的组件定义、状态管理、事件处理、UI构建等方面,展示了如何利用鸿蒙系统和第三方库实现文字转拼音的功能。【完整代码】导包1ohpm install pinyin-pro@3.18.3代码// 引入pinyin-pro库中的pinyin函数,用于将中文转换为拼音 import { pinyin } from "pinyin-pro"; // 定义一个类来存储字符和其对应的拼音 class PinyinBean { pinyin: string; // 拼音 character: string; // 对应的汉字 // 构造器,初始化拼音和字符 constructor(pinyin: string, character: string) { this.pinyin = pinyin; this.character = character; } } // 使用装饰器定义一个组件,该组件用于实现文字转拼音功能 @Entry @Component struct PinyinConverter { // 默认的用户输入内容 @State private defaultInput: string = "混沌未分天地乱,茫茫渺渺无人见。自从盘古破鸿蒙,开辟从兹清浊辨。"; // 组件的主题颜色 @State private themeColor: string | Color = Color.Orange; // 组件的文字颜色 @State private fontColor: string = "#2e2e2e"; // 组件的边框颜色 @State private lineColor: string = "#d5d5d5"; // 基础内边距值 @State private basePadding: number = 30; // 用户输入的内容,当这个状态改变时会触发convertToPinyin方法 @State @Watch('convertToPinyin') userInput: string = ""; // 转换结果显示,存储了转换后的拼音和对应字符 @State conversionResult: PinyinBean[] = []; // 输入框是否获得了焦点 @State isInputFocused: boolean = false; // 方法:将用户输入的文本转换成拼音 convertToPinyin() { // 使用pinyin-pro库将输入的文本转换成拼音数组 const pinyinArray: string[] = pinyin(this.userInput, { type: "array" }); // 将输入的文本分割成单个字符的数组 const charArray: string[] = this.userInput.split(""); // 清空转换结果数组 this.conversionResult.length = 0; // 遍历拼音数组,创建PinyinBean对象,并将其添加到转换结果数组中 for (let i = 0; i < pinyinArray.length; i++) { this.conversionResult.push(new PinyinBean(pinyinArray[i], charArray[i])); } } // 构建UI的方法 build() { // 创建一个垂直布局的容器 Column() { // 添加标题栏 Text('文字转拼音') .fontColor(this.fontColor) // 设置字体颜色 .fontSize(18) // 设置字体大小 .width('100%') // 设置宽度为100% .height(50) // 设置高度为50 .textAlign(TextAlign.Center) // 文本居中对齐 .backgroundColor(Color.White) // 设置背景色为白色 .shadow({ // 添加阴影效果 radius: 2, // 阴影圆角 color: this.lineColor, // 阴影颜色 offsetX: 0, // X轴偏移量 offsetY: 5 // Y轴偏移量 }); // 内部垂直布局 Column() { // 示例与清空按钮行 Row() { // 示例按钮 Text('示例') .fontColor("#5871ce") // 设置字体颜色 .fontSize(18) // 设置字体大小 .padding(`${this.basePadding / 2}lpx`) // 设置内边距 .backgroundColor("#f2f1fd") // 设置背景色 .borderRadius(5) // 设置圆角 .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.8 }) // 设置点击效果 .onClick(() => { // 点击事件处理 this.userInput = this.defaultInput; // 将默认输入设置为用户输入 }); // 空白间隔 Blank(); // 清空按钮 Text('清空') .fontColor("#e48742") // 设置字体颜色 .fontSize(18) // 设置字体大小 .padding(`${this.basePadding / 2}lpx`) // 设置内边距 .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.8 }) // 设置点击效果 .backgroundColor("#ffefe6") // 设置背景色 .borderRadius(5) // 设置圆角 .onClick(() => { // 点击事件处理 this.userInput = ""; // 清空用户输入 }); }.height(45) // 设置高度 .justifyContent(FlexAlign.SpaceBetween) // 子元素之间等间距分布 .width('100%'); // 设置宽度为100% // 用户输入框 Row() { TextArea({ text: $$this.userInput, // 绑定用户输入 placeholder: !this.isInputFocused ? `请输入内容。如:${this.defaultInput}` : '' // 设置占位符 }) .backgroundColor(Color.Transparent) // 设置背景色为透明 .padding(0) // 设置内边距 .height('100%') // 设置高度为100% .placeholderColor(this.isInputFocused ? this.themeColor : Color.Gray) // 设置占位符颜色 .fontColor(this.isInputFocused ? this.themeColor : this.fontColor) // 设置字体颜色 .caretColor(this.themeColor) // 设置光标颜色 .borderRadius(0) // 设置圆角 .onBlur(() => this.isInputFocused = false) // 当失去焦点时更新状态 .onFocus(() => this.isInputFocused = true) // 当获得焦点时更新状态 .width('100%'); // 设置宽度为100% } .padding(`${this.basePadding / 2}lpx`) // 设置内边距 .backgroundColor("#f2f1fd") // 设置背景色 .width('100%') // 设置宽度为100% .height(120) // 设置高度 .borderWidth(1) // 设置边框宽度 .borderRadius(10) // 设置圆角 .borderColor(this.isInputFocused ? this.themeColor : Color.Gray) // 设置边框颜色 .margin({ top: `${this.basePadding / 2}lpx` }); // 设置上边距 } .alignItems(HorizontalAlign.Start) // 设置子元素水平对齐方式 .width('650lpx') // 设置宽度 .padding(`${this.basePadding}lpx`) // 设置内边距 .margin({ top: `${this.basePadding}lpx` }) // 设置上边距 .borderRadius(10) // 设置圆角 .backgroundColor(Color.White) // 设置背景色 .shadow({ // 设置阴影 radius: 10, // 阴影圆角 color: this.lineColor, // 阴影颜色 offsetX: 0, // X轴偏移量 offsetY: 0 // Y轴偏移量 }); // 结果显示区域 Column() { Row() { Flex({ wrap: FlexWrap.Wrap }) { // 允许子元素换行 ForEach(this.conversionResult, (item: PinyinBean, index: number) => { // 遍历转换结果 Column() { // 显示计算结果(拼音) Text(`${item.pinyin}`).fontColor(this.fontColor).fontSize(18); // 显示计算结果(字符) Text(`${item.character}`).fontColor(this.fontColor).fontSize(18); }.padding(3); // 设置内边距 }) } }.justifyContent(FlexAlign.SpaceBetween) // 子元素之间等间距分布 .width('100%'); // 设置宽度为100% } .visibility(this.conversionResult.length != 0 ? Visibility.Visible : Visibility.None) // 根据是否有转换结果决定是否显示 .alignItems(HorizontalAlign.Start) // 设置子元素水平对齐方式 .width('650lpx') // 设置宽度 .padding(`${this.basePadding}lpx`) // 设置内边距 .margin({ top: `${this.basePadding}lpx` }) // 设置上边距 .borderRadius(10) // 设置圆角 .backgroundColor(Color.White) // 设置背景色 .shadow({ // 设置阴影 radius: 10, // 阴影圆角 color: this.lineColor, // 阴影颜色 offsetX: 0, // X轴偏移量 offsetY: 0 // Y轴偏移量 }); } .height('100%') // 设置高度为100% .width('100%') // 设置宽度为100% .backgroundColor("#f4f8fb"); // 设置背景色 } } 转载自https://www.cnblogs.com/zhongcx/p/18568438
-
【引言】温度是日常生活中常见的物理量,但不同国家和地区可能使用不同的温度单位,如摄氏度(Celsius)、华氏度(Fahrenheit)、开尔文(Kelvin)、兰氏度(Rankine)和列氏度(Reaumur)。为了方便用户在这些温度单位之间进行快速准确的转换,我们开发了一款温度转换工具。该工具支持五种温度单位之间的相互转换,旨在为用户提供便捷的服务。【环境准备】• 操作系统:Windows 10• 开发工具:DevEco Studio NEXT Beta1 Build Version: 5.0.3.806• 目标设备:华为Mate60 Pro• 开发语言:ArkTS• 框架:ArkUI• API版本:API 12• 三方库:@nutpi/temperature-converter(核心算法)【项目结构】项目的核心组件是 TemperatureConverterApp,它负责构建整个应用的用户界面,并处理用户输入及温度单位之间的转换逻辑。1. 温度单位类定义我们定义了一个温度单位类 TemperatureUnit,用于封装温度单位的基本信息及其操作方法。每个温度单位都有一个标题、当前温度值和输入框的焦点状态。通过 setValue 方法,可以设置温度值并保留三位小数。2. 温度单位类型枚举为了更好地管理和使用温度单位,我们定义了一个温度单位类型对象 TemperatureUnitType,列出了五种温度单位的名称。3. 应用程序主组件TemperatureConverterApp 组件是整个应用的入口,它定义了应用的样式属性,并实现了UI的构建逻辑。组件中包含了多个状态变量,用于设置应用的颜色、字体大小等样式。在UI构建逻辑中,我们使用了鸿蒙NEXT提供的布局组件,如 Column 和 Row,来组织页面的布局。页面顶部有一个标题 "温度转换",下方是一个垂直布局的容器,动态生成每个温度单位的输入框。每个输入框都绑定了 onChange 事件,当用户输入或更改温度值时,会触发相应的转换逻辑,更新其他温度单位的值。4. 温度转换逻辑温度转换逻辑通过调用 @nutpi/temperature-converter 库中的方法实现。当用户在某个温度单位的输入框中输入温度后,程序会根据当前输入的温度单位,调用相应的转换方法,计算出其他温度单位对应的值,并更新界面上的显示。例如,如果用户在摄氏度输入框中输入温度,程序会自动计算出华氏度、开尔文、兰氏度和列氏度的值,并更新相应的输入框。【用户体验】为了提升用户体验,我们在输入框上添加了焦点状态的处理。当输入框获得焦点时,背景颜色和边框颜色会发生变化,以提示用户当前的操作位置。此外,输入框还支持输入过滤,只允许输入数字和小数点,确保输入的有效性。【完整代码】导包1ohpm install @nutpi/temperature-converter代码// 引入温度转换器模块 import { TemperatureConverter } from "@nutpi/temperature-converter" // 定义温度单位类型对象,用于存储各温度单位的名称 const TemperatureUnitType: object = Object({ Celsius: '摄氏度', // 摄氏度 Fahrenheit: '华氏度', // 华氏度 Kelvin: '开尔文', // 开尔文 Rankine: '兰氏度', // 兰氏度 Reaumur: '列氏度' // 列氏度 }) // 使用装饰器定义一个温度单位类 @ObservedV2 class TemperatureUnit { title: string // 温度单位的标题 @Trace value: string = "" // 当前温度值,使用@Trace装饰器追踪变化 @Trace isInputFocused: boolean = false // 输入框是否获得焦点,同样使用@Trace追踪变化 // 构造函数,初始化时传入温度单位的标题 constructor(title: string) { this.title = title } // 设置温度值的方法,保留三位小数 setValue(value: number) { this.value = `${parseFloat(value.toFixed(3))}` // 将数值转换成字符串,保留三位小数 console.info(`温度值:${this.value}`) // 打印当前温度值到控制台 } } // 定义温度转换器应用程序的入口组件 @Entry @Component struct TemperatureConverterApp { // 定义一系列的状态变量,用于设置应用的颜色、字体大小等样式 @State private primaryColor: string = "#080808" // 主色调 @State private secondaryColor: string = "#f7f7f7" // 次要色调 @State private bgColor: string = "#f4f8fb" // 背景颜色 @State private placeholderColor: string = "#2f9b6c" // 占位符颜色 @State private textColor: string = "#a3a3a3" // 文本颜色 @State private fontSizeSmall: number = 16 // 较小的字体大小 @State private fontSizeLarge: number = 18 // 较大的字体大小 @State private basePadding: number = 30 // 基础内边距 // 初始化温度单位数组,创建每个温度单位的实例 @State private temperatureUnits: TemperatureUnit[] = Object.keys(TemperatureUnitType).map(unit => new TemperatureUnit(TemperatureUnitType[unit])) // 构建应用程序的UI build() { Column() { // 创建一个垂直布局容器 // 添加标题 Text('温度转换') .fontColor(this.primaryColor) // 设置字体颜色 .fontSize(this.fontSizeSmall) // 设置字体大小 .width('100%') // 设置宽度 .height(50) // 设置高度 .textAlign(TextAlign.Center) // 设置文本对齐方式 .backgroundColor(Color.White) // 设置背景颜色 .shadow({ // 添加阴影效果 radius: 2, // 阴影半径 color: this.secondaryColor, // 阴影颜色 offsetX: 0, // X轴偏移量 offsetY: 5 // Y轴偏移量 }); // 循环遍历温度单位数组,动态生成每个温度单位的输入框 Column() { ForEach(this.temperatureUnits, (unit: TemperatureUnit, index: number) => { Row() { // 创建一个水平布局容器 // 显示温度单位的标题 Text(`${unit.title}`).fontSize(this.fontSizeSmall).fontColor(this.primaryColor) // 创建输入框 Row() { TextInput({ text: unit.value, // 输入框的初始值 placeholder: !unit.isInputFocused ? `请输入${unit.title}` : '' // 输入框的占位符文本 }) .inputFilter('[0-9.-]', (e) => console.log(JSON.stringify(e))) // 过滤输入内容,只允许数字和小数点 .fontSize(this.fontSizeSmall) // 设置字体大小 .backgroundColor(Color.Transparent) // 设置背景颜色 .padding(0) // 设置内边距 .width('100%') // 设置宽度 .height('100%') // 设置高度 .placeholderColor(unit.isInputFocused ? this.placeholderColor : this.textColor) // 设置占位符颜色 .fontColor(unit.isInputFocused ? this.placeholderColor : this.primaryColor) // 设置字体颜色 .caretColor(this.placeholderColor) // 设置光标颜色 .borderRadius(0) // 设置圆角 .onBlur(() => unit.isInputFocused = false) // 失去焦点时的处理 .onFocus(() => unit.isInputFocused = true) // 获得焦点时的处理 .onChange((value: string) => { // 输入内容改变时的处理 if (!unit.isInputFocused) { // 如果输入框未获得焦点,则不处理数据 console.info(`当前位置${index}没有焦点,不处理数据内容`) return } if (unit.value == value) { // 如果新旧值相同,则不处理 console.info(`当前位置${index}内容与修改内容相同,不需要继续处理`) return } console.info(`onChange, unit.value:${unit.value}, value:${value}`) // 打印变更信息 const tempValue = Number(value); // 将输入的字符串转换成数字 unit.setValue(tempValue) // 更新当前温度单位的值 // 根据用户输入的温度单位,计算并更新其他温度单位的值 switch (index) { case 0: this.temperatureUnits[1].setValue(TemperatureConverter.celsiusToFahrenheit(tempValue)) this.temperatureUnits[2].setValue(TemperatureConverter.celsiusToKelvin(tempValue)) this.temperatureUnits[3].setValue(TemperatureConverter.celsiusToRankine(tempValue)) this.temperatureUnits[4].setValue(TemperatureConverter.celsiusToReaumur(tempValue)) break; case 1: this.temperatureUnits[0].setValue(TemperatureConverter.fahrenheitToCelsius(tempValue)) this.temperatureUnits[2].setValue(TemperatureConverter.fahrenheitToKelvin(tempValue)) this.temperatureUnits[3].setValue(TemperatureConverter.fahrenheitToRankine(tempValue)) this.temperatureUnits[4].setValue(TemperatureConverter.fahrenheitToReaumur(tempValue)) break; case 2: this.temperatureUnits[0].setValue(TemperatureConverter.kelvinToCelsius(tempValue)) this.temperatureUnits[1].setValue(TemperatureConverter.kelvinToFahrenheit(tempValue)) this.temperatureUnits[3].setValue(TemperatureConverter.kelvinToRankine(tempValue)) this.temperatureUnits[4].setValue(TemperatureConverter.kelvinToReaumur(tempValue)) break; case 3: this.temperatureUnits[0].setValue(TemperatureConverter.rankineToCelsius(tempValue)) this.temperatureUnits[1].setValue(TemperatureConverter.rankineToFahrenheit(tempValue)) this.temperatureUnits[2].setValue(TemperatureConverter.rankineToKelvin(tempValue)) this.temperatureUnits[4].setValue(TemperatureConverter.rankineToReaumur(tempValue)) break; case 4: this.temperatureUnits[0].setValue(TemperatureConverter.reaumurToCelsius(tempValue)) this.temperatureUnits[1].setValue(TemperatureConverter.reaumurToFahrenheit(tempValue)) this.temperatureUnits[2].setValue(TemperatureConverter.reaumurToKelvin(tempValue)) this.temperatureUnits[3].setValue(TemperatureConverter.reaumurToRankine(tempValue)) break; } }); } .padding(`${this.basePadding / 2}lpx`) // 设置内边距 .backgroundColor(unit.isInputFocused ? this.bgColor : Color.Transparent) // 设置背景颜色 .layoutWeight(1) // 设置布局权重 .height(40) // 设置高度 .borderWidth(1) // 设置边框宽度 .borderRadius(10) // 设置圆角 .borderColor(unit.isInputFocused ? this.placeholderColor : this.secondaryColor) // 设置边框颜色 .margin({ left: `${this.basePadding / 2}lpx`, right: `${this.basePadding / 2}lpx` }); // 设置外边距 }.margin({ top: `${this.basePadding / 2}lpx`, bottom: `${this.basePadding / 2}lpx` }); // 设置外边距 }) } .alignItems(HorizontalAlign.Start) // 设置水平对齐方式 .width('650lpx') // 设置宽度 .padding(`${this.basePadding}lpx`) // 设置内边距 .margin({ top: `${this.basePadding}lpx` }) // 设置外边距 .borderRadius(10) // 设置圆角 .backgroundColor(Color.White) // 设置背景颜色 .shadow({ // 添加阴影效果 radius: 10, // 阴影半径 color: this.secondaryColor, // 阴影颜色 offsetX: 0, // X轴偏移量 offsetY: 0 // Y轴偏移量 }); // 添加工具介绍部分 Column() { // 添加标题 Text('工具介绍').fontSize(this.fontSizeLarge).fontWeight(600).fontColor(this.primaryColor); // 添加工具介绍的文本 Text('这款温度单位转换工具专为满足您在科学研究、日常生活及工作中的需求而设计。借助此工具,您可以轻松实现摄氏度(Celsius)、华氏度(Fahrenheit)和开尔文(Kelvin)之间的无缝切换。无论是学术研究、日常应用还是专业工作,都能为您提供精准便捷的温度换算服务。') .textAlign(TextAlign.JUSTIFY) // 设置文本对齐方式 .fontSize(this.fontSizeSmall) // 设置字体大小 .fontColor(this.primaryColor) // 设置字体颜色 .margin({ top: `${this.basePadding / 2}lpx` }); // 设置外边距 } .alignItems(HorizontalAlign.Start) // 设置水平对齐方式 .width('650lpx') // 设置宽度 .padding(`${this.basePadding}lpx`) // 设置内边距 .margin({ top: `${this.basePadding}lpx` }) // 设置外边距 .borderRadius(10) // 设置圆角 .backgroundColor(Color.White) // 设置背景颜色 .shadow({ // 添加阴影效果 radius: 10, // 阴影半径 color: this.secondaryColor, // 阴影颜色 offsetX: 0, // X轴偏移量 offsetY: 0 // Y轴偏移量 }); } .height('100%') // 设置高度 .width('100%') // 设置宽度 .backgroundColor(this.bgColor); // 设置背景颜色 } } 转载自https://www.cnblogs.com/zhongcx/p/18570831
-
【引言】在鸿蒙NEXT开发中,九宫格抽奖是一个常见且有趣的应用场景。通过九宫格抽奖,用户可以随机获得不同奖品,增加互动性和趣味性。本文将介绍如何使用鸿蒙开发框架实现九宫格抽奖功能,并通过代码解析展示实现细节。【环境准备】• 操作系统:Windows 10• 开发工具:DevEco Studio NEXT Beta1 Build Version: 5.0.3.806• 目标设备:华为Mate60 Pro• 开发语言:ArkTS• 框架:ArkUI• API版本:API 12【思路】本案例中的“九宫格随机”应用旨在模拟一个简单的抽奖场景,用户点击抽奖按钮后,程序会从预先定义好的九个奖品中随机选择一个作为最终奖品。整个应用采用了响应式编程模式,结合鸿蒙NEXT提供的组件化开发方式,实现了交互流畅、视觉效果良好的用户体验。1. Prize类设计 应用首先定义了一个Prize类,用于表示奖品信息。该类使用了@ObservedV2装饰器,使得奖品属性(如标题、颜色、描述)的变化可以被自动追踪,从而实现UI的实时更新。构造函数允许创建具有特定属性值的奖品实例,便于后续管理。2. MyPrizeUpdate结构组件 为了提供奖品信息的编辑功能,我们创建了MyPrizeUpdate结构组件。它通过接收外部传入的数据(当前选中的奖品索引、抽奖顺序数组及所有奖品的数组),构建了一个包含文本输入框的界面,用户可以在其中修改奖品的标题、描述和颜色。任何对这些属性的更改都会即时反映到对应的奖品对象上,并触发UI的相应更新。3. LotteryPage入口组件LotteryPage是整个抽奖应用的核心组件,负责组织页面布局和处理用户交互逻辑。它初始化了一系列必要的状态变量,比如保存所有奖品的数组prizeArray、定义抽奖顺序的selectionOrder以及控制动画状态的isAnimating等。此外,该组件实现了抽奖过程的关键方法——startLottery(开始抽奖)、runAtConstantSpeed(匀速运行)和slowDown(减速),它们共同协作以模拟真实的抽奖体验。当用户点击抽奖按钮时,这些方法按照预定的速度模式依次调用,直到最终确定一个奖品为止。最后,通过弹出对话框的方式向用户展示抽奖结果。4. UI布局与样式 在构建UI方面,应用充分利用了鸿蒙NEXT提供的布局容器(如Column、Row、Flex)和样式属性(如宽度、高度、边距、背景色、圆角、阴影),精心设计了每个奖品项的外观。特别地,对于抽奖按钮,不仅设置了独特的背景颜色,还在点击事件中添加了动画效果,增强了用户的参与感。同时,考虑到不同设备屏幕尺寸的差异,所有布局元素均采用相对单位进行设置,确保了应用在各种终端上的良好适配性。5. 动画与交互优化 为了让抽奖过程看起来更加生动有趣,应用引入了加速、匀速、减速三个阶段的动画效果,使选中的奖品项能够以逐渐加快然后缓慢停止的方式出现在用户面前。这种变化不仅增加了悬念感,也提升了整体的娱乐性。此外,通过对点击事件的监听和处理,确保了即使是在动画过程中,用户的交互也不会受到影响,保证了良好的用户体验。【完整代码】// 定义一个可观察的Prize类,用于表示奖品信息。 @ObservedV2 class Prize { @Trace title: string // 奖品标题属性,使用@Trace进行追踪以便响应式更新UI @Trace color: string // 奖品颜色属性 @Trace description: string // 奖品描述属性 // 构造函数,用来初始化新的奖品实例 constructor(title: string, color: string, description: string = "") { this.title = title // 设置奖品标题 this.color = color // 设置奖品颜色 this.description = description // 设置奖品描述,默认为空字符串 } } // 定义MyPrizeUpdate结构组件,用于显示和编辑选中的奖品信息 @Component struct MyPrizeUpdate { @Consume selectedIndex: number // 当前选中的奖品索引 @Consume private selectionOrder: number[] // 保存抽奖顺序的数组 @Consume private prizeArray: Prize[] // 保存所有奖品的数组 build() { Column({ space: 20 }) { // 创建列布局容器,设置子元素之间的间距为20px Row() { // 创建行布局容器 Text('标题:') // 显示“标题”文本 TextInput({ text: this.prizeArray[this.selectionOrder[this.selectedIndex%this.selectionOrder.length]].title }) .width('300lpx') // 设置输入框宽度 .onChange((value) => { // 监听输入框内容变化 this.prizeArray[this.selectionOrder[this.selectedIndex%this.selectionOrder.length]].title = value // 更新奖品标题 }) } Row() { Text('描述:') TextInput({ text: `${this.prizeArray[this.selectionOrder[this.selectedIndex%this.selectionOrder.length]].description}` }).width('300lpx').onChange((value) => { // 同上,但针对奖品描述 this.prizeArray[this.selectionOrder[this.selectedIndex%this.selectionOrder.length]].description = value }) } Row() { Text('颜色:') TextInput({ text: `${this.prizeArray[this.selectionOrder[this.selectedIndex%this.selectionOrder.length]].color}` }).width('300lpx').onChange((value) => { // 同上,但针对奖品颜色 this.prizeArray[this.selectionOrder[this.selectedIndex%this.selectionOrder.length]].color = value }) } } .justifyContent(FlexAlign.Start) // 设置内容左对齐 .padding(40) // 设置内边距 .width('100%') // 设置宽度为100% .backgroundColor(Color.White) // 设置背景颜色为白色 } } // 定义抽奖页面入口组件 @Entry @Component struct LotteryPage { @Provide private selectedIndex: number = 0 // 提供当前选中的索引,初始值为0 private isAnimating: boolean = false // 标记是否正在进行动画,初始值为false @Provide private selectionOrder: number[] = [0, 1, 2, 5, 8, 7, 6, 3] // 定义抽奖顺序 private cellWidth: number = 200 // 单元格宽度 private baseMargin: number = 10 // 单元格边距 @Provide private prizeArray: Prize[] = [ new Prize("红包", "#ff9675", "10元"), // 初始化奖品数组,创建各种奖品对象 new Prize("话费", "#ff9f2e", "5元"), new Prize("红包", "#8e7fff", "50元"), new Prize("红包", "#48d1ea", "30元"), new Prize("开始抽奖", "#fffdfd"), // 抽奖按钮,没有具体奖品描述 new Prize("谢谢参与", "#5f5f5f"), new Prize("谢谢参与", "#5f5f5f"), new Prize("超市红包", "#5f5f5f", "100元"), new Prize("鲜花", "#75b0fe"), ] private intervalID: number = 0 // 定时器ID,用于控制抽奖速度 @State isSheetVisible: boolean = false // 控制底部弹出表单的可见性 // 开始抽奖逻辑 startLottery(speed: number = 500) { setTimeout(() => { // 设置延时执行 if (speed > 50) { // 如果速度大于50,则递归调用startLottery以逐渐加速 speed -= 50 this.startLottery(speed) } else { this.runAtConstantSpeed() // 达到最高速度后进入匀速阶段 return } this.selectedIndex++ // 每次调用时更新选中索引 }, speed) } // 以恒定速度运行抽奖 runAtConstantSpeed() { let speed = 40 + Math.floor(Math.random() * this.selectionOrder.length) // 随机生成一个速度值 clearInterval(this.intervalID) // 清除之前的定时器 this.intervalID = setInterval(() => { // 设置新的定时器来更新选中索引 if (this.selectedIndex >= speed) { // 如果选中索引达到速度值,停止并进入减速阶段 clearInterval(this.intervalID) this.slowDown() return } this.selectedIndex++ }, 50) } // 减速逻辑 slowDown(speed = 50) { setTimeout(() => { // 设置延时执行 if (speed < 500) { // 如果速度小于500,则递归调用slowDown以逐渐减速 speed += 50 this.slowDown(speed) } else { this.selectedIndex %= this.selectionOrder.length // 确保索引在有效范围内 let index = this.selectionOrder[this.selectedIndex] // 获取最终选中的奖品索引 this.isAnimating = false // 动画结束 this.getUIContext().showAlertDialog({ // 显示结果对话框 title: '结果', message: `${this.prizeArray[index].title}${this.prizeArray[index].description}`, // 显示奖品信息 confirm: { defaultFocus: true, value: '我知道了', // 确认按钮文本 action: () => {} // 点击确认后的操作 }, alignment: DialogAlignment.Center, }); return } this.selectedIndex++ }, speed) } // 构建UI方法 build() { Column() { // 使用Column布局容器 Flex({ wrap: FlexWrap.Wrap }) { // 使用弹性布局,允许换行 ForEach(this.prizeArray, (item: Prize, index: number) => { // 遍历奖品数组,创建每个奖品的UI Column() { // 使用Column布局容器为每个奖品项 Text(`${item.title}`) // 显示奖品标题 .fontColor(index == 4 ? Color.White : item.color) // 设置字体颜色,对于抽奖按钮特殊处理 .fontSize(16) Text(`${item.description}`) // 显示奖品描述 .fontColor(index == 4 ? Color.White : item.color) // 设置字体颜色 .fontSize(20) } .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.8 }) // 添加点击效果 .onClick(() => { // 处理点击事件 if (this.isAnimating) { // 如果正在动画中,忽略点击 return } if (index == 4) { // 如果点击的是抽奖按钮,开始抽奖 this.isAnimating = true this.startLottery() } else { for (let i = 0; i < this.selectionOrder.length; i++) { if (this.selectionOrder[i] == index) { this.selectedIndex = i // 更新选中索引到对应位置 } } } }) .alignItems(HorizontalAlign.Center) // 设置水平居中对齐 .justifyContent(FlexAlign.Center) // 设置垂直居中对齐 .width(`${this.cellWidth}lpx`) // 设置单元格宽度 .height(`${this.cellWidth}lpx`) // 设置单元格高度 .margin(`${this.baseMargin}lpx`) // 设置单元格边距 .backgroundColor(index == 4 ? "#ff5444" : // 抽奖按钮背景颜色特殊处理 (this.selectionOrder[this.selectedIndex % this.selectionOrder.length] == index ? Color.Gray : Color.White)) .borderRadius(10) // 设置圆角 .shadow({ // 设置阴影效果 radius: 10, color: "#f98732", offsetX: 0, offsetY: 20 }) }) }.width(`${this.cellWidth * 3 + this.baseMargin * 6}lpx`) // 设置整体宽度 .margin({ top: 30 }) // 设置顶部边距 MyPrizeUpdate().margin({top:20}) // 插入MyPrizeUpdate组件,并设置其上边距 } .height('100%') // 设置高度为100% .width('100%') // 设置宽度为100% .backgroundColor("#ffb350") // 设置页面背景颜色 } } 转载自https://www.cnblogs.com/zhongcx/p/18602222
-
【引言】保质期计算应用是一个基于鸿蒙NEXT框架开发的数字和文本统计组件。用户可以输入商品的生产日期和保质期天数,应用会自动计算并展示相关信息,包括保质状态、剩余天数、生产日期和到期日期。【环境准备】• 操作系统:Windows 10• 开发工具:DevEco Studio NEXT Beta1 Build Version: 5.0.3.806• 目标设备:华为Mate60 Pro• 开发语言:ArkTS• 框架:ArkUI• API版本:API 12【实现思路】1 组件定义在应用中,我们定义了一个名为ExpiryDateCalculator的组件,其中包含了各种状态变量和方法。通过监听输入文本变化和选择日期变化,实现了自动更新相关信息的功能。2 UI界面构建通过构建UI界面,我们使用了列布局和行布局来展示各个信息模块。包括标题展示、统计结果展示、示例和清空按钮、选择生产日期等功能。通过设置字体颜色、背景色、阴影效果等,使界面更加美观和易读。3 交互功能实现在交互功能方面,我们实现了输入框焦点状态的切换、清空按钮功能、选择日期功能等。用户可以方便地输入信息并查看计算结果,提升了用户体验和操作便捷性。【完整代码】@Entry @Component struct ExpiryDateCalculator { // 定义文本颜色的状态变量,初始值为深灰色 @State private textColor: string = "#2e2e2e"; // 定义阴影边框颜色的状态变量,初始值为浅灰色 @State private shadowColor: string = "#d5d5d5"; // 定义基础内边距的状态变量,初始值为30 @State private basePadding: number = 30; // 定义是否已过期的状态变量,初始值为false @State private isExpired: boolean = false; // 定义生产日期的状态变量,初始值为空字符串 @State private productionDate: string = ""; // 定义到期日期的状态变量,初始值为空字符串 @State private expiryDate: string = ""; // 定义剩余有效天数的状态变量,初始值为0 @State private remainingDays: number = 0; // 定义主题颜色,初始值为橙色 @State private themeColor: string | Color = Color.Orange; // 输入框是否获得了焦点的状态变量,初始值为false @State isInputFocused: boolean = false; // 定义监听输入文本变化的状态变量,初始值为"9" @State @Watch('inputChanged') private inputText: string = "9"; // 定义选择的日期状态变量,初始值为当前日期 @State @Watch('inputChanged') private selectedDate: Date = new Date() // 组件即将出现时的操作 aboutToAppear(): void { // 调用输入变化处理方法 this.inputChanged() } // 获取年月日的方法 getYearMonthDay(date: Date) { // 获取年份并格式化为4位数 const year: string = date.getFullYear().toString().padStart(4, '0'); // 获取月份并格式化为2位数 const month: string = (date.getMonth() + 1).toString().padStart(2, '0'); // 获取日期并格式化为2位数 const day: string = date.getDate().toString().padStart(2, '0'); // 返回格式化后的日期字符串 return `${year}年${month}月${day}日`; } // 输入变化时的处理方法 inputChanged() { // 打印当前选择的日期 console.info(`selectedDate:${this.selectedDate}`); // 更新生产日期为选择的日期 this.productionDate = this.getYearMonthDay(this.selectedDate); // 创建到期日期对象 let expiryDate: Date = new Date(this.selectedDate); // 根据输入的天数更新到期日期 expiryDate.setDate(expiryDate.getDate() + Number(this.inputText)); // 更新到期日期为格式化后的字符串 this.expiryDate = this.getYearMonthDay(expiryDate); // 判断是否已过期 this.isExpired = expiryDate.getTime() < new Date().getTime(); // 计算时间差 const timeDifference = expiryDate.getTime() - new Date().getTime(); // 计算剩余天数 this.remainingDays = Math.ceil(timeDifference / (1000 * 60 * 60 * 24)); } // 构建UI界面的方法 build() { // 创建一个列布局容器 Column() { // 添加标题 Text('保质期计算') .fontColor(this.textColor)// 设置字体颜色 .fontSize(18)// 设置字体大小 .width('100%')// 设置宽度为100% .height(50)// 设置高度为50 .textAlign(TextAlign.Center)// 设置文本对齐方式为居中 .backgroundColor(Color.White)// 设置背景颜色为白色 .shadow({ // 设置阴影效果 radius: 2, // 阴影半径 color: this.shadowColor, // 阴影颜色 offsetX: 0, // X轴偏移量 offsetY: 5 // Y轴偏移量 }); // 创建可滚动的容器 Scroll() { // 在可滚动容器内部创建列布局 Column() { // 添加统计结果展示 Column() { // 是否过期 Row() { Text() { Span(`保质状态:`) // 显示文本“保质状态:” Span(`${this.isExpired ? '已过期' : '未过期'}`)// 根据状态显示“已过期”或“未过期” .fontColor(this.isExpired ? "#e74c3c" : "#3ace7d") // 根据状态设置字体颜色 } .fontColor(this.textColor) // 设置字体颜色 .fontSize(16) // 设置字体大小 .layoutWeight(1); // 设置布局权重 } .constraintSize({ minHeight: 45 }) // 设置最小高度 .justifyContent(FlexAlign.SpaceBetween) // 设置内容对齐方式 .width('100%'); // 设置宽度为100% Divider(); // 添加分隔线 // 剩余天数 Row() { Text() { Span(`剩余天数:`) // 显示文本“剩余天数:” Span(`${this.remainingDays < 0 ? 0 : this.remainingDays} `).fontColor(Color.Orange) // 显示剩余天数,负数显示为0 Span('天') // 显示单位“天” } .fontColor(this.textColor) // 设置字体颜色 .fontSize(16) // 设置字体大小 .layoutWeight(1); // 设置布局权重 } .constraintSize({ minHeight: 45 }) // 设置最小高度 .justifyContent(FlexAlign.SpaceBetween) // 设置内容对齐方式 .width('100%'); // 设置宽度为100% Divider(); // 添加分隔线 // 生产日期 Row() { Text() { Span(`生产日期:`) // 显示文本“生产日期:” Span(`${this.productionDate} `) // 显示生产日期 } .fontColor(this.textColor) // 设置字体颜色 .fontSize(16) // 设置字体大小 .layoutWeight(1); // 设置布局权重 } .constraintSize({ minHeight: 45 }) // 设置最小高度 .justifyContent(FlexAlign.SpaceBetween) // 设置内容对齐方式 .width('100%'); // 设置宽度为100% Divider(); // 添加分隔线 // 到期日期 Row() { Text() { Span(`到期日期:`) // 显示文本“到期日期:” Span(`${this.expiryDate} `) // 显示到期日期 } .fontColor(this.textColor) // 设置字体颜色 .fontSize(16) // 设置字体大小 .layoutWeight(1); // 设置布局权重 } .constraintSize({ minHeight: 45 }) // 设置最小高度 .justifyContent(FlexAlign.SpaceBetween) // 设置内容对齐方式 .width('100%'); // 设置宽度为100% } .alignItems(HorizontalAlign.Start) // 设置子项对齐方式 .width('650lpx') // 设置宽度为650像素 .padding(`${this.basePadding}lpx`) // 设置内边距 .margin({ top: `${this.basePadding}lpx` }) // 设置上边距 .borderRadius(10) // 设置圆角 .backgroundColor(Color.White) // 设置背景颜色为白色 .shadow({ // 设置阴影效果 radius: 10, // 阴影半径 color: this.shadowColor, // 阴影颜色 offsetX: 0, // X轴偏移量 offsetY: 0 // Y轴偏移量 }); // 添加示例和清空按钮 Column() { Row() { // 添加文本输入区域 Row() { TextInput({ text: $$this.inputText, placeholder: `请输入保质期天数` })// 创建文本输入框 .type(InputType.Number)// 设置输入类型为数字 .layoutWeight(1)// 设置布局权重 .fontSize(16)// 设置字体大小 .textAlign(TextAlign.JUSTIFY)// 设置文本对齐方式 .backgroundColor(Color.Transparent)// 设置背景色为透明 .padding(0)// 设置内边距为0 .height('100%')// 设置高度为100% .placeholderColor(this.isInputFocused ? this.themeColor : Color.Gray)// 设置占位符颜色 .fontColor(this.isInputFocused ? this.themeColor : this.textColor)// 设置字体颜色 .caretColor(this.themeColor)// 设置光标颜色 .borderRadius(0)// 设置圆角为0 .onBlur(() => this.isInputFocused = false)// 失去焦点时更新状态 .onFocus(() => this.isInputFocused = true)// 获得焦点时更新状态 .width('100%'); // 设置宽度为100% } .padding(`${this.basePadding / 2}lpx`) // 设置内边距 .backgroundColor("#f2f1fd") // 设置背景色 .layoutWeight(1) // 设置布局权重 .borderWidth(1) // 设置边框宽度 .borderRadius(10) // 设置圆角为10 .borderColor(this.isInputFocused ? this.themeColor : Color.Gray) // 根据焦点状态设置边框颜色 .margin({ right: `${this.basePadding / 2}lpx` }); // 设置右边距 Blank(); // 添加空白占位符 // 清空按钮 Text('清空')// 显示文本“清空” .fontColor("#e48742")// 设置字体颜色 .fontSize(16)// 设置字体大小 .padding(`${this.basePadding / 2}lpx`)// 设置内边距 .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.8 })// 点击效果 .backgroundColor("#ffefe6")// 设置背景色 .borderRadius(5)// 设置圆角为5 .onClick(() => { // 点击事件处理 this.inputText = ""; // 清空输入文本 }); } .height(45) // 设置高度为45 .alignItems(VerticalAlign.Center) // 设置内容垂直居中 .justifyContent(FlexAlign.SpaceBetween) // 设置内容对齐方式 .width('100%'); // 设置宽度为100% Divider(); // 添加分隔线 // 选择生产日期 Row() { Text('请选择生产日期')// 显示文本“请选择生产日期” .fontColor("#5871ce")// 设置字体颜色 .fontSize(16)// 设置字体大小 .padding(`${this.basePadding / 2}lpx`)// 设置内边距 .backgroundColor("#f2f1fd")// 设置背景色 .borderRadius(5)// 设置圆角为5 .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.8 }) // 点击效果 Blank(); // 添加空白占位符 CalendarPicker({ hintRadius: 10, selected: this.selectedDate })// 创建日历选择器 .edgeAlign(CalendarAlign.END)// 设置对齐方式 .textStyle({ color: "#ff182431", font: { size: 20, weight: FontWeight.Normal } })// 设置文本样式 .margin(10)// 设置外边距 .onChange((date: Date) => { // 日期变化事件处理 this.selectedDate = date // 更新选择的日期 }) } .height(45) // 设置高度为45 .justifyContent(FlexAlign.SpaceBetween) // 设置内容对齐方式 .width('100%'); // 设置宽度为100% } .alignItems(HorizontalAlign.Start) // 设置内容水平对齐方式 .width('650lpx') // 设置宽度为650像素 .padding(`${this.basePadding}lpx`) // 设置内边距 .margin({ top: `${this.basePadding}lpx` }) // 设置上边距 .borderRadius(10) // 设置圆角为10 .backgroundColor(Color.White) // 设置背景颜色为白色 .shadow({ // 设置阴影效果 radius: 10, // 阴影半径 color: this.shadowColor, // 阴影颜色 offsetX: 0, // X轴偏移量 offsetY: 0 // Y轴偏移量 }); // 添加工具介绍 Column() { Text('工具介绍')// 显示文本“工具介绍” .fontSize(18)// 设置字体大小为18 .fontWeight(600)// 设置字体粗细 .fontColor(this.textColor); // 设置字体颜色为状态变量中定义的文本颜色 Text('输入需要计算保质期商品的生产日期和保质期天数,工具将为您自动计算商品的保质期状态、剩余天数、到期日。')// 显示工具介绍文本 .textAlign(TextAlign.JUSTIFY)// 设置文本对齐方式为两端对齐 .fontSize(16)// 设置字体大小为16 .fontColor(this.textColor)// 设置字体颜色为状态变量中定义的文本颜色 .margin({ top: `${this.basePadding / 2}lpx` }); // 设置上边距为基础内边距的一半 } .alignItems(HorizontalAlign.Start) // 设置内容水平对齐方式 .width('650lpx') // 设置宽度为650像素 .padding(`${this.basePadding}lpx`) // 设置内边距 .margin({ top: `${this.basePadding}lpx` }) // 设置上边距 .borderRadius(10) // 设置圆角为10 .backgroundColor(Color.White) // 设置背景颜色为白色 .shadow({ // 设置阴影效果 radius: 10, // 阴影半径 color: this.shadowColor, // 阴影颜色 offsetX: 0, // X轴偏移量 offsetY: 0 // Y轴偏移量 }); } } .scrollBar(BarState.Off) // 关闭滚动条 .clip(false); // 不裁剪内容 } .height('100%') // 设置高度为100% .width('100%') // 设置宽度为100% .backgroundColor("#f4f8fb"); // 设置背景颜色为指定颜色 } }转载自https://www.cnblogs.com/zhongcx/p/18603215
-
【引言】本案例将展示如何使用鸿蒙NEXT框架开发一个简单的世界时钟应用程序。该应用程序能够展示多个城市的当前时间,并支持搜索功能,方便用户快速查找所需城市的时间信息。在本文中,我们将详细介绍应用程序的实现思路,包括如何获取时区信息、更新城市时间、以及如何实现搜索高亮功能。【环境准备】• 操作系统:Windows 10• 开发工具:DevEco Studio NEXT Beta1 Build Version: 5.0.3.806• 目标设备:华为Mate60 Pro• 开发语言:ArkTS• 框架:ArkUI• API版本:API 12【实现思路】1. 组件结构设计我们的应用程序主要由两个核心组件构成:CityTimeInfo类和WorldClockApp组件。CityTimeInfo类用于存储每个城市的名称、当前时间和时区信息。WorldClockApp组件则负责管理城市时间列表、搜索功能以及用户界面。2. 获取时区信息在应用程序启动时,我们需要获取可用的时区信息。通过调用i18n.TimeZone.getAvailableIDs()方法,我们可以获取所有可用的时区ID。接着,我们使用这些ID创建CityTimeInfo实例,并将其添加到城市时间列表中。为了确保用户能够看到当前时间,我们还添加了北京的时间信息作为默认城市。3. 更新时间逻辑为了实时更新城市的当前时间,我们在updateAllCityTimes方法中实现了时间更新逻辑。通过获取系统的语言环境和相应的日历对象,我们可以根据城市的时区ID获取当前的年、月、日、时、分、秒,并将其格式化为字符串。这个方法会在页面显示时每秒调用一次,确保时间信息的准确性。4. 搜索功能实现为了提升用户体验,我们实现了搜索功能,允许用户通过输入关键词来筛选城市。在highlightSearchText方法中,我们对城市名称进行分段处理,将匹配的关键词高亮显示。通过这种方式,用户可以快速找到所需的城市,并且高亮的文本能够提供更好的视觉反馈。5. 用户界面构建最后,我们使用build方法构建用户界面。界面包括一个搜索框、城市名称和时间的显示区域。我们使用了Column和Row组件来布局,并通过设置样式属性来美化界面。滚动区域的实现使得用户可以方便地浏览多个城市的信息。【完整代码】import { i18n } from '@kit.LocalizationKit' // 导入国际化模块,用于处理多语言 import { inputMethod } from '@kit.IMEKit' // 导入输入法模块 @ObservedV2 // 观察者装饰器,用于观察状态变化 class CityTimeInfo { // 定义城市时间信息类 @Trace cityName: string = ""; // 城市名称,初始为空字符串 @Trace currentTime: string = ""; // 当前时间,初始为空字符串 timeZone: i18n.TimeZone; // 时区属性 constructor(cityName: string, timeZone: i18n.TimeZone) { // 构造函数,接收城市名称和时区 this.cityName = cityName; // 设置城市名称 this.timeZone = timeZone; // 设置时区 } @Trace isVisible: boolean = true; // 是否可见,初始为true } @Entry // 入口组件装饰器 @Component // 组件装饰器 struct WorldClockApp { // 定义世界时钟应用组件 @State private searchText: string = ''; // 搜索文本,初始为空字符串 @State private cityTimeList: CityTimeInfo[] = []; // 城市时间信息列表,初始为空数组 private lineColor: string = "#e6e6e6"; // 边框颜色 private titleBackgroundColor: string = "#f8f8f8"; // 标题背景色 private textColor: string = "#333333"; // 文字颜色 private basePadding: number = 4; // 内边距 private lineWidth: number = 2; // 边框宽度 private rowHeight: number = 50; // 行高 private ratio: number[] = [1, 1]; // 列宽比例 private textSize: number = 14; // 基础字体大小 private updateIntervalId = 0; // 更新间隔ID updateAllCityTimes() { // 更新所有城市的时间 const locale = i18n.System.getSystemLocale(); // 获取系统语言环境 for (const cityTime of this.cityTimeList) { // 遍历城市时间列表 const timeZoneId: string = cityTime.timeZone.getID(); // 获取时区ID const calendar = i18n.getCalendar(locale); // 获取日历对象 calendar.setTimeZone(timeZoneId); // 设置日历的时区 // 获取当前时间的各个部分 const year = calendar.get("year").toString().padStart(4, '0'); // 年 const month = (calendar.get("month")+1).toString().padStart(2, '0'); // 月 const day = calendar.get("date").toString().padStart(2, '0'); // 日 const hour = calendar.get("hour_of_day").toString().padStart(2, '0'); // 小时 const minute = calendar.get("minute").toString().padStart(2, '0'); // 分钟 const second = calendar.get("second").toString().padStart(2, '0'); // 秒 // 更新城市的当前时间字符串 cityTime.currentTime = `${year}年${month}月${day}日 ${hour}:${minute}:${second}`; } } onPageShow(): void { // 页面显示时的处理 clearInterval(this.updateIntervalId); // 清除之前的定时器 this.updateIntervalId = setInterval(() => { // 设置新的定时器 this.updateAllCityTimes(); // 每秒更新所有城市的时间 }, 1000); } onPageHide(): void { // 页面隐藏时的处理 clearInterval(this.updateIntervalId); // 清除定时器 } private highlightSearchText(cityTime: CityTimeInfo, keyword: string) { // 高亮搜索文本 let text = cityTime.cityName // 获取城市名称 if (!keyword) { // 如果没有关键词 cityTime.isVisible = true // 设置城市可见 return [text] // 返回城市名称 } let segments: string[] = []; // 存储分段文本 let lastMatchEnd: number = 0; // 上一个匹配结束的位置 while (true) { // 循环查找关键词 const matchIndex = text.indexOf(keyword, lastMatchEnd); // 查找关键词位置 if (matchIndex === -1) { // 如果没有找到 segments.push(text.slice(lastMatchEnd)); // 添加剩余文本 break; // 退出循环 } else { segments.push(text.slice(lastMatchEnd, matchIndex)); // 添加匹配前的文本 segments.push(text.slice(matchIndex, matchIndex + keyword.length)); // 添加匹配的关键词 lastMatchEnd = matchIndex + keyword.length; // 更新最后匹配结束位置 } } cityTime.isVisible = (segments.indexOf(keyword) != -1) // 设置城市可见性 return segments; // 返回分段文本 } aboutToAppear() { // 组件即将出现时的处理 const timeZoneIds: Array<string> = i18n.TimeZone.getAvailableIDs(); // 获取可用时区ID列表 this.cityTimeList.push(new CityTimeInfo('北京 (中国)', i18n.getTimeZone())); // 添加北京的城市时间信息 for (const id of timeZoneIds) { // 遍历时区ID const cityDisplayName = i18n.TimeZone.getCityDisplayName(id.split('/')[1], "zh-CN"); // 获取城市显示名称 if (cityDisplayName) { // 如果城市名称存在 this.cityTimeList.push(new CityTimeInfo(cityDisplayName, i18n.getTimeZone(id))); // 添加城市时间信息 } } this.updateAllCityTimes(); // 更新所有城市的时间 } build() { // 构建组件的UI Column({ space: 0 }) { // 创建一个垂直列 Search({ value: $$this.searchText })// 创建搜索框 .margin(this.basePadding)// 设置边距 .fontFeature("\"ss01\" on") // 设置字体特性 Column() { // 创建一个列 Row() { // 创建一行 Text('城市')// 显示“城市”文本 .height('100%')// 高度占满 .layoutWeight(this.ratio[0])// 设置布局权重 .textAlign(TextAlign.Center)// 文本居中 .fontSize(this.textSize)// 设置字体大小 .fontWeight(600)// 设置字体粗细 .fontColor(this.textColor) // 设置字体颜色 Line().height('100%').width(this.lineWidth).backgroundColor(this.lineColor) // 创建分隔线 Text('时间')// 显示“时间”文本 .height('100%')// 高度占满 .layoutWeight(this.ratio[1])// 设置布局权重 .textAlign(TextAlign.Center)// 文本居中 .fontSize(this.textSize)// 设置字体大小 .fontWeight(600)// 设置字体粗细 .fontColor(this.textColor) // 设置字体颜色 }.height(this.rowHeight).borderWidth(this.lineWidth).borderColor(this.lineColor) // 设置行高和边框 .backgroundColor(this.titleBackgroundColor) // 设置背景色 }.width(`100%`).padding({ left: this.basePadding, right: this.basePadding }) // 设置列宽和内边距 Scroll() { // 创建可滚动区域 Column() { // 创建一个列 ForEach(this.cityTimeList, (item: CityTimeInfo) => { // 遍历城市时间列表 Row() { // 创建一行 Text() { // 创建文本 ForEach(this.highlightSearchText(item, this.searchText), (segment: string, index: number) => { // 高亮搜索文本 ContainerSpan() { // 创建容器 Span(segment)// 创建文本段 .fontColor(segment === this.searchText ? Color.White : Color.Black)// 设置字体颜色 .onClick(() => { // 点击事件 console.info(`高亮文本被点击:${segment}`); // 输出点击的文本 console.info(`点击索引:${index}`); // 输出点击的索引 }); }.textBackgroundStyle({ // 设置文本背景样式 color: segment === this.searchText ? Color.Red : Color.Transparent // 根据是否匹配设置背景色 }); }); } .height('100%') // 高度占满 .layoutWeight(this.ratio[0]) // 设置布局权重 .textAlign(TextAlign.Center) // 文本居中 .fontSize(this.textSize) // 设置字体大小 .fontColor(this.textColor) // 设置字体颜色 Line().height('100%').width(this.lineWidth).backgroundColor(this.lineColor) // 创建分隔线 Text(item.currentTime)// 显示当前时间 .height('100%')// 高度占满 .layoutWeight(this.ratio[1])// 设置布局权重 .textAlign(TextAlign.Center)// 文本居中 .fontSize(this.textSize)// 设置字体大小 .fontColor(this.textColor) // 设置字体颜色 } .height(this.rowHeight) // 设置行高 .borderWidth({ left: this.lineWidth, right: this.lineWidth, bottom: this.lineWidth }) // 设置边框宽度 .borderColor(this.lineColor) // 设置边框颜色 .visibility(item.isVisible ? Visibility.Visible : Visibility.None) // 根据可见性设置显示状态 }) }.width(`100%`).padding({ left: this.basePadding, right: this.basePadding }) // 设置宽度和内边距 } .width('100%') // 设置宽度占满 .layoutWeight(1) // 设置布局权重 .align(Alignment.Top) // 对齐方式 .onScrollStart(() => { // 滚动开始事件 this.onPageHide() // 页面隐藏处理 }) .onScrollStop(() => { // 滚动停止事件 this.onPageShow() // 页面显示处理 }) .onTouch((event) => { // 触摸事件 if (event.type == TouchType.Down) { // 如果是按下事件 inputMethod.getController().stopInputSession() // 停止输入会话 } }) } } }转载自https://www.cnblogs.com/zhongcx/p/18605192
-
【引言】本文将介绍一个名为“颜文字搜索器”的开发案例,该应用是基于鸿蒙NEXT平台构建的,旨在帮助用户快速查找和使用各种风格的表情符号。通过本案例的学习,读者可以了解如何在鸿蒙平台上进行数据处理、UI设计以及交互逻辑的实现。【环境准备】• 操作系统:Windows 10• 开发工具:DevEco Studio NEXT Beta1 Build Version: 5.0.3.806• 目标设备:华为Mate60 Pro• 开发语言:ArkTS• 框架:ArkUI• API版本:API 12【开发思路】1. 数据模型设计为了表示单个表情符号的信息,我们定义了一个 EmoticonBean 类,它包含了表情符号的风格(style)、类型(type)、表情符号本身(emoticon)及其含义(meaning)。此外,还添加了一个布尔属性 isShown 来追踪表情符号是否应该显示给用户,这有助于在搜索时动态更新列表。2. UI 组件与布局应用的主界面由一个 Index 组件构成,它负责整体布局的设计。界面上部是一个搜索框,允许用户输入关键词来过滤表情符号列表。下方则以表格形式展示了所有符合条件的表情符号,每一行包括四个部分:风格、类型、表情符号和含义。为了提升用户体验,当用户点击某个表情符号或其含义中的高亮文本时,会触发相应的点击事件,并输出日志信息。3. 数据加载与处理表情符号的数据来源于一个本地 JSON 文件 (emoticons.json),该文件在组件初次渲染之前被读取并解析为 EmoticonBean 对象数组。每次用户修改搜索框中的内容时,都会调用 splitAndHighlight 方法对每个表情符号的含义进行分割,并检查是否存在匹配的关键字。如果存在,则设置 isShown 属性为 true,否则为 false,以此控制表情符号是否在界面上显示。4. 搜索与高亮splitAndHighlight 函数用于将表情符号的含义按关键字分割成多个片段,并返回这些片段组成的数组。对于包含关键字的片段,会在界面上以不同的颜色高亮显示,从而直观地指出匹配的部分。此外,此函数还会根据是否有匹配项来决定表情符号是否可见,确保只有相关的表情符号才会展示给用户。5. 用户交互为了让用户有更好的操作体验,我们在界面上实现了触摸事件监听,当用户点击非输入区域时,自动关闭键盘。这样既保证了界面整洁,又简化了用户的操作流程。【完整代码】数据源:src/main/resources/rawfile/emoticons.jsoncid:link_0代码// 引入必要的工具库 util 用于文本解码等操作 import { util } from '@kit.ArkTS' // 引入 BusinessError 类用于处理业务逻辑错误 import { BusinessError } from '@kit.BasicServicesKit' // 引入 inputMethod 模块用于管理输入法行为 import { inputMethod } from '@kit.IMEKit' // 定义一个可以被观察的数据模型 EmoticonBean 表示单个表情符号的信息 @ObservedV2 class EmoticonBean { // 定义风格属性,并初始化为空字符串 style: string = "" // 定义类型属性,并初始化为空字符串 type: string = "" // 定义表情符号本身,并初始化为空字符串 emoticon: string = "" // 定义含义属性,并初始化为空字符串 meaning: string = "" // 构造函数,允许在创建对象时设置上述属性 constructor(style: string, type: string, emoticon: string, meaning: string) { this.style = style this.type = type this.emoticon = emoticon this.meaning = meaning } // 定义是否显示的表情符号状态标记,默认为 true,使用 @Trace 装饰器使其可追踪变化 @Trace isShown: boolean = true } // 使用 @Entry 和 @Component 装饰器定义 Index 组件作为应用入口 @Entry @Component struct Index { // 定义一个状态变量 textInput 用于存储搜索框中的文本内容,默认为空字符串 @State private textInput: string = '' // 定义一个状态变量 emoticonList 用于存储表情符号列表,默认为空数组 @State private emoticonList: EmoticonBean[] = [] // 定义线条颜色属性 private lineColor: string = "#e6e6e6" // 定义标题背景色属性 private titleBackground: string = "#f8f8f8" // 定义文本颜色属性 private textColor: string = "#333333" // 定义基础填充大小 private basePadding: number = 4 // 定义线条宽度 private lineWidth: number = 2 // 定义单元格高度 private cellHeight: number = 50 // 定义列权重比例 private weightRatio: number[] = [1, 1, 5, 4] // 定义基础字体大小 private baseFontSize: number = 14 // 定义一个方法 splitAndHighlight 用于分割并高亮表情符号含义中的关键词 private splitAndHighlight(item: EmoticonBean, keyword: string): string[] { let text = item.meaning // 获取表情符号的含义文本 if (!keyword) { // 如果没有关键词,则直接返回整个文本,并显示该表情符号 item.isShown = true return [text] } let segments: string[] = []; // 用于存储分割后的文本片段 let lastMatchEnd: number = 0; // 记录上一次匹配结束的位置 while (true) { // 循环查找关键词在文本中的位置 const matchIndex = text.indexOf(keyword, lastMatchEnd); // 查找关键词出现的位置 if (matchIndex === -1) { // 如果找不到关键词,将剩余文本加入到segments中并退出循环 segments.push(text.slice(lastMatchEnd)); break; } else { // 如果找到关键词,将非关键词部分和关键词部分分别加入到segments中 segments.push(text.slice(lastMatchEnd, matchIndex)); // 非关键词部分 segments.push(text.slice(matchIndex, matchIndex + keyword.length)); // 关键词部分 lastMatchEnd = matchIndex + keyword.length; } } // 如果有关键词出现,则设置表情符号为显示状态 item.isShown = (segments.indexOf(keyword) != -1) return segments; } // 当组件即将出现在屏幕上时调用此方法,用于加载表情符号数据 aboutToAppear() { // 从资源管理器中读取本地文件 emoticons.json 的内容 getContext().resourceManager.getRawFileContent("emoticons.json", (err: BusinessError, data) => { if (err) { // 如果读取失败,打印错误信息 console.error('getRawFileContent error: ' + JSON.stringify(err)) return } // 创建一个文本解码器来将二进制数据转换为字符串 let textDecoder = util.TextDecoder.create('utf-8', { ignoreBOM: true }) let jsonString = textDecoder.decodeToString(data, { stream: false }) let jsonObjectArray: object[] = JSON.parse(jsonString) // 将 JSON 字符串解析为对象数组 for (let i = 0; i < jsonObjectArray.length; i++) { // 遍历对象数组,填充 emoticonList let item = jsonObjectArray[i] this.emoticonList.push(new EmoticonBean(item['s'], item['t'], item['e'], item['m'])) } try { // 打印 emoticonList 到控制台以供调试 console.info(`this.emoticonList:${JSON.stringify(this.emoticonList, null, '\u00a0\u00a0')}`) } catch (err) { console.error('parse error: ' + JSON.stringify(err)) } }) } // 定义 build 方法构建组件的UI结构 build() { Column({ space: 0 }) { // 创建一个列容器,内部元素之间没有间距 // 搜索框组件,绑定到 textInput 状态变量 Search({ value: $$this.textInput }) .margin(this.basePadding) // 设置外边距 .fontFeature("\"ss01\" on") // 设置字体特征 // 创建一个列容器用于表头 Column() { Row() { // 创建一行用于放置表头 // 表头文字:风格 Text('风格') .height('100%') // 设置高度为父容器的100% .layoutWeight(this.weightRatio[0]) // 根据权重分配宽度 .textAlign(TextAlign.Center) // 文本居中对齐 .fontSize(this.baseFontSize) // 设置字体大小 .fontWeight(600) // 设置字体粗细 .fontColor(this.textColor) // 设置文本颜色 // 分割线 Line().height('100%').width(this.lineWidth).backgroundColor(this.lineColor) // 表头文字:类型 Text('类型') .height('100%') .layoutWeight(this.weightRatio[1]) .textAlign(TextAlign.Center) .fontSize(this.baseFontSize) .fontWeight(600) .fontColor(this.textColor) // 分割线 Line().height('100%').width(this.lineWidth).backgroundColor(this.lineColor) // 表头文字:表情 Text('表情') .height('100%') .layoutWeight(this.weightRatio[2]) .textAlign(TextAlign.Center) .fontSize(this.baseFontSize) .fontWeight(600) .fontColor(this.textColor) // 分割线 Line().height('100%').width(this.lineWidth).backgroundColor(this.lineColor) // 表头文字:含义 Text('含义') .height('100%') .layoutWeight(this.weightRatio[3]) .textAlign(TextAlign.Center) .fontSize(this.baseFontSize) .fontWeight(600) .fontColor(this.textColor) }.height(this.cellHeight).borderWidth(this.lineWidth).borderColor(this.lineColor) .backgroundColor(this.titleBackground) // 设置背景颜色 }.width(`100%`).padding({ left: this.basePadding, right: this.basePadding }) // 创建一个滚动容器 Scroll 包含表情符号列表 Scroll() { Column() { // ForEach 循环遍历 emoticonList 数组,创建每一行代表一个表情符号条目 ForEach(this.emoticonList, (item: EmoticonBean) => { Row() { // 显示表情符号的风格 Text(item.style) .height('100%') .layoutWeight(this.weightRatio[0]) .textAlign(TextAlign.Center) .fontSize(this.baseFontSize) .fontColor(this.textColor) // 分割线 Line().height('100%').width(this.lineWidth).backgroundColor(this.lineColor) // 显示表情符号的类型 Text(item.type) .height('100%') .layoutWeight(this.weightRatio[1]) .textAlign(TextAlign.Center) .fontSize(this.baseFontSize) .fontColor(this.textColor) // 分割线 Line().height('100%').width(this.lineWidth).backgroundColor(this.lineColor) // 显示表情符号 Text(item.emoticon) .height('100%') .layoutWeight(this.weightRatio[2]) .textAlign(TextAlign.Center) .fontSize(this.baseFontSize) .fontColor(this.textColor) .copyOption(CopyOptions.LocalDevice) // 允许复制到剪贴板 // 分割线 Line().height('100%').width(this.lineWidth).backgroundColor(this.lineColor) // 显示表情符号的含义,支持关键字高亮 Text() { ForEach(this.splitAndHighlight(item, this.textInput), (segment: string, index: number) => { ContainerSpan() { Span(segment) .fontColor(segment === this.textInput ? Color.White : Color.Black) // 根据是否是关键词设置字体颜色 .onClick(() => { // 设置点击事件监听器 console.info(`Highlighted text clicked: ${segment}`); // 打印点击的文本信息 console.info(`Click index: ${index}`); // 打印点击的索引信息 }); }.textBackgroundStyle({ color: segment === this.textInput ? Color.Red : Color.Transparent // 根据是否是关键词设置背景颜色 }); }); } .height('100%') .layoutWeight(this.weightRatio[3]) .textAlign(TextAlign.Center) .fontSize(this.baseFontSize) .fontColor(this.textColor) .padding({ left: this.basePadding, right: this.basePadding }) } .height(this.cellHeight) .borderWidth({ left: this.lineWidth, right: this.lineWidth, bottom: this.lineWidth }) .borderColor(this.lineColor) // 根据表情符号的状态(是否显示)来决定其可见性 .visibility(item.isShown ? Visibility.Visible : Visibility.None) }) }.width(`100%`).padding({ left: this.basePadding, right: this.basePadding }) }.width('100%').layoutWeight(1).align(Alignment.Top) // 触摸事件处理,当用户点击空白区域时,关闭键盘输入 .onTouch((event) => { if (event.type == TouchType.Down) { // 如果是按下事件 inputMethod.getController().stopInputSession() // 停止当前的输入会话 } }) }.width('100%').height('100%').backgroundColor(Color.White); // 设置容器的宽高和背景颜色 } }转载自https://www.cnblogs.com/zhongcx/p/18606230
-
【引言】在鸿蒙NEXT平台上,我们可以轻松地开发出一个经纬度距离计算器,帮助用户快速计算两点之间的距离。本文将详细介绍如何在鸿蒙NEXT中实现这一功能,通过简单的用户界面和高效的计算逻辑,为用户提供便捷的服务。【环境准备】• 操作系统:Windows 10• 开发工具:DevEco Studio NEXT Beta1 Build Version: 5.0.3.806• 目标设备:华为Mate60 Pro• 开发语言:ArkTS• 框架:ArkUI• API版本:API 12【思路】在本案例中,我们将创建一个名为“距离计算器”的组件,用户可以输入起点和终点的经纬度,系统将自动计算并显示两点之间的距离。以下是实现的主要思路:1 组件结构设计:使用Column和Row布局组件来组织界面元素,使其具有良好的可读性和用户体验。在界面顶部添加标题,明确应用的功能。2 输入区域:提供两个输入框,分别用于输入起点和终点的经纬度。用户可以手动输入,也可以通过点击示例按钮快速填充常用位置(如北京和上海)。设计清空按钮,方便用户快速重置输入。3 状态管理:使用@State装饰器管理组件的状态,包括输入框的聚焦状态、经纬度值和计算结果。通过@Watch装饰器监视输入变化,确保在用户输入经纬度时,能够实时更新计算结果。4 距离计算逻辑:在输入变化时,调用地图模块的calculateDistance方法,计算两点之间的距离,并将结果更新到界面上。结果以公里为单位显示,确保用户能够直观理解计算结果。5 界面美化:通过设置颜色、边框、圆角等样式,使界面更加美观和用户友好。使用适当的字体和大小,确保信息的清晰可读。【完整代码】import { mapCommon } from '@kit.MapKit'; // 导入地图通用模块 import { map } from '@kit.MapKit'; // 导入地图模块 @Entry // 入口装饰器,标识该组件为应用的入口 @Component // 组件装饰器,定义一个组件 struct DistanceCalculator { // 定义一个名为 DistanceCalculator 的结构体 @State private primaryColor: string = '#fea024'; // 定义主题颜色,初始值为橙色 @State private fontColor: string = "#2e2e2e"; // 定义字体颜色,初始值为深灰色 @State private isStartFocused: boolean = false; // 定义起点输入框的聚焦状态,初始为 false @State private isEndFocused: boolean = false; // 定义终点输入框的聚焦状态,初始为 false @State private isSecondStartFocused: boolean = false; // 定义第二起点输入框的聚焦状态,初始为 false @State private isSecondEndFocused: boolean = false; // 定义第二终点输入框的聚焦状态,初始为 false @State private baseSpacing: number = 30; // 定义基础间距,初始值为 30 @State @Watch('onInputChange') private startLongitude: string = ""; // 定义起点经度,初始为空,并监视输入变化 @State @Watch('onInputChange') private startLatitude: string = ""; // 定义起点纬度,初始为空,并监视输入变化 @State @Watch('onInputChange') private endLongitude: string = ""; // 定义终点经度,初始为空,并监视输入变化 @State @Watch('onInputChange') private endLatitude: string = ""; // 定义终点纬度,初始为空,并监视输入变化 @State distance: number = 0; // 定义两点之间的距离,初始值为 0 aboutToAppear(): void { // 生命周期钩子函数,组件即将显示时调用 this.onInputChange(); // 调用输入变化处理函数以初始化 } onInputChange() { // 输入变化处理函数 let fromLatLng: mapCommon.LatLng = { // 创建起点经纬度对象 latitude: Number(this.startLatitude), // 将起点纬度转换为数字 longitude: Number(this.startLongitude) // 将起点经度转换为数字 }; let toLatLng: mapCommon.LatLng = { // 创建终点经纬度对象 latitude: Number(this.endLatitude), // 将终点纬度转换为数字 longitude: Number(this.endLongitude) // 将终点经度转换为数字 }; this.distance = map.calculateDistance(fromLatLng, toLatLng); // 计算起点和终点之间的距离 } build() { // 构建界面函数 Column() { // 垂直布局容器 // 标题栏,展示应用名 Text("经纬度距离计算") // 创建文本组件,显示标题 .width('100%') // 设置宽度为 100% .height(54) // 设置高度为 54 像素 .fontSize(18) // 设置字体大小为 18 .fontWeight(600) // 设置字体粗细为 600 .backgroundColor(Color.White) // 设置背景颜色为白色 .textAlign(TextAlign.Center) // 设置文本对齐方式为居中 .fontColor(this.fontColor); // 设置字体颜色为定义的字体颜色 // 输入区域 Column() { // 垂直布局容器 Row() { // 水平布局容器 Text('示例(北京-->上海)') // 创建文本组件,显示示例信息 .fontColor("#5871ce") // 设置字体颜色为蓝色 .fontSize(18) // 设置字体大小为 18 .padding(`${this.baseSpacing / 2}lpx`) // 设置内边距 .backgroundColor("#f2f1fd") // 设置背景颜色 .borderRadius(5) // 设置圆角半径为 5 .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.8 }) // 设置点击效果 .onClick(() => { // 点击事件处理 this.startLongitude = "116.4074"; // 设置起点经度为北京经度 this.startLatitude = "39.9042"; // 设置起点纬度为北京纬度 this.endLongitude = "121.4737"; // 设置终点经度为上海经度 this.endLatitude = "31.2304"; // 设置终点纬度为上海纬度 }); Blank(); // 占位符,用于占据空间 Text('清空') // 创建文本组件,显示“清空”按钮 .fontColor("#e48742") // 设置字体颜色为橙色 .fontSize(18) // 设置字体大小为 18 .padding(`${this.baseSpacing / 2}lpx`) // 设置内边距 .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.8 }) // 设置点击效果 .backgroundColor("#ffefe6") // 设置背景颜色 .borderRadius(5) // 设置圆角半径为 5 .onClick(() => { // 点击事件处理 this.startLongitude = ""; // 清空起点经度 this.startLatitude = ""; // 清空起点纬度 this.endLongitude = ""; // 清空终点经度 this.endLatitude = ""; // 清空终点纬度 }); }.height(45) // 设置行高为 45 像素 .justifyContent(FlexAlign.SpaceBetween) // 设置子元素在主轴上的对齐方式 .width('100%'); // 设置宽度为 100% Divider().margin({ top: 5, bottom: 5 }); // 创建分隔符,设置上下边距 // 起点输入 Row() { // 水平布局容器 Text('起点') // 创建文本组件,显示“起点” .fontWeight(600) // 设置字体粗细为 600 .fontSize(18) // 设置字体大小为 18 .fontColor(this.fontColor); // 设置字体颜色为定义的字体颜色 } .margin({ bottom: `${this.baseSpacing}lpx`, top: `${this.baseSpacing}lpx` }); // 设置上下边距 Row() { // 水平布局容器 TextInput({ text: $$this.startLongitude, placeholder: '经度' }) // 创建起点经度输入框 .caretColor(this.primaryColor) // 设置光标颜色为主题颜色 .layoutWeight(1) // 设置布局权重 .type(InputType.NUMBER_DECIMAL) // 设置输入类型为小数 .placeholderColor(this.isStartFocused ? this.primaryColor : Color.Gray) // 设置占位符颜色 .fontColor(this.isStartFocused ? this.primaryColor : this.fontColor) // 设置字体颜色 .borderColor(this.isStartFocused ? this.primaryColor : Color.Gray) // 设置边框颜色 .borderWidth(1) // 设置边框宽度 .borderRadius(10) // 设置圆角半径为 10 .backgroundColor(Color.White) // 设置背景颜色为白色 .showUnderline(false) // 不显示下划线 .onBlur(() => this.isStartFocused = false) // 失去焦点时设置聚焦状态为 false .onFocus(() => this.isStartFocused = true); // 获得焦点时设置聚焦状态为 true Line().width(10); // 创建分隔符,设置宽度为 10 像素 TextInput({ text: $$this.startLatitude, placeholder: '纬度' }) // 创建起点纬度输入框 .caretColor(this.primaryColor) // 设置光标颜色为主题颜色 .layoutWeight(1) // 设置布局权重 .type(InputType.NUMBER_DECIMAL) // 设置输入类型为小数 .placeholderColor(this.isEndFocused ? this.primaryColor : Color.Gray) // 设置占位符颜色 .fontColor(this.isEndFocused ? this.primaryColor : this.fontColor) // 设置字体颜色 .borderColor(this.isEndFocused ? this.primaryColor : Color.Gray) // 设置边框颜色 .borderWidth(1) // 设置边框宽度 .borderRadius(10) // 设置圆角半径为 10 .backgroundColor(Color.White) // 设置背景颜色为白色 .showUnderline(false) // 不显示下划线 .onBlur(() => this.isEndFocused = false) // 失去焦点时设置聚焦状态为 false .onFocus(() => this.isEndFocused = true); // 获得焦点时设置聚焦状态为 true } // 终点输入 Text('终点') // 创建文本组件,显示“终点” .fontWeight(600) // 设置字体粗细为 600 .fontSize(18) // 设置字体大小为 18 .fontColor(this.fontColor) // 设置字体颜色为定义的字体颜色 .margin({ bottom: `${this.baseSpacing}lpx`, top: `${this.baseSpacing}lpx` }); // 设置上下边距 Row() { // 水平布局容器 TextInput({ text: $$this.endLongitude, placeholder: '经度' }) // 创建终点经度输入框 .caretColor(this.primaryColor) // 设置光标颜色为主题颜色 .layoutWeight(1) // 设置布局权重 .type(InputType.NUMBER_DECIMAL) // 设置输入类型为小数 .placeholderColor(this.isSecondStartFocused ? this.primaryColor : Color.Gray) // 设置占位符颜色 .fontColor(this.isSecondStartFocused ? this.primaryColor : this.fontColor) // 设置字体颜色 .borderColor(this.isSecondStartFocused ? this.primaryColor : Color.Gray) // 设置边框颜色 .borderWidth(1) // 设置边框宽度 .borderRadius(10) // 设置圆角半径为 10 .backgroundColor(Color.White) // 设置背景颜色为白色 .showUnderline(false) // 不显示下划线 .onBlur(() => this.isSecondStartFocused = false) // 失去焦点时设置聚焦状态为 false .onFocus(() => this.isSecondStartFocused = true); // 获得焦点时设置聚焦状态为 true Line().width(10); // 创建分隔符,设置宽度为 10 像素 TextInput({ text: $$this.endLatitude, placeholder: '纬度' }) // 创建终点纬度输入框 .caretColor(this.primaryColor) // 设置光标颜色为主题颜色 .layoutWeight(1) // 设置布局权重 .type(InputType.NUMBER_DECIMAL) // 设置输入类型为小数 .placeholderColor(this.isSecondEndFocused ? this.primaryColor : Color.Gray) // 设置占位符颜色 .fontColor(this.isSecondEndFocused ? this.primaryColor : this.fontColor) // 设置字体颜色 .borderColor(this.isSecondEndFocused ? this.primaryColor : Color.Gray) // 设置边框颜色 .borderWidth(1) // 设置边框宽度 .borderRadius(10) // 设置圆角半径为 10 .backgroundColor(Color.White) // 设置背景颜色为白色 .showUnderline(false) // 不显示下划线 .onBlur(() => this.isSecondEndFocused = false) // 失去焦点时设置聚焦状态为 false .onFocus(() => this.isSecondEndFocused = true); // 获得焦点时设置聚焦状态为 true } } .width('650lpx') // 设置输入区域宽度为 650 像素 .padding(`${this.baseSpacing}lpx`) // 设置内边距 .margin({ top: 20 }) // 设置上边距为 20 像素 .backgroundColor(Color.White) // 设置背景颜色为白色 .borderRadius(10) // 设置圆角半径为 10 .alignItems(HorizontalAlign.Start); // 设置子元素在交叉轴上的对齐方式 // 显示计算结果 Column() { // 垂直布局容器 Text() { // 文本组件 Span(`两点之间的距离是:`) // 创建文本片段,显示提示信息 Span(`${(this.distance / 1000).toFixed(2)} `).fontColor(this.primaryColor) // 创建文本片段,显示距离(公里),并设置颜色 Span(`公里`) // 创建文本片段,显示单位“公里” } .fontWeight(600) // 设置字体粗细为 600 .fontSize(18) // 设置字体大小为 18 .fontColor(this.fontColor); // 设置字体颜色为定义的字体颜色 } .width('650lpx') // 设置结果显示区域宽度为 650 像素 .backgroundColor(Color.White) // 设置背景颜色为白色 .borderRadius(10) // 设置圆角半径为 10 .padding(`${this.baseSpacing}lpx`) // 设置内边距 .margin({ top: `${this.baseSpacing}lpx` }) // 设置上边距 .alignItems(HorizontalAlign.Start); // 设置子元素在交叉轴上的对齐方式 } .height('100%') // 设置整个组件高度为 100% .width('100%') // 设置整个组件宽度为 100% .backgroundColor("#eff0f3"); // 设置背景颜色为浅灰色 } } 转载自https://www.cnblogs.com/zhongcx/p/18607899
-
又是一年开学、开工季人已在学校上课和工位上班脑子却在迷茫不知道做什么别再摸鱼啦!新的一年学习目标来啦!专为零基础小白提供的5门行业大会的HarmonyOS精品课程。这些课程由权威技术大咖讲解,深入浅出带你轻松理解鸿蒙应用基础概念,助力你全面掌握前沿技术,拥抱职业发展的无限可能!1.精品课程《鸿蒙:万物智联时代的操作系统》,请戳学习链接2.精品课程《鸿蒙应用开发:从Android到HarmonyOS》,请戳学习链接3.精品课程《鸿蒙分布式软总线的前世今生》,请戳学习链接4.精品课程《万物智联下的HarmonyOS开发框架》,请戳学习链接5.精品课程《Zelda :鸿蒙应用静态分析框架》,请戳学习链接
-
在开发HarmonyOS应用的过程中,从初始测试到最终上架,每一步都需要精心规划与实施。本文将按照实际开发流程,详细介绍从生成签名证书文件到完成小规模真机内测直至最终应用上架的各个步骤。官方交互式资料体验中心:AppGallery Connect交互式资料体验中心一、生成签名证书文件在开发阶段结束并准备进行真机测试或上架之前,首先需要生成签名证书文件。这是确保应用安全性的重要步骤之一。步骤如下:1. 生成密钥和证书请求文件(CSR)使用DevEco Studio中的“生成密钥和CSR”功能,输入相关信息以生成密钥文件和CSR文件。注意,填写信息时应避免使用中文字符,以免引起后续问题。参考:文档中心2. 上传CSR文件生成证书登录AppGallery Connect平台,上传之前生成的CSR文件,以生成相应的证书文件(.cer)。参考: 文档中心3. 生成P7B文件再次登录AppGallery Connect,进入“Profile”页面,使用之前生成的.cer文件生成.p7b文件。参考:文档中心4. 配置签名信息在DevEco Studio中配置签名信息:依次选择File > Project Structure > Signing Configs,在此处指定之前生成的密钥文件及其密码。二、小规模真机内测方案完成签名证书文件的准备工作之后,下一步是进行小规模的真机内测。这有助于在正式发布前发现并解决潜在的问题。方案包括但不限于:1. 开放式测试• 打包方式:使用发布证书打包成.app格式的安装包。• 上传流程:登录AppGallery Connect上传安装包。• 用户安装:用户收到邀请后,可以在真机上安装并使用该应用。• 前置条件:打包时需要使用发布证书,打.app格式;软件著作权、备案并填写相关信息,因为上传测试包也需要提交审核资质。参考:文档中心2. 发布企业内部应用• 打包方式:使用企业证书打包成.hap格式的安装包。• 上传流程:登录AppGallery Connect上传安装包。• 用户安装:用户收到邀请后,可以在真机上安装并使用该应用。• 前置条件:打包时需要使用企业证书,打.hap格式;需要额外准备一个账号,并填写申请信息,包括企业全称、应用信息、申请原因、使用人数等。不支持元服务也不支持个人申请。参考:文档中心3. DevEco Testing命令安装• 打包方式:使用调试证书打包成.hap格式的安装包。• 上传流程:通过DevEco Testing命令app install -r <路径>\xx.hap安装。• 用户安装:用户通过命令直接安装测试包。• 前置条件:打包时需要使用调试证书,打.hap格式;安装DevEco Testing软件,手机开启开发者模式。参考:下载中心 | 华为开发者联盟-HarmonyOS开发者官网,共建鸿蒙生态三、应用上架完成内测并确认应用无误后,接下来就是正式上架的步骤。流程如下:1. 构建并打包APP• 通过DevEco Studio的“Build > Build Hap(s)/APP(s) > Build APP(s)”菜单构建APP。构建完成后,在项目的相应目录下找到.app格式的正式签名安装包,用于上传至应用商店。2. 上传至AppGallery Connect参考:AppGallery Connect• 登录AppGallery Connect,上传之前构建好的.app格式安装包。• 按照提示填写应用的相关信息,如应用名称、描述、截图等。3. 提交审核• 提交应用后,等待华为团队的审核。审核期间,保持通讯畅通以便及时回应可能出现的问题。4. 完成上架• 审核通过后,应用正式上架,用户可以在AppGallery中搜索并下载使用。四、所需资料在整个流程中,还需要准备一些必要的资料:1. APP备案根据华为官方指南,APP备案需通过华为云、阿里云或腾讯云等接入服务商完成。填写备案信息时,重点关注包名、公钥、签名信息(证书MD5指纹)等。参考:文档中心2. 电子版软件著作权准备好《软件版权申请信息采集表.doc》、《源代码.doc》、《xxxapp 操作手册.doc》等相关文档,以供申请电子版软件著作权。转载自https://www.cnblogs.com/zhongcx/articles/18444615
-
随着HarmonyOS的发展,API版本的更新带来了许多新的特性和限制。在API 11及以后的版本中,直接赋值对象的语法不再被支持,这要求开发者们采用新的方式来处理对象的创建和属性的访问。同时,HarmonyOS支持ETS(Enhanced TypeScript)文件,这是一种扩展了TypeScript的文件格式,用于更好地支持HarmonyOS的特性。然而,ETS文件并不支持所有的TypeScript语法特性,这就需要开发者灵活运用不同的文件格式来实现所需的功能。【完整示例】src/main/ets/common/FactoryUtil.ts123export function createInstance<T>(constructor: new () => T): T { return new constructor();} src/main/ets/pages/Index.ets123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100import { createInstance } from '../common/FactoryUtil.ts'; class Person { name: string = '张三';} class Person2 { name: string = '李四';} function sum(a: number, b: number) { console.info(`${a} + ${b} = ${a + b}`);} function subtract(a: number, b: number) { console.info(`${a} - ${b} = ${a - b}`);} function printString(str: string) { console.info(str);} class BirthInfo { birthDate: string = "2020-02-03";} class UserInfo { userName: string = ''; birthInfo?: BirthInfo = new BirthInfo(); calculateAge = (): number | string => { if (!this.birthInfo) { return '数据异常'; } const today = new Date(); const birthDate = new Date(this.birthInfo.birthDate); const age = today.getFullYear() - birthDate.getFullYear(); return age; };} class Action { description: string; action: Function; constructor(description: string, action: Function) { this.description = description; this.action = action; }} @Entry@Componentstruct MainPage { @State actions: Action[] = [ new Action('加法计算', sum), new Action('减法计算', subtract), new Action('打印字符串', printString), ]; build() { Column({ space: 10 }) { Button('创建对象并获取属性').onClick(() => { const person: object = Object({ name: '张三', age: 30 }); console.info(`person['name']:${person['name']}`); console.info(`person['age']:${person['age']}`); }); Button('执行动作').onClick(() => { this.actions.forEach(action => { if (action.description.includes('加法')) { action.action(1, 2); } else if (action.description.includes('减法')) { action.action(1, 2); } else if (action.description.includes('打印')) { action.action('Hello World'); } }); }); Button('从TS文件创建实例').onClick(() => { const person1 = createInstance(Person); console.info('person1.name', person1.name); const person2 = createInstance(Person2); console.info('person2.name', person2.name); }); Button('获取用户信息').onClick(() => { const userInfo = new UserInfo(); Object.keys(userInfo).forEach(key => { console.info(`key: ${key}`); }); console.info(`年龄: ${userInfo.calculateAge()}`); }); } .width('100%') .height('100%'); }} 打印1234567891011121314person['name']:张三person['age']:30 1 + 2 = 31 - 2 = -1Hello World person1.name 张三person2.name 李四 key: userNamekey: birthInfokey: calculateAge年龄: 4 技术要点解析1. 创建对象并获取属性使用Object()创建对象并使用索引访问属性,以确保兼容性与正确性。123const person:object = Object({ name: '张三', age: 30 });console.info(`person['name']:${person['name']}`);console.info(`person['age']:${person['age']}`); 2. 执行动作将方法定义为对象属性,并通过动态方式调用它们。123456this.actions.forEach(action => { if (action.description.includes('加法')) { action.action(1, 2); } // 其他条件分支...}); 3. 从TS文件创建实例在某些情况下,ETS文件不支持特定的TypeScript语法特性,如new () => T语法。这时可以将这部分逻辑移到TS文件中,并在ETS文件中导入使用。例如,创建一个通用的工厂函数来实例化类:12const person1 = createInstance(Person);console.info('person1.name', person1.name); 4. 遍历对象的属性可以使用Object.keys()遍历对象的属性,并利用面向对象的思想通过实例方法计算年龄。123456const userInfo = new UserInfo();Object.keys(userInfo).forEach(key => { console.info(`key: ${key}`);}); console.info(`年龄: ${userInfo.calculateAge()}`); 结论本文介绍了HarmonyOS应用开发中的几个关键技巧,包括使用Object()创建对象、动态调用方法、使用TS文件中的工厂函数创建实例,以及遍历对象属性。通过遵循良好的命名规范和代码组织结构,可以使代码更加清晰易懂,便于后期维护。掌握了这些技巧后,开发者能够更加高效地开发出高质量的HarmonyOS应用程序。转载自https://www.cnblogs.com/zhongcx/articles/18445830
-
/** * 根据比例混合两个十六进制颜色值。 * @param colorA 第一个颜色的十六进制值,例如红色 '#ff0000'。 * @param colorB 第二个颜色的十六进制值,例如黑色 '#000000'。 * @param ratio 混合比例,0 表示仅返回 colorA,1 表示仅返回 colorB,介于 0 和 1 之间的值会混合两个颜色。 * @returns 混合后的颜色的十六进制值。 */ function mixColors(colorA: string, colorB: string, ratio: number): string { let mix = (a: number, b: number, ratio: number) => Math.round(a + (b - a) * ratio).toString(16).padStart(2, '0'); let a = parseInt(colorA.slice(1), 16); let b = parseInt(colorB.slice(1), 16); let red = mix((a >> 16) & 255, (b >> 16) & 255, ratio); let green = mix((a >> 8) & 255, (b >> 8) & 255, ratio); let blue = mix(a & 255, b & 255, ratio); return `#${red}${green}${blue}`; } function convertDecimalColorToHex(decimalColorValue: number): string { if (decimalColorValue < 0 || decimalColorValue > 0xFFFFFFFF) { throw new Error('Color value must be within the range of 0 to 0xFFFFFFFF.'); } const paddedHexColor = ('00000000' + decimalColorValue.toString(16).toUpperCase()).slice(-8); return '#' + paddedHexColor.slice(-6); } @Entry @Component struct Index { @State resourceColor: Resource | undefined = undefined @State convertedColorHex: string = "" @State displayText: string = "测试测试" aboutToAppear(): void { this.resourceColor = $r(`app.color.start_window_background`); const colorFromResourceManager = getContext(this).resourceManager.getColorByNameSync('start_window_background'); console.info('从ResourceManager获取的颜色(十进制):', colorFromResourceManager); this.convertedColorHex = convertDecimalColorToHex(colorFromResourceManager); console.info('转换后的十六进制颜色:', this.convertedColorHex); } build() { Column() { Text('测试1').backgroundColor('rgba(0, 0, 0, 0.5)') Text('测试2').backgroundColor('#80000000') Text(mixColors('#ff0000', '#000000', 0)) .fontColor(Color.White) .backgroundColor(mixColors('#ff0000', '#000000', 0)) Text(mixColors('#ff0000', '#000000', 1)) .fontColor(Color.White) .backgroundColor(mixColors('#ff0000', '#000000', 1)) Text(mixColors('#ff0000', '#000000', 0.5)) .fontColor(Color.White) .backgroundColor(mixColors('#ff0000', '#000000', 0.5)) Text(mixColors('#ff0000', '#000000', 0.8)) .fontColor(Color.White) .backgroundColor(mixColors('#ff0000', '#000000', 0.8)) Text(this.displayText) .backgroundColor($r(`app.color.start_window_background`)) .fontSize(50) .fontWeight(FontWeight.Bold) Text(this.displayText) .backgroundColor(this.resourceColor) .fontSize(50) .fontWeight(FontWeight.Bold) Text(this.displayText) .backgroundColor(this.convertedColorHex) .fontSize(50) .fontWeight(FontWeight.Bold) } .height('100%') .width('100%') .backgroundColor(Color.Orange) } }在HarmonyOS应用开发中,颜色设置是一项基本但重要的功能。本文将介绍在ArkTS中如何设置颜色的透明度,并探讨如何在两种颜色之间进行混合以得到新的颜色值。一、设置颜色透明度在HarmonyOS的ArkTS框架中,可以通过多种方式来设置颜色的透明度。以下是两种常见的方法:1. 使用RGBA格式:通过直接指定RGBA值来设置颜色的透明度。1Text('测试1').backgroundColor('rgba(0, 0, 0, 0.5)')2. 使用十六进制颜色值:通过在颜色值前加上表示透明度的十六进制数来设置透明度。1Text('测试2').backgroundColor('#80000000')在此示例中,80表示半透明,00表示不透明,ff表示完全透明。二、颜色混合方案在一些应用场景中,需要实现两种颜色之间的混合效果。下面是一个示例,演示如何根据给定的比例混合两种颜色:/** * 根据比例混合两个十六进制颜色值。 * @param colorA 第一个颜色的十六进制值,例如红色 '#ff0000'。 * @param colorB 第二个颜色的十六进制值,例如黑色 '#000000'。 * @param ratio 混合比例,0 表示仅返回 colorA,1 表示仅返回 colorB,介于 0 和 1 之间的值会混合两个颜色。 * @returns 混合后的颜色的十六进制值。 */ mixColors(colorA: string, colorB: string, ratio: number): string { let mix = (a: number, b: number, ratio: number) => Math.round(a + (b - a) * ratio).toString(16).padStart(2, '0'); let a = parseInt(colorA.slice(1), 16); let b = parseInt(colorB.slice(1), 16); let red = mix((a >> 16) & 255, (b >> 16) & 255, ratio); let green = mix((a >> 8) & 255, (b >> 8) & 255, ratio); let blue = mix(a & 255, b & 255, ratio); return `#${red}${green}${blue}`; }三、解决预览器颜色显示问题在使用resourceManager获取颜色值时,可能会遇到在预览器中无法正确显示颜色的问题。为了避免这类问题,建议直接定义颜色常量:如果只是简单的颜色值,建议直接定义字符串类颜色值,如"#000000",这样预览器也能正确显示颜色转载自https://www.cnblogs.com/zhongcx/articles/18447243
上滑加载中
推荐直播
-
HDC深度解读系列 - Serverless与MCP融合创新,构建AI应用全新智能中枢2025/08/20 周三 16:30-18:00
张昆鹏 HCDG北京核心组代表
HDC2025期间,华为云展示了Serverless与MCP融合创新的解决方案,本期访谈直播,由华为云开发者专家(HCDE)兼华为云开发者社区组织HCDG北京核心组代表张鹏先生主持,华为云PaaS服务产品部 Serverless总监Ewen为大家深度解读华为云Serverless与MCP如何融合构建AI应用全新智能中枢
回顾中 -
关于RISC-V生态发展的思考2025/09/02 周二 17:00-18:00
中国科学院计算技术研究所副所长包云岗教授
中科院包云岗老师将在本次直播中,探讨处理器生态的关键要素及其联系,分享过去几年推动RISC-V生态建设实践过程中的经验与教训。
回顾中 -
一键搞定华为云万级资源,3步轻松管理企业成本2025/09/09 周二 15:00-16:00
阿言 华为云交易产品经理
本直播重点介绍如何一键续费万级资源,3步轻松管理成本,帮助提升日常管理效率!
回顾中
热门标签