从零开始:手把手教你用 Uniapp 搭建多平台应用

这个框架能帮你解决什么问题?

想象一下,你需要开发一个移动应用,但是你的老板说:“我们需要同时上线 Android、iOS 还有华为的鸿蒙系统”。传统的做法是什么?雇三个团队,写三套代码,维护三个项目… 想想都头疼对吧?

这个框架就是来拯救你的!一套代码,三个平台同时搞定。不用学 Java、Swift、ArkTS,只要你会 Vue,就能轻松上手。

简单来说,这个框架就像是一个"翻译官",你用熟悉的 Vue 语法写代码,它帮你自动翻译成各个平台能理解的语言。

这个框架有什么特别之处?

写一次代码,到处都能用

还记得小时候用万能遥控器吗?一个遥控器能控制所有家电。这个框架就是代码界的万能遥控器:

  • 你写的代码可以同时跑在 安卓手机、苹果手机、华为鸿蒙系统
  • 改了一行代码,所有平台都会同步更新,再也不用维护三套代码
  • 支持 热重载,就是你改完代码按保存,手机上的应用立马就更新了,不用重新打包

用的都是最新最好的技术

我们选择的技术栈就像买车一样,都挑最可靠的品牌:

  • Vue3:就是那个国内最流行的前端框架,简单易学,社区活跃
  • TypeScript:可以理解为"有智能提示的 JavaScript",写代码不容易出错
  • Pinia:管理应用状态的工具,比如用户信息、购物车数据这些
  • SCSS:升级版的 CSS,可以用变量、嵌套等高级功能

️ 自带全套工具,开箱即用

就像买房子带精装修一样,常用的功能我们都给你准备好了:

  • 网络请求:和服务器通信的工具,自动处理错误情况
  • 本地存储:把数据保存在手机里,支持设置过期时间
  • 常用组件:加载动画、空页面提示等,直接拿来用
  • 工具函数:日期格式化、字符串处理等常用功能

项目结构长什么样?

把这个项目想象成一个公司,每个文件夹就是一个部门,各司其职:

uniapp-multi-platform/          # 公司总部
├── src/                        # 核心业务部门
│   ├── api/                   #  对外联络部(负责和服务器打交道)
│   │   └── index.ts           # 统一管理所有接口
│   ├── assets/                #  资源管理部
│   │   ├── fonts/            # 字体库(各种好看的字体)
│   │   ├── icons/            # 图标库(小图标们的家)
│   │   ├── images/           # 图片库(大图片住这里)
│   │   └── styles/           # 设计部(统一的样式规范)
│   ├── components/            #  零件制造部
│   │   └── common/           # 通用零件车间
│   │       ├── EmptyState.vue      # "暂无数据"提示器
│   │       └── LoadingSpinner.vue  # 转圈圈加载器
│   ├── pages/                #  产品展示部(用户看到的页面)
│   │   ├── index/           # 首页
│   │   └── user/            # 用户中心
│   ├── store/               # ️ 数据仓库部
│   │   ├── app.ts          # 应用全局数据
│   │   └── user.ts         # 用户相关数据
│   ├── types/               #  规则制定部(TypeScript 类型定义)
│   │   └── uni.d.ts        # uniapp 的使用说明书
│   └── utils/               #  工具维修部
│       ├── common.ts       # 万能工具箱
│       ├── request.ts      # 网络通信工具
│       └── storage.ts      # 本地存储工具
├── App.vue                  #  公司大门(应用入口)
├── main.ts                  # ⚡ 电源总开关
├── pages.json              # ️ 导航地图(页面路由配置)
├── manifest.json           #  营业执照(应用信息配置)
└── uni.scss               #  公司VI标准(全局样式)

这样的结构有什么好处?找东西超级方便! 想找网络请求相关的代码?直接去 utils/request.ts。想添加新页面?去 pages/ 文件夹。想修改全局样式?uni.scss 等着你。

核心功能模块详解

1. 状态管理(Pinia)- 应用的"大脑"

这是啥?

