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

Vue3快速入门学习

初始化项目

# 创建项目
npm create vite@latest my-vue-app -- --template vue
# 安装依赖
npm i
# 运行
npm run dev

模板语法

文本插值

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

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

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

原始 HTML

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

<template>
  {{ num }}
  <span>Message: {{ msg }}</span>
  <p>Using text interpolation: {{ html }}</p>
  <p>Using v-html directive: <span v-html="html"></span></p>
</template>
<script setup>
let num = 123
let msg = "(*´▽`)ノノ"
let html = '<span style="color: red">This should be red.</span>'
</script>

Attribute 绑定

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


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

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

简写

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


<div :id="dynamicId"></div>
​
  <input v-bind:value="dynamicId"><br>
  <input :value="dynamicId">

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

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

布尔型 Attribute

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

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


<button :disabled="isButtonDisabled">Button</button>
​
  <button :disabled="!isButtonDisabled">Button</button>

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

动态绑定多个值

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


const objectOfAttrs = {
  id: 'container',
  class: 'wrapper'
}
通过不带参数的 v-bind,你可以将它们绑定到单个元素上:



<div v-bind="objectOfAttrs"></div>
​
  <div v-bind="objectOfAttrs">
    233
  </div>

使用 JavaScript 表达式

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


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

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

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

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

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

仅支持表达式

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

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


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

调用函数

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


<time :title="toTitleDate(date)" :datetime="date">
  {{ formatDate(date) }}
</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:


<a v-bind:href="url"> ... </a>
​
<!-- 简写 -->
<a :href="url"> ... </a>
这里 href 就是一个参数,它告诉 v-bind 指令将表达式 url 的值绑定到元素的 href attribute 上。在简写中,参数前的一切 (例如 v-bind:) 都会被缩略为一个 : 字符。

另一个例子是 v-on 指令,它将监听 DOM 事件:


<a v-on:click="doSomething"> ... </a>
​
<!-- 简写 -->
<a @click="doSomething"> ... </a>

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

动态参数

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


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

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

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


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

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

动态参数值的限制

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

动态参数语法的限制

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

<!-- 这会触发一个编译器警告 -->
<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 的功能时,你将会看到其他修饰符的例子。

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

指令语法图

总结

<template>
  <h3>{{ text }}</h3>
  <h3>{{ user }}</h3>

  <!--
  指令 v-
        v-model   数据双向绑定
        v-if      判断表达式的值,true则显示,false则隐藏 -- 控制dom元素的创建和销毁,应避免频繁切换状态
        v-show    和v-if区别 --  始终会被渲染并保留在dom中,只是css被隐藏了 "display: none;"  一次性的
        v-for     循环
        v-bind    绑定属性或对象
        v-on      注册事件
  -->
  <input v-model="user.age" />

  <span v-if="user.age == 18">成年人:{{ user.age }}</span>
  <span v-else-if="user.age < 18">未成年</span>
  <span v-else>长大了...</span>
  <span v-show="user.age >= 18">
    和v-if区别:始终会被渲染并保留在dom中,只是css被隐藏了 "display: none;"
  </span>

  <!-- <button v-bind:disabled="true">v-bind</button> -->
  <button :disabled="true">v-bind</button>

  <h6
    v-for="(friends,index) in user.friends"
    :key="index"
    style="color: rgb(70, 238, 146)"
  >
    {{ friends.name }} - {{ friends.age }} - {{ index }}
  </h6>

  <!-- <button v-on:click="addFriend">添加好友</button> -->
  <button @click="addFriend">添加好友</button>
  &nbsp;&nbsp;&nbsp;

  <button @click="deleteFriend">删除</button>


  <p :class="{ active: isActive }">Class 与 Style 绑定</p>
  <div v-for="i in 5">{{i}}</div>
</template>

<script setup>
import { ref, reactive } from "vue";

const text = ref("HelloWorld");
const isActive = ref(true);

const user = reactive({
  name: "小郑",
  age: 18,
  friends: [
    {
      name: "小张",
      age: 18,
    },
    {
      name: "小李",
      age: 20,
    },
    {
      name: "张三",
      age: 201,
    },
  ],
});

