当前位置:   article > 正文

vue3-vite前端快速入门教程 vue-element-admin_admin_vue3_vite

admin_vue3_vite

Vue3快速入门学习

初始化项目

  1. # 创建项目
  2. npm create vite@latest my-vue-app -- --template vue
  3. # 安装依赖
  4. npm i
  5. # 运行
  6. npm run dev

模板语法

文本插值

最基本的数据绑定形式是文本插值,它使用的是“Mustache”语法 (即双大括号):

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

双大括号标签会被替换为相应组件实例中 msg 属性的值。同时每次 msg 属性更改时它也会同步更新。

原始 HTML

双大括号会将数据解释为纯文本,而不是 HTML。若想插入 HTML,你需要使用 v-html 指令

  1. <template>
  2. {{ num }}
  3. <span>Message: {{ msg }}</span>
  4. <p>Using text interpolation: {{ html }}</p>
  5. <p>Using v-html directive: <span v-html="html"></span></p>
  6. </template>
  7. <script setup>
  8. let num = 123
  9. let msg = "(*´▽`)ノノ"
  10. let html = '<span style="color: red">This should be red.</span>'
  11. </script>

Attribute 绑定

双大括号不能在 HTML attributes 中使用。想要响应式地绑定一个 attribute,应该使用 v-bind 指令

  1. <div v-bind:id="dynamicId"></div>
  2. <input v-bind:value="dynamicId">
  3. let dynamicId=1

v-bind 指令指示 Vue 将元素的 id attribute 与组件的 dynamicId 属性保持一致。如果绑定的值是 null 或者 undefined,那么该 attribute 将会从渲染的元素上移除。

简写

因为 v-bind 非常常用,我们提供了特定的简写语法:

  1. <div :id="dynamicId"></div>
  2. <input v-bind:value="dynamicId"><br>
  3. <input :value="dynamicId">

开头为 : 的 attribute 可能和一般的 HTML attribute 看起来不太一样,但它的确是合法的 attribute 名称字符,并且所有支持 Vue 的浏览器都能正确解析它。此外,他们不会出现在最终渲染的 DOM 中。简写语法是可选的,但相信在你了解了它更多的用处后,你应该会更喜欢它。

接下来的指引中,我们都将在示例中使用简写语法,因为这是在实际开发中更常见的用法。

布尔型 Attribute

布尔型 attribute 依据 true / false 值来决定 attribute 是否应该存在于该元素上。disabled 就是最常见的例子之一。

v-bind 在这种场景下的行为略有不同:

  1. <button :disabled="isButtonDisabled">Button</button>
  2. <button :disabled="!isButtonDisabled">Button</button>

isButtonDisabled真值或一个空字符串 (即 <button disabled="">) 时,元素会包含这个 disabled attribute。而当其为其他假值时 attribute 将被忽略。

动态绑定多个值

如果你有像这样的一个包含多个 attribute 的 JavaScript 对象:

  1. const objectOfAttrs = {
  2. id: 'container',
  3. class: 'wrapper'
  4. }
  5. 通过不带参数的 v-bind,你可以将它们绑定到单个元素上:
  6. <div v-bind="objectOfAttrs"></div>
  7. <div v-bind="objectOfAttrs">
  8.   233
  9. </div>

使用 JavaScript 表达式

至此,我们仅在模板中绑定了一些简单的属性名。但是 Vue 实际上在所有的数据绑定中都支持完整的 JavaScript 表达式:

  1. {{ number + 1 }}
  2. {{ ok ? 'YES' : 'NO' }}
  3. {{ message.split('').reverse().join('') }}
  4. <div :id="`list-${id}`"></div>
  5. let number = 1
  6. let ok = 1
  7. let message = '如何快速进行多任务切换:多任 务切换的关键在 于能不能在一 段 时间内集 中火力, 把一件事做好,再去做下一件事。如果已经定下具 体的目标,一切的工具都只是辅助 ,必要时可以全都 舍弃掉。 '
  8. let id = 1

这些表达式都会被作为 JavaScript ,以当前组件实例为作用域解析执行。

在 Vue 模板内,JavaScript 表达式可以被使用在如下场景上:

  • 在文本插值中 (双大括号)

  • 在任何 Vue 指令 (以 v- 开头的特殊 attribute) attribute 的值中

仅支持表达式

每个绑定仅支持单一表达式,也就是一段能够被求值的 JavaScript 代码。一个简单的判断方法是是否可以合法地写在 return 后面。

因此,下面的例子都是无效的:

  1. <!-- 这是一个语句,而非表达式 -->
  2. {{ var a = 1 }}
  3. <!-- 条件控制也不支持,请使用三元表达式 -->
  4. {{ if (ok) { return message } }}

调用函数

可以在绑定的表达式中使用一个组件暴露的方法:

  1. <time :title="toTitleDate(date)" :datetime="date">
  2. {{ formatDate(date) }}
  3. </time>

TIP

绑定在表达式中的方法在组件每次更新时都会被重新调用,因此应该产生任何副作用,比如改变数据或触发异步操作。

受限的全局访问

模板中的表达式将被沙盒化,仅能够访问到有限的全局对象列表。该列表中会暴露常用的内置全局对象,比如 MathDate

没有显式包含在列表中的全局对象将不能在模板内表达式中访问,例如用户附加在 window 上的属性。然而,你也可以自行在 app.config.globalProperties 上显式地添加它们,供所有的 Vue 表达式使用。

