vue3基础
安装
前提安装node.js勾选npm工具https://nodejs.org/en/download
安装完重启
vscode 安装vue-official
插件
可选Vue VSCode Snippets
插件可快速开始模板
在vscode终端执行下面代码npm create vue@latest .
直接在以当前目录创建vue项目
npm create vue@latest
下载vue包,并指定项目名称,回答是否安装其他工具的问题默认都选否
> cd <your-project-name> //打开创建的项目目录
> npm install //安装依赖
> npm run dev //开发环境运行二选一
> npm run build //生产环境运行,会在 ./dist 文件夹创建一个生产版本
技巧
如果安装了Vue VSCode Snippets
- 输入vbase 然后tab可快速创建vue模板f
安装axiosnpm i axios
//请求插件
安装vue-routernpm install vue-router@4
多语言npm install vue-i18n@next
目录结构
project
├─ node_modules //依赖文件
├─ public //静态文件
├─ src //源码文件
├─ App.vue 根组件
└─ main.js 创建应用实列并引入应用app.vue根组件 全局css
├─ index.html //主html,调用main.js
├─ package-lock.json
├─ package.json
├─ README.md //自述文件
└─ vite.config.js //编译工具配置
快速开始
main.js:
import { createApp } from 'vue' //从vue引入创建app功能
import App from './App.vue' //导入vue根组件
createApp(App).mount('#app') //注册应用,并挂载#app
App.vue:
<template>
<div id="app">
{{ count }} //这里是变量
</div>
</template>
<script setup> //组合式,会自动将变量返回给模板无需显性返回
import { ref } from 'vue'; //从vue导入ref,初始值
const count = ref("哈哈1"); //指定变量初始值
</script>
<script> //选项式写法
import { ref } from 'vue'
export default {
// `setup` 是一个特殊的钩子,专门用于组合式 API。
setup() {
const count = ref("哈哈")
// 将将变量返回给模板
return {
count
}
}
}
</script>
多实列
import { createApp } from 'vue'; //引入创建实列
import App from './App.vue'; 创建一个实列,并指定文件
const app1 = createApp(App);
const app2 = createApp(App); //创建多个实列
app.mount('#app1');
app2.mount('#container-2')
修改变量标识
vite.config.js:
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [
vue({
template: {
compilerOptions: {
delimiters: ['${', '}'] //这里是替换的标志
}
}
}),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})
模板语法
插值文本变量{{var}}
//除非自己修改
v-html:插值html变量<span v-html="var"></span>
//若变量为html则需指定一个标签,html无法作为字符串拼接
v-bind:绑定标签的属性,例如<div v-bind:id="dynamicId"></div>
:id v-bind的第二种写法<div :id="dynamicId"></div>
//这里根据html标签各种属性的不同而取值不同,例如有的是布尔,有些是数字或字符
//如果绑定的值为null或undefined 则会从渲染中移除
绑定类和样式
<div :class="{ active: isActive }"></div> //isActive的布尔值决定是否显示
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div> //绑定样式
<div :class="[activeClass, errorClass]"></div> //绑定数组类
//下面active根据isActive的值判断是否显示,而errorClass会一直存在
<div :class="[{ 'active': isActive }, 'errorClass']"></div> //数组+判断
绑定多个值,定义一个数组变量
const objectOfAttrs = {
id: 'container',
class: 'wrapper'
}
然后<div v-bind="objectOfAttrs"></div>
//注意这里是等于号了
v-if: 插入或删除DOM,例如<p v-if="show">Now you see me</p>
//根据show的值来插入和删除元素
v-else-if
v-else
if可以单个用,也可以组合用示范:
<div v-if="type === 'A'">
A
</div>
<div v-else-if="type === 'B'">
B
</div>
<div v-else>
Not A/B/C
</div>
v-show: 显示或隐藏DOM,作用和上面相似,不过这个只是隐藏和显示
v-for: 数组循环,和列表对象循环
假设数组:const items = ref([{ message: 'Foo' }, { message: 'Bar' }])
<li v-for="item in items">
{{ item.message }}
</li>
//也可以增加一个参数索引<li v-for="(item, index) in items">
假设列表:
const myObject = reactive({
title: 'How to do lists in Vue',
author: 'Jane Doe',
publishedAt: '2016-04-10'
})
结果如下
<li v-for="(value, key) in myObject"> //也可以只有一个value值参数
{{ key }}: {{ value }}
</li>
循环后默认是按传递顺序排序,也可以指定排序字段:
<div v-for="item in items" :key="item.id"> //根据它的id键名来重新排序
<!-- 内容 -->
</div>
注意:v-if 和 v-for不可同级,因为v-if优先级高于v-for,可以再多包裹一层标签来实现二者
数组替换:
// `items` 是一个数组的 ref
items.value = items.value.filter((item) => item.message.match(/Foo/))
v-on: 监听事件
@: 监听事件的简写
<button @click="count++">加</button>
<p>数值: {{ count }}</p>
事件方法:@click
点击@submit
提交@scroll
滚动keyup.enter="submit"
按键,按下Enter时调用 submit
,鼠标按键:left,right,middle
v-model: 表单输入绑定,将会把输入类容传递给变量msg
<p>你输入的是: {{ msg }}</p>
<input v-model="msg" /> //每次输入后及时更新数据
<input v-model.lazy="msg" /> //每次变换失去焦点后更新数据
<input v-model.trim="msg" /> //去除两端的空格
<textarea v-model="msg"></textarea>
<input type="checkbox" id="checkbox" v-model="checked" />
<label for="checkbox">是否勾选:${ checked }</label>
<div>你选择的是: ${ picked }</div>
<input type="radio" id="one" value="选项1" v-model="picked" />
<label for="one">选项1</label>
<input type="radio" id="two" value="选项2" v-model="picked" />
<label for="two">选项2</label>
v-slot:
js表达式
表达式可以用在插值变量中,或者以v-开头的属性值中
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
<div :id="`list-${id}`"></div>
生命周期事件
onMounted 组件初始化后运行
onUpdated 组件导致DOM变更后执行
onUnmounted 组件和子组件不会再生效后执行
<script setup>
import { onMounted } from 'vue'
onMounted(() => {
console.log(`组件已经就绪`)
})
</script>
监听事件
//如果这里是一个变量对象,则返回两个值,新值,旧值(可选)
//如果这是一个运算函数,则返回它的值
watch(监听的值,(返回的值) => {
//这里是要执行的内容
})
//下面是初始化完后,也执行一次回调,就是再添加一个{ immediate: true }属性
watch(source, (newValue, oldValue) => {
// 立即执行,且当 `source` 改变时再次执行
}, { immediate: true })
import { ref, watch } from 'vue';
const myData = ref(0);
watch(myData, (newValue, oldValue) => {
console.log(`myData改变,新值:{{newValue}},旧值:{{oldValue}}`);
});
监听单个,监听函数,监听多个来源的数组的示范:
const x = ref(0)
const y = ref(0)
// 单个 ref
watch(x, (newX) => {
console.log(`x的新值: ${newX}`)
})
// getter 函数
watch(
() => x.value + y.value,//这里是监听x和y,任何值有改动将触发相加
(sum) => { //返回的结果在sun变量里
console.log(`x + y = ${sum}`)//这里是它执行的内容
}
)
// 多个来源组成的数组
watch([x,y], ([newX, newY],[jiuX, jiuY]) => { //监听数组,返回也是对应数组
console.log(`x新值: ${newX},x旧值: ${jiuX}, y 新值: ${newY},y旧值: ${jiuY}`)
})
手动停止监听
<template>
<div>${ x }<button @click="x++">x加1</button></div>
<button @click="kaishi">停止监听</button> //触发监听变量则停止监听
</template>
<script setup>
import { ref, watch } from 'vue';
const x = ref(0)
//将监听方法赋值给变量
const kaishi = watch(x, (newX,jiuX) => { //监听数组,返回也是对应数组
console.log(`x新值: ${newX},x旧值: ${jiuX}, y 新值: ${newY},y旧值: ${jiuY}`)
})
</script>
模板引用
reactive 返回原始对象,可以操作对象的name和value这些
ref 可以接收任何类型,直接返回value,如果处理的类型只有一个值时ref是更好的选择
v-for中使用ref
<script setup>
import { ref, onMounted } from 'vue'
const list = ref([1,2,3])
const itemRefs = ref([])
onMounted(() => console.log(itemRefs))
</script>
<template>
<ul>
//这里设置了ref变量为itemRefs,它会将每个item都存储到itemRefs
//
<li v-for="item in list" ref="itemRefs">
${ item }
</li>
</ul>
</template>
组件调用
局部组件调用
<template>
<Soso /> //调用组件,改组件就会在这里显示
</template>
<script setup>
import Soso from './Soso.vue' //导入组件并以Soso命名,组件名都使用首字母大写
</script>
全局组件
在main.js中:
import { createApp } from 'vue'
import App from './App.vue'
import Soso from './Soso.vue'; // 导入组件
const app = createApp(App);
app.component('Soso', Soso); // 全局注册组件
//注册后在所有模板中使用<Soso />调用即可
app.mount('#app');
传递参数
<script setup>
const props = defineProps(['foo','foo2'])
console.log("你输入的参数是:" + props.foo + props.foo2)
</script>
//指定参数的类型
<script setup>
const props = defineProps({
propA: Number, //数字
propB: [String, Number], // 多种可能的类型
propC: { //指定类型和条件
type: String, //字符串类型
required: true //必传
},
propD: {
type: Number,
default: 100 //设置默认值
},
// 对象类型的默认值
propE: Object,
})
</script>
调用组件:
<Soso foo="userName" foo2="userAge" /> //字符串静态传参,如果是非字符串仍然要用下面的
<Soso :foo="post.title" :foo2="var" /> //动态传参,假设这里传入的是变量
<!-- 仅写上键名会隐式转换为 `true` -->
<BlogPost is-published />
<BlogPost :is-published="false" />
<!-- 根据一个变量的值动态传入 -->
<BlogPost :is-published="post.isPublished" />
<!-- 将多个参数放置到一个变量 -->
const post = {
id: 1,
title: 'My Journey with Vue'
}
<BlogPost v-bind="post" />
<!-- 等同于<BlogPost :id="post.id" :title="post.title" /> -->
模块调用
假设有一个没有视图的功能模块:service/Getget.js
import axios from 'axios';
import {ref} from 'vue';
const getget = (url) => { //允许传入url参数
const data = ref() //定义一个变量为vue可追踪的响应变量
const load = async () => {
try {
const response = await axios(url) //await的作用是请求完毕后再赋值
data.value = response.data //将响应对象的值赋予
} catch (error) {
console.log(error)
}
}
load() //调用异步
return data //将响应对象返回给调用端
}
export default getget //导出getget允许被调用
调用:
<template>
<div> {{ ss }}</div>
</template>
<script setup>
import Getget from './service/Getget';
const ss = Getget('http://127.0.1.1/api/json')
//因为Getget返回的对象已经是ref响应式,所以不需要额外再导入vue的ref了
</script>
组件接收参数
Notification.vue:
<script setup>
import { ref,watchEffect } from 'vue';
//通过props接收通知参数
const props = defineProps({
notifications: Array
});
//监听通知数组的变化
watchEffect(() => {
props.notifications.forEach(notification => {
setTimeout(() => {
notification.show = ref(false); //3.5秒后隐藏通知
}, 3500);
});
});
</script>
在其他组件调用它?
<template>
<Notification :notifications="notifications"/>
</template>
<script setup>
import { ref} from 'vue';
const notifications = ref([]); //初始化通知数组
//在 函数中为数组增加一条数据
notifications.value.push({message: error,type:"warning",show:true});
</script>
修改根html属性
标题
document.title = '标题'
html标签的class
document.documentElement.classList.add('dark'); 添加class
document.documentElement.classList.remove('dark'); 删除class
路由
npm install vue-router@4
安装
项目目录创建router.js:
import { createRouter,createWebHistory } from "vue-router";
import Index from './Index.vue'
import Index from './Class.vue'
const routes = [ //指定路由
{path:"/",component:Index},
{path:"/class/:fenlei",component:Class}, //路由传递参数
]
const router = createRouter({ //创建路由
history: createWebHistory(), //路由模式
routes, //路由,由之前的数组变量作为参数
})
export default router //返回这个路由被调用
main.js启用路由中间件:
import './main.css'
import { createApp } from 'vue'
import App from './App.vue'
import router from './router';
const app = createApp(App);
app.use(router) //启用路由
app.mount('#app');
App.vue嵌入路由:
<template>
页头
<router-view></router-view>
页尾
</template>
<script setup>
import { RouterView } from 'vue-router'; //导入路由
</script>
中间的
在路由组件中访问路由参数:
<template>
{{ route.params.fenlei }}
</template>
<script setup>
import { useRoute } from 'vue-router';
const route = useRoute(); // 获取当前路由对象
const fenlei = route.params.fenlei;
</script>
路由跳转
<script setup>
import { useRouter } from 'vue-router';
const router = useRouter(); //路由跳转
//某函数内,vue3跳转
router.push('/login');
//第二种跳转方法,相当于点击a链接
window.location.href = '/jump';
</script>
获取路由参数
<script setup>
import { useRoute } from 'vue-router'
const route = useRoute()
// 获取路由参数
const id = route.params.id
// 获取查询参数
const query = route.query.query
</script>
路由守卫,监测路由变化产生事件
import { useRouter } from 'vue-router'; //导入路由
const router = useRouter();
//to是去向路由,from是当前路由,next是放行
router.beforeEach((to, from, next) => {
if (to.path === '/login' || to.path === '/register' || to.path === '/changepassword') {
showAcc.value = true;
} else {
showAcc.value = false;
}
next();
});
axios请求库
npm install axios
main.js:
import './static/main.css'
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import axios from 'axios'
//指定url前缀
axios.defaults.baseURL = "http://127.0.0.1:80"
const app = createApp(App);
app.provide("axios",axios) //全局共享axios
app.use(router) //启用路由
app.mount('#app');
Login.vue:
<template>
<Notification :notifications="notifications"/>
<form :model="loginuser" v-on:submit="login">
<input name="email" type="email" required v-model="loginuser.email">
<input name="password" v-model="loginuser.password" required> <button>登录</button>
</form>
</template>
<script setup>
import { ref,reactive,inject } from 'vue';
const axios = inject("axios"); //调用全局组件
//异步发送请求
const login = async(event)=>{
event.preventDefault(); //阻止表单提交
//异步请求
await axios.post("/login",{
email:loginuser.email,
password:loginuser.password,
})
.then(function (response) {
//登录成功处理
notifications.value.push({message: "登录成功",show:true});
// console.log("返回内容",response.data.msg);
})
.catch(function (error) { //错误处理
if (error.response) {
//如果收到响应,但状态码超出了2xx范围
notifications.value.push({message: error.response.data.msg,type:"error",show:true});
} else {
//网络请求错误
notifications.value.push({message: error,type:"warning",show:true});
};
});
};
</script>
全局状态pina
pina中存储的变量默认就是响应式
npm install pinia
安装
main.js:
//其他导入
import { createPinia } from 'pinia'
const app = createApp(App)
//其他组件
app.use(createPinia()) //启用全局状态管理
app.mount('#app')
一个pina通知的存储桶示范
store\notifications.js:
import { defineStore } from 'pinia';
export const useNotificationsStore = defineStore('notifications', {
state: () => ({ //定义变量
list: [] //这里的变量是通知的列表
}),
actions: { //定义方法
add(notification) { //添加
this.list.push(notification); //为list添加一条数据
},
remove(index) { //删除
this.list.splice(index, 1); //为list删除一条数据
}
}
})
Login.vue:
<template>
<!--导入通知模块-->
<Notification />
</template>
<script setup>
//其他导入
// 导入定义的通知pina存储
import { useNotificationsStore } from '@/store/notifications'
const notifications = useNotificationsStore() // 使用 store
//其他逻辑
{
//假设这是某函数内
//调用notifications的add方法添加一条数据,对象定义了消息和消息类型
notifications.add({ message: "这是消息", type: "warning" })
}
</script>
通知栏组件Notification.vue:
<template>
<div aria-live="assertive" class="pointer-events-none fixed inset-0 flex px-4 py-6 items-start sm:p-6 z-10">
<div class="flex w-full flex-col items-center sm:items-end space-y-2">
<div v-for="(notification) in notifications.list" class="pointer-events-auto w-full max-w-sm overflow-hidden rounded-lg bg-white shadow-lg ring-1 ring-black ring-opacity-5">
<div class="p-4">
<div class="flex items-start">
<div class="flex-shrink-0">
<svg v-if="notification.type === 'error'" viewBox="0 0 20 20" fill="currentColor"
class="h-6 w-6 text-yellow-500">
<path fill-rule="evenodd"
d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 5zm0 9a1 1 0 100-2 1 1 0 000 2z"
clip-rule="evenodd" />
</svg>
<svg v-else-if="notification.type === 'warning'" viewBox="0 0 20 20" fill="currentColor"
class="h-6 w-6 text-rose-600">
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z"
clip-rule="evenodd" />
</svg>
<svg v-else class="h-6 w-6 text-green-400" fill="none"
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round"
d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div class="ml-3 w-0 flex-1 pt-0.5">
<p class="text-sm font-medium text-gray-900"> {{ notification.message }}</p>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { watchEffect } from 'vue';
import { useNotificationsStore } from '@/store/notifications'
//导入和使用定义的pina通知函数
const notifications = useNotificationsStore()
//监听通知变量list数组的变化
watchEffect(() => {
notifications.list.forEach((notification, index) => {
// 在定时器结束后从数组中移除该通知对象
setTimeout(() => {
notifications.remove(index);
}, 3500);
});
});
</script>
浏览器存储桶localStorage
localStorage.setItem('name', 'Value'); //存储值
localStorage.getItem('name'); //获取值
localStorage.removeItem('name'); //删除值
localStorage.clear(); //清空所有
localStorage.length; //返回存储数量
使用代理解决跨域问题
vite.config.js:
export default defineConfig({
//配置代理
server: {
proxy: {
//下面这些路由,全部代理到127.0.0.1:5280
'/api': {
target: 'http://127.0.0.1:5280',
changeOrigin: true,
},
'/login': {
target: 'http://127.0.0.1:5280',
changeOrigin: true,
},
'/register': {
target: 'http://127.0.0.1:5280',
changeOrigin: true,
},
'/changepassword': {
target: 'http://127.0.0.1:5280',
changeOrigin: true,
}
}
}
})
服务端也要配置允许跨域处理。例如gin:
//假设下面是初始化gin函数:
r := gin.Default() //初始化gin
r.Use(cors.New(cors.Config{ //生产环境需注释
AllowOrigins: []string{"*"},
AllowMethods: []string{"POST", "GET", "OPTIONS", "PUT", "DELETE"},
AllowHeaders: []string{"Origin", "Content-Length", "Content-Type", "Authorization"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
}))
//假设下面是某其他函数
//设置cookie也要指定域名,假设vite前端是运行在
c.SetCookie("token", "", -1, "/", "localhost:5173", false, true)
三元表示法
如果showqr为true则使用hidden lg:flex否则使用flex
<div :class="showqr ? 'hidden lg:flex' : 'flex'">
重载当前页,刷新
router.go(0)//重新渲染
location.reload() //整个重新加载
时间序列化
年月日+时间:{{ new Date(created_at).toLocaleString() }}
只有年月日:{{new Date(km.Daoqi_date).toLocaleDateString()}}