# JS开发框架 **Repository Path**: legend0/jPages ## Basic Information - **Project Name**: JS开发框架 - **Description**: js框架,快速开发 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-01-17 - **Last Updated**: 2026-05-03 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # JPage 框架 - 轻量级插件化 SPA 框架 > 🎉 **JPage** - 基于 Hash 路由的轻量级单页应用框架,采用插件化架构,核心精简,功能按需加载! *** ## 📖 目录 - [核心特性](#核心特性) - [快速开始](#快速开始) - [核心API](#核心api) - [响应式系统](#响应式系统) - [错误处理](#错误处理) - [插件系统](#插件系统) - [路由管理](#路由管理) - [页面管理](#页面管理) - [组件系统](#组件系统) - [配置选项](#配置选项) - [项目结构](#项目结构) - [常见问题](#常见问题) *** ## ✨ 核心特性 - ✅ **插件化架构** - 灵活的插件系统,支持自定义插件开发(如状态管理、异步任务管理等) - ✅ **生产级模板引擎** - 内置轻量级模板引擎,支持过滤器、条件、循环等语法 - ✅ **工具函数批量导入** - 支持 `JPage.utils()` 批量导入,支持通配符 `*` - ✅ **Hash 路由系统** - 支持路由参数、查询参数、路由守卫和嵌套路由 - ✅ **页面管理** - 自动加载页面组件,支持 CSS 隔离和智能缓存 - ✅ **组件系统** - 支持组件的注册、加载、渲染和生命周期管理 - ✅ **生命周期** - 完整的页面和组件生命周期钩子函数 - ✅ **响应式系统** - 基于 Proxy 的自动响应式数据,数据变更自动触发视图更新 - ✅ **错误处理** - 统一的错误处理机制,支持错误分类和全局处理 - ✅ **事件通信** - 统一的事件通信机制,内部使用 `$on/$emit`,外部使用 `JPage.on/emit` - ✅ **调试模式** - 详细的日志输出,方便开发调试 - ✅ **TypeScript 友好** - 完整的 JSDoc 注释,类型提示完善 *** ## 🚀 快速开始 ### 安装 ```bash # 克隆项目 git clone https://github.com/your-repo/jpage.git # 安装依赖 npm install # 构建项目 npm run build ``` ### 基础使用 ```html JPage Demo
``` *** ## 📦 核心API ### JPage.options(config) 配置框架全局选项。 **参数:** | 参数 | 类型 | 默认值 | 说明 | | ------------ | ------- | ----------------------------------- | ------------- | | `root` | String | `'#app'` | 页面挂载的根元素选择器 | | `pages` | String | `'./pages'` | 页面组件目录路径 | | `components` | String | `'./pages/element'` | 元素组件目录路径 | | `utils` | String | `'./utils'` | 工具函数目录路径 | | `debug` | Boolean | `false` | 是否启用调试模式 | | `cache` | Object | `{ maxPages: 10, maxAge: 1800000 }` | 页面缓存配置 | | `transition` | Object | `{ name: 'fade', duration: 300 }` | 页面过渡动画配置,支持淡入淡出fade、滑动slide、缩放scale、翻转flip等效果 | | `guard` | Object | `{ beforeEach: null, afterEach: null }` | 路由守卫配置 | | `errorHandler` | Function | `null` | 全局错误处理函数 | | `plugins` | Array | `[]` | 预注册插件列表 | **示例:** ```javascript JPage.options({ root: '#app', pages: './pages', components: './components', utils: './utils', debug: true, cache: { maxPages: 20, // 最大缓存页面数 maxAge: 3600000 // 缓存有效期(毫秒) }, transition: { name: 'fade', // 过渡动画名称:fade、slide、scale、flip duration: 300 // 过渡动画时长(毫秒) }, guard: { beforeEach: null, // 全局前置守卫 afterEach: null // 全局后置守卫 }, errorHandler: null, // 全局错误处理函数 plugins: [] // 预注册插件列表 }); ``` *** ### JPage.utils(utilsConfig) 批量导入工具函数,支持通配符导入。 **参数:** - `utilsConfig` (Object): 工具函数配置对象 - 键:工具函数名称(或 `*` 表示通配符) - 值:模块路径 **返回值:** - `Promise`: JPage 实例(支持链式调用) **示例:** ```javascript // 导入单个工具函数 await JPage.utils({ format: './utils/format.js', storage: './utils/storage.js' }); // 通配符导入整个目录(导入模块的所有导出) await JPage.utils({ '*': './utils/index.js' }); // 混合导入 await JPage.utils({ format: './utils/format.js', '*': './utils/index.js' }); // 在页面/组件中使用 this.utils.format.date(new Date()); this.utils.storage.get('key'); ``` > **注意**:通配符 `*` 导入会将模块的所有命名导出注册为工具函数。同名工具函数会被后注册的覆盖。 *** ### JPage.use(plugin, options) 使用插件(JPageCore 原生方法)。 **参数:** - `plugin` (Object): 插件对象,需包含 `install(app, options)` 方法 - `options` (Object): 插件配置选项 **返回值:** - `JPage`: JPage 实例 *** ### JPage.plugin(name, pluginOrPath, options) 加载插件,支持路径和对象两种方式。 **参数:** - `name` (String): 插件名称 - `pluginOrPath` (String | Object): 插件路径或插件对象 - `options` (Object): 插件配置选项 **返回值:** - `Promise`: JPage 实例(支持链式调用) **示例:** ```javascript // 方式1:通过路径加载 await JPage.plugin('myPlugin', './plugins/myPlugin.js', { // 插件配置选项 }); // 方式2:直接传入插件对象 import myPlugin from './plugins/myPlugin.js'; await JPage.plugin('myPlugin', myPlugin); // 加载多个插件 await JPage.plugin('state', './plugins/state.js'); await JPage.plugin('event', './plugins/event.js'); ``` *** ### JPage.routes(routesConfig) 配置路由映射表。 **参数:** - `routesConfig` (Object): 路由配置对象 **路由配置项:** | 参数 | 类型 | 默认值 | 说明 | | ------------- | -------- | ------- | -------------- | | `component` | String | - | 页面组件名称 | | `css` | Boolean | `false` | 是否加载对应的 CSS 文件 | | `cache` | Boolean | `false` | 是否缓存页面 | | `meta` | Object | `{}` | 路由元信息 | | `beforeEnter` | Function | - | 路由进入前守卫 | **示例:** ```javascript JPage.routes({ '/': { component: 'home', css: true, meta: { title: '首页' } }, '/about': { component: 'about', css: true }, '/user/:id': { component: 'user', css: true, cache: true, beforeEnter: (to, from) => { // 路由守卫 return true; } }, '/post/:id/comment/:cid': { component: 'comment' } }); ``` *** ### JPage.init() 初始化框架,开始监听路由变化。 **返回值:** - `Promise`: JPage 实例 **示例:** ```javascript await JPage.init(); console.log('JPage 初始化完成'); ``` *** ### JPage.route(path, handler) 添加单个路由配置。 **参数:** - `path` (String): 路由路径 - `handler` (Object|Function): 路由配置对象或处理函数 **返回值:** - `JPage`: JPage 实例(支持链式调用) **示例:** ```javascript JPage.route('/about', { component: 'about', css: true }); JPage.route('/health', () => { console.log('健康检查'); }); ``` *** ### 页面操作 API #### JPage.pages.reload(data) 重新加载当前页面。 **参数:** - `data` (Object): 可选的数据对象 - `params` (Object): 新的路由参数 - `query` (Object): 新的查询参数 **返回值:** - `Promise`: 新的页面实例 **示例:** ```javascript const newPage = await JPage.pages.reload(); const newPage = await JPage.pages.reload({ params: { id: 456 }, query: { tab: 'info' } }); ``` #### JPage.pages.getCurrent() 获取当前页面实例。 **返回值:** - `Page`: 当前页面实例 **示例:** ```javascript const currentPage = JPage.pages.getCurrent(); console.log('当前页面:', currentPage.name); ``` #### JPage.pages.getPrevious() 获取上一个页面实例。 **返回值:** - `Page`: 上一个页面实例 **示例:** ```javascript const previousPage = JPage.pages.getPrevious(); console.log('上一页:', previousPage?.name); ``` #### JPage.pages.clearCache(pageName) 清除页面缓存。 **参数:** - `pageName` (String): 页面名称(可选,不传则清除所有缓存) **示例:** ```javascript // 清除指定页面缓存 JPage.pages.clearCache('user'); // 清除所有页面缓存 JPage.pages.clearCache(); ``` #### JPage.pages.has(name) 检查页面是否已注册。 **参数:** - `name` (String): 页面名称 **返回值:** - `Boolean`: 是否存在 **示例:** ```javascript if (JPage.pages.has('home')) { console.log('首页已注册'); } ``` *** #### JPage.error 统一的错误处理 API,支持注册错误处理器、创建标准化错误等功能。 **子方法:** | 方法 | 参数 | 返回值 | 说明 | | ---------------------------------- | ------------------------------------- | --------- | ----------- | | `error.register(type, handler)` | String, Function | ErrorHandler | 注册特定类型错误处理器 | | `error.registerGlobal(handler)` | Function | ErrorHandler | 注册全局错误处理器 | | `error.handle(error, context)` | Error, Object | Promise\ | 处理错误 | | `error.create(message, options)` | String, Object | JPageError | 创建标准化错误 | | `error.validation(field, message)` | String, String | JPageError | 创建验证错误 | | `error.auth(message)` | String | JPageError | 创建认证错误 | | `error.timeout(message)` | String | JPageError | 创建超时错误 | | `error.network(message)` | String | JPageError | 创建网络错误 | | `error.notFound(message)` | String | JPageError | 创建未找到错误 | | `error.server(message)` | String | JPageError | 创建服务器错误 | | `error.abort(message)` | String | JPageError | 创建中止错误 | **JPageError 对象属性:** | 属性 | 类型 | 说明 | | --------- | ------- | --------- | | `message` | String | 错误信息 | | `type` | String | 错误类型 | | `code` | String | 错误代码 | | `details` | Object | 错误详情 | | `retryable` | Boolean | 是否可重试 | | `timestamp` | Number | 错误时间戳 | **示例:** ```javascript // 注册全局错误处理器 JPage.error.registerGlobal((error, context) => { console.error('全局错误:', error.message); }); // 注册特定类型错误处理器 JPage.error.register('auth', (error, context) => { console.error('认证失败:', error.message); JPage.push('/login'); }); // 创建并处理错误 try { throw JPage.error.create('操作失败'); } catch (error) { JPage.error.handle(error, { type: 'custom' }); } ``` *** #### JPage.debug() 获取框架调试信息。 **返回值:** - `Object`: 包含框架状态的调试信息对象 **示例:** ```javascript const info = JPage.debug(); console.log('框架状态:', info); // { // initialized: true, // router: { currentRoute: {...}, routeCount: 5 }, // pageManager: { loadedPages: 3, currentPage: 'home', ... }, // componentManager: { registered: 2, instances: 5 }, // utils: ['format', 'storage'], // plugins: ['event'] // } ``` *** #### JPage.getUtils() 获取已加载的工具函数对象。 **示例:** ```javascript const utils = JPage.getUtils(); console.log('已加载工具:', Object.keys(utils)); ``` *** #### JPage.getRouter() 获取路由器实例。 **示例:** ```javascript const router = JPage.getRouter(); console.log('当前路由:', router.currentRoute); ``` *** #### JPage.getPageManager() 获取页面管理器实例。 **示例:** ```javascript const pageManager = JPage.getPageManager(); console.log('当前页面:', pageManager.getCurrentPage()); ``` *** #### JPage.getComponentManager() 获取组件管理器实例。 **示例:** ```javascript const componentManager = JPage.getComponentManager(); console.log('组件统计:', componentManager.getComponentCount()); ``` *** #### JPage.canGoBack() 检查是否可以后退。 **返回值:** - `Boolean`: 历史栈中是否有前一条记录 **示例:** ```javascript if (JPage.canGoBack()) { JPage.back(); } ``` *** #### JPage.canGoForward() 检查是否可以前进。 **返回值:** - `Boolean`: 历史栈中是否有后一条记录 **示例:** ```javascript if (JPage.canGoForward()) { JPage.forward(); } ``` *** #### JPage.getHistoryLength() 获取历史栈长度。 **返回值:** - `Number`: 历史栈中的记录数 **示例:** ```javascript const length = JPage.getHistoryLength(); console.log('历史记录数:', length); ``` *** #### JPage.service(name, factory, options) 注册依赖注入服务(JPageCore 原生方法)。 **参数:** - `name` (String): 服务名称 - `factory` (Function): 服务工厂函数,返回服务实例 - `options` (Object): 配置选项,支持 `{ singleton: true }` **返回值:** - `JPage`: JPage 实例 **示例:** ```javascript // 注册单例服务 JPage.service('api', () => new ApiService(), { singleton: true }); // 注册普通服务(每次调用返回新实例) JPage.service('logger', () => createLogger()); ``` *** #### JPage.resolve(name) 解析依赖注入服务(JPageCore 原生方法)。 **参数:** - `name` (String): 服务名称 **返回值:** - `*`: 服务实例,未找到返回 `undefined` **示例:** ```javascript const api = JPage.resolve('api'); if (api) { const data = await api.fetch('/users'); } ``` *** #### JPage.has(name) 检查服务是否已注册(JPageCore 原生方法)。 **参数:** - `name` (String): 服务名称 **返回值:** - `Boolean`: 服务是否已注册 **示例:** ```javascript if (JPage.has('api')) { console.log('API服务已注册'); } ``` *** #### JPage.destroy() 销毁框架实例,清理所有资源(JPageCore 原生方法)。 **示例:** ```javascript // 销毁框架 JPage.destroy(); ``` *** #### JPage.isInitialized 检查框架是否已初始化(只读属性)。 **返回值:** - `Boolean`: 是否已初始化 **示例:** ```javascript if (JPage.isInitialized) { console.log('框架已初始化'); } ``` *** #### JPage.isDestroyed 检查框架是否已销毁(只读属性)。 **返回值:** - `Boolean`: 是否已销毁 **示例:** ```javascript if (JPage.isDestroyed) { console.log('框架已销毁'); } ``` *** ## ⚡ 响应式系统 JPage 内置了基于 Proxy 的响应式数据系统,数据变更会自动触发视图更新,无需手动调用更新方法。 ### 基本使用 在页面或组件中定义 `data` 属性,框架会自动将其转换为响应式数据: ```javascript JPage.route('/user/:id', { name: 'user', template: `

{{ user.name }}

年龄: {{ user.age }}

描述: {{ computed.description }}

`, data() { return { user: { name: '张三', age: 25 } }; }, computed: { description() { return `${this.data.user.name}今年${this.data.user.age}岁`; } }, mounted() { // 直接修改数据即可触发更新 setTimeout(() => { this.data.user.name = '李四'; this.data.user.age = 26; }, 1000); } }); ``` ### 响应式特性 修改 `this.data` 中的属性会自动触发视图更新,支持嵌套对象和数组,通过 `computed` 定义计算属性可实现自动依赖追踪。 ### 计算属性 计算属性基于依赖数据动态计算,依赖变化时自动更新。 ```javascript computed: { fullName() { return `${this.data.firstName} ${this.data.lastName}`; } } ``` ### 生命周期钩子 数据变更时触发 `beforeUpdate` 和 `updated` 钩子,完整生命周期包括:`beforeLoad` → `loaded` → `beforeEnter` → `entered` → `mounted` → `beforeUpdate/updated` → `beforeLeave` → `leaved` → `beforeCache/cached` → `activated/deactivated`。 *** ## ❌ 错误处理 JPage 提供统一的错误处理 API `JPage.error`,支持错误类型分类、全局和特定类型错误处理。 ### 基本用法 ```javascript // 注册全局错误处理器 JPage.error.registerGlobal((error, context) => { console.error('全局错误:', error.message); // 发送错误到监控系统 // sendToMonitor(error, context); }); // 注册特定类型错误处理器 JPage.error.register('auth', (error, context) => { // 处理认证错误 console.error('认证失败:', error.message); // 跳转到登录页 JPage.push('/login'); }); // 创建标准化错误 const validationError = JPage.error.validation('email', '邮箱格式不正确'); ``` ### API 列表 | 方法 | 参数 | 说明 | | ---------------------------------- | -------------------------- | --------- | | `error.register(types, handler)` | types: 错误类型, handler: 处理函数 | 注册错误处理器 | | `error.registerGlobal(handler)` | handler: 处理函数 | 注册全局错误处理器 | | `error.handle(error, context)` | error: 错误, context: 上下文 | 处理错误 | | `error.create(message, options)` | message: 消息, options: 选项 | 创建标准化错误 | | `error.validation(field, message)` | field: 字段, message: 消息 | 创建验证错误 | | `error.auth(message)` | message: 消息 | 创建认证错误 | | `error.timeout(message)` | message: 消息 | 创建超时错误 | | `error.network(message)` | message: 消息 | 创建网络错误 | ### 错误类型 框架内置两类错误类型体系: **全局错误处理器类型**(由 `window.onerror`/`unhandledrejection` 自动分类): | 类型 | 说明 | | --------------- | -------------------------- | | `runtime:sync` | 同步运行时错误 | | `runtime:async` | 异步运行时错误(Promise rejection) | | `resource:load` | 资源加载错误(图片、脚本等) | | `router:error` | 路由错误 | | `page:load` | 页面加载错误 | **ErrorHandler 枚举类型**(用于 `JPage.error.register()` 注册自定义处理器): | 类型 | 说明 | | ------------ | ----- | | `abort` | 中止错误 | | `type` | 类型错误 | | `not-found` | 未找到错误 | | `server` | 服务器错误 | | `validation` | 验证错误 | | `auth` | 认证错误 | | `timeout` | 超时错误 | | `network` | 网络错误 | | `unknown` | 未知错误 | > **注意**:`JPage.error.register(type, handler)` 注册的处理器仅在 `type` 与 ErrorHandler 枚举类型匹配时生效。如需处理 `runtime:sync` 等全局类型,请使用 `JPage.error.registerGlobal(handler)`。 ### 使用示例 ```javascript try { const result = await fetchData(); } catch (error) { await JPage.error.handle(error, { type: 'api', endpoint: '/api/data', timestamp: Date.now() }); } // 创建并抛出错误 throw JPage.error.create('操作失败', { code: 'OPERATION_FAILED', retryable: true }); ``` *** ## 🔌 插件系统 JPage 提供了灵活的插件系统,允许开发者根据需求开发和使用自定义插件。 ### 插件结构 一个标准的 JPage 插件应该包含以下结构: ```javascript // myPlugin.js const myPlugin = { /** * 插件名称(可选) */ name: 'myPlugin', /** * 插件安装方法(必需) * @param {Object} JPage - JPage实例 * @param {Object} options - 插件配置选项 */ install(JPage, options = {}) { // 插件安装逻辑 // 1. 创建服务实例 const service = { method1() { // 方法实现 }, method2() { // 方法实现 } }; // 2. 添加到实例方法(页面/组件中使用 this.myService) JPage.prototype.myService = service; // 3. 添加到全局方法(全局使用 JPage.myService) JPage.myService = service; } }; export default myPlugin; ``` ### 使用插件 ```javascript // 方式1:通过路径加载 await JPage.plugin('myPlugin', './plugins/myPlugin.js', { // 配置选项 }); // 方式2:直接传入插件对象 import myPlugin from './plugins/myPlugin.js'; await JPage.plugin('myPlugin', myPlugin, { // 配置选项 }); // 使用插件功能 JPage.myService.method1(); // 全局调用 this.myService.method2(); // 实例调用(页面/组件中) ``` ### 插件开发最佳实践 > **注意**:JPage 框架已内置事件通信 (`JPage.on/emit`),状态管理需通过插件方式实现。以下示例作为自定义插件开发的参考。 #### 1. 自定义状态管理插件示例 JPage 框架核心不内置状态管理,需通过插件方式实现。以下是一个简单的状态管理插件示例: ```javascript // plugins/custom-state.js class CustomStateManager { constructor(options = {}) { this.state = {}; this.watchers = new Map(); this.enablePersistence = options.enablePersistence || false; // 从 localStorage 恢复状态(如果启用持久化) if (this.enablePersistence && typeof window !== 'undefined') { const saved = localStorage.getItem('app-state'); if (saved) { try { this.state = JSON.parse(saved); } catch (e) { console.error('Failed to load state from localStorage'); } } } } get(key) { return key ? this.state[key] : { ...this.state }; } set(key, value) { const oldValue = this.state[key]; this.state[key] = value; this._notifyWatchers(key, value, oldValue); // 持久化到 localStorage if (this.enablePersistence && typeof window !== 'undefined') { localStorage.setItem('app-state', JSON.stringify(this.state)); } } watch(key, callback) { if (!this.watchers.has(key)) { this.watchers.set(key, []); } this.watchers.get(key).push(callback); } _notifyWatchers(key, newValue, oldValue) { const watchers = this.watchers.get(key) || []; watchers.forEach(callback => callback(newValue, oldValue)); } } export default { name: 'customState', install(JPage, options = {}) { const stateManager = new CustomStateManager(options); // 添加到原型链和全局 JPage.prototype.customState = stateManager; JPage.customState = stateManager; } }; ``` #### 2. HTTP 请求插件示例 ```javascript // plugins/http.js class HttpClient { constructor(options = {}) { this.baseURL = options.baseURL || ''; this.headers = options.headers || {}; } async request(url, options = {}) { const response = await fetch(this.baseURL + url, { ...options, headers: { ...this.headers, ...options.headers } }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } return response.json(); } get(url, options = {}) { return this.request(url, { ...options, method: 'GET' }); } post(url, data, options = {}) { return this.request(url, { ...options, method: 'POST', body: JSON.stringify(data) }); } } export default { name: 'http', install(JPage, options = {}) { const http = new HttpClient(options); JPage.prototype.http = http; JPage.http = http; } }; ``` ### 插件开发注意事项 1. **命名规范**:插件名称应该具有描述性,避免与核心API冲突 2. **单一职责**:每个插件应该只负责一个特定的功能 3. **配置灵活**:提供合理的默认配置,同时允许用户自定义 4. **错误处理**:妥善处理错误,提供有意义的错误信息 5. **文档完善**:提供清晰的API文档和使用示例 *** ## 🛠️ 工具函数 ### 批量导入工具函数 ```javascript // 导入工具函数 await JPage.utils({ format: './utils/format.js', storage: './utils/storage.js', validate: './utils/validate.js', '*': './utils/index.js' // 通配符导入 }); // 使用工具函数 this.utils.format.date(new Date()); this.utils.storage.get('key'); this.utils.validate.email('test@example.com'); ``` ### 在页面/组件中按需导入 ```javascript // 页面组件 export default { template: `

{{ message }}

`, data() { return { message: 'Hello JPage' }; }, methods: { formatDate() { // 使用工具函数 const formatted = this.utils.format.date(new Date()); console.log('格式化日期:', formatted); } } }; ``` *** ## 🛣️ 路由管理 ### 路由导航 ```javascript // 编程式导航 JPage.push('/about'); // 跳转到 /about JPage.push('/user/123'); // 跳转到 /user/123 JPage.push('/post/456?tab=comments'); // 带查询参数 JPage.replace('/login'); // 替换当前路由(不留历史记录) JPage.back(); // 后退 JPage.forward(); // 前进 JPage.go(-2); // 后退2步 // 导航状态检查 JPage.canGoBack(); // 是否可以后退(返回 Boolean) JPage.canGoForward(); // 是否可以前进(返回 Boolean) JPage.getHistoryLength(); // 获取历史栈长度(返回 Number) // 获取当前路由信息 const router = JPage.getRouter(); const route = router.currentRoute; console.log(route.path); // 路由路径 console.log(route.params); // 路由参数 { id: '123' } console.log(route.query); // 查询参数 { tab: 'comments' } console.log(route.meta); // 路由元信息 ``` #### 导航状态 API 框架提供导航状态检查方法,可用于控制前进/后退按钮的启用/禁用状态: | 方法 | 返回值 | 说明 | |------|--------|------| | `JPage.canGoBack()` | `Boolean` | 是否可以后退(历史栈中有前一条记录) | | `JPage.canGoForward()` | `Boolean` | 是否可以前进(历史栈中有后一条记录) | | `JPage.getHistoryLength()` | `Number` | 历史栈长度 | **示例:实现前进/后退按钮状态控制** ```javascript // 更新导航按钮状态 function updateNavButtons() { const backBtn = document.querySelector('#backBtn'); const forwardBtn = document.querySelector('#forwardBtn'); if (backBtn) { backBtn.disabled = !JPage.canGoBack(); backBtn.classList.toggle('disabled', !JPage.canGoBack()); } if (forwardBtn) { forwardBtn.disabled = !JPage.canGoForward(); forwardBtn.classList.toggle('disabled', !JPage.canGoForward()); } } // 初始化时更新 updateNavButtons(); // 监听路由变化更新按钮状态 JPage.on('route:afterChange', () => { updateNavButtons(); }); ``` ### 路由守卫 ```javascript // 全局前置守卫(返回取消注册函数) const unregister = JPage.beforeEach((to, from) => { console.log('即将进入:', to.path); console.log('离开:', from.path); // 返回 false 取消导航 if (to.meta.requiresAuth && !isAuthenticated()) { JPage.push('/login'); return false; } return true; }); // 取消守卫注册 unregister(); // 全局后置守卫(返回取消注册函数) const unregisterAfter = JPage.afterEach((to, from) => { console.log('已进入:', to.path); // 更新页面标题 if (to.meta.title) { document.title = to.meta.title; } }); // 路由独享守卫 JPage.routes({ '/admin': { component: 'admin', beforeEnter: (to, from) => { if (!isAdmin()) { alert('无权访问'); return false; } return true; } } }); ``` ### 路由事件 框架内置以下路由事件,通过 `JPage.on()` 监听: | 事件名称 | 参数 | 说明 | | -------------------- | ----------------- | ---------- | | `route:beforeChange` | `{ to, from }` | 路由即将改变时触发 | | `route:afterChange` | `{ to, from }` | 路由已成功改变后触发 | | `route:error` | `{ path, error }` | 路由导航出错时触发 | **参数说明:** - `to`: 目标路由对象,包含 `path`、`params`、`query`、`name` 等属性 - `from`: 当前路由对象(可能为 `null`) - `path`: 出错的路径 - `error`: 错误信息或错误对象 ```javascript // 监听路由事件 JPage.on('route:beforeChange', ({ to, from }) => { console.log('路由即将改变:', to.path); }); JPage.on('route:afterChange', ({ to, from }) => { console.log('路由已改变:', to.path); }); JPage.on('route:error', ({ path, error }) => { console.error('路由错误:', path, error); }); ``` ### 路由管理器 API 通过 `JPage.getRouter()` 获取路由器实例,可使用以下方法: | 方法 | 参数 | 返回值 | 说明 | | ------------------------------ | ---------------------- | ------------ | --------- | | `routes(config)` | Object | Router | 批量配置路由 | | `route(path, handler)` | String, Object/Function | Router | 添加单个路由 | | `push(path, params, query)` | String, Object, Object | void | 导航到指定路由 | | `replace(path, params, query)` | String, Object, Object | void | 替换当前路由 | | `back()` | - | void | 后退 | | `forward()` | - | void | 前进 | | `go(n)` | Number | void | 前进/后退 n 步 | | `canGoBack()` | - | Boolean | 是否可以后退 | | `canGoForward()` | - | Boolean | 是否可以前进 | | `getHistoryLength()` | - | Number | 获取历史栈长度 | | `beforeEach(guard)` | Function | Function | 全局前置守卫,返回取消注册函数 | | `afterEach(guard)` | Function | Function | 全局后置守卫,返回取消注册函数 | | `getRoutes()` | - | Array | 获取所有路由 | | `hasRoute(path)` | String | Boolean | 检查路由是否存在 | | `getRoute(path)` | String | Route | 获取路由对象 | | `match(path)` | String | Object | 匹配路由 | | `start()` | - | void | 开始监听路由 | | `stop()` | - | void | 停止监听路由 | | `clear()` | - | void | 清除所有路由 | **示例:** ```javascript // 获取路由器实例 const router = JPage.getRouter(); // 检查路由是否存在 if (router.hasRoute('/admin')) { console.log('管理员路由已配置'); } // 获取所有路由 const routes = router.getRoutes(); console.log(`共有 ${routes.length} 个路由`); // 匹配路由 const matchResult = router.match('/user/123'); if (matchResult.matched) { console.log('匹配到路由:', matchResult.route); console.log('参数:', matchResult.params); } // 停止路由监听 router.stop(); // 重新开始监听 router.start(); ``` *** ## 📄 页面管理 ### 页面组件结构 ```javascript // pages/home.js export default { name: 'home', template: `

{{ title }}

{{ message }}

计数: {{ count }}

`, data() { return { title: '首页', message: '欢迎来到 JPage', count: 0 }; }, methods: { changeMessage() { this.message = '消息已改变!'; this.count++; } }, mounted() { document.getElementById('changeBtn').addEventListener('click', () => { this.changeMessage(); }); } }; ``` ### 模板语法 JPage 内置生产级模板引擎,支持丰富的模板语法,包括插值、表达式、条件判断、循环、过滤器等: #### 1. 插值表达式 使用 `{{ }}` 语法进行数据绑定,支持点分隔路径访问: ```javascript template: `

{{ message }}

{{ user.profile.name }}

{{ items.0.title }}

` ``` **特性:** - 自动 HTML 转义,防止 XSS 攻击 - 空值安全访问,`undefined`/`null` 自动转换为空字符串 - 支持数组索引访问(如 `items.0.name`) #### 2. 表达式计算 模板引擎支持在插值表达式中使用运算符和全局函数: ```javascript template: `

总价: {{ price * quantity }}

折扣价: {{ price * 0.8 }}

金额: {{ (price * quantity).toFixed(2) }}

是否成年: {{ age >= 18 }}

状态: {{ status === 'active' ? '在线' : '离线' }}

是否显示: {{ show && hasPermission }}

整数: {{ parseInt(price) }}

浮点数: {{ parseFloat(price) }}

非数字: {{ isNaN(value) }}

编码: {{ encodeURIComponent(url) }}

时间戳: {{ Date.now() }}

JSON: {{ JSON.stringify(data) }}

最大值: {{ Math.max(a, b) }}

随机数: {{ Math.random() }}

` ``` **支持的运算符:** | 类别 | 运算符 | |------|--------| | 算术 | `+`, `-`, `*`, `/`, `%` | | 比较 | `==`, `===`, `!=`, `!==`, `>`, `<`, `>=`, `<=` | | 逻辑 | `&&`, `\|\|`, `!` | | 三元 | `condition ? value1 : value2` | **支持的全局对象和函数:** | 类别 | 函数/对象 | |------|-----------| | 数学 | `parseInt`, `parseFloat`, `isNaN`, `isFinite` | | 编码 | `encodeURIComponent`, `decodeURIComponent` | | 构造函数 | `Array`, `Object`, `String`, `Number`, `Boolean`, `Date` | | 工具 | `Math`, `JSON` | > **注意**:出于安全考虑,`console` 对象未在沙箱中暴露 #### 3. 条件判断 使用 `{% if %}`、`{% elif %}`、`{% else %}`、`{% endif %}` 进行条件渲染: ```javascript template: `
{% if isLoggedIn %}

欢迎回来,{{ username }}!

{% elif hasInvitation %}

请先完成注册

{% else %}

请登录

{% endif %} {% if score >= 90 %} 优秀 {% elif score >= 60 %} 及格 {% else %} 不及格 {% endif %}
` ``` #### 4. 循环遍历 使用 `{% for %}`、`{% endfor %}` 遍历数组: ```javascript template: `
    {% for item in items %}
  • {{ item.name }} - {{ item.price }}
  • {% endfor %}
` ``` **循环变量:** 在循环体内可访问以下特殊变量: | 变量 | 说明 | |------|------| | `__index` | 当前索引(从 0 开始) | | `__first` | 是否为第一个元素 | | `__last` | 是否为最后一个元素 | ```javascript template: `
    {% for item in list %}
  • {{ __index + 1 }}. {{ item }}
  • {% endfor %}
` ``` **嵌套循环:** 支持循环嵌套,最大深度限制为 5 层: ```javascript template: `
{% for category in categories %}

{{ category.name }}

    {% for product in category.products %}
  • {{ product.name }}
  • {% endfor %}
{% endfor %}
` ``` #### 5. 过滤器管道 使用 `|` 语法应用过滤器,支持链式调用和参数传递: ```javascript template: `

日期: {{ createTime | formatDate 'YYYY-MM-DD HH:mm' }}

价格: {{ price | number '¥' 2 }}

名称: {{ name | upper }}

简介: {{ description | truncate 50 '...' }}

昵称: {{ nickname | default '匿名用户' }}

标签: {{ tags | join ', ' }}

` ``` **内置过滤器:** | 过滤器 | 说明 | 参数 | |--------|------|------| | `upper` | 转大写 | - | | `lower` | 转小写 | - | | `trim` | 去除首尾空格 | - | | `truncate` | 截断字符串 | `length`, `suffix` | | `default` | 默认值 | `defaultValue` | | `date` / `formatDate` | 日期格式化 | `format` | | `number` | 数字格式化 | `prefix`, `decimals` | | `join` | 数组转字符串 | `separator` | | `split` | 字符串转数组 | `separator` | | `json` | JSON 序列化 | - | **过滤器链式调用:** ```javascript template: `

{{ title | trim | upper }}

{{ content | truncate 100 '...' | lower }}

` ``` #### 6. 事件绑定 使用 `@event` 语法绑定事件处理器: ```javascript template: ` 链接 ` ``` **支持的事件修饰符:** | 修饰符 | 说明 | |--------|------| | `.prevent` | 阻止默认行为 | | `.stop` | 阻止事件冒泡 | | `.once` | 只触发一次 | | `.self` | 只在事件目标自身触发 | **事件参数:** 支持传递参数和特殊变量 `$event`: ```javascript template: ` ` ``` #### 7. 保留区域 使用 `{% preserve %}`、`{% endpreserve %}` 标记需要保留内部状态的区域: ```javascript template: `
{% preserve %} {% endpreserve %}
` ``` **应用场景:** - 表单输入框内容保留 - 富文本编辑器状态保留 - 第三方组件实例保留 **自动保留:** `
` 标签组合会自动被添加 preserve 保护,避免模板引擎解析代码示例中的模板语法:

```javascript
template: `
    

        // 这段代码中的 {{ }} 不会被解析
        const greeting = "{{ name }}";
    
` ``` #### 8. 模板注释 使用 `{# comment #}` 添加模板注释,注释不会输出到 HTML: ```javascript template: `
{# 这是一条注释 #}

{{ message }}

` ``` #### 9. 原始输出 使用 `{!! expression !!}` 进行原始输出(不进行 HTML 转义): ```javascript template: `
{!! htmlContent !!}
` ``` > **注意**:原始输出存在 XSS 风险,请确保内容是安全可信的。 ### 页面过渡动画 JPage 支持页面切换时的过渡动画效果,提供流畅的用户体验。 #### 配置过渡动画 ```javascript JPage.options({ transition: { name: 'fade', // 动画类型:fade、slide、scale、flip duration: 300 // 动画时长(毫秒) } }); ``` #### 支持的动画类型 | 动画类型 | 效果描述 | 适用场景 | |---------|---------|---------| | `fade` | 淡入淡出,伴随轻微位移 | 通用页面切换 | | `slide` | 水平滑动效果 | 水平导航场景 | | `scale` | 缩放淡入淡出 | 模态框、弹窗 | | `flip` | 3D翻转效果 | 卡片翻转、特殊效果 | #### 动画执行流程 页面过渡动画按"离开动画→卸载→挂载→进入动画→清理"顺序执行。 #### 同一页面检测 当路由参数或查询参数变化但页面名称相同时,框架会智能判断是否需要执行完整切换,只有完全相同的路由才会跳过切换只更新数据。 #### 自定义动画 可以通过 CSS 自定义动画效果: ```css /* 自定义过渡动画 */ .jpage-transition-custom.jpage-transition-enter { opacity: 0; transform: rotate(180deg); } .jpage-transition-custom.jpage-transition-enter-active { opacity: 1; transform: rotate(0); transition: all 500ms ease-in-out; } ``` #### 禁用动画 设置 `duration: 0` 或不配置 `transition` 即可禁用动画。 ### 事件绑定语法 JPage 提供强大的事件绑定语法,支持参数传递和复杂表达式。 #### 基础事件绑定 ```html ``` #### 支持的事件类型 - **鼠标事件**: `@click`, `@dblclick`, `@mouseover`, `@mouseout`, `@mousedown`, `@mouseup` - **键盘事件**: `@keydown`, `@keyup`, `@keypress` - **表单事件**: `@input`, `@change`, `@submit`, `@focus`, `@blur` - **触摸事件**: `@touchstart`, `@touchend`, `@touchmove` - **其他事件**: `@load`, `@error`, `@scroll`, `@resize` #### 参数类型支持 | 参数类型 | 示例 | 说明 | |---------|------|------| | 字符串 | `'hello'`, `"world"` | 单引号或双引号字符串 | | 数字 | `123`, `3.14` | 整数或浮点数 | | 布尔值 | `true`, `false` | 布尔值 | | null | `null` | null 值 | | 对象 | `{key: 'value'}` | 简单对象字面量 | | 特殊变量 | `$event` | DOM 事件对象 | #### 事件处理器定义 在页面或组件中定义事件处理器: ```javascript JPage.route('/example', { name: 'example', template: `
`, methods: { handleClick(message, number) { console.log('点击事件:', message, number); }, handleInput(event) { console.log('输入值:', event.target.value); } } }); ``` #### 事件绑定特性 事件处理器自动绑定 this、支持参数自动解析、错误自动捕获、页面卸载时自动清理监听器。 *** | `default` | `{{ val \| default '默认值' }}` | 默认值回退 | | `truncate` | `{{ text \| truncate 50 '...' }}` | 文本截断 | #### 3. 条件渲染 ```javascript template: `
{% if isLoggedIn %}

欢迎回来,{{ userName }}

{% endif %} {% if isAdmin %}

管理员权限

{% endif %} {% if score >= 90 %}

优秀

{% elif score >= 60 %}

及格

{% else %}

不及格

{% endif %}
` ``` #### 4. 列表渲染 ```javascript template: `
    {% for item in items %}
  • {{ __index }}. {{ item.name }} {% if __first %}(第一个){% endif %} {% if __last %}(最后一个){% endif %}
  • {% endfor %}
`, data() { return { items: [ { name: '苹果' }, { name: '香蕉' }, { name: '橙子' } ] }; } ``` **循环变量:** | 变量 | 说明 | | --------- | ---------- | | `__index` | 当前索引(从0开始) | | `__first` | 是否为第一个元素 | | `__last` | 是否为最后一个元素 | #### 5. 注释 ```javascript template: `
{# 这是注释,不会渲染 #}

{{ message }}

` ``` #### 6. 事件绑定 JPage 支持多种事件绑定方式,推荐使用模板语法 `@click`: ```javascript // 方式1:模板语法 @click(推荐) template: ` `, methods: { handleClick() { // 处理点击事件 JPage.push('/about'); } } // 方式2:使用 onclick 属性 template: ` ` // 方式3:在 mounted 钩子中绑定(数据更新会重新渲染,事件会失效) mounted() { document.getElementById('myButton').addEventListener('click', () => { this.handleClick(); }); } // 方式4:事件委托(适用于动态内容) mounted() { document.addEventListener('click', (e) => { if (e.target.id === 'myButton') { this.handleClick(); } }); } ``` **支持的事件类型:** - `@click` - 点击事件 - `@change` - 输入变化事件 - `@input` - 输入事件 - `@submit` - 表单提交事件 - `@focus` - 聚焦事件 - `@blur` - 失焦事件 - 其他原生 DOM 事件均支持 #### 7. 自定义过滤器 JPage 提供了优雅的过滤器注册方式,支持全局注册和局部定义两种方式。 **方式一:全局注册(推荐)** 通过 `JPage.template.filter()` 或 `JPage.template.filters()` 方法全局注册过滤器,注册后在所有页面和组件中都可使用。 ```javascript // 注册单个过滤器 JPage.template.filter('currency', function(val, symbol = '¥') { const num = parseFloat(val); return isNaN(num) ? '' : symbol + num.toFixed(2); }); // 批量注册多个过滤器 JPage.template.filters({ currency: function(val, symbol = '¥') { const num = parseFloat(val); return isNaN(num) ? '' : symbol + num.toFixed(2); }, uppercase: function(val) { return String(val == null ? '' : val).toUpperCase(); }, truncate: function(val, length = 50, suffix = '...') { const str = String(val == null ? '' : val); return str.length <= length ? str : str.slice(0, length) + suffix; } }); ``` **方式二:页面/组件局部定义** 在页面或组件的定义中通过 `filters` 属性局部注册过滤器,仅在当前页面或组件内生效。 ```javascript // 页面中定义局部过滤器 JPage.route('/product', { name: 'product', template: `

价格: {{ price | currency '¥' }}

描述: {{ description | truncate 100 }}

`, data() { return { price: 199.99, description: '这是一个很长的产品描述...' }; }, filters: { currency: function(val, symbol = '¥') { const num = parseFloat(val); return isNaN(num) ? '' : symbol + num.toFixed(2); }, truncate: function(val, length = 50) { const str = String(val == null ? '' : val); return str.length <= length ? str : str.slice(0, length) + '...'; } } }); ``` **过滤器优先级** 过滤器查找顺序为:组件/页面局部过滤器 → 全局注册过滤器 → 内置过滤器。 **内置过滤器列表** |
| 过滤器 | 说明 | 示例 | | :----------- | ----- | ---------------- | ---------------------------- | | `formatDate` | 日期格式化 | \`{{ createTime | formatDate 'YYYY-MM-DD' }}\` | | `number` | 数字千分位 | \`{{ price | number }}\` | | `upper` | 转大写 | \`{{ name | upper }}\` | | `lower` | 转小写 | \`{{ name | lower }}\` | | `default` | 默认值回退 | \`{{ nickname | default '匿名用户' }}\` | | `truncate` | 截断文本 | \`{{ description | truncate 50 }}\` | **使用示例** ```javascript template: `

用户名: {{ username | default '访客' | upper }}

价格: ¥{{ price | number }}

创建时间: {{ createTime | formatDate 'YYYY-MM-DD HH:mm' }}

简介: {{ intro | truncate 100 '...' }}

` ``` ### 页面 CSS 隔离 ```css /* pages/home.css */ .home-page { padding: 20px; } .home-page h1 { color: #333; } /* 全局样式(不会被隔离) */ /* @global */ .global-style { color: red; } /* 全局样式块 */ /* @global-start */ .global-block { background: blue; } /* @global-end */ ``` ### 页面缓存 ```javascript // 配置页面缓存 JPage.routes({ '/user/:id': { component: 'user', cache: true // 启用缓存 } }); // 手动清除缓存 JPage.getPageManager().cache.clear(); // 清除特定页面缓存 const pageManager = JPage.getPageManager(); pageManager.clearCache('user'); ``` ### 页面管理器 API 通过 `JPage.getPageManager()` 获取页面管理器实例,可使用以下方法: | 方法 | 参数 | 返回值 | 说明 | | ---------------------- | ------ | ------------- | -------- | | `load(route)` | Object | Promise\ | 加载页面 | | `unload(page)` | Page | Promise\ | 卸载页面 | | `reload(data)` | Object | Promise\ | 重新加载当前页面 | | `clearCache(pageName)` | String | PageManager | 清除页面缓存 | | `getCurrentPage()` | - | Page | 获取当前页面 | | `getPreviousPage()` | - | Page | 获取上一个页面 | | `hasPage(name)` | String | Boolean | 检查页面是否存在 | | `getStats()` | - | Object | 获取统计信息 | | `clear()` | - | void | 清除所有页面 | **示例:** ```javascript // 重新加载当前页面 await JPage.pages.reload({ params: { id: 123 } }); // 或使用页面管理器 const pageManager = JPage.getPageManager(); await pageManager.reload({ params: { id: 123 }, query: { tab: 'info' } }); // 获取当前页面信息 const currentPage = pageManager.getCurrentPage(); console.log(currentPage.name, currentPage.params); // 获取统计信息 const stats = pageManager.getStats(); console.log(stats); // { loadedPages: 3, currentPage: 'home', cacheSize: 2, cacheStats: {...} } ``` *** ## 🧩 组件系统 ### 注册组件 ```javascript // 注册全局组件 JPage.components.register('my-button', { name: 'my-button', template: ` `, data() { return { text: '按钮', type: 'default' }; }, mounted() { this.element.addEventListener('click', () => { console.log('按钮被点击'); }); } }); ``` ### 注册组件 **JPage.components.register(name, definition, options)** 注册一个组件定义,供后续渲染使用。 **参数:** | 参数 | 类型 | 默认值 | 说明 | | ------------ | ------ | ---- | ------ | | `name` | String | - | 组件名称 | | `definition` | Object | - | 组件定义对象 | | `options` | Object | `{}` | 组件选项 | **组件定义对象:** ```javascript { name: 'my-component', // 组件名称 template: '
...
', // 模板字符串 data() { // 数据函数 return { message: 'Hello' }; }, mounted() { // 挂载后钩子 console.log('组件已挂载'); }, destroyed() { // 销毁前钩子 console.log('组件将销毁'); } } ``` **示例:** ```javascript // 注册按钮组件 JPage.components.register('my-button', { name: 'my-button', template: '', data() { return { text: '按钮', type: 'primary' }; }, mounted() { console.log('按钮组件已挂载'); } }); // 注册后即可渲染 await JPage.components.element('my-button', { text: '点击我' }); ``` ### 渲染组件 **JPage.components.element(name, data, options)** 渲染组件到指定位置,返回组件实例。 **参数:** | 参数 | 类型 | 默认值 | 说明 | | ----------------- | -------------- | -------- | ---------- | | `name` | String | - | 组件名称 | | `data` | Object | `{}` | 组件数据/props | | `options` | Object | `{}` | 渲染选项 | | `options.target` | String/Element | `'#app'` | 目标容器 | | `options.css` | Boolean | `false` | 是否加载CSS | | `options.replace` | Boolean | `false` | 是否替换目标元素 | **示例:** ```javascript // 渲染组件到指定容器 const button = await JPage.components.element('my-button', { text: '点击我', type: 'primary' }, { target: '#button-container', css: true }); // 渲染组件并替换目标元素 const header = await JPage.components.element('page-header', { title: '页面标题' }, { target: '#header', replace: true }); ``` ### 重新加载组件 **JPage.components.reload(name, data)** 重新加载指定组件的所有实例。 **参数:** | 参数 | 类型 | 默认值 | 说明 | | ------ | ------ | ---- | ------ | | `name` | String | - | 组件名称 | | `data` | Object | `{}` | 新的组件数据 | **示例:** ```javascript // 重新加载组件,更新数据 await JPage.components.reload('my-button', { text: '新文本', type: 'success' }); ``` ### 移除组件 **JPage.components.remove(name, callback)** 移除指定组件的所有实例。 **参数:** | 参数 | 类型 | 默认值 | 说明 | | ---------- | -------- | --- | ------ | | `name` | String | - | 组件名称 | | `callback` | Function | - | 移除完成回调 | **示例:** ```javascript // 移除组件 JPage.components.remove('my-button', (instances) => { console.log(`已移除 ${instances.length} 个实例`); }); ``` ### 获取组件实例 **JPage.components.get(id)** 根据实例ID获取组件实例。 **参数:** | 参数 | 类型 | 默认值 | 说明 | | ---- | ------ | --- | ------ | | `id` | String | - | 组件实例ID | **返回值:** - `Component`: 组件实例 **示例:** ```javascript const component = JPage.components.get('component-id-123'); ``` ### 获取组件所有实例 **JPage.components.getInstances(name)** 获取指定名称的所有组件实例。 **参数:** | 参数 | 类型 | 默认值 | 说明 | | ------ | ------ | --- | ------------------ | | `name` | String | - | 组件名称(可选,不传则返回所有实例) | **返回值:** - `Array`: 组件实例数组 **示例:** ```javascript // 获取所有 my-button 实例 const buttons = JPage.components.getInstances('my-button'); // 获取所有组件实例 const allComponents = JPage.components.getInstances(); ``` ### 检查组件是否注册 **JPage.components.has(name)** 检查组件是否已注册。 **参数:** | 参数 | 类型 | 默认值 | 说明 | | ------ | ------ | --- | ---- | | `name` | String | - | 组件名称 | **返回值:** - `Boolean`: 是否存在 **示例:** ```javascript if (JPage.components.has('my-button')) { console.log('按钮组件已注册'); } ``` ### 检查实例是否存在 **JPage.components.hasInstance(id)** 检查组件实例是否存在。 **参数:** | 参数 | 类型 | 默认值 | 说明 | | ---- | ------ | --- | ------ | | `id` | String | - | 组件实例ID | **返回值:** - `Boolean`: 是否存在 **示例:** ```javascript if (JPage.components.hasInstance('component-id-123')) { console.log('实例存在'); } ``` ### 组件实例方法 ```javascript // 获取组件实例 const component = await JPage.components.element('my-button'); // 组件实例方法 component.$setData({ text: '新文本' }); // 更新数据 component.$setProps({ type: 'primary' }); // 更新props component.$mount('#container'); // 挂载到容器 component.$update({ text: '更新文本' }); // 更新数据并重新渲染 component.$destroy(); // 销毁组件 component.$addChild(childComponent); // 添加子组件 component.$removeChild(childComponent); // 移除子组件 // 组件自定义事件 component.$listen('custom:event', (data) => { ... }); // 监听自定义事件 component.$unlisten('custom:event', callback); // 取消监听 component.$listenOnce('custom:event', (data) => { ... }); // 监听一次 component.$emit('custom:event', { value: '数据' }); // 触发自定义事件(支持冒泡到父组件) ``` #### 组件完整生命周期 | 钩子 | 触发时机 | | -------------- | ------------------- | | `beforeCreate` | 组件实例创建前 | | `created` | 组件实例创建后 | | `beforeRender` | 组件渲染前 | | `rendered` | 组件渲染完成 | | `beforeMount` | 组件挂载前 | | `mounted` | 组件挂载到 DOM 后 | | `beforeUpdate` | 响应式数据变更前 | | `updated` | 响应式数据变更后 | | `beforeDestroy`| 组件销毁前 | | `destroyed` | 组件销毁后 | ### 组件通信 JPage 采用统一的事件通信机制,分为内部通信和外部通信: #### 内部通信(组件/页面内部) 使用 `$on`、`$emit`、`$off`、`$once` 进行组件/页面内部通信: ```javascript // 在组件中监听事件 this.$on('custom:event', (data) => { console.log('事件触发:', data); }); // 触发事件(支持事件冒泡到父组件) this.$emit('custom:event', { value: '数据' }); // 监听一次事件 this.$once('custom:event', (data) => { console.log('只触发一次:', data); }); // 取消监听 this.$off('custom:event'); ``` #### 外部通信(全局) 使用 `JPage.on`、`JPage.emit`、`JPage.off`、`JPage.once` 进行全局通信: ```javascript // 全局监听事件(返回包含 off 方法的对象) const listener = JPage.on('global:event', (data) => { console.log('全局事件:', data); }); // 监听一次 JPage.once('global:event', (data) => { console.log('全局事件(一次):', data); }); // 触发全局事件 JPage.emit('global:event', { message: '全局消息' }); // 取消监听 JPage.off('global:event', callback); // 或使用返回的 listener 取消 listener.off(); ``` **全局事件 API:** | 方法 | 参数 | 返回值 | 说明 | | ------------------------------- | ------------------------- | --------- | --------------- | | `JPage.on(event, callback, options)` | String, Function, Object | Object | 监听事件,返回 `{ event, id, off }` | | `JPage.once(event, callback, options)` | String, Function, Object | Object | 监听一次事件 | | `JPage.emit(event, ...args)` | String, ...any | Boolean | 触发事件 | | `JPage.off(event, callback)` | String, Function | Boolean | 取消监听 | **options 参数:** | 选项 | 类型 | 默认值 | 说明 | | ---------- | ------ | ---- | ----------- | | `priority` | Number | `0` | 监听器优先级(数值越大越先执行) | | `context` | Object | `null` | 监听器上下文(用于 offByContext 批量清理) | #### 数据传递方式 ```javascript // 1. 通过 props 传递数据 const childComponent = await JPage.element('child-component', { data: { value: '初始值' } }); // 2. 通过事件传递数据 // 父组件监听 this.$on('child:update', (child, data) => { console.log('子组件更新:', child.name, data); }); // 子组件触发 this.$emit('child:update', { value: '新值' }); ``` > **注意**:全局状态管理需通过插件方式实现,详见 [插件开发](#插件开发) 章节。 ### 组件管理器 API 通过 `JPage.getComponentManager()` 获取组件管理器实例,可使用以下方法: | 方法 | 参数 | 返回值 | 说明 | | ------------------------------------- | ---------------------- | ------------------------- | --------- | | `register(name, definition, options)` | String, Object, Object | ComponentManager | 注册组件 | | `load(name)` | String | Promise\ | 加载组件定义 | | `element(name, data, options)` | String, Object, Object | Promise\ | 渲染组件 | | `reload(name, data)` | String, Object | Promise\ | 重新加载组件 | | `remove(name, callback)` | String, Function | ComponentManager | 移除组件 | | `get(id)` | String | Component | 获取组件实例 | | `getInstances(name)` | String | Array\ | 获取组件实例列表 | | `has(name)` | String | Boolean | 检查组件是否已注册 | | `hasInstance(id)` | String | Boolean | 检查实例是否存在 | | `getComponentCount()` | - | Object | 获取组件统计信息 | | `clear()` | - | void | 清除所有组件 | | `destroyInstance(id)` | String | Boolean | 销毁指定组件实例 | *** ## ⚙️ 配置选项 ### 完整配置示例 ```javascript JPage.options({ // 基础配置 root: '#app', pages: './pages', components: './components', utils: './utils', debug: true, // 缓存配置 cache: { maxPages: 20, // 最大缓存页面数 maxAge: 3600000 // 缓存有效期(毫秒) }, // 过渡动画配置(预留) transition: { name: 'fade', // 过渡动画名称 duration: 300 // 过渡动画时长(毫秒) }, // 路由守卫配置 guard: { beforeEach: null, // 全局前置守卫函数 afterEach: null // 全局后置守卫函数 }, // 错误处理 errorHandler: null, // 全局错误处理函数 // 预注册插件 plugins: [] // 插件数组 }); ``` *** ## 📁 项目结构 ``` jpage/ ├── dist/ # 构建输出 │ ├── JPage.js # 未压缩版本 │ └── JPage.min.js # 压缩版本 ├── src/ │ ├── core/ # 核心模块 │ │ ├── JPage.js # 核心类 │ │ ├── Config.js # 配置管理 │ │ ├── DependencyInjector.js # 依赖注入 │ │ ├── InternalEventBus.js # 内部事件总线 │ │ └── InternalResourceManager.js # 内部资源管理 │ ├── modules/ # 功能模块 │ │ ├── router/ # 路由系统 │ │ │ ├── Router.js # 路由器 │ │ │ ├── Route.js # 路由对象 │ │ │ ├── Guard.js # 路由守卫 │ │ │ └── History.js # 历史记录 │ │ ├── page/ # 页面管理 │ │ │ ├── Page.js # 页面类 │ │ │ ├── PageManager.js # 页面管理器 │ │ │ ├── TemplateEngine.js # 模板引擎 │ │ │ └── Cache.js # 页面缓存 │ │ ├── component/ # 组件系统 │ │ │ ├── Component.js # 组件类 │ │ │ └── ComponentManager.js # 组件管理器 │ │ ├── reactive/ # 响应式系统 │ │ │ └── ReactiveProxy.js # 响应式代理 │ │ └── error/ # 错误处理 │ │ └── ErrorHandler.js # 错误处理器 │ ├── utils/ # 工具函数 │ │ ├── dom.js # DOM 操作 │ │ ├── path.js # 路径处理 │ │ ├── type.js # 类型判断 │ │ ├── logger.js # 日志工具 │ │ └── helpers.js # 辅助函数 │ └── index.js # 入口文件 ├── examples/ # 示例代码 │ ├── basic/ # 基础示例 │ ├── complete/ # 完整示例 │ └── plugins/ # 插件示例 │ ├── state.js # 状态管理插件示例 │ ├── event.js # 事件系统插件示例 │ └── message.js # 消息总线插件示例 └── README.md # 本文档 ``` *** ## ❓ 常见问题 ### 1. 如何调试 JPage? 启用调试模式: ```javascript JPage.options({ debug: true }); ``` 调试模式下会在控制台输出详细日志。 ### 2. 如何处理路由404? 配置404页面: ```javascript JPage.routes({ '/404': { component: 'not-found' } }); JPage.beforeEach((to, from) => { const router = JPage.getRouter(); if (!router.hasRoute(to.path)) { JPage.push('/404'); return false; } return true; }); ``` ### 3. 如何实现路由懒加载? 路由配置中指定组件路径: ```javascript JPage.routes({ '/about': { component: 'about', // 自动从 pages 目录加载 css: true } }); ``` ### 4. 如何在插件中访问 JPage 实例? 插件 install 方法的第一个参数就是 JPage 实例: ```javascript function myPlugin(JPage, options) { // 访问 JPage 实例 JPage.on('route:afterChange', () => { console.log('路由已改变'); }); } ``` ### 5. 如何开发自定义插件? 参考 [插件系统](#插件系统) 章节,了解插件开发最佳实践。 *** ## 📄 许可证 MIT License *** ## 🤝 贡献 欢迎提交 Issue 和 Pull Request! *** ## ⚠️ 安全注意事项 ### CSP(内容安全策略)兼容性 JPage 框架的模板引擎使用 `new Function` 实现动态代码生成,这在启用严格 CSP(Content Security Policy)的环境中可能会被阻止。 **受影响的功能:** - 模板编译和渲染 - 表达式求值 - 过滤器管道处理 **CSP 配置建议:** 如果您的应用需要严格的 CSP 策略,请添加以下指令: ```http Content-Security-Policy: script-src 'self' 'unsafe-eval' ``` **说明:** - `'unsafe-eval'` 允许 `new Function`、`eval()` 等动态代码执行 - 这是模板引擎正常工作的必要条件 - 在生产环境中,请确保只加载可信的模板内容 ### 模板安全性 框架已内置以下安全防护措施: | 防护措施 | 说明 | |---------|------| | **沙箱隔离** | 模板执行在隔离的沙箱环境中,阻断原型链访问 | | **XSS 防护** | 插值输出自动转义,防止跨站脚本攻击 | | **白名单机制** | 仅暴露安全的全局对象和函数 | | **表达式校验** | 对模板表达式进行词法分析和语法校验 | **最佳实践:** 1. 避免在模板中使用未经验证的用户输入 2. 对动态生成的模板内容进行严格校验 3. 生产环境中禁用调试模式 4. 定期更新框架版本以获取安全修复 *** **JPage** - 轻量级、插件化的 Hash Router SPA 框架 🚀