指令 Directives

指令是带有 v- 前缀的特殊 attribute。Vue 提供了许多内置指令,包括上面我们所介绍的 v-bindv-html

指令 attribute 的期望值为一个 JavaScript 表达式 (除了少数几个例外,即之后要讨论到的 v-forv-onv-slot)。一个指令的任务是在其表达式的值变化时响应式地更新 DOM。以 v-if 为例:

<p v-if="seen">Now you see me</p>

这里,v-if 指令会基于表达式 seen 的值的真假来移除/插入该 <p> 元素。

参数 Arguments

某些指令会需要一个“参数”,在指令名后通过一个冒号隔开做标识。例如用 v-bind 指令来响应式地更新一个 HTML attribute:

  1. <a v-bind:href="url"> ... </a>
  2. <!-- 简写 -->
  3. <a :href="url"> ... </a>
  4. 这里 href 就是一个参数,它告诉 v-bind 指令将表达式 url 的值绑定到元素的 href attribute 上。在简写中,参数前的一切 (例如 v-bind:) 都会被缩略为一个 : 字符。
  5. 另一个例子是 v-on 指令,它将监听 DOM 事件:
  6. <a v-on:click="doSomething"> ... </a>
  7. <!-- 简写 -->
  8. <a @click="doSomething"> ... </a>

这里的参数是要监听的事件名称:clickv-on 有一个相应的缩写,即 @ 字符。我们之后也会讨论关于事件处理的更多细节。

动态参数

同样在指令参数上也可以使用一个 JavaScript 表达式,需要包含在一对方括号内:

  1. <!--
  2. 注意,参数表达式有一些约束,
  3. 参见下面“动态参数值的限制”与“动态参数语法的限制”章节的解释
  4. -->
  5. <a v-bind:[attributeName]="url"> ... </a>
  6. <!-- 简写 -->
  7. <a :[attributeName]="url"> ... </a>

这里的 attributeName 会作为一个 JavaScript 表达式被动态执行,计算得到的值会被用作最终的参数。举例来说,如果你的组件实例有一个数据属性 attributeName,其值为 "href",那么这个绑定就等价于 v-bind:href

相似地,你还可以将一个函数绑定到动态的事件名称上:

  1. <a v-on:[eventName]="doSomething"> ... </a>
  2. <!-- 简写 -->
  3. <a @[eventName]="doSomething">

在此示例中,当 eventName 的值是 "focus" 时,v-on:[eventName] 就等价于 v-on:focus

动态参数值的限制

动态参数中表达式的值应当是一个字符串,或者是 null。特殊值 null 意为显式移除该绑定。其他非字符串的值会触发警告。

动态参数语法的限制

动态参数表达式因为某些字符的缘故有一些语法限制,比如空格和引号,在 HTML attribute 名称中都是不合法的。例如下面的示例:

  1. <!-- 这会触发一个编译器警告 -->
  2. <a :['foo' + bar]="value"> ... </a>

如果你需要传入一个复杂的动态参数,我们推荐使用计算属性替换复杂的表达式,也是 Vue 最基础的概念之一,我们很快就会讲到。

当使用 DOM 内嵌模板 (直接写在 HTML 文件里的模板) 时,我们需要避免在名称中使用大写字母,因为浏览器会强制将其转换为小写:

<a :[someAttr]="value"> ... </a>

上面的例子将会在 DOM 内嵌模板中被转换为 :[someattr]。如果你的组件拥有 “someAttr” 属性而非 “someattr”,这段代码将不会工作。单文件组件内的模板受此限制。

修饰符 Modifiers

修饰符是以点开头的特殊后缀,表明指令需要以一些特殊的方式被绑定。例如 .prevent 修饰符会告知 v-on 指令对触发的事件调用 event.preventDefault()

<form @submit.prevent="onSubmit">...</form>

之后在讲到 v-onv-model 的功能时,你将会看到其他修饰符的例子。

最后,在这里你可以直观地看到完整的指令语法:

指令语法图

总结

  1. <template>
  2. <h3>{{ text }}</h3>
  3. <h3>{{ user }}</h3>
  4. <!--
  5. 指令 v-
  6. v-model 数据双向绑定
  7. v-if 判断表达式的值,true则显示,false则隐藏 -- 控制dom元素的创建和销毁,应避免频繁切换状态
  8. v-show 和v-if区别 -- 始终会被渲染并保留在dom中,只是css被隐藏了 "display: none;" 一次性的
  9. v-for 循环
  10. v-bind 绑定属性或对象
  11. v-on 注册事件
  12. -->
  13. <input v-model="user.age" />
  14. <span v-if="user.age == 18">成年人:{{ user.age }}</span>
  15. <span v-else-if="user.age < 18">未成年</span>
  16. <span v-else>长大了...</span>
  17. <span v-show="user.age >= 18">
  18. 和v-if区别:始终会被渲染并保留在dom中,只是css被隐藏了 "display: none;"
  19. </span>
  20. <!-- <button v-bind:disabled="true">v-bind</button> -->
  21. <button :disabled="true">v-bind</button>
  22. <h6
  23. v-for="(friends,index) in user.friends"
  24. :key="index"
  25. style="color: rgb(70, 238, 146)"
  26. >
  27. {{ friends.name }} - {{ friends.age }} - {{ index }}
  28. </h6>
  29. <!-- <button v-on:click="addFriend">添加好友</button> -->
  30. <button @click="addFriend">添加好友</button>
  31. &nbsp;&nbsp;&nbsp;
  32. <button @click="deleteFriend">删除</button>
  33. <p :class="{ active: isActive }">Class 与 Style 绑定</p>
  34. <div v-for="i in 5">{{i}}</div>
  35. </template>
  36. <script setup>
  37. import { ref, reactive } from "vue";
  38. const text = ref("HelloWorld");
  39. const isActive = ref(true);
  40. const user = reactive({
  41. name: "小郑",
  42. age: 18,
  43. friends: [
  44. {
  45. name: "小张",
  46. age: 18,
  47. },
  48. {
  49. name: "小李",
  50. age: 20,
  51. },
  52. {
  53. name: "张三",
  54. age: 201,
  55. },
  56. ],
  57. });
  58. function addFriend() {
  59. user.friends.push({
  60. name: "哈基米",
  61. age: 18,
  62. });
  63. }
  64. function deleteFriend() {
  65. user.friends.pop()
  66. }
  67. </script>
  68. <style scoped>
  69. .active {
  70. color: rgb(134, 17, 250);
  71. }
  72. </style>

