9.案例

163邮箱邮件发送

代码链接:https://pan.wo.cn/s/1J1c4w90901 提取码:1Spc

参考博客:https://developer.huawei.com/consumer/cn/blog/topic/03148160059378015

在module.json5配置文件加上对权限的声明:

"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
}
]
// 导入所需的模块
import socket from '@ohos.net.socket'; // 网络套接字模块,用于TCP/TLS通信
import util from '@ohos.util'; // 工具模块,用于Base64编码/解码等

// 接口定义
interface LocalAddress {
address: string; // 本地绑定地址
family: number; // 地址族(IPv4=1, IPv6=2)
}

interface ServerAddress {
address: string; // 服务器地址
port: number; // 服务器端口
family: number; // 地址族
}

interface MessageValue {
message: ArrayBuffer; // 接收到的消息数据(二进制格式)
}

interface SecureOptions {
protocols: socket.Protocol[]; // 支持的TLS协议版本
cipherSuite: string; // 加密套件配置
}

interface ConnectOptions {
address: ServerAddress; // 服务器地址信息
secureOptions: SecureOptions; // TLS安全选项
}

// 全局TLSSocket实例
let tlsSocket: socket.TLSSocket | null = null;

@Entry
@Component
struct Index {
bindLocal: boolean = false // Socket是否已绑定本地地址
isServerResponse: boolean = false // 是否收到服务器响应
@State serverAddr: string = "smtp.163.com" // SMTP服务器地址
@State serverPort: number = 465 // SMTP TLS端口
@State userName: string = "staseaiot@163.com" // 邮箱用户名
@State passwd: string = "DAtTEFxcvXt76ByV" // 邮箱授权码(不是登录密码)
@State rcptList: string = "1970884166@qq.com" // 收件人列表(逗号分隔)
@State mailTitle: string = "意见反馈" // 邮件标题
@State mailFrom: string = "staseaiot@163.com" // 发件人邮箱
@State mailContent: string = "This is greeting from Harmony OS" // 邮件内容
@State canSend: boolean = false // 是否可以发送邮件(登录成功后设为true)

// UI构建函数
build() {
Row() {
Column() {
Text("邮件发送客户端(163邮箱 - 465 TLS)")
.fontSize(14)
.fontWeight(FontWeight.Bold)
.width('100%')
.textAlign(TextAlign.Center)
.padding(10)

// 服务器地址输入
Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
Text("邮箱服务器地址:")
.width(120)
.fontSize(14)
.flexGrow(0)
TextInput({ text: this.serverAddr })
.onChange((value) => { this.serverAddr = value })
.width(110)
.fontSize(12)
.flexGrow(1)
}.width('100%').padding(5)

// 服务器端口输入
Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
Text("邮箱服务器端口:")
.width(120)
.fontSize(14)
.flexGrow(0)
TextInput({ text: this.serverPort.toString() })
.type(InputType.Number)
.onChange((value) => { this.serverPort = parseInt(value) })
.width(110)
.fontSize(12)
.flexGrow(1)
}.width('100%').padding(5)

// 用户名输入
Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
Text("邮箱用户名:")
.width(90)
.fontSize(14)
.flexGrow(0)
TextInput({ text: this.userName })
.onChange((value) => { this.userName = value })
.width(110)
.fontSize(12)
.flexGrow(1)
}.width('100%').padding(5)

// 密码输入和登录按钮
Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
Text("登录密码(授权码):")
.width(130)
.fontSize(14)
.flexGrow(0)
TextInput({ text: this.passwd })
.onChange((value) => { this.passwd = value })
.width(100)
.fontSize(12)
.flexGrow(1)

Button("登录")
.onClick(() => { this.login() })
.width(70)
.fontSize(14)
.flexGrow(0)
}.width('100%').padding(5)

// 收件人输入
Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
Text("收件人邮箱:")
.width(90)
.fontSize(14)
.flexGrow(0)
TextInput({ placeholder: "多个使用逗号分隔", text: this.rcptList })
.onChange((value) => { this.rcptList = value })
.width(110)
.fontSize(12)
.flexGrow(1)
}.width('100%').padding(5)

// 邮件标题输入
Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
Text("标题:")
.width(50)
.fontSize(14)
.flexGrow(0)
TextInput({ text: this.mailTitle })
.onChange((value) => { this.mailTitle = value })
.width(110)
.fontSize(12)
.flexGrow(1)
}.width('100%').padding(5)

