下载安装node.js
创建项目目录,在里面npm init vite@latest,可以直接选框架语言快速生成,也可以选完框架后选择自定义,按提示安装相关vue组件,最后进去npm install安装依赖(根据package.json安装),npm run dev运行环境,vscode安装volar
目录中的 index.html 是项目的入口;package.json 是管理项目依赖和配置的文件;public 目录放置静态资源,比如 logo 等图片;vite.config.js 就是和 Vite 相关所有工程化的配置;src 就是工作的重点,我们大部分的代码都会在 src 目录下管理和书写,后面我们也会在 src 目录下细化项目规范。
访问提示里给的本地路径,可以看到vite的界面
Vue 负责核心,Vuex 负责管理数据,vue-router 负责管理路由
npm install vuex@next
npm install vue-router@next
如果在npm i的时候使用了--save,则该包会被安装到所有的环境
如果--save-dev,则只保存在dev环境
package.json里面,各个版本号的前面,^代表大版本号不动,后两位找当前最新,~代表前两位不动,最后一位最新
vue3基于proxy实现了响应机制

语法

风格

把vue.app删光光后,里面就剩下这两块东西
一块放js,一块放html
另外需要注意的是,默认情况下app.vue是程序的入口,也就是这里的东西会出现在所有的程序中,后面开发时,除非你有这方面的需求,如固定的侧边栏导航栏,不然不要在这里写页面代码(除了路由)

<template>
这里放html
</template>

<script>
这里放js
</script>

vue有两种风格,分别是选项式(vue2)和组合式(vue3)
选项式

<script>
export default {
  // data() 返回的属性将会成为响应式的状态
  // 并且暴露在 `this` 上
  data() {
    return {
      count: 0
    }
  },

  // methods 是一些用来更改状态与触发更新的函数
  // 它们可以在模板中作为事件处理器绑定
  methods: {
    increment() {
      this.count++
    }
  },

  // 生命周期钩子会在组件生命周期的各个不同阶段被调用
  // 例如这个函数就会在组件挂载完成后被调用
  mounted() {
    console.log(`The initial count is ${this.count}.`)
  }
}
</script>

<template>
  <button @click="increment">Count is: {{ count }}</button>
</template>

