搭建一个vite项目
https://vitejs.cn/guide/#scaffolding-your-first-vite-project
文章有点长,有写的不对的地方,请各位大佬指点,有问题一起讨论相互学习学习
创建一个空模板
//使用yarn
yarn create vitecd 进入项目
yarn //安装依赖包
yarn run dev //运行项目
使用模板创建项目
yarn create vite my-vue-app --template vue
安装element-plus
//安装
yarn add element-plus //引入
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'//注册
const app = createApp(App)
app.use(ElementPlus)
setup的使用
这个东西刚看是有点懵逼
setup有两种用法,一种是单文件组件
<template><p>{{msg}}<p>
</template>
<script setup>console.log('hello script setup')const msg = 'hello vue3';
</script>
使用对象方式
不使用在script标签上写的方式化就使用对象方式
<script>
export default {props: {title: String //接受父组件值},setup(props, ctx)
}
</script>
component组件的使用
不需要使用 components了,文件名也就是是组件名,无需使用name属性
<template><child />
</template><script setup>import child from './child.vue'
</script>
component简化组件的使用
无需手动import导入组件,在components写好组件,然后按需引入组件
方法
基于element-plus官网组件里推荐的按需导入的方式
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'import path from "path"// console.log(resolve)
// https://vitejs.dev/config/
export default defineConfig({plugins: [vue(),Components({// 用于搜索组件的目录的相对路径。dirs: ['src/components'],resolvers: [ElementPlusResolver()],// 生成 `components.d.ts` 全局声明,// 也接受自定义文件名 dts: 'src/components/components.d.ts'dts的路径:false , //非必填项})],})
使用keep-alive 缓存
vue3
官网提供了include、exclude、max参数,官网keep-alive文档:https://v3.cn.vuejs.org/api/built-in-components.html#keep-alive
注意
include和exclude匹配首先检查组件自身的 name 选项,如果 name 选项不可用,则匹配它的局部注册名称( 父组件 components 选项的键值),匿名组件不能被匹配
该name是指组件的name,并不是vue-router里配置的name
<template><div class="app-main"><router-view v-slot="{Component}"><keep-alive><component :is="Component" /></keep-alive></router-view></div>
</template><script setup>
</script>
已经取消keep-alive的api
<keep-alive><router-view></router-view></keep-alive>
计算属性computed
在script标签里获取返回的值,记住一点要使用 .value ,要解构也是在拿到value进行解构
import { computed } from 'vue'const val = computed(() => store.state.user.token)//要拿到具体值
const app = computed(() => store.getters.app)
console.log(app.value, app.value.sidebar.opened)
侦听器watch
import { reactive, watch } from 'vue'const formData = reactive({userName: '',password: ''
})
watch(formData, (newVal,oldVal) => {console.log(newVal, newVal.password, oldVal, oldVal.password)
})
reactive 代替 data函数
<script setup> 的规范是没有data函数这个概念
import { reactive } from 'vue'//使用
const formData = reactive({userName: '',password: ''
})
ref 基本变量
<script setup> 的规范是没有data函数这个概念
import { ref } from 'vue'//使用
const user = ref('start')
生命钩子函数
导入钩子函数
通过import {} from 'vue' 进行导入指定的钩子函数
使用钩子函数
//使用方式1
<script setup>
import { onBeforeMount} from 'vue'
onBeforeMount(() => {console.log('实例挂载前调用:onBeforeMount')
})
</script>//使用方式2
<script>
export default {setup() {onMounted(() => {console.log('Component is mounted!')})}
}
</script>
vue3钩子函数
注意:函数变动
尤大神介绍是moun比Destroy更形象
beforeDestroy 换成了onBeforeUnmount
destroyed 换成了 onUnmounted
<template><el-input v-model="inputVal"></el-input>
</template><script setup>
import {ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted, onErrorCaptured, onActivated, onDeactivated, onRenderTracked, onRenderTriggered} from 'vue'const inputVal = ref('')console.log('setup围绕beforeCreate和created生命钩子函数执行的,无需显示定义')onBeforeMount(() => {console.log('实例挂载前调用:onBeforeMount')
})onMounted(() => {console.log('实例挂载后调用:onMounted')
})onBeforeUpdate((val) => {console.log('数据更新前调用钩子函数', 'onBeforeUpdate',val)
})onUpdated(() => {console.log('数据更新后调用钩子函数', 'onUpdated')
})onBeforeUnmount(() => {console.log('在卸载实例之前调用', 'onBeforeUnmount')
})onUnmounted(() => {console.log("卸载组件实例前调用", 'onUnmounted')
})onErrorCaptured((err) => {console.log('捕获来自子孙组件的错误调用', 'onErrorCaptured')
})onRenderTracked(({ key, target, type }) => {console.log({ key, target, type })console.log('跟踪虚拟DOM重新渲染调用')
})onRenderTriggered(({ key, target, type }) => {console.log({ key, target, type })console.log('当寻DOM重新渲染为triggerd.Similarly为 renderTracked')
})onActivated(() => {console.log('keep-alivie缓存的组件激活是调用,服务端渲染不调用')
})
onDeactivated(() => {console.log('被 keep-alivie 缓存的组件停用时调用,服务端渲染不调用')
})
</script>
defineExpose将子组件数据暴露
将子组件的属性暴露出去
child
<script setup>
import {ref} from 'vue'const a = ref('我是子组件导出值a')
const b = ref('我是子组件导出值b')defineExpose({a,b
})
</script>
Parent
需要给子组件声明一个ref属性,然后再setup中声明变量,再钩子函数中读取
<template><p>父组件</p><defineExposeChild ref="childaa" />
</template><script setup>
import { ref, onMounted } from 'vue'import defineExposeChild from './defineExposeChild.vue'let childaa = ref(null);onMounted(() => {console.log(childaa.value.a)console.log(childaa.value.b)console.log(childaa.value.c); //未暴露的返回undefined
})
</script>
reactive 响应式赋值给element
因为不能想vue2那样使用 this了,现在用的const 和 let 又不能直接让接收 reactive函数返回变量改变指向;所以:方法很简单,修改变量的引用里的内容就可以,只要对象没有被冻结
const roleInfo = reactive({data: { xxx: 'ooo' }})
//使用ref
const roleInfo = ref({xxx: 'ooo'})//更新时:
roleInfo.data = { xxx: 'aaa' }//更新:
roleInfo.value = { xxx: 'aaa' }
实际应用
<template><el-table :data="departmentDataList.dataList"row-key="deptId"border></el-table>
</template><script setup>
import {reactive} from 'vue'// 准备表单数数据
const departmentDataList = reactive({dataList: []})//部门树型列表
deptList({}).then(res => {// 将el-table的数据修改成接口返回的数据departmentDataList.dataList = res.data.data
}, err => {console.log('部门树型列表', err)
})
</script>
废弃的过滤器最新使用
官网已取消过滤器,转而用计算属性替换过滤器
新写法:直接在官网就可以 https://v3.vuejs.org/guide/migration/filters.html#global-filters
<script>
// main.js
const app = createApp(App)app.config.globalProperties.$filters = {currencyUSD(value) {return '$' + value}
}
</script><template><h1>Bank Account Balance</h1><p>{{ $filters.currencyUSD(accountBalance) }}</p>
</template>
原型链绑定和组件使用
main.js || main.ts
//创建vue 实例
const app = createApp(App)
// 获取原型
const prototype = app.config.globalProperties
//绑定参数
porottype.name = '我是挂载的全局的属性'
//组件内获取使用//引入
import { getCurrentInstance } from "vue";
// 获取原型
const { proxy } = getCurrentInstance();
// 输出
console.log(proxy.name);
vue3监听路由变化
watch(() => router.currentRoute.value.params,(params) => {console.log('watch监听路由变化', params)},{ immediate: true }
)
vue3.0中globalProperties的定义以及 globalProperties定义的字段在setup中用法
import { getCurrentInstance } from 'vue'
const { proxy } = getCurrentInstance()
console.log('打印全局', proxy.$filters)
sass的使用
yarn add sass sass-loader --dev
element-plus采坑
slot-scope 语法废弃
<el-table-column prop="deptName" label="部门名称"><template #default="{row}">{{row.deptName}}</template>
</el-table-column>
Element puls 设置中文的方法
src/app.vue 引入包给 el-config-provider 配置就可以了
<template><el-config-provider :locale="zhCn"><Vab-App /></el-config-provider>
</template>
<script setup>import zhCn from 'element-plus/lib/locale/lang/zh-cn'
</script>
Vue3采坑
父子组件传值
父组件
child子组件 ,无需使用components了,文件名即组件名
@setTitle是子组件定义的emit事件名用来修改父组件值
:title=“title” 传给子组件的值,跟v2是一样的
<template><child @setTitle="setTitleHe" :title="title" /><el-alert title="我是父组件" type="success" /><el-input v-model="title" placeholder="Please input" /><p>f父组件值:{{title}}</p>
</template><script setup>
import {ref} from 'vue'
import child from './child.vue'let title = ref('start1121')const setTitleHe = () => {console.log(val)title.value = val;
}
</script>
子组件
defineEmits,defineProps无需导入,直接使用
defineProps 定义接受父组件值,参数为对象,对象里的key,value就是接受的值和类型,返回值是对象,你可以使用es6中解构
向父组件传值有些不同:需要先注册抛出的事件名:defineEmits([‘setTitle’]),再通过返回的方法去执行这个事件:emits(‘setTitle’, ‘修改了父组件值’) // 向父组件传递数据。
<template><div @click="toEmits">Child Components</div><p>父组件值:{{title}}</p>
</template><script setup>
// defineEmits,defineProps无需导入,直接使用
// 需要声明 emit
const emits = defineEmits(['setTitle'])
const {title} = defineProps({title: {type: String,defaule: 'defaule title'}
});const toEmits = () => {emits('setTitle', '修改了父组件值') // 向父组件传递数据
}// 获取父组件传递过来的数据
console.log(title); // parent value
</script>
也可以defineEmits([‘setTitle’, ‘setContent’]) 注册多个事件,无需每次单独组成
const emits = defineEmits(['setTitle', 'setContent'])
const toEmits = () => {emits('setTitle', '修改了父组件值') // 向父组件传递数据
}const setCon = () => {emits('setContent', '修改了父组件内容') // 向父组件传递数据
}
bug
通过emits修改父组件值之前一定要先通过defineEmits注册定义抛出的事件名,否则会弹警告,警告和错误都要修改!!!!
注意
一定要现在注册再使用,别把注册的步骤省略了,我当时傻乎乎的直接将注册和使用合并了了,错误了
const emits = defineEmits(['setTitle', 'setContent'])、
const toEmits = () => {emits('setTitle', '修改了父组件值') // 向父组件传递数据
}
当时省略成了,defineEmits就弹出不存在的错误了
defineEmits(['setTitle', 'setContent']);
defineEmits’ is not defined
.eslintrc.js
module.exports = {env: {'vue/setup-compiler-macros': true}
}
element-plus 打包过大
ome chunks are larger than 500 KiB after minification. Consider:
好家伙:刚创建的项目,引入个ui库,打包就超过500kb了,项目大了还得了
解决
使用按需导入element-plus
//组件按需导入
npm install -D unplugin-vue-components unplugin-auto-import//安装vite-plugin-style-import 组件样式按需导入
yarn add vite-plugin-style-import -D
vite.config.js
//批量导入模块
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'import styleImport from 'vite-plugin-style-import';// https://vitejs.dev/config/
export default defineConfig({plugins: [vue(),//按需导入组件AutoImport({resolvers: [ElementPlusResolver()],}),Components({resolvers: [ElementPlusResolver()],}),//按需导入组件样式styleImport({libs: [{libraryName: 'element-plus',esModule: true,resolveStyle: (name) => {return `element-plus/theme-chalk/${name}.css`;},}]})],
})
无法识别 @ scr目录
解决:vite.config.js
import path from "path";
export default defineConfig({resolve: {alias: {//配置src目录"@": path.resolve(__dirname, "src"),//导入其他目录"components": path.resolve(__dirname, "src/components")}},plugins: [vue()]
});
或者数组的形式
import path from "path";
export default defineConfig({resolve: {alias: [{find: '@',replacement: path.resolve(__dirname, "src"),},{find: 'components',replacement: path.resolve(__dirname, 'src/components')}]},plugins: [vue()]
});
element-plus表单无法输入
el-form 的model 中的 ruleForm 跟 ref 属性的名字冲突了,ref属性换其他的名称,model的变量不变就可以了,例如 loginRefrule
<template><div class="login"><div class="container"><el-form ref="ruleForm" :model="ruleForm" label-width="120px" class="demo-ruleForm"><el-form-item label="用户名" prop="name"><el-input v-model="ruleForm.name"></el-input></el-form-item><el-form-item label="密码" prop="password"><el-input v-model="ruleForm.password"></el-input></el-form-item><el-form-item><el-button type="primary">Create</el-button><el-button >Reset</el-button></el-form-item></el-form></div></div>
</template><script setup>
import {reactive } from "vue";
const ruleForm = reactive({
})const getForm = () => {
}
</script>
解决
只有 ref 属性的值跟 model 的变量不一样即可
<el-form ref="loginRefRule" :model="ruleForm">
</el-form>
vue3.0 子组件修改父组件传递过来的值
vue 的子组件 不是 不能直接更改父组件的值,需要将props 的值 给到子组件,后再修改,
否则:Unexpected mutation of “props” prop.
vue3提供了一个解决:toRefs:https://v3.cn.vuejs.org/api/refs-api.html#torefs
toRefs
非常有用,这样消费组件就可以在不丢失响应性的情况下对返回的对象进行解构/展开
没又必要使用compute 和 watch来监听改变
使用
const { foo, bar } = reactive({foo: 1,bar: 2
})// 核心解决, 使用reactive接收不会响应时更新,需要使用toRefs
const props = defineProps({drawerStatus: {type: Boolean}
})
const {drawerStatus} = toRefs(props)
使用toRefs进行解决
<template><el-drawer v-model="drawerStatus" title="设置部门助理" :before-close="handleClose"><div class="drawer-footer"><el-button>取消</el-button><el-button type="primary" @click="onSubmit">确定</el-button></div></el-drawer></template><script setup>
import {reactive, toRefs} from 'vue'const props = defineProps({drawerStatus: {type: Boolean}
})const emits = defineEmits(['upDrawerStatus'])const {drawerStatus} = toRefs(props)console.log(33333333, drawerStatus)// 新增角色数据
const formData = reactive({
})const onSubmit = () => {handleClose()
}
const handleClose = () => {console.log('关闭抽屉')emits('upDrawerStatus', false)
}
</script>
vue3.0 子组件修改父组件传递过来的值优化vue3 Unexpected mutation of “childFormData” prop
超级简单版,父组件只要传入el-form的对象,子组件接收父组件的props既可完成
父组件
父组件将 form表单的 popFormData传递给子组件
<!--el-form 父组件-->
<el-formref="ruleForm"label-position="top"label-width="110px":model="popFormData":rules="rules"style="padding: 20px"> <!--封装的子组件--><business-module :child-form-data="popFormData" />
</el-form>
子组件
子组件定义一个 viewsData 对象接收父组件传过来的值:formData: props.childFormData,然后el-select中使用即可;
示例中的@change=“chooseBusinessHandle” 选择后为父组件添加值或者修改值并不会报错:(vue3 Unexpected mutation of “childFormData” prop vue3“childFormData”属性意外突变)
如果还想为子组件添加验证:为子组件添加 prop 属性和 rules 属性即可。
<template><el-form-itemlabel="业务模块":prop="'formData.' + '.businessModule'":rules="{required: true,message: '请选择业务模块',trigger: ['blur', 'change'],}"><el-selectv-model="formData.businessModule"class="filter"clearablefilterable@change="chooseBusinessHandle"><el-optionv-for="item in businessList":key="item.id":label="item.businessName":value="item.id"/></el-select>{{ formData }}</el-form-item>
</template><script>import { reactive, toRefs } from 'vue'export default defineComponent({props: {childFormData: {type: Object,default() {return {}},},},setup(props) {// 视图数据const viewsData = reactive({formData: props.childFormData,})/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *//* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */// 视图操作const viewsOperate = {chooseBusinessHandle() {viewsData.formData.companyShortName = 666},}return {...toRefs(viewsData), // 视图数据...toRefs(viewsOperate), //视图操作}},})
</script><style lang="scss" scoped>.filter {width: 100%;}
</style>
效果
父组件点击确认的时候子组件也会跟着验证(实现),不需要通过emit告诉父组件去验证了
子组件值改变的时候 子组件会修改父组件传递过来的值,提示代码也看到了值的变动
修改element-plus组件样式
vue中使用 /deep/ 不生效
解决:使用::v-deep(.el-drawer) 替换深度选择器后生效【必须加括号】
::v-deep(.el-drawer) {background: #fff;}
不加报错
[@vue/compiler-sfc] ::v-deep usage as a combinator has been deprecated. Use :deep() instead.
自定义校验规则无效
// 表单校验
const validatetaxNo = (rule, value, callback) => {var reg = /^[0-9A-Z]{15,18}$/gif (!value) {callback(new Error('税号不能为空'))} else if (!reg.test(value)) {callback(new Error('税号格式不正确!'))} else {callback()}
}const rules = reactive({taxNo: [{required: true,trigger: 'blur',validator: validatetaxNo,},],
})
vue3 el-upload 失败了还会出现在文件列表
:on-change="触发"
if (!viewsData.fileTypeList.includes(file.raw.type)) {ElMessage.error('请上传JPG或PNG格式的图片')return false
}
为什么不是使用 before-upload 钩子函数呢
但是在对上传的文件做大小限制或者格式限制时,发现before-upload方法并不生效,查阅资料后发现:因为 before-upload 是指在文件上传之前、文件已被选中,但还没上传的时候触发,而设置了 :auto-upload=“false” 后,文件上传事件不被再次调用,,所以 before-upload 不生效,所以,限制图片大小和格式的时候,需绑定在 :on-change 里面,这样限制就生效了
问题来了,明明在 on-change return false 文件还是会显示在文件列表
解决:
if (!viewsData.fileTypeList.includes(file.raw.type)) {ElMessage.error('请上传JPG或PNG格式的图片')fileList.pop()return false
}
vue项目 设置scrollTop不起作用
<template><div class="addBoxMobile"style="padding: 10px; overflow-y: auto; height: 100%"></div>
</template><script>
//实现每次动态新增将位置定位在最后一个添加的位置上
document.getElementsByClassName('addBoxMobile')[0].scrollTop = appListRef.value[appListRef.value.length - 1].offsetTop
</script>
vite 插件使用require的common 报错: require is not defined?
vite踩上了有插件使用require的common规范的坑
解决:
因为:vite,基本不识别老插件的require规范了,要用vite的方式
Vite 共享配置
resolve.alias 配置系统路径别名
resolve: {alias: {"@": path.resolve(__dirname, "src"),"~": path.resolve(__dirname, "src/components")}
}
resolve.extensions 配置忽略的扩展名
字符串数组类型 : [‘.js’, ‘.ts’]等等,加上vue后缀名基础的上追加:[‘.mjs’, ‘.js’, ‘.ts’, ‘.jsx’, ‘.tsx’, ‘.json’, ‘.vue’]
vite不建议引入时忽略.vue扩展名,后期新版本的vue-cli也将 不支持忽略扩展名
尤神在github对不支持的回答 https://github.com/vitejs/vite/issues/178
resolve: {extensions: {['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue']}
}
Vite 配置开发服务配置
vite.config.js : https://vitejs.dev/config/
server.host ip地址
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'// https://cn.vitejs.dev/config/#server-host
// server.host 设置为 0.0.0.0 或者 true 将监听所有地址
export default defineConfig({plugins: [vue()],server: {host: true,}
})
server.port 开发端口
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'// https://cn.vitejs.dev/config/#server-host
// server.host 设置为 0.0.0.0 或者 true 将监听所有地址
export default defineConfig({plugins: [vue()],server: {host: true,port: 9527}
})
server.proxy开发端口
import path from "path"server: {proxy: { //配置代理'/api': {target: 'http://47.106.135.141:8092', // 所要代理的目标地址rewrite: path => path.replace(/^/api/, ''),changeOrigin: true, }}}
vite操作bug
打包问题
使用vite打包的是出现 rendering chunks(30)一直卡住不动
github解决方案
将vite的版本恢复到 2.5.10就可以了
eslint
安装
// vsCode 安装eslint扩展// 全局安装
npm i -g eslint babel-eslint//项目安装eslint依赖项
yarn add eslint babel-eslint eslint-plugin-vue -D//安装npx
npm install -g npx//在项目里同过npx 初始化eslint配置【按选项配置】
npx eslint --init
ESLint vs code 配置
打开插件 =====》点击扩展设置
添加配置
每次在保存的时候就可以根据 .eslintrc.js 文件里你配置的ESLint规则 来处理不符合规则的代码;
例如:配置了禁止分号,代码写了分号,保存时自动将分号干掉。
代码
eslintrc里有对应规则配置和解释
.eslintignore 文件
/node_modules/
/public/
.vscode
.idea
.eslintrc.js
module.exports = {"env": {"browser": true,"es2021": true,"node": true},"extends": ["eslint:recommended","plugin:vue/essential"],"parserOptions": {"ecmaVersion": 13,"sourceType": "module"},"plugins": ["vue"],/*** 这个选项是配置eslint 的验证规则的对象,[进入链接随便点击一个就是这个路径的配置]* https://eslint.bootcss.com/docs/rules/*/"rules": {"semi": [2, 'never'],"no-multiple-empty-lines": ["error", {"max": 2,"maxEOF": 1}],"for-direction": "error",/*** eslint-plugin-vue 对vue的配置规则*/// https://eslint.vuejs.org/rules/multi-word-component-names.html'vue/multi-word-component-names': [ "error", {// ignores要忽略的组件名称。将组件名称设置为允许, 其他非驼峰命名则错误ignores: ['index']}],}
}
过程问题
module’ is not defined
"env": {"node": true // 这个配置让文件支持 module.exports ,否则会 'module' is not defined.
},
使用方式
其中 配置文件中使用 extends": "eslint:recommended 来启用推荐的规则,再安装是初始化的配置项就启动了
// "eslint:recommended" 初始化时就开启了
module.exports = {"extends": ["eslint:recommended",],}
例如点击 for-direction
其中注释就写好语法了
"rules": {"for-direction": "error",}
BUG
Cannot find module ‘babel-eslint’ 找不到模块 ‘babel-eslint’
解决:
npm i -g eslint babel-eslint
vue-router4x
router的使用
安装
npm info vue-router versions 查看vue-router的版本然后下载最新的
//从这个版本信息中获取到版本号去安装
npm install vue-router@4.0.3
scr目录
- src|- router| |- index.js|- views|- Home.vue|- Contact.vue
index.js
import { createRouter, createWebHistory } from 'vue-router'// 开启历史模式
// vue2中使用 mode: history 实现
const routerHistory = createWebHistory();const router = createRouter({history: routerHistory,routes: [{path: '/',redirect: '/home'}]
})export default router
router钩子函数
router 404 配置
核心代码:vue-router3x 的跳转 不能用 * 去匹配会报错: Catch all routes ("") must now be defined using a param with a custom regexp.【捕获所有路由(“”)现在必须使用带有自定义正则表达式的参数来定义。】
解决:在router中添加路由,path的地址:/:catchAll(.*)
import {createRouter, createWebHashHistory} from 'vue-router'
const routes = [{path: "/",component: () => import("@/layout/index.vue"),redirect: "/home",children: [{path: "/roleManage",name: "roleManage",icon: "",meta: {title: '用户管理'},component: () => import(/* webpackChunkName: "roleManage" */"@/views/role-manage/index.vue")}] },{path: '/error/404',name: '404',meta: {title: '找不到页面'},component: () => import(/* webpackChunkName: "404" */ '../views/error/404.vue')},// 核心代码:path: '/:catchAll(.*)',{path: '/:catchAll(.*)',ame: 'error404',redirect: '/error/404'}
]
const router = createRouter({history: createWebHashHistory(),routes
})
export default router
vuex4x
vuex安装引入
安装
yarn add vuex@next --save
scr目录下store目录
- store|- modules|- user.js|- index.js
index.js
import { createStore } from 'vuex'//import.meta.globEager 导入modules下的所有js文件
const files = import.meta.globEager('./modules/*.js')const modules = {};
//提取modules文件下js导出的内容
for (const key in files) {if (Object.prototype.hasOwnProperty.call(files, key)) {modules[key.replace(/(./|.js)/g, '').replace('modules/', '')] = files[key].default}
}
//创建store
const store = createStore({modules
})export default store;
user.js,这倒是跟v2没多大区别
const state = {token: '我是token',name: ''
}const mutations = {
}const actions = {
}const getters = {
}export default {namespace: true,state,mutations,actions,getters
}
main.js,也跟v2没啥区别
import store from './store'const app = createApp(App);
app.use(store)
login.vue,使用store
先从vuex导出 useStore ,在使用 useStore函数返回的值
<template><div class="content"><p>呵呵:{{val}}</p></div>
</template><script setup>
import { computed } from 'vue'
import { useStore } from 'vuex'const store = useStore()
const val = computed(() => store.state.user.token)
</script>
vuex5 -> Pinia
vue3 钉钉扫码登录 最新解决方案
主要是使用方法和踩坑吧,坑踩的挺大,有vue3一起踩坑的可以相互讨论哈
注意看注释,很多注意点在注释和总结那里
往index.html 添加引入
<script src="https://g.alicdn.com/dingding/dinglogin/0.0.5/ddLogin.js"></script>
其他代码
// api。js 可忽略
// 扫码登录
export function scanCode (params) {return request({url: '/auth',method: 'get',params})
}
login.vue 代码
算个示例吧:核心实现代码
//往登录页面放一个div id为login_container
<div style="" class="qrCode" id="login_container">
</div><script setup>
import {reactive, ref, onMounted, watch } from "vue"
import {useRouter, useRoute} from "vue-router"
import {useStore} from 'vuex'import { scanCode } from '@/api/login'let goto = encodeURIComponent('https://oapi.dingtalk.com/connect/oauth2/sns_authorize?appid=appid&response_type=code&scope=snsapi_login&state=STATE&redirect_uri=你的项目vue项目地址或者你发版后页面的地址记得在钉钉回调url一致否则无法登录')
let tmpAuthCode = window.location.search.split("&")[0].split("=")[1]
onMounted(() => {DDLogin({id: "login_container", //这里需要你在自己的页面定义一个HTML标签并设置id,例如<div id="login_container"></div>或<span id="login_container"></span>goto: goto, //请参考注释里的方式style: "border:none;background-color:#FFFFFF;",width: "270",height: "370"})let handleMessage = function (event) {let origin = event.originconsole.log("origin", event.origin)if ( origin == "https://login.dingtalk.com" ) { //判断是否来自ddLogin扫码事件。let loginTmpCode = event.data //获取到loginTmpCode后就可以在这里构造跳转链接进行跳转了//将loginTmpCodeloginTmpCode拼接到url末尾用于跳转,不要open打开新的标签window.location.href = decodeURIComponent(goto + "&loginTmpCode=" + loginTmpCode) }}if (typeof window.addEventListener != 'undefined') {if (tmpAuthCode){//参数要用这种方式,不要直接拼接url,后端拿不到,这个坑也踩过了store.dispatch('scanCodeLogin', {code: tmpAuthCode,state: 'auth'}).then(res => {console.log('成功', res)window.location.href = 'http://192.168.0.235:2021/' ;// 这个地址要跟redirect_uri的地址一样,不然你跳转后请求后获取的用户信息的丢在redirect_uri的地址了,你肯定拿不到了,记住这个地址要得是redirect_uri的地址,redirect_uri的地址一点得是你项目的地址,不然会有热更新刷新问题【每隔一段时间就会自动刷新页面问题】}, err => {console.log('失败', err)}) }window.addEventListener('message', handleMessage, false)} else if (typeof window.attachEvent != 'undefined') {window.attachEvent('onmessage', handleMessage)}
})
</script>
钉钉端配置
redirect_uri的地址配置前端的vue启动的地址,红的的就是
问题总结
redirect_uri地址必须跟更vue启动的地址一致,也就是说必须 localhost+端口 :http://localhost:2021/、你本地的ip+端口:http://192.168.1.215:2021/;
redirect_uri 的地址必须通过encodeURIComponent进行处理哦
1、踩过使用后端穿透问题,有问题会出现302重定向的问题【就是登录后跳转redirect_uri,再去拿后端的接口不现实,因为redirect跳转是后端的地址前端控制不了】pass了;
2、前端使用钉钉穿透后,把穿透的地址给到后端放到钉钉的回调url里,这样确实是扫码登录后可以打开vue的项目,但是!!!一旦你将vue的穿透地址放到钉钉回调url【:也就是redirect_uri的地址】后有个非常非常严重的问题:【就是自动刷问题,一分钟页面自动刷新:罪魁祸首是 vite 的热更新功能,为什么跟热更新有关呢:因为vite热更新:真的这么智能:改下代码就刷新页面?不是的,是有轮训机制,定时去检测代码,检测的同时发现打开的域名【也就是穿透的地址】跟vue启动的域名不一致,vite始终匹配不上:就会触发window.location.reload()重新刷新,一直不匹配就一直不刷新】;
2的解决方法就是:localhost+端口 :http://localhost:2021/、你本地的ip+端口:http://192.168.1.215:2021/;localhost就没必要使用。配置ip就可以了
这种情况占用问题,昨天还好好的今天就穿透不了的问题:解决方法是【其实已经不用穿透了,在钉钉开发者平台上配置回调redirect_uri地址就是前端vue项目启东的地址就可以,然后前端就没必要穿透了】
//这种穿透的端口是vue项目启动后的端口
//将abcde的换掉就行:ding -config=ding.cfg -subdomain=start 9527
cd windows_64
ding -config=ding.cfg -subdomain=abcde 8080
问题效果:
****这一行错误
Version 1.7/1.7
Forwarding 127.0.0.1:4044
Forwarding 127.0.0.1:4044
Web Interface 127.0.0.1:4040
# Conn 0
Avg Conn Time 0.00ms
还有就是后端接口的:
http://zhangsan.vaiwan.com 这个接口地址是后端用花生壳穿透的,不要用ip
service.interceptors.request.use((config) => {if (store.getters.token){config.headers['Authentication'] = getCookie('token')} else {console.log(config)if (config.url.includes('/auth')){config.baseURL = 'http://zhangsan.vaiwan.com'}}console.log("拦截", config)return config
}, (error) => {return Promise.reject(error)
})
定位问题步骤总结
首先通过打包,确定开发环境是否有问题,还是只在运行项目的时候才有定时重载页面问题;定位到打包后不会刷新;
这个问题触及:vite 和更新源码了:vite 原理:HMR 和 Live Reload
更加vite 热更新原理,webSocket 来进行连接,重载主要在 handleHMRUpdate 负责代码的更新逻辑,当检测文件无法热更新的时候,只能全部加载也就是刷新了,但是这种的缺陷vite也没有做掐断机制以及报错。
webpack
webpack-dev-server配置了changeOrigin依然无效的解决方案
webpack-dev-server配置了changeOrigin依然无效的解决方案,出现这种情况时因为changeOrigin=true时,底层只会将request-header中的host字段修改,并不会修改Origin字段,同时会在request-header中添加x-forwarded相关字段传递代理前的信息,部分站点会获取x-forwarded信息做来源检测,使用以下配置绕过以上问题:
devServer: {disableHostCheck: true,proxy: {'/api': {target: 'http://www.marklion.cn/',changeOrigin: true,pathRewrite: { '^/api': '/' },headers:{//改写Origin,注意结尾不含 / Origin:"http://www.marklion.cn",//改写RefererReferer:"http://www.marklion.cn/",},"onProxyReq": (request, req, res) => {//移除x-forwarded相关headerrequest.removeHeader('x-forwarded-host');request.removeHeader('x-forwarded-proto');request.removeHeader('x-forwarded-port');request.removeHeader('x-forwarded-for');}}}
}
移动端兼容问题
ios 数据不回显问题
<divclass="addCustomerCom":class="{ iosStyle: dataAll.judgeClient() == `IOS` }"></div>
.iosStyle {/* 解决数据不回显问题 */input:disabled,input[disabled] {-webkit-opacity: 1 !important;opacity: 1 !important;color: #666 !important;webkit-text-fill-color: #666 !important;}:deep(input:disabled) {-webkit-opacity: 1 !important;opacity: 1 !important;color: #666 !important;webkit-text-fill-color: #666 !important;}:deep(input[disabled]) {-webkit-opacity: 1 !important;opacity: 1 !important;color: #666 !important;webkit-text-fill-color: #666 !important;}}
el-select ios 上无法拉起 键盘
el-select 再添加 filterable属性支持输入搜索时,在ios上无法拉起键盘
解决
<el-selectref="selectRef"v-model="item.appId"clearable:filter-method="searchAppName"filterableplaceholder="请输入产品"@hoot="setFocus"@visible-change="setFocus">
js代码
import { reactive, ref, nextTick, onMounted } from 'vue'
const selectRef = ref(null)
const { proxy } = getCurrentInstance() //获取全局setFocus: (value) => {nextTick(() => {if (!value) {setTimeout(() => {viewsOperate.delRead()}, 700)}})
},delRead: () => {const { selectRef } = proxy.$refsconsole.log(selectRef[0])if (selectRef[0]) {const input = selectRef[0].$el.querySelector('.el-input__inner') //这里需要打印出dom元素才算正常,如果拿不到使用原生的 document.getElementByIdif (input) {console.log(input)input.removeAttribute('readonly')}}},
ios 文件上传失败,安卓可以上传
错误
在进行文件上传后台报错
在使用axios进行文件上传时,后台 报500 :org.springframework.web.multipart.MultipartException: Current request is not a multipart request
经过排查不是后台程序的问题,在前端上传的时候对数据进行了处理
明明安卓可以上传,弹出的是后端的错误,这种情况,需要要抓包
原因
错误上传文件参数【参数都已经不是file了】
正常上传文件参数
解决
// 上传附件或者图片
export function uploadBatch(data = {}) {return request({url: '/uploadBatch',method: 'post',data,transformRequest: [], //防止被篡改,是过滤参数用的,防止钉钉篡改参数headers: { //header也要加上'Content-Type': 'multipart/form-data',},})
}