modify:新增小程序
This commit is contained in:
90
Sale/components/ec-canvas/ec-canvas.js
Normal file
90
Sale/components/ec-canvas/ec-canvas.js
Normal file
@@ -0,0 +1,90 @@
|
||||
Component({
|
||||
properties: {
|
||||
canvasId: {
|
||||
type: String,
|
||||
value: 'ec-canvas'
|
||||
},
|
||||
ec: {
|
||||
type: Object,
|
||||
value: {}
|
||||
},
|
||||
disableTouch: {
|
||||
type: Boolean,
|
||||
value: false
|
||||
}
|
||||
},
|
||||
|
||||
data: {
|
||||
isNew: true
|
||||
},
|
||||
|
||||
ready() {
|
||||
console.log('ec-canvas ready')
|
||||
|
||||
if (!this.data.ec) {
|
||||
console.warn('组件需绑定 ec 对象')
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.data.ec.onInit) {
|
||||
console.warn('ec 对象需包含 onInit 方法')
|
||||
return
|
||||
}
|
||||
|
||||
const query = this.createSelectorQuery()
|
||||
query.select(`#${this.data.canvasId}`)
|
||||
.fields({ node: true, size: true })
|
||||
.exec((res) => {
|
||||
console.log('Canvas query 结果:', res)
|
||||
|
||||
if (!res || !res[0]) {
|
||||
console.error('Canvas 节点未找到')
|
||||
return
|
||||
}
|
||||
|
||||
const canvasNode = res[0].node
|
||||
const ctx = canvasNode.getContext('2d')
|
||||
|
||||
const dpr = wx.getSystemInfoSync().pixelRatio
|
||||
const width = res[0].width
|
||||
const height = res[0].height
|
||||
|
||||
console.log('Canvas 尺寸信息:', { width, height, dpr })
|
||||
|
||||
canvasNode.width = width * dpr
|
||||
canvasNode.height = height * dpr
|
||||
ctx.scale(dpr, dpr)
|
||||
|
||||
const canvas = {
|
||||
width: width * dpr, // 使用缩放后的宽度
|
||||
height: height * dpr, // 使用缩放后的高度
|
||||
getContext: () => ctx,
|
||||
node: canvasNode
|
||||
}
|
||||
|
||||
console.log('准备调用 onInit,canvas 对象:', canvas)
|
||||
this.chart = this.data.ec.onInit(canvas, width, height, res)
|
||||
console.log('onInit 返回的 chart:', this.chart)
|
||||
})
|
||||
},
|
||||
|
||||
methods: {
|
||||
touchStart(e) {
|
||||
if (this.chart && this.chart.touchStart) {
|
||||
this.chart.touchStart(e)
|
||||
}
|
||||
},
|
||||
|
||||
touchMove(e) {
|
||||
if (this.chart && this.chart.touchMove) {
|
||||
this.chart.touchMove(e)
|
||||
}
|
||||
},
|
||||
|
||||
touchEnd(e) {
|
||||
if (this.chart && this.chart.touchEnd) {
|
||||
this.chart.touchEnd(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
4
Sale/components/ec-canvas/ec-canvas.json
Normal file
4
Sale/components/ec-canvas/ec-canvas.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {}
|
||||
}
|
||||
10
Sale/components/ec-canvas/ec-canvas.wxml
Normal file
10
Sale/components/ec-canvas/ec-canvas.wxml
Normal file
@@ -0,0 +1,10 @@
|
||||
<canvas
|
||||
type="2d"
|
||||
canvas-id="{{ canvasId }}"
|
||||
id="{{ canvasId }}"
|
||||
class="ec-canvas"
|
||||
bindinit="init"
|
||||
bindtouchstart="{{ ec.disableTouch ? '' : 'touchStart' }}"
|
||||
bindtouchmove="{{ ec.disableTouch ? '' : 'touchMove' }}"
|
||||
bindtouchend="{{ ec.disableTouch ? '' : 'touchEnd' }}"
|
||||
></canvas>
|
||||
4
Sale/components/ec-canvas/ec-canvas.wxss
Normal file
4
Sale/components/ec-canvas/ec-canvas.wxss
Normal file
@@ -0,0 +1,4 @@
|
||||
.ec-canvas {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
242
Sale/components/ec-canvas/echarts.js
Normal file
242
Sale/components/ec-canvas/echarts.js
Normal file
@@ -0,0 +1,242 @@
|
||||
/**
|
||||
* 简化版 ECharts - 仅支持折线图
|
||||
* 用于微信小程序 Canvas 2D
|
||||
*/
|
||||
|
||||
class ECharts {
|
||||
constructor(canvas) {
|
||||
this.canvas = canvas
|
||||
this.ctx = canvas.getContext('2d')
|
||||
this.option = null
|
||||
// canvas.width 已经是缩放后的尺寸,直接使用
|
||||
this.width = canvas.width || 750
|
||||
this.height = canvas.height || 500
|
||||
this.padding = { top: 40, right: 40, bottom: 60, left: 80 }
|
||||
|
||||
console.log('ECharts 构造函数:', {
|
||||
canvasWidth: canvas.width,
|
||||
canvasHeight: canvas.height,
|
||||
chartWidth: this.width,
|
||||
chartHeight: this.height
|
||||
})
|
||||
}
|
||||
|
||||
setOption(option) {
|
||||
this.option = option
|
||||
this.render()
|
||||
}
|
||||
|
||||
clear() {
|
||||
if (!this.ctx) return
|
||||
this.ctx.clearRect(0, 0, this.width, this.height)
|
||||
}
|
||||
|
||||
resize() {
|
||||
// 自动调整大小
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.option || !this.ctx) {
|
||||
console.log('ECharts render: 缺少 option 或 ctx')
|
||||
return
|
||||
}
|
||||
|
||||
const { xAxis, yAxis, series } = this.option
|
||||
if (!series || !series[0]) {
|
||||
console.log('ECharts render: 缺少 series 数据')
|
||||
return
|
||||
}
|
||||
|
||||
const data = series[0].data || []
|
||||
const categories = xAxis?.data || []
|
||||
|
||||
console.log('ECharts render:', {
|
||||
dataCount: data.length,
|
||||
categoryCount: categories.length,
|
||||
width: this.width,
|
||||
height: this.height
|
||||
})
|
||||
|
||||
this.clear()
|
||||
|
||||
// 计算绘图区域
|
||||
const chartWidth = this.width - this.padding.left - this.padding.right
|
||||
const chartHeight = this.height - this.padding.top - this.padding.bottom
|
||||
|
||||
// 计算数据范围
|
||||
const maxValue = Math.max(...data) * 1.1
|
||||
const minValue = Math.min(...data) * 0.9
|
||||
const valueRange = maxValue - minValue || 1
|
||||
|
||||
// 绘制坐标轴
|
||||
this.drawAxes(chartWidth, chartHeight, minValue, maxValue, categories)
|
||||
|
||||
// 绘制区域填充
|
||||
if (series[0].areaStyle) {
|
||||
this.drawArea(data, chartWidth, chartHeight, minValue, valueRange)
|
||||
}
|
||||
|
||||
// 绘制折线
|
||||
this.drawLine(data, chartWidth, chartHeight, minValue, valueRange)
|
||||
|
||||
// 绘制数据点
|
||||
this.drawPoints(data, chartWidth, chartHeight, minValue, valueRange)
|
||||
}
|
||||
|
||||
drawAxes(chartWidth, chartHeight, minValue, maxValue, categories) {
|
||||
const ctx = this.ctx
|
||||
const { top, right, bottom, left } = this.padding
|
||||
|
||||
ctx.strokeStyle = '#e0e0e0'
|
||||
ctx.lineWidth = 1
|
||||
|
||||
// X 轴
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(left, top + chartHeight)
|
||||
ctx.lineTo(left + chartWidth, top + chartHeight)
|
||||
ctx.stroke()
|
||||
|
||||
// Y 轴
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(left, top)
|
||||
ctx.lineTo(left, top + chartHeight)
|
||||
ctx.stroke()
|
||||
|
||||
// 绘制 Y 轴刻度
|
||||
ctx.fillStyle = '#666'
|
||||
ctx.font = '20px sans-serif'
|
||||
ctx.textAlign = 'right'
|
||||
ctx.textBaseline = 'middle'
|
||||
|
||||
const ySteps = 5
|
||||
for (let i = 0; i <= ySteps; i++) {
|
||||
const value = minValue + (maxValue - minValue) * (i / ySteps)
|
||||
const y = top + chartHeight - (chartHeight * (i / ySteps))
|
||||
|
||||
ctx.fillText(
|
||||
'¥' + Math.round(value),
|
||||
left - 10,
|
||||
y
|
||||
)
|
||||
|
||||
// 绘制网格线
|
||||
ctx.strokeStyle = '#f0f0f0'
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(left, y)
|
||||
ctx.lineTo(left + chartWidth, y)
|
||||
ctx.stroke()
|
||||
}
|
||||
|
||||
// 绘制 X 轴标签
|
||||
ctx.textAlign = 'center'
|
||||
ctx.textBaseline = 'top'
|
||||
|
||||
const xStep = chartWidth / (categories.length || 1)
|
||||
const skipStep = Math.ceil(categories.length / 6) // 最多显示6个标签
|
||||
|
||||
categories.forEach((category, index) => {
|
||||
if (index % skipStep !== 0) return // 跳过部分标签
|
||||
|
||||
const x = left + xStep * (index + 0.5)
|
||||
const y = top + chartHeight + 10
|
||||
|
||||
ctx.save()
|
||||
ctx.translate(x, y)
|
||||
ctx.rotate(45 * Math.PI / 180)
|
||||
ctx.fillText(category, 0, 0)
|
||||
ctx.restore()
|
||||
})
|
||||
}
|
||||
|
||||
drawLine(data, chartWidth, chartHeight, minValue, valueRange) {
|
||||
const ctx = this.ctx
|
||||
const { top, left } = this.padding
|
||||
|
||||
const xStep = chartWidth / (data.length || 1)
|
||||
|
||||
ctx.strokeStyle = '#1890ff'
|
||||
ctx.lineWidth = 3
|
||||
ctx.lineCap = 'round'
|
||||
ctx.lineJoin = 'round'
|
||||
|
||||
ctx.beginPath()
|
||||
|
||||
data.forEach((value, index) => {
|
||||
const x = left + xStep * (index + 0.5)
|
||||
const y = top + chartHeight - ((value - minValue) / valueRange) * chartHeight
|
||||
|
||||
if (index === 0) {
|
||||
ctx.moveTo(x, y)
|
||||
} else {
|
||||
ctx.lineTo(x, y)
|
||||
}
|
||||
})
|
||||
|
||||
ctx.stroke()
|
||||
}
|
||||
|
||||
drawArea(data, chartWidth, chartHeight, minValue, valueRange) {
|
||||
const ctx = this.ctx
|
||||
const { top, left } = this.padding
|
||||
|
||||
const xStep = chartWidth / (data.length || 1)
|
||||
|
||||
// 创建渐变
|
||||
const gradient = ctx.createLinearGradient(0, top, 0, top + chartHeight)
|
||||
gradient.addColorStop(0, 'rgba(24, 144, 255, 0.3)')
|
||||
gradient.addColorStop(1, 'rgba(24, 144, 255, 0.05)')
|
||||
|
||||
ctx.fillStyle = gradient
|
||||
ctx.beginPath()
|
||||
|
||||
data.forEach((value, index) => {
|
||||
const x = left + xStep * (index + 0.5)
|
||||
const y = top + chartHeight - ((value - minValue) / valueRange) * chartHeight
|
||||
|
||||
if (index === 0) {
|
||||
ctx.moveTo(x, y)
|
||||
} else {
|
||||
ctx.lineTo(x, y)
|
||||
}
|
||||
})
|
||||
|
||||
// 闭合路径
|
||||
const lastX = left + xStep * (data.length - 0.5)
|
||||
ctx.lineTo(lastX, top + chartHeight)
|
||||
ctx.lineTo(left + xStep * 0.5, top + chartHeight)
|
||||
ctx.closePath()
|
||||
|
||||
ctx.fill()
|
||||
}
|
||||
|
||||
drawPoints(data, chartWidth, chartHeight, minValue, valueRange) {
|
||||
const ctx = this.ctx
|
||||
const { top, left } = this.padding
|
||||
|
||||
const xStep = chartWidth / (data.length || 1)
|
||||
|
||||
data.forEach((value, index) => {
|
||||
const x = left + xStep * (index + 0.5)
|
||||
const y = top + chartHeight - ((value - minValue) / valueRange) * chartHeight
|
||||
|
||||
// 绘制点
|
||||
ctx.fillStyle = '#fff'
|
||||
ctx.strokeStyle = '#1890ff'
|
||||
ctx.lineWidth = 2
|
||||
|
||||
ctx.beginPath()
|
||||
ctx.arc(x, y, 4, 0, 2 * Math.PI)
|
||||
ctx.fill()
|
||||
ctx.stroke()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function init(canvas, width, height) {
|
||||
const chart = new ECharts(canvas)
|
||||
return chart
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init
|
||||
}
|
||||
61
Sale/components/ec-canvas/wx-canvas.js
Normal file
61
Sale/components/ec-canvas/wx-canvas.js
Normal file
@@ -0,0 +1,61 @@
|
||||
export default class WxCanvas {
|
||||
constructor(ctx, canvasId, isNew, canvasNode) {
|
||||
this.ctx = ctx
|
||||
this.canvasId = canvasId
|
||||
this.chart = null
|
||||
this.isNew = isNew
|
||||
|
||||
if (isNew) {
|
||||
this.canvasNode = canvasNode
|
||||
}
|
||||
|
||||
this.chart = null
|
||||
this._init()
|
||||
}
|
||||
|
||||
getContext(contextType) {
|
||||
return this.ctx
|
||||
}
|
||||
|
||||
setChart(chart) {
|
||||
this.chart = chart
|
||||
}
|
||||
|
||||
addEventListener() {
|
||||
// 暂不支持事件监听
|
||||
}
|
||||
|
||||
removeEventListener() {
|
||||
// 暂不支持事件监听
|
||||
}
|
||||
|
||||
_init() {
|
||||
const dpr = wx.getSystemInfoSync().pixelRatio
|
||||
this.ctx.scale(dpr, dpr)
|
||||
}
|
||||
|
||||
// 代理 canvas 方法
|
||||
setWidth(width) {
|
||||
// Canvas 2D 不需要手动设置
|
||||
}
|
||||
|
||||
setHeight(height) {
|
||||
// Canvas 2D 不需要手动设置
|
||||
}
|
||||
|
||||
getWidth() {
|
||||
return this.canvasNode.width
|
||||
}
|
||||
|
||||
getHeight() {
|
||||
return this.canvasNode.height
|
||||
}
|
||||
|
||||
addEvtListener() {
|
||||
// 事件监听
|
||||
}
|
||||
|
||||
removeEvtListener() {
|
||||
// 移除监听
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user