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

第4章:构建自己的物料解决方案

数据拦截简化数据获取流程

我们可以发现原来我们获取到的数据是这样的情况,里面的东西太多太复杂了,故此我们需要将进行简化。而我们在开发的过程中主要是需要data,code,message。

在src\utils\request.js里面添加这个代码

/*** 响应拦截器:* 服务器返回数据之后,前端 .then之前被调用*/
service.interceptors.response.use(response =>{const{success , message ,data } =response.dataif (success){return data}//TODO: 业务请求错误return Promise.reject(new Error(message))
})

更改完代码后我们可以发现,变成了如下的json数据。

业务组件:移动端navigationBar

首先我们要将mobile里面的调用数据的方法放到去父文件中的index.vue里面。

import { ref } from 'vue';
import {getCategory} from '@/api/category'const categorys = ref([ ])
const getCategoryData = async () => {const { categorys } = await getCategory()categorys.value = categorysconsole.log(categorys.value)
}
getCategoryData()

渲染数据

  1. 首先要在navigation里面修改为以下数据
<template><mobile-navigation-vue v-if="isMobile" :data="categoryData"/>
</template><script setup>
import { isMobile } from '@/utils/flexible'
import mobileNavigationVue from './mobile/index.vue'
import { ref } from 'vue';
import {getCategory} from '@/api/category'const categoryData = ref([ ])
const getCategoryData = async () => {const { categorys } = await getCategory()categoryData.value = categorys
}
getCategoryData()
</script><style scoped lang="scss"></style>
  1. 然后要在mobile里面渲染数据
<template><div ><ul><li v-for="item  in data" :key="item.id">{{ item.name }}</li></ul></div>
</template>

  1. 最后通过tailwind修改其样式
<template><div class="bg-white sticky top-0 left-0 z-10" ><ul class="relative flex overflow-x-auto p-1 text-xs text-zinc-600 overflow-hidden"><li v-for="item  in data" :key="item.id" class="shrink-0 px-1.5 py-0.5 z-10 duration-200">{{ item.name }}</li></ul></div>
</template>

动态rem基准值+修正tailwindcss样式

  1. 在src\utils\flexible.js里面添加动态rem修改方法
/*** 动态rem基准值,最大不超过40px* 根据用户的屏幕宽度,进行一些计算,把计算出来的值赋值给 html根标签作为fontsize大小*/
export const useREM =()=>{//定义最大的 fontsizeconst MAX_SIZE=40//监听 html 文档被解析完成的事件document.addEventListener('DOMContentLoaded',() =>{//拿到 html 标签const html = document.querySelector('html')//计算 fontsize,根据屏幕宽度/10let fontsize = window.innerWidth/10fontsize = fontsize>MAX_SIZE?MAX_SIZE:fontsize//赋值给 htmlhtml.style.fontSize = fontsize+'px'})
}
  1. 在mian.js里面修改
import { createApp } from 'vue'
import App from './App.vue'
import './styles/index.scss'
import router from './router'
import { useREM } from './utils/flexible'useREM()
createApp(App).use(router).mount('#app')
  1. 最后修改tailwind.config.js里面的方法
module.exports = {//tailwind可以应用的地方content: ['./index.html','./src/**/*.{vue,js}'],theme: {extend: {fontSize:{xs: ['0.25rem','0.35rem'],sm: ['0.35rem','0.45rem'],base: ['0.45rem','0.55rem'],lg: ['0.55rem','0.65rem'],xl: ['0.65rem','0.75rem']}},},plugins: [],
}

处理通用组件svg-icon

  1. 首先我们要构建svg-icon
<template><svg aria-hidden="true"><use :xlink:href="symbolId" :class="fillClass" :fill="color" /></svg>
</template><script setup>import { computed } from 'vue'const props = defineProps({// 显示的 svgname: {type: String,required: true},// svg 图标的颜色color: {type: String},// tailwind 指定 svg 颜色的类名fillClass: {type: String}
})
// 真实显示的 svg 图标 (拼接 #icon-)
const symbolId = computed(() => `#icon-${props.name}`)
</script><style lang="scss" scoped></style>
  1. 其次注册svg-icon
import svgIcon from './svg-icon/index.vue'export default{install(app){app.component('m-svg-icon',svgIcon)}
}
import { createApp } from 'vue'
import App from './App.vue'
import './styles/index.scss'
import router from './router'
import { useREM } from './utils/flexible'
import mLibs from './lib'useREM()
createApp(App).use(router).use({mLibs}).mount('#app')
  1. 修改svg-icon占位符
