index.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538
  1. <template>
  2. <div class="mypage-container">
  3. <!-- 프로필 섹션 -->
  4. <div class="profile-section">
  5. <div class="profile-card">
  6. <div class="profile-header">
  7. <div class="profile-avatar">
  8. {{ getMemberTypeName(userInfo.memberType) === "인플루언서" ? '❤️' : '💜' }}
  9. </div>
  10. <div class="profile-info">
  11. <h2>{{ userInfo.name }}</h2>
  12. <span class="member-type-badge">{{ getMemberTypeName(userInfo.memberType) }}</span>
  13. </div>
  14. <button
  15. class="edit-btn"
  16. @click="toggleEditMode"
  17. :class="{ active: isEditMode }"
  18. >
  19. {{ isEditMode ? '취소' : '수정' }}
  20. </button>
  21. </div>
  22. <!-- 정보 표시/수정 폼 -->
  23. <div class="profile-content">
  24. <div v-if="!isEditMode" class="info-display">
  25. <div class="info-row">
  26. <label>이름</label>
  27. <span>{{ userInfo.name }}</span>
  28. </div>
  29. <div class="info-row">
  30. <label>이메일</label>
  31. <span>{{ userInfo.email }}</span>
  32. </div>
  33. <div class="info-row" v-if="userInfo.memberType !== 'INFLUENCER'">
  34. <label>회사명</label>
  35. <span>{{ userInfo.companyName }}</span>
  36. </div>
  37. <div class="info-row">
  38. <label>전화번호</label>
  39. <span>{{ userInfo.phone || '미등록' }}</span>
  40. </div>
  41. </div>
  42. <div v-else class="info-edit">
  43. <div class="form-group">
  44. <label>이름 *</label>
  45. <input
  46. type="text"
  47. v-model="editForm.name"
  48. placeholder="이름을 입력하세요"
  49. class="form-input"
  50. />
  51. </div>
  52. <div class="form-group">
  53. <label>이메일 *</label>
  54. <input
  55. type="email"
  56. v-model="editForm.email"
  57. placeholder="이메일을 입력하세요"
  58. class="form-input"
  59. />
  60. </div>
  61. <div class="form-group" v-if="userInfo.memberType !== 'INFLUENCER'">
  62. <label>회사명</label>
  63. <input
  64. type="text"
  65. v-model="editForm.companyName"
  66. placeholder="회사명을 입력하세요"
  67. class="form-input"
  68. />
  69. </div>
  70. <div class="form-group">
  71. <label>전화번호</label>
  72. <input
  73. type="tel"
  74. v-model="editForm.phone"
  75. placeholder="전화번호를 입력하세요"
  76. class="form-input"
  77. />
  78. </div>
  79. <div class="form-actions">
  80. <button
  81. class="btn-save"
  82. @click="saveProfile"
  83. :disabled="isSaving"
  84. >
  85. {{ isSaving ? '저장중...' : '저장' }}
  86. </button>
  87. <button
  88. class="btn-cancel"
  89. @click="cancelEdit"
  90. >
  91. 취소
  92. </button>
  93. </div>
  94. </div>
  95. </div>
  96. </div>
  97. </div>
  98. <!-- 추가 정보 섹션 -->
  99. <div class="additional-info">
  100. <div class="info-card">
  101. <h3>계정 정보</h3>
  102. <div class="info-grid">
  103. <div class="info-item">
  104. <span class="info-label">회원 타입</span>
  105. <span class="info-value">{{ getMemberTypeName(userInfo.memberType) }}</span>
  106. </div>
  107. <div class="info-item">
  108. <span class="info-label">계정 ID</span>
  109. <span class="info-value">{{ userInfo.id }}</span>
  110. </div>
  111. <div class="info-item">
  112. <span class="info-label">가입일</span>
  113. <span class="info-value">{{ formatDate(userInfo.regDate) || '정보 없음' }}</span>
  114. </div>
  115. </div>
  116. </div>
  117. </div>
  118. </div>
  119. </template>
  120. <script setup>
  121. import "@vuepic/vue-datepicker/dist/main.css";
  122. /************************************************************************
  123. | 레이아웃
  124. ************************************************************************/
  125. definePageMeta({
  126. layout: "default",
  127. });
  128. /************************************************************************
  129. | PROPS
  130. ************************************************************************/
  131. const props = defineProps({
  132. propsData: {
  133. type: Object,
  134. default: () => {},
  135. },
  136. });
  137. /************************************************************************
  138. | 스토어
  139. ************************************************************************/
  140. const useDtStore = useDetailStore();
  141. const useAtStore = useAuthStore();
  142. /************************************************************************
  143. | 전역
  144. ************************************************************************/
  145. const { $toast, $log, $dayjs, $eventBus } = useNuxtApp();
  146. const router = useRouter();
  147. // 사용자 정보
  148. const userInfo = ref({
  149. id: useAtStore.auth.id,
  150. name: useAtStore.auth.name,
  151. email: useAtStore.auth.email,
  152. companyName: useAtStore.auth.companyName,
  153. companyNumber: useAtStore.auth.companyNumber,
  154. phone: useAtStore.auth.phone,
  155. memberType: useAtStore.auth.memberType,
  156. regDate: null
  157. });
  158. // 편집 모드
  159. const isEditMode = ref(false);
  160. const isSaving = ref(false);
  161. const editForm = ref({
  162. name: '',
  163. email: '',
  164. companyName: '',
  165. phone: ''
  166. });
  167. /* eslint-disable */
  168. /* prettier-ignore */
  169. /************************************************************************
  170. | 함수(METHODS)
  171. ************************************************************************/
  172. // 회원 타입 이름 반환
  173. const getMemberTypeName = (type) => {
  174. switch(type) {
  175. case 'INFLUENCER': return '인플루언서';
  176. case 'VENDOR': return '벤더사';
  177. case 'BRAND': return '브랜드사';
  178. default: return '사용자';
  179. }
  180. };
  181. // 편집 모드 토글
  182. const toggleEditMode = () => {
  183. if (isEditMode.value) {
  184. // 편집 취소
  185. isEditMode.value = false;
  186. } else {
  187. // 편집 시작 - 현재 값으로 폼 초기화
  188. editForm.value = {
  189. name: userInfo.value.name,
  190. email: userInfo.value.email,
  191. companyName: userInfo.value.companyName,
  192. phone: userInfo.value.phone
  193. };
  194. isEditMode.value = true;
  195. }
  196. };
  197. // 편집 취소
  198. const cancelEdit = () => {
  199. isEditMode.value = false;
  200. };
  201. // 프로필 저장
  202. const saveProfile = async () => {
  203. // 유효성 검사
  204. if (!editForm.value.name.trim()) {
  205. $toast.error('이름을 입력해주세요.');
  206. return;
  207. }
  208. if (!editForm.value.email.trim()) {
  209. $toast.error('이메일을 입력해주세요.');
  210. return;
  211. }
  212. isSaving.value = true;
  213. try {
  214. const updateData = {
  215. USER_SEQ: useAtStore.auth.seq,
  216. NAME: editForm.value.name,
  217. EMAIL: editForm.value.email,
  218. PHONE: editForm.value.phone
  219. };
  220. // 회사 정보는 벤더사, 브랜드사만
  221. if (userInfo.value.memberType !== 'INFLUENCER') {
  222. updateData.COMPANY_NAME = editForm.value.companyName;
  223. }
  224. // TODO: 실제 API 호출 (현재는 임시로 로컬 업데이트)
  225. // const response = await useAxios().post('/user/update', updateData);
  226. // 임시로 로컬 데이터 업데이트
  227. userInfo.value.name = editForm.value.name;
  228. userInfo.value.email = editForm.value.email;
  229. userInfo.value.phone = editForm.value.phone;
  230. if (userInfo.value.memberType !== 'INFLUENCER') {
  231. userInfo.value.companyName = editForm.value.companyName;
  232. }
  233. // 스토어도 업데이트
  234. useAtStore.auth.name = editForm.value.name;
  235. useAtStore.auth.email = editForm.value.email;
  236. useAtStore.auth.phone = editForm.value.phone;
  237. if (userInfo.value.memberType !== 'INFLUENCER') {
  238. useAtStore.auth.companyName = editForm.value.companyName;
  239. }
  240. $toast.success('프로필이 성공적으로 수정되었습니다.');
  241. isEditMode.value = false;
  242. } catch (error) {
  243. console.error('프로필 수정 실패:', error);
  244. $toast.error('프로필 수정에 실패했습니다.');
  245. } finally {
  246. isSaving.value = false;
  247. }
  248. };
  249. const formatDate = (dateStr) => {
  250. if (!dateStr) return '';
  251. const date = new Date(dateStr);
  252. const year = date.getFullYear();
  253. const month = String(date.getMonth() + 1).padStart(2, '0');
  254. const day = String(date.getDate()).padStart(2, '0');
  255. return `${year}.${month}.${day}`;
  256. };
  257. /************************************************************************
  258. | WATCH
  259. ************************************************************************/
  260. // 사용자 상세 정보 로드
  261. const fnDetail = () => {
  262. let req = {
  263. MEMBER_TYPE: useAtStore.auth.memberType,
  264. MEMBER_SEQ: useAtStore.auth.seq
  265. }
  266. console.warn(req)
  267. useAxios()
  268. .post(`/mypage/detail`, req)
  269. .then((res) => {
  270. console.error(res)
  271. userInfo.value = {
  272. id: userData.ID,
  273. name: userData.NAME,
  274. email: userData.EMAIL,
  275. companyName: userData.COMPANY_NAME,
  276. companyNumber: userData.COMPANY_NUMBER,
  277. phone: userData.PHONE,
  278. memberType: userData.MEMBER_TYPE,
  279. nickName: userData.NICK_NAME,
  280. introduction: userData.INTRODUCTION,
  281. regDate: userData.REGDATE
  282. };
  283. })
  284. };
  285. onMounted(() => {
  286. fnDetail();
  287. });
  288. </script>
  289. <style scoped>
  290. .mypage-container {
  291. margin: 0 auto;
  292. padding: 20px;
  293. }
  294. .page-header {
  295. margin-bottom: 32px;
  296. text-align: center;
  297. }
  298. .page-header h1 {
  299. font-size: 2.5rem;
  300. color: #1a237e;
  301. margin-bottom: 8px;
  302. }
  303. .page-header p {
  304. color: #666;
  305. font-size: 1.1rem;
  306. }
  307. .profile-section {
  308. margin-bottom: 32px;
  309. }
  310. .profile-card {
  311. background: white;
  312. border-radius: 20px;
  313. box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
  314. overflow: hidden;
  315. }
  316. .profile-header {
  317. display: flex;
  318. align-items: center;
  319. padding: 32px;
  320. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  321. color: white;
  322. }
  323. .profile-avatar {
  324. font-size: 3rem;
  325. margin-right: 24px;
  326. background: rgba(255, 255, 255, 0.2);
  327. width: 96px;
  328. height: 96px;
  329. overflow: hidden;
  330. border-radius: 50%;
  331. display: flex;
  332. align-items: center;
  333. justify-content: center;
  334. }
  335. .profile-info {
  336. flex: 1;
  337. }
  338. .profile-info h2 {
  339. margin-bottom: 15px;
  340. font-size: 1.8rem;
  341. }
  342. .member-type-badge {
  343. background: rgba(255, 255, 255, 0.2);
  344. padding: 4px 12px;
  345. border-radius: 20px;
  346. font-size: 0.9rem;
  347. }
  348. .edit-btn {
  349. background: rgba(255, 255, 255, 0.2);
  350. border: 2px solid rgba(255, 255, 255, 0.3);
  351. color: white;
  352. padding: 12px 24px;
  353. border-radius: 25px;
  354. cursor: pointer;
  355. transition: all 0.3s ease;
  356. font-weight: 500;
  357. }
  358. .edit-btn:hover, .edit-btn.active {
  359. background: white;
  360. color: #667eea;
  361. }
  362. .profile-content {
  363. padding: 32px;
  364. }
  365. /* 정보 표시 스타일 */
  366. .info-display .info-row {
  367. display: flex;
  368. padding: 16px 0;
  369. border-bottom: 1px solid #f0f0f0;
  370. }
  371. .info-display .info-row:last-child {
  372. border-bottom: none;
  373. }
  374. .info-display label {
  375. width: 120px;
  376. font-weight: 600;
  377. color: #333;
  378. }
  379. .info-display span {
  380. flex: 1;
  381. color: #666;
  382. }
  383. /* 편집 폼 스타일 */
  384. .form-group {
  385. margin-bottom: 24px;
  386. }
  387. .form-group label {
  388. display: block;
  389. margin-bottom: 8px;
  390. font-weight: 600;
  391. color: #333;
  392. }
  393. .form-input {
  394. width: 100%;
  395. padding: 12px 16px;
  396. border: 2px solid #e0e0e0;
  397. border-radius: 8px;
  398. font-size: 1rem;
  399. transition: border-color 0.3s ease;
  400. }
  401. .form-input:focus {
  402. outline: none;
  403. border-color: #667eea;
  404. }
  405. .form-actions {
  406. display: flex;
  407. gap: 12px;
  408. margin-top: 32px;
  409. }
  410. .btn-save {
  411. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  412. color: white;
  413. border: none;
  414. padding: 12px 24px;
  415. border-radius: 8px;
  416. cursor: pointer;
  417. font-weight: 500;
  418. transition: all 0.3s ease;
  419. }
  420. .btn-save:hover:not(:disabled) {
  421. transform: translateY(-2px);
  422. box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
  423. }
  424. .btn-save:disabled {
  425. opacity: 0.6;
  426. cursor: not-allowed;
  427. }
  428. .btn-cancel {
  429. background: #f5f5f5;
  430. color: #666;
  431. border: none;
  432. padding: 12px 24px;
  433. border-radius: 8px;
  434. cursor: pointer;
  435. font-weight: 500;
  436. transition: all 0.3s ease;
  437. }
  438. .btn-cancel:hover {
  439. background: #e0e0e0;
  440. }
  441. /* 추가 정보 섹션 */
  442. .additional-info {
  443. margin-top: 32px;
  444. }
  445. .info-card {
  446. background: white;
  447. border-radius: 16px;
  448. padding: 32px;
  449. box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
  450. }
  451. .info-card h3 {
  452. margin: 0 0 24px 0;
  453. color: #1a237e;
  454. font-size: 1.3rem;
  455. }
  456. .info-grid {
  457. display: grid;
  458. grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  459. gap: 24px;
  460. }
  461. .info-item {
  462. padding: 16px;
  463. background: #f8f9fa;
  464. border-radius: 12px;
  465. }
  466. .info-label {
  467. display: block;
  468. font-size: 0.9rem;
  469. color: #666;
  470. margin-bottom: 15px;
  471. }
  472. .info-value {
  473. font-weight: 600;
  474. color: #333;
  475. font-size: 1rem;
  476. }
  477. </style>