企业级通用业务 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>
  <div
    ref="containerTarget"
    class="group relative p-0.5 rounded-xl border-white duration-500 hover:bg-red-100/40"
  >
    <div>
      <!-- 搜索图标 -->
      <m-svg-icon
        class="w-1.5 h-1.5 absolute translate-y-[-50%] top-[50%] left-2"
        name="search"
        color="#707070"
      />
      <!-- 输入框 -->
      <input
        class="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-icon
        v-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>
      <!-- 分割线 -->
      <div
        class="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-button
        class="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">
      <div
        v-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 = true
  emits(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>
  <button
    class="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-icon
      v-if="loading"
      name="loading"
      class="w-2 h-2 animate-spin mr-1"
    ></m-svg-icon>
    <!-- icon 按钮 -->
    <m-svg-icon
      v-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">
      <div
        v-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 = 100

const 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 = false
    timeout = 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 = 0
        contentStyle.value.left =
          -useElementSize(contentTarget.value).width + 'px'
        break
      // 右上
      case PROP_TOP_RIGHT:
        contentStyle.value.top = 0
        contentStyle.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.mfbz.cn/a/608831.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MySQL学习笔记11——数据备份 范式 ER模型

数据备份 & 范式 & ER模型 一、数据备份1、如何进行数据备份&#xff08;1&#xff09;备份数据库中的表&#xff08;2&#xff09;备份数据库&#xff08;3&#xff09;备份整个数据库服务器 2、如何进行数据恢复3、如何导出和导入表里的数据&#xff08;1&#xff09…

ARP命令

按照缺省设置&#xff0c;ARP高速缓存中的项目是动态的&#xff0c;每当发送以恶个指定的数据报且高速缓存中不存在当前项目时&#xff0c;ARP便会自动添加该项目。一旦高速缓存的项目被输入&#xff0c;就已经开始走向失效状态。因此&#xff0c;如果ARP高速缓存中的项目很少或…

擎天科技与禅道合作,打造统一的项目管理平台

统一、全面的项目管理平台能够帮助企业优化管理流程&#xff0c;提升业务效率。擎天集团选择与禅道软件合作&#xff0c;打造统一的项目管理平台&#xff0c;在降低自研软件的研发成本、打破团队信息孤岛、保障数据全面性等方面效果显著&#xff0c;大大提高了团队沟通协作效率…

如何使用 ArcGIS Pro 计算容积率

容积率是指地上建筑物的总面积与用地面积的比率&#xff0c;数值越小越舒适&#xff0c;这里为大家介绍一下如何使用ArcGIS Pro 计算容积率&#xff0c;希望能对你有所帮助。 数据来源 教程所使用的数据是从水经微图中下载的建筑和小区数据&#xff0c;除了建筑和小区数据&am…

verilog中不重叠序列检测

编写一个序列检测模块&#xff0c;检测输入信号&#xff08;a&#xff09;是否满足011100序列&#xff0c; 要求以每六个输入为一组&#xff0c;不检测重复序列&#xff0c;例如第一位数据不符合&#xff0c;则不考虑后五位。一直到第七位数据即下一组信号的第一位开始检测。当…

AngularJS基本概念

版本&#xff1a; AngularJs 1.x&#xff1a;https://angularjs.org/ AngularJs 2&#xff1a;https://angular.io/ 或 https://angular.cn/ 实现语言&#xff1a; Angular 1.x&#xff1a;使用ES(avaScript)编写&#xff0c;可直接在浏览器中运行。 Angular 2&#xff1a…

Electron-Vue 脚手架避坑实录,兼容Win11,升级electron22,清理控制台错误

去年的还是有用的&#xff0c;大家继续看&#xff0c;今年再补充一些Electron-Vue 异常处理方案 M1 和 Window10_electron异常处理-CSDN博客 代码gitee.com地址 electron-demo: electron 22 初始代码开发和讲解 升级electron为22版本&#xff08;这个版本承上启下&#xff0c…

内网穿透速度慢

内网穿透速度慢原因及优化策略 在计算机网络应用中&#xff0c;内网穿透是一个常见的需求&#xff0c;它允许外部网络访问位于内部网络&#xff08;如企业局域网或家庭网络&#xff09;中的设备或服务。然而&#xff0c;有时用户在进行内网穿透时会遇到速度慢的问题&#xff0…

【二次元MMORPG游戏开发】任务系统技术拆解

引言 各位同学大家好。在今天的分享当中&#xff0c;我将对任务系统去做一个拆解。也许你见过很多任务系统&#xff0c;但是今天我要分享的是我们经过一个框架迭代以后的任务系统。我会结合客户端的功能演示给大家去讲解。 跟着演示学开发 基本操作 好&#xff0c;首先我们点…

C++ | Leetcode C++题解之第80题删除有序数组中的重复项II

题目&#xff1a; 题解&#xff1a; class Solution { public:int removeDuplicates(vector<int>& nums) {int n nums.size();if (n < 2) {return n;}int slow 2, fast 2;while (fast < n) {if (nums[slow - 2] ! nums[fast]) {nums[slow] nums[fast];slo…

【Linux】项目自动化构建工具make/makefile

&#x1f389;博主首页&#xff1a; 有趣的中国人 &#x1f389;专栏首页&#xff1a; Linux &#x1f389;其它专栏&#xff1a; C初阶 | C进阶 | 初阶数据结构 小伙伴们大家好&#xff0c;本片文章将会讲解Linux中项目自动化构建工具make/makefile的相关内容。 如果看到最后…

[windows系统安装/重装系统][step-2]BIOS设置UEFI引导、磁盘分区GPT分区、安装系统[含完整操作拍照图片]

背景 先准备U盘启动盘和系统镜像: [windows系统安装/重装系统][step-1]U盘启动盘制作&#xff0c;微软官方纯净系统镜像下载 前言&#xff08;略长&#xff0c;建议可跳过&#xff09; 我的笔记本升级了CPU升级了内存后出现了一个小问题&#xff0c; 每次启动徽标显示后会…

hyper-v启动centos7虚拟机不能联网

虚拟网卡要和之前虚拟机里面设置的GATEWAY一致。 安装CentOS遇到Error setting up base repository换url 或者换镜像包iso(这个有用&#xff09; 没掌握摸Yu的精髓 好累啊

安全加固

目录 1.文件锁定管理 2.设置用户账户有效期 3.查看并清除命令历史记录 4.设置用户超时登出时间 5.用户切换 6.用户提权 7.禁用重启热键CtrlAltDel 8.设置单用户模式密码 9.调整BIOS引导设置 10.禁止root用户从本地登录&#xff1a; 11.禁止root用户通过ss…

【算法刨析】完全背包

完全背包与01背包的区别 01背包对于一个物品只能选择一次&#xff0c;但是完全背包可以选择任意次&#xff1b; 思路 和01背包类似&#xff0c;01背包我们只需要判断选或不选&#xff0c;完全背包也是如此&#xff0c;不同的是&#xff0c;对于这个物品我们在判断选后在增加一…

【考试100】2023年监理《目标控制(土建)》真题及答案精选

​来源&#xff1a;考试100 一、单项选择题 1、工程建设与使用中&#xff0c;保证人身和环境免受危害&#xff0c;是建设工程质量特性中的&#xff08; &#xff09;要求。A .适用性 B .耐久性 C .安全性 D .可靠性 参考答案&#xff1a;C 解析&#xff1a;安全性&…

2024 全自动ai生成视频MoneyPrinterTurbo源码

只需提供一个视频 主题 或 关键词 &#xff0c;就可以全自动生成视频文案、视频素材、视频字幕、视频背景音乐&#xff0c;然后合成一个高清的短视频。 源码下载&#xff1a;https://download.csdn.net/download/m0_66047725/89208288 更多资源下载&#xff1a;关注我。

AGV与智能仓储系统集成的实践与优化

agv 根据相关研究报告指出&#xff0c;储存、装卸、等待及运送等程序&#xff0c;几乎占了整个生产制程95%的时间&#xff0c;因此导致工厂中的再制品及物料无法有效降低&#xff0c;耗损大量人工等问题&#xff0c;早已是制造业者在经营上的痛点。而智慧工厂中的AGV无人搬运车…

Gitee 码云与Git 交互

优质博文&#xff1a;IT-BLOG-CN 一、进入码云官方网站&#xff0c;注册用户 码云(Gitee.com)是一个类似于GitHub的在线代码托管平台。 码云提供了包括版本控制、代码托管、协作开发和代码分享等功能&#xff0c;基于Git开发&#xff0c;支持代码在线查看、历史版本查看、Fo…

《系统架构设计师教程(第2版)》第10章-软件架构的演化和维护-06-大型网站系统架构演化实例

文章目录 第一阶段&#xff1a;单体架构第二阶段&#xff1a;垂直架构第三阶段&#xff1a;使用缓存改善网站性能第四阶段&#xff1a;使用服务集群改善网站并发处理能力第五阶段&#xff1a;数据库读写分离第六阶段&#xff1a;使用反向代理和CDN加速网站响应第七阶段&#xf…
最新文章