目录
1. 初始化项目,上传 GitLab
2. Android App 打包流程
3. App 在真机上无法访问网络的问题
4. 替换 App 应用名 / 包名 / 唯一标识 scheme
5. 一些 Ionic 样式修改注意事项 / 返回按钮
6. 无数据组件 no-data.vue / !! 作用
7. async/await VS promise.all()【select and list】
8. await 最好返回 promise 防止意外错误发生
9. ion-select 使用过程中遇到的问题
10. Vue3 获取路由信息及路由传参
11. 在 ionic 中使用 Vuex【以登录为栗子】
12. 通用 vue 模板 / 其他细节模块 / 路由模块化
13. 移动端原型工程中的 http 相关配置
14. 获取事项列表的思路及遇到的问题
1. 初始化项目,上传 GitLab
- 拿到一个项目之后,先请项目经理开通 GitLab 项目权限,并新建一个仓库
- 接着下载 移动端原型工程,根据 GitLab 提示,将前述文件上传 GitLab
2. Android App 打包流程
- Vue3/Ionic 打包需要依靠 Android Studio + 命令行工具,环境配置类似于 Angular/Ionic
在项目根目录下,任意命令行工具中,执行如下操作:- 添加平台: ionic capacitor add android 【结束后命令行换行】
- 构建 原生app 资源:ionic capacitor build android 【结束后命令行换行,且自动打开 Android Studio 执行下述操作(构建app)】
- 构建完 原生app 资源之后,自动通过 Android Studio 构建 app 【可以观察面板右下角,会显示构建进度,初次构建app会比较慢】
- 点击 Android Studio 导航栏中的 Build → Build Bundles/APKs → Build APKs 【通过下方导航条的 build 可以查看打包进度】
后续在 vscode 中编写app,需要将 vscode 中的 静态资源/插件 更新到 Android Studio 中,执行下面两条命令的任意一条- ionic capacitor update android
- ionic capacitor sync
- 我的电脑上执行第一条不太行,执行第二条可以成功同步
- 同步完成之后,再次执行 Build APKs 即可获得新的 .apk 文件
3. App 在真机上无法访问网络的问题
- 在 res/xml 下,创建 network_security_config.xml 文件,并添加如下内容:
<?xml version="1.0" encoding="utf-8"?> <network-security-config> <base-config cleartextTrafficPermitted="true" /> </network-security-config>
- 接着,在 AndroidManifest.xml 文件下的 application 标签中,增加以下属性:
<application ... android:networkSecurityConfig="@xml/network_security_config" ... />
4. 替换 App 应用名 / 包名 / 唯一标识 scheme
- AndroidManifest.xml 是 app入口文件
- 从下图的位置可以点进配置 App 应用名 / 包名 / 唯一标识 scheme 的文件 【必须是在 Android Studio 中才能点进去】
- 点开之后长这样子:
- 说一下唯一标识的作用:比如我从 a应用 跳转到 b应用,就需要 b应用的唯一标识,命名规则目前还没有总结出来,需要后续项目完善
5. 一些 Ionic 样式修改注意事项 / 返回按钮
- 返回按钮:
- <ion-buttons slot="start">
<ion-back-button text=""></ion-back-button>
</ion-buttons>
列表项底边要求:- 让 最后的列表项 没有边:<ion-item lines="none"></ion-item>
- 让 除了最后一个之外的列表项 有边:<ion-item lines="full"></ion-item>
- 切换列表项状态:<ion-item :lines="index + 1 === listData.length ? 'none' : 'full'"></ion-item>
- 注意:动态绑定 lines 之后,他的值必须加单引号 ' '
ionic 在 <style lang="scss" scoped> 中,可以直接修改标签样式- ion-label {
display: flex !important;
height: 40px;
}- 需要区分 element-plus,在带 scoped 标签下修改它时,是通过 类名(不是标签名)修改样式,且需要添加 :deep()
在 vue3 中使用 ionic 时,页面中用到的 ionic 组件,都需要引入,举个栗子:- import { IonList, IonItem, IonLabel, IonThumbnail } from '@ionic/vue'
components: { IonList, IonItem, IonLabel, IonThumbnail },
如果不声明组件,也可以正常使用 ionic 组件,但网页会把他们当成 web component,而不是 Vue组件,也就是说,不能使用类似于 v-model 的 Vue 语法,举个栗子:- ion-select 组件,绑定的值是通过 :value="xx" 这么写的,但是这么写不是响应式的
- 在 vue 里通过 v-model 进行双向绑定实现响应式,而 ion-input 上不能直接使用 v-model 替代 :value,因为他是 web component
- 如果想在 ion-input 上使用 v-model 的话,需要将 ion-input 进行引入并声明
在 variables.css 中有常见的主题色变量,可以把 自定义颜色 放在该文件的 :root { } 里- 在 ionic 中,使用 主题色变量/全局变量:
- ion-content {
--background: var(--bg-color-black);
}
全局样式文件,可以在 assets/style/ 中创建文件,并在 main.ts 中进行引入- ionic 的全局样式,就可以在上述文件中修改
6. 无数据组件 no-data.vue / !! 作用
- no-data.vue
<slot v-if="!haveData"> <p class="no-data">暂无数据</p> </slot> <slot v-else name="haveData">需要展示的列表数据</slot>
- 使用列表及无数据组件
<no-data :haveData="haveData"> <template #haveData> <list-show :listData="listData"></list-show> </template> </no-data> // 导入 列表组件 及 列表组件内部导出的数据类型接口 import ListShow, { ListData } from '@/components/list-show.vue'
- 根据是否有列表数据,决定 haveData 的值
- state.haveData = !!state.listData.length;
- 注意:!! 可以把任意类型的判断,最终转换为布尔值
7. async/await VS promise.all()【select and list】
- promise.all() 获得的 成功结果数组里的 数据顺序,和 promise.all() 接收 异步方法数组的 顺序是一致的,举个栗子:
- 假设执行 promise.all([p1, p2]),即便 p1 结果 获取的比 p2结果晚,最终 p1结果在前
- 作用:没有逻辑关系的异步方法,在 onMounted 中同时调用
- 注意:promise.all() 让方法同时执行,而不是先后执行
async/await 可以控制方法执行的顺序,不是同步执行异步方法- 假设现在有一个页面:里面包含了需要接口请求的下拉框及列表,列表的内容是根据下拉框的内容决定的,此时就应该考虑 async/await 决定异步请求顺序
- 也就是说,必须 await 下拉框请求,且拿到结果之后,才能执行列表获取请求
- 此时不能使用 promise.all(),因为此方法只能保证结果获取的顺序,而不能保证方法执行的顺序,也就是说,会同时执行 下拉框获取 / 列表获取 两个方法,而不是先执行下拉框获取并且拿到结果,再执行列表获取方法
8. await 最好返回 promise 防止意外错误发生
- await 后面必须跟一个 promise,即使该 promise 返回空值;返回空值的情况下,也必须 resolve(), 否则,await getSelectOptions() 之后的代码都将无法执行
- 错误场景回顾:当时我写 await getSelectOptions() ,后面执行 getListData(),getListData() 方法内部使用了 state.selectValue,getSelectOptions() 没写 resolve(),这直接导致加载错误的 bug
解决方法:const getSelectOptions = (): Promise<void> => { return new Promise((resolve, reject) => { setTimeout(() => { state.eventTypeOptions.push({ label: '行政许可', value: '行政许可', }); state.eventTypeModel = state.eventTypeOptions[0].value; resolve(); }, 2000); }) };
- 一般情况下,获取下拉列表,都应返回首个选中的值,否则方法耦合度会高
9. ion-select 使用过程中遇到的问题
- ion-select 自带属性 和 自定义配置项 会同时生效
- 自带属性:cancelText="取消"
- 自定义配置项: :interface-options="customActionSheetOptions"
- const customActionSheetOptions = {
cssClass: 'my-custom-interface', // 自定义类名
header: '事件类型', // 下拉框标题
translucent: true, // 让选中项高亮
};<ion-select v-model="eventTypeModel" @ionChange="handleSecOrInpChange($event)" cancelText="取消" :interface-options="customActionSheetOptions" interface="action-sheet" > <ion-select-option v-for="(item, index) of eventTypeOptions" :value="item.value" :key="index"> {{ item.label }} </ion-select-option> </ion-select>
关于双向绑定:- 官方给的示例是 :value="eventTypeModel" 绑定下拉框当前绑定的值
- 如果想使用 vue 实现 --- 双向绑定 --- 的话,可替换为 v-model="eventTypeModel"
- 引入和声明 ionic 组件之后,才能让浏览器把它们当成 vue 组件,识别 vue 语法哦
关于下拉框列表获取方式【枚举 vs 接口获取】:- 应该通过接口获取下拉框列表
- 假设本地使用了枚举,如果需求变更,就需要修改枚举,重新打包发布 app,并让用户安装以获取更新,用户体验感会降低,因此必须使用接口获取下拉列表
使用枚举示例:- // 事项类型枚举
export enum eventType {
XZXK = '行政许可',
XZQR = '行政确认',
}- // 下拉框双向绑定的值
eventTypeModel: eventType.XZXK,
// 下拉框列表
eventTypeOptions: [{
value: eventType.XZQR,
label: eventType.XZQR,
}],
当下拉框列表发生变化时,可在 html 中传入 $event,监听变化- $event.detail 可以查看当前绑定的 value
- const handleSecOrInpChange = (ev: any) => {
console.log(ev.detail)
};
10. Vue3 获取路由信息及路由传参
- 路由跳转并传参【动词,router】:
import { useRouter } from 'vue-router';
const router = useRouter();router.push({
path: 'projectListPage',
query: {
eventName: item.eventName,
},
});路由参数获取【名词,route】:
import { useRoute } from 'vue-router'
const route = useRoute();
eventName: route.query.eventName,
11. 在 ionic 中使用 Vuex【以登录为栗子】
- 关于安装版本的问题:
- 最开始是直接去 vuex 官网安装,但是官网的版本是 3.x.x,本项目使用的是 ionic/vue3,因此要使用 4.0.0 以上的 vuex
- 综上所述,安装正确版本的方法 —— 修改 package.json:"vuex": "^4.0.2",然后 yarn
在页面中使用 vuex:- src/main.ts:
import store from './store/index';
const app = createApp(App)
.use(store);src/store/index.ts:
import { createStore } from 'vuex'; // 全局变量 const state = { // 用户信息 userInfo: { userId: '', userName: '', phone: '', } } // mutations const mutations = { // 更新用户信息 UPDATAUSERID(states: any, userinfo: object){ states.userInfo = userinfo; } } // actions const actions = { // 更新用户信息(异步) upDataUserInfo(context: any, userinfo: object) { console.log('当前登录用户信息', userinfo); context.commit('UPDATAUSERID', userinfo); } } export default createStore({ actions, mutations, state, })
src/views/login/login.vue:- import { useStore } from 'vuex';
- const store = useStore();
- const upDataUserInfo = (userInfo: any) => store.dispatch('upDataUserInfo', userInfo); // 异步更新用户信息
- upDataUserInfo({
userId: '2333',
userName: state.loginInfo.loginName,
phone: '110',
});
12. 通用 vue 模板 / 其他细节模块 / 路由模块化
- vue 通用模板:
<template> <ion-page> <ion-header> <ion-toolbar color="primary"> <ion-title>搜索</ion-title> </ion-toolbar> </ion-header> <ion-content> 搜索 </ion-content> </ion-page> </template> <script lang="ts"> import { IonPage, IonHeader, IonToolbar, IonTitle, IonContent, } from '@ionic/vue'; import { defineComponent } from 'vue'; import { useRouter } from 'vue-router'; export default defineComponent({ name: 'Login', components: { IonHeader, IonToolbar, IonTitle, IonContent, IonPage, }, setup(props, context) { const router = useRouter(); return { } } }); </script> <style lang="scss" scoped> </style>
tab 模块:
types 文件夹,定义项目中需要的 typescript 类型- 还可以加 enums 文件夹,定义项目中需要的 枚举enum 类型
路由根据 tab 可以分成多个模块,写在不同的 .ts 中,主路由模块中会加方法,将 router 文件夹下的所有路由 .ts 文件,添加到主路由模块中- 如果带了 子路由,展示地址: localhost:8080/tabs/home
- 如果不带 子路由,展示地址: localhost:8080/project-list-page
一个技巧:将路由路径存储到单独的 .ts 文件里,需要路由跳转时,通过变量代替直接写地址,这样可以避免后期修改路径名,需要全局挨个修改的麻烦// 路由变量 import { RouteApp } from '@/enums/route-config'; router.push({ path: RouteApp.ProjectList, query: { ... }, });
13. 移动端原型工程中的 http 相关配置
- 首先需要修改 public/config.js 文件夹中的全局地址
区分三种接口类型:
- 模拟接口,即通过 Rap2 或其他接口平台构建的模拟接口,在提供接口文档之后,前端负责编写
- 接口平台地址,即通过 token/interface/run.action 等访问接口数据,后端编写,在 Network 中,通过 run 过滤此类请求
- 自定义接口地址,即通过 getData.vm 这种访问接口数据
在 utils 文件夹中,包含 接口路径拼接 / axios设置 / 封装http请求 / 配置 token 等内容- 在 service 文件夹中,配置 具体业务的 http请求
- 以上图 事件列表 为例:
- 模拟接口复制 Rap2模拟接口平台中 写的地址即可
- 后端接口平台复制 interfaceID 字段即可
- 自定义开发接口复制 getData.vm 这种带.vm 的字段即可
- 在 useApi 中,控制当前使用哪种接口
注意:后端接口平台接口,需要配置 token,get/post 请求都需要
可以直接在 service 下的模块中,直接定义 接口所需参数类型 / 接口回复数据类型,也可以把这两个定义在 types 下的模块中
- 接口请求模板定义举例:
- 上述示例说明如下:
- 上面使用了 get/post 两种请求,都已经在 utils 里封装了,使用只有方法名的不同,没有别的区别
- 方法内部传入了当前使用的接口名,接口所需参数,是否显示加载等待
- param 指定接口参数类型,Promise 里接受泛型,传入 接口返回数据类型
14. 获取事项列表的思路及遇到的问题
- 页面初始化时,执行了 获取下拉列表 / 获取事项列表的方法,为什么会发送 两次 获取事项列表数据?
- 原因:我给 ionselect 绑定了 ionChange 方法,当获取到下拉列表后,下拉列表就已经发生了改变,执行了 ionChange 方法;而我在初始化中,又写了一次获取事项列表的请求,因此发送了两次请求