项目过程笔记

一、框架扩展

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-miniprogrammobx-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],并在datacomputedwatch等选项中定义相应的属性和方法。

注意事项

  • 在computed函数中,不能访问this,只能访问data对象。
  • 如果需要访问this或执行更复杂的逻辑,应该使用watch代替computed。
  • watch和computed的使用应该根据具体场景和需求来决定,以达到最优的性能和效果。

二、用户管理

01. 用户登录-什么是 token

定义与用途:

  • Token在微信小程序中扮演着类似于“钥匙”或“身份证”的角色。它是客户端与服务端之间通信的凭证,确保请求的来源是合法且经过验证的。
  • 通过获取Token,开发者可以安全地调用微信小程序的API接口,实现各种功能,如获取用户信息、发起支付等。

获取方式:

  • Token的获取通常涉及与微信服务器进行交互。开发者需要在小程序的后端服务器上,通过调用微信提供的API接口(如wx.logingetAccessToken),以换取有效的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.redirectTowx.reLaunch)返回到之前的页面(如个人中心)。
  • 在返回到原页面时,需要更新页面的登录状态。这可以通过从本地存储中读取登录凭据,并更新页面的UI来实现。

注意事项:

  • 确保在发送敏感信息(如用户名、密码、code等)到后端服务器时,使用HTTPS协议进行加密传输,以保证数据的安全性。
  • 在后端验证登录凭据时,应确保验证逻辑的安全性,避免被恶意攻击者利用。
  • 在前端保存登录凭据时,建议使用加密方式存储,并限制访问权限,以防止凭据被恶意获取。
  • 在用户退出登录时,需要清除本地存储中的登录凭据,以确保用户下次访问时处于未登录状态。

04. 用户登录-token 存储到 Store

实现步骤:

1、安装MobX

首先,需要在项目中安装mobxmobx-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提供的injectobserver高阶组件来连接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存储到了MobXstore中,并在组件中实现了对token的响应式管理。当tokenstore中发生变化时,所有使用到这个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、在组件中使用用户信息

在需要使用用户信息的组件中,通过injectobserver来连接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数组来存储选中的省市区值,而不是分别使用provinceNamecityNamedistrictName

2、在JS文件中处理change事件

在对应的JS文件中,需要定义onAddressChange函数来处理pickerchange事件。这个函数会接收一个参数,该参数是一个数组,包含了用户选择的省市区信息。

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介绍

  1. wx.getLocation():用于获取当前的地理位置、速度等信息。该接口需要用户主动触发,在获取用户地理位置时需要得到用户的同意。

  2. wx.chooseLocation():在地图上选择位置。调用此接口会打开地图界面,允许用户选择地图上的任意位置。

2、申请开通

由于地理定位功能涉及用户隐私,微信对其使用进行了限制。并不是所有小程序都可以使用地理定位功能,它暂时只对部分类目的小程序开放。如果你的小程序需要使用地理定位功能,你需要:

  1. 确保你的小程序属于开放的类目范围内。
  2. 通过小程序管理后台提交类目审核,并确保审核通过。
  3. 在小程序管理后台的「开发」-「开发管理」-「接口设置」中自助开通该接口权限。

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 中进行对比

onReachBottomloadMoreGoods方法中,你可以添加一个判断来检查是否已经加载了所有商品。

loadMoreGoods: function() {  
  // ... 省略其他代码 ...  
    
  if (this.data.goodsList.length >= this.data.total) {  
    // 如果已加载的商品数量大于或等于总数,说明数据已全部加载完毕  
    wx.showToast({  
      title: '数据加载完毕',  
      icon: 'none',  
      duration: 2000  
    });  
    return; // 不再发起请求加载更多数据  
  }  
    
  // 准备新的请求参数并发送请求  
  // ...  
  // 发起请求加载更多数据的代码  
}

3、模板中使用对比结果

在WXML模板中,你也可以根据goodsList.lengthtotal的对比结果来决定是否显示“数据加载完毕”的提示。

<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

首先,我们需要确定购物车接口的基本功能,包括:

  1. 添加到购物车:将商品添加到购物车中。
  2. 从购物车中移除商品:根据商品ID从购物车中移除特定商品。
  3. 更新购物车商品数量:修改购物车中特定商品的数量。
  4. 获取购物车列表:获取当前用户的购物车中所有商品。
  5. 清空购物车:移除购物车中的所有商品。
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

在购物车组件的lifetimesmethods中,你可以添加逻辑来访问和更新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、封装流程

  1. 确定支付服务提供商的API要求
    了解并熟悉你所使用的支付服务提供商(如微信支付)的API文档,包括参数要求、请求方式、响应格式等。

  2. 创建支付服务类
    在微信小程序的项目中,创建一个支付服务类,用于封装与支付服务提供商的交互。

  3. 实现支付接口方法
    在支付服务类中,根据支付服务提供商的API要求,实现支付请求、支付结果查询等接口方法。

  4. 处理异常和错误
    在接口方法中,添加异常处理和错误返回机制,确保调用方能够正确处理支付过程中可能出现的问题。

  5. 测试与验证
    在实际环境中测试封装的支付接口,确保其功能正确且稳定。

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、在页面的onLoadonShow生命周期函数中调用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),则需要相应调整请求参数和数据处理逻辑。
  • 16
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值