H5+搭建移动端应用 您所在的位置:网站首页 什么cms适合移动端登录 H5+搭建移动端应用

H5+搭建移动端应用

2023-08-07 11:37| 来源: 网络整理| 查看: 265

1. H5+搭建移动端应用

前两篇介绍了全栈系统里面后台和前端: 后台篇:Flask搭建后台 前端篇:Vue2.0搭建PC前端 项目线上地址:项目访问链接,账号:admin 密码:admin

今天讲述搭建全栈系统里面的移动端。本文讲述用Vue2.0 + mint-ui创建一个移动端APP,这属于全栈系统中的移动端,项目包含以下内容:

入口页面:定义登录页面和路由跳转 登录页面:实现系统登录功能 业务页面:写了两个业务页面1.> three.js加载gltf格式3D模型;2.> echart画图; 个人页面:显示用户信息页面 效果图: 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

1.1. 前端页面开发选用的技术栈如下:

开发语言:HTML+JS 开发框架:Vue2.0 + mint-ui + axios + echart + three.js 开发工具:Hbuilder X 2.7.9 系统后台:FlaskDemo

1.1.1. 技术选型

开发移动APP为什么不用原生语言来开发?为什么要用H5+来开发?下面详细说明, 不选原生开发的原因:1. 目前开发APP面临动态化内容需求日益增大,纯原生应用需要通过版本升级来更新内容,但应用上架、审核是需要周期的,这个周期对高速变化的互联网时代来说是很难接受的;2. 业务需求变化快,开发成本变大,一般都要维护 Android、iOS两个开发团队,版本迭代时,无论人力成本还是测试成本都会变大

为了避免原生开发面临的上述问题,我们选择跨平台技术,跨平台技术根据其原理,主要可分为如下三类, a. H5(HTML5)+原生( Cordova、 Tonic、微信小程序)。 b. Javascript开发+原生渲染( React Native、快应用)。 c. 自绘UI原生( QT + qml、 Flutter)。 本文采用H5+技术,后面的文章再介绍后面两种技术

选择H5+开发的原因:1. h5+应用能满足大部分APP需求,性能和体验都可以;2. 和PC web端开发技术相同,减少学习成本;3. 只需要写一套代码就能支持多个平台

1.2. 系统的详细开发过程 1.2.1. 用Hbuilder创建项目

在这里插入图片描述

项目创建完成后运行如下图: 在这里插入图片描述 这里要注意: 创建好项目时,需要创建一个vue.config.js的配置文件,配置内容如下:

const webpack = require('webpack') module.exports = { baseUrl: './',// 部署应用时的根路径(默认'/'),也可用相对路径(存在使用限制) outputDir: 'dist',// 运行时生成的生产环境构建文件的目录(默认''dist'',构建之前会被清除) assetsDir: '',//放置生成的静态资源(s、css、img、fonts)的(相对于 outputDir 的)目录(默认'') indexPath: 'index.html',//指定生成的 index.html 的输出路径(相对于 outputDir)也可以是一个绝对路径。 pages: {//pages 里配置的路径和文件名在你的文档目录必须存在 否则启动服务会报错 index: {//除了 entry 之外都是可选的 entry: 'src/main.js',// page 的入口,每个“page”应该有一个对应的 JavaScript 入口文件 template: 'public/index.html',// 模板来源 filename: 'index.html',// 在 dist/index.html 的输出 title: 'Index Page',// 当使用 title 选项时,在 template 中使用: chunks: ['chunk-vendors', 'chunk-common', 'index'] // 在这个页面中包含的块,默认情况下会包含,提取出来的通用 chunk 和 vendor chunk }, subpage: 'src/main.js'//官方解释:当使用只有入口的字符串格式时,模板会被推导为'public/subpage.html',若找不到就回退到'public/index.html',输出文件名会被推导为'subpage.html' }, lintOnSave: true,// 是否在保存的时候检查 productionSourceMap: false,// 生产环境是否生成 sourceMap 文件,false表示隐藏vue代码 css: { extract: true,// 是否使用css分离插件 ExtractTextPlugin sourceMap: false,// 开启 CSS source maps loaderOptions: {},// css预设器配置项 modules: false// 启用 CSS modules for all css / pre-processor files. }, devServer: {// 环境配置 host: '0.0.0.0', port: 8081, https: false, hotOnly: false, open: true, //配置自动启动浏览器 proxy: {// 配置多个代理(配置一个 proxy: 'http://localhost:4000' ) '/api': { target: '', ws: true, changeOrigin: true }, '/foo': { target: '' } } }, pluginOptions: {// 第三方插件配置 }, configureWebpack: { plugins: [ new webpack.ProvidePlugin({ $:"jquery", jQuery:"jquery", "windows.jQuery":"jquery" }) ] } } 1.2.2. 安装项目需要的依赖库

项目里面需要用到axios、jquery、vue-router、vuex、echarts,需要安装,命令如下: npm install --save axios jquery vue-router vuex mint-ui 编译菜单截图: 在这里插入图片描述

编译完成截图: 在这里插入图片描述

注意:如果编译过程中报错,根据提示安装缺失的包:npm install --save xxxx

1.2.3. 创建项目配置文件和目录

在这里插入图片描述

项目目录结构如上图,文件和目录的说明如下: dist:项目编译后生成的目录,该目录内容放到FlaskDemo中static目录下,就可以访问web页面 node_modules:项目依赖包安装目录 public:项目资源文件目录,这里存放着一个3D模型文件,用于加载到页面展示 src:vue源文件目录,assets存放资源,components存放实现的业务组件,后面详细描述 vue.config.js:Vue-cli3配置文件 manifest.json:配置APP打包信息文件 其他文件:创建Vue项目时自动生成的

下面详细介绍vue源文件目录

1.2.3.1. 创建App.vue

这个文件定义前端页面入口,引入了路由和页面布局、页面导航, **页面布局方式:**上面头部 + 中部路由显示 + 下面导航,页面布局是用mint-ui中mt-header、mt-tabbar组件实现 **页面导航:**有3个导航菜单,首页、业务、我的,通过监听绑定mt-tabbar组件的selected值来实现路由跳转

首页 业务 我的

监听selected值,实现路由跳转

watch:{ selected(val) { console.log(val, this.selected, this.$store.state.userInfo); if (this.$store.state.userInfo == '') return; if (val == 'tab1') { this.$router.push('/home'); } else if (val == 'tab2') { this.$router.push('/business'); } else if (val == 'tab3') { this.$router.push('/my'); } } }, 1.2.3.2. 创建main.js

这个文件引入项目需要的组件,创建Vue app,定义全局访问的方法 引入组件

import Vue from 'vue' import App from './App.vue' import Mint from 'mint-ui' import 'mint-ui/lib/style.css' import router from './router' import $ from 'jquery' import echarts from 'echarts' import store from './store' Vue.config.productionTip = false Vue.prototype.$echarts = echarts Vue.use(Mint) //配置axios import axios from 'axios' import qs from 'qs'

定义全局方法,如http访问

//配置axios import axios from 'axios' import qs from 'qs' axios.defaults.timeout = 5000; //响应时间 axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8'; //配置请求头 axios.defaults.baseURL = 'http://127.0.0.1:5000'; //配置接口地址 //POST传参序列化(添加请求拦截器) axios.interceptors.request.use((config) => { //在发送请求之前做某件事 if (config.method === 'post') { config.data = qs.stringify(config.data); } return config; }, (error) => { //console.log('错误的传参') return Promise.reject(error); }); //返回状态判断(添加响应拦截器) axios.interceptors.response.use((res) => { //对响应数据做些事 if (!res.data.success) { return Promise.resolve(res); } return res; }, (error) => { //console.log('网络异常') return Promise.reject(error); }); //返回一个Promise(发送put请求) Vue.prototype.$fetchPut = function(url, params) { return new Promise((resolve, reject) => { axios.put(url, params) .then(response => { resolve(response); }, err => { reject(err); }) .catch((error) => { reject(error) }) }) } //返回一个Promise(发送delete请求) Vue.prototype.$fetchDelete = function(url, params) { return new Promise((resolve, reject) => { axios({ method: "delete", url: url, data: params, }) .then(response => { resolve(response); }) .catch((error) => { reject(error) }) }) } //返回一个Promise(发送post请求) Vue.prototype.$fetchPost = function(url, params) { return new Promise((resolve, reject) => { axios.post(url, params) .then(response => { resolve(response); }, err => { reject(err); }) .catch((error) => { reject(error) }) }) } //返回一个Promise(发送get请求) Vue.prototype.$fetchGet = function(url, param) { return new Promise((resolve, reject) => { axios.get(url, { params: param }) .then(response => { resolve(response) }, err => { reject(err) }) .catch((error) => { reject(error) }) }) }

创建Vue,绑定路由和存储模块

new Vue({ render: h => h(App), store, router, }).$mount('#app') 1.2.3.3. 创建router.js

这个文件定义前端路由,关联导航菜单,跳转到具体页面

import Vue from 'vue' import Router from 'vue-router' import login from './components/login' import home from './components/home' import my from './components/my' import business from './components/business.vue' Vue.use(Router); export default new Router({ // mode: 'history', //去掉url中的# routes: [ { path: '/', name: 'login', lable: '登录', component: login }, { path: '/home', name: 'home', lable: '首页', component: home }, { path: '/my', name: 'my', lable: '我的', component: my }, { path: '/business', name: 'business', lable: '业务', component: business } ] })

代码中定义的lable就是导航菜单里面的名称,导航菜单内容根据用户权限返回,就可以根据不同用户动态展示导航菜单

1.2.3.4. 创建store.js

这个文件定义vuex保存数据

export default new vuex.Store({ state: { //xxxx: 'xxxxx', }, mutations: { setData(state, obj) { for (let k in state) { if (obj.hasOwnProperty(k)) { //xxxx = xxxxx; } } }, clearData(state) { for (let k in state) { //xxxx = ''; } } } });

由于vuex保存的数据在内存里面,页面一刷新,数据就会丢失,这里采用把数据临时保存到sessionStorage里面,刷后读取,再删除sessionStorage 具体代码在App.vue中created()方法实现。

created() {//处理刷新时vuex里面数据保存 //在页面加载时读取sessionStorage里的状态信息 if (sessionStorage.getItem("store")) { this.$store.replaceState(Object.assign({}, this.$store.state, JSON.parse(sessionStorage.getItem("store")))); sessionStorage.removeItem('store'); } // console.log(sessionStorage.getItem("store")) //在页面刷新时将vuex里的信息保存到sessionStorage里 window.addEventListener("beforeunload", () => { sessionStorage.setItem("store", JSON.stringify(this.$store.state)) }); } 1.2.3.5. 创建login.vue

这个文件创建登录页面,登录框是通过mint-ui中的mt-field、mt-button实现,

欢迎来到XXXXXX系统 记住密码 登录

数据结构定义

data() { return { form: { username: '', password: '', record: false } } },

登录功能实现

async login() { if (this.form.username == '' || this.form.password == '') { this.$toast('请输入账号名或者密码'); // this.$message.info('请输入账号名或者密码'); return; } let argc = { 'username': this.form.username, 'password': this.form.password }; let result = await this.$fetchPost('/login', argc); if (result.status == 200) { console.log(result.data); if (result.data.code == '0') { let groups = '{"首页": [], "业务菜单": ["3D模型", "画图展示", "业务3"], "系统设置": ["用户管理", "系统日志"]}'; let roles = '{"首页": ["读"], "3D模型": ["读", "写"], "业务2": ["读", "写"], "业务3": ["读", "写"], "用户管理": ["读", "写"], "系统日志": ["读", "写"]}'; localStorage.setItem('record', this.form.record); localStorage.setItem('username', this.form.username); this.$store.commit('setData', { 'access_token': this.form.username, 'userInfo': this.form.username, 'groups': this.$isJSONStr(groups) ? JSON.parse(groups) : {}, 'roles': this.$isJSONStr(roles) ? JSON.parse(roles) : {}, }); this.$router.push('/home'); this.form.password = ''; } else { this.$toast(result.data.msg); } } }

login函数说明: 1.> async配合await使用,http请求接口this.$fetchPost不需要写回调函数处理请求返回的结果,按顺序写处理结果代码,这样写逻辑清晰还能避免回调地狱 2.> 收到http请求后定义两个变量groups、roles模拟用户返回的权限,这里可以自己修改里面内容,看下登录后菜单显示的内容

1.2.3.6. 创建loadmodel.vue

这个文件是展示3D模型的组件,用来加载3D模型,了解更多WEB 3D知识:three.js 定义页面

引入组件

import * as THREE from 'three' import {OBJLoader, MTLLoader} from 'three-obj-mtl-loader' import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls' import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader'

定义数据模型

data() { return { camera: null, scene: null, light: null, renderer: null, controls: null, stats: null, } }

定义展示three.js3D模型的基本方法

methods: { initThree() {},//初始化three.js对象 initCamera(),//初始化相机 initScene() {},//初始场景 initLight() {},//初始化灯光 loadmodels() {},//加载gltf格式3D模型 initControl() {},//初始化模型控制器 onWindowResize() {},//渲染模型 render() {}, threeStart() { //启动流程函数 this.initThree(); this.initCamera(); this.initScene(); this.initLight(); this.loadmodels(); this.initControl(); this.renderer.clear(); this.renderer.render(this.scene, this.camera); } } 1.2.3.7. 创建business2.vue

这个文件是展示echart画图的组件 定义页面

定义数据模型

data() { return { height: document.documentElement.clientHeight - 160, builderJson: {}, downloadJson: {}, themeJson: {}, } }

定义画图方法

methods: { setOptionData(item, option) { var data; if (typeof option == "object") { data = option; } else { data = JSON.parse(option); } data["animation"] = true; var dom = document.getElementById(item); var myChart = this.$echarts.getInstanceByDom(dom); if (myChart != null && myChart != "" && myChart != undefined) { myChart.dispose(); } myChart = this.$echarts.init(dom, "roma"); if (data && typeof data === "object") { myChart.setOption(data, true); } } } 1.2.3.8. 创建my.vue

这个文件显示用户个人信息,通过定义用户信息userInfo数据结构,利用vue里面v-for和mint-ui里面mt-field实现多个信息显示,如图 在这里插入图片描述 页面定义

个人中心 {{info}} 退出

数据模型定义

data() { return { userInfo: { '部门': '', '岗位': '', '账户': '', '姓名': '', '手机号': '', '邮箱': '', '版本': 'V1.0' }, userName: this.$store.state.userInfo } }

处理用户信息方法

//查询用户信息,初始化数据 async init() { let userList = []; let results = await this.$fetchGet('/get/AccountUsers/get_value_list', {}); if (results.status == 200) { if (results.data.code == '0') { this.tableData = []; let info = results.data.data.filter(res => { return res[0].Name === this.userName; }); console.log(info); this.userInfo['部门'] = info[0][2].Name; this.userInfo['岗位'] = info[0][1].Name; this.userInfo['账户'] = info[0][0].Name; this.userInfo['姓名'] = info[0][0].Nick; this.userInfo['手机号'] = info[0][0].Mobile; this.userInfo['邮箱'] = info[0][0].Email; } } } //用户登出 async loginOut() { let results = await this.$fetchPost('/logout', {}); if (results.status == 200) { if (results.data.code == '0') { console.log(this.$store.state.userInfo); this.$store.commit('clearData'); console.log(this.$store.state.userInfo); this.$router.push('/'); } else { this.$toast(results.data.msg); } } } 1.3. 打包成APP

这里讲述打包成Android应用, 首先配置manifest.json文件,主要讲常用的基础配置和图标配置 基础配置: 在这里插入图片描述 a. 如果还没有AppID,点击获取,生成一个新的AppID,一个应用对应一个AppID b.输入应用名称、应用描述 c.配置应用入口页面,默认为index.html d.勾选配置app显示横屏、竖屏、横竖屏

图标配置: 在这里插入图片描述 如果为了简单省事,可以浏览一张图片,再点击“自动生成所以图标并替换”,就会生成各种尺寸的图片

1.4. 源码文件

后台源码:VueMobileDemo.zip 默认用户名:admin 默认密码:admin

1.5. 总结

我们来对比一下PC端和移动端

PC移动端技术对比Vue2.0 + element-ui + axios + echart + three.jsVue2.0 + mint-ui + axios + echart + three.js导航对比在navmennu.vue中实现在app.vue中通过tab实现业务代码共用共用

用h5+开发移动APP用到的技术基本一致,掌握了PC前端开发技术,开发移动APP也可以轻易实现

1.6. 后记

本文完整讲述了全栈系统中的移动端:利用Vue2.0+mint-ui创建移动端应用。 现在,后台、前端、移动端开发都讲完了。下章开始讲解后台部署(docker + nginx + uwsgi);前后端单元测试脚本;系统运维方面的知识。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有