组合式,以下笔记都将由组合式编写(为什么网上教程全是选项式的啊,怒了

<script setup>
import { ref, onMounted } from 'vue'

// 响应式状态
const count = ref(0)

// 用来修改状态、触发更新的函数
function increment() {
  count.value++
}

// 生命周期钩子
onMounted(() => {
  console.log(`The initial count is ${count.value}.`)
})
</script>

<template>
  <button @click="increment">Count is: {{ count }}</button>
</template>

组合式的风格可以把一组相关的数据和方法放在一起,以便更好地管理

function useTodos() {
  let title = ref("");
  let todos = ref([{ title: "学习Vue", done: false }]);
  function addTodo() {
    todos.value.push({
      title: title.value,
      done: false,
    });
    title.value = "";
  }
  function clear() {
    todos.value = todos.value.filter((v) => !v.done);
  }
  let active = computed(() => {
    return todos.value.filter((v) => !v.done).length;
  });
  let all = computed(() => todos.value.length);
  let allDone = computed({
    get: function () {
      return active.value === 0;
    },
    set: function (value) {
      todos.value.forEach((todo) => {
        todo.done = value;
      });
    },
  });
  return { title, todos, addTodo, clear, active, all, allDone };
}

这段代码定义了一个名为 useTodos 的函数,它返回一个对象,该对象包含了一些属性和方法,用于管理一个 todo 列表。

let title = ref("");: 定义了一个响应式数据 title,初始值为空字符串。

let todos = ref([{ title: "学习Vue", done: false }]);: 定义了一个响应式数据 todos,初始值是一个包含一个 todo 对象的数组,该对象有一个 title 属性和一个 done 属性。

function addTodo() { ... }: 定义了一个名为 addTodo 的方法,用于向 todos 数组中添加一个新的 todo 对象。该方法将新的 todo 对象 push 到 todos.value 中,并将 title.value 设置为空字符串。

function clear() { ... }: 定义了一个名为 clear 的方法,用于清除已完成的 todo 对象。该方法使用 filter 方法从 todos.value 数组中筛选出未完成的 todo 对象,然后将结果赋值给 todos.value。

let active = computed(() => { ... }): 定义了一个计算属性 active,它返回未完成的 todo 对象的数量。计算属性使用 todos.value 数组进行计算,并返回结果。

let all = computed(() => todos.value.length);: 定义了一个计算属性 all,它返回 todos.value 数组的长度。

let allDone = computed({ ... }): 定义了一个计算属性 allDone,它既可以读取也可以设置。当读取 allDone 时,它会判断 active.value 是否等于 0,并返回判断结果。当设置 allDone 时,它会遍历 todos.value 数组,并将每个 todo 对象的 done 属性设置为传入的值。

return { title, todos, addTodo, clear, active, all, allDone };: 返回一个包含 title、todos、addTodo、clear、active、all 和 allDone 属性的对象。这些属性和方法可以在组件中使用。
比如这样

<script>
import { useTodos } from './useTodos';
const { title, todos, addTodo, clear, active, all, allDone } = useTodos();
</script>
  <template>
  <div>
    <h1>{{ title }}</h1>
    <ul>
      <li v-for="todo in todos" :key="todo.id">{{ todo.text }}</li>
    </ul>
    <button @click="addTodo">Add Todo</button>
    <button @click="clear">Clear Todos</button>
  </div>
</template>

基础

值/属性绑定

文本插值,会根据变量的值动态更新,使用双大括号加载,被加载的值会被当做普通文本输出而不是html处理

<span>Message: {{ msg }}</span>

双大括号的写法不能用于html的属性,所以提供了另外一种写法

<div v-bind:id="dynamicId"></div>
或者简写成
<div :id="dynamicId"></div>

如果想一次性绑多个,可以这样写
const objectOfAttrs = {
  id: 'container',
  class: 'wrapper'
}
//不指定具体的属性
<div v-bind="objectOfAttrs"></div>

//动态的属性名
<a v-bind:[attributeName]="url"> ... </a>

<!-- 简写 -->
<a :[attributeName]="url"> ... </a>

当属性为真值或者true时,这个属性是显性的,如果属性值为false,那么这个属性将不被启用

js表达式

vue支持完整的js表达式,{{}}或者所有的v-指令中都能被识别
但是vue只支持能够返回值的语句,类似赋值语句或者判断语句都无法生效

{{ number + 1 }}

{{ ok ? 'YES' : 'NO' }}

{{ message.split('').reverse().join('') }}

<div :id="`list-${id}`"></div>

ref,reactive

响应式的创建变量、对象
注意是ref,不是Ref,ref用于创建一个响应式变量,而Ref是ts中的ref调用后返回的类型

<script>
import {reactive,computed,toRefs} from 'vue'
interface DataProps {
  count: number;
  double: number;
  increase: () => void;
}
const data: DataProps = reactive({
  count: 0,
  increase: () => {data.count++},
  double: computed(() => data.count * 2)
})
//const refdata = toRefs(data)
</script>

      <p>{{ data.count }}</p>
      <p>{{ data.double }}</p>
      <button @click="data.increase">clickhere</button>

//如果是用torefs转的响应式,increase要写成refdata.count.increase

示例:搜索框

<div class="content">
  <input type="text" placeholder="搜索" ref="searchWord">
  <span class="iconfont icon-fangdajing" @click="search(this.$refs.searchWord.value)"></span>
</div>

要注意的是,ref在函数中引用赋值时,需要指定访问xxx.value,但是从template传递过来时(包括从函数参数传过来时),可以直接访问
示例:翻页

<template>
    <div class="project_main">
      <div class="search_box"></div>
      <div class="project_table_box">
        <el-table :data="projectlist" style="width: 100%" :row-height="rowHeight">
          <el-table-column fixed prop="project_name" label="项目名" width="150" />
          <el-table-column prop="project_rank" label="重要等级" width="120" />
          <el-table-column prop="project_owner_id" label="负责人" width="120" />
          <el-table-column prop="project_create_time" label="创建时间" width="320" />
          <el-table-column prop="project_last_mod" label="最后修改时间" width="600" />
        </el-table>
      </div>
      <div class="pagination">
        <div class="demo-pagination-block">
          <div class="demonstration">每页显示数</div>
          <el-pagination
            v-model:current-page="page"
            v-model:page-size="page_size"
            :page-sizes="[10, 20, 30, 40, 50]"
            :small="small"
            :disabled="disabled"
            :background="background"
            layout="sizes, prev, pager, next"
            :total="100"
            @size-change="handleSizeChange"
            @current-change="handleCurrentChange"
          />
  </div>
      </div>
    </div>
  </template>
  
  <script setup>
  import { ref, onMounted } from 'vue';
  import { projectrequest } from '../network/projectrequest';

  
  const projectlist = ref([]);
  const page = ref(1);
  const page_size = ref(10);
  const rowHeight = 40; 
  const small = ref(false)
  const background = ref(false)
  const disabled = ref(false)
  
  onMounted(() => {
    projectinfo(page.value, page_size.value)
  });
  function projectinfo(currentpage,currentpage_size){
    projectrequest(currentpage, currentpage_size).then((res) => {
      projectlist.value = res.data.data;
      console.log(projectlist.value)
      console.log(currentpage,currentpage_size)
    });
  }

  function handleSizeChange(pagesize_now){
    page_size.value = pagesize_now;
    page.value = 1;
    projectinfo(page.value, page_size.value)
  };

  function handleCurrentChange(page_now){
    page.value = page_now
    projectinfo(page.value, page_size.value)
  }
  </script>

计算属性

利用现有定义的值来计算出一个新的值,好处是计算过一次后就会缓存

<script setup>
import { reactive, computed } from 'vue'

const author = reactive({
  name: 'John Doe',
  books: [
    'Vue 2 - Advanced Guide',
    'Vue 3 - Basic Guide',
    'Vue 4 - The Mystery'
  ]
})

// 一个计算属性 ref
const publishedBooksMessage = computed(() => {
  return author.books.length > 0 ? 'Yes' : 'No'
})
</script>

<template>
  <p>Has published books:</p>
  <span>{{ publishedBooksMessage }}</span>
</template>

v-if/show

v-i会根据值的真假来判断是否插入/移除该节点

<p v-if="seen">Now you see me</p>
<div v-if="type === 'A'">
  A
</div>
<div v-else-if="type === 'B'">
  B
</div>
<div v-else-if="type === 'C'">
  C
</div>
<div v-else>
  Not A/B/C
</div>

示例

<template>
  <div><button @click="onclickup" id="show">click here</button></div>
  <div v-if!="flag">U.N is her?</div>
  <div v-else>WOW!</div>
</template>

<script setup>
  import { ref } from 'vue';

  const flag = ref(false)

  function onclickup() {
    if (flag.value === false){
      flag.value = true
    }
    else if(flag.value === true){
      flag.value = false
    }
  }
</script>

v-show和v-if功能类似,都是控制节点是否展示,但是show是在样式添加css--display:none,隐藏后dom仍然存在,if则是增加删除对应的dom

v-on

监听dom事件,用于对事件的反应,可以用于函数的调用

<a v-on:click="counter += 1"> ... </a>

<!-- 简写 -->
<a @click="doSomething"> ... </a>

<a v-on:[eventName]="doSomething"> ... </a>

<!-- 简写 -->
<a @[eventName]="doSomething">

<!--带参-->
<button @click="say('hello')">Say hello</button>

function say(message) {
  alert(message)
}

v-for

遍历取值,绑定key属性可以让vue根据该属性所对应的值来绑定节点的身份,以方便其进行排序,如果没有可绑的可以用index

<template>
<--值only-->
<li v-for="item in agelist">{{ item.message }}</li>
<--值和索引-->
<li v-for="(item,index) in agelist">{{index}} : {{ item.message }}</li>
<--用第二个位置代表属性,用第三个位置代表索引-->
<li v-for="(value,key,index) in character" :key="key|index">{{ index }}:{{ key }}:{{ value }}</li>
<--范围内取值,从1开始-->
<span v-for="n in 10">{{ n }}</span>
</template>

<script setup>
import { ref } from 'vue';

const flag = ref(false)
const agelist = ref([{message:17},{message:20}])
const character = ref({
  yukari : 17,
  marisa : 20
})

</script>

<!-- 17
20
0 : 17
1 : 20
0:yukari:17
1:marisa:20
2:erin:9999
12345678910 -->

如果要同时使用v-for和v-if,不要放在同一级,if优先级比for更高,会影响代码功能
正确示范

<template v-for="todo in todos">
  <li v-if="!todo.isComplete">
    {{ todo.name }}
  </li>
</template>

v-model

表单绑定

<p>Message is: {{ message }}</p>
  <input v-model="message" placeholder="edit me" />

  <input type="checkbox" id="checkbox" v-model="checked" />
  <label for="checkbox">{{ checked }}</label>

  //复选框的值放入列表,列表中的值为选项的value
  <template>
  <div>choice list:{{ checklist }}</div>
  <form>
  <input type="checkbox" v-model="checklist" id="zhangsan" value="zhangsan">
  <label for="zhangsan">zhangsan</label>
  <input type="checkbox" v-model="checklist" id="lisi" value="lisi">
  <label for="lisi">lisi</label>
  <input type="checkbox" v-model="checklist" id="wangwu" value="wangwu">
  <label for="wangwu">wangwu</label>
  </form>
  </template>
  <script>
  const checklist = ref([])
</script>

  //自定义值选择器
  <select v-model="selected">
    <option v-for="option in options" :key="option.value" :value="option.value">
    {{ option.text }}
</option>
  </select>

  <div>Selected: {{ selected }}</div>
  const options = ref([
    { text: 'One', value: 'A' },
    { text: 'Two', value: 'B' },
    { text: 'Three', value: 'C' }
  ])

修饰符

<!-- 在 "change" 事件后同步更新而不是 "input" -->
<input v-model.lazy="msg" />

//自动转数字
<input v-model.number="age" />

//去除两端空格
<input v-model.trim="msg" />

watch

监听某个值的变化并对其进行操作
例:监听count的变化

<script setup>
import { ref, watch } from 'vue';
const count = ref(0)
watch(count,(newValue, oldValue)=>{
  if(newValue >=3){
    console.log("too much click",newValue)
  }
}
)
function clickup(){
  count.value++
}
</script>

<template>
  <button @click="clickup">clickhere:{{ count }}</button>

</template>

如果有多个变化的值,可以从newvalue中取对应的值,值名可在控制台看到

v-slot

插槽绑定
假如某模板如下

<template>
  <form class="validate-form-container">
    <slot name="default"></slot>
    <div class="submit-area" @click.prevent="submitForm">
      <slot name="submit">//当没有绑定时会显示默认的提交按钮
        <button type="submit" class="btn btn-primary">提交</button>
      </slot>
    </div>
  </form>
</template>

现对其使用

<template>
  <div class="login-page mx-auto p-3 w-330">
    <h5 class="my-4 text-center">登录到xx</h5>
    <validate-form @form-submit="onFormSubmit">
      <div class="mb-3">
        <label class="form-label">邮箱地址</label>
        <validate-input //上面模板的代码块
          :rules="emailRules" v-model="emailVal"
          placeholder="请输入邮箱地址"
          type="text"
          ref="inputRef"
        />
      </div>
      <div class="mb-3">
        <label class="form-label">密码</label>
        <validate-input
          type="password"
          placeholder="请输入密码"
          :rules="passwordRules"
          v-model="passwordVal"
        />
      </div>
      <template #submit>//v-slot的简写,指定name="submit"
        <button type="submit" class="btn btn-primary btn-block btn-large">登录</button>
      </template>
    </validate-form>
  </div>
</template>

响应式

下面使用组合式表达
想创建一个变量,用ref来进行声明,其值通过var.value输出
当你在模板中使用了一个 ref,然后改变了这个 ref 的值时,Vue 会自动检测到这个变化,并且相应地更新 DOM。这是通过一个基于依赖追踪的响应式系统实现的。当一个组件首次渲染时,Vue 会追踪在渲染过程中使用的每一个 ref。然后,当一个 ref 被修改时,它会触发追踪它的组件的一次重新渲染。<script setup> SFC可以避免我们手动在setup()中输出的繁琐

<template>
  <div>count:<button @click="onclickup">{{ count }}</button></div>
  </template>

  <script setup>
  import { ref } from 'vue';

const count = ref(111)

function onclickup() {
  count.value++
}
</script>

直接输出count和count.value的区别

RefImpl {__v_isShallow: false, dep: undefined, __v_isRef: true, _rawValue: 111, _value: 111}dep: undefined__v_isRef: true__v_isShallow: false_rawValue: 111_value: 111value: (...)[[Prototype]]: Object
                                                                                                                                                                              App.vue:9 111

在其他组件模板中使用ref的方式,无setup的情况下

import { ref } from 'vue'

export default {
  setup() {
    const count = ref(0)

    function increment() {
      // 在 JavaScript 中需要 .value
      count.value++
    }

    // 不要忘记同时暴露 increment 函数
    return {
      count,
      increment
    }
  }
}

引用模板

<template>
<HelloWorld />
</template>

<script setup>
import HelloWorld from './components/HelloWorld.vue';
</script>

如果需要父组件给子组件传递一些值,需要用到props
props传过来的值,不能在setup作为变量使用,但是可以在template里面使用

<template>
  <p>title:{{ title }}</p>
  <p>id:{{ paperid }}</p>
</template>

<script setup>
defineProps(['paperid','title'])
</script>
<template>
<HelloWorld v-for="paper in paperlist" :paperid="paper.id" :title="paper.title"/>
</template>

<script setup>
import HelloWorld from './components/HelloWorld.vue';
const paperlist = ref([
  {id: 1, title: 'hello on board'},
  {id: 2, title: 'guide for new blood'},
  {id: 3, title: '100 way to learn vue'}
])
</script>

更全能的写法

const props = defineProps({
  isOpen: Boolean,
  count: {
    type: Number, //类型
    default: 0 //默认值
  },
  title: {
    type: String,
    required: true //是否必须
  }
});

如果想要使用复杂的类型,比如接口数组,得用PropType包裹

export interface Columntype{
    id: number;
    title: string;
    avatar: string;
    abstract: string;
}
const props = defineProps({
    columnlist: {
        type: Array as PropType<Columntype[]>,
        required: true
    }
})

子调用父的方法,可以用emit
https://zhuanlan.zhihu.com/p/581646269?utm_id=0
emit有点像订阅发布的意思,子组件在自己身上注册了一个方法,然后告诉父组件我这里要调用一个名为xxx的方法,父组件真实定义xxx后,再把xxx放入子组件注册这个方法的地方真实调用。也就是子组件负责注册,父组件负责定义和调用

<template>
  <HelloWorld v-for="paper in paperlist" :paperid="paper.id" :title="paper.title" @printpaper="printpaper(paper.title)"/>
</template>
<script setup>
import HelloWorld from './components/HelloWorld.vue';
function printpaper(papername) {
  console.log("already printed paper ",papername)
}
</script>
<template>
  <p>title:{{ title }}</p>
  <p>id:{{ paperid }}</p>
  <button @click="$emit('printpaper')">Print</button>
</template>

<script setup>
defineProps(['paperid','title'])
defineEmits(['printpaper'])
</script>

页面切换,通过点击按钮来切换对应加载的组件,使用component

<template>
<button v-for="(_,tab) in tabs" @click="currentpage = tab" :key="tab">{{ tab }}</button>
<component :is="tabs[currentpage]"></component>
</template>

<script setup>
import { ref } from 'vue';
import HelloWorld from './components/HelloWorld.vue';
import shop from './components/shop.vue'
const currentpage = ref()
</script>

创建一个应用

在默认文件里有个main.js
createApp的参数为根组件,在这里根组件为App

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'

createApp(App).mount('#app')

监听

监听变量的值,当发生变化时执行操作
如果想要同时监控多个值,把第一个参数换成数组

import { watch } from 'vue'
watch(data,()=>{
  document.title = 'data updated'
})

如果想监控一个对象里面的值,需要把那个值函数化

const data: DataProps = reactive({
  count: 0,
  increase: () => {data.count++},
  double: computed(() => data.count * 2)
})
watch(()=>data.count,()=>{  //不能直接data.count
  document.title = 'data updated'
})

插槽

在子组件上提供一个插槽,以便于父组件传入的东西能够替代插槽里面的默认设置
https://zhuanlan.zhihu.com/p/529152853

子组件FancyButton
<button class="fancy-btn">
  <slot></slot> <!-- 插槽出口 -->
</button>

父组件中使用
<FancyButton>
  Click me! <!-- 插槽内容 -->
</FancyButton>

传送

传送可以让你把你的html挂载到其他的dom节点下
某个模组

<template>
    <Teleport to="#modal">//指定要传送的节点的id,被包裹的部分会给传送走
        <div id="centermodal" v-if="isOpen">
            <h2><slot>Modal window</slot></h2>
            <button @click="$emit('onModalclose')">close</button>
        </div>
    </Teleport>
</template>

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <link rel="icon" href="/favicon.ico">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Vite App</title>
  </head>
  <body>
    <div id="app"></div>
    <div id="modal"></div>//会被挂载在这里
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>

加载后按f12查看,发现窗口在modal这个div下,和app是平级,成功实现了传送
可以用来避免过深的嵌套

Promise、Suspense

异步执行
示例:两秒后打印42

<template>
    <h1>{{ result }}</h1>
    <span></span>
</template>

<script setup lang="ts">
import {ref} from 'vue'

const result = ref<number | null>(null);

new Promise<number>((resolve) => {
  setTimeout(() => {
    resolve(42);
  }, 2000);
}).then((value) => {
  result.value = value;
});
</script>

app.vue

  <Suspense>
    <template #default>//加载出来后再显示这块
      <Asyncshow />
    </template>
    <template #fallback> //加载失败时显示
      <h1>Loading...</h1>
    </template>
  </Suspense>

provide,inject

在根组件中一次申明,就可以在任意组件中取用

app.vue
const location = ref('HZ')
const changelocation = (input: string) => {
  location.value = input
}
provide('location',location)

      <button @click="changelocation('Tokyo')">changeyourlocation</button>
//点击按钮就能修改
xxx.vue
import {inject} from 'vue'
const location = inject('location')

获取节点

vue3移除了this指针,但是依旧有方法可以获得节点

<template>
  <div class="demo-dropdown-wrap" ref="dropdownRef">
</template>
<script setup lang="ts">
const dropdownRef = ref<null | HTMLElement>(null) //起名一定要和上面一样
const handleMenuClick = (e: MouseEvent) => {
    console.log('click', e);
    if(dropdownRef.value){
        if(dropdownRef.value.contains(e.target as HTMLElement)){
            console.log(dropdownRef.value)
        }
    }
};
</script>

$attrs

好像是阻止vue把子节点的属性合并到父节点上面去的,暂时没用到就先不写了

路由

https://router.vuejs.org/zh/guide
在vue中使用来显示对应的路由界面

基本路由

npm install vue-router@4 --save
当然也可以在创建项目的时候用手脚架快速搭建
在src/router下创建index.js
默认文件

import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'home',
      component: HomeView
    },
    {
      path: '/about',
      name: 'about',
      // route level code-splitting
      // this generates a separate chunk (About.[hash].js) for this route
      // which is lazy-loaded when the route is visited.
      component: () => import('../views/AboutView.vue')
    }
  ]
})

