实现效果
文章上传页面
文章列表页面
可以通过文章ID、文章标题模糊查询文章
查看文章详细信息
点击文章列表即可以查看文章详细信息
实现过程
前端
文章上传页面
<template>
<div>
<div class="containDiv">
<el-row :gutter="5">
<el-col :span="4">
<span class="articleText">文章标题</span>
</el-col>
<el-col :span="20">
<el-input v-model="articleTitle"></el-input>
</el-col>
</el-row>
<el-row :gutter="5">
<el-col :span="4">
<span class="articleText">文章摘要</span>
</el-col>
<el-col :span="20">
<el-input type="textarea" v-model="articleAbstract" :rows="4" @input="showAbstartTip"></el-input>
</el-col>
</el-row>
<vue-editor
id="editor"
useCustomImageHandler
@image-added="handleImageAdded"
@text-change="showContentTip"
v-model="articleContent"
></vue-editor>
</div>
<div class="containDiv">
<el-button type="success" plain id="uploadArticle" @click="uploadArticle">
上传
<i class="el-icon-upload el-icon--right"></i>
</el-button>
</div>
<div id="preview">预览效果</div>
<div id="previewArticle" class="containDiv">
<div v-html="articleTitle" class="previewArticleTitle"></div>
<p class="abstract" v-show="isShowAbstract">摘要</p>
<p v-html="articleAbstract" class="previewArticleAbstract"></p>
<p class="abstract" v-show="isShowContent">文章内容</p>
<p v-html="articleContent" class="previewArticleContent"></p>
</div>
</div>
</template>
<script>
import { VueEditor } from "vue2-editor";
let baseURL = "http://localhost:8888";
export default {
name: "uploadArticle",
components: { VueEditor },
data() {
return {
articleContent: "",
articleTitle: "",
articleAbstract: "",
isShowAbstract: false,
isShowContent: false
};
},
methods: {
showAbstartTip() {
this.isShowAbstract = true;
},
showContentTip() {
this.isShowContent = true;
},
uploadArticle() {
if(this.articleTitle == null || this.articleTitle.trim() == ""){
this.$notify.error({
title: '错误',
message: '请输入文章题目'
});
return;
}
this.$http({
url: baseURL + "/article/uploadArticle",
method: "post",
params: {
articleContent: this.articleContent,
articleTitle: this.articleTitle,
articleAbstract: this.articleAbstract
}
})
.then(response => {
if (response.data == 1) {
this.$notify({
title: "上传成功",
message: "文章上传成功",
type: "success"
});
this.articleTitle = "";
this.articleAbstract = "";
this.articleContent = "";
this.isShowContent = false;
this.isShowAbstract = false;
} else {
this.$notify.error({
title: "上传失败",
message: "文章上传失败!"
});
}
})
.catch(error => {
console.log(error);
});
},
handleImageAdded(file, Editor, cursorLocation) {
let formData = new FormData();
formData.append("file", file);
this.axios({
url: baseURL + "/article/uploadImage",
method: "POST",
data: formData
})
.then(response => {
let url = response.data;
Editor.insertEmbed(cursorLocation, "image", url);
})
.catch(error => {
console.log(error);
});
}
}
};
</script>
<style>
.containDiv {
position: relative;
width: 75%;
margin: 0 auto;
}
#editor {
height: 500px;
width: 100%;
margin: 0 auto;
}
#uploadArticle {
position: relative;
top: 20px;
left: 45%;
}
#preview {
position: relative;
right: 35%;
top: 20px;
bottom: 20px;
}
#previewArticle {
margin-top: 40px;
border: 1px solid #f0f0f0;
}
.el-row {
margin-bottom: 20px;
}
.el-col {
border-radius: 4px;
}
.articleText {
position: relative;
right: 20%;
}
.previewArticleTitle {
text-align: center;
font-size: 25px;
font-weight: bold;
margin-top: 10px;
margin-bottom: 20px;
}
.abstract {
text-align: left;
font-weight: bold;
font-weight: 20px;
margin-left: 20px;
}
.previewArticleAbstract {
margin-left: 20px;
margin-right: 20px;
text-align: justify;
font-size: 16px;
text-indent: 2em;
color: #4f4f4f;
margin-bottom: 40px;
}
.previewArticleContent {
margin-left: 20px;
margin-right: 20px;
text-align: justify;
text-indent: 2em;
}
</style>
文章列表页面
<template>
<div class="containDiv">
<el-form :ref="articleSearchConditon" :model="articleSearchConditon">
<el-row>
<el-col class="colWidth">
<el-form-item label="文章ID" prop="articleId">
<el-input v-model="articleSearchConditon.articleId" class="elWidth" />
</el-form-item>
</el-col>
<el-col class="colWidth">
<el-form-item label="文章标题" prop="articleTitle">
<el-input v-model="articleSearchConditon.articleTitle" class="elWidth"></el-input>
</el-form-item>
</el-col>
<el-col :span="6" id="colButton">
<el-form-item>
<el-button type="success" plain @click="getArticleList">查询</el-button>
<el-button @click="doClear">清空</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
<ul id="outerUL">
<ul
v-for="article in articleList"
v-bind:key="article.articleId"
@click="downloadArticleById(article.articleId, article.articleTitle, article.articleAbstract)"
>
<li class="articleTitleLi">{{ article.articleTitle }}</li>
<span class="articleCreateDateLi">
<i class="el-icon-timer">{{ article.articleCreateDate }}</i>
</span>
<li class="articleAbstractLi">
<a>{{ article.articleAbstract }}</a>
</li>
</ul>
</ul>
<el-pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
:current-page="pageIndex"
:page-sizes="[5, 10, 20, 50, 100]"
:page-size="pageSize"
:total="totalPage"
layout="total, sizes, prev, pager, next, jumper"
/>
<div>
<div ref="viewArticleContent" class="viewArticleById"></div>
</div>
</div>
</template>
<script>
let baseURL = "http://localhost:8888";
export default {
name: "articleList",
data() {
return {
articleSearchConditon: {
articleId: "",
articleTitle: ""
},
articleList: [],
pageIndex: 1,
pageSize: 5,
totalPage: 0
};
},
mounted() {
this.getArticleList();
},
methods: {
getArticleList() {
this.$http({
url: baseURL + "/article/getArticleList",
method: "post",
params: {
articleId: this.articleSearchConditon.articleId,
articleTitle: this.articleSearchConditon.articleTitle,
pageIndex: this.pageIndex,
pageSize: this.pageSize
}
})
.then(response => {
this.articleList = response.data.records;
this.totalPage = response.data.total;
})
.catch(error => {
console.log(error);
});
},
downloadArticleById(articleId, articleTitle, articleAbstract) {
this.$http({
url: baseURL + "/article/downloadArticleById",
method: "post",
params: {
articleId: articleId
}
})
.then(response => {
let title = "<h2>" + articleTitle + "</h2>";
let abstract =
"<p style = 'text-align: left; font-weight: bold; font-weight: 20px; margin-left: 20px;'>摘要 </p>" +
"<p style = 'margin-left: 10px; margin-right: 20px; text-align: justify; font-size: 14px; text-indent: 2em; color: #4f4f4f; margin-bottom: 20px;'>" +
articleAbstract +
"</p>";
let content =
"<p style = 'margin-left: 20px; text-align: justify; font-weight: bold;' > 文章内容</p>" +
"<div style = 'margin-left: 20px; text-align: justify; text-indent: 2em; margin-right: 20px;'>" +
response.data.articleContent +
"</div>";
this.$refs.viewArticleContent.innerHTML = title + abstract + content;
})
.catch(error => {
console.log(error);
});
},
doClear() {
this.articleSearchConditon = {};
},
sizeChangeHandle(val) {
console.log(`每页 ${val} 条`);
this.pageIndex = 1;
this.pageSize = val;
this.getArticleList();
},
currentChangeHandle(val) {
console.log(`当前页: ${val}`);
this.pageIndex = val;
this.getArticleList();
}
}
};
</script>
<style scoped>
.el-col .el-row .el-form-item .el-form .p .div {
margin: 0;
padding: 0;
border: 0;
}
.colWidth {
width: 458px;
}
.elWidth {
width: 340px;
}
#colButton {
width: 200px;
}
.containDiv {
position: relative;
width: 75%;
margin: 0 auto;
}
#outerUL {
position: relative;
margin: 10px auto;
padding: 0;
}
ul {
list-style: none;
border-top: 1px solid #f0f0f0;
}
li {
text-align: left;
margin-top: 20px;
margin-bottom: 20px;
}
.articleTitleLi {
font-weight: bold;
font-size: 18px;
}
.articleAbstractLi {
width: 85%;
}
.articleCreateDateLi {
font-size: 13px;
color: #7c7a7a;
float: right;
}
.viewArticleById {
margin-top: 40px;
border: 1px solid #f0f0f0;
}
a:hover {
color: rgb(131, 130, 126);
}
</style>
后端
Entity
public class Article {
// 文章ID
private String articleId;
// 文章标题
private String articleTitle;
// 文章副标题
private String articleAbstract;
// 文章内容
private String articleContent;
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
private Date articleCreateDate;
//get set方法
}
Mapper
@Mapper
public interface ArticleMapper extends BaseMapper<Article> {
int uploadArticle(@Param("params") Map<String, Object> params);
Article downloadArticleById(@Param("params") Map<String, String> params);
List<Article> getArticleList(IPage page, @Param("params") Map<String, Object> params);
}
Mapper.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.base.modules.article.mapper.ArticleMapper">
<insert id="uploadArticle">
insert into editor_article(article_title, article_abstract, article_content) values(
<choose>
<when test="params.articleTitle != null">
#{params.articleTitle},
</when>
<otherwise>
'',
</otherwise>
</choose>
<choose>
<when test="params.articleAbstract != null">
#{params.articleAbstract},
</when>
<otherwise>
'',
</otherwise>
</choose>
<choose>
<when test="params.articleContent != null">
#{params.articleContent}
</when>
<otherwise>
''
</otherwise>
</choose>
)
</insert>
<select id="getArticleList" resultType="com.base.modules.article.entity.Article">
select article_id, article_title, article_abstract, to_date(to_char(article_create_date, 'yyyy-MM-dd'), 'yyyy-MM-dd') as article_create_date from editor_article where del_flag = 1
<if test="params.articleId == null || params.articleId.trim() != ''">
and article_id like CONCAT(CONCAT('%',#{params.articleId}),'%')
</if>
<if test="params.articleTitle == null || params.articleTitle.trim() != ''">
and article_title like CONCAT(CONCAT('%',#{params.articleTitle}),'%')
</if>
</select>
<select id="downloadArticleById" resultType="com.base.modules.article.entity.Article" resultMap="articleResult">
select article_content from editor_article where del_flag = 1
<if test="params.articleId == null || params.articleId.trim() != ''">
and article_id = #{params.articleId}
</if>
</select>
<resultMap id="articleResult" type="com.base.modules.article.entity.Article">
<result property="articleContent" column="article_content" javaType="java.lang.String" jdbcType="CLOB"></result>
</resultMap>
</mapper>
Service
public interface ArticleService extends IService<Article> {
int uploadArticle(Map<String, Object> params);
Page getArticleList(Map<String, Object> params);
Article downloadArticleById(Map<String, String> params);
String uploadImage(MultipartFile uploadFile, HttpServletRequest request);
}
ServiceImpl
@Service
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article> implements ArticleService {
@Value("${upload.path}")
private String imagesPath;
// 文件的真实路径
@Value("${file.uploadFolder}")
private String realBasePath;
@Value("${file.accessPath}")
private String accessPath;
@Override
public int uploadArticle(Map<String, Object> params){
return baseMapper.uploadArticle(params);
}
@Override
public Page getArticleList(Map<String, Object> params){
Page<Article> page = new Query<Article>(params).getPage();
List<Article> articleList = baseMapper.getArticleList(page, params);
return page.setRecords(articleList);
}
@Override
public Article downloadArticleById(Map<String, String> params){
return baseMapper.downloadArticleById(params);
}
@Override
public String uploadImage(MultipartFile uploadFile, HttpServletRequest request) {
Date todayDate = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
String today = dateFormat.format(todayDate);
// 域名访问的相对路径(通过浏览器访问的链接-虚拟路径)
String saveToPath = accessPath + today + "/";
// 真实路径,实际储存的路径
String realPath = realBasePath + today + "/";
String fileName = uploadFile.getOriginalFilename();
String suffix = fileName.substring(fileName.lastIndexOf(".")).toLowerCase();
if (suffix.equals(".jpg") || suffix.equals(".jpeg") || suffix.equals(".png")) {
fileName = UpImagesUtils.getPhotoName("img", suffix);
// 储存文件的物理路径,使用本地路径储存
String filepath = realPath + fileName;
File targetFile = new File(filepath);
if (!targetFile.getParentFile().exists()) {
targetFile.getParentFile().mkdirs();
}
// 保存
try {
uploadFile.transferTo(targetFile);
} catch (Exception e) {
e.printStackTrace();
}
}
String returnUrl = request.getScheme() + "://"
+ request.getServerName() + ":" + request.getServerPort()
+ request.getContextPath() + saveToPath + fileName;
return returnUrl;
}
}
Controller
@RestController
@RequestMapping("/article")
public class ArticleController {
@Autowired
private ArticleService articleService;
@RequestMapping("/uploadArticle")
public int uploadArticle(@RequestParam Map<String, Object> params){
return articleService.uploadArticle(params);
}
@RequestMapping("/getArticleList")
public Page getArticleList(@RequestParam Map<String, Object> params){
System.out.println(params);
return articleService.getArticleList(params);
}
@RequestMapping("/downloadArticleById")
public Article downloadArticleById(@RequestParam Map<String, String> params){
return articleService.downloadArticleById(params);
}
@RequestMapping("/uploadImage")
public String uploadImage(@RequestParam(value = "file") MultipartFile uploadFile, HttpServletRequest request) {
return articleService.uploadImage(uploadFile, request);
}
}
yml文件中设置图片地址映射相关信息
# 上传的服务器上的映射文件夹
file.accessPath:
/upload/images/
#静态资源对外暴露的访问路径
file.staticAccessPath:
/upload/images/**
#文件上传目录
file.uploadFolder:
D:\\ProjectFile\\YSDMFile\\upload\\images\\
数据库端
create table EDITOR_ARTICLE
(
article_id VARCHAR2(64) default sys_guid() not null,
article_title VARCHAR2(1000),
article_abstract VARCHAR2(3000),
article_content CLOB,
article_create_date DATE default sysdate,
article_update_date DATE,
del_flag VARCHAR2(10) default 1
)