神领物流(司机端)uniapp实战项目

史上最全神领物流司机端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

  1. 将插件添加到 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)

  // 省略部分代码...
}
  1. 配置需要持久化的数据,创建 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 来特别指定要持久化的数据。

  1. 自定义存储方法(针对 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 的扩展组件中提供了关于表单数据验证

  1. 在表单上使用:model 绑定整体对象
  2. 使用v-model双向绑定数据
  3. 修改name字段
  4. 添加:rule规则
  5. 使用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>
  1. uni-forms 需要绑定model属性,值为表单的key\value 组成的对象
  2. 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 分页功能开发

节流防抖

这个分页的业务重新写一下

  1. 任务列表 getAnnounceList --page = 1 pageSize = 10
    1. 传入messageApi.list(200, page, pageSize)

  • 掌握v-if和v-show的区别
  • 掌握uniapp滚动分页
  • 掌握uniapp条件编译

一、前置知识

1.1 v-if和v-show

控制手段:

v-show隐藏则是为该元素添加css--display:nonedom元素依旧还在。

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
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值