From eb39a2a0b7c649c9174fc0e558c59988b2f763c2 Mon Sep 17 00:00:00 2001 From: dyhj625 Date: Mon, 7 Apr 2025 10:34:49 +0900 Subject: [PATCH] =?UTF-8?q?=EC=88=98=EC=A0=95=EC=82=AC=ED=95=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/css/custom.css | 6 + src/components/board/BoardCommentArea.vue | 6 + src/components/main/BoardMain.vue | 146 +++++++++--------- src/views/board/BoardList.vue | 5 +- src/views/board/BoardWrite.vue | 9 +- src/views/mypage/MyPage.vue | 180 ++++++++++++---------- 6 files changed, 196 insertions(+), 156 deletions(-) diff --git a/public/css/custom.css b/public/css/custom.css index 0319927..c710f06 100644 --- a/public/css/custom.css +++ b/public/css/custom.css @@ -811,4 +811,10 @@ input:checked + .slider:before { } .mr-1{ margin-right: 0.25rem !important; +} + +.nickname-ellipsis { + white-space: nowrap; + max-width: 100px; + vertical-align: middle; } \ No newline at end of file diff --git a/src/components/board/BoardCommentArea.vue b/src/components/board/BoardCommentArea.vue index ed522cf..37813a0 100644 --- a/src/components/board/BoardCommentArea.vue +++ b/src/components/board/BoardCommentArea.vue @@ -41,6 +41,8 @@ v-model="nickname" placeholder="닉네임" @input="clearAlert('nickname')" + @keypress="noSpace" + :maxlength="6" />
@@ -109,6 +111,10 @@ }, }); + const noSpace = (e) => { + if (e.key === ' ') e.preventDefault(); + }; + const $common = inject('common'); const comment = ref(''); const password = ref(''); diff --git a/src/components/main/BoardMain.vue b/src/components/main/BoardMain.vue index a596c62..7dec701 100644 --- a/src/components/main/BoardMain.vue +++ b/src/components/main/BoardMain.vue @@ -44,11 +44,17 @@ - - + - @@ -58,11 +64,13 @@ style="cursor: pointer;" @click="goDetail(post.id, selectedBoard)" > - - + @@ -105,7 +105,7 @@ N - + @@ -384,4 +384,5 @@ position: relative; top: -1px; } + diff --git a/src/views/board/BoardWrite.vue b/src/views/board/BoardWrite.vue index 6317db5..c44e3a3 100644 --- a/src/views/board/BoardWrite.vue +++ b/src/views/board/BoardWrite.vue @@ -50,6 +50,8 @@ v-model="nickname" @update:alert="nicknameAlert = $event" @input="validateNickname" + @keypress="noSpace" + :maxlength="6" /> { + if (e.key === ' ') e.preventDefault(); + }; + const fetchCategories = async () => { const response = await axios.get('board/categories'); categoryList.value = response.data.data; @@ -206,7 +212,8 @@ const validateNickname = () => { if (categoryValue.value === 300102) { - nicknameAlert.value = nickname.value.trim().length === 0; + nickname.value = nickname.value.replace(/\s/g, ''); // 공백 제거 + nicknameAlert.value = nickname.value.length === 0 ; } else { nicknameAlert.value = false; } diff --git a/src/views/mypage/MyPage.vue b/src/views/mypage/MyPage.vue index eb407d4..b908b7d 100644 --- a/src/views/mypage/MyPage.vue +++ b/src/views/mypage/MyPage.vue @@ -55,8 +55,11 @@ + + 전화번호 형식이 올바르지 않습니다. + 이미 사용 중인 전화번호입니다. @@ -142,6 +145,7 @@ const colorList = ref([]); const password = ref({ current: '', new: '', confirm: '' }); const passwordError = ref(false); +const phoneFormatError = ref(false); const showResetPw = ref(false); const canResetPassword = computed(() => { @@ -162,93 +166,101 @@ watch( ); const isChanged = computed(() => { -const f = form.value; -const o = originalData.value; -return ( - f.entryDate !== o.entryDate || f.birth !== o.birth || f.phone !== o.phone || - f.color !== o.color || f.mbti !== o.mbti || profileChanged.value || - f.address.address !== o.address.address || - f.address.detailAddress !== o.address.detailAddress || - f.address.postcode !== o.address.postcode -); + const f = form.value; + const o = originalData.value; + return ( + f.entryDate !== o.entryDate || f.birth !== o.birth || f.phone !== o.phone || + f.color !== o.color || f.mbti !== o.mbti || profileChanged.value || + f.address.address !== o.address.address || + f.address.detailAddress !== o.address.detailAddress || + f.address.postcode !== o.address.postcode + ); }); const baseUrl = $api.defaults.baseURL.replace(/api\/$/, ''); -const defaultProfile = "img/avatars/default-Profile.jpg"; -const getProfileImageUrl = (fileName) => -fileName?.trim() ? `${baseUrl}upload/img/profile/${fileName}?t=${Date.now()}` : defaultProfile; - +const defaultProfile = "/img/icons/icon.png"; +const getProfileImageUrl = (profilePath) => + profilePath && profilePath.trim() ? `${baseUrl}upload/img/profile/${profilePath}` : defaultProfile; const profilePreviewStyle = computed(() => ({ -width: '100px', -height: '100px', -backgroundImage: `url(${profile.value})`, -backgroundRepeat: 'no-repeat', -backgroundSize: 'cover', -backgroundPosition: 'center' + width: '100px', + height: '100px', + backgroundImage: `url(${profile.value})`, + backgroundRepeat: 'no-repeat', + backgroundSize: 'cover', + backgroundPosition: 'center' })); const profileUpload = (e) => { -const file = e.target.files[0]; -if (!file) return; -if (file.size > 5 * 1024 * 1024 || !['image/jpeg', 'image/png'].includes(file.type)) { - profilerr.value = '5MB 이하의 JPG/PNG 파일만 업로드 가능합니다.'; - return; -} -profilerr.value = ''; -if (currentBlobUrl.value) URL.revokeObjectURL(currentBlobUrl.value); -uploadedFile.value = file; -const newBlobUrl = URL.createObjectURL(file); -profile.value = newBlobUrl; -currentBlobUrl.value = newBlobUrl; -profileChanged.value = true; + const file = e.target.files[0]; + if (!file) return; + if (file.size > 5 * 1024 * 1024 || !['image/jpeg', 'image/png'].includes(file.type)) { + profilerr.value = '5MB 이하의 JPG/PNG 파일만 업로드 가능합니다.'; + return; + } + profilerr.value = ''; + if (currentBlobUrl.value) URL.revokeObjectURL(currentBlobUrl.value); + uploadedFile.value = file; + const newBlobUrl = URL.createObjectURL(file); + profile.value = newBlobUrl; + currentBlobUrl.value = newBlobUrl; + profileChanged.value = true; }; const onlyNumber = (e) => { -if (!/[0-9]/.test(e.key)) e.preventDefault(); + if (!/[0-9]/.test(e.key)) e.preventDefault(); }; -const checkPhoneDuplicate = async () => { -const currentPhone = form.value.phone; -phoneDuplicated.value = currentPhone !== originalData.value.phone && - !(await $api.get('/user/checkPhone', { params: { memberTel: currentPhone } })).data.data; +const checkPhoneDuplicateAndFormat = async () => { + const phone = form.value.phone.trim(); + + // 전화번호 정규식 + const phoneRegex = /^010\d{8}$/; + phoneFormatError.value = !phoneRegex.test(phone); + + if (!phoneFormatError.value) { + phoneDuplicated.value = phone !== originalData.value.phone && + !(await $api.get('/user/checkPhone', { + params: { memberTel: currentPhone }, + })).data.data; + } }; const handleColorUpdate = async (colorVal) => { -form.value.color = colorVal; -colorDuplicated.value = colorVal !== originalData.value.color && - (await $api.get('/user/checkColor', { params: { memberCol: colorVal } })).data.data; + form.value.color = colorVal; + colorDuplicated.value = colorVal !== originalData.value.color && + (await $api.get('/user/checkColor', { params: { memberCol: colorVal } })).data.data; }; const checkCurrentPassword = async () => { -if (!password.value.current) return; -const res = await $api.post('/user/checkPassword', { - id: form.value.id, - password: password.value.current -}); -passwordError.value = res.data.data; -showResetPw.value = !res.data.data; + if (!password.value.current) return; + const res = await $api.post('/user/checkPassword', { + id: form.value.id, + password: password.value.current + }); + passwordError.value = res.data.data; + showResetPw.value = !res.data.data; }; const handlePasswordReset = async () => { -const res = await $api.patch('/user/pwNew', { - id: form.value.id, - password: password.value.new -}); -if (res.data.data) { - toastStore.onToast('비밀번호가 변경되었습니다.', 's'); - password.value = { current: '', new: '', confirm: '' }; - showResetPw.value = false; - passwordError.value = false; -} else { - toastStore.onToast('비밀번호 변경 실패', 'e'); -} + const res = await $api.patch('/user/pwNew', { + id: form.value.id, + password: password.value.new + }); + if (res.data.data) { + toastStore.onToast('비밀번호가 변경되었습니다.', 's'); + password.value = { current: '', new: '', confirm: '' }; + showResetPw.value = false; + passwordError.value = false; + } else { + toastStore.onToast('비밀번호 변경 실패', 'e'); + } }; const formatDate = (isoDate) => isoDate?.split('T')[0] || ''; const loadInitialData = async () => { -const user = (await $api.get('/user/userInfo')).data.data; -const serverColors = (await $api.get('/user/color', { params: { type: 'YON' } })).data.data.map(c => ({ - value: c.CMNCODVAL, label: c.CMNCODNAM + const user = (await $api.get('/user/userInfo')).data.data; + const serverColors = (await $api.get('/user/color', { params: { type: 'YON' } })).data.data.map(c => ({ + value: c.CMNCODVAL, label: c.CMNCODNAM })); const matchedColor = serverColors.find(c => c.label === user.usercolor); const colorCode = matchedColor ? matchedColor.value : user.color; @@ -275,32 +287,32 @@ profile.value = getProfileImageUrl(user.profile); profileChanged.value = false; const mbtiRes = await $api.get('/user/mbti'); -mbtiList.value = mbtiRes.data.data.map(m => ({ value: m.CMNCODVAL, label: m.CMNCODNAM })); + mbtiList.value = mbtiRes.data.data.map(m => ({ value: m.CMNCODVAL, label: m.CMNCODNAM })); }; const handleSubmit = async () => { -const formData = new FormData(); -Object.entries(form.value).forEach(([k, v]) => { - if (typeof v === 'object') { - formData.append('address', v.address); - formData.append('detailAddress', v.detailAddress); - formData.append('postcode', v.postcode); - } else { - formData.append(k, v); + const formData = new FormData(); + Object.entries(form.value).forEach(([k, v]) => { + if (typeof v === 'object') { + formData.append('address', v.address); + formData.append('detailAddress', v.detailAddress); + formData.append('postcode', v.postcode); + } else { + formData.append(k, v); + } + }); + if (uploadedFile.value) formData.append('profileFile', uploadedFile.value); + + if (form.value.color !== originalData.value.color) { + if (form.value.color) await $api.patch('/user/updateColorYon', { color: form.value.color, type: 'YON' }); + if (originalData.value.color) await $api.patch('/user/updateColorChange', { color: originalData.value.color, type: 'YON' }); } -}); -if (uploadedFile.value) formData.append('profileFile', uploadedFile.value); -if (form.value.color !== originalData.value.color) { - if (form.value.color) await $api.patch('/user/updateColorYon', { color: form.value.color, type: 'YON' }); - if (originalData.value.color) await $api.patch('/user/updateColorChange', { color: originalData.value.color, type: 'YON' }); -} - -await $api.patch('/user/updateInfo', formData, { isFormData: true }); -originalData.value = { ...form.value }; -profileChanged.value = false; -location.reload(); -toastStore.onToast('정보가 수정되었습니다.', 's'); + await $api.patch('/user/updateInfo', formData, { isFormData: true }); + originalData.value = { ...form.value }; + profileChanged.value = false; + location.reload(); + toastStore.onToast('정보가 수정되었습니다.', 's'); }; onMounted(() => loadInitialData());
+ + +
{{ selectedBoard === 'anonymous' ? '닉네임' : '작성자' }} +
+
+
+ 제목 +
제목
+ +
{{ selectedBoard === 'anonymous' ? post.nickname : post.author }} +
-
+
{{ truncateTitle(post.title) }} [{{ post.commentCount }}] @@ -120,97 +128,97 @@ const anonymousList = ref([]); // 선택된 게시판에 따른 미리보기 목록 computed const currentList = computed(() => { -if (selectedBoard.value === 'notices') return noticeList.value; -if (selectedBoard.value === 'general') return freeList.value; -if (selectedBoard.value === 'anonymous') return anonymousList.value; -return []; + if (selectedBoard.value === 'notices') return noticeList.value; + if (selectedBoard.value === 'general') return freeList.value; + if (selectedBoard.value === 'anonymous') return anonymousList.value; + return []; }); // 날짜 포맷 함수: 오늘이면 HH:mm, 아니면 YYYY-MM-DD const formatDate = dateString => { -const date = dayjs(dateString); -return date.isToday() ? date.format('HH:mm') : date.format('YYYY-MM-DD'); + const date = dayjs(dateString); + return date.isToday() ? date.format('HH:mm') : date.format('YYYY-MM-DD'); }; // 제목이 14글자 넘어가면 ... 처리하는 함수 const truncateTitle = title => { -return title.length > 7 ? title.slice(0, 7) + '...' : title; + return title.length > 7 ? title.slice(0, 7) + '...' : title; }; // 공지사항 데이터 로드 (최대 5개) const fetchNoticePosts = async () => { -try { - const { data } = await axios.get('board/notices', { params: { size: 8 } }); - if (data?.data) { - noticeList.value = data.data.map(post => ({ - id: post.id, - title: post.title, - date: formatDate(post.date), - rawDate: post.date, - views: post.cnt || 0, - commentCount: post.commentCount, - img: post.firstImageUrl, - author: post.author || '관리자', - nickname: post.nickname || '관리자', - hasAttachment: post.hasAttachment, // 첨부파일 유무 - })); + try { + const { data } = await axios.get('board/notices', { params: { size: 8 } }); + if (data?.data) { + noticeList.value = data.data.map(post => ({ + id: post.id, + title: post.title, + date: formatDate(post.date), + rawDate: post.date, + views: post.cnt || 0, + commentCount: post.commentCount, + img: post.firstImageUrl, + author: post.author || '관리자', + nickname: post.nickname || '관리자', + hasAttachment: post.hasAttachment, // 첨부파일 유무 + })); + } + } catch (error) { } -} catch (error) { -} }; // board/general 게시글 로드 후 자유게시판과 익명게시판으로 분리 (최대 10개 조회 → 각각 최대 5개) const fetchGeneralPosts = async () => { -try { - const { data } = await axios.get('board/general', { params: { size: 16 } }); - if (data?.data && data.data.list) { - const freePosts = []; - const anonymousPosts = []; - data.data.list.forEach(post => { - if (post.nickname) { - // 닉네임이 있으면 익명게시판 데이터 - anonymousPosts.push({ - id: post.id, - title: post.title, - date: formatDate(post.date), - img: post.firstImageUrl, - rawDate: post.date, - views: post.cnt || 0, - commentCount: post.commentCount, - nickname: post.nickname, - hasAttachment: post.hasAttachment, // 첨부파일 유무 - }); - } else { - // 닉네임이 없으면 자유게시판 데이터 - freePosts.push({ - id: post.id, - title: post.title, - date: formatDate(post.date), - rawDate: post.date, - views: post.cnt || 0, - img: post.firstImageUrl, - commentCount: post.commentCount, - author: post.author || '익명', - hasAttachment: post.hasAttachment, // 첨부파일 유무 + try { + const { data } = await axios.get('board/general', { params: { size: 16 } }); + if (data?.data && data.data.list) { + const freePosts = []; + const anonymousPosts = []; + data.data.list.forEach(post => { + if (post.nickname) { + // 닉네임이 있으면 익명게시판 데이터 + anonymousPosts.push({ + id: post.id, + title: post.title, + date: formatDate(post.date), + img: post.firstImageUrl, + rawDate: post.date, + views: post.cnt || 0, + commentCount: post.commentCount, + nickname: post.nickname, + hasAttachment: post.hasAttachment, // 첨부파일 유무 + }); + } else { + // 닉네임이 없으면 자유게시판 데이터 + freePosts.push({ + id: post.id, + title: post.title, + date: formatDate(post.date), + rawDate: post.date, + views: post.cnt || 0, + img: post.firstImageUrl, + commentCount: post.commentCount, + author: post.author || '익명', + hasAttachment: post.hasAttachment, // 첨부파일 유무 + }); + } }); + freeList.value = freePosts.slice(0, 8); + anonymousList.value = anonymousPosts.slice(0, 8); } - }); - freeList.value = freePosts.slice(0, 8); - anonymousList.value = anonymousPosts.slice(0, 8); + } catch (error) { + console.error(error); } -} catch (error) { - console.error(error); -} }; // 탭 변경 함수 const changeBoard = type => { -selectedBoard.value = type; + selectedBoard.value = type; }; // 상세 페이지 이동 (게시판 타입 전달) const goDetail = (id, boardType) => { -router.push({ name: 'BoardDetail', params: { id }, query: { type: boardType } }); + router.push({ name: 'BoardDetail', params: { id }, query: { type: boardType } }); }; // 모달이 열릴 때 데이터 로드 @@ -220,6 +228,6 @@ fetchGeneralPosts(); diff --git a/src/views/board/BoardList.vue b/src/views/board/BoardList.vue index 2613414..a6f4ada 100644 --- a/src/views/board/BoardList.vue +++ b/src/views/board/BoardList.vue @@ -80,7 +80,7 @@ N
{{ notice.author }}{{ notice.author }} {{ notice.date }} {{ notice.views }}
{{ post.nickname ? post.nickname : post.author }}{{ post.nickname ? post.nickname : post.author }} {{ post.date }} {{ post.views }}