export default router

在views下创建个shop,添加到路由里面,访问路径/shop,即可看到对应的页面

import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import shop from '../views/shop.vue'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'home',
      component: HomeView
    },
    {
      path: '/about',
      name: 'about',
      // route level code-splitting
      // this generates a separate chunk (About.[hash].js) for this route
      // which is lazy-loaded when the route is visited.
      component: () => import('../views/AboutView.vue')
    },
    {
      path: '/shop',
      name: 'shop',
      component: shop//指定对应的组件名,如果没放在同级目录或者views下要像上面import,上面展示的是动态写法
    },
  ]
})

export default router

动态路由

动态路径,可以动态的识别参数,我这里把User.vue放在了/src/news/

import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import shop from '../views/shop.vue'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'home',
      component: HomeView
    },
    {
      path: '/User/:username/home/:id',
      name: 'User',
      component: () => import('../news/User.vue')
    }
  ]
})

export default router

User.vue

<template>
    <h1>User</h1>
    <p>welcome {{ $route.params.username }},your id is {{ $route.params.id }}</p>
</template>

//访问http://localhost:5173/User/alice/home/123
//显示User
//welcome alice,your id is 123

跳转

如果想要通过链接的形式跳转,使用RouterLink

<RouterLink to="/shop">Shop</RouterLink>

