创建vue+vite
npm init vite@latest esayblog-font-admin
运行该代码进行初始化vue+vite,npm install将node配置下载下来,
'vite' 不是内部或外部命令,也不是可运行的程序或批处理文件。
遇到这个问题是因为没有初始化 需要 npm install
npm ERR! code ENOENT
npm ERR! syscall open
npm ERR! path C:\Users\lenovo\Desktop\museum/package.json
npm ERR! errno -4058
npm ERR! enoent ENOENT: no such file or directory, open 'C:\Users\lenovo\Desktop\museum\package.json'
npm ERR! enoent This is related to npm not being able to find a file.
npm ERR! enoent
npm ERR! A complete log of this run can be found in:
npm ERR! C:\Users\lenovo\AppData\Local\npm-cache\_logs\2023-03-12T12_43_46_205Z-debug-0.log
安装element-plus库
在App组件中修改系统语言
项目框架结构视图:
components:放置封装的组件,将封装的组件配置到main.js(就是全局组件,每个组价可以直接调用,不需要导入组件模块)
view:储存所有页面组件,
Framework:页面框架三大部分(Header,left-aside, el-main)
配置路由文件:新建router文件夹index.js
创建router
npm install vue-router@4 -save
const router = createRouter({
routes,
history: createWebHistory(),
mode: 'hash',
// base: process.env.BASE_URL,
})
配置路由路径
const routes = [
{
name: '登录',
path: '/login',
component: () =>
import ('../view/Login.vue')
},
{
name: '框架页',
path: '/home',
component: () =>
import ('../view/Framework.vue'),
redirect: '/blog/list',
children: [{
path: '/blog/list',
name: '博客管理',
component: () =>
import ("../view/blog/BlogList.vue")
}]
}
]
设置拦截,当路由没有设置cookies就跳回登录页面
// 设置拦截,如果没有登录或者cookies失效了就要退回登录页面
router.beforeEach((to, from, next) => {
const userInfo = VueCookies.get("userInfo");
if (!userInfo && to.path != '/login') {
router.push('/login')
}
next();
})
export default router
配置代理
在vite.config.js中解决跨域问题,配置代理
export default defineConfig({
plugins: [vue()],
server: {
// open: true, 自动打开浏览器
hmr: true,
port: 3001,
proxy: {
'^/api': {
target: 'http://localhost:8081/', //目标代理接口地址
secure: false,
changeOrigin: true, //开启代理,在本地创建一个虚拟服务端
pathRewrite: {
'^/api': 'api',
},
},
},
},
})
配置路径别名(解决导入文件路径的麻烦)
resolve: {
//配置路径别名
alias: {
'@': path.resolve(__dirname, './src')
}
},
解决vue打包内存过大问题:
build: {
chunkSizeWarningLimit: 1500,
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes('node_modules')) {
return id.toString().split('node_modules/')[1].split('/')[0].toString();
}
}
}
}
},
组件封装
封装表格:
<el-table ref="dataTable"
:data="dataSource.list || []"
:height="tableHeight"
:stripe="options.stripe"
:border="options.border"
header-row-class-name="table-header-row"
highlight-current-row
@row-click="handleRowClick"
@selection-change="handleSelectionChange">
</el-table>
data:数据列表
height:高度
stripe: 是否为斑马纹 table(默认没有)
border:是否带有纵向边框(默认没有)
<!--selection选择框-->
<el-table-column v-if="options.selectType && options.selectType == 'checkbox'"
type="selection"
width="50"
align="center"></el-table-column>
<!--序号-->
<el-table-column v-if="options.showIndex"
label="序号"
type="index"
width="60"
align="center"></el-table-column>
selection选择框:在表格前面添加checkbox,可以继续多选选择删除功能
<!--数据列-->
<template v-for="(column, index) in columns">
<template v-if="column.scopedSlots">
<el-table-column :key="index"
:prop="column.prop"
:label="column.label"
:align="column.align || 'left'"
:width="column.width">
<template #default="scope">
<slot :name="column.scopedSlots"
:index="scope.$index"
:row="scope.row">
</slot>
</template>
</el-table-column>
</template>
<template v-else>
<el-table-column :key="index"
:prop="column.prop"
:label="column.label"
:align="column.align || 'left'"
:width="column.width"
:fixed="column.fixed">
</el-table-column>
</template>
</template>
判断数据是否重写数据列
如果有scopedSlots属性,就是自己定义template自己渲染数据列
设置分页:
<div class="pagination"
v-if="showPagination">
<el-pagination v-if="dataSource.totalCount"
background
:total="dataSource.totalCount"
:page-sizes="[15, 30, 50, 100]"
:page-size="dataSource.pageSize"
:current-page.sync="dataSource.pageNo"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handlePageSizeChange"
@current-change="handlePageNoChange"
style="text-align: center"></el-pagination>
</div>
</div>
//切换每页大小
const handlePageSizeChange = (size) => {
props.dataSource.pageSize = size;
props.dataSource.pageNo = 1;
props.fetch();
};
// 切换页码
const handlePageNoChange = (pageNo) => {
props.dataSource.pageNo = pageNo;
props.fetch();
封装对话框
<el-dialog
:show-close="false"
:draggable="true"
v-model="(show)"
:close-on-click-modal="false"
:title="title"
:showClose="showClose"
class="cust-dialog"
:top="top"
:width="width"
:showCancel="showCancel"
@close="close"
>
<el-dialog>
对话框内容
<div class="dialog-body">
<slot>
</slot>
</div>
利用slot插槽将对话框内容留在中间部位
<template v-if="buttons&&buttons.length>0|| showCancel">
<div class="dialog-footer">
<el-button link @click="close">
取消
</el-button>
<el-button v-for="btn in buttons" :type="btn.type" @click="btn.click">
{{btn.text}}
</el-button>
</div>
</template>
遍历按钮个数
defineProps学习
在父组件内传递变量的时候,需要加冒号:
,否则你就只是单纯的传递了一个字符串而已。
就是子组件定义defineProps属性,父组件直接在组件名后面写 :参数名=“参数”
子组件:(default为默认值)
const props=defineProps({
title:{
type:String
},
show: {
type: Boolean,
default: true
},
shwoClose:{
type:Boolean,
default:true,
},
top:{
type:String,
default:"50px"
},
width:{
type:String,
default:"30%"
},
buttons:{
Array
},
showCancel:{
type:Boolean,
default:true,
},
})
父组件调用传参
<Dialog
:title="dialogConfig.title"
:buttons="dialogConfig.buttons"
@close="dialogConfig.show = false"
:show="dialogConfig.show"
width="500px"
>
<Dialog>
ref和reactive学习
特点: reactive的参数一般都是对象或者数组,能够将复杂的类型变成响应式数据
reactive的响应式是深层次的,底层本质是将传入的数据转换成Proxy对象
defineProperty很Proxy的区别
defineProperty只能单一的监听已有的属性的修改或者变化,无法检测到对象属性的新增或者删除而Proxy可以轻松实现
defineProperty无法监听属性值是数组类型的变化,而Proxy可以轻松实现
用reactive去声明简单数据类型,就会提示警告,提示这个值不能被reactive创建的
通过vue3源码可以看出来,当使用reactive定义数据时,会先进行判断是否是对象,是对象才会进行数据响应的处理,反之就直接被return出去了
因此reactive跟推荐用于对象或者数组的数据类型
ref
ref的参数一般是基本数据类型,也可以是对象类型
如果参数是对象类型,其实底层的本质还是reactive,系统会自动将ref转换为reactive,例如
ref(1) ===> reactive({value:1})
在模板中访问ref中的数据,系统会自动帮我们添加.value,在JS中访问ref中的数据,需要手动添加.value
ref的底层原理同reactive一样,都是Proxy
ref vs reactive
相同特点:底层原理都是用的Proxy
reactive参数一般接收对象和数组,是深层次的响应式,ref 一般接收简单数据类型,若 ref接收对象为参数,本质上会转换成reactive方法
在js中访问ref的值需要手动添加.value,访问reactive不需要
store学习
状态管理
在vue中每一个组件实例都是已经在管理它自己的响应式状态,我们以一个简单的计数器为例:
<script setup>
import { ref } from 'vue'
// 状态
const count = ref(0)
// 动作
function increment() {
count.value++
}
</script>
<!-- 视图 -->
<template>{{ count }}</template>
它是一个独立单元,由状态,视图,交互三个部分组成
状态:驱动整个应用的数据源。
视图:在状态的一个声明式映射。
交互:状态根据用户在视图中的输入而作出响应变更的可能方式。
当我们有多个组件共享一个共同的状态时,就没有怎么简单了:
多个视图都依赖同一份状态
来自不同视图的交互也可能需要更改同一份状态
用响应式API做简单状态管理
如果你有一部分需要在多个组件实例间共享,你可以使用reactive来创建一个响应式对象,并将它导入到多个组件中
当组件A和组件B需要同一个count变量,在改变A中count,B中的count也要改变
我们可以在store.js中定义一个响应式对象
import { reactive } from 'vue'
export const store =reactive({
count:0,
increment(){
this.count++
}
})
组件A
<template>
<button @click="store.increment">
FormA:{{store.count}}
</button>
</template>
<script setup>
import { store } from './store.js'
</script>
组件B
<template>
<button @click="store.increment">
FormB:{{store.count}}
</button>
</template>
<script setup>
import { store } from './store.js'
</script>
vuex是专门为Vue.js设计的状态管理库,以利用vue.js的细粒度数据响应机制响应来进行高效的状态更新
更新页面处理
先获取请求地址(url),和请求参数(params),定义这些参数
let { url, params, dataType = 'form', showLoading = 'true' } = config;
let contentType = contentTypeForm;
if (dataType === "json") {
contentType = contentTypeJson;
} else if (dataType === "file") {
contentType = contentTypeFile;
let param = new FormData();
for (let key in params) {
param.append(key, params[key]);
}
params = param;
}
判断请求参数类型如果是json不需要遍历添加,如果是file,需要遍历添加
用axios请求
const instantce = axios.create({
baseURL: '/api', 基础地址
timeout: 10 * 1000, 超时设置
headers: {
'Content-Type': contentType, 请求头
'X-Requested-With': 'XMLHttpRequest',
}
})
请求拦截器
let loading = null;
instantce.interceptors.request.use(
(config) => {
if (showLoading) {
loading = ElLoading.service({ 加载
lock: true,
text: '加载中......',
background: 'rgba(0, 0, 0, 0.7)',
})
}
return config; 请求前做一个加载弹框
},
(error) => {
if (showLoading && loading) {
loading.close();
}
message.error("发送请求失败");
return Promise.reject("发送请求失败"); 请求失败返回失败信息
}
)
响应拦截器
instantce.interceptors.response.use(
(response) => { 获取响应后数据,取消加载框
if (showLoading && loading) {
loading.close();
}
const responseData = response.data;
if (responseData.status == "error") {
if (config.errorCallback) {
config.errorCallback();
}
return Promise.reject(responseData.info);
} else {
if (responseData.code == 200) {
return responseData;
} else if (responseData.code == 901) {
setTimeout(() => {
router.push("/login")
}, 2000);
return Promise.reject("登录超时");
}
}
},
(error) => {
console.log(error);
if (showLoading && loading) {
loading.close();
}
return Promise.reject("网络异常");
}
)
defineExpose用法
在vue3中,父组件可以创建一个ref(null) 然后将赋值的元素写在当前子组件上即可,在需要的时候,通过定义的响应式变量即可获取,获取后可取得当前子组件内部,dom以及当前子组件内部的变量方法等,并使用子组件=内部方法,但是有时候获取的时候返回没有什么信息。
重点:使用script setup 语法的组件是默认关闭的,通过ref或者$parent链获取的组件的公开实例,不会暴露任何在script setup中声明的绑定
方法: 为了在script setup语法组件中明确要暴露出去的属性,使用defineExpose编译器将需要暴露出去的变量和方法放入暴露出去就可以
子组件使用defineExpose暴露实例
<script setup>
import { ref } from 'vue';
const demo=ref('测试用例')
const handleVal=()=>{
demo.value="已改变"
}
将需要暴露出去的数据与方法都可以暴露出去
defineExpose({
demo,
handleVal,
})
</script>
<template>
<div class="inner">
{{demo}}
<button @click="handleVal">改值</button>
</div>
</template>
<style lang="scss" scoped>
</style>
父组件
<template>
<div class="container">
<h-demo ref="childDom" />
<button @click="getChild"></button>
</div>
</template>
<script setup>
import Hdemo from '@/components/Hdemo';
import { ref, } from 'vue';
const childDom=ref(null)
onMounted(() => {
const getChild = () =>{
console.log(childDom.value)
打印当前子组件暴露出来的数据
childDom.value.handleVal()
// 执行子组件方法
}
})