前端(Vue)headerSearch(页面搜索)通用解决方案 及 原理

news2024/9/24 1:26:35

简介

击后弹出输入框
image.png
image.png
输入框可以输入页面的索引,比如项目中包含了文章相关的
点击后可以进入对应界面
image.png
同时也支持英文索引
image.png

原理

headerSearch 是复杂后台系统中非常常见的一个功能,它可以:在指定搜索框中对当前应用中所有页面进行检索,以 **select** 的形式展示出被检索的页面,以达到快速进入的目的。
headerSearch 可以分为三个核心的功能点:

  1. 根据指定内容对所有页面进行检索
  2. **select** 形式展示检索出的页面
  3. 通过检索页面可快速进入对应页面

综上,根据指定内容检索所有页面,把检索出的页面以 **select** 展示,点击对应 **option** 可进入 => 即可实现。

方案

  1. 创建 headerSearch 组件,用作样式展示和用户输入内容获取
  2. 获取所有的页面数据,用作被检索的数据源
  3. 根据用户输入内容在数据源中进行 模糊搜索
  4. 把搜索到的内容以 select 进行展示
  5. 监听 selectchange 事件,完成对应跳转

创建 headerSearch 组件

remote-method 自定义远程搜索方法
change 选中值发生变化时触发

<template>
<!-- headerSearch其实包含两种状态(展示搜索框和隐藏,两个状态),需要绑定动态的class,动态添加isShow -->
  <div :class="{ show: isShow }" class="header-search">
<!-- innerHtml里导入icon,然后下面是select -->
    <svg-icon
      class-name="search-icon"
      icon="search"
      @click.stop="onShowClick"
      />
      <el-select
        ref="headerSearchSelectRef"
        class="header-search-select"
        v-model="search"
        filterable
        default-first-option
        remote
        placeholder="Search"
        :remote-method="querySearch"    <!--  搜索触发-->
        @change="onSelectChange"
        >
<!-- 指定select的options -->
        <el-option
          v-for="option in 5"
          :key="option"
          :label="option"
          :value="option"
          ></el-option>
      </el-select>
    </div>
</template>

<script setup>
  import { ref } from 'vue'

  // 控制 search 显示
  const isShow = ref(false)
  // el-select 实例
  const headerSearchSelectRef = ref(null)
  const onShowClick = () => {
    isShow.value = !isShow.value
    headerSearchSelectRef.value.focus()
  }

  // search 相关
  const search = ref('')
  // 搜索方法
  const querySearch = () => {
    console.log('querySearch')
  }
  // 选中回调
  const onSelectChange = () => {
    console.log('onSelectChange')
  }
</script>

<style lang="scss" scoped>
  .header-search {
    font-size: 0 !important;
    .search-icon {
      cursor: pointer;
      font-size: 18px;
      vertical-align: middle;
    }
    .header-search-select {
      font-size: 18px;
      transition: width 0.2s;
      width: 0;
      overflow: hidden;
      background: transparent;
      border-radius: 0;
      display: inline-block;
      vertical-align: middle;

      ::v-deep .el-input__inner {
        border-radius: 0;
        border: 0;
        padding-left: 0;
        padding-right: 0;
        box-shadow: none !important;
        border-bottom: 1px solid #d9d9d9;
        vertical-align: middle;
      }
    }
    &.show {
      .header-search-select {
        width: 210px;
        margin-left: 10px;
      }
    }
  }
</style>

检索数据源,路由表数据处理

检索数据源 表示:有哪些页面希望检索(被搜索)
检索数据源即为:菜单对应的数据源(可以进入的页面)

<script setup>
import { ref, computed } from 'vue'
import { filterRouters, generateMenus } from '@/utils/route'
import { useRouter } from 'vue-router'
...
// 检索数据源
const router = useRouter()
const searchPool = computed(() => {
  // 从项目的路由工具文件(@/utils/route)中导入的两个实用函数。根据名字推测,filterRouters 用于筛选路由,generateMenus 用于生成菜单。
  const filterRoutes = filterRouters(router.getRoutes())  // 筛选可以跳转的路由
  console.log(generateMenus(filterRoutes))
  return generateMenus(filterRoutes)
})
console.log(searchPool)
</script>

