添加公共讨论区,题目讨论区,比赛评论

This commit is contained in:
Himit_ZH 2021-05-09 23:41:34 +08:00
parent 60b249f901
commit ee71b85ab5
63 changed files with 3514 additions and 171 deletions

View File

@ -2,7 +2,7 @@
基于前后端分离分布式架构的在线测评平台hoj
在线Demo[http://www.hcode.top](http://www.hcode.top)
在线Demo[https://www.hcode.top](https://www.hcode.top)
> 上线日记
@ -15,6 +15,7 @@
| 2021-04-19 | 加入rsync实现评测数据同步修复一些已知的BUG | Himit_ZH |
| 2021-04-24 | 加入题目模板,修改页面页脚 | Himit_ZH |
| 2021-05-02 | 修复比赛后管理员重判题目导致排行榜失效的问题 | Himit_ZH |
| 2021-05-09 | 添加公共讨论区,题目讨论区,比赛评论 | Himit_ZH |
# 二、部署
@ -35,12 +36,15 @@
- [x] 题目支持特别判题
- [x] 题目支持可选择性去除提交代码的末尾空白符会影响CE判定
- [x] 题目支持可选择性允许用户查看各个测试点结果状态运行时间运行空间OI题目的测试点得分暂不支持测试点数据公开。
- [x] 题目讨论
- [x] 管理后台支持题目数据以ZIP上传或手动输入上传
- [x] 管理后台支持监控服务系统的状态及各判题服务的状态
- [x] 管理后台支持动态修改网站配置,例如邮件系统配置,数据库配置等
- [x] 比赛支持封榜支持ACM与OI模式
- [x] 比赛支持私有赛(需要密码才可查看与提交),保护赛(每个用户都可查看,提交需要密码),公开赛(每个用户都可查看与提交)三种模式
- [x] 用户提交失败时可重新提交,管理员支持提交重判与比赛题目所有提交重判
- [x] 公共讨论区
- [x] 比赛讨论
- [ ] ......
- 后端:
- [x] Web框架技术以Springboot为主
@ -111,33 +115,33 @@
> 首页页面
![hoj1](https://cdn.jsdelivr.net/gh/HimitZH/CDN/images/hoj1.png)
![首页](https://img-blog.csdnimg.cn/20210509232352226.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80Mzg1MzA5Nw==,size_16,color_FFFFFF,t_70)
> 题目列表页
![hoj2](https://cdn.jsdelivr.net/gh/HimitZH/CDN/images/hoj2.png)
![题目列表](https://img-blog.csdnimg.cn/20210509232501952.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80Mzg1MzA5Nw==,size_16,color_FFFFFF,t_70)
> 题目详情页
![hoj7](https://cdn.jsdelivr.net/gh/HimitZH/CDN/images/hoj7.png)
![题目详情页](https://img-blog.csdnimg.cn/20210509232609398.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80Mzg1MzA5Nw==,size_16,color_FFFFFF,t_70)
> 比赛列表页
![hoj3](https://cdn.jsdelivr.net/gh/HimitZH/CDN/images/hoj3.png)
![比赛列表](https://img-blog.csdnimg.cn/20210509232701288.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80Mzg1MzA5Nw==,size_16,color_FFFFFF,t_70)
> 比赛详情首页
![hoj4](https://cdn.jsdelivr.net/gh/HimitZH/CDN/images/hoj4.png)
![比赛详情](https://img-blog.csdnimg.cn/20210509232843932.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80Mzg1MzA5Nw==,size_16,color_FFFFFF,t_70#pic_center)
> 提交列表页
![提交列表](https://img-blog.csdnimg.cn/20210509232933478.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80Mzg1MzA5Nw==,size_16,color_FFFFFF,t_70)
> 排行榜
@ -145,15 +149,29 @@
> 公共讨论区
![公共讨论区](https://img-blog.csdnimg.cn/2021050923351998.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80Mzg1MzA5Nw==,size_16,color_FFFFFF,t_70)
> 评论组件
![评论组件](C:\Users\HZH990730\AppData\Roaming\Typora\typora-user-images\image-20210509233700989.png)
> 个人信息页
![hoj6](https://cdn.jsdelivr.net/gh/HimitZH/CDN/images/hoj6.png)
![个人信息](https://img-blog.csdnimg.cn/20210509233300701.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80Mzg1MzA5Nw==,size_16,color_FFFFFF,t_70)
> 个人设置页
![hoj8](https://cdn.jsdelivr.net/gh/HimitZH/CDN/images/hoj8.png)
![个人设置](https://img-blog.csdnimg.cn/20210509233439791.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80Mzg1MzA5Nw==,size_16,color_FFFFFF,t_70)
@ -165,11 +183,15 @@
> 部分手机端显示
![hojmb1](https://cdn.jsdelivr.net/gh/HimitZH/CDN/images/hojmb1.png)
![](https://img-blog.csdnimg.cn/20210509233756882.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80Mzg1MzA5Nw==,size_16,color_FFFFFF,t_70)
![评论区](https://img-blog.csdnimg.cn/20210509233845230.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80Mzg1MzA5Nw==,size_16,color_FFFFFF,t_70)
![hojmb2](https://cdn.jsdelivr.net/gh/HimitZH/CDN/images/hojmb2.png)
# 四、特判程序例子

View File

@ -69,7 +69,7 @@
-e JVM_XMN=64m \
-e MODE=standalone \
-e SPRING_DATASOURCE_PLATFORM=mysql \
-e MYSQL_SERVICE_HOST="数据库所在服务器ip" \
-e MYSQL_SERVICE_HOST="数据库所在服务器ip或使用容器ip172.18.0.1" \
-e MYSQL_SERVICE_PORT=3306 \
-e MYSQL_SERVICE_USER=root \
-e MYSQL_SERVICE_PASSWORD="数据库密码" \
@ -82,7 +82,7 @@
nacos/nacos-server
```
3. 查看自定义网络中各容器ip一般该hoj-network的ip应该是**172.18.0.2或172.19.0.2**
3. 查看自定义网络中各容器ip
```shell
//查看网络
@ -91,7 +91,7 @@
docker network inspect hoj-network
```
6. 连上nacos将后端服务需要的配置添加进去
4. 连上nacos将后端服务需要的配置添加进去
```she
http://ip:8848/nacos/index.html
@ -106,23 +106,23 @@
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210416154647434.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80Mzg1MzA5Nw==,size_16,color_FFFFFF,t_70)
7. hoj-data-backup-prod.yml的配置如下请自行修改
5. hoj-data-backup-prod.yml的配置如下请自行修改
```yaml
hoj:
jwt:
# 加密秘钥
secret: zsc-acm-hoj
# token有效时长24小时,单位秒
expire: 86400
# 6小时内还有请求,可进行刷新
checkRefreshExpire: 21600
# token有效时长3*3600*24单位秒
expire: 259200
# 2*3600*24s内还有请求,可进行刷新
checkRefreshExpire: 172800
header: token
judge:
# 调用判题服务器的token
token: zsc-acm-hoj-judge-server
db: # mysql数据库服务配置
host: your_mysql_host
host: your_mysql_host #如果是公用容器网络 请使用网络ip 例如1.1的172.18.0.1
port: your_mysql_port
name: your_mysql_database_name # 默认hoj
username: your_mysql_username
@ -135,7 +135,7 @@
port: your_email_port
background-img: https://cdn.jsdelivr.net/gh/HimitZH/CDN/images/HCODE.png # 邮箱系统发送邮件模板的背景图片地址
redis: # redis服务配置
host: your_redis_host
host: your_redis_host #如果是公用容器网络 请使用网络ip 例如1.3的172.18.0.2
port: 6371
password: your_redis_password
web-config:
@ -160,7 +160,7 @@
password: cf账号1密码,cf账号2密码,...
```
8. 添加好后点击发布再次添加hoj-judge-server-prod.yml流程一样
6. 添加好后点击发布再次添加hoj-judge-server-prod.yml流程一样
```yaml
hoj:
@ -207,7 +207,7 @@ docker run -d --name redis -p 6379:6379 -v /hoj/redis/data:/data -v /hoj/redis/c
```yaml
hoj-backstage:
port: 6688
nacos-url: 172.18.0.2:8848 # nacos地址,如果使用了docker network 可用使用network的ip 否则请使用服务器ip
nacos-url: 172.18.0.3:8848 # nacos地址,如果使用了docker network 可用使用network的ip 否则请使用服务器ip
```
2. 使用cmd打开当前JudgeServer文件夹路径然后使用mvn命令进行打包成jar包
@ -371,11 +371,7 @@ docker run -d --name redis -p 6379:6379 -v /hoj/redis/data:/data -v /hoj/redis/c
2. 前提是本地有vue-cli4请自行百度下载
3. 进入/hoj-vue文件夹修改生产环境下的后端服务地址
![在这里插入图片描述](https://img-blog.csdnimg.cn/2021041616214988.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80Mzg1MzA5Nw==,size_16,color_FFFFFF,t_70)
4. 然后在当前路径运行打包命令
4. 然后在当前src路径运行打包命令
```powershell
npm run build

View File

@ -130,11 +130,11 @@
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--热部署工具-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<!-- &lt;!&ndash;热部署工具&ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-devtools</artifactId>-->
<!-- </dependency>-->
<!--模板引擎,主要用于发送邮件模板-->
<dependency>
<groupId>org.springframework.boot</groupId>
@ -164,6 +164,12 @@
<artifactId>jsoup</artifactId>
<version>1.13.1</version>
</dependency>
<!--emoji表情格式转换-->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>java-emoji-converter</artifactId>
<version>1.0.1</version>
</dependency>
</dependencies>

View File

@ -1,15 +1,224 @@
package top.hcode.hoj.controller.oj;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.hutool.core.map.MapUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.github.binarywang.java.emoji.EmojiConverter;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import top.hcode.hoj.common.result.CommonResult;
import top.hcode.hoj.pojo.entity.Comment;
import top.hcode.hoj.pojo.entity.CommentLike;
import top.hcode.hoj.pojo.entity.Reply;
import top.hcode.hoj.pojo.vo.CommentsVo;
import top.hcode.hoj.pojo.vo.UserRolesVo;
import top.hcode.hoj.service.impl.CommentLikeServiceImpl;
import top.hcode.hoj.service.impl.CommentServiceImpl;
import top.hcode.hoj.service.impl.ReplyServiceImpl;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
/**
* @Author: Himit_ZH
* @Date: 2020/10/29 10:14
* @Date: 2021/5/5 15:41
* @Description:
*/
@RestController
@RequestMapping("/api")
public class CommentController {
}
@Autowired
private CommentServiceImpl commentService;
@Autowired
private CommentLikeServiceImpl commentLikeService;
@Autowired
private ReplyServiceImpl replyService;
private static EmojiConverter emojiConverter = EmojiConverter.getInstance();
@GetMapping("/comments")
public CommonResult getComments(@RequestParam(value = "cid", required = false) Long cid,
@RequestParam(value = "did", required = false) Integer did,
@RequestParam(value = "limit", required = false, defaultValue = "20") Integer limit,
@RequestParam(value = "currentPage", required = false, defaultValue = "1") Integer currentPage,
HttpServletRequest request) {
IPage<CommentsVo> commentList = commentService.getCommentList(limit, currentPage, cid, did);
// 获取当前登录的用户
HttpSession session = request.getSession();
UserRolesVo userRolesVo = (UserRolesVo) session.getAttribute("userInfo");
HashMap<Integer, Boolean> commentLikeMap = new HashMap<>();
if (userRolesVo != null) {
// 如果是有登录 需要检查是否对评论有点赞
List<Integer> commentIdList = new LinkedList<>();
for (CommentsVo commentsVo : commentList.getRecords()) {
commentIdList.add(commentsVo.getId());
}
if (commentIdList.size() > 0) {
QueryWrapper<CommentLike> commentLikeQueryWrapper = new QueryWrapper<>();
commentLikeQueryWrapper.in("cid", commentIdList);
List<CommentLike> commentLikeList = commentLikeService.list(commentLikeQueryWrapper);
// 如果存在记录需要修正Map为true
for (CommentLike tmp : commentLikeList) {
commentLikeMap.put(tmp.getCid(), true);
}
}
}
return CommonResult.successResponse(MapUtil.builder()
.put("commentList", commentList)
.put("commentLikeMap", commentLikeMap).map(), "获取成功");
}
@PostMapping("/comment")
@RequiresAuthentication
public CommonResult addComment(@RequestBody Comment comment, HttpServletRequest request) {
// 获取当前登录的用户
HttpSession session = request.getSession();
UserRolesVo userRolesVo = (UserRolesVo) session.getAttribute("userInfo");
comment.setFromAvatar(userRolesVo.getAvatar())
.setFromName(userRolesVo.getUsername())
.setFromUid(userRolesVo.getUid());
if (SecurityUtils.getSubject().hasRole("root")) {
comment.setFromRole("root");
} else if (SecurityUtils.getSubject().hasRole("admin")) {
comment.setFromRole("admin");
} else {
comment.setFromRole("user");
}
// 带有表情的字符串转换为编码
comment.setContent(emojiConverter.toHtml(comment.getContent()));
boolean isOk = commentService.saveOrUpdate(comment);
if (isOk) {
CommentsVo commentsVo = new CommentsVo();
commentsVo.setContent(comment.getContent());
commentsVo.setId(comment.getId());
commentsVo.setFromAvatar(comment.getFromAvatar());
commentsVo.setFromName(comment.getFromName());
commentsVo.setFromUid(comment.getFromUid());
commentsVo.setLikeNum(0);
commentsVo.setGmtCreate(comment.getGmtCreate());
commentsVo.setReplyList(new LinkedList<>());
return CommonResult.successResponse(commentsVo, "评论成功");
} else {
return CommonResult.errorResponse("评论失败,请重新尝试!");
}
}
@GetMapping("/comment-like")
@RequiresAuthentication
@Transactional
public CommonResult addDiscussionLike(@RequestParam("cid") Integer cid,
@RequestParam("toLike") Boolean toLike,
HttpServletRequest request) {
// 获取当前登录的用户
HttpSession session = request.getSession();
UserRolesVo userRolesVo = (UserRolesVo) session.getAttribute("userInfo");
QueryWrapper<CommentLike> commentLikeQueryWrapper = new QueryWrapper<>();
commentLikeQueryWrapper.eq("cid", cid).eq("uid", userRolesVo.getUid());
CommentLike commentLike = commentLikeService.getOne(commentLikeQueryWrapper, false);
if (toLike) { // 添加点赞
if (commentLike == null) { // 如果不存在就添加
boolean isSave = commentLikeService.saveOrUpdate(new CommentLike()
.setUid(userRolesVo.getUid())
.setCid(cid));
if (!isSave) {
return CommonResult.errorResponse("点赞失败,请重试尝试!");
}
}
// 点赞+1
UpdateWrapper<Comment> commentUpdateWrapper = new UpdateWrapper<>();
commentUpdateWrapper.setSql("like_num=like_num+1").eq("id", cid);
commentService.update(commentUpdateWrapper);
return CommonResult.successResponse(null, "点赞成功");
} else { // 取消点赞
if (commentLike != null) { // 如果存在就删除
boolean isDelete = commentLikeService.removeById(commentLike.getId());
if (!isDelete) {
return CommonResult.errorResponse("取消点赞失败,请重试尝试!");
}
}
// 点赞-1
UpdateWrapper<Comment> commentUpdateWrapper = new UpdateWrapper<>();
commentUpdateWrapper.setSql("like_num=like_num-1").eq("id", cid);
commentService.update(commentUpdateWrapper);
return CommonResult.successResponse(null, "取消成功");
}
}
@GetMapping("/reply")
public CommonResult getAllReply(@RequestParam("commentId") Integer commentId) {
QueryWrapper<Reply> replyQueryWrapper = new QueryWrapper<>();
replyQueryWrapper.eq("comment_id", commentId);
replyQueryWrapper.orderByDesc("gmt_create");
List<Reply> replyList = replyService.list(replyQueryWrapper);
return CommonResult.successResponse(replyList, "获取全部回复列表成功");
}
@PostMapping("/reply")
@RequiresAuthentication
public CommonResult addReply(@RequestBody Reply reply, HttpServletRequest request) {
// 获取当前登录的用户
HttpSession session = request.getSession();
UserRolesVo userRolesVo = (UserRolesVo) session.getAttribute("userInfo");
reply.setFromAvatar(userRolesVo.getAvatar())
.setFromName(userRolesVo.getUsername())
.setFromUid(userRolesVo.getUid());
if (SecurityUtils.getSubject().hasRole("root")) {
reply.setFromRole("root");
} else if (SecurityUtils.getSubject().hasRole("admin")) {
reply.setFromRole("admin");
} else {
reply.setFromRole("user");
}
// 带有表情的字符串转换为编码
reply.setContent(emojiConverter.toHtml(reply.getContent()));
boolean isOk = replyService.saveOrUpdate(reply);
if (isOk) {
return CommonResult.successResponse(reply, "评论成功");
} else {
return CommonResult.errorResponse("评论失败,请重新尝试!");
}
}
}

View File

@ -0,0 +1,209 @@
package top.hcode.hoj.controller.oj;
import cn.hutool.core.map.MapUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import top.hcode.hoj.common.result.CommonResult;
import top.hcode.hoj.pojo.entity.Category;
import top.hcode.hoj.pojo.entity.Discussion;
import top.hcode.hoj.pojo.entity.DiscussionLike;
import top.hcode.hoj.pojo.vo.DiscussionVo;
import top.hcode.hoj.pojo.vo.UserRolesVo;
import top.hcode.hoj.service.impl.CategoryServiceImpl;
import top.hcode.hoj.service.impl.DiscussionLikeServiceImpl;
import top.hcode.hoj.service.impl.DiscussionServiceImpl;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.Arrays;
import java.util.List;
/**
* @Author: Himit_ZH
* @Date: 2021/05/04 10:14
* @Description: 负责讨论与评论模块的数据接口
*/
@RestController
@RequestMapping("/api")
public class DiscussionController {
@Autowired
private DiscussionServiceImpl discussionService;
@Autowired
private DiscussionLikeServiceImpl discussionLikeService;
@Autowired
private CategoryServiceImpl categoryService;
@GetMapping("/discussions")
public CommonResult getDiscussionList(@RequestParam(value = "limit", required = false, defaultValue = "15") Integer limit,
@RequestParam(value = "currentPage", required = false, defaultValue = "1") Integer currentPage,
@RequestParam(value = "cid", required = false) Integer categoryId,
@RequestParam(value = "pid", required = false) String pid,
@RequestParam(value = "keyword", required = false) String keyword) {
QueryWrapper<Discussion> discussionQueryWrapper = new QueryWrapper<>();
IPage<Discussion> iPage = new Page<>(currentPage, limit);
if (categoryId != null) {
discussionQueryWrapper.eq("category_id", categoryId);
}
if (!StringUtils.isEmpty(keyword)) {
final String key = keyword.trim();
discussionQueryWrapper.and(wrapper -> wrapper.like("title", key).or()
.like("author", key).or()
.like("description", key));
}
if (!StringUtils.isEmpty(pid)) {
discussionQueryWrapper.eq("pid", pid);
}
discussionQueryWrapper.eq("status", false)
.orderByDesc("top_priority")
.orderByDesc("gmt_modified")
.orderByDesc("like_num")
.orderByDesc("view_num");
IPage<Discussion> discussionList = discussionService.page(iPage, discussionQueryWrapper);
if (discussionList.getTotal() == 0) { // 未查询到一条数据
return CommonResult.successResponse(discussionList, "暂无数据");
} else {
return CommonResult.successResponse(discussionList, "获取成功");
}
}
@GetMapping("/discussion")
public CommonResult getDiscussion(@RequestParam(value = "did", required = true) Integer did,
HttpServletRequest request) {
// 获取当前登录的用户
HttpSession session = request.getSession();
UserRolesVo userRolesVo = (UserRolesVo) session.getAttribute("userInfo");
String uid = null;
if (userRolesVo != null) {
uid = userRolesVo.getUid();
}
DiscussionVo discussion = discussionService.getDiscussion(did, uid);
if (discussion.getStatus()) {
return CommonResult.errorResponse("对不起,该讨论已被封禁!", CommonResult.STATUS_FORBIDDEN);
}
// 浏览量+1
UpdateWrapper<Discussion> discussionUpdateWrapper = new UpdateWrapper<>();
discussionUpdateWrapper.setSql("view_num=view_num+1").eq("id", discussion.getId());
discussionService.update(discussionUpdateWrapper);
discussion.setViewNum(discussion.getViewNum()+1);
return CommonResult.successResponse(discussion, "获取成功");
}
@PostMapping("/discussion")
@RequiresAuthentication
public CommonResult addDiscussion(@RequestBody Discussion discussion, HttpServletRequest request) {
// 获取当前登录的用户
HttpSession session = request.getSession();
UserRolesVo userRolesVo = (UserRolesVo) session.getAttribute("userInfo");
discussion.setAuthor(userRolesVo.getUsername())
.setAvatar(userRolesVo.getAvatar())
.setUid(userRolesVo.getUid());
if (SecurityUtils.getSubject().hasRole("root")) {
discussion.setRole("root");
} else if (SecurityUtils.getSubject().hasRole("admin")) {
discussion.setRole("admin");
} else {
// 如果不是管理员角色一律重置为不置顶
discussion.setTopPriority(false);
}
boolean isOk = discussionService.saveOrUpdate(discussion);
if (isOk) {
return CommonResult.successResponse(null, "发布成功!");
} else {
return CommonResult.errorResponse("发布失败,请重新尝试!");
}
}
@PutMapping("/discussion")
@RequiresAuthentication
public CommonResult updateDiscussion() {
return null;
}
@DeleteMapping("/discussion")
@RequiresAuthentication
public CommonResult removeDiscussion() {
return null;
}
@GetMapping("/discussion-like")
@Transactional
@RequiresAuthentication
public CommonResult addDiscussionLike(@RequestParam("did") Integer did,
@RequestParam("toLike") Boolean toLike,
HttpServletRequest request) {
// 获取当前登录的用户
HttpSession session = request.getSession();
UserRolesVo userRolesVo = (UserRolesVo) session.getAttribute("userInfo");
QueryWrapper<DiscussionLike> discussionLikeQueryWrapper = new QueryWrapper<>();
discussionLikeQueryWrapper.eq("did", did).eq("uid", userRolesVo.getUid());
DiscussionLike discussionLike = discussionLikeService.getOne(discussionLikeQueryWrapper, false);
if (toLike) { // 添加点赞
if (discussionLike == null) { // 如果不存在就添加
boolean isSave = discussionLikeService.saveOrUpdate(new DiscussionLike().setUid(userRolesVo.getUid()).setDid(did));
if (!isSave) {
return CommonResult.errorResponse("点赞失败,请重试尝试!");
}
}
// 点赞+1
UpdateWrapper<Discussion> discussionUpdateWrapper = new UpdateWrapper<>();
discussionUpdateWrapper.setSql("like_num=like_num+1").eq("id", did);
discussionService.update(discussionUpdateWrapper);
return CommonResult.successResponse(null, "点赞成功");
} else { // 取消点赞
if (discussionLike != null) { // 如果存在就删除
boolean isDelete = discussionLikeService.removeById(discussionLike.getId());
if (!isDelete) {
return CommonResult.errorResponse("取消点赞失败,请重试尝试!");
}
}
// 点赞-1
UpdateWrapper<Discussion> discussionUpdateWrapper = new UpdateWrapper<>();
discussionUpdateWrapper.setSql("like_num=like_num-1").eq("id", did);
discussionService.update(discussionUpdateWrapper);
return CommonResult.successResponse(null, "取消成功");
}
}
@GetMapping("/discussion-category")
public CommonResult getDiscussionCategory() {
List<Category> categoryList = categoryService.list();
return CommonResult.successResponse(categoryList, "获取成功");
}
}

View File

@ -85,16 +85,6 @@ public class JudgeController {
@Autowired
private RemoteJudgeDispatcher remoteJudgeDispatcher;
// @Autowired
// private RestTemplate restTemplate;
// @Value("${service-url.hoj-judge-server}") // restTemplate风格调用不使用
// private String REST_URL_PREFIX;
// @GetMapping("/submit-problem-judge")
// public String list(){
// return restTemplate.getForObject(REST_URL_PREFIX+"/hoj-judge-server/submit-problem-judge", String.class);
// }
/**
* @MethodName submitProblemJudge

View File

@ -135,7 +135,6 @@ public class ProblemController {
// 需要获取一下该token对应用户的数据
HttpSession session = request.getSession();
UserRolesVo userRolesVo = (UserRolesVo) session.getAttribute("userInfo");
HashMap<Long, Object> result = new HashMap<>();
// 先查询判断该用户对于这些题是否已经通过若已通过则无论后续再提交结果如何该题都标记为通过
QueryWrapper<Judge> queryWrapper = new QueryWrapper<>();

View File

@ -0,0 +1,11 @@
package top.hcode.hoj.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
import top.hcode.hoj.pojo.entity.Category;
@Mapper
@Repository
public interface CategoryMapper extends BaseMapper<Category> {
}

View File

@ -0,0 +1,12 @@
package top.hcode.hoj.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
import top.hcode.hoj.pojo.entity.CommentLike;
@Mapper
@Repository
public interface CommentLikeMapper extends BaseMapper<CommentLike> {
}

View File

@ -1,16 +1,26 @@
package top.hcode.hoj.dao;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import top.hcode.hoj.pojo.entity.Comment;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import top.hcode.hoj.pojo.vo.CommentsVo;
/**
* <p>
* Mapper 接口
* Mapper 接口
* </p>
*
* @author Himit_ZH
* @since 2020-10-23
*/
@Mapper
@Repository
public interface CommentMapper extends BaseMapper<Comment> {
IPage<CommentsVo> getCommentList(Page<CommentsVo> page, @Param("cid") Long cid, @Param("did") Integer did);
}

View File

@ -0,0 +1,12 @@
package top.hcode.hoj.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
import top.hcode.hoj.pojo.entity.DiscussionLike;
@Mapper
@Repository
public interface DiscussionLikeMapper extends BaseMapper<DiscussionLike> {
}

View File

@ -0,0 +1,15 @@
package top.hcode.hoj.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import top.hcode.hoj.pojo.entity.Discussion;
import top.hcode.hoj.pojo.vo.DiscussionVo;
@Mapper
@Repository
public interface DiscussionMapper extends BaseMapper<Discussion> {
DiscussionVo getDiscussion(@Param("did") Integer did, @Param("uid") String uid);
}

View File

@ -0,0 +1,17 @@
package top.hcode.hoj.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
import top.hcode.hoj.pojo.entity.Reply;
/**
* @Author: Himit_ZH
* @Date: 2021/5/5 22:07
* @Description:
*/
@Mapper
@Repository
public interface ReplyMapper extends BaseMapper<Reply> {
}

View File

@ -2,4 +2,37 @@
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="top.hcode.hoj.dao.CommentMapper">
<resultMap id="map_CommentList" type="top.hcode.hoj.pojo.vo.CommentsVo">
<id column="id" property="id"></id>
<result column="content" property="content"></result>
<result column="from_uid" property="fromUid"></result>
<result column="from_name" property="fromName"></result>
<result column="from_avatar" property="fromAvatar"></result>
<result column="from_role" property="fromRole"></result>
<result column="like_num" property="likeNum"></result>
<result column="total_reply_num" property="totalReplyNum"></result>
<result column="gmt_create" property="gmtCreate"></result>
<collection property="replyList" ofType="top.hcode.hoj.pojo.entity.Reply" select="getCommentListReply" column="id">
</collection>
</resultMap>
<!-- 主查询 -->
<select id="getCommentList" resultMap="map_CommentList" resultType="list">
SELECT c.*,(SELECT COUNT(1) FROM reply WHERE comment_id=c.id) as total_reply_num FROM comment c
<where>
<if test="cid!=null">
c.cid=#{cid}
</if>
<if test="did!=null">
AND c.did=#{did}
</if>
</where>
order by c.like_num desc,c.gmt_create desc
</select>
<!-- 子查询 -->
<select id="getCommentListReply" resultType="top.hcode.hoj.pojo.entity.Reply">
select r.* from reply r where r.comment_id=#{id} order by r.gmt_create desc LIMIT 3
</select>
</mapper>

View File

@ -0,0 +1,40 @@
<?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="top.hcode.hoj.dao.DiscussionMapper">
<resultMap id="map_DiscussionVo" type="top.hcode.hoj.pojo.vo.DiscussionVo">
<id column="id" property="id"></id>
<result column="content" property="content"></result>
<result column="description" property="description"></result>
<result column="title" property="title"></result>
<result column="category_id" property="categoryId"></result>
<result column="category_name" property="categoryName"></result>
<result column="pid" property="pid"></result>
<result column="uid" property="uid"></result>
<result column="author" property="author"></result>
<result column="avatar" property="avatar"></result>
<result column="role" property="role"></result>
<result column="view_num" property="viewNum"></result>
<result column="like_num" property="likeNum"></result>
<result column="has_like" property="hasLike"></result>
<result column="top_priority" property="topPriority"></result>
<result column="status" property="status"></result>
<result column="gmt_modified" property="gmtModified"></result>
<result column="gmt_create" property="gmtCreate"></result>
</resultMap>
<!-- 主查询 -->
<select id="getDiscussion" resultMap="map_DiscussionVo" resultType="top.hcode.hoj.pojo.vo.DiscussionVo">
SELECT d.*,c.id as category_id,c.name as category_name,
(SELECT 1 FROM discussion_like dl WHERE dl.uid = #{uid} AND dl.did = #{did} LIMIT 1) as has_like
FROM discussion d,category c
<where>
c.id = d.category_id
<if test="did!=null">
and d.id=#{did}
</if>
</where>
</select>
</mapper>

View File

@ -88,4 +88,4 @@
select r.* from role r,user_role ur where ur.uid=#{uuid} and ur.role_id = r.id
</select>
</mapper>
</mapper>

View File

@ -0,0 +1,54 @@
package top.hcode.hoj.pojo.vo;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import top.hcode.hoj.pojo.entity.Reply;
import java.util.Date;
import java.util.List;
/**
* @Author: Himit_ZH
* @Date: 2021/5/5 22:30
* @Description:
*/
@ApiModel(value = "评论数据列表VO", description = "")
@Data
public class CommentsVo {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "评论id")
private Integer id;
@ApiModelProperty(value = "评论内容")
private String content;
@ApiModelProperty(value = "评论者id")
private String fromUid;
@ApiModelProperty(value = "评论者用户名")
private String fromName;
@ApiModelProperty(value = "评论组头像地址")
private String fromAvatar;
@ApiModelProperty(value = "评论者角色")
private String fromRole;
@ApiModelProperty(value = "点赞数量")
private Integer likeNum;
@ApiModelProperty(value = "该评论的总回复数")
private Integer totalReplyNum;
private Date gmtCreate;
@ApiModelProperty(value = "该评论回复列表")
private List<Reply> replyList;
}

View File

@ -0,0 +1,74 @@
package top.hcode.hoj.pojo.vo;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
/**
* @Author: Himit_ZH
* @Date: 2021/5/7 10:06
* @Description:
*/
@Data
@ApiModel(value = "讨论数据VO", description = "")
public class DiscussionVo {
private Integer id;
@ApiModelProperty(value = "分类id")
private Integer categoryId;
@ApiModelProperty(value = "分类名字")
private String categoryName;
@ApiModelProperty(value = "讨论标题")
private String title;
@ApiModelProperty(value = "讨论简介")
private String description;
@ApiModelProperty(value = "讨论内容")
private String content;
@ApiModelProperty(value = "题目关联 默认为null则不关联题目")
private String pid;
@ApiModelProperty(value = "发表者id")
private String uid;
@ApiModelProperty(value = "发表者用户名")
private String author;
@ApiModelProperty(value = "发表者头像地址")
private String avatar;
@ApiModelProperty(value = "发表者角色")
private String role;
@ApiModelProperty(value = "浏览数量")
private Integer viewNum;
@ApiModelProperty(value = "点赞数量")
private Integer likeNum;
@ApiModelProperty(value = "如果有登录的话,是否点赞了")
private Boolean hasLike;
@ApiModelProperty(value = "优先级,是否置顶")
private Boolean topPriority;
@ApiModelProperty(value = "是否封禁")
private Boolean status;
@TableField(fill = FieldFill.INSERT)
private Date gmtCreate;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date gmtModified;
}

View File

@ -18,6 +18,8 @@ import java.util.List;
@Data
public class UserRolesVo implements Serializable {
private static final long serialVersionUID = 10000L;
@ApiModelProperty(value = "用户id")
private String uid;

View File

@ -0,0 +1,8 @@
package top.hcode.hoj.service;
import com.baomidou.mybatisplus.extension.service.IService;
import top.hcode.hoj.pojo.entity.Category;
public interface CategoryService extends IService<Category> {
}

View File

@ -0,0 +1,8 @@
package top.hcode.hoj.service;
import com.baomidou.mybatisplus.extension.service.IService;
import top.hcode.hoj.pojo.entity.CommentLike;
public interface CommentLikeService extends IService<CommentLike> {
}

View File

@ -1,7 +1,10 @@
package top.hcode.hoj.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import top.hcode.hoj.pojo.entity.Comment;
import com.baomidou.mybatisplus.extension.service.IService;
import top.hcode.hoj.pojo.vo.CommentsVo;
import top.hcode.hoj.pojo.vo.UserRolesVo;
/**
* <p>
@ -12,5 +15,5 @@ import com.baomidou.mybatisplus.extension.service.IService;
* @since 2020-10-23
*/
public interface CommentService extends IService<Comment> {
IPage<CommentsVo> getCommentList(int limit, int currentPage, Long cid,Integer did);
}

View File

@ -0,0 +1,7 @@
package top.hcode.hoj.service;
import com.baomidou.mybatisplus.extension.service.IService;
import top.hcode.hoj.pojo.entity.DiscussionLike;
public interface DiscussionLikeService extends IService<DiscussionLike> {
}

View File

@ -0,0 +1,10 @@
package top.hcode.hoj.service;
import com.baomidou.mybatisplus.extension.service.IService;
import top.hcode.hoj.pojo.entity.Discussion;
import top.hcode.hoj.pojo.vo.DiscussionVo;
public interface DiscussionService extends IService<Discussion> {
DiscussionVo getDiscussion(Integer did,String uid);
}

View File

@ -0,0 +1,12 @@
package top.hcode.hoj.service;
import com.baomidou.mybatisplus.extension.service.IService;
import top.hcode.hoj.pojo.entity.Reply;
/**
* @Author: Himit_ZH
* @Date: 2021/5/5 22:08
* @Description:
*/
public interface ReplyService extends IService<Reply> {
}

View File

@ -1,7 +1,6 @@
package top.hcode.hoj.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import top.hcode.hoj.pojo.entity.UserRole;
import com.baomidou.mybatisplus.extension.service.IService;
import top.hcode.hoj.pojo.vo.UserRolesVo;

View File

@ -0,0 +1,16 @@
package top.hcode.hoj.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import top.hcode.hoj.dao.CategoryMapper;
import top.hcode.hoj.pojo.entity.Category;
import top.hcode.hoj.service.CategoryService;
/**
* @Author: Himit_ZH
* @Date: 2021/5/4 22:30
* @Description:
*/
@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {
}

View File

@ -0,0 +1,17 @@
package top.hcode.hoj.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import top.hcode.hoj.dao.CommentLikeMapper;
import top.hcode.hoj.pojo.entity.CommentLike;
import top.hcode.hoj.service.CommentLikeService;
/**
* @Author: Himit_ZH
* @Date: 2021/5/4 22:31
* @Description:
*/
@Service
public class CommentLikeServiceImpl extends ServiceImpl<CommentLikeMapper, CommentLike> implements CommentLikeService {
}

View File

@ -1,14 +1,18 @@
package top.hcode.hoj.service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.beans.factory.annotation.Autowired;
import top.hcode.hoj.pojo.entity.Comment;
import top.hcode.hoj.dao.CommentMapper;
import top.hcode.hoj.pojo.vo.CommentsVo;
import top.hcode.hoj.service.CommentService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* <p>
* 服务实现类
* 服务实现类
* </p>
*
* @author Himit_ZH
@ -17,4 +21,13 @@ import org.springframework.stereotype.Service;
@Service
public class CommentServiceImpl extends ServiceImpl<CommentMapper, Comment> implements CommentService {
@Autowired
private CommentMapper commentMapper;
@Override
public IPage<CommentsVo> getCommentList(int limit, int currentPage, Long cid, Integer did) {
//新建分页
Page<CommentsVo> page = new Page<>(currentPage, limit);
return commentMapper.getCommentList(page, cid, did);
}
}

View File

@ -0,0 +1,16 @@
package top.hcode.hoj.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import top.hcode.hoj.dao.DiscussionLikeMapper;
import top.hcode.hoj.pojo.entity.DiscussionLike;
import top.hcode.hoj.service.DiscussionLikeService;
/**
* @Author: Himit_ZH
* @Date: 2021/5/4 22:31
* @Description:
*/
@Service
public class DiscussionLikeServiceImpl extends ServiceImpl<DiscussionLikeMapper, DiscussionLike> implements DiscussionLikeService {
}

View File

@ -0,0 +1,26 @@
package top.hcode.hoj.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import top.hcode.hoj.dao.DiscussionMapper;
import top.hcode.hoj.pojo.entity.Discussion;
import top.hcode.hoj.pojo.vo.DiscussionVo;
import top.hcode.hoj.service.DiscussionService;
/**
* @Author: Himit_ZH
* @Date: 2021/5/4 22:31
* @Description:
*/
@Service
public class DiscussionServiceImpl extends ServiceImpl<DiscussionMapper, Discussion> implements DiscussionService {
@Autowired
private DiscussionMapper discussionMapper;
@Override
public DiscussionVo getDiscussion(Integer did, String uid) {
return discussionMapper.getDiscussion(did, uid);
}
}

View File

@ -0,0 +1,16 @@
package top.hcode.hoj.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import top.hcode.hoj.dao.ReplyMapper;
import top.hcode.hoj.pojo.entity.Reply;
import top.hcode.hoj.service.ReplyService;
/**
* @Author: Himit_ZH
* @Date: 2021/5/5 22:09
* @Description:
*/
@Service
public class ReplyServiceImpl extends ServiceImpl<ReplyMapper, Reply> implements ReplyService {
}

View File

@ -1,6 +1,6 @@
hoj-backstage:
port: 6688 # 本服务器启动的端口号
nacos-url: 172.18.0.3:8848 # nacos的地址
nacos-url: 127.0.0.1:8848 # nacos的地址
server:
port: ${hoj-backstage.port}

View File

@ -28,16 +28,10 @@ import org.springframework.web.client.RestTemplate;
import top.hcode.hoj.common.result.CommonResult;
import top.hcode.hoj.dao.*;
import top.hcode.hoj.pojo.entity.*;
import top.hcode.hoj.pojo.vo.AnnouncementVo;
import top.hcode.hoj.pojo.vo.ContestVo;
import top.hcode.hoj.pojo.vo.RoleAuthsVo;
import top.hcode.hoj.pojo.vo.UserRolesVo;
import top.hcode.hoj.pojo.vo.*;
import top.hcode.hoj.service.UserInfoService;
import top.hcode.hoj.service.UserRoleService;
import top.hcode.hoj.service.impl.AnnouncementServiceImpl;
import top.hcode.hoj.service.impl.LanguageServiceImpl;
import top.hcode.hoj.service.impl.UserInfoServiceImpl;
import top.hcode.hoj.service.impl.UserRoleServiceImpl;
import top.hcode.hoj.service.impl.*;
import top.hcode.hoj.utils.Constants;
import top.hcode.hoj.utils.IpUtils;
import top.hcode.hoj.utils.JsoupUtils;
@ -79,37 +73,13 @@ public class DataBackupApplicationTests {
@Autowired
private AnnouncementServiceImpl announcementService;
@Autowired
private DiscussionServiceImpl discussionService;
@Test
public void Test1() {
// UserRolesVo roles = userRoleMapper.getUserRoles("c5ddbe4b38d641bea7d87ae0e102260d",null);
// System.out.println(roles);
// IPage<UserRolesVo> admin = userRoleService.getUserList(10, 1, "admin");
// System.out.println(admin.getPages());
// System.out.println(admin.getSize());
// System.out.println(admin.getRecords());
// List<String> list = new LinkedList<>();
// list.add("1");
// list.add("2");
// boolean b = userInfoService.removeByIds(list);
// System.out.println(b);
// UserInfo userInfo = new UserInfo().
// setUsername("111")
// .setPassword("1111")
// .setEmail("11111");
// boolean b = userInfoService.saveOrUpdate(userInfo);
// if (b){
// System.out.println(userInfo);
// }
// List<AnnouncementVo> contestAnnouncement = announcementService.getContestAnnouncement(1L);
// System.out.println(contestAnnouncement.size());
String test = "{1}....{2}";
String command = "/usr/bin/java -cp {1} -XX:MaxRAM={2}k -Djava.security.manager -Dfile.encoding=UTF-8 -Djava.security.policy==/etc/java_policy -Djava.awt.headless=true Main";
String exePath = "/judge/run/32/1.exe";
String exeDir = "/judge/run/32";
int maxMemory = 1024 * 10;
List<String> commandList = Arrays.asList(MessageFormat.format(command, exePath, exeDir, (maxMemory / 1024)).split(" "));
System.out.println(commandList);
DiscussionVo discussion = discussionService.getDiscussion(5, "c5ddbe4b38d641bea7d87ae0e102260d");
System.out.println(discussion);
}
@Autowired

View File

@ -0,0 +1,38 @@
package top.hcode.hoj.pojo.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.util.Date;
/**
* @Author: Himit_ZH
* @Date: 2021/5/4 22:09
* @Description:
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value="Category对象", description="")
public class Category {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@ApiModelProperty(value = "分类名字")
private String name;
@TableField(fill = FieldFill.INSERT)
private Date gmtCreate;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date gmtModified;
}

View File

@ -30,24 +30,31 @@ public class Comment implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Long id;
private Integer id;
private String uid;
@ApiModelProperty(value = "NULL表示无引用比赛")
private Long cid;
@ApiModelProperty(value = "讨论标题")
private String title;
@ApiModelProperty(value = "NULL表示无引用讨论")
private Integer did;
@ApiModelProperty(value = "讨论详情")
@ApiModelProperty(value = "评论内容")
private String content;
@ApiModelProperty(value = "标签id,0表示无")
private Long tid;
@ApiModelProperty(value = "评论者id")
private String fromUid;
@ApiModelProperty(value = "0表示无引用题目")
private Long pid;
@ApiModelProperty(value = "评论者用户名")
private String fromName;
@ApiModelProperty(value = "0表示无引用比赛")
private Long cid;
@ApiModelProperty(value = "评论组头像地址")
private String fromAvatar;
@ApiModelProperty(value = "评论者角色")
private String fromRole;
@ApiModelProperty(value = "点赞数量")
private Integer likeNum;
@TableField(fill = FieldFill.INSERT)
private Date gmtCreate;

View File

@ -0,0 +1,42 @@
package top.hcode.hoj.pojo.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.util.Date;
/**
* @Author: Himit_ZH
* @Date: 2021/5/5 11:36
* @Description:
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value="CommentLike对象", description="")
public class CommentLike {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@ApiModelProperty(value = "评论id")
private Integer cid;
@ApiModelProperty(value = "用户id")
private String uid;
@TableField(fill = FieldFill.INSERT)
private Date gmtCreate;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date gmtModified;
}

View File

@ -0,0 +1,75 @@
package top.hcode.hoj.pojo.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.util.Date;
/**
* @Author: Himit_ZH
* @Date: 2021/5/4 22:11
* @Description:
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value="Discussion对象", description="")
public class Discussion {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
@ApiModelProperty(value = "分类id")
private Integer categoryId;
@ApiModelProperty(value = "讨论标题")
private String title;
@ApiModelProperty(value = "讨论简介")
private String description;
@ApiModelProperty(value = "讨论内容")
private String content;
@ApiModelProperty(value = "题目关联 默认为null则不关联题目")
private String pid;
@ApiModelProperty(value = "发表者id")
private String uid;
@ApiModelProperty(value = "发表者用户名")
private String author;
@ApiModelProperty(value = "发表者头像地址")
private String avatar;
@ApiModelProperty(value = "发表者角色")
private String role;
@ApiModelProperty(value = "浏览数量")
private Integer viewNum;
@ApiModelProperty(value = "点赞数量")
private String likeNum;
@ApiModelProperty(value = "优先级,是否置顶")
private Boolean topPriority;
@ApiModelProperty(value = "是否封禁")
private Boolean status;
@TableField(fill = FieldFill.INSERT)
private Date gmtCreate;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date gmtModified;
}

View File

@ -0,0 +1,42 @@
package top.hcode.hoj.pojo.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.util.Date;
/**
* @Author: Himit_ZH
* @Date: 2021/5/5 11:36
* @Description:
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value="DiscussionLike对象", description="")
public class DiscussionLike {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@ApiModelProperty(value = "讨论id")
private Integer did;
@ApiModelProperty(value = "用户id")
private String uid;
@TableField(fill = FieldFill.INSERT)
private Date gmtCreate;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date gmtModified;
}

View File

@ -0,0 +1,64 @@
package top.hcode.hoj.pojo.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.util.Date;
/**
* @Author: Himit_ZH
* @Date: 2021/5/5 19:03
* @Description:
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value="Reply对象", description="")
public class Reply {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
@ApiModelProperty(value = "评论id")
private Integer commentId;
@ApiModelProperty(value = "回复评论者id")
private String fromUid;
@ApiModelProperty(value = "回复评论者用户名")
private String fromName;
@ApiModelProperty(value = "回复评论组头像地址")
private String fromAvatar;
@ApiModelProperty(value = "回复评论者角色")
private String fromRole;
@ApiModelProperty(value = "被回复的用户id")
private String toUid;
@ApiModelProperty(value = "被回复的用户名")
private String toName;
@ApiModelProperty(value = "被回复的用户头像地址")
private String toAvatar;
@ApiModelProperty(value = "回复的内容")
private String content;
@TableField(fill = FieldFill.INSERT)
private Date gmtCreate;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date gmtModified;
}

View File

@ -140,7 +140,7 @@ export default {
box-sizing: border-box;
}
body {
background-color: #eee !important;
background-color: #eff3f5 !important;
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB,
Microsoft YaHei, Arial, sans-serif !important;
-webkit-font-smoothing: antialiased !important;

View File

@ -11,14 +11,6 @@ import utils from '@/common/utils'
// NProgress.configure({ ease: 'ease', speed: 1000,showSpinner: false})
Vue.prototype.$http = axios
// 环境的切换
if (process.env.NODE_ENV == 'development') {
axios.defaults.baseURL = "http://localhost:6688";
} else if (process.env.NODE_ENV == 'debug') {
axios.defaults.baseURL = "http://localhost:6688";
} else if (process.env.NODE_ENV == 'production') {
axios.defaults.baseURL = '你的域名或者ip:端口号';
}
// 请求超时时间
axios.defaults.timeout = 40000;
@ -439,7 +431,85 @@ const ojApi = {
return ajax("/api/change-userInfo",'post',{
data
})
},
// 讨论页相关请求
getCategoryList(){
return ajax("/api/discussion-category",'get')
},
getDiscussionList(limit,searchParams){
let params = {
limit
}
Object.keys(searchParams).forEach((element) => {
if (searchParams[element]!==''&&searchParams[element]!==null&&searchParams[element]!==undefined) {
params[element] = searchParams[element]
}
})
return ajax("/api/discussions",'get',{
params
})
},
getDiscussion(did){
return ajax("/api/discussion",'get',{
params:{
did
}
})
},
addDiscussion(data){
return ajax("/api/discussion",'post',{
data
})
},
toLikeDiscussion(did,toLike){
return ajax("/api/discussion-like",'get',{
params:{
did,
toLike
}
})
},
getCommentList(params){
return ajax("/api/comments",'get',{
params
})
},
addComment(data){
return ajax("/api/comment",'post',{
data
})
},
toLikeComment(cid,toLike){
return ajax("/api/comment-like",'get',{
params:{
cid,
toLike
}
})
},
addReply(data){
return ajax("/api/reply",'post',{
data
})
},
getAllReply(commentId){
return ajax("/api/reply",'get',{
params:{
commentId
}
})
}
}
// 处理admin后台管理的请求

View File

@ -0,0 +1,32 @@
import $ from 'jquery'
import myMessage from '@/common/message';
export const addCodeBtn = _ => {
//markdown代码存放在pre code 标签对中
$('pre code').each(function () {
let lines = $(this).text().split('\n').length - 1
//添加有序列表
let $numbering = $('<ol/>').addClass('pre-numbering')
//添加复制按钮此处使用的是element-ui icon 图标
let $copy = $('<i title="copy"> COPY</i>').addClass('el-icon-document-copy code-copy')
$(this)
.parent()
.append($numbering)
.append($copy)
for (let i = 0; i <= lines; i++) {
$numbering.append($('<li/>'))
}
})
//监听复制按钮点击事件
$('pre i.code-copy').click(e => {
let text = $(e.target).siblings('code').text()
let element = $('<textarea>' + text + '</textarea>')
$('body').append(element)
element[0].select()
document.execCommand('Copy')
element.remove()
//这里是自定义的消息通知组件
myMessage.success('success')
})
}

View File

@ -4,13 +4,14 @@
ref="md"
@imgAdd="$imgAdd"
@imgDel="$imgDel"
:ishljs="true"
v-model="currentValue"
codeStyle="arduino-light"
></mavon-editor>
</div>
</template>
<script>
import myMessage from '@/common/message';
import { addCodeBtn } from '@/common/codeblock';
export default {
name: 'Editor',
props: {
@ -32,7 +33,7 @@ export default {
formdata.append('image', $file);
//
this.$http({
url: '/file/upload-md-img',
url: '/api/file/upload-md-img',
method: 'post',
data: formdata,
headers: { 'Content-Type': 'multipart/form-data' },
@ -44,7 +45,7 @@ export default {
$imgDel(pos) {
//
this.$http({
url: '/file/delete-md-img',
url: '/api/file/delete-md-img',
method: 'get',
params: {
filePath: this.img_file[pos[0]],
@ -61,8 +62,131 @@ export default {
currentValue(newVal, oldVal) {
if (newVal !== oldVal) {
this.$emit('update:value', newVal);
this.$nextTick((_) => {
addCodeBtn();
});
}
},
},
};
</script>
<style>
.markdown-body pre {
display: block;
border-radius: 3px !important;
border: 1px solid #c3ccd0;
padding: 0 16px 0 55px !important;
position: relative !important;
overflow-y: hidden !important;
font-size: 1rem !important;
background: #fff !important;
white-space: pre !important;
}
.markdown-body pre code {
line-height: 26px !important;
}
.markdown-body pre ol.pre-numbering {
position: absolute;
top: 0;
left: 0;
line-height: 26px;
margin: 0;
padding: 0;
list-style-type: none;
counter-reset: sectioncounter;
background: #f1f1f1;
color: #777;
font-size: 12px;
}
.markdown-body pre ol.pre-numbering li {
margin-top: 0 !important;
}
.markdown-body pre ol.pre-numbering li:before {
content: counter(sectioncounter) '';
counter-increment: sectioncounter;
display: inline-block;
width: 40px;
text-align: center;
}
.markdown-body pre i.code-copy {
position: absolute;
top: 0;
right: 0;
background-color: #2196f3;
display: none;
padding: 7px;
margin: 5px 5px 0 0;
font-size: 11px;
border-radius: inherit;
color: #fff;
cursor: pointer;
transition: all 0.3s ease-in-out;
}
.markdown-body pre:hover i.code-copy {
display: block;
}
.markdown-body pre i.code-copy:hover i.code-copy {
display: block;
}
.markdown-body blockquote {
color: #666;
border-left: 4px solid #8bc34a;
padding: 10px;
margin-left: 0;
font-size: 14px;
background: #f8f8f8;
}
.markdown-body h1,
.markdown-body h2,
.markdown-body h3,
.markdown-body h4,
.markdown-body h5,
.markdown-body h6 {
position: relative;
margin-top: 1em;
margin-bottom: 16px;
font-weight: bold;
line-height: 1.4;
}
.markdown-body h1 {
padding-bottom: 0.3em;
font-size: 2.25em;
line-height: 1.2;
border-bottom: 1px solid #eee;
}
.markdown-body h2 {
font-size: 1.45em;
line-height: 1.425;
border-bottom: 1px solid #eee;
background: #cce5ff;
padding: 8px 10px;
color: #545857;
border-radius: 3px;
}
.markdown-body h3 {
font-size: 1.3em;
line-height: 1.43;
}
.markdown-body h3:before {
content: '';
border-left: 4px solid #03a9f4;
padding-left: 6px;
}
.markdown-body h4 {
font-size: 1.12em;
}
.markdown-body h4:before {
content: '';
border-left: 4px solid #bbb;
padding-left: 6px;
}
.markdown-body img {
border: 0;
background: #ffffff;
padding: 15px;
margin: 5px 0;
box-shadow: inset 0 0 12px rgb(219 219 219);
}
</style>

View File

@ -0,0 +1,979 @@
<!--评论模块-->
<template>
<div>
<div class="container">
<div class="own-input">
<el-input
v-model="ownInputComment"
type="textarea"
:rows="8"
id="own-textarea"
placeholder="快来写下你的评论吧~😘"
>
</el-input>
<div class="input-bottom">
<span title="emoji表情">
<el-popover
placement="top-start"
width="300"
trigger="click"
v-model="facelistVisiable"
>
<div class="emotionList">
<a
href="javascript:void(0);"
@click="getEmo(index, true)"
v-for="(item, index) in faceList"
:key="index"
class="emotionItem"
>{{ item }}</a
>
</div>
<i class="fa fa-smile-o emotionSelect" slot="reference"></i>
</el-popover>
</span>
<span
class="markdown-key"
title="行内代码"
@click="addContentTips(0, false)"
><svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="1em"
height="1em"
fill="currentColor"
class="css-1rhb60f-Svg ea8ky5j0"
>
<path
fill-rule="evenodd"
d="M5.5 5h1.866a.5.5 0 01.486.617l-.96 4a.5.5 0 01-.486.383H5.5a.5.5 0 01-.5-.5v-4a.5.5 0 01.5-.5zm8 0h1.866a.5.5 0 01.486.617l-.96 4a.5.5 0 01-.486.383H13.5a.5.5 0 01-.5-.5v-4a.5.5 0 01.5-.5z"
clip-rule="evenodd"
></path>
</svg>
</span>
<span
class="markdown-key"
title="代码块"
@click="addContentTips(1, false)"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="1em"
height="1em"
fill="currentColor"
class="css-1rhb60f-Svg ea8ky5j0"
>
<path
fill-rule="evenodd"
d="M3.64 10.98c.147 0 .312-.022.495-.066.183-.044.348-.12.495-.23.147-.11.271-.269.374-.474.103-.205.154-.462.154-.77V5.084c0-.499.077-.928.231-1.287.154-.36.363-.66.627-.902s.565-.422.902-.539c.337-.117.69-.176 1.056-.176H9.58v1.848H8.37c-.425 0-.693.14-.803.418-.11.279-.165.58-.165.902v4.136c0 .425-.08.788-.242 1.09-.161.3-.352.545-.572.736-.22.19-.455.337-.704.44-.25.103-.462.161-.638.176v.044c.176.015.389.062.638.143.25.08.484.216.704.407.22.19.41.447.572.77.161.323.242.74.242 1.254v4.004c0 .323.055.623.165.902.11.279.378.418.803.418h1.21v1.848H7.974c-.367 0-.719-.059-1.056-.176a2.573 2.573 0 01-.902-.539 2.586 2.586 0 01-.627-.902c-.154-.36-.231-.788-.231-1.287V14.61c0-.352-.051-.638-.154-.858a1.494 1.494 0 00-.374-.517 1.18 1.18 0 00-.495-.253 2.143 2.143 0 00-.495-.066V10.98zm16.72 2.04c-.161 0-.33.022-.506.066a1.184 1.184 0 00-.484.253 1.494 1.494 0 00-.374.517c-.103.22-.154.506-.154.858v4.202c0 .499-.077.928-.231 1.287-.154.36-.363.66-.627.902a2.573 2.573 0 01-.902.54c-.337.116-.69.175-1.056.175H14.42v-1.848h1.21c.425 0 .693-.14.803-.418.11-.279.165-.58.165-.902v-4.004c0-.513.08-.931.242-1.254.161-.323.352-.58.572-.77.22-.19.455-.326.704-.407.25-.08.462-.128.638-.143v-.044a2.225 2.225 0 01-.638-.176 2.567 2.567 0 01-.704-.44c-.22-.19-.41-.436-.572-.737-.161-.3-.242-.664-.242-1.089V5.452c0-.323-.055-.623-.165-.902-.11-.279-.378-.418-.803-.418h-1.21V2.284h1.606c.367 0 .719.059 1.056.176.337.117.638.297.902.539.264.242.473.543.627.902.154.36.231.788.231 1.287v4.356c0 .308.051.565.154.77.103.205.227.363.374.473.147.11.308.187.484.231.176.044.345.066.506.066v1.936z"
clip-rule="evenodd"
></path>
</svg>
</span>
<span
class="markdown-key"
title="链接"
@click="addContentTips(2, false)"
><svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="1em"
height="1em"
fill="currentColor"
class="css-1rhb60f-Svg ea8ky5j0"
>
<path
fill-rule="evenodd"
d="M13 7a1 1 0 011-1h2a6 6 0 010 12h-2a1 1 0 110-2h2a4 4 0 000-8h-2a1 1 0 01-1-1zm-3 10a1 1 0 01-1 1H8A6 6 0 018 6h1a1 1 0 010 2H8a4 4 0 100 8h1a1 1 0 011 1zm-1-6h6a1 1 0 110 2H9a1 1 0 110-2z"
clip-rule="evenodd"
></path></svg
></span>
<span
class="markdown-key"
title="无序列表"
@click="addContentTips(3, false)"
><svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="1em"
height="1em"
fill="currentColor"
class="css-1rhb60f-Svg ea8ky5j0"
>
<path
fill-rule="evenodd"
d="M8 7a1 1 0 110-2h13a1 1 0 110 2H8zm0 6a1 1 0 110-2h13a1 1 0 110 2H8zm0 6a1 1 0 110-2h13a1 1 0 110 2H8zM2.952 6.85a1.201 1.201 0 111.698-1.7 1.201 1.201 0 01-1.698 1.7zm0 6a1.201 1.201 0 111.698-1.7 1.201 1.201 0 01-1.698 1.7zm0 6a1.201 1.201 0 111.698-1.7 1.201 1.201 0 01-1.698 1.7z"
clip-rule="evenodd"
></path></svg
></span>
<span
class="markdown-key"
title="有序列表"
@click="addContentTips(4, false)"
><svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="1em"
height="1em"
fill="currentColor"
class="css-1rhb60f-Svg ea8ky5j0"
>
<path
fill-rule="evenodd"
d="M7 6a1 1 0 011-1h13a1 1 0 110 2H8a1 1 0 01-1-1zm1 13a1 1 0 110-2h13a1 1 0 010 2H8zm0-6a1 1 0 110-2h13a1 1 0 010 2H8zM2.756 4.98l.236-1.18h2.22l-.92 4.6h-1.38l.684-3.42h-.84zm2.823 8.3l-.235 1.192H1.756l.174-.87.073-.117 1.911-1.498c.219-.178.368-.324.445-.434a.49.49 0 00.099-.287.208.208 0 00-.083-.18c-.067-.052-.176-.082-.335-.082a.897.897 0 00-.398.094 1.02 1.02 0 00-.342.272l-.12.144-.968-.702.118-.162c.193-.264.456-.473.786-.625.327-.15.684-.225 1.068-.225.318 0 .602.053.85.16.255.11.456.267.6.47.146.206.22.44.22.698 0 .298-.083.58-.247.839-.158.25-.424.518-.797.807l-.653.506h1.422zm-3.063 3.7l.245-1.18h3.345l-.166.867-.06.11-.93.869a1.205 1.205 0 01.737 1.144c-.001.327-.098.622-.29.881a1.856 1.856 0 01-.774.593c-.322.139-.685.208-1.087.208-.328 0-.636-.045-.924-.135a2.06 2.06 0 01-.743-.4l-.132-.114.688-1.05.173.147c.12.103.267.185.442.245.177.06.364.091.562.091.251 0 .435-.044.554-.124a.31.31 0 00.146-.282c0-.086-.025-.135-.08-.171-.076-.05-.213-.079-.41-.079h-.687l.173-.88.06-.108.674-.632H2.516z"
clip-rule="evenodd"
></path></svg
></span>
<span class="own-btn-comment">
<el-button class="btn" type="primary" round @click="commitComment"
><i class="el-icon-edit"> 提交评论</i></el-button
>
</span>
</div>
</div>
<h3 class="comment-total">
<div class="text">
<span>全部评论</span><span class="number">{{ totalComment }}</span>
</div>
</h3>
<div
class="comment"
id="commentbox"
v-for="(item, index) in comments"
:key="index"
>
<div
class="info"
@click="getInfoByUsername(item.fromUid, reply.fromName)"
:title="item.fromName"
>
<avatar
:username="item.fromName"
:inline="true"
:size="38"
color="#FFF"
:src="item.fromAvatar"
></avatar>
<div class="right">
<div class="name">{{ item.fromName }}</div>
<div class="date">{{ item.gmtCreate | localtime }}</div>
</div>
</div>
<div class="info-bottom">
<div
class="content markdown-content markdown-body"
v-html="mdToHtml(item.content)"
v-katex
v-highlight
></div>
<div class="control">
<span
class="like"
:class="{ active: commentLikeMap[item.id] == true }"
@click="likeClick(item)"
>
<i class="iconfont fa fa-thumbs-o-up"></i>
<span class="like-num">{{
item.likeNum > 0 ? item.likeNum + '人赞' : '赞'
}}</span>
</span>
<span class="comment-reply" @click="showCommentInput(item)">
<i class="iconfont el-icon-chat-square"></i>
<span>回复</span>
</span>
</div>
<div class="reply">
<div
class="item"
v-for="(reply, index) in item.replyList"
:key="index"
>
<div class="reply-content">
<span
class="from-name"
:title="reply.fromName"
@click="getInfoByUsername(reply.fromUid, reply.fromName)"
>
<avatar
:username="reply.fromName"
:inline="true"
:size="25"
class="user-avatar"
color="#FFF"
:src="reply.fromAvatar"
></avatar>
{{ reply.fromName }} </span
><span class="reply-text">回复</span>
<span
class="to-name"
:title="reply.toName"
@click="getInfoByUsername(reply.toUid, reply.toName)"
>@{{ reply.toName }}</span
>
</div>
<div style="padding: 8px 0;margin-left: 34px;">
<span
v-html="mdToHtml(reply.content)"
class="markdown-content markdown-body"
v-katex
v-highlight
></span>
</div>
<div class="reply-bottom">
<span>{{ reply.gmtCreate | localtime }}</span>
<span class="reply-text" @click="showCommentInput(item, reply)">
<i class="iconfont el-icon-chat-square"></i>
<span>回复</span>
</span>
</div>
</div>
<div class="view-more item" v-if="item.totalReplyNum > 3">
<b>{{ item.totalReplyNum }}</b
>条回复,
<a
class="btn-more"
@click="showAllReply(item)"
v-if="!item.hadOpen"
>点击查看全部</a
>
<a class="btn-more" @click="pickWayReply(item)" v-else>收起</a>
</div>
<transition name="fade">
<div class="input-wrapper" v-if="showItemId === item.id">
<el-input
v-model="replyInputComment"
type="textarea"
:rows="5"
autofocus
id="reply-textarea"
:placeholder="replyPlaceholder"
>
</el-input>
<div class="input-bottom">
<span title="emoji表情">
<el-popover
placement="top-start"
width="300"
trigger="click"
v-model="replyFacelistVisiable"
>
<div class="emotionList">
<a
href="javascript:void(0);"
@click="getEmo(index, false)"
v-for="(item, index) in faceList"
:key="index"
class="emotionItem"
>{{ item }}</a
>
</div>
<i
class="fa fa-smile-o emotionSelect"
slot="reference"
></i>
</el-popover>
</span>
<span
class="markdown-key"
title="行内代码"
@click="addContentTips(0, true)"
><svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="1em"
height="1em"
fill="currentColor"
class="css-1rhb60f-Svg ea8ky5j0"
>
<path
fill-rule="evenodd"
d="M5.5 5h1.866a.5.5 0 01.486.617l-.96 4a.5.5 0 01-.486.383H5.5a.5.5 0 01-.5-.5v-4a.5.5 0 01.5-.5zm8 0h1.866a.5.5 0 01.486.617l-.96 4a.5.5 0 01-.486.383H13.5a.5.5 0 01-.5-.5v-4a.5.5 0 01.5-.5z"
clip-rule="evenodd"
></path>
</svg>
</span>
<span
class="markdown-key"
title="代码块"
@click="addContentTips(1, true)"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="1em"
height="1em"
fill="currentColor"
class="css-1rhb60f-Svg ea8ky5j0"
>
<path
fill-rule="evenodd"
d="M3.64 10.98c.147 0 .312-.022.495-.066.183-.044.348-.12.495-.23.147-.11.271-.269.374-.474.103-.205.154-.462.154-.77V5.084c0-.499.077-.928.231-1.287.154-.36.363-.66.627-.902s.565-.422.902-.539c.337-.117.69-.176 1.056-.176H9.58v1.848H8.37c-.425 0-.693.14-.803.418-.11.279-.165.58-.165.902v4.136c0 .425-.08.788-.242 1.09-.161.3-.352.545-.572.736-.22.19-.455.337-.704.44-.25.103-.462.161-.638.176v.044c.176.015.389.062.638.143.25.08.484.216.704.407.22.19.41.447.572.77.161.323.242.74.242 1.254v4.004c0 .323.055.623.165.902.11.279.378.418.803.418h1.21v1.848H7.974c-.367 0-.719-.059-1.056-.176a2.573 2.573 0 01-.902-.539 2.586 2.586 0 01-.627-.902c-.154-.36-.231-.788-.231-1.287V14.61c0-.352-.051-.638-.154-.858a1.494 1.494 0 00-.374-.517 1.18 1.18 0 00-.495-.253 2.143 2.143 0 00-.495-.066V10.98zm16.72 2.04c-.161 0-.33.022-.506.066a1.184 1.184 0 00-.484.253 1.494 1.494 0 00-.374.517c-.103.22-.154.506-.154.858v4.202c0 .499-.077.928-.231 1.287-.154.36-.363.66-.627.902a2.573 2.573 0 01-.902.54c-.337.116-.69.175-1.056.175H14.42v-1.848h1.21c.425 0 .693-.14.803-.418.11-.279.165-.58.165-.902v-4.004c0-.513.08-.931.242-1.254.161-.323.352-.58.572-.77.22-.19.455-.326.704-.407.25-.08.462-.128.638-.143v-.044a2.225 2.225 0 01-.638-.176 2.567 2.567 0 01-.704-.44c-.22-.19-.41-.436-.572-.737-.161-.3-.242-.664-.242-1.089V5.452c0-.323-.055-.623-.165-.902-.11-.279-.378-.418-.803-.418h-1.21V2.284h1.606c.367 0 .719.059 1.056.176.337.117.638.297.902.539.264.242.473.543.627.902.154.36.231.788.231 1.287v4.356c0 .308.051.565.154.77.103.205.227.363.374.473.147.11.308.187.484.231.176.044.345.066.506.066v1.936z"
clip-rule="evenodd"
></path>
</svg>
</span>
<span
class="markdown-key"
title="链接"
@click="addContentTips(2, true)"
><svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="1em"
height="1em"
fill="currentColor"
class="css-1rhb60f-Svg ea8ky5j0"
>
<path
fill-rule="evenodd"
d="M13 7a1 1 0 011-1h2a6 6 0 010 12h-2a1 1 0 110-2h2a4 4 0 000-8h-2a1 1 0 01-1-1zm-3 10a1 1 0 01-1 1H8A6 6 0 018 6h1a1 1 0 010 2H8a4 4 0 100 8h1a1 1 0 011 1zm-1-6h6a1 1 0 110 2H9a1 1 0 110-2z"
clip-rule="evenodd"
></path></svg
></span>
<span
class="markdown-key"
title="无序列表"
@click="addContentTips(3, true)"
><svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="1em"
height="1em"
fill="currentColor"
class="css-1rhb60f-Svg ea8ky5j0"
>
<path
fill-rule="evenodd"
d="M8 7a1 1 0 110-2h13a1 1 0 110 2H8zm0 6a1 1 0 110-2h13a1 1 0 110 2H8zm0 6a1 1 0 110-2h13a1 1 0 110 2H8zM2.952 6.85a1.201 1.201 0 111.698-1.7 1.201 1.201 0 01-1.698 1.7zm0 6a1.201 1.201 0 111.698-1.7 1.201 1.201 0 01-1.698 1.7zm0 6a1.201 1.201 0 111.698-1.7 1.201 1.201 0 01-1.698 1.7z"
clip-rule="evenodd"
></path></svg
></span>
<span
class="markdown-key"
title="有序列表"
@click="addContentTips(4, true)"
><svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="1em"
height="1em"
fill="currentColor"
class="css-1rhb60f-Svg ea8ky5j0"
>
<path
fill-rule="evenodd"
d="M7 6a1 1 0 011-1h13a1 1 0 110 2H8a1 1 0 01-1-1zm1 13a1 1 0 110-2h13a1 1 0 010 2H8zm0-6a1 1 0 110-2h13a1 1 0 010 2H8zM2.756 4.98l.236-1.18h2.22l-.92 4.6h-1.38l.684-3.42h-.84zm2.823 8.3l-.235 1.192H1.756l.174-.87.073-.117 1.911-1.498c.219-.178.368-.324.445-.434a.49.49 0 00.099-.287.208.208 0 00-.083-.18c-.067-.052-.176-.082-.335-.082a.897.897 0 00-.398.094 1.02 1.02 0 00-.342.272l-.12.144-.968-.702.118-.162c.193-.264.456-.473.786-.625.327-.15.684-.225 1.068-.225.318 0 .602.053.85.16.255.11.456.267.6.47.146.206.22.44.22.698 0 .298-.083.58-.247.839-.158.25-.424.518-.797.807l-.653.506h1.422zm-3.063 3.7l.245-1.18h3.345l-.166.867-.06.11-.93.869a1.205 1.205 0 01.737 1.144c-.001.327-.098.622-.29.881a1.856 1.856 0 01-.774.593c-.322.139-.685.208-1.087.208-.328 0-.636-.045-.924-.135a2.06 2.06 0 01-.743-.4l-.132-.114.688-1.05.173.147c.12.103.267.185.442.245.177.06.364.091.562.091.251 0 .435-.044.554-.124a.31.31 0 00.146-.282c0-.086-.025-.135-.08-.171-.076-.05-.213-.079-.41-.079h-.687l.173-.88.06-.108.674-.632H2.516z"
clip-rule="evenodd"
></path></svg
></span>
<span class="btn-control">
<el-button
class="btn"
type="danger"
round
@click="cancel"
size="small"
>取消</el-button
>
<el-button
class="btn"
type="primary"
round
@click="commitReply(item.id)"
size="small"
>发送</el-button
>
</span>
</div>
</div>
</transition>
</div>
</div>
</div>
</div>
<div class="container loading-text" v-if="showloading">
<a style="background: #fff;padding:10px;" @click="loadMoreComment"
><span>加载更多...</span></a
>
</div>
</div>
</template>
<script>
import Avatar from 'vue-avatar';
import { mapGetters } from 'vuex';
import myMessage from '@/common/message';
import api from '@/common/api';
export default {
props: {
did: {
type: Number,
required: false,
default: null,
},
cid: {
type: Number,
require: false,
default: null,
},
},
components: { Avatar },
data() {
return {
replyInputComment: '',
ownInputComment: '',
showItemId: '',
faceList: [],
comments: [],
commentLikeMap: {},
facelistVisiable: false,
replyFacelistVisiable: false,
replyPlaceholder: '写下你的评论吧~',
query: {
limit: 10,
currentPage: 1,
cid: null,
did: null,
},
total: 0,
totalComment: 0,
replyObj: {
commentId: 0,
content: '',
toUid: '',
toName: '',
toAvatar: '',
},
};
},
created() {
const appData = require('./emoji.json');
for (let i in appData) {
this.faceList.push(appData[i]);
}
},
mounted() {
this.query.did = this.did;
this.query.cid = this.cid;
this.init();
},
computed: {
...mapGetters(['userInfo', 'isAuthenticated', 'isAdminRole']),
avatar() {
return this.$store.getters.userInfo.avatar;
},
},
methods: {
init() {
let queryParams = Object.assign({}, this.query);
api.getCommentList(queryParams).then((res) => {
let moreCommentList = res.data.data.commentList.records;
for (let i = 0; i < moreCommentList.length; i++) {
this.totalComment += 1 + moreCommentList[i].totalReplyNum;
}
this.comments = this.comments.concat(moreCommentList);
this.total = res.data.data.commentList.total;
this.commentLikeMap = res.data.data.commentLikeMap;
});
},
/**
* 点赞
*/
likeClick(item) {
//
let toLike = this.commentLikeMap[item.id] != true;
api.toLikeComment(item.id, toLike).then((res) => {
if (toLike) {
this.commentLikeMap[item.id] = true;
item.likeNum++;
} else {
item.likeNum--;
this.commentLikeMap[item.id] = false;
}
});
},
/**
* 点击取消按钮
*/
cancel() {
this.showItemId = '';
},
/**
* 提交评论
*/
commitComment() {
if (!this.isAuthenticated) {
myMessage.warning('请先登录');
this.$store.dispatch('changeModalStatus', { visible: true });
return;
}
let comment = {
content: this.ownInputComment,
cid: this.cid,
did: this.did,
};
api.addComment(comment).then((res) => {
this.comments = [res.data.data].concat(this.comments);
this.totalComment++;
myMessage.success(res.data.msg);
this.ownInputComment = '';
});
},
/**
* 提交回复
*/
commitReply() {
if (!this.isAuthenticated) {
myMessage.warning('请先登录');
this.$store.dispatch('changeModalStatus', { visible: true });
return;
}
this.replyObj.content = this.replyInputComment;
let replyData = Object.assign({}, this.replyObj);
api.addReply(replyData).then((res) => {
for (let i = 0; i < this.comments.length; i++) {
if (this.comments[i].id == this.replyObj.commentId) {
this.comments[i].replyList = [res.data.data].concat(
this.comments[i].replyList
);
if (this.comments[i].backupReplyList) {
this.comments[i].backupReplyList = [res.data.data].concat(
this.comments[i].backupReplyList
);
}
this.comments[i].totalReplyNum++;
if (this.comments[i].totalReplyNum > 3) {
this.comments[i].hadOpen = true;
}
break;
}
}
this.totalComment++;
myMessage.success(res.data.msg);
this.replyInputComment = '';
});
},
/**
* 点击评论按钮显示输入框
* item: 当前大评论
* reply: 当前回复的评论
*/
showCommentInput(item, reply) {
this.replyInputComment = '';
if (reply) {
this.replyPlaceholder = '@' + reply.fromName;
//
this.replyObj.commentId = item.id;
this.replyObj.toUid = reply.fromUid;
this.replyObj.toName = reply.fromName;
this.replyObj.toAvatar = reply.fromAvatar;
} else {
this.replyPlaceholder = '快来写下你的评论吧~';
this.replyObj.commentId = item.id;
this.replyObj.toUid = item.fromUid;
this.replyObj.toName = item.fromName;
this.replyObj.toAvatar = item.fromAvatar;
}
this.showItemId = item.id;
},
/**
* 获取emoji表情写入到文本域中
*
*/
getEmo(index, isOwn) {
var textArea;
if (isOwn) {
textArea = document.getElementById('own-textarea');
} else {
textArea = document.getElementById('reply-textarea');
}
function changeSelectedText(obj, str) {
if (window.getSelection) {
// IE
textArea.setRangeText(str);
//
textArea.selectionStart += str.length;
textArea.focus();
} else if (document.selection) {
// IE
obj.focus();
var sel = document.selection.createRange();
sel.text = str;
}
}
changeSelectedText(textArea, this.faceList[index]);
if (isOwn) {
this.ownInputComment = textArea.value; // data
this.facelistVisiable = false; //
} else {
this.replyInputComment = textArea.value; // data
this.replyFacelistVisiable = false; //
}
return;
},
addContentTips(num, isReply) {
let tips = '';
switch (num) {
case 0:
tips = '`your inline code...`';
break;
case 1:
tips = '\n```\ncode block\n```\n';
break;
case 2:
tips = '[HOJ](https://hcode.top)';
break;
case 3:
tips = '\n- 无序列表';
break;
case 4:
tips = '\n1. 有序列表';
break;
}
if (isReply) {
this.replyInputComment += tips;
} else {
this.ownInputComment += tips;
}
},
mdToHtml(content) {
return this.$markDown.render(content);
},
getInfoByUsername(uid, username) {
this.$router.push({
path: '/user-home',
query: { uid, username },
});
},
showAllReply(item) {
if (!item.backupReplyList) {
api.getAllReply(item.id).then((res) => {
item.replyList = res.data.data;
item.backupReplyList = res.data.data;
item.hadOpen = true;
this.totalComment += res.data.data.length - item.totalReplyNum;
item.totalReplyNum = res.data.data.length;
});
} else {
item.replyList = item.backupReplyList;
item.hadOpen = true;
}
},
pickWayReply(item) {
let newArr = [];
for (let i = 0; i < 3; i++) {
newArr.push(item.replyList[i]);
}
item.replyList = newArr;
item.hadOpen = false;
return;
},
loadMoreComment() {
this.query.currentPage += 1;
this.init();
},
},
computed: {
...mapGetters(['isAuthenticated']),
showloading() {
if (this.query.currentPage * this.query.limit >= this.total) {
return false;
} else {
return true;
}
},
},
watch: {
isAuthenticated(newVal, oldVal) {
if (newVal != oldVal) {
this.init();
}
},
},
};
</script>
<style>
.el-popover {
height: 200px !important;
width: 300px !important;
overflow: scroll !important;
overflow-x: hidden !important;
}
.comment .markdown-content p {
margin-top: 0 !important;
margin-bottom: 0 !important;
}
</style>
<style scoped>
.container {
padding: 10px 20px;
box-sizing: border-box;
background-color: #fff;
box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
border: 1px solid #ebeef5;
margin-bottom: 10px;
}
.container .own-input {
margin-top: 10px;
}
.container .input-bottom {
margin-top: 10px;
padding: 0 10px;
}
.container .input-bottom .markdown-key {
font-size: 20px;
margin-left: 5px;
cursor: pointer;
}
.container .own-input .own-btn-comment {
float: right;
}
.container .emotionSelect {
font-size: 25px;
cursor: pointer;
}
.emotionList {
display: flex;
flex-wrap: wrap;
padding: 5px;
}
.emotionItem {
width: 10%;
font-size: 20px;
text-align: center;
}
/*包含以下四种的链接*/
.emotionItem {
text-decoration: none;
}
/*正常的未被访问过的链接*/
.emotionItem:link {
text-decoration: none;
}
/*已经访问过的链接*/
.emotionItem:visited {
text-decoration: none;
}
/*鼠标划过(停留)的链接*/
.emotionItem:hover {
text-decoration: none;
}
/* 正在点击的链接*/
.emotionItem:active {
text-decoration: none;
}
.comment-total {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding-left: 12px;
border-left: 4px solid #03a9f4;
font-size: 18px;
font-weight: 500;
height: 20px;
line-height: 20px;
}
.comment-total .text {
display: flex;
align-items: center;
}
.comment-total .number {
margin-left: 6px;
font-size: 14px;
font-weight: normal;
}
.user-avatar {
margin-right: 5px !important;
vertical-align: middle;
}
.container .comment {
display: flex;
flex-direction: column;
padding: 10px;
border-top: 1px solid #eee;
}
.container .comment .info {
display: flex;
align-items: center;
cursor: pointer;
}
.container .comment .info .right {
display: flex;
flex-direction: column;
margin-left: 10px;
}
.container .comment .info .right .name {
font-size: 16px;
color: #409eff;
margin-bottom: 5px;
font-weight: 500;
}
.container .comment .info .right .date {
font-size: 12px;
color: #909399;
}
.container .comment .info-bottom {
margin-left: 47px;
}
.container .comment .content {
font-size: 16px;
color: #303133;
line-height: 20px;
padding: 10px 0;
}
.container .comment .control {
display: flex;
align-items: center;
font-size: 14px;
color: #909399;
}
.container .comment .control .like {
display: flex;
align-items: center;
margin-right: 20px;
cursor: pointer;
}
.container .comment .control .like.active,
.container .comment .control .like:hover {
color: #409eff;
}
.container .comment .control .like .iconfont {
font-size: 14px;
margin-right: 5px;
}
.container .comment .control .comment-reply {
display: flex;
align-items: center;
cursor: pointer;
}
.container .comment .control .comment-reply:hover {
color: #333;
}
.container .comment .control .comment-reply .iconfont {
font-size: 16px;
margin-right: 5px;
}
.container .comment .reply {
margin: 10px 0;
border-left: 2px solid #dcdfe6;
}
.container .comment .reply .item {
margin: 0 10px;
padding: 10px 0;
border-bottom: 1px dashed #ebeef5;
}
.container .comment .reply .item .reply-content {
display: flex;
align-items: center;
font-size: 14px;
color: #303133;
}
.container .comment .reply .item .reply-content .from-name {
color: #409eff;
cursor: pointer;
}
.container .comment .reply .item .reply-content .reply-text {
margin-left: 5px;
margin-right: 5px;
color: #333;
font-size: 14px;
font-weight: normal;
}
.container .comment .reply .item .reply-content .to-name {
color: #409eff;
margin-right: 5px;
}
.container .comment .reply .item .reply-bottom {
display: flex;
align-items: center;
font-size: 12px;
color: #909399;
margin-left: 34px;
}
.container .comment .reply .item .reply-bottom .reply-text {
display: flex;
align-items: center;
margin-left: 10px;
cursor: pointer;
}
.container .comment .reply .item .reply-bottom .reply-text:hover {
color: #333;
}
.container .comment .reply .item .reply-bottom .reply-text .icon-comment {
margin-right: 5px;
}
.container .comment .reply .view-more {
font-size: 12px;
color: #6d757a;
}
.container .comment .reply .view-more .btn-more {
padding: 2px 3px;
border-radius: 4px;
}
.container .comment .reply .view-more a {
outline: none;
color: #00a1d6;
text-decoration: none;
cursor: pointer;
}
.container .comment .reply .view-more a:hover {
background: #e5e9ef;
color: #00a1d6;
}
.container .comment .reply .fade-enter-active,
.container .comment .reply fade-leave-active {
transition: opacity 0.5s;
}
.container .comment .reply .fade-enter,
.container .comment .reply .fade-leave-to {
opacity: 0;
}
.container .comment .reply .input-wrapper {
padding: 10px;
}
.container .btn-control {
float: right;
align-items: center;
}
.container .comment .reply .input-wrapper .btn-control .cancel {
font-size: 16px;
color: #606266;
margin-right: 20px;
cursor: pointer;
}
.container .comment .reply .input-wrapper .btn-control .cancel:hover {
color: #333;
}
.container .comment .reply .input-wrapper .btn-control .confirm {
font-size: 16px;
}
.loading-text {
text-align: center;
}
.loading-text a {
color: #999;
}
.loading-text a:hover {
text-decoration: none;
color: #03a9f4;
}
</style>

View File

@ -0,0 +1 @@
["😀","😄","😁","😆","😅","🤣","😂","🙂","🙃","😉","😊","😇","🥰","😍","🤩","😘","😗","😚","😙","😋","😛","😜","🤪","😝","🤑","🤗","🤭","🤫","🤔","🤐","🤨","😐","😶","😶‍🌫️","😏","😒","🙄","😬","😮‍💨","🤥","😌","😔","😪","🤤","😴","😷","🤒","🤕","🤢","🤮","🤧","🥵","🥶","🥴","😵","😵‍💫","🤯","🤠","🥳","😎","🤓","🧐","😕","😟","🙁","☹️","😮","😯","😲","😳","🥺","😦","😧","😨","😰","😥","😢","😭","😱","😖","😣","😞","😓","😩","😫","🥱","😤","😡","😠","🤬","😈","👿","💀","☠️","💩","🤡","👹","👺","👻","👽","👾","🤖","😺","😸","😹","😻","😼","😽","🙀","😿","😾","🙈","🙉","🙊","💋","💌","💘","💝","💖","💗","💓","💞","💕","💟","❣️","❣","💔","❤️‍🔥","❤‍🩹","❤️","❤","🧡","💛","💚","💙","💜","🤎","🖤","🤍","💯","💢","💥","💫","💦","💨","🕳️","🕳","💣","💬","👁️‍🗨️","🗨️","🗨","🗯️","🗯","💭","💤","🙋","🙇","🙌","🙏","🚶","🏃","👯","💃","👫","👬","👭","💏","💑","👪","💪","👈","👉","☝","👆","👇","✌","✋","👌","👍","👎","✊","👊","👋","👏","👐","✍","🐁","🐂","🐅","🐇","🐉","🐍","🐎","🐐","🐒","🐓","🐕","🐖","💐","🌸","💮","🌹","🌺","🌻","🌼","🌷","🌱","🌿","🍀"]

View File

@ -79,6 +79,7 @@
<script>
import api from '@/common/api';
import { addCodeBtn } from '@/common/codeblock';
import Pagination from '@/components/oj/common/Pagination';
export default {
name: 'Announcement',
@ -142,6 +143,9 @@ export default {
this.announcement = announcement;
this.announcement.content = this.$markDown.render(announcement.content);
this.listVisible = false;
this.$nextTick((_) => {
addCodeBtn();
});
},
goBack() {
this.listVisible = true;

View File

@ -205,7 +205,6 @@ export default {
});
}
});
this.editor.focus();
},
methods: {
onEditorCodeChange(newCode) {

View File

@ -1,8 +1,10 @@
<template>
<pre
v-highlight="code"
:style="styleObject"
><code :class="language"></code></pre>
<div class="markdown-body">
<pre
v-highlight="code"
:style="styleObject"
><code :class="language"></code></pre>
</div>
</template>
<script>
@ -38,13 +40,8 @@ export default {
};
</script>
<style scoped>
pre {
padding: 0;
display: block;
}
code {
padding: 20px;
font-size: 1.1em;
<style>
.hljs {
padding: 0 !important;
}
</style>

View File

@ -33,6 +33,10 @@
<el-menu-item index="/oi-rank">OI Rank</el-menu-item>
</el-submenu>
<el-menu-item index="/discussion"
><i class="el-icon-s-comment"></i>Discussion</el-menu-item
>
<el-submenu index="about">
<template slot="title"><i class="el-icon-info"></i>About</template>
<el-menu-item index="/introduction">Introduction</el-menu-item>
@ -250,6 +254,18 @@
</mu-list-item>
</mu-list-item>
<mu-list-item
button
to="/discussion"
@click="opendrawer = !opendrawer"
active-class="mobile-menu-active"
>
<mu-list-item-action>
<mu-icon value=":fa fa-comments" size="24"></mu-icon>
</mu-list-item-action>
<mu-list-item-title>Discussion</mu-list-item-title>
</mu-list-item>
<mu-list-item
button
:ripple="false"
@ -380,6 +396,8 @@ export default {
activeMenuName() {
if (this.$route.path.split('/')[1] == 'submission-detail') {
return '/status';
} else if (this.$route.path.split('/')[1] == 'discussion-detail') {
return '/discussion';
}
return '/' + this.$route.path.split('/')[1];
},

View File

@ -46,7 +46,7 @@ import SlideVerify from 'vue-monoplasty-slide-verify'
// markdown编辑器
import mavonEditor from 'mavon-editor' //引入markdown编辑器
import 'mavon-editor/dist/css/index.css'
import 'mavon-editor/dist/css/index.css';
Vue.use(mavonEditor)
import {Drawer,List,Menu,Icon,AppBar,Button,Divider} from 'muse-ui';

View File

@ -15,7 +15,10 @@ import ContestProblemList from "@/views/oj/contest/children/ContestProblemList.v
import ContestRank from "@/views/oj/contest/children/ContestRank.vue"
import ACMInfoAdmin from "@/views/oj/contest/children/ACMInfoAdmin.vue"
import Announcements from "@/components/oj/common/Announcements.vue"
import ContestComment from "@/views/oj/contest/children/ContestComment.vue"
import ContestRejudgeAdmin from "@/views/oj/contest/children/ContestRejudgeAdmin.vue"
import DiscussionList from "@/views/oj/discussion/discussionList.vue"
import Discussion from "@/views/oj/discussion/discussion.vue"
import Introduction from "@/views/oj/about/Introduction.vue"
import Developer from "@/views/oj/about/Developer.vue"
import NotFound from "@/views/404.vue"
@ -151,20 +154,42 @@ const ojRoutes = [
path:'rejudge',
component:ContestRejudgeAdmin,
meta: { title: 'Contest Rejudge',requireSuperAdmin:true }
},
{
name: 'ContestComment',
path:'comment',
component: ContestComment,
meta: { title: 'Contest Comment'}
}
]
},
{
path: '/discussion',
name: 'AllDiscussion',
meta: {title: 'Discussion'},
component:DiscussionList
},
{
path: '/discussion/:problemID',
name: 'ProblemDiscussion',
meta: {title: 'Discussion'},
component:DiscussionList
},
{
path: '/discussion-detail/:discussionID',
name:'DiscussionDetail',
meta: {title: 'Discussion Detail'},
component: Discussion
},
{
path: '/introduction',
meta: {title: 'Introduction'},
component:Introduction,
meta: { title: 'Introduction' }
},
{
path: '/developer',
meta: {title: 'Developer'},
component:Developer,
meta: { title: 'Developer' }
},
{
path: '*',

View File

@ -178,7 +178,6 @@ export default {
title: '',
content: '',
status: 0,
content: '',
uid: '',
},
//

View File

@ -199,6 +199,7 @@ import api from '@/common/api';
import Announcements from '@/components/oj/common/Announcements.vue';
import { CONTEST_STATUS_REVERSE } from '@/common/constants';
import { mapState } from 'vuex';
import { addCodeBtn } from '@/common/codeblock';
import Avatar from 'vue-avatar';
export default {
name: 'home',
@ -246,6 +247,11 @@ export default {
this.contests[i].description
);
}
if (this.contests.length > 0) {
this.$nextTick((_) => {
addCodeBtn();
});
}
});
},
getRecentOtherContests() {

View File

@ -158,15 +158,15 @@
</transition>
</el-tab-pane>
<!-- <el-tab-pane name="ContestComment" lazy :disabled="contestMenuDisabled">
<el-tab-pane name="ContestComment" lazy :disabled="contestMenuDisabled">
<span slot="label"
><i class="fa fa-commenting" aria-hidden="true"></i
>&nbsp;Comments</span
>&nbsp;Comment</span
>
<transition name="el-zoom-in-bottom">
<router-view v-if="route_name === 'ContestComment'"></router-view>
</transition>
</el-tab-pane> -->
</el-tab-pane>
<el-tab-pane
name="ContestACInfo"
@ -208,6 +208,7 @@ import time from '@/common/time';
import moment from 'moment';
import api from '@/common/api';
import { mapState, mapGetters, mapActions } from 'vuex';
import { addCodeBtn } from '@/common/codeblock';
import {
CONTEST_STATUS_REVERSE,
CONTEST_STATUS,
@ -253,6 +254,9 @@ export default {
this.$store.commit('nowAdd1s');
}, 1000);
}
this.$nextTick((_) => {
addCodeBtn();
});
});
},
methods: {

View File

@ -0,0 +1,12 @@
<template>
<comment :cid="$route.params.contestID"></comment>
</template>
<script>
import comment from '@/components/oj/comment/comment';
export default {
name: 'ContestComment',
components: {
comment,
},
};
</script>

View File

@ -0,0 +1,264 @@
<template>
<div>
<div class="container">
<div class="title-article" style="text-align: left">
<h1 class="title" id="sharetitle">
<span>{{ discussion.title }}</span>
</h1>
<div class="title-msg">
<span>
<a
class="c999"
@click="getInfoByUsername(discussion.uid, discussion.author)"
:title="discussion.author"
>
<avatar
:username="discussion.author"
:inline="true"
:size="26"
color="#FFF"
class="user-avatar"
:src="discussion.avatar"
></avatar>
<span class="user-name">{{ discussion.author }}</span>
</a>
</span>
<span
class="role-root role"
title="Super Administrator"
v-if="discussion.role == 'root'"
>SPA</span
>
<span
class="role-admin role"
title="Administrator"
v-if="discussion.role == 'admin'"
>ADM</span
>
<span class="c999" style="padding:0 6px;"
><i class="el-icon-folder-opened"></i
><a
class="c999"
@click="toAllDiscussionByCid(discussion.categoryId)"
>
分类{{ discussion.categoryName }}</a
></span
>
<span class="c999"
><i class="fa fa-thumbs-o-up"></i
><span> 点赞{{ discussion.likeNum }}</span></span
>
<span class="c999"
><i class="fa fa-eye"></i
><span> 浏览{{ discussion.viewNum }}</span></span
>
<a href="javascript:void(0);" class="report" title="举报"
><i class="fa fa-envira"></i><span>举报</span></a
>
<a
@click="toLikeDiscussion(discussion.id, true)"
class="like"
title="点赞"
v-if="!discussion.hasLike"
>
<i class="fa fa-thumbs-o-up"></i> <span>点赞</span></a
>
<a
@click="toLikeDiscussion(discussion.id, false)"
class="like"
title="已点赞"
v-else
>
<i class="fa fa-thumbs-up"></i> <span>已点赞</span></a
>
<span>
<i class="fa fa-clock-o"> 最后修改于</i>
<span>{{ discussion.gmtModified | localtime }}</span>
</span>
</div>
</div>
<div class="body-article">
<div
class="markdown-body"
v-html="contentHtml"
v-katex
v-highlight
></div>
</div>
</div>
<comment :did="$route.params.discussionID"></comment>
</div>
</template>
<script>
import comment from '@/components/oj/comment/comment';
import api from '@/common/api';
import myMessage from '@/common/message';
import { addCodeBtn } from '@/common/codeblock';
import Avatar from 'vue-avatar';
import { mapGetters, mapActions } from 'vuex';
export default {
components: {
comment,
Avatar,
},
data() {
return {
discussion: {
author: 'HOJ',
avatar: '',
},
query: {
currentPage: 1,
did: null,
},
discussionID: 0,
};
},
mounted() {
this.init();
},
methods: {
...mapActions(['changeDomTitle']),
init() {
this.routeName = this.$route.name;
this.discussionID = this.$route.params.discussionID || '';
api.getDiscussion(this.discussionID).then((res) => {
this.discussion = res.data.data;
this.changeDomTitle({ title: this.discussion.title });
this.$nextTick((_) => {
addCodeBtn();
});
});
},
getInfoByUsername(uid, username) {
this.$router.push({
path: '/user-home',
query: { uid, username },
});
},
toAllDiscussionByCid(cid) {
this.$router.push({
path: '/discussion',
query: { cid },
});
},
toLikeDiscussion(did, toLike) {
api.toLikeDiscussion(did, toLike).then((res) => {
myMessage.success(res.data.msg);
if (toLike) {
this.discussion.likeNum++;
this.discussion.hasLike = true;
} else {
this.discussion.likeNum--;
this.discussion.hasLike = false;
}
});
},
},
computed: {
...mapGetters(['isAuthenticated', 'isAdminRole']),
contentHtml() {
if (this.discussion.content) {
return this.$markDown.render(this.discussion.content);
}
},
},
watch: {
isAuthenticated(newVal, oldVal) {
if (newVal != oldVal) {
this.init();
}
},
},
};
</script>
<style scoped>
.container {
box-sizing: border-box;
background-color: #fff;
box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
border: 1px solid #ebeef5;
margin-bottom: 20px;
}
.title-article {
background: #fff;
overflow: hidden;
padding: 10px 20px;
position: relative;
text-align: center;
}
.title-article h1.title {
font-size: 25px;
font-weight: 600;
color: #34495e;
padding: 0 0 10px;
width: 80%;
line-height: 32px;
word-break: break-all;
}
.title-article .title-msg {
margin-bottom: 0px;
font-size: 12px;
color: #999;
}
.title-article .title-msg span {
margin-right: 3px;
}
.title-article .title-msg span a.c999 {
color: #999 !important;
}
.title-article .title-msg span a.c999:hover {
color: #007bff !important;
text-decoration: none;
}
.user-avatar {
vertical-align: middle;
}
.user-name {
margin: 0 0.25rem !important;
}
.title-article .title-msg a.report {
position: absolute;
top: 30px;
right: 5px;
color: #4caf50 !important;
font-weight: bold;
font-size: 14px;
}
.title-article .title-msg a.like {
position: absolute;
top: 30px;
right: 60px;
color: #ff6700 !important;
font-weight: bold;
font-size: 14px;
}
@media screen and (max-width: 768px) {
.title-article .title-msg a.report {
top: 50px !important;
right: 12px !important;
}
.title-article .title-msg a.like {
top: 24px !important;
right: 12px !important;
}
}
.body-article {
background: #fff;
overflow: hidden;
width: 100%;
padding: 20px 20px;
text-align: left;
font-size: 14px;
line-height: 1.6;
}
</style>

View File

@ -0,0 +1,581 @@
<template>
<div class="container">
<el-row :gutter="20">
<el-col :md="18" :xs="24">
<div class="discussion-header">
<span style="padding: 16px;float:left;">
<el-breadcrumb separator-class="el-icon-arrow-right">
<template v-if="currentCategory">
<el-breadcrumb-item :to="{ name: routeName, query: null }"
>全部</el-breadcrumb-item
>
<el-breadcrumb-item
>{{ currentCategory }} ( {{ total }} )</el-breadcrumb-item
>
</template>
<template v-else>
<el-breadcrumb-item :to="{ name: routeName }"
>全部 ( {{ total }} )</el-breadcrumb-item
>
</template>
</el-breadcrumb>
</span>
<span class="search"
><vxe-input
v-model="query.keyword"
placeholder="Enter the keyword"
type="search"
@keyup.enter.native="handleQueryChange"
@search-click="handleQueryChange"
></vxe-input
></span>
</div>
<div
class="title-article"
v-for="(discussion, index) in discussionList"
:key="index"
>
<el-card shadow="hover" class="list-card">
<span class="svg-top" v-if="discussion.topPriority">
<svg
t="1620283436433"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="10095"
width="48"
height="48"
>
<path
d="M989.9222626666667 444.3410103333334L580.1490096666668 34.909091333333336H119.41107066666666l870.511192 870.596525V444.3410103333334z"
fill="#F44336"
p-id="10096"
></path>
<path
d="M621.3675956666667 219.39846433333332l-43.832889-43.770828-126.663111 126.841535-32.826182-32.780929 126.663112-126.841535-43.734627-43.673859 26.739071-26.775273 120.396283 120.224324-26.741657 26.776565zM582.6055756666667 284.67587833333334c24.030384-24.065293 50.614303-36.636444 79.751758-37.71604 29.134869-1.07701 55.240404 9.903838 78.31402 32.945131 21.950061 21.91903 32.323232 46.86998 31.120808 74.851556s-13.257697 53.441939-36.167111 76.383677c-23.901091 23.934707-50.254869 36.406303-79.057455 37.41608-28.806465 1.012364-54.481455-9.739636-77.024969-32.252121-22.016-21.98497-32.689131-47.067798-32.014223-75.244606 0.672323-28.179394 12.365576-53.638465 35.077172-76.383677z m36.196849 32.57794c-14.921697 14.943677-23.517091 30.756202-25.783596 47.438869-2.269091 16.68396 2.880646 31.297939 15.441454 43.841939 12.825859 12.807758 27.34804 18.234182 43.566546 16.271515 16.217212-1.960081 31.985778-10.608485 47.303111-25.947798 15.976727-15.998707 25.133253-32.109899 27.46699-48.332283 2.333737-16.221091-2.813414-30.637253-15.441455-43.247192-12.827152-12.809051-27.67903-18.133333-44.558222-15.972848-16.879192 2.157899-32.877899 10.808889-47.994828 25.947798zM780.1276766666667 524.3048083333333l-53.476848 53.553131-32.726627-32.681374 153.400889-153.616808 52.858829 52.783839c38.213818 38.159515 41.146182 73.44097 8.79709 105.83402-15.71297 15.737535-34.076444 22.586182-55.086545 20.552404-21.012687-2.032485-39.97996-11.897535-56.905697-29.591273l-16.861091-16.833939z m74.572283-74.67701l-49.516606 49.586424 14.182141 14.161454c19.240081 19.211636 37.209212 20.455434 53.913859 3.728809 16.305131-16.329697 14.941091-34.002747-4.101172-53.016566L854.6999596666667 449.6277983333334z"
fill="#FFFFFF"
p-id="10097"
></path>
</svg>
</span>
<a @click="toDiscussionDetail(discussion.id)" class="article-hlink">
<h1>
{{ discussion.title }}
</h1>
</a>
<a
@click="toDiscussionDetail(discussion.id)"
class="article-hlink2"
>
<p>{{ discussion.description }}</p>
</a>
<div class="title-msg">
<span>
<a
@click="getInfoByUsername(discussion.uid, discussion.author)"
:title="discussion.author"
>
<avatar
:username="discussion.author"
:inline="true"
:size="24"
color="#FFF"
class="user-avatar"
:src="discussion.avatar"
></avatar>
<span class="pl">{{ discussion.author }}</span></a
>
<span
class="role-root role"
title="Super Administrator"
v-if="discussion.role == 'root'"
>SPA</span
>
<span
class="role-admin role"
title="Administrator"
v-if="discussion.role == 'admin'"
>ADM</span
>
</span>
<span class="pr pl hidden-xs-only"
><label class="fw"><i class="fa fa-clock-o"></i></label
><span>
时间{{ discussion.gmtCreate | localtime }}</span
></span
>
<span class="pr"
><label class="fw"><i class="fa fa-thumbs-o-up"></i></label
><span> 点赞{{ discussion.likeNum }}</span></span
>
<span class="pr"
><label class="fw"><i class="fa fa-eye"></i></label
><span> 浏览{{ discussion.viewNum }}</span></span
>
<span
><label class="fw"><i class="el-icon-folder-opened"></i></label>
<a
@click="
pushRouter(
{ cid: discussion.categoryId },
{ problemID: query.pid },
routeName
)
"
>
{{ cidMapName[discussion.categoryId] }}</a
>
</span>
<span class="pr hidden-sm-and-up" style="float:right "
><label class="fw"><i class="fa fa-clock-o"></i></label
><span> {{ discussion.gmtCreate | localtime }}</span></span
>
</div>
</el-card>
</div>
<Pagination
:total="total"
:page-size="limit"
@on-change="changeRoute"
:current.sync="currentPage"
></Pagination>
</el-col>
<el-col :md="6" :xs="24">
<el-button
class="btn"
type="primary"
@click="toEditDiscussion"
style="width: 100%;"
><i class="el-icon-edit">
{{ this.query.pid == '' ? '发布讨论' : '发布题目讨论' }}</i
>
</el-button>
<template v-if="this.query.pid">
<el-button
class="btn"
type="success"
@click="toAllDiscussion"
style="width: 100%;margin-left:0;margin-top:10px"
><i class="el-icon-s-home"> 综合讨论区</i>
</el-button>
<el-button
class="btn"
type="warning"
@click="
pushRouter(null, { problemID: query.pid }, 'ProblemDetails')
"
style="width: 100%;margin-left:0;margin-top:10px"
><i class="el-icon-back"> 返回 ({{ query.pid }}) 题目</i>
</el-button>
</template>
<div class="category-body">
<h3 class="title-sidebar">
<a @click="pushRouter(null, { problemID: query.pid }, routeName)"
><i class="el-icon-folder-opened"></i> 讨论分类</a
>
</h3>
<el-row>
<el-col
:span="24"
class="category-item"
:title="category.name"
v-for="(category, index) in categoryList"
:key="index"
>
<a
@click="
pushRouter(
{ cid: category.id },
{ problemID: query.pid },
routeName
)
"
style="display: block"
>
<i class="fa fa-flag"> {{ category.name }}</i>
<span
class="el-icon-arrow-right"
style="float:right;font-weight: 600!important;"
></span>
</a>
</el-col>
</el-row>
</div>
</el-col>
</el-row>
<!--编辑讨论对话框-->
<el-dialog
:title="discussionDialogTitle"
:visible.sync="showEditDiscussionDialog"
:fullscreen="true"
@open="onOpenEditDialog"
>
<el-form label-position="top" :model="discussion">
<el-form-item label="讨论标题" required>
<el-input
v-model="discussion.title"
placeholder="请输入讨论标题"
class="title-input"
>
</el-input>
</el-form-item>
<el-form-item label="讨论简介" required>
<el-input
v-model="discussion.description"
placeholder="请输入讨论简介"
class="title-input"
>
</el-input>
</el-form-item>
<el-form-item label="讨论分类" required>
<el-select v-model="discussion.categoryId" placeholder="请选择">
<el-option
v-for="category in categoryList"
:key="category.id"
:label="category.name"
:value="category.id"
>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="是否置顶" required v-if="isAdminRole">
<el-switch v-model="discussion.topPriority"> </el-switch>
</el-form-item>
<el-form-item label="讨论内容" required>
<Editor :value.sync="discussion.content"></Editor>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button
type="danger"
@click.native="showEditDiscussionDialog = false"
>取消</el-button
>
<el-button type="primary" @click.native="submitDiscussion"
>发布</el-button
>
</span>
</el-dialog>
</div>
</template>
<script>
import Avatar from 'vue-avatar';
import Pagination from '@/components/oj/common/Pagination';
import Editor from '@/components/admin/Editor.vue';
import api from '@/common/api';
import myMessage from '@/common/message';
import { mapGetters, mapActions } from 'vuex';
export default {
components: {
Avatar,
Editor,
Pagination,
},
data() {
return {
total: 0,
limit: 15,
currentPage: 1,
showEditDiscussionDialog: false,
discussion: {
id: null,
pid: null, // id null
title: '',
content: '',
description: '',
categoryId: '',
topPriority: false,
uid: '',
author: '',
avatar: '',
},
//
discussionDialogTitle: 'Edit Discussion',
discussionList: [],
categoryList: [],
cidMapName: {},
currentCategory: '',
query: {
keyword: '',
cid: '',
currentPage: 1,
limit: 15,
pid: '',
},
routeName: '',
};
},
mounted() {
api.getCategoryList().then((res) => {
this.categoryList = res.data.data;
for (let i = 0; i < this.categoryList.length; i++) {
this.cidMapName[this.categoryList[i].id] = this.categoryList[i].name;
}
this.init();
});
},
methods: {
...mapActions(['changeDomTitle']),
init() {
this.routeName = this.$route.name;
let query = this.$route.query;
this.query.currentPage = parseInt(query.currentPage) || 1;
if (this.query.currentPage < 1) {
this.query.currentPage = 1;
}
this.query.keyword = query.keyword || '';
this.query.cid = query.cid || '';
this.query.pid = this.$route.params.problemID || '';
if (this.query.cid) {
this.currentCategory = this.cidMapName[this.query.cid];
} else {
this.currentCategory = '';
}
if (this.query.pid) {
this.discussion.pid = this.query.pid;
this.changeDomTitle({ title: this.query.pid + ' Discussion' });
} else {
this.discussion.pid = null;
}
this.getDiscussionList();
},
getDiscussionList() {
let queryParams = Object.assign({}, this.query);
api.getDiscussionList(this.limit, queryParams).then((res) => {
this.total = res.data.data.total;
this.discussionList = res.data.data.records;
});
},
getInfoByUsername(uid, username) {
this.$router.push({
path: '/user-home',
query: { uid, username },
});
},
toEditDiscussion() {
if (!this.isAuthenticated) {
myMessage.warning('请先登录');
this.$store.dispatch('changeModalStatus', { visible: true });
} else {
this.showEditDiscussionDialog = true;
}
},
handleQueryChange() {
this.pushRouter(
this.query,
{ problemID: this.query.pid },
this.routeName
);
},
toDiscussionDetail(discussionID) {
this.$router.push({
name: 'DiscussionDetail',
params: { discussionID: discussionID },
});
},
toAllDiscussion() {
this.$router.push({
path: '/discussion',
});
},
pushRouter(query, params, name) {
this.$router.push({
name: name,
query: query,
params: params,
});
},
//
onOpenEditDialog() {
// todo
// bug
setTimeout(() => {
if (document.createEvent) {
let event = document.createEvent('HTMLEvents');
event.initEvent('resize', true, true);
window.dispatchEvent(event);
} else if (document.createEventObject) {
window.fireEvent('onresize');
}
}, 0);
},
submitDiscussion() {
//
let discussion = Object.assign({}, this.discussion);
if (discussion.pid) {
discussion.title = '[' + discussion.pid + '] ' + discussion.title;
}
api.addDiscussion(discussion).then((res) => {
myMessage.success(res.data.msg);
this.showEditDiscussionDialog = false;
this.init();
});
},
},
watch: {
$route(newVal, oldVal) {
if (newVal != oldVal) {
this.init();
}
},
},
computed: {
...mapGetters(['isAuthenticated', 'userInfo', 'isAdminRole']),
},
};
</script>
<style>
.role-root {
background-color: #f9d681 !important;
color: #ff503f !important;
font-weight: 600;
}
.role-admin {
background-color: #409eff !important;
color: #fff !important;
}
.role {
display: inline-block;
font-size: 0.75rem;
padding: 0.1875rem 0.25rem;
line-height: 1;
vertical-align: middle;
}
</style>
<style scoped>
/deep/ .el-card__body {
padding: 0 !important;
}
.discussion-header {
background-color: #fff;
border-radius: 6px;
overflow: hidden;
margin-bottom: 10px;
height: 41px;
}
.discussion-header .search {
margin-top: 3px;
margin-right: 6px;
float: right;
}
.list-card {
border-radius: 6px;
margin-bottom: 10px;
padding: 15px;
text-align: left;
position: relative;
}
.list-card p {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
.list-card .article-hlink {
overflow: hidden;
display: block;
}
.svg-top {
position: absolute;
top: 0px;
right: 0px;
}
.article-hlink h1 {
font-size: 16px;
font-weight: 600;
color: #34495e;
margin-top: 5px;
}
a {
color: #34495e;
text-decoration: none;
}
.article-hlink2 p {
margin-bottom: 10px;
color: #888;
font-size: 12px;
margin: 0;
padding: 0;
}
.title-article .title-msg {
margin-top: 15px;
font-size: 12px;
color: #999 !important;
}
.title-article .title-msg a {
color: #999;
text-decoration: none;
}
.user-avatar {
vertical-align: middle;
}
.title-article .title-msg span {
margin-right: 3px;
}
.title-article .title-msg .pl {
padding-left: 0.3rem !important;
}
.title-article .title-msg .pr {
padding-right: 0.3rem !important;
}
.category-body {
background: #fff;
padding: 15px;
margin-bottom: 15px;
border-radius: 6px;
overflow: hidden;
margin-top: 12px;
}
.category-body .title-sidebar {
border-bottom: 1px solid #eee;
width: 100%;
color: #34495e;
font-size: 14px;
font-weight: 600;
padding-bottom: 10px;
margin-bottom: 10px;
overflow: hidden;
}
.category-body .title-sideba a {
color: #34495e;
}
.category-body h3 {
margin: 0;
padding: 0;
}
.category-item {
height: 30px;
font-size: 14px;
padding: 3px 10px;
margin-bottom: 5px;
}
.category-item a {
color: #34495e;
}
.category-item:hover {
background-color: #eff3f5 !important;
font-weight: bold;
color: #222;
}
</style>

View File

@ -25,6 +25,15 @@
><el-tag effect="plain" size="small">暂无标签</el-tag></span
>
<div class="problem-menu">
<span v-if="!contestID">
<el-link
type="primary"
:underline="false"
@click="goProblemDiscussion"
><i class="fa fa-comments" aria-hidden="true"></i>
Discussion</el-link
></span
>
<span>
<el-link
type="primary"
@ -40,7 +49,7 @@
:underline="false"
@click="goProblemSubmission"
><i class="fa fa-bars" aria-hidden="true"></i>
Submissions</el-link
Submission</el-link
></span
>
</div>
@ -538,6 +547,12 @@ export default {
});
}
},
goProblemDiscussion() {
this.$router.push({
name: 'ProblemDiscussion',
params: { problemID: this.problemID },
});
},
handleRoute(route) {
this.$router.push(route);

View File

@ -89,7 +89,7 @@
</vxe-table>
</el-col>
<el-col :span="24">
<el-col :span="24" style="margin-top: 13px;">
<Highlight
:code="submission.code"
:language="submission.language"
@ -184,7 +184,7 @@ import { JUDGE_STATUS, JUDGE_STATUS_RESERVE } from '@/common/constants';
import utils from '@/common/utils';
import Highlight from '@/components/oj/common/Highlight';
import myMessage from '@/common/message';
import { addCodeBtn } from '@/common/codeblock';
export default {
name: 'submissionDetails',
components: {
@ -309,6 +309,10 @@ export default {
} else {
this.codeShare = data.codeShare;
}
this.$nextTick((_) => {
addCodeBtn();
});
},
() => {
this.loadingTable = false;
@ -400,10 +404,7 @@ export default {
.el-row--flex {
flex-wrap: wrap;
}
pre {
border: none;
background: none;
}
.test-detail-item {
width: 100%;
padding: 15px;

View File

@ -56,8 +56,13 @@ module.exports={
devServer: {
open: true, // npm run serve后自动打开页面
host: '0.0.0.0', // 匹配本机IP地址(默认是0.0.0.0)
port: 8080, // 开发服务器运行端口号
proxy: null,
port: 8088, // 开发服务器运行端口号
proxy: {
'/api': { // 以'/api'开头的请求会被代理进行转发
target: 'http://localhost:6688', // 要发向的后台服务器地址 如果后台服务跑在后台开发人员的机器上,就写成 `http://ip:port` 如 `http:192.168.12.213:8081` ip为后台服务器的ip
changeOrigin: true
}
},
disableHostCheck: true,
},
//去除生产环境的productionSourceMap

View File

@ -2,10 +2,10 @@ hoj:
jwt:
# 加密秘钥
secret: zsc-acm-hoj
# token有效时长24小时,单位秒
expire: 86400
# 6小时内还有请求,可进行刷新
checkRefreshExpire: 21600
# token有效时长3*3600*24单位秒
expire: 259200
# 2*3600*24s内还有请求,可进行刷新
checkRefreshExpire: 172800
header: token
judge:
# 调用判题服务器的token

View File

@ -47,6 +47,18 @@ CREATE TABLE `auth` (
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;
/*Table structure for table `category` */
DROP TABLE IF EXISTS `category`;
CREATE TABLE `category` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
/*Table structure for table `code_template` */
DROP TABLE IF EXISTS `code_template`;
@ -71,25 +83,43 @@ CREATE TABLE `code_template` (
DROP TABLE IF EXISTS `comment`;
CREATE TABLE `comment` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`uid` varchar(32) NOT NULL,
`title` varchar(255) NOT NULL COMMENT '讨论标题',
`content` longtext COMMENT '讨论详情',
`tid` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '标签id,0表示无',
`pid` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '0表示无引用题目',
`cid` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '0表示无引用比赛',
`id` int(11) NOT NULL AUTO_INCREMENT,
`cid` bigint(20) unsigned DEFAULT NULL COMMENT 'null表示无引用比赛',
`did` int(11) DEFAULT NULL COMMENT 'null表示无引用讨论',
`content` longtext COMMENT '评论内容',
`from_uid` varchar(32) NOT NULL COMMENT '评论者id',
`from_name` varchar(255) DEFAULT NULL COMMENT '评论者用户名',
`from_avatar` varchar(255) DEFAULT NULL COMMENT '评论组头像地址',
`from_role` varchar(20) DEFAULT NULL COMMENT '评论者角色',
`like_num` int(11) DEFAULT '0' COMMENT '点赞数量',
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `uid` (`from_uid`),
KEY `from_avatar` (`from_avatar`),
KEY `comment_ibfk_7` (`did`),
KEY `cid` (`cid`),
CONSTRAINT `comment_ibfk_6` FOREIGN KEY (`from_avatar`) REFERENCES `user_info` (`avatar`) ON DELETE NO ACTION ON UPDATE CASCADE,
CONSTRAINT `comment_ibfk_7` FOREIGN KEY (`did`) REFERENCES `discussion` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `comment_ibfk_8` FOREIGN KEY (`cid`) REFERENCES `contest` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8;
/*Table structure for table `comment_like` */
DROP TABLE IF EXISTS `comment_like`;
CREATE TABLE `comment_like` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`uid` varchar(255) NOT NULL,
`cid` int(11) NOT NULL,
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `uid` (`uid`),
KEY `tid` (`tid`),
KEY `pid` (`pid`),
KEY `cid` (`cid`),
CONSTRAINT `comment_ibfk_1` FOREIGN KEY (`uid`) REFERENCES `user_info` (`uuid`) ON DELETE NO ACTION ON UPDATE CASCADE,
CONSTRAINT `comment_ibfk_2` FOREIGN KEY (`tid`) REFERENCES `tag` (`id`) ON DELETE NO ACTION ON UPDATE CASCADE,
CONSTRAINT `comment_ibfk_3` FOREIGN KEY (`pid`) REFERENCES `problem` (`id`) ON DELETE NO ACTION ON UPDATE CASCADE,
CONSTRAINT `comment_ibfk_4` FOREIGN KEY (`cid`) REFERENCES `contest` (`id`) ON DELETE NO ACTION ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CONSTRAINT `comment_like_ibfk_1` FOREIGN KEY (`uid`) REFERENCES `user_info` (`uuid`) ON DELETE NO ACTION ON UPDATE CASCADE,
CONSTRAINT `comment_like_ibfk_2` FOREIGN KEY (`cid`) REFERENCES `comment` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
/*Table structure for table `contest` */
@ -244,6 +274,55 @@ CREATE TABLE `contest_score` (
CONSTRAINT `contest_score_ibfk_1` FOREIGN KEY (`cid`) REFERENCES `contest` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*Table structure for table `discussion` */
DROP TABLE IF EXISTS `discussion`;
CREATE TABLE `discussion` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`category_id` int(11) NOT NULL COMMENT '分类id',
`title` varchar(255) DEFAULT NULL COMMENT '讨论标题',
`description` varchar(255) DEFAULT NULL COMMENT '讨论简介',
`content` longtext COMMENT '讨论内容',
`pid` varchar(255) DEFAULT NULL COMMENT '关联题目id',
`uid` varchar(32) NOT NULL COMMENT '发表者id',
`author` varchar(255) NOT NULL COMMENT '发表者用户名',
`avatar` varchar(255) DEFAULT NULL COMMENT '发表讨论者头像',
`role` varchar(25) DEFAULT 'user' COMMENT '发表者角色',
`view_num` int(11) DEFAULT '0' COMMENT '浏览数量',
`like_num` int(11) DEFAULT '0' COMMENT '点赞数量',
`top_priority` tinyint(1) DEFAULT '0' COMMENT '优先级,是否置顶',
`status` tinyint(1) DEFAULT '0' COMMENT '是否封禁',
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `category_id` (`category_id`),
KEY `discussion_ibfk_4` (`avatar`),
KEY `discussion_ibfk_1` (`uid`),
KEY `pid` (`pid`),
CONSTRAINT `discussion_ibfk_1` FOREIGN KEY (`uid`) REFERENCES `user_info` (`uuid`) ON DELETE NO ACTION ON UPDATE CASCADE,
CONSTRAINT `discussion_ibfk_2` FOREIGN KEY (`category_id`) REFERENCES `category` (`id`) ON DELETE NO ACTION ON UPDATE CASCADE,
CONSTRAINT `discussion_ibfk_4` FOREIGN KEY (`avatar`) REFERENCES `user_info` (`avatar`) ON DELETE NO ACTION ON UPDATE CASCADE,
CONSTRAINT `discussion_ibfk_6` FOREIGN KEY (`pid`) REFERENCES `problem` (`problem_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
/*Table structure for table `discussion_like` */
DROP TABLE IF EXISTS `discussion_like`;
CREATE TABLE `discussion_like` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`uid` varchar(255) NOT NULL,
`did` int(11) NOT NULL,
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `did` (`did`),
KEY `uid` (`uid`),
CONSTRAINT `discussion_like_ibfk_1` FOREIGN KEY (`did`) REFERENCES `discussion` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `discussion_like_ibfk_2` FOREIGN KEY (`uid`) REFERENCES `user_info` (`uuid`) ON DELETE NO ACTION ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
/*Table structure for table `file` */
DROP TABLE IF EXISTS `file`;
@ -298,7 +377,7 @@ CREATE TABLE `judge` (
CONSTRAINT `judge_ibfk_1` FOREIGN KEY (`pid`) REFERENCES `problem` (`id`) ON DELETE NO ACTION ON UPDATE CASCADE,
CONSTRAINT `judge_ibfk_2` FOREIGN KEY (`uid`) REFERENCES `user_info` (`uuid`) ON DELETE NO ACTION ON UPDATE CASCADE,
CONSTRAINT `judge_ibfk_3` FOREIGN KEY (`username`) REFERENCES `user_info` (`username`) ON DELETE NO ACTION ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=132 DEFAULT CHARSET=utf8;
) ENGINE=InnoDB AUTO_INCREMENT=145 DEFAULT CHARSET=utf8;
/*Table structure for table `judge_case` */
@ -327,7 +406,7 @@ CREATE TABLE `judge_case` (
CONSTRAINT `judge_case_ibfk_1` FOREIGN KEY (`uid`) REFERENCES `user_info` (`uuid`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `judge_case_ibfk_2` FOREIGN KEY (`pid`) REFERENCES `problem` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `judge_case_ibfk_3` FOREIGN KEY (`submit_id`) REFERENCES `judge` (`submit_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1525 DEFAULT CHARSET=utf8;
) ENGINE=InnoDB AUTO_INCREMENT=1733 DEFAULT CHARSET=utf8;
/*Table structure for table `judge_server` */
@ -348,7 +427,7 @@ CREATE TABLE `judge_server` (
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=124 DEFAULT CHARSET=utf8;
) ENGINE=InnoDB AUTO_INCREMENT=132 DEFAULT CHARSET=utf8;
/*Table structure for table `language` */
@ -399,8 +478,9 @@ CREATE TABLE `problem` (
`case_version` varchar(40) DEFAULT '0' COMMENT '题目测试数据的版本号',
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`,`problem_id`),
PRIMARY KEY (`id`),
KEY `author` (`author`),
KEY `problem_id` (`problem_id`),
CONSTRAINT `problem_ibfk_1` FOREIGN KEY (`author`) REFERENCES `user_info` (`username`) ON DELETE NO ACTION ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1065 DEFAULT CHARSET=utf8;
@ -481,6 +561,32 @@ CREATE TABLE `problem_tag` (
CONSTRAINT `problem_tag_ibfk_2` FOREIGN KEY (`tid`) REFERENCES `tag` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=71 DEFAULT CHARSET=utf8;
/*Table structure for table `reply` */
DROP TABLE IF EXISTS `reply`;
CREATE TABLE `reply` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`comment_id` int(11) NOT NULL COMMENT '被回复的评论id',
`from_uid` varchar(255) NOT NULL COMMENT '发起回复的用户id',
`from_name` varchar(255) NOT NULL COMMENT '发起回复的用户名',
`from_avatar` varchar(255) DEFAULT NULL COMMENT '发起回复的用户头像地址',
`from_role` varchar(255) DEFAULT NULL COMMENT '发起回复的用户角色',
`to_uid` varchar(255) NOT NULL COMMENT '被回复的用户id',
`to_name` varchar(255) NOT NULL COMMENT '被回复的用户名',
`to_avatar` varchar(255) DEFAULT NULL COMMENT '被回复的用户头像地址',
`content` longtext COMMENT '回复的内容',
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `comment_id` (`comment_id`),
KEY `from_avatar` (`from_avatar`),
KEY `to_avatar` (`to_avatar`),
CONSTRAINT `reply_ibfk_1` FOREIGN KEY (`comment_id`) REFERENCES `comment` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `reply_ibfk_2` FOREIGN KEY (`from_avatar`) REFERENCES `user_info` (`avatar`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `reply_ibfk_3` FOREIGN KEY (`to_avatar`) REFERENCES `user_info` (`avatar`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8;
/*Table structure for table `role` */
DROP TABLE IF EXISTS `role`;
@ -526,7 +632,7 @@ CREATE TABLE `session` (
PRIMARY KEY (`id`),
KEY `uid` (`uid`),
CONSTRAINT `session_ibfk_1` FOREIGN KEY (`uid`) REFERENCES `user_info` (`uuid`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=93 DEFAULT CHARSET=utf8;
) ENGINE=InnoDB AUTO_INCREMENT=118 DEFAULT CHARSET=utf8;
/*Table structure for table `tag` */
@ -560,7 +666,7 @@ CREATE TABLE `user_acproblem` (
CONSTRAINT `user_acproblem_ibfk_1` FOREIGN KEY (`uid`) REFERENCES `user_info` (`uuid`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `user_acproblem_ibfk_2` FOREIGN KEY (`pid`) REFERENCES `problem` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `user_acproblem_ibfk_3` FOREIGN KEY (`submit_id`) REFERENCES `judge` (`submit_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=279 DEFAULT CHARSET=utf8;
) ENGINE=InnoDB AUTO_INCREMENT=306 DEFAULT CHARSET=utf8;
/*Table structure for table `user_info` */
@ -586,7 +692,8 @@ CREATE TABLE `user_info` (
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`uuid`),
UNIQUE KEY `USERNAME_UNIQUE` (`username`),
UNIQUE KEY `EMAIL_UNIQUE` (`email`)
UNIQUE KEY `EMAIL_UNIQUE` (`email`),
UNIQUE KEY `avatar` (`avatar`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*Table structure for table `user_record` */
@ -605,7 +712,7 @@ CREATE TABLE `user_record` (
PRIMARY KEY (`id`,`uid`),
KEY `uid` (`uid`),
CONSTRAINT `user_record_ibfk_1` FOREIGN KEY (`uid`) REFERENCES `user_info` (`uuid`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=304 DEFAULT CHARSET=utf8;
) ENGINE=InnoDB AUTO_INCREMENT=305 DEFAULT CHARSET=utf8;
/*Table structure for table `user_role` */
@ -622,7 +729,7 @@ CREATE TABLE `user_role` (
KEY `role_id` (`role_id`) USING BTREE,
CONSTRAINT `user_role_ibfk_1` FOREIGN KEY (`uid`) REFERENCES `user_info` (`uuid`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `user_role_ibfk_2` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=306 DEFAULT CHARSET=utf8;
) ENGINE=InnoDB AUTO_INCREMENT=307 DEFAULT CHARSET=utf8;
/* Trigger structure for table `contest` */