想象一下你的应用是一个商店,状态管理就像是这个商店的"收银台",所有重要信息都在这里集中管理:用户信息、购物车内容、商品库存等等。

为什么需要它?

没有状态管理的应用就像没有大脑的人 - 页面 A 记住的用户信息,页面 B 就忘了;用户登录状态在这个组件有效,那个组件就不认识你了。

怎么用?让我们看个实例
// store/app.ts - 这是应用的"大脑"
export const useAppStore = defineStore('app', () => {
  // 这些是应用要记住的重要信息
  const appVersion = ref('1.0.0')          // 应用版本号
  const systemInfo = ref(null)             // 手机系统信息
  const networkType = ref('unknown')       // 网络状态
  const currentUser = ref(null)            // 当前登录用户
  
  // 初始化应用 - 就像开机启动时要做的事情
  const initApp = async () => {
    await getSystemInfo()    // 获取手机信息
    await getNetworkType()   // 检查网络状态
    checkLoginStatus()       // 检查是否已登录
  }
  
  // 用户登录
  const login = async (username, password) => {
    try {
      const user = await api.login(username, password)
      currentUser.value = user
      showToast('登录成功!')
    } catch (error) {
      showToast('登录失败,请检查用户名和密码')
    }
  }
  
  // 用户退出
  const logout = () => {
    currentUser.value = null
    removeStorage('token')  // 清除本地存储的登录凭证
    showToast('已退出登录')
  }
  
  return {
    appVersion,
    systemInfo,
    networkType,
    currentUser,
    initApp,
    login,
    logout
  }
})
在页面中怎么使用?
<template>
  <view>
    <text>当前版本:{{ appStore.appVersion }}</text>
    <text v-if="appStore.currentUser">
      欢迎,{{ appStore.currentUser.name }}</text>
    <button v-else @click="showLogin">点击登录</button>
  </view>
</template>

<script setup>
import { useAppStore } from '@/store/app'

const appStore = useAppStore()

const showLogin = () => {
  // 调用登录功能
  appStore.login('用户名', '密码')
}
</script>

好处是什么?

  • 任何页面都能访问用户信息,不用层层传递
  • 用户信息更新后,所有使用的地方都会自动更新
  • 数据持久化,应用重启后还能记住用户状态

2. 网络请求封装 - 应用的"快递员"

这是干啥的?

你的应用需要和服务器"聊天"对吧?比如登录、获取数据、上传图片等。网络请求就是这个"快递员",负责在你的应用和服务器之间传递消息。

为什么要封装?

原生的网络请求就像让你自己去邮局寄快递 - 每次都要填一大堆单子,很麻烦。我们的封装就像有了个专属快递员,你只要说"帮我寄个包裹到这个地址",其他的他都帮你搞定。

具体怎么用?
// utils/request.ts - 这是我们的"快递公司"
const request = axios.create({
  baseURL: 'https://api.yourapp.com',    // 服务器地址
  timeout: 10000,                       // 10秒没响应就算超时
  headers: {
    'Content-Type': 'application/json'  // 告诉服务器我们发的是JSON
  }
})

// 请求拦截器 - 快递出发前的检查
request.interceptors.request.use(config => {
  // 自动添加用户令牌(就像寄快递要写寄件人信息)
  const token = getStorage('token')
  if (token) {
    config.headers.Authorization = `Bearer ${token}`
  }
  
  // 添加时间戳防止缓存
  if (config.method === 'get') {
    config.params = { ...config.params, _t: Date.now() }
  }
  
  console.log('发送请求:', config.url)
  return config
})

