103 lines
3.4 KiB
Vue
103 lines
3.4 KiB
Vue
<script setup lang="ts">
|
|
import { ref } from 'vue'
|
|
|
|
const props = defineProps<{
|
|
title?: string
|
|
initialData?: Record<string, any>
|
|
fields: Array<{
|
|
name: string
|
|
label: string
|
|
type?: 'text' | 'number' | 'select' | 'textarea' | 'checkbox' | 'date'
|
|
required?: boolean
|
|
options?: Array<{ label: string; value: any }>
|
|
placeholder?: string
|
|
}>
|
|
isEditing?: boolean
|
|
}>()
|
|
|
|
const emit = defineEmits(['save', 'cancel', 'delete'])
|
|
|
|
const formData = ref<Record<string, any>>({ ...(props.initialData || {}) })
|
|
|
|
const handleSave = () => {
|
|
emit('save', formData.value)
|
|
}
|
|
|
|
const handleDelete = () => {
|
|
if (confirm('해당 레코드를 삭제(Soft Delete)하시겠습니까?')) {
|
|
emit('delete', formData.value)
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="card shadow-sm border">
|
|
<div class="card-header bg-navy text-white d-flex justify-content-between align-items-center py-2 px-3">
|
|
<h5 class="card-title m-0 font-weight-bold text-white fs-6">
|
|
<i class="ti ti-edit me-1"></i> {{ title || (isEditing ? '데이터 수정' : '신규 데이터 등록') }}
|
|
</h5>
|
|
<div class="d-flex gap-2">
|
|
<button type="button" class="btn btn-sm btn-success fw-bold px-3" @click="handleSave">
|
|
<span class="hotkey-badge me-1">F4</span>{{ isEditing ? '수정 저장' : '신규 저장' }}
|
|
</button>
|
|
<button v-if="isEditing" type="button" class="btn btn-sm btn-danger fw-bold px-3" @click="handleDelete">
|
|
<span class="hotkey-badge me-1">F5</span>삭제
|
|
</button>
|
|
<button type="button" class="btn btn-sm btn-secondary fw-bold px-3" @click="emit('cancel')">
|
|
취소
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card-body p-3">
|
|
<div class="row g-3">
|
|
<div v-for="field in fields" :key="field.name" class="col-md-6 col-12">
|
|
<label class="form-label fw-bold fs-7 mb-1">
|
|
<span v-if="field.required" class="text-danger me-1">*</span>{{ field.label }}
|
|
</label>
|
|
|
|
<template v-if="field.type === 'select'">
|
|
<select v-model="formData[field.name]" class="form-select form-select-sm fw-bold">
|
|
<option v-for="opt in field.options" :key="opt.value" :value="opt.value">
|
|
{{ opt.label }}
|
|
</option>
|
|
</select>
|
|
</template>
|
|
|
|
<template v-else-if="field.type === 'textarea'">
|
|
<textarea v-model="formData[field.name]" class="form-control form-control-sm fw-bold" rows="3" :placeholder="field.placeholder"></textarea>
|
|
</template>
|
|
|
|
<template v-else-if="field.type === 'checkbox'">
|
|
<div class="form-check mt-2">
|
|
<input v-model="formData[field.name]" type="checkbox" class="form-check-input" :id="field.name" />
|
|
<label class="form-check-label fs-7 fw-bold" :for="field.name">{{ field.label }}</label>
|
|
</div>
|
|
</template>
|
|
|
|
<template v-else>
|
|
<input
|
|
v-model="formData[field.name]"
|
|
:type="field.type || 'text'"
|
|
class="form-control form-control-sm fw-bold"
|
|
:placeholder="field.placeholder"
|
|
/>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.bg-navy {
|
|
background-color: #1E293B;
|
|
}
|
|
.hotkey-badge {
|
|
background: rgba(255, 255, 255, 0.2);
|
|
padding: 1px 4px;
|
|
border-radius: 2px;
|
|
font-size: 10px;
|
|
}
|
|
</style>
|