news 2026/9/2 16:37:52

Vue3移动端开发实战:从零构建H5应用的技术栈与最佳实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Vue3移动端开发实战:从零构建H5应用的技术栈与最佳实践

这次我们来看一个面向2026年的Vue3移动APP实战课程。这不是一个简单的概念讲解,而是一个从零开始、手把手带你构建一个完整移动应用的实战项目。对于前端开发者而言,Vue3的Composition API、TypeScript支持、Vite构建工具等新特性,以及如何将它们高效地应用于移动端开发,是当前必须掌握的核心技能。本课程的核心价值在于,它不仅仅教你Vue3的语法,更侧重于如何用Vue3生态解决移动端开发中的实际问题,比如响应式布局、移动端组件库选型、状态管理、性能优化以及与原生能力的交互。

如果你关心的是如何快速上手并产出可用的移动端项目,那么这个实战教程值得你重点关注。它解决了从环境搭建到项目上线的完整链路问题,尤其适合有一定前端基础(了解HTML、CSS、JavaScript),希望向Vue3移动端开发进阶的开发者。本文将基于实战课程的核心脉络,为你拆解其中的关键技术点、环境准备、开发流程以及最佳实践,让你即便不观看完整课程,也能掌握构建一个现代Vue3移动应用的关键步骤和避坑指南。

1. 核心能力速览

能力项说明
技术栈Vue 3 + TypeScript + Vite + Pinia + Vue Router + 移动端UI库(如Vant)
项目类型单页应用(SPA),适配移动端浏览器或WebView嵌入
核心目标从零构建一个功能完整的移动端应用,涵盖主流业务场景
环境门槛Node.js (建议16.x或18.x LTS)、npm/yarn/pnpm、代码编辑器(如VSCode)
硬件要求普通开发电脑即可,无特殊GPU或显存要求
启动方式命令行启动开发服务器,支持热重载(HMR)
构建输出可生成静态资源,用于部署到任何Web服务器或混合App框架(如Cordova、Capacitor)
适合场景移动端H5开发、混合应用开发、PWA应用、前端技能进阶

2. 适用场景与使用边界

这个Vue3移动APP实战教程主要适用于以下几类开发者:

  1. Vue2转Vue3的开发者:希望系统学习Vue3在移动端项目中的新写法和最佳实践。
  2. 前端新手寻求项目经验:通过一个完整的项目来巩固HTML、CSS、JS基础,并学习现代前端工程化流程。
  3. 需要开发移动端H5页面的工程师:无论是运营活动页还是完整的应用,本教程提供的技术选型和架构思路都有参考价值。
  4. 计划使用Uni-app、Taro等跨端框架的开发者:理解其底层的Vue3开发模式,能更好地驾驭这些框架。

它能解决的问题包括:

  • 工程化入门:如何使用Vite快速搭建、开发和构建Vue3项目。
  • 移动端适配:如何实现不同尺寸移动设备的UI自适应。
  • 状态管理:如何使用Pinia在大型应用中优雅地管理共享状态。
  • 路由设计:如何用Vue Router规划移动端多页面应用的导航结构。
  • 组件化开发:如何封装和复用移动端业务组件。
  • 性能优化:针对移动端网络和性能特点进行代码分割、懒加载等优化。
  • API交互:如何规范地与后端RESTful API进行数据通信。

需要注意的边界:

  • 非原生开发:本教程聚焦于使用Vue3开发Web技术栈的移动端应用(H5),不涉及使用Swift、Kotlin或React Native、Flutter进行原生或跨原生开发。
  • 特定功能依赖:如摄像头、蓝牙、GPS等深度设备功能,在纯H5环境中能力有限,通常需要借助Cordova、Capacitor等桥接方案或嵌入原生WebView实现。
  • 浏览器兼容性:现代Vue3语法(如<script setup>)和构建工具对旧版浏览器(如IE)支持不佳,目标通常是iOS/Android的现代WebView或主流移动浏览器。