ref 和 reactive 是 Vue 3 中用于管理数据的两个方法。

ref 用于创建一个可响应的指针。指针可以指向任何值,包括原始值、对象或数组。当指针指向的值发生变化时,Vue 会自动更新指针的值。

reactive 用于创建一个响应式的对象。对象的所有属性都将成为响应式变量。当对象的属性发生变化时,Vue 会自动更新该属性的值。

  • text 是一个 ref 对象,指向字 符串 "HelloWorld"。当字符串发生变化时,text 的值也会发生变化。
  • isActive 也是一个 ref 对象,指向布尔值 true。当布尔值发生变化时,isActive 的值也会发生变化。
  • user 是一个 reactive 对象,包含三个属性:nameage 和 friends。当这些属性发生变化时,user 对象的值也会发生变化。

使用场景

ref 通常用于以下场景:

  • 需要在组件中使用原始值、对象或数组时。
  • 需要监听值的变化时。

reactive 通常用于以下场景:

  • 需要在组件中使用对象时。
  • 需要监听对象属性的变化时。

导入其他vue组件

  1. <template>
  2. <HelloWorld />
  3. </template>
  4. <script setup>
  5. import HelloWorld from "./components/HelloWorld.vue";
  6. </script>

响应式API

  1. <template>
  2. <h3>{{ count }}</h3>
  3. <h3>{{ data }}</h3>
  4. <hr />
  5. <h3>{{ getStr }}</h3>
  6. <h3>{{ getStr2 }}</h3>
  7. <input type="text" v-model="getStr2" />
  8. <hr />
  9. <h3>name:{{ name }}</h3>
  10. <button @click="changeData">修改</button>
  11. </template>
  12. <script setup>
  13. import { ref, reactive, computed, watch, watchEffect, toRefs } from "vue";
  14. // ref:定义基本数据类型的响应式数据
  15. var count = ref(0);
  16. // reactive:定义“数组/对象/map”复杂数据类型的深层响应式数据,shallowReactive:浅层响应式(只保留对这个对象顶层次访问的响应性)
  17. const data = reactive({
  18. name: "小王",
  19. age: 18,
  20. girlfriends: [{ name: "小张" }],
  21. });
  22. // toRefs:解构响应式,没有的话,无法修改name值,修改的时候使用 name.value 修改
  23. const { name } = toRefs(
  24. reactive({
  25. name: "小郑",
  26. })
  27. );
  28. // computed:计算属性
  29. const str = ref("hello");
  30. const getStr = computed(() => {
  31. console.log("getStr计算属性执行了...");
  32. return str.value;
  33. });
  34. // 如果要修改计算属性值,上面的方式会报错 Write operation failed: computed value is readonly
  35. // 使用下面的方式
  36. const getStr2 = computed({
  37. get() {
  38. console.log("getStr2计算属性执行了...");
  39. return str.value;
  40. },
  41. set(val) {
  42. str.value = val;
  43. },
  44. });
  45. // watch:监听器
  46. watch(
  47. // count, // ref
  48. // () => geoObj.value.lng, // ref 对象中的某一个属性值
  49. data, // reactive
  50. // () => data.age, // reactive 对象中的某一个属性值
  51. // [count, data], // 监听多个数据
  52. // () => props.list, // 监听defineProps中的数据
  53. // () => proxy.$router.currentRoute.value, // 监听路由变化
  54. (newValue, oldValue) => {
  55. console.log("监听器执行了... ", newValue, oldValue);
  56. },
  57. {
  58. immediate: true, // 初始化执行一次
  59. // deep: true, // 深度监听 -- eg: 监听数组里面的数据变更
  60. }
  61. );
  62. // watchEffect:副作用函数,里面涉及到的属性有变更就会被触发执行
  63. watchEffect(() => {
  64. console.log("watchEffect执行了... ", data.age);
  65. });
  66. function changeData() {
  67. count.value++;
  68. data.age++;
  69. data.girlfriends = [{ name: "小甜" }, { name: "小李" }];
  70. data.girlfriends.push({ name: "哈基米" });
  71. name.value = "小郑变了"+count.value;
  72. str.value += "1";
  73. }
  74. </script>
  75. <style scoped>
  76. .active {
  77. color: rgb(134, 17, 250);
  78. }
  79. </style>

