create.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. <template>
  2. <div class="admin--manager-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
  8. v-model="formData.branch_id"
  9. class="admin--form-select"
  10. required
  11. >
  12. <option value="">지점을 선택하세요</option>
  13. <option
  14. v-for="branch in branches"
  15. :key="branch.id"
  16. :value="branch.id"
  17. :disabled="branch.is_active !== 1 && branch.is_active !== '1'"
  18. >
  19. {{ branch.name }}{{ (branch.is_active !== 1 && branch.is_active !== '1') ? ' (비활성화)' : '' }}
  20. </option>
  21. </select>
  22. </div>
  23. <!-- 아이디 -->
  24. <div class="admin--form-group">
  25. <label class="admin--form-label">아이디 <span class="admin--required">*</span></label>
  26. <div class="admin--input-with-button">
  27. <input
  28. v-model="formData.user_id"
  29. type="text"
  30. class="admin--form-input"
  31. placeholder="아이디를 입력하세요"
  32. required
  33. @input="handleUserIdInput"
  34. >
  35. <button
  36. type="button"
  37. class="admin--btn admin--btn-check"
  38. @click="checkDuplicateUserId"
  39. :disabled="!formData.user_id || isCheckingUserId"
  40. >
  41. {{ isCheckingUserId ? '확인 중...' : '중복 체크' }}
  42. </button>
  43. </div>
  44. <p v-if="userIdCheckResult === 'available'" class="admin--form-help admin--text-success">
  45. 사용 가능한 아이디입니다.
  46. </p>
  47. <p v-if="userIdCheckResult === 'duplicate'" class="admin--form-help admin--text-error">
  48. 이미 사용 중인 아이디입니다.
  49. </p>
  50. </div>
  51. <!-- 비밀번호 -->
  52. <div class="admin--form-group">
  53. <label class="admin--form-label">비밀번호 <span class="admin--required">*</span></label>
  54. <div class="admin--password-input-wrapper">
  55. <input
  56. v-model="formData.password"
  57. :type="showPassword ? 'text' : 'password'"
  58. class="admin--form-input"
  59. placeholder="비밀번호를 입력하세요"
  60. required
  61. >
  62. <button
  63. type="button"
  64. class="admin--password-toggle"
  65. @click="showPassword = !showPassword"
  66. >
  67. {{ showPassword ? '👁️' : '👁️‍🗨️' }}
  68. </button>
  69. </div>
  70. </div>
  71. <!-- 비밀번호 확인 -->
  72. <div class="admin--form-group">
  73. <label class="admin--form-label">비밀번호 확인 <span class="admin--required">*</span></label>
  74. <div class="admin--password-input-wrapper">
  75. <input
  76. v-model="formData.password_confirm"
  77. :type="showPasswordConfirm ? 'text' : 'password'"
  78. class="admin--form-input"
  79. placeholder="비밀번호를 다시 입력하세요"
  80. required
  81. >
  82. <button
  83. type="button"
  84. class="admin--password-toggle"
  85. @click="showPasswordConfirm = !showPasswordConfirm"
  86. >
  87. {{ showPasswordConfirm ? '👁️' : '👁️‍🗨️' }}
  88. </button>
  89. </div>
  90. </div>
  91. <!-- 관리자명 -->
  92. <div class="admin--form-group">
  93. <label class="admin--form-label">관리자명 <span class="admin--required">*</span></label>
  94. <input
  95. v-model="formData.name"
  96. type="text"
  97. class="admin--form-input"
  98. placeholder="이름을 입력하세요"
  99. required
  100. >
  101. </div>
  102. <!-- 이메일 -->
  103. <div class="admin--form-group">
  104. <label class="admin--form-label">이메일 <span class="admin--required">*</span></label>
  105. <input
  106. v-model="formData.email"
  107. type="email"
  108. class="admin--form-input"
  109. placeholder="email@example.com"
  110. required
  111. >
  112. </div>
  113. <!-- 인사말 -->
  114. <div class="admin--form-group">
  115. <label class="admin--form-label">인사말</label>
  116. <textarea
  117. v-model="formData.greeting"
  118. class="admin--form-textarea"
  119. placeholder="인사말을 입력하세요 (엔터로 줄바꿈 가능)"
  120. rows="5"
  121. ></textarea>
  122. <p class="admin--form-help">영업사원 목록 페이지 상단에 표시됩니다.</p>
  123. </div>
  124. <!-- 사진 -->
  125. <div class="admin--form-group">
  126. <label class="admin--form-label">사진</label>
  127. <input
  128. type="file"
  129. accept="image/*"
  130. class="admin--form-file"
  131. @change="handlePhotoUpload"
  132. >
  133. <div v-if="photoPreview" class="admin--image-preview">
  134. <img :src="photoPreview" alt="미리보기">
  135. <button type="button" class="admin--btn-remove-image" @click="removePhoto">
  136. 삭제
  137. </button>
  138. </div>
  139. </div>
  140. <!-- 버튼 영역 -->
  141. <div class="admin--form-actions">
  142. <button
  143. type="submit"
  144. class="admin--btn admin--btn-primary"
  145. :disabled="isSaving"
  146. >
  147. {{ isSaving ? '저장 중...' : '확인' }}
  148. </button>
  149. <button
  150. type="button"
  151. class="admin--btn admin--btn-secondary"
  152. @click="goToList"
  153. >
  154. 목록
  155. </button>
  156. </div>
  157. <!-- 성공/에러 메시지 -->
  158. <div v-if="successMessage" class="admin--alert admin--alert-success">
  159. {{ successMessage }}
  160. </div>
  161. <div v-if="errorMessage" class="admin--alert admin--alert-error">
  162. {{ errorMessage }}
  163. </div>
  164. </form>
  165. </div>
  166. </template>
  167. <script setup>
  168. import { ref, onMounted } from 'vue'
  169. import { useRouter } from 'vue-router'
  170. definePageMeta({
  171. layout: 'admin',
  172. middleware: ['auth']
  173. })
  174. const router = useRouter()
  175. const { get, post, upload } = useApi()
  176. const { getImageUrl } = useImage()
  177. const isSaving = ref(false)
  178. const successMessage = ref('')
  179. const errorMessage = ref('')
  180. const showPassword = ref(false)
  181. const showPasswordConfirm = ref(false)
  182. const branches = ref([])
  183. const photoPreview = ref(null)
  184. const photoFile = ref(null)
  185. const isCheckingUserId = ref(false)
  186. const userIdCheckResult = ref('') // 'available', 'duplicate', ''
  187. const formData = ref({
  188. branch_id: '',
  189. user_id: '',
  190. password: '',
  191. password_confirm: '',
  192. name: '',
  193. email: '',
  194. greeting: '',
  195. photo_url: ''
  196. })
  197. // 지점 목록 로드
  198. const loadBranches = async () => {
  199. const { data, error } = await get('/branch/list', { per_page: 1000 })
  200. console.log('[BranchManagerCreate] API 응답:', { data, error })
  201. if (data?.success && data?.data) {
  202. branches.value = data.data.items || []
  203. console.log('[BranchManagerCreate] 지점 목록 로드 성공')
  204. }
  205. }
  206. // 아이디 입력 시 중복 체크 결과 초기화
  207. const handleUserIdInput = () => {
  208. userIdCheckResult.value = ''
  209. }
  210. // 아이디 중복 체크
  211. const checkDuplicateUserId = async () => {
  212. if (!formData.value.user_id) {
  213. alert('아이디를 입력하세요.')
  214. return
  215. }
  216. isCheckingUserId.value = true
  217. userIdCheckResult.value = ''
  218. try {
  219. const { data, error } = await get('/branch/manager/check-userid', {
  220. params: {
  221. user_id: formData.value.user_id
  222. }
  223. })
  224. console.log('[BranchManagerCreate] 중복 체크 응답:', { data, error })
  225. if (error) {
  226. alert('중복 체크에 실패했습니다.')
  227. return
  228. }
  229. if (data?.success) {
  230. if (data?.data?.available) {
  231. userIdCheckResult.value = 'available'
  232. } else {
  233. userIdCheckResult.value = 'duplicate'
  234. }
  235. }
  236. } catch (error) {
  237. console.error('중복 체크 오류:', error)
  238. alert('중복 체크 중 오류가 발생했습니다.')
  239. } finally {
  240. isCheckingUserId.value = false
  241. }
  242. }
  243. // 사진 업로드
  244. const handlePhotoUpload = (event) => {
  245. const file = event.target.files[0]
  246. if (!file) return
  247. if (!file.type.startsWith('image/')) {
  248. alert('이미지 파일만 업로드 가능합니다.')
  249. return
  250. }
  251. photoFile.value = file
  252. const reader = new FileReader()
  253. reader.onload = (e) => {
  254. photoPreview.value = e.target.result
  255. }
  256. reader.readAsDataURL(file)
  257. }
  258. // 사진 삭제
  259. const removePhoto = () => {
  260. photoPreview.value = null
  261. photoFile.value = null
  262. formData.value.photo_url = ''
  263. }
  264. // 폼 제출
  265. const handleSubmit = async () => {
  266. successMessage.value = ''
  267. errorMessage.value = ''
  268. // 유효성 검사
  269. if (!formData.value.branch_id) {
  270. errorMessage.value = '지점을 선택하세요.'
  271. return
  272. }
  273. if (!formData.value.user_id) {
  274. errorMessage.value = '아이디를 입력하세요.'
  275. return
  276. }
  277. if (userIdCheckResult.value !== 'available') {
  278. errorMessage.value = '아이디 중복 체크를 해주세요.'
  279. return
  280. }
  281. if (!formData.value.password) {
  282. errorMessage.value = '비밀번호를 입력하세요.'
  283. return
  284. }
  285. if (formData.value.password !== formData.value.password_confirm) {
  286. errorMessage.value = '비밀번호가 일치하지 않습니다.'
  287. return
  288. }
  289. if (!formData.value.name) {
  290. errorMessage.value = '관리자명을 입력하세요.'
  291. return
  292. }
  293. if (!formData.value.email) {
  294. errorMessage.value = '이메일을 입력하세요.'
  295. return
  296. }
  297. isSaving.value = true
  298. try {
  299. let photoUrl = formData.value.photo_url
  300. // 새 사진 업로드
  301. if (photoFile.value) {
  302. const formDataImage = new FormData()
  303. formDataImage.append('file', photoFile.value)
  304. const { data: uploadData, error: uploadError } = await upload('/upload/bmanager-image', formDataImage)
  305. console.log('[BranchManagerCreate] 이미지 업로드 응답:', { data: uploadData, error: uploadError })
  306. if (uploadError) {
  307. errorMessage.value = '사진 업로드에 실패했습니다: ' + (uploadError.message || uploadError)
  308. isSaving.value = false
  309. return
  310. }
  311. if (!uploadData?.success || !uploadData?.data?.url) {
  312. errorMessage.value = '사진 업로드 응답이 올바르지 않습니다.'
  313. isSaving.value = false
  314. return
  315. }
  316. photoUrl = uploadData.data.url
  317. console.log('[BranchManagerCreate] 업로드된 이미지 URL:', photoUrl)
  318. }
  319. const submitData = {
  320. branch_id: formData.value.branch_id,
  321. user_id: formData.value.user_id,
  322. password: formData.value.password,
  323. name: formData.value.name,
  324. email: formData.value.email,
  325. greeting: formData.value.greeting,
  326. photo_url: photoUrl
  327. }
  328. const { data, error } = await post('/branch/manager', submitData)
  329. if (error) {
  330. errorMessage.value = error.message || '등록에 실패했습니다.'
  331. } else {
  332. successMessage.value = '지점장이 등록되었습니다.'
  333. setTimeout(() => {
  334. router.push('/site-manager/branch/manager')
  335. }, 1000)
  336. }
  337. } catch (error) {
  338. errorMessage.value = '서버 오류가 발생했습니다.'
  339. console.error('Save error:', error)
  340. } finally {
  341. isSaving.value = false
  342. }
  343. }
  344. // 목록으로 이동
  345. const goToList = () => {
  346. router.push('/site-manager/branch/manager')
  347. }
  348. onMounted(() => {
  349. loadBranches()
  350. })
  351. </script>