Vue3
1、新建第一个项目
#第一种方法
#语法 npm init vite-app 项目名称
#此方法需要自定义很多配置
npm init vite-app firstdemo
#第二种方法
#npm init vite@版本号 项目名称 --template vue
npm init vite@latest yang72 --template vue
cd firstdemo
#进入目录后一定要执行npm install 或者 yarn
#否则会报错'vite' 不是内部或外部命令,也不是可运行的程序或批处理文件。
yarn install
yanr dev
项目结构
vite项目的运行流程
在工程化的项目中, vue 要做的事情很单纯:通过 main.js 把 App.vue 渲染到 index.html 的指定区域中。
其中:
① App.vue 用来编写待渲染的 模板结构
② index.html 中需要预留一个 el 区域
③ main.js 把 App.vue 渲染到了 index.html 所预留的区域
改写App.vue中的内容
将如下内容改写,并删除components目录下的HelloWorld.vue文件
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<HelloWorld msg="Hello Vue 3.0 + Vite" />
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'App',
components: {
HelloWorld
}
}
</script>
改写为
<template>
<h1>
HelloWorld!
</h1>
</template>
查看index.html文件确认是否留下el区域
<!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>
<script type="module" src="/src/main.js"></script>
</body>
</html>
安装vue3的语法在main.js中渲染
//导入vue的创建模块
import { createApp } from 'vue'
//导入模板页
import App from './App.vue'
//导入样式,一般都会删除掉系统样式
import './index.css'
//挂载index.html中id为app的节点
createApp(App).mount('#app')
2、vue组件成结构
每个 .vue 组件都由 3 部分构成,别是:
templatetemplate-> 组件的 模板结构
scriptscript-> 组件的 JavaScript 行为
stylestyle-> 组件的 样式
其中, 每个组件中必须包含 template 模板结构 ,而 script 行为 和 style 样式 是可选的组成部分。
组件的 template 节点
vue规定:每个组件对应的 模板结构 ,需要定义到 节点 中。
<template>
<!--要被dom渲染的内容,必须在template内容-->
</template>
注意: 是 vue 提供的 容器标签 ,只起到 包裹性质的作用 ,它不会被渲染为真正的 DOM 元素中。
vue2仅支持单个根节点(超过一个跟节点会报错),vue3支持多个跟节点
vue2
<template>
<!--要被dom渲染的内容,必须在template内容-->
<div>
<div>
<p></p>
</div>
<div></div>
<p></p>
<h1><span></span></h1>
<span></span>
</div>
</template>
vue3
<template>
<!--要被dom渲染的内容,必须在template内容-->
<div>
<p></p>
</div>
<p></p>
<h1><span></span></h1>
<span></span>
</template>
组件的 script 节点
vue规定:组件内的
节点基本结构如下:
<script>
import {
ref
} from "vue"
//组件相关的data数据、methods方法等
//都要包裹在export default所导出的对象中
export default {
//name属性的名称指向当前页面的名称,建议名称使用驼峰法
name: "App",
//组件的数据
setup() {
const a1 = ref("a");
const person = ref([{
name: "李四",
sex: "男",
age: 18
}, {
name: "李四",
sex: "男",
age: 18
}]);
const list = ref({
name: "李四",
sex: "男",
age: 18
});
return {
a1,
person,
list
}
},
//组件的方法
methods: {
HelloWord() {
return "HelloWorld";
}
},
}
</script>
组件的 style 节点
vue规定:组件内的
基本结构如下:
<style lang="css" scoped>
a {
text-decoration: none;
color: #000000;
}
</style>
其中
使用less语法需要安装依赖包
yarn add less
3、 组件封装复用
下载bootstrap5.2.3生产包放在public下的中,npm或者yarn包引入看官方文档,这里使用暴力引入
<link href="/public/js/bootstrap-5.2.3/css/bootstrap.min.css" rel="stylesheet">
<script type="javascript" src="/public/js/bootstrap-5.2.3/js/bootstrap.bundle.min.js"></script>
组件封装
BaseFooter.vue
<template>
<header>
<nav class="navbar navbar-expand-md navbar-dark bg-dark">
<div class="container">
<img class="rounded-circle" src="logo" alt="logo" style="width: 45px; height: 45px" />
<a class="navbar-brand" href="#/index">wwwwwww</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#collapsibleNavbar">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="collapsibleNavbar">
<ul class="navbar-nav">
<li class="nav-item active">
<a class="nav-link" href="#/notes">常用笔记</a
>
</li>
<li class="nav-item active">
<a class="nav-link" href="#/labels"
>资源链接</a
>
</li>
<li class="nav-item active">
<a class="nav-link" href="#/videos"
>视频作品</a
>
</li>
<li class="nav-item active">
<a class="nav-link" href="#/photos"
>图片作品</a
>
</li>
<li class="nav-item active">
<a class="nav-link" href="#/tools"
>工具箱</a
>
</li>
<li class="nav-item active">
<a class="nav-link" href="#/musics"
>我的音乐</a
>
</li>
<li class="nav-item active">
<a class="nav-link" href="https://www.baidu.com/" target="_blank"
>我的博客</a
>
</li>
<li class="nav-item active">
<a class="nav-link" href="#/abouts"
>本尊</a
>
</li></ul>
</div>
</div>
</nav>
</header>
</template>
BaseHeader.vue
<template>
<footer class="footer container text-justify">
<p>
<strong
>Copyright © 2020-2023
<a :href="webinfo.web_domain" target="_blank">{{ webinfo.web_name }}版权所有</a
></strong
>
All rights reserved.
<span v-show="webinfo.web_nis != null">
<a :href="webinfo.web_nis_url" target="_blank">
{{ webinfo.web_nis }}
</a>
</span>
<span v-show="webinfo.web_nis != null">
<a
v-show="webinfo.web_icp != null"
:href="webinfo.web_icp_url"
target="_blank"
>{{ webinfo.web_icp }}</a
></span>
</p>
<p v-show="webinfo.web_email != null">
<span class="text-dark">邮编:{{ webinfo.web_post }}</span>
<span class="text-dark">联系地址:{{ webinfo.web_address }}</span>
<span class="text-dark">联系邮箱:{{ webinfo.web_email }}</span>
<span class="text-dark">联系地址:{{ webinfo.web_phone }}</span>
</p>
<p v-if="webinfo.web_expand_script !== null"></p>
<p>
{{ webinfo.web_responsibility }}
</p>
</footer>
</template>
<script>
import {
ref
} from 'vue'
export default {
name: "Footers",
setup() {
const webinfo = ref({
web_name: "wwwwwww",
web_icp: "XICP备202200354号-1",
web_domain: "http://www.baidu.com",
web_icp_url: "http://beian.miit.gov.cn/",
web_nis: "x公网安备20230316001001号",
web_nis_url: "https://www.beian.gov.cn/portal/registerSystemInfo?recordcode=12345678910",
web_technical_support: "wwwww",
web_organizer: "个人",
web_post: "666666",
web_address: "地球国地球省地球市地球县",
web_email: "123456789@qq.com",
web_phone: "12345678910",
web_expand_script: null,
web_introduction: null,
web_responsibility: "免责声明:wwwwww的网站所发布的一切软件资源均来自于互联网仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则一切后果请用户自负;本站所有软件信息来自网络,版权争议与本站无关;您必须在下载后的24个小时之内从您的电脑中彻底删除上述内容如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务;本站为非盈利性站点,网站会员注册费用是您喜欢本站而产生的捐赠赞助支持行为,仅为维持服务器的开支与维护,全凭自愿无任何强求;本站为个人博客,所有资源仅供学习参考,并不贩卖软件不存在任何商业目的及用途,如果您访问和下载此文件,表示您同意只将此文件用于参考、学习而非其他用途如有侵权请邮件与我们联系处理,我们会及时处理",
});
return {
webinfo
};
},
};
</script>
全局注册,在main.js中注册
import { createApp } from 'vue'
import App from './App.vue'
/*引入页面组件*/
import BaseFooterVue from "./components/BaseFooter.vue";
import BaseHeaderVue from "./components/BaseHeader.vue";
/*注册bootstrapjs*/
/*createApp(App)mount('#app')*/
/*又或者这样注册*/
const app=createApp(App);
//这样注册,推荐这样注册,更实用
app.components("BaseFooterVue",BaseFooterVue);
app.components("BaseHeaderVue",BaseHeaderVue);
//或者这样注册
//app.components("base-footer-vue",BaseFooterVue);
//app.components("base-header-vue",BaseHeaderVue);
//再或者这样注册
//这里的name是页面export default里面的name属性
/*export default {
name: "BaseHeader",
};
app.components(BaseHeaderVue.name,BaseHeaderVue);//相当于app.components("BaseHeader",BaseHeaderVue)
*/
app.mount('#app');
使用
<template>
<base-header-vue></base-header-vue>
<div class="container text-justify">
<h1>HelloWord</h1>
<!--有序列表-->
<ol type="1">
<li>君不见,黄河之水天上来,奔流到海不复回。</li>
<li>君不见,高堂明镜悲白发,朝如青丝暮成雪。</li>
<li>人生得意须尽欢,莫使金樽空对月。</li>
<li>天生我材必有用,千金散尽还复来。</li>
<li>烹羊宰牛且为乐,会须一饮三百杯。</li>
<li>岑夫子,丹丘生,将进酒,杯莫停。</li>
<li>与君歌一曲,请君为我倾耳听。</li>
<li>钟鼓馔玉不足贵,但愿长醉不愿醒。</li>
<li>古来圣贤皆寂寞,惟有饮者留其名。</li>
<li>陈王昔时宴平乐,斗酒十千恣欢谑。</li>
<li>主人何为言少钱,径须沽取对君酌。</li>
<li>五花马,千金裘,呼儿将出换美酒,与尔同销万古愁。</li>
</ol>
<!--或者是-->
<!--<router-view />-->
</div>
<base-footer-vue></base-footer-vue>
</template>
局部注册,在需要的页面中引入
<template>
<base-header-vue></base-header-vue>
<div class="container text-justify">
<h1>HelloWord</h1>
<!--有序列表-->
<ol type="1">
<li>君不见,黄河之水天上来,奔流到海不复回。</li>
<li>君不见,高堂明镜悲白发,朝如青丝暮成雪。</li>
<li>人生得意须尽欢,莫使金樽空对月。</li>
<li>天生我材必有用,千金散尽还复来。</li>
<li>烹羊宰牛且为乐,会须一饮三百杯。</li>
<li>岑夫子,丹丘生,将进酒,杯莫停。</li>
<li>与君歌一曲,请君为我倾耳听。</li>
<li>钟鼓馔玉不足贵,但愿长醉不愿醒。</li>
<li>古来圣贤皆寂寞,惟有饮者留其名。</li>
<li>陈王昔时宴平乐,斗酒十千恣欢谑。</li>
<li>主人何为言少钱,径须沽取对君酌。</li>
<li>五花马,千金裘,呼儿将出换美酒,与尔同销万古愁。</li>
</ol>
<!--或者是-->
<!--<router-view />-->
</div>
<base-footer-vue></base-footer-vue>
</template>
<script>
import BaseFooterVue from "./components/BaseFooter.vue";
import BaseHeaderVue from "./components/BaseHeader.vue";
export default {
name: "App",
components: {
BaseFooterVue,
BaseHeaderVue
}
};
</script>
全局注册和部的区别
被全局 注册的组件, 可以在全局任何一个组件内使用
被局部 注册的组件, 只能在当前注册的范围内使用
应用场景:
如果某些组件在开发期间的使用频率很高 ,推荐进行 全局 注册;如果某些组件 只在特定的情况下会被用到 ,推荐进行 局部 注册。
组件注册时名称的大小写
在进行组件的注册时, 定义组件注册名称的 方式 有两种:
① 使用 kebabkebab-case 命名法(俗称 短横线命名法 ,例如 my -swiper 和 my -searchsearch)
② 使用 PascalCase 命名法(俗称 帕斯卡命名法 或大驼峰命名法 ,例如 MySwiper 和 MySearchMySearch)
短横线命名法的特点:必须严格按照短横线名称进行使用
帕斯卡命名法的特点:既可以严格按照帕斯卡名称进行使用,又可以 转化为短横线名称 进行使用
注意:在实际开发中, 推荐使用帕斯卡命名法 为组件注册名称 ,因为它的 适用性更强 。
通过 name 属性注册组件
在注册组件期间,除了可以直接提供组件的注册名称 之外,还可以 把组件的 name 属性 作为注册后 组件的名称
4、组件的props
props是组件的 自定义属性 ,组件的 使用者 可以通过 props 把数据传递到子组件内部 ,供子组件内部进行使用。
props的作用 :父组件通过 props 向子 组件传递要展示的数据 。
props的好处 :提高了组件的 复用性 。
如果父组件 给子传递了未声明的 props 属性 ,则这些属性会被忽略,无法被子组件使用。
可以使用 v-bind 属性绑定 的形式,为组件动态绑定 props的值。
组件中如果使用“ camelCasecamelCase(驼峰命名法 )”声明了 props 属性的名称,则有两种方式为其绑定属性的值。
props数据组件封装
<template>
<footer class="footer container border-top">
<p class="text-center">
<strong
>Copyright © {{webBuild}}-{{new Date().getFullYear()}}
<a :href="web_domain" target="_blank">{{ webName }}版权所有</a
></strong
>
All rights reserved.
<span v-show="web_nis != null">
<img :src="nis" style="width:20px;height:20px;" />
<a :href="'https://www.beian.gov.cn/portal/registerSystemInfo?recordcode='+web_nis.replace(/[^\d]/g, '')" target="_blank">
{{ web_nis }}
</a>
</span>
<span v-show="web_nis != null">
<img :src="icp" style="width:20px;height:20px;" />
<a
v-show="web_icp != null"
href="http://beian.miit.gov.cn/"
target="_blank"
>{{ web_icp }}</a
></span>
</p>
<p v-show="web_email != null" class="text-center jianju">
<span class="text-dark">邮编:{{ web_post }}</span>
<span class="text-dark">联系地址:{{ web_address }}</span>
<span class="text-dark">联系邮箱:{{ web_email }}</span>
<span class="text-dark">联系地址:{{ web_phone }}</span>
<span class="text-dark">技术支持:{{ web_technical_support }}技术支持</span>
</p>
<p v-if="web_expand_script !== null"></p>
<p class="text-justify">
免责声明:wwwwww的网站所发布的一切软件资源均来自于互联网仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则一切后果请用户自负;本站所有软件信息来自网络,版权争议与本站无关;您必须在下载后的24个小时之内从您的电脑中彻底删除上述内容如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务;本站为非盈利性站点,网站会员注册费用是您喜欢本站而产生的捐赠赞助支持行为,仅为维持服务器的开支与维护,全凭自愿无任何强求;本站为个人博客,所有资源仅供学习参考,并不贩卖软件不存在任何商业目的及用途,如果您访问和下载此文件,表示您同意只将此文件用于参考、学习而非其他用途如有侵权请邮件与我们联系处理,我们会及时处理
</p>
</footer>
</template>
<script>
import {
ref
} from "vue";
export default {
name: "Footers",
props: ['webName', 'webBuild',
'web_icp', 'web_domain',
'web_nis', 'web_technical_support',
'web_organizer', 'web_post',
'web_address', 'web_email',
'web_phone', 'web_expand_script',
'web_introduction'
],
setup() {
const nis = ref("/img/beian.png");
const icp = ref("/img/icp.png");
return {
nis,
icp,
};
},
};
</script>
<style lang="css" scoped>
a {
text-decoration: none;
color: #000000;
}
p {
margin: 2px 0
}
</style>
组件使用props
<base-footer-vue
webName="我是你大爷"
web-build="2018"
:web_domain="'https://www.baidu.com'"
web_icp="XICP备202200354号-1"
web_nis="x公网安备20230316001001号"
web_technical_support="我是你大爷"
web_organizer="个人"
web_post="666666"
web_address="地球国地球省地球市地球县"
web_email="123456789@qq.com"
web_phone="12345678910"
web_expand_script=""
web_introduction=""
>
</base-footer-vue>
props数据验证
定义:在封装组件时 对外界传递过来的 props 数据 进行合法性的校验,从而防止数据不合法的问题。
使用数组类型 的 props 节点的缺:无法为每个 prop 指定具体数据类型。
对象类型的 props 节点 提供了多种数据验证方案,例如:① 基础的类型检查;② 多个可能的类型;③ 必填项校验;④ 属性默认值;⑤自定义函数验证。
可以直接为组件的 prop 属性指定 基础的校验类型 ,从而防止 组件的使用者为其绑定错误类型的数据。
如果某个 prop 属性值的 类型不唯一 ,此时可以通过数组的形式,为其指定多个可能的类型。
如果组件的某个 prop 属性是必填项 ,必须让组件的使用者为其传递属性的值。
在封装组件时,可以为某个prop 属性 指定默认值。
在封装组件时,可以为 prop 属性指定 自定义的验证函数 ,从而 对 prop 属性的值进行更加精确的控制 。
Vue3语法糖(steup)中父子组件传真使用props、emits
父组件
<template>
<FormTemplate :FormFields="ListData.formFields" :FromRules="ListData.formRules"
:formComponents="ListData.formComponents" :dialogVisible="ListData.dialogVisible" :title="'添加'"
@dialog-visible="dialogVisibleClick" @table-add="tableAdd"></FormTemplate>
<TableTemplate :total="ListData.total" :pageSizeArr="ListData.pageSizeArr" :TableColumnFields="ListData.columnData"
:TableFieldsData="ListData.tableData" @dialog-visible="dialogVisibleClick" @table-edit="tableEdits"
@table-del="tableDel"></TableTemplate>
</template>
<script setup lang="js">
import { reactive } from 'vue'
import FormTemplate from "../components/FormTemplate.vue"
import TableTemplate from "../components/TableTemplate.vue"
//接收组件返回的数据(控制弹出页面显示)
const dialogVisibleClick = (val) => {
ListData.dialogVisible = val;
}
//表格数据编辑
const tableEdits = (val) => {
ListData.dialogVisible = val[0];
ListData.formFields = val[1];
}
//表格数据添加
const tableAdd = (val) => {
ListData.dialogVisible = val[0];
ListData.tableData.push(val[1]);
}
//表格数删除
const tableDel = (val) => {
val.forEach(x => {
ListData.tableData = ListData.tableData.filter(c => c.No !== x);
});
}
//校验身份证号是否为空
const checkIdCard = (rule, value, callback) => {
if (!value) {
return callback(new Error('身份证号不允许为空'))
}
else if (!(RegExp(/\d{17}[\d|x]|\d{15}/).test(value))) {
callback(new Error('请输入合法身份证号'))
}
//哪怕无错误也要返回callback
//否则表单提交验证的时候成功就没有返回值
return callback()
}
//验证邮箱
const checkEmail = (rule, value, callback) => {
if (!value) {
return callback(new Error('邮箱不允许为空'))
}
else if (!(RegExp(/\w[-\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\.)+[A-Za-z]{2,14}/).test(value))) {
callback(new Error('请输入合法的邮箱'))
}
//哪怕无错误也要返回callback
//否则表单提交验证的时候成功就没有返回值
return callback()
}
const ListData = reactive({
//页面数据
total: 100,
pageSizeArr: [10, 20, 50, 100],
pageSize: 20,
qeuryName: '',
//弹出框
dialogVisible: false,
title: '添加',
//表格
columnData: [
{ type: "selection", prop: "", label: "编号", width: "55", sort: false },
{ type: "", prop: "No", label: "编号", width: "50", sort: false },
{ type: "", prop: "Name", label: "姓名", width: "80", sort: false },
{ type: "", prop: "Gender", label: "性别", width: "50", sort: false },
{ type: "", prop: "Nation", label: "民族", width: "80", sort: false },
{ type: "", prop: "Political", label: "政治面貌", width: "100", sort: false },
{ type: "", prop: "IdCard", label: "身份证号", width: "150", sort: false },
{ type: "", prop: "Address", label: "联系地址", width: "200", sort: false },
{ type: "", prop: "NativePlace", label: "籍贯", width: "150", sort: false },
{ type: "", prop: "AccountType", label: "户口类型", width: "80", sort: false },
{ type: "", prop: "Birthday", label: "出生日期", width: "100", sort: false },
{ type: "", prop: "Phone", label: "联系电话", width: "100", sort: false },
{ type: "", prop: "QQ", label: "QQ", width: "100", sort: false },
{ type: "", prop: "Email", label: "邮箱", width: "150", sort: false },
{ type: "", prop: "MaritalStatus", label: "婚姻状况", width: "80", sort: false },
{ type: "", prop: "GraduateSchool", label: "毕业院校", width: "150", sort: false },
{ type: "", prop: "Major", label: "专业", width: "150", sort: false },
{ type: "", prop: "Education", label: "学历", width: "50", sort: false }
],
tableData: [{
"No": 1,
"Name": "单于正豪",
"Gender": "男",
"Nation": "布依族",
"Political": "中共预备党员",
"IdCard": "150122198209171033",
"Address": "内蒙古自治区呼和浩特市托克托县",
"NativePlace": "内蒙古自治区 托克托县",
"AccountType": "城镇",
"Birthday": "2017-01-09T00:00:00",
"Phone": "18105510741",
"QQ": "6708666222",
"Email": "6708666222@qq.com",
"MaritalStatus": "已婚",
"GraduateSchool": "北京信息科技大学",
"Major": "机电工程",
"Education": "本科"
}, {
"No": 2,
"Name": "长孙明杰",
"Gender": "男",
"Nation": "鄂温克族",
"Political": "民盟盟员",
"IdCard": "632625198502160134",
"Address": "青海省果洛藏族自治州久治县",
"NativePlace": "青海省 久治县",
"AccountType": "城镇",
"Birthday": "2016-01-02T00:00:00",
"Phone": "13703693149",
"QQ": "6708666223",
"Email": "6708666223@qq.com",
"MaritalStatus": "未婚",
"GraduateSchool": "南开大学",
"Major": "工程管理",
"Education": "硕士"
}, {
"No": 3,
"Name": "端木弘文",
"Gender": "男",
"Nation": "维吾尔族",
"Political": "民革党员",
"IdCard": "620823197206130558",
"Address": "甘肃省平凉市崇信县",
"NativePlace": "甘肃省 崇信县",
"AccountType": "城镇",
"Birthday": "2013-01-06T00:00:00",
"Phone": "13706284193",
"QQ": "6708666224",
"Email": "6708666224@qq.com",
"MaritalStatus": "已婚",
"GraduateSchool": "北京交通大学",
"Major": "营销",
"Education": "本科"
}, {
"No": 4,
"Name": "拓拔凌文",
"Gender": "女",
"Nation": "纳西族",
"Political": "农工党党员",
"IdCard": "513436198809131609",
"Address": "四川省凉山彝族自治州美姑县",
"NativePlace": "四川省 美姑县",
"AccountType": "城镇",
"Birthday": "2013-01-09T00:00:00",
"Phone": "18204125140",
"QQ": "6708666225",
"Email": "18204125140@139.com",
"MaritalStatus": "已婚",
"GraduateSchool": "北京大学医学部",
"Major": "物流工程",
"Education": "硕士"
}, {
"No": 5,
"Name": "尉迟子轩",
"Gender": "男",
"Nation": "畲族",
"Political": "中共预备党员",
"IdCard": "230713198206280773",
"Address": "黑龙江省伊春市带岭区",
"NativePlace": "黑龙江省 带岭区",
"AccountType": "城镇",
"Birthday": "2028-01-06T00:00:00",
"Phone": "17704145721",
"QQ": "6708666226",
"Email": "6708666226@qq.com",
"MaritalStatus": "已婚",
"GraduateSchool": "北京航空航天大学",
"Major": "给水排水科学与工程",
"Education": "博士"
}, {
"No": 6,
"Name": "澹台天宇",
"Gender": "男",
"Nation": "土族",
"Political": "共青团员",
"IdCard": "370682199706211497",
"Address": "山东省烟台市莱阳市",
"NativePlace": "山东省 莱阳市",
"AccountType": "城镇",
"Birthday": "2021-01-06T00:00:00",
"Phone": "18302735359",
"QQ": "6708666227",
"Email": "6708666227@qq.com",
"MaritalStatus": "已婚",
"GraduateSchool": "战略支援部队航天工程大学",
"Major": "财务管理",
"Education": "硕士"
}, {
"No": 7,
"Name": "公良思松",
"Gender": "女",
"Nation": "仡佬族",
"Political": "中共预备党员",
"IdCard": "430922201008121122",
"Address": "湖南省益阳市桃江县",
"NativePlace": "湖南省 桃江县",
"AccountType": "城镇",
"Birthday": "2012-01-08T00:00:00",
"Phone": "15908822558",
"QQ": "6708666228",
"Email": "15908822558@139.com",
"MaritalStatus": "已婚",
"GraduateSchool": "北京建筑大学",
"Major": "汽车服务工程",
"Education": "本科"
}, {
"No": 8,
"Name": "督安柏",
"Gender": "女",
"Nation": "京族",
"Political": "致公党党员",
"IdCard": "230712198403190302",
"Address": "黑龙江省伊春市汤旺河区",
"NativePlace": "黑龙江省 汤旺河区",
"AccountType": "城镇",
"Birthday": "2019-01-03T00:00:00",
"Phone": "13904150828",
"QQ": "6708666229",
"Email": "13904150828@139.com",
"MaritalStatus": "未婚",
"GraduateSchool": "天津科技大学",
"Major": "软件工程",
"Education": "硕士"
}, {
"No": 9,
"Name": "百里荣轩",
"Gender": "男",
"Nation": "高山族",
"Political": "民进会员",
"IdCard": "411528197903171453",
"Address": "河南省信阳市息县",
"NativePlace": "河南省 息县",
"AccountType": "城镇",
"Birthday": "2017-01-03T00:00:00",
"Phone": "15707553625",
"QQ": "6708666230",
"Email": "6708666230@qq.com",
"MaritalStatus": "未婚",
"GraduateSchool": "南开大学",
"Major": "给水排水科学与工程",
"Education": "本科"
}, {
"No": 10,
"Name": "闻人苑博",
"Gender": "男",
"Nation": "佤族",
"Political": "民盟盟员",
"IdCard": "511529200609210916",
"Address": "四川省宜宾市屏山县",
"NativePlace": "四川省 屏山县",
"AccountType": "城镇",
"Birthday": "2021-01-09T00:00:00",
"Phone": "15702016573",
"QQ": "6708666231",
"Email": "6708666231@qq.com",
"MaritalStatus": "未婚",
"GraduateSchool": "中国传媒大学",
"Major": "给水排水科学与工程",
"Education": "博士"
}, {
"No": 11,
"Name": "濮阳峻熙",
"Gender": "男",
"Nation": "高山族",
"Political": "民建会员",
"IdCard": "540228201108060690",
"Address": "西藏自治区日喀则市白朗县",
"NativePlace": "西藏自治区 白朗县",
"AccountType": "城镇",
"Birthday": "2006-01-08T00:00:00",
"Phone": "18902409027",
"QQ": "6708666232",
"Email": "6708666232@qq.com",
"MaritalStatus": "未婚",
"GraduateSchool": "北京大学医学部",
"Major": "工程管理",
"Education": "硕士"
}, {
"No": 12,
"Name": "督明辉",
"Gender": "男",
"Nation": "俄罗斯族",
"Political": "农工党党员",
"IdCard": "371725199902110394",
"Address": "山东省菏泽市郓城县",
"NativePlace": "山东省 郓城县",
"AccountType": "城镇",
"Birthday": "2011-01-02T00:00:00",
"Phone": "18001723115",
"QQ": "6708666233",
"Email": "6708666233@qq.com",
"MaritalStatus": "已婚",
"GraduateSchool": "华北电力大学(北京)",
"Major": "软件工程",
"Education": "本科"
}, {
"No": 13,
"Name": "赫连志泽",
"Gender": "男",
"Nation": "哈尼族",
"Political": "民盟盟员",
"IdCard": "370828197805241778",
"Address": "山东省济宁市金乡县",
"NativePlace": "山东省 金乡县",
"AccountType": "城镇",
"Birthday": "2024-01-05T00:00:00",
"Phone": "18907244028",
"QQ": "6708666234",
"Email": "6708666234@qq.com",
"MaritalStatus": "未婚",
"GraduateSchool": "中国传媒大学",
"Major": "软件工程",
"Education": "本科"
}, {
"No": 14,
"Name": "乐正绮晴",
"Gender": "女",
"Nation": "羌族",
"Political": "民革党员",
"IdCard": "654022198007170902",
"Address": "新疆维吾尔自治区伊犁哈萨克自治州察布查尔锡伯自治县",
"NativePlace": "新疆维吾尔自治区 察布查尔锡伯自治县",
"AccountType": "城镇",
"Birthday": "2017-01-07T00:00:00",
"Phone": "15806878191",
"QQ": "6708666235",
"Email": "15806878191@139.com",
"MaritalStatus": "未婚",
"GraduateSchool": "华北电力大学(北京)",
"Major": "汽车服务工程",
"Education": "硕士"
}, {
"No": 15,
"Name": "海亦竹",
"Gender": "女",
"Nation": "朝鲜族",
"Political": "农工党党员",
"IdCard": "110117198306161849",
"Address": "北京市平谷区",
"NativePlace": "北京市 平谷区",
"AccountType": "城镇",
"Birthday": "2016-01-06T00:00:00",
"Phone": "18906747774",
"QQ": "6708666236",
"Email": "18906747774@139.com",
"MaritalStatus": "已婚",
"GraduateSchool": "北京航空航天大学",
"Major": "地理信息技术",
"Education": "本科"
}, {
"No": 16,
"Name": "鲜于文轩",
"Gender": "男",
"Nation": "崩龙族",
"Political": "共青团员",
"IdCard": "530521197208210194",
"Address": "云南省保山市施甸县",
"NativePlace": "云南省 施甸县",
"AccountType": "城镇",
"Birthday": "2021-01-08T00:00:00",
"Phone": "18201156229",
"QQ": "6708666237",
"Email": "6708666237@qq.com",
"MaritalStatus": "未婚",
"GraduateSchool": "中国政法大学",
"Major": "地理信息技术",
"Education": "硕士"
}, {
"No": 17,
"Name": "夹谷弘⽂",
"Gender": "男",
"Nation": "高山族",
"Political": "共青团员",
"IdCard": "610431200810050479",
"Address": "陕西省咸阳市武功县",
"NativePlace": "陕西省 武功县",
"AccountType": "城镇",
"Birthday": "2005-01-10T00:00:00",
"Phone": "13605158623",
"QQ": "6708666238",
"Email": "6708666238@qq.com",
"MaritalStatus": "已婚",
"GraduateSchool": "北京信息科技大学",
"Major": "计算机科学与技术(师范)",
"Education": "本科"
}, {
"No": 18,
"Name": "东方白竹",
"Gender": "女",
"Nation": "羌族",
"Political": "无党派人士",
"IdCard": "140471201505060960",
"Address": "山西省长治市山西长治高新技术产业园区",
"NativePlace": "山西省 山西长治高新技术产业园区",
"AccountType": "城镇",
"Birthday": "2006-01-05T00:00:00",
"Phone": "15007720455",
"QQ": "6708666239",
"Email": "15007720455@139.com",
"MaritalStatus": "未婚",
"GraduateSchool": "北京邮电大学",
"Major": "软件工程",
"Education": "本科"
}, {
"No": 19,
"Name": "宗政映雁",
"Gender": "女",
"Nation": "独龙族",
"Political": "民建会员",
"IdCard": "360822200807131025",
"Address": "江西省吉安市吉水县",
"NativePlace": "江西省 吉水县",
"AccountType": "城镇",
"Birthday": "2013-01-07T00:00:00",
"Phone": "15902746227",
"QQ": "6708666240",
"Email": "15902746227@139.com",
"MaritalStatus": "已婚",
"GraduateSchool": "北京中医药大学",
"Major": "机电工程",
"Education": "本科"
}, {
"No": 20,
"Name": "公良从蓉",
"Gender": "女",
"Nation": "基诺族",
"Political": "致公党党员",
"IdCard": "331023198102010165",
"Address": "浙江省台州市天台县",
"NativePlace": "浙江省 天台县",
"AccountType": "城镇",
"Birthday": "2001-01-02T00:00:00",
"Phone": "18904544448",
"QQ": "6708666241",
"Email": "18904544448@139.com",
"MaritalStatus": "未婚",
"GraduateSchool": "北京体育大学",
"Major": "给水排水科学与工程",
"Education": "博士"
}],
//表单组件类型、字典
formComponents: [
{ fieldName: 'No', componentType: 'text', label: '编号' },
{ fieldName: 'Name', componentType: 'text', label: '姓名' },
{ fieldName: 'Gender', componentType: 'radio', label: '性别', dict: [{ text: '男', value: '男' }, { text: '女', value: '女' }] },
{ fieldName: 'Nation', componentType: 'select', label: '民族', dict: [{ text: "汉族", value: "汉族" }, { text: "蒙古族", value: "蒙古族" }, { text: "傣族", value: "傣族" }] },
{
fieldName: 'Political', componentType: 'text', label: '政治面貌', dict: [{ text: "群众", value: "群众" }, { text: "党员", value: "党员" }, { text: "团员", value: "团员" }]
},
{ fieldName: 'IdCard', componentType: 'text', label: '身份证号' },
{ fieldName: 'Address', componentType: 'text', label: '联系地址' },
{ fieldName: 'NativePlace', componentType: 'text', label: '籍贯' },
{ fieldName: 'AccountType', componentType: 'select', label: '户口类型', dict: [{ text: "农村", value: "农村" }, { text: "城镇", value: "城镇" }] },
{ fieldName: 'Birthday', componentType: 'date', label: '出生日期' },
{ fieldName: 'Phone', componentType: 'text', label: '联系电话' },
{ fieldName: 'QQ', componentType: 'text', label: 'QQ' },
{ fieldName: 'Email', componentType: 'text', label: '邮箱' },
{ fieldName: 'MaritalStatus', componentType: 'select', label: '婚姻状况', dict: [{ text: "已婚", value: "已婚" }, { text: "未婚", value: "未婚" }, { text: "离异", value: "离异" }] },
{ fieldName: 'GraduateSchool', componentType: 'text', label: '毕业院校' },
{ fieldName: 'Major', componentType: 'text', label: '专业' },
{ fieldName: 'Education', componentType: 'select', label: '学历层次', dict: [{ label: "本科", value: "本科" }, { label: "专科", value: "专科" }, { label: "硕士研究生", value: "硕士研究生" }, { label: "博士研究生", value: "博士研究生" }, { label: "初中", value: "初中" }, { label: "高中/中专", value: "高中/中专" }, { label: "小学", value: "小学" }] }
],
//表单字段
formFields: {
No: '', Name: '', Gender: '男', Nation: '汉族', Political: '',
IdCard: '', Address: '',
NativePlace: '', AccountType: '农村',
Birthday: '2000-01-01', Phone: '',
QQ: '', Email: '',
MaritalStatus: '未婚', GraduateSchool: '',
Major: '', Education: '本科'
},
//表单字段验证规则
formRules: {
No: [{ required: true, message: '编号不允许为空', trigger: 'blur' }],
Name: [{ required: true, message: '姓名不允许为空', trigger: 'blur' }],
Nation: [{ required: true, message: '民族地址不允许为空', trigger: 'blur' }],
Political: [{ required: true, message: '政治面貌地址不允许为空', trigger: 'blur' }],
IdCard: [
{ required: true, message: '身份证号不允许为空', trigger: 'blur' },
{ validator: checkIdCard, trigger: 'blur' },
{ min: 15, max: 18, message: '身份证号位数为15-18', trigger: 'blur' }
],
Address: [{ required: true, message: '联系地址不允许为空', trigger: 'blur' }],
NativePlace: [{ required: true, message: '籍贯不允许为空', trigger: 'blur' }],
AccountType: [{ required: true, message: '户口类型不允许为空', trigger: 'blur' }],
Birthday: [{ required: true, message: '出生日期不允许为空', trigger: 'blur' }],
Phone: [
{ required: true, message: '联系电话不允许为空', trigger: 'blur' },
{ min: 6, max: 13, message: '联系电话位数为6-13', trigger: 'blur' }
],
Email: [
{ required: true, message: '邮箱不允许为空', trigger: 'blur' },
{ validator: checkEmail, trigger: 'blur' }
],
MaritalStatus: [{ required: true, message: '婚姻状态不允许为空', trigger: 'blur' }],
GraduateSchool: [{ required: true, message: '毕业院校不允许为空', trigger: 'blur' }],
Major: [{ required: true, message: '专业不允许为空', trigger: 'blur' }],
Education: [{ required: true, message: '学历不允许为空', trigger: 'blur' }]
}
})
</script>
表单子组件
<template>
<el-dialog :model-value="props.dialogVisible" :fullscreen="pageParams.isFullscreen" :center="true" :align-center="true"
:modal="true" width="70%" :show-close="false" draggable>
<template #header="{ close }">
<div style="display: flex;justify-content: space-between;">
<h4>{{ props.title }}</h4>
<div>
<el-button :icon="CaretTop" @click="pageParams.isFullscreen = !pageParams.isFullscreen"
text></el-button>
<el-button :icon="Close" @click="Closes" text></el-button>
</div>
</div>
</template>
<el-form ref="fromName" :rules="props.FromRules" :inline="true" :model="props.FormFields" :label-width="'120px'"
:label-suffix="':'" :label-position="'right'">
<template v-for="fields in props.formComponents">
<template v-if="fields.componentType === 'select'">
<el-form-item :label="fields.label" :prop="fields.fieldName">
<el-select v-model="props.FormFields[fields.fieldName]">
<template v-for="dict in fields.dict">
<el-option :label="dict.text" :value="dict.value" />
</template>
</el-select>
</el-form-item>
</template>
<template v-if="fields.componentType === 'radio'">
<el-form-item :label="fields.label" :prop="fields.fieldName">
<el-radio-group v-model="props.FormFields[fields.fieldName]">
<template v-for="dict in fields.dict">
<el-radio :label="dict.value">{{ dict.text }}</el-radio>
</template>
</el-radio-group>
</el-form-item>
</template>
<template v-if="fields.componentType === 'text'">
<el-form-item :label="fields.label" :prop="fields.fieldName">
<el-input v-model="props.FormFields[fields.fieldName]" maxlength="18"
:placeholder="'请输入' + fields.label" />
</el-form-item>
</template>
</template>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button type="primary" :icon="Plus" @click="onSubmit">
添加
</el-button>
<el-button type="danger" @click="onReset" :icon="Refresh">重置</el-button>
<el-button type="warning" @click="onCancel" :icon="Close">取消</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="js">
import {
ref,
reactive,
onMounted
} from 'vue'
import { Plus, Refresh, Close, CaretTop } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
onMounted(() => {
})
const props = defineProps({
dialogVisible: { type: Boolean, default: () => false },
title: {
type: String, default: () => '添加'
},
formSet: {
type: Object,
default: () => { }
},
formComponents: {
type: Array,
default: () => { }
},
FormFields: {
type: Object,
default: () => { }
},
FromRules: {
type: Object,
default: () => { }
}
})
//回显数据
const Emits = defineEmits(['DialogVisible,TableAdd'])
const pageParams = reactive({ isFullscreen: false })
//设置对象名称
const fromName = ref()
//提交表单
const onSubmit = () => {
//验证表单数据
fromName.value.validate((valid) => {
if (valid) {
ElMessage({
message: '表单验证成功',
type: 'success',
});
Emits('TableAdd', [false, props.FormFields])
//console.log();
} else {
ElMessage({
message: '表单验证失败',
type: 'error',
})
}
})
//打印表单数据
}
const onCancel = () => {
onReset();
//回显数据
Emits('DialogVisible', false);
}
const onReset = () => {
fromName.value.resetFields();
}
const Closes = () => {
onReset();
Emits('DialogVisible', false);
}
</script>
<style scoped lang="scss">
/*el-form-item的宽度,设置宽度后无法响应式对齐*/
:deep(.el-form-item) {
width: 46%;
}
@media (max-width: 920px) {
:deep(.el-form-item) {
width: 100%;
}
}
/*标签的样式*/
:deep(.el-form-item__label) {
font-size: 20px;
font-weight: bold;
font-family: '宋体';
color: black;
}
/*输入框的样式*/
:deep(.el-input__inner) {
height: 40px;
width: 100%;
}
/*选择框的样式*/
:deep(.el-input__wrapper) {
height: 40px;
width: 100%;
}
</style>
表格子组件
<template>
<div style="display: flex;justify-content: space-between;">
<div style="display: inline-block">
<el-input v-model="props.qeuryName" placeholder="请输入姓名" style="width: 200px;" />
<el-button type="primary" :icon="Search">查询</el-button>
</div>
<div>
<el-button type="primary" :icon="Plus" @click="dialogVisible">添加</el-button>
<el-button type="warning" :icon="Edit" @click="tableEdit">编辑</el-button>
<el-button type="danger" :icon="Delete" @click="tableDel">删除</el-button>
<el-button type="success" :icon="Refresh">刷新</el-button>
</div>
</div>
<el-table ref="multipleTableRef" :fix="true" :size="'small'" :stripe="true" :table-layout="'auto'"
:data="props.TableFieldsData" :border="true" height="700">
<el-table-column v-for="item in props.TableColumnFields" :type="item.type" :sorttable="item.sort"
:width="item.width" :prop="item.prop" :label="item.label" />
<el-table-column label="操作" :width="100">
<template #default="scoped">
<el-button type="warning" size="small" @click="tableEditClick(scoped.row)" :icon="Edit" circle></el-button>
<el-button type="danger" size="small" @click="tableDelClick(scoped.row)" :icon="Delete" circle></el-button>
</template>
</el-table-column>
</el-table>
<br />
<el-pagination :page-sizes="props.pageSizeArr" :small="true" :disabled="false" :background="true"
layout="total, sizes, prev, pager, next, jumper" :total="props.total" @size-change="constSizeChange"
@current-change="CurrentChange" style="justify-content: center;" />
</template>
<script setup lang="js">
import {
ref,
onMounted
} from 'vue'
import { ElMessage } from 'element-plus'
import { Plus, Edit, Refresh, Search, Delete } from '@element-plus/icons-vue'
onMounted(() => {
})
//设置对象名称
const multipleTableRef = ref()
//接收数据
const props = defineProps({
//总记录条数
total: {
type: Number,
default: () => 0
},
//页面可选尺寸
pageSizeArr: {
type: Array,
default: () => 0
},
//页面默认尺寸
pageSize: {
type: Number,
default: () => 20
},
//表格字段
TableColumnFields: {
type: Array,
default: () => []
},
//表格字段数据
TableFieldsData: {
type: Array,
default: () => []
},
qeuryName: { type: String, default: () => "" }
})
//回显数据
const Emits = defineEmits(['DialogVisible', 'TableEdit', 'TableDel'])
//尺寸改变的时候重新加载数据
const constSizeChange = (val) => {
props.pageSize = parseInt(val)
console.log(`每页 ${val} 条`);
}
//请求当前页数据
const CurrentChange = (val) => {
val = parseInt(val)
console.log(`当前页: ${val}`);
}
//添加数据
const dialogVisible = () => {
Emits('DialogVisible', true)
}
//编辑数据
const tableEdit = () => {
//获取选中行数据
var rows = multipleTableRef.value.getSelectionRows();
if (rows.length > 0) {
Emits('TableEdit', [true, rows[0]]);
}
else {
ElMessage.error('请选中一行数据')
}
}
//表格点击事件(编辑)
const tableEditClick = (rows) => {
Emits('TableEdit', [true, rows]);
}
//删除数据
const tableDel = () => {
//获取选中行数据
var rows = multipleTableRef.value.getSelectionRows();
if (rows.length > 0) {
Emits('TableDel', rows.map(x => x.No));
}
else {
ElMessage.error('请选中一行数据')
}
}
const tableDelClick = (rows) => {
Emits('TableDel', [rows.No]);
}
</script>
<style scoped lang="scss">
/*输入框的样式*/
:deep(.el-input__inner) {
height: 30px;
width: 50px;
}
/*选择框的样式*/
:deep(.el-input__wrapper) {
height: 30px;
width: 50px;
}
</style>
5、计算
属性
计算属性 本质上 就是 一个 function 函数 ,它可以 实时监听 data 中数据的变化,并 return 一个计算后的新值 , 供组件渲染 DOM 时使用。
计算属性需要以 function 函数 的形式声明到组件的 computed 选项中。
注意:计算属性 侧重于 得到一个 计算的结果 ,因此计算属性中 必须有 return 返回值 !
计算属性的使用注意点
① 计算属性 必须定义在 computed 节点中;
② 计算属性 必须是一 个 function 函数;
③ 计算属性 必须有返回值;
④ 计算属性 必须当做普通属性使用;
计算属性 vs 方法
相对于方法来说, 计算属性会缓存计算的结果 ,只有计算属性的 依赖项发生变化 时,才会 重新进行运算 。因此计算属性的性能更好。
<template>
<input type="text" v-model.number="count"/>
<h1>{{count}}+10后的值{{add}}</h1>
<button class="btn btn-info" @click="add1">方法点击</button>
<h1>{{count}}+10后的值{{add}}</h1>
<!--或者是-->
<!--<router-view />-->
</div>
</template>
<script>
import {
ref
} from "vue"
//组件相关的data数据、methods方法等
//都要包裹在export default所导出的对象中
export default {
//name属性的名称指向当前页面的名称,建议名称使用驼峰法
name: "App",
//组件的数据
setup() {
const count = ref(0);
return {
count
}
},
//组件的方法
methods: {
add1() {
console.log("方法");
this.count += 10;
}
},
computed: {
add() {
console.log("计算属性");
return this.count + 10;
}
}
</script>
6、自定义事件
7、watch
watch侦听器 允许开发者监视数据的变化,从而 针对数据的变化做特定的操作 。例如,监视用户名的变化并发
起请求,判断用户名是否可用。
计算属性 vs 侦听器
计算属性和侦听器 侧重的应用场景不同 :
计算属性侧重于监听 多个值 的变化,最终计算并 返回一个新值
侦听器侧重于监单个数据 的变化,最终 执行特定的业务处理,不需要有任何返回值
watch基本语法
import {ref} from "vue"
export default {
name: "App",
setup() {
const count = ref(0);
const msg = ref('');
return {
count,
msg,
}
},
watch: {
//监听count值的变化
//newval表示变化后的值, oldval表示变化之前的值
count(newval, oldval) {
this.msg = {'newval': newval,'oldval': oldval};
}
}
watch异步方法
#安装axios
yarn add axios
import axios from 'axios'
import {ref} from "vue"
export default {
name: "App",
setup() {
const name = ref(0);
const msg = ref('');
return {
name,
msg,
}
},
watch: {
//监听name值的变化
//newval表示变化后的值, oldval表示变化之前的值
//判断用户名是否可用
async name(newval, oldval) {
//判断一下,值不为空再
if (newval != '') {
const {data: res} = await axios.get('https://www.escook.cn/api/finduser/' + newval);
//{ "status": 1, "message": "用户名被占用!" }
//{ "status": 0, "message": "用户名可用!" }
this.msg = res.message;
}
}
}
immediate选项
默认情况下,组件在初次加载完毕后不会调用watch 侦听器。如果想让 watch 侦听器 立即被调用 ,则需要使用 immediate选项
import axios from 'axios'
import {ref} from "vue"
export default {
name: "App",
setup() {
const name = ref('admin');
const msg = ref('');
return {
name,
msg,
}
},
watch: {
//监听name值的变化
name:{
//newval表示变化后的值, oldval表示变化之前的值
//判断用户名是否可用
async handler(newval, oldval) {
//判断一下,值不为空再
if (newval != '') {
const {data: res} = await axios.get('https://www.escook.cn/api/finduser/' + newval);
//{ "status": 1, "message": "用户名被占用!" }
//{ "status": 0, "message": "用户名可用!" }
this.msg = res.message;
}
},
//初次加载完毕后立即执行一次监听
immediate: true
}
}
}
deep选项
当 watch 侦听的是一个对象 ,如果 对象中的属性值发生了变化 ,则 无法被监听到 。此时需要使用 deep 选项
import axios from 'axios'
import {ref} from "vue"
export default {
name: "App",
setup() {
const info = ref({name:'admin'});
const msg = ref('');
return {
info,
msg,
}
},
watch: {
//监听name值的变化
info:{
//newval表示变化后的值, oldval表示变化之前的值
//判断用户名是否可用
async handler(newval, oldval) {
//判断一下,值不为空再
if (newval.name != '') {
const {data: res} = await axios.get('https://www.escook.cn/api/finduser/' + newval.name);
//{ "status": 1, "message": "用户名被占用!" }
//{ "status": 0, "message": "用户名可用!" }
this.msg = res.message;
}
},
//监听熟悉值变化
deep:true
}
}
}
指定监听对象的某个值
import axios from 'axios'
import {ref} from "vue"
export default {
name: "App",
setup() {
const info = ref({name:'admin'});
const msg = ref('');
return {
info,
msg,
}
},
watch: {
//监听name值的变化
'info.name':{
//newval表示变化后的值, oldval表示变化之前的值
//判断用户名是否可用
async handler(newval, oldval) {
//判断一下,值不为空再
if (newval!= '') {
const {data: res} = await axios.get('https://www.escook.cn/api/finduser/' + newval);
//{ "status": 1, "message": "用户名被占用!" }
//{ "status": 0, "message": "用户名可用!" }
this.msg = res.message;
}
},
}
}
}
8、组件的生命周期
运行过程
监听组件的不同时刻
当组件的 data 数据更新之后,vue 会自动重新渲染组件的 DOM 结构,从而保证 View 视图展示的数据和Model 数据源保持一致。当组件被重新渲染完毕之后,会自动调用 updated 生命周期函数。
组件主要生命周期函数
组件全部生命周期函数
9、axios
安装axios
npm install axios
#或者
yarn add axios
全局配置axios(main.js中)
import { createApp } from 'vue'
import App from './App.vue'
import axios from 'axios'
//配置默认的url前缀(根据实际情况配置)
axios.default.baseURL='http://localhost:5001/api'
const app = createApp(App);
//将axios挂载为app的全局自定义熟悉之后
//每个组件都可以通过this直接访问全局挂载的自定义熟悉
app.config.globalProperties.$http=axios
app.mount('#app');
使用全局配置的axios
//配置全局属性前
import axios from 'axios'
axios.get("http://localhost:5001/api/user");
axios.$http.get(http://localhost:5001/api"/login");
//配置全局属性后
this.$http.get("/user");
this.$http.get("/login");
axios封装
api
import axios from 'axios'
import store from '../store/index'
import showMessage from './status'
//引入遮罩及提示框
import { ElLoading as Loading, ElMessage as Message } from 'element-plus';
//请求超时
axios.defaults.timeout = 60000;
axios.defaults.headers.post['Content-Type'] = 'application/json;charset=UTF-8';
let _Authorization = 'Authorization';
let loadingInstance;
let loadingStatus = false;
if (process.env.NODE_ENV == 'development') {
axios.defaults.baseURL = 'https://localhost:5001';
}
else if (process.env.NODE_ENV == 'debug') {
axios.defaults.baseURL = 'https://localhost:5001';
}
else if (process.env.NODE_ENV == 'production') {
axios.defaults.baseURL = 'https://www.yang72.cn';
}
if (!axios.defaults.baseURL.endsWith('/')) {
axios.defaults.baseURL += "/";
}
function httpUrl() { return axios.defaults.baseURL }
//请求拦截器
axios.interceptors.request.use((config) => {
return config;
}, (error) => {
return Promise.reject(error);
});
//响应拦截器
axios.interceptors.response.use((res) => {
closeLoading();
return Promise.resolve(res);
}, (error) => {
closeLoading();
return Promise.reject(error);
});
//开启遮罩
function showLoading(loading) {
if (!loading || loadingStatus) {
return;
}
loadingInstance = Loading.service({
lock: true,
text: 'Loading',
customClass: "http-loading",
background: typeof loading == "string" ? loading : '正在处理.....',
background: 'rgba(58, 61, 63, 0.32)'
});
}
//关闭遮罩
function closeLoading() {
if (loadingInstance) {
loadingInstance.close();
}
if (loadingStatus) {
loadingStatus = false;
if (loadingInstance) {
loadingInstance.close();
}
}
}
function getToken() {
return store.getters.getToken;
}
/*
url
params请求后台的参数,如:{name:123,values:['a','b','c']}
loading是否显示遮罩层,可以传入true.false.及提示文本
config配置信息,如{timeout:3000,headers:{token:123}}
*/
function post(url, params, loading) {
showLoading(loading);
axios.defaults.headers[_Authorization] = getToken();
return new Promise((resolve, reject) => {
axios.post(url, params)
.then(response => {
resolve(response.data);
}, err => {
Message.error(showMessage(err.code))
reject(err && err.data && err.data.message ? err.data.message : '服务器处理异常');
})
.catch((error) => {
Message.error(showMessage(error.code))
reject(error)
})
})
}
//=true异步请求时会显示遮罩层,=字符串,异步请求时遮罩层显示当前字符串
function get(url, param, loading) {
showLoading(loading);
axios.defaults.headers[_Authorization] = getToken();
return new Promise((resolve, reject) => {
axios.get(url, { params: param })
.then(response => {
resolve(response.data)
}, err => {
Message.error(showMessage(err.code))
reject(err)
})
.catch((error) => {
Message.error(showMessage(error.code))
reject(error)
})
})
}
//下载压缩包流
function zipdownloads(url, param, loading, filename) {
showLoading(loading);
axios.defaults.headers[_Authorization] = getToken();
axios({
method: "get",
url,
params: param,
responseType: 'blob'
})
.then(res => {
let blob = new Blob([res.data]);//type是文件类,详情可以参阅blob文件类型
// 创建新的URL并指向File对象或者Blob对象的地址
const blobURL = window.URL.createObjectURL(blob);
// 创建a标签,用于跳转至下载链接
const tempLink = document.createElement('a');
tempLink.style.display = 'none';
tempLink.href = blobURL;
tempLink.download = filename;
// 挂载a标签
document.body.appendChild(tempLink);
tempLink.click()
document.body.removeChild(tempLink);
// 释放blob URL地址
window.URL.revokeObjectURL(blobURL);
});
}
//下载文件流
function filedownloads(url, param, loading, filename) {
showLoading(loading);
axios.defaults.headers[_Authorization] = getToken();
axios({
method: "get",
url,
params: param,
responseType: 'blob'
})
.then(res => {
console.log(res);
let blob = new Blob([res.data]);//type是文件类,详情可以参阅blob文件类型
// 创建新的URL并指向File对象或者Blob对象的地址
const blobURL = window.URL.createObjectURL(blob);
// 创建a标签,用于跳转至下载链接
const tempLink = document.createElement('a');
tempLink.style.display = 'none';
tempLink.href = blobURL;
tempLink.download = filename;
// 挂载a标签
document.body.appendChild(tempLink);
tempLink.click()
document.body.removeChild(tempLink);
// 释放blob URL地址
window.URL.revokeObjectURL(blobURL);
});
}
export { httpUrl, post, get, filedownloads, zipdownloads }
status
//const showMessage = (status: number | string): string => { ts
const showMessage = (status) => {
let message = "";
switch (status) {
case 400:
message = "请求错误(400)";
break;
case 401:
message = "未授权,请重新登录(401)";
break;
case 403:
message = "拒绝访问(403)";
break;
case 404:
message = "请求出错(404)";
break;
case 408:
message = "请求超时(408)";
break;
case 500:
message = "服务器错误(500)";
break;
case 501:
message = "服务未实现(501)";
break;
case 502:
message = "网络错误(502)";
break;
case 503:
message = "服务不可用(503)";
break;
case 504:
message = "网络超时(504)";
break;
case 505:
message = "HTTP版本不受支持(505)";
break;
default:
message = `连接出错(${status})!`;
break;
}
return `${message},请检查网络或联系管理员!`;
};
export default showMessage
应用
<template>
<div style="width: 100%;,height: 100px;">
<el-button @click="getTest">GET</el-button>
<el-button @click="postTest">POST</el-button>
</div>
</template>
<script setup lang="js">
import { post, get } from '../api/index'
import { useStore } from 'vuex';
const store = useStore()
const getTest = () => {
get("api/WeatherForecast/GetToken", {}, true).then(x => {
console.log(x)
store.state.token = x;
})
}
const postTest = () => {
post("api/Persons/GetPageData",
{
PageNumber: 15,
OrderField: "Id",
PageSize: 10
}, true).then(res => {
console.log(res)
})
}
</script>
10、ref属性
11、动态组件
动态组件指的是动态切换组件的显示与隐藏。vue 提供了一个内置的 组件,专门用来实现组件的动态渲染。
① 是组件的占位符
② 通过 is 属性动态指定要渲染的组件名称
③
使用 keep-alive 保持状态:默认情况下,切换动态组件时无法保持组件的状态。此时可以使用 vue 内置的 组件保持动态组件的状态。
<keep-alive>
<compoent :is="comName"></compoent>
</keep-alive>
使用方法
复用组件
MyMovie.vue
<template>
<h3>Movie 组件</h3>
</template>
<script>
export default {
name: 'MyMovie',
}
</script>
MyHome.vue
<template>
<h3>Home 组件 --- {{ count }}</h3>
<button type="button" class="btn btn-primary" @click="count += 1">+1</button>
</template>
<script>
export default {
name: 'MyHome',
data() {
return {
count: 0,
}
},
created() {
console.log('created')
},
unmounted() {
console.log('unmounted')
}
}
</script>
主组件
<template>
<div>
<h1 class="mb-4">App 根组件</h1>
<button type="button" class="btn btn-primary" @click="comName = 'MyHome'">首页</button>
<button type="button" class="btn btn-info ml-2" @click="comName = 'MyMovie'">电影</button>
<hr />
<!-- 使用组件 -->
<!-- <my-home></my-home>
<my-movie></my-movie> -->
<keep-alive>
<component :is="comName"></component>
</keep-alive>
</div>
</template>
<script>
// 导入组件
import MyHome from './Home.vue'
import MyMovie from './Movie.vue'
export default {
name: 'MyApp',
data() {
return {
comName: 'MyHome'
}
},
// 注册组件
components: {
MyHome,
MyMovie,
},
}
</script>
12、插槽
插槽(Slot)是 vue 为组件的封装者提供的能力。允许开发者在封装组件时,把不确定的、希望由用户指定的部分定义为插槽。可以把插槽认为是组件封装期间,为用户预留的内容的占位符。
13、路由
安装路由
vue-router 是 vue.js 官方给出的路由解决方案。它只能结合 vue 项目进行使用,能够轻松的管理 SPA 项目中组件的切换。
vue-router 目前有 3.x 的版本和 4.x 的版本。其中:
⚫ vue-router 3.x 只能结合 vue2 进行使用
⚫ vue-router 4.x 只能结合 vue3 进行使用
vue-router 3.x 的官方文档地址:https://router.vuejs.org/zh/vue-router
vue-router 4.x 的官方文档地址:https://next.router.vuejs.org/
yarn add vue-router
使用路由
在src下新建一个router文件夹,在文件夹中新建一个index.js文件,文件名称可以自定义,合法即可,在文件内编写以下内容
/*导入路由组件*/
import { createRouter, createWebHashHistory } from 'vue-router'
/*带有children的路由一般为母版也,children才是实际路由,路由中的文件必须真实存在否则会报错*/
const routes = [{
path: '/',
name: 'index',
component: () => import ('../views/index.vue')
},
{
path: '/',
name: 'home',
component: () => import ('../Home.vue'),
children: [{
path: '/news',
name: 'news',
component:() => import ('../views/news.vue')
},
{
path: '/server',
name: 'server',
component: () => import ('../views/server.vue')
},
{
path: '/about',
name: 'about',
component: () => import ('../views/about.vue')
},
]
},
]
//创建路由,路由模式模式为hash,并添加路由的路径
const router = createRouter({
//指定路由的工作模式
history: createWebHashHistory(),
routes
})
//全局导航守卫会拦截每个路由规则,从而对每个路由进行访问权限的控制
router.beforeEach((to, from, next) => {
//to表示要跳转的路由
//from表示当前路由
//next表示一个函数,放行
//判断路径长度是否为0
if (to.matched.length == 0)
return next({ path: '/404' });
//一般没有登录或者没有权限在这里判断,有权限后或者登录后放行路由
if ('判断条件') {
return next();
}
//默认跳转到登录也
next({ path: '/login', query: { redirect: Math.random() } });
})
//导出路由
export default router
在main.js中注册路由
import { createApp } from 'vue'
import App from './App.vue'
/*导入路由*/
import router from './router'
/*注册路由*/
createApp(App).use(router).mount('#app')
/*或者是这样注册路由*/
/*const app = createApp(App);
app.use(router);
app..mount('#app');*/
添加页面路由链接
<!--方式1-->
<!--to中的值对应路由中的name
{
path: '/about',
name: 'about',
component: import ('../views/about.vue')
}
-->
<router-link to="about"></router-link>
<!--方式2-->
<!--href中的值对应路由中的path,根据常用的路由模式一般在前面加上#号
{
path: '/about',
name: 'about',
component: import ('../views/about.vue')
}
-->
<a href="#/home"></a>
<!--路由占位符,预览显示页面的位置,可以理解为html中iframe框架中的那个位置-->
<router-view></router-view>
路由重定向
路由重定向指的是:用户在访问地址 A 的时候,强制用户跳转到地址 C ,从而展示特定的组件页面。通过路由规则的 redirect 属性,指定一个新的路由地址,可以很方便地设置路由的重定向。比如访问跟目录的时候自动重定向到首页,或者页面不存在的时候重定向到404
导航守卫
① 在守卫方法中如果不声明 next 形参,则默认允许用户访问每一个路由!
② 在守卫方法中如果声明了 next 形参,则必须调用 next() 函数,否则不允许用户访问任何一个路由
直接放行:next()
强制其停留在当前页面:next(false)
强制其跳转到登录页面:next(‘/login’)
router.beforeEach((to, from, next) => {
//to表示要跳转的路由
//from表示当前路由
//next表示一个函数,放行
//判断路径长度是否为0
if (to.matched.length == 0)
return next({ path: '/404' });
//一般没有登录或者没有权限在这里判断,有权限后或者登录后放行路由
if ('判断条件') {
return next();
}
//默认跳转到登录也
next({ path: '/login', query: { redirect: Math.random() } });
})
页面使用路由
<template>
<p>
<img src="../../assets/404.png" />
<el-button type="primary">
<router-link to="/index">返回首页</router-link>
</el-button>
<el-button type="primary" @click="index">
返回首页
</el-button>
</p>
</template>
<script>
import {
useRouter,
} from 'vue-router'
export default {
setup() {
const router = useRouter()
},
methods: {
index() {
this.$router.push({
path: '/index',
query: {
redirect: Math.random()
}
})
}
}
}
</script>
<style scoped>
img {
width: 50%;
height: 50%;
}
p {
text-align: center;
}
a {
text-decoration: none;
color: white;
}
</style>