在这里插入图片描述

JavaScript参考手册本质上就是一个“随手查”的工具:

  • 能快速搜到(不然列表一长就很难用)
  • 能点进去看详情(常见参数、返回值、例子)
  • 最好能一键复制(开发时真的省事)

下面我按真实项目里常见的拆分方式来写,不把所有东西都堆在一个 build() 里。

1. 数据结构:别一上来就 Map

class JsMethod {
  final String name;
  final String desc;
  final String example;

  const JsMethod({
    required this.name,
    required this.desc,
    required this.example,
  });
}

说明

实际写页面时我不太建议用 Map<String, String> 直接硬塞,因为后面你大概率会加字段(比如 categorymdnUrltags)。用一个小的 model,改动成本会低很多,而且 IDE 自动补全也舒服。

2. 方法清单:先放在本地常量,后面再接配置/接口

const kJsMethods = <JsMethod>[
  JsMethod(
    name: 'console.log()',
    desc: '输出日志到控制台',
    example: "console.log('hello');",
  ),
  JsMethod(
    name: 'setTimeout()',
    desc: '延迟执行函数',
    example: "setTimeout(() => console.log('later'), 300);",
  ),
  JsMethod(
    name: 'JSON.parse()',
    desc: '解析 JSON 字符串',
    example: "const obj = JSON.parse('{\"a\":1}');",
  ),
  JsMethod(
    name: 'Array.filter()',
    desc: '数组过滤',
    example: 'const even = [1,2,3,4].filter(x => x % 2 === 0);',
  ),
];

说明

这里我刻意把例子写成“能直接复制就能跑”的那种,不写伪代码。后面如果你要做多语言(比如 JS/TS)或者分类筛选,也可以把这些数据挪到 JSON 文件里,启动时加载。

3. 列表页骨架:用 StatefulWidget 做搜索过滤

import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';

class JsReferencePage extends StatefulWidget {
  const JsReferencePage({super.key});

  
  State<JsReferencePage> createState() => _JsReferencePageState();
}

class _JsReferencePageState extends State<JsReferencePage> {
  final _controller = TextEditingController();
  String _keyword = '';

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

说明

搜索这种交互,不管你最后用不用状态管理(Provider / Riverpod / Bloc),最起码需要一个输入源和一个过滤条件。我习惯先从最简单的 TextEditingController 起步,后面再升级。

4. 搜索框 + 过滤逻辑:放在 build() 里,但别写太挤


Widget build(BuildContext context) {
  final shown = kJsMethods.where((m) {
    final k = _keyword.trim().toLowerCase();
    if (k.isEmpty) return true;
    return m.name.toLowerCase().contains(k) || m.desc.toLowerCase().contains(k);
  }).toList();

  return Scaffold(
    appBar: AppBar(title: const Text('JavaScript参考')),
    body: Padding(
      padding: EdgeInsets.all(16.w),
      child: Column(
        children: [
          TextField(
            controller: _controller,
            decoration: const InputDecoration(
              hintText: '输入关键字,比如 parse / timeout',
              prefixIcon: Icon(Icons.search),
            ),
            onChanged: (v) => setState(() => _keyword = v),
          ),
          SizedBox(height: 12.h),
          Expanded(
            child: ListView.builder(
              itemCount: shown.length,
              itemBuilder: (context, index) => _MethodTile(method: shown[index]),
            ),
          ),
        ],
      ),
    ),
  );
}

说明

这里的过滤我用的是最“朴素”的 contains。真实项目里如果数据量大、还要模糊匹配,你可以换成分词或拼音库,但一开始别过度设计。

另外 ScreenUtil 这类适配库,一定要确保在 main() 里已经初始化,不然 16.w 这种调用会直接不正常(这个坑我踩过一次)。

5. 列表项组件:把点击行为收口到一个地方

class _MethodTile extends StatelessWidget {
  final JsMethod method;
  const _MethodTile({required this.method});

  
  Widget build(BuildContext context) {
    return Card(
      margin: EdgeInsets.only(bottom: 12.h),
      child: ListTile(
        leading: Icon(Icons.code, color: Colors.yellow[700]),
        title: Text(
          method.name,
          style: const TextStyle(fontFamily: 'monospace', fontWeight: FontWeight.bold),
        ),
        subtitle: Text(method.desc),
        trailing: const Icon(Icons.arrow_forward_ios, size: 16),
        onTap: () => Navigator.of(context).push(
          MaterialPageRoute(builder: (_) => JsMethodDetailPage(method: method)),
        ),
      ),
    );
  }
}

说明

列表项组件抽出来之后,页面主结构就不会被 Card/ListTile 这些 UI 细节淹没。点进去的行为也集中管理,后面你要换成命名路由或者加埋点,改一个地方就够。

6. 详情页:展示示例 + 复制按钮(使用系统剪贴板)

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';

class JsMethodDetailPage extends StatelessWidget {
  final JsMethod method;
  const JsMethodDetailPage({super.key, required this.method});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(method.name)),
      body: Padding(
        padding: EdgeInsets.all(16.w),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(method.desc, style: Theme.of(context).textTheme.titleMedium),
            SizedBox(height: 12.h),
            _ExampleBlock(example: method.example),
          ],
        ),
      ),
    );
  }
}

说明

详情页我就做三件事:

  • 标题:方法名,一眼知道自己在看什么
  • 描述:一句话说明用途
  • 例子块:能复制,能直接拿去用

不要一上来把“参数解释、更多示例、链接跳转”全塞进来,先把核心体验打通。

7. 例子块:看起来像代码、但不追求做成复杂编辑器

class _ExampleBlock extends StatelessWidget {
  final String example;
  const _ExampleBlock({required this.example});

  
  Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      padding: EdgeInsets.all(12.w),
      decoration: BoxDecoration(
        color: Colors.black.withValues(alpha: 0.06),
        borderRadius: BorderRadius.circular(12.r),
      ),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Expanded(
            child: Text(
              example,
              style: const TextStyle(fontFamily: 'monospace'),
            ),
          ),
          IconButton(
            tooltip: '复制',
            icon: const Icon(Icons.copy),
            onPressed: () async {
              await Clipboard.setData(ClipboardData(text: example));
              if (!context.mounted) return;
              ScaffoldMessenger.of(context).showSnackBar(
                const SnackBar(content: Text('已复制到剪贴板')),
              );
            },
          ),
        ],
      ),
    );
  }
}

说明

我这里没上第三方高亮库(比如 code editor),原因很简单:这个页面是“参考”,不是“IDE”。做成一个干净的等宽字体块,再给一个复制按钮,基本就够用了。

顺手提一句:context.mounted 这个判断是为了避免页面已经被 pop 掉时还去弹 SnackBar,一些机型/场景下确实能碰到。

8. 这一页在项目里怎么接

如果你的首页是一个工具列表,一般就是点进去 push 这页。比如:

Navigator.of(context).push(
  MaterialPageRoute(builder: (_) => const JsReferencePage()),
);

说明

我个人更喜欢先用 MaterialPageRoute 把流程跑通;等页面多了、路由复杂了,再统一改成命名路由或路由库,不然一开始容易“为了架构而架构”。


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

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