一、框架扩展
01.mobx-miniprogram
1.1 mobx-miniprogram 介绍
背景:
- 项目复杂性:随着微信小程序项目的业务逻辑越来越复杂,组件和页面间的数据通信可能会变得非常繁琐。特别是当需要在多个页面间同步使用某些状态时,传统的数据通信方案可能会带来管理和维护上的困难。
- 解决方案:为了简化这一过程,小程序官方提供了一个扩展工具库——
mobx-miniprogram
,它使得开发者能够更方便地在小程序中进行全局状态的管理和共享。
主要功能:
- 响应式状态管理:使用
mobx-miniprogram
定义的状态是响应式的,即当状态发生改变时,所有与之关联的组件都会自动更新其数据。 - 全局状态共享:通过
mobx-miniprogram
,开发者可以全局共享状态,并在多个页面和组件间同步使用这些状态。 - 简化数据通信:通过使用
mobx-miniprogram
,开发者可以减少组件和页面间的数据传递,从而降低代码的复杂性和维护成本。
1.2 创建 Store 对象
`mobx-miniprogram` 三个核心概念:
- observable:用于创建一个被监测的对象,对象的属性即应用的状态。这些状态会被转换成响应式数据。
- action:用于修改状态的方法。使用
action
函数可以显式地声明创建这些方法。 - computed:根据已有状态生成的新值。计算属性是一个方法,需要使用
get
修饰符进行标识。
使用步骤:
- 安装:需要安装两个包:
mobx-miniprogram
和mobx-miniprogram-bindings
。前者用于创建Store对象,后者用于将状态和组件、页面进行绑定关联。 - 创建Store:在项目的根目录下创建
store
文件夹,并在其中创建index.js
文件来定义和管理应用的状态。 - 在组件中使用:通过
mobx-miniprogram-bindings
库中的ComponentWithStore
方法或storeBindingsBehavior
来将Store与组件或页面进行绑定。
注意事项:
- 确保你的 Store 实例是唯一的,并在整个应用程序中共享。
- 使用
action
来包裹修改状态的方法,以确保状态更新的正确性和可追踪性。 - 在页面或组件卸载时,使用
destroyStoreBindings
方法来清理绑定,以避免内存泄漏
1.3 在组件中使用数据
使用方式:
- 绑定数据:在组件或页面的 JavaScript 文件中,通过
import
语句引入mobx-miniprogram-bindings
和 Store。然后使用createStoreBindings
方法将 Store 中的数据和方法绑定到当前组件或页面。 - 使用数据:在组件或页面的 WXML 文件中,使用双大括号
{{ }}
插值表达式来读取绑定的数据。在 JavaScript 文件中,可以直接通过this.data.[字段名]
来访问绑定的数据,通过this.[方法名]
来调用绑定的方法。 - 销毁绑定:在组件或页面的
onUnload
生命周期函数中,调用destroyStoreBindings
方法来销毁与 Store 的绑定,避免内存泄漏。
注意事项:
- 确保 Store 实例是单例的,并在整个应用程序中共享。
- 在组件或页面的
onLoad
生命周期函数中初始化 store 绑定,在onUnload
生命周期函数中销毁绑定。 - 避免在组件或页面的
data
对象中直接存储 Store 中的数据,而应通过mobx-miniprogram-bindings
进行绑定。 - 在使用
computed
时,注意其依赖的数据发生变化时,computed
的值也会自动更新。
1.4 fields、actions 对象写法
fields 对象写法:
fields
是一个对象,其键是组件或页面想要从 Store 中访问的字段名,值通常是一个布尔值 true
(尽管在实际使用中,通常省略值,因为 true
是默认值)。这些字段名应该与 Store 中定义的 observable 字段名相匹配。
示例:
// store.js
import { observable } from 'mobx-miniprogram';
const store = observable({
count: 0,
name: 'Alice',
});
export default store;
// yourComponent.js
import { createStoreBindings } from 'mobx-miniprogram-bindings';
import store from './store';
Component({
// ...
onLoad: function() {
this.storeBindings = createStoreBindings(this, {
store,
fields: {
// 绑定 count 字段
count: true,
// 绑定 name 字段,这里也可以简写为 name: true
name: true,
},
});
this.storeBindings.initStoreBindings();
},
// ...
onUnload: function() {
this.storeBindings.destroyStoreBindings();
},
// ...
});
在 WXML 中,你可以直接通过 {{count}}
和 {{name}}
来访问这些字段。
actions 对象写法:
actions
是一个对象,其键是组件或页面想要绑定为事件处理函数的 Store 中的动作名,值通常是这些动作名本身(即省略值,因为动作名本身就是处理函数)。这些动作名应该与 Store 中定义的 action 函数名相匹配。
示例:
// store.js
import { observable, action } from 'mobx-miniprogram';
const store = observable({
count: 0,
increment: action(function() {
this.count++;
}),
});
export default store;
// yourComponent.js
import { createStoreBindings } from 'mobx-miniprogram-bindings';
import store from './store';
Component({
// ...
onLoad: function() {
this.storeBindings = createStoreBindings(this, {
store,
fields: {
count: true,
},
actions: {
// 绑定 increment 动作
increment: 'increment',
},
});
this.storeBindings.initStoreBindings();
},
// ...
methods: {
// 在方法中调用绑定的动作
handleClick: function() {
this.increment();
},
},
// ...
});
在上面的示例中,handleClick
方法调用了 this.increment()
,这实际上是调用了 Store 中的 increment
动作,它会增加 count
的值。
请注意,在 actions
对象中,你可以省略值(即 'increment': 'increment'
可以简写为 increment
),因为当只提供一个字符串时,mobx-miniprogram-bindings
会默认将该字符串作为动作名。
1.5 绑定多个 store 以及命名空间
在 mobx-miniprogram-bindings
中,如果你需要绑定多个 store 到同一个组件或页面,并且希望避免字段名冲突,你可以使用命名空间(namespaces)来区分不同 store 中的字段。但是,mobx-miniprogram-bindings
本身并没有直接提供命名空间的特性,但你可以通过一些约定或包装来实现这个目的。
以下是一个如何实现绑定多个 store 并使用命名空间的例子:
创建多个 Store
首先,你需要创建多个 store,每个 store 管理不同的状态。
// storeA.js
import { observable } from 'mobx-miniprogram';
const storeA = observable({
countA: 0,
});
export default storeA;
// storeB.js
import { observable } from 'mobx-miniprogram';
const storeB = observable({
countB: 0,
});
export default storeB;
在组件中绑定多个 Store
在组件中,你可以使用 createStoreBindings
多次来绑定不同的 store,但通常更好的做法是使用一个包装函数来统一管理这些绑定。
// yourComponent.js
import { createStoreBindings } from 'mobx-miniprogram-bindings';
import storeA from './storeA';
import storeB from './storeB';
// 创建一个包装函数来处理多个 store 的绑定
function createNamespacedBindings(component, stores, namespaces) {
stores.forEach((store, index) => {
const namespace = namespaces[index] || ''; // 如果没有提供命名空间,则默认为空字符串
// 创建并初始化绑定
const bindings = createStoreBindings(component, {
store,
fields: Object.keys(store).reduce((acc, key) => {
// 使用命名空间来重命名字段
acc[`${namespace}${key.charAt(0).toUpperCase()}${key.slice(1)}`] = true;
return acc;
}, {}),
actions: Object.keys(store).reduce((acc, key) => {
// 使用命名空间来重命名动作
if (typeof store[key] === 'function') {
acc[`${namespace}${key.charAt(0).toUpperCase()}${key.slice(1)}`] = key;
}
return acc;
}, {}),
});
bindings.initStoreBindings();
// 保存绑定实例以便后续销毁
component[`storeBindings${namespace ? namespace.charAt(0).toUpperCase() + namespace.slice(1) : ''}`] = bindings;
});
}
Component({
// ...
onLoad: function() {
createNamespacedBindings(this, [storeA, storeB], ['StoreA', 'StoreB']); // 使用命名空间
},
// ...
onUnload: function() {
// 销毁绑定
if (this.storeBindingsStoreA) this.storeBindingsStoreA.destroyStoreBindings();
if (this.storeBindingsStoreB) this.storeBindingsStoreB.destroyStoreBindings();
},
// ...
methods: {
// 访问带有命名空间的字段和调用动作
incrementCountA() {
this.StoreAIncrement(); // 调用 storeA 中的 increment 动作
},
// ...
},
// ...
});
在上面的例子中,createNamespacedBindings
函数接受组件实例、store 数组和命名空间数组作为参数。它遍历每个 store,并使用命名空间来重命名字段和动作。这样,在组件的模板和逻辑中,你就可以通过带有命名空间的字段名和动作名来访问它们了。
02. miniprogram-computed
2.1 miniprogram-computed介绍
功能特点:
计算属性(computed)
- 允许开发者根据组件的data或其他计算属性计算出新的值,并自动更新到组件的data中。
- 在computed函数中,只能访问data对象,不能直接访问this。
- 示例用法:
computed: {
sum(data) {
return data.a + data.b;
}
}
监听属性(watch)
- 允许开发者监听data中某个或某些属性的变化,并在这些属性变化时执行特定的逻辑。
- watch的性能比computed更好,但computed的用法更简洁。
- 示例用法:
watch: {
'a, b': function(a, b) {
this.setData({
sum: a + b
});
}
}
安装和使用
安装:
- 通过npm进行安装:
npm install --save miniprogram-computed
- 在开发者工具中依次点击“工具”——“构建npm”,以完成安装。
使用:
- 在需要使用计算属性和监听属性的组件或页面中,通过
require
引入miniprogram-computed的behavior。 - 在组件或页面的配置中,添加
behaviors: [computedBehavior]
,并在data
、computed
、watch
等选项中定义相应的属性和方法。
注意事项
- 在computed函数中,不能访问this,只能访问data对象。
- 如果需要访问this或执行更复杂的逻辑,应该使用watch代替computed。
- watch和computed的使用应该根据具体场景和需求来决定,以达到最优的性能和效果。
二、用户管理
01. 用户登录-什么是 token
定义与用途:
- Token在微信小程序中扮演着类似于“钥匙”或“身份证”的角色。它是客户端与服务端之间通信的凭证,确保请求的来源是合法且经过验证的。
- 通过获取Token,开发者可以安全地调用微信小程序的API接口,实现各种功能,如获取用户信息、发起支付等。
获取方式:
- Token的获取通常涉及与微信服务器进行交互。开发者需要在小程序的后端服务器上,通过调用微信提供的API接口(如
wx.login
和getAccessToken
),以换取有效的Token。 - 在这个过程中,微信服务器会验证请求的合法性和有效性,并返回一个包含Token的响应。
Token的具体流程:
用户登录→服务器验证凭据→生成Token→发送Token→前端存储Token→使用Token→服务器验证Token
02. 用户登录-小程序登录流程介绍
业务介绍:
传统的登录功能通常需要用户先注册账号,注册完成后,使用注册的账号和密码进行登录。这种方式虽然提供了用户身份验证的功能,但用户体验上可能会受到一些影响,如需要用户记住账号密码、注册流程繁琐等。
而微信小程序的登录操作则提供了一种更为简单和便捷的方式。小程序可以利用微信提供的登录能力,直接获取微信提供的用户身份标识(OpenID或UnionID)进行登录。这种方式免去了用户注册和输入账号密码的步骤,大大提高了用户体验。
小程序登录流程:
1、用户触发登录:
- 用户在微信小程序内点击“登录”按钮或进行需要登录的操作时,小程序会触发登录流程。
2、调用微信登录接口:
- 小程序前端调用微信提供的
wx.login
接口,请求微信服务器生成一个临时的登录凭证(code)。
3、发送code到开发者服务器:
- 小程序前端将获取到的code发送到开发者自己的后端服务器。
4、开发者服务器请求微信服务器:
- 开发者服务器接收到code后,使用code、AppID和AppSecret(应用密钥)调用微信提供的接口,请求微信服务器换取用户的session_key和openid。
5、微信服务器返回信息:
- 微信服务器验证code的有效性后,返回session_key和openid给开发者服务器。
6、开发者服务器生成自定义登录态:
- 开发者服务器可以根据需要,使用openid和session_key生成自己的登录态(如JWT、自定义token等),并存储到数据库或缓存中。
7、返回登录态给小程序前端:
- 开发者服务器将生成的登录态返回给小程序前端。
8、小程序前端保存登录态:
- 小程序前端接收到登录态后,将其保存到本地(如Storage),以便后续请求时携带。
通过以上流程,小程序可以便捷地实现登录功能,并且免去了用户注册和输入账号密码的步骤,提高了用户体验。同时,由于使用了微信提供的登录能力,还可以确保用户身份的真实性和安全性。
03. 用户登录-实现小程序登录功能
功能概述:
在小程序中,当用户未登录时,通常会在页面(如个人中心)中显示未登录状态,并提供一个登录入口(如点击头像)。用户点击登录入口后,会跳转到登录页面进行登录操作。登录成功后,需要返回到之前的页面(如个人中心),并更新登录状态。
实现步骤:
1、检测登录状态
- 在需要登录验证的页面(如个人中心)中,首先检测用户的登录状态。这通常可以通过检查本地存储(如Storage)中是否有有效的登录凭据(如token)来实现。
- 如果用户未登录(即没有有效的登录凭据),则显示未登录状态,并提供登录入口(如头像)。
2、点击登录入口
- 当用户点击登录入口(如头像)时,触发登录操作。
- 可以使用小程序的页面跳转功能(如
wx.navigateTo
)跳转到登录页面。
3、登录页面处理
- 在登录页面,用户可以输入用户名和密码(对于非微信登录的情况)或点击微信登录按钮进行登录。
- 对于微信登录,可以调用微信提供的
wx.login
接口获取code,然后将code发送到后端服务器进行验证,并获取session_key和openid等信息。 - 后端服务器验证成功后,会生成一个登录凭据(如token),并返回给前端。
4、保存登录凭据
- 前端接收到后端返回的登录凭据后,将其保存到本地存储(如Storage)中,以便后续使用。
5、返回原页面并更新状态
- 登录成功后,使用小程序的页面跳转功能(如
wx.redirectTo
或wx.reLaunch
)返回到之前的页面(如个人中心)。 - 在返回到原页面时,需要更新页面的登录状态。这可以通过从本地存储中读取登录凭据,并更新页面的UI来实现。
注意事项:
- 确保在发送敏感信息(如用户名、密码、code等)到后端服务器时,使用HTTPS协议进行加密传输,以保证数据的安全性。
- 在后端验证登录凭据时,应确保验证逻辑的安全性,避免被恶意攻击者利用。
- 在前端保存登录凭据时,建议使用加密方式存储,并限制访问权限,以防止凭据被恶意获取。
- 在用户退出登录时,需要清除本地存储中的登录凭据,以确保用户下次访问时处于未登录状态。
04. 用户登录-token 存储到 Store
实现步骤:
1、安装MobX
首先,需要在项目中安装mobx
和mobx-react
(如果使用的是React):
npm install mobx mobx-react
# 或者
yarn add mobx mobx-react
2、 创建MobX Store
然后,需要创建一个store
来管理token
数据。
// store.js
import { observable, action } from 'mobx';
class AuthStore {
@observable token = null; // 初始token为空
@action setToken = (newToken) => {
this.token = newToken;
}
@action removeToken = () => {
this.token = null;
}
}
export default new AuthStore(); // 导出实例化后的store
3、在组件中使用MobX Store
接下来,需要在组件中引入store
并使用mobx-react
提供的inject
和observer
高阶组件来连接store
和组件。
// MyComponent.js
import React from 'react';
import { inject, observer } from 'mobx-react';
import AuthStore from './store'; // 假设store在这里
@inject('authStore') // 将store注入到props中,这里假设您使用了Provider来提供store
@observer // 使得组件对store中的observable数据变化做出响应
class MyComponent extends React.Component {
componentDidMount() {
// 假设您从本地存储中获取token并设置到store中
const savedToken = localStorage.getItem('token');
if (savedToken) {
this.props.authStore.setToken(savedToken);
}
}
handleLogout = () => {
// 假设您在这里处理登出逻辑,比如从localStorage和store中移除token
localStorage.removeItem('token');
this.props.authStore.removeToken();
}
render() {
const { token } = this.props.authStore;
// 根据token的状态渲染组件...
return (
<div>
{/* 组件内容 */}
<button onClick={this.handleLogout}>登出</button>
</div>
);
}
}
export default MyComponent;
4、使用Provider提供Store
在根组件或者某个包裹所有需要store
的组件的父组件中,您需要使用Provider
来提供store
。
// App.js
import React from 'react';
import { Provider } from 'mobx-react';
import AuthStore from './store';
import MyComponent from './MyComponent';
const App = () => (
<Provider authStore={AuthStore}> {/* 将store传递给Provider */}
<MyComponent /> {/* MyComponent和其他需要store的组件 */}
</Provider>
);
export default App;
现在,已经成功地将token
存储到了MobX
的store
中,并在组件中实现了对token
的响应式管理。当token
在store
中发生变化时,所有使用到这个token
的组件都会重新渲染。
05. 用户信息-用户信息存储到 Store
实现步骤:
1、更新 MobX Store
在 store/index.js
中添加 userInfo
可观测字段以及相应的 action
方法。
// store/index.js
import { observable, action } from 'mobx';
class UserStore {
@observable userInfo = null; // 初始用户信息为空
@action setUserInfo = (userInfo) => {
this.userInfo = userInfo;
}
@action clearUserInfo = () => {
this.userInfo = null;
}
}
export default new UserStore();
2、 封装获取用户信息的 API 函数
在 API 封装文件中(例如 api/user.js
),根据接口文档封装获取用户信息的函数。
// api/user.js
import axios from 'axios';
// 假设这里有一个全局可访问的 token
let token = localStorage.getItem('token');
// 获取用户信息
export const getUserInfo = async () => {
try {
const response = await axios.get('http://39.98.123.211:8300/api/getUserInfo', {
headers: {
'Authorization': `Bearer ${token}` // 假设使用Bearer token
}
});
return response.data;
} catch (error) {
// 处理错误
console.error('Error fetching user info:', error);
throw error;
}
};
3、调用获取用户信息的 API 并在登录成功后设置用户信息
在登录成功的回调中,调用 getUserInfo
API 函数,并将获取到的用户信息存储到本地和 store
中。
// 假设这是登录成功的回调
async function handleLoginSuccess() {
// 假设这里已经获取到了token并存储到了localStorage
// localStorage.setItem('token', newToken);
// 获取用户信息
try {
const userInfo = await getUserInfo(); // 调用封装的API函数
// 存储到本地(如果需要)
localStorage.setItem('userInfo', JSON.stringify(userInfo));
// 调用action方法,将用户信息存储到Store
userStore.setUserInfo(userInfo); // 假设userStore是UserStore的实例
} catch (error) {
// 处理错误
console.error('Error fetching user info:', error);
// 可以选择重定向到错误页面或显示错误信息
}
}
// 注意:这里的userStore需要通过某种方式被引入,例如在React应用中可以通过MobX的Provider提供
4、在组件中使用用户信息
在需要使用用户信息的组件中,通过inject
和observer
来连接store
。
// MyComponent.js
import React from 'react';
import { inject, observer } from 'mobx-react';
import UserStore from './store/UserStore'; // 引入UserStore
@inject('userStore') // 将userStore注入到props中
@observer // 使得组件对用户Store中的数据变化做出响应
class MyComponent extends React.Component {
// ... 其他组件逻辑
render() {
const { userInfo } = this.props.userStore;
if (userInfo) {
// 根据userInfo渲染组件
} else {
// 用户信息为空时的渲染逻辑
}
return (
// ... 组件内容
);
}
}
export default MyComponent;
确保在根组件或父组件中使用了Provider
来提供userStore
。
// App.js 或其他父组件
import React from 'react';
import { Provider } from 'mobx-react';
import UserStore from './store/UserStore';
import MyComponent from './MyComponent';
const App = () => (
<Provider userStore={UserStore}>
<MyComponent />
{/* 其他组件 */}
</Provider>
);
export default App;
这样,您就可以在整个应用程序中方便地获取和使用用户信息了。当用户信息在store
中发生变化时,所有使用到这个用户信息的组件都会重新渲染。
06. 用户信息-使用数据渲染用户信息
实现步骤:
1、在个人中心页面导入ComponentWithStore
方法构建页面
首先,我们需要在个人中心页面组件中导入ComponentWithStore
,并应用它到我们的组件上。
// UserProfile.js
import React from 'react';
import { ComponentWithStore } from 'path/to/ComponentWithStore'; // 假设的路径
import UserStore from 'path/to/UserStore'; // 导入UserStore
// 使用ComponentWithStore高阶组件来包装我们的组件
const UserProfile = ComponentWithStore(UserStore)(({ userStore }) => {
// 组件内容
});
export default UserProfile;
2、配置 storeBindings
让组件和 Store
建立关联
如果ComponentWithStore
支持storeBindings
,我们可以使用它来指定哪些store属性应该被注入到组件的props中。但是,在MobX中,更常见的做法是直接通过props访问store,因为我们已经通过高阶组件建立了关联。
如果ComponentWithStore
没有storeBindings
配置,那么我们可以直接访问userStore
属性(如上面的代码所示)。
3、渲染页面
现在我们可以根据用户是否登录来渲染不同的内容。
// UserProfile.js(续)
const UserProfile = ComponentWithStore(UserStore)(({ userStore }) => {
const { userInfo } = userStore;
// 渲染用户信息
if (userInfo) {
// 用户已登录,展示头像、昵称和设置按钮
return (
<div>
<img src={userInfo.avatarUrl} alt="User Avatar" />
<h2>{userInfo.nickname}</h2>
<button onClick={handleSettings}>设置</button>
</div>
);
} else {
// 用户未登录,展示默认头像和登录提示
return (
<div>
<img src={defaultAvatarUrl} alt="Default Avatar" />
<p>请登录以查看您的个人信息</p>
</div>
);
}
// 处理设置按钮点击事件的函数(此处仅为示例,具体实现取决于您的应用)
function handleSettings() {
// 跳转到设置页面或弹出设置菜单等
}
});
export default UserProfile;
三、地址管理
01. 定义新增参数以及封装接口 API
1、在新增收货地址页面 data
中声明所需要的字段
首先,在新增收货地址页面的Vue组件中(或其他前端框架的组件),我们需要在data
函数中声明所需要的字段。这些字段将用于收集用户输入的信息。
export default {
data() {
return {
form: {
name: '', // 收货人
phone: '', // 手机号
provinceName: '', // 省
provinceCode: '', // 省 编码
cityName: '', // 市
cityCode: '', // 市 编码
districtName: '', // 区
districtCode: '', // 区 编码
fullAddress: '', // 详细地址
isDefault: false, // 设置默认地址,false代表不是默认,true代表是默认
},
// ...其他可能需要的data字段
};
},
// ...其他Vue组件选项
};
2、定义收货地址所需要的全部接口 API
函数
接下来,我们需要定义API函数来调用后端接口。这些函数通常会被封装在API服务模块中,以便在整个应用中进行复用。
// api/address.js
import axios from 'axios'; // 假设我们使用了axios库来处理HTTP请求
// 假设后端API的URL前缀是'https://api.example.com/v1/'
const API_BASE_URL = 'https://api.example.com/v1/';
// 新增收货地址API
export const addAddress = async (addressData) => {
try {
const response = await axios.post(`${API_BASE_URL}/addresses`, addressData);
return response.data; // 返回后端返回的数据
} catch (error) {
// 处理错误,例如显示错误消息或抛出异常
console.error('Error adding address:', error);
throw error;
}
};
// ...其他收货地址相关的API函数(如编辑、删除等)
在上面的代码中,addAddress
函数接收一个addressData
对象作为参数,该对象包含了所有需要发送到后端的数据。然后,它使用axios.post
方法向后端发送POST请求,并将addressData
作为请求体发送。最后,它返回后端返回的数据。
在新增收货地址页面的Vue组件中,你可以通过调用这个addAddress
函数来发送新增收货地址的请求。当用户填写完表单并点击提交按钮时,你可以收集表单数据,并调用addAddress
函数。例如:
methods: {
async submitForm() {
try {
const response = await addAddress(this.form); // 调用API函数并传入表单数据
// 处理响应数据,例如显示成功消息或跳转到其他页面
console.log('Address added successfully:', response);
this.$router.push('/success-page'); // 假设成功后跳转到成功页面
} catch (error) {
// 处理错误,例如显示错误消息
console.error('Failed to add address:', error);
alert('Failed to add address');
}
},
// ...其他方法
},
02. 收集省市区数据
1、添加change
事件监听
在picker
组件上添加bindchange
属性,并指定一个处理函数,比如onAddressChange
。
<!-- 省市县 -->
<view class="item">
<text class="label">省/市/县 (区)</text>
<picker
mode="region"
value="{{ region }}"
bindchange="onAddressChange"
style="width: 100%; height: 30px; line-height: 30px;"
>
<view class="picker">
{{ region[0] + region[1] + region[2] }}
</view>
</picker>
<!-- 定位功能按钮等 -->
</view>
注意:这里的value
属性我使用了region
数组来存储选中的省市区值,而不是分别使用provinceName
、cityName
、districtName
。
2、在JS文件中处理change
事件
在对应的JS文件中,需要定义onAddressChange
函数来处理picker
的change
事件。这个函数会接收一个参数,该参数是一个数组,包含了用户选择的省市区信息。
Page({
data: {
region: ['', '', ''], // 初始值,数组形式存储省市区
// ...其他data字段
},
onAddressChange: function(e) {
this.setData({
region: e.detail.value // 设置选中的省市区值
});
// 根据选中的省市区值,你可能还需要去获取对应的编码
// 这里只是一个示例,实际获取编码的逻辑需要根据你的后端API来实现
const provinceCode = this.getProvinceCode(e.detail.value[0]); // 假设的获取省编码函数
const cityCode = this.getCityCode(e.detail.value[0], e.detail.value[1]); // 假设的获取市编码函数
const districtCode = this.getDistrictCode(e.detail.value[0], e.detail.value[1], e.detail.value[2]); // 假设的获取区编码函数
// 更新data中的编码字段
this.setData({
provinceCode: provinceCode,
cityCode: cityCode,
districtCode: districtCode
});
},
// 假设的获取编码函数,实际开发中需要根据后端API来实现
getProvinceCode: function(provinceName) {
// ...获取省编码的逻辑
return 'someProvinceCode';
},
getCityCode: function(provinceName, cityName) {
// ...获取市编码的逻辑
return 'someCityCode';
},
getDistrictCode: function(provinceName, cityName, districtName) {
// ...获取区编码的逻辑
return 'someDistrictCode';
},
// ...其他函数和逻辑
});
03. 收集新增地址其他请求参数
1、设置表单数据
定义表单所需的字段,包括姓名、电话、地址详情(省市区)、是否默认地址等。
export default {
data() {
return {
name: '',
phone: '',
province: '',
city: '',
district: '',
detailAddress: '',
isDefault: false, // 使用布尔值代替0和1
};
},
// ...
};
2、双向数据绑定
在模板中,使用v-model
指令来实现双向数据绑定。
<template>
<form @submit.prevent="submitForm">
<input type="text" v-model="name" placeholder="姓名" />
<input type="tel" v-model="phone" placeholder="电话" />
<!-- 假设你已经有一个组件来处理省市区选择,并更新province, city, district的值 -->
<your-picker-component v-model="province" @change="updateAddress" />
<your-picker-component v-model="city" @change="updateAddress" />
<your-picker-component v-model="district" @change="updateAddress" />
<input type="text" v-model="detailAddress" placeholder="详细地址" />
<input type="checkbox" v-model="isDefault" true-value="1" false-value="0"> 设置为默认地址
<button type="submit">提交</button>
</form>
</template>
3、更新完整地址
当省市区选择发生变化时,你可能需要更新完整地址
methods: {
updateAddress() {
this.fullAddress = `${this.province} ${this.city} ${this.district} ${this.detailAddress}`;
},
// ...
},
4、提交表单
在submitForm
方法中,收集并整理数据,准备发送请求。
methods: {
submitForm() {
// 假设使用axios发送请求
const isDefaultValue = this.isDefault ? 1 : 0;
axios.post('/api/add-address', {
name: this.name,
phone: this.phone,
province: this.province,
city: this.city,
district: this.district,
detail_address: this.detailAddress,
is_default: isDefaultValue,
})
.then(response => {
// 处理成功响应
})
.catch(error => {
// 处理错误
});
},
// ...
},
04. 地理定位功能介绍
1、API介绍
-
wx.getLocation()
:用于获取当前的地理位置、速度等信息。该接口需要用户主动触发,在获取用户地理位置时需要得到用户的同意。 -
wx.chooseLocation()
:在地图上选择位置。调用此接口会打开地图界面,允许用户选择地图上的任意位置。
2、申请开通
由于地理定位功能涉及用户隐私,微信对其使用进行了限制。并不是所有小程序都可以使用地理定位功能,它暂时只对部分类目的小程序开放。如果你的小程序需要使用地理定位功能,你需要:
- 确保你的小程序属于开放的类目范围内。
- 通过小程序管理后台提交类目审核,并确保审核通过。
- 在小程序管理后台的「开发」-「开发管理」-「接口设置」中自助开通该接口权限。
3、使用方法
3.1 在app.json
中配置requiredPrivateInfos
:
从微信基础库版本2.3.0开始,需要在app.json
中声明需要使用的用户信息字段,以提示用户进行授权。对于地理定位功能,需要配置requiredPrivateInfos
来声明。
{
"pages": [
// ...
],
"requiredPrivateInfos": ["getLocation", "chooseLocation"]
}
3.2 调用wx.getLocation()
时的配置:
如果你需要使用wx.getLocation()
接口获取用户的精确地理位置,你需要在app.json
中配置permission
字段,并声明scope.userLocation
的用途。但是,从微信基础库版本2.3.0开始,建议使用requiredPrivateInfos
替代permission
字段。
// 注意:这是旧版本的配置方式,新版本建议使用requiredPrivateInfos
{
"permission": {
"scope.userLocation": {
"desc": "你的小程序需要使用地理位置信息"
}
}
}
3.3 调用wx.chooseLocation()
:
这个接口不需要在app.json
中进行额外的配置,可以直接调用。它会打开地图界面供用户选择位置,选择后会返回一个包含位置信息的对象。
wx.chooseLocation({
success: function (res) {
// res.name 位置名称
// res.address 详细地址
// res.latitude 纬度,浮点数,范围为-90~90,负数表示南纬
// res.longitude 经度,浮点数,范围为-180~180,负数表示西经
console.log(res);
},
fail: function (err) {
// 用户拒绝或使用失败
console.error(err);
}
});
05. 拒绝授权后的解决方案
1、捕获拒绝授权的错误
在调用 wx.getLocation()
之前,确保你已经处理了可能出现的错误。这通常是通过在调用后添加错误处理函数来实现的。
wx.getLocation({
type: 'wgs84',
success: function (res) {
// 成功获取位置信息
console.log(res.longitude, res.latitude);
},
fail: function (err) {
// 授权失败处理
if (err.errMsg.includes('scope.userLocation')) {
// 用户拒绝授权
handleUserDenyAuthorization();
}
}
});
2、检查并引导用户重新授权
你可以使用 wx.getSetting()
来检查用户的当前授权状态,并使用 wx.openSetting()
来引导用户手动开启授权。
function handleUserDenyAuthorization() {
// 检查用户设置
wx.getSetting({
success: function (res) {
if (!res.authSetting['scope.userLocation']) {
// 用户未授权地理位置
wx.showModal({
title: '授权提示',
content: '为了正常使用功能,请授权地理位置',
showCancel: false,
success: function (res) {
if (res.confirm) {
// 用户点击了确定,尝试打开设置页面
wx.openSetting({
success: function (res) {
if (res.authSetting['scope.userLocation']) {
// 用户重新授权了地理位置
// 这里可以再次尝试调用 wx.getLocation()
} else {
// 用户没有重新授权
wx.showToast({
title: '您未授权地理位置',
icon: 'none'
});
}
}
});
}
}
});
}
}
});
}
3、注意事项
- 确保
wx.openSetting()
的调用是在用户触发的事件处理函数中,比如按钮点击事件。 - 尊重用户的隐私选择,不要频繁地提示用户授权。
- 如果用户仍然选择不授权,你的应用应该有相应的处理逻辑,比如展示提示信息或者禁用依赖地理位置的功能。
06. async-validator 基本使用
1、安装和导入
首先,你需要在项目中安装 async-validator
。你可以使用 npm 或 yarn 来安装它:
npm install async-validator --save
# 或者
yarn add async-validator
然后,在你的 JavaScript 文件中导入它:
import Schema from 'async-validator';
2、创建验证规则
接下来,你需要定义你的验证规则。这些规则是一个对象数组,每个对象描述了一个字段的验证要求。
const rules = {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 3, max: 5, message: '用户名长度在 3 到 5 个字符', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, max: 16, message: '密码长度在 6 到 16 个字符', trigger: 'blur' }
]
};
3、创建验证实例并验证数据
使用上面定义的规则来创建一个 async-validator
的实例,并调用其 validate
方法来验证数据。
// 创建验证器实例
const validator = new Schema(rules);
// 需要验证的数据
const data = {
username: 'test',
password: '1234'
};
// 进行验证
validator.validate(data, (errors, fields) => {
if (errors) {
// 验证失败,处理错误信息
console.error('验证失败:', errors);
// 可以在这里处理错误展示逻辑
} else {
// 验证成功,继续后续操作
console.log('验证成功!');
// 可以在这里处理验证通过后的逻辑
}
});
在上面的示例中,validate
方法的第一个参数是要验证的数据对象,第二个参数是一个回调函数。回调函数接收两个参数:errors
和 fields
。如果 errors
不为空,则说明验证失败,errors
是一个数组,包含了所有的错误信息。如果验证成功,errors
将为 null
。
四、商品管理
01. 配置商品管理分包
1、创建分包目录
首先,在 modules
目录下创建一个新的文件夹,命名为 goodModule
。这个文件夹将用于存放与商品管理相关的所有文件和资源
2、配置分包
接下来,你需要在 app.json
文件中配置分包信息。找到 subpackages
字段(如果没有,则需要添加),并在其中配置你的商品管理分包。配置应该类似于以下内容:
{
// ... 其他配置项 ...
"subpackages": [
{
"root": "modules/goodModule",
"pages": [
"goodsList/goodsList",
"goodsDetail/goodsDetail"
],
"name": "goodsPackage"
}
// ... 可能有其他分包配置 ...
],
// ... 其他配置项 ...
}
在这个配置中,root
字段指定了分包根目录的路径,pages
字段列出了该分包中的所有页面,而 name
字段是该分包的名称(可选,但建议添加以增强可读性)。
3、配置预加载规则
为了在用户访问设置页面时预先加载商品管理分包,你需要在 app.json
中添加 preloadRule
配置。这个配置允许你指定当满足某些条件时预先加载特定的分包。配置应该类似于以下内容:
{
// ... 其他配置项 ...
"preloadRule": {
"pages/settings/settings": {
"network": "wifi",
"packages": ["goodsPackage"]
}
},
// ... 其他配置项 ...
}
在这个配置中,pages/settings/settings
是触发预加载的页面路径(你的设置页面路径可能不同,请根据实际情况修改)。network
字段指定了在何种网络条件下进行预加载("wifi"
表示仅在 Wi-Fi 网络下进行预加载,你也可以设置为 "all"
以在所有网络条件下进行预加载)。packages
字段列出了需要预加载的分包名称。
4、更新页面跳转路径
由于页面现在位于分包中,你需要更新所有跳转到商品列表和商品详情页面的路径。这通常意味着你需要在路径前添加分包名称,例如:/goodsPackage/goodsList/goodsList
和 /goodsPackage/goodsDetail/goodsDetail
。你可以使用全局搜索功能来查找和替换旧的路径。
02. 封装商品模块接口 API
1、确定接口需求
- 获取商品列表
- 获取商品详情
- 添加商品
- 更新商品
- 删除商品
2、创建 API 封装文件
在 goodModule
文件夹中,创建一个新的文件,比如 api.js
(或 api.ts
如果你使用 TypeScript),用于存放所有的 API 封装函数。
3、编写封装函数
// 引入小程序的网络请求方法,如 wx.request 或其他 HTTP 客户端库
const request = require('path/to/your/request/method');
// 封装获取商品列表的 API
function getGoodsList(params) {
return new Promise((resolve, reject) => {
request({
url: 'your_api_endpoint_for_goods_list', // 替换为你的 API 端点
method: 'GET',
data: params, // 传递任何需要的查询参数
success: (res) => {
if (res.data && res.data.success) {
resolve(res.data.data); // 假设服务器返回的数据结构是 { success: true, data: [...] }
} else {
reject(res.data.message || 'Failed to fetch goods list');
}
},
fail: (error) => {
reject(error);
}
});
});
}
// 导出封装好的 API 函数
module.exports = {
getGoodsList,
// ... 其他封装的 API 函数
};
4、使用封装好的 API
const api = require('path/to/your/api.js');
// 调用获取商品列表的 API
api.getGoodsList({ page: 1, limit: 10 })
.then(goodsList => {
console.log(goodsList); // 处理获取到的商品列表数据
})
.catch(error => {
console.error(error); // 处理错误情况
});
03. 商品列表-准备列表请求参数
实现步骤详解:
1、在商品列表的 data
字段中定义所需字段
首先,在你的商品列表页面的 JavaScript 文件中,你需要在 Page
或 Component
的 data
对象中定义你将要使用的字段。这些字段应该包括接口请求所需要的参数,以及用于存储商品列表数据的字段。
Page({
data: {
limit: 10, // 每页记录数,可以根据实际需求设置默认值
page: 1, // 当前页码,默认为第一页
category1Id: 0, // 一级分类ID,默认为0或不设置,根据实际情况调整
category2Id: 0, // 二级分类ID,默认为0或不设置,根据实际情况调整
goodsList: [], // 用于存储商品列表的数据
// ... 其他可能的数据字段
},
// ... 页面的其他部分
});
2、在商品列表的 onLoad
钩子函数中接收并合并请求参数
接下来,在 onLoad
函数中,你需要处理页面跳转时可能传递过来的参数,并将这些参数与你的默认参数进行合并。
Page({
// ... data 字段等其他部分
onLoad: function (options) {
// 假设从上一个页面通过查询参数传递了 category2Id
if (options.category2Id) {
this.setData({
category2Id: options.category2Id // 更新二级分类ID
});
}
// 如果还有其他参数,也可以在这里进行处理和设置
// 合并请求参数并发起请求
const requestParams = {
limit: this.data.limit,
page: this.data.page,
category1Id: this.data.category1Id,
category2Id: this.data.category2Id
};
this.getGoodsList(requestParams); // 假设 getGoodsList 是你定义的获取商品列表的函数
},
// ... 其他函数和逻辑,比如 getGoodsList 用于发起网络请求获取商品列表数据
});
在这个例子中,onLoad
函数会检查是否有通过查询参数传递过来的 category2Id
,如果有,就更新 data
中的对应字段。然后,函数会合并所有的请求参数,并调用一个假设的 getGoodsList
函数来发起网络请求获取商品列表数据。
请注意,这里的 getGoodsList
函数需要你根据实际的网络请求库或框架来实现。
04. 商品列表-获取商品列表数据并渲染
实现步骤:
1、导入封装好的API函数
首先,你需要在 /pages/goods/list/list.js
文件的顶部导入之前封装好的获取商品列表的API函数。
// 假设你的API函数被封装在api.js文件中
import { getGoodsList } from '../../api/api'; // 根据你的项目结构调整导入路径
确保路径正确指向你封装的API函数文件。
2、在onLoad
钩子函数中调用getGoodsList
方法
接下来,在onLoad
钩子函数中,你将调用getGoodsList
函数以获取商品列表数据。
Page({
data: {
goodsList: [], // 商品列表数据
// ... 其他数据
},
onLoad: function (options) {
// 准备请求参数
const params = {
limit: 10, // 每页商品数量
page: 1, // 当前页码
// 如果从分类页面进入,可能还需要category1Id或category2Id等参数
// 这些参数可以从options中获取,或者根据实际情况设置
};
// 调用API函数获取商品列表数据
getGoodsList(params).then(res => {
// 假设后端返回的数据结构是{ success: true, data: [...] }
if (res.success) {
this.setData({
goodsList: res.data // 更新商品列表数据
});
} else {
// 处理错误情况,例如弹出提示信息等
console.error('Failed to fetch goods list:', res.message);
}
}).catch(error => {
// 处理网络请求错误等情况
console.error('Error fetching goods list:', error);
});
},
// ... 其他页面逻辑
});
3、使用后端返回的数据渲染页面
在获取到商品列表数据并更新到页面的data
中后,你需要在页面的WXML模板中使用这些数据来渲染商品列表。
例如,在/pages/goods/list/list.wxml
文件中:
<view class="goods-list">
<block wx:for="{{goodsList}}" wx:key="index">
<view class="goods-item">
<image src="{{item.image}}" mode="aspectFill"></image>
<text>{{item.name}}</text>
<text>¥{{item.price}}</text>
<!-- 其他商品信息展示 -->
</view>
</block>
</view>
完成上述步骤后,当页面加载时,它会自动调用onLoad
钩子函数,进而调用getGoodsList
方法获取商品列表数据,并使用这些数据来渲染页面。
05. 商品列表-实现上拉加载更多功能
实现步骤:
1、声明onReachBottom
事件处理函数
在list.js
文件中,需要添加onReachBottom
方法来监听用户上拉行为。这个方法会在页面滚动到底部时被触发
Page({
// ... 其他代码
onReachBottom: function() {
// 用户上拉加载更多
this.loadMoreGoods();
},
loadMoreGoods: function() {
// 增加页码,准备加载下一页数据
let currentPage = this.data.page;
currentPage++;
this.setData({ page: currentPage });
// 准备新的请求参数并发送请求
const params = {
limit: this.data.limit,
page: currentPage,
// 如果需要,还可以添加其他参数,如category1Id, category2Id等
};
// 调用API函数获取下一页商品列表数据
getGoodsList(params).then(res => {
if (res.success) {
// 合并新旧数据
let newGoodsList = this.data.goodsList.concat(res.data);
this.setData({ goodsList: newGoodsList });
} else {
// 处理错误或没有更多数据的情况
wx.showToast({
title: '没有更多数据了',
icon: 'none',
duration: 2000
});
}
}).catch(error => {
// 处理网络请求错误
console.error('Error loading more goods:', error);
wx.showToast({
title: '加载失败,请重试',
icon: 'none',
duration: 2000
});
});
},
// ... 其他代码
});
2、在onReachBottom
函数中处理页码并发送请求
在上面的loadMoreGoods
方法中,我们已经实现了页码的增加,并准备了新的请求参数来获取下一页数据。这个方法会在用户上拉时被onReachBottom
调用。
3、实现参数的合并与数据的更新
当新的商品数据返回后,我们需要将新的数据与旧的数据进行合并,并更新页面的goodsList
数据。这在loadMoreGoods
方法中已经有体现,通过concat
方法将新旧数据合并,并通过setData
更新页面数据。
确保你的getGoodsList
函数能够正确处理分页参数,并返回相应页的数据。此外,当没有更多数据时,你的后端接口应该返回一个标识,以便前端知道何时停止加载更多数据。
06. 商品列表-判断数据是否加载完毕
实现步骤:
1、赋值 total
到 data
在获取商品列表数据后,你需要将返回数据中的total
字段(代表商品总数)赋值给页面data
中的一个变量,以便后续使用。
Page({
data: {
goodsList: [],
total: 0, // 添加total字段用于存储商品总数
// ... 其他数据
},
// ...
loadMoreGoods: function() {
// ... 省略其他代码 ...
getGoodsList(params).then(res => {
if (res.success) {
// 合并新旧数据
let newGoodsList = this.data.goodsList.concat(res.data.list);
this.setData({
goodsList: newGoodsList,
total: res.data.total // 将返回的总数赋值给data中的total
});
}
// ...
});
// ...
},
// ...
});
2、在 onReachBottom
中进行对比
在onReachBottom
或loadMoreGoods
方法中,你可以添加一个判断来检查是否已经加载了所有商品。
loadMoreGoods: function() {
// ... 省略其他代码 ...
if (this.data.goodsList.length >= this.data.total) {
// 如果已加载的商品数量大于或等于总数,说明数据已全部加载完毕
wx.showToast({
title: '数据加载完毕',
icon: 'none',
duration: 2000
});
return; // 不再发起请求加载更多数据
}
// 准备新的请求参数并发送请求
// ...
// 发起请求加载更多数据的代码
}
3、模板中使用对比结果
在WXML模板中,你也可以根据goodsList.length
和total
的对比结果来决定是否显示“数据加载完毕”的提示。
<view class="goods-list">
<!-- 商品列表项 -->
<block wx:for="{{goodsList}}" wx:key="index">
<!-- 商品项内容 -->
</block>
<!-- 数据加载完毕提示 -->
<view wx:if="{{goodsList.length >= total}}" class="load-complete-tip">
数据加载完毕
</view>
</view>
这样,当用户上拉加载更多时,如果商品列表数据已经加载完,就会显示“数据加载完毕”的提示,并且不再发起新的加载更多请求。同时,在页面上也会显示相应的提示信息。
五、购物车
01. 购物车-封装购物车接口 API
首先,我们需要确定购物车接口的基本功能,包括:
- 添加到购物车:将商品添加到购物车中。
- 从购物车中移除商品:根据商品ID从购物车中移除特定商品。
- 更新购物车商品数量:修改购物车中特定商品的数量。
- 获取购物车列表:获取当前用户的购物车中所有商品。
- 清空购物车:移除购物车中的所有商品。
const API_BASE_URL = 'https://your-api-endpoint.com/cart';
// 添加到购物车
async function addToCart(productId, quantity) {
const response = await fetch(`${API_BASE_URL}/add`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ productId, quantity }),
});
return response.json();
}
// 从购物车中移除商品
async function removeFromCart(productId) {
const response = await fetch(`${API_BASE_URL}/remove/${productId}`, {
method: 'DELETE',
});
return response.json();
}
// 更新购物车商品数量
async function updateCartItemQuantity(productId, quantity) {
const response = await fetch(`${API_BASE_URL}/update/${productId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ quantity }),
});
return response.json();
}
// 获取购物车列表
async function getCartItems() {
const response = await fetch(`${API_BASE_URL}/list`, {
method: 'GET',
});
return response.json();
}
// 清空购物车
async function clearCart() {
const response = await fetch(`${API_BASE_URL}/clear`, {
method: 'DELETE',
});
return response.json();
}
02. 加入购物车-模板分析和渲染
模板结构:
<!-- 购物车弹框 -->
<div v-if="showCartPopup" class="cart-popup">
<div class="popup-content">
<h3>选择购买数量</h3>
<input type="number" v-model="quantity" min="1" />
<h3>祝福语</h3>
<textarea v-model="message"></textarea>
<button @click="confirmPurchase">确定</button>
<button @click="closePopup">取消</button>
</div>
</div>
<!-- 商品详情及操作按钮 -->
<div class="product-details">
<!-- 商品信息展示 -->
<button @click="addToCart">加入购物车</button>
<button @click="buyNow">立即购买</button>
</div>
实例:
new Vue({
el: '#app',
data: {
showCartPopup: false, // 控制弹框显示与隐藏的状态
buyNow: false, // 区分立即购买与加入购物车操作
quantity: 1, // 购买数量
message: '' // 祝福语
},
methods: {
// 展示购物弹框,并设置购买类型
showPopup(isBuyNow) {
this.showCartPopup = true;
this.buyNow = isBuyNow;
if (isBuyNow) {
this.quantity = 1; // 如果是立即购买,数量固定为1
}
},
// 加入购物车操作
addToCart() {
this.showPopup(false);
},
// 立即购买操作
buyNow() {
this.showPopup(true);
},
// 确认购买或加入购物车
confirmPurchase() {
if (this.buyNow) {
// 立即购买的逻辑处理,跳转到结算支付页面等
console.log('立即购买', this.quantity, this.message);
// 跳转到结算页面,这里仅为示例,实际开发中应使用路由跳转等功能
// window.location.href = '/checkout';
} else {
// 加入购物车的逻辑处理,将商品信息、数量和祝福语发送到后端等
console.log('加入购物车', this.quantity, this.message);
}
this.closePopup(); // 操作完成后关闭弹框
},
// 关闭购物弹框
closePopup() {
this.showCartPopup = false;
}
}
});
在这个示例中,我们定义了一个showCartPopup
状态来控制购物弹框的显示与隐藏。当用户点击“加入购物车”或“立即购买”按钮时,会调用showPopup
方法,并传入一个布尔值来表示当前操作是立即购买还是加入购物车。在confirmPurchase
方法中,我们根据buyNow
状态来区分执行立即购买还是加入购物车的逻辑。
03. 加入购物车-关联 Store 对象
实现步骤:
1、定义 Store
首先,你需要定义一个全局的Store
对象来管理你的应用状态。这个Store
可以是一个简单的JavaScript对象,也可以是一个更复杂的状态管理库。
// store.js
const store = {
state: {
user: null, // 用户信息
cart: [], // 购物车数据
// 其他全局状态...
},
// 可以添加getter、setter或其他状态操作方法
};
export default store;
2、创建 BehaviorWithStore
接下来,创建一个Behavior
,它会在页面创建时与Store
建立关联,并通过this.store
使页面能够访问到Store
对象。
// behaviorWithStore.js
import store from './store';
export default Behavior({
created() {
this.store = store;
},
methods: {
// 可以在这里添加一些与store交互的通用方法
}
});
3、在页面中使用 BehaviorWithStore
现在,你可以在任何页面中使用这个Behavior
来与Store
对象关联。
// somePage.js
import BehaviorWithStore from './behaviorWithStore';
Page({
behaviors: [BehaviorWithStore],
onLoad() {
// 现在可以通过 this.store 访问全局状态
if (this.store.state.user) {
// 用户已登录的逻辑
} else {
// 用户未登录的逻辑
}
},
addToCart() {
// 使用 this.store 来更新购物车状态等
},
buyNow() {
// 使用 this.store 来处理立即购买的逻辑
},
// 其他页面逻辑...
});
4、处理登录和Token
关于登录和Token
的处理,通常你会在Store
中保存用户的登录状态,包括Token
。当页面需要判断用户是否登录时,可以直接从Store
中获取这个状态。
// 在页面的某个方法中
if (this.store.state.user && this.store.state.user.token) {
// 用户已登录且存在token,可以进行加入购物车或立即购买的操作
} else {
// 提示用户登录或执行其他未登录状态下的操作
}
04. 加入购物车和立即购买区分处理
实现步骤:
1、设置步进器组件:
在页面的wxml文件中,使用Vant Weapp的步进器组件,并绑定相关的事件和数据。
<van-stepper value="{{quantity}}" min="1" max="200" bind:change="onChangeQuantity" />
在对应的js文件中,处理步进器值改变的事件,并更新数据。
Page({
data: {
quantity: 1, // 初始化购买数量为1
// ... 其他数据
},
onChangeQuantity(event) {
this.setData({
quantity: event.detail
});
},
// ... 其他方法
});
2、导入购物车API:
假设你已经封装了购物车的API,你可以直接在页面中引入并使用它。
// 引入购物车API模块
import cartApi from '../../apis/cart';
Page({
// ... 其他代码
methods: {
async addToCart() {
// 调用加入购物车API
try {
const result = await cartApi.addToCart({
productId: 'product-id', // 替换为实际商品ID
quantity: this.data.quantity,
message: this.data.message
});
// 处理加入购物车结果
} catch (error) {
// 错误处理
}
},
// ... 其他方法
}
});
3、处理加入购物车和立即购买:
在点击事件中,根据用户的选择执行不同的操作。
Page({
// ... 其他代码
methods: {
// ... addToCart 方法
async confirmPurchase() {
if (!this.checkLogin()) {
// 如果用户未登录,跳转到登录页面
wx.redirectTo({
url: '/pages/login/login'
});
return;
}
if (this.data.buyNow) {
// 立即购买逻辑
// 跳转到结算页面,带上商品ID和祝福语
wx.navigateTo({
url: `/pages/checkout/checkout?productId=${this.data.productId}&message=${this.data.message}`
});
} else {
// 加入购物车逻辑
await this.addToCart();
// 可以选择关闭弹框或展示加入购物车成功提示
}
},
checkLogin() {
// 检查用户登录状态的逻辑,根据实际情况实现
// 返回true表示已登录,返回false表示未登录
},
// ... 其他方法
}
});
05. 加入购物车-展示购物车购买数量
1、判断用户登录状态:
通常,用户的登录状态可以通过检查本地存储的token
来判断。如果用户已登录,本地应该存储有有效的token
。
2、获取购物车数据:
如果用户已登录,你可以调用后端API获取购物车列表数据。这些数据通常包括商品ID、商品名称、购买数量等信息。
3、计算并展示购物车商品数量:
通过对购物车列表中商品的购买数量进行累加,可以得到总的购买数量。然后,你可以在页面的某个位置展示这个数量。
4、代码示例:
// pages/productDetail/productDetail.js
Page({
data: {
cartItemCount: 0, // 购物车商品总数
// ... 其他数据
},
onLoad: function () {
this.checkAndUpdateCartItemCount();
// ... 其他加载逻辑
},
checkAndUpdateCartItemCount: function () {
// 假设你有一个函数用来检查token是否存在,例如:isLoggedIn()
if (this.isLoggedIn()) {
this.getCartData();
} else {
// 用户未登录,不展示购物车数量
this.setData({ cartItemCount: 0 });
}
},
isLoggedIn: function () {
// 检查本地是否存在token,根据实际情况实现
const token = wx.getStorageSync('token');
return token !== null && token !== '';
},
getCartData: function () {
// 调用后端API获取购物车数据,假设你有一个封装好的API函数getCartList()
cartApi.getCartList().then(cartList => {
let totalCount = 0;
cartList.forEach(item => {
totalCount += item.quantity; // 假设每个购物车项有一个quantity属性表示数量
});
this.setData({ cartItemCount: totalCount });
}).catch(error => {
console.error('获取购物车数据失败', error);
// 处理错误情况,例如展示一个错误信息给用户
});
},
// ... 其他页面逻辑和事件处理函数
});
在这个示例中,checkAndUpdateCartItemCount
方法会在页面加载时被调用。它首先检查用户是否登录(通过检查本地存储的token
),如果用户已登录,则调用getCartData
方法获取购物车数据并计算商品总数。然后,通过setData
方法更新页面数据,以便在页面上展示购物车商品的总数。
06. 购物车-购物车关联 Store 对象
1、创建或引入Store对象:
首先,你需要有一个全局可访问的Store
对象。这个对象可以管理全局状态,包括用户的登录状态、购物车数据等。
2、创建购物车组件:
使用微信小程序的Component
方法创建一个购物车组件。
3、在组件中关联Store:
在购物车组件的lifetimes
或methods
中,你可以添加逻辑来访问和更新Store
中的状态。
4、示例代码:
// 假设你已经有了一个全局的Store对象
const store = {
state: {
isLoggedIn: false,
cartItems: []
},
// 假设有一些getter和setter方法来管理状态
getIsLoggedIn: function() {
return this.state.isLoggedIn;
},
setIsLoggedIn: function(value) {
this.state.isLoggedIn = value;
},
getCartItems: function() {
return this.state.cartItems;
},
// ... 其他状态管理方法
};
// 在购物车组件中
Component({
lifetimes: {
attached: function() {
// 组件加载时,根据Store中的登录状态来更新UI
this.updateUI();
}
},
methods: {
updateUI: function() {
if (store.getIsLoggedIn()) {
// 用户已登录,从Store中获取购物车数据并更新UI
const cartItems = store.getCartItems();
// 更新UI的代码...
} else {
// 用户未登录,显示登录提示或跳转到登录页面
}
},
// ... 其他组件方法
}
});
07. 购物车-获取并渲染购物车列表
1、导入API函数:
假设你已经封装好了获取购物车列表的API函数,你可以在小程序的JS文件中导入这个函数。
2、页面数据初始化:
在购物车组件的JS文件中,初始化数据,包括购物车列表、登录状态等。
3、处理页面显示逻辑:
在onShow
生命周期钩子中,根据用户的登录状态来获取购物车数据,并处理页面的不同提示。
4、渲染购物车列表:
获取到购物车数据后,使用setData
方法来更新页面数据,从而渲染购物车列表。
5、代码示例:
// cart.js
const app = getApp();
Page({
data: {
cartList: [], // 购物车列表数据
isLoggedIn: false, // 用户登录状态
emptyCartMsg: '还没有添加商品,快去添加吧~', // 购物车为空时的提示信息
notLoggedInMsg: '您尚未登录,点击登录获取更多权益', // 未登录时的提示信息
},
onShow: function() {
// 检查用户登录状态
this.checkLoginStatus();
},
checkLoginStatus: function() {
// 假设登录状态存储在app全局对象中
this.setData({
isLoggedIn: app.globalData.isUserLoggedIn
});
if (this.data.isLoggedIn) {
this.getCartList();
}
},
getCartList: function() {
// 调用封装好的API函数获取购物车列表数据
app.api.getCartList().then(res => {
if (res.data && res.data.length > 0) {
// 购物车有数据,更新页面数据并渲染列表
this.setData({
cartList: res.data
});
} else {
// 购物车为空,显示提示信息
this.setData({
emptyCartMsg: '还没有添加商品,快去添加吧~'
});
}
}).catch(err => {
console.error('获取购物车列表失败', err);
});
},
// ... 其他页面逻辑和事件处理函数
});
六、结算支付
01. 配置分包并跳转到结算页面
1、配置分包
在 app.json
或 app.config.json
文件中,你可以通过 subPackages
或 subpackages
字段来配置分包。例如,如果你想将结算支付功能作为一个分包,可以这样配置:
{
"pages": [
"pages/index/index",
// ... 其他主包页面
],
"subPackages": [
{
"root": "subpackages/settlement/",
"pages": [
"pages/settlement/settlement"
]
}
],
// ... 其他配置
}
这个例子中,subpackages/settlement/
是分包根目录,pages/settlement/settlement
是分包中的页面路径。请确保这些路径与你的项目文件结构相匹配。
2、预先加载分包
如果你希望在用户访问某个页面(如设置页面)时预先加载结算支付分包,你可以在设置页面的 onLoad
或 onShow
生命周期函数中使用 wx.loadSubPackage
方法来加载分包。但是,请注意,从微信基础库2.9.0开始,wx.loadSubPackage
已被废弃,微信会自动进行分包的预加载。因此,你通常不需要手动调用这个方法。
如果你确实需要在特定时机预加载分包,可以考虑使用其他策略,比如在用户进行某些操作(如点击结算按钮)之前,通过跳转到分包中的一个空白页面来触发分包的加载。
3、跳转到结算页面
当用户需要从其他页面跳转到结算页面时,你可以使用 wx.navigateTo
方法:
wx.navigateTo({
url: '/subpackages/settlement/pages/settlement/settlement'
});
请确保 URL 路径与你在 app.json
中配置的路径一致。
4、注意事项
- 分包的大小有限制,通常不超过 2M。确保你的分包不会超出这个限制。
- 主包会包含启动页面和所有分包都需要用到的公共资源或JS脚本,应合理规划这些内容以优化加载速度。
- 在发布前充分测试分包加载的行为,确保用户体验不受影响。
02. 封装结算支付的接口 API
1、封装流程:
-
确定支付服务提供商的API要求:
了解并熟悉你所使用的支付服务提供商(如微信支付)的API文档,包括参数要求、请求方式、响应格式等。 -
创建支付服务类:
在微信小程序的项目中,创建一个支付服务类,用于封装与支付服务提供商的交互。 -
实现支付接口方法:
在支付服务类中,根据支付服务提供商的API要求,实现支付请求、支付结果查询等接口方法。 -
处理异常和错误:
在接口方法中,添加异常处理和错误返回机制,确保调用方能够正确处理支付过程中可能出现的问题。 -
测试与验证:
在实际环境中测试封装的支付接口,确保其功能正确且稳定。
2、代码示例:
// paymentService.js
class PaymentService {
constructor(appId, mchId, apiKey) {
this.appId = appId;
this.mchId = mchId;
this.apiKey = apiKey;
this.baseURL = 'https://api.mch.weixin.qq.com/'; // 假设的微信支付API地址
}
async initiatePayment(orderId, totalFee, body, spbillCreateIp, notifyUrl) {
try {
const params = {
appid: this.appId,
mch_id: this.mchId,
nonce_str: this.generateNonceStr(), // 生成随机字符串的方法需要自行实现
body: body,
out_trade_no: orderId,
total_fee: totalFee, // 单位:分
spbill_create_ip: spbillCreateIp,
notify_url: notifyUrl,
trade_type: 'JSAPI', // 小程序支付类型
openid: '用户的openid' // 需要从微信登录接口获取
};
params.sign = this.generateSignature(params); // 生成签名的方法需要自行实现
const response = await wx.request({
url: this.baseURL + 'pay/unifiedorder',
method: 'POST',
data: params
});
if (response.data.return_code === 'SUCCESS' && response.data.result_code === 'SUCCESS') {
return response.data;
} else {
throw new Error('Payment initiation failed: ' + response.data.return_msg);
}
} catch (error) {
throw error;
}
}
// 其他支付相关方法,如查询支付结果等...
}
module.exports = PaymentService;
使用示例:
// 在需要使用支付服务的地方引入PaymentService类
const PaymentService = require('./paymentService');
// 实例化支付服务类,传入必要的配置参数
const paymentService = new PaymentService('your_app_id', 'your_mch_id', 'your_api_key');
try {
const paymentData = await paymentService.initiatePayment('order12345', 100, '商品描述', '123.123.123.123', 'https://yourdomain.com/notify');
// 处理支付数据,如调用wx.requestPayment等
} catch (error) {
console.error('Payment initiation failed:', error);
}
03. 商品结算-获取收货地址
1、调用API获取收货地址
当进入结算页面时,你需要调用微信小程序的API来获取用户的收货地址。微信小程序提供了一个wx.chooseAddress
的API来获取用户已经保存的收货地址
2、处理获取到的收货地址
根据wx.chooseAddress
的返回结果,你可以判断用户是否已经保存了收货地址。如果没有,则提示用户添加收货地址;如果已经保存了地址,则渲染这些地址供用户选择
3、渲染收货地址或添加地址的提示
根据上一步的判断结果,相应地渲染已保存的收货地址列表或者展示添加收货地址的提示
4、示例代码
Page({
data: {
addresses: [], // 用于存储收货地址
showAddAddressHint: false // 是否显示添加地址的提示
},
onLoad: function() {
this.getAddresses();
},
getAddresses: function() {
wx.chooseAddress({
success: (res) => {
if (res.errMsg === 'chooseAddress:ok') {
// 成功获取到收货地址
this.setData({
addresses: res.addresses,
showAddAddressHint: false
});
} else {
// 没有获取到收货地址,展示添加地址的提示
this.setData({
showAddAddressHint: true
});
}
},
fail: (err) => {
// 处理失败情况,例如展示错误信息或引导用户添加地址
this.setData({
showAddAddressHint: true
});
}
});
},
// 其他页面逻辑...
});
5、WXML模板示例
<view>
<!-- 如果有收货地址,则渲染地址列表 -->
<view wx:if="{{addresses.length > 0}}">
<block wx:for="{{addresses}}" wx:key="*this">
<view>{{item.userName}} {{item.postalCode}} {{item.provinceName}}{{item.cityName}}{{item.countyName}}{{item.detailInfo}}</view>
</block>
</view>
<!-- 如果没有收货地址,则展示添加地址的提示 -->
<view wx:else>
<view>请添加收货地址</view>
<button bindtap="addAddress">添加地址</button>
</view>
</view>
6、注意
wx.chooseAddress
只能在小程序中调用,且需要用户授权地址信息。- 如果用户未选择过地址,或者用户拒绝了地址授权,
wx.chooseAddress
会失败,这时可以引导用户去设置中添加地址或重新授权。 - 在实际项目中,你可能还需要处理用户选择具体某个地址的逻辑,以及添加、编辑地址的功能。
04. 商品结算-更新收货地址功能
1、在 app.js
中定义全局数据
首先,在 app.js
中定义 globalData
对象,并在其中添加一个 address
属性用于存储选择的收货地址。
// app.js
App({
globalData: {
address: null // 用于存储全局的收货地址信息
},
// ... 其他App级别的逻辑
});
2、跳转到收货地址页面
在结算页面中,当用户点击更改收货地址的按钮或箭头时,使用 wx.navigateTo
跳转到收货地址选择页面,并可以传递一个参数标识是从订单结算页面进入的。
// 结算页面中的方法,用于跳转到地址选择页面
goToAddressPage: function() {
wx.navigateTo({
url: '/pages/address/address?from=checkout'
});
}
3、选择收货地址并存储到全局数据
在收货地址页面中,当用户选择了一个地址后,将该地址信息存到 app.globalData.address
中。
// 地址选择页面中的方法,假设用户在页面上选择了一个地址
chooseAddress: function(event) {
const address = event.detail; // 假设event.detail包含了用户选择的地址信息
const app = getApp();
app.globalData.address = address;
wx.navigateBack(); // 返回结算页面
}
4、在结算页面渲染收货地址
回到结算页面后,在页面 onLoad
或 onShow
生命周期方法中检查 app.globalData.address
是否包含地址数据,并据此渲染地址信息。
Page({
onLoad: function() {
this.checkAndRenderAddress();
},
onShow: function() {
// 页面显示时也可以再次检查,以防用户在地址页面更改了地址
this.checkAndRenderAddress();
},
checkAndRenderAddress: function() {
const app = getApp();
if (app.globalData.address) {
// 如果存在全局地址数据,则渲染到页面上
this.setData({
address: app.globalData.address
});
} else {
// 如果不存在,可以设置一个默认提示或者执行其他逻辑
this.setData({
address: null
});
}
},
// ... 其他页面逻辑
});
5、注意事项
- 确保在地址选择页面返回结算页面之前,将选择的地址存储到
app.globalData.address
中。 - 在结算页面加载或显示时,都要检查并更新收货地址信息。
- 如果用户在地址页面没有选择地址就返回,需要处理这种情况,例如显示一个提示或者保持原来的地址不变。
- 使用全局变量时需要谨慎,因为它可以在整个小程序中访问和修改,可能会导致数据不一致或其他未预期的副作用。确保在适当的时候清除或更新全局数据。
05. 商品结算-获取订单详情数据
1、导入封装的接口API函数
// 假设你的API函数封装在api.js文件中
const API = require('../../utils/api'); // 路径根据实际情况调整
2、在页面加载时调用API获取订单详情数据
当结算页面加载时,你需要在onLoad
生命周期方法中调用API函数来获取订单详情数据。这些数据可能包括商品列表、总价、优惠信息等。
Page({
data: {
orderDetails: null, // 用于存储订单详情数据
// 其他页面数据...
},
onLoad: function(options) {
this.getOrderDetails();
},
getOrderDetails: function() {
// 调用API函数获取订单详情数据
API.getOrderDetails(options).then(res => {
// 假设后端返回的数据结构是{ orderId: '...', products: [...], totalPrice: ... }
this.setData({
orderDetails: res.data // 根据实际返回的数据结构进行调整
});
}).catch(err => {
console.error('获取订单详情失败', err);
// 处理错误,例如显示错误信息给用户
});
},
// 其他页面逻辑...
});
3、根据获取到的数据渲染页面结构
使用数据绑定来渲染订单详情。例如,展示商品列表、总价等。
<view>
<view>订单编号:{{orderDetails.orderId}}</view>
<view wx:for="{{orderDetails.products}}" wx:key="unique">
<view>商品名称:{{item.name}}</view>
<view>商品数量:{{item.quantity}}</view>
<view>商品单价:{{item.price}}</view>
</view>
<view>订单总价:{{orderDetails.totalPrice}}</view>
<!-- 其他页面元素 -->
</view>
4、注意事项
- 确保你的API函数正确处理了请求和响应,并且能够返回你需要的订单详情数据。
- 在调用API函数时,可能需要传递一些参数,如用户ID、购物车ID等,以便后端能够正确返回对应的订单详情。
- 根据后端返回的数据结构,你可能需要调整
setData
中的数据结构以及WXML模板中的数据绑定。 - 考虑到网络请求的异步性,确保在页面加载时正确处理数据的加载状态,并在必要时给用户适当的反馈(如加载指示器)。
06. 商品结算-获取立即购买数据
1、在页面onShow
中获取传递的参数
当用户从商品详情页点击立即购买进入商品结算页面时,会携带商品的id
和可能的祝福语
。在结算页面的onShow
生命周期方法中,我们可以获取这些参数。
Page({
data: {
productId: null, // 商品ID
customBlessing: '', // 祝福语
productInfo: null, // 商品信息
// 其他数据...
},
onShow: function() {
// 获取传递的参数
const query = wx.getLaunchOptionsSync().query;
if (query.productId) {
this.setData({
productId: query.productId,
customBlessing: query.blessing || '' // 如果有祝福语也一同获取
});
this.getOrderInfo(); // 调用获取订单信息的函数
}
},
// ... 其他代码
});
2、调用接口获取立即购买商品的信息
在获取了商品ID之后,我们可以调用后端接口来获取该商品的信息。
// 假设API.getProductInfo是用来获取商品信息的接口函数
getOrderInfo: function() {
if (this.data.productId) {
API.getProductInfo(this.data.productId).then(res => {
if (res.data && res.data.product) {
this.setData({
productInfo: res.data.product // 假设返回的数据中包含product字段
});
// 如果需要,还可以在这里调用其他接口,如获取用户地址信息等
} else {
// 处理错误情况,如商品不存在等
}
}).catch(err => {
console.error('获取商品信息失败', err);
// 错误处理逻辑,如弹出提示等
});
} else {
// 商品ID不存在的处理逻辑
}
},
3、根据获取到的数据渲染页面结构
使用数据绑定来展示立即购买商品的基本信息
<view wx:if="{{productInfo}}">
<view>商品名称:{{productInfo.name}}</view>
<view>商品价格:{{productInfo.price}}</view>
<view>商品描述:{{productInfo.description}}</view>
<!-- 其他商品信息展示 -->
</view>
<view wx:else>
<!-- 商品信息未加载或不存在的提示 -->
</view>
4、注意事项
- 确保
API.getProductInfo
函数能够正确处理请求并返回所需的商品信息。 - 根据实际后端返回的数据结构,调整
setData
中的字段以及WXML模板中的数据绑定。 - 考虑到网络请求的异步性,正确处理数据加载状态,并在数据未加载完成时给用户适当的反馈。
- 如果商品详情页传递了祝福语,确保在结算页面适当位置展示。
七、订单列表
01. 封装订单列表接口 API
首先,我们创建一个名为api.js
的文件,用于存放所有的API函数。然后,在api.js
中,我们可以封装一个名为getOrderList
的函数,该函数将负责发送请求到后端以获取订单列表。
// api.js
const app = getApp();
// 封装获取订单列表的API函数
function getOrderList(data, successCallback, errorCallback) {
wx.request({
url: app.globalData.apiBaseUrl + '/order/list', // 假设后端接口地址是/order/list
method: 'GET', // 根据后端要求选择合适的请求方法,如GET、POST等
data: data, // 传递给后端的参数,比如分页信息、用户ID等
header: {
'content-type': 'application/json', // 根据后端要求设置合适的请求头
'Authorization': 'Bearer ' + wx.getStorageSync('token') // 假设需要token验证
},
success: function(res) {
if (res.statusCode === 200) {
// 请求成功且返回状态码为200时,调用成功回调
successCallback(res.data);
} else {
// 请求成功但返回状态码不是200时,调用错误回调
errorCallback(res);
}
},
fail: function(err) {
// 请求失败时,调用错误回调
errorCallback(err);
}
});
}
// 导出封装的API函数,以便其他文件可以使用
module.exports = {
getOrderList: getOrderList
// 可以继续添加其他API函数
};
在上面的代码中,我们定义了一个getOrderList
函数,该函数接受三个参数:data
(传递给后端的参数)、successCallback
(请求成功时的回调函数)、errorCallback
(请求失败或返回状态码非200时的回调函数)。函数内部使用wx.request
方法发送GET请求到后端的/order/list
接口,并根据响应的状态码调用相应的回调函数。
现在,你可以在其他的JS文件中引入并使用这个封装的API函数了。例如,在你的订单列表页面的JS文件中:
const API = require('../../utils/api'); // 引入封装的API函数
Page({
// ... 其他代码 ...
onLoad: function() {
this.getOrderList(); // 页面加载时调用获取订单列表的函数
},
getOrderList: function() {
const data = { /* 传递给后端的参数 */ };
API.getOrderList(data, (orderList) => {
// 请求成功时的处理逻辑,比如更新页面数据等
this.setData({ orders: orderList });
}, (error) => {
// 请求失败或返回状态码非200时的处理逻辑,比如弹出错误提示等
console.error('获取订单列表失败:', error);
});
},
// ... 其他代码 ...
});
02. 获取订单列表数据并渲染
1、在页面的JS文件中引入API
const API = require('../../utils/api'); // 路径根据实际情况调整
2、在页面的onLoad
或onShow
生命周期函数中调用API
当页面加载或显示时,调用API函数来获取订单列表数据。
Page({
data: {
orders: [], // 用于存储订单列表的数据
},
onLoad: function() {
this.getOrderList();
},
getOrderList: function() {
API.getOrderList({}, (res) => {
// 假设后端返回的数据结构为 { code: 0, data: [...] }
if (res.code === 0) {
this.setData({
orders: res.data // 更新页面数据
});
} else {
// 处理错误情况,比如显示错误信息
wx.showToast({
title: '获取订单列表失败',
icon: 'none'
});
}
}, (err) => {
// 网络请求错误处理
wx.showToast({
title: '网络请求失败',
icon: 'none'
});
console.error(err);
});
}
});
3、在WXML模板中渲染数据
使用wx:for
指令来遍历orders
数组,并将每个订单的信息渲染到页面上。
<!-- orders.wxml -->
<view class="order-list">
<block wx:for="{{orders}}" wx:key="index">
<view class="order-item">
<view>订单编号:{{item.orderNumber}}</view>
<view>下单时间:{{item.createTime}}</view>
<view>订单金额:{{item.totalPrice}}</view>
<!-- 其他订单信息 -->
</view>
</block>
</view>
4、样式和布局调整
在WXSS文件中添加相应的样式来调整订单列表的布局和外观。
/* orders.wxss */
.order-list {
padding: 10px;
}
.order-item {
background-color: #fff;
margin-bottom: 10px;
padding: 10px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
03. 订单列表上拉加载更多
1、在页面的JS文件中添加onReachBottom
方法
onReachBottom
方法是微信小程序提供的一个页面生命周期函数,当用户上拉触底时会自动触发。
Page({
data: {
orders: [], // 订单列表数据
page: 1, // 当前页码
limit: 10, // 每页加载的订单数量
// 其他数据...
},
onLoad: function() {
this.loadMoreOrders(this.data.page, this.data.limit);
},
onReachBottom: function() {
// 用户上拉触底时,页码加1,并重新加载数据
this.data.page++;
this.loadMoreOrders(this.data.page, this.data.limit);
},
loadMoreOrders: function(page, limit) {
// 调用API获取订单数据,并更新页面数据
API.getOrderList({ page: page, limit: limit }, (res) => {
if (res.code === 0) {
// 将新获取到的订单数据与旧的订单数据合并
let newOrders = this.data.orders.concat(res.data);
this.setData({ orders: newOrders });
} else {
wx.showToast({
title: '加载更多订单失败',
icon: 'none'
});
}
}, (err) => {
wx.showToast({
title: '网络请求失败',
icon: 'none'
});
console.error(err);
});
}
// 其他方法和逻辑...
});
2、确保API支持分页
你的后端API需要支持分页功能,并能够接收page
(页码)和limit
(每页数量)作为参数。当客户端发送请求时,API应根据这些参数返回相应页码的订单数据。
3、调整WXML模板以展示订单数据
你的WXML模板应该已经设置好了用于展示订单数据的结构,如上例所示。当新数据加载并合并到orders
数组中后,模板会自动更新以显示新的订单项。
4、优化用户体验
在加载更多数据时,你可能想要给用户一些反馈,比如显示一个加载指示器。你可以在loadMoreOrders
方法开始时显示加载指示器,并在数据成功加载后隐藏它。
04. 判断数据是否加载完毕
1、修改数据模型
首先,你需要在页面的 data
对象中添加一个字段来存储后端返回的订单总数(totalOrders
)以及当前已加载的订单数量(这个数量可以通过 orders.length
获得,因此不需要额外存储)。
Page({
data: {
orders: [], // 已加载的订单列表
totalOrders: 0, // 后端返回的订单总数
// 其他数据...
},
// ...
});
2、处理后端返回的数据
当从后端获取订单列表时,除了订单数据外,还应接收并存储订单的总数。
loadMoreOrders: function(page, limit) {
API.getOrderList({ page: page, limit: limit }, (res) => {
if (res.code === 0) {
// 假设后端返回的数据结构包含 total 和 list 字段
this.setData({
orders: page === 1 ? res.data.list : this.data.orders.concat(res.data.list),
totalOrders: res.data.total // 存储后端返回的订单总数
});
} else {
// 错误处理...
}
}, (err) => {
// 错误处理...
});
}
3、在 onReachBottom
中进行判断
在 onReachBottom
方法中,你可以通过比较 totalOrders
和 orders.length
来判断数据是否加载完毕。
onReachBottom: function() {
if (this.data.orders.length < this.data.totalOrders) {
// 数据还未加载完,继续加载更多
this.data.page++; // 注意:这里的 page 应该用 this.setData 更新,或者在外部维护状态
this.loadMoreOrders(this.data.page, this.data.limit);
} else {
// 数据已加载完,可以停止监听上拉事件或给出提示
wx.showToast({
title: '没有更多订单了',
icon: 'none',
duration: 2000
});
}
}
4、注意事项
- 确保在
onLoad
或其他初始化方法中调用loadMoreOrders
以加载第一页数据。 - 如果你的页面同时支持下拉刷新和上拉加载更多,你可能需要在下拉刷新时重置
page
为 1。 - 考虑到用户体验,可以在页面上添加一个加载指示器,在数据加载时显示,加载完成后隐藏。
- 如果后端API的分页逻辑不是基于页码(
page
),而是基于游标(如上一次请求的最后一个订单ID),则需要相应调整请求参数和数据处理逻辑。