写在前面:datav视觉展示能力强大,但用户交互能力弱,无法应对产品经理的需求。原本在工程内的逻辑,要拆分成很多块来写。需要用原生js或者jquery实现。datav的底层实现没有渠道快速获知。遇到卡点的时候,得咨询datav工作人员才得解。如果不是项目要求,个人是不会尝试去使用这样的工具的。
一.涉及datav中的使用要点:
1.自定义组件
2.画布编辑器
3.蓝图编辑器
4.hook
二.UED:
三.需求实现要点:
1.按钮的文案是动态的,要调用接口去获取,按钮最多展示3个,其他的收到更多下拉当中。
2.点击按钮时,请求对应按钮的数据
3.默认展示第一个按钮对应的数据
四.自定义组件实现:
组件的文件组织结构如图1,其中dist为本地编译utils文件夹后的结果,编译的插件是
button-list.ts
import "./button-list.css";
var $ = require("jquery");
type ButtonListProps = {
config: {
showMore: Boolean;
showLength: number;
};
data: Record<string, string | number>[];
firstSelected: any;
buttonListChange: (val) => void;
};
function render(props: ButtonListProps) {
const { config, data, firstSelected, buttonListChange } = props;
const { showMore, showLength } = config;
let curData = [];
let selectorData = [];
if (showMore && showLength) {
curData = data.slice(0, showLength);
selectorData = data.slice(showLength);
} else if (showLength) {
curData = data.slice(0, showLength);
} else if (showMore) {
selectorData = data;
}
$("[name=button_list_select]").val(undefined);
function renderSelect() {
return `<div class="button_list_select_container" id="button-list-id">
<select class="button_list_select" name="button_list_select">
<option></option>
${selectorData
.map((item) => {
const { label, value } = item;
return `<option value=${value}>${label}</option>`;
})
.join("")}
</select>
</div>`;
}
function renderButtonList() {
return `${curData
.map((item) => {
const { label, value } = item;
return `<div class="button ${
firstSelected === value ? "selected" : ""
}" data-value=${value}><span class="text">${label}</span></div>`;
})
.join("")}`;
}
const container = $(`<div class="button_list">
${curData.length > 0 ? renderButtonList() : ""}
${selectorData.length > 0 ? renderSelect() : ""}
</div>`);
return container;
}
export { render };
button-list.less
.button_list {
margin-bottom: 10px;
display: flex;
justify-content: center;
font-family: "Microsoft Yahei";
.button {
height: 26px;
min-width: 77px;
background-color: rgba(255, 255, 255, 0.13);
display: flex;
align-items: center;
justify-content: center;
padding: 0 4px;
color: rgba(255, 255, 255, 0.8);
font-size: 16px;
line-height: 22px;
text-align: center;
cursor: pointer;
.text {
width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&:not(:last-child) {
margin-right: 12px;
}
&:hover,
&.selected {
background-color: rgba(0, 229, 255, 0.4);
}
}
.button_list_select_container {
position: relative;
.button_list_select {
display: none;
}
// 对select2 4.0.10 的样式覆盖
.select2-container.select2-container--default {
width: 70px;
font-size: 16px;
}
.select2-container.select2-container--default {
font-size: 16px;
.select2-results__option[aria-selected="true"] {
background-color: rgba(255, 255, 255, 0.13);
}
&:hover,
&.selected {
background-color: rgba(0, 229, 255, 0.4);
}
.select2-results__option--highlighted[aria-selected] {
background-color: rgba(0, 229, 255, 0.4);
}
.select2-search--dropdown {
display: none;
}
.select2-selection--single {
border-radius: 0px;
background-color: rgba(255, 255, 255, 0.13);
border: none;
.select2-selection__clear {
color: #fff;
font-size: 2em;
}
.select2-selection__rendered {
color: rgba(255, 255, 255, 0.8);
font-size: 16px;
}
.select2-selection__arrow b {
border-color: rgba(255, 255, 255, 0.13) transparent transparent
transparent;
}
}
.select2-dropdown {
border: 0px;
background-color: rgba(70, 74, 77);
}
}
.select2-results__option--selectable {
background-color: rgba(255, 255, 255, 0.13);
font-size: 16px;
color: rgba(255, 255, 255, 0.8);
display: inline-block;
width: 100%;
&.select2-results__option--selected {
background-color: rgba(0, 116, 140, 1);
}
&.select2-results__option--highlighted {
background-color: rgba(0, 116, 140, 1);
}
}
}
}
index.js
var Event = require("bcore/event");
var $ = require("jquery");
var _ = require("lodash");
// require("select2")($); // 4.0.10
// require("select2/dist/css/select2.min.css");
var utils = require("./utils/dist/button-list");
/**
* 马良基础类
*/
module.exports = Event.extend(
function Base(container, config) {
this.config = {
theme: {},
};
this.container = $(container); //容器
this.apis = config.apis; //hook一定要有
this._data = null; //数据
this.chart = null; //图表
this.init(config);
},
{
render: function (data, config) {
data = this.data(data);
var cfg = this.mergeConfig(config);
const emitFunc = (val) => {
this.emit("value_change", val);
};
const eleDom = data.data;
const firstSelected = eleDom[0].value;
if (eleDom && eleDom.length > 0) {
this.emit("button_list_success", "success");
}
const app = utils.render({
config: cfg,
data: eleDom || {},
firstSelected: firstSelected,
buttonListChange: (val) => {
emitFunc(val);
},
});
emitFunc(firstSelected);
this.container.html(app);
//如果有需要的话,更新样式
this.updateStyle();
},
}
);
hook.js的代码见六
package.json
{
"name": "@xxx/button-list",
"version": "0.0.33",
"dependencies": {
"bcore": "0.0.18",
"jquery": "2.1.4",
"lodash": "4.6.1"
},
"datav": {
"cn_name": "按钮组件",
"icon": "",
"protocol": 2,
"type": [
"regular"
],
"view": {
"width": "400",
"height": "200",
"minWidth": "200",
"minHeight": "100"
},
"apis": {
"source": {
"handler": "render",
"description": "接口描述",
"fields": {
"value": {
"description": "值说明"
}
}
}
},
"config": {
"width": {
"type": "text",
"name": "按钮区域的最大宽度",
"default": 400
},
"height": {
"type": "text",
"name": "按钮区域的最大高度",
"default": 26
},
"showMore": {
"name": "是否展示更多下拉",
"type": "switch",
"statusText": "是",
"default": true
},
"showLength": {
"name": "展示非下拉按钮的个数",
"type": "stepper",
"step": 1,
"min": 1,
"default": 3
}
},
"api_data": {
"source": {
"data": [
{
"label": "1001",
"value": "1001"
},
{
"label": "1002",
"value": "1002"
},
{
"label": "1003",
"value": "1003"
},
{
"label": "1004",
"value": "1004"
},
{
"label": "1005",
"value": "1005"
},
{
"label": "1006",
"value": "1006"
},
{
"label": "1007",
"value": "1007"
}
]
}
},
"events": {
"value_change": {
"description": "当值发生变化时"
},
"button_list_success": {
"description": "按钮数据获取完毕"
}
}
}
}
图1:
图2:
五.蓝图编辑器配置:
1.请求数据,并转化数据成按钮组件支持的数据结构,数据处理完成导入到按钮组件中(请求数据需要用到画布编辑器中的api数据源,可以自定义一个视觉层面上不可见组件,用以调用接口)
2.按钮组件的数据源配置
数据响应结果:受控模式、静态数据源
按钮组件的接收的数据结构
3.按钮点击时,设置回调id,供待展示数据的api数据源做请求入参
4.待展示数据的数据源配置
数据源类型:API
请求方式:GET(这种动态入参的方式,datav截止目前只支持get请求的方式)
六.HOOK代码(目前专业版的datav也有官网上说尊享版才有的hook功能)
执行流程:
1.按钮数据要请求
2.请求select2的css
3.请求select2的js
4.需要要确保dom.select2()在dom绘制完成后执行
////////// select2 /////////
/**
* @param {Stage} stage
*/
module.exports = (stage) => {
$(document).ready(function () {
console.log("------ready ------ DOM ready!");
});
// 疑问:以下为啥不会执行???
// document.addEventListener('DOMContentLoaded', function () {
// console.log("------ready ------ DOMContentLoaded!");
// });
// 专业版dom
const dom = stage.get("@xxx_button-list_XFLUi");
const container = $(dom.container);
$(container.find(".button_list_select")).ready(function () {
console.log("------ready ------ container -- button_list_select ready!");
});
// 获取select2的css文件
$("head").append(
`<link href = "https://cdn.jsdelivr.net/npm/select2@4.0.10/dist/css/select2.min.css" rel = "stylesheet" />`
);
//方式2:监听回调id事件
dom.on("button_list_success", function (data) {
if (data === "success") {
let script = document.createElement("script");
/*
为使onload内的方法执行,需要避免浏览器的disk cache。
解决方法是:在请求js时加上随机值
*/
script.src = `https://cdn.jsdelivr.net/npm/select2@4.0.10/dist/js/select2.min.js?${+new Date()}`;
script.onload = function () {
console.log("------select2 ------ script.onload!");
excuteMain();
};
document.head.append(script);
}
});
function excuteMain() {
const select2dom = container.find(".button_list_select").select2({
dropdownParent: container.find("#button-list-id"),
placeholder: "更多",
width: 72,
});
const emitFunc = (val) => {
dom.emit("value_change", val);
};
// 交互事件注册
container.find(".button").on("click", function (e) {
const val = $(e.currentTarget).data("value");
container.find(".button").removeClass("selected");
select2dom.val(undefined).trigger("change");
select2dom.siblings(".select2-container").removeClass("selected");
$(e.currentTarget).addClass("selected");
emitFunc(val);
});
select2dom.on("change", function (e) {
const val = $(e.currentTarget).val();
container.find(".button").removeClass("selected");
select2dom.siblings(".select2-container").addClass("selected");
if (val) {
emitFunc(val);
}
});
}
};
七.卡点问题沟通,及实现沟通(跟datav新人群的工作人员的沟通过程):
datav技术人员@钉钉群21931738:
- 不要用动态添加js以及全局的document对象来设置,用npm的方式引入
-
- 以下逻辑本地datav run生效,datav package之后到datav屏中引入不生效
-
-
- $("head").append(
<link href = "https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel = "stylesheet" />
);
let script = document.createElement("script");
script.src =
"cdn.jsdelivr.net/npm/select2…";
document.head.append(script);
- $("head").append(
-
- 问:index.js里的render是npm包加载完后执行的么?render方法是依赖都拉完之后执行的么?render是 = $(document).ready()的回调不??
-
- 答:只要不是动态追加的js标签,都是引入完成之后执行的
- 2.$(document).ready()回调里调用select2,dom元素只要在select2执行前已经绘制应该就没有问题
-
- 3.容器内不可操作容器外的dom,比如容器内操作document
- 问:render方法里写select2的调用***.select2()方法不生效的问题如何解决?select2官网是说,要在$(document).ready()回调里面执行:***.select2
-
- 答:这个应该只是参考把。dom元素只要在他执行前已经绘制应该就没有问题把
- 问:组件包上传报错
-
- 答:检查一下package是否定义了相应的依赖包 看看network请求或者dom元素 对应的js是否有加载。
- 答:上传之后 等几分钟在添加看看 打包需要点时间 并且需要强刷下浏览器
-
- 答:降低点版本把 版本太高了 可能会打包失败