AMMF WebUI 页面模块开发文档 
本文档旨在帮助开发者为 AMMF WebUI 创建新的页面模块。基于提供的 app.js、core.js、settings.js 和 about.js,我们将详细介绍如何开发一个新的页面模块,包括模块结构、核心功能实现、国际化支持和页面交互等。
1. 概述 
AMMF WebUI 是一个模块化的 Web 界面,用于管理 AMMF 模块。它通过 JavaScript 模块化设计,支持动态页面加载、国际化(i18n)、主题切换和 Shell 命令执行。每个页面模块(如 SettingsPage 和 AboutPage)是一个独立的 JavaScript 对象,负责特定页面的逻辑、渲染和交互。
新页面模块需要遵循以下原则:
- 模块化:每个页面模块是独立的,注册到全局作用域(如 window.MyPage)。
- 一致性:遵循现有模块的结构和 API(如 init、render、afterRender等)。
- 国际化:支持多语言,通过 I18n.translate提供翻译。
- 异步加载:支持数据预加载和异步初始化。
2. 创建页面模块 
以下是创建一个新页面模块的步骤。我们以一个示例模块 DashboardPage 为例,该模块显示一个仪表盘页面,展示模块状态和操作按钮。
2.1 文件结构 
创建一个新文件 dashboard.js,并将其放置在与 settings.js 和 about.js 相同的目录下。文件结构如下:
/**
 * AMMF WebUI 仪表盘页面模块
 * 显示模块状态和操作
 */
const DashboardPage = {
    // 模块代码
};
// 导出模块
window.DashboardPage = DashboardPage;2.2 基本模块结构 
页面模块是一个 JavaScript 对象,包含以下核心方法和属性:
- 属性: - 存储页面状态或数据(如 moduleInfo、isLoading)。
- 配置项(如 config)。
 