toRefs: toRefs是一个方法,它可以把一个响应式对象转换成普通对象,同时保留它的响应性。

比如有一个响应式对象:

const state = reactive({ foo: 1, bar: 2 })

如果我们解构它:

const { foo, bar } = state

那么foo和bar就失去了响应性。

但是如果我们使用toRefs:

  1. const state = reactive({ foo: 1, bar: 2 });
  2. const { foo, bar } = toRefs(state);

这样foo和bar仍然是响应式的!

所以toRefs的作用就是让解构后的变量也保持响应式。

computed: computed是一个方法,它可以创建一个计算属性。计算属性会根据它的依赖进行缓存和更新。监听一个值

比如:

  1. const count = ref(1)
  2. const plusOne = computed(() => count.value + 1)
  3. console.log(plusOne.value) // 2
  4. count.value++
  5. console.log(plusOne.value) // 3

这里plusOne会跟踪count的变化并实时更新自己的值。

所以computed的作用就是创建一个有缓存的属性,避免重复计算。

  1. 相同效果
  2. import { ref, reactive, computed, watch, watchEffect, toRefs } from "vue";
  3. const state = reactive({ foo: 1, bar: 2 });
  4. function changeData() {
  5. state.foo++
  6. state.bar++
  7. }
  8. const state = reactive({ foo: 1, bar: 2 });
  9. const { foo, bar } = toRefs(state);
  10. function changeData() {
  11. foo.value++
  12. bar.value++
  13. }

解决ref大量引入问题

unplugin-auto-import插件

解决 import { ref , reactive ..... } from 'vue' 大量引入的问题

配置后可以不用引入,直接使用

  1. npm install -g cnpm --registry=https://registry.npm.taobao.org
  2. cnpm i -D unplugin-auto-import
  3. vite.config.js
  4. import { defineConfig } from "vite";
  5. import vue from "@vitejs/plugin-vue";
  6. import AutoImport from "unplugin-auto-import/vite";
  7. // https://vitejs.dev/config/
  8. export default defineConfig({
  9. plugins: [
  10. vue(),
  11. AutoImport({
  12. imports: ["vue", "vue-router"],
  13. }),
  14. ],
  15. });

$ref语法糖 告别 .value


一、配置
法一
vue3.4版本之后废除 

  1. vite.config.js
  2. import { defineConfig } from 'vite'
  3. import vue from '@vitejs/plugin-vue'
  4. export default defineConfig({
  5.   plugins: [
  6.     vue({
  7.       reactivityTransform: true, // 启用响应式语法糖 $ref $computed $toRef ...
  8.     })
  9.   ]
  10. })


法二
https://vue-macros.sxzz.moe/zh-CN/features/reactivity-transform.html

tips: store(pinia版) 中使用 $ref 无法正常持久化数据!!!

  1. cnpm i -D @vue-macros/reactivity-transform
  2. vite.config.js
  3. import { defineConfig } from 'vite'
  4. import vue from '@vitejs/plugin-vue'
  5. import ReactivityTransform from '@vue-macros/reactivity-transform/vite';
  6. export default defineConfig({
  7.   plugins: [
  8.     vue(),
  9.     ReactivityTransform(), // 启用响应式语法糖 $ref ...
  10.   ]
  11. })
  12. 解决ESLint警告: '$ref' is not defined.
  13. .eslintrc.cjs
  14. module.exports = {
  15.   globals: { $ref: 'readonly', $computed: 'readonly', $shallowRef: 'readonly', $customRef: 'readonly', $toRef: 'readonly' },
  16. };


二、测试
原本 .value 响应式

  1. <template>
  2.   <h1>{{ count }}</h1>
  3.   <button @click="handleClick">click</button>
  4. </template>
  5. <script setup>
  6. let count = ref(0);
  7. function handleClick() {
  8.   count.value++;
  9. }
  10. </script>
  11. 现在 $ref 去除 .value
  12. <template>
  13.   <h1>{{ count }}</h1>
  14.   <button @click="handleClick">click</button>
  15. </template>
  16. <script setup>
  17. let count = $ref(0);
  18. function handleClick() {
  19.   count++;
  20. }
  21. </script>


三、注意事项
$ref 在以下情况无法直接使用

store pinia
watch 监听器

组件通信

通信方式:

  • props:父传子(子组件接收的数据只读)
  • emit:子传父
  • provide/inject:跨代传值
  • vuex/pinia:跨组件传值
  • 全局事件总线(mitt 或 tiny-emitter):不推荐使用
  • v-model
  • refs

一、props & emit

  • 父组件::list 传值
  • 子组件:defineProps接收父组件值,defineEmits调用父组件方法传值到父组件中 eg: proxy.$emit('handle-succ', data);
  1. 父组件 https://cn.vuejs.org/api/sfc-script-setup.html#defineprops-defineemits
  2. <template>
  3. <h3>{{ count }}</h3>
  4. <p>子组件:</p>
  5. <hello :list=list2 @add="handleAdd"/>
  6. </template>
  7. <script setup>
  8. import {ref} from "vue";
  9. import hello from "./components/hello.vue";
  10. var list2 = [{name: '小郑'}, {name: '张三'}]
  11. const count = ref(0);
  12. function handleAdd(data) {
  13. console.log(data);
  14. count.value++;
  15. }
  16. </script>
  17. <style scoped></style>
  18. 子组件
  19. <template>
  20. <h3>{{ list }}</h3>
  21. <button @click="handleSubmit">提交数据</button>
  22. </template>
  23. <script setup>
  24. // 父传子
  25. const props = defineProps({
  26. list: {
  27. type: Array,
  28. required: false,
  29. default: () => [],
  30. },
  31. });
  32. // 子传父
  33. const emits = defineEmits(["add"]);
  34. function handleSubmit() {
  35. emits("add", "child");
  36. }
  37. // 或直接通过 proxy.$emit("add", "child");
  38. </script>
  39. <style scoped></style>

二、v-model 父子组件双向绑定

  • 父组件:v-model
  • 子组件:props接收父组件值modelValueproxy.$emit("update:modelValue", 666);传值

tips: 如果父组件是v-model:num,那么子组件的modelValue变更为num

uniapp中可通过 props 来获取页面参数 (tips:子组件内无法通过这种方式获取到路径参数!) eg: /pages/index/index?code=xxx ==> const props = defineProps({ code: { type: String, required: false } });

  1. 父组件
  2. <template>
  3. <h3>父组件:{{ data }}</h3>
  4. <HelloWorld ref="helloRef" v-model="data" />
  5. </template>
  6. <script setup>
  7. import HelloWorld from "./components/HelloWorld.vue";
  8. const data = ref(0);
  9. </script>
  10. <style scoped></style>
  11. 子组件
  12. <template>
  13. <h3>子组件:{{ modelValue }}</h3>
  14. <button @click="changeData">click</button>
  15. </template>
  16. <script setup>
  17. const { proxy } = getCurrentInstance();
  18. const props = defineProps({
  19. modelValue: {
  20. type: Number,
  21. required: false,
  22. default: () => 0,
  23. },
  24. });
  25. function changeData() {
  26. proxy.$emit("update:modelValue", 666);
  27. }
  28. </script>
  29. <style scoped></style>
  30. 三、provide/inject:跨代传值
  31. 父组件
  32. import { provide } from 'vue'
  33. provide('msg', xxx)
  34. 子子组件
  35. import { inject } from 'vue'
  36. const msg = inject('msg')
  37. const msg = inject('msg', 'hello') // 没值的话使用默认值hello

父组件调用子组件方法

如果父组件想要调用子组件的方法

- 子组件为选项式api 可以在父组件中使用 `proxy.$refs.helloRef.changeData();` 调用
- 子组件为组合式api 需在子组件中使用 `defineExpose` 暴露需要调用的方法

父组件

  1. ```
  2. <template>
  3. <HelloWorld ref="helloRef" />
  4. <button @click="handleClick">click</button>
  5. <br />
  6. <button @click="$refs.helloRef.changeData()">调用子组件方法</button>
  7. </template>
  8. <script setup>
  9. const { proxy } = getCurrentInstance();
  10. import HelloWorld from "./components/HelloWorld.vue";
  11. function handleClick() {
  12. proxy.$refs.helloRef.changeData();
  13. }
  14. </script>
  15. <style scoped></style>
  16. ```
  17. 子组件
  18. ```
  19. <template>
  20. <h3>count:{{ count }}</h3>
  21. </template>
  22. <script setup>
  23. const count = ref(0);
  24. function changeData() {
  25. count.value++;
  26. }
  27. // 暴露方法
  28. defineExpose({
  29. changeData,
  30. });
  31. </script>
  32. <style scoped></style>
  33. ```

子组件调用父组件方法

  1. ### 父组件
  2. ```
  3. <template>
  4. <HelloWorld @ok="handleOk" />
  5. </template>
  6. <script setup>
  7. import { ref } from "vue";
  8. import HelloWorld from "./components/HelloWorld.vue";
  9. function handleOk(data) {
  10. console.log(data);
  11. }
  12. </script>
  13. <style scoped></style>
  14. ```
  15. ### 子组件
  16. 法一:
  17. ```
  18. <script setup>
  19. const { proxy } = getCurrentInstance();
  20. function changeData() {
  21. proxy.$emit('ok', 'hello');
  22. }
  23. </script>
  24. ```
  25. 法二:
  26. ```
  27. <script setup>
  28. const { proxy } = getCurrentInstance();
  29. const emits = defineEmits(["ok"]);
  30. function changeData() {
  31. emits("ok", "hello");
  32. }
  33. </script>
  34. ```

插槽

这是一个使用 Vue 3 的组件插槽(slots)的示例。

<template> 标签内定义了一个名为 HelloWorld 的组件。

这个组件有以下几种类型的插槽:

  1. 具名插槽(Named Slots)
  2. <slot name="left">
  3. <slot name="right">
  4. 使用方法:
  5. <template v-slot:left>...</template>
  6. <template #right>...</template>
  7. 作用域插槽(Scoped Slots)
  8. <slot :data="item">
  9. 可以将组件内的数据(item)传递给插槽。
  10. 使用方法:
  11. <template #default="{ data }">...</template>
  12. 动态插槽(Dynamic Slots)
  13. <slot name="dynamic">
  14. 插槽的名称可以动态绑定。
  15. 使用方法:
  16. <template #\[xx\]>...</template>
  17. 这里的 xx 可以是一个变量。

主要功能是组件内通过不同类型的插槽向组件外暴露数据或标记点,以便组件外插入自定义内容。

这提高了组件的灵活性和可复用性。

