史上最全神领物流司机端uniapp项目的实操笔记。本文覆盖项目的详细步骤和解决方案并对uniapp开始所涉及到的技术进行总结归纳。
项目主要模块
实名认证:司机扫脸认证,行程数据更加安全可靠
提货管理:提货任务、运输货品、车辆、线路安排的准确合理,提货流程清晰可控
交付管理:凭证、清单缺一不可,货品交付数据详细、实时可查
回车登记:异常信息及时掌握,保障车辆安全运行
Day1 项目初始化
项目拉取:git clone shenling-driver: A project developed with uni-app.
一、公共封装
1.2.1 网络请求
在 uni-app 中通过 uni.request
发起网络请求,在实际的应用中需要结合一些业务场景进行二次封装,比如配置 baseURL、拦截器等,社区有许多封装好的库,我们在项目中可以拿过来直接使用。
uni-app-fetch
是对 uni.request
的封装,通过 npm 来安装该模块
# 安装 uni-app-fetch 模块
npm install uni-app-fetch
然后根据自身的业务需求来配置 uni-app-fetch
// apis/uni-fetch.js
// 导入安装好的 uni-app-fetch 模块
import { createUniFetch } from 'uni-app-fetch'
// 配置符合自身业务的请求对象
export const uniFetch = createUniFetch({
loading: { title: '正在加载...' },
baseURL: 'https://slwl-api.itheima.net',
intercept: {
// 请求拦截器
request(options) {
// 后续补充实际逻辑
return options
},
// 响应拦截器
response(result) {
// 后续补充实际逻辑
return result
},
},
})
uni-app-fetch
更详细的使用文档在这里,它的使用逻辑是仿照 axios
来实现的,只是各别具体的用法稍有差异,在具体的应用中会给大家进行说明。
- loading 是否启动请求加载提示
- baseURL 配置请求接口的基地址
- intercept 配置请求和响应拦截器
配置完成后到项目首页(任务)测试是否请求能够发出,在测试时需要求【神领物流】全部接口都需要登录才能访问到数据,因此在测试请求时只要能发出请求即可,不关注返回结果是否有数据。
<!-- pages/task/index.vue -->
<script setup>
// 导入根据业务需要封装好的网络请求模块
import { uniFetch } from '@/apis/uni-fetch'
// 不关注请求结果是否有数据
uniFetch({
url: '/driver/tasks/list',
})
</script>
神领物流接口文档地址在这里。
1.2.2 轻提示
为了提升用户的体验,在项目的运行过程中需要给用户一些及时的反馈,比如数据验证错误、接口调用失败等,uni-app 中通过调用 uni.showToast
即可实现,但为了保证其使用的便捷性咱们也对它稍做封装处理。
// utils/utils.js
/**
* 项目中会用的一系列的工具方法
*/
export const utils = {
/**
* 用户反馈(轻提示)
* @param {string} title 提示文字内容
* @param {string} icon 提示图标类型
*/
toast(title = '数据加载失败!', icon = 'none') {
uni.showToast({
title,
icon,
mask: true,
})
}
}
// 方便全局进行引用
uni.utils = utils
在项目入口 main.js
中导入封装好的工具模块
// main.js
import App from './App'
import '@/utils/utils'
// 省略了其它部分代码...
到项目中测试工具方法是否可用,还是在首页面(task)中测试
// pages/task/index.vue
<script setup>
// 导入根据业务需要封装好的网络请求模块
import { uniFetch } from '@/apis/uni-fetch'
// 不关注请求结果是否有数据
uniFetch({
url: '/driver/tasks/list'
}).then((result) => {
if(result.statusCode !== 200) uni.utils.toast()
})
</script>
通过上述代码的演示我们还可以证实一个结论:uni-app-fetch
返加的是 Promise 对象,将来可配合 async/await
来使用。
二、Pinia状态管理
Pinia 是 Vue 专属于状态管理库,是 Vuex 状态管理工具的替代品,其具有一个特点:
- 提供了更简单的 API(去掉了 mutation)
- 提供组合式风格的 API
- 去掉了 modules 的概念,每一个 store 都是独立的模块
- 配合 TypeScript 更加友好,提供可靠的类型推断
2.1 安装 Pinia
# 安装 pinia 到项目当中
npm install pinia
在 uni-app 中内置集成了 Pinia 的支持,因此可以省略掉安装这个步骤,但如果是在非 uni-app 的 Vue3 项目使用 Pinia 时必须要安装后再使用。
2.2 Pinia 初始化
// main.js
import { createSSRApp } from 'vue'
// 引入 Pinia
import { createPinia } from 'pinia'
import App from './App'
import '@/utils/utils'
export function createApp() {
const app = createSSRApp(App)
// 实例化Pinia
const pinia = createPinia()
// 传递给项目应用
app.use(pinia)
return { app }
}
2.3 定义store
在 Vuex 中我们已经知道 了 store 本质上就是在【定义数据】及【处理数据】的方法,在 Pinia 中 store 也是用来【定义数据】和【处理数据】的方法的,所不同的是定义的方式不同。
- ref() 就是 state
- computed 就是 getters
- function() 就是 actions
// stores/counter.js
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useCounterStore = defineStore('counter', () => {
// 状态数据(相当于 state)
const count = ref(0)
// 定义方法(相当于 actions)
function increment() {
count.value++
}
function decrement() {
count.value--
}
// 一定要将定义的数据和方法返回
return { count, increment, decrement }
})
此时只需要将 Pinia 定义的 Store 当成普通模块来使用就可以了,详见下面代码:
<!-- pages/pinia/index.vue -->
<script setup>
import { useCounterStore } from '@/stores/counter'
// 获取 store 对象
const store = useCounterStore()
</script>
<template>
<view class="counter">
<button @click="store.decrement" class="button" type="primary">-</button>
<input class="input" v-model="store.count" type="text" />
<button @click="store.increment" class="button" type="primary">+</button>
</view>
</template>
2.4 stroreToRefs
使用 storeToRefs 函数可以辅助保持数据(state + getter)的响应式结构,即对数据进行解构处理后仍然能保持响应式的特性。在组件中可以直接使用store数据本身
<!-- pages/pinia/index.vue -->
<script setup>
import { storeToRefs } from 'pinia'
import { useCounterStore } from '@/stores/counter'
// 获取 store 对象
const store = useCounterStore()
// 直接使用数据 (state) 本身
const { count } = storeToRefs(store)
// store 中的方法直接结构即可
const { increment, decrement } = store
</script>
<template>
<view class="counter">
<button @click="decrement" class="button" type="primary">-</button>
<input class="input" v-model="count" type="text" />
<button @click="increment" class="button" type="primary">+</button>
</view>
</template>
2.5 持久化
在实际开发中我们有写业务需要长期的保存,在 Pinia 中管理数据状态的同时要实现数据的持久化,需要引入pinia-plugin-persistedstate插件。
# 安装 pinia-plugin-persistedstate 插件
npm i pinia-plugin-persistedstate
分成3个步骤来使用 pinia-plugin-persistedstate
- 将插件添加到 pinia 实例上
// main.js
import { createSSRApp } from 'vue'
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
// 省略中间部分代码...
export function createApp() {
const app = createSSRApp(App)
// 实例化Pinia
const pinia = createPinia()
// Pinia 持久化插件
pinia.use(piniaPluginPersistedstate)
// 省略部分代码...
}
- 配置需要持久化的数据,创建 Store 时,将 persist 选项设置为 true
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useCounterStore = defineStore('counter', () => {
// 状态数据(相当于 state)
const count = ref(0)
// 省略部分代码...
// 一定要将定义的数据和方法返回
return { count, increment, decrement }
}, {
persist: {
paths: ['count'],
}
})
当把 persist 设置为 true 表示当前 Store 中全部数据都会被持久化处理, 除此之外还可以通过 paths 来特别指定要持久化的数据。
- 自定义存储方法(针对 uni-app的处理)
小程序中和H5中实现本地存储的方法不同,为了解决这个问题需要自定义本地存储的逻辑。
// stores/persist.js
import { createPersistedState } from 'pinia-plugin-persistedstate'
export const piniaPluginPersistedstate = createPersistedState({
key: (id) => `__persisted__${id}`,
storage: {
getItem: (key) => {
return uni.getStorageSync(key)
},
setItem: (key, value) => {
uni.setStorageSync(key, value)
},
},
})
使用 uni-app 的 API uni.getStorageSync 和 uni.setStorageSync 能够同时兼容 H5、小程序以及 App,然后需要调整原来在 main.js 中的部分代码
// main.js
import { createSSRApp } from 'vue'
import { createPinia } from 'pinia'
// import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
import { piniaPluginPersistedstate } from '@/stores/persist'
// 省略中间部分代码...
export function createApp() {
const app = createSSRApp(App)
// 实例化Pinia
const pinia = createPinia()
// Pinia 持久化插件
pinia.use(piniaPluginPersistedstate)
// 省略部分代码...
}
三、uniForm表单验证
不同的组件库有不同的表单验证功能,但是大体上都是相似的。uni-app 的扩展组件中提供了关于表单数据验证。
- 在表单上使用
:model
绑定整体对象 - 使用
v-model
双向绑定数据 - 修改name字段
- 添加
:rule
规则 - 使用
ref
操作虚拟DOM出发验证
3.1 表单数据
定义表单的数据,指定 v-model 获取用户的数据
<!-- pages/login/components/account.vue -->
<script setup>
import { ref, reactive } from 'vue'
// 表单数据
const formData = reactive({
account: '',
password: '',
})
</script>
<template>
<uni-forms class="login-form" :model="formData">
<uni-forms-item name="account">
<input
type="text"
v-model="formData.account"
placeholder="请输入账号"
class="uni-input-input"
placeholder-style="color: #818181"
/>
</uni-forms-item>
...
</uni-forms>
</template>
- uni-forms 需要绑定model属性,值为表单的key\value 组成的对象。
- uni-forms-item 需要设置 name 属性为当前字段名,字段为 String|Array 类型。
3.2 验证规则
uni-app 的表单验证功能需要单独定义验证的规则:
<!-- pages/login/components/account.vue -->
<script setup>
import { ref, reactive } from 'vue'
// 表单数据
const formData = reactive({
account: '',
password: '',
})
// 定义表单数据验证规则
const accountRules = reactive({
account: {
rules: [
{ required: true, errorMessage: '请输入登录账号' },
{ pattern: '^[a-zA-Z0-9]{6,8}$', errorMessage: '登录账号格式不正确' },
],
},
password: {
rules: [
{ required: true, errorMessage: '请输入登录密码' },
{ pattern: '^\\d{6}$', errorMessage: '登录密码格式不正确' },
],
},
})
</script>
<template>
<uni-forms class="login-form" :rules="accountRules" :model="formData">
<uni-forms-item name="account">
<input
type="text"
v-model="formData.account"
placeholder="请输入账号"
class="uni-input-input"
placeholder-style="color: #818181"
/>
</uni-forms-item>
...
</uni-forms>
</template>
在定义 accountRules 时,对象中的 key 对应的是待验证的数据项目,如 account 表示要验证表单数据 account,rules 用来指验证的条件和错误信息提示。
3.3 触发验证
调用 validate
方法触发表单验单
<!-- pages/login/components/account.vue -->
<script setup>
import { ref, reactive } from 'vue'
// 表单元素的 ref 属性
const accountForm = ref()
// 表单数据
const formData = reactive({
account: '',
password: '',
})
// 定义表单数据验证规则
const accountRules = reactive({
account: {
rules: [
{ required: true, errorMessage: '请输入登录账号' },
{ pattern: '^[a-zA-Z0-9]{6,8}$', errorMessage: '登录账号格式不正确' },
],
},
password: {
rules: [
{ required: true, errorMessage: '请输入登录密码' },
{ pattern: '^\\d{6}$', errorMessage: '登录密码格式不正确' },
],
},
})
// 监听表单的提交
async function onFormSubmit() {
try {
// 验证通过
const formData = await accountForm.value.validate()
// 表单的数据
console.log(formData)
} catch (err) {
// 验证失败
console.log(err)
}
}
</script>
<template>
<uni-forms
class="login-form"
ref="accountForm"
:rules="accountRules"
:model="formData"
>
<uni-forms-item name="account">
<input
type="text"
v-model="formData.account"
placeholder="请输入账号"
class="uni-input-input"
placeholder-style="color: #818181"
/>
</uni-forms-item>
...
<button @click="onFormSubmit" class="submit-button">登录</button>
</uni-forms>
</template>
Day2 分页功能开发
节流防抖
这个分页的业务重新写一下
- 任务列表 getAnnounceList --page = 1 pageSize = 10
-
- 传入messageApi.list(200, page, pageSize)
- 掌握v-if和v-show的区别
- 掌握uniapp滚动分页
- 掌握uniapp条件编译
三
一、前置知识
1.1 v-if和v-show
控制手段:
v-show
隐藏则是为该元素添加css--display:none
,dom
元素依旧还在。
v-if
显示隐藏是将dom
元素整个添加或删除
编译过程:
v-show
只是简单的基于css切换,不会触发组件的生命周期
v-if
切换过程中合适地销毁和重建内部的事件监听和子组件。
二、任务通知切换
在 uni-app 跨端开发时,小程序不支持内置组件 Component 和 Keep-Alive,在实现类似标签页切换的功能时就变得十分的麻烦,在本项目中采用的是组合 v-if 和 v-show 指令来实现。
- v-if 指令保证组件只被加载一次
- v-show 指令控制组件的显示/隐藏
<!-- pages/message/index.vue -->
<script setup>
import { ref, reactive } from 'vue'
import slNotify from './components/notify'
import slAnnounce from './components/announce'
// Tab 标签页索引
const tabIndex = ref(0)
const tabMetas = reactive([
{title: '任务通知',rendered: true},
{ title: '公告',rendered: false},
])
// 切换标签页
function onTabChange(index) {
tabMetas[index].rendered = true
tabIndex.value = index
}
</script>
<template>
<view class="page-container">
...
<view v-show="tabIndex === 0" v-if="tabMetas[0].rendered" class="message-list">
<sl-notify />
</view>
<view v-show="tabIndex === 1" v-if="tabMetas[1].rendered" class="message-list">
<sl-announce />
</view>
</view>
</template>
- rendered 属性为 true 表明组件加载过,配合 v-if 来使用
- tabIndex 变量控制当前显示哪个组件,配合 v-show 来使用
二、滚动分页
2.1 监听 scrolltolower 事件
在项目中都会支持分页获取数据,在移动设备是常常配合页面的滚动来分页获取数据,在 scroll-view 组件中通过 scrolltolower 监听页面是否滚动到达底部,进而触发新的请求来获取分页的数据。
<scroll-view
@scrolltolower="onScrollToLower"
class="scroll-view"
refresher-enabled
scroll-y
>
...
</scroll-view>
2.2 计算下一页页码
页码是数字具具有连续性,我们只需要热行加 1 操作即可
// 任务列表
async function getNotifyList(p