加入BlogContent工具栏,完成点赞功能
This commit is contained in:
34
Server/src/APIs/BlogLike.ts
Normal file
34
Server/src/APIs/BlogLike.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { API } from "../Plugs/API/API";
|
||||||
|
import ServerStdResponse from "../ServerStdResponse";
|
||||||
|
import MySQLConnection from '../Plugs/MySQLConnection'
|
||||||
|
import { Buffer } from 'buffer';
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
|
||||||
|
// 点赞
|
||||||
|
class BlogLike extends API {
|
||||||
|
constructor() {
|
||||||
|
super('POST', '/blogLike');
|
||||||
|
}
|
||||||
|
private defaultAccessLevel = 6;
|
||||||
|
|
||||||
|
public async onRequset(data: any, res: any) {
|
||||||
|
let { bloguuid } = data;
|
||||||
|
if (!bloguuid || bloguuid.length != 32) {
|
||||||
|
return res.json(ServerStdResponse.INVALID_PARAMS);
|
||||||
|
}
|
||||||
|
|
||||||
|
let blogLikeRes = await MySQLConnection.execute('UPDATE blog SET like_count = like_count + 1 WHERE access_level > ? AND uuid = ? ', [this.defaultAccessLevel, bloguuid]);
|
||||||
|
if (!blogLikeRes) {
|
||||||
|
this.logger.error('点赞博客时,数据库发生错误');
|
||||||
|
return res.json(ServerStdResponse.SERVER_ERROR);
|
||||||
|
}
|
||||||
|
if (blogLikeRes.affectedRows != 1) {
|
||||||
|
this.logger.warn('查询的博客不存在或不可见', bloguuid);
|
||||||
|
return res.json(ServerStdResponse.BLOG.NOTFOUND);
|
||||||
|
}
|
||||||
|
return res.json(ServerStdResponse.OK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BlogLike;
|
||||||
@@ -6,6 +6,7 @@ import GetTest from "../APIs/GetTest";
|
|||||||
import GetResourceList from "../APIs/GetResourceList";
|
import GetResourceList from "../APIs/GetResourceList";
|
||||||
import GetBlogList from "../APIs/GetBlogList";
|
import GetBlogList from "../APIs/GetBlogList";
|
||||||
import GetBlogContent from "../APIs/GetBlogContent";
|
import GetBlogContent from "../APIs/GetBlogContent";
|
||||||
|
import BlogLike from "../APIs/BlogLike";
|
||||||
|
|
||||||
class Server {
|
class Server {
|
||||||
private logger = new Logger('Server');
|
private logger = new Logger('Server');
|
||||||
@@ -22,6 +23,7 @@ class Server {
|
|||||||
this.apiLoader.add(GetResourceList);
|
this.apiLoader.add(GetResourceList);
|
||||||
this.apiLoader.add(GetBlogList);
|
this.apiLoader.add(GetBlogList);
|
||||||
this.apiLoader.add(GetBlogContent);
|
this.apiLoader.add(GetBlogContent);
|
||||||
|
this.apiLoader.add(BlogLike);
|
||||||
|
|
||||||
this.apiLoader.start(config.apiPort);
|
this.apiLoader.start(config.apiPort);
|
||||||
}
|
}
|
||||||
|
|||||||
2
tonecn/components.d.ts
vendored
2
tonecn/components.d.ts
vendored
@@ -8,8 +8,10 @@ export {}
|
|||||||
declare module 'vue' {
|
declare module 'vue' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
Agreement: typeof import('./src/components/Common/Agreement.vue')['default']
|
Agreement: typeof import('./src/components/Common/Agreement.vue')['default']
|
||||||
|
BlogContentToolBar: typeof import('./src/components/Blog/BlogContentToolBar.vue')['default']
|
||||||
BlogToolBar: typeof import('./src/components/Blog/BlogToolBar.vue')['default']
|
BlogToolBar: typeof import('./src/components/Blog/BlogToolBar.vue')['default']
|
||||||
ElButton: typeof import('element-plus/es')['ElButton']
|
ElButton: typeof import('element-plus/es')['ElButton']
|
||||||
|
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||||
ElEmpty: typeof import('element-plus/es')['ElEmpty']
|
ElEmpty: typeof import('element-plus/es')['ElEmpty']
|
||||||
ElInput: typeof import('element-plus/es')['ElInput']
|
ElInput: typeof import('element-plus/es')['ElInput']
|
||||||
ElPopover: typeof import('element-plus/es')['ElPopover']
|
ElPopover: typeof import('element-plus/es')['ElPopover']
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"type-check": "vue-tsc --build --force"
|
"type-check": "vue-tsc --build --force"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@element-plus/icons-vue": "^2.3.1",
|
||||||
"axios": "^1.6.8",
|
"axios": "^1.6.8",
|
||||||
"highlight.js": "^11.10.0",
|
"highlight.js": "^11.10.0",
|
||||||
"marked": "^14.1.0",
|
"marked": "^14.1.0",
|
||||||
|
|||||||
4
tonecn/pnpm-lock.yaml
generated
4
tonecn/pnpm-lock.yaml
generated
@@ -5,6 +5,9 @@ settings:
|
|||||||
excludeLinksFromLockfile: false
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@element-plus/icons-vue':
|
||||||
|
specifier: ^2.3.1
|
||||||
|
version: 2.3.1(vue@3.4.27)
|
||||||
axios:
|
axios:
|
||||||
specifier: ^1.6.8
|
specifier: ^1.6.8
|
||||||
version: 1.6.8
|
version: 1.6.8
|
||||||
@@ -99,7 +102,6 @@ packages:
|
|||||||
vue: ^3.2.0
|
vue: ^3.2.0
|
||||||
dependencies:
|
dependencies:
|
||||||
vue: 3.4.27(typescript@5.4.5)
|
vue: 3.4.27(typescript@5.4.5)
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@esbuild/aix-ppc64@0.20.2:
|
/@esbuild/aix-ppc64@0.20.2:
|
||||||
resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==}
|
resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==}
|
||||||
|
|||||||
107
tonecn/src/components/Blog/BlogContentToolBar.vue
Normal file
107
tonecn/src/components/Blog/BlogContentToolBar.vue
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
<script setup>
|
||||||
|
import { Star, Edit, StarFilled } from '@element-plus/icons-vue'
|
||||||
|
import { ElMessage } from 'element-plus';
|
||||||
|
import { ref, onMounted, onUnmounted } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
import { request } from '@/lib/request';
|
||||||
|
const route = useRoute()
|
||||||
|
const bloguuid = route.params.uuid;
|
||||||
|
const inputComment = ref('')
|
||||||
|
const inputCommentName = ref('')
|
||||||
|
const commentDialogVisible = ref(false)
|
||||||
|
const toolBarVisible = ref(true);
|
||||||
|
const isLiked = ref(false)
|
||||||
|
let lastScrollTop = 0;
|
||||||
|
const handleScrollMove = () => {
|
||||||
|
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
|
||||||
|
if (scrollTop > lastScrollTop) {
|
||||||
|
toolBarVisible.value = false;// 下滑
|
||||||
|
} else {
|
||||||
|
toolBarVisible.value = true;// 上滑
|
||||||
|
}
|
||||||
|
lastScrollTop = scrollTop <= 0 ? 0 : scrollTop; // For Mobile or negative scrolling
|
||||||
|
}
|
||||||
|
const likeBlog = async () => {
|
||||||
|
if (isLiked.value) {
|
||||||
|
return ElMessage.success('已经点过赞啦~')
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
let likeRes = await request.post('/blogLike?bloguuid=' + bloguuid)
|
||||||
|
if (likeRes.code == 0) {
|
||||||
|
isLiked.value = true;
|
||||||
|
return ElMessage.success('点赞成功~')
|
||||||
|
} else {
|
||||||
|
throw new Error(likeRes.message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('点赞失败', error);
|
||||||
|
return ElMessage.warning('手速太快啦~稍后再来试试吧');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onMounted(async () => {
|
||||||
|
window.addEventListener('scroll', handleScrollMove)
|
||||||
|
});
|
||||||
|
onUnmounted(async () => {
|
||||||
|
window.removeEventListener('scroll', handleScrollMove)
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<transition name="el-zoom-in-bottom">
|
||||||
|
<div class="tool-bar-container" v-show="toolBarVisible">
|
||||||
|
<div class="tool-bar">
|
||||||
|
<el-input v-model="inputComment" autosize type="textarea" class="input-comment" placeholder="快来留下你的评论吧~"
|
||||||
|
clearable />
|
||||||
|
<el-button type="primary" :icon="Edit" class="button" circle @click=""></el-button>
|
||||||
|
<el-button type="danger" :icon="isLiked ? StarFilled : Star" class="button" @click="likeBlog"
|
||||||
|
circle></el-button>
|
||||||
|
</div>
|
||||||
|
<el-dialog v-model="commentDialogVisible" title="提示">
|
||||||
|
<div style="display: flex;flex-direction: column;gap: 5px;">
|
||||||
|
<div>每篇文章最多可发布3条评论,确认要现在发布吗?</div>
|
||||||
|
<div>另外,您可选择留下昵称:</div>
|
||||||
|
<el-input v-model="inputCommentName" placeholder="昵称(可选)" />
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<el-button @click="CommentDialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="sendComment">确认</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.tool-bar-container {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
/* height: 60px; */
|
||||||
|
background-color: #ffffff;
|
||||||
|
box-shadow: 0px 0px 9px 1px #ddd;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-bar {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 12px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-comment {
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 570px) {
|
||||||
|
.button {
|
||||||
|
background-color: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,201 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import { Star, Edit, StarFilled, Watch } from '@element-plus/icons-vue'
|
|
||||||
import { ref, onMounted, onUnmounted, watch } from 'vue';
|
|
||||||
import FingerprintJS from '@fingerprintjs/fingerprintjs';
|
|
||||||
import { useRoute } from 'vue-router';
|
|
||||||
import { request } from '@/lib/request';
|
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
|
||||||
let showToolBar = defineModel();
|
|
||||||
const emit = defineEmits(['loadComment'])
|
|
||||||
watch(showToolBar, (newData, oldData) => {
|
|
||||||
if (newData)
|
|
||||||
isScrollingUp.value = true;
|
|
||||||
})
|
|
||||||
const route = useRoute();
|
|
||||||
const uuid = route.params.uuid;
|
|
||||||
let input_comment = ref('')
|
|
||||||
let input_comment_name = ref('')
|
|
||||||
let isStarted = ref(false)
|
|
||||||
let fgId;
|
|
||||||
let CommentDialogVisible = ref(false)
|
|
||||||
let isScrollingUp = ref(true);
|
|
||||||
onMounted(async () => {
|
|
||||||
window.addEventListener('wheel', handleWheel);
|
|
||||||
|
|
||||||
const fp = await FingerprintJS.load();
|
|
||||||
const result = await fp.get();
|
|
||||||
fgId = result.visitorId;
|
|
||||||
});
|
|
||||||
onUnmounted(() => {
|
|
||||||
window.removeEventListener('wheel', handleWheel);
|
|
||||||
});
|
|
||||||
function handleWheel(event) {
|
|
||||||
// deltaY > 0 表示向下滚动,deltaY < 0 表示向上滚动
|
|
||||||
isScrollingUp.value = event.deltaY < 0;
|
|
||||||
showToolBar.value = isScrollingUp.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
let blogLike = async () => {
|
|
||||||
if (isStarted.value) {
|
|
||||||
ElMessage({
|
|
||||||
message: '你已经点过赞啦!',
|
|
||||||
type: 'success',
|
|
||||||
})
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let res = await ServerAPI.async_getRequest(`BlogLike?uuid=${uuid}&fgId=${fgId}`);
|
|
||||||
try {
|
|
||||||
if (res && res.status == 'OK') {
|
|
||||||
if (res.code == 1) {
|
|
||||||
// 点赞成功
|
|
||||||
isStarted.value = true;
|
|
||||||
ElMessage({
|
|
||||||
message: '点赞成功!',
|
|
||||||
type: 'success',
|
|
||||||
})
|
|
||||||
} else if (res.code == 0) {
|
|
||||||
// 重复点赞
|
|
||||||
isStarted.value = true;
|
|
||||||
ElMessage({
|
|
||||||
message: '你已经点过赞啦!',
|
|
||||||
type: 'success',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Error('点赞失败')
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// 网络错误等原因
|
|
||||||
console.log(error)
|
|
||||||
ElMessage({
|
|
||||||
message: '点赞失败',
|
|
||||||
type: 'error',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 评论按钮触发事件
|
|
||||||
let blogComment = async () => {
|
|
||||||
if (!input_comment.value) {
|
|
||||||
ElMessage({
|
|
||||||
message: '请先填写评论内容呀~',
|
|
||||||
type: 'warning'
|
|
||||||
})
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (input_comment.value.length >= 65535) {
|
|
||||||
ElMessage({
|
|
||||||
message: '评论内容太长啦~',
|
|
||||||
type: 'warning'
|
|
||||||
})
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
CommentDialogVisible.value = true;
|
|
||||||
}
|
|
||||||
// 提交评论
|
|
||||||
let sendComment = async () => {
|
|
||||||
if (input_comment_name.value.length >= 255) {
|
|
||||||
ElMessage({
|
|
||||||
message: '昵称太长啦~',
|
|
||||||
type: 'warning'
|
|
||||||
})
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let res = await ServerAPI.async_postRequest(`PostBlogComment`, {
|
|
||||||
'uuid': uuid,
|
|
||||||
'fgId': fgId,
|
|
||||||
'comment': input_comment.value,
|
|
||||||
'comment_name': input_comment_name.value ? input_comment_name.value : '匿名',
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
if (res && res.status == 'OK') {
|
|
||||||
if (res.code == 1) {
|
|
||||||
// 评论成功
|
|
||||||
ElMessage({
|
|
||||||
message: '评论成功!',
|
|
||||||
type: 'success',
|
|
||||||
})
|
|
||||||
CommentDialogVisible.value = false;
|
|
||||||
// 重新加载评论区内容
|
|
||||||
emit('loadComment');
|
|
||||||
return;
|
|
||||||
} else if (res.code == 0) {
|
|
||||||
// 评论3次机会用完
|
|
||||||
ElMessage({
|
|
||||||
message: '您的评论次数已达上限',
|
|
||||||
type: 'warning',
|
|
||||||
})
|
|
||||||
CommentDialogVisible.value = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new Error('评论失败')
|
|
||||||
} catch (error) {
|
|
||||||
// 网络错误等原因
|
|
||||||
console.log(error)
|
|
||||||
ElMessage({
|
|
||||||
message: '评论失败',
|
|
||||||
type: 'error',
|
|
||||||
})
|
|
||||||
CommentDialogVisible.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<transition name="el-zoom-in-bottom">
|
|
||||||
<div class="tool-bar-container" v-show="isScrollingUp">
|
|
||||||
<div class="tool-bar">
|
|
||||||
<el-input v-model="input_comment" autosize type="textarea" class="input-comment"
|
|
||||||
placeholder="快来留下你的评论吧~" clearable />
|
|
||||||
<el-button type="primary" :icon="Edit" class="button" circle @click="blogComment"></el-button>
|
|
||||||
<el-button type="danger" :icon="isStarted ? StarFilled : Star" class="button" @click="blogLike"
|
|
||||||
circle></el-button>
|
|
||||||
</div>
|
|
||||||
<el-dialog v-model="CommentDialogVisible" title="提示">
|
|
||||||
<div style="display: flex;flex-direction: column;gap: 5px;">
|
|
||||||
<div>每篇文章最多可发布3条评论,确认要现在发布吗?</div>
|
|
||||||
<div>另外,您可选择留下昵称:</div>
|
|
||||||
<el-input v-model="input_comment_name" placeholder="昵称(可选)" />
|
|
||||||
</div>
|
|
||||||
<template #footer>
|
|
||||||
<div class="dialog-footer">
|
|
||||||
<el-button @click="CommentDialogVisible = false">取消</el-button>
|
|
||||||
<el-button type="primary" @click="sendComment">确认</el-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</template>
|
|
||||||
<style scoped>
|
|
||||||
.tool-bar-container {
|
|
||||||
position: fixed;
|
|
||||||
left: 0;
|
|
||||||
bottom: 0;
|
|
||||||
width: 100%;
|
|
||||||
/* height: 60px; */
|
|
||||||
background-color: #ffffff;
|
|
||||||
box-shadow: 0px 0px 9px 1px #ddd;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tool-bar {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 600px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 12px 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-comment {
|
|
||||||
margin-right: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 570px) {
|
|
||||||
.button {
|
|
||||||
background-color: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -7,6 +7,7 @@ import { Marked } from 'marked';
|
|||||||
import { markedHighlight } from "marked-highlight";
|
import { markedHighlight } from "marked-highlight";
|
||||||
import hljs from 'highlight.js';
|
import hljs from 'highlight.js';
|
||||||
import "highlight.js/styles/xcode.css";
|
import "highlight.js/styles/xcode.css";
|
||||||
|
import BlogContentToolBar from '@/components/Blog/BlogContentToolBar.vue';
|
||||||
|
|
||||||
const loadStatus = ref(0);// 0加载中 -1加载失败 -2文章不存在或不可见 1加载成功
|
const loadStatus = ref(0);// 0加载中 -1加载失败 -2文章不存在或不可见 1加载成功
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
@@ -48,22 +49,6 @@ onMounted(async () => {
|
|||||||
console.error('请求博客内容发生错误 ', error);
|
console.error('请求博客内容发生错误 ', error);
|
||||||
loadStatus.value = -1;
|
loadStatus.value = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理iframe元素
|
|
||||||
let iframes = document.querySelectorAll('iframe');
|
|
||||||
for (let i = 0; i < iframes.length; i++) {
|
|
||||||
let iframe = iframes[i];
|
|
||||||
// 创建一个新的div元素
|
|
||||||
let wrapperDiv = document.createElement('div');
|
|
||||||
// 设置新div的类名
|
|
||||||
wrapperDiv.className = 'video-container';
|
|
||||||
// 获取iframe的父元素
|
|
||||||
let parent = iframe.parentNode;
|
|
||||||
// 将新的div插入到iframe之前
|
|
||||||
parent?.insertBefore(wrapperDiv, iframe);
|
|
||||||
// 将iframe移动到新创建的div内部
|
|
||||||
wrapperDiv.appendChild(iframe);
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
@@ -80,6 +65,7 @@ onMounted(async () => {
|
|||||||
<div v-html="blogContent" id="blogContentContainer"></div>
|
<div v-html="blogContent" id="blogContentContainer"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<BlogContentToolBar />
|
||||||
</template>
|
</template>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.bcc {
|
.bcc {
|
||||||
|
|||||||
Reference in New Issue
Block a user