mirror of
https://github.com/xiaoshi930/xiaoshi-pad-card.git
synced 2025-11-29 17:19:47 +00:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b7da24066 | ||
|
|
721b6a4ba2 | ||
|
|
b615d3dd7a | ||
|
|
6f58598f54 | ||
|
|
f71e51029c | ||
|
|
48f0c23456 | ||
|
|
910365f8b8 | ||
|
|
81b40138f5 | ||
|
|
4783085a99 | ||
|
|
737b174953 | ||
|
|
d49426da73 | ||
|
|
3bf33ef0cb | ||
|
|
2c3c3fd42d | ||
|
|
4fe21a2c74 | ||
|
|
3900897f15 | ||
|
|
2170f5423c | ||
|
|
82e71cf38d | ||
|
|
c79052b1e1 | ||
|
|
5ef261877c | ||
|
|
da16f27cf2 | ||
|
|
df6a678043 | ||
|
|
82930f4e41 | ||
|
|
4a2ffb628d | ||
|
|
3d5f6028c2 |
@@ -975,12 +975,12 @@ class XiaoshiBalanceCard extends LitElement {
|
||||
}
|
||||
|
||||
_handleRefresh() {
|
||||
this._handleClick();
|
||||
this._loadOilPriceData();
|
||||
navigator.vibrate(50);
|
||||
}
|
||||
|
||||
_handleEntityClick(entity) {
|
||||
navigator.vibrate(50);
|
||||
this._handleClick();
|
||||
// 点击实体时打开实体详情页
|
||||
if (entity.entity_id) {
|
||||
const evt = new Event('hass-more-info', { composed: true });
|
||||
@@ -989,6 +989,17 @@ class XiaoshiBalanceCard extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
_handleClick(){
|
||||
if (navigator.vibrate) {
|
||||
navigator.vibrate(50);
|
||||
}
|
||||
else if (navigator.webkitVibrate) {
|
||||
navigator.webkitVibrate(50);
|
||||
}
|
||||
else {
|
||||
}
|
||||
}
|
||||
|
||||
_evaluateWarningCondition(value, condition) {
|
||||
if (!condition) return false;
|
||||
|
||||
|
||||
@@ -291,9 +291,17 @@ class XiaoshiConsumablesButtonEditor extends LitElement {
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label> </label>
|
||||
<label>👇👇👇下面是弹出卡片内容👇👇👇</label>
|
||||
<label> </label>
|
||||
<label>👇👇👇下方弹出的卡片可增加的其他卡片👇👇👇</label>
|
||||
<textarea
|
||||
@change=${this._entityChanged}
|
||||
.value=${this.config.other_cards || ''}
|
||||
name="other_cards"
|
||||
placeholder='# 示例配置:添加button卡片
|
||||
- type: custom:button-card
|
||||
template: 测试模板(最好引用模板,否则大概率会报错)
|
||||
- type: custom:button-card
|
||||
template: 测试模板(最好引用模板,否则大概率会报错)'>
|
||||
</textarea>
|
||||
</div>
|
||||
|
||||
<div class="checkbox-group">
|
||||
@@ -301,11 +309,19 @@ class XiaoshiConsumablesButtonEditor extends LitElement {
|
||||
type="checkbox"
|
||||
class="checkbox-input"
|
||||
@change=${this._entityChanged}
|
||||
.checked=${this.config.show_preview !== false}
|
||||
name="show_preview"
|
||||
id="show_preview"
|
||||
.checked=${this.config.no_preview === true}
|
||||
name="no_preview"
|
||||
id="no_preview"
|
||||
/>
|
||||
<label for="show_preview" class="checkbox-label" style="color: red;"> 弹出卡片预览(正式使用时取消勾选)</label>
|
||||
<label for="no_preview" class="checkbox-label" style="color: red;">
|
||||
📻显示预览📻( 请先勾选测试显示效果 )
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label> </label>
|
||||
<label>👇👇👇下方是弹出的主卡配置项👇👇👇</label>
|
||||
<label> </label>
|
||||
</div>
|
||||
|
||||
<!-- button新元素 结束-->
|
||||
@@ -532,17 +548,15 @@ class XiaoshiConsumablesButtonEditor extends LitElement {
|
||||
</div>
|
||||
<div class="help-text">
|
||||
搜索并选择要显示的设备耗材实体,支持多选。每个实体可以配置:<br>
|
||||
• <strong>特殊实体显示:</strong>binary_sensor(off→正常,on→缺少), event(unknown→正常,其他→低电量)<br>
|
||||
• 属性名:留空使用实体状态,或输入属性名<br>
|
||||
• 名称重定义:勾选后可自定义显示名称<br>
|
||||
• 图标重定义:勾选后可自定义图标(如 mdi:phone)<br>
|
||||
• 单位重定义:勾选后可自定义单位(如 元、$、kWh 等)<br>
|
||||
• 预警条件:勾选后设置预警条件,支持 >10, >=10, <10, <=10, ==10, ==on, ==off, =="hello world" 等<br>
|
||||
• 换算:对数值进行数学运算,支持 +10, -10, *1.5, /2 等<br>
|
||||
• 未勾选重定义时,将使用实体的原始属性值
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
`;
|
||||
}
|
||||
@@ -553,7 +567,6 @@ class XiaoshiConsumablesButtonEditor extends LitElement {
|
||||
const { name, value, type, checked } = e.target;
|
||||
|
||||
let finalValue;
|
||||
|
||||
// 处理复选框
|
||||
if (type === 'checkbox') {
|
||||
finalValue = checked;
|
||||
@@ -770,9 +783,11 @@ class XiaoshiConsumablesButtonEditor extends LitElement {
|
||||
this._showEntityList = false;
|
||||
}
|
||||
|
||||
/*button新按钮方法 开始*/
|
||||
setConfig(config) {
|
||||
this.config = config;
|
||||
this.config = config || {};
|
||||
}
|
||||
/*button新按钮方法 结束*/
|
||||
}
|
||||
customElements.define('xiaoshi-consumables-button-editor', XiaoshiConsumablesButtonEditor);
|
||||
|
||||
@@ -1044,7 +1059,7 @@ class XiaoshiConsumablesButton extends LitElement {
|
||||
margin-right: 8px;
|
||||
color: var(--fg-color, #000);
|
||||
flex-shrink: 0;
|
||||
font-size: 10px;
|
||||
font-size: 11px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
display: flex;
|
||||
@@ -1054,7 +1069,7 @@ class XiaoshiConsumablesButton extends LitElement {
|
||||
|
||||
.device-name {
|
||||
color: var(--fg-color, #000);
|
||||
font-size: 9px;
|
||||
font-size: 11px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -1064,13 +1079,12 @@ class XiaoshiConsumablesButton extends LitElement {
|
||||
|
||||
.device-value {
|
||||
color: var(--fg-color, #000);
|
||||
font-size: 9px;
|
||||
font-size: 11px;
|
||||
flex-shrink: 0;
|
||||
font-weight: bold;
|
||||
max-width: 45%;
|
||||
text-align: right;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@@ -1079,7 +1093,7 @@ class XiaoshiConsumablesButton extends LitElement {
|
||||
}
|
||||
|
||||
.device-unit {
|
||||
font-size: 9px;
|
||||
font-size: 11px;
|
||||
color: var(--fg-color, #000);
|
||||
margin-left: 0.5px;
|
||||
font-weight: bold;
|
||||
@@ -1261,12 +1275,12 @@ class XiaoshiConsumablesButton extends LitElement {
|
||||
}
|
||||
|
||||
_handleRefresh() {
|
||||
this._handleClick();
|
||||
this._loadOilPriceData();
|
||||
navigator.vibrate(50);
|
||||
}
|
||||
|
||||
_handleEntityClick(entity) {
|
||||
navigator.vibrate(50);
|
||||
this._handleClick();
|
||||
// 点击实体时打开实体详情页
|
||||
if (entity.entity_id) {
|
||||
const evt = new Event('hass-more-info', { composed: true });
|
||||
@@ -1274,24 +1288,69 @@ class XiaoshiConsumablesButton extends LitElement {
|
||||
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 行为:弹出耗材卡片
|
||||
// 默认 tap_action 行为:弹出垂直堆叠卡片
|
||||
const excludedParams = ['type', 'button_height', 'button_width', 'button_font_size', 'button_icon_size', 'show_preview', 'tap_action'];
|
||||
const cardConfig = {};
|
||||
|
||||
// 构建垂直堆叠卡片的内容
|
||||
const cards = [];
|
||||
|
||||
// 1. 添加耗材卡片
|
||||
const consumablesCardConfig = {};
|
||||
Object.keys(this.config).forEach(key => {
|
||||
if (!excludedParams.includes(key)) {
|
||||
cardConfig[key] = this.config[key];
|
||||
if (!excludedParams.includes(key) && key !== 'other_cards' && key !== 'no_preview') {
|
||||
consumablesCardConfig[key] = this.config[key];
|
||||
}
|
||||
});
|
||||
|
||||
const popupContent = {
|
||||
cards.push({
|
||||
type: 'custom:xiaoshi-consumables-card',
|
||||
...cardConfig
|
||||
...consumablesCardConfig
|
||||
});
|
||||
|
||||
// 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 || `
|
||||
@@ -1307,8 +1366,158 @@ class XiaoshiConsumablesButton extends LitElement {
|
||||
console.warn('browser_mod not available, cannot show popup');
|
||||
}
|
||||
}
|
||||
navigator.vibrate(50);
|
||||
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新元素 结束*/
|
||||
|
||||
_renderDeviceItem(consumablesData) {
|
||||
@@ -1437,7 +1646,6 @@ class XiaoshiConsumablesButton extends LitElement {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
if (!this.hass) {
|
||||
return html`<div class="loading">等待Home Assistant连接...</div>`;
|
||||
@@ -1472,7 +1680,7 @@ class XiaoshiConsumablesButton extends LitElement {
|
||||
}).length;
|
||||
|
||||
/*button新元素 前9行和最后1行开始*/
|
||||
const showPreview = this.config.show_preview !== false;
|
||||
const showPreview = this.config.no_preview === true;
|
||||
|
||||
return html`
|
||||
<div class="consumables-status" style="--fg-color: ${fgColor}; --bg-color: ${bgColor};" @click=${this._handleButtonClick}>
|
||||
@@ -1517,9 +1725,11 @@ class XiaoshiConsumablesButton extends LitElement {
|
||||
}
|
||||
|
||||
setConfig(config) {
|
||||
this.config = config;
|
||||
|
||||
/*button新元素 开始*/
|
||||
// 不设置默认值,只有明确配置时才添加 no_preview
|
||||
this.config = {
|
||||
...config
|
||||
};
|
||||
if (config.button_width) {
|
||||
this.style.setProperty('--button-width', config.button_width);
|
||||
} else {
|
||||
@@ -1567,3 +1777,4 @@ class XiaoshiConsumablesButton extends LitElement {
|
||||
}
|
||||
}
|
||||
customElements.define('xiaoshi-consumables-button', XiaoshiConsumablesButton);
|
||||
|
||||
|
||||
@@ -906,7 +906,7 @@ class XiaoshiConsumablesCard extends LitElement {
|
||||
|
||||
.device-name {
|
||||
color: var(--fg-color, #000);
|
||||
font-size: 9px;
|
||||
font-size: 11px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -916,13 +916,12 @@ class XiaoshiConsumablesCard extends LitElement {
|
||||
|
||||
.device-value {
|
||||
color: var(--fg-color, #000);
|
||||
font-size: 9px;
|
||||
font-size: 11px;
|
||||
flex-shrink: 0;
|
||||
font-weight: bold;
|
||||
max-width: 45%;
|
||||
text-align: right;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@@ -931,7 +930,7 @@ class XiaoshiConsumablesCard extends LitElement {
|
||||
}
|
||||
|
||||
.device-unit {
|
||||
font-size: 9px;
|
||||
font-size: 11px;
|
||||
color: var(--fg-color, #000);
|
||||
margin-left: 0.5px;
|
||||
font-weight: bold;
|
||||
@@ -1113,12 +1112,12 @@ class XiaoshiConsumablesCard extends LitElement {
|
||||
}
|
||||
|
||||
_handleRefresh() {
|
||||
this._handleClick();
|
||||
this._loadOilPriceData();
|
||||
navigator.vibrate(50);
|
||||
}
|
||||
|
||||
_handleEntityClick(entity) {
|
||||
navigator.vibrate(50);
|
||||
this._handleClick();
|
||||
// 点击实体时打开实体详情页
|
||||
if (entity.entity_id) {
|
||||
const evt = new Event('hass-more-info', { composed: true });
|
||||
@@ -1127,6 +1126,17 @@ class XiaoshiConsumablesCard extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
_handleClick(){
|
||||
if (navigator.vibrate) {
|
||||
navigator.vibrate(50);
|
||||
}
|
||||
else if (navigator.webkitVibrate) {
|
||||
navigator.webkitVibrate(50);
|
||||
}
|
||||
else {
|
||||
}
|
||||
}
|
||||
|
||||
_renderDeviceItem(consumablesData) {
|
||||
let isWarning = false;
|
||||
|
||||
|
||||
@@ -766,6 +766,14 @@ export class XiaoshiHaInfoCard extends LitElement {
|
||||
if (entityId.startsWith('climate.')) return 'mdi:thermostat';
|
||||
if (entityId.startsWith('cover.')) return 'mdi:window-shutter';
|
||||
if (entityId.startsWith('weather.')) return 'mdi:weather-cloudy';
|
||||
if (entityId.startsWith('input_select.')) return 'mdi:form-select';
|
||||
if (entityId.startsWith('select.')) return 'mdi:form-select';
|
||||
if (entityId.startsWith('input_text.')) return 'mdi:form-textbox';
|
||||
if (entityId.startsWith('text.')) return 'mdi:form-textbox';
|
||||
if (entityId.startsWith('button.')) return 'mdi:button-pointer';
|
||||
if (entityId.startsWith('event.')) return 'mdi:gesture-tap-button';
|
||||
if (entityId.startsWith('device_tracker.')) return 'mdi:lan-connect';
|
||||
if (entityId.startsWith('notify.')) return 'mdi:message';
|
||||
return 'mdi:help-circle';
|
||||
}
|
||||
|
||||
@@ -788,13 +796,13 @@ export class XiaoshiHaInfoCard extends LitElement {
|
||||
}
|
||||
|
||||
_handleRefresh() {
|
||||
this._handleClick();
|
||||
this._loadOfflineDevices();
|
||||
navigator.vibrate(50);
|
||||
}
|
||||
|
||||
_handleDeviceClick(device) {
|
||||
navigator.vibrate(50);
|
||||
// 点击设备时跳转到设备详情页
|
||||
this._handleClick();
|
||||
if (device.device_id) {
|
||||
// 先关闭当前弹窗/界面
|
||||
this._closeCurrentDialog();
|
||||
@@ -817,7 +825,7 @@ export class XiaoshiHaInfoCard extends LitElement {
|
||||
}
|
||||
|
||||
_handleEntityClick(entity) {
|
||||
navigator.vibrate(50);
|
||||
this._handleClick();
|
||||
// 点击实体时打开实体详情页
|
||||
if (entity.entity_id) {
|
||||
// 使用您建议的第一种方式
|
||||
@@ -827,6 +835,17 @@ export class XiaoshiHaInfoCard extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
_handleClick(){
|
||||
if (navigator.vibrate) {
|
||||
navigator.vibrate(50);
|
||||
}
|
||||
else if (navigator.webkitVibrate) {
|
||||
navigator.webkitVibrate(50);
|
||||
}
|
||||
else {
|
||||
}
|
||||
}
|
||||
|
||||
_closeCurrentDialog() {
|
||||
// 查找并关闭当前可能的弹窗或对话框
|
||||
const dialogs = document.querySelectorAll('ha-dialog, .mdc-dialog, paper-dialog, vaadin-dialog');
|
||||
@@ -907,6 +926,12 @@ export class XiaoshiHaInfoCard extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
// 新增规则:如果skipped_version为null情况下,当latest_version !== installed_version时,
|
||||
// 且实体状态为off时,有可能是安装的版本比latest_version还高,这种不算更新的实体
|
||||
if (attributes.skipped_version === null && entity.state === 'off') {
|
||||
return; // 跳过此更新
|
||||
}
|
||||
|
||||
const updateData = {
|
||||
name: attributes.friendly_name || entity.entity_id.replace('update.', ''),
|
||||
current_version: attributes.installed_version,
|
||||
@@ -946,14 +971,14 @@ export class XiaoshiHaInfoCard extends LitElement {
|
||||
}
|
||||
|
||||
_handleRefresh() {
|
||||
this._handleClick();
|
||||
this._loadUpdateData();
|
||||
this._loadOfflineDevices();
|
||||
navigator.vibrate(50);
|
||||
}
|
||||
|
||||
_handleUpdateClick(update) {
|
||||
navigator.vibrate(50);
|
||||
// 点击更新项时弹出实体详情
|
||||
this._handleClick();
|
||||
|
||||
// 如果有entity_id,弹出实体详情
|
||||
if (update.entity_id) {
|
||||
@@ -974,14 +999,19 @@ export class XiaoshiHaInfoCard extends LitElement {
|
||||
_handleConfirmUpdate(update, event) {
|
||||
event.stopPropagation(); // 阻止事件冒泡
|
||||
event.preventDefault(); // 阻止默认行为
|
||||
navigator.vibrate(50);
|
||||
this._handleClick();
|
||||
|
||||
// 弹出确认对话框
|
||||
const confirmed = confirm(`确认要更新 ${update.name} 吗?\n当前版本: ${update.current_version}\n最新版本: ${update.latest_version}`);
|
||||
|
||||
if (confirmed) {
|
||||
this._executeUpdate(update);
|
||||
// 延迟3秒后刷新数据,给更新操作足够时间完成
|
||||
setTimeout(() => {
|
||||
this._loadUpdateData();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
_executeUpdate(update) {
|
||||
@@ -1245,7 +1275,7 @@ export class XiaoshiHaInfoCard extends LitElement {
|
||||
<div class="device-name">${update.name}</div>
|
||||
<div class="device-details">
|
||||
当前版本: ${update.current_version} → 最新版本: ${update.latest_version}
|
||||
${update.skipped_version ? html`<span style="color: #ff9800;"> 已跳过版本: ${update.skipped_version}</span>` : ''}
|
||||
${update.skipped_version ? html`<br><span style="color: #ff9800;">已跳过版本: ${update.skipped_version}</span>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
<div class="device-last-seen-update" @click=${(e) => this._handleConfirmUpdate(update, e)}>
|
||||
@@ -1270,7 +1300,7 @@ export class XiaoshiHaInfoCard extends LitElement {
|
||||
<div class="device-name">${update.name}</div>
|
||||
<div class="device-details">
|
||||
当前版本: ${update.current_version} → 最新版本: ${update.latest_version}
|
||||
${update.skipped_version ? html`<span style="color: #ff9800;"> 已跳过版本: ${update.skipped_version}</span>` : ''}
|
||||
${update.skipped_version ? html`<br><span style="color: #ff9800;">已跳过版本: ${update.skipped_version}</span>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
<div class="device-last-seen-update" @click=${(e) => this._handleConfirmUpdate(update, e)}>
|
||||
|
||||
@@ -667,12 +667,12 @@ export class XiaoshiOfflineCard extends LitElement {
|
||||
}
|
||||
|
||||
_handleRefresh() {
|
||||
this._handleClick();
|
||||
this._loadOfflineDevices();
|
||||
navigator.vibrate(50);
|
||||
}
|
||||
|
||||
_handleDeviceClick(device) {
|
||||
navigator.vibrate(50);
|
||||
this._handleClick();
|
||||
// 点击设备时跳转到设备详情页
|
||||
if (device.device_id) {
|
||||
// 先关闭当前弹窗/界面
|
||||
@@ -696,7 +696,7 @@ export class XiaoshiOfflineCard extends LitElement {
|
||||
}
|
||||
|
||||
_handleEntityClick(entity) {
|
||||
navigator.vibrate(50);
|
||||
this._handleClick();
|
||||
// 点击实体时打开实体详情页
|
||||
if (entity.entity_id) {
|
||||
// 使用您建议的第一种方式
|
||||
@@ -706,6 +706,17 @@ export class XiaoshiOfflineCard extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
_handleClick(){
|
||||
if (navigator.vibrate) {
|
||||
navigator.vibrate(50);
|
||||
}
|
||||
else if (navigator.webkitVibrate) {
|
||||
navigator.webkitVibrate(50);
|
||||
}
|
||||
else {
|
||||
}
|
||||
}
|
||||
|
||||
_closeCurrentDialog() {
|
||||
// 查找并关闭当前可能的弹窗或对话框
|
||||
const dialogs = document.querySelectorAll('ha-dialog, .mdc-dialog, paper-dialog, vaadin-dialog');
|
||||
|
||||
@@ -941,12 +941,12 @@ class XiaoshiTodoCard extends LitElement {
|
||||
}
|
||||
|
||||
_handleRefresh() {
|
||||
this._handleClick();
|
||||
this._loadTodoData();
|
||||
navigator.vibrate(50);
|
||||
}
|
||||
|
||||
_handleEntityClick(entity) {
|
||||
navigator.vibrate(50);
|
||||
this._handleClick();
|
||||
// 点击实体时打开实体详情页
|
||||
if (entity.entity_id) {
|
||||
const evt = new Event('hass-more-info', { composed: true });
|
||||
@@ -955,6 +955,17 @@ class XiaoshiTodoCard extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
_handleClick(){
|
||||
if (navigator.vibrate) {
|
||||
navigator.vibrate(50);
|
||||
}
|
||||
else if (navigator.webkitVibrate) {
|
||||
navigator.webkitVibrate(50);
|
||||
}
|
||||
else {
|
||||
}
|
||||
}
|
||||
|
||||
async _addTodoItem(entityId, item, description = '', due = '') {
|
||||
try {
|
||||
const params = {
|
||||
@@ -1082,7 +1093,25 @@ class XiaoshiTodoCard extends LitElement {
|
||||
${todoData.items.length === 0 ?
|
||||
html`<div class="no-devices">暂无待办事项</div>` :
|
||||
html`
|
||||
${todoData.items.map(item => {
|
||||
${(() => {
|
||||
// 将待办事项分为有时间和无时间两组
|
||||
const itemsWithoutTime = todoData.items.filter(item => !item.due);
|
||||
const itemsWithTime = todoData.items.filter(item => item.due);
|
||||
|
||||
// 没有时间的按名称排序
|
||||
itemsWithoutTime.sort((a, b) => (a.summary || '').localeCompare(b.summary || ''));
|
||||
|
||||
// 有时间的按时间排序
|
||||
itemsWithTime.sort((a, b) => {
|
||||
const dateA = new Date(a.due);
|
||||
const dateB = new Date(b.due);
|
||||
return dateA - dateB;
|
||||
});
|
||||
|
||||
// 合并结果:无时间的在前,有时间的在后
|
||||
const sortedItems = [...itemsWithoutTime, ...itemsWithTime];
|
||||
|
||||
return sortedItems.map(item => {
|
||||
const dueText = this._calculateDueDate(item.due);
|
||||
const isEditing = this._editingItem && this._editingItem.entityId === todoData.entity_id && this._editingItem.uid === item.uid;
|
||||
|
||||
@@ -1091,7 +1120,10 @@ class XiaoshiTodoCard extends LitElement {
|
||||
<input
|
||||
type="checkbox"
|
||||
.checked=${item.status === 'completed'}
|
||||
@change=${(e) => this._updateTodoItem(todoData.entity_id, item.summary || item.uid, e.target.checked ? 'completed' : 'needs_action')}
|
||||
@change=${(e) => {
|
||||
this._updateTodoItem(todoData.entity_id, item.summary || item.uid, e.target.checked ? 'completed' : 'needs_action');
|
||||
this._handleClick();
|
||||
}}
|
||||
style="margin-right: 8px; margin-top: 2px;"
|
||||
/>
|
||||
${isEditing ? html`
|
||||
@@ -1124,6 +1156,7 @@ class XiaoshiTodoCard extends LitElement {
|
||||
@input=${(e) => {
|
||||
this._editingItem.due = e.target.value;
|
||||
this.requestUpdate();
|
||||
this._handleClick();
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
@@ -1132,6 +1165,7 @@ class XiaoshiTodoCard extends LitElement {
|
||||
this._editTodoItem(todoData.entity_id, item.summary || item.uid, this._editingItem.summary, this._editingItem.description, this._editingItem.due);
|
||||
this._editingItem = null;
|
||||
this.requestUpdate();
|
||||
this._handleClick();
|
||||
}}
|
||||
>
|
||||
保存
|
||||
@@ -1141,6 +1175,7 @@ class XiaoshiTodoCard extends LitElement {
|
||||
@click=${() => {
|
||||
this._editingItem = null;
|
||||
this.requestUpdate();
|
||||
this._handleClick();
|
||||
}}
|
||||
>
|
||||
取消
|
||||
@@ -1168,6 +1203,7 @@ class XiaoshiTodoCard extends LitElement {
|
||||
due: this._formatDateForInput(item.due) || ''
|
||||
};
|
||||
this.requestUpdate();
|
||||
this._handleClick();
|
||||
}}
|
||||
style="margin-left: 8px; margin-top: 2px;"
|
||||
title="修改"
|
||||
@@ -1177,7 +1213,10 @@ class XiaoshiTodoCard extends LitElement {
|
||||
` : ''}
|
||||
<button
|
||||
class="remove-btn"
|
||||
@click=${() => this._removeTodoItem(todoData.entity_id, item.summary || item.uid)}
|
||||
@click=${() => {
|
||||
this._removeTodoItem(todoData.entity_id, item.summary || item.uid);
|
||||
this._handleClick();
|
||||
}}
|
||||
style="margin-left: 4px; margin-top: 2px;"
|
||||
title="删除"
|
||||
>
|
||||
@@ -1185,7 +1224,8 @@ class XiaoshiTodoCard extends LitElement {
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
})}
|
||||
});
|
||||
})()}
|
||||
`
|
||||
}
|
||||
<div>
|
||||
@@ -1197,6 +1237,7 @@ class XiaoshiTodoCard extends LitElement {
|
||||
[todoData.entity_id]: !this._expandedAddForm[todoData.entity_id]
|
||||
};
|
||||
this.requestUpdate();
|
||||
this._handleClick();
|
||||
}}
|
||||
>
|
||||
${this._expandedAddForm[todoData.entity_id] ? '收起' : '添加新待办事项'}
|
||||
@@ -1244,6 +1285,7 @@ class XiaoshiTodoCard extends LitElement {
|
||||
descInput.value = '';
|
||||
dateInput.value = '';
|
||||
}
|
||||
this._handleClick();
|
||||
}}
|
||||
>
|
||||
添加
|
||||
|
||||
@@ -159,7 +159,6 @@ export class XiaoshiUpdateCard extends LitElement {
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
background: var(--bg-color, #fff);
|
||||
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
@@ -189,7 +188,6 @@ export class XiaoshiUpdateCard extends LitElement {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
}
|
||||
|
||||
/*标题统计数字*/
|
||||
@@ -271,7 +269,8 @@ export class XiaoshiUpdateCard extends LitElement {
|
||||
align-items: center;
|
||||
padding: 0px;
|
||||
border-bottom: 1px solid rgb(150,150,150,0.2);
|
||||
margin: 0 24px 8px 32px;
|
||||
margin: 0 32px 4px 32px;
|
||||
padding: 4px 0 0 0;
|
||||
}
|
||||
|
||||
/*设备、实体明细背景*/
|
||||
@@ -279,7 +278,7 @@ export class XiaoshiUpdateCard extends LitElement {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
min-height: 0;
|
||||
padding: 0 0 8px 0;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.device-icon {
|
||||
@@ -294,7 +293,7 @@ export class XiaoshiUpdateCard extends LitElement {
|
||||
.device-name {
|
||||
font-weight: 500;
|
||||
color: var(--fg-color, #000);
|
||||
margin-bottom: 4px;
|
||||
margin: 2px 0;
|
||||
}
|
||||
|
||||
.device-entity {
|
||||
@@ -306,7 +305,6 @@ export class XiaoshiUpdateCard extends LitElement {
|
||||
.device-details {
|
||||
font-size: 10px;
|
||||
color: var(--fg-color, #000);
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.device-last-seen {
|
||||
@@ -327,13 +325,13 @@ export class XiaoshiUpdateCard extends LitElement {
|
||||
|
||||
.no-devices {
|
||||
text-align: center;
|
||||
padding: 8px 0 0 0;
|
||||
padding: 8px 0;
|
||||
color: var(--fg-color, #000);
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 0px;
|
||||
padding: 10px 0px;
|
||||
color: var(--fg-color, #000);
|
||||
}
|
||||
|
||||
@@ -409,12 +407,12 @@ export class XiaoshiUpdateCard extends LitElement {
|
||||
/* 备份信息独立容器 */
|
||||
.backup-info {
|
||||
padding: 4px 0 4px 16px;
|
||||
margin: 0 16px 0 30px;
|
||||
border-bottom: 1px solid rgb(150,150,150,0.2);
|
||||
margin: 0 32px 8px 32px;
|
||||
display: grid;
|
||||
grid-template-columns: auto auto auto;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid rgb(150,150,150,0.2);
|
||||
}
|
||||
`;
|
||||
}
|
||||
@@ -469,6 +467,28 @@ export class XiaoshiUpdateCard extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
_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 {
|
||||
}
|
||||
}
|
||||
|
||||
async _loadUpdateData() {
|
||||
if (!this.hass) return;
|
||||
|
||||
@@ -503,6 +523,11 @@ export class XiaoshiUpdateCard extends LitElement {
|
||||
return; // 跳过此更新
|
||||
}
|
||||
}
|
||||
// 新增规则:如果skipped_version为null情况下,当latest_version !== installed_version时,
|
||||
// 且实体状态为off时,有可能是安装的版本比latest_version还高,这种不算更新的实体
|
||||
if (attributes.skipped_version === null && entity.state === 'off') {
|
||||
return; // 跳过此更新
|
||||
}
|
||||
|
||||
const updateData = {
|
||||
name: attributes.friendly_name || entity.entity_id.replace('update.', ''),
|
||||
@@ -542,17 +567,13 @@ export class XiaoshiUpdateCard extends LitElement {
|
||||
this._loading = false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
_handleRefresh() {
|
||||
this._handleClick();
|
||||
this._loadUpdateData();
|
||||
navigator.vibrate(50);
|
||||
}
|
||||
|
||||
|
||||
|
||||
_handleUpdateClick(update) {
|
||||
navigator.vibrate(50);
|
||||
this._handleClick();
|
||||
// 点击更新项时弹出实体详情
|
||||
|
||||
// 如果有entity_id,弹出实体详情
|
||||
@@ -574,13 +595,17 @@ export class XiaoshiUpdateCard extends LitElement {
|
||||
_handleConfirmUpdate(update, event) {
|
||||
event.stopPropagation(); // 阻止事件冒泡
|
||||
event.preventDefault(); // 阻止默认行为
|
||||
navigator.vibrate(50);
|
||||
this._handleClick();
|
||||
|
||||
// 弹出确认对话框
|
||||
const confirmed = confirm(`确认要更新 ${update.name} 吗?\n当前版本: ${update.current_version}\n最新版本: ${update.latest_version}`);
|
||||
|
||||
if (confirmed) {
|
||||
this._executeUpdate(update);
|
||||
// 延迟3秒后刷新数据,给更新操作足够时间完成
|
||||
setTimeout(() => {
|
||||
this._loadUpdateData();
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -822,19 +847,9 @@ export class XiaoshiUpdateCard extends LitElement {
|
||||
${this._renderHAVersionInfo()}
|
||||
</div>
|
||||
|
||||
<!-- 备份信息 -->
|
||||
<div class="section-divider">
|
||||
<div class="section-title">
|
||||
<span> • 备份信息</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="backup-info">
|
||||
${this._renderBackupInfo()}
|
||||
</div>
|
||||
|
||||
<div class="devices-list">
|
||||
${this._loading ?
|
||||
html`<div class="loading">加载中...</div>` :
|
||||
html`<div class="loading">HA版本信息加载中...</div>` :
|
||||
|
||||
(this._haUpdates.length === 0 && this._otherUpdates.length === 0) ?
|
||||
html`<div class="no-devices">✅ 所有组件都是最新版本</div>` :
|
||||
@@ -847,7 +862,7 @@ export class XiaoshiUpdateCard extends LitElement {
|
||||
</div>
|
||||
</div>
|
||||
${this._haUpdates.map(update => html`
|
||||
<div class="device-item">
|
||||
<div class="device-item" @click=${() => this._handleEntityClick(update)}>
|
||||
<div class="device-icon">
|
||||
<ha-icon icon="${update.icon}"></ha-icon>
|
||||
</div>
|
||||
@@ -867,12 +882,12 @@ export class XiaoshiUpdateCard extends LitElement {
|
||||
${this._otherUpdates.length > 0 ? html`
|
||||
<div class="section-divider">
|
||||
<div class="section-title">
|
||||
<span> • 加载项、卡片更新</span>
|
||||
<span> • HACS更新</span>
|
||||
<span class="section-count ${this._otherUpdates.length > 0 ? 'non-zero' : 'zero'}">${this._otherUpdates.length}</span>
|
||||
</div>
|
||||
</div>
|
||||
${this._otherUpdates.map(update => html`
|
||||
<div class="device-item">
|
||||
<div class="device-item" @click=${() => this._handleEntityClick(update)}>
|
||||
<div class="device-icon">
|
||||
<ha-icon icon="${update.icon}"></ha-icon>
|
||||
</div>
|
||||
@@ -880,7 +895,7 @@ export class XiaoshiUpdateCard extends LitElement {
|
||||
<div class="device-name">${update.name}</div>
|
||||
<div class="device-details">
|
||||
当前版本: ${update.current_version} → 最新版本: ${update.latest_version}
|
||||
${update.skipped_version ? html`<br><span style="color: #ff9800;">已跳过版本: ${update.skipped_version}</span>` : ''}
|
||||
${update.skipped_version ? html`<br><span style="color: #ff9800;"> 已跳过版本: ${update.skipped_version}</span>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
<div class="device-last-seen" @click=${(e) => this._handleConfirmUpdate(update, e)}>
|
||||
@@ -890,8 +905,18 @@ export class XiaoshiUpdateCard extends LitElement {
|
||||
`)}\n ` : ''}
|
||||
`
|
||||
}
|
||||
|
||||
</div>
|
||||
</ha-card>
|
||||
<!-- 备份信息 -->
|
||||
<div class="section-divider">
|
||||
<div class="section-title">
|
||||
<span> • 备份信息</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="backup-info">
|
||||
${this._renderBackupInfo()}
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
console.info("%c 消逝卡-平板端 \n%c v 0.1.3 ", "color: red; font-weight: bold; background: black", "color: white; font-weight: bold; background: black");
|
||||
console.info("%c 消逝卡-平板端 \n%c v 0.1.5 ", "color: red; font-weight: bold; background: black", "color: white; font-weight: bold; background: black");
|
||||
|
||||
const loadCards = async () => {
|
||||
await import('./xiaoshi-pad-grid-card.js');
|
||||
|
||||
Reference in New Issue
Block a user