function addFriend() {
  user.friends.push({
    name: "哈基米",
    age: 18,
  });
}

function deleteFriend() {
  user.friends.pop()
}
</script>

<style scoped>
.active {
  color: rgb(134, 17, 250);
}
</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组件

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

响应式API

<template>
  <h3>{{ count }}</h3>
  <h3>{{ data }}</h3>
  <hr />
  <h3>{{ getStr }}</h3>
  <h3>{{ getStr2 }}</h3>
  <input type="text" v-model="getStr2" />
  <hr />
  <h3>name:{{ name }}</h3>
  <button @click="changeData">修改</button>
</template>

<script setup>
import { ref, reactive, computed, watch, watchEffect, toRefs } from "vue";

// ref:定义基本数据类型的响应式数据
var count = ref(0);

// reactive:定义“数组/对象/map”复杂数据类型的深层响应式数据,shallowReactive:浅层响应式(只保留对这个对象顶层次访问的响应性)
const data = reactive({
  name: "小王",
  age: 18,
  girlfriends: [{ name: "小张" }],
});

// toRefs:解构响应式,没有的话,无法修改name值,修改的时候使用 name.value 修改
const { name } = toRefs(
  reactive({
    name: "小郑",
  })
);

// computed:计算属性
const str = ref("hello");
const getStr = computed(() => {
  console.log("getStr计算属性执行了...");
  return str.value;
});
// 如果要修改计算属性值,上面的方式会报错 Write operation failed: computed value is readonly
// 使用下面的方式
const getStr2 = computed({
  get() {
    console.log("getStr2计算属性执行了...");
    return str.value;
  },
  set(val) {
    str.value = val;
  },
});

// watch:监听器
watch(
  // count, // ref
  // () => geoObj.value.lng, // ref 对象中的某一个属性值
  data, // reactive
  // () => data.age, // reactive 对象中的某一个属性值
  // [count, data], // 监听多个数据
  // () => props.list, // 监听defineProps中的数据
  // () => proxy.$router.currentRoute.value, // 监听路由变化
  (newValue, oldValue) => {
    console.log("监听器执行了... ", newValue, oldValue);
  },
  {
    immediate: true, // 初始化执行一次
    // deep: true, // 深度监听 -- eg: 监听数组里面的数据变更
  }
);

// watchEffect:副作用函数,里面涉及到的属性有变更就会被触发执行
watchEffect(() => {
  console.log("watchEffect执行了... ", data.age);
});

function changeData() {
  count.value++;

  data.age++;
  data.girlfriends = [{ name: "小甜" }, { name: "小李" }];
  data.girlfriends.push({ name: "哈基米" });

  name.value = "小郑变了"+count.value;

  str.value += "1";
}
</script>


<style scoped>
.active {
  color: rgb(134, 17, 250);
}
</style>

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

比如有一个响应式对象:

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

如果我们解构它:

const { foo, bar } = state

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

但是如果我们使用toRefs:

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

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

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

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

比如:

const count = ref(1)

const plusOne = computed(() => count.value + 1)

console.log(plusOne.value) // 2

count.value++

console.log(plusOne.value) // 3

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

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

相同效果
import { ref, reactive, computed, watch, watchEffect, toRefs } from "vue";

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


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

解决ref大量引入问题

unplugin-auto-import插件

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

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

npm install -g cnpm --registry=https://registry.npm.taobao.org
cnpm i -D unplugin-auto-import

vite.config.js

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import AutoImport from "unplugin-auto-import/vite";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    AutoImport({
      imports: ["vue", "vue-router"],
    }),
  ],
});

$ref语法糖 告别 .value


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

vite.config.js

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [
    vue({
      reactivityTransform: true, // 启用响应式语法糖 $ref $computed $toRef ...
    })
  ]
})


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

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

cnpm i -D @vue-macros/reactivity-transform
vite.config.js

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ReactivityTransform from '@vue-macros/reactivity-transform/vite';

export default defineConfig({
  plugins: [
    vue(),
    ReactivityTransform(), // 启用响应式语法糖 $ref ...
  ]
})
解决ESLint警告: '$ref' is not defined.


.eslintrc.cjs

