join.vue 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988
  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"></h2> -->
  17. <div class="tit-login">
  18. <strong>{{ titleh }} 회원가입</strong>
  19. <!-- <span><i>*</i>필수입력 항목</span> -->
  20. </div>
  21. <div v-show="form.formValue0 === 'Y'" class="login-input-wrap">
  22. <div class="txt-field-box">
  23. <v-text-field
  24. :disabled="useStore.getSnsTempData?.ID ? true : false"
  25. v-model="form.formValue1"
  26. placeholder="아이디를 입력해주세요"
  27. class="custom-input"
  28. @blur="checkId"
  29. ></v-text-field>
  30. <v-btn v-if="!useStore.getSnsTempData?.ID" class="ml-2 custom-btn mini btn-purple h--60" @click="checkId"
  31. >중복확인</v-btn
  32. >
  33. </div>
  34. <div class="txt-field-box">
  35. <v-text-field
  36. v-model="form.formValue2"
  37. :type="visible ? 'text' : 'password'"
  38. placeholder="비밀번호를 입력해주세요."
  39. class="custom-input"
  40. id="password"
  41. ></v-text-field>
  42. <i
  43. class="ico-eye"
  44. @click.stop="toggleVisibility"
  45. :class="visible ? 'eye-on' : 'eye-off'"
  46. ></i>
  47. <i class="ico"></i>
  48. </div>
  49. <div class="txt-field-box">
  50. <v-text-field
  51. v-model="form.formValue3"
  52. :type="visible ? 'text' : 'password'"
  53. placeholder="비밀번호를 한 번 더 입력해주세요."
  54. class="custom-input"
  55. ></v-text-field>
  56. <i
  57. class="ico-eye"
  58. @click.stop="toggleVisibility"
  59. :class="visible ? 'eye-on' : 'eye-off'"
  60. ></i>
  61. <i class="ico"></i>
  62. </div>
  63. <div class="txt-field-box">
  64. <v-text-field
  65. v-model="form.formValue4"
  66. :maxlength="20"
  67. :counter="20"
  68. :placeholder="'닉네임을 입력해주세요.'"
  69. class="custom-input"
  70. ></v-text-field>
  71. </div>
  72. <div class="txt-field-box">
  73. <v-text-field
  74. v-model="form.formValue5"
  75. :disabled="useStore.getSnsTempData?.NAME ? true : false"
  76. :maxlength="20"
  77. :counter="20"
  78. placeholder="이름을 입력해주세요"
  79. class="custom-input"
  80. ></v-text-field>
  81. </div>
  82. <div class="txt-field-box">
  83. <v-select
  84. v-model="form.formValue6"
  85. variant="outlined"
  86. :items="form.formValueItems6"
  87. item-title="text"
  88. item-value="value"
  89. label="소셜 채널 선택"
  90. class="custom-select border--type"
  91. ></v-select>
  92. </div>
  93. <div class="txt-field-box">
  94. <v-text-field
  95. v-model="form.formValue7"
  96. placeholder="소셜 ID 또는 주소를 입력해주세요."
  97. class="custom-input"
  98. ></v-text-field>
  99. </div>
  100. <div class="txt-field-box phone">
  101. <v-text-field
  102. placeholder=""
  103. class="custom-input"
  104. v-model="form.formValue8"
  105. :maxlength="4"
  106. ></v-text-field>
  107. -
  108. <v-text-field
  109. placeholder="1234"
  110. class="custom-input"
  111. v-model="form.formValue9"
  112. :maxlength="4"
  113. ></v-text-field>
  114. -
  115. <v-text-field
  116. placeholder="5678"
  117. class="custom-input"
  118. v-model="form.formValue10"
  119. :maxlength="4"
  120. ></v-text-field>
  121. </div>
  122. <div class="txt-field-box email">
  123. <v-text-field
  124. v-model="form.formValue12"
  125. :disabled="useStore.getSnsTempData?.EMAIL"
  126. class="custom-input"
  127. placeholder=""
  128. ></v-text-field>
  129. <span>@</span>
  130. <v-combobox
  131. variant="outlined"
  132. :disabled="useStore.getSnsTempData?.EMAIL ? true : false"
  133. v-model="form.formValue11"
  134. :items="['gmail.com', 'naver.com', 'daum.net', 'hotmail.com', 'yahoo.com']"
  135. placeholder="도메인 선택 또는 직접 입력"
  136. class="custom-select border--type"
  137. ></v-combobox>
  138. </div>
  139. <div class="d-flex agree--box">
  140. <v-checkbox hide-details class="custom-check type2" v-model="formVendor.formValue13" @click="handlePrivacyModal">
  141. <template v-slot:label>개인정보약관동의(필수)</template>
  142. </v-checkbox>
  143. <v-checkbox hide-details class="custom-check type2" v-model="formVendor.formValue14" @click="handleThirdPartyModal">
  144. <template v-slot:label>제3자 정보동의(선택)</template>
  145. </v-checkbox>
  146. </div>
  147. <!-- <div class="txt-field-box">
  148. <v-textarea
  149. v-model="form.formValue13"
  150. placeholder="자기소개를 입력해주세요. 벤더사들이 참고할 수 있도록 작성해주세요."
  151. class="custom-textarea"
  152. rows="3"
  153. ></v-textarea>
  154. </div> -->
  155. </div>
  156. <div v-show="form.formValue0 === 'N'" class="login-input-wrap">
  157. <div class="txt-field-box">
  158. <v-text-field
  159. v-model="formVendor.formValue1"
  160. placeholder="아이디를 입력해주세요."
  161. class="custom-input"
  162. ></v-text-field>
  163. <v-btn
  164. v-if="!useStore.getSnsTempData?.ID"
  165. class="ml-2 custom-btn mini h--60 btn-purple"
  166. @click="checkIdVendor"
  167. >중복확인</v-btn
  168. >
  169. </div>
  170. <div class="txt-field-box">
  171. <v-text-field
  172. v-model="formVendor.formValue2"
  173. :type="visible ? 'text' : 'password'"
  174. placeholder="패스워드를 입력해주세요."
  175. class="custom-input"
  176. id="password"
  177. ></v-text-field>
  178. <i
  179. class="ico-eye"
  180. @click.stop="toggleVisibility"
  181. :class="visible ? 'eye-on' : 'eye-off'"
  182. ></i>
  183. <i class="ico"></i>
  184. </div>
  185. <div class="txt-field-box">
  186. <v-text-field
  187. v-model="formVendor.formValue3"
  188. :type="visible ? 'text' : 'password'"
  189. placeholder="패스워드를 한 번 더 입력해주세요."
  190. class="custom-input"
  191. ></v-text-field>
  192. <i
  193. class="ico-eye"
  194. @click.stop="toggleVisibility"
  195. :class="visible ? 'eye-on' : 'eye-off'"
  196. ></i>
  197. <i class="ico"></i>
  198. </div>
  199. <div class="txt-field-box">
  200. <v-text-field
  201. v-model="formVendor.formValue4"
  202. :maxlength="20"
  203. :counter="20"
  204. placeholder="회사명을 입력해주세요."
  205. class="custom-input"
  206. ></v-text-field>
  207. </div>
  208. <div class="txt-field-box">
  209. <v-text-field
  210. v-model="formVendor.formValue5"
  211. :maxlength="20"
  212. :counter="20"
  213. placeholder="담당자명을 입력해주세요."
  214. class="custom-input"
  215. ></v-text-field>
  216. </div>
  217. <div class="txt-field-box phone">
  218. <v-text-field
  219. placeholder=""
  220. class="custom-input"
  221. v-model="formVendor.formValue8"
  222. :maxlength="4"
  223. ></v-text-field>
  224. -
  225. <v-text-field
  226. placeholder="1234"
  227. class="custom-input"
  228. v-model="formVendor.formValue9"
  229. :maxlength="4"
  230. ></v-text-field>
  231. -
  232. <v-text-field
  233. placeholder="5678"
  234. class="custom-input"
  235. v-model="formVendor.formValue10"
  236. :maxlength="4"
  237. ></v-text-field>
  238. </div>
  239. <div class="txt-field-box email">
  240. <v-text-field
  241. v-model="formVendor.formValue12"
  242. class="custom-input"
  243. placeholder=""
  244. ></v-text-field>
  245. <span>@</span>
  246. <v-combobox
  247. variant="outlined"
  248. v-model="formVendor.formValue11"
  249. :items="['gmail.com', 'naver.com', 'daum.net', 'hotmail.com', 'yahoo.com']"
  250. placeholder="도메인 선택 또는 직접 입력"
  251. class="custom-select border--type"
  252. ></v-combobox>
  253. </div>
  254. <div class="mt-5 d-flex agree--box">
  255. <v-checkbox class="custom-check type2"
  256. hide-details v-model="formVendor.formValue13" @click="handlePrivacyModal">
  257. <template v-slot:label>개인정보약관동의</template>
  258. </v-checkbox>
  259. <v-checkbox class="custom-check type2"
  260. hide-details v-model="formVendor.formValue14" @click="handleThirdPartyModal">
  261. <template v-slot:label>제3자 정보동의</template>
  262. </v-checkbox>
  263. </div>
  264. </div>
  265. <div class="login-btn-wrap">
  266. <v-btn
  267. v-show="form.formValue0 === 'Y'"
  268. class="custom-btn btn-blue"
  269. @click.stop="joinMember('influence')"
  270. >회원가입</v-btn
  271. >
  272. <v-btn
  273. v-show="typeParam === 'vendor'"
  274. class="custom-btn btn-blue"
  275. @click.stop="joinMember('vendor')"
  276. >회원가입</v-btn
  277. >
  278. <v-btn
  279. v-show="typeParam === 'brand'"
  280. class="custom-btn btn-blue"
  281. @click.stop="joinMember('brand')"
  282. >회원가입</v-btn
  283. >
  284. </div>
  285. </div>
  286. </div>
  287. <!-- footer -->
  288. <div class="login-footer">
  289. <div class="login--footer--l">
  290. <p>COPYRIGHT@2025 SHOPDELI INC. ALL RIGHTS RESERVED.</p>
  291. <p>마포구 합정동</p>
  292. </div>
  293. </div>
  294. <!-- 개인정보 약관 모달 -->
  295. <div v-if="showPrivacyModal" class="modal-overlay" @click="closePrivacyModal">
  296. <div class="modal-content" @click.stop>
  297. <div class="modal-header">
  298. <h3>개인정보 수집 및 이용 동의</h3>
  299. <button class="modal-close-btn" @click="closePrivacyModal">×</button>
  300. </div>
  301. <div class="modal-body">
  302. <div class="privacy-content">
  303. <h4>1. 개인정보 수집 목적</h4>
  304. <p>서비스 제공, 회원 관리, 고객 상담, 마케팅 활용</p>
  305. <h4>2. 수집하는 개인정보 항목</h4>
  306. <p>필수항목: 이름, 이메일, 전화번호, 회원구분</p>
  307. <p>선택항목: 닉네임, SNS 정보</p>
  308. <h4>3. 개인정보 보유 및 이용기간</h4>
  309. <p>회원 탈퇴 시까지 또는 법정 보존기간</p>
  310. <h4>4. 개인정보 제3자 제공</h4>
  311. <p>원칙적으로 개인정보를 제3자에게 제공하지 않습니다. 단, 법령에 의한 경우는 예외로 합니다.</p>
  312. <h4>5. 개인정보 처리 위탁</h4>
  313. <p>서비스 운영을 위해 필요한 경우 개인정보 처리를 위탁할 수 있습니다.</p>
  314. <h4>6. 개인정보 보호책임자</h4>
  315. <p>개인정보 보호책임자: 홍길동 (privacy@company.com)</p>
  316. <p class="privacy-notice">
  317. 위의 개인정보 수집 및 이용에 대한 동의를 거부할 권리가 있습니다.
  318. 그러나 동의를 거부할 경우 서비스 이용이 제한될 수 있습니다.
  319. </p>
  320. </div>
  321. </div>
  322. <div class="modal-footer">
  323. <button class="btn-disagree" @click="disagreePrivacy">미동의</button>
  324. <button class="btn-agree" @click="agreePrivacy">동의</button>
  325. </div>
  326. </div>
  327. </div>
  328. <!-- 제3자 정보 제공 동의 모달 -->
  329. <div v-if="showThirdPartyModal" class="modal-overlay" @click="closeThirdPartyModal">
  330. <div class="modal-content" @click.stop>
  331. <div class="modal-header">
  332. <h3>제3자 정보 제공 동의</h3>
  333. <button class="modal-close-btn" @click="closeThirdPartyModal">×</button>
  334. </div>
  335. <div class="modal-body">
  336. <div class="privacy-content">
  337. <h4>1. 제공받는 자</h4>
  338. <p>협력업체, 마케팅 대행사</p>
  339. <h4>2. 제공하는 개인정보 항목</h4>
  340. <p>이름, 이메일, 전화번호, 관심사</p>
  341. <h4>3. 제공 목적</h4>
  342. <p>맞춤형 광고, 이벤트 정보 제공, 협력업체 서비스 안내</p>
  343. <h4>4. 보유 및 이용기간</h4>
  344. <p>제공 목적 달성 시까지 또는 동의 철회 시까지</p>
  345. <p class="privacy-notice">
  346. 제3자 정보 제공에 대한 동의는 선택사항이며, 동의하지 않아도 기본 서비스 이용에는 제한이 없습니다.
  347. </p>
  348. </div>
  349. </div>
  350. <div class="modal-footer">
  351. <button class="btn-disagree" @click="disagreeThirdParty">미동의</button>
  352. <button class="btn-agree" @click="agreeThirdParty">동의</button>
  353. </div>
  354. </div>
  355. </div>
  356. </div>
  357. </template>
  358. <script setup>
  359. /************************
  360. * import
  361. ************************/
  362. const { $log, $toast, $eventBus } = useNuxtApp();
  363. /************************
  364. * layout setting
  365. ************************/
  366. definePageMeta({
  367. layout: "loginlayout",
  368. });
  369. /************************
  370. * 전역
  371. ************************/
  372. const titleh = ref("인플루언서");
  373. const useStore = useAuthStore();
  374. const randomString = ref("");
  375. // 모달 상태
  376. const showPrivacyModal = ref(false);
  377. const showThirdPartyModal = ref(false);
  378. // 중복확인 상태
  379. const isIdChecked = ref(false);
  380. const isIdCheckPassedInfluencer = ref(false);
  381. const isIdCheckPassedVendor = ref(false);
  382. // 비밀번호 표시/숨김 상태
  383. const visible = ref(false);
  384. //인플루언서 폼
  385. const form = ref({
  386. formValue0: "Y",
  387. formValue1: useStore.getSnsTempData?.ID || "", // 아이디
  388. formValue2: "", // 패스워드
  389. formValue3: "", // 패스워드 확인
  390. formValue4: "", // 닉네임
  391. formValue5: useStore.getSnsTempData?.NAME || "", // 이름
  392. formValue6: null, // 소셜 채널
  393. formValueItems6: [
  394. {
  395. text: "유튜브",
  396. value: "youtube",
  397. },
  398. {
  399. text: "인스타그램",
  400. value: "instagram",
  401. },
  402. {
  403. text: "네이버 블로그",
  404. value: "naverblog",
  405. },
  406. ], // 소셜 채널 아이템
  407. formValue7: "", // 소셜 ID 또는 주소
  408. formValue8: "010", // 휴대폰1
  409. formValue9: "", // 휴대폰2
  410. formValue10: "", // 휴대폰3
  411. formValue11: null, // 이메일 도메인 (combobox)
  412. formValue12: "", // 이메일2
  413. formValue13: "", // 자기소개
  414. });
  415. //밴더 폼
  416. const formVendor = ref({
  417. formValue0: "",
  418. formValue1: "", // 아이디
  419. formValue2: "", // 패스워드
  420. formValue3: "", // 패스워드 확인
  421. formValue4: "", // 닉네임
  422. formValue5: "", // 이름
  423. formValue6: "", // 소셜 채널
  424. formValue7: "", // 소셜 ID 또는 주소
  425. formValue8: "010", // 휴대폰1
  426. formValue9: "", // 휴대폰2
  427. formValue10: "", // 휴대폰3
  428. formValue11: null, // 이메일 도메인 (combobox)
  429. formValue12: "",
  430. formValue13: "", // 개인정보약관동의
  431. formValue14: "", // 제3자 정보동의
  432. });
  433. /************************
  434. * 함수
  435. ************************/
  436. const generateRandomAlphanumeric = (length) => {
  437. const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
  438. let result = "";
  439. const charsLength = chars.length;
  440. for (let i = 0; i < length; i++) {
  441. result += chars.charAt(Math.floor(Math.random() * charsLength));
  442. }
  443. return result;
  444. };
  445. // 비밀번호 표시/숨김 토글
  446. const toggleVisibility = () => {
  447. visible.value = !visible.value;
  448. };
  449. const checkId = async () => {
  450. if (!form.value.formValue1) {
  451. $toast.error("아이디를 입력해주세요.");
  452. return;
  453. }
  454. // 아이디 형식 검사 (영문, 숫자 6~20자)
  455. const idRegex = /^[a-zA-Z0-9]{6,20}$/;
  456. if (!idRegex.test(form.value.formValue1)) {
  457. $toast.error("아이디는 영문, 숫자 6~20자로 입력해주세요.");
  458. return;
  459. }
  460. try {
  461. const response = await useAxios().post("/auth/checkId", {
  462. id: form.value.formValue1,
  463. type: "influence",
  464. });
  465. if (response.data.isDuplicate) {
  466. $toast.error("이미 사용중인 아이디입니다.");
  467. isIdCheckPassedInfluencer.value = false;
  468. } else {
  469. $toast.success("사용 가능한 아이디입니다.");
  470. isIdCheckPassedInfluencer.value = true;
  471. }
  472. } catch (error) {
  473. console.error("ID check error:", error);
  474. $toast.error("아이디 중복 확인 중 오류가 발생했습니다.");
  475. isIdCheckPassedInfluencer.value = false;
  476. }
  477. };
  478. const checkIdVendor = async () => {
  479. if (!formVendor.value.formValue1) {
  480. $toast.error("아이디를 입력해주세요.");
  481. return;
  482. }
  483. // 아이디 형식 검사 (영문, 숫자 6~20자)
  484. const idRegex = /^[a-zA-Z0-9]{6,20}$/;
  485. if (!idRegex.test(formVendor.value.formValue1)) {
  486. $toast.error("아이디는 영문, 숫자 6~20자로 입력해주세요.");
  487. return;
  488. }
  489. try {
  490. const response = await useAxios().post("/auth/checkId", {
  491. id: formVendor.value.formValue1,
  492. type: typeParam.value,
  493. });
  494. if (response.data.isDuplicate) {
  495. $toast.error("이미 사용중인 아이디입니다.");
  496. isIdCheckPassedVendor.value = false;
  497. } else {
  498. $toast.success("사용 가능한 아이디입니다.");
  499. isIdCheckPassedVendor.value = true;
  500. }
  501. } catch (error) {
  502. console.error("ID check error:", error);
  503. $toast.error("아이디 중복 확인 중 오류가 발생했습니다.");
  504. isIdCheckPassedVendor.value = false;
  505. }
  506. };
  507. // 회원가입 전에 유효성 검사
  508. const joinMember = async (id_type) => {
  509. // 필수값 검사
  510. if (id_type === "influence") {
  511. // 인플루언서 필수값 검사
  512. if (!form.value.formValue1) {
  513. $toast.error("아이디를 입력해주세요.");
  514. return;
  515. }
  516. if (!form.value.formValue2) {
  517. $toast.error("비밀번호를 입력해주세요.");
  518. return;
  519. }
  520. if (!form.value.formValue3) {
  521. $toast.error("비밀번호 확인을 입력해주세요.");
  522. return;
  523. }
  524. if (form.value.formValue2 !== form.value.formValue3) {
  525. $toast.error("비밀번호가 일치하지 않습니다.");
  526. return;
  527. }
  528. if (!form.value.formValue4) {
  529. $toast.error("닉네임을 입력해주세요.");
  530. return;
  531. }
  532. if (!form.value.formValue5) {
  533. $toast.error("이름을 입력해주세요.");
  534. return;
  535. }
  536. if (!form.value.formValue8 || !form.value.formValue9 || !form.value.formValue10) {
  537. $toast.error("연락처를 입력해주세요.");
  538. return;
  539. }
  540. if (!form.value.formValue12) {
  541. $toast.error("이메일을 입력해주세요.");
  542. return;
  543. }
  544. // 개인정보약관동의 필수 체크
  545. if (!formVendor.value.formValue13) {
  546. $toast.error("개인정보약관동의는 필수입니다.");
  547. return;
  548. }
  549. // SNS 가입이 아닌 경우에만 아이디 중복확인 체크
  550. if (!useStore.getSnsTempData?.ID && !isIdCheckPassedInfluencer.value) {
  551. $toast.error("아이디 중복확인을 먼저 해주세요.");
  552. return;
  553. }
  554. } else if (id_type === "vendor" || id_type === "brand") {
  555. // 벤더/브랜드 필수값 검사
  556. if (!formVendor.value.formValue1) {
  557. $toast.error("아이디를 입력해주세요.");
  558. return;
  559. }
  560. if (!formVendor.value.formValue2) {
  561. $toast.error("비밀번호를 입력해주세요.");
  562. return;
  563. }
  564. if (!formVendor.value.formValue3) {
  565. $toast.error("비밀번호 확인을 입력해주세요.");
  566. return;
  567. }
  568. if (formVendor.value.formValue2 !== formVendor.value.formValue3) {
  569. $toast.error("비밀번호가 일치하지 않습니다.");
  570. return;
  571. }
  572. if (!formVendor.value.formValue4) {
  573. $toast.error("회사명을 입력해주세요.");
  574. return;
  575. }
  576. if (!formVendor.value.formValue5) {
  577. $toast.error("담당자명을 입력해주세요.");
  578. return;
  579. }
  580. if (!formVendor.value.formValue8 || !formVendor.value.formValue9 || !formVendor.value.formValue10) {
  581. $toast.error("연락처를 입력해주세요.");
  582. return;
  583. }
  584. if (!formVendor.value.formValue12) {
  585. $toast.error("이메일을 입력해주세요.");
  586. return;
  587. }
  588. // 개인정보약관동의 필수 체크
  589. if (!formVendor.value.formValue13) {
  590. $toast.error("개인정보약관동의는 필수입니다.");
  591. return;
  592. }
  593. // SNS 가입이 아닌 경우에만 아이디 중복확인 체크
  594. if (!useStore.getSnsTempData?.ID && !isIdCheckPassedVendor.value) {
  595. $toast.error("아이디 중복확인을 먼저 해주세요.");
  596. return;
  597. }
  598. }
  599. let param = {
  600. id: "회원가입",
  601. title: "회원가입",
  602. content: "회원가입을 진행하시겠습니까?",
  603. yes: {
  604. text: "확인",
  605. isProc: true,
  606. param: "",
  607. },
  608. no: {
  609. text: "취소",
  610. isProc: false,
  611. },
  612. };
  613. // 현재 회원가입 타입 저장
  614. currentJoinType.value = id_type;
  615. $eventBus.emit("OPEN_CONFIRM_POP_UP", param);
  616. return;
  617. };
  618. // 실제 회원가입 처리 함수
  619. const processJoinMember = async (id_type) => {
  620. let _req = "";
  621. let _api = "";
  622. if (id_type === "influence") {
  623. _api = "/auth/joinmember";
  624. _req = {
  625. ID: form.value.formValue1,
  626. PASSWORD: form.value.formValue2,
  627. NAME: form.value.formValue5,
  628. NICK_NAME: form.value.formValue4 || "", //닉네임 없으면 빈문자
  629. PHONE: `${form.value.formValue8}-${form.value.formValue9}-${form.value.formValue10}`,
  630. EMAIL: `${form.value.formValue12}@${form.value.formValue11 || ''}`,
  631. SNS_TYPE: form.value.formValue6,
  632. SNS_LINK_ID: form.value.formValue7,
  633. ADD_INFO1: form.value.formValue13,
  634. PRIVACY_AGREE: formVendor.value.formValue13 ? "Y" : "N", // 개인정보약관동의
  635. THIRDPARTY_AGREE: formVendor.value.formValue14 ? "Y" : "N", // 제3자 정보동의
  636. GOOGLE_REFRESH_TOKEN: useStore.getSnsTempData?.GOOGLE_REFRESH_TOKEN || "",
  637. TYPE: useStore.getSnsTempData ? "1" : "0", // SNS 가입일경우 1, 일반회원 가입일경우 0
  638. };
  639. } else if (id_type === "vendor") {
  640. _api = "/auth/joinvendor";
  641. _req = {
  642. ID: formVendor.value.formValue1,
  643. PASSWORD: formVendor.value.formValue3,
  644. COMPANY_NAME: formVendor.value.formValue4,
  645. COMPANY_NUMBER: (randomString.value = generateRandomAlphanumeric(100)),
  646. NAME: formVendor.value.formValue5,
  647. HP: `${formVendor.value.formValue8}-${formVendor.value.formValue9}-${formVendor.value.formValue10}`,
  648. EMAIL: `${formVendor.value.formValue12}@${formVendor.value.formValue11 || ''}`,
  649. PRIVACY_AGREE: formVendor.value.formValue13 ? "Y" : "N", // 개인정보약관동의
  650. THIRDPARTY_AGREE: formVendor.value.formValue14 ? "Y" : "N", // 제3자 정보동의
  651. }
  652. } else {
  653. _api = "/auth/joinbrand";
  654. _req = {
  655. ID: formVendor.value.formValue1,
  656. PASSWORD: formVendor.value.formValue3,
  657. COMPANY_NAME: formVendor.value.formValue4,
  658. COMPANY_NUMBER: (randomString.value = generateRandomAlphanumeric(100)),
  659. NAME: formVendor.value.formValue5,
  660. HP: `${formVendor.value.formValue8}-${formVendor.value.formValue9}-${formVendor.value.formValue10}`,
  661. EMAIL: `${formVendor.value.formValue12}@${formVendor.value.formValue11 || ''}`,
  662. PRIVACY_AGREE: formVendor.value.formValue13 ? "Y" : "N", // 개인정보약관동의
  663. THIRDPARTY_AGREE: formVendor.value.formValue14 ? "Y" : "N", // 제3자 정보동의
  664. };
  665. }
  666. useAxios()
  667. .post(_api, _req)
  668. .then((res) => {
  669. $toast.success("회원가입이 완료되었습니다!");
  670. if (_req.TYPE === "1") {
  671. // SNS 가입일 경우
  672. useStore.setTempData("");
  673. setTimeout(() => {
  674. useUtil.setPageMove("/");
  675. }, 1500);
  676. return;
  677. }
  678. // 일반 회원가입 완료 후 메인페이지로 이동
  679. setTimeout(() => {
  680. useUtil.setPageMove("/");
  681. }, 1500);
  682. })
  683. .catch((error) => {
  684. if (error.response) {
  685. //console.log("status:", error.response.status, "data:", error.response.data);
  686. // 안전하게 errCode, message 접근
  687. const errData = error.response.data || {};
  688. const errCode = errData.errCode || errData.errorCode || errData.code || "";
  689. const errMsg = errData.message || "알 수 없는 오류가 발생했습니다.";
  690. //console.log("errCode:", errCode, "message:", errMsg);
  691. } else {
  692. //console.log("error:", error.message, error.code);
  693. }
  694. if (error.response?.status) {
  695. fnLoginSet(error.response.data.messages.message);
  696. }
  697. $log.debug("[join][fnIdPwCheck][error]");
  698. })
  699. .finally(() => {
  700. $log.debug("[join][fnIdPwCheck][finished]");
  701. });
  702. };
  703. // 현재 회원가입 타입을 저장하는 변수
  704. let currentJoinType = ref('');
  705. // 개인정보 약관 모달 함수들
  706. const handlePrivacyModal = (event) => {
  707. event.preventDefault();
  708. showPrivacyModal.value = true;
  709. // 체크 상태 초기화 (모달에서 동의해야만 체크됨)
  710. formVendor.value.formValue13 = false;
  711. };
  712. const closePrivacyModal = () => {
  713. showPrivacyModal.value = false;
  714. };
  715. const agreePrivacy = () => {
  716. formVendor.value.formValue13 = true;
  717. showPrivacyModal.value = false;
  718. $toast.success('개인정보 수집 및 이용에 동의했습니다.');
  719. };
  720. const disagreePrivacy = () => {
  721. formVendor.value.formValue13 = false;
  722. showPrivacyModal.value = false;
  723. $toast.info('개인정보 수집 및 이용에 동의하지 않았습니다.');
  724. };
  725. // 제3자 정보 제공 모달 함수들
  726. const handleThirdPartyModal = (event) => {
  727. event.preventDefault();
  728. showThirdPartyModal.value = true;
  729. // 체크 상태 초기화 (모달에서 동의해야만 체크됨)
  730. formVendor.value.formValue14 = false;
  731. };
  732. const closeThirdPartyModal = () => {
  733. showThirdPartyModal.value = false;
  734. };
  735. const agreeThirdParty = () => {
  736. formVendor.value.formValue14 = true;
  737. showThirdPartyModal.value = false;
  738. $toast.success('제3자 정보 제공에 동의했습니다.');
  739. };
  740. const disagreeThirdParty = () => {
  741. formVendor.value.formValue14 = false;
  742. showThirdPartyModal.value = false;
  743. $toast.info('제3자 정보 제공에 동의하지 않았습니다.');
  744. };
  745. /************************
  746. * 반응형 변수
  747. ************************/
  748. const route = useRoute();
  749. const typeParam = ref(route.query.type);
  750. /************************
  751. * 마운트
  752. ************************/
  753. // 아이디 변경 시 중복확인 상태 초기화
  754. watch(() => form.value.formValue1, () => {
  755. isIdCheckPassedInfluencer.value = false;
  756. });
  757. watch(() => formVendor.value.formValue1, () => {
  758. isIdCheckPassedVendor.value = false;
  759. });
  760. onMounted(() => {
  761. if(typeParam.value === "influence"){
  762. (form.value.formValue0 = "Y");
  763. titleh.value = "인플루언서"
  764. } else if(typeParam.value === "vendor"){
  765. (form.value.formValue0 = "N") ;
  766. titleh.value = "벤더사"
  767. } else {
  768. (form.value.formValue0 = "N") ;
  769. titleh.value = "브랜드사"
  770. }
  771. if (useStore.getSnsTempData?.EMAIL) {
  772. form.value.formValue12 = useStore.getSnsTempData.EMAIL;
  773. form.value.formValue11 = "direct"; // 이메일 직접입력으로 설정
  774. }
  775. // 팝업 확인 이벤트 리스너
  776. $eventBus.off('회원가입_PROC_YES');
  777. $eventBus.on('회원가입_PROC_YES', () => {
  778. processJoinMember(currentJoinType.value);
  779. });
  780. });
  781. </script>
  782. <style scoped>
  783. /* 모달 스타일 */
  784. .modal-overlay {
  785. position: fixed;
  786. top: 0;
  787. left: 0;
  788. right: 0;
  789. bottom: 0;
  790. background: rgba(0, 0, 0, 0.5);
  791. display: flex;
  792. align-items: center;
  793. justify-content: center;
  794. z-index: 1000;
  795. }
  796. .modal-content {
  797. background: white;
  798. border-radius: 12px;
  799. width: 90%;
  800. max-width: 600px;
  801. max-height: 80vh;
  802. overflow: hidden;
  803. box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
  804. }
  805. .modal-header {
  806. display: flex;
  807. justify-content: space-between;
  808. align-items: center;
  809. padding: 20px 24px;
  810. border-bottom: 1px solid #e0e0e0;
  811. background: #f8f9fa;
  812. }
  813. .modal-header h3 {
  814. margin: 0;
  815. color: #333;
  816. font-size: 1.2rem;
  817. }
  818. .modal-close-btn {
  819. background: none;
  820. border: none;
  821. font-size: 24px;
  822. cursor: pointer;
  823. color: #666;
  824. padding: 0;
  825. width: 30px;
  826. height: 30px;
  827. display: flex;
  828. align-items: center;
  829. justify-content: center;
  830. }
  831. .modal-close-btn:hover {
  832. color: #333;
  833. }
  834. .modal-body {
  835. padding: 24px;
  836. max-height: 50vh;
  837. overflow-y: auto;
  838. }
  839. .privacy-content h4 {
  840. margin: 20px 0 8px 0;
  841. color: #333;
  842. font-size: 1rem;
  843. font-weight: 600;
  844. }
  845. .privacy-content h4:first-child {
  846. margin-top: 0;
  847. }
  848. .privacy-content p {
  849. margin: 8px 0;
  850. color: #666;
  851. line-height: 1.5;
  852. }
  853. .privacy-notice {
  854. background: #f8f9fa;
  855. padding: 12px;
  856. border-radius: 6px;
  857. margin-top: 20px;
  858. font-size: 14px;
  859. color: #555;
  860. }
  861. .modal-footer {
  862. display: flex;
  863. gap: 12px;
  864. padding: 20px 24px;
  865. border-top: 1px solid #e0e0e0;
  866. background: #f8f9fa;
  867. justify-content: flex-end;
  868. }
  869. .btn-agree {
  870. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  871. color: white;
  872. border: none;
  873. padding: 12px 24px;
  874. border-radius: 8px;
  875. cursor: pointer;
  876. font-weight: 500;
  877. transition: all 0.3s ease;
  878. }
  879. .btn-agree:hover {
  880. transform: translateY(-2px);
  881. box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
  882. }
  883. .btn-disagree {
  884. background: #f5f5f5;
  885. color: #666;
  886. border: none;
  887. padding: 12px 24px;
  888. border-radius: 8px;
  889. cursor: pointer;
  890. font-weight: 500;
  891. transition: all 0.3s ease;
  892. }
  893. .btn-disagree:hover {
  894. background: #e0e0e0;
  895. }
  896. </style>