如果是要用变量做跳转,记得直接绑定

<RouterLink :to="'/column/' + item.id" @click="console.log(item.id)">

当你想使用函数做跳转,你就需要push

// const goToHomePage = () => {
//     routerhome.push('/login');
// };

重定向

重定向

    {
      path: '/buy',
      redirect: '/shop'
    }

嵌套路由

嵌套路由

const routes = [
  {
    path: '/user/:id',
    component: User,
    children: [
      {
        // 当 /user/:id/profile 匹配成功
        // UserProfile 将被渲染到 User 的 <router-view> 内部
        path: 'profile',
        component: UserProfile,
      },
      {
        // 当 /user/:id/posts 匹配成功
        // UserPosts 将被渲染到 User 的 <router-view> 内部
        path: 'posts',
        component: UserPosts,
      },
    ],
  },
]

嵌套示例

import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import shop from '../views/shop.vue'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'home',
      component: HomeView
    },
    {
      path: '/about',
      name: 'about',
      // route level code-splitting
      // this generates a separate chunk (About.[hash].js) for this route
      // which is lazy-loaded when the route is visited.
      component: () => import('../views/AboutView.vue')
    },
    {
      path: '/shop',
      name: 'shop',
      component: shop
    },
    {
      path: '/User/:username/home/:id',
      name: 'User',
      component: () => import('../news/User.vue'),
      children: [
        {
          path: '',
          component: () => import('../news/Userhome.vue')
        },
        {
          path: 'profile',
          component: () => import('../news/Userprofile.vue')
        },
      ],
    },
    {
      path: '/buy',
      redirect: '/shop'
    }
  ]
})