// 发件人邮箱输入
Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
Text("发件人邮箱:")
.width(90)
.fontSize(14)
.flexGrow(0)
TextInput({ text: this.mailFrom })
.onChange((value) => { this.mailFrom = value })
.width(110)
.fontSize(12)
.flexGrow(1)
}.width('100%').padding(5)

// 邮件内容输入和发送按钮
Flex({ justifyContent: FlexAlign.Start, direction: FlexDirection.Column, alignItems: ItemAlign.Center }) {
Text("邮件内容:")
.width('100%')
.fontSize(14)

TextArea({ text: this.mailContent })
.onChange((value) => { this.mailContent = value })
.width('100%')
.height(120)
.fontSize(12)

Button("发送")
.enabled(this.canSend) // 仅在登录成功后可用
.onClick(() => { this.sendMail() })
.width(80)
.fontSize(14)
}
.flexGrow(1)
.width('100%')
.padding(5)
.height('100%')
}
.width('100%')
.justifyContent(FlexAlign.Start)
.height('100%')
.padding(10)
}
.height('100%')
}

// 创建新的TLSSocket实例
createNewSocket() {
if (tlsSocket) {
try { tlsSocket.close(); } catch (e) {}
}
tlsSocket = socket.constructTLSSocketInstance();
this.bindLocal = false;
}

// 绑定Socket到本地地址并设置监听器
async bindSocket() {
this.createNewSocket();

try {
let localAddress: LocalAddress = { address: "0.0.0.0", family: 1 }; // 绑定到所有IPv4地址
await tlsSocket!.bind(localAddress);
console.log("C: bind success");
this.bindLocal = true;
} catch (e) {
console.error(`C: bind fail - ${e.message || e}`);
}

// 监听服务器消息
tlsSocket!.on("message", (value: MessageValue) => {
this.isServerResponse = true;
let msg = buf2String(value.message);
console.log(`S: ${msg}`);
});

// 监听错误事件
tlsSocket!.on("error", (err) => {
console.error(`E: Socket 错误 - ${err.message || err.code || '未知 TLS 错误'}`);
console.error("详细错误对象:", err);
this.cleanupSocket();
});

// 监听连接关闭事件
tlsSocket!.on("close", () => {
console.log("C: 连接已被服务器断开(可能是空闲超时)");
console.log("C: 发送按钮已禁用,请重新点击'登录'或'发送'来重连");
this.canSend = false;
this.cleanupSocket();
});
}

// 清理Socket资源
cleanupSocket() {
if (tlsSocket) {
try { tlsSocket.close(); } catch (e) {}
tlsSocket = null;
}
this.bindLocal = false;
this.isServerResponse = false;
}

// SMTP登录流程
async login() {
try {
this.cleanupSocket();
await this.bindSocket();

let serverAddress: ServerAddress = { address: this.serverAddr, port: this.serverPort, family: 1 };

// 配置TLS选项
const secureOptions: SecureOptions = {
protocols: [socket.Protocol.TLSv12, socket.Protocol.TLSv13], // 支持TLS 1.2和1.3
cipherSuite: 'TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256' // 加密套件
};

const connectOptions: ConnectOptions = {
address: serverAddress,
secureOptions: secureOptions
};

// 连接SMTP服务器
console.log(`C: 正在连接 ${this.serverAddr}:${this.serverPort} (TLS)...`);
await tlsSocket!.connect(connectOptions);
console.log("C: TLS 连接成功!");

// 等待服务器欢迎消息(220)
await this.wait4ServerResponse(10000);
if (!this.isServerResponse) {
throw new Error("未收到 220 欢迎消息");
}

// SMTP握手流程
await this.exeCmdAndWait4Response("EHLO harmonyos.next"); // 发送EHLO命令
await this.exeCmdAndWait4Response("AUTH LOGIN"); // 开始认证

// Base64编码的用户名和密码
let loginName = string2Base64(this.userName);
await this.exeCmdAndWait4Response(loginName);

let passWd = string2Base64(this.passwd);
await this.exeCmdAndWait4Response(passWd);

if (this.isServerResponse) { // 简化判断(实际应检查235响应码)
this.canSend = true;
console.log("C: 登录成功!(235 Authentication successful)");
console.log("C: 可以开始发送邮件");
} else {
throw new Error("登录失败,请检查邮箱/授权码");
}

} catch (e) {
console.error(`C: 登录失败 - ${e.message || e}`);
this.cleanupSocket();
}
}