Vue 的插槽(slot)允许组件内注入自定义内容。它的工作原理可以简单概括为:

  1. 组件内部使用 <slot> 标签作为插槽容器并定义名称
  2. <div class="component">
  3. <slot name="header">默认内容</slot>
  4.  
  5. <div class="content">...</div>
  6. <slot name="footer"></slot>
  7. </div>
  8. 组件外部使用特殊的 template 语法插入自定义内容到插槽中
  9. <MyComponent>
  10. <template v-slot:header>
  11.   <!-- 自定义header -->
  12. </template>
  13.  
  14. <template v-slot:footer>
  15.   <!-- 自定义footer -->
  16. </template>
  17. </MyComponent>
  18. Vue 渲染组件时:
  19. 如果有插入内容,则替换 <slot> 对应位置为自定义内容
  20. 如果没有插入内容,则保留 <slot> 的默认内容

所以,插槽允许用户决定组件内部的某些部分应显示什么内容,提高了组件的灵活性和可复用性。

同时为了模块化,插槽内容和组件其他部分相互独立。这就是插槽的工作原理。

  1. 作用域插槽(Scoped Slots)
  2. 在组件中,可以为插槽传递数据:
  3. <slot :user="user">
  4. {{ user.lastName }}
  5. </slot>
  6. 组件使用方可以接收传递的数据并自定义插槽内容:
  7. <MyComponent>
  8. <template #default="slotProps">
  9.   {{ slotProps.user.firstName }}
  10. </template>  
  11. </MyComponent>
  12. 这样就实现了从组件内部将数据传递给插槽。
  13. 动态插槽(Dynamic Slots)
  14. 插槽名可以是动态绑定的:
  15. <slot :name="slotName">...</slot>
  16. 使用模板引用作为插槽名:
  17. <MyComponent>
  18. <template #[slotName]>
  19.   ...自定义内容
  20. </template>
  21. </MyComponent>

好的,举两个例子说明 Vue 插槽的高级用法:

slotName 可以是一个变量,这样插槽名就可以动态变化了。

这种方式非常灵活,允许根据条件自定义不同的插槽内容。

所以这两种机制都大大提高了插槽的可扩展性。

  1. 父组件:
  2. <template>
  3. <HelloWorld>
  4. <!--   <template v-slot:left>具名插槽-left</template>-->
  5.   <template v-slot:left></template>
  6.   <template #right>具名插槽-right</template>
  7.   <template #default="{ data }">作用域插槽:{{ data }}</template>
  8.   <template #[xx]>动态插槽</template>
  9. </HelloWorld>
  10. </template>
  11. <script setup>
  12. import HelloWorld from "./components/HelloWorld.vue";
  13. let xx = ref("dynamic"); // 这里可以随时变 dynamic/left => xx
  14. </script>
  15. <style scoped></style>
  16. 子组件:
  17. <template>
  18. <h3>hello</h3>
  19. <slot name="left"><h1>具名插槽-left</h1></slot>
  20. <br />
  21. <slot name="right"></slot>
  22. <div v-for="item in list" :key="item.id">
  23.   <slot :data="item"></slot>
  24.   <!-- 作用域插槽可回调值给父组件使用 <template #right-show="{ isShow }"></template> -->
  25.     <!-- <slot name="right-show" :is-show="isShowRightMenu" /> -->
  26. </div>
  27. <slot name="dynamic"></slot>
  28. </template>
  29. <script setup>
  30. let list = ref([
  31. { id: 1, name: "小张" },
  32. { id: 2, name: "小李" },
  33. ]);
  34. </script>
  35. <style scoped></style>

生命周期钩子

组合式 API:生命周期钩子 | Vue.js

  1. <template>
  2. <h3>hello</h3>
  3. <button id="count" @click="count++">{{ count }}</button>
  4. </template>
  5. <script setup>
  6. console.log(111);
  7. onMounted(() => {
  8. console.log(222);
  9. });
  10. const count = ref(0)
  11. onUpdated(() => {
  12. // 文本内容应该与当前的 `count.value` 一致
  13. // console.log(document.getElementById('count').textContent)
  14. console.log(count.value)
  15. })
  16. </script>
  17. <style scoped></style>
  1. 1. setup()
  2. ```
  3. // 组件初始化时执行,通常用于定义数据、方法等
  4. setup(){
  5. // 代码逻辑
  6. }
  7. ```
  8. 2. onBeforeMount()
  9. ```
  10. // 在组件渲染到页面之前执行
  11. onBeforeMount(){
  12. // 获取渲染前的 DOM 状态
  13. }
  14. ```
  15. 3. onMounted()
  16. ```
  17. // 组件渲染 complet 后执行
  18. // 可以获取到 DOM 元素
  19. onMounted(){
  20. // 已经可以通过 ref 获取到 DOM
  21. }
  22. ```
  23. 4. onBeforeUpdate()
  24. ```
  25. // 数据更新前执行
  26. onBeforeUpdate(){
  27. // 可以在这里获取更新前的状态
  28. }
  29. ```
  30. 5. onUpdated()
  31. ```
  32. // 数据更新后执行(在组件的任意 DOM 更新后被调用)
  33. onUpdated(){
  34. // 组件重新渲染完成
  35. }
  36. ```
  37. 这个钩子会在组件的任意 DOM 更新后被调用,这些更新可能是由不同的状态变更导致的,因为多个状态变更可以在同一个渲染周期中批量执行(考虑到性能因素)。如果你需要在某个特定的状态更改后访问更新后的 DOM,请使用 nextTick() 作为替代。
  38. 不要在 updated 钩子中更改组件的状态,这可能会导致无限的更新循环!
  39. 6. onBeforeUnmount()
  40. ```
  41. // 组件销毁前执行
  42. onBeforeUnmount(){
  43. // 清除定时器、事件监听等
  44. }
  45. ```
  46. 这些钩子函数可以让我们在不同阶段执行自定义逻辑,非常实用。

