feat: 多端同步服务端与引擎 + Capacitor 安卓工程(v1.13)

This commit is contained in:
root
2026-10-16 11:07:37 +08:00
parent ee795eeefb
commit de3add8881
70 changed files with 2039 additions and 12 deletions
+75
View File
@@ -13,6 +13,7 @@ import { downloadCsv } from '../lib/csv'
import { hhmm, parseDateStr, todayStr } from '../lib/date'
import { computeDay } from '../domain/attendance'
import { changeLoginPassword, deleteCurrentUser, getSession } from '../db/auth'
import { getSyncLastTime, isSyncConfigured, registerCloudAccount, runSync, type SyncResult } from '../sync/engine'
import { showToast } from '../lib/toast'
const rule = ref<WorkRule>({ work_start: '09:00', work_end: '18:00', grace_minutes: 10, lunch_minutes: 60, remind_weekly: 'friday' })
@@ -34,6 +35,54 @@ const newLoginPwd1 = ref('')
const newLoginPwd2 = ref('')
const accError = ref('')
const deleteAccConfirm = ref(false)
const syncBase = ref('')
const syncUser = ref('')
const syncPwd = ref('')
const syncStatus = ref('')
const syncError = ref('')
const syncing = ref(false)
const lastSync = ref('')
async function loadSyncInfo() {
syncBase.value = await getSetting('sync_base_url', '')
syncUser.value = await getSetting('sync_username', '')
syncPwd.value = await getSetting('sync_password', '')
lastSync.value = await getSyncLastTime()
}
async function saveSyncConfig() {
await setSetting('sync_base_url', syncBase.value.trim())
await setSetting('sync_username', syncUser.value.trim())
await setSetting('sync_password', syncPwd.value)
showToast('同步配置已保存')
}
async function registerCloud() {
syncError.value = ''
try {
await registerCloudAccount(syncBase.value, syncUser.value, syncPwd.value)
await saveSyncConfig()
showToast('云端账号已注册,可直接同步')
} catch (e) {
syncError.value = e instanceof Error ? e.message : '注册失败'
}
}
async function doSyncNow() {
if (syncing.value) return
syncError.value = ''
syncing.value = true
try {
await saveSyncConfig()
const result: SyncResult = await runSync({ baseUrl: syncBase.value, username: syncUser.value, password: syncPwd.value })
lastSync.value = result.at
syncStatus.value = '推送 ' + result.pushed + ' 条 · 拉取 ' + result.pulled + ' 条 · 删除 ' + result.removed + ' 条'
showToast('同步完成')
} catch (e) {
syncError.value = e instanceof Error ? e.message : '同步失败'
}
syncing.value = false
}
async function doChangeLoginPwd() {
accError.value = ''
@@ -64,6 +113,7 @@ async function load() {
aiBase.value = await getSetting('ai_base_url', '')
aiKey.value = await getSetting('ai_api_key', '')
aiModel.value = await getSetting('ai_model', '')
await loadSyncInfo()
}
async function saveAi() {
@@ -291,6 +341,31 @@ async function doImport() {
</div>
</div>
<div class="card section">
<div class="section-title">多端数据同步</div>
<div class="muted acc-line">在自建同步服务端注册云端账号后,手机(PWA)、桌面与网页端登录同一账号即可互通数据;密码库以密文同步,主密码永不出本机</div>
<div class="ai-grid">
<div class="rule-item">
<label>同步服务地址</label>
<input class="input" v-model="syncBase" placeholder="如 http://124.221.192.176:3780" />
</div>
<div class="rule-item">
<label>云端用户名</label>
<input class="input" v-model="syncUser" placeholder="云端账号用户名" />
</div>
<div class="rule-item">
<label>云端密码(至少 6 位)</label>
<input type="password" class="input" v-model="syncPwd" placeholder="云端账号密码" />
</div>
</div>
<div class="rule-save">
<button class="btn btn-sm" @click="registerCloud">注册云端账号</button>
<button class="btn btn-primary btn-sm" :disabled="syncing" @click="doSyncNow">{{ syncing ? '同步中' : '立即同步' }}</button>
</div>
<div v-if="syncStatus" class="muted acc-line">上次同步:{{ lastSync || '--' }} · {{ syncStatus }}</div>
<div v-if="syncError" class="form-error">{{ syncError }}</div>
</div>
<div class="card section">
<div class="section-title">AI 润色配置(可选)</div>
<div class="ai-grid">