在应用程序侧集成SDK

  1. 设计应用程序的登录系统、游戏内商店及其他页面的界面。
  2. 使用SDK方法在应用程序中实现用户认证、商店显示、购买及其他逻辑。
注:
您可以根据Cocos Creator手册或以演示场景为模板来创建自己的解决方案。
要使用基础SDK功能,请按照以下分步教程进行操作:可在SDK的<xsollaExtention>/assets/scripts/samples目录下找到使用的脚本。
注:
教程中的脚本使用SDK方法。请确保正确安装初始化了SDK。

通过用户名、邮箱和密码进行用户注册和登录

本说明解释如何使用SDK方法实现以下内容:

您可以使用用户名或邮箱地址来认证用户身份。以下示例中,我们使用用户的用户名来进行认证,使用邮箱地址来进行注册验证及重置密码。

注:
如果您在网站上(例如网页商城)使用了登录管理器小组件,请确保该网站与您的应用程序实现相同的用户认证方法。小组件默认使用邮箱地址进行认证。如要设置通过用户名进行用户登录,请联系您的客户成功经理或发送邮件至csm@xsolla.com

实现用户注册

本教程介绍以下逻辑的实现:

创建页面界面

为注册页面创建一个场景并添加以下元素:

  • 用户名字段
  • 用户邮箱地址字段
  • 用户密码字段
  • 注册按钮

页面结构示例:

创建注册脚本组件

  1. 创建一个RegistrationComponent并添加以下属性:
    • usernameEditBox
    • emailEditBox
    • passwordEditBox
    • signUpButton — 可选。用代码将回调函数与该按钮绑定时使用

  1. 将方法添加到点击SignUpButton时调用的RegistrationComponent类,并添加处理该点击事件的逻辑,如脚本示例中所示。
  2. 在场景中将RegistrationComponent添加到节点。可添加新节点或使用SDK初始化期间添加的含XsollaSettingsManager组件的现有节点。
  3. 将场景元素与RegistrationComponent的属性绑定,如图所示:

  1. 通过以下方式之一将回调与注册按钮绑定
    • 使用Inspector面板,如下图所示
    • 将下方代码块插入页面脚本

使用Inspector面板绑定回调:

通过代码绑定回调:

Copy
Full screen
Small screen
start() {
 this.signUpButton.node.on(Button.EventType.CLICK, this.onSignUpClicked, this);
 }
注:

在脚本示例中,onCompleteonError方法调用标准console.log方法。发生错误时,错误代码和描述在error参数中传递。

您还可以添加其他动作,例如打开一个包含重新发送注册邮件请求的页面或在注册成功时打开一个登录页面。

类脚本示例(RegistrationComponent):
Copy
Full screen
Small screen
import { _decorator, Button, Component, EditBox } from 'cc';
import { XsollaAuth } from 'db://xsolla-commerce-sdk/scripts/api/XsollaAuth';
const { ccclass, property } = _decorator;


@ccclass('RegistrationComponent')
export class RegistrationComponent extends Component {

    @property(EditBox)
    usernameEditBox: EditBox;

    @property(EditBox)
    emailEditBox: EditBox;

    @property(EditBox)
    passwordEditBox: EditBox;

    @property(Button)
    signUpButton: Button;

    start() {
        this.signUpButton.node.on(Button.EventType.CLICK, this.onSignUpClicked, this);
    }

    onSignUpClicked() {
        XsollaAuth.registerNewUser(this.usernameEditBox.string, this.passwordEditBox.string, this.emailEditBox.string, 'xsollatest', null, null, token => {
            if(token != null) {
                console.log(`Successful login. Token - ${token.access_token}`);
            }
            else {
                console.log('Thank you! We have sent you a confirmation email');
            }
        }, err => {
            console.log(err);
        });
    }
}

设置注册验证邮件

成功注册后,用户将在指定邮箱中收到一封注册验证邮件。您可以在发布商帐户中自定义发送给用户的邮件

如果您开发的是移动应用程序,请设置深度链接以在用户验证注册后将用户返回到应用程序。

注:
如果您安全性标准允许,可禁用通过邮箱地址进行注册验证。请联系您的客户成功经理进行禁用或发送邮件至csm@xsolla.com与我们联系。

实现重新发送注册验证邮件请求

本教程介绍以下逻辑的实现:

创建页面界面

为包含重新发送验证邮件请求的页面创建一个场景并添加以下元素:

  • 用户名/邮箱字段
  • 重新发送邮件按钮

页面结构示例:

创建重新发送邮件脚本组件

  1. 创建一个ResendConfirmationEmailComponent并添加以下属性:
    • usernameTextBox
    • resendEmailButton — 可选。用代码将回调函数与该按钮绑定时使用

  1. 将方法添加到点击ResendEmail时调用的ResendConfirmationEmailComponent类,并添加处理该点击事件的逻辑,如脚本示例中所示。
  2. 在场景中将ResendConfirmationEmailComponent添加到节点。可添加新节点或使用SDK初始化期间添加的含XsollaSettingsManager组件的现有节点。
  3. 将场景元素与ResendConfirmationEmailComponent的属性绑定,如图所示:

  1. 通过以下方式之一将回调与请求注册验证邮件按钮绑定:
    • 使用Inspector面板,如下图所示
    • 将下方代码块插入请求重新发送验证邮件的页面脚本

使用Inspector面板绑定回调:

通过代码绑定回调:

Copy
Full screen
Small screen
start() {
	this.resendEmailButton.node.on(Button.EventType.CLICK, this.onResendEmailClicked, this);
  }

如果请求成功,用户将在注册时指定的邮箱中收到一封注册验证邮件。

注:

在脚本示例中,onCompleteonError方法调用标准console.log方法。您还可以添加其他动作。

发生错误时,错误代码和描述在error参数中传递。

类脚本示例(ResendConfirmationEmailComponent):
Copy
Full screen
Small screen
import { _decorator, Button, Component, EditBox } from 'cc';
import { XsollaAuth } from 'db://xsolla-commerce-sdk/scripts/api/XsollaAuth';
const { ccclass, property } = _decorator;


@ccclass('ResendConfirmationEmailComponent')
export class ResendConfirmationEmailComponent extends Component {

    @property(EditBox)
    usernameEditBox: EditBox;

    @property(Button)
    resendEmailButton: Button;

    start() {
        this.resendEmailButton.node.on(Button.EventType.CLICK, this.onResendEmailClicked, this);
    }

