信息学院本科生创新项目总结
信息学院本科生创新项目总结报告
韩晓鹏
一、 研究目的与意义
在初步学习完html,css,javascript前端三大件之后,拥有了一定的网页编程基础,为了更好的实现前端对页面的布局以及需求,基于vue框架等技术实现一个简单的至少实现用户的登录,注册,以及用户信息的修改的前端优美界面,该框架具有良好的可适配性,并且使自身对前端的知识有更加深入的了解,完成本次创新项目的总结汇报,同时也方便之后使用vue进行开发项目。
通过使用vue框架,vue-cli可视化操作,axios发送监听请求,fastmock接口创建随机数据,sass简化对界面美化所书写的代码来掌握前端程序员的基本能力,理解vue执行程序时所需要的依赖、跨域处理以及route使用的操作目的,学会使用vue的模块开发,路由,状态管理等新特性。借此来完成一个简单的前端登录操作界面。
二、 研究内容与总体方案(或技术路线)
研究内容:
具有三个界面(登入,申请,home),每个界面要求布局美观,功能完整,每个界面布局美观,结构完整,所有的前端代码应该划分清楚,使用 vue-cli 创建工程,分清楚组件,具有工程化的目录结构。
技术栈:
- vue,vue-cli脚手架使用,vue-route路由
- 使用element-ui简化界面设计
- axios实现跨域请求以及后台处理(虽然本次实验过程最终没有实现与mysql相连)
- mysql数据库
- util正则匹配
- html,css,js
- Webpack打包使用
- express 框架应用(只是了解)
- sass/less样式库(引入包,未使用)
总体方案:
本项目使用vscode实现
项目创建使用vue-cli脚手架,可视化创建,并使用vue-route路由管理器使不同界面得以交互,让构建单页面应用变得易如反掌,使用sass/less预处理语言,扩展了 CSS 语言,增加了变量、Mixin、函数等特性,简化代码书写,导入element-ui包,并可视化创建页面源代码,并设计效果,使用util正则匹配实现输入判断,利用mysql储存后台数据,node,express,webpack创建后台框架,并用axios将前端vue与后台有机联系起来传递数据。
同时,使用postman软件来监测网页,提供请求和调试功能,方便更好的实现前后端分离。
在vue调试方面,可以选择安装chrome插件vue Devtools。打开vue项目,在console控制台选择vue面板。
用npm工具来安装vue-router:npm install vue-router
通过import导入并定义Vue模块、vue-router模块和需要使用的组件。
1.启动项目使用npm run dev 或 npm start
2.添加路由时:index.js中配置路由,并导出路由实例,在main.js中导入路由配置,并与vue实例关联到一起,最后在App.vue添加路由出口,路由中设置三个主界面,登录,注册,主页面,使用route来切换不同的页面。当用户点击 router-link 标签时,会去寻找它的 to 属性, 它的 to 属性和 js 中配置的路径{ path: '/home', component: Home} path 一一对应,从而找到了匹配的组件, 最后把组件渲染到 <router-view> 标签所在的地方。
3.安装element-ui组件库,npm i element-ui或者直接在package.json文件中加入element-ui对应版本,项目在login,register.vue, 添加给 DOM元素或组件,将来可以通过 this.$refs.ref的值 来获取到这个DOM对象或组件。然后,就可以进行DOM操作或获取组件数据、调用组件方法。
4. 在创建路由规则的时候,要配置子路由规则,而不是普通的路由,通过路由中的 children 属性来配置的,在需要切换内容的组件中,添加一个子路由的出口
5.获取用户数据的时候:在 data 配置中,添加数据 userList: [],在 created 钩子函数中,发送请求,获取用户列表数据,根据接口文档,找到接口和需要的参数,将接口返回的数据交给 userList,修改 el-table 表格组件中的 prop 属性,使其与接口返回的数据匹配
6.给搜索按钮绑定单击事件时,在事件中获取到文本框中输入的搜索内容 根据搜索内容,调用查询接口,查询数据。新功能对老功能的影响,也就是说:添加了一个新的功能进来,实现后,要自己测试一下有没有对老的功能产生影响
7.登录界面有输入用户名,输入密码,输入验证码三个模块,使用组件中的submitForm,handleCommand,randomNum,refreshCode,makeCode方法实现对登录用户,登录密码以及验证码的验证的效果等。
8.修改密码或者个人数据的时候,向后台发送请求,以此来判断修改是否合理,如果合理则确认修改成功,跳转成功界面,失败则会弹窗。
9.组件的样式说明:
如果产生影响,会到组件之间的样式冲突、覆盖等问题
Vue 中提供了一个属性 scoped,只要给 组件的style标签添加这个属性,那么,样式就只会对当前组件生效,而不会影响其他组件。所以,推荐在使用组件的时候,给style标签,添加scoped属性!!!
scoped 原理:动态给组件中的标签添加一个 data-v-组件唯一标识属性,并且样式中自动添加了属性选择器。因为每个组件的唯一标识是不一样的,所以,样式就只会对当前组件生效了。
10.scss以及less:使用@mixin和@include简化css代码,可以定义一个整个样式中重复使用的样式,以及将该样式混合引入css文件,同时mixin和include同时也允许$name:value传递参数,类似于javascript中的function功能一样,实现函数构造。
11. history模式:在使用vscode开发过程中可以去掉网页后缀的“#”,只需要在配置中添加mode:“history”,但是不能打包,一旦打包就会出现其他的一些问题。
12.vue+express实现前后端分离,搭建简易后台,虽说最后axios因为跨域问题没有做,基于node.js后端框架,负责路由,业务逻辑,数据库操作,页面和数据响应,即架构中的业务层,对前端的请求进行响应,需要数据库的拉取数据库内容,需要判断处理的返回处理结果,请求页面文件的返回html文件
13. 使用vue中http请求,安装:npm install vue-resource –save
14. vue模板中放入太多的逻辑(方法)会让模板过重且难以维护,使用计算属性computed可以让模板变得简洁易于维护。通过关键词computed属性对象中定义一个函数,并返回一个值,使用计算属性时和data中的数据使用方式一致。
三、 研究结果(四号,黑体,加粗)
1. 登录界面:
登录界面由账号,密码以及验证码组成,系统会根据输入对后台发送post请求,并通过请求得到的数据与输入的数据进行验证核对,验证成功返回success,失败则返回false,并弹框提示
2. 注册界面:
注册界面由用户名称,账号名称,密码,确认密码,邮箱,手机号码,身份证号码,出生日期,性别组成,使用element-ui的方式简化代码书写,并通过element-ui特有的方式,对出生日期,性别进行验证以及响应布局。在确认密码时会调用confirm函数获取上一次输入的密码进行核对。并用正则表达式来对手机号码,身份证号,邮箱有效验证,提高效率,并使用v-model使得填入的内容与表单传递的数据保持一致
3. 成功界面:
4. 主界面:
主界面由三部分构成head,sidebar,还有content内容,头部使用header.vue来渲染组件,侧边栏使用Sidebar.vue来渲染,侧边栏使用element-ui中的el-menu属性以及结合v-if,v-for等属性来实现动作效果,content内容则从知乎页面copy下来的,当然也可以换成其他的内容。
5. 修改密码:
修改密码同之前注册界面的页面设计一样的
6. 修改账户信息:
修改账户信息的功能,其中用户名称设置为“禁止填入状态”,其他操作内容与之前一致
7. 结构目录:
|— build 构建脚本目录
|— build.js 生产环境构建(编译打包)脚本
|— check-versions.js 版本验证工具
|— dev-client.js
|— dev-server.js
|— utils.js 构建相关工具方法(主要用来处理css类文件的loader)
|— vendor-manifest.json
|— vue-loader.conf.js 处理vue中的样式
|— webpack.base.conf.js webpack基础配置
|— webpack.dev.conf.js webpack开发环境配置
|— webpack.dll.conf.js
|— webapck.prod.conf.js webpack生产环境配置
|— config 项目配置
|— dev.env.js 开发环境变量
|— index.js 主配置文件
|— prod.env.js 生产环境变量
|— node_modules 项目依赖模块
|— screenshots
|— wms1.png 图片1
|— wms2.png 图片2
|— service 后台
|— api Api请求
|— userApi.js 请求数据库
|— db
|— db.js 连接数据库
|— sqlMap.js mysql数据库数据修改语言
|— app.js 后台执行程序
|— src 项目源码目录
|— assets 资源目录,资源会被webpack构建
|— logo.png
|— components 公共组件目录
|— common 组件1
|— Header.vue 头部组件
|— Home.vue 主页内容
|— Sidebar.vue 侧边栏
|— page 页面
|— Login.vue 登录组件
|— Password.vue 重置密码
|— user.vue 用户个人中心
|— Register.vue 注册界面
|— Success.vue 成功界面
|— Content.vue 中心内容
|— routers 前端路由目录
|— index.js
|— utils
|— utils.js
|— App.vue 根组件
|— main.js 入口js文件
|— static 纯静态资源,不会被webpack构建,eg:没有npm包模块
|— test 测试
|— unit 单元测试
|— e2e e2e测试
|— .babelrc babel的配置文件
|— .editorconfig 编辑器的配置文件
|— .gitignore git的忽略配置文件
|— .postcssrc.js postcss的配置文件
|— index.html html模板,入口页面
|— package.json npm包配置文件,依赖包信息
|— README.md 项目介绍
Home.vue:
<template> <div class="wrapper"> <v-head></v-head> <v-sidebar></v-sidebar> <div class="content"> <transition name="move" mode="out-in"><router-view></router-view></transition> </div> </div> </template> <script> import vHead from './Header.vue'; import vSidebar from './Sidebar.vue'; export default { components:{ vHead, vSidebar } } </script>
Header.vue:
<template> <div class="header"> <div class="logo">登录管理系统</div> <div class="user-info"> <el-dropdown trigger="click" @command="handleCommand"> <span class="el-dropdown-link"> <img class="user-logo" src="../../../static/img/img.jpg"> {{username}} </span> <el-dropdown-menu slot="dropdown"> <el-dropdown-item command="userCenter">个人中心</el-dropdown-item> <el-dropdown-item command="loginout">退出</el-dropdown-item> </el-dropdown-menu> </el-dropdown> </div> </div> </template> <script> export default { data() { return { name: '韩晓鹏' } }, computed:{ username(){ let username = sessionStorage.getItem('ms_username'); return username ? username : this.name; } }, methods:{ handleCommand(command) {//handle if(command == 'loginout'){ sessionStorage.removeItem('ms_username') sessionStorage.removeItem('ms_userId') this.$router.push('/login'); } else if (command == 'userCenter') { this.$router.push('/userCenter'); } } } } </script> <style scoped> .header { position: relative; box-sizing: border-box; width: 100%; height: 70px; font-size: 22px; line-height: 70px; color: #fff; } .header .logo{ float: left; width:250px; text-align: center; } .user-info { float: right; padding-right: 50px; font-size: 16px; color: #fff; } .user-info .el-dropdown-link{ position: relative; display: inline-block; padding-left: 50px; color: #fff; cursor: pointer; vertical-align: middle; } .user-info .user-logo{ position: absolute; left:0; top:15px; width:40px; height:40px; border-radius: 50%; } .el-dropdown-menu__item{ text-align: center; } </style>
SideBar.vue:
<template> <div class="sidebar"> <el-menu :default-active="onRoutes" class="el-menu-vertical-demo" theme="dark" unique-opened router> <template v-for="item in items"> <template v-if="item.subs"> <el-submenu :index="item.index"> <template slot="title"><i :class="item.icon"></i>{{ item.title }}</template> <el-menu-item v-for="(subItem,i) in item.subs" :key="i" :index="subItem.index">{{ subItem.title }} </el-menu-item> </el-submenu> </template> <template v-else> <el-menu-item :index="item.index"> <i :class="item.icon"></i>{{ item.title }} </el-menu-item> </template> </template> </el-menu> </div> </template> <script> export default { data() { return { items: [ { icon: 'el-icon-setting', index: 'readme', title: '简介' }, { icon: 'el-icon-setting', index: 'userCenter', title: '设置', subs: [ { index: 'modifyUser', title: '修改用户' }, { index: 'modifyPassword', title: '修改密码' } ] } ] } }, computed:{ onRoutes(){ return this.$route.path.replace('/',''); } } } </script> <style scoped> .sidebar{ display: block; position: absolute; width: 250px; left: 0; top: 70px; bottom:0; background: #2E363F; } .sidebar > ul { height:100%; } </style>
Login.vue:
<template> <div class="login-wrap"> <div class="ms-title">登录管理系统</div> <div class="ms-login"> <el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="0px" class="demo-ruleForm"> <div v-if="errorInfo"> <span>{{errInfo}}</span> </div> <el-form-item prop="name"> <el-input v-model="ruleForm.name" placeholder="账号" ></el-input> </el-form-item> <el-form-item prop="password"> <el-input type="password" placeholder="密码" v-model="ruleForm.password" @keyup.enter.native="submitForm('ruleForm')"></el-input> </el-form-item> <el-form-item prop="validate"> <el-input v-model="ruleForm.validate" class="validate-code" placeholder="验证码" ></el-input> <div class="code" @click="refreshCode"> <s-identify :identifyCode="identifyCode"></s-identify> </div> </el-form-item> <div class="login-btn"> <el-button type="primary" @click="submitForm('ruleForm')">登录</el-button> </div> <p class="register" @click="handleCommand()">注册</p> </el-form> </div> </div> </template> <script> export default { name: 'login', data() { return {//默认界面信息 identifyCodes: "1234567890", identifyCode: "", errorInfo : false, ruleForm: { name: '', password: '', validate: '' }, rules: { name: [ { required: true, message: '请输入用户名', trigger: 'blur' } ], password: [ { required: true, message: '请输入密码', trigger: 'blur' } ], validate: [ { required: true, message: '请输入验证码', trigger: 'blur' } ] } } }, mounted() { this.identifyCode = ""; this.makeCode(this.identifyCodes, 4);//验证码 }, methods: { submitForm(formName) { // debounceAjax(formName) const self = this; this.$router.push('/readme') }, handleCommand() { this.$router.push('/register'); }, randomNum(min, max) {//ran数字 return Math.floor(Math.random() * (max - min) + min); }, refreshCode() {//ran验证刷新 this.identifyCode = ""; this.makeCode(this.identifyCodes, 4); }, makeCode(o, l) {//ran验证 for (let i = 0; i < l; i++) { this.identifyCode += this.identifyCodes[ this.randomNum(0, this.identifyCodes.length) ]; } console.log(this.identifyCode); } } } </script> <style scoped> .login-wrap{ position: relative; width:100%; height:100%; } .ms-title{ position: absolute; top:50%; width:100%; margin-top: -230px; text-align: center; font-size:30px; color: #fff; } .ms-login{ position: absolute; left:50%; top:50%; width:300px; height:240px; margin:-150px 0 0 -190px; padding:40px; border-radius: 5px; background: #fff; } .ms-login span { color: red; } .login-btn{ text-align: center; } .login-btn button{ width:100%; height:36px; } .code { width: 112px; height: 35px; border: 1px solid #ccc; float: right; border-radius: 2px; } .validate-code { width: 136px; float: left; } .register { font-size:14px; line-height:30px; color:#999; cursor: pointer; float:right; } </style>
Register.vue:
<template> <div> <div class="crumbs crumbs-register"> <el-breadcrumb separator="/" class="register-title"> <el-breadcrumb-item><i class="el-icon-setting"></i>注册</el-breadcrumb-item> </el-breadcrumb> </div> <div class="userContent"> <el-form ref="form" :model="form" :rules="rules" label-width="80px"> <el-form-item prop="name" label="用户名称"> <el-input v-model="form.name" placeholder="请输入用户名称"></el-input> </el-form-item> <el-form-item prop="account" label="账号名称"> <el-input v-model="form.account" placeholder="请输入账号"></el-input> </el-form-item> <el-form-item prop="pass" label="密码"> <el-input v-model="form.pass" type="password" placeholder="请输入密码"></el-input> </el-form-item> <el-form-item prop="checkPass" label="确认密码"> <el-input v-model="form.checkPass" type="password" placeholder="请再次输入密码"></el-input> </el-form-item> <el-form-item prop="email" label="邮箱"> <el-input v-model="form.email" placeholder="请输入邮箱"></el-input> </el-form-item> <el-form-item prop="phone" label="手机"> <el-input v-model="form.phone" placeholder="请输入手机号"></el-input> </el-form-item> <el-form-item prop="card" label="身份证"> <el-input v-model="form.card" placeholder="请输入身份证号"></el-input> </el-form-item> <el-form-item prop="birth" label="出生日期"> <el-col :span="24"> <el-date-picker type="date" placeholder="选择日期" v-model="form.birth" value-format="yyyy-MM-dd" style="width: 100%;"></el-date-picker> </el-col> </el-form-item> <el-form-item prop="sex" label="性别"> <el-select class="select-sex" v-model="form.sex" placeholder="请选择性别"> <el-option label="男" value="man"></el-option> <el-option label="女" value="woman"></el-option> </el-select> </el-form-item> <el-form-item> <el-button type="primary" @click="onSubmit('form')">确定</el-button> <el-button @click="onCancle()">取消</el-button> </el-form-item> </el-form> </div> </div> </template> <script> import Util from '../../utils/utils'; export default { data() { var validatePass = (rule, value, callback) => { if(value === '') { callback(new Error('请输入密码')); } else { if(this.form.checkPass !== '') { this.$refs.form.validateField('checkPass'); } callback(); } }; var validatePass2 = (rule, value, callback) => { if(value === '') { callback(new Error('请再次输入密码')); } else if (value !== this.form.pass) { callback(new Error('两次输入的密码不一致')); } else { callback(); } }; var validateEmail = (rule, value, callback) => { if (value === '') { callback(new Error('请输入邮箱')); } else if (!Util.emailReg.test(this.form.email)){ callback(new Error('请输入正确的邮箱')); } else { callback(); } }; var validatePhone = (rule, value, callback) => { if (value === '') { callback(new Error('请输入手机号')); } else if (!Util.phoneReg.test(this.form.phone)){ callback(new Error('请输入正确的手机号')); } else { callback(); } }; var validateCard = (rule, value, callback) => { if (value === '') { callback(new Error('请输入身份证号')); } else if (!Util.idCardReg.test(this.form.card)){ callback(new Error('请输入正确的身份证号')); } else { callback(); } }; return { form: { name: '', account: '', pass: '', checkPass: '', email: '', phone: '', card: '', birth: '', sex: '' }, rules: { name: [ { required: true, message: '请输入用户名', trigger: 'blur' } ], account: [ { required: true, message: '请输入账号', trigger: 'blur' } ], pass: [ { validator: validatePass, trigger: 'blur' } ], checkPass: [ { validator: validatePass2, trigger: 'blur' } ], email: [ { validator: validateEmail, trigger: 'blur' } ], phone: [ { validator: validatePhone, trigger: 'blur' } ], card: [ { validator: validateCard, trigger: 'blur' } ], birth: [ { required: true, message: '请输入出生日期',type: 'date', trigger: 'blur' } ], sex: [ { required: true, message: '请输入性别', trigger: 'blur' } ] } } }, methods:{ onSubmit(formName) { const self = this; this.$router.push('/register-success'); }, onCancle() { this.$router.push('/login'); }, getDateTimes(str) { var str = new Date(str); return str; } } } </script> <style> .crumbs-register { background-color: #324157; height: 50px; line-height: 50px; } .register-title { line-height: 50px; margin: 0 auto; width: 50px; font-size: 16px; } .userContent { width: 400px; margin: 0 auto; } .select-sex { width: 320px; } </style>
router/index.js:
import Vue from 'vue';
import Router from 'vue-router';
Vue.use(Router);
export default new Router({
routes: [
{
path: '/',
redirect: '/login'
},
{
path: '/readme',
component: resolve => require(['../components/common/Home.vue'], resolve),
children:[
{
path: '/',
component: resolve => require(['../components/page/Readme.vue'], resolve)
},
{
path: '/userCenter',
component: resolve => require(['../components/page/UserCenter.vue'], resolve) // 拖拽列表组件
},
{
path: '/modifyUser',
component: resolve => require(['../components/page/ModifyUser.vue'], resolve)
},
{
path: '/modifyPassword',
component: resolve => require(['../components/page/ModifyPassword.vue'], resolve)
},
{
path: '/success',
component: resolve => require(['../components/page/Success.vue'], resolve)
}
]
},
{
path: '/register',
component: resolve => require(['../components/page/Register.vue'], resolve)
},
{
path: '/register-success',
component: resolve => require(['../components/page/RegisterSuccess.vue'], resolve)
},
{
path: '/login',
component: resolve => require(['../components/page/Login.vue'], resolve)
},
],
mode:"history"
})
service/api/userApi.js
var models = require('../db/db'); var express = require('express'); var router = express.Router(); var mysql = require('mysql'); var $sql = require('../db/sqlMap'); var conn = mysql.createConnection(models.mysql); conn.connect(); var jsonWrite = function(res, ret) { if(typeof ret === 'undefined') { res.send('err'); } else { console.log(ret); res.send(ret); } } var dateStr = function(str) { return new Date(str.slice(0,7)); } // 增加用户接口 router.post('/addUser', (req, res) => { var sql = $sql.user.add; var params = req.body; console.log(params); console.log(params.birth); conn.query(sql, [params.name, params.account, params.pass, params.checkPass, params.email, params.phone, params.card, dateStr(params.birth), params.sex], function(err, result) { if (err) { console.log(err); } if (result) { jsonWrite(res, result); } }) }); //查找用户接口 router.post('/login', (req, res) => { var sql_name = $sql.user.select_name; // var sql_password = $sql.user.select_password; var params = req.body; if (keywords.name) { sql_name += " where username ='"+ keywords.name +"'"; console.log(sql_name); } conn.query(sql_name, keywords.name, function(err, result) { if (err) { console.log(err); } // console.log(result); if (result[0] === undefined) { res.send('-1') //查询不出username,data 返回-1 } else { var resultArray = result[0]; console.log(resultArray); console.log(keywords); if(resultArray.password === params.password) { jsonWrite(res, result); } else { res.send('0') //username } } }) }); //获取用户信息 router.get('/getUser', (req, res) => { var sql_name = $sql.user.select_name; // var sql_password = $sql.user.select_password; var params = req.body; console.log(params); if (params.name) { sql_name += "where username ='"+ params.name +"'"; } conn.query(sql_name, params.name, function(err, result) { if (err) { console.log(err); } // console.log(result); if (result[0] === undefined) { res.send('-1') //查询不出username,data 返回-1 } else { jsonWrite(res, result); } }) }); //更新用户信息 router.post('/updateUser', (req, res) => { var sql_update = $sql.user.update_user; var params = req.body; console.log(params); if (params.id) { sql_update += " email = '" + params.email + "',phone = '" + params.phone + "',card = '" + params.card + "',birth = '" + params.birth + "',sex = '" + params.sex + "' where id ='"+ params.id + "'"; } conn.query(sql_update, params.id, function(err, result) { if (err) { console.log(err); } console.log(result); if (result.affectedRows === undefined) { res.send('更新失败,请联系管理员') //查询不出username,data 返回-1 } else { res.send('ok'); } }) }); //更改密码 router.post('/modifyPassword', (req, res) => { var sql_modify = $sql.user.update_user; var params = req.body; console.log(params); if (params.id) { sql_modify += " password = '" + params.pass + "',repeatPass = '" + params.checkPass + "' where id ='"+ params.id + "'"; } conn.query(sql_modify, params.id, function(err, result) { if (err) { console.log(err); } // console.log(result); if (result.affectedRows === undefined) { res.send('修改密码失败,请联系管理员') //查询不出username,data 返回-1 } else { res.send('ok'); } }) }); module.exports = router;
四、 参考文献(四号,黑体,加粗)
- 用户管理系统 https://gitee.com/leonima/vue-shop-1/tree/master
- Vue初级项目:https://gitee.com/wangzlls/a-simple-vue-entry-project?_from=gitee_search
- node连接mysql生成接口,vue通过接口实现数据操作:https://www.cnblogs.com/yyzhiqiu/p/12659644.html
- axios中文说明:https://www.kancloud.cn/yunye/axios/234845
- vue跨域处理:https://www.cnblogs.com/lihaohua/p/12372267.html
- sass操作手册:https://www.sass.hk/
- postman使用详解:https://blog.csdn.net/fxbin123/article/details/80428216
- vue和element-ui使用:https://blog.csdn.net/qq_37164847/article/details/80939741
- element操作系统:https://gitee.com/nmgwap/vueproject?_from=gitee_search
- vue+elementUI+Node+MySQL搭建用户登录页面:https://blog.csdn.net/joyvonlee/article/details/90039215