module.exports = {
  globals: { $ref: 'readonly', $computed: 'readonly', $shallowRef: 'readonly', $customRef: 'readonly', $toRef: 'readonly' },
};


二、测试
原本 .value 响应式

<template>
  <h1>{{ count }}</h1>
  <button @click="handleClick">click</button>
</template>

<script setup>
let count = ref(0);

function handleClick() {
  count.value++;
}
</script>
现在 $ref 去除 .value

<template>
  <h1>{{ count }}</h1>
  <button @click="handleClick">click</button>
</template>

<script setup>
let count = $ref(0);

function handleClick() {
  count++;
}
</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);
父组件 https://cn.vuejs.org/api/sfc-script-setup.html#defineprops-defineemits
<template>
  <h3>{{ count }}</h3>

  <p>子组件:</p>
  <hello :list=list2 @add="handleAdd"/>
</template>

<script setup>
import {ref} from "vue";
import hello from "./components/hello.vue";

var list2 = [{name: '小郑'}, {name: '张三'}]
const count = ref(0);

function handleAdd(data) {
  console.log(data);
  count.value++;
}
</script>

<style scoped></style>
子组件
<template>
  <h3>{{ list }}</h3>
  <button @click="handleSubmit">提交数据</button>
</template>

<script setup>
// 父传子
const props = defineProps({
  list: {
    type: Array,
    required: false,
    default: () => [],
  },
});

// 子传父
const emits = defineEmits(["add"]);

function handleSubmit() {
  emits("add", "child");
}

// 或直接通过 proxy.$emit("add", "child");
</script>

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

父组件
<template>
  <h3>父组件:{{ data }}</h3>
  <HelloWorld ref="helloRef" v-model="data" />
</template>

<script setup>
import HelloWorld from "./components/HelloWorld.vue";
const data = ref(0);
</script>

<style scoped></style>
子组件
<template>
  <h3>子组件:{{ modelValue }}</h3>
  <button @click="changeData">click</button>
</template>

<script setup>
const { proxy } = getCurrentInstance();
const props = defineProps({
  modelValue: {
    type: Number,
    required: false,
    default: () => 0,
  },
});
function changeData() {
  proxy.$emit("update:modelValue", 666);
}
</script>

<style scoped></style>
三、provide/inject:跨代传值
父组件
import { provide } from 'vue'
provide('msg', xxx)
子子组件
import { inject } from 'vue'
const msg = inject('msg')
const msg = inject('msg', 'hello') // 没值的话使用默认值hello

父组件调用子组件方法

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

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

父组件

```
<template>
  <HelloWorld ref="helloRef" />
  <button @click="handleClick">click</button>
  <br />
  <button @click="$refs.helloRef.changeData()">调用子组件方法</button>
</template>

<script setup>
const { proxy } = getCurrentInstance();
import HelloWorld from "./components/HelloWorld.vue";

function handleClick() {
  proxy.$refs.helloRef.changeData();
}
</script>

<style scoped></style>
```

子组件

```
<template>
  <h3>count:{{ count }}</h3>
</template>

<script setup>
const count = ref(0);

function changeData() {
  count.value++;
}

// 暴露方法
defineExpose({
  changeData,
});
</script>

<style scoped></style>
```

子组件调用父组件方法

### 父组件

```
<template>
  <HelloWorld @ok="handleOk" />
</template>
<script setup>
import { ref } from "vue";
import HelloWorld from "./components/HelloWorld.vue";

function handleOk(data) {
  console.log(data); 
}
</script>
<style scoped></style>
```

### 子组件

法一:

```
<script setup>
const { proxy } = getCurrentInstance();

function changeData() {
  proxy.$emit('ok', 'hello');
}
</script>
```

法二:

```
<script setup>
const { proxy } = getCurrentInstance();
const emits = defineEmits(["ok"]);

function changeData() {
  emits("ok", "hello");
}
</script>
```

插槽

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

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

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

具名插槽(Named Slots)

<slot name="left">

<slot name="right">

使用方法:

<template v-slot:left>...</template>
<template #right>...</template>
作用域插槽(Scoped Slots)

<slot :data="item">

可以将组件内的数据(item)传递给插槽。

使用方法:

