历史温度暂时提交

feasure-livedata
lld 2026-03-20 21:21:17 +08:00
parent 3583296329
commit 72a71589b4
23 changed files with 3457 additions and 51 deletions

View File

@ -218,6 +218,6 @@ export default {
<style lang="scss">
//
@import './tuniao-ui/iconfont.css';
//@import './tuniao-ui/iconfont.css'; // main.wxss
@import '@/static/scss/index.scss';
</style>

View File

@ -4,7 +4,7 @@
<view>
<uni-row class="demo-uni-row" >
<uni-col :span="8">
<uni-data-select style="background: #fff" v-model="imei" :localdata="agriList" @change="changeAgri"></uni-data-select>
<uni-data-select v-model="imei" :localdata="agriList" @change="changeAgri"></uni-data-select>
</uni-col>
<uni-col :span="16">
@ -26,9 +26,7 @@
:ontouch="true"
:disableScroll="true"
:onmovetip="true"
:onzoom="true"
tooltipFormat="tooltipDemo"
/>
<!-- 暂无数据 -->
</view>
@ -128,53 +126,8 @@ export default {
getHistoryData(param).then(response => {
if (response.code === 200 && response.data) {
var data = response.data;
var res = {
categories: data.timeList,
series: [
{
name: "温度1",
index: 0,
data: data.temp1List
},
{
name: "温度2",
index: 0,
data: data.temp2List
},
{
name: "温度3",
index: 0,
data: data.temp3List
},
{
name: "温度4",
index: 0,
data: data.temp4List
},
{
name: "湿度1",
index: 1,
data: data.humi1List
},
{
name: "湿度2",
index: 1,
data: data.humi2List
},
{
name: "湿度3",
index: 1,
data: data.humi3List
},
{
name: "湿度4",
index: 1,
data: data.humi4List
this.chartData = JSON.parse(JSON.stringify(data));
}
]
};
}
this.chartData = JSON.parse(JSON.stringify(res));
})
},
getAgriList() {
@ -208,7 +161,9 @@ export default {
})
},
changeAgri(imei) {
console.info("大棚下拉选择",imei)
if (imei) {
this.getServerData();
}
},
changeDate(date) {
// date[0]-date[1]
@ -230,4 +185,7 @@ export default {
width: 100%;
height: 500px;
}
/deep/ .uni-stat__select {
background: #fff !important;
}
</style>

View File

@ -0,0 +1,234 @@
## 2.0.72026-02-05
- fix: 修复非web无法缩放问题
## 2.0.62025-12-13
- feat: 修复类型问题
## 2.0.52025-12-12
- fix: 修复uniappx 微信小程序模糊问题
## 2.0.42025-12-12
- fix: 修复uniapp vue2 app引入依赖报错问题
## 2.0.32025-12-04
- feat: autoHideTooltip默认为false
## 2.0.22025-11-24
- feat: 增加`autoHideTooltip`属性
## 2.0.12025-10-28
- chore: 更新文档
## 2.0.02025-10-28
- feat: 基于 Vue 3 Composition API 重构 uni-app 端组件,提升代码可维护性和性能
- feat: 升级内置 ECharts 至 v6 版本,支持最新图表特性和性能优化
## 1.0.42025-05-16
- fix: 修复uniappx ios尺寸
## 1.0.32025-05-10
- fix: 修复nvue缺少`isDisposed`
## 1.0.22025-03-21
- fix: 修复词云无法设置字体大小的问题
## 1.0.12025-03-14
- fix: 修复抖音小程序不显示问题
## 1.0.02025-02-27
- fix: 修复uniappx微信小程序不显示问题
## 0.9.92025-02-24
- feat: 更新v4
## 0.9.82024-12-20
- fix: 修复 APP 无法放大问题
## 0.9.72024-12-02
- feat: uniapp 增加`landscape`,当`landscape`为`true`时旋转90deg达到横屏效果。
- feat: 支持uniapp x 微信小程序
## 0.9.62024-07-23
- fix: 修复 uni is not defined
## 0.9.52024-07-19
- chore: 鸿蒙`measureText`为异步,异步字体不正常,使用模拟方式。
## 0.9.42024-07-18
- chore: 更新文档
## 0.9.32024-07-16
- feat: 鸿蒙 canvas 事件缺失,待官方修复,如何在鸿蒙使用请看文档`常见问题 vue3`
## 0.9.22024-07-12
- chore: 删除多余文件
## 0.9.12024-07-12
- fix: 修复 安卓5不显示图表问题
## 0.9.02024-06-13
- chore: 合并nvue和uvue
## 0.8.92024-05-19
- chore: 更新文档
## 0.8.82024-05-13
- chore: 更新文档和uvue示例
## 0.8.72024-04-26
- fix: uniapp x需要HBX 4.13以上
## 0.8.62024-04-10
- feat: 支持 uniapp x ios
## 0.8.52024-04-03
- fix: 修复 nvue `reset`传值不生效问题
- feat: 支持 uniapp x web
## 0.8.42024-01-27
- chore: 更新文档
## 0.8.32024-01-21
- chore: 更新文档
## 0.8.22024-01-21
- feat: 支持 `uvue`
## 0.8.12023-08-24
- fix: app 的`touch`事件为`object` 导致无法显示 `tooltip`
## 0.8.02023-08-22
- fix: 离屏 报错问题
- fix: 微信小程序PC无法使用事件
- chore: 更新文档
## 0.7.92023-07-29
- chore: 更新文档
## 0.7.82023-07-29
- fix: 离屏 报错问题
## 0.7.72023-07-27
- chore: 更新文档
- chore: lime-echart 里的示例使用自定tooltips
- feat: 对支持离屏的使用离屏创建(微信、字节、支付宝)
## 0.7.62023-06-30
- fix: vue3 报`width`的错
## 0.7.52023-05-25
- chore: 更新文档 和 demo, 使用`lime-echart`这个标签即可查看示例
## 0.7.42023-05-22
- chore: 增加关于钉钉小程序上传时提示安全问题的说明及修改建议
## 0.7.32023-05-16
- chore: 更新 vue3 非微信小程序平台可能缺少`wx`的说明
## 0.7.22023-05-16
- chore: 更新 vue3 非微信小程序平台的可以缺少`wx`的说明
## 0.7.12023-04-26
- chore: 更新demo使用`lime-echart`这个标签即可查看示例
- chore微信小程序的`tooltip`文字有阴影,怀疑是微信的锅,临时解决方法是`tooltip.shadowBlur = 0`
## 0.7.02023-04-24
- fix: 修复`setAttribute is not a function`
## 0.6.92023-04-15
- chore: 更新文档vue3请使用echarts esm的包
## 0.6.82023-03-22
- feat: mac pc无法使用canvas 2d
## 0.6.72023-03-17
- feat: 更新文档
## 0.6.62023-03-17
- feat: 微信小程序PC已经支持canvas 2d故去掉判断PC
## 0.6.52022-11-03
- fix: 某些手机touches为对象导致无法交互。
## 0.6.42022-10-28
- fix: 优化点击事件的触发条件
## 0.6.32022-10-26
- fix: 修复 dataZoom 拖动问题
## 0.6.22022-10-23
- fix: 修复 飞书小程序 尺寸问题
## 0.6.12022-10-19
- fix: 修复 PC mousewheel 事件 鼠标位置不准确的BUG不兼容火狐
- feat: showLoading 增加传参
## 0.6.02022-09-16
- feat: 增加PC的mousewheel事件
## 0.5.42022-09-16
- fix: 修复 nvue 动态数据不显示问题
## 0.5.32022-09-16
- feat: 增加enableHover属性 在PC端时当鼠标进入显示tooltip不必按下。
- chore: 更新文档
## 0.5.22022-09-16
- feat: 增加enableHover属性 在PC端时当鼠标进入显示tooltip不必按下。
## 0.5.12022-09-16
- fix: 修复nvue报错
## 0.5.02022-09-15
- feat: init(echarts, theme?:string, opts?:{}, callback: function(chart))
## 0.4.82022-09-11
- feat: 增加 @finished
## 0.4.72022-08-24
- chore: 去掉 stylus
## 0.4.62022-08-24
- feat: 增加 beforeDelay
## 0.4.52022-08-12
- chore: 更新文档
## 0.4.42022-08-12
- fix: 修复 resize 无参数时报错
## 0.4.32022-08-07
# 评论有说本插件对新手不友好,让我做不好就不要发出来。 还有的说跟官网一样,发出来做什么,给我整无语了。
# 所以在此提醒一下准备要下载的你,如果你从未使用过 echarts 请不要下载 或 谨慎下载。
# 如果你确认要下载麻烦看完文档。还有请注意插件是让echarts在uniapp能运行API 配置请自行去官网查阅!
# 如果你不会echarts 但又需要图表,市场上有个很优秀的图表插件 uchart 你可以去使用这款插件uchart的作者人很好也热情。
# 每个人都有自己的本职工作,如果你能力强可以自行兼容,如果使用了他人的插件也麻烦尊重他人的成果和劳动时间。谢谢。
# 为了心情愉悦,本人已经使用插件屏蔽差评。
- chore: 更新文档
## 0.4.22022-07-20
- feat: 增加 resize
## 0.4.12022-06-07
- fix: 修复 canvasToTempFilePath 不生效问题
## 0.4.02022-06-04
- chore 为了词云 增加一个canvas 标签
- 词云下载地址[echart-wordcloud](https://ext.dcloud.net.cn/plugin?id=8430)
## 0.3.92022-06-02
- chore: 更新文档
- tips: lines 不支持 `trailLength`
## 0.3.82022-05-31
- fix: 修复 因mouse事件冲突tooltip跳动问题
## 0.3.72022-05-26
- chore: 更新文档
- chore: 设置默认宽高300px
- fix: 修复 vue3 微信小程序 拖影BUG
- chore: 支持PC
## 0.3.52022-04-28
- chore: 更新使用方式
- 🔔 必须使用hbuilderx 3.4.8-alpha以上
## 0.3.42021-08-03
- chore: 增加 setOption的参数值
## 0.3.32021-07-22
- fix: 修复 径向渐变报错的问题
## 0.3.22021-07-09
- chore: 统一命名规范,无须主动引入组件
## [代码示例站点1](https://limeui.qcoon.cn/#/echart-example)
## [代码示例站点2](http://liangei.gitee.io/limeui/#/echart-example)
## 0.3.12021-06-21
- fix: 修复 app-nvue ios is-enable 无效的问题
## [代码示例站点1](https://limeui.qcoon.cn/#/echart-example)
## [代码示例站点2](http://liangei.gitee.io/limeui/#/echart-example)
## 0.3.02021-06-14
- fix: 修复 头条系小程序 2d 报 JSON.stringify 的问题
- 目前 头条系小程序 2d 无法在开发工具上预览划动图表页面无法滚动axisLabel 字体颜色无法更改建议使用非2d。
## 0.2.92021-06-06
- fix: 修复 头条系小程序 2d 放大的BUG
- 头条系小程序 2d 无法在开发工具上预览,也存在划动图表页面无法滚动的问题。
## [代码示例http://liangei.gitee.io/limeui/#/echart-example](http://liangei.gitee.io/limeui/#/echart-example)
## 0.2.82021-05-19
- fix: 修复 微信小程序 PC 显示过大的问题
## 0.2.72021-05-19
- fix: 修复 微信小程序 PC 不显示问题
## [代码示例http://liangei.gitee.io/limeui/#/echart-example](http://liangei.gitee.io/limeui/#/echart-example)
## 0.2.62021-05-14
- feat: 支持 `image`
- feat: props 增加 `ec.clear`,更新时是否先删除图表样式
- feat: props 增加 `isDisableScroll` ,触摸图表时是否禁止页面滚动
- feat: props 增加 `webviewStyles` webview 的样式, 仅nvue有效
## 0.2.52021-05-13
- docs: 插件用到了css 预编译器 [stylus](https://ext.dcloud.net.cn/plugin?name=compile-stylus) 请安装它
## 0.2.42021-05-12
- fix: 修复 百度平台 多个图表ctx 和 渐变色 bug
- ## [代码示例http://liangei.gitee.io/limeui/#/echart-example](http://liangei.gitee.io/limeui/#/echart-example)
## 0.2.32021-05-10
- feat: 增加 `canvasToTempFilePath` 方法,用于生成图片
```js
this.$refs.chart.canvasToTempFilePath({success: (res) => {
console.log('tempFilePath:', res.tempFilePath)
}})
```
## 0.2.22021-05-10
- feat: 增加 `dispose` 方法,用于销毁实例
- feat: 增加 `isClickable` 是否派发点击
- feat: 实验性的支持 `nvue` 使用要慎重考虑
- ## [代码示例http://liangei.gitee.io/limeui/#/echart-example](http://liangei.gitee.io/limeui/#/echart-example)
## 0.2.12021-05-06
- fix修复 微信小程序 json 报错
- chore: `reset` 更改为 `setChart`
- feat: 增加 `isEnable` 开启初始化 启用这个后 无须再使用`init`方法
```html
<l-echart ref="chart" is-enable />
```
```js
// 显示加载
this.$refs.chart.showLoading()
// 使用实例回调
this.$refs.chart.setChart(chart => ...code)
// 直接设置图表配置
this.$refs.chart.setOption(data)
```
## 0.2.02021-05-05
- fix修复 头条 百度 偏移的问题
- docs: 更新文档
## [代码示例http://liangei.gitee.io/limeui/#/echart-example](http://liangei.gitee.io/limeui/#/echart-example)
## 0.1.02021-05-02
- chore: 第一次上传,基本全端兼容,使用方法与官网一致。
- 已知BUG非2d 无法使用背景色,已反馈官方
- 已知BUG头条 百度 有许些偏移
- 后期计划兼容nvue

View File

@ -0,0 +1,399 @@
import {getDeviceInfo} from './utils';
const cacheChart = {}
const fontSizeReg = /([\d\.]+)px/;
class EventEmit {
constructor() {
this.__events = {};
}
on(type, listener) {
if (!type || !listener) {
return;
}
const events = this.__events[type] || [];
events.push(listener);
this.__events[type] = events;
}
emit(type, e) {
if (type.constructor === Object) {
e = type;
type = e && e.type;
}
if (!type) {
return;
}
const events = this.__events[type];
if (!events || !events.length) {
return;
}
events.forEach((listener) => {
listener.call(this, e);
});
}
off(type, listener) {
const __events = this.__events;
const events = __events[type];
if (!events || !events.length) {
return;
}
if (!listener) {
delete __events[type];
return;
}
for (let i = 0, len = events.length; i < len; i++) {
if (events[i] === listener) {
events.splice(i, 1);
i--;
}
}
}
}
class Image {
constructor() {
this.currentSrc = null
this.naturalHeight = 0
this.naturalWidth = 0
this.width = 0
this.height = 0
this.tagName = 'IMG'
}
set src(src) {
this.currentSrc = src
uni.getImageInfo({
src,
success: (res) => {
this.naturalWidth = this.width = res.width
this.naturalHeight = this.height = res.height
this.onload()
},
fail: () => {
this.onerror()
}
})
}
get src() {
return this.currentSrc
}
}
class OffscreenCanvas {
constructor(ctx, com, canvasId) {
this.tagName = 'canvas'
this.com = com
this.canvasId = canvasId
this.ctx = ctx
}
set width(w) {
this.com.offscreenWidth = w
}
set height(h) {
this.com.offscreenHeight = h
}
get width() {
return this.com.offscreenWidth || 0
}
get height() {
return this.com.offscreenHeight || 0
}
getContext(type) {
return this.ctx
}
getImageData() {
return new Promise((resolve, reject) => {
this.com.$nextTick(() => {
uni.canvasGetImageData({
x:0,
y:0,
width: this.com.offscreenWidth,
height: this.com.offscreenHeight,
canvasId: this.canvasId,
success: (res) => {
resolve(res)
},
fail: (err) => {
reject(err)
},
}, this.com)
})
})
}
}
export class Canvas {
constructor(ctx, com, isNew, canvasNode={}) {
cacheChart[com.canvasId] = {ctx}
this.canvasId = com.canvasId;
this.chart = null;
this.isNew = isNew
this.tagName = 'canvas'
this.canvasNode = canvasNode;
this.com = com;
if (!isNew) {
this._initStyle(ctx)
}
this._initEvent();
this._ee = new EventEmit()
}
getContext(type) {
if (type === '2d') {
return this.ctx;
}
}
setAttribute(key, value) {
if(key === 'aria-label') {
this.com['ariaLabel'] = value
}
}
setChart(chart) {
this.chart = chart;
}
createOffscreenCanvas(param){
if(!this.children) {
// this.com.isOffscreenCanvas = true
// this.com.offscreenWidth = param.width||300
// this.com.offscreenHeight = param.height||300
// const com = this.com
// const canvasId = this.com.offscreenCanvasId
// const context = uni.createCanvasContext(canvasId, this.com)
// this._initStyle(context)
// this.children = new OffscreenCanvas(context, com, canvasId)
}
return this.children
}
appendChild(child) {
console.log('child', child)
}
dispatchEvent(type, e) {
if(typeof type == 'object') {
this._ee.emit(type.type, type);
} else {
this._ee.emit(type, e);
}
return true
}
attachEvent() {
}
detachEvent() {
}
addEventListener(type, listener) {
this._ee.on(type, listener)
}
removeEventListener(type, listener) {
this._ee.off(type, listener)
}
_initCanvas(zrender, ctx) {
// zrender.util.getContext = function() {
// return ctx;
// };
// zrender.util.$override('measureText', function(text, font) {
// ctx.font = font || '12px sans-serif';
// return ctx.measureText(text, font);
// });
}
_initStyle(ctx, child) {
const styles = [
'fillStyle',
'strokeStyle',
'fontSize',
'globalAlpha',
'opacity',
'textAlign',
'textBaseline',
'shadow',
'lineWidth',
'lineCap',
'lineJoin',
'lineDash',
'miterLimit',
// #ifdef H5 || APP
'font',
// #endif
];
const colorReg = /#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])\b/g;
styles.forEach(style => {
Object.defineProperty(ctx, style, {
set: value => {
// #ifdef H5 || APP
if (style === 'font' && fontSizeReg.test(value)) {
const match = fontSizeReg.exec(value);
ctx.setFontSize(match[1]);
return;
}
// #endif
if (style === 'opacity') {
ctx.setGlobalAlpha(value)
return;
}
if (style !== 'fillStyle' && style !== 'strokeStyle' || value !== 'none' && value !== null) {
// #ifdef H5 || APP-PLUS || MP-BAIDU
if(typeof value == 'object') {
if (value.hasOwnProperty('colorStop') || value.hasOwnProperty('colors')) {
ctx['set' + style.charAt(0).toUpperCase() + style.slice(1)](value);
}
return
}
// #endif
// #ifdef MP-TOUTIAO
if(colorReg.test(value)) {
value = value.replace(colorReg, '#$1$1$2$2$3$3')
}
// #endif
ctx['set' + style.charAt(0).toUpperCase() + style.slice(1)](value);
}
}
});
});
if(!this.isNew && !child) {
ctx.uniDrawImage = ctx.drawImage
ctx.drawImage = (...a) => {
a[0] = a[0].src
ctx.uniDrawImage(...a)
}
}
if(!ctx.createRadialGradient) {
ctx.createRadialGradient = function() {
return ctx.createCircularGradient(...[...arguments].slice(-3))
};
}
// 字节不支持
if (!ctx.strokeText) {
ctx.strokeText = (...a) => {
ctx.fillText(...a)
}
}
// 钉钉不支持 , 鸿蒙是异步
if (!ctx.measureText || getDeviceInfo().osName == 'harmonyos') {
ctx._measureText = ctx.measureText
const strLen = (str) => {
let len = 0;
for (let i = 0; i < str.length; i++) {
if (str.charCodeAt(i) > 0 && str.charCodeAt(i) < 128) {
len++;
} else {
len += 2;
}
}
return len;
}
ctx.measureText = (text, font) => {
let fontSize = ctx?.state?.fontSize || 12;
if (font) {
fontSize = parseInt(font.match(/([\d\.]+)px/)[1])
}
fontSize /= 2;
let isBold = fontSize >= 16;
const widthFactor = isBold ? 1.3 : 1;
// ctx._measureText(text, (res) => {})
return {
width: strLen(text) * fontSize * widthFactor
};
}
}
}
_initEvent(e) {
this.event = {};
const eventNames = [{
wxName: 'touchStart',
ecName: 'mousedown'
}, {
wxName: 'touchMove',
ecName: 'mousemove'
}, {
wxName: 'touchEnd',
ecName: 'mouseup'
}, {
wxName: 'touchEnd',
ecName: 'click'
}];
eventNames.forEach(name => {
this.event[name.wxName] = e => {
const touch = e.touches[0];
this.chart.getZr().handler.dispatch(name.ecName, {
zrX: name.wxName === 'tap' ? touch.clientX : touch.x,
zrY: name.wxName === 'tap' ? touch.clientY : touch.y
});
};
});
}
set width(w) {
this.canvasNode.width = w
}
set height(h) {
this.canvasNode.height = h
}
get width() {
return this.canvasNode.width || 0
}
get height() {
return this.canvasNode.height || 0
}
get ctx() {
return cacheChart[this.canvasId]['ctx'] || null
}
set chart(chart) {
cacheChart[this.canvasId]['chart'] = chart
}
get chart() {
return cacheChart[this.canvasId]['chart'] || null
}
}
export function dispatch(name, {x,y, wheelDelta}) {
this.dispatch(name, {
zrX: x,
zrY: y,
zrDelta: wheelDelta,
preventDefault: () => {},
stopPropagation: () =>{}
});
}
export function setCanvasCreator(echarts, {canvas, node}) {
if(echarts && !echarts.registerPreprocessor) {
return console.warn('echarts 版本不对或未传入echartsvue3请使用esm格式')
}
echarts.registerPreprocessor(option => {
if (option && option.series) {
if (option.series.length > 0) {
option.series.forEach(series => {
series.progressive = 0;
});
} else if (typeof option.series === 'object') {
option.series.progressive = 0;
}
}
});
function loadImage(src, onload, onerror) {
let img = null
if(node && node.createImage) {
img = node.createImage()
img.onload = onload.bind(img);
img.onerror = onerror.bind(img);
img.src = src;
return img
} else {
img = new Image()
img.onload = onload.bind(img)
img.onerror = onerror.bind(img);
img.src = src
return img
}
}
if(echarts.setPlatformAPI) {
echarts.setPlatformAPI({
loadImage: canvas.setChart ? loadImage : null,
createCanvas(){
const key = 'createOffscreenCanvas'
return uni.canIUse(key) && uni[key] ? uni[key]({type: '2d'}) : canvas
}
})
} else if(echarts.setCanvasCreator) {
echarts.setCanvasCreator(() => {
return canvas;
});
}
}

View File

@ -0,0 +1,322 @@
<template>
<!-- #ifdef APP -->
<web-view class="lime-echart" ref="chartRef" @load="loaded" :style="[lStyle]" :webview-styles="[webviewStyles]"
src="/uni_modules/lime-echart/static/app/uvue.html?v=10112">
</web-view>
<!-- #endif -->
<!-- #ifdef WEB -->
<div class="lime-echart" ref="chartRef"></div>
<!-- #endif -->
<!-- #ifndef WEB || APP-->
<view class="lime-echart">
<canvas style="width:100%; height:100%" v-if="canvasid" :id="canvasid" @touchstart="touchstart" @touchmove="touchmove" @touchend="touchend"></canvas>
</view>
<!-- #endif -->
</template>
<script lang="uts" setup>
// @ts-nocheck
import { echartsProps } from './type';
import { Echarts } from './uvue';
// #ifdef WEB
import { dispatch } from './canvas';
import * as echartsLibrary from '@/uni_modules/lime-echart/static/web/echarts.esm.min.js';
// #endif
// #ifndef APP || WEB
import { Canvas, setCanvasCreator, dispatch } from './canvas';
import { wrapTouch, convertTouchesToArray, devicePixelRatio, sleep, canIUseCanvas2d, getRect } from './utils';
// #endif
type EChartsResolveCallback = (value : Echarts) => void
const emits = defineEmits(['finished'])
const props = withDefaults(defineProps<echartsProps>(), {
isDisableScroll: false,
isClickable: true,
autoHideTooltip: false,
enableHover: false,
landscape: false,
beforeDelay: 30,
})
const instance = getCurrentInstance()!;
const canvasid = `lime-echart-${instance.uid}`
const finished = ref(false)
const initializationQueue = [] as EChartsResolveCallback[]
const callbackQueue = [] as EChartsResolveCallback[]
// let context = null as UniWebViewElement | null
let chartInstance = null as Echarts | null
let chartRef = ref<UniWebViewElement | null>(null)
let canvasNode:any|null = null
const processInitializationQueue = () => {
// #ifdef APP
if (finished.value) {
if (chartInstance == null) {
chartInstance = new Echarts(chartRef.value!)
}
while (initializationQueue.length > 0) {
const resolve = initializationQueue.pop() as EChartsResolveCallback
resolve(chartInstance!)
}
}
// #endif
// #ifndef APP
while (initializationQueue.length > 0) {
if (chartInstance != null) {
const resolve = initializationQueue.pop() as EChartsResolveCallback
resolve(chartInstance!)
}
}
// #endif
if (chartInstance != null) {
while (callbackQueue.length > 0) {
const callback = callbackQueue.pop() as EChartsResolveCallback
callback(chartInstance!)
}
}
}
// #ifdef APP
const loaded = (event : UniWebViewLoadEvent) => {
event.stopPropagation()
event.preventDefault()
nextTick(()=> {
chartRef.value?.getBoundingClientRectAsync()?.then(res => {
if(res.width > 0 && res.height > 0) {
finished.value = true
processInitializationQueue()
emits('finished')
} else {
console.warn('【lime-echart】获取尺寸失败请检查代码样式')
}
})
})
}
// #endif
const checkInitialization = () : boolean => {
if (chartInstance == null) {
console.warn(`组件还未初始化,请先使用 init`)
return true
}
return false
}
const setOption = (option : UTSJSONObject) => {
if (checkInitialization()) return
chartInstance!.setOption(option);
}
const showLoading = () => {
if (checkInitialization()) return
chartInstance!.showLoading();
}
const hideLoading = () => {
if (checkInitialization()) return
chartInstance!.hideLoading();
}
const clear = () => {
if (checkInitialization()) return
chartInstance!.clear();
}
const dispose = () => {
if (checkInitialization()) return
chartInstance!.dispose();
}
const resize = (size : UTSJSONObject) => {
if (checkInitialization()) return
chartInstance!.resize(size);
}
const canvasToTempFilePath = (opt : UTSJSONObject) => {
if (checkInitialization()) return
// #ifdef APP
chartInstance!.canvasToTempFilePath(opt);
// #endif
// #ifdef WEB
opt.success?.({
// @ts-ignore
tempFilePath: chartInstance!._api.getDataURL()
})
// #endif
// #ifndef WEB || APP
if(canvasNode) {
opt.success?.({
tempFilePath: canvasNode.toDataURL()
})
} else {
uni.canvasToTempFilePath({
...opt,
canvasId
}, instance.proxy);
}
// #endif
}
// #ifdef APP
function init(callback : ((chartInstance : Echarts) => void) | null) : Promise<Echarts> {
if (callback != null) {
callbackQueue.push(callback)
}
return new Promise<Echarts>((resolve) => {
initializationQueue.push(resolve)
processInitializationQueue()
})
}
// #endif
// #ifndef APP
// #ifndef WEB
let use2dCanvas = canIUseCanvas2d()
const getContext = async () => {
return new Promise((resolve, reject)=>{
uni.createCanvasContextAsync({
id: canvasid,
component: instance.proxy!,
success: (context : CanvasContext) => {
canvasNode = context
const canvasContext = context.getContext('2d')!;
const canvas = canvasContext.canvas;
let uniCanvas;
const width = canvas.offsetWidth
const height = canvas.offsetHeight
// 处理高清屏逻辑
const dpr = devicePixelRatio//uni.getDeviceInfo().devicePixelRatio ?? 1;
canvas.width = canvas.offsetWidth * dpr;
canvas.height = canvas.offsetHeight * dpr;
canvasContext.scale(dpr, dpr); // 仅需调用一次,当调用 reset 方法后需要再次 scale
if(use2dCanvas) {
uniCanvas = new Canvas(canvasContext, instance.proxy, true, context);
} else {
uniCanvas = new Canvas(canvasContext, instance.proxy, false);
}
resolve({ canvas: uniCanvas, width, height, devicePixelRatio: dpr, node: context});
},
fail(err) {
reject(err)
console.log('err', err)
}
})
})
}
// #endif
const getTouch = (e) => {
const touches = e.touches[0]
// #ifdef WEB
// @ts-ignore
const rect = chart!.getZr().dom.getBoundingClientRect();
const touch = {
x: touches.clientX - rect.left,
y: touches.clientY - rect.top
}
// #endif
// #ifndef WEB
const touch = {
x: touches.x,
y: touches.y
}
// #endif
return touch
}
const handleTouchStart = (e) => {
if (chartInstance == null) return
const handler = chartInstance.getZr().handler;
const touch = getTouch(e)
dispatch.call(handler, 'mousedown', touch)
dispatch.call(handler, 'mousemove', touch)
handler.processGesture(wrapTouch(e), 'start');
}
const handleTouchMove = (e) => {
if (chartInstance == null) return
const handler = chartInstance.getZr().handler;
const touch = getTouch(e)
dispatch.call(handler, 'mousemove', touch)
handler.processGesture(wrapTouch(e), 'change');
}
const handleTouchEnd = (e) => {
if (chartInstance == null) return
const handler = chartInstance.getZr().handler;
const touch = e.changedTouches ? e.changedTouches[0] : {}
handler.processGesture(wrapTouch(e), 'end');
dispatch.call(handler, 'mouseup', touch)
dispatch.call(handler, 'click', touch)
}
async function init(echarts : any, ...args : any[]) : Promise<Echarts> {
const library = echarts || echartsLibrary
if (library == null) {
console.error('请确保已经引入了 ECharts 库');
return Promise.reject('请确保已经引入了 ECharts 库');
}
let theme : string | null = null
let opts = {}
let callback : Function | null = null;
args.forEach(item => {
if (typeof item === 'function') {
callback = item
} else if (['string'].includes(typeof item)) {
theme = item
} else if (typeof item === 'object') {
opts = item
}
})
// #ifdef WEB
library.env.domSupported = true
library.env.hasGlobalWindow = true
library.env.node = false
library.env.pointerEventsSupported = false
library.env.svgSupported = true
library.env.touchEventsSupported = true
library.env.transform3dSupported = true
library.env.transformSupported = true
library.env.worker = false
library.env.wxa = false
chartInstance = library.init(chartRef.value, theme, opts)
// window.addEventListener('touchstart', touchstart)
// window.addEventListener('touchmove', touchmove)
// window.addEventListener('touchend', touchend)
// #endif
// #ifndef WEB
let config = await getContext();
setCanvasCreator(library, config)
chartInstance = library.init(config.canvas, theme, Object.assign({}, config, opts))
// #endif
if (callback != null && typeof callback == 'function') {
callbackQueue.push(callback)
}
return new Promise<Echarts>((resolve) => {
initializationQueue.push(resolve)
processInitializationQueue()
})
}
onMounted(() => {
nextTick(() => {
finished.value = true
processInitializationQueue()
emits('finished')
})
})
onUnmounted(() => {
// #ifdef WEB
// window.removeEventListener('touchstart', touchstart)
// window.removeEventListener('touchmove', touchmove)
// window.removeEventListener('touchend', touchend)
// #endif
})
// #endif
defineExpose({
init,
setOption,
showLoading,
hideLoading,
clear,
dispose,
resize,
canvasToTempFilePath
})
</script>
<style lang="scss">
.lime-echart {
flex: 1;
width: 100%;
}
</style>

View File

@ -0,0 +1,484 @@
<template>
<!-- #ifndef APP-NVUE || WEB -->
<view class="lime-echart"
:style="[lStyle]"
v-if="canvasId"
ref="chartContainer"
:aria-label="'图表'">
<canvas class="lime-echart__canvas"
type="2d"
:style="[styles]"
:id="canvasId"
:disable-scroll="isDisableScroll"
:canvas-id="canvasId"
@touchstart="handleTouchStart"
@touchmove="handleTouchMove"
@touchend="handleTouchEnd">
</canvas>
</view>
<!-- #endif -->
<!-- #ifdef WEB -->
<div class="lime-echart" ref="chartContainer" :style="[styles, lStyle]"></div>
<!-- #endif -->
<!-- #ifdef APP-NVUE -->
<view class="lime-echart" :style="[lStyle]">
<web-view class="lime-echart__canvas"
:webview-styles="webviewStyles"
:style="[styles]"
ref="chartContainer"
src="/uni_modules/lime-echart/static/app/uvue.html?v=1"
@pagefinish="isInitialized = true"
@onPostMessage="handleWebviewMessage"></web-view>
</view>
<!-- #endif -->
</template>
<script lang="ts">
// @ts-nocheck
import { defineComponent, getCurrentInstance, ref, onMounted, nextTick, onBeforeUnmount, watch, computed } from './vue'
import echartProps from './props'
// #ifndef APP-NVUE || WEB
import { Canvas, setCanvasCreator, dispatch } from './canvas';
import { wrapTouch, convertTouchesToArray, devicePixelRatio ,sleep, canIUseCanvas2d, getRect, getDeviceInfo } from './utils';
// #endif
// #ifdef APP-NVUE
import { base64ToPath, sleep } from './utils';
import { Echarts } from './nvue'
// #endif
// #ifdef WEB
import * as echartsLibrary from '@/uni_modules/lime-echart/static/web/echarts.esm.min.js';
// #endif
// #ifdef APP-VUE
// #ifdef VUE3
import '@/uni_modules/lime-echart/static/app/echarts.min.js';
const echartsLibrary = globalThis.echarts
// #endif
// #ifdef VUE2
import * as echartsLibrary from '@/uni_modules/lime-echart/static/web/echarts.esm.min.js';
// #endif
// #endif
export default defineComponent({
props: echartProps,
emits: ['finished'],
setup(props, { emit, expose }) {
// #ifndef APP-NVUE || WEB || APP-VUE
let echartsLibrary = null
// #endif
const instance = getCurrentInstance()!;
const canvasId = `lime-echart-${instance.uid}`
const isInitialized = ref(false)
const chartContainer = ref(null)
type ChartOptions = Record<string, any>
type EChartsInstance = typeof echartsLibrary
type EChartsResolveCallback = (value: EChartsInstance) => void
const initializationQueue = [] as EChartsResolveCallback[]
const callbackQueue = [] as EChartsResolveCallback[]
let chartInstance: null | EChartsInstance = null
const styles = computed(()=> {
if(props.landscape) {
return {
transform: 'translate(-50%,-50%) rotate(90deg)',
top: '50%',
left: '50%',
}
}
return {}
})
const checkInitialization = (): boolean => {
if(chartInstance) return false
console.warn(`组件还未初始化,请先使用 init`)
return true
}
const setOption = (options: ChartOptions) => {
if (checkInitialization()) return
chartInstance!.setOption(options);
}
const hideLoading = () => {
if (checkInitialization()) return
chartInstance!.showLoading();
}
const showLoading = () => {
if (checkInitialization()) return
chartInstance!.hideLoading();
}
const clear = () => {
if (checkInitialization()) return
chartInstance!.clear();
}
const dispose = () => {
if (checkInitialization()) return
chartInstance!.dispose();
}
const processInitializationQueue = () => {
while (initializationQueue.length > 0) {
if (chartInstance != null) {
const resolve = initializationQueue.pop() as EChartsResolveCallback
resolve(chartInstance!)
}
}
if (chartInstance != null) {
while (callbackQueue.length > 0) {
const callback = callbackQueue.pop() as EChartsResolveCallback
callback(chartInstance!)
}
}
}
const resize = (dimensions?: { width?: number; height?: number }) => {
if (checkInitialization()) return
// #ifdef APP-NVUE || WEB
chartInstance!.resize(dimensions);
// #endif
// #ifndef APP-NVUE || WEB
getRect(`#${canvasId}`, instance.proxy).then(res => {
chartInstance!.resize({width: res.width, height: res.height});
})
// #endif
}
// #ifdef APP-NVUE
let chartFile = ref(null);
const handleWebviewMessage = (e) => {
const detail = e?.detail?.data[0] || null;
const data = detail?.data
const key = detail?.event
const options = data?.options
const event = data?.event
const file = detail?.file
if (key == 'log' && data) {
console.log(data)
}
if(event) {
chartInstance.dispatchAction(event.replace(/"/g,''), options)
}
if(file) {
chartFile.value = file
}
}
const canvasToTempFilePath = (options: ChartOptions) => {
if (checkInitialization()) return
chartContainer.value.evalJs(`canvasToTempFilePath()`);
watch(chartFile, async (file) =>{
if(!file) return
const tempFilePath = await base64ToPath(file)
options.success({tempFilePath})
})
}
const getContext = () => {
if(isInitialized.value) {
return Promise.resolve(isInitialized.value)
}
return new Promise(resolve => {
watch(isInitialized, (val) =>{
if(!val) return
resolve(val)
})
})
}
const init = async (echarts, ...args) => {
let theme: string | null = null
let config:Record<string, any> = {}
let callback: Function | null = null;
args.forEach(item => {
if (typeof item === 'function') {
callback = item
} else if (typeof item === 'string') {
theme = item
} else if (typeof item === 'object') {
config = item
}
})
if(props.beforeDelay) {
await sleep(props.beforeDelay)
}
await getContext();
chartInstance = new Echarts(chartContainer.value)
chartContainer.value.evalJs(`init(null, null, ${JSON.stringify(config)}, ${theme})`)
if (callback && typeof callback === 'function') {
callbackQueue.push(callback)
}
return new Promise<EChartsInstance>((resolve) => {
nextTick(()=>{
initializationQueue.push(resolve)
processInitializationQueue()
})
})
}
// #endif
// #ifndef APP-NVUE || WEB
let canvasNode;
const canvasToTempFilePath = (options: ChartOptions) => {
if (checkInitialization()) return
if(canvasNode) {
options.success?.({
tempFilePath: canvasNode.toDataURL()
})
} else {
uni.canvasToTempFilePath({
...options,
canvasId
}, instance.proxy);
}
}
const getContext = () => {
return getRect(`#${canvasId}`, instance.proxy).then(res => {
let dpr = devicePixelRatio
let {width, height, node} = res
let canvas: Canvas | null = null;
if(!(width || height)) {
return Promise.reject('no rect')
}
if(node && node.getContext) {
const ctx = node.getContext('2d');
canvas = new Canvas(ctx, instance.proxy, true, node);
canvasNode = node
} else {
dpr = 1
const ctx = uni.createCanvasContext(canvasId, instance.proxy);
canvas = new Canvas(ctx, instance.proxy, false);
}
return { canvas, width, height, devicePixelRatio: dpr, node }
})
}
const getTouch = (e) => {
const touches = e.touches[0]
const touch = props.landscape
? {
x: touches.y,
y: touches.x
}
: {
x: touches.x,
y: touches.y
}
return touch
}
const handleTouchStart = (e) => {
if (chartInstance == null) return
const handler = chartInstance.getZr().handler;
const touch = getTouch(e)
dispatch.call(handler, 'mousedown', touch)
dispatch.call(handler, 'mousemove', touch)
handler.processGesture(wrapTouch(e), 'start');
}
const handleTouchMove = (e) => {
if (chartInstance == null) return
const handler = chartInstance.getZr().handler;
const touch = getTouch(e)
dispatch.call(handler, 'mousemove', touch)
handler.processGesture(wrapTouch(e), 'change');
}
const handleTouchEnd = (e) => {
if (chartInstance == null) return
const handler = chartInstance.getZr().handler;
const touch = e.changedTouches ? e.changedTouches[0] : {}
handler.processGesture(wrapTouch(e), 'end');
dispatch.call(handler, 'mouseup', touch)
dispatch.call(handler, 'click', touch)
}
const init = async (echartsLib: EChartsInstance = echartsLibrary, ...args: any[]): Promise<EChartsInstance> => {
const library = echartsLib || echartsLibrary
if (!library) {
console.error('ECharts library is required');
return Promise.reject('ECharts library is required');
}
let theme: string | null = null
let config:Record<string, any> = {}
let callback: Function | null = null;
args.forEach(item => {
if (typeof item === 'function') {
callback = item
} else if (typeof item === 'string') {
theme = item
} else if (typeof item === 'object') {
config = item
}
})
if(props.beforeDelay) {
await sleep(props.beforeDelay)
}
let options = await getContext();
setCanvasCreator(library, options)
chartInstance = library.init(options.canvas, theme, Object.assign({}, options, config))
if (callback && typeof callback === 'function') {
callbackQueue.push(callback)
}
return new Promise<EChartsInstance>((resolve) => {
initializationQueue.push(resolve)
processInitializationQueue()
})
}
// #endif
// #ifdef WEB
const canvasToTempFilePath = (options: ChartOptions) => {
if (checkInitialization()) return
options.success?.({
tempFilePath: chartInstance._api.getDataURL()
})
}
const init = async (echarts: EChartsInstance = echartsLibrary, ...args: any[]): Promise<EChartsInstance> => {
const library = echarts || echartsLibrary
if (!library) {
console.error('ECharts library is required');
return Promise.reject('ECharts library is required');
}
let theme: string | null = null
let config = {}
let callback: Function | null = null;
args.forEach(item => {
if (typeof item === 'function') {
callback = item
} else if (typeof item === 'string') {
theme = item
} else if (typeof item === 'object') {
config = item
}
})
// Configure ECharts environment
library.env.domSupported = true
library.env.hasGlobalWindow = true
library.env.node = false
library.env.pointerEventsSupported = false
library.env.svgSupported = true
library.env.touchEventsSupported = true
library.env.transform3dSupported = true
library.env.transformSupported = true
library.env.worker = false
library.env.wxa = false
chartInstance = library.init(chartContainer.value, theme, config)
if (callback != null && typeof callback === 'function') {
callbackQueue.push(callback)
}
return new Promise<EChartsInstance>((resolve) => {
initializationQueue.push(resolve)
processInitializationQueue()
})
}
// #endif
onMounted(() => {
nextTick(() => {
// #ifndef APP-NVUE
isInitialized.value = true
// #endif
emit('finished')
processInitializationQueue()
})
})
onBeforeUnmount(()=> {
clear()
dispose()
})
// #ifdef VUE3
expose({
init,
setOption,
hideLoading,
showLoading,
clear,
dispose,
resize,
canvasToTempFilePath
})
// #endif
return {
canvasId,
chartContainer,
styles,
// #ifndef WEB || APP-NVUE
handleTouchStart,
handleTouchMove,
handleTouchEnd,
// #endif
// #ifdef APP-NVUE
handleWebviewMessage,
isInitialized,
// #endif
// #ifdef VUE2
init,
setOption,
hideLoading,
showLoading,
clear,
dispose,
resize,
canvasToTempFilePath,
// #endif
}
}
})
</script>
<style>
.lime-echart {
position: relative;
/* #ifndef APP-NVUE */
width: 100%;
height: 100%;
/* #endif */
/* #ifdef APP-NVUE */
flex: 1;
/* #endif */
}
.lime-echart__canvas {
/* #ifndef APP-NVUE */
width: 100%;
height: 100%;
/* #endif */
/* #ifdef APP-NVUE */
flex: 1;
/* #endif */
}
/* #ifndef APP-NVUE */
.lime-echart__mask {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
z-index: 1;
}
/* #endif */
</style>

View File

@ -0,0 +1,56 @@
export class Echarts {
eventMap = new Map()
constructor(webview) {
this.webview = webview
this.options = null
}
setOption() {
this.options = arguments
console.log('setOption1')
this.webview.evalJs(`setOption(${JSON.stringify(arguments)})`);
}
getOption() {
return this.options
}
showLoading() {
this.webview.evalJs(`showLoading(${JSON.stringify(arguments)})`);
}
hideLoading() {
this.webview.evalJs(`hideLoading()`);
}
clear() {
this.webview.evalJs(`clear()`);
}
dispose() {
this.webview.evalJs(`dispose()`);
}
resize(size) {
if(size) {
this.webview.evalJs(`resize(${JSON.stringify(size)})`);
} else {
this.webview.evalJs(`resize()`);
}
}
on(type, ...args) {
const query = args[0]
const useQuery = query && typeof query != 'function'
const param = useQuery ? [type, query] : [type]
const key = `${type}${useQuery ? JSON.stringify(query): '' }`
const callback = useQuery ? args[1]: args[0]
if(typeof callback == 'function'){
this.eventMap.set(key, callback)
}
this.webview.evalJs(`on(${JSON.stringify(param)})`);
console.warn('nvue 暂不支持事件')
}
dispatchAction(type, options){
const handler = this.eventMap.get(type)
if(handler){
handler(options)
}
}
// 不让报错 无实际作用
isDisposed() {
return !!this.webview
}
}

View File

@ -0,0 +1,27 @@
export default {
// #ifdef MP-WEIXIN || MP-TOUTIAO
type: {
type: String,
default: '2d'
},
// #endif
// #ifdef APP-NVUE
webviewStyles: Object,
// #endif
lStyle: String,
isDisableScroll: Boolean,
isClickable: {
type: Boolean,
default: true
},
enableHover: Boolean,
beforeDelay: {
type: Number,
default: 30
},
landscape: Boolean,
autoHideTooltip: {
type: Boolean,
default: false
}
}

View File

@ -0,0 +1,11 @@
// @ts-nocheck
export interface echartsProps {
webviewStyles?: UTSJSONObject,
lStyle?: string | UTSJSONObject
isDisableScroll: boolean;
isClickable: boolean;
enableHover: boolean;
beforeDelay: number;
landscape: boolean;
autoHideTooltip: boolean;
}

View File

@ -0,0 +1,181 @@
// @ts-nocheck
/**
* 获取设备基础信息
*
* @see [uni.getDeviceInfo](https://uniapp.dcloud.net.cn/api/system/getDeviceInfo.html)
*/
export function getDeviceInfo() {
if (uni.getDeviceInfo || uni.canIUse('getDeviceInfo')) {
return uni.getDeviceInfo();
} else {
return uni.getSystemInfoSync();
}
}
/**
* 获取窗口信息
*
* @see [uni.getWindowInfo](https://uniapp.dcloud.net.cn/api/system/getWindowInfo.html)
*/
export function getWindowInfo() {
if (uni.getWindowInfo || uni.canIUse('getWindowInfo')) {
return uni.getWindowInfo();
} else {
return uni.getSystemInfoSync();
}
}
/**
* 获取APP基础信息
*
* @see [uni.getAppBaseInfo](https://uniapp.dcloud.net.cn/api/system/getAppBaseInfo.html)
*/
export function getAppBaseInfo() {
if (uni.getAppBaseInfo || uni.canIUse('getAppBaseInfo')) {
return uni.getAppBaseInfo();
} else {
return uni.getSystemInfoSync();
}
}
// #ifndef APP-NVUE
// 计算版本
export function compareVersion(v1, v2) {
v1 = v1.split('.')
v2 = v2.split('.')
const len = Math.max(v1.length, v2.length)
while (v1.length < len) {
v1.push('0')
}
while (v2.length < len) {
v2.push('0')
}
for (let i = 0; i < len; i++) {
const num1 = parseInt(v1[i], 10)
const num2 = parseInt(v2[i], 10)
if (num1 > num2) {
return 1
} else if (num1 < num2) {
return -1
}
}
return 0
}
// const systemInfo = uni.getSystemInfoSync();
function gte(version) {
// 截止 2023-03-22 mac pc小程序不支持 canvas 2d
// let {
// SDKVersion,
// platform
// } = systemInfo;
const { platform } = getDeviceInfo();
let { SDKVersion } = getAppBaseInfo();
// #ifdef MP-ALIPAY
SDKVersion = my.SDKVersion
// #endif
// #ifdef MP-WEIXIN
return platform !== 'mac' && compareVersion(SDKVersion, version) >= 0;
// #endif
return compareVersion(SDKVersion, version) >= 0;
}
export function canIUseCanvas2d() {
// #ifdef MP-WEIXIN
return gte('2.9.0');
// #endif
// #ifdef MP-ALIPAY
return gte('2.7.0');
// #endif
// #ifdef MP-TOUTIAO
return gte('1.78.0');
// #endif
return false
}
export function convertTouchesToArray(touches) {
// 如果 touches 是一个数组,则直接返回它
if (Array.isArray(touches)) {
return touches;
}
// 如果touches是一个对象则转换为数组
if (typeof touches === 'object' && touches !== null) {
return Object.values(touches);
}
// 对于其他类型,直接返回它
return touches;
}
export function wrapTouch(event) {
event.touches = convertTouchesToArray(event.touches)
for (let i = 0; i < event.touches.length; ++i) {
const touch = event.touches[i];
touch.offsetX = touch.x;
touch.offsetY = touch.y;
}
return event;
}
// export const devicePixelRatio = uni.getSystemInfoSync().pixelRatio
export const devicePixelRatio = getWindowInfo().pixelRatio;
// #endif
// #ifdef APP-NVUE
export function base64ToPath(base64) {
return new Promise((resolve, reject) => {
const [, format, bodyData] = /data:image\/(\w+);base64,(.*)/.exec(base64) || [];
const bitmap = new plus.nativeObj.Bitmap('bitmap' + Date.now())
bitmap.loadBase64Data(base64, () => {
if (!format) {
reject(new Error('ERROR_BASE64SRC_PARSE'))
}
const time = new Date().getTime();
const filePath = `_doc/uniapp_temp/${time}.${format}`
bitmap.save(filePath, {},
() => {
bitmap.clear()
resolve(filePath)
},
(error) => {
bitmap.clear()
console.error(`${JSON.stringify(error)}`)
reject(error)
})
}, (error) => {
bitmap.clear()
console.error(`${JSON.stringify(error)}`)
reject(error)
})
})
}
// #endif
export function sleep(time) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true)
}, time)
})
}
export function getRect(selector, context) {
return new Promise((resolve, reject) => {
const dom = uni.createSelectorQuery().in(context).select(selector);
const result = (rect) => {
if (rect) {
resolve(rect)
} else {
reject()
}
}
dom.fields({
node: true,
size: true,
rect: true
}, result).exec()
});
};

