Commit 3113c659 authored by qinhaitao's avatar qinhaitao

feat: 🎸 init

parent 3373bfd2
No preview for this file type
// babel-preset-taro 更多选项和默认值:
// https://github.com/NervJS/taro/blob/next/packages/babel-preset-taro/README.md
/* eslint-disable*/
module.exports = {
presets: [
[ 'taro', {
framework: 'react',
ts: false
} ]
]
}
# 新增requestType 说明
## 背景
淘宝需求涉及下单、会员等需要跟淘宝进行强交互的需求时,目前在自测阶段只能通过服务端自测,但是对于
客户端开发的同学,此刻只能通过`yapi`这种静态数据mock的方式获取数据进行展示,其中的不足是无法与
整个业务衔接
## 目的
客户端通过像配置`yapi`那样配置风驰台开放的接口,就能模拟下单、会员等强交互的业务,且整个业务的数
据完成跟服务端实际做的一致,在自测阶段即可模拟淘宝测试环境将强交互的业务需求打通
## 配合
要做到能正常调用风驰台接口,必须服务端同学配合,可以理解成风驰台只是一个中介,真正处理业务逻辑是
在服务端同学的本地机器启动的服务上
## ☑️忽略 Webview 域名合法性检查(仅在本地模拟、预览和远程调试时生效)
具体可在`淘宝开发工具`-> 右上角`详情`-> 页面最底下
### 如何获取到`tornadoAPI`地址?
最简单的方法是:
- 直接问服务端,让其开启服务,并直接给到tornadoAPI地址
### 如何查看表数据?
- 访问地址:https://tornado.duibadev.com.cn/#/data 选择项目,您想要获取的数据均可以在`基础表`查看到
### 如何模拟下单、会员
- 访问地址:https://tornado.duibadev.com.cn/#/data 选择项目,拉到页面最底下,`淘宝api`接口等着你设置模拟数据
const app = getApp();
const { tbcc, cloudName, cloud, requestType } = app;
const { utils: { generateAPI }, request: tbccRequest, capi } = tbcc;
const request = tbccRequest({
cloud,
cloudName,
requestType
});
// api handle 列表
const apiList = {
...capi,
getActivityBaseInfoById: {
handle: 'getActivityBaseInfoById',
method: 'GET',
isShowLoading: true
},
login: 'login'
};
// 生成API
const API = generateAPI(apiList, request);
export default API;
import cloud from '@tbmp/mp-cloud-sdk';
import tbcc from './tbcc-sdk';
cloud.init({
env: 'test' // 云函数环境 test 测试环境 online 线上环境
});
App({
requestType: 'yapi', // cloud: 云函数, yapi: yapi 接口, ams: ams接口,tornadoAPI: 风驰台接口
cloudName: 'CommonToC', // 主云函数项目名
tornadoAPI: 'https://tornado.duibadev.com.cn/tbServer/api?db=xxx&proxyIp=xxx', // 新增:当requestType: 'tornadoAPI'时,找服务端提供地址
cloud,
tbcc,
onLaunch(options) {
this.handleQuery(options.query || {});
},
onShow(options) {
// 从后台被 scheme 重新打开
},
/**
* 处理链接参数
* @param {object} query 小程序链接上的参数
*/
handleQuery(query) {
const { activityId } = query;
this.activityId = activityId || '5f8550a3964f97be3647a635';
}
});
{
"pages": [
"pages/index/index",
"pages/tbccDemo/tbccDemo",
"pages/myprize/myprize",
"pages/collectGoods/collectGoods",
"pages/browseGoods/browseGoods",
"pages/orderGoods/orderGoods",
"pages/endPage/endPage"
],
"window": {
"allowsBounceVertical": "NO",
"defaultTitle": "C端模板",
"enableSkia":"true"
}
}
### 注意!
assets文件夹只做本地开发测试使用
因为淘宝打包有体积限制,最大不超过1M,所以打包提审请同步图片等资源到CDN上,然后删除assets文件夹再打包提审。
\ No newline at end of file
.common-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 20;
}
.common-modal__shade {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.75);
}
.common-modal__container {
position: absolute;
width: 500rpx;
height: 300rpx;
background: #4eafd7;
background-size: 100% 100%;
left: 50%;
top: 50%;
margin-left: -250rpx;
margin-top: -150rpx;
padding: 30rpx;
}
.common-modal__bd {
font-size: 28rpx;
text-align: center;
color: #fff;
padding: 50rpx 0;
}
.common-modal__close {
position: absolute;
right: -20rpx;
top: -80rpx;
height: 86rpx;
width: 86rpx;
background: url('../../assets/modal_close_btn.png') center center no-repeat;
background-size: 48rpx 48rpx;
z-index: 9;
}
.common-modal__confirm-btn {
margin: 0 auto;
text-align: center;
width: 341rpx;
height: 77rpx;
line-height: 77rpx;
background: #FFF;
background-size: 100% 100%;
font-size: 30rpx;
color: #0077a9;
font-weight: bold;
}
<view class="common-modal">
<view class="common-modal__shade modal-animate-fade-in" />
<view class="common-modal__container modal-animate-zoom-in">
<view class="common-modal__close" onTap="onModalClose" />
<view class="common-modal__bd">
<text>{{data.content}}</text>
</view>
<view class="common-modal__ft" a:if="{{data.btnText}}">
<view onTap="onModalConfirm" class="common-modal__confirm-btn">{{data.btnText}}</view>
</view>
</view>
</view>
Component({
methods: {
onModalClose() {
const { onModalClose } = this.props;
onModalClose && onModalClose();
},
onModalConfirm () {
const { onModalConfirm } = this.props;
onModalConfirm && onModalConfirm();
}
}
});
\ No newline at end of file
{
"component": true
}
\ No newline at end of file
.countdown {
position: fixed;
width: 144rpx;
height: 162rpx;
right: 0;
top: 600rpx;
z-index: 20;
}
.countdown-box-clock {
position: absolute;
top: 0;
left: 50%;
margin-left: -41rpx;
width: 82rpx;
height: 88rpx;
background-size: 100% 100%;
z-index: 10;
}
.countdown-box-clock__time {
position: absolute;
top: 5rpx;
left: 0;
width: 82rpx;
height: 88rpx;
display: flex;
align-items: center;
justify-content: center;
}
.countdown-box-clock image {
width: 100%;
height: 100%;
}
.countdown-box {
position: absolute;
width: 130rpx;
height: 18rpx;
border-radius: 10rpx;
border: 1px solid #a6fffe;
background: #111192;
box-shadow: 0 0 4rpx 1rpx #7fd5ff;
top: 25rpx;
bottom: 20rpx;
left: 0;
right: 0;
margin: auto;
overflow: hidden;
z-index: 11;
}
@keyframes countdownAnimation {
to {
transform: translateX(0);
}
}
.countdown-box .countdown-time {
height: 100%;
width: 100%;
background: linear-gradient(#3996f8, #19dffd);
}
.countdown-txt {
position: absolute;
width: 147rpx;
height: 91rpx;
bottom: 0rpx;
z-index: 1;
}
.countdown-txt image {
width: 100%;
height: 100%;
}
.countdown-txt__content {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
line-height: 25rpx;
color: #2A2A2A;
text-align: center;
font-size: 20rpx;
}
.countdown-txt .txt:first-child{
margin-top: 25rpx;
}
.countdown-two {
position: absolute;
width: 554rpx;
height: 104rpx;
left: 50%;
margin-left: -277rpx;
bottom: 70rpx;
}
.countdown-two-clock {
position: absolute;
right: -10rpx;
bottom: 10rpx;
width: 104rpx;
height: 95rpx;
z-index: 15;
}
.countdown-two-clock image {
width: 100%;
height: 100%;
}
.countdown-two-clock .countdown-box-clock__time {
position: absolute;
top: 5rpx;
left: 0;
width: 104rpx;
height: 95rpx;
display: flex;
align-items: center;
justify-content: center;
}
.countdown-two-clock .countdown-box{
position: absolute;
left: 0;
bottom: -20rpx;
}
.countdown-two-txt {
position: absolute;
right: 90rpx;
top: 5rpx;
width: 323rpx;
height: 60rpx;
z-index: 14;
background: url('//yun.dui88.com/taobaomini/clientCTest/count_bg_02@2x.png');
background-size: 100% 100%;
color: #2A2A2A;
text-align: center;
font-size: 20rpx;
line-height: 60rpx;
}
.countdown-two-txt image {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 14;
}
.countdown-two__content {
position: absolute;
left: 0;
top: 0;
width: 323rpx;
height: 60rpx;
color: #2A2A2A;
text-align: center;
font-size: 20rpx;
}
\ No newline at end of file
<view class="countdown" a:if="{{styleType == '01'}}">
<view class="countdown-box-clock">
<image mode="scaleToFill" src="{{data.clock_bg}}"/>
<view class="countdown-box-clock__time" style="{{{'color':data.countTxtColor,'fontSize':data.countTxtSize+'rpx'}}}">{{countTime}}</view>
</view>
<view class="countdown-box" style="{{{'height':data.progressHeight+'rpx','width':data.progressWidth+'rpx'}}}">
<view class="countdown-time" style="transform: translateX({{percent*10 -100}}%);animation: countdownAnimation {{countTime-percent}}s forwards linear;"></view>
</view>
<view class="countdown-txt">
<image mode="scaleToFill" src="{{data.textBg}}"/>
<view class="countdown-txt__content">
<view class="txt">{{data.text.split('\n')[0]}}</view>
<view class="txt">{{data.text.split('\n')[1]}}</view>
</view>
</view>
</view>
<view class="countdown-two" a:else>
<view class="countdown-two-clock">
<image mode="scaleToFill" src="{{data.clock_bg}}"/>
<view class="countdown-box-clock__time" style="{{{'color':data.countTxtColor,'fontSize':42+'rpx'}}}">{{countTime}}</view>
</view>
<view class="countdown-box" style="{{{'height':55+'rpx','width':490+'rpx'}}}">
<view class="countdown-time" style="transform: translateX({{percent*10 -100}}%);animation: countdownAnimation {{countTime-percent}}s forwards linear;"></view>
</view>
<view class="countdown-two-txt" style="{{{'background':`url(${data.textBg}) no-repeat`,'backgroundSize': '100% 100%'}}}">
{{data.text}}
</view>
</view>
/**
* data: {
* text: //文案描述
* textBg: //文案背景
* progressWidth: //进度条宽
* progressHeight: //进度条高
* clock_bg: //时钟icon
* countTxt: //时钟文案
* countTxtColor: //时钟文字颜色
* countTxtSize: //时钟文字大小
* }
* hasPercent: //任务完成剩余时长
* countTime: //倒计时长
* styleType: //样式(01——右对齐,02—底部)
*/
Component({
mixins: [],
data: {},
props: {
percent: 10,
hasPercent:10,
countTime: 10,
styleType: '01',//browse_txt_bg count_bg_02
data: {
text: '浏览商品10s\n获得任务奖励',
textBg: '//yun.duiba.com.cn/taobaomini/clientCTest/browse_txt_bg@2x.png',
progressWidth:'130',
progressHeight:'18',
clock_bg: '//yun.duiba.com.cn/taobaomini/clientCTest/clock@2x.png',
countTxt: '10',
countTxtColor: '#1CA4ED',
countTxtSize: '32'
}
},
didMount() {},
didUpdate() {},
didUnmount() { },
methods: {},
});
{
"component": true
}
\ No newline at end of file
.end-modal-container {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
min-height: 100vh;
/* background: url('//yun.dui88.com/taobaomini/clientCTest/end_bg@2x.png') no-repeat; */
background-size: 750rpx 1624rpx;
overflow: hidden;
z-index: 20;
}
.end-modal-container-content {
position: absolute;
width: 750rpx;
height: 480rpx;
top: 50%;
left: 0;
margin-top: -240rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
}
.end-modal-container-content__icon {
width: 478rpx;
height: 340rpx;
}
.end-modal-container-content__icon image {
width: 100%;
height: 100%;
}
.end-modal-container-content__desc {
font-size: 36rpx;
font-family: Source Han Sans CN;
font-weight: 400;
color: #ADADAD;
}
\ No newline at end of file
<view class="end-modal-container" style="{{{'background':`url(${data.image.bg}) no-repeat`}}}">
<view class="end-modal-container-content">
<view class="end-modal-container-content__icon">
<image mode="scaleToFill" src="{{data.image.end_icon}}"/>
</view>
<view class="end-modal-container-content__desc">{{data.text}}</view>
</view>
</view>
\ No newline at end of file
Component({
mixins: [],
data: {},
props: {
data: {
image: {
bg: '//yun.dui88.com/taobaomini/clientCTest/end_bg@2x.png',
end_icon: '//yun.dui88.com/taobaomini/clientCTest/end_icon@2x.png',
},
text: '活动未开始'
}
},
didMount() {},
didUpdate() {},
didUnmount() {},
methods: {},
});
{
"component": true
}
\ No newline at end of file
<view class="goods-modal-container" style="{{{'background':`url(${task.image.bg}) no-repeat`,'backgroundSize': '750rpx 1624rpx'}}}">
<view a:if="{{task.type == '01'}}"
style="{{{'background':`url(${task.image.title}) no-repeat`,'backgroundSize': '100% 100%'}}}"
class="goods-modal-container__title">
</view>
<view a:else class="goods-modal-container__banner" style="{{{'background':`url(${task.image.banner}) no-repeat`,'backgroundSize': '100% 100%','height':task.bannerHeight+'rpx'}}}"></view>
<view class="goods-modal-container__content" style="{{{'top':task.type == '02'?task.bannerHeight+'rpx':'209rpx'}}}">
<scroll-view scroll-y="{{true}}" class="goods-modal-container__scroll">
<view class="goods-modal-container__content-list">
<view class="goods-modal-container__content-item" a:for="{{goodsList}}" a:for-item="item">
<view class="goods-modal-container__content-item__img" data-item="{{item}}" onTap="goToGoodsDetail">
<image mode="scaleToFill" src="{{item.image}}"/>
</view>
<view class="goods-modal-container__content-item__name" style="{{{'color':task.color}}}">{{item.name}}</view>
<view class="goods-modal-container__content-item__price">
<view class="goods-modal-container__content-item__price-num"><text>¥</text>{{item.price}}</view>
<view a:if="{{task.taskType == 'collectGoods'}}"
data-item="{{item}}"
onTap="goToCollectGoods"
class="{{item.collected ? 'goods-modal-container__content-item__price-collect' : 'goods-modal-container__content-item__price-no-collect'}}"
>
<image mode="scaleToFill" src="{{item.collected?task.image.collect:task.image.no_collect}}"/>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
</view>
\ No newline at end of file
/**
* 任务类型(taskType):collectGoods、orderGoods、browseGoods、
*/
const app = getApp();
const { tbcc } = app;
const { openDetail, collectGoods, checkGoodsCollectedStatus, commonToast } = tbcc.tb;
Component({
mixins: [],
data: {
},
props: {
isBackFlag: 0,
goodsList: [1,2,3],
task: {
itemId: '617724147979,617724563528,617300295119',
taskType: 'browseGoods',
type: '02',
bannerHeight: 430,
image: {
bg: '//yun.dui88.com/taobaomini/clientCTest/goods_bg@2x.png',
banner: '//yun.dui88.com/taobaomini/clientCTest/goods_banner@2x.png',
title: '//yun.dui88.com/taobaomini/clientCTest/goods_title@2x.png',
collect: '//yun.dui88.com/taobaomini/clientCTest/goods_collection@2x.png',
no_collect: '//yun.dui88.com/taobaomini/clientCTest/collection_no_collect@2x.png',
img: '//yun.dui88.com/taobaomini/clientCTest/goods_img@2x.png',
},
color: '#181818'
},
onCompleteTask: () => {},
onOpenDetail: () => {}
},
didMount() {
},
didUpdate(prevProps,prevData) {
},
didUnmount() {
},
methods: {
/**
* 跳转商品详情页
*/
async goToGoodsDetail(e) {
const { taskType } = this.props.task
const { itemId } = e.target.dataset.item
if(taskType == 'browseGoods') {
this.props.onOpenDetail(itemId)
}
await openDetail(String(itemId))
},
/**
* 收藏商品
*/
async goToCollectGoods(e) {
let { itemId, collected } = e.target.dataset.item
const { taskType } = this.props.task
if(collected) {
commonToast('您已收藏过该商品了')
return;
}
//判断是否活动外已收藏商品
let isCollected = await checkGoodsCollectedStatus(+itemId)
if(isCollected) {
this.props.onCompleteTask(taskType, itemId)
return;
}
let result = await collectGoods(+itemId)
if(result) {
this.props.onCompleteTask(taskType, itemId)
}
}
},
});
<view class="logistics-modal-container">
<view class="logistics-modal-container__content"
style="{{prizeItem.shipStatus == 3 ?`background: url(${task.shipped}) no-repeat;background-size: 100% 100%;`
:`background: url(${task.notShipped}) no-repeat;background-size: 100% 100%;`}}">
<view class="logistics-modal-container__close" onTap="closeModal"></view>
<view class="logistics-modal-container__content-item">
<view class="logistics-modal-container__content-left">
<image mode="scaleToFill" src="{{prizeItem.image}}"/>
</view>
<view class="logistics-modal-container__content-right">
<view class="logistics-modal-container__content-name">{{prizeItem.name}}</view>
<view class="logistics-modal-container__content-copy">
<view class="logistics-modal-container__content-copy__code">奖品编码:{{prizeItem._id}}</view>
<view class="logistics-modal-container__content-copy__img" onTap="copyPrizeId"></view>
</view>
<view class="logistics-modal-container__content-desc">{{prizeItem.shipStatus== 3?'发货状态:已发货':'发货状态:未发货'}}</view>
<view class="logistics-modal-container__content-desc" a:if="{{prizeItem.shipStatus == 2}}">温馨提示:奖品将在活动结束7天内陆续发出</view>
<view class="logistics-modal-container__content-copy" a:else>
<view class="logistics-modal-container__content-copy__code">{{'物流单号:'+prizeItem.shipNum}}</view>
<view class="logistics-modal-container__content-copy__img" style="{{`background: url(${task.copyIcon}) no-repeat;background-size: 100% 100%;`}}" onTap="copyCode"></view>
</view>
</view>
</view>
</view>
</view>
\ No newline at end of file
const app = getApp();
const { tbcc } = app;
const { commonToast } = tbcc.tb;
Component({
mixins: [],
data: {},
props: {
prizeItem: {},
onCloseModal: () => { },
task: {
shipped: '',
notShipped: '',
}
},
didMount() { },
didUpdate() { },
didUnmount() { },
methods: {
closeModal() {
this.props.onCloseModal()
},
//复制运单号
copyCode() {
let { shipNum } = this.props.prizeItem
my.setClipboard({
text: shipNum + '',
success: () => {
commonToast('复制成功')
}
});
},
copyPrizeId() {
let { _id } = this.props.prizeItem
my.setClipboard({
text: _id + '',
success: () => {
commonToast('复制成功')
}
});
}
},
});
{
"component": true
}
\ No newline at end of file
.rule-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 20;
}
.rule-modal__shade {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.75);
}
.rule-modal__container {
position: absolute;
width: 500rpx;
height: 600rpx;
background: #4eafd7;
background-size: 100% 100%;
left: 50%;
top: 50%;
margin-left: -250rpx;
margin-top: -300rpx;
}
.rule-modal__bd {
position: absolute;
top: 40rpx;
left: 40rpx;
right: 40rpx;
bottom: 40rpx;
overflow-x: hidden;
font-size: 24rpx;
color: #fff;
line-height: 1.5;
}
.rule-modal__close {
position: absolute;
right: -20rpx;
top: -80rpx;
height: 86rpx;
width: 86rpx;
background: url('../../assets/modal_close_btn.png') center center no-repeat;
background-size: 48rpx 48rpx;
z-index: 9;
}
<view class="rule-modal">
<view class="rule-modal__shade modal-animate-fade-in" />
<view class="rule-modal__container modal-animate-zoom-in">
<view class="rule-modal__close" onTap="onModalClose" />
<view class="rule-modal__bd">
<slot />
</view>
</view>
</view>
Component({
methods: {
onModalClose() {
const { onModalClose } = this.props;
onModalClose && onModalClose();
}
}
});
\ No newline at end of file
const { tbcc } = getApp();
const { constants: tbccConstants } = tbcc;
// 分享配置
export const SHARE_CONFIG = {
title: '分享测试标题',
desc: '分享测试描述',
imageUrl: '', // 分享图片URL
path: 'pages/index/index'
};
// 链接配置
export const REDIRECT_URL = {
'testpage': '/pages/testPage/testPage',
'tmall': 'https://www.tmall.com'
};
export const DRAW_STATUS = tbccConstants.DRAW_STATUS;
export const PRIZE_TYPE = tbccConstants.PRIZE_TYPE;
{
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@tbmp/mp-cloud-sdk": {
"version": "1.3.4",
"resolved": "https://registry.npm.taobao.org/@tbmp/mp-cloud-sdk/download/@tbmp/mp-cloud-sdk-1.3.4.tgz",
"integrity": "sha1-+1HCYryYfVc0kze0SOWv1oL6xgk="
},
"crypto": {
"version": "1.0.1",
"resolved": "https://registry.npm.taobao.org/crypto/download/crypto-1.0.1.tgz",
"integrity": "sha1-KvG3ytgXXSTIobB3glV5SiGAMDc="
}
}
}
{
"name": "",
"version": "1.0.0",
"main": "",
"license": "MIT",
"dependencies": {
"@tbmp/mp-cloud-sdk": "*",
"crypto": "^1.0.1"
}
}
<view class="goods-page-container">
<goods-modal
isBackFlag="{{isBackFlag}}"
goodsList="{{goodsList}}"
task="{{task}}"
onCompleteTask="onCompleteTask" />
<!-- 倒计时 -->
<count-down-modal a:if="{{isShowCountdown && completeType == 1}}" percent="{{percent}}" hasPercent="{{(10 - usePercent)}}" styleType="{{'01'}}" />
</view>
\ No newline at end of file
/**
* 任务类型(taskType): collectGoods 、 orderGoods 、 browseGoods 、exchangeCredits
* task: {
* itemId: //商品ID
* taskType: //任务类型
* type: //头部样式(02——banner,01——标题)
* image: {//图片
* bg: //集合页背景
* banner: //头部banner
* title: //头部title
* collect: //商品以收藏
* no_collect: //商品未收藏
* }
* color: //商品名称:文字颜色
* }
*/
import API from '../../api';
const app = getApp();
const { tbcc } = app;
const { commonToast } = tbcc.tb;
const completeType = 1 // 1 浏览完成 2 点击任意商品完成
const completeTime = 10 // 浏览时间(s)
const taskConfig = {
taskType: 'browseGoods',
type: '01',
image: {
bg: '//yun.duiba.com.cn/taobaomini/clientCTest/goods_bg@2x.png',
banner: '//yun.duiba.com.cn/taobaomini/clientCTest/goods_banner@2x.png',
title: '//yun.duiba.com.cn/taobaomini/clientCTest/goods_title@2x.png',
collect: '//yun.duiba.com.cn/taobaomini/clientCTest/goods_collection@2x.png',
no_collect: '//yun.duiba.com.cn/taobaomini/clientCTest/collection_no_collect@2x.png',
img: '//yun.duiba.com.cn/taobaomini/clientCTest/goods_img@2x.png',
},
color: '#181818'
}
Page({
timer: null,
data: {
completeType: completeType,
usePercent: 0, // 已进行的进度条
percent: 0,// 进度条
isShowCountdown: false, // 是否显示倒计时
query: {},
task: taskConfig,
goodsList: []
},
onLoad(query) {
this.query = query || {}
this.initPage()
},
onShow() {
//重新回到页面开启倒计时
if(!this.timer && completeType == 1) {
this.setData({ usePercent: this.data.percent })
this.browseGoodsTimes()
}
},
onHide() {
// 页面隐藏
if (this.timer) {
clearInterval(this.timer)
}
},
onUnload() {
// 页面被关闭
if (this.timer) {
clearInterval(this.timer)
}
},
async initPage() {
const { activityId } = app
const { itemIds } = this.query
const { success, data } = await API.getItemListByItemIds({ activityId, itemIds }).catch(res => {
res && commonToast(res)
}) || {}
if(success) {
this.setData({ goodsList: data.list || [] })
}
},
//任务完成
async onCompleteTask(itemId) {
const { activityId } = app
let browsResult = await API.doBrowseGoodsTask({ activityId, itemId }).catch(res => {
res && commonToast(res)
}) || {}
if(browsResult.success) {
commonToast('浏览成功')
}
},
/**
* 跳转商品详情页
*/
onOpenDetail(itemId = '') {
if(completeType == 1) {
this.setData({
percent: this.data.usePercent,
}, () => {
clearInterval(this.timer)
})
}
else {
this.onCompleteTask(itemId)
}
},
/**
* 浏览商品倒计时
*/
browseGoodsTimes() {
this.timer = setInterval(() => {
this.setData({
usePercent: this.data.usePercent + 1
}, () => {
if (this.data.usePercent >= completeTime) {
this.onCompleteTask()
clearInterval(this.timer)
}
})
},1000)
}
});
{
"usingComponents": {
"goods-modal": "/components/goodsModal/goodsModal",
"count-down-modal": "/components/countDownModal/countDownModal"
}
}
\ No newline at end of file
<view class="goodsPage">
<goods-modal
goodsList="{{goodsList}}"
task="{{task}}"
onCompleteTask="onCompleteTask" />
</view>
\ No newline at end of file
/**
* 任务类型(taskType):collectGoods、 orderGoods 、 browseGoods 、exchangeCredits
* task: {
* itemId: //商品ID
* taskType: //任务类型
* type: //头部样式(02——banner,01——标题)
* image: {//图片
* bg: //集合页背景
* banner: //头部banner
* title: //头部title
* collect: //商品以收藏
* no_collect: //商品未收藏
* }
* color: //商品名称:文字颜色
* }
*/
import API from '../../api';
const app = getApp();
const { tbcc } = app;
const { commonToast } = tbcc.tb;
const taskConfig = {
taskType: 'collectGoods',
type: '01',
image: {
bg: '//yun.duiba.com.cn/taobaomini/clientCTest/goods_bg@2x.png',
banner: '//yun.duiba.com.cn/taobaomini/clientCTest/goods_banner@2x.png',
title: '//yun.duiba.com.cn/taobaomini/clientCTest/goods_title@2x.png',
collect: '//yun.duiba.com.cn/taobaomini/clientCTest/goods_collection@2x.png',
no_collect: '//yun.duiba.com.cn/taobaomini/clientCTest/collection_no_collect@2x.png',
img: '//yun.duiba.com.cn/taobaomini/clientCTest/goods_img@2x.png',
},
color: '#181818'
}
Page({
data: {
query: {},
task: taskConfig,
goodsList: []
},
onLoad(query) {
this.query = query || {}
this.initPage()
},
onShow() {
},
async initPage() {
const { activityId } = app
const { itemIds } = this.query
const { success, data } = await API.getCollectGoodsList({ activityId, itemIds }).catch(res => {
res && commonToast(res)
}) || {}
if(success) {
this.setData({ goodsList: data.list || [] })
}
},
//任务完成 616444374083
async onCompleteTask(taskType,itemId) {
console.log(taskType,itemId)
const { activityId } = app
let { success, data } = await API.doCollectGoodsTask({ activityId, itemId }).catch(res => {
res && commonToast(res)
}) || {}
if(success) {
commonToast('收藏成功')
this.initPage()
}
}
});
{
"usingComponents": {
"goods-modal": "/components/goodsModal/goodsModal"
}
}
\ No newline at end of file
<view class="end-page-container">
<end-modal data="{{endData}}" />
</view>
\ No newline at end of file
Page({
data: {
endData: {
image: {
bg: '//yun.dui88.com/taobaomini/clientCTest/end_bg@2x.png',
end_icon: '//yun.dui88.com/taobaomini/clientCTest/end_icon@2x.png',
},
text: '活动未开始'
}
},
onLoad() {},
});
{
"usingComponents": {
"end-modal": "/components/endModal/endModal"
}
}
\ No newline at end of file
.index-page {
min-height: 100vh;
}
.index-rule-btn, .index-myprize-btn {
position: absolute;
top: 30rpx;
width: 126rpx;
height: 48rpx;
line-height: 48rpx;
font-size: 22rpx;
background: rgba(0, 0, 0, 0.8);
color: #FFF;
text-align: center;
z-index: 5;
}
.index-rule-btn {
left: 0;
border-radius: 0 48rpx 48rpx 0;
}
.index-myprize-btn {
right: 0;
border-radius: 48rpx 0 0 48rpx;
}
.index-page__title {
font-size: 50rpx;
text-align: center;
padding: 100rpx 0 50rpx 0;
}
.index-page__content {
text-align: center;
font-size: 30rpx;
}
.index-page__tips {
padding-top: 40rpx;
text-align: center;
font-size: 22rpx;
}
.index-start-btn {
background: #0077a9;
color: #FFF;
width: 300rpx;
height: 80rpx;
line-height: 80rpx;
text-align: center;
border-radius: 10rpx;
margin: 100rpx auto 50rpx auto;
font-size: 30rpx;
}
.index-tbcc-btn {
background: #0077a9;
color: #FFF;
width: 300rpx;
height: 80rpx;
line-height: 80rpx;
text-align: center;
border-radius: 10rpx;
margin: 0 auto;
font-size: 30rpx;
}
.index-link-btn {
text-align: center;
color: #333;
font-size: 26rpx;
}
\ No newline at end of file
<view class="index-page">
<view class="index-rule-btn" onTap="showRuleModal">活动规则</view>
<navigator open-type="navigate" url="/pages/myprize/myprize" class="index-myprize-btn">我的奖品</navigator>
<view class="index-page__title">{{title}}</view>
<view class="index-page__content">{{content}}</view>
<view class="index-page__tips">mock接口只支持PC端预览,手机淘宝预览需要使用云函数模式</view>
<view class="index-start-btn" onTap="doStart">开始</view>
<navigator open-type="navigate" url="/pages/tbccDemo/tbccDemo" class="index-tbcc-btn">tbcc demo</navigator>
<navigator style="{{{'marginTop':'16rpx'}}}" open-type="navigate" url="/pages/goodsPage/goodsPage?itemId=617724147979,617724563528" class="index-tbcc-btn">商品集合页面</navigator>
</view>
<rule-modal a:if="{{ruleModalVisible}}" onModalClose="closeRuleModal"><text>{{rule}}</text></rule-modal>
<common-modal a:if="{{commonModalVisible}}" data="{{commonModalData}}" onModalClose="closeCommonModal" onModalConfirm="confirmCommonModal" />
\ No newline at end of file
import { SHARE_CONFIG, REDIRECT_URL } from '../../constants';
import API from '../../api';
const app = getApp();
const { tbcc } = app;
const { commonToast, getAuthUserInfo, navigateToOutside } = tbcc.tb;
Page({
data: {
title: '',
content: '',
rule: '',
commonModalData: {
content: '',
btnText: ''
},
commonModalConfirm: null,
commonModalVisible: false,
ruleModalVisible: false
},
onLoad(query) {
// 页面加载
this.getAuth();
console.info(`Page onLoad with query: ${JSON.stringify(query)}`);
},
init() {
this.getActivityBaseInfoById();
},
onReady() {
// 页面加载完成
},
onShow() {
// 页面显示
},
onHide() {
// 页面隐藏
},
onUnload() {
// 页面被关闭
},
onTitleClick() {
// 标题被点击
},
onPullDownRefresh() {
// 页面被下拉
},
onReachBottom() {
// 页面被拉到底部
},
/**
* 获取用户授权信息
*/
async getAuth() {
const userInfo = await getAuthUserInfo().catch(err => {
console.log('未授权成功', err);
});
if (userInfo) {
console.log('userInfo', userInfo);
const { nickName, avatar } = userInfo;
app.nickName = nickName;
app.avatar = avatar;
this.login();
}
},
/**
* 获取主接口
*/
async getActivityBaseInfoById() {
const { success, data } = await API.getActivityBaseInfoById().catch(res => {
commonToast(res && res.message);
}) || {};
if (success && data) {
const { title, content, rule } = data;
this.setData({ title, content, rule });
}
},
/**
* 登录接口
*/
async login() {
const { nickName, avatar } = app;
const { success } = await API.login({
userNick: nickName, avatar, isFollow: true,entry:1
}).catch(res => {
commonToast(res && res.message);
}) || {};
if (success) {
this.init();
}
},
/**
* 显示规则弹层
*/
showRuleModal() {
this.setData({
ruleModalVisible: true
});
},
/**
* 通用弹出确认按钮回调
*/
confirmCommonModal() {
const { commonModalConfirm } = this.data;
commonModalConfirm && commonModalConfirm();
},
/**
* 执行开始
*/
doStart() {
this.showCommonModal({
content: '活动还未开始\n请稍后',
btnText: '我知道了',
commonModalVisible: true,
confirmCallback: () => {
this.closeCommonModal();
}
});
},
/**
* 显示通用弹层
* @param {object} options content: 通用弹层内容 btnText:按钮文案 confirmCallback: 按钮确认回调
*/
showCommonModal({ content, btnText, confirmCallback }) {
this.setData({
commonModalConfirm: confirmCallback || null,
commonModalVisible: true,
commonModalData: { content, btnText }
});
},
/**
* 跳转到天猫页面
*/
goTmallPage() {
navigateToOutside(REDIRECT_URL['tmall']);
},
/**
* 关闭通用弹层
*/
closeCommonModal() {
this.setData({
commonModalVisible: false
});
},
/**
* 关闭活动规则弹层
*/
closeRuleModal() {
this.setData({
ruleModalVisible: false
});
},
onShareAppMessage(res) {
console.log(res)
// 返回自定义分享信息
return SHARE_CONFIG;
},
});
{
"usingComponents": {
"rule-modal": "/components/ruleModal/ruleModal",
"common-modal": "/components/commonModal/commonModal"
}
}
\ No newline at end of file
.my-prize-container {
width: 100vw;
height: 100vh;
opacity: 1;
position: absolute;
top: 0;
left: 0;
overflow: hidden;
/* background: url('') no-repeat center top/cover; */
}
.my-prize-container .my-prize-label {
width: 208rpx;
height: 36rpx;
margin: 140rpx auto 40rpx;
background: url('//yun.dui88.com/duiba-components-c-myprizeprize_title.png') no-repeat;
background-size: 100% 100%;
}
.my-prize-content {
position: absolute;
left: 0;
top: 363rpx;
bottom: 0;
width: 100%;
}
.my-prize-content__scroll {
width: 100%;
height: 100%;
padding-top: 30rpx;
}
.my-prize-container .my-prize-list {
width: 100%;
height: auto;
}
.my-prize-container .my-prize-item__empty {
text-align: center;
color: #fff;
margin-top: 30rpx;
}
.my-prize-list .my-prize-item {
display: flex;
justify-content: center;
align-items: center;
padding: 0 28rpx 0 26rpx;
margin: 0 auto 24rpx;
position: relative;
box-sizing: border-box;
width: 700rpx;
height: 161rpx;
background-color: #ffffff;
box-shadow: 2rpx 3rpx 18rpx 0 rgba(230, 146, 255, 0.6);
border-radius: 10rpx;
box-sizing: border-box;
}
.my-prize-list .my-prize-item .my-prize-item__avatar {
width: 120rpx;
height: 120rpx;
}
.my-prize-list .my-prize-item .my-prize-item__content {
flex: 1;
min-width: 0;
padding: 0 20rpx;
display: flex;
flex-direction: column;
height: 130rpx;
align-items: flex-start;
justify-content: center
}
.my-prize-list .my-prize-item .my-prize-item__subtitle {
display: flex;
justify-content: center;
align-items: center;
}
.my-prize-list .my-prize-item .my-prize-item__copy {
width: 20rpx;
height: 20rpx;
margin-left: 18rpx;
}
.my-prize-list .my-prize-item .prize-item__status {
width: 150rpx;
}
.prize-item__status>image {
width: 100%;
}
.my-prize-list .my-prize-item .my-prize-item__name {
width: 100%;
font-size: 28rpx;
color: #2d2d2d;
letter-spacing: -1rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.my-prize-list .my-prize-item .my-prize-item__tip {
margin-top: 8rpx;
font-size: 20rpx;
line-height: 20rpx;
letter-spacing: 0;
color: #8a8a8a;
}
.my-prize-list .my-prize-item .my-prize-item__code {
width: 290rpx;
font-family: PingFangSC-Regular;
font-size: 20rpx;
color: #8a8a8a;
letter-spacing: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
\ No newline at end of file
<view class="my-prize-container" style="{{{'background':`url(${prizeConfig.bg}) no-repeat center top/cover`}}}">
<view class="my-prize-label" style="{{{'background':`url(${prizeConfig.title}) no-repeat`,'backgroundSize':'100% 100%'}}}"></view>
<view class="my-prize-content" style="{{ {'top': prizeConfig.contentTop }}}">
<scroll-view scroll-y="{{true}}" class="my-prize-content__scroll">
<view class="my-prize-list" a:if="{{myPrizeList && myPrizeList.length > 0}}">
<view class="my-prize-item" a:for="{{myPrizeList}}">
<image class="my-prize-item__avatar" src="{{item.image}}"/>
<view class="my-prize-item__content">
<label class="my-prize-item__name">{{item.name}}</label>
<view class="my-prize-item__subtitle" a:if="{{isShowPirzeId}}">
<label class="my-prize-item__code">奖品编码:{{item.id || item._id}}</label>
<!-- 复制按钮 -->
<image class="my-prize-item__copy" data-text="{{item.id || item._id}}" onTap="handleCopyId" src="{{prizeConfig.copyIcon}}" />
</view>
<view class="my-prize-item__tip" a:if="{{item.drawStatus === DRAW_STATUS.FAIL}}">{{item.remark}}</view>
<view class="my-prize-item__tip" a:if="{{item.drawStatus === DRAW_STATUS.RETRY || (item.drawStatus === DRAW_STATUS.WAITAWARD)}}">请于{{endTime}}前领取</view>
<view class="my-prize-item__tip" a:if="{{item.drawStatus === DRAW_STATUS.EXPIRED }}">奖品已过期失效</view>
</view>
<view class="prize-item__status" style="{{prizeConfig.btnStyle}}">
<!-- 实物奖品状态 -->
<block a:if="{{item.type === PRIZE_TYPE.OBJECT}}">
<image onTap="handleClick" data-item="{{item}}" src="{{objectStatus[item.drawStatus]}}" mode="widthFix" />
</block>
<!-- 非实物奖品状态 -->
<block a:else>
<image onTap="handleClick" data-item="{{item}}" src="{{(item.drawStatus === 3 && !item.useUrl) ? prizeConfig['receiveBtn'] : enameStatus[item.drawStatus]}}" mode="widthFix" />
</block>
</view>
</view>
</view>
<view a:if="{{myPrizeList && myPrizeList.length === 0}}" class="my-prize-item__empty">{{prizeConfig.blankTxt}}</view>
</scroll-view>
</view>
</view>
<logistics-modal
a:if="{{isLogisticModal}}"
prizeItem="{{currentPrize}}"
onCloseModal="closeLogisticModal"
task="{{logisticsImage}}" />
\ No newline at end of file
'use strict';
import API from "/api";
const app = getApp();
const { tbcc } = app;
const { commonToast, navigateToOutside, getUserAddress, setClipboard } = tbcc.tb;
const { dateFormatter, throttleHandle } = tbcc.utils;
const { PRIZE_TYPE, DRAW_STATUS } = tbcc.constants;
// 我的奖品配置项
const prizeConfig = {
bg: '//yun.duiba.com.cn/aurora/assets/e13fab3165d3a6bb4cbe8827e8f0fd41084b0091.png', //背景图
title: '//yun.dui88.com/duiba-components-c-myprizeprize_title.png',//标题
copyIcon: '//yun.dui88.com/duiba-components-c-myprizemyprize_copy_code.png', //复制
receiveBtn: '//yun.duiba.com.cn/bestoreBox/prize_received.png', //已领取 权益类奖品未配置 uesUrl使用
commonStatus: {
6: '//yun.dui88.com/duiba-components-c-myprizeget_again.png', //重新领取
5: '//yun.duiba.com.cn/spark/assets/914ba8643c8abb8d710a3126ec312aecf6c4daf0.png', //已失效
4: '//yun.duiba.com.cn/spark/assets/8267375232058a6d0140270b1594427fb08d6ae3.png', //领取失败
},
objectStatus: {
1: '//yun.dui88.com/duiba-components-c-myprizeto_address.png', //填写地址(实物奖品)
3: '//yun.dui88.com/duiba-components-c-myprizelogistics.png', //查看物流
},
enameStatus: {
1: '//yun.dui88.com/duiba-components-c-myprizeget_now.png', //立即领取(除实物奖品外)
3: '//yun.duiba.com.cn/bestoreBox/prize_received.png', //去使用(除实物奖品外)
},
logisticsImage: {
shipped: '//yun.dui88.com/duiba-components-c-myprizeprize_shipped.png', // 已发货物理弹窗
notShipped: '//yun.dui88.com/duiba-components-c-myprizenotshipped.png', // 待发货物流弹窗
},
btnStyle: {
width: '150rpx'
},
contentTop: '210rpx',
blankTxt: '暂无奖品',
hasLogistic: true, // 是否展示物流信息
isShowPirzeId: true // 是否显示奖品编号
}
Page({
data: {
PRIZE_TYPE,
DRAW_STATUS,
myPrizeList: [], //我的奖品
endTime: null,//到期时间
prizeConfig: prizeConfig,
objectStatus: {
...prizeConfig.commonStatus,
...prizeConfig.objectStatus
},
enameStatus: {
...prizeConfig.commonStatus,
...prizeConfig.enameStatus
},
logisticsImage: {
...prizeConfig.logisticsImage,
copyIcon: prizeConfig.copyIcon
},
hasLogistic: prizeConfig.hasLogistic,
isShowPirzeId: prizeConfig.isShowPirzeId,
isLogisticModal: false,
currentPrize: {}
},
onLoad() {
this.getMyPrize();
},
onShow() {
},
// 到期时间
getEndTime(endTime) {
if (!endTime) return null
this.setData({
endTime: dateFormatter(endTime, 'yyyy/MM/dd hh:mm:ss')
})
console.log(this.data.endTime)
},
/**
* 获取奖品列表
* @param {string}
*/
async getMyPrize() {
const { activityId } = app;
const { success, data } = await API.getMyPrizeList({ activityId }).catch(res => {
commonToast(res && res.message);
}) || {};
if (success && data) {
const { list } = data
this.setData({
myPrizeList: list
})
const endTime = list && list[0] && list[0].expiredTime
this.getEndTime(endTime)
}
},
//领取奖品
handleClick: throttleHandle(async function(e) {
const { type, drawStatus, useUrl = '', id, _id } = e.target.dataset.item
const { hasLogistic } = this.data
if(drawStatus == 3) {
if(type == PRIZE_TYPE.OBJECT && hasLogistic) {
this.setData({ currentPrize: e.target.dataset.item, isLogisticModal: true })
return;
}
if(type != PRIZE_TYPE.OBJECT && useUrl) {
navigateToOutside(useUrl)
return;
}
}
if(!(drawStatus == 6 || drawStatus == 1)) {
return;
}
// 领取权益
const prizeId = id || _id
if (type === PRIZE_TYPE.ENAME) return this.handleGetEquity(prizeId)
// 领取实物
if (type === PRIZE_TYPE.OBJECT) return this.handleGetEntity(prizeId)
// 领取积分
if (type === PRIZE_TYPE.CREDITS) return this.handleGetIntergation(prizeId)
},2000),
// 优惠券
async handleGetEquity(id) {
const { activityId } = app;
const { success, data, message } = await API.receiveEnamePrize({ activityId, id }).catch(res => {
this.getMyPrize()
commonToast(res && res.message)
}) || {};
if (success && data) {
commonToast('领取成功')
this.getMyPrize()
}
},
// 领取实物前询问
async handleGetEntity(id) {
const { activityId } = app;
const userAddress = await getUserAddress().catch(err => {
commonToast(err && err.errorMessage);
});
if (!userAddress) return
const { name, telNumber, provinceName, cityName, cityCode, countyName, detailInfo, streetName } = userAddress || {};
const params = {
activityId,
name,
phone: telNumber,
addressDetail: detailInfo,
cityCode,
city: cityName,
province: provinceName,
area: countyName,
streetName,
id
}
my.confirm({
title: '提示',
content: '确认使用该收货地址:' + name + telNumber + userAddress.duibaAddress.address,
confirmButtonText: '确定',
cancelButtonText: '取消',
success: (result) => {
console.log(result)
const { confirm } = result;
if (confirm) {
this.receiveObjectPrize(params);
}
},
});
},
// 领取实物
async receiveObjectPrize(params) {
const { success, data } = await API.receiveObjectPrize(params).catch(res => {
commonToast(res && res.message)
}) || {}
if (success && data) {
commonToast('领取成功')
}
this.getMyPrize()
},
// 积分
async handleGetIntergation(id) {
// 判断是否为会员
const { isVip, url } = await this.getUserInfo() || {}
if (!isVip) {
commonToast('需加入会员才能领取成功哦', 2000,() => { navigateToOutside(url) });
return;
}
const { activityId } = app;
const { success, data } = await API.receiveEnamePrize({ activityId, id }).catch(res => {
commonToast(res && res.message)
}) || {};
if (success && data) {
commonToast('领取成功')
}
this.getMyPrize()
},
//获取用户vip信息
async getUserInfo() {
const { activityId } = app;
const { success, data } = await API.getVipInfo({ activityId }).catch(res => {
commonToast(res && res.message)
}) || {};
if (success && data) {
return data
}
},
// 复制编码
async handleCopyId(e) {
const { text } = e.target.dataset || {}
await setClipboard({ text })
commonToast('复制成功')
},
/**
* 关闭物流弹窗
*/
closeLogisticModal() {
this.setData({ isLogisticModal: false })
},
});
\ No newline at end of file
{
"usingComponents": {
"logistics-modal": "/components/logisticsModal/logisticsModal"
}
}
\ No newline at end of file
<view class="goodsPage">
<goods-modal
goodsList="{{goodsList}}"
task="{{task}}"
onCompleteTask="onCompleteTask" />
</view>
\ No newline at end of file
/**
* 任务类型(taskType): collectGoods 、 orderGoods 、 browseGoods 、exchangeCredits
* task: {
* itemId: //商品ID
* taskType: //任务类型
* type: //头部样式(02——banner,01——标题)
* image: {//图片
* bg: //集合页背景
* banner: //头部banner
* title: //头部title
* collect: //商品以收藏
* no_collect: //商品未收藏
* }
* color: //商品名称:文字颜色
* }
*/
import API from '../../api';
const app = getApp();
const { tbcc } = app;
const { commonToast } = tbcc.tb;
const taskConfig = {
taskType: 'orderGoods',
type: '01',
image: {
bg: '//yun.duiba.com.cn/taobaomini/clientCTest/goods_bg@2x.png',
banner: '//yun.duiba.com.cn/taobaomini/clientCTest/goods_banner@2x.png',
title: '//yun.duiba.com.cn/taobaomini/clientCTest/goods_title@2x.png',
collect: '//yun.duiba.com.cn/taobaomini/clientCTest/goods_collection@2x.png',
no_collect: '//yun.duiba.com.cn/taobaomini/clientCTest/collection_no_collect@2x.png',
img: '//yun.duiba.com.cn/taobaomini/clientCTest/goods_img@2x.png',
},
color: '#181818'
}
Page({
data: {
query: {},
task: taskConfig,
goodsList: []
},
onLoad(query) {
this.query = query || {}
this.initPage()
},
onShow() {
},
async initPage() {
const { activityId } = app
const { itemIds } = this.query
const { success, data } = await API.getItemListByItemIds({ activityId, itemIds }).catch(res => {
res && commonToast(res)
}) || {}
if(success) {
this.setData({ goodsList: data.list || [] })
}
},
});
{
"usingComponents": {
"goods-modal": "/components/goodsModal/goodsModal"
}
}
\ No newline at end of file
.tbcc-demo-page {
text-align: center;
padding: 60rpx 20rpx 20rpx;
}
.tbcc-demo-page button {
margin-bottom: 10rpx;
}
.tbcc-demo-page .upload-image {
width: 200rpx;
height: 200rpx;
}
.tbcc-demo-page .input-item {
width: 684rpx;
height: 80rpx;
}
.text-risk-item {
margin: 30rpx auto;
}
.tbcc-demo__title {
padding: 20rpx;
font-weight: bold;
font-size: 36rpx;
}
\ No newline at end of file
<view class="tbcc-demo-page">
<view class="tbcc-demo__title">常用tb方法</view>
<button onTap="showCommomToast">弱提示</button>
<button onTap="showCommomConfirm">通用确认弹层</button>
<button onTap="getAuth">获取用户授权</button>
<button onTap="getSystemInfo">获取系统信息</button>
<button onTap="getServerTime">获取系统时间</button>
<button onTap="favorShop">关注店铺(百草味为例需预览)</button>
<button onTap="checkShopFavoredStatus">店铺关注状态(百草味为例需预览)</button>
<button onTap="getUserAddress">获取收货地址(需预览)</button>
<button onTap="collectGoods">收藏商品(iPad Pro为例)</button>
<button onTap="checkGoodsCollectedStatus">商品收藏状态(iPad Pro为例)</button>
<button onTap="openDetail">跳转淘宝商品页(iPad Pro为例)</button>
<button onTap="navigateToOutside">跳转外部链接(需预览)</button>
<button onTap="navigateTo">跳转内部链接(新开窗口)</button>
<button onTap="redirectTo">跳转内部链接(不新开窗口)</button>
<button onTap="uploadImage">选择上传图片</button>
<view a:if="{{uploadImgUrl}}">
<image mode="aspectFit" src="{{uploadImgUrl}}" class="upload-image"></image>
</view>
<button onTap="chattingSendCard">分享到群聊卡片(需预览)</button>
<view class="text-risk-item">
<input type="text" onInput="bindKeyInput" data-name="riskInput" placeholder="请输入校验文本" class="input-item" />
<button onTap="textRiskIdentification">文本风险检测(需预览)</button>
</view>
<view class="tbcc-demo__title">通用API方法</view>
<button onTap="receiveObjectPrize">领取实物奖励</button>
<button onTap="receiveEnamePrize">领取权益奖励</button>
</view>
import API from '../../api';
const app = getApp();
const { tbcc } = app;
const {
commonToast,
simpleAlert,
commonConfirm,
getAuthUserInfo,
getSystemInfo,
getServerTime,
favorShop,
checkShopFavoredStatus,
getUserAddress,
collectGoods,
checkGoodsCollectedStatus,
openDetail,
navigateToOutside,
navigateTo,
redirectTo,
uploadImage,
chattingSendCard,
textRiskIdentification
} = tbcc.tb;
const { dateFormatter, versionCompare } = tbcc.utils;
Page({
data: {
uploadImgUrl: '',
riskInput: ''
},
onLoad() {},
bindKeyInput(e) {
const { value } = e.detail;
const { name } = e.target.dataset;
this.setData({
[name]: value,
});
},
/**
* toast提示
*/
showCommomToast() {
commonToast('tbcc', 3000, () => {
console.log('tbcc toast loaded');
});
},
/**
* confirm弹窗
*/
showCommomConfirm() {
commonConfirm('提示', '内容', () => {
commonToast('保存成功');
});
},
/**
* 获取用户授权信息
*/
async getAuth() {
const userInfo = await getAuthUserInfo().catch(err => {
console.log('未授权成功', err);
});
if (userInfo) {
console.log('userInfo', userInfo);
const { nickName, avatar } = userInfo;
app.nickName = nickName;
app.avatar = avatar;
simpleAlert(nickName);
}
},
/**
* 获取系统信息
*/
async getSystemInfo() {
const systemInfo = await getSystemInfo().catch(err => {
console.log('获取系统信息失败', err);
});
if (systemInfo) {
console.log('systemInfo', systemInfo);
const { version } = systemInfo;
const compareVersion = '9.8.0';
const isNewVersion = versionCompare(version, compareVersion);
simpleAlert('当前版本:' + version + `${isNewVersion ? '为新版本': '为老版本'}`);
}
},
/**
* 获取系统时间
*/
async getServerTime() {
const severTime = await getServerTime().catch(err => {
console.log('获取系统时间失败', err);
});
simpleAlert('系统时间:' + dateFormatter(severTime, 'yyyy/MM/dd hh:mm:ss'));
},
/**
* 关注店铺
*/
async favorShop() {
const sellerId = 628189716; // 百草味sellerId
const favorStatus = await favorShop(sellerId).catch(err => {
console.log('关注店铺失败', err);
});
commonToast(favorStatus ? '关注成功' : '关注失败');
},
/**
* 查询店铺关注状态
*/
async checkShopFavoredStatus() {
const sellerId = 628189716; // 百草味sellerId
const favoredStatus = await checkShopFavoredStatus(sellerId).catch(err => {
console.log('查询关注店铺状态失败', err);
});
simpleAlert(favoredStatus ? '已关注店铺' : '未关注店铺');
},
/**
* 获取用户收货地址
*/
async getUserAddress() {
const userAddress = await getUserAddress().catch(err => {
commonToast(err.errorMessage);
});
if (userAddress) {
console.log('userAddress', userAddress);
const { name, phone, address } = userAddress.duibaAddress;
commonConfirm('提示', '确认使用该收货地址:' + name + phone + address, () => {
// TODO 保存接口
commonToast('保存成功');
});
}
},
/**
* 收藏商品
*/
async collectGoods() {
const itemId = 622499363381; // iPad Pro 为例
const collectGoodsStatus = await collectGoods(itemId).catch(err => {
console.log('收藏商品失败', err);
});
commonToast(collectGoodsStatus ? '收藏商品成功' : '收藏商品失败');
},
/**
* 收藏商品
*/
async checkGoodsCollectedStatus() {
const itemId = 622499363381; // iPad Pro 为例
const goodscollectedStatus = await checkGoodsCollectedStatus(itemId).catch(err => {
console.log('查询商品收场状态失败', err);
});
simpleAlert(goodscollectedStatus ? '商品已收藏' : '商品未收藏');
},
/**
* 跳转到淘宝商品页
*/
async openDetail() {
const itemId = '622499363381'; // iPad Pro 为例
await openDetail(itemId).catch(err => {
console.log('打开淘宝商品页失败', err);
});
},
/**
* 跳转到外部链接
*/
navigateToOutside() {
const url = 'https://www.tmall.com';
navigateToOutside(url);
},
/**
* 跳转到小程序内部链接(新开窗口)
*/
navigateTo() {
const url = '/pages/index/index';
navigateTo(url);
},
/**
* 跳转到小程序内部链接(不新开窗口)
*/
redirectTo() {
const url = '/pages/index/index';
redirectTo(url);
},
/**
* 选择上传图片
*/
async uploadImage() {
const uploadImageResultUrl = await uploadImage().catch(err => {
commonToast(err && (err.errorMessage || '上传图片失败'));
});
if (uploadImageResultUrl) {
this.setData({
uploadImgUrl: uploadImageResultUrl
});
}
},
/**
* 分享到群聊卡片
*/
async chattingSendCard() {
// 测试请求参数(格式示例)
const requestInfo = {
"biz_ext_map": "{\"activityId\":\"028fe748-9413-4df1-ad15-7d21d9ca16c9\",\"shareId\":\"AAGrFC1BALWl0Axwb3svqUyd\"}",
"card_template_id": 1588754547060,
"group_id": "0_G_2207623078795_1591670473134",
"weex_data_json": "{\"url\":\"https://m.duanqu.com?_ariver_appid=3000000005188064&nbsv=0.1.2007151634.12&nbsource=debug&nbsn=DEBUG&_mp_code=tb&query=activityId%3D028fe748-9413-4df1-ad15-7d21d9ca16c9%26groupId%3D0_G_2207623078795_1591670473134%26shareId%3DAAGrFC1BALWl0Axwb3svqUyd\",\"act_title\":\"一起出道赢大奖\",\"share\":\"@pilee0827 多度\",\"picture3\":\"https://yun.duiba.com.cn/upload/ltuwhbf39x.jpg\"}",
"message_id": "5f0fcdfeef507182be6c2247",
"app_secret": "appSecret",
"app_code": "30123678"
};
const messageResult = await chattingSendCard(requestInfo).catch(err => {
commonToast(err && err.errorMessage);
});
if (messageResult) {
const { result: { value, success } } = messageResult;
commonToast(value && success ? '分享到群聊成功' : '分享到群聊失败');
}
},
/**
* 文本风险校验
*/
async textRiskIdentification() {
const { riskInput } = this.data;
const success = await textRiskIdentification(riskInput).catch(err => {
console.log(err);
});
simpleAlert(`检测文本"${riskInput}",校验结果${success ? '通过' : '不通过'}`);
},
/**
* 领取实物奖励
*/
async receiveObjectPrize() {
const _id = '123'; // 测试奖品ID
const success = await API.fn.receiveObjectPrize({ _id });
if (success) {
commonToast('保存成功');
}
},
/**
* 领取权益奖励
*/
async receiveEnamePrize() {
const _id = '123'; // 测试奖品ID
const success = await API.fn.receiveEnamePrize({ _id });
if (success) {
commonToast('领取成功');
}
}
});
import * as tb from './lib/tb';
import * as tbMock from './lib/tbMock';
import * as utils from './lib/utils';
import * as constants from './lib/constants';
import capi from './lib/capi';
import request from './lib/request';
const isH5 = 'undefined' !== typeof window;
// 如果为h5环境走mock数据
let _tb = {};
Object.entries(tb).forEach(([key, value]) => {
_tb[key] = isH5 ? tbMock[key] : value;
});
export default {
capi,
tb: _tb,
utils,
request,
constants
};
\ No newline at end of file
const capi = {
getMyPrizeList: {
method: 'GET',
handle: 'getMyPrizeList',
isShowLoading: true
},
receiveObjectPrize: { method: 'GET', handle: 'receiveObjectPrize' },
receiveEnamePrize: { method: 'GET', handle: 'receiveEnamePrize' },
getItemListByItemIds: { method: 'GET', handle: 'getItemListByItemIds' },
getCollectGoodsList: { method: 'GET', handle: 'getCollectGoodsList' },
doCollectGoodsTask: { method: 'GET', handle: 'doCompleteTask', defaultParams: { taskType: 'collectGoods'} },
doBrowseGoodsTask: { method: 'GET', handle: 'doCompleteTask', defaultParams: { taskType: 'browseGoods'} },
doJumpLinkTask: { method: 'GET', handle: 'doCompleteTask', defaultParams: { taskType: 'jumpLink'} },
doExchangeCreditsTask: { method: 'GET', handle: 'doCompleteTask', defaultParams: { taskType: 'exchangeCredits'} },
getVipInfo: { method: 'GET', handle: 'getVipInfo' }
};
export default capi;
import { getUserAddress, commonToast } from './tb';
import { getRequestParams } from './utils';
// 通用API封装方法
const capiFn = (apiList, request) => {
return {
async receiveObjectPrize(params) {
const { handle, method, ext } = getRequestParams(apiList['receiveObjectPrize']);
const userAddress = await getUserAddress().catch(err => {
commonToast(err.errorMessage);
});
if (userAddress) {
const { name, phone, address } = userAddress.duibaAddress;
commonConfirm('提示', '确认使用该收货地址:' + name + phone + address, async () => {
params = {
name,
phone,
address,
...params
}
const result = await request(handle, method, params, ext).catch(res => {
commonToast(res && res.message);
});
if (result && result.success) {
return true;
}
});
}
return false;
},
async receiveEnamePrize(params) {
const { handle, method, ext } = getRequestParams(apiList['receiveEnamePrize']);
const result = await request(handle, method, params, ext).catch(res => {
commonToast(res && res.message);
});
if (result && result.success) {
return true;
}
return false;
}
}
};
export default capiFn;
/**
* 通用toast
* @param {string} content toast内容
* @param {number} duration 显示时长
* @param {function} successCb 成功回调
*/
export const commonToast = (content, duration = 3000, successCb) => {
return '暂不支持toast';
};
/**
* 简易alert
* @param {string} content toast内容
*/
export const simpleAlert = content => {
alert(content);
};
/**
* 通用确认弹窗
* @param {string} title 标题
* @param {string} content 内容
* @param {function} successCb 成功回调
*/
export const commonConfirm = (title = '提示', content, successCb) => {
my.confirm({
title,
content,
confirmButtonText: '确定',
cancelButtonText: '取消',
success: result => {
const { confirm } = result;
if (confirm) {
successCb && successCb();
}
}
});
};
/**
* 获取授权用户信息
* @returns {object} 用户登录信息
*/
export const getAuthUserInfo = async () => {
return {
nickName: 'nickName',
avatar: '123',
mixNick: '1234'
};
};
/**
* 获取系统信息
* @returns {object} 小程序系统信息
*/
export const getSystemInfo = async () => {
return {
version: '9.5.0'
};
};
/**
* 执行关注店铺
* @param {number} sellerId 店铺归属的卖家Id
* @returns {boolean} 关注状态
*/
export const favorShop = async sellerId => {
if (!sellerId) {
console.error('favorShop: 请传入卖家Id');
return false;
}
return {};
};
/**
* 判断是否关注店铺
* @param {number} sellerId 店铺归属的卖家Id
* @returns {boolean} 关注状态
*/
export const checkShopFavoredStatus = async sellerId => {
if (!sellerId) {
console.error('checkShopFavoredStatus: 请传入卖家Id');
return false;
}
return true;
};
/**
* 跳转到外部链接
* @param {string} url 跳转链接
*/
export const navigateToOutside = url => {
if (!url) {
console.error('navigateToOutside: 请传入url');
return false;
}
window.location = url;
};
/**
* 跳转到内部链接(新开窗口)
* @param {string} url 跳转链接
*/
export const navigateTo = url => {
if (!url) {
console.error('navigateTo: 请传入url');
return false;
}
window.location = url;
};
/**
* 跳转到内部链接(不新开窗口)
* @param {string} url 跳转链接
*/
export const redirectTo = url => {
if (!url) {
console.error('redirectTo: 请传入url');
return false;
}
window.replace(url);
};
/**
* 获取服务器时间
* @returns {number} 服务器时间戳
*/
export const getServerTime = async () => {
return +new Date();
};
/**
* 收藏商品
* @param {number} 商品id
* @returns {object} 收藏结果
*/
export const collectGoods = async id => {
if (!id) {
console.error('collectGoods: 请传入商品id');
return false;
}
return {};
};
/**
* 查询商品收藏状态
* @param {number} 商品id
* @returns {boolean} 商品收场状态
*/
export const checkGoodsCollectedStatus = async id => {
if (!id) {
console.error('checkGoodsCollectedStatus: 请传入商品id');
return false;
}
return true;
};
/**
* 跳转到淘宝商品页
* @param {string} itemId 商品ID
*/
export const openDetail = async itemId => {
if (!itemId) {
console.error('openDetail: 请传入商品id');
return false;
}
window.location = 'https://item.taobao.com/item.htm?id=' + itemId;
};
/**
* 获取淘宝用户收货地址
*/
export const getUserAddress = async (opts = {}) => {
return {
name: 1,
phone: 1,
address: 123
}
};
/**
* 选择图片
*/
export const chooseImage = async (opts = {}) => {
let defaults = {
count: 1,
sourceType: ['camera', 'album'],
compressLevel: 1
};
let _opts = Object.assign({}, defaults, opts);
const { count, sourceType, compressLevel } = _opts;
return new Promise((resolve, reject) => {
if (!my.chooseImage) {
reject({
noSupport: true,
errorMessage: '当前版本不支持选择收货地址,请升级到最新版本'
});
return false;
}
my.chooseImage({
count,
sourceType,
success: res => {
my.compressImage({
apFilePaths: res.apFilePaths,
level: compressLevel,
success: data => {
resolve(data);
},
fail: err => {
resolve(err);
}
});
},
fail: err => {
// error code 2001 为摄像头授权取消、ios 11为用户取消操作不认作失败场景 、安卓 为1
if (err.error !== 2001 && err.error !== 11 && err.error !== 1) {
reject(err);
} else {
resolve(false);
}
}
});
});
};
/**
* 获取图片信息
* @param {string} src 图片地址
*/
export const getImageInfo = async src => {
return new Promise((resolve, reject) => {
my.getImageInfo({
src,
success: res => {
resolve(res);
},
fail: err => {
reject(err);
}
});
});
};
/**
* 获取图片信息
* @param {*} opts chooseImage配置参数
*/
export const uploadImage = async (opts = {}) => {
const { cloud } = getApp();
if (!cloud) {
console.error('chattingSendCard: 请传入app.cloud');
return false;
}
const chooseImageResult = await chooseImage(opts).catch(err => {
commonToast(err && (err.errorMessage || '选择图片失败'));
});
if (chooseImageResult) {
const { apFilePaths } = chooseImageResult;
const filePath = apFilePaths[0];
const imageInfo = await getImageInfo(filePath).catch(err => {
commonToast(err && err.errorMessage);
});
// 找不到图片信息
if (!imageInfo) {
return false;
}
const { path: imagePath } = imageInfo;
const { url } = await cloud.file.uploadFile({
filePath,
fileType: 'image',
fileName: imagePath.split('/').pop()
});
return url;
}
return false;
}
/**
* 分享到群聊卡片
* @param {object} requestInfo 分享到群聊卡片所需请求参数
*/
export const chattingSendCard = async (requestInfo) => {
alert('无法模拟');
return false;
};
/**
* 分享到群聊卡片
* @param {*} cloud 小程序云
* @param {object} requestInfo 分享到群聊卡片所需请求参数
*/
export const textRiskIdentification = async (text) => {
return true;
}
\ No newline at end of file
module.exports = {
env: {
NODE_ENV: '"development"'
},
defineConstants: {
},
mini: {},
h5: {
esnextModules: [ 'taro-ui' ]
}
}
// eslint-disable-next-line import/no-commonjs
const path = require('path')
const config = {
projectName: 'taro-test',
date: '2021-2-25',
designWidth: 750,
deviceRatio: {
640: 2.34 / 2,
750: 1,
828: 1.81 / 2
},
sourceRoot: 'src',
outputRoot: 'dist',
alias: {
'@': path.resolve(__dirname, '..', 'src')
},
plugins: [
[
'@pluve/taro-plugin-mars', // taro暂时未支持阿里小程序自定义插件 需要引用插件的页面需配置
{
usingComponents: {
enable: true,
include: [ 'pages/index/index', 'pages/myPrize/myPrize' ]
}
}
]
],
defineConstants: {
},
copy: {
patterns: [
],
options: {
}
},
framework: 'react',
mini: {
postcss: {
pxtransform: {
enable: true,
config: {
}
},
url: {
enable: true,
config: {
limit: 1024 // 设定转换尺寸上限
}
},
cssModules: {
enable: true, // 默认为 false,如需使用 css modules 功能,则设为 true
config: {
namingPattern: 'module', // 转换模式,取值为 global/module
generateScopedName: '[name]__[local]___[hash:base64:5]'
}
}
},
webpackChain(chain, webpack) {
chain.module
.rule('less')
.pre()
.use('style-resources')
.loader('style-resources-loader')
.options({
patterns: path.resolve(__dirname, '../src/styles/index.less'),
injector: 'append'
})
}
},
h5: {
publicPath: '/',
staticDirectory: 'static',
postcss: {
autoprefixer: {
enable: true,
config: {
}
},
cssModules: {
enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true
config: {
namingPattern: 'module', // 转换模式,取值为 global/module
generateScopedName: '[name]__[local]___[hash:base64:5]'
}
}
}
}
}
module.exports = function(merge) {
if (process.env.NODE_ENV === 'development') {
return merge({}, config, require('./dev'))
}
return merge({}, config, require('./prod'))
}
module.exports = {
env: {
NODE_ENV: '"production"'
},
defineConstants: {
},
mini: {},
h5: {
/**
* 如果h5端编译后体积过大,可以使用webpack-bundle-analyzer插件对打包体积进行分析。
* 参考代码如下:
* webpackChain (chain) {
* chain.plugin('analyzer')
* .use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin, [])
* }
*/
}
}
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": [
"./src/*"
],
}
}
}
\ No newline at end of file
{
"cloud": {
"type": "CLOUDAPP"
},
"exclude": [
"server/**"
]
}
{
"name": "taro-test",
"version": "1.0.0",
"private": true,
"description": "测试项目",
"templateInfo": {
"name": "taro-ui",
"typescript": false,
"css": "less"
},
"scripts": {
"build:weapp": "taro build --type weapp",
"build:swan": "taro build --type swan",
"build:alipay": "taro build --type alipay",
"build:tt": "taro build --type tt",
"build:h5": "taro build --type h5",
"build:rn": "taro build --type rn",
"build:qq": "taro build --type qq",
"build:jd": "taro build --type jd",
"build:quickapp": "taro build --type quickapp",
"dev:weapp": "npm run build:weapp -- --watch",
"dev:swan": "npm run build:swan -- --watch",
"dev:alipay": "npm run build:alipay -- --watch",
"dev:tt": "npm run build:tt -- --watch",
"dev:h5": "npm run build:h5 -- --watch",
"dev:rn": "npm run build:rn -- --watch",
"dev:qq": "npm run build:qq -- --watch",
"dev:jd": "npm run build:jd -- --watch",
"dev:quickapp": "npm run build:quickapp -- --watch"
},
"browserslist": [
"last 3 versions",
"Android >= 4.1",
"ios >= 8"
],
"author": "",
"dependencies": {
"@babel/helper-create-class-features-plugin": "^7.13.0",
"@babel/runtime": "^7.7.7",
"@pluve/taro-plugin-mars": "^1.1.1",
"@tarojs/cli": "v3.2.0-canary.9",
"@tarojs/components": "v3.2.0-canary.9",
"@tarojs/react": "v3.2.0-canary.9",
"@tarojs/runtime": "v3.2.0-canary.9",
"@tarojs/taro": "v3.2.0-canary.9",
"@tbmp/mp-cloud-sdk": "^1.4.2",
"classnames": "^2.2.6",
"hox": "^1.1.2",
"lodash": "4.17.15",
"react": "^16.10.0",
"react-dom": "^16.10.0",
"taro-ui": "^3.0.0-alpha.3"
},
"devDependencies": {
"@babel/core": "^7.8.0",
"@tarojs/mini-runner": "v3.2.0-canary.9",
"@tarojs/webpack-runner": "v3.2.0-canary.9",
"@types/react": "^16.0.0",
"@types/webpack-env": "^1.13.6",
"babel-eslint": "^10.1.0",
"babel-preset-taro": "v3.2.0-canary.9",
"eslint": "^7.12.1",
"eslint-config-standard": "^16.0.2",
"eslint-config-standard-jsx": "^10.0.0",
"eslint-config-standard-react": "^11.0.1",
"eslint-config-taro": "v3.2.0-canary.9",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.3.1",
"eslint-plugin-react": "^7.22.0",
"eslint-plugin-react-hooks": "^1.6.1",
"style-resources-loader": "^1.4.1",
"stylelint": "9.3.0"
}
}
{
"miniprogramRoot": "dist/",
"projectname": "taro-test",
"description": "测试项目",
"appid": "touristappid",
"setting": {
"urlCheck": true,
"es6": false,
"postcss": false,
"preloadBackgroundData": false,
"minified": false,
"newFeature": true,
"autoAudits": false,
"coverView": true,
"showShadowRootInWxmlPanel": false,
"scopeDataCheck": false,
"useCompilerModule": false
},
"compileType": "miniprogram",
"simulatorType": "alipay",
"simulatorPluginLibVersion": {},
"condition": {}
}
import cloud from '@tbmp/mp-cloud-sdk'
import tbRequest from './utils/request'
import conifg from './config'
const { cloudName, requestType } = conifg
const request = tbRequest({
cloud,
requestType,
cloudName
})
/**
const defaultConfig = {
method: 'GET',
showLoading: false, 请求是否loading
toastError: true success为false时是否toastMessage
}
* **/
export const login = (params, config) => request('login', params, config)
export const getActivityBaseInfoById = (params, config) => request('getActivityBaseInfoById', params, config)
export const getVipInfo = (params, config) => request('getVipInfo', params, config)
export const getShareInfo = (params, config) => request('getShareInfo', params, config)
export const getMyPrizeList = (params, config) => request('getMyPrizeList', params, config)
export const receiveEnamePrize = (params, config) => request('receiveEnamePrize', params, config)
export const receiveObjectPrize = (params, config) => request('receiveObjectPrize', params, config)
export const doHelp = (params, config) => request('doHelp', params, config)
export const drawLotteryPrize = (params, config) => request('drawLotteryPrize', params, config)
export const resetTest = (params, config) => request('resetTest', params, config)
export const addStat = (params, config) => request('addStat', params, config)
export const receiveTaskRewards = (params, config) => request('receiveTaskRewards', params, config)
export const getTaskList = (params, config) => request('getTaskList', params, config)
export default {
pages: [
'pages/index/index',
'pages/myPrize/myPrize',
'pages/browseGoods/browseGoods'
],
window: {
enableSkia: 'true',
allowsBounceVertical: 'NO',
navigationBarTitleText: '活动名称'
},
plugins: {
cemMember: {
version: '*',
provider: '3000000026642582'
}
}
}
import cloud from '@tbmp/mp-cloud-sdk'
import { Component } from 'react'
import './app.less'
// 使用taro-ui 按需引入的组件样式
import './taro-ui.scss'
import config from './config'
import './utils/mdProxy'
const { env } = config
cloud.init({ env })
class App extends Component {
componentDidMount() {}
onLaunch(options) {
console.log(options, 'options')
const { activityId } = options.query || {}
const app = getApp()
app.activityId = activityId || config.defaultActivityId
}
componentDidShow() {}
componentDidHide() {}
componentDidCatchError() { }
onGlobalClick(e) {
console.log('onGlobalClick :>> ', e)
}
// this.props.children 是将要会渲染的页面
render() {
return this.props.children
}
}
// export default withModel(useAuthInfoModel, authInfo => ({
// authInfo
// }))(App);
export default App
@import "./styles/animate.less";
.position-build();
.border-build();
.flex-build();
.text-build();
.layout-build();
.position-build();
import React from 'react'
import { View, Image } from '@tarojs/components'
import './DeliveryModal.less'
export default function DeliveryModal(props) {
const { prizeItem, task, onClose = () => {} } = props
return (
<View className='logistics-modal-container'>
<View
className='logistics-modal-container__content'
style={prizeItem.shipStatus === 3
? `background: url(${task.shipped}) no-repeat;background-size: 100% 100%;`
: `background: url(${task.notShipped}) no-repeat;background-size: 100% 100%;`}
>
<View className='logistics-modal-container__close' onTap={onClose} />
<View className='logistics-modal-container__content-item'>
<View className='logistics-modal-container__content-left'>
<Image mode='scaleToFill' src={prizeItem.image} />
</View>
<View className='logistics-modal-container__content-right'>
<View className='logistics-modal-container__content-name'>{prizeItem.name}</View>
<View className='logistics-modal-container__content-copy'>
<View className='logistics-modal-container__content-copy__code'>奖品编码:{prizeItem._id}</View>
<View className='logistics-modal-container__content-copy__img' onTap='copyPrizeId' />
</View>
<View className='logistics-modal-container__content-desc'>{prizeItem.shipStatus === 3 ? '发货状态:已发货' : '发货状态:未发货'}</View>
{prizeItem.shipStatus === 2 && <View className='logistics-modal-container__content-desc'>温馨提示:奖品将在活动结束7天内陆续发出</View>}
{
prizeItem.shipStatus !== 2 &&
<View className='logistics-modal-container__content-copy'>
<View className='logistics-modal-container__content-copy__code'>{'物流单号:' + prizeItem.shipNum}</View>
<View className='logistics-modal-container__content-copy__img' style={`background: url(${task.copyIcon}) no-repeat;background-size: 100% 100%;`} onTap={onClose} />
</View>
}
</View>
</View>
</View>
</View>
)
}
......@@ -5,7 +5,7 @@
}
.logistics-modal-container {
position: absolute;
position: fixed;
top: 0;
left: 0;
right: 0;
......@@ -13,20 +13,20 @@
background: rgba(0, 0, 0, 0.7);
z-index: 20;
opacity: 0;
animation: modalAnimation .3s forwards;
animation: modalAnimation 0.3s forwards;
}
@keyframes containerAnimation {
to {
transform: scale(1)
transform: scale(1);
}
}
.logistics-modal-container__content {
width: 646rpx;
height: 244rpx;
padding: 0 46rpx;
position: absolute;
width: 646px;
height: 244px;
padding: 0 46px;
position: fixed;
left: 0;
top: 0;
right: 0;
......@@ -35,27 +35,28 @@
display: flex;
align-items: center;
transform: scale(0);
animation: containerAnimation .3s forwards;
animation: containerAnimation 0.3s forwards;
}
.logistics-modal-container__close {
position: absolute;
right: -10rpx;
top: -70rpx;
height: 48rpx;
width: 48rpx;
background: url("//yun.duiba.com.cn/taobaomini/aupresViva/tc_close.png") no-repeat;
right: -10px;
top: -70px;
height: 48px;
width: 48px;
background: url("//yun.duiba.com.cn/taobaomini/aupresViva/tc_close.png")
no-repeat;
background-size: 100% 100%;
}
.logistics-modal-container__content-item {
border-radius: 10rpx;
border-radius: 10px;
display: flex;
}
.logistics-modal-container__content-left {
width: 144rpx;
height: 144rpx;
width: 144px;
height: 144px;
background: rgba(244, 244, 244, 1);
}
......@@ -65,12 +66,12 @@
}
.logistics-modal-container__content-right {
height: 144rpx;
margin-left: 24rpx;
height: 144px;
margin-left: 24px;
display: flex;
flex-direction: column;
justify-content: space-between;
font-size: 20rpx;
font-size: 20px;
color: #959595;
flex: 1;
overflow: hidden;
......@@ -78,31 +79,31 @@
}
.logistics-modal-container__content-name {
font-size: 28rpx;
font-size: 28px;
color: rgba(51, 51, 51, 1);
}
.logistics-modal-container__content-desc {
font-size: 20rpx;
font-size: 20px;
}
.logistics-modal-container__content-ids {
margin-bottom: 20rpx;
margin-bottom: 20px;
}
.logistics-modal-container__content-copy {
display: flex;
align-items: center;
height: 30rpx;
height: 30px;
}
.logistics-modal-container__content-copy__code {
font-size: 20rpx;
font-size: 20px;
}
.logistics-modal-container__content-copy__img {
width: 20rpx;
height: 20rpx;
width: 20px;
height: 20px;
background-size: 100% 100%;
margin-left: 20rpx;
margin-left: 20px;
}
import { View } from '@tarojs/components'
import React, { useState } from 'react'
import { useThrottle } from '@/hooks/useThrottle'
import classnames from 'classnames'
import Modal from '@/components/_base/Modal/Modal'
import styles from './DoHelpModal.module.less'
import { doHelp } from '@/api'
import { checkIsMember, commonToast } from '@/utils/tb'
import { showToast } from '@tarojs/taro'
import { useLoginInfoModel } from '@/store'
export default function RuleModal(props) {
const { bg = '', width = 300, height = 300, onClose = () => {}, closePostion, top = '40%', newVip = false } = props
const [ memberModalVisible, setMemberModalVisible ] = useState(false)
const { loginInfo: { inviteId } } = useLoginInfoModel()
const onAuthSuccess = async() => {
const helpRes = await doHelp({ inviteId })
if (helpRes?.success) {
showToast({ title: '助力成功' })
} else {
commonToast(helpRes?.message)
}
onClose()
setMemberModalVisible(false)
}
const onAuthFail = () => {
setMemberModalVisible(false)
}
const onDoHelp = useThrottle(async() => {
// 条件不是需要新会员的情况下,直接助力
if (!newVip) {
const helpRes = await doHelp({ inviteId })
if (helpRes?.success) {
showToast({ title: '助力成功' })
} else {
commonToast(helpRes?.message)
}
onClose()
}
// 助力条件为新会员
const res = await checkIsMember()
const isVip = res?.businessSuccess && res?.data?.isMember
// 已经是会员
if (isVip) {
commonToast('新会员才能助力哦~')
} else {
// 还不是会员,弹出会员弹窗
setMemberModalVisible(true)
}
}, 2000)
return (
<View>
<Modal onClose={onClose} closePostion={closePostion} top={top}>
<View className={styles.content} style={{ width: `${width / 100}rem`, height: `${height / 100}rem`, backgroundImage: `url(${bg})` }}>
<View className={styles.title}>接受邀请</View>
<View className={styles.image} />
<View className={styles.desc}>是一段描述</View>
<View className={styles.help_button} onTap={onDoHelp}>助力按钮</View>
</View>
</Modal>
{
memberModalVisible &&
<member-modal
onClose={() => setMemberModalVisible(false)}
onAuthFail={onAuthFail}
onAuthSuccess={onAuthSuccess}
/>
}
</View>
)
}
.content {
.image-property();
background-color: #fff;
overflow: hidden;
padding: 30px;
display: flex;
flex-direction: column;
align-items: center;
}
.title {
font-size: 40px;
text-align: center;
.image-property();
}
.image {
.wh(200px, 200px);
.image("//yun.duiba.com.cn/taobaomini/template/250x250.png");
margin: 40px 0 0 0;
}
.desc {
font-size: 24px;
margin: 30px 0 0 0;
text-align: center;
}
.help_button {
text-align: center;
.wh(150px, 40px);
margin: 40px 0 0 0;
border: 1px solid #000;
}
import { View, ScrollView, Image } from '@tarojs/components'
import classnames from 'classnames'
import React, { useEffect, useState } from 'react'
import './GoodsPage.less'
export default function GoodsPage(props) {
const {
isBackFlag = 0, goodsList = [], task = {
itemId: '617724147979,617724563528,617300295119',
taskType: 'browseGoods',
type: '02',
bannerHeight: 430,
image: {
bg: '//yun.dui88.com/taobaomini/clientCTest/goods_bg@2x.png',
banner: '//yun.dui88.com/taobaomini/clientCTest/goods_banner@2x.png',
title: '//yun.dui88.com/taobaomini/clientCTest/goods_title@2x.png',
collect: '//yun.dui88.com/taobaomini/clientCTest/goods_collection@2x.png',
no_collect: '//yun.dui88.com/taobaomini/clientCTest/collection_no_collect@2x.png',
img: '//yun.dui88.com/taobaomini/clientCTest/goods_img@2x.png'
},
color: '#181818'
}
} = props
return (
<View className='goods-modal-container' style={{ background: `url(${task.image.bg}) no-repeat`, backgroundSize: '750rpx 1624rpx' }}>
{task.type === '01'
? <View
style={{ background: `url(${task.image.title}) no-repeat`, backgroundSize: '100% 100%' }}
className='goods-modal-container__title'
/>
: <View className='goods-modal-container__banner' style={{ background: `url(${task.image.banner}) no-repeat`, backgroundSize: '100% 100%', height: task.bannerHeight + 'rpx' }} />}
<View className='goods-modal-container__content' style={{ top: task.type === '02' ? task.bannerHeight + 'rpx' : '209rpx' }}>
<ScrollView scrollY className='goods-modal-container__scroll'>
<View className='goods-modal-container__content-list'>
{
goodsList.map((item, i) => {
return (
<View className='goods-modal-container__content-item' key={item}>
<View className='goods-modal-container__content-item__img' data-item={item} onTap='goToGoodsDetail'>
<Image mode='scaleToFill' src={item.image} />
</View>
<View className='goods-modal-container__content-item__name' style={{ color: task.color }}>{item.name}</View>
<View className='goods-modal-container__content-item__price'>
<View className='goods-modal-container__content-item__price-num'><text>¥</text>{item.price}</View>
{
task.taskType === 'collectGoods' &&
<View
data-item={item}
onTap='goToCollectGoods'
className={item.collected ? 'goods-modal-container__content-item__price-collect' : 'goods-modal-container__content-item__price-no-collect'}
>
<Image mode='scaleToFill' src={item.collected ? task.image.collect : task.image.no_collect} />
</View>
}
</View>
</View>
)
})
}
</View>
</ScrollView>
</View>
</View>
)
}
......@@ -7,29 +7,29 @@
width: 100%;
min-height: 100vh;
/* background: url('//yun.dui88.com/taobaomini/clientCTestgoods_bg@2x.png') no-repeat; */
background-size: 750rpx 1624rpx;
background-size: 750px 1624px;
overflow: hidden;
z-index: 20;
}
.goods-modal-container__title {
width: 750rpx;
height: 120rpx;
margin: 96rpx 0 11rpx;
width: 750px;
height: 120px;
margin: 96px 0 11px;
}
.goods-modal-container__banner {
width: 750rpx;
height: 400rpx;
margin-bottom: 30rpx;
background: #E1E1E1;
width: 750px;
height: 400px;
margin-bottom: 30px;
background: #e1e1e1;
}
.goods-modal-container__content {
position: absolute;
top: 190rpx;
top: 190px;
left: 0;
bottom: 0;
padding-top: 41rpx;
padding: 41rpx 25rpx 0;
width: 750rpx;
padding-top: 41px;
padding: 41px 25px 0;
width: 750px;
/* height: inherit; */
}
.goods-modal-container__scroll {
......@@ -41,54 +41,54 @@
flex-wrap: wrap;
}
.goods-modal-container__content-item {
width: 340rpx;
height: 510rpx;
width: 340px;
height: 510px;
background: #fff;
margin-bottom: 20rpx;
margin-bottom: 20px;
}
.goods-modal-container__content-item:nth-child(even) {
margin-left: 20rpx;
margin-left: 20px;
}
.goods-modal-container__content-item__img {
width: 340rpx;
height: 340rpx;
background: #E1E1E1;
width: 340px;
height: 340px;
background: #e1e1e1;
}
.goods-modal-container__content-item__img image {
width: 100%;
height: 100%;
}
.goods-modal-container__content-item__name {
margin: 25rpx 0 31rpx 21rpx;
width:310rpx;
height:60rpx;
font-size:24rpx;
font-family:PingFang SC;
font-weight:400;
color:rgba(24,24,24,1);
line-height:30rpx;
margin: 25px 0 31px 21px;
width: 310px;
height: 60px;
font-size: 24px;
font-family: PingFang SC;
font-weight: 400;
color: rgba(24, 24, 24, 1);
line-height: 30px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.goods-modal-container__content-item__price {
padding: 0 31rpx 0 24rpx;
padding: 0 31px 0 24px;
display: flex;
justify-content: space-between;
}
.goods-modal-container__content-item__price-num {
font-size:32rpx;
font-family:PingFang SC;
font-weight:600;
color:rgba(255,42,0,1);
font-size: 32px;
font-family: PingFang SC;
font-weight: 600;
color: rgba(255, 42, 0, 1);
}
.goods-modal-container__content-item__price-num text {
font-size: 24rpx;
margin-right: 2rpx;
font-size: 24px;
margin-right: 2px;
}
.goods-modal-container__content-item__price-collect {
width: 118rpx;
height: 31rpx;
width: 118px;
height: 31px;
background-size: 100% 100%;
}
.goods-modal-container__content-item__price-collect image {
......@@ -96,18 +96,18 @@
height: 100%;
}
.goods-modal-container__content-item__price-no-collect {
width: 30rpx;
height: 30rpx;
width: 30px;
height: 30px;
}
.goods-modal-container__content-item__price-no-collect image {
width: 100%;
height: 100%;
}
.goods-modal-timer {
width: 147rpx;
height: 223rpx;
width: 147px;
height: 223px;
position: absolute;
top: 502rpx;
right: 19rpx;
top: 502px;
right: 19px;
background: #eee;
}
import { View, ScrollView, Text } from '@tarojs/components'
import React from 'react'
import classnames from 'classnames'
import Modal from '@/components/_base/Modal/Modal'
import styles from './RuleModal.module.less'
export default function RuleModal(props) {
const { bg = '', width = 300, height = 300, rule = '', onClose, closePostion, top = '40%' } = props
return (
<Modal onClose={onClose} closePostion={closePostion} top={top}>
<View className={styles.content} style={{ width: `${width / 100}rem`, height: `${height / 100}rem`, backgroundImage: `url(${bg})` }}>
<View className={styles.title}>活动规则</View>
<ScrollView className={styles.scroll} scrollY>
<Text className={styles.rule}>{rule}</Text>
</ScrollView>
</View>
</Modal>
)
}
.content {
.image-property();
background-color: #fff;
overflow: hidden;
padding: 30px;
display: flex;
flex-direction: column;
align-items: center;
}
.scroll {
width: 100%;
height: 100%;
margin-top: 20px;
}
.title {
font-size: 40px;
text-align: center;
.image-property();
}
.rule {
font-size: 24px;
line-height: 30px;
flex: 1;
}
import { View } from '@tarojs/components'
import React, { useState } from 'react'
import Popup from '@/components/_base/Popup/Popup'
import { noopFn } from '@/utils/util'
import { useTasks } from '@/hooks/useTasks'
export default function TasksModal(props) {
const { onClose = noopFn } = props
const { taskList } = useTasks()
return (
<Popup onClose={onClose}>4433443</Popup>
)
}
import { View } from '@tarojs/components'
import classnames from 'classnames'
import React, { useState } from 'react'
import Overlay from '../Overlay/Overlay'
import styles from './modal.module.less'
export default function Modal(props) {
const { closePostion = 'top-right', onClose = () => { }, top = '40%', hideCloseButton = false, closeOnClickOverlay = false } = props
const closeBtnClass = classnames(styles.closeButton, {
[`${styles.closeButton_bottom}`]: closePostion === 'bottom',
[`${styles.closeButton_right_top}`]: closePostion === 'top-right'
})
const [ isShowAnimateOut, setShowAnimate ] = useState(false)
const modalClass = classnames(styles.modal, {
'animate-fade-out': isShowAnimateOut
})
const onCloseModal = () => {
setShowAnimate(true)
setTimeout(() => {
setShowAnimate(false)
onClose()
}, 350)
}
const onClickOverlay = () => {
closeOnClickOverlay && onCloseModal()
}
return (
<View className={modalClass}>
<Overlay onTap={onClickOverlay} />
<View className={`${styles.content} animate-zoom-in`} style={{ top }}>
{!hideCloseButton && <View className={closeBtnClass} onTap={onCloseModal} />}
{props.children}
</View>
</View>
)
}
.modal {
.cover(fixed);
z-index: 20;
}
.content {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%) scale(0);
z-index: 22;
}
.closeButton {
.wh(50px, 50px);
.image("//yun.duiba.com.cn/taobaomini/template/modal_close_btn.png", contain);
}
.closeButton_right_top {
.pos-top-right(absolute, -50px);
}
.closeButton_bottom {
.pos-bottom();
left: 50%;
bottom: -80px;
transform: translateX(-50%);
}
.overlay {
.cover();
z-index: 21;
opacity: 0;
background-color: rgba(0, 0, 0, 0.7);
}
import { noopFn } from '@/utils/util'
import { View } from '@tarojs/components'
import React from 'react'
import styles from './OverLay.module.less'
export default function Overlay(props) {
const { onClick = noopFn, onTap = noopFn } = props
return (
<View className={`${styles.overlay} animate-fade-in`} onClick={onClick} onTap={onTap} />
)
}
import { noopFn } from '@/utils/util'
import { View } from '@tarojs/components'
import classnames from 'classnames'
import React, { useEffect, useState } from 'react'
import Overlay from '../Overlay/Overlay'
import styles from './Popup.module.less'
export default function Popup(props) {
const { onClose = noopFn, position = 'bottom', height = '30%', width = '100%', closeOnClickOverlay = true } = props
const [ isShowAnimate, setShowAnimate ] = useState(false)
const onCloseModal = () => {
setShowAnimate(true)
setTimeout(() => {
setShowAnimate(false)
onClose()
}, 350)
}
const onClickOverlay = () => {
closeOnClickOverlay && onCloseModal()
}
const wrapClass = classnames(styles.wrap, {
'animate-fade-out': isShowAnimate
})
const contentClass = classnames(styles.content, styles[position], {
'animate-fly-in': true,
'animate-fly-out': isShowAnimate
})
return (
<View className={wrapClass}>
<Overlay onTap={onClickOverlay} />
<View className={contentClass} style={{ width, height }}>
{props.children}
</View>
</View>
)
}
.content {
position: fixed;
max-height: 100%;
overflow-y: auto;
background-color: #fff;
// transition: transform 0.3s;
z-index: 24;
}
.bottom {
width: 100%;
bottom: 0;
left: 0;
transition: transform 0.3s;
}
.show {
transform: translateY(0);
transition: transform 0.3s;
}
<member-shop-center expend="{{true}}" onClose="onMemberModalClose" onAuthFail="onAuthFail" onAuthSuccess="onAuthSuccess"/>
Component({
data: {
visible: true
},
methods: {
onMemberModalClose() {
// console.log('this.props :>> ', this.props);
// 原生支付宝小程序组件无法调用 props 传入的函数 https://github.com/NervJS/taro/issues/7938
this.props.onClose({
type: 'close',
target: {
id: this.props.id
}
})
},
onAuthFail() {
this.props.onAuthFail({
type: 'authfail',
target: {
id: this.props.id
}
})
},
onAuthSuccess() {
this.props.onAuthSuccess({
type: 'authsuccess',
target: {
id: this.props.id
}
})
}
}
})
{
"component": true,
"usingComponents": {
"count-down-modal": "/components/countDownModal/countDownModal"
"member-shop-center": "plugin://cemMember/member-shop-center"
}
}
\ No newline at end of file
export default {
env: 'test', // 云函数环境 test 测试环境 online 线上环境
requestType: 'cloud', // cloud: 云函数, yapi: yapi 接口, ams: ams接口,tornadoAPI: 风驰台接口
cloudName: 'richMan', // 主云函数项目名
sellerId: '2207644377875',
tornadoAPI: 'https://tornado.duibadev.com.cn/tbServer/api?db=xxx&proxyIp=xxx', // 新增:当requestType: 'tornadoAPI'时,找服务端提供地址
defaultActivityId: '5ff7e0a3e5c0d7dafd30dce5' // 默认活动id
}
// 分享配置
export const SHARE_CONFIG = {
title: '分享测试标题',
desc: '分享测试描述',
imageUrl: '' // 分享图片URL
}
// 领取奖品状态
export const DRAW_STATUS = {
// 待领取
......@@ -20,4 +27,29 @@ export const PRIZE_TYPE = {
CREDITS: 2,
OBJECT: 3,
THANKS: 5
};
\ No newline at end of file
}
// 我的奖品状态
export const PRIZE_DRAW_STATUS_MAP = {
RECEIVE: 1, // 待领取
DEALING: 2, // 处理中
SUCCESS: 3, // 领取成功
FAILURE: 4, // 领取失败
FINISHED: 5, // 已过期
REPEAT: 6 // 重新领取
}
// 奖品类型
export const PRIZE_TYPE_MAP = {
EQUITY: 1, // 权益
INTEGRATION: 2, // 积分
ENTITY: 3, // 实物
THANKS: 5 // 谢谢参与
}
// 活动状态
export const ACTIVITY_STATUS = {
NO_EXIST: 0, // 不存在
PROCESSING: 1, // 正在进行
NO_START: 2, // 未开始
END: 3 // 已经结束
}
import { useState, useEffect } from 'react'
import { getAuthUserInfo } from '../utils/tb'
export function useAuthInfo() {
const [ authInfo, setAuthInfo ] = useState({
nickName: '',
avatar: ''
})
// 是否授权完成
const [ authorized, setAuthorizeStatus ] = useState(false)
const authUserInfo = async() => {
const info = await getAuthUserInfo().catch(err => {
console.log('未授权成功', err)
})
console.log('authInfo :>> ', info)
if (info) {
setAuthInfo(info)
setAuthorizeStatus(true)
}
}
useEffect(() => {
authUserInfo()
}, [])
return {
authInfo,
authorized
}
}
import { useRef, useEffect, useCallback } from 'react'
export function useDebounce(fn, delay, dep = []) {
const { current } = useRef({ fn, timer: null })
useEffect(function() {
current.fn = fn
}, [ fn ])
return useCallback(function f(...args) {
if (current.timer) {
clearTimeout(current.timer)
}
current.timer = setTimeout(() => {
current.fn.call(this, ...args)
}, delay)
}, dep)
}
import { useEffect, useState } from 'react'
import { useRouter } from '@tarojs/taro'
import { useLoginInfoModel } from '../store/loginInfo'
import { login } from '../api'
import { checkShopFavoredStatus } from '../utils/tb'
import config from '../config'
import { useAuthInfo } from './useAuth'
import { noopFn } from '@/utils/util'
export function useLogin(callback = noopFn) {
const { authorized, authInfo } = useAuthInfo()
const { loginInfo, setLoginInfo } = useLoginInfoModel()
const [ isLogin, setLoginStatus ] = useState(false)
const { params } = useRouter()
const doLogin = async() => {
const isFollow = await checkShopFavoredStatus(config.sellerId)
const { nickName: userNick, avatar } = authInfo
const { inviteId } = params
const res = await login({ userNick, avatar, isFollow, inviteId })
if (res?.success) {
setLoginInfo({
...res.data,
userNick,
avatar,
inviteId
})
setLoginStatus(true)
console.log('isLogin :>> ', true)
callback(loginInfo)
}
}
useEffect(() => {
if (authorized) {
doLogin()
}
console.log('authorized:>> ', authorized)
}, [ authorized ])
return { isLogin }
}
export function useLoginFromShare(callback = noopFn) {
const { loginInfo } = useLoginInfoModel()
// 只执行一次
const [ executed, setExecuted ] = useState(false)
useEffect(() => {
if (loginInfo.inviteId && !executed) {
callback(loginInfo.inviteId)
setExecuted(true)
}
}, [ loginInfo ])
return {
inviteId: loginInfo.inviteId
}
}
import { useState, useEffect } from 'react'
export function useRequest(api, params = {}, config = {}) {
const [ data, setData ] = useState(null)
const [ code, setCode ] = useState('')
const [ success, setSuccess ] = useState(false)
const [ message, setMessage ] = useState('')
const [ loading, setLoading ] = useState(false)
const [ error, setError ] = useState(null)
const request = async() => {
setLoading(true)
try {
const { data: resData, success: resSuccess, message: resMsg, code: resCode } = await api(params, config)
setSuccess(resSuccess)
setData(resData)
if (!resSuccess) {
setMessage(resMsg)
setCode(resCode)
}
} catch (err) {
console.log('useRequest catch :>> ', err)
setError(err)
setLoading(false)
}
setLoading(false)
}
useEffect(async => {
request()
}, [])
return {
data,
code,
success,
message,
loading,
error
}
}
import { getTaskList } from '@/api'
import { useState, useEffect } from 'react'
import { useRequest } from './useRequest'
export function useTasks() {
const [ taskList, setTaskList ] = useState([])
const { data, success } = useRequest(getTaskList)
useEffect(() => {
success && setTaskList(data?.list || [])
}, [ success ])
return {
taskList
}
}
import { useRef, useEffect, useCallback } from 'react'
export function useThrottle(fn, delay, dep = []) {
const { current } = useRef({ fn, timer: null })
useEffect(function() {
current.fn = fn
}, [ fn ])
return useCallback(function f(...args) {
if (!current.timer) {
current.timer = setTimeout(() => {
delete current.timer
}, delay)
current.fn.call(this, ...args)
}
}, dep)
}
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta content="width=device-width,initial-scale=1,user-scalable=no" name="viewport">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-touch-fullscreen" content="yes">
<meta name="format-detection" content="telephone=no,address=no">
<meta name="apple-mobile-web-app-status-bar-style" content="white">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title></title>
<script>
!function (x) { function w() { var v, u, t, tes, s = x.document, r = s.documentElement, a = r.getBoundingClientRect().width; if (!v && !u) { var n = !!x.navigator.appVersion.match(/AppleWebKit.*Mobile.*/); v = x.devicePixelRatio; tes = x.devicePixelRatio; v = n ? v : 1, u = 1 / v } if (a >= 640) { r.style.fontSize = "40px" } else { if (a <= 320) { r.style.fontSize = "20px" } else { r.style.fontSize = a / 320 * 20 + "px" } } } x.addEventListener("resize", function () { w() }); w() }(window);
</script>
</head>
<body>
<div id="app"></div>
</body>
</html>
\ No newline at end of file
export default {
navigationBarTitleText: '我的奖品',
enableSkia: 'true',
allowsBounceVertical: 'NO',
enableShareAppMessage: true
}
import React, { useEffect, useState } from 'react'
import { Image, ScrollView, View } from '@tarojs/components'
import { getMyPrizeList, receiveEnamePrize, receiveObjectPrize } from '@/api'
import { DRAW_STATUS, PRIZE_TYPE } from '@/const'
import GoodsPage from '@/components/GoodsPage/GoodsPage'
import { dateFormatter } from '@/utils/date'
import { checkIsMember, commonToast, getUserAddress, navigateToOutside, setClipboard } from '@/utils/tb'
import { useThrottle } from '@/hooks/useThrottle'
import { useDidShow } from '@tarojs/taro'
import './browseGoods.less'
function BrowseGoodsPage() {
return (
<View>
<GoodsPage />
</View>
)
}
export default BrowseGoodsPage
export default {
navigationBarTitleText: '首页',
enableSkia: 'true',
allowsBounceVertical: 'NO',
enableShareAppMessage: true,
usingComponents: {
'member-modal': '../../components/_miniprogram/memberModal/memberModal'
}
}
import React, { useState } from 'react'
import { View } from '@tarojs/components'
import Taro, { useShareAppMessage } from '@tarojs/taro'
import { SHARE_CONFIG } from '@/const.js'
import { useLogin, useLoginFromShare } from '@/hooks/useLogin'
import { useActivityInfoModel, useLoginInfoModel } from '@/store'
import { getActivityBaseInfoById } from '@/api'
import RuleModal from '@/components/RuleModal/RuleModal'
import DoHelpModal from '@/components/DoHelpModal/DoHelpModal'
import TasksModal from '@/components/TasksModal/TasksModal'
import styles from './index.module.less'
const INDEX_CONFIG = {
bg: '', // 背景
ruleButton: '', // 规则按钮
myPrizeButton: '', // 我的奖品按钮
shareButton: '', // 分享按钮
taskButton: '' // 任务按钮
}
function Index() {
const { activityInfo, setActivityInfoAndStatus } = useActivityInfoModel()
const { loginInfo } = useLoginInfoModel()
const [ memberVisible, setMemberVisible ] = useState(false)
const [ doHelpModalVisible, setDoHelpModalVisible ] = useState(false)
const [ ruleModalVisible, setRuleModalVisible ] = useState(false)
const [ tasksModalVisible, setTasksModalVisible ] = useState(true)
const fetchActivityInfo = async() => {
const res = await getActivityBaseInfoById()
const { data } = res
if (res?.success) {
setActivityInfoAndStatus(data)
}
}
// 授权登录完成
useLogin(() => {
fetchActivityInfo()
})
// 分享链接进入活动
useLoginFromShare((inviteId) => {
console.log('inviteId :>> ', inviteId)
setDoHelpModalVisible(true)
})
useShareAppMessage(() => {
return {
...SHARE_CONFIG,
path: `pages/index/index?inviteId=${loginInfo.openId}`
}
})
const onClose = () => {
setMemberVisible(false)
}
const onAuthSuccess = () => {
setMemberVisible(false)
}
const onAuthFail = () => {
setMemberVisible(false)
}
const onShare = () => my.showSharePanel()
const onGoMyPrizePage = () => Taro.navigateTo({ url: '/pages/myPrize/myPrize' })
return (
<View className={styles.container} style={{ backgroundImage: `${INDEX_CONFIG.bg}` }}>
<View className={styles.rule} style={{ backgroundImage: `${INDEX_CONFIG.ruleButton}` }} onTap={() => setRuleModalVisible(true)}>活动规则</View>
<View className={styles.my_prize} style={{ backgroundImage: `${INDEX_CONFIG.myPrizeButton}` }} onTap={onGoMyPrizePage}>我的奖品</View>
<View className={styles.share} style={{ backgroundImage: `${INDEX_CONFIG.shareButton}` }} onTap={onShare}>分享</View>
<View className={styles.tasks} style={{ backgroundImage: `${INDEX_CONFIG.taskButton}` }} onTap={() => setTasksModalVisible(true)}>任务</View>
<View className={styles.bemember} onTap={() => setMemberVisible(true)}>入会</View>
{tasksModalVisible && <TasksModal onClose={() => setTasksModalVisible(false)} />}
{
ruleModalVisible &&
<RuleModal
closePostion='bottom'
bg=''
width='500'
height='300'
rule={activityInfo.rule}
onClose={() => setRuleModalVisible(false)}
/>
}
{
doHelpModalVisible &&
<DoHelpModal
closePostion='bottom'
bg=''
width='500'
height='500'
top='45%'
newVip
onClose={() => setDoHelpModalVisible(false)}
/>
}
{memberVisible && <member-modal onClose={onClose} onAuthFail={onAuthFail} onAuthSuccess={onAuthSuccess} />}
</View>
)
}
export default Index
.container {
width: 100%;
min-height: 100vh;
.image-property(cover, top center);
}
.index_button() {
.wh(150px, 46px);
.border(#000);
.image-property();
line-height: 46px;
position: absolute;
font-size: 24px;
border-radius: 4px;
text-align: center;
}
.rule {
.index_button();
left: 20px;
top: 100px;
}
.my_prize {
.index_button();
right: 20px;
top: 100px;
}
.share {
.index_button();
right: 20px;
top: 180px;
}
.bemember {
.index_button();
right: 20px;
top: 260px;
}
.tasks {
.index_button();
right: 20px;
top: 340px;
}
export default {
navigationBarTitleText: '我的奖品',
enableSkia: 'true',
allowsBounceVertical: 'NO',
enableShareAppMessage: true,
usingComponents: {
'member-modal': '../../components/_miniprogram/memberModal/memberModal'
}
}
import React, { useEffect, useState } from 'react'
import { Image, ScrollView, View } from '@tarojs/components'
import { getMyPrizeList, receiveEnamePrize, receiveObjectPrize } from '@/api'
import { DRAW_STATUS, PRIZE_TYPE } from '@/const'
import DeliveryModal from '@/components/DeliveryModal/DeliveryModal'
import './myPrize.less'
import { dateFormatter } from '@/utils/date'
import { checkIsMember, commonToast, getUserAddress, navigateToOutside, setClipboard } from '@/utils/tb'
import { useThrottle } from '@/hooks/useThrottle'
import { useDidShow } from '@tarojs/taro'
// 我的奖品配置项
const prizeConfig = {
bg: '//yun.duiba.com.cn/aurora/assets/e13fab3165d3a6bb4cbe8827e8f0fd41084b0091.png', // 背景图
title: '//yun.dui88.com/duiba-components-c-myprizeprize_title.png', // 标题
copyIcon: '//yun.dui88.com/duiba-components-c-myprizemyprize_copy_code.png', // 复制
receiveBtn: '//yun.duiba.com.cn/bestoreBox/prize_received.png', // 已领取 权益类奖品未配置 uesUrl使用
commonStatus: {
6: '//yun.dui88.com/duiba-components-c-myprizeget_again.png', // 重新领取
5: '//yun.duiba.com.cn/spark/assets/914ba8643c8abb8d710a3126ec312aecf6c4daf0.png', // 已失效
4: '//yun.duiba.com.cn/spark/assets/8267375232058a6d0140270b1594427fb08d6ae3.png' // 领取失败
},
objectStatus: {
1: '//yun.dui88.com/duiba-components-c-myprizeto_address.png', // 填写地址(实物奖品)
3: '//yun.dui88.com/duiba-components-c-myprizelogistics.png' // 查看物流
},
enameStatus: {
1: '//yun.dui88.com/duiba-components-c-myprizeget_now.png', // 立即领取(除实物奖品外)
3: '//yun.duiba.com.cn/bestoreBox/prize_received.png' // 去使用(除实物奖品外)
},
logisticsImage: {
shipped: '//yun.dui88.com/duiba-components-c-myprizeprize_shipped.png', // 已发货物理弹窗
notShipped: '//yun.dui88.com/duiba-components-c-myprizenotshipped.png' // 待发货物流弹窗
},
btnStyle: {
width: '150rpx'
},
contentTop: '210rpx',
blankTxt: '暂无奖品',
hasLogistic: true, // 是否展示物流信息
isShowPirzeId: true // 是否显示奖品编号
}
function Empty(blankTxt) {
return <View className='my-prize-item__empty'>{blankTxt}</View>
}
function MyPrizeList() {
const [ myPrizeList, setMyPrizeList ] = useState([])
const [ expiredTime, setExpiredTime ] = useState('')
const [ currentPrize, setCurrentPrize ] = useState({})
const [ memberVisible, setMemberVisible ] = useState(false)
const [ deliveryModalVisible, setDeliveryModalVisible ] = useState(false)
// useEffect(() => {
// fetchMyPriceList()
// }, [])
useDidShow(() => {
fetchMyPriceList()
})
async function fetchMyPriceList() {
const res = await getMyPrizeList()
if (res.success && res?.data?.list?.length) {
setMyPrizeList(res?.data?.list)
}
if (res?.data?.expiredTime) {
setExpiredTime(dateFormatter(res?.data?.expiredTime, 'yyyy/MM/dd hh:mm:ss'))
}
}
// 复制编码
const handleCopyId = async(text) => {
await setClipboard({ text })
commonToast('复制成功')
}
// 优惠券
async function handleGetEquity(id) {
const { success, data } = await receiveEnamePrize({ id })
if (success && data) {
commonToast('领取成功')
}
fetchMyPriceList()
}
// 领取实物
async function handleReceiveObjectPrize(params) {
const { success, data } = await receiveObjectPrize(params).catch(res => {
commonToast(res && res.message)
}) || {}
if (success && data) {
commonToast('领取成功')
}
fetchMyPriceList()
}
// 积分
async function handleGetCredits(id) {
// 判断是否为会员
const res = await checkIsMember()
const isVip = res?.businessSuccess && res?.data?.isMember
if (!isVip) {
commonToast('需加入会员才能领取成功哦', 2000, () => { setMemberVisible(true) })
return
}
const { success, data } = await receiveEnamePrize({ id })
if (success && data) {
commonToast('领取成功')
}
fetchMyPriceList()
}
// 领取实物前询问
async function handleChooseAddress(id) {
const userAddress = await getUserAddress().catch(err => {
commonToast(err && err.errorMessage)
})
if (!userAddress) return
const { name, telNumber, provinceName, cityName, cityCode, countyName, detailInfo, streetName } = userAddress || {}
const params = {
name,
phone: telNumber,
addressDetail: detailInfo,
cityCode,
city: cityName,
province: provinceName,
area: countyName,
streetName,
id
}
my.confirm({
title: '提示',
content: '确认使用该收货地址:' + name + telNumber + userAddress.duibaAddress.address,
confirmButtonText: '确定',
cancelButtonText: '取消',
success: (result) => {
console.log(result)
const { confirm } = result
if (confirm) {
handleReceiveObjectPrize(params)
}
}
})
}
const handleClick = useThrottle(async(item) => {
const { type, drawStatus, useUrl = '', id, _id } = item
if (drawStatus === DRAW_STATUS.SUCCESS) {
if (type === PRIZE_TYPE.OBJECT && prizeConfig.hasLogistic) {
setCurrentPrize(item)
setDeliveryModalVisible(true)
return
}
if (type !== PRIZE_TYPE.OBJECT && useUrl) {
navigateToOutside(useUrl)
return
}
}
if (!(drawStatus === DRAW_STATUS.RETRY || drawStatus === DRAW_STATUS.WAITAWARD)) {
return
}
// 领取权益
const prizeId = id || _id
if (type === PRIZE_TYPE.ENAME) return handleGetEquity(prizeId)
// 领取实物
if (type === PRIZE_TYPE.OBJECT) return handleChooseAddress(prizeId)
// 领取积分
if (type === PRIZE_TYPE.CREDITS) return handleGetCredits(prizeId)
}, 2000)
const onClose = () => {
setMemberVisible(false)
}
const onAuthSuccess = () => {
setMemberVisible(false)
}
const onAuthFail = () => {
setMemberVisible(false)
}
return (
<View>
<View className='my-prize-container' style={{ background: `url(${prizeConfig.bg}) no-repeat center top/cover` }}>
<View className='my-prize-label' style={{ background: `url(${prizeConfig.title}) no-repeat`, backgroundSize: '100% 100%' }} />
<View className='my-prize-content' style={{ top: prizeConfig.contentTop }}>
<ScrollView scrollY className='my-prize-content__scroll'>
{myPrizeList.length > 0 &&
<View className='my-prize-list'>
{myPrizeList.map((item, i) => {
return (
<View className='my-prize-item' key={item}>
<Image className='my-prize-item__avatar' src={item.image} />
<View className='my-prize-item__content'>
<View className='my-prize-item__name'>{item.name}</View>
{
prizeConfig.isShowPirzeId &&
<View className='my-prize-item__subtitle'>
<View className='my-prize-item__code'>奖品编码:{item.id || item._id}</View>
{/* <!-- 复制按钮 --> */}
<Image className='my-prize-item__copy' onTap={() => handleCopyId(item.id || item._id)} src={prizeConfig.copyIcon} />
</View>
}
{item.drawStatus === DRAW_STATUS.FAIL && <View className='my-prize-item__tip'>{item.remark}</View>}
{(item.drawStatus === DRAW_STATUS.RETRY || item.drawStatus === DRAW_STATUS.WAITAWARD) && <View className='my-prize-item__tip'>请于{expiredTime}前领取</View>}
{item.drawStatus === DRAW_STATUS.EXPIRED && <View className='my-prize-item__tip'>奖品已过期失效</View>}
</View>
<View className='prize-item__status' style={prizeConfig.btnStyle}>
{item.type === PRIZE_TYPE.OBJECT
? <Image onTap={() => handleClick(item)} src={prizeConfig.objectStatus[item.drawStatus]} mode='widthFix' />
: <Image onTap={() => handleClick(item)} src={(item.drawStatus === 3 && !item.useUrl) ? prizeConfig.receiveBtn : prizeConfig.enameStatus[item.drawStatus]} mode='widthFix' />}
</View>
</View>
)
})}
</View>}
{
myPrizeList.length === 0 && Empty(prizeConfig.blankTxt)
}
</ScrollView>
</View>
</View>
{deliveryModalVisible && <DeliveryModal prizeItem={currentPrize} task={prizeConfig.logisticsImage} onClose={() => setDeliveryModalVisible(false)} />}
{memberVisible && <member-modal onClose={onClose} onAuthFail={onAuthFail} onAuthSuccess={onAuthSuccess} />}
</View>
)
}
export default MyPrizeList
.my-prize-container {
width: 100vw;
height: 100vh;
opacity: 1;
position: absolute;
top: 0;
left: 0;
overflow: hidden;
/* background: url('') no-repeat center top/cover; */
}
.my-prize-container .my-prize-label {
width: 208px;
height: 36px;
margin: 140px auto 40px;
background: url("//yun.duiba.com.cn/duiba-components-c-myprizeprize_title.png")
no-repeat;
background-size: 100% 100%;
}
.my-prize-content {
position: absolute;
left: 0;
top: 363px;
bottom: 0;
width: 100%;
}
.my-prize-content__scroll {
width: 100%;
height: 100%;
padding-top: 30px;
}
.my-prize-container .my-prize-list {
width: 100%;
height: auto;
}
.my-prize-container .my-prize-item__empty {
text-align: center;
color: #fff;
margin-top: 30px;
}
.my-prize-list .my-prize-item {
display: flex;
justify-content: center;
align-items: center;
padding: 0 28px 0 26px;
margin: 0 auto 24px;
position: relative;
box-sizing: border-box;
width: 700px;
height: 161px;
background-color: #ffffff;
box-shadow: 2px 3px 18px 0 rgba(230, 146, 255, 0.6);
border-radius: 10px;
box-sizing: border-box;
}
.my-prize-list .my-prize-item .my-prize-item__avatar {
width: 120px;
height: 120px;
}
.my-prize-list .my-prize-item .my-prize-item__content {
flex: 1;
min-width: 0;
padding: 0 20px;
display: flex;
flex-direction: column;
height: 130px;
align-items: flex-start;
justify-content: center;
}
.my-prize-list .my-prize-item .my-prize-item__subtitle {
display: flex;
justify-content: center;
align-items: center;
}
.my-prize-list .my-prize-item .my-prize-item__copy {
width: 20px;
height: 20px;
margin-left: 18px;
}
.my-prize-list .my-prize-item .prize-item__status {
width: 150px;
}
.prize-item__status > image {
width: 100%;
}
.my-prize-list .my-prize-item .my-prize-item__name {
width: 100%;
font-size: 28px;
color: #2d2d2d;
letter-spacing: -1px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.my-prize-list .my-prize-item .my-prize-item__tip {
margin-top: 8px;
font-size: 20px;
line-height: 20px;
letter-spacing: 0;
color: #8a8a8a;
}
.my-prize-list .my-prize-item .my-prize-item__code {
width: 290px;
font-family: PingFangSC-Regular;
font-size: 20px;
color: #8a8a8a;
letter-spacing: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
import { useState } from 'react'
import { createModel } from 'hox'
import { ACTIVITY_STATUS } from '../const'
function useActivityInfo() {
const [ activityInfo, setActivityInfo ] = useState({
_id: '',
title: '',
startTime: 0,
endTime: 0,
rule: '',
activityStatus: ACTIVITY_STATUS.NO_EXIST
})
/**
*
* 获取活动状态
*
* @param {*} acInfo 活动信息
* @return {*} 活动状态
*/
const getActivityStatus = acInfo => {
const { startTime, endTime } = acInfo
const now = Date.now()
const noStart = startTime > now
const isEnd = endTime < now
if (startTime === 0 && endTime === 0) return ACTIVITY_STATUS.NO_EXIST
if (noStart) return ACTIVITY_STATUS.NO_START
if (isEnd) return ACTIVITY_STATUS.END
return ACTIVITY_STATUS.PROCESSING
}
const setActivityInfoAndStatus = acInfo => {
const activityStatus = getActivityStatus(acInfo)
setActivityInfo({
...acInfo,
activityStatus
})
}
return {
activityInfo,
setActivityInfoAndStatus
}
}
export const useActivityInfoModel = createModel(useActivityInfo)
export * from './activityInfo'
export * from './loginInfo'
import { useState } from 'react'
import { createModel } from 'hox'
function useLoginInfo() {
const [ loginInfo, setLoginInfo ] = useState({
newUser: '',
openId: '',
firstLoginToday: '',
userNick: '',
avatar: '',
inviteId: ''
})
return {
loginInfo,
setLoginInfo
}
}
export const useLoginInfoModel = createModel(useLoginInfo)
page {
background: #f7f7f7;
font-family: 'Helvetica Neue', Helvetica, Hiragino Sans GB, 'Microsoft Yahei', STHeiTi, Arial, sans-serif;
}
* {
box-sizing: border-box;
}
.d-flex {
display: flex
}
.d-flex__item {
flex: 1;
}
.d-flex--aligncenter {
align-items: center;
.animate-fade-in {
opacity: 0;
animation: animateFadeIn 0.3s forwards;
}
.modal-animate-fade-in {
opacity: 0;
animation: modalAnimateFadeIn .3s forwards;
.animate-fade-out {
animation: animateFadeOut 0.3s forwards;
}
.modal-animate-zoom-in {
transform: scale(0);
animation: modalAnimateZoomIn .3s forwards;
.animate-zoom-in {
animation: animateZoomIn 0.3s forwards;
transform-origin: center;
}
.modal-animate-fly-in {
.animate-fly-in {
transform: translateY(100%);
animation: modalAnimateFlyIn .3s forwards;
animation: modalAnimateFlyIn 0.3s forwards;
}
.animate-fly-out {
transform: translateY(0);
animation: modalAnimateFlyOut 0.3s forwards;
}
.btn-animate-pulse {
animation: btnAnimatePulse .8s infinite;
animation: btnAnimatePulse 0.8s infinite;
}
@keyframes modalAnimateFadeIn {
@keyframes animateFadeIn {
to {
opacity: 1;
}
}
@keyframes animateFadeOut {
to {
opacity: 0;
}
}
@keyframes modalAnimateZoomIn {
@keyframes animateZoomIn {
to {
transform: scale(1);
transform: translate(-50%, -50%) scale(1);
}
}
......@@ -56,6 +47,11 @@ page {
transform: translateY(0);
}
}
@keyframes modalAnimateFlyOut {
to {
transform: translateY(100%);
}
}
@keyframes btnAnimatePulse {
from {
......
@import './var.less';
.borderDir(@dir, @width: @border-width, @color: @border-color) {
border-@{dir}: @width solid @color;
}
.border(@color: @border-color, @width: @border-width) {
border: @width solid @color;
}
.border-top(@color: @border-color, @width: @border-width) {
.borderDir(top, @width, @color);
}
.border-bottom(@color: @border-color, @width: @border-width) {
.borderDir(bottom, @width, @color);
}
.border-left(@color: @border-color, @width: @border-width) {
.borderDir(left, @width, @color);
}
.border-right(@color: @border-color, @width: @border-width) {
.borderDir(right, @width, @color);
}
.radius(@radius: @radius-md, @overflow: hidden) {
border-radius: @radius;
overflow: @overflow;
}
.radius-circle(@overflow: hidden) {
.radius(@radius-circle, @overflow);
}
.radius-round(@overflow: hidden) {
.radius(@radius-round, @overflow);
}
.border-build() {
.border-left {
.border-left();
}
.border-right {
.border-right();
}
.border-top {
.border-top();
}
.border-bottom {
.border-bottom();
}
.border {
.border();
}
.radius {
.radius();
}
.radius-circle {
.radius-circle();
}
.radius-round {
.radius-round();
}
}
.flex-row-normal() {
display: flex;
align-items: center;
}
.flex-row(@children: @flex-grow-children) {
.flex-row-normal();
.flex-row-child(@children);
}
.flex-row-child(@children: @flex-grow-children) {
.loop(@children, 0);
.loop(@arr, @index: 0) when (@index < length(@arr)) {
@item: extract(@arr, @index + 1);
& > @{item} {
flex-grow: 1;
overflow: hidden;
}
.loopMore(@flex-shrink-children, 0, @item);
.loop(@arr, @index + 1);
}
.loopMore(@arr, @index: 0, @not) when (@index < length(@arr)) {
@item: extract(@arr, @index + 1);
.child-map({
flex-shrink: 0;
}, @item, @not);
.loopMore(@arr, @index + 1, @not);
}
}
.flex-row-top() {
align-items: flex-start;
}
.flex-row-middle() {
align-items: center;
}
.flex-row-bottom() {
align-items: flex-end;
}
.flex-row-stretch() {
align-items: stretch;
}
.flex-row-left() {
justify-content: flex-start;
}
.flex-row-center() {
justify-content: center;
}
.flex-row-right() {
justify-content: flex-end;
}
.flex-row-space() {
justify-content: space-between;
}
.flex-row-wrap() {
flex-wrap: wrap;
}
.flex-col-normal() {
display: flex;
flex-direction: column;
}
.flex-col(@children: @flex-grow-children) {
.flex-col-normal();
.flex-col-child(@children);
}
.flex-col-child(@children: @flex-grow-children) {
.loop(@children, 0);
.loop(@arr, @index: 0) when (@index < length(@arr)) {
@item: extract(@arr, @index + 1);
& > @{item} {
flex-grow: 1;
}
.loopMore(@flex-shrink-children, 0, @item);
.loop(@arr, @index + 1);
}
.loopMore(@arr, @index: 0, @not) when (@index < length(@arr)) {
@item: extract(@arr, @index + 1);
.child-map({
flex-shrink: 0;
}, @item, @not);
.loopMore(@arr, @index + 1, @not);
}
}
.flex-col-top() {
justify-content: flex-start;
}
.flex-col-middle() {
justify-content: center;
}
.flex-col-bottom() {
justify-content: flex-end;
}
.flex-col-space() {
justify-content: space-between;
}
.flex-col-left() {
align-items: flex-start;
}
.flex-col-center() {
align-items: center;
}
.flex-col-right() {
align-items: flex-end;
}
.flex-center() {
.flex-row-normal();
.flex-row-center();
}
.flex-column(@column: 2, @children: @flex-column-grow-children) {
display: flex;
.loop(@children, 0);
.loop(@arr, @index: 0) when (@index < length(@arr)) {
@item: extract(@arr, @index + 1);
& > @{item} {
flex-grow: 1;
flex-basis: (100 / @column) * 1%;
}
.loop(@arr, @index + 1);
}
}
.flex-build() {
.flex-row {
.flex-row();
&.top {
.flex-row-top();
}
&.middle {
.flex-row-middle();
}
&.bottom {
.flex-row-bottom();
}
&.stretch {
.flex-row-stretch();
}
&.left {
.flex-row-left();
}
&.center {
.flex-row-center();
}
&.right {
.flex-row-right();
}
&.space {
.flex-row-space();
}
&.wrap {
.flex-row-wrap();
}
}
.flex-col {
.flex-col();
&.top {
.flex-col-top();
}
&.middle {
.flex-col-middle();
}
&.bottom {
.flex-col-bottom();
}
&.space {
.flex-col-space();
}
&.left {
.flex-col-left();
}
&.center {
.flex-col-center();
}
&.right {
.flex-col-right();
}
}
.flex-center {
.flex-center();
}
.col-in {
&-2 {
.flex-column(2);
}
&-3 {
.flex-column(3);
}
&-4 {
.flex-column(4);
}
&-5 {
.flex-column(5);
}
}
}
.input-file(@children: @input-file-children) {
position: relative;
vertical-align: middle;
cursor: pointer;
.loop(@children, 0);
.loop(@arr, @index: 0) when (@index < length(@arr)) {
@item: extract(@arr, @index + 1);
& > @{item} {
position: absolute;
left: -999em;
}
.loop(@arr, @index + 1);
}
}
.pure() {
.reset();
}
.form-build() {
label.input-file {
.input-file();
}
.pure {
.pure();
}
}
.gap-build();
@import './var.less';
.margin(@n: @row-gap) {
margin: @n;
}
.margin-row(@n: @row-gap) {
margin-left: @n;
margin-right: @n;
}
.margin-col(@n: @row-gap) {
margin-top: @n;
margin-bottom: @n;
}
.padding(@n: @row-gap) {
padding: @n;
}
.padding-row(@n: @row-gap) {
padding-left: @n;
padding-right: @n;
}
.padding-col(@n: @row-gap) {
padding-top: @n;
padding-bottom: @n;
}
.margin-center() {
.margin-row(auto);
}
.child-gap-right(@n: @child-gap, @children: @child-gap-children, @not: @child-gap-children-not) {
.loop(@children, 0);
.loop(@arr, @index: 0) when (@index < length(@arr)) {
@item: extract(@arr, @index + 1);
.child-map({
margin-right: @n;
}, @item, @not);
.loop(@arr, @index + 1);
}
}
.child-gap-bottom(@n: @child-gap, @children: @child-gap-children, @not: @child-gap-children-not) {
.loop(@children, 0);
.loop(@arr, @index: 0) when (@index < length(@arr)) {
@item: extract(@arr, @index + 1);
.child-map({
margin-bottom: @n;
}, @item, @not);
.loop(@arr, @index + 1);
}
}
.gap-build() {
.margin-dir-loop(@dir:right, @n, @i:1) when (@i <= @n) {
@gap: @i * 5;
.margin-@{dir}-@{gap} {
margin-@{dir}: @gap * @px;
}
.margin-dir-loop(@dir, @n, (@i + 1));
}
.margin-dir-loop(left, 10);
.margin-dir-loop(right, 10);
.margin-dir-loop(top, 10);
.margin-dir-loop(bottom, 10);
.padding-dir-loop(@dir:right, @n, @i:1) when (@i <= @n) {
@gap: @i * 5;
.padding-@{dir}-@{gap} {
padding-@{dir}: @gap * @px;
}
.padding-dir-loop(@dir, @n, (@i + 1));
}
.padding-dir-loop(left, 10);
.padding-dir-loop(right, 10);
.padding-dir-loop(top, 10);
.padding-dir-loop(bottom, 10);
.child-gap-loop(@dir: right, @n, @i: 1) when (@i <= @n) {
@gap: @i * 5;
.gap-@{dir}-@{gap} {
.if(@_dir) when (@_dir = right) {
.child-gap-right(@gap * @px);
}
.if(@_dir) when (@_dir = bottom) {
.child-gap-bottom(@gap * @px);
}
.if(@dir);
}
.child-gap-loop(@dir, @n, (@i + 1));
}
.child-gap-loop(right, 10);
.child-gap-loop(bottom, 10);
.margin-center {
.margin-center();
}
}
.image(@url: 1, @size: cover, @postion: center, @repeat: no-pepeat) {
background-image: url(@url);
background-position: @postion;
background-repeat: @repeat;
background-size: @size;
}
.image-property(@size: cover, @postion: center, @repeat: no-pepeat) {
background-position: @postion;
background-repeat: @repeat;
background-size: @size;
}
@import "./var.less";
@import "./border.less";
@import "./flex.less";
@import "./form.less";
@import "./gap.less";
@import "./layout.less";
@import "./others.less";
@import "./position.less";
@import "./text.less";
@import "./utils.less";
@import "./image.less";
@import "./others.less";
.wh(@width, @height) {
width: @width;
height: @height;
}
.inblock-row(@children: @inblock-row-children) {
letter-spacing: -1em;
.loop(@children, 0);
.loop(@arr, @index: 0) when (@index < length(@arr)) {
@item: extract(@arr, @index + 1);
& > @{item} {
display: inline-block;
letter-spacing: 0;
}
.loop(@arr, @index + 1);
}
}
.float-row(@children: @float-row-children) {
.clearfix();
.loop(@children, 0);
.loop(@arr, @index: 0) when (@index < length(@arr)) {
@item: extract(@arr, @index + 1);
& > @{item} {
float: left;
}
.loop(@arr, @index + 1);
}
}
.scroller() {
overflow: auto;
-webkit-overflow-scrolling: touch;
}
.scroller(@dir) when (@dir = y) {
overflow-x: hidden;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
.scroller(@dir) when (@dir = x) {
overflow-x: auto;
overflow-y: hidden;
-webkit-overflow-scrolling: touch;
}
.scroller-x() {
.scroller(x);
}
.scroller-y() {
.scroller(y);
}
.layout-build() {
.block {
display: block;
}
.inline {
display: inline;
}
.inblock {
display: inline-block;
}
.inblock-row {
.inblock-row();
}
.float-row {
.float-row();
}
.scroller {
.scroller();
}
.scroller-y {
.scroller-y();
}
.scroller-x {
.scroller-x();
}
}
@import './position.less';
.reset() {
font: inherit;
color: inherit;
background: none;
border: none;
padding:0 0;
text-decoration: none;
&::after {
display: none;
}
}
.normal-list() {
margin-top: 0;
margin-bottom: 0;
padding-left: 0;
list-style: none;
}
.clearfix() {
&::after {
display: block;
clear: both;
content: '';
}
}
.rect-box(@n: 1em) {
width: @n;
height: @n;
display: block;
}
.disabled(@grey: false) {
pointer-events: none;
user-select: none;
}
.disabled(@grey) when (@grey = true) {
filter: grayscale(1);
}
.__ratioBefore(@ratio: 1) {
&::before {
content: '';
display: block;
padding-top: @ratio * 100%;
}
}
.ratio(@ratio: 1) {
position: relative;
.__ratioBefore(@ratio);
&::after {
content: '';
display: block;
clear: both;
}
}
.image-ratio(@ratio: 1, @fit: cover, @children: @image-ratio-children) {
position: relative;
.__ratioBefore(@ratio);
.loop(@children, 0);
.loop(@arr, @index: 0) when (@index < length(@arr)) {
@item: extract(@arr, @index + 1);
& > @{item} {
.fit-cover(@fit);
}
.loop(@arr, @index + 1);
}
}
.seo-only() {
border: 0;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute !important;
width: 1px;
}
.others-build() {
.reset {
.reset();
}
.normal-list {
.normal-list();
}
.clearfix {
.clearfix();
}
.rect-box {
.rect-box();
}
.disabled {
.disabled();
&.grey {
.disabled(true);
}
}
.ratio {
.ratio();
}
.image-ratio {
.image-ratio();
}
.seo-only {
.seo-only();
}
}
.cover(@type: absolute) {
position: @type;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.fit-cover(@fit: cover) {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: @fit;
}
.pos-center(@type: absolute) {
position: @type;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.pos-bottom(@type: absolute) {
position: @type;
left: 0;
right: 0;
bottom: 0;
}
.pos-top(@type: absolute) {
position: @type;
top: 0;
left: 0;
right: 0;
}
.pos-left(@type: absolute) {
position: @type;
top: 0;
left: 0;
bottom: 0;
}
.pos-right(@type: absolute) {
position: @type;
top: 0;
right: 0;
bottom: 0;
}
.pos-top-left(@type: absolute, @offset: 0) {
position: @type;
top: @offset;
left: @offset;
}
.pos-top-right(@type: absolute, @offset: 0) {
position: @type;
top: @offset;
right: @offset;
}
.pos-bottom-left(@type: absolute, @offset: 0) {
position: @type;
bottom: @offset;
left: @offset;
}
.pos-bottom-right(@type: absolute, @offset: 0) {
position: @type;
bottom: @offset;
right: @offset;
}
.pos-hide() {
position: absolute;
left: -9999em;
}
.position-build() {
.relative {
position: relative;
}
.absolute {
position: absolute;
}
.fixed {
position: fixed;
}
.sticky {
position: sticky;
}
.cover {
.cover();
}
.fit-cover {
.fit-cover();
}
.pos-center {
.pos-center();
}
.pos-top {
.pos-top();
}
.pos-bottom {
.pos-bottom();
}
.pos-left {
.pos-left();
}
.pos-right {
.pos-right();
}
.fixed-center {
.pos-center(fixed);
}
.fixed-top {
.pos-top(fixed);
}
.fixed-bottom {
.pos-bottom(fixed);
}
.fixed-left {
.pos-left(fixed);
}
.fixed-right {
.pos-right(fixed);
}
.pos-hide {
.pos-hide();
}
}
.text-overflow() {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.text-overflow(@line) when (@line = 1) {
.text-overflow();
}
.text-overflow(@line) when (@line > 1) {
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
display: -webkit-box;
-webkit-line-clamp: @line;
-webkit-box-orient: vertical;
}
.text-left() {
text-align: left;
}
.text-right() {
text-align: left;
}
.text-center() {
text-align: left;
}
.text-last-justify(@height: 1.4em) {
text-align-last: justify;
}
.text-last-justify2(@height: 1.4em) {
text-align: justify;
height: @height;
line-height: @height;
&::after {
content: '';
width: 100%;
height: 0;
display: inline-block;
}
}
.text-build() {
.text-overflow {
.text-overflow(1);
}
.text-overflow-1 {
.text-overflow(1);
}
.text-overflow-2 {
.text-overflow(2);
}
.text-overflow-3 {
.text-overflow(3);
}
.text-overflow-4 {
.text-overflow(4);
}
.text-left {
.text-left();
}
.text-right {
.text-right();
}
.text-center {
.text-center();
}
.text-last-justify {
.text-last-justify();
}
}
.child-map(@rules, @child: '', @not: ':last-child') {
& > @{child}:not(@{not}) {
@rules();
}
}
@PI: 3.14159265359;
@px: 1px;
@gap-xs: 5 * @px;
@gap-sm: 10 * @px;
@gap-md: 15 * @px;
@gap-xl: 30 * @px;
@row-gap: @gap-md;
@child-gap: @gap-xs;
@radius-md: 4 * @px;
@radius-round: 1000em;
@radius-circle: 50%;
@border-width: 1PX;
@border-color: #e8e8e8;
@flex-grow-children: ~'.grow';
@flex-shrink-children: ~'';
@flex-column-grow-children: ~'.item', ~'.col';
@float-row-children: ~'*';
@inblock-row-children: ~'*';
@child-gap-children: ~'';
@child-gap-children-not: ~':last-child';
@input-file-children: ~'[type="file"]';
@image-ratio-children: ~'*';
@import "~taro-ui/dist/style/components/action-sheet.scss";
import capiFn from './capiFn';
/**
* 时间格式化
* @param date 接收可以被new Date()方法转换的内容
......@@ -6,12 +5,13 @@ import capiFn from './capiFn';
* @returns {String}
*/
export const dateFormatter = (date, format = 'yyyy/MM/dd') => {
if (!date) return '-';
if (!date) return '-'
date = new Date(
// eslint-disable-next-line no-restricted-globals
typeof date === 'string' && isNaN(date) ? date.replace(/-/g, '/') : date
);
let o = {
)
const o = {
'M+': date.getMonth() + 1,
'd+': date.getDate(),
'h+': date.getHours(),
......@@ -19,97 +19,20 @@ export const dateFormatter = (date, format = 'yyyy/MM/dd') => {
's+': date.getSeconds(),
'q+': Math.floor((date.getMonth() + 3) / 3),
S: date.getMilliseconds()
};
}
if (/(y+)/.test(format)) {
format = format.replace(
RegExp.$1,
(date.getFullYear() + '').substr(4 - RegExp.$1.length)
);
)
}
for (let k in o) {
for (const k in o) {
if (new RegExp('(' + k + ')').test(format)) {
format = format.replace(
RegExp.$1,
RegExp.$1.length === 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length)
);
}
}
return format;
};
/**
* 版本比较
* @param {string} cur 当前版本
* @param {string} compare 比较版本
*/
export const versionCompare = (cur, compare) => {
const _cur = cur.split('.');
const _compare = compare.split('.');
if (+_cur[0] < +_compare[0]) {
return false;
} else if ( +_cur[0] === +_compare[0] && +_cur[1] < +_compare[1] ) {
return false;
} else if (+_cur[0] === +_compare[0] && +_cur[1] === +_compare[1] && +_cur[2] < +_compare[2]) {
return false;
}
return true;
};
/**
* 获取request请求所需参数
* @param {object/string} value API项配置
*/
export const getRequestParams = value => {
if (typeof value === 'string') {
return {
handle: value,
method: 'POST',
ext: {}
}
} else if (typeof value === 'object') {
const { handle, method = 'POST', defaultParams = {}, ...ext } = value;
return {
handle,
method,
defaultParams,
ext: { ...ext }
}
} else {
console.error('getRequestParams: 传参有误');
}
};
/**
* 生成API
* @param {array} apiList API数据数组
* @param {function} request 请求
*/
export const generateAPI = (apiList, request) => {
const api = {};
Object.entries(apiList).forEach(([key, value]) => {
const { handle, method, ext, defaultParams } = getRequestParams(value);
api[key] = params => request(handle, method, Object.assign({}, defaultParams, params) , ext);
});
api.fn = capiFn(apiList, request);
return api;
};
/**
* 防连点
* @param {fn} fn 函数
* @param {wait} wait 时间
*/
export const throttleHandle = (fn, wait = 500) => {
let lastTime = 0
return function cb(...args) {
let nowTime = Date.now()
if(nowTime - lastTime > wait) {
fn.call(this,...args)
lastTime = nowTime
)
}
}
return format
}
/* eslint-disable*/
const { addStat } = require('../api')
!(function (t, i) {
typeof exports === 'object' && typeof module !== 'undefined'
? (module.exports = i())
: typeof define === 'function' && define.amd
? define(i)
: ((t = t || self).SRT = i())
})(this, function() {
'use strict'
console.log('...proxyMd')
const oldPage = Page
const oldComponent = Component
return (
(Page = function(t) {
// console.log('...Page', t)
const oldEn = t.eh
let lastTriggerTime = ''
let lastTriggerMd = ''
// 重新监听事件,加入埋点逻辑
t.eh = function(...args) {
const event = args?.[0]
const eventType = event?.type
if (eventType === 'tap') {
const element = document.getElementById(args?.[0]?.target?.id)
const mdType = element.dataset?.md
// 事件会触发两次,这里过滤掉
const canTrigger = !lastTriggerTime || mdType !== lastTriggerMd || event.timeStamp - lastTriggerTime >= 100
if (mdType && canTrigger) {
console.log('hit md ------------:>> ', mdType);
lastTriggerTime = event.timeStamp
lastTriggerMd = mdType
addStat({ type: mdType })
}
}
oldEn.call(this, ...args)
}
return oldPage(t)
}),
(Component = function(t) {
// console.log('...Comp', t)
// if (oldPage.option.proxyComponent) {
// var i = Z(
// oldPage.track.bind(oldPage),
// oldPage.setChan.bind(oldPage),
// oldPage.option.autoTrack,
// oldPage.option.openSdkShareDepth
// )
// return et(i(t))
// }
return oldComponent(t)
})
)
})
import * as tb from './tb';
const { getAuthUserInfo } = tb;
var Buffer = require("buffer").Buffer
var crypto = require("crypto")
import { Buffer } from 'buffer'
import crypto from 'crypto'
import { commonToast, getAuthUserInfo } from './tb'
const request = ({ cloud, cloudName, requestType = 'cloud', mockUrl }) => {
if (!cloud) {
console.error('请传入cloud');
return false;
}
if (!cloudName) {
console.error('请传入云函数名称');
return false;
// if (!cloud) {
// console.error('请传入cloud');
// return false;
// }
// if (!cloudName) {
// console.error('请传入云函数名称');
// return false;
// }
return async(handle, params = {}, ext = {}) => {
const defaultExt = {
method: 'GET',
showLoading: false,
toastError: true
}
return async (handle, method = 'POST', params, ext = {}) => {
const { activityId, tornadoAPI, nickName='', tbcc } = getApp();
ext = Object.assign({}, defaultExt, ext)
const method = ext.method
const { activityId, tornadoAPI, nickName = '' } = getApp()
// 默认注入activityId
params = { activityId, ...params };
params = { activityId, ...params }
if (!params.activityId) {
console.error(`${handle}:请传入activityId`);
return false;
console.error(`${handle}:请传入activityId`)
return false
}
console.log(params, handle)
const _cloudName = ext.cloudName || cloudName;
const { isShowLoading } = ext;
// console.info(params?.data, handle)
const _cloudName = ext.cloudName || cloudName
const { showLoading } = ext
const hideMyLoading = () => {
if (isShowLoading) {
my.hideLoading();
if (showLoading) {
my.hideLoading()
}
}
};
if (isShowLoading) {
my.showLoading();
if (showLoading) {
my.showLoading()
}
const requestMock = () => {
// 若请求的是风驰台的api
if (requestType === 'tornadoAPI') {
// 处理涉及的三个参数
let matchRes = tornadoAPI.match(/db=(\w*)&proxyIp=([\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3})/)
let db = matchRes[1]
let proxyIp = matchRes[2]
let data_ = {
const matchRes = tornadoAPI.match(/db=(\w*)&proxyIp=([\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3})/)
const db = matchRes[1]
const proxyIp = matchRes[2]
const data_ = {
handler: handle,
data: params
}
console.log(`data_`, data_)
let params_ = {
console.log('data_', data_)
const params_ = {
db,
proxyIp,
data: data_
}
console.log(`params_`, params_)
console.log('params_', params_)
// nickName 转 md5 作为唯一的openId
if (!nickName) {
return getAuthUserInfo().catch(err => {
console.log('未授权成功', err);
console.log('未授权成功', err)
}).then(res => {
return cb(res)
})
}
// return cb({nickName: '卞龙亭'})
function cb({nickName}) {
function cb(userInfo) {
if (!db || !proxyIp) {
my.confirm({
title: '风驰台地址错误',
content: '请确认风驰台的地址是否正确,未查找到db和proxyIp',
confirmButtonText: '确定',
cancelButtonText: '取消'
});
})
return false
}
params_.openId = md5(nickName)
// @ts-ignore
params_.openId = md5(userInfo.nickName)
return new Promise((resolve, reject) => {
my.request({
url: tornadoAPI,
......@@ -77,31 +86,32 @@ const request = ({ cloud, cloudName, requestType = 'cloud', mockUrl }) => {
data: params_,
dataType: 'json'
}).then(({ data: res }) => {
console.log(`调用风驰台返回结果`, res)
hideMyLoading();
console.log('调用风驰台返回结果', res)
hideMyLoading()
if (res && res.success) {
resolve(res);
resolve(res)
} else {
reject(res);
reject(res)
}
}).catch((e) => {
hideMyLoading();
console.log(e)
reject();
});
hideMyLoading()
reject(e)
})
})
function md5(data) {
var buf = new Buffer(data);
var str = buf.toString("binary");
return crypto.createHash("md5").update(str).digest("hex").slice(0, 30);
const buf = Buffer.from(data)
const str = buf.toString('binary')
return crypto.createHash('md5').update(str).digest('hex').slice(0, 30)
}
}
} else {
const mockUrlPrefix = {
ams: 'https://ams.dui88.com/server/index.php?g=Web&c=Mock&o=simple&projectID=218&uri=',
yapi: 'https://docs.dui88.com/mock/140/'
};
const requestPrefix = mockUrl || mockUrlPrefix[requestType];
}
// @ts-ignore
const requestPrefix = mockUrl || mockUrlPrefix[requestType]
return new Promise((resolve, reject) => {
my.request({
url: requestPrefix + _cloudName + '.' + handle,
......@@ -109,41 +119,44 @@ const request = ({ cloud, cloudName, requestType = 'cloud', mockUrl }) => {
data: params,
dataType: 'json'
}).then(({ data: res }) => {
hideMyLoading();
if (res && res.success) {
resolve(res);
} else {
resolve(res);
// reject(res);
hideMyLoading()
if (res && !res.success && ext.toastError) {
commonToast(res.message || '系统错误')
}
}).catch(() => {
hideMyLoading();
reject();
});
resolve(res)
}).catch((e) => {
hideMyLoading()
if (ext.toastError) {
commonToast('系统错误')
}
reject(e)
})
})
}
};
}
const requestCloud = () => {
return new Promise((resolve, reject) => {
cloud.function.invoke(_cloudName, params, handle).then(res => {
console.log(handle, params, res);
hideMyLoading();
if (res && res.success) {
resolve(res);
} else {
reject(res);
}
return res;
}).catch(() => {
hideMyLoading();
reject();
});
});
};
cloud.function.invoke(_cloudName, params, handle).then((res) => {
console.log(`%c${handle} 参数 ----->`, 'color: #43bb88;font-size: 12px;', params)
console.log(`%c${handle} 返回值 ----->`, 'color: #43bb88;font-size: 12px;', res)
return ['ams', 'yapi', 'tornadoAPI'].includes(requestType) ? requestMock() : requestCloud();
};
};
hideMyLoading()
if (res && !res.success && ext.toastError) {
commonToast(res.message || '系统错误')
}
resolve(res)
return res
}).catch((err) => {
console.log(`${handle} catch ----->`, params, err)
hideMyLoading()
reject(err)
})
})
}
return [ 'ams', 'yapi', 'tornadoAPI' ].includes(requestType) ? requestMock() : requestCloud()
}
}
export default request;
\ No newline at end of file
export default request
// eslint-disable-next-line no-undef
const memberPlugin = requirePlugin('cemMember')
/**
* 通用toast
* @param {string} content toast内容
......@@ -9,8 +13,8 @@ export const commonToast = (content, duration = 3000, successCb) => {
content: content || '换个姿势再试一次~',
duration,
success: successCb
});
};
})
}
/**
* 简易alert
......@@ -19,8 +23,8 @@ export const commonToast = (content, duration = 3000, successCb) => {
export const simpleAlert = content => {
my.alert({
content
});
};
})
}
/**
* 通用确认弹窗
......@@ -35,55 +39,55 @@ export const commonConfirm = (title = '提示', content, successCb) => {
confirmButtonText: '确定',
cancelButtonText: '取消',
success: result => {
const { confirm } = result;
const { confirm } = result
if (confirm) {
successCb && successCb();
successCb && successCb()
}
}
});
};
})
}
/**
* 获取授权用户信息
* @returns {object} 用户登录信息
*/
export const getAuthUserInfo = async () => {
export const getAuthUserInfo = async() => {
return new Promise((resolve, reject) => {
my.authorize({
scopes: 'scope.userInfo',
success: () => {
my.getAuthUserInfo({
success: userInfo => {
resolve(userInfo);
resolve(userInfo)
},
fail: err => {
reject(err);
reject(err)
}
});
})
},
fail: err => {
reject(err);
reject(err)
}
});
});
};
})
})
}
/**
* 获取系统信息
* @returns {object} 小程序系统信息
*/
export const getSystemInfo = async () => {
export const getSystemInfo = async() => {
return new Promise((resolve, reject) => {
my.getSystemInfo({
success: res => {
resolve(res);
resolve(res)
},
fail: err => {
reject(err);
reject(err)
}
});
});
};
})
})
}
/**
* 执行关注店铺
......@@ -92,25 +96,25 @@ export const getSystemInfo = async () => {
*/
export const favorShop = async sellerId => {
if (!sellerId) {
console.error('favorShop: 请传入卖家Id');
return false;
console.error('favorShop: 请传入卖家Id')
return false
}
return new Promise((resolve, reject) => {
my.tb.favorShop({
id: +sellerId,
success: res => {
resolve(res);
resolve(res)
},
fail: err => {
if (err.error === 11) {
// error code 11 为用户取消操作不认作失败场景
resolve(false);
resolve(false)
}
reject(err);
reject(err)
}
});
});
};
})
})
}
/**
* 判断是否关注店铺
......@@ -119,33 +123,55 @@ export const favorShop = async sellerId => {
*/
export const checkShopFavoredStatus = async sellerId => {
if (!sellerId) {
console.error('checkShopFavoredStatus: 请传入卖家Id');
return false;
console.error('checkShopFavoredStatus: 请传入卖家Id')
return false
}
return new Promise((resolve, reject) => {
my.tb.checkShopFavoredStatus({
id: +sellerId,
success: res => {
resolve(res.isFavor);
// checkShopFavoredStatus 仅支持真机调用
resolve(res?.isFavor === undefined ? true : res?.isFavor)
},
fail: err => {
reject(err);
reject(err)
}
});
});
};
})
})
}
export const checkIsMember = (sellerId = '') => {
return new Promise((resolve, reject) => {
memberPlugin.checkMember({
// sellerId为可选参数,不填则为当前小程序Owner用户ID
sellerId,
success(res) {
console.log('会员检查成功 :>> ', res)
resolve(res)
},
fail(v) {
console.log('会员检查失败 :>> ', v)
console.warn('checkMember 需在真机上调试')
reject(v)
// my.alert({
// title: '会员检查失败',
// content: JSON.stringify(v)
// })
}
})
})
}
/**
* 跳转到外部链接
* @param {string} url 跳转链接
*/
export const navigateToOutside = url => {
if (!url) {
console.error('navigateToOutside: 请传入url');
return false;
console.error('navigateToOutside: 请传入url')
return false
}
my.call('navigateToOutside', { url });
};
my.call('navigateToOutside', { url })
}
/**
* 跳转到内部链接(新开窗口)
......@@ -153,11 +179,11 @@ export const navigateToOutside = url => {
*/
export const navigateTo = url => {
if (!url) {
console.error('navigateTo: 请传入url');
return false;
console.error('navigateTo: 请传入url')
return false
}
my.navigateTo({ url });
};
my.navigateTo({ url })
}
/**
* 跳转到内部链接(不新开窗口)
......@@ -165,28 +191,28 @@ export const navigateTo = url => {
*/
export const redirectTo = url => {
if (!url) {
console.error('redirectTo: 请传入url');
return false;
console.error('redirectTo: 请传入url')
return false
}
my.redirectTo({ url });
};
my.redirectTo({ url })
}
/**
* 获取服务器时间
* @returns {number} 服务器时间戳
*/
export const getServerTime = async () => {
export const getServerTime = async() => {
return new Promise((resolve, reject) => {
my.getServerTime({
success: res => {
resolve(res.time);
resolve(res.time)
},
fail: err => {
reject(err);
reject(err)
}
});
});
};
})
})
}
/**
* 收藏商品
......@@ -195,25 +221,25 @@ export const getServerTime = async () => {
*/
export const collectGoods = async id => {
if (!id) {
console.error('collectGoods: 请传入商品id');
return false;
console.error('collectGoods: 请传入商品id')
return false
}
return new Promise((resolve, reject) => {
my.tb.collectGoods({
id: +id,
success: res => {
resolve(res);
resolve(res)
},
fail: err => {
if (err.error === 11) {
// error code 11 为用户取消操作不认作失败场景
resolve(false);
resolve(false)
}
reject(err);
reject(err)
}
});
});
};
})
})
}
/**
* 查询商品收藏状态
......@@ -222,18 +248,18 @@ export const collectGoods = async id => {
*/
export const checkGoodsCollectedStatus = async id => {
if (!id) {
console.error('checkGoodsCollectedStatus: 请传入商品id');
return false;
console.error('checkGoodsCollectedStatus: 请传入商品id')
return false
}
return new Promise(resolve => {
my.tb.checkGoodsCollectedStatus({
id: +id,
success: res => {
resolve(res.isCollect);
resolve(res.isCollect)
}
});
});
};
})
})
}
/**
* 跳转到淘宝商品页
......@@ -241,40 +267,40 @@ export const checkGoodsCollectedStatus = async id => {
*/
export const openDetail = async itemId => {
if (!itemId) {
console.error('openDetail: 请传入商品id');
return false;
console.error('openDetail: 请传入商品id')
return false
}
return new Promise((resolve, reject) => {
my.tb.openDetail({
itemId: itemId + '',
success: res => {
resolve(res);
resolve(res)
},
fail: res => {
reject(res);
reject(res)
}
});
});
};
})
})
}
/**
* 创建音频类
*/
class AudioModel {
constructor(opts = {}) {
const { src } = opts;
const { src } = opts
if (!src) {
console.error('请输入音频链接');
return false;
console.error('请输入音频链接')
return false
}
try {
const innerAudioContext = my.createInnerAudioContext();
const innerAudioContext = my.createInnerAudioContext()
Object.keys(opts).forEach(item => {
innerAudioContext[item] = opts[item];
});
return innerAudioContext;
innerAudioContext[item] = opts[item]
})
return innerAudioContext
} catch (e) {
console.error('当前版本不支持音频');
console.error('当前版本不支持音频')
}
}
}
......@@ -283,32 +309,33 @@ class AudioModel {
* @param {object} opts 配置对象 例 { src: '音频链接', autoplay: false }
*/
export const createAudio = opts => {
const audioInstance = new AudioModel(opts);
return audioInstance;
};
const audioInstance = new AudioModel(opts)
return audioInstance
}
/**
* 获取淘宝用户收货地址
*/
export const getUserAddress = async (opts = {}) => {
let defaults = {
export const getUserAddress = async(opts = {}) => {
const defaults = {
addAddress: 'show',
searchAddress: 'hide',
locateAddress: 'hide',
joinTag: '' // 分割收货地址符号(淘宝商家有特定发货需求)
};
let _opts = Object.assign({}, defaults, opts);
}
const _opts = Object.assign({}, defaults, opts)
return new Promise((resolve, reject) => {
my.authorize({
scopes: 'scope.addressList',
success: () => {
const { addAddress, searchAddress, locateAddress, joinTag } = _opts;
const { addAddress, searchAddress, locateAddress, joinTag } = _opts
if (!my.tb.chooseAddress) {
/* eslint-disable*/
reject({
noSupport: true,
errorMessage: '当前版本不支持选择收货地址,请升级到最新版本'
});
return false;
})
return false
}
my.tb.chooseAddress(
{
......@@ -317,51 +344,51 @@ export const getUserAddress = async (opts = {}) => {
locateAddress
},
res => {
const { error, provinceName, countyName, cityName, streetName, detailInfo, name, telNumber } = res;
const { error, provinceName, countyName, cityName, streetName, detailInfo, name, telNumber } = res
// 如果有error为用户取消操作不做操作
if (error) {
resolve(false);
resolve(false)
} else {
resolve({
...res,
duibaAddress: {
name,
phone: telNumber,
address: [provinceName, cityName, countyName, streetName, detailInfo].join(joinTag)
address: [ provinceName, cityName, countyName, streetName, detailInfo ].join(joinTag)
}
});
})
}
},
res => {
reject(res);
reject(res)
}
);
)
},
fail: err => {
reject(err)
}
});
});
};
})
})
}
/**
* 选择图片
*/
export const chooseImage = async (opts = {}) => {
let defaults = {
export const chooseImage = async(opts = {}) => {
const defaults = {
count: 1,
sourceType: ['camera', 'album'],
sourceType: [ 'camera', 'album' ],
compressLevel: 1
};
let _opts = Object.assign({}, defaults, opts);
const { count, sourceType, compressLevel } = _opts;
}
const _opts = Object.assign({}, defaults, opts)
const { count, sourceType, compressLevel } = _opts
return new Promise((resolve, reject) => {
if (!my.chooseImage) {
reject({
noSupport: true,
errorMessage: '当前版本不支持选择图片,请升级到最新版本'
});
return false;
})
return false
}
my.chooseImage({
count,
......@@ -371,24 +398,24 @@ export const chooseImage = async (opts = {}) => {
apFilePaths: res.apFilePaths,
level: compressLevel,
success: data => {
resolve(data);
resolve(data)
},
fail: err => {
resolve(err);
resolve(err)
}
});
})
},
fail: err => {
// error code 2001 为摄像头授权取消、ios 11为用户取消操作不认作失败场景 、安卓 为1
if (err.error !== 2001 && err.error !== 11 && err.error !== 1) {
reject(err);
reject(err)
} else {
resolve(false);
resolve(false)
}
}
});
});
};
})
})
}
/**
* 获取图片信息
......@@ -399,133 +426,133 @@ export const getImageInfo = async src => {
my.getImageInfo({
src,
success: res => {
resolve(res);
resolve(res)
},
fail: err => {
reject(err);
reject(err)
}
});
});
};
})
})
}
/**
* 获取图片信息
* @param {*} opts chooseImage配置参数
*/
export const uploadImage = async (opts = {}) => {
const { cloud } = getApp();
export const uploadImage = async(opts = {}) => {
const { cloud } = getApp()
if (!cloud) {
console.error('chattingSendCard: 请传入app.cloud');
return false;
console.error('chattingSendCard: 请传入app.cloud')
return false
}
const chooseImageResult = await chooseImage(opts).catch(err => {
commonToast(err && (err.errorMessage || '选择图片失败'));
});
commonToast(err && (err.errorMessage || '选择图片失败'))
})
if (chooseImageResult) {
const { apFilePaths } = chooseImageResult;
const filePath = apFilePaths[0];
const { apFilePaths } = chooseImageResult
const filePath = apFilePaths[0]
const imageInfo = await getImageInfo(filePath).catch(err => {
commonToast(err && err.errorMessage);
});
commonToast(err && err.errorMessage)
})
// 找不到图片信息
if (!imageInfo) {
return false;
return false
}
const { path: imagePath } = imageInfo;
const { path: imagePath } = imageInfo
const { url } = await cloud.file.uploadFile({
filePath,
fileType: 'image',
fileName: imagePath.split('/').pop()
});
return url;
})
return url
}
return false;
return false
}
/**
* 分享到群聊卡片
* @param {object} requestInfo 分享到群聊卡片所需请求参数
*/
export const chattingSendCard = async (requestInfo) => {
const { cloud } = getApp();
export const chattingSendCard = async(requestInfo) => {
const { cloud } = getApp()
if (!cloud) {
console.error('chattingSendCard: 请传入app.cloud');
return false;
console.error('chattingSendCard: 请传入app.cloud')
return false
}
if (!requestInfo) {
console.error('chattingSendCard: 请传入卡片请求参数');
return false;
console.error('chattingSendCard: 请传入卡片请求参数')
return false
}
return new Promise((resolve, reject) => {
my.authorize({
scopes: 'scope.groupChat',
success: async () => {
success: async() => {
const result = await cloud.topApi
.invoke({
api: 'taobao.chatting.platform.message.sendcard',
data: { request_info: JSON.stringify(requestInfo) }
})
.catch(err => {
reject(err);
});
reject(err)
})
if (result) {
const { error } = result;
const { error } = result
if (error) {
resolve(false);
resolve(false)
} else {
resolve(result);
resolve(result)
}
}
},
fail: err => {
if (err.error === 11) {
// error code 11 为用户取消操作不认作失败场景
resolve(false);
resolve(false)
}
reject(err);
reject(err)
}
});
});
};
})
})
}
/**
* 文本风险校验
* @param {string} text 文本
*/
export const textRiskIdentification = async (text) => {
export const textRiskIdentification = async(text) => {
return new Promise((resolve, reject) => {
if (!my.tb.textRiskIdentification) {
/* eslint-disable*/
reject({
noSupport: true,
errorMessage: '不支持文本校验'
});
})
}
my.tb.textRiskIdentification({
data:{
data: {
text
},
success: res => {
if (res.data && res.data.result) {
const { suggestion }= res.data.result;
const { suggestion } = res.data.result
if (suggestion === 'pass') {
resolve(true);
resolve(true)
}
}
resolve(false);
resolve(false)
},
fail: err => {
reject(err)
},
}
})
})
}
// 剪切板
export const setClipboard = async (params) => {
export const setClipboard = async(params) => {
return await promisifyMyApi('setClipboard', params)
};
}
function promisifyMyApi(methodName, params = {}) {
return new Promise((resolve, reject) => {
......
export const noopFn = () => {}
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment