使用vue-cli来构建一个vue项目
什么是vue-cli?有了它我们还需要webpack吗?
通过几天的学习都知道webpack的使用难度真的有点高,而且文档又很难阅读。而vue-cli是一个可用帮助我们直接进行开发vue的脚手架(帮助我们建立项目框架),使用了vue-cli之后,我们仍然需要像webpack那样去配置一些配置文件如webpack.config.js文件。像进行开发和dev-server仍然像使用webpack那样。所以,只要将vue-cli理解为,它就只是帮助我们进行构建vue的项目骨架就好了。
安装
npm install -g @vue/cli
npm install -g @vue/cli-service-global
在vue-cli3中,使用vue create 来创建一个新的项目,或者使用vue ui来图形化创建
但是这两个命令都是在vue-cli3.0才可以使用,所以如果全局安装了旧版的,先安装3.0版本(会有提示你这么做)
vue create cli1
然后会有提示你是否需要在淘宝镜像下进行更新,选择是或者否
再后,会提示你选择什么配置,选择默认就好
命令执行完毕后,进入刚刚建立的目录,然后执行
cd cli1
npm run serve
这样创建之后,项目目录结构如下:
如果执行vue ui
vue ui
会出现下面这个界面
如果要安装vue的插件,可以使用vue add 如
vue add router
但仅仅可以安装vue的插件,不能安装其他的包
如果使用vue-cli2.0来创建一个项目目录,使用
vue init webpack demo
它会提醒你需要安装一个插件,按照提示安装就好
然后一路提示就按默认就好,安装好后的项目目录如下
其中,build文件夹下面的是webpack的配置
build.js:是我们完成项目之后需要运行的, 可以将我们的项目文件打包成 静态文件,存放在项目根目录的 dist 文件夹中(现在目录里还没有这个文件夹,build的时候会自动生成)。
check-versions.js:主要是检查一些所依赖的工具的版本是否适用,如nodejs、npm,若版本太低则会提示出来。
logo.png:存放的是vuelogo图片。
utils.js:提供工具函数,包括生成处理各种样式语言的loader,获取资源文件存放路径的工具函数。
vue-loader.conf.js: 引入了utils.js ,应该是用于切换开发模式和生产模式的文件,以便于用不同模式来解析loader。
webpack.base.conf.js:此配置文件是vue开发环境的wepack相关配置文件,主要用来处理各种文件的配置。
webpack.dev.conf.js:在webpack.base.conf的基础上增加完善了开发环境下面的配置。
webpack.prod.conf.js:构建的时候用到的webpack配置来自webpack.prod.conf.js,该配置同样是在webpack.base.conf基础上的进一步完善。
config文件夹是webpack的环境配置
src文件夹是我们开发代码放置的地方
assets:主要存放一些静态图片资源的目录。
components:这里存放的是开发需要的的各种组件,各个组件联系在一起组成一个完整的项目。
router:存放了项目路由文件。
App.vue:是项目主组件,也是项目所有组件和路由的出口,之后它会被渲染到项目根目录的 index.html 中显示出来,我们可以在这里写一些适合全局的css样式。
main.js:入口文件,引入了vue模块和app.vue组件以及路由router,我们需要在全局使用的一些东西也可以定义在这里面。
static文件夹存放的是项目的静态文件。
test文件夹是测试文件目录,unit是单元测试,为每个组件编写单元测试,e2e是端到端的测试
.babelrc:ES6语法编译配置。
.editorconfig:代码编写规格。
.eslintignore:项目的根目录中创建文件来告诉ESLint忽略特定的文件和目录,该文件是纯文本文件。
.eslintrc.js:eslint的配置文件,eslint是用来管理和检测js代码风格的工具,可以和编辑器搭配使用,如vscode的eslint插件,当有不符合配置文件内容的代码出现就会报错或者警告。
.gitignore:忽略的文件。
.postcssrc.js:兼容选项(如果已经安装postcss,需要一大堆loader配置,这时项目根目录会生成“.postcssrc.js”文件)。
index.html:项目文件入口。
package.json:项目及工具的依赖配置文件。
README.md:项目说明。
因为我们的后台管理系统需要用到element,所以需要安装element ui
vue add element
弹出来的选择,
选择全局引入
yes
选择语言
安装结束后的目录结构如下
然后还需要安装vuex
vue add vuex
因为要跟后台进行交互,需要用到axios,所以也要安装axios
vue add axios
好了现在开始正式编程了
首先先来配置一些文件
在根目录下面建立一个vue.config.js文件,用来配置项目构建的一些选项
module.exports = {
// 部署生产环境和开发环境下的URL。
// 默认情况下,Vue CLI 会假设你的应用是被部署在一个域名的根路径上
//例如 https://www.my-app.com/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.my-app.com/my-app/,则设置 baseUrl 为 /my-app/。
publicPath: "/",
outputDir: "dist",
assetsDir: "assets",
// indexPath: "myIndex.html",
//默认情况下,生成的静态资源在它们的文件名中包含了 hash 以便更好的控制缓存。你可以通过将这个选项设为 false 来关闭文件名哈希。(false的时候就是让原来的文件名不改变)
filenameHashing: false,
// lintOnSave:{ type:Boolean default:true } 问你是否使用eslint
lintOnSave: true,
//如果你想要在生产构建时禁用 eslint-loader,你可以用如下配置
// lintOnSave: process.env.NODE_ENV !== 'production',
//是否使用包含运行时编译器的 Vue 构建版本。设置为 true 后你就可以在 Vue 组件中使用 template 选项了,但是这会让你的应用额外增加 10kb 左右。(默认false)
runtimeCompiler: true,
/**
* 如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建。
* 打包之后发现map文件过大,项目文件体积很大,设置为false就可以不输出map文件
* map文件的作用在于:项目打包后,代码都是经过压缩加密的,如果运行时报错,输出的错误信息无法准确得知是哪里的代码报错。
* 有了map就可以像未加密的代码一样,准确的输出是哪一行哪一列有错。
* */
productionSourceMap: false,
devServer: {
host: "localhost",
port: 8888, // 端口号
open: true, //配置自动启动浏览器
}
};
接下来写登录页面
要求每次访问都要先确定是否已经登录,如果已经登录就可以继续访问(登录的时间有一个失效)
那么我们可以将路由指向登录组件,每次都检查是否已经登录
此处需要用到了vue的导航守卫,beforeEach。
如果是按照上面的步骤进行安装配置的话,那么,vue-cli已经帮我们将router配置了,在src下面的main.js中,可以看到Import router from "./router",而且也已经全局注册了这个router,于是我们打开router目录,打开下面的index.js,可以发现这里已经使用了vuerouter了,我们现在所需要做的工作就是
在routes里面添加我们的路由
编写导航守卫
第一步比较简单,就是将所有的路由写到routes里面
const routes = [
{
path: '/login',
component: Login
},
{
path:"/news",
component:News
}
];
什么是导航守卫?
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
// ...
})
根据我的理解就是在在跳转的过程当中进行控制,一共有三个参数,第一个参数是即将要跳转的路由,第二个参数是当前的路由,next是用它来触发跳转完成的钩子。
router.beforeEach((to, from, next) => {
if (to.path == '/login') {
sessionStorage.removeItem('user');
}
let user = JSON.parse(sessionStorage.getItem('user'));
if (!user && to.path != '/login') {
next({ path: '/login' })
} else {
next()
}
});
上面的逻辑就是,首先判断当前要跳转的是不是登录页面,如果是,就移除掉session中user的选项。
然后在登录页面当中进行登录的话,就判断当前是否已经填写了user如果是就进行跳转。如果不是就重新跳转回登录页面
下面开始写登录页面
基本的页面布局如下
系统登录
登录
但是现在我们有两个任务,第一需要在输入的时候有验证并有相关的ui提示。第二如果输入正确以后存入session中并跳转到主页面中。
先来做第一件事情,输入的时候规定一些格式进行验证,等输入完毕之后,按登录这个键的时候,就要判断输入的账号密码是否是我们先前所设置好的,如果是则进行新的跳转,如果不是,仍然输出一些提示要重新输入
系统登录
登录
我们这里首先使用element的validate来进行是否输入的验证
加黑的部分就是element的validate的部分了,这里遇到了一个bug,就是el-form元素的v-model属性,刚开始设定的是v-model的时候,会出现一些bug,但是当换成:model的时候就解决了这个bug,所以我就开始思考,:model和v-model的区别是什么?
V-model是双向数据绑定,一可以是设置这个组件的值,二是可以通过它来传值给子组件。(父子组件都绑定一个数据,并且子组件中的prop是value)
而:model是v-bind:model的缩写,也就是说,这个model属性是element的的form表单的一个属性,我们通过将这个model传给form这个表单组件,然后经过element的相关绑定。
接下来就是将数据发送给后端进行验证了。
首先,在vue.config.js中如下设置
let path = require("path");
module.exports = {
// 部署生产环境和开发环境下的URL。
// 默认情况下,Vue CLI 会假设你的应用是被部署在一个域名的根路径上
//例如 https://www.my-app.com/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.my-app.com/my-app/,则设置 baseUrl 为 /my-app/。
publicPath: "/",
outputDir: "dist",
assetsDir: "assets",
//指定生成的 index.html 的输出路径 (打包之后,改变系统默认的index.html的文件名)
// indexPath: "myIndex.html",
//默认情况下,生成的静态资源在它们的文件名中包含了 hash 以便更好的控制缓存。你可以通过将这个选项设为 false 来关闭文件名哈希。(false的时候就是让原来的文件名不改变)
filenameHashing: false,
// lintOnSave:{ type:Boolean default:true } 问你是否使用eslint
lintOnSave: true,
//如果你想要在生产构建时禁用 eslint-loader,你可以用如下配置
// lintOnSave: process.env.NODE_ENV !== 'production',
//是否使用包含运行时编译器的 Vue 构建版本。设置为 true 后你就可以在 Vue 组件中使用 template 选项了,但是这会让你的应用额外增加 10kb 左右。(默认false)
runtimeCompiler: true,
/**
* 如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建。
* 打包之后发现map文件过大,项目文件体积很大,设置为false就可以不输出map文件
* map文件的作用在于:项目打包后,代码都是经过压缩加密的,如果运行时报错,输出的错误信息无法准确得知是哪里的代码报错。
* 有了map就可以像未加密的代码一样,准确的输出是哪一行哪一列有错。
* */
productionSourceMap: false,
devServer: {
host: "localhost",
port: 8888, // 端口号
open: true, //配置自动启动浏览器
proxy: {
'/php': {
target: 'http://localhost/myvuecli/vuecli1/php',//设置你调用的接口域名和端口号 别忘了加http
changeOrigin: true,
pathRewrite: {
'^/php': ''//这里理解成用‘/api’代替target里面的地址,后面组件中我们掉接口时直接用api代替 比如我要调用'http://40.00.100.100:3002/user/add',直接写‘/api/user/add’即可
}
}
}
}
};
然后在使用axios就好
然后发现这样使用axios,php接收不到传过来的参数,
if (this.userform.user && this.userform.password) {
console.log(this.userform.user,this.userform.password);
axios.post("/php/login.php",{
user:this.userform.user,
password:this.userform.password
}).then(function (data) {
console.log(data);
}).catch(function (data) {
console.log(data);
});
}
然后改成了下面的写法
if (this.userform.user && this.userform.password) {
console.log(this.userform.user,this.userform.password);
let params = new URLSearchParams();
params.append('user', this.userform.user);
params.append('password', this.userform.password);
axios.post("/php/login.php",params).then(function (data) {
console.log(data);
}).catch(function (data) {
console.log(data);
});
}
这个时候就可以接收到数据了,那么接下来就是进行判断返回的数据并进行处理
服务器返回的数据如果是true证明账户密码正确,否则就是账户密码错误或者其他的错误
我们先封装一个文件,里面全部都是跟服务器进行交互的函数,这样方便我们进行管理
在src目录下面新建一个文件夹api,这这里新建一个api.js文件,内容如下
import axios from "axios";
export const requestLogin = params => {
return axios.post(`/php/login.php`, params);
};
//存放跟服务器进行交互的函数
这样在需要引入的时候,就引入这个文件然后使用这个函数就好。例如我们的login组件需要用到这个requestLogin,就在login组件中这样写
import { requestLogin } from '../api/api';
export default {
name: "login",
data(){
return {
userform:{
user:"",
password:""
},
rules:{
user:[
{
required:true,
message:"请输入账户",
trigger:"blur"
},
],
password:[
{
required:true,
message:"请输入密码",
trigger:"blur"
},
]
}
}
},
methods:{
checkPass(){
//将pass发送给后台进行验证
//先写一个PHP文件跟这里进行交互,使用axios
if (this.userform.user && this.userform.password) {
console.log(this.userform.user,this.userform.password);
let params = new URLSearchParams();
params.append('user', this.userform.user);
params.append('password', this.userform.password);
requestLogin(params).then(res => {
let data = res.data;
if (data){
sessionStorage.setItem('user', JSON.stringify(this.userform.user));
this.$router.push({ path: '/news' });
} else {
//账户或密码错误
this.$message.error('账户或密码错误!');
}
}).catch(function (data) {
console.log(data);
});
}else {
this.$message.error('请输入账号密码!');
}
}
}
}
这样校验成功后就跳转到了管理页面
现在是这样,我希望在管理页面中,头部,侧边栏都是一样的,但是每个侧边栏所对应的内容又是不一样的。怎么实现呢?
不懂就学,去vue-route中看一下路由信息
vue-route中一共有嵌套路由,命名路由,
这里使用嵌套路由,首先在router下面的index.js文件中的routers定义里面,我们主要有两个大的router,
一个是login一个是admin,我们将所有管理页面项目都作为admin的儿子,写在admin的children属性里面,如下:
const routes = [
{
path: '/login',
component: Login,
hidden:true
},
{
path:"/",
component:Admin,
name:"主页",
iconCls:"el-icon-message",
hidden:false,
children:[
{
path:"/home",
component:Home,
name:"管理主页",
icon:"iconfont icon-zhuye"
},
{
path:"/news",
component:News,
name: "管理新闻",
icon:"iconfont icon-xinwen"
},
{
path:"/recoder",
component:Recoder,
name:"管理记录仪",
icon:"iconfont icon-techreport-"
},
{
path:"/video",
component:Viedo,
name:"管理视频",
icon:"iconfont icon-shipin"
},
{
path:"/recurit",
component:Recurit,
name:"管理招聘",
icon:"iconfont icon-recruit"
},
{
path:"/company",
component:Company,
name:"管理企业信息",
icon:"iconfont icon-qiyejianjie"
}
]
}
];
这样我们在admin组件当中,写头部,侧边栏,在侧边栏里面写router-link,在我们的显示区域写router-view,就可以实现了。如下:
{{child.name}}
上面中的icon里面是我自己引入的字体,引入步骤如下:
引入字体:https://blog.csdn.net/sleepwalker_1992/article/details/82818970
遇到的问题
1.:model 和v-model,答案:el-form中要使用:model
2.:src动态绑定资源不显示:答案
:src="require(`../assets/`+editForm.img)">
不适用也可以,这里的主要问题不是required,而是304
如何解决src动态绑定图片不显示的问题?()
找了好多答案都不行,我快烦死了,但是要冷静冷静。
ok,慢慢来分析
首先,先看看浏览器有没有加载到这张图片,如果没有加载到就是路径问题
然后看到返回状态码为304 Not Modified,网上说这个是条件请求,。
当客户端缓存了目标资源但不确定该缓存资源是否是最新版本的时候, 就会发送一个条件请求。在进行条件请求时,客户端会提供给服务器一个If-Modified-Since请求头,其值为服务器上次返回响应头中Last-Modified值,还会提供一个If-None-Match请求头,值为服务器上次返回的ETag响应头的值。
服务器会读取到这两个请求头中的值,判断出客户端缓存的资源是否是最新的,如果是的话,服务器就会返回HTTP/304 Not Modified响应头, 但没有响应体.客户端收到304响应后,就会从本地缓存中读取对应的资源。 所以:当访问资源出现304访问的情况下其实就是先在本地缓存了访问的资源。
然后我勾选了不缓存,发现
图片是已经加载过来,但是它的类型居然是text/html,这又是为什么?难道是跟webpack模块化,把所有资源当做模块来看待的原因吗?
网上找不到答案,于是我打开这个图片,发现它会被跳转到登录页面,也就是说,这个图片被当做是一个页面。所以我猜测应该是跟webpack模块化有关系。
所以,这里的原因应该就是,图片路径没有被正确编译了。
靠,最后直接在:src上面添加require就成功了。我搞了一下午。呜唧唧。
那为什么之前将参数传入一个函数,然后这个函数返回require(url)是不成功呢?而且后面我不要require也可以的,这里也不知道为什么了。
3.ok现在有一个需求就是上传图片,
element有一个上传的组件el-upload,在这里有几个点需要强调一下。
(1)因为我限制了一次只能上传一张,所以当我选中了一张以后,不想要,想要上传另外一张,但是此时因为已经选中了一张,所以需要手动的点击已经选中图片的右上角的的叉号,把它移除掉了再进行删除。这个交互感觉不太好,所以我在每次用户点击上传的按钮的时候,我就清空一下已经选择的文件,在选取文件的按钮里面进行绑定。
selectChange(){
this.fileList = [];
},
(2)上传时候的限制,限制类型,限制大小。
限制类型我们可以在el-upload里面加一个accept属性,指定可以接收的类型,这样当你进行选取文件的时候,就会只看到这些类型的文件。
ref="uploadimg2" name="homesamimg" :on-success="uploadSuccess" :on-error="uploadFaile" :file-list="fileList" :before-upload="beforeUpload2"
accept=".jpg,.jpeg,.png,.gif,.JPG,.JPEG,.GIF,.PNG">
(3)限制大小,在before-upload指定的函数里面,首先计算这个文件的大小,如果超过了一定的限制,就返回false。官网指定只要返回false上传就会被终止。
beforeUpload(file){
const filesize = file.size;
const fileList = filesize/1024<=600;
if (!fileList){
this.fileSize = false;
this.$message.error("文件大于600kb,请重新选择");
this.fileList = [];
}else {
this.editBannerForm.img = ("img/"+String(file.name));
this.fileSize = true;
}
},
(4)第四个需要注意的是,这里上传的的name属性是跟php文件里面$_FILES[]对应的,不然会获取不到文件。
4.表格里面的批量删除
首先在表格前面的多选框,指定一下selection-change这个事件的处理函数,那么每次被选择之后,element会自动把当前所有被选择的项传给这个函数当做参数,所以,这table中指定事件,然后将这个事件穿回来的数据赋值给我们的数据,得到了index后传给后台就可以了。如下
border @selection-change="handleSelectionChange" :row-class-name="tableRowClassName"
:fit="Boolean(1)" empty-text="-">
handleSelectionChange(val) {
this.multipleSelection = val;
},
在确认批量删除的时候,遍历这个数组,跟当前所有的数据进行对比,然后得到index。下面的isEqual是我自己写的函数,它的作用主要就是判断两个对象是否相等。多选的时候只保存选中的index(遍历两个数组,找到index)
this.multipleSelection.map((value)=>{
this.allnews.map((news,index)=>{
if (this.isEqual(value,news)){
this.multipleSelectionIndex.push(index);
}
})
});
那么这个时候,我们只需要把这个数组传给后台就可以了。
5、使用文本编辑器,使用wangeditor,并进行配置,不能插入图片和视频
首先先安装, npm install wangeditor
然后新建一个editor组件,这里我放到了components里面,在这个组件里面,引入激活wangeitor
import E from "wangeditor";
export default {
name: "editor",
data(){
return {
editor:null
}
},
props: {
value: {
default: ""
}
},
mounted(){
this.editor = new E(this.$refs.editor);
this.editor.customConfig.menus = [//自定义菜单
'head', // 标题
'bold', // 粗体
'fontSize', // 字号
'fontName', // 字体
'italic', // 斜体
'underline', // 下划线
'strikeThrough', // 删除线
'foreColor', // 文字颜色
'backColor', // 背景颜色
'link', // 插入链接
'list', // 列表
'justify', // 对齐方式
'quote', // 引用
'table', // 表格
'code', // 插入代码
'undo', // 撤销
'redo' // 重复
]
this.editor.customConfig.onchange = (html)=>{
//获取内容
this.$emit("input",html);
};
this.editor.create();
this.editor.txt.html(this.value)//设置内容
}
}
https://blog.csdn.net/weixin_42869548/article/details/82589253
https://www.kancloud.cn/wangfupeng/wangeditor3/335777
6、布局的问题(左右布局)float的问题,解决不了
(使用父子组件之间传参进行设置)这么麻烦的可能也只有我来做了。
但是当前第一次点击的时候会有延迟,还是有待改进。
首先现在父组件中绑定
然后在子组件中传送
activated:function () {
let that = this;
setTimeout(function () {
that.sendDataToAdmin();
console.log("recruit:"+that.$refs.content.offsetHeight);
},0);
}
methods:{
sendDataToAdmin(){
this.$emit("changeAsideHeight",this.$refs.content.offsetHeight);
},
}
上面的意思就是,子组件在被选中显示的时候,就执行sendDataToAdmin这个函数,为什么要用setTimeOut呢,因为我需要当前页面显示完成的时候再传送过去,只有这样,页面的高度才会被完全撑开。此时我们再把高度传过来就可以了。
然后在admin组件中获取这个高度,设置aside的高度就可以了