增加音乐,摇一摇,服务等页面

This commit is contained in:
cansnow
2026-02-15 19:41:13 +08:00
parent abd279e7a7
commit 271ec4b9c2
74 changed files with 14021 additions and 2 deletions
@@ -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;
});
}
}
@@ -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>
@@ -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>
@@ -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
}
}
@@ -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
}
}
@@ -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;
}
@@ -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()
});
};
@@ -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
@@ -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