수정사항

This commit is contained in:
dyhj625 2025-04-07 10:34:49 +09:00
parent 44cec4cccd
commit eb39a2a0b7
6 changed files with 196 additions and 156 deletions

View File

@ -812,3 +812,9 @@ input:checked + .slider:before {
.mr-1{
margin-right: 0.25rem !important;
}
.nickname-ellipsis {
white-space: nowrap;
max-width: 100px;
vertical-align: middle;
}

View File

@ -41,6 +41,8 @@
v-model="nickname"
placeholder="닉네임"
@input="clearAlert('nickname')"
@keypress="noSpace"
:maxlength="6"
/>
<!-- 닉네임 경고 메시지 -->
<div v-if="nicknameAlert" class="position-absolute text-danger small top-100 start-0">
@ -109,6 +111,10 @@
},
});
const noSpace = (e) => {
if (e.key === ' ') e.preventDefault();
};
const $common = inject('common');
const comment = ref('');
const password = ref('');

View File

@ -44,11 +44,17 @@
<table class="table">
<thead>
<tr>
<!-- 익명게시판은 'nickname', 나머지는 'writer' -->
<th class="text-center" style="width: 20%;">
<!-- 익명게시판은 '닉네임', 나머지는 '작성자' -->
<th class="text-start">
<div class="ms-4">
{{ selectedBoard === 'anonymous' ? '닉네임' : '작성자' }}
</div>
</th>
<th class="text-start" style="width: 65%;">
<div class="ms-4">
제목
</div>
</th>
<th class="text-center" style="width: 65%;">제목</th>
</tr>
</thead>
<tbody>
@ -58,11 +64,13 @@
style="cursor: pointer;"
@click="goDetail(post.id, selectedBoard)"
>
<td class="text-center small">
<td class="text-start nickname-ellipsis small">
<div class="ms-4">
{{ selectedBoard === 'anonymous' ? post.nickname : post.author }}
</div>
</td>
<td class="text-start fs-6">
<div class="ms-2">
<div class="ms-4">
{{ truncateTitle(post.title) }}
<span v-if="post.commentCount" class="text-danger ml-1 small">
[{{ 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();
<style scoped>
.table > :not(caption) > * > * {
padding: 0 !important;
padding: 0 !important;
}
</style>

View File

@ -80,7 +80,7 @@
<span v-if="isNewPost(notice.rawDate)" class="box-new badge text-white ms-2 fs-tiny"> N </span>
</div>
</td>
<td class="text-center">{{ notice.author }}</td>
<td class="text-start">{{ notice.author }}</td>
<td class="text-center">{{ notice.date }}</td>
<td class="text-center">{{ notice.views }}</td>
</tr>
@ -105,7 +105,7 @@
<span v-if="isNewPost(post.rawDate)" class="box-new badge text-white ms-2 fs-tiny">N</span>
</div>
</td>
<td class="text-center">{{ post.nickname ? post.nickname : post.author }}</td>
<td class="text-start nickname-ellipsis">{{ post.nickname ? post.nickname : post.author }}</td>
<td class="text-center">{{ post.date }}</td>
<td class="text-center">{{ post.views }}</td>
</tr>
@ -384,4 +384,5 @@
position: relative;
top: -1px;
}
</style>

View File

@ -50,6 +50,8 @@
v-model="nickname"
@update:alert="nicknameAlert = $event"
@input="validateNickname"
@keypress="noSpace"
:maxlength="6"
/>
<FormInput
title="비밀번호"
@ -146,6 +148,10 @@
const editorUploadedImgList = ref([]);
const editorDeleteImgList = ref([]);
const noSpace = (e) => {
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;
}

View File

@ -55,8 +55,11 @@
<ArrInput title="주소" name="address" v-model="form.address" :disabled="true" />
<UserFormInput title="전화번호" name="phone" :value="form.phone"
@update:data="form.phone = $event" @blur="checkPhoneDuplicate"
@update:data="form.phone = $event" @blur="checkPhoneDuplicateAndFormat"
:maxlength="11" @keypress="onlyNumber" />
<span v-if="phoneFormatError" class="text-danger invalid-feedback mt-1 d-block">
전화번호 형식이 올바르지 않습니다.
</span>
<span v-if="phoneDuplicated" class="text-danger invalid-feedback mt-1 d-block">
이미 사용 중인 전화번호입니다.
</span>
@ -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());