Flutter for OpenHarmony Web开发助手App实战:快捷键参考

快捷键能大幅提升开发效率。今天我们实现一个常用快捷键参考页面。

需求拆一下(做出来像"项目里的一个页面")

  • 按工具分类:VS Code、Chrome DevTools、Git 等。
  • 支持快速检索:输入关键词过滤(按键/描述都能搜)。
  • 交互要顺手:点一下按键组合直接复制,省得手抄。
  • 样式适配:Web / PC 屏幕宽度变化大,页面间距和字号最好做响应式。

下面我按项目里常见的拆法写:先定义数据结构,再做列表单元,最后拼成页面。

1) 数据结构:别用 Map<String, dynamic> 硬扛

dart 复制代码
class ShortcutEntry {
  final String keys;
  final String desc;

  const ShortcutEntry({
    required this.keys,
    required this.desc,
  });
}

const Map<String, List<ShortcutEntry>> kShortcuts = {
  'VS Code': [
    ShortcutEntry(keys: 'Ctrl + P', desc: '快速打开文件'),
    ShortcutEntry(keys: 'Ctrl + Shift + P', desc: '命令面板'),
    ShortcutEntry(keys: 'Ctrl + /', desc: '切换注释'),
    ShortcutEntry(keys: 'Alt + ↑/↓', desc: '移动行'),
    ShortcutEntry(keys: 'Ctrl + D', desc: '选择下一个匹配项'),
  ],
  'Chrome DevTools': [
    ShortcutEntry(keys: 'F12', desc: '打开开发者工具'),
    ShortcutEntry(keys: 'Ctrl + Shift + C', desc: '选择元素'),
    ShortcutEntry(keys: 'Ctrl + Shift + J', desc: '打开控制台'),
    ShortcutEntry(keys: 'Ctrl + R', desc: '刷新页面'),
  ],
};

说明

  • [类型明确] ShortcutEntry 这个小模型能让代码更稳,后面做搜索、排序、埋点都方便。
  • [数据可替换] 这里先用 const 做内置数据;后续如果要从接口下发,把 kShortcuts 换成仓库层(Repository)返回的数据结构就行。

2) 列表单元:展示 + 一键复制

dart 复制代码
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';

class ShortcutTile extends StatelessWidget {
  final ShortcutEntry entry;

  const ShortcutTile({
    super.key,
    required this.entry,
  });

  @override
  Widget build(BuildContext context) {
    return Card(
      margin: EdgeInsets.only(bottom: 8.h),
      child: ListTile(
        onTap: () async {
          await Clipboard.setData(ClipboardData(text: entry.keys));
          if (!context.mounted) return;
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(content: Text('已复制:${entry.keys}')),
          );
        },
        leading: Container(
          padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
          decoration: BoxDecoration(
            color: Colors.grey.shade200,
            borderRadius: BorderRadius.circular(4.r),
          ),
          child: Text(
            entry.keys,
            style: TextStyle(
              fontFamily: 'monospace',
              fontSize: 12.sp,
              fontWeight: FontWeight.w700,
            ),
          ),
        ),
        title: Text(entry.desc),
        trailing: Icon(Icons.copy, size: 18.sp, color: Colors.grey.shade600),
      ),
    );
  }
}

说明

  • [点击复制]Clipboard.setData 是 Web/桌面常用的交互,配合 SnackBar 给到反馈,用户会觉得"这个页面能用"。
  • [mounted 判断] await 之后补一个 context.mounted,避免页面刚好被销毁时弹 SnackBar 报错,这个在真实项目里挺常见。
  • [视觉层次] 左侧按键用等宽字体 + 浅灰底,扫一眼就能区分"按键组合"和"说明文字"。

3) 分组块:标题 + 列表

dart 复制代码
class ShortcutSection extends StatelessWidget {
  final String title;
  final List<ShortcutEntry> items;

  const ShortcutSection({
    super.key,
    required this.title,
    required this.items,
  });

  @override
  Widget build(BuildContext context) {
    if (items.isEmpty) return const SizedBox.shrink();

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Padding(
          padding: EdgeInsets.symmetric(vertical: 12.h),
          child: Text(
            title,
            style: TextStyle(
              fontSize: 18.sp,
              fontWeight: FontWeight.w800,
              color: Colors.blue,
            ),
          ),
        ),
        for (final entry in items) ShortcutTile(entry: entry),
        SizedBox(height: 12.h),
      ],
    );
  }
}

