H5 端 uniapp
是默认开启 scoped 的!!
常见标签的编译转换规则
小程序原生标签 | H5 编译结果 | 结构变化特点 | 潜在样式风险 |
---|---|---|---|
<view> |
<uni-view> |
直接平替,无额外嵌套 | 最小风险 |
<navigator> |
<uni-navigator> |
包裹一层 <a> 标签 |
样式可能失效 |
<button> |
<uni-button> |
直接平替,无额外嵌套 | 最小风险 |
<image> |
<uni-image> |
包含双重结构 (div 背景层+img ) |
样式层级依赖问题 |
<text> |
<uni-text> |
内部增加 <span> 包裹 |
文本样式继承可能改变 |
<input> |
<uni-input> |
复杂结构包裹原生 <input> |
焦点/边框样式异常 |
<scroll-view> |
<uni-scroll-view> |
嵌套多层级容器元素 | 滚动条样式错位 |
<swiper> |
<uni-swiper> |
生成复杂结构 | 全局样式污染风险 |
<picker> |
<uni-picker> |
生成复杂结构 | 定位/动画异常 |
ini
<navigator
class="category-item"
hover-class="none"
open-type="switchTab"
url="/pages/category/category"
>
...
</navigator>
会被编译成以下代码:
ini
<uni-navigator data-v-44ae5a81="" class="category-item">
<a class="navigator-wrap" href="/pages/category/category">
...
</a>
</uni-navigator>
这就有个坑的地方,注意到了 uni-navigator
身上出现了 data-v-44ae5a81
属性,但是新生成的 a 标签上面没有生成这个属性,如果你在 css
使用了 .category-item a {...},这种用法会报错的,因为这样子会被编译成 .category-item a[data-v-44ae5a81] {...},可是 a 实际上没有自定义属性。方案就是在 navigator
标签内部再加上一个 view
标签,然后所有的样式作用在这个 view
标签身上,这样也可以实现多端兼容。
其实一般情况下作用在 navigator
的样式都是正常的,只有你在 navigator
标签上使用了 flex 布局,并且 flex 布局没有传递到新增的 a 标签,此时页面样式就会乱。
确实可以起作用,可以发现 a 标签已经完全消失,不见了。
支持平台
平台 | 支持版本要求 | 说明 |
---|---|---|
Web平台 | uni-app 3.7.6+ |
使用Vue2或Vue3渲染的H5页面 |
App-vue3 | uni-app 3.7.6+ |
使用Vue3渲染的App平台 |
其他平台 | ❌ 不支持 | 小程序(微信、支付宝等)、其他渲染模式 |
arduino
<text class="text">猜你喜欢</text>
会被编译成以下代码:
xml
<uni-text data-v-e94a02da="" class="text">
<span>猜你喜欢</span>
</uni-text>
arduino
<image class="image" :src="http://yjy-xiaotuxian-dev.oss-cn-beijing.aliyuncs.com/picture/2021-04-15/dfc11bb0-4af5-4e9b-9458-99f615cc685a.jpg">
</image>
会被编译成以下代码:
xml
<uni-image data-v-17cc3903="" class="image">
<!-- 背景图 -->
<div style="background-image: url("http://yjy-xiaotuxian-dev.oss-cn-beijing.aliyuncs.com/picture/2021-04-15/dfc11bb0-4af5-4e9b-9458-99f615cc685a.jpg"); background-position: 0% 0%; background-size: 100% 100%;"></div>
<!-- img 标签 -->
<img src="http://yjy-xiaotuxian-dev.oss-cn-beijing.aliyuncs.com/picture/2021-04-15/dfc11bb0-4af5-4e9b-9458-99f615cc685a.jpg" draggable="false">
</uni-image>

uni-image
这个标签还会有默认样式,导致页面所有的 image 样式都会乱掉,直接手动修改 uniapp
的 uni-image
标签样式就好了。
image 标签添加 lazy-load 属性,使用懒加载:

对于 H5 一点作用没有,只有在小程序里有用,我说为什么设置了懒加载还是疯狂请求,最终想到方案了:
xml
<!-- #ifdef APP-PLUS || MP-WEIXIN -->
<image class="image" :src="item.picture" lazy-load></image>
<!-- #endif -->
<!-- #ifdef H5 -->
<img class="image" :src="item.picture" loading="lazy" alt="" />
<!-- #endif -->
使用条件编译配合 img
标签的 loading="lazy"
实现 H5 图片的懒加载。
ini
<input placeholder="搜索小兔鲜商品" />
会被编译成以下代码:
xml
<uni-input data-v-cdfa925e=""> <!-- 根元素有父组件的scoped属性 -->
<div class="uni-input-wrapper">
<!-- 内部元素继承父组件作用域 -->
<div class="uni-input-placeholder"
data-v-6412fd5e="" <!-- 子组件自己的scoped -->
data-v-cdfa925e="" <!-- 继承的父组件scoped -->
>搜索小兔鲜商品</div>
</div>
</uni-input>
其实有可能就是 uni-input-placeholder
是一个子组件,继承了父组件 scoped,同时生成了自己子组件的 scoped。
xml
<scroll-view
scroll-y
class="scroll-view"
>
<!-- 内容区域 -->
</scroll-view>
会被编译成以下代码:
xml
<uni-scroll-view data-v-83a5a03c="" class="scroll-view">
<div class="uni-scroll-view">
<div class="uni-scroll-view" style="overflow: hidden auto;">
<!-- 下拉刷新区域 -->
<div class="uni-scroll-view-refresher"
style="background-color: rgb(255, 255, 255); height: 0px; transition: height 0.3s;">
<div class="uni-scroll-view-refresh">
<div class="uni-scroll-view-refresh-inner"></div>
</div>
</div>
<!-- 实际内容容器 -->
<div class="uni-scroll-view-content">
<!-- 这里包含原内容组件 -->
</div>
</div>
</div>
</uni-scroll-view>
ini
<picker
class="picker"
mode="date"
start="1900-01-01"
:end="new Date()"
:value="form.birthday"
@change="changeBirthday"
>
<view v-if="form.birthday">{{ form.birthday }}</view>
<view class="placeholder" v-else>请选择日期</view>
</picker>
会编译成以下代码:
xml
<uni-picker class="picker">
<!-- 选择器弹出层 -->
<div class="uni-picker-container uni-region-select">
<!-- 遮罩层 -->
<div class="uni-mask uni-picker-mask" style="display: none;"></div>
<!-- 选择器主体 -->
<div class="uni-picker-custom">
<!-- 标题栏 -->
<div class="uni-picker-header">
<div class="uni-picker-action uni-picker-action-cancel">取消</div>
<div class="uni-picker-action uni-picker-action-confirm">完成</div>
</div>
<!-- 选项区域,包含选项数据 -->
<div class="uni-picker-select">...</div>
<div></div> <!-- 布局占位 -->
</div>
</div>
<!-- 当前值展示区 -->
<div>
<uni-view class="placeholder">请选择日期</uni-view>
</div>
</uni-picker>
省市区选择器 app 和 H5 端用不了。
ruby
<swiper :circular="true" :autoplay="true" :interval="3000" @change="onChange">
<swiper-item v-for="item in list" :key="item.id">
<navigator url="/pages/index/index" hover-class="none" class="navigator">
<image class="image" :src="item.imgUrl"></image>
</navigator>
</swiper-item>
</swiper>
会编译成以下代码:
xml
<uni-swiper>
<!-- 轮播容器 -->
<div class="uni-swiper-wrapper">
<!-- 幻灯片组 -->
<div class="uni-swiper-slides">
<!-- 幻灯片框架 (循环5次) -->
<div class="uni-swiper-slide-frame" style="transform: translate(-200%, 0px);">
<!-- 实际幻灯片项 -->
<uni-swiper-item style="transform: translate(100%, 0px);">
<!-- 导航元素 -->
<uni-navigator>
<a href="/pages/index/index">
<!-- 图像组件 (双重渲染) -->
<uni-image>
<div style="background-image:url(http://...)"></div>
<img src="http://..." draggable="false">
</uni-image>
</a>
</uni-navigator>
</uni-swiper-item>
</div>
<!-- 其他重复结构... -->
</div>
</div>
</uni-swiper>
UniApp
样式编译机制详解:
1. 标签前缀转换规则
原始标签 | 编译后标签 |
---|---|
view |
uni-view |
navigator |
uni-navigator |
input |
uni-input |
scroll-view |
uni-scroll-view |
swiper |
uni-swiper |
image |
uni-image |
button |
uni-button |
2. 页面样式转换规则
特征 | 原始样式 | 编译后样式 | 变化原因 |
---|---|---|---|
选择器作用域 | 普通类选择器 | 带 [data-v-xxxx] 属性选择器 |
Vue Scoped CSS 作用域隔离 |
单位转换 | rpx 单位 |
rem 单位 |
跨平台适配 |
特殊选择器 | page 选择器 |
转为 uni-page-body 和 body |
小程序特有选择器转换 |
在小程序 vs H5 的页面结构
-
小程序页面结构:
xml<!-- 每个小程序页面的根元素 --> <page> <view>页面内容</view> </page>
核心概念 :小程序的
page
元素是整个页面的唯一根容器,直接包含所有内容。 -
H5 实际结构:
xml<body> <!-- 整个网页的基础容器 --> <div class="app-container"> <uni-page-head>顶部导航</uni-page-head> <uni-page-body> <!-- 这是真正的内容容器 --> 页面内容 </uni-page-body> <uni-tab-bar>底部导航</uni-tab-bar> </div> </body>
关键区别 :H5 中的
uni-page-body
对应小程序page
。也就是说
uni-page-body
是网页端项目的页面容器,此时可以在全局样式文件中通过条件编译来把小程序中 page 的样式作用在uni-page-body
元素身上:css/* #ifdef H5 */ uni-page-body { height: 100%; display: flex; flex-direction: column; overflow: hidden; background-color: #f7f7f8; } /* #endif */
核心目标 :通过设置
uni-page-body
的样式,使其成为内容区域的布局容器 ,在垂直方向上自动填充顶部导航栏与底部TabBar
之间的可视区域(即视口的可操作区域),同时保证背景色和布局能覆盖整个内容区域。这样既保持了与小程序的page
容器相似的布局行为,又能正确适配 H5 的多组件嵌套结构。


css
如果你在 H5 端,页面底部有个按钮,比如支付按钮使用了 fixed 布局,并且因为 H5 端的 bottom: 0 是在页面底部的,会和用 `dom` 元素生成的 `tabBar` 重叠,此时 `uniapp` 给你提供了一个帮助:

go
我们可以给按钮设置这么一个 `css` 变量,来让按钮往上抬高一个 `tabBar` 的高度,但这个只是某些情况才这么做,一般页面不这么进行布局,而且 `css` 变量支持动态替换。
UniApp
如何转换 page
选择器
当你在不同页面的CSS中写:
css
page {
display: flex;
background-color: #f4f4f4;
}
UniApp
会编译为:
css
/* 针对内容区域的样式 */
uni-page-body[data-v-db0227ed] {
display: flex;
background-color: #f4f4f4;
}
/* 针对整个页面底层的样式,只保留背景颜色样式 */
body[data-v-db0227ed] {
background-color: #f4f4f4;
}
运行时如何区分页面
-
进入A页面时:
- 给
body
标签添加属性:<body data-v-db0227ed>
- 此时A页面的样式
body[data-v-db0227ed]
生效
- 给
-
切换到B页面时:
- 移除
body
上的data-v-db0227ed
- 添加新属性:
<body data-v-8749bc2f>
- 此时B页面的样式
body[data-v-8749bc2f]
生效
- 移除
关键问题的原因(为什么背景显示不全)
当设置page{background:black;}
时实际发生了什么:
-
body[data-v-xxxx] {background:black;}
:- 使整个网页底层变为黑色
- 影响整个浏览器窗口的背景
-
uni-page-body[data-v-xxxx] {background:black;}
:- 使中间内容区域容器的背景变为黑色
问题根源:
-
uni-page-body
这个容器默认可能有白色背景 -
假设你设置了黑色背景但内容区还是白色,就像这样:
lua+---------------------- 浏览器窗口(黑色背景)----------------------+ | | | [顶部导航] - 可能透明也可能有自己背景 | | +-------------------------------------------------------+ | | | uni-page-body容器(默认白色背景,覆盖了黑色) | | | | | 页面内容 | | | | +-------------------------------------------------------+ | | [底部导航] - 可能透明也可能有自己背景 | | | +---------------------------------------------------------------+
-
效果:底层背景是黑色,但因为中间内容区的白色容器挡在上面,你实际看到的是白色背景。
rpx
单位转 rem
:

ini
<uni-swipe-action-item v-for="address in addressList" :key="address.id">
对于这种组件标签,不要在他身上添加事件监听器,因为你不知道在编译为 H5 代码后这个标签还有没有存在,最好就是在 view 标签,text 标签身上去绑定事件监听器,因为在编译后是一定存在的。
css
:host {...}
这个选择器在小程序中可以直接作用在组件根元素,但是在 H5 就一点作用没有,会被编译成:
markdown
[data-v-ccde5587]:host {...}
而且因为 H5 也没有使用 shadow-dom
所以这个选择器选中不了任何元素。那怎么办呢?
css
/* #ifdef APP-PLUS || MP-WEIXIN */
// 根元素,box-sizing 保证高度不超出 100vh
:host {
height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
background-color: #f7f7f8;
box-sizing: border-box;
}
/* #endif */
使用条件渲染来控制组件样式,小程序端是可以正常使用 :host 标签的,那 H5 不行怎么办呢?
css
/* global.scss 全局添加样式 */
/* #ifdef H5 */
uni-page-body {
height: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
background-color: #f7f7f8;
}
/* #endif */
然后我还发现了一个 bug:

热更新前:
css
.scroll-view[data-v-db0227ed] { flex: 1; }
热更新后:
css
.scroll-view { flex: 1; }
比如删除文件里的一个选择器,整个文件所有选择器的自定义属性就全都丢了,刷新页面也没用,其实这也不算 bug,因为我的 vue
文件实际上是没有添加 scoped 的,估计是 uniapp
会在最开始的阶段给我的样式添加一个 scoped,然后因为我的 style 没有添加 scoped,后续热更新就给他刷掉了。
小程序特有功能,比如微信登录,又比如微信专属的动画效果:

还有微信特有的按钮:

这些都得用条件编译给他去掉。
uniapp
打包的网页实现了和微信小程序一样的缓存页面栈,就是会自动缓存已经访问并且没有销毁的页面,核心原理是什么?


接下来解决 H5 端和 app 端骨架屏失效的问题,为什么微信小程序没问题,在这边就会有问题呢?先来介绍微信小程序的骨架屏为什么可以正常显示:

接下来再来介绍为什么 H5 端跟 App 端不能正常显示骨架屏:

scss
/* #ifdef H5 || APP-PLUS */
/* 引入骨架屏宽高样式 */
@import './style/CategoryPanel.scss';
@import './style/HotPanel.scss';
@import '@/components/style/XtxSwiper.scss';
@import '@/components/style/XtxGuess.scss';
/* #endif */
在骨架屏组件内部使用这种方式来引入骨架屏的宽高样式,记住啊,这里后缀要加 .scss
,不然会找不到这个文件,因为 vite
默认不会解析 .scss
后缀文件,
ini
*// Vite 默认的解析器会尝试查找这些扩展名* const defaultResolveExtensions = [ '.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue', '.svelte', '.marko', '.astro' *// 框架文件扩展* ];
也可以在 vite
中配置:

这样就可以正确识别了,不过最简单的还是把路径写全。
uniCloud
前端网页托管异常
📁 问题描述
-
部署场景
- 项目类型:
uni-app
开发的多端项目(H5+小程序+App) - 部署路径:
https://你的域名/web/
- 后端:
uniCloud
(无法修改服务器配置)
- 项目类型:
-
问题表现
- 访问
https://你的域名/web/
时 - 服务器返回的是根目录的index.html(而非/web目录下的)
- 所有资源路径错误(缺少
/web/
前缀) - 控制台报错404(资源加载失败)
- 访问
其实就是 uniCloud
默认支持了历史模式,不管你访问什么页面,只要不是静态资源,它就直接给你返回 index.html
。
💡 解决方案
在manifest.json
中添加网页端基础路径配置:
json
// manifest.json
{
"h5": {
"router": {
"base": "/web/" // 关键配置!
},
}
✅ 生效原理
路由基础路径 base: "/web/"
会让 vue-router
所有路由自动添加/web/
前缀
这里还有个坑啊,如果是 /web
,那路径拼装会变成 /webstatic/...
,坑比啊。
💎 解决方案优势
- 多端兼容 - 只影响H5端,不影响小程序/App
- 配置简单 - 只需修改
manifest.json
- 维护方便 - 路径配置集中化管理
- 自动生效 - 无需修改业务代码
就上面配置了 base: "/web/"
,接下来会发生什么事情呢?首先来看一下开发服务器的项目结构:

可以注意到在服务器根目录下生成了 web 目录,然后还可以注意到 images 静态资源是放在 /web/src
目录下,tabs 静态资源是放在 /web
目录下,我们接下来模拟访问开发服务器的整个流程:
步骤 1:用户输入根路径并加载首页
-
用户在地址栏输入:
http://localhost:5173/
(根路径) -
浏览器发起请求:
GET http://localhost:5173/
-
服务器处理请求:
- 服务器配置:重定向到
/web
,请求直接返回/web
目录下的index.html
文件。 - 服务器没有为其他路径做任何路由处理。
- 服务器配置:重定向到
-
服务器响应: 将
index.html
文件的内容返回给浏览器。 -
浏览器渲染 HTML: 开始解析收到的
index.html
。
步骤 2:加载 JavaScript 入口文件
-
HTML 解析到 JS 加载标签:
index.html
中有一行关键代码:<script type="module" src="/web/src/main.ts"></script>
-
浏览器发起 JS 请求:
- 浏览器将
src
属性解释为相对于服务器根目录的路径。 - 请求 URL:
GET http://localhost:5173/web/src/main.ts
- 浏览器将
-
服务器处理 JS 请求:
Vite
开发服务器在其项目目录结构中查找/web/src/main.ts
文件。
-
服务器响应: 找到文件
/web/src/main.ts
后,将其内容(以及可能需要的转换/热更新信息)返回给浏览器。 -
浏览器执行 JS: 浏览器接收到 JS 文件内容并开始执行它。此时您的
Vue
应用代码 (main.ts
或其导入的模块) 开始运行。
步骤 3:前端路由初始化并修改 URL
-
路由库初始化:
- 在前端框架(
Vue
)初始化的过程中,前端路由库(Vue Router
)被创建和挂载。 - 路由库读取到配置项
base: "/web/"
。
- 在前端框架(
-
识别前端当前路径:
- 路由库检查当前的浏览器地址栏路径:
http://localhost:5173/
(根路径)。 - 因为它匹配了路由
base ( /web/ )
对应的应用根目录(实际上当前路径/
比/web/
更短)。
- 路由库检查当前的浏览器地址栏路径:
-
前端路径重定向:
- 改变地址栏(客户端行为): 它将地址栏路径替换 为
http://localhost:5173/web/#/
。注意:这个过程完全在浏览器内部进行,并未向服务器发送新的请求! 浏览器只是显示了新 URL。
- 改变地址栏(客户端行为): 它将地址栏路径替换 为
-
当前视图: 前端路由库根据当前匹配的路由路径(比如
/#/
),渲染对应的组件到页面上。
步骤 4:加载应用所需的其他资源(CSS, 图片)
-
应用渲染到 DOM:
- 随着对应组件的渲染,浏览器开始生成最终的 DOM 结构。
- 组件可能包含类似代码:
<img src="/web/static/tabs/cart_default.png">
。这个src
也是以根路径开头的/web/...
。
-
浏览器发起静态资源请求:
- 浏览器遇到
<img>
标签需要加载图片。它将src
属性/web/static/tabs/cart_default.png
解释为相对于服务器根目录的路径。 - 请求 URL:
GET http://localhost:5173/web/static/tabs/cart_default.png
- 浏览器遇到
-
服务器处理静态资源请求:
- 开发服务器识别请求路径
/web/static/tabs/cart_default.png
。
- 开发服务器识别请求路径
-
服务器响应: 找到文件后,将图片数据返回给浏览器。
-
浏览器显示图片:
浏览器接收到图片数据,并将其渲染到页面上的
<img>
标签位置。
步骤 5:用户尝试直接访问或刷新子路由
-
场景1:刷新当前应用页面: 用户在应用内部(URL 为
http://localhost:5173/web/#/pages/category/category
)按下 F5 键刷新页面。 -
场景2:直接访问子路由: 用户在地址栏直接输入
http://localhost:5173/web/#/pages/category/category
并回车。 -
浏览器发起请求:
GET http://localhost:5173/web/
(注意:URL hash#/pages/category/category
不会被发送到服务器!) -
服务器处理请求:
- 服务器识别请求路径
/web/
。 - 用户补充的关键点生效: 服务器配置为访问
/web
路径时会返回根目录下的index.html
。
- 服务器识别请求路径
-
服务器响应: 再次返回
index.html
。 -
浏览器加载过程和初始化: 重复 步骤 1(解析HTML)-> 步骤 2(加载JS)-> 步骤 3(路由初始化) 。
-
前端路由接管:
- 前端路由库初始化时读取
base: "/web/"
。 - 它获取到浏览器地址栏中的完整URL (包括 Hash):
http://localhost:5173/web/#/pages/category/category
。 - 路由库在客户端直接匹配到
/pages/category/category
路由,并渲染对应的页面组件。
- 前端路由库初始化时读取
-
页面渲染完成: 应用成功显示
/pages/category/category
页面。
上面讲述的这个流程对开发环境和生产环境都是适用的,因为开发环境其实也是生成了和上线后一样的项目结构,下次遇到同样的问题,其实可以在开发者工具的源代码中去看我们的项目结构,一下子就一目了然了。
最后进行验证,设置 base: "./"
可以看见开发者服务器生成的目录,就不包含 web 目录了:

此时的路径可以看到是相对路径:

相对谁呢?你想想,先添加 index.html,再添加的 js
,渲染页面,然后加载图片资源,也是说图片是加载在 index.html 这个文件内部的,那图片的相对路径就是 index.html,毕竟是单页应用嘛,所有的相对路径其实都是相对于唯一的 index.html 文件。
这是 SPA 应用的基础设计原则:
-
index.html 是应用的唯一入口点
-
所有资源请求都基于此文件的路径
-
前端路由变化不影响已加载资源的相对路径解析
-
浏览器的路径解析机制:
- 以当前文档的 URL 为基准
- 解析所有相对路径(
./
或../
)
这种设计保证了应用的路径一致性,无论客户端路由如何变化,资源加载路径始终基于初始 HTML 文件位置。
uni-data-picker
组件工作流程
案例背景:
用户选择的行政区信息:
- 省:北京市(code=110000)
- 市:市辖区(code=110100)
- 区:东城区(code=110101)
实际上,传递进入组件的只有 :value="form.countyCode"
也就是 区:东城区(code=110101)
的数据。
xml
<uni-data-picker
<!-- 绑定最终选择的区/县级编码 -->
<!-- 示例:form.countyCode = '110101'(东城区代码) -->
:value="form.countyCode"
<!-- 指定使用的云数据库表名 -->
collection="opendb-city-china"
<!-- field:定义查询返回的字段和映射关系 -->
<!-- 数据库会执行:SELECT code, name FROM region ... -->
<!-- 映射为:{ value: code, text: name } -->
field="code as value, name as text"
<!-- orderby:查询排序规则 -->
<!-- 数据库会执行:ORDER BY code ASC -->
orderby="value asc"
<!-- step-search:是否启用分步搜索(建议更正拼写为step-search) -->
:step-searh="true"
<!-- 用于标识传递的 value 其实对应的是数据库中的哪个字段,参与数据库查询 -->
<!-- 注意,这里得是一个唯一标识符,code = '110101' 类似这样子 -->
self-field="code"
<!-- 用于标识怎么关联上一级节点的字段 -->
<!-- 举个例子,比如现在我们有一个东城区的json数据,我们要怎么找到他上一级市的数据呢? -->
<!-- 在东城区的json数据中有一个parent_code字段可以直接拿到上一级的数据 -->
parent-field="parent_code"
@change="changeRegionAppH5"
>
<view v-if="form.fullLocation">{{ form.fullLocation }}</view>
<view class="placeholder" v-else>请选择城市</view>
</uni-data-picker>
这个组件其实还支持插槽,插槽内部的内容会覆盖掉主件的默认显示,默认显示是一个 input 表单,并且带有清空按钮:


就长这样。

一、未打开状态(回显路径)
具体步骤:
-
查询区级记录:
vbnetSELECT * FROM region WHERE code = '110101' /* 返回:{ code:"110101", name:"东城区", parent_code:"110100" } */
-
查询市级记录:
vbnetSELECT * FROM region WHERE code = '110100' /* 由上条记录的parent_code获得 */ /* 返回:{ code:"110100", name:"市辖区", parent_code:"110000" } */
-
查询省级记录:
sqlSELECT * FROM region WHERE code = '110000' /* 由上条记录的parent_code获得 */ /* 返回:{ code:"110000", name:"北京市", parent_code:null } */
结果生成:
arduino
// 组建selected数组
selected = [
{ value: "110000", text: "北京市" }, // 省
{ value: "110100", text: "市辖区" }, // 市
{ value: "110101", text: "东城区" } // 区
]
// 显示文本
"北京市 / 市辖区 / 东城区"
关键特点:
- 顺序查询:区→市→省(从终端节点向上溯源)
- 字段映射 :自动转换
code → value
,name → text
二、打开状态(级联加载)
具体步骤:
-
查询省级列表:
vbnetSELECT code as value, name as text FROM region WHERE parent_code IS NULL /* 返回所有省份:北京、天津、河北... */
-
查询市级列表:
vbnetSELECT code as value, name as text FROM region WHERE parent_code = '110000' /* 来自selected[0].value */ /* 返回北京市的下级:市辖区、县区... */
-
查询区级列表:
vbnetSELECT code as value, name as text FROM region WHERE parent_code = '110100' /* 来自selected[1].value */ /* 返回市辖区的下级:东城区、西城区... */
这里前端做了优化,根据选中元素列表,直接把三次查询的语句拼成一个数组传递给云函数,云函数可以直接调用三次查询,而不是前端调用三次云函数进行查询,这样就省了请求数,因为本来需要三次请求能实现的事情,现在通过调用一次云函数请求就实现了。
结果展示:
lua
| 省级列表 | 市级列表 | 区级列表 |
|-------------|-------------|-------------|
| ☑ 北京市 | ☑ 北京市 | ☑ 东城区 |
| 天津市 | | 西城区 |
| 河北省 | | |
关键特点:
-
层级依赖:
- 省级查询:
parent_code IS NULL
- 市级查询:
parent_code=省级value
- 区级查询:
parent_code=市级value
- 省级查询:
三、特殊场景处理
场景1:新用户首次打开(无预选值)

用户操作路径:
- 选择「河北省」
- 自动触发市级查询
(parent_code=130000)
- 选择「石家庄市」
- 自动触发区级查询
(parent_code=130100)
场景2:中途变更选择
ini
-- 用户将北京改为天津时:
1. 取消未完成的市/区查询
2. 查询天津的市级: WHERE parent_code='120000'
3. 自动查询首市的区级: WHERE parent_code='120100'