|
|
@@ -1,558 +0,0 @@
|
|
|
-<template>
|
|
|
- <div class="admin--popup-form">
|
|
|
- <div v-if="isLoading" class="admin--loading">데이터를 불러오는 중...</div>
|
|
|
-
|
|
|
- <form v-else @submit.prevent="handleSubmit" class="admin--form">
|
|
|
- <!-- 형태 -->
|
|
|
- <div class="admin--form-group">
|
|
|
- <label class="admin--form-label"
|
|
|
- >형태 <span class="admin--required">*</span></label
|
|
|
- >
|
|
|
- <div class="admin--radio-group">
|
|
|
- <label class="admin--radio-label">
|
|
|
- <input v-model="formData.type" type="radio" value="html" name="type" />
|
|
|
- <span>HTML</span>
|
|
|
- </label>
|
|
|
- <label class="admin--radio-label">
|
|
|
- <input v-model="formData.type" type="radio" value="image" name="type" />
|
|
|
- <span>단일페이지</span>
|
|
|
- </label>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 사이트 선택 -->
|
|
|
- <div class="admin--form-group">
|
|
|
- <label class="admin--form-label"
|
|
|
- >사이트 <span class="admin--required">*</span></label
|
|
|
- >
|
|
|
- <select v-model="formData.site" class="admin--form-select" required>
|
|
|
- <option value="ford">포드</option>
|
|
|
- <option value="lincoln">링컨</option>
|
|
|
- </select>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 제목 -->
|
|
|
- <div class="admin--form-group">
|
|
|
- <label class="admin--form-label"
|
|
|
- >제목 <span class="admin--required">*</span></label
|
|
|
- >
|
|
|
- <input
|
|
|
- v-model="formData.title"
|
|
|
- type="text"
|
|
|
- class="admin--form-input"
|
|
|
- placeholder="팝업 제목을 입력하세요"
|
|
|
- required
|
|
|
- />
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 시작일/종료일 -->
|
|
|
- <div class="admin--form-group">
|
|
|
- <label class="admin--form-label"
|
|
|
- >시작일/종료일 <span class="admin--required">*</span></label
|
|
|
- >
|
|
|
- <div class="admin--date-range">
|
|
|
- <DatePicker
|
|
|
- v-model="formData.start_date"
|
|
|
- placeholder="시작일 선택"
|
|
|
- :max-date="formData.end_date"
|
|
|
- required
|
|
|
- />
|
|
|
- <span class="admin--date-separator">~</span>
|
|
|
- <DatePicker
|
|
|
- v-model="formData.end_date"
|
|
|
- placeholder="종료일 선택"
|
|
|
- :min-date="formData.start_date"
|
|
|
- required
|
|
|
- />
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 시작시간 (선택) -->
|
|
|
- <div class="admin--form-group">
|
|
|
- <label class="admin--form-label">시작시간 <span class="admin--hint">(미입력 시 시작일 00:00부터 바로 출력)</span></label>
|
|
|
- <div class="admin--time-group">
|
|
|
- <label class="admin--radio-label">
|
|
|
- <input v-model="formData.start_period" type="radio" value="AM" name="start_period" />
|
|
|
- <span>오전</span>
|
|
|
- </label>
|
|
|
- <label class="admin--radio-label">
|
|
|
- <input v-model="formData.start_period" type="radio" value="PM" name="start_period" />
|
|
|
- <span>오후</span>
|
|
|
- </label>
|
|
|
- <input
|
|
|
- v-model.number="formData.start_hour"
|
|
|
- type="number"
|
|
|
- class="admin--form-input admin--time-input"
|
|
|
- placeholder="시"
|
|
|
- min="1"
|
|
|
- max="12"
|
|
|
- />
|
|
|
- <span>:</span>
|
|
|
- <input
|
|
|
- v-model.number="formData.start_minute"
|
|
|
- type="number"
|
|
|
- class="admin--form-input admin--time-input"
|
|
|
- placeholder="분"
|
|
|
- min="0"
|
|
|
- max="59"
|
|
|
- />
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 종료시간 (선택) -->
|
|
|
- <div class="admin--form-group">
|
|
|
- <label class="admin--form-label">종료시간 <span class="admin--hint">(미입력 시 종료일 자정까지 노출)</span></label>
|
|
|
- <div class="admin--time-group">
|
|
|
- <label class="admin--radio-label">
|
|
|
- <input v-model="formData.end_period" type="radio" value="AM" name="end_period" />
|
|
|
- <span>오전</span>
|
|
|
- </label>
|
|
|
- <label class="admin--radio-label">
|
|
|
- <input v-model="formData.end_period" type="radio" value="PM" name="end_period" />
|
|
|
- <span>오후</span>
|
|
|
- </label>
|
|
|
- <input
|
|
|
- v-model.number="formData.end_hour"
|
|
|
- type="number"
|
|
|
- class="admin--form-input admin--time-input"
|
|
|
- placeholder="시"
|
|
|
- min="1"
|
|
|
- max="12"
|
|
|
- />
|
|
|
- <span>:</span>
|
|
|
- <input
|
|
|
- v-model.number="formData.end_minute"
|
|
|
- type="number"
|
|
|
- class="admin--form-input admin--time-input"
|
|
|
- placeholder="분"
|
|
|
- min="0"
|
|
|
- max="59"
|
|
|
- />
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 팝업창 사이즈 -->
|
|
|
- <div class="admin--form-group">
|
|
|
- <label class="admin--form-label"
|
|
|
- >팝업창 사이즈 <span class="admin--required">*</span></label
|
|
|
- >
|
|
|
- <div class="admin--size-group">
|
|
|
- <div class="admin--size-item">
|
|
|
- <label>가로</label>
|
|
|
- <input
|
|
|
- v-model.number="formData.width"
|
|
|
- type="number"
|
|
|
- class="admin--form-input"
|
|
|
- placeholder="800"
|
|
|
- min="100"
|
|
|
- required
|
|
|
- />
|
|
|
- <span>px</span>
|
|
|
- </div>
|
|
|
- <div class="admin--size-item">
|
|
|
- <label>세로</label>
|
|
|
- <input
|
|
|
- v-model.number="formData.height"
|
|
|
- type="number"
|
|
|
- class="admin--form-input"
|
|
|
- placeholder="600"
|
|
|
- min="100"
|
|
|
- required
|
|
|
- />
|
|
|
- <span>px</span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 팝업창 위치 -->
|
|
|
- <div class="admin--form-group">
|
|
|
- <label class="admin--form-label"
|
|
|
- >팝업창 위치 <span class="admin--required">*</span></label
|
|
|
- >
|
|
|
- <div class="admin--size-group">
|
|
|
- <div class="admin--size-item">
|
|
|
- <label>TOP</label>
|
|
|
- <input
|
|
|
- v-model.number="formData.position_top"
|
|
|
- type="number"
|
|
|
- class="admin--form-input"
|
|
|
- placeholder="100"
|
|
|
- min="0"
|
|
|
- required
|
|
|
- />
|
|
|
- <span>px</span>
|
|
|
- </div>
|
|
|
- <div class="admin--size-item">
|
|
|
- <label>LEFT</label>
|
|
|
- <input
|
|
|
- v-model.number="formData.position_left"
|
|
|
- type="number"
|
|
|
- class="admin--form-input"
|
|
|
- placeholder="100"
|
|
|
- min="0"
|
|
|
- required
|
|
|
- />
|
|
|
- <span>px</span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 쿠키설정 -->
|
|
|
- <div class="admin--form-group">
|
|
|
- <label class="admin--form-label">쿠키설정</label>
|
|
|
- <div class="admin--radio-group">
|
|
|
- <label class="admin--radio-label">
|
|
|
- <input
|
|
|
- v-model="formData.cookie_setting"
|
|
|
- type="radio"
|
|
|
- value="today"
|
|
|
- name="cookie_setting"
|
|
|
- />
|
|
|
- <span>오늘 하루 창 띄우지 않음</span>
|
|
|
- </label>
|
|
|
- <label class="admin--radio-label">
|
|
|
- <input
|
|
|
- v-model="formData.cookie_setting"
|
|
|
- type="radio"
|
|
|
- value="forever"
|
|
|
- name="cookie_setting"
|
|
|
- />
|
|
|
- <span>다시는 창을 띄우지 않음</span>
|
|
|
- </label>
|
|
|
- <label class="admin--radio-label">
|
|
|
- <input
|
|
|
- v-model="formData.cookie_setting"
|
|
|
- type="radio"
|
|
|
- value="none"
|
|
|
- name="cookie_setting"
|
|
|
- />
|
|
|
- <span>사용 안 함</span>
|
|
|
- </label>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 출력내용 (HTML) -->
|
|
|
- <div v-if="formData.type === 'html'" class="admin--form-group">
|
|
|
- <label class="admin--form-label"
|
|
|
- >출력내용 <span class="admin--required">*</span></label
|
|
|
- >
|
|
|
- <SunEditor
|
|
|
- v-model="formData.content"
|
|
|
- height="400px"
|
|
|
- placeholder="팝업 내용을 입력하세요"
|
|
|
- />
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 출력내용 (이미지) -->
|
|
|
- <div v-if="formData.type === 'image'" class="admin--form-group">
|
|
|
- <label class="admin--form-label"
|
|
|
- >이미지 첨부 <span class="admin--required">*</span></label
|
|
|
- >
|
|
|
- <input
|
|
|
- type="file"
|
|
|
- accept="image/*"
|
|
|
- class="admin--form-file"
|
|
|
- @change="handleImageUpload"
|
|
|
- />
|
|
|
- <div v-if="imagePreview || formData.image_url" class="admin--image-preview">
|
|
|
- <img :src="imagePreview || formData.image_url" alt="미리보기" />
|
|
|
- <button type="button" class="admin--btn-remove-image" @click="removeImage">
|
|
|
- 삭제
|
|
|
- </button>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 링크 URL (단일페이지일 경우) -->
|
|
|
- <div v-if="formData.type === 'image'" class="admin--form-group">
|
|
|
- <label class="admin--form-label">링크 URL</label>
|
|
|
- <input
|
|
|
- v-model="formData.link_url"
|
|
|
- type="url"
|
|
|
- class="admin--form-input"
|
|
|
- placeholder="https://example.com"
|
|
|
- />
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 링크 타겟 (단일페이지일 경우) -->
|
|
|
- <div v-if="formData.type === 'image'" class="admin--form-group">
|
|
|
- <label class="admin--form-label">링크 열기 방식</label>
|
|
|
- <div class="admin--radio-group">
|
|
|
- <label class="admin--radio-label">
|
|
|
- <input
|
|
|
- v-model="formData.link_target"
|
|
|
- type="radio"
|
|
|
- value="_blank"
|
|
|
- name="link_target"
|
|
|
- />
|
|
|
- <span>새창</span>
|
|
|
- </label>
|
|
|
- <label class="admin--radio-label">
|
|
|
- <input
|
|
|
- v-model="formData.link_target"
|
|
|
- type="radio"
|
|
|
- value="_self"
|
|
|
- name="link_target"
|
|
|
- />
|
|
|
- <span>현재창</span>
|
|
|
- </label>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 버튼 영역 -->
|
|
|
- <div class="admin--form-actions">
|
|
|
- <button type="submit" class="admin--btn admin--btn-primary" :disabled="isSaving">
|
|
|
- {{ isSaving ? "저장 중..." : "확인" }}
|
|
|
- </button>
|
|
|
- <button type="button" class="admin--btn admin--btn-secondary" @click="goToList">
|
|
|
- 목록
|
|
|
- </button>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 성공/에러 메시지 -->
|
|
|
- <div v-if="successMessage" class="admin--alert admin--alert-success">
|
|
|
- {{ successMessage }}
|
|
|
- </div>
|
|
|
- <div v-if="errorMessage" class="admin--alert admin--alert-error">
|
|
|
- {{ errorMessage }}
|
|
|
- </div>
|
|
|
- </form>
|
|
|
- </div>
|
|
|
-</template>
|
|
|
-
|
|
|
-<script setup>
|
|
|
- import { ref, onMounted } from "vue";
|
|
|
- import { useRoute, useRouter } from "vue-router";
|
|
|
- import SunEditor from "~/components/admin/SunEditor.vue";
|
|
|
- import DatePicker from "~/components/admin/DatePicker.vue";
|
|
|
-
|
|
|
- definePageMeta({
|
|
|
- layout: "admin",
|
|
|
- middleware: ["auth"],
|
|
|
- });
|
|
|
-
|
|
|
- const route = useRoute();
|
|
|
- const router = useRouter();
|
|
|
- const { get, put, upload } = useApi();
|
|
|
- const { getImageUrl } = useImage();
|
|
|
-
|
|
|
- const isLoading = ref(true);
|
|
|
- const isSaving = ref(false);
|
|
|
- const successMessage = ref("");
|
|
|
- const errorMessage = ref("");
|
|
|
- const imagePreview = ref(null);
|
|
|
- const imageFile = ref(null);
|
|
|
-
|
|
|
- const formData = ref({
|
|
|
- type: "html",
|
|
|
- site: "ford",
|
|
|
- title: "",
|
|
|
- start_date: "",
|
|
|
- end_date: "",
|
|
|
- start_period: "AM",
|
|
|
- start_hour: null,
|
|
|
- start_minute: null,
|
|
|
- end_period: "AM",
|
|
|
- end_hour: null,
|
|
|
- end_minute: null,
|
|
|
- width: 800,
|
|
|
- height: 600,
|
|
|
- position_top: 100,
|
|
|
- position_left: 100,
|
|
|
- cookie_setting: "none",
|
|
|
- content: "",
|
|
|
- image_url: "",
|
|
|
- link_url: "",
|
|
|
- link_target: "_blank",
|
|
|
- });
|
|
|
-
|
|
|
- // 12시간제 ↔ 24시간 "HH:MM:SS" 변환
|
|
|
- const toTime24 = (period, hour, minute) => {
|
|
|
- if (hour === null || hour === undefined || hour === "") return null;
|
|
|
- let h = Number(hour);
|
|
|
- const m = Number(minute || 0);
|
|
|
- if (isNaN(h) || h < 1 || h > 12) return null;
|
|
|
- if (period === "PM" && h < 12) h += 12;
|
|
|
- if (period === "AM" && h === 12) h = 0;
|
|
|
- return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:00`;
|
|
|
- };
|
|
|
-
|
|
|
- const fromTime24 = (timeStr) => {
|
|
|
- if (!timeStr) return { period: "AM", hour: null, minute: null };
|
|
|
- const [hStr, mStr] = String(timeStr).split(":");
|
|
|
- let h = Number(hStr);
|
|
|
- const m = Number(mStr || 0);
|
|
|
- if (isNaN(h)) return { period: "AM", hour: null, minute: null };
|
|
|
- const period = h >= 12 ? "PM" : "AM";
|
|
|
- const hour12 = h % 12 === 0 ? 12 : h % 12;
|
|
|
- return { period, hour: hour12, minute: m };
|
|
|
- };
|
|
|
-
|
|
|
- // 데이터 로드
|
|
|
- const loadPopup = async () => {
|
|
|
- isLoading.value = true;
|
|
|
-
|
|
|
- const id = route.params.id;
|
|
|
- const { data, error } = await get(`/basic/popup/${id}`);
|
|
|
-
|
|
|
- console.log("[Popup Edit] 데이터 로드 응답:", { data, error });
|
|
|
-
|
|
|
- // API 응답: { success: true, data: {...}, message }
|
|
|
- if (data?.success && data?.data) {
|
|
|
- const popup = data.data;
|
|
|
- const startParts = fromTime24(popup.start_time);
|
|
|
- const endParts = fromTime24(popup.end_time);
|
|
|
- formData.value = {
|
|
|
- type: popup.type || "html",
|
|
|
- site: popup.site || "ford",
|
|
|
- title: popup.title || "",
|
|
|
- start_date: popup.start_date || "",
|
|
|
- end_date: popup.end_date || "",
|
|
|
- start_period: startParts.period,
|
|
|
- start_hour: startParts.hour,
|
|
|
- start_minute: startParts.minute,
|
|
|
- end_period: endParts.period,
|
|
|
- end_hour: endParts.hour,
|
|
|
- end_minute: endParts.minute,
|
|
|
- width: popup.width || 800,
|
|
|
- height: popup.height || 600,
|
|
|
- position_top: popup.position_top || 100,
|
|
|
- position_left: popup.position_left || 100,
|
|
|
- cookie_setting: popup.cookie_setting || "none",
|
|
|
- content: popup.content || "",
|
|
|
- image_url: popup.image_url || "",
|
|
|
- link_url: popup.link_url || "",
|
|
|
- link_target: popup.link_target || "_blank",
|
|
|
- };
|
|
|
-
|
|
|
- // 이미지 미리보기는 새 이미지 업로드시에만 사용
|
|
|
- // 기존 이미지는 formData.image_url을 통해 getImageUrl()로 표시
|
|
|
- imagePreview.value = null;
|
|
|
- console.log("[Popup Edit] 이미지 URL:", popup.image_url);
|
|
|
-
|
|
|
- console.log("[Popup Edit] 데이터 로드 성공:", formData.value);
|
|
|
- } else {
|
|
|
- console.log("[Popup Edit] 데이터 로드 실패");
|
|
|
- }
|
|
|
-
|
|
|
- isLoading.value = false;
|
|
|
- };
|
|
|
-
|
|
|
- // 이미지 업로드
|
|
|
- const handleImageUpload = (event) => {
|
|
|
- const file = event.target.files[0];
|
|
|
- if (!file) return;
|
|
|
-
|
|
|
- if (!file.type.startsWith("image/")) {
|
|
|
- alert("이미지 파일만 업로드 가능합니다.");
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- imageFile.value = file;
|
|
|
-
|
|
|
- // 미리보기
|
|
|
- const reader = new FileReader();
|
|
|
- reader.onload = (e) => {
|
|
|
- imagePreview.value = e.target.result;
|
|
|
- };
|
|
|
- reader.readAsDataURL(file);
|
|
|
- };
|
|
|
-
|
|
|
- // 이미지 삭제
|
|
|
- const removeImage = () => {
|
|
|
- imagePreview.value = null;
|
|
|
- imageFile.value = null;
|
|
|
- formData.value.image_url = "";
|
|
|
- };
|
|
|
-
|
|
|
- // 폼 제출
|
|
|
- const handleSubmit = async () => {
|
|
|
- successMessage.value = "";
|
|
|
- errorMessage.value = "";
|
|
|
-
|
|
|
- // 유효성 검사
|
|
|
- if (!formData.value.title) {
|
|
|
- errorMessage.value = "제목을 입력하세요.";
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- if (!formData.value.start_date || !formData.value.end_date) {
|
|
|
- errorMessage.value = "시작일과 종료일을 선택하세요.";
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- if (formData.value.type === "html" && !formData.value.content) {
|
|
|
- errorMessage.value = "출력내용을 입력하세요.";
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- if (
|
|
|
- formData.value.type === "image" &&
|
|
|
- !imageFile.value &&
|
|
|
- !formData.value.image_url
|
|
|
- ) {
|
|
|
- errorMessage.value = "이미지를 첨부하세요.";
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- isSaving.value = true;
|
|
|
-
|
|
|
- try {
|
|
|
- let imageUrl = formData.value.image_url;
|
|
|
-
|
|
|
- // 이미지 업로드 (새로운 이미지가 있는 경우)
|
|
|
- if (formData.value.type === "image" && imageFile.value) {
|
|
|
- const formDataImage = new FormData();
|
|
|
- formDataImage.append("file", imageFile.value);
|
|
|
-
|
|
|
- const { data: uploadData, error: uploadError } = await upload(
|
|
|
- "/upload/image",
|
|
|
- formDataImage
|
|
|
- );
|
|
|
-
|
|
|
- console.log("[Popup Edit] 이미지 업로드 응답:", { uploadData, uploadError });
|
|
|
-
|
|
|
- if (uploadError || !uploadData?.success) {
|
|
|
- errorMessage.value = uploadError?.message || "이미지 업로드에 실패했습니다.";
|
|
|
- isSaving.value = false;
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- imageUrl = uploadData.data?.url || uploadData.data;
|
|
|
- }
|
|
|
-
|
|
|
- // 팝업 수정
|
|
|
- const submitData = {
|
|
|
- ...formData.value,
|
|
|
- image_url: imageUrl,
|
|
|
- start_time: toTime24(formData.value.start_period, formData.value.start_hour, formData.value.start_minute),
|
|
|
- end_time: toTime24(formData.value.end_period, formData.value.end_hour, formData.value.end_minute),
|
|
|
- };
|
|
|
-
|
|
|
- const id = route.params.id;
|
|
|
- const { data, error } = await put(`/basic/popup/${id}`, submitData);
|
|
|
-
|
|
|
- if (error || !data?.success) {
|
|
|
- errorMessage.value = error?.message || data?.message || "수정에 실패했습니다.";
|
|
|
- } else {
|
|
|
- successMessage.value = data.message || "팝업이 수정되었습니다.";
|
|
|
- setTimeout(() => {
|
|
|
- router.push("/site-manager/basic/popup");
|
|
|
- }, 1000);
|
|
|
- }
|
|
|
- } catch (error) {
|
|
|
- errorMessage.value = "서버 오류가 발생했습니다.";
|
|
|
- console.error("Save error:", error);
|
|
|
- } finally {
|
|
|
- isSaving.value = false;
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- // 목록으로 이동
|
|
|
- const goToList = () => {
|
|
|
- router.push("/site-manager/basic/popup");
|
|
|
- };
|
|
|
-
|
|
|
- onMounted(() => {
|
|
|
- loadPopup();
|
|
|
- });
|
|
|
-</script>
|