export default router
import { RouterView } from 'vue-router';
<template>
    <h1>User</h1>
    <p>welcome {{ $route.params.username }},your id is {{ $route.params.id }}</p>
    <RouterView></RouterView> //必须有这行来显示子组件
</template>

Userhome.vue
<template>
    <p>here is home</p>
</template>

Userprofile.vue
<template>
    <p>here is profile</p>
</template>

懒加载

懒加载可以让你访问到对应路由的时候再加载对应的资源,以减少资源开销和加载时间

// 将
// import UserDetails from './views/UserDetails.vue'
// 替换成
const UserDetails = () => import('./views/UserDetails.vue')

const router = createRouter({
  // ...
  routes: [{ path: '/users/:id', component: UserDetails }],
})

props传参

URL /search?q=vue 将传递 {query: 'vue'} 作为 props 传给 SearchUser 组件

const routes = [
  {
    path: '/search',
    component: SearchUser,
    props: route => ({ query: route.query.q })
  }
]

在对应组件使用$route.query.q来获取值
另外一种params的方式在上面动态路由里面展示过了

回退

退回上n页

router.go(n)

push

import { useRouter } from 'vue-router';
const routerhome = useRouter()
const goToHomePage = () => {
    routerhome.push('/');
};

守卫

声明式api里面有这样三个路由守卫