// 响应拦截器 - 快递到了的处理
request.interceptors.response.use(
  response => {
    console.log('请求成功:', response.config.url)
    
    // 统一处理服务器返回的数据格式
    const { code, data, message } = response.data
    
    if (code === 200) {
      return data  // 只返回真正的数据
    } else if (code === 401) {
      // 登录过期,自动跳转到登录页
      showToast('登录过期,请重新登录')
      uni.navigateTo({ url: '/pages/login/login' })
      return Promise.reject(new Error('登录过期'))
    } else {
      // 其他错误,显示错误信息
      showToast(message || '请求失败')
      return Promise.reject(new Error(message))
    }
  },
  error => {
    console.log('请求失败:', error)
    
    // 网络错误的友好提示
    if (error.code === 'NETWORK_ERROR') {
      showToast('网络连接失败,请检查网络')
    } else if (error.code === 'TIMEOUT') {
      showToast('请求超时,请稍后重试')
    } else {
      showToast('服务器开小差了,请稍后重试')
    }
    
    return Promise.reject(error)
  }
)
实际使用示例
// api/index.ts - 定义具体的接口
export const userApi = {
  // 用户登录
  login: (username: string, password: string) => {
    return request.post('/auth/login', { username, password })
  },
  
  // 获取用户信息
  getUserInfo: () => {
    return request.get('/user/info')
  },
  
  // 更新用户头像
  updateAvatar: (avatarFile: File) => {
    const formData = new FormData()
    formData.append('avatar', avatarFile)
    return request.post('/user/avatar', formData, {
      headers: { 'Content-Type': 'multipart/form-data' }
    })
  },
  
  // 获取商品列表
  getProductList: (page: number, size: number) => {
    return request.get('/products', { params: { page, size } })
  }
}

// 在页面中使用
const handleLogin = async () => {
  try {
    showLoading('登录中...')
    const userInfo = await userApi.login(username.value, password.value)
    console.log('登录成功,用户信息:', userInfo)
    
    // 保存用户信息到状态管理
    const appStore = useAppStore()
    appStore.setUserInfo(userInfo)
    
    // 跳转到首页
    uni.switchTab({ url: '/pages/index/index' })
  } catch (error) {
    console.log('登录失败:', error.message)
    // 错误信息已经在拦截器中显示了,这里不用再处理
  } finally {
    hideLoading()
  }
}

这样封装的好处:

  • 自动处理错误:网络断了、服务器挂了,都有友好提示
  • 自动添加认证:登录后的请求自动带上用户令牌
  • 统一数据格式:不管后端返回什么格式,前端拿到的都是统一的
  • 简化使用:一行代码发请求,不用写一堆配置

3. 本地存储工具 - 应用的"记忆库"

这是啥?

想象一下你的手机有个小盒子,可以把重要的东西放进去,下次打开应用还能找到。这就是本地存储 - 把数据保存在用户手机里,应用关闭了再打开,数据还在。

常见的使用场景
  • 用户偏好设置:主题色、字体大小、语言选择
  • 登录状态:用户 token,下次打开应用自动登录
  • 临时数据:购物车内容、表单草稿
  • 缓存数据:避免重复请求的商品列表、用户信息等
我们的存储工具有什么特别的?

支持过期时间! 就像超市的食品有保质期一样,我们可以给数据设定"保质期",过期自动删除。

// utils/storage.ts - 智能存储工具
export const setStorage = (key: string, value: any, expire?: number) => {
  const data = {
    value,                                    // 真正要存的数据
    expire: expire ? Date.now() + expire : null,  // 过期时间戳
    createTime: Date.now()                   // 创建时间
  }
  
  try {
    uni.setStorageSync(key, JSON.stringify(data))
    console.log(`✅ 数据已保存: ${key}`)
  } catch (error) {
    console.error('❌ 存储失败:', error)
  }
}

export const getStorage = (key: string) => {
  try {
    const jsonData = uni.getStorageSync(key)
    if (!jsonData) return null
    
    const data = JSON.parse(jsonData)
    
    // 检查是否过期
    if (data.expire && Date.now() > data.expire) {
      console.log(`⏰ 数据已过期,自动清除: ${key}`)
      removeStorage(key)
      return null
    }
    
    return data.value
  } catch (error) {
    console.error('❌ 读取存储失败:', error)
    return null
  }
}

export const removeStorage = (key: string) => {
  try {
    uni.removeStorageSync(key)
    console.log(`️ 数据已删除: ${key}`)
  } catch (error) {
    console.error('❌ 删除失败:', error)
  }
}