Teleport

 https://cn.vuejs.org/guide/built-ins/teleport.html

可以将一个组件内部的一部分模板“传送”到该组件的 DOM 结构外层的位置去。

  1. <template>
  2. <div class="body"></div>
  3. <button @click="open = open^1">Open Modal</button>
  4. <Teleport to=".body">
  5. <div v-if="open" class="modal">
  6. <p>Hello from the modal!</p>
  7. <button @click="open = false">Close</button>
  8. </div>
  9. </Teleport>
  10. </template>
  11. <script setup>
  12. let open = ref(false);
  13. </script>
  14. <style scoped>
  15. .body {
  16. width: 200px;
  17. height: 200px;
  18. background-color: #0ee757;
  19. }
  20. .modal {
  21. background-color: #1789ca;
  22. }
  23. </style>

动态组件

组件基础 | Vue.js

有些场景会需要在两个组件间来回切换,比如 Tab 界面

  1. <template>
  2. <div class="home">
  3. <ul>
  4. <li v-for="(item, index) in tabList" :key="index" @click="change(index,item)">
  5. {{ item.name }}
  6. </li>
  7. </ul>
  8. <component :is="currentComponent.com"></component>
  9. </div>
  10. </template>
  11. <script setup>
  12. // import { reactive } from "vue";
  13. import A from "./components/A.vue";
  14. import B from "./components/B.vue";
  15. let tabList = reactive([
  16. { name: "显示组件A", com: markRaw(A) },
  17. { name: "显示组件B", com: markRaw(B) },
  18. ]);
  19. let currentComponent = reactive({
  20. com: A,
  21. });
  22. const change = (index,item) => {
  23. console.log('index',index,'item',item.name)
  24. currentComponent.com = tabList[index].com;
  25. };
  26. </script>
  27. <template><h1>A组件</h1></template>
  28. <template><h2>B组件</h2><button >按钮B</button></template>
  29.  这段代码实现了一个 tab 切换不同组件的功能。
  30. 主要逻辑是:
  31. 1. 定义了一个 tabList 数组,里面是不同的 tab 项,每个项包含 name 和 com 两个属性。
  32.    - name:tab 的名称
  33.    - com:对应的组件,使用 markRaw 包装过可以直接当组件使用
  34. 2. 定义一个当前组件 currentComponent,里面有一个 com 属性,默认设置为组件 A。
  35. 3. 使用 v-for 渲染 tab 列表,当点击不同的 tab 时,调用 change 方法。
  36. 4. change 方法中,根据点击的 tab 索引,设置 currentComponent.com 为对应的组件,这样就切换了当前的组件。
  37. 5. 使用 component 标签,动态绑定 currentComponent.com,所以就可以渲染不同的组件了。
  38. 这样通过 tab 切换,动态渲染不同的组件,实现了组件的动态切换。
  39. 举个例子,默认情况下渲染 A 组件,当我们点击“显示组件 B”这个 tab 时,就会切换到 B 组件。
  40. 所以这个例子实现了一个通过 tab 动态切换组件的功能。
  41. 在 `<component>` 组件中,`:is` 是用于指定动态组件的属性。
  42. `:is="currentComponent.com"` 表示动态绑定 currentComponent.com 中指定的组件,并渲染该组件。
  43. 一些关键点:
  44. - `<component>` 是 Vue 提供的一个内置组件,用于动态渲染组件
  45. - `:is` 特殊 attribute,可以用来指定要动态渲染的组件
  46. - 其值 `currentComponent.com` 需要是一个组件对象,这里通过引入并 `markRaw` 包装了组件满足要求
  47. - 当 `currentComponent.com` 更新时,`:is` 也会动态更新,实现组件切换
  48. 所以简单说,`:is` 指定了 `<component>` 需要渲染的目标组件。其动态性允许我们实现组件的动态切换。
  49. 这样通过 `currentComponent.com` 和 `:is` 的配合,我们就可以灵活地切换不同的组件了。

发送数据,接收数据更新网页

  1. <div class="container">
  2. <table>
  3. <thead>
  4. <tr>
  5. <th>标题</th>
  6. <th>描述</th>
  7. </tr>
  8. </thead>
  9. <tbody>
  10. <tr v-for="(item, index) in items" :key="index">
  11. <td>{{ item.title }}</td>
  12. <td>{{ item.description }}</td>
  13. </tr>
  14. </tbody>
  15. </table>
  16. </div>
  17. 这两个代码示例的主要区别在于使用了不同的 HTTP 客户端来获取数据:
  18. 1. 使用 axios 获取数据:
  19. ```
  20. const response = await axios.get('http://127.0.0.1:8000/api/data');
  21. items.value = response.data;
  22. ```
  23. axios 是一个流行的 HTTP 客户端,它会自动为我们转换响应数据到 JSON。所以我们可以直接通过 `response.data` 来访问 JSON 数据。
  24. 2. 使用原生 fetch 获取数据:
  25. ```
  26. const response = await fetch('http://127.0.0.1:8000/api/data');
  27. const data = await response.json();
  28. items.value = data;
  29. ```
  30. fetch 是浏览器原生提供的 API。它获取到的响应体是一个流数据,我们需要通过调用 `response.json()` 方法来自己把它转换成 JSON 对象。然后才能保存到 `data` 变量中,最后赋值给 `items.value`。
  31. 总结主要区别:
  32. - axios 会自动转换响应数据,fetch 需要手动转换
  33. - axios 是第三方库,fetch 是原生 API
  34. - 语法上 axios 更简单直观,fetch 需要额外转换步骤
  35. 所以对于大多数用例来说,使用 axios 会更加便捷。但 fetch 作为原生 API 也有其优势,比如体积更小,兼容性更好。

