create.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. <template>
  2. <div class="admin--sales-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. <select v-model.number="formData.showroom" class="admin--form-select" required>
  8. <option value="">전시장을 선택하세요</option>
  9. <option v-for="showroom in showrooms" :key="showroom.id" :value="showroom.id">
  10. {{ showroom.name }}
  11. </option>
  12. </select>
  13. </div>
  14. <!-- 영업팀 -->
  15. <div class="admin--form-group">
  16. <label class="admin--form-label">영업팀 <span class="admin--required">*</span></label>
  17. <select v-model.number="formData.team" class="admin--form-select" required>
  18. <option value="">영업팀을 선택하세요</option>
  19. <option v-for="team in teams" :key="team.id" :value="team.id">
  20. {{ team.name }}
  21. </option>
  22. </select>
  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. <select v-model.number="formData.position" class="admin--form-select" required>
  39. <option value="">직책을 선택하세요</option>
  40. <option :value="10">팀장</option>
  41. <option :value="15">마스터</option>
  42. <option :value="20">차장</option>
  43. <option :value="30">과장</option>
  44. <option :value="40">대리</option>
  45. <option :value="60">사원</option>
  46. </select>
  47. </div>
  48. <!-- 대표번호 -->
  49. <div class="admin--form-group">
  50. <label class="admin--form-label">대표번호 <span class="admin--required">*</span></label>
  51. <input
  52. v-model="formData.main_phone"
  53. type="tel"
  54. class="admin--form-input"
  55. placeholder="02-1234-5678"
  56. required
  57. >
  58. </div>
  59. <!-- 직통번호 -->
  60. <div class="admin--form-group">
  61. <label class="admin--form-label">직통번호</label>
  62. <input
  63. v-model="formData.direct_phone"
  64. type="tel"
  65. class="admin--form-input"
  66. placeholder="02-1234-5679"
  67. >
  68. </div>
  69. <!-- 핸드폰 -->
  70. <div class="admin--form-group">
  71. <label class="admin--form-label">핸드폰</label>
  72. <input
  73. v-model="formData.mobile"
  74. type="tel"
  75. class="admin--form-input"
  76. placeholder="010-1234-5678"
  77. >
  78. </div>
  79. <!-- 이메일 -->
  80. <div class="admin--form-group">
  81. <label class="admin--form-label">이메일</label>
  82. <input
  83. v-model="formData.email"
  84. type="email"
  85. class="admin--form-input"
  86. placeholder="email@example.com"
  87. >
  88. </div>
  89. <!-- 사진 -->
  90. <div class="admin--form-group">
  91. <label class="admin--form-label">사진</label>
  92. <input
  93. type="file"
  94. accept="image/*"
  95. class="admin--form-file"
  96. @change="handlePhotoUpload"
  97. >
  98. <div v-if="photoPreview" class="admin--image-preview">
  99. <img :src="photoPreview" alt="미리보기">
  100. <button type="button" class="admin--btn-remove-image" @click="removePhoto">
  101. 삭제
  102. </button>
  103. </div>
  104. </div>
  105. <!-- SACT -->
  106. <div class="admin--form-group">
  107. <label class="admin--form-label">SACT</label>
  108. <div class="admin--radio-group">
  109. <label class="admin--radio-label">
  110. <input v-model="formData.is_sact" type="radio" :value="true" name="is_sact">
  111. <span>예</span>
  112. </label>
  113. <label class="admin--radio-label">
  114. <input v-model="formData.is_sact" type="radio" :value="false" name="is_sact">
  115. <span>아니오</span>
  116. </label>
  117. </div>
  118. </div>
  119. <!-- TOP30 -->
  120. <div class="admin--form-group">
  121. <label class="admin--form-label">TOP30</label>
  122. <div class="admin--radio-group">
  123. <label class="admin--radio-label">
  124. <input v-model="formData.is_top30" type="radio" :value="true" name="is_top30">
  125. <span>예</span>
  126. </label>
  127. <label class="admin--radio-label">
  128. <input v-model="formData.is_top30" type="radio" :value="false" name="is_top30">
  129. <span>아니오</span>
  130. </label>
  131. </div>
  132. </div>
  133. <!-- 노출순서 -->
  134. <div class="admin--form-group">
  135. <label class="admin--form-label">노출순서</label>
  136. <input
  137. v-model.number="formData.display_order"
  138. type="number"
  139. class="admin--form-input"
  140. placeholder="숫자만 입력"
  141. min="0"
  142. >
  143. <p class="admin--form-help">숫자가 작을수록 먼저 노출됩니다.</p>
  144. </div>
  145. <!-- 버튼 영역 -->
  146. <div class="admin--form-actions">
  147. <button
  148. type="submit"
  149. class="admin--btn admin--btn-primary"
  150. :disabled="isSaving"
  151. >
  152. {{ isSaving ? '저장 중...' : '확인' }}
  153. </button>
  154. <button
  155. type="button"
  156. class="admin--btn admin--btn-secondary"
  157. @click="goToList"
  158. >
  159. 목록
  160. </button>
  161. </div>
  162. <!-- 성공/에러 메시지 -->
  163. <div v-if="successMessage" class="admin--alert admin--alert-success">
  164. {{ successMessage }}
  165. </div>
  166. <div v-if="errorMessage" class="admin--alert admin--alert-error">
  167. {{ errorMessage }}
  168. </div>
  169. </form>
  170. </div>
  171. </template>
  172. <script setup>
  173. import { ref, onMounted } from 'vue'
  174. import { useRouter } from 'vue-router'
  175. definePageMeta({
  176. layout: 'admin',
  177. middleware: ['auth']
  178. })
  179. const router = useRouter()
  180. const { get, post, upload } = useApi()
  181. const isSaving = ref(false)
  182. const successMessage = ref('')
  183. const errorMessage = ref('')
  184. const photoPreview = ref(null)
  185. const photoFile = ref(null)
  186. const showrooms = ref([])
  187. // 영업팀 수동 리스트 (0번째는 마스터팀)
  188. const teams = ref([
  189. { id: 0, name: '마스터팀' },
  190. { id: 1, name: '1팀' },
  191. { id: 2, name: '2팀' },
  192. { id: 3, name: '3팀' },
  193. { id: 4, name: '4팀' },
  194. { id: 5, name: '5팀' },
  195. { id: 6, name: '6팀' },
  196. { id: 7, name: '7팀' },
  197. { id: 8, name: '8팀' },
  198. { id: 9, name: '9팀' },
  199. { id: 10, name: '10팀' }
  200. ])
  201. const formData = ref({
  202. showroom: '',
  203. team: '',
  204. name: '',
  205. position: '',
  206. main_phone: '',
  207. direct_phone: '',
  208. mobile: '',
  209. email: '',
  210. photo_url: '',
  211. is_sact: false,
  212. is_top30: false,
  213. display_order: 0
  214. })
  215. // 필터 데이터 로드
  216. const loadFilters = async () => {
  217. // 전시장 리스트 (지점 목록)
  218. const { data: branchData, error: branchError } = await get('/branch/list', { per_page: 1000 })
  219. console.log('[SalesCreate] 전시장(지점) API 응답:', { data: branchData, error: branchError })
  220. if (branchData?.success && branchData?.data) {
  221. showrooms.value = branchData.data.items || []
  222. console.log('[SalesCreate] 전시장(지점) 로드 성공')
  223. }
  224. }
  225. // 사진 업로드
  226. const handlePhotoUpload = (event) => {
  227. const file = event.target.files[0]
  228. if (!file) return
  229. if (!file.type.startsWith('image/')) {
  230. alert('이미지 파일만 업로드 가능합니다.')
  231. return
  232. }
  233. photoFile.value = file
  234. const reader = new FileReader()
  235. reader.onload = (e) => {
  236. photoPreview.value = e.target.result
  237. }
  238. reader.readAsDataURL(file)
  239. }
  240. // 사진 삭제
  241. const removePhoto = () => {
  242. photoPreview.value = null
  243. photoFile.value = null
  244. formData.value.photo_url = ''
  245. }
  246. // 폼 제출
  247. const handleSubmit = async () => {
  248. successMessage.value = ''
  249. errorMessage.value = ''
  250. if (formData.value.showroom === '' || formData.value.showroom === null) {
  251. errorMessage.value = '전시장을 선택하세요.'
  252. return
  253. }
  254. if (formData.value.team === '' || formData.value.team === null) {
  255. errorMessage.value = '영업팀을 선택하세요.'
  256. return
  257. }
  258. if (!formData.value.name) {
  259. errorMessage.value = '이름을 입력하세요.'
  260. return
  261. }
  262. if (formData.value.position === '' || formData.value.position === null) {
  263. errorMessage.value = '직책을 선택하세요.'
  264. return
  265. }
  266. isSaving.value = true
  267. try {
  268. let photoUrl = formData.value.photo_url
  269. // 사진 업로드
  270. if (photoFile.value) {
  271. const formDataImage = new FormData()
  272. formDataImage.append('file', photoFile.value)
  273. const { data: uploadData, error: uploadError } = await upload('/upload/staff-image', formDataImage)
  274. console.log('[SalesCreate] 이미지 업로드 응답:', { data: uploadData, error: uploadError })
  275. if (uploadError) {
  276. errorMessage.value = '사진 업로드에 실패했습니다: ' + (uploadError.message || uploadError)
  277. isSaving.value = false
  278. return
  279. }
  280. if (!uploadData?.success || !uploadData?.data?.url) {
  281. errorMessage.value = '사진 업로드 응답이 올바르지 않습니다.'
  282. isSaving.value = false
  283. return
  284. }
  285. photoUrl = uploadData.data.url
  286. console.log('[SalesCreate] 업로드된 이미지 URL:', photoUrl)
  287. }
  288. const submitData = {
  289. ...formData.value,
  290. photo_url: photoUrl
  291. }
  292. const { data, error } = await post('/staff/sales', submitData)
  293. if (error) {
  294. errorMessage.value = error.message || '등록에 실패했습니다.'
  295. } else {
  296. successMessage.value = '영업사원이 등록되었습니다.'
  297. setTimeout(() => {
  298. router.push('/admin/staff/sales')
  299. }, 1000)
  300. }
  301. } catch (error) {
  302. errorMessage.value = '서버 오류가 발생했습니다.'
  303. console.error('Save error:', error)
  304. } finally {
  305. isSaving.value = false
  306. }
  307. }
  308. const goToList = () => {
  309. router.push('/admin/staff/sales')
  310. }
  311. onMounted(() => {
  312. loadFilters()
  313. })
  314. </script>