snapshot: index.html @ 20260904_140313

由 index.html.bak.20260904_140313 重建的历史快照。
时间取自备份文件名(备份生成时刻,Asia/Shanghai)。
此提交只含 index.html,当时其余文件的状态没有对应备份,无法还原。
This commit is contained in:
OpenClaw Agent
2026-09-04 14:03:13 +08:00
parent df342c5ee2
commit 5071fc7dd3
+80 -7
View File
@@ -768,6 +768,7 @@
<p class="text-[10px] text-rose-700 leading-relaxed">{{ sync.lastError }}</p> <p class="text-[10px] text-rose-700 leading-relaxed">{{ sync.lastError }}</p>
</div> </div>
<button @click="doSyncNow" :disabled="sync.busy" class="w-full flex items-center gap-2 px-5 py-3.5 text-xs font-medium text-indigo-600 active:bg-slate-50 transition disabled:opacity-40"><svg class="ico text-[15px]"><use href="#i-refresh-cw" /></svg>立即同步{{ sync.pending > 0 ? '(待上传 ' + sync.pending + ' 项)' : '' }}</button> <button @click="doSyncNow" :disabled="sync.busy" class="w-full flex items-center gap-2 px-5 py-3.5 text-xs font-medium text-indigo-600 active:bg-slate-50 transition disabled:opacity-40"><svg class="ico text-[15px]"><use href="#i-refresh-cw" /></svg>立即同步{{ sync.pending > 0 ? '(待上传 ' + sync.pending + ' 项)' : '' }}</button>
<button @click="reopenMerge" :disabled="sync.busy" class="w-full flex items-center gap-2 px-5 py-3.5 text-xs font-medium text-slate-500 active:bg-slate-50 transition disabled:opacity-40"><svg class="ico text-[15px]"><use href="#i-shield-alert" /></svg>比对本机与服务器(有冲突时可选)</button>
<button @click="doPushAll" :disabled="sync.busy" class="w-full flex items-center gap-2 px-5 py-3.5 text-xs font-medium text-slate-500 active:bg-slate-50 transition disabled:opacity-40"><svg class="ico text-[15px]"><use href="#i-upload" /></svg>把本机全部数据上传</button> <button @click="doPushAll" :disabled="sync.busy" class="w-full flex items-center gap-2 px-5 py-3.5 text-xs font-medium text-slate-500 active:bg-slate-50 transition disabled:opacity-40"><svg class="ico text-[15px]"><use href="#i-upload" /></svg>把本机全部数据上传</button>
<button @click="doPullAll" :disabled="sync.busy" class="w-full flex items-center gap-2 px-5 py-3.5 text-xs font-medium text-slate-500 active:bg-slate-50 transition disabled:opacity-40"><svg class="ico text-[15px]"><use href="#i-download" /></svg>用服务器数据覆盖本机</button> <button @click="doPullAll" :disabled="sync.busy" class="w-full flex items-center gap-2 px-5 py-3.5 text-xs font-medium text-slate-500 active:bg-slate-50 transition disabled:opacity-40"><svg class="ico text-[15px]"><use href="#i-download" /></svg>用服务器数据覆盖本机</button>
<button @click="doLogout" :disabled="sync.busy" class="w-full flex items-center gap-2 px-5 py-3.5 text-xs font-medium text-rose-600 active:bg-rose-50 transition disabled:opacity-40"><svg class="ico text-[15px]"><use href="#i-log-out" /></svg>退出登录(保留本地数据)</button> <button @click="doLogout" :disabled="sync.busy" class="w-full flex items-center gap-2 px-5 py-3.5 text-xs font-medium text-rose-600 active:bg-rose-50 transition disabled:opacity-40"><svg class="ico text-[15px]"><use href="#i-log-out" /></svg>退出登录(保留本地数据)</button>
@@ -1027,6 +1028,67 @@
</div> </div>
</transition> </transition>
<!-- ===== 首次登录合并:本机与服务器同一天内容不同时,让用户自己选 ===== -->
<transition name="drawer">
<div v-if="mergeOpen" class="fixed inset-0 z-[55] flex items-end justify-center bg-black/40 backdrop-blur-sm">
<div class="w-full max-w-md bg-white rounded-t-3xl px-5 pt-5 pb-6 shadow-2xl overflow-y-auto max-h-[88vh] no-scrollbar">
<div class="mb-4 pb-3 border-b border-slate-100">
<h3 class="text-base font-bold text-slate-800 flex items-center gap-1.5">
<svg class="ico text-[18px] text-amber-500"><use href="#i-shield-alert" /></svg>本机与服务器有冲突
</h3>
<p class="text-[10px] text-slate-400 mt-1 leading-relaxed">
下面这{{ mergeRows.length }}项两边内容不一样,需要你决定保留哪份。<span class="text-slate-500 font-medium">其余数据两边都会保留,不会丢。</span>
</p>
</div>
<div class="inset bg-slate-50 border border-slate-200/70 p-3 mb-3 flex items-center justify-around text-center">
<div>
<p class="text-[10px] text-slate-400">本机</p>
<p class="text-sm font-bold text-slate-700 font-mono-num">{{ mergeInfo ? mergeInfo.localDays : 0 }} 天</p>
</div>
<svg class="ico text-base text-slate-300"><use href="#i-refresh-cw" /></svg>
<div>
<p class="text-[10px] text-slate-400">服务器</p>
<p class="text-sm font-bold text-slate-700 font-mono-num">{{ mergeInfo ? mergeInfo.remoteDays : 0 }} 天</p>
</div>
</div>
<div class="space-y-2 mb-4">
<div v-for="r in mergeRows" :key="r.key" class="inset border border-slate-200/70 p-3">
<p class="text-[11px] font-bold text-slate-700 mb-1.5">{{ r.label }}</p>
<div class="space-y-1 text-[11px]">
<div class="flex justify-between gap-3">
<span class="text-slate-400 shrink-0">本机</span>
<span class="text-slate-700 text-right">{{ r.local }}</span>
</div>
<div class="flex justify-between gap-3">
<span class="text-slate-400 shrink-0">服务器</span>
<span class="text-slate-700 text-right">{{ r.remote }}</span>
</div>
</div>
</div>
</div>
<div class="space-y-2">
<button @click="resolveMerge('local')" :disabled="sync.busy"
class="w-full py-3 bg-indigo-600 text-white rounded-xl text-sm font-bold shadow-md active:scale-95 transition disabled:opacity-40">
冲突处保留本机(推荐)
</button>
<button @click="resolveMerge('remote')" :disabled="sync.busy"
class="w-full py-3 bg-slate-100 text-slate-600 rounded-xl text-xs font-bold active:scale-95 transition disabled:opacity-40">
冲突处改用服务器版本
</button>
<button @click="deferMerge" :disabled="sync.busy"
class="w-full py-2.5 text-[11px] font-medium text-slate-400 active:text-slate-600 transition disabled:opacity-40">
先不处理(保持登录,数据都不动)
</button>
</div>
<p class="text-center text-[10px] text-slate-400 mt-2.5 leading-relaxed">合并前会自动留一份本机快照。两边独有的数据一律双向保留。</p>
</div>
</div>
</transition>
<!-- ===== 轻提示 ===== --> <!-- ===== 轻提示 ===== -->
<transition name="fade"> <transition name="fade">
<div v-if="toast" class="fixed bottom-24 left-1/2 -translate-x-1/2 z-[60] bg-slate-800/95 text-white text-xs font-medium px-4 py-2.5 rounded-full shadow-lg whitespace-nowrap"> <div v-if="toast" class="fixed bottom-24 left-1/2 -translate-x-1/2 z-[60] bg-slate-800/95 text-white text-xs font-medium px-4 py-2.5 rounded-full shadow-lg whitespace-nowrap">
@@ -1695,7 +1757,7 @@ createApp({
// ===== 云端同步(离线优先:本地 localStorage 仍是唯一真相,同步只是额外把数据推到服务器)===== // ===== 云端同步(离线优先:本地 localStorage 仍是唯一真相,同步只是额外把数据推到服务器)=====
const sync = reactive({ const sync = reactive({
status: 'idle', user: null, pending: 0, lastSyncAt: null, lastError: '', status: 'idle', user: null, pending: 0, lastSyncAt: null, lastError: '',
serverUsers: null, apiBase: '', busy: false serverUsers: null, apiBase: '', busy: false, hold: null
}); });
const syncPanelOpen = ref(false); const syncPanelOpen = ref(false);
const loginForm = ref({ username: '', password: '' }); const loginForm = ref({ username: '', password: '' });
@@ -1743,21 +1805,27 @@ createApp({
return rows; return rows;
}); });
const syncStatusText = computed(() => ({ const syncStatusText = computed(() => {
if (sync.hold) return '有冲突·已暂停同步';
return ({
idle: '未登录·仅存本机', idle: '未登录·仅存本机',
ready: sync.pending > 0 ? `待上传 ${sync.pending} 项` : '已同步', ready: sync.pending > 0 ? `待上传 ${sync.pending} 项` : '已同步',
syncing: '同步中…', syncing: '同步中…',
offline: '连不上服务器·本地照常用', offline: '连不上服务器·本地照常用',
error: '同步出错' error: '同步出错'
})[sync.status] || sync.status); })[sync.status] || sync.status;
});
const syncStatusClass = computed(() => ({ const syncStatusClass = computed(() => {
if (sync.hold) return 'text-amber-700 bg-amber-100';
return ({
idle: 'text-slate-500 bg-slate-100', idle: 'text-slate-500 bg-slate-100',
ready: sync.pending > 0 ? 'text-amber-700 bg-amber-100' : 'text-emerald-700 bg-emerald-100', ready: sync.pending > 0 ? 'text-amber-700 bg-amber-100' : 'text-emerald-700 bg-emerald-100',
syncing: 'text-indigo-700 bg-indigo-100', syncing: 'text-indigo-700 bg-indigo-100',
offline: 'text-amber-700 bg-amber-100', offline: 'text-amber-700 bg-amber-100',
error: 'text-rose-700 bg-rose-100' error: 'text-rose-700 bg-rose-100'
})[sync.status] || 'text-slate-500 bg-slate-100'); })[sync.status] || 'text-slate-500 bg-slate-100';
});
const syncTimeText = computed(() => { const syncTimeText = computed(() => {
if (!sync.lastSyncAt) return ''; if (!sync.lastSyncAt) return '';
@@ -1837,6 +1905,7 @@ createApp({
async function runMerge(diff, winner) { async function runMerge(diff, winner) {
// 合并前留一份本机快照。远端值落地是不可逆的,出意外至少数据还在。 // 合并前留一份本机快照。远端值落地是不可逆的,出意外至少数据还在。
window.WHTSync.snapshotLocal('login-merge-' + winner); window.WHTSync.snapshotLocal('login-merge-' + winner);
window.WHTSync.releaseSync(); // 用户已拍板,解除同步冻结
const excludeRecords = winner === 'remote' ? diff.conflict : []; const excludeRecords = winner === 'remote' ? diff.conflict : [];
const excludeAdjust = winner === 'remote' ? diff.adjustConflict : []; const excludeAdjust = winner === 'remote' ? diff.adjustConflict : [];
@@ -1865,10 +1934,13 @@ createApp({
mergeInfo.value = null; mergeInfo.value = null;
}); });
// 先不合并:保持登录但不动任何数据。同步面板会留一个入口重新进来。 // 先不合并:保持登录但冻结自动同步。
// 必须冻结 —— 否则下一次改动触发的 syncNow 会把远端值静默盖到本机冲突日上,
// 用户看到的就是「我选了不处理,数据却自己变了」。
const deferMerge = () => { const deferMerge = () => {
mergeOpen.value = false; mergeOpen.value = false;
showToast('已跳过合并,本机数据未改动'); window.WHTSync.holdSync('login-conflict');
showToast('已暂停自动同步,本机数据未改动');
}; };
const reopenMerge = () => withBusy(async () => { const reopenMerge = () => withBusy(async () => {
@@ -1946,6 +2018,7 @@ createApp({
sync.lastSyncAt = s.lastSyncAt; sync.lastSyncAt = s.lastSyncAt;
sync.lastError = s.lastError; sync.lastError = s.lastError;
sync.serverUsers = s.serverUsers; sync.serverUsers = s.serverUsers;
sync.hold = s.hold;
}); });
sync.apiBase = window.WHTSync.getApiBase() === location.origin ? '' : window.WHTSync.getApiBase(); sync.apiBase = window.WHTSync.getApiBase() === location.origin ? '' : window.WHTSync.getApiBase();
// 探一下服务端在不在、有没有账号,决定设置页该显示「创建账号」还是「登录」 // 探一下服务端在不在、有没有账号,决定设置页该显示「创建账号」还是「登录」