2024年12月29日14:51:24

main
LeJingS 2 months ago
parent 526820f9cb
commit 3b6e837ebd

@ -0,0 +1,22 @@
package com.sky.config;
//配置类Oss
import com.sky.properties.AliOssProperties;
import com.sky.utils.AliOssUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@Slf4j
public class OssConfiguration {
@Bean
@ConditionalOnMissingBean
public AliOssUtil aliOssUtil(AliOssProperties aliOssProperties){
log.info("开始上传阿里云文件上传工具类对象:{}",aliOssProperties);
return new AliOssUtil(aliOssProperties.getEndpoint(),aliOssProperties.getAccessKeyId(),
aliOssProperties.getAccessKeySecret(),aliOssProperties.getBucketName());
}
}

@ -1,14 +1,19 @@
package com.sky.controller.admin;
import com.sky.result.Result;
import com.sky.utils.AliOssUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.UUID;
/*
*
*hello 715|WGoDylnmBWl4YX9VNNBqDUxWe0349SBVs3RLNaXw
@ -19,11 +24,26 @@ import org.springframework.web.multipart.MultipartFile;
@Slf4j
public class CommonController {
@Autowired
private AliOssUtil aliOssUtil;
@PostMapping("/upload")
@ApiOperation("文件上传")
public Result<String> upload(MultipartFile file){
log.info("文件开始上传",file);
log.info("文件开始上传:{}",file);
// 上传到阿里云服务器空间
try {
// 原始文件名
String originalFilename = file.getOriginalFilename();
// 截取后缀
String extension = originalFilename.substring(originalFilename.lastIndexOf("."));
// 重命名
String objectName = UUID.randomUUID().toString() + extension;
String filePath = aliOssUtil.upload(file.getBytes(), objectName);
return Result.success(filePath);
} catch (IOException e) {
log.error("文件上传失败",e );
}
return null;
}
}

@ -0,0 +1,35 @@
package com.sky.controller.admin;
import com.sky.dto.DishDTO;
import com.sky.result.Result;
import com.sky.service.DishService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/*
*
* */
@RestController
@RequestMapping("/admin/dish")
@Api(tags = "菜品相关接口")
@Slf4j
public class DishController {
@Autowired
private DishService dishService;
@PostMapping
@ApiOperation("新增菜品")
public Result save(@RequestBody DishDTO dishDTO){
log.info("新增菜品{}",dishDTO);
return Result.success();
}
}

@ -0,0 +1,13 @@
package com.sky.mapper;
import com.sky.entity.DishFlavor;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface DishFlavorMapper {
// 批量插入
void insertBatch(List<DishFlavor> flavors);
}

@ -1,5 +1,8 @@
package com.sky.mapper;
import com.sky.annotation.AutoFill;
import com.sky.entity.Dish;
import com.sky.enumeration.OperationType;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@ -14,4 +17,7 @@ public interface DishMapper {
@Select("select count(id) from dish where category_id = #{categoryId}")
Integer countByCategoryId(Long categoryId);
@AutoFill(value = OperationType.INSERT)
void insert(Dish dish);
}

@ -0,0 +1,8 @@
package com.sky.service;
import com.sky.dto.DishDTO;
public interface DishService {
// 新增菜品和对应的口味
public void saveWithFlavor(DishDTO dishDTO);
}

@ -0,0 +1,50 @@
package com.sky.service.impl;
import com.sky.dto.DishDTO;
import com.sky.entity.Dish;
import com.sky.entity.DishFlavor;
import com.sky.mapper.DishFlavorMapper;
import com.sky.mapper.DishMapper;
import com.sky.service.DishService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
@Slf4j
public class DishServiceImpl implements DishService {
@Autowired
private DishMapper dishMapper;
@Autowired
private DishFlavorMapper dishFlavorMapper;
//新增菜品和口味数据,加上事务管理,原子性。成功全成功,失败全失败
@Transactional
@Override
public void saveWithFlavor(DishDTO dishDTO) {
Dish dish = new Dish();
//对象的属性拷贝
BeanUtils.copyProperties(dishDTO,dish);
//向菜品表插入一条数据
dishMapper.insert(dish);
Long dishId = dish.getId();
// 向口味表插入n条数据
List<DishFlavor> flavors = dishDTO.getFlavors();
if(flavors != null&&flavors.size() > 0){
//给口味附加上菜品的id
flavors.forEach(dishFlavor -> {
dishFlavor.setDishId(dishId);
});
dishFlavorMapper.insertBatch(flavors);
}
}
}

@ -6,3 +6,10 @@ sky:
database: sky_take_out
username: root
password: 123456
alioss:
endpoint: oss-cn-wuhan-lr.aliyuncs.com
access-key-id: LTAI5tJ3L3iMHw1n6XLRJxz4
access-key-secret: QVFc0M102xx2m6HDrO4fODewZixHbu
bucket-name: web-sky-lejings

@ -37,3 +37,9 @@ sky:
admin-ttl: 720000000
# 设置前端传递过来的令牌名称
admin-token-name: token
alioss:
endpoint: ${sky.alioss.endpoint}
access-key-id: ${sky.alioss.access-key-id}
access-key-secret: ${sky.alioss.access-key-secret}
bucket-name: ${sky.alioss.bucket-name}

