ui: 全组件审核打磨(按下态/细滚动条/tooltip修复/统计卡语义色条/进度条完成态/危险确认警示/耗时chips/日历今日高亮/密码等宽/登录页质感)

This commit is contained in:
root
2026-10-26 21:48:33 +08:00
parent 3b48a27921
commit 5a6ab2b322
19 changed files with 119 additions and 34 deletions
+27
View File
@@ -0,0 +1,27 @@
# v0.6.7 发布说明(2026-10-26)
## 全组件 UI 审核打磨(19 个界面/组件逐一过审)
**交互反馈**
- 按钮增加按下态(轻微下沉 + 主按钮阴影收起),主按钮带品牌色微阴影
- 全局细滚动条(8px 圆角,滚动区域观感统一)
- 修复侧栏 tooltip:仅折叠态(平板)显示,宽侧栏不再多余弹泡
**语义色与信息层级**
- 记账统计卡增加语义色条:收入绿 / 支出灰 / 结余蓝 / 待报销橙
- 长期计划进度条 100% 完成时变绿
- 危险确认弹窗标题红色警示
- 详情页出勤时间数字加大加粗;工作记录内容字重提升;总结页摘要数字加粗
- 密码库密码文本改等宽字体
- 记账表格金额列右对齐
**体验升级**
- 记录弹窗「耗时」新增快捷选择 chips(15/30/60/90/120 分)
- 登录页背景渐变质感、输入框加高
- 日历:今日格子淡蓝高亮、非本月格灰底、周标题字重
- 移动端「更多」弹层加拖拽指示条
## 部署
- 服务器:`cd /opt/daywork && git pull && cd server && docker compose up -d --build`(网页端即生效)
- 桌面 / 安卓:安装 0.6.7 安装包
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,2 +1,2 @@
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./web-B4MkFHMk.js","./index-DP7vL7H5.js","./index-BuZbSc5_.css"])))=>i.map(i=>d[i]); const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./web-CEj0vlG8.js","./index-o_Kd7ZbB.js","./index-CsewO6wh.css"])))=>i.map(i=>d[i]);
import{r as s,_ as i}from"./index-DP7vL7H5.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-B4MkFHMk.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-o_Kd7ZbB.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-CEj0vlG8.js"),__vite__mapDeps([0,1,2]),import.meta.url).then(e=>new e.FilesystemWeb)});export{t as Directory,a as Encoding,E as Filesystem};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -8,8 +8,8 @@
<link rel="icon" href="./icons/icon.svg" type="image/svg+xml" /> <link rel="icon" href="./icons/icon.svg" type="image/svg+xml" />
<link rel="apple-touch-icon" href="./icons/icon-192.png" /> <link rel="apple-touch-icon" href="./icons/icon-192.png" />
<title>实习工作日志</title> <title>实习工作日志</title>
<script type="module" crossorigin src="./assets/index-DP7vL7H5.js"></script> <script type="module" crossorigin src="./assets/index-o_Kd7ZbB.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index-BuZbSc5_.css"> <link rel="stylesheet" crossorigin href="./assets/index-CsewO6wh.css">
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "intern-log", "name": "intern-log",
"private": true, "private": true,
"version": "0.6.6", "version": "0.6.7",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
+12 -1
View File
@@ -333,6 +333,7 @@ onMounted(async () => {
<!-- 手机端「更多」底部弹层 --> <!-- 手机端「更多」底部弹层 -->
<div v-if="moreOpen" class="sheet-mask" @click="moreOpen = false"> <div v-if="moreOpen" class="sheet-mask" @click="moreOpen = false">
<div class="more-sheet" @click.stop> <div class="more-sheet" @click.stop>
<div class="sheet-handle"></div>
<div class="sheet-user num">{{ session?.username }}</div> <div class="sheet-user num">{{ session?.username }}</div>
<button v-for="v in moreViews" :key="v.key" class="sheet-row" @click="navFromSheet(v.key)"> <button v-for="v in moreViews" :key="v.key" class="sheet-row" @click="navFromSheet(v.key)">
<Icon :name="v.icon" :size="18" /> <Icon :name="v.icon" :size="18" />
@@ -452,6 +453,7 @@ onMounted(async () => {
background: var(--accent); background: var(--accent);
} }
.nav-label { white-space: nowrap; } .nav-label { white-space: nowrap; }
/* tooltip 仅在折叠态(图标-only)显示;宽侧栏文字可见,不需要 */
.nav-btn::after { .nav-btn::after {
content: attr(data-tip); content: attr(data-tip);
position: absolute; position: absolute;
@@ -647,10 +649,18 @@ onMounted(async () => {
bottom: 0; bottom: 0;
background: var(--bg-page); background: var(--bg-page);
border-radius: 16px 16px 0 0; border-radius: 16px 16px 0 0;
padding: 10px 14px calc(14px + env(safe-area-inset-bottom)); padding: 6px 14px calc(14px + env(safe-area-inset-bottom));
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.sheet-handle {
width: 36px;
height: 4px;
border-radius: 2px;
background: var(--border-strong);
margin: 4px auto 8px;
flex: none;
}
.sheet-user { .sheet-user {
font-size: 12px; font-size: 12px;
color: var(--text-3); color: var(--text-3);
@@ -721,6 +731,7 @@ onMounted(async () => {
.brand-name, .nav-label, .nav-group { display: none; } .brand-name, .nav-label, .nav-group { display: none; }
.nav-btn { justify-content: center; padding: 0; width: 40px; align-self: center; } .nav-btn { justify-content: center; padding: 0; width: 40px; align-self: center; }
.nav-btn.active::before { display: none; } .nav-btn.active::before { display: none; }
.nav-btn:hover::after { opacity: 1; }
.side-foot { flex-direction: column; gap: 6px; } .side-foot { flex-direction: column; gap: 6px; }
.foot-name { display: none; } .foot-name { display: none; }
} }
+2 -1
View File
@@ -28,7 +28,7 @@ onUnmounted(() => window.removeEventListener('keydown', onKey))
<transition name="modal"> <transition name="modal">
<div v-if="open" class="mask" @click.self="emit('cancel')"> <div v-if="open" class="mask" @click.self="emit('cancel')">
<div class="dialog" role="dialog" :aria-label="title"> <div class="dialog" role="dialog" :aria-label="title">
<div class="dialog-title">{{ title }}</div> <div class="dialog-title" :class="{ 'is-danger': danger }">{{ title }}</div>
<div class="dialog-msg">{{ message }}</div> <div class="dialog-msg">{{ message }}</div>
<div class="dialog-btns"> <div class="dialog-btns">
<button class="btn btn-sm" @click="emit('cancel')">{{ cancelText }}</button> <button class="btn btn-sm" @click="emit('cancel')">{{ cancelText }}</button>
@@ -42,6 +42,7 @@ onUnmounted(() => window.removeEventListener('keydown', onKey))
<style scoped> <style scoped>
.dialog { width: 380px; } .dialog { width: 380px; }
.dialog-title.is-danger { color: var(--danger); }
.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; } .dialog-btns { margin-top: 20px; }
</style> </style>
+2 -1
View File
@@ -420,6 +420,7 @@ async function confirmDelete() {
.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 { 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; }
.att-val > .num { font-size: 14px; font-weight: 600; letter-spacing: 0.2px; }
.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; }
@@ -444,7 +445,7 @@ async function confirmDelete() {
} }
.item:hover { border-color: var(--border-strong); background: var(--bg-page); } .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; font-weight: 500; }
.item-meta { font-size: 12px; } .item-meta { font-size: 12px; }
.item-sub { .item-sub {
margin-top: 6px; padding-top: 6px; border-top: 1px dashed var(--border); margin-top: 6px; padding-top: 6px; border-top: 1px dashed var(--border);
@@ -146,6 +146,13 @@ function save() {
<div class="field"> <div class="field">
<label>耗时(分钟)</label> <label>耗时(分钟)</label>
<input class="input" type="number" min="0" v-model.number="formMinutes" placeholder="选填" /> <input class="input" type="number" min="0" v-model.number="formMinutes" placeholder="选填" />
<div class="min-chips">
<button
v-for="m in [15, 30, 60, 90, 120]" :key="m" type="button"
class="min-chip" :class="{ on: formMinutes === m }"
@click="formMinutes = formMinutes === m ? null : m"
>{{ m }}分</button>
</div>
</div> </div>
<div class="field"> <div class="field">
<label>主要收获</label> <label>主要收获</label>
@@ -183,4 +190,18 @@ function save() {
<style scoped> <style scoped>
.dialog { width: 560px; } .dialog { width: 560px; }
.min-chips { display: flex; gap: 6px; margin-top: 6px; flex-wrap: wrap; }
.min-chip {
border: 1px solid var(--border-strong);
background: var(--bg-page);
border-radius: 999px;
height: 24px;
padding: 0 10px;
font-size: 12px;
color: var(--text-2);
cursor: pointer;
transition: all 0.12s;
}
.min-chip:hover { background: var(--bg-hover); }
.min-chip.on { background: var(--accent-bg); border-color: var(--accent); color: var(--accent); }
</style> </style>
+13 -1
View File
@@ -78,9 +78,11 @@ button, input, select, textarea { font: inherit; color: inherit; }
transition: background 0.12s, border-color 0.12s, color 0.12s; transition: background 0.12s, border-color 0.12s, color 0.12s;
} }
.btn:hover { background: var(--bg-hover); border-color: var(--border-strong); } .btn:hover { background: var(--bg-hover); border-color: var(--border-strong); }
.btn:active { transform: translateY(1px); }
.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; box-shadow: 0 1px 2px rgba(51, 112, 255, 0.28); }
.btn-primary:hover { background: var(--accent-deep); border-color: var(--accent-deep); } .btn-primary:hover { background: var(--accent-deep); border-color: var(--accent-deep); }
.btn-primary:active { box-shadow: none; }
.btn-danger { color: var(--danger); border-color: var(--danger); background: var(--bg-page); } .btn-danger { color: var(--danger); border-color: var(--danger); background: var(--bg-page); }
.btn-danger:hover { background: #FDF0F0; } .btn-danger:hover { background: #FDF0F0; }
.btn-danger-solid { background: var(--danger); border-color: var(--danger); color: #FFFFFF; } .btn-danger-solid { background: var(--danger); border-color: var(--danger); color: #FFFFFF; }
@@ -279,6 +281,16 @@ button, input, select, textarea { font: inherit; color: inherit; }
.bar-fill { display: block; height: 100%; background: var(--accent); border-radius: 4px; } .bar-fill { display: block; height: 100%; background: var(--accent); border-radius: 4px; }
.bar-num { font-size: 12px; flex: none; color: var(--text-3); } .bar-num { font-size: 12px; flex: none; color: var(--text-3); }
/* ---------- 滚动条(桌面细滚动条,移动端系统默认) ---------- */
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-thumb { background: #D3D7DE; border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: #B9BEC7; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-corner { background: transparent; }
/* 记账表格:金额列右对齐(数字位对齐更利扫读) */
.tx-table th:nth-child(3), .tx-table td:nth-child(3) { text-align: right; }
/* ---------- 图标 ---------- */ /* ---------- 图标 ---------- */
.icon { display: inline-flex; align-items: center; justify-content: center; flex: none; } .icon { display: inline-flex; align-items: center; justify-content: center; flex: none; }
.icon svg { width: 100%; height: 100%; display: block; } .icon svg { width: 100%; height: 100%; display: block; }
+4 -2
View File
@@ -104,7 +104,7 @@ watch(focusDate, d => {
<button <button
v-for="(c, idx) in cells" :key="idx" v-for="(c, idx) in cells" :key="idx"
class="cal-cell" class="cal-cell"
:class="{ blank: !c.inMonth, weekend: c.weekend, selected: selected === c.date }" :class="{ blank: !c.inMonth, weekend: c.weekend, selected: selected === c.date, 'is-today': c.date === t }"
:title="c.inMonth ? cellTitle(c.date) : undefined" :title="c.inMonth ? cellTitle(c.date) : undefined"
@click="selectDay(c.date, c.inMonth)" @click="selectDay(c.date, c.inMonth)"
> >
@@ -149,6 +149,7 @@ watch(focusDate, d => {
text-align: center; text-align: center;
padding: 7px 0; padding: 7px 0;
font-size: 12px; font-size: 12px;
font-weight: 500;
color: var(--text-3); color: var(--text-3);
} }
.cal-cell { .cal-cell {
@@ -163,7 +164,8 @@ watch(focusDate, d => {
transition: background 0.12s; 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; background: var(--bg-subtle); }
.cal-cell.is-today { background: #F5F9FF; }
.cal-cell.selected { background: var(--accent-bg); box-shadow: inset 0 0 0 2px var(--accent); } .cal-cell.selected { background: var(--accent-bg); box-shadow: inset 0 0 0 2px var(--accent); }
.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); }
+9 -4
View File
@@ -236,10 +236,10 @@ async function confirmDelete() {
</div> </div>
<div class="cards"> <div class="cards">
<div class="card stat-card"><div class="muted">本月收入(元)</div><div class="stat-num num amt-income">{{ yuan(summary.income) }}</div></div> <div class="card stat-card stat-income"><div class="muted">本月收入(元)</div><div class="stat-num num amt-income">{{ yuan(summary.income) }}</div></div>
<div class="card stat-card"><div class="muted">本月支出(元)</div><div class="stat-num num">{{ yuan(summary.expense) }}</div></div> <div class="card stat-card stat-expense"><div class="muted">本月支出(元)</div><div class="stat-num num">{{ yuan(summary.expense) }}</div></div>
<div class="card stat-card"><div class="muted">本月结余(元)</div><div class="stat-num num" :class="{ 'amt-income': summary.balance >= 0, 'amt-expense': summary.balance < 0 }">{{ yuan(summary.balance) }}</div></div> <div class="card stat-card stat-balance"><div class="muted">本月结余(元)</div><div class="stat-num num" :class="{ 'amt-income': summary.balance >= 0, 'amt-expense': summary.balance < 0 }">{{ yuan(summary.balance) }}</div></div>
<div class="card stat-card"><div class="muted">待报销(元)</div><div class="stat-num num amt-warn">{{ yuan(summary.pending) }}</div></div> <div class="card stat-card stat-pending"><div class="muted">待报销(元)</div><div class="stat-num num amt-warn">{{ yuan(summary.pending) }}</div></div>
</div> </div>
<div class="card sec" v-if="categoryStats.length"> <div class="card sec" v-if="categoryStats.length">
@@ -319,6 +319,11 @@ 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 { border-left: 3px solid var(--border); }
.stat-income { border-left-color: var(--ok); }
.stat-expense { border-left-color: var(--text-3); }
.stat-balance { border-left-color: var(--accent); }
.stat-pending { border-left-color: var(--warn); }
.card.sec, .card.section { margin-top: 12px; } .card.sec, .card.section { margin-top: 12px; }
.bar-label { width: 56px; } .bar-label { width: 56px; }
+2 -1
View File
@@ -78,12 +78,13 @@ async function submit() {
.login-page { .login-page {
min-height: 100vh; min-height: 100vh;
min-height: 100dvh; min-height: 100dvh;
background: var(--bg-app); background: linear-gradient(180deg, #EBF0F8 0%, var(--bg-app) 46%);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 20px; padding: 20px;
} }
.login-card .input { height: 38px; }
.login-card { .login-card {
width: 400px; width: 400px;
max-width: 100%; max-width: 100%;
+4 -1
View File
@@ -236,7 +236,10 @@ function weekday(s: string): string {
<div class="goal-bar"> <div class="goal-bar">
<span <span
class="goal-bar-fill" 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) + '%' }" :style="{
width: (goalProg[String(g.id)] ? Math.round(goalProg[String(g.id)].done / Math.max(1, goalProg[String(g.id)].total) * 100) : 0) + '%',
background: goalProg[String(g.id)] && goalProg[String(g.id)].done >= goalProg[String(g.id)].total && goalProg[String(g.id)].total > 0 ? 'var(--ok)' : 'var(--accent)'
}"
></span> ></span>
</div> </div>
<div class="muted num goal-meta"> <div class="muted num goal-meta">
+1
View File
@@ -221,6 +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; }
.sum-row span:last-child { font-weight: 500; }
.section-sub { font-weight: 600; margin: 12px 0 4px; font-size: 12px; color: var(--text-2); } .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; }
+1 -1
View File
@@ -375,7 +375,7 @@ async function doChangePwd() {
.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; }
.pwd-text { letter-spacing: 1px; } .pwd-text { letter-spacing: 1px; font-family: ui-monospace, SFMono-Regular, Consolas, monospace; }
.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; }