create.vue 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. <template>
  2. <div class="admin--showroom-form">
  3. <form @submit.prevent="handleSubmit" class="admin--form">
  4. <!-- 전시장명 -->
  5. <div class="admin--form-group">
  6. <label class="admin--form-label">전시장명 <span class="admin--required">*</span></label>
  7. <input
  8. v-model="formData.name"
  9. type="text"
  10. class="admin--form-input"
  11. placeholder="전시장명을 입력하세요"
  12. required
  13. >
  14. </div>
  15. <!-- 소속명 선택 -->
  16. <div class="admin--form-group">
  17. <label class="admin--form-label">소속명 <span class="admin--required">*</span></label>
  18. <select
  19. v-model="formData.branch_id"
  20. class="admin--form-select"
  21. required
  22. >
  23. <option value="">소속 지점을 선택하세요</option>
  24. <option v-for="branch in branches" :key="branch.id" :value="branch.id">
  25. {{ branch.name }}
  26. </option>
  27. </select>
  28. </div>
  29. <!-- 대표번호 -->
  30. <div class="admin--form-group">
  31. <label class="admin--form-label">대표번호 <span class="admin--required">*</span></label>
  32. <input
  33. v-model="formData.phone"
  34. type="tel"
  35. class="admin--form-input"
  36. placeholder="02-1234-5678"
  37. required
  38. >
  39. </div>
  40. <!-- 주소 -->
  41. <div class="admin--form-group">
  42. <label class="admin--form-label">주소 <span class="admin--required">*</span></label>
  43. <input
  44. v-model="formData.address"
  45. type="text"
  46. class="admin--form-input"
  47. placeholder="주소를 입력하세요"
  48. required
  49. >
  50. </div>
  51. <!-- 상세주소 -->
  52. <div class="admin--form-group">
  53. <label class="admin--form-label">상세주소</label>
  54. <input
  55. v-model="formData.detail_address"
  56. type="text"
  57. class="admin--form-input"
  58. placeholder="상세주소를 입력하세요"
  59. >
  60. </div>
  61. <!-- 위도/경도 -->
  62. <div class="admin--form-group">
  63. <label class="admin--form-label">위치 좌표</label>
  64. <div class="admin--coordinate-group">
  65. <div class="admin--coordinate-item">
  66. <label>위도</label>
  67. <input
  68. v-model.number="formData.latitude"
  69. type="number"
  70. step="any"
  71. class="admin--form-input"
  72. placeholder="37.5665"
  73. >
  74. </div>
  75. <div class="admin--coordinate-item">
  76. <label>경도</label>
  77. <input
  78. v-model.number="formData.longitude"
  79. type="number"
  80. step="any"
  81. class="admin--form-input"
  82. placeholder="126.9780"
  83. >
  84. </div>
  85. </div>
  86. </div>
  87. <!-- 영업시간 -->
  88. <div class="admin--form-group">
  89. <label class="admin--form-label">영업시간</label>
  90. <textarea
  91. v-model="formData.business_hours"
  92. class="admin--form-textarea"
  93. rows="3"
  94. placeholder="평일: 09:00 - 18:00&#10;주말: 10:00 - 17:00"
  95. ></textarea>
  96. </div>
  97. <!-- 견적요청 링크 -->
  98. <div class="admin--form-group">
  99. <label class="admin--form-label">견적요청 링크</label>
  100. <input
  101. v-model="formData.quote_link"
  102. type="url"
  103. class="admin--form-input"
  104. placeholder="https://example.com/quote"
  105. >
  106. </div>
  107. <!-- 시승신청 링크 -->
  108. <div class="admin--form-group">
  109. <label class="admin--form-label">시승신청 링크</label>
  110. <input
  111. v-model="formData.test_drive_link"
  112. type="url"
  113. class="admin--form-input"
  114. placeholder="https://example.com/test-drive"
  115. >
  116. </div>
  117. <!-- 링크 관리 -->
  118. <div class="admin--form-group">
  119. <label class="admin--form-label">관련 링크</label>
  120. <div class="admin--multi-input-wrapper">
  121. <div
  122. v-for="(link, index) in formData.links"
  123. :key="index"
  124. class="admin--multi-input-item"
  125. >
  126. <input
  127. v-model="formData.links[index]"
  128. type="url"
  129. class="admin--form-input"
  130. placeholder="https://example.com"
  131. >
  132. <button
  133. type="button"
  134. class="admin--btn-remove"
  135. @click="removeLink(index)"
  136. >
  137. 삭제
  138. </button>
  139. </div>
  140. <button
  141. type="button"
  142. class="admin--btn-add"
  143. @click="addLink"
  144. >
  145. + 링크 추가
  146. </button>
  147. </div>
  148. </div>
  149. <!-- 버튼 영역 -->
  150. <div class="admin--form-actions">
  151. <button
  152. type="submit"
  153. class="admin--btn admin--btn-primary"
  154. :disabled="isSaving"
  155. >
  156. {{ isSaving ? '저장 중...' : '확인' }}
  157. </button>
  158. <button
  159. type="button"
  160. class="admin--btn admin--btn-secondary"
  161. @click="goToList"
  162. >
  163. 목록
  164. </button>
  165. </div>
  166. <!-- 성공/에러 메시지 -->
  167. <div v-if="successMessage" class="admin--alert admin--alert-success">
  168. {{ successMessage }}
  169. </div>
  170. <div v-if="errorMessage" class="admin--alert admin--alert-error">
  171. {{ errorMessage }}
  172. </div>
  173. </form>
  174. </div>
  175. </template>
  176. <script setup>
  177. import { ref, onMounted } from 'vue'
  178. import { useRouter } from 'vue-router'
  179. definePageMeta({
  180. layout: 'admin',
  181. middleware: ['auth']
  182. })
  183. const router = useRouter()
  184. const { get, post } = useApi()
  185. const isSaving = ref(false)
  186. const successMessage = ref('')
  187. const errorMessage = ref('')
  188. const branches = ref([])
  189. const formData = ref({
  190. name: '',
  191. branch_id: '',
  192. phone: '',
  193. address: '',
  194. detail_address: '',
  195. latitude: null,
  196. longitude: null,
  197. business_hours: '',
  198. quote_link: '',
  199. test_drive_link: '',
  200. links: [''] // 초기 링크 하나
  201. })
  202. // 지점 목록 로드
  203. const loadBranches = async () => {
  204. const { data, error } = await get('/branch/list', { params: { per_page: 1000 } })
  205. if (data?.success && data?.data?.items) {
  206. branches.value = data.data.items.filter(branch => branch.is_active == 1)
  207. }
  208. }
  209. // 링크 추가
  210. const addLink = () => {
  211. formData.value.links.push('')
  212. }
  213. // 링크 삭제
  214. const removeLink = (index) => {
  215. formData.value.links.splice(index, 1)
  216. }
  217. // 폼 제출
  218. const handleSubmit = async () => {
  219. successMessage.value = ''
  220. errorMessage.value = ''
  221. // 유효성 검사
  222. if (!formData.value.name) {
  223. errorMessage.value = '전시장명을 입력하세요.'
  224. return
  225. }
  226. if (!formData.value.branch_id) {
  227. errorMessage.value = '소속 지점을 선택하세요.'
  228. return
  229. }
  230. if (!formData.value.phone) {
  231. errorMessage.value = '대표번호를 입력하세요.'
  232. return
  233. }
  234. if (!formData.value.address) {
  235. errorMessage.value = '주소를 입력하세요.'
  236. return
  237. }
  238. isSaving.value = true
  239. try {
  240. // 빈 링크 제거
  241. const submitData = {
  242. ...formData.value,
  243. links: formData.value.links.filter(link => link.trim() !== '')
  244. }
  245. const { data, error } = await post('/showroom', submitData)
  246. if (error || !data?.success) {
  247. errorMessage.value = error?.message || data?.message || '등록에 실패했습니다.'
  248. } else {
  249. successMessage.value = data.message || '전시장이 등록되었습니다.'
  250. setTimeout(() => {
  251. router.push('/site-manager/showroom/list')
  252. }, 1000)
  253. }
  254. } catch (error) {
  255. errorMessage.value = '서버 오류가 발생했습니다.'
  256. console.error('Save error:', error)
  257. } finally {
  258. isSaving.value = false
  259. }
  260. }
  261. // 목록으로 이동
  262. const goToList = () => {
  263. router.push('/site-manager/showroom/list')
  264. }
  265. onMounted(() => {
  266. loadBranches()
  267. })
  268. </script>
  269. <style scoped>
  270. .admin--coordinate-group {
  271. display: flex;
  272. gap: 16px;
  273. }
  274. .admin--coordinate-item {
  275. flex: 1;
  276. }
  277. .admin--coordinate-item label {
  278. display: block;
  279. margin-bottom: 8px;
  280. font-size: 14px;
  281. color: #666;
  282. }
  283. </style>