export const clearStorage = () => {
  try {
    uni.clearStorageSync()
    console.log(' 所有存储数据已清除')
  } catch (error) {
    console.error('❌ 清除失败:', error)
  }
}

// 检查存储使用情况
export const getStorageInfo = () => {
  try {
    const info = uni.getStorageInfoSync()
    console.log(' 存储使用情况:', {
      已使用: `${info.currentSize}KB`,
      总容量: `${info.limitSize}KB`,
      所有key: info.keys
    })
    return info
  } catch (error) {
    console.error('❌ 获取存储信息失败:', error)
    return null
  }
}
实际使用示例
// 实际应用中怎么用

// 1. 保存用户登录信息(7天后过期)
const saveUserLogin = (userInfo, token) => {
  setStorage('userInfo', userInfo, 7 * 24 * 60 * 60 * 1000)  // 7天
  setStorage('token', token, 7 * 24 * 60 * 60 * 1000)
}

// 2. 保存用户设置(永不过期)
const saveUserSettings = (settings) => {
  setStorage('userSettings', settings)  // 不传过期时间就是永久保存
}

// 3. 缓存商品列表(30分钟后过期)
const cacheProductList = (products) => {
  setStorage('productList', products, 30 * 60 * 1000)  // 30分钟
}

// 4. 保存表单草稿(1小时后过期)
const saveDraft = (formData) => {
  setStorage('formDraft', formData, 60 * 60 * 1000)  // 1小时
}

// 读取数据
const userInfo = getStorage('userInfo')       // 可能返回用户信息或null(如果过期)
const settings = getStorage('userSettings')   // 用户设置
const products = getStorage('productList')    // 可能返回商品列表或null(如果过期)

// 在页面中使用
const loadUserData = () => {
  const userInfo = getStorage('userInfo')
  const token = getStorage('token')
  
  if (userInfo && token) {
    // 用户已登录且未过期
    console.log('用户自动登录成功')
    appStore.setUserInfo(userInfo)
  } else {
    // 用户未登录或登录已过期
    console.log('需要重新登录')
    uni.navigateTo({ url: '/pages/login/login' })
  }
}

// 主题设置示例
const toggleTheme = () => {
  const currentTheme = getStorage('theme') || 'light'
  const newTheme = currentTheme === 'light' ? 'dark' : 'light'
  
  setStorage('theme', newTheme)  // 永久保存主题设置
  applyTheme(newTheme)
}
高级用法:缓存管理
// 智能缓存管理
export const CacheManager = {
  // 缓存用户数据
  cacheUserData: (userId: string, userData: any) => {
    setStorage(`user_${userId}`, userData, 24 * 60 * 60 * 1000)  // 24小时缓存
  },
  
  // 获取用户数据缓存
  getUserDataCache: (userId: string) => {
    return getStorage(`user_${userId}`)
  },
  
  // 清除所有用户相关缓存
  clearUserCache: (userId: string) => {
    const keys = ['userInfo', 'token', `user_${userId}`, 'userSettings']
    keys.forEach(key => removeStorage(key))
  },
  
  // 检查并清理过期缓存
  cleanExpiredCache: () => {
    const info = getStorageInfo()
    if (info && info.keys) {
      info.keys.forEach(key => {
        // 触发一次读取,自动清理过期数据
        getStorage(key)
      })
    }
  }
}

为什么要用这个工具?

  • 智能过期:自动清理过期数据,避免存储空间被垃圾数据占满
  • 容错处理:存储失败时不会让应用崩溃
  • 便捷调试:有日志输出,方便开发时查看存储情况
  • 统一接口:不用记住 uni 的各种存储 API,用我们的就够了

4. TypeScript 支持 - 代码的"智能助手"

这东西有啥用?

TypeScript 就像给 JavaScript 加了个"智能语法检查器",写代码时会提示你哪里写错了,什么类型不匹配,避免很多低级错误。

举个例子:

