lee-zt 4 weeks ago
commit d5e8ae9f7c

@ -15,6 +15,7 @@
<img v-if="previewAvatar" :src="previewAvatar" alt="新头像预览" class="new-avatar"> <img v-if="previewAvatar" :src="previewAvatar" alt="新头像预览" class="new-avatar">
</div> </div>
<label class="upload-btn"> <label class="upload-btn">
<span class="plus-icon">+</span>
选择新头像 选择新头像
<input type="file" accept="image/*" @change="handleAvatarChange" class="file-input"> <input type="file" accept="image/*" @change="handleAvatarChange" class="file-input">
</label> </label>
@ -143,7 +144,7 @@ import { ref, computed, onMounted } from 'vue';
import { useUserStore } from '@/stores/user'; import { useUserStore } from '@/stores/user';
import request from '@/utils/request'; import request from '@/utils/request';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router';
const userStore = useUserStore(); const userStore = useUserStore();
const userInfo = computed(() => userStore.userInfo); const userInfo = computed(() => userStore.userInfo);
@ -384,9 +385,10 @@ onMounted(() => {
max-width: 700px; max-width: 700px;
margin: 2rem auto; margin: 2rem auto;
padding: 2.5rem 2rem 2rem 2rem; padding: 2.5rem 2rem 2rem 2rem;
background: #f8f9fa; background: #fff;
border-radius: 12px; border-radius: 12px;
box-shadow: 0 4px 16px rgba(52, 152, 219, 0.08); box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
position: relative;
} }
.title { .title {
@ -396,6 +398,8 @@ onMounted(() => {
margin-bottom: 2.2rem; margin-bottom: 2.2rem;
letter-spacing: 1px; letter-spacing: 1px;
text-align: center; text-align: center;
border-bottom: 2px solid #ff88aa;
padding-bottom: 10px;
} }
.section-title { .section-title {
@ -428,13 +432,15 @@ onMounted(() => {
} }
.new-avatar { .new-avatar {
border-color: #3498db; border-color: #ff88aa;
} }
.upload-btn { .upload-btn {
display: inline-block; display: flex;
align-items: center;
gap: 8px;
padding: 0.5rem 1.2rem; padding: 0.5rem 1.2rem;
background: linear-gradient(90deg, #3498db 60%, #6dd5fa 100%); background: #ff88aa;
color: white; color: white;
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;
@ -445,7 +451,12 @@ onMounted(() => {
} }
.upload-btn:hover { .upload-btn:hover {
background: linear-gradient(90deg, #2980b9 60%, #3498db 100%); background: #ff6699;
}
.plus-icon {
font-size: 20px;
font-weight: bold;
} }
.file-input { .file-input {
@ -483,17 +494,17 @@ onMounted(() => {
.input, .textarea, select.input { .input, .textarea, select.input {
width: 100%; width: 100%;
padding: 0.75rem; padding: 0.75rem;
border: 1px solid #bdc3c7; border: 1px solid #e0e0e0;
border-radius: 4px; border-radius: 8px;
font-size: 0.95rem; font-size: 0.95rem;
transition: border-color 0.3s ease; transition: border-color 0.3s ease;
background: #fff; background: #f9f9f9;
} }
.input:focus, .textarea:focus, select.input:focus { .input:focus, .textarea:focus, select.input:focus {
outline: none; outline: none;
border-color: #3498db; border-color: #ff88aa;
box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.08); box-shadow: 0 0 0 2px rgba(255, 136, 170, 0.08);
} }
.input-error { .input-error {
@ -514,10 +525,10 @@ onMounted(() => {
.submit-btn { .submit-btn {
width: 100%; width: 100%;
padding: 0.85rem; padding: 0.85rem;
background: linear-gradient(90deg, #3498db 60%, #6dd5fa 100%); background: #ff88aa;
color: white; color: white;
border: none; border: none;
border-radius: 4px; border-radius: 8px;
font-size: 1rem; font-size: 1rem;
font-weight: 600; font-weight: 600;
cursor: pointer; cursor: pointer;
@ -527,6 +538,34 @@ onMounted(() => {
} }
.submit-btn:hover { .submit-btn:hover {
background: linear-gradient(90deg, #2980b9 60%, #3498db 100%); background: #ff6699;
}
/* 花瓣动画(与背景呼应) */
@keyframes fall {
0% { transform: translateY(-100vh) rotate(0deg); opacity: 1; }
100% { transform: translateY(100vh) rotate(360deg); opacity: 0.5; }
}
.petal {
position: absolute;
width: 10px;
height: 15px;
background: pink;
border-radius: 50% 50% 0 0;
animation: fall 5s linear infinite;
z-index: -1;
}
/* 初始化花瓣可在mounted中动态生成此处简化 */
.change-info-container::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
animation: none; /* 避免重复动画 */
} }
</style> </style>

@ -12,34 +12,49 @@
maxlength="50" maxlength="50"
placeholder="请输入标题3-50个字符" placeholder="请输入标题3-50个字符"
required required
class="input-field"
/> />
</div> </div>
<!-- 分类选择 --> <!-- 分类选择 -->
<div class="form-row"> <div class="form-row">
<label>分类</label> <label>分类</label>
<select v-model="form.categoryId" required> <select v-model="form.categoryId" required class="input-field">
<option disabled value="">请选择分类</option> <option disabled value="">请选择分类</option>
<option v-for="cat in categories" :key="cat.id" :value="cat.id">{{ cat.name }}</option> <option v-for="cat in categories" :key="cat.id" :value="cat.id">{{ cat.name }}</option>
</select> </select>
</div> </div>
<!-- 图片上传 --> <!-- 图片上传自定义+号按钮 -->
<div class="form-row"> <div class="form-row">
<label>图片</label> <label>封面</label>
<input type="file" accept="image/*" @change="onFileChange" /> <div class="upload-wrapper">
<div v-if="form.imagePreview" class="img-preview"> <!-- 隐藏的文件选择input -->
<img :src="form.imagePreview" alt="预览" /> <input
<button type="button" @click="removeImage"></button> type="file"
accept="image/*"
@change="onFileChange"
id="image-upload"
class="hidden-input"
/>
<!-- 自定义+号按钮 -->
<label for="image-upload" class="upload-btn">
<span class="plus-icon">+</span>
</label>
<!-- 图片预览 -->
<div v-if="form.imagePreview" class="img-preview">
<img :src="form.imagePreview" alt="预览" class="preview-img" />
<button type="button" @click="removeImage" class="remove-btn">×</button>
</div>
</div> </div>
</div> </div>
<!-- 匿名发布选项放在内容编辑器下方或合适位置 --> <!-- 匿名发布选项 -->
<div class="form-row"> <div class="form-row anonymous-option">
<label> <label class="anonymous-label">
<input type="checkbox" v-model="form.status" true-value="1" false-value="0" /> <input type="checkbox" v-model="form.status" true-value="1" false-value="0" class="anonymous-checkbox" />
匿名发布 匿名发布
</label> </label>
</div> </div>
<!-- 内容编辑器 --> <!-- 内容编辑器 -->
@ -50,12 +65,13 @@
rows="8" rows="8"
placeholder="请输入帖子内容支持Markdown语法" placeholder="请输入帖子内容支持Markdown语法"
required required
class="textarea-field"
></textarea> ></textarea>
</div> </div>
<!-- 提交按钮 --> <!-- 提交按钮 -->
<div class="submit-area"> <div class="submit-area">
<button type="submit" :disabled="submitting"> <button type="submit" :disabled="submitting" class="submit-btn">
{{ submitting ? '发布中...' : '立即发布' }} {{ submitting ? '发布中...' : '立即发布' }}
</button> </button>
</div> </div>
@ -102,9 +118,7 @@ export default {
const onFileChange = async (e) => { const onFileChange = async (e) => {
const file = e.target.files[0] const file = e.target.files[0]
if (!file) return if (!file) return
//
form.value.imagePreview = URL.createObjectURL(file) form.value.imagePreview = URL.createObjectURL(file)
//
const fd = new FormData() const fd = new FormData()
fd.append('file', file) fd.append('file', file)
try { try {
@ -132,6 +146,10 @@ export default {
showResult('error', '请填写完整信息') showResult('error', '请填写完整信息')
return return
} }
if (!form.value.image) {
showResult('error', '请先选择帖子封面')
return
}
if (form.value.title.length < 3 || form.value.title.length > 50) { if (form.value.title.length < 3 || form.value.title.length > 50) {
showResult('error', '标题长度需3-50字符') showResult('error', '标题长度需3-50字符')
return return
@ -152,7 +170,6 @@ export default {
const res = await request.post('/post', postData) const res = await request.post('/post', postData)
if (res.code === 200) { if (res.code === 200) {
showResult('success', '帖子发布成功') showResult('success', '帖子发布成功')
console.log(res.data);
setTimeout(() => { setTimeout(() => {
router.push(`/`) router.push(`/`)
}, 1200) }, 1200)
@ -187,94 +204,181 @@ export default {
</script> </script>
<style scoped> <style scoped>
.upload-wrapper {
display: flex;
align-items: center;
gap: 15px;
}
.hidden-input {
position: absolute;
opacity: 0;
width: 0;
height: 0;
}
.upload-btn {
width: 80px;
height: 80px;
border: 2px dashed #ff88aa;
border-radius: 8px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s;
}
.upload-btn:hover {
background-color: #fff5f8;
border-color: #ff6699;
}
.plus-icon {
font-size: 32px;
color: #ff88aa;
font-weight: lighter;
}
.img-preview {
position: relative;
display: inline-block;
}
.preview-img {
width: 80px;
height: 80px;
object-fit: cover;
border-radius: 8px;
border: 1px solid #eee;
}
.remove-btn {
position: absolute;
top: -8px;
right: -8px;
width: 20px;
height: 20px;
background: #ff6699;
color: white;
border: none;
border-radius: 50%;
padding: 0;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
}
.post-container { .post-container {
max-width: 600px; max-width: 600px;
margin: 30px auto; margin: 30px auto;
padding: 30px; padding: 30px;
background: #fff; background: #fff;
border-radius: 8px; border-radius: 12px;
box-shadow: 0 2px 12px 0 rgba(0,0,0,0.08); box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
} }
.page-title { .page-title {
text-align: center; text-align: center;
margin-bottom: 24px; margin-bottom: 24px;
font-size: 1.6em; font-size: 1.8em;
color: #2c3e50; color: #2c3e50;
border-bottom: 2px solid #ff88aa;
padding-bottom: 10px;
} }
.editor-wrapper {
padding: 20px;
}
.form-row { .form-row {
margin-bottom: 18px; margin-bottom: 20px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.form-row label { .form-row label {
font-weight: bold; font-weight: bold;
margin-bottom: 6px; margin-bottom: 8px;
color: #333;
} }
.form-row input[type="text"],
.form-row select, .input-field,
.form-row textarea { .textarea-field {
padding: 8px; padding: 10px;
border: 1px solid #dcdcdc; border: 1px solid #e0e0e0;
border-radius: 4px; border-radius: 8px;
font-size: 15px; font-size: 15px;
transition: border-color 0.3s;
} }
.form-row textarea {
resize: vertical; .input-field:focus,
.textarea-field:focus {
outline: none;
border-color: #ff88aa;
} }
.img-preview {
margin-top: 8px; .anonymous-option {
position: relative; flex-direction: row;
display: inline-block; align-items: center;
justify-content: flex-start;
margin-bottom: 15px;
} }
.img-preview img {
max-width: 120px; .anonymous-label {
max-height: 120px; display: flex;
border-radius: 4px; align-items: center;
border: 1px solid #eee; gap: 8px;
color: #666;
} }
.img-preview button {
position: absolute; .anonymous-checkbox {
top: 2px; width: 18px;
right: 2px; height: 18px;
background: #f56c6c; accent-color: #ff88aa;
color: #fff;
border: none;
border-radius: 3px;
padding: 2px 8px;
cursor: pointer;
font-size: 12px;
} }
.textarea-field {
resize: vertical;
min-height: 150px;
}
.submit-area { .submit-area {
text-align: center; text-align: center;
margin-top: 24px; margin-top: 30px;
} }
.submit-area button {
background: #409eff; .submit-btn {
color: #fff; background: #ff88aa;
color: white;
border: none; border: none;
padding: 10px 32px; padding: 12px 40px;
border-radius: 4px; border-radius: 8px;
font-size: 16px; font-size: 16px;
cursor: pointer; cursor: pointer;
transition: background 0.3s;
} }
.submit-area button:disabled {
background: #b3d8ff; .submit-btn:disabled {
background: #ffcccc;
cursor: not-allowed; cursor: not-allowed;
} }
.submit-btn:hover {
background: #ff6699;
}
.result-alert { .result-alert {
margin-top: 20px; margin-top: 20px;
padding: 12px 18px; padding: 15px 20px;
border-radius: 4px; border-radius: 8px;
font-size: 15px; font-size: 15px;
text-align: center; text-align: center;
} }
.result-alert.success { .result-alert.success {
background: #e1f3d8; background: #e1f3d8;
color: #3a7a1c; color: #3a7a1c;
} }
.result-alert.error { .result-alert.error {
background: #fde2e2; background: #fde2e2;
color: #c0392b; color: #c0392b;

Loading…
Cancel
Save