Vue2-Editor+SpringBoot+Oracle实现上传文章

9 篇文章 0 订阅
1 篇文章 0 订阅

实现效果

文章上传页面

上传图片页面

文章列表页面

可以通过文章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
)
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值