<template #default="{ data }">...</template>
动态插槽(Dynamic Slots)

<slot name="dynamic">

插槽的名称可以动态绑定。

使用方法:

<template #\[xx\]>...</template>
这里的 xx 可以是一个变量。

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

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

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

组件内部使用 <slot> 标签作为插槽容器并定义名称

<div class="component">
  <slot name="header">默认内容</slot>
  
  <div class="content">...</div>
​
  <slot name="footer"></slot>
</div>
组件外部使用特殊的 template 语法插入自定义内容到插槽中

<MyComponent>
  <template v-slot:header>
    <!-- 自定义header -->
  </template>
  
  <template v-slot:footer>
   <!-- 自定义footer -->
  </template>
</MyComponent>
Vue 渲染组件时:

如果有插入内容,则替换 <slot> 对应位置为自定义内容

如果没有插入内容,则保留 <slot> 的默认内容

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

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

作用域插槽(Scoped Slots)

在组件中,可以为插槽传递数据:

<slot :user="user">
  {{ user.lastName }}
</slot>
组件使用方可以接收传递的数据并自定义插槽内容:

<MyComponent>
  <template #default="slotProps">
    {{ slotProps.user.firstName }}
  </template>  
</MyComponent>
这样就实现了从组件内部将数据传递给插槽。

动态插槽(Dynamic Slots)

插槽名可以是动态绑定的:

<slot :name="slotName">...</slot>
使用模板引用作为插槽名:

<MyComponent>
  <template #[slotName]>
    ...自定义内容
  </template>
</MyComponent>

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

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

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

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


父组件:
<template>
  <HelloWorld>
<!--    <template v-slot:left>具名插槽-left</template>-->
    <template v-slot:left></template>
    <template #right>具名插槽-right</template>
    <template #default="{ data }">作用域插槽:{{ data }}</template>
    <template #[xx]>动态插槽</template>
  </HelloWorld>
</template>
<script setup>
import HelloWorld from "./components/HelloWorld.vue";
let xx = ref("dynamic"); // 这里可以随时变 dynamic/left => xx
</script>
<style scoped></style>
​
子组件:
<template>
  <h3>hello</h3>
  <slot name="left"><h1>具名插槽-left</h1></slot>
  <br />
  <slot name="right"></slot>
  <div v-for="item in list" :key="item.id">
    <slot :data="item"></slot>
    <!-- 作用域插槽可回调值给父组件使用 <template #right-show="{ isShow }"></template> -->
     <!--  <slot name="right-show" :is-show="isShowRightMenu" /> -->
  </div>
  <slot name="dynamic"></slot>
</template>
<script setup>
let list = ref([
  { id: 1, name: "小张" },
  { id: 2, name: "小李" },
]);
</script>
<style scoped></style>

生命周期钩子

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

<template>
  <h3>hello</h3>

    <button id="count" @click="count++">{{ count }}</button>

</template>

<script setup>
console.log(111);

onMounted(() => {
  console.log(222);
});

const count = ref(0)

onUpdated(() => {
  // 文本内容应该与当前的 `count.value` 一致
  // console.log(document.getElementById('count').textContent)
  console.log(count.value)
})
</script>
<style scoped></style>
1. setup() 

```
// 组件初始化时执行,通常用于定义数据、方法等
setup(){
  // 代码逻辑
}
```

2. onBeforeMount()

```
// 在组件渲染到页面之前执行
onBeforeMount(){
  // 获取渲染前的 DOM 状态
}  
```

3. onMounted()

``` 
// 组件渲染 complet 后执行 
// 可以获取到 DOM 元素
onMounted(){
  // 已经可以通过 ref 获取到 DOM
}
```

4. onBeforeUpdate() 

```
// 数据更新前执行
onBeforeUpdate(){
  // 可以在这里获取更新前的状态  
}
```

5. onUpdated()

```
// 数据更新后执行(在组件的任意 DOM 更新后被调用)
onUpdated(){
  // 组件重新渲染完成
} 
```
这个钩子会在组件的任意 DOM 更新后被调用,这些更新可能是由不同的状态变更导致的,因为多个状态变更可以在同一个渲染周期中批量执行(考虑到性能因素)。如果你需要在某个特定的状态更改后访问更新后的 DOM,请使用 nextTick() 作为替代。
不要在 updated 钩子中更改组件的状态,这可能会导致无限的更新循环!