// 发送邮件
async sendMail() {
if (!this.canSend || !tlsSocket) {
console.log("C: 连接不可用,正在自动尝试重连...");
await this.login();
if (!this.canSend) return;
}

try {
console.log("C: 开始发送邮件...");

// SMTP发送流程
await this.exeCmdAndWait4Response(`MAIL FROM:<${this.mailFrom}>`); // 发件人

// 收件人列表(支持多个)
let rcptMails = this.rcptList.split(',');
for (let rcpt of rcptMails) {
rcpt = rcpt.trim();
if (rcpt) await this.exeCmdAndWait4Response(`RCPT TO:<${rcpt}>`);
}

await this.exeCmdAndWait4Response("DATA"); // 准备发送数据

// 构建邮件头
let mailBody = `From: ${this.mailFrom}\r\n`;
mailBody += `To: ${this.rcptList}\r\n`;
mailBody += `Subject: =?utf-8?B?${string2Base64(this.mailTitle)}?=\r\n`; // Base64编码标题
mailBody += `Date: ${new Date().toUTCString()}\r\n`;
mailBody += `Content-Type: text/plain; charset="utf-8"\r\n`;
mailBody += `Content-Transfer-Encoding: base64\r\n\r\n`;
mailBody += `${string2Base64(this.mailContent)}\r\n`; // Base64编码内容
mailBody += ".\r\n"; // SMTP数据结束标记

// 分块发送邮件内容(避免大包)
const chunkSize = 2048;
for (let i = 0; i < mailBody.length; i += chunkSize) {
const chunk = mailBody.substring(i, i + chunkSize);
await tlsSocket!.send(chunk);
console.log(`C: [chunk sent, length=${chunk.length}]`);
}

// 等待服务器确认
await this.wait4ServerResponse(15000);
if (!this.isServerResponse) {
throw new Error("服务器未确认邮件接收");
}

console.log("C: 邮件发送成功!连接保持打开,可继续发送下一封");

} catch (e) {
console.error(`C: 发送失败 - ${e.message || e}`);
this.cleanupSocket();
}
}

// 执行SMTP命令并等待响应
async exeCmdAndWait4Response(cmd: string) {
if (!tlsSocket) {
throw new Error("Socket 未初始化");
}

this.isServerResponse = false;
let fullCmd = cmd + "\r\n"; // SMTP命令以CRLF结束
try {
await tlsSocket.send(fullCmd);
console.log(`C: ${cmd}`);
} catch (sendErr) {
console.error(`C: 发送命令失败 - ${sendErr.message || sendErr}`);
throw new Error(sendErr.message || sendErr);
}

await this.wait4ServerResponse(10000);
}

// 等待服务器响应(轮询方式)
async wait4ServerResponse(timeoutMs: number = 10000) {
const start = Date.now();
while (!this.isServerResponse) {
if (Date.now() - start > timeoutMs) {
throw new Error(`等待响应超时 (${timeoutMs}ms)`);
}
await sleep(100); // 每100毫秒检查一次
}
}
}

// Base64 编码函数
function string2Base64(src: string): string {
try {
let textEncoder = new util.TextEncoder();
let encodeValue = textEncoder.encodeInto(src); // 字符串转Uint8Array
let tool = new util.Base64Helper();
return tool.encodeToStringSync(encodeValue); // Base64编码
} catch (e) {
console.error("Base64 失败:", e);
return "";
}
}

// ArrayBuffer 转字符串函数
function buf2String(buf: ArrayBuffer): string {
try {
let msgArray = new Uint8Array(buf);
let textDecoder = util.TextDecoder.create("utf-8");
return textDecoder.decodeWithStream(msgArray); // UTF-8解码
} catch (e) {
console.error("解码失败:", e);
return "[解码失败]";
}
}

// 异步休眠函数
function sleep(time: number) {
return new Promise<void>((resolve) => setTimeout(resolve, time));
}

天气

代码链接:https://pan.wo.cn/s/1X1A4u98932 提取码:FxuX

位置定位:https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-positioning#section1774418211717

在module.json5中添加网络访问权限:

"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
}
]

utils/HttpUtil.ts

// utils/HttpUtil.ts
import http from '@ohos.net.http';
import util from '@ohos.util';

