Files
smart_storage_app/pages/tabbar/components/topology/pv2first.vue
2025-12-26 11:26:40 +08:00

1172 lines
25 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="warp">
<topoCanvas ref="topoCanvasRef" cId="thirdcanvas" :width="'100%'" :height="'100%'" :canvas-data="canvasData"
:noloading="noloading" />
</view>
</template>
<script>
import topoCanvas from '@/components/new-canvas/index.vue'
export default {
components: {
topoCanvas
},
data() {
return {
canvasData: [],
partList: {},
noloading: false,
// 文字
textCanvasData: [
{
type: "text",
coord: [
[260, 65]
],
font: [{
text: this.$t("homePage.home.load"),
size: 12,
color: "#666666",
width: 50,
}]
},
{
type: "text",
coord: [
[10, 65]
],
font: [{
text: this.$t("homePage.home.grid"),
size: 12,
color: "#666666",
width: 50,
}]
},
{
type: "text",
coord: [
[40, 245]
],
font: [{
text: "MPPT1",
size: 12,
color: "#666666",
width: 50,
}]
},
{
type: "text",
coord: [
[140, 245]
],
font: [{
text: "MPPT2",
size: 12,
color: "#666666",
width: 50,
}]
},
{
type: "text",
coord: [
[250, 250]
],
font: [{
text: this.$t("homePage.home.cabinet"),
size: 12,
color: "#666666",
width: 50,
}]
},
],
// 图片
imageCanvasData: [{
//负载
type: "image",
url: "/static/topology/load.png",
coord: [
[250, 10],
[40, 40],
],
},
{
//电网
type: "image",
url: "/static/topology/dianwang.png",
coord: [
[15, 10],
[40, 40],
],
},
{
//一体舱
type: "image",
url: "/static/topology/pv.png",
coord: [
[45, 195],
[30, 40],
],
},
{
//一体舱
type: "image",
url: "/static/topology/pv.png",
coord: [
[145, 195],
[30, 40],
],
},
{
//一体舱
type: "image",
url: "/static/topology/yiticang.png",
coord: [
[255, 195],
[30, 40],
],
},
{
//电表
type: "image",
url: "/static/topology/ammeter.png",
coord: [
[170, 35],
[30, 30],
],
},
{
//sts
type: "image",
url: "/static/topology/ACDC.png",
coord: [
[87, 85],
[40, 40],
],
},
],
// 线
lineCanvasData: [{
type: "line",
coord: [
[60, 30],
[240, 30],
],
color: "#19875c",
width: 2,
dash: [10, 5],
},
{
type: "line",
coord: [
[110, 30],
[110, 85],
],
color: "#19875c",
width: 2,
dash: [10, 5],
},
{
type: "line",
coord: [
[110, 125],
[110, 150],
],
color: "#19875c",
width: 2,
dash: [10, 5],
},
{
type: "line",
coord: [
[60, 150],
[60, 190],
],
color: "#19875c",
width: 2,
dash: [10, 5],
},
{
type: "line",
coord: [
[160, 150],
[160, 190],
],
color: "#19875c",
width: 2,
dash: [10, 5],
},
{
type: "line",
coord: [
[60, 150],
[270, 150],
],
color: "#19875c",
width: 2,
dash: [10, 5],
},
{
type: "line",
coord: [
[270, 150],
[270, 190],
],
color: "#19875c",
width: 2,
dash: [10, 5],
},
{
type: "line",
coord: [
[110, 55],
[160, 55],
],
color: "#19875c",
width: 2,
dash: [10, 5],
}
],
// 点
circleCanvasData: [{
type: "circle",
coord: [
[60, 30]
],
color: "#0DA17D",
isMove: false,
r: 3,
},
{
type: "circle",
coord: [
[240, 30]
],
color: "#ff0000",
isMove: false,
r: 3,
},
{
type: "circle",
coord: [
[270, 190]
],
color: "#58D86B",
isMove: false,
r: 3,
},
{
type: "circle",
coord: [
[60, 190]
],
color: "#338EFE",
isMove: false,
r: 3,
},
{
type: "circle",
coord: [
[160, 190]
],
color: "#338EFE",
isMove: false,
r: 3,
}
],
animationId: null, // requestAnimationFrame ID
movingPoints: [
// 点1左上角 → 向右移动
{
x: 60,
y: 30,
path: [{
x: 60,
y: 30
}, // 起点
{
x: 240,
y: 30
} // 终点
],
currentStep: 0, // 当前在路径的哪一步
isActive: false, // 标识动画是否激活
id: 'point-1',
color: '#0DA17D'
},
// 点2左上角 → 向右 → 向下
{
x: 60,
y: 30,
path: [{
x: 60,
y: 30
},
{
x: 110,
y: 30
},
{
x: 110,
y: 85
}
],
currentStep: 0,
isActive: false, // 标识动画是否激活
id: 'point-2',
color: '#0DA17D'
},
// 点4中间下 → 向下 → 向右 → 向下
{
x: 110,
y: 127,
path: [{
x: 110,
y: 127
},
{
x: 110,
y: 150
},
{
x: 270,
y: 150
},
{
x: 270,
y: 190
}
],
currentStep: 0,
isActive: false, // 标识动画是否激活
id: 'point-4',
color: '#0DA17D'
},
// 新点5中间上 → 向上 → 向右
{
x: 110,
y: 85,
path: [{
x: 110,
y: 85
},
{
x: 110,
y: 30
},
{
x: 240,
y: 30
} // 终点
],
currentStep: 0,
isActive: false, // 标识动画是否激活
id: 'point-5',
color: '#338EFE',
delay: 500
},
// 点6中间上 → 向上 → 向右
{
x: 110,
y: 85,
path: [{
x: 110,
y: 85
},
{
x: 110,
y: 30
},
{
x: 240,
y: 30
} // 终点
],
currentStep: 0,
isActive: false, // 标识动画是否激活
id: 'point-6',
color: '#58D86B'
},
// 点7左下角 → 向上 → 向右 → 向上
{
x: 60,
y: 190,
path: [{
x: 60,
y: 190
},
{
x: 60,
y: 150
},
{
x: 110,
y: 150
},
{
x: 110,
y: 127
}
],
currentStep: 0,
isActive: false, // 标识动画是否激活
id: 'point-7',
color: '#338EFE'
},
// 点8底部中间 → 向上 → 向左 → 向上
{
x: 160,
y: 190,
path: [{
x: 160,
y: 190
},
{
x: 160,
y: 150
},
{
x: 110,
y: 150
},
{
x: 110,
y: 127
}
],
currentStep: 0,
isActive: false, // 标识动画是否激活
id: 'point-8',
color: '#338EFE'
},
// 点9右下角 → 向上 → 向左 → 向上
{
x: 270,
y: 190,
path: [{
x: 270,
y: 190
},
{
x: 270,
y: 150
},
{
x: 110,
y: 150
},
{
x: 110,
y: 127
}
],
currentStep: 0,
isActive: false, // 标识动画是否激活
id: 'point-9',
color: '#58D86B'
},
// 点3左下角 → 向上 → 向右 → 向下
{
x: 60,
y: 190,
path: [{
x: 60,
y: 190,
},
{
x: 60,
y: 150
},
{
x: 270,
y: 150
},
{
x: 270,
y: 190
}
],
currentStep: 0,
isActive: false, // 标识动画是否激活
id: 'point-3',
color: '#338EFE'
},
// 点10底部中间 → 向上 → 向右 → 向下
{
x: 160,
y: 190,
path: [{
x: 160,
y: 190
},
{
x: 160,
y: 150
},
{
x: 270,
y: 150
},
{
x: 270,
y: 190
}
],
currentStep: 0,
isActive: false, // 标识动画是否激活
id: 'point-10',
color: '#338EFE'
},
],
pointSpeed: 0.6, // 移动速度(像素/帧)
kWValues: {
left: null, // 存储 getLeftPcs 的 kW 值
center: null, // 存储 getCenterPcs 的 kW 值
right: null // 存储 getRightPcs 的 kW 值
}
}
},
computed: {
currentStation() {
return this.vuex_currentStation;
},
},
beforeDestroy() {
// 强制取消动画帧
if (this.animationId) {
if (typeof requestAnimationFrame !== 'undefined') {
cancelAnimationFrame(this.animationId);
} else {
clearTimeout(this.animationId);
}
this.animationId = null; // 重置ID
}
// 重置所有动态点状态
this.movingPoints.forEach(point => {
point.isActive = false;
point.currentStep = 0;
point.x = point.path[0].x;
point.y = point.path[0].y;
});
// 清空canvasData
this.canvasData = [];
},
watch: {
currentStation:{
handler(val) {
if (val && val.id) {
this.getData(val.id);
}
},
deep: true,
immediate: true
}
},
methods: {
getTextWidth(text, chineseExtraWidth = 8) {
// 判断是否为中文(只要包含一个中文字符就算中文,但你保证了非混排)
const isChinese = /[\u4e00-\u9fa5]/.test(text);
// 使用 Canvas 测量实际宽度
// const ctx = document.createElement('canvas').getContext('2d');
// ctx.font = '14px sans-serif';
let width = 15;
// 如果是中文,加上固定偏移
if (isChinese) {
width += chineseExtraWidth;
}
return width;
},
// 启动动画
startAnimation() {
const canvasRef = this.$refs.topoCanvasRef;
if (!canvasRef) return;
// 记录动画启动的初始时间(用于计算延迟)
this.animationStartTime = Date.now(); // 新增:存储启动时间
// 清除旧动画
if (this.animationId !== null) {
if (requestAnimationFrame && this.animationId) {
cancelAnimationFrame(this.animationId);
} else {
clearTimeout(this.animationId);
}
this.animationId = null;
}
// 直接启动新动画
this.animate();
},
animate() {
const canvasRef = this.$refs.topoCanvasRef;
if (!canvasRef) return;
const animateFrame = () => {
// 当前时间 - 动画启动时间 = 已运行时间
const elapsedTime = Date.now() - this.animationStartTime;
// 1. 更新移动点的位置
this.movingPoints.forEach(point => {
if (!point.isActive) return;
// 关键判断延迟是否结束默认delay=0无延迟
const delayEndTime = point.delay || 0;
if (elapsedTime < delayEndTime) {
return; // 延迟未结束,不更新位置
}
const currentIndex = point.currentStep;
const nextTarget = point.path[currentIndex + 1];
if (!nextTarget) {
point.x = point.path[0].x;
point.y = point.path[0].y;
point.currentStep = 0;
return;
}
const dx = nextTarget.x - point.x;
const dy = nextTarget.y - point.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < this.pointSpeed) {
point.x = nextTarget.x;
point.y = nextTarget.y;
point.currentStep++;
} else {
point.x += (dx / distance) * this.pointSpeed;
point.y += (dy / distance) * this.pointSpeed;
}
});
// 2. 更新 canvasData 中的动态点
this.updateMovingPoints();
// 3. 清除并重绘
canvasRef.clear();
canvasRef.draw(null);
// 4. 继续下一帧(✅ 安全判断)
if (typeof requestAnimationFrame !== 'undefined') {
this.animationId = requestAnimationFrame(animateFrame);
} else {
this.animationId = setTimeout(animateFrame, 16);
}
};
// 启动第一帧(✅ 安全判断)
if (typeof requestAnimationFrame !== 'undefined') {
requestAnimationFrame(animateFrame);
} else {
setTimeout(animateFrame, 16);
}
},
updateMovingPoints() {
this.movingPoints.forEach(point => {
const dataIndex = this.canvasData.findIndex(item => item.id === point.id);
if (dataIndex !== -1) {
this.canvasData[dataIndex].coord = [
[point.x, point.y]
];
}
});
},
changeEnglish() {
this.textCanvasData = [{
type: "text",
coord: [
[260, 65]
],
font: [{
text: this.$t("homePage.home.load"),
size: 12,
color: "#666666",
width: 50,
}]
},
{
type: "text",
coord: [
[10, 65]
],
font: [{
text: this.$t("homePage.home.grid"),
size: 12,
color: "#666666",
width: 50,
}]
},
{
type: "text",
coord: [
[40, 245]
],
font: [{
text: "MPPT1",
size: 12,
color: "#666666",
width: 50,
}]
},
{
type: "text",
coord: [
[140, 245]
],
font: [{
text: "MPPT2",
size: 12,
color: "#666666",
width: 50,
}]
},
{
type: "text",
coord: [
[250, 250]
],
font: [{
text: this.$t("homePage.home.cabinet"),
size: 12,
color: "#666666",
width: 50,
}]
},
]
},
getData(val) {
// 彻底重置画布数据
this.canvasData = [];
// 重置所有点状态
this.movingPoints.forEach(point => (point.isActive = false, point.isMove = false, point.currentStep = 0,
point.x = point.path[0].x, point.y = point.path[0].y));
this.stationId = val;
this.textCanvasData.splice(5); // 清除文本数据
const api = [this.getLeftPcs(), this.getCenterPcs(), this.getRightPcs(), this.getacdcAmmeter(),this.getAmmeter()];
Promise.all(api).finally(() => {
// 获取三个 kW 值
const {
left,
center,
right
} = this.kWValues;
const values = [left, center, right];
const allZero = values.every(v => v === 0);
const anyPositive = values.some(v => v > 0);
const anyNegative = values.some(v => v < 0);
// 重置所有点(确保干净)
this.movingPoints.forEach(p => (p.isActive = false));
// this.movingPoints.forEach(p => (p.isActive = true));
// 条件3全部为0 → 只显示点1
// if (allZero) {
// this.movingPoints.find(p => p.id === 'point-1').isActive = true;
// }
// // 否则按原逻辑处理
// else {
// // 条件1任一大于0 → 点6运动
// if (anyPositive) {
// this.movingPoints.find(p => p.id === 'point-1').isActive = true;
// this.movingPoints.find(p => p.id === 'point-2').isActive = true;
// }
// // 条件2任一小于0 → 点1和点2运动
// if (anyNegative) {
// this.movingPoints.find(p => p.id === 'point-6').isActive = true;
// this.movingPoints.find(p => p.id === 'point-5').isActive = true;
// }
// 只激活指定的点point-1、point-5、point-6、point-7、point-8、point-9
const activePointIds = ['point-1'];
activePointIds.forEach(id => {
const point = this.movingPoints.find(p => p.id === id);
if (point) point.isActive = true;
});
// 再设置点3~9的显示逻辑
this.updatePcsPoints();
// }
// 重新构建 canvasData
this.canvasData = [
...this.textCanvasData,
...this.imageCanvasData,
...this.lineCanvasData,
...this.circleCanvasData
];
// 动态添加 movingPoints
this.movingPoints.forEach(point => {
this.canvasData.push({
type: "circle",
id: point.id,
coord: [
[point.x, point.y]
],
color: point.color,
r: 3,
isMove: true
});
});
this.noloading = true;
this.$nextTick(() => {
this.startAnimation();
});
});
},
countChineseAndEnglishCharacters(str, x) {
var chineseCount = str.match(/[\u4e00-\u9fa5]/g) ? str.match(/[\u4e00-\u9fa5]/g).length : 0
var englishCount = str.match(/[a-zA-Z]/g) ? str.match(/[a-zA-Z]/g).length : 0
var otherCount = str.length - chineseCount - englishCount
const obj = {
otherCount: otherCount,
chineseCount: chineseCount,
englishCount: englishCount
}
return (obj.englishCount * 4) + (obj.chineseCount * 10) + (obj.otherCount * 8) + x
},
getAmmeter() {
let self = this;
return new Promise((resolve, reject) => {
self.$u.api.homePageData
.GetDynamicConfig({
stationId: this.stationId,
pageLocation: 'triad-ammeter'
})
.then((res) => {
if (res.data && res.data.length) {
res.data.forEach((item, index) => {
this.textCanvasData.push({
type: "text",
coord: [
[130, 78 + index * 15]
],
font: [{
text: item.name,
size: 12,
color: "#666666",
width: 50,
}]
}, {
type: "text",
coord: [
[this.countChineseAndEnglishCharacters(item.name, 145), 78 + index * 15]
],
font: [{
text: item.value,
size: 12,
color: "#666666",
width: 50,
}]
})
})
}
resolve()
})
})
},
getacdcAmmeter() {
let self = this;
return new Promise((resolve, reject) => {
self.$u.api.homePageData
.GetDynamicConfig({
stationId: this.stationId,
pageLocation: 'triad-acdc-center'
})
.then((res) => {
if (res.data && res.data.length) {
res.data.forEach((item, index) => {
this.textCanvasData.push({
type: "text",
coord: [
[130, 108 + index * 15]
],
font: [{
text: item.name,
size: 12,
color: "#666666",
width: 50,
}]
}, {
type: "text",
coord: [
[this.countChineseAndEnglishCharacters(item.name, 145), 108 + index * 15]
],
font: [{
text: item.value,
size: 12,
color: "#666666",
width: 50,
}]
})
})
}
resolve()
})
})
},
getLeftPcs() {
let self = this;
return new Promise((resolve, reject) => {
self.$u.api.homePageData.GetDynamicConfig({
stationId: this.stationId,
pageLocation: 'triad-pcs-left'
}).then((res) => {
if (res.data && res.data.length) {
res.data.forEach((item, index) => {
if (item.name.includes('kW') && !item.name.includes('kWh')) { // 检查是否包含'kW'
const value = parseFloat(item.value);
this.kWValues.left = value; // 只记录
}
this.textCanvasData.push({
type: "text",
coord: [
[this.getTextWidth(item.name, 10), 265 + index * 30]
],
font: [{
text: item.name,
size: 12,
color: "#666666",
width: 50,
}]
}, {
type: "text",
coord: [
[36, 280 + index * 30]
],
font: [{
text: item.value,
size: 12,
color: "#666666",
width: 50,
}]
})
})
} else {
this.kWValues.left = 0; // 没数据视为 0
}
resolve()
}).catch(() => {
this.kWValues.left = 0; // 出错也视为 0
resolve();
})
})
},
getCenterPcs() {
let self = this;
return new Promise((resolve, reject) => {
self.$u.api.homePageData
.GetDynamicConfig({
stationId: this.stationId,
pageLocation: 'triad-pcs-center'
})
.then((res) => {
if (res.data && res.data.length) {
res.data.forEach((item, index) => {
if (item.name.includes('kW') && !item.name.includes('kWh')) { // 检查是否包含'kW'
const value = parseFloat(item.value);
this.kWValues.center = value;
}
this.textCanvasData.push({
type: "text",
coord: [
[105 + this.getTextWidth(item.name, 20), 265 + index * 30]
],
font: [{
text:item.name,
size: 12,
color: "#666666",
width: 50,
}]
}, {
type: "text",
coord: [
[140, 280 + index * 30]
],
font: [{
text: item.value,
size: 12,
color: "#666666",
width: 50,
}]
})
})
// this.textCanvasData.push({
// type: "text",
// coord: [
// [150, 265]
// ],
// font: [{
// text: `${this.$t('homePage.home.operatingPower')}`,
// size: 12,
// color: "#666666",
// width: 50,
// }]
// }, {
// type: "text",
// coord: [
// [160, 285]
// ],
// font: [{
// text: `${43.26} kW`,
// size: 12,
// color: "#666666",
// width: 50,
// }]
// })
} else {
this.kWValues.center = 0;
}
resolve()
}).catch(() => {
this.kWValues.center = 0;
resolve();
})
})
},
getRightPcs() {
let self = this;
return new Promise((resolve, reject) => {
self.$u.api.homePageData
.GetDynamicConfig({
stationId: this.stationId,
pageLocation: 'triad-pcs-right'
})
.then((res) => {
if (res.data && res.data.length) {
res.data.forEach((item, index) => {
if (!item.name.includes('kW')) {
this.textCanvasData.push({
type: "text",
coord: [
[250, 265 + index*30]
],
font: [{
text: item.name,
size: 12,
color: "#666666",
width: 50,
}]
},{
type: "text",
coord: [
[255, 280 + index*30]
],
font: [{
text: item.value,
size: 12,
color: "#666666",
width: 50,
}]
})
}
})
const stationName = res.data.find(el => el.name.includes('kW'));
if(stationName){
const value = parseFloat(item.value);
this.kWValues.right = value;
}
} else {
this.kWValues.right = 0;
}
resolve()
}).catch(() => {
this.kWValues.right = 0;
resolve();
})
})
},
updatePcsPoints() {
// const {
// left,
// center,
// right
// } = this.kWValues;
this.kWValues = {
left:0,
center:0,
right:-1
}
const {
left,
center,
right
} = this.kWValues;
const point7 = this.movingPoints.find(p => p.id === 'point-7');
const point8 = this.movingPoints.find(p => p.id === 'point-8');
const point6 = this.movingPoints.find(p => p.id === 'point-6');
const point5 = this.movingPoints.find(p => p.id === 'point-5');
const point9 = this.movingPoints.find(p => p.id === 'point-9');
const point2 = this.movingPoints.find(p => p.id === 'point-2');
const point4 = this.movingPoints.find(p => p.id === 'point-4');
const point3 = this.movingPoints.find(p => p.id === 'point-3');
const point10 = this.movingPoints.find(p => p.id === 'point-10');
// 处理左边点7
if (left === 0) {
point7.isActive = false;
} else if (left > 0) {
point7.isActive = true;
} else if (left < 0) {
point7.isActive = false;
}
// 处理中间 点5
if (left === 0 && center === 0){
point5.isActive = false;
} else {
point5.isActive = true;
}
// 处理中间 点8
if (center === 0) {
point8.isActive = false;
} else if (center > 0) {
point8.isActive = true;
} else if (center < 0) {
point8.isActive = false;
}
// 处理右边点5 和 点9
if (right === 0) {
point6.isActive = false;
point9.isActive = false;
} else if (right > 0) {
point6.isActive = true;
point9.isActive = true;
} else if (right < 0) {
point6.isActive = false;
point9.isActive = false;
point2.isActive = true;
point4.isActive = true;
}
}
}
}
</script>
<style lang="scss" scoped>
.warp {
width: 650rpx;
height: 650rpx;
position: relative;
}
</style>