const UserDetails = {
  template: `...`,
  beforeRouteEnter(to, from) {
    // 在渲染该组件的对应路由被验证前调用
    // 不能获取组件实例 `this` !
    // 因为当守卫执行时,组件实例还没被创建!
  },
  beforeRouteUpdate(to, from) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,
    // 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`
  },
  beforeRouteLeave(to, from) {
    // 在导航离开渲染该组件的对应路由时调用
    // 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
  },
}

换到setup的组合式里面,由于setup中路由已经创建完毕,所以只有leave和update两个可用
如果你想使用enter,可以参考这篇,其实原理就是除了setup以外另写个script,在里面用export default
https://blog.richex.cn/vue3-how-to-use-beforerouteenter-in-script-setup-syntactic-sugar.html

import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
import { ref } from 'vue'

export default {
  setup() {
    // 与 beforeRouteLeave 相同,无法访问 `this`
    onBeforeRouteLeave((to, from) => {
      const answer = window.confirm(
        'Do you really want to leave? you have unsaved changes!'
      )
      // 取消导航并停留在同一页面上
      if (!answer) return false
    })

    const userData = ref()

    // 与 beforeRouteUpdate 相同,无法访问 `this`
    onBeforeRouteUpdate(async (to, from) => {
      //仅当 id 更改时才获取用户,例如仅 query 或 hash 值已更改
      if (to.params.id !== from.params.id) {
        userData.value = await fetchUser(to.params.id)
      }
    })
  },
}

当然你也可以用用看路由独享的守卫,不过这个得写在router里面

const routes = [
  {
    path: '/users/:id',
    component: UserDetails,
    beforeEnter: (to, from) => {
      // reject the navigation
      return false
    },
  },
]

完整的导航解析流程

  1. 导航被触发。
  2. 在失活的组件里调用 beforeRouteLeave 守卫。
  3. 调用全局的 beforeEach 守卫。
  4. 在重用的组件里调用 beforeRouteUpdate 守卫(2.2+)。
  5. 在路由配置里调用 beforeEnter。
  6. 解析异步路由组件。
  7. 在被激活的组件里调用 beforeRouteEnter。
  8. 调用全局的 beforeResolve 守卫(2.5+)。
  9. 导航被确认。
  10. 调用全局的 afterEach 钩子。
  11. 触发 DOM 更新。
  12. 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。

示例:检测用户是否登录,否则通通逐一发送login(老牛皮藓网址了

import { globalFlagsStore } from '@/stores/store'
router.beforeEach((to, from, next) => {
  const { user } = globalFlagsStore().flags;
  
  // 如果用户未登录且访问的不是登录页面,则导航至登录页面
  if (!user.isLoging && to.path !== '/login') {
    next('/login');
  } else {
    next();
  }
});

meta

可以在路由上面带元信息,用来配合守卫之类的功能

import ColumnListVue from '@/hooks/ColumnList.vue'
import ColumnDetailVue from '@/views/ColumnDetail.vue'
import HomeVue from '@/views/Home.vue'
import LoginVue from '@/views/Login.vue'
import PostpageVue from '@/views/Postpage.vue'
import { createRouter, createWebHistory } from 'vue-router'
import { globalFlagsStore } from '@/stores/store'


const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'home',
      component: HomeVue
    },
    {
      path: '/login',
      name: 'login',
      component: LoginVue,
      meta: {requirenologin: true}
    },
    {
      path: '/column/',
      name: 'column',
      component: ColumnListVue
    },
    {
      path: '/column/:id',
      name: 'columndetail',
      component: ColumnDetailVue
    },
    {
      path: '/create/',
      name: 'createpost',
      component: PostpageVue,
      meta: {requirelogin: true},
    }

  ]
})

router.beforeEach((to, from, next) => {
  const { user } = globalFlagsStore().flags;
  //登录的不给访问login,没登陆的不给访问create
  if (!user.isLoging && to.meta.requirelogin) {
    next('/login');
  }else if (to.meta.requirenologin && user.isLoging){
    next('/');
  }else {
    next()
  }
});


export default router

promise

const test = new Promise((resolve,rejects) =>{
    setTimeout(() => {
        const data = { name: "John", age: 30 };
        // 如果成功获取到数据,调用 resolve 并传递数据
        resolve(data);
        // 如果获取数据失败,调用 reject 并传递错误信息
        rejects("Error fetching data");
      }, 2000);  
}).then((data)=>{
    if(true){
        console.log(data)
    }
}).catch((error) => {
    console.error(error)
})

async await

async表明一个函数是个异步函数,在async内部可以使用await,await表面这个调用是个promise调用,如果调用成功会返回调用结果,如果失败则会返回报错

async function getData() {
  try {
    const response = await fetch('https://api.example.com/data'); // 等待fetch请求的Promise对象完成
    const data = await response.json(); // 等待解析响应的Promise对象完成
    console.log(data);
  } catch (error) {
    console.log('Error:', error);
  }
}

getData();

pinia

状态管理工具,对标vuex,更轻量
vite自定义安装的时候可以直接勾选,如果快速安装则需要另外再装下
npm install pinia
状态是啥,说白了就是当前一些值,比如说我当前在查询3369这个单号的工单,然后我需要切换到另外一个组件继续处理,我希望再切换过去的时候依旧能保持工单号,这样可以减少再操作的麻烦,并且这种情况可能会出现多次,于是我就需要一个状态保持工具
pinia中的store是全局的,不跟随组件的加载卸载改变,并且每个组件都可以读或者写,它有三个概念,state、getter 和 action,相当于组件中的 data、 computed 和 function
(选项式真的太丑了,我宁愿写组合式)

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const pinia = createPinia()
const app = createApp(App)

app.use(pinia)
app.mount('#app')


如果你选的是自定义安装,那么自带的示例长这样,放在stores目录下(不过好像没被使用

import { ref, computed } from 'vue'
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', () => {
  const count = ref(0) //代表变量
  const doubleCount = computed(() => count.value * 2) //代表计算属性
  function increment() { //代表函数
    count.value++
  }

  return { count, doubleCount, increment }
})

让我们写个vue调用一下他

<template>
    <h3>hello,here is shop</h3>
    <p>here is your point:{{ store.count }}</p>
    <button @click="store.increment">click</button>
</template>

<script setup>
import { useCounterStore } from '@/stores/counter'
const store = useCounterStore()
</script>

钩子


使用不同的钩子函数可以在程序的不同生命周期来调用功能
vue3中beforeDestory变成beforeUnmount,destoryed变成unmounted,beforecreate和created合成了setup
另外新增两个调试用的

renderTracked -> onRenderTracked,状态跟踪,vue3新引入的钩子函数,只有在开发环境有用,用于跟踪所有响应式变量和方法,一旦页面有update,就会跟踪他们并返回一个event对象

renderTriggered -> onRenderTriggered,状态触发,同样是vue3新引入的钩子函数,只有在开发环境有效,与onRenderTracked的效果类似,但不会跟踪所有的响应式变量方法,只会定点追踪发生改变的数据,同样返回一个event对象


使用示例

import {onMounted, onUpdated,onRenderTriggered} from 'vue'

onMounted(()=> {
  console.log('mounted')
})
onUpdated(()=>{
  console.log('updated')
})
onRenderTriggered(()=>{
  console.log(event)
})

监听鼠标坐标

const x = ref(0)
const y = ref(0)
function updateMouse(e: MouseEvent){
  x.value = e.pageX
  y.value = e.pageY
}
onMounted(()=>{
  document.addEventListener('click',updateMouse)
})
onUnmounted(()=>{
  document.removeEventListener('click',updateMouse)
})

打包

npm run build
npm run preview
vite自带rollup打包,不用另外去折腾webpack啥的
rollup带tree shaking,也就是摇树机制,会自动把没用到的代码删除掉

  "scripts": {
    "dev": "vite",
    "build": "run-p type-check \"build-only {@}\" --", //打包语句,@会被替换成build-only
    "preview": "vite preview",
    "test:unit": "vitest",
    "build-only": "vite build",
    "type-check": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false"
  },

第三方

axios

npm install --save axios
npm install --save querystring
基于promise
(这里不知道为啥全局引用失败,先单个引用先)
如果安装了报但是还是报找不到这个模块,那么看下这个处理方法
https://blog.csdn.net/qq_22841387/article/details/123433223?spm=1001.2014.3001.5501

基础

get

axios({
    method: "get",
    url: "xxxx"
}).then(res => {
    console.log(res.data);
})

简写
axios.get("xxxx")
    .then(res =>{
      console.log(res.data);
    })

post

axios({
    method:"post",
    url:"xxx",
    data:qs.stringify({
        param1:"xxx",
        param2:"xxxx",
        verification_code:"xxxxx"
    })
}).then(res =>{
    console.log(res.data);
})

简写
axios.post("xxx", qs.stringify({
      param: "xxx",
  ......
    }))
      .then(res => {
        console.log(res.data);
      })

跨域问题
在vue/vite.config.js中加上

devServer: {
    proxy: {
      '/api': {
        target: '<url>',
        changeOrigin: true
      }
    }
}

ts例子

结合TS使用

import { ref } from 'vue'
import axios from 'axios'