interface ZlibModule {
unzip(data: ArrayBuffer, callback: (err: Error, result: ArrayBuffer) => void): void;
}

declare function requireNapi(moduleName: string): ZlibModule;

/**
* 检查数据是否为 GZIP 格式
*/
function isGzipData(data: ArrayBuffer | Uint8Array): boolean {
const uint8Array = data instanceof Uint8Array ? data : new Uint8Array(data);
return uint8Array.length >= 2 && uint8Array[0] === 0x1F && uint8Array[1] === 0x8B;
}

/**
* 使用鸿蒙系统 zlib 解压 GZIP 数据
*/
async function ungzipData(compressedData: ArrayBuffer): Promise<string> {
return new Promise((resolve, reject) => {
try {
const zlib: ZlibModule = requireNapi('zlib');
zlib.unzip(compressedData, (err: Error, result: ArrayBuffer) => {
if (err) {
reject(err);
return;
}
const decoder = new util.TextDecoder('utf-8');
const uint8Result = new Uint8Array(result);
const output = decoder.decodeWithStream(uint8Result);
resolve(output);
});
} catch (error) {
console.error('zlib 加载失败', error);
reject(new Error('zlib 解压失败'));
}
});
}

/**
* 网络请求工具类 - 使用 @ohos.net.http,支持 GZIP 自动解压或手动处理
* @param url 请求地址
* @returns 响应数据
*/
export function request<T>(url: string): Promise<T> {
return new Promise<T>((resolve, reject) => {
console.log('发起请求:', url);
const httpRequest = http.createHttp();
httpRequest.request(url, {
method: http.RequestMethod.GET,
header: {
'Accept-Encoding': 'gzip', // 允许压缩
}
// 鸿蒙http模块不支持responseType参数
}).then(response => {
console.log('请求响应码:', response.responseCode);
console.log('响应数据类型:', typeof response.result);
if (response.responseCode === 200) {
try {
let dataStr: string;

// 处理数据的函数
const processData = () => {
try {
// 解析 JSON
console.log('尝试解析 JSON,数据前50字符:', dataStr.substring(0, 50));
const result = JSON.parse(dataStr) as T;
console.log('JSON 解析成功');
resolve(result);
} catch (error) {
console.error('处理响应失败:', error);
console.log('原始数据前200:', (response.result as string)?.substring(0, 200));
reject(new Error('处理响应失败: ' + error.message));
}
};

if (typeof response.result === 'string') {
console.log('收到字符串数据,长度:', response.result.length);
// 系统已自动解压,直接使用
dataStr = response.result;
processData();
} else if (response.result instanceof ArrayBuffer) {
// 如果是 ArrayBuffer,手动处理
console.log('收到 ArrayBuffer 数据,长度:', response.result.byteLength);
const uint8 = new Uint8Array(response.result);
if (isGzipData(uint8)) {
ungzipData(response.result).then(decompressed => {
dataStr = decompressed;
// 继续处理逻辑
processData();
}).catch((error: Error) => {
reject(new Error('解压失败: ' + error.message));
return;
});
return; // 等待异步操作完成
} else {
const decoder = new util.TextDecoder('utf-8');
dataStr = decoder.decodeWithStream(uint8);
// 继续处理逻辑
processData();
}
} else {
reject(new Error('未知响应数据类型'));
return;
}
// ArrayBuffer数据在各自的处理分支中调用processData()
} catch (error) {
console.error('数据处理失败:', error);
reject(new Error('数据处理失败: ' + error.message));
}
} else {
reject(new Error(`请求失败,状态码:${response.responseCode}`));
}
}).catch((error: Error) => {
console.error('网络请求失败:', error);
reject(new Error('网络请求失败: ' + error.message));
}).finally(() => {
httpRequest.destroy(); // 必须销毁
});
});
}

service/WeatherService.ts

// service/WeatherService.ts
import {
DailyForecast,
RealTimeWeather,
WeatherResponse,
LocationResponse,
RealTimeWeatherResponse,
DailyForecastResponse,
LocationItem
} from "../model/WeatherModel";
import { request } from "../utils/HttpUtil";

// 和风天气API配置
const API_KEY = 'dc6b637284d547fb9427f5caf4475554';
const API_HOST = 'https://q53yfrtevq.re.qweatherapi.com'; // 你的专属域名

