当前位置: 首页 > news >正文

上海网站建设哪培训网页设计吗

上海网站建设哪,培训网页设计吗,本地服务器 wordpress,郑州官网制做Vue3 vite Ts pinia 实战 源码 electron 仓库地址#xff1a;https://gitee.com/szxio/vue3-vite-ts-pinia 视频地址#xff1a;小满Vue3#xff08;课程导读#xff09;_哔哩哔哩_bilibili 课件地址#xff1a;Vue3_小满zs的博客-CSDN博客 初始化Vue3项目 方式一 …Vue3 vite Ts pinia 实战 源码 electron 仓库地址https://gitee.com/szxio/vue3-vite-ts-pinia 视频地址小满Vue3课程导读_哔哩哔哩_bilibili 课件地址Vue3_小满zs的博客-CSDN博客 初始化Vue3项目 方式一 npm init vitelatest生成的目录结构 vite-demo ├── .vscode │ └── extensions.json ├── public │ └── vite.svg ├── src │ ├── assets │ │ └── vue.svg │ ├── components │ │ └── HelloWorld.vue │ ├── App.vue │ ├── main.ts │ ├── style.css │ └── vite-env.d.ts ├── README.md ├── index.html ├── package.json ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts启动 npm run dev方式二 npm init vuelatest生成的目录结构 vue-demo ├── .vscode │ └── extensions.json ├── public │ └── favicon.ico ├── src │ ├── assets │ │ ├── base.css │ │ ├── logo.svg │ │ └── main.css │ ├── components │ │ ├── __tests__ │ │ ├── icons │ │ ├── HelloWorld.vue │ │ ├── TheWelcome.vue │ │ └── WelcomeItem.vue │ ├── router │ │ └── index.ts │ ├── stores │ │ └── counter.ts │ ├── views │ │ ├── AboutView.vue │ │ └── HomeView.vue │ ├── App.vue │ └── main.ts ├── .eslintrc.cjs ├── .prettierrc.json ├── README.md ├── env.d.ts ├── index.html ├── package.json ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.node.json ├── tsconfig.vitest.json ├── vite.config.ts └── vitest.config.ts用这种方式生成的项目会全一点 启动 npm run dev自动生成路由 添加 gen-router.js 文件 var fs require(fs); const readline require(readline); const os require(os);const vueDir ./src/views/;fs.readdir(vueDir, function (err, files) {if (err) {console.log(err);return;}let routers ;// 对文件进行排序let sortFiles files.sort((a,b){return a.split(_)[0] - b.split(_)[0]});for (const filename of sortFiles) {if (filename.indexOf(.) 0) {continue;}var [name, ext] filename.split(.);if (ext ! vue) {continue;}let routerName nullconst contentFull fs.readFileSync( ${vueDir}${filename}, utf-8 );var match /\\!\-\-\s*(.*)\s*\-\-\/g.exec(contentFull.split(os.EOL)[0]);if (match) {routerName match[1];}routers {path: /${name root ? : encodeURIComponent(name)},name:${name}, component: () import(/* webpackChunkName: ${name} */ /views/${filename}) ${ routerName ? ,name: routerName : } },\n;}const result import { createRouter, createWebHistory } from vue-router import Layout from /layout/index.vueconst router createRouter({history: createWebHistory(import.meta.env.BASE_URL),routes: [{path: /,name: index,component: Layout,redirect: /index,children:[${routers}]},] })export default router // console.log(result);fs.writeFile(./src/router/index.ts,result, utf-8,(err) {if (err) throw err;}); });修改 package.json 中的启动命令 scripts: {dev: node gen-router.js vite, },这样每次新建完一个文件后需要重启一下服务然后会自动生成路由文件配置菜单动态显示即可 Ref全家桶 ref 接受一个内部值并返回一个可变响应式的 ref 对象。ref 对象仅有一个 .value property指向该内部值。 templatediv{{ product }}/divhrbutton clickchange点击/button /templatescript setup langtsimport {ref} from vue;const product ref({id:001,name:小米手机 })const change () {product.value.name 华为手机console.log(product) }/script调试小技巧 我们打印 ref 对象时需要点开两层才能看到信息如下 可以打开 启用自定义格式化程序 之后打印就会直接展示具体的信息 isRef 判断一个对象是否是响应式对象 import { ref, isRef } from vue;const product ref({id: 001,name: 小米手机 })const change () {product.value.name 华为手机// isRef判断一个对象是否是响应式对象console.log(isRef(product)) // true }shallowRef 创建一个跟踪自身 .value 变化的 ref但不会使其值也变成响应式的 import { ref, isRef, shallowRef } from vue;const shaRef shallowRef({price: 100 })const change () {// product.value.name 华为手机// isRef判断一个对象是否是响应式对象console.log(isRef(product)) // trueshaRef.value.price 200console.log(shaRef.value); }上面的例子中页面不会发生变化 triggerRef 强制更新页面 import { ref, isRef, shallowRef, triggerRef } from vue;const product ref({id: 001,name: 小米手机 })const shaRef shallowRef({price: 100 })const change () {// product.value.name 华为手机// isRef判断一个对象是否是响应式对象console.log(isRef(product)) // trueshaRef.value.price 200console.log(shaRef.value);triggerRef(shaRef) }需要传入一个要更新的对象 customRef 自定义一个ref响应式数据 import { customRef } from vue;function myRefT(value: T) {return customRef((track, trigger) {return {get() {track()return value},set(newVal) {value newValtrigger()},}}) }const song1 myRef(123)const change () {song1.value 456}Reactive全家桶 Reactive 用来绑定复杂的数据类型 例如 对象 数组 源码中限定只能传入类型是Object的数据 templatediv{{ form }}/divbutton clickchange改变/buttonhrulli v-foritem in list.value{{ item }}/li/ulbutton clickgetList获取/button /templatescript setup langts nameReactive import { reactive, } from vue;let form reactive({name: 张三,age: 18 }) function change() {form.age }let list reactive({value: [lisi, wangwu] }) function getList() {setTimeout(() {let res [Anly, Jack]// 直接给reactive赋值会破坏原有的响应式list.value resconsole.log(list);}, 1000); } /scriptReadonly 将一个对象设置为只读 import { reactive, readonly } from vue;let form reactive({name: 张三,age: 18 }) let readOnlyForm readonly(form) function change() {readOnlyForm.age }shallowReactive 浅层的响应式 import { shallowReactive } from vue;let shaReactive shallowReactive({a: {b: 123} }) function chageSha() {shaReactive.a.b 456 // 页面不会发生改变console.log(shaReactive); // 打印的数据发生改变 }to系列全家桶 toRef 将对象中的某个属性变成响应式的 如果原始数据是非响应式的则经过 toRef 之后也不会更新视图但是数据会发生变化 templatediv{{ student }}/divdivlikeRef:{{ likeRef }}/divbutton clickchange修改/button /template script setup langts import { toRef } from vueconst student {name: Jack,age: 18,like: 画画 }let likeRef toRef(student, like)function change() {// 如果源数据是非响应式的则经过toRef后也不会触发页面更新likeRef.value 足球console.log(student);console.log(likeRef);}/script如果源数据就是响应式的则会触发页面更新 templatediv{{ student }}/divdivlikeRef:{{ likeRef }}/divbutton clickchange修改/button /template script setup langts import { toRef, reactive } from vueconst student reactive({name: Jack,age: 18,like: 画画 })let likeRef toRef(student, like)function change() {// 如果源数据是非响应式的则经过toRef后也不会触发页面更新likeRef.value 足球console.log(student);console.log(likeRef);}/scripttoRefs 将对象的所有数据都变成响应式数据 import { toRef, toRefs, toRaw, ref, reactive } from vueconst student reactive({name: Jack,age: 18,like: 画画,code: [1, 2] })// 自实现toRefs function myToRefsT extends Object(object: T) {let map: any {}for (const key in object) {map[key] toRef(object, key)}return map } function refs() {console.log(myToRefs(student)); // 打印结果如下图 }// 使用场景对象解构 let { name, age, code } toRefs(student) function fun1() {name.value Timage.value 16code.value.push(3) }myToRefs 打印结果 toRaw 返回对象的原始信息 function fun2() {console.log(toRaw(student)); }打印 Vue3响应式源码实现 初始化项目结构 vue-proxy ├── effect.js ├── effect.ts ├── index.html ├── index.js ├── package.json ├── reactive.js ├── reactive.ts └── webpack.config.jsreactive.ts import { track, trigger } from ./effect// 判断是否是对象 const isObject (target) target ! null typeof target object// 泛型约束只能传入Object类型 export const reactive T extends object(target: T) {return new Proxy(target, {get(target, key, receiver) {console.log(target);console.log(key);console.log(receiver);let res Reflect.get(target, key, receiver)track(target, key)if (isObject(res)) {return reactive(res)}return res},set(target, key, value, receiver) {let res Reflect.set(target, key, value, receiver)console.log(target, key, value);trigger(target, key)return res}})}effect.ts // 更新视图的方法 let activeEffect; export const effect (fn: Function) {const _effect function () {activeEffect _effect;fn()}_effect() }// 收集依赖 const targetMap new WeakMap() export const track (target, key) {let depsMap targetMap.get(target)if (!depsMap) {depsMap new Map()targetMap.set(target, depsMap)}let deps depsMap.get(key)if (!deps) {deps new Set()depsMap.set(key, deps)}deps.add(activeEffect) }// 触发更新 export const trigger (target, key) {const depsMap targetMap.get(target)const deps depsMap.get(key)deps.forEach(effect effect()) }测试 执行 tsc 转成 js 代码没有 tsc 的全局安装 typescript npm install typescript -g新建 index.js分别引入 effect.js 和 reactive.js import { effect } from ./effect.js; import { reactive } from ./reactive.js;let data reactive({name: lisit,age: 18,foor: {bar: 汽车} })effect(() {document.getElementById(app).innerText 数据绑定${data.name} -- ${data.age} -- ${data.foor.bar} })document.getElementById(btn).addEventListener(click, () {data.age })新建index.html !DOCTYPE html html langenheadmeta charsetUTF-8meta nameviewport contentwidthdevice-width, initial-scale1.0titleDocument/title /headbodydiv idapp/divbutton idbtn按钮/button /body然后再根目录执行 npm init -y安装依赖 npm install webpack webpack-cli webpack-dev-server html-webpack-plugin -D然后新建 webpack.config.js const path require(path) const HtmlWebpakcPlugin require(html-webpack-plugin)module.exports {entry: ./index.js,output: {path: path.resolve(__dirname, dist)},plugins: [new HtmlWebpakcPlugin({template: path.resolve(__dirname, ./index.html)})],mode: development,// 开发服务器devServer: {host: localhost, // 启动服务器域名port: 3000, // 启动服务器端口号open: true, // 是否自动打开浏览器}, }执行命令启动项目 npx webpack servecomputed的简单使用 templatedivtable border width600 cellspacing0 cellpadding0theadth名称/thth价格/thth数量/thth总价/thth操作/th/theadtbodytr v-for(item,index) in choosList styletext-align: centertd{{ item.name }}/tdtd{{ item.price }}/tdtd{{ item.count }}/tdtdel-button typeprimary clickitem.count---/el-button{{ item.price * item.count }}el-button typeprimary clickitem.count/el-button/tdtdel-button typedanger clickremove删除/el-button/td/tr/tbodytfoot alignrighttrtd colspan5总价{{total}}/td/tr/tfoot/table/div /templatescript setup import {reactive,computed} from vue;let choosList reactive([{name:裤子,price:100,count:1,},{name:衣服,price:200,count:1,},{name:鞋子,price:300,count:1,},{name:帽子,price:400,count:1,} ]) let total computed((){let total 0;choosList.forEach(item{total item.price * item.count;})return total; })function remove(index){choosList.splice(index,1); } /scriptstyle scoped/stylecomputed源码实现 effect.ts // 更新视图方法 let activeEffect export const effect (fn:Function,options) {console.log(effect触发)const _effect function () {activeEffect _effectreturn fn()}_effect.options options_effect()return _effect }// 依赖收集 const targetMap new WeakMap() export const track (target, key) {let depsMap targetMap.get(key)if (!depsMap) {depsMap new Map()targetMap.set(target, depsMap)}let deps depsMap.get(key)if (!deps) {deps new Set()depsMap.set(key, deps)}deps.add(activeEffect) }// 触发更新 export const trigger (target, key) {const depsMap targetMap.get(target)const deps depsMap.get(key)deps.forEach(effect {if (effect.options.scheduler){effect.options.scheduler()}else{effect()}}) }reactive.ts import {track, trigger} from ./effect // 判断是否是对象类型 const isObject (target) typeof target object target ! nullexport const reactive (target) {return new Proxy(target, {get(target, key, receiver) {console.log(reactive.get-,key)const res Reflect.get(target, key, receiver)// 收集依赖track(target, key)// 递归return isObject(res) ? reactive(res) : res},set(target, key, value, receiver) {console.log(reactive.set-,key)const res Reflect.set(target, key, value, receiver)// 触发依赖trigger(target, key)return res}}) }computed.ts import {effect} from ./effectexport const myComputed (getter:Function){let _value effect(getter,{scheduler:(){_dirty true}})// 判断是否需要重新计算结果let _dirty true// 缓存结果let catchValueclass ComputedRefImpl{get value(){if(_dirty){console.log(依赖发生变化时执行)catchValue _value()_dirty false}return catchValue}}return new ComputedRefImpl() }watch监听器 监听单属性值 let name ref(李四)watch(name,(newValue,oldValue){console.log(newValue,oldValue) })同时监听多个属性 let name ref(李四) let age ref(20)watch([name,age],(newValue,oldValue){console.log(newValue,oldValue) })深度监听 let obj ref({foo:{bar:{name:张三}} })watch(obj,(newValue,oldValue){console.log(obj.value.foo.bar.name) },{deep:true, // 深度监听immediate:true, // 立即执行 })监听对象中的某一个属性 let obj ref({foo:{bar:{name:张三,age:18}} })// 监听某个属性是要传入一个函数来返回要监听的属性值 watch(()obj.value.foo.bar.age,(newValue,oldValue){console.log(obj.value.foo.bar.age) },{immediate:true })watchEffect 简介 watchEffect不需要传入任何参数它是一个函数当依赖变化时这个函数就会执行它内部会根据响应式数据的依赖关系自动执行监听函数 使用 templateel-input idmsg1 v-modelmsg1 placeholderplaceholder/el-inputel-input v-modelmsg2 placeholderplaceholder/el-inputel-button typeprimary clickstopWatch停止监听/el-button /templatescript setup import {ref, watchEffect,nextTick} from vuelet msg1 ref(msg1) let msg2 ref(msg2)// watchEffect不需要传入任何参数它是一个函数当依赖变化时这个函数就会执行 // 它内部会根据响应式数据的依赖关系自动执行监听函数 const stop watchEffect((){console.log(msg1.value)console.log(msg2.value) })function stopWatch(){// 停止监听stop() }/scriptBEM架构和Layout布局 Layout目录结构 layout ├── Content │ └── index.vue ├── Header │ └── index.vue ├── Menu │ └── index.vue ├── css │ └── bem.scss └── index.vue新建 bem.scss $namespace: zx !default; $block-sel:- !default; $element-sel:__ !default; $modifier-sel:-- !default;mixin bfc{height:100%;overflow: hidden; }mixin b($block){// 拼接的结果为:zx-xxx$B:$namespace $block-sel $block;.#{$B}{content;} }mixin e($element){// 拼接的结果为:zx-xxx__xxx$selector:;at-root {$E:$selector $element-sel $element;#{$E}{content;}} }mixin m($modifier){// 拼接的结果为:zx-xxx--xxx$selector:;at-root {$M:$selector $modifier-sel $modifier;#{$M}{content;}} }配置全局生效 import { fileURLToPath, URL } from node:urlimport { defineConfig } from vite import vue from vitejs/plugin-vue import vueJsx from vitejs/plugin-vue-jsx import AutoImport from unplugin-auto-import/vite import Components from unplugin-vue-components/vite import { ElementPlusResolver } from unplugin-vue-components/resolvers// https://vitejs.dev/config/ export default defineConfig({plugins: [vue(),vueJsx(),AutoImport({resolvers: [ElementPlusResolver()],}),Components({resolvers: [ElementPlusResolver()],}),],resolve: {alias: {: fileURLToPath(new URL(./src, import.meta.url))}},css: {preprocessorOptions: {// 配置全局CSSscss: {additionalData: import ./src/layout_v2/css/bem.scss;}}} })index.vue templatediv classzx-boxdiv classzx-box__menuMenu//divdiv classzx-box__mainHeader/Content//div/div /templatescript setupimport Menu from /layout_v2/Menu/index.vue; import Header from /layout_v2/Header/index.vue; import Content from /layout_v2/Content/index.vue; /scriptstyle scoped langscss include b(box){height: 100%;display: flex;include e(menu){width: 250px;height: 100%;border-right: 1px solid #ebebeb;}include e(main){flex: 1;display: flex;flex-direction: column;} }/styleMenu/index templatedivMenu/div /templatescript setup/scriptHeader/index.vue template div classzx-headerHeader /div /templatescript setup /scriptstyle scoped langscss include b(header){width: 100%;height: 60px;line-height: 60px;border-bottom: 1px solid #ccc; } /styleContent/index.vue templatediv classzx-contentdiv v-foritem in 50 classzx-content__item{{item}}/div/div /templatescript setup/scriptstyle scoped langscss include b(content){height: 100%;overflow: auto;include e(item){height: 60px;line-height: 60px;text-align: center;border-radius: 5px;border: 1px solid pink;margin: 10px;} } /style布局效果 父子组件传值 简单使用 定义父组件 templatediv classparent-box父组件div子组件传过来的值{{count}}/divbutton clickgetSubInfo获取子组件的所有属性和方法/buttonSubComponent refsubCom :valuetitle changeCountchangeCount//div /templatescript setup langts import SubComponent from /components/SubComponent.vue; import {ref} from vue;let title 给儿子传值; let count refnumber()// 定义组件类型 let subCom refInstanceTypetypeof SubComponent()// 子组件触发的父组件方法 const changeCount (newVal) {count.value newVal; }const getSubInfo () {// 调用子组件的实例方法subCom.value.open()// 获取子组件的属性console.log(subCom.value.order) } /scriptstyle scoped .parent-box{width: 300px;height: 300px;border: 1px solid #ccc;padding: 30px; } /style子组件 templatediv classchildren-box父组件传递的值 {{ value }}button clickchangeParentCount改变父组件的值/button/div /templatescript setup langts import {defineProps, defineEmits, defineExpose, ref} from vue;// 父组件传过来的值,带个问号表示可选 const props defineProps{value: string }()// JS中获取父组件传过来的值 console.log(props.value)// 点击按钮触发父组件的自定义事件 const changeParentCount () {emit(changeCount, 10) }// 触发父组件的自定义事件 const emit defineEmits([changeCount])// 定义对外暴露的属性 let order ref(10) const open () {console.log(open) } // 使用defineExpose暴露出去 defineExpose({order,open }) /scriptstyle scoped .children-box{border: 1px solid #ccc;padding: 30px; } /style实现瀑布流布局 父组件 templateWaterfallFlow :listlist/ /templatescript setup langtsimport WaterfallFlow from /components/WaterfallFlow.vue; import {reactive} from vue; type listType {height:number,color:string } // 随机生成100个高度和颜色的对象 let list reactivelistType[]([...Array.from({length:100},()({height:Math.floor(Math.random()*250)50,color:rgb(${Math.floor(Math.random()*255)},${Math.floor(Math.random()*255)},${Math.floor(Math.random()*255)})}))]) /script子组件 templatediv classwrapsdiv v-foritem in list classitem :style{left: item.left px,top: item.top px,height: item.height px,backgroundColor: item.color,}/div/div /templatescript setup langts import {defineProps, onMounted} from vueconst props defineProps{list: any[] }()const initLayout () {// 上下左右间隙距离let margin 10// 每个元素的宽度let elWidth 120 margin// 每行展示的列数let colNumber Math.floor(document.querySelector(.app-content).clientWidth / elWidth)// 存放元素高度的listlet heightList []// 遍历所有元素for (let i 0; i props.list.length; i) {let el props.list[i]// i小于colNumber表示第一行元素if(i colNumber){el.top 0el.left elWidth * iheightList.push(el.height)}else{// 找出最小的高度let minHeight Math.min(...heightList)// 找出最小高度的索引let minHeightIndex heightList.indexOf(minHeight)// 设置元素的位置el.left elWidth * minHeightIndexel.top minHeight margin// 更新高度集合heightList[minHeightIndex] minHeight el.height margin}} }// 监听app-content元素的宽度变化 window.onresize () {initLayout() }onMounted(() {initLayout() }) /scriptstyle scoped langscss .wraps{height: 100%;position: relative;.item{position: absolute;width: 120px;} } /style效果展示 组件递归 实现一个如下的东西 父组件 templateTreeVue :treeDatatreeData/ /templatescript setupimport {reactive} from vue import TreeVue from /components/TreeVue.vue;let treeData reactive([{label:1,checked:false,children:[{label:1-1,checked:false,},{label: 1-2,checked:true,}]},{label:2,checked:false,children: [{label: 2-1,checked:false,children:[{label: 2-1-1,checked:false,children:[{label: 2-1-1-1,checked:false,}]}]}]},{label:3,checked:false,} ])/scriptTreeVue.vue templatediv v-foritem in treeData stylemargin-left: 15px click.stopgetCurrNode(item,$event)input typecheckbox v-modelitem.checked/span{{item.label}}/spanTreeVue v-ifitem.children :tree-dataitem.children//div /templatescript setup import {defineProps} from vue defineProps([treeData])const getCurrNode (currNode,event) {console.log(currNode)console.log(event) } /script控制台打印的东西 动态组件 templatediv styledisplay: flex;gap: 15pxdiv v-for(item,index) in tabData :keyindexclasstab-item:class{active:active index}clickswitchCom(item,index)div{{item.tab}}/div/div/divcomponent :iscurrCom/component /templatescript setup import {reactive, ref, shallowRef,markRaw} from vue import ComA from /components/13/ComA.vue import ComB from /components/13/ComB.vue import ComC from /components/13/ComC.vue// 使用shallowRef避免深层相应 let currCom shallowRef(ComA) let active ref(0)let tabData reactive([{tab:组件A,// 使用markRaw使组件不会被vue进行响应式处理提高性能com:markRaw(ComA)},{tab:组件B,com:markRaw(ComB)},{tab:组件C,com:markRaw(ComC)} ])const switchCom (item,index) {currCom.value item.comactive.value index } /scriptstyle scoped .tab-item{padding: 5px 15px;border: 1px solid black; } .active{background-color: deepskyblue; } /style插槽 定义子组件 templatediv classboxdiv classheaderslot nameheader/slot/divdiv classmain!--默认插槽--slot :linklink :ageage/slot/divdiv classfooterslot namefooter/slot/div/div /template script setup langts import {ref} from vue;const link ref(Tome) const age ref(18) /scriptstyle scoped langscss // 父元素高度100% .box{height: 100%;display: flex;flex-direction: column; } .header {height: 100px;background: pink;width: 100%; } .main{flex: 1;background-color: #c6e2ff; } .footer{height: 100px;background: blueviolet;width: 100%; } /style定义父组件 templateDialogtemplate #header具名插槽-header/templatetemplate #default{link,age}这是默认插槽{{link}} -- {{age}}/templatetemplate #footer具名插槽-footer/template/Dialog /templatescript setup langts import Dialog from /components/14/Dialog.vue /script效果 异步组件 添加骨架屏组件 Skeleton.vue templateel-skeleton style--el-skeleton-circle-size: 100pxtemplate #templateel-skeleton-item variantcircle //template/el-skeletonbr /el-skeleton / /template效果是这个样子 添加新闻组件 添加新闻数据在 public 文件夹中添加 newinfo.json [{title: 秋粮陆续成熟 多措并举保粮食丰收,description: 眼下从南到北各地秋粮陆续成熟。人们全力以赴抓好秋粮生产多措并举保粮食丰收。\n\n金秋时节安徽水稻主产区无为市85万亩水稻进入收割期当地组织机械作业服务队帮助农民机耕机收颗粒归仓。今年安徽计划投入各类农机具240万台套力争玉米、大豆、中晚稻机收水平达八成以上。,url: https://baijiahao.baidu.com/s?id1777244368223895628,image: https://szx-bucket1.oss-cn-hangzhou.aliyuncs.com/picgo/img.png} ]引入 axios请求这个文件 src/api/index.js import axios from axiosexport function getNewDataFun(){return axios(../public/newinfo.json) }编写组件 NewCar.vue templatediv v-foritem in newData classnew-boxdiv classimageimg :srcitem.image alt//divdiv classcontentdiv classtitle{{item.title}}/divdiv classdesc{{item.description}}/div/div/div /templatescript setup langts import {onMounted, reactive, ref} from vue; import {getNewDataFun} from /api/index;type dataType {title:string,description:string,url:string,image:string, }const newData refdataType[]([])await getNewDataFun().then(res{setTimeout((){newData.value res.data},2000) }) /scriptstyle scoped langscss .new-box{display: flex;gap: 15px;.image{width: 200px;border-radius: 5px;overflow: hidden;img{width: 100%;}}.content{width: 80%;display: flex;flex-direction: column;gap: 10px;.title{font-weight: 700;font-size: 16px;}} } /style效果展示 使用异步组件 Suspense 是vue内置的一个组件有两个插槽 default默认插槽展示等待结果返回后的组件fallback等待过程中展示的组件 templatediv stylepadding: 20pxSuspensetemplate #defaultNewCar//templatetemplate #fallbackSkeleton//template/Suspense/div /templatescript setup langts import {defineAsyncComponent} from vueimport Skeleton from /components/15/Skeleton.vueconst NewCar defineAsyncComponent(()import(/components/15/NewCar.vue)) /script异步组件必须使用 defineAsyncComponent 函数来导入接收一个回调函数 TelePore传送组件 自定义一个弹框组件 templatediv classzx-dialogslot/slotdiv classzx-dialog__footerslot namefooter/slot/div/div /templatestyle scoped langscss include b(dialog){width: 200px;height: 200px;position: absolute;left: 50%;top: 50%;margin-left: -100px;margin-top: -100px;border: 1px solid #ccc;background-color: #c6e2ff;include e(footer){position: absolute;bottom: 0;left: 0;right: 0;height: 50px;line-height: 50px;text-align: right;} } /style使用TelePore 父组件使用这个组件 templatediv classboxel-button typeprimary clickswitchDialog !switchDialog打开Dialog/el-button!--使用teleport将组件渲染到body标签下面避免受到父组件的position: absolute;定位影响--teleport tobody v-ifswitchDialogMyDialogtemplate #default弹框内容/templatetemplate #footerel-button clickswitchDialog !switchDialog关闭/el-button/template/MyDialog/teleport/div /templatescript setup import {ref} from vue import MyDialog from /components/MyDialog.vue;let switchDialog ref(false);/scriptstyle scoped .box{width: 100%;height: 50%;background-color: gold;position: absolute;} /style效果 KeepAlive 可以缓存组件内容 默认使用 切换组件显示后组件内容不会丢失 templatedivdivel-button typeprimary clickswitchFlag切换组件/el-button/divkeep-aliveAliveA v-ifflag/AliveB v-else//keep-alive/div /templatescript setup import {ref} from vue; import AliveA from /components/AliveA.vue; import AliveB from /components/AliveB.vue;let flag ref(true)const switchFlag () {flag.value !flag.value } /scriptincludes 只缓存AliveA组件 keep-alive :include[AliveA]AliveA v-ifflag/AliveB v-else/ /keep-aliveexclude 不缓存AliveA组件 keep-alive :exclude[AliveA]AliveA v-ifflag/AliveB v-else/ /keep-alivemax 最多缓存的组件个数 keep-alive :max10AliveA v-ifflag/AliveB v-else/ /keep-alivekeep-alive的钩子函数 script langts setup import { ref,onMounted,onActivated,onDeactivated,onUnmounted, } from vue// 组件显示时只会触发一次 onMounted((){console.log(mounted) })// 组件显示时触发 onActivated((){console.log(activated) }) // 组件隐藏时触发 onDeactivated((){console.log(deactivated) }) // 被keepalive包裹时组件销毁不会触发unmounted onUnmounted((){console.log(unmounted) })transition 基本用法 在进入/离开的过渡中会有 6 个 class 切换。 v-enter-from定义进入过渡的开始状态。在元素被插入之前生效在元素被插入之后的下一帧移除。 v-enter-active定义进入过渡生效时的状态。在整个进入过渡的阶段中应用在元素被插入之前生效在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间延迟和曲线函数。 v-enter-to定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter-from 被移除)在过渡/动画完成之后移除。 v-leave-from定义离开过渡的开始状态。在离开过渡被触发时立刻生效下一帧被移除。 v-leave-active定义离开过渡生效时的状态。在整个离开过渡的阶段中应用在离开过渡被触发时立刻生效在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间延迟和曲线函数。 v-leave-to离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave-from 被移除)在过渡/动画完成之后移除。 template divel-button typeprimary clickflag !flag切换/el-buttontransition namefadediv classbox v-ifflag/div/transition /div /templatescript setup import {ref} from vue;let flag ref(true) /scriptstyle scoped langscss .box{width: 200px;height: 200px;background-color: red; }//开始过度.fade-enter-from{background:red;width:0px;height:0px;transform:rotate(360deg)} //开始过度了.fade-enter-active{transition: all 1s ease;} //过度完成.fade-enter-to{background:yellow;width:200px;height:200px;} //离开的过度.fade-leave-from{width:200px;height:200px;transform:rotate(360deg)} //离开中过度.fade-leave-active{transition: all 1s linear;} //离开完成.fade-leave-to{width:0px;height:0px;} /style结合animate 安装 npm install animate.css -D官网中有很多动画示例 Animate.css | A cross-browser library of CSS animations. templatediv classroot-boxdiv classapp-menuMenu //divdiv classapp-content!-- 路由出口 --!-- 路由匹配到的组件将渲染在这里 --router-view v-slot{ Component,route }transition enter-active-classanimate__animated animate__fadeInUp!-- 这里加一个div是防止页面没有根组件时动画失效 --div :keyroute.name styleheight: 100%component :isComponent //div/transition/router-view/div/div /template script setup import Menu from ./Menu.vue import animate.css; // 设置所有动画的时间在0.3秒内完成 document.documentElement.style.setProperty(--animate-duration, 0.3s); /scriptstyle scoped langless .root-box {display: flex;width: 100%;height: 100vh;.app-menu {width: 200px;height: 100vh;background-color: #a18cd1;text-overflow: ellipsis;white-space: nowrap;overflow: auto;}.app-content {flex: 1;height: 100vh;background-color: white;overflow: auto;} } /styletranstion生命周期 transition before-enterbeforeEnter enterenter leaveleavediv v-ifgsapFlag classgsap-box/div /transitionbefore-enterbeforeEnter //对应enter-fromenterenter//对应enter-activeafter-enterafterEnter//对应enter-toenter-cancelledenterCancelled//显示过度打断before-leavebeforeLeave//对应leave-fromleaveleave//对应enter-activeafter-leaveafterLeave//对应leave-toleave-cancelledleaveCancelled//离开过度打断结合gsap 安装官网https://greensock.com/ npm install gsap使用 html el-button typeprimary clickgsapFlag !gsapFlag切换/el-button transition before-enterbeforeEnter enterenter leaveleavediv v-ifgsapFlag classgsap-box/div /transitionjs script setup import gsap from gsap; import {ref} from vue;let gsapFlag ref(true)const beforeEnter (el) {console.log(显示之前)gsap.set(el,{width:0,height:0,background:green}) } const enter (el,done) {gsap.to(el,{width:200px,height:200px,background:red,rotate:360dge,duration:1, // 动画时长单位是秒onComplete:done, // 动画完成后的回调函数}) } const leave (el,done) {gsap.to(el,{width:0,height:0,background:green,rotate:-360dge,duration:1, // 动画时长单位是秒onComplete:done}) }效果 appear属性 在 transtion 组件中添加 appear 可以在进入页面时就触发对应的样式代码 appear-class初始样式appear-to-class结束样式appear-active-class动画曲线 transition appear appear-class appear-to-class appear-active-classanimate__animated animate__rubberBand namefadediv classbox v-ifflag/div /transition结合animate__animated实现一个进入页面就执行的一个动画效果 transition-group 在遍历数组的时候可以给每一个元素添加过度动画生命周期和transition一致我们结合animate来实现一个列表的动画效果 divel-button typeprimary clickaddadd/el-buttonel-button typedanger clickpoppop/el-button/divdiv classwarptransition-groupenter-active-classanimate__animated animate__bounceInLeftleave-active-classanimate__animated animate__fadeOutRightdiv v-foritem in groupList :keyitem classitem{{item}}/div/transition-group/divimport {ref,reactive} from vue; import animate.cssconst groupList reactive([1,2,3,4,5])const add () {groupList.push(groupList.length 1) } const pop () {groupList.pop() }动画效果 实现一个炫酷的动画效果 安装lodash库 Lodash 简介 | Lodash中文文档 | Lodash中文网 (lodashjs.com) npm i --save lodash实现代码 div stylemargin-top: 20px平面动画过度效果/div el-button typeprimary clickshuffle动画/el-button div classnum-wraptransition-group move-classmove-classdiv v-foritem in numList :keyitem.id classnum-item{{item.value}}/div/transition-group /divimport {ref,reactive} from vue; import _ from lodashlet numList ref(Array.apply(null, {length: 81}).map((_,index){return {id:index,value:(index % 9) 1} }))const shuffle () {// shuffle 用来创建一个被打乱值的集合numList.value _.shuffle(numList.value) }$numWidth:60px;.move-class{transition: all 1s ease; } .num-wrap{display: flex;flex-wrap: wrap;width: calc(#{$numWidth} * 9 5px * 8);gap: 5px;.num-item{width: $numWidth;height: $numWidth;line-height: $numWidth;text-align: center;border: 1px solid #ccc;} }实现效果 使用gsap实现数字滚动 div stylemargin-top: 20px;height: 2px使用gsap实现数字滚动/div el-input v-modelrolling.num placeholderplaceholder stylewidth: 200px/el-input h1{{rolling.numRul.toFixed(0)}} /h1import gsap from gsap; import {ref,reactive,watch} from vue;let rolling reactive({num:10,numRul:10 }) watch(()rolling.num,(newVal){gsap.to(rolling,{numRul:newVal,duration:1,}) })依赖注入provide和inject 爷爷组件 templateh1爷爷组件/h1el-button typeprimary clicksetColor(red)红色/el-buttonel-button typeprimary clicksetColor(blue)蓝色/el-buttonel-button typeprimary clicksetColor(pink)粉色/el-buttondiv classbox/divhrProvideA/hrProvideB//templatescript setup langts import {provide, inject, ref} from vue import ProvideA from /components/ProvideA.vue; import ProvideB from /components/ProvideB.vue;let color ref(red)provide(color,color)const setColor (c) {color.value c }/scriptstyle scoped .box{width: 100px;height: 100px;background-color: v-bind(color); } /styleProvideA templatedivh1爸爸组件/h1div classbox/div/div /templatescript setup langts import {inject} from vue import type {Ref} from vue let color:Refstring inject(color) /scriptstyle scoped .box{width: 100px;height: 100px;background-color: v-bind(color); } /styleProvideB templatedivh1孙子组件/h1div classbox/divbutton clicksetColor设置粉色/button/div /templatescript setup langts import {inject} from vue import type {Ref} from vue let color:Refstring inject(color)const setColor () {color.value pink } /scriptstyle scoped .box{width: 100px;height: 100px;background-color: v-bind(color); } /style实现效果 兄弟传参 Mitt 安装 npm install mitt局部使用 添加一个JS文件导出 utils/mitt.js import mitt from mitt export default mitt()使用分别定义 A B两个组件 BusA templatediv classbox我是A组件el-button typeprimary clickchangeFlag改变/el-button/div /templatescript setup import mitt from ../utils/mitt import {ref} from vue;let flag ref(false) const changeFlag () {flag.value !flag.valuemitt.emit(changeFlag,flag.value) }/scriptBusB templatediv classbox我是B组件{{flag}}/div /templatescript setup import mitt from ../utils/mitt; import {ref,onBeforeUnmount} from vue; let flag ref(false)mitt.on(changeFlag, data{flag.value data })onBeforeUnmount((){mitt.off(changeFlag) }) /script在父组件引入 templateBusA/BusB/ /templatescript setup import BusA from /components/BusA.vue; import BusB from /components/BusB.vue;/script效果 全局使用 main文件添加 import ./assets/main.css import { createApp } from vue import { createPinia } from pinia import ElementPlus from element-plus import App from ./App.vue import router from ./router import element-plus/dist/index.css import zhCn from element-plus/dist/locale/zh-cn.min.js import dayjs/locale/zh-cn import mitt from mittconst Mitt mitt()const app createApp(App) app.use(createPinia()) app.use(router) app.use(ElementPlus, { locale: zhCn }) app.mount(#app) declare module vue{export interface ComponentCustomProperties {$Bus: typeof Mitt}}app.config.globalProperties.$bus Mitt文件内部通过从 vue 中导出 getCurrentInstance 方法获取当前实例获取定义的全局变量使用 BusA templatediv classbox我是A组件el-button typeprimary clickchangeFlag改变/el-button/div /templatescript setup import {ref,getCurrentInstance} from vue;let flag ref(false) let instance getCurrentInstance()const changeFlag () {flag.value !flag.valueinstance?.proxy?.$bus.emit(changeFlag,flag.value) } /scriptBusB templatediv classbox我是B组件{{flag}}/div /templatescript setup import {ref, onBeforeUnmount, getCurrentInstance} from vue; let flag ref(false) let instance getCurrentInstance()instance?.proxy?.$bus.on(changeFlag, data{flag.value data })onBeforeUnmount((){instance?.proxy?.$bus.off(changeFlag) }) /script手写Bus class MyBus{constructor() {this.list {}}emit(event, ...args){let funs this.list[event]funs.forEach((fun) {fun.apply(this,args)})}on(event, callback){let funs this.list[event]if(funs){funs.push(callback)}else{funs [callback]}this.list[event] funs}off(event){delete this.list[event]} } export default new MyBus()jsx插件 安装 npm in stall vitejs/plugin-vue-jsx -D在 vite.config.js 中使用 import { fileURLToPath, URL } from node:urlimport { defineConfig } from vite import vue from vitejs/plugin-vue import vueJsx from vitejs/plugin-vue-jsx import AutoImport from unplugin-auto-import/vite import Components from unplugin-vue-components/vite import { ElementPlusResolver } from unplugin-vue-components/resolvers// https://vitejs.dev/config/ export default defineConfig({module:es2022,plugins: [vue(),vueJsx(),AutoImport({resolvers: [ElementPlusResolver()],}),Components({resolvers: [ElementPlusResolver()],}),],resolve: {alias: {: fileURLToPath(new URL(./src, import.meta.url))}},css: {preprocessorOptions: {scss: {additionalData: import ./src/layout_v2/css/bem.scss;}}} })新建 JsxCom.tsx import {defineComponent, reactive, ref} from vue import {ElButton} from element-plusinterface propType {msg?:string }export default defineComponent({props:{msg:String,},emits:[],setup(prop:propType,{emit,attrs,slots,expose}){let flag ref(false)const chagneFlag () {flag.value true}let list reactive([1,2,3,4,5])return () {/*遍历循环*/}{list.map(item h1{item}/h1)}hr/{/*按钮事件使用οnclick{()chagneFlag()}*/}ElButton typeprimary οnclick{()chagneFlag()}改变这个值/ElButton{flag.value h1改变后的值/h1}hr/div父组件传递的值{prop.msg}/div/}, })在vue中可以把这个当成普通的组件使用 templateJsxCom msgHello Jsx/ /template script setup import JsxCom from ../components/JsxCom /script页面效果 自动引入插件 安装 npm istall unplugin-auto-import/vite配置 import AutoImport from unplugin-auto-import/viteexport default defineConfig({module:es2022,plugins: [vue(),vueJsx(),AutoImport({resolvers: [ElementPlusResolver()],imports: [vue, vue-router], // 自动引入vue和vue-router相关dts: src/auto-imports.d.ts // 自动生成的依赖文件}),Components({resolvers: [ElementPlusResolver()],}),],})保存后查看 src/auto-imports.d.ts 内容 里面自动的帮我们了引入 然后再组件中不需要手动的导入 vue就可以使用vue中的各种声明 templateel-button typeprimary clickflag !flagbuttonCont/el-buttondiv{{flag}}/div /template script setup let flag ref(false) /scriptv-model在组件中的使用 基本使用 vue3中在组件上绑定v-model时默认的prop变成了modelValue 子组件 Vmodel templatedivel-input v-modelinput placeholderplaceholder inputchangeValue stylewidth: 200px/el-inputel-button关闭/el-button/div /templatescript setup langts import {defineProps,defineEmits} from vueconst props defineProps{modelValue:string, }() // 更新model绑定的值固定写法: update:modelValue const emit defineEmits([update:modelValue])let input ref()onMounted((){input.value props.modelValue })const changeValue (e) {// 修改父组件的值emit(update:modelValue,e) }/script父组件 template父组件的值{{value}}div classboxVmodel v-modelvalue //div /templatescript setup langts import Vmodel from /components/Vmodel.vue;let value ref(你好) /scriptstyle scoped .box{border: 2px solid black;padding: 30px; } /style绑定多个v-model 父组件 template父组件的值{{value}}el-button clickisShow !isShow切换显示/el-buttondiv classboxVmodel v-modelvalue v-model:isShowisShow//div /templatescript setup langts import Vmodel from /components/Vmodel.vue;let value ref(你好) let isShow ref(true) /scriptstyle scoped .box{border: 2px solid black;padding: 30px; } /style子组件 templatediv v-ifisShowel-input v-modelinput placeholderplaceholder inputchangeValue stylewidth: 200px/el-inputel-button clickclose关闭/el-button/div /templatescript setup langts import {defineProps,defineEmits} from vueconst props defineProps{modelValue:string,isShow:boolean }() // 更新model绑定的值固定写法: update:modelValue const emit defineEmits([update:modelValue,update:isShow])let input ref()onMounted((){input.value props.modelValue })const changeValue (e) {// 修改父组件的值emit(update:modelValue,e) }const close () {emit(update:isShow,false) } /script自定义指令 自定义指令的声明周期 templatediv classbox v-resizeonResize/div /templatescript setup langts import { Directive } from vueconst onResize () {console.log(宽高变化) }// 声明一个局部自定义指令必须以v开头 const vResize:Directive {created(){console.log(created)},beforeMount(){console.log(beforeMount)},mounted(...arg){console.log(mounted)console.log(arg)},beforeUpdate(){console.log(beforeUpdate)},updated(){console.log(updated)},beforeUnmount(){console.log(beforeUnmount)},unmounted(){console.log(unmounted)} } /scriptstyle scoped .box{height: 100%;background-color: #f5f5f5; } /style在任意一个钩子函数头能拿到自定义指令绑定的参数我们通过打印 arg 看看参数有什么 我们利用这两个参数实现监听元素宽高变化的指令当元素宽高发生变化时调用绑定的函数 mounted(el,bindings){console.log(mounted)// 监听元素宽高变化const resizeObserver new ResizeObserver(entries {let width entries[0].contentRect.width;let height entries[0].contentRect.height;console.log(元素宽度${width},元素高度${height})bindings.value()});resizeObserver.observe(el); },修改 mounted 钩子的内容通过observe 观察 el然后调用 bindings.value 自定义指令的简写方式 我们也可以通过函数的方式来自定义指令 templateel-button v-has-showorder:add typeprimary新增/el-buttonel-button v-has-showorder:update typewarning修改/el-buttonel-button v-has-showorder:delete typedanger删除/el-button /templatescript setup langts import { Directive } from vue //permission localStorage.setItem(userId,songzx)//mock后台返回的数据 const permission [// songzx:order:add,songzx:order:update,songzx:order:delete ]const userId localStorage.getItem(userId) as stringconst vHasShow:DirectiveHTMLElement,string (el,binding){if(!permission.includes(${userId}:${binding.value})){// 直接移除这个元素比使用 el.style.display none 更安全el.remove()} }/script上面的例子是一个按钮级别权限的demo 鼠标拖动元素案例 templatediv classrootdiv classbox v-movediv classheader/div/div/div/templatescript setup langts import { Directive } from vueconst vMove:DirectiveHTMLElement (el){let moveEl:HTMLElement el.querySelector(.header)const mousedown (e:MouseEvent) {// 鼠标按下时获取当前鼠标的位置和移动物体相对于浏览器的位置let X e.x - el.offsetLeftlet Y e.y - el.offsetTop// 移动const move (e:MouseEvent){// 在移动物体时需要减去偏移量el.style.left e.clientX - X pxel.style.top e.clientY - Y px}document.addEventListener(mousemove, move)document.addEventListener(mouseup, (){document.removeEventListener(mousemove, move)})}// 鼠标按下头部时触发moveEl.addEventListener(mousedown,mousedown) }/scriptstyle scoped langscss .root{position: relative; } .box{position: absolute;width: 200px;height: 200px;left: 100px;top: 100px;border: 2px solid black;.header{width: 100%;height: 20px;background-color: black;} } /style图片懒加载案例 templatediv classrootimg v-foritem in arr v-lazyitem stylewidth: 100%;//div/templatescript setup langts import { Directive,DirectiveBinding } from vue// import.meta.glob 引入目标路径中的所有文件返回一个对象默认使用module引入 // 添加了eager: true则变成同步引入 let imgList import.meta.glob(../assets/images/*.*,{eager: true}) // 得到所有图片地址 let arr Object.values(imgList).map(itemitem.default)// 自定义懒加载指令 const vLazy:Directive async (el:HTMLImageElement,binding:DirectiveBinding){// 先给一个默认值const def await import(../assets/logo.svg)el.src def.default// 判断元素是否在可视范围内const intersection new IntersectionObserver((e){// 判断是否在可视范围内if(e[0].intersectionRatio 0){setTimeout((){el.src binding.value},500)intersection.unobserve(el)}})intersection.observe(el) }/scriptstyle scoped langscss .root{width: 361px;height: 800px;overflow: auto; } /style自定义Hook 好用的第三方库 vueuse npm i vueuse/core网址Get Started | VueUse — 开始使用 |Vueuse 图片转base64 新建 useImgToBase64.ts import {onMounted} from vuetype optionsType {el:String }export default function (options:optionsType):Promisestring{return new Promise((resolve, reject) {onMounted((){let img:HTMLImageElement document.querySelector(options.el)img.onload (){resolve(toBase64(img))}const toBase64 (img:HTMLImageElement) {let canvas:HTMLCanvasElement document.createElement(canvas)let ctx:CanvasRenderingContext2D canvas.getContext(2d)canvas.width img.widthcanvas.height img.heightctx.drawImage(img, 0, 0, canvas.width, canvas.height)return canvas.toDataURL(image/jpeg)}})}) }使用 templateimg src../assets/images/1.jpeg/ /templatescript setup langts import useImgToBase64 from /utils/useImgToBase64;useImgToBase64({el:img}).then(res{console.log(res) }) /script自定义Vite库并发布到NPM 封装useResize 用于监听绑定元素的宽高变化当元素宽高发生变化时触发回调并获取最新的宽高 新建项目 结合上面学到的 Hook 和 自定义指令封装一个监听元素宽高变化的指令并发布到 npm 项目结构 useResize ├── src │ └── index.ts ├── README.md ├── index.d.ts ├── package-lock.json ├── package.json ├── tsconfig.json └── vite.config.tssrc/index.ts import type {App} from vue;/*** 自定义Hook* param el* param callback*/ const weakMap new WeakMapHTMLElement, Function(); const resizeObserver new ResizeObserver((entries) {for (const entry of entries) {const handle weakMap.get(entry.target as HTMLElement);handle handle(entry)} })function useResize(el: HTMLElement, callback: Function) {if (weakMap.get(el)) {return}weakMap.set(el, callback)resizeObserver.observe(el) }/*** 定义vite插件时vue会在底层调用插件的install方法* param app*/ function install(app: App) {app.directive(resize, {mounted(el: HTMLElement, binding: { value: Function }) {useResize(el, binding.value)}}) }useResize.install installexport default useResizevite.config.ts import {defineConfig} from viteexport default defineConfig({build:{lib:{// 打包入口文件entry:src/index.ts,// namename:useResize},rollupOptions:{// 忽略打包的文件external:[vue],output:{globals:{useResize:useResize}}}} })index.d.ts declare const useResize:{(element:HTMLElement, callback:Function):voidinstall:(app:any) void }export default useResizepackage.json {name: v-resize-songzx,version: 1.0.0,description: ,main: dist/v-resize-songzx.umd.js,module: dist/v-resize-songzx.mjs,scripts: {test: echo \Error: no test specified\ exit 1,build: vite build},keywords: [],author: songzx,files: [dist,index.d.ts],license: ISC,devDependencies: {vue: ^3.3.4},dependencies: {vite: ^4.4.9} }pachage.json 文件属性说明 name对应打包后生成的包名也就是上传到npm上面的包名不能包含数字和特殊符号version包的版本号main对应打包后的 umd.js 文件在使用 app.use 时会访问使用文件module使用import、require等方式引入时会使用 mjs 文件files指定那些文件需要上传 打包 npm run build登录npm npm login发布 npm publish打开 npm 网站搜索查看是否发布成功 使用自己的库 安装 npm i v-resize-songzx使用方式一 全局注册 v-resze 指令 main.ts 引入 import useResize from v-resize-songzx;const app createApp(App)app.use(useResize) app.mount(#app)templatediv classresize v-resizegetNewWH/div /templatescript setup langts const getNewWH (e) {console.log(e.contentRect.width, e.contentRect.height); }/scriptstyle scoped /*把一个元素设置成可以改变宽高的样子*/ .resize {resize: both;width: 200px;height: 200px;border: 1px solid;overflow: hidden; } /style使用方式二 使用Hook的方式 templatediv classresize/div /templatescript setup langtsimport useResize from v-resize-songzx;onMounted(() {useResize(document.querySelector(.resize), e {console.log(e.contentRect.width, e.contentRect.height);}) })/scriptstyle scoped /*把一个元素设置成可以改变宽高的样子*/ .resize {resize: both;width: 200px;height: 200px;border: 1px solid;overflow: hidden; } /style定义全局变量和方法 在 main.ts 中添加 import dayjs from dayjs import mitt from mittconst Mitt mitt()// 定义全局变量 app.config.globalProperties.$bus Mitt app.config.globalProperties.$BaseUrl http://localhost app.config.globalProperties.$formatDate (date: Date) dayjs(date).format(YYYY-MM-DD HH:mm:ss)// 定义声明文件 declare module vue {export interface ComponentCustomProperties {$bus: typeof Mitt,$BaseUrl: string,$formatDate: Date} }在任何组件中都可以使用 templatediv{{ $BaseUrl }}/div /templatescript setup langts import {getCurrentInstance} from vue // 获取当前实例 const instance getCurrentInstance()console.log(instance.proxy.$BaseUrl) // http://localhost console.log(instance.proxy.$formatDate(new Date())) // 2023-09-25 13:51:23/script自定义插件之全局Loading ElementPlus的默认全局Loading 如果完整引入了 Element Plus那么 app.config.globalProperties 上会有一个全局方法$loading同样会返回一个 Loading 实例。 名称说明类型默认targetLoading 需要覆盖的 DOM 节点。 可传入一个 DOM 对象或字符串 若传入字符串则会将其作为参数传入 document.querySelector以获取到对应 DOM 节点string / HTMLElementdocument.bodybody同 v-loading 指令中的 body 修饰符booleanfalsefullscreen同 v-loading 指令中的 fullscreen 修饰符booleantruelock同 v-loading 指令中的 lock 修饰符booleanfalsetext显示在加载图标下方的加载文案string—spinner自定义加载图标类名string—background遮罩背景色string—customClassLoading 的自定义类名string— 指令的方式使用 templatediv classbox v-loadingisLoadingcontent/divel-button typeprimary clickshowDivLoading显示loading/el-button /templatescript setup langts // 显示局部loading let isLoading ref(false)const showDivLoading () {isLoading.value !isLoading.value }/scriptstyle scoped .box {width: 200px;height: 200px;border: 1px solid; } /style函数式调用 templateel-button typeprimary clickshowLoadingshowLoading/el-button /templatescript setup langts import {getCurrentInstance} from vue // 获取当前实例 const {proxy} getCurrentInstance()// 显示全局loading const showLoading () {const loading proxy.$loading()setTimeout(() {loading.close()}, 2000) } /script自定义全局Loading 我们自己动手来实现一个和ElementPlus的Loading同时支持函数调用和指令调用 添加MyLoading.vue templatetransition enter-active-classanimate__animated animate__fadeInleave-active-classanimate__animated animate__fadeOutdiv classroot-box v-ifshowdiv classwrapimg src../assets/images/loading.gif//div/div/transition /templatescript setup let show ref(false)const showLoading () {show.value true } const hideLoading (callback) {show.value falsecallback setTimeout(() callback(), 500) }defineExpose({show,showLoading,hideLoading })/scriptstyle scoped langscss .animate__animated.animate__fadeIn {--animate-duration: 0.5s; }.animate__animated.animate__fadeOut {--animate-duration: 0.5s; }.root-box {position: absolute;left: 0;top: 0;bottom: 0;right: 0;margin: 0;background-color: rgba(255, 255, 255, 0.9);z-index: 2000;display: flex;justify-content: center;align-items: center;.wrap {width: 100px;height: 100px;display: flex;justify-content: center;align-items: center;overflow: hidden;img {width: 100%;transform: scale(2.5);}} } /style添加MyLoading.ts import type {App, VNode,} from vue import {createVNode, render, cloneVNode} from vue import MyLoading from /components/MyLoading.vueexport default {install(app: App) {// 使用vue底层的createVNode方法将组件渲染为虚拟节点const VNode: VNode createVNode(MyLoading)// 使用render函数将组件挂载到body中render(VNode, document.body)// 定义全局方法设置组件的显示和隐藏app.config.globalProperties.$showLoading VNode.component?.exposed.showLoadingapp.config.globalProperties.$hideLoading VNode.component?.exposed.hideLoadingconst weakMap new WeakMap()// 自定义Loading指令app.directive(zx-loading, {mounted(el) {if (weakMap.get(el)) return// 记录当前绑定元素的positionweakMap.set(el, window.getComputedStyle(el).position)},updated(el: HTMLElement, binding: { value: Boolean }) {const oldPosition weakMap.get(el);// 如果不是position: relative或者absolute就设置为relative// 这里的目的是确保loading组件正确覆盖当前绑定的元素if (oldPosition ! absolute oldPosition ! relative) {el.style.position relative}// 克隆一份loading元素,// 作用是当页面上有多个zx-loading时每个dom都维护一份属于自己的loading不会冲突const newVNode cloneVNode(VNode)// 挂载当前节点render(newVNode, el)// 判断绑定的值if (binding.value) {newVNode.component?.exposed.showLoading()} else {newVNode.component?.exposed.hideLoading(() {// 还原布局方式el.style.position oldPosition})}}})} }在上面的文件中定义了两个全局函数和一个自定义指令 $showLoading全局显示一个Loading$hideLoading关闭全局的Loadingzx-loading自定义指令 在main.ts中挂载 在 main.ts 中去挂载我们自定义的 Loading import {createApp} from vue import MyLoading from /utils/MyLoading;const app createApp(App) // 引入自定义的全局Loading app.use(MyLoading)app.mount(#app)使用方法一函数式使用 调用全局方法弹出Loading template!--自定义全局loading--el-button typeprimary clickshowMyLoading显示自定义的全局loading/el-button /templatescript setup langts import {getCurrentInstance} from vue // 获取当前实例 const {proxy} getCurrentInstance()// 全局显示自定义loading const showMyLoading () {proxy.$showLoading()setTimeout(() {proxy.$hideLoading()}, 2000) } /script使用方法二指令式使用 templatediv!--自定义的loading指令使用--div classbox v-zx-loadingisLoading指令的方式使用/divel-button typeprimary clickshowDivLoading显示loading/el-button!--自定义的loading指令使用-- div classparentdiv classchild v-zx-loadingchildLoading/div/divel-button typeprimary clickshowChildLoading显示childLoading/el-button/div /templatescript setup langts // 显示局部loading let isLoading ref(false) const showDivLoading () {isLoading.value !isLoading.value }const childLoading ref(false) const showChildLoading () {childLoading.value !childLoading.value } /scriptstyle scoped langscss .box {width: 200px;height: 200px;border: 1px solid; }.parent {position: relative;width: 300px;height: 300px;border: 1px solid;padding: 30px;.child {position: absolute;right: 30px;bottom: 30px;width: 200px;height: 200px;border: 1px solid;} } /styleuse函数源码实现 添加 MyUse.ts import type {App} from vue import {app} from /main// 定义一个接口声明install方法必传 interface Use {install: (app: App, ...options: any[]) void }const installList new Set()export default function myUseT extends Use(plugin: T, ...options: any[]) {// 判断这个插件是否已经注册过了如果注册过了则报错if (installList.has(plugin)) {console.error(Plugin already installed)return}// 调用插件身上的install方法并传入main.ts导出的appplugin.install(app, ...options)installList.add(plugin) }使用自定义的myUse方法注册我们自定义的Loading import {createApp} from vue// 自定义全局Loading import MyLoading from /utils/MyLoading; // 自定义app.use方法 import myUse from /utils/MyUse;export const app createApp(App) // 引入自定义的全局Loading myUse(MyLoading)app.mount(#app)CSS选择器 :deep 使用 :deep() 将选择器包裹起来可以将第三方库的样式进行修改 templatedivel-input placeholderplaceholder v-modelname//div /templatescript setup let name ref() /scriptstyle scoped langscss .el-input{:deep(.el-input__inner) {background-color: red;} } /style:slotted 使用 :slotted() 将插槽中的类名包裹起来可以修改插槽中的元素样式 SlotTestCom.vue templatediv父组件slot/slot/div /templatestyle scoped :slotted(.msg) {font-weight: bold;color: red; } /styleSlotTestComdiv classmsg私人订制DIV/div /SlotTestCom:global 使用 :global() 用于设置全局样式 :global(div){font-size: 17px;color: #222222; }全局设置div的样式 css中使用v-bind let color ref(pink) // 随机一个颜色 const randomColor () {color.value rgb(${Math.random() * 255},${Math.random() * 255},${Math.random() * 255}) }使用 v-bind() 将JS中变量包裹起来即可使用 .el-input {width: 300px;:deep(.el-input__inner) {background-color: v-bind(color);} }Vue3集成Tailwind CSS 官网地址Tailwind CSS 中文文档 - 无需离开您的HTML即可快速建立现代网站。 安装 npm install -D tailwindcsslatest postcsslatest autoprefixerlatest生成配置文件 npx tailwindcss init -p修改配置文件 tailwind.config.js 2.6版本 module.exports {purge: [./index.html, ./src/**/*.{vue,js,ts,jsx,tsx}],theme: {extend: {},},plugins: [], }3.0版本 module.exports {content: [./index.html, ./src/**/*.{vue,js,ts,jsx,tsx}],theme: {extend: {},},plugins: [], }新建 index.css 并在 main.ts 中引入 tailwind base; tailwind components; tailwind utilities;基础使用 详细类名见文档https://www.tailwindcss.cn/docs/font-family templatediv classh-full flex justify-center items-center bg-teal-400div classtext-8xl text-rose-700font-bold text-whiteHello Word/div/div /templatenextTick vue 中更新DOM操作是异步的但是JS程序是同步的所以当遇到操作DOM时可能会出现延迟更新的情况vue 也给了一个解决方案就是可以将操作 DOM 的代码放在 nextTick 中执行nextTick 会执行一个 Promise 函数去更新DOM来实现同步更新DOM的操作 这样做的好处是可以提高程序性能例如执行一个for循环每次循环会改变变量的值然后吧这个变量输出到页面上。用一个watch去监听这个变量watch函数并不会触发多次而是只会执行一次 下面是一个小案例 templatediv classbox refboxdiv classitem v-for(item,index) in msgList :keyindex{{ item.msg }}/div/divel-input v-modelmsg stylewidth: 200px/el-button typeprimary clicksend发送/el-button /templatescript setup langts import {nextTick, ref, reactive} from vuelet msgList reactive([{msg: Hello world} ]) let msg ref() let box refHTMLDivElement()const send () {msgList.push({msg: msg.value})nextTick(() {// 发送完消息后自动滚动到底部box.value.scrollTop box.value.scrollHeight}) } /scriptstyle scoped langscss .box {width: 300px;border: 2px solid #ddd;height: 400px;overflow: auto;.item {height: 30px;line-height: 30px;padding-left: 1em;background-color: #dddddd;margin: 2px;} } /styleVue3开发安卓和IOS 参照博客https://xiaoman.blog.csdn.net/article/details/131507483 安装安卓开发工具 安装完成后打开 首次运行需要安装一些SDK ionic安装 npm install -g ionic/cli初始化项目 ionic start app tabs --type vueapp 项目名称tabs 使用的预设–type vue 使用的是vue就写vuereact就写react 启动项目 npm run dev打包和构建 先执行打包命令 npm run build再执行构建命令将程序打包成Android包 ionic capacitor copy android运行成功后会自动多一个android文件夹 然后运行下面命令进行预览 ionic capacitor open android会自动打开安卓编辑器 等待项目加载完成后点击绿色的箭头即可启动 H5适配 添加meat信息 meta nameviewport contentwidthdevice-width, initial-scale1.0清除默认样式 stylehtml,body,#app{height: 100%;overflow: hidden;}*{padding: 0;margin: 0;} /style圣杯布局 templatediv classheaderdiv/divdiv/divdiv/div/div /templatestyle scoped langscss .header{width: 100%;height: 50px;line-height: 50px;display: flex;div:nth-child(1),div:nth-child(3){width: 100px;background-color: deepskyblue;}div:nth-child(2){flex: 1;background-color: pink;} } /style使用postCSS将px单位转成vh和vw 百分比是相对于父元素 vw和vh相对于视口 编写postCSS插件 新建 plugins/PxToVwVh.ts import {Plugin} from postcsslet Options {defaultWidth: 390,defaultHeight: 844, } interface OptionsTypes {defaultWidth?:number,defaultHeight?:number, }export function PxToVwVh(options:OptionsTypesOptions):Plugin{let opt Object.assign({}, options)return {postcssPlugin:px-to-vw-vh,// 钩子函数Declaration(node){if(node.value.includes(px)){const num parseFloat(node.value)if(node.prop.includes(width)){node.value ${((num / opt.defaultWidth) * 100).toFixed(2)}vw}else if(node.prop.includes(height)){node.value ${((num / opt.defaultHeight) * 100).toFixed(2)}vh}}}} }在 tsconfig.node.json 中引入 {extends: tsconfig/node18/tsconfig.json,include: [vite.config.*,vitest.config.*,cypress.config.*,nightwatch.conf.*,playwright.config.*,plugins/**/*],compilerOptions: {composite: true,module: ESNext,moduleResolution: Bundler,types: [node],noImplicitAny: false} }include中添加 plugins/**/*noImplicitAny 允许隐式的使用any 使用插件 在 vite.config.ts 中使用 import { fileURLToPath, URL } from node:urlimport { defineConfig } from vite import vue from vitejs/plugin-vue import {PxToVwVh} from ./plugins/PxToVwVh;// https://vitejs.dev/config/ export default defineConfig({plugins: [vue(),],css: {postcss: {plugins: [PxToVwVh()]},},resolve: {alias: {: fileURLToPath(new URL(./src, import.meta.url))}} })效果展示 我们通过编写插件实现了将PX单位转换成相对于视口这样保证了在不同尺寸的屏幕上都会有一个相同的展示布局 全局控制字体大小 设置全局CSS变量 :root{--font-size:16px; }然后全局可以通过 var(–font-size) 使用 templatediv classheaderdiv返回/divdivH5适配/divdiv取消/div/divbutton clickchangeFontSize(15)默认/buttonbutton clickchangeFontSize(24)大/buttonbutton clickchangeFontSize(36)特大/button /templatescript setupimport {onMounted} from vue;onMounted((){document.documentElement.style.setProperty(--font-size,localStorage.getItem(fontSize) || 16px) })const changeFontSize (size) {document.documentElement.style.setProperty(--font-size,size px)localStorage.setItem(fontSize,size px); } /scriptstyle scoped langscss .header{width: 100%;height: 50px;line-height: 50px;display: flex;text-align: center;font-size: var(--font-size);div:nth-child(1),div:nth-child(3){width: 100px;background-color: deepskyblue;}div:nth-child(2){flex: 1;background-color: pink;} }button{margin-right: 10px; } /style点击按钮可以实现字体大小切换 unoCss原子化 官网https://unocss.dev/ 什么是css原子化 CSS原子化的优缺点 1.减少了css体积提高了css复用 2.减少起名的复杂度 3.增加了记忆成本 将css拆分为原子之后你势必要记住一些class才能书写哪怕tailwindcss提供了完善的工具链你写background也要记住开头是bg 安装 npm i -D unocss配置插件 // vite.config.ts import UnoCSS from unocss/vite import { defineConfig } from viteexport default defineConfig({plugins: [UnoCSS(),], })创建一个 uno.config.js 文件 // uno.config.js import { defineConfig } from unocssexport default defineConfig({// 自定义规则rules:[[red,{ color:red,font-size:25px }]] }) 在 main.ts 文件中添加 // main.ts import virtual:uno.css使用 直接在页面中使用类名即可 div classredHello Word /div动态配置类名 rules: [[/^m-(\d)$/, ([, d]) ({ margin: ${Number(d) * 10}px })],[flex, { display: flex }] ]使用 div classred m-10Hello Word /div使用预设 修改 uno.config.js // uno.config.js import { defineConfig,presetIcons,presetAttributify,presetUno } from unocssexport default defineConfig({// 自定义规则rules:[[/^m-(\d)$/, ([, d]) ({ margin: ${Number(d) * 10}px })],[red,{ color:red,font-size:25px }],],// 使用预设presets:[presetIcons(),presetAttributify(),presetUno()] })presetIcons 这个是图标 presetAttributify 这个是美化CSS presetUno 预设实验阶段是一系列流行的原子化框架的 通用超集包括了 Tailwind CSSWindi CSSBootstrapTachyons 等。 例如ml-3Tailwindms-2Bootstrapma4Tachyonsmt-10pxWindi CSS均会生效。 使用图标 在官网中找到自己需要的图标https://icones.js.org/ 然后选中后安装 查看页面路径上的单词然后安装 npm i -D iconify-json/svg-spinners点击某个要使用的图标复制类名即可 div classi-svg-spinners-bars-fade font-size-50px color-pink/divVue编译宏 首先vue版本必须是3.3及以上版本 子组件 templateel-button typeprimary clickadd添加/el-buttonulli v-foritem in props.nameList{{item}}/li/ul /templatescript setup langts import {defineProps,defineOptions,defineEmits,defineSlots} from vue// defineProps,可以定义类型 const props defineProps{nameList:string[] }()const add () {emit(addName,Tome) } // defineEmits,可以定义事件 // 第一个参数是事件名称第二个参数是事件参数类型问号表示可选 const emit defineEmits{(event:addName,args?:any):void }()// defineOptions常用来定义组件名字 defineOptions({name:DefineComponents })/script父组件 templateDefineComponents :nameListnameList addNameaddName/ /templatescript setup langts import DefineComponents from /components/DefineComponents.vue;let nameList:string[] reactive([张三,李四, 王五])const addName (args) {nameList.push(args) }/script函数名称含义defineProps接收父组件传递过来的参数defineEmits定义事件名称defineOptions配置组件名称和其他信息 Vue环境变量 在项目根目录新建两个文件分别表示开发环境配置、生成环境配置 注意设置环境变量时必须以 VITE_ 开头否则不生效 .env.development # .env.development VITE_APIhttp://localhost:8080.env.production # .env.production VITE_API/prod-api修改 package.json 中的运行命令,在启动dev是设置mode是development表示读取开发环境配置名称可以自定义但是要和上面新建的配置文件后缀名保持一致 scripts: {dev: vite --mode development, },然后在 vue 文件中通过下面方式获取配置项 console.log(import.meta.env)这里是开发环境读取到的 VITE_API 是 http://localhost:8080 然后打包项目再看一下打印结果 在 vite.config.ts 中获取环境变量时通过如下方式获取 import { defineConfig,loadEnv } from vitelet {VITE_API} loadEnv(process.env.NODE_ENV,process.cwd())console.log(VITE_API)控制台会打印出定义的环境变量 Webpack从0到1构建Vue3工程 项目结构 webpack-vue ├── config │ ├── webpack.dev.js │ └── webpack.prod.js ├── src │ ├── App.vue │ └── Child.vue ├── index.html ├── main.js ├── package.json └── pnpm-lock.yamlpackage.json {name: webpack-vue,version: 1.0.0,description: ,main: index.js,scripts: {test: echo \Error: no test specified\ exit 1,build: webpack --config config/webpack.prod.js,dev: webpack serve --config config/webpack.dev.js},keywords: [],author: ,license: ISC,dependencies: {vue/compiler-sfc: ^3.3.4,clean-webpack-plugin: ^4.0.0,css-loader: ^6.8.1,friendly-errors-webpack-plugin: ^1.7.0,html-webpack-plugin: ^5.5.3,less: ^4.2.0,less-loader: ^11.1.3,style-loader: ^3.3.3,typescript: ^5.2.2,vue: ^3.3.4,vue-loader: ^17.3.0,webpack: ^5.89.0,webpack-cli: ^5.1.4,webpack-dev-server: ^4.15.1} }webpack.dev.js const path require(path) const HtmlWebpackPlugin require(html-webpack-plugin); const {CleanWebpackPlugin} require(clean-webpack-plugin); const {VueLoaderPlugin} require(vue-loader);module.exports {mode:development,entry: ./main.js,output: {filename: js/[name].[contenthash:10].js,path: path.resolve(__dirname, dist)},module: {rules: [{test:/\.vue$/,use: vue-loader},{test: /\.css$/, //解析cssuse: [style-loader, css-loader],},{test:/\.less/,use: [style-loader,css-loader, less-loader],}]},resolve: {alias: {/: path.resolve(__dirname, ./src) // 别名},extensions: [.js, .json, .vue, .ts, .tsx] //识别后缀},plugins: [new CleanWebpackPlugin(),new VueLoaderPlugin(),new HtmlWebpackPlugin({template: ./index.html,}),],devServer: {port: 8088,open: true,host: localhost,historyApiFallback: true, // 解决vue-router刷新404问题proxy: {/api: {changeOrigin: true,pathRewrite: {^/api: }}}} }webpack.prod.js const path require(path) const HtmlWebpackPlugin require(html-webpack-plugin); const {CleanWebpackPlugin} require(clean-webpack-plugin); const {VueLoaderPlugin} require(vue-loader);module.exports {mode:production,entry: ./main.js,output: {filename: js/[name].[contenthash:10].js,path: path.resolve(__dirname, ../dist)},module: {rules: [{test:/\.vue$/,use: vue-loader},{test: /\.css$/, //解析cssuse: [style-loader, css-loader],},{test:/\.less/,use: [style-loader,css-loader, less-loader],}]},resolve: {alias: {: path.resolve(__dirname, ./src) // 别名},extensions: [.js, .json, .vue, .ts, .tsx] //识别后缀},plugins: [new CleanWebpackPlugin(),new VueLoaderPlugin(),new HtmlWebpackPlugin({template: ./index.html,}),], }Vite性能优化 打包优化 vite.config.js 添加 build 配置项 import { fileURLToPath, URL } from node:urlimport { defineConfig,loadEnv } from vite import vue from vitejs/plugin-vue import vueJsx from vitejs/plugin-vue-jsx import AutoImport from unplugin-auto-import/vite import Components from unplugin-vue-components/vite import { ElementPlusResolver } from unplugin-vue-components/resolvers import unocss from unocss/vitelet {VITE_API} loadEnv(process.env.NODE_ENV,process.cwd())console.log(VITE_API)// https://vitejs.dev/config/ export default defineConfig({module:es2022,plugins: [vue(),vueJsx(),AutoImport({resolvers: [ElementPlusResolver()],imports: [vue, vue-router],dts: src/auto-imports.d.ts}),Components({resolvers: [ElementPlusResolver()],}),unocss(),],resolve: {alias: {: fileURLToPath(new URL(./src, import.meta.url))}},css: {preprocessorOptions: {scss: {additionalData: import ./src/layout_v2/css/bem.scss;}}},build:{minify:esbuild, // esbuild打包速度最快terser 打包体积最小cssCodeSplit:true,// 拆分CSS文件chunkSizeWarningLimit:2000, // 单文件超过2000kb警告assetsInlineLimit:1024*10, // 静态资源文件低于10KB时自动转Base64} })Pinia 安装 npm install pinia在 main.ts 中引入 import {createApp} from vue import {createPinia} from piniaexport const app createApp(App) app.use(createPinia())app.mount(#app)基本使用 userInfoStore.js import {defineStore} from piniaexport const useUserInfoStore defineStore(userInfo, {state: () {return {name: 李斯特,age: 18}},getters: {userMsg() {return this.name --- this.age}},actions: {setName(newName) {console.log(this.name)this.name newName}} })actions 中的函数也是支持异步的this 指向指向的是 state 中返回的对象地址所以可以通过this来获取到 state 中的属性值 vue文件中使用方法 templatedivulli{{ userInfoStore.name }}/lili{{ userInfoStore.age }}/lili{{ userInfoStore.userMsg }}/li/ulel-button typeprimary clickchangechange/el-button/div /templatescript setup import {useUserInfoStore} from /stores/userInfoStore;const userInfoStore useUserInfoStore()const change () {userInfoStore.setName(张三丰) }/scriptPinia的一些API $reset 重置数据$subscribe 监听数据变化$onAction 监听 action 数据变化 import {useUserInfoStore} from /stores/userInfoStore;const userInfoStore useUserInfoStore()const change () {userInfoStore.setName(张三丰) }// $reset 重置数据 const reset () {userInfoStore.$reset() }// $subscribe 监听数据变化 userInfoStore.$subscribe((mutation, state) {console.log(mutation, state) })// $onAction 监听 action 数据变化 userInfoStore.$onAction((action, state) {console.log(action, state) })Pinia持久化缓存 安装 npm install pinia-plugin-persistedstate配置 import {createApp} from vue import {createPinia} from pinia import PiniaPluginPersistedstate from pinia-plugin-persistedstateexport const app createApp(App) // 配置Pinia并设置持久化缓存 const Pinia createPinia() Pinia.use(PiniaPluginPersistedstate)app.use(Pinia) app.mount(#app)然后在需要设置持久化缓存的pinia文件中开启persist配置 import {defineStore} from piniaexport const useUserInfoStore defineStore(userInfo, {state: () {return {name: 李斯特,age: 18}},getters: {userMsg() {return this.name --- this.age}},actions: {setName(newName) {console.log(this.name)this.name newName}},// 开启数据持久化persist: true })效果展示 它原理是将pinia数据保存到 localStorage 缓存中刷新页面后优先从缓存中读取如果缓存中没有则再从代码中读取 Echarts展示地图 效果图 安装 npm install echarts默认安装的是 5.x 版本 在这个版本中的引入方式必须是下面这种方法 import * as echarts from echarts源码 首先要下载好地图数据 china.js 下载地址https://szx-bucket1.oss-cn-hangzhou.aliyuncs.com/picgo/china.js下载到本地使用即可 地图实现源码 templatediv classh-full flex justify-center items-centerdiv idmapDom classh-full w-full/div/div /templatescript setup import { onMounted } from vue import * as echarts from echarts import ../assets/china import { getCityPositionByName } from /assets/cityPostion// 模拟10条数据 let mockData [{ name: 北京, value: 500 },{ name: 天津, value: 200 },{ name: 河南, value: 300 },{ name: 广西, value: 300 },{ name: 广东, value: 300 },{ name: 河北, value: 300 }, ]onMounted(() {let data mockData.map(i {let cityPosition getCityPositionByName(i.name).valuereturn {name: i.name,value: cityPosition.concat(i.value),}})let initMap echarts.init(document.querySelector(#mapDom))initMap.setOption({backgroundColor: transparent, // 设置背景色透明// 必须设置tooltip: {show: false,},// 地图阴影配置geo: {map: china,// 这里必须定义不然后面series里面不生效tooltip: {show: false,},label: {show: false,},zoom: 1.03,silent: true, // 不响应鼠标时间show: true,roam: false, // 地图缩放和平移aspectScale: 0.75, // scale 地图的长宽比itemStyle: {borderColor: #0FA3F0,borderWidth: 1,areaColor: #070f71,shadowColor: rgba(1,34,73,0.48),shadowBlur: 10,shadowOffsetX: -10,shadowOffsetY: 10,},select: {disabled: true,},emphasis: {disabled: true,areaColor: #00F1FF,},// 地图区域的多边形 图形样式 阴影效果// z值小的图形会被z值大的图形覆盖top: 10%,left: center,// 去除南海诸岛阴影 series map里面没有此属性regions: [{name: 南海诸岛,selected: false,emphasis: {disabled: true,},itemStyle: {areaColor: #00000000,borderColor: #00000000,},}],z: 1,},series: [// 地图配置{type: map,map: china,zoom: 1,tooltip: {show: false,},label: {show: true, // 显示省份名称color: #ffffff,align: center,},top: 10%,left: center,aspectScale: 0.75,roam: false, // 地图缩放和平移itemStyle: {borderColor: #3ad6ff, // 省分界线颜色 阴影效果的borderWidth: 1,areaColor: #17348b,opacity: 1,},// 去除选中状态select: {disabled: true,},// 控制鼠标悬浮上去的效果emphasis: { // 聚焦后颜色disabled: false, // 开启高亮label: {align: center,color: #ffffff,},itemStyle: {color: #ffffff,areaColor: #0075f4,// 阴影效果 鼠标移动上去的颜色},},z: 2,data: data,},{type: scatter,coordinateSystem: geo,symbol: pin,symbolSize: [50, 50],label: {show: true,color: #fff,formatter(value) {return value.data.value[2]},},itemStyle: {color: #e30707, //标志颜色},z: 2,data: data,},],}) }) /scriptcityPostion.js 文件代码这个文件主要是通过省份名称获取经纬度 const positionArr [{ name: 北京, value: [116.3979471, 39.9081726] },{ name: 上海, value: [121.4692688, 31.2381763] },{ name: 天津, value: [117.2523808, 39.1038561] },{ name: 重庆, value: [106.548425, 29.5549144] },{ name: 河北, value: [114.4897766, 38.0451279] },{ name: 山西, value: [112.5223053, 37.8357424] },{ name: 辽宁, value: [123.4116821, 41.7966156] },{ name: 吉林, value: [125.3154297, 43.8925629] },{ name: 黑龙江, value: [126.6433411, 45.7414932] },{ name: 浙江, value: [120.1592484, 30.265995] },{ name: 福建, value: [119.2978134, 26.0785904] },{ name: 山东, value: [117.0056, 36.6670723] },{ name: 河南, value: [113.6500473, 34.7570343] },{ name: 湖北, value: [114.2919388, 30.5675144] },{ name: 湖南, value: [112.9812698, 28.2008247] },{ name: 广东, value: [113.2614288, 23.1189117] },{ name: 海南, value: [110.3465118, 20.0317936] },{ name: 四川, value: [104.0817566, 30.6610565] },{ name: 贵州, value: [106.7113724, 26.5768738] },{ name: 云南, value: [102.704567, 25.0438442] },{ name: 江西, value: [115.8999176, 28.6759911] },{ name: 陕西, value: [108.949028, 34.2616844] },{ name: 青海, value: [101.7874527, 36.6094475] },{ name: 甘肃, value: [103.7500534, 36.0680389] },{ name: 广西, value: [108.3117676, 22.8065434] },{ name: 新疆, value: [87.6061172, 43.7909393] },{ name: 内蒙古, value: [111.6632996, 40.8209419] },{ name: 西藏, value: [91.1320496, 29.657589] },{ name: 宁夏, value: [106.2719421, 38.4680099] },{ name: 台湾, value: [120.9581316, 23.8516062] },{ name: 香港, value: [114.139452, 22.391577] },{ name: 澳门, value: [113.5678411, 22.167654] },{ name: 安徽, value: [117.2757034, 31.8632545] },{ name: 江苏, value: [118.7727814, 32.0476151] }, ]export function getCityPositionByName(name) {return positionArr.find(item item.name name) }Vue-Router 安装 npm install vue-router安装完成后检查一下安装的版本是否是 4.x 版本确保在 vue3 中可以使用 定义路由和404 新建 router/index.js import {createRouter,createWebHashHistory} from vue-routerconst router createRouter({// 定义路由模式哈希模式history:createWebHashHistory(),routes:[{path:/,component:()import(../views/home.vue)},{path:/about,component:()import(../views/about.vue)},// 匹配404页面当所有路径都匹配不到时就跳转到404{path: /:pathMatch(.*),component: ()import(../views/404.vue),},] })// 导出路由 export default router注册路由 main.js import { createApp } from vue import App from ./App.vue import router from ./routerconst app createApp(App)app.use(router)app.mount(#app)定义路由出口 App.vue templaterouter-view/ /template路由跳转 方式一router-link router-link classmr-10 to/home/router-link router-link to/aboutabout/router-linkrouter-link是vue-router内置的组件通过to属性定义要跳转的地址属性值要和路由中的 path 相对应 方式二通过js的方式跳转 定义两个按钮点击按钮实现跳转 button classmr-10 clicktoPath(/)home/button button clicktoPath(/about)about/buttonjs方法 import {useRouter} from vue-routerconst router useRouter()const toPath (url) {router.push({path:url}) }控制路由返回与前进 定义两个按钮分别实现返回和前进 button classmr-10 clickback()返回/button button classmr-10 clickadvance()前进/button实现两个方法 const back () {// 方式一// router.go(-1)// 方式二router.back() }const advance () {router.go(1) }replace 默认通过 push 的方式跳转会留下历史记录。如果不想留下历史记录可以通过 replace 这种方法跳转。 例如在登录成功后就可以使用 replace 来跳转 在 router-link 标签上添加 replace 属性 router-link replace classmr-10 to/home/router-link router-link replace classmr-10 to/aboutabout/router-link或者通过 router.replace const toPath (url) {router.replace({path:url}) }这种跳转方式不会留下历史记录 路由传参 通过添加 query 参数来实现传参 const toPath (url) {router.push({path:url,query:{id:1,name:李四,}}) }通过如下方法接收路由参数 template我是详情页接收到的路由参数是{{route.query}} /templatescript setup import {useRoute} from vue-router;const route useRoute()console.log(route.query)/script接收到到的是一个对象 动态URL 我们也可以将参数作为页面URL的一部分 首先定义路由 注意 这里要多定义一个参数name动态路由跳转时需要通过 name 来跳转 使用 /dyDetail/:xxx/:xxx 这种方式定义动态参数名称 {path:/dyDetail/:id/:name,name:DyDetail,component:()import(../views/dyDetail.vue) },添加跳转方法 const toDyDetail () {router.push({// 这里使用name来跳转name名称也要和路由中定义的name一致name:DyDetail,// 这里传递的属性名必须和路由中定义的参数名一致params:{id:1,name:张三}}) }获取动态路由参数方法通过 route.params 方法获取 templatedivid{{route.params.id}}/divdivname{{route.params.name}}/div /templatescript setup import {useRoute} from vue-router;const route useRoute()console.log(route.params)/script这里观察地址栏中的显示方式直接将参数获取url的一部分来显示 路由嵌套 定义路由 {path:/system,component:()import(../views/system/index.vue),children:[{path:menu,component:()import(../views/system/menu.vue)},{path:role,component:()import(../views/system/role.vue)},] }system/index.vue templatediv classparentbutton clicktoPath(menu)菜单管理/buttonbutton clicktoPath(role)角色管理/button/divrouter-view/ /templatescript setup import {useRouter} from vue-router;const router useRouter() const toPath (url) {router.push({path:/system/${url}}) }/scriptstyle scoped .parent{height: 45px;background-color: pink;display: flex;gap: 15px;align-items: center;justify-content: center; } /style跳转到子路由时需要加上父路由地址 重定向 {path:/system,// 重定向到第一个子菜单redirect:/system/menu,component:()import(../views/system/index.vue),children:[{path:menu,component:()import(../views/system/menu.vue)},{path:role,component:()import(../views/system/role.vue)},] }路由守卫 全局前置路由守卫 // 全局前置路由守卫 router.beforeResolve((to,from,next){console.log(to) // 去哪个页面console.log(from) // 从哪个页面来next() // 下一步必须要写否则无法跳转 })全局后置路由守卫 // 全局后置路由守卫 router.afterEach((to,from){console.log(to) // 去哪个页面console.log(from) // 从哪个页面来 })局部路由守卫 {path:menu,component:()import(../views/system/menu.vue),// 局部前置路由守卫beforeEnter:((to,from,next){console.log(to,局部前置路由守卫)console.log(from,局部前置路由守卫)next()}) },滚动行为 import {createRouter,createWebHashHistory} from vue-routerconst router createRouter({// 定义路由模式哈希模式history:createWebHashHistory(),// 滚动模式scrollBehavior:(to,from,savedPosition){if(savedPosition){// 如果有滚动的位置则重新回到之前滚动的位置return savedPosition}else{// 否则页面滚动到顶部return {x:0,y:0}}},routes:[{path:/,component:()import(../views/home.vue)},{path:/about,component:()import(../views/about.vue)},{path:/detail,component:()import(../views/detail.vue)},] })// 导出路由 export default router动态路由 在后台管理系统中常见的场景根据不同的角色显示不同的菜单 编写方法根据不同的账号名返回不同的菜单 export function getDynamicRouting(name){return new Promise((resolve,reject){// root角色登录if(name admin){resolve([{path:/about,component:about.vue},{path:/detail,component:detail.vue},{path:/system,redirect:/system/menu,component:system/index.vue,children:[{path:menu,component:system/menu.vue,},{path:role,component:system/role.vue},],},])}// 普通人员登录if(name tome){resolve([{path:/about,component:about.vue},{path:/detail,component:detail.vue},])}}) }login.vue 登录成功后根据返回的路由信息添加路由 templatedivinput placeholder请输入账号 v-modelname/input placeholder请输入密码 typepassword v-modelpwd/button clicklogin登录/button/div /templatescript setup import {ref} from vue; import router from ../router import {getDynamicRouting} from ../../mock/mockRouter.js;let name ref() let pwd ref()const login () {getDynamicRouting(name.value).then(routers{let dyRouter setDyRouter(routers)// 只需要添加一级路由信息即可dyRouter.forEach(rootRouter{router.addRoute(rootRouter)})}) }const setDyRouter (routers,parentPath) {routers.forEach(item{item.component import(../views/${item.component})if(!item.path.startsWith(/)){item.path ${parentPath}/${item.path}}if(item.children){setDyRouter(item.children,item.path)}})return routers } /script测试 首先用admin登录然后点击菜单管理可以正常返回 然后刷新页面使用tome登录然后点击菜单管理发现是404 上面的例子只是简单的实现了一个动态路由实际开发中我们会根据接口返回的路由数据渲染不同的菜单来显示 MarkDown语法高亮 安装 npm install marked highlight.js --save or pnpm add marked highlight.js --save注册 import ./assets/main.css import { createApp } from vue import { createPinia } from pinia import App from ./App.vue import router from ./router import highlight from highlight.js import highlight.js/styles/atom-one-dark.cssconst app createApp(App)app.use(createPinia()) app.use(router)app.directive(highlight,function(el){let blocks el.querySelectorAll(pre code);blocks.forEach((block){highlight.highlightBlock(block);}) })app.mount(#app)使用 div v-highlight v-htmlcontent/divscript import { marked } from marked const content ref() // 需要使用marked方法吧语法转成html页面 content marked(content) /script效果
http://www.zqtcl.cn/news/662723/

