uni-app跨平台开发

在数字化浪潮席卷的当下,多端适配的需求愈发迫切。传统的原生开发方式,针对 iOS、Android、Web 等不同平台分别开发,不仅耗时耗力,还需要维护多套代码,成本极高。而 uni-app 作为一款强大的跨平台开发框架,凭借 “一套代码,多端发布” 的特性,成为开发者的福音。本文将在基础开发流程之上,进一步深入挖掘 uni-app 的开发技巧,带领大家打造一个功能更丰富、性能更优化的跨平台博客应用。​

一、项目搭建与基础配置的深度拓展​

1.1 环境优化与版本管理​

在安装 Node.js 时,建议使用 nvm(Node Version Manager)进行版本管理,它可以方便地在不同 Node.js 版本间切换,避免因版本差异导致的兼容性问题。例如,在 Linux 或 macOS 系统中,通过以下命令安装 nvm:​

TypeScript

取消自动换行复制

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash​

安装完成后,使用nvm install <version>安装指定版本的 Node.js,并通过nvm use <version>切换版本。​

对于 HBuilderX,定期更新到最新版本,以获取新功能和性能优化。同时,在项目中使用 Git 进行版本控制,创建清晰的分支策略,如主分支用于稳定版本,开发分支用于日常开发,方便团队协作和代码回滚。​

1.2 项目目录结构优化​

除了基础的目录结构,还可以进一步细分目录,提高项目的可维护性。例如,创建store目录用于存放 Vuex 相关代码,管理应用的全局状态;utils目录下再细分request、filter等子目录,分别存放数据请求相关代码和数据过滤函数;styles目录用于管理全局样式和公共样式文件。优化后的目录结构如下:​

TypeScript

取消自动换行复制

blog-app​

├─ components // 组件目录​

├─ pages // 页面目录​

├─ static // 静态资源目录​

├─ uni_modules // uni_modules插件目录​

├─ store // Vuex状态管理目录​

├─ utils ​

│ ├─ request // 数据请求目录​

│ └─ filter // 数据过滤目录​

├─ styles // 样式目录​

├─ main.js // 入口文件​

├─ App.vue // 应用根组件​

└─ manifest.json // 应用配置文件​

└─ pages.json // 页面路由配置文件​

1.3 高级路由配置​

在pages.json中,除了基础的页面路由配置,还可以配置路由守卫,实现权限控制等功能。例如,在进入文章编辑页时,检查用户是否登录:​

TypeScript

取消自动换行复制

{​

"pages": [​

// 其他页面路由配置​

{​

"path": "pages/article/edit",​

"style": {​

"navigationBarTitleText": "编辑文章"​

},​

"meta": {​

"requireLogin": true​

}​

}​

]​

}​

在main.js中设置路由守卫:​

TypeScript

取消自动换行复制

import Vue from 'vue';​

import App from './App';​

import router from './router'; // 假设已将路由配置提取到单独文件​

router.beforeEach((to, from, next) => {​

if (to.matched.some(record => record.meta.requireLogin)) {​

const isLoggedIn = uni.getStorageSync('isLoggedIn'); // 假设登录状态存储在本地缓存​

if (!isLoggedIn) {​

next({​

path: '/pages/login/login',​

query: { redirect: to.fullPath }​

});​

} else {​

next();​

}​

} else {​

next();​

}​

});​

new Vue({​

router,​

render: h => h(App)​

}).$mount('#app');​

二、数据请求与展示的进阶应用​

2.1 数据请求拦截与响应处理​

在utils/request.js中,添加请求拦截器和响应拦截器,统一处理请求头、错误提示等。例如,在请求头中添加 token:​

TypeScript

取消自动换行复制

// utils/request.js​

import uni from '@dcloudio/uni-app';​

export function request(url, data = {}, method = 'GET') {​

return new Promise((resolve, reject) => {​

const token = uni.getStorageSync('token');​

uni.request({​

url: url,​

data: data,​

method: method,​

header: {​

'Content-Type': 'application/json',​

'token': token​

},​

success: (res) => {​

if (res.statusCode === 200) {​

resolve(res.data);​

} else {​

uni.showToast({​

title: '请求失败',​

icon: 'none'​

});​

reject(res);​

}​

},​

fail: (err) => {​

uni.showToast({​

title: '网络错误',​

icon: 'none'​

});​

reject(err);​

}​

});​

});​

}​

响应拦截器用于处理统一的错误码,如 401 表示未登录,自动跳转到登录页:​

TypeScript

取消自动换行复制

// 在main.js中添加响应拦截器​

import { request } from './utils/request.js';​

