制作博客主页及单篇博客界面
This commit is contained in:
parent
82380bdc86
commit
add84cc1c2
@ -1,4 +1,6 @@
|
||||
<script setup>
|
||||
import {setRandomBGCL} from "../utils/randomBGCL.js";
|
||||
|
||||
defineProps({
|
||||
blog: Object,
|
||||
});
|
||||
@ -6,12 +8,17 @@ defineProps({
|
||||
|
||||
<template>
|
||||
<router-link :to="'/blog/'+blog.id">
|
||||
<div class="blog-box">
|
||||
<img :src="blog.image" alt="Blog image" class="blog-image" />
|
||||
<div class="blog-box" :style="{background: setRandomBGCL(blog.title)}">
|
||||
|
||||
<div class="image-container" :style="{background: setRandomBGCL(blog.title)}">
|
||||
<div class="cover-character">{{ blog.title[0] }}</div>
|
||||
<img v-if="blog.image" :src="blog.image" alt="Blog image" class="blog-image" />
|
||||
</div>
|
||||
|
||||
<div class="blog-details">
|
||||
<h3>{{ blog.title }}</h3>
|
||||
<p class="author">By {{ blog.author }}</p>
|
||||
<p class="time"> {{ blog.creatTime }}</p>
|
||||
<p class="author">By uid{{ blog.poster }}</p>
|
||||
<p class="time"> {{ blog.post_date }}</p>
|
||||
<div class="tags">
|
||||
<span v-for="tag in blog.tags" :key="tag" class="tag">{{ tag }}</span>
|
||||
</div>
|
||||
@ -32,7 +39,19 @@ defineProps({
|
||||
cursor: pointer;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.image-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.image-container .cover-character {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 200px;;
|
||||
font-family: 'huangkaihua', Tahoma, Geneva, Verdana, sans-serif;
|
||||
color: gray;
|
||||
mix-blend-mode: difference;
|
||||
}
|
||||
.blog-image {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
@ -40,9 +59,14 @@ defineProps({
|
||||
}
|
||||
|
||||
.blog-details {
|
||||
background: #111111;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
.theme-light .blog-details {
|
||||
background: white;
|
||||
}
|
||||
|
||||
.blog-details h3 {
|
||||
margin: 5px 0;
|
||||
|
@ -1,21 +1,21 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import {onMounted, ref} from 'vue';
|
||||
import BlogBox from "../components/Blog_box.vue";
|
||||
import {getRandomIMG} from "../utils/randomIMG.js";
|
||||
import PagingController from "../components/PagingController.vue";
|
||||
import api from "../utils/axios.js";
|
||||
import store from "../store/index.js";
|
||||
|
||||
const tags = ref([
|
||||
{ name: '家猪饲养', active: false },
|
||||
{ name: '猪舍管理', active: false },
|
||||
]);
|
||||
const tags = ref([]);
|
||||
|
||||
|
||||
const searchQuery = ref('');
|
||||
|
||||
const blogs = ref([
|
||||
{ title: '如何提升家猪的生长速度', author: '张强', image: null, creatTime: '2025-02-12 08:30', tags: ['家猪饲养', '生长调控'], id: 1}])
|
||||
|
||||
const blogs = ref(store.state.sessionStore.blog?.blogs || []);
|
||||
const amount = ref(store.state.sessionStore.blog?.blogAmount);
|
||||
const currentPage = ref(store.state.sessionStore.blog?.BlogCurrentPage);
|
||||
|
||||
const loadingText = ref('正在加载...');
|
||||
|
||||
const toggleTag = (tag) => {
|
||||
tag.active = !tag.active;
|
||||
@ -28,23 +28,54 @@ const filterBlogs = () => {
|
||||
return (matchesTags || tags.value.every(t => !t.active)) && matchesSearch;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const refreshBlogs = async (page, size, sort) => {
|
||||
size = size || 20;
|
||||
sort = sort || 'post_date';
|
||||
const tempPage = currentPage.value;
|
||||
currentPage.value = page;
|
||||
const blogsResponse = await api.get(`/blogs?page=${page}&size=${size}&sort=${sort}`);
|
||||
if (!blogsResponse.amount) {
|
||||
currentPage.value = tempPage;
|
||||
return;
|
||||
}
|
||||
amount.value = Math.ceil(blogsResponse.amount / size);
|
||||
blogs.value = blogsResponse.blogs;
|
||||
currentPage.value = page;
|
||||
store.commit('setSessionValue', {key: 'blog', value: {blogs: blogs, blogAmount: amount, blogCurrentPage: currentPage}});
|
||||
console.log(store.state.sessionStore.blog)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
refreshBlogs(1, 20, 'post_date');
|
||||
console.log(blogs.value)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container">
|
||||
<div class="filters">
|
||||
<div class="tags">
|
||||
<Transition name="fade">
|
||||
<div class="filters" v-if="tags">
|
||||
<div class="tags">
|
||||
<span v-for="tag in tags" :key="tag.name" :class="{ active: tag.active }" @click="toggleTag(tag)">
|
||||
{{ tag.name }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- <input type="text" v-model="searchQuery" placeholder="搜索博客..." class="search-bar" />-->
|
||||
</div>
|
||||
<input type="text" v-model="searchQuery" placeholder="搜索博客..." class="search-bar" />
|
||||
</div>
|
||||
</Transition>
|
||||
<Transition name="fade">
|
||||
<div class="blogs" v-if="amount">
|
||||
<BlogBox v-for="blog in blogs" :key="blog.title" :blog="blog"/>
|
||||
</div>
|
||||
<span v-else style="position: absolute">正在加载...</span>
|
||||
</Transition>
|
||||
<Transition name="fade">
|
||||
<PagingController v-if="amount" :current-page="currentPage" :amount="amount"
|
||||
:go-page-func="refreshBlogs"></PagingController>
|
||||
</Transition>
|
||||
|
||||
<div class="blogs">
|
||||
<BlogBox v-for="blog in filterBlogs()" :key="blog.title" :blog="blog" />
|
||||
</div>
|
||||
<PagingController :current-page="3" :amount="10" :go-page-func="()=>{}"></PagingController>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -73,7 +104,7 @@ const filterBlogs = () => {
|
||||
|
||||
.tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap; /* 允许标签换行 */
|
||||
flex-wrap: wrap; /* 允许标签换行 */
|
||||
gap: 10px;
|
||||
justify-content: center; /* 使标签居中 */
|
||||
max-width: 1200px;
|
||||
@ -119,4 +150,21 @@ const filterBlogs = () => {
|
||||
::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.5s ease, transform 0.5s ease;
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(20px); /* 从下方 20px 进入 */
|
||||
}
|
||||
|
||||
.fade-enter-to,
|
||||
.fade-leave-from {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
</style>
|
||||
|
@ -236,7 +236,7 @@ const submitBlog = async () => {
|
||||
formData.append("title", title);
|
||||
formData.append("content", content);
|
||||
formData.append("allow_comments", allowComments);
|
||||
formData.append("draft", true);
|
||||
formData.append("draft", false);
|
||||
|
||||
console.log(images);
|
||||
images.forEach((imgData) => {
|
||||
@ -252,7 +252,7 @@ const submitBlog = async () => {
|
||||
},
|
||||
}).then(response => {
|
||||
if (response.code === 0) {
|
||||
swal.window('success', `提交成功, 博客id${response.blogId || '未找到blogId字段'}`, 'ok', '好的');
|
||||
swal.window('success', `提交成功, 博客id${response.blogId || '未找到blogId字段'}`,'', 'ok', '好的');
|
||||
return;
|
||||
}
|
||||
swal.window('error', '提交失败', `code字段为${response.code}; 错误信息: ${response.message}`, 'ok', '好的');
|
||||
|
@ -1,24 +1,157 @@
|
||||
<script setup>
|
||||
import {onMounted, ref} from "vue";
|
||||
import {ref, onMounted, computed} from 'vue'
|
||||
import {useRoute} from 'vue-router'
|
||||
import api from '../../utils/axios.js'
|
||||
import {blogImage, userProfile} from '../../utils/imageResource'
|
||||
import router from "../../router/index.js";
|
||||
|
||||
const blogDisplay = ref(null);
|
||||
// 获取路由参数
|
||||
const route = useRoute()
|
||||
const blog = ref(null)
|
||||
const posterInfo = ref(null)
|
||||
const id = route.params.id
|
||||
|
||||
onMounted(() => {
|
||||
// 在组件挂载时获取数据
|
||||
onMounted(async () => {
|
||||
// 获取博客数据
|
||||
const blogResponse = await api.get(`/blogs/${id}`)
|
||||
if (blogResponse.code === 1) {
|
||||
await router.push('/404')
|
||||
return;
|
||||
}
|
||||
if (blogResponse.code === 0) {
|
||||
blog.value = blogResponse.blog
|
||||
// 获取发布者信息
|
||||
const posterResponse = await api.get(`/userinfo?UID=${blog.value.poster}`)
|
||||
if (posterResponse.code === 0) {
|
||||
posterInfo.value = posterResponse.info
|
||||
} else {
|
||||
console.error('Failed to fetch poster info:', posterResponse)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 处理博客内容中的 <holder> 占位符,替换为 <img> 标签
|
||||
const processedContent = computed(() => {
|
||||
if (!blog.value || !blog.value.content) return ''
|
||||
let content = blog.value.content
|
||||
const regex = /<holder image ([\w.]+\.\w+) width=([\d.]+) height=([\d.]+)>/g
|
||||
return content.replace(regex, (match, filename, width, height) => {
|
||||
const url = blogImage(filename) // filename 是完整的 "1a2b3c4d.png"
|
||||
return `<img src="${url}" width="${width}" height="${height}" alt="Blog Image" />`
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container">
|
||||
<div v-html="blogDisplay"></div>
|
||||
<div class="blog-container">
|
||||
<!-- 使用 Transition 包裹博客内容 -->
|
||||
<Transition name="fade">
|
||||
<div v-if="blog" class="blog-wrapper">
|
||||
<h1>{{ blog.title }}</h1>
|
||||
<div class="blog-meta">
|
||||
<div class="avatar">
|
||||
<img
|
||||
v-if="posterInfo"
|
||||
:src="userProfile(posterInfo.profile)"
|
||||
alt="Poster Avatar"
|
||||
/>
|
||||
</div>
|
||||
<span>{{ posterInfo?.username || '' }}</span>
|
||||
<span>{{ blog.post_date }}</span>
|
||||
</div>
|
||||
<div class="blog-content" v-html="processedContent"></div>
|
||||
<div class="comments-section">
|
||||
<Transition name="fade">
|
||||
<p v-if="blog.allow_comments === 1">评论区域待实现</p>
|
||||
<p v-else>不允许评论</p>
|
||||
</Transition>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 当 blog 未加载时显示加载提示 -->
|
||||
<div v-else class="loading">加载中...</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
.loading {
|
||||
height: 0;
|
||||
text-align: center;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.blog-wrapper {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
height: calc(100vh - 130px);
|
||||
}
|
||||
|
||||
.blog-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
|
||||
overflow: auto;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px 30px 50px;
|
||||
background-color: #333; /* 默认深色背景 */
|
||||
color: #fff; /* 默认深色文字 */
|
||||
}
|
||||
|
||||
.blog-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.avatar, .avatar img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.blog-content {
|
||||
margin-bottom: 20px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.comments-section {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
/* 浅色模式 */
|
||||
.theme-light .blog-container {
|
||||
background-color: #ffffff;
|
||||
color: #333;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.5s ease, transform 0.5s ease;
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(20px); /* 从下方 20px 进入 */
|
||||
}
|
||||
|
||||
.fade-enter-to,
|
||||
.fade-leave-from {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
</style>
|
@ -31,6 +31,7 @@ import Editor from "../pages/Editor.vue";
|
||||
import NotFound from "../pages/errorPages/notFound.vue";
|
||||
|
||||
import Test_page from "../pages/Test_page.vue";
|
||||
import SingleBlog_page from "../pages/blogPages/SingleBlog_page.vue";
|
||||
|
||||
const routes = [
|
||||
{path: '/404',
|
||||
@ -52,6 +53,11 @@ const routes = [
|
||||
name: 'Blog',
|
||||
component: Blog_home,
|
||||
meta: {title: '博客'}
|
||||
}, {
|
||||
path: '/blog/:id',
|
||||
name: 'Blogs',
|
||||
component: SingleBlog_page,
|
||||
meta: {title: '博客'}
|
||||
}, {
|
||||
path: '/projects',
|
||||
name: 'Projects',
|
||||
|
@ -12,6 +12,7 @@ const store = createStore({
|
||||
editStore: {},
|
||||
editAutoSave: {on: true, interval: 30000},
|
||||
demosLocal: {},
|
||||
sessionStore: {},
|
||||
},
|
||||
mutations: {
|
||||
toggleTheme(state) {
|
||||
@ -41,6 +42,12 @@ const store = createStore({
|
||||
deleteLocalDemoValue(state, obj) {
|
||||
delete state.demosLocal[obj.demo][obj.value];
|
||||
},
|
||||
setSessionValue(state, obj) {
|
||||
state.sessionStore[obj.key] = {...state.sessionStore[obj.key], ...obj.value}
|
||||
},
|
||||
deleteSessionValue(state, obj) {
|
||||
delete state.sessionStore[obj.key][obj.value];
|
||||
},
|
||||
saveEdit(state, obj) {
|
||||
state.editStore = {...state.editStore, ...obj};
|
||||
},
|
||||
|
@ -2,4 +2,8 @@ import {getDomain} from "./getDomain.js";
|
||||
|
||||
export function blogImage(id) {
|
||||
return `https://${getDomain()}/data/blog/images/${id}`;
|
||||
}
|
||||
|
||||
export function userProfile(id) {
|
||||
return `https://${getDomain()}/data/user/profile/${id}`
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user