uniapp生成的小程序支持动态下载子组件,但是app和h5支持
在 uniapp app 端实现远程下载子组件到本地运行需要以下几个步骤:
下载和文件管理:// utils/file-manager.js export class FileManager { constructor() { // app本地存储根路径 this.rootPath = plus.io.convertLocalFileSystemURL('_downloads/components/') } // 下载组件 async downloadComponent(url, componentName) { return new Promise((resolve, reject) => { const downloadTask = uni.downloadFile({ url, success: (res) => { if (res.statusCode === 200) { // 保存文件到本地 this.saveFile(res.tempFilePath, componentName) .then(savedPath => resolve(savedPath)) .catch(err => reject(err)) } else { reject(new Error('Download failed')) } }, fail: reject }) }) } // 保存文件 async saveFile(tempPath, fileName) { return new Promise((resolve, reject) => { const targetPath = `${this.rootPath}${fileName}` plus.io.resolveLocalFileSystemURL(tempPath, (entry) => { entry.moveTo(targetPath, () => resolve(targetPath), (err) => reject(err) ) }, reject) }) } // 读取本地文件 async readFile(filePath) { return new Promise((resolve, reject) => { plus.io.resolveLocalFileSystemURL(filePath, (entry) => { entry.file((file) => { const reader = new plus.io.FileReader() reader.onloadend = (e) => { resolve(e.target.result) } reader.onerror = reject reader.readAsText(file) }, reject) }, reject) }) } }组件加载器:
// components/remote-loader.vue <template> <view class="remote-component"> <component v-if="dynamicComponent" :is="dynamicComponent" v-bind="componentProps" /> <view v-else-if="loading">加载中...</view> <view v-else-if="error">{{ error }}</view> </view> </template> <script> import { FileManager } from '../utils/file-manager' import { parseComponent } from '../utils/component-parser' export default { name: 'RemoteLoader', props: { componentUrl: { type: String, required: true }, componentProps: { type: Object, default: () => ({}) } }, data() { return { dynamicComponent: null, loading: false, error: null, fileManager: new FileManager() } }, created() { this.loadRemoteComponent() }, methods: { async loadRemoteComponent() { this.loading = true this.error = null try { // 获取组件名称 const componentName = this.getComponentName(this.componentUrl) // 下载组件文件 const localPath = await this.fileManager.downloadComponent( this.componentUrl, componentName ) // 读取组件内容 const componentContent = await this.fileManager.readFile(localPath) // 解析组件 const component = parseComponent(componentContent) // 注册组件 this.dynamicComponent = this.registerComponent(component) } catch (err) { this.error = '组件加载失败:' + err.message console.error('组件加载错误:', err) } finally { this.loading = false } }, getComponentName(url) { return url.split('/').pop().split('.')[0] }, registerComponent(component) { // 创建新的组件构造器 return Vue.extend(component) } } } </script>组件解析工具:
// utils/component-parser.js export function parseComponent(content) { try { // 简单的组件解析示例 // 实际使用需要更复杂的解析逻辑 const component = eval(`(${content})`) return { ...component, // 添加一些默认配置 name: component.name || 'RemoteComponent' } } catch (err) { throw new Error('组件解析失败: ' + err.message) } }使用示例:
// pages/index.vue <template> <view class="container"> <remote-loader v-for="(component, index) in remoteComponents" :key="index" :component-url="component.url" :component-props="component.props" @error="handleError" /> </view> </template> <script> import RemoteLoader from '../components/remote-loader.vue' export default { components: { RemoteLoader }, data() { return { remoteComponents: [ { url: 'https://example.com/components/banner.js', props: { title: '测试banner' } } // 更多远程组件... ] } }, methods: { handleError(error) { uni.showToast({ title: error, icon: 'none' }) } } } </script>缓存管理:
// utils/cache-manager.js export class CacheManager { constructor() { this.cache = new Map() } async getComponent(url) { const cached = this.cache.get(url) if (cached && !this.isExpired(cached)) { return cached.component } return null } setComponent(url, component, ttl = 3600000) { // 1小时过期 this.cache.set(url, { component, timestamp: Date.now(), ttl }) } isExpired(cached) { return Date.now() - cached.timestamp > cached.ttl } clearCache() { this.cache.clear() } }安全检查:
// utils/security.js export class SecurityChecker { static validateComponent(component) { // 检查组件结构 if (!component || typeof component !== 'object') { throw new Error('无效的组件格式') } // 检查必要属性 if (!component.template) { throw new Error('组件缺少template') } // 检查是否包含危险代码 const dangerousPatterns = [ 'eval(', 'Function(', 'setTimeout(', 'setInterval(' ] const componentString = JSON.stringify(component) for (const pattern of dangerousPatterns) { if (componentString.includes(pattern)) { throw new Error('组件包含不安全代码') } } return true } }
注意事项:
安全性考虑:
只从可信源下载组件实现完善的组件验证机制避免执行危险代码性能优化:
实现组件缓存控制并发下载数量压缩组件代码错误处理:
完善的错误捕获和提示降级方案重试机制版本控制:
组件版本管理更新机制缓存清理兼容性:
不同平台兼容性处理降级方案错误提示网友回复