// 普通JavaScript - 容易出错
function getUserAge(user) {
  return user.age + 1  // 如果user是null会报错,如果age是字符串会出现'251'这种奇怪结果
}

// TypeScript - 智能提示和错误检查
function getUserAge(user: User | null): number {
  if (!user) return 0           // 编辑器会提示user可能为null
  return Number(user.age) + 1   // 编辑器会提示age的类型
}
我们为 uniapp 准备了完整的类型定义
// types/uni.d.ts - uniapp的"说明书"
declare global {
  const uni: UniNamespace.Uni
  
  namespace UniNamespace {
    interface Uni {
      // 消息提示
      showToast(options: ShowToastOptions): void
      showModal(options: ShowModalOptions): void
      showLoading(options?: ShowLoadingOptions): void
      hideLoading(): void
      
      // 页面跳转
      navigateTo(options: NavigateToOptions): void
      redirectTo(options: RedirectToOptions): void
      switchTab(options: SwitchTabOptions): void
      navigateBack(options?: NavigateBackOptions): void
      
      // 网络请求
      request(options: RequestOptions): RequestTask
      uploadFile(options: UploadFileOptions): UploadTask
      downloadFile(options: DownloadFileOptions): DownloadTask
      
      // 本地存储
      setStorageSync(key: string, data: any): void
      getStorageSync(key: string): any
      removeStorageSync(key: string): void
      clearStorageSync(): void
      
      // 设备信息
      getSystemInfo(options?: GetSystemInfoOptions): void
      getNetworkType(options?: GetNetworkTypeOptions): void
      
      // 更多 API...
    }
    
    // 详细的参数类型定义
    interface ShowToastOptions {
      title: string              // 提示文字,必填
      icon?: 'success' | 'error' | 'loading' | 'none'  // 图标类型
      duration?: number          // 显示时长,默认1500ms
      mask?: boolean            // 是否显示透明蒙层,防止触摸穿透
      success?: () => void      // 成功回调
      fail?: () => void         // 失败回调
      complete?: () => void     // 完成回调
    }
    
    interface RequestOptions {
      url: string               // 请求地址,必填
      data?: any               // 请求参数
      method?: 'GET' | 'POST' | 'PUT' | 'DELETE'  // 请求方法
      header?: Record<string, string>  // 请求头
      timeout?: number         // 超时时间
      success?: (res: RequestSuccessCallbackResult) => void
      fail?: (err: any) => void
      complete?: () => void
    }
    
    // 更多类型定义...
  }
}

export {}  // 使这个文件成为一个模块
实际开发中的好处
// 1. 智能提示 - 编辑器会自动提示可用的属性和方法
uni.showToast({
  title: '操作成功',
  icon: 'success',  // 编辑器会提示可选值:'success' | 'error' | 'loading' | 'none'
  duration: 2000
})

// 2. 错误检查 - 类型不匹配会立即报错
uni.showToast({
  title: '操作成功',
  icon: 'ok'  // ❌ 报错:'ok' 不是有效的图标类型
})

// 3. 函数参数检查
const navigateToPage = (url: string, params?: Record<string, any>) => {
  const query = params ? '?' + new URLSearchParams(params).toString() : ''
  uni.navigateTo({
    url: url + query,
    success: () => console.log('跳转成功'),
    fail: (err) => console.log('跳转失败', err)
  })
}

// 使用时有智能提示
navigateToPage('/pages/detail/detail', { 
  id: 123,           // ✅ 正确
  name: 'product'    // ✅ 正确
})

// 4. 接口数据类型定义
interface User {
  id: number
  name: string
  email: string
  avatar?: string    // 可选属性
  createdAt: Date
}

interface Product {
  id: number
  title: string
  price: number
  images: string[]
  category: {
    id: number
    name: string
  }
}

// API 返回数据类型定义
interface ApiResponse<T = any> {
  code: number
  message: string
  data: T
}

