VUE3X
--卸载依赖 npm uninstall xxxx --element npm 8x使用下面这句 npm install --legacy-peer-deps element-ui --save --element依赖 npm install element-plus --save --官方 npm i element-ui -S --路由依赖 npm install vue-router@4 --vuex依赖 npm install vuex@next --save --axios依赖 npm install axios --axios低版本浏览器补丁es6-promise依赖包 npm install --save es6-promise --less依赖 npm install --save-dev less less-loader --cookie封装库 npm install --save js-cookie --状态管理安装 npm install --save vuex
关闭语法检查防止报错
Component name "Home" should always be multi-word vue/multi-word-component-names
const {defineConfig} = require('@vue/cli-service') module.exports = defineConfig({ transpileDependencies: true, lintOnSave: false //eslint是语法检查工具,限制太过于严格,大部分开发人员无法适应,所以需要加上false })
vue router
执行安装router
PS D:\WebStormProject\vue\vue-demo> npm install vue-router@4
npm install vue-router@4
可以在package.json看到添加的依赖
"dependencies": { "core-js": "^3.8.3", "vue": "^3.2.13", "vue-router": "^4.1.2" },
创建文件夹 view
创建router/index.js
import {createRouter,createWebHashHistory} from 'vue-router' // 1. 定义路由组件. // 也可以从其他文件导入 import Home from "@/views/Home"; import About from "@/views/About"; // 2. 定义一些路由 // 每个路由都需要映射到一个组件。 // 我们后面再讨论嵌套路由。 const routes = [ {path: '/', component: Home}, {path: '/about', component: About}, ] // 3. 创建路由实例并传递 `routes` 配置 // 你可以在这里输入更多的配置,但我们在这里 // 暂时保持简单 const router = createRouter({ // 4. 内部提供了 history 模式的实现。为了简单起见,我们在这里使用 hash 模式。 history: createWebHashHistory(), routes, // `routes: routes` 的缩写 }) //导出router export default router
在main.js里面使用router
import {createApp} from 'vue' import App from './App.vue' //导入router import router from "@/router"; // 1 挂载 2 使用 const app = createApp(App) app.use(router) app.mount('#app')
路由带参数
App.vue
<!--路由带参 /user/123 --> <router-link to="/user/123">Go to User</router-link>router/index.js
const routes = [ {path: '/', component: Home}, {path: '/about', component: About}, {path: '/user/:id', component: User} ]User.vue
<template> <div>用户</div> </template> <!-- 1 --> <script> export default { name: "User", mounted() { //当前活跃的router对象 console.log(this.$route.params.id); } } </script> <!-- 2 --> <script setup> import {useRoute} from 'vue-router' console.log(useRoute().params.id); </script> <style scoped> </style>
配置404 not found
<template> <h2>404 not found</h2> <p>找不到页面</p> </template> <script> export default { name: "NotFound" }
动态路由(参数)router/index.js
import NotFound from "@/views/NotFound"; const routes = [ //正则匹配 参数只能是数字 {path:'/user/:id(\\d+)', component: User}, //动态路由 参数 可有可无 不可重复叠加 {path:'/user/:id?', component: User}, //动态路由 参数 可有可无 可多可少 {path:'/user/:id?', component: User}, //动态路由多个参数 {path:'/news/:id+', component: News}, //动态路由 参数 可有可无 可多可少 {path:'/news/:id*', component: News}, //使用正则匹配方式 {path: '/:path(.*)',component: NotFound} ]
正则匹配规则
.匹配一个任意字符
\d
匹配0~9这样的数字
\D
则匹配一个非数字
\w
可以匹配一个字母、数字或下划线,w代表word
\s
可以匹配一个空格字符,注意空格字符不但包括空格,还包括tab字符(在Java中用\t
表示)
修饰符*可以匹配任意个字符 比如 \d*
修饰符+
可以匹配至少一个字符
修饰符?
可以匹配0个或一个字符
嵌套路由
Parent.vue
<template> <div> <h2>父组件parent</h2> <!-- children --> <router-link to="/parent/styleone">style one</router-link> <router-link to="/parent/styletwo">style two</router-link> <!-- 占位 --> <router-view></router-view> </div> </template>
router/index.js
const routes = [ //父组件路由 { path: '/parent', component: parent, children: [{ path: 'styleone', component: styleOne }, { path: 'styletwo', component: styleTwo } ] } ]
通过js跳转页面 编程式导航
export default { name: "Page", methods: { goPage: function () { console.log(this.$router) this.$router.push('/') } } }// 字符串路径 router.push('/users/eduardo') // 带有路径的对象 router.push({ path: '/users/eduardo' }) // 命名的路由,并加上参数,让路由建立 url router.push({ name: 'user', params: { username: 'eduardo' } }) // 带查询参数,结果是 /register?plan=private router.push({ path: '/register', query: { plan: 'private' } }) // 带 hash,结果是 /about#team router.push({ path: '/about', hash: '#team' })const username = 'eduardo' // 我们可以手动建立 url,但我们必须自己处理编码 router.push(`/user/${username}`) // -> /user/eduardo // 同样 router.push({ path: `/user/${username}` }) // -> /user/eduardo // 如果可能的话,使用 `name` 和 `params` 从自动 URL 编码中获益 // routes 里的path上面需要加name属性 router.push({ name: 'user', params: { username } }) // -> /user/eduardo // `params` 不能与 `path` 一起使用 router.push({ path: '/user', params: { username } }) // -> /user// 替换 不会保存history router.push({ path: '/home', replace: true }) // 相当于 router.replace({ path: '/home' })// 向前移动一条记录,与 router.forward() 相同 router.go(1) // 返回一条记录,与 router.back() 相同 router.go(-1) // 前进 3 条记录 router.go(3) // 如果没有那么多记录,静默失败 router.go(-100) router.go(100) //类似于 back() , forward()<router-link :to="{ name: 'user', params: { username: 'erina' }}"> User </router-link>
命名试图
<router-view class="view left-sidebar" name="LeftSidebar"></router-view> <router-view class="view main-content"></router-view> <router-view class="view right-sidebar" name="RightSidebar"></router-view>const router = createRouter({ history: createWebHashHistory(), routes: [ { path: '/', components: { default: Home, // LeftSidebar: LeftSidebar 的缩写 LeftSidebar, // 它们与 `<router-view>` 上的 `name` 属性匹配 RightSidebar, }, }, ], })
重定向和别名
const routes = [{ path: '/home', redirect: '/' }]
重定向的目标也可以是一个命名的路由:
const routes = [{ path: '/home', redirect: { name: 'homepage' } }]
甚至是一个方法,动态返回重定向目标:
const routes = [ { // /search/screens -> /search?q=screens path: '/search/:searchText', redirect: to => { // 方法接收目标路由作为参数 // return 重定向的字符串路径/路径对象 return { path: '/search', query: { q: to.params.searchText } } }, }, { path: '/search', // ... }, ]
别名
重定向是指当用户访问 /home
时,URL 会被 /
替换,然后匹配成 /
。那么什么是别名呢?
将 /
别名为 /home
,意味着当用户访问 /home
时,URL 仍然是 /home
,但会被匹配为用户正在访问 /
。
上面对应的路由配置为:
const routes = [{ path: '/', component: Homepage, alias: '/home' //{ path: '', component: UserList, alias: ['/people', 'list'] }, }]
路由组件传参(props)
//路由 const routes = [{ path: '/user/:id', component: User, props: true }] //script export default { props: ['id'], mounted() { console.log(this.id) } }
组合式api
<script setup> const props = defineProps({ id: String }) console.log("defineProps:" + props.id) </script>router/index.js
//shop const routes =[{ path: '/shop/:id*', components: { default: shopMain, shopTop, shopFooter }, props: {default: true, shopFooter: false, shopTop: false} }]导航守卫
const router = createRouter({ //内部提供了 history 模式的实现。为了简单起见,我们在这里使用 * 模式。 history: createWebHistory(), routes, // `routes: routes` 的缩写 }) router.beforeEach((to, from) => { // ... // 返回 false 以取消导航 return false })
全局前置守卫
// GOOD router.beforeEach((to, from, next) => { if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' }) else next() //next 就相当于一个同行证(可选的参数 next) })
每路守卫
//每路守卫 beforeEnter:(to,from,next)=>{ if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' }) else next() }
组件内的守卫
const UserDetails = { template: `...`, beforeRouteEnter(to, from) { // 在渲染该组件的对应路由被验证前调用 // 不能获取组件实例 `this` ! // 因为当守卫执行时,组件实例还没被创建! }, beforeRouteUpdate(to, from) { // 在当前路由改变,但是该组件被复用时调用 // 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候, // 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。 // 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this` }, beforeRouteLeave(to, from) { // 在导航离开渲染该组件的对应路由时调用 // 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this` }, } //注意 beforeRouteEnter 是支持给 next 传递回调的唯一守卫。对于 beforeRouteUpdate 和 beforeRouteLeave 来说,this 已经可用了,所以不支持 传递回调,因为没有必要了:
懒加载路由
// 将 // import UserDetails from './views/UserDetails' // 替换成 const UserDetails = () => import('./views/UserDetails')
状态管理(不使用vuex)
store/index.js
/* 公用数据 仓库管理 * 实现响应式(reactive) App组件通过 provide 提供 * */ import {reactive} from "vue"; const store = { state: reactive({ msg: "欢迎欢迎" }), updateMsg: function () { this.state.msg = "你好,再见。" } } export default store
App.vue
<template> <img alt="Vue logo" src="./assets/logo.png"> <home/> </template> <!--vue3 中设置状态管理--> <!-- provide/ inject 跨级通信管理。--> <script> import store from "@/store"; import Home from "@/views/Home"; export default { provide: { store }, components: { Home, } } <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>
views/Home.vue
<template> <div>{{ store.state.msg }}</div> <button @click="updateMsg">update msg</button> </template> <script> export default { name: "Home", inject: ['store'], methods: { updateMsg: function () { this.store.updateMsg() } } } </script>
vue router 前后端请求交互
export default { name:'login', methods:{ this.$store.dispatch("Login", this.loginForm).then(() => { this.$router.push({ path: this.redirect || "/" }).catch(()=>{}); }).catch(() => { this.loading = false; if (this.captchaEnabled) { this.getCode(); } }); } }
Fetch(原生js) 数据交互(前后端交互)
组件 Home.vue
<template> <div>{{ store.state.msg }}</div> <button @click="updateMsg">update msg</button> <p>home.vue datalist</p> <ul> <li v-for="item in dataList" :key="item.id">{{ item.movieName }}</li> </ul> <br/> <p>home.vue datalist</p> <ul> <li v-for="item in store.state.dataList" :key="item.id">{{ item.director }}</li> </ul> </template> <script> export default { name: "Home", inject: ['store'], data() { return { dataList: [] } }, created() { // 这里需要跨域访问 fetch('http://localhost:8081/moviecon/queryAll', {method: 'GET'}).then(function (res) { /*json() 将响应的body 解析json的promise */ // console.log(res.json()); return res.json() }).then((res) => { console.log(res); this.dataList = res.rows // this.store.state.dataList = res.rows //传给 index.js 里面是store仓库 this.store.updateDataList(res.rows) }) }, methods: { updateMsg: function () { this.store.updateMsg() } } } </script>
store/index.js
/* 公用数据 仓库管理 * 实现响应式(reactive) App组件通过 provide 提供 * */ import {reactive} from "vue"; const store = { state: reactive({ msg: "欢迎欢迎", dataList: [] }), updateMsg: function () { this.state.msg = "你好,再见。" }, updateDataList(data) { this.state.dataList = data console.log(this.state.dataList); } } export default store
App.vue
<template> <img alt="Vue logo" src="./assets/logo.png"> <home/> </template> <!--vue3 中设置状态管理--> <!-- provide/ inject 跨级通信管理。--> <script> import store from "@/store"; import Home from "@/views/Home"; export default { provide: { store }, components: { Home, } } </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>
fetch请求参数 常用配置选项:
method (String) : HTTP请求方法,默认为GET (GET、DELETE、POST、PUT)
body (String) : HTTP请求参数
headers (Object) : HTTP的请求头,默认为{}
axios
安装
npm install axios
// 为给定 ID 的 user 创建请求 axios.get('/user?ID=12345') .then(function (response) { console.log(response); }).catch(function (error) { console.log(error); }); // 上面的请求也可以这样做 axios.get('/user', { params: { ID: 12345 } }).then(function (response) { console.log(response); }).catch(function (error) { console.log(error); }); //post axios.post('/user', { firstName: 'Fred', lastName: 'Flintstone' }).then(function (response) { console.log(response); }).catch(function (error) { console.log(error); }); // 获取远端图片、文件(当获取文件时候,responceType必须是stream,默认是json) axios({ method:'get', url:'http://bit.ly/2mTM3nY', responseType:'stream' }).then(function(response) { // node.js端的文件转存 response.data.pipe(fs.createWriteStream('ada_lovelace.jpg')) });
跨域访问(浏览器的同源策略)可以使用 代理 proxy 或者 jsonp
Home.vue
<template> </template> <script> import axios from "axios"; export default { name: "Home", created() { //axios 基于promise的html库 /* 猫眼电影 热映 数据 Request URL: https://i.maoyan.com/api/mmdb/movie/v3/list/hot.json?ct=%E6%97%A5%E7%85%A7&ci=228&channelId=4 */ axios.get('/path/api/mmdb/movie/v3/list/hot.json?ct=%E6%97%A5%E7%85%A7&ci=228&channelId=4').then((res)=>{ console.log(res); }) } } </script>
vue.config.js
const {defineConfig} = require('@vue/cli-service') module.exports = defineConfig({ transpileDependencies: true, lintOnSave: false, devServer: { proxy: { '/path': { target: 'https://i.maoyan.com', changeOrigin: true, rewrite: path => path.replace(/^\/path/, '') } } } })devServer: { // host: 'localhost', port: 8090, //项目运行的端口号 open: true, //配置自动启动浏览器 proxy: { "/opAdmin": { target: "http://116.66.65.193:8081", //对应服务端的接口地址 changeOrigin: true, // 开启跨域 pathRewrite: { "^/opAdmin": "/opAdmin" //将以 /opAdmin 开头的接口重写http://116.66.65.193:8081/opAdmin ,调用时直接以 /opAdmin开头即表示调用http://116.66.65.193:8081/opAdmin // "^/opAdmin": "/" //将以 /opAdmin 开头的接口重写http://116.66.65.193:8081/ ,调用时直接以 /opAdmin开头即表示调用http://116.66.65.193:8081/ // "^/opAdmin": "" //将以 /opAdmin 开头的接口重写http://116.66.65.193:8081 ,调用时直接以 /opAdmin开头即表示调用http://116.66.65.193:8081 } }, "/DoorStatus": { target: "http://47.99.11.102:8088", //对应服务端的接口地址 changeOrigin: true } } } }
axios prototype 挂载 http
main.js
import axios from "axios"; //挂载 axios 原型 Vue.prototype.$http = axios axios.defaults.baseURL=''Login.vue
<script> export default { name: 'Login', <!-- …… refs element语法 --> methods: { resetLoginFrom: function () { this.$refs.LoginformRef.resetFields(); }, submitLoginForm: function () { this.$refs.LoginformRef.validate(async (valid) => { //valid boolean if (valid) { //axois 使用$http发起请求 传参 //返回一个 promise 可以用 await方法 方法用async //const result = await this.$http.post('login', this.Loginform) const {data: res} = await this.$http.post('login', this.Loginform) console.log(res) } else { console.log('error'); return false; } }); } } } </script>
axios 前后端交互
export default { name: 'Login', data() { return { //绑定数据对象 Loginform: { username: 'admin', password: '123456' }, rules: { username: [ {required: true, message: '请输入用户名', trigger: 'blur'}, {min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur'} ], password: [ {required: true, message: '请输入密码', trigger: 'blur'}, {min: 6, message: '至少6个字符', trigger: 'blur'} ] } } }, methods: { resetLoginFrom: function () { this.$refs.LoginformRef.resetFields(); }, submitLoginForm: function () { this.$refs.LoginformRef.validate(async (valid) => { //valid boolean if (valid) { this.$axios({ method: 'GET', url: '/path/user/login', params: { username: this.Loginform.username, password: this.Loginform.password } }).then(async (res) => { console.log(await res.data); this.$message.success('成功') }).catch((err) => { return err }) } else { console.log('error'); return false; } }); } } }
@GetMapping("/login") public ResultData login(String username, String password) { User user = userService.selectUserByLoginName(username); if (user.getPassword().equals(password) && user != null) { return ResultData.success(user); } return ResultData.error(400, "error", null); }
@RequestBody可以用在POST方法不可以用在GET,axios中的 param:{} 改为 data:{}
vuex
安装
npm install vuex@next --save
基础使用 mutations
<template> <div> <h2>{{ $store.state.count }}</h2> <button @click="increment(6)">count++</button> </div> </template> <script> export default { name: "HomeView", methods: { increment(val) { this.$store.commit('increment',val); } } } </script>
store.js
import {createStore} from "vuex"; //创建 store 实例 const store = createStore({ state() { return { count: 0 } }, mutations: { increment(state,val) { state.count+=val; } } }) export default store
main.js
import {createApp} from 'vue' import App from './App.vue' import router from '@/router' import store from "@/store"; createApp(App).use(router).use(store).mount('#app')
getters
store/index.js
import {createStore} from "vuex"; //创建 store 实例 const store = createStore({ state() { return { count: 0, todos: [ {id: 1, text: '看书', done: true}, {id: 2, text: '学习', done: false} ] } }, getters: { doneTodos(state) { return state.todos.filter(todo => todo.done) }, doneTodosCount(state, getters) { return getters.doneTodos.length; } } export default store
HomeView.vue
<template> <div> <h2>{{ $store.getters.doneTodos }}</h2> <h2>{{ $store.getters.doneTodosCount }}</h2> </div> </template> <script> export default { name: "HomeView", } </script>
Action
-
Action 提交的是 mutation,而不是直接变更状态。
-
Action 可以包含任意异步操作。
<template> <div> </div> </template> <script> export default { name: "HomeView", mounted() { this.$store.dispatch('getHot','value') } } </script>
store/index.js
import {createStore} from "vuex"; import axios from "axios"; //创建 store 实例 const store = createStore({ state() { return { hotList: [] } }, getters: { mutations: { updateHotList: function (state, value) { state.hotList = value } }, actions: { getHot: function (context,playoad) { //context 与store 实例具有相同的属性和方法,playoad 传参 axios.get('/path/nc/article/headline/T1348647853363/0-40.html').then((res) => { console.log(res); console.log(context) context.commit('updateHotList',res.data.T1348647853363) console.log(context.state.hotList) console.log(playoad) }) } } }) export default store
跨域访问配置 vue.config.js
const {defineConfig} = require('@vue/cli-service') module.exports = defineConfig({ transpileDependencies: true, devServer: { proxy: { '/path': { target: 'http://c.m.163.com', changeOrigin: true, pathRewrite: { '^/path': '' } } } } })
moudle 模块
Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:
const moduleA = { state: () => ({ ... }), mutations: { ... }, actions: { ... }, getters: { ... } } const moduleB = { state: () => ({ ... }), mutations: { ... }, actions: { ... } } const store = createStore({ modules: { a: moduleA, b: moduleB } }) store.state.a // -> moduleA 的状态 store.state.b // -> moduleB 的状态
对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象。
const moduleA = { state: () => ({ count: 0 }), mutations: { increment (state) { // 这里的 `state` 对象是模块的局部状态 state.count++ } }, getters: { doubleCount (state) { return state.count * 2 } }, actions: { incrementIfOddOnRootSum ({ state, commit, rootState }) { if ((state.count + rootState.count) % 2 === 1) { commit('increment') } } } }
实例 HomeViews.vue
<template> <div> <!-- moudles 模块--> <h2>{{ $store.getters.userNameAge }}</h2> <!-- state 获取module数据 --> <h2>{{ $store.state.a.userName }}</h2> <button @click="changeName">change Name</button> </div> </template> <script> export default { name: "HomeView", methods: { changeName() { this.$store.commit('updateUserName') } } } </script>
store/index.js
import {createStore} from "vuex"; const moduleA = { state() { return { // 通过$store.state.module userName: 'chuen' } }, getters: { // 局部state $store.getters //触发函数 $store.commit userNameAge: function (state) { return state.userName + ' 18 years old' } }, mutations: { // state 局部状态 updateUserName: function (state) { state.userName = 'chuenyu' } } } //创建 store 实例 const store = createStore({ modules: { a: moduleA } }) export default store
命名空间moudle
store/user/index.js
创建一个 module
const user = { //使用 namespace 会导致路径上的变化 namespaced:true, state() { return { // 通过$store.state.module userName: 'chuen' } }, getters: { // 局部state $store.getters //触发函数 $store.commit userNameAge: function (state, getters) { //getters 对象 console.log(getters) return state.userName + ' 18 years old' } }, mutations: { // state 局部状态 updateUserName: function (state) { state.userName = 'chuenyu' } } } export default user
store/index.js
import {createStore} from "vuex"; import axios from "axios"; //引入module import user from "@/store/user"; //创建 store 实例 const store = createStore({ <!-- …… --> modules: { a: user } }) export default store
view.vue
<template> <div> <!-- moudles 模块--> <h2>{{ $store.getters['a/userNameAge'] }}</h2> <!-- state 获取module数据 --> <h2>{{ $store.state.a.userName }}</h2> <button @click="changeName">change Name</button> </div> </template> <script> export default { name: "HomeView", changeName() { // this.$store.commit('a/updateUserName') this.updateUserName() }, <!-- 使用辅助函数 --> ...mapMutations('a', ['updateUserName']), } } </script>