request.interceptors.response.use(​

response => {​

return response;​

},​

error => {​

if (error.statusCode === 401) {​

uni.navigateTo({​

url: '/pages/login/login'​

});​

}​

return Promise.reject(error);​

}​

);​

2.2 复杂数据展示与交互​

在文章列表页,实现下拉刷新和上拉加载更多功能。在pages/home/home.vue中:​

TypeScript

取消自动换行复制

<template>​

<view class="home">​

<scroll-view​

:scroll-y="true"​

@scrolltolower="loadMore"​

:style="{ height: windowHeight + 'px' }"​

>​

<article-card v-for="article in articleList" :key="article.id" :article="article" @click="goToArticleDetail"></article-card>​

<view v-if="loading" class="loading">加载中...</view>​

</scroll-view>​

<view v-if="!hasMore" class="no-more">没有更多内容了</view>​

</view>​

</template>​

<script>​

import articleCard from '@/components/article-card/article-card.vue';​

import { request } from '@/utils/request.js';​

export default {​

components: {​

articleCard​

},​

data() {​

return {​

articleList: [],​

page: 1,​

pageSize: 10,​

loading: false,​

hasMore: true,​

windowHeight: 0​

};​

},​

onLoad() {​

this.getWindowHeight();​

this.getArticleList();​

},​

methods: {​

async getWindowHeight() {​

const res = await uni.getSystemInfo();​

this.windowHeight = res.windowHeight;​

},​

async getArticleList() {​

this.loading = true;​

try {​

const res = await request(`/api/articles?page=${this.page}&pageSize=${this.pageSize}`);​

if (res.data.length < this.pageSize) {​

this.hasMore = false;​

}​

this.articleList = this.articleList.concat(res.data);​

} catch (error) {​

console.error('获取文章列表失败:', error);​

} finally {​

this.loading = false;​

}​

},​

loadMore() {​

if (!this.loading && this.hasMore) {​

this.page++;​

this.getArticleList();​

}​

},​

goToArticleDetail(id) {​

uni.navigateTo({​

url: `/pages/article/article?id=${id}`​

});​

}​

}​

};​

</script>​

在文章详情页,实现点赞、收藏功能。通过调用后端接口更新数据,并实时展示状态:​

TypeScript

取消自动换行复制

<template>​

<view class="article">​

<text class="article-title">{{ article.title }}</text>​

<text class="article-author">作者:{{ article.author }}</text>​

<view class="article-content" v-html="article.content"></view>​

<view class="action-buttons">​

<view class="action-button" @click="likeArticle">​

<text :class="{ 'liked': article.isLiked }">{{ article.isLiked? '已点赞' : '点赞' }}</text>​

<text class="like-count">{{ article.likeCount }}</text>​

</view>​

<view class="action-button" @click="collectArticle">​

<text :class="{ 'collected': article.isCollected }">{{ article.isCollected? '已收藏' : '收藏' }}</text>​

</view>​

</view>​

</view>​

</template>​

<script>​

import { request } from '@/utils/request.js';​

export default {​

data() {​

return {​

article: {}​

};​

},​

onLoad(options) {​

const id = options.id;​

this.getArticleDetail(id);​

},​

methods: {​

async getArticleDetail(id) {​

try {​

const res = await request(`/api/articles/${id}`);​

this.article = res.data;​

} catch (error) {​

console.error('获取文章详情失败:', error);​

}​

},​

async likeArticle() {​

if (this.article.isLiked) {​

return;​

}​

try {​

const res = await request(`/api/articles/${this.article.id}/like`, {}, 'POST');​

this.article.isLiked = true;​

this.article.likeCount = res.data.likeCount;​

} catch (error) {​

console.error('点赞失败:', error);​

}​

},​

async collectArticle() {​

if (this.article.isCollected) {​

return;​

}​

try {​

const res = await request(`/api/articles/${this.article.id}/collect`, {}, 'POST');​

this.article.isCollected = true;​

} catch (error) {​

console.error('收藏失败:', error);​

}​

}​

}​

};​

</script>​

三、组件化开发的深入实践​

3.1 复杂组件的封装与复用​

创建一个评论组件(components/comment/comment.vue),支持多级评论展示和评论发布功能。组件接收文章 ID 作为参数,通过数据请求获取评论列表,并实现评论输入和提交:​

TypeScript

取消自动换行复制

<template>​

<view class="comment-section">​

<view v-for="(comment, index) in commentList" :key="index" class="comment-item">​

<text class="comment-author">{{ comment.nickname }}</text>​

<text class="comment-time">{{ comment.createTime }}</text>​

<view class="comment-content">{{ comment.content }}</view>​

<view v-if="comment.replies && comment.replies.length > 0" class="reply-section">​

<view v-for="(reply, replyIndex) in comment.replies" :key="replyIndex" class="reply-item">​

