Merge branch 'main' of git.opencomputing.cn:yumoqing/bricks
This commit is contained in:
commit
f5a58a77cb
20
README.md
20
README.md
@ -2,21 +2,11 @@
|
||||
A new web application development framework to make web application development more easier like play bricks
|
||||
|
||||
## Documentation
|
||||
We have English and Chinese versions documents,
|
||||
Documents in English, please read from [docs](docs/README.md), 中文资料看[这里](docs/cn/README.md)
|
||||
|
||||
## Development base on components
|
||||
We have documents in 3 language
|
||||
|
||||
We built web development components which use a options objects as API.
|
||||
third party can develops their component suply the standard of components API
|
||||
* [中文](docs/zh/brief.md)
|
||||
* [日本語](docs/ja/brief.md)
|
||||
* [English](docs/en/brief.md)
|
||||
|
||||
Most front-end development tools only help user to build the front-end UI, and use script to build the app's logic.
|
||||
|
||||
Bricks not only build the UI but also the front-end logic.
|
||||
|
||||
Bricks provide a new mathiciam to description the event fire, and event handler, Bricks use json data to descripts event and it handler, when event fire, according the json data, Bricks dynamicly constructs the event handler.
|
||||
|
||||
|
||||
## Dependanance
|
||||
|
||||
[Marked](https://github.com/yumoqing/marked) is a tool for markdown text parser, extends from [MarkedJs marked](https://github.com/markedjs/marked), we extends audio and video link, user can directly use `!v[text](url)` pattern to show a video player, and `!a[text](url)` pattern to show a audio player
|
||||
NOTE: all the documents was generated by Qwen3-max
|
||||
|
||||
BIN
bricks/.DS_Store
vendored
BIN
bricks/.DS_Store
vendored
Binary file not shown.
@ -1,4 +1,13 @@
|
||||
var bricks = window.bricks || {};
|
||||
bricks.get_current_language=function(){
|
||||
var lang = navigator.language.substring(0, 2);
|
||||
if (bricks.app){
|
||||
if (bricks.app.lang) return bricks.app.lang;
|
||||
bricks.app.lang = lang;
|
||||
return lang;
|
||||
}
|
||||
return;
|
||||
}
|
||||
bricks.app = null;
|
||||
/*
|
||||
all type of bind action's desc has the following attributes:
|
||||
@ -231,19 +240,21 @@ bricks.universal_handler = async function(from_widget, widget, desc, event){
|
||||
bricks.default_popup = function(opts){
|
||||
var popts = bricks.get_popup_default_options();
|
||||
bricks.extend(popts, opts);
|
||||
popts.origin_event = event;
|
||||
return new bricks.Popup(popts);
|
||||
}
|
||||
bricks.default_popupwindow = function(opts){
|
||||
bricks.default_popupwindow = function(opts,event){
|
||||
var popts = bricks.get_popupwindow_default_options();
|
||||
bricks.extend(popts, opts);
|
||||
popts.origin_event = event;
|
||||
return new bricks.PopupWindow(popts);
|
||||
}
|
||||
bricks.buildEventHandler = async function(w, desc, event){
|
||||
var target;
|
||||
if (desc.target == 'Popup'){
|
||||
target = bricks.default_popup(desc.popup_options || {});
|
||||
target = bricks.default_popup(desc.popup_options || {}, event);
|
||||
} else if (desc.target == 'PopupWindow') {
|
||||
target = bricks.default_popupwindow(desc.popup_options || {});
|
||||
target = bricks.default_popupwindow(desc.popup_options || {}, event);
|
||||
} else if ( desc.target instanceof bricks.JsWidget ) {
|
||||
target = desc.target;
|
||||
} else {
|
||||
@ -325,22 +336,11 @@ var _buildWidget = async function(from_widget, target, mode, options, desc){
|
||||
bricks.debug('options=', options, 'widgetBuild() failed');
|
||||
return;
|
||||
}
|
||||
|
||||
if (w.parent) {
|
||||
if (target instanceof bricks.Popup || target instanceof bricks.PopupWindow){
|
||||
target.destroy();
|
||||
}
|
||||
if (w instanceof bricks.Popup) {
|
||||
return;
|
||||
}
|
||||
if (target instanceof bricks.Popup || target instanceof bricks.PopupWindow) {
|
||||
if (! target.parent){
|
||||
bricks.app.add_widget(target);
|
||||
}
|
||||
if (desc.popup_options){
|
||||
if (desc.popup_options.eventpos){
|
||||
target.bind('opened', bricks.relocate_by_eventpos.bind(null, desc.event, target));
|
||||
}
|
||||
}
|
||||
if (w instanceof bricks.NewWindow) {
|
||||
return;
|
||||
}
|
||||
if (mode == 'replace'){
|
||||
target.clear_widgets();
|
||||
@ -569,14 +569,15 @@ bricks.App = class extends bricks.Layout {
|
||||
this.lang = this.opts.language;
|
||||
}
|
||||
else {
|
||||
this.lang = navigator.language;
|
||||
this.lang = navigator.language.substring(0,2);
|
||||
}
|
||||
this.lang_x = this.observable('lang', this.lang);
|
||||
this.zindex = 10000;
|
||||
this.textList = [];
|
||||
this.i18n = new bricks.I18n(objget(opts, 'i18n', {}));
|
||||
this.session_id = null;
|
||||
this.tooltip = new bricks.Tooltip({otext:'test',i18n:true, wrap:true});
|
||||
this.tooltip = new bricks.Tooltip({otext:'',i18n:true, wrap:true});
|
||||
this.tooltip.hide();
|
||||
this.add_widget(this.tooltip);
|
||||
this._Width = this.dom_element.offsetWidth;
|
||||
this._Height = this.dom_element.offsetHeight;
|
||||
@ -598,7 +599,10 @@ bricks.App = class extends bricks.Layout {
|
||||
console.log('event=', event);
|
||||
event.preventDefault();
|
||||
event.stopPropagation()
|
||||
this.wins_panel = new bricks.WindowsPanel({})
|
||||
var opts = bricks.get_popup_default_options();
|
||||
opts.auto_open = false;
|
||||
this.wins_panel = new bricks.WindowsPanel(opts);
|
||||
this.wins_panel.open();
|
||||
}
|
||||
get_color(){
|
||||
return getComputedStyle(this.dom_element).color;
|
||||
@ -716,7 +720,7 @@ bricks.App = class extends bricks.Layout {
|
||||
return w;
|
||||
}
|
||||
async run(){
|
||||
await (this.change_language(this));
|
||||
await (this.change_language(this.lang));
|
||||
var w = await this.build();
|
||||
this.root = w;
|
||||
if (!w){
|
||||
|
||||
@ -11,7 +11,7 @@ SOURCES=" page_data_loader.js factory.js uitypesdef.js utils.js uitype.js \
|
||||
llm.js websocket.js datarow.js tabular.js continueaudio.js \
|
||||
line.js pie.js bar.js gobang.js period.js iconbarpage.js \
|
||||
keypress.js asr.js webspeech.js countdown.js progressbar.js \
|
||||
qaframe.js svg.js videoplayer.js "
|
||||
qaframe.js svg.js videoplayer.js sctter.js radar.js kline.js heatmap.js map.js"
|
||||
echo ${SOURCES}
|
||||
cat ${SOURCES} > ../dist/bricks.js
|
||||
# uglifyjs --compress --mangle -- ../dist/bricks.js > ../dist/bricks.min.js
|
||||
|
||||
@ -605,3 +605,9 @@ hr {
|
||||
color: #00aaff;
|
||||
background: #f0faff;
|
||||
}
|
||||
|
||||
.mini-window {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
|
||||
@ -22,6 +22,10 @@ bricks.EchartsExt = class extends bricks.VBox {
|
||||
if(!this.idField) this.idField = 'id';
|
||||
if(!this.nameField) this.nameField = 'name';
|
||||
if(!this.valueFields) this.valueFields = ['value'];
|
||||
// === 新增:处理 refresh_period ===
|
||||
this.refresh_period = opts.refresh_period; // 单位:秒
|
||||
this._refresh_timer = null;
|
||||
|
||||
this.build_title_widget();
|
||||
this.build_description_widget();
|
||||
this.holder = new bricks.Filler({});
|
||||
@ -33,7 +37,49 @@ bricks.EchartsExt = class extends bricks.VBox {
|
||||
schedule_once(this.render_urldata.bind(this), 0.1);
|
||||
}
|
||||
this.bind('element_resize', this.chart.resize.bind(this.chart));
|
||||
// === 启动定时刷新(如果配置了 refresh_period)===
|
||||
if (this.refresh_period && this.data_url) {
|
||||
this.start_auto_refresh();
|
||||
}
|
||||
}
|
||||
// === 启动自动刷新任务 ===
|
||||
start_auto_refresh() {
|
||||
if (this._refresh_timer) return; // 防止重复启动
|
||||
|
||||
const doRefresh = () => {
|
||||
this.render_urldata().then(() => {
|
||||
// 继续下一轮
|
||||
if (this._refresh_timer) { // 检查是否已被取消
|
||||
this._refresh_timer = setTimeout(doRefresh, this.refresh_period * 1000);
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error('Auto-refresh failed:', err);
|
||||
this._refresh_timer = setTimeout(doRefresh, this.refresh_period * 1000); // 失败也重试
|
||||
});
|
||||
};
|
||||
|
||||
// 初始延迟后开始第一轮,之后循环
|
||||
this._refresh_timer = setTimeout(doRefresh, this.refresh_period * 1000);
|
||||
}
|
||||
|
||||
// === 停止自动刷新 ===
|
||||
stop_auto_refresh() {
|
||||
if (this._refresh_timer) {
|
||||
clearTimeout(this._refresh_timer);
|
||||
this._refresh_timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
// === 覆盖 destroy 方法,清理定时器 ===
|
||||
destroy() {
|
||||
this.stop_auto_refresh(); // 清理资源
|
||||
if (this.chart) {
|
||||
this.chart.dispose();
|
||||
this.chart = null;
|
||||
}
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
pivotify(data){
|
||||
var series = [];
|
||||
data.forEach(x => {
|
||||
@ -83,7 +129,6 @@ bricks.EchartsExt = class extends bricks.VBox {
|
||||
this.valueFields = this.get_series(data);
|
||||
data = this.pivotify(data);
|
||||
}
|
||||
this.chart = echarts.init(this.holder.dom_element);
|
||||
var opts = this.setup_options(data);
|
||||
opts.grid = {
|
||||
left: '3%',
|
||||
|
||||
@ -22,6 +22,11 @@ bricks.FieldGroup = class {
|
||||
this.build_fields(form, dc, fields[i].fields);
|
||||
parent.add_widget(dc);
|
||||
dc = new bricks.DynamicColumn({mobile_cols:2});
|
||||
dc.set_id(fields[i].name+'_box');
|
||||
if (fields[i].nonuse){
|
||||
dc.disabled(true);
|
||||
dc.hide();
|
||||
}
|
||||
} else {
|
||||
var box;
|
||||
if (! form.opts.input_layout || form.opts.input_layout == 'VBox'){
|
||||
@ -42,6 +47,11 @@ bricks.FieldGroup = class {
|
||||
height:'auto',
|
||||
i18n:true});
|
||||
box.add_widget(txt);
|
||||
box.set_id(fields[i].name + '_box')
|
||||
if (fields[i].nonuse){
|
||||
box.disabled(true);
|
||||
box.hide();
|
||||
}
|
||||
var w = Input.factory(fields[i]);
|
||||
if (w){
|
||||
box.add_widget(w);
|
||||
@ -72,6 +82,10 @@ bricks.FormBody = class extends bricks.VScrollPanel {
|
||||
},
|
||||
...
|
||||
]
|
||||
exclusionfields:[
|
||||
[a,b,c], # a,b,c互斥,a enabled,b,c必须disabled
|
||||
[x,y] # x,y互斥
|
||||
]
|
||||
}
|
||||
*/
|
||||
constructor(form, opts){
|
||||
@ -234,7 +248,33 @@ bricks.FormBase = class extends bricks.Layout {
|
||||
}
|
||||
return this.get_formdata();
|
||||
}
|
||||
|
||||
toggle_disable(field_name, flg){
|
||||
var w = bricks.getWidgetById(field_name + '_box', this);
|
||||
if (! w) return;
|
||||
w.disabled(flg);
|
||||
if (flg) w.hide();
|
||||
else w.show();
|
||||
if (flg) return;
|
||||
this.exclusionfields.forEach(arr =>{
|
||||
if (arr.include(field_name)){
|
||||
arr.forEach(x => {
|
||||
if (x!=field_name){
|
||||
var w1 = bricks.getWidgetById(x + '_box', this);
|
||||
if (w1) {
|
||||
w1.disabled(true);
|
||||
w1.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
enable_field(field_name){
|
||||
this.toggle_disable(field_name, false);
|
||||
}
|
||||
disable_field(field_name){
|
||||
this.toggle_disable(field_name, true);
|
||||
}
|
||||
get_formdata(){
|
||||
var data = new FormData();
|
||||
var changed = false;
|
||||
@ -243,6 +283,7 @@ bricks.FormBase = class extends bricks.Layout {
|
||||
continue;
|
||||
}
|
||||
var w = this.name_inputs[name];
|
||||
if (w.parent.is_disabled()) continue;
|
||||
var d = w.getValue();
|
||||
if (w.required && ( d[name] == '' || d[name] === null)){
|
||||
new bricks.Error({title:'Requirement', message:'required field must input"' + w.label + '"'})
|
||||
@ -326,8 +367,19 @@ bricks.Form = class extends bricks.FormBase {
|
||||
toolbar:
|
||||
submit_url:
|
||||
method:
|
||||
exclussionfields:[
|
||||
[a,b,c],
|
||||
[x,y]
|
||||
]
|
||||
fields
|
||||
}
|
||||
field {
|
||||
name:
|
||||
label:
|
||||
uitype:
|
||||
nonuse: # 不使用
|
||||
...
|
||||
}
|
||||
*/
|
||||
constructor(opts){
|
||||
opts.height = "100%";
|
||||
|
||||
@ -6,7 +6,9 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="stylesheet" href="{{entire_url('/bricks/3parties/xterm.css')}}" />
|
||||
<link rel="stylesheet" href="{{entire_url('/bricks/css/bricks.css')}}">
|
||||
<link rel="stylesheet", href="{{entire_url('/css/myapp.css')}}">
|
||||
{% for mycss in cssfiles() %}
|
||||
<link rel="stylesheet" href="{{entire_url(mycss)}}">
|
||||
{% endfor %}
|
||||
</head>
|
||||
<body>
|
||||
<!--
|
||||
@ -27,12 +29,13 @@
|
||||
<script src="{{entire_url('/bricks/3parties/xterm.js')}}"></script>
|
||||
<script src="{{entire_url('/bricks/3parties/xterm-addon-fit.js')}}"></script>
|
||||
<script src="{{entire_url('/bricks/3parties/video.min.js')}}"></script>
|
||||
|
||||
<script src="{{entire_url('/bricks/3parties/recorder.wav.min.js')}}"></script>
|
||||
<script src="https://sage.opencomputing.cn/bricks/3parties/hls.js"></script>
|
||||
<script src="https://sage.opencomputing.cn/bricks/3parties/dash.all.min.js"></script>
|
||||
<script src="{{entire_url('/bricks/3parties/hls.js')}}"></script>
|
||||
<script src="{{entire_url('/bricks/3parties/dash.all.min.js')}}"></script>
|
||||
<script src="{{entire_url('/bricks/bricks.js')}}"></script>
|
||||
<script src="{{entire_url('/js/myapp.js')}}"></script>
|
||||
{% for myjs in jsfiles() %}
|
||||
<script src="{{entire_url(myjs)}}"></script>
|
||||
{% endfor %}
|
||||
<script>
|
||||
const opts = {
|
||||
"widget":
|
||||
|
||||
73
bricks/heatmap.js
Normal file
73
bricks/heatmap.js
Normal file
@ -0,0 +1,73 @@
|
||||
var bricks = window.bricks || {};
|
||||
|
||||
bricks.ChartHeatmap = class extends bricks.EchartsExt {
|
||||
/*
|
||||
数据格式:
|
||||
[
|
||||
{ x: '周一', y: '上午', value: 120 },
|
||||
{ x: '周二', y: '下午', value: 90 }, ...
|
||||
]
|
||||
|
||||
参数:
|
||||
{
|
||||
data_url,
|
||||
xField: 'x', // X 轴字段
|
||||
yField: 'y', // Y 轴字段
|
||||
valueField: 'value'
|
||||
}
|
||||
*/
|
||||
setup_options(data) {
|
||||
const xField = this.xField || 'x';
|
||||
const yField = this.yField || 'y';
|
||||
const valueField = this.valueField || 'value';
|
||||
|
||||
const xs = [...new Set(data.map(d => d[xField]))];
|
||||
const ys = [...new Set(data.map(d => d[yField]))];
|
||||
|
||||
const map = {};
|
||||
ys.forEach(y => map[y] = {});
|
||||
data.forEach(d => {
|
||||
map[d[yField]][d[xField]] = d[valueField];
|
||||
});
|
||||
|
||||
const heatmapData = [];
|
||||
for (let j = 0; j < ys.length; j++) {
|
||||
const y = ys[j];
|
||||
for (let i = 0; i < xs.length; i++) {
|
||||
const x = xs[i];
|
||||
heatmapData.push([i, j, map[y][x] || 0]);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
tooltip: { position: 'top' },
|
||||
grid: { height: '80%', top: '10%' },
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: xs,
|
||||
splitArea: { show: true }
|
||||
},
|
||||
yAxis: {
|
||||
type: 'category',
|
||||
data: ys,
|
||||
splitArea: { show: true }
|
||||
},
|
||||
visualMap: {
|
||||
min: 0,
|
||||
max: Math.max(...data.map(d => d[valueField])),
|
||||
calculable: true,
|
||||
orient: 'horizontal',
|
||||
left: 'center',
|
||||
bottom: '5%'
|
||||
},
|
||||
series: [{
|
||||
type: 'heatmap',
|
||||
data: heatmapData,
|
||||
label: { show: false },
|
||||
emphasis: { itemStyle: { shadowBlur: 10, shadowColor: 'rgba(0,0,0,0.5)' } }
|
||||
}]
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
bricks.Factory.register('ChartHeatmap', bricks.ChartHeatmap);
|
||||
@ -52,13 +52,17 @@ bricks.HttpText = class {
|
||||
width = bricks.app.screenWidth();
|
||||
height = bricks.app.screenHeight();
|
||||
}
|
||||
console.log('=====bricks.app=', bricks.app);
|
||||
|
||||
this.params = {
|
||||
"_webbricks_":1,
|
||||
"width":width,
|
||||
"height":height,
|
||||
"_width":width,
|
||||
"_height":height,
|
||||
"_is_mobile":is_mobile
|
||||
}
|
||||
if (bricks.app){
|
||||
this.params['_lang'] = bricks.app.lang;
|
||||
}
|
||||
}
|
||||
url_parse(url){
|
||||
var a = url.split('?');
|
||||
@ -90,6 +94,7 @@ bricks.HttpText = class {
|
||||
if (! params)
|
||||
params = {};
|
||||
var p = bricks.extend({}, this.params);
|
||||
var lang = bricks.get_current_language()
|
||||
p = bricks.extend(p, params);
|
||||
if (session){
|
||||
bricks.extend(p,{session:session});
|
||||
|
||||
49
bricks/kline.js
Normal file
49
bricks/kline.js
Normal file
@ -0,0 +1,49 @@
|
||||
var bricks = window.bricks || {};
|
||||
|
||||
bricks.ChartKLine = class extends bricks.EchartsExt {
|
||||
/*
|
||||
数据格式:
|
||||
[
|
||||
{ name: '2025-04-01', open: 2100, close: 2150, low: 2080, high: 2160 },
|
||||
...
|
||||
]
|
||||
|
||||
参数:
|
||||
{
|
||||
data_url,
|
||||
nameField: 'name', // 时间轴字段
|
||||
valueFields: ['open','close','low','high']
|
||||
}
|
||||
*/
|
||||
setup_options(data) {
|
||||
const { nameField } = this;
|
||||
const categories = data.map(d => d[nameField]);
|
||||
const kData = data.map(d => [d.open, d.close, d.low, d.high]);
|
||||
|
||||
return {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { type: 'cross' }
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: categories,
|
||||
scale: true
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
scale: true
|
||||
},
|
||||
series: [{
|
||||
type: 'candlestick',
|
||||
data: kData
|
||||
}],
|
||||
dataZoom: [
|
||||
{ type: 'inside' },
|
||||
{ type: 'slider' }
|
||||
]
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
bricks.Factory.register('ChartKLine', bricks.ChartKLine);
|
||||
@ -67,7 +67,7 @@ bricks.ModelOutput = class extends bricks.VBox {
|
||||
|
||||
this.content = new bricks.HBox({width:'100%'});
|
||||
this.add_widget(this.content);
|
||||
this.logid = null;
|
||||
this.llmusageid = null;
|
||||
this.run = new bricks.BaseRunning({target:this, cheight:2, cwidth:2});
|
||||
this.content.add_widget(this.run);
|
||||
this.filler = new bricks.LlmOut({width: '100%', css: 'card'});
|
||||
@ -102,7 +102,7 @@ bricks.ModelOutput = class extends bricks.VBox {
|
||||
"widgettype":"urlwidget",
|
||||
"options":{
|
||||
"params":{
|
||||
"logid":this.logid,
|
||||
"id":this.llmusageid,
|
||||
"value":val
|
||||
},
|
||||
"url":this.estimate_url
|
||||
@ -120,6 +120,9 @@ bricks.ModelOutput = class extends bricks.VBox {
|
||||
this.run = null;
|
||||
}
|
||||
this.filler.update(data);
|
||||
if (data.llmusageid) {
|
||||
this.llmusageid = data.llmusageid
|
||||
}
|
||||
return;
|
||||
}
|
||||
finish(){
|
||||
@ -205,6 +208,7 @@ bricks.LlmModel = class extends bricks.JsWidget {
|
||||
var d = this.inputdata2uploaddata(data);
|
||||
var hr = new bricks.HttpResponseStream();
|
||||
var resp = await hr.post(this.opts.url, {params:d});
|
||||
if (! resp) return;
|
||||
await hr.handle_chunk(resp, this.chunk_response.bind(this, mout));
|
||||
this.chunk_ended();
|
||||
} else {
|
||||
@ -212,6 +216,7 @@ bricks.LlmModel = class extends bricks.JsWidget {
|
||||
console.log('data_inouted=', data, 'upload_data=', d);
|
||||
var hj = new bricks.HttpJson()
|
||||
var resp = await hj.post(this.opts.url, {params:d});
|
||||
if (! resp) return;
|
||||
mout.update_data(resp);
|
||||
}
|
||||
mout.estimate_w.show();
|
||||
|
||||
65
bricks/map.js
Normal file
65
bricks/map.js
Normal file
@ -0,0 +1,65 @@
|
||||
var bricks = window.bricks || {};
|
||||
|
||||
bricks.ChartMap = class extends bricks.EchartsExt {
|
||||
/*
|
||||
数据格式:
|
||||
[
|
||||
{ name: '北京', value: 120 },
|
||||
{ name: '上海', value: 90 }, ...
|
||||
]
|
||||
|
||||
参数:
|
||||
{
|
||||
data_url,
|
||||
nameField: 'name',
|
||||
valueField: 'value',
|
||||
map_name: 'china', // 对应 echarts.registerMap 注册的地图名
|
||||
map_options: {} // 可选自定义样式
|
||||
}
|
||||
*/
|
||||
async setup_options(data) {
|
||||
const { nameField, valueField = 'value', map_name = 'china', map_options } = this;
|
||||
|
||||
const mapData = data.map(d => ({
|
||||
name: d[nameField],
|
||||
value: d[valueField]
|
||||
}));
|
||||
|
||||
return {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{b}: {c}'
|
||||
},
|
||||
visualMap: {
|
||||
min: 0,
|
||||
max: Math.max(...mapData.map(d => d.value)),
|
||||
left: 'left',
|
||||
top: 'bottom',
|
||||
text: ['高', '低'],
|
||||
calculable: true
|
||||
},
|
||||
series: [{
|
||||
name: '数据',
|
||||
type: 'map',
|
||||
map: map_name,
|
||||
roam: false,
|
||||
label: { show: true, color: '#fff' },
|
||||
itemStyle: {
|
||||
areaColor: '#eee',
|
||||
borderColor: '#444'
|
||||
},
|
||||
emphasis: {
|
||||
label: { color: '#fff' },
|
||||
itemStyle: { areaColor: '#e09191' }
|
||||
},
|
||||
data: mapData
|
||||
}],
|
||||
...(map_options || {})
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
bricks.Factory.register('ChartMap', bricks.ChartMap);
|
||||
/*
|
||||
💡 使用前需通过 `echarts.registerMap('china', geoJson)` 注册地图数据。
|
||||
*/
|
||||
@ -1,13 +1,14 @@
|
||||
var bricks = window.bricks || {};
|
||||
/*
|
||||
*/
|
||||
bricks.Menu = class extends bricks.VBox {
|
||||
bricks.Menu = class extends bricks.VScrollPanel {
|
||||
/*
|
||||
{
|
||||
"items":
|
||||
}
|
||||
*/
|
||||
constructor(options){
|
||||
opts.height = '100%';
|
||||
super(options);
|
||||
this.dom_element.style.display = "";
|
||||
this.dom_element.style.backgroundColor = options.bgcolor || "white";
|
||||
|
||||
102
bricks/popup.js
102
bricks/popup.js
@ -1,7 +1,7 @@
|
||||
var bricks = window.bricks || {};
|
||||
|
||||
bricks.get_popup_default_options = function(){
|
||||
return {
|
||||
ret = {
|
||||
timeout:0,
|
||||
archor:'cc',
|
||||
auto_open:true,
|
||||
@ -11,6 +11,14 @@ bricks.get_popup_default_options = function(){
|
||||
resizable:false,
|
||||
modal:true
|
||||
}
|
||||
if (bricks.is_mobile()) {
|
||||
ret.width = '100%';
|
||||
ret.height = '100%';
|
||||
} else {
|
||||
ret.width = '70%';
|
||||
ret.height = '70%';
|
||||
}
|
||||
return ret
|
||||
}
|
||||
bricks.Popup = class extends bricks.VBox {
|
||||
/*
|
||||
@ -171,6 +179,10 @@ bricks.Popup = class extends bricks.VBox {
|
||||
|
||||
positify_tl(){
|
||||
var rect, w, h, t, l;
|
||||
if (this.opts.eventpos && this.opts.origin_event){
|
||||
bricks.relocate_by_eventpos(this.opts.origin_event, this);
|
||||
return;
|
||||
}
|
||||
if (this.top && this.left){
|
||||
this.set_style('top', this.top);
|
||||
this.set_style('left', this.left);
|
||||
@ -309,7 +321,6 @@ bricks.Popup = class extends bricks.VBox {
|
||||
this.target_w = w;
|
||||
}
|
||||
}
|
||||
this.positify_tl()
|
||||
}
|
||||
this.no_opened = false;
|
||||
this.set_style('display', 'block');
|
||||
@ -321,6 +332,7 @@ bricks.Popup = class extends bricks.VBox {
|
||||
this.target_w.disabled(true);
|
||||
}
|
||||
this.bring_to_top();
|
||||
this.positify_tl();
|
||||
}
|
||||
dismiss(){
|
||||
if (! this.opened) return;
|
||||
@ -364,7 +376,7 @@ bricks.Popup = class extends bricks.VBox {
|
||||
}
|
||||
|
||||
bricks.get_popupwindow_default_options = function(){
|
||||
return {
|
||||
ret = {
|
||||
timeout:0,
|
||||
archor:'cc',
|
||||
auto_open:true,
|
||||
@ -374,63 +386,45 @@ bricks.get_popupwindow_default_options = function(){
|
||||
resizable:true,
|
||||
modal:true
|
||||
}
|
||||
if (bricks.is_mobile()) {
|
||||
ret.width = '100%';
|
||||
ret.height = '100%';
|
||||
} else {
|
||||
ret.width = '70%';
|
||||
ret.height = '70%';
|
||||
}
|
||||
return ret
|
||||
}
|
||||
bricks.WindowsPanel = class extends bricks.Popup {
|
||||
constructor(opts){
|
||||
opts.width = "80%";
|
||||
opts.auto_open = false;
|
||||
opts.auto_dismiss = true;
|
||||
opts.auto_destroy = true;
|
||||
opts.height = "80%";
|
||||
super(opts);
|
||||
this.content = new bricks.Cols({
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
record_view: {
|
||||
widgettype: "VBox",
|
||||
options: {
|
||||
css: "app-icon"
|
||||
},
|
||||
subwidgets: [
|
||||
{
|
||||
widgettype: "Text",
|
||||
options: {
|
||||
otext: "${title}",
|
||||
i18n: true,
|
||||
wrap: true
|
||||
}
|
||||
},
|
||||
{
|
||||
widgettype: "Svg",
|
||||
options: {
|
||||
rate: 2,
|
||||
url: "${url}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
this.content.bind('record_click', this.del_window.bind(this));
|
||||
var data = {
|
||||
total: bricks.app.mwins.length,
|
||||
rows:[]
|
||||
};
|
||||
for (var i=0; i< bricks.app.mwins.length; i++){
|
||||
data.rows.push({
|
||||
title: bricks.app.mwins[i].title,
|
||||
url: bricks.app.mwins[i].url,
|
||||
pos: i
|
||||
open(){
|
||||
this.auto_open = false;
|
||||
var dc = new bricks.DynamicColumn({});
|
||||
bricks.app.mwins.forEach(x => {
|
||||
var w = new bricks.VBox({
|
||||
"css": "mini-window card"
|
||||
});
|
||||
}
|
||||
this.content.dataHandle(data);
|
||||
this.add_widget(this.content);
|
||||
dc.add_widget(w);
|
||||
w.bind('click', this.reopen_window.bind(this, x));
|
||||
var tw = new bricks.Title6({
|
||||
width:'100%',
|
||||
wrap:true,
|
||||
text:x.title
|
||||
});
|
||||
var iw = new bricks.Svg({url:x.icon, rate:1.5});
|
||||
w.add_widget(iw);
|
||||
w.add_widget(tw);
|
||||
});
|
||||
this.add_widget(dc);
|
||||
super.open();
|
||||
}
|
||||
|
||||
del_window(event){
|
||||
var pos = event.params.pos;
|
||||
var w = bricks.app.mwins[pos];
|
||||
reopen_window(w){
|
||||
var nws = [];
|
||||
w.open();
|
||||
bricks.app.mwins.splice(pos, 1);
|
||||
bricks.app.mwins.forEach(x => {
|
||||
if (x != w) nws.push(x);
|
||||
});
|
||||
bricks.app.mwins = nws;
|
||||
this.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,8 +15,8 @@ bricks.ProgressBar = class extends bricks.HBox {
|
||||
this.add_widget(this.text_w);
|
||||
}
|
||||
set_value(v){
|
||||
var pzt = (current / total) * 100;
|
||||
pzt = Math.max(0, Math.min(100, percentage));
|
||||
var pzt = this.total_value ? (v / this.total_value) * 100 : 0;
|
||||
pzt = Math.max(0, Math.min(100, pzt));
|
||||
this.text_w.set_style('width', pzt + '%')
|
||||
}
|
||||
}
|
||||
|
||||
54
bricks/radar.js
Normal file
54
bricks/radar.js
Normal file
@ -0,0 +1,54 @@
|
||||
var bricks = window.bricks || {};
|
||||
|
||||
bricks.ChartRadar = class extends bricks.EchartsExt {
|
||||
/*
|
||||
数据格式示例:
|
||||
[
|
||||
{ name: '张三', indicator1: 80, indicator2: 60, ... },
|
||||
{ name: '李四', indicator1: 70, indicator2: 90, ... }
|
||||
]
|
||||
|
||||
参数:
|
||||
{
|
||||
data_url,
|
||||
data_params,
|
||||
nameField: 'name',
|
||||
valueFields: ['indicator1', 'indicator2', ...],
|
||||
radar_options: { // 自定义雷达配置
|
||||
indicator: [
|
||||
{ name: '销售', max: 100 },
|
||||
{ name: '管理', max: 100 }, ...
|
||||
]
|
||||
}
|
||||
}
|
||||
*/
|
||||
setup_options(data) {
|
||||
const { nameField, valueFields } = this;
|
||||
const series = [];
|
||||
const indicator = this.radar_options?.indicator || valueFields.map(f => ({ name: f, max: 100 }));
|
||||
|
||||
data.forEach(item => {
|
||||
series.push({
|
||||
name: item[nameField],
|
||||
type: 'radar',
|
||||
data: [
|
||||
{
|
||||
value: valueFields.map(f => item[f]),
|
||||
name: item[nameField]
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
tooltip: { trigger: 'item' },
|
||||
legend: { data: data.map(d => d[nameField]) },
|
||||
radar: { indicator },
|
||||
series
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
bricks.Factory.register('ChartRadar', bricks.ChartRadar);
|
||||
/*
|
||||
*/
|
||||
@ -7,12 +7,15 @@ bricks.BaseRunning = class extends bricks.FHBox {
|
||||
}
|
||||
*/
|
||||
constructor(opts){
|
||||
if (! opts.cwidth) opts.cwidth = 2;
|
||||
if (! opts.cheight) opts.cheight = 2;
|
||||
super(opts);
|
||||
this.icon_w = new bricks.Icon({
|
||||
rate: opts.rate|| 2,
|
||||
url:opts.icon || bricks_resource('imgs/running.gif')
|
||||
});
|
||||
this.time_w = new bricks.Text({
|
||||
text:'test123',
|
||||
text:'00:00:00',
|
||||
color:'#222',
|
||||
wrap:false,
|
||||
i18n:false
|
||||
|
||||
98
bricks/scatter.js
Normal file
98
bricks/scatter.js
Normal file
@ -0,0 +1,98 @@
|
||||
var bricks = window.bricks || {};
|
||||
|
||||
bricks.ChartScatter = class extends bricks.EchartsExt {
|
||||
/*
|
||||
* opts:
|
||||
* {
|
||||
* data_url,
|
||||
* data_params,
|
||||
* method,
|
||||
* user_data,
|
||||
* nameField: 可选,用于标签
|
||||
* xField: X坐标字段
|
||||
* yField: Y坐标字段
|
||||
* sizeField?: 气泡大小字段(可选)
|
||||
* categoryField?: 分组字段(用于不同颜色)
|
||||
* }
|
||||
*/
|
||||
|
||||
setup_options(data) {
|
||||
let seriesMap = {};
|
||||
|
||||
if (this.categoryField) {
|
||||
// 按 categoryField 分组
|
||||
data.forEach(item => {
|
||||
const cat = item[this.categoryField] || 'default';
|
||||
if (!seriesMap[cat]) {
|
||||
seriesMap[cat] = {
|
||||
name: cat,
|
||||
type: 'scatter',
|
||||
data: []
|
||||
};
|
||||
}
|
||||
seriesMap[cat].data.push([
|
||||
item[this.xField],
|
||||
item[this.yField],
|
||||
item[this.nameField] || null,
|
||||
item[this.sizeField] || 1
|
||||
]);
|
||||
});
|
||||
} else {
|
||||
// 单一系列
|
||||
const seriesData = data.map(item => [
|
||||
item[this.xField],
|
||||
item[this.yField],
|
||||
item[this.nameField] || null,
|
||||
item[this.sizeField] || 1
|
||||
]);
|
||||
|
||||
seriesMap['scatter'] = {
|
||||
type: 'scatter',
|
||||
data: seriesData
|
||||
};
|
||||
}
|
||||
|
||||
const series = Object.values(seriesMap);
|
||||
const xAxisName = this.xField;
|
||||
const yAxisName = this.yField;
|
||||
|
||||
return {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
formatter: function(params) {
|
||||
const p = params[0];
|
||||
return `${p.seriesName}<br/>${xAxisName}: ${p.value[0]}<br/>${yAxisName}: ${p.value[1]}`;
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
data: Object.keys(seriesMap)
|
||||
},
|
||||
xAxis: {
|
||||
type: 'value',
|
||||
name: xAxisName
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: yAxisName
|
||||
},
|
||||
series: series
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// 注册工厂
|
||||
bricks.Factory.register('ChartScatter', bricks.ChartScatter);
|
||||
|
||||
/*
|
||||
new bricks.ChartScatter({
|
||||
user_data: [
|
||||
{ x: 10, y: 20, size: 30, group: 'Group A', label: 'Point 1' },
|
||||
{ x: 15, y: 25, size: 40, group: 'Group B', label: 'Point 2' }
|
||||
],
|
||||
xField: 'x',
|
||||
yField: 'y',
|
||||
sizeField: 'size',
|
||||
categoryField: 'group',
|
||||
nameField: 'label'
|
||||
});
|
||||
*/
|
||||
@ -94,6 +94,9 @@ bricks.JsWidget = class {
|
||||
observable(name, value){
|
||||
return new bricks.Observable(this, name, value);
|
||||
}
|
||||
is_disabled(){
|
||||
return this.dom_element.disabled == true;
|
||||
}
|
||||
disabled(flag){
|
||||
if(flag){
|
||||
this.dom_element.disabled = true;
|
||||
|
||||
5
doc.sh
Normal file
5
doc.sh
Normal file
@ -0,0 +1,5 @@
|
||||
sources=$(find bricks -name "*.js" -exec grep -l '.register(' {} \; -print|sort -u)
|
||||
for s in $sources
|
||||
do
|
||||
t2t -p prompt.md -o docs/ai $s
|
||||
done
|
||||
BIN
docs/.DS_Store
vendored
BIN
docs/.DS_Store
vendored
Binary file not shown.
36
docs/HBox.md
36
docs/HBox.md
@ -1,36 +0,0 @@
|
||||
# HBox
|
||||
Hbox是一个水平布局小部件,它按水平方向布局子小部件
|
||||
## 用法
|
||||
```html
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<link rel="stylesheet" href="/bricks/css/bricks.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script src="/bricks/bricks.js"></script>
|
||||
|
||||
<script>
|
||||
const opts =
|
||||
{
|
||||
"widget": {
|
||||
"widgettype": "HBox",
|
||||
"options":
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
const app = new BricksApp(opts);
|
||||
app.run();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
|
||||
|
||||
**注意:hbox控件是水平排列的控件,相当于一个容器,可以容纳其他的控件,如button,form等**
|
||||
|
||||
@ -1,307 +0,0 @@
|
||||
# OEPNAI帮助生成的文档
|
||||
|
||||
我已经解压并直接从你上传的仓库源码里抓取并解析了第一批核心文件(uitypesdef.js、bricks.js、jsoncall.js、input.js、form.js、button.js),并把关键发现整理成第一阶段交付:(1)Bricks JSON 的基本结构与约定;(2)从源码明确的首批控件/类型与字段解析;(3)接下来我会继续批量解析剩余控件并把完整规范输出为 Bricks JSON 规范 v2。
|
||||
|
||||
下面是我现在能直接、可靠交付的结果 —— 基于仓库源码的准确提取(你可以直接拿去作为 SwiftBricks 设计依据)。
|
||||
|
||||
## 一、从源码得出的最重要全局约定(JSON 树格式)
|
||||
|
||||
(这些约定在 bricks.widgetBuild / 示例文件 中被反复使用——是框架运行时的标准格式)
|
||||
|
||||
根:任意 widget 描述是一个对象,关键字段包括:
|
||||
|
||||
widgettype — 必需,字符串,指定要创建的 widget 类型(与 bricks.Factory.register('XXX', ...) 注册名一致)。
|
||||
|
||||
options — 对象,控件的属性/配置(控件会读取 opts = desc.options || {})。
|
||||
|
||||
subwidgets — 数组,可选,子 widget 列表(用于容器/布局类)。
|
||||
|
||||
binds — 数组,可选,事件绑定定义(见下)。
|
||||
|
||||
wid — 可选,用于给 widget 指定 id(可在绑定中引用)。
|
||||
|
||||
actiontype / action 等:某些场景下顶层会直接包含 action 相关字段(详见具体控件)。
|
||||
|
||||
binds(事件绑定)结构(示例):
|
||||
```
|
||||
"binds": [
|
||||
{
|
||||
"wid":"self",
|
||||
"event":"click",
|
||||
"actiontype":"script",
|
||||
"script":"alert('HBox clicked');"
|
||||
}
|
||||
]
|
||||
```
|
||||
常见字段有:
|
||||
|
||||
wid:事件源 id("self" 表示当前)
|
||||
|
||||
event:事件名(如 "click", "change")
|
||||
|
||||
actiontype:执行动作类型(script, ajax, bricks 等)
|
||||
|
||||
script / url / method / params:动作所需额外字段
|
||||
|
||||
数据/表达式处理:
|
||||
|
||||
bricks.apply_data(desc, data) 用来把 data 应用到描述里(框架支持在 desc 中使用数据表达式并替换)。
|
||||
|
||||
## 二、uitypesdef.js(基础 UI 类型定义)
|
||||
|
||||
bricks/uitypesdef.js 中明确了基本的 简单数据类型 view/input 构造器(主要用于表单字段、简易数据渲染):
|
||||
|
||||
从文件中提取到的 已注册基础类型:
|
||||
|
||||
* View/Input builder 类型(通过 bricks.uitypesdef.setViewBuilder / setInputBuilder 注册):
|
||||
|
||||
1 str (string)
|
||||
|
||||
2 password
|
||||
|
||||
3 int
|
||||
|
||||
4 float
|
||||
|
||||
5 code
|
||||
|
||||
这说明:表单/字段层面,字段类型会使用这些 uitypesdef 注册的 builder 来渲染 view 或 input(例如 password 会用 UiPassword,str 用 UiText 等)。
|
||||
|
||||
## 三、解析的核心控件(文件级别提取:input.js, button.js, form.js)
|
||||
|
||||
下面是我从源码实现中抽取出来的控件属性/行为(可直接用作规范文档条目):
|
||||
|
||||
1) Button (bricks.Button — button.js)
|
||||
|
||||
注册名:'Button' (bricks.Factory.register('Button', bricks.Button);)
|
||||
|
||||
JSON 示例位置:examples/button.ui
|
||||
|
||||
主要读取位置:this.opts(来自 desc.options)
|
||||
|
||||
常见 options 字段(在源码注释/实现中出现):
|
||||
|
||||
name:控件名
|
||||
|
||||
label:显示文本
|
||||
|
||||
icon:图标路径
|
||||
|
||||
tooltip:提示
|
||||
|
||||
bgcolor / color / css 等样式配置
|
||||
|
||||
orientation, nonepack, height, width, item_rate 等布局/尺寸相关
|
||||
|
||||
action:动作对象(格式可包含 target, datawidget 等)
|
||||
|
||||
事件:
|
||||
|
||||
内部会 dispatch('click', this.opts),并触发 binds 中注册的 click。
|
||||
|
||||
可通过 binds 或 action 指定脚本或 RPC。
|
||||
|
||||
children:
|
||||
|
||||
Button 通常是单节点(不常用 subwidgets),但可嵌于容器(如 HBox 的 subwidgets 中为 Button)。
|
||||
|
||||
2) UiType / 基本 Input(input.js)
|
||||
|
||||
主要类:bricks.UiType(extends bricks.Layout)
|
||||
|
||||
常见 opts / options 字段:
|
||||
|
||||
name:字段名(表单字段标识)
|
||||
|
||||
required:是否必填
|
||||
|
||||
ctype:字段类别(例如 'text')
|
||||
|
||||
value / defaultvalue:初始值
|
||||
|
||||
公开方法:
|
||||
|
||||
getValue():返回 { name: value } 对象
|
||||
|
||||
resultValue():拿到实际值(子类通常覆盖)
|
||||
|
||||
getUserData():从表单/父 widget 获取额外数据
|
||||
|
||||
行为:
|
||||
|
||||
Input 节点在 form 内会被 Form 收集(Form 里有 build_fields、getValue 整体收集逻辑)。
|
||||
|
||||
3) Form(form.js)
|
||||
|
||||
负责构建 form 字段、处理提交并与后端交互
|
||||
|
||||
与表单相关常量:bricks.need_formdata_fields = ['file', 'video', 'audio'];(决定是否使用 multipart/form-data)
|
||||
|
||||
主要职责:
|
||||
|
||||
build_fields(form, parent, fields):根据 fields 描述构造表单 field widget(支持 miniform、FieldGroup 等抽象)
|
||||
|
||||
command_handle(event):处理表单工具栏命令(如提交、reset)
|
||||
|
||||
show_resp_message_or_error(resp):显示服务端返回作为 widget build 的 JSON(可把服务器返回的 JSON 当作 widget 描述渲染)
|
||||
|
||||
表单的 options/items 格式在 examples 下的 miniform.ui / login_form.json 有具体样例(我会在后续批次把每个示例抽成规范片段)
|
||||
|
||||
## 四、jsoncall.js(HTTP/JSON / RPC 支持)
|
||||
|
||||
包含 bricks.HttpJson、httpcall、withLoginInfo 等函数,用于:
|
||||
|
||||
向服务器发送请求并解析流式响应(支持 chunked / SSE-like)
|
||||
|
||||
自动处理 cookie/session(示例里会把 Set-Cookie 存入 bricks.app.save_session)
|
||||
|
||||
支持超时、重试、与 widgetBuild 配合把 JSON 响应直接渲染成 widget
|
||||
|
||||
在 bricks.widgetBuild 中,如果 widgettype == 'urlwidget' 或 desc.options.url,会使用 jsoncall 发起请求并用返回数据继续构造 widget
|
||||
|
||||
## 五、仓库中注册的所有 widget 类型(已从所有 *.js 文件的 bricks.Factory.register 抽取)
|
||||
```
|
||||
Accordion
|
||||
ASRClient
|
||||
AudioPlayer
|
||||
AudioRecorder
|
||||
TextedAudioPlayer
|
||||
ChartBar
|
||||
Button
|
||||
Camera
|
||||
Cols
|
||||
Conform
|
||||
ContinueAudioPlayer
|
||||
Countdown
|
||||
TimePassed
|
||||
DataGrid
|
||||
DataRow
|
||||
DataViewer
|
||||
DOCXviewer
|
||||
EXCELviewer
|
||||
PDFviewer
|
||||
DynamicAccordion
|
||||
DynamicColumn
|
||||
EChart
|
||||
FilePlayer
|
||||
FloatIconBar
|
||||
Footer
|
||||
Form
|
||||
Filler
|
||||
Goban
|
||||
Grid
|
||||
Header
|
||||
HeaderBar
|
||||
HeaderBar2
|
||||
HeaderTitle
|
||||
HBox
|
||||
IconBarPage
|
||||
IconTextBar
|
||||
Image
|
||||
ImageList
|
||||
Input
|
||||
IFrame
|
||||
KeyPress
|
||||
KeyEvent
|
||||
Label
|
||||
Layout
|
||||
Li
|
||||
List
|
||||
ListTable
|
||||
Login
|
||||
MapView
|
||||
MarkdownViewer
|
||||
Menu
|
||||
Message
|
||||
MiniForm
|
||||
Modal
|
||||
MultiStateImage
|
||||
MyOperator
|
||||
Navigator
|
||||
Notification
|
||||
PageDataLoader
|
||||
Paging
|
||||
Panel
|
||||
Pie
|
||||
Popup
|
||||
ProgressBar
|
||||
QRCode
|
||||
RadialChart
|
||||
Range
|
||||
Row
|
||||
Scroll
|
||||
Select
|
||||
Splitter
|
||||
Spinner
|
||||
SQLConsole
|
||||
SvgImage
|
||||
Tab
|
||||
Tabular
|
||||
Table
|
||||
TablePager
|
||||
Text
|
||||
TextArea
|
||||
TextInput
|
||||
TimeAxis
|
||||
Toolbar
|
||||
Tree
|
||||
TreeChoose
|
||||
Tooltip
|
||||
Title1
|
||||
Title2
|
||||
Title3
|
||||
Title4
|
||||
Title5
|
||||
Title6
|
||||
VideoPlayer
|
||||
Video
|
||||
WebSocket
|
||||
WebTTS
|
||||
WebASR
|
||||
Tooltip
|
||||
Text
|
||||
KeyinText
|
||||
Title1
|
||||
Title2
|
||||
Title3
|
||||
Title4
|
||||
Title5
|
||||
Title6
|
||||
LlmIO
|
||||
Wterm
|
||||
```
|
||||
|
||||
## 六、示例(摘自 examples/button.ui) — 典型的 widget JSON
|
||||
```
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"options": {
|
||||
"height":"auto",
|
||||
"width":"100%"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Button",
|
||||
"options": {
|
||||
"name": "test",
|
||||
"icon": "/bricks/imgs/submit.png",
|
||||
"label": "test once",
|
||||
"tooltip": "This is a joke too",
|
||||
"bgcolor": "#ff00ff",
|
||||
"nonepack": true,
|
||||
"orientation": "horizontal"
|
||||
}
|
||||
}
|
||||
],
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "script",
|
||||
"script": "alert('HBox clicked');"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
这种结构是框架的标准单位:widgettype + options + subwidgets + binds。
|
||||
|
||||
@ -30,6 +30,50 @@ bricks采用控件这一概念来描述web GUI的显示部件,每个控件均
|
||||
|
||||
控件的详细介绍请参看[控件说明](widgets.md)
|
||||
|
||||
### 控件扩展
|
||||
如果现有的控件没法满足系统要求,bricks支持控件扩展,控件扩展需遵守:
|
||||
* 控件class继承自某一个控件的class
|
||||
|
||||
* 按照需求实现控件逻辑
|
||||
|
||||
* 在需要的地方用this.dispatch触发此控件的事件
|
||||
|
||||
假设需要扩展一个名字叫ExtContainer的控件
|
||||
```
|
||||
bricks.ExtContainer = class extends bricks.VBox {
|
||||
constructor(opts){
|
||||
super(opts);
|
||||
/* 新控件的创建代码 */
|
||||
}
|
||||
......
|
||||
/* 对象的其他方法,在需要的时候,在某个方法中,使用this.dispatch('new_event', data)方法引发事件 */
|
||||
}
|
||||
bricks.register('ExtContainer', bricks.ExtContainer); /* 注册新控件 */
|
||||
```
|
||||
新控件的使用,example.ui
|
||||
```
|
||||
{
|
||||
"widgettype":"ExtContainer",
|
||||
"options":{
|
||||
....
|
||||
},
|
||||
"subwidgets":[
|
||||
...
|
||||
],
|
||||
"binds":[
|
||||
{
|
||||
"wid":"self",
|
||||
"event":"new_event",
|
||||
"actiontype":"urlwidget",
|
||||
"target":"some_container",
|
||||
"options":{
|
||||
"url":"{{entire_url('./some_ui.ui')}}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 事件以及事件处理
|
||||
每个控件都能触发所映射dom元素的事件,以及控件js类的成员函数以及祖先类的
|
||||
成员函数中dispatch出的事件
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user