文章目录
- 前端项目
- node 版本
- node镜像
- 构建项目
- 创建项目
- 安装项目所有依赖
- 图片资源网站
- encodeURI & decodeURI
- app.config.globalProperties与getCurrentInstance
- Object.assign
- vue-cookies
- 安装vue-cookies
- 使用vue-cookies
- router.currentRoute
- preserve log
- import.meta.env
- Request.js
- 热更新问题
- vue3动态组件
- vuex4命名空间
- 二级菜单界面控制
- defineExpose暴露组件的方法
- 图片url加随机数,防止浏览器缓存
- Table封装,slot相关
- Table.vue
- FileList.vue
- vue3样式穿透
- prop单向数据流
- ref引用组件或dom 与 v-if/show
- @mouseenter & @mouseleave监听鼠标移入移出事件
- el-table和el-input在一起,获取焦点方法失效问题
- 路由子组件通知父组件实现
- 可以使用provide/inject的机制
- Framework.vue
- Main.vue
- 可以直接在<router-view @kk="kk"/>绑定事件监听
- Framework.vue
- Main.vue
- 可以使用\<router-view />的组件写法
- Framework.vue
- Main.vue
- el-popver 点击触发和使用方法触发
- 文件上传逻辑转移
前端项目
node 版本
v16.20.0
node历史下载地址:https://nodejs.org/download/release/v16.20.0/
node镜像
npm config get registry
# 返回: https://registry.npmmirror.com/
# 如果不是这个那么设置镜像
# npm config set registry https://registry.npmmirror.com
构建项目
创建项目
npm init vite@latest easypan-front
安装项目所有依赖
npm install
@highlightjs/vue-plugin
@moefe/vue-aplayer
aplayer
axios
docx-preview
dplayer
element-plus
highlight.js
js-md5
sass
sass-loader
spark-md5
vue-clipboard3
vue-cookies
vue-pdf-embed
vue-router
vue3-pdfjs
xlsx
--save
图片资源网站
可以免费下载图片: undraw
encodeURI & decodeURI
encodeURI()、encodeURIComponent()区别及使用场景
# 输出:http://www.baidu.com?query=a%201&car4(%5E
encodeURI('http://www.baidu.com?query=a 1&car4(^')
# 输出:http://www.baidu.com?query=a 1&car4(^
decodeURI('http://www.baidu.com?query=a%201&car4(%5E')
app.config.globalProperties与getCurrentInstance
// 挂载到全局
app.config.globalProperties.Request = Request
// 使用
import { getCurrentInstance } from "vue";
const {proxy} = getCurrentInstance()
proxy.Request(..)
Object.assign
Object.assign详解
vue-cookies
vue-cookies使用方法,vue中使用获取cookie
安装vue-cookies
npm i vue-cookies -D
使用vue-cookies
import VueCookies from 'vue-cookies'
// 存入
// 其实存入的时候就是 encodeURIComponent(JSON.stringify({'nickName':'zzhua'}))
// 可以使用JSON.parse(decodeURIComponent(encodeURIComponent(JSON.stringify({'nickName':'zzhua'})))) 解析出来
VueCookies.set('userInfo', {'nickName':'zzhua'})
// 获取
let userInfo = VueCookies.get('userInfo')
// 移除
VueCookies.remove('userInfo')
// 获取所有的key
VueCookies.keys()
// cookie中是否存在指定的key
VueCookies.isKey()
// httpOnly默认为true打对号√情况下,禁止javascript操作cookie,导致获取不到,可以让后端设置false;
router.currentRoute
获取当前的路由
preserve log
可以在浏览器的控制台勾选preserve log,在页面跳转时,可以保留记录页面跳转前的请求
import.meta.env
vue3+vite中开发环境与生产环境全局变量配置指南
Request.js
axios的response.config是什么(就是请求的时候传入的配置对象)
& response.config.responseType & axios的request中的config配置自定义属性(都是同一个config,并且可以手动往里面设置自定义属性)
axios的onUploadProgress
import axios from 'axios'
import { ElLoading } from 'element-plus'
import router from '@/router'
import Message from '../utils/Message'
const contentTypeForm = 'application/x-www-form-urlencoded;charset=UTF-8'
const contentTypeJson = 'application/json'
//arraybuffer ArrayBuffer对象
//blob Blob对象
//document Documnet对象
//json JavaScript object, parsed from a JSON string returned by the server
//text DOMString
const responseTypeJson = "json"
let loading = null;
const instance = axios.create({
baseURL: '/api',
timeout: -1,
});
//请求前拦截器
instance.interceptors.request.use(
(config) => {
if (config.showLoading) {
loading = ElLoading.service({
lock: true,
text: '加载中......',
background: 'rgba(0, 0, 0, 0.0)',
});
}
return config;
},
(error) => {
if (config.showLoading && loading) {
loading.close();
}
Message.error("请求发送失败");
return Promise.reject("请求发送失败");
}
);
//请求后拦截器
instance.interceptors.response.use(
(response) => {
// 这里的response.config是什么? todo, 感觉应该就是axios发起请求前传入的配置对象
const { showLoading, errorCallback, showError = true, responseType } = response.config;
if (showLoading && loading) {
loading.close()
}
const responseData = response.data;
// 获取到响应类型是response.config.responseType todo
if (responseType == "arraybuffer" || responseType == "blob") {
return responseData;
}
//正常请求
if (responseData.code == 200) {
return responseData;
} else if (responseData.code == 901) {
//登录超时
// 登录超时跳转到登录前, 对当前路径进行uri编码, 记录此路径到url上
router.push("/login?redirectUrl=" + encodeURI(router.currentRoute.value.path));
return Promise.reject({ showError: false, msg: "登录超时" });
} else {
//其他错误
if (errorCallback) {
errorCallback(responseData.info);
}
return Promise.reject({ showError: showError, msg: responseData.info });
}
},
(error) => {
// error里面也可以拿到config吗? todo
if (error.config.showLoading && loading) {
loading.close();
}
return Promise.reject({ showError: true, msg: "网络异常" })
}
);
const request = (config) => {
const { url, params, dataType, showLoading = true, responseType = responseTypeJson } = config;
let contentType = contentTypeForm;
let formData = new FormData();// 创建form对象
for (let key in params) {
formData.append(key, params[key] == undefined ? "" : params[key]);
}
if (dataType != null && dataType == 'json') {
contentType = contentTypeJson;
}
let headers = {
'Content-Type': contentType,
'X-Requested-With': 'XMLHttpRequest',
}
return instance.post(url, formData, {
onUploadProgress: (event) => {
if (config.uploadProgressCallback) {
config.uploadProgressCallback(event);
}
},
responseType: responseType, // responsType是axios中已定义的配置选项
headers: headers,
showLoading: showLoading,
errorCallback: config.errorCallback,
showError: config.showError
}).catch(error => {
console.log(error);
if (error.showError) {
Message.error(error.msg);
}
return null;
});
};
export default request;
热更新问题
路由中使用import导入的组件所写的路径一定要注意大小写,否则热更新会没用,见:vue3+vite热更新失效问题
vue3动态组件
vue3动态组件的is不能写字符串,要直接写组件的引用
vuex4命名空间
命名空间文档
二级菜单界面控制
defineExpose暴露组件的方法
图片url加随机数,防止浏览器缓存
Table封装,slot相关
Table.vue
<template>
<div>
<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"
>
<!--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>
<!--数据列-->
<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>
</el-table>
<!-- 分页 -->
<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="layout"
@size-change="handlePageSizeChange"
@current-change="handlePageNoChange"
style="text-align: right"
></el-pagination>
</div>
</div>
</template>
<script setup>
import { ref, computed } from "vue";
const emit = defineEmits(["rowSelected", "rowClick"]);
const props = defineProps({
dataSource: Object,
showPagination: {
type: Boolean,
default: true,
},
showPageSize: {
type: Boolean,
default: true,
},
options: {
type: Object,
default: {
extHeight: 0,
showIndex: false,
},
},
columns: Array,
fetch: Function, // 获取数据的函数
initFetch: {
type: Boolean,
default: true,
},
});
const layout = computed(() => {
return `total, ${
props.showPageSize ? "sizes" : ""
}, prev, pager, next, jumper`;
});
//顶部 60 , 内容区域距离顶部 20, 内容上下内间距 15*2 分页区域高度 46
const topHeight = 60 + 20 + 30 + 46;
const tableHeight = ref(
props.options.tableHeight
? props.options.tableHeight
: window.innerHeight - topHeight - props.options.extHeight
);
//初始化
const init = () => {
if (props.initFetch && props.fetch) {
props.fetch();
}
};
init();
const dataTable = ref();
//清除选中
const clearSelection = () => {
dataTable.value.clearSelection();
};
//设置行选中
const setCurrentRow = (rowKey, rowValue) => {
let row = props.dataSource.list.find((item) => {
return item[rowKey] === rowValue;
});
dataTable.value.setCurrentRow(row);
};
//将子组件暴露出去,否则父组件无法调用
defineExpose({ setCurrentRow, clearSelection });
//行点击
const handleRowClick = (row) => {
emit("rowClick", row);
};
//多选
const handleSelectionChange = (row) => {
emit("rowSelected", row);
};
//切换每页大小
const handlePageSizeChange = (size) => {
props.dataSource.pageSize = size;
props.dataSource.pageNo = 1;
props.fetch();
};
// 切换页码
const handlePageNoChange = (pageNo) => {
props.dataSource.pageNo = pageNo;
props.fetch();
};
</script>
<style lang="scss" scoped>
.pagination {
padding-top: 10px;
padding-right: 10px;
}
.el-pagination {
justify-content: right;
}
:deep .el-table__cell {
padding: 4px 0px;
}
</style>
FileList.vue
<div class="file-list">
<Table
:columns="columns"
:showPagination="true"
:dataSource="tableData"
:fetch="loadDataList"
:initFetch="false"
:options="tableOptions"
@rowSelected="rowSelected"
>
<template #fileName="{ index, row }">
<div
class="file-item"
@mouseenter="showOp(row)"
@mouseleave="cancelShowOp(row)"
>
<template
v-if="(row.fileType == 3 || row.fileType == 1) && row.status == 2"
>
<icon :cover="row.fileCover" :width="32"></icon>
</template>
<template v-else>
<icon v-if="row.folderType == 0" :fileType="row.fileType"></icon>
<icon v-if="row.folderType == 1" :fileType="0"></icon>
</template>
<span class="file-name" v-if="!row.showEdit" :title="row.fileName">
<span @click="preview(row)">{{ row.fileName }}</span>
<span v-if="row.status == 0" class="transfer-status">转码中</span>
<span v-if="row.status == 1" class="transfer-status transfer-fail"
>转码失败</span
>
</span>
<div class="edit-panel" v-if="row.showEdit">
<el-input
v-model.trim="row.fileNameReal"
:maxLength="190"
@keyup.enter="saveNameEdit(index)"
>
<template #suffix>{{ row.fileSuffix }}</template>
</el-input>
<span
:class="[
'iconfont icon-right1',
row.fileNameReal ? '' : 'not-allow',
]"
@click="saveNameEdit(index)"
></span>
<span
class="iconfont icon-error"
@click="cancelNameEdit(index)"
></span>
</div>
<span class="op">
<template v-if="row.showOp && row.fileId">
<span
class="iconfont icon-download"
@click="download(row)"
v-if="row.folderType == 0"
>下载</span
>
<span class="iconfont icon-del" @click="delFile(row)"
>删除</span
>
</template>
</span>
</div>
</template>
<template #fileSize="{ index, row }">
<span v-if="row.fileSize">
{{ proxy.Utils.sizeToStr(row.fileSize) }}</span
>
</template>
</Table>
</div>
vue3样式穿透
:deep .docx-wrapper > section.docx {
margin-bottom: 0px;
}
prop单向数据流
父组件传给子组件的属性,子组件不能直接改这个传过来的属性,但是如果这个属性值是个对象,是可以在子组件间中直接改这个对象中的属性的,这并不违背prop单向数据流
ref引用组件或dom 与 v-if/show
- v-if:当flag为true的时候,divRef是有值的;当flag为false的时候,divRef是undefined/null(刚开始是undefined,后面就是null了)
- v-show:divRef都有值
<template>
<div class="main">
Main
<el-button @click="flag = !flag">切换flag - {{ flag }}</el-button>
<el-button @click="logRef">获取divRef</el-button>
<div v-if="flag" ref="divRef">测试divRef</div>
</div>
</template>
<script setup>
import { ref,reactive } from 'vue'
let flag = ref(false)
const divRef = ref()
function logRef () {
console.log(divRef.value);
}
</script>
<style lang="scss">
</style>
@mouseenter & @mouseleave监听鼠标移入移出事件
el-table和el-input在一起,获取焦点方法失效问题
发现不能使用v-show,而是要使用v-if,猜测:我觉得应该是使用v-show的话,使用editNameRef就会引用到多个组件,而vue3中引入多个组件的用法在此中的源码的写法本身就不对,因此拿不到对应的组件,而使用v-if的话,就只有一个组件,因此能拿到唯一的组件,因此就能调用focus方法了
路由子组件通知父组件实现
Framework.vue中通过路由出口<router-view/>,展示了Main.vue组件,但是现在需要Main.vue组件中去调用Framework.vue组件中的方法,可以有以下几种做法
可以使用provide/inject的机制
Framework.vue
<template>
<div>
framework.vue
<router-view/>
</div>
<template>
<script>
import { ref, reactive, getCurrentInstance, nextTick, watch, provide } from 'vue'
provide('testFun',()=>{
console.log('framework testFun()..');
})
</script>
Main.vue
<template>
<div class="main">
<el-button @click="testFun">tt</el-button>
</div>
<template>
<script>
import { ref,reactive, nextTick, inject } from 'vue'
let testFun = inject('testFun')
</script>
可以直接在<router-view @kk=“kk”/>绑定事件监听
Framework.vue
<template>
<div>
framework.vue
<router-view @kk="kk"/>
</div>
<template>
<script>
import { ref, reactive, getCurrentInstance, nextTick, watch, provide } from 'vue'
const kk = () =>{
console.log('kk');
}
</script>
Main.vue
<template>
<div class="main">
<el-button @click="testKK">KK</el-button>
</div>
</template>
<script setup>
import { ref,reactive, nextTick, inject } from 'vue'
const emit = defineEmits([
'kk'
])
const testKK = ()=>{
emit('kk')
}
</script>
<style lang="scss"></style>
可以使用<router-view />的组件写法
Framework.vue
<template>
<div>
framework.vue
<router-view v-slot:="{Component,route}">
<component @kk="kk" :is="Component" :key="route.path"/>
</router-view>
</div>
<template>
<script>
import { ref, reactive, getCurrentInstance, nextTick, watch, provide } from 'vue'
const kk = () =>{
console.log('kk');
}
</script>
Main.vue
<template>
<div class="main">
<el-button @click="testKK">KK</el-button>
</div>
</template>
<script setup>
import { ref,reactive, nextTick, inject } from 'vue'
const emit = defineEmits([
'kk'
])
const testKK = ()=>{
emit('kk')
}
</script>
<style lang="scss"></style>
el-popver 点击触发和使用方法触发
- 下面2个按钮都能触发popver的显示
<template>
<div class="main">
<!-- 这里可以直接使用v-model:visible = popoverShow-->
<!-- 经过测试, 发现:点击reference的触发按钮,
当popover未显示的时候, 会触发update:visible事件(携带的参数是true);
当popover显示的时候,会触发update:visible事件(携带的参数是false) ,并且触发了2次
点击下面的触发按钮,
只有当popover未显示的时候,才会触发update:visible事件(携带的参数是false) ,并且触发了2次
-->
<el-popover :visible="popoverShow" @update:visible="value => popoverShow = value" placement="bottom" trigger="click">
<template #reference>
<el-button>触发</el-button>
</template>
<template #default>
halo~
</template>
</el-popover>
<el-button @click="popoverShow = true">触发</el-button>
</div>
</template>
<script setup>
import { ref,reactive, nextTick, inject } from 'vue'
const popoverShow = ref(false)
</script>
<style lang="scss">
</style>
文件上传逻辑转移
Main.vue组件(路由子组件)中负责选择文件,触发Framework.vue组件的addFile方法,并且把file这个blob传过来了,然后在Framework.vue组件中,主动的显示el-popver组件(el-popover的插槽中放入了Uploader.vue组件),最后调用这个Uploader.vue组件的上传方法(并且传入file这个blob),完成文件上传逻辑的转移。