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