-
治愈计划 — PC 端治愈系智能计划管理应用构建案例1 案例概述1.1 项目背景在快节奏的学习和工作环境中,学生和上班族常常面临任务繁杂、时间管理混乱的问题。传统的待办事项应用界面生硬、功能单一,缺乏人性化的交互体验。本项目构建一款治愈系智能计划管理桌面应用,通过自然语言解析、语音输入、AI 智能解析等技术手段,帮助用户快速整理待办事项;以温暖的治愈风格 UI 和桌宠陪伴机制,让任务管理不再枯燥。1.2 项目目标目标描述智能输入支持自然语言、语音输入(在线识别 + 录音转写双模式),自动拆分多任务并提取时间/类别/优先级任务管理今日/本周/全部视图,支持筛选、搜索、增删改查温馨提醒应用内 Toast 提醒 + Electron 原生系统通知,温和文案便利贴任务可生成桌面便利贴,7 种类别对应 7 种颜色桌宠陪伴可拖动桌宠,点击互动(眨眼/飘心/随机文案),4 种心情状态,进度环桌面应用Electron 无边框窗口、系统托盘、窗口状态记忆、开机自启、NSIS 安装包治愈风格奶油白/浅绿/浅蓝/浅粉暖色系,大圆角,柔和阴影,充足留白1.3 技术选型技术选型理由桌面框架Electron 33跨平台 PC 桌面应用,支持系统通知、托盘、原生能力前端框架Vue 3 + Vite 6Composition API + <script setup>,开发体验好,构建快状态管理Pinia 2.2Vue 3 官方推荐,轻量灵活路由Vue Router 4.4Hash 模式(适配 Electron),懒加载本地存储localStorage轻量持久化,无需数据库服务语音识别Web Speech API + MediaRecorder在线识别零依赖,录音转写支持多家 STT APIAI 接口OpenAI 兼容 + Mock支持华为云盘古大模型等多家提供商 + 自定义,内置模拟测试UI 方案纯手写 CSSCSS 变量驱动治愈主题,零第三方 UI 库依赖打包electron-builder + NSISWindows 安装包,支持自定义安装目录2 环境准备2.1 开发环境要求Node.js >= 16.xnpm >= 8.x操作系统:Windows / macOS / Linux推荐 IDE:华为云 CodeArts2.2 项目初始化步骤 1:创建项目目录mkdir healing-planner && cd healing-planner步骤 2:配置 package.jsonpackage.json 关键配置:{ "name": "healing-planner", "version": "1.0.0", "description": "治愈系智能计划管理应用", "main": "electron/main.js", "scripts": { "dev": "vite", "build": "vite build", "electron:dev": "concurrently \"vite\" \"wait-on http://localhost:5173 && electron .\"", "electron:build": "vite build && electron-builder" }, "dependencies": { "pinia": "^2.2.6", "vue": "^3.5.13", "vue-router": "^4.4.5" }, "devDependencies": { "@vitejs/plugin-vue": "^5.2.1", "concurrently": "^9.1.0", "electron": "^33.2.1", "electron-builder": "^25.1.8", "vite": "^6.0.5", "wait-on": "^8.0.1" }, "build": { "appId": "com.healing.planner", "productName": "治愈计划", "directories": { "output": "dist-electron" }, "files": ["dist/**/*", "electron/**/*"], "win": { "target": [{ "target": "nsis", "arch": ["x64"] }], "icon": "public/icon.png" }, "nsis": { "oneClick": false, "allowToChangeInstallationDirectory": true, "createDesktopShortcut": true, "createStartMenuShortcut": true, "shortcutName": "治愈计划" } } } 关键说明:main 字段指向 Electron 主进程入口electron:dev 使用 concurrently 同时启动 Vite 和 Electron,wait-on 确保 Vite 就绪后才启动窗口build 配置了 NSIS 安装包,支持自定义安装目录、桌面快捷方式步骤 3:安装依赖# 推荐使用华为云镜像源加速 npm config set registry https://mirrors.huaweicloud.com/repository/npm/ npm config set electron_mirror https://mirrors.huaweicloud.com/electron/ npm install 3 项目结构设计3.1 目录结构healing-planner/ ├── electron/ # Electron 主进程 │ ├── main.js # 窗口创建、系统托盘、IPC 通信、原生通知、开机自启 │ └── preload.js # contextBridge 安全桥接,暴露 API 给渲染进程 ├── src/ # 前端源码 │ ├── assets/styles/ │ │ └── main.css # 全局样式(CSS 变量、治愈主题、动画) │ ├── components/ # UI 组件 │ │ ├── layout/ # 布局组件 │ │ │ ├── TitleBar.vue # 自定义标题栏(品牌 + 窗口控制按钮) │ │ │ └── Sidebar.vue # 侧边栏(导航 + 日期 + 进度 + 桌宠预览) │ │ ├── pet/ │ │ │ └── DesktopPet.vue # 桌宠(拖拽/互动/心情/进度环) │ │ ├── reminder/ │ │ │ └── ReminderToast.vue # 提醒 Toast 通知 │ │ ├── sticky/ │ │ │ └── StickyCard.vue # 便利贴卡片 │ │ └── task/ │ │ ├── TaskCard.vue # 任务卡片 │ │ ├── TaskEditModal.vue # 任务编辑弹窗 │ │ └── QuickAddModal.vue # 快速添加弹窗 │ ├── router/index.js # 路由配置(Hash 模式,5 页面懒加载) │ ├── services/ # 业务逻辑层 │ │ ├── storage.js # localStorage 数据持久化 │ │ ├── taskParser.js # 自然语言任务解析引擎 │ │ ├── mockAI.js # AI 模拟解析 │ │ ├── voiceInput.js # Web Speech API 在线语音识别 │ │ └── voiceRecorder.js # MediaRecorder 录音 + STT 转写 │ ├── stores/ # Pinia 状态管理 │ │ ├── taskStore.js # 任务状态 │ │ ├── stickyStore.js # 便利贴状态 │ │ ├── petStore.js # 桌宠状态 │ │ ├── reminderStore.js # 提醒状态 │ │ └── settingsStore.js # 设置状态 │ ├── views/ # 页面视图 │ │ ├── HomeView.vue # 首页仪表盘 │ │ ├── SmartInputView.vue # 智能输入页 │ │ ├── TaskListView.vue # 任务列表页 │ │ ├── StickyNotesView.vue # 便利贴页 │ │ └── SettingsView.vue # 设置页 │ ├── App.vue # 根组件 │ └── main.js # 入口文件 ├── public/ │ └── icon.png # 应用图标 ├── index.html # HTML 入口(启动加载动画) ├── package.json └── vite.config.js3.2 架构设计应用采用分层架构,各层职责清晰:┌──────────────────────────────────────────────┐ │ Views(页面层) │ 5 个页面视图,路由分发 ├──────────────────────────────────────────────┤ │ Components(组件层) │ 可复用 UI 组件 ├──────────────────────────────────────────────┤ │ Stores(状态层) │ Pinia 状态管理,5 个 Store ├──────────────────────────────────────────────┤ │ Services(服务层) │ 业务逻辑与外部接口 ├──────────────────────────────────────────────┤ │ Storage(持久层) │ localStorage 数据存储 └──────────────────────────────────────────────┘ ↕ IPC 通信 ┌──────────────────────────────────────────────┐ │ Electron 主进程 │ 窗口管理 / 托盘 / 通知 / 自启 └──────────────────────────────────────────────┘关键设计:Services 层是核心,taskParser.js 负责规则解析,mockAI.js 负责模拟 AI 解析,两者可无缝切换,方便后续接入真实大模型。Electron 主进程通过 preload.js 的 contextBridge 安全暴露 API,渲染进程通过 window.electronAPI 调用。4 核心功能实现4.1 关键节点一:Electron 桌面应用壳功能描述:Electron 主进程负责窗口管理、系统托盘、原生通知、开机自启等桌面能力,是应用从"网页"升级为"桌面应用"的核心。核心文件:electron/main.js、electron/preload.js4.1.1 无边框窗口与状态记忆// 窗口状态持久化 —— 记住用户上次的位置和大小 function loadWindowState() { try { const data = fs.readFileSync(windowStatePath, 'utf-8') return JSON.parse(data) } catch { return null } } const mainWindow = new BrowserWindow({ frame: false, // 无边框,使用自定义 TitleBar show: false, // 防白屏:先隐藏,ready-to-show 后再显示 backgroundColor: '#FFF9F0', // 与 CSS 变量一致的暖色背景 minWidth: 900, minHeight: 600, width: savedState?.width || 1200, height: savedState?.height || 800, x: savedState?.x, y: savedState?.y, webPreferences: { preload: path.join(__dirname, 'preload.js'), contextIsolation: true, // 安全隔离 nodeIntegration: false } }) 设计要点:frame: false 去掉原生标题栏,前端使用自定义 TitleBar.vue 替代show: false + ready-to-show 事件避免窗口白屏闪烁窗口位置/大小/最大化状态通过 window-state.json 持久化,重启后恢复4.1.2 系统托盘const tray = new Tray(nativeImage.createFromDataURL(ICON_BASE64)) tray.on('double-click', () => mainWindow.show()) const contextMenu = Menu.buildFromTemplate([ { label: '打开治愈计划', click: () => mainWindow.show() }, { label: '快速添加任务', click: () => { mainWindow.show() mainWindow.webContents.send('navigate', '/input') }}, { type: 'separator' }, { label: '退出', click: () => { app.quit() } } ]) tray.setContextMenu(contextMenu) 设计要点:最小化和关闭都隐藏到托盘而非退出,符合桌面应用习惯双击托盘图标恢复窗口右键菜单提供"快速添加任务"快捷入口,直接跳转到智能输入页通过 webContents.send('navigate', '/input') 实现托盘到渲染进程的导航4.1.3 IPC 通信与安全桥接preload.js 通过 contextBridge 安全暴露 API:contextBridge.exposeInMainWorld('electronAPI', { minimizeWindow: () => ipcRenderer.send('window-minimize'), maximizeWindow: () => ipcRenderer.send('window-maximize'), closeWindow: () => ipcRenderer.send('window-close'), quitWindow: () => ipcRenderer.send('window-quit'), showNotification: (title, body) => ipcRenderer.send('show-notification', title, body), isMaximized: () => ipcRenderer.invoke('window-is-maximized'), setAutoLaunch: (enabled) => ipcRenderer.invoke('set-auto-launch', enabled), getAutoLaunch: () => ipcRenderer.invoke('get-auto-launch'), onNavigate: (callback) => ipcRenderer.on('navigate', (_, path) => callback(path)) }) 设计要点:contextIsolation: true + contextBridge 确保渲染进程无法直接访问 Node.js API窗口控制(最小化/最大化/关闭)通过 IPC 发送到主进程处理isMaximized 使用 ipcRenderer.invoke 返回 Promise,支持异步查询onNavigate 监听托盘导航事件,渲染进程通过 window.electronAPI.onNavigate() 接收4.2 关键节点二:自然语言任务解析功能描述:用户输入一段自然语言文本,系统自动拆分为多个任务,提取标题、时间、类别、优先级等字段。核心文件:src/services/taskParser.js4.2.1 时间表达式匹配// 时间表达式匹配规则 —— 按优先级从具体到模糊排列(共 25 种模式) const TIME_PATTERNS = [ { pattern: /明天上午(\d+)[点时::](\d+)?分?/, handler: (m, now) => getNextDayTime(now, parseInt(m[1]), m[2] ? parseInt(m[2]) : 0) }, { pattern: /明天下午(\d+)[点时::](\d+)?分?/, handler: (m, now) => getNextDayTime(now, parseInt(m[1]) + 12, m[2] ? parseInt(m[2]) : 0) }, { pattern: /明天上午/, handler: (m, now) => getNextDayTime(now, 10, 0) }, { pattern: /明天/, handler: (m, now) => getNextDayTime(now, 9, 0) }, { pattern: /周([一二三四五六日天])前/, handler: (m, now) => getThisWeekdayTime(now, m[1]) }, { pattern: /后天/, handler: (m, now) => getRelativeDayTime(now, 2) }, { pattern: /大后天/, handler: (m, now) => getRelativeDayTime(now, 3) }, { pattern: /(\d+)天后/, handler: (m, now) => getRelativeDayTime(now, parseInt(m[1])) }, // ... 更多规则 ] 4.2.2 类别检测// 关键词映射 —— 7 种类别 const CATEGORY_KEYWORDS = { '学习': ['学习', '复习', '背', '英语', '单词', '作业', '考试', '论文', '预习'], '工作': ['报告', 'PPT', '代码', '提交', '项目', '需求', '上线', '部署', '评审'], '会议': ['会议', '开会', '组会', '讨论', '汇报', '面试', '答辩'], '生活': ['吃饭', '运动', '健身', '打扫', '洗衣', '买菜', '做饭', '休息'], '购物': ['买', '购物', '下单', '取快递', '淘宝', '京东'], '长期计划': ['计划', '目标', '习惯', '坚持', '每天', '每周'], '其他': [] // 兜底类别 } 4.2.3 提醒时间自动计算function calculateReminderTime(deadline, category) { if (!deadline) return null const d = new Date(deadline) if (category === '会议') return new Date(d.getTime() - 30 * 60 * 1000) // 会议提前 30 分钟 if (['工作', '学习'].includes(category)) return new Date(d.getTime() - 24 * 60 * 60 * 1000) // 提前 1 天 return new Date(d.getTime() - 60 * 60 * 1000) // 其他提前 1 小时 } 设计要点:时间规则按从具体到模糊的优先级排列,先匹配"明天上午十点"再匹配"明天上午"最后匹配"明天"类别检测通过关键词映射实现,共 7 种类别(学习/工作/会议/生活/购物/长期计划/其他)提醒时间根据类别智能计算:会议提前 30 分钟,工作/学习提前 1 天,其他提前 1 小时smartParse() 函数是统一入口,后续可替换为大模型 API 调用解析示例:输入:"明天上午十点交软件工程报告,下午三点开组会,周五前把PPT做完,晚上背英语单词"解析结果:#标题时间类别优先级提醒时间1交软件工程报告明天 10:00工作中明天 09:002开组会明天 15:00会议中明天 14:303把PPT做完周五 18:00工作中周四 18:004背英语单词今天 20:00学习中今天 19:004.3 关键节点三:语音输入双模式功能描述:支持两种语音输入模式——在线识别(Web Speech API)和录音转写(MediaRecorder + STT API),适应不同网络环境。核心文件:src/services/voiceInput.js、src/services/voiceRecorder.js4.3.1 在线语音识别(Web Speech API)class VoiceInputService { constructor() { const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition if (SpeechRecognition) { this.recognition = new SpeechRecognition() this.recognition.lang = 'zh-CN' // 中文识别 this.recognition.continuous = true // 连续识别模式 this.recognition.interimResults = true // 返回中间结果 this.supported = true } } start(onResult, onEnd, onError) { this.recognition.onresult = (event) => { let final = '', interim = '' for (let i = event.resultIndex; i < event.results.length; i++) { if (event.results[i].isFinal) { final += event.results[i][0].transcript } else { interim += event.results[i][0].transcript } } onResult?.({ final, interim, all: final + interim }) } this.recognition.start() } } 设计要点:continuous: true 允许持续识别,用户可以说多句话interimResults: true 返回中间结果,实现实时反馈不支持时 supported 为 false,UI 层显示提示信息4.3.2 录音转写(MediaRecorder + STT API)class VoiceRecorderService { async startRecording() { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }) // 自动检测支持的 MIME 类型:webm/opus > webm > ogg > mp4 const mimeType = ['audio/webm;codecs=opus', 'audio/webm', 'audio/ogg', 'audio/mp4'] .find(type => MediaRecorder.isTypeSupported(type)) this.mediaRecorder = new MediaRecorder(stream, { mimeType }) this.chunks = [] this.mediaRecorder.ondataavailable = (e) => this.chunks.push(e.data) this.mediaRecorder.start() } async stopAndTranscribe() { return new Promise((resolve) => { this.mediaRecorder.onstop = async () => { const blob = new Blob(this.chunks, { type: this.mediaRecorder.mimeType }) const text = await sttService.transcribeWithAPI(blob) resolve(text) } this.mediaRecorder.stop() }) } } class STTService { async transcribeWithAPI(audioBlob) { const formData = new FormData() formData.append('file', audioBlob, 'recording.webm') const response = await fetch(apiEndpoint, { method: 'POST', headers: { 'Authorization': `Bearer ${apiKey}` }, body: formData }) return response.json() } } 设计要点:双模式设计:在线识别无需额外配置,录音转写支持华为云语音交互等多家 STT 服务MIME 类型自动检测,优先使用 webm/opus 高质量编码录音权限拒绝时提供友好提示STT API 配置在设置页统一管理,API Key 应从安全配置或环境变量读取,切勿硬编码或提交到代码仓库4.4 关键节点四:AI 智能解析与 Mock 模式功能描述:支持接入 OpenAI 兼容 API 进行智能解析,同时内置 Mock 模式供无 API 场景测试。核心文件:src/services/mockAI.js、src/views/SmartInputView.vueMock 解析关键代码:function generateMockResponse(text) { const segments = text.split(/[,,;;。\n]/).filter(s => s.trim()) const tasks = segments.map(seg => { if (/学习|复习|背|英语|单词/.test(seg)) category = '学习' else if (/报告|PPT|代码|提交/.test(seg)) category = '工作' else if (/会议|开会|组会/.test(seg)) category = '会议' if (/紧急|重要/.test(seg)) priority = '高' return { title, category, priority, deadline, ... } }) return { tasks } } export function mockAIParse(text) { return new Promise((resolve) => { setTimeout(() => resolve(generateMockResponse(text)), 800 + Math.random() * 600) }) } 设计要点:Mock 模式模拟了 800~1400ms 的网络延迟,体验接近真实 API解析逻辑与 taskParser.js 互补,Mock 侧重语义理解,规则解析侧重时间提取在智能输入页,当用户未配置 API 或选择了"模拟测试"时,自动使用 MockAI 调用流程:用户点击「AI 解析」 │ ├── API 已配置且非 Mock → fetch 真实 API → 解析 JSON 结果 │ └── 未配置或 Mock 模式 → mockAIParse() → 模拟解析结果支持的 AI 提供商:提供商API 格式说明模拟测试Mock内置模拟,无需 API Key华为云盘古大模型OpenAI 兼容华为云官方大模型服务,推荐优先接入自定义OpenAI 兼容任意 OpenAI 兼容接口4.5 关键节点五:可拖动桌宠功能描述:桌宠可自由拖动到屏幕任意位置,点击触发互动动画和治愈文案,4 种心情状态随任务进度变化。核心文件:src/components/pet/DesktopPet.vue4.5.1 拖动与点击交互function startDrag(e) { if (e.button !== 0) return // 仅左键 isDragging.value = true hasMoved = false // 区分点击和拖动 dragOffsetX = e.clientX - posX.value dragOffsetY = e.clientY - posY.value } function onDrag(e) { if (!isDragging.value) return hasMoved = true posX.value = e.clientX - dragOffsetX posY.value = e.clientY - dragOffsetY } function stopDrag() { isDragging.value = false // 边界吸附:防止拖出可视区域 posX.value = Math.max(0, Math.min(posX.value, window.innerWidth - 100)) posY.value = Math.max(0, Math.min(posY.value, window.innerHeight - 160)) } function handlePetClick() { if (hasMoved) return // 拖动过则不触发点击 isWinking.value = true // 眨眼动画 setTimeout(() => { isWinking.value = false }, 400) showHearts.value = true // 飘心动画 setTimeout(() => { showHearts.value = false }, 900) petStore.currentMessage = randomMessage() } 4.5.2 心情系统// 4 种心情状态,随任务进度自动切换 const MOOD_MAP = { happy: { color: '#7DB88A', label: '开心' }, // 默认状态 excited: { color: '#F0B87A', label: '兴奋' }, // 完成任务时 worried: { color: '#89C4E1', label: '担心' }, // 任务过多时 sad: { color: '#89C4E1', label: '难过' } // 任务逾期时 } 设计要点:通过 hasMoved 标记区分拖动和点击,避免拖动结束触发点击事件document 级别的 mousemove/mouseup 监听确保拖出组件范围仍能正常工作松手后自动吸附到屏幕边界内,防止桌宠被拖出可视区域互动文案 13 条,涵盖摸头、鼓励、闲聊等场景心情状态由 petStore 管理,根据任务完成/逾期/数量自动切换4.6 关键节点六:数据持久化功能描述:所有数据(任务、便利贴、设置、桌宠状态)通过 localStorage 持久化,应用关闭后数据不丢失。核心文件:src/services/storage.jsconst DB_KEY = 'healing_planner_db' function getDB() { const raw = localStorage.getItem(DB_KEY) if (!raw) { const initial = { tasks: [], stickies: [], settings: { petName: '小团子', petType: 'dumpling', reminderEnabled: true, reminderSound: true, apiProvider: 'mock', apiEndpoint: '', apiKey: '', apiModel: '', voiceMode: 'online', sttApiProvider: 'whisper', sttApiEndpoint: '', sttApiKey: '', sttApiModel: '' }, petState: { mood: 'happy', lastMessage: '你好呀,今天也要加油哦~', totalCompleted: 0 } } localStorage.setItem(DB_KEY, JSON.stringify(initial)) return initial } return JSON.parse(raw) } export const storage = { getTasks() { return getDB().tasks }, addTask(task) { const db = getDB(); db.tasks.push(task); saveDB(db) }, updateTask(id, updates) { /* 查找 → 合并 → 保存 */ }, deleteTask(id) { /* 过滤 → 保存 */ }, // 便利贴、设置、桌宠同理 } 设计要点:单一 DB_KEY 管理所有数据,结构清晰首次访问自动初始化默认数据所有 Store 通过 storage 对象读写,保证数据一致性后续可替换为 SQLite 或远程 API,只需修改 storage.js4.7 关键节点七:治愈风格 UI 体系功能描述:基于 CSS 变量构建完整的治愈风格设计系统,全局统一配色、圆角、阴影。核心文件:src/assets/styles/main.css4.7.1 色彩体系:root { /* 背景色系 —— 温暖奶油白到浅杏的层次 */ --bg-primary: #FFF9F0; /* 主背景(奶油白) */ --bg-secondary: #FFF5E8; /* 侧边栏/次要背景 */ --bg-card: #FFFFFF; /* 卡片背景 */ --bg-hover: #FFF0D6; /* 悬停背景 */ --bg-input: #FFFCF5; /* 输入框背景 */ /* 文字色系 —— 暖棕色系,高对比度但不刺眼 */ --text-primary: #5D4E37; /* 主文字(暖棕) */ --text-secondary: #9E8E78; /* 次要文字 */ --text-muted: #C4B8A6; /* 弱化文字 */ /* 主色调 —— 温暖绿 */ --accent: #7DB88A; --accent-hover: #6AA878; /* 辅助色 —— 柔和的治愈色系 */ --accent-green: #8BC99A; --accent-blue: #89C4E1; --accent-pink: #F2A7B3; --accent-yellow: #F5D98A; --accent-orange: #F0B87A; --accent-cream: #F5E6C8; } 4.7.2 类别色与优先级色:root { /* 7 种任务类别色 —— 浅底深字原则 */ --color-study: #E8F5E9; --color-study-text: #4CAF50; /* 学习 - 浅绿 */ --color-work: #E3F2FD; --color-work-text: #64B5F6; /* 工作 - 浅蓝 */ --color-meeting: #F3E5F5; --color-meeting-text: #BA68C8; /* 会议 - 浅紫 */ --color-life: #FFF8E1; --color-life-text: #FFB74D; /* 生活 - 浅黄 */ --color-shopping: #FCE4EC; --color-shopping-text: #F48FB1; /* 购物 - 浅粉 */ --color-longterm: #E0F7FA; --color-longterm-text: #4DD0E1; /* 长期计划 - 浅青 */ --color-other: #EFEBE9; --color-other-text: #A1887F; /* 其他 - 浅棕 */ /* 3 级优先级色 */ --priority-high: #FFCDD2; --priority-high-bg: #FFF0F0; --priority-high-text: #E57373; --priority-medium: #FFF9C4; --priority-medium-bg: #FFFCF0; --priority-medium-text: #C9A94E; --priority-low: #C8E6C9; --priority-low-bg: #F0FFF0; --priority-low-text: #81C784; } 4.7.3 圆角与阴影体系:root { /* 圆角 —— 从 12px 到胶囊形的渐进 */ --radius-sm: 12px; --radius-md: 16px; --radius-lg: 20px; --radius-xl: 26px; --radius-full: 9999px; /* 胶囊形(药丸按钮/标签) */ /* 阴影 —— 暖棕色调的柔和阴影 */ --shadow-soft: 0 2px 12px rgba(139,109,70,0.06); --shadow-card: 0 4px 20px rgba(139,109,70,0.08); --shadow-elevated: 0 8px 40px rgba(139,109,70,0.12); } 设计要点:所有颜色、圆角、阴影通过 CSS 变量管理,一处修改全局生效类别色采用"浅底深字"原则,7 种类别对应 7 组配色,确保可读性阴影使用暖棕色调(rgba(139,109,70,...)),与整体暖色系协调圆角最小 12px,配合充足留白营造柔和感输入框聚焦增加暖色光晕 box-shadow: 0 0 0 3px rgba(125,184,138,0.15)4.7.4 全局动画/* 页面切换动画 */ .fade-enter-active, .fade-leave-active { transition: opacity 0.2s ease } .fade-enter-from, .fade-leave-to { opacity: 0 } /* 通用动画 */ @keyframes fadeInUp { from { opacity: 0; transform: translateY(16px) } to { opacity: 1; transform: translateY(0) } } @keyframes gentleBounce { 0%, 100% { transform: translateY(0) } 50% { transform: translateY(-6px) } } @keyframes softPulse { 0%, 100% { opacity: 1 } 50% { opacity: 0.7 } } @keyframes shimmer { 0% { background-position: -200% 0 } 100% { background-position: 200% 0 } } 4.8 关键节点八:自定义标题栏与侧边栏功能描述:替代原生标题栏,提供品牌展示和窗口控制;侧边栏集成导航、日期、任务统计和桌宠预览。核心文件:src/components/layout/TitleBar.vue、src/components/layout/Sidebar.vue4.8.1 自定义标题栏左侧:品牌标识 🌸 治愈计划(带弹跳动画)右侧:最小化 / 最大化(图标随状态切换)/ 关闭按钮关闭按钮悬停变粉色支持 -webkit-app-region: drag 拖拽移动窗口通过 window.electronAPI.isMaximized() 查询最大化状态4.8.2 侧边栏顶部:日期数字 + 星期显示导航菜单:今日计划(带任务数徽章)/ 智能输入 / 全部任务 / 便利贴 / 设置底部:桌宠迷你头像 + 当前消息 + 今日进度条当前路由高亮,悬停右移动画4.9 关键节点九:提醒系统功能描述:每 30 秒检查一次任务提醒时间,匹配后触发应用内 Toast 和 Electron 原生通知。核心文件:src/stores/reminderStore.js、src/components/reminder/ReminderToast.vue// 提醒检查逻辑 function checkReminders() { const now = new Date() const tasks = storage.getTasks() tasks.forEach(task => { if (task.reminderTime && !task.reminded && task.status !== 'completed') { const reminderTime = new Date(task.reminderTime) // 匹配当前 1 分钟内 if (Math.abs(now - reminderTime) < 60 * 1000) { const message = randomReminderMessage(task.title) activeReminders.push({ task, message }) // 触发 Electron 原生通知 window.electronAPI?.showNotification('治愈计划提醒', message) } } }) } 设计要点:提醒文案从 5 条温馨模板中随机选择,如"该做 xxx 啦,加油哦~"应用内 Toast 固定右上角,左侧黄色边线,slide 入场动画同时触发 Electron 原生系统通知(即使窗口最小化也能看到)已提醒的任务标记 reminded: true,避免重复提醒5 构建与运行5.1 浏览器开发模式npm run dev启动后访问 http://localhost:5173。注意:语音输入功能需使用 Chrome 浏览器,且需要网络连接。Electron 特有功能(托盘、原生通知、开机自启)在浏览器模式下不可用。5.2 Electron 桌面应用开发模式npm run electron:dev同时启动 Vite 开发服务器和 Electron 窗口,支持热更新。5.3 构建 Windows 安装包npm run electron:build构建产物在 dist-electron/ 目录下,生成 NSIS 安装包(.exe),支持:自定义安装目录桌面快捷方式开始菜单快捷方式6 执行效果展示6.1 启动加载页应用启动时显示加载动画:🌸 图标弹跳 + “治愈计划加载中…” + 三点脉冲动画,加载完成后淡出移除。6.2 首页仪表盘顶部问候语(根据时段显示"早上好/上午好/中午好/下午好/晚上好/夜深了")日期 + 励志文案(“每一步都算数”、"慢慢来,比较快"等轮换)进度卡片:SVG 环形进度 + 进度条 + 完成数/总数 + 鼓励文案今日任务列表(按优先级排序),使用 TaskCard 组件即将到来任务(未来 3 天内,最多 5 条)空状态引导6.3 智能输入页文本输入区:大文本框,支持 Ctrl+Enter 快捷解析语音输入:在线识别 / 录音转写双模式切换AI 解析:支持模拟解析和真实 API 调用(华为云盘古大模型等多个提供商 + 自定义)本地智能解析(taskParser.smartParse)解析结果展示:可编辑的任务卡片(标题/截止时间/提醒时间/类别/优先级)确认添加全部任务输入小贴士(3 个提示卡片)6.4 任务列表页时间筛选标签:今日 / 本周 / 全部下拉筛选:类别 / 优先级 / 状态任务卡片:左侧优先级色条,圆形完成按钮,类别/优先级标签,hover 显示操作按钮编辑弹窗:类别/优先级 Chip 选择器,毛玻璃遮罩空状态引导6.5 便利贴页彩色便利贴卡片:7 种类别对应 7 种背景色悬停微旋转 + 上浮效果一键完成 / 删除完成便利贴时同步完成原任务6.6 桌宠位置:右下角,可拖动到任意位置点击互动:眨眼 + 飘心 + 随机文案(13 条)4 种心情:开心(绿色)/ 兴奋(橙色,弹跳)/ 担心(浅蓝)/ 难过(浅蓝)进度环:SVG 显示今日完成百分比侧边栏底部迷你预览6.7 设置页AI 智能解析配置:华为云盘古大模型等多个提供商选择,API 地址/Key/模型配置,连接测试语音转写 API 配置:华为云语音交互等多个提供商选择,API 地址/Key/模型桌宠设置:名称 / 类型(小团子/小猫/小熊/云朵)提醒设置:启用提醒 / 提醒音效开关桌面应用设置:开机自启动(仅 Electron 环境显示)数据管理:导出 JSON / 导入 JSON / 清除数据关于:版本信息7 AI 测试指南应用内置了模拟测试模式,无需任何真实 API 即可体验 AI 智能解析:进入 设置 页面在「AI 智能解析」区域,点击 模拟测试 卡片点击「测试连接」,提示模拟模式已启用进入 智能输入 页面,输入:明天上午十点交软件工程报告,下午三点开组会,周五前把PPT做完,晚上背英语单词点击「AI 解析」按钮,等待约 1 秒,即可看到解析结果如需接入真实 API,选择对应服务商,填入 API Key 和模型名称即可。8 扩展方向方向说明改动点接入大模型替换 smartParse() 为大模型 API 调用taskParser.js语音识别升级替换 Web Speech API 为专业语音服务voiceInput.js桌面悬浮便利贴Electron BrowserWindow 实现独立小窗口electron/main.js + 新建窗口管理云同步数据存储替换为远程 APIstorage.jsSQLite 存储替换 localStorage 为 better-sqlite3storage.js + Electron IPC复杂桌宠动画Lottie / Spine 动画替换静态 SVGDesktopPet.vue主题切换支持多种治愈色系主题main.css + settingsStore.js快捷键全局快捷键呼出窗口/快速添加electron/main.js + preload.js9 总结本案例完整演示了如何使用 Electron + Vue 3 + Pinia 技术栈构建一个 PC 端治愈系智能计划管理桌面应用。核心亮点包括:Electron 桌面应用壳:无边框窗口 + 系统托盘 + 窗口状态记忆 + 开机自启 + NSIS 安装包自然语言解析引擎:25 种时间模式 + 7 种类别检测 + 智能提醒时间计算,代码结构支持无缝切换到大模型语音输入双模式:在线识别(Web Speech API)+ 录音转写(MediaRecorder + STT API),适应不同网络环境AI Mock 模式:内置模拟解析,无需 API 即可测试完整流程,支持 6 家提供商 + 自定义可拖动桌宠:拖动 + 点击互动 + 4 种心情 + 进度环 + 13 条治愈文案治愈风格 UI:CSS 变量驱动的暖色设计系统,奶油白/浅绿/浅蓝/浅粉配色,大圆角,柔和阴影提醒系统:应用内 Toast + Electron 原生通知双通道,温馨文案模块化架构:Services / Stores / Components 三层分离,Electron 主进程通过 contextBridge 安全通信,易于扩展项目代码已模块化组织,各功能模块独立可替换,方便后续接入大模型接口、语音识别服务和系统通知能力。
-
vscode关闭后CodeArts Agent 历史对话无法重新导入。 vscode关闭重新打开是常规操作,但关闭重新打开后,在历史对话中可以看到历史对话,但无法点击导入,这就很尴尬,每次重开后要重新梳理需求。
-
在公司内部使用VSCode-huawei 2025版代码编辑器开发,CodeArts支持这个版本的VSCode吗?插件市场里搜不到CodeArts插件
-
VScode上ESP32开发插件安装失败怎么办?
-
怎么在VSCode里DeepSeek插件安装与配置
-
VSCode有哪些扩展能显著提升前端开发体验? VSCode扩展推荐
-
VSCode的配置文件(JSON)如何灵活管理不同工作区
-
Visual Studio Code (VS Code) 和 VSCodium的相似与区别
-
如下图,若本地调试受限,为何迟迟不开放远端开发(ssh/sstp) ?
-
案例介绍本案例选择Flask作为后端服务,并借助开发者空间云开发环境提供的免费GaussDB数据库和HCE2.0开发环境进行本地部署,便捷地提供了一种能够保障题库安全的在线考试系统。案例内容一、概述1.1 案例介绍Flask是一个用Python编写的轻量级Web应用框架。它被称为“微框架”,核心功能简单,提供了构建Web应用所需的最少工具。华为开发者空间,是为全球开发者打造的专属开发者空间,致力于为每位开发者提供一台云开发环境、一套开发工具和云上存储空间,汇聚昇腾、鸿蒙、鲲鹏、GaussDB、欧拉等华为各项根技术的开发工具资源,并提供配套案例指导开发者 从开发编码到应用调测,基于华为根技术生态高效便捷的知识学习、技术体验、应用创新。GaussDB是华为自主创新研发的分布式关系型数据库。该产品支持分布式事务,同城跨AZ部署,数据0丢失,支持1000+的扩展能力,PB级海量存储。同时拥有云上高可用,高可靠,高安全,弹性伸缩,一键部署,快速备份恢复,监控告警等关键能力,能为企业提供功能全面,稳定可靠,扩展性强,性能优越的企业级数据库服务.本案例借助开发者空间云开发环境,通过Flask与开发者空间提供的免费GaussDB数据库实例对接,直接使用 SQL 语句对GaussDB 数据库实例进行操作,完成一个现代化 Web 应用构建。1.2 适用对象企业个人开发者高校学生1.3 案例时间本案例总时长预计40分钟。1.4 案例流程 说明:申请华为云开发者空间的GaussDB数据库;进行华为云开发者空间的云开发环境进行文件创建与编辑;启用Flask Web 服务,适配GaussDB,在GaussDB在线操作页面建表于插入题目数据。1.5 资源总览本案例预计花费0.33元。资源名称规格单价(元)时长(分钟)华为开发者空间 - 云开发环境鲲鹏通用计算增强型 kc1 | 2vCPUs | 4G | HCE2.0免费40华为开发者空间 - 生态版GaussDB单副本集中式版 | 4 vCPUs | 16G | HCE OS 64bit (200GB)免费40弹性公网IP按需计费 5Mbit/s0.33元/时40 二、案例准备工作2.1 配置云开发环境根据案例《开发者空间 - 云开发环境使用指导》,请查看下面链接,配置云开发环境,并通过vscode链接工具登录云开发环境。本地VSCode基于华为开发者空间云开发环境完成小程序开发2.1 领取免费版GaussDB免费领取GaussDB在线试用版(2025年06月 21日 - 2025年 12月 31日),有1000个名额,数量有限,链接如下:免费领取GaussDB在线试用版(1-3工作日内报名审核通过后会有短信通知)(1)报名申请审核通过后,进入开发者空间工作台,会有GaussDB免费试用提示,点击立即开通即可。(2)填写GaussDB数据库开通参数:虚拟私有云:如下图到控制台创建,采用默认参数即可。安全组:采用默认安全组。管理员密码:自定义设置,保存好后续使用。(3)设置完后点击开通,开通成功后如下图提示:(4)开通成功后,点击立即前往进入GaussDB数据库实例页面,等待状态切换为"正常",如下图所示: (5)绑定弹性公网EIP,如果需要远程操作数据库必须要绑定公网EIP(EIP需购买,按需计费0.33元/小时,以控制台为准)点击数据库实例名称,进入GaussDB的基本信息界面: 点击绑定弹性公网IP,若开发者没有购买,则需要购买弹性公网IP。购买时选择按需计费,购买成功后截图如下:回到数据库基本信息界面,再次绑定公网IP,绑定成功截图如下:2.2 验证云开发环境与GaussDB互通通过xshell或windows命令终端登录云开发环境,测试GaussDB能否连接,如下是通 2.3 登录GaussDB数据库回到云数据库控制台,登录数据库:点击登录,再点击同意并继续输入密码,点击测试连接,再次点击登录,即可登录成功2.3.1 新建数据库登录成功后,点击新建数据库输入数据库名称 test 点击 确定 按钮,数据库创建成功: 2.3.2 创建数据库表点击 SQL操作,点击SQL查询库名选择test,Schema选择 public ,执行以下SQL语句,创建表:CREATE TABLE t_question ( id SERIAL PRIMARY KEY, question_text TEXT NOT NULL, option_a VARCHAR(255) NOT NULL, option_b VARCHAR(255) NOT NULL, option_c VARCHAR(255) NOT NULL, option_d VARCHAR(255) NOT NULL, correct_answer CHAR(1) NOT NULL);创建表成功:向表中插入测试题目和答案INSERT INTO t_question (question_text, option_a, option_b, option_c, option_d, correct_answer)VALUES ('华为云的旗舰数据库服务是什么?', 'RDS for MySQL', 'GaussDB', 'DWS', 'TaurusDB', 'B'), ('在华为云上,用于管理容器化应用的PaaS服务叫什么?', 'CCE', 'ECS', 'OBS', 'ELB', 'A'), ('华为云上用于对象存储的服务名称是?', 'SFS', 'EVS', 'OBS', 'VPC', 'C');执行SQL,插入成功:查询表中数据:SELECT *FROM t_question删除指定题目:DELETE FROM t_questionWHERE id = 1;再执行查询的SQL语句:SELECT *FROM t_question可以看到已成功删除id为1的数据。 三、 Flask部署与Python驱动安装通过本地IDE连接云开发环境,参考使用VSCode连接云开发环境通过VSCode连接成功后,打开终端 3.1 创建项目虚拟环境因为业务场景的Python开发,多数都是构建一个大型应用程序,并且不希望各种组件的各种版本之间相互冲突,所以需要设置一个虚拟环境。先需要更新下载源。执行如下命令 sudo yum -y updatesudo yum -y upgradepip3 install virtualenv -i https://repo.huaweicloud.com/repository/pypi/simple/ #安装virtualenvpython3 -m venv myenv #创建虚拟环境source myenv/bin/activate #激活环境 环境激活后,用户名前会有(myenv)字样,如上图所示。3.2 安装Flask Web 框架。安装之前,需要更新下pip。python3 -m pip install --upgrade pip -i https://pypi.tuna.tsinghua.edu.cn/simplepip3 install flask -i https://pypi.tuna.tsinghua.edu.cn/simple3.3 安装GaussDB数据库对应的Python驱动GaussDB数据库对应的Python驱动为psycopg2。即Flask Web 框架允许通过psycopg2驱动,连接GaussDB数据库,并操作数据对象。不建议直接用pip去安装psycopg2驱动。1.从华为云GaussDB数据库官方文档中下载驱动。wget https://dbs-download.obs.cn-north-1.myhuaweicloud.com/GaussDB/1750076538851/GaussDB_driver.zipunzip GaussDB_driver.zipcd GaussDB_driver/Centralized/Hce2.0_arm_64/tar zxvf GaussDB-Kernel_505.2.1_Hce_64bit_Python.tar.gz 解压后有两个文件夹:psycopg2:psycopg2库文件。lib:lib库文件。2.使用root用户将psycopg2复制到python安装目录下的site-packages文件夹下。sudo cp psycopg2 $(python3 -c 'import site; print(site.getsitepackages()[0])') -r3.修改psycopg2目录权限为755。sudo chmod 755 $(python3 -c 'import site; print(site.getsitepackages()[0])')/psycopg2 -R4.将psycopg2目录添加到环境变量$PYTHONPATH,并使之生效。export PYTHONPATH=$(python3 -c 'import site; print(site.getsitepackages()[0])'):$PYTHONPATH5.将解压后的上述lib目录,配置在环境变量LD_LIBRARY_PATH中。本案例对应的lib目录是/home/xxx/GaussDB_driver/Centralized/Hce2.0_arm_64/lib,读者根据自己实际情况做修改export LD_LIBRARY_PATH=/home/xxx/GaussDB_driver/Centralized/Hce2.0_arm_64/lib:$LD_LIBRARY_PATH6.复制驱动到 python 的 site-packagessudo cp -r /home/developer/GaussDB_driver/Centralized/Hce2.0_arm_64/psycopg2 \ /usr/lib/python3.9/site-packages/7.修改权限sudo chmod -R 755 /usr/lib/python3.9/site-packages/psycopg2注意:如果引入psycopg2报找不到libpq.so.5.5,是因为环境变量LD_LIBRARY_PATH没有包含libpq.so.5.5的目录路径,由于上述步骤5用export设置的临时环境变量,所以在新的会话中需要重新设置LD_LIBRARY_PATH指定之前解压的驱动lib目录3.4 通过本地IDE创建Flask Web工程在IDE里终端窗口创建web项目并进入。mkdir ~/testcd ~/test3.5 创建test项目的Web应用(1)在test的项目目录里,创建app.py文件和子目录templates以及templates下的index.html app.py:文件作为考试系统的后端,与GaussDB进行通信,对前端index.html提供数据index.html:用来展示题目与选项 (2)把以下代码复制粘贴到app.py,注意修改为自己的GaussDB的ip、账号、密码、数据库from flask import Flask, render_template, request, jsonifyimport psycopg2import psycopg2.extrasapp = Flask(__name__)# Database connection configurationdb_config = { 'host': 'your_gaussdb_host', 'user': 'your_user', 'password': 'your_password', 'database': 'test', 'port': 8000}def get_db_connection(): """Establish database connection""" conn = psycopg2.connect(**db_config) return conn@app.route('/')def index(): """Home route: Display all exam questions""" conn = get_db_connection() cursor = conn.cursor(cursor_factory=psycopg2.extras.DictCursor) cursor.execute('SELECT * FROM t_question') questions = cursor.fetchall() cursor.close() conn.close() return render_template('index.html', questions=questions)@app.route('/submit_all_answers', methods=['POST'])def submit_all_answers(): """Submit all answers route: Check if user answers are correct""" answers = {} # Dictionary to store question IDs and user answers correct_count = 0 # Loop through all the answers in the form data for question_index in range(1, len(request.form) // 2 + 1): question_id = request.form[f"question_id_{question_index}"] user_answer = request.form.get(f"answer_{question_index}") conn = get_db_connection() cursor = conn.cursor(cursor_factory=psycopg2.extras.DictCursor) cursor.execute('SELECT correct_answer FROM t_question WHERE id = %s', (question_id,)) result = cursor.fetchone() if result: correct_answer = result['correct_answer'] if user_answer == correct_answer: correct_count += 1 cursor.close() conn.close() return jsonify({ 'message': f'You answered {correct_count} questions correctly!', 'correct_count': correct_count })if __name__ == '__main__': app.run(debug=True, host='0.0.0.0', port=8080)(3)把以下代码复制粘贴到index.html中<!DOCTYPE html><html><head> <title>在线考试系统</title> <style> body { font-family: Arial, sans-serif; background-color: #f4f7fa; margin: 0; padding: 0; } .container { max-width: 900px; margin: 50px auto; background-color: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); } h1 { text-align: center; color: #333; } .question-container { border-bottom: 1px solid #ccc; margin-bottom: 20px; padding-bottom: 15px; } .question-container p { font-size: 18px; margin: 10px 0; } .question-container label { display: block; font-size: 16px; margin-bottom: 8px; } .question-container input[type="radio"] { margin-right: 10px; } .submit-btn { background-color: #4CAF50; color: white; padding: 12px 24px; border: none; border-radius: 5px; cursor: pointer; font-size: 16px; } .submit-btn:hover { background-color: #45a049; } </style></head><body> <div class="container"> <h1>在线考试</h1> <form action="/submit_all_answers" method="post"> {% for question in questions %} <div class="question-container"> <p><strong>{{ loop.index }}. {{ question.question_text }}</strong></p> <input type="hidden" name="question_id_{{ loop.index }}" value="{{ question.id }}"> <label><input type="radio" name="answer_{{ loop.index }}" value="A"> A. {{ question.option_a }}</label> <label><input type="radio" name="answer_{{ loop.index }}" value="B"> B. {{ question.option_b }}</label> <label><input type="radio" name="answer_{{ loop.index }}" value="C"> C. {{ question.option_c }}</label> <label><input type="radio" name="answer_{{ loop.index }}" value="D"> D. {{ question.option_d }}</label> </div> {% endfor %} <div style="text-align: center;"> <input type="submit" value="提交所有答案" class="submit-btn"> </div> </form> </div></body></html>(4)然后在终端中运行python3 app.py 在浏览器中打开http://127.0.0.1:8080即可看到在线考试页面 四、释放资源1. 删除虚拟私有云VPC进入虚拟私有云控制台,点击全选按钮,点击“删除”,会弹出关联资源列表的对话框,删除子网和弹性公网IP2. 删除弹性公网IP EIP进入弹性公网IP控制台,首先解绑弹性公网IP,然后删除。3. 删除GaussDB数据库实例进入GaussDB控制台,点击“更多 -> 删除实例”。注意:删除的实例会被移至回收站,但达到回收站策略设置的天数后,该实例会被永久删除,请谨慎操作。 至此,基于华为开发者空间云开发环境+GaussDB实现在线考试系统案例已全部完成。我正在参加【案例共创】第6期 开发者空间-基于云开发环境和GaussDB构建应用 cid:link_3
-
[OHOS ERROR] [330/331] ACTION //device/hisilicon/hispark_pegasus/sdk_liteos:run_wifiiot_scons(//build/lite/toolchain:riscv32-unknown-elf)[OHOS ERROR] FAILED: obj/device/hisilicon/hispark_pegasus/sdk_liteos/run_wifiiot_scons_build_ext_components.txt[OHOS ERROR] d:\devtools_hi3861v100_v1.0\thirdparty\python38\python.exe ../../../build/lite/build_ext_components.py --path=../../../device/hisilicon/hispark_pegasus/sdk_liteos "--command=sh hm_build.sh D:/Xuanxi/src/out/hispark_pegasus/wifiiot_hispark_pegasus win" --target_dir=D:/Xuanxi/src/out/hispark_pegasus/wifiiot_hispark_pegasus/obj/device/hisilicon/hispark_pegasus/sdk_liteos/build.log --out_dir=D:/Xuanxi/src/out/hispark_pegasus/wifiiot_hispark_pegasus/error.log[OHOS ERROR] '/d/Xuanxi/src/device/hisilicon/hispark_pegasus/sdk_liteos/build/win_scripts/app/demo/app.json' -> '/d/Xuanxi/src/device/hisilicon/hispark_pegasus/sdk_liteos/app/demo/app.json'[OHOS ERROR] '/d/Xuanxi/src/device/hisilicon/hispark_pegasus/sdk_liteos/build/win_scripts/boot/flashboot/Makefile' -> '/d/Xuanxi/src/device/hisilicon/hispark_pegasus/sdk_liteos/boot/flashboot/Makefile' [OHOS ERROR] '/d/Xuanxi/src/device/hisilicon/hispark_pegasus/sdk_liteos/build/win_scripts/boot/flashboot/SConscript' -> '/d/Xuanxi/src/device/hisilicon/hispark_pegasus/sdk_liteos/boot/flashboot/SConscript'[OHOS ERROR] '/d/Xuanxi/src/device/hisilicon/hispark_pegasus/sdk_liteos/build/win_scripts/boot/loaderboot/SConscript' -> '/d/Xuanxi/src/device/hisilicon/hispark_pegasus/sdk_liteos/boot/loaderboot/SConscript'[OHOS ERROR] '/d/Xuanxi/src/device/hisilicon/hispark_pegasus/sdk_liteos/build/win_scripts/build/make_scripts/config.mk' -> '/d/Xuanxi/src/device/hisilicon/hispark_pegasus/sdk_liteos/build/make_scripts/config.mk'[OHOS ERROR] '/d/Xuanxi/src/device/hisilicon/hispark_pegasus/sdk_liteos/build/win_scripts/build/scripts/scons_env_cfg.py' -> '/d/Xuanxi/src/device/hisilicon/hispark_pegasus/sdk_liteos/build/scripts/scons_env_cfg.py'[OHOS ERROR] '/d/Xuanxi/src/device/hisilicon/hispark_pegasus/sdk_liteos/build/win_scripts/build/scripts/scons_utils.py' -> '/d/Xuanxi/src/device/hisilicon/hispark_pegasus/sdk_liteos/build/scripts/scons_utils.py'[OHOS ERROR] '/d/Xuanxi/src/device/hisilicon/hispark_pegasus/sdk_liteos/build/win_scripts/build_patch.sh' -> '/d/Xuanxi/src/device/hisilicon/hispark_pegasus/sdk_liteos/build_patch.sh'[OHOS ERROR] '/d/Xuanxi/src/device/hisilicon/hispark_pegasus/sdk_liteos/build/win_scripts/SConstruct' -> '/d/Xuanxi/src/device/hisilicon/hispark_pegasus/sdk_liteos/SConstruct'[OHOS ERROR] '/d/Xuanxi/src/device/hisilicon/hispark_pegasus/sdk_liteos/build/win_scripts/tools/lzma_tool/lzma_tool.exe' -> '/d/Xuanxi/src/device/hisilicon/hispark_pegasus/sdk_liteos/tools/lzma_tool/lzma_tool.exe'[OHOS ERROR] '/d/Xuanxi/src/device/hisilicon/hispark_pegasus/sdk_liteos/build/win_scripts/tools/nvtool/build_nv.py' -> '/d/Xuanxi/src/device/hisilicon/hispark_pegasus/sdk_liteos/tools/nvtool/build_nv.py' [OHOS ERROR] '/d/Xuanxi/src/device/hisilicon/hispark_pegasus/sdk_liteos/build/win_scripts/tools/nvtool/tools/nv/cdbm.exe' -> '/d/Xuanxi/src/device/hisilicon/hispark_pegasus/sdk_liteos/tools/nvtool/tools/nv/cdbm.exe'[OHOS ERROR] '/d/Xuanxi/src/device/hisilicon/hispark_pegasus/sdk_liteos/build/win_scripts/tools/sign_tool/sign_tool.exe' -> '/d/Xuanxi/src/device/hisilicon/hispark_pegasus/sdk_liteos/tools/sign_tool/sign_tool.exe'[OHOS ERROR] execute ota_builder with python...[OHOS ERROR] riscv32-unknown-elf-gcc -Iplatform\os\Huawei_LiteOS\kernel\include -Iconfig -DCONFIG_TEE_HUKS_SUPPORT -DCONFIG_CHIP_PKT_32K -DHI_BOARD_ASIC -DHI_ON_FLASH -E build\link\system_config.ld.S -o build\build_tmp\scripts\system_config.ld -P[OHOS ERROR] nvimg_builder(["build\build_tmp\scripts\system_config.ld"], ["build\link\system_config.ld.S"])[OHOS ERROR] product_name_list: ['sta'][OHOS ERROR] xml h file: D:\Xuanxi\src\device\hisilicon\hispark_pegasus\sdk_liteos\tools\nvtool\h_file\nv[OHOS ERROR] D:\Xuanxi\src\device\hisilicon\hispark_pegasus\sdk_liteos\tools\nvtool\out_nv_bin\sta[OHOS ERROR] app_subver_list: ['demo'][OHOS ERROR] current subver_name:demo[OHOS ERROR] root_dir: D:\Xuanxi\src\device\hisilicon\hispark_pegasus\sdk_liteos\tools\nvtool[OHOS ERROR] mdm_xml: D:\Xuanxi\src\device\hisilicon\hispark_pegasus\sdk_liteos\tools\nvtool\xml_file\mss_nvi_db.xml[OHOS ERROR] HNV NAME:mss_nvi_db.xml[OHOS ERROR] nv_tool: D:\Xuanxi\src\device\hisilicon\hispark_pegasus\sdk_liteos\tools\nvtool\tools\nv\cdbm.exe[OHOS ERROR] app_subver_temp_dir:D:\Xuanxi\src\device\hisilicon\hispark_pegasus\sdk_liteos\tools\nvtool\out_nv_bin\sta\nv\Hi3861_wifiiot_app\outside_demo[OHOS ERROR] make app_subver_temp_dir[OHOS ERROR] mdmxml:D:\Xuanxi\src\device\hisilicon\hispark_pegasus\sdk_liteos\tools\nvtool\xml_file\mss_nvi_db.xml[OHOS ERROR] src_xml_list: ['D:\\Xuanxi\\src\\device\\hisilicon\\hispark_pegasus\\sdk_liteos\\tools\\nvtool\\xml_file\\mss_nvi_db.xml'][OHOS ERROR] combin_xml:D:\Xuanxi\src\device\hisilicon\hispark_pegasus\sdk_liteos\tools\nvtool\out_nv_bin\sta\nv\Hi3861_wifiiot_app\mss_nvi_db.xml[OHOS ERROR] root_list [<Element 'HISTUDIO' at 0x000002AAE62B94A0>][OHOS ERROR] hi_nv_modify_by_cfg[OHOS ERROR] cfg_file: nv[OHOS ERROR][OHOS ERROR] cmd_line= ('D:\\Xuanxi\\src\\device\\hisilicon\\hispark_pegasus\\sdk_liteos\\tools\\nvtool\\tools\\nv\\cdbm.exe', 'D:\\Xuanxi\\src\\device\\hisilicon\\hispark_pegasus\\sdk_liteos\\tools\\nvtool\\out_nv_bin\\sta\\nv\\Hi3861_wifiiot_app\\outside_demo\\nv.xml', 'D:\\Xuanxi\\src\\device\\hisilicon\\hispark_pegasus\\sdk_liteos\\tools\\nvtool\\h_file\\nv', 'D:\\Xuanxi\\src\\device\\hisilicon\\hispark_pegasus\\sdk_liteos\\tools\\nvtool\\out_nv_bin\\sta\\nv\\Hi3861_wifiiot_app\\outside_demo\\nv')[OHOS ERROR] D:\Xuanxi\src\device\hisilicon\hispark_pegasus\sdk_liteos\tools\nvtool\out_nv_bin\sta\nv\Hi3861_wifiiot_app\outside_demo\nv.xml[OHOS ERROR] D:\Xuanxi\src\device\hisilicon\hispark_pegasus\sdk_liteos\tools\nvtool\h_file\nv[OHOS ERROR] D:\Xuanxi\src\device\hisilicon\hispark_pegasus\sdk_liteos\tools\nvtool\out_nv_bin\sta\nv\Hi3861_wifiiot_app\outside_demo\nv[OHOS ERROR] ===========Init HDB Start===========[OHOS ERROR] *********strDataTypeDefPath:D:\Xuanxi\src\device\hisilicon\hispark_pegasus\sdk_liteos\tools\nvtool\h_file\nv/../nv/nv_modem_struct_def.txt[OHOS ERROR] *********strDataTypeDefPath:D:\Xuanxi\src\device\hisilicon\hispark_pegasus\sdk_liteos\tools\nvtool\h_file\nv/../nv/nv_modem_struct_def.txt[OHOS ERROR] *********strDataTypeDefPath:D:\Xuanxi\src\device\hisilicon\hispark_pegasus\sdk_liteos\tools\nvtool\h_file\nv/../nv/nv_factory_struct_def.txt[OHOS ERROR] ===========Init HDB finish===========[OHOS ERROR] ===========Write NV Buffer To File===========[OHOS ERROR] ===========Write NV Buffer Finish===========[OHOS ERROR] RETURN VALUE:0[OHOS ERROR] dst_hnv= D:\Xuanxi\src\device\hisilicon\hispark_pegasus\sdk_liteos\tools\nvtool\out_nv_bin\sta\nv\hnv\Hi3861_wifiiot_app.hnv[OHOS ERROR] Factory NV total num:10[OHOS ERROR] NV_FILE_STRU :28[OHOS ERROR] Factory NV total size:824[OHOS ERROR] Normal NV total num:13[OHOS ERROR] NV_FILE_STRU :28[OHOS ERROR] Normal NV total size:766[OHOS ERROR] build hnv OK:D:\Xuanxi\src\device\hisilicon\hispark_pegasus\sdk_liteos\tools\nvtool\out_nv_bin\sta\nv\hnv\Hi3861_wifiiot_app.hnv[OHOS ERROR] ENDING[OHOS ERROR] riscv32-unknown-elf-gcc -Iplatform\os\Huawei_LiteOS\kernel\include -Iconfig -DCONFIG_TEE_HUKS_SUPPORT -DCONFIG_CHIP_PKT_32K -DHI_BOARD_ASIC -DHI_ON_FLASH -DFLASH_FIRM_START=4248512 -E build\link\link.ld.S -o build\build_tmp\scripts\link.lds -P[OHOS ERROR] riscv32-unknown-elf-ld -nostartfiles -nostdlib -static --gc-sections -LD:\DevTools_Hi3861V100_v1.0\hcc_riscv32_win\bin\..\lib\gcc\riscv32-unknown-elf\7.3.0 -LD:\Xuanxi\src\device\hisilicon\hispark_pegasus\sdk_liteos\build\scripts\..\..\..\..\..\huawei\hms\hilink\ohos\l0\hi3861\lib\wifi -Lbuild\build_tmp\libs\app\wifiiot_app -Lbuild\build_tmp\libs\components\at -Lbuild\build_tmp\libs\platform\drivers -Lbuild\build_tmp\libs\platform\system -Lbuild\build_tmp\scripts -Lbuild\libs -Lbuild\libs\hi3861\release\no_mesh -Lbuild\scripts -Lohos\libs -Tbuild\build_tmp\scripts\link.lds -Map=output\bin\Hi3861_wifiiot_app.map -o output\bin\Hi3861_wifiiot_app.out --start-group -ladc -lat -lc_flash -lcfg -lcoap -lcpup -ldiag -ldrv -lflash -lgcc -lgpio -lhistreaminglink -li2c -lio -liperf -llitekernel_flash -lltoswpa -llwip -lm_flash -lmbedtls -lmqtt -lparttab -lpwm -lres_cfg -lsdio -lsec_flash -lspiffs -lsystem -ltsensor -luart -lupg -lwifi -lwifi_flash -lwifiiot_app -lwpa --whole-archive -lauthmanager -lbearpi_iot_hardware_hals -lbootstrap -lbroadcast -lcjson_static -lcmsis -ldeviceauth -ldeviceauth_hal_liteos -ldiscovery -ldump_static -lhal_file_static -lhal_iothardware -lhal_sysparam -lhal_token_static -lhal_update_static -lhal_wifiaware -lhichainsdk -lhilog_lite -lhiview_lite -lhota -lhuks_3.0_sdk -lnative_file -lposix -lsamgr -lsamgr_adapter -lsamgr_source -lsysparam -ltoken_static -ltrans_service -lutils_kv_store -lwifiaware -lwifiservice -lXuanxi_main --no-whole-archive --end-group[OHOS ERROR] riscv32-unknown-elf-ld: ohos\libs\libXuanxi_main.a(libXuanxi_main.Xuanxi_main.o): in function `F1Pressed':[OHOS ERROR] Xuanxi_main.c:(.text.F1Pressed+0x22): undefined reference to `sinf'[OHOS ERROR] scons: *** [output\bin\Hi3861_wifiiot_app.out] Error 1[OHOS ERROR] BUILD FAILED!!!![OHOS ERROR] Failed building output\bin\Hi3861_wifiiot_app.out: Error 1[OHOS ERROR] you can check build log in D:\Xuanxi\src\out\hispark_pegasus\wifiiot_hispark_pegasus\build.log[OHOS ERROR] command: "D:\DevTools_Hi3861V100_v1.0\thirdparty\python38\scripts\ninja.exe -w dupbuild=warn -C D:\Xuanxi\src\out\hispark_pegasus\wifiiot_hispark_pegasus" failed[OHOS ERROR] return code: 1[OHOS ERROR] execution path: D:\Xuanxi\srcscons: *** [src\out\hispark_pegasus\wifiiot_hispark_pegasus\target.elf] Failed to build!
-
CodeArts Snap 在VSCode上怎么用
-
在网上下载sdk安装包拖进用户西面的.olink文件夹里面的sdk但是重启vscode一直显示安装iotlink依赖环境
-
首先,vscode 的插件开发很简单。如果读完这篇需要 5 分钟,那么至少有 4 分钟是花在看我写的废话上,实际上只用 1 分钟就能入门。好想把标题改成“1 分钟入门 vscode 插件开发”!写个网页现在还得用 vue 或 react,免不了搭建个环境,还得构建项目、打包……而开发 vscode 插件,你只需要安装一个 vscode,其他什么都不要(不要用太老版本的 vscode)。下面是一个极简的 vscode 插件,代码只有四行。功能是:在 vscode 启动后,弹出一个通知:(从现在开始计时,不过分吧……)你只需要新建一个文件夹,然后用 vscode 打开这个文件夹,再创建以下两个文件:一、package.jsonpackage.json 是描述项目的文件,存放基本信息(项目名、版本、环境等){ "name": "vsc-plugin-demo", "version": "0.0.1", "engines": { "vscode": "^1.47.3" }, "activationEvents": [ "onStartupFinished" ], "main": "./ext.js"}这种文件,一般不称之为“代码”。需要说明的是:activationEvents: 声明插件启动的时机,此处是在 onStartupFinished,即(vscode)启动完成时(启动插件)main: 主程序文件的路径二、ext.js主程序只有这四行代码:const vscode = require('vscode')exports.activate = function() { vscode.window.showInformationMessage('hello, ppz')}第 1 行代码,引入 vscode 核心库,不需要手动安装,它提供了开发插件所需的大部分 api。第 2 到 4 行代码,定义了“启动插件的函数”,即通过这个函数,让插件跑起来。函数体部分只有一行,通过调用 vscode 提供的 api,展示一个通知:hello, ppz。运行至此,插件已经完成,运行一下试试:用鼠标点一下 ext.js 文件按键盘上的 f5点击弹出窗口中的 VS Code Extension Development(你已经入门 vscode 插件开发,计时结束)最后上面的代码可以从 github 上下载:仓库地址git clone https://github.com/ppz-pro/vsc-plugin-startup.git如果说 vscode 插件开发比某一种程序开发难,那是因为你在它上面才花了 1 分钟。相比于动辄几个月的技术培训,这 1 分钟实在是太微乎其微了。如果你也感兴趣,可以直接看 vscode 的官方教程,很全面,也很细。当然,在这忙忙碌碌、繁繁乱乱的生活中,能静下心来学习,已实属不易,共勉吧!最后的最后,自荐我在 2022 年上海疫情封控期间写的插件 —— PPZ,它提供了操作数据库的图形界面,目前功能还很简单,后续功能会慢慢丰富起来。 原文链接:vscode 插件开发极简教程 - 知乎 (zhihu.com)
-
内容提要VSCode 组成结构插件在 VSCode 中能做什么编写 Hello world 了解插件生命周期主要配置和 APIsWeb View 示例VSCode 组成结构VSCode 是基于 Electron 构建的,主要由三部分构成:Electron: UIMonaco EditorExtension HostLanguage Server Protocol & Debug Adapter ProtocolVSCode 中的大部分功能都是通过 Extension Host 来实现的。符合 LSP 的插件对应的高亮等语言特性就会反映到 Monaco Editor 上。从源码的 extensions 目录中可以看到,VSCode 默认集成了各种语言的插件。Monaco Editor是一个基于网页的编辑器,有符合 LSP 的插件就可以进行高亮、悬停提示,导航到定义、自动补全、格式化等功能。它的代码位于 monaco-editorExtension HostVSCode 的主进程和插件进程是分开管理的,Extension Host 就是用来管理插件进程的。Extension Host 是用来确保插件:不影响启动速度不会减低 UI 响应速度不会改变 UI 样式因此保证 VSCode 的稳定和快速的密码就在于使用 Extension Host 将主进程和插件进程分开,使插件不会影响到 VSCode 主进程的性能和稳定。在编写插件的时候 VSCode 可以让插件设置 Activation Events 来对插件懒加载。比如只有打开了 Markdown 文件才打开对应的插件。这样可以降低无谓的 CPU 和内存使用。Language Server Protocol & Debug Adapter Protocol这两个协议主要是为了将编辑器和编程语言/调试服务的功能分离开,实现任何语言只要编写对应的语言服务即可。目前各大编辑器都已经支持了这个协议。插件在 VSCode 中能做什么主题界面和文本(TextMate 语法)主题色图标样式通用功能添加命令添加配置项添加快捷键添加菜单项添加右键菜单从文本输入框获取输入(QuickPick)存储数据(localStorage)工作区扩展活动栏项目显示提示框状态栏信息显示进度条打开文件显示网页(web view)程序语言实现新语言的高亮实现新语言的调试器代码库管理定义和执行 Task定义 snippet主题可以修改的内容如下图(来自VSCode 官方文档)所示,主要是背景和文字的颜色,各类图标等。因为有 web view 并且底层是 node.js 虽然官方不推荐,但是实际是可以做到非常多的事情。另外底层的 Electron 是阉割版的,如果需要的功能没有,也可以下载官方的 Electron 替换掉 VSCode 中的版本。程序语言实现实现编程语言的高亮、悬停提示,导航到定义、自动补全、格式化、调试等功能。下面是 mssql 的架构和数据流:编写 Hello world 了解插件生命周期编写插件至少需要 Javascript 或 Typescript 来做入口。除了入口必须用 JS 或 TS,具体实现完全可以用你熟悉的任何语言,只要在 VSCode 的电脑上可以执行。例如 Java Language Server 插件的大部分功能都是由 Java 实现的,插件和 Java 代码之间通过 json-RPC 来进行通信。Hello world 介绍以下内容使用了 VSCode 插件文档中的 your first extension,原始代码在 helloworld-sample这个插件主要功能是运行 hello world 命令,弹出消息。从生命周期上来看,插件编写有三大个部分:Activation Event:设置插件激活的时机。位于 package.json 中。Contribution Point:设置在 VSCode 中哪些地方添加新功能,也就是这个插件增强了哪些功能。位于 package.json 中。Register:在 extension.ts 中给要写的功能用 vscode.commands.register... 给 Activation Event 或 Contribution Point 中配置的事件绑定方法或者设置监听器。位于入口文件(默认是 extension.ts)的 activate() 函数中。packages.jsonpackage 中和插件有关的主要内容是如下几个项目,其中 main 是插件代码的入口文件。"activationEvents": [ "onCommand:extension.helloWorld" ], "main": "./out/extension.js", "contributes": { "commands": [ { "command": "extension.helloWorld", "title": "Hello World" }, { "command": "extension.helloVscode", "title": "Hello vscode" } ] },activationEvents官方文档: activation-events原来的 "Hello World" 只是在执行 extension.helloWorld 的命令后才会激活插件,所以如果需要在其他情况下激活插件的话,则需要添加对应的命令才行。所以新添加了 onCommand:extension.helloVscode。"activationEvents": [ "onCommand:extension.helloWorld", "onCommand:extension.helloVscode" ],如果忘记添加这个命令则会造成执行命令后,插件并没有启动,命令执行失败。Contribution Points官方文档: contribution-points这个是在 package.json 中配置的项目。说明了插件对哪些项目进行了增强。对于 "hello world" 示例,如果需要在原有功能上添加一个命令 hello Vscode,下面的 command 为 "extension.helloVscode" 的就是新添加的命令了。"contributes": { "commands": [ { "command": "extension.helloWorld", "title": "Hello World" }, { "command": "extension.helloVscode", "title": "Hello vscode" } ]},Extension.ts这个文件是插件的入口,一般包括两个函数 activate 和 deactivate。其中 activate 函数是插件激活时也就是在注册的 Activation Event 发生的时候就会执行。deactivate 中放的是插件关闭时执行的代码。在 activate() 函数中通过 return 返回的数据或函数可以作为接口供其他插件使用。VSCode 在运过程中,会通过 Extension Host 来管理所有插件中这两个函数的生命周期。import * as vscode from "vscode";// 插件激活时的入口export function activate(context: vscode.ExtensionContext) { // 注册 extension.helloWorld 命令 let disposable = vscode.commands.registerCommand("extension.helloWorld", () => { vscode.window.showInformationMessage("Hello World!"); }); // 给插件订阅 helloWorld 命令 context.subscriptions.push(disposable); // 新增的代码 let helloVscode = vscode.commands.registerCommand("extension.helloVscode", () => { vscode.window.showInformationMessage("Hello Vscode"); }); context.subscriptions.push(helloVscode); // return 的内容可以作为这个插件对外的接口 return { hello() { return "hello world"; } };}// 插件释放的时候触发export function deactivate() {}activateExtensionContext字面上意思是上下文信息,实际上就是当前插件的状态信息。registerCommand 和 subscriptions.push()完整的 API 是:registerCommand(command: string, callback: (args: any[]) => any, thisArg?: any): Disposable这个的主要功能是给功能代码(callback)注册一个命令(command),然后通过 subscriptions.push() 给插件订阅对应的 command 事件。return 给其他插件提供接口如果需要使用其他插件提供的接口,则可以在 package.json 中将对应插件添加到 extensionDependency 中,然后使用 getExtension 函数中的 export 属性。export function activate(context: vscode.ExtensionContext) { let api = { hello() { return "hello world"; } }; return api;}// 引入其他插件接口let helloWorld = extensions.getExtension("helloWorld");let importedApi = helloWorld.exports;console.log(importedApi.hello());Debug Extension在 launch.json 中添加一个扩展开发的配置,然后按F5就可以打开一个新的 VSCode,然后就可以在这个新的 VSCode 中进行插件测试。并且也可以在插件代码的那个 VSCode 中打断点调试。建议在“args”中添加"--disable-extensions",不然要调试的插件加载太慢,还以为写的有问题。Unit Test具体文档请参考 testing extension测试插件可以使用 vscode-test API 来做测试。需要给它的 runTests 提供 extensionDevelopmentPath, extensionTestsPath 即开发目录和测试文件目录。测试则使用习惯的单元测试框架即可。import * as path from "path";import { runTests } from "vscode-test";async function main() { try { // The folder containing the Extension Manifest package.json // Passed to `--extensionDevelopmentPath` const extensionDevelopmentPath = path.resolve(__dirname, "../../"); // The path to the extension test script // Passed to --extensionTestsPath const extensionTestsPath = path.resolve(__dirname, "./suite/index"); // Download VS Code, unzip it and run the integration test await runTests({ extensionDevelopmentPath, extensionTestsPath }); } catch (err) { console.error("Failed to run tests"); process.exit(1); }}main();如果要对测试做Debug的话,则可以参考下面内容配置 launch.json。其中设置关闭了其他的插件,如果需要打开其他插件,则删掉 --disable-extensions。也可以通过给 runTests 再添加一个参数 launchArgs: ['--disable-extensions'] 来关闭其他插件。{ "version": "0.2.0", "configurations": [ { "name": "Extension Tests", "type": "extensionHost", "request": "launch", "runtimeExecutable": "${execPath}", "args": [ "--disable-extensions", "--extensionDevelopmentPath=${workspaceFolder}", "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" ], "outFiles": ["${workspaceFolder}/out/test/**/*.js"] } ]}await runTests({ extensionDevelopmentPath, extensionTestsPath, launchArgs: ["--disable-extensions"]});主要配置和 APIsActivation Events这个项目定义的是插件打开的时机,可以在以下情况时打开:onLanguage: 在打开对应语言文件时onCommand: 在执行对应命令时onDebug: 在 debug 会话开始前onDebugInitialConfigurations: 在初始化 debug 设置前onDebugResolve: 在 debug 设置处理完之前workspaceContains: 在打开一个文件夹后,如果文件夹内包含设置的文件名模式时onFileSystem: 打开的文件或文件夹,是来自于设置的类型或协议时onView: 侧边栏中设置的 id 项目展开时onUri: 在基于 vscode 或 vscode-insiders 协议的 url 打开时onWebviewPanel: 在打开设置的 webview 时*: 在打开 vscode 的时候,如果不是必须一般不建议这么设置官方文档:activation-eventsContribution Points这个是用来用来描述你所写的插件在哪些地方添加了功能,是什么样的功能,添加的内容会显示到界面上,前面的 hello world 示例就是在 commands 中添加了相应的 hello world 命令,然后这个命令就可以在命令窗口执行了。configurationconfigurationDefaultscommandsmenuskeybindingslanguagesdebuggersbreakpointsgrammarsthemessnippetsjsonValidationviewsviewsContainersproblemMatchersproblemPatternstaskDefinitionscolorstypescriptServerPluginsresourceLabelFormatters官方文档:contribution-pointsAPIs所有的 API 定义在 vscode.d.ts 中,其注释也写的非常详细。主要是以下各类 API:commandscommentsdebugenvextensionslanguagesscmtaskswindowworkspaceAPI 设计的模式官方文档:vscode-api#api-patternsPromiseVSCode API 中异步操作使用的是 Promise,所以可以使用 Then 或者 await。大部分情况下 Thenable 是可选的,如果 promise 是可选的,则会有一个可选类型。provideNumber(): number | Thenable<number>Cancellation Tokens在一个操作完成前,会开始于一个不稳定的状态。比如在开始代码智能提示时,最开始的操作会因为后面持续输入的内容过时。很多 API 会有一个 CancellationToken,来检查操作是否取消 (isCancellationRequested),或者在发生取消操作时得到通知 (onCancellationRequested)。这个 Token 一般是函数的最后一个可选(回调)参数。DisposablesVSCode API 对使用的各类资源利用 dispose pattern 来进行释放。应用于事件监听、命令、UI 交互等。例如:对于 setStatusBarMessage(value: string)(给状态栏显示消息)函数返回一个 Disposable 类型,然后可以通过调用它的 dispose 来移除信息。Events事件在 VSCode API 里面是通过订阅监听函数来实现的。订阅后会返回一个支持 Disposable 接口的变量。调用 dispose 就可以取消监听。var listener = function(event) { console.log("It happened", event); }; // 开始监听 var subscription = fsWatcher.onDidDelete(listener); // 搞事情 subscription.dispose(); // 停止监听对于事件的命名遵循 on[Will|Did]VerbNoun? 模式。onWill:即将发生onDid:已经发生verb:发生了什么noun:事件所处环境,如果发生在所处的环境则可以不加。例如:window.onDidChangeActiveTextEditor。Web View 示例web view 是 VSCode 插件里面比较有意思并且写起来也相对自由的部分,web view 用好了可以做出非常棒的插件,例如下面这个 Visual Embedded Rust。web view 生命周期生命周期包括三部分:创建:panel = vscode.window.createWebviewPanel()显示:panel.webview.html = htmlString关闭:panel.dispose() 主动关闭,panel.onDidDispose 设置关闭时清理的内容。export function webViewPanel(context: vscode.ExtensionContext) { // 1. 使用 createWebviewPanel 创建一个 panel,然后给 panel 放入 html 即可展示 web view const panel = vscode.window.createWebviewPanel( 'helloWorld', 'Hello world', vscode.ViewColumn.One, // web view 显示位置 { enableScripts: true, // 允许 JavaScript retainContextWhenHidden: true // 在 hidden 的时候保持不关闭 } ); const innerHtml = `<h1>Hello Web View</h1>`; panel.webview.html = getWebViewContent(innerHtml); // 2. 周期性改变 html 中的内容,因为是直接给 webview.html 赋值,所以是刷新整个内容 function changeWebView() { const newData = Math.ceil(Math.random() * 100); panel.webview.html = getWebViewContent(`${innerHtml}<p>${newData}</p>`); } const interval = setInterval(changeWebView, 1000); // 3. 可以通过设置 panel.onDidDispose,让 webView 在关闭时执行一些清理工作。 panel.onDidDispose( () => { clearInterval(interval); }, null, context.subscriptions );}function getWebViewContent(body: string, pic?: vscode.Uri) { return `<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>Document</title> </head> <body> ${body} <br /> <img id="picture" src="${pic || 'https://media.giphy.com/media/JIX9t2j0ZTN9S/giphy.gif'}" width="300" /> </body></html> `;}读取本地文件一般情况下 web view 是不能直接访问本地文件的,需要使用vscode-resource开头的地址。并且只能访问插件所在目录和当前工作区。在高于 1.38 版 VSCode 下可以使用 panel.webview.asWebviewUri(onDiskPath) 生成对应的地址,否则需要使用onDiskPath.with({ scheme: 'vscode-resource' })。export function webViewLocalContent(context: vscode.ExtensionContext) { const panel = vscode.window.createWebviewPanel( 'HelloWebViewLocalContent', 'Web View Local Content', vscode.ViewColumn.One, { localResourceRoots: [ vscode.Uri.file(path.join(context.extensionPath, 'media')) ] } ); const onDiskPath = vscode.Uri.file( path.join(context.extensionPath, 'media', 'cat.jpg') ); // 生成一个特殊的 URI 来给 web view 用。 // 实际是:vscode-resource: 开头的一个 URI // 资源文件只能放到插件安装目录或则用户当前工作区里面 // 1.38以后才有这个 API,前面版本可以用onDiskPath.with({ scheme: 'vscode-resource' }); const catPicSrc = panel.webview.asWebviewUri(onDiskPath); const body = `<h1>hello local cat</h1>`; panel.webview.html = getWebViewContent(body, catPicSrc);}web view 和插件通信向 web view 发信息是通过 currentPanel.webview.postMessage({}) 发送一个json数据。在 web view 中通过window.addEventListener('message', callback)监听message信息。// 插件发送数据currentPanel.webview.postMessage({ command: 'hello web view' });// web 接收window.addEventListener('message', event => { const message = event.data; console.log(message.command);})由于 web view 不能直接调用 vscode 的 API,不过 vscode 还是给它提供了一个 acquireVsCodeApi() 的函数,它返回的对象中有一个 postMessage 方法。web view 可以通过这个方法给 vscode 发送消息。插件端则通过currentPanel.webview.onDidReceiveMessage()订阅接收消息的事件。// web view 向插件端发送数据const vscode = acquireVsCodeApi();vscode.postMessage({ command: 'alert', text: 'hello vscode'});// 插件端接收数据currentPanel.webview.onDidReceiveMessage( message => { switch (message.command) { case 'alert': vscode.window.showInformationMessage(message.text); return; } }, undefined, context.subscriptions );调试 web view可以使用下面 web view develop tool 命令。原文链接:VSCode插件开发入门 - 知乎 (zhihu.com)
上滑加载中
推荐直播
-
华为云码道Agent集成与鸿蒙实战2026/08/11 周二 19:00-21:00
王一男-华为云码道产品规划专家;李炎-华为云码道产品专家;彭江敏-华为云鸿蒙端云一体化开发专家
本次直播带你解读华为云码道7月份产品新特性、新功能。更有专家演示码道Agent Space × 钉钉机器集成实战,从0到1打通消息通道;码道鸿蒙端云一体化实战,快速搭建员工签到系统。
回顾中 -
华为云开发者AI素养直播课·第五期2026/09/04 周五 16:00-18:00
林华鼎-华为云AI开发者运营负责人;蒋春阳-华为云AI开发者案例开发专家
本期直播内容: AI工具体验营 · 第5-8课连讲。Agent-Team 多智能体协作完成毕业设计实践
回顾中 -
华为云开发者AI素养ClassRoom·第六期2026/09/08 周二 19:00-20:00
樊渊-2026华为软件挑战赛冠军
高手来了:看软挑高手解析二维排样问题—从工业难题到算法突破
回顾中
热门标签