相关文章:

  • 刘连康seo培训哪家强网站优化推广平台
  • 网站推广内容滁州做网站的
  • 黄山做网站公司山东省住房和城乡建设厅举报电话
  • 中医科网站建设素材上海文明城市建设网站
  • html课程教学网站模板手机微信小程序开发教程
  • 用电脑做兼职的网站比较好食品网站建设网站定制开发
  • 网站开发 加密保护小程序制作开发进度表
  • 深圳坪山站外贸展示型网站建设
  • 手机端自定义做链接网站济南网站制作方案
  • 软件网站是怎么做的帮别人做网站赚多少钱
  • 纯静态网站 搜索功能佛山网站建设 奇锐科技
  • 四川省建设厅官方网站联系电话自己网站做虚拟币违法吗
  • 同城招聘网站自助建站2014 网站建设
  • 个人网站空间大小江油官方网站建设
  • 怎样建网站做什么网站能吸引流量
  • 做vi设计的网站网络营销推广思路
  • 简述网站设计流程沁水做网站
  • 南京公司网站建设怎么收费获奖网页设计
  • 网站域名试用期水墨风格网站源码
  • 长沙网站开长沙手机网站建设哪些内容
  • 网站建设算固定资产吗做泵阀生意到哪个网站
  • 佛山网站建设定制杭州人防质监站网址
  • 什么网站可以做微官网定制小程序制作一个需要多少钱
  • 扒下来的网站怎么做修改什么样是权网站重高的
  • 淘宝客做网站链接潍坊网站建设wfzhy
  • 怎样做二维码链接到网站上做的比较好的美食网站有哪些
  • 自动化科技产品网站建设响应式博客wordpress
  • 个人建站如何赚钱男人的好看网
  • 门户网站建设管理工作作一手房用什么做网站
  • 网站建设优化服务案例三合一网站程序