补充

/**
 * 返回所有子路由
 */
const getChildrenRoutes = (routes) => {
  const result = []
  routes.forEach((route) => {
    // 检查当前路由是否有 children 属性并且非空
    if (route.children && route.children.length > 0) {
      result.push(...route.children)
    }
  })
  return result
}

/**
 * 处理脱离层级的路由:filterRouters 函数接收一个 routes 参数,该参数通常是通过 router.getRoutes() 方法获取的路由数组。它的作用是过滤掉那些既是一级路由又是其他路由子级的路由,从而保留清晰的路由层级结构。
 * @param {*} routes router.getRoutes()
 */
export const filterRouters = (routes) => {
  // 返回所有子路由
  const childrenRoutes = getChildrenRoutes(routes)
  return routes.filter((route) => {
    return !childrenRoutes.find((childrenRoute) => {
      return childrenRoute.path === route.path
    })
  })
}

/**
 * 该方法的作用:根据 routes 数据,返回对应 menu 规则数组。
 * 方法本质为构建了一个:递归
 * @param {*} routes 需要解析的路由数组,是整个应用的路由配置。
 * @param {*} basePath 可选参数,用于处理路径拼接,默认值为空字符串。这个参数在递归调用时用于构建完整的路由路径。
 * @returns 返回一个数组,该数组会在 SidebarMenu 中被 v-for 循环用于 sidebar-item 的渲染
*/
export function generateMenus(routes, basePath = '') {
  // 最终需要返回的值
  const result = []
  // 遍历路由表
  routes.forEach((item) => {
    // 不存在 children && 不存在 meta 则被认为是 《忽略不需要处理的路由》, 直接 return
    if (isNull(item.meta) && isNull(item.children)) return
    // 如果一个路由没有 meta 信息但有子路由,则认为它是一个父节点路由,需要递归处理其子路由。这里使用递归调用 generateMenus 来处理子路由,并将结果追加到 result 数组中
    if (isNull(item.meta) && !isNull(item.children)) {
      result.push(...generateMenus(item.children))
      return
    }
    // 合并 path 作为跳转路径
    const routePath = path.resolve(basePath, item.path)
    // 路由分离之后,存在同名父路由(指的是:name 相同的路由对象)的情况,需要单独处理,避免重复
    let route = result.find((item) => item.path === routePath)

    // 查找匹配的 route 对象,如果 route 对象不存在,则表示当前的 route 还没有放入到 result 数组中,所以我们需要构建一个新的 route 对象,并且把它放入到 result 里面
    if (!route) {
      route = {
        ...item,
        path: routePath,
        children: []
      }

      // icon 与 title 必须全部存在,只有这样,我们才认为它是一个需要在 《menu item》 中展示的数据
      if (route.meta.icon && route.meta.title) {
        // meta 存在生成 route 对象,放入 arr
        result.push(route)
      }
    }

    // 如果当前路由有子路由,则再次调用 generateMenus 递归处理子路由,并将生成的子菜单项添加到当前路由的 children 属性中。
    if (item.children) {
      route.children.push(...generateMenus(item.children, route.path))
    }
  })
  return result
}

对检索数据源进行模糊搜索

如果我们想要进行  模糊搜索  的话,那么需要依赖一个第三方的库  fuse.js => 专门做模糊搜索的
image.png

  1. 安装 fuse.js
npm install --save fuse.js@6.4.6
  1. 初始化 Fuse,更多初始化配置项 可点击这里
import Fuse from 'fuse.js'

/**
 * 搜索库相关
 * Fuse接收两个参数,首先是搜索的数据源,然后就说fuse的配置对象了。
 * 配置对象的配置项如下
 */
const fuse = new Fuse(list, {
  // 是否按优先级进行排序,,最匹配的结果会排在前面。
  shouldSort: true,
  // 匹配长度超过这个值的才会被认为是匹配的
  // 如果用户搜索 "s",并且这个值为 1,那么长度为 1 的查询也是有效的。如果设置为 2,那么 "s" 不会触发匹配,只有 "se" 及以上长度的查询才会被考虑。
  minMatchCharLength: 1,
  // 将被搜索的键列表。 这支持嵌套路径、加权搜索、在字符串和对象数组中搜索。
  // name:搜索的键
  // weight:对应的权重
  // 这里通过数据源的title和path进行搜索
  keys: [
    {
      name: 'title',
      weight: 0.7
    },
    {
      name: 'path',
      weight: 0.3
    }
  ]
})
  1. 参考 Fuse Demo 与 最终效果,可以得出,最终期望得到如下的检索数据源结构 :

