企业级通用业务 Header 处理方案

目录

01: 处理 PC 端基础架构 

02: 通用组件:search 搜索框能力分析

03: 通用组件:search 搜索框样式处理

04: 通用组件:Button 按钮能力分析 

05: 通用组件:Button 按钮功能实现 

06: 通用组件:完善 search 基本能力

07: 通用组件:popover 气泡卡片能力分析

08: 通用组件:popover 气泡卡片基础功能实现 

09: 通用组件:popover 功能延伸,控制气泡展示位置 

10: 通用组件:处理慢速移动时,气泡消失问题 


01: 处理 PC 端基础架构 

- layout
- - components
- - - header
- - - - index.vue
- - - floating.vue
- - - main.vue
- - index.vue
// 设置 header 和 main 区域高度// tailwind.config.js
module.exports = {……theme: {extend: {……height: {header: '72px',main: 'calc(100vh - 72px)'}}}
}// 使用
// l-white => shadow-l-white
// height  => h-header<header-vue class="h-header"></>
<main-vue class="h-main"></>

02: 通用组件:search 搜索框能力分析

        既然是通用组件,就需要分析它的能力,它应该具备什么样的功能:

        1. 输入内容实现双向数据绑定

        2. 鼠标移入与获取焦点时的动画

        3. 一键清空文本功能

        4. 搜索触发功能

        5. 可控制,可填充的下拉展示区

        6. 监听到以下事件列表:

                1. clear:删除所有文本事件

                2. input:输入事件

                3. focus:获取焦点事件

                4. blur:失去焦点事件

                5. search:触发搜索(点击或回车)事件

03: 通用组件:search 搜索框样式处理

 

- libs
- - search
- - - index.vue
<template><divref="containerTarget"class="group relative p-0.5 rounded-xl border-white duration-500 hover:bg-red-100/40"><div><!-- 搜索图标 --><m-svg-iconclass="w-1.5 h-1.5 absolute translate-y-[-50%] top-[50%] left-2"name="search"color="#707070"/><!-- 输入框 --><inputclass="block w-full h-[44px] pl-4 text-sm outline-0 bg-zinc-100 dark:bg-zinc-800 caret-zinc-400 rounded-xl text-zinc-900 dark:text-zinc-200 tracking-wide font-semibold border border-zinc-100 dark:border-zinc-700 duration-500 group-hover:bg-white dark:group-hover:bg-zinc-900 group-hover:border-zinc-200 dark:group-hover:border-zinc-700 focus:border-red-300"type="text"placeholder="搜索"v-model="inputValue"@focus="onFocusHandler"@blur="onBlurHandler"@keyup.enter="onSearchHandlder"/><!-- 删除按钮 --><m-svg-iconv-show="inputValue"name="input-delete"class="h-1.5 w-1.5 absolute translate-y-[-50%] top-[50%] right-9 duration-500 cursor-pointer"@click="onClearClick"></m-svg-icon><!-- 分割线 --><divclass="opacity-0 h-1.5 w-[1px] absolute translate-y-[-50%] top-[50%] right-[62px] duration-500 bg-zinc-200 group-hover:opacity-100"></div><!-- TODO: 搜索按钮(通用组件) --><m-buttonclass="absolute translate-y-[-50%] top-[50%] right-1 rounded-xl duration-500 opacity-0 group-hover:opacity-100"icon="search"iconColor="#ffffff"@click="onSearchHandlder"></m-button></div><!-- 下拉区 --><transition name="slide"><divv-if="$slots.dropdown"v-show="isFocus"class="max-h-[368px] w-full text-base overflow-auto bg-white dark:bg-zinc-800 absolute z-20 left-0 top-[56px] p-2 rounded border border-zinc-200 dark:border-zinc-600 duration-200 hover:shadow-3xl scrollbar-thin scrollbar-thumb-zinc-200 dark:scrollbar-thumb-zinc-900 scrollbar-track-transparent"><slot name="dropdown" /></div></transition></div>
</template><script>
// 更新事件
const EMIT_UPDATE_MODELVALUE = 'update:modelValue'
// 触发搜索(点击或回车)事件
const EMIT_SEARCH = 'search'
// 删除所有文本事件
const EMIT_CLEAR = 'clear'
// 输入事件
const EMIT_INPUT = 'input'
// 获取焦点事件
const EMIT_FOCUS = 'focus'
// 失去焦点事件
const EMIT_BLUR = 'blur'
</script><script setup>
import { watch, ref } from 'vue'
import { useVModel, onClickOutside } from '@vueuse/core'const props = defineProps({modelValue: {type: String,required: true}
})const emits = defineEmits([EMIT_UPDATE_MODELVALUE,EMIT_CLEAR,EMIT_INPUT,EMIT_FOCUS,EMIT_BLUR,EMIT_SEARCH
])// 输入文本
const inputValue = useVModel(props)/*** 清空文本*/
const onClearClick = () => {inputValue.value = ''emits(EMIT_CLEAR, '')
}/*** 触发搜索*/
const onSearchHandlder = () => {emits(EMIT_SEARCH, inputValue.value)
}/*** 监听焦点行为*/
const isFocus = ref(false)
const onFocusHandler = () => {isFocus.value = trueemits(EMIT_FOCUS)
}/*** 失去焦点*/
const onBlurHandler = () => {emits(EMIT_BLUR)
}/*** 点击区域外隐藏 dropdown*/
const containerTarget = ref(null)
onClickOutside(containerTarget, () => {isFocus.value = false
})/*** 监听输入行为*/
watch(inputValue, (val) => {emits(EMIT_INPUT, val)
})
</script><style lang="scss" scoped>
.slide-enter-active {transition: all 0.5s;
}.slide-leave-active {transition: all 0.5s;
}.slide-enter-from,
.slide-leave-to {transform: translateY(40px);opacity: 0;
}
</style>

