7 Commits

46 changed files with 2090 additions and 184 deletions

View File

@ -4,7 +4,7 @@ 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
VUE_APP_BASE_API = 'http://121.237.176.143:9091/api'
VUE_APP_BASE_API = 'https://zetatech.zzkj-cloud.com/api'
VUE_APP_VISUALE_API = http://192.168.0.82:9527
VUE_APP_ZUTAI_API = http://192.168.100.83:8001

View File

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

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

@ -0,0 +1,161 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Privacy Policy</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>Privacy Policy</h1>
<div class="section">
<h2>Introduction</h2>
<p>ZETATECH HONG KONG INVESTMENT LIMITED (hereinafter referred to as "we") highly values the protection of personal information and strictly complies with applicable data protection laws and regulations. We sincerely appreciate your trust in this application.</p>
<p>As a service provider dedicated to [application business areas such as energy storage project display], we fully understand the importance of protecting user privacy globally. This Privacy Policy aims to clearly explain how we collect, use, store, share, and protect your personal information, ensuring our practices meet international data protection standards and comply with legal requirements in each region where we operate, including but not limited to the EU General Data Protection Regulation (GDPR), California Consumer Privacy Act (CCPA), the Cybersecurity Law of the People's Republic of China, the Personal Information Protection Law of the People's Republic of China, and the Data Security Law of the People's Republic of China.</p>
</div>
<div class="section">
<h2>1. Scope and Methods of Information Collection</h2>
<p>When you use this application (including accessing features within the app, registering an account, using services, etc.), we may directly collect the following personal information:</p>
<h3>(1) Basic Business Required Information</h3>
<ul>
<li>Basic identification information: phone number/email address (for verification), username, password, IP address (security risk control);</li>
<li>Device and network information: IP address, device model, operating system version, device identifier (such as IMEI/Android ID/OpenUDID, OAID, device model, etc.), network type (Wi-Fi/mobile network), browser type, etc. (to ensure service stability, security risk control, and functional adaptation);</li>
<li>Log information: access records, operation logs, error logs (to optimize service);</li>
<li>Location services: real-time location information (requires enabling location permissions for querying nearby energy storage sites);</li>
<li>Permission descriptions: reading device status (to ensure login security and obtain OAID information), network access (data transmission).</li>
</ul>
<h3>(2) Optional Feature Additional Information</h3>
<ul>
<li>Location services: real-time location information (requires enabling location permissions for querying nearby energy storage sites)</li>
<li>Storage permissions: read/write external storage (caching map data to reduce data consumption)</li>
<li>All additional functions require separate authorization; refusal does not affect basic services.</li>
</ul>
</div>
<div class="section">
<h2>2. Purpose of Information Usage</h2>
<p>The collected personal information will be used for the following reasonable and necessary purposes:</p>
<ol>
<li>Providing basic services: implementing account registration/login, energy storage project display and interaction, business process handling (e.g., order/service applications), customer service responses, and other core functionalities;</li>
<li>Ensuring security and compliance: performing security risk control through device/network information (identifying abnormal logins, preventing malicious attacks), complying with legal requirements (such as retaining data for regulatory inspection);</li>
<li>Business operations and improvements: internal data analysis (e.g., analyzing user needs, evaluating service effectiveness) to drive product iteration and service upgrades;</li>
</ol>
</div>
<div class="section">
<h2>3. Cross-border Data Transfer</h2>
<p>Normally, your personal information will be stored in [the regions where the application servers are located, e.g., domestic Huawei Cloud server (Amazon Web Services server in Paris, France)].</p>
<p>To ensure data security, we take the following measures:</p>
<ol>
<li>Compliance assessment and authorization: conducting legality assessments on cross-border data transfers to ensure compliance with domestic and target region data protection regulations;</li>
<li>Technical protection: adopting encryption during transmission, access controls, and other technical measures to reduce the risk of data leakage;</li>
<li>Contractual constraints: signing strict data protection agreements with overseas recipients, clarifying both parties' responsibilities and obligations, requiring them to safeguard data security.</li>
</ol>
</div>
<div class="section">
<h2>4. Sharing and Disclosure of Information</h2>
<p>We commit to strictly keeping your personal information confidential and will not share or disclose it to any unrelated third parties except under the following circumstances:</p>
<h3>(1) Necessary Business Collaboration</h3>
<p>We delegate third-party providers such as Alibaba Cloud, Huawei Cloud, and Amazon Web Services to provide technical support, authorizing them to process information only within necessary limits and having signed strict confidentiality agreements.</p>
<p>We share necessary information with carefully selected third-party service providers (e.g., cloud service providers, data analytics companies) to support application functionality implementation (e.g., server hosting, data statistics and analysis). We will only share the minimum scope of information required to complete the collaboration and require partners to sign confidentiality agreements to constrain their data processing activities.</p>
<h3>(2) Legal Requirements</h3>
<p>In cases of mandatory legal requirements (e.g., court subpoenas, administrative investigations) or fulfilling statutory obligations (e.g., cooperating with regulatory compliance checks), we may legally disclose necessary personal information.</p>
<h3>(3) Corporate Restructuring/Mergers & Acquisitions</h3>
<p>If there is a significant change such as corporate merger, division, or asset transfer, we will follow the principles of "legality, necessity, minimization" when transferring personal information and notify you in advance through prominent means (e.g., in-app announcements, emails).</p>
</div>
<div class="section">
<h2>5. Information Security and Protection</h2>
<p>We are committed to protecting your personal information through both technical and management measures:</p>
<h3>(1) Technical Protection</h3>
<ul>
<li>Implementing data encryption (e.g., transmission encryption, storage encryption), access control (e.g., account password verification, tiered permission management), security audits (regularly checking system logs), etc., to prevent data leakage, tampering, and unauthorized access;</li>
<li>Real-time monitoring of important systems and services to promptly detect and address security vulnerabilities and abnormal access behaviors.</li>
</ul>
<h3>(2) Management Safeguards</h3>
<ul>
<li>Establishing strict data security management systems, clearly defining employee data access rights and operational guidelines;</li>
<li>Regularly conducting employee data security training to enhance overall privacy protection awareness;</li>
<li>Conducting compliance audits on data processing procedures to ensure adherence to policy requirements.</li>
</ul>
</div>
<div class="section">
<h2>6. Your Rights Regarding Personal Information and How to Exercise Them</h2>
<p>Based on applicable laws and regulations, you have the following rights regarding your personal information:</p>
<ul>
<li><strong>Query and Correction:</strong> Within the app path → My → Settings → Personal Information Management</li>
<li><strong>Delete Request:</strong> Supports account cancellation and scenarios of non-compliant information processing</li>
<li><strong>Permission Management:</strong> Phone settings → App Management → “STORA SMART” → disable corresponding permissions</li>
<li><strong>Account Deactivation:</strong> Within the app path → My → Settings → Account Security → Deactivate Account</li>
</ul>
</div>
<div class="section">
<h2>7. Changes and Notifications to This Privacy Policy</h2>
<p>We will periodically update this Privacy Policy to reflect business adjustments and regulatory changes. After updates, we will notify you via the following methods:</p>
<ul>
<li>In-app pop-ups, push notifications;</li>
<li>Email;</li>
<li>Prominent prompts on the “Settings - Privacy Policy” page within the app.</li>
</ul>
<p>Please review this policy regularly. Continuing to use the application constitutes acceptance of the updated terms.</p>
</div>
<div class="section">
<h2>8. Protection of Minors and Contact Information</h2>
<p>We do not actively collect personal information from minors under the age of 14. If the guardian discovers that personal information of minors under the age of 14 has been collected, or if you have any questions, suggestions, or need to exercise your personal information rights regarding this privacy policy, you can contact us through the following methods:</p>
<ul>
<li>Customer Service Email: <a href="mailto:info@zetatech-energy.com">info@zetatech-energy.com</a> (We will respond within 48 hours);</li>
<li>Mailing Address: No. 88 Gunan Street, Pidu District, Chengdu City, Sichuan Province (Postal Code: 611730).</li>
</ul>
<p>This policy is interpreted by ZETATECH HONG KONG INVESTMENT LIMITED. In case of conflicts with laws and regulations, laws and regulations shall prevail; if conflicting with other rules within the app, this policy (updated version) shall prevail.</p>
</div>
<div class="section">
<p><strong>[Release Date: July 15, 2025]</strong></p>
<p><strong>[Effective Date: July 15, 2025]</strong></p>
</div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@ -202,20 +202,20 @@ export function kgFormat(num, digits) {
export function moneyUnitFormat(num) {
if (sessionStorage.getItem('language') === 'en') {
if (!num) {
return 'RMB'
return 'EUR'
}
if (Number(num) >= 1E4) {
return 'M RMB'
return 'EUR10K'
}
return 'RMB'
return 'EUR'
} else {
if (!num) {
return ''
return 'EUR'
}
if (Number(num) >= 1E4) {
return '万元'
return 'EUR10K'
}
return ''
return 'EUR'
}
}
/**
@ -231,7 +231,7 @@ export function KgUnitFormat(num) {
return 'kg'
}
if (Number(num) >= 1E3) {
return 'T'
return 't'
}
return 'kg'
} else {

View File

@ -32,13 +32,13 @@ export default {
stationRunPlan: 'The actual operation plan curve of the power station',
runEarning: 'Actual operating earnings',
recommend: 'Referral strategy earnings',
valley: '谷(0.1kWh/)',
flat: '平(0.3kWh/)',
peak: '峰(0.6kWh/)',
valley: '谷(0.1kWh/EUR)',
flat: '平(0.3kWh/EUR)',
peak: '峰(0.6kWh/EUR)',
earningForecast: 'Revenue forecasts',
defaultEarning: 'Default policy earnings',
estimateEarning: 'Estimated earnings increase',
rmb: 'RMB',
rmb: 'EUR',
stts: 'Earnings budgeting',
sjsyje: 'Amount of actual earnings',
mrjhqx: 'Default plan curve earnings',

View File

@ -32,13 +32,13 @@ export default {
stationRunPlan: '电站实际运行计划曲线',
runEarning: '实际运行收益',
recommend: '推荐策略收益',
valley: '谷(0.1kWh/)',
flat: '平(0.3kWh/)',
peak: '峰(0.6kWh/)',
valley: '谷(0.1kWh/EUR)',
flat: '平(0.3kWh/EUR)',
peak: '峰(0.6kWh/EUR)',
earningForecast: '收益预测',
defaultEarning: '默认策略收益',
estimateEarning: '预估收益提升',
rmb: '',
rmb: 'EUR',
stts: '收益预算',
sjsyje: '实际收益金额',
mrjhqx: '默认计划曲线收益',

View File

@ -89,7 +89,7 @@ export default {
sure: 'Sure',
cancel: 'Cancel',
more: 'More',
tem: 'Temperature',
tem: 'TEMP',
xxxq: 'Message details',
pleaseInput: 'Please input',
sureRead: 'Confirm has been read',
@ -106,7 +106,7 @@ export default {
noPagePermiss: 'There is no permission for this page'
},
login: {
title: 'Hoenergy smart energy storage management platform',
title: 'Zetatech smart energy storage management platform',
logIn: 'Login',
username: 'Username',
password: 'Password',

View File

@ -80,9 +80,11 @@ export function getLanguage() {
// if has not choose language
const language = (navigator.language || navigator.browserLanguage).toLowerCase()
const locales = Object.keys(messages)
console.log(JSON.stringify('语言:' + language, locales))
for (const locale of locales) {
if (language.indexOf(locale) > -1) {
return locale
return locale !== 'en' ? 'en' : locale
}
}
return 'en'

View File

@ -34,7 +34,7 @@ export default {
selectTemValid: 'Please select template validity',
type: 'Type',
selectType: 'Please select type',
electrovalency: 'Electricity(RMB)',
electrovalency: 'Electricity(EUR)',
sure: 'Sure',
cancel: 'Cancel',
noSelectData: 'No data is selected',
@ -121,7 +121,7 @@ export default {
img: 'Image',
eleLevel: 'Electricity Price Level',
fsdd: 'The price of time-of-use electricity',
dietailUnit: 'RMB/kWh',
dietailUnit: 'EUR/kWh',
eleprice: 'The price of electricity and electricity',
historytrend: 'Historical trends',
curve: 'Curve',
@ -154,11 +154,11 @@ export default {
charge: 'Charge',
time: 'Time',
ele: 'Electricity(kWh)',
expend: 'expenditures(RMB)',
expend: 'expenditures(EUR)',
disCharge: 'Discharge',
projectRevenue: 'Project Revenue',
monthTotalChargePrice: 'Total Charging Price Monthly',
rmb: 'RMB',
rmb: 'EUR',
monthTotalDisChargePrice: 'Total Discharging Price Monthly',
earnings: 'Revenue',
earningsRecalculation: 'Revenue Recalculation',
@ -204,7 +204,7 @@ export default {
},
glance: {
day: 'day',
wRMB: 'Million RMB',
wRMB: 'EUR10K',
safeDays: 'Safe Operation Days',
totalCapacity: 'Total Installed Capacity',
totalCharge: 'Total Charging Volume',
@ -232,7 +232,7 @@ export default {
setRevenue: 'Settlement Revenue',
chargeCost: 'Charging Cost',
dischargeRev: 'Discharge Revenue',
rmb: 'RMB',
rmb: 'EUR',
setRatio: 'Settlement Ratio',
selectSetRatio: 'Please select settlement ratio',
priceCurve: 'Real-Time Electricity Price Curve',
@ -243,9 +243,9 @@ export default {
noData: 'No Data',
inputNumber: 'Please input a number from 0-100',
earningGlance: 'Revenue Glance',
unit: 'Unit:RMB/kWh',
unit: 'Unit:EUR/kWh',
unitKw: 'Unit:kW',
planCurve: 'Plan Curve',
unitRMB: 'Unit:RMB'
unitRMB: 'Unit:EUR'
}
}

View File

@ -34,7 +34,7 @@ export default {
selectTemValid: '请选择模板有效期',
type: '类型',
selectType: '请选择类型',
electrovalency: '电价()',
electrovalency: '电价(EUR)',
sure: '确认',
cancel: '取消',
noSelectData: '未选择数据',
@ -123,7 +123,7 @@ export default {
img: '图片',
eleLevel: '电价水平',
fsdd: '分时电度用电价格',
dietailUnit: '/千瓦时',
dietailUnit: 'EUR/千瓦时',
eleprice: '电度用电价格',
historytrend: '历史趋势',
curve: '曲线',
@ -153,11 +153,11 @@ export default {
charge: '充电',
time: '时段',
ele: '电量(kWh)',
expend: '支出()',
expend: '支出(EUR)',
disCharge: '放电',
projectRevenue: '项目收益情况',
monthTotalChargePrice: '本月总充电量总价',
rmb: '',
rmb: 'EUR',
monthTotalDisChargePrice: '本月总放电量总价',
earnings: '收益',
earningsRecalculation: '收益重算',
@ -203,7 +203,7 @@ export default {
},
glance: {
day: '天',
wRMB: '万元',
wRMB: 'EUR10K',
safeDays: '安全运行天数',
totalCapacity: '装机总容量',
totalCharge: '总充电量',
@ -231,7 +231,7 @@ export default {
setRevenue: '结算收益',
chargeCost: '充电成本',
dischargeRev: '放电收益',
rmb: '',
rmb: 'EUR',
setRatio: '计算比例',
selectSetRatio: '请选择结算比例',
priceCurve: '实时电价曲线',
@ -242,9 +242,9 @@ export default {
noData: '暂无数据',
inputNumber: '请输入0-100的数字',
earningGlance: '收益概览',
unit: '单位:/kWh',
unit: '单位:EUR/kWh',
unitKw: '单位:kW',
planCurve: '计划曲线',
unitRMB: '单位:'
unitRMB: '单位:EUR'
}
}

View File

@ -9,19 +9,19 @@ export default {
totalCharge: 'Total charge',
totalDisCharge: 'Total discharge',
regionStation: 'Regional distribution of power stations',
totalStation: 'Total Station',
totalStation: 'Total Stations',
noData: 'No Data',
energySaving: 'Energy Conservation',
planted: 'Equivalent tree plantings',
co2: 'Equivalent CO2 reduction',
coal: 'Equivalent coal savings',
income: 'Equivalent economic income',
tree: 'Tree',
tree: 'Trees',
yesEarning: "Yesterday's Earnings",
annualEarning: 'Annual Income',
totalEarning: 'Total Revenue',
groupEarning: 'Group Income',
mRMB: 'Million RMB',
mRMB: 'EUR10K',
high: 'High',
low: 'Low',
comTime: 'Commission Time',
@ -37,7 +37,8 @@ export default {
stationName: 'Station Name',
cap: 'Capacity(kWh)',
Eff: 'Efficiency(%)',
groupData: 'Group Income',
groupData: 'Group statistics',
ztscreenTitle: 'Zetatech Smart energy storage management and control platform',
screenTitle: 'Smart energy storage management and control platform',
xjpScreenTitle: 'FAVFAM Microgrid Al Monitor Platform',
cncharge: 'Energy Storage power',

View File

@ -2,8 +2,8 @@ export default {
screen: {
capacity: '装机容量',
stationNum: '电站个数',
dayCharge: '日充电量',
dayDisCharge: '日放电量',
dailyCharge: '日充电量',
dailyDischarge: '日放电量',
yearCharge: '年充电量',
yearDisCharge: '年放电量',
totalCharge: '总充电量',
@ -21,7 +21,7 @@ export default {
annualEarning: '年收益',
totalEarning: '总收益',
groupEarning: '集团收益',
mRMB: '万元',
mRMB: 'EUR10K',
high: '高',
low: '低',
comTime: '投运时间',
@ -38,6 +38,7 @@ export default {
cap: '容量(kWh)',
Eff: '转换效率(%)',
groupData: '集团数据',
ztscreenTitle: '智慧储能管控平台',
screenTitle: '智慧储能管控平台',
xjpScreenTitle: '新加坡微电网平台',
cncharge: '储能发电量',

View File

@ -280,7 +280,7 @@ export default {
safeLowerError: 'The minimum security capacity cannot be greater than the maximum security capacity',
saveSuccess: 'Save succeeded',
buildSuccess: 'Generate succeeded',
unit: 'Unit: RMB/kWh',
unit: 'Unit: EUR/kWh',
loadCurve: 'Load curve',
importSuccess: 'Import succeeded',
newUnit: 'Unit:kWh'

View File

@ -277,7 +277,7 @@ export default {
safeLowerError: '安全容量下限不能大于安全容量上限',
saveSuccess: '保存成功',
buildSuccess: '生成成功',
unit: '单位:/kWh',
unit: '单位:EUR/kWh',
loadCurve: '负荷曲线',
importSuccess: '导入成功',
newUnit: '单位:kW'

View File

@ -889,7 +889,7 @@ export default {
day: '日',
power: '发电量',
benefit: '收益',
wanji: '万元'
wanji: 'EUR10K'
},
sophCharging: { // 智慧充电
station: '电站',
@ -905,7 +905,7 @@ export default {
online: '在线',
offline: '离线',
accruedIncome: '累计收益',
tenThousand: '万元',
tenThousand: 'EUR10K',
faultNum: '故障个数',
acAuto: '交流汽车',
chargeBenefit: '充电桩日充电量/收益',
@ -920,9 +920,9 @@ export default {
peak: '峰',
currentPos: '当前位置',
chargeFees: '充电收费',
chargeFeesUnit: '/kWh',
chargeFeesUnit: 'EUR/kWh',
serviceFee: '服务费',
servecrFeeUnit: '/次',
servecrFeeUnit: 'EUR/次',
pileType: '充电桩类型',
chargeNum: '今日充电次数',
free: '空闲',

View File

@ -88,9 +88,9 @@
class="others-wrapper"
:style="{ 'min-width': clientWidth > 800 && language === 'zh' ? '520px' : '200px' }"
>
<div v-if="weatherData && clientWidth > 600 && language === 'zh'" class="weather">
<div v-if="weatherData && clientWidth > 600" class="weather">
<div v-if="+stationId !== 1069" class="item">
<img :src="weather[weatherData.skyCon]" alt>
<img :src="weatheren[weatherData.code]" alt>
</div>
<div
v-if="weatherData && +stationId !== 1069"
@ -104,7 +104,7 @@
<div class="item">
<div class="weather-text">{{ weatherData.skyCon }}</div>
<div class="weather-text">
{{ $t('navbar.tem') }}{{ fixNumber(weatherData.minTemperature) }}-{{
{{ $t('navbar.tem') }} {{ fixNumber(weatherData.minTemperature) }}-{{
fixNumber(weatherData.maxTemperature)
}}
</div>
@ -401,6 +401,56 @@ export default {
yuzhuanqing,
zhongxue,
zhongyu,
weatheren: {
1000: qing,
1003: duoyun,
1006: duoyun,
1009: yintian,
1030: wu,
1063: xiaoyu,
1066: xiaoxue,
1069: yujiaxue,
1072: yujiaxue,
1087: leiyu,
1114: daxue,
1117: daxue,
1135: wu,
1147: wu,
1150: xiaoyu,
1153: xiaoyu,
1168: yujiaxue,
1171: yujiaxue,
1180: xiaoyu,
1183: xiaoyu,
1186: zhongyu,
1189: zhongyu,
1192: dayu,
1195: dayu,
1198: yujiaxue,
1201: yujiaxue,
1204: leiyu,
1207: leiyu,
1210: xiaoxue,
1213: xiaoxue,
1216: zhongxue,
1219: zhongxue,
1222: daxue,
1225: daxue,
1237: yujiaxue,
1240: xiaoyu,
1243: dayu,
1246: baoyu,
1249: yujiaxue,
1252: yujiaxue,
1255: zhongxue,
1258: daxue,
1261: yujiaxue,
1264: yujiaxue,
1273: leiyu,
1276: leiyu,
1279: yujiaxue,
1282: yujiaxue
},
weather: {
: qing,
少云: duoyun,
@ -994,7 +1044,7 @@ export default {
flex-shrink: 0;
font-size: 18px !important;
cursor: pointer;
min-width: 100px;
// min-width: 100px;
i {
font-size: 28px;
@ -1049,7 +1099,7 @@ export default {
height: 100%;
display: flex;
align-items: center;
flex-wrap: wrap;
// padding-right: 15px;
.iconfont {
font-size: 40px;

View File

@ -26,13 +26,18 @@
:collapse-transition="false"
mode="vertical"
>
<sidebar-item
<el-tooltip
v-for="route in menuList"
:key="route.id"
:item="route"
:base-path="route.url"
:active-path="activePath"
/>
:content="route.title"
placement="top-start"
>
<sidebar-item
:item="route"
:base-path="route.url"
:active-path="activePath"
/>
</el-tooltip>
</el-menu>
</el-scrollbar>
<div class="open-btn" @click="handleClickOutside">

View File

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

View File

@ -13,7 +13,7 @@ const service = axios.create({
// request interceptor
service.interceptors.request.use(
config => {
config.headers['lang'] = sessionStorage.getItem('language') === 'en' ? 'en_US' : 'zh_CN'
config.headers['lang'] = sessionStorage.getItem('language') === 'zh' ? 'zh_CN' : 'en_US'
// do something before request is sent
if (config.headers.isScreen) {
config.headers['authorization'] = getScreenToken()

View File

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

View File

@ -534,7 +534,7 @@
</svg>
</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>
</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>
</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>
</template>

View File

@ -460,7 +460,7 @@
</svg>
</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>
</template>

View File

@ -158,6 +158,9 @@ import dispositionBottomRight from './components/bottom-right/disposition.vue'
import { getDashboard } from '@/api/station/maintain'
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 {
name: 'Index',
components: {
@ -200,8 +203,9 @@ export default {
dispositionBottomRight,
controlTopRight,
secondDispositionTopCenter,
tenthTopCenter
tenthTopCenter,
pv1AndStorage_261,
pv2AndStorage_261
},
props: {},
data() {

View File

@ -6,9 +6,7 @@
<img :src="logo">
</div>
<div class="title">
智慧储能管控平台
</div>
<div class="title">Zetatech smart energy storage management platform</div>
</div>
<el-form
ref="loginForm"
@ -25,7 +23,7 @@
<el-input
ref="username"
v-model="loginForm.username"
placeholder="账号"
placeholder="username"
name="username"
type="text"
tabindex="1"
@ -48,7 +46,7 @@
ref="password"
v-model="loginForm.password"
:type="passwordType"
placeholder="密码"
placeholder="password"
name="password"
tabindex="2"
autocomplete="on"
@ -70,7 +68,7 @@
class="login-btn"
@click.native.prevent="handleLogin"
>
登录
Login
</el-button>
<!-- <div class="forget-psd">
<lang-select class="set-language" />
@ -106,7 +104,7 @@
<script>
// import LangSelect from '@/components/LangSelect'
import SocialSign from './components/SocialSignin'
import logo from '@/assets/login_images/zhongzilogo.png'
import logo from '@/assets/login_images/zetatechlogo.png'
// import { validURL } from '@/utils/validate'
export default {
@ -337,7 +335,7 @@ $light_gray: #eee;
.login-content {
width: fit-content;
max-width: 100%;
max-width: 355px;
margin: 0 auto;
overflow: hidden;
margin-bottom: 40px;
@ -346,7 +344,7 @@ $light_gray: #eee;
align-items: center;
// box-shadow: rgba(0, 0, 0, 0.15) 0px 15px 25px, rgba(0, 0, 0, 0.05) 0px 5px 10px;
.title-container {
width: 100%;
// width: 100%;
position: relative;
.logo {
width: 100%;
@ -358,9 +356,9 @@ $light_gray: #eee;
}
.title {
font-size: 2rem;
font-size: 1.5rem;
color: #fff;
margin: 30px 20px 40px;
margin: 30px 0 40px;
text-align: center;
letter-spacing: 5px;
}
@ -382,7 +380,7 @@ $light_gray: #eee;
.login-form-item {
width: 100%;
background: #fff;
margin-bottom: 20px;
margin-bottom: 25px;
::v-deep .el-form-item__content {
display: flex;
}

View File

@ -1,6 +1,6 @@
<template>
<div class="center-bottom-warp">
<item :title="'日充放电对比'">
<item :title="$t('screen.chargeDischargeComparison')">
<dv-loading v-if="Loading">Loading...</dv-loading>
<Chart v-else :options="options" />
</item>
@ -20,6 +20,11 @@ export default {
Loading: true
}
},
computed: {
lang() {
return this.$store.getters.language
}
},
created() {
},
@ -62,8 +67,8 @@ export default {
right: 30,
itemWidth: 14,
data: [
{ icon: 'image://' + dayChargeIcon, name: '充电' },
{ icon: 'image://' + dayDischargeIcon, name: '放电' }
{ icon: 'image://' + dayChargeIcon, name: this.$t('screen.charge') },
{ icon: 'image://' + dayDischargeIcon, name: this.$t('screen.discharge') }
]
},
@ -129,7 +134,7 @@ export default {
// 柱体
{
name: '充电',
name: this.$t('screen.charge'),
type: 'bar',
barGap: '20%',
@ -157,7 +162,7 @@ export default {
data: dayCharge
},
{
name: '放电',
name: this.$t('screen.discharge'),
type: 'bar',
barGap: '20%',
barCategoryGap: '50%', /* 多个并排柱子设置柱子之间的间距*/
@ -189,7 +194,7 @@ export default {
} else {
this.options = {
title: {
text: '暂无数据',
text: this.$t('screen.noData'),
x: 'center',
y: 'center',
textStyle: {

View File

@ -3,40 +3,40 @@
<div class="income">
<div class="title">
<span class="square" />
<span>昨日收益</span>
<span>{{ $t('screen.yesEarning') }}</span>
</div>
<div class="value">
<template v-for="(item,index) in info.yestProfit">
<div :key="index" class="number">{{ item }}</div>
</template>
<div class="unit">万元</div>
<div class="unit">{{ $t('screen.mRMB') }}</div>
</div>
</div>
<div class="income">
<div class="title">
<span class="square" />
<span>年收益</span>
<span>{{ $t('screen.annualEarning') }}</span>
</div>
<div class="value">
<template v-for="(item,index) in info.yearProfit">
<div :key="index" class="number">{{ item }}</div>
</template>
<div class="unit">万元</div>
<div class="unit">{{ $t('screen.mRMB') }}</div>
</div>
</div>
<div class="income">
<div class="title">
<span class="square" />
<span>总收益</span>
<span>{{ $t('screen.totalEarning') }}</span>
</div>
<div class="value">
<template v-for="(item,index) in info.totalProfit">
<div :key="index" class="number">{{ item }}</div>
</template>
<div class="unit">万元</div>
<div class="unit">{{ $t('screen.mRMB') }}</div>
</div>
</div>
</div>

View File

@ -1,17 +1,17 @@
<template>
<div class="left-bottom-warp">
<item title="节能减排">
<item :title="$t('screen.energySaving')">
<dv-loading v-if="loading">Loading...</dv-loading>
<div v-else class="top-all-box">
<div class="top-con-box">
<div class="top-item-box">
<div class="data">{{ info.treePlanting }}<span class="unit"></span></div>
<div class="title">等效植树量</div>
<div class="data">{{ info.treePlanting }}<span class="unit">{{ $t('screen.tree') }}</span></div>
<div class="title">{{ $t('screen.planted') }}</div>
<img :src="screenItemBg" class="item-bg-img">
</div>
<div class="top-item-box">
<div class="data">{{ info.reductionCO2 | kgFormat }}<span class="unit">{{ info.reductionCO2 | KgUnitFormat }}</span></div>
<div class="title">等效CO2减排</div>
<div class="title">{{ $t('screen.co2') }}</div>
<img :src="screenItemBg" class="item-bg-img">
</div>
@ -19,12 +19,12 @@
<div class="top-con-box">
<div class="top-item-box">
<div class="data">{{ info.equivalentCoal | kgFormat }}<span class="unit">{{ info.equivalentCoal | KgUnitFormat }}</span></div>
<div class="title">等效节约煤</div>
<div class="title">{{ $t('screen.coal') }}</div>
<img :src="screenItemBg" class="item-bg-img">
</div>
<div class="top-item-box">
<div class="data">{{ info.income | moneyFormat }}<span class="unit">{{ info.income | moneyUnitFormat }}</span></div>
<div class="title">等效经济收入</div>
<div class="title">{{ $t('screen.income') }}</div>
<img :src="screenItemBg" class="item-bg-img">
</div>
</div>
@ -57,9 +57,7 @@ export default {
},
mounted() {
},
methods: {
async getData(deptId) {
try {
this.loading = true

View File

@ -1,7 +1,7 @@
<template>
<div class="left-center-warp">
<item :title="'电站区域分布'">
<item :title="$t('screen.regionStation')">
<dv-loading v-if="loading">Loading...</dv-loading>
<div v-else class="chart-box">
<Chart :options="options" :class-name="'chart'" />
@ -54,7 +54,7 @@ export default {
const count = this.arrCount(ydata)
this.options = {
title: [{
text: '电站总数',
text: this.$t('screen.totalStation'),
subtext: count,
subtextStyle: {

View File

@ -1,46 +1,78 @@
<template>
<div class="left-top-warp">
<item>
<item :title="$t('screen.groupData')">
<dv-loading v-if="loading">Loading...</dv-loading>
<div v-else class="top-all-box">
<div class="value-box">
<div class="title">装机容量</div>
<div class="value">{{ info.capacity |kwhFormat }}</div>
<el-tooltip class="item" effect="dark" :content="$t('screen.capacity')" placement="top">
<div class="title">{{ $t('screen.capacity') }}</div>
</el-tooltip>
<el-tooltip class="item" effect="dark" :content="info.capacity |kwhFormat" placement="top">
<div class="value">{{ info.capacity |kwhFormat }}</div>
</el-tooltip>
<div class="unit">{{ info.capacity | kwhUnitFormat }}</div>
</div>
<div class="value-box">
<div class="title">电站个数</div>
<div class="value">{{ info.stationNumber }}</div>
<div class="unit"></div>
<el-tooltip class="item" effect="dark" :content="$t('screen.stationNum')" placement="top">
<div class="title">{{ $t('screen.stationNum') }}</div>
</el-tooltip>
<el-tooltip class="item" effect="dark" :content="info.stationNumber" placement="top">
<div class="value">{{ info.stationNumber }}</div>
</el-tooltip>
<div class="unit">{{ lang === 'zh'? '个':'' }}</div>
</div>
<div class="value-box">
<div class="title">{{ $t('dashboard.dailyCharge') }}</div>
<div class="value">{{ info.dayCharge |kwhFormat }}</div>
<el-tooltip class="item" effect="dark" :content="$t('screen.dailyCharge')" placement="top">
<div class="title">{{ $t('screen.dailyCharge') }}</div>
</el-tooltip>
<el-tooltip class="item" effect="dark" :content="info.dayCharge |kwhFormat" placement="top">
<div class="value">{{ info.dayCharge |kwhFormat }}</div>
</el-tooltip>
<div class="unit">{{ info.dayCharge |kwhUnitFormat }}</div>
</div>
<div class="value-box">
<div class="title">{{ $t('dashboard.dailyDischarge') }}</div>
<div class="value">{{ info.dayDischarge |kwhFormat }}</div>
<el-tooltip class="item" effect="dark" :content="$t('screen.dailyDischarge')" placement="top">
<div class="title">{{ $t('screen.dailyDischarge') }}</div>
</el-tooltip>
<el-tooltip class="item" effect="dark" :content="info.dayDischarge |kwhFormat" placement="top">
<div class="value">{{ info.dayDischarge |kwhFormat }}</div>
</el-tooltip>
<div class="unit">{{ info.dayDischarge |kwhUnitFormat }}</div>
</div>
<div class="value-box">
<div class="title">年充电量</div>
<div class="value">{{ info.yearCharge |kwhFormat }}</div>
<el-tooltip class="item" effect="dark" :content="$t('screen.yearCharge')" placement="top">
<div class="title">{{ $t('screen.yearCharge') }}</div>
</el-tooltip>
<el-tooltip class="item" effect="dark" :content="info.yearCharge |kwhFormat" placement="top">
<div class="value">{{ info.yearCharge |kwhFormat }}</div>
</el-tooltip>
<div class="unit">{{ info.yearCharge |kwhUnitFormat }}</div>
</div>
<div class="value-box">
<div class="title">年放电量</div>
<div class="value">{{ info.yearDischarge |kwhFormat }}</div>
<el-tooltip class="item" effect="dark" :content="$t('screen.yearDisCharge')" placement="top">
<div class="title">{{ $t('screen.yearDisCharge') }}</div>
</el-tooltip>
<el-tooltip class="item" effect="dark" :content="info.yearDischarge |kwhFormat" placement="top">
<div class="value">{{ info.yearDischarge |kwhFormat }}</div>
</el-tooltip>
<div class="unit">{{ info.yearDischarge |kwhUnitFormat }}</div>
</div>
<div class="value-box">
<div class="title">总充电量</div>
<div class="value">{{ info.totalCharge |kwhFormat }}</div>
<el-tooltip class="item" effect="dark" :content="$t('screen.totalCharge')" placement="top">
<div class="title">{{ $t('screen.totalCharge') }}</div>
</el-tooltip>
<el-tooltip class="item" effect="dark" :content="info.totalCharge |kwhFormat" placement="top">
<div class="value">{{ info.totalCharge |kwhFormat }}</div>
</el-tooltip>
<div class="unit">{{ info.totalCharge |kwhUnitFormat }}</div>
</div>
<div class="value-box">
<div class="title">{{ $t('dashboard.totalDischarge') }}</div>
<div class="value">{{ info.totalDischarge |kwhFormat }}</div>
<el-tooltip class="item" effect="dark" :content="$t('screen.totalDisCharge')" placement="top">
<div class="title">{{ $t('screen.totalDisCharge') }}</div>
</el-tooltip>
<el-tooltip class="item" effect="dark" :content="info.totalDischarge |kwhFormat" placement="top">
<div class="value">{{ info.totalDischarge |kwhFormat }}</div>
</el-tooltip>
<div class="unit">{{ info.totalDischarge |kwhUnitFormat }}</div>
</div>
</div>
@ -64,10 +96,14 @@ export default {
loading: true
}
},
computed: {
lang() {
return this.$store.getters.language
}
},
watch: {
info: {
handler(val) {
console.log(1111, val)
if (val) {
this.loading = false
} else {
@ -115,6 +151,10 @@ export default {
.title{
color: rgba(179, 221, 253, 1);
overflow: hidden;
text-overflow: ellipsis;
min-width: 90px;
max-width: 90px;
padding-left: 16px;
white-space: nowrap;
}

View File

@ -14,8 +14,9 @@
</div>
</template>
<script>
import chinaMap from '@/assets/mapJson/chinaMap.json'
import { chinaMapOutline } from '@/assets/mapJson/chinaMapOut.js'
// import chinaMap from '@/assets/mapJson/chinaMap.json'
import worldJson from 'echarts/map/json/world.json'
// import { chinaMapOutline } from '@/assets/mapJson/chinaMapOut.js'
import blue from '../../../assets/new-screen/map-blue.png'
import { GetStationInfo, GetCapacity, GetRegionalDistribution } from '@/api/screen/zzScreen'
export default {
@ -80,14 +81,14 @@ export default {
const province = []
if (data.length) {
data.forEach((el) => {
el.value = [el.latitude, el.longitude]
el.value = [el.longitude, el.latitude]
el.symbol = 'image://' + blue
el.cityCode = 1000
province.push(el)
})
this.pointData = province
} else {
this.pointData = [{ symbol: 'image://' + blue, cityCode: 1000, value: [118.8062, 31.9208], name: '江苏' }]
this.pointData = [{ symbol: 'image://' + blue, cityCode: 1000, value: [30.787045, 103.923008], name: 'Sichuan' }]
}
} finally {
this.loading = false
@ -95,15 +96,14 @@ export default {
this.getInitMap()
},
getInitMap() {
this.$echarts.registerMap('china', chinaMap)
this.$echarts.registerMap('chinaMapOutline', chinaMapOutline)
this.$echarts.registerMap('world', worldJson)
var series = [
{
map: 'china',
name: '国家',
map: 'world',
type: 'map',
roam: false,
zoom: 1.65,
zoom: 1.1,
label: {
normal: {
show: false,
@ -117,7 +117,7 @@ export default {
}
}
},
top: '29%',
top: '10%',
tooltip: {
show: false
},
@ -150,14 +150,14 @@ export default {
itemStyle: {
normal: {
color: '#F4E925',
shadowColor: '#333'
}
},
symbolSize: 50,
data: [this.pointData[this.pointIndex]]
symbolSize: 24,
symbolOffset: [0, '-50%'], // 或 [0, -12] 更精确
data: [this.pointData[this.pointIndex]],
// showEffectOn: 'render' // 加载完毕显示特效
showEffectOn: 'render' // 加载完毕显示特效
}
]
@ -167,7 +167,7 @@ export default {
max: Math.max(...this.stationNum),
right: 'right',
top: 'bottom',
text: ['高(电站个数)', '低(电站个数)'],
text: [this.$t('screen.high') + '(' + this.$t('screen.stationNum') + ')', this.$t('screen.low') + '(' + this.$t('screen.stationNum') + ')'],
calculable: true,
inRange: {
// color: ['#5edfd6', '#37d4cf', '#14c9c9', '#0da5aa', '#07828b']
@ -187,25 +187,25 @@ export default {
trigger: 'item',
alwaysShowContent: true,
backgroundColor: 'transparent',
position: 'top',
position: 'bottom',
triggerOn: 'click',
enterable: true,
formatter: params => {
// 获取xAxis data中的数据
let dataStr = `<div></div>`
dataStr += `<div>
<span style="padding-left:10px;font-weight:bold;margin-left:20px;margin-top:5px;font-size="14px">${params.name}</span>
<span style="padding-left:20px;font-weight:bold;margin-left:20px;margin-top:5px;font-size="14px">${params.name}</span>
</div>`
dataStr += `<div style="margin-top:15px">
<span style="padding-left:10px;">投运时间</span>
<span style="padding-left:10px;">${this.$t('screen.comTime')}</span>
<span style="margin-left:5px;margin-right:10px;color:rgba(245, 221, 0, 1);font-family:DIN;">${params.data.createTime}</span>
</div>`
dataStr += `<div style="margin-top:2px;">
<span style="padding-left:10px;">装机容量</span>
<span style="padding-left:10px;">${this.$t('screen.capacity')}</span>
<span style="margin-left:5px;margin-right:5px;color:rgba(245, 221, 0, 1);font-family:DIN;">${params.data.capacity}</span>
<span style="color:rgba(245, 221, 0, 1);">kWh</span>
</div>`
const div = `<div style='max-width:220px;height:85px;
const div = `<div style='height:85px;
background-image:url(${require('../../../assets/new-screen/tooltip-bg.png')});background-repeat: no-repeat;background-size:100% 100%;' >${dataStr}</div>`
return div
}
@ -214,10 +214,8 @@ export default {
geo: [
{
silent: true,
map: 'chinaMapOutline',
map: 'world',
zoom: 1.1,
top: '10%',
label: {
normal: {
show: false,
@ -255,8 +253,7 @@ export default {
{
silent: true,
map: 'chinaMapOutline',
zoom: 1.1,
zoom: 1.2,
top: '7%',
label: {
normal: {

View File

@ -1,7 +1,7 @@
<template>
<div class="left-bottom-warp">
<item title="系统转换效率">
<item :title="$t('screen.sysEff')">
<dv-loading v-if="tableLoading">Loading...</dv-loading>
<div v-else class="con-box">
<SwiperTable
@ -27,9 +27,9 @@ export default {
data() {
return {
tableLoading: false,
tableTitles: ['电站名称', '容量(kWh)', '转换效率(%)'],
tableTitles: [this.$t('screen.stationName'), this.$t('screen.cap'), this.$t('screen.Eff')],
tableData: [],
widths: ['200', 'auto', 'auto', 'auto'],
widths: ['auto', 'auto', 'auto', 'auto'],
contentHeight: 200,
tableColumns: [
'stationName',
@ -66,5 +66,8 @@ export default {
width: 100%;
padding-top: 10px;
}
}
}
.class1{
color: #F4E925;
}
</style>

View File

@ -1,6 +1,6 @@
<template>
<div class="right-top-warp">
<item :title="'充放收益排名'">
<item :title="$t('screen.earningRanking')">
<dv-loading v-if="tableLoading">Loading...</dv-loading>
<div v-else class="con-box">
<SwiperTableCenter
@ -47,7 +47,7 @@ export default {
})
this.tableData = res.data
this.tableData.map((el) => {
el.incomeValue = el.incomeValue + '万元'
el.incomeValue = el.incomeValue + ' ' + this.$t('screen.mRMB')
})
} finally {
this.tableLoading = false

View File

@ -1,6 +1,6 @@
<template>
<div class="right-top-warp">
<item title="集团收益">
<item :title="$t('screen.groupEarning')">
<div class="box">
<div class="button-box">
<el-tabs
@ -8,9 +8,9 @@
tab-position="top"
@tab-click="handleClick"
>
<el-tab-pane label="近30天" name="day" />
<el-tab-pane label="月度" name="month" />
<el-tab-pane label="年度" name="year" />
<el-tab-pane :label="$t('screen.daily30')" name="day" />
<el-tab-pane :label="$t('screen.monthly')" name="month" />
<el-tab-pane :label="$t('screen.yearly')" name="year" />
</el-tabs>
</div>
<div class="chart-box">
@ -108,7 +108,7 @@ export default {
yAxis: [
{
type: 'value',
name: '万元',
name: this.$t('screen.mRMB'),
nameTextStyle: {
color: '#6E7174',
fontSize: 12,
@ -137,7 +137,7 @@ export default {
],
series: [
{
name: '收益',
name: this.$t('screen.earning'),
type: 'line',
data: profit_data,
smooth: true,
@ -176,7 +176,7 @@ export default {
} else {
this.options = {
title: {
text: '暂无数据',
text: this.$t('screen.noData'),
x: 'center',
y: 'center',
textStyle: {

View File

@ -6,17 +6,14 @@
<!-- 头部 s -->
<div class="title_wrap">
<div class="title">
<span class="left-title" />
<span class="title-text">中自智慧储能管控平台</span>
<span :class="lang === 'zh'? 'left-title' : 'left-title-en'" />
<span :class="lang === 'zh'? 'title-text' : 'en-eitle-text'">{{ $t('screen.ztscreenTitle') }}</span>
<div class="right-title">
<span>{{ time }}</span>
<span class="weather">{{ weatherInfo ? weatherInfo.skyCon : '' }} {{ weatherInfo? weatherInfo.minTemperature :'' }} ~ {{ weatherInfo ? weatherInfo.maxTemperature : '' }}</span>
<!-- <span class="weather">{{ weatherInfo ? weatherInfo.skyCon : '' }} {{ weatherInfo? weatherInfo.minTemperature :'' }} ~ {{ weatherInfo ? weatherInfo.maxTemperature : '' }}</span> -->
</div>
</div>
</div>
<div class="box">
<div class="left">
<div class="left-top">
@ -97,6 +94,11 @@ export default {
}
}
},
computed: {
lang() {
return this.$store.getters.language
}
},
created() {
this.changeFavicon(`./zhongzi.ico`)
setInterval(() => {
@ -142,7 +144,7 @@ export default {
this.$refs.RightTopRef.getData(this.deptId)
this.$refs.RightCenterRef.getData(this.deptId)
this.$refs.RightRightRef.getData(this.deptId)
this.getWeatherInfo()
// this.getWeatherInfo() 国外大屏不展示天气
this.$refs.MapCenterRef.getStationNum(this.deptId)
this.$refs.MapCenterRef.getData(this.deptId)
this.$refs.MapCenterRef.getLeftData(this.deptId)
@ -337,7 +339,17 @@ $margin: 16px;
left: $margin;
top: 30px;
}
.left-title-en {
width: 200px;
height: 52px;
background: url(../../assets/new-screen/zhongzien.png) no-repeat;
background-size: 100% 100%;
color: #fff;
font-size: 16px;
position: absolute;
left: $margin;
top: 30px;
}
.right-title {
font-family: LCD;
color: #fff;
@ -349,7 +361,16 @@ $margin: 16px;
margin-left: 16px;
}
}
.en-eitle-text{
font-size: 18px;
letter-spacing: 1px;
font-weight: 900;
width: 100%;
background: linear-gradient(92deg, #fff, #fff 48.85254%, #fff);
font-family: PangMenNumber;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.title-text {
font-size: 38px;
font-weight: 900;

View File

@ -77,7 +77,7 @@
</el-table-column>
<el-table-column width="150" :label="$t('remote.yhxgz')">
<template slot-scope="scope">
<el-input v-model="scope.row.modifyValue" type="number" placeholder="请输入" />
<el-input v-model="scope.row.modifyValue" type="number" :placeholder="$t('remote.pleaseInput')" />
</template>
</el-table-column>
<!-- <el-table-column width="180" label="站内接入点">

View File

@ -1901,12 +1901,12 @@ box-shadow: inset 0px 2px 16px 0px rgba(0, 148, 255, 0.4);' >${dataStr}</div>`
}
.label {
width: 110px;
flex: 1;
color: rgba(255, 255, 255, 1);
}
.value {
flex: 1;
flex: 0.5;
margin-left: 5px;
text-align: right;
color: rgba(0, 148, 255, 1);

View File

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

View File

@ -41,8 +41,8 @@
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="6">
<el-form-item :label="$t('surveillance.chargeType') + ':'" label-width="100px">
<el-select v-model="filter.segmentType" style="width: 100%;" placeholder="请选择" @change="changeType">
<el-form-item :label="$t('surveillance.chargeType') + ':'">
<el-select v-model="filter.segmentType" placeholder="请选择" @change="changeType">
<el-option
v-for="item in dateTypeList"
:key="item.value"

View File

@ -1,7 +1,7 @@
<template>
<div class="realtime-wrapper">
<el-row :gutter="10">
<el-col :xs="24" :sm="12" :md="12" :lg="6" :xl="6">
<el-row class="card-total-box" :gutter="10">
<el-col class="card-box" :xs="24" :sm="12" :md="12" :lg="6" :xl="6">
<div class="card-total station">
<div class="station-name">{{ panel_data.stationName }}</div>
<div class="content">
@ -25,7 +25,7 @@
</div>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="6" :xl="6">
<el-col class="card-box" :xs="24" :sm="12" :md="12" :lg="6" :xl="6">
<div class="card-total charge">
<div class="content">
<div class="name">
@ -39,7 +39,7 @@
</div>
</div>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="6" :xl="6">
<el-col class="card-box" :xs="24" :sm="12" :md="12" :lg="6" :xl="6">
<div class="card-total discharge">
<div class="content">
<div class="name">
@ -53,7 +53,7 @@
</div>
</div>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="6" :xl="6">
<el-col class="card-box" :xs="24" :sm="12" :md="12" :lg="6" :xl="6">
<div class="card-total earnings">
<div class="content">
<div class="name">
@ -1055,15 +1055,25 @@ box-shadow: inset 0px 2px 16px 0px rgba(0, 148, 255, 0.4);' >${dataStr}</div>`
.search-row {
margin-top: 10px
}
/* 父容器 el-row 改为 flex 布局,确保列等高 */
.card-total-box {
display: flex;
align-items: stretch; /* 关键:让所有列拉伸至相同高度 */
}
/* 确保每个 el-col 占满行高 */
.card-box {
display: flex;
}
.card-total {
width: 100%;
height: 138px;
min-height: 138px;
margin-top: 10px;
padding: 10px;
display: flex;
position: relative;
/* 确保卡片内容撑满高度 */
flex-direction: column;
.content {
display: flex;
justify-content: space-between;

View File

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

View File

@ -6,7 +6,7 @@ function resolve(dir) {
return path.join(__dirname, dir)
}
const name = defaultSettings.title || '弘正新能源' // page title
const name = defaultSettings.title || '中自新能源' // page title
// If your port is set to 80,
// use administrator privileges to execute the command line.