- 存储页面状态或数据(如 
- 方法: - init():初始化模块,加载数据并注册事件。
- render():渲染页面 HTML。
- afterRender():渲染后绑定事件或执行其他逻辑。
- preloadData():预加载数据(可选)。
- registerActions():注册页面操作按钮。
- onActivate():页面激活时的回调。
- onDeactivate():页面停用时的清理逻辑。
- onLanguageChanged():语言切换时的处理逻辑。
 
示例模块骨架:
/**
 * AMMF WebUI 仪表盘页面模块
 * 显示模块状态和操作
 */
const DashboardPage = {
    // 状态和数据
    status: {},
    isLoading: false,
    config: {
        refreshInterval: 5000 // 自动刷新间隔(毫秒)
    },
    // 预加载数据
    async preloadData() {
        try {
            const statusData = await Core.execCommand('some_status_command');
            return { status: this.parseStatus(statusData) };
        } catch (error) {
            console.warn('预加载仪表盘数据失败:', error);
            return {};
        }
    },
    // 初始化
    async init() {
        try {
            this.registerActions();
            const preloadedData = PreloadManager.getData('dashboard') || await this.preloadData();
            this.status = preloadedData.status || {};
            I18n.registerLanguageChangeHandler(this.onLanguageChanged.bind(this));
            return true;
        } catch (error) {
            console.error('初始化仪表盘页面失败:', error);
            return false;
        }
    },
    // 渲染页面
    render() {
        return `
            <div class="dashboard-container">
                <h2>${I18n.translate('DASHBOARD_TITLE', '仪表盘')}</h2>
                <div class="status-card">
                    <p>${I18n.translate('STATUS', '状态')}: ${this.status.value || '未知'}</p>
                </div>
            </div>
        `;
    },
    // 渲染后回调
    afterRender() {
        // 绑定事件
        const refreshButton = document.getElementById('refresh-dashboard');
        if (refreshButton) {
            refreshButton.addEventListener('click', () => this.refreshStatus());
        }
    },
    // 注册操作按钮
    registerActions() {
        UI.registerPageActions('dashboard', [
            {
                id: 'refresh-dashboard',
                icon: 'refresh',
                title: I18n.translate('REFRESH', '刷新'),
                onClick: 'refreshStatus'
            }
        ]);
    },
    // 刷新状态
    async refreshStatus() {
        try {
            this.showLoading();
            const statusData = await Core.execCommand('some_status_command');
            this.status = this.parseStatus(statusData);
            this.updateDisplay();
            Core.showToast(I18n.translate('STATUS_REFRESHED', '状态已刷新'));
        } catch (error) {
            console.error('刷新状态失败:', error);
            Core.showToast(I18n.translate('STATUS_REFRESH_ERROR', '刷新状态失败'), 'error');
        } finally {
            this.hideLoading();
        }
    },
    // 解析状态数据
    parseStatus(data) {
        // 假设数据是键值对
        return { value: data.trim() || '未知' };
    },
    // 更新显示
    updateDisplay() {
        const container = document.querySelector('.dashboard-container');
        if (container) {
            container.innerHTML = this.render().trim();
            this.afterRender();
        }
    },
    // 显示加载中
    showLoading() {
        this.isLoading = true;
        // 显示加载动画(可参考 `SettingsPage.showLoading`)
    },
    // 隐藏加载中
    hideLoading() {
        this.isLoading = false;
        // 隐藏加载动画(可参考 `SettingsPage.hideLoading`)
    },
    // 语言切换处理
    onLanguageChanged() {
        this.updateDisplay();
    },
    // 页面激活
    onActivate() {
        // 可在此启动定时刷新等
    },
    // 页面停用
    onDeactivate() {
        I18n.unregisterLanguageChangeHandler(this.onLanguageChanged.bind(this));
        UI.clearPageActions('dashboard');
    }
};
// 导出模块
window.DashboardPage = DashboardPage;3. 核心功能实现 
3.1 初始化和数据加载 
- 使用 init方法:初始化页面模块,加载数据并注册事件。参考SettingsPage.init和AboutPage.init:- 调用 registerActions注册操作按钮。
- 使用 PreloadManager.getData获取预加载数据,或调用preloadData加载数据。
- 注册语言切换处理器 I18n.registerLanguageChangeHandler。
 
- 调用 
- 异步操作:使用 async/await确保数据加载完成。
- 错误处理:捕获异常并记录日志,返回 true(成功)或false(失败)。
async init() {
    try {
        this.registerActions();
        const preloadedData = PreloadManager.getData('dashboard') || await this.preloadData();
        this.status = Forthcoming data.status || {};
        I18n.registerLanguageChangeHandler(this.onLanguageChanged.bind(this));
        return true;
    } catch (error) {
        console.error('初始化仪表盘页面失败:', error);
        return false;
    }
}3.2 渲染页面 
- 使用 render方法:返回页面的 HTML 字符串。参考SettingsPage.render和AboutPage.render:- 使用模板字符串(`)生成 HTML。
- 嵌入国际化文本 I18n.translate(key, fallback)。
- 动态插入数据(如 this.status.value)。
 
- 使用模板字符串(
- 结构化 HTML:使用语义化的类名(如 dashboard-container、status-card)和 Material Design 图标。
render() {
    return `
        <div class="dashboard-container">
            <h2>${I18n.translate('DASHBOARD_TITLE', '仪表盘')}</h2>
            <div class="status-card">
                <p>${I18n.translate('STATUS', '状态')}: ${this.status.value || '未知'}</p>
            </div>
        </div>
    `;
}3.3 渲染后逻辑 
- 使用 afterRender方法:在页面渲染后绑定事件或执行其他逻辑。参考AboutPage.afterRender:- 绑定按钮点击事件(如 refreshButton.addEventListener)。
- 初始化动态组件(如滑块、对话框)。
 
- 绑定按钮点击事件(如 
- 事件委托:考虑使用事件委托(如 SettingsPage.bindSettingEvents)以提高性能。
afterRender() {
    const refreshButton = document.getElementById('refresh-dashboard');
    if (refreshButton) {
        refreshButton.addEventListener('click', () => this.refreshStatus());
    }
}3.4 操作按钮 
- 使用 registerActions方法:注册页面操作按钮,参考SettingsPage.registerActions和AboutPage.registerActions:- 调用 UI.registerPageActions注册按钮。
- 每个按钮需要 id、icon、title和onClick属性。
- 可选 disabled函数控制按钮禁用状态。
 
- 调用 
registerActions() {
    UI.registerPageActions('dashboard', [
        {
            id: 'refresh-dashboard',
            icon: 'refresh',
            title: I18n.translate('REFRESH', '刷新'),
            onClick: 'refreshStatus'
        }
    ]);
}3.5 数据预加载 
- 使用 preloadData方法:提前加载数据以提高页面加载速度。参考AboutPage.preloadData:- 使用 Core.execCommand执行 Shell 命令获取数据。
- 缓存数据到 sessionStorage或PreloadManager。
 
- 使用 
- 注册到 PreloadManager:在模块加载时注册预加载函数。
// 在模块加载时注册
PreloadManager.registerDataLoader('dashboard', DashboardPage.preloadData.bind(DashboardPage));3.6 国际化支持 
- 使用 I18n.translate:为所有用户界面文本提供翻译,参考SettingsPage.renderSettings和AboutPage.render:- 格式:I18n.translate('KEY', '默认值')。
- 确保提供中文(zh)和英文(en)翻译。
 
- 格式:
- 语言切换处理:实现 onLanguageChanged方法,重新渲染页面。
onLanguageChanged() {
    this.updateDisplay();
}3.7 页面生命周期 
- 激活 (onActivate):页面显示时调用,可启动定时任务或初始化状态。
- 停用 (onDeactivate):页面隐藏时调用,清理资源和事件监听器,参考SettingsPage.onDeactivate和AboutPage.onDeactivate。
onActivate() {
    // 启动定时刷新
}
onDeactivate() {
    I18n.unregisterLanguageChangeHandler(this.onLanguageChanged.bind(this));
    UI.clearPageActions('dashboard');
}3.8 Shell 命令执行 
- 使用 Core.execCommand:执行 Shell 命令以获取数据或执行操作,参考SettingsPage.loadSettingsData和AboutPage.loadModuleInfo:- 异步调用,返回命令输出。
- 处理错误并显示用户提示(Core.showToast)。
 
async refreshStatus() {
    try {
        const statusData = await Core.execCommand('some_status_command');
        this.status = this.parseStatus(statusData);
        Core.showToast(I18n.translate('STATUS_REFRESHED', '状态已刷新'));
    } catch (error) {
        Core.showToast(I18n.translate('STATUS_REFRESH_ERROR', '刷新状态失败'), 'error');
    }
}3.9 用户提示 
- 使用 Core.showToast:显示通知消息,参考SettingsPage.saveSettings和AboutPage.refreshModuleInfo:- 参数:message(消息文本)、type(info、success、warning、error)、duration(显示时长,毫秒)。
 
- 参数:
Core.showToast(I18n.translate('STATUS_REFRESHED', '状态已刷新'), 'success');4. 集成到应用 
4.1 注册路由 
在 app.js 的 Router.modules 中注册新页面模块:
static modules = {
    status: 'StatusPage',
    logs: 'LogsPage',
    settings: 'SettingsPage',
    about: 'AboutPage',
    dashboard: 'DashboardPage' // 添加新页面
};4.2 更新导航 
在主 HTML 文件中添加导航项,确保与 Router.modules 的 dashboard 键匹配:
<div class="nav-item" data-page="dashboard">
    <span class="material-symbols-rounded">dashboard</span>
    <span data-i18n="NAV_DASHBOARD">仪表盘</span>
</div>4.3 加载模块 
确保 dashboard.js 在主 HTML 中加载:
<script src="js/dashboard.js"></script>5. 最佳实践 
- 错误处理:在所有异步操作中捕获异常,使用 Core.showToast通知用户。
- 性能优化: - 使用 PreloadManager预加载数据。
- 使用事件委托减少事件监听器。
- 避免频繁 DOM 操作,使用 requestAnimationFrame优化动画。
 
- 使用 
- 国际化:为所有文本提供 I18n.translate调用,确保支持多语言。
- 清理资源:在 onDeactivate中移除事件监听器和定时器。
- 一致性:遵循现有模块的命名约定和代码风格(如 SettingsPage和AboutPage)。
- 安全:对用户输入进行 HTML 转义(如 SettingsPage.escapeHtml)以防止 XSS 攻击。
6. 示例完整代码 
以下是 dashboard.js 的完整示例代码:
/**
 * AMMF WebUI 仪表盘页面模块
 * 显示模块状态和操作
 */
const DashboardPage = {
    status: {},
    isLoading: false,
    config: {
        refreshInterval: 5000
    },
    async preloadData() {
        try {
            const statusData = await Core.execCommand('some_status_command');
            return { status: this.parseStatus(statusData) };
        } catch (error) {
            console.warn('预加载仪表盘数据失败:', error);
            return {};
        }
    },
    async init() {
        try {
            this.registerActions();
            const preloadedData = PreloadManager.getData('dashboard') || await this.preloadData();
            this.status = preloadedData.status || {};
            I18n.registerLanguageChangeHandler(this.onLanguageChanged.bind(this));
            return true;
        } catch (error) {
            console.error('初始化仪表盘页面失败:', error);
            return false;
        }
    },
    render() {
        return `
            <div class="dashboard-container">
                <h2>${I18n.translate('DASHBOARD_TITLE', '仪表盘')}</h2>
                <div class="status-card">
                    <p>${I18n.translate('STATUS', '状态')}: ${this.status.value || '未知'}</p>
                </div>
            </div>
        `;
    },
    afterRender() {
        const refreshButton = document.getElementById('refresh-dashboard');
        if (refreshButton) {
            refreshButton.addEventListener('click', () => this.refreshStatus());
        }
    },
    registerActions() {
        UI.registerPageActions('dashboard', [
            {
                id: 'refresh-dashboard',
                icon: 'refresh',
                title: I18n.translate('REFRESH', '刷新'),
                onClick: 'refreshStatus'
            }
        ]);
    },
    async refreshStatus() {
        try {
            this.showLoading();
            const statusData = await Core.execCommand('some_status_command');
            this.status = this.parseStatus(statusData);
            this.updateDisplay();
            Core.showToast(I18n.translate('STATUS_REFRESHED', '状态已刷新'));
        } catch (error) {
            console.error('刷新状态失败:', error);
            Core.showToast(I18n.translate('STATUS_REFRESH_ERROR', '刷新状态失败'), 'error');
        } finally {
            this.hideLoading();
        }
    },
    parseStatus(data) {
        return { value: data.trim() || '未知' };
    },
    updateDisplay() {
        const container = document.querySelector('.dashboard-container');
        if (container) {
            container.innerHTML = this.render().trim();
            this.afterRender();
        }
    },
    showLoading() {
        this.isLoading = true;
        const loadingElement = document.createElement('div');
        loadingElement.className = 'loading-overlay';
        loadingElement.innerHTML = '<div class="loading-spinner"></div>';
        document.querySelector('.dashboard-container')?.appendChild(loadingElement);
    },
    hideLoading() {
        this.isLoading = false;
        const loadingElement = document.querySelector('.loading-overlay');
        if (loadingElement) {
            loadingElement.remove();
        }
    },
    onLanguageChanged() {
        this.updateDisplay();
    },
    onActivate() {},
    onDeactivate() {
        I18n.unregisterLanguageChangeHandler(this.onLanguageChanged.bind(this));
        UI.clearPageActions('dashboard');
    }
};
// 注册预加载
PreloadManager.registerDataLoader('dashboard', DashboardPage.preloadData.bind(DashboardPage));
// 导出模块
window.DashboardPage = DashboardPage;7. 常见问题 
Q1:如何调试页面模块?
- 使用 console.log和console.error记录日志。
- 在浏览器开发者工具中检查 DOM 和网络请求。
- 确保 Core.execCommand返回预期输出。
Q2:如何添加新的操作按钮?
- 在 registerActions中添加新的按钮配置,确保id唯一,onClick指向模块中的方法。
Q3:如何支持多语言?
- 使用 I18n.translate为所有文本提供翻译键和默认值。
- 实现 onLanguageChanged方法以更新页面。
Q4:如何处理异步操作超时?
- 使用 Promise.race设置超时,参考SettingsPage.loadSettingsData。
8. 总结 
通过遵循本文档,您可以快速为 AMMF WebUI 创建新的页面模块。关键是保持模块化、一致性和国际化支持,同时利用 Core 和 App 提供的 API(如 execCommand、showToast、renderUI)。参考 SettingsPage 和 AboutPage 的实现,确保代码健壮且用户友好。
如需进一步帮助,请查看现有模块代码或联系 AMMF WebUI 开发团队。