vue-element-admin介绍


vue-element-admin是一个后台前端解决方案,它基于vue和element-ui实现。它使用了最新的前端技术栈,内置了 i18 国际化解决方案,动态路由,权限验证,提炼了典型的业务模型,提供了丰富的功能组件,它可以帮助你快速搭建企业级中后台产品原型。

目录结构


一上来就看到那么多文件夹确实头疼,咱先不管别的,主要先了解标注部分文件

  1. ├── build # 构建相关
  2. ├── config # 配置相关
  3. ├── mock # 项目mock 模拟数据
  4. ├── plop-templates
  5. ├── public # 静态资源
  6. │ ├── favicon.ico
  7. │ └── index.html
  8. ├── src # 源代码
  9. │ ├── api # 所有请求
  10. │ ├── assets # 主题 字体等静态资源
  11. │ ├── components # 全局公用组件
  12. │ ├── directive # 全局指令
  13. │ ├── filters # 全局 filter
  14. │ ├── icons # 项目所有 svg icons
  15. │ ├── lang # 国际化 language
  16. │ ├── layout # 布局相关
  17. │ ├── mock # 项目mock 模拟数据
  18. │ ├── router # 路由
  19. │ ├── store # 全局 store管理
  20. │ ├── styles # 全局样式
  21. │ ├── utils # 全局公用方法
  22. │ ├── vendor # 公用vendor
  23. │ ├── views # view
  24. │ ├── App.vue # 入口页面
  25. │ ├── main.js # 入口 加载组件 初始化等
  26. │ └── permission.js # 权限管理
  27. ├── tests
  28. ├── static # 第三方不打包资源
  29. │ └── Tinymce # 富文本
  30. ├── .babelrc # babel-loader 配置
  31. ├── eslintrc.js # eslint 配置项
  32. ├── .gitignore # git 忽略项
  33. ├── favicon.ico # favicon图标
  34. ├── index.html # html模板
  35. ├── .env.xxx
  36. ├── .eslintrc.js
  37. ├── .travis.yml
  38. ├── vue.config.js # vue-cli 配置
  39. └── package.json


安装

  1. # 克隆项目
  2. git clone https://github.com/PanJiaChen/vue-element-admin.git
  3. # 进入项目目录
  4. cd vue-element-admin
  5. # 建议不要用 cnpm 安装 会有各种诡异的bug 可以通过如下操作解决 npm 下载速度慢的问题
  6. npm install --registry=https://registry.npm.taobao.org
  7. # 安装依赖
  8. npm install
  9. # 本地开发 启动项目
  10. npm run dev

跨域配置

新建一下request.js文件,修改如下

  1. import request from '@/utils/request_new'
  2. export function system_status() {
  3. return request({
  4. url: '/api/system_status',
  5. method: 'get'
  6. })
  7. }
  8. import axios from 'axios'
  9. import { MessageBox, Message } from 'element-ui'
  10. import store from '@/store'
  11. import { getToken } from '@/utils/auth'
  12. // create an axios instance
  13. const service = axios.create({
  14. baseURL:'http://127.0.0.1:8003', // url = base url + request url
  15. // withCredentials: true, // send cookies when cross-domain requests
  16. timeout: 5000, // request timeout
  17. })
  18. // request interceptor
  19. service.interceptors.request.use(
  20. config => {
  21. // do something before request is sent
  22. if (store.getters.token) {
  23. // let each request carry token
  24. // ['X-Token'] is a custom headers key
  25. // please modify it according to the actual situation
  26. config.headers['X-Token'] = getToken()
  27. }
  28. return config
  29. },
  30. error => {
  31. // do something with request error
  32. console.log(error) // for debug
  33. return Promise.reject(error)
  34. }
  35. )
  36. // response interceptor
  37. service.interceptors.response.use(
  38. /**
  39. * If you want to get http information such as headers or status
  40. * Please return response => response
  41. */
  42. /**
  43. * Determine the request status by custom code
  44. * Here is just an example
  45. * You can also judge the status by HTTP Status Code
  46. */
  47. response => {
  48. const res = response.data
  49. return res
  50. },
  51. error => {
  52. console.log('err' + error) // for debug
  53. Message({
  54. message: error.message,
  55. type: 'error',
  56. duration: 5 * 1000
  57. })
  58. return Promise.reject(error)
  59. }
  60. )
  61. export default service

参考资料:手摸手,带你用vue撸后台 系列一(基础篇) - 掘金

快速上手 | Vue.js

vue-element-admin 跨域配置 - CSDN文库

vue配置多个服务端请求地址(使用vue-admin-template举例说明)_vue-admin-template改变请求地址-CSDN博客

https://github.com/qingqingxuan/vue-admin-work/blob/master/vue-admin-work%E6%93%8D%E4%BD%9C%E6%96%87%E6%A1%A3.md

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读