Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3bbc68b63e | ||
|
|
d55c768f16 | ||
|
|
5a6ab2b322 |
@@ -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 安装包
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# v0.6.8 发布说明(2026-10-26)
|
||||||
|
|
||||||
|
## 确认弹窗重做
|
||||||
|
|
||||||
|
- 修复内容贴边(上一轮组件基座统一时确认框未获得内边距,标题/正文/按钮挤作一团)
|
||||||
|
- 重做为标准企业级危险确认:红色警告徽标 + 标题/正文分区 + 22px 内边距 + 右对齐操作区
|
||||||
|
- 危险操作:红色三角警告徽标 + 红色标题 + 红色实心确认按钮;普通确认保持蓝白风格
|
||||||
|
- 移动端按钮等宽 40px;Esc/点击遮罩关闭行为不变(全部 10+ 处调用点零改动)
|
||||||
|
|
||||||
|
## 部署
|
||||||
|
|
||||||
|
- 服务器:`cd /opt/daywork && git pull && cd server && docker compose up -d --build`(网页端即生效)
|
||||||
|
- 桌面 / 安卓:安装 0.6.8 安装包
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# v0.6.9 发布说明(2026-10-26)
|
||||||
|
|
||||||
|
## 分类管理去原生弹窗,全部自绘
|
||||||
|
|
||||||
|
- **添加 / 修改分类**:不再使用浏览器原生 prompt,改为自绘「输入弹窗」——自动聚焦全选、回车提交、**内联校验**(空值 / 重名直接在弹窗内红字提示,不再 toast 打断)
|
||||||
|
- **删除分类**:不再使用原生 confirm,改为统一的红色警告确认框(含保留提示:历史记录不受影响)
|
||||||
|
- 新增通用 InputDialog 组件(输入类弹窗基座),后续项目改名等场景可复用
|
||||||
|
- 全应用已无任何浏览器原生弹窗
|
||||||
|
|
||||||
|
## 部署
|
||||||
|
|
||||||
|
- 服务器:`cd /opt/daywork && git pull && cd server && docker compose up -d --build`(网页端即生效)
|
||||||
|
- 桌面 / 安卓:安装 0.6.9 安装包
|
||||||
+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
File diff suppressed because one or more lines are too long
-27
File diff suppressed because one or more lines are too long
+2
-2
@@ -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-OZOB5r8S.js","./index-BkEETrCl.js","./index-CJ5TTgSd.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-BkEETrCl.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-OZOB5r8S.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
-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-DP7vL7H5.js"></script>
|
<script type="module" crossorigin src="./assets/index-BkEETrCl.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="./assets/index-BuZbSc5_.css">
|
<link rel="stylesheet" crossorigin href="./assets/index-CJ5TTgSd.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.6.6",
|
"version": "0.6.9",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
+12
-1
@@ -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; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onUnmounted, watch } from 'vue'
|
import { onUnmounted, watch } from 'vue'
|
||||||
|
import Icon from './Icon.vue'
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
open: boolean
|
open: boolean
|
||||||
@@ -27,12 +28,19 @@ onUnmounted(() => window.removeEventListener('keydown', onKey))
|
|||||||
<teleport to="body">
|
<teleport to="body">
|
||||||
<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 confirm-dialog" role="dialog" :aria-label="title">
|
||||||
<div class="dialog-title">{{ title }}</div>
|
<div class="confirm-body">
|
||||||
<div class="dialog-msg">{{ message }}</div>
|
<div v-if="danger" class="confirm-badge">
|
||||||
<div class="dialog-btns">
|
<Icon name="warning" :size="22" />
|
||||||
<button class="btn btn-sm" @click="emit('cancel')">{{ cancelText }}</button>
|
</div>
|
||||||
<button class="btn btn-sm" :class="danger ? 'btn-danger-solid' : 'btn-primary'" @click="emit('confirm')">{{ confirmText }}</button>
|
<div class="confirm-text">
|
||||||
|
<div class="confirm-title" :class="{ 'is-danger': danger }">{{ title }}</div>
|
||||||
|
<div class="confirm-msg">{{ message }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="confirm-btns">
|
||||||
|
<button class="btn" @click="emit('cancel')">{{ cancelText }}</button>
|
||||||
|
<button class="btn" :class="danger ? 'btn-danger-solid' : 'btn-primary'" @click="emit('confirm')">{{ confirmText }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -41,7 +49,34 @@ onUnmounted(() => window.removeEventListener('keydown', onKey))
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.dialog { width: 380px; }
|
.confirm-dialog {
|
||||||
.dialog-msg { margin-top: 8px; color: var(--text-2); font-size: 13px; line-height: 1.7; word-break: break-all; }
|
width: 400px;
|
||||||
.dialog-btns { margin-top: 20px; }
|
padding: 22px 22px 18px;
|
||||||
|
}
|
||||||
|
.confirm-body { display: flex; gap: 14px; align-items: flex-start; }
|
||||||
|
.confirm-badge {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
flex: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #FDEEEE;
|
||||||
|
color: var(--danger);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.confirm-text { flex: 1; min-width: 0; }
|
||||||
|
.confirm-title { font-size: 15px; font-weight: 600; color: var(--text-1); line-height: 1.4; }
|
||||||
|
.confirm-title.is-danger { color: var(--danger); }
|
||||||
|
.confirm-msg { margin-top: 6px; color: var(--text-2); font-size: 13px; line-height: 1.7; word-break: break-all; }
|
||||||
|
.confirm-btns { margin-top: 18px; display: flex; justify-content: flex-end; gap: 8px; }
|
||||||
|
|
||||||
|
.modal-enter-active, .modal-leave-active { transition: opacity 0.15s ease; }
|
||||||
|
.modal-enter-from, .modal-leave-to { opacity: 0; }
|
||||||
|
|
||||||
|
@media (max-width: 720px) {
|
||||||
|
.confirm-dialog { width: calc(100vw - 16px) !important; max-width: calc(100vw - 16px) !important; }
|
||||||
|
.confirm-btns .btn { flex: 1; height: 40px; }
|
||||||
|
.confirm-btns { margin-top: 16px; }
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { nextTick, onUnmounted, ref, watch } from 'vue'
|
||||||
|
import Icon from './Icon.vue'
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
open: boolean
|
||||||
|
title: string
|
||||||
|
label?: string
|
||||||
|
placeholder?: string
|
||||||
|
initial?: string
|
||||||
|
confirmText?: string
|
||||||
|
validate?: (v: string) => string | null
|
||||||
|
}>(), { label: '', placeholder: '', initial: '', confirmText: '确定' })
|
||||||
|
|
||||||
|
const emit = defineEmits<{ (e: 'cancel'): void; (e: 'confirm', value: string): void }>()
|
||||||
|
|
||||||
|
const value = ref('')
|
||||||
|
const err = ref('')
|
||||||
|
const input = ref<HTMLInputElement | null>(null)
|
||||||
|
|
||||||
|
function onKey(e: KeyboardEvent) {
|
||||||
|
if (!props.open) return
|
||||||
|
if (e.key === 'Escape') emit('cancel')
|
||||||
|
}
|
||||||
|
watch(() => props.open, async v => {
|
||||||
|
if (v) {
|
||||||
|
value.value = props.initial
|
||||||
|
err.value = ''
|
||||||
|
window.addEventListener('keydown', onKey)
|
||||||
|
await nextTick()
|
||||||
|
input.value?.focus()
|
||||||
|
input.value?.select()
|
||||||
|
} else {
|
||||||
|
window.removeEventListener('keydown', onKey)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
onUnmounted(() => window.removeEventListener('keydown', onKey))
|
||||||
|
|
||||||
|
function confirm() {
|
||||||
|
const v = value.value.trim()
|
||||||
|
if (!v) { err.value = '请输入内容'; return }
|
||||||
|
const e = props.validate ? props.validate(v) : null
|
||||||
|
if (e) { err.value = e; return }
|
||||||
|
emit('confirm', v)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<teleport to="body">
|
||||||
|
<transition name="modal">
|
||||||
|
<div v-if="open" class="mask" @click.self="emit('cancel')">
|
||||||
|
<div class="dialog input-dialog" role="dialog" :aria-label="title">
|
||||||
|
<div class="dialog-head">
|
||||||
|
<span class="dialog-title">{{ title }}</span>
|
||||||
|
<button class="icon-btn" title="关闭" @click="emit('cancel')">
|
||||||
|
<Icon name="close" :size="16" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="dialog-body">
|
||||||
|
<div class="field">
|
||||||
|
<label v-if="label">{{ label }}</label>
|
||||||
|
<input
|
||||||
|
ref="input" class="input" :class="{ invalid: err }"
|
||||||
|
v-model="value" :placeholder="placeholder" maxlength="20"
|
||||||
|
@keydown.enter="confirm"
|
||||||
|
/>
|
||||||
|
<div v-if="err" class="form-error">{{ err }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dialog-btns">
|
||||||
|
<button class="btn btn-sm" @click="emit('cancel')">取消</button>
|
||||||
|
<button class="btn btn-primary btn-sm" @click="confirm">{{ confirmText }}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</teleport>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.input-dialog { width: 400px; }
|
||||||
|
.field label { display: block; font-size: 12px; color: var(--text-2); margin-bottom: 4px; }
|
||||||
|
.modal-enter-active, .modal-leave-active { transition: opacity 0.15s ease; }
|
||||||
|
.modal-enter-from, .modal-leave-to { opacity: 0; }
|
||||||
|
@media (max-width: 720px) {
|
||||||
|
.input-dialog { width: calc(100vw - 16px) !important; max-width: calc(100vw - 16px) !important; }
|
||||||
|
.dialog-btns .btn { flex: 1; height: 40px; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -25,5 +25,6 @@ export const ICONS: Record<string, string> = {
|
|||||||
wallet: svg('<rect x="3.5" y="6" width="17" height="12.5" rx="2"/><path d="M3.5 10h17"/><circle cx="16.4" cy="14.2" r="1.1"/>'),
|
wallet: svg('<rect x="3.5" y="6" width="17" height="12.5" rx="2"/><path d="M3.5 10h17"/><circle cx="16.4" cy="14.2" r="1.1"/>'),
|
||||||
more: svg('<circle cx="5.4" cy="12" r="1.7"/><circle cx="12" cy="12" r="1.7"/><circle cx="18.6" cy="12" r="1.7"/>'),
|
more: svg('<circle cx="5.4" cy="12" r="1.7"/><circle cx="12" cy="12" r="1.7"/><circle cx="18.6" cy="12" r="1.7"/>'),
|
||||||
shield: svg('<path d="M12 3.5l7.2 2.7v5.1c0 4.7-3.1 7.8-7.2 9.4-4.1-1.6-7.2-4.7-7.2-9.4V6.2z"/><path d="M9.3 12.2l1.9 1.9 3.6-4"/>'),
|
shield: svg('<path d="M12 3.5l7.2 2.7v5.1c0 4.7-3.1 7.8-7.2 9.4-4.1-1.6-7.2-4.7-7.2-9.4V6.2z"/><path d="M9.3 12.2l1.9 1.9 3.6-4"/>'),
|
||||||
|
warning: svg('<path d="M12 4.2L21 19.2H3L12 4.2z"/><path d="M12 10.2v3.6M12 16.4h.01"/>'),
|
||||||
plan: svg('<rect x="4.5" y="4" width="15" height="16.5" rx="2"/><path d="M8.5 9.5l1.4 1.4 2.6-2.9M8.5 15l1.4 1.4 2.6-2.9M15 9.8h.01M15 15.3h.01"/>')
|
plan: svg('<rect x="4.5" y="4" width="15" height="16.5" rx="2"/><path d="M8.5 9.5l1.4 1.4 2.6-2.9M8.5 15l1.4 1.4 2.6-2.9M15 9.8h.01M15 15.3h.01"/>')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|||||||
@@ -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); }
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|||||||
@@ -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%;
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { onMounted, ref, watch } from 'vue'
|
import { onMounted, ref, watch } from 'vue'
|
||||||
import Icon from '../components/Icon.vue'
|
import Icon from '../components/Icon.vue'
|
||||||
import ConfirmDialog from '../components/ConfirmDialog.vue'
|
import ConfirmDialog from '../components/ConfirmDialog.vue'
|
||||||
|
import InputDialog from '../components/InputDialog.vue'
|
||||||
import {
|
import {
|
||||||
addProject, exportAll, getLocationPresets, getSetting, getWorkRule, importAll,
|
addProject, exportAll, getLocationPresets, getSetting, getWorkRule, importAll,
|
||||||
listAttendanceBetween, listLogItemsBetween, listProjects,
|
listAttendanceBetween, listLogItemsBetween, listProjects,
|
||||||
@@ -39,37 +40,53 @@ async function loadCategories() {
|
|||||||
expenseCats.value = await getLedgerCategories('expense')
|
expenseCats.value = await getLedgerCategories('expense')
|
||||||
incomeCats.value = await getLedgerCategories('income')
|
incomeCats.value = await getLedgerCategories('income')
|
||||||
}
|
}
|
||||||
async function addCat(kind: 'log' | 'expense' | 'income') {
|
// ---- 分类编辑:自绘输入弹窗 + 危险确认框(替代浏览器原生 prompt/confirm)----
|
||||||
const name = window.prompt('新分类名称')
|
type CatKind = 'log' | 'expense' | 'income'
|
||||||
if (!name || !name.trim()) return
|
const catDialog = ref<{ open: boolean; kind: CatKind; index: number; initial: string }>({ open: false, kind: 'log', index: -1, initial: '' })
|
||||||
const list = kind === 'log' ? logCats.value : kind === 'expense' ? expenseCats.value : incomeCats.value
|
const delCat = ref<{ kind: CatKind; index: number; name: string } | null>(null)
|
||||||
const clean = name.trim()
|
|
||||||
if (list.includes(clean)) { showToast('分类已存在'); return }
|
function catListOf(kind: CatKind): string[] {
|
||||||
const next = [...list, clean]
|
return kind === 'log' ? logCats.value : kind === 'expense' ? expenseCats.value : incomeCats.value
|
||||||
if (kind === 'log') { logCats.value = next; await setLogCategories(next) }
|
|
||||||
else if (kind === 'expense') { expenseCats.value = next; await setLedgerCategories('expense', next) }
|
|
||||||
else { incomeCats.value = next; await setLedgerCategories('income', next) }
|
|
||||||
showToast('分类已添加')
|
|
||||||
}
|
}
|
||||||
async function renameCat(kind: 'log' | 'expense' | 'income', index: number, oldName: string) {
|
async function writeCats(kind: CatKind, next: string[]) {
|
||||||
const name = window.prompt('修改分类名称', oldName)
|
|
||||||
if (!name || !name.trim() || name.trim() === oldName) return
|
|
||||||
const list = kind === 'log' ? logCats.value : kind === 'expense' ? expenseCats.value : incomeCats.value
|
|
||||||
const next = list.map((x, i) => (i === index ? name.trim() : x))
|
|
||||||
if (kind === 'log') { logCats.value = next; await setLogCategories(next) }
|
if (kind === 'log') { logCats.value = next; await setLogCategories(next) }
|
||||||
else if (kind === 'expense') { expenseCats.value = next; await setLedgerCategories('expense', next) }
|
else if (kind === 'expense') { expenseCats.value = next; await setLedgerCategories('expense', next) }
|
||||||
else { incomeCats.value = next; await setLedgerCategories('income', next) }
|
else { incomeCats.value = next; await setLedgerCategories('income', next) }
|
||||||
|
}
|
||||||
|
function openCatAdd(kind: CatKind) {
|
||||||
|
catDialog.value = { open: true, kind, index: -1, initial: '' }
|
||||||
|
}
|
||||||
|
function openCatRename(kind: CatKind, index: number, oldName: string) {
|
||||||
|
catDialog.value = { open: true, kind, index, initial: oldName }
|
||||||
|
}
|
||||||
|
function validateCat(v: string): string | null {
|
||||||
|
const { kind, index } = catDialog.value
|
||||||
|
const list = catListOf(kind)
|
||||||
|
if (list.includes(v) && !(index >= 0 && list[index] === v)) return '该分类已存在'
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
async function onCatDialogConfirm(v: string) {
|
||||||
|
const { kind, index } = catDialog.value
|
||||||
|
if (index >= 0) {
|
||||||
|
await writeCats(kind, catListOf(kind).map((x, i) => (i === index ? v : x)))
|
||||||
showToast('分类已更新(历史记录自动跟随显示)')
|
showToast('分类已更新(历史记录自动跟随显示)')
|
||||||
|
} else {
|
||||||
|
await writeCats(kind, [...catListOf(kind), v])
|
||||||
|
showToast('分类已添加')
|
||||||
|
}
|
||||||
|
catDialog.value.open = false
|
||||||
}
|
}
|
||||||
async function removeCat(kind: 'log' | 'expense' | 'income', index: number) {
|
function askRemoveCat(kind: CatKind, index: number) {
|
||||||
const list = kind === 'log' ? logCats.value : kind === 'expense' ? expenseCats.value : incomeCats.value
|
const list = catListOf(kind)
|
||||||
if (list.length <= 1) { showToast('至少保留一个分类'); return }
|
if (list.length <= 1) { showToast('至少保留一个分类'); return }
|
||||||
if (!window.confirm('删除分类「' + list[index] + '」?历史记录不受影响。')) return
|
delCat.value = { kind, index, name: list[index] }
|
||||||
const next = list.filter((_, i) => i !== index)
|
}
|
||||||
if (kind === 'log') { logCats.value = next; await setLogCategories(next) }
|
async function confirmRemoveCat() {
|
||||||
else if (kind === 'expense') { expenseCats.value = next; await setLedgerCategories('expense', next) }
|
if (!delCat.value) return
|
||||||
else { incomeCats.value = next; await setLedgerCategories('income', next) }
|
const { kind, index } = delCat.value
|
||||||
|
await writeCats(kind, catListOf(kind).filter((_, i) => i !== index))
|
||||||
showToast('分类已删除')
|
showToast('分类已删除')
|
||||||
|
delCat.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileInput = ref<HTMLInputElement | null>(null)
|
const fileInput = ref<HTMLInputElement | null>(null)
|
||||||
@@ -424,32 +441,32 @@ async function doImport() {
|
|||||||
<div class="card section">
|
<div class="card section">
|
||||||
<div class="section-title">工作分类</div>
|
<div class="section-title">工作分类</div>
|
||||||
<div class="cat-block">
|
<div class="cat-block">
|
||||||
<div class="cat-head"><span class="muted">工作记录分类</span><button class="btn btn-xs" @click="addCat('log')">添加</button></div>
|
<div class="cat-head"><span class="muted">工作记录分类</span><button class="btn btn-xs" @click="openCatAdd('log')">添加</button></div>
|
||||||
<div class="cat-chips">
|
<div class="cat-chips">
|
||||||
<span v-for="(c, i) in logCats" :key="c + i" class="cat-chip">
|
<span v-for="(c, i) in logCats" :key="c + i" class="cat-chip">
|
||||||
{{ c }}
|
{{ c }}
|
||||||
<button class="chip-btn" title="修改" @click="renameCat('log', i, c)">改</button>
|
<button class="chip-btn" title="修改" @click="openCatRename('log', i, c)">改</button>
|
||||||
<button class="chip-btn" title="删除" @click="removeCat('log', i)">删</button>
|
<button class="chip-btn" title="删除" @click="askRemoveCat('log', i)">删</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="cat-block">
|
<div class="cat-block">
|
||||||
<div class="cat-head"><span class="muted">记账支出分类</span><button class="btn btn-xs" @click="addCat('expense')">添加</button></div>
|
<div class="cat-head"><span class="muted">记账支出分类</span><button class="btn btn-xs" @click="openCatAdd('expense')">添加</button></div>
|
||||||
<div class="cat-chips">
|
<div class="cat-chips">
|
||||||
<span v-for="(c, i) in expenseCats" :key="c + i" class="cat-chip">
|
<span v-for="(c, i) in expenseCats" :key="c + i" class="cat-chip">
|
||||||
{{ c }}
|
{{ c }}
|
||||||
<button class="chip-btn" title="修改" @click="renameCat('expense', i, c)">改</button>
|
<button class="chip-btn" title="修改" @click="openCatRename('expense', i, c)">改</button>
|
||||||
<button class="chip-btn" title="删除" @click="removeCat('expense', i)">删</button>
|
<button class="chip-btn" title="删除" @click="askRemoveCat('expense', i)">删</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="cat-block">
|
<div class="cat-block">
|
||||||
<div class="cat-head"><span class="muted">记账收入分类</span><button class="btn btn-xs" @click="addCat('income')">添加</button></div>
|
<div class="cat-head"><span class="muted">记账收入分类</span><button class="btn btn-xs" @click="openCatAdd('income')">添加</button></div>
|
||||||
<div class="cat-chips">
|
<div class="cat-chips">
|
||||||
<span v-for="(c, i) in incomeCats" :key="c + i" class="cat-chip">
|
<span v-for="(c, i) in incomeCats" :key="c + i" class="cat-chip">
|
||||||
{{ c }}
|
{{ c }}
|
||||||
<button class="chip-btn" title="修改" @click="renameCat('income', i, c)">改</button>
|
<button class="chip-btn" title="修改" @click="openCatRename('income', i, c)">改</button>
|
||||||
<button class="chip-btn" title="删除" @click="removeCat('income', i)">删</button>
|
<button class="chip-btn" title="删除" @click="askRemoveCat('income', i)">删</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -471,6 +488,27 @@ async function doImport() {
|
|||||||
<div class="muted backup-note">数据保存在云端服务器;导出的 JSON 备份用于跨设备迁移或灾后恢复。</div>
|
<div class="muted backup-note">数据保存在云端服务器;导出的 JSON 备份用于跨设备迁移或灾后恢复。</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<InputDialog
|
||||||
|
:open="catDialog.open"
|
||||||
|
:title="catDialog.index >= 0 ? '修改分类名称' : '添加分类'"
|
||||||
|
label="分类名称"
|
||||||
|
placeholder="如:制度学习 / 差旅交通"
|
||||||
|
:initial="catDialog.initial"
|
||||||
|
:validate="validateCat"
|
||||||
|
confirm-text="保存"
|
||||||
|
@cancel="catDialog.open = false"
|
||||||
|
@confirm="onCatDialogConfirm"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ConfirmDialog
|
||||||
|
:open="!!delCat"
|
||||||
|
title="删除分类"
|
||||||
|
:message="delCat ? '将删除分类「' + delCat.name + '」。历史记录不受影响,仍按原分类展示。' : ''"
|
||||||
|
confirm-text="删除" danger
|
||||||
|
@cancel="delCat = null"
|
||||||
|
@confirm="confirmRemoveCat"
|
||||||
|
/>
|
||||||
|
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
:open="!!pendingImport"
|
:open="!!pendingImport"
|
||||||
title="导入确认"
|
title="导入确认"
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|||||||
Reference in New Issue
Block a user