function useURLLoader (url: string){
    const result = ref(null)
    const loading = ref(true)
    const loaded = ref(false)
    const error = ref(null)

    axios.get(url).then((rawData) => {
        loading.value = false
        loaded.value = true
        result.value = rawData.data
    }).catch(e => {
        error.value = e
        loading.value = false
    })
    return {
        result,
        loading,
        loaded,
        error
    }
}

export default useURLLoader
import useURLLoader from './hooks/useURLLoader'
const {result,loading,loaded} = useURLLoader("https://mock.apifox.cn/m1/3372030-0-default/pet/1")
      <h1 v-if="loading">Loading</h1>
      <p v-if="loaded">reslut:{{ result }}</p>

如果想要使用的调用都在某个网站上的不同路径下,可以在main直接设置根目录,然后调用api的时候只要写相对目录就行了

axios.defaults.baseURL = 'https://根目录'
axios.get('/users')

拦截器

说白了就是把包拦下来改点东西再继续传,分成请求和响应两种
请求拦截器

axios.interceptors.request.use(
  (config) => {
    // 在发送请求之前,对请求进行处理
    // 比如添加请求头,验证身份信息等
    config.headers.Authorization = 'Bearer token';

    return config;//必须返回,不能只拦不放
  },
  (error) => {
    // 请求错误时的处理
    return Promise.reject(error);
  }
);

在 Axios 中,请求拦截器的回调函数中的 config 参数是一个包含请求配置的对象。这个对象具有以下常用属性:

url:请求的 URL 地址。
method:请求的 HTTP 方法,例如 GET、POST。
baseURL:基础 URL 地址,会被添加到 url 前面。
headers:请求的头部信息,是一个对象,可以设置请求头的内容,例如设置认证信息。
params:请求的 URL 参数,也是一个对象。这个对象会被自动转换为 URL 查询字符串的形式,并添加到 URL 的末尾。
data:请求的主体数据,通常用于 POST、PUT、PATCH 等请求方法。
timeout:请求的超时时间,单位是毫秒。
transformRequest:请求数据的转换函数,可以用来转换请求数据的格式。
transformResponse:响应数据的转换函数,可以用来转换响应数据的格式。
paramsSerializer:URL 参数的序列化函数,可以自定义参数的序列化方式。
responseType:响应的数据类型,例如 ‘json’、‘text’ 等。
withCredentials:是否允许携带跨域请求的凭证。
auth:用于 HTTP 基础认证的用户名和密码。
onUploadProgress:上传进度的回调函数。
onDownloadProgress:下载进度的回调函数。

响应拦截器

axios.interceptors.response.use(
  (response) => {
    // 在接收到响应之前,对响应进行处理
    // 比如解析数据,统一处理错误等
    const data = response.data;
    if (data.code !== 200) {
      // 处理错误
      console.log('请求出错');
    }

    return response;
  },
  (error) => {
    // 响应错误时的处理
    return Promise.reject(error);
  }
);

封装、并发

很多时候肯定要用的网址不止一个,单纯封一个默认的网址有点不够用,可以用create来创建独立的axios示例
axios.all可以处理并发请求,他的参数为一个包含多个请求的数组
axios本身也是个promise函数,也可以用then catch来处理结果

import axios from 'axios'

const userRequest = axios.create({
  baseURL:"xx"
}
)
const goodsRequest = axios.create({
  baseURL: "xxxx"
})

axios.all([
  userRequest.get(),
  goodsRequest.post({})
])
.then(axios.spread((userResponse, goodsResponse) => {
  console.log('User Response:', userResponse);
  console.log('Goods Response:', goodsResponse);
}))
.catch(error => {
  console.log('Error:', error);
});

环境隔离

不同环境需要封装的地址是不同的,可以通过配置环境文件,来做到在启动的时候就进行自动读取对应环境下的配置,以实现环境隔离封装
https://cn.vitejs.dev/guide/env-and-mode.html
示例:
在项目根目录下创建.env.development

VITE_MODE_NAME=development
VITE_APP_BASE_URL=http://127.0.0.1:8000/

配置vite.config.ts

export default defineConfig({
  plugins: [
    vue(),
    vueJsx(),
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  },
  envDir: './'//按照实际的路径来
})

封装使用

import axios, { type InternalAxiosRequestConfig, type AxiosResponse } from 'axios';

function request(config) {
    const instance = axios.create({
        baseURL: import.meta.env.VITE_APP_BASE_URL,
        timeout: 5000
    });
    return instance(config);
}

quillEditor

富文本编辑器
https://blog.csdn.net/weixin_42232622/article/details/126317622
https://www.kancloud.cn/liuwave/quill/1434140

npm install @vueup/vue-quill@alpha --save

无缝滚动vue3-seamless-scroll

https://doc.wssio.com/opensource/vue3-seamless-scroll/

webpack

npm install webpack-dev-server;

UI

antd vue

npm install ant-design-vue@next --save
https://2x.antdv.com/docs/vue/getting-started-cn

import Antd from 'ant-design-vue';
app.use(Antd)

element-plus

npm install element-plus --save
默认源下不了可以用国内源--registry=https://registry.npmmirror.com

import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'

const app = createApp(App)

app.use(ElementPlus)
app.mount('#app')

iconfont

一个前端图表素材库
https://www.iconfont.cn/
选择图表放入购物车,结算到项目,选择下载到本地,解压后放到本地目录下
打开里面的html文件,根据提示引用对应的组件,也可直接在main引用css

标签: none

添加新评论