<template><div class="bg-white sticky top-0 left-0 z-10" ><ul class="relative flex overflow-x-auto p-1 text-xs text-zinc-600 overflow-hidden"><li class="fixed top-0 right-[-1px] h-4 px-1 flex items-center bg-white z-20 shadow-l-white"><m-svg-icon class="w-1.5 h-1.5" name="hamburger"></m-svg-icon></li><li v-for="item  in data" :key="item.id" class="shrink-0 px-1.5 py-0.5 z-10 duration-200 last:mr-4">{{ item.name }}</li></ul></div>
</template>
module.exports = {//tailwind可以应用的地方content: ['./index.html','./src/**/*.{vue,js}'],theme: {extend: {fontSize:{xs: ['0.25rem','0.35rem'],sm: ['0.35rem','0.45rem'],base: ['0.45rem','0.55rem'],lg: ['0.55rem','0.65rem'],xl: ['0.65rem','0.75rem']}},boxShadow:{'l-white':'-10px 0 10px white'}},plugins: [],
}

vite处理svg-icon

无论是vue-cli还是vite默认它们都不会主动导入svg矢量图标,因此我们需要使用一个vite的plugin

  1. 首先安装vite-plugin-svg-icons到项目中
npm i --save-dev vite-plugin-svg-icons@2.0.1
  1. 在vite.config.js中注册
