From daae901d1bd897a3cdcff2d577afb9163f904812 Mon Sep 17 00:00:00 2001 From: xiaoshi <115949669+xiaoshi930@users.noreply.github.com> Date: Sat, 6 Dec 2025 20:30:31 +0800 Subject: [PATCH] Delete xiaoshi-device-balance-button.js --- xiaoshi-device-balance-button.js | 1840 ------------------------------ 1 file changed, 1840 deletions(-) delete mode 100644 xiaoshi-device-balance-button.js diff --git a/xiaoshi-device-balance-button.js b/xiaoshi-device-balance-button.js deleted file mode 100644 index 8e1039d..0000000 --- a/xiaoshi-device-balance-button.js +++ /dev/null @@ -1,1840 +0,0 @@ -import { LitElement, html, css } from "https://unpkg.com/lit-element@2.4.0/lit-element.js?module"; - -class XiaoshiBalanceButtonEditor extends LitElement { - static get properties() { - return { - hass: { type: Object }, - config: { type: Object }, - _searchTerm: { type: String }, - _filteredEntities: { type: Array }, - _showEntityList: { type: Boolean }, //button新元素 - _specificSearchTerm: { type: String }, //button新元素 - _specificFilteredEntities: { type: Array }, //button新元素 - _showSpecificEntityList: { type: Boolean } //button新元素 - }; - } - - 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` -
- - -
- -
- -
- - -
- -
- - -
- -
- -
- - ${this._showSpecificEntityList ? html` -
- ${this._specificFilteredEntities.map(entity => html` -
this._selectSpecificEntity(entity.entity_id)} - > -
-
-
${entity.attributes.friendly_name || entity.entity_id}
-
${entity.entity_id}
-
- -
- ${this.config.specific_entity_id === entity.entity_id ? - html`` : ''} -
- `)} - ${this._specificFilteredEntities.length === 0 ? html` -
未找到匹配的实体
- ` : ''} -
- ` : ''} -
-
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- - -
- - - -
- - - -
- - -
- -
- - -
- -
- - -
- -
- -
- - -
-
- -
- - -
- -
- -
- - ${this._showEntityList ? html` -
- ${this._filteredEntities.map(entity => html` -
this._toggleEntity(entity.entity_id)} - > -
-
-
${entity.attributes.friendly_name || entity.entity_id}
-
${entity.entity_id}
-
- -
- ${this.config.entities && this.config.entities.some(e => e.entity_id === entity.entity_id) ? - html`` : ''} -
- `)} - ${this._filteredEntities.length === 0 ? html` -
未找到匹配的实体
- ` : ''} -
- ` : ''} -
-
- ${this.config.entities && this.config.entities.length > 0 ? html` -
已选择的实体:
- ${this.config.entities.map((entityConfig, index) => { - const entity = this.hass.states[entityConfig.entity_id]; - return html` -
-
- ${entity?.attributes.friendly_name || entityConfig.entity_id} - - -
-
- this._updateEntityAttribute(index, e.target.value)} - .value=${entityConfig.attribute || ''} - placeholder="留空使用实体状态,或输入属性名" - class="attribute-input" - /> - -
- this._updateEntityOverride(index, 'icon', e.target.checked)} - .checked=${entityConfig.overrides?.icon !== undefined} - /> - 图标: - this._updateEntityOverrideValue(index, 'icon', e.target.value)} - .value=${entityConfig.overrides?.icon || ''} - placeholder="mdi:icon-name" - ?disabled=${entityConfig.overrides?.icon === undefined} - /> -
- -
- this._updateEntityOverride(index, 'name', e.target.checked)} - .checked=${entityConfig.overrides?.name !== undefined} - /> - 名称: - this._updateEntityOverrideValue(index, 'name', e.target.value)} - .value=${entityConfig.overrides?.name || ''} - placeholder="自定义名称" - ?disabled=${entityConfig.overrides?.name === undefined} - /> -
- -
- this._updateEntityOverride(index, 'unit_of_measurement', e.target.checked)} - .checked=${entityConfig.overrides?.unit_of_measurement !== undefined} - /> - 单位: - this._updateEntityOverrideValue(index, 'unit_of_measurement', e.target.value)} - .value=${entityConfig.overrides?.unit_of_measurement || ''} - placeholder="自定义单位" - ?disabled=${entityConfig.overrides?.unit_of_measurement === undefined} - /> -
- -
- this._updateEntityOverride(index, 'warning', e.target.checked)} - .checked=${entityConfig.overrides?.warning !== undefined} - /> - 预警: - this._updateEntityOverrideValue(index, 'warning', e.target.value)} - .value=${entityConfig.overrides?.warning || ''} - placeholder="如: >10, <=5, ==on, ==off, =='hello world'" - ?disabled=${entityConfig.overrides?.warning === undefined} - /> -
-
-
- `; - })} - ` : ''} -
-
- 搜索并选择要显示的设备余额实体,支持多选。每个实体可以配置:
- • 属性名:留空使用实体状态,或输入属性名
- • 名称重定义:勾选后可自定义显示名称
- • 图标重定义:勾选后可自定义图标(如 mdi:phone)
- • 单位重定义:勾选后可自定义单位(如 元、$、kWh 等)
- • 预警条件:勾选后设置预警条件,支持 >10, >=10, <10, <=10, ==10, ==on, ==off, =="hello world" 等
- • 未勾选重定义时,将使用实体的原始属性值 -
-
-
- - `; - } - - _entityChanged(e) { - - /*button新按钮方法 开始*/ - const { name, value, type, checked } = e.target; - - let finalValue; - // 处理复选框 - if (type === 'checkbox') { - finalValue = checked; - } else { - if (!value && name !== 'theme' && name !== 'button_width' && name !== 'button_height' && name !== 'button_font_size' && name !== 'button_icon_size' && name !== 'width' && name !== 'tap_action' && name !== 'display_mode' && name !== 'decimal_precision') return; - finalValue = value - } - - // 处理不同字段的默认值 - if (name === 'button_width') { - finalValue = value || '100%'; - } else if (name === 'button_height') { - finalValue = value || '24px'; - } else if (name === 'button_font_size') { - finalValue = value || '11px'; - } else if (name === 'button_icon_size') { - finalValue = value || '15px'; - } else if (name === 'width') { - finalValue = value || '100%'; - } else if (name === 'display_mode') { - finalValue = value || 'min_value'; - // 当切换到显示最小值模式时,清理specific_entity_id - if (finalValue === 'min_value') { - this.config = { - ...this.config, - [name]: finalValue, - specific_entity_id: undefined - }; - - this.dispatchEvent(new CustomEvent('config-changed', { - detail: { config: this.config }, - bubbles: true, - composed: true - })); - - this.requestUpdate(); - return; - } - } else if (name === 'decimal_precision') { - finalValue = value !== undefined ? parseInt(value) : 1; - } else if (name === 'tap_action') { - // 处理 tap_action 的特殊逻辑 - if (value === 'tap_action') { - // 如果是弹出卡片,则不设置 tap_action,让组件使用默认逻辑 - finalValue = undefined; - } else { - finalValue = value; - } - } - /*button新按钮方法 结束*/ - 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 { - // 添加实体 - const newEntity = { - entity_id: entityId, - overrides: undefined - }; - // 只有在明确指定属性时才添加 attribute 字段 - newEntities = [...currentEntities, newEntity]; - } - - 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]) { - const updatedEntity = { ...newEntities[index] }; - - if (attributeValue.trim()) { - // 只有当属性值不为空时才设置 attribute 字段 - updatedEntity.attribute = attributeValue.trim(); - } else { - // 如果属性值为空,则移除 attribute 字段 - delete updatedEntity.attribute; - } - - newEntities[index] = updatedEntity; - } - - 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._showSpecificEntityList = false; //button新元素 - this.requestUpdate(); - } - }); - } - - //button新元素 开始 - _onSpecificEntitySearch(e) { - const searchTerm = e.target.value.toLowerCase(); - this._specificSearchTerm = searchTerm; - this._showSpecificEntityList = true; - - if (!this.hass) return; - - // 获取所有实体 - const allEntities = Object.values(this.hass.states); - - // 过滤实体 - this._specificFilteredEntities = 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(); - } - - _getSpecificEntityDisplayName() { - const specificEntityId = this.config.specific_entity_id; - if (specificEntityId) { - return specificEntityId; - } - return ''; - } - - _selectSpecificEntity(entityId) { - this.config = { - ...this.config, - specific_entity_id: entityId - }; - - this.dispatchEvent(new CustomEvent('config-changed', { - detail: { config: this.config }, - bubbles: true, - composed: true - })); - - // 选中后隐藏下拉列表 - this._showSpecificEntityList = false; - this._specificSearchTerm = ''; - - this.requestUpdate(); - } - //button新元素 结束 - - constructor() { - super(); - this._searchTerm = ''; - this._filteredEntities = []; - this._showEntityList = false; - this._specificSearchTerm = ''; //button新元素 - this._specificFilteredEntities = []; //button新元素 - this._showSpecificEntityList = false; //button新元素 - } - - setConfig(config) { - this.config = config; - } -} -customElements.define('xiaoshi-balance-button-editor', XiaoshiBalanceButtonEditor); - -class XiaoshiBalanceButton extends LitElement { - static get properties() { - return { - hass: Object, - config: Object, - _oilPriceData: Array, - _loading: Boolean, - _refreshInterval: Number, - _dataLoaded: Boolean, //button新元素 - theme: { type: String } - }; - } - - static get styles() { - return css` - :host { - display: block; - width: var(--card-width, 100%); - } - - /*button新元素 开始*/ - .balance-status { - width: var(--button-width, 70px); - height: var(--button-height, 24px); - padding: 0; - margin: 0; - background: var(--bg-color, #fff); - color: var(--fg-color, #000); - border-radius: 10px; - font-size: var(--button-font-size, 11px); - font-weight: 500; - text-align: center; - box-sizing: border-box; - display: flex; - align-items: center; - justify-content: center; - gap: 0; - cursor: pointer; - transition: background-color 0.2s, transform 0.1s; - position: relative; - } - - .status-icon { - --mdc-icon-size: var(--button-icon-size, 15px); - color: var(--fg-color, #000); - } - - /*button新元素 结束*/ - - 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: var(--warning-color, #F44336); - } - - .device-unit { - font-size: 12px; - color: var(--fg-color, #000); - margin-left: 4px; - font-weight: bold; - } - - .device-unit.warning { - color: var(--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._dataLoaded = false; //button新元素 - this._refreshInterval = null; - this.theme = 'on'; - } - - static getConfigElement() { - return document.createElement("xiaoshi-balance-button-editor"); - } - - connectedCallback() { - super.connectedCallback(); - this._loadOilPriceData(); - - // 设置主题属性 - this.setAttribute('theme', this._evaluateTheme()); - - //button新元素 开始 - setTimeout(() => { - this._loadOilPriceData(); - }, 50); - //button新元素 结束 - - // 每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; - - // button新元素 开始 删除下面 - // this._loading = true; - // this.requestUpdate(); - // button新元素 介素 删除下面 - - 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; - this._dataLoaded = true; //button新元素 - } catch (error) { - console.error('加载设备余额数据失败:', error); - this._oilPriceData = []; - this._dataLoaded = true; //button新元素 - } - - this._loading = false; - } - - _handleRefresh() { - this._handleClick(); - this._loadOilPriceData(); - } - - _handleEntityClick(entity) { - this._handleClick(); - // 点击实体时打开实体详情页 - if (entity.entity_id) { - const evt = new Event('hass-more-info', { composed: true }); - evt.detail = { entityId: entity.entity_id }; - this.dispatchEvent(evt); - } - } - - _handleClick(){ - if (navigator.vibrate) { - navigator.vibrate(50); - } - else if (navigator.webkitVibrate) { - navigator.webkitVibrate(50); - } - else { - } - } - - /*button新元素 开始*/ - _handleButtonClick() { - const tapAction = this.config.tap_action; - - if (!tapAction || tapAction !== 'none') { - // 默认 tap_action 行为:弹出垂直堆叠卡片 - const excludedParams = ['type', 'button_height', 'button_width', 'button_font_size', 'button_icon_size', 'show_preview', 'tap_action']; - - // 构建垂直堆叠卡片的内容 - const cards = []; - - // 1. 添加余额信息卡片 - const balanceCardConfig = {}; - Object.keys(this.config).forEach(key => { - if (!excludedParams.includes(key) && key !== 'other_cards' && key !== 'no_preview') { - balanceCardConfig[key] = this.config[key]; - } - }); - - cards.push({ - type: 'custom:xiaoshi-balance-card', - ...balanceCardConfig - }); - - // 2. 添加附加卡片 - if (this.config.other_cards && this.config.other_cards.trim()) { - try { - const additionalCardsConfig = this._parseYamlCards(this.config.other_cards); - - // 为每个附加卡片传递 theme 值 - const cardsWithTheme = additionalCardsConfig.map(card => { - // 如果卡片没有 theme 配置,则从当前卡片配置中传递 - if (!card.theme && this.config.theme) { - return { - ...card, - theme: this.config.theme - }; - } - return card; - }); - - cards.push(...cardsWithTheme); - } catch (error) { - console.error('解析附加卡片配置失败:', error); - } - } - - // 创建垂直堆叠卡片 - const popupContent = { - type: 'vertical-stack', - cards: cards - }; - - const popupStyle = this.config.popup_style || ` - --mdc-theme-surface: rgb(0,0,0,0); - --dialog-backdrop-filter: blur(10px) brightness(1); - `; - - if (window.browser_mod) { - window.browser_mod.service('popup', { - style: popupStyle, - content: popupContent - }); - } else { - console.warn('browser_mod not available, cannot show popup'); - } - } - this._handleClick(); - } - - _parseYamlCards(yamlString) { - try { - const lines = yamlString.split('\n'); - const cards = []; - let currentCard = null; - let indentStack = []; - let contextStack = []; - - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - const trimmed = line.trim(); - - if (!trimmed || trimmed.startsWith('#')) continue; - - const indentLevel = line.length - line.trimStart().length; - if (trimmed.startsWith('- type')) { - if (currentCard) { - cards.push(currentCard); - currentCard = null; - indentStack = []; - contextStack = []; - } - const content = trimmed.substring(1).trim(); - if (content.includes(':')) { - const [key, ...valueParts] = content.split(':'); - const value = valueParts.join(':').trim(); - currentCard = {}; - this._setNestedValue(currentCard, key.trim(), this._parseValue(value)); - } else { - currentCard = { type: content }; - } - - indentStack = [indentLevel]; - contextStack = [currentCard]; - } else if (currentCard && trimmed.startsWith('-')) { - while (indentStack.length > 1 && indentLevel <= indentStack[indentStack.length - 1]) { - indentStack.pop(); - contextStack.pop(); - } - - let currentContext = contextStack[contextStack.length - 1]; - const itemValue = trimmed.substring(1).trim(); - - if (!Array.isArray(currentContext)) { - if (contextStack.length > 1) { - const parentContext = contextStack[contextStack.length - 2]; - for (let key in parentContext) { - if (parentContext[key] === currentContext) { - parentContext[key] = []; - contextStack[contextStack.length - 1] = parentContext[key]; - currentContext = parentContext[key]; - break; - } - } - } - } - if (Array.isArray(currentContext)) { - if (itemValue.includes(':')) { - const [key, ...valueParts] = itemValue.split(':'); - const value = valueParts.join(':').trim(); - const obj = {}; - obj[key.trim()] = this._parseValue(value); - currentContext.push(obj); - } else { - currentContext.push(this._parseValue(itemValue)); - } - } - } else if (currentCard && trimmed.includes(':')) { - const [key, ...valueParts] = trimmed.split(':'); - const value = valueParts.join(':').trim(); - const keyName = key.trim(); - - while (indentStack.length > 1 && indentLevel <= indentStack[indentStack.length - 1]) { - indentStack.pop(); - contextStack.pop(); - } - - const currentContext = contextStack[contextStack.length - 1]; - - if (value) { - this._setNestedValue(currentContext, keyName, this._parseValue(value)); - } else { - let nextLine = null, nextIndent = null; - for (let j = i + 1; j < lines.length; j++) { - const nextTrimmed = lines[j].trim(); - if (nextTrimmed && !nextTrimmed.startsWith('#')) { - nextLine = nextTrimmed; - nextIndent = lines[j].length - lines[j].trimStart().length; - break; - } - } - - currentContext[keyName] = (nextLine && nextLine.startsWith('-') && nextIndent > indentLevel) - ? [] : (currentContext[keyName] || {}); - - indentStack.push(indentLevel); - contextStack.push(currentContext[keyName]); - } - } - } - - if (currentCard) cards.push(currentCard); - - return cards; - } catch (error) { - console.error('YAML解析错误:', error); - return []; - } - } - - _parseValue(value) { - if (!value) return ''; - - // 移除引号 - if ((value.startsWith('"') && value.endsWith('"')) || - (value.startsWith("'") && value.endsWith("'"))) { - return value.slice(1, -1); - } - - // 尝试解析为数字 - if (!isNaN(value) && value.trim() !== '') { - return Number(value); - } - - // 尝试解析为布尔值 - if (value === 'true') return true; - if (value === 'false') return false; - if (value === 'null') return null; - - // 返回字符串 - return value; - } - - _setNestedValue(obj, path, value) { - // 支持嵌套路径,如 "styles.card" - const keys = path.split('.'); - let current = obj; - - for (let i = 0; i < keys.length - 1; i++) { - const key = keys[i]; - if (!current[key] || typeof current[key] !== 'object') { - current[key] = {}; - } - current = current[key]; - } - - current[keys[keys.length - 1]] = value; - } - - /*button新元素 结束*/ - - _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`
等待Home Assistant连接...
`; - } - // 获取主题和颜色 - 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)'; - - - /*button新元素 前9行和最后1行开始*/ - const showPreview = this.config.no_preview === true; - - // 获取参数 - const buttonIcon = this.config.button_icon || 'mdi:cellphone'; - - // 设置背景颜色 - const buttonBgColor = bgColor; - - // 获取显示模式 - const displayMode = this.config.display_mode || 'min_value'; - // 获取小数点精度 - const decimalPrecision = this.config.decimal_precision !== undefined ? parseInt(this.config.decimal_precision) : 1; - - // 计算显示值 - let displayValue = null; - let displayUnit = '元'; - let isWarning = false; // 是否处于预警状态 - - if (!this._dataLoaded) { - // 数据加载中 - displayValue = '加载中'; - displayUnit = ''; - } else if (this._oilPriceData.length === 0) { - // 无数据 - displayValue = '无数据'; - displayUnit = ''; - } else if (displayMode === 'min_value') { - // 显示最小值模式 - const numericValues = this._oilPriceData - .map(item => { - const value = parseFloat(item.value); - return { value: isNaN(value) ? null : value, item }; - }) - .filter(item => item.value !== null); - - if (numericValues.length > 0) { - // 找到最小值 - const minValue = Math.min(...numericValues.map(item => item.value)); - const minItem = numericValues.find(item => item.value === minValue); - displayValue = minValue; - displayUnit = '元'; - - // 检查全局预警条件 - if (this.config.global_warning && this.config.global_warning.trim() !== '') { - isWarning = this._evaluateWarningCondition(displayValue, this.config.global_warning); - } - } else { - displayValue = '无有效数值'; - displayUnit = ''; - } - } else if (displayMode === 'specific_entity') { - // 显示指定实体模式 - const specificEntityId = this.config.specific_entity_id; - if (specificEntityId) { - // 直接从hass状态中获取实体数据 - const entity = this.hass.states[specificEntityId]; - if (entity) { - const rawValue = entity.state; - displayValue = parseFloat(rawValue); - displayUnit = entity.attributes.unit_of_measurement || '元'; - - // 如果不是数字,直接显示原始值 - if (isNaN(displayValue)) { - displayValue = rawValue; - } else { - // 检查全局预警条件 - if (this.config.global_warning && this.config.global_warning.trim() !== '') { - isWarning = this._evaluateWarningCondition(displayValue, this.config.global_warning); - } - } - } else { - displayValue = '实体未找到'; - displayUnit = ''; - } - } else { - displayValue = '请选择实体'; - displayUnit = ''; - } - } - - // 格式化显示值 - let formattedDisplayValue; - if (typeof displayValue === 'number') { - formattedDisplayValue = displayValue.toFixed(decimalPrecision); - // 移除末尾多余的0 - formattedDisplayValue = parseFloat(formattedDisplayValue).toString(); - } else { - formattedDisplayValue = displayValue; - } - - // 构建显示文本 - const displayText = formattedDisplayValue !== null && displayUnit ? `${formattedDisplayValue}${displayUnit}` : formattedDisplayValue; - - // 获取预警颜色 - const warningColor = this.config.warning_color || '#f44336'; - - // 根据预警状态设置数字颜色 - const numberColor = isWarning ? warningColor : fgColor; - - // 渲染按钮 - const buttonHtml = html` -
- - ${displayText} -
- `; - - // 返回最终的渲染结果(包括按钮和预览卡片) - return html` - ${buttonHtml} - ${showPreview ? html` -
- -
- - -
-
- - ${this.config.name || '电话余额信息'} -
-
- -
- ${this._loading ? - html`
加载中...
` : - - this._oilPriceData.length === 0 ? - html`
请配置余额实体
` : - html` - ${this._oilPriceData.map(balanceData => { - // 明细预警优先级最高 - let isWarning = false; - - // 首先检查明细预警,如果存在且满足条件,直接设为预警状态 - if (balanceData.warning_threshold && balanceData.warning_threshold.trim() !== '') { - isWarning = this._evaluateWarningCondition(balanceData.value, balanceData.warning_threshold); - } else { - // 只有在没有明细预警时才检查全局预警 - if (this.config.global_warning && this.config.global_warning.trim() !== '') { - isWarning = this._evaluateWarningCondition(balanceData.value, this.config.global_warning); - } - } - - return html` -
this._handleEntityClick(balanceData)}> -
- -
${balanceData.friendly_name}
-
-
- ${balanceData.value} - ${balanceData.unit} -
-
- `; - })} - ` - } -
-
- ` : html``} - `; - /*button新元素 结束*/ - } - - setConfig(config) { - /*button新元素 开始*/ - // 不设置默认值,只有明确配置时才添加 no_preview - this.config = { - ...config - }; - if (config.button_width) { - this.style.setProperty('--button-width', config.button_width); - } else { - this.style.setProperty('--button-width', '70px'); - } - - // 设置按钮高度(只控制 balance-status) - if (config.button_height) { - this.style.setProperty('--button-height', config.button_height); - } else { - this.style.setProperty('--button-height', '24px'); - } - - // 设置按钮文字大小(只控制 balance-status) - if (config.button_font_size) { - this.style.setProperty('--button-font-size', config.button_font_size); - } else { - this.style.setProperty('--button-font-size', '11px'); - } - - // 设置按钮图标大小(只控制 balance-status) - if (config.button_icon_size) { - this.style.setProperty('--button-icon-size', config.button_icon_size); - } else { - this.style.setProperty('--button-icon-size', '15px'); - } - - // 设置卡片宽度(控制原来的 UI) - if (config.width) { - this.style.setProperty('--card-width', config.width); - } else { - this.style.setProperty('--card-width', '100%'); - } - - /*button新元素 结束*/ - - // 设置预警颜色 - if (config.warning_color) { - this.style.setProperty('--warning-color', config.warning_color); - } else { - this.style.setProperty('--warning-color', '#f44336'); - } - - 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-button', XiaoshiBalanceButton);