3. 环境准备与前置条件

在开始编码之前,确保你的开发环境已经就绪。一个稳定、版本匹配的环境能避免大量后续的配置错误。

  1. Node.js与包管理器

    • Node.js:这是运行JavaScript和服务的基础。建议安装Node.js 16.x18.x的LTS(长期支持)版本。你可以从 Node.js官网 下载安装包。安装完成后,在终端中运行以下命令验证:
      node -v npm -v
    • 包管理器:npm随Node.js安装。你也可以选择更快的yarnpnpmpnpm因其磁盘空间效率和速度近年来备受推崇。安装pnpm
      npm install -g pnpm
  2. 代码编辑器

    • Visual Studio Code (VSCode)是当前前端开发的首选,拥有丰富的Vue和TypeScript插件生态。务必安装以下插件以提升开发体验:
      • Volar:Vue3官方推荐的语言支持插件,替代之前的Vetur。
      • TypeScript Vue Plugin (Volar):为Vue单文件组件中的TypeScript提供支持。
      • ESLint:代码规范检查。
      • Prettier:代码自动格式化。
  3. 浏览器开发者工具

    • 推荐使用ChromeEdge浏览器进行开发调试。熟练使用其设备模拟器(Device Toolbar)来切换不同手机型号、分辨率,并模拟触摸事件、慢速网络等移动端场景。
  4. 项目目录规划(心理准备)

    • 想好你的项目要放在哪个磁盘路径。一个清晰的目录结构有助于长期维护。

4. 项目创建与工程化配置

