Files
xiaoshi-pad-card/xiaoshi-device-balance-card.js
2025-11-25 19:15:23 +08:00

1116 lines
33 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { LitElement, html, css } from "https://unpkg.com/lit-element@2.4.0/lit-element.js?module";
class XiaoshiBalanceCardEditor extends LitElement {
static get properties() {
return {
hass: { type: Object },
config: { type: Object },
_searchTerm: { type: String },
_filteredEntities: { type: Array },
_showEntityList: { type: Boolean }
};
}
static get styles() {
return css`
.form {
display: flex;
flex-direction: column;
gap: 10px;
min-height: 500px;
}
.form-group {
display: flex;
flex-direction: column;
gap: 5px;
}
label {
font-weight: bold;
}
select, input, textarea {
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
textarea {
min-height: 80px;
resize: vertical;
}
.help-text {
font-size: 0.85em;
color: #666;
margin-top: 4px;
}
.entity-selector {
position: relative;
}
.entity-search-input {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
box-sizing: border-box;
}
.entity-dropdown {
position: absolute;
top: 100%;
left: 0;
right: 0;
height: 300px;
overflow-y: auto;
background: white;
border: 1px solid #ddd;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
z-index: 1000;
margin-top: 2px;
}
.entity-option {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 12px;
cursor: pointer;
border-bottom: 1px solid #eee;
}
.entity-option:hover {
background: #f5f5f5;
}
.entity-option.selected {
background: #e3f2fd;
}
.entity-info {
display: flex;
align-items: center;
gap: 8px;
flex: 1;
justify-content: space-between;
}
.entity-details {
flex: 1;
}
.entity-name {
font-weight: 500;
font-size: 14px;
color: #000;
}
.entity-id {
font-size: 12px;
color: #000;
font-family: monospace;
}
.check-icon {
color: #4CAF50;
}
.no-results {
padding: 12px;
text-align: center;
color: #666;
font-style: italic;
}
.selected-entities {
margin-top: 8px;
}
.selected-label {
font-size: 12px;
font-weight: bold;
margin-bottom: 4px;
color: #333;
}
.selected-entity-config {
margin-bottom: 8px;
border: 1px solid #ddd;
border-radius: 4px;
padding: 8px;
background: #f9f9f9;
}
.selected-entity {
display: flex;
align-items: center;
gap: 4px;
margin-bottom: 8px;
font-size: 12px;
color: #000;
justify-content: space-between;
}
.attribute-config {
margin-top: 4px;
display: flex;
flex-direction: column;
gap: 4px;
}
.attribute-input {
width: 100%;
padding: 4px 8px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 12px;
box-sizing: border-box;
}
.override-config {
display: flex;
align-items: center;
gap: 4px;
margin-top: 2px;
}
.override-checkbox {
margin-right: 4px;
}
.override-input {
flex: 1;
padding: 2px 6px;
border: 1px solid #ddd;
border-radius: 3px;
font-size: 11px;
box-sizing: border-box;
}
.override-label {
font-size: 11px;
color: #666;
white-space: nowrap;
}
.remove-btn {
background: none;
border: none;
cursor: pointer;
padding: 0;
display: flex;
align-items: center;
color: #666;
margin-left: auto;
}
.remove-btn:hover {
color: #f44336;
}
`;
}
render() {
if (!this.hass) return html``;
return html`
<div class="form">
<div class="form-group">
<label>卡片宽度:支持像素(px)和百分比(%)默认100%</label>
<input
type="text"
@change=${this._entityChanged}
.value=${this.config.width !== undefined ? this.config.width : '100%'}
name="width"
placeholder="默认100%"
/>
</div>
<div class="form-group">
<label>标题名称:配置卡片显示的标题</label>
<input
type="text"
@change=${this._entityChanged}
.value=${this.config.name !== undefined ? this.config.name : '电话余额信息'}
name="name"
placeholder="默认:电话余额信息"
/>
</div>
<div class="form-group">
<label>全局预警条件:当任一实体满足此条件时触发预警</label>
<input
type="text"
@change=${this._entityChanged}
.value=${this.config.global_warning || ''}
name="global_warning"
placeholder="如: >10, <=5, ==on, ==off, =='hello world'"
/>
<div class="help-text">
全局预警条件:当任一实体满足此条件时,该实体显示为红色预警状态<br>
优先级:明细预警 > 全局预警 > 无预警
</div>
</div>
<div class="form-group">
<label>主题</label>
<select
@change=${this._entityChanged}
.value=${this.config.theme !== undefined ? this.config.theme : 'on'}
name="theme"
>
<option value="on">浅色主题(白底黑字)</option>
<option value="off">深色主题(深灰底白字)</option>
</select>
</div>
<div class="form-group">
<label>设备余额实体:搜索并选择实体</label>
<div class="entity-selector">
<input
type="text"
@input=${this._onEntitySearch}
@focus=${this._onEntitySearch}
.value=${this._searchTerm || ''}
placeholder="搜索实体..."
class="entity-search-input"
/>
${this._showEntityList ? html`
<div class="entity-dropdown">
${this._filteredEntities.map(entity => html`
<div
class="entity-option ${this.config.entities && this.config.entities.some(e => e.entity_id === entity.entity_id) ? 'selected' : ''}"
@click=${() => this._toggleEntity(entity.entity_id)}
>
<div class="entity-info">
<div class="entity-details">
<div class="entity-name">${entity.attributes.friendly_name || entity.entity_id}</div>
<div class="entity-id">${entity.entity_id}</div>
</div>
<ha-icon icon="${entity.attributes.icon || 'mdi:help-circle'}"></ha-icon>
</div>
${this.config.entities && this.config.entities.some(e => e.entity_id === entity.entity_id) ?
html`<ha-icon icon="mdi:check" class="check-icon"></ha-icon>` : ''}
</div>
`)}
${this._filteredEntities.length === 0 ? html`
<div class="no-results">未找到匹配的实体</div>
` : ''}
</div>
` : ''}
</div>
<div class="selected-entities">
${this.config.entities && this.config.entities.length > 0 ? html`
<div class="selected-label">已选择的实体:</div>
${this.config.entities.map((entityConfig, index) => {
const entity = this.hass.states[entityConfig.entity_id];
return html`
<div class="selected-entity-config">
<div class="selected-entity">
<span>${entity?.attributes.friendly_name || entityConfig.entity_id}</span>
<ha-icon icon="${entity?.attributes.icon || 'mdi:help-circle'}"></ha-icon>
<button class="remove-btn" @click=${() => this._removeEntity(index)}>
<ha-icon icon="mdi:close"></ha-icon>
</button>
</div>
<div class="attribute-config">
<input
type="text"
@change=${(e) => this._updateEntityAttribute(index, e.target.value)}
.value=${entityConfig.attribute || ''}
placeholder="留空使用实体状态,或输入属性名"
class="attribute-input"
/>
<div class="override-config">
<input
type="checkbox"
class="override-checkbox"
@change=${(e) => this._updateEntityOverride(index, 'icon', e.target.checked)}
.checked=${entityConfig.overrides?.icon !== undefined}
/>
<span class="override-label">图标:</span>
<input
type="text"
class="override-input"
@change=${(e) => this._updateEntityOverrideValue(index, 'icon', e.target.value)}
.value=${entityConfig.overrides?.icon || ''}
placeholder="mdi:icon-name"
?disabled=${entityConfig.overrides?.icon === undefined}
/>
</div>
<div class="override-config">
<input
type="checkbox"
class="override-checkbox"
@change=${(e) => this._updateEntityOverride(index, 'name', e.target.checked)}
.checked=${entityConfig.overrides?.name !== undefined}
/>
<span class="override-label">名称:</span>
<input
type="text"
class="override-input"
@change=${(e) => this._updateEntityOverrideValue(index, 'name', e.target.value)}
.value=${entityConfig.overrides?.name || ''}
placeholder="自定义名称"
?disabled=${entityConfig.overrides?.name === undefined}
/>
</div>
<div class="override-config">
<input
type="checkbox"
class="override-checkbox"
@change=${(e) => this._updateEntityOverride(index, 'unit_of_measurement', e.target.checked)}
.checked=${entityConfig.overrides?.unit_of_measurement !== undefined}
/>
<span class="override-label">单位:</span>
<input
type="text"
class="override-input"
@change=${(e) => this._updateEntityOverrideValue(index, 'unit_of_measurement', e.target.value)}
.value=${entityConfig.overrides?.unit_of_measurement || ''}
placeholder="自定义单位"
?disabled=${entityConfig.overrides?.unit_of_measurement === undefined}
/>
</div>
<div class="override-config">
<input
type="checkbox"
class="override-checkbox"
@change=${(e) => this._updateEntityOverride(index, 'warning', e.target.checked)}
.checked=${entityConfig.overrides?.warning !== undefined}
/>
<span class="override-label">预警:</span>
<input
type="text"
class="override-input"
@change=${(e) => this._updateEntityOverrideValue(index, 'warning', e.target.value)}
.value=${entityConfig.overrides?.warning || ''}
placeholder="如: >10, <=5, ==on, ==off, =='hello world'"
?disabled=${entityConfig.overrides?.warning === undefined}
/>
</div>
</div>
</div>
`;
})}
` : ''}
</div>
<div class="help-text">
搜索并选择要显示的设备余额实体,支持多选。每个实体可以配置:<br>
• 属性名:留空使用实体状态,或输入属性名<br>
• 名称重定义:勾选后可自定义显示名称<br>
• 图标重定义:勾选后可自定义图标(如 mdi:phone<br>
• 单位重定义:勾选后可自定义单位(如 元、$、kWh 等)<br>
• 预警条件:勾选后设置预警条件,支持 >10, >=10, <10, <=10, ==10, ==on, ==off, =="hello world" 等<br>
• 未勾选重定义时,将使用实体的原始属性值
</div>
</div>
</div>
`;
}
_entityChanged(e) {
const { name, value } = e.target;
if (!value && name !== 'theme' && name !== 'width' ) return;
let finalValue = value;
// 处理不同字段的默认值
if (name === 'width') {
finalValue = value || '100%';
}
this.config = {
...this.config,
[name]: finalValue
};
this.dispatchEvent(new CustomEvent('config-changed', {
detail: { config: this.config },
bubbles: true,
composed: true
}));
}
_onEntitySearch(e) {
const searchTerm = e.target.value.toLowerCase();
this._searchTerm = searchTerm;
this._showEntityList = true;
if (!this.hass) return;
// 获取所有实体
const allEntities = Object.values(this.hass.states);
// 过滤实体
this._filteredEntities = allEntities.filter(entity => {
const entityId = entity.entity_id.toLowerCase();
const friendlyName = (entity.attributes.friendly_name || '').toLowerCase();
return entityId.includes(searchTerm) || friendlyName.includes(searchTerm);
}).slice(0, 50); // 限制显示数量
this.requestUpdate();
}
_toggleEntity(entityId) {
const currentEntities = this.config.entities || [];
let newEntities;
if (currentEntities.some(e => e.entity_id === entityId)) {
// 移除实体
newEntities = currentEntities.filter(e => e.entity_id !== entityId);
} else {
// 添加实体
newEntities = [...currentEntities, {
entity_id: entityId,
attribute: null,
overrides: undefined
}];
}
this.config = {
...this.config,
entities: newEntities
};
this.dispatchEvent(new CustomEvent('config-changed', {
detail: { config: this.config },
bubbles: true,
composed: true
}));
this.requestUpdate();
}
_removeEntity(index) {
const currentEntities = this.config.entities || [];
const newEntities = currentEntities.filter((_, i) => i !== index);
this.config = {
...this.config,
entities: newEntities
};
this.dispatchEvent(new CustomEvent('config-changed', {
detail: { config: this.config },
bubbles: true,
composed: true
}));
this.requestUpdate();
}
_updateEntityAttribute(index, attributeValue) {
const currentEntities = this.config.entities || [];
const newEntities = [...currentEntities];
if (newEntities[index]) {
newEntities[index] = {
...newEntities[index],
attribute: attributeValue.trim() || null
};
}
this.config = {
...this.config,
entities: newEntities
};
this.dispatchEvent(new CustomEvent('config-changed', {
detail: { config: this.config },
bubbles: true,
composed: true
}));
this.requestUpdate();
}
_updateEntityOverride(index, overrideType, enabled) {
const currentEntities = this.config.entities || [];
const newEntities = [...currentEntities];
if (newEntities[index]) {
const overrides = { ...newEntities[index].overrides };
if (enabled) {
// 启用覆盖,设置默认值
overrides[overrideType] = '';
} else {
// 禁用覆盖,删除该属性
delete overrides[overrideType];
}
newEntities[index] = {
...newEntities[index],
overrides: Object.keys(overrides).length > 0 ? overrides : undefined
};
}
this.config = {
...this.config,
entities: newEntities
};
this.dispatchEvent(new CustomEvent('config-changed', {
detail: { config: this.config },
bubbles: true,
composed: true
}));
this.requestUpdate();
}
_updateEntityOverrideValue(index, overrideType, value) {
const currentEntities = this.config.entities || [];
const newEntities = [...currentEntities];
if (newEntities[index] && newEntities[index].overrides && newEntities[index].overrides[overrideType] !== undefined) {
const overrides = { ...newEntities[index].overrides };
overrides[overrideType] = value.trim();
newEntities[index] = {
...newEntities[index],
overrides: overrides
};
}
this.config = {
...this.config,
entities: newEntities
};
this.dispatchEvent(new CustomEvent('config-changed', {
detail: { config: this.config },
bubbles: true,
composed: true
}));
this.requestUpdate();
}
// 点击外部关闭下拉列表
firstUpdated() {
document.addEventListener('click', (e) => {
if (!e.target.closest('.entity-selector')) {
this._showEntityList = false;
this.requestUpdate();
}
});
}
constructor() {
super();
this._searchTerm = '';
this._filteredEntities = [];
this._showEntityList = false;
}
setConfig(config) {
this.config = config;
}
}
customElements.define('xiaoshi-balance-card-editor', XiaoshiBalanceCardEditor);
class XiaoshiBalanceCard extends LitElement {
static get properties() {
return {
hass: Object,
config: Object,
_oilPriceData: Array,
_loading: Boolean,
_refreshInterval: Number,
theme: { type: String }
};
}
static get styles() {
return css`
:host {
display: block;
width: var(--card-width, 100%);
}
ha-card {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
background: var(--bg-color, #fff);
border-radius: 12px;
}
/*标题容器*/
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px;
background: var(--bg-color, #fff);
border-radius: 12px;
}
/*标题红色圆点*/
.offline-indicator {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 8px;
}
/*标题红色圆点动画*/
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}
/*标题*/
.card-title {
font-size: 20px;
font-weight: 500;
color: var(--fg-color, #000);
height: 30px;
line-height: 30px;
display: flex;
align-items: center;
justify-content: center;
}
/*标题统计数字*/
.device-count {
color: var(--fg-color, #000);
border-radius: 8px;
font-size: 13px;
width: 30px;
height: 30px;
text-align: center;
line-height: 30px;
font-weight: bold;
padding: 0px;
}
.device-count.non-zero {
background: rgb(2, 250, 250, 0.5);
}
.device-count.zero {
background: rgb(0, 205, 0);
}
/*标题刷新按钮*/
.refresh-btn {
color: var(--fg-color, #fff);
border: none;
border-radius: 8px;
padding: 5px;
cursor: pointer;
font-size: 13px;
width: 50px;
height: 30px;
line-height: 30px;
text-align: center;
font-weight: bold;
padding: 0px;
}
/*2级标题*/
.section-divider {
margin: 0 0 8px 0;
padding: 8px 8px;
background: var(--bg-color, #fff);
font-weight: 500;
color: var(--fg-color, #000);
border-top: 1px solid rgb(150,150,150,0.5);
border-bottom: 1px solid rgb(150,150,150,0.5);
margin: 0 16px 0 16px;
}
/*2级标题字体*/
.section-title {
display: flex;
align-items: center;
justify-content: space-between;
color: var(--fg-color, #000);
font-size: 13px;
}
/*2级标题,统计数量字体*/
.section-count {
background: rgb(255,0,0,0.5);
color: var(--fg-color, #000);
border-radius: 12px;
width: 15px;
height: 15px;
text-align: center;
line-height: 15px;
padding: 3px;
font-size: 12px;
font-weight: bold;
}
/*设备、实体明细*/
.device-item {
display: flex;
align-items: center;
justify-content: space-between;
margin: 0px 16px;
padding: 8px 0;
border-bottom: 1px solid rgb(150,150,150,0.5);
cursor: pointer;
transition: background-color 0.2s;
}
.device-item:first-child {
border-top: 1px solid rgb(150,150,150,0.5);
}
.device-item:hover {
background-color: rgba(150,150,150,0.1);
}
/*设备、实体明细背景*/
.devices-list {
flex: 1;
overflow-y: auto;
min-height: 0;
padding: 0 0 8px 0;
}
.device-left {
display: flex;
align-items: center;
flex: 1;
min-width: 0;
}
.device-icon {
margin-right: 12px;
color: var(--fg-color, #000);
flex-shrink: 0;
}
.device-name {
color: var(--fg-color, #000);
font-size: 12px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.device-value {
color: var(--fg-color, #000);
font-size: 12px;
margin-left: auto;
flex-shrink: 0;
font-weight: bold;
}
.device-value.warning {
color: #F44336;
}
.device-unit {
font-size: 12px;
color: var(--fg-color, #000);
margin-left: 4px;
font-weight: bold;
}
.device-unit.warning {
color: #F44336;
}
.no-devices {
text-align: center;
padding: 10px 0;
color: var(--fg-color, #000);
}
.loading {
text-align: center;
padding: 10px 0;
color: var(--fg-color, #000);
}
`;
}
constructor() {
super();
this._oilPriceData = [];
this._loading = false;
this._refreshInterval = null;
this.theme = 'on';
}
static getConfigElement() {
return document.createElement("xiaoshi-balance-card-editor");
}
connectedCallback() {
super.connectedCallback();
this._loadOilPriceData();
// 设置主题属性
this.setAttribute('theme', this._evaluateTheme());
// 每300秒刷新一次数据减少频繁刷新
this._refreshInterval = setInterval(() => {
this._loadOilPriceData();
}, 300000);
}
_evaluateTheme() {
try {
if (!this.config || !this.config.theme) return 'on';
if (typeof this.config.theme === 'function') {
return this.config.theme();
}
if (typeof this.config.theme === 'string' &&
(this.config.theme.includes('return') || this.config.theme.includes('=>'))) {
return (new Function(`return ${this.config.theme}`))();
}
return this.config.theme;
} catch(e) {
console.error('计算主题时出错:', e);
return 'on';
}
}
disconnectedCallback() {
super.disconnectedCallback();
if (this._refreshInterval) {
clearInterval(this._refreshInterval);
}
}
async _loadOilPriceData() {
if (!this.hass) return;
this._loading = true;
this.requestUpdate();
try {
const entities = this.config.entities || [];
const balanceData = [];
for (const entityConfig of entities) {
const entityId = entityConfig.entity_id;
const attributeName = entityConfig.attribute;
const entity = this.hass.states[entityId];
if (!entity) continue;
const attributes = entity.attributes;
let value = entity.state;
let unit = '元';
// 如果指定了属性,则使用属性值
if (attributeName && attributes[attributeName] !== undefined) {
value = attributes[attributeName];
}
// 尝试从属性中获取单位
if (attributes.unit_of_measurement) {
unit = attributes.unit_of_measurement;
} else {
// 如果实体没有单位,则不显示单位
unit = '';
}
// 应用属性重定义
let friendlyName = attributes.friendly_name || entityId;
let icon = attributes.icon || 'mdi:help-circle';
let warningThreshold = undefined;
// 应用用户自定义的重定义
if (entityConfig.overrides) {
if (entityConfig.overrides.name !== undefined && entityConfig.overrides.name !== '') {
friendlyName = entityConfig.overrides.name;
}
if (entityConfig.overrides.icon !== undefined && entityConfig.overrides.icon !== '') {
icon = entityConfig.overrides.icon;
}
if (entityConfig.overrides.unit_of_measurement !== undefined && entityConfig.overrides.unit_of_measurement !== '') {
unit = entityConfig.overrides.unit_of_measurement;
}
if (entityConfig.overrides.warning !== undefined && entityConfig.overrides.warning !== '') {
warningThreshold = entityConfig.overrides.warning; // 保持原始字符串
}
}
balanceData.push({
entity_id: entityId,
friendly_name: friendlyName,
value: value,
unit: unit,
icon: icon,
warning_threshold: warningThreshold
});
}
this._oilPriceData = balanceData;
} catch (error) {
console.error('加载设备余额数据失败:', error);
this._oilPriceData = [];
}
this._loading = false;
}
_handleRefresh() {
this._loadOilPriceData();
navigator.vibrate(50);
}
_handleEntityClick(entity) {
navigator.vibrate(50);
// 点击实体时打开实体详情页
if (entity.entity_id) {
const evt = new Event('hass-more-info', { composed: true });
evt.detail = { entityId: entity.entity_id };
this.dispatchEvent(evt);
}
}
_evaluateWarningCondition(value, condition) {
if (!condition) return false;
// 解析条件字符串,支持操作符后可能有空格
const match = condition.match(/^(>=|<=|>|<|==|!=)\s*(.+)$/);
if (!match) return false;
const operator = match[1];
let compareValue = match[2].trim();
// 移除比较值两端的引号(如果有的话)
if ((compareValue.startsWith('"') && compareValue.endsWith('"')) ||
(compareValue.startsWith("'") && compareValue.endsWith("'"))) {
compareValue = compareValue.slice(1, -1);
}
// 尝试将值转换为数字
const numericValue = parseFloat(value);
const numericCompare = parseFloat(compareValue);
// 如果两个值都是数字,进行数值比较
if (!isNaN(numericValue) && !isNaN(numericCompare)) {
switch (operator) {
case '>': return numericValue > numericCompare;
case '>=': return numericValue >= numericCompare;
case '<': return numericValue < numericCompare;
case '<=': return numericValue <= numericCompare;
case '==': return numericValue === numericCompare;
case '!=': return numericValue !== numericCompare;
}
}
// 字符串比较(用于 ==on, ==off, ==66 66 等)
const stringValue = String(value);
const stringCompare = compareValue;
switch (operator) {
case '==': return stringValue === stringCompare;
case '!=': return stringValue !== stringCompare;
case '>': return stringValue > stringCompare;
case '>=': return stringValue >= stringCompare;
case '<': return stringValue < stringCompare;
case '<=': return stringValue <= stringCompare;
}
return false;
}
render() {
if (!this.hass) {
return html`<div class="loading">等待Home Assistant连接...</div>`;
}
// 获取主题和颜色
const theme = this._evaluateTheme();
const fgColor = theme === 'on' ? 'rgb(0, 0, 0)' : 'rgb(255, 255, 255)';
const bgColor = theme === 'on' ? 'rgb(255, 255, 255)' : 'rgb(50, 50, 50)';
return html`
<ha-card style="--fg-color: ${fgColor}; --bg-color: ${bgColor};">
<div class="card-header">
<div class="card-title">
<span class="offline-indicator" style="background: rgb(0,222,220); animation: pulse 2s infinite"></span>
${this.config.name || '电话余额信息'}
</div>
</div>
<div class="devices-list">
${this._loading ?
html`<div class="loading">加载中...</div>` :
this._oilPriceData.length === 0 ?
html`<div class="no-devices">请配置余额实体</div>` :
html`
${this._oilPriceData.map(balanceData => {
// 明细预警优先级最高
let isWarning = false;
// 首先检查明细预警,如果存在且满足条件,直接设为预警状态
if (balanceData.warning_threshold && balanceData.warning_threshold.trim() !== '') {
isWarning = this._evaluateWarningCondition(balanceData.value, balanceData.warning_threshold);
console.log(`明细预警 - 实体: ${balanceData.friendly_name}, 值: "${balanceData.value}", 条件: "${balanceData.warning_threshold}", 预警: ${isWarning}`);
} else {
// 只有在没有明细预警时才检查全局预警
if (this.config.global_warning && this.config.global_warning.trim() !== '') {
isWarning = this._evaluateWarningCondition(balanceData.value, this.config.global_warning);
console.log(`全局预警 - 实体: ${balanceData.friendly_name}, 值: "${balanceData.value}", 条件: "${this.config.global_warning}", 预警: ${isWarning}`);
}
}
return html`
<div class="device-item" @click=${() => this._handleEntityClick(balanceData)}>
<div class="device-left">
<ha-icon class="device-icon" icon="${balanceData.icon}"></ha-icon>
<div class="device-name">${balanceData.friendly_name}</div>
</div>
<div class="device-value ${isWarning ? 'warning' : ''}">
${balanceData.value}
<span class="device-unit ${isWarning ? 'warning' : ''}">${balanceData.unit}</span>
</div>
</div>
`;
})}
`
}
</div>
</ha-card>
`;
}
setConfig(config) {
this.config = config;
// 设置CSS变量来控制卡片的宽度和高度
if (config.width) {
this.style.setProperty('--card-width', config.width);
}
// 设置主题
if (config.theme) {
this.setAttribute('theme', config.theme);
}
}
getCardSize() {
// 根据设备余额实体数量动态计算卡片大小
const baseSize = 3;
const entitySize = Math.max(0, Math.min(this._oilPriceData.length * 2, 10));
return baseSize + entitySize;
}
}
customElements.define('xiaoshi-balance-card', XiaoshiBalanceCard);