6. onBeforeUnmount()

``` 
// 组件销毁前执行
onBeforeUnmount(){
  // 清除定时器、事件监听等
}
```

这些钩子函数可以让我们在不同阶段执行自定义逻辑,非常实用。

Teleport

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

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

<template>
  <div class="body"></div>

  <button @click="open = open^1">Open Modal</button>

  <Teleport to=".body">
    <div v-if="open" class="modal">
      <p>Hello from the modal!</p>
      <button @click="open = false">Close</button>
    </div>
  </Teleport>
</template>
<script setup>
let open = ref(false);
</script>
<style scoped>
.body {
  width: 200px;
  height: 200px;
  background-color: #0ee757;
}

.modal {
  background-color: #1789ca;
}
</style>

动态组件

组件基础 | Vue.js

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

<template>
  <div class="home">
    <ul>
      <li v-for="(item, index) in tabList" :key="index" @click="change(index,item)">
        {{ item.name }}
      </li>
    </ul>
    <component :is="currentComponent.com"></component>
  </div>
</template>

<script setup>
// import { reactive } from "vue";
import A from "./components/A.vue";
import B from "./components/B.vue";
let tabList = reactive([
  { name: "显示组件A", com: markRaw(A) },
  { name: "显示组件B", com: markRaw(B) },
]);

let currentComponent = reactive({
  com: A,
});

const change = (index,item) => {
  console.log('index',index,'item',item.name)
  currentComponent.com = tabList[index].com;
};
</script>

<template><h1>A组件</h1></template>

<template><h2>B组件</h2><button >按钮B</button></template>

 这段代码实现了一个 tab 切换不同组件的功能。

主要逻辑是:

1. 定义了一个 tabList 数组,里面是不同的 tab 项,每个项包含 name 和 com 两个属性。
   - name:tab 的名称
   - com:对应的组件,使用 markRaw 包装过可以直接当组件使用
2. 定义一个当前组件 currentComponent,里面有一个 com 属性,默认设置为组件 A。
3. 使用 v-for 渲染 tab 列表,当点击不同的 tab 时,调用 change 方法。
4. change 方法中,根据点击的 tab 索引,设置 currentComponent.com 为对应的组件,这样就切换了当前的组件。
5. 使用 component 标签,动态绑定 currentComponent.com,所以就可以渲染不同的组件了。
这样通过 tab 切换,动态渲染不同的组件,实现了组件的动态切换。
举个例子,默认情况下渲染 A 组件,当我们点击“显示组件 B”这个 tab 时,就会切换到 B 组件。



所以这个例子实现了一个通过 tab 动态切换组件的功能。
 在 `<component>` 组件中,`:is` 是用于指定动态组件的属性。
`:is="currentComponent.com"` 表示动态绑定 currentComponent.com 中指定的组件,并渲染该组件。

一些关键点:

- `<component>` 是 Vue 提供的一个内置组件,用于动态渲染组件
- `:is` 特殊 attribute,可以用来指定要动态渲染的组件
- 其值 `currentComponent.com` 需要是一个组件对象,这里通过引入并 `markRaw` 包装了组件满足要求
- 当 `currentComponent.com` 更新时,`:is` 也会动态更新,实现组件切换

所以简单说,`:is` 指定了 `<component>` 需要渲染的目标组件。其动态性允许我们实现组件的动态切换。

这样通过 `currentComponent.com` 和 `:is` 的配合,我们就可以灵活地切换不同的组件了。

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

  <div class="container">
    <table>
      <thead>
      <tr>
        <th>标题</th>
        <th>描述</th>
      </tr>
      </thead>
      <tbody>
      <tr v-for="(item, index) in items" :key="index">
        <td>{{ item.title }}</td>
        <td>{{ item.description }}</td>
      </tr>
      </tbody>
    </table>
  </div>
这两个代码示例的主要区别在于使用了不同的 HTTP 客户端来获取数据:

1. 使用 axios 获取数据:

```
const response = await axios.get('http://127.0.0.1:8000/api/data');
items.value = response.data;
```