04: 通用组件:Button 按钮能力分析 

对于这个按钮来说,我们期望拥有以下能力:

        1. 可以显示文字按钮,并提供 loading 功能

        2. 可以显示 icon 按钮,并可以任意指定 icon 颜色

        3. 可以开关的点击动画

        4. 可以指定各种风格和大小

        5. 当指定的风格或大小不符合预设时,需要给开发者以提示消息

05: 通用组件:Button 按钮功能实现 

- libs
- - button
- - - index.vue
/*** 实现步骤:* 1. 构建 type 风格可选项 和 size 大小可选项* 2. 通过 props 让开发者控制按钮* 3. 区分 icon button 和 text button* 4. 依据当前数据,实现视图* 5. 处理点击事件*/

 书写习惯:setup 是写逻辑的地方,不希望在这里写大量的常量。可以在 <script setup> 上面再去创建一个 <script>

// 定义 main 颜色
// tailwind.config.js
module.exports = {theme: {extend: {colors: {main: '#f44c58','hover-main': '#F2F9EC',}}}
}// 使用
class = "bg-main"
<script>
// type 可选项:表示按钮风格
const typeEnum = {primary:'text-white  bg-zinc-800 dark:bg-zinc-900  hover:bg-zinc-900 dark:hover:bg-zinc-700 active:bg-zinc-800 dark:active:bg-zinc-700',main: 'text-white  bg-main dark:bg-zinc-900  hover:bg-hover-main dark:hover:bg-zinc-700 active:bg-main dark:active:bg-zinc-700',info: 'text-zinc-800 dark:text-zinc-300  bg-zinc-200 dark:bg-zinc-700 hover:bg-zinc-300 dark:hover:bg-zinc-600 active:bg-zinc-200 dark:active:bg-zinc-700 '
}
// size 可选项:表示按钮大小。区分文字按钮和icon按钮
const sizeEnum = {default: {button: 'w-8 h-4 text-base',icon: ''},'icon-default': {button: 'w-4 h-4',icon: 'w-1.5 h-1.5'},small: {button: 'w-7 h-3 text-base',icon: ''},'icon-small': {button: 'w-3 h-3',icon: 'w-1.5 h-1.5'}
}
</script>
// 通过 props 让开发者控制按钮
<script setup>
const props = defineProps({// icon 图标名字icon: {type: String},// icon 图标颜色iconColor: {type: String},// icon 图标类名(匹配 tailwind)iconClass: {type: String},// 按钮风格type: {type: String,default: 'main',validator(val) {// 获取所有的可选的按钮风格const keys = Object.keys(typeEnum)// 开发者指定风格是否在可选风格中const result = keys.includes(val)// 如果不在则给开发者提示if (!result) {throw new Error(`你的 type 必须是 ${keys.join('、')} 中的一个`)}// 返回校验结果return result}},// 大小风格size: {type: String,default: 'default',validator(val) {// 获取所有的可选的大小(注意剔除 icon 开头的元素,因为我们期望开发者输入 size="default",但不期望开发者输入 size="icon-default")const keys = Object.keys(sizeEnum).filter((key) => !key.includes('icon'))// 开发者指定大小是否在可选大小中const result = keys.includes(val)// 如果不在则给开发者提示if (!result) {throw new Error(`你的 size 必须是 ${keys.join('、')} 中的一个`)}// 返回校验结果return result}},// 按钮在点击时是否需要动画isActiveAnim: {type: Boolean,default: true},// 加载状态loading: {type: Boolean,default: false}
})
</script>
// 区分 icon button 和 text button
// 传递了 icon props 则默认按钮类型为 icon button// 处理大小的 key 值
const sizeKey = computed(() => {return props.icon ? 'icon-' + props.size : props.size
})
// 依据当前的数据,实现视图
<template><buttonclass="text-sm text-center rounded duration-150 flex justify-center items-center":class="[typeEnum[type],sizeEnum[sizeKey].button,{ 'active:scale-105': isActiveAnim }]"@click.stop="onBtnClick"><!-- 展示 loading --><m-svg-iconv-if="loading"name="loading"class="w-2 h-2 animate-spin mr-1"></m-svg-icon><!-- icon 按钮 --><m-svg-iconv-if="icon":name="icon"class="m-auto":class="sizeEnum[sizeKey].icon":color="iconColor":fillClass="iconClass"></m-svg-icon><!-- 文字按钮 --><slot v-else /></button>
</template>
// 处理点击事件
const EMITS_CLICK = 'click'
const emits = defineEmits([EMITS_CLICK])
/*** 按钮点击事件处理*/
const onBtnClick = () => {if (props.loading) {return}emits(EMITS_CLICK)
}