// 使用时有完整的类型提示
const getUserInfo = async (userId: number): Promise<User> => {
  const response: ApiResponse<User> = await request.get(`/users/${userId}`)
  return response.data  // 这里会有 User 类型的智能提示
}

// 5. 组件属性类型定义
interface ButtonProps {
  type?: 'primary' | 'secondary' | 'danger'
  size?: 'small' | 'medium' | 'large'
  disabled?: boolean
  loading?: boolean
  onClick?: () => void
}

// Vue组件中使用
const props = defineProps<ButtonProps>()
// 现在所有属性都有类型检查和智能提示
开发环境配置
// tsconfig.json - TypeScript 配置文件
{
  "compilerOptions": {
    "target": "es2020",
    "module": "es2020",
    "moduleResolution": "node",
    "strict": true,                    // 开启严格模式
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "forceConsistentCasingInFileNames": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],               // 路径别名
      "@/components/*": ["src/components/*"],
      "@/utils/*": ["src/utils/*"],
      "@/api/*": ["src/api/*"]
    },
    "types": [
      "@dcloudio/types",              // uniapp 官方类型
      "./src/types/uni.d.ts"         // 我们扩展的类型
    ]
  },
  "include": [
    "src/**/*",
    "*.vue",
    "*.ts"
  ],
  "exclude": [
    "node_modules",
    "dist",
    "unpackage"
  ]
}

TypeScript 的实际好处:

  • 写代码时就发现错误:不用等到运行时才知道哪里写错了
  • 智能提示超级棒:编辑器知道每个变量的类型,提示精准
  • 重构更安全:改个变量名,所有引用的地方都会自动更新
  • 团队协作更顺畅:代码即文档,看类型就知道怎么用

组件系统 - 可复用的"零件"

想象一下乐高积木,每个小零件都有特定的功能,可以组合成各种复杂的作品。我们的组件就是代码世界的"乐高积木"。

通用组件 - 最常用的"积木"

我们已经为你准备好了最常用的组件,拿来就能用!

1. 加载动画组件(LoadingSpinner)- 转圈圈等待器

什么时候用?

  • 登录时显示"登录中…"
  • 数据加载时显示"加载中…"
  • 文件上传时显示进度

怎么用?

<template>
  <view>
    <!-- 简单用法 -->
    <LoadingSpinner :visible="isLoading" />
    
    <!-- 完整用法 -->
    <LoadingSpinner 
      :visible="isLoading" 
      text="正在登录中..." 
      size="large" 
      :overlay="true" 
    />
  </view>
</template>

<script setup>
import LoadingSpinner from '@/components/common/LoadingSpinner.vue'

const isLoading = ref(false)

const handleLogin = async () => {
  isLoading.value = true  // 显示加载动画
  try {
    await api.login(username, password)
    console.log('登录成功')
  } finally {
    isLoading.value = false  // 隐藏加载动画
  }
}
</script>

参数说明:

  • visible: 是否显示(true/false)
  • text: 显示的文字,比如"加载中…"
  • size: 大小(‘small’ | ‘medium’ | ‘large’)
  • overlay: 是否显示遮罩层,防止用户乱点
2. 空状态组件(EmptyState)- "暂无数据"提示器

什么时候用?

  • 商品列表为空时
  • 搜索无结果时
  • 购物车为空时
  • 消息列表为空时

怎么用?

<template>
  <view>
    <!-- 基础用法 -->
    <EmptyState v-if="productList.length === 0" />
    
    <!-- 自定义用法 -->
    <EmptyState
      v-if="searchResults.length === 0"
      title="没有找到相关商品"
      description="试试其他关键词,或者浏览推荐商品"
      icon=""
      button-text="查看推荐"
      @button-click="showRecommended"
    />
    
    <!-- 购物车为空 -->
    <EmptyState
      v-if="cartItems.length === 0"
      title="购物车是空的"
      description="快去挑选心仪的商品吧"
      icon=""
      button-text="去购物"
      @button-click="goShopping"
    />
  </view>
</template>

<script setup>
import EmptyState from '@/components/common/EmptyState.vue'