......
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'// https://vitejs.dev/config/
export default defineConfig({plugins: [vue(),createSvgIconsPlugin({// 指定需要缓存的图标文件夹iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],// 指定 symbolId 格式symbolId: 'icon-[name]'})],......
})
  1. 在main.js里面注册icon图标
// 注册 svg-icons
import 'virtual:svg-icons-register'

slider滑块处理

在src\views\main\components\navigation\mobile\index.vue里面添加代码

<!-- 滑块 --><li ref="sliderTarget" :style="sliderStyle" class=" absolute h-[22px] bg-zinc-900 rounded-lg duration-200"><script setup>
import { ref } from 'vue';......const sliderStyle = ref({transform: 'translateX(0px)',width:'60px'
})
</script>

处理滑块要想到达目的主要是从以下几个方面考虑

  1. 选中的item下标:currentCategoryIndex
  2. 所有item元素:itemRefs
  3. ul的横向滚动偏离位置:ulScrollLeft
  4. 最后在currentCategoryIndex发生改变时,获取item下标元素的left和width,计算sliderStyle即可
<template><div class="bg-white sticky top-0 left-0 z-10"><ulref="ulTarget"class="relative flex overflow-x-auto p-1 text-xs text-zinc-600 overflow-hidden"><!-- 滑块 --><liref="sliderTarget":style="sliderStyle"class="absolute h-[22px] bg-zinc-900 rounded-lg duration-200"></li><liclass="fixed top-0 right-[-1px] h-4 px-1 flex items-center bg-white z-20 shadow-l-white"><m-svg-icon class="w-1.5 h-1.5" name="hamburger"></m-svg-icon></li><!-- items --><liv-for="(item, index) in data":key="item.id"class="shrink-0 px-1.5 py-0.5 z-10 duration-200 last:mr-4":class="{'text-zinc-100': currentCategoryIndex === index}":ref="setItemRef"@click="onItemClick(index)">{{ item.name }}</li></ul></div>
</template><script setup>
import { useScroll } from '@vueuse/core'
import { onBeforeUpdate, ref, watch } from 'vue'// 在vite 构建项目中,我们可以直接使用 defineProps方法
defineProps({data: {type: Array,required: true}
})const sliderStyle = ref({transform: 'translateX(0px)',width: '60px'
})//选中 item下标
const currentCategoryIndex = ref(0)//获取所有的item函数
let itemRefs = []
const setItemRef = (el) => {if (el) {itemRefs.push(el)}
}//数据改变之后,DON改变之前
onBeforeUpdate(() => {itemRefs = []
})//获取url元素
const ulTarget = ref(null)
//通过vueuse里面的useScroll获取响应式的scroll滚动距离
const { x: ulScrollLeft } = useScroll(ulTarget)// watch 监听
watch(currentCategoryIndex, (val) => {const { left, width } = itemRefs[val].getBoundingClientRect()sliderStyle.value = {// 滑块的位置 = ul 横向滚动的位置 + 当前元素的 left - ul 的 paddingtransform: `translateX(${ulScrollLeft.value + left - 10}px)`,width: width + 'px'}
})// item 点击事件
const onItemClick = (index) => {currentCategoryIndex.value = index
}
</script><style scoped lang=""></style>

补全category

首先要在src\constants\index.js定义一个常量

//category的本地构建数据
export const ALL_ITEM= {id: 'all',name: '全部'
}

然后在src\views\main\components\navigation\index.vue里面,添加首个元素

import { ALL_ITEM } from '@/constants';const categoryData = ref([ ])
const getCategoryData = async () => {const { categorys } = await getCategory()categoryData.value = categoryscategoryData.value.unshift(ALL_ITEM)
}

弹出窗口popup

我们点击按钮时候,会有一个弹出窗口popup自低而上弹出,那么这样的一个功能,我们一样可以把它处理为项目的通用组件。以下是popup的能力:

  1. 当popup展开时,内容视图应该不属于任何一个组件内部,而应该直接被插入到body下面
  2. popip应该包含两部分内容,一部分是背景蒙板,一部分为内容的包裹容器
  3. popip应该通过一个双向绑定进行控制展示和隐藏
  4. popup展示时,滚动应该被锁定
  5. 内容区域应该接受所有的attrs,并且应该通过插槽让调用方指定其内容

简单导入:

  1. 在src\libs\popup\index.vue添加一下内容
<template><div><teleport to='body' ><!-- 蒙版 --><div>蒙版</div><!-- 内容 --><div>组件</div></teleport></div>
</template><script setup>
</script><style lang='scss' scoped></style>
  1. 将m-popup注册
import svgIcon from './svg-icon/index.vue'
import popup from './popup/index.vue'export default {install(app) {app.component('m-svg-icon', svgIcon)app.component('m-popup',popup)}
}
  1. 将其在src\views\main\components\navigation\mobile\index.vue实现
<template><div class="bg-white sticky top-0 left-0 z-10"><ul>......</ul><m-popup/></div>
</template>

最终实现

src\views\main\components\navigation\mobile\index.vue

<template><div class="bg-white sticky top-0 left-0 z-10"><ulref="ulTarget"class="relative flex overflow-x-auto p-1 text-xs text-zinc-600 overflow-hidden"><!-- 滑块 --><liref="sliderTarget":style="sliderStyle"class="absolute h-[22px] bg-zinc-900 rounded-lg duration-200"></li><!-- 按钮 --><liclass="fixed top-0 right-[-1px] h-4 px-1 flex items-center bg-white z-20 shadow-l-white"@click="onShowPopup"><m-svg-icon class="w-1.5 h-1.5" name="hamburger"></m-svg-icon></li><!-- items --><liv-for="(item, index) in data":key="item.id"class="shrink-0 px-1.5 py-0.5 z-10 duration-200 last:mr-4":class="{'text-zinc-100': currentCategoryIndex === index}":ref="setItemRef"@click="onItemClick(index)">{{ item.name }}</li></ul><m-popup v-model="isVisable"><div>我是内容</div></m-popup></div>
</template><script setup>
import { useScroll } from '@vueuse/core'
import { onBeforeUpdate, ref, watch } from 'vue'// 在vite 构建项目中,我们可以直接使用 defineProps方法
defineProps({data: {type: Array,required: true}
})const sliderStyle = ref({transform: 'translateX(0px)',width: '52px'
})//选中 item下标
const currentCategoryIndex = ref(0)//获取所有的item函数
let itemRefs = []
const setItemRef = (el) => {if (el) {itemRefs.push(el)}
}//数据改变之后,DON改变之前
onBeforeUpdate(() => {itemRefs = []
})//获取url元素
const ulTarget = ref(null)
//通过vueuse里面的useScroll获取响应式的scroll滚动距离
const { x: ulScrollLeft } = useScroll(ulTarget)// watch 监听
watch(currentCategoryIndex, (val) => {const { left, width } = itemRefs[val].getBoundingClientRect()sliderStyle.value = {// 滑块的位置 = ul 横向滚动的位置 + 当前元素的 left - ul 的 paddingtransform: `translateX(${ulScrollLeft.value + left - 10}px)`,width: width + 'px'}
})// item 点击事件
const onItemClick = (index) => {currentCategoryIndex.value = index
}//控制popup展示
const isVisable = ref(false)
const onShowPopup =() =>{isVisable.value=true
}
</script><style scoped lang=""></style>

src\libs\popup\index.vue

<template><div ><teleport to='body' ><!-- 蒙版 --><transition name="fade" v-if="modelValue" @click="emits('update:modelValue',false)"><div class="w-screen h-screen bg-zinc-900/80 z-40 fixed top-0 left-0"></div></transition><!-- 内容 --><transition name="popup-down-up"><div v-bind="$attrs" class="w-screen bg-white z-60 fixed bottom-0" v-if="modelValue"><slot/></div></transition>         </teleport></div>
</template><script setup>
import { ref, watch } from 'vue';
import { useScrollLock } from '@vueuse/core';const props = defineProps({modelValue:{required: true,type: Boolean}
})const emits =  defineEmits(['update:modelValue'])//锁定滚动
const isLocked =  useScrollLock(document.body)
watch(() => props.modelValue,(val) =>{isLocked.value = val},{immediate:true}
)
</script><style lang='scss' scoped>//fade动画
.fade-enter-active,
.fade-leave-active{transition: all 0.3s;
}//准备进入,离开完成
.fade-enter-from,
.fade-leave-from{opacity: 0;
}.popup-down-up-enter-active,
.popup-down-up-leave-active{transition: all 0.3s;}.popup-down-up-enter-from,
.popup-down-up-leave-from{transform: translateY(100%);
}
</style>

双向数据绑定优化

useVModel可以直接帮我们完成数据间的双向绑定,主要是将项目里面的v-model改为isVisable

<template><div ><teleport to='body' ><!-- 蒙版 --><transition name="fade" v-if="isVisable" @click="isVisable =false"><div class="w-screen h-screen bg-zinc-900/80 z-40 fixed top-0 left-0"></div></transition><!-- 内容 --><transition name="popup-down-up"><div v-bind="$attrs" class="w-screen bg-white z-60 fixed bottom-0" v-if="modelValue"><slot/></div></transition>         </teleport></div>
</template><script setup>
import { ref, watch } from 'vue';
import { useScrollLock,useVModel } from '@vueuse/core';const props = defineProps({modelValue:{required: true,type: Boolean}
})defineEmits(['update:modelValue'])//是一个响应式数据,当isVisable 值发送改变时,会自动触发emit修改modelValue
const isVisable = useVModel(props)//锁定滚动
const isLocked =  useScrollLock(document.body)
watch(isVisable,(val) =>{isLocked.value = val},{immediate:true}
)
</script><style lang='scss' scoped>//fade动画
.fade-enter-active,
.fade-leave-active{transition: all 0.3s;
}//准备进入,离开完成
.fade-enter-from,
.fade-leave-from{opacity: 0;
}.popup-down-up-enter-active,
.popup-down-up-leave-active{transition: all 0.3s;}.popup-down-up-enter-from,
.popup-down-up-leave-from{transform: translateY(100%);
}
</style>

vite通用组件自动化注册

目前我们在项目中已经完成了两个通用组件,将来我们还需要更多的通用组件开发,如果每次开发完成一个通用组件之后,都要手动去注册,未免太麻烦了,所以我们通过vite提供的功能,进行组件自动化注册。

  1. vite的Glob功能:改功能帮助我们可以在文件系统中导入多个模块
  2. vue的defineAsyncComponent方法:该方法可以创建一个按需加载的异步组件

基于上面的两个方法,实现组件自动注册

最终代码

import { defineAsyncComponent } from 'vue'export default {install(app) {//1.获取当前路径下所有文件夹中的index.vueconst components =  import.meta.glob('./*/index.vue')//2.遍历获取到的组件模块for(const [fullPath,fn] of Object.entries(components)){//3.利用app.component进行注册 ./popup/index.vu分割为popupconst componentName ='m-'+fullPath.replace('./','').split('/')[0]app.component(componentName,defineAsyncComponent(fn))}}
}

最终代码

<template><div class="bg-white sticky top-0 left-0 z-10"><ulref="ulTarget"class="relative flex overflow-x-auto p-1 text-xs text-zinc-600 overflow-hidden"><!-- 滑块 --><liref="sliderTarget":style="sliderStyle"class="absolute h-[22px] bg-zinc-900 rounded-lg duration-200"></li><!-- 按钮 --><liclass="fixed top-0 right-[-1px] h-4 px-1 flex items-center bg-white z-20 shadow-l-white"@click="onShowPopup"><m-svg-icon class="w-1.5 h-1.5" name="hamburger"></m-svg-icon></li><!-- items --><liv-for="(item, index) in data":key="item.id"class="shrink-0 px-1.5 py-0.5 z-10 duration-200 last:mr-4":class="{'text-zinc-100': currentCategoryIndex === index}":ref="setItemRef"@click="onItemClick(index)">{{ item.name }}</li></ul><m-popup v-model="isVisable"><menu-vue :categorys="data" @onItemClick="onItemClick"></menu-vue></m-popup></div>
</template><script setup>
import { useScroll } from '@vueuse/core'
import { onBeforeUpdate, ref, watch } from 'vue'
import MenuVue from '@/views/main/components/menu/index.vue'// 在vite 构建项目中,我们可以直接使用 defineProps方法
defineProps({data: {type: Array,required: true}
})const sliderStyle = ref({transform: 'translateX(0px)',width: '52px'
})//选中 item下标
const currentCategoryIndex = ref(0)//获取所有的item函数
let itemRefs = []
const setItemRef = (el) => {if (el) {itemRefs.push(el)}
}//数据改变之后,DON改变之前
onBeforeUpdate(() => {itemRefs = []
})//获取url元素
const ulTarget = ref(null)
//通过vueuse里面的useScroll获取响应式的scroll滚动距离
const { x: ulScrollLeft } = useScroll(ulTarget)// watch 监听
watch(currentCategoryIndex, (val) => {const { left, width } = itemRefs[val].getBoundingClientRect()sliderStyle.value = {// 滑块的位置 = ul 横向滚动的位置 + 当前元素的 left - ul 的 paddingtransform: `translateX(${ulScrollLeft.value + left - 10}px)`,width: width + 'px'}
})// item 点击事件
const onItemClick = (index) => {currentCategoryIndex.value = indexisVisable.value=false
}//控制popup展示
const isVisable = ref(false)
const onShowPopup =() =>{isVisable.value=true
}
</script><style scoped lang=""></style>
<template><div class="py-2 h-[80vh] flex flex-col"><h2 class="text-xl text-zinc-900 font-bold mb-2 px-1">所有分类</h2><ul class="overflow-y-scroll"><liv-for="(item, index) in categorys":key="item.id"class="text-lg text-zinc-900 px-1 py-1.5 duration-100 active: bg-zinc-100"@click="$emit('onItemClick',index)">{{ item.name }}</li></ul></div></template>
<script setup>
defineProps({categorys:{type: Array,required:true}})//推荐使用的item进行注册
defineEmits(['onItemClick'])
</script><style lang='scss' scoped></style>
http://www.dtcms.com/a/394214.html

相关文章:

  • 华为昇腾 950 系列芯片深度解析
  • 2025华为杯 C题围岩裂隙精准识别与三维模型重构保姆级教程思路分析【国奖版】
  • 搭建Electron桌面项目
  • Linux 线程之pthread库
  • 内存泄漏、内存溢出与内存访问越界
  • C++初阶(11)string类的模拟实现
  • Python快速入门专业版(三十九):Python集合:去重与集合运算(交集、并集、差集)
  • pytorch 中meshgrid()函数详解
  • 深度探秘GAIA:一个为下一代AI量身打造的挑战性基准
  • 今日分享C++ ---继承
  • TableGPT:浙江大学发布的表格大模型
  • Linux 概述
  • 领码学堂·定时任务新思维[二]——七大替代方案总览:场景、优缺点与快速选型
  • NLP:详解FastText
  • 【力扣】hot100系列(一)哈希部分解析(多解法+时间复杂度分析)
  • 用AI开发HTML双语阅读工具助力英语阅读
  • AI论文速读 | 当大语言模型遇上时间序列:大语言模型能否执行多步时间序列推理与推断
  • 如何使用升腾C92主机搭建本地Linux编译服务器并通过Windows映射访问共享目录
  • 测试DuckDB-rs项目中的示例程序
  • 分布式协议与算法实战-实战篇
  • 【硬件-笔试面试题-105】硬件/电子工程师,笔试面试题(知识点:详细讲讲什么是链表和数组)
  • 【获取地址栏的搜索关键字】功能-总结
  • 关于__sync_bool_compare_and_swap的使用及在多核多线程下使用时的思考
  • 【嵌入式简单外设篇】-433MHz 无线遥控模块
  • 计算机视觉(opencv)实战三十——摄像头实时风格迁移,附多种风格转换
  • 【数据分享】《中国农村统计年鉴》(1985-2024年)全pdf和excel
  • 2025年中国研究生数学建模竞赛“华为杯”C题 围岩裂隙精准识别与三维模型重构完整高质量成品 思路 代码 结果分享!全网首发!
  • [Linux]文件与 fd
  • FFmpeg 深入精讲(二)FFmpeg 初级开发
  • 睡眠脑电技术文章大纲