@ -0,0 +1,16 @@
/**
* ┌─────────────────────────────────────────────────────────────┐
* │┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐│
* ││Esc│!1 │@2 │#3 │$4 │%5 │^6 │&7 │*8 │(9 │)0 │_- │+= │|\ │`~ ││
* │├───┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴───┤│
* ││ Tab │ Q │ W │ E │ R │ T │ Y │ U │ I │ O │ P │{[ │}] │ BS ││
* │├─────┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴─────┤│
* ││ Ctrl │ A │ S │ D │ F │ G │ H │ J │ K │ L │: ;│" '│ Enter ││
* │├──────┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴────┬───┤│
* ││ Shift │ Z │ X │ C │ V │ B │ N │ M │< ,│> .│? /│Shift │Fn ││
* │└─────┬──┴┬──┴──┬┴───┴───┴───┴───┴───┴──┬┴───┴┬──┴┬─────┴───┘│
* │ │Fn │ Alt │ Space │ Alt │Win│ HHKB │
* │ └───┴─────┴───────────────────────┴─────┴───┘ │
* └─────────────────────────────────────────────────────────────┘
* Happy Hacking auto coding
*/

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.DishFlavorMapper">
<insert id="insertBatch">
insert into dish_flavor (dish_id, name, value) VALUES
<foreach collection="flavors" item="df" separator=",">
(#{df.dishId},#{df.name},#{df.value}})
</foreach>
</insert>
</mapper>

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.DishMapper">
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
insert into dish (name, category_id, price, image, description, status, create_time, update_time, create_user, update_user,status)
values
(#{name},#{categoryId},#{price},#{image},#{description},#{status},#{createTime},#{updateTime},#{createUser},#{updateUser},#{status})
</insert>
</mapper>

@ -6,21 +6,21 @@
<div>
<!-- 跳转路由 图片点击跳转到首页 -->
<RouterLink to="/home">
<img src="./assets/img/index.png" alt="back to home">
<img src="./assets/img/index.png" alt="点击回到主页">
</RouterLink>
</div>
<div>
<!-- 跳转路由 跳转到登录 或者个人页面-->
<RouterLink to="/personalSpace">
<img :src="imgUrl" alt="clike to login">
<img :src="imgUrl" alt="点击登录">
</RouterLink>
</div>
</div>
<div class="lxp">
<div>
<!-- 展示区 -->
<RouterView></RouterView>
</div>
@ -47,10 +47,10 @@
loginRoute.value = {path: '/personalSpace'}
imgUrl.value = Login
console.log('already login',imgUrl.value)
console.log('已经登录',imgUrl.value)
}
else{
console.log('not login')
console.log('未登录')
}
});
</script>
@ -61,12 +61,9 @@ html, body {
height: 100%;
margin: 0;
padding: 0;
background-color: black;
}
.lxp{
background-color: rgb(29, 116, 193);
}
#app {
height: 100%;
position: relative; /* 确保子元素可以相对于此进行定位 */
@ -76,8 +73,8 @@ html, body {
display: flex;
justify-content: space-between;
align-items: center;
background-color: rgba(135, 148, 62, 0.8); /* 半透明背景色,以便文字可见 */
color: rgb(193, 92, 92);
background-color: rgba(220, 221, 214, 0.8); /* 半透明背景色,以便文字可见 */
color: white;
padding: 10px 20px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
z-index: 10; /* 确保头部在其他内容之上 */

Binary file not shown.

Before

Width:  |  Height:  |  Size: 371 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

@ -0,0 +1,176 @@
<template>
<div v-if="visible" class="modal">
<h2>{{ title }}</h2>
<button @click="closeModal" class="close-button">关闭</button>
<div v-for="comment in comments" :key="comment.comment_id" class="comment">
<button @click="handleDelete(comment)" class="delete-button">删除</button>
<p class="post-title"><strong>文章标题:</strong> {{ comment.post_title }}</p>
<p class="comment-content"><strong>评论内容:</strong> {{ comment.content }}</p>
<p class="comment-author"><strong>评论者:</strong> {{ comment.username }}</p>
<p class="comment-time"><strong>评论时间:</strong> {{ comment.updated_at }}</p>
</div>
</div>
</template>
<script lang="ts" setup>
import { defineProps, defineEmits } from 'vue';
import { useLoginStore } from '@/stores/Login';
import axios from 'axios';
import { ElMessageBox } from 'element-plus';
const props = defineProps<{
title: string;
visible: boolean;
comments: Array<{
comment_id: number;
post_id: number;
user_id: number;
content: string;
updated_at: string;
username: string;
post_title: string;
}>;
}>();
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void;
}>();
const loginStore = useLoginStore();
const closeModal = () => {
emit('update:visible', false);
};
const handleDelete = (comment: {
comment_id: number;
post_id: number;
user_id: number;
content: string;
updated_at: string;
username: string;
post_title: string;
}) => {
console.log("当前登录用户的用户名:",loginStore.userInfo.username);
console.log("当前评论用户的用户名:",String(comment.username));
if (loginStore.userInfo.username === comment.username) {
//
ElMessageBox.confirm('确定要删除此评论吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
//
axios.delete('http://localhost:8080/comment/delete', {
data: {
token: loginStore.userInfo.token,
comment_id: comment.comment_id,
post_id: comment.post_id
}
})
.then(response => {
//
console.log('Response:', response.data);
// comments
props.comments.splice(props.comments.indexOf(comment), 1);
ElMessageBox.alert('评论已删除', '成功', {
confirmButtonText: '确定',
type: 'success'
});
})
.catch(error => {
//
console.error('Error:', error);
ElMessageBox.alert('删除评论失败,请重试', '错误', {
confirmButtonText: '确定',
type: 'error'
});
});
}).catch(() => {
//
ElMessageBox.alert('已取消删除', '提示', {
confirmButtonText: '确定',
type: 'info'
});
});
} else {
//
ElMessageBox.alert('您只能删除您自己的评论', '提示', {
confirmButtonText: '确定',
type: 'warning'
});
}
};
</script>
<style scoped>
.modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
z-index: 1000;
width: 80%;
max-width: 600px;
}
.close-button {
position: absolute;
top: 10px;
right: 10px;
background-color: #f4f4f4;
border: none;
border-radius: 5px;
padding: 5px 10px;
cursor: pointer;
}
.close-button:hover {
background-color: #ddd;
}
.comment {
margin-bottom: 20px;
border-bottom: 1px solid #eee;
padding-bottom: 10px;
position: relative;
}
.comment-content {
font-size: 18px;
color: #333;
margin: 10px 0;
}
.comment-author {
font-size: 16px;
color: #555;
margin: 5px 0;
}
.post-title, .comment-time {
font-size: 14px;
color: #888;
margin: 5px 0;
}
.delete-button {
position: absolute;
top: 10px;
left: 500px;
background-color: #ff4d4d;
color: white;
border: none;
padding: 5px 10px;
border-radius: 5px;
cursor: pointer;
}
.delete-button:hover {
background-color: #ff1a1a;
}
</style>

