HarmonyOS 6.1 PC应用实战:键鼠协同与多窗体——电商后台管理系统“上车”
系列番外·生产力工具篇。车机篇发布后有开发者问“手机、车机都写了那传说中的鸿蒙PC呢咱们这个电商Demo能不能搞个PC版的后台管理系统” 问到点子上了随着HarmonyOS NEXT向PC端延伸一次开发多端部署的价值将被最大化。今天我们将电商Demo的“后台管理端”适配到PC平台。重点攻克三大PC开发独有的难关键鼠交互范式Hover、右键、拖拽、多窗体架构主窗口子窗口弹窗、响应式布局进阶从移动端单列到PC端多列侧边栏。全程基于API23含官方文档未涉及的PC端UX设计规范。一、前言为什么PC端是鸿蒙生态的“临门一脚”在移动端我们习惯了触摸交互和全屏应用。但在PC端用户的心智模型完全不同效率优先用户使用键盘快捷键CtrlC/V、鼠标精确点击、多窗口并排来提升效率。空间充裕PC拥有大屏幕1920x1080起可以容纳更多信息密度不再需要“隐藏式菜单”。多任务并行用户希望一边看数据报表一边回客服消息一边查商品库存。我们的电商Demo之前只有“C端用户版”手机App缺少“B端商家版”后台管理。今天我们利用ArkUI的跨平台能力在不重写业务逻辑的前提下快速构建一个运行在PC上的电商后台管理系统。这将充分展示ArkUI-X和HarmonyOS NEXT的威力一套代码跑在手机、车机和PC上。二、核心概念辨析移动端思维 vs PC端思维很多开发者直接把手机UI拉伸到PC屏幕上结果惨不忍睹。必须先转变思维维度移动端手机/车机PC端桌面/笔记本交互核心​触摸粗放键鼠精细​ 触摸二合一设备导航结构​底部Tab栏 页面栈顶部菜单栏​ 侧边导航栏 多标签页内容布局​单列流式布局多列栅格布局​ 分栏布局Master-Detail窗口形态​强制全屏自由窗口可缩放、最小化、最大化关键事件​onClick, onTouchonHover, onKeyEvent, onDrop, onDrag​核心认知PC端开发不仅仅是屏幕变大了而是交互逻辑的根本改变。我们需要为鼠标悬停Hover提供视觉反馈为键盘快捷键提供响应逻辑为窗口大小变化提供动态布局。三、代码实现从“手持电商”到“桌面后台”3.1 环境准备与配置新建PC模块在DevEco Studio中右键项目 - New - Module - PC Module。设备类型声明修改module.json5声明支持PC。{ module: { deviceTypes: [pc], // 声明为PC应用 abilities: [ { name: EntryAbility, window: { designWidth: 1920, // 设计稿基准宽度 autoDesignWidth: true, maxWindowRatio: 3.0, // 最大窗口比例 minWindowRatio: 0.5 // 最小窗口比例 } } ] } }资源目录在resources下新建layout-pc目录存放PC专属布局。3.2 构建PC端经典布局侧边栏主内容区创建entry_pc/src/main/ets/pages/AdminIndex.ets。采用经典的“主-详”布局。import { router } from kit.ArkUI import { CommonConstants } from ../common/Constants // 定义菜单项接口 interface MenuItem { id: string icon: Resource label: string route: string } Entry Component struct AdminIndex { State currentMenu: string dashboard State windowWidth: number 1920 // 监听窗口宽度 private menus: MenuItem[] [ { id: dashboard, icon: $r(app.media.ic_dashboard), label: 数据看板, route: pages/admin/Dashboard }, { id: products, icon: $r(app.media.ic_goods), label: 商品管理, route: pages/admin/ProductList }, { id: orders, icon: $r(app.media.ic_order), label: 订单管理, route: pages/admin/OrderList }, { id: settings, icon: $r(app.media.ic_settings), label: 系统设置, route: pages/admin/Settings } ] aboutToAppear(): void { // 监听窗口尺寸变化实现响应式布局 window.getLastWindow(getContext(this)).then((win) { win.on(windowSizeChange, (size) { this.windowWidth size.width }) }) } build() { Row() { // 左侧固定导航栏PC专属 Column() { // Logo Text(Shop Admin) .fontSize(24) .fontWeight(FontWeight.Bold) .fontColor(#0A59F7) .margin({ bottom: 40 }) // 菜单列表 List() { ForEach(this.menus, (menu: MenuItem) { Row() { Image(menu.icon) .width(24) .height(24) .fillColor(this.currentMenu menu.id ? #0A59F7 : #666) if (this.windowWidth 1200) { // 宽屏显示文字窄屏只显示图标 Text(menu.label) .fontSize(16) .fontColor(this.currentMenu menu.id ? #0A59F7 : #333) .margin({ left: 12 }) } } .width(100%) .padding(16) .backgroundColor(this.currentMenu menu.id ? #E8F0FE : Color.Transparent) .borderRadius(12) .onClick(() { this.currentMenu menu.id // PC端通常是单页应用SPA这里用路由替换内容区而不是打开新页面 router.replacePath({ url: menu.route }) }) .onHover((isHover) { // 鼠标悬停效果PC核心交互 if (isHover this.currentMenu ! menu.id) { // 可以在这里改变背景色提供反馈 } }) }) } .layoutWeight(1) // 底部用户信息 Row() { Image($r(app.media.avatar)) .width(40) .height(40) .borderRadius(20) if (this.windowWidth 1200) { Column() { Text(Admin) .fontSize(14) .fontWeight(FontWeight.Medium) Text(adminshop.com) .fontSize(12) .fontColor(#999) } .margin({ left: 12 }) .layoutWeight(1) } } .padding(16) } .width(this.windowWidth 1200 ? 240 : 80) // 响应式宽度 .height(100%) .backgroundColor(#F8F9FA) .padding(16) .transition(TransitionEffect.OPACITY.animation({ duration: 300 })) // 平滑过渡 // 右侧主内容区 Column() { // 顶部工具栏面包屑搜索快捷键提示 Row() { Text(this.menus.find(m m.id this.currentMenu)?.label || ) .fontSize(20) .fontWeight(FontWeight.Medium) Blank() TextInput({ placeholder: 搜索商品/订单... }) .width(300) .height(40) .backgroundColor(#F0F2F5) .borderRadius(20) .padding({ left: 16 }) Text(⌨️ CtrlK) .fontSize(12) .fontColor(#999) .margin({ left: 10 }) } .width(100%) .padding(16) .border({ width: { bottom: 1 }, color: #EEE }) // 内容区根据菜单切换 Stack() { // 这里根据currentMenu加载不同的Component // 例如Dashboard, ProductList等 this.ContentArea() } .layoutWeight(1) .backgroundColor(#FFFFFF) } .layoutWeight(1) .height(100%) } .width(100%) .height(100%) .onKeyEvent((event: KeyEvent) { // PC端键盘快捷键支持 if (event.type KeyType.Down) { // Ctrl K 聚焦搜索框 if (event.ctrlKey event.keyCode KeyCode.KEY_K) { // 实际开发中这里需要拿到TextInput的Controller并调用focus() promptAction.showToast({ message: 搜索快捷键触发 }) return true // 阻止事件冒泡 } // ESC 返回仪表盘 if (event.keyCode KeyCode.KEY_ESCAPE) { this.currentMenu dashboard return true } } return false }) } /** * 根据当前菜单渲染内容区 */ Builder ContentArea() { if (this.currentMenu products) { // 复用手机端的商品列表逻辑但改变UI布局 this.ProductManagementTable() } else { // 其他页面的占位符 Text(内容加载中...) .fontSize(16) .fontColor(#999) } } /** * PC端商品管理表格替代手机端的List */ Builder ProductManagementTable() { List() { // 表头 ListItem() { Row() { Text(商品名称).layoutWeight(3) Text(价格).layoutWeight(1) Text(库存).layoutWeight(1) Text(状态).layoutWeight(1) Text(操作).layoutWeight(2) } .padding(16) .backgroundColor(#F8F9FA) .fontWeight(FontWeight.Medium) } // 数据行 ForEach(this.getProducts(), (item: GoodsBean) { ListItem() { Row() { // 商品名图 Row() { Image(item.image).width(48).height(48).borderRadius(8).objectFit(ImageFit.Cover) Text(item.name).margin({ left: 12 }).maxLines(1) }.layoutWeight(3) Text(¥${item.price}).layoutWeight(1) Text(item.stock.toString()).layoutWeight(1) Text(item.status 1 ? 在售 : 下架) .layoutWeight(1) .fontColor(item.status 1 ? #4CAF50 : #F44336) // 操作按钮组 Row() { Button(编辑).height(32).backgroundColor(#E8F0FE).fontColor(#0A59F7) Button(下架).height(32).backgroundColor(#FFF3E0).fontColor(#FF9800).margin({ left: 8 }) }.layoutWeight(2) } .padding(16) .border({ width: { bottom: 1 }, color: #EEE }) .onHover((isHover) { // 鼠标悬停高亮行 // 实际开发中可以通过状态变量控制背景色 }) // PC端支持右键菜单 .onContextMenu((event: ContextMenuEvent) { this.showContextMenu(event.x, event.y, item) }) } }) } .sticky(StickyStyle.Header) // 表头吸顶 .width(100%) .height(100%) } /** * 显示右键菜单PC专属 */ showContextMenu(x: number, y: number, item: GoodsBean): void { ContextMenu.show({ x: x, y: y, menuItems: [ { title: 复制商品ID, icon: $r(app.media.ic_copy), action: () console.log(item.id) }, { title: 查看详情, icon: $r(app.media.ic_view), action: () router.pushUrl({ url: pages/admin/ProductDetail, params: { id: item.id } }) }, { divider: true }, { title: 删除商品, icon: $r(app.media.ic_delete), fontColor: #F44336, action: () this.deleteProduct(item.id) } ] }) } // 模拟数据 getProducts(): GoodsBean[] { return [ { id: 1, name: HarmonyOS 6.1 定制款手机, price: 5999, stock: 99, status: 1, image: phone.jpg }, { id: 2, name: 鸿蒙智行 车载充电器, price: 199, stock: 500, status: 1, image: charger.jpg }, { id: 3, name: 星闪无线鼠标, price: 299, stock: 0, status: 0, image: mouse.jpg } ] } deleteProduct(id: number): void { promptAction.showDialog({ title: 确认删除, message: 确定要删除商品ID: ${id} 吗, buttons: [{ text: 取消 }, { text: 删除, color: #F44336 }] }).then((result) { if (result.index 1) { // 执行删除逻辑 promptAction.showToast({ message: 删除成功 }) } }) } }3.3 关键PC交互实现上面的代码展示了三个关键的PC交互Hover悬停.onHover()。当鼠标移到列表行上时可以改变背景色给用户即时的视觉反馈。Context Menu右键菜单.onContextMenu()。PC用户习惯右键操作这里替代了手机上的长按菜单。Keyboard Shortcuts键盘快捷键.onKeyEvent()。监听CtrlK、ESC等组合键大幅提升操作效率。四、踩坑记录官方文档没写的5个PC坑窗口尺寸监听windowSizeChange回调非常频繁如果在回调里直接做重计算如重新构建整个UI树会导致界面卡顿。解决使用防抖Debounce或节流Throttle技术限制更新频率如200ms一次。焦点管理FocusPC端有“焦点”的概念。点击一个按钮后焦点可能还在按钮上此时按空格键会再次触发点击。在输入框失去焦点时需要手动管理焦点状态。拖拽文件上传PC端常见需求是拖拽文件到窗口上传。onDrop事件需要处理DragEvent从中提取文件的URIevent.items[0].uri然后通过文件管理API读取。官方文档对此流的介绍比较分散。多窗口层级PC端可能有多个窗口如主窗口、设置窗口、弹窗。promptAction.showDialog默认是模态的会阻塞主窗口。对于非阻塞通知应该使用Toast或自定义的Overlay。字体渲染PC端屏幕PPI差异巨大从普通显示器到4K屏。使用fp单位字体像素而不是px确保字体在不同DPI下物理大小一致。

相关新闻

STM32串口通信开发实战:从初始化到中断处理

STM32串口通信开发实战:从初始化到中断处理

1. STM32串口通信核心模块解析在STM32开发中,usart.c作为串口通信的核心驱动文件,承担着硬件初始化、数据收发和中断处理等关键功能。这个文件通常包含两个核心函数:uart_init()用于串口硬件初始化,USART1_IRQHandler()处理串口中…

2026/10/31 7:33:18 阅读更多 →
HarmonyOS 6.1 车机应用实战:把电商“搬”进智能座舱

HarmonyOS 6.1 车机应用实战:把电商“搬”进智能座舱

系列番外智慧出行篇。上篇聊完空间计算,评论区有车友问:“鸿蒙车机这么火,能不能把咱们的电商Demo适配到车机上?等红灯时逛个街?” 这需求太真实了!HarmonyOS智能座舱(HarmonySpace)…

2026/10/29 1:01:15 阅读更多 →
HarmonyOS 6.1 空间计算初探:把电商展厅搬进“虚实融合”新世界

HarmonyOS 6.1 空间计算初探:把电商展厅搬进“虚实融合”新世界

系列番外空间计算篇。终章发布后,有读者留言:“现在都在聊Vision Pro和空间计算,鸿蒙在这方面有布局吗?能不能把我们这个电商Demo弄成3D的?” 答案是肯定的。HarmonyOS NEXT的ArkUI 3D和Spatial Computing Kit正是为此…

2026/10/29 1:01:22 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/10/31 0:19:11 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/10/31 10:20:19 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/10/31 10:20:23 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/10/31 0:59:28 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/10/31 10:30:19 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/10/31 9:29:43 阅读更多 →

日新闻

周新闻

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/31 18:03:41 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/31 11:00:40 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/31 0:09:01 阅读更多 →