create.vue 7.9 KB

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