ui: 分类管理去原生弹窗(自绘输入弹窗带内联校验 + 危险确认框) + 通用InputDialog组件

This commit is contained in:
root
2026-10-26 23:25:57 +08:00
parent d55c768f16
commit 3bbc68b63e
11 changed files with 207 additions and 67 deletions
+71 -33
View File
@@ -2,6 +2,7 @@
import { onMounted, ref, watch } from 'vue'
import Icon from '../components/Icon.vue'
import ConfirmDialog from '../components/ConfirmDialog.vue'
import InputDialog from '../components/InputDialog.vue'
import {
addProject, exportAll, getLocationPresets, getSetting, getWorkRule, importAll,
listAttendanceBetween, listLogItemsBetween, listProjects,
@@ -39,37 +40,53 @@ async function loadCategories() {
expenseCats.value = await getLedgerCategories('expense')
incomeCats.value = await getLedgerCategories('income')
}
async function addCat(kind: 'log' | 'expense' | 'income') {
const name = window.prompt('新分类名称')
if (!name || !name.trim()) return
const list = kind === 'log' ? logCats.value : kind === 'expense' ? expenseCats.value : incomeCats.value
const clean = name.trim()
if (list.includes(clean)) { showToast('分类已存在'); return }
const next = [...list, clean]
// ---- 分类编辑:自绘输入弹窗 + 危险确认框(替代浏览器原生 prompt/confirm)----
type CatKind = 'log' | 'expense' | 'income'
const catDialog = ref<{ open: boolean; kind: CatKind; index: number; initial: string }>({ open: false, kind: 'log', index: -1, initial: '' })
const delCat = ref<{ kind: CatKind; index: number; name: string } | null>(null)
function catListOf(kind: CatKind): string[] {
return kind === 'log' ? logCats.value : kind === 'expense' ? expenseCats.value : incomeCats.value
}
async function writeCats(kind: CatKind, next: string[]) {
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) {
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) }
else if (kind === 'expense') { expenseCats.value = next; await setLedgerCategories('expense', next) }
else { incomeCats.value = next; await setLedgerCategories('income', next) }
showToast('分类已更新(历史记录自动跟随显示)')
function openCatAdd(kind: CatKind) {
catDialog.value = { open: true, kind, index: -1, initial: '' }
}
async function removeCat(kind: 'log' | 'expense' | 'income', index: number) {
const list = kind === 'log' ? logCats.value : kind === 'expense' ? expenseCats.value : incomeCats.value
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('分类已更新(历史记录自动跟随显示)')
} else {
await writeCats(kind, [...catListOf(kind), v])
showToast('分类已添加')
}
catDialog.value.open = false
}
function askRemoveCat(kind: CatKind, index: number) {
const list = catListOf(kind)
if (list.length <= 1) { showToast('至少保留一个分类'); return }
if (!window.confirm('删除分类「' + list[index] + '」?历史记录不受影响。')) return
const next = list.filter((_, i) => i !== index)
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) }
delCat.value = { kind, index, name: list[index] }
}
async function confirmRemoveCat() {
if (!delCat.value) return
const { kind, index } = delCat.value
await writeCats(kind, catListOf(kind).filter((_, i) => i !== index))
showToast('分类已删除')
delCat.value = null
}
const fileInput = ref<HTMLInputElement | null>(null)
@@ -424,32 +441,32 @@ async function doImport() {
<div class="card section">
<div class="section-title">工作分类</div>
<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">
<span v-for="(c, i) in logCats" :key="c + i" class="cat-chip">
{{ c }}
<button class="chip-btn" title="修改" @click="renameCat('log', i, c)">改</button>
<button class="chip-btn" title="删除" @click="removeCat('log', i)">删</button>
<button class="chip-btn" title="修改" @click="openCatRename('log', i, c)">改</button>
<button class="chip-btn" title="删除" @click="askRemoveCat('log', i)">删</button>
</span>
</div>
</div>
<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">
<span v-for="(c, i) in expenseCats" :key="c + i" class="cat-chip">
{{ c }}
<button class="chip-btn" title="修改" @click="renameCat('expense', i, c)">改</button>
<button class="chip-btn" title="删除" @click="removeCat('expense', i)">删</button>
<button class="chip-btn" title="修改" @click="openCatRename('expense', i, c)">改</button>
<button class="chip-btn" title="删除" @click="askRemoveCat('expense', i)">删</button>
</span>
</div>
</div>
<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">
<span v-for="(c, i) in incomeCats" :key="c + i" class="cat-chip">
{{ c }}
<button class="chip-btn" title="修改" @click="renameCat('income', i, c)">改</button>
<button class="chip-btn" title="删除" @click="removeCat('income', i)">删</button>
<button class="chip-btn" title="修改" @click="openCatRename('income', i, c)">改</button>
<button class="chip-btn" title="删除" @click="askRemoveCat('income', i)">删</button>
</span>
</div>
</div>
@@ -471,6 +488,27 @@ async function doImport() {
<div class="muted backup-note">数据保存在云端服务器;导出的 JSON 备份用于跨设备迁移或灾后恢复。</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
:open="!!pendingImport"
title="导入确认"