06: 通用组件:完善 search 基本能力

/*** 1. 输入内容实现双向数据绑定* 2. 搜索按钮在 hover 时展示* 3. 一键清空文本功能* 4. 触发搜索* 5. 控制下拉展示区的展示* 6. 事件处理*/
// 事件处理:
//     双向绑定
//     search 搜索
//     删除所有文本
//     输入事件
//     获取焦点事件
//     失去焦点事件

07: 通用组件:popover 气泡卡片能力分析

/*** 具备两个插槽。*     第一个插槽描述触发弹出层的视图。这个视图可以定为具名插槽。*     第二个插槽描述弹出层内容。这个内容可以定为匿名插槽。* 弹出层气泡可以在指定位置弹出。*/

08: 通用组件:popover 气泡卡片基础功能实现 

- libs
- - popover
- - - index.vue
<template><div class="relative" @mouseleave="onMouseleave" @mouseenter="onMouseenter"><div ref="referenceTarget"><!-- 具名插槽 --><slot name="reference" /></div><!-- 气泡展示动画 --><transition name="slide"><divv-show="isVisable"ref="contentTarget"class="absolute p-1 z-20 bg-white dark:bg-zinc-900 border rounded-md dark:border-zinc-700":style="contentStyle"><!-- 匿名插槽 --><slot /></div></transition></div>
</template><script>
// 延迟关闭时长
const DELAY_TIME = 100const PROP_TOP_LEFT = 'top-left'
const PROP_TOP_RIGHT = 'top-right'
const PROP_BOTTOM_LEFT = 'bottom-left'
const PROP_BOTTOM_RIGHT = 'bottom-right'// 定义指定位置的 Enum
const placementEnum = [PROP_TOP_LEFT,PROP_TOP_RIGHT,PROP_BOTTOM_LEFT,PROP_BOTTOM_RIGHT
]
</script><script setup>
import { ref, watch, nextTick } from 'vue'const props = defineProps({// 控制气泡弹出位置,并给出开发者错误的提示placement: {type: String,default: 'bottom-left',validator(val) {const result = placementEnum.includes(val)if (!result) {throw new Error(`你的 placement 必须是 ${placementEnum.join('、')} 中的一个`)}return result}}
})// 控制 menu 展示
const isVisable = ref(false)// 控制延迟关闭
let timeout = null
/*** 鼠标移入的触发行为*/
const onMouseenter = () => {isVisable.value = true// 再次触发时,清理延时装置if (timeout) {clearTimeout(timeout)}
}
/*** 鼠标移出的触发行为*/
const onMouseleave = () => {// 延时装置timeout = setTimeout(() => {isVisable.value = falsetimeout = null}, DELAY_TIME)
}/*** 计算元素尺寸*/
const referenceTarget = ref(null)
const contentTarget = ref(null)
const useElementSize = (target) => {if (!target) return {}return {width: target.offsetWidth,height: target.offsetHeight}
}/*** 计算弹层位置*/
const contentStyle = ref({top: 0,left: 0
})/*** 监听展示的变化,在展示时计算气泡位置*/
watch(isVisable, (val) => {if (!val) {return}// 等待渲染成功之后nextTick(() => {switch (props.placement) {// 左上case PROP_TOP_LEFT:contentStyle.value.top = 0contentStyle.value.left =-useElementSize(contentTarget.value).width + 'px'break// 右上case PROP_TOP_RIGHT:contentStyle.value.top = 0contentStyle.value.left =useElementSize(referenceTarget.value).width + 'px'break// 左下case PROP_BOTTOM_LEFT:contentStyle.value.top =useElementSize(referenceTarget.value).height + 'px'contentStyle.value.left =-useElementSize(contentTarget.value).width + 'px'break// 右下case PROP_BOTTOM_RIGHT:contentStyle.value.top =useElementSize(referenceTarget.value).height + 'px'contentStyle.value.left =useElementSize(referenceTarget.value).width + 'px'break}})
})
</script><style lang="scss" scoped>
// slide 展示动画
.slide-enter-active {transition: opacity 0.3s, transform 0.3s;
}.slide-leave-active {transition: opacity 0.3s, transform 0.3s;
}.slide-enter-from,
.slide-leave-to {transform: translateY(20px);opacity: 0;
}
</style>

