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

安装axios
npm i axios //请求插件

安装vue-router
npm 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>

中间的 会根据路由显示router文件配置的vue组件

在路由组件中访问路由参数:

<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()}}