join.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615
  1. <template>
  2. <div class="login-wrap type--join">
  3. <!-- header -->
  4. <div class="login--header">
  5. <div class="login--header--l">
  6. <div class="logo">
  7. <!-- prettier-ignore -->
  8. SHOPDELI
  9. </div>
  10. </div>
  11. <div class="login--header--r"></div>
  12. </div>
  13. <!-- login -->
  14. <div class="login-box type--join">
  15. <div class="login-r">
  16. <h2 class="mk--title">{{ titleh }}</h2>
  17. <!-- <div class="join--type">
  18. <v-radio-group v-model="form.formValue0" row inline class="custom-radio type2">
  19. <v-radio value="Y" label="인플루언스"></v-radio>
  20. <v-radio value="N" label="벤더"></v-radio>
  21. </v-radio-group>
  22. </div> -->
  23. <div class="tit-login">
  24. <strong>회원가입</strong>
  25. <span><i>*</i>필수입력 항목</span>
  26. </div>
  27. <div v-show="form.formValue0 === 'Y'" class="login-input-wrap">
  28. <div class="txt-field-box">
  29. <v-text-field
  30. :disabled="useStore.getSnsTempData?.ID ? true : false"
  31. v-model="form.formValue1"
  32. placeholder="아이디를 입력해주세요"
  33. class="custom-input"
  34. @blur="checkId"
  35. ></v-text-field>
  36. <v-btn v-if="!useStore.getSnsTempData?.ID" small class="ml-2" @click="checkId"
  37. >중복확인</v-btn
  38. >
  39. </div>
  40. <div class="txt-field-box">
  41. <v-text-field
  42. v-model="form.formValue2"
  43. :type="visible ? 'text' : 'password'"
  44. placeholder="패스워드를 입력해주세요."
  45. class="custom-input"
  46. id="password"
  47. ></v-text-field>
  48. <i
  49. class="ico-eye"
  50. @click.stop="toggleVisibility"
  51. :class="visible ? 'eye-on' : 'eye-off'"
  52. ></i>
  53. <i class="ico"></i>
  54. </div>
  55. <div class="txt-field-box">
  56. <v-text-field
  57. v-model="form.formValue3"
  58. :type="visible ? 'text' : 'password'"
  59. placeholder="패스워드 확인"
  60. class="custom-input"
  61. ></v-text-field>
  62. <i
  63. class="ico-eye"
  64. @click.stop="toggleVisibility"
  65. :class="visible ? 'eye-on' : 'eye-off'"
  66. ></i>
  67. <i class="ico"></i>
  68. </div>
  69. <div class="txt-field-box">
  70. <v-text-field
  71. v-model="form.formValue4"
  72. :maxlength="20"
  73. :counter="20"
  74. :placeholder="'닉네임을 입력해주세요.'"
  75. class="custom-input"
  76. ></v-text-field>
  77. </div>
  78. <div class="txt-field-box">
  79. <v-text-field
  80. v-model="form.formValue5"
  81. :disabled="useStore.getSnsTempData?.NAME ? true : false"
  82. :maxlength="20"
  83. :counter="20"
  84. placeholder="이름을 입력해주세요"
  85. class="custom-input"
  86. ></v-text-field>
  87. </div>
  88. <div class="txt-field-box">
  89. <v-select
  90. v-model="form.formValue6"
  91. :items="form.formValueItems6"
  92. item-title="text"
  93. item-value="value"
  94. class="custom-select"
  95. ></v-select>
  96. </div>
  97. <div class="txt-field-box">
  98. <v-text-field
  99. v-model="form.formValue7"
  100. placeholder="소셜 ID 또는 주소를 입력해주세요."
  101. class="custom-input"
  102. ></v-text-field>
  103. </div>
  104. <div class="txt-field-box phone">
  105. <v-text-field
  106. placeholder=""
  107. class="custom-input"
  108. v-model="form.formValue8"
  109. ></v-text-field>
  110. -
  111. <v-text-field
  112. placeholder="1234"
  113. class="custom-input"
  114. v-model="form.formValue9"
  115. ></v-text-field>
  116. -
  117. <v-text-field
  118. placeholder="5678"
  119. class="custom-input"
  120. v-model="form.formValue10"
  121. ></v-text-field>
  122. </div>
  123. <div class="txt-field-box email">
  124. <v-text-field
  125. v-model="form.formValue12"
  126. :disabled="useStore.getSnsTempData?.EMAIL"
  127. class="custom-input"
  128. placeholder=""
  129. ></v-text-field>
  130. <span v-if="form.formValue11 != 'direct'">@</span>
  131. <v-select
  132. :disabled="useStore.getSnsTempData?.EMAIL ? true : false"
  133. v-model="form.formValue11"
  134. :items="form.formValueItems11"
  135. item-title="text"
  136. item-value="value"
  137. class="custom-select"
  138. ></v-select>
  139. </div>
  140. <div class="txt-field-box">
  141. <v-textarea
  142. v-model="form.formValue13"
  143. placeholder="자기소개를 입력해주세요. 벤더사들이 참고할 수 있도록 작성해주세요."
  144. class="custom-textarea"
  145. rows="3"
  146. ></v-textarea>
  147. </div>
  148. </div>
  149. <div v-show="form.formValue0 === 'N'" class="login-input-wrap">
  150. <div class="txt-field-box">
  151. <v-text-field
  152. v-model="formVendor.formValue1"
  153. placeholder="아이디를 입력해주세요"
  154. class="custom-input"
  155. ></v-text-field>
  156. <v-btn
  157. v-if="!useStore.getSnsTempData?.ID"
  158. small
  159. class="ml-2"
  160. @click="checkIdVendor"
  161. >중복확인</v-btn
  162. >
  163. </div>
  164. <div class="txt-field-box">
  165. <v-text-field
  166. v-model="formVendor.formValue2"
  167. :type="visible ? 'text' : 'password'"
  168. placeholder="패스워드를 입력해주세요."
  169. class="custom-input"
  170. id="password"
  171. ></v-text-field>
  172. <i
  173. class="ico-eye"
  174. @click.stop="toggleVisibility"
  175. :class="visible ? 'eye-on' : 'eye-off'"
  176. ></i>
  177. <i class="ico"></i>
  178. </div>
  179. <div class="txt-field-box">
  180. <v-text-field
  181. v-model="formVendor.formValue3"
  182. :type="visible ? 'text' : 'password'"
  183. placeholder="패스워드 확인"
  184. class="custom-input"
  185. ></v-text-field>
  186. <i
  187. class="ico-eye"
  188. @click.stop="toggleVisibility"
  189. :class="visible ? 'eye-on' : 'eye-off'"
  190. ></i>
  191. <i class="ico"></i>
  192. </div>
  193. <div class="txt-field-box">
  194. <v-text-field
  195. v-model="formVendor.formValue4"
  196. :maxlength="20"
  197. :counter="20"
  198. placeholder="회사명"
  199. class="custom-input"
  200. ></v-text-field>
  201. </div>
  202. <div class="txt-field-box">
  203. <v-text-field
  204. v-model="formVendor.formValue5"
  205. :maxlength="20"
  206. :counter="20"
  207. placeholder="담당자 명"
  208. class="custom-input"
  209. ></v-text-field>
  210. </div>
  211. <div class="txt-field-box phone">
  212. <v-text-field
  213. placeholder=""
  214. class="custom-input"
  215. v-model="formVendor.formValue8"
  216. ></v-text-field>
  217. -
  218. <v-text-field
  219. placeholder="1234"
  220. class="custom-input"
  221. v-model="formVendor.formValue9"
  222. ></v-text-field>
  223. -
  224. <v-text-field
  225. placeholder="5678"
  226. class="custom-input"
  227. v-model="formVendor.formValue10"
  228. ></v-text-field>
  229. </div>
  230. <div class="txt-field-box email">
  231. <v-text-field
  232. v-model="formVendor.formValue12"
  233. class="custom-input"
  234. placeholder=""
  235. ></v-text-field>
  236. <span v-if="formVendor.formValue11 != 'direct'">@</span>
  237. <v-select
  238. v-model="formVendor.formValue11"
  239. :items="formVendor.formValueItems11"
  240. item-title="text"
  241. item-value="value"
  242. class="custom-select"
  243. ></v-select>
  244. </div>
  245. <div class="mt-5 d-flex agree--box">
  246. <v-checkbox class="custom-check type2" v-model="formVendor.formValue13">
  247. <template v-slot:label>개인정보약관동의</template>
  248. </v-checkbox>
  249. <v-checkbox class="custom-check type2" v-model="formVendor.formValue14">
  250. <template v-slot:label>제3자 정보동의</template>
  251. </v-checkbox>
  252. </div>
  253. </div>
  254. <div class="login-btn-wrap">
  255. <v-btn
  256. v-show="form.formValue0 === 'Y'"
  257. class="custom-btn btn-blue"
  258. @click.stop="joinMember('influence')"
  259. >회원가입</v-btn
  260. >
  261. <v-btn
  262. v-show="form.formValue0 === 'N'"
  263. class="custom-btn btn-blue"
  264. @click.stop="joinMember('vendor')"
  265. >회원가입</v-btn
  266. >
  267. </div>
  268. </div>
  269. </div>
  270. <!-- footer -->
  271. <div class="login-footer">
  272. <div class="login--footer--l">
  273. <p>COPYRIGHT@2025 SHOPDELI INC. ALL RIGHTS RESERVED.</p>
  274. <p>마포구 합정동</p>
  275. </div>
  276. </div>
  277. </div>
  278. </template>
  279. <script setup>
  280. /************************
  281. * import
  282. ************************/
  283. import apiUrl from "@/composables/useApi";
  284. import { useI18n } from "vue-i18n";
  285. const { $log, $toast } = useNuxtApp();
  286. /************************
  287. * layout setting
  288. ************************/
  289. definePageMeta({
  290. layout: "loginlayout",
  291. });
  292. /************************
  293. * 전역
  294. ************************/
  295. const titleh = ref("인플루언스");
  296. const useStore = useAuthStore();
  297. const randomString = ref("");
  298. //인플루언서 폼
  299. const form = ref({
  300. formValue0: "Y",
  301. formValue1: useStore.getSnsTempData?.ID || "", // 아이디
  302. formValue2: "", // 패스워드
  303. formValue3: "", // 패스워드 확인
  304. formValue4: "", // 닉네임
  305. formValue5: useStore.getSnsTempData?.NAME || "", // 이름
  306. formValue6: "소셜채널 선택", // 소셜 채널
  307. formValueItems6: [
  308. {
  309. text: "유튜브",
  310. value: "youtube",
  311. },
  312. {
  313. text: "인스타",
  314. value: "instagram",
  315. },
  316. {
  317. text: "네이버 블로그",
  318. value: "naverblog",
  319. },
  320. {
  321. text: "네이버 카페",
  322. value: "navercafe",
  323. },
  324. {
  325. text: "스마트스토어",
  326. value: "smartstore",
  327. },
  328. ], // 소셜 채널 아이템
  329. formValue7: "", // 소셜 ID 또는 주소
  330. formValue8: "010", // 휴대폰1
  331. formValue9: "", // 휴대폰2
  332. formValue10: "", // 휴대폰3
  333. formValue11: "email", // 이메일1
  334. formValueItems11: [
  335. {
  336. text: "이메일 선택",
  337. value: "email",
  338. },
  339. {
  340. text: "직접입력",
  341. value: "direct",
  342. },
  343. {
  344. text: "naver.com",
  345. value: "naver",
  346. },
  347. {
  348. text: "gmail.com",
  349. value: "gmail",
  350. },
  351. {
  352. text: "daum.net",
  353. value: "daum",
  354. },
  355. ], // 이메일 아이템
  356. formValue12: "", // 이메일2
  357. formValue13: "", // 자기소개
  358. });
  359. //밴더 폼
  360. const formVendor = ref({
  361. formValue0: "",
  362. formValue1: "", // 아이디
  363. formValue2: "", // 패스워드
  364. formValue3: "", // 패스워드 확인
  365. formValue4: "", // 닉네임
  366. formValue5: "", // 이름
  367. formValue6: "소셜채널 선택", // 소셜 채널
  368. formValue7: "", // 소셜 ID 또는 주소
  369. formValue8: "010", // 휴대폰1
  370. formValue9: "", // 휴대폰2
  371. formValue10: "", // 휴대폰3
  372. formValue11: "email", // 이메일1
  373. formValueItems11: [
  374. {
  375. text: "이메일 선택",
  376. value: "email",
  377. },
  378. {
  379. text: "직접입력",
  380. value: "direct",
  381. },
  382. {
  383. text: "naver.com",
  384. value: "naver",
  385. },
  386. {
  387. text: "gmail.com",
  388. value: "gmail",
  389. },
  390. {
  391. text: "daum.net",
  392. value: "daum",
  393. },
  394. ], // 이메일 아이템
  395. formValue12: "",
  396. formValue13: "", // 개인정보약관동의
  397. formValue14: "", // 제3자 정보동의
  398. });
  399. /************************
  400. * 함수
  401. ************************/
  402. const generateRandomAlphanumeric = (length) => {
  403. const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
  404. let result = "";
  405. const charsLength = chars.length;
  406. for (let i = 0; i < length; i++) {
  407. result += chars.charAt(Math.floor(Math.random() * charsLength));
  408. }
  409. return result;
  410. };
  411. const checkId = async () => {
  412. if (!form.value.formValue1) {
  413. $toast.error("아이디를 입력해주세요.");
  414. return;
  415. }
  416. // 아이디 형식 검사 (영문, 숫자 조합 6~20자)
  417. const idRegex = /^[a-zA-Z0-9]{6,20}$/;
  418. if (!idRegex.test(form.value.formValue1)) {
  419. $toast.error("아이디는 영문, 숫자 조합 6~20자로 입력해주세요.");
  420. return;
  421. }
  422. try {
  423. const response = await useAxios().post("/auth/checkId", {
  424. ID: form.value.formValue1,
  425. TYPE: "influence",
  426. });
  427. if (response.data.isDuplicate) {
  428. $toast.error("이미 사용중인 아이디입니다.");
  429. } else {
  430. $toast.success("사용 가능한 아이디입니다.");
  431. }
  432. } catch (error) {
  433. console.error("ID check error:", error);
  434. $toast.error("아이디 중복 확인 중 오류가 발생했습니다.");
  435. }
  436. };
  437. const checkIdVendor = async () => {
  438. if (!formVendor.value.formValue1) {
  439. $toast.error("아이디를 입력해주세요.");
  440. return;
  441. }
  442. // 아이디 형식 검사 (영문, 숫자 조합 6~20자)
  443. const idRegex = /^[a-zA-Z0-9]{6,20}$/;
  444. if (!idRegex.test(formVendor.value.formValue1)) {
  445. $toast.error("아이디는 영문, 숫자 조합 6~20자로 입력해주세요.");
  446. return;
  447. }
  448. try {
  449. const response = await useAxios().post("/auth/checkId", {
  450. ID: formVendor.value.formValue1,
  451. TYPE: "vendor",
  452. });
  453. if (response.data.isDuplicate) {
  454. $toast.error("이미 사용중인 아이디입니다.");
  455. } else {
  456. $toast.success("사용 가능한 아이디입니다.");
  457. }
  458. } catch (error) {
  459. console.error("ID check error:", error);
  460. $toast.error("아이디 중복 확인 중 오류가 발생했습니다.");
  461. }
  462. };
  463. // 회원가입 전에 아이디 유효성 검사 추가
  464. const joinMember = async (id_type) => {
  465. if (!useStore.getSnsTempData?.ID) {
  466. if (id_type === "vendor") {
  467. if (!formVendor.value.formValue1) {
  468. $toast.error("아이디를 입력해주세요.");
  469. return;
  470. }
  471. // const idRegex = /^[a-zA-Z0-9]{6,20}$/;
  472. // if (!idRegex.test(form.value.formValue1)) {
  473. // $toast.error("아이디는 영문, 숫자 조합 6~20자로 입력해주세요.");
  474. // return;
  475. // }
  476. } else {
  477. if (!formVendor.value.formValue1) {
  478. $toast.error("아이디를 입력해주세요.");
  479. return;
  480. }
  481. const idRegex = /^[a-zA-Z0-9]{6,20}$/;
  482. if (!idRegex.test(form.value.formValue1)) {
  483. $toast.error("아이디는 영문, 숫자 조합 6~20자로 입력해주세요.");
  484. return;
  485. }
  486. }
  487. }
  488. let _req = "";
  489. let _api = "";
  490. if (id_type === "influence") {
  491. _api = "/auth/joinmember";
  492. _req = {
  493. ID: form.value.formValue1,
  494. PASSWORD: form.value.formValue2,
  495. NAME: form.value.formValue5,
  496. NICK_NAME: form.value.formValue4 || "", //닉네임 없으면 빈문자
  497. PHONE: `${form.value.formValue8}-${form.value.formValue9}-${form.value.formValue10}`,
  498. EMAIL: form.value.formValue12,
  499. SNS_TYPE: form.value.formValue6,
  500. SNS_LINK_ID: form.value.formValue7,
  501. ADD_INFO1: form.value.formValue13,
  502. GOOGLE_REFRESH_TOKEN: useStore.getSnsTempData?.GOOGLE_REFRESH_TOKEN || "",
  503. TYPE: useStore.getSnsTempData ? "1" : "0", // SNS 가입일경우 1, 일반회원 가입일경우 0
  504. };
  505. } else {
  506. _api = "/auth/joinvendor";
  507. _req = {
  508. ID: formVendor.value.formValue1,
  509. PASSWORD: formVendor.value.formValue3,
  510. COMPANY_NAME: formVendor.value.formValue4,
  511. COMPANY_NUMBER: (randomString.value = generateRandomAlphanumeric(100)),
  512. NAME: formVendor.value.formValue5,
  513. HP: `${formVendor.value.formValue8}-${formVendor.value.formValue9}-${formVendor.value.formValue10}`,
  514. EMAIL: formVendor.value.formValue12,
  515. };
  516. }
  517. useAxios()
  518. .post(_api, _req)
  519. .then((res) => {
  520. if (_req.TYPE === "1") {
  521. // SNS 가입일 경우
  522. useStore.setTempData("");
  523. useUtil.setPageMove("/?type=influence");
  524. return;
  525. }
  526. if (form.value.formValue0 === "Y") {
  527. useUtil.setPageMove("/?type=influence");
  528. } else {
  529. useUtil.setPageMove("/?type=vendor");
  530. }
  531. })
  532. .catch((error) => {
  533. if (error.response) {
  534. console.log("status:", error.response.status, "data:", error.response.data);
  535. // 안전하게 errCode, message 접근
  536. const errData = error.response.data || {};
  537. const errCode = errData.errCode || errData.errorCode || errData.code || "";
  538. const errMsg = errData.message || "알 수 없는 오류가 발생했습니다.";
  539. console.log("errCode:", errCode, "message:", errMsg);
  540. } else {
  541. console.log("error:", error.message, error.code);
  542. }
  543. if (error.response?.status) {
  544. fnLoginSet(error.response.data.messages.message);
  545. }
  546. $log.debug("[join][fnIdPwCheck][error]");
  547. })
  548. .finally(() => {
  549. $log.debug("[join][fnIdPwCheck][finished]");
  550. });
  551. };
  552. /************************
  553. * 마운트
  554. ************************/
  555. onMounted(() => {
  556. const route = useRoute();
  557. const typeParam = route.query.type;
  558. typeParam === "influence"
  559. ? (form.value.formValue0 = "Y")
  560. : (form.value.formValue0 = "N");
  561. if (useStore.getSnsTempData?.EMAIL) {
  562. form.value.formValue12 = useStore.getSnsTempData.EMAIL;
  563. form.value.formValue11 = "direct"; // 이메일 직접입력으로 설정
  564. }
  565. });
  566. watch(
  567. () => form.value.formValue0,
  568. (newValue) => {
  569. if (newValue === "Y") {
  570. titleh.value = "인플루언서";
  571. } else {
  572. titleh.value = "벤더";
  573. }
  574. }
  575. );
  576. </script>