    onResendEmailClicked() {
        XsollaAuth.resendAccountConfirmationEmail(this.usernameEditBox.string, 'xsollatest', null, () => {
            console.log('A verification link has been successfully sent to your email');
        }, err => {
            console.log(err);
        });
    }
}

实现用户登录

本教程介绍以下逻辑的实现:

创建页面界面

为登录页面创建一个场景并添加以下元素:

  • 用户名字段
  • 密码字段
  • “记住我”开关
  • 登录按钮

页面结构示例:

创建登录脚本组件

  1. 创建一个LoginComponent并添加以下属性:
    • usernameEditBox
    • passwordEditBox
    • remeberMeToggle
    • loginButton — 可选。用代码将回调函数与该按钮绑定时使用

  1. 将方法添加到点击LoginButton时调用的LoginComponent类,并添加处理该点击事件的逻辑,如脚本示例中所示。
  2. 在场景中将LoginComponent添加到节点。可添加新节点或使用SDK初始化期间添加的含XsollaSettingsManager组件的现有节点。
  3. 将场景元素与LoginComponent的属性绑定,如图所示:

  1. 通过以下方式之一将回调与登录按钮绑定:
    • 使用Inspector面板,如下图所示
    • 将下方代码块插入页面脚本

使用Inspector面板绑定回调:

通过代码绑定回调:

Copy
Full screen
Small screen
start() {
	 this.loginButton.node.on(Button.EventType.CLICK, this.onLoginClicked, this);
  }
注:

在脚本示例中,onCompleteonError方法调用标准console.log方法。您还可以添加其他动作。

发生错误时,错误代码和描述在error参数中传递。

类脚本示例(LoginComponent):
Copy
Full screen
Small screen
import { _decorator, Button, Component, EditBox, Toggle } from 'cc';
import { XsollaAuth } from 'db://xsolla-commerce-sdk/scripts/api/XsollaAuth';
const { ccclass, property } = _decorator;


@ccclass('LoginComponent')
export class LoginComponent extends Component {

    @property(EditBox)
    usernameEditBox: EditBox;

    @property(EditBox)
    passwordEditBox: EditBox;

    @property(Toggle)
    remeberMeToggle: Toggle;

    @property(Button)
    loginButton: Button;

    start() {
        this.loginButton.node.on(Button.EventType.CLICK, this.onLoginClicked, this);
    }

    onLoginClicked() {
        XsollaAuth.authByUsernameAndPassword(this.usernameEditBox.string, this.passwordEditBox.string, this.remeberMeToggle.isChecked, token => {
            console.log('Successful login. Token - ${token.access_token}');
        }, err => {
            console.log(err);
        });
    }
}

实现密码重置

本教程介绍以下逻辑的实现:

创建页面界面

为重置密码页面创建一个场景并添加以下元素:

  • 用户名字段
  • 重置密码按钮

页面结构示例:

创建重置密码脚本组件

  1. 创建一个ResetPasswordComponent并添加以下属性:
    • usernameEditBox
    • resetPasswordButton — 可选。用代码将回调函数与该按钮绑定时使用

  1. 将方法添加到点击ResetPassword时调用的ResetPasswordComponent类,并添加处理该点击事件的逻辑,如脚本示例中所示。
  2. 在场景中将ResetPasswordComponent添加到节点。可添加新节点或使用SDK初始化期间添加的含XsollaSettingsManager组件的现有节点。
  3. 将场景元素与ResetPasswordComponent的属性绑定,如图所示:

  1. 通过以下方式之一将回调与密码重置按钮绑定:
    • 使用Inspector面板,如下图所示
    • 将下方代码块插入页面脚本

使用Inspector面板绑定回调:

通过代码绑定回调:

Copy
Full screen
Small screen
start() {
        this.resetPasswordButton.node.on(Button.EventType.CLICK, this.onResetPasswordClicked, this);
    }
注:

在脚本示例中,onCompleteonError方法调用标准console.log方法。您还可以添加其他动作。

发生错误时,错误代码和描述在error参数中传递。

类脚本示例(ResetPasswordComponent):
Copy
Full screen
Small screen
import { _decorator, Button, Component, EditBox } from 'cc';
import { XsollaAuth } from 'db://xsolla-commerce-sdk/scripts/api/XsollaAuth';
const { ccclass, property } = _decorator;


@ccclass('ResetPasswordComponent')
export class ResetPasswordComponent extends Component {

    @property(EditBox)
    usernameEditBox: EditBox;

    @property(Button)
    resetPasswordButton: Button;

    start() {
        this.resetPasswordButton.node.on(Button.EventType.CLICK, this.onResetPasswordClicked, this);
    }

    onResetPasswordClicked() {
        XsollaAuth.resetPassword(this.usernameEditBox.string, null, () => {
            console.log('Follow the instructions we sent to your email');
        }, err => {
            console.log(err);
        });
    }
}

社交网络登录

注:
Cocos Creator专用SDK仅对Android和iOS编译版本支持社交网络登录。

本指南说明如何使用SDK方法实现用户通过社交网络帐户进行注册和登录。

通过用户名/邮箱地址和密码进行用户认证不同,您无需实现单独的用户注册逻辑。如果用户的首次登录是通过社交网络进行,将自动创建一个帐户。

如果您在应用程序中将社交网络登录设置为一种备选认证方法,则在满足下列条件的情况下,社交网络帐户将自动与现有用户帐户关联:

  • 使用用户名/邮箱地址和密码注册的用户通过社交网络帐户登录您的应用程序。
  • 社交网络返回一个邮箱地址。
  • 社交网络帐户中用户邮箱地址与其注册您的应用程序时使用的邮箱地址一致。

本教程介绍以下逻辑的实现:

示例展示了如何设置通过Facebook帐户进行用户登录。您可以用相同的方法设置其他社交网络。

示例提供对SDK方法的基本展示。实际应用通常需要更复杂的界面和逻辑。

创建页面界面

为社交网络登录页创建一个场景并在其中添加社交网络登录按钮。

页面结构示例:

