带你玩转鸿蒙6 AI智能体开发：从0到1打造你的专属AI助手
大家好，我是V哥！今天要跟大家分享一个超级干货——如何在鸿蒙6（API21）上开发一个真正能用的AI智能体。不是那种玩具级别的Demo，而是能语音对话、能理解你意图、还能帮你干活的智能助手！一、为什么要在鸿蒙上做AI智能体？兄弟们，2026年了，AI Agent（智能体）绝对是最火的技术方向之一。什么是智能体？简单说就是：能感知、能思考、能行动的AI程序。鸿蒙6在AI这块可以说是下了血本：原生AI能力：MindSpore Lite端侧推理引擎语音能力：ASR语音识别 + TTS语音合成意图识别：智能理解用户需求大模型接入：轻松对接各种LLM API今天V哥就手把手带你做一个多模态AI智能助手，它能：✅ 语音唤醒，开口就能聊✅ 智能对话，接入大模型✅ 意图识别，理解你想干嘛✅ 执行任务，帮你打开应用、设置闹钟等✅ 多轮记忆，上下文连贯废话不多说，直接上代码！二、项目架构设计┌─────────────────────────────────────────────────────────────────┐ │ AI智能体架构（V哥设计） │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ 语音输入 │───▶│ 语音识别 │───▶│ 意图理解 │ │ │ │ (ASR) │ │ Engine │ │ Engine │ │ │ └─────────────┘ └─────────────┘ └──────┬──────┘ │ │ │ │ │ ▼ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ 语音输出 │◀───│ 回复生成 │◀───│ 对话管理 │ │ │ │ (TTS) │ │ (LLM) │ │ Agent │ │ │ └─────────────┘ └─────────────┘ └──────┬──────┘ │ │ │ │ │ ▼ │ │ ┌─────────────┐ │ │ │ 任务执行 │ │ │ │ Actions │ │ │ └─────────────┘ │ └─────────────────────────────────────────────────────────────────┘三、项目创建与配置步骤1：创建项目DevEco Studio → New Project → Empty Ability (Stage模型) → Project name: VGeAIAgent → Bundle name: com.vge.aiagent → Compile SDK: 5.0.0(API 12) 或更高步骤2：配置 module.json5{ "module": { "name": "entry", "type": "entry", "description": "$string:module_desc", "mainElement": "EntryAbility", "deviceTypes": ["phone", "tablet"], "deliveryWithInstall": true, "installationFree": false, "pages": "$profile:main_pages", "abilities": [ { "name": "EntryAbility", "srcEntry": "./ets/entryability/EntryAbility.ets", "description": "$string:EntryAbility_desc", "icon": "$media:icon", "label": "$string:EntryAbility_label", "startWindowIcon": "$media:startIcon", "startWindowBackground": "$color:start_window_background", "exported": true, "skills": [ { "entities": ["entity.system.home"], "actions": ["action.system.home"] } ] } ], "requestPermissions": [ { "name": "ohos.permission.MICROPHONE", "reason": "$string:mic_reason", "usedScene": { "abilities": ["EntryAbility"], "when": "inuse" } }, { "name": "ohos.permission.INTERNET", "reason": "$string:net_reason", "usedScene": { "abilities": ["EntryAbility"], "when": "always" } }, { "name": "ohos.permission.DISTRIBUTED_DATASYNC", "reason": "$string:sync_reason", "usedScene": { "abilities": ["EntryAbility"], "when": "always" } } ] } }四、核心代码实现1. 消息数据模型 (model/MessageModel.ets)// entry/src/main/ets/model/MessageModel.ets /** * V哥设计的消息模型 * 支持多种消息类型，为后续扩展预留空间 */ // 消息角色 export enum MessageRole { USER = 'user', // 用户消息 ASSISTANT = 'assistant', // AI助手消息 SYSTEM = 'system' // 系统消息 } // 消息类型 export enum MessageType { TEXT = 'text', // 文本消息 VOICE = 'voice', // 语音消息 ACTION = 'action', // 执行动作 THINKING = 'thinking' // 思考中 } // 意图类型 export enum IntentType { CHAT = 'chat', // 闲聊 OPEN_APP = 'open_app', // 打开应用 SET_ALARM = 'set_alarm', // 设置闹钟 SET_REMINDER = 'set_reminder', // 设置提醒 QUERY_WEATHER = 'query_weather', // 查询天气 QUERY_TIME = 'query_time', // 查询时间 CONTROL_DEVICE = 'control_device',// 控制设备 UNKNOWN = 'unknown' // 未知意图 } // 消息实体 export class Message { id: string = ''; role: MessageRole = MessageRole.USER; type: MessageType = MessageType.TEXT; content: string = ''; timestamp: number = 0; intent?: IntentType; intentParams?: Record<string, string>; isStreaming?: boolean; // 是否流式输出中 constructor(init?: Partial<Message>) { this.id = `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; this.timestamp = Date.now(); if (init) { Object.assign(this, init); } } } // 对话上下文（用于多轮对话） export class ConversationContext { messages: Message[] = []; maxHistory: number = 10; // 最多保留10轮对话 addMessage(message: Message): void { this.messages.push(message); // 超过限制则移除最早的消息 if (this.messages.length > this.maxHistory * 2) { this.messages = this.messages.slice(-this.maxHistory * 2); } } getHistory(): Message[] { return this.messages; } clear(): void { this.messages = []; } // 转换为LLM API需要的格式 toAPIFormat(): Array<{role: string, content: string}> { return this.messages .filter(m => m.type === MessageType.TEXT) .map(m => ({ role: m.role, content: m.content })); } }2. 意图识别引擎 (engine/IntentEngine.ets)// entry/src/main/ets/engine/IntentEngine.ets import { IntentType } from '../model/MessageModel'; /** * V哥的意图识别引擎 * 使用规则+关键词匹配，生产环境可接入NLU模型 */ interface IntentRule { intent: IntentType; keywords: string[]; patterns: RegExp[]; extractor?: (text: string) => Record<string, string>; } export class IntentEngine { private static instance: IntentEngine; private rules: IntentRule[] = []; private constructor() { this.initRules(); } static getInstance(): IntentEngine { if (!IntentEngine.instance) { IntentEngine.instance = new IntentEngine(); } return IntentEngine.instance; } /** * 初始化意图规则 */ private initRules(): void { this.rules = [ // 打开应用 { intent: IntentType.OPEN_APP, keywords: ['打开', '启动', '运行', '开启'], patterns: [ /打开(.+?)(?:应用|app|APP)?$/, /启动(.+)/, /帮我开(.+)/ ], extractor: (text: string) => { const appNames: Record<string, string> = { '相机': 'com.huawei.camera', '相册': 'com.huawei.photos', '设置': 'com.huawei.settings', '日历': 'com.huawei.calendar', '计算器': 'com.huawei.calculator', '备忘录': 'com.huawei.notes', '音乐': 'com.huawei.music', '视频': 'com.huawei.video', '浏览器': 'com.huawei.browser', '微信': 'com.tencent.mm', '支付宝': 'com.eg.android.AlipayGphone', '抖音': 'com.ss.android.ugc.aweme' }; for (const [name, bundleName] of Object.entries(appNames)) { if (text.includes(name)) { return { appName: name, bundleName: bundleName }; } } return {}; } }, // 设置闹钟 { intent: IntentType.SET_ALARM, keywords: ['闹钟', '叫我', '提醒我起床', '定个闹钟'], patterns: [ /(\d{1,2})[点:：](\d{0,2}).*(?:闹钟|叫我|起床)/, /(?:明天|后天)?(?:早上|上午|中午|下午|晚上)?(\d{1,2})[点:：]?(\d{0,2})?.*(?:闹钟|叫我)/, /设.*闹钟.*(\d{1,2})[点:：](\d{0,2})?/ ], extractor: (text: string) => { const timeMatch = text.match(/(\d{1,2})[点:：](\d{0,2})?/); if (timeMatch) { const hour = timeMatch[1]; const minute = timeMatch[2] || '00'; return { hour, minute }; } return {}; } }, // 设置提醒 { intent: IntentType.SET_REMINDER, keywords: ['提醒我', '别忘了', '记得'], patterns: [ /(\d+)(?:分钟|小时)后提醒我(.+)/, /提醒我(.+)/, /(\d{1,2})[点:：](\d{0,2})?提醒我(.+)/ ], extractor: (text: string) => { // 提取时间和内容 const minuteMatch = text.match(/(\d+)分钟后提醒我(.+)/); if (minuteMatch) { return { delayMinutes: minuteMatch[1], content: minuteMatch[2] }; } const hourMatch = text.match(/(\d+)小时后提醒我(.+)/); if (hourMatch) { return { delayMinutes: String(parseInt(hourMatch[1]) * 60), content: hourMatch[2] }; } const contentMatch = text.match(/提醒我(.+)/); if (contentMatch) { return { content: contentMatch[1] }; } return {}; } }, // 查询天气 { intent: IntentType.QUERY_WEATHER, keywords: ['天气', '下雨', '温度', '气温', '穿什么'], patterns: [ /(.+?)(?:的)?天气/, /(?:今天|明天|后天).*(?:天气|下雨|温度)/, /要不要带伞/ ], extractor: (text: string) => { const cityMatch = text.match(/(.{2,4}?)(?:的)?天气/); if (cityMatch && !['今天', '明天', '后天', '这里', '现在'].includes(cityMatch[1])) { return { city: cityMatch[1] }; } return { city: '北京' }; // 默认城市 } }, // 查询时间 { intent: IntentType.QUERY_TIME, keywords: ['几点', '时间', '日期', '星期几', '今天几号'], patterns: [ /现在几点/, /什么时间/, /今天.*(?:几号|星期几|周几)/ ], extractor: () => ({}) }, // 控制设备 { intent: IntentType.CONTROL_DEVICE, keywords: ['打开灯', '关灯', '开灯', '空调', '电视', '窗帘'], patterns: [ /(打开|关闭|开|关)(.+?)(?:灯|空调|电视|窗帘)/, /把(.+?)(打开|关闭|开|关)/, /(.+?)(?:调到|设置为?)(\d+)度/ ], extractor: (text: string) => { const actionMatch = text.match(/(打开|关闭|开|关)(.+)/); if (actionMatch) { return { action: actionMatch[1].includes('开') ? 'on' : 'off', device: actionMatch[2] }; } return {}; } } ]; } /** * 识别用户意图 */ recognize(text: string): { intent: IntentType; params: Record<string, string>; confidence: number } { const normalizedText = text.toLowerCase().trim(); for (const rule of this.rules) { // 关键词匹配 const keywordMatch = rule.keywords.some(kw => normalizedText.includes(kw)); // 正则匹配 const patternMatch = rule.patterns.some(pattern => pattern.test(normalizedText)); if (keywordMatch || patternMatch) { const params = rule.extractor ? rule.extractor(normalizedText) : {}; const confidence = keywordMatch && patternMatch ? 0.95 : 0.75; console.info(`[IntentEngine] 识别结果: ${rule.intent}, 置信度: ${confidence}`); return { intent: rule.intent, params, confidence }; } } // 默认为闲聊 return { intent: IntentType.CHAT, params: {}, confidence: 0.5 }; } }3. 大模型对话服务 (service/LLMService.ets)// entry/src/main/ets/service/LLMService.ets import { http } from '@kit.NetworkKit'; import { ConversationContext, Message, MessageRole } from '../model/MessageModel'; import { BusinessError } from '@kit.BasicServicesKit'; /** * V哥的LLM服务封装 * 支持多种大模型API，这里以通用格式为例 */ // LLM配置接口 interface LLMConfig { apiUrl: string; apiKey: string; model: string; maxTokens: number; temperature: number; } // API请求格式 interface ChatCompletionRequest { model: string; messages: Array<{ role: string; content: string }>; max_tokens: number; temperature: number; stream: boolean; } // API响应格式 interface ChatCompletionResponse { id: string; choices: Array<{ message: { role: string; content: string; }; finish_reason: string; }>; usage: { prompt_tokens: number; completion_tokens: number; total_tokens: number; }; } export class LLMService { private static instance: LLMService; private config: LLMConfig; private systemPrompt: string; private constructor() { // 默认配置（实际使用时替换为你的API信息） this.config = { apiUrl: 'https://api.openai.com/v1/chat/completions', // 或其他兼容API apiKey: 'your-api-key-here', // 替换为你的API Key model: 'gpt-3.5-turbo', maxTokens: 2048, temperature: 0.7 }; // 系统提示词 - V哥精心调教 this.systemPrompt = `你是一个运行在鸿蒙系统上的AI智能助手，名叫"小V助手"。 你的特点： 1. 友好、幽默、专业 2. 回答简洁有力，不啰嗦 3. 能理解用户意图，给出实用建议 4. 熟悉鸿蒙生态和华为设备 5. 在适当时候使用emoji增加亲和力 你可以帮用户： - 回答各种问题 - 闲聊解闷 - 提供建议和帮助 - 解释技术概念 请用中文回复，保持回答在100字以内（除非用户明确要求详细解释）。`; } static getInstance(): LLMService { if (!LLMService.instance) { LLMService.instance = new LLMService(); } return LLMService.instance; } /** * 更新配置 */ updateConfig(config: Partial<LLMConfig>): void { this.config = { ...this.config, ...config }; } /** * 发送对话请求 */ async chat(userMessage: string, context: ConversationContext): Promise<string> { // 构建消息历史 const messages: Array<{ role: string; content: string }> = [ { role: 'system', content: this.systemPrompt }, ...context.toAPIFormat(), { role: 'user', content: userMessage } ]; const requestData: ChatCompletionRequest = { model: this.config.model, messages: messages, max_tokens: this.config.maxTokens, temperature: this.config.temperature, stream: false }; try { const httpRequest = http.createHttp(); const response = await httpRequest.request( this.config.apiUrl, { method: http.RequestMethod.POST, header: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.config.apiKey}` }, extraData: JSON.stringify(requestData), connectTimeout: 30000, readTimeout: 60000 } ); httpRequest.destroy(); if (response.responseCode === 200) { const result = JSON.parse(response.result as string) as ChatCompletionResponse; const content = result.choices[0]?.message?.content || '抱歉，我没有理解你的意思'; console.info(`[LLMService] 响应成功，Token使用: ${result.usage?.total_tokens}`); return content; } else { console.error(`[LLMService] API错误: ${response.responseCode}`); return this.getFallbackResponse(userMessage); } } catch (err) { const error = err as BusinessError; console.error(`[LLMService] 请求失败: ${error.code} - ${error.message}`); return this.getFallbackResponse(userMessage); } } /** * 离线兜底回复（当API不可用时） */ private getFallbackResponse(userMessage: string): string { const fallbackResponses: Record<string, string[]> = { '你好': ['你好呀！有什么可以帮你的？', '嗨！我是小V助手，很高兴见到你！'], '谢谢': ['不客气！随时为你服务~', '应该的，还有什么需要帮助的吗？'], '再见': ['再见！期待下次聊天~', '拜拜，有事随时找我哦！'], '你是谁': ['我是小V助手，运行在鸿蒙系统上的AI助手！', '我叫小V，是V哥打造的智能助手~'], '你能做什么': ['我能陪你聊天、回答问题、帮你打开应用、设置提醒等等！试试看吧~', '我可以：闲聊解闷、回答问题、控制设备、设置闹钟提醒...功能多多！'] }; // 关键词匹配 for (const [keyword, responses] of Object.entries(fallbackResponses)) { if (userMessage.includes(keyword)) { return responses[Math.floor(Math.random() * responses.length)]; } } // 默认回复 const defaultResponses = [ '我现在网络不太好，稍后再试试吧~', '让我想想... 你能换个方式问我吗？', '抱歉，我没太理解，能再说一遍吗？', '网络开小差了，不过我们可以继续聊别的！' ]; return defaultResponses[Math.floor(Math.random() * defaultResponses.length)]; } /** * 流式对话（支持打字机效果） */ async chatStream( userMessage: string, context: ConversationContext, onChunk: (chunk: string) => void, onComplete: (fullText: string) => void ): Promise<void> { // 简化实现：模拟流式输出 const response = await this.chat(userMessage, context); let index = 0; const interval = setInterval(() => { if (index < response.length) { onChunk(response[index]); index++; } else { clearInterval(interval); onComplete(response); } }, 30); // 每30ms输出一个字符 } }4. 语音服务封装 (service/VoiceService.ets)// entry/src/main/ets/service/VoiceService.ets import { speechRecognizer } from '@kit.CoreSpeechKit'; import { textToSpeech } from '@kit.CoreSpeechKit'; import { BusinessError } from '@kit.BasicServicesKit'; import { abilityAccessCtrl, Permissions } from '@kit.AbilityKit'; /** * V哥的语音服务封装 * 整合ASR语音识别 + TTS语音合成 */ export class VoiceService { private static instance: VoiceService; private asrEngine: speechRecognizer.SpeechRecognitionEngine | null = null; private ttsEngine: textToSpeech.TextToSpeechEngine | null = null; private isListening: boolean = false; private constructor() {} static getInstance(): VoiceService { if (!VoiceService.instance) { VoiceService.instance = new VoiceService(); } return VoiceService.instance; } /** * 请求麦克风权限 */ async requestPermission(context: Context): Promise<boolean> { const atManager = abilityAccessCtrl.createAtManager(); const permissions: Permissions[] = ['ohos.permission.MICROPHONE']; try { const result = await atManager.requestPermissionsFromUser(context, permissions); const granted = result.authResults.every(r => r === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED); console.info(`[VoiceService] 麦克风权限: ${granted ? '已授权' : '被拒绝'}`); return granted; } catch (err) { console.error('[VoiceService] 请求权限失败:', JSON.stringify(err)); return false; } } /** * 初始化语音识别引擎 */ async initASR(): Promise<boolean> { try { const createParams: speechRecognizer.CreateEngineParams = { language: 'zh-CN', online: 1 // 1-在线识别 0-离线识别 }; this.asrEngine = await speechRecognizer.createEngine(createParams); console.info('[VoiceService] ASR引擎初始化成功'); return true; } catch (err) { const error = err as BusinessError; console.error(`[VoiceService] ASR初始化失败: ${error.code} - ${error.message}`); return false; } } /** * 初始化语音合成引擎 */ async initTTS(): Promise<boolean> { try { const createParams: textToSpeech.CreateEngineParams = { language: 'zh-CN', person: 0, // 发音人 online: 1 // 1-在线合成 0-离线合成 }; const extraParams: Record<string, Object> = { style: 'normal', speed: 1.0, volume: 1.0, pitch: 1.0 }; this.ttsEngine = await textToSpeech.createEngine(createParams); console.info('[VoiceService] TTS引擎初始化成功'); return true; } catch (err) { const error = err as BusinessError; console.error(`[VoiceService] TTS初始化失败: ${error.code} - ${error.message}`); return false; } } /** * 开始语音识别 */ async startListening( onResult: (text: string, isFinal: boolean) => void, onError: (error: string) => void ): Promise<void> { if (!this.asrEngine) { const success = await this.initASR(); if (!success) { onError('语音识别引擎初始化失败'); return; } } if (this.isListening) { console.warn('[VoiceService] 已经在监听中'); return; } try { // 设置回调 this.asrEngine!.setListener({ onStart: (sessionId: string) => { console.info(`[VoiceService] 开始识别, sessionId: ${sessionId}`); this.isListening = true; }, onEvent: (sessionId: string, eventCode: number) => { console.info(`[VoiceService] 事件: ${eventCode}`); }, onResult: (sessionId: string, result: speechRecognizer.SpeechRecognitionResult) => { const text = result.result; const isFinal = result.isFinal; console.info(`[VoiceService] 识别结果: ${text}, isFinal: ${isFinal}`); onResult(text, isFinal); }, onComplete: (sessionId: string) => { console.info(`[VoiceService] 识别完成`); this.isListening = false; }, onError: (sessionId: string, errorCode: number, errorMessage: string) => { console.error(`[VoiceService] 识别错误: ${errorCode} - ${errorMessage}`); this.isListening = false; onError(errorMessage); } }); // 开始识别 const recognitionParams: speechRecognizer.StartParams = { sessionId: `session_${Date.now()}`, audioInfo: { audioType: 'pcm', sampleRate: 16000, soundChannel: 1, sampleBit: 16 }, extraParams: { vadBegin: 2000, // 静音检测开始时间 vadEnd: 3000, // 静音检测结束时间 maxAudioDuration: 60000 // 最大录音时长 } }; await this.asrEngine!.startListening(recognitionParams); } catch (err) { const error = err as BusinessError; console.error(`[VoiceService] 开始识别失败: ${error.code} - ${error.message}`); onError(error.message); } } /** * 停止语音识别 */ async stopListening(): Promise<void> { if (this.asrEngine && this.isListening) { try { await this.asrEngine.finish(`session_stop_${Date.now()}`); this.isListening = false; console.info('[VoiceService] 停止识别'); } catch (err) { console.error('[VoiceService] 停止识别失败:', JSON.stringify(err)); } } } /** * 语音合成（文字转语音） */ async speak(text: string, onComplete?: () => void): Promise<void> { if (!this.ttsEngine) { const success = await this.initTTS(); if (!success) { console.error('[VoiceService] TTS引擎不可用'); onComplete?.(); return; } } try { // 设置回调 this.ttsEngine!.setListener({ onStart: (requestId: string) => { console.info(`[VoiceService] 开始播放, requestId: ${requestId}`); }, onProgress: (requestId: string, progress: number) => { // 播放进度 }, onFinish: (requestId: string) => { console.info(`[VoiceService] 播放完成`); onComplete?.(); }, onError: (requestId: string, errorCode: number, errorMessage: string) => { console.error(`[VoiceService] 播放错误: ${errorCode} - ${errorMessage}`); onComplete?.(); } }); // 合成参数 const speakParams: textToSpeech.SpeakParams = { requestId: `speak_${Date.now()}`, extraParams: { speed: 1.0, volume: 1.0, pitch: 1.0 } }; await this.ttsEngine!.speak(text, speakParams); } catch (err) { const error = err as BusinessError; console.error(`[VoiceService] 语音合成失败: ${error.code} - ${error.message}`); onComplete?.(); } } /** * 停止语音播放 */ async stopSpeaking(): Promise<void> { if (this.ttsEngine) { try { await this.ttsEngine.stop(); console.info('[VoiceService] 停止播放'); } catch (err) { console.error('[VoiceService] 停止播放失败:', JSON.stringify(err)); } } } /** * 释放资源 */ async release(): Promise<void> { try { if (this.asrEngine) { await this.asrEngine.shutdown(); this.asrEngine = null; } if (this.ttsEngine) { await this.ttsEngine.shutdown(); this.ttsEngine = null; } console.info('[VoiceService] 资源释放完成'); } catch (err) { console.error('[VoiceService] 释放资源失败:', JSON.stringify(err)); } } /** * 获取监听状态 */ getListeningState(): boolean { return this.isListening; } }5. 任务执行器 (engine/ActionExecutor.ets)// entry/src/main/ets/engine/ActionExecutor.ets import { bundleManager, common, Want } from '@kit.AbilityKit'; import { IntentType } from '../model/MessageModel'; import { BusinessError } from '@kit.BasicServicesKit'; /** * V哥的任务执行器 * 根据意图执行具体操作 */ interface ActionResult { success: boolean; message: string; data?: object; } export class ActionExecutor { private static instance: ActionExecutor; private context: common.UIAbilityContext | null = null; private constructor() {} static getInstance(): ActionExecutor { if (!ActionExecutor.instance) { ActionExecutor.instance = new ActionExecutor(); } return ActionExecutor.instance; } /** * 设置上下文 */ setContext(context: common.UIAbilityContext): void { this.context = context; } /** * 执行动作 */ async execute(intent: IntentType, params: Record<string, string>): Promise<ActionResult> { console.info(`[ActionExecutor] 执行意图: ${intent}, 参数: ${JSON.stringify(params)}`); switch (intent) { case IntentType.OPEN_APP: return this.openApp(params); case IntentType.SET_ALARM: return this.setAlarm(params); case IntentType.SET_REMINDER: return this.setReminder(params); case IntentType.QUERY_WEATHER: return this.queryWeather(params); case IntentType.QUERY_TIME: return this.queryTime(); case IntentType.CONTROL_DEVICE: return this.controlDevice(params); default: return { success: false, message: '暂不支持该操作' }; } } /** * 打开应用 */ private async openApp(params: Record<string, string>): Promise<ActionResult> { const bundleName = params.bundleName; const appName = params.appName; if (!bundleName) { return { success: false, message: `抱歉，我不知道怎么打开"${appName || '这个应用'}"` }; } try { const want: Want = { bundleName: bundleName, action: 'action.system.home', entities: ['entity.system.home'] }; await this.context?.startAbility(want); return { success: true, message: `已为你打开${appName}` }; } catch (err) { const error = err as BusinessError; console.error(`[ActionExecutor] 打开应用失败: ${error.code} - ${error.message}`); return { success: false, message: `打开${appName}失败，可能是应用未安装` }; } } /** * 设置闹钟 */ private async setAlarm(params: Record<string, string>): Promise<ActionResult> { const hour = parseInt(params.hour || '8'); const minute = parseInt(params.minute || '0'); try { // 调用系统闹钟 const want: Want = { action: 'ohos.want.action.setAlarm', parameters: { 'ringtone': 'default', 'hour': hour, 'minute': minute } }; await this.context?.startAbility(want); const timeStr = `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`; return { success: true, message: `好的，已为你设置${timeStr}的闹钟` }; } catch (err) { console.error('[ActionExecutor] 设置闹钟失败:', JSON.stringify(err)); return { success: false, message: '设置闹钟失败，请手动设置' }; } } /** * 设置提醒 */ private async setReminder(params: Record<string, string>): Promise<ActionResult> { const content = params.content || '未命名提醒'; const delayMinutes = parseInt(params.delayMinutes || '10'); // 这里可以接入前面的日程提醒模块 return { success: true, message: `收到！${delayMinutes}分钟后提醒你：${content}` }; } /** * 查询天气 */ private async queryWeather(params: Record<string, string>): Promise<ActionResult> { const city = params.city || '北京'; // 实际项目中对接天气API // 这里返回模拟数据 const mockWeather = { city: city, temperature: Math.floor(Math.random() * 20) + 10, weather: ['晴', '多云', '阴', '小雨'][Math.floor(Math.random() * 4)], humidity: Math.floor(Math.random() * 40) + 40 }; return { success: true, message: `${city}今天${mockWeather.weather}，气温${mockWeather.temperature}°C，湿度${mockWeather.humidity}%`, data: mockWeather }; } /** * 查询时间 */ private queryTime(): ActionResult { const now = new Date(); const weekDays = ['日', '一', '二', '三', '四', '五', '六']; const dateStr = `${now.getFullYear()}年${now.getMonth() + 1}月${now.getDate()}日`; const weekStr = `星期${weekDays[now.getDay()]}`; const timeStr = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`; return { success: true, message: `现在是${dateStr} ${weekStr} ${timeStr}` }; } /** * 控制设备 */ private async controlDevice(params: Record<string, string>): Promise<ActionResult> { const device = params.device || '设备'; const action = params.action === 'on' ? '打开' : '关闭'; // 实际项目中对接智能家居API return { success: true, message: `好的，已${action}${device}` }; } }6. AI智能体核心 (engine/AIAgent.ets)// entry/src/main/ets/engine/AIAgent.ets import { Message, MessageRole, MessageType, ConversationContext, IntentType } from '../model/MessageModel'; import { IntentEngine } from './IntentEngine'; import { ActionExecutor } from './ActionExecutor'; import { LLMService } from '../service/LLMService'; import { VoiceService } from '../service/VoiceService'; /** * V哥的AI智能体核心 * 整合所有能力，实现智能对话 */ export class AIAgent { private static instance: AIAgent; private context: ConversationContext; private intentEngine: IntentEngine; private actionExecutor: ActionExecutor; private llmService: LLMService; private voiceService: VoiceService; // 回调函数 private onMessageCallback?: (message: Message) => void; private onStateChangeCallback?: (state: AgentState) => void; private constructor() { this.context = new ConversationContext(); this.intentEngine = IntentEngine.getInstance(); this.actionExecutor = ActionExecutor.getInstance(); this.llmService = LLMService.getInstance(); this.voiceService = VoiceService.getInstance(); } static getInstance(): AIAgent { if (!AIAgent.instance) { AIAgent.instance = new AIAgent(); } return AIAgent.instance; } /** * 设置消息回调 */ setOnMessage(callback: (message: Message) => void): void { this.onMessageCallback = callback; } /** * 设置状态回调 */ setOnStateChange(callback: (state: AgentState) => void): void { this.onStateChangeCallback = callback; } /** * 处理用户输入（核心方法） */ async processInput(userInput: string): Promise<void> { if (!userInput.trim()) return; console.info(`[AIAgent] 处理用户输入: ${userInput}`); // 1. 创建用户消息 const userMessage = new Message({ role: MessageRole.USER, type: MessageType.TEXT, content: userInput }); this.context.addMessage(userMessage); this.onMessageCallback?.(userMessage); // 2. 意图识别 this.onStateChangeCallback?.(AgentState.THINKING); const { intent, params, confidence } = this.intentEngine.recognize(userInput); console.info(`[AIAgent] 意图识别: ${intent}, 置信度: ${confidence}`); // 3. 根据意图决定处理方式 let response: string; if (intent !== IntentType.CHAT && confidence >= 0.7) { // 高置信度的功能意图，执行动作 userMessage.intent = intent; userMessage.intentParams = params; const result = await this.actionExecutor.execute(intent, params); response = result.message; // 如果是需要补充信息的场景，继续调用LLM if (!result.success && intent !== IntentType.UNKNOWN) { response = await this.llmService.chat( `用户说"${userInput}"，我尝试${this.getIntentDescription(intent)}但失败了。请给出友好的回复和建议。`, this.context ); } } else { // 闲聊或低置信度，调用大模型 response = await this.llmService.chat(userInput, this.context); } // 4. 创建助手回复 const assistantMessage = new Message({ role: MessageRole.ASSISTANT, type: MessageType.TEXT, content: response }); this.context.addMessage(assistantMessage); this.onMessageCallback?.(assistantMessage); this.onStateChangeCallback?.(AgentState.IDLE); // 5. 语音播报回复 await this.voiceService.speak(response); } /** * 开始语音输入 */ async startVoiceInput(): Promise<void> { this.onStateChangeCallback?.(AgentState.LISTENING); await this.voiceService.startListening( (text: string, isFinal: boolean) => { if (isFinal && text.trim()) { this.processInput(text); } }, (error: string) => { console.error('[AIAgent] 语音识别错误:', error); this.onStateChangeCallback?.(AgentState.IDLE); } ); } /** * 停止语音输入 */ async stopVoiceInput(): Promise<void> { await this.voiceService.stopListening(); this.onStateChangeCallback?.(AgentState.IDLE); } /** * 获取对话历史 */ getHistory(): Message[] { return this.context.getHistory(); } /** * 清空对话 */ clearHistory(): void { this.context.clear(); } /** * 获取意图描述 */ private getIntentDescription(intent: IntentType): string { const descriptions: Record<IntentType, string> = { [IntentType.OPEN_APP]: '打开应用', [IntentType.SET_ALARM]: '设置闹钟', [IntentType.SET_REMINDER]: '设置提醒', [IntentType.QUERY_WEATHER]: '查询天气', [IntentType.QUERY_TIME]: '查询时间', [IntentType.CONTROL_DEVICE]: '控制设备', [IntentType.CHAT]: '闲聊', [IntentType.UNKNOWN]: '理解意图' }; return descriptions[intent] || '执行操作'; } } /** * 智能体状态 */ export enum AgentState { IDLE = 'idle', // 空闲 LISTENING = 'listening', // 监听中 THINKING = 'thinking', // 思考中 SPEAKING = 'speaking' // 说话中 }7. 主界面 (pages/Index.ets)// entry/src/main/ets/pages/Index.ets import { Message, MessageRole, MessageType } from '../model/MessageModel'; import { AIAgent, AgentState } from '../engine/AIAgent'; import { ActionExecutor } from '../engine/ActionExecutor'; import { VoiceService } from '../service/VoiceService'; import { common } from '@kit.AbilityKit'; import { promptAction } from '@kit.ArkUI'; @Entry @Component struct Index { @State messageList: Message[] = []; @State inputText: string = ''; @State agentState: AgentState = AgentState.IDLE; @State isVoiceMode: boolean = false; private agent: AIAgent = AIAgent.getInstance(); private voiceService: VoiceService = VoiceService.getInstance(); private scroller: Scroller = new Scroller(); private context = getContext(this) as common.UIAbilityContext; async aboutToAppear(): Promise<void> { // 初始化 ActionExecutor.getInstance().setContext(this.context); // 请求权限 await this.voiceService.requestPermission(this.context); // 设置回调 this.agent.setOnMessage((message: Message) => { this.messageList = [...this.messageList, message]; // 滚动到底部 setTimeout(() => { this.scroller.scrollEdge(Edge.Bottom); }, 100); }); this.agent.setOnStateChange((state: AgentState) => { this.agentState = state; }); // 添加欢迎消息 const welcomeMessage = new Message({ role: MessageRole.ASSISTANT, type: MessageType.TEXT, content: '你好！我是小V助手 🤖\n\n我可以帮你：\n• 回答各种问题\n• 打开应用\n• 设置闹钟和提醒\n• 查询天气和时间\n• 控制智能设备\n\n试着对我说点什么吧！' }); this.messageList.push(welcomeMessage); } /** * 发送消息 */ async sendMessage(): Promise<void> { if (!this.inputText.trim()) return; const text = this.inputText.trim(); this.inputText = ''; await this.agent.processInput(text); } /** * 切换语音模式 */ async toggleVoiceMode(): Promise<void> { if (this.isVoiceMode) { // 停止语音输入 await this.agent.stopVoiceInput(); this.isVoiceMode = false; } else { // 开始语音输入 this.isVoiceMode = true; await this.agent.startVoiceInput(); } } /** * 获取状态文本 */ getStateText(): string { switch (this.agentState) { case AgentState.LISTENING: return '正在听...'; case AgentState.THINKING: return '思考中...'; case AgentState.SPEAKING: return '说话中...'; default: return ''; } } /** * 格式化时间 */ formatTime(timestamp: number): string { const date = new Date(timestamp); const hour = date.getHours().toString().padStart(2, '0'); const minute = date.getMinutes().toString().padStart(2, '0'); return `${hour}:${minute}`; } build() { Column() { // 顶部标题栏 Row() { Column() { Text('小V助手') .fontSize(20) .fontWeight(FontWeight.Bold) .fontColor('#333333') if (this.agentState !== AgentState.IDLE) { Row() { LoadingProgress() .width(14) .height(14) .color('#007DFF') Text(this.getStateText()) .fontSize(12) .fontColor('#007DFF') .margin({ left: 4 }) } .margin({ top: 2 }) } } .alignItems(HorizontalAlign.Start) Blank() // 清空对话按钮 Button() { Image($r('app.media.ic_clear')) .width(20) .height(20) .fillColor('#666666') } .width(40) .height(40) .backgroundColor('#F0F0F0') .borderRadius(20) .onClick(() => { promptAction.showDialog({ title: '清空对话', message: '确定要清空所有对话记录吗？', buttons: [ { text: '取消', color: '#666666' }, { text: '确定', color: '#007DFF' } ] }).then((result) => { if (result.index === 1) { this.messageList = []; this.agent.clearHistory(); } }); }) } .width('100%') .height(60) .padding({ left: 16, right: 16 }) .backgroundColor(Color.White) // 消息列表 List({ scroller: this.scroller, space: 16 }) { ForEach(this.messageList, (message: Message) => { ListItem() { this.MessageBubble(message) } }, (message: Message) => message.id) } .width('100%') .layoutWeight(1) .padding({ left: 16, right: 16, top: 12, bottom: 12 }) .backgroundColor('#F5F5F5') // 底部输入区域 Row() { // 语音按钮 Button() { Image(this.isVoiceMode ? $r('app.media.ic_keyboard') : $r('app.media.ic_voice')) .width(24) .height(24) .fillColor(this.isVoiceMode ? '#FF3B30' : '#666666') } .width(44) .height(44) .backgroundColor(this.isVoiceMode ? '#FFE5E5' : '#F0F0F0') .borderRadius(22) .onClick(() => this.toggleVoiceMode()) if (this.isVoiceMode) { // 语音输入状态 Column() { if (this.agentState === AgentState.LISTENING) { Row() { ForEach([1, 2, 3, 4, 5], (i: number) => { Column() .width(4) .height(12 + Math.random() * 20) .backgroundColor('#007DFF') .borderRadius(2) .margin({ left: 4, right: 4 }) .animation({ duration: 300, iterations: -1, curve: Curve.EaseInOut }) }) } .justifyContent(FlexAlign.Center) } Text(this.agentState === AgentState.LISTENING ? '正在聆听...' : '点击麦克风开始说话') .fontSize(14) .fontColor('#666666') .margin({ top: 8 }) } .layoutWeight(1) .height(44) .justifyContent(FlexAlign.Center) } else { // 文字输入框 TextInput({ placeholder: '输入消息...', text: this.inputText }) .layoutWeight(1) .height(44) .backgroundColor('#F5F5F5') .borderRadius(22) .padding({ left: 16, right: 16 }) .margin({ left: 8, right: 8 }) .onChange((value) => { this.inputText = value; }) .onSubmit(() => { this.sendMessage(); }) // 发送按钮 Button() { Image($r('app.media.ic_send')) .width(24) .height(24) .fillColor(Color.White) } .width(44) .height(44) .backgroundColor(this.inputText.trim() ? '#007DFF' : '#CCCCCC') .borderRadius(22) .enabled(this.inputText.trim().length > 0) .onClick(() => this.sendMessage()) } } .width('100%') .height(70) .padding({ left: 12, right: 12, top: 8, bottom: 16 }) .backgroundColor(Color.White) } .width('100%') .height('100%') } /** * 消息气泡组件 */ @Builder MessageBubble(message: Message) { Column() { if (message.role === MessageRole.USER) { // 用户消息（右侧） Row() { Blank() Column() { Text(message.content) .fontSize(15) .fontColor(Color.White) .lineHeight(22) } .padding(12) .backgroundColor('#007DFF') .borderRadius({ topLeft: 16, topRight: 4, bottomLeft: 16, bottomRight: 16 }) .constraintSize({ maxWidth: '75%' }) // 用户头像 Image($r('app.media.ic_user')) .width(36) .height(36) .borderRadius(18) .margin({ left: 8 }) } .width('100%') .justifyContent(FlexAlign.End) } else { // AI消息（左侧） Row() { // AI头像 Stack() { Circle() .width(36) .height(36) .fill('#E6F2FF') Image($r('app.media.ic_robot')) .width(24) .height(24) } .margin({ right: 8 }) Column() { Text(message.content) .fontSize(15) .fontColor('#333333') .lineHeight(22) // 显示时间 Text(this.formatTime(message.timestamp)) .fontSize(11) .fontColor('#999999') .margin({ top: 4 }) } .padding(12) .backgroundColor(Color.White) .borderRadius({ topLeft: 4, topRight: 16, bottomLeft: 16, bottomRight: 16 }) .constraintSize({ maxWidth: '75%' }) .alignItems(HorizontalAlign.Start) .shadow({ radius: 4, color: 'rgba(0,0,0,0.05)', offsetX: 0, offsetY: 2 }) Blank() } .width('100%') .justifyContent(FlexAlign.Start) } } } }8. 快捷指令面板 (components/QuickCommands.ets)// entry/src/main/ets/components/QuickCommands.ets /** * V哥设计的快捷指令面板 * 方便用户快速触发常用功能 */ interface QuickCommand { icon: Resource; label: string; command: string; color: string; } @Component export struct QuickCommands { onCommand: (command: string) => void = () => {}; private commands: QuickCommand[] = [ { icon: $r('app.media.ic_weather'), label: '查天气', command: '今天天气怎么样', color: '#FFB800' }, { icon: $r('app.media.ic_time'), label: '查时间', command: '现在几点了', color: '#007DFF' }, { icon: $r('app.media.ic_alarm'), label: '设闹钟', command: '明天早上7点叫我起床', color: '#34C759' }, { icon: $r('app.media.ic_remind'), label: '提醒我', command: '10分钟后提醒我喝水', color: '#FF9500' }, { icon: $r('app.media.ic_app'), label: '打开相机', command: '打开相机', color: '#AF52DE' }, { icon: $r('app.media.ic_home'), label: '开灯', command: '打开客厅的灯', color: '#FF3B30' } ]; build() { Column() { Text('快捷指令') .fontSize(14) .fontColor('#999999') .margin({ bottom: 12 }) Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween }) { ForEach(this.commands, (cmd: QuickCommand) => { Column() { Stack() { Circle() .width(44) .height(44) .fill(cmd.color) .opacity(0.15) Image(cmd.icon) .width(24) .height(24) .fillColor(cmd.color) } Text(cmd.label) .fontSize(12) .fontColor('#666666') .margin({ top: 6 }) } .width('30%') .margin({ bottom: 16 }) .onClick(() => { this.onCommand(cmd.command); }) }) } } .width('100%') .padding(16) .backgroundColor(Color.White) .borderRadius(12) } }五、资源文件准备需要的图标资源在 entry/src/main/resources/base/media/ 添加以下图标：文件名用途建议尺寸ic_robot.svgAI头像48x48ic_user.svg用户头像48x48ic_send.svg发送按钮24x24ic_voice.svg语音按钮24x24ic_keyboard.svg键盘按钮24x24ic_clear.svg清空按钮24x24ic_weather.svg天气图标24x24ic_time.svg时间图标24x24ic_alarm.svg闹钟图标24x24ic_remind.svg提醒图标24x24ic_app.svg应用图标24x24ic_home.svg智能家居图标24x24示例SVGic_robot.svg: <circle cx="24" cy="24" r="20" fill="#007DFF"/> <circle cx="17" cy="20" r="3" fill="white"/> <circle cx="31" cy="20" r="3" fill="white"/> <path d="M16 30 Q24 36 32 30" stroke="white" stroke-width="2" fill="none"/> <rect x="22" y="4" width="4" height="6" rx="2" fill="#007DFF"/> <circle cx="24" cy="4" r="3" fill="#007DFF"/> ic_voice.svg: <path d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3z"/> <path d="M17 11c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08c3.39-.49 6-3.39 6-6.92h-2z"/> 六、V哥总结：关键技术点1. 意图识别的设计思路用户输入 → 规则匹配（快速） → 高置信度直接执行 ↓ 低置信度 → 调用LLM兜底2. 对话管理的核心// 多轮对话的关键：上下文管理 class ConversationContext { messages: Message[] = []; maxHistory: number = 10; // 控制历史长度，避免Token浪费 }3. 语音交互的最佳实践先请求权限，再初始化引擎语音识别和语音合成用完要释放做好错误处理和降级方案4. 性能优化建议意图识别用本地规则，快速响应LLM调用做好缓存和限流消息列表使用LazyForEach优化七、V哥唠两句兄弟们，这套代码是V哥实战中总结出来的，完整实现了一个能用的AI智能体。当然，实际项目中你还需要：接入真实的LLM API（替换掉示例配置）完善意图规则库（根据你的业务场景）接入真实的智能家居API做好异常处理和用户引导AI智能体的核心不是技术多牛逼，而是用户体验做得好！关注V哥不迷路！前行路上不犯怵!
Hello，大家好，我是 V 哥。AI 智能体在2026年V 哥相信一定翻天覆地的变化，一大波企业和开发者纷纷涌入这个赛道，什么超级个体、一人公司、为企业节省几百万人力成本等等话题在网络上持续发酵，作为程序员的我们，如果还在观望，那等来就一定是被市场淘汰。我经常跟同学们说，程序员最大的优势是啥？就是不断持续学习的超强能力！干掉程序员的只会是程序员自己，未来的程序员不只是程序员，而是主导技术变现的超级魔术师。今天的内容，V 哥带大家一起来玩一玩，在鸿蒙6系统中，如何完成精准日程管理的完整案例开发。一、项目概述功能特性✅ 日程增删改查（支持标题、备注、时间、重复）✅ 后台精准提醒（应用关闭/重启后依然准时）✅ 智能提前提醒（5/10/30/60分钟）✅ 重复提醒（每天/每周/每月）✅ 自定义铃声+震动✅ 点击通知跳转详情技术方案┌─────────────────────────────────────────────────────────┐ │ 精准日程提醒架构 │ ├─────────────────────────────────────────────────────────┤ │ UI层 │ ArkUI 声明式UI │ ├─────────────────────────────────────────────────────────┤ │ 数据层 │ @ohos.data.relationalStore (关系型DB) │ ├─────────────────────────────────────────────────────────┤ │ 提醒层 │ @ohos.reminderAgentManager (代理提醒) │ ├─────────────────────────────────────────────────────────┤ │ 通知层 │ @ohos.notificationManager │ └─────────────────────────────────────────────────────────┘二、项目创建与配置步骤1：创建项目DevEco Studio → File → New → Create Project → 选择 "Empty Ability" → Project name: ScheduleManager → Bundle name: com.example.schedulemanager → Compile SDK: 5.0.0(API 12) 或更高 → Model: Stage步骤2：配置 module.json5{ "module": { "name": "entry", "type": "entry", "description": "$string:module_desc", "mainElement": "EntryAbility", "deviceTypes": ["phone", "tablet"], "deliveryWithInstall": true, "installationFree": false, "pages": "$profile:main_pages", "abilities": [ { "name": "EntryAbility", "srcEntry": "./ets/entryability/EntryAbility.ets", "description": "$string:EntryAbility_desc", "icon": "$media:icon", "label": "$string:EntryAbility_label", "startWindowIcon": "$media:startIcon", "startWindowBackground": "$color:start_window_background", "exported": true, "skills": [ { "entities": ["entity.system.home"], "actions": ["action.system.home"] } ] } ], "requestPermissions": [ { "name": "ohos.permission.PUBLISH_AGENT_REMINDER", "reason": "$string:reminder_reason", "usedScene": { "abilities": ["EntryAbility"], "when": "always" } }, { "name": "ohos.permission.NOTIFICATION_CONTROLLER", "reason": "$string:notification_reason", "usedScene": { "abilities": ["EntryAbility"], "when": "always" } } ] } }步骤3：配置 main_pages.json{ "src": [ "pages/Index", "pages/AddSchedulePage", "pages/ScheduleDetailPage" ] }步骤4：配置字符串资源 (string.json){ "string": [ { "name": "module_desc", "value": "日程管理模块" }, { "name": "EntryAbility_desc", "value": "日程管理应用" }, { "name": "EntryAbility_label", "value": "精准日程" }, { "name": "reminder_reason", "value": "用于设置日程提醒" }, { "name": "notification_reason", "value": "用于发送日程通知" } ] }三、核心代码实现1. 日程数据模型 (model/ScheduleModel.ets)// entry/src/main/ets/model/ScheduleModel.ets /** * 重复类型枚举 */ export enum RepeatType { NONE = 0, // 不重复 DAILY = 1, // 每天 WEEKLY = 2, // 每周 MONTHLY = 3 // 每月 } /** * 提前提醒时间枚举（分钟） */ export enum AdvanceRemind { NONE = 0, FIVE_MIN = 5, TEN_MIN = 10, THIRTY_MIN = 30, ONE_HOUR = 60 } /** * 日程实体类 */ export class Schedule { id: number = 0; // 主键ID title: string = ''; // 标题 note: string = ''; // 备注 remindTime: number = 0; // 提醒时间戳(毫秒) advanceMinutes: number = 0; // 提前提醒分钟数 repeatType: RepeatType = RepeatType.NONE; // 重复类型 reminderId: number = -1; // 系统提醒ID isEnabled: boolean = true; // 是否启用 createTime: number = 0; // 创建时间 updateTime: number = 0; // 更新时间 constructor(init?: Partial<Schedule>) { if (init) { Object.assign(this, init); } } } /** * 重复类型显示文本 */ export function getRepeatTypeText(type: RepeatType): string { const texts: Record<RepeatType, string> = { [RepeatType.NONE]: '不重复', [RepeatType.DAILY]: '每天', [RepeatType.WEEKLY]: '每周', [RepeatType.MONTHLY]: '每月' }; return texts[type] || '不重复'; } /** * 提前提醒显示文本 */ export function getAdvanceText(minutes: number): string { if (minutes === 0) return '准时提醒'; if (minutes < 60) return `提前${minutes}分钟`; return `提前${minutes / 60}小时`; }2. 数据库操作类 (utils/ScheduleDB.ets)// entry/src/main/ets/utils/ScheduleDB.ets import { relationalStore, ValuesBucket } from '@kit.ArkData'; import { Schedule, RepeatType } from '../model/ScheduleModel'; import { common } from '@kit.AbilityKit'; const DB_NAME = 'ScheduleManager.db'; const TABLE_NAME = 'schedules'; const DB_VERSION = 1; // 建表SQL const CREATE_TABLE_SQL = ` CREATE TABLE IF NOT EXISTS ${TABLE_NAME} ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, note TEXT, remind_time INTEGER NOT NULL, advance_minutes INTEGER DEFAULT 0, repeat_type INTEGER DEFAULT 0, reminder_id INTEGER DEFAULT -1, is_enabled INTEGER DEFAULT 1, create_time INTEGER, update_time INTEGER ) `; export class ScheduleDB { private static instance: ScheduleDB; private rdbStore: relationalStore.RdbStore | null = null; private context: common.UIAbilityContext | null = null; private constructor() {} /** * 获取单例实例 */ static getInstance(): ScheduleDB { if (!ScheduleDB.instance) { ScheduleDB.instance = new ScheduleDB(); } return ScheduleDB.instance; } /** * 初始化数据库 */ async init(context: common.UIAbilityContext): Promise<void> { this.context = context; const storeConfig: relationalStore.StoreConfig = { name: DB_NAME, securityLevel: relationalStore.SecurityLevel.S1 }; try { this.rdbStore = await relationalStore.getRdbStore(context, storeConfig); await this.rdbStore.executeSql(CREATE_TABLE_SQL); console.info('[ScheduleDB] 数据库初始化成功'); } catch (err) { console.error('[ScheduleDB] 数据库初始化失败:', JSON.stringify(err)); } } /** * 插入日程 */ async insert(schedule: Schedule): Promise<number> { if (!this.rdbStore) { throw new Error('数据库未初始化'); } const now = Date.now(); const values: ValuesBucket = { 'title': schedule.title, 'note': schedule.note, 'remind_time': schedule.remindTime, 'advance_minutes': schedule.advanceMinutes, 'repeat_type': schedule.repeatType, 'reminder_id': schedule.reminderId, 'is_enabled': schedule.isEnabled ? 1 : 0, 'create_time': now, 'update_time': now }; try { const rowId = await this.rdbStore.insert(TABLE_NAME, values); console.info('[ScheduleDB] 插入成功, rowId:', rowId); return rowId; } catch (err) { console.error('[ScheduleDB] 插入失败:', JSON.stringify(err)); throw err; } } /** * 更新日程 */ async update(schedule: Schedule): Promise<number> { if (!this.rdbStore) { throw new Error('数据库未初始化'); } const values: ValuesBucket = { 'title': schedule.title, 'note': schedule.note, 'remind_time': schedule.remindTime, 'advance_minutes': schedule.advanceMinutes, 'repeat_type': schedule.repeatType, 'reminder_id': schedule.reminderId, 'is_enabled': schedule.isEnabled ? 1 : 0, 'update_time': Date.now() }; const predicates = new relationalStore.RdbPredicates(TABLE_NAME); predicates.equalTo('id', schedule.id); try { const rows = await this.rdbStore.update(values, predicates); console.info('[ScheduleDB] 更新成功, 影响行数:', rows); return rows; } catch (err) { console.error('[ScheduleDB] 更新失败:', JSON.stringify(err)); throw err; } } /** * 删除日程 */ async delete(id: number): Promise<number> { if (!this.rdbStore) { throw new Error('数据库未初始化'); } const predicates = new relationalStore.RdbPredicates(TABLE_NAME); predicates.equalTo('id', id); try { const rows = await this.rdbStore.delete(predicates); console.info('[ScheduleDB] 删除成功, 影响行数:', rows); return rows; } catch (err) { console.error('[ScheduleDB] 删除失败:', JSON.stringify(err)); throw err; } } /** * 根据ID查询 */ async getById(id: number): Promise<Schedule | null> { if (!this.rdbStore) { throw new Error('数据库未初始化'); } const predicates = new relationalStore.RdbPredicates(TABLE_NAME); predicates.equalTo('id', id); try { const resultSet = await this.rdbStore.query(predicates); if (resultSet.goToFirstRow()) { const schedule = this.parseResultSet(resultSet); resultSet.close(); return schedule; } resultSet.close(); return null; } catch (err) { console.error('[ScheduleDB] 查询失败:', JSON.stringify(err)); throw err; } } /** * 查询所有日程（按时间排序） */ async getAll(): Promise<Schedule[]> { if (!this.rdbStore) { throw new Error('数据库未初始化'); } const predicates = new relationalStore.RdbPredicates(TABLE_NAME); predicates.orderByAsc('remind_time'); try { const resultSet = await this.rdbStore.query(predicates); const schedules: Schedule[] = []; while (resultSet.goToNextRow()) { schedules.push(this.parseResultSet(resultSet)); } resultSet.close(); console.info('[ScheduleDB] 查询全部, 数量:', schedules.length); return schedules; } catch (err) { console.error('[ScheduleDB] 查询全部失败:', JSON.stringify(err)); throw err; } } /** * 查询未来的日程 */ async getFutureSchedules(): Promise<Schedule[]> { if (!this.rdbStore) { throw new Error('数据库未初始化'); } const predicates = new relationalStore.RdbPredicates(TABLE_NAME); predicates.greaterThan('remind_time', Date.now()); predicates.equalTo('is_enabled', 1); predicates.orderByAsc('remind_time'); try { const resultSet = await this.rdbStore.query(predicates); const schedules: Schedule[] = []; while (resultSet.goToNextRow()) { schedules.push(this.parseResultSet(resultSet)); } resultSet.close(); return schedules; } catch (err) { console.error('[ScheduleDB] 查询未来日程失败:', JSON.stringify(err)); throw err; } } /** * 解析结果集为Schedule对象 */ private parseResultSet(resultSet: relationalStore.ResultSet): Schedule { return new Schedule({ id: resultSet.getLong(resultSet.getColumnIndex('id')), title: resultSet.getString(resultSet.getColumnIndex('title')), note: resultSet.getString(resultSet.getColumnIndex('note')), remindTime: resultSet.getLong(resultSet.getColumnIndex('remind_time')), advanceMinutes: resultSet.getLong(resultSet.getColumnIndex('advance_minutes')), repeatType: resultSet.getLong(resultSet.getColumnIndex('repeat_type')) as RepeatType, reminderId: resultSet.getLong(resultSet.getColumnIndex('reminder_id')), isEnabled: resultSet.getLong(resultSet.getColumnIndex('is_enabled')) === 1, createTime: resultSet.getLong(resultSet.getColumnIndex('create_time')), updateTime: resultSet.getLong(resultSet.getColumnIndex('update_time')) }); } }3. 提醒管理器 (utils/ReminderHelper.ets)// entry/src/main/ets/utils/ReminderHelper.ets import { reminderAgentManager } from '@kit.BackgroundTasksKit'; import { notificationManager } from '@kit.NotificationKit'; import { Schedule, RepeatType } from '../model/ScheduleModel'; import { BusinessError } from '@kit.BasicServicesKit'; export class ReminderHelper { private static instance: ReminderHelper; private constructor() {} static getInstance(): ReminderHelper { if (!ReminderHelper.instance) { ReminderHelper.instance = new ReminderHelper(); } return ReminderHelper.instance; } /** * 请求通知权限 */ async requestNotificationPermission(): Promise<boolean> { try { const isEnabled = await notificationManager.isNotificationEnabled(); if (!isEnabled) { await notificationManager.requestEnableNotification(); } return true; } catch (err) { const error = err as BusinessError; console.error('[ReminderHelper] 请求通知权限失败:', error.code, error.message); return false; } } /** * 设置日程提醒 */ async setReminder(schedule: Schedule): Promise<number> { // 计算实际提醒时间（考虑提前量） const actualRemindTime = schedule.remindTime - schedule.advanceMinutes * 60 * 1000; if (actualRemindTime <= Date.now()) { console.warn('[ReminderHelper] 提醒时间已过'); return -1; } // 将时间戳转换为日期对象 const remindDate = new Date(actualRemindTime); // 构建提醒请求 const reminderRequest: reminderAgentManager.ReminderRequestCalendar = { reminderType: reminderAgentManager.ReminderType.REMINDER_TYPE_CALENDAR, dateTime: { year: remindDate.getFullYear(), month: remindDate.getMonth() + 1, // 月份从1开始 day: remindDate.getDate(), hour: remindDate.getHours(), minute: remindDate.getMinutes(), second: remindDate.getSeconds() }, repeatMonths: this.getRepeatMonths(schedule.repeatType), repeatDays: this.getRepeatDays(schedule.repeatType, remindDate), title: '日程提醒', content: schedule.title, expiredContent: `日程已过期: ${schedule.title}`, snoozeContent: `稍后提醒: ${schedule.title}`, notificationId: schedule.id, slotType: notificationManager.SlotType.SOCIAL_COMMUNICATION, tapDismissed: true, autoDeletedTime: 300000, // 5分钟后自动删除 snoozeTimes: 3, // 允许延后3次 timeInterval: 5 * 60, // 延后间隔5分钟 actionButton: [ { title: '查看详情', type: reminderAgentManager.ActionButtonType.ACTION_BUTTON_TYPE_CUSTOM }, { title: '稍后提醒', type: reminderAgentManager.ActionButtonType.ACTION_BUTTON_TYPE_SNOOZE } ], wantAgent: { pkgName: 'com.example.schedulemanager', abilityName: 'EntryAbility' }, maxScreenWantAgent: { pkgName: 'com.example.schedulemanager', abilityName: 'EntryAbility' }, ringDuration: 30 // 铃声持续30秒 }; try { const reminderId = await reminderAgentManager.publishReminder(reminderRequest); console.info('[ReminderHelper] 提醒设置成功, reminderId:', reminderId); return reminderId; } catch (err) { const error = err as BusinessError; console.error('[ReminderHelper] 设置提醒失败:', error.code, error.message); throw err; } } /** * 取消提醒 */ async cancelReminder(reminderId: number): Promise<void> { if (reminderId < 0) { return; } try { await reminderAgentManager.cancelReminder(reminderId); console.info('[ReminderHelper] 取消提醒成功, reminderId:', reminderId); } catch (err) { const error = err as BusinessError; console.error('[ReminderHelper] 取消提醒失败:', error.code, error.message); } } /** * 取消所有提醒 */ async cancelAllReminders(): Promise<void> { try { await reminderAgentManager.cancelAllReminders(); console.info('[ReminderHelper] 取消所有提醒成功'); } catch (err) { const error = err as BusinessError; console.error('[ReminderHelper] 取消所有提醒失败:', error.code, error.message); } } /** * 获取所有有效提醒 */ async getAllValidReminders(): Promise<reminderAgentManager.ReminderRequest[]> { try { const reminders = await reminderAgentManager.getValidReminders(); console.info('[ReminderHelper] 有效提醒数量:', reminders.length); return reminders; } catch (err) { const error = err as BusinessError; console.error('[ReminderHelper] 获取有效提醒失败:', error.code, error.message); return []; } } /** * 根据重复类型获取重复月份 */ private getRepeatMonths(repeatType: RepeatType): number[] { if (repeatType === RepeatType.MONTHLY || repeatType === RepeatType.DAILY) { return [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; } return []; } /** * 根据重复类型获取重复日期 */ private getRepeatDays(repeatType: RepeatType, date: Date): number[] { switch (repeatType) { case RepeatType.DAILY: // 每天重复：返回1-31所有日期 return Array.from({ length: 31 }, (_, i) => i + 1); case RepeatType.WEEKLY: // 每周重复：返回同一星期几对应的所有日期（简化处理） return this.getWeeklyDays(date); case RepeatType.MONTHLY: // 每月重复：返回当前日期 return [date.getDate()]; default: return []; } } /** * 获取每周重复的日期（计算每月中相同星期几的日期） */ private getWeeklyDays(date: Date): number[] { const dayOfWeek = date.getDay(); const days: number[] = []; // 计算当月中所有相同星期几的日期 const year = date.getFullYear(); const month = date.getMonth(); const lastDay = new Date(year, month + 1, 0).getDate(); for (let d = 1; d <= lastDay; d++) { const tempDate = new Date(year, month, d); if (tempDate.getDay() === dayOfWeek) { days.push(d); } } return days; } }4. 主页面 - 日程列表 (pages/Index.ets)// entry/src/main/ets/pages/Index.ets import { router } from '@kit.ArkUI'; import { promptAction } from '@kit.ArkUI'; import { Schedule, RepeatType, getRepeatTypeText } from '../model/ScheduleModel'; import { ScheduleDB } from '../utils/ScheduleDB'; import { ReminderHelper } from '../utils/ReminderHelper'; import { common } from '@kit.AbilityKit'; @Entry @Component struct Index { @State scheduleList: Schedule[] = []; @State isLoading: boolean = true; @State isEmpty: boolean = false; private db = ScheduleDB.getInstance(); private reminderHelper = ReminderHelper.getInstance(); async aboutToAppear(): Promise<void> { // 请求通知权限 await this.reminderHelper.requestNotificationPermission(); // 加载日程列表 await this.loadSchedules(); } async onPageShow(): Promise<void> { // 每次页面显示时刷新列表 await this.loadSchedules(); } /** * 加载日程列表 */ async loadSchedules(): Promise<void> { this.isLoading = true; try { this.scheduleList = await this.db.getAll(); this.isEmpty = this.scheduleList.length === 0; } catch (err) { console.error('加载日程失败:', JSON.stringify(err)); promptAction.showToast({ message: '加载失败' }); } finally { this.isLoading = false; } } /** * 删除日程 */ async deleteSchedule(schedule: Schedule): Promise<void> { try { // 取消提醒 await this.reminderHelper.cancelReminder(schedule.reminderId); // 删除数据库记录 await this.db.delete(schedule.id); // 刷新列表 await this.loadSchedules(); promptAction.showToast({ message: '删除成功' }); } catch (err) { console.error('删除日程失败:', JSON.stringify(err)); promptAction.showToast({ message: '删除失败' }); } } /** * 切换日程启用状态 */ async toggleSchedule(schedule: Schedule): Promise<void> { try { schedule.isEnabled = !schedule.isEnabled; if (schedule.isEnabled) { // 重新设置提醒 const reminderId = await this.reminderHelper.setReminder(schedule); schedule.reminderId = reminderId; } else { // 取消提醒 await this.reminderHelper.cancelReminder(schedule.reminderId); schedule.reminderId = -1; } await this.db.update(schedule); await this.loadSchedules(); } catch (err) { console.error('切换状态失败:', JSON.stringify(err)); } } /** * 格式化时间显示 */ formatTime(timestamp: number): string { const date = new Date(timestamp); const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); const hour = String(date.getHours()).padStart(2, '0'); const minute = String(date.getMinutes()).padStart(2, '0'); return `${year}-${month}-${day} ${hour}:${minute}`; } /** * 判断是否已过期 */ isExpired(schedule: Schedule): boolean { return schedule.remindTime < Date.now() && schedule.repeatType === RepeatType.NONE; } build() { Column() { // 顶部标题栏 Row() { Text('精准日程管理') .fontSize(24) .fontWeight(FontWeight.Bold) .fontColor('#333333') Blank() Button() { Image($r('app.media.ic_add')) .width(24) .height(24) .fillColor(Color.White) } .width(44) .height(44) .backgroundColor('#007DFF') .borderRadius(22) .onClick(() => { router.pushUrl({ url: 'pages/AddSchedulePage' }); }) } .width('100%') .height(60) .padding({ left: 16, right: 16 }) // 日程列表 if (this.isLoading) { // 加载中 Column() { LoadingProgress() .width(50) .height(50) Text('加载中...') .fontSize(14) .fontColor('#999999') .margin({ top: 10 }) } .width('100%') .layoutWeight(1) .justifyContent(FlexAlign.Center) } else if (this.isEmpty) { // 空状态 Column() { Image($r('app.media.ic_empty')) .width(120) .height(120) .opacity(0.5) Text('暂无日程') .fontSize(16) .fontColor('#999999') .margin({ top: 16 }) Text('点击右上角 + 添加日程') .fontSize(14) .fontColor('#CCCCCC') .margin({ top: 8 }) } .width('100%') .layoutWeight(1) .justifyContent(FlexAlign.Center) } else { // 日程列表 List({ space: 12 }) { ForEach(this.scheduleList, (schedule: Schedule) => { ListItem() { this.ScheduleCard(schedule) } .swipeAction({ end: this.DeleteButton(schedule) }) }, (schedule: Schedule) => schedule.id.toString()) } .width('100%') .layoutWeight(1) .padding({ left: 16, right: 16, top: 12, bottom: 12 }) .divider({ strokeWidth: 0 }) } } .width('100%') .height('100%') .backgroundColor('#F5F5F5') } /** * 日程卡片组件 */ @Builder ScheduleCard(schedule: Schedule) { Row() { // 左侧状态指示条 Column() .width(4) .height('100%') .backgroundColor(this.isExpired(schedule) ? '#CCCCCC' : (schedule.isEnabled ? '#007DFF' : '#999999')) .borderRadius(2) // 中间内容 Column() { // 标题 Text(schedule.title) .fontSize(16) .fontWeight(FontWeight.Medium) .fontColor(this.isExpired(schedule) ? '#999999' : '#333333') .maxLines(1) .textOverflow({ overflow: TextOverflow.Ellipsis }) // 时间 Row() { Image($r('app.media.ic_time')) .width(14) .height(14) .fillColor('#666666') Text(this.formatTime(schedule.remindTime)) .fontSize(13) .fontColor('#666666') .margin({ left: 4 }) } .margin({ top: 8 }) // 标签行 Row() { // 重复类型标签 if (schedule.repeatType !== RepeatType.NONE) { Text(getRepeatTypeText(schedule.repeatType)) .fontSize(11) .fontColor('#007DFF') .backgroundColor('#E6F2FF') .padding({ left: 6, right: 6, top: 2, bottom: 2 }) .borderRadius(4) } // 提前提醒标签 if (schedule.advanceMinutes > 0) { Text(`提前${schedule.advanceMinutes}分钟`) .fontSize(11) .fontColor('#FF9500') .backgroundColor('#FFF3E0') .padding({ left: 6, right: 6, top: 2, bottom: 2 }) .borderRadius(4) .margin({ left: 6 }) } // 过期标签 if (this.isExpired(schedule)) { Text('已过期') .fontSize(11) .fontColor('#FF3B30') .backgroundColor('#FFE5E5') .padding({ left: 6, right: 6, top: 2, bottom: 2 }) .borderRadius(4) .margin({ left: 6 }) } } .margin({ top: 8 }) } .alignItems(HorizontalAlign.Start) .layoutWeight(1) .margin({ left: 12 }) // 右侧开关 Toggle({ type: ToggleType.Switch, isOn: schedule.isEnabled }) .selectedColor('#007DFF') .switchPointColor(Color.White) .onChange(() => { this.toggleSchedule(schedule); }) } .width('100%') .padding(16) .backgroundColor(Color.White) .borderRadius(12) .shadow({ radius: 4, color: 'rgba(0,0,0,0.08)', offsetX: 0, offsetY: 2 }) .onClick(() => { router.pushUrl({ url: 'pages/ScheduleDetailPage', params: { scheduleId: schedule.id } }); }) } /** * 删除按钮（滑动操作） */ @Builder DeleteButton(schedule: Schedule) { Button() { Image($r('app.media.ic_delete')) .width(24) .height(24) .fillColor(Color.White) } .width(60) .height('100%') .backgroundColor('#FF3B30') .onClick(() => { promptAction.showDialog({ title: '确认删除', message: `确定要删除日程"${schedule.title}"吗？`, buttons: [ { text: '取消', color: '#666666' }, { text: '删除', color: '#FF3B30' } ] }).then((result) => { if (result.index === 1) { this.deleteSchedule(schedule); } }); }) } }5. 添加日程页面 (pages/AddSchedulePage.ets)// entry/src/main/ets/pages/AddSchedulePage.ets import { router } from '@kit.ArkUI'; import { promptAction } from '@kit.ArkUI'; import { Schedule, RepeatType, AdvanceRemind } from '../model/ScheduleModel'; import { ScheduleDB } from '../utils/ScheduleDB'; import { ReminderHelper } from '../utils/ReminderHelper'; interface RepeatOption { value: RepeatType; label: string; } interface AdvanceOption { value: number; label: string; } @Entry @Component struct AddSchedulePage { @State title: string = ''; @State note: string = ''; @State selectedDate: Date = new Date(); @State selectedTime: Date = new Date(); @State repeatType: RepeatType = RepeatType.NONE; @State advanceMinutes: number = 0; @State isSaving: boolean = false; // 日期选择弹窗状态 @State showDatePicker: boolean = false; @State showTimePicker: boolean = false; private db = ScheduleDB.getInstance(); private reminderHelper = ReminderHelper.getInstance(); // 重复选项 private repeatOptions: RepeatOption[] = [ { value: RepeatType.NONE, label: '不重复' }, { value: RepeatType.DAILY, label: '每天' }, { value: RepeatType.WEEKLY, label: '每周' }, { value: RepeatType.MONTHLY, label: '每月' } ]; // 提前提醒选项 private advanceOptions: AdvanceOption[] = [ { value: 0, label: '准时提醒' }, { value: 5, label: '提前5分钟' }, { value: 10, label: '提前10分钟' }, { value: 30, label: '提前30分钟' }, { value: 60, label: '提前1小时' } ]; aboutToAppear(): void { // 默认时间设为下一个整点 const now = new Date(); now.setHours(now.getHours() + 1, 0, 0, 0); this.selectedDate = now; this.selectedTime = now; } /** * 保存日程 */ async saveSchedule(): Promise<void> { // 表单验证 if (!this.title.trim()) { promptAction.showToast({ message: '请输入日程标题' }); return; } // 合并日期和时间 const remindTime = new Date( this.selectedDate.getFullYear(), this.selectedDate.getMonth(), this.selectedDate.getDate(), this.selectedTime.getHours(), this.selectedTime.getMinutes(), 0 ).getTime(); // 验证时间 if (remindTime <= Date.now()) { promptAction.showToast({ message: '提醒时间必须晚于当前时间' }); return; } this.isSaving = true; try { // 创建日程对象 const schedule = new Schedule({ title: this.title.trim(), note: this.note.trim(), remindTime: remindTime, advanceMinutes: this.advanceMinutes, repeatType: this.repeatType, isEnabled: true }); // 设置系统提醒 const reminderId = await this.reminderHelper.setReminder(schedule); schedule.reminderId = reminderId; // 保存到数据库 const id = await this.db.insert(schedule); schedule.id = id; promptAction.showToast({ message: '日程添加成功' }); router.back(); } catch (err) { console.error('保存日程失败:', JSON.stringify(err)); promptAction.showToast({ message: '保存失败，请重试' }); } finally { this.isSaving = false; } } /** * 格式化日期显示 */ formatDate(date: Date): string { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); const weekDays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']; const weekDay = weekDays[date.getDay()]; return `${year}年${month}月${day}日 ${weekDay}`; } /** * 格式化时间显示 */ formatTimeDisplay(date: Date): string { const hour = String(date.getHours()).padStart(2, '0'); const minute = String(date.getMinutes()).padStart(2, '0'); return `${hour}:${minute}`; } build() { Column() { // 顶部导航栏 Row() { Button() { Image($r('app.media.ic_back')) .width(24) .height(24) .fillColor('#333333') } .backgroundColor(Color.Transparent) .onClick(() => router.back()) Text('添加日程') .fontSize(18) .fontWeight(FontWeight.Medium) .fontColor('#333333') .layoutWeight(1) .textAlign(TextAlign.Center) Button('保存') .fontSize(16) .fontColor('#007DFF') .backgroundColor(Color.Transparent) .enabled(!this.isSaving) .onClick(() => this.saveSchedule()) } .width('100%') .height(56) .padding({ left: 8, right: 16 }) // 表单内容 Scroll() { Column() { // 标题输入 Column() { Text('日程标题') .fontSize(14) .fontColor('#999999') .margin({ bottom: 8 }) TextInput({ placeholder: '请输入日程标题', text: this.title }) .fontSize(16) .placeholderColor('#CCCCCC') .backgroundColor('#F5F5F5') .borderRadius(8) .padding(12) .height(48) .onChange((value) => { this.title = value; }) } .width('100%') .alignItems(HorizontalAlign.Start) .padding(16) Divider().color('#EEEEEE') // 备注输入 Column() { Text('备注') .fontSize(14) .fontColor('#999999') .margin({ bottom: 8 }) TextArea({ placeholder: '添加备注（可选）', text: this.note }) .fontSize(16) .placeholderColor('#CCCCCC') .backgroundColor('#F5F5F5') .borderRadius(8) .padding(12) .height(100) .onChange((value) => { this.note = value; }) } .width('100%') .alignItems(HorizontalAlign.Start) .padding(16) Divider().color('#EEEEEE') // 日期选择 Row() { Column() { Text('提醒日期') .fontSize(14) .fontColor('#999999') Text(this.formatDate(this.selectedDate)) .fontSize(16) .fontColor('#333333') .margin({ top: 4 }) } .alignItems(HorizontalAlign.Start) Blank() Image($r('app.media.ic_arrow_right')) .width(20) .height(20) .fillColor('#CCCCCC') } .width('100%') .padding(16) .onClick(() => { DatePickerDialog.show({ start: new Date(), end: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000 * 2), // 2年后 selected: this.selectedDate, onDateAccept: (value: Date) => { this.selectedDate = value; } }); }) Divider().color('#EEEEEE') // 时间选择 Row() { Column() { Text('提醒时间') .fontSize(14) .fontColor('#999999') Text(this.formatTimeDisplay(this.selectedTime)) .fontSize(16) .fontColor('#333333') .margin({ top: 4 }) } .alignItems(HorizontalAlign.Start) Blank() Image($r('app.media.ic_arrow_right')) .width(20) .height(20) .fillColor('#CCCCCC') } .width('100%') .padding(16) .onClick(() => { TimePickerDialog.show({ selected: this.selectedTime, useMilitaryTime: true, onAccept: (value: TimePickerResult) => { const newTime = new Date(); newTime.setHours(value.hour || 0, value.minute || 0, 0, 0); this.selectedTime = newTime; } }); }) Divider().color('#EEEEEE') // 提前提醒选择 Row() { Column() { Text('提前提醒') .fontSize(14) .fontColor('#999999') Text(this.advanceOptions.find(o => o.value === this.advanceMinutes)?.label || '准时提醒') .fontSize(16) .fontColor('#333333') .margin({ top: 4 }) } .alignItems(HorizontalAlign.Start) Blank() Image($r('app.media.ic_arrow_right')) .width(20) .height(20) .fillColor('#CCCCCC') } .width('100%') .padding(16) .onClick(() => { TextPickerDialog.show({ range: this.advanceOptions.map(o => o.label), selected: this.advanceOptions.findIndex(o => o.value === this.advanceMinutes), onAccept: (value: TextPickerResult) => { const index = typeof value.index === 'number' ? value.index : 0; this.advanceMinutes = this.advanceOptions[index].value; } }); }) Divider().color('#EEEEEE') // 重复选择 Row() { Column() { Text('重复') .fontSize(14) .fontColor('#999999') Text(this.repeatOptions.find(o => o.value === this.repeatType)?.label || '不重复') .fontSize(16) .fontColor('#333333') .margin({ top: 4 }) } .alignItems(HorizontalAlign.Start) Blank() Image($r('app.media.ic_arrow_right')) .width(20) .height(20) .fillColor('#CCCCCC') } .width('100%') .padding(16) .onClick(() => { TextPickerDialog.show({ range: this.repeatOptions.map(o => o.label), selected: this.repeatOptions.findIndex(o => o.value === this.repeatType), onAccept: (value: TextPickerResult) => { const index = typeof value.index === 'number' ? value.index : 0; this.repeatType = this.repeatOptions[index].value; } }); }) // 底部间距 Column().height(100) } } .layoutWeight(1) .scrollBar(BarState.Off) } .width('100%') .height('100%') .backgroundColor(Color.White) } }6. 日程详情页面 (pages/ScheduleDetailPage.ets)// entry/src/main/ets/pages/ScheduleDetailPage.ets import { router } from '@kit.ArkUI'; import { promptAction } from '@kit.ArkUI'; import { Schedule, RepeatType, getRepeatTypeText, getAdvanceText } from '../model/ScheduleModel'; import { ScheduleDB } from '../utils/ScheduleDB'; import { ReminderHelper } from '../utils/ReminderHelper'; interface RouterParams { scheduleId?: number; } @Entry @Component struct ScheduleDetailPage { @State schedule: Schedule | null = null; @State isLoading: boolean = true; private db = ScheduleDB.getInstance(); private reminderHelper = ReminderHelper.getInstance(); private scheduleId: number = 0; async aboutToAppear(): Promise<void> { const params = router.getParams() as RouterParams; if (params?.scheduleId) { this.scheduleId = params.scheduleId; await this.loadSchedule(); } } async loadSchedule(): Promise<void> { this.isLoading = true; try { this.schedule = await this.db.getById(this.scheduleId); } catch (err) { console.error('加载日程详情失败:', JSON.stringify(err)); } finally { this.isLoading = false; } } async deleteSchedule(): Promise<void> { if (!this.schedule) return; promptAction.showDialog({ title: '确认删除', message: '删除后无法恢复，确定要删除吗？', buttons: [ { text: '取消', color: '#666666' }, { text: '删除', color: '#FF3B30' } ] }).then(async (result) => { if (result.index === 1 && this.schedule) { try { await this.reminderHelper.cancelReminder(this.schedule.reminderId); await this.db.delete(this.schedule.id); promptAction.showToast({ message: '删除成功' }); router.back(); } catch (err) { promptAction.showToast({ message: '删除失败' }); } } }); } formatDateTime(timestamp: number): string { const date = new Date(timestamp); const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); const hour = String(date.getHours()).padStart(2, '0'); const minute = String(date.getMinutes()).padStart(2, '0'); const weekDays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']; const weekDay = weekDays[date.getDay()]; return `${year}年${month}月${day}日 ${weekDay} ${hour}:${minute}`; } build() { Column() { // 顶部导航 Row() { Button() { Image($r('app.media.ic_back')) .width(24) .height(24) .fillColor('#333333') } .backgroundColor(Color.Transparent) .onClick(() => router.back()) Text('日程详情') .fontSize(18) .fontWeight(FontWeight.Medium) .fontColor('#333333') .layoutWeight(1) .textAlign(TextAlign.Center) Button() { Image($r('app.media.ic_delete')) .width(24) .height(24) .fillColor('#FF3B30') } .backgroundColor(Color.Transparent) .onClick(() => this.deleteSchedule()) } .width('100%') .height(56) .padding({ left: 8, right: 8 }) if (this.isLoading) { Column() { LoadingProgress().width(50).height(50) } .width('100%') .layoutWeight(1) .justifyContent(FlexAlign.Center) } else if (this.schedule) { Scroll() { Column() { // 标题卡片 Column() { Text(this.schedule.title) .fontSize(22) .fontWeight(FontWeight.Bold) .fontColor('#333333') if (this.schedule.note) { Text(this.schedule.note) .fontSize(15) .fontColor('#666666') .margin({ top: 12 }) } } .width('100%') .padding(20) .backgroundColor(Color.White) .borderRadius(12) .alignItems(HorizontalAlign.Start) // 详情信息卡片 Column() { // 提醒时间 this.DetailRow('提醒时间', this.formatDateTime(this.schedule.remindTime)) Divider().color('#F0F0F0').margin({ left: 16, right: 16 }) // 提前提醒 this.DetailRow('提前提醒', getAdvanceText(this.schedule.advanceMinutes)) Divider().color('#F0F0F0').margin({ left: 16, right: 16 }) // 重复 this.DetailRow('重复', getRepeatTypeText(this.schedule.repeatType)) Divider().color('#F0F0F0').margin({ left: 16, right: 16 }) // 状态 this.DetailRow('状态', this.schedule.isEnabled ? '已启用' : '已禁用') } .width('100%') .backgroundColor(Color.White) .borderRadius(12) .margin({ top: 16 }) } .padding(16) } .layoutWeight(1) } } .width('100%') .height('100%') .backgroundColor('#F5F5F5') } @Builder DetailRow(label: string, value: string) { Row() { Text(label) .fontSize(15) .fontColor('#999999') Blank() Text(value) .fontSize(15) .fontColor('#333333') } .width('100%') .padding(16) } }7. EntryAbility 入口 (entryability/EntryAbility.ets)// entry/src/main/ets/entryability/EntryAbility.ets import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; import { hilog } from '@kit.PerformanceAnalysisKit'; import { window } from '@kit.ArkUI'; import { ScheduleDB } from '../utils/ScheduleDB'; export default class EntryAbility extends UIAbility { async onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): Promise<void> { hilog.info(0x0000, 'ScheduleManager', 'Ability onCreate'); // 初始化数据库 await ScheduleDB.getInstance().init(this.context); } onDestroy(): void { hilog.info(0x0000, 'ScheduleManager', 'Ability onDestroy'); } async onWindowStageCreate(windowStage: window.WindowStage): Promise<void> { hilog.info(0x0000, 'ScheduleManager', 'Ability onWindowStageCreate'); windowStage.loadContent('pages/Index', (err) => { if (err.code) { hilog.error(0x0000, 'ScheduleManager', 'Failed to load content: %{public}s', JSON.stringify(err)); return; } hilog.info(0x0000, 'ScheduleManager', 'Succeeded in loading content'); }); } onWindowStageDestroy(): void { hilog.info(0x0000, 'ScheduleManager', 'Ability onWindowStageDestroy'); } onForeground(): void { hilog.info(0x0000, 'ScheduleManager', 'Ability onForeground'); } onBackground(): void { hilog.info(0x0000, 'ScheduleManager', 'Ability onBackground'); } }四、资源文件准备需要准备的图标资源在 entry/src/main/resources/base/media/ 目录下添加：文件名用途ic_add.svg添加按钮图标ic_back.svg返回按钮图标ic_delete.svg删除按钮图标ic_time.svg时间图标ic_arrow_right.svg右箭头图标ic_empty.svg空状态图标示例 SVG 图标内容ic_add.svg: <path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/> ic_back.svg: <path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/> 五、运行与测试步骤1：编译运行# 在 DevEco Studio 中 1. 连接真机或启动模拟器 2. 点击 Run 按钮或按 Shift+F10 3. 等待应用安装完成步骤2：功能测试1. 添加日程测试 - 点击右上角 + 按钮 - 输入标题：测试日程 - 选择时间：5分钟后 - 选择提前提醒：准时提醒 - 点击保存 2. 提醒测试 - 返回主页等待 - 5分钟后应收到系统通知 - 即使关闭应用也会收到提醒 3. 重复日程测试 - 添加一个每天重复的日程 - 验证每天都会收到提醒六、核心API说明reminderAgentManager 关键APIAPI功能说明publishReminder()发布提醒设置定时提醒，返回 reminderIdcancelReminder()取消提醒根据 reminderId 取消getValidReminders()获取有效提醒获取所有未触发的提醒cancelAllReminders()取消所有提醒取消当前应用所有提醒提醒类型// 日历提醒（精确到秒） ReminderType.REMINDER_TYPE_CALENDAR // 闹钟提醒（每天固定时间） ReminderType.REMINDER_TYPE_ALARM // 倒计时提醒 ReminderType.REMINDER_TYPE_TIMER七、注意事项权限申请：必须在 module.json5 中声明 ohos.permission.PUBLISH_AGENT_REMINDER时间限制：提醒时间必须大于当前时间数量限制：单个应用最多设置 30 个提醒重复规则：重复日程需要正确设置 repeatMonths 和 repeatDays后台保活：reminderAgentManager 由系统管理，无需应用保活这套代码已经过实测，可以直接复制使用！我是 V 哥，关注我，一起探索新技术的魅力海洋。