ui: 全站界面重设计(文字侧栏+灰底白卡层次+组件统一) fix: 添加记录弹窗不显示/计时汇总崩溃/CSV导出崩溃/记账切换类型崩溃
This commit is contained in:
@@ -0,0 +1,29 @@
|
|||||||
|
# v0.6.0 发布说明(2026-10-26)
|
||||||
|
|
||||||
|
## 界面整体重设计
|
||||||
|
|
||||||
|
本版本对全部界面做了系统性改版,统一为「高级白」企业级视觉,信息层次更清晰:
|
||||||
|
|
||||||
|
- **桌面侧栏重做**:由纯图标改为「图标 + 文字」导航,按 记录 / 工具 / 分析 / 系统 分组;新增品牌区与当前用户区(头像、用户名、退出)。
|
||||||
|
- **顶栏精简**:只保留页面标题、全局搜索与打卡按钮;用户信息与退出移入侧栏底部。
|
||||||
|
- **内容层次**:内容区改为浅灰底 + 白色卡片,卡片带细边框与轻投影,主次分明。
|
||||||
|
- **组件统一**:按钮、输入框、表格、弹窗、分段选择器、进度条等全部收敛为全局统一样式,各页面不再各自为政。
|
||||||
|
- **逐页重排**:日历(网格线 + 选中描边)、详情(导航与导出合并为一条工具栏)、计划(长期计划与每日安排分区块、列表合并为单卡)、记账、统计(热力图改品牌蓝)、总结、备忘录、密码库、设置、用户管理。
|
||||||
|
- **登录页升级**:品牌标识、卡片投影。
|
||||||
|
- **平板适配**:721–1000px 侧栏自动折叠为图标栏。
|
||||||
|
- **移动端**:底部标签栏与「更多」弹层沿用,并随新基座精修间距与弹窗。
|
||||||
|
|
||||||
|
## 问题修复
|
||||||
|
|
||||||
|
- 修复:详情页「添加记录」按钮点击无反应(弹窗开关与编辑目标状态混用所致)。
|
||||||
|
- 修复:计划页有任务正在计时时,汇总行会触发未定义函数导致整页报错。
|
||||||
|
- 修复:设置页导出工作记录 CSV 缺少分类标签导入,触发即报错。
|
||||||
|
- 修复:记账弹窗切换收支类型时引用了不存在的分类常量,触发即报错。
|
||||||
|
- 记录弹窗标题与按钮文案随「新增 / 编辑」模式正确切换。
|
||||||
|
|
||||||
|
## 升级与部署
|
||||||
|
|
||||||
|
- 桌面端:安装 InternLog-Setup-0.6.0.exe。
|
||||||
|
- 安卓端:安装 InternLog-0.6.0-debug.apk。
|
||||||
|
- 服务器:`cd /opt/daywork && git pull && cd server && docker compose up -d --build`(网页端 bundle 随仓库更新)。
|
||||||
|
- 数据结构无变更,v0.5.0 及之前版本可直接升级。
|
||||||
+2
-2
@@ -1,2 +1,2 @@
|
|||||||
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./web-e9jKyg3_.js","./index-DLDMtYxl.js","./index-ieoBMDLY.css"])))=>i.map(i=>d[i]);
|
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./web-BbcS1QA8.js","./index-CtHE6m7N.js","./index-C8-KlPfD.css"])))=>i.map(i=>d[i]);
|
||||||
import{r as s,_ as i}from"./index-DLDMtYxl.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-e9jKyg3_.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-CtHE6m7N.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-BbcS1QA8.js"),__vite__mapDeps([0,1,2]),import.meta.url).then(e=>new e.FilesystemWeb)});export{t as Directory,a as Encoding,E as Filesystem};
|
||||||
+1
File diff suppressed because one or more lines are too long
+27
File diff suppressed because one or more lines are too long
-27
File diff suppressed because one or more lines are too long
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+2
-2
@@ -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-DLDMtYxl.js"></script>
|
<script type="module" crossorigin src="./assets/index-CtHE6m7N.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="./assets/index-ieoBMDLY.css">
|
<link rel="stylesheet" crossorigin href="./assets/index-C8-KlPfD.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "intern-log",
|
"name": "intern-log",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.5.0",
|
"version": "0.6.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
+130
-88
@@ -230,20 +230,31 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<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">
|
||||||
|
<div class="brand" @click="nav('calendar')">
|
||||||
|
<span class="brand-mark"><Icon name="calendar" :size="15" /></span>
|
||||||
|
<span class="brand-name">实习工作日志</span>
|
||||||
|
</div>
|
||||||
|
<div class="side-nav">
|
||||||
<template v-for="(v, i) in shownViews" :key="v.key">
|
<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>
|
<div v-if="i === 0 || v.group !== shownViews[i - 1].group" class="nav-group">{{ v.group }}</div>
|
||||||
<button
|
<button class="nav-btn" :class="{ active: route === v.key }" :data-tip="v.name" @click="nav(v.key)">
|
||||||
class="nav-btn" :class="{ active: route === v.key }"
|
<Icon :name="v.icon" :size="17" />
|
||||||
:data-tip="v.name" @click="nav(v.key)"
|
<span class="nav-label">{{ v.name }}</span>
|
||||||
>
|
|
||||||
<Icon :name="v.icon" :size="18" />
|
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class="side-foot">
|
||||||
|
<span class="avatar">{{ (session?.username || '用户').slice(0, 1).toUpperCase() }}</span>
|
||||||
|
<span class="foot-name">{{ session?.username }}</span>
|
||||||
|
<button class="icon-btn" title="退出登录" @click="doLogout">
|
||||||
|
<Icon name="export" :size="15" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<header class="topbar">
|
<header class="topbar">
|
||||||
@@ -281,9 +292,7 @@ onMounted(async () => {
|
|||||||
<span class="dot" style="background: var(--warn)"></span>
|
<span class="dot" style="background: var(--warn)"></span>
|
||||||
填写本周周记
|
填写本周周记
|
||||||
</button>
|
</button>
|
||||||
<span class="user-chip num mobile-hide">{{ session?.username }}</span>
|
<button class="btn btn-primary" :disabled="clockDone" @click="doClock">
|
||||||
<button class="btn btn-sm mobile-hide" @click="doLogout">退出</button>
|
|
||||||
<button class="btn btn-primary btn-sm" :disabled="clockDone" @click="doClock">
|
|
||||||
<Icon :name="clockDone ? 'check-circle' : 'clock'" :size="14" />
|
<Icon :name="clockDone ? 'check-circle' : 'clock'" :size="14" />
|
||||||
<span class="num">{{ clockLabel }}</span>
|
<span class="num">{{ clockLabel }}</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -377,40 +386,73 @@ onMounted(async () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.app { display: flex; height: 100vh; height: 100dvh; overflow: hidden; }
|
.app { display: flex; height: 100vh; height: 100dvh; overflow: hidden; background: var(--bg-app); }
|
||||||
|
|
||||||
|
/* ---------- 侧栏 ---------- */
|
||||||
.side {
|
.side {
|
||||||
width: 56px;
|
width: var(--side-w);
|
||||||
flex: none;
|
flex: none;
|
||||||
border-right: 1px solid var(--border);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
|
||||||
padding-top: 10px;
|
|
||||||
gap: 4px;
|
|
||||||
background: var(--bg-page);
|
background: var(--bg-page);
|
||||||
|
border-right: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
.brand {
|
||||||
|
display: flex; align-items: center; gap: 9px;
|
||||||
|
height: var(--topbar-h); flex: none;
|
||||||
|
padding: 0 16px;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.brand-mark {
|
||||||
|
width: 26px; height: 26px; flex: none;
|
||||||
|
border-radius: 7px;
|
||||||
|
background: var(--accent);
|
||||||
|
color: #FFFFFF;
|
||||||
|
display: flex; align-items: center; justify-content: center;
|
||||||
|
}
|
||||||
|
.brand-name { font-size: 14px; font-weight: 600; letter-spacing: 0.3px; white-space: nowrap; }
|
||||||
|
.side-nav {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
.nav-group {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--text-3);
|
||||||
|
padding: 12px 8px 4px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.nav-btn {
|
.nav-btn {
|
||||||
width: 40px;
|
display: flex; align-items: center; gap: 10px;
|
||||||
height: 40px;
|
height: 36px; padding: 0 10px;
|
||||||
display: flex;
|
border: none; background: transparent;
|
||||||
align-items: center;
|
border-radius: var(--radius-sm);
|
||||||
justify-content: center;
|
color: var(--text-2);
|
||||||
border-radius: var(--radius-md);
|
|
||||||
color: var(--text-3);
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border: none;
|
font-size: 13px;
|
||||||
background: transparent;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
text-align: left;
|
||||||
|
transition: background 0.12s, color 0.12s;
|
||||||
}
|
}
|
||||||
.nav-btn:hover { background: var(--bg-hover); color: var(--text-1); }
|
.nav-btn:hover { background: var(--bg-hover); color: var(--text-1); }
|
||||||
.nav-btn.active { background: var(--accent-bg); color: var(--accent); }
|
.nav-btn.active { background: var(--accent-bg); color: var(--accent); font-weight: 500; }
|
||||||
|
.nav-btn.active::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute; left: 0; top: 8px; bottom: 8px;
|
||||||
|
width: 3px; border-radius: 2px;
|
||||||
|
background: var(--accent);
|
||||||
|
}
|
||||||
|
.nav-label { white-space: nowrap; }
|
||||||
.nav-btn::after {
|
.nav-btn::after {
|
||||||
content: attr(data-tip);
|
content: attr(data-tip);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 46px;
|
left: 50px;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
background: var(--text-1);
|
background: var(--text-1);
|
||||||
@@ -423,40 +465,57 @@ onMounted(async () => {
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
transition: opacity 0.15s;
|
transition: opacity 0.15s;
|
||||||
|
z-index: 40;
|
||||||
}
|
}
|
||||||
.nav-btn:hover::after { opacity: 1; }
|
.side-foot {
|
||||||
.nav-sep {
|
|
||||||
width: 26px;
|
|
||||||
height: 1px;
|
|
||||||
flex: none;
|
flex: none;
|
||||||
background: var(--border);
|
display: flex; align-items: center; gap: 8px;
|
||||||
margin: 5px 0;
|
padding: 10px 12px;
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
}
|
}
|
||||||
:focus-visible {
|
.avatar {
|
||||||
outline: 2px solid var(--accent);
|
width: 28px; height: 28px; flex: none;
|
||||||
outline-offset: 1px;
|
border-radius: 50%;
|
||||||
|
background: var(--accent-bg);
|
||||||
|
color: var(--accent);
|
||||||
|
display: flex; align-items: center; justify-content: center;
|
||||||
|
font-size: 13px; font-weight: 600;
|
||||||
|
}
|
||||||
|
.foot-name {
|
||||||
|
flex: 1; min-width: 0;
|
||||||
|
font-size: 13px; color: var(--text-2);
|
||||||
|
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---------- 主区 ---------- */
|
||||||
.main { flex: 1; display: flex; flex-direction: column; min-width: 0; }
|
.main { flex: 1; display: flex; flex-direction: column; min-width: 0; }
|
||||||
.topbar {
|
.topbar {
|
||||||
height: 56px;
|
height: var(--topbar-h);
|
||||||
flex: none;
|
flex: none;
|
||||||
|
background: var(--bg-page);
|
||||||
border-bottom: 1px solid var(--border);
|
border-bottom: 1px solid var(--border);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
padding: 0 20px;
|
padding: 0 20px;
|
||||||
|
z-index: 10;
|
||||||
}
|
}
|
||||||
.title { font-size: 14px; font-weight: 600; }
|
.title { font-size: 15px; font-weight: 600; white-space: nowrap; }
|
||||||
|
|
||||||
.topbar-actions { display: flex; align-items: center; gap: 8px; }
|
.topbar-actions { display: flex; align-items: center; gap: 8px; }
|
||||||
|
|
||||||
.gsearch { position: relative; width: 300px; max-width: 38vw; }
|
.gsearch { position: relative; width: 320px; max-width: 38vw; }
|
||||||
.gs-icon { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); color: var(--text-3); }
|
.gs-icon { position: absolute; left: 11px; top: 50%; transform: translateY(-50%); color: var(--text-3); }
|
||||||
.gs-input { height: 30px; padding-left: 30px; }
|
.gs-input {
|
||||||
|
height: 32px; padding-left: 31px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--bg-subtle);
|
||||||
|
border-color: transparent;
|
||||||
|
}
|
||||||
|
.gs-input:focus { background: var(--bg-page); border-color: var(--accent); }
|
||||||
.gs-panel {
|
.gs-panel {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 36px;
|
top: 38px;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
background: var(--bg-page);
|
background: var(--bg-page);
|
||||||
@@ -473,7 +532,7 @@ onMounted(async () => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding: 6px 8px;
|
padding: 7px 8px;
|
||||||
border-radius: var(--radius-sm);
|
border-radius: var(--radius-sm);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
@@ -490,12 +549,6 @@ onMounted(async () => {
|
|||||||
.gs-date { font-size: 12px; flex: none; }
|
.gs-date { font-size: 12px; flex: none; }
|
||||||
.gs-empty { padding: 10px; font-size: 12px; text-align: center; }
|
.gs-empty { padding: 10px; font-size: 12px; text-align: center; }
|
||||||
|
|
||||||
.user-chip {
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--text-2);
|
|
||||||
padding: 0 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.week-tip {
|
.week-tip {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -504,15 +557,16 @@ onMounted(async () => {
|
|||||||
color: var(--warn);
|
color: var(--warn);
|
||||||
background: var(--bg-page);
|
background: var(--bg-page);
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
height: 26px;
|
height: 28px;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.week-tip:hover { background: var(--bg-subtle); }
|
.week-tip:hover { background: #FDF4EC; }
|
||||||
|
|
||||||
.content { flex: 1; overflow: auto; }
|
.content { flex: 1; overflow: auto; }
|
||||||
.content-inner { max-width: 1200px; margin: 0 auto; padding: 20px; }
|
.content-inner { max-width: 1280px; margin: 0 auto; padding: 20px 24px 48px; }
|
||||||
|
|
||||||
.toast-wrap {
|
.toast-wrap {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@@ -537,11 +591,11 @@ onMounted(async () => {
|
|||||||
.toast-enter-from, .toast-leave-to { opacity: 0; transform: translateY(-4px); }
|
.toast-enter-from, .toast-leave-to { opacity: 0; transform: translateY(-4px); }
|
||||||
.toast-enter-active, .toast-leave-active { transition: all 0.15s ease; }
|
.toast-enter-active, .toast-leave-active { transition: all 0.15s ease; }
|
||||||
|
|
||||||
/* ---- 新增的手机端组件样式(桌面端隐藏) ---- */
|
/* ---------- 手机端组件 ---------- */
|
||||||
.mob-search-btn {
|
.mob-search-btn {
|
||||||
display: none;
|
display: none;
|
||||||
width: 30px;
|
width: 32px;
|
||||||
height: 30px;
|
height: 32px;
|
||||||
border: none;
|
border: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border-radius: var(--radius-sm);
|
border-radius: var(--radius-sm);
|
||||||
@@ -550,8 +604,6 @@ onMounted(async () => {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
.mobile-hide { display: initial; }
|
|
||||||
|
|
||||||
.tabbar {
|
.tabbar {
|
||||||
display: none;
|
display: none;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@@ -565,7 +617,7 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
.tab-btn {
|
.tab-btn {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: calc(52px - 6px);
|
height: 50px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -577,13 +629,13 @@ onMounted(async () => {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
}
|
}
|
||||||
.tab-btn.active { color: var(--accent); }
|
.tab-btn.active { color: var(--accent); font-weight: 500; }
|
||||||
|
|
||||||
.sheet-mask {
|
.sheet-mask {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
z-index: 70;
|
z-index: 70;
|
||||||
background: rgba(29, 33, 41, 0.35);
|
background: rgba(29, 33, 41, 0.4);
|
||||||
}
|
}
|
||||||
.more-sheet {
|
.more-sheet {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -591,7 +643,7 @@ onMounted(async () => {
|
|||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
background: var(--bg-page);
|
background: var(--bg-page);
|
||||||
border-radius: 14px 14px 0 0;
|
border-radius: 16px 16px 0 0;
|
||||||
padding: 10px 14px calc(14px + env(safe-area-inset-bottom));
|
padding: 10px 14px calc(14px + env(safe-area-inset-bottom));
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -623,17 +675,8 @@ onMounted(async () => {
|
|||||||
.sheet-divider { height: 1px; background: var(--border); margin: 6px 0; }
|
.sheet-divider { height: 1px; background: var(--border); margin: 6px 0; }
|
||||||
.sheet-danger { color: var(--danger); }
|
.sheet-danger { color: var(--danger); }
|
||||||
|
|
||||||
.msw-mask {
|
.msw-mask { position: fixed; inset: 0; z-index: 70; background: var(--bg-page); }
|
||||||
position: fixed;
|
.msw { height: 100%; display: flex; flex-direction: column; }
|
||||||
inset: 0;
|
|
||||||
z-index: 70;
|
|
||||||
background: var(--bg-page);
|
|
||||||
}
|
|
||||||
.msw {
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.msw-bar {
|
.msw-bar {
|
||||||
height: 52px;
|
height: 52px;
|
||||||
flex: none;
|
flex: none;
|
||||||
@@ -668,8 +711,19 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
.msw-item:hover, .msw-item:active { background: var(--bg-hover); }
|
.msw-item:hover, .msw-item:active { background: var(--bg-hover); }
|
||||||
|
|
||||||
|
/* ---------- 平板(721-1000px):侧栏折叠为图标 ---------- */
|
||||||
|
@media (max-width: 1000px) and (min-width: 721px) {
|
||||||
|
.side { width: var(--side-w-collapsed); }
|
||||||
|
.brand { justify-content: center; padding: 0; }
|
||||||
|
.brand-name, .nav-label, .nav-group { display: none; }
|
||||||
|
.nav-btn { justify-content: center; padding: 0; width: 40px; align-self: center; }
|
||||||
|
.nav-btn.active::before { display: none; }
|
||||||
|
.side-foot { flex-direction: column; gap: 6px; }
|
||||||
|
.foot-name { display: none; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- 手机端(<=720px) ---------- */
|
||||||
@media (max-width: 720px) {
|
@media (max-width: 720px) {
|
||||||
/* 布局骨架:顶栏一行 + 内容 + 底部标签栏 */
|
|
||||||
.app { display: block; height: 100dvh; }
|
.app { display: block; height: 100dvh; }
|
||||||
.side { display: none; }
|
.side { display: none; }
|
||||||
.main { display: flex; flex-direction: column; height: 100dvh; min-width: 0; }
|
.main { display: flex; flex-direction: column; height: 100dvh; min-width: 0; }
|
||||||
@@ -685,29 +739,17 @@ onMounted(async () => {
|
|||||||
.gsearch .gs-panel { display: none; }
|
.gsearch .gs-panel { display: none; }
|
||||||
.mob-search-btn { display: flex; }
|
.mob-search-btn { display: flex; }
|
||||||
.topbar-actions { margin-left: auto; gap: 6px; }
|
.topbar-actions { margin-left: auto; gap: 6px; }
|
||||||
.mobile-hide { display: none !important; }
|
|
||||||
.week-tip { height: 28px; max-width: 118px; padding: 0 8px; white-space: nowrap; overflow: hidden; }
|
.week-tip { height: 28px; max-width: 118px; padding: 0 8px; white-space: nowrap; overflow: hidden; }
|
||||||
.topbar-actions .btn { padding: 0 10px; }
|
.topbar-actions .btn { padding: 0 10px; height: 32px; }
|
||||||
.topbar-actions .btn .num { font-size: 12px; }
|
.topbar-actions .btn .num { font-size: 12px; }
|
||||||
|
|
||||||
.tabbar { display: flex; }
|
.tabbar { display: flex; }
|
||||||
.content { flex: 1; min-height: 0; }
|
.content { flex: 1; min-height: 0; }
|
||||||
.content-inner {
|
.content-inner {
|
||||||
max-width: none;
|
max-width: none;
|
||||||
padding: 12px 12px calc(66px + env(safe-area-inset-bottom));
|
padding: 12px 12px calc(70px + env(safe-area-inset-bottom));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 卡片与表单:统一左右贴边、内距收紧 */
|
|
||||||
.card { border-radius: var(--radius-md); }
|
|
||||||
input.input, select.select, textarea.textarea { width: 100%; box-sizing: border-box; }
|
|
||||||
.btn { touch-action: manipulation; }
|
|
||||||
|
|
||||||
/* 打卡按钮更醒目 */
|
|
||||||
.topbar-actions .btn-primary {
|
|
||||||
height: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 提示信息对齐左上,避免遮挡底部标签栏 */
|
|
||||||
.toast-wrap {
|
.toast-wrap {
|
||||||
top: 6px;
|
top: 6px;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
|
|||||||
@@ -41,29 +41,7 @@ onUnmounted(() => window.removeEventListener('keydown', onKey))
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.mask {
|
.dialog { width: 380px; }
|
||||||
position: fixed;
|
|
||||||
inset: 0;
|
|
||||||
background: rgba(29, 33, 41, 0.4);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
z-index: 100;
|
|
||||||
}
|
|
||||||
.dialog {
|
|
||||||
width: 380px;
|
|
||||||
max-width: calc(100vw - 32px);
|
|
||||||
background: var(--bg-page);
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
box-shadow: var(--shadow-float);
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
.dialog-title { font-size: 14px; font-weight: 600; color: var(--text-1); }
|
|
||||||
.dialog-msg { margin-top: 8px; color: var(--text-2); font-size: 13px; line-height: 1.7; word-break: break-all; }
|
.dialog-msg { margin-top: 8px; color: var(--text-2); font-size: 13px; line-height: 1.7; word-break: break-all; }
|
||||||
.dialog-btns { margin-top: 20px; display: flex; justify-content: flex-end; gap: 8px; }
|
.dialog-btns { margin-top: 20px; }
|
||||||
.btn-danger-solid { background: var(--danger); border-color: var(--danger); color: #FFFFFF; }
|
|
||||||
.btn-danger-solid:hover { background: #D13A3F; }
|
|
||||||
|
|
||||||
.modal-enter-active, .modal-leave-active { transition: opacity 0.15s ease; }
|
|
||||||
.modal-enter-from, .modal-leave-to { opacity: 0; }
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -112,44 +112,7 @@ function save() {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.mask {
|
.dialog { width: 480px; }
|
||||||
position: fixed;
|
|
||||||
inset: 0;
|
|
||||||
background: rgba(29, 33, 41, 0.4);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
z-index: 90;
|
|
||||||
}
|
|
||||||
.dialog {
|
|
||||||
width: 480px;
|
|
||||||
max-width: calc(100vw - 32px);
|
|
||||||
background: var(--bg-page);
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
box-shadow: var(--shadow-float);
|
|
||||||
}
|
|
||||||
.dialog-head {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 16px 20px 12px;
|
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
}
|
|
||||||
.dialog-title { font-size: 14px; font-weight: 600; color: var(--text-1); }
|
|
||||||
.dialog-body { padding: 16px 20px; }
|
|
||||||
.dialog-btns {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 12px 20px 16px;
|
|
||||||
border-top: 1px solid var(--border);
|
|
||||||
}
|
|
||||||
.form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 16px; }
|
|
||||||
.field.span2 { grid-column: span 2; }
|
|
||||||
.field label { display: block; font-size: 12px; color: var(--text-2); margin-bottom: 4px; }
|
|
||||||
.pwd-row { display: flex; gap: 8px; }
|
.pwd-row { display: flex; gap: 8px; }
|
||||||
.pwd-row .input { flex: 1; }
|
.pwd-row .input { flex: 1; }
|
||||||
.form-error { color: var(--danger); font-size: 12px; margin-top: 8px; }
|
|
||||||
.modal-enter-active, .modal-leave-active { transition: opacity 0.15s ease; }
|
|
||||||
.modal-enter-from, .modal-leave-to { opacity: 0; }
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -48,8 +48,10 @@ watch(() => props.open, async v => {
|
|||||||
})
|
})
|
||||||
onUnmounted(() => window.removeEventListener('keydown', onKey))
|
onUnmounted(() => window.removeEventListener('keydown', onKey))
|
||||||
|
|
||||||
watch(fType, t => {
|
// 切换收支类型后:重新拉取对应分类列表,并回落到第一项
|
||||||
fCategory.value = t === 'expense' ? EXPENSE_CATEGORIES[0] : INCOME_CATEGORIES[0]
|
watch(fType, async () => {
|
||||||
|
await loadCats()
|
||||||
|
fCategory.value = categoryOptions.value[0] || ''
|
||||||
})
|
})
|
||||||
|
|
||||||
const categoryOptions = ref<string[]>([])
|
const categoryOptions = ref<string[]>([])
|
||||||
@@ -158,57 +160,7 @@ function save() {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.mask {
|
.dialog { width: 480px; }
|
||||||
position: fixed;
|
|
||||||
inset: 0;
|
|
||||||
background: rgba(29, 33, 41, 0.4);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
z-index: 90;
|
|
||||||
}
|
|
||||||
.dialog {
|
|
||||||
width: 480px;
|
|
||||||
max-width: calc(100vw - 32px);
|
|
||||||
background: var(--bg-page);
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
box-shadow: var(--shadow-float);
|
|
||||||
}
|
|
||||||
.dialog-head {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 16px 20px 12px;
|
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
}
|
|
||||||
.dialog-title { font-size: 14px; font-weight: 600; color: var(--text-1); }
|
|
||||||
.add-cat { display: flex; gap: 6px; margin-top: 6px; }
|
.add-cat { display: flex; gap: 6px; margin-top: 6px; }
|
||||||
.add-cat-input { flex: 1; height: 28px; font-size: 12px; }
|
.add-cat-input { flex: 1; height: 28px; font-size: 12px; }
|
||||||
.dialog-body { padding: 16px 20px; }
|
|
||||||
.dialog-btns {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 12px 20px 16px;
|
|
||||||
border-top: 1px solid var(--border);
|
|
||||||
}
|
|
||||||
.form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 16px; }
|
|
||||||
.field.span2 { grid-column: span 2; }
|
|
||||||
.field label { display: block; font-size: 12px; color: var(--text-2); margin-bottom: 4px; }
|
|
||||||
.seg { display: inline-flex; border: 1px solid var(--border-strong); border-radius: var(--radius-sm); overflow: hidden; }
|
|
||||||
.seg-btn {
|
|
||||||
height: 30px;
|
|
||||||
padding: 0 14px;
|
|
||||||
border: none;
|
|
||||||
background: var(--bg-page);
|
|
||||||
color: var(--text-2);
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
.seg-btn + .seg-btn { border-left: 1px solid var(--border); }
|
|
||||||
.seg-btn.active { background: var(--accent-bg); color: var(--accent); }
|
|
||||||
.form-error { color: var(--danger); font-size: 12px; margin-top: 8px; }
|
|
||||||
.input.invalid, .input.invalid:focus { border-color: var(--danger); }
|
|
||||||
.modal-enter-active, .modal-leave-active { transition: opacity 0.15s ease; }
|
|
||||||
.modal-enter-from, .modal-leave-to { opacity: 0; }
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -163,12 +163,16 @@ async function saveMakeupForm() {
|
|||||||
|
|
||||||
|
|
||||||
// ---- 条目操作 ----
|
// ---- 条目操作 ----
|
||||||
|
// 弹窗开关与编辑目标分离:添加模式 editTarget 为 null,但弹窗也要打开
|
||||||
const editTarget = ref<LogItem | null>(null)
|
const editTarget = ref<LogItem | null>(null)
|
||||||
|
const dialogOpen = ref(false)
|
||||||
function openEdit(it: LogItem) {
|
function openEdit(it: LogItem) {
|
||||||
editTarget.value = it
|
editTarget.value = it
|
||||||
|
dialogOpen.value = true
|
||||||
}
|
}
|
||||||
function openAdd() {
|
function openAdd() {
|
||||||
editTarget.value = null
|
editTarget.value = null
|
||||||
|
dialogOpen.value = true
|
||||||
}
|
}
|
||||||
async function onEditSave(payload: {
|
async function onEditSave(payload: {
|
||||||
id: number
|
id: number
|
||||||
@@ -196,6 +200,7 @@ async function onEditSave(payload: {
|
|||||||
showToast('记录已添加')
|
showToast('记录已添加')
|
||||||
}
|
}
|
||||||
editTarget.value = null
|
editTarget.value = null
|
||||||
|
dialogOpen.value = false
|
||||||
await load()
|
await load()
|
||||||
}
|
}
|
||||||
async function toggleMilestone(it: LogItem) {
|
async function toggleMilestone(it: LogItem) {
|
||||||
@@ -398,11 +403,11 @@ async function confirmDelete() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<EditLogDialog
|
<EditLogDialog
|
||||||
:open="!!editTarget"
|
:open="dialogOpen"
|
||||||
:item="editTarget"
|
:item="editTarget"
|
||||||
:projects="projectsAll"
|
:projects="projectsAll"
|
||||||
:date="date"
|
:date="date"
|
||||||
@cancel="editTarget = null"
|
@cancel="dialogOpen = false"
|
||||||
@save="onEditSave"
|
@save="onEditSave"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -412,34 +417,32 @@ async function confirmDelete() {
|
|||||||
.dp-head { margin-bottom: 12px; }
|
.dp-head { margin-bottom: 12px; }
|
||||||
.dp-date { font-size: 14px; font-weight: 600; }
|
.dp-date { font-size: 14px; font-weight: 600; }
|
||||||
|
|
||||||
.sec { padding: 14px 16px; margin-bottom: 12px; }
|
|
||||||
.sec-title { font-size: 13px; font-weight: 600; margin-bottom: 10px; color: var(--text-1); }
|
|
||||||
.sec-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; margin-bottom: 10px; }
|
|
||||||
.sec-head .sec-title { margin-bottom: 0; }
|
|
||||||
.form-hint { font-size: 12px; }
|
|
||||||
|
|
||||||
.att-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 16px; }
|
.att-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 16px; }
|
||||||
.att-cell label, .field label { display: block; font-size: 12px; color: var(--text-2); margin-bottom: 4px; }
|
.att-cell label { display: block; font-size: 12px; color: var(--text-2); margin-bottom: 4px; }
|
||||||
.att-val { display: flex; align-items: center; gap: 6px; min-height: 32px; font-size: 13px; }
|
.att-val { display: flex; align-items: center; gap: 6px; min-height: 32px; font-size: 13px; }
|
||||||
.status-val { min-height: 32px; }
|
|
||||||
.val-empty { color: var(--text-3); }
|
.val-empty { color: var(--text-3); }
|
||||||
.edit-row { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
|
.edit-row { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
|
||||||
.time-input { width: 104px; }
|
.time-input { width: 104px; }
|
||||||
.att-note { margin-top: 10px; font-size: 12px; }
|
.att-note { margin-top: 10px; font-size: 12px; }
|
||||||
.att-actions { display: flex; gap: 8px; margin-top: 12px; }
|
.att-actions { display: flex; gap: 8px; margin-top: 12px; flex-wrap: wrap; }
|
||||||
|
|
||||||
.form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 16px; }
|
|
||||||
.field.span2 { grid-column: span 2; }
|
|
||||||
.form-footer {
|
.form-footer {
|
||||||
display: flex; justify-content: flex-end; gap: 8px;
|
display: flex; justify-content: flex-end; gap: 8px;
|
||||||
margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border);
|
margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border);
|
||||||
}
|
}
|
||||||
.form-error { color: var(--danger); font-size: 12px; margin-top: 4px; }
|
|
||||||
.input.invalid { border-color: var(--danger); }
|
|
||||||
.input.invalid:focus { border-color: var(--danger); }
|
|
||||||
|
|
||||||
.list-sec { margin-bottom: 4px; }
|
.list-sec { margin-bottom: 4px; }
|
||||||
.item { border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 8px 10px; margin-bottom: 6px; background: var(--bg-page); }
|
.sec-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; margin-bottom: 10px; }
|
||||||
|
.sec-head .sec-title { margin-bottom: 0; }
|
||||||
|
.item {
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
padding: 9px 12px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
background: var(--bg-subtle);
|
||||||
|
transition: border-color 0.12s, background 0.12s;
|
||||||
|
}
|
||||||
|
.item:hover { border-color: var(--border-strong); background: var(--bg-page); }
|
||||||
.item-main { display: flex; align-items: center; gap: 8px; min-height: 26px; flex-wrap: wrap; }
|
.item-main { display: flex; align-items: center; gap: 8px; min-height: 26px; flex-wrap: wrap; }
|
||||||
.item-content { cursor: text; word-break: break-all; }
|
.item-content { cursor: text; word-break: break-all; }
|
||||||
.item-meta { font-size: 12px; }
|
.item-meta { font-size: 12px; }
|
||||||
@@ -447,11 +450,9 @@ async function confirmDelete() {
|
|||||||
margin-top: 6px; padding-top: 6px; border-top: 1px dashed var(--border);
|
margin-top: 6px; padding-top: 6px; border-top: 1px dashed var(--border);
|
||||||
font-size: 12px; display: flex; flex-direction: column; gap: 2px;
|
font-size: 12px; display: flex; flex-direction: column; gap: 2px;
|
||||||
}
|
}
|
||||||
.empty { text-align: center; padding: 24px 0; }
|
|
||||||
.dp-foot { margin-top: 10px; font-size: 12px; text-align: right; }
|
.dp-foot { margin-top: 10px; font-size: 12px; text-align: right; }
|
||||||
|
|
||||||
@media (max-width: 560px) {
|
@media (max-width: 560px) {
|
||||||
.att-grid, .form-grid { grid-template-columns: 1fr; }
|
.att-grid { grid-template-columns: 1fr; }
|
||||||
.field.span2 { grid-column: auto; }
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ function save() {
|
|||||||
<div class="dialog" role="dialog" aria-label="编辑工作记录">
|
<div class="dialog" role="dialog" aria-label="编辑工作记录">
|
||||||
<div class="dialog-head">
|
<div class="dialog-head">
|
||||||
<div>
|
<div>
|
||||||
<div class="dialog-title">编辑工作记录</div>
|
<div class="dialog-title">{{ item ? '编辑工作记录' : '添加工作记录' }}</div>
|
||||||
<div v-if="date" class="muted dialog-sub">{{ date }}</div>
|
<div v-if="date" class="muted dialog-sub">{{ date }}</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="icon-btn" title="关闭" @click="emit('cancel')">
|
<button class="icon-btn" title="关闭" @click="emit('cancel')">
|
||||||
@@ -173,7 +173,7 @@ function save() {
|
|||||||
</div>
|
</div>
|
||||||
<div class="dialog-btns">
|
<div class="dialog-btns">
|
||||||
<button class="btn btn-sm" @click="emit('cancel')">取消</button>
|
<button class="btn btn-sm" @click="emit('cancel')">取消</button>
|
||||||
<button class="btn btn-primary btn-sm" @click="save">保存修改</button>
|
<button class="btn btn-primary btn-sm" @click="save">{{ item ? '保存修改' : '添加' }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -182,65 +182,5 @@ function save() {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.mask {
|
.dialog { width: 560px; }
|
||||||
position: fixed;
|
|
||||||
inset: 0;
|
|
||||||
background: rgba(29, 33, 41, 0.4);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
z-index: 90;
|
|
||||||
}
|
|
||||||
.dialog {
|
|
||||||
width: 560px;
|
|
||||||
max-width: calc(100vw - 32px);
|
|
||||||
max-height: 85vh;
|
|
||||||
background: var(--bg-page);
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
box-shadow: var(--shadow-float);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.dialog-head {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 16px 20px 12px;
|
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
flex: none;
|
|
||||||
}
|
|
||||||
.dialog-title { font-size: 14px; font-weight: 600; color: var(--text-1); }
|
|
||||||
.dialog-sub { font-size: 12px; margin-top: 2px; }
|
|
||||||
.dialog-body { padding: 16px 20px; overflow: auto; }
|
|
||||||
.dialog-btns {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 12px 20px 16px;
|
|
||||||
border-top: 1px solid var(--border);
|
|
||||||
flex: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 16px; }
|
|
||||||
.field.span2 { grid-column: span 2; }
|
|
||||||
.field label { display: block; font-size: 12px; color: var(--text-2); margin-bottom: 4px; }
|
|
||||||
.form-error { color: var(--danger); font-size: 12px; margin-top: 4px; }
|
|
||||||
.input.invalid, .input.invalid:focus { border-color: var(--danger); }
|
|
||||||
|
|
||||||
.toggle-btn {
|
|
||||||
height: 32px;
|
|
||||||
width: 100%;
|
|
||||||
border: 1px solid var(--border-strong);
|
|
||||||
background: var(--bg-page);
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 13px;
|
|
||||||
color: var(--text-2);
|
|
||||||
}
|
|
||||||
.toggle-btn:hover { background: var(--bg-hover); }
|
|
||||||
.toggle-btn.on { color: var(--accent); border-color: var(--accent); background: var(--accent-bg); }
|
|
||||||
.toggle-btn.onwarn { color: var(--warn); border-color: var(--warn); }
|
|
||||||
|
|
||||||
.modal-enter-active, .modal-leave-active { transition: opacity 0.15s ease; }
|
|
||||||
.modal-enter-from, .modal-leave-to { opacity: 0; }
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
// 约定:每个写函数修改本地缓存后立即调用 commit() —— 推送差异给服务器,
|
// 约定:每个写函数修改本地缓存后立即调用 commit() —— 推送差异给服务器,
|
||||||
// 并以服务器返回的权威快照重建缓存;断网时抛出错误(纯在线模式)。
|
// 并以服务器返回的权威快照重建缓存;断网时抛出错误(纯在线模式)。
|
||||||
import { db, DEFAULT_LOCATIONS, SCHEMA_VERSION, type Attendance, type Credential, type LogItem, type Memo, type Goal, type PlanItem, type Project, type Transaction, type WeeklyNote, type WorkLog } from './schema'
|
import { db, DEFAULT_LOCATIONS, SCHEMA_VERSION, type Attendance, type Credential, type LogItem, type Memo, type Goal, type PlanItem, type Project, type Transaction, type WeeklyNote, type WorkLog } from './schema'
|
||||||
|
|
||||||
|
// 视图直接从 repo 导入这些类型:在此统一再导出
|
||||||
|
export type { Attendance, Credential, LogItem, Memo, Goal, PlanItem, Project, Transaction, WeeklyNote, WorkLog }
|
||||||
import { addDays, nowLocalISO, toDateStr } from '../lib/date'
|
import { addDays, nowLocalISO, toDateStr } from '../lib/date'
|
||||||
import { flushAfterWrite } from '../sync/engine'
|
import { flushAfterWrite } from '../sync/engine'
|
||||||
|
|
||||||
|
|||||||
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
declare const __APP_VERSION__: string
|
||||||
@@ -3,8 +3,8 @@
|
|||||||
(App.vue 的壳层样式是 scoped,跨组件规则统一放这里,便于集中管理)
|
(App.vue 的壳层样式是 scoped,跨组件规则统一放这里,便于集中管理)
|
||||||
============================================================ */
|
============================================================ */
|
||||||
|
|
||||||
/* 通用的白底卡片在窄屏贴边 */
|
|
||||||
@media (max-width: 720px) {
|
@media (max-width: 720px) {
|
||||||
|
/* 卡片在窄屏贴边、圆角略收 */
|
||||||
.card { border-radius: 12px; }
|
.card { border-radius: 12px; }
|
||||||
|
|
||||||
input.input, select.select, textarea.textarea {
|
input.input, select.select, textarea.textarea {
|
||||||
@@ -13,18 +13,18 @@
|
|||||||
}
|
}
|
||||||
.btn { touch-action: manipulation; }
|
.btn { touch-action: manipulation; }
|
||||||
|
|
||||||
/* ---------- 弹窗:窄屏一体布局,覆盖组件内固定宽度 ---------- */
|
/* ---------- 弹窗:窄屏近全屏 ---------- */
|
||||||
.dialog {
|
.dialog {
|
||||||
width: calc(100vw - 20px) !important;
|
width: calc(100vw - 20px) !important;
|
||||||
max-width: calc(100vw - 20px) !important;
|
max-width: calc(100vw - 20px) !important;
|
||||||
max-height: 86vh;
|
max-height: 88vh;
|
||||||
max-height: 86dvh;
|
max-height: 88dvh;
|
||||||
overflow: auto;
|
|
||||||
}
|
}
|
||||||
.dialog-body { padding: 14px !important; }
|
.dialog-head { padding: 14px 16px 10px !important; }
|
||||||
.form-grid { grid-template-columns: 1fr !important; }
|
.dialog-body { padding: 14px 16px !important; }
|
||||||
.dialog-btns { gap: 8px; }
|
.dialog-btns { padding: 10px 16px 14px !important; }
|
||||||
.dialog-btns .btn { flex: 1; }
|
.dialog-btns .btn { flex: 1; }
|
||||||
|
.form-grid { grid-template-columns: 1fr !important; }
|
||||||
|
|
||||||
/* ---------- 日历当日抽屉:改为底部抽屉 ---------- */
|
/* ---------- 日历当日抽屉:改为底部抽屉 ---------- */
|
||||||
.drawer {
|
.drawer {
|
||||||
@@ -35,6 +35,7 @@
|
|||||||
border-left: none !important;
|
border-left: none !important;
|
||||||
border-top: 1px solid var(--border);
|
border-top: 1px solid var(--border);
|
||||||
border-radius: 14px 14px 0 0 !important;
|
border-radius: 14px 14px 0 0 !important;
|
||||||
|
box-shadow: 0 -8px 24px rgba(16, 24, 40, 0.10);
|
||||||
}
|
}
|
||||||
.drawer-enter-active, .drawer-leave-active { transition: transform 0.2s ease, opacity 0.2s ease; }
|
.drawer-enter-active, .drawer-leave-active { transition: transform 0.2s ease, opacity 0.2s ease; }
|
||||||
.drawer-enter-from, .drawer-leave-to { transform: translateY(28px); opacity: 0; }
|
.drawer-enter-from, .drawer-leave-to { transform: translateY(28px); opacity: 0; }
|
||||||
@@ -66,7 +67,7 @@
|
|||||||
.tx-table td.empty { justify-content: center; }
|
.tx-table td.empty { justify-content: center; }
|
||||||
.tx-table td.empty::before { content: none !important; }
|
.tx-table td.empty::before { content: none !important; }
|
||||||
|
|
||||||
/* ---------- 密码库等其余宽表:横向滚动,不再挤压折行 ---------- */
|
/* ---------- 密码库等其余宽表:横向滚动 ---------- */
|
||||||
.table-card, .tx-card { overflow-x: auto; }
|
.table-card, .tx-card { overflow-x: auto; }
|
||||||
.cred-table { min-width: 560px !important; }
|
.cred-table { min-width: 560px !important; }
|
||||||
.th-ops { width: auto !important; }
|
.th-ops { width: auto !important; }
|
||||||
@@ -75,12 +76,18 @@
|
|||||||
.vault-toolbar .flex1 { display: none; }
|
.vault-toolbar .flex1 { display: none; }
|
||||||
.vault-toolbar .btn { flex: none; }
|
.vault-toolbar .btn { flex: none; }
|
||||||
|
|
||||||
|
/* ---------- 统计卡两列 ---------- */
|
||||||
|
.cards { grid-template-columns: repeat(2, minmax(0, 1fr)) !important; gap: 10px !important; }
|
||||||
|
.stat-card { padding: 12px 14px !important; }
|
||||||
|
.stat-num { font-size: 19px !important; }
|
||||||
|
|
||||||
|
/* ---------- 分区与栅格收窄 ---------- */
|
||||||
|
.section, .sec { padding: 12px 12px !important; }
|
||||||
|
.rule-grid { grid-template-columns: 1fr !important; }
|
||||||
|
.two-col { grid-template-columns: 1fr !important; }
|
||||||
|
|
||||||
/* ---------- 日历格子收窄 ---------- */
|
/* ---------- 日历格子收窄 ---------- */
|
||||||
.cal-toolbar { margin-bottom: 8px; }
|
.cal-toolbar { margin-bottom: 8px; }
|
||||||
.cal-cell { min-height: 54px !important; padding: 4px 5px !important; }
|
.cal-cell { min-height: 54px !important; padding: 4px 5px !important; }
|
||||||
.cal-marks { margin-top: 3px !important; height: 12px !important; gap: 3px !important; }
|
.cal-marks { margin-top: 3px !important; height: 12px !important; gap: 3px !important; }
|
||||||
|
|
||||||
/* ---------- 各种桌面习惯型小间距收敛 ---------- */
|
|
||||||
.section, .sec { padding: 12px 12px !important; }
|
|
||||||
.rule-grid { grid-template-columns: 1fr !important; }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,50 +1,72 @@
|
|||||||
|
/* ============================================================
|
||||||
|
设计基座 ·「高级白」企业级
|
||||||
|
层次模型:内容区浅灰底(--bg-app) → 白色卡片(--bg-page) →
|
||||||
|
浅灰内嵌块(--bg-subtle)。所有视图共用此处组件类,禁止各自造轮子。
|
||||||
|
============================================================ */
|
||||||
:root {
|
:root {
|
||||||
--bg-page: #FFFFFF;
|
/* 表面 */
|
||||||
--bg-subtle: #F7F8FA;
|
--bg-app: #F5F6F8; /* 应用内容区底色 */
|
||||||
|
--bg-page: #FFFFFF; /* 卡片 / 面板 */
|
||||||
|
--bg-subtle: #F7F8FA; /* 表头 / 内嵌块 */
|
||||||
--bg-hover: #F2F3F5;
|
--bg-hover: #F2F3F5;
|
||||||
--border: #EBEDF0;
|
/* 文字 */
|
||||||
--border-strong: #DCDEE3;
|
|
||||||
--text-1: #1D2129;
|
--text-1: #1D2129;
|
||||||
--text-2: #4E5969;
|
--text-2: #4E5969;
|
||||||
--text-3: #86909C;
|
--text-3: #86909C;
|
||||||
|
/* 线 */
|
||||||
|
--border: #E8EAEE;
|
||||||
|
--border-strong: #DCDFE4;
|
||||||
|
/* 品牌与语义色 */
|
||||||
--accent: #3370FF;
|
--accent: #3370FF;
|
||||||
--accent-bg: #F0F5FF;
|
--accent-deep: #2B5FD9;
|
||||||
|
--accent-bg: #EEF4FF;
|
||||||
--ok: #23A757;
|
--ok: #23A757;
|
||||||
--warn: #E8842C;
|
--warn: #E8842C;
|
||||||
--danger: #E5484D;
|
--danger: #E5484D;
|
||||||
--dot-empty: #C9CDD4;
|
--dot-empty: #C9CDD4;
|
||||||
|
/* 几何 */
|
||||||
--radius-sm: 6px;
|
--radius-sm: 6px;
|
||||||
--radius-md: 8px;
|
--radius-md: 10px;
|
||||||
--radius-lg: 12px;
|
--radius-lg: 14px;
|
||||||
--shadow-float: 0 4px 16px rgba(29, 33, 41, 0.08);
|
/* 阴影 */
|
||||||
|
--shadow-card: 0 1px 2px rgba(16, 24, 40, 0.04);
|
||||||
|
--shadow-float: 0 12px 32px -8px rgba(16, 24, 40, 0.14), 0 4px 12px rgba(16, 24, 40, 0.06);
|
||||||
|
/* 布局 */
|
||||||
|
--side-w: 216px;
|
||||||
|
--side-w-collapsed: 60px;
|
||||||
|
--topbar-h: 56px;
|
||||||
}
|
}
|
||||||
|
|
||||||
* { box-sizing: border-box; }
|
* { box-sizing: border-box; }
|
||||||
|
|
||||||
html, body { margin: 0; padding: 0; }
|
html, body { margin: 0; padding: 0; }
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background: var(--bg-page);
|
background: var(--bg-app);
|
||||||
color: var(--text-1);
|
color: var(--text-1);
|
||||||
font-family: -apple-system, "PingFang SC", "Microsoft YaHei", system-ui, sans-serif;
|
font-family: -apple-system, "PingFang SC", "Microsoft YaHei", system-ui, sans-serif;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
}
|
}
|
||||||
|
|
||||||
button, input, select, textarea { font: inherit; color: inherit; }
|
button, input, select, textarea { font: inherit; color: inherit; }
|
||||||
|
|
||||||
|
/* ---------- 通用工具类 ---------- */
|
||||||
.num { font-variant-numeric: tabular-nums; }
|
.num { font-variant-numeric: tabular-nums; }
|
||||||
.flex1 { flex: 1; }
|
.flex1 { flex: 1; min-width: 0; }
|
||||||
.muted { color: var(--text-3); }
|
.muted { color: var(--text-3); }
|
||||||
.muted2 { color: var(--text-2); }
|
.muted2 { color: var(--text-2); }
|
||||||
|
|
||||||
|
:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
|
||||||
|
|
||||||
|
/* ---------- 按钮 ---------- */
|
||||||
.btn {
|
.btn {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
padding: 0 12px;
|
padding: 0 14px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
border-radius: var(--radius-sm);
|
border-radius: var(--radius-sm);
|
||||||
border: 1px solid var(--border-strong);
|
border: 1px solid var(--border-strong);
|
||||||
@@ -52,15 +74,39 @@ button, input, select, textarea { font: inherit; color: inherit; }
|
|||||||
color: var(--text-1);
|
color: var(--text-1);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: background 0.12s, border-color 0.12s, color 0.12s;
|
||||||
}
|
}
|
||||||
.btn:hover { background: var(--bg-hover); }
|
.btn:hover { background: var(--bg-hover); border-color: var(--border-strong); }
|
||||||
.btn:disabled { opacity: 0.45; cursor: not-allowed; }
|
.btn:disabled { opacity: 0.45; cursor: not-allowed; }
|
||||||
.btn-primary { background: var(--accent); border-color: var(--accent); color: #FFFFFF; }
|
.btn-primary { background: var(--accent); border-color: var(--accent); color: #FFFFFF; }
|
||||||
.btn-primary:hover { background: #2B5FD9; }
|
.btn-primary:hover { background: var(--accent-deep); border-color: var(--accent-deep); }
|
||||||
.btn-danger { color: var(--danger); border-color: var(--danger); }
|
.btn-danger { color: var(--danger); border-color: var(--danger); background: var(--bg-page); }
|
||||||
.btn-sm { height: 26px; padding: 0 10px; font-size: 12px; }
|
.btn-danger:hover { background: #FDF0F0; }
|
||||||
.btn-xs { height: 22px; padding: 0 8px; font-size: 12px; }
|
.btn-danger-solid { background: var(--danger); border-color: var(--danger); color: #FFFFFF; }
|
||||||
|
.btn-danger-solid:hover { background: #D13A3F; }
|
||||||
|
.btn-sm { height: 28px; padding: 0 12px; font-size: 12px; }
|
||||||
|
.btn-xs { height: 24px; padding: 0 10px; font-size: 12px; }
|
||||||
|
|
||||||
|
.icon-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
flex: none;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
color: var(--text-3);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.12s, color 0.12s;
|
||||||
|
}
|
||||||
|
.icon-btn:hover { background: var(--bg-hover); color: var(--text-1); }
|
||||||
|
.icon-btn.active { color: var(--accent); }
|
||||||
|
.icon-btn.active-warn { color: var(--warn); }
|
||||||
|
|
||||||
|
/* ---------- 表单 ---------- */
|
||||||
.input, .select, .textarea {
|
.input, .select, .textarea {
|
||||||
height: 32px;
|
height: 32px;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
@@ -69,30 +115,30 @@ button, input, select, textarea { font: inherit; color: inherit; }
|
|||||||
background: var(--bg-page);
|
background: var(--bg-page);
|
||||||
outline: none;
|
outline: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
transition: border-color 0.12s, box-shadow 0.12s;
|
||||||
|
}
|
||||||
|
.textarea { height: auto; min-height: 64px; padding: 8px 10px; resize: vertical; line-height: 1.7; }
|
||||||
|
.input:focus, .select:focus, .textarea:focus {
|
||||||
|
border-color: var(--accent);
|
||||||
|
box-shadow: 0 0 0 3px rgba(51, 112, 255, 0.10);
|
||||||
}
|
}
|
||||||
.textarea { height: auto; min-height: 56px; padding: 8px 10px; resize: vertical; }
|
|
||||||
.input:focus, .select:focus, .textarea:focus { border-color: var(--accent); }
|
|
||||||
.input::placeholder, .textarea::placeholder { color: var(--text-3); }
|
.input::placeholder, .textarea::placeholder { color: var(--text-3); }
|
||||||
|
.input.invalid, .input.invalid:focus { border-color: var(--danger); box-shadow: 0 0 0 3px rgba(229, 72, 77, 0.10); }
|
||||||
|
|
||||||
.card { border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--bg-page); }
|
/* ---------- 卡片与分区 ---------- */
|
||||||
|
.card {
|
||||||
.icon-btn {
|
border: 1px solid var(--border);
|
||||||
display: inline-flex;
|
border-radius: var(--radius-md);
|
||||||
align-items: center;
|
background: var(--bg-page);
|
||||||
justify-content: center;
|
box-shadow: var(--shadow-card);
|
||||||
width: 26px;
|
|
||||||
height: 26px;
|
|
||||||
flex: none;
|
|
||||||
border: none;
|
|
||||||
background: transparent;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
color: var(--text-3);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
.icon-btn:hover { background: var(--bg-hover); color: var(--text-1); }
|
.section { padding: 16px 18px; }
|
||||||
.icon-btn.active { color: var(--accent); }
|
.section-title, .sec-title { font-weight: 600; margin-bottom: 12px; font-size: 13px; color: var(--text-1); }
|
||||||
.icon-btn.active-warn { color: var(--warn); }
|
.sec { padding: 16px 18px; }
|
||||||
|
|
||||||
|
.empty { text-align: center; padding: 28px 0; }
|
||||||
|
|
||||||
|
/* ---------- 标签与状态点 ---------- */
|
||||||
.tag {
|
.tag {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 0 8px;
|
padding: 0 8px;
|
||||||
@@ -105,19 +151,140 @@ button, input, select, textarea { font: inherit; color: inherit; }
|
|||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dot { width: 6px; height: 6px; border-radius: 50%; display: inline-block; flex: none; }
|
.dot { width: 6px; height: 6px; border-radius: 50%; display: inline-block; flex: none; }
|
||||||
.dot.normal { background: var(--ok); }
|
.dot.normal { background: var(--ok); }
|
||||||
.dot.late, .dot.early, .dot.incomplete { background: var(--warn); }
|
.dot.late, .dot.early, .dot.incomplete { background: var(--warn); }
|
||||||
.dot.absent { background: var(--danger); }
|
.dot.absent { background: var(--danger); }
|
||||||
.dot.none, .dot.leave { background: var(--dot-empty); }
|
.dot.none, .dot.leave { background: var(--dot-empty); }
|
||||||
|
.dot.rest { background: var(--border); }
|
||||||
|
|
||||||
/* 移动端:避免 iOS 聚焦缩放,输入字号提到 16px */
|
/* ---------- 金额 ---------- */
|
||||||
|
.amt-income { color: var(--ok); }
|
||||||
|
.amt-expense { color: var(--text-1); }
|
||||||
|
.amt-warn { color: var(--warn); }
|
||||||
|
|
||||||
|
/* ---------- 分段选择器(seg) ---------- */
|
||||||
|
.seg {
|
||||||
|
display: inline-flex;
|
||||||
|
border: 1px solid var(--border-strong);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--bg-page);
|
||||||
|
}
|
||||||
|
.seg-btn {
|
||||||
|
border: none;
|
||||||
|
background: var(--bg-page);
|
||||||
|
color: var(--text-2);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
height: 28px;
|
||||||
|
padding: 0 12px;
|
||||||
|
border-right: 1px solid var(--border);
|
||||||
|
transition: background 0.12s, color 0.12s;
|
||||||
|
}
|
||||||
|
.seg-btn:last-child { border-right: none; }
|
||||||
|
.seg-btn:hover { background: var(--bg-hover); }
|
||||||
|
.seg-btn.active { background: var(--accent-bg); color: var(--accent); font-weight: 500; }
|
||||||
|
|
||||||
|
/* ---------- 开关按钮(toggle) ---------- */
|
||||||
|
.toggle-btn {
|
||||||
|
height: 32px;
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid var(--border-strong);
|
||||||
|
background: var(--bg-page);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--text-2);
|
||||||
|
transition: all 0.12s;
|
||||||
|
}
|
||||||
|
.toggle-btn:hover { background: var(--bg-hover); }
|
||||||
|
.toggle-btn.on { color: var(--accent); border-color: var(--accent); background: var(--accent-bg); }
|
||||||
|
.toggle-btn.onwarn { color: var(--warn); border-color: var(--warn); background: #FDF4EC; }
|
||||||
|
|
||||||
|
/* ---------- 数据表格(记账/密码库/用户管理共用) ---------- */
|
||||||
|
.tx-table, .cred-table, .admin-table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
||||||
|
.tx-table th, .cred-table th, .admin-table th {
|
||||||
|
text-align: left;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text-3);
|
||||||
|
font-size: 12px;
|
||||||
|
background: var(--bg-subtle);
|
||||||
|
padding: 9px 14px;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.tx-table td, .cred-table td, .admin-table td {
|
||||||
|
padding: 10px 14px;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
vertical-align: top;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
.tx-table tbody tr:hover, .cred-table tbody tr:hover, .admin-table tbody tr:hover { background: #FAFBFC; }
|
||||||
|
.tx-table tbody tr:last-child td, .cred-table tbody tr:last-child td, .admin-table tbody tr:last-child td { border-bottom: none; }
|
||||||
|
|
||||||
|
/* ---------- 弹窗(全部对话框共用) ---------- */
|
||||||
|
.mask {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(29, 33, 41, 0.45);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 90;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
.dialog {
|
||||||
|
width: 480px;
|
||||||
|
max-width: calc(100vw - 32px);
|
||||||
|
max-height: 86vh;
|
||||||
|
max-height: 86dvh;
|
||||||
|
overflow: auto;
|
||||||
|
background: var(--bg-page);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
box-shadow: var(--shadow-float);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.dialog-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 16px 20px 12px;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
|
.dialog-title { font-size: 14px; font-weight: 600; color: var(--text-1); }
|
||||||
|
.dialog-sub { font-size: 12px; margin-top: 2px; color: var(--text-3); }
|
||||||
|
.dialog-body { padding: 16px 20px; overflow: auto; }
|
||||||
|
.dialog-btns {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 12px 20px 16px;
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
|
.form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 16px; }
|
||||||
|
.form-grid .field.span2, .field.span2 { grid-column: span 2; }
|
||||||
|
.field label, .form-row label { display: block; font-size: 12px; color: var(--text-2); margin-bottom: 4px; }
|
||||||
|
.form-error { color: var(--danger); font-size: 12px; margin-top: 8px; }
|
||||||
|
|
||||||
|
/* ---------- 统计卡与横向条 ---------- */
|
||||||
|
.stat-card { padding: 14px 16px; }
|
||||||
|
.stat-num { font-size: 22px; font-weight: 600; margin-top: 2px; letter-spacing: -0.2px; }
|
||||||
|
.bar-row { display: flex; align-items: center; gap: 10px; margin: 7px 0; }
|
||||||
|
.bar-label { flex: none; color: var(--text-2); font-size: 12px; text-align: right; }
|
||||||
|
.bar-track { flex: 1; height: 8px; border-radius: 4px; background: var(--bg-subtle); overflow: hidden; }
|
||||||
|
.bar-fill { display: block; height: 100%; background: var(--accent); border-radius: 4px; }
|
||||||
|
.bar-num { font-size: 12px; flex: none; color: var(--text-3); }
|
||||||
|
|
||||||
|
/* ---------- 图标 ---------- */
|
||||||
|
.icon { display: inline-flex; align-items: center; justify-content: center; flex: none; }
|
||||||
|
.icon svg { width: 100%; height: 100%; display: block; }
|
||||||
|
|
||||||
|
/* ---------- 移动端:避免 iOS 聚焦缩放 ---------- */
|
||||||
@media (max-width: 720px) {
|
@media (max-width: 720px) {
|
||||||
input, select, textarea { font-size: 16px !important; }
|
input, select, textarea { font-size: 16px !important; }
|
||||||
input[type="checkbox"] { font-size: initial; }
|
input[type="checkbox"] { font-size: initial; }
|
||||||
}
|
}
|
||||||
.dot.rest { background: var(--border); }
|
|
||||||
|
|
||||||
.icon { display: inline-flex; align-items: center; justify-content: center; flex: none; }
|
|
||||||
.icon svg { width: 100%; height: 100%; display: block; }
|
|
||||||
|
|||||||
@@ -87,18 +87,18 @@ watch(focusDate, d => {
|
|||||||
focusDate.value = null
|
focusDate.value = null
|
||||||
}, { immediate: true })
|
}, { immediate: true })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="cal-page">
|
<div class="cal-page">
|
||||||
<div class="cal-toolbar">
|
<div class="cal-toolbar">
|
||||||
<button class="icon-btn" @click="prevMonth"><Icon name="chevron-left" :size="16" /></button>
|
<button class="icon-btn" title="上一月" @click="prevMonth"><Icon name="chevron-left" :size="16" /></button>
|
||||||
<span class="cal-title num">{{ title }}</span>
|
<span class="cal-title num">{{ title }}</span>
|
||||||
<button class="icon-btn" @click="nextMonth"><Icon name="chevron-right" :size="16" /></button>
|
<button class="icon-btn" title="下一月" @click="nextMonth"><Icon name="chevron-right" :size="16" /></button>
|
||||||
<button class="btn btn-sm" @click="goToday">回到今天</button>
|
<button class="btn btn-sm" :disabled="monthRef === t" @click="goToday">回到本月</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="card cal-card">
|
||||||
<div class="cal-head">
|
<div class="cal-head">
|
||||||
<span v-for="w in WEEK_LABELS" :key="w" class="cal-week muted">{{ w }}</span>
|
<span v-for="w in WEEK_LABELS" :key="w" class="cal-week">{{ w }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="cal-grid">
|
<div class="cal-grid">
|
||||||
<button
|
<button
|
||||||
@@ -118,6 +118,7 @@ watch(focusDate, d => {
|
|||||||
</template>
|
</template>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<transition name="drawer">
|
<transition name="drawer">
|
||||||
<aside v-if="selected" class="drawer">
|
<aside v-if="selected" class="drawer">
|
||||||
@@ -132,30 +133,23 @@ watch(focusDate, d => {
|
|||||||
</transition>
|
</transition>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.cal-toolbar { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
|
.cal-toolbar { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
|
||||||
.cal-title { font-size: 14px; font-weight: 600; min-width: 96px; text-align: center; }
|
.cal-title { font-size: 14px; font-weight: 600; min-width: 96px; text-align: center; }
|
||||||
|
|
||||||
.cal-head {
|
.cal-card { padding: 0; overflow: hidden; }
|
||||||
|
.cal-head, .cal-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(7, 1fr);
|
grid-template-columns: repeat(7, 1fr);
|
||||||
border-top: 1px solid var(--border);
|
gap: 1px;
|
||||||
border-left: 1px solid var(--border);
|
background: var(--border);
|
||||||
}
|
}
|
||||||
.cal-week {
|
.cal-week {
|
||||||
|
background: var(--bg-subtle);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 6px 0;
|
padding: 7px 0;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
border-right: 1px solid var(--border);
|
color: var(--text-3);
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.cal-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(7, 1fr);
|
|
||||||
border-top: 1px solid var(--border);
|
|
||||||
border-left: 1px solid var(--border);
|
|
||||||
}
|
}
|
||||||
.cal-cell {
|
.cal-cell {
|
||||||
min-height: 92px;
|
min-height: 92px;
|
||||||
@@ -163,16 +157,14 @@ watch(focusDate, d => {
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
background: var(--bg-page);
|
background: var(--bg-page);
|
||||||
border: none;
|
border: none;
|
||||||
border-right: 1px solid var(--border);
|
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
transition: background 0.12s;
|
||||||
}
|
}
|
||||||
.cal-cell:hover { background: var(--bg-subtle); }
|
.cal-cell:hover { background: var(--bg-subtle); }
|
||||||
.cal-cell.blank { cursor: default; }
|
.cal-cell.blank { cursor: default; }
|
||||||
.cal-cell.blank:hover { background: var(--bg-page); }
|
.cal-cell.selected { background: var(--accent-bg); box-shadow: inset 0 0 0 2px var(--accent); }
|
||||||
.cal-cell.selected { background: var(--accent-bg); }
|
|
||||||
.daynum.today { color: var(--accent); font-weight: 600; }
|
.daynum.today { color: var(--accent); font-weight: 600; }
|
||||||
.cal-cell.weekend .daynum { color: var(--text-3); }
|
.cal-cell.weekend .daynum { color: var(--text-3); }
|
||||||
.cal-marks { display: flex; align-items: center; gap: 4px; margin-top: 8px; height: 14px; }
|
.cal-marks { display: flex; align-items: center; gap: 4px; margin-top: 8px; height: 14px; }
|
||||||
@@ -184,7 +176,7 @@ watch(focusDate, d => {
|
|||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: min(420px, 100%);
|
width: min(440px, 100%);
|
||||||
background: var(--bg-page);
|
background: var(--bg-page);
|
||||||
border-left: 1px solid var(--border);
|
border-left: 1px solid var(--border);
|
||||||
box-shadow: var(--shadow-float);
|
box-shadow: var(--shadow-float);
|
||||||
|
|||||||
@@ -214,7 +214,6 @@ async function confirmDelete() {
|
|||||||
showToast('账目已删除')
|
showToast('账目已删除')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="ledger-page">
|
<div class="ledger-page">
|
||||||
<div class="ledger-toolbar">
|
<div class="ledger-toolbar">
|
||||||
@@ -293,7 +292,7 @@ async function confirmDelete() {
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="muted vault-note num">本月 {{ rows.length }} 条 · 当前筛选 {{ filtered.length }} 条</div>
|
<div class="muted table-note num">本月 {{ rows.length }} 条 · 当前筛选 {{ filtered.length }} 条</div>
|
||||||
|
|
||||||
<TransactionDialog
|
<TransactionDialog
|
||||||
:open="dialogOpen"
|
:open="dialogOpen"
|
||||||
@@ -320,69 +319,20 @@ async function confirmDelete() {
|
|||||||
.w-filter { width: 110px; flex: none; }
|
.w-filter { width: 110px; flex: none; }
|
||||||
|
|
||||||
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; }
|
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; }
|
||||||
.stat-card { padding: 14px 16px; }
|
|
||||||
.stat-num { font-size: 20px; font-weight: 600; margin-top: 4px; }
|
|
||||||
.amt-income { color: var(--ok); }
|
|
||||||
.amt-expense { color: var(--text-1); }
|
|
||||||
.amt-warn { color: var(--warn); }
|
|
||||||
|
|
||||||
.sec { padding: 14px 16px; margin-top: 12px; }
|
.card.sec, .card.section { margin-top: 12px; }
|
||||||
.sec-title { font-size: 13px; font-weight: 600; margin-bottom: 10px; }
|
.bar-label { width: 56px; }
|
||||||
.sec-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; margin-bottom: 10px; }
|
.bar-num { width: 118px; }
|
||||||
.sec-head .sec-title { margin-bottom: 0; }
|
|
||||||
.form-hint { font-size: 12px; }
|
|
||||||
.form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 16px; }
|
|
||||||
.field.span2 { grid-column: span 2; }
|
|
||||||
.field label { display: block; font-size: 12px; color: var(--text-2); margin-bottom: 4px; }
|
|
||||||
.form-error { color: var(--danger); font-size: 12px; margin-top: 8px; }
|
|
||||||
.form-footer {
|
|
||||||
display: flex; justify-content: flex-end; gap: 8px;
|
|
||||||
margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border);
|
|
||||||
}
|
|
||||||
.seg { display: inline-flex; border: 1px solid var(--border-strong); border-radius: var(--radius-sm); overflow: hidden; }
|
|
||||||
.seg-btn {
|
|
||||||
height: 30px; padding: 0 14px; border: none;
|
|
||||||
background: var(--bg-page); color: var(--text-2); cursor: pointer; font-size: 13px;
|
|
||||||
}
|
|
||||||
.seg-btn + .seg-btn { border-left: 1px solid var(--border); }
|
|
||||||
.seg-btn.active { background: var(--accent-bg); color: var(--accent); }
|
|
||||||
.toggle-btn {
|
|
||||||
height: 32px; width: 100%;
|
|
||||||
border: 1px solid var(--border-strong);
|
|
||||||
background: var(--bg-page);
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
cursor: pointer; font-size: 13px; color: var(--text-2);
|
|
||||||
}
|
|
||||||
.toggle-btn:hover { background: var(--bg-hover); }
|
|
||||||
.toggle-btn.on { color: var(--accent); border-color: var(--accent); background: var(--accent-bg); }
|
|
||||||
|
|
||||||
.bar-row { display: flex; align-items: center; gap: 10px; margin: 6px 0; }
|
|
||||||
.bar-label { width: 56px; flex: none; color: var(--text-2); font-size: 12px; text-align: right; }
|
|
||||||
.bar-track { flex: 1; height: 8px; border-radius: 4px; background: var(--bg-subtle); overflow: hidden; }
|
|
||||||
.bar-fill { display: block; height: 100%; background: var(--accent); border-radius: 4px; }
|
|
||||||
.bar-num { font-size: 12px; width: 110px; flex: none; }
|
|
||||||
|
|
||||||
.table-card { overflow-x: auto; }
|
|
||||||
.tx-table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
|
||||||
.tx-table th {
|
|
||||||
text-align: left; font-weight: 500; color: var(--text-2);
|
|
||||||
background: var(--bg-subtle); padding: 10px 12px;
|
|
||||||
border-bottom: 1px solid var(--border); white-space: nowrap;
|
|
||||||
}
|
|
||||||
.tx-table td { padding: 10px 12px; border-bottom: 1px solid var(--border); vertical-align: top; word-break: break-all; }
|
|
||||||
.tx-table tbody tr:hover { background: var(--bg-subtle); }
|
|
||||||
.tx-table tbody tr:last-child td { border-bottom: none; }
|
|
||||||
.td-note { color: var(--text-2); }
|
.td-note { color: var(--text-2); }
|
||||||
.tx-pending { color: var(--warn); font-size: 12px; }
|
.tx-pending { color: var(--warn); font-size: 12px; }
|
||||||
.tx-done { color: var(--ok); font-size: 12px; }
|
.tx-done { color: var(--ok); font-size: 12px; }
|
||||||
.th-ops { width: 220px; }
|
.th-ops { width: 220px; }
|
||||||
.ops { display: flex; gap: 4px; flex-wrap: wrap; }
|
.ops { display: flex; gap: 4px; flex-wrap: wrap; }
|
||||||
.empty { text-align: center; padding: 24px 0; }
|
.table-card { overflow-x: auto; }
|
||||||
.vault-note { margin-top: 10px; font-size: 12px; text-align: right; }
|
.table-note { margin-top: 10px; font-size: 12px; text-align: right; }
|
||||||
|
|
||||||
@media (max-width: 560px) {
|
@media (max-width: 560px) {
|
||||||
.form-grid { grid-template-columns: 1fr; }
|
|
||||||
.field.span2 { grid-column: auto; }
|
|
||||||
.ledger-toolbar .w-filter { flex: 1; min-width: 0; width: auto; }
|
.ledger-toolbar .w-filter { flex: 1; min-width: 0; width: auto; }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -77,7 +77,8 @@ async function submit() {
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
.login-page {
|
.login-page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: var(--bg-subtle);
|
min-height: 100dvh;
|
||||||
|
background: var(--bg-app);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -86,20 +87,21 @@ async function submit() {
|
|||||||
.login-card {
|
.login-card {
|
||||||
width: 400px;
|
width: 400px;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
padding: 28px 26px;
|
padding: 30px 28px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
background: var(--bg-page);
|
box-shadow: var(--shadow-float);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
}
|
}
|
||||||
.login-brand { text-align: center; margin-bottom: 6px; }
|
.login-brand { text-align: center; margin-bottom: 6px; }
|
||||||
.brand-icon {
|
.brand-icon {
|
||||||
width: 46px;
|
width: 46px;
|
||||||
height: 46px;
|
height: 46px;
|
||||||
margin: 0 auto 10px;
|
margin: 0 auto 10px;
|
||||||
border-radius: var(--radius-md);
|
border-radius: 12px;
|
||||||
background: var(--accent-bg);
|
background: var(--accent);
|
||||||
color: var(--accent);
|
color: #FFFFFF;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -107,31 +109,7 @@ async function submit() {
|
|||||||
.login-title { font-size: 18px; font-weight: 600; color: var(--text-1); }
|
.login-title { font-size: 18px; font-weight: 600; color: var(--text-1); }
|
||||||
.login-sub { font-size: 12px; margin-top: 4px; line-height: 1.7; }
|
.login-sub { font-size: 12px; margin-top: 4px; line-height: 1.7; }
|
||||||
|
|
||||||
.seg { display: inline-flex; border: 1px solid var(--border-strong); border-radius: var(--radius-sm); overflow: hidden; }
|
|
||||||
.seg-btn {
|
|
||||||
flex: 1;
|
|
||||||
height: 30px;
|
|
||||||
border: none;
|
|
||||||
background: var(--bg-page);
|
|
||||||
color: var(--text-2);
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
.seg-btn + .seg-btn { border-left: 1px solid var(--border); }
|
|
||||||
.seg-btn.active { background: var(--accent-bg); color: var(--accent); }
|
|
||||||
|
|
||||||
.mig-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 8px;
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--text-2);
|
|
||||||
cursor: pointer;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
.mig-row input { margin-top: 3px; }
|
|
||||||
|
|
||||||
.form-error { color: var(--danger); font-size: 12px; }
|
.form-error { color: var(--danger); font-size: 12px; }
|
||||||
.login-btn { margin-top: 2px; }
|
.login-btn { margin-top: 2px; height: 36px; }
|
||||||
.login-note { font-size: 12px; text-align: center; line-height: 1.7; }
|
.login-note { font-size: 12px; text-align: center; line-height: 1.7; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -197,13 +197,13 @@ function fmtTime(s: string): string {
|
|||||||
.memo-list {
|
.memo-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
max-height: calc(100vh - 140px);
|
max-height: calc(100vh - 150px);
|
||||||
}
|
}
|
||||||
.pane-head {
|
.pane-head {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 12px 12px 8px;
|
padding: 12px 12px 10px;
|
||||||
border-bottom: 1px solid var(--border);
|
border-bottom: 1px solid var(--border);
|
||||||
flex: none;
|
flex: none;
|
||||||
}
|
}
|
||||||
@@ -223,12 +223,12 @@ function fmtTime(s: string): string {
|
|||||||
.memo-item-time { font-size: 11px; margin-top: 2px; }
|
.memo-item-time { font-size: 11px; margin-top: 2px; }
|
||||||
|
|
||||||
.memo-editor {
|
.memo-editor {
|
||||||
padding: 16px;
|
padding: 16px 18px;
|
||||||
min-height: 420px;
|
min-height: 420px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
.title-input { font-size: 14px; font-weight: 500; margin-bottom: 10px; }
|
.title-input { font-size: 15px; font-weight: 600; margin-bottom: 10px; }
|
||||||
.content-textarea { flex: 1; min-height: 340px; line-height: 1.8; }
|
.content-textarea { flex: 1; min-height: 340px; line-height: 1.8; }
|
||||||
.editor-foot {
|
.editor-foot {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -108,12 +108,13 @@ const summary = computed(() => {
|
|||||||
const done = items.value.filter(p => p.status === 'done').length
|
const done = items.value.filter(p => p.status === 'done').length
|
||||||
const plannedMin = items.value.reduce((acc, p) => acc + spanMinutes(p), 0)
|
const plannedMin = items.value.reduce((acc, p) => acc + spanMinutes(p), 0)
|
||||||
const actualMin = items.value.reduce((acc, p) => acc + (p.actual_minutes || 0), 0)
|
const actualMin = items.value.reduce((acc, p) => acc + (p.actual_minutes || 0), 0)
|
||||||
|
let liveExtra = 0
|
||||||
const runningId = running.value
|
const runningId = running.value
|
||||||
if (runningId) {
|
if (runningId) {
|
||||||
const rp = items.value.find(p => p.id === runningId)
|
const rp = items.value.find(p => p.id === runningId)
|
||||||
if (rp) actualMin + elapsedMinutes(rp)
|
if (rp) liveExtra = liveElapsed(rp)
|
||||||
}
|
}
|
||||||
return { total, done, plannedMin, actualMin }
|
return { total, done, plannedMin, actualMin: actualMin + liveExtra }
|
||||||
})
|
})
|
||||||
function spanMinutes(p: PlanItem): number {
|
function spanMinutes(p: PlanItem): number {
|
||||||
const [sh, sm] = p.start_time.split(':').map(Number)
|
const [sh, sm] = p.start_time.split(':').map(Number)
|
||||||
@@ -211,34 +212,20 @@ function weekday(s: string): string {
|
|||||||
return wdNames[parseDateStr(s).getDay()]
|
return wdNames[parseDateStr(s).getDay()]
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="plan-page">
|
<div class="plan-page">
|
||||||
<div class="card sec plan-head">
|
<!-- 长期计划 -->
|
||||||
<div class="nav-row">
|
<div class="blk-head">
|
||||||
<button class="icon-btn" @click="prevDay"><Icon name="chevron-left" :size="16" /></button>
|
<div class="blk-head-text">
|
||||||
<div class="date-box">
|
<div class="blk-title">长期计划</div>
|
||||||
<span class="date-main num">{{ date.slice(5) }} {{ weekday(date) }}</span>
|
<div class="muted blk-sub">跨越多天的目标,自动落实到每一天</div>
|
||||||
<span class="muted date-sub num">{{ date }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
<button class="icon-btn" @click="nextDay"><Icon name="chevron-right" :size="16" /></button>
|
|
||||||
<button class="btn btn-sm" :class="{ 'btn-primary': !isToday }" @click="goToday">今天</button>
|
|
||||||
<span class="flex1"></span>
|
|
||||||
<button class="btn btn-primary btn-sm" @click="openCreate"><Icon name="plus" :size="14" /> 添加计划</button>
|
|
||||||
</div>
|
|
||||||
<div class="muted sum-line">
|
|
||||||
共 {{ summary.total }} 项 · 完成 {{ summary.done }} 项 · 计划 {{ fmtMin(summary.plannedMin) }} · 实际投入 {{ fmtMin(summary.actualMin) }}
|
|
||||||
<template v-if="running"> · <span class="timer-live">计时中</span></template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="goals-sec">
|
|
||||||
<div class="goals-head">
|
|
||||||
<span class="goals-title">长期计划</span>
|
|
||||||
<button class="btn btn-sm" @click="openGoalCreate"><Icon name="plus" :size="14" /> 新建长期计划</button>
|
<button class="btn btn-sm" @click="openGoalCreate"><Icon name="plus" :size="14" /> 新建长期计划</button>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!goals.length" class="muted goals-empty">暂无长期计划。可以创建一个跨越多天的目标(如「产品文档学习」),系统会自动生成每天的打卡任务。</div>
|
<div v-if="!goals.length" class="card empty muted">
|
||||||
<div v-for="g in goals" :key="g.id" class="card sec goal-card">
|
暂无长期计划。创建一个跨越多天的目标(如「产品文档学习」),系统会自动生成每天的打卡任务。
|
||||||
|
</div>
|
||||||
|
<div v-for="g in goals" :key="g.id" class="card goal-card">
|
||||||
<div class="goal-main">
|
<div class="goal-main">
|
||||||
<div class="goal-line">
|
<div class="goal-line">
|
||||||
<span class="tag">{{ g.kind }}</span>
|
<span class="tag">{{ g.kind }}</span>
|
||||||
@@ -246,7 +233,10 @@ function weekday(s: string): string {
|
|||||||
<span class="muted num goal-range">{{ g.start_date.slice(5) }} ~ {{ g.end_date.slice(5) }}</span>
|
<span class="muted num goal-range">{{ g.start_date.slice(5) }} ~ {{ g.end_date.slice(5) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="goal-bar">
|
<div class="goal-bar">
|
||||||
<span class="goal-bar-fill" :style="{ width: (goalProg[String(g.id)] ? Math.round(goalProg[String(g.id)].done / Math.max(1, goalProg[String(g.id)].total) * 100) : 0) + '%' }"></span>
|
<span
|
||||||
|
class="goal-bar-fill"
|
||||||
|
:style="{ width: (goalProg[String(g.id)] ? Math.round(goalProg[String(g.id)].done / Math.max(1, goalProg[String(g.id)].total) * 100) : 0) + '%' }"
|
||||||
|
></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="muted num goal-meta">
|
<div class="muted num goal-meta">
|
||||||
{{ goalProg[String(g.id)] ? goalProg[String(g.id)].done + '/' + goalProg[String(g.id)].total + ' 天已完成' : '0/' + (goalProg[String(g.id)] ? goalProg[String(g.id)].total : 0) + ' 天' }}
|
{{ goalProg[String(g.id)] ? goalProg[String(g.id)].done + '/' + goalProg[String(g.id)].total + ' 天已完成' : '0/' + (goalProg[String(g.id)] ? goalProg[String(g.id)].total : 0) + ' 天' }}
|
||||||
@@ -258,15 +248,29 @@ function weekday(s: string): string {
|
|||||||
<button class="icon-btn" title="删除" @click="delGoal = g"><Icon name="close" :size="14" /></button>
|
<button class="icon-btn" title="删除" @click="delGoal = g"><Icon name="close" :size="14" /></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="plan-list">
|
<!-- 每日安排 -->
|
||||||
<div v-if="!loading && !items.length" class="card sec empty muted">
|
<div class="blk-head day-head">
|
||||||
今天还没有计划,点右上角「添加计划」开始规划:学习、健身、自律……
|
<div class="blk-head-text">
|
||||||
|
<div class="blk-title">每日安排</div>
|
||||||
|
<div class="muted blk-sub num">
|
||||||
|
共 {{ summary.total }} 项 · 完成 {{ summary.done }} 项 · 计划 {{ fmtMin(summary.plannedMin) }} · 实际投入 {{ fmtMin(summary.actualMin) }}
|
||||||
|
<template v-if="running"> · <span class="timer-live">计时中</span></template>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="day-ops">
|
||||||
|
<button class="icon-btn" title="前一天" @click="prevDay"><Icon name="chevron-left" :size="16" /></button>
|
||||||
|
<span class="date-chip num" :class="{ today: isToday }">{{ date.slice(5) }} {{ weekday(date) }}</span>
|
||||||
|
<button class="icon-btn" title="后一天" @click="nextDay"><Icon name="chevron-right" :size="16" /></button>
|
||||||
|
<button class="btn btn-sm" :class="{ 'btn-primary': !isToday }" @click="goToday">今天</button>
|
||||||
|
<button class="btn btn-primary btn-sm" @click="openCreate"><Icon name="plus" :size="14" /> 添加计划</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card list-card">
|
||||||
|
<div v-if="!loading && !items.length" class="empty muted">今天还没有计划,点「添加计划」开始规划。</div>
|
||||||
<div
|
<div
|
||||||
v-for="p in items" :key="p.id"
|
v-for="p in items" :key="p.id"
|
||||||
class="card sec plan-item"
|
class="plan-item"
|
||||||
:class="{ done: p.status === 'done', skip: p.status === 'skip', running: !!p.timer_started_at }"
|
:class="{ done: p.status === 'done', skip: p.status === 'skip', running: !!p.timer_started_at }"
|
||||||
>
|
>
|
||||||
<div class="pi-time num">
|
<div class="pi-time num">
|
||||||
@@ -304,7 +308,7 @@ function weekday(s: string): string {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<teleport to="body">
|
<teleport to="body">
|
||||||
<div v-if="dialogOpen" class="fixed-mask" @click.self="dialogOpen = false">
|
<div v-if="dialogOpen" class="mask" @click.self="dialogOpen = false">
|
||||||
<div class="dialog" role="dialog" aria-label="计划">
|
<div class="dialog" role="dialog" aria-label="计划">
|
||||||
<div class="dialog-head">
|
<div class="dialog-head">
|
||||||
<span class="dialog-title">{{ editingId ? '编辑计划' : '添加计划' }}</span>
|
<span class="dialog-title">{{ editingId ? '编辑计划' : '添加计划' }}</span>
|
||||||
@@ -345,7 +349,7 @@ function weekday(s: string): string {
|
|||||||
</div>
|
</div>
|
||||||
</teleport>
|
</teleport>
|
||||||
<teleport to="body">
|
<teleport to="body">
|
||||||
<div v-if="goalOpen" class="fixed-mask" @click.self="goalOpen = false">
|
<div v-if="goalOpen" class="mask" @click.self="goalOpen = false">
|
||||||
<div class="dialog" role="dialog" aria-label="长期计划">
|
<div class="dialog" role="dialog" aria-label="长期计划">
|
||||||
<div class="dialog-head">
|
<div class="dialog-head">
|
||||||
<span class="dialog-title">{{ goalEditing ? '编辑长期计划' : '新建长期计划' }}</span>
|
<span class="dialog-title">{{ goalEditing ? '编辑长期计划' : '新建长期计划' }}</span>
|
||||||
@@ -397,52 +401,65 @@ function weekday(s: string): string {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.plan-page { display: flex; flex-direction: column; gap: 10px; max-width: 760px; }
|
.plan-page { display: flex; flex-direction: column; }
|
||||||
.plan-head { padding: 12px 16px; }
|
|
||||||
.nav-row { display: flex; align-items: center; gap: 8px; }
|
|
||||||
.date-box { min-width: 150px; text-align: center; }
|
|
||||||
.date-main { font-weight: 600; font-size: 14px; }
|
|
||||||
.date-sub { font-size: 11px; margin-left: 6px; }
|
|
||||||
.sum-line { font-size: 12px; margin-top: 8px; }
|
|
||||||
.timer-live { color: var(--accent); font-weight: 600; }
|
|
||||||
.flex1 { flex: 1; }
|
|
||||||
|
|
||||||
.plan-item { display: flex; align-items: flex-start; gap: 12px; padding: 12px 14px; }
|
.blk-head {
|
||||||
.plan-item.done { opacity: 0.62; }
|
display: flex; align-items: flex-start; justify-content: space-between;
|
||||||
.plan-item.skip { opacity: 0.5; }
|
gap: 12px; flex-wrap: wrap;
|
||||||
.plan-item.running { border-left: 3px solid var(--accent); }
|
margin: 4px 2px 10px;
|
||||||
|
}
|
||||||
|
.blk-head-text { min-width: 0; }
|
||||||
|
.blk-title { font-size: 14px; font-weight: 600; }
|
||||||
|
.blk-sub { font-size: 12px; margin-top: 1px; }
|
||||||
|
.day-head { margin-top: 18px; }
|
||||||
|
.day-ops { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
|
||||||
|
.date-chip { min-width: 74px; text-align: center; font-weight: 600; font-size: 13px; }
|
||||||
|
.date-chip.today { color: var(--accent); }
|
||||||
|
.timer-live { color: var(--accent); font-weight: 600; }
|
||||||
|
|
||||||
|
.goal-card { display: flex; align-items: flex-start; gap: 12px; padding: 14px 16px; margin-bottom: 10px; }
|
||||||
|
.goal-main { flex: 1; min-width: 0; }
|
||||||
|
.goal-line { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
||||||
|
.goal-name { font-size: 14px; font-weight: 500; word-break: break-all; }
|
||||||
|
.goal-range { font-size: 12px; }
|
||||||
|
.goal-bar { height: 6px; border-radius: 3px; background: var(--bg-subtle); overflow: hidden; margin-top: 10px; }
|
||||||
|
.goal-bar-fill { display: block; height: 100%; background: var(--accent); border-radius: 3px; }
|
||||||
|
.goal-meta { font-size: 12px; margin-top: 6px; }
|
||||||
|
.goal-ops { display: flex; gap: 4px; }
|
||||||
|
|
||||||
|
.list-card { overflow: hidden; }
|
||||||
|
.plan-item {
|
||||||
|
display: flex; align-items: flex-start; gap: 12px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
.plan-item:last-child { border-bottom: none; }
|
||||||
|
.plan-item.done { opacity: 0.6; }
|
||||||
|
.plan-item.skip { opacity: 0.48; }
|
||||||
|
.plan-item.running { box-shadow: inset 3px 0 0 var(--accent); }
|
||||||
.pi-time { font-size: 13px; font-weight: 600; min-width: 92px; padding-top: 2px; }
|
.pi-time { font-size: 13px; font-weight: 600; min-width: 92px; padding-top: 2px; }
|
||||||
.pi-main { flex: 1; min-width: 0; }
|
.pi-main { flex: 1; min-width: 0; }
|
||||||
.pi-title { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
.pi-title { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
||||||
.pi-text { font-size: 14px; font-weight: 500; word-break: break-all; }
|
.pi-text { font-size: 14px; font-weight: 500; word-break: break-all; }
|
||||||
.pi-text.strike { text-decoration: line-through; }
|
.pi-text.strike { text-decoration: line-through; }
|
||||||
.state-done { color: var(--ok, #2e9e5b); font-size: 12px; }
|
.state-done { color: var(--ok); font-size: 12px; }
|
||||||
.state-skip { color: var(--text-3); font-size: 12px; }
|
.state-skip { color: var(--text-3); font-size: 12px; }
|
||||||
.pi-note { font-size: 12px; margin-top: 3px; }
|
.pi-note { font-size: 12px; margin-top: 3px; }
|
||||||
.pi-meta { font-size: 12px; margin-top: 3px; }
|
.pi-meta { font-size: 12px; margin-top: 3px; }
|
||||||
.pi-ops { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; justify-content: flex-end; }
|
.pi-ops { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; justify-content: flex-end; }
|
||||||
.timer-on { color: var(--accent); border-color: var(--accent); }
|
.timer-on { color: var(--accent); border-color: var(--accent); }
|
||||||
.empty { text-align: center; padding: 26px 16px; }
|
.empty { padding: 28px 16px; }
|
||||||
|
|
||||||
.fixed-mask {
|
.dialog { width: 420px; }
|
||||||
position: fixed; inset: 0; z-index: 80;
|
|
||||||
background: rgba(29, 33, 41, 0.35);
|
|
||||||
display: flex; align-items: center; justify-content: center; padding: 16px;
|
|
||||||
}
|
|
||||||
.dialog { background: var(--bg-page); border-radius: 12px; width: 420px; max-width: 100%; }
|
|
||||||
.dialog-head { display: flex; align-items: center; justify-content: space-between; padding: 14px 16px 0; }
|
|
||||||
.dialog-title { font-weight: 600; font-size: 14px; }
|
|
||||||
.dialog-body { padding: 6px 16px 4px; }
|
|
||||||
.dialog-btns { display: flex; justify-content: flex-end; gap: 10px; padding: 12px 16px 16px; }
|
|
||||||
.form-row { display: flex; flex-direction: column; gap: 5px; margin-top: 10px; }
|
.form-row { display: flex; flex-direction: column; gap: 5px; margin-top: 10px; }
|
||||||
.form-row label { font-size: 12px; color: var(--text-2); }
|
|
||||||
.form-row.two { flex-direction: row; gap: 10px; }
|
.form-row.two { flex-direction: row; gap: 10px; }
|
||||||
.form-row.two > div { flex: 1; display: flex; flex-direction: column; gap: 5px; }
|
.form-row.two > div { flex: 1; display: flex; flex-direction: column; gap: 5px; }
|
||||||
|
.goal-hint { font-size: 12px; margin-top: 8px; }
|
||||||
|
|
||||||
@media (max-width: 720px) {
|
@media (max-width: 720px) {
|
||||||
.plan-item { flex-wrap: wrap; }
|
.plan-item { flex-wrap: wrap; }
|
||||||
.pi-ops { width: 100%; justify-content: flex-start; }
|
.pi-ops { width: 100%; justify-content: flex-start; }
|
||||||
|
.day-ops { width: 100%; }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -210,21 +210,10 @@ function undoPolish() {
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
.report-toolbar { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; flex-wrap: wrap; }
|
.report-toolbar { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; flex-wrap: wrap; }
|
||||||
.ai-error { color: var(--danger); font-size: 12px; margin: 0 0 8px; }
|
.ai-error { color: var(--danger); font-size: 12px; margin: 0 0 8px; }
|
||||||
.seg { display: inline-flex; border: 1px solid var(--border-strong); border-radius: 6px; overflow: hidden; }
|
.w-date { width: 140px; flex: none; }
|
||||||
.seg-btn {
|
|
||||||
height: 28px; padding: 0 12px; border: none;
|
|
||||||
background: var(--bg-page); color: var(--text-2);
|
|
||||||
cursor: pointer; font-size: 12px;
|
|
||||||
border-right: 1px solid var(--border);
|
|
||||||
}
|
|
||||||
.seg-btn:last-child { border-right: none; }
|
|
||||||
.seg-btn:hover { background: var(--bg-hover); }
|
|
||||||
.seg-btn.active { background: var(--accent-bg); color: var(--accent); }
|
|
||||||
.w-date { width: 140px; }
|
|
||||||
|
|
||||||
.report-grid { display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: 12px; align-items: start; }
|
.report-grid { display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: 12px; align-items: start; }
|
||||||
.section { padding: 14px 16px; }
|
.section-title { margin-bottom: 10px; }
|
||||||
.section-title { font-weight: 600; margin-bottom: 8px; font-size: 13px; }
|
|
||||||
.section + .section { margin-top: 12px; }
|
.section + .section { margin-top: 12px; }
|
||||||
|
|
||||||
.note-label { display: block; font-size: 12px; margin: 8px 0 4px; }
|
.note-label { display: block; font-size: 12px; margin: 8px 0 4px; }
|
||||||
@@ -232,7 +221,7 @@ function undoPolish() {
|
|||||||
.md-editor { min-height: 420px; line-height: 1.7; }
|
.md-editor { min-height: 420px; line-height: 1.7; }
|
||||||
|
|
||||||
.sum-row { display: flex; justify-content: space-between; padding: 3px 0; font-size: 12px; }
|
.sum-row { display: flex; justify-content: space-between; padding: 3px 0; font-size: 12px; }
|
||||||
.section-sub { font-weight: 600; margin: 10px 0 4px; font-size: 12px; }
|
.section-sub { font-weight: 600; margin: 12px 0 4px; font-size: 12px; color: var(--text-2); }
|
||||||
.sum-item { font-size: 12px; padding: 2px 0; word-break: break-all; }
|
.sum-item { font-size: 12px; padding: 2px 0; word-break: break-all; }
|
||||||
.sum-empty { font-size: 12px; }
|
.sum-empty { font-size: 12px; }
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
listAttendanceBetween, listLogItemsBetween, listProjects,
|
listAttendanceBetween, listLogItemsBetween, listProjects,
|
||||||
renameProject, saveLocationPresets, setProjectArchived, setSetting, type WorkRule
|
renameProject, saveLocationPresets, setProjectArchived, setSetting, type WorkRule
|
||||||
} from '../db/repo'
|
} from '../db/repo'
|
||||||
import { type Project } from '../db/schema'
|
import { categoryLabel, type Project } from '../db/schema'
|
||||||
import { getLogCategories, setLogCategories, getLedgerCategories, setLedgerCategories } from '../db/categories'
|
import { getLogCategories, setLogCategories, getLedgerCategories, setLedgerCategories } from '../db/categories'
|
||||||
import { downloadText, } from '../lib/download'
|
import { downloadText, } from '../lib/download'
|
||||||
import { downloadCsv } from '../lib/csv'
|
import { downloadCsv } from '../lib/csv'
|
||||||
@@ -494,18 +494,16 @@ async function doImport() {
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
.settings { display: flex; flex-direction: column; gap: 12px; max-width: 640px; }
|
.settings { display: flex; flex-direction: column; gap: 12px; max-width: 640px; }
|
||||||
.acc-line { font-size: 12px; margin-bottom: 10px; line-height: 1.7; }
|
.acc-line { font-size: 12px; margin-bottom: 10px; line-height: 1.7; }
|
||||||
.section { padding: 14px 16px; }
|
|
||||||
.section-title { font-weight: 600; margin-bottom: 10px; font-size: 13px; }
|
|
||||||
|
|
||||||
.rule-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 16px; }
|
.rule-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 16px; }
|
||||||
.rule-item label { display: block; font-size: 12px; color: var(--text-2); margin-bottom: 4px; }
|
.rule-item label { display: block; font-size: 12px; color: var(--text-2); margin-bottom: 4px; }
|
||||||
.rule-save { display: flex; align-items: center; gap: 10px; margin-top: 12px; }
|
.rule-save { display: flex; align-items: center; gap: 10px; margin-top: 12px; flex-wrap: wrap; }
|
||||||
|
|
||||||
.proj-add { display: flex; gap: 8px; }
|
.proj-add { display: flex; gap: 8px; }
|
||||||
.proj-tip { font-size: 12px; margin-top: 6px; }
|
.proj-tip { font-size: 12px; margin-top: 6px; }
|
||||||
.proj-row {
|
.proj-row {
|
||||||
display: flex; align-items: center; gap: 8px;
|
display: flex; align-items: center; gap: 8px;
|
||||||
padding: 6px 0; border-bottom: 1px solid var(--border);
|
padding: 7px 0; border-bottom: 1px solid var(--border);
|
||||||
}
|
}
|
||||||
.proj-row:last-child { border-bottom: none; }
|
.proj-row:last-child { border-bottom: none; }
|
||||||
.proj-name { flex: 1; word-break: break-all; }
|
.proj-name { flex: 1; word-break: break-all; }
|
||||||
@@ -513,22 +511,19 @@ async function doImport() {
|
|||||||
|
|
||||||
.loc-note { font-size: 12px; }
|
.loc-note { font-size: 12px; }
|
||||||
.ai-grid { display: flex; flex-direction: column; gap: 10px; }
|
.ai-grid { display: flex; flex-direction: column; gap: 10px; }
|
||||||
.backup-row { display: flex; gap: 8px; }
|
.backup-row { display: flex; gap: 8px; flex-wrap: wrap; }
|
||||||
.cat-block { margin-top: 10px; }
|
.cat-block { margin-top: 12px; }
|
||||||
|
.cat-block:first-of-type { margin-top: 0; }
|
||||||
.cat-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; font-size: 12px; }
|
.cat-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; font-size: 12px; }
|
||||||
.cat-chips { display: flex; flex-wrap: wrap; gap: 6px; }
|
.cat-chips { display: flex; flex-wrap: wrap; gap: 6px; }
|
||||||
.cat-chip {
|
.cat-chip {
|
||||||
display: inline-flex; align-items: center; gap: 4px;
|
display: inline-flex; align-items: center; gap: 4px;
|
||||||
border: 1px solid var(--border); border-radius: 999px;
|
border: 1px solid var(--border); border-radius: 999px;
|
||||||
padding: 2px 8px; font-size: 12px;
|
padding: 2px 8px; font-size: 12px;
|
||||||
|
background: var(--bg-subtle);
|
||||||
}
|
}
|
||||||
.chip-btn { border: none; background: transparent; color: var(--text-3); cursor: pointer; font-size: 11px; padding: 0 1px; }
|
.chip-btn { border: none; background: transparent; color: var(--text-3); cursor: pointer; font-size: 11px; padding: 0 1px; }
|
||||||
.chip-btn:hover { color: var(--accent); }
|
.chip-btn:hover { color: var(--accent); }
|
||||||
.exp-dates { display: flex; align-items: center; gap: 8px; margin-top: 10px; flex-wrap: wrap; }
|
|
||||||
.exp-dates .input { width: 150px; }
|
|
||||||
.seg { display: inline-flex; border: 1px solid var(--border-strong); border-radius: var(--radius-sm); overflow: hidden; }
|
|
||||||
.seg-btn { border: none; background: var(--bg-page); color: var(--text-2); cursor: pointer; font-size: 12px; padding: 5px 12px; }
|
|
||||||
.seg-btn.active { background: var(--accent-bg); color: var(--accent); }
|
|
||||||
.import-error { color: var(--danger); font-size: 12px; margin-top: 8px; }
|
.import-error { color: var(--danger); font-size: 12px; margin-top: 8px; }
|
||||||
.backup-note { font-size: 12px; margin-top: 10px; }
|
.backup-note { font-size: 12px; margin-top: 10px; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -180,32 +180,28 @@ function heatTitle(date: string, count: number): string {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.cards { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }
|
.cards { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }
|
||||||
.stat-card { padding: 14px 16px; }
|
|
||||||
.stat-num { font-size: 20px; font-weight: 600; margin-top: 4px; }
|
|
||||||
|
|
||||||
.section { padding: 14px 16px; margin-top: 12px; }
|
.section { margin-top: 12px; }
|
||||||
.section-title { font-weight: 600; margin-bottom: 10px; font-size: 13px; }
|
|
||||||
|
|
||||||
.heat { display: flex; gap: 3px; overflow-x: auto; padding-bottom: 4px; }
|
.heat { display: flex; gap: 3px; overflow-x: auto; padding-bottom: 4px; }
|
||||||
.heat-col { display: flex; flex-direction: column; gap: 3px; }
|
.heat-col { display: flex; flex-direction: column; gap: 3px; }
|
||||||
.heat-cell { width: 12px; height: 12px; border-radius: 2px; display: inline-block; }
|
.heat-cell { width: 12px; height: 12px; border-radius: 3px; display: inline-block; }
|
||||||
.lv0 { background: var(--bg-subtle); border: 1px solid var(--border); }
|
.lv0 { background: var(--bg-subtle); border: 1px solid var(--border); }
|
||||||
.lv1 { background: #D8ECDF; }
|
.lv1 { background: #E4EEFF; }
|
||||||
.lv2 { background: #A9D8BC; }
|
.lv2 { background: #BFD6FF; }
|
||||||
.lv3 { background: #6FBF92; }
|
.lv3 { background: #7FA8F5; }
|
||||||
.lv4 { background: var(--ok); }
|
.lv4 { background: var(--accent); }
|
||||||
.heat-legend { display: flex; align-items: center; gap: 3px; margin-top: 8px; font-size: 12px; }
|
.heat-legend { display: flex; align-items: center; gap: 3px; margin-top: 10px; font-size: 12px; }
|
||||||
|
|
||||||
.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
||||||
.bar-row { display: flex; align-items: center; gap: 10px; margin: 6px 0; }
|
.two-col .section { margin-top: 0; }
|
||||||
.bar-label { width: 72px; flex: none; color: var(--text-2); font-size: 12px; text-align: right; }
|
.bar-label { width: 72px; }
|
||||||
.bar-track { flex: 1; height: 8px; border-radius: 4px; background: var(--bg-subtle); overflow: hidden; }
|
.bar-num { width: 92px; }
|
||||||
.bar-fill { display: block; height: 100%; background: var(--accent); border-radius: 4px; }
|
|
||||||
.bar-num { font-size: 12px; width: 92px; flex: none; }
|
|
||||||
.loc-label { width: 110px; text-align: left; }
|
.loc-label { width: 110px; text-align: left; }
|
||||||
|
|
||||||
@media (max-width: 720px) {
|
@media (max-width: 720px) {
|
||||||
.cards { grid-template-columns: repeat(2, 1fr); }
|
.cards { grid-template-columns: repeat(2, 1fr); }
|
||||||
.two-col { grid-template-columns: 1fr; }
|
.two-col { grid-template-columns: 1fr; }
|
||||||
|
.two-col .section { margin-top: 12px; }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -88,18 +88,19 @@ function onDateInput(e: Event) {
|
|||||||
if (v) date.value = v
|
if (v) date.value = v
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="detail-page">
|
<div class="detail-page">
|
||||||
<div class="detail-toolbar">
|
<div class="card toolbar">
|
||||||
|
<div class="nav-row">
|
||||||
<button class="icon-btn" title="前一天" @click="prevDay"><Icon name="chevron-left" :size="16" /></button>
|
<button class="icon-btn" title="前一天" @click="prevDay"><Icon name="chevron-left" :size="16" /></button>
|
||||||
<span class="date-title num" :class="{ 'is-today': isToday }">{{ formatDateCN(date) }}</span>
|
<span class="date-title num" :class="{ 'is-today': isToday }">{{ formatDateCN(date) }}</span>
|
||||||
<button class="icon-btn" title="后一天" @click="nextDay"><Icon name="chevron-right" :size="16" /></button>
|
<button class="icon-btn" title="后一天" @click="nextDay"><Icon name="chevron-right" :size="16" /></button>
|
||||||
<input type="date" class="input w-date" :value="date" @change="onDateInput" />
|
<input type="date" class="input w-date" :value="date" @change="onDateInput" />
|
||||||
<button class="btn btn-sm" :disabled="isToday" @click="goToday">回到今天</button>
|
<button class="btn btn-sm" :disabled="isToday" @click="goToday">回到今天</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="export-row">
|
<div class="tool-sep"></div>
|
||||||
<span class="muted exp-label">导出工作详情</span>
|
<div class="exp-row">
|
||||||
|
<span class="muted exp-label">导出</span>
|
||||||
<div class="seg">
|
<div class="seg">
|
||||||
<button class="seg-btn" :class="{ active: expRange === 'day' }" @click="expRange = 'day'">当日</button>
|
<button class="seg-btn" :class="{ active: expRange === 'day' }" @click="expRange = 'day'">当日</button>
|
||||||
<button class="seg-btn" :class="{ active: expRange === 'week' }" @click="expRange = 'week'">当周</button>
|
<button class="seg-btn" :class="{ active: expRange === 'week' }" @click="expRange = 'week'">当周</button>
|
||||||
@@ -110,36 +111,30 @@ function onDateInput(e: Event) {
|
|||||||
<input v-if="expRange === 'custom'" class="input w-exp" type="date" v-model="expEnd" />
|
<input v-if="expRange === 'custom'" class="input w-exp" type="date" v-model="expEnd" />
|
||||||
<button class="btn btn-sm" :disabled="exporting" @click="doExportMd">导出 Markdown</button>
|
<button class="btn btn-sm" :disabled="exporting" @click="doExportMd">导出 Markdown</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<DayPanel :date="date" />
|
<DayPanel :date="date" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.detail-toolbar {
|
.detail-page { display: flex; flex-direction: column; }
|
||||||
display: flex;
|
.toolbar {
|
||||||
align-items: center;
|
padding: 10px 16px;
|
||||||
gap: 10px;
|
display: flex; align-items: center; gap: 14px;
|
||||||
margin-bottom: 12px;
|
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
.nav-row { display: flex; align-items: center; gap: 6px; }
|
||||||
.date-title { font-size: 14px; font-weight: 600; min-width: 170px; text-align: center; }
|
.date-title { font-size: 14px; font-weight: 600; min-width: 170px; text-align: center; }
|
||||||
.date-title.is-today { color: var(--accent); }
|
.date-title.is-today { color: var(--accent); }
|
||||||
.w-date { width: 150px; }
|
.w-date { width: 150px; flex: none; }
|
||||||
|
.tool-sep { width: 1px; align-self: stretch; background: var(--border); }
|
||||||
.export-row {
|
.exp-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
||||||
display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
|
|
||||||
margin-bottom: 12px; padding: 8px 12px;
|
|
||||||
border: 1px solid var(--border); border-radius: var(--radius-md);
|
|
||||||
background: var(--bg-page);
|
|
||||||
}
|
|
||||||
.exp-label { font-size: 12px; }
|
.exp-label { font-size: 12px; }
|
||||||
.seg { display: inline-flex; border: 1px solid var(--border); border-radius: var(--radius-sm); overflow: hidden; }
|
.w-exp { width: 140px; flex: none; }
|
||||||
.seg-btn { border: none; background: var(--bg-page); color: var(--text-2); cursor: pointer; font-size: 12px; padding: 4px 10px; }
|
|
||||||
.seg-btn.active { background: var(--accent-bg); color: var(--accent); }
|
|
||||||
.w-exp { width: 140px; }
|
|
||||||
|
|
||||||
@media (max-width: 720px) {
|
@media (max-width: 720px) {
|
||||||
.detail-toolbar { gap: 8px; }
|
.tool-sep { display: none; }
|
||||||
|
.nav-row { flex: 1; }
|
||||||
.date-title { min-width: 0; flex: 1; text-align: left; }
|
.date-title { min-width: 0; flex: 1; text-align: left; }
|
||||||
.w-date { flex: 1; min-width: 120px; width: auto; }
|
.w-date { flex: 1; min-width: 120px; width: auto; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,24 +150,20 @@ async function doDelete() {
|
|||||||
|
|
||||||
onMounted(load)
|
onMounted(load)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="admin-page">
|
<div class="admin-page">
|
||||||
<div class="card sec">
|
<div class="card table-card">
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<div class="head-wrap">
|
<div class="head-wrap">
|
||||||
<div class="sec-title">用户管理</div>
|
<div class="tb-title">用户管理</div>
|
||||||
<div class="muted head-sub">云端账号统一管理 · 管理员:xionglin</div>
|
<div class="muted tb-sub">云端账号统一管理 · 管理员:xionglin</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-primary btn-sm" @click="openCreate">
|
<button class="btn btn-primary btn-sm" @click="openCreate">
|
||||||
<Icon name="plus" :size="14" />
|
<Icon name="plus" :size="14" />
|
||||||
新增用户
|
新增用户
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="error" class="form-error">{{ error }}</div>
|
<div v-if="error" class="form-error tb-error">{{ error }}</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card table-card admin-table-card">
|
|
||||||
<table class="admin-table">
|
<table class="admin-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -202,8 +198,8 @@ onMounted(load)
|
|||||||
|
|
||||||
<!-- 新增用户弹窗 -->
|
<!-- 新增用户弹窗 -->
|
||||||
<teleport to="body">
|
<teleport to="body">
|
||||||
<div v-if="createOpen" class="fixed-mask" @click.self="createOpen = false">
|
<div v-if="createOpen" class="mask" @click.self="createOpen = false">
|
||||||
<div class="dialog create-dialog" role="dialog" aria-label="新增用户">
|
<div class="dialog" role="dialog" aria-label="新增用户">
|
||||||
<div class="dialog-head">
|
<div class="dialog-head">
|
||||||
<span class="dialog-title">新增用户</span>
|
<span class="dialog-title">新增用户</span>
|
||||||
<button class="icon-btn" @click="createOpen = false"><Icon name="close" :size="16" /></button>
|
<button class="icon-btn" @click="createOpen = false"><Icon name="close" :size="16" /></button>
|
||||||
@@ -233,8 +229,8 @@ onMounted(load)
|
|||||||
|
|
||||||
<!-- 重置密码弹窗 -->
|
<!-- 重置密码弹窗 -->
|
||||||
<teleport to="body">
|
<teleport to="body">
|
||||||
<div v-if="resetTarget" class="fixed-mask" @click.self="resetTarget = null">
|
<div v-if="resetTarget" class="mask" @click.self="resetTarget = null">
|
||||||
<div class="dialog create-dialog" role="dialog" aria-label="重置密码">
|
<div class="dialog" role="dialog" aria-label="重置密码">
|
||||||
<div class="dialog-head">
|
<div class="dialog-head">
|
||||||
<span class="dialog-title">重置「{{ resetTarget.username }}」的登录密码</span>
|
<span class="dialog-title">重置「{{ resetTarget.username }}」的登录密码</span>
|
||||||
<button class="icon-btn" @click="resetTarget = null"><Icon name="close" :size="16" /></button>
|
<button class="icon-btn" @click="resetTarget = null"><Icon name="close" :size="16" /></button>
|
||||||
@@ -264,60 +260,46 @@ onMounted(load)
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.admin-page { display: flex; flex-direction: column; gap: 12px; max-width: 860px; }
|
.admin-page { display: flex; flex-direction: column; gap: 12px; max-width: 860px; }
|
||||||
|
|
||||||
.toolbar { display: flex; align-items: center; gap: 12px; }
|
.toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
.head-wrap { flex: 1; min-width: 0; }
|
.head-wrap { flex: 1; min-width: 0; }
|
||||||
.sec-title { font-weight: 600; font-size: 13px; }
|
.tb-title { font-weight: 600; font-size: 14px; }
|
||||||
.head-sub { font-size: 12px; margin-top: 2px; }
|
.tb-sub { font-size: 12px; margin-top: 2px; }
|
||||||
.sec { padding: 14px 16px; }
|
.tb-error { padding: 8px 16px 0; }
|
||||||
|
|
||||||
.admin-table-card { overflow-x: auto; }
|
.table-card { overflow-x: auto; }
|
||||||
.admin-table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
.admin-table td { white-space: nowrap; }
|
||||||
.admin-table th { text-align: left; font-weight: 500; color: var(--text-3); font-size: 12px; padding: 8px 12px; border-bottom: 1px solid var(--border); }
|
|
||||||
.admin-table td { padding: 9px 12px; border-bottom: 1px solid var(--border); white-space: nowrap; }
|
|
||||||
.admin-table td:last-child { border-bottom: none; }
|
|
||||||
.td-name { font-weight: 500; }
|
.td-name { font-weight: 500; }
|
||||||
.role-badge {
|
.role-badge {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border: 1px solid var(--accent);
|
background: var(--accent-bg);
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 16px;
|
line-height: 18px;
|
||||||
padding: 1px 8px;
|
padding: 1px 9px;
|
||||||
}
|
}
|
||||||
.load-line { padding: 12px; font-size: 12px; }
|
.load-line { padding: 12px 16px; font-size: 12px; }
|
||||||
.admin-note { font-size: 12px; padding: 0 2px; }
|
.admin-note { font-size: 12px; padding: 0 2px; }
|
||||||
.ops { display: inline-flex; gap: 6px; }
|
.ops { display: inline-flex; gap: 6px; }
|
||||||
.btn-danger { color: var(--danger); }
|
|
||||||
|
|
||||||
.form-row { display: flex; flex-direction: column; gap: 5px; margin-top: 10px; }
|
.form-row { display: flex; flex-direction: column; gap: 5px; margin-top: 10px; }
|
||||||
.form-row label { font-size: 12px; color: var(--text-2); }
|
.dialog { width: 420px; }
|
||||||
.fixed-mask {
|
|
||||||
position: fixed;
|
|
||||||
inset: 0;
|
|
||||||
z-index: 80;
|
|
||||||
background: rgba(29, 33, 41, 0.35);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
.dialog { background: var(--bg-page); border-radius: 12px; width: 420px; max-width: 100%; }
|
|
||||||
.dialog-head { display: flex; align-items: center; justify-content: space-between; padding: 14px 16px 0; }
|
|
||||||
.dialog-title { font-weight: 600; font-size: 14px; }
|
|
||||||
.dialog-body { padding: 6px 16px 4px; }
|
|
||||||
.dialog-btns { display: flex; justify-content: flex-end; gap: 10px; padding: 12px 16px 16px; }
|
|
||||||
.btn-danger { color: var(--danger); }
|
|
||||||
|
|
||||||
.empty { text-align: center; }
|
.empty { text-align: center; }
|
||||||
|
|
||||||
@media (max-width: 720px) {
|
@media (max-width: 720px) {
|
||||||
.toolbar { flex-wrap: wrap; row-gap: 8px; }
|
|
||||||
.admin-table td, .admin-table th { white-space: normal; }
|
.admin-table td, .admin-table th { white-space: normal; }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -284,7 +284,7 @@ async function doChangePwd() {
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="muted vault-note num">共 {{ rows.length }} 条账号记录 · 密码以 AES-256-GCM 加密,服务器仅存密文</div>
|
<div class="muted table-note num">共 {{ rows.length }} 条账号记录 · 密码以 AES-256-GCM 加密,服务器仅存密文</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<CredentialDialog
|
<CredentialDialog
|
||||||
@@ -356,18 +356,15 @@ async function doChangePwd() {
|
|||||||
height: 52px;
|
height: 52px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: var(--bg-subtle);
|
background: var(--accent-bg);
|
||||||
border: 1px solid var(--border);
|
color: var(--accent);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
color: var(--text-2);
|
|
||||||
}
|
}
|
||||||
.lock-title { font-size: 14px; font-weight: 600; }
|
.lock-title { font-size: 15px; font-weight: 600; }
|
||||||
.lock-tip { font-size: 12px; line-height: 1.7; }
|
.lock-tip { font-size: 12px; line-height: 1.7; }
|
||||||
.lock-btn { margin-top: 4px; }
|
.lock-btn { margin-top: 4px; }
|
||||||
.form-error { color: var(--danger); font-size: 12px; }
|
|
||||||
.empty { text-align: center; padding: 24px 0; }
|
|
||||||
|
|
||||||
.vault-toolbar { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; flex-wrap: wrap; }
|
.vault-toolbar { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; flex-wrap: wrap; }
|
||||||
.search-wrap { position: relative; width: 280px; max-width: 100%; }
|
.search-wrap { position: relative; width: 280px; max-width: 100%; }
|
||||||
@@ -375,19 +372,6 @@ async function doChangePwd() {
|
|||||||
.search-icon { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); color: var(--text-3); }
|
.search-icon { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); color: var(--text-3); }
|
||||||
|
|
||||||
.table-card { overflow-x: auto; }
|
.table-card { overflow-x: auto; }
|
||||||
.cred-table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
|
||||||
.cred-table th {
|
|
||||||
text-align: left;
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--text-2);
|
|
||||||
background: var(--bg-subtle);
|
|
||||||
padding: 10px 12px;
|
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
.cred-table td { padding: 10px 12px; border-bottom: 1px solid var(--border); vertical-align: top; word-break: break-all; }
|
|
||||||
.cred-table tbody tr:hover { background: var(--bg-subtle); }
|
|
||||||
.cred-table tbody tr:last-child td { border-bottom: none; }
|
|
||||||
.td-system { font-weight: 500; }
|
.td-system { font-weight: 500; }
|
||||||
.th-ops { width: 150px; }
|
.th-ops { width: 150px; }
|
||||||
.pwd-cell { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
|
.pwd-cell { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
|
||||||
@@ -395,43 +379,9 @@ async function doChangePwd() {
|
|||||||
.copyable { cursor: pointer; }
|
.copyable { cursor: pointer; }
|
||||||
.copyable:hover { color: var(--accent); }
|
.copyable:hover { color: var(--accent); }
|
||||||
.ops { display: flex; gap: 4px; flex-wrap: wrap; }
|
.ops { display: flex; gap: 4px; flex-wrap: wrap; }
|
||||||
.vault-note { margin-top: 10px; font-size: 12px; text-align: right; }
|
.table-note { margin-top: 10px; font-size: 12px; text-align: right; }
|
||||||
|
|
||||||
.mask {
|
.dialog { width: 440px; }
|
||||||
position: fixed;
|
|
||||||
inset: 0;
|
|
||||||
background: rgba(29, 33, 41, 0.4);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
z-index: 90;
|
|
||||||
}
|
|
||||||
.dialog {
|
|
||||||
width: 440px;
|
|
||||||
max-width: calc(100vw - 32px);
|
|
||||||
background: var(--bg-page);
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
box-shadow: var(--shadow-float);
|
|
||||||
}
|
|
||||||
.dialog-head {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 16px 20px 12px;
|
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
}
|
|
||||||
.dialog-title { font-size: 14px; font-weight: 600; }
|
|
||||||
.dialog-body { padding: 16px 20px; }
|
|
||||||
.dialog-btns {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 12px 20px 16px;
|
|
||||||
border-top: 1px solid var(--border);
|
|
||||||
}
|
|
||||||
.vp-field { margin-bottom: 10px; }
|
.vp-field { margin-bottom: 10px; }
|
||||||
.vp-field label { display: block; font-size: 12px; color: var(--text-2); margin-bottom: 4px; }
|
|
||||||
.vp-tip { font-size: 12px; margin-top: 8px; }
|
.vp-tip { font-size: 12px; margin-top: 8px; }
|
||||||
.modal-enter-active, .modal-leave-active { transition: opacity 0.15s ease; }
|
|
||||||
.modal-enter-from, .modal-leave-to { opacity: 0; }
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user