/**
* 根据城市获取天气数据
* @param city 城市名称
*/
export async function getWeatherByCity(city: string): Promise<WeatherResponse> {
try {
// 1. 获取城市Location ID - 移除禁用压缩参数,让服务器正常 gzip,fetch 自动处理
const baseParams = `location=${encodeURIComponent(city)}&key=${API_KEY}`;
const locationUrl = `${API_HOST}/geo/v2/city/lookup?${baseParams}`;
console.log('请求位置URL:', locationUrl);

const locationRes = await request<LocationResponse>(locationUrl);
console.log('位置响应成功');

if (!locationRes.location || locationRes.location.length === 0) {
throw new Error('未找到该城市的天气数据');
}

const locationId = locationRes.location[0].id;
console.log('找到城市ID:', locationId, '城市名:', locationRes.location[0].name);

// 2. 获取实时天气 - 移除禁用压缩参数
const weatherParams = `location=${locationId}&key=${API_KEY}`;
const realTimeUrl = `${API_HOST}/v7/weather/now?${weatherParams}`;
console.log('请求实时天气URL:', realTimeUrl);

const realTimeRes = await request<RealTimeWeatherResponse>(realTimeUrl);
console.log('实时天气响应成功');

// 3. 获取未来3天预报
const dailyUrl = `${API_HOST}/v7/weather/3d?${weatherParams}`;
console.log('请求天气预报URL:', dailyUrl);

const dailyRes = await request<DailyForecastResponse>(dailyUrl);
console.log('天气预报响应成功');

// 数据转换(保持不变)
const realTime: RealTimeWeather = {
temp: realTimeRes.now.temp,
text: realTimeRes.now.text,
windDir: realTimeRes.now.windDir,
windScale: realTimeRes.now.windScale,
humidity: realTimeRes.now.humidity,
updateTime: realTimeRes.updateTime
};

const dailyForecasts: DailyForecast[] = dailyRes.daily.map((item: DailyForecast): DailyForecast => ({
fxDate: item.fxDate,
tempMax: item.tempMax,
tempMin: item.tempMin,
textDay: item.textDay
}));

console.log('数据处理完成');
return { realTime, dailyForecasts };
} catch (error) {
console.error('获取天气失败:', error);
throw new Error(error.message);
}
}

model/WeatherModel.ts

// model/WeatherModel.ts
/**
* 实时天气数据模型
*/
export interface RealTimeWeather {
temp: string; // 温度
text: string; // 天气状况(晴/雨/多云)
windDir: string; // 风向
windScale: string; // 风力等级
humidity: string; // 湿度
updateTime: string; // 更新时间
}

/**
* 未来预报数据模型
*/
export interface DailyForecast {
fxDate: string; // 日期
tempMax: string; // 最高温
tempMin: string; // 最低温
textDay: string; // 白天天气状况
}

/**
* 天气响应体模型
*/
export interface WeatherResponse {
realTime: RealTimeWeather;
dailyForecasts: DailyForecast[];
}

/**
* 位置查询响应模型
*/
export interface LocationItem {
id: string;
name: string;
}

export interface LocationResponse {
location: LocationItem[];
}

/**
* 实时天气响应模型
*/
export interface RealTimeWeatherResponse {
now: RealTimeWeather;
updateTime: string;
}

/**
* 未来预报响应模型
*/
export interface DailyForecastResponse {
daily: DailyForecast[];
}

pages/WeatherPage.ts

import { DailyForecast, RealTimeWeather } from '../model/WeatherModel';
import { getWeatherByCity } from '../service/WeatherService';