View File

@ -0,0 +1,136 @@
// @ts-nocheck
// #ifdef APP
type EchartsEventHandler = (event: UTSJSONObject)=>void
// type EchartsTempResolve = (obj : UTSJSONObject) => void
// type EchartsTempOptions = UTSJSONObject
export class Echarts {
options: UTSJSONObject = {} as UTSJSONObject
context: UniWebViewElement
eventMap: Map<string, EchartsEventHandler> = new Map()
private temp: UTSJSONObject[] = []
constructor(context: UniWebViewElement){
this.context = context
this.init()
}
init(){
this.context.evalJS(`init(null, null, ${JSON.stringify({})})`)
this.context.addEventListener('message', (e : UniWebViewMessageEvent) => {
// event.stopPropagation()
// event.preventDefault()
const detail = e.detail.data[0]
const file = detail.getString('file')
const data = detail.get('data')
const key = detail.getString('event')
const options = typeof data == 'object' ? (data as UTSJSONObject).getJSON('options'): null
const event = typeof data == 'object' ? (data as UTSJSONObject).getString('event'): null
if (key == 'log' && data != null) {
console.log(data)
}
if (event != null && options != null) {
this.dispatchAction(event.replace(/"/g,''), options)
}
if(file != null){
while (this.temp.length > 0) {
const opt = this.temp.pop()
const success = opt?.get('success')
if(typeof success == 'function'){
success as (res: UTSJSONObject) => void
success({tempFilePath: file})
}
}
}
})
}
setOption(option: UTSJSONObject){
this.options = option;
this.context.evalJS(`setOption(${JSON.stringify([option])})`)
}
setOption(option: UTSJSONObject, notMerge: boolean = false, lazyUpdate: boolean = false){
this.options = option;
this.context.evalJS(`setOption(${JSON.stringify([option, notMerge, lazyUpdate])})`)
}
setOption(option: UTSJSONObject, notMerge: UTSJSONObject){
this.options = option;
this.context.evalJS(`setOption(${JSON.stringify([option, notMerge])})`)
}
getOption(): UTSJSONObject {
return this.options
}
showLoading(){
this.context.evalJS(`showLoading(${JSON.stringify([] as any[])})`);
}
showLoading(type: string, opts: UTSJSONObject){
this.context.evalJS(`showLoading(${JSON.stringify([type, opts])})`);
}
hideLoading(){
this.context.evalJS(`hideLoading()`);
}
clear(){
this.context.evalJS(`clear()`);
}
dispose(){
this.context.evalJS(`dispose()`);
}
resize(size:UTSJSONObject){
setTimeout(()=>{
this.context.evalJS(`resize(${JSON.stringify(size)})`);
},0)
}
resize(){
setTimeout(()=>{
this.context.evalJS(`resize()`);
},10)
}
on(type:string, query: any, callback: EchartsEventHandler) {
const key = `${type}${JSON.stringify(query)}`
if(typeof callback == 'function'){
this.eventMap.set(key, callback)
}
this.context.evalJS(`on(${JSON.stringify([type, query])})`);
console.warn('uvue 暂不支持事件')
}
on(type:string, callback: EchartsEventHandler) {
const key = `${type}`
if(typeof callback == 'function'){
this.eventMap.set(key, callback)
}
this.context.evalJS(`on(${JSON.stringify([type])})`);
console.warn('uvue 暂不支持事件')
}
dispatchAction(type:string, options: UTSJSONObject){
const handler = this.eventMap.get(type)
if(handler!=null){
handler(options)
}
}
canvasToTempFilePath(opt: UTSJSONObject){
// this.context.evalJS(`on(${JSON.stringify(opt)})`);
this.context.evalJS(`canvasToTempFilePath(${JSON.stringify(opt)})`);
this.temp.push(opt)
}
isDisposed():boolean {
return false
}
}
// #endif
// #ifndef APP
export class Echarts {
constructor() {}
setOption(option: UTSJSONObject): void
isDisposed(): boolean;
clear(): void;
resize(size:UTSJSONObject): void;
resize(): void;
canvasToTempFilePath(opt : UTSJSONObject): void;
dispose(): void;
showLoading(cfg?: UTSJSONObject): void;
showLoading(name?: string, cfg?: UTSJSONObject): void;
hideLoading(): void;
getZr(): any
}
// #endif

View File

@ -0,0 +1,16 @@
// @ts-nocheck
// #ifdef VUE3
export * from 'vue';
// #endif
// #ifndef VUE3
export * from '@vue/composition-api';
// #ifdef APP-NVUE
import Vue from 'vue'
import VueCompositionAPI from '@vue/composition-api'
Vue.use(VueCompositionAPI)
// #endif
// #endif

View File

@ -0,0 +1,227 @@
<template>
<view>
<view style="height: 750rpx; position: relative">
<l-echart ref="chart" @finished="init"></l-echart>
<view
class="customTooltips"
:style="{ left: position[0] + 'px', top: position[1] + 'px' }"
v-if="params.length && position.length && showTip"
>
<view>这是个自定的tooltips</view>
<view>{{ params[0]['axisValue'] }}</view>
<view v-for="item in params">
<view>
<text>{{ item.seriesName }}</text>
<text>{{ item.value }}</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
// nvue
// #ifdef VUE2
import * as echarts from '@/uni_modules/lime-echart/static/echarts.min'
// #endif
// #ifdef VUE3
// #ifdef MP
// vue3 使vite umd使使require
const echarts = require('../../static/echarts.min')
// #endif
// #ifndef MP
// vue3 使vite umdnpm
import * as echarts from 'echarts/dist/echarts.esm'
// #endif
// #endif
export default {
data() {
return {
showTip: false,
position: [],
params: [],
option: {
tooltip: {
trigger: 'axis',
// shadowBlur: 0,
textStyle: {
textShadowBlur: 0,
},
renderMode: 'richText',
position: (point, params, dom, rect, size) => {
// tooltips
const box = [170, 170]
//
const offsetX = point[0] < size.viewSize[0] / 2 ? 20 : -box[0] - 20
const offsetY = point[1] < size.viewSize[1] / 2 ? 20 : -box[1] - 20
const x = point[0] + offsetX
const y = point[1] + offsetY
this.position = [x, y]
this.params = params
},
formatter: (params, ticket, callback) => {},
},
legend: {
data: ['邮件营销', '联盟广告', '视频广告', '直接访问', '搜索引擎'],
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true,
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
},
yAxis: {
type: 'value',
},
series: [
{
name: '邮件营销',
type: 'line',
stack: '总量',
data: [120, 132, 101, 134, 90, 230, 210],
},
{
name: '联盟广告',
type: 'line',
stack: '总量',
data: [220, 182, 191, 234, 290, 330, 310],
},
{
name: '视频广告',
type: 'line',
stack: '总量',
data: [150, 232, 201, 154, 190, 330, 410],
},
{
name: '直接访问',
type: 'line',
stack: '总量',
data: [320, 332, 301, 334, 390, 330, 320],
},
{
name: '搜索引擎',
type: 'line',
stack: '总量',
data: [820, 932, 901, 934, 1290, 1330, 1320],
},
],
},
}
},
methods: {
init() {
// init(echarts, theme?:string, opts?:{}, chart => {})
// echarts nvuenvue
// theme 'dark'
// opts = { //
// locale?: string // `5.0.0`
// }
// chart => {} callback
// setTimeout(()=>{
// this.$refs.chart.init(echarts, chart => {
// chart.setOption(this.option);
// });
// },300)
this.$refs.chart.init(echarts, (chart) => {
chart.setOption(this.option)
// tooltip
chart.on('showTip', (params) => {
this.showTip = true
console.log('showTip::')
})
chart.on('hideTip', (params) => {
setTimeout(() => {
this.showTip = false
}, 300)
})
setTimeout(() => {
const option = {
tooltip: {
trigger: 'axis',
// shadowBlur: 0,
textStyle: {
textShadowBlur: 0,
},
renderMode: 'richText',
},
legend: {
data: ['邮件营销', '联盟广告', '视频广告', '直接访问', '搜索引擎'],
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true,
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
},
yAxis: {
type: 'value',
},
series: [
{
name: '邮件营销',
type: 'line',
stack: '总量',
data: [1120, 132, 101, 134, 90, 230, 210],
},
{
name: '联盟广告',
type: 'line',
stack: '总量',
data: [220, 182, 191, 234, 290, 330, 310],
},
{
name: '视频广告',
type: 'line',
stack: '总量',
data: [150, 632, 201, 154, 190, 330, 410],
},
{
name: '直接访问',
type: 'line',
stack: '总量',
data: [820, 332, 301, 334, 390, 330, 320],
},
{
name: '搜索引擎',
type: 'line',
stack: '总量',
data: [820, 932, 901, 934, 1290, 1330, 1320],
},
],
}
chart.setOption(option)
}, 1000)
})
},
save() {
this.$refs.chart.canvasToTempFilePath({
success(res) {
console.log('res::::', res)
},
})
},
},
}
</script>
<style>
.customTooltips {
position: absolute;
background-color: rgba(255, 255, 255, 0.8);
padding: 20rpx;
}
</style>

View File

@ -0,0 +1,159 @@
<template>
<view style="width: 100%; height: 408px;">
<l-echart ref="chartRef" @finished="init"></l-echart>
</view>
</template>
<script>
export default {
data() {
return {
showTip: false,
option: {
tooltip: {
trigger: 'axis',
// shadowBlur: 0,
textStyle: {
textShadowBlur: 0
},
renderMode: 'richText',
},
legend: {
data: ['邮件营销', '联盟广告', '视频广告', '直接访问', '搜索引擎']
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
},
yAxis: {
type: 'value'
},
series: [
{
name: '邮件营销',
type: 'line',
stack: '总量',
data: [120, 132, 101, 134, 90, 230, 210]
},
{
name: '联盟广告',
type: 'line',
stack: '总量',
data: [220, 182, 191, 234, 290, 330, 310]
},
{
name: '视频广告',
type: 'line',
stack: '总量',
data: [150, 232, 201, 154, 190, 330, 410]
},
{
name: '直接访问',
type: 'line',
stack: '总量',
data: [320, 332, 301, 334, 390, 330, 320]
},
{
name: '搜索引擎',
type: 'line',
stack: '总量',
data: [820, 932, 901, 934, 1290, 1330, 1320]
}
]
}
}
},
mounted() {
console.log('lime echarts nvue')
},
methods: {
init() {
const chartRef = this.$refs['chartRef']
chartRef.init(chart => {
chart.setOption(this.option);
setTimeout(()=>{
const option = {
tooltip: {
trigger: 'axis',
// shadowBlur: 0,
textStyle: {
textShadowBlur: 0
},
renderMode: 'richText',
},
legend: {
data: ['邮件营销', '联盟广告', '视频广告', '直接访问', '搜索引擎']
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
},
yAxis: {
type: 'value'
},
series: [
{
name: '邮件营销',
type: 'line',
stack: '总量',
data: [120, 132, 101, 134, 90, 230, 210]
},
{
name: '联盟广告',
type: 'line',
stack: '总量',
data: [220, 182, 191, 234, 290, 330, 310]
},
{
name: '视频广告',
type: 'line',
stack: '总量',
data: [150, 232, 201, 154, 190, 330, 410]
},
{
name: '直接访问',
type: 'line',
stack: '总量',
data: [320, 332, 301, 334, 390, 330, 320]
},
{
name: '搜索引擎',
type: 'line',
stack: '总量',
data: [820, 932, 901, 934, 1290, 1330, 1320]
}
]
}
chart.setOption(option);
},1000)
})
},
save() {
// this.$refs.chart.canvasToTempFilePath({
// success(res) {
// console.log('res::::', res)
// }
// })
}
}
}
</script>
<style>
</style>

View File

@ -0,0 +1,158 @@
<template>
<view style="width: 100%; height: 408px;">
<l-echart ref="chartRef" @finished="init"></l-echart>
</view>
</template>
<script lang="uts" setup>
// #ifdef MP
// 引入小程序依赖包require只能是当前文件的相对路径
const echarts = require('../../../../static/echarts.min.js')
// #endif
// #ifndef MP
// 非小程序不需要引入
const echarts = null
// #endif
const chartRef = ref<LEchartComponentPublicInstance|null>(null)
const option = {
tooltip: {
trigger: 'axis',
// shadowBlur: 0,
textStyle: {
textShadowBlur: 0
},
renderMode: 'richText',
},
// formatter: async (params: any) => {
// console.log('params', params)
// return 1
// },
legend: {
data: ['邮件营销', '联盟广告', '视频广告', '直接访问', '搜索引擎']
},
// grid: {
// left: '3%',
// right: '4%',
// bottom: '3%',
// containLabel: true
// },
xAxis: {
type: 'category',
boundaryGap: false,
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
},
yAxis: {
type: 'value'
},
series: [
{
name: '邮件营销',
type: 'line',
stack: '总量',
data: [120, 132, 101, 134, 90, 230, 210]
},
{
name: '联盟广告',
type: 'line',
stack: '总量',
data: [220, 182, 191, 234, 290, 330, 310]
},
{
name: '视频广告',
type: 'line',
stack: '总量',
data: [150, 232, 201, 154, 190, 330, 410]
},
{
name: '直接访问',
type: 'line',
stack: '总量',
data: [320, 332, 301, 334, 390, 330, 320]
},
{
name: '搜索引擎',
type: 'line',
stack: '总量',
data: [820, 932, 901, 934, 1290, 1330, 1320]
}
]
}
const init = async () =>{
if(chartRef.value== null) return
const chart = await chartRef.value!.init(echarts)
chart.setOption(option)
// chart.on('mouseover', function (params) {
// console.log('params', params);
// });
// setTimeout(()=> {
// const option1 = {
// tooltip: {
// trigger: 'axis',
// // shadowBlur: 0,
// textStyle: {
// textShadowBlur: 0
// },
// renderMode: 'richText',
// },
// legend: {
// data: ['邮件营销', '联盟广告', '视频广告', '直接访问', '搜索引擎']
// },
// grid: {
// left: '3%',
// right: '4%',
// bottom: '3%',
// containLabel: true
// },
// xAxis: {
// type: 'category',
// boundaryGap: false,
// data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
// },
// yAxis: {
// type: 'value'
// },
// series: [
// {
// name: '邮件营销',
// type: 'line',
// stack: '总量',
// data: [820, 132, 101, 134, 90, 230, 210]
// },
// {
// name: '联盟广告',
// type: 'line',
// stack: '总量',
// data: [220, 182, 191, 234, 290, 330, 310]
// },
// {
// name: '视频广告',
// type: 'line',
// stack: '总量',
// data: [950, 232, 201, 154, 190, 330, 410]
// },
// {
// name: '直接访问',
// type: 'line',
// stack: '总量',
// data: [320, 332, 301, 334, 390, 330, 320]
// },
// {
// name: '搜索引擎',
// type: 'line',
// stack: '总量',
// data: [820, 932, 901, 934, 1290, 1330, 1320]
// }
// ]
// }
// chart.setOption(option1)
// },1000)
}
</script>
<style>
</style>

View File

@ -0,0 +1,152 @@
<template>
<view>
<view style="height: 750rpx; position: relative">
<l-echart ref="chart" @finished="init"></l-echart>
<view class="customTooltips" :style="{ left: position[0] + 'px', top: position[1] + 'px' }"
v-if="params.length && position.length && showTip">
<view>这是个自定的tooltips</view>
<view>{{ params[0]['axisValue'] }}</view>
<view v-for="item in params">
<view>
<text>{{ item.seriesName }}</text>
<text>{{ item.value }}</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
// #ifdef MP
// require
const echarts = require('../../../../static/echarts.min.js');
// #endif
// #ifndef MP
//
const echarts = null
// #endif
export default {
data() {
return {
showTip: false,
position: [],
params: [],
option: {
tooltip: {
trigger: 'axis',
// shadowBlur: 0,
textStyle: {
textShadowBlur: 0,
},
renderMode: 'richText',
position: (point, params, dom, rect, size) => {
// tooltips
const box = [170, 170]
//
const offsetX = point[0] < size.viewSize[0] / 2 ? 20 : -box[0] - 20
const offsetY = point[1] < size.viewSize[1] / 2 ? 20 : -box[1] - 20
const x = point[0] + offsetX
const y = point[1] + offsetY
this.position = [x, y]
this.params = params
},
formatter: (params, ticket, callback) => {},
},
legend: {
data: ['邮件营销', '联盟广告', '视频广告', '直接访问', '搜索引擎'],
},
// grid: {
// left: '3%',
// right: '4%',
// bottom: '3%',
// containLabel: true,
// },
xAxis: {
type: 'category',
boundaryGap: false,
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
},
yAxis: {
type: 'value',
},
series: [{
name: '邮件营销',
type: 'line',
stack: '总量',
data: [120, 132, 101, 134, 90, 230, 210],
},
{
name: '联盟广告',
type: 'line',
stack: '总量',
data: [220, 182, 191, 234, 290, 330, 310],
},
{
name: '视频广告',
type: 'line',
stack: '总量',
data: [150, 232, 201, 154, 190, 330, 410],
},
{
name: '直接访问',
type: 'line',
stack: '总量',
data: [320, 332, 301, 334, 390, 330, 320],
},
{
name: '搜索引擎',
type: 'line',
stack: '总量',
data: [820, 932, 901, 934, 1290, 1330, 1320],
},
],
},
}
},
methods: {
async init() {
// init(echarts, theme?:string, opts?:{}, chart => {})
// echarts nvuenvue
// theme 'dark'
// opts = { //
// locale?: string // `5.0.0`
// }
// chart => {} callback
// setTimeout(()=>{
// this.$refs.chart.init(echarts, chart => {
// chart.setOption(this.option);
// });
// },300)
const chart = await this.$refs.chart.init(echarts)
chart.setOption(this.option)
// tooltip
chart.on('showTip', (params) => {
this.showTip = true
console.log('showTip::')
})
chart.on('hideTip', (params) => {
setTimeout(() => {
this.showTip = false
}, 300)
})
},
save() {
this.$refs.chart.canvasToTempFilePath({
success(res) {
console.log('res::::', res)
},
})
},
},
}
</script>
<style>
.customTooltips {
position: absolute;
background-color: rgba(255, 255, 255, 0.8);
padding: 20rpx;
}
</style>

View File

@ -0,0 +1,111 @@
{
"id": "lime-echart",
"displayName": "lime-echart echarts图表",
"version": "2.0.7",
"description": "lime-echart 为 UniApp 和 UniAppX 提供 ECharts 图表兼容支持, 使 ECharts 图表能在H5、小程序、App中运行",
"keywords": [
"echarts",
"canvas",
"图表",
"可视化"
],
"repository": "https://gitee.com/liangei/lime-echart",
"engines": {
"HBuilderX": "^3.6.4",
"uni-app": "^4.65",
"uni-app-x": "^4.71"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "",
"type": "component-vue",
"darkmode": "x",
"i18n": "x",
"widescreen": "x"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "x",
"aliyun": "x",
"alipay": "x"
},
"client": {
"uni-app": {
"vue": {
"vue2": "√",
"vue3": "√"
},
"web": {
"safari": "√",
"chrome": "√"
},
"app": {
"vue": "√",
"nvue": "√",
"android": {
"extVersion": "",
"minVersion": "21"
},
"ios": "√",
"harmony": "√"
},
"mp": {
"weixin": "√",
"alipay": "√",
"toutiao": "√",
"baidu": "√",
"kuaishou": "√",
"jd": "√",
"harmony": "-",
"qq": "√",
"lark": "√",
"xhs": "-"
},
"quickapp": {
"huawei": "-",
"union": "-"
}
},
"uni-app-x": {
"web": {
"safari": "√",
"chrome": "√"
},
"app": {
"android": {
"extVersion": "",
"minVersion": "21"
},
"ios": "√",
"harmony": "√"
},
"mp": {
"weixin": "√"
}
}
}
}
},
"dependencies": {
"echarts": "^5.4.1",
"zrender": "^5.4.3"
}
}

