实现发帖但帖子仍无法展示

main
Hacker-00001 4 weeks ago
parent 538276c248
commit e683171570

@ -1,36 +1,36 @@
##本地开发环境 ###本地开发环境
lj: # lj:
db: # db:
host: localhost # host: localhost
password: 123456 # password: 123456
redis: # redis:
host: localhost # host: localhost
port: 6379 # port: 6379
password: 123456 # password: 123456
rabbitmq: # rabbitmq:
host: localhost # host: localhost
port: 5672 # port: 5672
username: root # username: root
password: 123456 # password: 123456
minio: # minio:
endpoint: http://localhost:9000 # endpoint: http://localhost:9000
accessKey: root # accessKey: root
secretKey: 12345678 # secretKey: 12345678
#lj: lj:
# db: db:
# host: 192.168.125.128 host: 192.168.125.128
# password: MySQL@5678 password: MySQL@5678
# redis: redis:
# host: 192.168.125.128 host: 192.168.125.128
# port: 6379 port: 6379
# password: Redis@9012 password: Redis@9012
# rabbitmq: rabbitmq:
# host: 192.168.125.128 host: 192.168.125.128
# port: 5672 port: 5672
# username: rabbit_admin username: rabbit_admin
# password: Rabbit@3456 password: Rabbit@3456
# minio: minio:
# endpoint: http://192.168.125.128:9000 endpoint: http://192.168.125.128:9000
# accessKey: minio_admin accessKey: minio_admin
# secretKey: Minio@1234 secretKey: Minio@1234

@ -140,15 +140,14 @@ export const usePostDetailStore = defineStore("postDetail", {
likeCount, likeCount,
favoriteCount, favoriteCount,
viewCount, viewCount,
author: { // 新增 author 字段
userId,
userName,
userAvatar,
followers: 1234, // 先预设粉丝占位
},
}; };
// 用户信息
this.userInfo = {
userId,
userName,
userAvatar: userAvatar,
followers:1234,//先预设粉丝占位
};
// 其余字段 // 其余字段
this.isLike = isLike; this.isLike = isLike;
// 获取评论列表 // 获取评论列表