@Entry
@Component
struct WeatherPage {
// 状态管理
@State private cityName: string = '北京'; // 默认查询北京
@State private inputCity: string = '';
@State private realTimeWeather: RealTimeWeather | null = null;
@State private dailyForecasts: DailyForecast[] = [];
@State private isLoading: boolean = false;
@State private errorMsg: string = '';

// 页面加载时初始化数据
aboutToAppear() {
this.fetchWeatherData(this.cityName);
}

build() {
Column() {
// 标题区域
Text('鸿蒙天气')
.fontSize(28)
.fontWeight(FontWeight.Bold)
.margin({ top: 20, bottom: 15 })
.alignSelf(ItemAlign.Center);

// 城市查询区域
Row({ space: 10 }) {
TextInput({ placeholder: '请输入城市名称...', text: this.inputCity })
.onChange((value: string) => {
this.inputCity = value;
})
.width('70%')
.height(45)
.border({ width: 1, radius: 8, color: '#E5E5E5' })
.padding({ left: 10 });

Button('查询')
.width('20%')
.height(45)
.backgroundColor('#007DFF')
.fontColor(Color.White)
.borderRadius(8)
.onClick(() => {
if (this.inputCity.trim()) {
this.fetchWeatherData(this.inputCity.trim());
}
});
}
.margin({ bottom: 20 })
.padding({ left: 15, right: 15 });

// 加载状态提示
if (this.isLoading) {
LoadingProgress()
.width(40)
.height(40)
.margin({ bottom: 20 })
.alignSelf(ItemAlign.Center);
}

// 错误提示
if (this.errorMsg) {
Text(this.errorMsg)
.fontSize(14)
.fontColor('#FF4D4F')
.margin({ bottom: 20 })
.alignSelf(ItemAlign.Center);
}

// 实时天气展示
if (this.realTimeWeather) {
Column() {
Text(`${this.cityName} 实时天气`)
.fontSize(20)
.fontWeight(FontWeight.Medium)
.margin({ bottom: 10 });

Row({ space: 20 }) {
Text(`${this.realTimeWeather.temp}°C`)
.fontSize(48)
.fontWeight(FontWeight.Bold);

Column() {
Text(this.realTimeWeather.text)
.fontSize(18)
.margin({ bottom: 5 });
Text(`更新时间:${this.formatTime(this.realTimeWeather.updateTime)}`)
.fontSize(12)
.fontColor('#999');
}
}
.margin({ bottom: 15 });

// 天气详情
Grid() {
GridItem() {
this.buildWeatherInfoItem('风向', `${this.realTimeWeather.windDir}`);
}
GridItem() {
this.buildWeatherInfoItem('风力', `${this.realTimeWeather.windScale}级`);
}
GridItem() {
this.buildWeatherInfoItem('湿度', `${this.realTimeWeather.humidity}%`);
}
}
.columnsTemplate('1fr 1fr 1fr')
.width('90%')
.margin({ bottom: 30 });
}
.padding(20)
.backgroundColor(Color.White)
.borderRadius(12)
.shadow({ radius: 4, color: '#00000010', offsetX: 0, offsetY: 2 })
.width('90%')
.alignSelf(ItemAlign.Center);
}

// 未来预报展示
if (this.dailyForecasts.length > 0) {
Text('未来3天预报')
.fontSize(18)
.fontWeight(FontWeight.Medium)
.margin({ bottom: 10, top: 20 })
.alignSelf(ItemAlign.Start)
.padding({ left: 15 });

List({ space: 10 }) {
ForEach(this.dailyForecasts, (item: DailyForecast) => {
ListItem() {
Row({ space: 10 }) {
Text(item.fxDate)
.width(80)
.fontSize(14);
Text(item.textDay)
.width(60)
.fontSize(14);
Text(`↑${item.tempMax}°C`)
.width(50)
.fontSize(14)
.fontColor('#FF4D4F');
Text(`↓${item.tempMin}°C`)
.width(50)
.fontSize(14)
.fontColor('#007DFF');
}
.padding(15)
.backgroundColor(Color.White)
.borderRadius(8)
.width('100%');
}
})
}
.padding({ left: 15, right: 15 })
.width('100%');
}
}
.width('100%')
.height('100%')
.backgroundColor('#F8F8F8');
}

/**
* 构建天气信息子项
*/
@Builder
private buildWeatherInfoItem(title: string, value: string) {
Column() {
Text(title)
.fontSize(14)
.fontColor('#999')
.margin({ bottom: 5 });
Text(value)
.fontSize(16)
.fontWeight(FontWeight.Medium);
}
.alignItems(HorizontalAlign.Center);
}

/**
* 格式化时间
*/
private formatTime(timeStr: string): string {
return timeStr.replace('T', ' ').substring(0, 16);
}

/**
* 获取天气数据
*/
private async fetchWeatherData(city: string) {
this.isLoading = true;
this.errorMsg = '';
try {
const weatherData = await getWeatherByCity(city);
this.cityName = city;
this.realTimeWeather = weatherData.realTime;
this.dailyForecasts = weatherData.dailyForecasts;
} catch (error) {
this.errorMsg = error.message || '获取天气数据失败,请重试';
console.error('获取天气失败:', error);
} finally {
this.isLoading = false;
}
}
}