news 2026/11/2 8:06:35

uni-app H5 hash路由配置与导航守卫实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
uni-app H5 hash路由配置与导航守卫实战

上周有个朋友拿着他们组的 H5 项目来找我,说“守卫写了但拦不住”。我打开代码翻了十分钟就发现问题不在守卫:他们为了绕开某些分享链接被截断的情况,把 manifest.json 里的路由模式从 history 改回了 hash,改完之后没重新编译,只在浏览器里刷新了一下页面看效果。更麻烦的是,他们的拦截逻辑一半写在 uni.addInterceptor 里,一半写在自建的 router/index.js 里,而 uni-app 里根本没有那个“自建 router 入口”的位置。

这类问题在 uni-app 的 H5 端特别常见。很多人是带着纯 Vue 项目的经验过来的,建 router 目录、写 beforeEach、挂到 main.js,一条龙都很熟练。结果到 uni-app 里发现:没有 router/index.js 这个入口,pages.json 里没有 meta 字段,main.js 里也拿不到 router 实例。于是开始怀疑“uni-app 不支持导航守卫”。

其实它支持,只是入口和普通 Vue 项目完全不同。这篇就把 hash 模式的配置位置、字段分工、三种拦截方式的适用边界,以及我实际项目中遇到问题的排查过程,完整梳理一遍。适合已经在写 uni-app、准备给 H5 端加登录态校验的人看,也适合那些“守卫明明写了却不生效”的人对照检查。

1. 先把“壳”摸清楚:uni-app H5 端的路由到底是谁在干活

1.1 编译产物里的 Vue2 + vue-router 3.x

很多人不知道的是,uni-app 编译到 H5 之后,底层用的就是标准的 Vue2 加 vue-router 3.x。你写的每一个页面,最终都会变成 vue-router 路由表里的一条 record;你调用的uni.navigateTo,内部最终会落到router.push上;uni.navigateBack对应router.back。打开编译后 dist 目录里的 JS,能看到 vue-router 的源码片段。

搞清楚这一层之后,很多事情就通了。比如为什么to.path是/pages/detail/detail这种带前导斜杠的形式,因为那是 vue-router 的 path 规范,而不是 pages.json 里的pages/detail/detail。再比如为什么to.query能拿到参数,因为跳转时参数被转成了 vue-router 的 query 对象。

但反过来也要注意:这层“壳”意味着你不应该把它当成完整的 vue-router 来用。pages.json 才是页面清单的唯一真相来源,你在 router 上动态addRoutes加出来的页面,uni-app 的页面栈管理是不认的,跳过去会出现各种奇怪的回退问题。这个边界必须记牢。

1.2 pages.json 管页面清单,manifest.json 管 URL 形态

这两个文件的分工经常被搞混,我见过有人往 pages.json 里加"mode": "hash"的,那当然不会有任何效果。

  • pages.json:决定“应用里有哪些页面”,包括路径、导航栏样式、是否 tabBar 页。它对应的概念是 vue-router 的路由表。
  • manifest.json:决定“这个应用编译到各端之后长什么样”,其中h5.router这一段才管 H5 端的 URL 形态,也就是 hash 还是 history、base 是多少。

所以标题里说的“配置 hash 路由”,动作只有一处:改 manifest.json。pages.json 那边一根手指都不用动。

还有一个容易被忽略的细节:uni-app 的 manifest.json 支持注释(语法上是 JSON with Comments),HBuilderX 里改完会自动格式化。但如果你用 CLI 创建的项目,改完 manifest.json 之后开发服务器不一定会热更新配置,必须重启npm run dev:h5。我遇到过好几次“改完没反应”,都是因为只等热更新了。

1.3 配置改了却没反应的三种情况

排查“为什么我配了 hash 没生效”的时候,我一般按这三步走:

第一种,改错了文件。去 pages.json 或者 vue.config.js 里找路由配置,这是最常见的。hash 模式只认 manifest.json 的h5.router.mode。

第二种,没重新编译。manifest.json 属于编译期配置,改动之后必须重启编译进程。HBuilderX 里的表现是“运行到浏览器”需要重新点一次,CLI 项目要 Ctrl+C 再起。