创建社交网络登录脚本组件

  1. 创建一个SocialLoginComponent。要通过代码将回调函数与社交网络登录按钮绑定,请添加socialLoginButton属性。
  2. 将方法添加到点击SocialLogin时调用的SocialLoginComponent类,并添加处理该点击事件的逻辑,如脚本示例中所示。

  1. 在场景中将SocialLoginComponent添加到节点。可添加新节点或使用SDK初始化期间添加的含XsollaSettingsManager组件的现有节点。
  2. SocialLogin按钮与SocialLoginComponentsocialLoginButton属性绑定,如图所示:

  1. 通过以下方式之一将回调与社交网络登录按钮绑定
    • 使用Inspector面板,如下图所示
    • 将下方代码块插入页面脚本

使用Inspector面板绑定回调:

通过代码绑定回调:

Copy
Full screen
Small screen
start() {
        this.socialLoginButton.node.on(Button.EventType.CLICK, this.onSocialLoginClicked, this);
}
注:

在脚本示例中,onCompleteonCanceledonError方法调用标准console.log方法。发生错误时,错误代码和描述在error参数中传递。

您还可以添加其他动作,例如打开一个包含重新发送注册邮件请求的页面或在注册成功时打开一个登录页面。

类脚本示例(SocialLoginComponent):
Copy
Full screen
Small screen
import { _decorator, Button, Component } from 'cc';
import { Token, XsollaAuth } from '../../api/XsollaAuth';
const { ccclass, property } = _decorator;


namespace authorization {

    @ccclass('SocialLoginComponent')
    export class SocialLoginComponent extends Component {

        @property(Button)
        socialLoginButton: Button;


        start() {
            this.socialLoginButton.node.on(Button.EventType.CLICK, this.onSocialLoginClicked, this);
        }


        onSocialLoginClicked() {
            XsollaAuth.authSocial('facebook', (token: Token) => {
                console.log(`Successful social authentication. Token - ${token.access_token}`);
            }, () => {
                console.log('Social auth was canceled');
            }, (error:string) => {
                console.log(error);
            });
        }
    }
}

显示商品目录

本教程介绍如何使用SDK方法在游戏内商店中显示以下商品:

  • 虚拟物品
  • 捆绑包
  • 虚拟货币套餐

开始之前,请在发布商帐户中配置商品:

  1. 配置虚拟物品和虚拟物品组
  2. 配置虚拟货币套餐
  3. 配置捆绑包

本教程介绍以下逻辑的实现:

注:

目录中的每个商品示例显示以下内容:

  • 名称
  • 描述
  • 价格
  • 图片

如果游戏内商店中还存储了其他信息,也可以显示那些信息。

实现虚拟物品的显示

创建物品小组件

  1. 创建一个预制件。方法是从文件夹的快捷菜单中选择Create > Node Prefab
  2. 打开创建的预制件。
  3. UITransform组件添加到预制件的根,如图所示,并设置内容尺寸。

  1. 添加以下UI元素作为预制件的子对象并配置其视效:
    • 小组件背景图片
    • 商品名称
    • 商品描述
    • 商品价格
    • 商品图片

小组件结构的示例:

创建商品小组件脚本

  1. 创建一个StoreItemComponent并添加以下属性:
    • iconSprite
    • itemNameLabel
    • itemDescriptionLabel
    • priceLabel

  1. init方法和初始化逻辑添加到StoreItemComponent类,如脚本示例中所示。
  2. 将StoreItemComponent组件附加到预制件的根节点
  3. 将预制件元素与StoreItemComponent的属性绑定,如图所示:

小组件脚本示例(StoreItemComponent):

Copy
Full screen
Small screen
import { _decorator, assetManager, Component, ImageAsset, Label, Sprite, SpriteFrame, Texture2D } from 'cc';
import { StoreItem } from 'db://xsolla-commerce-sdk/scripts/api/XsollaCatalog';
const { ccclass, property } = _decorator;


@ccclass('StoreItemComponent')
export class StoreItemComponent extends Component {

    @property(Sprite)
    iconSprite: Sprite;

    @property(Label)
    itemNameLabel: Label;

    @property(Label)
    itemDescriptionLabel: Label;

    @property(Label)
    priceLabel: Label;

    private _data: StoreItem;

    init(data: StoreItem) {

        this._data = data;

        this.itemNameLabel.string = data.name;
        this.itemDescriptionLabel.string = data.description;

        if (data.virtual_prices.length > 0) {
            this.priceLabel.string = data.virtual_prices[0].amount.toString() + ' ' + data.virtual_prices[0].name;
        } else {
            this.priceLabel.string = parseFloat(data.price.amount) + ' ' + data.price.currency;
        }

        assetManager.loadRemote<ImageAsset>(data.image_url, (err, imageAsset) => {
            if(err == null) {
            const spriteFrame = new SpriteFrame();
            const texture = new Texture2D();
            texture.image = imageAsset;
            spriteFrame.texture = texture;
            this.iconSprite.spriteFrame = spriteFrame;
            } else {
                console.log(`Can’t load image with URL ${data.image_url}`);
            }
        });
    }
}

创建页面界面

为物品目录页面创建一个场景,并向其添加ScrollView元素。

页面结构示例:

要让ScrollView与创建的StoreItem预制件匹配,请设置其大小:

  1. 更改ScrollView节点和内部view节点中ContentSize参数的值。
  2. Layout组件与content节点绑定并完成设置。示例中选择了以下设置:
    • Type == vertical
    • ResizeMode == Container

创建物品目录脚本组件

  1. 创建一个ItemsCatalogComponent并添加以下属性
    • itemsScrollView
    • storeItemPrefab

  1. start生命周期函数和初始化逻辑添加到ItemsCatalogComponent类,如脚本示例中所示。
  2. 在场景中将ItemsCatalogComponent添加到节点。可添加新节点或使用SDK初始化期间添加的含XsollaSettingsManager组件的现有节点。
  3. 将预制件元素与ItemsCatalogComponent的属性绑定,如图所示:

类脚本示例(ItemsCatalogComponent):

Copy
Full screen
Small screen
import { _decorator, Component, instantiate, Prefab, ScrollView } from 'cc';
import { XsollaCatalog } from 'db://xsolla-commerce-sdk/scripts/api/XsollaCatalog';
import { StoreItemComponent } from './StoreItemComponent';
const { ccclass, property } = _decorator;


@ccclass('ItemsCatalogComponent')
export class ItemsCatalogComponent extends Component {

    @property(ScrollView)
    itemsScrollView: ScrollView;

    @property(Prefab)
    storeItemPrefab: Prefab;