<text class="reply-author">{{ reply.nickname }}</text>​

<text class="reply-time">{{ reply.createTime }}</text>​

<view class="reply-content">{{ reply.content }}</view>​

</view>​

</view>​

<view class="reply-input" v-if="!replyToComment">​

<input v-model="replyContent" placeholder="回复评论"></input>​

<button @click="replyComment(comment.id)">回复</button>​

</view>​

<view v-else class="reply-input">​

<input v-model="replyContent" placeholder="回复 {{ replyToComment.nickname }}"></input>​

<button @click="submitReply(replyToComment.id)">发送</button>​

</view>​

</view>​

<view class="comment-input">​

<input v-model="newComment.content" placeholder="发表评论"></input>​

<button @click="submitComment">提交</button>​

</view>​

</view>​

</template>​

<script>​

import { request } from '@/utils/request.js';​

export default {​

props: {​

articleId: {​

type: String,​

required: true​

}​

},​

data() {​

return {​

commentList: [],​

newComment: {​

content: '',​

parentId: null​

},​

replyContent: '',​

replyToComment: null​

};​

},​

onLoad() {​

this.getCommentList();​

},​

methods: {​

async getCommentList() {​

try {​

const res = await request(`/api/articles/${this.articleId}/comments`);​

this.commentList = res.data;​

} catch (error) {​

console.error('获取评论列表失败:', error);​

}​

},​

replyComment(commentId) {​

this.replyToComment = this.commentList.find(c => c.id === commentId);​

},​

async submitReply(parentId) {​

if (!this.replyContent) {​

return;​

}​

try {​

const res = await request(`/api/articles/${this.articleId}/comments/reply`, {​

content: this.replyContent,​

parentId: parentId​

}, 'POST');​

this.getCommentList();​

this.replyContent = '';​

this.replyToComment = null;​

} catch (error) {​

console.error('回复评论失败:', error);​

}​

},​

async submitComment() {​

if (!this.newComment.content) {​

return;​

}​

try {​

const res = await request(`/api/articles/${this.articleId}/comments`, this.newComment, 'POST');​

this.getCommentList();​

this.newComment.content = '';​

} catch (error) {​

console.error('提交评论失败:', error);​

}​

}​

}​

};​

</script>​

<style>​

.comment-section {​

padding: 15px;​

}​

.comment-item {​

margin-bottom: 15px;​

padding-bottom: 15px;​

border-bottom: 1px solid #ccc;​

}​

.reply-section {​

margin-left: 20px;​

}​

.reply-item {​

margin-bottom: 5px;​

}​

.comment-input,​

.reply-input {​

margin-top: 15px;​

}​

</style>​

在文章详情页引入评论组件:​

TypeScript

取消自动换行复制

<template>​

<view class="article">​

<!-- 文章标题、作者、内容展示 -->​

<comment :articleId="article.id"></comment>​

</view>​

</template>​

<script>​

import comment from '@/components/comment/comment.vue';​

export default {​

components: {​

comment​

},​

// 其他代码不变​

};​

</script>​

3.2 组件通信与状态管理​

当组件之间存在复杂的通信需求时,使用 Vuex 进行状态管理。例如,在用户登录成功后,更新全局的用户登录状态,并在多个组件中使用。在store/index.js中定义用户状态:​

TypeScript

取消自动换行复制

import Vue from 'vue';​

import Vuex from 'vuex';​

Vue.use(Vuex);​

export default new Vuex.Store({​

state: {​

user: null,​

isLoggedIn: false​

},​

mutations: {​

SET_USER(state, user) {​

state.user = user;​

state.isLoggedIn = true;​

},​

LOGOUT(state) {​

state.user = null;​

state.isLoggedIn = false;​

}​

},​

actions: {​

async login({ commit }, userData) {​

try {​

const res = await request('/api/login', userData, 'POST');​

commit('SET_USER', res.data);​

uni.setStorageSync('token', res.data.token);​

uni.setStorageSync('user', res.data);​

} catch (error) {​

console.error('登录失败:', error);​

}​

},​

logout({ commit }) {​

commit('LOGOUT');​

uni.removeStorageSync('token');​

uni.removeStorageSync('user');​

}​

},​

getters: {​

getUser: state => state.user,​

getIsLoggedIn: state => state.isLoggedIn​

}​

});​

在登录页面调用login方法:​

TypeScript

取消自动换行复制

<template>​

<view class="login">​

<input v-model="userData.username" placeholder="用户名"></input>​

<input v-model="userData.password" placeholder="密码" type="password"></input>​

<button @click="handleLogin">登录</button>​

</view>​

</template>​

<script>​

import { request</doubaocanvas>​

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值