说明

  • [空分组直接隐藏] 搜索过滤后可能出现某些工具没有匹配项,items.isEmpty 时返回空控件,列表会更干净。
  • [for 循环代替 map] 在 UI 代码里我更偏向用 for,断点调试更直观,阅读成本也低一些。

4) 页面本体:搜索过滤 + 渲染

dart 复制代码
class ShortcutsPage extends StatefulWidget {
  const ShortcutsPage({super.key});

  @override
  State<ShortcutsPage> createState() => _ShortcutsPageState();
}

class _ShortcutsPageState extends State<ShortcutsPage> {
  final _controller = TextEditingController();

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  Map<String, List<ShortcutEntry>> _filter(String q) {
    final query = q.trim().toLowerCase();
    if (query.isEmpty) return kShortcuts;

    return {
      for (final e in kShortcuts.entries)
        e.key: e.value
            .where((it) =>
                it.keys.toLowerCase().contains(query) ||
                it.desc.toLowerCase().contains(query))
            .toList(),
    };
  }

  @override
  Widget build(BuildContext context) {
    final filtered = _filter(_controller.text);

    return Scaffold(
      appBar: AppBar(title: const Text('快捷键参考')),
      body: ListView(
        padding: EdgeInsets.all(16.w),
        children: [
          TextField(
            controller: _controller,
            decoration: InputDecoration(
              hintText: '搜索:比如 Ctrl、F12、控制台...',
              prefixIcon: const Icon(Icons.search),
              border: OutlineInputBorder(
                borderRadius: BorderRadius.circular(10.r),
              ),
              isDense: true,
            ),
            onChanged: (_) => setState(() {}),
          ),
          SizedBox(height: 12.h),
          for (final entry in filtered.entries)
            ShortcutSection(title: entry.key, items: entry.value),
        ],
      ),
    );
  }
}

说明

  • [过滤逻辑放在函数里] _filter 单独抽出来,后面要加"只看收藏""按热度排序"时不至于把 build 搞得一团乱。
  • [过滤策略] 同时对 keys / desc 过滤;并且用 trim + toLowerCase,能覆盖大多数输入习惯。
  • [不要一次性 setState 太重] 这里直接 onChanged => setState 是最简单的写法;如果数据变多(比如几百条),再加个 debounce 就够用了。

5) 接入到项目:ScreenUtil 初始化(常见遗漏点)

dart 复制代码
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';

void main() {
  runApp(const App());
}

class App extends StatelessWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context) {
    return ScreenUtilInit(
      designSize: const Size(375, 812),
      minTextAdapt: true,
      builder: (_, __) {
        return MaterialApp(
          home: const ShortcutsPage(),
        );
      },
    );
  }
}

说明

  • [先初始化再用 .w/.h/.sp] flutter_screenutil 最容易踩坑的就是:没 ScreenUtilInit 就开始用 16.w,结果某些端上尺寸会不对。
  • [designSize 怎么选] 这里用常见的 iPhone X 尺寸只是示例;你项目里如果有设计稿基准,按设计稿来就行。

一点小经验(写在最后)

  • [快捷键会变] 这类内容维护成本不低,最好把数据结构留好口子,后面能换成配置文件或后台下发。
  • [复制反馈要轻] SnackBar 不要太重,也别弹 Dialog,不然用户会觉得"打断操作"。
  • [Web 上更需要搜索] 屏幕能放很多内容,但用户更倾向于"搜一下马上找到",检索框属于必需品。

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

相关推荐
wangdaoyin20102 小时前
若依vue2前后端分离集成flowable
开发语言·前端·javascript
心柠2 小时前
vue3相关知识总结
前端·javascript·vue.js
Amumu121383 小时前
Vue Router(二)
java·前端
mocoding3 小时前
flutter通信小能手pigeon三方库已完成鸿蒙化适配
flutter·华为·harmonyos
一起养小猫3 小时前
Flutter for OpenHarmony 实战:2048游戏完整开发指南
flutter·游戏·harmonyos
a1117763 小时前
图书借阅管理系统(FastAPI + Vue)
前端·vue.js·fastapi
常年游走在bug的边缘4 小时前
掌握JavaScript作用域:从函数作用域到块级作用域的演进与实践
开发语言·前端·javascript
极致♀雨4 小时前
vue2+elementUI table表格勾选行冻结/置顶
前端·javascript·vue.js·elementui
林shir4 小时前
3-15-前端Web实战(Vue工程化+ElementPlus)
前端·javascript·vue.js