    start() {
        XsollaCatalog.getCatalog(null, null, [], itemsData => {
            for (let i = 0; i < itemsData.items.length; ++i) {
                let storeItem = instantiate(this.storeItemPrefab);
                this.itemsScrollView.content.addChild(storeItem);
                storeItem.getComponent(StoreItemComponent).init(itemsData.items[i]);
            }
        });
    }
}

脚本工作结果示例:

实现捆绑包的显示

创建捆绑包小组件

  1. 创建一个预制件。方法是从文件夹的快捷菜单中选择Create > Node Prefab
  2. 打开创建的预制件。
  3. UITransform组件添加到预制件的根,如图所示,并设置内容尺寸。

  1. 添加以下UI元素作为预制件的子对象并配置其视效:
    • 小组件背景图片
    • 捆绑包名称
    • 捆绑包描述
    • 捆绑包价格
    • 捆绑包图片
    • 捆绑包内容描述(商品及其数量)

小组件结构的示例:

创建捆绑包小组件脚本

  1. 创建一个BundleItemComponent并添加以下属性:
    • iconSprite
    • bundleNameLabel
    • bundleDescriptionLabel
    • priceLabel
    • contentDescriptionlabel

  1. init方法和初始化逻辑添加到BundleItemComponent类,如脚本示例中所示。
  2. 将BundleItemComponent添加到预制件的根节点
  3. 将预制件元素与BundleItemComponent的属性绑定,如图所示:

小组件脚本示例(BundleItemComponent):

Copy
Full screen
Small screen
import { _decorator, assetManager, Component, ImageAsset, Label, Sprite, SpriteFrame, Texture2D } from 'cc';
import { StoreBundle } from 'db://xsolla-commerce-sdk/scripts/api/XsollaCatalog';
const { ccclass, property } = _decorator;


@ccclass('BundleItemComponent')
export class BundleItemComponent extends Component {

    @property(Sprite)
    iconSprite: Sprite;

    @property(Label)
    bundleNameLabel: Label;

    @property(Label)
    bundleDescriptionLabel: Label;

    @property(Label)
    priceLabel: Label;

    @property(Label)
    contentDescriptionLabel: Label;

    init(data: StoreBundle) {

        this.bundleNameLabel.string = data.name;
        this.bundleDescriptionLabel.string = data.description;

        if (data.virtual_prices.length > 0) {
            this.priceLabel.string = data.virtual_prices[0].amount.toString() + ' ' + data.virtual_prices[0].name;
        } else {
            this.priceLabel.string = parseFloat(data.price.amount) + ' ' + data.price.currency;
        }

        assetManager.loadRemote<ImageAsset>(data.image_url, (err, imageAsset) => {
            if(err == null) {
            const spriteFrame = new SpriteFrame();
            const texture = new Texture2D();
            texture.image = imageAsset;
            spriteFrame.texture = texture;
            this.iconSprite.spriteFrame = spriteFrame;
            } else {
                console.log(`Can’t load image with URL ${data.image_url}`);
            }
        });

        this.contentDescriptionLabel.string = 'This bundle includes '+ data.content.length + ' items: ';
        var bandles = data.content.map(bundle => bundle.name).join(', ');
        this.contentDescriptionLabel.string += bandles;
    }
}

创建页面界面

为捆绑包目录页面创建一个场景,并向其添加ScrollView元素。

页面结构示例:

要让ScrollView与创建的BundleItem预制件匹配,请设置其大小:

  1. 更改ScrollView节点和内部view节点中ContentSize参数的值。
  2. Layout组件与content节点绑定并完成设置。示例中选择了以下设置:
    • Type == vertical
    • ResizeMode == Container

创建捆绑包目录脚本组件

  1. 创建一个BundlesCatalogComponent并添加以下属性:
    • itemsScrollView
    • bundleItemPrefab

  1. start生命周期函数和初始化逻辑添加到BundlesCatalogComponent类,如脚本示例中所示。
  2. 在场景中将BundlesCatalogComponent添加到节点。可添加新节点或使用SDK初始化期间添加的含XsollaSettingsManager组件的现有节点。
  3. 将预制件元素与BundlesCatalogComponent的属性绑定,如图所示:

类脚本示例(BundlesCatalogComponent):

Copy
Full screen
Small screen
import { _decorator, Component, instantiate, Prefab, ScrollView } from 'cc';
import { XsollaCatalog } from 'db://xsolla-commerce-sdk/scripts/api/XsollaCatalog';
import { BundleItemComponent } from './BundleItemComponent';
const { ccclass, property } = _decorator;


@ccclass('BundlesCatalogComponent')
export class BundlesCatalogComponent extends Component {

    @property(ScrollView)
    itemsScrollView: ScrollView;

    @property(Prefab)
    bundleItemPrefab: Prefab;

    start() {
        XsollaCatalog.getBundleList(null, null, [], itemsData => {
        for (let i = 0; i < itemsData.items.length; ++i) {
                let bundleItem = instantiate(this.bundleItemPrefab);
                this.itemsScrollView.content.addChild(bundleItem);
                bundleItem.getComponent(BundleItemComponent).init(itemsData.items[i]);
            }
        });
    }
}

脚本工作结果示例:

实现虚拟货币套餐目录的显示

创建虚拟货币套餐的小组件

  1. 创建一个预制件。方法是从文件夹的快捷菜单中选择Create > Node Prefab
  2. 打开创建的预制件。
  3. UITransform组件添加到预制件的根,如图所示,并设置内容尺寸。

  1. 添加以下UI元素作为预制件的子对象并配置其视效:
    • 小组件背景图片
    • 货币名称
    • 货币描述
    • 货币价格
    • 货币图片

小组件结构的示例:

创建虚拟货币套餐的小组件脚本

  1. 创建一个CurrencyPackageItemComponent并添加以下属性:
    • iconSprite
    • currencyNameLabel
    • currencyDescriptionLabel
    • priceLabel

  1. init方法和初始化逻辑添加到CurrencyPackageItemComponent类,如脚本示例中所示。
  2. 将CurrencyPackageItemComponent组件附加到预制件的根节点
  3. 将预制件元素与CurrencyPackageItemComponent的属性绑定,如图所示:

小组件脚本示例(CurrencyPackageItemComponent):

Copy
Full screen
Small screen
import { _decorator, assetManager, Component, ImageAsset, Label, Sprite, SpriteFrame, Texture2D } from 'cc';
import { VirtualCurrencyPackage } from 'db://xsolla-commerce-sdk/scripts/api/XsollaCatalog';
const { ccclass, property } = _decorator;