我们将使用Vite作为构建工具,它比传统的Vue CLI启动更快,配置更简洁。

  1. 使用Vite创建Vue3项目: 打开终端,进入你计划存放项目的目录,执行以下命令。这里我们使用pnpm,并选择vue-ts模板来集成TypeScript。

    pnpm create vite@latest my-vue3-mobile-app --template vue-ts

    命令执行后,按照提示进入项目目录并安装依赖:

    cd my-vue3-mobile-app pnpm install
  2. 启动开发服务器: 依赖安装完成后,运行以下命令启动开发服务器:

    pnpm run dev

    终端会输出本地服务器地址(通常是http://localhost:5173)。在浏览器中打开它,你应该能看到Vue的默认欢迎页面。这证明你的基础项目已经成功运行。

  3. 安装核心依赖: 一个完整的移动端项目通常需要以下库。在项目根目录下执行:

    pnpm add vue-router@4 pinia pnpm add -D sass
    • vue-router@4: Vue3官方路由管理器。
    • pinia: Vue3官方推荐的状态管理库,比Vuex更简洁、类型安全。
    • sass: 预处理器,用于编写更强大的样式。
  4. 安装移动端UI组件库: 为了提高开发效率,我们选用一个成熟的移动端Vue组件库。Vant是一个不错的选择,它组件丰富、文档清晰,且对Vue3支持良好。

    pnpm add vant

    为了方便按需引入组件,减少打包体积,我们还需要安装一个插件:

    pnpm add -D unplugin-vue-components

    然后,在vite.config.ts中进行配置:

    import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import Components from 'unplugin-vue-components/vite' import { VantResolver } from 'unplugin-vue-components/resolvers' export default defineConfig({ plugins: [ vue(), Components({ resolvers: [VantResolver()], }), ], })

    这样配置后,你就可以在模板中直接使用Vant组件(如<van-button>),它们会被自动按需引入。

5. 核心功能开发与实战演练

接下来,我们将模拟一个典型的移动应用(例如一个简单的商品展示与购物车应用)来串联核心功能。

5.1 项目结构与路由配置

首先,清理src目录,并创建符合业务逻辑的文件夹结构:

src/ ├── api/ # 接口请求封装 ├── assets/ # 静态资源 ├── components/ # 公共组件 ├── composables/ # 组合式函数 ├── router/ # 路由配置 ├── stores/ # Pinia状态仓库 ├── styles/ # 全局样式 ├── utils/ # 工具函数 ├── views/ # 页面组件 ├── App.vue └── main.ts

src/router/index.ts中配置路由:

import { createRouter, createWebHistory } from 'vue-router' import Home from '../views/Home.vue' import Category from '../views/Category.vue' import Cart from '../views/Cart.vue' import Mine from '../views/Mine.vue' const routes = [ { path: '/', redirect: '/home' }, { path: '/home', component: Home, meta: { title: '首页' } }, { path: '/category', component: Category, meta: { title: '分类' } }, { path: '/cart', component: Cart, meta: { title: '购物车' } }, { path: '/mine', component: Mine, meta: { title: '我的' } }, ] const router = createRouter({ history: createWebHistory(), routes, }) // 全局路由守卫示例:修改页面标题 router.beforeEach((to) => { document.title = to.meta.title as string || 'Vue3移动APP' }) export default router

main.ts中引入并使用路由。

5.2 移动端适配与全局样式

移动端适配的核心是让页面在不同宽度和DPR(设备像素比)的设备上都能正常显示。我们通常采用viewport+rem/vw的方案。

  1. Viewport设置:在index.html<head>中添加:

    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">

    viewport-fit=cover对于全面屏手机尤其重要。

  2. REM适配(推荐):我们使用postcss-pxtorem插件自动将px转为rem。首先安装:

    pnpm add -D postcss-pxtorem

    然后在项目根目录创建postcss.config.js

    export default { plugins: { 'postcss-pxtorem': { rootValue: 37.5, // 设计稿宽度 / 10。若设计稿为375,则设为37.5 propList: ['*'], // 需要转换的属性,*表示所有 selectorBlackList: ['.norem'] // 忽略转换的类名 } } }

    src/styles/下创建base.scss,设置根字体大小:

    // 基于375px设计稿的rem基准值 html { font-size: calc(100vw / 3.75); // 100vw / (设计稿宽度/100) } body { margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; background-color: #f7f8fa; // Vant默认背景色 }

    main.ts中引入此样式文件。

5.3 状态管理(Pinia)实战:购物车

购物车状态是典型的全局状态,非常适合用Pinia管理。

  1. 创建购物车Store:在src/stores/cart.ts中:

    import { defineStore } from 'pinia' import { ref, computed } from 'vue' // 定义商品类型 interface CartItem { id: number name: string price: number count: number image: string } export const useCartStore = defineStore('cart', () => { // 状态 const items = ref<CartItem[]>([]) // Getter (计算属性) const totalCount = computed(() => { return items.value.reduce((sum, item) => sum + item.count, 0) }) const totalPrice = computed(() => { return items.value.reduce((sum, item) => sum + item.price * item.count, 0) }) // Actions (方法) const addItem = (goods: Omit<CartItem, 'count'>) => { const existingItem = items.value.find(item => item.id === goods.id) if (existingItem) { existingItem.count += 1 } else { items.value.push({ ...goods, count: 1 }) } } const removeItem = (id: number) => { const index = items.value.findIndex(item => item.id === id) if (index > -1) { items.value.splice(index, 1) } } const updateItemCount = (id: number, count: number) => { const item = items.value.find(item => item.id === id) if (item) { item.count = Math.max(0, count) if (item.count === 0) { removeItem(id) } } } const clearCart = () => { items.value = [] } return { items, totalCount, totalPrice, addItem, removeItem, updateItemCount, clearCart } })
  2. 在组件中使用Store:在src/views/Cart.vue中:

    <script setup lang="ts"> import { useCartStore } from '@/stores/cart' import { storeToRefs } from 'pinia' import { VanEmpty, VanCard, VanSubmitBar } from 'vant' const cartStore = useCartStore() // 使用storeToRefs保持响应式 const { items, totalPrice } = storeToRefs(cartStore) const { updateItemCount, removeItem, clearCart } = cartStore </script> <template> <div class="cart-page"> <van-empty v-if="items.length === 0" description="购物车空空如也" /> <div v-else> <van-card v-for="item in items" :key="item.id" :price="item.price" :title="item.name" :thumb="item.image" > <template #footer> <van-stepper v-model="item.count" @change="(val) => updateItemCount(item.id, val)" theme="round" button-size="22" /> <van-button size="mini" type="danger" @click="removeItem(item.id)" >删除</van-button> </template> </van-card> <van-submit-bar :price="totalPrice * 100" // Vant价格单位是分 button-text="提交订单" @submit="handleSubmit" /> </div> </div> </template>

5.4 组件封装与复用:商品卡片

将商品卡片封装成可复用组件,是提升开发效率的关键。

src/components/GoodsCard.vue中:

<script setup lang="ts"> import { computed } from 'vue' interface Props { id: number title: string price: number image: string desc?: string tag?: string } const props = withDefaults(defineProps<Props>(), { desc: '', tag: '' }) const emit = defineEmits<{ (e: 'addToCart', id: number): void (e: 'click', id: number): void }>() const formattedPrice = computed(() => `¥${props.price.toFixed(2)}`) </script> <template> <div class="goods-card" @click="emit('click', id)"> <div class="card-image"> <img :src="image" :alt="title" /> <span v-if="tag" class="image-tag">{{ tag }}</span> </div> <div class="card-content"> <h3 class="title">{{ title }}</h3> <p v-if="desc" class="desc">{{ desc }}</p> <div class="footer"> <span class="price">{{ formattedPrice }}</span> <van-button size="small" type="primary" round @click.stop="emit('addToCart', id)" >加入购物车</van-button> </div> </div> </div> </template> <style scoped lang="scss"> .goods-card { background: #fff; border-radius: 8px; overflow: hidden; margin-bottom: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.05); .card-image { position: relative; width: 100%; height: 0; padding-bottom: 75%; // 4:3比例 img { position: absolute; width: 100%; height: 100%; object-fit: cover; } .image-tag { position: absolute; top: 8px; left: 8px; background: #ff4444; color: white; padding: 2px 6px; border-radius: 4px; font-size: 12px; } } .card-content { padding: 12px; .title { margin: 0 0 6px 0; font-size: 16px; line-height: 1.4; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; } .desc { color: #999; font-size: 12px; margin-bottom: 10px; } .footer { display: flex; justify-content: space-between; align-items: center; .price { color: #ff4444; font-size: 18px; font-weight: bold; } } } } </style>

5.5 网络请求封装(Axios)

src/utils/request.ts中封装一个通用的axios实例:

import axios from 'axios' import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios' import { showToast } from 'vant' // 创建axios实例 const service: AxiosInstance = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL || '/api', // 从环境变量读取 timeout: 10000, headers: { 'Content-Type': 'application/json;charset=utf-8' } }) // 请求拦截器 service.interceptors.request.use( (config: AxiosRequestConfig) => { // 可在此处添加token等 // const token = localStorage.getItem('token') // if (token) { // config.headers!['Authorization'] = `Bearer ${token}` // } return config }, error => { return Promise.reject(error) } ) // 响应拦截器 service.interceptors.response.use( (response: AxiosResponse) => { const res = response.data // 根据后端约定调整 if (res.code !== 200) { showToast(res.message || '请求失败') return Promise.reject(new Error(res.message || 'Error')) } else { return res.data } }, error => { showToast('网络错误或请求超时') return Promise.reject(error) } ) export default service

src/api/goods.ts中定义商品相关的接口函数:

import request from '@/utils/request' export interface GoodsItem { id: number name: string price: number image: string desc: string } export function fetchGoodsList(params: { page: number; size: number }) { return request.get<GoodsItem[]>('/goods/list', { params }) } export function fetchGoodsDetail(id: number) { return request.get<GoodsItem>(`/goods/${id}`) }

6. 性能优化与构建部署

移动端应用对性能尤为敏感,以下是一些关键的优化点。

6.1 路由懒加载

Vite支持动态导入,这会让每个路由组件打包成独立的chunk,实现按需加载。 修改src/router/index.ts

const routes = [ { path: '/home', component: () => import('../views/Home.vue'), // 懒加载 meta: { title: '首页' } }, // ... 其他路由同理 ]

6.2 图片资源优化

  1. 使用WebP格式:在支持WebP的浏览器中,使用WebP图片可以显著减小体积。可以使用构建插件(如vite-plugin-webp)或在云服务端进行转换。
  2. 图片懒加载:对于长列表中的图片,使用Vant的<van-image>组件或原生的loading="lazy"属性。
  3. 使用CDN:将静态资源部署到CDN,加速资源加载。

6.3 构建与部署

  1. 环境变量:在项目根目录创建.env.development.env.production文件,管理不同环境下的API地址等配置。

    # .env.production VITE_API_BASE_URL=https://api.your-domain.com
  2. 构建命令:运行pnpm run build,Vite会将项目打包到dist目录。这个目录包含了所有静态文件(HTML, JS, CSS, 图片等)。

  3. 部署:将dist目录下的所有文件上传到任何静态文件托管服务,如Nginx、Apache、Vercel、Netlify、GitHub Pages或对象存储(OSS)等。

7. 常见问题与排查方法

在Vue3移动端开发过程中,你可能会遇到以下典型问题:

问题现象可能原因排查方式解决方案
页面样式错乱,布局异常1. REM适配未生效。
2. UI组件库样式未正确引入。
3. 存在全局样式污染。
1. 检查postcss.config.js配置和根元素font-size计算。
2. 检查浏览器开发者工具,查看元素样式是否被正确应用。
3. 检查是否有全局CSS覆盖了组件样式。
1. 确认设计稿基准值(rootValue)设置正确。
2. 确保按需引入插件(如unplugin-vue-components)配置正确。
3. 使用scoped样式或CSS Modules隔离组件样式。
Vant组件无法显示或功能异常1. 组件未正确注册或引入。
2. 版本不兼容(Vue3项目使用了Vant3)。
1. 检查是否使用了按需引入,并确认组件名拼写正确。
2. 检查package.jsonvant的版本是否为^4.x(Vue3版本)。
1. 确认vite.config.tsVantResolver已配置。
2. 运行pnpm update vant确保版本为最新4.x。
TypeScript类型报错1. 未安装或未正确配置Volar插件。
2. 自定义类型声明文件缺失。
1. 在VSCode中检查Volar和TypeScript Vue Plugin是否启用。
2. 查看终端或编辑器中的具体错误信息。
1. 在VSCode中禁用Vetur,启用Volar。
2. 为第三方库或自定义模块创建.d.ts声明文件。
路由跳转后页面空白1. 路由组件路径错误或组件未导出默认对象。
2. 路由模式(history)与服务器配置不匹配。
1. 检查路由配置中的component路径和组件文件是否存在且默认导出。
2. 部署到服务器后,刷新非根路径页面出现404。
1. 使用@/别名确保路径正确,检查组件文件。
2. 在Nginx等服务器配置中,将所有非静态资源请求重定向到index.html(SPA Fallback)。
移动端点击有延迟或无法滚动1. 未使用@touchstart等原生事件或未处理touch事件。
2. 有元素阻止了默认滚动行为。
1. 在iOS Safari上测试。
2. 检查是否有preventDefault错误地阻止了触摸事件。
1. 引入fastclick库(已不推荐)或使用CSS属性touch-action: manipulation;
2. 确保在自定义滚动区域正确使用@touchmovepreventDefault
构建后文件过大1. 未进行代码分割。
2. 图片等静态资源未压缩。
3. 引入了未使用的第三方库。
1. 运行pnpm run preview或使用rollup-plugin-visualizer分析包体积。
2. 检查dist/assets目录下图片大小。
1. 确保路由使用懒加载(() => import())。
2. 使用Vite内置的资产处理或vite-plugin-imagemin压缩图片。
3. 使用按需引入,并利用Tree Shaking。

8. 最佳实践与使用建议

  1. 始终使用<script setup>语法:这是Vue3组合式API的编译时语法糖,能让代码更简洁、类型推导更友好,是当前Vue3单文件组件的首选写法。
  2. 拥抱TypeScript:即使项目初期不大,也建议直接使用TypeScript。它能提供更好的代码提示、减少运行时错误,并与Pinia、Vue Router等生态完美结合。
  3. 状态管理分层:不要将所有状态都塞进Pinia。将组件内部状态用ref/reactive管理,将需要跨组件共享的状态才放入Pinia Store。
  4. 合理拆分组件:遵循“单一职责”原则。将大的页面拆分为可复用的业务组件和展示组件,放在src/components目录下。
  5. 善用Composables:将可复用的逻辑(如表单验证、数据获取、设备能力判断)抽取到src/composables目录下的函数中,这是组合式API的核心优势。
  6. 移动端专属处理
    • 安全区域:使用env(safe-area-inset-bottom)等CSS常量来处理iPhone等设备的刘海和底部横条。
    • 点击态:为按钮等可点击元素添加:active样式,提升交互反馈。
    • 虚拟键盘:注意输入框聚焦时,虚拟键盘弹出可能导致的布局挤压问题。
  7. 代码规范与提交:在项目初期就配置好ESLint和Prettier,并考虑使用huskylint-staged在提交前自动检查和格式化代码。
  8. 监控与错误收集:在生产环境中,集成像Sentry这样的错误监控工具,以便及时发现和修复用户端的问题。

通过以上从环境搭建、工程配置、核心功能开发到性能优化和问题排查的完整流程,你已经掌握了使用Vue3构建一个现代化移动端应用的核心技能栈。这个实战框架具有很强的可扩展性,你可以在此基础上接入更复杂的业务模块、状态管理、动画库或跨端方案。下一步,可以尝试将你的应用与真正的后端API对接,或者探索如何将其打包成混合应用(如使用Capacitor),向应用商店发布你的第一个作品。建议将本文提及的配置、代码片段和排查清单收藏备用,在实际开发中遇到问题时快速回顾。

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

CAN总线Matlab驱动开发与调试实战:从收发报文到DBC解析

简介&#xff1a;针对 CAN 总线与 MATLAB 联调需求&#xff0c;这份驱动包以周立功 USBCAN 设备为对象&#xff0c;涵盖从底层驱动配置到上层 GUI 设计的完整链路&#xff0c;特别适合汽车电子、工业自动化等领域的嵌入式工程师和 MATLAB 开发者。资源共 159 个文件&#xff0c…

作者头像 李华
网站建设 2026/9/2 16:33:06

【单片机课程设计/毕业设计】基于 STM32 的智能环境安防语音控制系统设计 基于 STM32 与 ESP-01S 的物联网家居环境监控系统设计

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机&#xff0c;Java、小程序技术领域和毕业项目实战 ✌️…

作者头像 李华
网站建设 2026/9/2 16:29:25

华为AI软件岗面试攻略:从机试到大模型高频考点与避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/2 16:28:27

JBoss EAP 7.1.0部署调优实战:企业级Java中间件的稳定之选

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/2 16:26:22

PCB布线过孔添加与设置笔记

PCB布线过孔添加与设置笔记前言一、过孔的作用二、过孔的类型三、线宽与过孔选择四、添加过孔的完整流程4.1 未添加过孔时的问题4.2 在规则管理器中添加过孔五、添加自定义过孔5.1 指定过孔文件路径5.2 路径配置详解5.3 在 Edit Via List 中添加过孔文件六、走线中放置过孔七、…

作者头像 李华
网站建设 2026/9/2 16:25:20

超越承重:铸铁台选型评估框架与工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华