4 Commits

19 changed files with 1863 additions and 22 deletions

View File

@ -1,10 +1,10 @@
# just a flag # just a flag
ENV = 'development' ENV = 'development'
# base api //http://192.168.1.181:8000/api 192.168.1.199:8000/api // http://124.71.192.230:8000/api // http://127.0.0.1:4523/m1/1450402-0-default # base api //http://1.95.170.86:8002/api 192.168.1.199:8000/api // https://zzkj-cloud.com/api // http://127.0.0.1:4523/m1/1450402-0-default
VUE_APP_BASE_API = 'http://121.237.176.143:9091/api' VUE_APP_BASE_API = 'https://zzkj-cloud.com/api'
VUE_APP_VISUALE_API = http://192.168.0.82:9527 VUE_APP_VISUALE_API = http://192.168.0.82:9527
VUE_APP_ZUTAI_API = http://192.168.100.83:8001 VUE_APP_ZUTAI_API = http://192.168.100.83:8001

View File

@ -2,5 +2,5 @@
ENV = 'production' ENV = 'production'
# base api # base api
VUE_APP_BASE_API = 'http://121.237.176.143:8001/api' VUE_APP_BASE_API = 'https://zzkj-cloud.com/api'

View File

@ -125,4 +125,4 @@ Modern browsers and Internet Explorer 10+.
## License ## License
Copyright (c) 2022-present Hoenergy Copyright (c) 2025-present Sinocat

161
public/app-privacy-zh.html Normal file
View File