@ccclass('CurrencyPackageItemComponent')
export class CurrencyPackageItemComponent extends Component {

    @property(Sprite)
    iconSprite: Sprite;

    @property(Label)
    currencyNameLabel: Label;

    @property(Label)
    currencyDescriptionLabel: Label;

    @property(Label)
    priceLabel: Label;

    init(data: VirtualCurrencyPackage) {

        this.currencyNameLabel.string = data.name;
        this.currencyDescriptionLabel.string = data.description;

        if (data.virtual_prices.length > 0) {
            this.priceLabel.string = data.virtual_prices[0].amount.toString() + ' ' + data.virtual_prices[0].name;
        } else {
            this.priceLabel.string = parseFloat(data.price.amount) + ' ' + data.price.currency;
        }

        assetManager.loadRemote<ImageAsset>(data.image_url, (err, imageAsset) => {
            if(err == null) {
            const spriteFrame = new SpriteFrame();
            const texture = new Texture2D();
            texture.image = imageAsset;
            spriteFrame.texture = texture;
            this.iconSprite.spriteFrame = spriteFrame;
            } else {
                console.log(`Can’t load image with URL ${data.image_url}`);
            }
        });
    }
}

创建页面界面

为虚拟货币套餐目录页面创建一个场景,并向其添加ScrollView元素。

页面结构示例:

要让ScrollView与创建的CurrencyPackageItem预制件匹配,请设置其大小:

  1. 更改ScrollView节点和内部view节点中ContentSize参数的值。
  2. Layout组件与content节点绑定并完成设置。示例中选择了以下设置:
    • Type == vertical
    • ResizeMode == Container

创建货币套餐目录脚本组件

  1. 创建一个CurrencyPackagesCatalogComponent并添加以下属性:
    • itemsScrollView
    • currencyPackageItemPrefab

  1. start生命周期函数和初始化逻辑添加到CurrencyPackagesCatalogComponent类,如脚本示例中所示。
  2. 在场景中将CurrencyPackagesCatalogComponent添加到节点。可添加新节点或使用SDK初始化期间添加的含XsollaSettingsManager组件的现有节点。
  3. 将预制件元素与CurrencyPackagesCatalogComponent的属性绑定,如图所示:

类脚本示例(CurrencyPackagesCatalogComponent):