const productList = ref([])
const searchResults = ref([])
const cartItems = ref([])

const showRecommended = () => {
  // 显示推荐商品
  uni.navigateTo({ url: '/pages/recommend/recommend' })
}

const goShopping = () => {
  // 跳转到商品页面
  uni.switchTab({ url: '/pages/products/products' })
}
</script>

参数说明:

  • title: 主标题,比如"暂无数据"
  • description: 描述文字(可选)
  • icon: 显示的图标(可选),可以用 emoji 或图片
  • button-text: 按钮文字(可选)
  • @button-click: 按钮点击事件(可选)
3. 自己写组件的规范

组件文件命名:

PascalCase.vue  // 每个单词首字母大写,比如 UserProfile.vue

组件基本结构:

<template>
  <view class="my-component">
    <!-- 组件内容 -->
  </view>
</template>

<script setup lang="ts">
// 1. 定义属性类型
interface Props {
  title: string           // 必填属性
  subtitle?: string       // 可选属性
  type?: 'primary' | 'secondary'  // 限定值
  onClick?: () => void    // 事件回调
}

// 2. 定义默认值
const props = withDefaults(defineProps<Props>(), {
  type: 'primary',
  subtitle: ''
})

// 3. 定义事件
const emit = defineEmits<{
  click: []              // 无参数事件
  change: [value: string] // 有参数事件
}>()

// 4. 组件逻辑
const handleClick = () => {
  emit('click')
  props.onClick?.()
}
</script>

<style lang="scss" scoped>
.my-component {
  // 组件样式
}
</style>

使用自定义组件:

<template>
  <MyComponent 
    title="标题" 
    :type="buttonType" 
    @click="handleComponentClick"
  />
</template>

<script setup>
import MyComponent from '@/components/MyComponent.vue'

const buttonType = ref('primary')

const handleComponentClick = () => {
  console.log('组件被点击了')
}
</script>

示例页面解析

首页(index/index.vue)- 应用门面

这个页面展示了框架的核心能力:

  • 系统信息获取:自动获取设备型号、屏幕尺寸等
  • 网络状态监控:实时显示网络类型(WiFi/4G/5G)
  • 状态管理应用:使用 Pinia 管理应用状态
  • 响应式设计:适配不同屏幕尺寸
用户中心(user/user.vue)- 用户管理

展示用户相关功能:

  • 用户信息展示:头像、昵称、个人资料
  • 设置选项:主题切换、通知设置等
  • 本地存储应用:设置数据持久化保存

快速样式系统

内置工具类 - 像拼积木一样写样式

<!-- 布局相关 -->
<view class="flex">横向排列</view>
<view class="flex-center">居中对齐</view>
<view class="flex-between">两端对齐</view>

<!-- 间距相关 -->
<view class="m-2 p-3">外边距16px,内边距24px</view>
<view class="mt-1 mb-2">上边距8px,下边距16px</view>

<!-- 文字相关 -->
<text class="text-center text-primary">居中的蓝色文字</text>
<text class="text-lg">大号文字</text>

<!-- 颜色相关 -->
<view class="text-success">成功绿色</view>
<view class="text-warning">警告橙色</view>
<view class="text-danger">危险红色</view>

数字规律:

  • 数字代表倍数:m-1 = 8px,m-2 = 16px,m-3 = 24px
  • 方向简写:mt(上) mr(右) mb(下) ml(左)
  • 内外边距:m(margin外边距) p(padding内边距)

10分钟上手指南

第一步:准备开发环境

  1. 下载 HBuilderX:去官网 https://www.dcloud.io/hbuilderx.html 下载
  2. 安装依赖:打开项目文件夹,运行 npm install
  3. 连接手机:用数据线连接手机,开启开发者模式

第二步:运行项目

# 在项目根目录运行
npm install           # 安装依赖

然后在 HBuilderX 中:

  • 点击运行运行到手机或模拟器运行到Android App基座

第三步:开始开发

添加新页面(5分钟搞定)
  1. 创建页面文件src/pages/demo/demo.vue