axios 是一个流行的 HTTP 客户端,它会自动为我们转换响应数据到 JSON。所以我们可以直接通过 `response.data` 来访问 JSON 数据。

2. 使用原生 fetch 获取数据:

```
const response = await fetch('http://127.0.0.1:8000/api/data');
const data = await response.json(); 
items.value = data;
```

fetch 是浏览器原生提供的 API。它获取到的响应体是一个流数据,我们需要通过调用 `response.json()` 方法来自己把它转换成 JSON 对象。然后才能保存到 `data` 变量中,最后赋值给 `items.value`。

总结主要区别:

- axios 会自动转换响应数据,fetch 需要手动转换
- axios 是第三方库,fetch 是原生 API
- 语法上 axios 更简单直观,fetch 需要额外转换步骤

所以对于大多数用例来说,使用 axios 会更加便捷。但 fetch 作为原生 API 也有其优势,比如体积更小,兼容性更好。

vue-element-admin介绍


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

目录结构


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

├── build            # 构建相关
├── config           # 配置相关
├── mock             # 项目mock 模拟数据
├── plop-templates       
├── public           # 静态资源
│   ├── favicon.ico       
│   └── index.html       
├── src              # 源代码
│   ├── api          # 所有请求
│   ├── assets       # 主题 字体等静态资源
│   ├── components   # 全局公用组件
│   ├── directive    # 全局指令
│   ├── filters      # 全局 filter
│   ├── icons        # 项目所有 svg icons
│   ├── lang         # 国际化 language
│   ├── layout       # 布局相关
│   ├── mock         # 项目mock 模拟数据
│   ├── router       # 路由
│   ├── store        # 全局 store管理
│   ├── styles       # 全局样式
│   ├── utils        # 全局公用方法
│   ├── vendor       # 公用vendor
│   ├── views        # view
│   ├── App.vue      # 入口页面
│   ├── main.js      # 入口 加载组件 初始化等
│   └── permission.js # 权限管理
├── tests            
├── static           # 第三方不打包资源
│   └── Tinymce      # 富文本
├── .babelrc          # babel-loader 配置
├── eslintrc.js       # eslint 配置项
├── .gitignore        # git 忽略项
├── favicon.ico       # favicon图标
├── index.html        # html模板
├── .env.xxx          
├── .eslintrc.js        
├── .travis.yml         
├── vue.config.js      # vue-cli 配置
└── package.json


安装

# 克隆项目
git clone https://github.com/PanJiaChen/vue-element-admin.git

# 进入项目目录
cd vue-element-admin

# 建议不要用 cnpm 安装 会有各种诡异的bug 可以通过如下操作解决 npm 下载速度慢的问题
npm install --registry=https://registry.npm.taobao.org

# 安装依赖
npm install

# 本地开发 启动项目
npm run dev

跨域配置

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

import request from '@/utils/request_new'

export function system_status() {
    return request({
        url: '/api/system_status',
        method: 'get'
    })
}



import axios from 'axios'
import { MessageBox, Message } from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'


// create an axios instance
const service = axios.create({
  baseURL:'http://127.0.0.1:8003', // url = base url + request url
  // withCredentials: true, // send cookies when cross-domain requests
  timeout: 5000, // request timeout

})

// request interceptor
service.interceptors.request.use(
  config => {
    // do something before request is sent

    if (store.getters.token) {
      // let each request carry token
      // ['X-Token'] is a custom headers key
      // please modify it according to the actual situation
      config.headers['X-Token'] = getToken()
    }
    return config
  },
  error => {
    // do something with request error
    console.log(error) // for debug
    return Promise.reject(error)
  }
)

// response interceptor
service.interceptors.response.use(
  /**
   * If you want to get http information such as headers or status
   * Please return  response => response
  */

  /**
   * Determine the request status by custom code
   * Here is just an example
   * You can also judge the status by HTTP Status Code
   */
  response => {
    const res = response.data

    return res

  },
  error => {
    console.log('err' + error) // for debug
    Message({
      message: error.message,
      type: 'error',
      duration: 5 * 1000
    })
    return Promise.reject(error)
  }
)

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

  • 21
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值