官网数据结构:

[
  {
    "title": "Old Man's War",
    "author": "John Scalzi",
    "tags": ["fiction"]
  },
  {
    "title": "The Lock Artist",
    "author": "Steve",
    "tags": ["thriller"]
  }
]

设计的数据结构:

[
    {
        "path":"/my",
        "title":[
            "个人中心"
        ]
    },
    {
        "path":"/user",
        "title":[
            "用户"
        ]
    },
    {
        "path":"/user/manage",
        "title":[
            "用户",
            "用户管理"
        ]
    },
    {
        "path":"/user/info",
        "title":[
            "用户",
            "用户信息"
        ]
    },
    {
        "path":"/article",
        "title":[
            "文章"
        ]
    },
    {
        "path":"/article/ranking",
        "title":[
            "文章",
            "文章排名"
        ]
    },
    {
        "path":"/article/create",
        "title":[
            "文章",
            "创建文章"
        ]
    }
]
  1. 之前处理了的数据源并不符合我们的需要,所以我们需要对数据源进行重新处理

image.png

数据源重处理,生成  searchPool

generateRoutes => path和title都分别进行处理。
title 设置为数组,因为路由如果是多层级,需要展示为用户>员工管理,因此title的话以父子层级的形式去展示。
generateRoutes 函数接收三个参数:

  1. **routes**: 路由表,是通过 filterRoutes 生成的,包含了路由的所有信息。
  2. **basePath**: 基础路径,默认值为 /,用于构建完整路径。
  3. **prefixTitle**: 前缀标题,默认是一个空数组,用于构建嵌套路由的完整标题。
import path from 'path'
import i18n from '@/i18n'
/**
 * 筛选出可供搜索的路由对象
 * @param routes 路由表 (通过filterRoutes生成的)
 * @param basePath 基础路径,默认为 /
 * @param prefixTitle
 */
export const generateRoutes = (routes, basePath = '/', prefixTitle = []) => {
  // 创建 result 数据
  let res = []
  // 循环 routes 路由
  // route路由包含很多内容,我们只需要path和title
  for (const route of routes) {
    // 创建包含 path 和 title 的 item
    const data = {
      path: path.resolve(basePath, route.path), // 合并 => 使用 path.resolve 将基础路径 basePath 和当前路由的 path 合并,生成完整的路径
      title: [...prefixTitle] // 初始化 title 数组,将 prefixTitle(前缀标题)复制到 title 中。这个数组将用于存储完整的路由标题。
    }

    // ( meta国家化)当前存在 meta 时,需要使用 i18n 解析国际化数据,最后组合成新的 title 内容
    // 动态路由不允许被搜索
    // 匹配动态路由的正则  =>  !re.exec(route.path) 不是动态路由
    const re = /.*\/:.*/ // 是否包含冒号,无论前后是啥,就认为是动态路由
    if (route.meta && route.meta.title && !re.exec(route.path)) {
      const i18ntitle = i18n.global.t(`msg.route.${route.meta.title}`)
      data.title = [...data.title, i18ntitle]
      res.push(data)
    }

    // 存在 children 时,迭代调用
    if (route.children) {
      // 传递children,父路由path,父title。得到所有子路由筛选的出的route
      const tempRoutes = generateRoutes(route.children, data.path, data.title)
      if (tempRoutes.length >= 1) {
        res = [...res, ...tempRoutes]
      }
    }
  }
  return res
}

使用

<script setup>
import { computed, ref } from 'vue'
import { generateRoutes } from './FuseData'
import Fuse from 'fuse.js'
import { filterRouters } from '@/utils/route'
import { useRouter } from 'vue-router'

...

// 检索数据源
const router = useRouter()
const searchPool = computed(() => {
  const filterRoutes = filterRouters(router.getRoutes())
  return generateRoutes(filterRoutes)
})
/**
 * 搜索库相关
 */
