| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594 |
- <template>
- <div>
- <div class="inner--headers">
- <h2>{{ pageId }}</h2>
- <div class="bread--crumbs--wrap">
- <span>홈</span>
- <span>제품 관리</span>
- <span>{{ pageId }}</span>
- </div>
- </div>
- <div class="product-register--form">
- <div class="form--container">
- <div class="form--section">
- <h3>기본 정보</h3>
- <div class="form--row">
- <div class="form--field">
- <label class="form--label">제품명 <span class="required">*</span></label>
- <v-text-field
- v-model="form.productName"
- placeholder="제품명을 입력하세요"
- class="custom-input"
- :rules="[rules.required]"
- ></v-text-field>
- </div>
- </div>
- <div class="form--row">
- <div class="form--field">
- <label class="form--label">공급가 <span class="required">*</span></label>
- <v-text-field
- v-model="form.supplyPrice"
- type="number"
- placeholder="공급가를 입력하세요"
- class="custom-input"
- :rules="[rules.required]"
- ></v-text-field>
- </div>
- <div class="form--field">
- <label class="form--label">판매가 <span class="required">*</span></label>
- <v-text-field
- v-model="form.sellPrice"
- type="number"
- placeholder="판매가를 입력하세요"
- class="custom-input"
- :rules="[rules.required]"
- ></v-text-field>
- </div>
- </div>
- <div class="form--row">
- <div class="form--field">
- <label class="form--label">배송비 <span class="required">*</span></label>
- <v-text-field
- v-model="form.shippingCost"
- placeholder="배송비 정보를 입력하세요 (예: 3,000원, 무료배송)"
- class="custom-input"
- :rules="[rules.required]"
- ></v-text-field>
- </div>
- </div>
- <div class="form--row">
- <div class="form--field">
- <label class="form--label">소타이틀</label>
- <v-text-field
- v-model="form.subtitle"
- placeholder="소타이틀을 입력하세요"
- class="custom-input"
- ></v-text-field>
- </div>
- </div>
- </div>
- <div class="form--section">
- <h3>상세 정보</h3>
- <div class="form--row">
- <div class="form--field full-width">
- <label class="form--label">상세내용</label>
- <div class="editor--container">
- <div class="editor--toolbar">
- <v-btn-toggle v-model="editorMode" density="compact" class="editor--mode-toggle">
- <v-btn value="html" size="small">HTML</v-btn>
- <v-btn value="text" size="small">TEXT</v-btn>
- </v-btn-toggle>
- </div>
- <div v-show="editorMode === 'html'" class="html-editor">
- <textarea
- v-model="form.detailContent"
- placeholder="HTML 내용을 입력하세요"
- class="html-textarea"
- rows="15"
- ></textarea>
- </div>
- <div v-show="editorMode === 'text'" class="text-editor">
- <v-textarea
- v-model="form.detailContent"
- placeholder="텍스트 내용을 입력하세요"
- class="custom-input"
- rows="15"
- ></v-textarea>
- </div>
- </div>
- </div>
- </div>
- </div>
- <div class="form--section">
- <h3>첨부파일</h3>
- <div class="form--row">
- <div class="form--field">
- <label class="form--label">상세다운로드</label>
- <div class="file-upload--container">
- <input
- type="file"
- ref="fileInput"
- @change="handleFileUpload"
- accept=".zip"
- class="file-input"
- style="display: none"
- />
- <div class="file-upload--area" @click="triggerFileUpload">
- <div v-if="!form.detailFile" class="file-upload--placeholder">
- <i class="upload-icon">📎</i>
- <p>ZIP 파일을 선택하세요</p>
- <small>클릭하여 파일 선택</small>
- </div>
- <div v-else class="file-upload--selected">
- <i class="file-icon">📁</i>
- <div class="file-info">
- <p class="file-name">{{ form.detailFile.name }}</p>
- <small class="file-size">{{ formatFileSize(form.detailFile.size) }}</small>
- </div>
- <v-btn
- @click.stop="removeFile"
- class="file-remove"
- size="small"
- color="error"
- icon="mdi-close"
- ></v-btn>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- <div class="form--section">
- <h3>상태 설정</h3>
- <div class="form--row">
- <div class="form--field">
- <label class="form--label">상태 <span class="required">*</span></label>
- <v-select
- v-model="form.status"
- :items="statusOptions"
- variant="outlined"
- class="custom-select"
- :rules="[rules.required]"
- ></v-select>
- </div>
- <div class="form--field">
- <label class="form--label">노출상태 <span class="required">*</span></label>
- <v-select
- v-model="form.displayStatus"
- :items="displayStatusOptions"
- variant="outlined"
- class="custom-select"
- :rules="[rules.required]"
- ></v-select>
- </div>
- </div>
- </div>
- <div class="form--section">
- <h3>업데이트 내역</h3>
- <div class="form--row">
- <div class="form--field full-width">
- <label class="form--label">업데이트 내역</label>
- <v-textarea
- v-model="form.updateHistory"
- placeholder="업데이트 내역을 입력하세요 (최대 500자)"
- class="custom-input"
- rows="5"
- :counter="500"
- :rules="[rules.maxLength(500)]"
- ></v-textarea>
- </div>
- </div>
- </div>
- <div class="form--actions">
- <v-btn
- class="custom-btn btn-white"
- @click="goBack"
- >
- 취소
- </v-btn>
- <v-btn
- class="custom-btn btn-blue"
- @click="saveProduct"
- :loading="loading"
- >
- 저장
- </v-btn>
- </div>
- </div>
- </div>
- </div>
- </template>
- <script setup>
- /************************************************************************
- | 레이아웃
- ************************************************************************/
- definePageMeta({
- layout: "default",
- });
- /************************************************************************
- | 스토어
- ************************************************************************/
- const useDtStore = useDetailStore();
- /************************************************************************
- | 전역 변수
- ************************************************************************/
- const { $toast, $log } = useNuxtApp();
- const router = useRouter();
- const pageId = ref("제품 등록");
- const loading = ref(false);
- const editorMode = ref("text");
- const fileInput = ref(null);
- /************************************************************************
- | 폼 데이터
- ************************************************************************/
- const form = ref({
- productName: "",
- supplyPrice: "",
- sellPrice: "",
- shippingCost: "",
- subtitle: "",
- detailContent: "",
- detailFile: null,
- status: "판매중",
- displayStatus: "노출",
- updateHistory: ""
- });
- /************************************************************************
- | 옵션 데이터
- ************************************************************************/
- const statusOptions = ref([
- { title: "판매중", value: "판매중" },
- { title: "품절", value: "품절" }
- ]);
- const displayStatusOptions = ref([
- { title: "노출", value: "노출" },
- { title: "비노출", value: "비노출" }
- ]);
- /************************************************************************
- | 유효성 검사
- ************************************************************************/
- const rules = {
- required: (value) => !!value || "필수 입력 항목입니다.",
- maxLength: (max) => (value) => {
- if (!value) return true;
- return value.length <= max || `최대 ${max}자까지 입력 가능합니다.`;
- }
- };
- /************************************************************************
- | 함수(METHODS)
- ************************************************************************/
- // 파일 업로드 트리거
- const triggerFileUpload = () => {
- fileInput.value.click();
- };
- // 파일 업로드 처리
- const handleFileUpload = (event) => {
- const file = event.target.files[0];
- if (file) {
- if (file.type !== 'application/zip' && !file.name.endsWith('.zip')) {
- $toast.error('ZIP 파일만 업로드 가능합니다.');
- return;
- }
-
- form.value.detailFile = file;
- $toast.success('파일이 선택되었습니다.');
- }
- };
- // 파일 제거
- const removeFile = () => {
- form.value.detailFile = null;
- if (fileInput.value) {
- fileInput.value.value = '';
- }
- };
- // 파일 크기 포맷팅
- const formatFileSize = (bytes) => {
- if (bytes === 0) return '0 Bytes';
- const k = 1024;
- const sizes = ['Bytes', 'KB', 'MB', 'GB'];
- const i = Math.floor(Math.log(bytes) / Math.log(k));
- return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
- };
- // 뒤로가기
- const goBack = () => {
- router.push('/view/vendor');
- };
- // 제품 저장
- const saveProduct = async () => {
- // 유효성 검사
- if (!form.value.productName) {
- $toast.error('제품명을 입력하세요.');
- return;
- }
-
- if (!form.value.supplyPrice) {
- $toast.error('공급가를 입력하세요.');
- return;
- }
-
- if (!form.value.sellPrice) {
- $toast.error('판매가를 입력하세요.');
- return;
- }
-
- if (!form.value.shippingCost) {
- $toast.error('배송비를 입력하세요.');
- return;
- }
- loading.value = true;
-
- try {
- // FormData 생성 (파일 업로드용)
- const formData = new FormData();
- formData.append('productName', form.value.productName);
- formData.append('supplyPrice', form.value.supplyPrice);
- formData.append('sellPrice', form.value.sellPrice);
- formData.append('shippingCost', form.value.shippingCost);
- formData.append('subtitle', form.value.subtitle);
- formData.append('detailContent', form.value.detailContent);
- formData.append('status', form.value.status);
- formData.append('displayStatus', form.value.displayStatus);
- formData.append('updateHistory', form.value.updateHistory);
- formData.append('compId', useAuthStore().getCompanyId);
-
- if (form.value.detailFile) {
- formData.append('detailFile', form.value.detailFile);
- }
- await useAxios()
- .post("/product/register", formData, {
- headers: {
- 'Content-Type': 'multipart/form-data'
- }
- })
- .then((res) => {
- if (res.data.success) {
- $toast.success('제품이 등록되었습니다.');
- router.push('/view/vendor');
- } else {
- $toast.error('제품 등록에 실패했습니다.');
- }
- });
- } catch (error) {
- $log.error('제품 등록 오류:', error);
- $toast.error('제품 등록 중 오류가 발생했습니다.');
- } finally {
- loading.value = false;
- }
- };
- </script>
- <style scoped>
- .product-register--form {
- margin: 20px 0;
- }
- .form--container {
- background: white;
- border-radius: 8px;
- padding: 30px;
- box-shadow: 0 2px 8px rgba(0,0,0,0.1);
- }
- .form--section {
- margin-bottom: 40px;
- }
- .form--section h3 {
- font-size: 18px;
- font-weight: 600;
- color: #333;
- margin-bottom: 20px;
- padding-bottom: 10px;
- border-bottom: 2px solid #f0f0f0;
- }
- .form--row {
- display: flex;
- gap: 20px;
- margin-bottom: 20px;
- }
- .form--field {
- flex: 1;
- }
- .form--field.full-width {
- width: 100%;
- }
- .form--label {
- display: block;
- font-size: 14px;
- font-weight: 500;
- color: #333;
- margin-bottom: 8px;
- }
- .form--label .required {
- color: #f44336;
- }
- .custom-input {
- width: 100%;
- }
- .custom-select {
- width: 100%;
- }
- /* 에디터 스타일 */
- .editor--container {
- border: 1px solid #ddd;
- border-radius: 4px;
- overflow: hidden;
- }
- .editor--toolbar {
- background: #f8f9fa;
- padding: 10px;
- border-bottom: 1px solid #ddd;
- }
- .editor--mode-toggle {
- background: white;
- border-radius: 4px;
- }
- .html-textarea {
- width: 100%;
- border: none;
- outline: none;
- padding: 15px;
- font-family: 'Courier New', monospace;
- font-size: 14px;
- resize: vertical;
- min-height: 400px;
- }
- .text-editor {
- padding: 15px;
- }
- /* 파일 업로드 스타일 */
- .file-upload--container {
- width: 100%;
- }
- .file-upload--area {
- border: 2px dashed #ddd;
- border-radius: 8px;
- padding: 30px;
- text-align: center;
- cursor: pointer;
- transition: all 0.3s ease;
- }
- .file-upload--area:hover {
- border-color: #3f51b5;
- background: #f8f9ff;
- }
- .file-upload--placeholder {
- color: #666;
- }
- .file-upload--placeholder .upload-icon {
- font-size: 48px;
- display: block;
- margin-bottom: 10px;
- }
- .file-upload--placeholder p {
- font-size: 16px;
- margin: 10px 0;
- }
- .file-upload--placeholder small {
- color: #999;
- }
- .file-upload--selected {
- display: flex;
- align-items: center;
- gap: 15px;
- padding: 15px;
- background: #f8f9fa;
- border-radius: 4px;
- }
- .file-upload--selected .file-icon {
- font-size: 24px;
- }
- .file-info {
- flex: 1;
- text-align: left;
- }
- .file-name {
- font-weight: 500;
- margin: 0;
- }
- .file-size {
- color: #666;
- }
- .file-remove {
- margin-left: auto;
- }
- /* 액션 버튼 스타일 */
- .form--actions {
- display: flex;
- justify-content: center;
- gap: 15px;
- margin-top: 40px;
- padding-top: 20px;
- border-top: 1px solid #e0e0e0;
- }
- .custom-btn {
- padding: 12px 30px;
- font-size: 14px;
- font-weight: 500;
- border-radius: 4px;
- min-width: 120px;
- }
- .btn-white {
- background: white;
- color: #666;
- border: 1px solid #ddd;
- }
- .btn-blue {
- background: #3f51b5;
- color: white;
- }
- /* 반응형 */
- @media (max-width: 768px) {
- .form--row {
- flex-direction: column;
- gap: 15px;
- }
-
- .form--container {
- padding: 20px;
- }
-
- .form--actions {
- flex-direction: column;
- }
-
- .custom-btn {
- width: 100%;
- }
- }
- </style>
|