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

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

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

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

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