springboot+vue实现帖子功能-论坛
分类: springboot vue 专栏: springboot vue 常用功能 标签: springboot+vue实现帖子功能-论坛
2025-02-19 22:07:41 284浏览
springboot+vue实现帖子功能-论坛
-- ---------------------------- DROP TABLE IF EXISTS `xl_forum`; CREATE TABLE `xl_forum` ( `id` int(0) NOT NULL AUTO_INCREMENT, `fname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '标题', `fcont` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT '内容', `cts` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '创建时间', `sh` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '0待审核1通过-1不通过', `uid` int(0) DEFAULT NULL COMMENT '用户id', `imgs` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '图片集合', `msg` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '不通过原因', `comment_num` int(0) DEFAULT NULL COMMENT '评论数量', `csee_num` int(0) DEFAULT NULL COMMENT '浏览量', `fdesc` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '简介', `tid` int(0) DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 135 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact; SET FOREIGN_KEY_CHECKS = 1;
bean
package com.jff.xinli.bean;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.util.List;
@Data
@TableName("xl_forum")
public class XlForum {
@TableId(type = IdType.AUTO)
private Integer id;
private Integer uid;
private Integer tid;
private Integer commentNum;
private Integer cseeNum;
private String fname;
private String fcont;
private String cts;
private String imgs;
private String sh;//0待审核1通过-1不通过
private String msg;
private String fdesc;//简介
/*---*/
@TableField(exist = false)
private String keywords;
@TableField(exist = false)
private String nickname;
@TableField(exist = false)
private String faceimg;
@TableField(exist = false)
private List<String> imgsli;
@TableField(exist = false)
private String tname;
}
xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.jff.xinli.dao.XlForumMapper">
<sql id="field"> f.*,u.nickname ,u.faceimg,t.tname </sql>
<select id="getlistByJoin" resultType="com.jff.xinli.bean.XlForum" parameterType="com.jff.xinli.bean.XlForum">
select
<include refid="field"></include>
from xl_forum f
left join xl_users u on u.id=f.uid
left join xl_type t on t.id=f.tid
where 1=1
<if test="sh != null and sh!='' ">
and f.sh=#{sh}
</if>
<if test="uid != null ">
and f.uid = #{uid}
</if>
<if test="tid != null ">
and f.tid = #{tid}
</if>
<if test="keywords != null and keywords != ''">
and (
u.nickname like concat('%',#{keywords},'%')
or f.fname like concat('%',#{keywords},'%')
or f.fcont like concat('%',#{keywords},'%')
)
</if>
</select>
<select id="getByIdJoin" resultType="com.jff.xinli.bean.XlForum"
parameterType="java.lang.Integer">
select
<include refid="field"></include>
from xl_forum f
left join xl_users u on u.id=f.uid
left join xl_type t on t.id=f.tid
where f.id=#{0}
</select>
</mapper>
dao
package com.jff.xinli.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jff.xinli.bean.XlForum;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@Mapper
public interface XlForumMapper extends BaseMapper<XlForum> {
List<XlForum> getlistByJoin(XlForum o);
XlForum getByIdJoin(Integer id);
}
service
package com.jff.xinli.service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jff.xinli.bean.XlForum;
import com.jff.xinli.dao.XlForumMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class XlForumService extends ServiceImpl<XlForumMapper, XlForum> {
@Autowired
XlForumMapper forumMapper;
public List<XlForum> getlistByJoin(XlForum o) {
return forumMapper.getlistByJoin(o);
}
public XlForum getByIdJoin(Integer id){
return forumMapper.getByIdJoin(id);
}
}
controller
package com.jff.xinli.control.api;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Resource;
import com.github.pagehelper.PageInfo;
import com.jff.xinli.bean.XlForum;
import com.jff.xinli.service.XlCommentService;
import com.jff.xinli.service.XlForumService;
import com.jff.xinli.util.DateUtils;
import com.jff.xinli.util.DocumentUntil;
import com.jff.xinli.util.MessUntil;
import com.jff.xinli.util.UploadFile;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.github.pagehelper.PageHelper;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@CrossOrigin
@RestController
@RequestMapping("/forum")
public class XlForumController {
@Resource
XlForumService forumService;
@Resource
XlCommentService commentService;
@RequestMapping("/page")
public MessUntil page(@RequestParam(value="pageNo",defaultValue="1")int pageNo , @RequestParam(value="pageSize",defaultValue="10")int pageSize ,
XlForum o) {
MessUntil mess=new MessUntil();
PageHelper.startPage(pageNo, pageSize," id desc ");
List<XlForum> li=forumService.getlistByJoin(o);
PageInfo<XlForum> pageInfo = new PageInfo<XlForum>(li, pageSize);
for(XlForum f:pageInfo.getList()){
f.setImgsli(strToList(f.getImgs()));
}
return mess.succ(pageInfo);
}
@RequestMapping("/detail")
public MessUntil detail( XlForum o) {
MessUntil mess=new MessUntil();
o=forumService.getByIdJoin(o.getId());
if(o!=null && o.getSh()!=null&& o.getSh().equals("1")) {
XlForum n=new XlForum();
n.setId(o.getId());
n.setCseeNum(o.getCseeNum()+1);
forumService.updateById(n);
return mess.succ(o) ;
}
return mess.error("该数据不存在或待审核");
}
private String getimgs(String cont){
List<String> li= DocumentUntil.getIimgs(cont);
String p="";
for(String img:li){
p+=img+",";
}
if(p.lastIndexOf(",")>-1)p=p.substring(0,p.length()-1);
return p;
}
@RequestMapping("/save")
public MessUntil save( XlForum o,Integer jf) {
MessUntil mess=new MessUntil();
if(o.getId()!=null){
XlForum ol=forumService.getById(o.getId());
if(ol.getSh().equals("-1"))o.setSh("1");
o.setImgs(getimgs(o.getFcont()));
o.setFdesc(DocumentUntil.getP(o.getFcont(),200));
forumService.updateById(o);
}else {
o.setSh("1");
o.setCts(DateUtils.getNowDateTsString());
o.setImgs(getimgs(o.getFcont()));
o.setFdesc(DocumentUntil.getP(o.getFcont(),200));
o.setCommentNum(0);
o.setCseeNum(0);
forumService.save(o);
}
return mess.succ();
}
@RequestMapping("/update")
public MessUntil update( XlForum o) {
MessUntil mess=new MessUntil();
forumService.updateById(o);
return mess.succ();
}
@RequestMapping("/del")
public MessUntil del( Integer id) {
MessUntil mess=new MessUntil();
try{
XlForum ol=forumService.getByIdJoin(id);
commentService.deleteByFid(id);
forumService.removeById(id);
}catch (Exception e){
e.printStackTrace();
return mess.error("删除失败,请先删除关联信息");
}
return mess.succ();
}
private List<String> strToList(String imgs){
List<String> imgli=new ArrayList<>();
if(imgs !=null&&imgs.trim().length()>0){
String[] arr=imgs.split(",");
for(String img:arr){
if(img!=null&&img.trim().length()>0){
imgli.add(img);
}
}
}
return imgli;
}
}
vue
admin
<template>
<div class="about">
<v-header />
<v-sidebar />
<div class="content-box" >
<div class="content">
<div>
<div class="crumbs">
<el-breadcrumb separator="/">
<el-breadcrumb-item>
<i class="el-icon-s-home"></i>
帖子管理
</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="container">
<div class="handle-box">
<el-input placeholder="标题,内容,作者昵称" style="width:250px;" v-model="query.keywords"></el-input>
<el-select placeholder="请选择分类" v-model="query.tid" filterable>
<el-option label="请选择分类" value=""></el-option>
<el-option v-for="(t,i) in typeli" :label="t.tname" :value="t.id"></el-option>
</el-select>
<el-select v-model="query.sh" placeholder="请选择状态" >
<el-option label="请选择状态" value=""></el-option>
<el-option label="待审核" value="0"></el-option>
<el-option label="审核通过" value="1"></el-option>
<el-option label="审核不通过" value="-1"></el-option>
</el-select>
<el-button type="primary" @click="searchHandle">查找</el-button>
<el-button type="danger" @click="addHandle">发布帖子</el-button>
</div>
<el-card style="margin-bottom: 10px;" class="box-card" v-for="(t,i) in tableData" >
<div slot="header" class="clearfix">
<span>{{t.fname}}</span>
<el-tag type="info" v-if="t.sh=='0'">待审核</el-tag>
<el-tag type="danger" v-if="t.sh=='-1'">不通过</el-tag>
<el-tag type="success" v-if="t.sh=='1'">审核通过</el-tag>
<span v-if="t.sh=='-1'" style="color: red">({{t.msg}})</span>
<el-button v-if="lander.id==t.uid" style="float: right;" size="mini" type="danger" @click="delHandle(t)">删除</el-button>
<el-button v-if="lander.id==t.uid" style="float: right;" size="mini" type="success" @click="editHandle(t)">编辑</el-button>
<el-button v-if="lander.role=='admin'" style="float: right;" size="mini" type="warning" @click="seeinfoHandle(t)">审核</el-button>
</div>
<div class="hh">
{{t.fdesc}}..<span @click="seeinfoHandle(t)" style="color:blue;font-size: 12px;">【查看详情】</span>
<br>
<br>
<div style="flex-direction:row;display: flex;">
<div v-for="(t2,i2) in t.imgsli" style="width: 100px;height: 100px;overflow: hidden;border-radius: 10px;background-color: #f3f3f3;margin: 1px;">
<img :src="t2" style="width: 100px;height: 100px; ">
</div>
</div>
<br>
<div >
<img :src="FILE_URL+t.faceimg" style="width: 30px;height:30px;border-radius: 50%;float: left;">
<span style="font-size: 13px;color: #666;line-height: 30px;margin-left: 5px;">{{t.nickname}} {{t.cts}}发布 , 分类:{{t.tname}} {{t.commentNum}}评论 {{t.cseeNum}}浏览</span>
</div>
</div>
</el-card>
<div v-if="tableData.length==0" style="text-align:center;">
<br>
<br>
<img src="../../assets/img/nosj.png">
<br>
<br>
暂无数据
<br>
<br>
</div>
<el-pagination v-else
:page-sizes="[10,20,50,100,500]"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page.sync="query.pageNo"
:page-size="query.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
</div>
</div>
</div>
</div>
<el-dialog title="查看 " fullscreen :visible.sync="infoVisible" >
<h1 style="text-align: center;">{{infoObj.fname}}</h1>
<div class="hh" style="padding: 50px 10%;">
<br>
<div>
<img :src="FILE_URL+infoObj.faceimg" style="width: 30px;height:30px;border-radius: 50%;float: left;">
<span style="font-size: 13px;color: #666;line-height: 30px;margin-left: 5px;">{{infoObj.nickname}} {{infoObj.cts}}发布 , 分类:{{infoObj.tname}}
{{infoObj.commentNum}}评论 {{infoObj.cseeNum}}浏览</span>
</div>
<el-divider></el-divider>
<br>
<div v-html="infoObj.fcont"></div>
<br>
<br>
<br>
<div style="text-align: center;" v-if="lander.role=='admin'">
<el-divider></el-divider>
<el-button type="danger" @click="savestate(-1)">审核不通过</el-button>
<el-button type="primary" @click="savestate(1)">审核通过</el-button>
</div>
</div>
</el-dialog>
<el-dialog :title="(ruleForm.id?'编辑':'添加') " fullscreen :visible.sync="addVisible" >
<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="120px" class="demo-ruleForm">
<el-form-item label="标题" prop="fname">
<el-input v-model="ruleForm.fname"></el-input>
</el-form-item>
<el-form-item label="分类" prop="tid">
<el-select v-model="ruleForm.tid" filterable>
<el-option v-for="(t,i) in typeli" :label="t.tname" :value="t.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="内容" >
<quill-editor ref="myQuillEditor" v-model="ruleForm.fcont" :options="editorOption" />
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="addVisible = false">取 消</el-button>
<el-button type="primary" @click="submitForm">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { quillEditor,Quill} from 'vue-quill-editor'
import {container, ImageExtend, QuillWatch} from 'quill-image-extend-module'
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'
Quill.register('modules/ImageExtend', ImageExtend)
import '../../api/image-paste.min.js';
let CON_ACTION=process.env.VUE_APP_API_ROOT+'/common/upimg?type=forum_detail';
import vHeader from "./Header.vue";
import vSidebar from "./Sidebar.vue";
import {
forum_page,
forum_add,
forum_del, type_list,
forum_update,
} from '../../api/index'
export default {
name: "users",
components: {
vHeader,
vSidebar,
quillEditor
},
data() {
return {
FILE_URL:'',
typeli:[],
infoVisible:false,
infoObj:{},
lander:{},
query:{
pageNo:1,
pageSize:10,
keywords:'',
sh:'',
},
total:0,
tableData:[],
addVisible: false,
ruleForm: {
fname: '',
tid: '',
fcont: '',
},
rules: {
fname: [ {required: true, message: '请输入', trigger: 'blur'}, ],
tid: [ {required: true, message: '请输入', trigger: 'blur'}, ],
fcont: [ {required: true, message: '请输入', trigger: 'blur'}, ],
},
editorOption: {
modules: {
/* 还有一些其他的模块*/
imagePaste: {
addImageBlob: function (blob, callback) {
var formData = new FormData()
formData.append('file', blob)
// your upload function, get the uploaded image url, add then
let config = {
headers: {
"Content-Type": "multipart/form-data",
"Accept": "*/*"
}
}
// 上传接口
axios.post(CON_ACTION, formData, config).then(res => {
console.log(res);
let imgUrl = res.data.obj // 服务器返回的图片url
callback(imgUrl)
})
}
},
ImageExtend: { // 如果不作设置,即{} 则依然开启复制粘贴功能且以base64插入
loading: true,//可选参数 是否显示上传进度和提示语
name: 'file', // 图片参数名
size: 3, // 可选参数 图片大小,单位为M,1M = 1024kb
action: CON_ACTION, // 服务器地址, 如果action为空,则采用base64插入图片
// response 为一个函数用来获取服务器返回的具体图片地址
// 例如服务器返回{code: 200; data:{ url: 'baidu.com'}}
// 则 return res.data.url
response: res => {
return res.obj;
},
// headers: xhr => {
// 上传图片请求需要携带token的 在xhr.setRequestHeader中设置
// xhr.setRequestHeader(
// "Authorization",
// this.getCookie("usefname")
// ? this.getCookie("usefname").token_type +
// this.getCookie("usefname").access_token
// : "Basic emh4eTp6aHh5"
// );
// }, // 可选参数 设置请求头部
sizeError: () => {
}, // 图片超过大小的回调
start: () => {
}, // 可选参数 自定义开始上传触发事件
end: () => {
}, // 可选参数 自定义上传结束触发的事件,无论成功或者失败
error: () => {
}, // 可选参数 上传失败触发的事件
success: () => {
}, // 可选参数 上传成功触发的事件
// change: (xhr, formData) => {
// xhr.setRequestHeader('myHeader','myValue')
// formData.append('token', 'myToken')
// } // 可选参数 每次选择图片触发,也可用来设置头部,但比headers多了一个参数,可设置formData
},
toolbar: {
container: container,
handlers: {
'image': function () {
QuillWatch.emit(this.quill.id)
}
}
}
},
//主题
theme: "snow",
placeholder: "请输入正文"
},
};
},
methods:{
savestate( sh){
if(sh=='1'){
forum_update({id: this.infoObj.id,sh:sh,msg:''}).then((res) => {
let data = res.data;
if(data.status==1){
this.$message.success(data.msg);
this.getPage();
this.infoVisible=false;
}else{
this.$message.error(data.msg);
}
})
}else{
this.$prompt('请输入审核不通过 的原因', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
}).then(({ value }) => {
forum_update( { id:this.infoObj.id,sh:sh,msg:value } ).then((res)=>{
this.getPage();
this.infoVisible=false;
})
}).catch((e) => {
console.log(e)
this.$message({
type: 'info',
message: '取消输入'
});
});
}
},
seeinfoHandle(t) {
this.infoVisible=true;
this.infoObj=t;
if( this.infoObj.fcont!=null){
this.infoObj.fcont= this.infoObj.fcont.replace(/<img/g,"<img style='max-width:100%;height:auto;'");;
}
},
searchHandle(){
this.query.pageNo=1;
this.getPage();
},
handleSizeChange(val) {
this.query.pageSize=val;
this.query.pageNo=1;
this.getPage();
},
handleCurrentChange(val) {
this.query.pageNo=val;
this.getPage();
},
getPage(){
forum_page( this.query ).then((res)=>{
let data=res.data;
this.tableData=data.obj.list;
this.total=data.obj.total;
})
},
addHandle(){
this.ruleForm={};
this.addVisible=true;
this.ruleForm.uid=this.lander.id;
this.ruleForm.ntype =this.query.ntype;
},
editHandle(row){
this.ruleForm.id=row.id;
this.ruleForm.fname=row.fname;
this.ruleForm.tid=row.tid;
if(row.fcont!=null){
this.ruleForm.fcont= row.fcont.replace(/<img/g,"<img style='max-width:100%;height:auto;'");;
}
this.addVisible=true;
},
delHandle(row) {
this.$confirm('确定删除?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
forum_del({id: row.id}).then((res) => {
let data = res.data;
if(data.status==1){
this.getPage();
}else{
this.$message.error(data.msg);
}
})
}).catch(() => {
this.$message({
type: 'info',
message: '已取消'
});
});
},
submitForm() {
this.$refs.ruleForm.validate((valid) => {
if (valid) {
forum_add(this.ruleForm).then((res)=>{
let data=res.data;
if(data.status==1){
this.addVisible=false;
this.getPage();
}else{
this.$message.error(data.msg);
}
})
} else {
this.$message.info("请按要求输入")
return false;
}
});
},
gettypeli(){
type_list( {ttype:'forum'} ).then((res)=>{
let data=res.data;
this.typeli=data.obj ;
})
},
},
mounted() {
this.lander=JSON.parse(localStorage.loginUser)
this.FILE_URL=process.env.VUE_APP_API_ROOT
if(this.lander.role=='psy')this.query.uid=this.lander.id;
if(this.lander.role=='pt')this.query.uid=this.lander.id;
this.getPage();
this.gettypeli();
},
}
</script>
<style scoped>
.handle-box {
margin-bottom: 20px;
}
.hh{ word-wrap: break-word; word-break:break-all }
</style>
qt
<template>
<div>
<!-- section begin -->
<section id="de-subheader" class="mt-sm-60 pt20 pb20 bg-gradient-45-deg text-light">
<div class="container relative z-index-1000">
<div class="row align-items-center">
<div class="col-lg-6">
<h3 class="mb-0"></h3>
</div>
<div class="col-lg-6 text-lg-end">
<ul class="crumb">
<li><router-link to="/Index">首页</router-link></li>
<li class="active">论坛</li>
</ul>
</div>
</div>
</div>
</section>
<!-- section close -->
<div style="width:80%;margin-left:10%;margin-top:50px;">
<el-row>
<el-col :span="4" >
<div @click="searchbytid('')" :class="'typeitem ' +(query.tid==''?'typeitemsel':'')">
全部
</div>
</el-col>
<el-col :span="4" v-for="(t,i) in tli">
<div @click="searchbytid(t.id)" :style="'background-image:url('+FILE_URL+t.timg+');background-size:100% 100%;'"
:class="'typeitem ' +(query.tid==t.id?'typeitemsel':'')">
<span> {{t.tname}}</span>
</div>
</el-col>
</el-row>
<br>
<br>
<br>
<!--/content-inner-section-->
<div class="row" >
<div class="col-md-8 ">
<el-row>
<el-col :span="20">
<el-input v-model="query.keywords" placeholder="请输入标题,昵称,内容" type="text"/>
</el-col>
<el-col :span="4">
<el-button icon="el-icon-search" type="success" @click="handleSearch"> 搜索</el-button>
</el-col>
</el-row>
<br>
<div class="zwsj" v-if="tableData.length==0">
<img style="width:50%" src="../../assets/img/notz.png">
<br>
<br>
<br>
</div>
<el-row v-for="(t,i) in tableData" >
<el-col :span="4">
<router-link :to="'/QtForumDetail?id='+t.id" target="_blank" title="w3ls" rel="author"><img :src="FILE_URL+t.faceimg" alt="" class="txx"></router-link>
</el-col>
<el-col :span="20">
<h5 class="hh"><router-link :to="'/QtForumDetail?id='+t.id" target="_blank">{{t.fname}}</router-link></h5>
<p class="hh">{{t.fdesc}}...<router-link :to="'/QtForumDetail?id='+t.id" target="_blank" style="color: blue">详情</router-link></p>
<div class="imgsdiv" v-if="t.imgsli!=null&&t.imgsli.length>0">
<div class="imgsitem" v-for="(t2,i2) in t.imgsli" v-if="i2<4"><router-link :to="'/QtForumDetail?id='+t.id" target="_blank"><img :src="t2"></router-link></div>
</div>
<div class="clearfix"></div>
<div class="agile-post">
<router-link :to="'/QtForumDetail?id='+t.id" target="_blank" style="color: #f40;">{{t.nickname}}</router-link> 于 {{t.cts}} 发布 <router-link :to="'/QtForumDetail?id='+t.id" target="_blank">{{t.commentNum}}
评论</router-link>,{{t.cseeNum}}浏览,{{t.tname}}
<el-button @click="delForum(t)" v-if="lander!=null&&lander.id==t.uid" style="color: red;float:right;" type="text"><i class="el-icon-delete"></i>删除</el-button>
</div>
<hr>
</el-col>
</el-row>
<div class="pagination" v-if="tableData.length>0">
<el-pagination
@size-change="handleSizeChange"
@current-change="handlePageChange"
:current-page="query.pageNo"
:page-sizes="[10,20,50,100, 200, 300, 400,500]"
:page-size="query.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="pageTotal">
</el-pagination>
</div>
<el-card id="fatiediv">
<div slot="header" class="clearfix">
<span>发布帖子</span>
</div>
<div>
<el-form
label-width="70px"
:model="dataForm"
:rules="dataFormRule"
ref="dataForm"
>
<el-form-item label="标题" prop="fname" >
<el-input v-model="dataForm.fname" ></el-input>
</el-form-item>
<el-form-item label="内容" prop="fcont" >
<quill-editor ref="myQuillEditor" v-model="dataForm.fcont" :options="editorOption" />
</el-form-item>
<el-form-item label="交流圈" prop="tid" v-if="query.tid==''">
<el-select filterable v-model="dataForm.tid" >
<el-option v-for="t in tli"
:key="t.id"
:label="t.tname"
:value="t.id"></el-option>
</el-select>
</el-form-item>
</el-form>
<div style="padding-left:100px;">
<el-button v-if="islogin==1" type="primary" @click="saveEdit">确 定 发 布</el-button>
<el-button v-else type="primary" @click="gologin">请登录</el-button>
</div>
</div>
</el-card>
</div>
<div class="col-md-4 ">
<div>
<table style="width: 100%;text-align: center;">
<tr>
<td> <el-button style="width:90%" type="danger" @click="fatie()">发布帖子</el-button></td>
<td>
<el-button @click="manageForum" style="width: 90%" type="success" v-if="islogin==1">管理帖子</el-button>
<el-button @click="needlogin" style="width: 90%" type="success" v-else>管理帖子</el-button>
</td>
</tr>
</table>
</div>
<br>
<br>
<h4 >最新评论</h4>
<table v-for="(t,i) in cList" width="100%" style="border-bottom: 1px solid #f3f3f3;margin-bottom: 10px;margin-top: 10px;">
<tr>
<td width="60px;"> <img :src="FILE_URL+t.faceimg" class="pftx"></td>
<td class="hh">{{t.userNickname}}<small style="float: right;">{{t.cts}}</small><div style="font-size: 12px;color: #666666;" v-html="t.context"></div> </td>
</tr>
</table>
<p class="zwsj" v-if="cList.length==0">
<br>
<img style="width: 50%" src="../../assets/img/nopj.png">
<br>
暂无评论
<br>
</p>
<div class="clearfix"></div>
</div>
<div class="clearfix"></div>
</div>
<!--//content-inner-section-->
</div>
</div>
</template>
<script>
import { quillEditor,Quill} from 'vue-quill-editor'
import {container, ImageExtend, QuillWatch} from 'quill-image-extend-module'
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'
Quill.register('modules/ImageExtend', ImageExtend)
import '../../api/image-paste.min.js';
let CON_ACTION=process.env.VUE_APP_API_ROOT+'/common/upimg?type=forum_detail';
import {
forum_add,
forum_page,
comment_page,
forum_del,
type_list
} from '../../api';
export default {
name: "Index",
components: {
quillEditor,
},
data() {
return {
islogin:0,
cList:[],
lander:{},
FILE_URL : '',
tli: [],
tableData: [],
query: {
tid: '',
keywords: '',
pageNo: 1,
pageSize: 10,
sh:1,
},
pageTotal: 0,
dataForm: {
fname: '',
fcont:'',
tid:'',
},
dataFormRule: {
fname: [{required: true, message: '不能为空', trigger: 'blur'}],
tid: [{required: true, message: '不能为空', trigger: 'blur'}],
},
editorOption: {
modules: {
/* 还有一些其他的模块*/
imagePaste: {
addImageBlob: function (blob, callback) {
var formData = new FormData()
formData.append('file', blob)
// your upload function, get the uploaded image url, add then
let config = {
headers: {
"Content-Type": "multipart/form-data",
"Accept": "*/*"
}
}
// 上传接口
axios.post(CON_ACTION, formData, config).then(res => {
console.log(res);
let imgUrl = res.data.obj // 服务器返回的图片url
callback(imgUrl)
})
}
},
ImageExtend: { // 如果不作设置,即{} 则依然开启复制粘贴功能且以base64插入
loading: true,//可选参数 是否显示上传进度和提示语
name: 'file', // 图片参数名
size: 3, // 可选参数 图片大小,单位为M,1M = 1024kb
action: CON_ACTION, // 服务器地址, 如果action为空,则采用base64插入图片
// response 为一个函数用来获取服务器返回的具体图片地址
// 例如服务器返回{code: 200; data:{ url: 'baidu.com'}}
// 则 return res.data.url
response: res => {
return res.obj;
},
// headers: xhr => {
// 上传图片请求需要携带token的 在xhr.setRequestHeader中设置
// xhr.setRequestHeader(
// "Authorization",
// this.getCookie("username")
// ? this.getCookie("username").token_type +
// this.getCookie("username").access_token
// : "Basic emh4eTp6aHh5"
// );
// }, // 可选参数 设置请求头部
sizeError: () => {
}, // 图片超过大小的回调
start: () => {
}, // 可选参数 自定义开始上传触发事件
end: () => {
}, // 可选参数 自定义上传结束触发的事件,无论成功或者失败
error: () => {
}, // 可选参数 上传失败触发的事件
success: () => {
}, // 可选参数 上传成功触发的事件
// change: (xhr, formData) => {
// xhr.setRequestHeader('myHeader','myValue')
// formData.append('token', 'myToken')
// } // 可选参数 每次选择图片触发,也可用来设置头部,但比headers多了一个参数,可设置formData
},
toolbar: {
container: container,
handlers: {
'image': function () {
QuillWatch.emit(this.quill.id)
}
}
}
},
//主题
theme: "snow",
placeholder: "请输入正文"
},
};
},
methods:{
delForum(t){
// 二次确认删除
this.$confirm("确定要删除吗?", "提示", {
type: "warning",
})
.then(() => {
forum_del({id: t.id}).then((res) => {
if(res.data.status==1){
this.$message.success("删除成功");
this.getPage();
}else{
this.$notify({
title: '提示',
message: res.data.msg,
duration: 0
});
}
})
})
.catch(() => {
this.$message({
type: 'info',
message: '已取消'
});
});
},
gologin(){
this.$router.push('/Login');
},
needlogin(){
this.$message.info('请登录')
},
manageForum(){
this.$router.push('/Forum');
},
fatie(){
window.location='#fatiediv';
},
saveEdit() {
if(this.islogin==0){
this.$message.error('请登录');
return ;
}
if (this.$refs.myQuillEditor.value == ''||this.dataForm.fcont.trim().length==0) {
this.$message.error('请输入内容');
return ;
}
this.dataForm.uid=this.lander.id;
this.$refs.dataForm.validate(valid => {
//表单验证失败
if (!valid) {
//提示语
this.$message.error("请按提示填写表单");
return false;
} else {
this.loading = this.$loading({
lock: true,
text: "发布中,请稍等,,,,",
spinner: "el-icon-loading",
background: "rgba(0, 0, 0, 0.5)",
});
forum_add(this.dataForm).then((res) => {
this.loading.close();
if(res.data.status==1){
this.query.pageNo=1;
this.getPage();
if(res.data.obj!=null&&res.data.obj>0&&this.vobj!=null&&this.vobj.forumNum!=undefined){
this.vobj.forumNum=res.data.obj;
}
this.dataForm.fname='';
this.dataForm.fcont='';
this.$message.success("已发布")
}else{
this.$message.error(res.data.msg)
}
})
}
})
},
gettypeli(){
type_list({ttype:'forum'}).then((res) => {
this.tli = res.data.obj
})
},
getPage() {
forum_page(this.query).then((res) => {
this.tableData = res.data.obj.list
this.pageTotal = res.data.obj.total//该页总共多少条记录
})
},
handleSizeChange(val){
this.query.pageNo = 1;
this.query.pageSize=val;
this.getPage();
},
// 查询操作
handleSearch() {
this.query.pageNo = 1;
this.getPage();
},
// 分页导航
handlePageChange(val) {
this.query.pageNo = val;
this.getPage();
},
getCPage(){
comment_page({pageNo:1,pageSize:30,ctype:'forum',tid:this.query.tid}).then((res) => {
if(res.data.status==1){
this.cList=res.data.obj.list;
}
})
},
searchbytid(tid){
this.query.tid=tid;
this.dataForm.tid=tid;
this.query.pageNo=1;
this.query.keywords='';
this.getPage();
this.getCPage();
}
},
mounted() {
this.FILE_URL=process.env.VUE_APP_API_ROOT;
try{
this.lander=JSON.parse(localStorage.loginUser);
this.islogin=1;
}catch (e) {
this.islogin=0;
}
this.gettypeli();
this.getPage();
this.getCPage();
},
watch:{
$route:function(value){
}
}
}
</script>
<style scoped>
.sel{color: darkgreen;font-weight: bolder;}
.imgsdiv{height: 120px;}
.imgsdiv .imgsitem{width: 100px;
overflow: hidden;
height: 100px;float: left;margin-right: 10px;background: #dddddd;text-align: center;border-radius: 5px;}
.imgsdiv .imgsitem img{ width: 100%; height: 100%;}
.txx{width: 90px;height: 90px;border-radius: 50%;}
.pftx{width: 50px;height: 50px;border-radius: 50%;margin-right: 10px; }
.agile-post{font-size: 12px;color: #666;}
.zwsj{text-align: center;}
.typeitem{
width:150px;
height:150px;
line-height:150px;
background-color: #7df374;
border-radius: 50%;
border:3px dotted #ddd;
margin:5px auto ;
text-align: center;
cursor: pointer;
}
.typeitem span{ background-color: rgba(0,0,0,0.5);color: #fff; padding: 10px;font-size: 13px;}
.typeitemsel{
border:3px dotted darkgreen;
}
.typeitemsel span{
background-color: rgba(0,0,0,0.8);color: #fff; padding: 10px;
}
</style>
详情
<template>
<div>
<!-- section begin -->
<section id="de-subheader" class="mt-sm-60 pt20 pb20 bg-gradient-45-deg text-light">
<div class="container relative z-index-1000">
<div class="row align-items-center">
<div class="col-lg-6">
<h3 class="mb-0"></h3>
</div>
<div class="col-lg-6 text-lg-end">
<ul class="crumb">
<li><router-link to="/Index">首页</router-link></li>
<li class="active">{{obj.fname}}</li>
</ul>
</div>
</div>
</div>
</section>
<!-- section close -->
<div class="container" >
<el-row>
<el-col :span="16">
<table style="width: 100%;margin-top: 30px;margin-bottom: 30px;">
<tr>
<td valign="top" class="td td1 hh">
<img class="touxx" :src="FILE_URL+obj.faceimg">
<br>
<el-tag size="mini" type="danger">作者</el-tag>
<br>
<p>{{obj.nickname}}</p>
</td>
<td class="td hh">
<h1>{{obj.fname}}</h1>
<br>
<div v-html="obj.fcont"></div>
<div>
<hr>
<div class="row actions">
<div class="col-sm-6">发布时间 {{obj.cts}}</div>
<div class="col-sm-2">浏览 {{obj.cseeNum}}</div>
<div class="col-sm-2">评论 {{obj.commentNum}}</div>
<div class="col-sm-2"><el-button @click="delForum()" v-if="lander!=null&&lander.id==obj.uid" style="color: red;" type="text"><i class="el-icon-delete"></i>删除</el-button></div>
</div>
</div>
</td>
</tr>
<tr v-for="(t,i) in tableData">
<td valign="top" class="td td1 hh">
<img class="touxx" :src="FILE_URL+t.faceimg">
<br>
<el-tag v-if="t.uid==obj.uid" size="mini" type="danger">作者</el-tag>
<br>
<p>{{t.userNickname}}</p>
</td>
<td class="td hh">
<div v-html="t.context"></div>
<br>
<div>
<hr>
<div class="row actions">
<div class="col-sm-6">评论时间 {{t.cts}}</div>
<div class="col-sm-2">回复 {{t.hfli!=null?t.hfli.length:'0'}}</div>
<div class="col-sm-2"><el-button @click="replyC(t,i)" v-if="lander!=null " style="color: green;" type="text"><i class="el-icon-news"></i>回复</el-button></div>
<div class="col-sm-2"><el-button @click="delC(t,i)" v-if="lander!=null&&lander.id==t.uid" style="color: red;" type="text"><i class="el-icon-delete"></i>删除</el-button></div>
</div>
</div>
<div v-if="t.hfli!=null&&t.hfli.length>0">
<div v-for="(t2,i2) in t.hfli" v-if="i2<5" class="ritem">
<p><img class="xtxx" :src="FILE_URL+t2.faceimg">
<span style="color: blue">{{t2.userNickname}}</span>
<el-tag v-if="t2.uid==obj.uid" size="mini" type="danger">作者</el-tag>
回复
<img class="xtxx" :src="FILE_URL+t2.uhFaceimg">
<span style="color: blue">{{t2.uhUserNickname}}</span>
<el-tag v-if="t2.hfUid==obj.uid" size="mini" type="danger">作者</el-tag>
</p>
<div style="padding: 20px;" v-html="t2.context"></div>
<hr>
<div class="row actions">
<div class="col-sm-8">回复时间 {{t2.cts}}</div>
<div class="col-sm-2"><el-button @click="replyC(t,i)" v-if="lander!=null&&lander.id==t2.uid" style="color: green;" type="text"><i class="el-icon-news"></i>回复</el-button></div>
<div class="col-sm-2"><el-button @click="delC(t2,i2)" v-if="lander!=null&&lander.id==t2.uid" style="color: red;" type="text"><i class="el-icon-delete"></i>删除</el-button></div>
</div>
</div>
<div v-for="(t2,i2) in t.hfli" v-if="i2>=5&&t.showsy" class="ritem">
<p><img class="xtxx" :src="FILE_URL+t2.faceimg">
<span style="color: blue">{{t2.userNickname}}</span>
<el-tag v-if="t2.uid==obj.uid" size="mini" type="danger">作者</el-tag>
回复
<img class="xtxx" :src="FILE_URL+t2.uhFaceimg">
<span style="color: blue">{{t2.uhUserNickname}}</span>
<el-tag v-if="t2.hfUid==obj.uid" size="mini" type="danger">作者</el-tag>
</p>
<div style="padding: 20px;" v-html="t2.context"></div>
<hr>
<div class="row actions">
<div class="col-sm-8">回复时间 {{t2.cts}}</div>
<div class="col-sm-2"><el-button @click="replyC(t,i)" v-if="lander!=null&&lander.id==t2.uid" style="color: green;" type="text"><i class="el-icon-news"></i>回复</el-button></div>
<div class="col-sm-2"><el-button @click="delC(t2,i2)" v-if="lander!=null&&lander.id==t2.uid" style="color: red;" type="text"><i class="el-icon-delete"></i>删除</el-button></div>
</div>
</div>
<div @click="t.showsy=!t.showsy" class="zwsj" v-if="t.hfli.length-5>0">{{t.showsy?'隐藏':'查看'}}剩余{{t.hfli.length-5}}条数据</div>
</div>
</td>
</tr>
</table>
<div class="pagination" v-if="tableData.length>0">
<el-pagination
@size-change="handleSizeChange"
@current-change="handlePageChange"
:current-page="query.pageNo"
:page-sizes="[10,20,50,100, 200, 300, 400,500]"
:page-size="query.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="pageTotal">
</el-pagination>
</div>
<div class="all-comments-info">
<h5>发布评论</h5>
<div class="agile-info-wthree-box">
<div class="col-md-12 form-info">
<quill-editor ref="myQuillEditor" v-model="context" :options="editorOption" />
<br>
<el-button @click="saveC()" type="success" >发布评论</el-button>
</div>
<div class="clearfix"> </div>
<br>
<br>
<br>
</div>
</div>
</el-col>
<el-col :span="8" >
<br>
<div style="border: 1px solid #ddd;padding: 10px;margin-left: 10px;">
<h4 >最新帖子</h4>
<br>
<table v-for="(t,i) in lastforumli" width="100%" style="border-bottom: 1px solid #f3f3f3;margin-bottom: 10px;margin-top: 10px;">
<tr>
<td width="60px;"> <img :src="FILE_URL+t.faceimg" class="pftx"></td>
<td class="hh">{{t.nickname}}<small style="float: right;">{{t.cts}}</small><div style="font-size: 12px;color: #666666;" >
<router-link :to="'/QtForumDetail?id='+t.id"> {{t.fname}}</router-link>
</div> </td>
</tr>
</table>
<p class="zwsj" v-if="lastforumli.length==0">
<br>
<img style="width: 50%" src="../../assets/img/nopj.png">
<br>
暂无评论
<br>
</p>
</div>
</el-col>
</el-row>
<!-- 上传图片弹出框 -->
<el-dialog title="回复" :visible.sync="replayVisible" >
<quill-editor ref="myQuillEditor2" v-model="replay_context" :options="editorOption" />
<el-button @click="saveR()" type="primary" >确定回复</el-button>
</el-dialog>
</div>
</div>
</template>
<script>
import { quillEditor,Quill} from 'vue-quill-editor'
import {container, ImageExtend, QuillWatch} from 'quill-image-extend-module'
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'
Quill.register('modules/ImageExtend', ImageExtend)
import '../../api/image-paste.min.js';
let CON_ACTION=process.env.VUE_APP_API_ROOT+'/common/upimg?type=comment_detail';
import {
forum_detail
, comment_page
, comment_save
, forum_del
, comment_del, forum_page
} from '../../api';
export default {
name: "Detail",
components: {
quillEditor,
},
data() {
return {
lastforumli:[],
islogin:0,
replay_context:'',
replayObj:{},
replayVisible:false,
iscollect:false,
context:'',
FILE_URL:'',
lander:{},
id:'',
obj:{},
query: {
fid: '',
pageNo: 1,
pageSize: 10,
},
tableData: [],
pageTotal: 0,
editorOption: {
modules: {
/* 还有一些其他的模块*/
imagePaste: {
addImageBlob: function (blob, callback) {
var formData = new FormData()
formData.append('file', blob)
// your upload function, get the uploaded image url, add then
let config = {
headers: {
"Content-Type": "multipart/form-data",
"Accept": "*/*"
}
}
// 上传接口
axios.post(CON_ACTION, formData, config).then(res => {
console.log(res);
let imgUrl = res.data.obj // 服务器返回的图片url
callback(imgUrl)
})
}
},
ImageExtend: { // 如果不作设置,即{} 则依然开启复制粘贴功能且以base64插入
loading: true,//可选参数 是否显示上传进度和提示语
name: 'file', // 图片参数名
size: 3, // 可选参数 图片大小,单位为M,1M = 1024kb
action: CON_ACTION, // 服务器地址, 如果action为空,则采用base64插入图片
// response 为一个函数用来获取服务器返回的具体图片地址
// 例如服务器返回{code: 200; data:{ url: 'baidu.com'}}
// 则 return res.data.url
response: res => {
return res.obj;
},
// headers: xhr => {
// 上传图片请求需要携带token的 在xhr.setRequestHeader中设置
// xhr.setRequestHeader(
// "Authorization",
// this.getCookie("username")
// ? this.getCookie("username").token_type +
// this.getCookie("username").access_token
// : "Basic emh4eTp6aHh5"
// );
// }, // 可选参数 设置请求头部
sizeError: () => {
}, // 图片超过大小的回调
start: () => {
}, // 可选参数 自定义开始上传触发事件
end: () => {
}, // 可选参数 自定义上传结束触发的事件,无论成功或者失败
error: () => {
}, // 可选参数 上传失败触发的事件
success: () => {
}, // 可选参数 上传成功触发的事件
// change: (xhr, formData) => {
// xhr.setRequestHeader('myHeader','myValue')
// formData.append('token', 'myToken')
// } // 可选参数 每次选择图片触发,也可用来设置头部,但比headers多了一个参数,可设置formData
},
toolbar: {
container: container,
handlers: {
'image': function () {
QuillWatch.emit(this.quill.id)
}
}
}
},
//主题
theme: "snow",
placeholder: "请输入正文"
},
};
},
methods:{
// 分页导航
handlePageChange(val) {
this.query.pageNo = val;
this.getPage();
},
handleSizeChange(val){
this.query.pageNo = 1;
this.query.pageSize=val;
this.getPage();
},
replyC(t,i){
this.replayVisible=true;
this.replayObj=t;
this.replayObj.index=i;
},
saveR() {
if(this.islogin==0){
this.$message.info("请登录");return;
}
if (this.$refs.myQuillEditor2.value == ''||this.replay_context.trim().length==0) {
this.$message.error('请输入内容');
return ;
}
comment_save({uid:this.lander.id,fid:this.id,context: this.replay_context,hfId:this.replayObj.id,ctype:'forum',hfUid:this.replayObj.uid }).then((res) => {
if(res.data.status==1){
this.replay_context='';
this.$message.success("回复成功");
this.replayVisible=false;
this.getPage();
this.obj.commentNum=res.data.obj;
}else{
this.$message.info(res.data.msg);
}
})
},
saveC() {
if(this.islogin==0){
this.$message.info("请登录");return;
}
if (this.$refs.myQuillEditor.value == ''||this.context.trim().length==0) {
this.$message.error('请输入内容');
return ;
}
comment_save({uid:this.lander.id,fid:this.id,context: this.context,hfId:0,ctype:'forum' }).then((res) => {
if(res.data.status==1){
this.context='';
this.$message.success("评论成功");
this.query.pageNo=1;
this.getPage();
this.obj.commentNum=res.data.obj;
}else{
this.$message.info(res.data.msg);
}
})
},
getPage() {
this.query.fid=this.id;
this.query.hfId=0;
comment_page(this.query).then((res) => {
this.tableData = [];
this.pageTotal = res.data.obj.total//该页总共多少条记录
for(let i=0;i<res.data.obj.list.length;i++){
res.data.obj.list[i].showsy=false;
this.tableData.push(res.data.obj.list[i])
}
})
},
getObj(){
forum_detail({id:this.id}).then((res) => {
if(res.data.status==1){
this.obj = res.data.obj ;
this.getlastforum();
}else{
localStorage.systip=res.data.msg;
this.$router.replace("/Tip")
}
})
},
delForum(){
// 二次确认删除
this.$confirm("确定要删除吗?", "提示", {
type: "warning",
})
.then(() => {
forum_del({id:this.obj.id}).then((res) => {
if(res.data.status==1){
this.$message.success("删除成功");
localStorage.systip="该数据已删除!"
this.$router.replace("/Tip")
}else{
this.$notify({
title: '提示',
message: res.data.msg,
duration: 0
});
}
})
})
.catch(() => {
this.$message({
type: 'info',
message: '已取消'
});
});
},
delC(t,index){
// 二次确认删除
this.$confirm("确定要删除吗?", "提示", {
type: "warning",
})
.then(() => {
comment_del({id:t.id}).then((res) => {
if(res.data.status==1){
this.$message.success("删除成功");
this.obj.commentNum=res.data.obj;
this.getPage();
}else{
this.$notify({
title: '提示',
message: res.data.msg,
duration: 0
});
}
})
})
.catch(() => {
this.$message({
type: 'info',
message: '已取消'
});
});
},
getlastforum(){
forum_page( {tid:this.obj.tid,sh:1} ).then((res)=>{
let data=res.data;
this.lastforumli=data.obj.list;
})
},
},
mounted() {
this.FILE_URL=process.env.VUE_APP_API_ROOT;
this.id = this.$route.query.id;
localStorage.tol='/QtForumDetail?id='+this.id;
try{
this.lander=JSON.parse(localStorage.loginUser);
this.islogin=1;
}catch (e) {
this.islogin=0;
}
this.getObj();
this.getPage();
},
watch:{
$route:function(value){
this.id =value.query.id;
this.getObj();
this.getPage();
}
}
}
</script>
<style scoped>
.ritem{background: #f3f3f3;margin-bottom: 5px;padding: 10px;}
.xtxx{width: 20px;height:20px;border-radius: 50%;margin-right: 5px;}
.touxx{width: 70px;height: 70px;border-radius: 50%}
.td{border: 1px solid #dddddd;padding: 20px;}
.center{text-align: center;}
.td1{text-align: center;background: #f3f3f3;width: 150px ;}
.actions{width: 100%;font-size: 12px;color: #666666;line-height: 50px;}
.zwsj{text-align: center;}
.pftx{width: 50px;height: 50px;border-radius: 50%;margin-right: 10px; }
</style>
好博客就要一起分享哦!分享海报
此处可发布评论
评论(0)展开评论
暂无评论,快来写一下吧
展开评论
他的专栏
他感兴趣的技术










java
vue
springboot
Mysql
ssm
小程序
uniapp
js和jquery