View File

@ -0,0 +1,499 @@
# lime-echart 📊
为 UniApp 和 UniAppX 提供 ECharts 图表兼容支持,使 ECharts 图表能在 H5、小程序、App 等多端环境中正常运行。
## 特性 ✨
- 📱 **跨平台兼容**:支持 H5、微信小程序、支付宝小程序、App 等多端
- 🎯 **简单易用**:统一 API使用方式与原生 ECharts 基本一致
- ⚡ **性能优化**:针对不同平台进行了渲染优化
- 🔄 **双框架支持**:同时支持 uni-app 和 uni-app-x
## 文档与示例 📚
更多详细文档与示例:
- [lime-echart 组件文档](https://limeui.qcoon.cn/#/echart)
- [在线示例](https://limeui.qcoon.cn/#/echart-example)
- [ECharts 官方示例](https://echarts.apache.org/examples/zh/index.html)
- [lime-echart 组件文档2](https://limex.qcoon.cn/components/echart.html) (将来用到,目前未上线)
## 安装方法 📦
### 插件市场安装
1. 在uni-app插件市场中搜索并导入`lime-echart`
2. 导入后重新编译项目
3. 在页面中直接使用 `l-echart` 组件
### CLI 项目安装
```bash
# 下载插件到项目的 src/uni_modules 目录
mkdir -p src/uni_modules
# 将插件解压到上述目录
```
## 前置依赖 ⚙️
### 小程序平台(重点说明)
小程序平台必须下载并引入 ECharts 自定义构建包:
1. 小程序中引入ECharts方式
- **本地构建文件**
- 访问 [ECharts 在线构建](https://echarts.apache.org/zh/builder.html) 下载所需图表类型的精简版
- 注意:在线构建工具**仅支持生成UMD格式**(默认,`echarts.min.js`),通过 `require` 引入
- 建议只勾选项目所需的图表类型和组件,以减小文件体积
2. **文件放置位置**
- 📁 **主包**:将文件放入项目根目录的 `static` 文件夹
- 📁 **分包**:将文件放入对应分包的 `static` 文件夹(如 `pagesB/static/`
3. **相对路径引用示例**
```js
// UMD格式 - 页面位于主包根目录 - 相对路径引用示例(仅在线构建或本地文件使用)
// ├─pages
// │ └─index
// │ └─index.vue
// └─static
const echarts = require('../../static/echarts.min.js')
// UMD格式 - 页面位于主包三层目录 - 相对路径引用示例(仅在线构建或本地文件使用)
// ├─pages
// │ └─user
// │ └─settings
// │ └─profile.vue
// └─static
const echarts = require('../../../static/echarts.min.js')
// UMD格式 - 页面位于分包中 - 相对路径引用示例(仅在线构建或本地文件使用)
// ├─pagesB (分包)
// │ ├─static
// │ └─detail
// │ └─detail.vue
const echarts = require('../static/echarts.min.js')
// ES模块格式
import * as echarts from 'echarts'
```
> 注意:
> - `require` 是小程序平台特有的API仅在小程序环境下使用
> - 路径是相对于当前页面文件的路径,请根据实际项目结构调整路径
## 使用示例 🎯
### 图表配置项示例
以下是一个柱状图的配置项示例,在后续使用示例中将引用此配置:
```js
// 图表配置项示例 - 柱状图
const chartOption = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
confine: true
},
legend: {
data: ['热度', '正面', '负面']
},
xAxis: [
{
type: 'value',
axisLine: {
lineStyle: {
color: '#999999'
}
},
axisLabel: {
color: '#666666'
}
}
],
yAxis: [
{
type: 'category',
axisTick: { show: false },
data: ['汽车之家', '今日头条', '百度贴吧', '一点资讯', '微信', '微博', '知乎'],
axisLine: {
lineStyle: {
color: '#999999'
}
},
axisLabel: {
color: '#666666'
}
}
],
series: [
{
name: '热度',
type: 'bar',
label: {
show: true,
position: 'inside'
},
data: [300, 270, 340, 344, 300, 320, 310]
},
{
name: '正面',
type: 'bar',
stack: '总量',
label: {
show: true
},
data: [120, 102, 141, 174, 190, 250, 220]
},
{
name: '负面',
type: 'bar',
stack: '总量',
label: {
show: true,
position: 'left'
},
data: [-20, -32, -21, -34, -90, -130, -110]
}
]
}
```
> **说明**:在实际项目中,可以根据需求修改上述配置项。
> - 更多配置选项请参考 [ECharts 官方文档](https://echarts.apache.org/zh/option.html)
> - 查看更多图表样式请访问 [ECharts 官方示例](https://echarts.apache.org/examples/zh/index.html)
### uni-app 使用方式
#### 组合式 API 方式
```html
<template>
<view style="width: 750rpx; height: 750rpx;">
<l-echart ref="chartRef" @finished="initChart"></l-echart>
</view>
</template>
```
```js
import { ref } from 'vue';
const chartRef = ref(null)
// 仅在小程序环境下引入 ECharts
// #ifdef MP
const echarts = require('../../static/echarts.min.js') // 根据实际路径调整
// #endif
// #ifndef MP
const echarts = null // H5 和 App 环境不需要手动引入
// #endif
// 使用上面定义的图表配置项
const option = chartOption
// 初始化图表
const initChart = async () => {
if (!chartRef.value) return
try {
const chart = await chartRef.value.init(echarts)
chart.setOption(option)
} catch (error) {
console.error('图表初始化失败:', error)
}
}
```
#### 选项式 API 方式
```html
<template>
<view style="width: 750rpx; height: 750rpx;">
<l-echart ref="chartRef" @finished="initChart"></l-echart>
</view>
</template>
```
```js
// 仅在小程序环境下引入 ECharts
// #ifdef MP
const echarts = require('../../static/echarts.min.js') // 根据实际路径调整
// #endif
// #ifndef MP
const echarts = null // H5 和 App 环境不需要手动引入
// #endif
export default {
data() {
return {
// 使用上面定义的图表配置项
option: chartOption,
// 图表实例,用于后续操作
chartInstance: null,
}
},
methods: {
// 初始化图表
async initChart() {
if (!this.$refs.chartRef) return
try {
this.chartInstance = await this.$refs.chartRef.init(echarts)
this.chartInstance.setOption(this.option)
} catch (error) {
console.error('图表初始化失败:', error)
}
},
// 更新图表数据
updateChart(newOption) {
if (this.chartInstance) {
this.chartInstance.setOption(newOption)
} else if (this.$refs.chartRef) {
this.$refs.chartRef.setOption(newOption)
}
},
// 调整图表大小
resizeChart() {
if (this.$refs.chartRef) {
this.$refs.chartRef.resize()
}
}
},
// 页面卸载时销毁图表实例
beforeUnmount() {
if (this.$refs.chartRef) {
this.$refs.chartRef.dispose()
}
}
}
```
### uni-app-x 使用方式
```html
<template>
<view style="width: 100%; height: 408px;">
<l-echart ref="chartRef" @finished="initChart"></l-echart>
</view>
</template>
```
```ts
const chartRef = ref<LEchartComponentPublicInstance | null>(null)
// 仅在小程序环境下引入 ECharts
// #ifdef MP
const echarts = require('../../static/echarts.min.js') // 根据实际路径调整
// #endif
// #ifndef MP
const echarts = null
// #endif
// 使用上面定义的图表配置项
const option = chartOption
// 初始化图表
const initChart = async () => {
if (chartRef.value === null) return
try {
const chart = await chartRef.value.init(echarts, null)
chart.setOption(option)
} catch (error) {
console.error('图表初始化失败:', error)
}
}
```
## 高级功能 💪
### 数据更新 🔄
图表支持动态更新数据,有两种常用方式:
#### 方式一:通过组件引用更新
```js
// Vue 3 Composition API
chartRef.value?.setOption(newOption)
// Vue 2 Options API
this.$refs.chart.setOption(newOption)
```
#### 方式二:通过图表实例更新
```js
// 在初始化时保存图表实例
let chartInstance = null
const initChart = async () => {
if (!chartRef.value) return
chartInstance = await chartRef.value.init(echarts)
chartInstance.setOption(option)
}
// 后续更新数据
const updateChart = () => {
if (chartInstance) {
chartInstance.setOption(newData)
}
}
```
### 图表大小调整 📏
当容器大小改变时,可以调用 `resize` 方法重新调整图表尺寸:
```js
// 自动适应容器大小
chartRef.value?.resize()
// 手动指定尺寸
chartRef.value?.resize({
width: 375, // 像素值
height: 375 // 像素值
})
```
**💡 提示**:在窗口大小变化或屏幕旋转时,可以监听相应事件并调用 `resize` 方法。
### Vue 2 兼容配置 🔄
如果您的项目使用 Vue 2需要先安装并引入 Vue Composition API
```js
// main.js
import Vue from 'vue'
import VueCompositionAPI from '@vue/composition-api'
Vue.use(VueCompositionAPI)
```
详细配置请参考:[Vue Composition API 官方文档](https://uniapp.dcloud.net.cn/tutorial/vue-composition-api.html)
### 组件标签说明 🏷️
| 标签名 | 说明 |
|-------|------|
| `l-echart` | 正式使用的组件标签 |
| `lime-echart` | 演示用组件标签 |
### 快速预览 🚀
导入插件后,可以直接使用演示标签查看效果:
```html
<template>
<view style="width: 100%; height: 400px;">
<!-- 演示组件 -->
<lime-echart />
</view>
</template>
```
## 常见问题与解决方案 🐛
### 平台特殊问题
#### 微信小程序
- **画布层级问题**:微信开发工具中 canvas 可能出现层级过高或不跟随页面滚动的情况,真机环境下通常不受影响
- **Tooltip 阴影**:如需去除文字阴影,可添加配置:`tooltip.shadowBlur = 0`
#### 钉钉小程序
- **文字测量精度**:由于钉钉小程序没有原生 `measureText`,字体粗细测量可能不够精确
- **安全扫描警告**:如遇到 `Uint8Clamped` 安全问题,可按以下方式修改 ECharts 文件:
```js
// 查找类似代码并修改
// 原代码
["Int8","Uint8","Uint8Clamped","Int16","Uint16","Int32","Uint32","Float32","Float64"],(function(t,e){return t["[object "+e+"Array]"]
// 修改为
["Int8","Uint8","Uint8_Clamped","Int16","Uint16","Int32","Uint32","Float32","Float64"],(function(t,e){return t["[object "+e.replace('_','')+"Array]"]
```
### 功能限制 ⚠️
- **Toolbox**:不支持 `saveImage` 功能
- **Lines 图表**:不支持 `trailLength` 属性,请设置为 `0`
- **DataZoom**H5 平台不建议设置 `showDetail` 属性
- **自定义 Tooltips**uvue 和 vue 中不支持 DOM 操作相关的自定义 Tooltips
## API 参考 📝
### Props
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|------|------|------|--------|------|
| l-style | 自定义样式 | string | - | - |
| type | 指定 canvas 类型(废除) | string | "2d" | - |
| is-disable-scroll | 触摸图表时是否禁止页面滚动 | boolean | false | - |
| beforeDelay | 延迟初始化时间(毫秒) | number | 30 | - |
| enableHover | PC端是否启用鼠标悬浮效果废除 | boolean | false | - |
| landscape | 是否旋转90度模拟横屏效果 | boolean | false | - |
| autoHideTooltip | 是否自动隐藏Tooltip | boolean | false | - |
### 组件方法
| 方法名 | 参数 | 返回值 | 说明 |
|--------|------|--------|------|
| init | `echarts: Object, config?: Object` | `Promise<ChartInstance>` | 初始化图表实例 |
| setOption | `option: Object` | `void` | 设置或更新图表配置项 |
| resize | `size?: {width: number, height: number}` | `void` | 调整图表尺寸 |
| clear | `-` | `void` | 清空图表内容 |
| dispose | `-` | `void` | 销毁图表实例 |
| showLoading | `-` | `void` | 显示加载动画 |
| hideLoading | `-` | `void` | 隐藏加载动画 |
| canvasToTempFilePath | `options: Object` | `Promise<Object>` | 生成图表图片,与 uni-app 官方 API 类似,但无需传入 canvasId |
### 事件
| 事件名 | 参数 | 说明 |
|--------|------|------|
| finished | 无 | 图表准备就绪时触发,此时可调用 init 方法 |
## 其他平台依赖说明 🌐
### uni-app 非 nvue 端
- **推荐使用 `npm` 安装**
- 通过 npm 安装可以获得完整的 ES 模块格式支持
```bash
npm install echarts --save
```
- 安装后可直接在代码中通过 `import` 引入
```js
import * as echarts from 'echarts'
```
### uni-app-x 非 App 端
- **推荐使用 npm 安装**获取 ES 模块格式
```bash
npm install echarts --save
```
- ES 模块格式具有更好的性能和构建优化支持
- 通过 `import` 引入使用
```js
import * as echarts from 'echarts'
```
> 💡 注意H5 和 App 原生环境通常不需要手动引入 ECharts组件会自动处理。只有在需要自定义 ECharts 版本或配置时才需要手动引入。
## 技术支持 🆘
如果您在使用过程中遇到问题,可以通过以下方式获取帮助:
1. 查看 [在线文档](https://limeui.qcoon.cn/#/echart) 获取详细使用说明
2. 检查 [常见问题](#常见问题与解决方案) 章节查找解决方案
## 贡献与支持 💙
如果您觉得本插件对您有帮助,欢迎给作者点个赞或提供支持:
| 支付宝 | 微信 |
|--------|------|
| ![支付宝](https://testingcf.jsdelivr.net/gh/liangei/image@1.9/alipay.png) | ![微信](https://testingcf.jsdelivr.net/gh/liangei/image@1.9/wpay.png) |
您的支持是作者持续开发和维护的动力! 🌟

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,184 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title></title>
<style type="text/css">
html,
body {
overflow: hidden;
/* 隐藏滚动条 */
overscroll-behavior: none;
/* 禁止橡皮筋效果 */
}
html,
body,
.canvas {
padding: 0;
margin: 0;
overflow-y: hidden;
background-color: transparent;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div class="canvas" id="limeChart"></div>
<script type="text/javascript" src="./uni.webview.1.5.5.js"></script>
<script type="text/javascript" src="./echarts.min.js"></script>
<script type="text/javascript" src="./ecStat.min.js"></script>
<!-- <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts-liquidfill@latest/dist/echarts-liquidfill.min.js"></script> -->
<script>
let chart = null;
let cache = [];
console.log = function() {
emit('log', {
log: arguments,
})
}
function emit(event, data) {
postMessage({
event,
data
})
cache = []
}
function postMessage(data) {
uni.webView.postMessage({
data
})
// window.__uniapp_x_.postMessage(JSON.stringify(data))
};
function stringify(key, value) {
if (typeof value === 'object' && value !== null) {
if (cache.indexOf(value) !== -1) {
return;
}
cache.push(value);
}
return value;
}
function parse(name, callback, options) {
const optionNameReg = /[\w]+\.setOption\(([\w]+\.)?([\w]+)\)/
if (optionNameReg.test(callback)) {
const optionNames = callback.match(optionNameReg)
if (optionNames[1]) {
const _this = optionNames[1].split('.')[0]
window[_this] = {}
window[_this][optionNames[2]] = options
return optionNames[2]
} else {
return null
}
}
return null
}
function init(callback, options, opts, theme) {
if (!chart) {
chart = echarts.init(document.getElementById('limeChart'), theme, opts)
if (options) {
chart.setOption(options)
}
}
}
function on(data) {
if (chart && data.length > 0) {
const [type, query] = data
const key = `${type}${JSON.stringify(query||'')}`
if (query) {
chart.on(type, query, function(options) {
var obj = {};
Object.keys(options).forEach(function(key) {
if (key != 'event') {
obj[key] = options[key];
}
});
emit(key, {
event: key,
options: obj,
});
});
} else {
chart.on(type, function(options) {
var obj = {};
Object.keys(options).forEach(function(key) {
if (key != 'event') {
obj[key] = options[key];
}
});
emit(key, {
event: key,
options: obj,
});
});
}
}
}
function setChart(callback, options) {
if (!callback) return
if (chart && callback && options) {
var r = null
const name = parse('r', callback, options)
if (name) this[name] = options
eval(`r = ${callback};`)
if (r) {
r(chart)
}
}
}
function setOption(data) {
if (chart) chart.setOption(data[0], data[1])
}
function showLoading(data) {
if (chart) chart.showLoading(data[0], data[1])
}
function hideLoading() {
if (chart) chart.hideLoading()
}
function clear() {
if (chart) chart.clear()
}
function dispose() {
if (chart) chart.dispose()
}
function resize(size) {
if (chart) chart.resize(size)
}
function canvasToTempFilePath(opt) {
if (chart) {
delete opt.success
const src = chart.getDataURL(opt)
postMessage({
// event: 'file',
file: src
})
}
}
document.addEventListener('touchmove', () => {
})
</script>
</body>
</html>

File diff suppressed because one or more lines are too long