当前位置: 首页 > news >正文

uniapp学习【整体实践】

项目目录结构详解

my-project/
├── pages/                 # 页面文件目录
│   ├── index/
│   │   ├── index.vue     # 页面组件
│   │   └── index.json    # 页面配置文件
├── static/               # 静态资源目录
├── components/           # 自定义组件目录
├── uni_modules/          # uni模块目录
├── App.vue              # 应用入口文件
├── main.js              # 主入口文件
├── manifest.json        # 应用配置文件
├── pages.json           # 页面路由与样式配置
└── uni.scss             # 全局样式文件

配置文件详解

manifest.json 应用配置

{
"name": "我的应用",
"appid": "__UNI__XXXXXX",
"description": "应用描述",
"versionName": "1.0.0",
"versionCode": "100",
"mp-weixin": {
"appid": "wx1234567890", // 微信小程序appid
"setting": {
"urlCheck": false,
"es6": true,
"enhance": true
},
"usingComponents": true
},
"vueVersion": "3"
}

pages.json 页面配置
{"pages": [{"path": "pages/index/index","style": {"navigationBarTitleText": "首页","enablePullDownRefresh": true,"navigationStyle": "default"}}],"globalStyle": {"navigationBarTextStyle": "black","navigationBarTitleText": "uni-app","navigationBarBackgroundColor": "#F8F8F8","backgroundColor": "#F8F8F8"},"tabBar": {"color": "#7A7E83","selectedColor": "#3cc51f","borderStyle": "black","backgroundColor": "#ffffff","list": [{"pagePath": "pages/index/index","iconPath": "static/tabbar/home.png","selectedIconPath": "static/tabbar/home-active.png","text": "首页"}]}
}

Vue3基础与uniapp结合

Vue3组合式API基础

<template><view class="container"><text>{{ count }}</text><button @click="increment">增加</button><text>{{ doubleCount }}</text></view>
</template><script setup>
import { ref, computed, onMounted } from 'vue'// 响应式数据
const count = ref(0)// 计算属性
const doubleCount = computed(() => count.value * 2)// 方法
const increment = () => {count.value++
}// 生命周期
onMounted(() => {console.log('组件挂载完成')
})
</script><style scoped>
.container {padding: 20rpx;
}
</style>

Uniapp生命周期

<script setup>
import { onLoad, onShow, onReady, onHide, onUnload } from '@dcloudio/uni-app'// 页面加载时触发
onLoad((options) => {console.log('页面加载', options)
})// 页面显示时触发
onShow(() => {console.log('页面显示')
})// 页面初次渲染完成
onReady(() => {console.log('页面就绪')
})// 页面隐藏时触发
onHide(() => {console.log('页面隐藏')
})// 页面卸载时触发
onUnload(() => {console.log('页面卸载')
})
</script>

条件编译

<template><view><!-- #ifdef MP-WEIXIN --><view>仅在小程序中显示</view><!-- #endif --><!-- #ifdef H5 --><view>仅在H5中显示</view><!-- #endif --><!-- #ifdef APP-PLUS --><view>仅在App中显示</view><!-- #endif --></view>
</template><script setup>
// 条件编译JS代码
// #ifdef MP-WEIXIN
const weixinOnly = '微信小程序特有'
// #endif// #ifdef H5
const h5Only = 'H5特有'
// #endif
</script><style>
/* 条件编译样式 */
/* #ifdef MP-WEIXIN */
.weixin-style {color: red;
}
/* #endif */
</style>

UI组件库详解

常用UI组件库介绍

uView UI(推荐)
# 安装uView
npm install uview-ui# 或通过uni_modules安装
在uni-app插件市场搜索uView,导入到项目中
配置uView
// main.js
import uView from 'uview-ui'
import { createSSRApp } from 'vue'export function createApp() {const app = createSSRApp(App)app.use(uView)return {app}
}
// uni.scss
@import 'uview-ui/theme.scss';
// pages.json
{"easycom": {"^u-(.*)": "uview-ui/components/u-$1/u-$1.vue"}
}

基础组件使用