const fuse = new Fuse(searchPool.value, {
  ...
})
</script>

通过 querySearch 测试搜索结果

// 搜索方法
const querySearch = query => {
  console.log(fuse.search(query))
}

输入文章
image.png

渲染检索数据

据源处理完成之后,最后就只需要完成:

  1. 渲染检索出的数据
  2. 完成对应跳转

渲染检索出的数据

<template>
  <el-option
      v-for="option in searchOptions"
      :key="option.item.path"
      :label="option.item.title.join(' > ')"  // 通过箭头的方式拼接
      :value="option.item"
  ></el-option>
</template>

<script setup>
...
// 搜索结果
const searchOptions = ref([])
// 搜索方法
const querySearch = query => {
  if (query !== '') {
    searchOptions.value = fuse.search(query)
  } else {
    searchOptions.value = []
  }
}
...
</script>

完成对应跳转

// 选中回调
const onSelectChange = val => {
  router.push(val.path)
}

其余细节问题处理

search 打开时,点击 body 关闭 search;在 search 关闭时,清理 searchOptions

问题:search关闭后,再打开,之前搜索的内容还存在,点击的选项也存在。
监听 search 打开,处理 close 事件,关闭时删除该事件
关闭事件 => 去除焦点,隐藏输入框,删除搜索内容。

/**
 * 关闭 search 的处理事件
 */
const onClose = () => {
  headerSearchSelectRef.value.blur()
  isShow.value = false
  searchOptions.value = []
}
/**
 * 监听 search 打开,处理 close 事件
 */
watch(isShow, val => {
  if (val) {
    document.body.addEventListener('click', onClose)
  } else {
    document.body.removeEventListener('click', onClose)
  }
})

headerSearch 应该具备国际化能力

接下来是国际化的问题,想要处理这个问题非常简单,我们只需要:监听语言变化,重新计算数据源初始化 **fuse** 即可

  1. utils/i18n 下,新建方法 watchSwitchLang ,监听语言的变化,语言变化后执行传递过来的回调函数。
  • **watch**: 这是 Vue.js 3 中的一个函数,用于监听某个响应式数据的变化,并在数据变化时执行指定的回调。它有两个主要参数:
    • 第一个参数: 一个函数,用于返回需要监听的响应式数据。在这里,监听的是 store.getters.language,即当前应用的语言。
    • 第二个参数: 另一个函数,它会在第一个参数返回的值变化时被调用。在这里,当语言发生变化时,第二个参数的函数体将会执行。
  • **store.getters.language**: 这是从 Vuex 中获取当前语言的 getter。它是一个响应式的数据,所以当语言发生变化时,watch 会监听到这一变化。
import { watch } from 'vue'
import store from '@/store'

/**
 *
 * @param  {...any} cbs 所有的回调
 * ...cbs 可以传递任意多的回调函数
 */
export function watchSwitchLang(...cbs) {
  watch(
    () => store.getters.language,
    () => {
      cbs.forEach(cb => cb(store.getters.language))
    }
  )
}

headerSearch 监听变化,重新赋值

<script setup>
...
import { watchSwitchLang } from '@/utils/i18n'

...

// 检索数据源(初始化搜索数据源)
const router = useRouter()
let searchPool = computed(() => {
  const filterRoutes = filterRouters(router.getRoutes())
  return generateRoutes(filterRoutes)
})
/**
 * 搜索库相关
 */
