Files
daywork/intern-log/src/components/log/EditLogDialog.vue
T

187 lines
6.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { computed, nextTick, onUnmounted, ref, watch } from 'vue'
import Icon from '../Icon.vue'
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
projects: Project[]
date?: string
}>()
const emit = defineEmits<{
(e: 'cancel'): void
(e: 'save', payload: {
id: number
content: string
project_id: number | null
category: string
minutes: number | null
takeaway: string | null
remark: string | null
is_milestone: number
needs_followup: number
}): void
}>()
const formContent = ref('')
const formProjectId = ref<number | null>(null)
const formCategory = ref('dev')
const formMinutes = ref<number | null>(null)
const formTakeaway = ref('')
const formRemark = ref('')
const formMilestone = ref(0)
const formFollowup = ref(0)
const contentError = ref('')
const contentInput = ref<HTMLInputElement | null>(null)
// 归档项目回显:当前条目引用了已归档项目时,下拉中追加该项避免丢失
const projectOptions = computed(() => {
const active = props.projects.filter(p => p.archived !== 1)
const cur = props.item?.project_id
if (cur && !active.some(p => p.id === cur)) {
const arch = props.projects.find(p => p.id === cur)
const list = active.slice()
if (arch) list.unshift({ ...arch, name: arch.name + '(已归档)' })
return list
}
return active
})
function onKey(e: KeyboardEvent) {
if (!props.open) return
if (e.key === 'Escape') emit('cancel')
}
watch(() => props.open, async v => {
if (v) {
const it = props.item
formContent.value = it?.content ?? ''
formProjectId.value = it?.project_id ?? null
formCategory.value = it?.category ?? 'dev'
formMinutes.value = it?.minutes ?? null
formTakeaway.value = it?.takeaway ?? ''
formRemark.value = it?.remark ?? ''
formMilestone.value = it?.is_milestone ?? 0
formFollowup.value = it?.needs_followup ?? 0
contentError.value = ''
window.addEventListener('keydown', onKey)
await nextTick()
contentInput.value?.focus()
} else {
window.removeEventListener('keydown', onKey)
}
})
onUnmounted(() => window.removeEventListener('keydown', onKey))
watch(formContent, () => {
if (contentError.value) contentError.value = ''
})
function save() {
const text = formContent.value.trim()
if (!text) {
contentError.value = '请填写工作内容'
return
}
if (!props.item || !props.item.id) {
emit('save', { id: 0, content: text, project_id: formProjectId.value, category: formCategory.value, minutes: formMinutes.value && formMinutes.value > 0 ? formMinutes.value : null, takeaway: formTakeaway.value.trim() || null, remark: formRemark.value.trim() || null, is_milestone: formMilestone.value, needs_followup: formFollowup.value })
return
}
emit('save', {
id: props.item.id,
content: text,
project_id: formProjectId.value,
category: formCategory.value,
minutes: formMinutes.value && formMinutes.value > 0 ? formMinutes.value : null,
takeaway: formTakeaway.value.trim() || null,
remark: formRemark.value.trim() || null,
is_milestone: formMilestone.value ? 1 : 0,
needs_followup: formFollowup.value ? 1 : 0
})
}
</script>
<template>
<teleport to="body">
<transition name="modal">
<div v-if="open" class="mask" @click.self="emit('cancel')">
<div class="dialog" role="dialog" aria-label="编辑工作记录">
<div class="dialog-head">
<div>
<div class="dialog-title">{{ item ? '编辑工作记录' : '添加工作记录' }}</div>
<div v-if="date" class="muted dialog-sub">{{ date }}</div>
</div>
<button class="icon-btn" title="关闭" @click="emit('cancel')">
<Icon name="close" :size="16" />
</button>
</div>
<div class="dialog-body">
<div class="form-grid">
<div class="field span2">
<label>工作内容(必填)</label>
<input
ref="contentInput" class="input" :class="{ invalid: contentError }"
v-model="formContent" @keydown.enter="save"
/>
<div v-if="contentError" class="form-error">{{ contentError }}</div>
</div>
<div class="field">
<label>所属项目</label>
<select class="select" v-model="formProjectId">
<option :value="null">无项目</option>
<option v-for="p in projectOptions" :key="p.id" :value="p.id">{{ p.name }}</option>
</select>
</div>
<div class="field">
<label>工作分类</label>
<select class="select" v-model="formCategory">
<option v-for="c in logCats" :key="c" :value="c">{{ c }}</option>
</select>
</div>
<div class="field">
<label>耗时(分钟)</label>
<input class="input" type="number" min="0" v-model.number="formMinutes" placeholder="选填" />
</div>
<div class="field">
<label>主要收获</label>
<input class="input" v-model="formTakeaway" placeholder="一句话:学到了什么" />
</div>
<div class="field span2">
<label>备注</label>
<textarea class="textarea" v-model="formRemark" placeholder="背景、链接、未决事项(选填)"></textarea>
</div>
<div class="field">
<label>成果标记</label>
<button
class="toggle-btn" :class="{ on: formMilestone === 1 }"
@click="formMilestone = formMilestone === 1 ? 0 : 1"
>{{ formMilestone === 1 ? '已标记为成果' : '未标记' }}</button>
</div>
<div class="field">
<label>待跟进</label>
<button
class="toggle-btn" :class="{ onwarn: formFollowup === 1 }"
@click="formFollowup = formFollowup === 1 ? 0 : 1"
>{{ formFollowup === 1 ? '需要跟进' : '无需跟进' }}</button>
</div>
</div>
</div>
<div class="dialog-btns">
<button class="btn btn-sm" @click="emit('cancel')">取消</button>
<button class="btn btn-primary btn-sm" @click="save">{{ item ? '保存修改' : '添加' }}</button>
</div>
</div>
</div>
</transition>
</teleport>
</template>
<style scoped>
.dialog { width: 560px; }
</style>