<template>
  <view class="demo-page">
    <text class="title">这是我的新页面</text>
    <button @click="handleClick">点击我</button>
  </view>
</template>

<script setup lang="ts">
import { showToast } from '@/utils/common'

const handleClick = () => {
  showToast('Hello World!')
}
</script>

<style lang="scss" scoped>
.demo-page {
  padding: 20px;
  text-align: center;
}

.title {
  font-size: 18px;
  margin-bottom: 20px;
}
</style>
  1. 注册页面路由:在 pages.json 中添加
{
  "pages": [
    // ... 其他页面
    {
      "path": "src/pages/demo/demo",
      "style": {
        "navigationBarTitleText": "演示页面"
      }
    }
  ]
}
  1. 跳转到新页面
<button @click="goToDemo">去新页面</button>

<script setup>
const goToDemo = () => {
  uni.navigateTo({ url: '/src/pages/demo/demo' })
}
</script>
调用接口获取数据(3分钟搞定)
<script setup lang="ts">
import { request } from '@/utils/request'

const userList = ref([])
const loading = ref(false)

const loadUsers = async () => {
  loading.value = true
  try {
    const data = await request.get('/api/users')
    userList.value = data
  } catch (error) {
    console.log('获取用户列表失败:', error)
  } finally {
    loading.value = false
  }
}

// 页面加载时获取数据
onMounted(() => {
  loadUsers()
})
</script>
保存数据到本地(1分钟搞定)
<script setup lang="ts">
import { setStorage, getStorage } from '@/utils/storage'

// 保存用户偏好
const savePreference = () => {
  setStorage('userTheme', 'dark')  // 永久保存
  setStorage('tempData', { id: 1 }, 60 * 60 * 1000)  // 1小时后过期
}

// 读取用户偏好
const loadPreference = () => {
  const theme = getStorage('userTheme')
  if (theme) {
    console.log('用户喜欢的主题:', theme)
  }
}
</script>

第四步:打包发布

  1. 生成正式包:在 HBuilderX 中点击发行原生App-云打包
  2. 填写应用信息:应用名称、版本号、图标等
  3. 等待打包:几分钟后就能下载 APK 文件

常用开发技巧

调试技巧

// 在真机上查看日志
console.log('调试信息:', data)

// 显示调试信息
uni.showModal({
  title: '调试',
  content: JSON.stringify(data, null, 2)
})

样式适配技巧

<style lang="scss" scoped>
/* 使用 rpx 单位自动适配屏幕 */
.container {
  width: 750rpx;     /* 等于屏幕宽度 */
  height: 200rpx;    /* 在任何设备上都是合适的高度 */
}

/* 安全区域适配 */
.bottom-bar {
  padding-bottom: env(safe-area-inset-bottom);  /* 适配刘海屏 */
}
</style>

性能优化技巧

<template>
  <!-- 长列表优化 -->
  <scroll-view scroll-y class="list">
    <view v-for="item in visibleItems" :key="item.id">
      {{ item.name }}
    </view>
  </scroll-view>
</template>

<script setup>
// 只渲染可见的列表项
const visibleItems = computed(() => {
  return allItems.value.slice(0, 50)  // 只显示前50项
})
</script>

写在最后

这个框架就像一个"开发工具箱",里面有你需要的各种工具:

  • 状态管理:管理应用数据
  • 网络请求:与服务器通信
  • 本地存储:保存用户数据
  • 通用组件:快速搭建界面
  • TypeScript:减少代码错误

一句话总结: 学会Vue,就能开发多平台应用!

有问题?看代码注释,或者直接在现有功能基础上修改,这是学习最快的方式。祝你开发愉快!


项目地址

完整源码地址: https://gitcode.com/codeCao/uniapp-multi-platform.git

欢迎 Star ⭐ 和 Fork ,一起完善这个多平台开发框架!

如果这个项目对你有帮助,别忘了给个 Star 支持一下~

你可能感兴趣的:(uniapp,uni-app,移动端,跨平台,harmonyOS)