移动端应用
2026/3/20大约 4 分钟
移动端应用技术详解
技术栈
| 技术 | 版本 | 用途 |
|---|---|---|
| Vue | 3.4.21 | 前端框架 |
| Vite | 5.2.8 | 构建工具 |
| Vant | 4.8.11 | 移动端 UI 组件库 |
| Pinia | 2.1.7 | 状态管理 |
| Vue Router | 4.3.0 | 路由管理 |
| Vue I18n | 9.13.1 | 国际化 |
项目结构
salted-fish-mobile/
├── src/
│ ├── api/ # API 接口
│ │ ├── OutOrder.js # 订单接口
│ │ └── mock/ # Mock 数据
│ ├── assets/ # 静态资源
│ │ ├── icons/ # SVG 图标
│ │ ├── images/ # 图片资源
│ │ └── styles/ # 全局样式
│ ├── components/ # 公共组件
│ │ ├── nav-bar/ # 导航栏
│ │ ├── svg-icon/ # SVG 图标组件
│ │ └── tabbar/ # 底部导航
│ ├── layout/ # 布局组件
│ ├── locales/ # 国际化配置
│ │ ├── en/ # 英文
│ │ └── zh-CN/ # 中文
│ ├── router/ # 路由配置
│ │ ├── index.js # 路由入口
│ │ └── routes.js # 路由定义
│ ├── store/ # Pinia 状态
│ │ ├── index.js # Store 入口
│ │ └── modules/
│ │ └── user.js # 用户状态
│ ├── utils/ # 工具函数
│ │ ├── request.js # HTTP 请求
│ │ ├── auth.js # 认证工具
│ │ ├── cache.js # 缓存工具
│ │ └── progress.js # 进度条
│ ├── views/ # 页面组件
│ │ ├── home/ # 首页
│ │ ├── order-list/ # 订单列表
│ │ └── publish/ # 发布功能
│ ├── App.vue # 根组件
│ ├── main.js # 入口文件
│ └── settings.js # 应用配置
├── mock/ # Mock 服务
├── public/ # 公共资源
├── .env.development # 开发环境配置
├── .env.production # 生产环境配置
├── vite.config.js # Vite 配置
└── package.json # 项目依赖
环境配置
开发环境
# .env.development
NODE_ENV = 'development'
VITE_BASE_API = '/api'
VITE_ENABLE_VCONSOLE = true
VITE_BASE_URL = ''
生产环境
# .env.production
NODE_ENV = 'production'
VITE_BASE_API = 'https://api.example.com'
VITE_ENABLE_VCONSOLE = false
VITE_BASE_URL = ''
移动端适配
REM 适配方案
使用 amfe-flexible + postcss-pxtorem 实现移动端适配:
// main.js
import 'amfe-flexible';
// postcss.config.js
module.exports = {
plugins: {
'postcss-pxtorem': {
rootValue: 37.5, // 设计稿宽度 / 10
propList: ['*'],
selectorBlackList: ['.norem'] // 排除类名
}
}
};
Viewport 配置
<!-- index.html -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
UI 组件使用
Vant 组件引入
// main.js
import { createApp } from 'vue';
import { Button, NavBar, Tabbar, TabbarItem, List, Cell } from 'vant';
import 'vant/lib/index.css';
const app = createApp(App);
app.use(Button);
app.use(NavBar);
app.use(Tabbar);
app.use(TabbarItem);
app.use(List);
app.use(Cell);
页面示例
<!-- views/home/index.vue -->
<template>
<div class="home-container">
<!-- 导航栏 -->
<van-nav-bar title="首页" />
<!-- 内容区域 -->
<div class="content">
<van-list
v-model:loading="loading"
:finished="finished"
@load="onLoad"
>
<van-cell
v-for="item in list"
:key="item.id"
:title="item.title"
/>
</van-list>
</div>
<!-- 底部导航 -->
<van-tabbar v-model="active">
<van-tabbar-item icon="home-o">首页</van-tabbar-item>
<van-tabbar-item icon="orders-o">订单</van-tabbar-item>
<van-tabbar-item icon="user-o">我的</van-tabbar-item>
</van-tabbar>
</div>
</template>
网络请求
请求封装
// utils/request.js
import axios from 'axios';
import { getToken } from './auth';
import { showToast } from 'vant';
const service = axios.create({
baseURL: import.meta.env.VITE_BASE_API,
timeout: 10000
});
// 请求拦截器
service.interceptors.request.use(
config => {
const token = getToken();
if (token) {
config.headers['Authorization'] = `Bearer ${token}`;
}
return config;
},
error => Promise.reject(error)
);
// 响应拦截器
service.interceptors.response.use(
response => {
const { code, data, message } = response.data;
if (code === 200) {
return data;
}
showToast(message || '请求失败');
return Promise.reject(new Error(message));
},
error => {
showToast(error.message || '网络错误');
return Promise.reject(error);
}
);
export default service;
API 定义
// api/OutOrder.js
import request from '@/utils/request';
// 获取订单列表
export function getOrderList(params) {
return request({
url: '/mobile/orders/',
method: 'get',
params
});
}
// 获取订单详情
export function getOrderDetail(id) {
return request({
url: `/mobile/orders/${id}/`,
method: 'get'
});
}
// 更新订单状态
export function updateOrderStatus(id, status) {
return request({
url: `/mobile/orders/${id}/`,
method: 'patch',
data: { status }
});
}
状态管理
用户状态
// store/modules/user.js
import { defineStore } from 'pinia';
import { getToken, setToken, removeToken } from '@/utils/auth';
export const useUserStore = defineStore('user', {
state: () => ({
token: getToken(),
userInfo: null
}),
getters: {
isLoggedIn: state => !!state.token
},
actions: {
setToken(token) {
this.token = token;
setToken(token);
},
setUserInfo(info) {
this.userInfo = info;
},
logout() {
this.token = '';
this.userInfo = null;
removeToken();
}
}
});
路由配置
// router/routes.js
export default [
{
path: '/',
redirect: '/home'
},
{
path: '/home',
name: 'Home',
component: () => import('@/views/home/index.vue'),
meta: { title: '首页', showTabbar: true }
},
{
path: '/order-list',
name: 'OrderList',
component: () => import('@/views/order-list/index.vue'),
meta: { title: '订单列表', showTabbar: true }
},
{
path: '/publish',
name: 'Publish',
component: () => import('@/views/publish/index.vue'),
meta: { title: '发布', requireAuth: true }
}
];
路由守卫
// router/index.js
import { createRouter, createWebHistory } from 'vue-router';
import routes from './routes';
import { getToken } from '@/utils/auth';
const router = createRouter({
history: createWebHistory(),
routes
});
router.beforeEach((to, from, next) => {
// 设置页面标题
document.title = to.meta.title || 'SaltedFish';
// 需要登录验证
if (to.meta.requireAuth && !getToken()) {
next({ path: '/login', query: { redirect: to.fullPath } });
} else {
next();
}
});
export default router;
国际化
配置
// locales/index.js
import { createI18n } from 'vue-i18n';
import zhCN from './zh-CN';
import en from './en';
const i18n = createI18n({
locale: 'zh-CN',
fallbackLocale: 'en',
messages: {
'zh-CN': zhCN,
'en': en
}
});
export default i18n;
使用
<template>
<div>
<p>{{ $t('home.welcome') }}</p>
<van-button @click="switchLocale">切换语言</van-button>
</div>
</template>
<script setup>
import { useI18n } from 'vue-i18n';
const { locale } = useI18n();
const switchLocale = () => {
locale.value = locale.value === 'zh-CN' ? 'en' : 'zh-CN';
};
</script>
调试工具
VConsole
// main.js
if (import.meta.env.VITE_ENABLE_VCONSOLE === 'true') {
import('vconsole').then(({ default: VConsole }) => {
new VConsole();
});
}
构建配置
// vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import path from 'path';
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
},
server: {
host: '0.0.0.0',
port: 8080,
proxy: {
'/api': {
target: 'http://localhost:8000',
changeOrigin: true
}
}
},
build: {
outDir: 'dist',
assetsDir: 'assets',
sourcemap: false,
minify: 'terser',
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true
}
}
}
});
开发命令
# 安装依赖
npm install
# 启动开发服务器
npm run dev
# 构建生产版本
npm run build
# 预览构建结果
npm run preview
移动端最佳实践
1. 性能优化
- 图片懒加载 (Vant LazyLoad)
- 列表虚拟滚动
- 组件按需加载
- 使用 CSS3 动画替代 JS 动画
2. 用户体验
- 骨架屏加载
- 下拉刷新
- 上拉加载更多
- 触摸反馈
3. 兼容性
- 使用 autoprefixer
- 测试主流机型
- 处理 iOS 安全区域