09: 通用组件:popover 功能延伸,控制气泡展示位置 

/*** 步骤:* 1. 指定所有可选位置的常量,并生成 enum* 2. 通过 prop 控制指定位置* 3. 获取元素的 DOM;创建读取元素尺寸的方法* 4. 生成气泡的样式对象,用来控制每个位置对应的样式* 5. 根据 prop,计算样式对象*/

10: 通用组件:处理慢速移动时,气泡消失问题 

        想要解决这个问题,可以利用 类似于防抖(debounce)的概念。

        也就是:鼠标刚离开时,不去立刻修改 isVisible,而是延迟一段时间,如果在这段时间之内,再次触发了鼠标移入事件,则不再修改 isVisible。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/681461.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

地图位置的二维码怎么做?在线制作地图二维码的方法

怎么定位一个位置做成二维码呢&#xff1f;随着互联网的不断发展&#xff0c;现在通过扫描二维码来获取导航位置的方式有很多的场景都在应用。这种方式的好处在于其他人都可以通过这个二维码来获取位置&#xff0c;有利于分享。 导航地图二维码可以在电脑的二维码生成器上快速…

推荐 3 个 yyds 的开源项目!

本期推荐开源项目目录&#xff1a; 1. AI 搜索引擎 2. 大模型聊天框架 3. 模仿抖音的移动端短视频 01 AI 搜索引擎 Perplexica 是一个开源的、由 AI 驱动的搜索引擎。它深入互联网寻找答案&#xff0c;不仅搜索网络&#xff0c;还理解您的问题。 Perplexica 受到 Perplexity AI…

