Commit 3c56c468 authored by 汪忙忙's avatar 汪忙忙

init

parent a3afdbf2
File added
# taobao-mini-template
module.exports = [
{
type: 'input',
name: 'author',
message: 'author?',
},
{
type: 'input',
name: 'description',
message: 'description?',
}
]
\ No newline at end of file
import request from './utils/request';
const API = {
// 上传数据生成xslx文件 uploadDataCreateFile
uploadDataCreateFile: params => request('exportAwardsList', 'POST', params),
// 保存活动配置 saveActivityInfo
saveActivityInfo: params => request('saveActivityInfo', 'POST', params),
// 删除活动 delActivity
delActivity: params => request('delActivity', 'POST', params),
// 授权信息保存 sellerSave
sellerSave: params => request('sellerSave', 'POST', params),
// 生成活动规则 generateRule
generateRule: params => request('generateRule', 'POST', params),
// 获取中奖名单 findWinnerInfoList
findWinnerInfoList: params => request('findWinnerInfoList', 'POST', params),
// 获取活动列表云函数 getActivityList
getActivityList: params => request('getActivityList', 'POST', params),
// 获取活动配置 getActivityDetail
getActivityDetail: params => request('getActivityDetail', 'POST', params),
// 获取淘宝店家商品 findItemListByStatus
findItemListByStatus: params => request('findItemListByStatus', 'POST', params, {
isShowLoading: true
}),
// 通过id去查找商品 findItemListByIds
findItemListByIds: params => request('findItemListByIds', 'POST', params, {
isShowLoading: true
}),
// 通过权益ename查询商品 queryBenefitByEname
queryBenefitByEname: params => request('queryBenefitByEname', 'POST', params),
};
export default API;
\ No newline at end of file
page {
background: #f7f7f7;
min-height: 100vh;
}
.adaptive-framework {
min-height: calc(100vh - 50px);
}
\ No newline at end of file
import cloud from '@tbmp/mp-cloud-sdk';
import { env } from './config';
cloud.init({
env,
});
App({
cloud,
onLaunch(options) {
console.info('App onLaunch');
},
onShow(options) {
// 从后台被 scheme 重新打开
// options.query == {number:1}
},
});
\ No newline at end of file
{
"pages": [
"pages/index/index"
],
"window": {
"defaultTitle": "xxxxx店铺商家后台"
},
"plugins": {
"myPlugin": {
"version": "*",
"provider": "3000000002026202"
}
}
}
.db-dialog-content {
width: 515px;
background-color: #fff;
}
.db-dialog-title {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 16px;
font-weight: bold;
border-bottom: 1px solid rgba(215,219,224,1);
height: 60px;
padding: 0 24px;
}
.db-dialog-content_inner {
/* width: 100%; */
padding: 24px;
}
.db-dialog-footer {
display: flex;
justify-content: flex-end;
border-top: 1px solid rgba(215,219,224,1);
}
.db-dialog-btns {
padding: 12px;
}
.db-dialog-btns Button {
margin-right: 15px;
}
\ No newline at end of file
<overlay
visible="{{ visible }}"
hasMask="{{true}}"
triggerType="click"
wrapperClassName="overlay"
disableScroll
target=".edit-breadcrumb"
data-index="1"
canCloseByMask="{{true}}"
>
<view class="db-dialog-content" style="{{ { width: (width + 'px').replace('pxpx', 'px') } }}">
<view class="db-dialog-title">{{title}}<icon size="xs" type="close" onTap="closeDialog" style="color: #999999;" /></view>
<view class="db-dialog-content_inner">
<slot/>
</view>
<view class="db-dialog-footer" a:if="{{hasFooter}}">
<view class="db-dialog-btns">
<button type="primary">确定</button>
<button onTap="closeDialog">取消</button>
</view>
</view>
</view>
</overlay>
\ No newline at end of file
Component({
mixins: [],
data: {},
props: {
title: '',
visible: false,
onClose: () => {}
},
didMount() {},
didUpdate() {},
didUnmount() {},
methods: {
closeDialog() {
this.props.onClose()
}
},
});
.db-layout {
padding: 25px 75px;
display: flex;
}
.db-content {
flex:1;
width: 100%;
margin: 0 0 0 25px;
}
\ No newline at end of file
<view class="db-layout">
<side-bar
menu="{{menu}}"
info="{{info}}"
defaultActiveKey="{{defaultActiveKey}}"
activeKey="{{activeKey}}"
onMenuChange="onSideBarMenuChange"
/>
<view class="db-content">
<slot/>
</view>
</view>
\ No newline at end of file
Component({
mixins: [],
data: {},
props: {
menu: () => [],
info: () => ({}),
activeKey: '',
onMenuChange: () => {
}
},
didUpdate() {},
didUnmount() {},
methods: {
onSideBarMenuChange({ key, path }) {
this.props.onMenuChange({ key, path })
}
},
});
{
"component": true,
"usingComponents": {
"side-bar": "/components/base/side-bar/side-bar"
}
}
\ No newline at end of file
.db-side-bar {
width: 180px;
min-width: 180px;
min-height: calc(100vh - 50px);
background-color: #fff;
}
.db-infos {
width: 100%;
height: 144px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.db-logo {
width: 40px;
height: 46px;
}
.db-company-name {
font-size: 14px;
font-weight: bold;
text-align: center;
margin: 18px 0 0 0;
}
.db-menu-list {
height:48px;
color:rgba(51,134,241,1);
line-height:48px;
text-align: center;
font-size: 14px;
}
.db-menu-list_active {
background:rgba(235,243,255,1);
}
\ No newline at end of file
<view class="db-side-bar">
<view class="db-infos">
<image class="db-logo" src="{{info.logo}}" />
<text class="db-company-name">{{info.company}}</text>
</view>
<view>
<view onTap="handleMenuClick" data-key="{{item.key}}" data-path="{{item.path}}" class="{{item.key === activeKey ? `db-menu-list db-menu-list_active`: `db-menu-list`}}" a:for="{{menu}}" a:key="*this">
<text>{{item.title}}</text>
</view>
</view>
</view>
\ No newline at end of file
Component({
mixins: [],
data: {},
props: {
info: () => {
return {
company: '兑吧科技',
miniappName: '兑吧科技',
logo: '//yun.duiba.com.cn/duiba_miniProgram/logo.png'
}
},
activeKey: '',
onMenuChange: () => {}
},
didMount() {},
didUpdate() {},
didUnmount() {},
methods: {
handleMenuClick(event) {
let { key, path } = event.target.dataset;
this.props.onMenuChange({ key, path });
}
},
});
{
"component": true
}
\ No newline at end of file
.confirm-dialog-content {
width: 515px!important;
min-height: 225px;
background-color: #fff;
}
.confirm-dialog-title {
color:rgba(51,51,51,1);
font-size: 16px;
height: 60px;
padding: 0 24px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid rgba(215,219,224,1);
font-weight: bold;
}
.confirm-dialog-tip {
height: 100px;
padding: 33px 24px 0 24px;
font-size: 12px;
}
.confirm-dialog-tip_inner {
display: flex;
align-items: center;
}
.close-icon {
font-size: 16px;
cursor: pointer;
}
.confirm-dialog-footer {
display: flex;
justify-content: flex-end;
border-top: 1px solid rgba(215,219,224,1);
}
.confirm-dialog-btns {
padding: 12px;
}
.confirm-dialog-btns Button {
margin-right: 15px;
}
\ No newline at end of file
<overlay
visible="{{ visible }}"
hasMask="{{true}}"
triggerType="click"
wrapperClassName="overlay"
disableScroll
target=".edit-breadcrumb"
data-index="1"
canCloseByMask="{{true}}"
>
<view class="confirm-dialog-content">
<view class="confirm-dialog-title">提示<icon size="xs" type="close" onTap="closeDialog" style="color: #999999;" /></view>
<view class="confirm-dialog-tip">
<view class="confirm-dialog-tip_inner">
<icon size="xs" type="warning" onTap="closeDialog" style="color: #FBA628;margin-right: 4px;" />
<text>{{content}}</text>
</view>
</view>
<view class="confirm-dialog-footer">
<view class="confirm-dialog-btns">
<button type="primary" onTap="confirmDialog">确定</button>
<button onTap="closeDialog">取消</button>
</view>
</view>
</view>
</overlay>
\ No newline at end of file
Component({
mixins: [],
data: {},
props: {
content: '',
visible: false,
onClose: () => {},
onConfirm: () => {}
},
didMount() {},
didUpdate() {},
didUnmount() {},
methods: {
closeDialog() {
this.props.onClose("link");
},
confirmDialog() {
this.props.onConfirm();
}
},
});
.modal-content-imageUpload-wrap{
display:flex;
padding-top:10px;
box-sizing:border-box;
}
.modal-content-imageUpload-btn view{
margin-bottom:5px;
color:#666666
}
.edit-content-formitem-choosePrize-tips-success{
color:#71B204 !important;
padding-top:20px;
font-size:14px;
}
.edit-content-formitem-choosePrize-tips-fail{
color:#F23C3C !important;
padding-top:20px;
font-size:14px;
}
.edit-content-formitem-choosePrize-wrap{
display:flex;
}
.edit-content-input.edit-probablity-input {
width: 300px!important;
margin-right: 10px;
}
.edit-content-formitem-goequity{
width:80px;
height:30px;
border-radius:5px;
background:#3386F1;
color:#fff;
text-align:center;
line-height:30px;
}
.modal-content-imageUpload-btn{
display:flex;
flex-direction:column;
justify-content:space-around;
}
.modal-content-imageUpload-btn text{
font-size:12px;
margin-bottom:5px;
color:#666666;
}
.edit-content-formItem-nocoupon{
color:#000 !important;
margin-left:5px;
}
\ No newline at end of file
<view>
<overlay
visible="{{showPrize}}"
onRequestClose="onClose"
disableScroll
hasMask="{{showDialogMask}}"
wrapperClassName="overlay"
wrapperStyle="{{{'top':dialogTop + 'px'}}}"
target=".edit-breadcrumb"
canCloseByMask="true"
data-index="0"
>
<view class="modal-content">
<view class="modal-content-title">选择奖品<icon size="small" type="close" onTap="closePrize" style="color: #999999;" /></view>
<view class="modal-content-form">
<form class="edit-content-form" inline="true">
<form-item style="width:100%" size="large" class="edit-content-formItem edit-content-formItem-choosePrize" label="奖品类型" required>
<view class="edit-content-formitem-choosePrize-wrap">
<select onChange="onPrizeTypeChange" defaultValue="{{prizeDialogEdit ? prizeDialogData.record.type : prizeInitData.type}}">
<option value="2">优惠券</option>
<option value="4">实物</option>
<option value="3">积分</option>
</select>
</view>
</form-item>
<form-item
a:if="{{prizeInitData.type == OBJECT_TYPE || prizeInitData.type == CREDITS_TYPE}}"
size="large"
class="edit-content-formItem"
label="奖品名称"
validateState="{{prizeNameTips.status}}" help="{{prizeNameTips.content}}"
required>
<input class="edit-content-input" data-name="name" onChange="onChangePrizeValue" maxLength="10" value="{{prizeInitData.name}}" hasLimitHint="true" name="奖品名称" defaultValue="{{prizeDialogEdit ? prizeDialogData.record.name : prizeInitData.name}}" placeholder="请输入奖品名称"/>
</form-item>
<form-item
a:if="{{prizeInitData.type == OBJECT_TYPE}}"
size="large"
class="edit-content-formItem"
label="奖品库存"
validateState="{{prizeNumberTips.status}}" help="{{prizeNumberTips.content}}"
required>
<input type="number" class="edit-content-input" data-name="restStock" onChange="onChangePrizeValue" type="number" value="{{prizeInitData.restStock}}" hasLimitHint="true" name="奖品库存" defaultValue="{{prizeDialogEdit ? prizeDialogData.record.restStock : prizeInitData.restStock}}" placeholder="请输入奖品库存"/>
</form-item>
<form-item
a:if="{{prizeInitData.type == CREDITS_TYPE}}"
size="large"
class="edit-content-formItem"
label="积分价值"
validateState="{{creditsValueTips.status}}" help="{{creditsValueTips.content}}"
required>
<input type="number" class="edit-content-input" data-name="credits" onChange="onChangePrizeValue" type="number" value="{{prizeInitData.credits}}" hasLimitHint="true" name="积分价值" defaultValue="{{prizeDialogEdit ? prizeDialogData.record.credits : prizeInitData.credits}}" placeholder="请输入积分价值"/>
</form-item>
<form-item a:if="{{prizeInitData.type == EQUITY_TYPE}}" validateState="{{choosePrizeTips.status}}" style="width:100%" size="large" help="{{choosePrizeTips.content}}" class="edit-content-formItem edit-content-formItem-choosePrize" label="选择奖品" required>
<view class="edit-content-formitem-choosePrize-wrap">
<input a:if="{{prizeInitData.name}}" disabled="true" class="edit-content-input" style="margin-right:10px" onChange="changeInput" maxLength="10" value="{{prizeInitData.name}}" hasLimitHint="true" name="活动名称" defaultValue="{{prizeDialogEdit ? prizeDialogData.record.name : prizeInitData.name}}" placeholder="请选择奖品"/>
<view class="edit-content-formitem-goequity" onTap="navigateToPlugin" type="primary">{{prizeInitData.ename ? '重新选择' : '选择奖品'}}</view>
</view>
</form-item>
<form-item size="large" required class="edit-content-formItem" label="奖品图片" validateState="{{imageTips.status}}" help="{{imageTips.content}}">
<view class="modal-content-imageUpload-wrap">
<image style="width:90px;height:90px;border:1px solid #ccc;margin-right:10px" src="{{prizeInitData.image}}"></image>
<view class="modal-content-imageUpload-btn">
<view>奖品图片</view>
<text>尺寸为300px * 300px,格式为jpg/png</text>
<view></view>
<button onTap="uploadImage" type="primary">上传图片</button>
</view>
</view>
</form-item>
<form-item size="large" class="edit-content-formItem" label="中奖概率" validateState="{{probablityTips.status}}" help="{{probablityTips.content}}" required>
<input
class="edit-content-input edit-probablity-input" type="number" onChange="onChangePrizeValue" data-name="probablity" value="{{prizeInitData.probablity}}" name="中奖概率" defaultValue="{{prizeInitData.probablity}}" placeholder="当前概率不能超过100"/>%
</form-item>
<!-- <form-item size="large" class="edit-content-formItem" label="奖品库存" required>
<input disabled="true" class="edit-content-input" onChange="changeInput" maxLength="10" value="{{prizeInitData.name}}" hasLimitHint="true" name="活动名称" defaultValue="{{prizeDialogEdit ? prizeDialogData.record.name : prizeInitData.name}}" placeholder="请输入奖品库存"/>
</form-item> -->
</form>
</view>
<view class="modal-content-btn">
<button onTap="updatePrize" type="primary">确定</button>
<button onTap="closePrize">取消</button>
</view>
</view>
</overlay>
</view>
\ No newline at end of file
const {
cloud
} = getApp()
const {
function: fc
} = cloud
const plugin = requirePlugin('myPlugin')
import {
bizCode
} from '../../config';
import {
queryBenefitByEname
} from '../../api';
import { chooseImage, getImageInfo, validateRangeNumber } from '/utils';
const EQUITY_TYPE = 2;
const CREDITS_TYPE = 3;
const OBJECT_TYPE = 4;
const THANKS_TYPE = 5;
Component({
mixins: [],
data: {
EQUITY_TYPE,
CREDITS_TYPE,
OBJECT_TYPE,
THANKS_TYPE,
showDialogMask: true,
dialogTop: '',
rankTips: {
status: 'success',
content: ''
},
imageTips: {
status: 'success',
content: ''
},
probablityTips: {
status: 'success',
content: ''
},
prizeNameTips: {
status: 'success',
content: ''
},
prizeNumberTips: {
status: 'success',
content: ''
},
creditsValueTips: {
status: 'success',
content: ''
},
prizeInitData: {
ename: '',
id: '',
restStock: '',
activityOutId: '',
type: 2,
image: '',
credits: '',
probablity: '',
name: ''
}
},
props: {
prizeDialogData: {
index: '',
record: {}
},
activityOutId: '',
prizeDialogEdit: false,
showPrize: false,
hasEditPrize: false,
datasource: [],
onCloseDialog: function () {},
onUpdateLevel: function () {},
onUpdateHasChange: function () {},
},
didMount() {
this.initPosition()
let that = this
const bridge = {
bizCode, //此处输入想配置的商家应用appID
//这个方法用于获取插件中用户选择的奖池enname
async getCheckBenefitID({ ename, poolID }) {
that.setData({
prizeInitData: {
...that.data.prizeInitData,
ename
}
})
my.showToast({
type: 'success',
content: '已选择权益奖品'
})
const { success, data, message } = await queryBenefitByEname({ ename })
if (success) {
const { benefitName, rightTypeId, startTime, endTime, restStock } = data[0];
that.setData({
prizeInitData: {
...that.data.prizeInitData,
name: benefitName,
type: rightTypeId,
startTime,
endTime,
restStock
}
})
that.$page.data.backPageTimeOut = setTimeout(() => {
my.navigateBack({
delta: 1
})
}, 6000)
} else {
my.showToast({
type: 'fail',
content: message
})
}
}
}
this.resetPrizeInit()
plugin.setBridge(bridge)
},
methods: {
initPosition() {
const {
scrollTop
} = this.$page.data
this.setData({
dialogTop: scrollTop * 2
})
},
onPrizeTypeChange(e) {
let value = e.detail.value;
this.setData({
prizeInitData: {
...this.data.prizeInitData,
type: value
}
})
},
navigateToPlugin() {
clearTimeout(this.$page.data.backPageTimeOut)
my.navigateTo({
url: 'plugin://myPlugin/orightindex-page'
})
},
resetPrizeInit() {
if (this.props.prizeDialogEdit) {
const {
rank,
...rest
} = this.props.prizeDialogData.record
this.setData({
prizeInitData: {
...rest
}
})
}
},
async uploadImage() {
try {
this.setData({
showDialogMask: false
})
const res = await chooseImage();
if (!res.apFilePaths.length) return
const {
height,
width,
type,
path
} = await getImageInfo({
src: res.apFilePaths[0]
});
let imgBool = ~path.indexOf('.png') || ~path.indexOf('.jpg')
if (height !== 300 || width !== 300 || !imgBool) {
this.showItemTips('imageTips', 'error', '请按要求上传图片');
} else {
this.showItemTips('imageTips', 'success', '');
}
const {
url
} = await cloud.file.uploadFile({
filePath: res.apFilePaths[0],
fileType: 'image',
fileName: path.split('/').pop()
})
this.setData({
prizeInitData: {
...this.data.prizeInitData,
image: url
}
})
} catch (error) {
this.setData({
showDialogMask: true
})
console.error(error)
}
},
onChangePrizeValue(e) {
const key = e.target.dataset.name;
const value = e.detail.value;
this.setData({
prizeInitData: {
...this.data.prizeInitData,
[key]: value
}
})
},
changeInput(e) {
this.setData({
prizeInitData: {
...this.data.prizeInitData,
name: e.detail.value
}
})
},
updatePrize() {
const {
name,
ename,
image,
type,
startTime,
probablity,
restStock,
credits,
endTime
} = this.data.prizeInitData
const {
imageTips
} = this.data;
let probablityRexp = /^\d+(\.\d{1,2})?$/;
if (!probablityRexp.test(probablity) || probablity > 100) {
this.showItemTips('probablityTips', 'error', '奖品概率必须在0-100%之间且最多支持两位小数');
} else {
this.showItemTips('probablityTips', 'success', '');
}
const {
LUKCY_POCKET_TYPE,
COUPON_TYPE,
CREDITS_TYPE,
OBJECT_TYPE,
THANKS_TYPE,
} = this.data;
if (type === LUKCY_POCKET_TYPE && !ename) {
my.showToast({
type: 'fail',
content: '优惠券配置错误, 请重新配置'
});
return;
}
if((type == OBJECT_TYPE || type == CREDITS_TYPE) &&!name) {
this.showItemTips('prizeNameTips', 'error', '请输入奖品名称')
} else {
this.showItemTips('prizeNameTips', 'success', '')
}
if ((type == OBJECT_TYPE) && !validateRangeNumber(restStock, [0, 9999])) {
this.showItemTips('prizeNumberTips', 'error', '奖品库存为0-9999之间的整数');
} else {
this.showItemTips('prizeNumberTips', 'success', '');
}
if (type == CREDITS_TYPE && !validateRangeNumber(credits, [0, 999])) {
this.showItemTips('creditsValueTips', 'error', '请输入积分价值为0-999的整数');
} else {
this.showItemTips('creditsValueTips', 'success', '');
}
const { probablityTips, prizeNumberTips, creditsValueTips } = this.data;
const isImagePass = image && imageTips.status !== 'error';
const isEquietyPass = (type == EQUITY_TYPE) && name && isImagePass && ename && probablityTips.status !== 'error';
const isObjectPass = type == OBJECT_TYPE && name && restStock && isImagePass && probablityTips.status !== 'error' && prizeNumberTips.status !== 'error';
const isCreditsPass = type == CREDITS_TYPE && name && credits && isImagePass && probablityTips.status !== 'error' && creditsValueTips.status !== 'error';
if (
isEquietyPass || isObjectPass || isCreditsPass
) {
this.props.onUpdateLevel({
image,
name,
ename,
type,
probablity,
restStock,
credits
},
this.props.prizeDialogData.index
)
if (this.props.activityOutId) {
this.props.onUpdateHasChange(true, this.props.prizeDialogData.index);
}
this.closePrize();
} else {
my.showToast({
type: 'fail',
content: '请完整填写奖品信息'
})
}
},
showItemTips(type, status, content) {
this.setData({
[type]: {
status,
content
}
})
},
closePrize() {
this.setData({
showDialogMask: true
})
this.props.onCloseDialog('prize')
}
}
})
\ No newline at end of file
{
"component": true,
"plugins": {
"myPlugin": {
"version": "0.0.12",
"provider": "3000000002026202"
}
}
}
\ No newline at end of file
export default {
// ams:ams接口,cloud: 云函数
requestType: 'cloud',
// app环境
env: 'test',
// 线上活动地址
activityURL: 'https://m.duanqu.com/?_ariver_appid=3000000002099934&nbsv=1.0.3&nbsource=debug&nbsn=TRIAL',
// 配置的商家应用appID, 权益插件用
bizCode: '3000000003498711'
}
\ No newline at end of file
{
"name": "",
"version": "1.0.0",
"main": "",
"license": "MIT",
"dependencies": {
"@tbmp/mp-cloud-sdk": "1.1.4",
"miniapp-router": "^0.1.6",
"moment": "^2.24.0"
}
}
.edit-container {
background-color: #fff;
}
.edit-title {
font-size: 12px;
font-weight: 400;
color: rgba(153, 153, 153, 1);
}
.edit-breadcrumb {
padding: 20px 24px;
border-bottom: 2px solid rgba(240, 242, 245, 1);
}
.edit-title-separate {
margin: 0 5px;
font-weight: 900;
}
.edit-tip-icon {
width: 14px;
height: 14px;
margin-left: -10px;
}
.edit-content {
padding: 20px;
}
.edit-content-title {
font-size: 16px;
margin-bottom: 20px;
font-weight: 600;
padding-left: 26px;
}
.edit-content-input {
width: 400px !important;
}
.edit-content-formItem {
font-size: 14px;
font-weight: 400;
position: relative;
}
.edit-content-formItem-add {
width: 42px;
height: 42px;
border: 1px dashed #C9CED4;
display: flex;
justify-content: center;
align-items: center;
}
.edit-content-formItem label {
color: #fff;
}
.edit-content-formItem-qunliao {
margin-top: 15px;
font-size: 14px;
}
.edit-content-formItem-qunliao text {
color: #333333;
}
.edit-content-formItem-qunliao .formItem-qunliao-num {
color: #3386F1;
}
.edit-content-formItem-qunliao .formItem-qunliao-btn {
color: #3386F1;
margin-left: 10px;
}
.edit-content-formItem-addTableList {
height: 54px;
/* width: 600px; */
line-height: 54px;
color: #3386F1;
border-left: 1px solid #D7DBE0;
border-bottom: 1px solid #D7DBE0;
border-right: 1px solid #D7DBE0;
}
.edit-content-formItem-addTableList-notAvailable {
height: 54px;
width: 450px;
line-height: 54px;
border-left: 1px solid #D7DBE0;
border-bottom: 1px solid #D7DBE0;
border-right: 1px solid #D7DBE0;
color: #D7DBE0;
}
.edit-content-specialUrl {
color: #000 !important;
margin-right: 10px;
}
.edit-content-formItem-addTableList Icon {
margin: 0 10px;
}
.edit-content-formItem-image {
display: flex;
align-items: center;
}
.edit-content-formItem-image image {
margin-right: 5px;
}
.xunbao-tips {
width:14px;
height:14px;
display: inline-block;
position: relative;
top: 2px;
left: 4px;
background: url(//yun.duiba.com.cn/activity/mini-taobao/question.png) center no-repeat;
background-size: 100% 100%;
}
.xunbao-required {
color: #F23C3C;
font-size: 12px;
padding-right: 4px;
}
.edit-content-formItem-edit {
color: #3386F1;
}
.edit-content-formItem-delete {
margin-left: 10px;
}
.edit-content-formItem-checkbox {
margin-left: 15px;
}
.checkbox-content {
margin-left: 80px;
background-color: #F7F8FA;
padding: 20px;
}
.checkbox-content view {
margin-bottom: 10px;
}
.checkbox-content view text {
color: #3386F1;
}
.checkbox-content view .has-choose-baby {
color: #000;
margin-right: 10px;
}
.checkbox-tips {
margin: 10px 100px;
color: red;
font-size: 12px;
}
.edit-content-formItem-nodata {
height: 40px;
line-height: 40px;
display: flex;
}
.edit-content-formItem-nodata text {
color: #3386F1;
margin-left: 5px;
font-size: 14px;
}
.has-choose-baby {
margin: 0 12px 0 0;
}
.has-choose-baby text {
color: #3386F1;
}
.rule-config-textarea {
margin-left: 100px;
}
.rule-config-textarea Textarea {
width: 400px;
height: 100px;
}
.edit-activity-config-input {
width: 40px;
margin: 4px 0 0 0;
}
.submit-btn {
margin: 20px 0 0 80px;
}
.submit-btn Button {
margin-left: 20px;
}
.edit-activity-config {
display: flex;
align-items: center;
}
.modal-content {
background: #FFFFFF;
width: 738px;
max-height: 600px;
border: 1px solid #D7DBE0;
display: flex;
flex-direction: column;
box-shadow: 0px 6px 8px 0px #aaa;
}
.modal-content-title {
flex-grow: 0;
flex-shrink: 0;
font-size: 16px;
font-weight: 600;
padding: 20px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #D7DBE0;
}
.modal-content-btn {
flex-grow: 0;
flex-shrink: 0;
padding: 20px;
display: flex;
justify-content: flex-end;
border-top: 1px solid #D7DBE0;
}
.modal-content-btn Button {
margin-right: 15px;
}
.modal-content-form {
padding: 20px 20px 0px;
}
.modal-content-imageUpload {
width: 100%;
padding-left: 80px;
display: flex;
align-items: center;
margin-bottom: 20px;
}
.modal-content-imageUpload image {
margin-right: 10px;
}
.edit-content-formItem-choosePrize text {
color: #3386F1;
margin: 0 5px;
}
.edit-content-formItem-choosePrize-btn {
margin-left: 15px !important;
}
.overlay {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
right: 0;
bottom: 0;
margin: auto;
display: flex;
align-items: center;
justify-content: center;
}
.task-error-tip {
font-size: 12px;
color: #F23C3C;
margin-left: 24px;
}
.add-prize-wrap {
/* display: inline-block; */
display: inline-flex;
/* display: flex; */
align-items: center;
justify-content: center;
width: 14px;
height: 14px;
border-radius: 50%;
margin:0 8px 0 16px;
}
/* 表格样式覆盖 */
.edit-custom-table .next-table-header .next-table-cell-wrapper {
height: 50px;
line-height: 28px;
}
/* 表格样式覆盖 */
\ No newline at end of file
<view class="edit-container">
<view class="edit-breadcrumb">
<text class="edit-title" onTap="backToActivityList">我的活动</text><text class="edit-title edit-title-separate">/</text><text class="edit-title">{{activityId ? '编辑' : '新建'}} 「 xxxx 」 活动</text>
</view>
<view class="edit-content">
<form class="edit-content-form" inline="true" labelTextAlign="right" size="large" labelCol="{{labelCol}}">
<view class="edit-content-title">基础配置</view>
<form-item labelTextAlign="right" style="width:100%" validateState="{{timeRangeTipInfo.status}}" help="{{timeRangeTipInfo.content}}" class="edit-content-formItem" label="活动时间" asterisk="{{false}}">
<range-picker style="width: 400px" onVisibleChange="dialogChange" hasClear="true" value="{{timeRange}}" data-time="{{timeRange}}" onChange="pickerChange" show-time="{{ format: 'HH:mm' }}" class="block" />
</form-item>
</form>
<view class="submit-btn" a:if="{{!isEnd}}">
<button onTap="submitActive" type="primary">确定</button>
<button onTap="backToActivityList">取消</button>
</view>
</view>
</view>
\ No newline at end of file
import moment from 'moment';
import { getActivityDetail, saveActivityInfo } from '../../../api';
import { activityType } from '../const';
import { addFloat } from '../../../utils/helper';
Component({
data: {
activityInfos: {},
timeRange: [],
startTime: '',
endTime: '',
labelCol: {
fixedSpan: 5
}
},
props: {},
didMount() {
const { id } = this.$page.$router.params;
if (id || id === 0) {
this.getActivityInfo(id);
} else {
this.setDefaultTime();
}
},
methods: {
// 创建活动时, 根据规则设置活动时间
setDefaultTime() {
let nowTime = new Date().getTime();
let oneDay = 24 * 3600 * 1000;
// 当前时间 + 10分钟
let startNow = moment(nowTime + 600000).format("YYYY-MM-DD HH:mm:ss");
// 开始时间 + 7天
let endNow = moment(nowTime + 600000 + 7 * oneDay).format("YYYY-MM-DD HH:mm:ss");
this.setData({
timeRange: [ startNow, endNow ],
startTime: startNow,
endTime: endNow
})
},
backToActivityList() {
this.$page.$router.push('/activity/list')
},
// 获取id活动信息
async getActivityInfo(activityId) {
try {
const { success, data, message } = await getActivityDetail({ activityId, activityType });
if (!success) {
this.showFailToast(message);
return;
}
let { prizeInfoList, ...rest } = data;
const startTime = moment(+rest.startTime).format("YYYY-MM-DD HH:mm:ss");
const endTime = moment(+rest.endTime).format("YYYY-MM-DD HH:mm:ss");
this.setData({
activityInfos: data
})
} catch (error) {
console.log(err, 'err')
}
}
}
});
\ No newline at end of file
{
"component": true,
"usingComponents": {
"prize-dialog": "/components/prize-dialog/prize-dialog"
}
}
\ No newline at end of file
<view>
<router-view>
<list slot="list" />
<add slot="add" />
</router-view>
</view>
Component({
mixins: [],
data: {},
props: {},
didMount() {},
didUpdate() {},
didUnmount() {},
methods: {},
});
{
"component": true,
"usingComponents": {
"router-view": "miniapp-router/router-view/router-view",
"list": "../list/list",
"add": "../add/add"
}
}
\ No newline at end of file
export const activityType = 2;
\ No newline at end of file
.create {
display: flex;
justify-content: space-between;
height: 96px;
padding: 0 24px;
background-color: #fff;
margin-bottom: 18px;
align-items: center;
}
.db-title {
font-size:20px;
font-weight: bold;
}
.list-container {
padding: 0 24px;
background-color: #fff;
}
.list-container .next-table-header .next-table-cell {
background-color: #fff;
}
.list-container .next-table-row {
height: 80px;
}
.list-container .next-table-header .next-table-cell-wrapper {
height: 60px;
line-height: 38px;
}
.time-column {
font-size: 14px;
}
.font-14 {
font-size: 14px;
}
.font-13 {
font-size: 13px;
}
.font-12 {
font-size: 12px;
}
.mb-4 {
margin-bottom: 4px;
}
.edit {
margin-right: 8px;
}
.operate-column {
display: flex;
justify-content: center;
}
.pagination {
margin-top: 20px;
align-items: center;
justify-content: flex-end;
display: flex;
}
.balloon-content {
padding: 10px 0px 10px 5px;
}
.balloon-content-title {
font-size: 12px;
padding-bottom: 10px;
}
.balloon-content view {
color: #696969;
}
.balloon-content button {
font-size: 10px;
}
.pagination-text {
font-size: 12px;
color: rgba(51, 51, 51, 1);
margin-right: 10px;
}
\ No newline at end of file
<view class="create">
<text class="db-title">活动管理</text>
<button type="primary" onTap="tapname">创建活动</button>
</view>
<view class="list-container">
<table dataSource="{{dataSource}}" hasBorder="{{false}}" loading="{{isLoadingList}}">
<table-column title="活动名称" >
<view class="font-14" slot-scope="x">
<text>{{x.record.title}}</text>
</view>
</table-column>
<table-column title="活动时间" >
<view class="font-13" slot-scope="x">
<view class="mb-4">起: {{x.record.startTime}}</view>
<view>止: {{x.record.endTime}}</view>
</view>
</table-column>
<table-column title="操作" dataIndex="id" alignHeader="left">
<button class="edit" onTap="handleClickEdit" type="primary" text="true" data-x="{{x}}" slot-scope="x">编辑活动</button>
<button class="edit" type="primary" text="true" data-x="{{x}}" slot-scope="x" onTap="exportWinnerList">导出中奖名单</button>
<button class="edit" type="primary" text="true" data-x="{{x}}" slot-scope="x" onTap="handleTapDelete">删除</button>
<button class="edit" type="primary" text="true" data-x="{{x}}" slot-scope="x" onTap="onCopyLink">复制链接</button>
</table-column>
</table>
</view>
<view class="pagination">
<text class="pagination-text">共{{pageInfo.total}}条</text>
<pagination shape="arrow-only" hideOnlyOnePage="true" defaultCurrent="1" current="{{pageInfo.pageNo}}" pageSize="{{pageInfo.pageSize}}" onChange="changePagination" pageShowCount="5" total="{{pageInfo.total}}" />
</view>
<confirm-dialog
visible="{{confirmDialog.visible}}"
content="{{confirmDialog.content}}"
onClose="onCloseConfirmDialog"
onConfirm="onConfirmDelete"
/>
import moment from 'moment';
import {
getActivityList,
delActivity,
findWinnerInfoList,
sellerSave,
uploadDataCreateFile
} from '/api';
import { activityURL } from '../../../config';
import { activityType } from '../const';
import { setClipboard } from '/utils'
const { cloud } = getApp();
const { function: fc } = cloud;
Component({
mixins: [],
data: {
confirmDialog: {
visible: false,
content: '删除活动后该活动用户参与信息将全部删除,确认删除吗?'
},
winnerListDialogVisible: false,
winnerList: [],
deleteId: '',
selectedItem: '',
dataSource: [],
pageInfo: {
pageNo: 1,
pageSize: 10,
total: 0
},
isLoadingList: false,
showWinning: false
},
props: {},
didMount() {
this.getList();
},
methods: {
/**
*获取活动列表
*
* @param {number} [currentPage=1] 页数
* @param {number} [size=10] 页码尺寸
* @returns {void}
*/
async getList(currentPage = 1, size = 10) {
this.setData({ isLoadingList: true });
try {
const { success, data, message } = await getActivityList({
pageNo: currentPage,
pageSize: size,
activityType
})
this.setData({ isLoadingList: false })
if (!success) {
my.showToast({
type: 'fail',
content: message
})
return;
}
const { list, pageNo, pageSize, total } = data;
this.setData({
dataSource: list,
pageInfo: {
pageNo,
pageSize,
total
}
})
} catch (error) {
this.setData({
isLoadingList: false
})
}
},
// 导出中奖名单
async exportWinnerList(evt) {
const { activityId } = evt.target.dataset.x.record;
my.showLoading({ content: '生成文件中...' })
try {
const { success, data, message } = await uploadDataCreateFile({
activityId,
activityType
})
my.hideLoading();
if (success) {
await setClipboard({ text: data.url.replace(/amp;/g, '') })
my.showToast({
type: 'success',
content: '中奖名单链接复制成功,请在浏览器中打开下载'
});
} else {
my.showToast({
type: 'fail',
content: message
})
}
} catch (error) {
my.hideLoading();
console.log(error, 'exportList-error');
}
},
// 复制链接
async onCopyLink(evt) {
const { activityId } = evt.target.dataset.x.record;
try {
await setClipboard({
text: `${activityURL}&activityId=${activityId}`
})
my.showToast({
type: 'success',
content: '复制活动链接成功'
})
} catch (error) {
console.log(error)
}
},
onCloseConfirmDialog() {
this.setData({
confirmDialog: Object.assign({},
this.data.confirmDialog, {
visible: false
})
})
},
// 点击列表删除
handleTapDelete(evt) {
const { activityId } = evt.target.dataset.x.record;
this.setData({
confirmDialog: Object.assign({}, this.data.confirmDialog, {
visible: true
}),
deleteId: activityId
})
},
onConfirmDelete() {
const activityId = this.data.deleteId;
this.setData({
confirmDialog: Object.assign({}, this.data.confirmDialog, {
visible: false
})
})
activityId && this.deleteActivityFromId(activityId)
},
// 删除活动
async deleteActivityFromId(activityId) {
try {
const { success, message } = await delActivity({ activityId, activityType });
if (success) {
this.getList()
} else {
my.showToast({
type: 'fail',
content: message
})
}
} catch (error) {
console.log(err)
}
},
// 编辑
handleClickEdit(evt) {
let { activityId } = evt.target.dataset.x.record;
this.$page.$router.push(`/activity/edit/${activityId}`)
},
changePagination(evt) {
const { value } = evt.detail;
this.getList(value)
},
// 创建活动
tapname() {
this.$page.$router.push('/activity/add')
}
},
});
\ No newline at end of file
{
"component": true,
"usingComponents": {
"confirm-dialog": "/components/confirm-dialog/confirm-dialog"
}
}
\ No newline at end of file
/* required by usingComponents */
/* 修改模块样式framework */
/* .aside-menu-item {
line-height: 20px !important;
display: flex !important;
align-items: center !important;
justify-content: center;
}
.aside-menu-item:nth-child(2) {
padding: 0 30px !important;
justify-content: flex-start !important;
text-align: left !important;
} */
.db-content-wrap {
max-height: calc(100vh - 50px);
overflow-x: hidden;
overflow-y: auto;
}
\ No newline at end of file
<view id="root">
<layout menu="{{menu}}" info="{{info}}" activeKey="{{activeKey}}" onMenuChange="onMenuChange">
<view class="db-content-wrap">
<router-view>
<base slot="activity" />
</router-view>
</view>
</layout>
</view>
const { cloud } = getApp();
import routerInit from 'miniapp-router';
import routerConfig, { basePath } from '../../router/index'
import { sellerSave } from '../../api';
Page({
data: {
activeKey: 'activity',
showCopyVisible: false,
scrollTop: '',
info: {
company: '兑吧科技',
miniappName: '兑吧科技',
logo: '//yun.duiba.com.cn/duiba_miniProgram/logo.png'
},
menu: [{
name: '活动管理',
key: 'activity',
title: '活动管理',
path: '/activity/list'
}]
},
onUpdateShowCopy() {
this.setData({
showCopyVisible: false
})
},
onLoad(query) {
my.authorize({
scopes: 'scope.userInfo',
success: (res) => {
sellerSave({}).then(res => {
if (!res.success) {
my.showToast({
type: 'fail',
content: res.message
})
}
});
},
error: () => {
}
});
// 页面加载
routerInit.call(this, routerConfig);
// console.info(`Page onLoad with query: ${JSON.stringify(routerConfig)}`);
},
onPageScroll: function (e) { // 获取滚动条当前位置
this.setData({
scrollTop: e.scrollTop,
})
},
onMenuChange({ key, path }) {
this.setData({
activeKey: key
});
this.$router.push(path)
}
});
\ No newline at end of file
{
"component": true,
"usingComponents": {
"router-view": "miniapp-router/router-view/router-view",
"base": "/pages/activity/base/base",
"layout": "/components/base/layout/layout"
},
"plugins": {
"myPlugin": {
"version": "0.0.12",
"provider": "3000000002026202"
}
}
}
\ No newline at end of file
export const basePath = `/activity/list`;
export default {
routes: [{
path: '/activity',
component: 'activity',
children: [
{
path: '/base',
component: 'base'
},
{
path: '/list',
component: 'list'
},
{
path: '/add',
component: 'add'
},
{
path: '/edit/:id',
component: 'add'
}
]
}
],
option: {
initPath: basePath,
}
}
\ No newline at end of file
export function debounce(fn, wait) {
var timeout = null
return function () {
if (timeout !== null) clearTimeout(timeout)
timeout = setTimeout(fn, wait)
}
}
// 解决小数相加精度问题
export function addFloat(num1, num2) {
var r1, r2, m
r1 = num1.toString().split(".")[1] ? num1.toString().split(".")[1].length : 0
r2 = num2.toString().split(".")[1] ? num2.toString().split(".")[1].length : 0
m = Math.pow(10, Math.max(r1, r2))
return (num1 * m + num2 * m) / m
}
\ No newline at end of file
export * from './helper'
export * from './my-api';
export * from './validate';
\ No newline at end of file
// promise化 淘宝api
// 具体 api参数 -> https://miniapp.open.taobao.com/doc.htm?docId=117502&docType=1
export function chooseImage() {
return promisifyMyApi('chooseImage')
}
export function getImageInfo(params) {
return promisifyMyApi('getImageInfo', params)
};
// 剪切板
export function setClipboard(params) {
return promisifyMyApi('setClipboard', params)
};
function promisifyMyApi(methodName, params = {}) {
return new Promise((resolve, reject) => {
my[methodName]({
...params,
success: res => {
resolve(res)
},
fail: err => {
reject(err)
}
})
})
}
\ No newline at end of file
const {
cloud
} = getApp();
import { requestType } from '../config';
const REQUEST_PREFIX = 'backstage';
const request = (url, method, params, ext = {}) => {
const {
isShowLoading
} = ext;
const hideMyLoading = () => {
if (isShowLoading) {
my.hideLoading();
}
};
if (isShowLoading) {
my.showLoading();
}
const requestAms = () => {
return my.request({
url: `https://ams.dui88.com/server/index.php?g=Web&c=Mock&o=simple&projectID=218&uri=${REQUEST_PREFIX}.${url}`,
method,
data: params,
dataType: 'json'
}).then(res => {
hideMyLoading();
return res.data;
}).catch(() => {
hideMyLoading();
});
}
const requestCloud = () => {
return cloud.function.invoke(REQUEST_PREFIX, params, url).then(res => {
hideMyLoading();
return res;
}).catch(() => {
hideMyLoading();
});
}
return requestType === 'ams' ? requestAms() : requestCloud();
};
export default request;
\ No newline at end of file
// 正整数
export const validatePositiveInteger = number => /^[1-9]\d*$/.test(number);
// 整数
export const validateInteger = number => /^-?\d*$/.test(number);
// 判断是否为数字
export const isNumber = val => {
var regPos = /^\d+(\.\d+)?$/; //非负浮点数
var regNeg = /^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))$/; //负浮点数
if(regPos.test(val) && regNeg.test(val)){
return true;
}else{
return false;
}
}
/**
*
* 验证一定范围内数,支持整数或小数
* @param {*} number
* @param {*} [ min, max ] 范围 [0,99] [-Infinity, 0] [999, Infinity]
* @param {number} [fixed=0] 小数位数 0时为整数
* @returns { boolean }
*/
export const validateRangeNumber = (number, [ min, max ] = [], fixed = 0) => {
const pattern = new RegExp(`^-?[0-9]+(\\.[0-9]{0,${fixed}})?$`);
if(min > max) {
console.error('数字范围的最小值不能大于最大值');
return;
}
if(!pattern.test(number) || number === '') return false;
if(number < min || number > max) return false;
return true;
}
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@tbmp/mp-cloud-sdk@1.1.4":
version "1.1.4"
resolved "https://registry.npm.taobao.org/@tbmp/mp-cloud-sdk/download/@tbmp/mp-cloud-sdk-1.1.4.tgz#0ccb91cd77d17c52cd6f0cb1584368cfdfd1a9dd"
integrity sha1-DMuRzXfRfFLNbwyxWENoz9/Rqd0=
miniapp-router@^0.1.6:
version "0.1.7"
resolved "https://registry.npm.taobao.org/miniapp-router/download/miniapp-router-0.1.7.tgz#e92989574cbb0cf11793d62508d0413fe165b948"
integrity sha1-6SmJV0y7DPEXk9YlCNBBP+FluUg=
moment@^2.24.0:
version "2.24.0"
resolved "https://registry.npm.taobao.org/moment/download/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
integrity sha1-DQVdU/UFKqZTyfbraLtdEr9cK1s=
{
"templates": [
{
"name": "c_server",
"branch": "c_server",
"tags": [
{
"desc": "通用模板",
"version": "v1.0"
}
]
}
]
}
\ No newline at end of file
.tea
.DS_Store
.vscode
debug.log
node_modules
\ No newline at end of file
# 数据模拟
```bash
cnpm install
```
```bash
cd mockServer
```
```bash
cnpm install
```
```bash
➜ mockServer npm run start
```
## 库表规范
http://cf.dui88.com/pages/viewpage.action?pageId=63937755
## sdk 接口文档说明
http://cf.dui88.com/pages/viewpage.action?pageId=66194323
{
"runtime": "nodejs8",
"version": "1.0"
}
\ No newline at end of file
const { DEFAULT_CODE_TYPES } = require('./sdk');
let BusinessError = {
// 更新重试失败(用于并发更新)
ERROR_UPDATE_RETRY: {
code: `210001`,
defaultMsg: `系统错误`
},
// 活动不存在
ERROR_NO_ACTIVITY: {
code: `220001`,
defaultMsg: `活动不存在`
},
// 活动未开始
ERROR_ACTIVITY_NOSTART: {
code: `220002`,
defaultMsg: `活动未开始`
},
// 活动已结束
ERROR_ACTIVITY_OVER: {
code: `220003`,
defaultMsg: `活动已结束`
},
// 用户不存在
ERROR_NO_USER: {
code: `310002`,
defaultMsg: `用户不存在`
},
// 需要成为会员才能助力哦
ERROR_NEEDMEMBER_DOHELP: {
code: `310004`,
defaultMsg: '需要成为会员才能助力哦'
},
// 任务奖励已领取
ERROR_TASK_ALREADYRECEIVE: {
code: `430002`,
defaultMsg: `任务奖励已领取`
},
// 任务已完成
ERROR_TASK_COMPLETE: {
code: `430003`,
defaultMsg: `任务已完成`
},
// 暂无次数可领取
ERROR_TASK_NORECEIVE: {
code: `430003`,
defaultMsg: `暂无次数可领取`
},
ERROR_FORBIDDEN_OPE: {
code: `530001`,
defaultMsg: `非法操作`
},
ERROR_RECEIVE_PRIZE: {
code: `630002`,
defaultMsg: `奖励已领取`
},
ERROR_SEND_PRIZE: {
code: `730001`,
defaultMsg: `发奖失败`
},
ERROR_NO_PRIZE: {
code: `730002`,
defaultMsg: `奖品不存在`
},
ERROR_NO_STOCK: {
code: `730003`,
defaultMsg: `库存不足`
},
ERROR_PRIZE_EXPIRED: {
code: `740001`,
defaultMsg: `奖品已超过领取时间`
}
}
const CODE_TYPES = Object.assign({}, DEFAULT_CODE_TYPES, BusinessError);
//tb 接口返回部分错误
const TBERROR = {
'USER_PERMISSION_EXCEED_MAX_RIGHT_COUNT_IN_DAY': '今日领取达到上限,明日再来领取哦',
'COUPON_INVALID_OR_DELETED': '权益无效或者被删除,请联系商家',
'APPLY_OWNSELF_COUPON': '不能领取自己家店铺权益',
'APPLY_SINGLE_COUPON_COUNT_EXCEED_LIMIT': '请至卡券包删除无用权益再领取',
'NO_RIGHT_QUANTITY': '权益库存不足,请联系商家',
'ERRORA_3_567': '领取失败,为风险用户',
'Invalid session': '订购应用已过期,请联系商家处理',
'NO_RIGHT_QUANTITY': '权益库存不足,请联系商家'
}
// 领取奖品状态
const DRAW_STATUS = {
// 待领取
WAITAWARD: 1,
// 处理中
PROCESSING: 2,
// 领取成功
SUCCESS: 3,
// 领取失败
FAIL: 4,
// 已过期
EXPIRED: 5,
// 重新领取
RETRY: 6
}
// 奖品类型
const PRIZE_TYPE = {
ENAME: 1,
CREDITS: 2,
OBJECT: 3,
THANKS: 5
};
// C端的APP NAME
const C_APP_NAME = 'promotioncenter-${需要补充}';
// 订单状态
const TAOBAO_SUCCESS_ORDER_STATUS = [
"WAIT_SELLER_SEND_GOODS",
"SELLER_CONSIGNED_PART",
"WAIT_BUYER_CONFIRM_GOODS",
"TRADE_BUYER_SIGNED",
"TRADE_FINISHED",
"PAID_FORBID_CONSIGN",
];
// 活动状态
const ACTIVITY_STATUS = {
NORMAL: 1,
DELETE: 2
};
let rand = (function(){
let seed = (new Date()).getTime()
function r(){
seed = (seed*9301+49297)%233280
return seed/(233280.0)
}
return function(number){
return Math.ceil(r()*number)
}
})()
module.exports = {
CODE_TYPES,
C_APP_NAME,
TBERROR,
DRAW_STATUS,
PRIZE_TYPE,
TAOBAO_SUCCESS_ORDER_STATUS,
ACTIVITY_STATUS,
rand
}
\ No newline at end of file
const { pipeActivityInfo } = require('./base.controller');
const UserService = require('../service/user.service');
const AwardsService = require('../service/awards.service');
const { CODE_TYPES } = require('../constants');
const { ResultsModel } = require('../sdk');
let resultModel = new ResultsModel();
/**
* 我的奖品列表
* @param {*} context
*/
const getMyPrizeList = async (context) => {
// 判断活动
let pipeRes = await pipeActivityInfo(context);
// 活动结束可以查看我的奖品
if (pipeRes.code && pipeRes.code !== CODE_TYPES.ERROR_ACTIVITY_OVER.code) {
return pipeRes;
}
let awardSer = new AwardsService(context);
let result = await awardSer.getMyPrizeList({openId: context.openId, activityId: context.data.activityId});
return resultModel.success(result);
}
/**
* 领取实物
* @param {*} context
*/
const receiveObjectPrize = async(context) => {
// 判断活动
let pipeRes = await pipeActivityInfo(context);
// 活动结束可以领取实物
if (pipeRes.code && pipeRes.code !== CODE_TYPES.ERROR_ACTIVITY_OVER.code) {
return pipeRes;
}
let { provice, city, area, addressdetail, _id, name, phone, activityId } = context.data;
if (!_id) {
return resultModel.error(CODE_TYPES.PARAMS_ERROR, `缺少_id`)
}
if (!name) {
return resultModel.error(CODE_TYPES.PARAMS_ERROR, `缺少name`)
}
if (!phone) {
return resultModel.error(CODE_TYPES.PARAMS_ERROR, `缺少phone`)
}
if (!provice) {
return resultModel.error(CODE_TYPES.PARAMS_ERROR, `缺少provice`)
}
if (!city) {
return resultModel.error(CODE_TYPES.PARAMS_ERROR, `缺少city`)
}
if (!area) {
return resultModel.error(CODE_TYPES.PARAMS_ERROR, `缺少area`)
}
if (!addressdetail) {
return resultModel.error(CODE_TYPES.PARAMS_ERROR, `缺少addressdetail`)
}
let awardSer = new AwardsService(context);
let result = await awardSer.recieveObjectPrize(_id, {activityId, provice, city, area, addressdetail, name, phone});
if (result.code) {
return resultModel.error(result);
}
return resultModel.success(result);
}
/**
* 权益重新领取
* @param {*} context
*/
const receiveEnamePrize = async(context) => {
let pipeRes = await pipeActivityInfo(context);
// 活动结束可以领取权益
if (pipeRes.code && pipeRes.code !== CODE_TYPES.ERROR_ACTIVITY_OVER.code) {
return pipeRes;
}
let {_id} = context.data;
if (!_id) {
return resultModel.error(CODE_TYPES.PARAMS_ERROR, `缺少_id`)
}
let awardsSer = new AwardsService(context);
let result = await awardsSer.recieveEnamePrize(_id, pipeRes._id);
if (result.code) {
return resultModel.error(result);
}
if (result.remark) {
return resultModel.error(result, result.remark);
}
return resultModel.success(result);
}
module.exports = {
getMyPrizeList,
receiveObjectPrize,
receiveEnamePrize
}
\ No newline at end of file
const BaseService = require('../service/base.service');
const { CODE_TYPES, ACTIVITY_STATUS } = require('../constants');
const { ResultsModel } = require('../sdk');
let resultModel = new ResultsModel();
// 活动判断
const pipeActivityInfo = async (context) => {
let activitySer = new BaseService(context);
let { activityId } = context.data;
// 参数校验
if (!activityId) {
return resultModel.error(CODE_TYPES.PARAMS_ERROR, `activityId必填`);
}
// 活动基本情况
let activityInfo = await activitySer.getBaseInfo(activityId);
// 活动不存在
if (!activityInfo && activityInfo.deleteStatus === ACTIVITY_STATUS.DELETE) {
return resultModel.error(CODE_TYPES.ERROR_NO_ACTIVITY);
}
let { startTime, endTime } = activityInfo;
let currentTime = Date.now();
if (currentTime < startTime) {
return resultModel.error(CODE_TYPES.ERROR_ACTIVITY_NOSTART, `活动未开始`);
}
if (currentTime > endTime) {
return resultModel.error(CODE_TYPES.ERROR_ACTIVITY_OVER, `活动已结束`);
}
return activityInfo;
}
// 活动基本信息
const getActivityBaseInfoById = async(context) => {
let activitySer = new BaseService(context);
let { activityId } = context.data;
// 参数校验
if (!activityId) {
return resultModel.error(CODE_TYPES.PARAMS_ERROR, `activityId必填`);
}
// 活动基本情况
let activityInfo = await activitySer.getBaseInfo(activityId);
return resultModel.success(activityInfo);
}
module.exports = {
pipeActivityInfo,
getActivityBaseInfoById
}
\ No newline at end of file
const ACCESS_DB_NAME = 'c_user_access';
const USER_DB_NAME = 'c_user';
const AWARDS_DB_NAME = 'c_awards_info';
const ERROR_LOG_DB_NAME = 'error_log';
const SELLER_INFO_DB_NAME = 'a_seller_info';
const PRIZE_CONFIG_DB_NAME = 'b_prize_config';
const ACTIVITY_CONFIG_DB_NAME = 'b_activity_config';
module.exports = {
ACCESS_DB_NAME,
USER_DB_NAME,
AWARDS_DB_NAME,
ERROR_LOG_DB_NAME,
SELLER_INFO_DB_NAME,
PRIZE_CONFIG_DB_NAME,
ACTIVITY_CONFIG_DB_NAME
}
const {
getActivityBaseInfoById
} = require('./controller/base.controller');
const {
getMyPrizeList,
receiveObjectPrize,
receiveEnamePrize
} = require('./controller/awards.controller');
module.exports = {
getActivityBaseInfoById,
getMyPrizeList,
receiveObjectPrize,
receiveEnamePrize
}
\ No newline at end of file
{
"name": "mockserver",
"version": "1.0.0",
"description": "",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "nodemon --watch sdk --watch ../ --watch server.js server.js mock"
},
"author": "",
"license": "ISC",
"dependencies": {
"axios": "^0.19.2",
"body-parser": "^1.19.0",
"cookie-parser": "^1.4.5",
"express": "^4.17.1",
"http-proxy-middleware": "^1.0.5",
"qs": "^6.9.4",
"request": "^2.88.2",
"request-promise": "^4.2.5",
"string-object-to-object": "^1.0.3"
},
"port": 5555,
"serverProxy": {
"target": "http://0.0.0.0:8888/"
},
"devDependencies": {
"nodemon": "^2.0.4"
}
}
const {CURD_REQUEST} = require('./request');
class BaseDao {
constructor(context, dbName) {
this.db = context.db;
this.TABLE = dbName;
}
/**
* 查询一条数据
* @param query
* @param options
*/
async findOne(query, options) {
let result = await CURD_REQUEST({db: this.db, TABLE: this.TABLE, type: `find`, query: query || {}, options: options || {}});
return result[0];
}
/**
* 查询多条数据
* @param query
* @param options
*/
async find(query, options) {
console.log(`db`, this.db)
let result = await CURD_REQUEST({db: this.db, TABLE: this.TABLE, type: `find`, query: query || {}, options: options || {}});
return result;
}
/**
* 插入单条数据
* @param document
*/
async insertOne(document) {
console.log(`insertOne`, document)
let result = await CURD_REQUEST({db: this.db, TABLE: this.TABLE, type: `insert`, document: document || {}});
return result;
}
/**
* 插入多条数据
* @param documents 插入对象
*/
async insertMany(documents) {
let result = await CURD_REQUEST({db: this.db, TABLE: this.TABLE, type: `insert`, document: documents || [{}]});
return result;
}
/**
* 更新数据
* @param query
* @param options
*/
async update(query, options) {
let result = await CURD_REQUEST({db: this.db, TABLE: this.TABLE, type: `update`, query: query || {}, options: options || {}});
console.log(`update结果`, result);
return result;
}
/**
* 删除多条数据
* @param query
* @param options
*/
async delete(query, options) {
let result = await CURD_REQUEST({db: this.db, TABLE: this.TABLE, type: `delete`, query: query || {}, options: options || {}});
return result;
}
/**
* 获取条目数
* @param query
* @param options
*/
async count(query, options) {
let result = await CURD_REQUEST({db: this.db, TABLE: this.TABLE, type: `count`, query: query || {}, options: options || {}});
return result;
}
}
module.exports = BaseDao;
\ No newline at end of file
const {TBAPI_REQUEST} = require('./request');
let TBAPIS = {}
const APIS = [
`getItemListByItemIds`,
`getPrizeByEname`,
`getShopVipUrl`,
`queryVipinfo`,
`benefitSend`,
`queryCredits`,
`changeCredits`,
`getShopInfo`,
`getBuyerOrderList`,
];
APIS.forEach(v => {
TBAPIS[v] = async (query) => {
let result = await TBAPI_REQUEST(process.db, v, query);
console.log(`TBAPIS.${v}----参数`, process.db, v, query);
console.log(`result`, result);
return result.tbvalue;
};
});
module.exports = TBAPIS;
\ No newline at end of file
const dateFormatter = (thisDate, fmt = 'yyyy-MM-dd hh:mm:ss') => {
thisDate = new Date(thisDate)
console.log(`thisDate`, thisDate)
const o = {
'M+': thisDate.getMonth() + 1,
'd+': thisDate.getDate(),
'h+': thisDate.getHours(),
'm+': thisDate.getMinutes(),
's+': thisDate.getSeconds(),
'q+': Math.floor((thisDate.getMonth() + 3) / 3),
S: thisDate.getMilliseconds(),
};
if (/(y+)/.test(fmt))
fmt = fmt.replace(
RegExp.$1,
(thisDate.getFullYear() + '').substr(4 - RegExp.$1.length)
);
for (const k in o) {
if (new RegExp('(' + k + ')').test(fmt)) {
fmt = fmt.replace(
RegExp.$1,
RegExp.$1.length === 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length)
);
}
}
return fmt;
};
module.exports = dateFormatter
\ No newline at end of file
const BaseDao = require('./BaseDao');
const TBAPIS = require('./TBAPIS');
const transformBeijingDate = require('./transformBeijingDate');
const dateFormatter = require('./dateFormatter')
module.exports = {
BaseDao,
TBAPIS,
transformBeijingDate,
dateFormatter
}
\ No newline at end of file
var axios = require('axios')
let port = require('../package.json').port|| 5555;
const request = async (path, params) => {
let res = await axios({
method: 'post',
url: path,
data: params,
baseURL: `http://127.0.0.1:${port}`
});
if (!res.data.data) {
console.log(`调用${path}${JSON.stringify(params)}\n返回结果`, res.data)
return false
}
return res.data.data
}
const CURD_REQUEST= async ({db, TABLE, type, query, options, document}) => {
// 参数
let data={
dbName: db,
TABLE,
query,
options,
document
};
console.log(`请求参数`, data)
let path=`/proxy/dataMock/${type}`;
return await request(path, data);
};
const TBAPI_REQUEST=async (db,funName,query) => {
// 参数
let data={
dbName: db,
funName
};
let path=`/proxy/dataMock/tbapi`;
let params={
dbName: data.dbName,
funName: data.funName,
query
}
return await request(path, params);
};
module.exports={
CURD_REQUEST,
TBAPI_REQUEST
};
\ No newline at end of file
module.exports = (day) => {
if (day) {
return new Date(day).getTime();
}else {
return new Date().getTime();
}
}
\ No newline at end of file
var express = require('express');
var bodyParser = require("body-parser");
var cookieParser = require("cookie-parser");
var {createProxyMiddleware} = require('http-proxy-middleware');
const routers = require('../index');
const {port, serverProxy } = require('./package.json');
var app = express();
app.use(cookieParser());
app.use(
bodyParser.urlencoded({
extended: true,
})
);
app.use(bodyParser.json());
app.get('/', (req, res) => {
res.json({message: `ok`});
});
// 执行本地云函数方法
app.post('/handler', async (req, res) => {
console.log(req.body);
let {data, openId, db } = req.body;
if(!db) {
res.json({message: `参数错误,缺少数据库db`});
}
if(!openId) {
res.json({message: `参数错误,缺少openId`});
}
try{
let query = JSON.parse(JSON.stringify(data))
}catch(e) {
res.json({message: `查询参数格式错误`});
}
let {handler, data: queryData} = data
if (!handler) {
res.json({message: `格式错误, handler参数不能为空`});
}
if (!queryData) {
res.json({message: `格式错误, data参数不能为空`});
}
if(!process.db) {
process.db = db;
}
req.context = {
openId,
db,
data: queryData
};
if(routers[handler]) {
let result = await routers[handler](req.context);
res.json(result);
}else {
res.json({message: `函数${handler}不存在`});
}
});
// 获取本地export的数据库表及云函数
app.get('/getDBHandlers', async(req, res) => {
let dbs = require('../db');
let handlers = routers;
// dbsInfo
let dbArray = []
let handlerArray = []
Object.keys(dbs).map(v => {
dbArray.push(dbs[v]);
});
Object.keys(handlers).map(v => {
handlerArray.push(v)
});
console.log(dbArray, handlerArray)
res.json({
dbs: dbArray,
handlers: handlerArray,
success: true
});
})
const ProxyOption = {
target: serverProxy.target,
changeOrigin: true,
pathRewrite: (path, req) => {
console.log(`path`, path)
return path.replace('/proxy', '');
},
onProxyReq: (proxyReq, req, res) => {
console.log(`req.body`, req.body)
if (req.body) {
let bodyData = JSON.stringify(req.body);
console.log(`bodyData`, bodyData)
// incase if content-type is application/x-www-form-urlencoded -> we need to change to application/json
proxyReq.setHeader('Content-Type', 'application/json');
proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData));
// stream the content
proxyReq.write(bodyData);
}
}
}
app.use('/proxy', createProxyMiddleware(ProxyOption));
app.listen(port || 5555);
{
"name": "<%=projectName%>",
"version": "1.0.0",
"description": "<%=description%>",
"main": "index.js",
"author": "<%=author%>",
"license": "ISC",
"sdkVersion": "*",
"dependencies": {
"node-xlsx": "^0.15.0",
"taobao-mini-sdk": "0.1.2",
"dayjs": "^1.8.28"
},
"config": {
"notNeedLogin": [
]
}
}
// 特别注意以下的引用方式,需要.default
const { BaseDao, TBAPI, Utils } = require('taobao-mini-sdk/lib/index').default;
const { DEFAULT_CODE_TYPES, dateFormatter,transformBeijingDate,getStartTimestamp,getEndTimestamp, ResultsModel} = Utils.default;
const TBAPIS = TBAPI.default;
let default_sdk = {
DEFAULT_CODE_TYPES,
ResultsModel,
dateFormatter,
getStartTimestamp,
getEndTimestamp
};
let Config
let mock = process.argv[2];
console.log(`mock`, mock);
// 云函数环境
if (mock !== 'mock') {
Config = Object.assign({}, default_sdk, {
BaseDao,
TBAPIS,
transformBeijingDate
});
}
// 本地环境
else {
let MockConfig = require('./mockServer/sdk');
Config = Object.assign({}, default_sdk, MockConfig);
console.log(Config);
}
module.exports = Config;
/**
* 访问明细
*/
const {
BaseDao
} = require('../sdk');
const { getToday } = require('../utils');
const { ACCESS_DB_NAME } = require('../db');
class UserAccessService {
constructor(context) {
this.context = context;
this.accessdao = new BaseDao(context, ACCESS_DB_NAME);
}
/**
* 增加访问记录
* @param {是否关注店铺} newFollow
*/
async addAccess(newFollow) {
let {
openId,
data
} = this.context;
let {
activityId,
userNick,
avatar,
inviteId
} = data;
return await this.accessdao.insertOne({
openId,
activityId,
userNick,
avatar,
isReceiveShare: !!inviteId,
inviteId,
newFollow,
accessTime: Date.now(),
createTime: Date.now(),
updateTime: Date.now(),
createDay: getToday()
});
}
}
module.exports = UserAccessService;
/**
* 奖励
*/
const { BaseDao } = require('../sdk');
const UserService = require('./user.service');
const { CODE_TYPES, DRAW_STATUS, PRIZE_TYPE, ACTIVITY_STATUS } = require('../constants');
const {AWARDS_DB_NAME, PRIZE_CONFIG_DB_NAME } = require('../db');
const { sendTBAward, getSellerSession } = require('../utils');
class AwardsService extends UserService{
constructor(context) {
super(context);
this.awardsdao = new BaseDao(context, AWARDS_DB_NAME);
this.activityprizedao = new BaseDao(context, PRIZE_CONFIG_DB_NAME);
}
// 根据奖品Id扣库存
async reduceStock(_id) {
let result = await this.activityprizedao.findOne({_id, deleteStatus: ACTIVITY_STATUS.NORMAL});
// 奖品不存在
if (!result) {
return CODE_TYPES.ERROR_NO_PRIZE;
}
let {switchStock, stock, useStock} = result;
// 若不限制库存
if (switchStock === 2) {
return true;
}
// 若库存不足
if (useStock >= stock) {
return CODE_TYPES.ERROR_NO_STOCK;
}
try{
await this.activityprizedao.update(
{
_id ,
$where: "this.useStock < this.stock"
},
{
$inc: {
useStock: +1
}
}
);
return true;
}catch(e) {
console.log(`扣库存(实物)失败:`, e);
return CODE_TYPES.SYSTEM_ERROR;
}
}
/**
* 插入奖品记录
* @param {*} award
*/
async addAward(award) {
try {
return await this.awardsdao.insertOne({
...award,
createTime: Date.now(),
updateTime: Date.now()
});
}catch(e) {
console.log(`插入中奖记录错误:`, e);
return CODE_TYPES.SYSTEM_ERROR;
}
}
/**
* 我的奖品
* @param {*} param0
*/
async getMyPrizeList({openId, activityId}) {
// 获取奖品领取过期时间
let { awardReceiveExpiredTime } = await this.getBaseInfo(activityId);
let myprizeList = await this.awardsdao.find({openId, activityId}, {
sort: {
createTime: -1
}
});
// 若有过期时间,且已过期
if (awardReceiveExpiredTime) {
let currentTime = Date.now()
myprizeList.map(v => {
if(currentTime > awardReceiveExpiredTime) {
if ([DRAW_STATUS.WAITAWARD, DRAW_STATUS.RETRY].includes(v.drawStatus)) {
v.drawStatus = DRAW_STATUS.EXPIRED;
v.remark = `奖品已过期`;
}
}
v.expiredTime = awardReceiveExpiredTime;
})
}
return myprizeList;
}
// 领取实物
async recieveObjectPrize(_id, {activityId, provice, city, area, addressdetail, name, phone}) {
// 若有过期时间,且已过期
let { awardReceiveExpiredTime } = await this.getBaseInfo(activityId);
if (awardReceiveExpiredTime && Date.now() > awardReceiveExpiredTime) {
return CODE_TYPES.ERROR_PRIZE_EXPIRED
}
let award = await this.awardsdao.findOne({_id});
console.log(`receiveObject-award`, award);
// 奖品不存在
if (!award) {
return CODE_TYPES.ERROR_NO_PRIZE;
}
// 已领取
if (award.drawStatus === DRAW_STATUS.SUCCESS) {
return CODE_TYPES.ERROR_RECEIVE_PRIZE;
}
// 不是实物,非法操作
if (award.type !== PRIZE_TYPE.OBJECT) {
return CODE_TYPES.ERROR_FORBIDDEN_OPE;
}
try {
let result = await this.awardsdao.update({_id}, {
$set: {
receiveName: name,
phone,
drawStatus: DRAW_STATUS.SUCCESS,
provice,
city,
area,
addressdetail,
receiveTime: Date.now(),
updateTime: Date.now()
}
});
return true;
}catch(e) {
console.log(`领取实物错误:`, e);
return CODE_TYPES.SYSTEM_ERROR;
}
}
// 发放淘宝权益(奖品数据已插入到awards_info表,且状态drawStatus 为1或者6)
async recieveEnamePrize(_id, activityId) {
// 若有过期时间,且已过期
let { awardReceiveExpiredTime } = await this.getBaseInfo(activityId);
if (awardReceiveExpiredTime && Date.now() > awardReceiveExpiredTime) {
return CODE_TYPES.ERROR_PRIZE_EXPIRED;
}
let result = {};
let award = await this.awardsdao.findOne({_id});
// 奖品不存在
if (!award) {
return CODE_TYPES.ERROR_NO_PRIZE;
}
// 已领取
if (award.drawStatus === DRAW_STATUS.SUCCESS) {
return CODE_TYPES.ERROR_RECEIVE_PRIZE;
}
console.log(`award.drawStatus`, award.drawStatus);
// 状态不是1,6
if (![DRAW_STATUS.WAITAWARD, DRAW_STATUS.RETRY].includes(award.drawStatus)) {
return CODE_TYPES.ERROR_FORBIDDEN_OPE;
}
let { session } = await getSellerSession(this.context);
// 发放淘宝权益
result = await sendTBAward(this.context, session, award);
// 更新
await this.awardsdao.update({_id}, {
$set: {
drawStatus: result.drawStatus,
remark: result.remark,
updateTime: Date.now()
}
});
return result;
}
/**
* 获取活动配置项奖品
* @param {*} _id
*/
async getActivityPrizeById(_id) {
return await this.activityprizedao.findOne({_id, deleteStatus: ACTIVITY_STATUS.NORMAL}, {
sort: {
index: 1
}
});
}
}
module.exports = AwardsService;
\ No newline at end of file
/**
* 基本信息
*/
const { BaseDao, transformBeijingDate, dateFormatter } = require('../sdk');
const { ACTIVITY_CONFIG_DB_NAME } = require('../db');
class BaseService {
constructor(context) {
this.context = context;
this.activitydao = new BaseDao(context, ACTIVITY_CONFIG_DB_NAME);
}
/**
* @desc 活动基本信息
* @param {活动id} activityId
* @returns 返回活动详情,若不存在活动,返回为null
*/
async getBaseInfo(activityId) {
if (!activityId) {
activityId = this.context.data.activityId
}
return await this.activitydao.findOne({_id: activityId});
}
}
module.exports = BaseService;
\ No newline at end of file
/**
* 用户相关方法
*/
const { BaseDao, TBAPIS } = require('../sdk');
const BaseService = require('./base.service');
const { USER_DB_NAME } = require('../db');
class UserService extends BaseService{
constructor(context) {
super(context);
this.userdao = new BaseDao(context, USER_DB_NAME);
}
/**
* @desc 获取当前打开活动的用户详情
* @returns 若用户不存在,返回null; 用户存在,返回用户信息(object对象)
*/
async getUserInfo() {
let { openId, data } = this.context;
let { activityId } = data;
let record = await this.userdao.findOne({openId, activityId });
return record;
}
/**
* @desc 根据inviteId获取用户详情
* @desc 常用于助力分享码为用户openId, 被邀请人打开活动助力时需要获取邀请人的用户详情
* @param {邀请人的openId} inviteId
* @returns 若用户不存在,返回null; 用户存在,返回用户信息(object对象)
*/
async getUserInfoByOpenId(inviteId) {
let { activityId } = this.context.data;
let record = await this.userdao.findOne({openId: inviteId, activityId });
return record;
}
/**
* @desc 获取是否是会员
* @param {调用淘宝接口的session} session
* @returns {isvip: boolean(是否是会员), url: string(入会链接) }
*/
async getShopVip(session) {
// 会员mock,当传递的参数有vipmock: true,则表示已经是会员
let { vipmock = false} = this.context.data;
if (vipmock) {
return {
isvip: true
}
}
let result = {}
let shopUrl = {};
try{
result = await TBAPIS.queryVipinfo(this.context, session);
shopUrl = await TBAPIS.getShopVipUrl(this.context, session, {
"source": "isvapp", "entrance": "duiba"
});
}catch(e) {
console.log(e);
}
console.log(`result, shopUrl`, result, shopUrl);
return {
isvip: !!(result.result && result.result.member_info),
url: shopUrl.result && shopUrl.result.result
}
}
/**
* @desc 更新用户表
* @param {用户的主键id} _id
* @param {更新的对象} document
* @returns 若更新成功,返回为1; 若更新失败,返回为 0 或系统直接报错
*/
async updateUser(_id, document) {
console.log(document);
return await this.userdao.update({_id}, {$set: {
...document,
updateTime: Date.now()
}});
}
/**
* @desc 根据用户主键id查找用户详情
* @param {用户的主键id} _id
* @returns 若用户不存在,返回null; 用户存在,返回用户信息(object对象)
*/
async getUserInfoById(_id) {
return await this.userdao.findOne({_id});
}
}
module.exports = UserService;
\ No newline at end of file
// 获取商家session
const getSellerSession = require('./package/getSession');
// 获取当天时间进行format
const getToday = require('./package/getToday');
// 发放淘宝奖品(权益/积分)
const sendTBAward = require('./package/sendTBAward');
// 通用并发接口
const lockUpdate = require('./package/lockUpdate');
// 用户订单
const getUserOrderlist = require('./package/getUserOrderlist');
module.exports = {
getSellerSession,
getToday,
sendTBAward,
lockUpdate,
getUserOrderlist
}
/**
* 获取商家session
*/
const { SELLER_INFO_DB_NAME, ACTIVITY_CONFIG_DB_NAME } = require('../../db');
const { BaseDao } = require('../../sdk')
const getSellerSession = async (context, activityId) => {
if (!activityId) {
activityId = context.data.activityId;
}
let activitydao = new BaseDao(context, ACTIVITY_CONFIG_DB_NAME)
let sellerdao = new BaseDao(context, SELLER_INFO_DB_NAME)
let activityConfigResult = await activitydao.find({ _id: activityId });
if (!activityConfigResult || !activityConfigResult[0]) return {};
let result = await sellerdao.find({
openId: activityConfigResult[0].openId
});
console.log(`getSellerSession`, result);
if (!result || !result[0]) return {};
return {
session: result[0].accessToken
};
}
module.exports = getSellerSession;
\ No newline at end of file
const { transformBeijingDate, dateFormatter } = require('../../sdk');
// 获取今天的时间
const getToday = ()=> {
return dateFormatter(transformBeijingDate(), 'yyyy/MM/dd');
}
module.exports = getToday;
\ No newline at end of file
/**
* 用户购买记录查询
*/
const dayjs = require('dayjs');
const {dateFormatter, transformBeijingDate, TBAPIS} = require('../../sdk');
const getSellerSession = require('./getSession');
const { TAOBAO_SUCCESS_ORDER_STATUS } = require('../../constants');
const getUserOrderlist = async (context, queryStartTime, queryEndTime) => {
try{
let results = [];
let currentTime = Date.now();
// 开始时间以Math.max(当前时间倒推3个月,queryStartTime)
let lastUpdateTime = dayjs().add(-3, 'month').valueOf();
console.log(`lastUpdateTime`, lastUpdateTime);
// 若倒推的3个月时间小于传入的查询开始时间, 则以查询开始时间作为最后更新时间
if(lastUpdateTime < queryStartTime) {
lastUpdateTime = queryStartTime;
}
let session = await getSellerSession(context);
// 获取用户在lastUpdateTime ~ currentTime区间的购买情况
let params = {
startTime: dateFormatter(transformBeijingDate(lastUpdateTime)),
endTime: dateFormatter(transformBeijingDate(queryEndTime || currentTime)),
openId: context.openId,
session
}
let orderResult = await TBAPIS.getBuyerOrderList(context, params);
if (orderResult.total_results > 0) {
const { trade } = orderResult.trades;
trade.forEach((i) => {
if (TAOBAO_SUCCESS_ORDER_STATUS.includes(i.status)) {
i.orders.order.forEach((s) => {
results.push({
payTime: i.pay_time,
itemId: s.num_iid,
time: i.created,
price: s.price,
orderId: s.oid,
img: s.pic_path,
title: s.title,
});
});
}
});
}
console.log(`已支付的订单列表`, results);
return results;
}catch(e) {
console.log(`错误`, e);
return [];
}
}
module.exports = getUserOrderlist;
\ No newline at end of file
const {CODE_TYPES} = require('../../constants');
/**
* 并发更新
* @param {接收数组或对象} daoqueryArr : [{dao: userdao, query:{id:_id}}, {dao: activityprizedao, query: {id:_id}}]
* @param {*} fn
* @param {*} times
*/
const lockUpdate = async(daoqueryArr, fn, times=5) => {
if (typeof daoqueryArr !=='object') {
console.log(`lockUpdate 参数错误`);
return false;
}
if (daoqueryArr instanceof Object) {
daoqueryArr = [daoqueryArr];
}
let time = 0;
console.log(`进入update并发处理次数`, times);
let success = false;
try{
while (time++ < times && !success) {
// 依次锁住表
let updateRes = [];
let error = false
for(let i = 0; i < daoqueryArr.length; i++) {
let daoquery = daoqueryArr[i];
if (!daoquery.dao || !daoquery.query) {
success = true;
error = true
break
}else {
let canupdate = await daoquery.dao.update(daoquery.query, {$set: {lockStatus: 2}});
updateRes.push(canupdate);
}
}
if (error) {
return `调用lockUpdate方法参数不符合规范`
}
// 若都锁定成功了,执行内部调用
if (!daoqueryArr.includes(0)) {
const ret = await fn();
daoqueryArr.forEach(async daoquery => {
let ures = await daoquery.dao.update(daoquery.query, {$set: {lockStatus: 1}});
console.log(`更新lockStatus为1`, ures);
});
success = true;
return ret;
} else {
console.log(`进入waitFor了,需等待30ms`)
await waitFor(30);
success = false;
}
}
}catch(e) {
console.log(e);
daoqueryArr.forEach(async daoquery => {
let ures = await daoquery.dao.update(daoquery.query, {$set: {lockStatus: 1}});
console.log(`更新lockStatus为0`, ures);
});
success = false;
}
// 稍后重试
if (!success) {
daoqueryArr.forEach(async daoquery => {
let ures = await daoquery.dao.update(daoquery.query, {$set: {lockStatus: 1}});
console.log(`操作${times}次后更新lockStatus为1`, ures);
});
return CODE_TYPES.ERROR_UPDATE_RETRY;
}
function waitFor(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms)
});
}
}
module.exports = lockUpdate;
/**
* 调用示例
*/
// let res = await lockUpdate({dao: this.activityprizedao, query: {_id}}, async ()=> {
// TODO...
// });
// console.log(res);
// if (!res) {
// return CODE_TYPES.ERROR_UPDATE_RETRY;
// }
// return true;
const { PRIZE_TYPE, DRAW_STATUS, C_APP_NAME, TBERROR, CODE_TYPES } = require('../../constants');
const { TBAPIS } = require('../../sdk');
/**
* 发奖,包含权益,实物,积分
* @param {*} document 必须要有{ type, ename, _id }
* @param {*} session
* @param {*} context
*/
const sendTBAward = async (context, session, document) => {
let { type, ename, _id, credits } = document;
if (!type) {
return CODE_TYPES.PARAMS_ERROR;
}
if (type === PRIZE_TYPE.OBJECT) {
return document;
}
// 发奖
try {
console.log(`type`, type);
// 若中奖为权益
if (type === PRIZE_TYPE.ENAME) {
console.log(`中权益`);
let {drawStatus, remark} = await sendEname(ename, _id, session, context);
console.log(`drawStatus, remark`, drawStatus, remark);
document.drawStatus = drawStatus;
document.remark = remark;
} else if (type === PRIZE_TYPE.CREDITS) {
let { drawStatus, remark } = await sendCredits(credits, session, context);
document.drawStatus = drawStatus;
document.remark = remark;
}
return document;
}catch(e) {
console.log(`发奖错误`, e);
return CODE_TYPES.SYSTEM_ERROR;
}
}
/**
* 发放积分
* @param {发放的积分数} credits
* @param {*} session
* @param {*} context
* @returns {drawStatus, remark}
*/
const sendCredits = async (credits, session, context) => {
let drawStatus = '';
let remark = '';
let result = await TBAPIS.changeCredits(context, session, {
quantity: credits,
change_type: 2,
opt_type: '0',
remark: `参与活动加积分`
});
if (result) {
drawStatus = DRAW_STATUS.SUCCESS;
} else {
drawStatus = DRAW_STATUS.FAIL;
remark = result.result_msg;
}
return {drawStatus, remark};
}
/**
* @desc 发放权益
* @param {权益名称} ename
* @param {唯一key} uniqueId
* @param {*} session
* @param {*} context
* @returns { drawStatus, remark }
*/
const sendEname = async (ename, uniqueId, session, context) => {
let result = {};
try{
result = await TBAPIS.benefitSend(context, {
right_ename: ename,
receiver_id: context.openId,
unique_id: uniqueId,
app_name: C_APP_NAME,
session
});
}catch(e) {
console.log(`发放权益失败`, e);
result = e;
}
let drawStatus = '';
let remark = '';
if (result.result_success) {
drawStatus = DRAW_STATUS.SUCCESS;
} else {
let result_code = result.result_code;
if (['APPLY_SINGLE_COUPON_COUNT_EXCEED_LIMIT', 'APPLY_ONE_SELLER_COUNT_EXCEED_LIMIT', 'USER_PERMISSION_EXCEED_MAX_RIGHT_COUNT_IN_DAY'].includes(result_code)) {
drawStatus = DRAW_STATUS.RETRY;
} else {
drawStatus = DRAW_STATUS.FAIL;
}
remark = TBERROR[result_code || result.msg] || result.result_msg || result.sub_msg || `发放失败`;
}
return {remark, drawStatus};
}
module.exports = sendTBAward;
\ No newline at end of file
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