ui: 侧边栏按“记录/工具/分析/系统”重排并分组分隔;精简设置/登录/记账页冗余文案(修正旧架构遗留说明);键盘焦点态

This commit is contained in:
root
2026-10-17 11:07:04 +08:00
parent 08b0317d35
commit a1f2a45623
10 changed files with 69 additions and 70 deletions
@@ -1,2 +1,2 @@
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./web-DtE1VpZK.js","./index-D6jOKt8Z.js","./index-2EGKD3r-.css"])))=>i.map(i=>d[i]); const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./web-Tgz3gT_A.js","./index-D8vVtKDY.js","./index-DxgZcHRE.css"])))=>i.map(i=>d[i]);
import{r as s,_ as i}from"./index-D6jOKt8Z.js";var t;(function(e){e.Documents="DOCUMENTS",e.Data="DATA",e.Library="LIBRARY",e.Cache="CACHE",e.External="EXTERNAL",e.ExternalStorage="EXTERNAL_STORAGE"})(t||(t={}));var a;(function(e){e.UTF8="utf8",e.ASCII="ascii",e.UTF16="utf16"})(a||(a={}));const E=s("Filesystem",{web:()=>i(()=>import("./web-DtE1VpZK.js"),__vite__mapDeps([0,1,2]),import.meta.url).then(e=>new e.FilesystemWeb)});export{t as Directory,a as Encoding,E as Filesystem}; import{r as s,_ as i}from"./index-D8vVtKDY.js";var t;(function(e){e.Documents="DOCUMENTS",e.Data="DATA",e.Library="LIBRARY",e.Cache="CACHE",e.External="EXTERNAL",e.ExternalStorage="EXTERNAL_STORAGE"})(t||(t={}));var a;(function(e){e.UTF8="utf8",e.ASCII="ascii",e.UTF16="utf16"})(a||(a={}));const E=s("Filesystem",{web:()=>i(()=>import("./web-Tgz3gT_A.js"),__vite__mapDeps([0,1,2]),import.meta.url).then(e=>new e.FilesystemWeb)});export{t as Directory,a as Encoding,E as Filesystem};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -8,8 +8,8 @@
<link rel="icon" href="./icons/icon.svg" type="image/svg+xml" /> <link rel="icon" href="./icons/icon.svg" type="image/svg+xml" />
<link rel="apple-touch-icon" href="./icons/icon-192.png" /> <link rel="apple-touch-icon" href="./icons/icon-192.png" />
<title>实习工作日志</title> <title>实习工作日志</title>
<script type="module" crossorigin src="./assets/index-D6jOKt8Z.js"></script> <script type="module" crossorigin src="./assets/index-D8vVtKDY.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index-2EGKD3r-.css"> <link rel="stylesheet" crossorigin href="./assets/index-DxgZcHRE.css">
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "intern-log", "name": "intern-log",
"private": true, "private": true,
"version": "0.3.0", "version": "0.3.1",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
+23 -10
View File
@@ -110,15 +110,15 @@ function onGlobalKey(e: KeyboardEvent) {
} }
const VIEWS = [ const VIEWS = [
{ key: 'calendar', name: '日历', icon: 'calendar' }, { key: 'calendar', name: '日历', icon: 'calendar', group: '记录' },
{ key: 'today', name: '详情', icon: 'edit' }, { key: 'today', name: '详情', icon: 'edit', group: '记录' },
{ key: 'stats', name: '统计', icon: 'chart' }, { key: 'ledger', name: '记账', icon: 'wallet', group: '工具' },
{ key: 'report', name: '总结', icon: 'export' }, { key: 'memo', name: '备忘录', icon: 'memo', group: '工具' },
{ key: 'ledger', name: '记账', icon: 'wallet' }, { key: 'stats', name: '统计', icon: 'chart', group: '分析' },
{ key: 'memo', name: '备忘录', icon: 'memo' }, { key: 'report', name: '总结', icon: 'export', group: '分析' },
{ key: 'vault', name: '密码库', icon: 'lock' }, { key: 'vault', name: '密码库', icon: 'lock', group: '系统' },
{ key: 'settings', name: '设置', icon: 'settings' }, { key: 'settings', name: '设置', icon: 'settings', group: '系统' },
{ key: 'admin', name: '用户管理', icon: 'shield' } { key: 'admin', name: '用户管理', icon: 'shield', group: '系统' }
] ]
function readRoute(): string { function readRoute(): string {
@@ -233,13 +233,15 @@ onMounted(async () => {
<LoginView v-if="!session" @login="onLogin" /> <LoginView v-if="!session" @login="onLogin" />
<div v-else class="app"> <div v-else class="app">
<nav class="side"> <nav class="side">
<template v-for="(v, i) in shownViews" :key="v.key">
<div v-if="i > 0 && v.group !== shownViews[i - 1].group" class="nav-sep"></div>
<button <button
v-for="v in shownViews" :key="v.key"
class="nav-btn" :class="{ active: route === v.key }" class="nav-btn" :class="{ active: route === v.key }"
:data-tip="v.name" @click="nav(v.key)" :data-tip="v.name" @click="nav(v.key)"
> >
<Icon :name="v.icon" :size="18" /> <Icon :name="v.icon" :size="18" />
</button> </button>
</template>
</nav> </nav>
<div class="main"> <div class="main">
<header class="topbar"> <header class="topbar">
@@ -420,6 +422,17 @@ onMounted(async () => {
transition: opacity 0.15s; transition: opacity 0.15s;
} }
.nav-btn:hover::after { opacity: 1; } .nav-btn:hover::after { opacity: 1; }
.nav-sep {
width: 26px;
height: 1px;
flex: none;
background: var(--border);
margin: 5px 0;
}
:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 1px;
}
.main { flex: 1; display: flex; flex-direction: column; min-width: 0; } .main { flex: 1; display: flex; flex-direction: column; min-width: 0; }
.topbar { .topbar {
+1 -1
View File
@@ -320,7 +320,7 @@ async function confirmDelete() {
</table> </table>
</div> </div>
<div class="muted vault-note num">筛选结果 {{ filtered.length }} 条 / 本月共 {{ rows.length }} 条</div> <div class="muted vault-note num">本月 {{ rows.length }} 条 · 当前筛选 {{ filtered.length }} 条</div>
<TransactionDialog <TransactionDialog
:open="dialogOpen" :open="dialogOpen"
+6 -20
View File
@@ -13,8 +13,7 @@ import { downloadCsv } from '../lib/csv'
import { hhmm, parseDateStr, todayStr } from '../lib/date' import { hhmm, parseDateStr, todayStr } from '../lib/date'
import { computeDay } from '../domain/attendance' import { computeDay } from '../domain/attendance'
import { changeLoginPassword, deleteCurrentUser, getSession } from '../db/auth' import { changeLoginPassword, deleteCurrentUser, getSession } from '../db/auth'
import { setSyncBaseOverride } from '../lib/server' import { ensureCloudBinding, getSyncLastTime, isSyncConfigured, syncWithStoredConfig, syncLastError, type SyncResult } from '../sync/engine'
import { ensureCloudBinding, getSyncBase, getSyncLastTime, isSyncConfigured, syncWithStoredConfig, syncLastError, type SyncResult } from '../sync/engine'
import { getCloudCreds } from '../db/auth' import { getCloudCreds } from '../db/auth'
import { showToast } from '../lib/toast' import { showToast } from '../lib/toast'
@@ -38,7 +37,6 @@ const newLoginPwd2 = ref('')
const accError = ref('') const accError = ref('')
const deleteAccConfirm = ref(false) const deleteAccConfirm = ref(false)
const appVersion = __APP_VERSION__ const appVersion = __APP_VERSION__
const syncBase = ref('')
const cloudUser = ref('') const cloudUser = ref('')
const syncStatus = ref('') const syncStatus = ref('')
const syncError = ref('') const syncError = ref('')
@@ -47,7 +45,6 @@ const syncing = ref(false)
const lastSync = ref('') const lastSync = ref('')
async function loadSyncInfo() { async function loadSyncInfo() {
syncBase.value = await getSyncBase()
cloudUser.value = getCloudCreds()?.username || '' cloudUser.value = getCloudCreds()?.username || ''
lastSync.value = await getSyncLastTime() lastSync.value = await getSyncLastTime()
autoSyncErr.value = syncLastError.value autoSyncErr.value = syncLastError.value
@@ -55,11 +52,6 @@ async function loadSyncInfo() {
watch(syncLastError, v => { autoSyncErr.value = v }) watch(syncLastError, v => { autoSyncErr.value = v })
async function saveSyncConfig() {
await setSyncBaseOverride(syncBase.value.trim())
showToast('同步地址已保存')
}
async function rebindCloud() { async function rebindCloud() {
syncError.value = '' syncError.value = ''
try { try {
@@ -76,7 +68,6 @@ async function doSyncNow() {
autoSyncErr.value = syncLastError.value autoSyncErr.value = syncLastError.value
syncing.value = true syncing.value = true
try { try {
await saveSyncConfig()
const result: SyncResult = await syncWithStoredConfig() const result: SyncResult = await syncWithStoredConfig()
lastSync.value = result.at lastSync.value = result.at
syncStatus.value = '推送 ' + result.pushed + ' 条 · 拉取 ' + result.pulled + ' 条 · 删除 ' + result.removed + ' 条' syncStatus.value = '推送 ' + result.pushed + ' 条 · 拉取 ' + result.pulled + ' 条 · 删除 ' + result.removed + ' 条'
@@ -263,7 +254,7 @@ async function doImport() {
<div class="settings"> <div class="settings">
<div class="card section"> <div class="card section">
<div class="section-title">账号与隐私</div> <div class="section-title">账号与隐私</div>
<div class="muted acc-line">版本 v{{ appVersion }} · 当前用户:{{ username }} · 每位用户独立数据库隔离存储于本机 · 登录密码仅保存 PBKDF2 哈希</div> <div class="muted acc-line">v{{ appVersion }} · 当前账号 {{ username }} · 登录密码仅存 PBKDF2 哈希</div>
<div class="ai-grid"> <div class="ai-grid">
<div class="rule-item"> <div class="rule-item">
<label>旧登录密码</label> <label>旧登录密码</label>
@@ -352,15 +343,10 @@ async function doImport() {
<div class="card section"> <div class="card section">
<div class="section-title">多端数据同步</div> <div class="section-title">多端数据同步</div>
<div class="muted acc-line">云端账号:{{ cloudUser || '未绑定' }} · 数据仅保存在服务器,本机只是实时缓存(变更 ~3 秒推上云端 + 其他端秒级自动刷新);密码库同步密文,主密码永不出本机</div> <div class="muted acc-line">云端账号:{{ cloudUser || '未绑定' }} · 数据存于服务器,其他端变更秒级自动刷新</div>
<div class="rule-item">
<label>同步服务地址(默认即可)</label>
<input class="input" v-model="syncBase" placeholder="https://daywk.linyux.cn" />
</div>
<div class="rule-save"> <div class="rule-save">
<button class="btn btn-sm" @click="saveSyncConfig">保存地址</button>
<button class="btn btn-primary btn-sm" :disabled="syncing" @click="doSyncNow">{{ syncing ? '同步中' : '立即同步' }}</button> <button class="btn btn-primary btn-sm" :disabled="syncing" @click="doSyncNow">{{ syncing ? '同步中' : '立即同步' }}</button>
<button class="btn btn-sm" @click="rebindCloud">重建云端绑定</button> <button class="btn btn-sm" @click="rebindCloud">重新校验连接</button>
</div> </div>
<div v-if="syncStatus" class="muted acc-line">上次同步:{{ lastSync || '--' }} · {{ syncStatus }}</div> <div v-if="syncStatus" class="muted acc-line">上次同步:{{ lastSync || '--' }} · {{ syncStatus }}</div>
<div v-if="syncError" class="form-error">{{ syncError }}</div> <div v-if="syncError" class="form-error">{{ syncError }}</div>
@@ -385,7 +371,7 @@ async function doImport() {
</div> </div>
<div class="rule-save"> <div class="rule-save">
<button class="btn btn-primary btn-sm" @click="saveAi">保存 AI 配置</button> <button class="btn btn-primary btn-sm" @click="saveAi">保存 AI 配置</button>
<span class="muted loc-note">配置仅保存在本机浏览器;总结页可用 AI 润色草稿,需服务端允许浏览器跨域</span> <span class="muted loc-note">配置仅保存在本机;总结页「AI 润色」需此处先配置</span>
</div> </div>
</div> </div>
@@ -402,7 +388,7 @@ async function doImport() {
/> />
</div> </div>
<div v-if="importError" class="import-error">{{ importError }}</div> <div v-if="importError" class="import-error">{{ importError }}</div>
<div class="muted backup-note">数据保存在浏览器 IndexedDB 中,建议定期导出 JSON 备份。</div> <div class="muted backup-note">数据保存在云端服务器;导出的 JSON 备份用于跨设备迁移或灾后恢复。</div>
</div> </div>
<ConfirmDialog <ConfirmDialog