第三种,本来就是 hash,你以为没生效。这一点很多人会忽略:uni-app H5 端的默认路由模式就是 hash,mode字段不写也是 hash。所以你看到 URL 里带#/pages/index/index,不是配错了,是它一直就这样。真正需要显式配置的场景通常是两个:一是项目之前被改成了 history,现在要改回来;二是在团队协作里把默认值写死,防止有人误改。

我自己偏向显式写出来。虽然是默认值,但写在 manifest.json 里等于给团队留了一份声明,比“靠默认值”靠谱得多。

2. hash 模式的落地点:manifest.json 里那几个字段的真实分工

2.1 mode、base、publicPath 各管一段

这三个字段是最容易配错的组合,我见过把它们当成一个东西来回试的。实际分工是这样的:

字段位置作用配错的典型症状
router.modeh5.router.mode决定 URL 是#/pages/x还是/pages/x配错后刷新 404,或分享链接被截断
router.baseh5.router.base路由前缀,影响路由解析和生成的链接部署到子目录后路径全错,跳转 404
publicPathh5.publicPath静态资源(js/css/图片)的加载前缀页面白屏,控制台一片 404 的 chunk 请求

publicPath和路由完全没关系,它管的是 webpack 打包出来的资源引用路径。但这两个字段经常要一起改:部署到https://example.com/app/这种二级目录下时,publicPath不写/app/,JS 加载不到,页面直接白屏;router.base不写/app/,路由解析会从根开始算,跳转全 404。

关于 base 在 hash 模式下的行为,可以这样理解:它会被拼在域名和#之间。比如 base 是/app/,最终 URL 长这样https://example.com/app/#/pages/index/index。而 history 模式下 base 是直接拼在 path 上的。两种模式下 base 的语义一致,只是外观不同。

2.2 二级目录部署的组合写法

给一份我实际在用的配置,直接抄就行:

{ "h5": { "publicPath": "/app/", "router": { "mode": "hash", "base": "/app/" } } }

如果是部署在根目录,两个值都写/就行,这是默认情况,一般不用动。

这里有个坑要说清楚:publicPath和router.base在写法上必须以/开头和结尾,少了斜杠就会出现/app#/pages/index这种拼接错误。我见过写成"base": "app"的,结果跳转全指向根目录,排查了半天以为是守卫的问题。

还有一种更省心的做法,如果你不确定最终部署路径,publicPath可以写成相对路径./,这样资源会相对于 index.html 加载,部署到任何目录都不会白屏。但router.base不建议用相对路径,路由前缀还是老老实实写绝对路径比较稳。

2.3 hash 与 history 的取舍对照

既然标题是“配置 hash”,那为什么不是 history?这个选择在不同项目里答案不一样,我把实际对比列出来:

维度hash 模式history 模式
URL 外观带#,如/app/#/pages/index/index干净,如/app/pages/index/index
服务端要求不需要任何配置必须配置回退到 index.html,否则刷新 404
分享链接兼容性部分平台会截断#之后的内容无此问题
第三方回调带参参数容易落在#前面,需要在入口处处理参数直接在 search 上,解析简单
页面内锚点跳转和路由 hash 冲突,基本用不了正常可用
部署成本低,扔上静态服务器就能跑需要运维配合

选择 hash 最实在的理由就是部署成本低。一个纯静态托管的环境,history 模式刷新页面必 404,因为服务器找不到/pages/detail/detail这个真实文件。要么让运维配转发规则,要么自己加一层 404 兜底,都得多花功夫。如果项目里没有强 SEO 需求,也不想折腾服务端,hash 是性价比最高的选择。

相对应的,它的代价就是那两个:分享链接可能被截断、第三方回调参数位置不对。这两个坑我在第 5 章会展开讲怎么处理,都是可以解决的问题,不是硬伤。

3. 拦截入口不止一个:addInterceptor、beforeEach、页面内判断的边界

3.1 uni.addInterceptor 管的是 API 调用

