欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。
📌 概述
外观设置模块主要负责控制喝茶记录应用在视觉层面的呈现方式,包括主题模式(明亮/深色)、主色调、字体大小以及卡片密度等。这个模块表面上只是“换皮肤”,但在 Cordova 与 OpenHarmony 混合架构下,它同时要协调 Web CSS 变量、LocalStorage/IndexedDB 配置以及 ArkTS 原生侧的窗口样式、状态栏图标样式等多种能力。合理的设计可以让用户在 PC、平板等设备上获得一致且舒适的体验,同时又兼顾性能和可维护性。
在整体架构上,外观设置模块遵循“配置中心 + CSS 变量 + 原生桥接”的思路:所有外观相关的用户偏好被集中保存在appearanceSettings结构中,通过 IndexedDB 与原生 Preferences 同步;Web 侧通过设置:root上的一组 CSS 自定义属性来驱动主题变化;原生 ArkTS 通过插件读取关键信息,例如当前是否为暗色模式,从而调整 ArkUI 窗口背景、系统栏图标颜色等。这种划分使得外观逻辑既高度集中,又与业务逻辑解耦。
🔗 完整流程
第一步:加载外观偏好并应用到 CSS
当应用启动或用户打开“外观设置”页面时,前端首先会从 IndexedDB 的settings表中读取已保存的外观配置(如themeMode、primaryColor、fontScale等)。如果没有记录,就使用默认值(例如跟随系统或固定浅色)。随后,应用会调用一个统一的applyAppearance(settings)方法,将这些配置转换为 CSS 变量写入到document.documentElement.style上。这一步会立即影响整站的配色和部分布局,例如卡片阴影、背景色和字体大小等,而不需要重新加载页面。
第二步:用户在外观设置页面中实时预览
用户进入“外观设置”页面后,可以通过切换主题模式(浅色/深色/跟随系统)、选择主色调(如茶色、绿色、蓝色)、调整字体大小(小/中/大)等操作立即看到效果。外观设置页面并不会立即写入数据库,而是先更新内存中的appearanceSettings,并调用applyAppearance()做实时预览。当用户对当前效果满意并点击“保存外观”按钮时,才会把最终配置持久化到 IndexedDB 与 OpenHarmony Preferences 中。这种“先体验再保存”的交互方式避免了频繁磁盘写入,也让用户操作更有安全感。
第三步:原生侧同步与系统级外观协调
在用户保存外观设置时,除了 Web 层写入settings,还会通过cordova.exec调用 ArkTS 侧的AppearanceBridge.saveNativeAppearance()。原生插件会把themeMode、primaryColor等关键信息写入 Preferences,并在下次窗口创建或主题切换时应用:例如暗色模式下使用深色背景并将状态栏图标改为浅色。对于“跟随系统”模式,ArkTS 还会监听系统主题变化事件,在系统切换深浅色时主动回调 Web 端(可选实现),触发一次applyAppearance({ themeMode: 'system' }),从而保持两端视觉一致。
🔧 Web 代码实现
外观设置页面 HTML 结构
<divid="appearance-settings-page"class="page"><divclass="page-header"><h1>外观设置</h1></div><formid="appearance-form"class="form"><divclass="form-group"><labelfor="theme-mode">主题模式</label><selectid="theme-mode"name="themeMode"><optionvalue="light">浅色模式</option><optionvalue="dark">深色模式</option><optionvalue="system">跟随系统</option></select></div><divclass="form-group"><labelfor="primary-color">主色调</label><selectid="primary-color"name="primaryColor"><optionvalue="#409EFF">经典蓝</option><optionvalue="#67c23a">茶叶绿</option><optionvalue="#e6a23c">暖茶橙</option><optionvalue="#f56c6c">红茶红</option></select></div><divclass="form-group"><labelfor="font-scale">字体大小</label><selectid="font-scale"name="fontScale"><optionvalue="0.9">小号</option><optionvalue="1">标准</option><optionvalue="1.1">偏大</option><optionvalue="1.2">超大</option></select></div><divclass="form-group"><labelfor="card-density">卡片密度</label><selectid="card-density"name="cardDensity"><optionvalue="comfortable">宽松</option><optionvalue="cozy">适中</option><optionvalue="compact">紧凑</option></select></div><divclass="form-actions"><buttontype="button"class="btn btn-secondary"onclick="resetAppearance()">恢复默认</button><buttontype="submit"class="btn btn-primary">保存外观</button></div></form></div>这部分 HTML 定义了外观设置的表单结构,包含主题模式、主色调、字体缩放以及卡片密度四大类配置。采用下拉框而不是开关,可以方便后续扩展更多选项(例如新增主题色、字体比例)。表单提交会由 JavaScript 统一处理,而“恢复默认”按钮用于一键回到设计推荐配置。
外观设置逻辑与 CSS 变量应用
letappearanceSettings={themeMode:'light',primaryColor:'#409EFF',fontScale:1,cardDensity:'cozy'};asyncfunctioninitAppearanceSettingsPage(){try{constsaved=awaitdb.getAppearanceSettings();if(saved){appearanceSettings={...appearanceSettings,...saved};}fillAppearanceForm();applyAppearance(appearanceSettings);document.getElementById('appearance-form').addEventListener('submit',handleAppearanceSubmit);}catch(error){console.error('Failed to init appearance page:',error);showToast('加载外观设置失败','error');fillAppearanceForm();}}functionfillAppearanceForm(){document.getElementById('theme-mode').value=appearanceSettings.themeMode;document.getElementById('primary-color').value=appearanceSettings.primaryColor;document.getElementById('font-scale').value=String(appearanceSettings.fontScale);document.getElementById('card-density').value=appearanceSettings.cardDensity;}functionapplyAppearance(settings){constroot=document.documentElement;root.style.setProperty('--primary-color',settings.primaryColor);root.style.setProperty('--font-scale',String(settings.fontScale));if(settings.themeMode==='dark'){root.classList.add('theme-dark');root.classList.remove('theme-light');}else{root.classList.add('theme-light');root.classList.remove('theme-dark');}root.classList.remove('density-comfortable','density-cozy','density-compact');root.classList.add(`density-${settings.cardDensity}`);}asyncfunctionhandleAppearanceSubmit(event){event.preventDefault();constformData=newFormData(document.getElementById('appearance-form'));constnewSettings={themeMode:formData.get('themeMode')||'light',primaryColor:formData.get('primaryColor')||'#409EFF',fontScale:parseFloat(formData.get('fontScale')||'1'),cardDensity:formData.get('cardDensity')||'cozy'};appearanceSettings=newSettings;applyAppearance(newSettings);try{awaitdb.saveAppearanceSettings(newSettings);if(window.cordova){cordova.exec(()=>console.log('Native appearance saved'),err=>console.error('Save native appearance error:',err),'AppearanceBridge','saveNativeAppearance',[newSettings]);}showToast('外观设置已保存','success');}catch(error){console.error('Failed to save appearance:',error);showToast('保存失败,请重试','error');}}functionresetAppearance(){appearanceSettings={themeMode:'light',primaryColor:'#409EFF',fontScale:1,cardDensity:'cozy'};fillAppearanceForm();applyAppearance(appearanceSettings);showToast('已恢复默认外观(记得点击保存)','info');}这段 JavaScript 展示了外观设置从加载到应用再到保存的完整流程。applyAppearance()将配置映射到 CSS 自定义属性与根元素的 class 上,CSS 文件中只需根据--primary-color、--font-scale和.theme-dark/.density-compact等选择器来调整颜色和间距即可。通过这种方式,不需要在多处手动切换 class,只要维护好一套 CSS 变量即可。
🔌 OpenHarmony 原生代码(ArkTS)
AppearanceBridge:桥接主题和系统栏样式
// entry/src/main/ets/plugins/AppearanceBridge.etsimportpreferencesfrom'@ohos.data.preferences';importwindowfrom'@ohos.window';constPREF_NAME='tea_app_appearance';exportclassAppearanceBridge{staticasyncsaveNativeAppearance(settings:Record<string,unknown>):Promise<void>{constpref=awaitpreferences.getPreferences(globalThis.context,PREF_NAME);if(settings.themeMode!==undefined){awaitpref.put('themeMode',settings.themeModeasstring);}if(settings.primaryColor!==undefined){awaitpref.put('primaryColor',settings.primaryColorasstring);}awaitpref.flush();awaitthis.applyToWindow(settings);}staticasyncapplyToWindow(settings:Record<string,unknown>):Promise<void>{constmode=(settings.themeModeasstring)??'light';constwin=awaitwindow.getLastWindow(globalThis.context);if(mode==='dark'){awaitwin.setWindowBackgroundColor('#121212');awaitwin.setSystemBarProperties({statusBarContentColor:'#FFFFFF',navigationBarContentColor:'#FFFFFF'});}else{awaitwin.setWindowBackgroundColor('#F5F7FA');awaitwin.setSystemBarProperties({statusBarContentColor:'#000000',navigationBarContentColor:'#000000'});}}}AppearanceBridge插件在原生侧接收来自 Web 的外观配置,并写入 Preferences 后立即对当前窗口进行调整:themeMode为dark时使用深色背景并把系统栏内容颜色设为白色,反之则使用浅色背景与深色图标。通过这种方式,Web 的 CSS 主题与 ArkUI 窗口的外观可以保持统一,避免出现“页面是深色、系统栏是亮色”的割裂体验。
📝 总结
外观设置模块将简单的“切换主题”上升为一套完整的跨层外观管理方案:
- Web 层通过表单和 CSS 变量负责 UI 呈现和即时预览;
- IndexedDB 与 Preferences 负责长期持久化;
- ArkTS 原生插件负责窗口背景、系统栏样式等系统级外观;
- Cordova 作为桥梁让两侧在一次操作中同时生效。
在这个实践中,你可以清楚地看到 Cordova 与 OpenHarmony 在 UI 层的协同方式:绝大多数组件、布局和动画仍由 Web 完成,而需要与系统风格一致的部分(比如状态栏、导航栏、窗口背景)则交由 ArkTS 来处理。这样的分层既保留了 Web 开发的高效率,又充分利用了 HarmonyOS 的原生能力,是混合应用在 UI 外观方面的一种典型最佳实践。