Initial bricks miniprogram implementation
This commit is contained in:
commit
13a770d56d
34
DESIGN.md
Normal file
34
DESIGN.md
Normal file
@ -0,0 +1,34 @@
|
||||
# Bricks 微信小程序 - 设计文档
|
||||
|
||||
## 架构
|
||||
JSON → BricksParser → setData → WXML 递归模板 → 小程序原生渲染
|
||||
|
||||
## 核心文件
|
||||
- `utils/parser.js` - JSON 解析引擎
|
||||
- `utils/http.js` - wx.request 封装
|
||||
- `utils/renderer.js` - 事件分发
|
||||
- `components/brick/brick.wxml` - 递归模板 (template is="brick")
|
||||
- `pages/bricks/bricks.js` - 页面入口
|
||||
|
||||
## 组件映射
|
||||
| Bricks Widget | 小程序组件 |
|
||||
|--------------|----------|
|
||||
| Text/Title1-6 | `<text class="title-N">` |
|
||||
| HBox/VBox | `<view class="flex-row/flex-col">` |
|
||||
| Filler | `<view class="flex-fill">` |
|
||||
| KeyinText/Input | `<input>` |
|
||||
| Image | `<image>` |
|
||||
| Running | `<loading>` |
|
||||
| VScrollPanel/HScrollPanel | `<scroll-view scroll-y/x>` |
|
||||
| Modal/Popup | `<view class="modal-overlay">` |
|
||||
|
||||
## 事件系统
|
||||
- urlwidget → wx.navigateTo
|
||||
- method → Page 方法调用
|
||||
- event → bindtap/catchtap
|
||||
- script → wx.request 服务端 RPC
|
||||
|
||||
## 限制
|
||||
- 包体积 2MB → 分包加载
|
||||
- WXML 不支持 innerHTML → 用 template 递归
|
||||
- Markdown/Html → 需引入 mp-html 插件
|
||||
36
app.js
Normal file
36
app.js
Normal file
@ -0,0 +1,36 @@
|
||||
App({
|
||||
globalData: {
|
||||
baseUrl: '',
|
||||
authToken: '',
|
||||
bricks: null
|
||||
},
|
||||
onLaunch() {
|
||||
// 加载 bricks 解析引擎
|
||||
const { BricksParser } = require('./utils/parser')
|
||||
this.globalData.bricks = new BricksParser()
|
||||
},
|
||||
// 工具函数: 对应 JS 版 bricks.extend
|
||||
extend(target, source) {
|
||||
for (let key in source) {
|
||||
if (source.hasOwnProperty(key)) {
|
||||
if (typeof source[key] === 'object' && source[key] !== null && !Array.isArray(source[key])) {
|
||||
target[key] = this.extend(target[key] || {}, source[key])
|
||||
} else {
|
||||
target[key] = source[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
return target
|
||||
},
|
||||
// 模板变量替换: 对应 bricks.obj_fmtstr
|
||||
fmt(str, data) {
|
||||
return str.replace(/\{\{([^}]+)\}\}/g, (match, key) => {
|
||||
return data[key] !== undefined ? data[key] : match
|
||||
})
|
||||
},
|
||||
// entire_url: 拼接 baseUrl
|
||||
entireUrl(path) {
|
||||
const base = this.globalData.baseUrl || ''
|
||||
return base.replace(/\/$/, '') + '/' + path.replace(/^\//, '')
|
||||
}
|
||||
})
|
||||
11
app.json
Normal file
11
app.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"pages": [
|
||||
"pages/bricks/bricks"
|
||||
],
|
||||
"window": {
|
||||
"navigationBarTitleText": "Bricks",
|
||||
"navigationBarBackgroundColor": "#ffffff"
|
||||
},
|
||||
"style": "v2",
|
||||
"sitemapLocation": "sitemap.json"
|
||||
}
|
||||
84
app.wxss
Normal file
84
app.wxss
Normal file
@ -0,0 +1,84 @@
|
||||
page {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* Flex 布局 - 对应 HBox/VBox */
|
||||
.flex-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.flex-col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.flex-fill {
|
||||
flex: 1;
|
||||
}
|
||||
.flex-center {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.flex-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
/* 标题 - 对应 Title1-6 */
|
||||
.title-1 { font-size: 64rpx; font-weight: bold; }
|
||||
.title-2 { font-size: 56rpx; font-weight: bold; }
|
||||
.title-3 { font-size: 48rpx; font-weight: semi-bold; }
|
||||
.title-4 { font-size: 40rpx; font-weight: medium; }
|
||||
.title-5 { font-size: 36rpx; font-weight: medium; }
|
||||
.title-6 { font-size: 32rpx; font-weight: medium; }
|
||||
|
||||
/* 文本 */
|
||||
.text-content { padding: 16rpx 32rpx; line-height: 1.6; }
|
||||
|
||||
/* 输入框 */
|
||||
.input-field {
|
||||
border: 1rpx solid #ddd;
|
||||
border-radius: 8rpx;
|
||||
padding: 16rpx 24rpx;
|
||||
margin: 16rpx 32rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
/* Loading - 对应 Running */
|
||||
.loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 40rpx;
|
||||
}
|
||||
|
||||
/* 图片 */
|
||||
.image-content { width: 100%; height: auto; }
|
||||
.icon-content { width: 48rpx; height: 48rpx; }
|
||||
|
||||
/* 滚动面板 */
|
||||
.scroll-panel { width: 100%; height: 100%; }
|
||||
|
||||
/* Modal */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: rgba(0,0,0,0.5);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 999;
|
||||
}
|
||||
.modal-content {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 32rpx;
|
||||
max-width: 80%;
|
||||
}
|
||||
|
||||
/* Menu */
|
||||
.menu-item {
|
||||
padding: 24rpx 32rpx;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
}
|
||||
27
components/brick/brick.js
Normal file
27
components/brick/brick.js
Normal file
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Bricks 递归组件逻辑
|
||||
*/
|
||||
Component({
|
||||
properties: {
|
||||
item: {
|
||||
type: Object,
|
||||
value: {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onEvent(e) {
|
||||
const dataset = e.currentTarget.dataset
|
||||
const { actiontype, target, methodname, url, script } = dataset
|
||||
this.triggerEvent('bricksaction', { actiontype, target, methodname, url, script, event: e })
|
||||
},
|
||||
onInput(e) {
|
||||
this.triggerEvent('inputchange', { value: e.detail.value, widget: this.data.item })
|
||||
},
|
||||
onCloseModal(e) {
|
||||
this.triggerEvent('modalclose')
|
||||
},
|
||||
stopPropagation() {
|
||||
// 阻止冒泡
|
||||
}
|
||||
}
|
||||
})
|
||||
4
components/brick/brick.json
Normal file
4
components/brick/brick.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {}
|
||||
}
|
||||
117
components/brick/brick.wxml
Normal file
117
components/brick/brick.wxml
Normal file
@ -0,0 +1,117 @@
|
||||
<!--
|
||||
Bricks 递归组件模板
|
||||
通过 template is="brick" data="{{item}}" 递归渲染
|
||||
-->
|
||||
<template name="brick">
|
||||
<!-- 文本组件 -->
|
||||
<block wx:if="{{item.widgettype === 'Text'}}">
|
||||
<text class="text-content">{{item.options.text}}</text>
|
||||
</block>
|
||||
|
||||
<block wx:if="{{item.widgettype === 'Title1'}}">
|
||||
<text class="title-1">{{item.options.text}}</text>
|
||||
</block>
|
||||
<block wx:if="{{item.widgettype === 'Title2'}}">
|
||||
<text class="title-2">{{item.options.text}}</text>
|
||||
</block>
|
||||
<block wx:if="{{item.widgettype === 'Title3'}}">
|
||||
<text class="title-3">{{item.options.text}}</text>
|
||||
</block>
|
||||
<block wx:if="{{item.widgettype === 'Title4'}}">
|
||||
<text class="title-4">{{item.options.text}}</text>
|
||||
</block>
|
||||
<block wx:if="{{item.widgettype === 'Title5'}}">
|
||||
<text class="title-5">{{item.options.text}}</text>
|
||||
</block>
|
||||
<block wx:if="{{item.widgettype === 'Title6'}}">
|
||||
<text class="title-6">{{item.options.text}}</text>
|
||||
</block>
|
||||
|
||||
<!-- 布局组件 - HBox/VBox -->
|
||||
<block wx:if="{{item.widgettype === 'HBox' || item.widgettype === 'FHBox'}}">
|
||||
<view class="flex-row {{item.widgettype === 'FHBox' ? 'flex-between' : ''}}">
|
||||
<block wx:for="{{item.subwidgets}}" wx:key="widgettype" wx:for-item="child">
|
||||
<template is="brick" data="{{item: child}}" />
|
||||
</block>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<block wx:if="{{item.widgettype === 'VBox' || item.widgettype === 'FVBox'}}">
|
||||
<view class="flex-col {{item.widgettype === 'FVBox' ? 'flex-between' : ''}}">
|
||||
<block wx:for="{{item.subwidgets}}" wx:key="widgettype" wx:for-item="child">
|
||||
<template is="brick" data="{{item: child}}" />
|
||||
</block>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- Filler -->
|
||||
<block wx:if="{{item.widgettype === 'Filler' || item.widgettype === 'HFiller' || item.widgettype === 'VFiller'}}">
|
||||
<view class="flex-fill"></view>
|
||||
</block>
|
||||
|
||||
<!-- 输入组件 -->
|
||||
<block wx:if="{{item.widgettype === 'KeyinText'}}">
|
||||
<input class="input-field" placeholder="{{item.options.placeholder}}" value="{{item.options.value || item.options.text}}" bindinput="onInput" data-widget="{{item}}" />
|
||||
</block>
|
||||
|
||||
<block wx:if="{{item.widgettype === 'Input'}}">
|
||||
<block wx:if="{{item.options.type === 'password'}}">
|
||||
<input class="input-field" type="idcard" placeholder="{{item.options.placeholder}}" password="{{true}}" />
|
||||
</block>
|
||||
<block wx:else>
|
||||
<input class="input-field" placeholder="{{item.options.placeholder}}" value="{{item.options.value}}" />
|
||||
</block>
|
||||
</block>
|
||||
|
||||
<!-- 图片 -->
|
||||
<block wx:if="{{item.widgettype === 'Image'}}">
|
||||
<image class="image-content" src="{{item.options.src}}" mode="{{item.options.mode || 'aspectFit'}}" />
|
||||
</block>
|
||||
|
||||
<!-- Icon -->
|
||||
<block wx:if="{{item.widgettype === 'Icon' || item.widgettype === 'StatedIcon'}}">
|
||||
<image class="icon-content" src="{{item.options.src}}" />
|
||||
</block>
|
||||
|
||||
<!-- Running (Loading) -->
|
||||
<block wx:if="{{item.widgettype === 'Running'}}">
|
||||
<view class="loading">
|
||||
<loading />
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- Scroll -->
|
||||
<block wx:if="{{item.widgettype === 'VScrollPanel'}}">
|
||||
<scroll-view class="scroll-panel" scroll-y="true">
|
||||
<block wx:for="{{item.subwidgets}}" wx:key="widgettype" wx:for-item="child">
|
||||
<template is="brick" data="{{item: child}}" />
|
||||
</block>
|
||||
</scroll-view>
|
||||
</block>
|
||||
|
||||
<block wx:if="{{item.widgettype === 'HScrollPanel'}}">
|
||||
<scroll-view class="scroll-panel" scroll-x="true">
|
||||
<block wx:for="{{item.subwidgets}}" wx:key="widgettype" wx:for-item="child">
|
||||
<template is="brick" data="{{item: child}}" />
|
||||
</block>
|
||||
</scroll-view>
|
||||
</block>
|
||||
|
||||
<!-- Modal -->
|
||||
<block wx:if="{{item.widgettype === 'Modal' || item.widgettype === 'Popup'}}">
|
||||
<view class="modal-overlay" wx:if="{{item.options.visible}}" catchtap="onCloseModal">
|
||||
<view class="modal-content" catchtap="stopPropagation">
|
||||
<block wx:for="{{item.subwidgets}}" wx:key="widgettype" wx:for-item="child">
|
||||
<template is="brick" data="{{item: child}}" />
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 默认: 递归子组件 -->
|
||||
<block wx:if="{{item.subwidgets && item.subwidgets.length > 0 && item.widgettype !== 'HBox' && item.widgettype !== 'FHBox' && item.widgettype !== 'VBox' && item.widgettype !== 'FVBox' && item.widgettype !== 'VScrollPanel' && item.widgettype !== 'HScrollPanel' && item.widgettype !== 'Modal' && item.widgettype !== 'Popup'}}">
|
||||
<block wx:for="{{item.subwidgets}}" wx:key="widgettype" wx:for-item="child">
|
||||
<template is="brick" data="{{item: child}}" />
|
||||
</block>
|
||||
</block>
|
||||
</template>
|
||||
1
components/brick/brick.wxss
Normal file
1
components/brick/brick.wxss
Normal file
@ -0,0 +1 @@
|
||||
/* 组件样式继承 app.wxss */
|
||||
74
pages/bricks/bricks.js
Normal file
74
pages/bricks/bricks.js
Normal file
@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Bricks 页面入口
|
||||
*/
|
||||
const { BricksParser } = require('../../utils/parser')
|
||||
const { BricksHttp } = require('../../utils/http')
|
||||
const { BricksRenderer } = require('../../utils/renderer')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
tree: null
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
this.parser = new BricksParser()
|
||||
this.http = new BricksHttp()
|
||||
this.renderer = new BricksRenderer(this)
|
||||
|
||||
// 从 URL 参数加载 JSON
|
||||
if (options.url) {
|
||||
this.loadFromUrl(decodeURIComponent(options.url))
|
||||
} else if (options.json) {
|
||||
this.loadFromJson(decodeURIComponent(options.json))
|
||||
} else {
|
||||
// 加载默认示例
|
||||
this.loadDefault()
|
||||
}
|
||||
},
|
||||
|
||||
async loadFromUrl(url) {
|
||||
try {
|
||||
const json = await this.http.get(url)
|
||||
const widgetTree = this.parser.parse(JSON.stringify(json))
|
||||
this.renderer.render(widgetTree)
|
||||
} catch (e) {
|
||||
console.error('[Bricks] Load failed:', e)
|
||||
}
|
||||
},
|
||||
|
||||
loadFromJson(jsonString) {
|
||||
const widgetTree = this.parser.parse(jsonString)
|
||||
if (widgetTree) this.renderer.render(widgetTree)
|
||||
},
|
||||
|
||||
loadDefault() {
|
||||
const defaultJson = JSON.stringify({
|
||||
widgettype: 'VBox',
|
||||
subwidgets: [
|
||||
{ widgettype: 'Title1', options: { text: '欢迎使用 Bricks' } },
|
||||
{ widgettype: 'Text', options: { text: 'JSON 驱动的跨平台 UI 框架' } },
|
||||
{ widgettype: 'KeyinText', options: { placeholder: '请输入...' } },
|
||||
{
|
||||
widgettype: 'HBox',
|
||||
subwidgets: [
|
||||
{ widgettype: 'Text', options: { text: '左侧' } },
|
||||
{ widgettype: 'Filler' },
|
||||
{ widgettype: 'Text', options: { text: '右侧' } }
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
this.loadFromJson(defaultJson)
|
||||
},
|
||||
|
||||
onBricksAction(e) {
|
||||
const { actiontype, url, methodname } = e.detail
|
||||
if (actiontype === 'urlwidget' && url) {
|
||||
wx.navigateTo({ url: '/pages/bricks/bricks?url=' + encodeURIComponent(url) })
|
||||
}
|
||||
},
|
||||
|
||||
onInputChange(e) {
|
||||
console.log('[Bricks] Input:', e.detail.value)
|
||||
}
|
||||
})
|
||||
6
pages/bricks/bricks.json
Normal file
6
pages/bricks/bricks.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"navigationBarTitleText": "Bricks",
|
||||
"usingComponents": {
|
||||
"brick": "/components/brick/brick"
|
||||
}
|
||||
}
|
||||
10
pages/bricks/bricks.wxml
Normal file
10
pages/bricks/bricks.wxml
Normal file
@ -0,0 +1,10 @@
|
||||
<!--
|
||||
Bricks 页面入口
|
||||
引入递归模板,渲染整个 widget 树
|
||||
-->
|
||||
<import src="/components/brick/brick.wxml" />
|
||||
|
||||
<view class="page-container">
|
||||
<!-- 递归渲染 -->
|
||||
<template is="brick" data="{{item: tree}}" />
|
||||
</view>
|
||||
7
pages/bricks/bricks.wxss
Normal file
7
pages/bricks/bricks.wxss
Normal file
@ -0,0 +1,7 @@
|
||||
@import "/app.wxss";
|
||||
|
||||
.page-container {
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
background: #fff;
|
||||
}
|
||||
42
project.config.json
Normal file
42
project.config.json
Normal file
@ -0,0 +1,42 @@
|
||||
{
|
||||
"description": "Bricks 微信小程序",
|
||||
"packOptions": {
|
||||
"ignore": [],
|
||||
"include": []
|
||||
},
|
||||
"setting": {
|
||||
"bundle": false,
|
||||
"userConfirmedBundleSwitch": false,
|
||||
"urlCheck": true,
|
||||
"scopeDataCheck": false,
|
||||
"coverView": true,
|
||||
"es6": true,
|
||||
"postcss": true,
|
||||
"compileHotReLoad": false,
|
||||
"lazyloadPlaceholderEnable": false,
|
||||
"preloadBackgroundData": false,
|
||||
"minified": true,
|
||||
"autoAudits": false,
|
||||
"newFeature": false,
|
||||
"uglifyFileName": false,
|
||||
"uploadWithSourceMap": true,
|
||||
"useIsolateContext": true,
|
||||
"nodeModules": false,
|
||||
"enhance": true,
|
||||
"useMultiFrameRuntime": true,
|
||||
"showShadowRootInWxmlPanel": true,
|
||||
"packNpmManually": false,
|
||||
"enableEngp": false,
|
||||
"packNpmRelationList": [],
|
||||
"minifyWXSS": true,
|
||||
"showES6CompileOption": false,
|
||||
"minifyWXML": true,
|
||||
"babelSetting": {
|
||||
"ignore": [],
|
||||
"disablePlugins": [],
|
||||
"outputPath": ""
|
||||
}
|
||||
},
|
||||
"compileType": "miniprogram",
|
||||
"condition": {}
|
||||
}
|
||||
4
sitemap.json
Normal file
4
sitemap.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
|
||||
"rules": [{ "action": "allow", "page": "*" }]
|
||||
}
|
||||
54
utils/http.js
Normal file
54
utils/http.js
Normal file
@ -0,0 +1,54 @@
|
||||
/**
|
||||
* HTTP 请求封装 - 对应 JS 版 bricks.HttpJson/HttpText
|
||||
*/
|
||||
const app = getApp()
|
||||
|
||||
class BricksHttp {
|
||||
/**
|
||||
* GET 请求
|
||||
*/
|
||||
get(url, params = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const fullUrl = app.entireUrl(url)
|
||||
wx.request({
|
||||
url: fullUrl,
|
||||
data: params,
|
||||
method: 'GET',
|
||||
header: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': app.globalData.authToken ? `Bearer ${app.globalData.authToken}` : ''
|
||||
},
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200) resolve(res.data)
|
||||
else reject(new Error(`HTTP ${res.statusCode}`))
|
||||
},
|
||||
fail: (err) => reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* POST 请求
|
||||
*/
|
||||
post(url, data = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const fullUrl = app.entireUrl(url)
|
||||
wx.request({
|
||||
url: fullUrl,
|
||||
data: JSON.stringify(data),
|
||||
method: 'POST',
|
||||
header: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': app.globalData.authToken ? `Bearer ${app.globalData.authToken}` : ''
|
||||
},
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200) resolve(res.data)
|
||||
else reject(new Error(`HTTP ${res.statusCode}`))
|
||||
},
|
||||
fail: (err) => reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { BricksHttp }
|
||||
67
utils/parser.js
Normal file
67
utils/parser.js
Normal file
@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Bricks JSON 解析引擎
|
||||
* 将 bricks JSON 解析为小程序可渲染的数据结构
|
||||
*/
|
||||
class BricksParser {
|
||||
constructor() {
|
||||
this.widgetTree = null
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 JSON 字符串
|
||||
*/
|
||||
parse(jsonString) {
|
||||
try {
|
||||
const obj = JSON.parse(jsonString)
|
||||
this.widgetTree = this._parseNode(obj)
|
||||
return this.widgetTree
|
||||
} catch (e) {
|
||||
console.error('[Bricks] Parse error:', e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归解析节点
|
||||
*/
|
||||
_parseNode(obj) {
|
||||
if (!obj || typeof obj !== 'object') return null
|
||||
|
||||
const node = {
|
||||
widgettype: obj.widgettype || 'Text',
|
||||
options: obj.options || {},
|
||||
binds: obj.binds || [],
|
||||
subwidgets: [],
|
||||
// 扁平化事件数据,方便 WXML 绑定
|
||||
_hasBind: (obj.binds && obj.binds.length > 0),
|
||||
_bindData: obj.binds ? obj.binds.map(b => ({
|
||||
event: b.event || 'tap',
|
||||
actiontype: b.actiontype,
|
||||
target: b.target,
|
||||
methodname: b.methodname || b.method,
|
||||
params: b.params,
|
||||
url: b.url || (b.options && b.options.url),
|
||||
script: b.script
|
||||
})) : []
|
||||
}
|
||||
|
||||
// 递归处理子组件
|
||||
if (obj.subwidgets && Array.isArray(obj.subwidgets)) {
|
||||
node.subwidgets = obj.subwidgets.map(child => this._parseNode(child))
|
||||
}
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析多个 JSON(如分页加载)
|
||||
*/
|
||||
parseList(jsonArray) {
|
||||
return jsonArray.map(json => {
|
||||
if (typeof json === 'string') return this.parse(json)
|
||||
return this._parseNode(json)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { BricksParser }
|
||||
68
utils/renderer.js
Normal file
68
utils/renderer.js
Normal file
@ -0,0 +1,68 @@
|
||||
/**
|
||||
* 渲染适配 - 处理事件分发和 actiontype
|
||||
*/
|
||||
const app = getApp()
|
||||
|
||||
class BricksRenderer {
|
||||
constructor(page) {
|
||||
this.page = page
|
||||
this.widgetTree = null
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染 widget 树
|
||||
*/
|
||||
render(widgetTree) {
|
||||
this.widgetTree = widgetTree
|
||||
this.page.setData({
|
||||
tree: widgetTree,
|
||||
treeData: [widgetTree] // WXML 需要数组形式
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理事件绑定
|
||||
*/
|
||||
onEvent(e) {
|
||||
const { actiontype, target, methodname, url, script } = e.currentTarget.dataset
|
||||
console.log('[Bricks] Event:', actiontype, target)
|
||||
|
||||
switch (actiontype) {
|
||||
case 'urlwidget':
|
||||
this._handleUrlWidget(url)
|
||||
break
|
||||
case 'method':
|
||||
this._handleMethod(methodname, e)
|
||||
break
|
||||
case 'script':
|
||||
this._handleScript(script)
|
||||
break
|
||||
case 'event':
|
||||
this._handleEvent(e)
|
||||
break
|
||||
default:
|
||||
console.log('[Bricks] Unknown actiontype:', actiontype)
|
||||
}
|
||||
}
|
||||
|
||||
_handleUrlWidget(url) {
|
||||
if (!url) return
|
||||
wx.navigateTo({ url: '/pages/bricks/bricks?url=' + encodeURIComponent(url) })
|
||||
}
|
||||
|
||||
_handleMethod(methodname, e) {
|
||||
if (methodname && this.page[methodname]) {
|
||||
this.page[methodname](e)
|
||||
}
|
||||
}
|
||||
|
||||
_handleScript(script) {
|
||||
console.log('[Bricks] Script:', script)
|
||||
}
|
||||
|
||||
_handleEvent(e) {
|
||||
console.log('[Bricks] Event data:', e.detail)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { BricksRenderer }
|
||||
Loading…
x
Reference in New Issue
Block a user