先把这个最容易误解的说清楚:uni.addInterceptor拦截的是“你调用了某个 API”这个动作,而不是“地址发生了变化”这个事实。

const apis = ['navigateTo', 'redirectTo', 'reLaunch', 'switchTab'] apis.forEach(api => { uni.addInterceptor(api, { invoke(args) { // 返回 false 可以阻止本次调用 return true }, success(res) {}, fail(err) {} }) })

invoke会在 API 执行前触发,参数里带着原始的args,你可以读取甚至改写args.url。返回false会直接阻止这次调用,uni.navigateTo会走fail回调。

这套机制的好处是同步、直接、不用等异步,适合做“明显没登录就别发了”的粗筛。但它有两个明确的边界:

一是拦不住地址栏变化。用户在浏览器里手动改 hash、点浏览器的前进后退按钮、扫码直接打开某个页面,这些都不经过uni.navigateTo,拦截器根本不知道发生过什么。

二是switchTab的表现不一致。tabBar 页的点击在 H5 端最终会走switchTab,理论上能被拦。但如果你在invoke里return false,tabBar 的高亮状态可能已经切换过去了,视觉上会出现“高亮在 A 页,内容还是 B 页”的错位。所以 tabBar 场景我一般交给beforeEach处理,不在这一层做拦截。

3.2 从 App.vue 里取 $router 注册全局守卫

这是真正能覆盖所有入口的方式。难点只有一个:怎么拿到 router 实例。

uni-app 不允许你自建 router 入口,所以import router from './router'这条路是走不通的。可行的姿势是把守卫注册放到 App.vue 的生命周期里,因为 App.vue 是根组件的上一级,它的this.$router在 H5 端是可用的。

export default { onLaunch() { // #ifdef H5 const router = this.$router if (router) { router.beforeEach((to, from, next) => { // ... 校验逻辑 }) } // #endif } }

// #ifdef H5这段条件编译注释非常关键。App.vue 的代码会被编译到小程序、App 等多端,那些端上没有$router,不包起来会直接报错。

如果某些场景下 App.vue 里取不到实例,还有一条备用路径:在页面组件里用this.$router,或者用getApp().$router。但这两个都不适合注册全局守卫,只适合读取当前路由信息。注册守卫还是应该集中在 App.vue 里,只注册一次,避免重复注册导致守卫被调用多次。

3.3 页面级兜底为什么不能省

即使有了全局守卫,我还是建议在关键页面里留一道页面级判断。原因有两个:

第一个是时序问题。App.vue 的onLaunch和首次路由解析之间,不同 uni-app 版本的执行顺序存在细微差异。如果你的首页在onLoad里就发起了跳转,有可能抢在守卫注册之前。这种情况下守卫是空的,页面直接就进去了。

第二个是状态失效问题。用户把页面挂在后台放了几个小时,回来点了一下,token 早就过期了,但这一下可能只是触发了页面的onShow,没有发生路由跳转,守卫根本不会被调用。这时候页面级判断是唯一的兜底。

页面级的写法很轻:

onShow() { // #ifdef H5 if (!uni.getStorageSync('token') && this.needAuth) { uni.reLaunch({ url: '/pages/login/login' }) } // #endif }

别嫌它重复。全局守卫负责“进入这个页面的那一刻”,页面级判断负责“回到这个页面的那一刻”,两个时机不一样,覆盖的是不同场景。

3.4 三种方式的分工

汇总一下我自己的用法:

场景用哪个原因
未登录时阻止主动跳转uni.addInterceptor同步、开销小,能在 API 层直接掐掉
地址栏直输、扫码进入、前进后退router.beforeEach唯一能覆盖地址变化的入口
从后台切回、长时间挂起后页面onShow不经过路由,守卫不会触发
tabBar 页的登录校验router.beforeEach拦截器在这一层有视觉副作用

这三层叠起来才叫完整。只写一层就想覆盖所有场景,一定会漏。

4. 写一版能直接用的守卫:白名单、异步鉴权、回跳参数

4.1 路径比对的第一道坑:前导斜杠

这个问题我第一次遇到时查了快一个小时。现象是白名单里明明写了登录页,但守卫还是把登录页拦下来了,形成死循环。

原因是:白名单里的路径通常是从 pages.json 抄来的,写成了pages/login/login,不带前导斜杠。而to.path是 vue-router 的规范,一定是/pages/login/login。字符串直接比,永远不相等。

所以要统一格式。我一般写一个归一化函数:

function normalize(path) { if (!path) return '' // 去掉 query 部分 const pure = path.split('?')[0] // 补前导斜杠 const withSlash = pure.startsWith('/') ? pure : '/' + pure // 去掉末尾多余的斜杠 return withSlash.replace(/\/+$/, '') || '/' }

除了前导斜杠,还要处理末尾斜杠和 query 混入。有人在白名单里写/pages/index/index?id=1,那更是永远匹配不上,因为to.path里根本不带 query,参数在to.query里。

4.2 白名单与 token 的读取顺序

白名单要放在 token 判断之前,这个顺序反了就是死循环的根源。登录页本身必然是没有 token 的,如果先判断 token 再判断白名单,登录页会被重定向到登录页,无限循环下去,控制台会刷出一堆 “Redirected when going from ... to ...” 的警告。

// #ifdef H5 const WHITE_LIST = [ '/pages/login/login', '/pages/login/register', '/pages/common/webview', '/pages/error/404' ] function isWhite(path) { const p = normalize(path) return WHITE_LIST.some(item => normalize(item) === p) } function getToken() { return uni.getStorageSync('token') || '' } // #endif

getToken这里刻意用的是uni.getStorageSync,而不是从 Vuex 里读。原因在第 5 章会说,简单讲就是刷新页面之后 Vuex 是空的,而 storage 是持久的。

白名单里我一般会额外放 webview 页和错误页。webview 页通常用于嵌入外部协议页,带不了 token 也没关系;错误页更不能拦,否则一旦跳过去就出不来了。

4.3 异步鉴权里 next 的调用纪律

真正的业务里,token 校验往往是异步的:可能需要向服务端确认 token 是否有效、是否被顶下线、有没有续期。这时候next的调用最容易出问题。

router.beforeEach(async (to, from, next) => { const path = normalize(to.path) if (isWhite(path)) { return next() } const token = getToken() if (!token) { return next({ path: '/pages/login/login', query: { redirect: encodeURIComponent(to.fullPath) } }) } try { const valid = await checkTokenOnline(token) if (!valid) { uni.removeStorageSync('token') return next({ path: '/pages/login/login', query: { redirect: encodeURIComponent(to.fullPath) } }) } next() } catch (e) { // 网络异常时放行还是拦截,要按业务定 next() } })

这里有几条纪律必须遵守:

第一,next必须保证被调用且只调用一次。如果await中间抛了异常又没 catch 住,next永远不会执行,导航会永久挂起,页面白屏,而且控制台一点报错都没有。这是最难排查的一类问题,因为它看起来像“卡住了”,实际上是在等一个永远不会来的next。

第二,不要在next之后继续写逻辑。next调用之后,控制权就交回路由了,后面的代码可能在导航完成之后才执行,容易产生竞态。

第三,网络异常时的兜底策略要想清楚。上面那个catch里我选择放行,因为如果鉴权接口本身挂了,把所有用户都踢回登录页是很糟糕的体验。但如果你的业务是强安全场景,就应该选择拦截。这个没有标准答案,取决于业务对“误放行”和“误拦截”哪个更敏感。

4.4 回跳地址的拼接与解码

登录成功之后要跳回原页面,这个细节做不好体验会很差。

存进去的时候要encodeURIComponent,因为to.fullPath里可能带着 query,直接拼在 URL 里会被二次解析。取出来的时候要decodeURIComponent,一进一出必须配对。

// 登录页拿到 redirect const pages = getCurrentPages() const current = pages[pages.length - 1] const redirect = current.options && current.options.redirect if (redirect) { const target = decodeURIComponent(redirect) uni.reLaunch({ url: target }) } else { uni.reLaunch({ url: '/pages/index/index' }) }

这里用reLaunch而不是redirectTo,理由是reLaunch能处理 tabBar 页,而redirectTo跳 tabBar 页会直接失败。回跳的目标页面是用户上次想去的地方,你没法保证它一定不是 tabBar 页,所以用reLaunch最保险。

代价是reLaunch会关闭所有页面,用户登录完成后按返回键会直接退出应用。如果这个体验不能接受,可以加一层判断:目标页在 tabBar 列表里就用reLaunch,否则用redirectTo。

5. 实战里真正会卡住人的五个场景

5.1 登录页被自己的守卫拦下:死循环的成因

死循环的外观很好认:页面疯狂闪烁,控制台刷出一堆重定向警告,浏览器甚至可能崩掉。

根因就是白名单没生效。但白名单没生效的原因不止一种,我按排查顺序列一下:

  • 路径格式不一致(前导斜杠、query、大小写),这是最常见的;
  • 白名单变量在守卫注册之后才初始化,导致第一次判断时是undefined。这种情况在模块循环引用时会出现;
  • 守卫被注册了多次。App.vue 里注册一次,某个页面里又注册一次,两个守卫互相打架。beforeEach是累加的,不像有些框架会覆盖。

第二种情况特别隐蔽。如果你把白名单放在单独的文件里导出,而那个文件又反过来引用了某个工具函数,很容易出现“守卫先跑起来,白名单还没赋值”的情况。我的做法是把白名单直接写在守卫定义的作用域里,不做跨文件引用。

还有一个小技巧:在守卫的第一行加一句判断,防止自己在登录页的时候被重复调用:

if (path === '/pages/login/login' && isWhite(path)) { return next() }

这种“提前返回”的写法能让逻辑更清晰,也更容易排查。

5.2 刷新即掉登录:别在守卫里读 Vuex

这个坑在 H5 端特别典型。原因是 hash 模式下刷新页面,整个 JS 上下文重新执行,Vuex 的状态全部丢失,store.state.user.token读出来就是空字符串。守卫一看没 token,直接把人踢回登录页。用户的感觉就是“随便刷新一下就要重新登录”。

在小程序里不会遇到这个问题,因为小程序的页面刷新是重新走一遍onLoad,但应用实例没有销毁。H5 就成了重灾区。

解决方法很直接:守卫里读 token 一律走uni.getStorageSync,不碰 Vuex。如果确实要在守卫里用用户信息,从 storage 里恢复一份,或者干脆只判断 token 是否存在,具体信息等到页面里再取。

顺带说一句,uni.getStorageSync是同步的,在守卫里调用完全没有性能问题。有些人担心同步读取本地存储会卡,实测下来这点消耗可以忽略不计,远不如一次网络请求。

5.3 第三方回调参数落在 # 前面

这个坑是 hash 模式特有的,也是很多人切回 hash 之后才发现的。

假设你的页面地址是https://example.com/app/#/pages/index/index,某个第三方服务(授权登录、支付回跳、短信短链)要在你的页面上追加参数,它拼接出来的地址通常是https://example.com/app/?code=xxx&state=yyy#/pages/index/index。注意参数落在了?后面、#前面。

而 vue-router 在 hash 模式下解析的是#之后的部分,to.query里自然什么都拿不到。代码里读to.query.code永远是undefined。

处理方式有两种,我推荐第一种:

方式一:入口处抢救。在 App.vue 的onLaunch里,先看window.location.search有没有内容,有的话解析出来,用history.replaceState把参数挪进 hash 里,再让路由正常解析。

// #ifdef H5 function rescueSearchParams() { const search = window.location.search if (!search) return const query = search.replace(/^\?/, '') const hash = window.location.hash || '#/pages/index/index' const [hashPath, hashQuery] = hash.split('?') const finalPath = hashQuery ? `${hashPath}?${hashQuery}&${query}` : `${hashPath}?${query}` window.history.replaceState(null, '', window.location.pathname + finalPath) } rescueSearchParams() // #endif

执行完之后 URL 变成https://example.com/app/#/pages/index/index?code=xxx&state=yyy,to.query就能正常读到了。注意这段代码要在路由解析之前执行,放在onLaunch的最前面比较合适。

方式二:直接读原始参数。不走 vue-router,在需要的页面里直接new URLSearchParams(window.location.search).get('code')。这种方式简单粗暴,缺点是参数不会进入路由状态,页面内其他逻辑拿不到,而且一旦发生路由跳转,参数就彻底丢了。

如果你的回调只在一个页面里用一次,方式二够用。如果回调参数需要流转到后续页面,用方式一。

5.4 switchTab 与 navigateTo 回调的行为差异

这一点文档里没有明说,但实际写代码时会撞上:当守卫在beforeEach里做了重定向,uni.navigateTo的success回调可能仍然会触发,也可能走fail,具体表现和 uni-app 版本有关。

这意味着你不能依赖navigateTo的success回调来做后续逻辑。比如这样写:

uni.navigateTo({ url: '/pages/order/detail?id=1', success() { // 以为跳过去了,其实被守卫重定向到了登录页 this.doSomething() } })

如果守卫把这次跳转重定向到了登录页,success里执行业务逻辑就是错的。

我的处理原则是:凡是可能被守卫拦截的跳转,都不在回调里写业务逻辑。需要传数据就用 storage 或者 Vuex,需要等页面加载完再做事情就放到目标页的onLoad里。

还有switchTab的问题:在addInterceptor里对switchTab返回false,tabBar 的高亮可能已经变了。这个视觉错位用户是能看到的,所以 tabBar 页的校验一定要放在beforeEach里,让它从根上就不允许导航发生。

5.5 首次进入时守卫可能抢跑,以及怎么验证

前面提过,onLaunch里注册守卫和首次路由解析之间的时序,不同版本有细微差别。我遇到过一次首页直接进去没被拦的情况,加了日志才发现首次导航发生时守卫还没注册上。

比较稳的做法是双保险:

onLaunch() { // #ifdef H5 rescueSearchParams() const router = this.$router if (router) { router.beforeEach(guard) } // #endif }

然后在首页的onLoad里再做一次判断,作为兜底。如果你的首页本身就是需要登录的页面,这一道兜底不能省。

验证守卫是否真的生效,我一般用这三个手段:

第一,在守卫里打日志,输出to.path、from.path和判断结果。用浏览器控制台看每一次跳转的顺序,一眼就能看出哪一次漏了。

第二,打开 vue-devtools 的路由面板,看路由历史。被拦截的跳转不会出现在历史里,通过这一点能区分“跳了但被重定向”和“压根没跳”。

第三,手动测试四个入口:点击页面内按钮跳转、浏览器地址栏直接输 URL、按浏览器后退键、从外部链接(比如微信里粘贴)进入。四个都过了,才算真的覆盖完整。

6. 上线前值得花半小时做的检查

我自己有个习惯,H5 项目加完守卫之后,上线前一定会过一遍这几个点。这些检查花不了多少时间,但能省掉不少线上排查的功夫。

检查项怎么查不通过的风险
hash 模式是否真的生效地址栏应带#/pages/xxx,刷新不 404配错成 history,静态托管下刷新全挂
base 与 publicPath 是否匹配部署路径打开任意二级页面,看资源请求前缀白屏,JS chunk 404
白名单路径格式打印to.path与白名单逐条比对登录页死循环
刷新后是否掉登录登录后按 F5,看是否回到登录页Vuex 读 token,用户天天重登
第三方回调参数手动在 URL 上加?code=1测试授权、支付回跳全失效
tabBar 页面拦截效果未登录状态点每个 tab高亮错位或直接进页面
回跳地址带 query 的参数页跳登录再回跳参数丢失,页面显示异常

这里面我最看重的是“刷新后是否掉登录”。因为这个问题的用户感知最强,而且排查起来最像玄学——开发阶段你可能一直在同一个会话里测试,根本发现不了。

7. 最后一点个人的实操习惯

我现在做 uni-app 的 H5 项目,路由这块会先定三件事,写在项目 README 的第一屏:路由模式是什么、base 是什么、白名单在哪维护。这三件事定下来,后面接手的人不用再去 manifest.json 里翻。

守卫的代码我会单独放一个common/guard.js,导出两个函数:setupGuard(router)和checkPageAuth()。前者在 App.vue 里调一次,后者在需要鉴权的页面onShow里调。这样全局和页面级两条线是分开的,不至于一改就牵一发而动全身。

条件编译的注释放的位置也有讲究。我一般把// #ifdef H5包在最外层,整段 H5 专属逻辑都在里面,包括window对象的操作、$router的访问、history.replaceState的调用。这样编译到小程序端的时候整块代码会被直接剔除,不会因为一个window is not defined导致整个小程序起不来。

还有一个习惯是:每次改动路由配置之后,一定用手机浏览器真机跑一遍,别只在开发者工具里看。工具栏里的模拟器和真机的表现差异比想象中大,尤其是涉及浏览器前进后退、从微信里打开、从后台切回这些场景,模拟器根本模拟不出来。我吃过一次亏——工具里一切正常,真机上从微信打开直接白屏,原因是publicPath配了绝对路径但实际部署在子目录。这种事,早发现半小时,少熬一个通宵。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/2 8:05:16

MUI Chip组件实践:交互事件、受控数据流与可访问性

1. Chip 在界面里到底解决什么问题&#xff0c;什么时候不该用它先说一个我反复遇到的场景&#xff1a;后台列表页里&#xff0c;一条记录同时挂着状态、渠道、负责人、几个业务标签&#xff0c;用<span>拼字符串能跑&#xff0c;但过两周产品要求"状态可以点开筛选…

作者头像 李华
网站建设 2026/11/2 8:05:16

Transformer凭什么打败LSTM?揭秘Google那个20%提升的真相

Transformer 打败 LSTM&#xff0c;Google 那个 20% 到底是怎么来的&#xff1f;做 NLP 的人&#xff0c;这两年应该都有一个很直观的感受&#xff1a;LSTM 正在从我们的工具箱里慢慢消失。不管是 TensorFlow 还是 PyTorch&#xff0c;新开的项目里默认 baseline 几乎全是 Tran…

作者头像 李华
网站建设 2026/11/2 8:05:00

本地AI写脚本跑命令的五大卡点:离无人值守还差多远

本地AI能写脚本、能执行命令&#xff0c;这是最近大半年我一直在折腾的方向。很多朋友看到我发的演示视频&#xff0c;第一反应都是“那岂不是可以挂机了&#xff1f;”“让AI自己跑任务&#xff0c;晚上睡觉前丢个需求&#xff0c;第二天收结果”。说实话&#xff0c;我当初也…

作者头像 李华
网站建设 2026/11/2 8:04:20

Mac系统Redis安装与性能优化全攻略

1. Mac环境下Redis安装全指南Redis作为当前最流行的内存数据库之一&#xff0c;在Mac系统上的安装过程虽然简单&#xff0c;但不同芯片架构&#xff08;Intel/Apple Silicon&#xff09;和系统版本存在诸多细节差异。我结合多年运维经验&#xff0c;整理出这份包含版本选择、性…

作者头像 李华
网站建设 2026/11/2 8:04:16

机器人算法演进与核心技术解析

1. 机器人算法演进全景图十年前我刚入行时&#xff0c;机器人还停留在固定轨迹重复作业阶段。如今走进任何一家现代化工厂&#xff0c;都能看到配备3D视觉的机械臂正在自主分拣零件&#xff0c;AGV车队通过无线调度系统完成物料流转——这些场景背后是算法技术的三次代际跃迁。…

作者头像 李华
网站建设 2026/11/2 8:04:13

Tectonic调试与错误处理:10个实用排查方法

1. 为什么Tectonic的调试体验和常规工具完全不一样Tectonic这个工具&#xff0c;第一次上手的人十有八九会在某个环节卡住——不是编译报错看不懂&#xff0c;就是日志刷了一屏却找不到关键信息&#xff0c;再不然就是明明本地跑通了换台机器又挂了。我自己最早接触它的时候&am…

作者头像 李华