create.vue 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. <template>
  2. <div class="admin--ir-form">
  3. <form @submit.prevent="handleSubmit" class="admin--form">
  4. <!-- 댓글허용 -->
  5. <div class="admin--form-group">
  6. <label class="admin--form-label">댓글허용</label>
  7. <div class="admin--checkbox-group">
  8. <label class="admin--checkbox-label">
  9. <input v-model="formData.allow_comment" type="checkbox">
  10. <span>댓글 허용</span>
  11. </label>
  12. </div>
  13. </div>
  14. <!-- 공지 -->
  15. <div class="admin--form-group">
  16. <label class="admin--form-label">공지</label>
  17. <div class="admin--checkbox-group">
  18. <label class="admin--checkbox-label">
  19. <input v-model="formData.is_notice" type="checkbox">
  20. <span>공지글로 등록</span>
  21. </label>
  22. </div>
  23. </div>
  24. <!-- 이름 -->
  25. <div class="admin--form-group">
  26. <label class="admin--form-label">이름 <span class="admin--required">*</span></label>
  27. <input
  28. v-model="formData.name"
  29. type="text"
  30. class="admin--form-input"
  31. placeholder="이름을 입력하세요"
  32. required
  33. >
  34. </div>
  35. <!-- 이메일 -->
  36. <div class="admin--form-group">
  37. <label class="admin--form-label">이메일 <span class="admin--required">*</span></label>
  38. <input
  39. v-model="formData.email"
  40. type="email"
  41. class="admin--form-input"
  42. placeholder="이메일을 입력하세요"
  43. required
  44. >
  45. </div>
  46. <!-- URL -->
  47. <div class="admin--form-group">
  48. <label class="admin--form-label">URL</label>
  49. <input
  50. v-model="formData.url"
  51. type="url"
  52. class="admin--form-input"
  53. placeholder="https://example.com"
  54. >
  55. </div>
  56. <!-- 제목 -->
  57. <div class="admin--form-group">
  58. <label class="admin--form-label">제목 <span class="admin--required">*</span></label>
  59. <input
  60. v-model="formData.title"
  61. type="text"
  62. class="admin--form-input"
  63. placeholder="제목을 입력하세요"
  64. required
  65. >
  66. </div>
  67. <!-- 내용 -->
  68. <div class="admin--form-group">
  69. <label class="admin--form-label">내용 <span class="admin--required">*</span></label>
  70. <SunEditor v-model="formData.content" />
  71. </div>
  72. <!-- 파일첨부 -->
  73. <div class="admin--form-group">
  74. <label class="admin--form-label">파일첨부</label>
  75. <div class="admin--file-list">
  76. <div v-for="(file, index) in attachedFiles" :key="index" class="admin--file-item">
  77. <span class="admin--file-name">{{ file.name }}</span>
  78. <span class="admin--file-size">({{ formatFileSize(file.size) }})</span>
  79. <button type="button" class="admin--btn-remove-file" @click="removeFile(index)">
  80. 삭제
  81. </button>
  82. </div>
  83. </div>
  84. <input
  85. ref="fileInput"
  86. type="file"
  87. multiple
  88. class="admin--form-file-hidden"
  89. @change="handleFileAdd"
  90. >
  91. <button type="button" class="admin--btn admin--btn-secondary" @click="triggerFileInput">
  92. 파일 추가
  93. </button>
  94. </div>
  95. <!-- 버튼 영역 -->
  96. <div class="admin--form-actions">
  97. <button
  98. type="submit"
  99. class="admin--btn admin--btn-primary"
  100. :disabled="isSaving"
  101. >
  102. {{ isSaving ? '저장 중...' : '확인' }}
  103. </button>
  104. <button
  105. type="button"
  106. class="admin--btn admin--btn-secondary"
  107. @click="goToList"
  108. >
  109. 목록
  110. </button>
  111. </div>
  112. <!-- 성공/에러 메시지 -->
  113. <div v-if="successMessage" class="admin--alert admin--alert-success">
  114. {{ successMessage }}
  115. </div>
  116. <div v-if="errorMessage" class="admin--alert admin--alert-error">
  117. {{ errorMessage }}
  118. </div>
  119. </form>
  120. </div>
  121. </template>
  122. <script setup>
  123. import { ref } from 'vue'
  124. import { useRouter } from 'vue-router'
  125. import SunEditor from '~/components/admin/SunEditor.vue'
  126. definePageMeta({
  127. layout: 'admin',
  128. middleware: ['auth']
  129. })
  130. const router = useRouter()
  131. const { post, upload } = useApi()
  132. const isSaving = ref(false)
  133. const successMessage = ref('')
  134. const errorMessage = ref('')
  135. const attachedFiles = ref([])
  136. const fileInput = ref(null)
  137. const formData = ref({
  138. allow_comment: false,
  139. is_notice: false,
  140. name: '고진',
  141. email: 'admin@admin.kr',
  142. url: '',
  143. title: '',
  144. content: '',
  145. file_urls: []
  146. })
  147. const triggerFileInput = () => {
  148. fileInput.value?.click()
  149. }
  150. const handleFileAdd = (event) => {
  151. const files = Array.from(event.target.files)
  152. attachedFiles.value.push(...files)
  153. event.target.value = ''
  154. }
  155. const removeFile = (index) => {
  156. attachedFiles.value.splice(index, 1)
  157. }
  158. const formatFileSize = (bytes) => {
  159. if (bytes === 0) return '0 Bytes'
  160. const k = 1024
  161. const sizes = ['Bytes', 'KB', 'MB', 'GB']
  162. const i = Math.floor(Math.log(bytes) / Math.log(k))
  163. return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]
  164. }
  165. const handleSubmit = async () => {
  166. successMessage.value = ''
  167. errorMessage.value = ''
  168. if (!formData.value.title) {
  169. errorMessage.value = '제목을 입력하세요.'
  170. return
  171. }
  172. if (!formData.value.content) {
  173. errorMessage.value = '내용을 입력하세요.'
  174. return
  175. }
  176. isSaving.value = true
  177. try {
  178. let fileUrls = []
  179. // 파일 업로드
  180. if (attachedFiles.value.length > 0) {
  181. for (const file of attachedFiles.value) {
  182. const formDataFile = new FormData()
  183. formDataFile.append('file', file)
  184. const { data: uploadData, error: uploadError } = await upload('/upload/file', formDataFile)
  185. if (uploadError) {
  186. errorMessage.value = `파일 업로드에 실패했습니다: ${file.name}`
  187. isSaving.value = false
  188. return
  189. }
  190. fileUrls.push({
  191. name: file.name,
  192. url: uploadData.url,
  193. size: file.size
  194. })
  195. }
  196. }
  197. const submitData = {
  198. ...formData.value,
  199. file_urls: fileUrls
  200. }
  201. const { data, error } = await post('/board/ir', submitData)
  202. if (error) {
  203. errorMessage.value = error.message || '등록에 실패했습니다.'
  204. } else {
  205. successMessage.value = 'IR 자료가 등록되었습니다.'
  206. setTimeout(() => {
  207. router.push('/admin/board/ir')
  208. }, 1000)
  209. }
  210. } catch (error) {
  211. errorMessage.value = '서버 오류가 발생했습니다.'
  212. console.error('Save error:', error)
  213. } finally {
  214. isSaving.value = false
  215. }
  216. }
  217. const goToList = () => {
  218. router.push('/admin/board/ir')
  219. }
  220. </script>