<template><view class="container"><!-- 布局组件 --><u-grid :col="3"><u-grid-item v-for="item in 6" :key="item"><u-icon name="photo" :size="46"></u-icon><text class="grid-text">网格{{item}}</text></u-grid-item></u-grid><!-- 表单组件 --><u-form :model="form" :rules="rules" ref="uForm"><u-form-item label="姓名" prop="name"><u-input v-model="form.name" placeholder="请输入姓名" /></u-form-item><u-form-item label="年龄" prop="age"><u-input v-model="form.age" type="number" placeholder="请输入年龄" /></u-form-item></u-form><!-- 按钮组件 --><u-button type="primary" @click="submit">提交</u-button><!-- 反馈组件 --><u-toast ref="uToast"></u-toast></view>
</template><script setup>
import { ref, reactive } from 'vue'const form = reactive({name: '',age: ''
})const rules = {name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],age: [{ required: true, message: '请输入年龄', trigger: 'blur' },{ type: 'number', message: '年龄必须为数字', trigger: 'blur' }]
}const submit = () => {// 表单验证uni.showToast({title: '提交成功',icon: 'success'})
}
</script>

自定义组件开发

<!-- components/my-button/my-button.vue -->
<template><button class="my-button" :class="[type, size, { disabled: disabled }]":disabled="disabled"@click="handleClick"><slot></slot></button>
</template><script setup>
import { computed } from 'vue'const props = defineProps({type: {type: String,default: 'default',validator: (value) => {return ['default', 'primary', 'success', 'warning', 'danger'].includes(value)}},size: {type: String,default: 'normal',validator: (value) => {return ['small', 'normal', 'large'].includes(value)}},disabled: {type: Boolean,default: false}
})const emit = defineEmits(['click'])const handleClick = (event) => {if (!props.disabled) {emit('click', event)}
}
</script><style scoped>
.my-button {padding: 20rpx 40rpx;border: none;border-radius: 10rpx;font-size: 32rpx;transition: all 0.3s;
}.my-button.primary {background-color: #2979ff;color: white;
}.my-button.small {padding: 15rpx 30rpx;font-size: 28rpx;
}.my-button.large {padding: 25rpx 50rpx;font-size: 36rpx;
}.my-button.disabled {opacity: 0.6;pointer-events: none;
}
</style>

路由与导航

页面跳转