Copy
Full screen
Small screen
    import { _decorator, Component, instantiate, Prefab, ScrollView } from 'cc';
    import { XsollaCatalog } from 'db://xsolla-commerce-sdk/scripts/api/XsollaCatalog';
    import { CurrencyPackageItemComponent } from './CurrencyPackageItemComponent';
    const { ccclass, property } = _decorator;
    
    
    @ccclass('CurrencyPackagesCatalogComponent')
    export class CurrencyPackagesCatalogComponent extends Component {
    
        @property(ScrollView)
        itemsScrollView: ScrollView;
    
        @property(Prefab)
        currencyPackageItemPrefab: Prefab;
    
        start() {
            XsollaCatalog.getVirtualCurrencyPackages(null, null, [], itemsData => {
            for (let i = 0; i < itemsData.items.length; ++i) {
                    let currencyPackageItem = instantiate(this.currencyPackageItemPrefab);
                    this.itemsScrollView.content.addChild(currencyPackageItem);
                    currencyPackageItem.getComponent(CurrencyPackageItemComponent).init(itemsData.items[i]);
                }
            });
        }
    }
    

    脚本工作结果示例:

    以真实货币的形式销售虚拟物品

    本节介绍如何通过SDK方法实现以真实货币形式销售虚拟物品。

    开始之前,需实现在目录中显示虚拟物品。下方示例中我们描述了如何实现虚拟物品的购买。其他商品类型的配置与此类似。

    本教程介绍以下逻辑的实现:

    注意

    在登录脚本示例中我们使用的是演示帐户的凭证(用户名:xsolla,密码:xsolla)。该帐户仅适用于演示项目。

    脚本示例不包含按页显示目录中商品(分页)的实现。请使用getCatalog SDK方法的offsetlimit参数来实现分页。每页最大商品数为50。如目录中的商品数超过50,则需要分页。

    完成商品小组件

    在商品小组件中添加购买按钮,并配置其显示元素。
    注:
    在示例中,修改的类更改为RC_StoreItemComponent
    小组件结构的示例:

    完成商品小组件脚本

    1. 要通过代码将回调函数与购买按钮绑定,请向RC_StoreItemComponent添加buyButton属性。
    2. 将方法添加到点击BuyButton时调用的RC_StoreItemComponent类,并添加处理该点击事件的逻辑,脚本示例中所示。
    3. 通过以下方式之一:将回调与购买按钮绑定

      • 使用Inspector面板,如下图所示
      • 将下方代码块插入页面脚本

    使用Inspector面板绑定回调:

    通过代码绑定回调:

    Copy
    Full screen
    Small screen
    start() {
    	this.buyButton.node.on(Button.EventType.CLICK, this.onBuyClicked, this);
      }
    

    小组件脚本示例(RC_StoreItemComponent):

    Copy
    Full screen
    Small screen
    import { _decorator, assetManager, Button, Component, ImageAsset, Label, Sprite, SpriteFrame, Texture2D } from 'cc';
    import { StoreItem, XsollaCatalog } from '../../api/XsollaCatalog';
    import { TokenStorage } from '../../common/TokenStorage';
    import { OrderTracker } from '../../common/OrderTracker';
    import { XsollaPayments } from '../../api/XsollaPayments';
    const { ccclass, property } = _decorator;
    
    
    export namespace sellForRealMoneyItem {
    
    
        @ccclass('RC_StoreItemComponent')
        export class RC_StoreItemComponent extends Component {
    
    
            @property(Sprite)
            iconSprite: Sprite;
    
    
            @property(Label)
            itemNameLabel: Label;
    
    
            @property(Label)
            itemDescriptionLabel: Label;
    
    
            @property(Label)
            priceLabel: Label;
    
    
            @property(Button)
            buyButton: Button;
    
    
            private _data: StoreItem;
    
    
            start() {
                this.buyButton.node.on(Button.EventType.CLICK, this.onBuyClicked, this);
            }
    
    
            init(data: StoreItem) {
    
                this._data = data;
    
    
                this.itemNameLabel.string = data.name;
                this.itemDescriptionLabel.string = data.description;
    
                if (data.virtual_prices.length > 0) {
                    this.priceLabel.string = data.virtual_prices[0].amount.toString() + ' ' + data.virtual_prices[0].name;
                } else {
                    this.priceLabel.string = parseFloat(data.price.amount) + ' ' + data.price.currency;
                }
    
    
                assetManager.loadRemote<ImageAsset>(data.image_url, (err, imageAsset) => {
                    if(err == null) {
                    const spriteFrame = new SpriteFrame();
                    const texture = new Texture2D();
                    texture.image = imageAsset;
                    spriteFrame.texture = texture;
                    this.iconSprite.spriteFrame = spriteFrame;
                    } else {
                        console.log(`Cant load image with url ${data.image_url}`);
                    }
                });
            }
    
    
            onBuyClicked() {
                XsollaCatalog.fetchPaymentToken(TokenStorage.getToken().access_token, this._data.sku, 1, undefined, undefined, undefined, undefined, undefined, result => {
                    OrderTracker.checkPendingOrder(result.token, result.orderId, () => {
                        console.log('success purchase!');
                    }, error => {
                        console.log(`Order checking failed - Status code: ${error.status}, Error code: ${error.code}, Error message: ${error.description}`);
                    });
                    XsollaPayments.openPurchaseUI(result.token);
                }, error => {
                    console.log(error.description);
                });
            }
        }
    }
    

    完成商品目录脚本组件

    注:
    在示例脚本中,修改的类更改为RC_ItemsCatalogComponent

    RC_ItemsCatalogComponent类的start方法添加获取有效授权令牌的逻辑,如脚本示例中所示。

    类脚本示例(RC_ItemsCatalogComponent):

    Copy
    Full screen
    Small screen
    import { _decorator, Component, instantiate, Prefab, ScrollView } from 'cc';
    import { XsollaCatalog } from 'db://xsolla-commerce-sdk/scripts/api/XsollaCatalog';
    import { RC_StoreItemComponent } from './RC_StoreItemComponent';
    import { XsollaAuth } from 'db://xsolla-commerce-sdk/scripts/api/XsollaAuth';
    import { TokenStorage } from 'db://xsolla-commerce-sdk/scripts/common/TokenStorage';
    const { ccclass, property } = _decorator;
    
    
    
    
    @ccclass('RC_ItemsCatalogComponent')
    export class RC_ItemsCatalogComponent extends Component {
    
        @property(ScrollView)
        itemsScrollView: ScrollView;
    
    
        @property(Prefab)
        storeItemPrefab: Prefab;
    
    
        start() {
            XsollaAuth.authByUsernameAndPassword('xsolla', 'xsolla', false, token => {
                TokenStorage.saveToken(token, false);
                XsollaCatalog.getCatalog(null, null, [], itemsData => {
                    for (let i = 0; i < itemsData.items.length; ++i) {
                            let storeItem = instantiate(this.storeItemPrefab);
                            this.itemsScrollView.content.addChild(storeItem);
                            storeItem.getComponent(RC_StoreItemComponent).init(itemsData.items[i]);
                        }
                    });
            });
        }
    }
    

    脚本工作结果示例:

    以虚拟货币计价的形式销售虚拟物品

    本节介绍如何通过SDK方法实现以虚拟货币形式销售虚拟物品。

    开始之前,需实现在目录中显示虚拟物品。下方示例中我们描述了如何实现虚拟物品的购买。其他商品类型的配置与此类似。

    本教程介绍以下逻辑的实现:

    注意

    在登录脚本示例中我们使用的是演示帐户的凭证(用户名:xsolla,密码:xsolla)。该帐户仅适用于演示项目。

    脚本示例不包含按页显示目录中商品(分页)的实现。请使用getCatalog SDK方法的offsetlimit参数来实现分页。每页最大商品数为50。如目录中的商品数超过50,则需要分页。

    完成商品小组件

    在商品小组件中添加购买按钮,并配置其显示元素。
    注:
    在示例中,修改的类更改为VC_StoreItemComponent
    小组件结构的示例:

    完成商品小组件脚本

    1. 要通过代码将回调函数与购买按钮绑定,请向VC_StoreItemComponent添加buyButton属性。
    2. 将方法添加到点击BuyButton时调用的VC_StoreItemComponent类,并添加处理该点击事件的逻辑,脚本示例中所示。
    3. 通过以下方式之一:将回调与购买按钮绑定

      • 使用Inspector面板,如下图所示
      • 将下方代码块插入页面脚本

    使用Inspector面板绑定回调:

    通过代码绑定回调:

    Copy
    Full screen
    Small screen
    start() {
    	this.buyButton.node.on(Button.EventType.CLICK, this.onBuyClicked, this);
      }
    

    小组件脚本示例(VC_StoreItemComponent):

    Copy
    Full screen
    Small screen
    import { _decorator, assetManager, Button, Component, ImageAsset, Label, Sprite, SpriteFrame, Texture2D } from 'cc';
    import { StoreItem, XsollaCatalog } from 'db://xsolla-commerce-sdk/scripts/api/XsollaCatalog';
    import { TokenStorage } from 'db://xsolla-commerce-sdk/scripts/common/TokenStorage';
    import { OrderTracker } from 'db://xsolla-commerce-sdk/scripts/common/OrderTracker';
    const { ccclass, property } = _decorator;
    
    
    @ccclass('VC_StoreItemComponent')
    export class VC_StoreItemComponent extends Component {
    
        @property(Sprite)
        iconSprite: Sprite;
    
        @property(Label)
        itemNameLabel: Label;
    
        @property(Label)
        itemDescriptionLabel: Label;
    
        @property(Label)
        priceLabel: Label;
    
        @property(Button)
        buyButton: Button;
    
        private _data: StoreItem;
    
        start() {
            this.buyButton.node.on(Button.EventType.CLICK, this.onBuyClicked, this);
        }
    
        init(data: StoreItem) {
    
            this._data = data;
    
            this.itemNameLabel.string = data.name;
            this.itemDescriptionLabel.string = data.description;
    
            if (data.virtual_prices.length > 0) {
                this.priceLabel.string = data.virtual_prices[0].amount.toString() + ' ' + data.virtual_prices[0].name;
            } else {
                this.priceLabel.string = parseFloat(data.price.amount) + ' ' + data.price.currency;
            }
    
            assetManager.loadRemote<ImageAsset>(data.image_url, (err, imageAsset) => {
                if(err == null) {
                const spriteFrame = new SpriteFrame();
                const texture = new Texture2D();
                texture.image = imageAsset;
                spriteFrame.texture = texture;
                this.iconSprite.spriteFrame = spriteFrame;
                } else {
                    console.log(`Can’t load image with URL ${data.image_url}`);
                }
            });
        }
    
        onBuyClicked() {
            XsollaCatalog.purchaseItemForVirtualCurrency(TokenStorage.getToken().access_token, this._data.sku, this._data.virtual_prices[0].sku, orderId => {
                OrderTracker.checkPendingOrder(TokenStorage.getToken().access_token, orderId, () => {
                    console.log('success purchase!');
                }, error => {
                    console.log(`Order checking failed - Status code: ${error.status}, Error code: ${error.code}, Error message: ${error.description}`);
                });
            }, error => {
                console.log(error.description);
            });
        }
    }
    

    完成商品目录脚本组件

    注:
    在示例脚本中,修改的类更改为VC_ItemsCatalogComponent

    VC_ItemsCatalogComponent类的start方法添加获取有效授权令牌的逻辑,如脚本示例中所示。

    类脚本示例(VC_ItemsCatalogComponent):

    Copy
    Full screen
    Small screen
    import { _decorator, Component, instantiate, Prefab, ScrollView } from 'cc';
    import { XsollaCatalog } from 'db://xsolla-commerce-sdk/scripts/api/XsollaCatalog';
    import { VC_StoreItemComponent } from './VC_StoreItemComponent';
    import { XsollaAuth } from 'db://xsolla-commerce-sdk/scripts/api/XsollaAuth';
    import { TokenStorage } from 'db://xsolla-commerce-sdk/scripts/common/TokenStorage';
    const { ccclass, property } = _decorator;
    
    
    @ccclass('VC_ItemsCatalogComponent')
    export class VC_ItemsCatalogComponent extends Component {
    
        @property(ScrollView)
        itemsScrollView: ScrollView;
    
        @property(Prefab)
        storeItemPrefab: Prefab;
    
        start() {
            XsollaAuth.authByUsernameAndPassword('xsolla', 'xsolla', false, token => {
                TokenStorage.saveToken(token, false);
                XsollaCatalog.getCatalog(null, null, [], itemsData => {
                    for (let i = 0; i < itemsData.items.length; ++i) {
                            let storeItem = instantiate(this.storeItemPrefab);
                            this.itemsScrollView.content.addChild(storeItem);
                            storeItem.getComponent(VC_StoreItemComponent).init(itemsData.items[i]);
                        }
                    });
            });
        }
    }
    

    脚本工作结果示例:

    显示虚拟货币余额

    本教程介绍如何使用SDK方法在应用中显示虚拟货币的余额。

    创建显示余额的小组件

    1. 创建一个预制件。方法是从文件夹的快捷菜单中选择Create > Node Prefab
    2. 打开创建的预制件。
    3. UITransform组件添加到预制件的根,如图所示,并设置内容尺寸。

    1. 添加以下UI元素作为预制件的子对象并配置其视效:
      • 小组件背景图片
      • 货币名称
      • 货币数量
      • 货币图片

    小组件结构的示例:

    创建显示余额的小组件脚本

    1. 创建一个CurrencyBalanceItemComponent并添加以下属性:
      • iconSprite
      • currencyNameLabel
      • quantityLabel

    1. init方法和初始化逻辑添加到CurrencyBalanceItemComponent类,如脚本示例中所示。
    2. 将CurrencyBalanceItemComponent组件附加到预制件的根节点
    3. 将预制件元素与CurrencyBalanceItemComponent的属性绑定,如图所示:

    小组件脚本示例(CurrencyBalanceItemComponent):

    Copy
    Full screen
    Small screen
    import { _decorator, assetManager, Component, ImageAsset, Label, Sprite, SpriteFrame, Texture2D } from 'cc';
    import { VirtualCurrencyBalance } from 'db://xsolla-commerce-sdk/scripts/api/XsollaInventory';
    const { ccclass, property } = _decorator;
    
    
    @ccclass('CurrencyBalanceItemComponent')
    export class CurrencyBalanceItemComponent extends Component {
    
        @property(Sprite)
        iconSprite: Sprite;
    
        @property(Label)
        currencyNameLabel: Label;
    
        @property(Label)
        quantityLabel: Label;
    
        init(data: VirtualCurrencyBalance) {
    
            this.currencyNameLabel.string = data.name;
            this.quantityLabel.string = data.amount.toString();
    
            assetManager.loadRemote<ImageAsset>(data.image_url, (err, imageAsset) => {
                if(err == null) {
                const spriteFrame = new SpriteFrame();
                const texture = new Texture2D();
                texture.image = imageAsset;
                spriteFrame.texture = texture;
                this.iconSprite.spriteFrame = spriteFrame;
                } else {
                    console.log(`Can’t load image with URL ${data.image_url}`);
                }
            });
        }
    }
    

    创建页面界面

    为虚拟货币余额页面创建一个场景,并向其添加ScrollView元素。

    页面结构示例:

    要让ScrollView与创建的CurrencyBalanceItem预制件匹配,请设置其大小:

    1. 更改ScrollView节点和内部view节点中ContentSize参数的值。
    2. Layout组件与content节点绑定并完成设置。示例中选择了以下设置:
      • Type == vertical
      • ResizeMode == Container

    创建货币余额脚本组件

    1. 创建一个CurrencyBalanceComponent并添加以下属性:
      • itemsScrollView
      • currencyBalanceItemPrefab

    1. start生命周期函数和初始化逻辑添加到CurrencyBalanceComponent类,如脚本示例中所示。
    2. 在场景中将CurrencyBalanceComponent添加到节点。可添加新节点或使用SDK初始化期间添加的含XsollaSettingsManager组件的现有节点。
    3. 将预制件元素与CurrencyBalanceItemComponent的属性绑定,如图所示:
    注:
    在登录脚本示例中我们使用的是演示帐户的凭证(用户名:xsolla,密码:xsolla)。该帐户仅适用于演示项目。
    类脚本示例(CurrencyBalanceComponent):
    Copy
    Full screen
    Small screen
    import { _decorator, Component, instantiate, Prefab, ScrollView } from 'cc';
    import { XsollaAuth } from 'db://xsolla-commerce-sdk/scripts/api/XsollaAuth';
    import { XsollaInventory } from 'db://xsolla-commerce-sdk/scripts/api/XsollaInventory';
    import { CurrencyBalanceItemComponent } from './CurrencyBalanceItemComponent';
    const { ccclass, property } = _decorator;
    
    
    @ccclass('CurrencyBalanceComponent')
    export class CurrencyBalanceComponent extends Component {
    
        @property(ScrollView)
        itemsList: ScrollView;
    
        @property(Prefab)
        currencyBalanceItemPrefab: Prefab;
    
        start() {
            XsollaAuth.authByUsernameAndPassword('xsolla', 'xsolla', false, token => {
                XsollaInventory.getVirtualCurrencyBalance(token.access_token, null, itemsData => {
                    for (let i = 0; i < itemsData.items.length; ++i) {
                            let currencyBalanceItem = instantiate(this.currencyBalanceItemPrefab);
                            this.itemsList.content.addChild(currencyBalanceItem);
                                 currencyBalanceItem.getComponent(CurrencyBalanceItemComponent).init(itemsData.items[i]);
                        }
                    });
            });
    
        }
    }
    

    脚本工作结果示例:

    显示物品库中的物品

    本教程介绍如何使用SDK方法显示用户物品库中的商品。

    创建物品小组件

    1. 创建一个预制件。方法是从文件夹的快捷菜单中选择Create > Node Prefab
    2. 打开创建的预制件。
    3. UITransform组件添加到预制件的根,如图所示,并设置内容尺寸。

    1. 添加以下UI元素作为预制件的子对象并配置其视效:
      • 小组件背景图片
      • 物品库物品名称
      • 物品库物品描述
      • 物品数量
      • 物品图片

    小组件结构的示例:

    创建物品小组件脚本

    1. 创建一个InventoryItemComponent并添加以下属性:
      • iconSprite
      • itemNameLabel
      • itemDescriptionLabel
      • quantityLabel

    1. init方法和初始化逻辑添加到InventoryItemComponent类,如脚本示例中所示。
    2. 将InventoryItemComponent组件附加到预制件的根节点
    3. 将预制件元素与InventoryItemComponent的属性绑定,如图所示:

    小组件脚本示例(InventoryItemComponent):

    Copy
    Full screen
    Small screen
    import { _decorator, assetManager, Component, ImageAsset, Label, Sprite, SpriteFrame, Texture2D } from 'cc';
    import { InventoryItem } from 'db://xsolla-commerce-sdk/scripts/api/XsollaInventory';
    const { ccclass, property } = _decorator;
    
    @ccclass('InventoryItemComponent')
    export class InventoryItemComponent extends Component {
    
        @property(Sprite)
        iconSprite: Sprite;
    
        @property(Label)
        itemNameLabel: Label;
    
        @property(Label)
        itemDescriptionLabel: Label;
    
        @property(Label)
        quantityLabel: Label;
    
        init(data: InventoryItem) {
    
            this.itemNameLabel.string = data.name;
            this.itemDescriptionLabel.string = data.description;
            this.quantityLabel.string = data.quantity.toString();
    
            assetManager.loadRemote<ImageAsset>(data.image_url, (err, imageAsset) => {
                if(err == null) {
                const spriteFrame = new SpriteFrame();
                const texture = new Texture2D();
                texture.image = imageAsset;
                spriteFrame.texture = texture;
                this.iconSprite.spriteFrame = spriteFrame;
                } else {
                    console.log(`Can’t load image with URL ${data.image_url}`);
                }
            });
        }
    }
    

    创建页面界面

    为物品库页面创建一个场景,并向其添加ScrollView元素。

    页面结构示例:

    要让ScrollView与创建的InventoryItem预制件匹配,请设置其大小:

    1. 更改ScrollView节点和内部view节点中ContentSize参数的值。
    2. Layout组件与content节点绑定并完成设置。示例中选择了以下设置:
      • Type == vertical
      • ResizeMode == Container

    创建物品库页面脚本组件

    1. 创建一个InventoryItemsComponent并添加以下属性:
      • itemsScrollView
      • inventoryItemPrefab

    1. start生命周期函数和初始化逻辑添加到InventoryItemsComponent,如脚本示例中所示。
    2. 在场景中将InventoryItemsComponent添加到节点。可添加新节点或使用SDK初始化期间添加的含XsollaSettingsManager组件的现有节点。
    3. 将预制件元素与InventoryItemsComponent的属性绑定,如图所示:
    注:
    在登录脚本示例中我们使用的是演示帐户的凭证(用户名:xsolla,密码:xsolla)。该帐户仅适用于演示项目。
    类脚本示例(InventoryItemsComponent):
    Copy
    Full screen
    Small screen
    import { _decorator, Component, instantiate, Prefab, ScrollView } from 'cc';
    import { XsollaAuth } from 'db://xsolla-commerce-sdk/scripts/api/XsollaAuth';
    import { XsollaInventory } from 'db://xsolla-commerce-sdk/scripts/api/XsollaInventory';
    import { InventoryItemComponent } from './InventoryItemComponent';
    const { ccclass, property } = _decorator;
    
    
    @ccclass('InventoryItemsComponent')
    export class InventoryItemsComponent extends Component {
    
        @property(ScrollView)
        itemsScrollView: ScrollView;
    
        @property(Prefab)
        inventoryItemPrefab: Prefab;
    
        start() {
            XsollaAuth.authByUsernameAndPassword('xsolla', 'xsolla', false, token => {
                XsollaInventory.getInventory(token.access_token, null, itemsData => {
                    for (let i = 0; i < itemsData.items.length; ++i) {
                            let inventoryItem = instantiate(this.inventoryItemPrefab);
                            this.itemsScrollView.content.addChild(inventoryItem);
                            inventoryItem.getComponent(InventoryItemComponent).init(itemsData.items[i]);
                        }
                    });
            });
    
        }
    }
    

    脚本工作结果示例:

    本文对您的有帮助吗?
    谢谢!
    我们还有其他可改进之处吗? 留言
    非常抱歉
    请说明为何本文没有帮助到您。 留言
    感谢您的反馈!
    我们会查看您的留言并运用它改进用户体验。
    上次更新时间: 2024年1月22日

    发现了错别字或其他内容错误? 请选择文本,然后按Ctrl+Enter。

    报告问题
    我们非常重视内容质量。您的反馈将帮助我们做得更好。
    请留下邮箱以便我们后续跟进
    感谢您的反馈!