方式一
序号 | 说明 | 命令 |
---|---|---|
1 | 初始化一个项目 | vue init webpack 项目名称 |
2 | 初始化完成后不要选择使用npm下载,选择手动下载–>No,I will handle that myself | No,I will handle that myself |
3 | 先用cd命令进入项目的路径 | cd 项目目录 |
4 | 接着npm安装 | npm install |
5 | 项目测试运行 | npm run dev |
D:\WorkSpace\webWorkSpace>vue init webpack 20200319vuerouter
? Project name 20200319vuerouter
? Project description A Vue.js project
? Author Prometheusxiao <iapetosee@163.com>
? Vue build standalone
? Install vue-router? Yes
? Use ESLint to lint your code? No
? Set up unit tests No
? Setup e2e tests with Nightwatch? No
? Should we run `npm install` for you after the project has been created? (recommended) no
D:\WorkSpace\webWorkSpace>cd 20200319vuerouter
D:\WorkSpace\webWorkSpace\20200319vuerouter>npm install
D:\WorkSpace\webWorkSpace\20200319vuerouter>npm run dev
方式二
序号 | 说明 | 命令 |
---|---|---|
1 | 创建一个新项目 | vue create 项目名 |
2 | Manually select features(手动选择项目特性) | 选择 |
3 | 配置 | 参考图1.1.1与图1.1.2 |
4 | 试运行 | npm run serve |
图1.1.1
Check the features needed for your project: (Press to select, to toggle all, to invert selection)
[为你的项目选择需要的特性,按下空格去选择单项,按下a去选择全部,i反选]
◉ Babel [在项目中把es6转化成所有浏览器能识别的es5代码]
◯ TypeScript [micrsoft出的一个js库,能做类型校验]
◯ Progressive Web App (PWA) Support[把网页做的更像原生app]
◉ Router [vue路由插件]
◉ Vuex [vue状态管理,vue的数据库]
◉ CSS Pre-processors [css预处理器]
◯ Linter / Formatter [js代码校验/格式化]
◯ Unit Testing [单元测试,提前找出bug]
◯ E2E Testing [end to end test 端对端测试,测试全流程]
图1.1.2
Vue CLI v4.2.3
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Vuex, CSS Pre-processors
? Use history mode for router? (Requires proper server setup for index fallback in production) No
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Less
? Where do you prefer placing config for Babel, ESLint, etc.? In package.json
? Save this as a preset for future projects? No
参考地址.
1. 路由组件放在view或者pages文件夹下
2. 非路由组件放在components文件夹下
3. 路由配置命名index.js 放在router文件夹下
# 控制台
npm install bootstrap --save
# main.js
import "bootstrap/dist/css/bootstrap.css"
参考链接: vue中导入bootstrap.css
import About from '@/pages/About' //@ 代表webpack.base.conf.js中的别名设置 相当于src
import Home from '../pages/Home' //另外一种方式
import News from '../pages/News'
import Messages from '../pages/Messages'
//引入vue
import Vue from 'vue'
//引入路由
import Router from 'vue-router';
//引入路由组件
import About from '@/pages/About' //@ 代表webpack.base.conf.js中的别名设置 相当于src
import Home from '../pages/Home' //另外一种方式
import News from '../pages/News'
import Messages from '../pages/Messages'
//使用路由器
Vue.use(Router)
//Vue.use(Router)就可以在任何组件内通过 this.$router 访问路由器,也可以通过 this.$route 访问当前路由
//配置路由具体映射关系
export default new Router({
routes: [
{
path: '/about',
name: 'About',
component: About
},
{
path: '/home',
name: 'Home',
component: Home,
children:[
{
path: '/home/news', //path中的斜杠永远代表根路径,标准写法
name: 'News',
component: News
},
{
path: 'messages', //path中的斜杠永远代表根路径,简单写法
name: 'Messages',
component: Messages
},
{
path: '', //配置默认路由 '' 就代表当前
redirect: '/home/news'
}
]
},
{
path: '/', //配置默认路由
redirect: './home'
}
]
})
import Vue from 'vue'
import App from './App'
//注入配置好的路由
import router from './router' //因为是index名称,就不用./router/index.js了
import 'bootstrap/dist/css/bootstrap.css'
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router, //这个是对象默认属性名称一样是可以简写,不一样时就:router: 你自己的名称,就是import那个名字
components: { App },
template: ' '
})
//连接
<router-link to="****" class="list-group-item">About</router-link>
//渲染
<router-view></router-view>
使用keep-alive标签
<keep-alive>
<router-view></router-view>
</keep-alive>
解决方法增加当前路由的监听
<template>
<div>
详情页面 - {{$route.params.id}}
<h4>{{data.title}}</h4>
<p>{{data.content}}</p>
<p style="width: 20px">{{data.id}}</p>
</div>
</template>
<script>
export default {
name: "message-detail",
mounted(){
this.getContent();
//路由组件创建后只执行一次后续点击不会改变数据,所以要监听这个路由组件的变动
},
methods:{
getContent() {
let id = this.$route.params.id;
let data = this.msgs.find(element => element.id == id);
this.data = data;
}
},
data() {
return {
msgs:[
{id: 1, title: 'messages1', content: '哈哈'},
{id: 2, title: 'messages2', content: '嘻嘻'},
{id: 3, title: 'messages3', content: '兔兔'}
],
data:{}
}
},
watch:{
//监听当前路由
$route:function(newRoute){
let id = newRoute.params.id;
this.data = this.msgs.find(element => element.id == id);
}
}
}
</script>
<style scoped>
</style>
route代表路由组件映射,router代表路由器
// 1. 注意不是this.$route而是this.$router!!!
// 2. this.$router.push(path): 相当于点击路由链接(可以返回到当前路由界面)
// 3. this.$router.replace(path): 用心路由替换当前路由(不可以返回当前路面界面)
// 4. this.$router.back(): 请求(返回)上一个记录路由
// 5. this.$router.go(-1): 请求(返回)上一个记录路由
methods: {
pushShow(id){
this.$router.push(`/home/messages/detail/${id}`);//值就是路由路径值
},
replaceShow(id){
this.$router.replace(`/home/messages/detail/${id}`);//值就是路由路径值
}
}
一定注意:npm install vuex --save 不然搞个卵
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex) //声明使用
const state = {
count: 0
}
const mutations = {
increment(state){
state.count+=1;
},
decrement(state){
state.count-=1;
},
incrementIsOdd(state){
if(state.count%2!=0){
state.count += 1;
}
},
incrementIsAsync(state){
state.count+=1;
}
}
const actions = {
increment({commit}){
commit('increment')
},
decrement({commit}){
commit('decrement')
},
incrementIsOdd({commit}){
commit('incrementIsOdd')
},
incrementIsAsync({commit}){
setTimeout(()=>{
commit('incrementIsAsync')
},1000);
}
}
const getters = {
evenOrOdd(state){
return state.count%2==0?"偶数":"奇数"
}
}
/**
* state : 类似vue中的data
* actions : 类似vue中的methods,注意action可执行异步操作,但是mutation必须同步执行 --> 随意了
* mutations : 就是actions与state的中继件,注意action可执行异步操作,但是mutation必须同步执行 --> 最好全大写
* getters : 类似vue的计算属性
*/
const store = new Vuex.Store({
state,
mutations,
actions,
getters
});
export default store;//向外默认暴露store对象
import Vue from 'vue'
import App from './App'
import store from './store'
Vue.config.productionTip = false
new Vue({
el: '#app',
store,
components: { App },
template: ' '
})
方式一:
<template>
<div id="app">
<P> click {{$store.state.count}} times,count is {{evenOrOdd}}</P>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="incrementIsOdd">if odd</button>
<button @click="incrementIsAsync">async</button>
</div>
</template>
<script>
export default {
name: 'App',
computed:{
evenOrOdd:function(){
return this.$store.getters.evenOrOdd
}
},
methods:{
increment(){
this.$store.dispatch('increment')
},
decrement(){
this.$store.dispatch('decrement')
},
incrementIsOdd(){
this.$store.dispatch('incrementIsOdd')
},
incrementIsAsync(){
this.$store.dispatch('incrementIsAsync')
}
}
}
</script>
<style>
</style>
方式二:
<template>
<div id="app">
<P> click {{count}} times,count is {{evenOrOdd2}}</P>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="incrementIsOdd">if odd</button>
<button @click="incrementIsAsync">async</button>
</div>
</template>
<script>
import {mapState,mapActions,mapGetters} from 'vuex'
export default {
name: 'App',
computed:{
//...mapGetters(['evenOrOdd2']),
//名字对应不上怎么办
...mapGetters({'evenOrOdd':'evenOrOdd2'}),
// const getters = {
// evenOrOdd2(state){
// return state.count%2==0?"偶数":"奇数"
// }
// }
...mapState(['count'])
},
methods:{
...mapActions(['increment','decrement','incrementIsOdd','incrementIsAsync'])
}
}
</script>
<style>
</style>
vuex模块化
==vuex中的store分模块管理,需要在store的index.js中引入各个模块,为了解决不同模块命名冲突的问题, 同模块的namespaced:true,之后在不同页面中引入getter、actions、mutations时,需要加上所属的模块名
==
使用vuex模块中的数据
export default {
name: 'App',
computed: {
xx: function () {
return this.$store.state.authentication.authenticated;
}
},
mounted(){
console.log(this.$store.state.authentication.authenticated);
console.log(this.$store.state.layout.test);
console.log(this.$store.state.menu.test);
}
}
/**
* 跨域问题过滤器
*/
@Bean
public FilterRegistrationBean corsFilter(){
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.addAllowedHeader("*");
UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
corsConfigurationSource.registerCorsConfiguration("/**",corsConfiguration);
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(corsConfigurationSource));
bean.setOrder(1);//在安全过滤器之前
return bean;
}
正式环境是在一块的就没有跨域的问题了,再说还有nginx这个玩意
const proxy = require('http-proxy-middleware');//必须引入否则搞死你也不知道为什么没有效果
module.exports = {
devServer: {
proxy: {
'/api':{
target: 'http://localhost:8888',//代理地址,这里设置的地址会代替axios中设置的baseURL
changeOrigin: true,// 如果接口跨域,需要进行这个参数配置
//必须是pathRewrite不是这个冒牌的pathRequiresRewrite!!!!!
pathRewrite: {
'^/api': '/'
}
}
}
}
}
watch:{
searchDate:{
deep: true,//开启深度监听
handler: (newV,oldV) => {
}
},
searchDate:(newV,oldV) => {
}
},
<template>
<div>
<div style="text-align: right">
<el-pagination v-on:size-change="handleSizeChange"
v-on:current-change="handleCurrentChange"
:current-page="currentPageComponent"
:page-sizes="[10,20, 50, 100, 200]"
:page-size="pageSizeComponent"
layout="total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
</div>
</div>
</template>
<script>
export default {
name: "page-paging",
props: {
total: {
type: Number,
default: 0,
required: true,
validator: function (value) {
return value >= 0
}
},
currentPage: {
type: Number,
default: 1,
required: true,
validator: function (value) {
return value >= 0
}
},
pageSize: {
type: Number,
default: 10,
required: true,
validator: function (value) {
return value >= 0
}
}
},
mounted(){
this.currentPageComponent = this.currentPage;
this.pageSizeComponent = this.pageSize;
},
methods: {
handleSizeChange(val) {
this.pageSizeComponent = val ;
this.$emit('pageComponentChange',this.currentPageComponent,val);
},
handleCurrentChange(val) {
this.currentPageComponent = val ;
this.$emit('pageComponentChange',val,this.pageSizeComponent);
}
},
data() {
return {
currentPageComponent:1,
pageSizeComponent:10
}
}
}
</script>
<style scoped>
</style>
调用
<template>
<div>
<paging :total="total" :currentPage="currentPage" :pageSize="pageSize" @pageComponentChange="pageChange(arguments)"></paging>
</div>
</template>
<script>
import paging from '@/components/page/paging.vue'
export default {
name: "a",
components:{
paging
},
methods: {
pageChange(pageInfo){
this.currentPage = pageInfo[0];
this.pageSize = pageInfo[1];
this.getData()
},
},
data() {
return {
total: 0,
currentPage:1,
pageSize:10 ,
}
}
}
</script>
<style scoped>
</style>
<template>
<div>
<!-- append-to-body 嵌套dialog 必须指定这个为true 否则会出现遮罩层显示问题-->
<!--:destroy-on-close="true" 必须添加这个销毁这个el-dialog 否则 视频流将继续播放!!!很总要-->
<el-dialog :visible.sync="showDialog" :close-on-click-modal="true" :close-on-press-escape="true" :before-close="cancelDialog" :destroy-on-close="true" top="6%" width="70%" :append-to-body="true">
<div v-if="boxNum === 1" style="text-align: center">
<el-image style="height: 100%" :src="url" ></el-image>
</div>
<div v-if="boxNum === 2" style="text-align: center">
<audio id="audiobox" :src="url" autoplay controls loop></audio><!--muted 属性静音-->
</div>
<div v-if="boxNum === 3" style="text-align: center">
<video id="videobox" :src="url" style="height: 100%" controls loop><!--muted 属性静音-->
</video>
</div>
</el-dialog>
</div>
</template>
<script>
export default {
name: "img-audio-video-file",
methods: {
show: function (url) {
this.url = url;
let png = this.url.lastIndexOf(".png");
let jpg = this.url.lastIndexOf(".jpg");
let jpeg = this.url.lastIndexOf(".jpeg");
if(png > -1 || jpg > -1 || jpeg > -1){
this.boxNum = 1;
this.showDialog = true;
return;
}
let mp4 = this.url.lastIndexOf(".mp4");
if(mp4 > -1){
this.boxNum = 3;
this.showDialog = true;
return;
}
let mp3 = this.url.lastIndexOf(".mp3");
if(mp3 > -1){
this.boxNum = 2;
this.showDialog = true;
return;
}
window.open(url, "_blank");
},
cancelDialog(){
console.log("img-audio-video-file cancelDialog");
if(this.boxNum === 3){
let videobox = document.getElementById("videobox");
if(videobox){
videobox.pause();
}
}
if(this.boxNum === 2){
let audiobox = document.getElementById("audiobox");
if(audiobox){
audiobox.pause();
}
}
this.showDialog = false;
}
},
beforeDestroy() {
console.log("img-audio-video-file beforeDestroy");
if(this.boxNum === 3){
let videobox = document.getElementById("videobox");
if(videobox){
videobox.pause();
}
}
if(this.boxNum === 2){
let audiobox = document.getElementById("audiobox");
if(audiobox){
audiobox.pause();
}
}
},
data() {
return {
boxNum:1,
showDialog: false,
url:''
}
}
}
</script>
<style scoped>
</style>
调用
<template>
<div>
<iavf ref="iavfbox"></iavf>
</div>
</template>
<script>
import iavf from '@/components/media/img-audio-video-file.vue'
export default {
name: "a",
components:{
iavf
},
methods: {
showFile(url){
this.$refs['iavfbox'].show(url)
}
}
}
</script>
<style scoped>
</style>
<template>
<div v-loading.fullscreen.lock="loading" v-cloak>
<div style="text-align: left">
<el-upload
multiple
ref="uploadFile"
:action="uploadUrl"
:headers="importHeaders"
:auto-upload="false"
:limit="fileMax"
:file-list="fileListFJ"
:on-exceed="handleOnExceed"
:before-upload="checkFile"
:on-success="successFile"
:on-remove="removeFile"
:on-preview="showFile">
<!-- :on-preview="lookFile" 查看上传了的文件-->
<!-- 多选文件 multiple -->
<el-button slot="trigger" size="small" type="primary">选取文件</el-button>
<el-button style="margin-left: 10px;" size="small" type="success" @click="submitFile">上传到服务器
</el-button>
<div><p style="font-size: x-small;color: #999999">注:允许上传{{fileMax}}个文件,允许格式是{{filePermit}}!</p></div>
</el-upload>
</div>
<iavf ref="iavfbox"></iavf>
</div>
</template>
<script>
import iavf from '@/components/media/img-audio-video-file.vue'
export default {
name: "upload-file",
components:{
iavf
},
created(){
this.importHeaders = {
Authorization:this.authenticated
}
},
mounted(){
this.initFile(this.files);
},
props: {
//已经存在的文件数组
fileList: {
type: String,
default:null
},
//最大允许上传的文件数量
fileMax: {
type: Number,
default: 1,
required: true,
validator: function (value) {
return value >= 0
}
},
//格式校验
filePermit: {
type: String,
default: 'JPG,JPEG,PNG,TXT,XLSX,XLS,DOCX,DOC,PPT,POTX,POTM,PPA,POT,PPSX,PPTX,MP4'
}
},
computed: {
authenticated() {
return this.$store.getters['authentication/getAuthenticated'];
},
baseUrl() {
return this.$store.getters['authentication/getBaseUrl'];
},
baseFileUrl() {
return this.$store.getters['authentication/getBaseFileUrl'];
},
files(){
return this.fileList;
}
},
methods: {
initFile(files){
this.fileListFJ = [];
this.fileResult = [];
if(files && files.length>0){
this.$axios({
url:`/api/file/getByIds`,
method: 'POST',
data:{
fileIds:files
}
}).then(respanse => {
let data = respanse.data.data.data;
if(data){
for(let i=0;i<data.length;i++){
let dataFile = data[i];
let file = {
name:dataFile.originalName,
url:this.baseFileUrl+"user/"+dataFile.id,
status:"success"
};
this.fileListFJ.push(file);
this.fileResult.push(dataFile.id)
}
}
}).catch(error => {
this.$message({ showClose: true, message: '查询错误,请检查网络后重试', duration:1500});
});
}
},
checkFile: function (file) {
//文件检查
if(file.name){
let checkPermit = this.filePermit;
let index = file.name.lastIndexOf(".");
let len = file.name.length;
let suffix = file.name.substring(index+1,len);
if(suffix){
let check = checkPermit.indexOf(suffix.toUpperCase())>-1?true:false;
if(!check){
this.$message.error('上传的文件不合法!');
}
return check;
} else {
this.$message.error('请选择上传的文件!');
return false;
}
} else {
this.$message.error('请选择上传的文件!');
return false;
}
},
handleOnExceed(){
this.$message({ type: 'warning',showClose: true, message: "超出最大允许上传的文件数量!", duration:1500});
},
successFile: function (response, file, fileList) {
let id = response.data.data[0];
if(id){
this.fileResult.push(id);
}
},
removeFile(file, fileList) {
//新上传的文件--> 新的有response但是没有url
let index = -1;
let id = null;
if(file.response){
id = file.response.data.data[0];
index = this.fileResult.findIndex(item => item === id);
}
//旧的已经存在的文件 --> 有url但是包含了请求域名
let url = file.url;
if(url){
id = url.replace(this.baseFileUrl+"user/","");
index = this.fileResult.findIndex(item => item === id);
}
if(index > -1){
this.fileResult.splice(index,1);
}
this.deleteFile(id);
//没有上传的文件 --> 不用处理因为本身没有调用successFile所有fileResult中是没有数据的
},
submitFile() {
this.$refs.uploadFile.submit(); //提交文件
},
deleteFile(id){
if(id !=null ){
this.loading = true;
this.$axios({
url:`/api/file/delete`,
method: 'POST',
data: {
id:id
}
}).then(respanse => {
this.$message({ showClose: true, message: respanse.data.msg==null?"删除成功": respanse.data.msg, duration:1500});
this.loading = false;
}).catch(error => {
this.loading = false;
this.$message({ showClose: true, message: '查询错误,请检查网络后重试', duration:1500});
});
}
},
showFile(file){
let url = file.url;
if(url != null){
this.$refs['iavfbox'].show(url);
return;
}
let resUrl = file.response.data.data[0];;
if(resUrl != null){
this.$refs['iavfbox'].show(this.baseFileUrl+"user/"+resUrl);
return;
}
this.$message({ showClose: true, message: '文件没有上传', duration:1500});
},
updateFiles(){
if(this.fileResult.length>0){
this.$emit('updateFiles',this.fileResult.join(","));
} else {
this.$emit('updateFiles',"");
}
}
},
watch:{
files:{
deep: true,
handler:function (newVla) {
this.initFile(newVla);
}
},
fileResult:{
deep:true,
handler:function (newVal) {
//监听数据的变化更新父组件数据
this.updateFiles();
}
}
},
data() {
return {
loading: false,
importHeaders:'',
uploadUrl:'/api/file/add',
fileListFJ:[],//初始化文件列表
fileResult:[],//文件上传返回值
}
}
}
</script>
<style scoped>
</style>
调用
<template>
<div>
<!--fileList 已经存在的文件id,逗号分割-->
<!--filePermit 合法文件格式-->
<!-- 最大允许文件数量-->
<uploadfile :fileList="文件id逗号分割的" :fileMax="9" :filePermit="'JPG,JPEG,PNG,MP4'" @updateFiles="updateFiles(arguments)" ></uploadfile>
</div>
</template>
<script>
import uploadfile from '@/components/file/upload-file.vue'
export default {
name: "a",
components:{
uploadfile
},
methods: {
updateFiles(value){
let fileResult = value[0];//返回的文件id,逗号分割的
}
}
}
</script>
<style scoped>
</style>
/*
这样修改有效
*/
<style>
.el-transfer-panel {
width: 599.9px;
}
</style>
/*
这样修改无效
*/
<style scoped>
.el-transfer-panel {
width: 599.9px;
}
</style>
因为第三方组件的样式是在别的文件中引进的,加了scoped限制之后写的样式只能在本文件中起作用,是没办法覆盖第三方组件自带的样式的,所以要去掉
放在mounted中Error in callback for immediate watcher "height"错误要放到created生命周期中
height:879px;overflow-y:auto;overflow-x:hidden;
<el-tab-pane label="入库信息" name="inventory" style="height:879px;overflow-y:auto;overflow-x:hidden;">
</el-tab-pane>
Vue-router 报NavigationDuplicated的可能解决方案
出现这个问题,控制台会报 [NavigationDuplicated {_name: “NavigationDuplicated”, name: “NavigationDuplicated”}]。其原因在于Vue-router在3.1之后把$router.push()方法改为了Promise。所以假如没有回调函数,错误信息就会交给全局的路由错误处理,因此就会报上述的错误。
如果你仔细观察并复现了多次错误你会发现,vue-router是先报了一个Uncaught (in promise)的错误(因为push没加回调),然后再点击路由的时候才会触发NavigationDuplicated的错误(路由出现的错误,全局错误处理打印了出来)。
解决方案
方案1
固定vue-router版本到3.0.7以下。这个方案没什么说的,就是简单粗暴,没有任何理由。但是你能确保以后不升级vue-router吗?
方案2
禁止全局路由错误处理打印,这个也是vue-router开发者给出的解决方案:
import Router from 'vue-router'
const originalPush = Router.prototype.push
Router.prototype.push = function push(location, onResolve, onReject) {
if (onResolve || onReject) return originalPush.call(this, location, onResolve, onReject)
return originalPush.call(this, location).catch(err => err)
}
把这段代码放在引入vue-router之后就行,一般在main.js里,如果你的路由单独抽取出来了,那可能在其他的路由文件中。
方案3(高成本高收益)
vue-router的开发者也给出了解决方法,你需要为每个router.push增加回调函数。
router.push('/location').catch(err => {err})
对于我们来说这个解决方案的成本可能很高,但是是值得的。在vue-router 3.1版本之前的push调用时不会返回任何信息,假如push之后路由出现了问题也不会有任何的错误信息。如果你使用方案1固定了vue-router的版本,那么以后的项目需要路由的回调时你根本无从下手。
方案4
如果你使用了Element-UI,并且方案2无法解决你的问题。那么你只能用方案1来固定你的vue-router版本了。这是因为Element-UI的el-menu在重复点击路由的时候报的错误,而且这个错误是Element-UI内部的路由问题,你无法通过方案2和3去解决。只能选择暂时不升级Vue-router。
好消息是Element-UI已经有了解决方案,预计在2.13.0版本会解决这个问题。
本段来源 https://www.cnblogs.com/rever/p/11577322.html