<script setup>
// 保留当前页面,跳转到应用内的某个页面
const navigateTo = () => {uni.navigateTo({url: '/pages/detail/detail?id=1&name=test',success: () => console.log('跳转成功'),fail: (err) => console.log('跳转失败', err)})
}// 关闭当前页面,跳转到应用内的某个页面
const redirectTo = () => {uni.redirectTo({url: '/pages/detail/detail'})
}// 跳转到 tabBar 页面
const switchTab = () => {uni.switchTab({url: '/pages/home/home'})
}// 关闭所有页面,打开到应用内的某个页面
const reLaunch = () => {uni.reLaunch({url: '/pages/index/index'})
}// 返回上一页面
const navigateBack = () => {uni.navigateBack({delta: 1 // 返回层数})
}
</script>

页面传参与接收

<!-- pages/detail/detail.vue -->
<template><view><text>ID: {{ id }}</text><text>名称: {{ name }}</text></view>
</template><script setup>
import { ref, onLoad } from '@dcloudio/uni-app'const id = ref('')
const name = ref('')onLoad((options) => {id.value = options.id || ''name.value = options.name || ''
})
</script>

导航栏自定义

// pages.json
{"pages": [{"path": "pages/index/index","style": {"navigationBarTitleText": "首页","navigationStyle": "custom", // 自定义导航栏"enablePullDownRefresh": true,"onReachBottomDistance": 50}}]
}
<!-- 自定义导航栏组件 -->
<template><view class="custom-navbar" :style="{ height: navbarHeight + 'px' }"><view class="navbar-content" :style="{ height: statusBarHeight + 'px', paddingTop: statusBarHeight + 'px' }"><view class="navbar-title">{{ title }}</view><view class="navbar-actions"><slot name="right"></slot></view></view></view>
</template><script setup>
import { ref, onMounted } from 'vue'defineProps({title: {type: String,default: ''}
})const navbarHeight = ref(0)
const statusBarHeight = ref(0)onMounted(() => {// 获取系统信息const systemInfo = uni.getSystemInfoSync()statusBarHeight.value = systemInfo.statusBarHeight || 0// 小程序导航栏高度// #ifdef MP-WEIXINconst menuButtonInfo = uni.getMenuButtonBoundingClientRect()navbarHeight.value = menuButtonInfo.bottom + menuButtonInfo.top - systemInfo.statusBarHeight// #endif// #ifdef H5 || APP-PLUSnavbarHeight.value = 44 + statusBarHeight.value// #endif
})
</script>

状态管理

Pinia状态管理(推荐)

# 安装Pinia
npm install pinia @pinia/nuxt
// stores/counter.js
import { defineStore } from 'pinia'export const useCounterStore = defineStore('counter', {state: () => ({count: 0,name: '计数器'}),getters: {doubleCount: (state) => state.count * 2,doubleCountPlusOne() {return this.doubleCount + 1}},actions: {increment() {this.count++},decrement() {this.count--},async incrementAsync() {await new Promise(resolve => setTimeout(resolve, 1000))this.increment()}}
})
// main.js
import { createSSRApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'export function createApp() {const app = createSSRApp(App)const pinia = createPinia()app.use(pinia)return {app}
}

 在组件中使用状态

<template><view class="container"><text>计数: {{ count }}</text><text>双倍计数: {{ doubleCount }}</text><button @click="increment">增加</button><button @click="incrementAsync">异步增加</button></view>
</template><script setup>
import { useCounterStore } from '@/stores/counter'
import { storeToRefs } from 'pinia'const counterStore = useCounterStore()// 使用storeToRefs保持响应式
const { count, doubleCount } = storeToRefs(counterStore)// 直接解构action
const { increment, incrementAsync } = counterStore
</script>

网络请求

请求封装

// utils/request.js
class Request {constructor() {this.baseURL = 'https://api.example.com'this.timeout = 10000}request(config) {return new Promise((resolve, reject) => {uni.request({url: this.baseURL + config.url,method: config.method || 'GET',data: config.data || {},header: {'Content-Type': 'application/json','Authorization': uni.getStorageSync('token') || '',...config.header},timeout: this.timeout,success: (res) => {if (res.statusCode === 200) {resolve(res.data)} else {reject(this.handleError(res))}},fail: (err) => {reject(this.handleError(err))}})})}handleError(error) {let message = '网络请求失败'if (error.statusCode) {switch (error.statusCode) {case 401:message = '未授权,请重新登录'// 跳转到登录页uni.navigateTo({ url: '/pages/login/login' })breakcase 404:message = '请求地址不存在'breakcase 500:message = '服务器内部错误'breakdefault:message = error.data?.message || `请求失败:${error.statusCode}`}}uni.showToast({title: message,icon: 'none'})return message}get(url, data = {}) {return this.request({ url, method: 'GET', data })}post(url, data = {}) {return this.request({ url, method: 'POST', data })}put(url, data = {}) {return this.request({ url, method: 'PUT', data })}delete(url, data = {}) {return this.request({ url, method: 'DELETE', data })}
}export default new Request()

API接口管理

// api/user.js
import request from '@/utils/request'export const userApi = {// 用户登录login(data) {return request.post('/user/login', data)},// 获取用户信息getUserInfo() {return request.get('/user/info')},// 更新用户信息updateUserInfo(data) {return request.put('/user/info', data)},// 上传头像uploadAvatar(filePath) {return new Promise((resolve, reject) => {uni.uploadFile({url: request.baseURL + '/user/avatar',filePath: filePath,name: 'file',header: {'Authorization': uni.getStorageSync('token')},success: (res) => {const data = JSON.parse(res.data)resolve(data)},fail: reject})})}
}

在组件中使用

<script setup>
import { ref, onMounted } from 'vue'
import { userApi } from '@/api/user'const userInfo = ref({})
const loading = ref(false)const getUserInfo = async () => {loading.value = truetry {const res = await userApi.getUserInfo()userInfo.value = res.data} catch (error) {console.error('获取用户信息失败', error)} finally {loading.value = false}
}onMounted(() => {getUserInfo()
})
</script>

数据缓存

缓存工具类

// utils/storage.js
class Storage {// 同步设置缓存setSync(key, value) {try {uni.setStorageSync(key, value)return true} catch (e) {console.error('设置缓存失败', e)return false}}// 同步获取缓存getSync(key, defaultValue = null) {try {const value = uni.getStorageSync(key)return value || defaultValue} catch (e) {console.error('获取缓存失败', e)return defaultValue}}// 同步移除缓存removeSync(key) {try {uni.removeStorageSync(key)return true} catch (e) {console.error('移除缓存失败', e)return false}}// 清空缓存clearSync() {try {uni.clearStorageSync()return true} catch (e) {console.error('清空缓存失败', e)return false}}// 异步设置缓存set(key, value) {return new Promise((resolve, reject) => {uni.setStorage({key,data: value,success: resolve,fail: reject})})}// 异步获取缓存get(key, defaultValue = null) {return new Promise((resolve) => {uni.getStorage({key,success: (res) => resolve(res.data),fail: () => resolve(defaultValue)})})}
}export default new Storage()

使用示例

<script setup>
import storage from '@/utils/storage'
import { ref, onMounted } from 'vue'const userToken = ref('')// 设置token
const setToken = () => {storage.setSync('token', 'your-token-here')
}// 获取token
const getToken = () => {userToken.value = storage.getSync('token', '')
}// 移除token
const removeToken = () => {storage.removeSync('token')
}onMounted(() => {getToken()
})
</script>

设备API与平台兼容

常用设备API

<script setup>
// 获取系统信息
const getSystemInfo = () => {const systemInfo = uni.getSystemInfoSync()console.log('系统信息:', systemInfo)
}// 网络状态
const getNetworkType = () => {uni.getNetworkType({success: (res) => {console.log('网络类型:', res.networkType)}})
}// 地理位置
const getLocation = () => {uni.getLocation({type: 'wgs84',success: (res) => {console.log('位置:', res.latitude, res.longitude)},fail: (err) => {console.error('获取位置失败', err)}})
}// 扫码
const scanCode = () => {uni.scanCode({success: (res) => {console.log('扫码结果:', res.result)}})
}// 图片选择
const chooseImage = () => {uni.chooseImage({count: 1,sizeType: ['compressed'],sourceType: ['album', 'camera'],success: (res) => {console.log('图片路径:', res.tempFilePaths[0])}})
}// 显示操作菜单
const showActionSheet = () => {uni.showActionSheet({itemList: ['选项1', '选项2', '选项3'],success: (res) => {console.log('选中:', res.tapIndex)}})
}
</script>

平台兼容处理

// utils/platform.js
export const isWeapp = () => {// #ifdef MP-WEIXINreturn true// #endifreturn false
}export const isH5 = () => {// #ifdef H5return true// #endifreturn false
}export const isApp = () => {// #ifdef APP-PLUSreturn true// #endifreturn false
}export const getPlatform = () => {// #ifdef MP-WEIXINreturn 'weapp'// #endif// #ifdef H5return 'h5'// #endif// #ifdef APP-PLUSreturn 'app'// #endifreturn 'unknown'
}

性能优化

图片优化

<template><view><!-- 使用webp格式(小程序支持) --><image :src="imageUrl" mode="aspectFill"lazy-load@load="onImageLoad"@error="onImageError"></image><!-- 图片预加载 --><image v-for="img in preloadImages" :key="img" :src="img" style="display: none;" /></view>
</template><script setup>
import { ref } from 'vue'const imageUrl = ref('')
const preloadImages = ref([])// 压缩图片
const compressImage = (src) => {return new Promise((resolve, reject) => {// #ifdef MP-WEIXINwx.compressImage({src,quality: 80,success: (res) => resolve(res.tempFilePath),fail: reject})// #endif// #ifdef H5 || APP-PLUSresolve(src) // H5和App需要自己实现压缩// #endif})
}const onImageLoad = (e) => {console.log('图片加载完成')
}const onImageError = (e) => {console.error('图片加载失败', e)
}
</script>

数据懒加载

<template><view><view v-for="item in visibleData" :key="item.id"class="list-item">{{ item.name }}</view><!-- 加载更多 --><view v-if="hasMore" class="load-more" @click="loadMore">{{ loading ? '加载中...' : '加载更多' }}</view></view>
</template><script setup>
import { ref, onMounted, onReachBottom } from '@dcloudio/uni-app'const allData = ref([])
const visibleData = ref([])
const page = ref(1)
const pageSize = 10
const hasMore = ref(true)
const loading = ref(false)const loadData = async () => {if (loading.value || !hasMore.value) returnloading.value = truetry {// 模拟API调用const newData = await mockApi(page.value, pageSize)if (newData.length < pageSize) {hasMore.value = false}allData.value = [...allData.value, ...newData]visibleData.value = allData.value.slice(0, page.value * pageSize)page.value++} catch (error) {console.error('加载数据失败', error)} finally {loading.value = false}
}// 上拉加载更多
onReachBottom(() => {loadData()
})onMounted(() => {loadData()
})
</script>

打包发布

小程序发布流程

// manifest.json 配置
{"mp-weixin": {"appid": "你的小程序appid","setting": {"urlCheck": false,"es6": true,"enhance": true,"postcss": true},"usingComponents": true,"permission": {"scope.userLocation": {"desc": "你的位置信息将用于小程序位置接口的效果展示"}}}
}

发行步骤

开发环境测试

# 运行到微信开发者工具
npm run dev:mp-weixin

生产环境构建

# 构建小程序
npm run build:mp-weixin

上传代码

  • 在微信开发者工具中点击"上传"

  • 填写版本号和项目备注

提交审核

  • 登录微信公众平台

  • 在管理后台提交审核

发布上线

  • 审核通过后,点击发布

http://www.dtcms.com/a/478746.html

相关文章:

  • Rabbitmq如何避免消息丢失
  • 建设一个朋友的网站工商局注册公司网站
  • wap网站建设免费关于网站建设费用的报告
  • asp网站开发实训报告亚马逊开店需要什么条件
  • cms管理手机网站制作网站的页面设计怎么做
  • 湖北工程公司建设公司网站腾讯云服务器免费体验
  • 面试问题—你接受加班吗?
  • 使用Asp.Net WebApi(.net 8)托管Unity WebGL
  • 用凡科做网站需要花钱吗localhostwordpress打不开
  • 15 【C++11 新特性】统一的列表初始化和变量类型推导
  • 合肥制作网站单位有哪些免费网站
  • 【密码学实战】openHiTLS client命令行:国密TLCP/DTLCP客户端工具
  • 鸿蒙:将项目的rawfile目录下全部文件拷贝到app沙箱目录(第二种方案)
  • PWM输出频率计
  • RuntimeBroker.exe应用程序错误?3种专业修复方法
  • PHP网站开发有哪些框架互联网推广品牌
  • [Power BI] VALUES函数
  • Idea 启动报 未找到有效的 Maven 安装问题
  • 智慧公厕管理系统全流程智能化
  • Pinia 状态管理:从入门到精通
  • 江苏城乡住房建设厅网站做公众号微网站
  • iBizModel 系统数据同步代理(PSSYSDATASYNCAGENT)与实体数据同步(PSDEDATASYNC)模型详解
  • 深度相机结构光vs.激光雷达
  • 我的全栈学习之旅:Colcon, CMake, Ninja/Make,编译器之间的关系
  • 【数值分析】解线性方程组的迭代法经典算法
  • 东莞网站空间wordpress迁移hexo
  • 小白如何建网站wordpress gstatic
  • 关于JMM
  • 嵌入式学习笔记- 单片机的低功耗以及唤醒
  • Dify从入门到精通 第12天 RAG知识库概念引入:深入理解知识库、分词与向量化