@ -0,0 +1,114 @@
<!-- src/components/Modal.vue -->
<template>
<div v-if="visible" class="modal-overlay" @click="closeModal">
<div class="modal-content" @click.stop>
<h2>{{ title }}</h2>
<div v-if="step === 1">
<button @click="selectType('blog')"></button>
<button @click="selectType('comment')"></button>
</div>
<div v-else-if="step === 2">
<div v-if="selectedType === 'blog'">
<button @click="selectCriteria('author')"></button>
<button @click="selectCriteria('title')"></button>
</div>
<div v-else-if="selectedType === 'comment'">
<button @click="selectCriteria('user')"></button>
<button @click="selectCriteria('content')"></button>
</div>
</div>
<div v-else-if="step === 3">
<input v-model="searchQuery" type="text" placeholder="请输入查询内容" />
<button @click="performSearch"></button>
<button @click="closeModal"></button>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
const props = defineProps<{
title: string;
visible: boolean;
}>();
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void;
(e: 'select', type: string, criteria: string, query: string): void;
}>();
const step = ref(1);
const selectedType = ref('');
const selectedCriteria = ref('');
const searchQuery = ref('');
const closeModal = () => {
emit('update:visible', false);
step.value = 1;
selectedType.value = '';
selectedCriteria.value = '';
searchQuery.value = '';
};
const selectType = (type: string) => {
selectedType.value = type;
step.value = 2;
};
const selectCriteria = (criteria: string) => {
selectedCriteria.value = criteria;
step.value = 3;
};
const performSearch = () => {
emit('select', selectedType.value, selectedCriteria.value, searchQuery.value);
closeModal();
};
</script>
<style scoped>
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
}
.modal-content {
background: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
width: 300px;
text-align: center;
}
.modal-content button {
margin: 10px;
padding: 10px 20px;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
.modal-content button:hover {
background-color: #0056b3;
}
.modal-content input {
margin: 10px;
padding: 10px;
width: calc(100% - 20px);
border: 1px solid #ccc;
border-radius: 5px;
}
</style>

@ -2,7 +2,7 @@
<template>
<div class="publicity-container">
<img src="../../assets/img/R-C.jpg" alt="">
<img src="../../assets/img/publicity.png" alt="">
</div>
</template>

@ -1,37 +1,46 @@
<template>
<div class="user-profile">
<h2>personalSpace</h2>
<h2>个人空间</h2>
<div class="profile-info">
<img src="../../assets/img/11.jpg" alt="Avatar" class="avatar" />
<img src="../../assets/img/defaultAvatar.png" alt="Avatar" class="avatar" />
<p><strong>用户名:</strong> {{ loginStore.userInfo.username }}</p>
</div>
<button @click="logout">exit</button>
<button @click="showEditDialog = true">modify information</button>
<button @click="logout"></button>
<button @click="showEditDialog = true">修改信息</button>
<button @click="openConfirmationModal" class="logout-button">注销此账号</button>
</div>
<!-- 文章展示区域 -->
<!-- 文章展示区域 -->
<div class="articles">
<div class="articles-header">
<b class="articles-title">all of your articles</b>
<select v-model="selectedOption" class="dropdown-select">
<option value="byLike">heat</option>
<option value="byData">date</option>
</select>
排序
<button class="confirm-button" @click="">confirm</button>
<b class="articles-title">您的全部文章</b>
<select v-model="selectedOption" class="dropdown-select">
<option value="byLike">最受欢迎</option>
<option value="byData">发布日期</option>
</select>
排序
<button class="confirm-button" @click="handleConfirm"></button>
</div>
<div class="article">
<!-- 文章内容 -->
<div v-for="(p, index) in postOverviewList" :key="p.post_id">
<Article
:post_id="p.post_id"
:user_id="p.user_id"
:username="p.username"
:title="p.title"
:updated_at="p.updated_at"
:Likes="p.Likes"
:showButtons="true"
@destroy="handleDestroy(index)"
/>
</div>
</div>
<div class="article">
<!-- 文章内容 -->
<div v-for="(p,index) in postOverviewList" :key="p.post_id">
<Article :post_id="p.post_id" :user_id="p.user_id" :username="p.username" :title="p.title" :updated_at="p.updated_at" :Likes="p.Likes" :showButtons="true"
@destroy="handleDestroy(index)"/>
</div>
</div>
<!-- 更多文章可以继续添加 -->
<!-- 更多文章可以继续添加 -->
</div>
<div >
<div>
<el-dialog v-model="showEditDialog" title="修改信息">
<el-form :model="form" label-width="80px">
<el-form-item label="用户名">
@ -60,24 +69,35 @@
</template>
</el-dialog>
<!-- 注销确认模态框 -->
<el-dialog v-model="isConfirmationModalVisible" title="确认注销">
<p>此账号注销后无法找回请谨慎操作</p>
<el-input v-model="confirmationText" placeholder="请输入“我确认要注销此账号”"></el-input>
<template #footer>
<span class="dialog-footer">
<el-button @click="closeConfirmationModal"></el-button>
<el-button type="primary" @click="handleConfirmLogout"></el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { defineComponent, ref, reactive, onMounted } from 'vue';
import Article from '@/components/utils/Article.vue'
import { ref, reactive, onMounted } from 'vue';
import Article from '@/components/utils/Article.vue';
import { useLoginStore } from '@/stores/Login';
import { useRouter } from 'vue-router';
import { ElDialog, ElForm, ElFormItem, ElInput, ElButton, ElMessageBox } from 'element-plus';
import axios from 'axios';
//
import { useToast } from 'vue-toastification'
import { useToast } from 'vue-toastification';
const toast = useToast();
const router = useRouter();
const loginStore = useLoginStore();
//
interface postOverview{
interface postOverview {
post_id: number;
user_id: number;
Likes: number;
@ -86,8 +106,11 @@ interface postOverview{
updated_at: string;
}
let postOverviewList = reactive<postOverview[]>([]);
const showEditDialog = ref(false);
const passwordType = ref('password');
const confirmationText = ref('');
//
const selectedOption = ref('byData'); // 1
@ -114,20 +137,19 @@ function getAll(by: string) {
})
.then(res => {
// 使
postOverviewList.splice(0, postOverviewList.length)
postOverviewList.push(...res.data.data)
postOverviewList.splice(0, postOverviewList.length);
postOverviewList.push(...res.data.data);
console.log("获取文章概览数据成功", res.data.data);
})
.catch(error => console.error('There was an error fetchingthe data!', error))
.catch(error => console.error('There was an error fetching the data!', error));
}
const handleSubmit = () => {
//
console.log(form);
showEditDialog.value = false;
//axios
console.log("-------------",passwordType.value,'-------------',loginStore.userInfo.token)
axios.put('http://localhost:8080/user/revise',{
// axios
axios.put('http://localhost:8080/user/revise', {
username: form.username,
password: form.password,
token: loginStore.userInfo.token
@ -142,42 +164,87 @@ const handleSubmit = () => {
.catch(error => {
//
console.error('Error:', error);
})
});
toast.success("信息修改成功");
};
const logout = () => {
// useLoginStore
loginStore.userInfo = { id: '',
//
avatarurl: '../../assets/img/deLogin.png',
username: '',
token: ''
};
toast.success("退出登录成功");
loginStore.userInfo = {
id: '',
avatarurl: '../../assets/img/deLogin.png',
username: '',
token: ''
};
toast.success("成功");
//
router.push('/');
};
const handleConfirm = () => {
console.log('选择的选项是:', selectedOption.value);
getAll(selectedOption.value);
};
//
const isConfirmationModalVisible = ref(false);
const openConfirmationModal = () => {
isConfirmationModalVisible.value = true;
};
const closeConfirmationModal = () => {
isConfirmationModalVisible.value = false;
confirmationText.value = '';
};
const handleConfirmLogout = () => {
if (confirmationText.value === "确认") {
//
logoutAccount();
} else {
alert("输入不正确,请重新输入。");
}
};
const logoutAccount = () => {
//
console.log("账号已注销");
//
axios.delete('http://localhost:8080/user/logout', {
data: { token: loginStore.userInfo.token }
})
.then(response => {
//
console.log('Response:', response.data);
toast.success("注销成功");
//
logout();
})
.catch(error => {
})
loginStore.userInfo = {
id: '',
avatarurl: '../../assets/img/deLogin.png',
username: '',
token: ''
};
logout();
};
onMounted(() => {
// getAll
getAll(selectedOption.value);
});
</script>
<style scoped>
html, body {
background-color: black;
}
.user-profile {
text-align: center;
padding: 40px;
background-color: #a28585;
background-color: #f9f9f9;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
max-width: 400px;
@ -198,8 +265,8 @@ html, body {
button {
padding: 12px 24px;
background-color: #469fff;
color: rgb(94, 72, 72);
background-color: #007bff;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
@ -209,8 +276,17 @@ button {
}
button:hover {
background-color: #244f7d;
background-color: #0056b3;
}
.logout-button {
background-color: #ff4d4d;
}
.logout-button:hover {
background-color: #ff1a1a;
}
.articles {
flex: 1; /* 占据剩余空间 */
padding: 20px;
@ -224,12 +300,13 @@ button:hover {
/* 单篇文章样式 */
.article {
margin-bottom: 20px;
background-color: rgb(219, 162, 162);
background-color: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
width: 100%;
}
/* 文章标题样式 */
.articles-title {
font-size: 24px;
@ -242,7 +319,7 @@ button:hover {
.dropdown-select {
margin-left: 10px;
padding: 5px;
border: 1px solid #ccc8c8;
border: 1px solid #ccc;
border-radius: 4px;
}
</style>

@ -8,11 +8,11 @@
</div>
<div class="article-details">
<div class="article-buttons" v-if="showButtons">
<button class="edit-button" @click.stop="handleEdit">alter</button>
<button class="delete-button" @click.stop="handleDelete">delete</button>
<button class="edit-button" @click.stop="handleEdit">修改</button>
<button class="delete-button" @click.stop="handleDelete">删除</button>
</div>
<div class="article-likes">likes: {{ Likes }}</div>
<div class="article-published-date">time of release {{ updated_at }}</div>
<div class="article-likes">点赞数: {{ Likes }}</div>
<div class="article-published-date">发布时间: {{ updated_at }}</div>
</div>
</div>
</div>
@ -32,9 +32,9 @@ const props = defineProps(['post_id','user_id','username','title','updated_at','
console.log("Likes:",props.Likes);
function handleDelete() {
ElMessageBox.confirm('sure?', '提示', {
confirmButtonText: 'yse',
cancelButtonText: 'no',
ElMessageBox.confirm('您确定要删除这篇文章吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
//
@ -43,11 +43,11 @@ function handleDelete() {
post_id: props.post_id, // post_id
}
})
toast.success("success");
toast.success("删除成功");
emit('destroy');
}).catch(() => {
//
console.log('whats the id',props.post_id)
console.log('要删除的文章id是',props.post_id)
});
}
@ -73,7 +73,7 @@ function handleClick(post_id:number) {
display: flex;
flex-direction: column;
padding: 15px;
border: 2px solid #5b3b3b;
border: 1px solid #ddd;
border-radius: 5px;
margin-bottom: 10px;
text-decoration: none;
@ -82,7 +82,7 @@ function handleClick(post_id:number) {
}
.article-overview:hover {
background-color: #6e3d3d;
background-color: #f4f4f4;
}
.article-header {
@ -97,7 +97,7 @@ function handleClick(post_id:number) {
}
.article-title {
font-size: 20px;
font-size: 18px;
font-weight: bold;
margin-bottom: 5px;
}
@ -110,12 +110,12 @@ function handleClick(post_id:number) {
.article-details {
display: flex;
flex-direction: column;
font-size: 16px;
font-size: 12px;
color: #777;
}
.article-likes {
margin-bottom: 3px;
margin-bottom: 2px;
}
.article-published-date {

@ -0,0 +1,144 @@
<template>
<div class="comment-section">
<h2>评论区</h2>
<!-- 评论列表 -->
<div v-for="(comment, index) in commentList" :key="index" class="comment">
<p class="comment-content">{{ comment.content }}</p>
<div class="comment-meta">
<span class="comment-username">{{ comment.username }}</span>
<span class="comment-updated-at">{{ comment.updated_at }}</span>
</div>
</div>
<!-- 添加评论表单 -->
<form @submit.prevent="addComment">
<textarea v-model="newComment" placeholder="添加评论..." required></textarea>
<button type="submit">提交</button>
</form>
</div>
</template>
<script lang="ts" setup>
import { ref,reactive,onMounted } from 'vue';
import axios from 'axios';
import { useLoginStore } from '@/stores/Login';
import { useToast } from 'vue-toastification'
const toast = useToast();
const loginStore = useLoginStore();
const props = defineProps(['post_id'])
interface comment{
post_id: number;
username: string;
content: string;
token: string;
updated_at: string;
}
let commentList = ref<comment[]>([]);
function getComments(){
axios.get('http://localhost:8080/comment',{
params:{
post_id: props.post_id
}
})
.then(res=>{
commentList.value = res.data.data;
console.log(commentList.value);
})
.catch(err=>{
console.log(err);
})
}
const newComment = ref('');
const addComment = async () => {
if(loginStore.userInfo.token===''||loginStore.userInfo.token===null){
toast.error("请先登录");
}
else{
axios.post('http://localhost:8080/comment',{
post_id: props.post_id,
content: newComment.value,
token: loginStore.userInfo.token
})
.then(res=>{
toast.success("评论成功");
getComments();
newComment.value = '';
})
.catch(err=>{
console.log(err);
})
}
};
onMounted(()=>{
getComments();
})
</script>
<style scoped>
.comment-section {
position: fixed;
top: 100px;
right: 50%; /* 调整右侧位置 */
transform: translateX(250%); /* 调整位置以居中 */
width: 300px;
background-color: #fff;
border: none;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
padding: 10px;
height: 75vh;
overflow-y: auto;
overflow-x: hidden;
}
.comment {
margin-bottom: 15px;
padding: 10px;
border: 1px solid #ddd; /* 添加边框 */
border-radius: 5px; /* 圆角边框 */
background-color: #f9f9f9; /* 浅背景色 */
}
.comment-content {
font-size: 16px;
margin-bottom: 5px;
}
.comment-meta {
font-size: 12px;
color: #888;
}
.comment-username {
margin-right: 10px;
}
textarea {
width: 100%;
height: 50px;
margin-bottom: 10px;
padding: 5px;
border: 1px solid #ccc;
border-radius: 3px;
}
button {
padding: 5px 10px;
border: none;
background-color: #007bff;
color: #fff;
border-radius: 3px;
cursor: pointer;
}
button:hover {
background-color: #0056b3;
}
</style>

@ -1,12 +1,12 @@
<template>
<div class="paper-container">
<h1>{{ paper.title }}</h1>
<p><strong>author:</strong> {{ paper.writer }}</p>
<p><strong>date-time</strong> {{ paper.updated_at }}</p>
<p><strong>作者:</strong> {{ paper.writer }}</p>
<p><strong>发布时间:</strong> {{ paper.updated_at }}</p>
<div class="interaction-buttons">
<button @click="toggleLike" :class="{ active: isLiked }">
<img :src="likeIconSrc" alt="Like" class="icon">
Likes{{ paper.Likes }}
点赞数{{ paper.Likes }}
</button>
<button @click="toggleDislike" :class="{ active: isDisliked }">
<img :src="dislikeIconSrc" alt="Dislike" class="icon">
@ -19,10 +19,14 @@
</div>
<div v-html="compiledMarkdown"></div>
</div>
<div>
<CommentSection :post_id="post_id"/>
</div>
</template>
<script lang="ts" setup>
import { computed, onMounted, ref,reactive } from 'vue';
import CommentSection from './CommentSection.vue';
import { marked } from 'marked';
import axios from 'axios';
import { useRoute } from 'vue-router';
@ -64,8 +68,8 @@ function getpaper(){
axios.get('http://127.0.0.1:8080/post/essay',{ params: {post_id:post_id}})
.then(res => {
console.log("success", res.data.data);
console.log("object");
console.log("获取文章详情数据成功", res.data.data);
console.log("传入对象");
paper.id = res.data.data.post_id
paper.title = res.data.data.title
paper.content = res.data.data.content
@ -113,7 +117,6 @@ onMounted(() => {
background-color: #fff;
border: 1px solid #ccc;
border-radius: 5px;
height: 900px; /* 设置固定高度 */
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}

@ -29,19 +29,19 @@
<style scoped>
.custom-button {
position: fixed;
bottom: 80%; /* 调整位置以固定在回到顶部按钮上方 */
right: 30px;
padding: 12px 22px;
background-color: #ffffff;
color: rgb(12, 1, 1);
bottom: 60px; /* 调整位置以固定在回到顶部按钮上方 */
right: 20px;
padding: 10px 20px;
background-color: #007bff;
color: white;
border: none;
border-radius: 10px;
border-radius: 5px;
cursor: pointer;
box-shadow: 0 4px 10px rgba(44, 0, 0, 0.3);
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
z-index: 1000; /* 确保按钮在最上层 */
}
.custom-button:hover {
background-color: #25568a;
background-color: #0056b3;
}
</style>

@ -1,7 +1,7 @@
<template>
<div class="writing-container">
<!-- 标题输入框 -->
<input v-model="title" class="title-input" placeholder="please enter title" />
<input v-model="title" class="title-input" placeholder="请输入文章标题" />
<!-- 编辑器与预览容器 -->
<div class="content-container">
<div class="editor-container">
@ -28,6 +28,7 @@ import { ref, computed, onMounted,nextTick } from 'vue';
import { marked } from 'marked';
import axios from 'axios';
import { useRoute } from 'vue-router';
import { useRouter } from 'vue-router';
import { useLoginStore } from '@/stores/Login';
//
import { useToast } from 'vue-toastification'
@ -38,6 +39,7 @@ const loginStore = useLoginStore();
const toast = useToast();
const route = useRoute();
const post_id = route.params.id;
const router = useRouter();
// 使 marked Markdown HTML
const compiledMarkdown = computed(() => {
return marked.parse(markdownContent.value, { breaks: true });
@ -95,8 +97,9 @@ const scrollToTop = () => {
.then(response => {
//
console.log(response.data);
toast.success("success");
toast.success("发布成功");
//
router.push('/home');
})
.catch(error => {
@ -107,7 +110,7 @@ const scrollToTop = () => {
}
else{
//
console.log("modifying",post_id)
console.log("正在修改中",post_id)
//
console.log(title.value,'id',post_id)
axios.post('http://localhost:8080/post/update', {
@ -135,7 +138,7 @@ body {
justify-content: center;
align-items: stretch; /* 让子元素拉伸以填充父容器 */
min-height: 100vh;
background-color: #00ddff; /* 更柔和的背景颜色 */
background-color: #f9f9f9; /* 更柔和的背景颜色 */
}
/* 内容容器 */
@ -162,7 +165,7 @@ body {
width: 100%; /* 留出一点边缘空间 */
height: 100%; /* 占据整个高度 */
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
background-color: #00d9ff;
background-color: #fff;
border-radius: 8px;
position: relative;
overflow: auto; /* 使用浏览器自带的滚动条 */
@ -177,8 +180,8 @@ body {
left: 50%;
transform: translateX(-50%);
width: 60%; /* 调整宽度 */
padding: 12px;
font-size: 20px; /* 减小字体大小 */
padding: 10px;
font-size: 18px; /* 减小字体大小 */
margin-bottom: 20px;
border: none;
border-bottom: 2px solid #ccc;
@ -194,17 +197,17 @@ body {
/* 编辑器容器 */
.editor-container {
background-color: #ff0000;
background-color: #f7f7f7;
height: 100%;
box-sizing: border-box;
padding: 30px;
padding: 20px;
overflow: hidden; /* 确保内容不会溢出 */
}
/* 预览容器 */
.preview-container {
background-color: #c41212;
border-left: 1px solid #a52424;
background-color: #fff;
border-left: 1px solid #ccc;
height: 100%;
box-sizing: border-box;
padding: 20px;

@ -29,10 +29,10 @@ function getAll() {
axios.get('http://127.0.0.1:8080/announcements')
.then(res => {
// 使
console.log("Start storage",res.data)
console.log("开始存放",res.data)
announcements.splice(0, announcements.length)
announcements.push(...res.data.data)
console.log("acquire success", res.data.data);
console.log("获取公告数据成功", res.data.data);
})
.catch(error => console.error('There was an error fetching the data!', error))
}
@ -45,10 +45,10 @@ function getAll() {
<style scoped>
.announcement-container {
border-top: 2px solid #312e23; /* 黄色边框 */
border-top: 2px solid #ffcc00; /* 黄色边框 */
padding: 20px;
background-color: #fff9c4; /* 浅黄色背景 */
box-shadow: 0 6px 6px rgba(0, 0, 0, 0.1); /* 阴影效果 */
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* 阴影效果 */
border-radius: 8px; /* 圆角 */
margin: 20px;
}
@ -56,26 +56,26 @@ function getAll() {
.announcement-item {
margin-bottom: 20px;
padding: 10px;
background-color: #723d3d; /* 白色背景 */
background-color: #fff; /* 白色背景 */
border: 1px solid #ffcc00; /* 黄色边框 */
border-radius: 4px; /* 圆角 */
box-shadow: 0 3px 3px rgba(0, 0, 0, 0.05); /* 阴影效果 */
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); /* 阴影效果 */
}
.announcement-item h3 {
color: #855c45; /* 橙色标题 */
color: #e65100; /* 橙色标题 */
font-size: 1.2em;
margin-bottom: 4px;
margin-bottom: 5px;
}
.announcement-time {
color: #bbb5b5;
color: #888;
font-size: 0.9em;
margin-bottom: 10px;
}
.announcement-content {
margin-top: 10px;
color: #cd3e3e; /* 深色内容 */
color: #333; /* 深色内容 */
}
</style>

@ -9,42 +9,62 @@
<div class="main-content">
<!-- 系统消息发布区 -->
<div class="system-messages">
<Announcement/>
</div>
<!-- 文章展示区 -->
<div class="articles">
<div class="articles-header">
<b class="articles-title"></b>
<b class="articles-title">全部文章</b>
按照
<select v-model="selectedOption" class="dropdown-select">
<option value="byLike">popularity</option>
<option value="byData">date</option>
<option value="byLike">最受欢迎</option>
<option value="byData">发布日期</option>
</select>
排序
<button class="confirm-button" @click="handleConfirm">verify</button>
<button class="confirm-button" @click="handleConfirm"></button>
</div>
<div class="article">
<!-- 文章内容 -->
<div v-for="p in postOverviewList" :key="p.post_id">
<div v-for="p in postOverviewList" :key="p.post_id">
<Article :post_id="p.post_id" :user_id="p.user_id" :username="p.username" :title="p.title" :updated_at="p.updated_at" :Likes="p.Likes" :showButtons="false" />
</div>
</div>
</div>
<!-- 更多文章可以继续添加 -->
</div>
</div>
<!-- 开始写作按钮 -->
<StartWriting buttonText="start writing" />
<!-- 开始写作按钮 -->
<StartWriting buttonText="开始写作" />
<!-- 底部查询栏 -->
<button class="scroll-to-top find-button" @click="openModal"></button>
<!-- 回到顶部按钮 -->
<button class="scroll-to-top" @click="scrollToTop">back top</button>
<button class="scroll-to-top" @click="scrollToTop"></button>
<!-- 模态框 -->
<Modal
title="查询选择"
:visible="isModalVisible"
@update:visible="isModalVisible = $event"
@select="handleSelect"
/>
<!-- 评论浮窗 -->
<CommentModal
title="全部评论"
:visible="isCommentModalVisible"
:comments="commentList"
@update:visible="isCommentModalVisible = $event"
/>
</div>
</template>
<script lang="ts" setup>
import { ref,reactive,onMounted } from 'vue';
import { ref, reactive, onMounted } from 'vue';
import axios from 'axios';
import Modal from '@/components/Modal.vue';
import CommentModal from '@/components/CommentModal.vue';
//
import Publicity from '../components/home/Publicity.vue';
//
@ -55,7 +75,7 @@ import Announcement from './Announcement.vue';
import StartWriting from '@/components/utils/StartWriting.vue';
console.log('home');
// -----------------------------------------
interface postOverview{
interface postOverview {
post_id: number;
user_id: number;
Likes: number;
@ -65,6 +85,18 @@ interface postOverview{
}
let postOverviewList = reactive<postOverview[]>([]);
interface comment {
comment_id: number;
post_id: number;
user_id: number;
content: string;
updated_at: string;
username: string;
post_title:string;
}
let commentList = reactive<comment[]>([]);
//
function getAll(by: string) {
axios.get('http://127.0.0.1:8080/post/overview', {
@ -75,9 +107,9 @@ function getAll(by: string) {
// 使
postOverviewList.splice(0, postOverviewList.length)
postOverviewList.push(...res.data.data)
console.log("success", res.data.data);
console.log("获取文章概览数据成功", res.data.data);
})
.catch(error => console.error('There was an error fetchingthe data!', error))
.catch(error => console.error('There was an error fetching the data!', error))
}
//---------------------------------------
@ -87,17 +119,118 @@ const selectedOption = ref('byData'); // 默认值为选项1
const scrollToTop = () => {
window.scrollTo({ top: 0, behavior: 'smooth' });
};
//
const isModalVisible = ref(false);
const isCommentModalVisible = ref(false);
const openModal = () => {
isModalVisible.value = true;
};
const closeModal = () => {
isModalVisible.value = false;
};
const handleSelect = (type: string, criteria: string, query: string) => {
console.log('选择的类型:', type);
console.log('选择的条件:', criteria);
console.log('查询内容:', query);
//
if (type === 'blog') {
if (criteria === 'author') {
//
console.log('根据作者名称查询博客');
searchBlogByAuthor(query);
} else if (criteria === 'title') {
//
console.log('根据文章名称查询博客');
searchBlogByTitle(query);
}
} else if (type === 'comment') {
if (criteria === 'user') {
//
console.log('根据用户名称查询评论');
searchCommentByUser(query);
} else if (criteria === 'content') {
//
console.log('根据评论内容查询评论');
searchCommentByContent(query);
}
}
closeModal();
};
//
const handleConfirm = () => {
console.log('选择的是:', selectedOption.value);
getAll(selectedOption.value)
console.log('选择的选项是:', selectedOption.value);
getAll(selectedOption.value);
//
};
onMounted(()=>{
getAll(selectedOption.value)
})
</script>
onMounted(() => {
getAll(selectedOption.value);
});
// piniaxing
//
const searchBlogByTitle = (author: string) => {
axios.get('http://127.0.0.1:8080/post/search', {
params: { title: "",username: author },
headers: { token: '' }
})
.then(res => {
postOverviewList.splice(0, postOverviewList.length);
postOverviewList.push(...res.data.data);
console.log("根据作者名称查询博客成功", res.data.data);
})
.catch(error => console.error('There was an error fetching the data!', error));
};
//
const searchBlogByAuthor = (title: string) => {
axios.get('http://127.0.0.1:8080/post/search', {
params: { title: title ,username:''},
headers: { token: '' }
})
.then(res => {
postOverviewList.splice(0, postOverviewList.length);
postOverviewList.push(...res.data.data);
console.log("根据文章名称查询博客成功", res.data.data);
})
.catch(error => console.error('There was an error fetching the data!', error));
};
//
//
const searchCommentByUser = (user: string) => {
axios.get('http://127.0.0.1:8080/comment/search', {
params: { content: "",username: user },
headers: { token: '' }
})
.then(res => {
commentList.splice(0, commentList.length);
commentList.push(...res.data.data);
console.log("根据用户名称查询评论成功", res.data.data);
isCommentModalVisible.value = true;
})
.catch(error => console.error('There was an error fetching the data!', error));
};
//
const searchCommentByContent = (content: string) => {
axios.get('http://127.0.0.1:8080/comment/search', {
params: { content: content,username: "" },
headers: { token: '' }
})
.then(res => {
commentList.splice(0, commentList.length);
commentList.push(...res.data.data);
console.log("根据评论内容查询评论成功", res.data.data);
isCommentModalVisible.value = true;
})
.catch(error => console.error('There was an error fetching the data!', error));
};
</script>
<style scoped>
/* 容器样式设置为flex布局垂直排列 */
@ -124,7 +257,7 @@ onMounted(()=>{
/* 系统消息发布区样式 */
.system-messages {
width: 300px; /* 固定宽度 */
background-color: #662929;
background-color: #f4f4f4;
padding: 20px;
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1);
position: sticky; /* 始终固定在左侧 */
@ -147,7 +280,7 @@ onMounted(()=>{
/* 单篇文章样式 */
.article {
margin-bottom: 20px;
background-color: rgb(0, 255, 213);
background-color: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
@ -160,8 +293,8 @@ onMounted(()=>{
bottom: 20px;
right: 20px;
padding: 10px 20px;
background-color: #1468c3;
color: rgb(142, 120, 120);
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
@ -169,6 +302,11 @@ onMounted(()=>{
z-index: 1000; /* 确保按钮在最上层 */
}
/* 查询按钮样式 */
.find-button {
bottom: 100px; /* 调整为比回到顶部按钮更大的值 */
}
/* 回到顶部按钮悬停效果 */
.scroll-to-top:hover {
background-color: #0056b3;
@ -177,18 +315,17 @@ onMounted(()=>{
/* 文章标题样式 */
.articles-title {
font-size: 24px;
color: #7c3939;
color: #333;
font-weight: bold;
margin-bottom: 9px;
margin-bottom: 10px;
}
/* 下拉选择框样式 */
.dropdown-select {
margin-left: 10px;
padding: 5px;
border: 1px solid #8a7979;
border: 1px solid #ccc;
border-radius: 4px;
background-color: rgb(0, 123, 255);
}
/* 确认按钮样式 */
@ -196,7 +333,7 @@ onMounted(()=>{
margin-left: 10px;
padding: 5px 10px;
background-color: #007bff;
color: rgb(151, 56, 56);
color: white;
border: none;
border-radius: 4px;
cursor: pointer;

@ -3,17 +3,17 @@
<h2>登录</h2>
<form @submit.prevent="handleLogin">
<div class="form-group">
<label for="username">username</label>
<label for="username">用户名</label>
<input type="text" id="username" v-model="username" required />
</div>
<div class="form-group">
<label for="password">secret code</label>
<label for="password">密码</label>
<input type="password" id="password" v-model="password" required />
</div>
<button type="submit">login</button>
<button type="submit">登录</button>
<div class="action-buttons">
<button type="button" @click="handleForgotPassword">get the code back</button>
<button type="button" @click="handleRegister">register</button>
<button type="button" @click="handleForgotPassword"></button>
<button type="button" @click="handleRegister"></button>
</div>
</form>
</div>
@ -31,8 +31,8 @@ const password = ref('');
const store = useLoginStore();
const handleLogin = async () => {
// API
console.log('username:', username.value);
console.log('secret passward:', password.value);
console.log('用户名:', username.value);
console.log('密码:', password.value);
try {
//
@ -49,18 +49,19 @@ const handleLogin = async () => {
console.log(response.data.code);
if (response.data.code === 1) {
//
console.log('Success');
console.log('登录成功');
store.userInfo.token = response.data.data;
store.userInfo.username = username.value;
console.log(store.userInfo.token);
router.push('/home');
console
return;
} else {
//
alert('error');
alert('用户名或密码错误');
}
} catch (error) {
console.error('defected:', error);
console.error('登录请求失败:', error);
//
}
@ -71,7 +72,7 @@ const handleLogin = async () => {
const handleForgotPassword = () => {
//
console.log('get the code back');
console.log('找回密码');
//
router.push('/retrievePassword');
};

@ -0,0 +1,23 @@
package top.lejings.demo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // 对所有的URL都生效
.allowedOrigins("http://localhost:82") // 允许的来源
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 允许的方法
.allowedHeaders("*") // 允许的头部
.allowCredentials(true); // 是否允许发送Cookie
}
};
}
}

@ -0,0 +1,57 @@
package top.lejings.demo.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import top.lejings.demo.pojo.Comments;
import top.lejings.demo.pojo.Result;
import top.lejings.demo.service.CommentsService;
import java.util.List;
/*
*
*
*
* */
@Slf4j
@RestController
@RequestMapping("/comment")
@CrossOrigin(origins = "http://localhost:82")
public class CommentsController {
@Autowired
CommentsService commentsService;
@GetMapping
@CrossOrigin(origins = "http://localhost:82")
public Result getComment(Integer post_id){
log.info("获取文章id为{}的评论区",post_id);
List<Comments> CommentsList = commentsService.getComment(post_id);
log.info("列表为{}",CommentsList);
return Result.success(CommentsList);
}
@PostMapping
@CrossOrigin(origins = "http://localhost:82")
public Result addComment(@RequestBody Comments comments){
log.info("增加新的评论{}",comments);
commentsService.addComment(comments);
return Result.success();
}
@GetMapping("/search")
public Result search(String content,String username){
log.info("特定查询评论");
List<Comments> searchComments = commentsService.search(content,username);
return Result.success(searchComments);
}
@DeleteMapping("/delete")
public Result deleteComment(@RequestBody Comments comments){
log.info("删除评论{}",comments.getComment_id());
commentsService.delete(comments);
return Result.success();
}
}

@ -22,6 +22,12 @@ public class PostsController {
log.info("查询文章所有概览");
List<Posts> postsOverviewList = postsService.postsOverview(by,token);
System.out.println(postsOverviewList);
// 遍历列表,处理 username 为空或 null 的情况
for (Posts post : postsOverviewList) {
if (post.getUsername() == null || post.getUsername().isEmpty()) {
post.setUsername("账号已注销");
}
}
return Result.success(postsOverviewList);
}
//具体文章,接收参数 文章id essay
@ -58,4 +64,19 @@ public class PostsController {
postsService.postsUpdate(posts);
return Result.success("修改成功");
}
@GetMapping("/search")
public Result search(String title,String username){
log.info("特定条件查询");
List<Posts> postList = postsService.search(title,username);
// 遍历列表,处理 username 为空或 null 的情况
for (Posts post : postList) {
if (post.getUsername() == null || post.getUsername().isEmpty()) {
post.setUsername("账号已注销");
}
}
return Result.success(postList);
}
}

@ -38,7 +38,7 @@ public class UsersController {
claims.put("username",u.getUsername());
claims.put("email",u.getEmail());
String jwt = JwtUtils.generateJwt(claims);//jwt包含了当前员工信息
log.info("{}",jwt);
return Result.success(jwt);
}
//登录失败,返回错误信息
@ -83,6 +83,12 @@ public class UsersController {
return Result.success();
}
// 注销账号
@DeleteMapping("/logout")
public Result delete(@RequestBody Users users){
log.info("开始注销");
usersService.logout(users);
return Result.success();
}
}

@ -0,0 +1,28 @@
package top.lejings.demo.mapper;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import top.lejings.demo.pojo.Comments;
import org.apache.ibatis.annotations.Delete;
import java.util.List;
@Mapper
public interface CommentsMapper {
List<Comments> getComment(Integer postId);
void addComment(Comments comments);
// 根据用户名搜索评论
List<Comments> searchByUser(@Param("username") String username);
// 根据内容搜索评论
List<Comments> searchByContent(@Param("content") String content);
@Delete("DELETE FROM comments WHERE comment_id = #{comment_id} AND post_id = #{post_id} AND user_id = #{user_id}")
void delete(Comments comments);
}

@ -24,4 +24,11 @@ public interface PostsMapper {
@Update("UPDATE posts SET title = #{title}, content = #{content} WHERE post_id = #{post_id};")
void postsUpdate(Posts posts);
List<Posts> searchByTitle(String title);
List<Posts> searchByUser(String username);
}

@ -1,9 +1,6 @@
package top.lejings.demo.mapper;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import org.apache.ibatis.annotations.*;
import top.lejings.demo.pojo.Users;
@Mapper
@ -22,4 +19,8 @@ public interface UsersMapper {
@Update("UPDATE users SET username = #{username},password = #{password} where user_id = #{user_id}")
void revise(Users users);
@Delete("DELETE FROM users WHERE user_id = #{userId}")
void logout(Integer userId);
}

@ -0,0 +1,25 @@
package top.lejings.demo.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/*
* DTO
*
* */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Comments {
private Integer comment_id;
private Integer post_id;
private String post_title;
private Integer user_id;
private String content;
private String username;
private String token;//可以为空,为空则为游客
private LocalDateTime updated_at;
}

@ -0,0 +1,15 @@
package top.lejings.demo.service;
import top.lejings.demo.pojo.Comments;
import java.util.List;
public interface CommentsService {
List<Comments> getComment(Integer postId);
void addComment(Comments comments);
List<Comments> search(String content, String username);
void delete(Comments comments);
}

@ -14,4 +14,6 @@ public interface PostsService {
void deletePost(Integer postId);
void postsUpdate(Posts posts);
List<Posts> search(String title,String username);
}

@ -10,4 +10,6 @@ public interface UsersService {
boolean findPassword(String email);
void revise(Users users);
void logout(Users users);
}

@ -0,0 +1,55 @@
package top.lejings.demo.service.impl;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import top.lejings.demo.mapper.CommentsMapper;
import top.lejings.demo.pojo.Comments;
import top.lejings.demo.service.CommentsService;
import top.lejings.demo.utils.JwtUtils;
import java.util.List;
@Service
public class CommentsServiceImpl implements CommentsService {
@Autowired
CommentsMapper commentsMapper;
@Override
public List<Comments> getComment(Integer postId) {
return commentsMapper.getComment(postId);
}
@Override
public void addComment(Comments comments) {
String token = comments.getToken();
if (token != null) {
Claims claims = JwtUtils.parseJWT(token);
Integer userId = (Integer) claims.get("user_id");
comments.setUser_id(userId);
commentsMapper.addComment(comments);
} else {
throw new IllegalArgumentException("Token cannot be null");
}
}
@Override
public List<Comments> search(String content, String username) {
if (content == null || content.equals("")) {
// by作者名
return commentsMapper.searchByUser(username);
}
else{
// by 内容
return commentsMapper.searchByContent(content);
}
}
@Override
public void delete(Comments comments) {
//解析jwt令牌获得user_id
Claims claims = JwtUtils.parseJWT(comments.getToken());
Integer userId = (Integer) claims.get("user_id");
comments.setUser_id(userId);
commentsMapper.delete(comments);
}
}

@ -64,4 +64,16 @@ public class PostsServiceImpl implements PostsService {
//本来还有解析token判断逻辑。但是懒得写了
postsMapper.postsUpdate(posts);
}
@Override
public List<Posts> search(String title, String username) {
if (title == null || title.equals("")) {
// by作者名
return postsMapper.searchByTitle(username);
}
else{
// by 标题
return postsMapper.searchByUser(title);
}
}
}

@ -64,4 +64,16 @@ public class UsersServiceImpl implements UsersService {
usersMapper.revise(users);
}
@Override
public void logout(Users users) {
//解析令牌得到user_id将username设置为用户已注销password等删除
//根据token解析出id原用户名原密码来
Claims claims = JwtUtils.parseJWT(users.getToken());
Integer user_id = claims.get("user_id", Integer.class); // 假设 user_id 是字符串类型
log.info("注销id为{}",user_id);
usersMapper.logout(user_id);
}
}

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="top.lejings.demo.mapper.CommentsMapper">
<insert id="addComment">
INSERT INTO comments (post_id,user_id, content)
VALUES (#{post_id},#{user_id},#{content});
</insert>
<!-- 同包同名工作空间一致id一致返回值一致即resultType单条结果的封装类的全类名 -->
<select id="getComment" resultType="top.lejings.demo.pojo.Comments">
SELECT
c.comment_id ,
c.content ,
c.updated_at ,
u.username
FROM
comments c
JOIN
users u ON c.user_id = u.user_id
WHERE
c.post_id = ${ postId }
AND c.is_deleted = FALSE
ORDER BY
c.created_at ASC;
</select>
<select id="searchByUser" resultType="top.lejings.demo.pojo.Comments">
SELECT
c.comment_id,
c.post_id,
c.user_id,
c.content,
c.updated_at,
u.username,
p.title AS post_title -- 添加对post表的title列
FROM
comments c
JOIN
users u ON c.user_id = u.user_id
JOIN
posts p ON c.post_id = p.post_id -- 加入对posts表的连接
WHERE
u.username = #{username}
AND c.is_deleted = FALSE
ORDER BY
c.created_at DESC
</select>
<select id="searchByContent" resultType="top.lejings.demo.pojo.Comments">
SELECT
c.comment_id,
c.post_id,
c.user_id,
c.content,
c.updated_at,
u.username,
p.title AS post_title -- 添加对post表的title列
FROM
comments c
JOIN
users u ON c.user_id = u.user_id
JOIN
posts p ON c.post_id = p.post_id -- 加入对posts表的连接
WHERE
c.content LIKE CONCAT('%', #{content}, '%')
AND c.is_deleted = FALSE
ORDER BY
c.created_at DESC
</select>
</mapper>

@ -55,6 +55,24 @@
p.post_id, p.user_id, u.username, p.title, p.content, p.updated_at, r.like_count;
</select>
<select id="searchByTitle" resultType="top.lejings.demo.pojo.Posts">
SELECT p.post_id, p.user_id, COUNT(r.reaction_id) AS Likes, u.username, p.title, p.updated_at
FROM posts p
JOIN users u ON p.user_id = u.user_id
LEFT JOIN reactions r ON p.post_id = r.post_id AND r.type = 'like'
WHERE p.title LIKE CONCAT('%', #{title}, '%')
GROUP BY p.post_id, p.user_id, u.username, p.title, p.updated_at
ORDER BY p.updated_at DESC;
</select>
<select id="searchByUser" resultType="top.lejings.demo.pojo.Posts">
SELECT p.post_id, p.user_id, COUNT(r.reaction_id) AS Likes, u.username, p.title, p.content, p.updated_at
FROM posts p
JOIN users u ON p.user_id = u.user_id
LEFT JOIN reactions r ON p.post_id = r.post_id AND r.type = 'like'
WHERE u.username = #{username}
GROUP BY p.post_id, p.user_id, u.username, p.title, p.content, p.updated_at
ORDER BY p.updated_at DESC;
</select>
<insert id="postsNew">
INSERT INTO posts (user_id, title, content, priority)
VALUES (#{user_id}, #{title}, #{content}, 0);

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="top.lejings.demo.mapper.CommentsMapper">
<insert id="addComment">
INSERT INTO comments (post_id,user_id, content)
VALUES (#{post_id},#{user_id},#{content});
</insert>
<!-- 同包同名工作空间一致id一致返回值一致即resultType单条结果的封装类的全类名 -->
<select id="getComment" resultType="top.lejings.demo.pojo.Comments">
SELECT
c.comment_id ,
c.content ,
c.updated_at ,
u.username
FROM
comments c
JOIN
users u ON c.user_id = u.user_id
WHERE
c.post_id = ${ postId }
AND c.is_deleted = FALSE
ORDER BY
c.created_at ASC;
</select>
<select id="searchByUser" resultType="top.lejings.demo.pojo.Comments">
SELECT
c.comment_id,
c.post_id,
c.user_id,
c.content,
c.updated_at,
u.username,
p.title AS post_title -- 添加对post表的title列
FROM
comments c
JOIN
users u ON c.user_id = u.user_id
JOIN
posts p ON c.post_id = p.post_id -- 加入对posts表的连接
WHERE
u.username = #{username}
AND c.is_deleted = FALSE
ORDER BY
c.created_at DESC
</select>
<select id="searchByContent" resultType="top.lejings.demo.pojo.Comments">
SELECT
c.comment_id,
c.post_id,
c.user_id,
c.content,
c.updated_at,
u.username,
p.title AS post_title -- 添加对post表的title列
FROM
comments c
JOIN
users u ON c.user_id = u.user_id
JOIN
posts p ON c.post_id = p.post_id -- 加入对posts表的连接
WHERE
c.content LIKE CONCAT('%', #{content}, '%')
AND c.is_deleted = FALSE
ORDER BY
c.created_at DESC
</select>
</mapper>

@ -55,6 +55,24 @@
p.post_id, p.user_id, u.username, p.title, p.content, p.updated_at, r.like_count;
</select>
<select id="searchByTitle" resultType="top.lejings.demo.pojo.Posts">
SELECT p.post_id, p.user_id, COUNT(r.reaction_id) AS Likes, u.username, p.title, p.updated_at
FROM posts p
JOIN users u ON p.user_id = u.user_id
LEFT JOIN reactions r ON p.post_id = r.post_id AND r.type = 'like'
WHERE p.title LIKE CONCAT('%', #{title}, '%')
GROUP BY p.post_id, p.user_id, u.username, p.title, p.updated_at
ORDER BY p.updated_at DESC;
</select>
<select id="searchByUser" resultType="top.lejings.demo.pojo.Posts">
SELECT p.post_id, p.user_id, COUNT(r.reaction_id) AS Likes, u.username, p.title, p.content, p.updated_at
FROM posts p
JOIN users u ON p.user_id = u.user_id
LEFT JOIN reactions r ON p.post_id = r.post_id AND r.type = 'like'
WHERE u.username = #{username}
GROUP BY p.post_id, p.user_id, u.username, p.title, p.content, p.updated_at
ORDER BY p.updated_at DESC;
</select>
<insert id="postsNew">
INSERT INTO posts (user_id, title, content, priority)
VALUES (#{user_id}, #{title}, #{content}, 0);

Binary file not shown.

@ -123,6 +123,12 @@ ALTER TABLE reactions ADD CONSTRAINT unique_user_post_reaction UNIQUE (post_id,
### 新增评论表
CREATE TABLE comments ( comment_id INT AUTO_INCREMENT PRIMARY KEY, post_id INT NOT NULL, user_id INT NOT NULL, content TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, is_deleted BOOLEAN DEFAULT FALSE, -- 标记评论是否被删除,默认不删除 FOREIGN KEY (post_id) REFERENCES posts(post_id) ON DELETE CASCADE, FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE );
Loading…
Cancel
Save