@ -1,317 +1,272 @@
<template> <template>
<div class="post-container"> <div class="post-container">
<h1 class="page-title">发布新帖子</h1> <h1 class="page-title">发布新帖子</h1>
<div class="editor-wrapper"> <div class="editor-wrapper">
<el-form <form @submit.prevent="submitForm">
ref="postFormRef"
:model="form"
:rules="rules"
label-width="80px"
@submit.prevent="submitForm"
>
<!-- 标题输入 --> <!-- 标题输入 -->
<el-form-item label="标题" prop="title"> <div class="form-row">
<el-input <label>标题</label>
<input
v-model="form.title" v-model="form.title"
placeholder="请输入标题3-50个字符" type="text"
maxlength="50" maxlength="50"
show-word-limit placeholder="请输入标题3-50个字符"
required
/> />
</el-form-item> </div>
<!-- 分类选择 --> <!-- 分类选择 -->
<el-form-item label="分类" prop="categoryId"> <div class="form-row">
<el-select <label>分类</label>
v-model="form.categoryId" <select v-model="form.categoryId" required>
placeholder="请选择分类" <option disabled value="">请选择分类</option>
class="category-selector" <option v-for="cat in categories" :key="cat.id" :value="cat.id">{{ cat.name }}</option>
> </select>
<el-option </div>
v-for="category in categories"
:key="category.id"
:label="category.name"
:value="category.id"
/>
</el-select>
</el-form-item>
<!-- 图片上传 --> <!-- 图片上传 -->
<el-form-item label="图片" prop="image"> <div class="form-row">
<el-upload <label>图片</label>
action="/api/upload" <input type="file" accept="image/*" @change="onFileChange" />
list-type="picture-card" <div v-if="form.imagePreview" class="img-preview">
:file-list="fileList" <img :src="form.imagePreview" alt="预览" />
:on-success="handleUploadSuccess" <button type="button" @click="removeImage"></button>
:on-remove="handleRemove" </div>
:limit="3" </div>
>
<el-icon><Plus /></el-icon>
</el-upload>
</el-form-item>
<!-- 内容编辑器 --> <!-- 内容编辑器 -->
<el-form-item label="内容" prop="content"> <div class="form-row">
<el-input <label>内容</label>
<textarea
v-model="form.content" v-model="form.content"
type="textarea" rows="8"
:rows="8"
placeholder="请输入帖子内容支持Markdown语法" placeholder="请输入帖子内容支持Markdown语法"
resize="none" required
/> ></textarea>
<div class="markdown-tips"> </div>
<span>Markdown语法支持</span>
<el-link type="info" href="/help/markdown" target="_blank">查看帮助</el-link>
</div>
</el-form-item>
<!-- 提交按钮 --> <!-- 提交按钮 -->
<div class="submit-area"> <div class="submit-area">
<el-button <button type="submit" :disabled="submitting">
type="primary"
size="large"
:loading="submitting"
@click="submitForm"
>
{{ submitting ? '发布中...' : '立即发布' }} {{ submitting ? '发布中...' : '立即发布' }}
</el-button> </button>
<el-button plain @click="saveDraft">稿</el-button>
</div> </div>
</el-form> </form>
</div> </div>
<!-- 操作反馈 --> <!-- 操作反馈 -->
<el-alert <div v-if="submitResult.visible" :class="['result-alert', submitResult.type]">
v-if="submitResult.visible" {{ submitResult.title }}
:title="submitResult.title" </div>
:type="submitResult.type"
:closable="false"
class="result-alert"
/>
</div> </div>
</template> </template>
<script setup> <script>
import { ref, reactive, onMounted } from 'vue'; import request from '@/utils/request'
import { useRouter } from 'vue-router'; import { ref } from 'vue'
import { Plus } from '@element-plus/icons-vue'; import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus';
import request from '@/utils/request';
import { useUserStore } from '@/stores/user';
const router = useRouter();
const userStore = useUserStore();
const postFormRef = ref(null);
//
const submitting = ref(false);
const categories = ref([]);
const fileList = ref([]);
const form = reactive({
title: '',
content: '',
categoryId: null,
image: '',
status: 0 // 0
});
const rules = { export default {
title: [ setup() {
{ required: true, message: '标题不能为空', trigger: 'blur' }, const router = useRouter()
{ min: 3, max: 50, message: '长度在3到50个字符', trigger: 'blur' } const submitting = ref(false)
], const categories = ref([
content: [ { id: 1, name: '学习' },
{ required: true, message: '内容不能为空', trigger: 'blur' }, { id: 2, name: '娱乐' },
{ min: 10, message: '内容至少10个字符', trigger: 'blur' } { id: 3, name: '二手交易' }
], ])
categoryId: [ const form = ref({
{ required: true, message: '请选择分类', trigger: 'change' } title: '',
] content: '',
}; categoryId: '',
image: '',
imagePreview: '',
status: 0
})
const submitResult = ref({
visible: false,
type: 'success',
title: ''
})
const submitResult = reactive({ //
visible: false, const onFileChange = async (e) => {
type: 'success', const file = e.target.files[0]
title: '' if (!file) return
}); //
form.value.imagePreview = URL.createObjectURL(file)
// //
const checkLoginStatus = () => { const fd = new FormData()
// fd.append('file', file)
if (!userStore.isLoggedIn) { try {
// localStorage const res = await request.post('/post/cover', fd, {
const userId = localStorage.getItem('userId'); headers: { 'Content-Type': 'multipart/form-data' }
const username = localStorage.getItem('username'); })
const accessToken = localStorage.getItem('accessToken'); if (res.code === 200 && res.data) {
const refreshToken = localStorage.getItem('refreshToken'); form.value.image = res.data
const avatar = localStorage.getItem('avatar'); } else {
const role = localStorage.getItem('role'); showResult('error', res.msg || '图片上传失败')
}
if (userId && username && accessToken && refreshToken) { } catch (err) {
userStore.login({ showResult('error', '图片上传失败')
userid: userId, }
userName: username,
avatar: avatar || '',
role: role || 1,
accessToken,
refreshToken
});
console.log('从localStorage恢复用户登录状态:', username);
return true;
} else {
ElMessage.warning('请先登录');
router.push('/');
return false;
} }
}
return true;
};
// const removeImage = () => {
const loadCategories = async () => { form.value.image = ''
try { form.value.imagePreview = ''
const response = await request.get('/post/category');
if (response && response.code === 200) {
categories.value = response.data || [];
} }
} catch (error) {
console.error('加载分类失败:', error);
ElMessage.error('加载分类失败,请刷新页面重试');
}
};
// //
const handleUploadSuccess = (response) => { const submitForm = async () => {
form.image = form.image if (!form.value.title || !form.value.content || !form.value.categoryId) {
? `${form.image},${response.data.url}` showResult('error', '请填写完整信息')
: response.data.url; return
}; }
if (form.value.title.length < 3 || form.value.title.length > 50) {
const handleRemove = (file) => { showResult('error', '标题长度需3-50字符')
if (!form.image) return; return
const urls = form.image.split(','); }
form.image = urls.filter(url => url !== file.url).join(','); if (form.value.content.length < 10) {
}; showResult('error', '内容至少10个字符')
return
}
submitting.value = true
try {
const postData = {
title: form.value.title,
content: form.value.content,
categoryId: form.value.categoryId,
image: form.value.image,
status: form.value.status
}
const res = await request.post('/post', postData)
if (res.code === 200) {
showResult('success', '帖子发布成功')
setTimeout(() => {
router.push(`/post/${res.data}`)
}, 1200)
} else {
showResult('error', res.msg || '发布失败')
}
} catch (err) {
showResult('error', '网络错误')
} finally {
submitting.value = false
}
}
// const showResult = (type, title) => {
const submitForm = async () => { submitResult.value = { visible: true, type, title }
if (!checkLoginStatus() || !postFormRef.value) return;
try {
submitting.value = true;
await postFormRef.value.validate();
const response = await request.post('/post/publish', form);
if (response && response.code === 200) {
showResult('success', '帖子发布成功');
setTimeout(() => { setTimeout(() => {
router.push(`/post/${response.data}`); submitResult.value.visible = false
}, 1500); }, 2000)
} else {
showResult('error', response.msg || '发布失败');
} }
} catch (error) {
console.error('发布失败:', error);
showResult('error', error.response?.data?.msg || '网络错误');
} finally {
submitting.value = false;
}
};
// 稿 return {
const saveDraft = async () => { form,
if (!checkLoginStatus()) return; categories,
submitting,
try { submitResult,
const response = await request.post('/post/draft', { onFileChange,
...form, removeImage,
status: 1 // 1稿 submitForm
});
if (response && response.code === 200) {
showResult('success', '草稿保存成功');
} else {
showResult('error', response.msg || '保存失败');
} }
} catch (error) {
console.error('保存草稿失败:', error);
showResult('error', error.response?.data?.msg || '网络错误');
} }
}; }
//
const showResult = (type, message) => {
submitResult.visible = true;
submitResult.type = type;
submitResult.title = message;
setTimeout(() => {
submitResult.visible = false;
}, 3000);
};
//
onMounted(() => {
if (checkLoginStatus()) {
loadCategories();
}
});
</script> </script>
<style scoped> <style scoped>
.post-container { .post-container {
max-width: 800px; max-width: 600px;
margin: 80px auto 20px; /* 增加顶部边距,避免被导航栏遮挡 */ margin: 30px auto;
padding: 30px; padding: 30px;
background-color: #f9f9f9; background: #fff;
border-radius: 8px; border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0,0,0,0.08);
} }
.page-title { .page-title {
color: #2c3e50;
text-align: center; text-align: center;
margin-bottom: 30px; margin-bottom: 24px;
font-size: 1.8em; font-size: 1.6em;
color: #2c3e50;
} }
.editor-wrapper { .editor-wrapper {
background: white; padding: 20px;
padding: 25px;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1);
} }
.form-row {
.category-selector { margin-bottom: 18px;
width: 100%; display: flex;
flex-direction: column;
}
.form-row label {
font-weight: bold;
margin-bottom: 6px;
}
.form-row input[type="text"],
.form-row select,
.form-row textarea {
padding: 8px;
border: 1px solid #dcdcdc;
border-radius: 4px;
font-size: 15px;
}
.form-row textarea {
resize: vertical;
}
.img-preview {
margin-top: 8px;
position: relative;
display: inline-block;
}
.img-preview img {
max-width: 120px;
max-height: 120px;
border-radius: 4px;
border: 1px solid #eee;
}
.img-preview button {
position: absolute;
top: 2px;
right: 2px;
background: #f56c6c;
color: #fff;
border: none;
border-radius: 3px;
padding: 2px 8px;
cursor: pointer;
font-size: 12px;
} }
.submit-area { .submit-area {
text-align: center; text-align: center;
margin-top: 30px; margin-top: 24px;
padding-top: 20px;
border-top: 1px solid #eee;
} }
.submit-area button {
.markdown-tips { background: #409eff;
margin-top: 10px; color: #fff;
font-size: 0.9em; border: none;
color: #666; padding: 10px 32px;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
}
.submit-area button:disabled {
background: #b3d8ff;
cursor: not-allowed;
} }
.result-alert { .result-alert {
margin-top: 20px; margin-top: 20px;
} padding: 12px 18px;
/* 保持与通知页面一致的输入框样式 */
:deep(.el-textarea__inner),
:deep(.el-input__inner) {
border-radius: 4px; border-radius: 4px;
border: 1px solid #e4e4e4; font-size: 15px;
transition: border-color 0.2s; text-align: center;
} }
.result-alert.success {
:deep(.el-input__inner):focus { background: #e1f3d8;
border-color: #409eff; color: #3a7a1c;
}
.result-alert.error {
background: #fde2e2;
color: #c0392b;
} }
</style> </style>
Loading…
Cancel
Save