安卓使用Fiddler抓包 2024

简介 最近试了一下安卓使用fiddler 抓包&#xff0c;发现https包基本都会丢失。原因是Anandroid 7版本针对ssl安全性做了加强&#xff0c;不认可用户的证书。我们要做的就是把fiddler导出的证书进过处理后放置到系统证书目录下面&#xff0c;这样才能抓包https请求。 这里使用…

分割模型Maskformer系列

maskformer&#xff1a;Per-Pixel Classification is Not All You Need for Semantic Segmentation 论文地址&#xff1a;https://arxiv.org/pdf/2107.06278 1.概述 传统的语义分割方法通常采用逐像素分类&#xff08;per-pixel classification&#xff09;&#xff0c;而实…

Ubuntu20.4中复现Graspness

Ubuntu20.4中复现Graspness 文章目录 Ubuntu20.4中复现Graspness1.安装cuda和cudnn2.安装pytorch3.安装MinkowskiEngine4.编译graspnetAPI5. RuntimeError: "floor" "_vml_cpu" not implemented for IntRefernece &#x1f680;非常重要的环境配置&#x1…

uniapp:抖音PK进度条(nvue)

nvue中,仿抖音PK进度条效果, <template><view class="index" :style="{width:windowWidth+px,height:index_windowHeight+px,paddingTop:windowTop+px}"><view class="pk"><text class="pk_jindu_left_val fsz-24 …

UE和three.js的区别

UE&#xff08;Unreal Engine&#xff09;和three.js都是用于创建3D图形的软件平台&#xff0c;但它们在功能、目标和应用场景方面存在一些差异。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合作。 功能 UE 是一款功能全面的3D游戏引擎&…

Excel操作之工具类

需求&#xff1a;根据指定的路径下模版进行解析 将模版上传到指定的文件服务器。 1&#xff1a;将路径下的excel文件进行解析 下载 A:创建excel表格对应的字段注解 ExcelColumn Retention(RetentionPolicy.RUNTIME) Target(ElementType.FIELD) public interface ExcelColumn …

汽车EDI:IAC Elmdon EDI 对接指南

近期收到客户C公司的需求&#xff0c;需要与其合作伙伴IAC Elmdon建立EDI连接&#xff0c;本文将主要为大家介绍IAC Elmdon EDI 对接指南&#xff0c;了解EDI项目的对接流程。 项目需求 传输协议&#xff1a;OFTP2 IAC Elmdon 与其供应商之间使用的传输协议为OFTP2。OFTP2是…

qt5-入门-xml文件读写

本地环境&#xff1a; win10专业版&#xff0c;64位&#xff0c;Qt 5.12 代码已经测试通过。其他例子日后更新。 假设需要读写的xml文档结构如下图所示&#xff1a; 那么首先需要修改.pro文件&#xff0c;增加一句&#xff1a; 然后执行qmake。 代码 #include <QtXml/Q…

如何更快地执行 Selenium 测试用例?

前言&#xff1a; 当我们谈论自动化时&#xff0c;首先想到的工具之一是 Selenium。我们都知道Selenium WebDriver 是一个出色的 Web 自动化工具。实施Selenium 自动化测试的主要原因是加速 selenium 测试。在大多数情况下&#xff0c;Selenium 的性能比手动的要好得多。但是&…

FreeRTOS的移植

在工程模版里新建FreeRTOS的文件夹 将FreeRTOSv9.0.0文件夹下的\FreeRTOS\Source里的文件&#xff0c;复制到刚刚创建的FreeRTOS文件夹中。 在FreeRTOS\portable的文件夹下&#xff0c;使用Keil环境时&#xff0c;只使用Keil、MemMang、RVDS文件&#xff0c;可以删除其他的文件…