@ -0,0 +1,161 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>隐私政策</title>
<style>
body {
font-family: Arial, sans-serif;
line-height: 1.6;
margin: 20px;
}
h1 {
color: blue;
text-align: center; /* 标题居中 */
}
h2 {
color: blue;
border-bottom: 1px solid #ccc;
padding-bottom: 5px;
}
ul {
list-style-type: disc;
margin-left: 20px;
}
ol {
margin-left: 20px;
}
.section {
margin-top: 20px;
}
</style>
</head>
<body>
<h1>隐私政策</h1>
<div class="section">
<h2>前言</h2>
<p>中自科技股份有限公司(以下简称 “我们”)高度重视对个人信息的保护,严格遵守适用的数据保护法律法规,衷心感谢您对本应用的信任。</p>
<p>作为专注于 [应用业务领域,如储能项目展示等] 的服务提供者,我们深知在全球范围内保护用户隐私的重要性。本隐私政策旨在向您清晰说明我们收集、使用、存储、分享及保护您个人信息的方式,并确保我们的做法符合国际数据保护标准及各运营所在地的法律法规要求,包括但不限于欧盟《通用数据保护条例》(GDPR)、美国《加利福尼亚州消费者隐私法案》(CCPA)、《中华人民共和国网络安全法》《中华人民共和国个人信息保护法》及《中华人民共和国数据安全法》等。</p>
</div>
<div class="section">
<h2>一、信息收集范围与方式</h2>
<p>在您使用本应用(包括访问应用内功能、注册账号、使用服务等过程中),我们可能直接收集以下个人信息:</p>
<h3>(一)基础业务必需信息</h3>
<ul>
<li>基本身份信息:手机号码/邮箱地址用于验证、用户名、密码、IP地址安全风控</li>
<li>设备与网络信息IP 地址、设备型号、操作系统版本、设备标识符(如 IMEI/Android ID/OpenUDID、OAID、设备型号等、网络类型Wi-Fi/移动网络)、浏览器类型等(用于保障服务稳定性、安全风控、功能适配);</li>
<li>日志信息:访问记录、操作日志、错误日志(优化服务);</li>
<li>位置服务:实时位置信息(需开启位置权限,用于附近储能站点查询);</li>
<li>权限说明读取设备状态保障登录安全获取OAID等信息、网络访问数据传输</li>
</ul>
<h3>(二)可选功能附加信息</h3>
<ul>
<li>位置服务:实时位置信息(需开启位置权限,用于附近储能站点查询)</li>
<li>存储权限:读写外部存储(缓存地图数据,减少流量消耗)</li>
<li>所有附加功能均需单独授权,拒绝不影响基础服务</li>
</ul>
</div>
<div class="section">
<h2>二、信息使用目的</h2>
<p>收集的个人信息将用于以下合理、必要目的:</p>
<ol>
<li>提供基础服务:实现账号注册 / 登录、储能项目展示与交互、业务流程办理(如订单 / 服务申请 )、客服响应等核心功能;</li>
<li>保障安全合规:通过设备 / 网络信息进行安全风控(识别异常登录、防范恶意攻击 ),符合法律法规要求(如数据留存以备监管查询 </li>
<li>业务运营与改进:用于内部数据分析(如统计用户需求、评估服务效果 ),推动产品迭代、服务升级;</li>
</ol>
</div>
<div class="section">
<h2>三、跨境数据传输</h2>
<p>通常情况下,您的个人信息会存储在 [应用服务器所在地区,如国内华为云服务器(法国巴黎亚马逊云服务器)等]。</p>
<p>为保障数据安全,我们会采取以下措施:</p>
<ol>
<li>合规评估与授权:对跨境传输行为进行合法性评估,确保符合国内及目标地区数据保护法规;</li>
<li>技术防护:采用加密传输、访问控制等技术手段,降低数据泄露风险;</li>
<li>合同约束:与境外接收方签订严格的数据保护协议,明确双方责任与义务,要求其保障数据安全。</li>
</ol>
</div>
<div class="section">
<h2>四、信息共享与披露</h2>
<p>我们承诺对您的个人信息严格保密,除以下情形外,不会向任何无关第三方共享 /披露:</p>
<h3>(一)必要业务合作</h3>
<p>我们委托阿里云、华为云、亚马逊云等第三方提供技术支持,仅授权其在必要范围内处理信息,并签署严格保密协议。</p>
<p>与经严格筛选的第三方服务提供商(如云服务提供商、数据分析公司)共享必要信息,以支持应用功能实现(如服务器托管、数据统计分析)。我们仅会共享完成合作所需的最小范围信息,并要求合作方签署保密协议,约束其数据处理行为。</p>
<h3>(二)法律法规要求</h3>
<p>在法律强制要求(如法院传票、行政机关调查)、履行法定义务(如配合监管合规检查)等情形下,我们可能依法披露必要的个人信息。</p>
<h3>(三)企业重组/并购</h3>
<p>若发生企业合并、分立、资产转让等重大变更,我们会遵循 “合法、必要、最小化” 原则转移个人信息,并提前以显著方式(如应用内公告、邮件 )通知您。</p>
</div>
<div class="section">
<h2>五、信息安全与保护</h2>
<p>我们致力于通过技术、管理双重措施保障您的个人信息安全:</p>
<h3>(一)技术防护</h3>
<ul>
<li>采用数据加密(如传输加密、存储加密)、访问控制(如账号密码验证、权限分级管理)、安全审计(定期检查系统日志)等技术手段,防范数据泄露、篡改、非法访问;</li>
<li>对重要系统、服务进行实时监控,及时发现并处置安全漏洞、异常访问行为。</li>
</ul>
<h3>(二)管理保障</h3>
<ul>
<li>制定严格的数据安全管理制度,明确员工数据访问权限与操作规范;</li>
<li>定期开展员工数据安全培训,提升全员隐私保护意识;</li>
<li>对数据处理流程进行合规审计,确保符合政策要求。</li>
</ul>
</div>
<div class="section">
<h2>六、您的个人信息权利及行使方式</h2>
<p>依据适用法律法规,您享有以下个人信息相关权利:</p>
<ul>
<li><strong>查询与更正:</strong>APP内路径 → 我的 → 设置 → 个人信息管理</li>
<li><strong>删除请求:</strong>支持账号注销、信息处理违规等场景</li>
<li><strong>权限管理:</strong>手机设置 → 应用管理 → “中自储能” → 关闭对应权限</li>
<li><strong>账号注销:</strong>APP内路径 → 我的 → 设置 → 账号安全 → 注销账号</li>
</ul>
</div>
<div class="section">
<h2>七、隐私政策的变更与通知</h2>
<p>我们会适时更新本隐私政策,以反映业务调整、法规变化。更新后,我们将通过以下方式通知您:</p>
<ul>
<li>应用内弹窗、消息推送;</li>
<li>邮件;</li>
<li>应用内“设置 - 隐私政策”页面显著提示。</li>
</ul>
<p>请定期查阅本政策,您继续使用应用即视为接受变更后的条款。</p>
</div>
<div class="section">
<h2>八、未成年人保护及联系方式</h2>
<p>我们不主动收集未满14周岁未成年人的个人信息。若监护人发现未满 14 周岁未成年人个人信息被收集,或您对本隐私政策有疑问、建议、需行使个人信息权利,均可通过以下方式联系我们:</p>
<ul>
<li>客服邮箱:<a href="mailto:info@zetatech-energy.com">info@zetatech-energy.com</a>(我们将在 48小时内回复</li>
<li>邮寄地址四川省成都市郫都区古南街88号邮编611730</li>
</ul>
<p>本政策由中自科技股份有限公司负责解释,若与法律法规冲突,以法律法规为准;若与应用内其他规则冲突,以本政策(更新后版本 )为准。</p>
</div>
<div class="section">
<p><strong>[发布日期2025 年 7 月 15 日]</strong></p>
<p><strong>[生效日期2025 年 7 月 15 日]</strong></p>
</div>
</body>
</html>

View File

@ -106,7 +106,7 @@ export default {
noPagePermiss: 'There is no permission for this page' noPagePermiss: 'There is no permission for this page'
}, },
login: { login: {
title: 'Hoenergy smart energy storage management platform', title: 'Sinotech smart energy storage management platform',
logIn: 'Login', logIn: 'Login',
username: 'Username', username: 'Username',
password: 'Password', password: 'Password',

View File

@ -174,6 +174,7 @@ export default {
laterQuery: '%,请稍后查询。', laterQuery: '%,请稍后查询。',
earningReport: '收益报表', earningReport: '收益报表',
bill: '账单' bill: '账单'
}, },
region: { region: {
regionName: '区域名称', regionName: '区域名称',

View File

@ -14,7 +14,8 @@ const whiteList = [
'/screen', '/screen',
'/new-screen', '/new-screen',
'/new-screen-zz', '/new-screen-zz',
'/common-screen' '/common-screen',
'/app-privacy-zh.html'
] // no redirect whitelist ] // no redirect whitelist
router.beforeEach(async(to, from, next) => { router.beforeEach(async(to, from, next) => {

View File

@ -586,12 +586,12 @@
</svg> </svg>
</div> </div>
</div> </div>
<dispositionPointDialog <DispositionPointData
is-topology is-topology
:show-div-location="true" :show-div-location="true"
:max-length="12" :max-length="12"
:page-location="pageLocation" :page-location="pageLocation"
:visible="show_point_dispostion" :disposition-show="show_point_dispostion"
@close="closePoint" @close="closePoint"
@getData="getDynamicPointData" @getData="getDynamicPointData"
/> />

View File

@ -534,7 +534,7 @@
</svg> </svg>
</div> </div>
</div> </div>
<dispositionPointDialog :visible="show_point_dispostion" :page-location="pageLocation" @close="closePoint" @getData="getDynamicPointData" /> <DispositionPointData :disposition-show="show_point_dispostion" :page-location="pageLocation" @close="closePoint" @getData="getDynamicPointData" />
</ItemBox> </ItemBox>
</template> </template>

View File

@ -0,0 +1,784 @@
<template>
<ItemBox :title="$t('dashboard.stationTopo')" style="min-height: 560px;">
<div ref="svgLine" class="center-box">
<div v-loading="loading">
<svg
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xml:space="preserve"
class="circle-load-rect-svg"
viewBox="0 0 800 500"
>
<defs>
<defs>
<radialGradient id="RadialGradient1">
<stop offset="0%" stop-color="#fff" />
<stop offset="30%" stop-color="#0171c1" />
<stop offset="50%" stop-color="#035088" />
<stop offset="100%" stop-color="#02395a" />
</radialGradient>
</defs>
<marker
id="markerCircle"
markerWidth="15"
markerHeight="15"
refX="5"
refY="5"
>
<circle
cx="5"
cy="5"
r="5"
style="stroke: none; fill: url(#RadialGradient1)"
/>
</marker>
<marker
id="markerArrow"
markerWidth="13"
markerHeight="13"
refX="2"
refY="6"
orient="auto"
>
<path d="M2,2 L2,11 L10,6 L2,2" style="fill: black" />
</marker>
</defs>
<!-- //线条 -->
<!-- top -->
<polyline
points="120,50 650,50"
fill="none"
class="g-rect-path"
stroke="#0094FF"
stroke-dasharray="3 3"
/>
<!-- <rect
x="154"
y="78"
width="231"
height="4"
:style="{ fill: 'rgba(100,100,100,0)', stroke: 'transparent',cursor: 'pointer' }"
@click="lookDeviceDetail('line-top-left')"
/> -->
<!-- <polyline
points="385,80 600,80"
fill="none"
class="g-rect-path"
stroke="#0094FF"
stroke-dasharray="3 3"
/> -->
<!-- <rect
x="385"
y="78"
width="215"
height="4"
:style="{ fill: 'rgba(100,100,100,0)', stroke: 'transparent',cursor: 'pointer' }"
@click="lookDeviceDetail('line-top-right')"
/> -->
<!-- center -->
<polyline
points="385,50 385,283"
fill="none"
class="g-rect-path"
stroke="#0094FF"
stroke-dasharray="3 3"
/>
<!-- <rect
x="383"
y="80"
width="4"
height="281"
:style="{ fill: 'rgba(100,100,100,0)', stroke: 'transparent',cursor: 'pointer' }"
@click="lookDeviceDetail('line-bottom-center')"
/> -->
<polyline
points="385,100 460,100"
fill="none"
class="g-rect-path"
stroke="#0094FF"
stroke-dasharray="3 3"
/>
<!-- bottom -->
<polyline
points="175,283 385,283"
fill="none"
class="g-rect-path"
stroke="#0094FF"
stroke-dasharray="3 3"
/>
<polyline
points="175,283 175,417"
fill="none"
class="g-rect-path"
stroke="#0094FF"
stroke-dasharray="3 3"
/>
<!-- <rect
x="104"
y="361"
width="283"
height="4"
:style="{ fill: 'rgba(100,100,100,0)', stroke: 'transparent',cursor: 'pointer' }"
@click="lookDeviceDetail('line-bottom-left')"
/> -->
<polyline
points="385,283 603,283"
fill="none"
class="g-rect-path"
stroke="#0094FF"
stroke-dasharray="3 3"
/>
<polyline
points="603,283 603,417"
fill="none"
class="g-rect-path"
stroke="#0094FF"
stroke-dasharray="3 3"
/>
<!-- <rect
x="383"
y="361"
width="272"
height="4"
:style="{ fill: 'rgba(100,100,100,0)', stroke: 'transparent',cursor: 'pointer' }"
@click="lookDeviceDetail('line-bottom-right')"
/> -->
<!-- top -->
<circle
cx="120"
cy="50"
r="8"
style="stroke: none; fill: url(#RadialGradient1)"
/>
<circle
cx="650"
cy="50"
r="8"
style="stroke: none; fill: url(#RadialGradient1)"
/>
<!-- 动点 -->
<circle
cx="120"
cy="50"
r="8"
style="stroke: none; fill: url(#RadialGradient1)"
>
<animateMotion
repeatCount="indefinite"
dur="4s"
begin="0s"
:path="dotData.lineTopLeft"
/>
</circle>
<circle
cx="385"
cy="283"
r="8"
style="stroke: none; fill: url(#RadialGradient1)"
>
<animateMotion
repeatCount="indefinite"
dur="4s"
begin="2s"
:path="dotData.lineCenter"
/>
</circle>
<circle
cx="650"
cy="50"
r="8"
style="stroke: none; fill: url(#RadialGradient1)"
>
<animateMotion
repeatCount="indefinite"
dur="4s"
begin="0s"
:path="dotData.lineTopRight"
/>
</circle>
<!-- bottom -->
<circle
cx="175"
cy="417"
r="8"
style="stroke: none; fill: url(#RadialGradient1)"
/>
<circle
cx="385"
cy="283"
r="8"
style="stroke: none; fill: url(#RadialGradient1)"
/>
<circle
cx="603"
cy="417"
r="8"
style="stroke: none; fill: url(#RadialGradient1)"
/>
<circle
cx="385"
cy="283"
r="8"
style="stroke: none; fill: url(#RadialGradient1)"
>
<animateMotion
repeatCount="indefinite"
dur="4s"
begin="2s"
:path="dotData.lineBottomLeft"
/>
</circle>
<circle
cx="175"
cy="417"
r="8"
style="stroke: none; fill: url(#RadialGradient1)"
>
<animateMotion
repeatCount="indefinite"
dur="4s"
begin="2s"
:path="dotData.lineBottomLeft"
/>
</circle>
<circle
cx="603"
cy="417"
r="8"
style="stroke: none; fill: url(#RadialGradient1)"
>
<animateMotion
repeatCount="indefinite"
dur="4s"
begin="2s"
:path="dotData.lineBottomRight"
/>
</circle>
<!-- 图片和文字 -->
<!-- 市电 -->
<image
:xlink:href="configData.grid"
x="60"
y="20"
width="50"
height="70"
/>
<!-- <text x="90" y="160" fill="#ffffff" font-size="14"> {{ $t("dashboard.grid") }}</text> -->
<!-- {{ $t("dashboard.load") }} -->
<image
:xlink:href="configData.house"
x="660"
y="20"
width="50"
height="70"
/>
<!-- <text x="645" y="160" fill="#ffffff" font-size="14"> {{ $t("dashboard.load") }}</text> -->
<!-- 1PV -->
<image
:xlink:href="frameImg"
x="145"
y="430"
width="50"
height="50"
style="cursor: pointer;"
@click="lookDeviceDetail('triad-pcs-left')"
/>
<!-- DCDC -->
<image
:xlink:href="DCImg"
x="157"
y="330"
width="35"
height="35"
/>
<!-- <text x="86" y="490" fill="#ffffff" font-size="14">1#{{ $t("dashboard.cabinet") }}</text> -->
<!-- 2PV -->
<!-- <image
:xlink:href="frameImg"
x="355"
y="430"
width="50"
height="50"
style="cursor: pointer;"
@click="lookDeviceDetail('triad-pcs-center')"
/> -->
<!-- DCDC -->
<!-- <image
:xlink:href="DCImg"
x="367"
y="330"
width="35"
height="35"
/> -->
<!-- <text x="360" y="490" fill="#ffffff" font-size="14">
2#{{ $t("dashboard.cabinet") }}
</text> -->
<!-- 3 -->
<image
:xlink:href="configData.pcs2"
x="574"
y="430"
width="50"
height="50"
style="cursor: pointer;"
@click="lookDeviceDetail('triad-pcs-right')"
/>
<!-- <text x="614" y="490" fill="#ffffff" font-size="14">3#{{ $t("dashboard.cabinet") }}</text> -->
<!-- STS -->
<!-- <image
:xlink:href="STSImg"
x="365"
y="130"
width="40"
height="40"
style="cursor: pointer;"
@click="lookDeviceDetail('triad-sts-center')"
/> -->
<!-- AC/DC -->
<image
:xlink:href="ACDCImg"
x="368"
y="200"
width="35"
height="35"
style="cursor: pointer;"
@click="lookDeviceDetail('triad-acdc-center')"
/>
<!-- {{ $t("dashboard.ammeter") }} -->
<image
:xlink:href="ammeterImg"
x="450"
y="80"
width="70"
height="42"
style="cursor: pointer;"
@click="lookDeviceDetail('triad-ammeter')"
/>
<!-- 瞬时总有功 -->
<g v-for="(item,index) in ammeterData" :key="item.id">
<text x="520" :y="105 + ( 20 * index)" fill="#ffffff" font-size="14">
{{ item.name }}
</text>
<text :x="countChineseAndEnglishCharacters(item.name,lang === 'zh'?510:470)" :y="105 + ( 20 * index)" fill="#FFB800" font-size="14">
{{ item.value }}
</text>
</g>
<!-- ACDC -->
<g v-for="(item,index) in acdcCenterData" :key="item.id">
<text x="415" :y="213 + ( 20 * index)" fill="#ffffff" font-size="14">
{{ item.name }}
</text>
<text :x="countChineseAndEnglishCharacters(item.name,lang === 'zh'?415:375)" :y="213 + ( 20 * index)" fill="#FFB800" font-size="14">
{{ item.value }}
</text>
</g>
<!-- 左侧 1PV -->
<g v-for="(item,index) in pcsLeftData" :key="item.id">
<foreignObject
:x="200"
:y="450 + (20 * index) - 14"
width="150"
height="20"
style="overflow: visible;"
>
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: center;">
<el-tooltip
:content="item.name"
placement="top"
:open-delay="400"
effect="dark"
popper-class="svg-tooltip"
>
<span style="font-size: 14px;font-family: sans-serif;color: #ffffff;max-width: 120px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis">
{{ truncateText(item.name, 120) }}
</span>
</el-tooltip>
</div>
</foreignObject>
<text :x="calculateValueX(item.name, 200)" :y="450 + ( 20 * index)" fill="#FFB800" font-size="14">
{{ item.value }}
</text>
</g>
<!-- 右侧 3PCS -->
<g v-for="(item,index) in pcsRightData" :key="item.id">
<text x="622" :y="450 + ( 20 * index)" fill="#ffffff" font-size="14">
{{ item.name }}
</text>
<text :x="countChineseAndEnglishCharacters(item.name,622)" :y="450 + ( 20 * index)" fill="#FFB800" font-size="14">
{{ item.value }}
</text>
</g>
</svg>
</div>
</div>
<DispositionPointData
:disposition-show="show_point_dispostion"
:max="4"
:page-location="pageLocation"
@close="closePoint"
@getData="getDynamicPointData"
/>
</ItemBox>
</template>
<script>
import gridImg from '@/assets/images/wxjd/grid.png'
import ammeterImg from '@/assets/images/wxjd/ammeter.png'
import cabinetImg from '@/assets/images/wxjd/cabinet.png'
import chargingPileImg from '@/assets/images/home-office.png'
import distributionCabinetImg from '@/assets/images/wxjd/guiImg.png'
import frameImg from '@/assets/images/wxjd/frame.png'
import cangImg from '@/assets/images/home-cang.png'
import { DynamicConfigPoint } from '@/api/home-page/index'
import config from './config'
import { changeTheme } from '@/utils/index'
import ACDCImg from '@/assets/images/wxjd/ACDC.png'
import DCImg from '@/assets/images/wxjd/DC.png'
import STSImg from '@/assets/images/wxjd/STS.png'
export default {
name: 'Index',
props: {
stationType: {
type: Number,
default: 0
},
stationId: {
type: Number,
default: 0
}
},
data() {
return {
gridImg,
ammeterImg,
cabinetImg,
chargingPileImg,
distributionCabinetImg,
cangImg,
frameImg,
ACDCImg,
DCImg,
STSImg,
dotData: {
lineTop: 'M 0,0 0,0',
lineTopLeft: 'M 0,0 0,0',
lineBottomLeft: 'M 0,0 0,0 0,0',
lineBottomRight: 'M 0,0 0,0 0,0',
lineBottomLeftTwo: 'M 0,0 0,0 0,0',
lineBottomRightTwo: 'M 0,0 0,0 0,0'
},
partList: [
{ soc: 0, soh: 0, activePowerPCS: 0.0, reactivePowerPCS: 0.0 },
{ soc: 0, soh: 0, activePowerPCS: 0.0, reactivePowerPCS: 0.0 },
{ soc: 0, soh: 0, activePowerPCS: 0.0, reactivePowerPCS: 0.0 },
{ soc: 0, soh: 0, activePowerPCS: 0.0, reactivePowerPCS: 0.0 }
],
loading: false,
activePowerTotal: {},
show_point_dispostion: false,
pageLocation: '',
permissionId: null,
pcsLeftData: [],
pcsCenterData: [],
pcsRightData: [],
stsCenterData: [],
acdcCenterData: [],
ammeterData: [],
configData: {}
}
},
computed: {
lang() {
return this.$store.getters.language
}
},
watch: {},
created() {
const result = changeTheme()
this.configData = config[result]
console.log('配置' + JSON.stringify(this.configData))
setTimeout(() => {
if (this.$store.getters.menuList.length) {
this.permissionId = this.$store.getters.menuList.find(item => {
return item.url === this.$route.path
}).id
}
}, 300)
},
mounted() {},
methods: {
truncateText(text, maxWidth) {
if (!text) return ''
const width = this.measureTextWidth(text)
if (width <= maxWidth) return text
let truncated = text
while (this.measureTextWidth(truncated + '...') > maxWidth && truncated.length > 0) {
truncated = truncated.slice(0, -1)
}
return truncated + (truncated.length < text.length ? '...' : '')
},
// 精确测量文本渲染宽度(像素)
measureTextWidth(text, font = '14px sans-serif') {
// 创建或复用一个 canvas避免重复创建
if (!this._textMeasurementCanvas) {
this._textMeasurementCanvas = document.createElement('canvas')
}
const ctx = this._textMeasurementCanvas.getContext('2d')
ctx.font = font
return ctx.measureText(text).width
},
// 计算 value 的 x 坐标baseX + name 的实际宽度 + 一点间距
calculateValueX(name, baseX = 200) {
const truncatedName = this.truncateText(name, 120) // 限制 name 最大宽度
const width = this.measureTextWidth(truncatedName)
return baseX + width + 6 // +6 是 name 和 value 之间的小空隙
},
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 * 11) + (obj.chineseCount * 16) + (obj.otherCount * 8) + x
},
lookDeviceDetail(val) { // 查看设备详情
this.pageLocation = val
this.show_point_dispostion = true
},
closePoint() {
this.show_point_dispostion = false
},
async getpcsCenter() {
this.loading = true
try {
const res = await DynamicConfigPoint({
pageLocation: 'triad-pcs-center',
permissionId: this.permissionId,
stationId: this.stationId })
this.pcsCenterData = res.data
} catch (error) {
// console.log(error);
} finally {
this.loading = false
}
},
// async getstsCenter() {
// this.loading = true
// try {
// const res = await DynamicConfigPoint({
// pageLocation: 'triad-sts-center',
// permissionId: this.permissionId,
// stationId: this.stationId })
// this.stsCenterData = res.data
// } catch (error) {
// // console.log(error);
// } finally {
// this.loading = false
// }
// },
async getacdcCenter() {
this.loading = true
try {
const res = await DynamicConfigPoint({
pageLocation: 'triad-acdc-center',
permissionId: this.permissionId,
stationId: this.stationId })
this.acdcCenterData = res.data
} catch (error) {
// console.log(error);
} finally {
this.loading = false
}
},
async getLineStatus(type) {
this.loading = true
try {
const res = await DynamicConfigPoint({
pageLocation: type,
permissionId: this.permissionId,
stationId: this.stationId })
if (type === 'line-bottom-left') {
if (res.data?.length) {
if (res.data[0].value > 0) {
this.dotData.lineBottomLeft = 'M 283,0, 280, 0, 0 ,0'
} else if (res.data[0].value < 0) {
this.dotData.lineBottomLeft = 'M 0,0 281,0 281,0'
} else {
this.dotData.lineBottomLeft = ''
}
} else {
this.dotData.lineBottomLeft = ''
}
} else if (type === 'line-bottom-right') {
if (res.data?.length) {
if (res.data[0].value > 0) {
this.dotData.lineBottomRight = 'M -271, 0 , -271 , 0 , 0 , 0'
} else if (res.data[0].value < 0) {
this.dotData.lineBottomRight = 'M 0,0 -271,0 -271,0'
} else {
this.dotData.lineBottomRight = ''
}
} else {
this.dotData.lineBottomRight = ''
}
} else if (type === 'line-bottom-center') {
if (res.data?.length) {
if (res.data[0].value > 0) {
this.dotData.lineCenter = 'M 0,-283,0,0,0,0'
} else if (res.data[0].value < 0) {
this.dotData.lineCenter = 'M 0,0,0,0,0,-283'
} else {
this.dotData.lineCenter = ''
}
} else {
this.dotData.lineCenter = ''
}
} else if (type === 'line-top-left') {
if (res.data?.length) {
if (res.data[0].value > 0) {
this.dotData.lineTopLeft = 'M 230,0 0,0'
} else if (res.data[0].value < 0) {
this.dotData.lineTopLeft = 'M 0,0 225,0'
} else {
this.dotData.lineTopLeft = ''
}
} else {
this.dotData.lineTopLeft = ''
}
} else if (type === 'line-top-right') {
if (res.data?.length) {
if (res.data[0].value > 0) {
this.dotData.lineTopRight = 'M -230,0 0,0'
} else if (res.data[0].value < 0) {
this.dotData.lineTopRight = 'M 0,0 -225,0'
} else {
this.dotData.lineTopRight = ''
}
} else {
this.dotData.lineTopRight = ''
}
}
} catch (error) {
// console.log(error);
} finally {
this.loading = false
}
},
async getpcsLeft() {
this.loading = true
try {
const res = await DynamicConfigPoint({
pageLocation: 'triad-pcs-left',
permissionId: this.permissionId,
stationId: this.stationId })
this.pcsLeftData = res.data
} catch (error) {
// console.log(error);
} finally {
this.loading = false
}
},
async getammeter() {
this.loading = true
try {
const res = await DynamicConfigPoint({
pageLocation: 'triad-ammeter',
permissionId: this.permissionId,
stationId: this.stationId })
this.ammeterData = res.data
} catch (error) {
// console.log(error);
} finally {
this.loading = false
}
},
async getpcsRight() {
this.loading = true
try {
const res = await DynamicConfigPoint({
pageLocation: 'triad-pcs-right',
permissionId: this.permissionId,
stationId: this.stationId })
this.pcsRightData = res.data
} catch (error) {
// console.log(error);
} finally {
this.loading = false
}
},
getData() {
this.getpcsRight()
this.getpcsCenter()
// this.getstsCenter()
this.getacdcCenter()
this.getpcsLeft()
this.getammeter()
this.getLineStatus('line-bottom-left')
this.getLineStatus('line-bottom-center')
this.getLineStatus('line-bottom-right')
this.getLineStatus('line-top-left')
this.getLineStatus('line-top-right')
this.$forceUpdate()
},
getDynamicPointData() {
this.getData()
}
}
}
</script>
<style lang="scss" scoped>
.center-box {
width: 100%;
height: 100%;
}
@mixin move-round($duration, $delay) {
fill: none;
stroke-width: 5;
stroke-linejoin: round;
stroke-linecap: round;
stroke-dasharray: 3, 900;
stroke-dashoffset: 0;
animation: lineMove $duration $delay cubic-bezier(0, 0, 0.74, 0.74) infinite;
}
@keyframes lineMove {
0% {
stroke-dashoffset: -850;
}
100% {
stroke-dashoffset: -0;
}
}
.g-rect-fill-two {
@include move-round(3.5s, 1s);
}
</style>

View File

@ -0,0 +1,822 @@
<template>
<ItemBox :title="$t('dashboard.stationTopo')" style="min-height: 560px;">
<div ref="svgLine" class="center-box">
<div v-loading="loading">
<svg
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xml:space="preserve"
class="circle-load-rect-svg"
viewBox="0 0 800 500"
>
<defs>
<defs>
<radialGradient id="RadialGradient1">
<stop offset="0%" stop-color="#fff" />
<stop offset="30%" stop-color="#0171c1" />
<stop offset="50%" stop-color="#035088" />
<stop offset="100%" stop-color="#02395a" />
</radialGradient>
</defs>
<marker
id="markerCircle"
markerWidth="15"
markerHeight="15"
refX="5"
refY="5"
>
<circle
cx="5"
cy="5"
r="5"
style="stroke: none; fill: url(#RadialGradient1)"
/>
</marker>
<marker
id="markerArrow"
markerWidth="13"
markerHeight="13"
refX="2"
refY="6"
orient="auto"
>
<path d="M2,2 L2,11 L10,6 L2,2" style="fill: black" />
</marker>
</defs>
<!-- //线条 -->
<!-- top -->
<polyline
points="120,50 650,50"
fill="none"
class="g-rect-path"
stroke="#0094FF"
stroke-dasharray="3 3"
/>
<!-- <rect
x="154"
y="78"
width="231"
height="4"
:style="{ fill: 'rgba(100,100,100,0)', stroke: 'transparent',cursor: 'pointer' }"
@click="lookDeviceDetail('line-top-left')"
/> -->
<!-- <polyline
points="385,80 600,80"
fill="none"
class="g-rect-path"
stroke="#0094FF"
stroke-dasharray="3 3"
/> -->
<!-- <rect
x="385"
y="78"
width="215"
height="4"
:style="{ fill: 'rgba(100,100,100,0)', stroke: 'transparent',cursor: 'pointer' }"
@click="lookDeviceDetail('line-top-right')"
/> -->
<!-- center -->
<polyline
points="385,50 385,417"
fill="none"
class="g-rect-path"
stroke="#0094FF"
stroke-dasharray="3 3"
/>
<!-- <rect
x="383"
y="80"
width="4"
height="281"
:style="{ fill: 'rgba(100,100,100,0)', stroke: 'transparent',cursor: 'pointer' }"
@click="lookDeviceDetail('line-bottom-center')"
/> -->
<polyline
points="385,100 460,100"
fill="none"
class="g-rect-path"
stroke="#0094FF"
stroke-dasharray="3 3"
/>
<!-- bottom -->
<polyline
points="175,283 385,283"
fill="none"
class="g-rect-path"
stroke="#0094FF"
stroke-dasharray="3 3"
/>
<polyline
points="175,283 175,417"
fill="none"
class="g-rect-path"
stroke="#0094FF"
stroke-dasharray="3 3"
/>
<!-- <rect
x="104"
y="361"
width="283"
height="4"
:style="{ fill: 'rgba(100,100,100,0)', stroke: 'transparent',cursor: 'pointer' }"
@click="lookDeviceDetail('line-bottom-left')"
/> -->
<polyline
points="385,283 603,283"
fill="none"
class="g-rect-path"
stroke="#0094FF"
stroke-dasharray="3 3"
/>
<polyline
points="603,283 603,417"
fill="none"
class="g-rect-path"
stroke="#0094FF"
stroke-dasharray="3 3"
/>
<!-- <rect
x="383"
y="361"
width="272"
height="4"
:style="{ fill: 'rgba(100,100,100,0)', stroke: 'transparent',cursor: 'pointer' }"
@click="lookDeviceDetail('line-bottom-right')"
/> -->
<!-- top -->
<circle
cx="120"
cy="50"
r="8"
style="stroke: none; fill: url(#RadialGradient1)"
/>
<circle
cx="650"
cy="50"
r="8"
style="stroke: none; fill: url(#RadialGradient1)"
/>
<!-- 动点 -->
<circle
cx="120"
cy="50"
r="8"
style="stroke: none; fill: url(#RadialGradient1)"
>
<animateMotion
repeatCount="indefinite"
dur="4s"
begin="0s"
:path="dotData.lineTopLeft"
/>
</circle>
<circle
cx="385"
cy="417"
r="8"
style="stroke: none; fill: url(#RadialGradient1)"
>
<animateMotion
repeatCount="indefinite"
dur="4s"
begin="2s"
:path="dotData.lineCenter"
/>
</circle>
<circle
cx="650"
cy="50"
r="8"
style="stroke: none; fill: url(#RadialGradient1)"
>
<animateMotion
repeatCount="indefinite"
dur="4s"
begin="0s"
:path="dotData.lineTopRight"
/>
</circle>
<!-- bottom -->
<circle
cx="175"
cy="417"
r="8"
style="stroke: none; fill: url(#RadialGradient1)"
/>
<circle
cx="385"
cy="417"
r="8"
style="stroke: none; fill: url(#RadialGradient1)"
/>
<circle
cx="603"
cy="417"
r="8"
style="stroke: none; fill: url(#RadialGradient1)"
/>
<circle
cx="385"
cy="417"
r="8"
style="stroke: none; fill: url(#RadialGradient1)"
/>
<circle
cx="175"
cy="417"
r="8"
style="stroke: none; fill: url(#RadialGradient1)"
>
<animateMotion
repeatCount="indefinite"
dur="4s"
begin="2s"
:path="dotData.lineBottomLeft"
/>
</circle>
<circle
cx="603"
cy="417"
r="8"
style="stroke: none; fill: url(#RadialGradient1)"
>
<animateMotion
repeatCount="indefinite"
dur="4s"
begin="2s"
:path="dotData.lineBottomRight"
/>
</circle>
<!-- 图片和文字 -->
<!-- 市电 -->
<image
:xlink:href="configData.grid"
x="60"
y="20"
width="50"
height="70"
/>
<!-- <text x="90" y="160" fill="#ffffff" font-size="14"> {{ $t("dashboard.grid") }}</text> -->
<!-- {{ $t("dashboard.load") }} -->
<image
:xlink:href="configData.house"
x="660"
y="20"
width="50"
height="70"
/>
<!-- <text x="645" y="160" fill="#ffffff" font-size="14"> {{ $t("dashboard.load") }}</text> -->
<!-- 1PV -->
<image
:xlink:href="frameImg"
x="145"
y="430"
width="50"
height="50"
style="cursor: pointer;"
@click="lookDeviceDetail('triad-pcs-left')"
/>
<!-- DCDC -->
<image
:xlink:href="DCImg"
x="157"
y="330"
width="35"
height="35"
/>
<!-- <text x="86" y="490" fill="#ffffff" font-size="14">1#{{ $t("dashboard.cabinet") }}</text> -->
<!-- 2PV -->
<image
:xlink:href="frameImg"
x="355"
y="430"
width="50"
height="50"
style="cursor: pointer;"
@click="lookDeviceDetail('triad-pcs-center')"
/>
<!-- DCDC -->
<image
:xlink:href="DCImg"
x="367"
y="330"
width="35"
height="35"
/>
<!-- <text x="360" y="490" fill="#ffffff" font-size="14">
2#{{ $t("dashboard.cabinet") }}
</text> -->
<!-- 3 -->
<image
:xlink:href="configData.pcs2"
x="574"
y="430"
width="50"
height="50"
style="cursor: pointer;"
@click="lookDeviceDetail('triad-pcs-right')"
/>
<!-- <text x="614" y="490" fill="#ffffff" font-size="14">3#{{ $t("dashboard.cabinet") }}</text> -->
<!-- STS -->
<image
:xlink:href="STSImg"
x="365"
y="130"
width="40"
height="40"
style="cursor: pointer;"
@click="lookDeviceDetail('triad-sts-center')"
/>
<!-- AC/DC -->
<image
:xlink:href="ACDCImg"
x="368"
y="200"
width="35"
height="35"
style="cursor: pointer;"
@click="lookDeviceDetail('triad-acdc-center')"
/>
<!-- {{ $t("dashboard.ammeter") }} -->
<image
:xlink:href="ammeterImg"
x="450"
y="80"
width="70"
height="42"
style="cursor: pointer;"
@click="lookDeviceDetail('triad-ammeter')"
/>
<!-- 瞬时总有功 -->
<g v-for="(item,index) in ammeterData" :key="item.id">
<text x="520" :y="105 + ( 20 * index)" fill="#ffffff" font-size="14">
{{ item.name }}
</text>
<text :x="countChineseAndEnglishCharacters(item.name,lang === 'zh'?510:465)" :y="105 + ( 20 * index)" fill="#FFB800" font-size="14">
{{ item.value }}
</text>
</g>
<!-- STS -->
<g v-for="(item,index) in stsCenterData" :key="item.id">
<text x="415" :y="153 + ( 20 * index)" fill="#ffffff" font-size="14">
{{ item.name }}
</text>
<text :x="countChineseAndEnglishCharacters(item.name,lang === 'zh'?415:360)" :y="153 + ( 20 * index)" fill="#FFB800" font-size="14">
{{ item.value }}
</text>
</g>
<!-- ACDC -->
<g v-for="(item,index) in acdcCenterData" :key="item.id">
<text x="415" :y="213 + ( 20 * index)" fill="#ffffff" font-size="14">
{{ item.name }}
</text>
<text :x="countChineseAndEnglishCharacters(item.name,lang === 'zh'?415:360)" :y="213 + ( 20 * index)" fill="#FFB800" font-size="14">
{{ item.value }}
</text>
</g>
<!-- 左侧 1PV -->
<g v-for="(item,index) in pcsLeftData" :key="item.id">
<foreignObject
:x="200"
:y="450 + (20 * index) - 14"
width="150"
height="20"
style="overflow: visible;"
>
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: center;">
<el-tooltip
:content="item.name"
placement="top"
:open-delay="400"
effect="dark"
popper-class="svg-tooltip"
>
<span style="font-size: 14px;font-family: sans-serif;color: #ffffff;max-width: 120px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis">
{{ truncateText(item.name, 100) }}
</span>
</el-tooltip>
</div>
</foreignObject>
<!-- <text x="200" :y="450 + ( 20 * index)" fill="#ffffff" font-size="14">
{{ item.name }}
</text> -->
<text :x="calculateValueX(item.name, 185)" :y="450 + ( 20 * index)" fill="#FFB800" font-size="14">
{{ item.value }}
</text>
</g>
<!-- 中间 2PV -->
<g v-for="(item,index) in pcsCenterData" :key="item.id">
<foreignObject
:x="410"
:y="450 + (20 * index) - 14"
width="150"
height="20"
style="overflow: visible;"
>
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: center;">
<el-tooltip
:content="item.name"
placement="top"
:open-delay="400"
effect="dark"
popper-class="svg-tooltip"
>
<span style="font-size: 14px;font-family: sans-serif;color: #ffffff;max-width: 120px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis">
{{ truncateText(item.name, 100) }}
</span>
</el-tooltip>
</div>
</foreignObject>
<!-- <text x="410" :y="450 + ( 20 * index)" fill="#ffffff" font-size="14">
{{ item.name }}
</text> -->
<text :x="calculateValueX(item.name, 395)" :y="450 + ( 20 * index)" fill="#FFB800" font-size="14">
{{ item.value }}
</text>
</g>
<!-- 右侧 3PCS -->
<g v-for="(item,index) in pcsRightData" :key="item.id">
<text x="622" :y="450 + ( 20 * index)" fill="#ffffff" font-size="14">
{{ item.name }}
</text>
<text :x="countChineseAndEnglishCharacters(item.name,622)" :y="450 + ( 20 * index)" fill="#FFB800" font-size="14">
{{ item.value }}
</text>
</g>
</svg>
</div>
</div>
<DispositionPointData
:disposition-show="show_point_dispostion"
:max="4"
:page-location="pageLocation"
@close="closePoint"
@getData="getDynamicPointData"
/>
</ItemBox>
</template>
<script>
import gridImg from '@/assets/images/wxjd/grid.png'
import ammeterImg from '@/assets/images/wxjd/ammeter.png'
import cabinetImg from '@/assets/images/wxjd/cabinet.png'
import chargingPileImg from '@/assets/images/home-office.png'
import distributionCabinetImg from '@/assets/images/wxjd/guiImg.png'
import frameImg from '@/assets/images/wxjd/frame.png'
import cangImg from '@/assets/images/home-cang.png'
import { DynamicConfigPoint } from '@/api/home-page/index'
import config from './config'
import { changeTheme } from '@/utils/index'
import ACDCImg from '@/assets/images/wxjd/ACDC.png'
import DCImg from '@/assets/images/wxjd/DC.png'
import STSImg from '@/assets/images/wxjd/STS.png'
export default {
name: 'Index',
props: {
stationType: {
type: Number,
default: 0
},
stationId: {
type: Number,
default: 0
}
},
data() {
return {
gridImg,
ammeterImg,
cabinetImg,
chargingPileImg,
distributionCabinetImg,
cangImg,
frameImg,
ACDCImg,
DCImg,
STSImg,
dotData: {
lineTop: 'M 0,0 0,0',
lineTopLeft: 'M 0,0 0,0',
lineBottomLeft: 'M 0,0 0,0 0,0',
lineBottomRight: 'M 0,0 0,0 0,0',
lineBottomLeftTwo: 'M 0,0 0,0 0,0',
lineBottomRightTwo: 'M 0,0 0,0 0,0'
},
partList: [
{ soc: 0, soh: 0, activePowerPCS: 0.0, reactivePowerPCS: 0.0 },
{ soc: 0, soh: 0, activePowerPCS: 0.0, reactivePowerPCS: 0.0 },
{ soc: 0, soh: 0, activePowerPCS: 0.0, reactivePowerPCS: 0.0 },
{ soc: 0, soh: 0, activePowerPCS: 0.0, reactivePowerPCS: 0.0 }
],
loading: false,
activePowerTotal: {},
show_point_dispostion: false,
pageLocation: '',
permissionId: null,
pcsLeftData: [],
pcsCenterData: [],
pcsRightData: [],
stsCenterData: [],
acdcCenterData: [],
ammeterData: [],
configData: {}
}
},
computed: {
lang() {
return this.$store.getters.language
}
},
watch: {},
created() {
const result = changeTheme()
this.configData = config[result]
console.log('配置' + JSON.stringify(this.configData))
setTimeout(() => {
if (this.$store.getters.menuList.length) {
this.permissionId = this.$store.getters.menuList.find(item => {
return item.url === this.$route.path
}).id
}
}, 300)
},
mounted() {},
methods: {
truncateText(text, maxWidth) {
if (!text) return ''
const width = this.measureTextWidth(text)
if (width <= maxWidth) return text
let truncated = text
while (this.measureTextWidth(truncated + '...') > maxWidth && truncated.length > 0) {
truncated = truncated.slice(0, -1)
}
return truncated + (truncated.length < text.length ? '...' : '')
},
// 精确测量文本渲染宽度(像素)
measureTextWidth(text, font = '14px sans-serif') {
// 创建或复用一个 canvas避免重复创建
if (!this._textMeasurementCanvas) {
this._textMeasurementCanvas = document.createElement('canvas')
}
const ctx = this._textMeasurementCanvas.getContext('2d')
ctx.font = font
return ctx.measureText(text).width
},
// 计算 value 的 x 坐标baseX + name 的实际宽度 + 一点间距
calculateValueX(name, baseX = 200) {
const truncatedName = this.truncateText(name, 120) // 限制 name 最大宽度
const width = this.measureTextWidth(truncatedName)
return baseX + width + 6 // +6 是 name 和 value 之间的小空隙
},
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 * 11) + (obj.chineseCount * 16) + (obj.otherCount * 8) + x
},
lookDeviceDetail(val) { // 查看设备详情
this.pageLocation = val
this.show_point_dispostion = true
},
closePoint() {
this.show_point_dispostion = false
},
async getpcsCenter() {
this.loading = true
try {
const res = await DynamicConfigPoint({
pageLocation: 'triad-pcs-center',
permissionId: this.permissionId,
stationId: this.stationId })
this.pcsCenterData = res.data
} catch (error) {
// console.log(error);
} finally {
this.loading = false
}
},
async getstsCenter() {
this.loading = true
try {
const res = await DynamicConfigPoint({
pageLocation: 'triad-sts-center',
permissionId: this.permissionId,
stationId: this.stationId })
this.stsCenterData = res.data
} catch (error) {
// console.log(error);
} finally {
this.loading = false
}
},
async getacdcCenter() {
this.loading = true
try {
const res = await DynamicConfigPoint({
pageLocation: 'triad-acdc-center',
permissionId: this.permissionId,
stationId: this.stationId })
this.acdcCenterData = res.data
} catch (error) {
// console.log(error);
} finally {
this.loading = false
}
},
async getLineStatus(type) {
this.loading = true
try {
const res = await DynamicConfigPoint({
pageLocation: type,
permissionId: this.permissionId,
stationId: this.stationId })
if (type === 'line-bottom-left') {
if (res.data?.length) {
if (res.data[0].value > 0) {
this.dotData.lineBottomLeft = 'M 283,0, 280, 0, 0 ,0'
} else if (res.data[0].value < 0) {
this.dotData.lineBottomLeft = 'M 0,0 281,0 281,0'
} else {
this.dotData.lineBottomLeft = ''
}
} else {
this.dotData.lineBottomLeft = ''
}
} else if (type === 'line-bottom-right') {
if (res.data?.length) {
if (res.data[0].value > 0) {
this.dotData.lineBottomRight = 'M -271, 0 , -271 , 0 , 0 , 0'
} else if (res.data[0].value < 0) {
this.dotData.lineBottomRight = 'M 0,0 -271,0 -271,0'
} else {
this.dotData.lineBottomRight = ''
}
} else {
this.dotData.lineBottomRight = ''
}
} else if (type === 'line-bottom-center') {
if (res.data?.length) {
if (res.data[0].value > 0) {
this.dotData.lineCenter = 'M 0,-283,0,0,0,0'
} else if (res.data[0].value < 0) {
this.dotData.lineCenter = 'M 0,0,0,0,0,-283'
} else {
this.dotData.lineCenter = ''
}
} else {
this.dotData.lineCenter = ''
}
} else if (type === 'line-top-left') {
if (res.data?.length) {
if (res.data[0].value > 0) {
this.dotData.lineTopLeft = 'M 230,0 0,0'
} else if (res.data[0].value < 0) {
this.dotData.lineTopLeft = 'M 0,0 225,0'
} else {
this.dotData.lineTopLeft = ''
}
} else {
this.dotData.lineTopLeft = ''
}
} else if (type === 'line-top-right') {
if (res.data?.length) {
if (res.data[0].value > 0) {
this.dotData.lineTopRight = 'M -230,0 0,0'
} else if (res.data[0].value < 0) {
this.dotData.lineTopRight = 'M 0,0 -225,0'
} else {
this.dotData.lineTopRight = ''
}
} else {
this.dotData.lineTopRight = ''
}
}
} catch (error) {
// console.log(error);
} finally {
this.loading = false
}
},
async getpcsLeft() {
this.loading = true
try {
const res = await DynamicConfigPoint({
pageLocation: 'triad-pcs-left',
permissionId: this.permissionId,
stationId: this.stationId })
this.pcsLeftData = res.data
} catch (error) {
// console.log(error);
} finally {
this.loading = false
}
},
async getammeter() {
this.loading = true
try {
const res = await DynamicConfigPoint({
pageLocation: 'triad-ammeter',
permissionId: this.permissionId,
stationId: this.stationId })
this.ammeterData = res.data
} catch (error) {
// console.log(error);
} finally {
this.loading = false
}
},
async getpcsRight() {
this.loading = true
try {
const res = await DynamicConfigPoint({
pageLocation: 'triad-pcs-right',
permissionId: this.permissionId,
stationId: this.stationId })
this.pcsRightData = res.data
} catch (error) {
// console.log(error);
} finally {
this.loading = false
}
},
getData() {
this.getpcsRight()
this.getpcsCenter()
this.getstsCenter()
this.getacdcCenter()
this.getpcsLeft()
this.getammeter()
this.getLineStatus('line-bottom-left')
this.getLineStatus('line-bottom-center')
this.getLineStatus('line-bottom-right')
this.getLineStatus('line-top-left')
this.getLineStatus('line-top-right')
this.$forceUpdate()
},
getDynamicPointData() {
this.getData()
}
}
}
</script>
<style lang="scss" scoped>
.center-box {
width: 100%;
height: 100%;
}
@mixin move-round($duration, $delay) {
fill: none;
stroke-width: 5;
stroke-linejoin: round;
stroke-linecap: round;
stroke-dasharray: 3, 900;
stroke-dashoffset: 0;
animation: lineMove $duration $delay cubic-bezier(0, 0, 0.74, 0.74) infinite;
}
@keyframes lineMove {
0% {
stroke-dashoffset: -850;
}
100% {
stroke-dashoffset: -0;
}
}
.g-rect-fill-two {
@include move-round(3.5s, 1s);
}
</style>

View File

@ -343,7 +343,7 @@
</svg> </svg>
</div> </div>
</div> </div>
<dispositionPointDialog :visible="show_point_dispostion" :max="4" :page-location="pageLocation" @close="closePoint" @getData="getDynamicPointData" /> <DispositionPointData :disposition-show="show_point_dispostion" :max="4" :page-location="pageLocation" @close="closePoint" @getData="getDynamicPointData" />
</ItemBox> </ItemBox>
</template> </template>

View File

@ -460,7 +460,7 @@
</svg> </svg>
</div> </div>
</div> </div>
<dispositionPointDialog :visible="show_point_dispostion" :page-location="pageLocation" @close="closePoint" @getData="getDynamicPointData" /> <DispositionPointData :disposition-show="show_point_dispostion" :page-location="pageLocation" @close="closePoint" @getData="getDynamicPointData" />
</ItemBox> </ItemBox>
</template> </template>

View File

@ -158,6 +158,8 @@ import dispositionBottomRight from './components/bottom-right/disposition.vue'
import { getDashboard } from '@/api/station/maintain' import { getDashboard } from '@/api/station/maintain'
import { queryElecMeterConfig } from '@/api/home-page/index' import { queryElecMeterConfig } from '@/api/home-page/index'
import pv1AndStorage_261 from './components/top-center/pv1storage261.vue'
import pv2AndStorage_261 from './components/top-center/pv2storage261.vue'
export default { export default {
name: 'Index', name: 'Index',
components: { components: {
@ -200,7 +202,9 @@ export default {
dispositionBottomRight, dispositionBottomRight,
controlTopRight, controlTopRight,
secondDispositionTopCenter, secondDispositionTopCenter,
tenthTopCenter tenthTopCenter,
pv1AndStorage_261,
pv2AndStorage_261
}, },
props: {}, props: {},

View File

@ -39,6 +39,19 @@
<div class="unit">万元</div> <div class="unit">万元</div>
</div> </div>
</div> </div>
<div class="income today-income">
<div class="title">
<span class="square" />
<span>今日实时收益</span>
</div>
<div class="value">
<template v-for="(item,index) in info.todayProfit">
<div :key="index" class="number">{{ item }}</div>
</template>
<div class="unit"></div>
</div>
</div>
</div> </div>
</template> </template>
@ -73,10 +86,12 @@ export default {
height: 100%; height: 100%;
display: flex; display: flex;
justify-content: space-around; justify-content: space-around;
flex-wrap: wrap;
.today-income{
width: 90%;
}
.income { .income {
height: 100%; height: 100%;
padding-top: 15px;
z-index: 99999; z-index: 99999;
.title { .title {
display: flex; display: flex;

View File

@ -85,10 +85,12 @@ export default {
leftBottomInfo: null, leftBottomInfo: null,
deptId: null, deptId: null,
interval: null, interval: null,
intervalTodayIncome: null,
centerTopInfo: { centerTopInfo: {
totalProfit: 0, totalProfit: 0,
yearProfit: 0, yearProfit: 0,
yestProfit: 0 yestProfit: 0,
todayProfit: 0
}, },
weatherInfo: { weatherInfo: {
skyCon: '', skyCon: '',
@ -108,7 +110,9 @@ export default {
}, },
destroyed() { destroyed() {
clearInterval(this.interval) clearInterval(this.interval)
clearInterval(this.intervalTodayIncome)
this.interval = null this.interval = null
this.intervalTodayIncome = null
}, },
methods: { methods: {
// 修改Favicon的方法 // 修改Favicon的方法
@ -148,6 +152,47 @@ export default {
this.$refs.MapCenterRef.getLeftData(this.deptId) this.$refs.MapCenterRef.getLeftData(this.deptId)
this.$refs.CenterBottomRef.getData(this.deptId) this.$refs.CenterBottomRef.getData(this.deptId)
// const res = await GetOverviewData({
// deptId: this.deptId
// })
// this.leftTopInfo = {
// capacity: res.data.capacity,
// stationNumber: res.data.stationNumber,
// yearCharge: res.data.yearCharge,
// yearDischarge: res.data.yearDischarge,
// totalCharge: res.data.totalCharge,
// totalDischarge: res.data.totalDischarge,
// dayCharge: res.data.dayCharge,
// dayDischarge: res.data.dayDischarge
// }
// this.centerTopInfo = {
// totalProfit: (Number(res.data.totalProfit) / 1E4).toFixed(2),
// yearProfit: (Number(res.data.yearProfit) / 1E4).toFixed(2),
// yestProfit: (Number(res.data.yestProfit) / 1E4).toFixed(2),
// todayProfit: Number(res.data.todayProfit).toFixed(2)
// }
} catch (err) {
// this.leftTopInfo = {
// capacity: 0,
// stationNumber: 0,
// yearCharge: 0,
// yearDischarge: 0,
// totalCharge: 0,
// totalDischarge: 0,
// dayCharge: 0,
// dayDischarge: 0
// }
// this.centerTopInfo = {
// totalProfit: 0,
// yearProfit: 0,
// yestProfit: 0,
// todayProfit: 0
// }
}
},
async getDataIncome() {
try {
const res = await GetOverviewData({ const res = await GetOverviewData({
deptId: this.deptId deptId: this.deptId
}) })
@ -161,11 +206,11 @@ export default {
dayCharge: res.data.dayCharge, dayCharge: res.data.dayCharge,
dayDischarge: res.data.dayDischarge dayDischarge: res.data.dayDischarge
} }
this.centerTopInfo = { this.centerTopInfo = {
totalProfit: (Number(res.data.totalProfit) / 1E4).toFixed(2), totalProfit: (Number(res.data.totalProfit) / 1E4).toFixed(2),
yearProfit: (Number(res.data.yearProfit) / 1E4).toFixed(2), yearProfit: (Number(res.data.yearProfit) / 1E4).toFixed(2),
yestProfit: (Number(res.data.yestProfit) / 1E4).toFixed(2) yestProfit: (Number(res.data.yestProfit) / 1E4).toFixed(2),
todayProfit: Number(res.data.todayProfit).toFixed(2)
} }
} catch (err) { } catch (err) {
this.leftTopInfo = { this.leftTopInfo = {
@ -181,7 +226,8 @@ export default {
this.centerTopInfo = { this.centerTopInfo = {
totalProfit: 0, totalProfit: 0,
yearProfit: 0, yearProfit: 0,
yestProfit: 0 yestProfit: 0,
todayProfit: 0
} }
} }
}, },
@ -196,9 +242,13 @@ export default {
}).then(res => { }).then(res => {
this.deptId = res.data?.deptId this.deptId = res.data?.deptId
this.getData() this.getData()
this.getDataIncome()
this.interval = setInterval(() => { this.interval = setInterval(() => {
this.getData() this.getData()
}, 600000) }, 600000)
this.intervalTodayIncome = setInterval(() => {
this.getDataIncome()
}, 10000)
}) })
} }
} }

View File

@ -205,7 +205,7 @@ import {
GetPageSelectAll, GetPageSelectAll,
DeleteTemplate DeleteTemplate
} from '@/api/surveillance/battery-analysis' } from '@/api/surveillance/battery-analysis'
import { GetQueryPointList } from '@/api/surveillance/battery-analysis' import { GetQueryPoint } from '@/api/surveillance/battery-analysis'
import ModelKuang from '@/components/ModelKuang/index.vue' import ModelKuang from '@/components/ModelKuang/index.vue'
import { pageSize, pageSizes } from '@/config' import { pageSize, pageSizes } from '@/config'
import Pagingbar from '@/components/Pagingbar' import Pagingbar from '@/components/Pagingbar'
@ -663,7 +663,7 @@ export default {
stationId: this.filters.stationId stationId: this.filters.stationId
} }
try { try {
const res = await GetQueryPointList(params) const res = await GetQueryPoint(params)
const option = res.data const option = res.data
// 将指标的数据处理成树 // 将指标的数据处理成树
if (option.length > 0) { if (option.length > 0) {

View File

@ -13,6 +13,8 @@ const leftBottomList = [
] ]
const topCenterList = [ const topCenterList = [
{ label: '215kWh', value: 'topCenter_215' }, { label: '215kWh', value: 'topCenter_215' },
{ label: '261光储1', value: 'pv1AndStorage_261' },
{ label: '261光储2', value: 'pv2AndStorage_261' },
{ label: '标准', value: 'commonTopCenter' }, { label: '标准', value: 'commonTopCenter' },
{ label: '单柜配置', value: 'onceTopCenter' }, { label: '单柜配置', value: 'onceTopCenter' },
{ label: '二合一', value: 'secondTopCenter' }, { label: '二合一', value: 'secondTopCenter' },
@ -26,7 +28,8 @@ const topCenterList = [
{ label: '七合一(配置)', value: 'seventhTopCenter' }, { label: '七合一(配置)', value: 'seventhTopCenter' },
{ label: '八合一(配置)', value: 'eighthTopCenter' }, { label: '八合一(配置)', value: 'eighthTopCenter' },
{ label: '九合一(配置)', value: 'ninthTopCenter' }, { label: '九合一(配置)', value: 'ninthTopCenter' },
{ label: '十合一(配置)', value: 'tenthTopCenter' } { label: '十合一(配置)', value: 'tenthTopCenter' },
{ label: '一百二十合一(配置)', value: 'zzhbTopCenter' }
] ]
const rightTopList = [ const rightTopList = [
{ label: '实时告警', value: 'topRight' }, { label: '实时告警', value: 'topRight' },

View File

@ -14,7 +14,7 @@ const name = defaultSettings.title || '弘正新能源' // page title
// You can change the port by the following method: // You can change the port by the following method:
// port = 9527 npm run dev OR npm run dev --port = 9527 // port = 9527 npm run dev OR npm run dev --port = 9527
const port = process.env.port || process.env.npm_config_port || 9527 // dev port const port = process.env.port || process.env.npm_config_port || 10324 // dev port
// All configuration item explanations can be find in https://cli.vuejs.org/config/ // All configuration item explanations can be find in https://cli.vuejs.org/config/
module.exports = { module.exports = {