概述
数据库期末项目开发心得
文章目录
- 数据库期末项目开发心得
- 1、架构的实践案例
- **(1)[(23条消息) Vue + Spring Boot 项目实战(一):项目简介_Evan 的博客-CSDN博客_vue实战](https://learner.blog.csdn.net/article/details/88925013)**
- **(2)**[(23条消息) Vue + Spring Boot 项目实战(二):使用 CLI 搭建 Vue.js 项目_Evan 的博客-CSDN博客](https://learner.blog.csdn.net/article/details/88926242)
- **(3)**[(23条消息) Vue + Spring Boot 项目实战(三):前后端结合测试(登录页面开发)_Evan 的博客-CSDN博客_vue前端开发实战](https://learner.blog.csdn.net/article/details/88955387)
- **(4)[(25条消息) Vue + Spring Boot 项目实战(四):数据库的引入_Evan-Nightly的博客-CSDN博客](https://learner.blog.csdn.net/article/details/89294300)**
- (5)[(25条消息) Vue + Spring Boot 项目实战(五):使用 Element 辅助前端开发_Evan-Nightly的博客-CSDN博客](https://learner.blog.csdn.net/article/details/89298717)
- (6)[(25条消息) Vue + Spring Boot 项目实战(六):前端路由与登录拦截器_Evan-Nightly的博客-CSDN博客](https://learner.blog.csdn.net/article/details/89422585)
- (8)[(25条消息) Vue + Spring Boot 项目实战(八):数据库设计与增删改查_Evan-Nightly的博客-CSDN博客](https://learner.blog.csdn.net/article/details/92413933)
- (9)[(25条消息) Vue + Spring Boot 项目实战(九):核心功能的前端实现_Evan-Nightly的博客-CSDN博客](https://learner.blog.csdn.net/article/details/95310666)
- 2、项目的基本框架
- 3、项目技术栈
- 前端
- 1、vue:
- 2、vuex:(保存登录状态)
- 3、element学习参考:(编写开发)
- 4、axios学习参考:
- 5、mockjs:拦截ajax请求生成数据用来调试前端:
- 6、qs:
- 7、echarts:
- 后端:
- 其他知识的一些学习:
- 1、jwt:
- 2、http
- 4、技术细节
- 1、前端脚手架安装
- 2、前端基本配置
- 3、注册页面的编写,mock调试的过程
- 4、验证码(参考网上的代码)
- 5.导航栏
- 6、用户基本信息的修改
- 7、echarts引入(实验室)
- 8、论文库首页
- 1、提交新的论文
- 2、下拉表单子组件
- 3、面包屑导航栏
- 4、论文首页
- 5、论文内容
1、架构的实践案例
(1)(23条消息) Vue + Spring Boot 项目实战(一):项目简介_Evan 的博客-CSDN博客_vue实战
开发一个线上图书馆的示例
前端用vue+ElementUI,后端用spring框架
(2)(23条消息) Vue + Spring Boot 项目实战(二):使用 CLI 搭建 Vue.js 项目_Evan 的博客-CSDN博客
Vue开发环境的安装
(3)(23条消息) Vue + Spring Boot 项目实战(三):前后端结合测试(登录页面开发)_Evan 的博客-CSDN博客_vue前端开发实战
前后端结合测试:后端采用springboot进行开发
*关于正向代理和反向代理:原博客对反向代理作出了详细的解释和说明:
在开发的时候,前端用前端的服务器(Nginx),后端用后端的服务器(Tomcat),当我开发前端内容的时候,可以把前端的请求通过前端服务器转发给后端(称为反向代理),这样就能实时观察结果,并且不需要知道后端怎么实现,而只需要知道接口提供的功能,两边的开发人员(两个我)就可以各司其职啦。
附上一篇详细解释的博客:[终于有人把正向代理和反向代理解释的明明白白了! - 云+社区 - 腾讯云 (tencent.com)](https://cloud.tencent.com/developer/article/1418457#:~:text=2、 正向代理一般是客户端架设的 ,比如在自己的机器上安装一个代理软件。. 而 反向代理一般是服务器架设的 ,比如在自己的机器集群中部署一个反向代理服务器。. 3、 正向代理中,服务器不知道真正的客户端到底是谁,而在 反向代理中,客户端不知道真正的服务器是谁 ,以为自己访问的就是真实的服务器。. 4、正向代理和反向代理的作用和目的不同。. 正向代理主要是用来解决访问限制问题。. 而反向代理则是提供负载均衡、安全防护等作用。. 二者均能提高访问速度。. PS:本文的特殊形式只是为了更加通俗易懂的讲解知识。.)
(4)(25条消息) Vue + Spring Boot 项目实战(四):数据库的引入_Evan-Nightly的博客-CSDN博客
(a)数据库:ENGINE=InnoDB(默认的引擎就是InnoDB但是为了规范话在建表查询的同时声明了这个引擎)
AUTO_INCREMENT=2,表示自增的大小,自增会每次增加2
(b)什么是pojo?:POJO,即"Plain Old Java Object"。我们可以认为POJO是一个"纯粹"的Java对象,而什么是"纯粹"的Java对象?就是以Java语言规范为基础设计的对象就是一个POJO。从而进一步我们可以理解POJO是指没有实现相关第三方接口、继承相关的第三方类的Java对象,即非侵入的Java对象。
原文连接https://blog.csdn.net/weixin_46015825/article/details/113798732
(c)注解(25条消息) Spring常用注解_一筒君的博客-CSDN博客_spring常用注解
@Entity//实体
@Table(name="")//表
@
(5)(25条消息) Vue + Spring Boot 项目实战(五):使用 Element 辅助前端开发_Evan-Nightly的博客-CSDN博客
Element_UI的官网: http://element-cn.eleme.io/#/zh-CN
(6)(25条消息) Vue + Spring Boot 项目实战(六):前端路由与登录拦截器_Evan-Nightly的博客-CSDN博客
前端路由的 hash 模式与 history 模式
history 模式下后端错误页面的配置
登录拦截的实现
(7)
(8)(25条消息) Vue + Spring Boot 项目实战(八):数据库设计与增删改查_Evan-Nightly的博客-CSDN博客
(a)建表的完整数据:https://github.com/Antabot/White-Jotter/tree/master/wj/src/main/resources
(b)
/*Book中*/
@ManyToOne
@JoinColumn(name="cid")
private Category category;
/*Dao中的查询语句之所以可以根据category查询来源于上述注解的作用*/
List<Book> findAllByCategory (Category category);
©orElse解决空指针异常:orElse - 搜索 (bing.com)
(d)@RestController=@Controller+@ResponseBody
【SpringBoot】 http请求注解之@RestController - 简书 (jianshu.com)
(e)@GetMapping 和@PostMapping
(25条消息) GetMapping 和 PostMapping_大鹏小站的博客-CSDN博客_getmapping和postmapping
(f)
(9)(25条消息) Vue + Spring Boot 项目实战(九):核心功能的前端实现_Evan-Nightly的博客-CSDN博客
(a)@CrossOrigin:(25条消息) 注解@CrossOrigin详解_MobiusStrip的博客-CSDN博客_crossorigin注解
(25条消息) 注解@CrossOrigin详解_MobiusStrip的博客-CSDN博客_crossorigin注解
(b)钩子函数 —— mounted:
©
2、项目的基本框架
3、项目技术栈
前端
vue学习的参考博客:
1、vue:
(22条消息) Vue学习之从入门到神经(万字长文 建议收藏)_白大锅的博客-CSDN博客_vue学习
(22条消息) 狂神说Vue笔记整理_one peice的博客-CSDN博客_狂神说vue笔记
2、vuex:(保存登录状态)
VueX(Vue状态管理模式) - 简书 (jianshu.com)(登录login保存登录状态)
3、element学习参考:(编写开发)
element-ui组件库:组件 | Element
4、axios学习参考:
Axios 中文文档_w3cschool
5、mockjs:拦截ajax请求生成数据用来调试前端:
mock.js的使用方法 - 简书 (jianshu.com)
6、qs:
查询参数序列化和解析库
7、echarts:
https://echarts.apache.org/zh/index.html
后端:
Spring boot+Spring Security 学习参考:Spring All master https://mrbird.cc
Mybatis:
其他知识的一些学习:
1、jwt:
【深度知识】JSON Web令牌(JWT)的原理,流程和数据结构 - 云+社区 - 腾讯云 (tencent.com)
2、http
HTTP 响应头信息 | 菜鸟教程 (runoob.com)
4、技术细节
1、前端脚手架安装
(26条消息) 在IDEA中对vue项目第一次导入需要的操作_程序媛小白白的博客-CSDN博客
npm i -g cnpm --registry=https://registry.npm.taobao.org
npm i -g vue-cli
vue init webpack $项目名$(记得cd 项目文件夹)
cnpm install
npm run dev
因为我们的项目还需要使用axios库:基于promise的http库
element-ui辅助前端页面开发
qs:数据序列化
mockjs:生成随机数据
vuex:保存数据状态
还需要安装
npm install --save axios
npm i element-ui -S
cnpm install qs --save
cnpm install mockjs --save-dev
cnpm install vuex --save
那么基本的前端开发环境就搭建好了
2、前端基本配置
因为要使用Element框架辅助开发,axios进行请求,mockjs进行调试,Vuex保存数据,所以要进行相关设置
创建store文件夹,添加一个index.js
src/store/index.js:
import Vue from 'vue'
import Vuex from 'vuex'
//挂载vuex对象
Vue.use(Vuex)
export default new Vuex.Store({
//存放的状态
state:{
},
//状态操作的集合类似于methods
mutations:{
},
//异步操作
actions:{
},
//模块化状态管理
modules:{
}
})
路由设置:src/router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import Index from '../components/Index'
import Login from "../components/Login";
Vue.use(Router)
const routes=[
{
path: '/index',
name: 'Index',
component: Index
},
{
path:'/login',
name:'Login',
component:Login
}
]
const router=new Router({
mode:'history',
base:process.env.BASE_URL,
routes
})
export default router
值得注意的是这里的模式要把hash模式改为history模式支持页面的回转
并且设置基路由
src/main.js:
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'
import Element from 'element-ui'
import "element-ui/lib/theme-chalk/index.css"
import axios from 'axios'
Vue.prototype.$axios = axios
Vue.config.productionTip = false
Vue.use(Element)
require("./mock.js")
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store,
components: { App },
template: '<App/>',
})
引用了上述的一些基本库,包括axios,Element-ui,Vuex(store文件夹中)
3、注册页面的编写,mock调试的过程
<template>
<body id="login">
<el-form class="login-form"
:model="loginForm"
:rules="rules"
label-position="left"
label-width="80px"
ref="loginForm"
>
<h3>欢迎来到论文管理系统</h3>
<el-row>
<el-form-item label="用户名" prop="username">
<el-input type="text" v-model="loginForm.username" prefix-icon="el-icon-user" size="medium"></el-input>
</el-form-item>
</el-row>
<el-row>
<el-form-item label="密码" prop="password">
<el-input type="password" v-model="loginForm.password" prefix-icon="el-icon-key" size="medium"></el-input>
</el-form-item>
</el-row>
<el-row>
<el-col :span="10" >
<el-form-item label="验证码" prop="code">
<el-input type="text" v-model="loginForm.code" prefix-icon="el-icon-edit" style="width: 100px"></el-input>
</el-form-item>
</el-col>
<el-image class="validateCode" :src="validateCode" @click="getValidateCode"></el-image>
</el-row>
<el-button type="primary" @click="handleLogin('loginForm')" style="margin-top: 20px">登录</el-button>
</el-form>
</body>
</template>
<script>
export default {
name: 'Login',
data () {
return {
loginForm: {
username: '',
password: '',
code: '',
token:'',
},
rules: {
username:[
{
required:true ,message:'请输入用户名',trigger:'blur'
}
],
password:[
{
required:true ,message:'请输入密码',trigger:'blur'
}
],
code:[
{
required:true ,message:'请输入验证码',trigger:'blur'
},
{
min:4,max:4 ,message:'验证码长度不正确',trigger:'blur'
}
],
},
validateCode:null,
}
},
methods: {
//登录
handleLogin (formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
this.$axios.post('/login',this.loginForm)
.then(res=>{
const jwt=res.headers['authorization']
/*
commit(opt,arg)
opt是方法名,
jwt是参数
*/
this.$store.commit('SET_TOKEN',jwt)
this.$router.push("/index")
})
} else {
return false;
}
});
},
//生成验证码
getValidateCode(){
this.$axios.get('/validateCode').then(res => {
this.loginForm.token = res.data.data.token
this.validateCode = res.data.data.validateCode
})
}
},
//钩子函数:用于渲染验证码
created(){
this.getValidateCode()
}
}
</script>
<style scoped>
#login{
background: url("../assets/loginBackground.jpg") no-repeat ;
background-position: center;
height: 100%;
width: 100%;
background-size: cover;
position: fixed;
}
html,body{
margin:0;
padding:0;
}
.login-form{
border-radius: 20px;
margin: 200px auto;
width:350px;
border:2px solid lightblue;
padding:20px;
background: #ffffff;
}
/deep/ .el-form-item__error {
color: #e6a23c;
width: 100px;
}
</style>
页面UI直接先抄Element-ui库里的源代码进行修改
页面设计一部分参考了上面的设计
后续有可能添加登录方式以后在改设计
src/mock.js
// 引入mockjs
const Mock = require('mockjs')
// 获取 mock.Random 对象// 参考:https://github.com/nuysoft/Mock/wiki/Mock.Random
const Random = Mock.Random
let Result = {
code: 200,
msg: '操作成功',
data: null
}
/***
* Mock.mock( url, post/get , function(options));
* url 表示需要拦截的 URL,
* post/get 需要拦截的 Ajax 请求类型
* 用于生成响应数据的函数 */
// 获取验证码图片base64编码以及一个随机码
Mock.mock('/validateCode', 'get', () => {
Result.data = {
token: Random.string(32), // 获取一个32位的随机字符串,
validateCode: Random.dataImage("120x40", "1111")//生成验证码为11111的base64图片编码
}
return Result
})
Mock.mock('/login', 'post', () => {
//无法在handler中传入数据jwt
return Result
})
测试了两个属性
src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
//挂载vuex对象
Vue.use(Vuex)
export default new Vuex.Store({
//存放的状态
state:{
token:''
},
//状态操作的集合类似于methods
mutations:{
SET_TOKEN:(state,token)=>{
state.token =token
localStorage.setItem("token",token)
}
},
//异步操作
actions:{
},
//模块化状态管理
modules:{
}
})
4、验证码(参考网上的代码)
(45条消息) Vue实现验证码功能_吴冬雪~的博客-CSDN博客_vue验证码怎么做
具体的思路是写一个静态的js文件,通过这个js文件配置验证码的各个属性
/更新 这个地方是有bug的,每次刷新的时候,验证码的refresh不对数组进行清空,所以需要把数组清空一下/
src/assets/js/validateCode.js
function GVerify (options) { // 创建一个图形验证码对象,接收options对象为参数
this.options = { // 默认options参数值
id: '', // 容器Id
canvasId: 'validateCanvas', // canvas的ID
width: '80', // 默认canvas宽度
height: '30', // 默认canvas高度
type: 'number', // 图形验证码默认类型blend:数字字母混合类型、number:纯数字、letter:纯字母
code: ''//网上的评论说设置成null更好,但是实际操作下来因为涉及到数组拷贝所以不能直接设置成null
}
if (Object.prototype.toString.call(options) === '[object Object]') { // 判断传入参数类型
for (var i in options) { // 根据传入的参数,修改默认参数值
this.options[i] = options[i]
}
} else {
this.options.id = options
}
this.options.numArr = '0,1,2,3,4,5,6,7,8,9'.split(',')
this.options.letterArr = getAllLetter()
this._init()
this.refresh()
}
GVerify.prototype = {
/** 版本号**/
version: '1.0.0',
/** 初始化方法**/
_init: function () {
var con = document.getElementById(this.options.id)
var canvas = document.createElement('canvas')
// this.options.width = con.offsetWidth > 0 ? con.offsetWidth : '30'
// this.options.height = con.offsetHeight > 0 ? con.offsetHeight : '30'
this.options.width = '160'
this.options.height = '40'
canvas.id = this.options.canvasId
canvas.width = this.options.width
canvas.height = this.options.height
canvas.style.cursor = 'pointer'
canvas.innerHTML = '您的浏览器版本不支持canvas'
con.appendChild(canvas)
var parent = this
canvas.onclick = function () {
parent.refresh()
}
},
/** 生成验证码**/
refresh: function () {
var canvas = document.getElementById(this.options.canvasId)
if (canvas.getContext) {
var ctx = canvas.getContext('2d')
}
ctx.textBaseline = 'middle'
ctx.fillStyle = randomColor(180, 240)
ctx.fillRect(0, 0, this.options.width, this.options.height)
//清空验证码值
this.options.code=[]
if (this.options.type === 'blend') { // 判断验证码类型
var txtArr = this.options.numArr.concat(this.options.letterArr)
} else if (this.options.type === 'number') {
var txtArr = this.options.numArr
} else {
var txtArr = this.options.letterArr
}
for (var i = 1; i <= 4; i++) {
var txt = txtArr[randomNum(0, txtArr.length)]
this.options.code += txt
ctx.font = randomNum(this.options.height / 2, this.options.height) + 'px SimHei' // 随机生成字体大小
ctx.fillStyle = randomColor(50, 160) // 随机生成字体颜色
ctx.shadowOffsetX = randomNum(-3, 3)
ctx.shadowOffsetY = randomNum(-3, 3)
ctx.shadowBlur = randomNum(-3, 3)
ctx.shadowColor = 'rgba(0, 0, 0, 0.3)'
var x = this.options.width / 5 * i
var y = this.options.height / 2
var deg = randomNum(-30, 30)
/** 设置旋转角度和坐标原点**/
ctx.translate(x, y)
ctx.rotate(deg * Math.PI / 180)
ctx.fillText(txt, 0, 0)
/** 恢复旋转角度和坐标原点**/
ctx.rotate(-deg * Math.PI / 180)
ctx.translate(-x, -y)
}
/** 绘制干扰线**/
for (var i = 0; i < 4; i++) {
ctx.strokeStyle = randomColor(40, 180)
ctx.beginPath()
ctx.moveTo(randomNum(0, this.options.width), randomNum(0, this.options.height))
ctx.lineTo(randomNum(0, this.options.width), randomNum(0, this.options.height))
ctx.stroke()
}
/** 绘制干扰点**/
for (var i = 0; i < this.options.width / 4; i++) {
ctx.fillStyle = randomColor(0, 255)
ctx.beginPath()
ctx.arc(randomNum(0, this.options.width), randomNum(0, this.options.height), 1, 0, 2 * Math.PI)
ctx.fill()
}
},
/** 验证验证码**/
validate: function (code) {
var code = code.toLowerCase()
var v_code = this.options.code.toLowerCase()
if (code == v_code) {
return true
} else {
return false
}
}
}
/** 生成字母数组**/
function getAllLetter () {
var letterStr = 'a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z'
return letterStr.split(',')
}
/** 生成一个随机数**/
function randomNum (min, max) {
return Math.floor(Math.random() * (max - min) + min)
}
/** 生成一个随机色**/
function randomColor (min, max) {
var r = randomNum(min, max)
var g = randomNum(min, max)
var b = randomNum(min, max)
return 'rgb(' + r + ',' + g + ',' + b + ')'
}
export {
GVerify
}
src/components/login/Login.vue
将之前测试的验证码的逻辑更改一下:
html部分注意v_container中加载了验证码
script部分值得注意的是mounted函数,还有handleLogin函数
<template>
<body id="login">
<el-form class="login-form"
:model="loginForm"
:rules="rules"
label-position="left"
label-width="80px"
ref="loginForm"
>
<h3>欢迎来到论文管理系统</h3>
<el-row>
<el-form-item label="用户名" prop="username">
<el-input type="text" v-model="loginForm.username" prefix-icon="el-icon-user" ></el-input>
</el-form-item>
</el-row>
<el-row>
<el-form-item label="密码" prop="password">
<el-input type="password" v-model="loginForm.password" prefix-icon="el-icon-key" show-password></el-input>
</el-form-item>
</el-row>
<el-row>
<el-col :span="10" >
<el-form-item label="验证码" prop="code">
<el-input type="text" v-model="loginForm.code" prefix-icon="el-icon-edit" style="width: 80px"></el-input>
</el-form-item>
</el-col>
<!-- 生成验证码 -->
<div id="v_container"></div>
</el-row>
<el-row type="flex" justify="end">
<el-button type="primary" @click="handleLogin('loginForm')" style="margin-top: 20px">登录</el-button>
<el-link href="/register" icon="el-icon-view" class="link" style="margin-top: 20px">还未注册?点这里</el-link>
</el-row>
</el-form>
</body>
</template>
<script>
import { GVerify } from '../../assets/js/validateCode';
import Element from "element-ui";
export default {
name: 'Login',
data () {
var usernameValidate=(rule,value,callback)=>{
if(value==='')
callback(new Error('请输入用户名'))
callback()
};
var passwordValidate=(rule,value,callback)=>{
if(value==='')
callback(new Error('请输入密码'))
callback()
};
var codeValidate=(rule,value,callback)=>{
if(value==='')
callback(new Error('请输入验证码'))
callback()
}
return {
loginForm: {
username: '',
password: '',
code: '',
jwt:'',
},
rules: {
username:[
{
validator:usernameValidate,trigger:'blur'
}
],
password:[
{
validator:passwordValidate,trigger:'blur'
}
],
code:[
{
validator:codeValidate,trigger:'blur'
},
{
min:4,max:4,message:'验证码长度不正确',trigger:'blur'
}
],
},
validateCode:null,
}
},
methods: {
//登录
handleLogin (formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
//this.loginForm.password=this.$md5(this.loginForm.password)
this.$axios.post('/user/login',this.loginForm)
.then(res=>{
var that = this
// 获取验证码
var validateCode = this.loginForm.code
var validateFlag = this.validateCode.validate(validateCode)
if (!validateFlag) {
that.$message.error('验证码错误')
return false;
}
const jwt=res.data.data.jwt
/*
commit(opt,arg)
opt是方法名,
jwt是参数
*/
this.$store.commit('SET_TOKEN',jwt)
this.$router.push("/index")
}).catch(failRes=>{
this.$message.error(failRes)
})
} else {
return false;
}
})
},
//生成验证码
// getValidateCode(){
// this.$axios.get('/validateCode').then(res => {
// this.loginForm.token = res.data.data.token
// this.validateCode = res.data.data.validateCode
// })
// }
},
//钩子函数:用于渲染验证码
mounted(){
this.validateCode =new GVerify('v_container')
}
}
</script>
<style scoped>
#login{
background: url('../../../src/assets/loginBackground.jpg') no-repeat center;
height: 100%;
width: 100%;
background-size: cover;
position: fixed;
}
html,body{
margin:0;
padding:0;
}
.login-form{
border-radius: 20px;
margin: 200px auto;
width:350px;
border:2px solid lightblue;
padding:20px;
background: #ffffff;
}
/deep/ .el-form-item__error {
color: #e6a23c;
width: 200px;
display: flex;
left:0;
}
.link{
margin-left: 30px;
}
#v_container {
width: auto;
height: auto;
display: inline-flex;
position: relative;
}
</style>
5.导航栏
src/components/common/Navmenu.vue
<template xmlns:el-col="http://www.w3.org/1999/html">
<div class="page-container">
<el-row>
<el-col :span="7" class="menu-col">
<el-menu
:default-active="$route.path"
router
text-color="#222"
ref="navMenu"
active-text-color="red"
style="min-width: 1300px"
class="index-menu"
mode="horizontal">
<el-menu-item v-for="route in routes"
:key="route.path"
:index="route.path"
:class="$route.path==route.path?'is-active':''">
<div>
<i v-bind:class="route.icon"></i>
{{ route.navItem }}
</div>
</el-menu-item>
</el-menu>
</el-col>
<el-col :offset="2" :span="8">
<el-input type="text" v-model="keywords" prefix-icon="el-icon-search" class="menu-search" placeholder="论文/科研方向" @keyup.enter.native="search"></el-input>
</el-col>
<el-col :span="7">
<el-dropdown @command="handleCommand">
<span class="el-dropdown-link">
<el-avatar src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"></el-avatar>
{{username}}
<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="/team">我的小组</el-dropdown-item>
<el-dropdown-item command="/comments">我的评论</el-dropdown-item>
<el-dropdown-item command="/library">我上传的论文</el-dropdown-item>
<el-dropdown-item command="/note">我的笔记本</el-dropdown-item>
<el-dropdown-item command="/space">个人空间</el-dropdown-item>
<el-dropdown-item command="logout" divided>登出</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</el-col>
</el-row>
</div>
</template>
<script>
export default {
name: "NavMenu",
data() {
return {
routes: [
{path: '/index', navItem: '首页', icon: 'el-icon-s-home'},
{path: '/labor', navItem: '我的实验室', icon: 'el-icon-s-help'},
{path: '/library', navItem: '论文库', icon: 'el-icon-s-management'},
{path: '/space', navItem: '我的', icon: 'el-icon-user-solid'}
],
username:'',
keywords:''
}
},
methods:{
handleCommand(command){
if(command==='logout')
this.logout()
this.$router.push(command)
},
logout()
{
this.$axios.post('/user/logout',{
jwt:window.localStorage.getItem('token')
}).then(res=>{
if(res.data.data===1) {
this.$store.commit('clear')
this.$router.replace('/index')
}
else{
this.$message.error('注销失败')
}
})
},
},
mounted() {
this.username=this.$store.state.username
}
}
</script>
<style scoped>
.page-container{
display: inline-block;
}
div,span{
caret-color: transparent;
}
.el-dropdown-link {
cursor: pointer;
color: #42b983;
}
.menu-search {
margin-top: 10px;
caret-color: #000000;
}
/deep/ .el-input__inner { /*或者 .s2>>>.el-input__inner */
border-radius: 40px; /*输入框圆角值*/
}
</style>
值得注意的是:
1、menu打开路由模式:第三行的router
<el-menu
:default-active="$route.path"
router
text-color="#222"
ref="navMenu"
active-text-color="red"
style="min-width: 1300px"
class="index-menu"
mode="horizontal">
2、动态匹配导航栏路由项:
<el-menu-item v-for="route in routes"
:key="route.path"
:index="route.path"
:class="$route.path==route.path?'is-active':''">
这里用$route.path和页面数据里的path进行path进行判断,可以使页面刷新不易失动态匹配导航栏的路由项
6、用户基本信息的修改
src/components/Space/Info
<template>
<el-form
ref="userForm"
class="info-form"
:model="userForm"
label-width="100px">
<h3>用户资料</h3>
<el-row>
<el-form-item label="用户名">
<el-input v-model="userForm.username" :disabled="true"></el-input>
</el-form-item>
</el-row>
<el-row>
<el-col :span="20">
<el-form-item label="密码">
<el-input type="password" v-model="userForm.password" :disabled="true"></el-input>
</el-form-item>
</el-col>
<el-button type="primary" @click="passVisible = true">更改</el-button>
</el-row>
<el-dialog
title="密码修改"
:visible.sync="passVisible"
width="30%"
>
<el-form
ref="changePassForm"
class="change-form"
:model="changePassForm"
:rules="changePassRules"
label-width="80px"
>
<el-form-item label="旧密码" prop="oldPass">
<el-input type="password"
v-model="changePassForm.oldPass"
autocomplete="off"
prefix-icon="el-icon-key"
placeholder="请输入旧密码"
show-password></el-input>
</el-form-item>
<el-form-item label="新密码" prop="newPass">
<el-input type="password"
v-model="changePassForm.newPass"
autocomplete="off"
prefix-icon="el-icon-key"
placeholder="请输入新密码"
show-password></el-input>
</el-form-item>
<el-form-item label="确认密码" prop="checkPass">
<el-input type="password"
v-model="changePassForm.checkPass"
autocomplete="off"
prefix-icon="el-icon-key"
placeholder="请再次输入新密码"
show-password></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="formSubmit('changePassForm')">更改</el-button>
<el-button @click="reset">取 消</el-button>
</span>
</el-dialog>
<el-row>
<el-col :span="20">
<el-form-item label="电话">
<el-input v-model="userForm.phone" :disabled="true"></el-input>
</el-form-item>
</el-col>
<el-button type="primary" @click="phoneVisible = true">更改</el-button>
</el-row>
<el-dialog
title="电话更改"
:visible.sync="phoneVisible"
width="30%"
>
<el-form
ref="changePhoneForm"
class="change-form"
:model="changePhoneForm"
:rules="changePhoneRules"
label-width="80px"
>
<el-form-item label="电话" prop="phone">
<el-input type="text"
v-model="changePhoneForm.phone"
autocomplete="off"
prefix-icon="el-icon-mobile"
placeholder="请输入电话号码"
style="width: 100%"
></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="formSubmit('changePhoneForm')">更改</el-button>
<el-button @click="reset">取 消</el-button>
</span>
</el-dialog>
<el-row>
<el-col :span="20">
<el-form-item label="邮箱">
<el-input v-model="userForm.email" :disabled="true"></el-input>
</el-form-item>
</el-col>
<el-button type="primary" @click="emailVisible = true">更改</el-button>
</el-row>
<el-dialog
title="邮箱验证"
:visible.sync="emailVisible"
width="30%"
>
<el-form
ref="changeEmailForm"
class="change-form"
:model="changeEmailForm"
:rules="changeEmailRules"
label-width="80px"
>
<el-form-item label="邮箱" prop="email">
<el-input type="text"
v-model="changeEmailForm.email"
autocomplete="off"
prefix-icon="el-icon-message"
placeholder="请输入邮箱号"
style="width: 68%"
></el-input>
<el-button type="primary" style="width: 30%" @click="send('changeEmailForm')" :disabled="emailButtonDis">
{{ buttonName }}
</el-button>
</el-form-item>
<el-form-item label="验证码" prop="validateCode">
<el-input type="text"
v-model="changeEmailForm.validateCode"
autocomplete="off"
prefix-icon="el-icon-check"
placeholder="请输入邮箱中的验证码"
></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="formSubmit('changeEmailForm')">验证</el-button>
<el-button @click="reset">取 消</el-button>
</span>
</el-dialog>
<el-alert
title="邮箱已验证"
type="success"
show-icon
:closable="false"
style="width: 150px; margin-left: 40px"
v-if="this.userForm.email!==null">
</el-alert>
<el-alert
title="邮箱未验证"
type="warning"
show-icon
:closable="false"
style="width: 150px; margin-left: 40px"
v-if="this.userForm.email===null">
</el-alert>
</el-form>
</template>
<script>
export default {
name: "Info",
data() {
var oldPassValidate = (rule, value, callback) => {
this.$axios.post('/user/checkPassword', {
id: window.localStorage.getItem('id'),
password: this.changePassForm.oldPass
}).then(res => {
if (res.data.data === true) {
callback()
}
else
callback(new Error('密码错误'))
}).catch(failRes => {
callback(new Error('系统错误')+failRes)
})
}
var passwordValidate = (rule, value, callback) => {
var reg = /^(?![0-9]+$)(?![a-zA-Z]+$)[,.#%'+*-:;^_`0-9A-Za-z]{6,16}$/
if (value === '') {
callback(new Error('请输入密码'))
} else if (!reg.test(value)) {
callback(new Error('密码必须为6-16位,必须含有包含数字和英文字母'))
} else if (this.changePassForm.checkPass !== '') {
this.$refs.changePassForm.validateField('checkPass');
}
callback()
};
//自定义的重复密码校验
var checkPassValidate = (rule, value, callback) => {
if (value === '') {
callback(new Error('请再次输入密码'))
} else if (value !== this.changePassForm.newPass) {
callback(new Error('两次输入密码不一致'))
} else {
callback()
}
};
var phoneValidate = (rule, value, callback) => {
if(value==='') callback(new Error('请输入电话号码'))
var phoneReg = /^((13|14|15|16|17|18|19)d{9})$/
if (!phoneReg.test(value))
callback(new Error('请输入正确的手机号码格式'))
else
callback()
};
var emailValidate = (rule, value, callback) => {
if (value === '') callback(new Error('请输入邮箱'))
var reg = /^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(.[a-zA-Z0-9_-]+)+$/
if (!reg.test(value))
callback(new Error('请输入正确的邮箱格式'))
callback()
};
var codeValidate = (rule, value, callback) => {
if (value === '')
callback(new Error('请输入验证码'))
this.$axios.post('/email/check',{
id:window.localStorage.getItem('id'),
code:this.changeEmailForm.validateCode
}).then(res=>{
if(res.data.data===true)
callback()
else
callback(new Error('验证码错误'))
}).catch(failRes=>{
this.$message.error('系统异常')
callback()
})
};
return {
userForm: {
username: '',
password: '',
phone: '',
email: '',
},
changePassForm: {
oldPass: '',
newPass: '',
checkPass: ''
},
changePassRules: {
oldPass: [
{
validator: oldPassValidate, trigger: 'blur'
}
],
newPass: [
{
validator: passwordValidate, trigger: 'blur'
}],
checkPass: [
{
validator: checkPassValidate, trigger: 'blur'
}],
},
changePhoneForm: {
phone:''
},
changePhoneRules: {
phone:[{
validator: phoneValidate, trigger:'blur'
}]
},
changeEmailForm: {
email: '',
validateCode: ''
},
changeEmailRules: {
email: [{
validator: emailValidate, trigger: 'blur'
}],
validateCode: [
{
validator: codeValidate, trigger: 'blur'
},
{
min: 6, max: 6, message: '验证码长度不正确', trigger: 'blur'
}]
},
buttonName: '发送验证码',
emailButtonDis: false,
passVisible: false,
phoneVisible:false,
emailVisible: false,
}
},
mounted() {
let url='/user/info/id='+localStorage.getItem('id')
this.$axios.get(url).then(res=>{
if(res.data.code===200)
{
this.userForm.username=this.$store.state.username
this.userForm.password=res.data.data.password
this.userForm.email=res.data.data.email
this.userForm.phone=res.data.data.phone
}
})
},
methods: {
send(formName) {
const _this=this
let timerId
this.$refs[formName].validateField('email', error => {
if (!error) {
this.$axios.post('/email/send', {
id: window.localStorage.getItem('id'),
email: this.changeEmailForm.email
}).then(res => {
this.$message({
showClose:true,
message:'发送成功,有效期5分钟',
type:'success'
})
let count = 60
_this.changeEmailForm.validateCode=''
_this.emailButtonDis = true
_this.buttonName=`${count--}s`
timerId=window.setInterval(()=>{
_this.buttonName=`${count--}s`
if(count<=0)
{
window.clearInterval(timerId)
_this.buttonName='发送验证码'
_this.emailButtonDis=false
}
},1000)
setTimeout(() => {
this.emailButtonDis = false
}, 60000)
}).catch(failRes => {
this.$message.error(failRes)
this.reset()
})
} else
return false
})
},
formSubmit(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
var pass = this.userForm.password
var phone = this.userForm.phone
var email = this.userForm.email
if (formName === 'changePassForm') {
pass = this.changePassForm.newPass
} else if (formName === 'changeEmailForm') {
email = this.changeEmailForm.email
}else{
phone =this.changePhoneForm.phone
}
this.$axios.post('/user/update', {
id: window.localStorage.getItem('id'),
password: pass,
phone: phone,
email: email
}).then(res => {
if (res.data.code === 200) {
this.$message.info('修改成功')
this.userForm.password = pass
this.userForm.email = email
this.userForm.phone = phone
this.reset()
this.passVisible = false
} else {
this.$message.error('修改失败')
return false
}
}).catch(() => {
})
} else
return false
})
},
reset() {
this.changePassForm.oldPass = ''
this.changePassForm.newPass = ''
this.changePassForm.checkPass = ''
this.changeEmailForm.email = ''
this.changeEmailForm.validateCode = ''
this.emailButtonDis = false
this.passVisible = false
this.emailVisible = false
}
}
}
</script>
<style scoped>
.info-form {
width: 500px
}
.change-form {
width: 100%
}
/deep/ .el-form-item__error {
color: #e6a23c;
width: 200px;
display: flex;
left: 0;
}
</style>
1、定时器的制作:
为了限制邮箱验证码的反复发送设置了一个定时器用来限制邮箱验证码的发送:
let count = 60//倒计时
_this.changeEmailForm.validateCode=''//清空表单
_this.emailButtonDis = true//禁用按钮
_this.buttonName=`${count--}s`//按钮显示文案
/*
window.setInterval(func,count)
可以实现每隔count的时间执行一次func
*/
timerId=window.setInterval(()=>{
_this.buttonName=`${count--}s`
if(count<=0)
{
window.clearInterval(timerId)
_this.buttonName='发送验证码'
_this.emailButtonDis=false
}
},1000)
/*
setTimeout(func,count)
在count结束后执行func
*/
setTimeout(() => {
this.emailButtonDis = false
}, 60000)
2、各个表单的验证
这次验证加入了和后端的交互
var codeValidate = (rule, value, callback) => {
if (value === '')
callback(new Error('请输入验证码'))
this.$axios.post('/email/check',{
id:window.localStorage.getItem('id'),
code:this.changeEmailForm.validateCode
}).then(res=>{
if(res.data.data===true)
callback()
else
callback(new Error('验证码错误'))
}).catch(failRes=>{
this.$message.error('系统异常')
callback()
})
};
7、echarts引入(实验室)
安装
npm init
npm install echarts --save
引入
import echarts from 'echarts'
//需要挂载到Vue原型上
Vue.prototype.$echarts = echarts
遇到了数据动态渲染问题,先run
(54条消息) vue+echarts 实现点击切换数据(监听值的变化重新渲染)_程序员xiaoQ的博客-CSDN博客_echarts 数据切换
8、论文库首页
首先对于论文库必须上传论文所以必须要有一个提交的表单处理新的论文
1、提交新的论文
src/components/library/EditForm
这部分有很多问题难住我了,截止到2022/05/08的晚上我也不知道怎么去处理这个表单的摘要部分和研究方向部分,引用部分的需求仍然需要我去和助教交流,暂时把基本的提交给搞定了;
明天可能需要考虑如何去优化摘要还有研究方向
可以考虑把研究方向变成一个下拉表单
更新到5/30
因为疫情事情太多了导致更新的很慢,这个表单修改了好多bug而且又按照设计精化过一遍,现在这份应该是没有问题的
论文有校验规则,有多选下拉表单,作者还可以按照回车进行分割
<template>
<div>
<i class="el-icon-circle-plus-outline" @click="dialogFormVisible=true"></i>
<el-dialog
title="添加论文/修改论文数据"
:visible.sync="dialogFormVisible"
@close="clear"
width="30%">
<el-form v-model="form"
style="text-align: left"
ref="paperForm"
:model="form"
:rules="rules"
label-width="80px">
<el-form-item label="论文名" prop="title">
<el-input v-model="form.title"
auto-complete="off"
style="caret-color: black; " placeholder="无需添加《》"></el-input>
</el-form-item>
<el-form-item label="作者" prop="author">
<el-select
v-model="form.author"
placeholder="依此使用回车键入作者的名字"
filterable
multiple
allow-create
style="width: 100%;caret-color:black"
:popper-append-to-body="false"
default-first-option
ref="authorSelect">
</el-select>
</el-form-item>
<el-form-item label="发表会议" prop="conference">
<el-input v-model="form.conference" auto-complete="off" style="caret-color: black"
placeholder="请输入发表会议"></el-input>
</el-form-item>
<el-form-item label="发表日期" prop="releaseDate">
<el-date-picker v-model="form.releaseDate"
type="date"
value-format="yyyy-MM-dd"
placeholder="请选择发布日期"
style="width: 100%"></el-date-picker>
</el-form-item>
<el-form-item label="论文类型" prop="type">
<el-select v-model="form.type"
style="width: 100%"
placeholder="请选择论文类型">
<el-option value="0" label="理论证明型"></el-option>
<el-option value="1" label="综述型"></el-option>
<el-option value="2" label="实验型"></el-option>
<el-option value="3" label="⼯具型"></el-option>
<el-option value="4" label="数据集型"></el-option>
</el-select>
</el-form-item>
<el-form-item label="论文链接" prop="paperLink">
<el-input placeholder="请输入内容" v-model="form.paperLink" style="caret-color: black">
<template slot="prepend">Https://</template>
</el-input>
</el-form-item>
<el-form-item label="研究方向" prop="directions">
<tree-select
:lazy="true"
:props="props"
:multiple="true"
:collapse="true"
:options="optionData"
v-model="form.directions"
:clearable="true"
:accordion="false"
:expandedNode="false"
style="width: 100%"
@getValue="getValue($event)"
@load="loadNodes"
></tree-select>
</el-form-item>
<el-form-item prop="abstract" label="摘要">
<el-input
type="textarea"
:autosize="{ minRows: 2, maxRows: 4}"
placeholder="请输入摘要"
v-model="form.abstract"
style="caret-color: black"
resize="none"
></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="onSubmit('paperForm')">确定</el-button>
<el-button @click="clear">取消</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import TreeSelect from "../utils/TreeSelect";
export default {
name: "EditForm",
components: {TreeSelect},
data() {
//论文名校验
var titleValidate = (rule, value, callback) => {
if (value === '')
callback(new Error('请输入论文名'))
callback()
};
//作者校验
var authorValidate = (rule, value, callback) => {
if (value.length===0)
callback(new Error('请输入至少一个作者名字'))
callback()
};
//会议校验
var conferenceValidate = (rule, value, callback) => {
if (value === '')
callback(new Error('请输入会议名称'))
callback()
};
//日期校验
var dateValidate = (rule, value, callback) => {
if (value === '')
callback(new Error('请选择论文发表日期'))
callback()
};
//类型校验
var typeValidate = (rule, value, callback) => {
if (value === '')
callback(new Error('请选择论文类型'))
callback()
};
//URL校验
var linkValidate = (rule, value, callback) => {
if (value === '')
callback(new Error('请输入论文链接地址'))
callback()
};
//研究方向校验
var directionValidate = (rule, value, callback) => {
if (value.length===0)
callback(new Error('请输入至少选择一个论文方向'))
callback()
};
//摘要校验
var abstractValidate = (rule, value, callback) => {
if (value.length===0)
callback(new Error('请输入摘要内容'))
callback()
};
return {
//表单属性
dialogFormVisible: false,
formLabWidth: '80px',
form: {
id: '',
title: '',
author: [],
conference: '',
releaseDate: '',
type: '',
abstract: '',
paperLink: '',
directions: []
},
/*
表单的验证方式
*/
rules: {
title: [
{
validator: titleValidate, trigger: 'blur'
},
{
min: 2, max: 30, trigger: 'blur'
}
],
author: [
{
validator: authorValidate, trigger: 'blur'
}
],
conference:[
{
validator:conferenceValidate,trigger:'blur'
}
],
releaseDate:[
{
validator:dateValidate,trigger:'blur'
}
],
type:[
{
validator:typeValidate,trigger:'blur'
}
],
paperLink:[
{
validator:linkValidate,trigger:'blur'
}
],
directions:[
{
validator:directionValidate,trigger:'blur'
}
],
abstract:[
{
validator:abstractValidate,trigger:'blur'
}
]
},
/*
研究方向节点属性
*/
props: {
value: 'id',
label: 'name',
pid: 'parentId',
children: 'children',
isLeaf: 'leaf'
},
selectList: []
}
},
methods: {
/*
清空
*/
clear() {
this.form = {
id: '',
title: '',
author: [],
conference: '',
releaseDate: '',
type: '',
abstract: '',
paperLink: '',
directions: []
}
this.dialogFormVisible= false
},
/*
加载方向
非懒加载
*/
// getAllDirection(){
// this.$axios.get('/direction/all').then(res=>{
// this.selectList=res.data.data.directionList
// }).catch(failRes=>{
// this.$message.error(failRes)
// })
// },
/*
提交
*/
onSubmit(formName) {
var author=this.form.author.join(' ')
var directionId=JSON.parse(JSON.stringify(this.form.directions))
this.$refs[formName].validate((valid) => {
if (valid) {
this.$axios.post('/paper/upload', {
title: this.form.title,
author: author,
conference: this.form.conference,
releaseDate: this.form.releaseDate,
type: this.form.type,
paperLink: this.form.paperLink,
directionId: directionId,
id: this.$store.state.id,
uploadDate: this.currentDate(),
abstract: this.form.abstract
}).then(res => {
this.dialogFormVisible = false
this.$emit('onSubmit')
}).catch(() => {
})
} else {
return false
}
})
},
/*
加载时间
*/
currentDate() {
let d = new Date();
let year = d.getFullYear();
let month = d.getMonth();
month = month + 1 > 12 ? 1 : month + 1;
month = month > 9 ? month : "0" + month.toString();
let day = d.getDate();
let hour = d.getHours();
hour = hour > 9 ? hour : "0" + hour.toString();
let minute = d.getMinutes();
minute = minute > 9 ? minute : "0" + minute.toString();
let second = d.getSeconds();
return `${year}-${month}-${day} ${hour}:${minute}:${second}`;
},
/*
懒加载节点
*/
loadNodes(node, resolve) {
let self = this
let treeData = []
if (node.level === 0) {
self.$axios.get(`/direction/getChildren/0`).then(res => {
res.data.data.forEach(e => {
e.id = e.directionId
treeData.push(e)
})
resolve(treeData)
}).catch(fail => {
resolve([])
})
} else {
self.$axios.get(`/direction/getChildren/${node.data.id}`).then(res => {
res.data.data.forEach(e => {
e.id = e.directionId
treeData.push(e)
})
resolve(treeData)
}).catch(fail => {
resolve([])
})
}
},
//调试用
getValue(value){
this.form.directions=value
console.log(this.form.directions)
}
},
computed: {
/*
遍历selectList的所有选项,然后将所有的子元素挂载在父元素之下,
然后返回前一层
*/
optionData() {
let cloneData = JSON.parse(JSON.stringify(this.selectList))
return cloneData.filter(father => {
let branchArr = cloneData.filter(child => father.id == child.parentId); // 返回每一项的子级数组
branchArr.length > 0 ? (father.children = branchArr) : ""; //给父级添加一个children属性,并赋值
return father.parentId == 0; //返回第一层
})
}
},
mounted() {
// this.$refs.authorSelect.blur()
}
}
</script>
<style scoped>
.el-icon-circle-plus-outline {
margin: 0 0 0 20px;
font-size: 30px;
float: right;
caret-color: transparent;
}
div, span {
caret-color: transparent;
}
/*
隐藏右侧下拉表单箭头
*/
/deep/ .el-select .el-input .el-select__caret {
display: none !important;
}
/*
隐藏下拉表单
*/
/deep/ .el-select-dropdown {
display: none !important;
}
</style>
2、下拉表单子组件
5.19号自己手写了一个下拉表单,当然也参考了博客上的写法,将el-tree和el-select包装起来,并且实现了懒加载、只有子节点能够现实的多种设置
注释和复用性有些差,到时候回来在refine一下
注意一下这个地方需要安装一下less的样式加载器的,需要进行less的预加载
src/utils/TreeSelect.vue
<template>
<el-select
:value="valueTitle"
:clearable="clearable"
:multiple="multiple"
:collapse-tags="collapse"
@remove-tag="handleRemoveTag"
@clear="clearHandle">
<el-input
class="selectInput"
:placeholder="placeholder"
v-model="filterText"
></el-input>
<el-option :value="valueId" :label="label" class="options">
<el-tree id="tree-option"
ref="selectTree"
:accordion="accordion"
:data="options"
:show-checkbox="multiple"
:check-strictly="true"
:props="props"
:node-key="props.value"
:default-expanded-keys="defaultExpandedKey"
:default-expand-all="expand"
:auto-expand-parent="expandParent"
:expand-on-click-node="expandNode"
:lazy="lazy"
:load="loadNodes"
:filter-node-method="filterNode"
@node-click="handleCheckClick"
@check-change="handleCheckClick">
</el-tree>
</el-option>
</el-select>
</template>
<script>
export default {
name: "TreeSelect",
props:{
//配置项
props:{
type:Object,
default:()=>{
return{
value:'id',
label:'title',
children:'children',
isLeaf:'leaf'
}
}
},
//选项列表的数据
options:{
type:Array,
default:()=>{
return []
}
},
//初始值
value:{
type: [String, Number, Boolean, Array],
default:()=>{
return null
}
},
// 宽度
width:{
type:String,
default:()=>{
return '270px'
}
},
// 是否多选
multiple:{
type:Boolean,
default:()=>{
return true
}
},
// 是否展示复选框
checkbox:{
type:Boolean,
default:()=>{
return false
}
},
// 多选是否将选中值按文字表示
collapse:{
type:Boolean,
default:()=>{
return true
}
},
// 是否可清空
clearable:{
type:Boolean,
default:()=>{
return true
}
},
// 是否显示check
show:{
type:Boolean,
default:()=>{
return false
}
},
// 是否自动展开
accordion:{
type:Boolean,
default:()=>{
return false
}
},
// 搜索框的提示
placeholder:{
type:String,
default:()=>{
return "检索关键字"
}
},
// 是否展开所有节点
expand:{
type:Boolean,
default:()=>{return false}
},
// 展开子节点是否需要展开父节点
expandParent:{
type:Boolean,
default:() =>{return true}
},
// 是否在点击节点时展开或者收缩节点
expandNode:{
type:Boolean,
default:() =>{return true}
},
lazy:{
type:Boolean,
default:()=>{return false}
}
},
data(){
return{
valueTitle:'',//当前选中的label
valueId:this.value,//当前选中的value
defaultExpandedKey:[],
filterText:'',
label:''
}
},
mounted() {
this.initTreeSelect()
},
methods:{
/*
初始化函数
*/
initTreeSelect(){
if(this.valueId && !this.multiple)//单选
{
var node = this.$refs.selectTree.getNode(this.valueId);
if(node){
this.valueTitle = node.data[this.treeProps.label]; // 初始化显示
this.defaultExpandedKeys = [this.valueId]; // 设置默认展开
this.$nextTick(() => {
this.$refs.selectTree.setCurrentKey(this.valueId); // 设置默认选中
})
}
}
else if(this.valueId && this.multiple) {//多选
this.defaultExpandedKeys =this.defaultExpandedKey
this.$nextTick(()=>{
this.$refs.selectTree.setChecked(this.valueId); // 设置默认选中
})
}
this.initScroll()
},
/*
初始化滚动条
*/
initScroll(){
this.$nextTick(()=>{
let scrollWrap = document.querySelectorAll('.el-scrollbar .el-select-dropdown__wrap')[0]
let scrollBar = document.querySelectorAll('.el-scrollbar .el-scrollbar__bar')
scrollWrap.style.cssText = 'margin: 0px; max-height: none; overflow: hidden;'
scrollBar.forEach(ele => ele.style.width = 0)
})
},
/*
单选
*/
handleNodeClick(node){
this.valueTitle = node[this.props.label]
this.valueId =node[this.props.value]
//返回父组件
this.$emit('getValue',this.valueId)
this.defaultExpandedKey=[]
},
/*
多选,节点发生变化时回调
*/
handleCheckClick(data,checked,childrenCheck){
this.valueId = this.$refs.selectTree.getCheckedKeys()
var checkedNodes = this.$refs.selectTree.getCheckedNodes()
this.valueTitle = checkedNodes.map((node)=>{
return node[this.props.label]
})
this.defaultExpandedKeys =[]
this.$emit('getValue',this.valueId)
},
/*
清除选中的选项/checkbox
*/
clearHandle(){
this.valueTitle=''//当前选中的label
this.valueId=''//当前选中的value
this.defaultExpandedKey=[]
if(this.multiple){
this.$refs.selectTree.setChecked([])
}
else{
this.$refs.selectTree.setCurrentKey(null)
}
this.$emit('getValue',null)
},
/*
多选中除去任意选中
*/
handleRemoveTag(val){
var checkNodes = this.$refs.selectTree.getCheckedNodes();
var node=checkNodes.find(node => node[this.props.label] ===val)
this.$refs.selectTree.setChecked(node[this.props.value],false)
},
/*
过滤方法
*/
filterNode(value, data) {
if (!value) return true
return data.name.indexOf(value) !== -1
},
/*
加载节点方法
*/
loadNodes(node,resolve){
this.$emit('load',node,resolve)
}
},
watch:{
value(){
this.valueId=this.value
this.initTreeSelect()
},
filterText(val) {
this.$refs.selectTree.filter(val);
}
},
computed:{
selectStyle(){
return{
width:`${this.width}`
}
}
}
}
</script>
<style scoped>
.el-scrollbar .el-scrollbar__view .el-select-dropdown__item {
height: auto;
max-height: 274px;
padding: 0;
overflow: hidden;
overflow-y: auto;
}
.el-scrollbar .el-select-dropdown__item.selected {
font-weight: normal;
}
/*横向滚动条*/
.el-scrollbar__bar.is-horizontal {
height: 6px;
left: 2px;
}
/*纵向滚动条*/
.el-scrollbar__bar.is-vertical {
/*width: 6px;*/
/*top: 2px;*/
display: none;
}
/*字体和大小*/
.custom-tree-node {
font-family:"Microsoft YaHei";
font-size: 14px;
position: relative;
}
/*原生 el-tree-node的div是块级元素,需要改为inline-block,才能显示滚动条 */
.treeSelect .el-tree >.el-tree-node {
display: inline-block;
min-width: 100%;
}
ul li ::v-deep .el-tree .el-tree-node__content {
height: auto;
padding: 0 20px;
}
.el-tree-node__label {
font-weight: normal;
}
.el-tree ::v-deep .is-current .el-tree-node__label {
color: #1B65B9;
font-weight: 700;
}
.el-tree ::v-deep .is-current .el-tree-node__children .el-tree-node__label {
color: #606266;
font-weight: normal;
}
</style>
<style lang="less">
.el-tree-node {
.is-leaf + .el-checkbox .el-checkbox__inner {
display:inline-block;
}
.el-checkbox__input> .el-checkbox__inner {
display:none;
}
}
</style>
3、面包屑导航栏
这个主要用于通过研究方向分类查询论文
5.10目前正在调试
更新到5.30号,基础逻辑已经搞定了,现在就是和和后端数据交互仍然有问题,
更新到6.1号,现在面包屑导航栏已经基本没有问题,可以根据方向显示论文,点击论文库返回首页
src/library/Breadcrumb.vue
<template>
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item v-for="(item,i) in breadList"
:key="i"
:index="item.name">
<el-dropdown :hide-on-click="false"
@command="handleCommand">
<span @click="paperReload" style="font-weight: bolder;cursor: pointer" v-if="i===0">{{item.name}}</span>
<span class="el-dropdown-link" v-else>
{{ item.name }}
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-for="(child,j) in item.children"
:key="j"
:index="child.name"
:command="child">
{{child.name}}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</el-breadcrumb-item>
</el-breadcrumb>
</template>
<script>
export default {
inject:['reload'],
name: "Breadcrumb",
data() {
return {
breadList:[
{directionId:0,parentId:-1,name:'论文库',leaf:false,index:0,children:[]}
],
currentId:0,
activeIndex:0
}
},
methods:{
/*
处理面包导航下拉表单
*/
handleCommand(command) {
//把list回溯到command的父节点处
let parentId=command.parentId
let newList=[]
for(let i=0;i<this.breadList.length;i++)
{
newList.push(this.breadList[i])
if(this.breadList[i].directionId===parentId){
this.activeIndex=i
break
}
}
this.breadList=newList
//选中的选项index为父节点index+1
command.index=this.activeIndex+1
//获取子节点
if(!command.leaf) this.getChildren(command.directionId)
this.breadList.push(command)
this.currentId=command.directionId
this.activeIndex++
//向父组件传commandId的值
this.$emit('selectChange',command.directionId)
},
/*
获得子节点
*/
getChildren(id){
this.$axios.get(`/direction/getChildren/${id}`).then(res=>{
this.breadList[this.activeIndex].children=res.data.data
}).catch(failRes=>{
})
},
/*
刷新界面
*/
paperReload(){
this.reload()
}
},
mounted(){
this.getChildren(this.currentId)
}
}
</script>
<style scoped>
div, span {
caret-color: transparent;
}
</style>
4、论文首页
然后就是现阶段的论文首页了,仍然在调试阶段
更新到5.30,现在后端那边没有给出按分类处理论文的接口,
更新到6.1,对整个论文库进行大改,路由发生大改,新建了一批论文库的子页面,同时论文库首页由Library编程LibraryIndex,而Library是其中的一个子路由
library经过优化可以通过主要的loadAllPapers函数来加载所需要的论文,并且可以按照相关的排序
router/main.js(部分)
{
path:'/library',
name:'LibraryIndex',
redirect: '/library/papers',
component: LibraryIndex,
meta:{
requireAuth:true
},
children: [
{
path:'/library/papers',
name:'Library',
component: Library,
meta: {
requireAuth: true
}
},
{
path:'/library/content',
name:'Content',
component: Content,
meta: {
requireAuth: true
}
}
]
},
src/library/LibraryIndex.vue
<template>
<el-container>
<el-aside width="200px">
<side-menu></side-menu>
</el-aside>
<el-main>
<router-view></router-view>
</el-main>
</el-container>
</template>
<script>
import SideMenu from "./SideMenu";
import Library from "./Library";
import Content from "./Content";
export default {
name: "LibraryIndex",
components:{SideMenu,Library,Content}
}
</script>
<style scoped>
</style>
src/library/LibraryIndex.vue
<template>
<el-container>
<el-header height="100px">
<el-row>
<el-col :span="10">
<breadcrumb @selectChange="loadPapersById($event)"></breadcrumb>
</el-col>
<el-col>
<edit-form ref="edit" @onSubmit="loadAllPapers"></edit-form>
</el-col>
</el-row>
<el-divider></el-divider>
<el-row :gutter="20" type="flex" justify="end">
<el-col :xs="8" :sm="6" :md="4" :lg="3">
<div>
<el-dropdown @command="handleSort">
<el-button type="primary">
{{ this.sortMethod.name }}<i class="el-icon-arrow-down el-icon--right"></i>
</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-for="(option,i) in this.sortOption"
:key="i"
:index="option.name"
:command="option">{{ option.name }}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</el-col>
<el-col :xs="8" :sm="6" :md="4" :lg="3">
<div style="margin-left: 20px">
<el-button type="primary" @click="changeOrder">{{ this.sortOrder.name }}</el-button>
</div>
</el-col>
</el-row>
</el-header>
<el-main>
<el-tooltip effect="dark" placeholder="right"
v-for="item in papers.slice((currentPage-1)*pageSize,currentPage*pageSize)"
:key="item.id">
<p slot="content" style="font-size: 14px;margin-bottom: 6px;">{{ item.title }}</p>
<p slot="content" style="font-size: 13px;margin-bottom: 6px">
<span>作者:{{ item.author }}</span>
<span>发布时间:{{ item.releaseDate }}</span>
</p>
<p slot="content" style="font-size: 13px;margin-bottom: 6px">
<span>上传者:{{ item.uploader }}</span>
<span>上传时间:{{ item.uploadDate }}</span>
</p>
<!-- <p slot="content" style="font-size: 13px;margin-bottom: 6px">-->
<!-- <span v-for="did in item.directionId" >{{directionName(did)}}</span>-->
<!-- </p>-->
<p slot="content" style="font-size: 13px;margin-bottom: 6px">
<span>{{ item.conference }}</span>
<span>{{ item.type }}</span>
</p>
<el-card class="paper" shadow="hover">
<div slot="header" class="paper-title">
<el-link target="_blank" type="primary" @click="handlePaperLink(item.paperLink)">
<span style="font-size: x-large">{{ item.title }}</span>
</el-link>
</div>
<div class="paper-info">
<i class="el-icon-user"/> <span class="item">{{ item.author }}</span>
<i class="el-icon-paperclip"/><span class="item">{{ item.conference }}</span>
<i class="el-icon-date"/><span class="item">{{ item.releaseDate }}</span>
</div>
<!-- <div class="paper-domain">-->
<!-- <span v-for="dir in item.domains" v-if="dir.leaf===true">{{dir.name}}</span>-->
<!-- </div>-->
<div class="paper-abstract">
<span>{{ item.abstract | ellipsis }}</span>
</div>
<div class="paper-footer">
上传者:{{ item.uploader }},
上传时间:{{ item.uploadDate }}
</div>
</el-card>
</el-tooltip>
<el-row>
<el-pagination
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-size="pageSize"
:total="papers.length"
class="pagination">
</el-pagination>
</el-row>
<el-empty description="还没有论文哦" v-if="papers.length===0"></el-empty>
</el-main>
</el-container>
</template>
<script>
import Breadcrumb from "./Breadcrumb";
import EditForm from "./EditForm";
export default {
name: "Library",
components: {Breadcrumb, EditForm},
data() {
return {
//论文相关
currentPage: 1,
pageSize: 15,
papers: [],
//方向相关
currentDirection: 0,
//排序相关
sortMethod: {name: '上传时间', command: 'releaseDate'},
sortOrder: {name: '升序', order: 'asc'},
sortOption: [
{name: '上传时间', command: 'releaseDate'},
{name: '发布时间', command: 'releaseDate'},
]
}
},
filters: {
ellipsis(value) {
if (!value) return '';
if (value.length > 200)
return value.slice(0, 200) + '...';
return value
}
},
methods: {
/*
加载所有论文
*/
loadAllPapers() {
this.$axios.get(`/paper/getAll/directionId=${this.currentDirection}/sortMethod=${this.sortMethod.command}/order=${this.sortOrder.order}`).then(res => {
this.papers = res.data.data
}).catch(failRes => {
this.papers=[]
console.log(failRes)
})
},
/*
按照方向加载论文
*/
loadPapersById(value) {
this.currentDirection = value
this.loadAllPapers()
},
/*
处理页号
*/
handleCurrentChange(page) {
this.currentPage = page
},
//排序相关
/*
处理排序方式
*/
handleSort(command) {
this.sortMethod = command
this.loadAllPapers()
},
changeOrder() {
if (this.sortOrder.order === 'asc') {
this.sortOrder.order = 'desc'
this.sortOrder.name = '降序'
} else {
this.sortOrder.order = 'asc'
this.sortOrder.name = '升序'
}
this.loadAllPapers()
},
//查看论文相关信息
handlePaperLink(url){
this.$router.push({
name:'Content',
params:{
url:url
}
})
}
},
mounted() {
this.loadAllPapers()
}
}
</script>
<style scoped>
.paper-title {
display: flex;
right: 0;
}
.paper-info {
display: flex;
right: 0;
margin-bottom: 20px;
}
.paper-domain {
display: flex;
right: 0;
margin-bottom: 20px;
}
.paper-abstract {
display: inline-block;
text-align: left;
right: 0;
margin-bottom: 20px;
}
.paper-footer {
display: flex;
right: 0;
color: #888888;
font-size: x-small;
}
.item {
margin-left: 10px;
margin-right: 10px;
}
.pagination {
position: fixed;
bottom: 5%;
right: 10%;
}
</style>
5、论文内容
主要方式是library界面通过传参把相关的url参数传到论文内容中来,然后这个页面主要通过ifram进行页面加载,具体代码如下
更新到6.1:iframe确实不太会用明天再学一下,然后把笔记本的抽屉给加上看看能不能行
src/library/Content.vue
<template>
<div>
<iframe :src="url" id="paperContent" scrolling="no" frameborder="0"
class="content"></iframe>
</div>
</template>
<script>
export default {
name: "Content",
data () {
return {
url:'',
}
},
mounted(){
/*
初始化链接
*/
this.url=this.$route.params.url
/**
* iframe-宽高自适应显示
*/
function changeMobsfIframe(){
const mobsf = document.getElementById('paperContent');
const deviceWidth = document.documentElement.clientWidth;
const deviceHeight = document.documentElement.clientHeight;
mobsf.style.width = (Number(deviceWidth)-360) + 'px'; //数字是页面布局宽度差值
mobsf.style.height = (Number(deviceHeight)-100) + 'px'; //数字是页面布局高度差
}
changeMobsfIframe()
window.onresize = function(){
changeMobsfIframe()
}
}
}
</script>
<style scoped>
div{
caret-color: transparent;
}
.content{
position:absolute;
top:64px;
left: 240px;
right:0px;
bottom:100px;
height: auto;
width: auto;
transform-origin: 90% 83%;
}
</style>
console.log(failRes)
})
},
/*
按照方向加载论文
*/
loadPapersById(value) {
this.currentDirection = value
this.loadAllPapers()
},
/*
处理页号
*/
handleCurrentChange(page) {
this.currentPage = page
},
//排序相关
/*
处理排序方式
*/
handleSort(command) {
this.sortMethod = command
this.loadAllPapers()
},
changeOrder() {
if (this.sortOrder.order === 'asc') {
this.sortOrder.order = 'desc'
this.sortOrder.name = '降序'
} else {
this.sortOrder.order = 'asc'
this.sortOrder.name = '升序'
}
this.loadAllPapers()
},
//查看论文相关信息
handlePaperLink(url){
this.$router.push({
name:'Content',
params:{
url:url
}
})
}
},
mounted() {
this.loadAllPapers()
}
}
##### 5、论文内容
主要方式是library界面通过传参把相关的url参数传到论文内容中来,然后这个页面主要通过ifram进行页面加载,具体代码如下
****
更新到6.1:iframe确实不太会用明天再学一下,然后把笔记本的抽屉给加上看看能不能行
**src/library/Content.vue**
最后
以上就是成就朋友为你收集整理的数据库期末项目开发心得(持续更新中)数据库期末项目开发心得的全部内容,希望文章能够帮你解决数据库期末项目开发心得(持续更新中)数据库期末项目开发心得所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复