feat: 计划模式(时间段+打卡+计时时钟) / 工作详情范围导出 / 工作与记账分类自定义(含历史迁移)
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, onUnmounted, ref, watch } from 'vue'
|
||||
import Icon from './Icon.vue'
|
||||
import { EXPENSE_CATEGORIES, INCOME_CATEGORIES } from '../db/schema'
|
||||
import { getLedgerCategories, setLedgerCategories } from '../db/categories'
|
||||
|
||||
const props = defineProps<{
|
||||
open: boolean
|
||||
@@ -15,7 +15,7 @@ const emit = defineEmits<{
|
||||
}>()
|
||||
|
||||
const fType = ref<'expense' | 'income'>('expense')
|
||||
const fCategory = ref('餐饮')
|
||||
const fCategory = ref('')
|
||||
const fAmount = ref('')
|
||||
const fDate = ref('')
|
||||
const fNote = ref('')
|
||||
@@ -30,6 +30,7 @@ function onKey(e: KeyboardEvent) {
|
||||
}
|
||||
watch(() => props.open, async v => {
|
||||
if (v) {
|
||||
await loadCats()
|
||||
fType.value = (props.initial.type === 'income' ? 'income' : 'expense')
|
||||
fCategory.value = props.initial.category
|
||||
fAmount.value = props.initial.amountYuan
|
||||
@@ -51,7 +52,20 @@ watch(fType, t => {
|
||||
fCategory.value = t === 'expense' ? EXPENSE_CATEGORIES[0] : INCOME_CATEGORIES[0]
|
||||
})
|
||||
|
||||
const categoryOptions = computed(() => (fType.value === 'expense' ? EXPENSE_CATEGORIES : INCOME_CATEGORIES))
|
||||
const categoryOptions = ref<string[]>([])
|
||||
async function loadCats() {
|
||||
categoryOptions.value = await getLedgerCategories(fType.value)
|
||||
}
|
||||
const newCat = ref('')
|
||||
async function addCategory() {
|
||||
const name = newCat.value.trim()
|
||||
if (!name) return
|
||||
if (categoryOptions.value.includes(name)) { newCat.value = ''; return }
|
||||
categoryOptions.value.push(name)
|
||||
await setLedgerCategories(fType.value, categoryOptions.value)
|
||||
fCategory.value = name
|
||||
newCat.value = ''
|
||||
}
|
||||
function computedCategory() {
|
||||
return (fCategory.value && categoryOptions.value.includes(fCategory.value)) ? fCategory.value : categoryOptions.value[0]
|
||||
}
|
||||
@@ -109,10 +123,14 @@ function save() {
|
||||
<input class="input" type="date" v-model="fDate" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>分类</label>
|
||||
<label>分类(可直接新增)</label>
|
||||
<select class="select" v-model="fCategory">
|
||||
<option v-for="c in categoryOptions" :key="c" :value="c">{{ c }}</option>
|
||||
</select>
|
||||
<div class="add-cat">
|
||||
<input class="input add-cat-input" v-model="newCat" placeholder="新增分类名" @keydown.enter.prevent="addCategory" />
|
||||
<button class="btn btn-xs" type="button" @click="addCategory">添加</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field" v-if="fType === 'expense'">
|
||||
<label>报销状态</label>
|
||||
@@ -164,6 +182,8 @@ function save() {
|
||||
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-input { flex: 1; height: 28px; font-size: 12px; }
|
||||
.dialog-body { padding: 16px 20px; }
|
||||
.dialog-btns {
|
||||
display: flex;
|
||||
|
||||
@@ -24,5 +24,6 @@ export const ICONS: Record<string, string> = {
|
||||
lock: svg('<rect x="5.5" y="10.5" width="13" height="9.5" rx="1.5"/><path d="M8.5 10.5V8a3.5 3.5 0 0 1 7 0v2.5"/>'),
|
||||
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"/>'),
|
||||
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"/>'),
|
||||
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"/>')
|
||||
}
|
||||
|
||||
@@ -8,13 +8,16 @@ import {
|
||||
getLocationPresets, getWorkRule, listLogItemsByDate, listProjects, saveMakeup,
|
||||
setAttendanceLocation, setAttendanceTime, setLeave, updateLogItem
|
||||
} from '../../db/repo'
|
||||
import { CATEGORIES, categoryLabel, type Attendance, type LogItem, type Project } from '../../db/schema'
|
||||
import { categoryLabel, type Attendance, type LogItem, type Project } from '../../db/schema'
|
||||
import { getLogCategories } from '../../db/categories'
|
||||
import { formatDateCN, hhmm, todayStr } from '../../lib/date'
|
||||
import { computeDay, type DayAttendanceInfo } from '../../domain/attendance'
|
||||
import type { WorkRule } from '../../db/repo'
|
||||
import { showToast } from '../../lib/toast'
|
||||
import { attendanceChanged, notifyAttendanceChanged } from '../../lib/bus'
|
||||
|
||||
const logCats = ref<string[]>([])
|
||||
getLogCategories().then(l => { logCats.value = l })
|
||||
const props = defineProps<{ date: string }>()
|
||||
|
||||
const loading = ref(true)
|
||||
@@ -402,7 +405,7 @@ async function confirmDelete() {
|
||||
<div class="field">
|
||||
<label>工作分类</label>
|
||||
<select class="select" v-model="category">
|
||||
<option v-for="c in CATEGORIES" :key="c.key" :value="c.key">{{ c.label }}</option>
|
||||
<option v-for="c in logCats" :key="c" :value="c">{{ c }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, onUnmounted, ref, watch } from 'vue'
|
||||
import Icon from '../Icon.vue'
|
||||
import { CATEGORIES, type LogItem, type Project } from '../../db/schema'
|
||||
import { type LogItem, type Project } from '../../db/schema'
|
||||
import { getLogCategories } from '../../db/categories'
|
||||
|
||||
const logCats = ref<string[]>([])
|
||||
getLogCategories().then(l => { logCats.value = l })
|
||||
const props = defineProps<{
|
||||
open: boolean
|
||||
item: LogItem | null
|
||||
@@ -137,7 +140,7 @@ function save() {
|
||||
<div class="field">
|
||||
<label>工作分类</label>
|
||||
<select class="select" v-model="formCategory">
|
||||
<option v-for="c in CATEGORIES" :key="c.key" :value="c.key">{{ c.label }}</option>
|
||||
<option v-for="c in logCats" :key="c" :value="c">{{ c }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
|
||||
Reference in New Issue
Block a user