let fuse
const initFuse = searchPool => {
  fuse = new Fuse(searchPool, {
    ...
}
initFuse(searchPool.value)

...

// 处理国际化
watchSwitchLang(() => {
  searchPool = computed(() => {
    const filterRoutes = filterRouters(router.getRoutes())
    return generateRoutes(filterRoutes)
  })
  initFuse(searchPool.value)
})
</script>

当语言发生变化时

  • **重新计算 ****searchPool**: 重新计算 searchPool,这个过程包括:
    1. 获取最新的路由配置(可能会因为语言变化而改变)。
    2. 重新生成带有新语言的路由标题的搜索数据源。
  • **重新初始化 ****Fuse.js**: 调用 initFuse(searchPool.value) 重新初始化 Fuse 实例,使得搜索功能使用最新的 searchPool 数据源。

整体流程

  • 初始加载时:
    • 创建路由实例并生成初始的 searchPool
    • 使用 searchPool 初始化 Fuse 搜索库。
  • 当用户切换语言时:
    • watchSwitchLang 检测到语言变化,触发回调函数。
    • 回调函数重新生成 searchPool,使其包含新的语言标题。
    • 再次初始化 Fuse 实例,使得搜索数据源与当前语言同步。

headerSearch 方案总结

整个 headerSearch 把握住三个核心的关键点

  1. 根据指定内容对所有页面(数据源)进行检索
  2. select 形式展示检索出的页面
  3. 通过检索页面可快速进入对应页面

关于细节的处理,可能比较复杂的地方有两个:

  1. 模糊搜索
  2. 【创建】检索数据源

对于这两块,依赖于 fuse.js 进行了实现,大大简化了我们的业务处理流程。

补充:  fuse.js 库

简介

Fuse.js 是一个强大的轻量级 JavaScript 库,用于在小到中等大小的数据集合中进行模糊搜索。它的设计目标是提供一种灵活、易用的方式,帮助开发者在没有数据库的情况下实现高效的搜索功能。
模糊搜索的特点是能够在用户输入的查询与实际数据不完全匹配时仍能找到相关结果。例如,用户输入 “wrd” 可能会匹配到 “word”。

快速上手

安装 Fuse.js

通过 npm 或 yarn 来安装 Fuse.js:

npm install fuse.js

或者

yarn add fuse.js

基本用法

如 数据集:

const data = [
  { title: "Old Man's War", author: "John Scalzi" },
  { title: "The Lock Artist", author: "Steve Hamilton" },
  { title: "The Hero of Ages", author: "Brandon Sanderson" },
  { title: "The Colour of Magic", author: "Terry Pratchett" },
  { title: "The Light Fantastic", author: "Terry Pratchett" }
];

可以使用 Fuse.js 进行搜索:

import Fuse from 'fuse.js';

// 配置搜索选项
const options = {
  includeScore: true,    // 是否包含搜索得分
  keys: ['title', 'author'] // 需要搜索的字段
};

// 创建 Fuse 实例
const fuse = new Fuse(data, options);

// 搜索关键字
const result = fuse.search('The Hero');

// 打印搜索结果
console.log(result);

配置选项

Fuse.js 提供了许多配置选项来定制搜索行为:

  • keys: 需要搜索的字段,如 ['title', 'author']
  • includeScore: 如果设置为 true,则搜索结果将包含每个匹配项的得分。
  • threshold: 设置匹配阈值,值在 01 之间。0 表示完全匹配,1 表示匹配所有项目。
  • minMatchCharLength: 设置最小匹配字符长度,只有当搜索词的长度达到指定值时,才会进行搜索。
const options = {
  includeScore: true,
  threshold: 0.3,          // 匹配度阈值,数值越低匹配要求越高
  minMatchCharLength: 2,   // 最小匹配字符长度
  keys: ['title', 'author']
};

高级用法:权重和搜索模式

可以为不同的搜索字段分配不同的权重,以提高搜索的精度。例如 :

const options = {
  includeScore: true,
  keys: [
    { name: 'title', weight: 0.7 },
    { name: 'author', weight: 0.3 }
  ]
};

const fuse = new Fuse(data, options);
const result = fuse.search('Terry');

在这个示例中,title 字段的权重是 0.7author 的权重是 0.3,表示我们希望在搜索中更重视标题匹配。

使用 Fuse.js 搜索

当你调用 fuse.search(query) 时,Fuse.js 会返回一个结果数组。每个结果对象包含以下信息:

  • item: 匹配的对象。
  • score: (可选)匹配得分,数值越低表示匹配度越高。
  • refIndex: (可选)在原始数据中的索引。
const result = fuse.search('magic');
result.forEach(({ item, score }) => {
  console.log(`Found: ${item.title}, Score: ${score}`);
});

总结

Fuse.js 是一个功能强大的模糊搜索工具,适用于需要在前端进行快速模糊搜索的小型数据集。通过配置选项,你可以灵活地定制搜索行为,使其适应不同的应用场景。结合简单的 API 和丰富的功能,Fuse.js 是一个极为实用的搜索库。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

stm32MX+freertos在创建task时,选项的含义

任务名称&#xff08;Task Name&#xff09;&#xff1a; 用于标识任务的名称&#xff0c;便于调试和日志记录。 优先级&#xff08;Priority&#xff09;&#xff1a; 任务的执行优先级。FreeRTOS支持多个优先级&#xff0c;高优先级的任务会优先于低优先级的任务执行。 堆栈…

功率放大器——前面板操作界面(AWA5871为例)

功率放大器的前面板操作包括多个功能按钮、旋钮和指示灯。如下图所示为AWA5871功率放大器。 1、电源开关&#xff08;Power Switch&#xff09; 功能&#xff1a;用于打开或关闭放大器的电源。 操作&#xff1a;按下电源开关&#xff0c;放大器通电并启动&#xff1b;再次按下…

【区块链 + 航运物流】运力链 | FISCO BCOS应用案例

根据速达物联的战略规划&#xff0c;2023年物流平台将由单一调度平台升级为物流生态平台。基于此&#xff0c;虎彩集团采用 FISCO BCOS区块链技术构建的运力链&#xff0c;可以帮助客户实现资源广泛快速连接、合作伙伴间的高效协同和低摩擦交 易&#xff0c;最终达成可信同城货…

redis面试(二十一)读写锁互斥

读锁非互斥 非互斥的意思就是&#xff0c;一个客户端或者线程加锁之后&#xff0c;另一个客户端线程也可以来进行加锁。 还是拿着ReadLock的lua脚本来看看 刚才我们已经分析过第一个线程来加读锁的逻辑了 所以上半截不用重复说了&#xff0c; hset anyLock mode read hset an…

AI周报(8.18-8.24)

AI应用-XGO-Rider: 全球首款轮腿式桌面 AI 机器人 中国的 Luwu 智能打造的XGO-Rider 是全球首款轮腿式桌面 AI 机器人。这个小巧紧凑的机器人将轮式机器人的灵活性与腿式机器人的障碍处理能力相结合&#xff0c;可以全方位移动&#xff0c;轻松适应各种地形。 XGO-Rider 主要设…

如何使用ssm实现亿互游在线平台设计与开发+vue

TOC ssm118亿互游在线平台设计与开发vue 绪论 1.1研究背景 时代的发展&#xff0c;我们迎来了数字化信息时代&#xff0c;它正在渐渐的改变着人们的工作、学习以及娱乐方式。计算机网络&#xff0c;Internet扮演着越来越重要的角色&#xff0c;人们已经离不开网络了&#x…

AITDK SEO扩展:为网站优化提供一站式解决方案

AITDK SEO扩展&#xff1a;为网站优化提供一站式解决方案 想提升你的网站在搜索引擎中的排名&#xff1f;让我们来看看AITDK SEO扩展&#xff0c;它是你网站优化的得力助手&#xff01;在这篇文章中&#xff0c;我将为你介绍AITDK SEO扩展的功能特点&#xff0c;以及它如何帮助…

RK3588 技术分享 | 在Android系统中使用NPU实现Yolov5分类检测-迅为电子

随着人工智能和大数据时代的到来&#xff0c;传统嵌入式处理器中的CPU和GPU逐渐无法满足日益增长的深度学习需求。为了应对这一挑战&#xff0c;在一些高端处理器中&#xff0c;NPU&#xff08;神经网络处理单元&#xff09;也被集成到了处理器里。NPU的出现不仅减轻了CPU和GPU…

前端——盒子模型

一个盒子的特点组成 外边距就是两个元素之前的距离 padding就是填充区的大小 从上开始 顺时针进行设置&#xff0c;没有则对称 也可以单独对某个方向进行设定&#xff0c;比如&#xff1a;padding-top border 边框区 符合属性 border-style 边框样式 border-color 边框颜色…

web前端之选项卡集合、动态添加类名、动态移除类名、动态添加样式、激活、间距、节流、tabBar

MENU input的checked属性(HtmlStyle)伪元素(HtmlStyleJavaScript)激活类(HtmlStyleJavaScript)vue伪元素 input的checked属性(HtmlStyle) 前言 代码段创建一个使用HTML和CSS实现的标签式内容切换组件。通过选择不同的标签&#xff0c;可以展示相应的内容。 代码段实现一个简洁…

掌握时间的艺术:Python的sched库深度解析

文章目录 掌握时间的艺术&#xff1a;Python的sched库深度解析背景&#xff1a;为何选择sched&#xff1f;什么是sched库&#xff1f;如何安装sched库&#xff1f;简单库函数使用方法1. 创建调度器实例2. 安排事件3. 取消事件4. 运行调度器5. 检查事件是否在队列中 场景应用1. …

iOS profiles文件过期如何更新

创建发布用的Certificates 首先进入到https://developer.apple.com/account页面选择【证书】进入【新建证书】页面 点击【新建证书】按钮&#xff1a; 根据需求选中对应的【证书类型】&#xff0c;我选的是【Apple Distribution】&#xff0c; 开发者证书选择【Apple Devel…

数码管进阶设计验证

前言 随着数字电路和嵌入式系统的广泛应用&#xff0c;数码管作为一种常见的显示设备&#xff0c;在各种电子产品中扮演着重要角色。数码管以其结构简单、显示清晰和成本低廉的特点&#xff0c;广泛应用于计数器、时钟、测量仪器等领域。然而&#xff0c;传统的数码管设计通常仅…

小梅哥 xilinx fpga VGA

module VGA_CTRL(Clk,Reset_n,Data,Data_Req,VGA_HS, //行VGA_VS, //场VGA_BLK, //数据有效的那一段VGA_RGB );input Clk;input Reset_n;input [23:0]Data;output reg Data_Req;output reg VGA_HS;output reg VGA_VS; output reg VGA_BLK;output reg [23:0]VGA_RGB;//{R[7:0]、…

Android常见界面控件(三)

目录 前言 列表控件ListView 常用属性 常用适配器 1.BaseAdapter 2.SimpleAdapter 3.ArrayAdapter 购物商城 选择菜品照片 创建布局文件 实现购物商城列表界面的显示效果 前言 在前面&#xff0c;我们已经讲了六个常用的界面控件和五个界面布局&#xff0c;那么本篇…

【HarmonyOS NEXT星河版开发实战】灯泡定时开关

个人主页→VON 收录专栏→鸿蒙综合案例开发​​​​​ 代码及其图片资源会发布于gitee上面&#xff08;已发布&#xff09; 所有与鸿蒙开发有关的知识点都会在gitee上面进行发布 gitee地址https://gitee.com/wang-xin-jie234 目录 前言 界面功能介绍 界面构建思路 头部 中间…

数据结构——二叉树经典OJ题

1.单值二叉树 单值二叉树&#xff1a;就是判断二叉树里的所有值是否都一样 bool isUnivalTree(struct TreeNode* root) {if(rootNULL)return true;//查找有没有左子树并且看左子树当前指向的值是否和根当前指向的值相等if(root -> left && root -> left -> v…

【三维室内数据集】ScanNet v2使用说明

【版权声明】本文为博主原创文章&#xff0c;未经博主允许严禁转载&#xff0c;我们会定期进行侵权检索。 参考书籍&#xff1a;《人工智能点云处理及深度学习算法》 本文为专栏《Python三维点云实战宝典》系列文章&#xff0c;专栏介绍地址“【python三维深度学习】python…

Python自动化:图片批量添加水印

前言 本文将讲述怎样通过Python自动化的方法&#xff0c;来对照片进行批量的加水印&#xff0c;从而能够有效地阻止他人的非法占有&#xff0c;提高工作的效率。 Python自动化&#xff1a;办公效率的革命 自动化解决方案 实现步骤 读取指定文件夹中的图片&#xff1a;打开…

YOLOv9改进策略【卷积层】| 利用MobileNetv4中的UIB、ExtraDW优化RepNCSPELAN4

一、本文介绍 本文记录的是利用ExtraDW优化YOLOv9中的RepNCSPELAN4&#xff0c;详细说明了优化原因&#xff0c;注意事项等。ExtraDW是MobileNetv4模型中提出的新模块&#xff0c;允许以低成本增加网络深度和感受野&#xff0c;具有ConvNext和IB的组合优势。可以在提高模型精度…