Commit 7e8df88a authored by 秦海涛's avatar 秦海涛

init

parent ad5fc65b
node_modules
.tea
.DS_Store
.vscode
\ No newline at end of file
import request from './utils/request'; import request from "./utils/request";
const API = { const API = {
// 上传数据生成xslx文件 uploadDataCreateFile // 上传数据生成xslx文件 uploadDataCreateFile
uploadDataCreateFile: params => request('exportAwardsList', 'POST', params), uploadDataCreateFile: (params) => request("exportAwardsList", "POST", params),
// 保存活动配置 saveActivityInfo // 保存活动配置 saveActivityInfo
saveActivityInfo: params => request('saveActivityInfo', 'POST', params), saveActivityInfo: (params) => request("saveActivityInfo", "POST", params),
// 删除活动 delActivity // 删除活动 delActivity
delActivity: params => request('delActivity', 'POST', params), delActivity: (params) => request("delActivity", "POST", params),
// 授权信息保存 sellerSave // 授权信息保存 sellerSave
sellerSave: params => request('sellerSave', 'POST', params), sellerSave: (params) => request("sellerSave", "POST", params),
// 生成活动规则 generateRule
generateRule: params => request('generateRule', 'POST', params),
// 获取中奖名单 findWinnerInfoList // 获取中奖名单 findWinnerInfoList
findWinnerInfoList: params => request('findWinnerInfoList', 'POST', params), findWinnerInfoList: (params) => request("findWinnerInfoList", "POST", params),
// 获取活动列表云函数 getActivityList // 获取活动列表云函数 getActivityList
getActivityList: params => request('getActivityList', 'POST', params), getActivityList: (params) => request("getActivityList", "POST", params),
// 获取活动配置 getActivityDetail // 获取活动配置 getActivityDetail
getActivityDetail: params => request('getActivityDetail', 'POST', params), getActivityDetail: (params) => request("getActivityDetail", "POST", params),
// 获取淘宝店家商品 findItemListByStatus // 获取淘宝店家商品 findItemListByStatus
findItemListByStatus: params => request('findItemListByStatus', 'POST', params, { findItemListByStatus: (params) =>
isShowLoading: true request("findItemListByStatus", "POST", params, {
}), isShowLoading: true,
}),
// 通过id去查找商品 findItemListByIds // 通过id去查找商品 findItemListByIds
findItemListByIds: params => request('findItemListByIds', 'POST', params, { findItemListByIds: (params) =>
isShowLoading: true request("findItemListByIds", "POST", params, {
}), isShowLoading: true,
}),
// 通过权益ename查询商品 queryBenefitByEname // 通过权益ename查询商品 queryBenefitByEname
queryBenefitByEname: params => request('queryBenefitByEname', 'POST', params), queryBenefitByEname: (params) =>
request("queryBenefitByEname", "POST", params),
// 复制创建新活动
createCopyActivity: (params) => request("copyNewActivity", "POST", params),
}; };
export default API; export default API;
\ No newline at end of file
{ {
"pages": [ "pages": [
"pages/index/index" "pages/index/index",
"pages/cmp/index/index"
], ],
"window": { "window": {
"defaultTitle": "xxxxx店铺商家后台" "defaultTitle": "店铺漂流记商家后台"
}, },
"plugins": { "plugins": {
"myPlugin": { "myPlugin": {
......
export const passUrlList = [
"1688.cn",
"taobao.cn",
"taobao.com",
"taobao.net",
"tb.cn",
"tmall.com",
"zhifu.com",
"zhifubao.com",
"juhuasuan.com",
"tmall.hk",
"tmall.com.hk",
"dingtalk.com",
"yushanfang.com",
"guoguo-app.com",
"umeng.com",
];
\ No newline at end of file
export const getMillSeconsByDays = (days = 1) => days * 24 * 3600 * 1000;
\ No newline at end of file
.db-dialog-content { .db-dialog-content {
width: 515px;
background-color: #fff; background-color: #fff;
position: fixed;
left: 50%;
top: 100px;
} }
.db-dialog-title { .db-dialog-title {
display: flex; display: flex;
...@@ -12,6 +14,10 @@ ...@@ -12,6 +14,10 @@
height: 60px; height: 60px;
padding: 0 24px; padding: 0 24px;
} }
.db-dialog-title_no {
height: 30px;
justify-content: flex-end;
}
.db-dialog-content_inner { .db-dialog-content_inner {
/* width: 100%; */ /* width: 100%; */
......
<overlay <overlay
visible="{{ visible }}" visible="{{ visible }}"
hasMask="{{true}}" hasMask="{{hasMask}}"
triggerType="click" triggerType="click"
wrapperClassName="overlay" target=".aaa"
disableScroll
target=".edit-breadcrumb"
data-index="1" data-index="1"
canCloseByMask="{{true}}" canCloseByMask="{{true}}"
> >
<view class="db-dialog-content" style="{{ { width: (width + 'px').replace('pxpx', 'px') } }}"> <view class="db-dialog-content" style="{{ { width: (width + 'px').replace('pxpx', 'px'), marginLeft: '-' + (width / 2 + '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-title {{!title ? 'db-dialog-title_no': ''}}">{{title}}<icon size="xs" type="close" onTap="onCloseDialog" style="color: #999999;" /></view>
<view class="db-dialog-content_inner"> <view class="db-dialog-content_inner">
<slot/> <slot/>
</view> </view>
......
...@@ -2,15 +2,17 @@ Component({ ...@@ -2,15 +2,17 @@ Component({
mixins: [], mixins: [],
data: {}, data: {},
props: { props: {
width: 515,
title: '', title: '',
visible: false, visible: false,
onClose: () => {} onClose: () => {},
hasMask: true
}, },
didMount() {}, didMount() {},
didUpdate() {}, didUpdate() {},
didUnmount() {}, didUnmount() {},
methods: { methods: {
closeDialog() { onCloseDialog() {
this.props.onClose() this.props.onClose()
} }
}, },
......
{ {
"component": true, "component": true,
"usingComponents": { "usingComponents": {
"side-bar": "/components/base/side-bar/side-bar" "side-bar": "/components/basic/side-bar/side-bar"
} }
} }
\ No newline at end of file
.tb-qa-tips {
width: 12px;
height: 12px;
display: inline-block;
background: url(//yun.duiba.com.cn/activity/mini-taobao/question.png) center no-repeat;
background-size: 100% 100%;
}
\ No newline at end of file
<balloon closable="{{false}}" visible="{{balloonVisible}}" align="{{tipsAlign}}" onClose="setBalloonVisibleFalse">
<view
data-x="balloonVisible"
slot="trigger"
onTap="setBalloonVisible"
class="tb-qa-tips" >
</view>
<text>{{tips}}</text>
</balloon>
Component({
data: {
balloonVisible: false
},
props: {
tipsAlign: 'r',
tips: ''
},
didMount() {},
methods: {
setBalloonVisible() {
this.setData({
balloonVisible: true
})
},
setBalloonVisibleFalse() {
this.setData({
balloonVisible: false
})
}
},
});
.confirm-dialog-content { .confirm-dialog-content {
width: 515px!important; width: 515px!important;
min-height: 225px; min-height: 225px;
position:fixed;
left: 50%;
margin-left: -257px;
top: 200px;
background-color: #fff; background-color: #fff;
} }
.confirm-dialog-title { .confirm-dialog-title {
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
triggerType="click" triggerType="click"
wrapperClassName="overlay" wrapperClassName="overlay"
disableScroll disableScroll
target=".edit-breadcrumb" target=".aaa"
data-index="1" data-index="1"
canCloseByMask="{{true}}" canCloseByMask="{{true}}"
> >
......
{
"component": true
}
\ No newline at end of file
.copy-link-wrap{
color:#333;
}
.copy-link-title{
margin-top:10px;
}
.link-view{
display:flex;
}
.link-view .copy-link-url{
width:70%;
height:25px;
overflow:auto;
}
.copy-link-url{
background-color:#eee;
padding:5px 20px;
flex-grow:1;
width:70%;
overflow:auto;
margin-right:10px;
}
.link-btn{
flex-grow:0;
flex-shrink:0;
}
.copy-link-title-export{
margin-bottom:10px;
}
<dialog-wrap
title="如何导出名单"
visible="{{visible}}"
onClose="onCloseDialog"
>
<view class="copy-link-wrap">
<view >导出方式:</view>
<view class="copy-link-title">1.下载文件:复制下方链接-打开电脑浏览器一 输入链接下载Excel;</view>
<view>2.更改文件属性:将下载的文件重命名,在文件名后输入xlsx即可;</view>
<view class="copy-link-title copy-link-title-export">导出链接:</view>
<view class="link-view">
<text class="copy-link-url">{{url}}</text><button type="primary" class="link-btn" onTap="copyLink" >复制链接</button>
</view>
</view>
</dialog-wrap>
\ No newline at end of file
Component({
mixins: [],
data: {},
props: {
onClose: () => {},
url: "",
visible: false,
},
didMount() {},
didUpdate() {},
didUnmount() {},
methods: {
onCloseDialog() {
this.props && this.props.onClose();
},
copyLink() {
const { url } = this.props;
my.setClipboard({
text: url.replace(/amp;/g, ""),
success(res) {
my.showToast({
type: "success",
content: "下载链接复制成功,请在浏览器中打开下载",
});
},
});
},
},
});
{
"component": true,
"usingComponents": {
"dialog-wrap": "../../basic/dialiog-wrap/dialiog-wrap"
}
}
\ No newline at end of file
.modal-content-imageUpload-wrap{
.probablity-prize-content {
background: #FFFFFF;
max-height: 507px;
display: flex;
flex-direction: column;
}
.probablity-prize-content-imageUpload-wrap{
display:flex; display:flex;
padding-top:10px; padding-top:10px;
box-sizing:border-box; box-sizing:border-box;
} }
.modal-content-imageUpload-btn view{ .probablity-prize-content-imageUpload-btn view{
margin-bottom:5px; margin-bottom:5px;
color:#666666 color:#666666
} }
.edit-content-formitem-choosePrize-tips-success{ .probablity-prize-content-formitem-choosePrize-tips-success{
color:#71B204 !important; color:#71B204 !important;
padding-top:20px; padding-top:20px;
font-size:14px; font-size:14px;
} }
.edit-content-formitem-choosePrize-tips-fail{ .probablity-prize-content-formitem-choosePrize-tips-fail{
color:#F23C3C !important; color:#F23C3C !important;
padding-top:20px; padding-top:20px;
font-size:14px; font-size:14px;
} }
.edit-content-formitem-choosePrize-wrap{ .probablity-prize-content-formitem-choosePrize-wrap{
display:flex; display:flex;
} }
.edit-content-input.edit-probablity-input { .probablity-prize-content-input.edit-probablity-input {
width: 300px!important; width: 310px!important;
margin-right: 10px; margin-right: 10px;
} }
.edit-content-formitem-goequity{ .probablity-prize-content-input.dialog-rank-input {
width: 310px!important;
}
.probablity-prize-content-formitem-goequity{
width:80px; width:80px;
height:30px; height:30px;
border-radius:5px; border-radius:5px;
...@@ -38,17 +49,27 @@ ...@@ -38,17 +49,27 @@
text-align:center; text-align:center;
line-height:30px; line-height:30px;
} }
.modal-content-imageUpload-btn{ .probablity-prize-content-imageUpload-btn{
display:flex; display:flex;
flex-direction:column; flex-direction:column;
justify-content:space-around; justify-content:space-around;
} }
.modal-content-imageUpload-btn text{ .probablity-prize-content-imageUpload-btn text{
font-size:12px; font-size:12px;
margin-bottom:5px; margin-bottom:5px;
color:#666666; color:#666666;
} }
.edit-content-formItem-nocoupon{ /* .probablity-prize-content-formItem {
display: block;
} */
.probablity-prize-content-formItem-nocoupon{
color:#000 !important; color:#000 !important;
margin-left:5px; margin-left:5px;
}
.probablity-prize-content-btn {
display: flex;
justify-content: flex-end;
}
.probablity-prize-content-btn_confirm {
margin-right: 10px;
} }
\ No newline at end of file
<dialog-wrap
title="选择奖品"
visible="{{visible}}"
hasMask="{{showDialogMask}}"
onClose="onCloseDialog"
width="738"
>
<view class="probablity-prize-content">
<view class="probablity-prize-content-form">
<form inline="true">
<form-item style="width:100%" size="large" class="probablity-prize-content-formItem" label="奖品类型" required>
<view class="probablity-prize-content-formitem-choosePrize-wrap">
<select onChange="onPrizeTypeChange" defaultValue="{{isEdit ? prizeDialogData.record.type : prizeInitData.type}}">
<option value="1">优惠券</option>
<option value="3">实物</option>
<option value="2">积分</option>
</select>
</view>
</form-item>
<form-item a:if="{{prizeInitData.type == EQUITY_TYPE}}" validateState="{{choosePrizeTips.status}}" style="width:100%" size="large" help="{{choosePrizeTips.content}}" class="probablity-prize-content-formItem probablity-prize-content-formItem-choosePrize" label="选择奖品" required>
<view class="probablity-prize-content-formitem-choosePrize-wrap">
<input a:if="{{prizeInitData.name}}" disabled="true" class="probablity-prize-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="probablity-prize-content-formitem-goequity" onTap="navigateToPlugin" type="primary">{{prizeInitData.ename ? '重新选择' : '选择奖品'}}</view>
</view>
</form-item>
<form-item
style="width:100%"
a:if="{{prizeInitData.type == OBJECT_TYPE || prizeInitData.type == CREDITS_TYPE}}"
size="large"
class="probablity-prize-content-formItem"
label="奖品名称"
validateState="{{prizeNameTips.status}}" help="{{prizeNameTips.content}}"
required>
<input class="probablity-prize-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 size="large" required class="probablity-prize-content-formItem" label="奖品图片" validateState="{{imageTips.status}}" help="{{imageTips.content}}">
<view class="probablity-prize-content-imageUpload-wrap">
<image style="width:90px;height:90px;border:1px solid #ccc;margin-right:10px" src="{{prizeInitData.image}}"></image>
<view class="probablity-prize-content-imageUpload-btn">
<view>奖品图片</view>
<text>尺寸为{{imageLimit[0]}}px * {{imageLimit[1]}}px,格式为jpg/png</text>
<view></view>
<button onTap="uploadImage" type="primary">上传图片</button>
</view>
</view>
</form-item>
<form-item
a:if="{{(prizeInitData.type == OBJECT_TYPE || prizeInitData.type == EQUITY_TYPE) && type === 'probablity'}}"
style="width:100%"
size="large"
class="probablity-prize-content-formItem"
label="库存类型"
required>
<radio-group value="{{prizeInitData.limitStock}}" onChange="onLimitStockChange">
<radio value="{{0}}">不限库存</radio>
<radio value="{{1}}">限制库存</radio>
</radio-group>
</form-item>
<form-item
style="width:100%"
a:if="{{(prizeInitData.type == OBJECT_TYPE || prizeInitData.type == EQUITY_TYPE) && prizeInitData.limitStock && type === 'probablity'}}"
size="large"
class="probablity-prize-content-formItem"
label="奖品库存"
validateState="{{prizeNumberTips.status}}" help="{{prizeNumberTips.content}}"
required>
<input
type="number"
maxLength="5"
class="probablity-prize-content-input"
data-name="stock"
onChange="onChangePrizeValue"
addonTextAfter="已发放库存{{(prizeDialogEdit ? prizeDialogData.record.useStock : prizeInitData.useStock) || 0}}件"
type="number"
value="{{prizeInitData.stock}}"
name="奖品库存"
defaultValue="{{prizeDialogEdit ? prizeDialogData.record.stock : prizeInitData.stock}}"
placeholder="请输入奖品库存"/>
</form-item>
<form-item
a:if="{{prizeInitData.type == CREDITS_TYPE}}"
size="large"
class="probablity-prize-content-formItem"
label="积分价值"
style="width:100%"
validateState="{{creditsValueTips.status}}" help="{{creditsValueTips.content}}"
required>
<input type="number" class="probablity-prize-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="{{type==='rank'}}"
size="large"
class="probablity-prize-content-formItem"
label="领奖名次"
style="width:100%"
validateState="{{rankValueTips.status}}" help="{{rankValueTips.content}}"
required>
<input
type="text"
class="probablity-prize-content-input dialog-rank-input"
data-name="rank"
onChange="onChangePrizeValue"
type="number"
value="{{prizeInitData.rank}}"
hasLimitHint="true"
name="领奖名次"
defaultValue="{{prizeDialogEdit ? prizeDialogData.record.rank : prizeInitData.rank}}"
placeholder='领奖名次, 第5名输入"5", 第5-10名输入"5-10"'/>
</form-item>
<form-item
a:if="{{type==='probablity'}}"
style="width:100%"
size="large"
class="probablity-prize-content-formItem"
label="中奖概率"
validateState="{{probablityTips.status}}"
help="{{probablityTips.content}}"
required>
<input
class="probablity-prize-content-input edit-probablity-input"
type="number"
onChange="onChangePrizeValue"
data-name="probablity"
value="{{prizeInitData.probablity}}"
name="中奖概率"
defaultValue="{{prizeInitData.probablity}}"
placeholder="当前概率不能超过100"/>%
</form-item>
</form>
</view>
<view class="probablity-prize-content-btn">
<button class="probablity-prize-content-btn_confirm" onTap="updatePrize" type="primary">确定</button>
<button onTap="onCloseDialog">取消</button>
</view>
</view>
</dialog-wrap>
\ 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 = 1;
const CREDITS_TYPE = 2;
const OBJECT_TYPE = 3;
const THANKS_TYPE = 4;
const INIT_DATA = {
ename: "",
id: "",
stock: "",
type: 1,
image: "",
credits: "",
probablity: "",
name: "",
limitStock: 0,
useStock: 0,
rank: ""
};
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: "",
},
rankValueTips: {
status: "success",
content: "",
},
prizeInitData: {
ename: "",
id: "",
stock: "",
activityOutId: "",
type: 1,
image: "",
credits: "",
probablity: "",
name: "",
limitStock: 0,
useStock: 0,
rank: ""
},
},
props: {
prizeDialogData: {
index: '',
record: {}
},
type: 'probablity', // 奖品弹窗类型 probablity: 概率 rank: 排名
imageLimit: [ 200, 200 ],
activityOutId: "",
isEdit: false,
visible: false,
hasEditPrize: false,
datasource: [],
onCloseDialog: function () {},
onUpdate: function () {}
},
didMount() {
this.initPosition();
this.resetPrizeInit();
this.initBenefit();
},
methods: {
initPosition() {
const { scrollTop } = this.$page.data;
this.setData({
dialogTop: scrollTop * 2,
});
},
initBenefit() {
const 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, stock } = data[0];
that.setData({
prizeInitData: {
...that.data.prizeInitData,
name: benefitName,
type: rightTypeId,
startTime,
endTime,
stock: stock
},
});
that.$page.data.backPageTimeOut = setTimeout(() => {
my.navigateBack({
delta: 1,
});
}, 6000);
} else {
my.showToast({
type: "fail",
content: message,
});
}
},
};
plugin.setBridge(bridge);
},
// 编辑更换类型时重置数据
resetPrizeData(type) {
this.setData({
prizeInitData: {
...INIT_DATA,
type
},
});
},
onPrizeTypeChange(e) {
let value = e.detail.value;
const { type } = this.data.prizeInitData;
let initData = {
ename: "",
id: "",
stock: "",
useStock: "",
type: value,
image: "",
credits: "",
probablity: "",
name: "",
limitStock: 0
};
if(type !== value) {
this.resetPrizeData(value);
return;
}
this.setData({
prizeInitData: {
...this.data.prizeInitData,
...initData
},
});
},
navigateToPlugin() {
clearTimeout(this.$page.data.backPageTimeOut);
my.navigateTo({
url: "plugin://myPlugin/orightindex-page",
});
},
resetPrizeInit() {
const { isEdit, prizeDialogData } = this.props;
console.log(isEdit, prizeDialogData)
if (isEdit) {
console.log(prizeDialogData)
const { ...rest } = prizeDialogData.record;
this.setData({
prizeInitData: {
...rest,
},
});
} else {
this.setData({
prizeInitData: INIT_DATA
});
}
},
onLimitStockChange(e) {
const value = e.detail.value;
this.setData({
prizeInitData: {
...this.data.prizeInitData,
limitStock: value
},
});
},
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],
});
const { imageLimit } = this.props;
let imgBool = ~path.indexOf(".png") || ~path.indexOf(".jpg");
if (height !== imageLimit[1] || width !== imageLimit[0] || !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,
showDialogMask: true
},
});
} 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,
stock,
credits,
limitStock,
endTime,
rank,
...rest
} = this.data.prizeInitData;
const { imageTips } = this.data;
const { type: dialogType } = this.props;
let probablityRexp = /^\d+(\.\d{1,2})?$/;
if (!probablityRexp.test(probablity) || probablity > 100) {
this.showItemTips(
"probablityTips",
"error",
"奖品概率必须在0-100%之间且最多支持两位小数"
);
} else {
this.showItemTips("probablityTips", "success", "");
}
if(dialogType !== 'probablity') {
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 || type == EQUITY_TYPE) && limitStock && !validateRangeNumber(stock, [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", "");
}
if(dialogType === 'rank') {
if(!rank) {
this.showItemTips(
"rankValueTips",
"error",
"请输入正确的领奖名次"
);
} else {
this.showItemTips("rankValueTips", "success", "");
}
}
const { probablityTips, prizeNumberTips, creditsValueTips } = this.data;
const isImagePass = image && imageTips.status !== "error";
let stockPass = true;
if(limitStock && !stock) {
stockPass = false;
}
const isEquietyPass =
type == EQUITY_TYPE &&
name &&
isImagePass &&
ename &&
probablityTips.status !== "error";
const isObjectPass =
type == OBJECT_TYPE &&
name &&
stockPass &&
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) {
const { isEdit, prizeDialogData, onUpdate, onAdd } = this.props;
let prizeData = {
image,
name,
ename,
type,
probablity,
stock,
credits,
rank,
...rest,
};
console.log(prizeData)
if(isEdit) {
onUpdate && onUpdate(prizeData, prizeDialogData.index);
} else {
onAdd && onAdd(prizeData)
}
this.onCloseDialog();
} else {
my.showToast({
type: "fail",
content: "请完整填写奖品信息",
});
}
},
showItemTips(type, status, content) {
this.setData({
[type]: {
status,
content,
},
});
},
onCloseDialog() {
this.props.onClose && this.props.onClose();
},
},
});
...@@ -5,5 +5,8 @@ ...@@ -5,5 +5,8 @@
"version": "0.0.12", "version": "0.0.12",
"provider": "3000000002026202" "provider": "3000000002026202"
} }
},
"usingComponents": {
"dialog-wrap": "../../basic/dialiog-wrap/dialiog-wrap"
} }
} }
\ No newline at end of file
.rank-dialog-wrap {
}
.other-winner-list {
display: flex;
justify-content: flex-end;
margin: 0 0 20px 0;
}
.now-winner-list {
display: flex;
justify-content: flex-end;
margin: 20px 0;
}
.rank-pagination {
display: flex;
justify-content: flex-end;
margin: 10px 0 0 0;
}
\ No newline at end of file
<dialog-wrap
title="中奖信息"
visible="{{false}}"
onClose="onCloseDialog"
width="800"
>
<view class="rank-dialog-wrap">
<view class="other-winner-list">
<button type="text">导出其它中奖名单</button>
</view>
<tab shape="wrapped">
<tab-item
a:for="{{tabs}}"
title="{{item.tab}}"
key="{{item.key}}"
>
<view class="now-winner-list">
<button type="primary">导出本期中奖名单</button>
</view>
<table dataSource="{{dataSource}}">
<table-column a:for="{{columns}}" title="{{item.title}}" dataIndex="{{item.prop}}"/>
</table>
<view class="rank-pagination">
<text class="rank-pagination-text">共{{pageInfo.total}}条</text>
<pagination shape="arrow-only" hideOnlyOnePage="true" defaultCurrent="1" current="{{pageInfo.pageNo}}" pageSize="{{pageInfo.pageSize}}" onChange="changePagination" pageShowCount="100" total="{{pageInfo.total}}" />
</view>
</tab-item>
</tab>
</view>
</dialog-wrap>
\ No newline at end of file
Component({
mixins: [],
data: {
tabs: [{
tab: '0608-0712',
key: 111
},{
tab: '03308-0712',
key: 43
},{
tab: '064548-0712',
key: 435
},{
tab: '0608-0712',
key: 436545
},{
tab: '0608-0712',
key: 65
},{
tab: '0608-0712',
key: 11561
},{
tab: '0608-0712',
key: 13311
},{
tab: '0608-0712',
key: 145611
},{
tab: '0608-0712',
key: 1178761
}],
dataSource: [],
columns: [
{ title: '排名', prop: 'rank' },
{ title: '用户昵称', prop: 'userNick' },
{ title: '奖品类型', prop: 'type' },
{ title: '奖品名称', prop: 'name' },
{ title: '收货人', prop: 'receiveName' },
{ title: '联系方式', prop: 'phone' },
{ title: '收货地址', prop: 'address' }
],
pageInfo: {
pageNo: 1,
pageSize: 100,
total: 0
}
},
props: {},
didMount() {},
didUpdate() {},
didUnmount() {},
methods: {},
});
{
"component": true,
"usingComponents": {
"dialog-wrap": "../../basic/dialiog-wrap/dialiog-wrap"
}
}
\ No newline at end of file
.task-day-limit {
margin: 0 0 0 60px;
color: #333;
/* font-size: 13px; */
display: flex;
align-items: center;
}
.task-content-btn {
display: flex;
justify-content: flex-end;
}
.task-content-btn_confirm {
margin-right: 10px;
}
\ No newline at end of file
<dialog-wrap
visible="{{visible}}"
onClose="onCloseDialog"
width="460"
>
<view class="task-dialog-wrap">
<form style="width: 100%" data-name="form" inline="{{true}}">
<form-item label="任务标题">
<input
style="width: 250px"
onChange="onChange"
data-name="title"
defaultValue="{{title}}"
maxLength="{{10}}"
maxlength="{{10}}"
hasLimitHint="{{true}}"
name="title"
placeholder="请输入任务标题"/>
</form-item>
<form-item label="任务类型" style="width:100%;">
<radio-group size="small" value="{{taskRateType}}" onChange="onTaskRateTypeChange">
<radio size="small" value="{{1}}">永久一次</radio>
<radio size="small" value="{{2}}" >每天限次</radio>
<radio size="small" value="{{3}}" >不限次</radio>
</radio-group>
</form-item>
<form-item a:if="{{taskRateType === 2}}">
<view class="task-day-limit">
<view>每天最多完成</view>
<view style="margin: 0 6px;"><select value="{{times}}" onChange="onTimesChange" dataSource="{{dataSource}}"/>
</view>次任务</view>
</form-item>
<form-item label="跳转链接" a:if="{{!noLink}}">
<input style="width: 250px" onChange="onChange" data-name="link" defaultValue="{{link}}" name="link" placeholder="请输入跳转链接"/>
</form-item>
<form-item label="任务奖励">
<input onChange="onChange" data-name="value" defaultValue="{{value}}" name="value" addonTextAfter="{{unit}}"/>
</form-item>
</form>
<view class="task-content-btn">
<button class="task-content-btn_confirm" onTap="onSaveTask" type="primary">保存</button>
<button onTap="onCloseDialog">取消</button>
</view>
</view>
</dialog-wrap>
\ No newline at end of file
const { passUrlList } = require('../../../cmpUtils/const');
Component({
mixins: [],
data: {
dataSource:[
{ label: 1, value: 1 },
{ label: 2, value: 2 },
{ label: 3, value: 3 },
{ label: 4, value: 4 },
{ label: 5, value:5 },
{ label: 6, value:6 },
{ label: 7, value:7 },
{ label: 8, value:8 },
{ label: 9, value:9 },
{ label: 10, value:10 }
],
taskRateType: 1,
times: 1,
title: '',
link: '',
value: ''
},
props: {
visible: false,
noLink: false,
type: '',
unit: '',
onClose: () => {},
onUpdate: () => {}
},
didMount() {},
didUpdate() {},
didUnmount() {},
methods: {
onCloseDialog() {
this.props.onClose();
},
onTimesChange(e) {
const { value } = e.detail;
this.setData({
times: value
})
},
onTaskRateTypeChange(e) {
const { value } = e.detail;
this.setData({
taskRateType: value
})
},
onChange(e) {
const { value } = e.detail;
const { name } = e.target.dataset;
this.setData({
[name]: value
})
},
validateLink(link) {
if(!link) return false;
return true;
},
onSaveTask() {
const { title, link, value, taskRateType, times } = this.data;
if(!title) {
my.showToast({
type: 'fail',
content: '请输入任务标题'
})
return;
}
if(!this.props.noLink && !this.validateLink(link)) {
my.showToast({
type: 'fail',
content: '请输入正确的链接地址'
})
return;
}
if(!value) {
my.showToast({
type: 'fail',
content: '请输入正确的任务奖励'
})
return;
}
this.props.onUpdate && this.props.onUpdate({ title, link, value, taskRateType, times, type: this.props.type })
this.onCloseDialog();
}
},
});
{
"component": true,
"usingComponents": {
"dialog-wrap": "../../basic/dialiog-wrap/dialiog-wrap"
}
}
\ No newline at end of file
.tb-cfg-input {
display: flex;
align-items: center;
font-size: 13px;
color: #333;
}
.tb-cfg-input_prev {
margin-right: 8px;
}
.tb-cfg-input_append {
margin-left: 8px;
}
<view class="tb-cfg-input">
<view class="tb-cfg-input_prev">{{textBefore}}</view>
<input style="width: 60px;" value="{{value}}" onChange="onChange" placeholder="{{placeholder}}" maxlength="{{maxlength}}"/>
<view class="tb-cfg-input_append">{{textAfter}}</view>
</view>
\ No newline at end of file
Component({
data: {
maxlength: ''
},
props: {
textBefore: '',
textAfter: '',
validateRange: [],
value: '',
placeholder: '',
onChange: () => {}
},
didMount() {
this.formatMaxlength();
},
didUpdate() {},
methods: {
onChange(e) {
this.props.onChange && this.props.onChange(e);
},
// 根据校验限制输入长度
formatMaxlength() {
const { validateRange } = this.props;
if(!validateRange.length) return;
let maxLimit = validateRange[1];
if(maxLimit) {
this.setData({
maxlength: (maxLimit+'').length
})
}
}
},
});
{
"component": true
}
\ No newline at end of file
.tb-imageUpload-wrap{
display:flex;
padding-top:10px;
box-sizing:border-box;
}
.tb-imageUpload-btn{
display:flex;
flex-direction:column;
justify-content:space-around;
}
.tb-imageUpload-btn text{
font-size:12px;
margin-bottom:5px;
color:#666666;
}
.tb-imageUpload-btn view{
margin-bottom:5px;
color:#666666
}
\ No newline at end of file
<view class="tb-imageUpload-wrap">
<image style="width:90px;height:90px;border:1px solid #ccc;margin-right:10px" src="{{prizeInitData.image}}"></image>
<view class="tb-imageUpload-btn">
<view>图片</view>
<text>尺寸为{{imageLimit[0]}}px * {{imageLimit[1]}}px,格式为jpg/png</text>
<view></view>
<button onTap="uploadImage" type="primary">上传图片</button>
</view>
</view>
\ No newline at end of file
import { chooseImage, getImageInfo, validateRangeNumber } from "/utils";
Component({
mixins: [],
data: {},
props: {
imageLimit: [200, 200],
onSuccess: () => {},
onError: () => {}
},
didMount() {},
didUpdate() {},
didUnmount() {},
methods: {
async uploadImage() {
const { onSuccess, onError } = this.props;
try {
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) {
onError && onError({height, width, type, path })
}
const { url } = await cloud.file.uploadFile({
filePath: res.apFilePaths[0],
fileType: "image",
fileName: path.split("/").pop(),
});
onSuccess && onSuccess(url)
} catch (error) {
console.error(error);
}
},
},
});
{
"component": true
}
\ No newline at end of file
.tb-input-container {
display: inline-block;
position: relative;
}
.tb-question-tips {
display: inline-block;
position: relative;
top: -8px;
left: 6px;
}
\ No newline at end of file
<view class="tb-input-container">
<input
value="{{value}}"
size="{{size}}"
onChange="onChange"
onKeyDown="onKeyDown"
disabled="{{disabled}}"
maxLength="{{maxLength}}"
maxlength="{{maxLength}}"
hasLimitHint="{{hasLimitHint}}"
cutString="{{cutString}}"
readOnly="{{readOnly}}"
trim="{{trim}}"
data-name="{{dataName}}"
placeholder="{{placeholder}}"
onFocus="onFocus"
onBlur="onBlur"
name="{{name}}"
state="{{state}}"
hasClear="{{hasClear}}"
hasBorder="{{hasBorder}}"
onPressEnter="onPressEnter"
hint="{{hint}}"
addonTextBefore="{{addonTextBefore}}"
addonTextAfter="{{addonTextAfter}}"
autoFocus="{{autoFocus}}"
htmlType="{{htmlType}}"
style="width: {{width}}"
/>
<view class="tb-question-tips">
<question-tips a:if="{{tips}}" tips="{{tips}}"/>
</view>
</view>
\ No newline at end of file
Component({
mixins: [],
data: {
balloonVisible: false
},
props: {
value: '',
size: 'medium',
disabled: false,
maxLength: null,
hasLimitHint: false,
cutString: false,
readOnly: false,
tips: false,
trim: false,
placeholder: '',
name: '',
state: '',
hasClear: '',
hasBorder: true,
hint: '',
addonTextBefore: '',
addonTextAfter: '',
autoFocus: '',
htmlType: '',
width: '',
tipsAlign: 't',
dataName: '',
onChange: () => {},
onKeyDown: () => {},
onFocus: () => {},
onBlur: () => {},
onPressEnter: () => {},
},
didMount() {},
didUpdate() {},
didUnmount() {},
methods: {
onChange(e) {
this.props.onChange && this.props.onChange(e);
},
onKeyDown(e) {
this.props.onKeyDown && this.props.onKeyDown(e);
},
onFocus(e) {
this.props.onFocus && this.props.onFocus(e);
},
onBlur(e) {
this.props.onBlur && this.props.onBlur(e);
},
onPressEnter(e) {
this.props.onPressEnter && this.props.onPressEnter(e);
},
setBalloonVisible() {
this.setData({
balloonVisible: true
})
},
setBalloonVisibleFalse() {
this.setData({
balloonVisible: false
})
}
},
});
{
"component": true,
"usingComponents": {
"question-tips": "/components/basic/question-tips/question-tips"
}
}
\ No newline at end of file
.tb-label {
font-size: 16px;
margin-bottom: 20px;
font-weight: 600;
padding-left: 26px;
}
<view class="tb-label">
{{title}}
</view>
\ No newline at end of file
Component({
props: {
title: ''
}
});
{
"component": true
}
\ No newline at end of file
.tb-picker-question-tips {
display: inline-block;
position: relative;
top: -8px;
left: 6px;
}
.error-tips {
color: red;
font-size: 12px;
}
\ No newline at end of file
<view class="className">
<range-picker
style="width: 320px"
onVisibleChange="dialogChange"
hasClear="true"
value="{{innerTimeRange}}"
data-time="{{innerTimeRange}}"
onChange="pickerChange"
disabled="{{disabled}}"
show-time="{{ format: 'HH:mm' }}"
state="{{state}}"
help="{{errorTips}}"
/>
<view class="tb-picker-question-tips">
<question-tips a:if="{{tips}}" tips="{{tips}}"/>
</view>
</view>
\ No newline at end of file
import dayjs from 'dayjs';
import { getMillSeconsByDays } from '../../../cmpUtils/time';
Component({
mixins: [],
data: {
innerTimeRange: [],
state: '',
errorTips: ''
},
props: {
timeRange: [],
// 用于活动开始后,不可更改开始时间的备份
originalStartTime: '',
disabled: false,
onChange: () => {},
disableStartTime: false
},
didMount() {
this.initTime();
},
didUpdate(prevProps) {
this.changeTime(prevProps, this.props)
},
didUnmount() {},
methods: {
// 初始化时间, 默认开始时间为当前时间后的10分钟 结束时间为7天之后
initTime() {
const { timeRange } = this.props;
const nowTime = new Date().getTime();
const sevenDays = getMillSeconsByDays(7);
const tenMinutes = 600000;
let defaultStartTime = timeRange[0];
let defaultEndTime = timeRange[1];
// 当前时间 + 10分钟
let startNow = dayjs(nowTime + tenMinutes).format("YYYY-MM-DD HH:mm:ss");
// 开始时间 + 7天
let endNow = dayjs(nowTime + tenMinutes + sevenDays).format("YYYY-MM-DD HH:mm:ss");
// 配置了初始时间,则使用初始时间
if(defaultStartTime) {
startNow = defaultStartTime;
}
defaultEndTime && (endNow = defaultEndTime);
this.setData({
innerTimeRange: [ startNow, endNow ]
})
this.confrimChangeTime([new Date(startNow).getTime(), new Date(endNow).getTime()])
},
// defaultStartTime, defaultEndTime props变化
changeTime(prevProps, currentProps) {
let prevStartTime = prevProps.timeRange[0];
let currentStartTime = currentProps.timeRange[0];
let prevEndTime = prevProps.timeRange[1];
let currentEndTime = currentProps.timeRange[1];
const isTimeChange = currentStartTime !== prevStartTime
|| currentEndTime !== prevEndTime;
if(isTimeChange) {
this.setData({
innerTimeRange: [
dayjs(currentStartTime).format("YYYY-MM-DD HH:mm:ss"),
dayjs(currentEndTime).format("YYYY-MM-DD HH:mm:ss")
]
})
}
},
pickerChange(e) {
const [startTime, endTime] = e.detail.value;
const innerTimeRange = [ startTime || '', endTime || '' ];
if(!startTime && !endTime) {
this.confrimChangeTime(['', ''])
}
this.setData({
innerTimeRange
})
},
// 确认时间
checkoutTime(startTime, endTime) {
const { disableStartTime, originalStartTime } = this.props;
const isStart = originalStartTime < Date.now();
// 编辑活动时,如果活动已经开始, 开始时间不能编辑
if (disableStartTime && isStart) {
this.confrimChangeTime([dayjs(originalStartTime).format("YYYY-MM-DD HH:mm:ss"), endTime])
startTime = originalStartTime
}
let timeHasSame = new Date(endTime).getTime() === new Date(startTime).getTime()
let timeIsCantChoose = new Date().getTime() > new Date(startTime).getTime()
if (timeHasSame || timeIsCantChoose) {
let errTips = '';
if (timeIsCantChoose && !disableStartTime) {
errTips = `活动开始时间不能小于当前时间`
console.log('error', '活动开始时间不能小于当前时间')
}
if (timeHasSame) {
errTips = `开始时间不能等于结束时间`
console.log('error', '开始时间不能等于结束时间')
}
this.confrimChangeTime([startTime, endTime], errTips)
} else {
this.setData({
state: '',
errorTips: ''
})
this.confrimChangeTime([startTime, endTime])
}
},
// 时间选择框状态变化
dialogChange(e) {
const [startTime, endTime] = e.target.dataset.time
const {
value
} = e.detail
if (!value) {
this.checkoutTime(startTime, endTime)
}
},
confrimChangeTime([startTime, endTime], error) {
this.props.onChange && this.props.onChange([ new Date(startTime).getTime() || '', new Date(endTime).getTime() || ''], error);
}
}
});
{
"component": true,
"usingComponents": {
"question-tips": "/components/basic/question-tips/question-tips"
}
}
\ No newline at end of file
.tb-rule-title {
font-size: 12px;
color: #333;
}
\ No newline at end of file
<view class="tb-rule-container">
<view class="tb-rule-title">用于展示在活动页的规则说明</view>
<textarea
style="width:400px;margin: 10px 0 0 0"
value="{{rule}}"
onChange="onChange"
maxLength="2000"
hasLimitHint="{{true}}"
>
</textarea>
</view>
\ No newline at end of file
Component({
data: {},
props: {
onChange: () => {}
},
didMount() {},
didUpdate() {},
methods: {
onChange(e) {
this.props.onChange && this.props.onChange(e);
},
},
});
{
"component": true
}
\ No newline at end of file
.tb-list-container {
padding: 0 24px;
background-color: #fff;
}
.tb-list-container .next-table-header .next-table-cell {
background-color: #fff;
}
.tb-list-container .next-table-row {
height: 80px;
}
.tb-list-container .next-table-header .next-table-cell-wrapper {
height: 60px;
line-height: 38px;
}
.tb-list-pagination {
margin-top: 20px;
align-items: center;
justify-content: flex-end;
display: flex;
}
.tb-list-edit {
margin-right: 8px;
}
.font-14 {
font-size: 14px;
}
.font-13 {
font-size: 13px;
}
.font-12 {
font-size: 12px;
}
.mb-4 {
margin-bottom: 4px;
}
\ No newline at end of file
<view class="tb-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">
<view slot-scope="x">
<button a:if="{{buttons.includes('edit')}}" class="tb-list-edit" onTap="handleClickEdit" type="primary" text="true" data-x="{{x}}" >编辑活动</button>
<button a:if="{{buttons.includes('export')}}" class="tb-list-edit" type="primary" text="true" data-x="{{x}}" data-type="object" onTap="exportWinnerList">导出实物中奖名单</button>
<button a:if="{{buttons.includes('delete')}}" class="tb-list-edit" type="primary" text="true" data-x="{{x}}" onTap="handleTapDelete">删除</button>
<button a:if="{{buttons.includes('copyLink')}}" class="tb-list-edit" type="primary" text="true" data-x="{{x}}" onTap="onCopyLink">复制活动链接</button>
<button a:if="{{buttons.includes('copyNewActivity')}}" class="tb-list-edit" type="primary" text="true" data-x="{{x}}" onTap="onCreateNewActivity">复制新活动</button>
</view>
</table-column>
</table>
</view>
<view class="tb-list-pagination">
<text class="tb-list-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>
<copy-link-dialog visible="{{exportDialogVisible}}" url="{{exportUrl}}" onClose="onCloseExportDialog"/>
<confirm-dialog
visible="{{confirmDialog.visible}}"
content="{{confirmDialog.content}}"
onClose="onCloseConfirmDialog"
onConfirm="onConfirmDelete"
/>
<rank-dialog />
\ No newline at end of file
import dayjs from "dayjs";
import {
getActivityList,
delActivity,
findWinnerInfoList,
uploadDataCreateFile,
createCopyActivity,
} from "/api";
import { appId } from "/config";
import { setClipboard } from "/utils";
Component({
data: {
isLoadingList: false,
exportDialogVisible: false,
exportUrl: '',
dataSource: [],
confirmDialog: {
visible: false,
content: "删除活动后该活动用户参与信息将全部删除,确认删除吗?",
},
pageInfo: {
pageNo: 1,
pgaeSize: 10,
total: 0
},
deleteId: "",
selectedItem: "",
},
props: {
buttons: ['edit', 'export', 'delete', 'copyLink']
},
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,
});
this.setData({ isLoadingList: false });
if (!success) {
my.showToast({
type: "fail",
content: message,
});
return;
}
const { list, pageNo, pageSize, total } = data;
const timeNow = new Date().getTime();
let formatList = list.map((i) => {
return {
...i,
startTime: dayjs(+i.startTime).format("YYYY-MM-DD HH:mm:ss"),
endTime: dayjs(+i.endTime).format("YYYY-MM-DD HH:mm:ss"),
};
});
this.setData({
dataSource: formatList,
pageInfo: {
pageNo,
pageSize,
total,
},
});
} catch (error) {
this.setData({
isLoadingList: false,
});
}
},
// 复制链接
async onCopyLink(evt) {
const { activityId } = evt.target.dataset.x.record;
let text = `https://m.duanqu.com/?_ariver_appid=${appId}?query=activityId%3D${activityId}`
try {
await setClipboard({
text
});
my.showToast({
type: "success",
content: "复制活动链接成功",
});
} catch (error) {
console.log(error);
}
},
onCloseExportDialog() {
this.setData({
exportDialogVisible: false
})
},
//复制新活动
async onCreateNewActivity(evt) {
const { activityId } = evt.target.dataset.x.record;
let res = await createCopyActivity({ activityId });
if (res.success) {
this.getList();
} else {
my.showToast({
content: res.message,
type: "fail",
});
}
},
// 点击列表删除
handleTapDelete(evt) {
const { activityId } = evt.target.dataset.x.record;
console.log(1)
this.setData({
confirmDialog: Object.assign({}, this.data.confirmDialog, {
visible: true,
}),
deleteId: activityId,
});
},
onCloseConfirmDialog() {
this.setData({
confirmDialog: Object.assign({}, this.data.confirmDialog, {
visible: false,
}),
});
},
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
});
if (success) {
this.getList();
} else {
my.showToast({
type: "fail",
content: message,
});
}
} catch (error) {
console.log(error);
}
},
// 导出中奖名单
async exportWinnerList(evt) {
const { activityId } = evt.target.dataset.x.record;
const { type } = evt.target.dataset;
let isObject = type === "object";
my.showLoading({ content: "生成文件中..." });
try {
const { success, data, message } = await uploadDataCreateFile({
activityId,
isObject,
});
my.hideLoading();
if (success) {
await setClipboard({ text: data.url.replace(/amp;/g, "") });
this.setData({
exportDialogVisible: true,
exportUrl: data.url.replace(/amp;/g, "")
})
} else {
my.showToast({
type: "fail",
content: message,
});
}
} catch (error) {
my.hideLoading();
console.log(error, "exportList-error");
}
},
// 编辑
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);
},
},
});
{
"component": true,
"usingComponents": {
"copy-link-dialog": "/components/dialog/copy-link-dialog/copy-link-dialog",
"confirm-dialog": "/components/dialog/confirm-dialog/confirm-dialog",
"rank-dialog": "/components/dialog/rank-dialog/rank-dialog"
}
}
\ 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
.tb-rate-table-container {
width: 800px;
}
.tb-rate-table-add {
border-bottom: 1px solid #ddd;
display: flex;
align-items: center;
justify-content: center;
height: 50px;
background: #fff;
color: #3386F1;
}
.tb-rate-table-add_icon {
width: 12px;
height: 12px;
border: 1px solid #3386F1;
border-radius: 50%;
color: #3386F1;
display: flex;
justify-content: center;
align-items: center;
margin-right: 10px;
}
.tb-rate-table-operate {
color: #3386F1;
font-size: 12px;
margin: 0 4px 0 0;
}
.tb-rate-table-prize-image {
display: flex;
align-items: center;
}
.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;
}
.tb-rate-table-add_disabled {
height: 54px;
line-height: 54px;
border-left: 1px solid #D7DBE0;
border-bottom: 1px solid #D7DBE0;
border-right: 1px solid #D7DBE0;
color: #D7DBE0;
}
.tb-rate-table-add_disabled .tb-rate-table-add_icon {
color: #D7DBE0;
border: 1px solid #D7DBE0;
}
/* 表格样式覆盖 */
.tb-rate-table .next-table-header .next-table-cell-wrapper {
height: 50px;
line-height: 28px;
}
/* 表格样式覆盖 */
\ No newline at end of file
<view class="tb-rate-table-container" style="border: 1px solid #ddd;border-bottom:none;margin: 10px 0 0 0;">
<table dataSource="{{prizeInfoList}}"hasBorder="{{false}}" class="tb-rate-table">
<!-- <table-column title="排序" dataIndex="_sort">
<view slot-scope="x">
<view>
<text a:if="{{x.index !== 0}}" data-idx="{{x.index}}" onTap="up">⬆️</text>
<text a:if="{{x.index + 1 !== prizeInfoList.length}}" data-idx="{{x.index}}" onTap="down">⬇️</text>
</view>
</view>
</table-column> -->
<table-column title="序号" dataIndex="_index">
<view slot-scope="x">{{x.index + 1}}</view>
</table-column>
<table-column title="奖品信息" dataIndex="image">
<view class="tb-rate-table-prize-image" slot-scope="x">
<view a:if="{{x.value}}">
<image style="width:42px;height:42px;margin: 0 6px 0 0" src="{{x.value}}"></image>
</view>
<view>{{x.record.name}} </view>
</view>
</table-column>
<table-column title="奖品数量(件)" dataIndex="stock">
<view slot-scope="x">{{x.value || '-'}}</view>
</table-column>
<table-column title="中奖概率(%)" dataIndex="probablity">
<view slot-scope="x">{{x.value}}</view>
</table-column>
<table-column title="操作">
<text class="tb-rate-table-operate" onTap="onEditPrize" data-x="{{x}}" data-name="edit" slot-scope="x">编辑</text>
<text slot-scope="x" class="tb-rate-table-operate edit-content-formItem-delete" onTap="handleDeleteClick" data-x="{{x}}" slot-scope="x">删除</text>
</table-column>
</table>
<view class="tb-rate-table-add {{prizeInfoList.length >= limit && 'tb-rate-table-add_disabled'}}" onTap="addPrize">
<view class="tb-rate-table-add_icon"><icon size="xxs" type="add"/></view>
添加奖品 ({{prizeInfoList.length}}/{{limit}})
</view>
</view>
<prize-dialog
a:if="{{dialogVisible}}"
visible="{{dialogVisible}}"
isEdit="{{isEdit}}"
prizeDialogData="{{prizeDialogData}}"
onClose="onCloseDialog"
onUpdate="onPrizeUpdate"
onAdd="onPrizeAdd"/>
\ No newline at end of file
Component({
mixins: [],
data: {
prizeInfoList: [{
_id: "2321321",
image: "https://img.alicdn.com/imgextra/i2/742982950/O1CN01zuwaKU1Xf905ZgMno_!!742982950-2-miniprogram.png",
name: "一等奖大礼包",
stock: 34,
probablity: 22,
restStock: 10,
type: 2,
limitStock: 0
}],
dialogVisible: false,
isEdit: false,
prizeDialogData: {}
},
props: {
limit: 20
},
didMount() {},
didUpdate() {},
didUnmount() {},
methods: {
// 删除奖品
handleDeleteClick(e) {
const { index } = e.target.dataset.x;
this.setData({ prizeInfoList: this.data.prizeInfoList.filter(((i, k) => k !== index)) })
},
addPrize() {
const { prizeInfoList } = this.data;
const { limit } = this.props;
if (prizeInfoList.length >= limit) {
my.showToast({ type: 'fail', content: `最多创建${limit}个奖励配置` });
return;
}
this.setData({
dialogVisible: true,
isEdit: false,
prizeDialogData: {}
})
},
onEditPrize(e) {
const { name, x } = e.target.dataset;
console.log(x)
this.setData({
prizeDialogData: x,
isEdit: true,
dialogVisible: true
})
},
onCloseDialog() {
this.setData({ dialogVisible: false })
},
onPrizeUpdate(data, index) {
const updatedData = this.data.prizeInfoList.map((v, i) => {
if(index === i) return {...v, ...data}
return v;
})
this.setData({
prizeInfoList: updatedData
})
},
onPrizeAdd(data) {
const { prizeInfoList } = this.data;
this.setData({
prizeInfoList: [...prizeInfoList, data]
})
}
},
});
{
"component": true,
"usingComponents": {
"prize-dialog": "/components/dialog/prize-dialog/prize-dialog"
}
}
\ No newline at end of file
.rank-form-item {
width: 100%;
}
\ No newline at end of file
<view>
<view>排行榜配置<view>
<form style="margin: 20px 0 0 0" data-name="form" onChange="onChange" inline="true">
<form-item class="rank-form-item" label="周期设置" >
<radio-group dataSource="{{periodConfig}}" onChange="onChangeHeihei" />
</form-item>
<form-item class="rank-form-item" label="最近期时间" >
<tb-range-picker
onChange="onTimeChange"
disableStartTime="{{false}}"
/>
</form-item>
<form-item class="rank-form-item" label="持续期次" >
<radio-group dataSource="{{lastedPeriodConfig}}" onChange="onChangeHeihei" />
</form-item>
<form-item class="rank-form-item" label="剩余期次" >
<input addonTextBefore="包含最近期共" addonTextAfter="期" />
</form-item>
<form-item class="rank-form-item" label="开奖时间" >
<input style="width: 160px" addonTextBefore="榜单截止后第" addonTextAfter="天" />
</form-item>
</view>
\ No newline at end of file
Component({
mixins: [],
data: {
periodConfig: [
{
label: '自然周',
value: 'natural',
},
{
label: '自定义',
value: 'custom',
}
],
lastedPeriodConfig: [
{
label: '自动循环',
value: 'autoLoop',
},
{
label: '自定义',
value: 'custom',
}
]
},
props: {},
didMount() {},
didUpdate() {},
didUnmount() {},
methods: {
onChange(e) {
console.log(e)
}
},
});
{
"component": true,
"usingComponents": {
"tb-range-picker": "/components/form/tb-range-picker/tb-range-picker"
}
}
\ No newline at end of file
.tb-rank-table-container {
width: 600px;
}
.tb-rank-table-add {
border-bottom: 1px solid #ddd;
display: flex;
align-items: center;
justify-content: center;
height: 50px;
background: #fff;
color: #3386F1;
}
.tb-rank-table-add_icon {
width: 12px;
height: 12px;
border: 1px solid #3386F1;
border-radius: 50%;
color: #3386F1;
display: flex;
justify-content: center;
align-items: center;
margin-right: 10px;
}
.tb-rank-table-operate {
color: #3386F1;
font-size: 12px;
margin: 0 4px 0 0;
}
.tb-rank-table-prize-image {
display: flex;
align-items: center;
}
.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;
}
.tb-rank-table-add_disabled {
height: 54px;
line-height: 54px;
border-left: 1px solid #D7DBE0;
border-bottom: 1px solid #D7DBE0;
border-right: 1px solid #D7DBE0;
color: #D7DBE0;
}
.tb-rank-table-add_disabled .tb-rank-table-add_icon {
color: #D7DBE0;
border: 1px solid #D7DBE0;
}
/* 表格样式覆盖 */
.tb-rank-table .next-table-header .next-table-cell-wrapper {
height: 50px;
line-height: 28px;
}
/* 表格样式覆盖 */
\ No newline at end of file
<view class="tb-rank-table-container" style="border: 1px solid #ddd;border-bottom:none;margin: 10px 0 0 0;">
<table dataSource="{{prizeInfoList}}"hasBorder="{{false}}" class="tb-rate-table">
<table-column title="名次" dataIndex="rank">
<view slot-scope="x">{{
x.value.split("-").length === 2 ?
(x.value.split("-")[0] !== x.value.split("-")[1]? '第 ' +x.value.split("-")[0]+'-'+x.value.split("-")[1] +' 名':'第 ' + x.value.split("-")[1] +' 名')
:
`第 ${x.value} 名`
}}</view>
</table-column>
<table-column title="奖品信息" dataIndex="image">
<view class="tb-rank-table-prize-image" slot-scope="x">
<view a:if="{{x.record.image}}">
<image style="width:42px;height:42px;margin: 0 6px 0 0" src="{{x.record.image}}"></image>
</view>
<view>{{x.record.name}} </view>
</view>
</table-column>
<table-column title="操作">
<text class="tb-rank-table-operate" onTap="onEditPrize" data-x="{{x}}" data-name="edit" slot-scope="x">编辑</text>
<text slot-scope="x" class="tb-rank-table-operate edit-content-formItem-delete" onTap="handleDeleteClick" data-x="{{x}}" slot-scope="x">删除</text>
</table-column>
</table>
<view class="tb-rank-table-add {{prizeInfoList.length >= limit && 'tb-rank-table-add_disabled'}}" onTap="addPrize">
<view class="tb-rank-table-add_icon"><icon size="xxs" type="add"/></view>
添加奖品 ({{prizeInfoList.length}}/{{limit}})
</view>
</view>
<prize-dialog
a:if="{{dialogVisible}}"
visible="{{dialogVisible}}"
isEdit="{{isEdit}}"
prizeDialogData="{{prizeDialogData}}"
onClose="onCloseDialog"
type="rank"
onUpdate="onPrizeUpdate"
onAdd="onPrizeAdd"/>
\ No newline at end of file
Component({
mixins: [],
data: {
prizeInfoList: [
// {
// _id: "2321321",
// image: "https://img.alicdn.com/imgextra/i2/742982950/O1CN01zuwaKU1Xf905ZgMno_!!742982950-2-miniprogram.png",
// name: "一等奖大礼包",
// type: 2,
// limitStock: 0,
// rank: '1',
// },
// {
// _id: "2321321",
// image: "https://img.alicdn.com/imgextra/i2/742982950/O1CN01zuwaKU1Xf905ZgMno_!!742982950-2-miniprogram.png",
// name: "二等奖大礼包",
// type: 2,
// limitStock: 0,
// rank: '2-2',
// },
// {
// _id: "2321321",
// image: "https://img.alicdn.com/imgextra/i2/742982950/O1CN01zuwaKU1Xf905ZgMno_!!742982950-2-miniprogram.png",
// name: "三等奖大礼包",
// type: 2,
// limitStock: 0,
// rank: '3-3',
// },
// {
// _id: "2321321",
// image: "https://img.alicdn.com/imgextra/i2/742982950/O1CN01zuwaKU1Xf905ZgMno_!!742982950-2-miniprogram.png",
// name: "大礼包",
// type: 2,
// limitStock: 0,
// rank: '4-100',
// }
],
dialogVisible: false,
isEdit: false,
prizeDialogData: {}
},
props: {
limit: 20
},
didMount() {},
didUpdate() {},
didUnmount() {},
methods: {
// 格式化排名信息
formatRank(rank) {
return rank
},
// 删除奖品
handleDeleteClick(e) {
const { index } = e.target.dataset.x;
this.setData({ prizeInfoList: this.data.prizeInfoList.filter(((i, k) => k !== index)) })
},
addPrize() {
const { prizeInfoList } = this.data;
const { limit } = this.props;
if (prizeInfoList.length >= limit) {
my.showToast({ type: 'fail', content: `最多创建${limit}个奖励配置` });
return;
}
this.setData({
dialogVisible: true,
isEdit: false,
prizeDialogData: {}
})
},
onEditPrize(e) {
const { name, x } = e.target.dataset;
console.log(x)
this.setData({
prizeDialogData: x,
isEdit: true,
dialogVisible: true
})
},
onCloseDialog() {
this.setData({ dialogVisible: false })
},
onPrizeUpdate(data, index) {
const updatedData = this.data.prizeInfoList.map((v, i) => {
if(index === i) return {...v, ...data}
return v;
})
this.setData({
prizeInfoList: updatedData
})
},
onPrizeAdd(data) {
const { prizeInfoList } = this.data;
this.setData({
prizeInfoList: [...prizeInfoList, data]
})
}
},
});
{
"component": true,
"usingComponents": {
"prize-dialog": "/components/dialog/prize-dialog/prize-dialog"
}
}
\ No newline at end of file
<view class="url-task">
<button type="primary" text="{{true}}" onTap="onOpenDialog" >{{taskData.title || '编辑任务'}}</button>
<task-dialog visible="{{visible}}" onClose="onClose" onUpdate="onUpdate" unit="炸弹" noLink="{{noLink}}"/>
</view>
Component({
mixins: [],
data: {
visible: false,
taskData: {
title: ''
},
},
props: {
noLink: false
},
didMount() {},
didUpdate() {},
didUnmount() {},
methods: {
onClose() {
this.setData({
visible: false
})
},
onOpenDialog() {
this.setData({
visible: true
})
},
onUpdate(taskData) {
this.setData({
taskData
})
}
},
});
{
"component": true,
"usingComponents": {
"task-dialog": "/components/dialog/task-dialog/task-dialog"
}
}
\ No newline at end of file
export default { export default {
// ams:ams接口,cloud: 云函数 // ams:ams接口,cloud: 云函数
requestType: 'cloud', requestType: "ams",
// app环境 // app环境
env: 'test', env: "online", // online 线上 test 测试
// 线上活动地址 // 默认云函数名称
activityURL: 'https://m.duanqu.com/?_ariver_appid=3000000002099934&nbsv=1.0.3&nbsource=debug&nbsn=TRIAL', cloudFnName: 'backstage',
// 配置的商家应用appID, 权益插件用 // 小程序appId
bizCode: '3000000003498711' appId: "3000000002099934",
}
\ No newline at end of file // 配置应用appID, 权益插件用
bizCode: "3000000003498711",
};
{
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@tbmp/mp-cloud-sdk": {
"version": "1.1.4",
"resolved": "https://registry.npm.taobao.org/@tbmp/mp-cloud-sdk/download/@tbmp/mp-cloud-sdk-1.1.4.tgz",
"integrity": "sha1-DMuRzXfRfFLNbwyxWENoz9/Rqd0="
},
"async-validator": {
"version": "3.3.0",
"resolved": "https://registry.npm.taobao.org/async-validator/download/async-validator-3.3.0.tgz",
"integrity": "sha1-HZIZO75g1tbIskZpLHAF6e0UqO4="
},
"dayjs": {
"version": "1.8.28",
"resolved": "https://registry.npm.taobao.org/dayjs/download/dayjs-1.8.28.tgz",
"integrity": "sha1-N6piAd9IPQiWRctsj2zvbwxNvAc="
},
"miniapp-router": {
"version": "0.1.7",
"resolved": "https://registry.npm.taobao.org/miniapp-router/download/miniapp-router-0.1.7.tgz",
"integrity": "sha1-6SmJV0y7DPEXk9YlCNBBP+FluUg="
},
"moment": {
"version": "2.24.0",
"resolved": "http://registry.npm.taobao.org/moment/download/moment-2.24.0.tgz",
"integrity": "sha1-DQVdU/UFKqZTyfbraLtdEr9cK1s="
}
}
}
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@tbmp/mp-cloud-sdk": "1.1.4", "@tbmp/mp-cloud-sdk": "1.1.4",
"async-validator": "^3.3.0",
"dayjs": "^1.8.28",
"miniapp-router": "^0.1.6", "miniapp-router": "^0.1.6",
"moment": "^2.24.0" "moment": "^2.24.0"
} }
......
...@@ -5,7 +5,9 @@ ...@@ -5,7 +5,9 @@
font-size: 12px; font-size: 12px;
font-weight: 400; font-weight: 400;
color: rgba(153, 153, 153, 1); color: rgba(153, 153, 153, 1);
}
.edit-title_my {
color:rgba(51,134,241,1);
} }
.edit-breadcrumb { .edit-breadcrumb {
...@@ -18,294 +20,18 @@ ...@@ -18,294 +20,18 @@
font-weight: 900; font-weight: 900;
} }
.edit-tip-icon {
width: 14px;
height: 14px;
margin-left: -10px;
}
.edit-content { .edit-content {
padding: 20px; padding: 20px;
} }
.edit-content-form-item {
.edit-content-title { width: 100%;
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 { .submit-btn {
margin: 20px 0 0 80px; margin: 20px 0 0 80px;
} }
.submit-btn Button { .submit-btn Button {
margin-left: 20px; margin-left: 20px;
} }
\ No newline at end of file
.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-container">
<view class="edit-breadcrumb"> <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> <text class="edit-title edit-title_my" onTap="backList">我的活动</text>
<text class="edit-title edit-title-separate">/</text>
<text class="edit-title">{{activityId ? '编辑' : '新建'}} 「 店铺漂流记 」 活动</text>
</view> </view>
<view class="edit-content"> <view class="edit-content">
<form class="edit-content-form" inline="true" labelTextAlign="right" size="large" labelCol="{{labelCol}}"> <form class="edit-content-form" inline="true" labelTextAlign="right" labelCol="{{labelCol}}">
<view class="edit-content-title">基础配置</view> <tb-label title="基础配置"/>
<form-item labelTextAlign="right" style="width:100%" validateState="{{timeRangeTipInfo.status}}" help="{{timeRangeTipInfo.content}}" class="edit-content-formItem" label="活动时间" asterisk="{{false}}"> <form-item
<range-picker style="width: 400px" onVisibleChange="dialogChange" hasClear="true" value="{{timeRange}}" data-time="{{timeRange}}" onChange="pickerChange" show-time="{{ format: 'HH:mm' }}" class="block" /> class="edit-content-form-item"
label="活动时间"
validateState="{{formState.timeRange.status}}"
help="{{formState.timeRange.message}}"
asterisk="{{false}}">
<tb-range-picker
timeRange="{{timeRange}}"
originalStartTime="{{originalStartTime}}"
onChange="onTimeChange"
disableStartTime="{{isStart}}"
tips="demo"
/>
</form-item> </form-item>
<form-item
validateState="{{formState.title.status}}"
help="{{formState.title.message}}"
class="edit-content-form-item"
label="活动名称"
asterisk="{{false}}">
<tb-input
placeholder="请输入活动名称"
maxLength="12"
value="{{title}}"
dataName="title"
hasLimitHint="{{true}}"
onChange="onChange"
/>
</form-item>
<form-item
validateState="{{formState.subtitle.status}}"
help="{{formState.subtitle.message}}"
class="edit-content-form-item"
label="活动副标题"
asterisk="{{false}}">
<tb-input
placeholder="请输入活动副标题"
maxLength="16"
value="{{subtitle}}"
dataName="subtitle"
hasLimitHint="{{true}}"
onChange="onChange"
/>
</form-item>
<tb-label title="奖品配置"/>
<form-item
class="edit-content-form-item"
label="奖品配置"
asterisk="{{false}}">
<rank-table/>
</form-item>
<tb-label title="任务配置"/>
<form-item
class="edit-content-form-item"
label="关注店铺"
asterisk="{{false}}">
<tb-config-input
textBefore="用户关注店铺后,获得"
textAfter="次参与机会,每个用户永久一次"
validateRange="{{[1, 99]}}"
value="{{limitValue}}"
onChange="onConfigInputChange",
placeholder="1-99"
/>
</form-item>
<form-item
class="edit-content-form-item"
label="成为会员"
asterisk="{{false}}">
<tb-config-input
textBefore="用户成为会员后,获得"
textAfter="次参与机会,每个用户永久一次"
validateRange="{{[1, 99]}}"
value="{{limitValue}}"
onChange="onConfigInputChange",
placeholder="1-999"
/>
</form-item>
<form-item
class="edit-content-form-item"
label="邀请好友"
asterisk="{{false}}">
<task-config noLink="{{true}}" type="inviteFriends"/>
</form-item>
<form-item
class="edit-content-form-item"
label="浏览指定页面"
asterisk="{{false}}">
<task-config type="jumpLink"/>
</form-item>
<form-item
validateState="{{formState.logoImage.status}}"
help="{{formState.logoImage.message}}"
class="edit-content-form-item"
label="logo图片"
asterisk="{{false}}">
<tb-image-upload/>
</form-item>
<tb-label title="邀请配置"/>
<form-item
validateState="{{formState.commandTitle.status}}"
help="{{formState.commandTitle.message}}"
class="edit-content-form-item"
label="邀请淘口令名称"
asterisk="{{false}}">
<tb-input
placeholder="请输入邀请者淘口令名称"
maxLength="12"
value="{{commandTitle}}"
dataName="commandTitle"
hasLimitHint="{{true}}"
onChange="onChange"
/>
</form-item>
<form-item
validateState="{{formState.beenInvitedText.status}}"
help="{{formState.beenInvitedText.message}}"
class="edit-content-form-item"
label="被邀请者文案"
asterisk="{{false}}">
<tb-input
placeholder="请输入被邀请者文案"
maxLength="12"
value="{{beenInvitedText}}"
dataName="beenInvitedText"
hasLimitHint="{{true}}"
onChange="onChange"
/>
</form-item>
<form-item
validateState="{{formState.commandImg.status}}"
help="{{formState.commandImg.message}}"
class="edit-content-form-item"
label="淘口令图片"
asterisk="{{false}}">
<tb-image-upload/>
</form-item>
<view class="submit-btn">
<button onTap="onSubmit" type="primary">确定</button>
<button>取消</button>
</view>
</form> </form>
<view class="submit-btn" a:if="{{!isEnd}}">
<button onTap="submitActive" type="primary">确定</button>
<button onTap="backToActivityList">取消</button>
</view>
</view>
</view> </view>
\ No newline at end of file
import moment from 'moment'; import moment from 'moment'
import { getActivityDetail, saveActivityInfo } from '../../../api'; import schema from 'async-validator';
import { activityType } from '../const'; import { descriptor, formatValidator } from './validate';
import { addFloat } from '../../../utils/helper'; import {
getActivityDetail,
saveActivityInfo
} from '/api'
import {
addFloat
} from '/utils/helper'
const THANKS_TYPE = 5
var validator = new schema(descriptor);
/**
*
* 1.任务相关
* 体力值 power
* 参与次数 gameTimes
* 粮食(牧草、水滴等) food
* 金币 coins
* 道具(炸弹、复活卡等) tools
*
*/
Component({ Component({
data: { data: {
activityInfos: {},
timeRange: [],
startTime: '',
endTime: '',
labelCol: { labelCol: {
fixedSpan: 5 fixedSpan: 5
} },
id: '',
title: '',
subtitle: '',
startTime: '',
endTime: '',
timeRange: [],
rule: '',
isEnd: false,
isStart: false,
originalStartTime: '',
limitValue: '',
prizeInfoList: [],
prizeDialogData: {},
commandTitle: '',
beenInvitedText: '',
formState: formatValidator(validator.rules)
}, },
props: {}, props: {},
didMount() { didMount() {
const { id } = this.$page.$router.params; const { id } = this.$page.$router.params
if (id || id === 0) { if (id) {
this.getActivityInfo(id); this.getActivityInfo(id)
} else {
this.setDefaultTime();
} }
}, },
methods: { 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活动信息 // 获取id活动信息
async getActivityInfo(activityId) { async getActivityInfo(activityId) {
try { try {
const { success, data, message } = await getActivityDetail({ activityId, activityType }); const { success, data, message } = await getActivityDetail({ activityId })
if (!success) { if (!success) {
this.showFailToast(message); this.showFailToast(message);
return; return;
} }
let { prizeInfoList, ...rest } = data; 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({ this.setData({
activityInfos: data ...rest,
timeRange: [+rest.startTime, +rest.endTime],
originalStartTime: +rest.startTime,
isStart: +rest.startTime < Date.now(),
isEnd: +rest.endTime < Date.now(),
prizeInfoList: prizeInfoList && prizeInfoList.length > 0 ? prizeInfoList : this.data.prizeInfoList
}) })
} catch (error) { } catch (error) {
console.log(err, 'err') console.log(error, 'err')
}
},
onChange(e) {
const { value } = e.detail;
const { name } = e.target.dataset;
this.setData({
[name]: value
})
},
onConfigInputChange(e) {
this.setData({
limitValue: e.detail.value
})
},
onTimeChange(timeRange, error) {
const { formState } = this.data;
this.setData({
timeRange,
startTime: timeRange[0],
endTime: timeRange[1],
formState: {
...formState,
timeRange: {
status: error ? 'error' : 'success',
message: error || ''
}
}
})
},
validateForm(data, target) {
const _this = this;
return new Promise((resolve, reject) => {
validator.validate(data, (errors, fields) => {
const { formState } = _this.data;
_this.setFormTips(formState, errors, target)
if(errors) {
resolve(false);
} else {
resolve(true);
}
// validation passed
});
})
},
setFormTips(formState, errors, target) {
errors = errors || []
let newValidator = {};
Object.keys(formState).forEach(key => {
let error = errors.filter(v => target ? v.field === target && v.field === key : v.field === key).length && errors.filter(v => v.field === key)[0];
// 时间实时校验
if(key === 'timeRange' && formState[key].status === 'error') {
return
}
newValidator[key] = {
status: error ? 'error' : 'success',
message: error ? error.message : ''
}
})
this.setData({
formState: newValidator
})
},
// 提交信息
async onSubmit() {
const {
id,
activityId,
startTime,
endTime,
rule,
prizeInfoList,
joinTimesEveryday,
joinNeedCredits
} = this.data
const isValidForm = await this.validateForm(this.data);
if(!isValidForm) return;
console.log('成功')
return;
if (!prizeInfoList.length) {
this.setDataTips('prizeTipsInfo', 'error', '请至少添加一个奖品配置')
}
if (prizeInfoList.length) {
let totalPercent = prizeInfoList.reduce((total, next) => {
return total = addFloat(total, +next.probablity)
}, 0)
if (totalPercent > 100) {
this.setDataTips('prizeTipsInfo', 'error', '奖品的中奖概率相加不能超过100')
} else {
this.setDataTips('prizeTipsInfo', 'success', '')
}
}
if (joinTimesEveryday !== '' && joinTimesEveryday >= 0 && joinTimesEveryday <= 99 && Number.isInteger(+joinTimesEveryday)) {
this.setDataTips('joinTimesEverydayTipsInfo', 'success', '')
} else {
this.setDataTips('joinTimesEverydayTipsInfo', 'error', '请输入正确的每日可参与次数(0-99)')
}
if (joinNeedCredits !== '' && joinNeedCredits > 0 && joinNeedCredits <= 999 && Number.isInteger(+joinNeedCredits)) {
this.setDataTips('joinNeedCreditsTipsInfo', 'success', '')
} else {
this.setDataTips('joinNeedCreditsTipsInfo', 'error', '请输入正确的购买盲盒消耗积分(1-999)')
}
this.setData({
ruleTipsInfo: !rule
})
const {
prizeTipsInfo,
ruleTipsInfo,
timeRangeTipInfo,
joinTimesEverydayTipsInfo,
joinNeedCreditsTipsInfo
} = this.data
if (
prizeTipsInfo.status === "error" ||
ruleTipsInfo ||
timeRangeTipInfo.status === 'error' ||
joinTimesEverydayTipsInfo.status === 'error' ||
joinNeedCreditsTipsInfo.status === 'error'
) {
this.showFailToast('请检查信息是否全部填写正确')
return
} else {
const params = this.formatActivityParams(this.data)
saveActivityInfo(params)
.then(res => {
if (res.success) {
this.goToActivityList()
} else {
this.showFailToast(res.message)
}
}).catch(err => {
console.log(err)
})
}
},
showFailToast(text) {
my.showToast({
type: 'fail',
content: text
})
},
// 格式化参数
formatActivityParams({
activityId,
startTime,
endTime,
rule,
joinTimesEveryday,
joinNeedCredits,
prizeInfoList
}) {
// 补足谢谢参与类型
let totalPercent = prizeInfoList.reduce((total, next) => {
return total = addFloat(total, +next.probablity)
}, 0)
let prizeInfoListCopy = prizeInfoList.concat()
if (totalPercent < 100) {
let thanksType = {
type: THANKS_TYPE,
name: '谢谢参与',
probablity: 100 - totalPercent
}
prizeInfoListCopy.push(thanksType)
}
return {
activityId,
startTime: new Date(startTime).getTime(),
endTime: new Date(endTime).getTime(),
rule,
prizeInfoList: prizeInfoListCopy.map((v, index) => ({ ...v, level: index })),
joinTimesEveryday,
joinNeedCredits
} }
},
cancleEdit() {
this.goToActivityList()
},
onInputChange(e) {
let { detail: { value },
currentTarget: { dataset }
} = e
let { name } = dataset
this.setData({ [name]: value })
},
// 选择奖品弹窗
handlerShowPrize(evt) {
const {
prizeInfoList
} = this.data
let {
name
} = evt.target.dataset
if (name === 'edit') {
this.setData({
showPrize: true,
prizeDialogData: evt.target.dataset.x,
prizeDialogEdit: true,
hasEditPrize: true
})
} else {
if (prizeInfoList.length >= 20) {
this.showFailToast('最多创建20个奖励配置')
return false
}
this.setData({
showPrize: true,
prizeDialogData: {},
prizeDialogEdit: false,
hasEditPrize: false
})
}
},
onCloseDialog(data) {
switch (data) {
case "group":
this.setData({
showGroup: false
})
return
case "prize":
this.setData({
showPrize: false,
prizeDialogData: {}
})
return
case "baby":
this.setData({
showBaby: false
})
return
case "link":
this.setData({
showLink: false
})
return
}
},
backList() {
this.$page.$router.push("/activity/list");
},
// 更新奖品名次
onUpdateLevel(data, type) {
let dataSourceCopy
if (type || type === 0) {
dataSourceCopy = this.data.prizeInfoList.reduce((s, i, index) => {
if (index === type) {
return s = [...s, data]
} else {
return s = [...s, i]
}
}, [])
} else {
dataSourceCopy = [].concat(this.data.prizeInfoList, data)
}
this.setData({
prizeInfoList: dataSourceCopy
})
} }
} },
}); })
\ No newline at end of file \ No newline at end of file
{ {
"component": true, "component": true,
"usingComponents": { "usingComponents": {
"prize-dialog": "/components/prize-dialog/prize-dialog" "tb-label": "/components/form/tb-label/tb-label",
"tb-input": "/components/form/tb-input/tb-input",
"tb-config-input": "/components/form/tb-config-input/tb-config-input",
"tb-range-picker": "/components/form/tb-range-picker/tb-range-picker",
"tb-rule": "/components/form/tb-rule/tb-rule",
"probability-prize-table": "/components/prize/probability-prize-table/probability-prize-table",
"list-table": "/components/list/list-table/list-table",
"rank-config": "/components/rank/rank-config/rank-config",
"rank-table": "/components/rank/rank-table/rank-table",
"task-config": "/components/task/task-config/task-config",
"tb-image-upload": "/components/form/tb-image-upload/tb-image-upload"
} }
} }
\ No newline at end of file
export const descriptor = {
title: {
required: true,
validator: (rule, value) => !!value && value.length <= 12,
message: "请输入正确的活动名称"
},
subtitle: {
required: true,
validator: (rule, value) => !!value && value.length <= 16,
message: "请输入正确的活动副标题"
},
timeRange: {
required: true
}
};
export const formatValidator = descriptor => {
const validators = {}
Object.keys(descriptor).forEach(key => {
validators[key] = {
status: 'success',
message: ''
}
});
return validators
}
\ No newline at end of file
...@@ -2,36 +2,4 @@ ...@@ -2,36 +2,4 @@
<text class="db-title">活动管理</text> <text class="db-title">活动管理</text>
<button type="primary" onTap="tapname">创建活动</button> <button type="primary" onTap="tapname">创建活动</button>
</view> </view>
<view class="list-container"> <list-table/>
<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 { cloud } = getApp();
const { function: fc } = cloud; const { function: fc } = cloud;
Component({ 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: {}, props: {},
didMount() {
this.getList();
},
methods: { 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() { tapname() {
this.$page.$router.push('/activity/add') this.$page.$router.push("/activity/add");
} },
}, },
}); });
\ No newline at end of file
{ {
"component": true, "component": true,
"usingComponents": { "usingComponents": {
"confirm-dialog": "/components/confirm-dialog/confirm-dialog" "list-table": "/components/list/list-table/list-table"
} }
} }
\ No newline at end of file
.cmp-content {
padding: 100px 40px 40px;
}
.hr {
margin: 20px 0 20px;
}
\ No newline at end of file
<view class="cmp-content">
<tb-image-upload/>
<view class="hr"></view>
<task-config/>
<view class="hr"></view>
<rank-table/>
<view class="hr"></view>
<rank-config/>
<view class="hr"></view>
<tb-range-picker
defaultStartTime="{{activityInfo.startTime}}"
defaultEndTime="{{activityInfo.endTime}}"
originalStartTime="{{activityInfo.originalStartTime}}"
onChange="onTimeChange"
disableStartTime="{{true}}"
tips="432"
/>
<view class="hr"></view>
<tb-input
placeholder="测试生死时速"
maxLength="12"
value="{{title}}"
hasLimitHint="{{true}}"
onChange="onChange"
tips="fdsfsfs"
/>
<view class="hr"></view>
<tb-config-input
textBefore="每日赠送"
textAfter="次免费参与机会"
validateRange="{{[1, 999]}}"
value="{{limitValue}}"
onChange="onConfigInputChange",
placeholder="1-99"
/>
<view class="hr"></view>
<tb-rule value="{{rule}}" onChange="onRuleChange"></tb-rule>
<view class="hr"></view>
<probability-prize-table/>
<view class="hr"></view>
<list-table/>
<view class="hr"></view>
</view>
\ No newline at end of file
Page({
data: {
title: '',
limitValue: '',
rule: '',
activityInfo: {
startTime: '',
endTime: '',
originalStartTime: ''
},
probabilityRrizeRialogVisible: false,
},
onChange(e) {
this.setData({
title: e.detail.value
})
},
onConfigInputChange(e) {
this.setData({
limitValue: e.detail.value
})
},
onRuleChange(e) {
this.setData({
rule: e.detail.value
})
},
onTimeChange(time) {
console.log(time);
const { activityInfo } = this.data;
activityInfo.startTime = time[0];
activityInfo.endTime = time[1]
this.setData({
activityInfo: {
...activityInfo
}
})
},
onLoad() {
let activityInfo = {
startTime: 1591340703158,
endTime: 1592340433145
}
setTimeout(() => {
activityInfo.originalStartTime = activityInfo.startTime;
this.setData({
activityInfo
})
}, 2000);
},
});
{
"usingComponents": {
"tb-input": "/components/form/tb-input/tb-input",
"tb-config-input": "/components/form/tb-config-input/tb-config-input",
"tb-range-picker": "/components/form/tb-range-picker/tb-range-picker",
"tb-rule": "/components/form/tb-rule/tb-rule",
"probability-prize-table": "/components/prize/probability-prize-table/probability-prize-table",
"list-table": "/components/list/list-table/list-table",
"rank-config": "/components/rank/rank-config/rank-config",
"rank-table": "/components/rank/rank-table/rank-table",
"task-config": "/components/task/task-config/task-config",
"tb-image-upload": "/components/form/tb-image-upload/tb-image-upload"
}
}
\ No newline at end of file
const { cloud } = getApp(); const { cloud } = getApp();
import routerInit from 'miniapp-router'; import routerInit from 'miniapp-router';
import routerConfig, { basePath } from '../../router/index' import routerConfig, { basePath } from '../../router/index'
import { sellerSave } from '../../api'; import { sellerSave } from '/api';
...@@ -16,7 +16,6 @@ Page({ ...@@ -16,7 +16,6 @@ Page({
logo: '//yun.duiba.com.cn/duiba_miniProgram/logo.png' logo: '//yun.duiba.com.cn/duiba_miniProgram/logo.png'
}, },
menu: [{ menu: [{
name: '活动管理',
key: 'activity', key: 'activity',
title: '活动管理', title: '活动管理',
path: '/activity/list' path: '/activity/list'
...@@ -46,7 +45,6 @@ Page({ ...@@ -46,7 +45,6 @@ Page({
}); });
// 页面加载 // 页面加载
routerInit.call(this, routerConfig); routerInit.call(this, routerConfig);
// console.info(`Page onLoad with query: ${JSON.stringify(routerConfig)}`);
}, },
onPageScroll: function (e) { // 获取滚动条当前位置 onPageScroll: function (e) { // 获取滚动条当前位置
this.setData({ this.setData({
...@@ -58,5 +56,33 @@ Page({ ...@@ -58,5 +56,33 @@ Page({
activeKey: key activeKey: key
}); });
this.$router.push(path) this.$router.push(path)
},
onChange({
detail: {
value
}
}) {
console.log('************$router', value)
if (value === '/activity') {
value = '/activity/list';
}
if (value === "/help") {
this.setData({
showCopyVisible: true
})
return false;
}
this.$router.push(value);
this.setData({
activeKey: value
})
},
onActiveKeyChange(event) {
const {
path
} = event.target.dataset;
this.setData({
activeKey: path
})
} }
}); });
\ No newline at end of file
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
"usingComponents": { "usingComponents": {
"router-view": "miniapp-router/router-view/router-view", "router-view": "miniapp-router/router-view/router-view",
"base": "/pages/activity/base/base", "base": "/pages/activity/base/base",
"layout": "/components/base/layout/layout" "layout": "/components/basic/layout/layout"
}, },
"plugins": { "plugins": {
"myPlugin": { "myPlugin": {
......
export const basePath = `/activity/list`; export const basePath = `/activity/list`;
export default { export default {
routes: [{ routes: [
path: '/activity', {
component: 'activity', path: "/activity",
component: "activity",
children: [ children: [
{ {
path: '/base', path: "/base",
component: 'base' component: "base",
}, },
{ {
path: '/list', path: "/list",
component: 'list' component: "list",
}, },
{ {
path: '/add', path: "/add",
component: 'add' component: "add",
}, },
{ {
path: '/edit/:id', path: "/edit/:id",
component: 'add' component: "add",
} },
] ],
} }
], ],
option: { option: {
initPath: basePath, initPath: basePath,
} },
} };
\ No newline at end of file
...@@ -2,9 +2,8 @@ const { ...@@ -2,9 +2,8 @@ const {
cloud cloud
} = getApp(); } = getApp();
import { requestType } from '../config'; import { requestType, cloudFnName } from '../config';
const REQUEST_PREFIX = 'backstage';
const request = (url, method, params, ext = {}) => { const request = (url, method, params, ext = {}) => {
const { const {
isShowLoading isShowLoading
...@@ -20,7 +19,7 @@ const request = (url, method, params, ext = {}) => { ...@@ -20,7 +19,7 @@ const request = (url, method, params, ext = {}) => {
const requestAms = () => { const requestAms = () => {
return my.request({ return my.request({
url: `https://ams.dui88.com/server/index.php?g=Web&c=Mock&o=simple&projectID=218&uri=${REQUEST_PREFIX}.${url}`, url: `https://ams.dui88.com/server/index.php?g=Web&c=Mock&o=simple&projectID=218&uri=${cloudFnName}.${url}`,
method, method,
data: params, data: params,
dataType: 'json' dataType: 'json'
...@@ -33,7 +32,7 @@ const request = (url, method, params, ext = {}) => { ...@@ -33,7 +32,7 @@ const request = (url, method, params, ext = {}) => {
} }
const requestCloud = () => { const requestCloud = () => {
return cloud.function.invoke(REQUEST_PREFIX, params, url).then(res => { return cloud.function.invoke(cloudFnName, params, url).then(res => {
hideMyLoading(); hideMyLoading();
return res; return res;
}).catch(() => { }).catch(() => {
......
{
"runtime": "nodejs8",
"version": "1.0"
}
\ No newline at end of file
// B端涉及的数据库
const ACTIVITY_BASE_CONFIG = "activity_base_config";
const ACTIVITY_PRIZE_CONFIG = "activity_prize_config";
const ACTIVITY_SELLER_SAVE = "activity_seller_save";
const ACTIVITY_INSTANCE = "miniapp_instantiate";
// C端涉及的数据库
const RANK_OPEN_PRIZE = "rank_open_prize";
const RANK_SCAN = "rank_scan";
const RANK_SCORE = "rank_score";
module.exports = {
ACTIVITY_BASE_CONFIG,
ACTIVITY_PRIZE_CONFIG,
ACTIVITY_SELLER_SAVE,
ACTIVITY_INSTANCE,
RANK_OPEN_PRIZE,
RANK_SCAN,
RANK_SCORE,
};
const ActivityConfigService = require("../service/activityconfig.service");
const { CODE_TYPES } = require("../utils/constants");
const ResultsModel = require("../utils/results.model");
let resultsModel = new ResultsModel();
const { DELETE_STATUS } = require("../utils/constants");
const ActivitySellerService = require("../service/activityseller.service");
let ActivityTopService = require("../service/activitytop.service");
let RankScanService = require("../service/rankscan.service");
let ActivityInstanceService = require("../service/activityinstance.service");
const { loginfo, passUrlList } = require("../utils/utils");
const Url = require("url");
//openId获取活动列表
const getActivityList = async function (context) {
loginfo(context, "getActivityListByOpenId");
const { openId } = context;
let ActivityConfig = new ActivityConfigService(context);
let ActivityInstance = new ActivityInstanceService(context);
let ActivitySeller = new ActivitySellerService(context);
try {
let sellInfo = await ActivitySeller.findSellerInfo(openId);
console.log(sellInfo, "sellInfo");
if (sellInfo && sellInfo[0]) {
let result = await ActivityConfig.getActivityListByOpenId(
sellInfo && sellInfo[0].shopId,
context.data
);
let instanceInfo = await ActivityInstance.getMiniAppInstanceInfo(
context.openId
);
if (result) {
result.list.map((item) => {
item.activityId = item._id;
item.onlineUrl =
instanceInfo && instanceInfo[0] ? instanceInfo[0].online_url : "";
return item;
});
return resultsModel.success(result);
}
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, "查询活动列表失败");
} else {
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, "小程序未授权");
}
} catch (e) {
console.log("catch", "查询活动列表失败");
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, "查询活动列表失败");
}
};
//保存或编辑活动信息
const saveActivityInfo = async function (context) {
loginfo(context, "saveActivityInfoByHasId");
let ActivityConfig = new ActivityConfigService(context);
let ActivityTop = new ActivityTopService(context);
let nowTime = Date.now();
const { openId } = context;
let {
title,
subtitle,
activityId = "",
startTime,
endTime,
specifyPageUrl = "",
rule,
browseItemIds = "",
prizeInfoList = [],
attentionStore = false,
beMembership = false,
inviteFriends = false,
} = context.data;
let initialData;
if (activityId) {
initialData = await ActivityConfig.getActivityInfoByActivityId(activityId);
}
if (!title) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, "活动名称为空");
} else if (title.length > 12) {
return resultsModel.error(
CODE_TYPES.PARAMS_ERROR,
"活动名称不可超过12个字"
);
}
if (!subtitle) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, "活动副标题为空");
} else if (subtitle.length > 16) {
return resultsModel.error(
CODE_TYPES.PARAMS_ERROR,
"活动副标题不可超过16个字"
);
}
if (
!attentionStore &&
!beMembership &&
!inviteFriends &&
browseItemIds.length === 0 &&
specifyPageUrl.length === 0
) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, "任务配置至少选择一项");
} else if (browseItemIds.split(",").length > 20) {
return resultsModel.error(
CODE_TYPES.PARAMS_ERROR,
"浏览宝贝最多选择20个商品"
);
}
if (specifyPageUrl) {
const specifyPageUrlParse = Url.parse(specifyPageUrl);
let urlHasPass = false;
passUrlList.forEach((item) => {
if (~specifyPageUrlParse.host.indexOf(item)) {
urlHasPass = true;
}
});
console.log(specifyPageUrlParse.protocol, urlHasPass);
if (!specifyPageUrlParse.protocol || !urlHasPass) {
return resultsModel.error(
CODE_TYPES.PARAMS_ERROR,
"浏览链接域名校验不通过"
);
}
}
if (prizeInfoList.length === 0) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, "奖品至少配置3名");
} else if (prizeInfoList.length > 8) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, "奖品最多配置8个档位");
} else {
let canPassPrize = true;
prizeInfoList.forEach((item) => {
if (!item.ename || !item.rank) {
canPassPrize = false;
}
});
if (+prizeInfoList[prizeInfoList.length - 1].rank.split("-") > 100)
canPassPrize = false;
if (!canPassPrize) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, "奖品配置不正确");
}
}
if (!rule) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, "活动规则不可为空");
}
if (!startTime || !endTime) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, "请配置活动时间");
} else if (!activityId && startTime < Date.now()) {
return resultsModel.error(
CODE_TYPES.PARAMS_ERROR,
"新建活动开始时间需小于当前时间"
);
} else if (startTime > endTime) {
return resultsModel.error(
CODE_TYPES.PARAMS_ERROR,
"开始时间不得大于结束时间"
);
} else if (
activityId &&
nowTime <= +initialData.baseConfig.startTime &&
nowTime <= +initialData.baseConfig.endTime
) {
if (+startTime !== +initialData.baseConfig.startTime) {
return resultsModel.error(
CODE_TYPES.PARAMS_ERROR,
"活动开始后不可更改活动开始时间"
);
}
}
let shopInfoResult = await ActivityTop.getShopId();
if (!shopInfoResult.success) {
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, "获取淘宝店铺信息失败");
}
console.log(shopInfoResult, "shopInfo");
const baseData = {
title,
subtitle,
startTime,
endTime,
rule,
shopId: shopInfoResult.data.sid,
openId,
taskInfo: {
specifyPageUrl,
browseItemIds,
attentionStore,
beMembership,
inviteFriends,
},
};
let result;
console.log("activityId", activityId);
try {
if (!activityId) {
result = await ActivityConfig.saveNewActivity(
baseData,
prizeInfoList,
openId
);
} else {
let prizeInfoListNoId = prizeInfoList.map((item) => {
item.activityId = activityId;
item.updateTime = Date.now();
item.createTime = Date.now();
item.deleteStatus = DELETE_STATUS.EXIST;
delete item._id;
return item;
});
let rankscanService = new RankScanService(context);
let oldActivityRankScanInfo = await rankscanService.getPrizeTime(
activityId
);
if (
oldActivityRankScanInfo &&
+oldActivityRankScanInfo.openPrizeTime !== +endTime
) {
await rankscanService.changeOpenPrizeTime(activityId, endTime);
}
console.log(prizeInfoListNoId, "prizeInfoListNoId");
result = await ActivityConfig.updateActivity(
activityId,
baseData,
prizeInfoListNoId
);
}
if (result) {
return resultsModel.success(true);
} else {
console.log(result, "保存活动失败");
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, "保存活动失败");
}
} catch (e) {
console.log(e, "保存活动失败");
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, "保存活动失败");
}
};
//删除活动
const delActivity = async function (context) {
loginfo(context, "deleteActivityById");
let ActivityConfig = new ActivityConfigService(context);
const { activityId = "" } = context.data;
try {
if (activityId) {
let result = await ActivityConfig.deleteActivityByActivityId(activityId);
if (result === 0 || result) {
return resultsModel.success(true);
} else {
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, "删除活动失败");
}
} else {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, "删除活动id不存在");
}
} catch (e) {
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, "删除活动失败");
}
};
//获取活动信息
const getActivityDetail = async function (context) {
loginfo(context, "getActivityInfoByActivityId");
let ActivityConfig = new ActivityConfigService(context);
const { activityId = "" } = context.data;
try {
if (activityId) {
let result = await ActivityConfig.getActivityInfoByActivityId(activityId);
const { taskInfo, ...rest } = result.baseConfig;
let prizeInfoList = result.prizeConfig.sort((a, b) => {
return a.rank.split("-")[0] - b.rank.split("-")[0];
});
let activityInfo = {
...taskInfo,
activityId,
...rest,
prizeInfoList,
};
return resultsModel.success(activityInfo);
} else {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, "当前活动不存在");
}
} catch (e) {
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, "获取活动配置信息失败");
}
};
module.exports = {
getActivityDetail,
delActivity,
saveActivityInfo,
getActivityList,
};
const {
formatTime
} = require('../utils/utils');
const {
CODE_TYPES,
EIGHT_HOURS
} = require('../utils/constants');
const ResultsModel = require('../utils/results.model');
let resultsModel = new ResultsModel();
//生成规则
module.exports = async (context) => {
console.log(JSON.stringify(context.data), 'context.data')
let {
title = '', startTime = '', endTime = '', prizeInfoList = []
} = context.data;
if (!title) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, '活动名称为空');
} else if (title.length > 12) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, '活动名称不可超过12个字');
}
if (!startTime || !endTime) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, '请配置活动时间');
} else if (startTime > endTime) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, '开始时间不得大于结束时间');
}
if (prizeInfoList.length === 0) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, '奖品至少配置3名');
} else if (prizeInfoList.length > 8) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, '奖品最多配置8个档位');
} else {
let canPassPrize = true;
prizeInfoList.forEach(item => {
if (!item.ename || !item.rank) {
canPassPrize = false;
}
})
if (!canPassPrize) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, '奖品配置不正确');
}
}
let resultRule = '';
let startTimeDate = formatTime(new Date(+startTime + EIGHT_HOURS), "yyyy-MM-dd hh:mm:ss");
let endTimeDate = formatTime(new Date(+endTime + EIGHT_HOURS), "yyyy-MM-dd hh:mm:ss");
resultRule = `1.活动时间:${startTimeDate}--${endTimeDate};\n2.活动结束根据排行榜排名获得对应的奖励,每个人取活动中单局最高分;\n3.每天3次免费参与资格,超过次数后邀请群内好友参与游戏可获得额外次数(每个用户可助力一次);\n4.参与签到可获得道具,每局结束游戏后可使用道具;\n5.完成任务每天可获得额外的复活机会,每局只能使用一张复活卡;\n\n`
let prizeStr = prizeInfoList.reduce((s, v) => {
let rankArr = v.rank.split('-');
if (rankArr[0] == rankArr[1]) {
return s += `第${rankArr[0]}名: ${v.name}\n`
} else {
return s += `第${v.rank}名: ${v.name}\n`
}
}, '奖品:\n')
resultRule += prizeStr;
return resultsModel.success(resultRule);
}
\ No newline at end of file
const { loginfo } = require("../utils/utils");
const { CODE_TYPES } = require("../utils/constants");
const ResultsModel = require("../utils/results.model");
let resultsModel = new ResultsModel();
const ActivityTopService = require("../service/activitytop.service");
//通过状态获取top商品列表
const findItemListByStatus = async function (context) {
loginfo(context, "findItemListByStatus");
let ActivityTop = new ActivityTopService(context);
try {
let result = await ActivityTop.getItemListByStatus();
return result;
} catch (e) {
console.log(e, "获取top商品列表失败");
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, "获取top商品列表失败");
}
};
//通过itemIds获取商品列表
const findItemListByIds = async (context) => {
loginfo(context, "findItemListByIdsAction");
let ActivityTop = new ActivityTopService(context);
try {
let result = ActivityTop.getItemListByIds();
return result;
} catch (e) {
console.log(e, "获取商品列表失败");
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, "获取商品列表失败");
}
};
//通过ename获取权益信息
const queryBenefitByEname = async (context) => {
loginfo(context, "getBenefitByEname");
let ActivityTop = new ActivityTopService(context);
try {
let result = await ActivityTop.getPrizeByEname();
return result;
} catch (e) {
console.log(e, "获取ename权益信息失败");
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, "获取ename权益信息失败");
}
};
//测试getTemplateInstantiate
const getTemplateInstantiate = async (context) => {
loginfo(context, "getTemplateInstantiate");
let ActivityTop = new ActivityTopService(context);
try {
let result = await ActivityTop.getTemplateInstantiate();
return result;
} catch (e) {
console.log(e, "获取getTemplateInstantiate失败");
return resultsModel.error(
CODE_TYPES.SYSTEM_ERROR,
"获取getTemplateInstantiate失败"
);
}
};
module.exports = {
findItemListByStatus,
findItemListByIds,
queryBenefitByEname,
};
const ActivitySellerService = require('../service/activityseller.service');
const {
CODE_TYPES,
TEMPLATE_INFO
} = require('../utils/constants');
const ActivityTopService = require('../service/activitytop.service');
const ActivityInstanceService = require('../service/activityinstance.service');
const {
loginfo
} = require('../utils/utils');
const ResultsModel = require('../utils/results.model');
let resultsModel = new ResultsModel();
//保存授权信息
module.exports = async (context) => {
loginfo(context, 'sellerInfo');
const {
openId,
accessToken,
appKey,
userNick,
appOwnerOpenId,
consumeMiniAppId
} = context;
let ActivitySeller = new ActivitySellerService(context);
let ActivityTop = new ActivityTopService(context);
let ActivityInstance = new ActivityInstanceService(context);
try {
let hasResult = await ActivitySeller.findSellerInfo(context.openId);
let instanceOldInfo = await ActivityInstance.getMiniAppInstanceInfo(context.openId);
if (!instanceOldInfo || !instanceOldInfo[0]) {
let instanceInfo = await ActivityTop.getTemplateInstantiate();
console.log(instanceInfo, 'instanceInfo');
const {
app_id,
app_version
} = instanceInfo;
if (instanceInfo) {
let onlineInstance = await ActivityTop.pushOnlineInstance({
app_id,
app_version
})
console.log(onlineInstance, 'onlineInstance', onlineInstance.online_results)
await ActivityInstance.saveInstanceInfo({
...instanceInfo,
clients: onlineInstance.online_results.miniapp_instance_app_online_dto.reduce((s, v) => {
return s += v.client + ','
}, ''),
...onlineInstance.app_info,
accessToken,
template_version: TEMPLATE_INFO.template_version,
openId,
appKey,
consumeMiniAppId,
userNick,
appOwnerOpenId
})
} else {
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, '获取实例化信息失败')
}
} else {
console.log(instanceOldInfo[0])
if (TEMPLATE_INFO.template_version !== instanceOldInfo[0].template_version) {
const {
clients,
app_id,
template_id
} = instanceOldInfo[0];
let updateInfo = {
clients,
app_id,
template_id,
ext_json: {
name: 'online'
},
template_version: TEMPLATE_INFO.template_version
}
let updateInstanceInfo = await ActivityTop.updateMiniInstance(updateInfo);
let onlineInstance = await ActivityTop.pushOnlineInstance({
app_id: updateInstanceInfo.app_id,
app_version: updateInstanceInfo.app_version
})
await ActivityInstance.updateInstanceInfo({
template_version: TEMPLATE_INFO.template_version,
...onlineInstance.app_info
})
}
}
if (hasResult && hasResult[0]) {
await ActivitySeller.updateSellerInfo(context.openId, {
accessToken
})
await ActivityInstance.updateInstanceInfo(context.openId, {
accessToken
})
return resultsModel.success(true);
} else {
let shopInfoResult = await ActivityTop.getShopId();
if (!shopInfoResult.success) {
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, '获取淘宝店铺信息失败')
}
try {
let result = await ActivitySeller.saveSellerInfo({
accessToken,
openId,
appKey,
shopId: shopInfoResult.data.sid,
userNick,
appOwnerOpenId
})
return resultsModel.success(result);
} catch (e) {
console.log(e, '保存授权信息失败');
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, '保存授权信息失败');
}
}
} catch (e) {
console.log(e, '获取授权信息失败');
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, '获取授权信息失败');
}
}
\ No newline at end of file
/**
* 中奖名单 及导出中奖名单
*/
const RankopenprizeService = require('../service/rankopenprize.service');
const ResultsModel = require('../utils/results.model');
const {
CODE_TYPES
} = require('../utils/constants');
const xlsx = require('node-xlsx');
let resultsModel = new ResultsModel();
// 活动中奖名单
const findWinnerInfoList = async (context) => {
let {
activityId
} = context.data;
if (!activityId) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, `缺少activityId`);
}
let rankopenprizeService = new RankopenprizeService(context);
// 获取活动中奖列表
let awardslist = await rankopenprizeService.getAwardslistByActivityId(activityId);
console.log(`awardslist: ${JSON.stringify(awardslist)}`);
let winnersObj = {};
awardslist.forEach((v, index, arr) => {
if (!winnersObj[v.rank]) {
winnersObj[v.rank] = [];
}
winnersObj[v.rank].push({
userNick: v.userNick,
id: v.openId
});
});
console.log(`winnersObj: ${JSON.stringify(winnersObj)}`);
let results = [];
new Map(Object.entries(winnersObj)).forEach((v, index, arr) => {
results.push({
rank: index,
winnerDetailList: v
});
});
return resultsModel.success(results);
}
// 导出活动中奖名单
const exportAwardsList = async (context) => {
let {
cloud
} = context;
let {
activityId,
title
} = context.data;
if (!activityId) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, `缺少activityId`);
}
if (!title) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, `缺少title`);
}
let rankopenprizeService = new RankopenprizeService(context);
// 获取活动中奖列表
let awardslist = await rankopenprizeService.getAwardslistByActivityId(activityId);
console.log(`awardslist: ${JSON.stringify(awardslist)}`);
let xlsxData = [
['序列', '名次', '昵称', '中奖名称']
];
awardslist.forEach((v, index, arr) => {
xlsxData.push([index + 1, v.rank, v.userNick, v.prizeName]);
});
let buffer = xlsx.build([{
name: title + new Date().getTime(),
data: xlsxData
}]);
console.log(`xlsxData: ${JSON.stringify(xlsxData)}`);
try {
let result = await cloud.file.uploadFile({
fileContent: buffer,
fileName: title + new Date().getTime() + '.xlsx'
});
// result.url 需进行处理
if (result.url) {
result.url = result.url.replace('http', 'https').replace('-internal', '');
}
return resultsModel.success(result);
} catch (e) {
console.log('上传文件出错', e);
// 打印日志
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, `上传文件错误`);
}
}
module.exports = {
findWinnerInfoList,
exportAwardsList
}
\ No newline at end of file
const RankscanService = require('../service/rankscan.service')
const ActivityprizeService = require('../service/activityprize.service')
const RankscoreService = require('../service/rankscore.service');
const RankopenprizeService = require('../service/rankopenprize.service');
const ResultsModel = require('../utils/results.model');
let resultsModel = new ResultsModel();
// 声明排行榜扫描服务
let rankscanService = null;
// 声明奖品服务
let activityprizeService = null;
// 声明分数排行榜服务
let rankscoreService = null;
// 声明待开奖服务
let rankopenprizeService = null;
// 查询该活动的奖品列表 及 发奖数
const getPrizeListAndAwards = async (activityId) => {
let prizeList = await activityprizeService.getPrizeListByActivityId(activityId);
console.log(prizeList);
// 若不存在奖品列表,则为脏数据,不做处理
if (!prizeList.length) {
console.log(`活动Id为${activityId}不存在对应的奖品信息`);
return {
prizeList: [],
totalAwards: 0
};
}
// 获取总共发奖的个数
let totalAwards = prizeList.reduce((total, curVal, curIndex, arr) => {
// 当没有rank名次,直接返回
if (!curVal.rank) {
return total;
}
let awards = +curVal.rank.split('-')[1];
if (awards > total) {
total = awards;
}
return total;
}, 0);
return {
prizeList,
totalAwards
}
}
// 获取待开奖列表
const getWaitAwardsList = (prizeList, topscoreList, totalAwards, shopId) => {
let waitAwardslist = [];
// 若分数排行榜没有数据,或发奖数为0,返回[]
if (!totalAwards || !topscoreList.length) {
return waitAwardslist;
}
prizeList.forEach((v, index, arr) => {
if (!v.rank) {
return;
}
let rankPeriod = v.rank.split('-');
// 截取排行区间的分数排行列表
let ranklist = topscoreList.slice(+rankPeriod[0] - 1, Math.min(+rankPeriod[1], totalAwards));
// 整合开奖数据
ranklist.map(rank => {
waitAwardslist.push({
activityId: v.activityId,
openId: rank.openId,
rank: v.rank,
ename: v.ename,
startTime: v.startTime,
endTime: v.endTime,
// prizeId: v.goodsId,
prizeName: v.name,
userNick: rank.userNick,
rankTime: rank.updateTime,
image: v.image,
score: rank.score,
shopId: shopId,
type: v.type || '',
amount: v.amount || '',
});
});
});
return waitAwardslist;
}
// 设置活动开奖
const setActivity2openprize = async (waitAwardslist, _id) => {
// 插入排行榜开奖记录表 rank_open_prize 若失败,则变更
let results = await rankopenprizeService.addWaitAwardsList(waitAwardslist);
console.log(`result: ${JSON.stringify(results)}`);
if (results) {
await rankscanService.update2Success(_id);
console.log(`开奖成功`);
return true;
} else {
await rankscanService.update2Fail(_id, `批量插入rank_open_prize表不成功`);
console.log(`开奖失败`);
return false;
}
}
/**
* 定时触发开奖
*/
const endingNotify = async (context) => {
// 初始化排行榜扫描服务
rankscanService = new RankscanService(context);
// 初始化返回结果
let results = {
success: [],
fails: []
};
// 获取服务器时间
let serverTime = Date.now();
// 触发开奖列表
let notifyList = await rankscanService.getNodifyList(serverTime);
console.log(notifyList.length);
if (!notifyList.length) {
console.log(`没有待开奖的活动`);
return resultsModel.success(results);
}
// 初始化奖品服务
activityprizeService = new ActivityprizeService(context);
// 分数排行榜
rankscoreService = new RankscoreService(context);
// 待开奖服务
rankopenprizeService = new RankopenprizeService(context);
// 遍历列表
for (let i = 0; i < notifyList.length; i++) {
let {
activityId,
_id,
shopId
} = notifyList[i];
// 将该条记录变更为处理中
let updateResult = await rankscanService.update2Process(_id);
console.log(`updateResult: ${updateResult}`)
if (!updateResult) {
console.log(`将rank_scan该活动的开奖状态变更为处理中失败`);
continue;
}
// 查询该活动的奖品列表 及 发奖数
let {
prizeList,
totalAwards
} = await getPrizeListAndAwards(activityId);
console.log(`prizeList: ${JSON.stringify(prizeList)}; totalAwards: ${totalAwards}`);
// 没查找到奖品列表, 继续下个活动循环
if (!prizeList.length) {
// 开奖失败,记录日志
await rankscanService.update2Fail(_id, `活动不存在对应的奖品列表,开奖失败`);
results.fails.push(activityId);
continue;
}
// 查找排行榜分数榜里的前totalAwards个记录
let topscoreList = await rankscoreService.getToplistByActivityId(activityId, totalAwards);
console.log(`topscoreList: ${JSON.stringify(topscoreList)}`)
// 根据分数排行榜列表及奖品列表整合数据,待开奖列表
let waitAwardslist = getWaitAwardsList(prizeList, topscoreList, totalAwards, shopId);
// if (!waitAwardslist.length) {
// }
console.log(`waitAwardslist: ${JSON.stringify(waitAwardslist)}`)
// 开奖
let issuccess = await setActivity2openprize(waitAwardslist, _id);
issuccess ? results.success.push(activityId) : results.fails.push(activityId);
// end
}
return resultsModel.success(results);
}
module.exports = {
endingNotify
}
\ No newline at end of file
class BaseDao {
constructor(context, dbName) {
this.db = context.cloud.db;
this.dbName = dbName;
this.openId = context.openId;
this.context = context;
}
async find(query, options) {
console.log(`query: ${JSON.stringify(query)}`)
console.log(`options: ${JSON.stringify(options)}`)
return await this.db.collection(this.dbName).find(query, options);
}
// 更新数据
async update(query, options) {
return await this.db.collection(this.dbName).updateMany(query, options);
}
//插入单条数据
async insertOne(options) {
return await this.db.collection(this.dbName).insertOne(options);
}
async count(query) {
return await this.db.collection(this.dbName).count(query);
}
async deleteMany(query) {
return await this.db.collection(this.dbName).deleteMany(query);
}
// options Array
async insertMany(options) {
return await this.db.collection(this.dbName).insertMany(options);
}
}
module.exports = BaseDao;
\ No newline at end of file
const { endingNotify } = require("./controller/endingNodify.controller");
const {
findWinnerInfoList,
exportAwardsList,
} = require("./controller/awards.controller");
const {
getActivityDetail,
delActivity,
saveActivityInfo,
getActivityList,
} = require("./controller/activity.controller");
const sellerSave = require("./controller/activityseller.controller");
const generateRule = require("./controller/activitygenerateRule.controller");
const {
findItemListByStatus,
findItemListByIds,
queryBenefitByEname,
} = require("./controller/activitygettbitem.controller");
exports.hello = async (context) => {
return "aaaa";
};
module.exports = {
//获取商家配置权益信息
queryBenefitByEname,
//通过选择的商品id获取商家商品
findItemListByIds,
//获取商家商品列表
findItemListByStatus,
//生成规则
generateRule,
//授权信息,用来对接商家淘宝api
sellerSave,
//获取活动详细信息
getActivityDetail,
//删除活动
delActivity,
//保存活动
saveActivityInfo,
//获取活动列表
getActivityList,
//导出中奖名单
exportAwardsList,
//获取中奖名单
findWinnerInfoList,
//定时开奖
endingNotify,
};
{
"name": "demotest",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"adler-32": {
"version": "1.2.0",
"resolved": "https://registry.npm.taobao.org/adler-32/download/adler-32-1.2.0.tgz",
"integrity": "sha1-aj5r8KY5ALoVZSgIyxXGgT0aXyU=",
"requires": {
"exit-on-epipe": "~1.0.1",
"printj": "~1.1.0"
}
},
"buffer-from": {
"version": "1.1.1",
"resolved": "https://registry.npm.taobao.org/buffer-from/download/buffer-from-1.1.1.tgz",
"integrity": "sha1-MnE7wCj3XAL9txDXx7zsHyxgcO8="
},
"cfb": {
"version": "1.1.4",
"resolved": "https://registry.npm.taobao.org/cfb/download/cfb-1.1.4.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcfb%2Fdownload%2Fcfb-1.1.4.tgz",
"integrity": "sha1-gf017eTJGdjwliqUWC4d+vcFHio=",
"requires": {
"adler-32": "~1.2.0",
"commander": "^2.16.0",
"crc-32": "~1.2.0",
"printj": "~1.1.2"
}
},
"codepage": {
"version": "1.14.0",
"resolved": "https://registry.npm.taobao.org/codepage/download/codepage-1.14.0.tgz",
"integrity": "sha1-jL4lSBMjVZ19MHVxsP/5HnodL5k=",
"requires": {
"commander": "~2.14.1",
"exit-on-epipe": "~1.0.1"
},
"dependencies": {
"commander": {
"version": "2.14.1",
"resolved": "https://registry.npm.taobao.org/commander/download/commander-2.14.1.tgz",
"integrity": "sha1-IjUSPjevjKPGXfRbAm29NXsBuao="
}
}
},
"commander": {
"version": "2.17.1",
"resolved": "https://registry.npm.taobao.org/commander/download/commander-2.17.1.tgz",
"integrity": "sha1-vXerfebelCBc6sxy8XFtKfIKd78="
},
"crc-32": {
"version": "1.2.0",
"resolved": "https://registry.npm.taobao.org/crc-32/download/crc-32-1.2.0.tgz",
"integrity": "sha1-yy224puIUI4y2d0OwWk+e0Ghggg=",
"requires": {
"exit-on-epipe": "~1.0.1",
"printj": "~1.1.0"
}
},
"exit-on-epipe": {
"version": "1.0.1",
"resolved": "https://registry.npm.taobao.org/exit-on-epipe/download/exit-on-epipe-1.0.1.tgz",
"integrity": "sha1-C92S6H1ShdJn2qgXHQ6wYVlolpI="
},
"frac": {
"version": "1.1.2",
"resolved": "https://registry.npm.taobao.org/frac/download/frac-1.1.2.tgz",
"integrity": "sha1-PXT39keMiKG1AgMG10fcYxPHTQs="
},
"node-xlsx": {
"version": "0.15.0",
"resolved": "https://registry.npm.taobao.org/node-xlsx/download/node-xlsx-0.15.0.tgz",
"integrity": "sha1-HxsNetzlxwboa/2WpaoABb+KncM=",
"requires": {
"buffer-from": "^1.1.0",
"xlsx": "^0.14.1"
}
},
"printj": {
"version": "1.1.2",
"resolved": "https://registry.npm.taobao.org/printj/download/printj-1.1.2.tgz",
"integrity": "sha1-2Q3rKXWoufYA+zoclOP0xTx4oiI="
},
"ssf": {
"version": "0.10.3",
"resolved": "https://registry.npm.taobao.org/ssf/download/ssf-0.10.3.tgz",
"integrity": "sha1-jq4fwpyQpVLnkhII+BiS1vd6yys=",
"requires": {
"frac": "~1.1.2"
}
},
"xlsx": {
"version": "0.14.5",
"resolved": "https://registry.npm.taobao.org/xlsx/download/xlsx-0.14.5.tgz",
"integrity": "sha1-NjfpFNeRvcpzgoFuFz99cl7Q4NI=",
"requires": {
"adler-32": "~1.2.0",
"cfb": "^1.1.2",
"codepage": "~1.14.0",
"commander": "~2.17.1",
"crc-32": "~1.2.0",
"exit-on-epipe": "~1.0.1",
"ssf": "~0.10.2"
}
}
}
}
{
"name": "duiba",
"version": "1.0.0",
"description": "",
"main": "index.js",
"author": "",
"license": "ISC",
"sdkVersion": "*",
"dependencies": {
"node-xlsx": "^0.15.0"
},
"config": {
"notNeedLogin": [
"endingNotify"
]
}
}
const BaseDao = require('../dao/base.dao');
const {
ACTIVITY_BASE_CONFIG,
ACTIVITY_PRIZE_CONFIG
} = require('../config/db_config');
const {
DELETE_STATUS
} = require("../utils/constants");
module.exports = class ActivityConfigService {
constructor(context) {
this.activityconfigDao = new BaseDao(context, ACTIVITY_BASE_CONFIG);
this.activityconfigPrizeDao = new BaseDao(context, ACTIVITY_PRIZE_CONFIG);
}
//保存新建活动
async saveNewActivity(data, prize, openId) {
try {
let result = await this.activityconfigDao.insertOne({
...data,
createTime: Date.now(),
updateTime: Date.now(),
deleteStatus: DELETE_STATUS.EXIST
});
console.log(result, '保存基础配置result')
if (result) {
try {
prize.map(item => {
item.activityId = result;
item.openId = openId;
item.createTime = Date.now();
item.updateTime = Date.now();
item.deleteStatus = DELETE_STATUS.EXIST;
return item;
})
return await this.activityconfigPrizeDao.insertMany(prize);
} catch (e) {
console.log(e, '保存奖品配置出错')
// 如果奖品未保存删除之前的活动
await this.activityconfigDao.deleteMany({
_id: result
})
}
}
} catch (e) {
//保存活动失败
console.log(e, '保存活动失败')
}
}
//更新活动配置
async updateActivity(activityId, data, prizeNoId) {
console.log(activityId, data, prizeNoId, 'activityId, data, prizeNoId, oldPrizeIds')
try {
let originalData = await this.activityconfigDao.find({
_id: activityId
})
let result = await this.activityconfigDao.update({
_id: activityId
}, {
$set: {
...data,
createTime: originalData[0].createTime,
updateTime: Date.now()
}
});
if (result) {
try {
let prizeOldIdsByActivity = (await this.activityconfigPrizeDao.find({
activityId
})).reduce((s, v) => {
return s = [...s, v._id];
}, [])
let prizeResult = await this.activityconfigPrizeDao.insertMany(prizeNoId);
if (prizeResult) {
//删除之前的奖品
let deleteOldPrizeList = prizeOldIdsByActivity.reduce((s, item) => {
return s = [...s, this.activityconfigPrizeDao.deleteMany({
_id: item
})]
}, [])
return await Promise.all(deleteOldPrizeList);
}
} catch (e) {
await this.activityconfigDao.update({
_id: activityId
}, {
$set: {
...originalData[0]
}
})
}
}
} catch (e) {
//更新失败
}
}
//通过openId去获取当前商家活动列表
async getActivityListByOpenId(shopId, {
pageSize = 10,
pageNo = 1
}) {
return {
list: await this.activityconfigDao.find({
shopId,
deleteStatus: DELETE_STATUS.EXIST
}, {
projection: {
startTime: 1,
endTime: 1,
title: 1
},
sort: {
createTime: -1
},
limit: pageSize,
skip: (pageNo - 1) * pageSize
}),
total: await this.activityconfigDao.count({
shopId,
deleteStatus: DELETE_STATUS.EXIST
}),
pageSize,
pageNo
}
}
//通过activityId去获取活动配置信息
async getActivityInfoByActivityId(activityId) {
return {
baseConfig: (await this.activityconfigDao.find({
_id: activityId,
deleteStatus: DELETE_STATUS.EXIST
}))[0],
prizeConfig: await this.activityconfigPrizeDao.find({
activityId,
deleteStatus: DELETE_STATUS.EXIST
})
}
}
//通过activityId去删除活动
async deleteActivityByActivityId(activityId) {
try {
let result = await this.activityconfigDao.update({
_id: activityId
}, {
$set: {
deleteStatus: DELETE_STATUS.DELETE
}
})
if (result) {
return await this.activityconfigPrizeDao.update({
activityId
}, {
$set: {
deleteStatus: DELETE_STATUS.DELETE
}
})
}
} catch (e) {
console.log(e, '删除活动失败')
}
}
}
\ No newline at end of file
const BaseDao = require('../dao/base.dao');
const {
ACTIVITY_INSTANCE
} = require('../config/db_config');
module.exports = class ActivitySellerService {
constructor(context) {
this.activityinstanceDao = new BaseDao(context, ACTIVITY_INSTANCE);
}
//保存商家授权信息
async saveInstanceInfo(instanceInfo) {
return await this.activityinstanceDao.insertOne({
...instanceInfo,
updateTime: Date.now(),
createTime: Date.now()
})
}
//查找商家授权信息
async findInstanceInfo(openId) {
return await this.activityinstanceDao.find({
openId
})
}
//更新商家授权信息
async updateInstanceInfo(openId, updateinfo) {
return await this.activityinstanceDao.update({
openId: openId
}, {
$set: {
...updateinfo,
updateTime: Date.now()
}
})
}
async getMiniAppInstanceInfo(openId) {
return await this.activityinstanceDao.find({
openId
})
}
}
\ No newline at end of file
const BaseDao = require('../dao/base.dao');
const DBName = 'activity_prize_config';
class ActivityprizeService {
constructor(context) {
this.activityprizeDao = new BaseDao(context, DBName);
}
// 根据活动id查询奖品列表
async getPrizeListByActivityId(activityId) {
console.log(13, activityId)
return await this.activityprizeDao.find({
activityId: activityId
});
}
}
module.exports = ActivityprizeService;
\ No newline at end of file
const BaseDao = require('../dao/base.dao');
const {
ACTIVITY_SELLER_SAVE
} = require('../config/db_config');
module.exports = class ActivitySellerService {
constructor(context) {
this.activitysellerDao = new BaseDao(context, ACTIVITY_SELLER_SAVE);
}
//保存商家授权信息
async saveSellerInfo(sellerInfo) {
return await this.activitysellerDao.insertOne({
...sellerInfo,
updateTime: Date.now(),
createTime: Date.now()
})
}
//查找商家授权信息
async findSellerInfo(openId) {
return await this.activitysellerDao.find({
openId
})
}
//更新商家授权信息
async updateSellerInfo(openId, sellerInfo) {
console.log(openId, sellerInfo, 'openId, sellerInfo')
return await this.activitysellerDao.update({
openId: openId
}, {
$set: {
...sellerInfo,
updateTime: Date.now()
}
})
}
}
\ No newline at end of file
const {
ACTIVITY_SELLER_SAVE,
ACTIVITY_BASE_CONFIG,
} = require("../config/db_config");
const {
CODE_TYPES,
GOODSINFO,
B_APP_NAME,
TEMPLATE_INFO,
TBAPIS,
} = require("../utils/constants");
const ResultsModel = require("../utils/results.model");
const { MathRand } = require("../utils/utils");
let resultsModel = new ResultsModel();
const BaseDao = require("../dao/base.dao");
class ActivityTopService {
constructor(context) {
this.activitySellerDao = new BaseDao(context, ACTIVITY_SELLER_SAVE);
this.activityBaseDao = new BaseDao(context, ACTIVITY_BASE_CONFIG);
this.context = context;
}
//获取商家授权session
async getAccessToken(activityId) {
let result;
if (activityId) {
result = await this.activityBaseDao.find({
_id: activityId,
});
} else {
let openId = this.context.openId;
result = await this.activitySellerDao.find({
openId,
});
}
if (result[0]) {
let shopId = result[0].shopId;
let sellResult = await this.activitySellerDao.find(
{
shopId,
},
{
sort: {
createTime: 1,
},
}
);
console.log(sellResult, "sellResult");
if (sellResult[0]) {
let { accessToken } = sellResult[0];
return {
session: accessToken,
};
}
}
}
//淘宝top接口获取商品列表
async getItemListByIds(activityId, itemIds) {
let sellerConfig = await this.getAccessToken(activityId);
console.log(sellerConfig, "sellerConfig");
try {
let result = await TBAPIS.getItemListByItemIds(
this.context,
...sellerConfig,
itemIds,
{
fields: GOODSINFO,
}
);
// console.log(JSON.stringify(result), '获取商品通过ids')
if (result) {
let itemsData = {
list:
(result &&
result.items &&
result.items.item.reduce((s, v) => {
return (s = [
...s,
{
itemId: v.num_iid,
name: v.title,
price: v.price,
detailUrl: v.detail_url,
picUrl: v.pic_url,
},
]);
}, [])) ||
[],
totalCount: result.items.item.length,
};
return resultsModel.success(itemsData);
}
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, "获取商家ids列表失败");
} catch (e) {
console.log(e, "获取ids商品列表失败");
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, "获取商家ids列表失败");
}
}
//淘宝top接口获取权益商品信息
async getPrizeByEname(activityId, ename) {
let sellerConfig = await this.getAccessToken(activityId);
console.log(sellerConfig, "sellerConfig");
try {
let benefitData = await TBAPIS.getPrizeByEname(
this.context,
...sellerConfig,
activityId ? ename : this.context.ename,
B_APP_NAME
);
console.log(benefitData, benefitData.result, "benefitData.result.");
if (benefitData.result.success) {
const { result } = benefitData;
let data =
result.datas &&
result.datas["oright_dto"].reduce((s, v) => {
return (s = [
...s,
{
benefitName: v.benefit_name,
rightTypeId: v.right_type_id,
startTime: new Date(v.start_date).getTime(),
endTime: new Date(v.end_date).getTime(),
amount: v.amount ? v.amount / 100 + "" : "",
},
]);
}, []);
console.log(JSON.stringify(result), "alibaba.benefit.query");
return resultsModel.success(data);
}
return resultsModel.error(
CODE_TYPES.SYSTEM_ERROR,
"获取ename商品信息失败"
);
} catch (e) {
console.log(e, "权益获取失败");
return resultsModel.error(
CODE_TYPES.SYSTEM_ERROR,
"获取ename商品信息失败"
);
}
}
//淘宝top接口获取店铺会员链接
async getVipUrlByActivity(activityId) {
let sellerConfig = await this.getAccessToken(activityId);
console.log(sellerConfig, "sellerConfig");
try {
let result = await TBAPIS.getShopVipUrl(this.context, ...sellerConfig, {
source: "isvapp",
activityId: activityId,
entrance: "duiba",
});
if (result) {
return resultsModel.success(result);
}
console.log(JSON.stringify(result), "获取商家会员链接");
return resultsModel.error(
CODE_TYPES.SYSTEM_ERROR,
"获取商家会员链接失败"
);
} catch (e) {
console.log(e, "获取商家会员链接失败");
return resultsModel.error(
CODE_TYPES.SYSTEM_ERROR,
"获取商家会员链接失败"
);
}
}
//淘宝top接口获取店铺信息
async getShopId() {
let sellerConfig = await this.getAccessToken();
console.log(sellerConfig, "sellerConfig");
try {
let result = await TBAPIS.getShopInfo(this.context, {
...sellerConfig,
fields: "sid,title,pic_path",
});
console.log(result, "getShopId result");
if (result) {
return resultsModel.success(result.shop);
} else {
return resultsModel.error(
CODE_TYPES.SYSTEM_ERROR,
"获取淘宝top店铺信息失败"
);
}
} catch (e) {
console.log(e, "获取店铺信息失败");
return resultsModel.error(
CODE_TYPES.SYSTEM_ERROR,
"获取淘宝top店铺信息失败"
);
}
}
// 获取商品信息
async getItemListByStatus() {
let sellerConfig = await this.getAccessToken();
console.log(sellerConfig, "sellerConfig");
const {
approveStatus = "onsale",
title = "",
pageNo = 1,
pageSize = 10,
} = this.context.data;
let data = {
fields: GOODSINFO,
page_no: pageNo,
q: title,
page_size: pageSize,
session: this.context.data.session || sellerConfig.session,
};
console.log(data, "data");
try {
let result =
approveStatus === "onsale"
? await TBAPIS.getItemListOnSale(this.context, data)
: await TBAPIS.getItemListInStock(this.context, data);
if (result) {
let { items, total_results } = result;
let itemsData = {
pageNo,
pageSize,
totalPages: Math.ceil(total_results / pageSize),
totalCount: total_results,
list:
(items &&
items.item &&
items.item.reduce((s, v) => {
return (s = [
...s,
{
itemId: v.num_iid,
name: v.title,
price: v.price,
approveStatus: v.approveStatus || approveStatus,
picUrl: v.pic_url,
detailUrl: v.detail_url,
},
]);
}, [])) ||
[],
};
return resultsModel.success(itemsData);
}
return resultsModel.error(
CODE_TYPES.SYSTEM_ERROR,
"获取商家在售列表失败"
);
} catch (e) {
console.log(e, "获取商家在售列表失败");
return resultsModel.error(
CODE_TYPES.SYSTEM_ERROR,
"获取商家在售列表失败"
);
}
}
// 实例化小程序
async getTemplateInstantiate() {
let sellerConfig = await this.getAccessToken();
console.log(sellerConfig, "sellerConfig");
let data = {
description:
"此应用用于商家引导活动,商家可设置对应的奖品,在一定时间 内出奖,提升店铺用户活跃以及引导购买",
ext_json: {
name: "online",
},
icon:
"https://ossgw.alicdn.com/taobao-miniapp/img/0193eaa9cc037b568acd9ccfe68a8499.jpg",
name: "店铺漂流" + MathRand(),
...TEMPLATE_INFO,
...sellerConfig,
};
console.log("getTemplateInstantiateParams", data);
try {
let result = await TBAPIS.getTemplateInstantiate(this.context, data);
if (result) {
console.log(JSON.stringify(result), "getTemplateInstantiate");
return result;
}
return false;
} catch (e) {
console.log(e, "获取getTemplateInstantiate失败");
return false;
}
}
// 模板小程序上线
async pushOnlineInstance(instanceInfo) {
let sellerConfig = await this.getAccessToken();
console.log(sellerConfig, "sellerConfig");
let data = {
...TEMPLATE_INFO,
...instanceInfo,
...sellerConfig,
};
console.log("pushOnlineInstanceParams", data);
try {
let result = await TBAPIS.pushInstanceOnline(this.context, data);
if (result) {
console.log(JSON.stringify(result), "pushOnlineInstance");
return result;
}
return false;
} catch (e) {
console.log(e, "pushOnlineInstance失败");
return false;
}
}
// 模板小程序更新
async updateMiniInstance(instanceInfo) {
let sellerConfig = await this.getAccessToken();
console.log(sellerConfig, "sellerConfig");
let data = {
...TEMPLATE_INFO,
...instanceInfo,
...sellerConfig,
};
try {
let result = await TBAPIS.updateMiniInstance(this.context, data);
if (result) {
console.log(JSON.stringify(result), "updateMiniInstance");
return result;
}
return false;
} catch (e) {
console.log(e, "updateMiniInstance失败");
return false;
}
}
}
module.exports = ActivityTopService;
const ResultsModel = require("../utils/results.model");
const { CODE_TYPES } = require("../utils/constants");
const BaseDao = require("../dao/base.dao");
const { RANK_SCORE } = require("../config/db_config");
const DB_NAME = RANK_SCORE;
class RankService {
constructor(context) {
this.rankDao = new BaseDao(context, DB_NAME);
this.resultsModel = new ResultsModel();
}
async doScoreSubmit(activityId, score, nickName, avatar) {
try {
const result = await this.rankDao.find({
activityId: activityId,
openId: this.rankDao.openId,
});
let maxScore = score;
if (result.length) {
// 用户已经存在
if (result["0"].score < score) {
//当前提交的分数大于库存分数
await this.rankDao.update(
{ activityId: activityId, openId: this.rankDao.openId },
{
$set: {
score: score,
updateTime: Date.now(),
},
}
);
} else {
maxScore = result["0"].score;
}
} else {
//新用户新增记录
await this.rankDao.insertOne({
activityId: activityId,
openId: this.rankDao.openId,
userNick: nickName,
avatar: avatar,
score: score,
createTime: Date.now(),
updateTime: Date.now(),
});
}
//根据自己的最高分,拿到自己的排名
let myRank = await this.rankDao.count({
score: { $gt: maxScore },
activityId: activityId,
});
myRank = myRank + 1;
//根据排名,检索自己前后的用户数据
const resultList = await this.rankDao.find(
{ activityId: activityId },
{
projection: { score: 1, userNick: 1, avatar: 1 },
sort: { score: -1, updateTime: -1 },
limit: 3,
skip: myRank - 2,
}
);
return this.resultsModel.success({
rankList: resultList,
myRank: myRank,
myMaxScore: maxScore,
});
} catch (e) {
return this.resultsModel.error(CODE_TYPES.ERROR_SERVER);
}
}
async getRankList(activityId, page, limit) {
try {
//获取自己在当前活动的分数
const myScoreInfo = await this.rankDao.find(
{ activityId: activityId, openId: this.rankDao.openId },
{
projection: { score: 1 },
}
);
let myScore = 0;
let myRank;
if (myScoreInfo && myScoreInfo["0"]) {
myScore = myScoreInfo["0"].score;
}
//根据自己的分数,获取排名
if (myScore) {
myRank = await this.rankDao.count({
score: { $gt: myScore },
activityId: activityId,
});
myRank = myRank + 1;
} else {
myRank = "100+";
}
const totalCount = await this.rankDao.count({ activityId: activityId });
//获取排行版
const result = await this.rankDao.find(
{ activityId: activityId },
{
projection: { score: 1, userNick: 1 },
sort: { score: -1, updateTime: -1 },
limit: limit,
skip: (page - 1) * limit,
}
);
return this.resultsModel.success({
list: result,
myRank: myRank,
myMaxScore: myScore,
totalCount: totalCount,
});
} catch (e) {
return this.resultsModel.error(CODE_TYPES.ERROR_SERVER);
}
}
}
module.exports = RankService;
// 排行榜开奖记录
const BaseDao = require("../dao/base.dao");
const { DRAW_STATUS } = require("../utils/constants");
const { RANK_OPEN_PRIZE } = require("../config/db_config");
const DBName = RANK_OPEN_PRIZE;
class RankopenprizeService {
constructor(context) {
this.rankopenprizeDao = new BaseDao(context, DBName);
}
// 添加排行榜待开奖列表
async addWaitAwardsList(list) {
if (!list.length) {
return true;
}
list.map((v) => {
(v.drawStatus = DRAW_STATUS.WAITAWARD), (v.message = "");
});
try {
await this.rankopenprizeDao.insertMany(list);
return true;
} catch (e) {
console.log(`添加排行榜待开奖列表出错:${e}`);
return false;
}
}
// 根据活动id查找中奖名单
async getAwardslistByActivityId(activityId) {
if (!activityId) {
return false;
}
return await this.rankopenprizeDao.find(
{
activityId: activityId,
},
{
sort: {
score: -1,
rankTime: -1,
},
}
);
}
}
module.exports = RankopenprizeService;
const BaseDao = require("../dao/base.dao");
const { OPEN_PRIZE_STATUS } = require("../utils/constants");
const { RANK_SCAN } = require("../config/db_config");
const DBName = RANK_SCAN;
class RankscanService {
constructor(context) {
this.rankscanDao = new BaseDao(context, DBName);
}
//更改开奖时间
async changeOpenPrizeTime(activityId, openPrizeTime) {
try {
await this.rankscanDao.update(
{
activityId,
},
{
$set: {
openPrizeTime,
updateTime: Date.now(),
},
}
);
return true;
} catch (e) {
console.log(e);
return false;
}
}
//查询开奖时间
async getPrizeTime(activityId) {
try {
let list = await this.rankscanDao.find({
activityId,
});
if (list && list[0]) {
return list[0];
} else {
return false;
}
} catch (e) {
console.log(e);
return false;
}
}
// 查询待开奖列表
async getNodifyList(serverTime) {
let list = await this.rankscanDao.find({
openPrizeTime: {
$lt: serverTime,
},
openPrizeStatus: OPEN_PRIZE_STATUS.WAIT_AWARD,
});
console.log(list);
return list;
}
// 更新openPrizeStatus为处理中
async update2Process(_id) {
try {
await this.rankscanDao.update(
{
_id: _id,
},
{
$set: {
openPrizeStatus: OPEN_PRIZE_STATUS.PROCESSING,
updateTime: Date.now(),
},
}
);
return true;
} catch (e) {
// 日志记录
console.log(e);
return false;
}
}
// 更新openPrizeStatus为成功
async update2Success(_id) {
try {
await this.rankscanDao.update(
{
_id: _id,
},
{
$set: {
openPrizeStatus: OPEN_PRIZE_STATUS.SUCCESS,
openPrizeMsg: "",
updateTime: Date.now(),
},
}
);
return true;
} catch (e) {
// 日志记录
console.log(e);
return false;
}
}
// 更新openPrizeStatus为失败
async update2Fail(_id, message) {
try {
await this.rankscanDao.update(
{
_id: _id,
},
{
$set: {
openPrizeStatus: OPEN_PRIZE_STATUS.FAIL,
openPrizeMsg: message || "",
updateTime: Date.now(),
},
}
);
return true;
} catch (e) {
// 日志记录
console.log(e);
return false;
}
}
}
module.exports = RankscanService;
const BaseDao = require("../dao/base.dao");
const { RANK_SCORE } = require("../config/db_config");
const DBName = RANK_SCORE;
class RankscoreService {
constructor(context) {
this.rankscoreDao = new BaseDao(context, DBName);
}
// 根据活动id查询分数排行榜
async getToplistByActivityId(activityId, totalAwards) {
let list = await this.rankscoreDao.find(
{
activityId: activityId,
},
{
sort: {
score: -1,
updateTime: 1,
},
limit: +totalAwards,
}
);
console.log(`getToplistByActivityId: ${JSON.stringify(list)}`);
return list;
}
}
module.exports = RankscoreService;
taobao-mini-sdk @ 025197b3
Subproject commit 025197b38aaf883547384d73b5960782c9093c23
const {
BaseDao,
TBAPI,
Utils,
} = require("../taobao-mini-sdk/lib/index").default;
const { DEFAULT_CODE_TYPES, dateFormatter, ResultsModel } = Utils.default;
const TBAPIS = TBAPI.default;
// 活动开奖状态码
const OPEN_PRIZE_STATUS = {
// 待开奖
WAIT_AWARD: 1,
// 开奖中
PROCESSING: 2,
// 开奖成功
SUCCESS: 3,
// 开奖失败
FAIL: 4,
};
// 领取奖品状态
const DRAW_STATUS = {
// 待领取
WAITAWARD: 1,
// 处理中
PROCESSING: 2,
// 领取成功
SUCCESS: 3,
// 领取失败
FAIL: 4,
// 已过期
EXPIRED: 5,
// 重新领取
RETRY: 6,
};
// 日志类型: error,info
const LOGGER_TYPE = {
ERROR: 1,
INFO: 2,
};
// code类型
const CODE_TYPES = {
PARAMS_ERROR: {
code: "100000",
defaultMsg: `参数错误`,
},
SYSTEM_ERROR: {
code: "500000",
defaultMsg: `系统错误`,
},
SUCCESS: {
code: "000000",
defaultMsg: `成功`,
},
// TODO 补充业务类型错误, 固定以2开头,如B端业务错误:200001,200002,C端业务错误:210001,210002
ERROR_SERVER: {
code: "210001",
defaultMsg: `服务器异常`,
},
ERROR_NO_USER: {
code: "210002",
defaultMsg: `用户不存在`,
},
ERROR_INVITE_LIMIT: {
code: "210003",
defaultMsg: `邀请已达上限`,
},
ERROR_INVITE_SELF: {
code: "210004",
defaultMsg: `无法邀请自己`,
},
ERROR_TASK_DONE: {
code: "210005",
defaultMsg: `该任务已经完成`,
},
ERROR_NO_ACTIVITY: {
code: "210006",
defaultMsg: `该活动ID无效`,
},
ERROR_NO_TASK: {
code: "210007",
defaultMsg: `该任务不存在`,
},
ERROR_NO_POWER: {
code: "210008",
defaultMsg: `体力值不足`,
},
ERROR_NO_START: {
code: "210009",
defaultMsg: `该活动暂未开始`,
},
ERROR_NO_SHAREID: {
code: "210010",
defaultMsg: `该分享ID不存在`,
},
ERROR_NO_TASK: {
code: "210011",
defaultMsg: `该活动无任务`,
},
ERROR_NO_PRIZE: {
code: "210012",
defaultMsg: `该用户暂未获奖`,
},
ERROR_SEND_PRIZE_FAIL: {
code: "210013",
defaultMsg: `领取失败`,
},
ERROR_NO_SHOP: {
code: "210014",
defaultMsg: `该店铺ID不存在`,
},
ERROR_DELETE_ACTIVITY: {
code: "210015",
defaultMsg: `该活动已删除`,
},
ERROR_ACTIVITY_OVER: {
code: "210016",
defaultMsg: `该活动已经结束`,
},
};
const SHARE_TOTAL_COUNT = 5; //验证要求5人后,完成分享任务
const APP_NAME = "promotioncenter-3000000002590532"; // C端的APP NAME
const B_APP_NAME = "promotioncenter-3000000002693435"; // B端的APP NAME
const GOODSINFO =
"detail_url,approve_status,num_iid,title,nick,type,cid,pic_url,num,props,valid_thru,list_time,price,has_discount,has_invoice,has_warranty,has_showcase,modified,delist_time,postage_id,seller_cids,outer_id,sold_quantity";
const EIGHT_HOURS = 60 * 60 * 8 * 1000;
const DELETE_STATUS = {
DELETE: 0,
EXIST: 1,
};
const TEMPLATE_INFO = {
clients: "taobao,tmall",
template_id: 3000000002590532,
template_version: "0.0.3",
};
module.exports = {
OPEN_PRIZE_STATUS,
DRAW_STATUS,
LOGGER_TYPE,
CODE_TYPES,
SHARE_TOTAL_COUNT,
APP_NAME,
DELETE_STATUS,
GOODSINFO,
B_APP_NAME,
BaseDao,
DEFAULT_CODE_TYPES,
dateFormatter,
ResultsModel,
TBAPIS,
EIGHT_HOURS,
TEMPLATE_INFO,
};
class ResultsModel {
constructor() {
}
// 不填errorType,默认为系统错误
error(errorType, message) {
if (!errorType) {
errorType = { code: '500000', defaultMsg: '系统错误' };
}
return {
success: false,
code: errorType.errorCode,
message: message || errorType.defaultMsg
}
}
success(data) {
return {
success: true,
code: 10000,
data: data,
message: `成功`
}
}
}
module.exports = ResultsModel
\ No newline at end of file
function loginfo(context, handler) {
const {
fcName,
data,
env
} = context;
console.log(`函数名:${fcName}---函数handler:${handler}---当前环境:${env}---请求参数:${JSON.stringify(data)}`)
}
const passUrlList = [
'1688.cn',
'taobao.cn',
'taobao.com',
'taobao.net',
'tb.cn',
'tmall.com',
'zhifu.com',
'zhifubao.com',
'juhuasuan.com',
'tmall.hk',
'tmall.com.hk',
'dingtalk.com',
'yushanfang.com',
'guoguo-app.com',
'umeng.com'
]
/**
* 传入时间戳(毫秒)
* 根据时间戳转换成number型日期进行差值结算,比如:2020318-2020317
*/
function isNewDay(time) {
let date = new Date(time);
let dateNum = +(date.getFullYear() + "" + (date.getMonth() + 1) + "" + date.getDate());
let nowdate = new Date(Date.now());
let nowdateNum = +(nowdate.getFullYear() + "" + (nowdate.getMonth() + 1) + "" + nowdate.getDate());
return nowdateNum - dateNum > 0
}
const formatTime = function dateFormat(thisDate, fmt = "yyyy-MM-dd hh:mm:ss") {
var 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 (var 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;
}
function MathRand() {
var Num = "";
for (var i = 0; i < 6; i++) {
Num += Math.floor(Math.random() * 10);
}
return Num;
}
module.exports = {
loginfo,
MathRand,
passUrlList,
isNewDay,
formatTime
}
\ No newline at end of file
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
adler-32@~1.2.0:
version "1.2.0"
resolved "https://registry.npm.taobao.org/adler-32/download/adler-32-1.2.0.tgz#6a3e6bf0a63900ba15652808cb15c6813d1a5f25"
integrity sha1-aj5r8KY5ALoVZSgIyxXGgT0aXyU=
dependencies:
exit-on-epipe "~1.0.1"
printj "~1.1.0"
buffer-from@^1.1.0:
version "1.1.1"
resolved "https://registry.npm.taobao.org/buffer-from/download/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
integrity sha1-MnE7wCj3XAL9txDXx7zsHyxgcO8=
cfb@^1.1.2:
version "1.1.4"
resolved "https://registry.npm.taobao.org/cfb/download/cfb-1.1.4.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcfb%2Fdownload%2Fcfb-1.1.4.tgz#81fd35ede4c919d8f0962a94582e1dfaf7051e2a"
integrity sha1-gf017eTJGdjwliqUWC4d+vcFHio=
dependencies:
adler-32 "~1.2.0"
commander "^2.16.0"
crc-32 "~1.2.0"
printj "~1.1.2"
codepage@~1.14.0:
version "1.14.0"
resolved "https://registry.npm.taobao.org/codepage/download/codepage-1.14.0.tgz#8cbe25481323559d7d307571b0fff91e7a1d2f99"
integrity sha1-jL4lSBMjVZ19MHVxsP/5HnodL5k=
dependencies:
commander "~2.14.1"
exit-on-epipe "~1.0.1"
commander@^2.16.0:
version "2.20.3"
resolved "https://registry.npm.taobao.org/commander/download/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha1-/UhehMA+tIgcIHIrpIA16FMa6zM=
commander@~2.14.1:
version "2.14.1"
resolved "https://registry.npm.taobao.org/commander/download/commander-2.14.1.tgz#2235123e37af8ca3c65df45b026dbd357b01b9aa"
integrity sha1-IjUSPjevjKPGXfRbAm29NXsBuao=
commander@~2.17.1:
version "2.17.1"
resolved "https://registry.npm.taobao.org/commander/download/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf"
integrity sha1-vXerfebelCBc6sxy8XFtKfIKd78=
crc-32@~1.2.0:
version "1.2.0"
resolved "https://registry.npm.taobao.org/crc-32/download/crc-32-1.2.0.tgz#cb2db6e29b88508e32d9dd0ec1693e7b41a18208"
integrity sha1-yy224puIUI4y2d0OwWk+e0Ghggg=
dependencies:
exit-on-epipe "~1.0.1"
printj "~1.1.0"
exit-on-epipe@~1.0.1:
version "1.0.1"
resolved "https://registry.npm.taobao.org/exit-on-epipe/download/exit-on-epipe-1.0.1.tgz#0bdd92e87d5285d267daa8171d0eb06159689692"
integrity sha1-C92S6H1ShdJn2qgXHQ6wYVlolpI=
frac@~1.1.2:
version "1.1.2"
resolved "https://registry.npm.taobao.org/frac/download/frac-1.1.2.tgz#3d74f7f6478c88a1b5020306d747dc6313c74d0b"
integrity sha1-PXT39keMiKG1AgMG10fcYxPHTQs=
node-xlsx@^0.15.0:
version "0.15.0"
resolved "https://registry.npm.taobao.org/node-xlsx/download/node-xlsx-0.15.0.tgz#1f1b0d7adce5c706e86bfd96a5aa0005bf8a9dc3"
integrity sha1-HxsNetzlxwboa/2WpaoABb+KncM=
dependencies:
buffer-from "^1.1.0"
xlsx "^0.14.1"
printj@~1.1.0, printj@~1.1.2:
version "1.1.2"
resolved "https://registry.npm.taobao.org/printj/download/printj-1.1.2.tgz#d90deb2975a8b9f600fb3a1c94e3f4c53c78a222"
integrity sha1-2Q3rKXWoufYA+zoclOP0xTx4oiI=
ssf@~0.10.2:
version "0.10.3"
resolved "https://registry.npm.taobao.org/ssf/download/ssf-0.10.3.tgz#8eae1fc29c90a552e7921208f81892d6f77acb2b"
integrity sha1-jq4fwpyQpVLnkhII+BiS1vd6yys=
dependencies:
frac "~1.1.2"
xlsx@^0.14.1:
version "0.14.5"
resolved "https://registry.npm.taobao.org/xlsx/download/xlsx-0.14.5.tgz#3637e914d791bdca7382816e173f7d725ed0e0d2"
integrity sha1-NjfpFNeRvcpzgoFuFz99cl7Q4NI=
dependencies:
adler-32 "~1.2.0"
cfb "^1.1.2"
codepage "~1.14.0"
commander "~2.17.1"
crc-32 "~1.2.0"
exit-on-epipe "~1.0.1"
ssf "~0.10.2"
.tea
.DS_Store
.vscode
debug.log
node_modules
\ No newline at end of file
# 通用模板
## 库表规范
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_GOODSIDS: {
code: `220004`,
defaultMsg: `未配置活动商品`
},
// 用户不存在
ERROR_NO_USER: {
code: `310002`,
defaultMsg: `用户不存在`
},
// 需要成为会员才能助力哦
ERROR_NEEDMEMBER_DOHELP: {
code: `310004`,
defaultMsg: '需要成为会员才能助力哦'
},
// 用户信息更新失败
ERROR_USER_UPDATE_ERROR: {
code: `310005`,
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_VIP_JOIN_GAME: {
code: `720001`,
defaultMsg: `必须是会员才能参加游戏`
},
ERROR_NOT_ENOUGH_POWER: {
code: `720002`,
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",
];
// 每日 30 体力值
const INIT_POWER_EVERYDAY = 30;
// 游戏消耗体力值
const GAME_POWER_USE = 10;
module.exports = {
CODE_TYPES,
C_APP_NAME,
TBERROR,
DRAW_STATUS,
PRIZE_TYPE,
TAOBAO_SUCCESS_ORDER_STATUS,
INIT_POWER_EVERYDAY,
GAME_POWER_USE
}
\ 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 { resultModel } = require('../sdk');
/**
* 我的奖品列表
* @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.getMyPrize({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 { address, _id, name, phone, cityCode } = context.data;
if (!address) {
return resultModel.error(CODE_TYPES.PARAMS_ERROR, `缺少address信息`)
}
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 (!cityCode) {
return resultModel.error(CODE_TYPES.PARAMS_ERROR, `缺少cityCode`)
}
let awardSer = new AwardsService(context);
let result = await awardSer.recieveObjectPrize(_id, {name, phone, address, cityCode});
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);
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 } = require('../constants');
const { resultModel } = require('../sdk');
// 活动判断
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) {
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 { pipeActivityInfo } = require('./base.controller');
const { logger } = require('../utils');
const { resultModel } = require('../sdk');
const { CODE_TYPES } = require('../constants')
const UserService = require('../service/user.service');
// 完成新手引导
const completeGuide = async context => {
const { activityId } = context.data;
logger('completeGuide', context);
const res = await pipeActivityInfo(context, activityId);
if(res.code) return res;
const userService = new UserService(context);
const userInfo = await userService.getUserInfo();
if(!userInfo) return resultModel.error(CODE_TYPES.ERROR_NO_USER);
try {
await userService.updateUser(userInfo._id, {
$set: {
completeGuide: true
}
})
} catch (error) {
return resultModel.error(CODE_TYPES.ERROR_UPDATE_RETRY)
}
return resultModel.success(true)
}
module.exports = {
completeGuide
}
\ No newline at end of file
const { pipeActivityInfo,getActivityBaseInfoById } = require('./base.controller');
const { logger, getSellerSession, getToday } = require('../utils');
const { resultModel, TBAPIS } = require('../sdk');
const { CODE_TYPES, GAME_POWER_USE } = require('../constants')
const UserService = require('../service/user.service');
const JoinService = require('../service/join.service');
// 完成新手引导
const getGameInfo = async context => {
const { activityId } = context.data;
logger('getGameInfo', context);
const res = await pipeActivityInfo(context, activityId);
if(res.code) return res;
const userService = new UserService(context);
const userInfo = await userService.getUserInfo();
if(!userInfo) return resultModel.error(CODE_TYPES.ERROR_NO_USER);
return resultModel.success({
maxScore: userInfo.maxScore,
power: userInfo.power
})
}
const startGame = async context => {
const { activityId } = context.data;
logger('startGame', context);
const res = await pipeActivityInfo(context, activityId);
if(res.code) return res;
const userService = new UserService(context);
const userInfo = await userService.getUserInfo();
if(!userInfo) return resultModel.error(CODE_TYPES.ERROR_NO_USER);
const { taskInfo: { beMembership } } = res;
const { power } = userInfo;
if(beMembership) {
const { session } = await getSellerSession(context);
const { isvip, url } = await userService.getShopVip(session);
if(!isvip) {
return resultModel.error(CODE_TYPES.ERROR_VIP_JOIN_GAME, '必须是会员才能参与游戏', { url });
}
}
// 体力值不足
if(power< GAME_POWER_USE) {
return resultModel.error(CODE_TYPES.ERROR_NOT_ENOUGH_POWER);
}
await userService.updateUser(userInfo._id, {
$set: {
power: power - GAME_POWER_USE
}
})
const joinService = new JoinService(context);
const joinId = await joinService.addJoinRecord(userInfo);
return resultModel.success({
id: joinId
})
}
const submitGame = async context => {
const { activityId, score, id } = context.data;
logger('submitGame', context);
if(!activityId) return resultModel.error(CODE_TYPES.PARAMS_ERROR, '活动id必填');
if(!score && score !== 0) return resultModel.error(CODE_TYPES.PARAMS_ERROR, 'score必填')
if(!id) return resultModel.error(CODE_TYPES.PARAMS_ERROR, '本局id必填')
const userService = new UserService(context);
const userInfo = await userService.getUserInfo();
if(!userInfo) return resultModel.error(CODE_TYPES.ERROR_NO_USER);
let { maxScore, _id } = userInfo;
const now = Date.now();
if(score>= maxScore) {
await userService.updateUser(_id, {
$set: {
maxScore: score,
updateScoreTime: now
}
})
maxScore = score;
}
const joinService = new JoinService(context);
await joinService.updateJoinRecord(id, {
$set: {
score,
submitTime: now,
submitDay: getToday()
}
})
}
// 获取宝箱展示商品
const getBoxGoods = async context => {
const { activityId } = context.data;
logger('getBoxGoods', context);
const res = await pipeActivityInfo(context, activityId);
if(res.code) return res;
const userService = new UserService(context);
const userInfo = await userService.getUserInfo();
if(!userInfo) return resultModel.error(CODE_TYPES.ERROR_NO_USER);
let { exposureItemIds } = userInfo;
const { taskInfo: { browseItemIds } } = res;
const { session } = await getSellerSession(context);
const result = await TBAPIS.getItemListByItemIds(context, session, browseItemIds)
let list = result.items.map(v => {
return {
url: v.detail_url,
id: v.num_iid,
name: v.title,
image: v.pic_url,
desc: v.desc
}
})
return resultModel.success(list);
}
// 商品曝光
const exposureGoods = async context => {
let {
activityId,
itemIds
} = context.data;
let res = await pipeActivityInfo(context, activityId);
if (res.code) return res;
if (!itemIds || !Array.isArray(itemIds)) {
console.log(1);
return resultModel.error(CODE_TYPES.PARAMS_ERROR);
}
let userService = new UserService(context);
let userInfo = await userService.getUserInfo();
if (!userInfo) return resultModel.error(CODE_TYPES.ERROR_NO_USER);
let activityRes = await getActivityBaseInfoById(context);
if(!activityRes.success){
return activityRes;
}
let info = activityRes.data;
let goodIds = info.taskInfo.browseItemIds;
if(!goodIds){
return resultModel.error(CODE_TYPES.ERROR_NO_GOODSIDS);
}
let ids = goodIds.split(",");
let arr = userInfo.exposureItemIds;
if (!arr) arr = new Array();
for (let id of itemIds) {
console.log(id);
console.log(ids);
if(ids.indexOf(id)<0){
console.log(2);
return resultModel.error(CODE_TYPES.PARAMS_ERROR);
}
if (arr.indexOf(id) < 0) {
arr.push(id);
}
}
console.log(arr);
try {
let updateUserRes = await userService.updateUser(userInfo._id,
{
$set: {
updateTime: Date.now(),
exposureItemIds:arr
}
});
if (!updateUserRes) {
return resultModel.error(CODE_TYPES.ERROR_USER_UPDATE_ERROR);
}
} catch (error) {
console.log(error);
return resultModel.error(CODE_TYPES.SYSTEM_ERROR);
}
return resultModel.success();
};
//获取宝箱奖品
const getBoxPrize = async context => {
};
module.exports = {
getGameInfo,
startGame,
submitGame,
getBoxGoods,
exposureGoods,
}
\ No newline at end of file
const { resultModel } = require('../sdk');
const { logger, getSellerSession } = require('../utils');
const { CODE_TYPES } = require('../constants');
const ActivityService = require('../service/base.service');
const UserService = require('../service/user.service');
const AccessService = require('../service/access.service');
// const RecordService = require('../service/record.service');
const login = async context => {
const { activityId, userNick, avatar, inviteId } = context.data;
const { openId } = context;
logger('login', context);
if(!activityId) {
return resultModel.error(CODE_TYPES.PARAMS_ERROR, `activityId必填`);
}
if (!userNick || !avatar) {
return resultModel.error(CODE_TYPES.PARAMS_ERROR, `userNick, avatar 必填`);
}
const userService = new UserService(context);
const accessService = new AccessService(context);
// const recordService = new RecordService(context);
const activityService = new ActivityService(context);
let activityInfo = await activityService.getBaseInfo(activityId);
// 活动不存在
if (!activityInfo) {
return resultModel.error(CODE_TYPES.ERROR_NO_ACTIVITY);
}
let { session } = await getSellerSession(context, activityId);
// 是否是会员
// let { isvip } = await userService.getShopVip(session);
let isvip = true;
try {
const userInfo = await userService.addUserInfo(isvip, activityInfo);
await accessService.addAccess(userInfo.follow.newFollow);
// 受邀链接
if (inviteId && inviteId !== openId) {
// // 邀请人增加待领取水滴
// await userService.completeInviteTask({ inviteId, userNick });
// const recordService = new RecordService(context);
// await recordService.addReceiveShareRecord();
}
return resultModel.success({
inviteId: openId,
guideComplete: !!userInfo.guideComplete
})
} catch (error) {
console.log(error, '错误')
return resultModel.error(CODE_TYPES.SYSTEM_ERROR, '未知错误');
}
}
module.exports = {
login
}
\ 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 JOIN_DB_NAME = 'c_user_join';
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,
JOIN_DB_NAME
}
const {
getActivityBaseInfoById
} = require('./controller/base.controller');
const {
login
} = require('./controller/login.controller');
const { completeGuide } = require('./controller/do.controller');
const { getGameInfo, startGame, submitGame, getBoxGoods,exposureGoods } = require('./controller/game.controller');
exports.getActivityBaseInfoById = getActivityBaseInfoById;
exports.login = login;
exports.completeGuide = completeGuide;
exports.getGameInfo = getGameInfo;
exports.startGame = startGame;
exports.submitGame = submitGame;
exports.getBoxGoods = getBoxGoods;
exports.exposureGoods = exposureGoods;
{
"name": "drift2c",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@types/node": {
"version": "12.12.47",
"resolved": "https://registry.npm.taobao.org/@types/node/download/@types/node-12.12.47.tgz?cache=0&sync_timestamp=1592987688114&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fnode%2Fdownload%2F%40types%2Fnode-12.12.47.tgz",
"integrity": "sha1-UAe4hmovkVDegjNcp+JN0dWb37U="
},
"adler-32": {
"version": "1.2.0",
"resolved": "https://registry.npm.taobao.org/adler-32/download/adler-32-1.2.0.tgz",
"integrity": "sha1-aj5r8KY5ALoVZSgIyxXGgT0aXyU=",
"requires": {
"exit-on-epipe": "~1.0.1",
"printj": "~1.1.0"
}
},
"buffer-from": {
"version": "1.1.1",
"resolved": "https://registry.npm.taobao.org/buffer-from/download/buffer-from-1.1.1.tgz",
"integrity": "sha1-MnE7wCj3XAL9txDXx7zsHyxgcO8="
},
"cfb": {
"version": "1.1.4",
"resolved": "https://registry.npm.taobao.org/cfb/download/cfb-1.1.4.tgz?cache=0&sync_timestamp=1584080716545&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcfb%2Fdownload%2Fcfb-1.1.4.tgz",
"integrity": "sha1-gf017eTJGdjwliqUWC4d+vcFHio=",
"requires": {
"adler-32": "~1.2.0",
"commander": "^2.16.0",
"crc-32": "~1.2.0",
"printj": "~1.1.2"
}
},
"codepage": {
"version": "1.14.0",
"resolved": "https://registry.npm.taobao.org/codepage/download/codepage-1.14.0.tgz",
"integrity": "sha1-jL4lSBMjVZ19MHVxsP/5HnodL5k=",
"requires": {
"commander": "~2.14.1",
"exit-on-epipe": "~1.0.1"
},
"dependencies": {
"commander": {
"version": "2.14.1",
"resolved": "https://registry.npm.taobao.org/commander/download/commander-2.14.1.tgz?cache=0&sync_timestamp=1592632075120&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcommander%2Fdownload%2Fcommander-2.14.1.tgz",
"integrity": "sha1-IjUSPjevjKPGXfRbAm29NXsBuao="
}
}
},
"commander": {
"version": "2.17.1",
"resolved": "https://registry.npm.taobao.org/commander/download/commander-2.17.1.tgz?cache=0&sync_timestamp=1592632075120&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcommander%2Fdownload%2Fcommander-2.17.1.tgz",
"integrity": "sha1-vXerfebelCBc6sxy8XFtKfIKd78="
},
"crc-32": {
"version": "1.2.0",
"resolved": "https://registry.npm.taobao.org/crc-32/download/crc-32-1.2.0.tgz",
"integrity": "sha1-yy224puIUI4y2d0OwWk+e0Ghggg=",
"requires": {
"exit-on-epipe": "~1.0.1",
"printj": "~1.1.0"
}
},
"dayjs": {
"version": "1.8.29",
"resolved": "https://registry.npm.taobao.org/dayjs/download/dayjs-1.8.29.tgz?cache=0&sync_timestamp=1593704488382&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdayjs%2Fdownload%2Fdayjs-1.8.29.tgz",
"integrity": "sha1-XSPjQd5r+9IGwBE20vsPAYd4IPU="
},
"exit-on-epipe": {
"version": "1.0.1",
"resolved": "https://registry.npm.taobao.org/exit-on-epipe/download/exit-on-epipe-1.0.1.tgz",
"integrity": "sha1-C92S6H1ShdJn2qgXHQ6wYVlolpI="
},
"frac": {
"version": "1.1.2",
"resolved": "https://registry.npm.taobao.org/frac/download/frac-1.1.2.tgz",
"integrity": "sha1-PXT39keMiKG1AgMG10fcYxPHTQs="
},
"node-xlsx": {
"version": "0.15.0",
"resolved": "https://registry.npm.taobao.org/node-xlsx/download/node-xlsx-0.15.0.tgz",
"integrity": "sha1-HxsNetzlxwboa/2WpaoABb+KncM=",
"requires": {
"buffer-from": "^1.1.0",
"xlsx": "^0.14.1"
}
},
"printj": {
"version": "1.1.2",
"resolved": "https://registry.npm.taobao.org/printj/download/printj-1.1.2.tgz",
"integrity": "sha1-2Q3rKXWoufYA+zoclOP0xTx4oiI="
},
"ssf": {
"version": "0.10.3",
"resolved": "https://registry.npm.taobao.org/ssf/download/ssf-0.10.3.tgz",
"integrity": "sha1-jq4fwpyQpVLnkhII+BiS1vd6yys=",
"requires": {
"frac": "~1.1.2"
}
},
"taobao-mini-sdk": {
"version": "0.1.0",
"resolved": "https://registry.npm.taobao.org/taobao-mini-sdk/download/taobao-mini-sdk-0.1.0.tgz",
"integrity": "sha1-p23Kzxypk23tRYAFp7cQ1hZd71s=",
"requires": {
"@types/node": "^12.12.31",
"node-xlsx": "^0.15.0",
"typescript": "^3.8.3"
},
"dependencies": {
"taobao-mini-sdk": {
"version": "0.0.8",
"resolved": "https://registry.npm.taobao.org/taobao-mini-sdk/download/taobao-mini-sdk-0.0.8.tgz",
"integrity": "sha1-cEr8oDusceDwq09Jjloyh2N/mLA=",
"requires": {
"@types/node": "^12.12.31",
"typescript": "^3.8.3"
}
}
}
},
"typescript": {
"version": "3.9.6",
"resolved": "https://registry.npm.taobao.org/typescript/download/typescript-3.9.6.tgz?cache=0&sync_timestamp=1593845285428&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ftypescript%2Fdownload%2Ftypescript-3.9.6.tgz",
"integrity": "sha1-jz4BmKNMOuFwkbNVcdOv0xmZNlo="
},
"xlsx": {
"version": "0.14.5",
"resolved": "https://registry.npm.taobao.org/xlsx/download/xlsx-0.14.5.tgz?cache=0&sync_timestamp=1593422484071&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fxlsx%2Fdownload%2Fxlsx-0.14.5.tgz",
"integrity": "sha1-NjfpFNeRvcpzgoFuFz99cl7Q4NI=",
"requires": {
"adler-32": "~1.2.0",
"cfb": "^1.1.2",
"codepage": "~1.14.0",
"commander": "~2.17.1",
"crc-32": "~1.2.0",
"exit-on-epipe": "~1.0.1",
"ssf": "~0.10.2"
}
}
}
}
{
"name": "drift2c",
"version": "1.0.0",
"description": "漂流",
"main": "index.js",
"author": "qinhaitao",
"license": "ISC",
"sdkVersion": "*",
"dependencies": {
"node-xlsx": "^0.15.0",
"taobao-mini-sdk": "0.1.0",
"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;
module.exports = {
DEFAULT_CODE_TYPES,
resultModel: new ResultsModel(),
BaseDao,
TBAPIS,
dateFormatter,
transformBeijingDate,
getStartTimestamp,
getEndTimestamp
};
/**
* 访问明细
*/
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 } = 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});
// 奖品不存在
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}, {$set: {
useStock: 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 {*} document
*/
async sendAward(document) {
let { session } = await getSellerSession(this.context);
// 发放淘宝权益
let result = await sendTBAward(this.context, session, document);
if (result.code) {
return result;
}
let { _id } = document;
// 更新结果
let update = await this.awardsdao.update({_id}, {
$set: {
drawStatus: document.drawStatus,
remark: document.remark,
updateTime: Date.now()
}
});
console.log(`更新奖品状态`, update, document);
return document;
}
/**
* 我的奖品
* @param {*} param0
*/
async getMyPrize({openId, activityId}) {
console.log(`activiyId`, activityId);
return await this.awardsdao.find({openId, activityId}, {
sort: {
createTime: -1
}
});
}
// 领取实物
async recieveObjectPrize(_id, {name, phone, address, codeCode}) {
let result = {};
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,
address,
drawStatus: DRAW_STATUS.SUCCESS,
codeCode,
receiveTime: Date.now(),
updateTime: Date.now()
}
});
return true;
}catch(e) {
console.log(`领取实物错误:`, e);
return CODE_TYPES.SYSTEM_ERROR;
}
}
// 重新领取、待领取
async recieveEnamePrize(_id) {
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);
// 状态不是重新领取
if (![DRAW_STATUS.RETRY].includes(award.drawStatus)) {
return CODE_TYPES.ERROR_FORBIDDEN_OPE;
}
let { session } = await getSellerSession(this.context);
// 发放淘宝权益
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});
}
}
module.exports = AwardsService;
\ No newline at end of file
/**
* 基本信息
*/
const { BaseDao } = 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) {
return await this.activitydao.findOne({_id: activityId});
}
}
module.exports = BaseService;
\ No newline at end of file
const { BaseDao } = require('../sdk');
const { JOIN_DB_NAME } = require('../db');
const { getToday } = require('../utils');
class JoinService {
constructor(context) {
this.context = context;
this.joindao = new BaseDao(context, JOIN_DB_NAME);
}
async addJoinRecord(userInfo = {}) {
const { openId } = this.context;
const { activityId } = this.context.data;
const { userNick } = userInfo;
const record = {
activityId,
openId,
userNick,
score: 0,
createTime: Date.now(),
createDay: getToday(),
updateTime: Date.now(),
getLifeCard: false
}
return await this.joindao.insertOne(record)
}
async updateJoinRecord(_id, document) {
return this.joindao.update({_id}, document)
}
}
module.exports = JoinService;
\ No newline at end of file
/**
* 用户相关方法
*/
const { BaseDao, TBAPIS } = require('../sdk');
const BaseService = require('./base.service');
const { INIT_POWER_EVERYDAY } = require('../constants')
const { USER_DB_NAME } = require('../db');
const { getToday } = require('../utils');
class UserService extends BaseService{
constructor(context) {
super(context);
this.userdao = new BaseDao(context, USER_DB_NAME);
}
// 添加用户
async addUserInfo(isVip) {
const { openId, data: { activityId, isFollow } } = this.context;
let userInfo = await this.userdao.findOne({
openId,
activityId
});
// 新用户
if(!userInfo) {
const record = this.initUserData(isVip);
const result = await this.userdao.insertOne(record);
console.log(result, '新用户')
return record;
}
// 老用户
// 之前进入活动未关注,现在进入关注,则视为新关注店铺用户
const followBefore = userInfo.follow.flag;
const followNow = isFollow;
const newFollow = userInfo.follow.newFollow;
if(!followBefore && !newFollow && followNow) {
userInfo.follow.newFollow = true;
userInfo.follow.followTime = Date.now();
userInfo.follow.followDay = getToday();
}
// 之前进入活动非会员,现在进入会员,则视为新会员用户
const memberBefore = userInfo.member.flag;
const memberNow = isVip;
if(!memberBefore && memberNow) {
userInfo.member.newMember = true;
userInfo.member.bememberTime = Date.now();
userInfo.member.bememberDay = getToday();
};
const today = getToday();
if(!userInfo['login'][today]) {
userInfo.power = INIT_POWER_EVERYDAY;
}
// 添加登录次数
userInfo['login'][today] = userInfo['login'][today] ? userInfo['login'][today] + 1 : 1;
userInfo['info'][today] = userInfo['info'][today] || {};
await this.updateUser(userInfo._id, {
$set: {
updateTime: Date.now(),
follow: userInfo.follow,
login: userInfo['login'],
member: userInfo.member,
info: userInfo['info'],
power: userInfo.power
}
})
console.log(userInfo, '旧用户')
return userInfo;
}
// 初始化用户数据
initUserData(isVip) {
const { openId, data: { activityId, userNick, avatar, isFollow, inviteId } } = this.context;
let remainTimes = {
invites: 0, // 邀请成功待领取次数
browseGoods: 0, // 浏览商品待领取次数
member: 0, // 成为会员待领取次数
follow: 0,// 关注店铺待领取次数
};
let follow = {
flag: false
};
let followTaskComplete = false;
if(isFollow) {
follow.flag = true;
follow.newFollow = false;
follow.followTime = '';
remainTimes.follow = 1;
}
// 初始化会员情况
let member = {
flag: false
};
let memberTaskComplete = false;
if(isVip) {
member.flag = true;
member.newMember = false;
member.bememberTime = '';
remainTimes.member = 1;
}
const record = {
openId,
activityId,
userNick,
avatar,
inviteId,
member,
follow,
maxScore: 0,
power: INIT_POWER_EVERYDAY,
guideComplete: false,
exposureItemIds: [],
login: {
[getToday()]: 1
},
remainTimes,
info: {
[getToday()]: {}
},
taskInfo: {},
createTime: Date.now(),
updateTime: Date.now(),
createDay: getToday(),
tag: 0
}
return record;
}
/**
* 获取用户排名
*/
async getUserRank(maxScore) {
let { activityId } = this.context.data;
let rank = "";
let sameScoreList = await this.userdao.find({ maxScore: maxScore, activityId: activityId }, {
sort: { updateScoreTime: 1 }
});
console.log("sameScoreList.length", sameScoreList.length);
let gap = 0;
//说明有多个跟自己同分数的人
for (let j = 0; j < sameScoreList.length; j++) {
if (sameScoreList[j].openId == this.context.openId) {
gap = j;
}
}
rank = await this.userdao.count({ maxScore: { $gt: maxScore }, activityId: activityId });
console.log("rank", rank, "----", gap);
rank = rank + 1 + gap;
return { rank }
}
// 获取周边两位排名用户,及自己
async getSideScoreUser(maxScore, myRank) {
let { activityId } = this.context.data;
let sameScoreList = await this.userdao.find({ maxScore: maxScore, activityId: activityId }, {
sort: { updateScoreTime: 1 },
projection: {
userNick: 1,
avatar: 1,
macScore: 1
},
});
// 大于分数的一位
const greaterUser = await this.find.count({ maxScore: { $gt: maxScore }, activityId: activityId }, {
limit: 1,
projection: {
userNick: 1,
avatar: 1,
macScore: 1
},
sort: {
maxScore: 1,
updateScoreTime: 1
}
});
// 小于分数的一位
const lessUser = await this.find.count({ maxScore: { $lt: maxScore }, activityId: activityId }, {
limit: 1,
projection: {
userNick: 1,
avatar: 1,
macScore: 1
},
sort: {
maxScore: -1,
updateScoreTime: 1
}
});
// 没有和自己相同分数的人;
const noSameUser = sameScoreList.length === 1;
if(noSameUser) {
let arr = [];
if(greaterUser) {
arr.push(greaterUser)
};
arr.push(sameScoreList[0])
if(lessUser) {
arr.push(lessUser)
}
return arr
}
let gap = 0;
//说明有多个跟自己同分数的人
for (let j = 0; j < sameScoreList.length; j++) {
if (sameScoreList[j].openId == this.context.openId) {
gap = j;
}
}
}
/**
* @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.vipmock;
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}, document);
}
/**
* @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 logger = require('./package/logger');
module.exports = {
getSellerSession,
getToday,
sendTBAward,
lockUpdate,
logger
}
/**
* 获取商家session
*/
const { SELLER_INFO_DB_NAME, ACTIVITY_CONFIG_DB_NAME } = require('../../db');
const getSellerSession = async (context, activityId) => {
if (!activityId) {
activityId = context.data.activityId;
}
let activityConfigResult = await context.cloud.db.collection(ACTIVITY_CONFIG_DB_NAME).find({ _id: activityId });
if (!activityConfigResult || !activityConfigResult[0]) return {};
let result = await context.cloud.db.collection(SELLER_INFO_DB_NAME).find({
openId: activityConfigResult[0].openId
});
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) => {
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(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;
}
module.exports = getUserOrderlist;
\ No newline at end of file
/**
* 并发更新
* @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' && typeof daoqueryArr !=='array') {
console.log(`lockUpdate 参数错误`);
return false;
}
if (typeof daoqueryArr === 'object') {
daoqueryArr = [daoqueryArr];
}
let time = 0;
console.log(`进入update并发处理次数`, times);
let success = false;
try{
while (time++ < times && !success) {
// 依次锁住表
let updateRes = [];
daoqueryArr.forEach(async daoquery => {
let canupdate = await daoquery.dao.update(daoquery.query, {$set: {lockStatus: 2}});
updateRes.push(canupdate);
});
// 若都锁定成功了,执行内部调用
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为0`, 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为0`, 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 ()=> {
// return await this.activityprizedao.update({_id}, {$set: {
// useStock: useStock + 1,
// updateTime: Date.now()
// }});
// });
// console.log(res);
// if (!res) {
// return CODE_TYPES.ERROR_UPDATE_RETRY;
// }
// return true;
const logger = (name, context) => {
console.log(`-----调用方法:${name}-----`, `调用参数:${JSON.stringify(context.data)}`)
}
module.exports = logger;
\ No newline at end of file
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
.tea
.DS_Store
.vscode
debug.log
node_modules
\ No newline at end of file
# 通用模板
## 库表规范
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_RANKOEPN:{
code: `220004`,
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_EXPIRE:{
code: `730004`,
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_POP = {
...DRAW_STATUS,
NO_PRIZE:7,
NO_PRIZE_POPED:8
}
// 奖品类型
const AWARD_TYPE = {
VOTE: 1,
RANK: 2
}
// 奖品类型
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",
];
module.exports = {
CODE_TYPES,
C_APP_NAME,
TBERROR,
AWARD_TYPE,
DRAW_STATUS,
PRIZE_POP,
PRIZE_TYPE,
TAOBAO_SUCCESS_ORDER_STATUS
}
\ 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 ,DRAW_STATUS,PRIZE_POP} = 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;
}
//奖品失效时间为活动后48h
let endPrizeTime = pipeRes.endTime + 48 * 60 * 60 * 1000;
let awardSer = new AwardsService(context);
let list = await awardSer.getMyPrize({openId: context.openId, activityId: context.data.activityId,endPrizeTime});
let result = {
endPrizeTime,
list
}
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 { address, _id, name, phone, cityCode } = context.data;
if (!address) {
return resultModel.error(CODE_TYPES.PARAMS_ERROR, `缺少address信息`)
}
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 (!cityCode) {
return resultModel.error(CODE_TYPES.PARAMS_ERROR, `缺少cityCode`)
}
//奖品失效时间为活动后48h
let endPrizeTime = pipeRes.endTime + 48 * 60 * 60 * 1000;
let awardSer = new AwardsService(context);
let result = await awardSer.recieveObjectPrize(_id, {name, phone, address, cityCode},endPrizeTime);
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`)
}
//奖品失效时间为活动后48h
let endPrizeTime = pipeRes.endTime + 48 * 60 * 60 * 1000;
let awardsSer = new AwardsService(context);
let result = await awardsSer.recieveEnamePrize(_id,endPrizeTime);
if (result.code) {
return resultModel.error(result);
}
if (result.remark) {
return resultModel.error(result,result.remark);
}
return resultModel.success(result);
}
const rankingList = async(context) => {
let data = {list: [],prizeList: [],detail: {}};
let pipeRes = await pipeActivityInfo(context);
// 活动结束可以查看排行榜列表
if (pipeRes.code && pipeRes.code !== CODE_TYPES.ERROR_ACTIVITY_OVER.code) {
return pipeRes;
}
let userSer = new UserService(context);
let {activityId} = context.data;
let result = await userSer.getRankList(activityId);
//玩家昵称脱敏
if(result){
for(var i = 0; i < result.length;i++){
result[i].userNick = desensitizeUserNick(result[i].userNick);
}
}
data.list = result;
let userInfo = await userSer.getUserInfo();
if (userInfo) {
data.detail = {
maxScore: userInfo.maxScore,
userNick: desensitizeUserNick(userInfo.userNick)
};
let rank = '100+';
for(let i = 1; i <= result.length; i++ ) {
if (result[i-1].openId === userInfo.openId) {
rank = i;
}
}
//排行榜用户不足100名,且用户未入榜
if(result.length < 100 && rank === '100+'){
rank = result.length + 1;
}
data.detail.rank = rank;
}
//获取排行榜奖品列表
let awardsSer = new AwardsService(context);
data.prizeList = await awardsSer.getRankPrizeListByActivityId(activityId);
return resultModel.success(data);
}
// 我的排行榜奖品
const getMyRankPrize = async(context) => {
let pipeRes = await pipeActivityInfo(context);
// 活动结束可以查看我的排行榜奖品
if (pipeRes.code && pipeRes.code !== CODE_TYPES.ERROR_ACTIVITY_OVER.code) {
return pipeRes;
}
// let currentTime = Date.now();
// //排行榜开奖时间为活动结束时间
// let rankOpenTime = pipeRes.endTime - 8 * 60 * 60 * 1000;
// if (currentTime < rankOpenTime) {
// return resultModel.error(CODE_TYPES.ERROR_NO_RANKOEPN);
// }
let userSer = new UserService(context);
let userInfo = await userSer.getUserInfo();
// 用户未参与活动
if (!userInfo) {
return resultModel.error(CODE_TYPES.ERROR_NO_USER, `用户未参与活动`);
}
// 查找是否中排行榜奖品
let awardSer = new AwardsService(context);
let prize = await awardSer.getMyRankPrize(context.data.activityId);
if (!prize) {
if(!userInfo.noPrizePop){
//保存已弹出未获奖弹框信息
await userSer.setNoPrizePoped();
return resultModel.success({drawStatus:PRIZE_POP.NO_PRIZE});
}else{
return resultModel.success({drawStatus:PRIZE_POP.NO_PRIZE_POPED});
}
}
return resultModel.success({drawStatus:prize.drawStatus});
}
/**
* 玩家名称进行脱敏,首位字符+"**"+末尾字符
* @param {*} userNick
*/
function desensitizeUserNick(userNick){
if(userNick && userNick.length >= 2){
return userNick.substring(0,1) + '**' + userNick.substring(userNick.length-1);
}
return userNick;
}
module.exports = {
getMyPrizeList,
receiveObjectPrize,
receiveEnamePrize,
rankingList,
getMyRankPrize
}
\ No newline at end of file
const BaseService = require('../service/base.service');
const { CODE_TYPES } = 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) {
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');
exports.getActivityBaseInfoById = getActivityBaseInfoById;
const {
getMyPrizeList,
receiveObjectPrize,
receiveEnamePrize,
rankingList,
getMyRankPrize
} = require('./controller/awards.controller');
exports.getMyPrizeList = getMyPrizeList;
exports.receiveObjectPrize = receiveObjectPrize;
exports.receiveEnamePrize = receiveEnamePrize;
exports.rankingList = rankingList;
exports.getMyRankPrize = getMyRankPrize;
\ No newline at end of file
{
"name": "drift2c",
"version": "1.0.0",
"description": "漂流",
"main": "index.js",
"author": "qinhaitao",
"license": "ISC",
"sdkVersion": "*",
"dependencies": {
"node-xlsx": "^0.15.0",
"taobao-mini-sdk": "0.1.0",
"dayjs": "^1.8.28"
},
"config": {
"notNeedLogin": [
]
}
}
// 特别注意以下的引用方式,需要.default
const { BaseDao, TBAPI, Utils } = require('taobao-mini-sdk/lib').default;
const { DEFAULT_CODE_TYPES, dateFormatter,transformBeijingDate,getStartTimestamp,getEndTimestamp, ResultsModel} = Utils.default;
const TBAPIS = TBAPI.default;
module.exports = {
DEFAULT_CODE_TYPES,
ResultsModel,
BaseDao,
TBAPIS,
dateFormatter,
transformBeijingDate,
getStartTimestamp,
getEndTimestamp
};
/**
* 访问明细
*/
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,AWARD_TYPE,PRIZE_TYPE } = 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});
// 奖品不存在
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}, {$set: {
useStock: 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 {*} document
*/
async sendAward(document) {
let { session } = await getSellerSession(this.context);
// 发放淘宝权益
let result = await sendTBAward(this.context, session, document);
if (result.code) {
return result;
}
let { _id } = document;
// 更新结果
let update = await this.awardsdao.update({_id}, {
$set: {
drawStatus: document.drawStatus,
remark: document.remark,
updateTime: Date.now()
}
});
console.log(`更新奖品状态`, update, document);
return document;
}
/**
* 我的奖品
* @param {*} param0
*/
async getMyPrize({openId, activityId,endPrizeTime}) {
console.log(`activiyId`, activityId);
let list = await this.awardsdao.find({openId, activityId}, {
sort: {
createTime: -1
}
});
//未领取、重新领取的奖品是否过期
let now = Date.now();
for(var i = 0; i < list.length; i++){
if(list[i] && [DRAW_STATUS.WAITAWARD,DRAW_STATUS.RETRY].includes(list[i].drawStatus)){
if(now > endPrizeTime){
list[i].drawStatus = DRAW_STATUS.EXPIRED;
}
}
}
return list;
}
// 领取实物
async recieveObjectPrize(_id, {name, phone, address, codeCode},endPrizeTime) {
let result = {};
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;
}
// 状态不是重新领取
if (![DRAW_STATUS.WAITAWARD,DRAW_STATUS.RETRY].includes(award.drawStatus)) {
return CODE_TYPES.ERROR_FORBIDDEN_OPE;
}
//奖品过期失效
if(Date.now() > endPrizeTime){
await this.awardsdao.update({_id}, {
$set: {
drawStatus: DRAW_STATUS.EXPIRED,
remark: CODE_TYPES.ERROR_PRIZE_EXPIRE.defaultMsg,
updateTime: Date.now()
}
});
return CODE_TYPES.ERROR_PRIZE_EXPIRE;
}
try {
let result = await this.awardsdao.update({_id}, {
$set: {
receiveName: name,
phone,
address,
drawStatus: DRAW_STATUS.SUCCESS,
codeCode,
receiveTime: Date.now(),
updateTime: Date.now()
}
});
return true;
}catch(e) {
console.log(`领取实物错误:`, e);
return CODE_TYPES.SYSTEM_ERROR;
}
}
// 重新领取、待领取
async recieveEnamePrize(_id,endPrizeTime) {
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);
// 状态不是重新领取
if (![DRAW_STATUS.WAITAWARD,DRAW_STATUS.RETRY].includes(award.drawStatus)) {
return CODE_TYPES.ERROR_FORBIDDEN_OPE;
}
//奖品过期失效
if(Date.now() > endPrizeTime){
await this.awardsdao.update({_id}, {
$set: {
drawStatus: DRAW_STATUS.EXPIRED,
remark: CODE_TYPES.ERROR_PRIZE_EXPIRE.defaultMsg,
updateTime: Date.now()
}
});
result = {
drawStatus: DRAW_STATUS.EXPIRED,
remark: CODE_TYPES.ERROR_PRIZE_EXPIRE.defaultMsg
}
return result;
}
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});
}
/**
* 获取排行榜奖品列表
* @param {*} activityId
*/
async getRankPrizeListByActivityId(activityId){
return await this.activityprizedao.find({activityId,deleteStatus:1},{
sort:{
level: 1
}
});
}
// 我的排行榜奖品
async getMyRankPrize(activityId) {
return await this.awardsdao.findOne({activityId, openId: this.context.openId, prizeType: AWARD_TYPE.RANK});
}
}
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) {
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.vipmock;
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}, document);
}
/**
* @desc 根据用户主键id查找用户详情
* @param {用户的主键id} _id
* @returns 若用户不存在,返回null; 用户存在,返回用户信息(object对象)
*/
async getUserInfoById(_id) {
return await this.userdao.findOne({_id});
}
// 排行榜, 前100名
async getRankList(activityId) {
console.log(`activityId`, activityId);
return await this.userdao.find({activityId,updateScoreTime:{$exists: true}}, {
projection: {
userNick: 1,
avatar: 1,
maxScore: 1,
openId: 1
},
sort: {
maxScore: -1,
updateScoreTime: 1
},
limit: 100,
skip: 0,
});
}
/**
* 设置用户状态为未获奖信息已弹出
*/
async setNoPrizePoped(){
let { openId, data } = this.context;
let { activityId } = data;
await this.userdao.update({openId, activityId },{
$set:{
noPrizePop: "poped",
updateTime: Date.now()
}
});
}
}
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');
module.exports = {
getSellerSession,
getToday,
sendTBAward,
lockUpdate
}
/**
* 获取商家session
*/
const { SELLER_INFO_DB_NAME, ACTIVITY_CONFIG_DB_NAME } = require('../../db');
const getSellerSession = async (context, activityId) => {
if (!activityId) {
activityId = context.data.activityId;
}
let activityConfigResult = await context.cloud.db.collection(ACTIVITY_CONFIG_DB_NAME).find({ _id: activityId });
if (!activityConfigResult || !activityConfigResult[0]) return {};
let result = await context.cloud.db.collection(SELLER_INFO_DB_NAME).find({
openId: activityConfigResult[0].openId
});
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) => {
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(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;
}
module.exports = getUserOrderlist;
\ No newline at end of file
/**
* 并发更新
* @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' && typeof daoqueryArr !=='array') {
console.log(`lockUpdate 参数错误`);
return false;
}
if (typeof daoqueryArr === 'object') {
daoqueryArr = [daoqueryArr];
}
let time = 0;
console.log(`进入update并发处理次数`, times);
let success = false;
try{
while (time++ < times && !success) {
// 依次锁住表
let updateRes = [];
daoqueryArr.forEach(async daoquery => {
let canupdate = await daoquery.dao.update(daoquery.query, {$set: {lockStatus: 2}});
updateRes.push(canupdate);
});
// 若都锁定成功了,执行内部调用
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为0`, 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为0`, 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 ()=> {
// return await this.activityprizedao.update({_id}, {$set: {
// useStock: useStock + 1,
// updateTime: Date.now()
// }});
// });
// 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
.tea
.DS_Store
.vscode
debug.log
node_modules
\ No newline at end of file
# 通用模板
## 库表规范
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_TASK_TYPE: {
code: `430004`,
defaultMsg: `任务类型不存在`
},
//不支持手动领取
ERROR_TASK_NO_SUPPORT_RECEIVE: {
code: `430005`,
defaultMsg: `邀请好友任务不支持手动了领取奖励`
},
//暂无可体力值奖励
ERROR_TASK_NO_POWER_RECEIVE: {
code: `430006`,
defaultMsg: `暂无可体力值奖励`
},
//更新失败
ERROR_TASK_UPDATE_ERROR: {
code: `430007`,
defaultMsg: `更新失败`
},
//已是会员
ERROR_TASK_ALREADY_MEMBER: {
code: `430008`,
defaultMsg: `该用户已经是会员`
},
// 当日任务已完成
ERROR_TASK_DAY_COMPLETE: {
code: `430009`,
defaultMsg: `当日任务已完成`
},
//不支持手动领取
ERROR_TASK_NO_SUPPORT_COMPLETE: {
code: `430010`,
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: `库存不足`
}
}
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 TASK = {
// 成为会员
BE_MEMBER_SHIP: `beMembership`,
// 邀请好友
INVITE_FRIENDS: `inviteFriends`,
// 浏览页面
JUMP_LINK: `jumpLink`
,
//状态类型
STATUS: {
//未完成
unfinished: 0,
//已完成
completed: 1,
//已领取
received: 2
}
}
module.exports = {
CODE_TYPES,
C_APP_NAME,
TBERROR,
DRAW_STATUS,
PRIZE_TYPE,
TAOBAO_SUCCESS_ORDER_STATUS,
TASK
}
\ 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.getMyPrize({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 { address, _id, name, phone, cityCode } = context.data;
if (!address) {
return resultModel.error(CODE_TYPES.PARAMS_ERROR, `缺少address信息`)
}
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 (!cityCode) {
return resultModel.error(CODE_TYPES.PARAMS_ERROR, `缺少cityCode`)
}
let awardSer = new AwardsService(context);
let result = await awardSer.recieveObjectPrize(_id, {name, phone, address, cityCode});
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);
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 } = 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) {
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 {
pipeActivityInfo
} = require('./base.controller');
const {
CODE_TYPES,
TASK
} = require('../constants');
const {
ResultsModel
} = require('../sdk');
const {
getSellerSession,
getToday
} = require("../utils/index")
let resultModel = new ResultsModel();
const TaskService = require('../service/task.service');
const UserService = require('../service/user.service');
/**
* 任务列表
*/
const getTaskList = async (context) => {
// 判断活动
let pipeRes = await pipeActivityInfo(context);
//判断活动状态
if (pipeRes.success != null && !pipeRes.success) {
return pipeRes;
}
let {
activityId
} = context.data;
let taskService = new TaskService(context);
let taskList = await taskService.getTaskList(activityId);
if (!Array.isArray(taskList)) {
return resultModel.error(taskList);
}
return resultModel.success(taskList);
};
/**
* 领取任务奖励
*/
const receiveTaskRewards = async (context) => {
// 判断活动
let pipeRes = await pipeActivityInfo(context);
//判断活动状态
if (pipeRes.success != null && !pipeRes.success) {
return pipeRes;
}
//检查任务类型
let {
taskType
} = context.data;
let userService = new UserService(context);
let userInfo = await userService.getUserInfo();
if (null == userInfo) {
return resultModel.error(CODE_TYPES.ERROR_NO_USER);
}
//list 转map
let map = new Map();
pipeRes.taskList.forEach(row => {
map.set(row.type,row);
})
if (!map.has(taskType)) {
return resultModel.error(CODE_TYPES.ERROR_TASK_TYPE);
}
let remainTimes = userInfo.remainTimes;
let power = 0;
switch (taskType) {
case TASK.BE_MEMBER_SHIP:
//加入会员任务
power = remainTimes.member;
remainTimes.member = 0;
userInfo.member.completed = true;
break;
case TASK.JUMP_LINK:
//浏览页面任务
power = remainTimes.browsePage;
remainTimes.browsePage = 0;
break;
case TASK.INVITE_FRIENDS:
//邀请好友任务 不支持手动领取
return resultModel.error(CODE_TYPES.ERROR_TASK_NO_SUPPORT_RECEIVE);
}
//没有可领取的奖励
if (!power) {
return resultModel.error(CODE_TYPES.ERROR_TASK_NO_POWER_RECEIVE);
}
userInfo.power += power;
try {
let res = await userService.updateUser(userInfo._id, {
$set: {
...userInfo
}
});
if (!res) {
return resultModel.error(CODE_TYPES.ERROR_TASK_UPDATE_ERROR);
}
} catch (error) {
return resultModel.error(CODE_TYPES.SYSTEM_ERROR);
}
return resultModel.success();
};
/**
* 完成任务
*/
const completeTask = async (context) => {
// 判断活动
let pipeRes = await pipeActivityInfo(context);
//判断活动状态
if (pipeRes.success != null && !pipeRes.success) {
return pipeRes;
}
let {
taskType,activityId
} = context.data;
let userService = new UserService(context);
let userInfo = await userService.getUserInfo();
if (null == userInfo) {
return resultModel.error(CODE_TYPES.ERROR_NO_USER);
}
//list 转map
let map = new Map();
pipeRes.taskList.forEach(row => {
map.set(row.type,row);
})
console.log(map);
if (!map.has(taskType)) {
return resultModel.error(CODE_TYPES.ERROR_TASK_TYPE);
}
let task = map.get(taskType);
let today = getToday();
let remainTimes = userInfo.remainTimes;
let taskInfo = userInfo.taskInfo;
if (!taskInfo[today]) {
taskInfo[today] = {};
}
switch (taskType) {
case TASK.BE_MEMBER_SHIP:
//加入会员任务
let member = userInfo.member;
if (member.flag || member.newMember) {
return resultModel.error(CODE_TYPES.ERROR_TASK_ALREADY_MEMBER);
}
let {
session
} = await getSellerSession(context, activityId);
let {
isvip
} = await userService.getShopVip(session);
if (isvip) {
member.newMember = true;
member.bememberTime = new Date();
taskInfo[today].member = 1
remainTimes.member += task.value;
}
break;
case TASK.JUMP_LINK:
if (!taskInfo[today].browsePage) {
taskInfo[today].browsePage = 0;
}
//浏览页面任务
if (taskInfo[today].browsePage < task.times) {
remainTimes.browsePage += task.value;
}
taskInfo[today].browsePage += 1;
break;
case TASK.INVITE_FRIENDS:
//邀请好友任务
return resultModel.error(CODE_TYPES.ERROR_TASK_NO_SUPPORT_COMPLETE);
}
try {
let res = await userService.updateUser(userInfo._id, {
$set: {
...userInfo
}
});
if (!res) {
return resultModel.error(CODE_TYPES.ERROR_TASK_UPDATE_ERROR);
}
} catch (error) {
console.log(error);
return resultModel.error(CODE_TYPES.SYSTEM_ERROR);
}
return resultModel.success();
};
/**
* 测试邀请好友
*/
const inviteCompleted =async (context) => {
let taskService = new TaskService(context);
let taskList = await taskService.inviteCompleted(context.openId,context.activityId);
console.log(taskList);
};
module.exports = {
getTaskList,
receiveTaskRewards,
completeTask,
inviteCompleted
}
\ 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');
exports.getActivityBaseInfoById = getActivityBaseInfoById;
const {
getMyPrizeList,
receiveObjectPrize,
receiveEnamePrize
} = require('./controller/awards.controller');
const{
getTaskList,
receiveTaskRewards,
completeTask,
inviteCompleted
} = require('./controller/task.controller');
exports.getMyPrizeList = getMyPrizeList;
exports.receiveObjectPrize = receiveObjectPrize;
exports.receiveEnamePrize = receiveEnamePrize;
exports.getTaskList = getTaskList;
exports.receiveTaskRewards=receiveTaskRewards;
exports.completeTask=completeTask;
exports.inviteCompleted=inviteCompleted;
\ No newline at end of file
{
"name": "drift2c",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@types/node": {
"version": "12.12.47",
"resolved": "https://registry.npm.taobao.org/@types/node/download/@types/node-12.12.47.tgz?cache=0&sync_timestamp=1592987688114&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fnode%2Fdownload%2F%40types%2Fnode-12.12.47.tgz",
"integrity": "sha1-UAe4hmovkVDegjNcp+JN0dWb37U="
},
"adler-32": {
"version": "1.2.0",
"resolved": "https://registry.npm.taobao.org/adler-32/download/adler-32-1.2.0.tgz",
"integrity": "sha1-aj5r8KY5ALoVZSgIyxXGgT0aXyU=",
"requires": {
"exit-on-epipe": "~1.0.1",
"printj": "~1.1.0"
}
},
"buffer-from": {
"version": "1.1.1",
"resolved": "https://registry.npm.taobao.org/buffer-from/download/buffer-from-1.1.1.tgz",
"integrity": "sha1-MnE7wCj3XAL9txDXx7zsHyxgcO8="
},
"cfb": {
"version": "1.1.4",
"resolved": "https://registry.npm.taobao.org/cfb/download/cfb-1.1.4.tgz?cache=0&sync_timestamp=1584080716545&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcfb%2Fdownload%2Fcfb-1.1.4.tgz",
"integrity": "sha1-gf017eTJGdjwliqUWC4d+vcFHio=",
"requires": {
"adler-32": "~1.2.0",
"commander": "^2.16.0",
"crc-32": "~1.2.0",
"printj": "~1.1.2"
}
},
"codepage": {
"version": "1.14.0",
"resolved": "https://registry.npm.taobao.org/codepage/download/codepage-1.14.0.tgz",
"integrity": "sha1-jL4lSBMjVZ19MHVxsP/5HnodL5k=",
"requires": {
"commander": "~2.14.1",
"exit-on-epipe": "~1.0.1"
},
"dependencies": {
"commander": {
"version": "2.14.1",
"resolved": "https://registry.npm.taobao.org/commander/download/commander-2.14.1.tgz?cache=0&sync_timestamp=1592632075120&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcommander%2Fdownload%2Fcommander-2.14.1.tgz",
"integrity": "sha1-IjUSPjevjKPGXfRbAm29NXsBuao="
}
}
},
"commander": {
"version": "2.17.1",
"resolved": "https://registry.npm.taobao.org/commander/download/commander-2.17.1.tgz?cache=0&sync_timestamp=1592632075120&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcommander%2Fdownload%2Fcommander-2.17.1.tgz",
"integrity": "sha1-vXerfebelCBc6sxy8XFtKfIKd78="
},
"crc-32": {
"version": "1.2.0",
"resolved": "https://registry.npm.taobao.org/crc-32/download/crc-32-1.2.0.tgz",
"integrity": "sha1-yy224puIUI4y2d0OwWk+e0Ghggg=",
"requires": {
"exit-on-epipe": "~1.0.1",
"printj": "~1.1.0"
}
},
"dayjs": {
"version": "1.8.28",
"resolved": "https://registry.npm.taobao.org/dayjs/download/dayjs-1.8.28.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdayjs%2Fdownload%2Fdayjs-1.8.28.tgz",
"integrity": "sha1-N6piAd9IPQiWRctsj2zvbwxNvAc="
},
"exit-on-epipe": {
"version": "1.0.1",
"resolved": "https://registry.npm.taobao.org/exit-on-epipe/download/exit-on-epipe-1.0.1.tgz",
"integrity": "sha1-C92S6H1ShdJn2qgXHQ6wYVlolpI="
},
"frac": {
"version": "1.1.2",
"resolved": "https://registry.npm.taobao.org/frac/download/frac-1.1.2.tgz",
"integrity": "sha1-PXT39keMiKG1AgMG10fcYxPHTQs="
},
"node-xlsx": {
"version": "0.15.0",
"resolved": "https://registry.npm.taobao.org/node-xlsx/download/node-xlsx-0.15.0.tgz",
"integrity": "sha1-HxsNetzlxwboa/2WpaoABb+KncM=",
"requires": {
"buffer-from": "^1.1.0",
"xlsx": "^0.14.1"
}
},
"printj": {
"version": "1.1.2",
"resolved": "https://registry.npm.taobao.org/printj/download/printj-1.1.2.tgz",
"integrity": "sha1-2Q3rKXWoufYA+zoclOP0xTx4oiI="
},
"ssf": {
"version": "0.10.3",
"resolved": "https://registry.npm.taobao.org/ssf/download/ssf-0.10.3.tgz",
"integrity": "sha1-jq4fwpyQpVLnkhII+BiS1vd6yys=",
"requires": {
"frac": "~1.1.2"
}
},
"taobao-mini-sdk": {
"version": "0.1.0",
"resolved": "https://registry.npm.taobao.org/taobao-mini-sdk/download/taobao-mini-sdk-0.1.0.tgz",
"integrity": "sha1-p23Kzxypk23tRYAFp7cQ1hZd71s=",
"requires": {
"@types/node": "^12.12.31",
"node-xlsx": "^0.15.0",
"typescript": "^3.8.3"
},
"dependencies": {
"taobao-mini-sdk": {
"version": "0.0.8",
"resolved": "https://registry.npm.taobao.org/taobao-mini-sdk/download/taobao-mini-sdk-0.0.8.tgz",
"integrity": "sha1-cEr8oDusceDwq09Jjloyh2N/mLA=",
"requires": {
"@types/node": "^12.12.31",
"typescript": "^3.8.3"
}
}
}
},
"typescript": {
"version": "3.9.5",
"resolved": "https://registry.npm.taobao.org/typescript/download/typescript-3.9.5.tgz",
"integrity": "sha1-WG8NujAM3ovlLdGsT34QCcGxPzY="
},
"xlsx": {
"version": "0.14.5",
"resolved": "https://registry.npm.taobao.org/xlsx/download/xlsx-0.14.5.tgz?cache=0&sync_timestamp=1593422484071&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fxlsx%2Fdownload%2Fxlsx-0.14.5.tgz",
"integrity": "sha1-NjfpFNeRvcpzgoFuFz99cl7Q4NI=",
"requires": {
"adler-32": "~1.2.0",
"cfb": "^1.1.2",
"codepage": "~1.14.0",
"commander": "~2.17.1",
"crc-32": "~1.2.0",
"exit-on-epipe": "~1.0.1",
"ssf": "~0.10.2"
}
}
}
}
{
"name": "drift2c",
"version": "1.0.0",
"description": "漂流",
"main": "index.js",
"author": "qinhaitao",
"license": "ISC",
"sdkVersion": "*",
"dependencies": {
"node-xlsx": "^0.15.0",
"taobao-mini-sdk": "0.1.0",
"dayjs": "^1.8.28"
},
"config": {
"notNeedLogin": []
}
}
// 特别注意以下的引用方式,需要.default
const { BaseDao, TBAPI, Utils } = require('taobao-mini-sdk/lib').default;
const { DEFAULT_CODE_TYPES, dateFormatter,transformBeijingDate,getStartTimestamp,getEndTimestamp, ResultsModel} = Utils.default;
const TBAPIS = TBAPI.default;
module.exports = {
DEFAULT_CODE_TYPES,
ResultsModel,
BaseDao,
TBAPIS,
dateFormatter,
transformBeijingDate,
getStartTimestamp,
getEndTimestamp
};
/**
* 访问明细
*/
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 } = 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});
// 奖品不存在
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}, {$set: {
useStock: 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 {*} document
*/
async sendAward(document) {
let { session } = await getSellerSession(this.context);
// 发放淘宝权益
let result = await sendTBAward(this.context, session, document);
if (result.code) {
return result;
}
let { _id } = document;
// 更新结果
let update = await this.awardsdao.update({_id}, {
$set: {
drawStatus: document.drawStatus,
remark: document.remark,
updateTime: Date.now()
}
});
console.log(`更新奖品状态`, update, document);
return document;
}
/**
* 我的奖品
* @param {*} param0
*/
async getMyPrize({openId, activityId}) {
console.log(`activiyId`, activityId);
return await this.awardsdao.find({openId, activityId}, {
sort: {
createTime: -1
}
});
}
// 领取实物
async recieveObjectPrize(_id, {name, phone, address, codeCode}) {
let result = {};
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,
address,
drawStatus: DRAW_STATUS.SUCCESS,
codeCode,
receiveTime: Date.now(),
updateTime: Date.now()
}
});
return true;
}catch(e) {
console.log(`领取实物错误:`, e);
return CODE_TYPES.SYSTEM_ERROR;
}
}
// 重新领取、待领取
async recieveEnamePrize(_id) {
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);
// 状态不是重新领取
if (![DRAW_STATUS.RETRY].includes(award.drawStatus)) {
return CODE_TYPES.ERROR_FORBIDDEN_OPE;
}
let { session } = await getSellerSession(this.context);
// 发放淘宝权益
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});
}
}
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) {
return await this.activitydao.findOne({_id: activityId});
}
}
module.exports = BaseService;
\ No newline at end of file
const {
BaseDao
} = require("../sdk");
const {
getToday,
getSellerSession
} = require("../utils/index");
const {
TASK,
CODE_TYPES
} = require("../constants");
const UserService = require('../service/user.service');
const DB_USER = "c_user";
const DB_ACTIVITY_CONFIG = "b_activity_config";
class TaskService {
constructor(context) {
this.context = context;
this.userDao = new BaseDao(context, DB_USER)
this.activityConfigDao = new BaseDao(context, DB_ACTIVITY_CONFIG)
}
/**
* 获取任务列表
* @param {*} activityId
*/
async getTaskList(activityId) {
try {
//查找活动配置
let act = await this.activityConfigDao.findOne({
_id: activityId
}, {
projection: {
taskList: 1
}
});
//查找用户详情
let today = getToday();
let userInfo = await this.userDao.findOne({
activityId: activityId,
openId: this.context.openId
});
if (!userInfo) {
return CODE_TYPES.ERROR_NO_USER;
}
//任务列表
let taskList = act.taskList;
console.log(taskList);
console.log(userInfo);
//组装任务列表
let array = new Array();
for (let task of taskList) {
if (task.type == TASK.BE_MEMBER_SHIP) {
console.log(1);
let member = userInfo.member;
let status = TASK.STATUS.unfinished;
let progress = "0/1";
if (member.flag || member.newMember) {
status = TASK.STATUS.completed;
progress = "1/1"
}
if (member.completed) {
status = TASK.STATUS.received;
}
//获取会员入会链接
let {
session
} = await getSellerSession(this.context, activityId);
let {
url
} = await new UserService(this.context).getShopVip(session);
array.push(this.getTaskInfo(task.type, status, true, progress, task.value, url))
} else if (task.type == TASK.INVITE_FRIENDS) {
console.log(2);
let taskInfo = userInfo.taskInfo[today];
let remainTimes = userInfo.remainTimes;
let status = TASK.STATUS.unfinished;
let dayTimes = task.times;
let progress = "0/" + dayTimes;
console.log(taskInfo);
if (taskInfo) {
let invites = taskInfo.invites;
let times = !!invites ? invites.length : 0;
//progress = times >dayTimes ? dayTimes : times +"/"+dayTimes;
progress = times + "/" + dayTimes;
if (times >= dayTimes) {
status = TASK.STATUS.completed;
if (!remainTimes.invites) {
status = TASK.STATUS.received;
}
}
}
array.push(this.getTaskInfo(task.type, status, false, progress, task.value))
} else if (task.type == TASK.JUMP_LINK) {
console.log(3);
let taskInfo = userInfo.taskInfo[today];
let remainTimes = userInfo.remainTimes;
let status = TASK.STATUS.unfinished;
let dayTimes = task.times;
let progress = "0/" + dayTimes;
if (taskInfo) {
let times = taskInfo.browsePage;
//progress = times >dayTimes ? dayTimes : times +"/"+dayTimes;
progress = times + "/" + dayTimes;
if (times >= dayTimes) {
status = TASK.STATUS.completed;
if (!remainTimes.browsePage) {
status = TASK.STATUS.received;
}
}
}
array.push(this.getTaskInfo(task.type, status, false, progress, task.value))
}
}
console.log(array);
return array;
} catch (error) {
console.log(`查询任务列表失败:`, error);
return CODE_TYPES.SYSTEM_ERROR;
}
}
//组装任务对象
getTaskInfo(taskType, status, isOnce, progress, power, url) {
return {
"taskType": taskType,
"status": status,
"isOnce": isOnce,
"progress": progress,
"power": power,
"url": url
};
}
/**
* 邀请好友任务完成
* @param {*} openId 邀请人openId
* @param {*} activityId 活动id
*/
async inviteCompleted(openId, activityId) {
let userInfo = await this.userDao.findOne({
activityId: activityId,
openId: openId
});
if (!userInfo) {
return CODE_TYPES.ERROR_NO_USER;
}
//查找活动配置
let act = await this.activityConfigDao.findOne({
_id: activityId
}, {
projection: {
taskList: 1
}
});
let taskList = act.taskList;
//list 转map
let map = new Map();
taskList.forEach(row => {
map.set(row.type, row);
})
let taskInfo = map.get(TASK.INVITE_FRIENDS);
let today = getToday();
let invites = userInfo.taskInfo[today].invites;
if (!invites) {
invites = [];
}
if (invites.length < taskInfo.times) {
userInfo.power += taskInfo.value;
}
invites.push({
openId: openId,
times: today
});
userInfo.taskInfo[today].invites = invites;
console.log(userInfo);
try {
let res = await this.userDao.update({
_id: userInfo._id
}, {
$set: {
...userInfo
}
});
if (!res) {
return CODE_TYPES.ERROR_TASK_UPDATE_ERROR;
}
} catch (error) {
console.log(error);
return CODE_TYPES.SYSTEM_ERROR;
}
return true;
};
}
module.exports = TaskService;
\ No newline at end of file
let taskList = [
{
"type": "beMembership",
"value": 20
},
{
"times": 3,
"itemIds": "111,222,333",
"taskRateType": 3,
"type": "browseGoods",
"title": "标题",
"value": 90
},
{
"times": 3,
"itemIds": "jumpLink",
"link": "http://www.taobao.com",
"taskRateType": 3,
"title": "标题",
"value": 90
},
{
"times": 3,
"taskRateType": 1,
"type": "inviteFriends",
"title": "标题",
"value": 30
}
];
taskList.forEach((task)=>{
console.log(task);
});
\ 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,
url:"http://www.baidu.com"
}
}
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}, document);
}
/**
* @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');
module.exports = {
getSellerSession,
getToday,
sendTBAward,
lockUpdate
}
/**
* 获取商家session
*/
const { SELLER_INFO_DB_NAME, ACTIVITY_CONFIG_DB_NAME } = require('../../db');
const getSellerSession = async (context, activityId) => {
if (!activityId) {
activityId = context.data.activityId;
}
let activityConfigResult = await context.cloud.db.collection(ACTIVITY_CONFIG_DB_NAME).find({ _id: activityId });
if (!activityConfigResult || !activityConfigResult[0]) return {};
let result = await context.cloud.db.collection(SELLER_INFO_DB_NAME).find({
openId: activityConfigResult[0].openId
});
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) => {
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(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;
}
module.exports = getUserOrderlist;
\ No newline at end of file
/**
* 并发更新
* @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' && typeof daoqueryArr !=='array') {
console.log(`lockUpdate 参数错误`);
return false;
}
if (typeof daoqueryArr === 'object') {
daoqueryArr = [daoqueryArr];
}
let time = 0;
console.log(`进入update并发处理次数`, times);
let success = false;
try{
while (time++ < times && !success) {
// 依次锁住表
let updateRes = [];
daoqueryArr.forEach(async daoquery => {
let canupdate = await daoquery.dao.update(daoquery.query, {$set: {lockStatus: 2}});
updateRes.push(canupdate);
});
// 若都锁定成功了,执行内部调用
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为0`, 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为0`, 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 ()=> {
// return await this.activityprizedao.update({_id}, {$set: {
// useStock: useStock + 1,
// updateTime: Date.now()
// }});
// });
// 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
{
"runtime": "nodejs8",
"version": "1.0"
}
\ No newline at end of file
const ACTIVITY_BASE_CONFIG = "b_activity_config";
const ACTIVITY_PRIZE_CONFIG = "b_prize_config";
const ACTIVITY_SELLER_SAVE = "a_seller_info";
const ACTIVITY_INSTANCE = "miniapp_instantiate";
const USER_PRIZE = "c_awards_info";
const USER_INFO = "c_user"
module.exports = {
ACTIVITY_BASE_CONFIG,
ACTIVITY_PRIZE_CONFIG,
ACTIVITY_SELLER_SAVE,
ACTIVITY_INSTANCE,
USER_PRIZE,
USER_INFO
};
// const {
// formatTime
// } = require('../utils/utils');
// const secret = '99f00e460bfae520f85ae5f5a38e5b4a';
// const appKey = 28640163;
const getSellerInfoByContext = (context) => {
return {
// timestamp: formatTime(new Date(), "yyyy-MM-dd hh:mm:ss"),
// open_id: context.openId,
// 'appkey': appKey,
// 'appsecret': secret,
session: context.accessToken,
// REST_URL: 'http://gw.api.taobao.com/router/rest'
};
};
const getConsumerSeller = ({ accessToken, openId }) => {
return {
// timestamp: formatTime(new Date(), "yyyy-MM-dd hh:mm:ss"),
open_id: openId,
// 'appkey': appKey,
// 'appsecret': secret,
session: accessToken,
// REST_URL: 'http://gw.api.taobao.com/router/rest'
};
};
module.exports = {
getSellerInfoByContext,
getConsumerSeller,
};
const ActivityConfigService = require("../service/activityconfig.service");
const {
CODE_TYPES,
TASK_CHECK_TYPE,
TASK_TYPE_CHINA,
} = require("../utils/constants");
const ResultsModel = require("../utils/results.model");
let resultsModel = new ResultsModel();
const { DELETE_STATUS } = require("../utils/constants");
const ActivitySellerService = require("../service/activityseller.service");
let ActivityTopService = require("../service/activitytop.service");
let ActivityInstanceService = require("../service/activityinstance.service");
const { loginfo, passUrlList } = require("../utils/utils");
const regTestFn = (value, reg) => {
return reg.test(value);
};
const getIds = (data) => {
return data.reduce((s, v) => {
if (v._id) {
s = [...s, v._id];
}
return s;
}, []);
};
const urlCheck = (link, passUrlList) => {
if (!link.startsWith("http://") && !link.startsWith("https://")) {
return {
status: false,
message: "页面链接必须以https://或http://开头",
};
}
let passUrl = false;
passUrlList.some((i) => {
if (~link.indexOf(i)) {
passUrl = true;
return true;
}
return false;
});
if (!passUrl)
return {
status: false,
message: "域名校验不通过",
};
return {
status: true,
message: "",
};
};
//openId获取活动列表
const getActivityListByOpenId = async function (context) {
loginfo(context, "getActivityListByOpenId");
const { openId } = context;
let ActivityConfig = new ActivityConfigService(context);
let ActivityInstance = new ActivityInstanceService(context);
let ActivitySeller = new ActivitySellerService(context);
try {
let sellInfo = await ActivitySeller.findSellerInfo(openId);
console.log(sellInfo, "sellInfo");
if (sellInfo && sellInfo[0]) {
let result = await ActivityConfig.getActivityListByOpenId(
sellInfo && sellInfo[0].shopId,
context.data
);
let instanceInfo = await ActivityInstance.getMiniAppInstanceInfo(
context.openId
);
if (result) {
result.list.map((item) => {
item.activityId = item._id;
item.onlineUrl =
instanceInfo && instanceInfo[0] ? instanceInfo[0].online_url : "";
return item;
});
return resultsModel.success(result);
}
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, "查询活动列表失败");
} else {
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, "小程序未授权");
}
} catch (e) {
console.log("catch", "查询活动列表失败");
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, "查询活动列表失败");
}
};
//保存或编辑活动信息
const saveActivityInfoByHasId = async function (context) {
loginfo(context, "saveActivityInfoByHasId");
let ActivityConfig = new ActivityConfigService(context);
let ActivityTop = new ActivityTopService(context);
let nowTime = Date.now();
const { openId } = context;
let {
title,
subtitle,
activityId = "",
startTime,
endTime,
rule,
logoImg,
prizeInfoList = [],
taskList = [],
commandTitle,
beenInvitedText,
commandImg,
} = context.data;
let initialData;
if (activityId) {
initialData = await ActivityConfig.getActivityInfoByActivityId(activityId);
}
if (!title) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, "活动名称为空");
} else if (title.length > 12) {
return resultsModel.error(
CODE_TYPES.PARAMS_ERROR,
"活动名称不可超过12个字"
);
}
if (!subtitle) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, "活动副标题为空");
} else if (subtitle.length > 16) {
return resultsModel.error(
CODE_TYPES.PARAMS_ERROR,
"活动副标题不可超过16个字"
);
}
if (!logoImg) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, "logo图片必须配置");
}
if (!commandTitle) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, "邀请者淘口令必须配置");
}
if (!beenInvitedText) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, "被邀请者文案必须配置");
}
if (!commandImg) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, "淘口令图片必须配置");
}
if (taskList.length === 0) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, "任务配置必须配置一项");
} else {
let canPassTask = { status: true, message: "" };
taskList = taskList.map((i) => {
if (TASK_CHECK_TYPE.link.list.includes(i.type)) {
if (canPassTask.status) {
canPassTask = urlCheck(i.link, passUrlList);
}
}
if (TASK_CHECK_TYPE.itemIds.list.includes(i.type)) {
if (!i.itemIds) {
canPassTask = {
status: false,
message: TASK_TYPE_CHINA[i.type] + "必须配置",
};
}
}
if (TASK_CHECK_TYPE.value.list.includes(i.type)) {
if (!i.value) {
canPassTask = {
status: false,
message: TASK_TYPE_CHINA[i.type] + "任务值必须配置",
};
}
if (!regTestFn(i.value, TASK_CHECK_TYPE.value.reg)) {
canPassTask = {
status: false,
message:
TASK_TYPE_CHINA[i.type] +
"任务值在" +
TASK_CHECK_TYPE.value.regName,
};
}
}
if (TASK_CHECK_TYPE.times.list.includes(i.type)) {
if (!i.times) {
canPassTask = {
status: false,
message: TASK_TYPE_CHINA[i.type] + "任务次数必须配置",
};
}
}
if (TASK_CHECK_TYPE.title.list.includes(i.type)) {
if (!i.title) {
canPassTask = {
status: false,
message: TASK_TYPE_CHINA[i.type] + "任务标题必须配置",
};
}
}
if (TASK_CHECK_TYPE.taskRateType.list.includes(i.type)) {
if (!i.taskRateType) {
canPassTask = {
status: false,
message: TASK_TYPE_CHINA[i.type] + "任务类型必须配置",
};
}
}
i.value = +i.value;
i.times = +i.times || "";
i.taskRateType = +i.taskRateType || "";
return i;
});
if (!canPassTask.status)
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, canPassTask.message);
}
if (prizeInfoList.length === 0) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, "请配置排行榜奖品");
} else if (prizeInfoList.length > 8) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, "奖品最多配置8个档位");
} else {
let canPassPrize = { status: true, message: "" };
prizeInfoList.forEach((item) => {
if (!item.ename || !item.rank) {
canPassPrize.status = false;
canPassPrize.message = "奖品配置参数缺失";
}
});
let maxRank = ~prizeInfoList[prizeInfoList.length - 1].rank.indexOf("-")
? prizeInfoList[prizeInfoList.length - 1].rank.split("-")[1]
: prizeInfoList[prizeInfoList.length - 1].rank;
if (maxRank > 100)
canPassPrize = { status: false, message: "奖品排名配置不可超过100" };
if (!canPassPrize.status) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, canPassPrize.message);
}
}
if (!rule) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, "活动规则不可为空");
}
if (!startTime || !endTime) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, "请配置活动时间");
} else if (!activityId && startTime < Date.now()) {
return resultsModel.error(
CODE_TYPES.PARAMS_ERROR,
"新建活动开始时间需小于当前时间"
);
} else if (startTime > endTime) {
return resultsModel.error(
CODE_TYPES.PARAMS_ERROR,
"开始时间不得大于结束时间"
);
} else if (
activityId &&
+initialData.baseConfig.startTime <= nowTime &&
nowTime <= +initialData.baseConfig.endTime
) {
if (+startTime !== +initialData.baseConfig.startTime) {
return resultsModel.error(
CODE_TYPES.PARAMS_ERROR,
"活动开始后不可更改活动开始时间"
);
}
}
let shopInfoResult = await ActivityTop.getShopId();
if (!shopInfoResult.success) {
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, "获取淘宝店铺信息失败");
}
console.log(shopInfoResult, "shopInfo");
const baseData = {
title,
subtitle,
startTime,
endTime,
rule,
shopId: shopInfoResult.data.sid,
openId,
beenInvitedText,
commandTitle,
commandImg,
logoImg,
taskList,
};
prizeInfoList = prizeInfoList.map((v, i) => {
return { ...v, level: i + 1 };
});
let result;
let allPrizeList = prizeInfoList;
try {
if (!activityId) {
result = await ActivityConfig.saveNewActivity(
baseData,
prizeInfoList,
openId
);
} else {
result = await ActivityConfig.updateBaseConfigInfo(activityId, baseData);
let allNewIds = getIds(allPrizeList);
let oldPrizeList = await ActivityConfig.getAllOldPrizeList(activityId);
let oldIds = getIds(oldPrizeList);
let deleteIds = [];
let bothIds = oldIds.reduce((s, i) => {
if (allNewIds.includes(i)) {
s = [...s, i];
} else {
deleteIds.push(i);
}
return s;
}, []);
for (let i = 0; i < deleteIds.length; i++) {
result = await ActivityConfig.updateDeleteStatusById(deleteIds[i]);
}
for (let i = 0; i < allPrizeList.length; i++) {
if (!allPrizeList[i]._id) {
result = await ActivityConfig.insertPrizeByActivityId(
activityId,
allPrizeList[i]
);
} else if (bothIds.includes(allPrizeList[i]._id)) {
result = await ActivityConfig.updateOnePrizeById(
allPrizeList[i]._id,
allPrizeList[i]
);
}
}
}
if (result || result === 0) {
return resultsModel.success(true);
} else {
console.log(result, "保存活动失败");
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, "保存活动失败");
}
} catch (e) {
console.log(e, "保存活动失败");
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, "保存活动失败");
}
};
//删除活动
const deleteActivityById = async function (context) {
loginfo(context, "deleteActivityById");
let ActivityConfig = new ActivityConfigService(context);
const { activityId = "" } = context.data;
try {
if (activityId) {
let result = await ActivityConfig.deleteActivityByActivityId(activityId);
if (result === 0 || result) {
return resultsModel.success(true);
} else {
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, "删除活动失败");
}
} else {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, "删除活动id不存在");
}
} catch (e) {
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, "删除活动失败");
}
};
//获取活动信息
const getActivityInfo = async function (context) {
loginfo(context, "getActivityInfoByActivityId");
let ActivityConfig = new ActivityConfigService(context);
const { activityId = "" } = context.data;
try {
if (activityId) {
let result = await ActivityConfig.getActivityInfoByActivityId(activityId);
let prizeInfoList = result.prizeConfig;
let activityInfo = {
activityId,
...result.baseConfig,
prizeInfoList,
};
return resultsModel.success(activityInfo);
} else {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, "当前活动不存在");
}
} catch (e) {
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, "获取活动配置信息失败");
}
};
module.exports = {
getActivityListByOpenId,
saveActivityInfoByHasId,
deleteActivityById,
getActivityInfo,
};
const {
formatTime
} = require('../utils/utils');
const {
CODE_TYPES,
EIGHT_HOURS
} = require('../utils/constants');
const ResultsModel = require('../utils/results.model');
let resultsModel = new ResultsModel();
//生成规则
module.exports = async (context) => {
console.log(JSON.stringify(context.data), 'context.data')
let {
title = '', startTime = '', endTime = '', prizeInfoList = []
} = context.data;
if (!title) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, '活动名称为空');
} else if (title.length > 12) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, '活动名称不可超过12个字');
}
if (!startTime || !endTime) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, '请配置活动时间');
} else if (startTime > endTime) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, '开始时间不得大于结束时间');
}
if (prizeInfoList.length === 0) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, '奖品至少配置3名');
} else if (prizeInfoList.length > 8) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, '奖品最多配置8个档位');
} else {
let canPassPrize = true;
prizeInfoList.forEach(item => {
if (!item.ename || !item.rank) {
canPassPrize = false;
}
})
if (!canPassPrize) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, '奖品配置不正确');
}
}
let resultRule = '';
let startTimeDate = formatTime(new Date(+startTime + EIGHT_HOURS), "yyyy-MM-dd hh:mm:ss");
let endTimeDate = formatTime(new Date(+endTime + EIGHT_HOURS), "yyyy-MM-dd hh:mm:ss");
resultRule = `1.活动时间:${startTimeDate}--${endTimeDate};\n2.活动结束根据排行榜排名获得对应的奖励,每个人取活动中单局最高分;\n3.每天3次免费参与资格,超过次数后邀请群内好友参与游戏可获得额外次数(每个用户可助力一次);\n4.参与签到可获得道具,每局结束游戏后可使用道具;\n5.完成任务每天可获得额外的复活机会,每局只能使用一张复活卡;\n\n`
let prizeStr = prizeInfoList.reduce((s, v) => {
let rankArr = v.rank.split('-');
if (rankArr[0] == rankArr[1]) {
return s += `第${rankArr[0]}名: ${v.name}\n`
} else {
return s += `第${v.rank}名: ${v.name}\n`
}
}, '奖品:\n')
resultRule += prizeStr;
return resultsModel.success(resultRule);
}
\ No newline at end of file
const {
loginfo
} = require('../utils/utils');
const {
CODE_TYPES
} = require('../utils/constants');
const ResultsModel = require('../utils/results.model');
let resultsModel = new ResultsModel();
const ActivityTopService = require('../service/activitytop.service');
//通过状态获取top商品列表
const findItemListByStatusAction = async function (context) {
loginfo(context, 'findItemListByStatus');
let ActivityTop = new ActivityTopService(context);
try {
let result = await ActivityTop.getItemListByStatus();
return result;
} catch (e) {
console.log(e, '获取top商品列表失败')
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, '获取top商品列表失败');
}
}
//通过itemIds获取商品列表
const findItemListByIdsAction = async (context) => {
loginfo(context, 'findItemListByIdsAction');
let ActivityTop = new ActivityTopService(context);
try {
let result = ActivityTop.getItemListByIds();
return result;
} catch (e) {
console.log(e, '获取商品列表失败')
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, '获取商品列表失败');
}
}
//通过ename获取权益信息
const getBenefitByEname = async (context) => {
loginfo(context, 'getBenefitByEname');
let ActivityTop = new ActivityTopService(context);
try {
let result = await ActivityTop.getPrizeByEname();
return result;
} catch (e) {
console.log(e, '获取ename权益信息失败')
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, '获取ename权益信息失败');
}
}
//测试getTemplateInstantiate
const getTemplateInstantiate = async (context) => {
loginfo(context, 'getTemplateInstantiate');
let ActivityTop = new ActivityTopService(context);
try {
let result = await ActivityTop.getTemplateInstantiate();
return result;
} catch (e) {
console.log(e, '获取getTemplateInstantiate失败')
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, '获取getTemplateInstantiate失败');
}
}
module.exports = {
findItemListByIdsAction,
findItemListByStatusAction,
getBenefitByEname,
getTemplateInstantiate
}
\ No newline at end of file
const ActivitySellerService = require("../service/activityseller.service");
const { CODE_TYPES, TEMPLATE_INFO } = require("../utils/constants");
const ActivityTopService = require("../service/activitytop.service");
const ActivityInstanceService = require("../service/activityinstance.service");
const { loginfo } = require("../utils/utils");
const ResultsModel = require("../utils/results.model");
let resultsModel = new ResultsModel();
//保存授权信息
module.exports = async (context) => {
loginfo(context, "sellerInfo");
const {
openId,
accessToken,
appKey,
userNick,
appOwnerOpenId,
consumeMiniAppId,
} = context;
let ActivitySeller = new ActivitySellerService(context);
let ActivityTop = new ActivityTopService(context);
let ActivityInstance = new ActivityInstanceService(context);
try {
let hasResult = await ActivitySeller.findSellerInfo(context.openId);
let instanceOldInfo = await ActivityInstance.getMiniAppInstanceInfo(
context.openId
);
console.log(instanceOldInfo, "insabsajs");
if (!instanceOldInfo || !instanceOldInfo[0]) {
let instanceInfo = await ActivityTop.getTemplateInstantiate();
console.log(instanceInfo, "instanceInfo");
const { app_id, app_version } = instanceInfo;
if (instanceInfo) {
let onlineInstance = await ActivityTop.pushOnlineInstance({
app_id,
app_version,
});
console.log(
onlineInstance,
"onlineInstance",
onlineInstance.online_results
);
await ActivityInstance.saveInstanceInfo({
...instanceInfo,
clients: onlineInstance.online_results.miniapp_instance_app_online_dto.reduce(
(s, v) => {
return (s += v.client + ",");
},
""
),
...onlineInstance.app_info,
accessToken,
template_version: TEMPLATE_INFO.template_version,
openId,
appKey,
consumeMiniAppId,
userNick,
appOwnerOpenId,
});
} else {
return resultsModel.error(
CODE_TYPES.SYSTEM_ERROR,
"获取实例化信息失败"
);
}
} else {
console.log(instanceOldInfo[0]);
if (
TEMPLATE_INFO.template_version !== instanceOldInfo[0].template_version
) {
const { clients, app_id, template_id } = instanceOldInfo[0];
let updateInfo = {
clients,
app_id,
template_id,
ext_json: {
name: "online",
},
template_version: TEMPLATE_INFO.template_version,
};
let updateInstanceInfo = await ActivityTop.updateMiniInstance(
updateInfo
);
let onlineInstance = await ActivityTop.pushOnlineInstance({
app_id: updateInstanceInfo.app_id,
app_version: updateInstanceInfo.app_version,
});
await ActivityInstance.updateInstanceInfo({
template_version: TEMPLATE_INFO.template_version,
...onlineInstance.app_info,
});
}
}
if (hasResult && hasResult[0]) {
await ActivitySeller.updateSellerInfo(context.openId, {
accessToken,
});
await ActivityInstance.updateInstanceInfo(context.openId, {
accessToken,
});
return resultsModel.success(true);
} else {
let shopInfoResult = await ActivityTop.getShopId();
if (!shopInfoResult.success) {
return resultsModel.error(
CODE_TYPES.SYSTEM_ERROR,
"获取淘宝店铺信息失败"
);
}
try {
let result = await ActivitySeller.saveSellerInfo({
accessToken,
openId,
appKey,
shopId: shopInfoResult.data.sid,
userNick,
appOwnerOpenId,
});
return resultsModel.success(result);
} catch (e) {
console.log(e, "保存授权信息失败");
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, "保存授权信息失败");
}
}
} catch (e) {
console.log(e, "获取授权信息失败");
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, "获取授权信息失败");
}
};
/**
* 中奖名单 及导出中奖名单
*/
const RankopenprizeService = require('../service/rankopenprize.service');
const ResultsModel = require('../utils/results.model');
const {
CODE_TYPES
} = require('../utils/constants');
const xlsx = require('node-xlsx');
let resultsModel = new ResultsModel();
// 活动中奖名单
const findWinnerInfoList = async (context) => {
let {
activityId
} = context.data;
if (!activityId) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, `缺少activityId`);
}
let rankopenprizeService = new RankopenprizeService(context);
// 获取活动中奖列表
let awardslist = await rankopenprizeService.getAwardslistByActivityId(activityId);
console.log(`awardslist: ${JSON.stringify(awardslist)}`);
let winnersObj = {};
awardslist.forEach((v, index, arr) => {
if (!winnersObj[v.rank]) {
winnersObj[v.rank] = [];
}
winnersObj[v.rank].push({
userNick: v.userNick,
id: v.openId
});
});
console.log(`winnersObj: ${JSON.stringify(winnersObj)}`);
let results = [];
new Map(Object.entries(winnersObj)).forEach((v, index, arr) => {
results.push({
rank: index,
winnerDetailList: v
});
});
return resultsModel.success(results);
}
// 导出活动中奖名单
const exportAwardsList = async (context) => {
let {
cloud
} = context;
let {
activityId,
title
} = context.data;
if (!activityId) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, `缺少activityId`);
}
if (!title) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, `缺少title`);
}
let rankopenprizeService = new RankopenprizeService(context);
// 获取活动中奖列表
let awardslist = await rankopenprizeService.getAwardslistByActivityId(activityId);
console.log(`awardslist: ${JSON.stringify(awardslist)}`);
let xlsxData = [
['序列', '名次', '昵称', '中奖名称']
];
awardslist.forEach((v, index, arr) => {
xlsxData.push([index + 1, v.rank, v.userNick, v.prizeName]);
});
let buffer = xlsx.build([{
name: title + new Date().getTime(),
data: xlsxData
}]);
console.log(`xlsxData: ${JSON.stringify(xlsxData)}`);
try {
let result = await cloud.file.uploadFile({
fileContent: buffer,
fileName: title + new Date().getTime() + '.xlsx'
});
// result.url 需进行处理
if (result.url) {
result.url = result.url.replace('http', 'https').replace('-internal', '');
}
return resultsModel.success(result);
} catch (e) {
console.log('上传文件出错', e);
// 打印日志
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, `上传文件错误`);
}
}
module.exports = {
findWinnerInfoList,
exportAwardsList
}
\ No newline at end of file
const RankscanService = require("../service/activityconfig.service");
const ActivityprizeService = require("../service/activityprize.service");
const RankscoreService = require("../service/rankscore.service");
const RankopenprizeService = require("../service/rankopenprize.service");
const ResultsModel = require("../utils/results.model");
const { Utils, EIGHT_HOURS } = require("../utils/constants");
let resultsModel = new ResultsModel();
// 声明排行榜扫描服务
let rankscanService = null;
// 声明奖品服务
let activityprizeService = null;
// 声明分数排行榜服务
let rankscoreService = null;
// 声明待开奖服务
let rankopenprizeService = null;
// 查询该活动的奖品列表 及 发奖数
const getPrizeListAndAwards = async (activityId) => {
let prizeList = await activityprizeService.getPrizeListByActivityId(
activityId
);
console.log(prizeList);
// 若不存在奖品列表,则为脏数据,不做处理
if (!prizeList.length) {
console.log(`活动Id为${activityId}不存在对应的奖品信息`);
return {
prizeList: [],
totalAwards: 0,
};
}
// 获取总共发奖的个数
let totalAwards = prizeList.reduce((total, curVal, curIndex, arr) => {
// 当没有rank名次,直接返回
if (!curVal.rank) {
return total;
}
let awards = +curVal.rank.split("-")[1];
if (awards > total) {
total = awards;
}
return total;
}, 0);
return {
prizeList,
totalAwards,
};
};
// 获取待开奖列表
const getWaitAwardsList = (prizeList, topscoreList, totalAwards, shopId) => {
let waitAwardslist = [];
// 若分数排行榜没有数据,或发奖数为0,返回[]
if (!totalAwards || !topscoreList.length) {
return waitAwardslist;
}
prizeList.forEach((v, index, arr) => {
if (!v.rank) {
return;
}
let rankPeriod = ~v.rank.indexOf("-")
? v.rank.split("-")
: [v.rank, v.rank + 1];
// 截取排行区间的分数排行列表
let ranklist = topscoreList.slice(
+rankPeriod[0] - 1,
Math.min(+rankPeriod[1], totalAwards)
);
// 整合开奖数据
ranklist.map((rank) => {
waitAwardslist.push({
activityId: v.activityId,
openId: rank.openId,
rankRange: v.rank,
rank: rank.rank,
ename: v.ename,
startTime: v.startTime,
endTime: v.endTime,
prizeId: v._id,
prizeName: v.name,
userNick: rank.userNick,
rankTime: rank.updateTime,
image: v.image,
score: rank.score,
shopId: shopId,
type: v.type || "",
amount: v.amount || "",
createDay: Utils.default.dateFormatter(
new Date(Date.now() + EIGHT_HOURS),
"yyyy/MM/dd"
),
});
});
});
return waitAwardslist;
};
// 设置活动开奖
const setActivity2openprize = async (waitAwardslist, _id) => {
// 插入排行榜开奖记录表 rank_open_prize 若失败,则变更
let results = await rankopenprizeService.addWaitAwardsList(waitAwardslist);
console.log(`result: ${JSON.stringify(results)}`);
if (results) {
await rankscanService.update2Success(_id);
console.log(`开奖成功`);
return true;
} else {
await rankscanService.update2Fail(_id, `批量插入rank_open_prize表不成功`);
console.log(`开奖失败`);
return false;
}
};
/**
* 定时触发开奖
*/
const endingNotify = async (context) => {
// 初始化排行榜扫描服务
rankscanService = new RankscanService(context);
// 初始化返回结果
let results = {
success: [],
fails: [],
};
// 获取服务器时间
let serverTime = Date.now();
// 触发开奖列表
let notifyList = await rankscanService.getNodifyList(serverTime);
console.log(notifyList.length);
if (!notifyList.length) {
console.log(`没有待开奖的活动`);
return resultsModel.success(results);
}
// 初始化奖品服务
activityprizeService = new ActivityprizeService(context);
// 分数排行榜
rankscoreService = new RankscoreService(context);
// 待开奖服务
rankopenprizeService = new RankopenprizeService(context);
// 遍历列表
for (let i = 0; i < notifyList.length; i++) {
let { _id, shopId } = notifyList[i];
// 将该条记录变更为处理中
let updateResult = await rankscanService.update2Process(_id);
console.log(`updateResult: ${updateResult}`);
if (!updateResult) {
console.log(`将rank_scan该活动的开奖状态变更为处理中失败`);
continue;
}
// 查询该活动的奖品列表 及 发奖数
let { prizeList, totalAwards } = await getPrizeListAndAwards(_id);
console.log(
`prizeList: ${JSON.stringify(prizeList)}; totalAwards: ${totalAwards}`
);
// 没查找到奖品列表, 继续下个活动循环
if (!prizeList.length) {
// 开奖失败,记录日志
await rankscanService.update2Fail(
_id,
`活动不存在对应的奖品列表,开奖失败`
);
results.fails.push(_id);
continue;
}
// 查找排行榜分数榜里的前totalAwards个记录
let topscoreList = await rankscoreService.getToplistByActivityId(
_id,
totalAwards
);
topscoreList = topscoreList.map((v, i) => {
return { ...v, rank: i + 1 };
});
console.log(`topscoreList: ${JSON.stringify(topscoreList)}`);
// 根据分数排行榜列表及奖品列表整合数据,待开奖列表
let waitAwardslist = getWaitAwardsList(
prizeList,
topscoreList,
totalAwards,
shopId
);
// if (!waitAwardslist.length) {
// }
console.log(`waitAwardslist: ${JSON.stringify(waitAwardslist)}`);
// 开奖
let issuccess = await setActivity2openprize(waitAwardslist, _id);
issuccess ? results.success.push(_id) : results.fails.push(_id);
// end
}
return resultsModel.success(results);
};
module.exports = {
endingNotify,
};
const { BaseDao } = require("../utils/constants");
module.exports = BaseDao;
const { endingNotify } = require("./controller/endingNodify.controller");
const {
findWinnerInfoList,
exportAwardsList,
} = require("./controller/awards.controller");
const {
getActivityListByOpenId,
saveActivityInfoByHasId,
deleteActivityById,
getActivityInfo,
} = require("./controller/activity.controller");
const saveSellerInfo = require("./controller/activityseller.controller");
const generateRuleAction = require("./controller/activitygenerateRule.controller");
const {
findItemListByStatusAction,
findItemListByIdsAction,
getBenefitByEname,
} = require("./controller/activitygettbitem.controller");
// 定时开奖
exports.endingNotify = endingNotify;
// 活动中奖名单
exports.findWinnerInfoList = findWinnerInfoList;
// 导出中奖名单
exports.exportAwardsList = exportAwardsList;
exports.getActivityList = getActivityListByOpenId;
exports.saveActivityInfo = saveActivityInfoByHasId;
exports.delActivity = deleteActivityById;
exports.getActivityDetail = getActivityInfo;
exports.sellerSave = saveSellerInfo;
exports.generateRule = generateRuleAction;
exports.findItemListByStatus = findItemListByStatusAction;
exports.findItemListByIds = findItemListByIdsAction;
exports.queryBenefitByEname = getBenefitByEname;
{
"name": "duiba",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"adler-32": {
"version": "1.2.0",
"resolved": "https://registry.npm.taobao.org/adler-32/download/adler-32-1.2.0.tgz",
"integrity": "sha1-aj5r8KY5ALoVZSgIyxXGgT0aXyU=",
"requires": {
"exit-on-epipe": "~1.0.1",
"printj": "~1.1.0"
}
},
"buffer-from": {
"version": "1.1.1",
"resolved": "https://registry.npm.taobao.org/buffer-from/download/buffer-from-1.1.1.tgz",
"integrity": "sha1-MnE7wCj3XAL9txDXx7zsHyxgcO8="
},
"cfb": {
"version": "1.1.4",
"resolved": "https://registry.npm.taobao.org/cfb/download/cfb-1.1.4.tgz",
"integrity": "sha1-gf017eTJGdjwliqUWC4d+vcFHio=",
"requires": {
"adler-32": "~1.2.0",
"commander": "^2.16.0",
"crc-32": "~1.2.0",
"printj": "~1.1.2"
}
},
"codepage": {
"version": "1.14.0",
"resolved": "https://registry.npm.taobao.org/codepage/download/codepage-1.14.0.tgz",
"integrity": "sha1-jL4lSBMjVZ19MHVxsP/5HnodL5k=",
"requires": {
"commander": "~2.14.1",
"exit-on-epipe": "~1.0.1"
},
"dependencies": {
"commander": {
"version": "2.14.1",
"resolved": "https://registry.npm.taobao.org/commander/download/commander-2.14.1.tgz?cache=0&sync_timestamp=1592632075120&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcommander%2Fdownload%2Fcommander-2.14.1.tgz",
"integrity": "sha1-IjUSPjevjKPGXfRbAm29NXsBuao="
}
}
},
"commander": {
"version": "2.17.1",
"resolved": "https://registry.npm.taobao.org/commander/download/commander-2.17.1.tgz?cache=0&sync_timestamp=1592632075120&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcommander%2Fdownload%2Fcommander-2.17.1.tgz",
"integrity": "sha1-vXerfebelCBc6sxy8XFtKfIKd78="
},
"crc-32": {
"version": "1.2.0",
"resolved": "https://registry.npm.taobao.org/crc-32/download/crc-32-1.2.0.tgz",
"integrity": "sha1-yy224puIUI4y2d0OwWk+e0Ghggg=",
"requires": {
"exit-on-epipe": "~1.0.1",
"printj": "~1.1.0"
}
},
"exit-on-epipe": {
"version": "1.0.1",
"resolved": "https://registry.npm.taobao.org/exit-on-epipe/download/exit-on-epipe-1.0.1.tgz",
"integrity": "sha1-C92S6H1ShdJn2qgXHQ6wYVlolpI="
},
"frac": {
"version": "1.1.2",
"resolved": "https://registry.npm.taobao.org/frac/download/frac-1.1.2.tgz",
"integrity": "sha1-PXT39keMiKG1AgMG10fcYxPHTQs="
},
"node-xlsx": {
"version": "0.15.0",
"resolved": "https://registry.npm.taobao.org/node-xlsx/download/node-xlsx-0.15.0.tgz",
"integrity": "sha1-HxsNetzlxwboa/2WpaoABb+KncM=",
"requires": {
"buffer-from": "^1.1.0",
"xlsx": "^0.14.1"
}
},
"printj": {
"version": "1.1.2",
"resolved": "https://registry.npm.taobao.org/printj/download/printj-1.1.2.tgz",
"integrity": "sha1-2Q3rKXWoufYA+zoclOP0xTx4oiI="
},
"ssf": {
"version": "0.10.3",
"resolved": "https://registry.npm.taobao.org/ssf/download/ssf-0.10.3.tgz",
"integrity": "sha1-jq4fwpyQpVLnkhII+BiS1vd6yys=",
"requires": {
"frac": "~1.1.2"
}
},
"taobao-mini-sdk": {
"version": "git+http://luofangping@duiba.com.cn:duiba123@gitlab2.dui88.com/huhu/taobao-mini-sdk.git#7cde981223c800143ae99e1c200a3597b76713d1",
"from": "git+http://luofangping@duiba.com.cn:duiba123@gitlab2.dui88.com/huhu/taobao-mini-sdk.git"
},
"xlsx": {
"version": "0.14.5",
"resolved": "https://registry.npm.taobao.org/xlsx/download/xlsx-0.14.5.tgz?cache=0&sync_timestamp=1593422715008&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fxlsx%2Fdownload%2Fxlsx-0.14.5.tgz",
"integrity": "sha1-NjfpFNeRvcpzgoFuFz99cl7Q4NI=",
"requires": {
"adler-32": "~1.2.0",
"cfb": "^1.1.2",
"codepage": "~1.14.0",
"commander": "~2.17.1",
"crc-32": "~1.2.0",
"exit-on-epipe": "~1.0.1",
"ssf": "~0.10.2"
}
}
}
}
{
"name": "duiba",
"version": "1.0.0",
"description": "",
"main": "index.js",
"author": "",
"license": "ISC",
"sdkVersion": "*",
"dependencies": {
"node-xlsx": "^0.15.0",
"taobao-mini-sdk": "^0.1.2"
},
"config": {
"notNeedLogin": [
"endingNotify"
]
}
}
\ No newline at end of file
const BaseDao = require("../dao/base.dao");
const {
ACTIVITY_BASE_CONFIG,
ACTIVITY_PRIZE_CONFIG,
} = require("../config/db_config");
const { DELETE_STATUS, OPEN_PRIZE_STATUS } = require("../utils/constants");
module.exports = class ActivityConfigService {
constructor(context) {
this.activityconfigDao = new BaseDao(context, ACTIVITY_BASE_CONFIG);
this.activityconfigPrizeDao = new BaseDao(context, ACTIVITY_PRIZE_CONFIG);
}
//保存新建活动
async saveNewActivity(data, prize, openId) {
try {
let result = await this.activityconfigDao.insertOne({
...data,
createTime: Date.now(),
updateTime: Date.now(),
deleteStatus: DELETE_STATUS.EXIST,
openPrizeStatus: OPEN_PRIZE_STATUS.WAIT_AWARD,
});
console.log(result, "保存基础配置result");
if (result) {
try {
prize.map((item) => {
item.activityId = result;
item.openId = openId;
item.createTime = Date.now();
item.updateTime = Date.now();
item.deleteStatus = DELETE_STATUS.EXIST;
return item;
});
return await this.activityconfigPrizeDao.insertMany(prize);
} catch (e) {
console.log(e, "保存奖品配置出错");
// 如果奖品未保存删除之前的活动
await this.activityconfigDao.deleteMany({
_id: result,
});
}
}
} catch (e) {
//保存活动失败
console.log(e, "保存活动失败");
}
}
// 更新活动基本信息
async updateBaseConfigInfo(id, data) {
return await this.activityconfigDao.update(
{
_id: id,
},
{
$set: {
...data,
updateTime: Date.now(),
deleteStatus: DELETE_STATUS.EXIST,
},
}
);
}
// 通过id获取奖品信息
async getPrizeInfoById(id) {
let result = await this.activityconfigPrizeDao.find({
_id: id,
});
if (result && result[0]) {
return result[0];
}
return false;
}
// 奖品置为删除状态
async updateDeleteStatusById(id) {
return await this.activityconfigPrizeDao.update(
{
_id: id,
},
{
$set: {
deleteStatus: DELETE_STATUS.DELETE,
},
}
);
}
// 更新单条数据
async updateOnePrizeById(id, data) {
return await this.activityconfigPrizeDao.update(
{
_id: id,
},
{
$set: {
...data,
updateTime: Date.now(),
deleteStatus: DELETE_STATUS.EXIST,
},
}
);
}
// 插入单条奖品数据
async insertPrizeByActivityId(activityId, data) {
return await this.activityconfigPrizeDao.insertOne({
...data,
activityId,
createTime: Date.now(),
updateTime: Date.now(),
deleteStatus: DELETE_STATUS.EXIST,
});
}
// 获取奖品所有配置
async getAllOldPrizeList(activityId) {
return await this.activityconfigPrizeDao.find({
activityId,
});
}
//更新活动配置
async updateActivity(activityId, data, prizeNoId) {
console.log(
activityId,
data,
prizeNoId,
"activityId, data, prizeNoId, oldPrizeIds"
);
try {
let originalData = await this.activityconfigDao.findOne({
_id: activityId,
});
let result = await this.activityconfigDao.update(
{
_id: activityId,
},
{
$set: {
...data,
createTime: originalData.createTime,
updateTime: Date.now(),
},
}
);
if (result) {
try {
let prizeOldIdsByActivity = (
await this.activityconfigPrizeDao.find({
activityId,
})
).reduce((s, v) => {
return (s = [...s, v._id]);
}, []);
let prizeResult = await this.activityconfigPrizeDao.insertMany(
prizeNoId
);
if (prizeResult) {
//删除之前的奖品
let deleteOldPrizeList = prizeOldIdsByActivity.reduce((s, item) => {
return (s = [
...s,
this.activityconfigPrizeDao.deleteMany({
_id: item,
}),
]);
}, []);
return await Promise.all(deleteOldPrizeList);
}
} catch (e) {
await this.activityconfigDao.update(
{
_id: activityId,
},
{
$set: {
...originalData[0],
},
}
);
}
}
} catch (e) {
//更新失败
}
}
//通过openId去获取当前商家活动列表
async getActivityListByOpenId(shopId, { pageSize = 10, pageNo = 1 }) {
return {
list: await this.activityconfigDao.find(
{
shopId,
deleteStatus: DELETE_STATUS.EXIST,
},
{
projection: {
startTime: 1,
endTime: 1,
title: 1,
},
sort: {
createTime: -1,
},
limit: pageSize,
skip: (pageNo - 1) * pageSize,
}
),
total: await this.activityconfigDao.count({
shopId,
deleteStatus: DELETE_STATUS.EXIST,
}),
pageSize,
pageNo,
};
}
//通过activityId去获取活动配置信息
async getActivityInfoByActivityId(activityId) {
return {
baseConfig: await this.activityconfigDao.findOne(
{
_id: activityId,
deleteStatus: DELETE_STATUS.EXIST,
},
{
projection: {
commandImg: 1,
rule: 1,
taskList: 1,
title: 1,
logoImg: 1,
prizeInfoList: 1,
subtitle: 1,
commandTitle: 1,
startTime: 1,
endTime: 1,
beenInvitedText: 1,
},
}
),
prizeConfig: await this.activityconfigPrizeDao.find(
{
activityId,
deleteStatus: DELETE_STATUS.EXIST,
},
{
sort: {
level: 1,
},
}
),
};
}
//通过activityId去删除活动
async deleteActivityByActivityId(activityId) {
try {
let result = await this.activityconfigDao.update(
{
_id: activityId,
},
{
$set: {
deleteStatus: DELETE_STATUS.DELETE,
},
}
);
if (result) {
return await this.activityconfigPrizeDao.update(
{
activityId,
},
{
$set: {
deleteStatus: DELETE_STATUS.DELETE,
},
}
);
}
} catch (e) {
console.log(e, "删除活动失败");
}
}
// 查询待开奖列表
async getNodifyList(serverTime) {
let list = await this.activityconfigDao.find({
endTime: {
$lt: serverTime,
},
openPrizeStatus: OPEN_PRIZE_STATUS.WAIT_AWARD,
deleteStatus: DELETE_STATUS.EXIST,
});
console.log(list);
return list;
}
// 更新openPrizeStatus为处理中
async update2Process(_id) {
try {
await this.activityconfigDao.update(
{
_id,
},
{
$set: {
openPrizeStatus: OPEN_PRIZE_STATUS.PROCESSING,
updateTime: Date.now(),
},
}
);
return true;
} catch (e) {
// 日志记录
console.log(e);
return false;
}
}
// 更新openPrizeStatus为成功
async update2Success(_id) {
try {
await this.activityconfigDao.update(
{
_id,
},
{
$set: {
openPrizeStatus: OPEN_PRIZE_STATUS.SUCCESS,
openPrizeMsg: "",
updateTime: Date.now(),
},
}
);
return true;
} catch (e) {
// 日志记录
console.log(e);
return false;
}
}
// 更新openPrizeStatus为失败
async update2Fail(_id, message) {
try {
await this.activityconfigDao.update(
{
_id,
},
{
$set: {
openPrizeStatus: OPEN_PRIZE_STATUS.FAIL,
openPrizeMsg: message || "",
updateTime: Date.now(),
},
}
);
return true;
} catch (e) {
// 日志记录
console.log(e);
return false;
}
}
};
const BaseDao = require('../dao/base.dao');
const {
ACTIVITY_INSTANCE
} = require('../config/db_config');
module.exports = class ActivitySellerService {
constructor(context) {
this.activityinstanceDao = new BaseDao(context, ACTIVITY_INSTANCE);
}
//保存商家授权信息
async saveInstanceInfo(instanceInfo) {
return await this.activityinstanceDao.insertOne({
...instanceInfo,
updateTime: Date.now(),
createTime: Date.now()
})
}
//查找商家授权信息
async findInstanceInfo(openId) {
return await this.activityinstanceDao.find({
openId
})
}
//更新商家授权信息
async updateInstanceInfo(openId, updateinfo) {
return await this.activityinstanceDao.update({
openId: openId
}, {
$set: {
...updateinfo,
updateTime: Date.now()
}
})
}
async getMiniAppInstanceInfo(openId) {
return await this.activityinstanceDao.find({
openId
})
}
}
\ No newline at end of file
const BaseDao = require("../dao/base.dao");
const { OPEN_PRIZE_STATUS, DELETE_STATUS } = require("../utils/constants");
const DBName = "activity_prize_config";
class ActivityprizeService {
constructor(context) {
this.activityprizeDao = new BaseDao(context, DBName);
}
// 根据活动id查询奖品列表
async getPrizeListByActivityId(activityId) {
console.log(13, activityId);
return await this.activityprizeDao.find({
activityId: activityId,
deleteStatus: DELETE_STATUS.EXIST,
});
}
}
module.exports = ActivityprizeService;
const BaseDao = require('../dao/base.dao');
const {
ACTIVITY_SELLER_SAVE
} = require('../config/db_config');
module.exports = class ActivitySellerService {
constructor(context) {
this.activitysellerDao = new BaseDao(context, ACTIVITY_SELLER_SAVE);
}
//保存商家授权信息
async saveSellerInfo(sellerInfo) {
return await this.activitysellerDao.insertOne({
...sellerInfo,
updateTime: Date.now(),
createTime: Date.now()
})
}
//查找商家授权信息
async findSellerInfo(openId) {
return await this.activitysellerDao.find({
openId
})
}
//更新商家授权信息
async updateSellerInfo(openId, sellerInfo) {
console.log(openId, sellerInfo, 'openId, sellerInfo')
return await this.activitysellerDao.update({
openId: openId
}, {
$set: {
...sellerInfo,
updateTime: Date.now()
}
})
}
}
\ No newline at end of file
const {
ACTIVITY_SELLER_SAVE,
ACTIVITY_BASE_CONFIG,
} = require("../config/db_config");
const {
getConsumerSeller,
getSellerInfoByContext,
} = require("../config/seller_config");
const {
CODE_TYPES,
GOODSINFO,
B_APP_NAME,
TEMPLATE_INFO,
TBAPIS,
} = require("../utils/constants");
const ResultsModel = require("../utils/results.model");
const { MathRand } = require("../utils/utils");
let resultsModel = new ResultsModel();
const BaseDao = require("../dao/base.dao");
class ActivityTopService {
constructor(context) {
this.activitySellerDao = new BaseDao(context, ACTIVITY_SELLER_SAVE);
this.activityBaseDao = new BaseDao(context, ACTIVITY_BASE_CONFIG);
this.context = context;
}
//获取商家授权session
async getAccessToken(activityId) {
let result = await this.activityBaseDao.find({
_id: activityId,
});
if (result[0]) {
let shopId = result[0].shopId;
let sellResult = await this.activitySellerDao.find(
{
shopId,
},
{
sort: {
createTime: 1,
},
}
);
console.log(sellResult, "sellResult");
if (sellResult[0]) {
let { accessToken } = sellResult[0];
return {
session: accessToken,
};
}
}
}
//通过openId去查找session
async getAccessTokenByOpenId() {
let openId = this.context.openId;
console.log(openId);
let result = await this.activitySellerDao.find({
openId,
});
console.log(result, "getAccessTokenByOpenId");
if (result[0]) {
let shopId = result[0].shopId;
let sellResult = await this.activitySellerDao.find(
{
shopId,
},
{
sort: {
createTime: 1,
},
}
);
console.log(sellResult, "sellResult");
if (sellResult[0]) {
let { accessToken } = sellResult[0];
return {
session: accessToken,
};
}
}
}
//淘宝top接口获取商品列表
async getItemListByIds(activityId, itemIds) {
let sellerConfig;
if (activityId) {
let businessSeller = await this.getAccessToken(activityId);
sellerConfig = getConsumerSeller(businessSeller);
} else {
sellerConfig = await this.getAccessTokenByOpenId();
itemIds = this.context.data.itemIds;
}
console.log(sellerConfig, "sellerConfig");
try {
let result = await TBAPIS.getItemListByItemIds(
this.context,
sellerConfig.session,
itemIds,
{ fields: GOODSINFO }
);
// let result = await this.context.cloud.topApi.invoke({
// api: "taobao.items.seller.list.get",
// data: {
// ...sellerConfig,
// fields: GOODSINFO,
// num_iids: itemIds,
// },
// });
// console.log(JSON.stringify(result), '获取商品通过ids')
if (result) {
let itemsData = {
list:
(result &&
result.items &&
result.items.item.reduce((s, v) => {
return (s = [
...s,
{
itemId: v.num_iid,
name: v.title,
price: v.price,
detailUrl: v.detail_url,
picUrl: v.pic_url,
},
]);
}, [])) ||
[],
totalCount: result.items.item.length,
};
return resultsModel.success(itemsData);
}
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, "获取商家ids列表失败");
} catch (e) {
console.log(e, "获取ids商品列表失败");
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, "获取商家ids列表失败");
}
}
//淘宝top接口获取权益商品信息
async getPrizeByEname(activityId, ename) {
let sellerConfig;
if (activityId) {
let businessSeller = await this.getAccessToken(activityId);
sellerConfig = getConsumerSeller(businessSeller);
} else {
sellerConfig = await this.getAccessTokenByOpenId();
ename = this.context.data.ename;
}
console.log(sellerConfig, "sellerConfig");
try {
let benefitData = await TBAPIS.getPrizeByEname(
this.context,
sellerConfig.session,
ename,
B_APP_NAME
);
console.log(benefitData, benefitData.result, "benefitData.result.");
if (benefitData.result.success) {
const { result } = benefitData;
let data =
result.datas &&
result.datas["oright_dto"].reduce((s, v) => {
return (s = [
...s,
{
benefitName: v.benefit_name,
rightTypeId: v.right_type_id,
startTime: new Date(v.start_date).getTime(),
endTime: new Date(v.end_date).getTime(),
amount: v.amount ? v.amount / 100 + "" : "",
},
]);
}, []);
console.log(JSON.stringify(result), "alibaba.benefit.query");
return resultsModel.success(data);
}
return resultsModel.error(
CODE_TYPES.SYSTEM_ERROR,
"获取ename商品信息失败"
);
} catch (e) {
console.log(e, "权益获取失败");
return resultsModel.error(
CODE_TYPES.SYSTEM_ERROR,
"获取ename商品信息失败"
);
}
}
//淘宝top接口获取店铺会员链接
async getVipUrlByActivity(activityId) {
let businessSeller = await this.getAccessToken(activityId);
let consumerSellerInfo = getConsumerSeller(businessSeller);
try {
let result = await this.context.cloud.topApi.invoke({
api: "taobao.crm.member.joinurl.get",
data: {
...consumerSellerInfo,
extra_info: {
source: "isvapp",
activityId: activityId,
entrance: "duiba",
},
},
});
if (result) {
return resultsModel.success(result);
}
console.log(JSON.stringify(result), "获取商家会员链接");
return resultsModel.error(
CODE_TYPES.SYSTEM_ERROR,
"获取商家会员链接失败"
);
} catch (e) {
console.log(e, "获取商家会员链接失败");
return resultsModel.error(
CODE_TYPES.SYSTEM_ERROR,
"获取商家会员链接失败"
);
}
}
//淘宝top接口获取店铺信息
async getShopId() {
let sellerInfo = await this.getAccessTokenByOpenId();
console.log(sellerInfo, "getShopId sellerInfo");
try {
let result = await TBAPIS.getShopInfo(this.context, {
session: sellerInfo.session,
fields: "sid,title,pic_path",
});
console.log(result, "getShopId result");
if (result) {
return resultsModel.success(result.shop);
} else {
return resultsModel.error(
CODE_TYPES.SYSTEM_ERROR,
"获取淘宝top店铺信息失败"
);
}
} catch (e) {
console.log(e, "获取店铺信息失败");
return resultsModel.error(
CODE_TYPES.SYSTEM_ERROR,
"获取淘宝top店铺信息失败"
);
}
}
async getItemListByStatus() {
let sellerConfig = await this.getAccessTokenByOpenId();
const {
approveStatus = "onsale",
title = "",
pageNo = 1,
pageSize = 10,
} = this.context.data;
let data = {
fields: GOODSINFO,
page_no: pageNo,
q: title,
page_size: pageSize,
...sellerConfig,
};
console.log(data, "data");
try {
let result =
approveStatus === "onsale"
? await TBAPIS.getItemListOnSale(this.context, data)
: await TBAPIS.getItemListInStock(this.context, data);
if (result) {
// console.log(
// JSON.stringify(result),
// `comming ${approveStatus}`,
// "success"
// );
let { items, total_results } = result;
let itemsData = {
pageNo,
pageSize,
totalPages: Math.ceil(total_results / pageSize),
totalCount: total_results,
list:
(items &&
items.item &&
items.item.reduce((s, v) => {
return (s = [
...s,
{
itemId: v.num_iid,
name: v.title,
price: v.price,
approveStatus: v.approveStatus || approveStatus,
picUrl: v.pic_url,
},
]);
}, [])) ||
[],
};
return resultsModel.success(itemsData);
}
return resultsModel.error(
CODE_TYPES.SYSTEM_ERROR,
"获取商家在售列表失败"
);
} catch (e) {
console.log(e, "获取商家在售列表失败");
return resultsModel.error(
CODE_TYPES.SYSTEM_ERROR,
"获取商家在售列表失败"
);
}
}
async getTemplateInstantiate() {
let sellerConfig = await this.getAccessTokenByOpenId();
let data = {
description:
"此应用用于商家引导活动,商家可设置对应的奖品,在一定时间 内出奖,提升店铺用户活跃以及引导购买",
ext_json: {
name: "online",
},
icon:
"https://ossgw.alicdn.com/taobao-miniapp/img/0193eaa9cc037b568acd9ccfe68a8499.jpg",
name: "店铺漂流" + MathRand(),
...TEMPLATE_INFO,
...sellerConfig,
};
console.log("getTemplateInstantiateParams", data);
try {
let result = await TBAPIS.getTemplateInstantiate(this.context, data);
if (result) {
console.log(JSON.stringify(result), "getTemplateInstantiate");
return result;
}
return false;
} catch (e) {
console.log(e, "获取getTemplateInstantiate失败");
return false;
}
}
//模板小程序上线
async pushOnlineInstance(instanceInfo) {
let sellerConfig = await this.getAccessTokenByOpenId();
let data = {
...TEMPLATE_INFO,
...instanceInfo,
...sellerConfig,
};
console.log("pushOnlineInstanceParams", data);
try {
let result = await TBAPIS.pushInstanceOnline(this.context, data);
if (result) {
console.log(JSON.stringify(result), "pushOnlineInstance");
return result;
}
return false;
} catch (e) {
console.log(e, "pushOnlineInstance失败");
return false;
}
}
//模板小程序更新
async updateMiniInstance(instanceInfo) {
let sellerConfig = await this.getAccessTokenByOpenId();
let data = {
...TEMPLATE_INFO,
...instanceInfo,
...sellerConfig,
};
try {
let result = await TBAPIS.updateMiniInstance(this.context, data);
if (result) {
console.log(JSON.stringify(result), "updateMiniInstance");
return result;
}
return false;
} catch (e) {
console.log(e, "updateMiniInstance失败");
return false;
}
}
}
module.exports = ActivityTopService;
// 排行榜开奖记录
const BaseDao = require('../dao/base.dao');
const { DRAW_STATUS } = require('../utils/constants');
const DBName = 'rank_open_prize';
class RankopenprizeService {
constructor(context) {
this.rankopenprizeDao = new BaseDao(context, DBName);
}
// 添加排行榜待开奖列表
async addWaitAwardsList(list) {
if (!list.length) {
return true;
}
list.map(v => {
v.drawStatus = DRAW_STATUS.WAITAWARD,
v.message = '';
});
try {
await this.rankopenprizeDao.insertMany(list);
return true;
}catch(e) {
console.log(`添加排行榜待开奖列表出错:${e}`);
return false;
}
}
// 根据活动id查找中奖名单
async getAwardslistByActivityId(activityId) {
if (!activityId) {
return false;
}
return await this.rankopenprizeDao.find({
activityId: activityId
}, {
sort: {
score: -1,
rankTime: -1
}
});
}
}
module.exports = RankopenprizeService;
\ No newline at end of file
const BaseDao = require("../dao/base.dao");
const { USER_INFO } = require("../config/db_config");
const DBName = USER_INFO;
class RankscoreService {
constructor(context) {
this.rankscoreDao = new BaseDao(context, DBName);
}
// 根据活动id查询分数排行榜
async getToplistByActivityId(activityId, totalAwards) {
let list = await this.rankscoreDao.find(
{
activityId: activityId,
},
{
sort: {
maxScore: -1,
updateScoreTime: 1,
},
limit: +totalAwards,
}
);
console.log(`getToplistByActivityId: ${JSON.stringify(list)}`);
return list;
}
}
module.exports = RankscoreService;
const { BaseDao, TBAPI, Utils } = require("taobao-mini-sdk").default;
// 活动开奖状态码
const OPEN_PRIZE_STATUS = {
// 待开奖
WAIT_AWARD: 1,
// 开奖中
PROCESSING: 2,
// 开奖成功
SUCCESS: 3,
// 开奖失败
FAIL: 4,
};
// 领取奖品状态
const DRAW_STATUS = {
// 待领取
WAITAWARD: 1,
// 处理中
PROCESSING: 2,
// 领取成功
SUCCESS: 3,
// 领取失败
FAIL: 4,
// 已过期
EXPIRED: 5,
// 重新领取
RETRY: 6,
};
// 日志类型: error,info
const LOGGER_TYPE = {
ERROR: 1,
INFO: 2,
};
// code类型
const CODE_TYPES = {
PARAMS_ERROR: {
code: "100000",
defaultMsg: `参数错误`,
},
SYSTEM_ERROR: {
code: "500000",
defaultMsg: `系统错误`,
},
SUCCESS: {
code: "000000",
defaultMsg: `成功`,
},
// TODO 补充业务类型错误, 固定以2开头,如B端业务错误:200001,200002,C端业务错误:210001,210002
};
const SHARE_TOTAL_COUNT = 5; //验证要求5人后,完成分享任务
const B_APP_NAME = "promotioncenter-3000000002693435"; // B端的APP NAME
const GOODSINFO =
"detail_url,approve_status,num_iid,title,nick,type,cid,pic_url,num,props,valid_thru,list_time,price,has_discount,has_invoice,has_warranty,has_showcase,modified,delist_time,postage_id,seller_cids,outer_id,sold_quantity";
const EIGHT_HOURS = 60 * 60 * 8 * 1000;
const DELETE_STATUS = {
DELETE: 2,
EXIST: 1,
};
const TEMPLATE_INFO = {
clients: "taobao,tmall",
template_id: 3000000002590532,
template_version: "0.0.3",
};
const TASK_TYPE_CHINA = {
beMembership: "成为会员",
attentionStore: "关注店铺",
sign: "签到",
exchangeCredits: "兑换积分",
inviteFriends: "邀请好友",
orderGoods: "下单商品",
browseGoods: "浏览商品",
jumpLink: "跳转链接",
collectGoods: "收藏商品",
};
const TASK_CHECK_TYPE = {
value: {
list: [
"beMembership",
"attentionStore",
"sign",
"exchangeCredits",
"inviteFriends",
"orderGoods",
"browseGoods",
"jumpLink",
"collectGoods",
],
reg: /[1-9]/,
regName: "1-9之内",
},
times: {
list: [
"exchangeCredits",
"inviteFriends",
"orderGoods",
"browseGoods",
"jumpLink",
"collectGoods",
],
},
title: {
list: [
"inviteFriends",
"orderGoods",
"browseGoods",
"jumpLink",
"collectGoods",
],
},
itemIds: {
list: ["orderGoods", "browseGoods", "collectGoods"],
},
taskRateType: {
list: ["inviteFriends", "orderGoods", "browseGoods", "collectGoods"],
},
link: { list: ["jumpLink"] },
};
module.exports = {
OPEN_PRIZE_STATUS,
DRAW_STATUS,
LOGGER_TYPE,
CODE_TYPES,
SHARE_TOTAL_COUNT,
DELETE_STATUS,
GOODSINFO,
B_APP_NAME,
EIGHT_HOURS,
TEMPLATE_INFO,
BaseDao,
TBAPIS: TBAPI.default,
Utils,
TASK_CHECK_TYPE,
TASK_TYPE_CHINA,
};
const errorCode = {
"000000": 'ok',
"000001": '请填写活动名称',
"000002": '请填写活动副标题',
"000003": '活动名称不得大于12个字符',
"000004": '活动副标题不得大于16个字符',
"000005": '任务配置至少选择一项',
"000006": '浏览链接配置错误',
"000007": '奖品至少配置三名',
"000008": "浏览宝贝最多选择20个商品",
"000010": '请先生成规则',
"000011": '请配置活动时间',
"000012": '新建活动开始时间需小于当前时间',
"000013": '开始时间不得大于结束时间',
"000014": '奖品最多配置8个档位',
"000015": '查询列表错误',
"000016": '查询列表总数错误',
"000017": '更新奖品列表出错',
"000020": '删除活动失败',
"000021": '保存活动失败',
"000022": "删除活动id不存在",
"000023": '当前活动不存在',
"000024": '获取授权信息失败',
"000025": '保存授权信息失败',
"000026": '获取商家在售列表失败',
"000027": '获取商家ids列表失败',
"000030": '获取ename商品信息失败',
"000031": '奖品配置不正确'
}
module.exports = {
errorCode
}
\ No newline at end of file
class ResultsModel {
constructor() {
}
// 不填errorType,默认为系统错误
error(errorType, message) {
if (!errorType) {
errorType = { code: '500000', defaultMsg: '系统错误' };
}
return {
success: false,
code: errorType.errorCode,
message: message || errorType.defaultMsg
}
}
success(data) {
return {
success: true,
code: 10000,
data: data,
message: `成功`
}
}
}
module.exports = ResultsModel
\ No newline at end of file
function loginfo(context, handler) {
const {
fcName,
data,
env
} = context;
console.log(`函数名:${fcName}---函数handler:${handler}---当前环境:${env}---请求参数:${JSON.stringify(data)}`)
}
const passUrlList = [
'1688.cn',
'taobao.cn',
'taobao.com',
'taobao.net',
'tb.cn',
'tmall.com',
'zhifu.com',
'zhifubao.com',
'juhuasuan.com',
'tmall.hk',
'tmall.com.hk',
'dingtalk.com',
'yushanfang.com',
'guoguo-app.com',
'umeng.com'
]
/**
* 传入时间戳(毫秒)
* 根据时间戳转换成number型日期进行差值结算,比如:2020318-2020317
*/
function isNewDay(time) {
let date = new Date(time);
let dateNum = +(date.getFullYear() + "" + (date.getMonth() + 1) + "" + date.getDate());
let nowdate = new Date(Date.now());
let nowdateNum = +(nowdate.getFullYear() + "" + (nowdate.getMonth() + 1) + "" + nowdate.getDate());
return nowdateNum - dateNum > 0
}
const formatTime = function dateFormat(thisDate, fmt = "yyyy-MM-dd hh:mm:ss") {
var 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 (var 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;
}
function MathRand() {
var Num = "";
for (var i = 0; i < 6; i++) {
Num += Math.floor(Math.random() * 10);
}
return Num;
}
module.exports = {
loginfo,
MathRand,
passUrlList,
isNewDay,
formatTime
}
\ No newline at end of file
{
"runtime": "nodejs8",
"version": "1.0"
}
\ No newline at end of file
const ACTIVITY_BASE_CONFIG = 'activity_base_config';
const ACTIVITY_PRIZE_CONFIG = 'activity_prize_config';
const ACTIVITY_SELLER_SAVE = 'activity_seller_save';
module.exports = {
ACTIVITY_BASE_CONFIG,
ACTIVITY_PRIZE_CONFIG,
ACTIVITY_SELLER_SAVE
}
\ No newline at end of file
const {
formatTime
} = require('../utils/utils');
const secret = '99f00e460bfae520f85ae5f5a38e5b4a';
const appKey = 28640163;
const getSellerInfoByContext = (context) => {
return {
timestamp: formatTime(new Date(), "yyyy-MM-dd hh:mm:ss"),
open_id: context.openId,
'appkey': appKey,
'appsecret': secret,
'session': context.accessToken,
REST_URL: 'http://gw.api.taobao.com/router/rest'
}
}
const getConsumerSeller = ({
accessToken,
openId
}) => {
return {
timestamp: formatTime(new Date(), "yyyy-MM-dd hh:mm:ss"),
open_id: openId,
'appkey': appKey,
'appsecret': secret,
'session': accessToken,
REST_URL: 'http://gw.api.taobao.com/router/rest'
}
}
module.exports = {
getSellerInfoByContext,
getConsumerSeller
};
\ No newline at end of file
const ActivityConfigService = require('../service/activityconfig.service')
const {
CODE_TYPES
} = require('../utils/constants')
const ResultsModel = require('../utils/results.model')
let resultsModel = new ResultsModel()
const {
DELETE_STATUS
} = require("../utils/constants");
let ActivityTopService = require('../service/activitytop.service');
const {
loginfo,
passUrlList
} = require('../utils/utils')
const Url = require('url')
//openId获取活动列表
const getActivityListByOpenId = async function (context) {
loginfo(context, 'getActivityListByOpenId')
let ActivityConfig = new ActivityConfigService(context);
try {
let result = await ActivityConfig.getActivityListByOpenId(
context.openId,
context.data
)
if (result) {
result.list.map(item => {
item.activityId = item._id;
return item;
})
return resultsModel.success(result)
}
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, '查询活动列表失败')
} catch (e) {
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, '查询活动列表失败')
}
}
//保存或编辑活动信息
const saveActivityInfoByHasId = async function (context) {
loginfo(context, 'saveActivityInfoByHasId')
let ActivityConfig = new ActivityConfigService(context)
let ActivityTop = new ActivityTopService(context);
const {
openId
} = context
let {
title,
subtitle,
activityId = '',
startTime,
endTime,
specifyPageUrl = '',
rule,
browseItemIds = '',
prizeInfoList = [],
attentionStore = false,
beMembership = false,
inviteFriends = false
} = context.data
if (!title) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, '活动名称为空')
} else if (title.length > 12) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, '活动名称不可超过12个字')
}
if (!subtitle) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, '活动副标题为空')
} else if (subtitle.length > 16) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, '活动副标题不可超过16个字')
}
if (
!attentionStore &&
!beMembership &&
!inviteFriends &&
browseItemIds.length === 0 &&
specifyPageUrl.length === 0
) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, '任务配置至少选择一项')
} else if (browseItemIds.split(',').length > 20) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, '浏览宝贝最多选择20个商品')
}
if (specifyPageUrl) {
const specifyPageUrlParse = Url.parse(specifyPageUrl)
let urlHasPass = false
passUrlList.forEach(item => {
if (~specifyPageUrlParse.host.indexOf(item)) {
urlHasPass = true
}
})
console.log(specifyPageUrlParse.protocol, urlHasPass)
if (!specifyPageUrlParse.protocol || !urlHasPass) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, '浏览链接域名校验不通过')
}
}
if (prizeInfoList.length === 0) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, '奖品至少配置3名')
} else if (prizeInfoList.length > 8) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, '奖品最多配置8个档位')
} else {
let canPassPrize = true
prizeInfoList.forEach(item => {
if (!item.ename || !item.rank) {
canPassPrize = false
}
})
if (+prizeInfoList[prizeInfoList.length - 1].rank.split('-') > 100) canPassPrize = false;
if (!canPassPrize) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, '奖品配置不正确');
}
}
if (!rule) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, '活动规则不可为空')
}
if (!startTime || !endTime) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, '请配置活动时间')
} else if (!activityId && startTime < Date.now()) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, '新建活动开始时间需小于当前时间')
} else if (startTime > endTime) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, '开始时间不得大于结束时间')
}
let shopInfoResult = await ActivityTop.getShopId();
if (!shopInfoResult.success) {
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, '获取淘宝店铺信息失败')
}
console.log(shopInfoResult, 'shopInfo');
const baseData = {
title,
subtitle,
startTime,
endTime,
rule,
shopId: shopInfoResult.data.sid,
openId,
taskInfo: {
specifyPageUrl,
browseItemIds,
attentionStore,
beMembership,
inviteFriends
}
}
let result
console.log('activityId', activityId)
try {
if (!activityId) {
result = await ActivityConfig.saveNewActivity(
baseData,
prizeInfoList,
openId
)
} else {
let prizeInfoListNoId = prizeInfoList.map(item => {
item.activityId = activityId
item.updateTime = Date.now()
item.createTime = Date.now()
item.deleteStatus = DELETE_STATUS.EXIST
delete item._id
return item
})
// console.log(prizeInfoListNoId, 'prizeInfoListNoId')
result = await ActivityConfig.updateActivity(
activityId,
baseData,
prizeInfoListNoId
)
}
if (result) {
return resultsModel.success(true)
} else {
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, '保存活动失败')
}
} catch (e) {
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, '保存活动失败')
}
}
//删除活动
const deleteActivityById = async function (context) {
loginfo(context, 'deleteActivityById')
let ActivityConfig = new ActivityConfigService(context)
const {
activityId = ''
} = context.data
try {
if (activityId) {
let result = await ActivityConfig.deleteActivityByActivityId(activityId)
if (result === 0 || result) {
return resultsModel.success(true)
} else {
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, '删除活动失败')
}
} else {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, '删除活动id不存在')
}
} catch (e) {
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, '删除活动失败')
}
}
//获取活动信息
const getActivityInfo = async function (context) {
loginfo(context, 'getActivityInfoByActivityId')
let ActivityConfig = new ActivityConfigService(context)
const {
activityId = ''
} = context.data
try {
if (activityId) {
let result = await ActivityConfig.getActivityInfoByActivityId(activityId)
const {
taskInfo,
...rest
} = result.baseConfig
let prizeInfoList = result.prizeConfig.sort((a, b) => {
return a.rank.split('-')[0] - b.rank.split('-')[0]
})
let activityInfo = {
...taskInfo,
activityId,
...rest,
prizeInfoList
}
return resultsModel.success(activityInfo)
} else {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, '当前活动不存在')
}
} catch (e) {
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, '获取活动配置信息失败')
}
}
module.exports = {
getActivityListByOpenId,
saveActivityInfoByHasId,
deleteActivityById,
getActivityInfo
}
\ No newline at end of file
const {
formatTime
} = require('../utils/utils');
const {
CODE_TYPES
} = require('../utils/constants');
const ResultsModel = require('../utils/results.model');
let resultsModel = new ResultsModel();
//生成规则
module.exports = async (context) => {
console.log(JSON.stringify(context.data), 'context.data')
let {
title = '', startTime = '', endTime = '', prizeInfoList = []
} = context.data;
if (!title) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, '活动名称为空');
} else if (title.length > 12) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, '活动名称不可超过12个字');
}
if (!startTime || !endTime) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, '请配置活动时间');
} else if (startTime > endTime) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, '开始时间不得大于结束时间');
}
if (prizeInfoList.length === 0) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, '奖品至少配置3名');
} else if (prizeInfoList.length > 8) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, '奖品最多配置8个档位');
} else {
let canPassPrize = true;
prizeInfoList.forEach(item => {
if (!item.ename || !item.rank) {
canPassPrize = false;
}
})
if (!canPassPrize) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, '奖品配置不正确');
}
}
let resultRule = '';
let startTimeDate = formatTime(new Date(+startTime), "yyyy-MM-dd hh:mm:ss");
let endTimeDate = formatTime(new Date(+endTime), "yyyy-MM-dd hh:mm:ss");
resultRule = `1.活动时间:${startTimeDate}--${endTimeDate};\n2.活动结束根据排行榜排名获得对应的奖励,每个人取活动中单局最高分;\n3.每天3次免费参与资格,超过次数后邀请群内好友参与游戏可获得额外次数(每个用户可助力一次);\n4.参与签到可获得道具,每局结束游戏后可使用道具;\n5.完成任务每天可获得额外的复活机会,每局只能使用一张复活卡;\n\n`
let prizeStr = prizeInfoList.reduce((s, v) => {
let rankArr = v.rank.split('-');
if (rankArr[0] == rankArr[1]) {
return s += `第${rankArr[0]}名: ${v.name}\n`
} else {
return s += `第${v.rank}名: ${v.name}\n`
}
}, '奖品:\n')
resultRule += prizeStr;
return resultsModel.success(resultRule);
}
\ No newline at end of file
const {
loginfo
} = require('../utils/utils');
const {
CODE_TYPES
} = require('../utils/constants');
const ResultsModel = require('../utils/results.model');
let resultsModel = new ResultsModel();
const ActivityTopService = require('../service/activitytop.service');
//通过状态获取top商品列表
const findItemListByStatusAction = async function (context) {
loginfo(context, 'findItemListByStatus');
let ActivityTop = new ActivityTopService(context);
try {
let result = await ActivityTop.getItemListByStatus();
return result;
} catch (e) {
console.log(e, '获取top商品列表失败')
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, '获取top商品列表失败');
}
}
//通过itemIds获取商品列表
const findItemListByIdsAction = async (context) => {
loginfo(context, 'findItemListByIdsAction');
let ActivityTop = new ActivityTopService(context);
try {
let result = ActivityTop.getItemListByIds();
return result;
} catch (e) {
console.log(e, '获取商品列表失败')
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, '获取商品列表失败');
}
}
//通过ename获取权益信息
const getBenefitByEname = async (context) => {
loginfo(context, 'getBenefitByEname');
let ActivityTop = new ActivityTopService(context);
try {
let result = await ActivityTop.getPrizeByEname();
return result;
} catch (e) {
console.log(e, '获取ename权益信息失败')
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, '获取ename权益信息失败');
}
}
module.exports = {
findItemListByIdsAction,
findItemListByStatusAction,
getBenefitByEname
}
\ No newline at end of file
const ActivitySellerService = require('../service/activityseller.service');
const {
CODE_TYPES
} = require('../utils/constants');
const {
getSellerInfoByContext
} = require('../config/seller_config');
const ActivityTopService = require('../service/activitytop.service');
const {
loginfo
} = require('../utils/utils');
const ResultsModel = require('../utils/results.model');
let resultsModel = new ResultsModel();
//保存授权信息
module.exports = async (context) => {
loginfo(context, 'sellerInfo');
const {
openId,
accessToken,
appKey,
userNick,
appOwnerOpenId
} = context;
let ActivitySeller = new ActivitySellerService(context);
let ActivityTop = new ActivityTopService(context);
try {
let hasResult = await ActivitySeller.findSellerInfo(context.openId);
console.log(hasResult, 'hasSellerResult');
if (hasResult && hasResult[0]) {
await ActivitySeller.updateSellerInfo(context.openId, {
accessToken,
appKey,
userNick,
appOwnerOpenId
})
return resultsModel.success(true);
} else {
let shopInfoResult = await ActivityTop.getShopId();
if (!shopInfoResult.success) {
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, '获取淘宝店铺信息失败')
}
try {
let result = await ActivitySeller.saveSellerInfo({
accessToken,
openId,
appKey,
shopId: shopInfoResult.data.sid,
userNick,
appOwnerOpenId
})
return resultsModel.success(result);
} catch (e) {
console.log(e, '保存授权信息失败');
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, '保存授权信息失败');
}
}
} catch (e) {
console.log(e, '获取授权信息失败');
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, '获取授权信息失败');
}
}
\ No newline at end of file
/**
* 中奖名单 及导出中奖名单
*/
const RankopenprizeService = require('../service/rankopenprize.service');
const ResultsModel = require('../utils/results.model');
const {
CODE_TYPES
} = require('../utils/constants');
const xlsx = require('node-xlsx');
let resultsModel = new ResultsModel();
// 活动中奖名单
const findWinnerInfoList = async (context) => {
let {
activityId
} = context.data;
if (!activityId) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, `缺少activityId`);
}
let rankopenprizeService = new RankopenprizeService(context);
// 获取活动中奖列表
let awardslist = await rankopenprizeService.getAwardslistByActivityId(activityId);
console.log(`awardslist: ${JSON.stringify(awardslist)}`);
let winnersObj = {};
awardslist.forEach((v, index, arr) => {
if (!winnersObj[v.rank]) {
winnersObj[v.rank] = [];
}
winnersObj[v.rank].push({
userNick: v.userNick,
id: v.openId
});
});
console.log(`winnersObj: ${JSON.stringify(winnersObj)}`);
let results = [];
new Map(Object.entries(winnersObj)).forEach((v, index, arr) => {
results.push({
rank: index,
winnerDetailList: v
});
});
return resultsModel.success(results);
}
// 导出活动中奖名单
const exportAwardsList = async (context) => {
let {
cloud
} = context;
let {
activityId,
title
} = context.data;
if (!activityId) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, `缺少activityId`);
}
if (!title) {
return resultsModel.error(CODE_TYPES.PARAMS_ERROR, `缺少title`);
}
let rankopenprizeService = new RankopenprizeService(context);
// 获取活动中奖列表
let awardslist = await rankopenprizeService.getAwardslistByActivityId(activityId);
console.log(`awardslist: ${JSON.stringify(awardslist)}`);
let xlsxData = [
['序列', '名次', '昵称', '中奖名称']
];
awardslist.forEach((v, index, arr) => {
xlsxData.push([index + 1, v.rank, v.userNick, v.prizeName]);
});
let buffer = xlsx.build([{
name: title + new Date().getTime(),
data: xlsxData
}]);
console.log(`xlsxData: ${JSON.stringify(xlsxData)}`);
try {
let result = await cloud.file.uploadFile({
fileContent: buffer,
fileName: title + new Date().getTime() + '.xlsx'
});
// result.url 需进行处理
if (result.url) {
result.url = result.url.replace('http', 'https').replace('-internal', '');
}
return resultsModel.success(result);
} catch (e) {
console.log('上传文件出错', e);
// 打印日志
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, `上传文件错误`);
}
}
module.exports = {
findWinnerInfoList,
exportAwardsList
}
\ No newline at end of file
const RankscanService = require('../service/rankscan.service')
const ActivityprizeService = require('../service/activityprize.service')
const RankscoreService = require('../service/rankscore.service');
const RankopenprizeService = require('../service/rankopenprize.service');
const ResultsModel = require('../utils/results.model');
let resultsModel = new ResultsModel();
// 声明排行榜扫描服务
let rankscanService = null;
// 声明奖品服务
let activityprizeService = null;
// 声明分数排行榜服务
let rankscoreService = null;
// 声明待开奖服务
let rankopenprizeService = null;
// 查询该活动的奖品列表 及 发奖数
const getPrizeListAndAwards = async (activityId) => {
let prizeList = await activityprizeService.getPrizeListByActivityId(activityId);
console.log(prizeList);
// 若不存在奖品列表,则为脏数据,不做处理
if (!prizeList.length) {
console.log(`活动Id为${activityId}不存在对应的奖品信息`);
return {
prizeList: [],
totalAwards: 0
};
}
// 获取总共发奖的个数
let totalAwards = prizeList.reduce((total, curVal, curIndex, arr) => {
// 当没有rank名次,直接返回
if (!curVal.rank) {
return total;
}
let awards = +curVal.rank.split('-')[1];
if (awards > total) {
total = awards;
}
return total;
}, 0);
return {
prizeList,
totalAwards
}
}
// 获取待开奖列表
const getWaitAwardsList = (prizeList, topscoreList, totalAwards, shopId) => {
let waitAwardslist = [];
// 若分数排行榜没有数据,或发奖数为0,返回[]
if (!totalAwards || !topscoreList.length) {
return waitAwardslist;
}
prizeList.forEach((v, index, arr) => {
if (!v.rank) {
return;
}
let rankPeriod = v.rank.split('-');
// 截取排行区间的分数排行列表
let ranklist = topscoreList.slice(+rankPeriod[0] - 1, Math.min(+rankPeriod[1], totalAwards));
// 整合开奖数据
ranklist.map(rank => {
waitAwardslist.push({
activityId: v.activityId,
openId: rank.openId,
rank: v.rank,
ename: v.ename,
startTime: v.startTime,
endTime: v.endTime,
prizeId: v.goodsId,
prizeName: v.name,
userNick: rank.userNick,
rankTime: rank.updateTime,
image: v.image,
score: rank.score,
shopId: shopId
});
});
});
return waitAwardslist;
}
// 设置活动开奖
const setActivity2openprize = async (waitAwardslist, _id) => {
// 插入排行榜开奖记录表 rank_open_prize 若失败,则变更
let results = await rankopenprizeService.addWaitAwardsList(waitAwardslist);
console.log(`result: ${JSON.stringify(results)}`);
if (results) {
await rankscanService.update2Success(_id);
console.log(`开奖成功`);
return true;
} else {
await rankscanService.update2Fail(_id, `批量插入rank_open_prize表不成功`);
console.log(`开奖失败`);
return false;
}
}
/**
* 定时触发开奖
*/
const endingNotify = async (context) => {
// 初始化排行榜扫描服务
rankscanService = new RankscanService(context);
// 初始化返回结果
let results = {
success: [],
fails: []
};
// 获取服务器时间
let serverTime = Date.now();
// 触发开奖列表
let notifyList = await rankscanService.getNodifyList(serverTime);
console.log(notifyList.length);
if (!notifyList.length) {
console.log(`没有待开奖的活动`);
return resultsModel.success(results);
}
// 初始化奖品服务
activityprizeService = new ActivityprizeService(context);
// 分数排行榜
rankscoreService = new RankscoreService(context);
// 待开奖服务
rankopenprizeService = new RankopenprizeService(context);
// 遍历列表
for (let i = 0; i < notifyList.length; i++) {
let {
activityId,
_id,
shopId
} = notifyList[i];
// 将该条记录变更为处理中
let updateResult = await rankscanService.update2Process(_id);
console.log(`updateResult: ${updateResult}`)
if (!updateResult) {
console.log(`将rank_scan该活动的开奖状态变更为处理中失败`);
continue;
}
// 查询该活动的奖品列表 及 发奖数
let {
prizeList,
totalAwards
} = await getPrizeListAndAwards(activityId);
console.log(`prizeList: ${JSON.stringify(prizeList)}; totalAwards: ${totalAwards}`);
// 没查找到奖品列表, 继续下个活动循环
if (!prizeList.length) {
// 开奖失败,记录日志
await rankscanService.update2Fail(_id, `活动不存在对应的奖品列表,开奖失败`);
results.fails.push(activityId);
continue;
}
// 查找排行榜分数榜里的前totalAwards个记录
let topscoreList = await rankscoreService.getToplistByActivityId(activityId, totalAwards);
console.log(`topscoreList: ${JSON.stringify(topscoreList)}`)
// 根据分数排行榜列表及奖品列表整合数据,待开奖列表
let waitAwardslist = getWaitAwardsList(prizeList, topscoreList, totalAwards, shopId);
// if (!waitAwardslist.length) {
// }
console.log(`waitAwardslist: ${JSON.stringify(waitAwardslist)}`)
// 开奖
let issuccess = await setActivity2openprize(waitAwardslist, _id);
issuccess ? results.success.push(activityId) : results.fails.push(activityId);
// end
}
return resultsModel.success(results);
}
module.exports = {
endingNotify
}
\ No newline at end of file
const ResultsModel = require('../utils/results.model');
const { CODE_TYPES } = require('../utils/constants');
const GameService = require("../service/game.service");
/**
* 获取游戏规则标题等相关配置
* @param {*} context
*/
const getGameInfo = async (context) => {
const { activityId,
nickName='暂无昵称' } = context.data;
if (!activityId) {
return new ResultsModel().error(CODE_TYPES.PARAMS_ERROR);
}
const gameService = new GameService(context);
return await gameService.getGameInfo(activityId,nickName);
}
module.exports = {
getGameInfo
}
\ No newline at end of file
const ResultsModel = require('../utils/results.model');
const { CODE_TYPES } = require('../utils/constants');
const ActivityprizeService = require("../service/activityprize.service");
const PrizeService = require("../service/prize.service");
/**
* 获取排行版下方奖品展示
* @param {*} context
*/
const getRankPrize = async (context) => {
let activityId = context.data.activityId;
if (!activityId) {
return new ResultsModel().error(CODE_TYPES.PARAMS_ERROR);
}
const activityprizeService = new ActivityprizeService(context);
let result = await activityprizeService.getPrizeListByActivityId(activityId);
let resultData = result.sort((a,b)=>{
return +a.rank.split("-")[0]>+b.rank.split("-")[0];
})
return new ResultsModel().success(resultData)
}
/**
* 获取我的奖品信息
* @param {*} context
*/
const getMyPrize = async (context) => {
let activityId = context.data.activityId;
if (!activityId) {
return new ResultsModel().error(CODE_TYPES.PARAMS_ERROR);
}
const prizeService = new PrizeService(context);
return await prizeService.getMyPrize(activityId);
}
/**
* 领奖
* @param {*} context
*/
const collectionPrize = async (context) => {
let {activityId, ename } = context.data;
if (!activityId || !ename) {
return new ResultsModel().error(CODE_TYPES.PARAMS_ERROR);
}
const prizeService = new PrizeService(context);
return await prizeService.collectionPrize(activityId,ename);
}
const getPrizeList = async(context)=>{
let {activityId } = context.data;
if (!activityId ) {
return new ResultsModel().error(CODE_TYPES.PARAMS_ERROR);
}
const prizeService = new PrizeService(context);
return await prizeService.getPrizeList(activityId);
}
module.exports = {
getRankPrize,
getMyPrize,
collectionPrize,
getPrizeList
}
const ResultsModel = require('../utils/results.model');
const { CODE_TYPES } = require('../utils/constants');
const RankService = require("../service/rank.service");
/**
* 提交分数
* @param {*} context
*/
const doScoreSubmit = async (context)=>{
let{
activityId,
score,
nickName,
avatar
} = context.data;
if(!activityId || !score || !nickName || !avatar)
{
return new ResultsModel().error(CODE_TYPES.PARAMS_ERROR);
}
const rankService=new RankService(context);
return await rankService.doScoreSubmit(activityId,score,nickName,avatar);
}
/**
* 获取排行
* @param {*} context
*/
const getRankList = async(context)=>{
let{
activityId,
page=1,
limit=10
} = context.data;
if(!activityId)
{
return new ResultsModel().error(CODE_TYPES.PARAMS_ERROR);
}
const rankService=new RankService(context);
return await rankService.getRankList(activityId,page,limit);
}
module.exports={
doScoreSubmit,getRankList
}
\ No newline at end of file
const ResultsModel = require('../utils/results.model');
const { CODE_TYPES } = require('../utils/constants');
const ShareService = require("../service/share.service");
/**
* 获取分享id
* @param {*} context
*/
const getShareId = async (context)=>{
let{
activityId
} = context.data;
if(!activityId )
{
return new ResultsModel().error(CODE_TYPES.PARAMS_ERROR);
}
const shareService=new ShareService(context);
return await shareService.getShareId(activityId);
}
/**
* 验证分享
* @param {*} context
*/
const doShareComplete = async(context)=>{
let{
activityId,
shareId
} = context.data;
if(!activityId || !shareId)
{
return new ResultsModel().error(CODE_TYPES.PARAMS_ERROR);
}
const shareService=new ShareService(context);
return await shareService.doShareComplete(activityId,shareId);
}
module.exports={
getShareId,doShareComplete
}
\ No newline at end of file
const ResultsModel = require('../utils/results.model');
const { CODE_TYPES } = require('../utils/constants');
const StatExportService = require('../service/statexport.service');
const statTotalData = async(context)=>{
let activityId=context.data.activityId;
if(!activityId)
{
return new ResultsModel().error(CODE_TYPES.PARAMS_ERROR);
}
const statExportService=new StatExportService(context);
return await statExportService.statTotalData(activityId);
}
const statJoinData = async(context)=>{
let activityId=context.data.activityId;
if(!activityId)
{
return new ResultsModel().error(CODE_TYPES.PARAMS_ERROR);
}
const statExportService=new StatExportService(context);
return await statExportService.statJoinData(activityId);
}
const statTaskData = async(context)=>{
let activityId=context.data.activityId;
if(!activityId)
{
return new ResultsModel().error(CODE_TYPES.PARAMS_ERROR);
}
const statExportService=new StatExportService(context);
return await statExportService.statTaskData(activityId);
}
const statItemData = async(context)=>{
let activityId=context.data.activityId;
if(!activityId)
{
return new ResultsModel().error(CODE_TYPES.PARAMS_ERROR);
}
const statExportService=new StatExportService(context);
return await statExportService.statItemData(activityId);
}
const statPrizeData = async(context)=>{
let activityId=context.data.activityId;
if(!activityId)
{
return new ResultsModel().error(CODE_TYPES.PARAMS_ERROR);
}
const statExportService=new StatExportService(context);
return await statExportService.statPrizeData(activityId);
}
module.exports = {statTotalData,statJoinData,statTaskData,statItemData,statPrizeData}
\ No newline at end of file
const ResultsModel = require('../utils/results.model');
const { CODE_TYPES } = require('../utils/constants');
const StatItemService = require('../service/statitem.service');
const itemShow = async(context)=>{
let activityId=context.data.activityId;
if(!activityId)
{
return new ResultsModel().error(CODE_TYPES.PARAMS_ERROR);
}
const statItemService=new StatItemService(context);
return await statItemService.itemShow(activityId);
}
const itemTap = async(context)=>{
let activityId=context.data.activityId;
if(!activityId)
{
return new ResultsModel().error(CODE_TYPES.PARAMS_ERROR);
}
const statItemService=new StatItemService(context);
return await statItemService.itemTap(activityId);
}
module.exports = {itemShow,itemTap}
\ No newline at end of file
const ResultsModel = require('../utils/results.model');
const { CODE_TYPES } = require('../utils/constants');
const TaskService = require("../service/task.service");
/**
* 获取任务列表
* @param {} context
*/
const getTaskList=async(context)=>{
let{
activityId
} = context.data;
if(!activityId )
{
return new ResultsModel().error(CODE_TYPES.PARAMS_ERROR);
}
const taskService=new TaskService(context);
return await taskService.getTaskList(activityId);
}
/**
* 完成任务
* @param {*} context
*/
const doTaskComplete= async(context)=>{
let{
activityId,
taskName
} = context.data;
if(!activityId )
{
return new ResultsModel().error(CODE_TYPES.PARAMS_ERROR);
}
const taskService=new TaskService(context);
return await taskService.doTaskComplete(activityId,taskName);
}
/**
* 检查会员
* @param {*} context
*/
const checkMember= async(context)=>{
let{
activityId
} = context.data;
if(!activityId )
{
return new ResultsModel().error(CODE_TYPES.PARAMS_ERROR);
}
const taskService=new TaskService(context);
return await taskService.checkMember(activityId);
}
module.exports={
getTaskList,doTaskComplete,checkMember
}
\ No newline at end of file
const ResultsModel = require('../utils/results.model');
const { CODE_TYPES } = require('../utils/constants');
const UserService = require("../service/user.service");
/**
* 用户登录
*/
const login = async (context) => {
let { activityId,
avatar,
nickName,
isFavor
} = context.data;
if (!activityId || !avatar || !nickName || isFavor == undefined) {
return new ResultsModel().error(CODE_TYPES.PARAMS_ERROR);
}
let userService = new UserService(context);
return await userService.login(activityId, nickName, avatar,isFavor);
}
/**
* 获取体力值
* @param {*} context
*/
const getPower = async (context) => {
let activityId = context.data.activityId;
if (!activityId) new ResultsModel().error(CODE_TYPES.PARAMS_ERROR_2C);
let userService = new UserService(context);
return await userService.getPower(activityId);
}
/**
* 开始游戏
* @param {*} context
*/
const startGame = async (context) => {
let {activityId,nickName='暂无昵称'} = context.data;
if (!activityId ) new ResultsModel().error(CODE_TYPES.PARAMS_ERROR_2C);
let userService = new UserService(context);
return await userService.startGame(activityId,nickName);
}
module.exports = {
login,
getPower,
startGame
}
\ No newline at end of file
class BaseDao {
constructor(context, dbName) {
this.db = context.cloud.db;
this.dbName = dbName;
this.openId = context.openId;
this.context = context;
}
async find(query, options) {
console.log(`query: ${JSON.stringify(query)}`)
console.log(`options: ${JSON.stringify(options)}`)
return await this.db.collection(this.dbName).find(query, options);
}
async findOne(query, options) {
console.log(`query: ${JSON.stringify(query)}`)
console.log(`options: ${JSON.stringify(options)}`)
const result = await this.db.collection(this.dbName).find(query, options);
if(result.length)return result[0];
return null;
}
// 更新数据
async update(query, options) {
return await this.db.collection(this.dbName).updateMany(query, options);
}
//插入单条数据
async insertOne(options) {
return await this.db.collection(this.dbName).insertOne(options);
}
async count(query) {
return await this.db.collection(this.dbName).count(query);
}
async deleteMany(query) {
return await this.db.collection(this.dbName).deleteMany(query);
}
// options Array
async insertMany(options) {
return await this.db.collection(this.dbName).insertMany(options);
}
}
module.exports = BaseDao;
\ No newline at end of file
const ACTIVITY_BASE_CONFIG = 'activity_base_config';
const ACTIVITY_PRIZE_CONFIG = 'activity_prize_config';
const ACTIVITY_SELLER_SAVE = 'activity_seller_save';
module.exports = {
ACTIVITY_BASE_CONFIG,
ACTIVITY_PRIZE_CONFIG,
ACTIVITY_SELLER_SAVE
}
\ No newline at end of file
const {
endingNotify
} = require('./controller/endingNodify.controller');
const {
findWinnerInfoList,
exportAwardsList
} = require('./controller/awards.controller');
const {
login,
getPower,
startGame
} = require('./controller/user.controller');
const {
doScoreSubmit,
getRankList
} = require('./controller/rank.controller');
const {
getShareId,
doShareComplete
} = require('./controller/share.controller');
const {
getTaskList,
doTaskComplete,
checkMember
} = require('./controller/task.controller');
const {
getGameInfo
} = require('./controller/game.controller');
const {
getRankPrize,
getMyPrize,
collectionPrize,
getPrizeList
} = require('./controller/prize.controller');
const {
getActivityListByOpenId,
saveActivityInfoByHasId,
deleteActivityById,
getActivityInfo
} = require('./controller/activity.controller');
const saveSellerInfo = require('./controller/activityseller.controller');
const generateRuleAction = require('./controller/activitygenerateRule.controller');
const {
findItemListByStatusAction,
findItemListByIdsAction,
getBenefitByEname,
getVipUrlAction
} = require("./controller/activitygettbitem.controller");
const {
statTotalData,
statJoinData,
statTaskData,
statItemData,
statPrizeData
} = require("./controller/statexport.controller");
const{
itemShow,
itemTap
}=require("./controller/statitem.controller");
//数据统计相关
exports.statTotalData = statTotalData;
exports.statJoinData = statJoinData;
exports.statTaskData = statTaskData;
exports.statItemData = statItemData;
exports.statPrizeData=statPrizeData;
exports.itemShow = itemShow;
exports.itemTap=itemTap;
// 定时开奖
exports.endingNotify = endingNotify;
// 活动中奖名单
exports.findWinnerInfoList = findWinnerInfoList;
// 导出中奖名单
exports.exportAwardsList = exportAwardsList;
exports.login = login;
exports.getPower = getPower;
exports.startGame = startGame;
exports.doScoreSubmit = doScoreSubmit;
exports.getRankList = getRankList;
exports.getShareId = getShareId;
exports.doShareComplete = doShareComplete;
exports.getTaskList = getTaskList;
exports.doTaskComplete = doTaskComplete;
exports.checkMember=checkMember;
exports.getGameInfo = getGameInfo;
exports.getRankPrize = getRankPrize;
exports.getMyPrize = getMyPrize;
exports.collectionPrize = collectionPrize;
exports.getPrizeList = getPrizeList;
exports.getActivityList = getActivityListByOpenId;
exports.saveActivityInfo = saveActivityInfoByHasId;
exports.delActivity = deleteActivityById;
exports.getActivityDetail = getActivityInfo;
exports.sellerSave = saveSellerInfo;
exports.generateRule = generateRuleAction;
exports.findItemListByStatus = findItemListByStatusAction;
exports.findItemListByIds = findItemListByIdsAction;
exports.queryBenefitByEname = getBenefitByEname;
{
"name": "duiba",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"adler-32": {
"version": "1.2.0",
"resolved": "https://registry.npm.taobao.org/adler-32/download/adler-32-1.2.0.tgz",
"integrity": "sha1-aj5r8KY5ALoVZSgIyxXGgT0aXyU=",
"requires": {
"exit-on-epipe": "~1.0.1",
"printj": "~1.1.0"
}
},
"buffer-from": {
"version": "1.1.1",
"resolved": "https://registry.npm.taobao.org/buffer-from/download/buffer-from-1.1.1.tgz",
"integrity": "sha1-MnE7wCj3XAL9txDXx7zsHyxgcO8="
},
"cfb": {
"version": "1.1.4",
"resolved": "https://registry.npm.taobao.org/cfb/download/cfb-1.1.4.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcfb%2Fdownload%2Fcfb-1.1.4.tgz",
"integrity": "sha1-gf017eTJGdjwliqUWC4d+vcFHio=",
"requires": {
"adler-32": "~1.2.0",
"commander": "^2.16.0",
"crc-32": "~1.2.0",
"printj": "~1.1.2"
}
},
"codepage": {
"version": "1.14.0",
"resolved": "https://registry.npm.taobao.org/codepage/download/codepage-1.14.0.tgz",
"integrity": "sha1-jL4lSBMjVZ19MHVxsP/5HnodL5k=",
"requires": {
"commander": "~2.14.1",
"exit-on-epipe": "~1.0.1"
},
"dependencies": {
"commander": {
"version": "2.14.1",
"resolved": "https://registry.npm.taobao.org/commander/download/commander-2.14.1.tgz",
"integrity": "sha1-IjUSPjevjKPGXfRbAm29NXsBuao="
}
}
},
"commander": {
"version": "2.17.1",
"resolved": "https://registry.npm.taobao.org/commander/download/commander-2.17.1.tgz",
"integrity": "sha1-vXerfebelCBc6sxy8XFtKfIKd78="
},
"crc-32": {
"version": "1.2.0",
"resolved": "https://registry.npm.taobao.org/crc-32/download/crc-32-1.2.0.tgz",
"integrity": "sha1-yy224puIUI4y2d0OwWk+e0Ghggg=",
"requires": {
"exit-on-epipe": "~1.0.1",
"printj": "~1.1.0"
}
},
"exit-on-epipe": {
"version": "1.0.1",
"resolved": "https://registry.npm.taobao.org/exit-on-epipe/download/exit-on-epipe-1.0.1.tgz",
"integrity": "sha1-C92S6H1ShdJn2qgXHQ6wYVlolpI="
},
"frac": {
"version": "1.1.2",
"resolved": "https://registry.npm.taobao.org/frac/download/frac-1.1.2.tgz",
"integrity": "sha1-PXT39keMiKG1AgMG10fcYxPHTQs="
},
"node-xlsx": {
"version": "0.15.0",
"resolved": "https://registry.npm.taobao.org/node-xlsx/download/node-xlsx-0.15.0.tgz",
"integrity": "sha1-HxsNetzlxwboa/2WpaoABb+KncM=",
"requires": {
"buffer-from": "^1.1.0",
"xlsx": "^0.14.1"
}
},
"printj": {
"version": "1.1.2",
"resolved": "https://registry.npm.taobao.org/printj/download/printj-1.1.2.tgz",
"integrity": "sha1-2Q3rKXWoufYA+zoclOP0xTx4oiI="
},
"ssf": {
"version": "0.10.3",
"resolved": "https://registry.npm.taobao.org/ssf/download/ssf-0.10.3.tgz",
"integrity": "sha1-jq4fwpyQpVLnkhII+BiS1vd6yys=",
"requires": {
"frac": "~1.1.2"
}
},
"xlsx": {
"version": "0.14.5",
"resolved": "https://registry.npm.taobao.org/xlsx/download/xlsx-0.14.5.tgz",
"integrity": "sha1-NjfpFNeRvcpzgoFuFz99cl7Q4NI=",
"requires": {
"adler-32": "~1.2.0",
"cfb": "^1.1.2",
"codepage": "~1.14.0",
"commander": "~2.17.1",
"crc-32": "~1.2.0",
"exit-on-epipe": "~1.0.1",
"ssf": "~0.10.2"
}
}
}
}
{
"name": "duiba",
"version": "1.0.0",
"description": "",
"main": "index.js",
"author": "",
"license": "ISC",
"sdkVersion": "*",
"dependencies": {
"node-xlsx": "^0.15.0"
},
"config": {
"notNeedLogin": ["endingNotify"]
}
}
const {
formatTime
} = require('../utils/utils');
const secret = '99f00e460bfae520f85ae5f5a38e5b4a';
const appKey = 28640163;
const getSellerInfoByContext = (context) => {
return {
timestamp: formatTime(new Date(), "yyyy-MM-dd hh:mm:ss"),
open_id: context.openId,
'appkey': appKey,
'appsecret': secret,
'session': context.accessToken,
REST_URL: 'http://gw.api.taobao.com/router/rest'
}
}
const getConsumerSeller = ({
accessToken,
openId
}) => {
return {
timestamp: formatTime(new Date(), "yyyy-MM-dd hh:mm:ss"),
open_id: openId,
'appkey': appKey,
'appsecret': secret,
'session': accessToken,
REST_URL: 'http://gw.api.taobao.com/router/rest'
}
}
module.exports = {
getSellerInfoByContext,
getConsumerSeller
};
\ No newline at end of file
const BaseDao = require('../dao/base.dao');
const {
ACTIVITY_BASE_CONFIG,
ACTIVITY_PRIZE_CONFIG
} = require('../config/db_config');
const {
DELETE_STATUS
} = require("../utils/constants");
module.exports = class ActivityConfigService {
constructor(context) {
this.activityconfigDao = new BaseDao(context, ACTIVITY_BASE_CONFIG);
this.activityconfigPrizeDao = new BaseDao(context, ACTIVITY_PRIZE_CONFIG);
}
//保存新建活动
async saveNewActivity(data, prize, openId) {
try {
let result = await this.activityconfigDao.insertOne({
...data,
createTime: Date.now,
deleteStatus: DELETE_STATUS.EXIST,
updateTime: Date.now
});
console.log(result, '保存基础配置result')
if (result) {
try {
prize.map(item => {
item.activityId = result;
item.openId = openId;
item.createTime = Date.now();
item.updateTime = Date.now();
item.deleteStatus = DELETE_STATUS.EXIST;
return item;
})
return await this.activityconfigPrizeDao.insertMany(prize);
} catch (e) {
console.log(e, '保存奖品配置出错')
// 如果奖品未保存删除之前的活动
await this.activityconfigDao.deleteMany({
_id: result
})
}
}
} catch (e) {
//保存活动失败
console.log(e, '保存活动失败')
}
}
//更新活动配置
async updateActivity(activityId, data, prizeNoId) {
console.log(activityId, data, prizeNoId, 'activityId, data, prizeNoId, oldPrizeIds')
try {
let originalData = await this.activityconfigDao.find({
_id: activityId
})
let result = await this.activityconfigDao.update({
_id: activityId
}, {
$set: {
...data,
createTime: originalData[0].createTime,
updateTime: Date.now()
}
});
if (result) {
try {
let prizeOldIdsByActivity = (await this.activityconfigPrizeDao.find({
activityId
})).reduce((s, v) => {
return s = [...s, v._id];
}, [])
let prizeResult = await this.activityconfigPrizeDao.insertMany(prizeNoId);
if (prizeResult) {
//删除之前的奖品
let deleteOldPrizeList = prizeOldIdsByActivity.reduce((s, item) => {
return s = [...s, this.activityconfigPrizeDao.deleteMany({
_id: item
})]
}, [])
return await Promise.all(deleteOldPrizeList);
}
} catch (e) {
await this.activityconfigDao.update({
_id: activityId
}, {
$set: {
...originalData[0]
}
})
}
}
} catch (e) {
//更新失败
}
}
//通过openId去获取当前商家活动列表
async getActivityListByOpenId(openId, {
pageSize = 10,
pageNo = 1
}) {
return {
list: await this.activityconfigDao.find({
openId,
deleteStatus: DELETE_STATUS.EXIST
}, {
projection: {
startTime: 1,
endTime: 1,
title: 1
},
sort: {
updateTime: -1
},
limit: pageSize,
skip: (pageNo - 1) * pageSize
}),
total: await this.activityconfigDao.count({
openId
}),
pageSize,
pageNo
}
}
//通过activityId去获取活动配置信息
async getActivityInfoByActivityId(activityId) {
return {
baseConfig: (await this.activityconfigDao.find({
_id: activityId,
deleteStatus: DELETE_STATUS.EXIST
}))[0],
prizeConfig: await this.activityconfigPrizeDao.find({
activityId,
deleteStatus: DELETE_STATUS.EXIST
})
}
}
//通过activityId去删除活动
async deleteActivityByActivityId(activityId) {
try {
let result = await this.activityconfigDao.update({
_id: activityId
}, {
$set: {
deleteStatus: DELETE_STATUS.DELETE
}
})
if (result) {
return await this.activityconfigPrizeDao.update({
activityId
}, {
$set: {
deleteStatus: DELETE_STATUS.DELETE
}
})
}
} catch (e) {
console.log(e, '删除活动失败')
}
}
}
\ No newline at end of file
const BaseDao = require('../dao/base.dao');
const { OPEN_PRIZE_STATUS } = require('../utils/constants');
const DBName = 'activity_prize_config';
class ActivityprizeService {
constructor(context) {
this.activityprizeDao = new BaseDao(context, DBName);
}
// 根据活动id查询奖品列表
async getPrizeListByActivityId(activityId) {
console.log(13, activityId)
return await this.activityprizeDao.find({
activityId: activityId
});
}
}
module.exports = ActivityprizeService;
\ No newline at end of file
const BaseDao = require('../dao/base.dao');
const {
ACTIVITY_SELLER_SAVE
} = require('../config/db_config');
module.exports = class ActivitySellerService {
constructor(context) {
this.activitysellerDao = new BaseDao(context, ACTIVITY_SELLER_SAVE);
}
//保存商家授权信息
async saveSellerInfo(sellerInfo) {
return await this.activitysellerDao.insertOne({
...sellerInfo,
updateTime: Date.now(),
createTime: Date.now()
})
}
//查找商家授权信息
async findSellerInfo(openId) {
return await this.activitysellerDao.find({
openId
})
}
//更新商家授权信息
async updateSellerInfo(openId, sellerInfo) {
console.log(openId, sellerInfo, 'openId, sellerInfo')
return await this.activitysellerDao.update({
openId: openId
}, {
$set: {
...sellerInfo,
updateTime: Date.now()
}
})
}
}
\ No newline at end of file
const {
ACTIVITY_SELLER_SAVE,
ACTIVITY_BASE_CONFIG
} = require('../config/db_config');
const {
getConsumerSeller,
getSellerInfoByContext
} = require('../config/seller_config');
const {
CODE_TYPES,
GOODSINFO,
B_APP_NAME
} = require('../utils/constants');
const ResultsModel = require('../utils/results.model');
let resultsModel = new ResultsModel();
const BaseDao = require('../dao/base.dao');
class ActivityTopService {
constructor(context) {
this.activitySellerDao = new BaseDao(context, ACTIVITY_SELLER_SAVE);
this.activityBaseDao = new BaseDao(context, ACTIVITY_BASE_CONFIG);
this.context = context;
}
//获取商家授权session
async getAccessToken(activityId) {
let result = await this.activityBaseDao.find({
_id: activityId
})
if (result[0]) {
let openId = result[0].openId;
let sellResult = await this.activitySellerDao.find({
openId
})
if (sellResult[0]) {
let {
accessToken
} = sellResult[0];
return {
accessToken,
openId
};
}
}
}
//淘宝top接口获取商品列表
async getItemListByIds(activityId, itemIds) {
let sellerConfig;
if (activityId) {
let businessSeller = await this.getAccessToken(activityId);
sellerConfig = getConsumerSeller(businessSeller);
} else {
sellerConfig = getSellerInfoByContext(this.context);
itemIds = this.context.data.itemIds;
}
console.log(sellerConfig, 'sellerConfig')
try {
let result = await this.context.cloud.topApi.invoke({
api: 'taobao.items.seller.list.get',
data: {
...sellerConfig,
fields: GOODSINFO,
num_iids: itemIds
}
});
console.log(JSON.stringify(result), '获取商品通过ids');
if (result) {
let itemsData = {
list: result && result.items && result.items.item.reduce((s, v) => {
return s = [...s, {
"itemId": v.num_iid,
"name": v.title,
"price": v.price,
"detailUrl": v.detail_url,
"picUrl": v.pic_url
}]
}, []) || [],
totalCount: result.items.item.length
}
return resultsModel.success(itemsData);
}
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, '获取商家ids列表失败');
} catch (e) {
console.log(e, '获取ids商品列表失败');
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, '获取商家ids列表失败');
}
}
//淘宝top接口获取权益商品信息
async getPrizeByEname(activityId, ename) {
let sellerConfig;
if (activityId) {
let businessSeller = await this.getAccessToken(activityId);
sellerConfig = getConsumerSeller(businessSeller);
} else {
sellerConfig = getSellerInfoByContext(this.context);
ename = this.context.data.ename;
}
console.log(sellerConfig, 'sellerConfig')
try {
let benefitData = await this.context.cloud.topApi.invoke({
api: 'alibaba.benefit.query',
data: {
...sellerConfig,
ename,
app_name: B_APP_NAME,
award_type: 1,
}
});
if (benefitData.success) {
const {
result
} = benefitData;
let data = result.datas && result.datas["oright_dto"].reduce((s, v) => {
return s = [...s, {
benefitName: v.benefit_name,
rightTypeId: v.right_type_id,
startDate: v.start_date,
endDate: v.end_date
}]
}, []);
console.log(JSON.stringify(result), 'alibaba.benefit.query');
return resultsModel.success(data);
}
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, '获取ename商品信息失败');
} catch (e) {
console.log(e, '权益获取失败');
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, '获取ename商品信息失败');
}
}
//淘宝top接口获取店铺会员链接
async getVipUrlByActivity(activityId) {
let businessSeller = await this.getAccessToken(activityId);
let consumerSellerInfo = getConsumerSeller(businessSeller);
console.log(businessSeller, 'businessSeller', consumerSellerInfo, 'consumerSellerInfo')
try {
let result = await this.context.cloud.topApi.invoke({
api: 'taobao.crm.member.joinurl.get',
data: {
...consumerSellerInfo,
extra_info: {
"source": "isvapp",
"activityId": activityId,
"entrance": "duiba"
},
}
});
if (result) {
return resultsModel.success(result);
}
console.log(JSON.stringify(result), '获取商家会员链接');
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, '获取商家会员链接失败');
} catch (e) {
console.log(e, '获取商家会员链接失败');
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, '获取商家会员链接失败');
}
}
//淘宝top接口获取店铺信息
async getShopId() {
let sellerInfo = getSellerInfoByContext(this.context);
console.log(sellerInfo, 'getShopId sellerInfo')
try {
let result = await this.context.cloud.topApi.invoke({
api: 'taobao.shop.seller.get',
data: {
...sellerInfo,
fields: 'sid,title,pic_path',
v: '2.0'
}
})
console.log(result, 'getShopId result')
if (result) {
return resultsModel.success(result.shop);
} else {
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, '获取淘宝top店铺信息失败');
}
} catch (e) {
console.log(e, '获取店铺信息失败');
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, '获取淘宝top店铺信息失败');
}
}
async getItemListByStatus() {
let sellerConfig = getSellerInfoByContext(this.context);
const {
approveStatus = 'onsale',
title = '',
pageNo = 1,
pageSize = 10
} = this.context.data;
let data = {
fields: GOODSINFO,
page_no: pageNo,
q: title,
page_size: pageSize,
...sellerConfig
}
try {
let result = await this.context.cloud.topApi.invoke({
api: approveStatus === 'onsale' ? 'taobao.items.onsale.get' : 'taobao.items.inventory.get',
data,
autoSession: true
});
if (result) {
console.log(JSON.stringify(result), `comming ${approveStatus}`, 'success')
let {
items,
total_results
} = result;
let itemsData = {
pageNo,
pageSize,
totalPages: Math.ceil(total_results / pageSize),
totalCount: total_results,
list: items && items.item && items.item.reduce((s, v) => {
return s = [...s, {
"itemId": v.num_iid,
"name": v.title,
"price": v.price,
"approveStatus": v.approveStatus || approveStatus,
"picUrl": v.pic_url
}]
}, []) || []
}
return resultsModel.success(itemsData);
}
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, '获取商家在售列表失败');
} catch (e) {
console.log(e, '获取商家在售列表失败')
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, '获取商家在售列表失败');
}
}
}
module.exports = ActivityTopService;
\ No newline at end of file
const ResultsModel = require('../utils/results.model');
const { CODE_TYPES ,ACTIVITY_STATUS} = require('../utils/constants');
const {OPEN_PRIZE_STATUS}=require("../utils/constants");
const BaseDao = require('../dao/base.dao');
const DB_NAME = "activity_base_config";
const DB_RANKSCAN="rank_scan";
const StatPvuvService = require("./statpvuv.service");
class GameService
{
constructor(context)
{
this.baseConfigDao=new BaseDao(context,DB_NAME);
this.rankScanDao=new BaseDao(context,DB_RANKSCAN);
this.resultsModel=new ResultsModel();
this.statPvuvService=new StatPvuvService(context);
}
async getGameInfo(activityId,nickName)
{
try
{
await this.statPvuvService.insertUserOpenData(activityId,nickName);
const result =await this.baseConfigDao.find({_id:activityId},{
projection:{subtitle:1,rule:1,shopId:1}
})
const openPrizeInfo = await this.rankScanDao.find({activityId:activityId},{
projection:{openPrizeStatus:1}
})
const activityStatus = await this.checkActivityStatus(activityId);
if(result.length)
{
let resultData={
...result["0"],
activityStatus:activityStatus,
openPrizeStatus:openPrizeInfo["0"]&&openPrizeInfo["0"].openPrizeStatus || OPEN_PRIZE_STATUS.WAIT_AWARD,
}
return this.resultsModel.success(resultData);
}else{
//该活动ID无效
return this.resultsModel.error(CODE_TYPES.ERROR_NO_ACTIVITY);
}
}catch(e)
{
return this.resultsModel.error(CODE_TYPES.ERROR_SERVER);
}
}
/**
* 检测活动状态 - 未开始,已结束和已删除
* @param {*} activityId
*/
async checkActivityStatus(activityId)
{
const activityStatus = await this.baseConfigDao.find({_id:activityId},{projection:{deleteStatus:1,startTime: 1, endTime: 1}});
if(activityStatus.length)
{
let nowDate = Date.now();
//检测活动是否未开始
if (nowDate < +activityStatus["0"].startTime) {
return ACTIVITY_STATUS.NOSTART;
}
if (nowDate >= +activityStatus["0"].endTime) {
return ACTIVITY_STATUS.OVER;
}
//检测活动是否被删除
if(activityStatus["0"].deleteStatus != 1)
{
return ACTIVITY_STATUS.DELETE;
}
return ACTIVITY_STATUS.ING;
} else {
return ACTIVITY_STATUS.ERRORID;
}
}
}
module.exports=GameService;
\ No newline at end of file
const ResultsModel = require('../utils/results.model');
const ActivityTopService = require("./activitytop.service");
const { CODE_TYPES, DRAW_STATUS, APP_NAME, PRIZE_GET_DELAY_TIME } = require('../utils/constants');
const {formatTime} =require('../utils/utils');
const UserService = require("./user.service");
const BaseDao = require('../dao/base.dao');
const DB_NAME = "rank_open_prize";
const DB_BASECFG = "activity_base_config";
class PrizeService {
constructor(context) {
this.rankOpenDao = new BaseDao(context, DB_NAME);
this.baseConfigDao = new BaseDao(context, DB_BASECFG);
this.resultsModel = new ResultsModel();
this.activityTopService = new ActivityTopService(context);
this.userService = new UserService(context);
}
async getMyPrize(activityId) {
try {
const activityStatus = await this.baseConfigDao.find({ _id: activityId }, { projection: { startTime:1,endTime: 1 } });
let endTime = +activityStatus["0"].endTime + PRIZE_GET_DELAY_TIME;
//已经过期状态
if (Date.now() >= endTime) {
let timeDate = formatTime(new Date(endTime+8*60*60*1000));
let msg = `未在${timeDate}前领取`;
await this.rankOpenDao.update({ activityId: activityId, openId: this.rankOpenDao.openId, drawStatus: { $ne: DRAW_STATUS.SUCCESS } }, {
$set: {
drawStatus: DRAW_STATUS.EXPIRED,
message: msg
}
})
}
const prizeInfo = await this.rankOpenDao.find({ activityId: activityId, openId: this.rankOpenDao.openId }, {
projection: {
activityId: 0, openId: 0, _id: 0
},
sort:{
rankTime:-1
}
});
if (!prizeInfo.length) {
return this.resultsModel.success(null);
}
let startTime = +activityStatus["0"].startTime;
const obj = {
...prizeInfo[0],
endTime,
startTime
}
return this.resultsModel.success(obj);
} catch (e) {
return this.resultsModel.error(CODE_TYPES.ERROR_SERVER);
}
}
/**
* 获取用户当前店铺下的所有奖品列表
* @param {*} activityId
*/
async getPrizeList(activityId) {
try {
const shopId = await this.userService.getShopIdByActivity(activityId);
if (shopId) {
const activityStatus = await this.baseConfigDao.find({ _id: activityId }, { projection: { endTime: 1 } });
let endTime = +activityStatus["0"].endTime + PRIZE_GET_DELAY_TIME;
//已经过期状态
if (Date.now() >= endTime) {
let timeDate = formatTime(new Date(endTime+8*60*60*1000));
let msg = `未在${timeDate}前领取`;
await this.rankOpenDao.update({ activityId: activityId, openId: this.rankOpenDao.openId, drawStatus: { $ne: DRAW_STATUS.SUCCESS } }, {
$set: {
drawStatus: DRAW_STATUS.EXPIRED,
message: msg
}
})
}
const prizeInfo = await this.rankOpenDao.find({ shopId: shopId, openId: this.rankOpenDao.openId }, {
projection: {
openId: 0, _id: 0
},
sort:{
rankTime:-1
}
});
if (prizeInfo.length) {
let resultObj = {
list: prizeInfo,
endTime: endTime
};
return this.resultsModel.success(resultObj);
}
return this.resultsModel.success();
} else {
return this.resultsModel.error(CODE_TYPES.ERROR_NO_SHOP);
}
} catch (e) {
return this.resultsModel.error(CODE_TYPES.PARAMS_ERROR);
}
}
async collectionPrize(activityId, ename) {
try {
//先查询该用户是否有中奖纪录
const prizeInfo = await this.rankOpenDao.find({ activityId: activityId, openId: this.rankOpenDao.openId, ename: ename });
if (!prizeInfo.length) {
return this.resultsModel.error(CODE_TYPES.ERROR_NO_PRIZE);
}
const activityStatus = await this.baseConfigDao.find({ _id: activityId }, { projection: { endTime: 1 } });
let endTime = +activityStatus["0"].endTime + PRIZE_GET_DELAY_TIME;
let timeDate = formatTime(new Date(endTime+8*60*60*1000));
//已经过期状态
if (Date.now() >= endTime) {
let msg = `未在${timeDate}前领取`;
await this.rankOpenDao.update({ activityId: activityId, openId: this.rankOpenDao.openId }, {
$set: {
drawStatus: DRAW_STATUS.EXPIRED,
message: msg
}
})
return this.resultsModel.error(CODE_TYPES.ERROR_PRIZE_EXPIRED);
}
const unique_id = prizeInfo["0"]._id;
const session = await this.activityTopService.getAccessToken(activityId);
const result = await this.rankOpenDao.context.cloud.topApi.invoke({
api: 'alibaba.benefit.send',
data: {
'right_ename': ename,
'receiver_id': this.rankOpenDao.openId,
'user_type': 'taobao',
'unique_id': unique_id,
'app_name': APP_NAME,
'session': session.accessToken
}
});
//发放成功
if (result.result_success) {
await this.rankOpenDao.update({ activityId: activityId, openId: this.rankOpenDao.openId }, {
$set: {
drawStatus: DRAW_STATUS.SUCCESS,
rightId: result.right_id, //新增权益ID
message: result.result_msg
}
})
return this.resultsModel.success();
} else {
//发放失败
let drawStatus = DRAW_STATUS.FAIL;
let msg = "";
switch (result.result_code) {
case "APPLY_SINGLE_COUPON_COUNT_EXCEED_LIMIT": //用户该类型权益超过10张,请至卡券包删除无用权益继续领取
case "APPLY_ONE_SELLER_COUNT_EXCEED_LIMIT": //用户在本店铺该类型权益超过10张,请至卡券包删除无用权益继续领取
msg = `请于${timeDate}前领取`;
drawStatus = DRAW_STATUS.RETRY;
break;
case "USER_PERMISSION_EXCEED_MAX_RIGHT_COUNT_IN_DAY"://用户超过单位时间[每天]内最大权益限
msg = `请于${timeDate}前领取`;
drawStatus = DRAW_STATUS.RETRY;
break;
}
await this.rankOpenDao.update({ activityId: activityId, openId: this.rankOpenDao.openId }, {
$set: {
drawStatus: drawStatus,
message: msg ? msg : result.result_msg
}
})
return this.resultsModel.error(CODE_TYPES.ERROR_SEND_PRIZE_FAIL, msg ? msg : result.result_msg);
}
} catch (e) {
return this.resultsModel.error(CODE_TYPES.ERROR_SERVER)
}
}
}
module.exports = PrizeService;
\ No newline at end of file
const ResultsModel = require('../utils/results.model');
const { CODE_TYPES } = require('../utils/constants');
const {formatName} =require('../utils/utils');
const BaseDao = require('../dao/base.dao');
const DB_NAME = "rank_score";
const DB_BASE_CONFIG = "activity_base_config";
class RankService {
constructor(context) {
this.rankDao = new BaseDao(context, DB_NAME);
this.resultsModel=new ResultsModel();
this.baseConfigDao = new BaseDao(context, DB_BASE_CONFIG);
}
async doScoreSubmit(activityId, score, nickName, avatar) {
try {
const activityErr = await this.checkActivityStatus(activityId);
if(activityErr)
{
return this.resultsModel.error(activityErr);
}
const result = await this.rankDao.find({ activityId: activityId, openId: this.rankDao.openId });
let maxScore=score;
if (result.length) { // 用户已经存在
if (result["0"].score < score) { //当前提交的分数大于库存分数
await this.rankDao.update({ activityId: activityId, openId: this.rankDao.openId }, {
$set: {
score: score,
updateTime: Date.now(),
}
})
}else{
maxScore=result["0"].score;
}
} else {
//新用户新增记录
await this.rankDao.insertOne({
activityId: activityId,
openId: this.rankDao.openId,
userNick: nickName,
avatar: avatar,
score: score,
createTime: Date.now(),
updateTime: Date.now()
});
}
//根据自己的最高分,拿到自己的排名
let myRank= await this.rankDao.count({score:{$gt:maxScore},activityId:activityId});
myRank=myRank+1;
//根据排名,检索自己前后的用户数据
const resultList = await this.rankDao.find({ activityId: activityId }, {
projection: { score: 1, userNick: 1, avatar: 1 },
sort: { score: -1 ,updateTime:-1},
limit: 3,
skip: myRank-2
})
return this.resultsModel.success({rankList:resultList,myRank:myRank,myMaxScore:maxScore});
} catch (e) {
return this.resultsModel.error(CODE_TYPES.ERROR_SERVER);
}
}
async getRankList(activityId, page, limit) {
try {
//获取自己在当前活动的分数
const myScoreInfo = await this.rankDao.find({activityId: activityId,openId:this.rankDao.openId},
{
projection:{score:1}
});
let myScore=0;
let myRank;
if(myScoreInfo && myScoreInfo["0"])
{
myScore=myScoreInfo["0"].score
}
//获取排行版
const result = await this.rankDao.find({ activityId: activityId }, {
projection: { score: 1, userNick: 1},
sort: { score: -1 ,updateTime:1},
limit: limit,
skip: (page - 1) * limit
})
//根据自己的分数,获取排名
if(myScore)
{
let sameScoreList = await this.rankDao.find({score:myScore,activityId:activityId},{
sort:{updateTime:1}
});
let gap =0;
//说明有多个跟自己同分数的人
for(let j=0;j<sameScoreList.length;j++)
{
if(sameScoreList[j].openId == this.rankDao.openId)
{
gap=j;
}
}
myRank= await this.rankDao.count({score:{$gt:myScore},activityId:activityId});
myRank=myRank+1+gap;
}else{
myRank="100+";
}
const totalCount = await this.rankDao.count({activityId:activityId});
if(totalCount>100 && page * limit >100)
{
return this.resultsModel.success({list:[],myRank:myRank,myMaxScore:myScore,totalCount:totalCount});
}
for(var i=0;i<result.length;i++)
{
let nick = result[i].userNick;
result[i].userNick = formatName(nick);
}
return this.resultsModel.success({list:result,myRank:myRank,myMaxScore:myScore,totalCount:totalCount});
} catch (e) {
return this.resultsModel.error(CODE_TYPES.ERROR_SERVER);
}
}
/**
* 检测活动状态 - 未开始,已结束和已删除
* @param {*} activityId
*/
async checkActivityStatus(activityId)
{
const activityStatus = await this.baseConfigDao.find({_id:activityId},{projection:{deleteStatus:1,startTime: 1, endTime: 1}});
if(activityStatus.length)
{
let nowDate = Date.now();
//检测活动是否未开始
if (nowDate < +activityStatus["0"].startTime) {
return CODE_TYPES.ERROR_NO_START
}
if (nowDate >= +activityStatus["0"].endTime) {
return CODE_TYPES.ERROR_ACTIVITY_OVER
}
//检测活动是否被删除
if(activityStatus["0"].deleteStatus != 1)
{
return CODE_TYPES.ERROR_DELETE_ACTIVITY;
}
return "";
} else {
return this.resultsModel.error(CODE_TYPES.ERROR_NO_ACTIVITY);
}
}
}
module.exports = RankService;
\ No newline at end of file
// 排行榜开奖记录
const BaseDao = require('../dao/base.dao');
const { DRAW_STATUS } = require('../utils/constants');
const DBName = 'rank_open_prize';
class RankopenprizeService {
constructor(context) {
this.rankopenprizeDao = new BaseDao(context, DBName);
}
// 添加排行榜待开奖列表
async addWaitAwardsList(list) {
if (!list.length) {
return true;
}
list.map(v => {
v.drawStatus = DRAW_STATUS.WAITAWARD,
v.message = '';
});
try {
await this.rankopenprizeDao.insetMany(list);
return true;
}catch(e) {
console.log(`添加排行榜待开奖列表出错:${e}`);
return false;
}
}
// 根据活动id查找中奖名单
async getAwardslistByActivityId(activityId) {
if (!activityId) {
return false;
}
return await this.rankopenprizeDao.find({
activityId: activityId
}, {
sort: {
score: -1,
rankTime: -1
}
});
}
}
module.exports = RankopenprizeService;
\ No newline at end of file
const BaseDao = require('../dao/base.dao');
const { OPEN_PRIZE_STATUS } = require('../utils/constants');
const DBName = 'rank_scan';
class RankscanService {
constructor(context) {
this.rankscanDao = new BaseDao(context, DBName);
}
// 查询待开奖列表
async getNodifyList(serverTime) {
let list = await this.rankscanDao.find({
openPrizeTime : { $lt: serverTime },
openPrizeStatus: OPEN_PRIZE_STATUS.WAIT_AWARD
});
console.log(list);
return list;
}
// 更新openPrizeStatus为处理中
async update2Process(_id) {
try {
await this.rankscanDao.update({ _id: _id}, {
$set: {
openPrizeStatus: OPEN_PRIZE_STATUS.PROCESSING,
updateTime: Date.now()
}
});
return true;
}catch(e) {
// 日志记录
console.log(e);
return false;
}
}
// 更新openPrizeStatus为成功
async update2Success(_id) {
try {
await this.rankscanDao.update({ _id: _id}, {
$set: {
openPrizeStatus: OPEN_PRIZE_STATUS.SUCCESS,
openPrizeMsg: '',
updateTime: Date.now()
}
});
return true;
}catch(e) {
// 日志记录
console.log(e);
return false;
}
}
// 更新openPrizeStatus为失败
async update2Fail(_id, message) {
try {
await this.rankscanDao.update({ _id: _id}, {
$set: {
openPrizeStatus: OPEN_PRIZE_STATUS.FAIL,
openPrizeMsg: message || '',
updateTime: Date.now()
}
});
return true;
}catch(e) {
// 日志记录
console.log(e);
return false;
}
}
}
module.exports = RankscanService;
\ No newline at end of file
const BaseDao = require('../dao/base.dao');
const DBName = 'rank_score';
class RankscoreService {
constructor(context) {
this.rankscoreDao = new BaseDao(context, DBName);
}
// 根据活动id查询分数排行榜
async getToplistByActivityId(activityId, totalAwards) {
let list = await this.rankscoreDao.find({
activityId: activityId
}, {
sort: {
score: -1,
updateTime: 1
},
limit: +totalAwards
});
console.log(`getToplistByActivityId: ${JSON.stringify(list)}`);
return list;
}
}
module.exports = RankscoreService;
\ No newline at end of file
const ResultsModel = require('../utils/results.model');
const { CODE_TYPES, SHARE_TOTAL_COUNT ,TASK_ADD_POWER} = require('../utils/constants');
const { getDayByTimeFix } = require('../utils/utils');
const BaseDao = require('../dao/base.dao');
const DB_SHAREID = "pl2c_shareidsave"; // 用户shareid保存表
const DB_SHARERECORD = "pl2c_sharerecord"; //用户分享受邀记录表
const DB_TASK = "pl2c_task";
const DB_USER = "pl2c_user";
class ShareService {
constructor(context) {
this.shareIdDao = new BaseDao(context, DB_SHAREID);
this.shareRecordDao = new BaseDao(context, DB_SHARERECORD);
this.taskDao = new BaseDao(context, DB_TASK);
this.resultsModel = new ResultsModel();
this.userDao = new BaseDao(context, DB_USER);
}
async getShareId(activityId) {
try {
let result = await this.shareIdDao.find({ activityId: activityId, openId: this.shareIdDao.openId }, {
projection: { shareId: 1 }
});
//已经存在有shareId,直接返回
if (result["0"]) {
let shareId = result["0"].shareId;
return this.resultsModel.success({ shareId: shareId });
}
const day = getDayByTimeFix(Date.now());
//新建shareId,并且插入数据
let shareId = this.shareIdDao.openId;
await this.shareIdDao.insertOne({
activityId: activityId,
openId: this.shareIdDao.openId,
shareId: shareId,
day: day,
createTime: Date.now(),
updateTime: Date.now()
});
return this.resultsModel.success({ shareId: shareId });
} catch (e) {
return this.resultsModel.error(CODE_TYPES.ERROR_SERVER);
}
}
async doShareComplete(activityId, shareId) {
try {
//根据shareId查询来源openId
let fromUser = await this.shareIdDao.find({ activityId: activityId, shareId: shareId });
if (!fromUser || !fromUser["0"]) {
console.log(`该分享id不存在${shareId}`);
//该分享ID不存在
return this.resultsModel.error(CODE_TYPES.ERROR_NO_SHAREID);
}
let fromOpenId = fromUser["0"].openId;
//自己打开自己分享的链接,直接提示无法邀请自己
if (fromOpenId === this.shareRecordDao.openId) {
console.log(`自己打开自己分享的链接${shareId}`);
// return this.resultsModel.success();
return this.resultsModel.error(CODE_TYPES.ERROR_SHARE_SELF);
}
// 受邀用户是否已经助力过
let shareedInfo = await this.shareRecordDao.find({ activityId: activityId, shareId: shareId, receiveOpenId: this.shareRecordDao.openId });
if (shareedInfo["0"]) {
console.log(`受邀用户已经助力过${shareId}`);
// return this.resultsModel.success();
return this.resultsModel.error(CODE_TYPES.ERROR_SHARE_DID);
}
const day = getDayByTimeFix(Date.now());
let count = await this.shareRecordDao.count({ activityId: activityId, shareId: shareId, day: day });
//记录受邀信息
await this.shareRecordDao.insertOne({
activityId: activityId,
openId: fromOpenId,
shareId: shareId,
day: day,
receiveOpenId: this.shareRecordDao.openId,
createTime: Date.now(),
updateTime: Date.now()
});
//邀请人数超过任务要求的话,插入数据,正常提示成功
if (count && count >= SHARE_TOTAL_COUNT) {
console.log(`邀请人数超过任务要求的话,插入数据${shareId}`);
// return this.resultsModel.success();
return this.resultsModel.error(CODE_TYPES.ERROR_SHARE_DONE);
}
let newCount = count + 1;
//更新任务完成状态
let friendInfo = await this.taskDao.find({ activityId: activityId, openId: fromOpenId, taskName: "inviteFriends", day: day });
//已有记录直接更新
if (friendInfo["0"]) {
await this.taskDao.update({ activityId: activityId, openId: fromOpenId, taskName: "inviteFriends", day: day }, {
$set: {
hasInviteCount: newCount,
isComplete: newCount >= SHARE_TOTAL_COUNT
}
})
} else {
await this.taskDao.insertOne({
activityId: activityId,
openId: fromOpenId,
taskName: "inviteFriends",
hasInviteCount: newCount,
day: day,
isComplete: newCount >= SHARE_TOTAL_COUNT,
createTime: Date.now(),
updateTime: Date.now()
})
}
const userInfo = await this.userDao.find({ openId: fromOpenId, activityId: activityId }, {
projection: { power: 1 }
})
//更新用户体力值
let newPower = userInfo["0"].power + TASK_ADD_POWER;
console.log(`用户最新体力值:${newPower}`);
await this.userDao.update({ openId: fromOpenId, activityId: activityId },
{
$set: {
power: newPower,
updateTime:Date.now()
}
})
return this.resultsModel.success();
} catch (e) {
return this.resultsModel.error(CODE_TYPES.ERROR_SERVER);
}
}
}
module.exports = ShareService;
\ No newline at end of file
const StatPvuvService = require('./statpvuv.service');
const StatItemService = require('./statitem.service');
const ResultsModel = require('../utils/results.model');
const BaseDao = require('../dao/base.dao');
const DB_TASK = "pl2c_task";
const DB_BASE_CFG = "activity_base_config";
const DB_SHAREID = "pl2c_shareidsave"; // 用户shareid保存表
const DB_SHARERECORD = "pl2c_sharerecord"; //用户分享受邀记录表
const DB_OPEN_PRIZE = "rank_open_prize"; //开奖表
const { getdaystarttime, getdayendtime, getDayByTimeFix } = require('../utils/utils');
const { CODE_TYPES,DRAW_STATUS} = require('../utils/constants');
class StatExportService {
constructor(context) {
this.pvService = new StatPvuvService(context);
this.itemService = new StatItemService(context);
this.taskDao = new BaseDao(context, DB_TASK);
this.baseCfgDao = new BaseDao(context, DB_BASE_CFG);
this.shareRecordDao = new BaseDao(context, DB_SHARERECORD);
this.shareIdDao = new BaseDao(context, DB_SHAREID);
this.openPrizeDao = new BaseDao(context,DB_OPEN_PRIZE);
this.resultsModel = new ResultsModel();
}
//数据总览
async statTotalData(activityId) {
try {
//打开活动次数
let totalOpenCount = 0;
//活动参与次数
let totalJoinCount = 0;
let joinData = await this.statJoinData(activityId);
console.log("joinData...",joinData);
joinData=joinData.data;
console.log("joinData1111...",joinData);
for (let date in joinData) {
totalOpenCount += joinData[date].openCount;
totalJoinCount += joinData[date].joinCount;
}
//新增会员数
let newMemberCount = 0;
//新增关注数
let newAttentionStoreCount = 0;
let taskData = await this.statTaskData(activityId);
taskData=taskData.data;
for (let date in taskData) {
newMemberCount += taskData[date].beMemeberCount;
newAttentionStoreCount += taskData[date].attentionStoreCount;
}
//商品曝光数量
let itemShowCount = 0;
let itemData = await this.statItemData(activityId);
itemData=itemData.data;
for (let date in itemData) {
itemShowCount += itemData[date].itemShowCount;
}
let obj = {
totalOpenCount,
totalJoinCount,
newMemberCount,
newAttentionStoreCount,
itemShowCount
}
return this.resultsModel.success(obj);
} catch (e) {
return this.resultsModel.error(CODE_TYPES.ERROR_SERVER);
}
}
//参与数据
async statJoinData(activityId) {
try {
const cfgInfo = await this.baseCfgDao.findOne({ _id: activityId }, {
projection: { startTime: 1, endTime: 1 }
});
const startTime = cfgInfo.startTime; // 活动开始时间戳
const endTime = cfgInfo.endTime; // 活动结束时间戳
const startDay = getDayByTimeFix(startTime); //活动开始日期
const endDay = getDayByTimeFix(endTime); //活动结束日期
let beginStamp = getdaystarttime(startDay); //活动开始日期的0点
let endStamp = getdayendtime(endDay); //活动结束日期的24点
let time = beginStamp;
let list = {};
do {
//日期
let date = getDayByTimeFix(time);
//打开人数
let openUV = await this.pvService.getOpenUV(activityId, date);
//打开次数
let openCount = await this.pvService.getOpenCount(activityId, date);
//参与人数
let joinUV = await this.pvService.getJoinUV(activityId, date);
//参与次数
let joinCount = await this.pvService.getJoinCount(activityId, date);
//人均参与数
let joinRate = joinUV == 0?"0":(joinCount / joinUV).toFixed(2);
list[date] = {
openUV,
openCount,
joinUV,
joinCount,
joinRate
}
time += 24 * 60 * 60 * 1000;
} while (time < endStamp);
return this.resultsModel.success(list);
} catch (e) {
return this.resultsModel.error(CODE_TYPES.ERROR_SERVER);
}
}
//任务数据
async statTaskData(activityId) {
try {
const cfgInfo = await this.baseCfgDao.findOne({ _id: activityId }, {
projection: { startTime: 1, endTime: 1 }
});
const startTime = cfgInfo.startTime; // 活动开始时间戳
const endTime = cfgInfo.endTime; // 活动结束时间戳
const startDay = getDayByTimeFix(startTime); //活动开始日期
const endDay = getDayByTimeFix(endTime); //活动结束日期
let beginStamp = getdaystarttime(startDay); //活动开始日期的0点
let endStamp = getdayendtime(endDay); //活动结束日期的24点
let time = beginStamp;
let list = {};
let taskInfo = this.taskDao.find({ activityId: activityId, isComplete: true }, {
projection: { _id: 0 }
});
const taskInfoLen = taskInfo.length;
do {
//日期
let date = getDayByTimeFix(time);
//发起邀请人数
let doShareCount = await this.shareIdDao.count({ activityId: activityId, day: date });
//成功邀请人数
let friendsCount = await this.shareRecordDao.count({ activityId: activityId, day: date });
//新增会员数
// let beMemeberCount = 0;
let beMemeberCount = await this.taskDao.count({ activityId: activityId, isComplete: true, statDay: date, taskName: "beMembership" });
//关注店铺数
// let attentionStoreCount = 0;
let attentionStoreCount = await this.taskDao.count({ activityId: activityId, isComplete: true, statDay: date, taskName: "attentionStore" });
//浏览指定页面人数
let pageUrlUVArr = await this.taskDao.find({ activityId: activityId, isComplete: true, taskName: "specifyPageUrl", day: date }, {
projection: { _id: 0, openId: 1 }
});
let lenArr=[];
for(let i=0;i<pageUrlUVArr.length;i++)
{
lenArr.push(pageUrlUVArr[i]["openId"]);
}
let pageUrlUV = [...new Set(lenArr)].length;
//浏览指定页面次数
let pageUrlCount = 0;
for (let i = 0; i < taskInfoLen; i++) {
let task = taskInfo[i];
switch (task.taskName) {
case "specifyPageUrl":
pageUrlCount += 1;
break;
}
}
list[date] = {
doShareCount,
friendsCount,
beMemeberCount,
attentionStoreCount,
pageUrlUV,
pageUrlCount
}
time += 24 * 60 * 60 * 1000;
} while (time < endStamp);
return this.resultsModel.success(list);
} catch (e) {
return this.resultsModel.error(CODE_TYPES.ERROR_SERVER);
}
}
//商品曝光数据
async statItemData(activityId) {
try {
const cfgInfo = await this.baseCfgDao.findOne({ _id: activityId }, {
projection: { startTime: 1, endTime: 1 }
});
const startTime = cfgInfo.startTime; // 活动开始时间戳
const endTime = cfgInfo.endTime; // 活动结束时间戳
const startDay = getDayByTimeFix(startTime); //活动开始日期
const endDay = getDayByTimeFix(endTime); //活动结束日期
let beginStamp = getdaystarttime(startDay); //活动开始日期的0点
let endStamp = getdayendtime(endDay); //活动结束日期的24点
let time = beginStamp;
let list = {};
do {
//日期
let date = getDayByTimeFix(time);
//商品曝光数量
let itemShowCount = await this.itemService.getItemShowCount(activityId, date);
//商品点击次数
let itemTapCount = await this.itemService.getItemTapCount(activityId, date);
//商品点击人数
let itemTapUV = await this.itemService.getItemTapUV(activityId, date);
list[date] = {
itemShowCount,
itemTapCount,
itemTapUV
}
time += 24 * 60 * 60 * 1000;
} while (time < endStamp);
return this.resultsModel.success(list);
} catch (e) {
return this.resultsModel.error(CODE_TYPES.ERROR_SERVER);
}
//奖品成功领取人数----暂无
}
//奖品数据
async statPrizeData(activityId)
{
let prizeInfo = await this.openPrizeDao.find({activityId:activityId});
console.log("prizeInfo..",prizeInfo);
//中奖人数
let winningUV = 0;
let winningArr=[];
//奖品成功领取人数
let collectionUV=0;
let collectionArr=[];
for(let i=0;i<prizeInfo.length;i++)
{
winningArr.push(prizeInfo[i].openId);
if(prizeInfo[i].drawStatus == DRAW_STATUS.SUCCESS)
{
collectionArr.push(prizeInfo[i].openId);
}
}
winningUV = [...new Set(winningArr)].length;
collectionUV=[...new Set(collectionArr)].length;
let obj = {
winningUV,
collectionUV
}
return this.resultsModel.success(obj);
}
//TODO 参与用户名单
async statJoinDetailData(activityId)
{
//参与usernick
//首次参与时间
//漂流最远距离
//排名
//奖品名称
//是否领取
//领取状态
}
}
module.exports = StatExportService;
\ No newline at end of file
const BaseDao = require('../dao/base.dao');
const DB_NAME = "stat_item"; //主要是奖品
const { getDayByTimeFix } = require('../utils/utils');
class StatItemService {
constructor(context) {
this.itemDao = new BaseDao(context, DB_NAME);
}
async itemShow(activityId) {
const date = getDayByTimeFix(Date.now());
await this.itemDao.insertOne({
activityId: activityId,
openId: this.itemDao.openId,
day: date,
type:"show",
createTime: Date.now()
});
}
async itemTap(activityId) {
const date = getDayByTimeFix(Date.now());
await this.itemDao.insertOne({
activityId: activityId,
openId: this.itemDao.openId,
day: date,
type:"tap",
createTime: Date.now()
});
}
async getItemShowCount(activityId,day)
{
return await this.itemDao.count({activityId:activityId,day:day,type:"show"});
}
async getItemTapCount(activityId,day)
{
return await this.itemDao.count({activityId:activityId,day:day,type:"tap"});
}
async getItemTapUV(activityId,day)
{
let list = await this.itemDao.find({activityId:activityId,day:day,type:"tap"},{
projection:{_id:0,openId:1}
});
let arr=[];
for(let i=0;i<list.length;i++)
{
arr.push(list[i]["openId"]);
}
return [...new Set(arr)].length;
}
}
module.exports = StatItemService;
\ No newline at end of file
/**
* 主要记录打开和参与的用户数据
*
*/
const BaseDao = require('../dao/base.dao');
const {getDayByTimeFix} =require('../utils/utils');
const DB_NAME="stat_pvuv";
class StatPvuvService
{
constructor(context)
{
this.statDao=new BaseDao(context,DB_NAME);
}
/**
* 记录用户打开数据
* @param {*} activityId
* @param {*} nickName
*/
async insertUserOpenData(activityId,nickName)
{
let day=getDayByTimeFix(Date.now());
this.statDao.insertOne({
activityId:activityId,
nickName:nickName,
openId:this.statDao.openId,
createTime:Date.now(),
updateTime:Date.now(),
day:day,
type:"open"
})
}
async insertUserJoinData(activityId,nickName)
{
let day=getDayByTimeFix(Date.now());
this.statDao.insertOne({
activityId:activityId,
nickName:nickName,
openId:this.statDao.openId,
createTime:Date.now(),
updateTime:Date.now(),
day:day,
type:"join"
})
}
/**
* 打开人数
* @param {*} activityId
* @param {*} day
*/
async getOpenUV(activityId,day)
{
let infoList = await this.statDao.find({activityId:activityId,day:day,type:"open"},{
projection:{_id:0,openId:1}
})
let openIdArr=[];
for(let i=0;i<infoList.length;i++)
{
openIdArr.push(infoList[i]["openId"]);
}
return [...new Set(openIdArr)].length;
}
async getOpenCount(activityId,day)
{
if(day)
{
return await this.statDao.count({activityId:activityId,day:day,type:"open"});
}
return await this.statDao.count({activityId:activityId,type:"open"});
}
/**
* 参与人数UV
* @param {*} activityId
* @param {*} startTime
* @param {*} endTime
*/
async getJoinUV(activityId,day)
{
let infoList = await this.statDao.find({activityId:activityId,day:day,type:"join"},{
projection:{_id:0,openId:1}
})
let openIdArr=[];
for(let i=0;i<infoList.length;i++)
{
openIdArr.push(infoList[i]["openId"]);
}
return [...new Set(openIdArr)].length;
}
/**
*
* 参与次数
* @param {*} activityId
* @param {*} startTime
* @param {*} endTime
*/
async getJoinCount(activityId,day)
{
if(day)
{
return await this.statDao.count({activityId:activityId,day:day,type:"join"});
}
return await this.statDao.count({activityId:activityId,type:"join"});
}
}
module.exports=StatPvuvService;
\ No newline at end of file
const ResultsModel = require('../utils/results.model');
const { CODE_TYPES, SHARE_TOTAL_COUNT, TASK_ADD_POWER } = require('../utils/constants');
const { getDayByTimeFix } = require("../utils/utils");
const ActivityTopService = require("./activitytop.service");
const BaseDao = require('../dao/base.dao');
const DB_TASK = "pl2c_task";
const DB_USER = "pl2c_user";
const DB_activity_base_config = "activity_base_config";
class TaskService {
constructor(context) {
this.taskDao = new BaseDao(context, DB_TASK);
this.userDao = new BaseDao(context, DB_USER);
this.taskConfigDao = new BaseDao(context, DB_activity_base_config);
this.resultsModel = new ResultsModel();
this.activityTopService = new ActivityTopService(context);
}
/**
* 每日重置任务,目前只有浏览指定页面任务
* @param {*} context
*/
async resetTaskStatus(activityId) {
return await this.resetTaskStatus(activityId, "specifyPageUrl");
}
async getTaskList(activityId) {
try {
const taskConfig = await this.taskConfigDao.find({ _id: activityId }, {
projection: { taskInfo: 1 }
})
const day = getDayByTimeFix(Date.now());
let taskList1 = await this.taskDao.find({ activityId: activityId, openId: this.taskDao.openId, day: { $exists: false } }) || [];
let taskList2 = await this.taskDao.find({ activityId: activityId, openId: this.taskDao.openId, day: day }) || [];
const taskList = taskList1.concat(taskList2);
console.log("day..",day);
console.log("taskList1.================.",taskList1);
console.log("taskList2..-------------",taskList2);
if (!taskConfig.length) {
return this.resultsModel.error(CODE_TYPES.ERROR_NO_TASK);
} else {
var result = {};
const taskConfigInfo = taskConfig["0"].taskInfo;
for (const taskname in taskConfigInfo) {
if (taskConfigInfo.hasOwnProperty(taskname)) {
const task = taskConfigInfo[taskname];
if (task) {
if (!result[`${taskname}`]) result[`${taskname}`] = {};
result[`${taskname}`].content = task;
result[`${taskname}`].isComplete = false;
switch (taskname) {
case "inviteFriends":
result[`${taskname}`].hasInviteCount = 0;
result[`${taskname}`].totalInviteCount = SHARE_TOTAL_COUNT;
break;
case "browseItemIds":
result["browseItemIds"].content = [];
const itemsInfoList = await this.activityTopService.getItemListByIds(activityId, task);
if (itemsInfoList.code == 10000) {
result["browseItemIds"].content = itemsInfoList.data.list || [];
} else {
console.log(`获取browseItemIds奖品信息失败`);
}
break;
case "beMembership":
result["beMembership"].content = "";
try {
const vipUrlData = await this.activityTopService.getVipUrlByActivity(activityId);
console.log("vipUrlData.......", vipUrlData);
result["beMembership"].content = vipUrlData.data.result.result;
} catch (e) {
console.log(`获取VIP链接失败`)
}
break;
}
for (var i = 0; i < taskList.length; i++) {
if (taskList[i].taskName == taskname) {
result[`${taskname}`].isComplete = taskList[i][`isComplete`];
if (taskname == "inviteFriends") {
result[`${taskname}`].hasInviteCount = taskList[i]["hasInviteCount"];
}
}
}
}
}
}
}
return this.resultsModel.success(result);
} catch (e) {
return this.resultsModel.error(CODE_TYPES.ERROR_SERVER);
}
}
/**
* 获取用户当前是否已经是会员
* @param {} activityId
*/
async checkMember(activityId) {
try {
let { mixNick } = this.taskDao.context;
const session = await this.activityTopService.getAccessToken(activityId);
const result = await this.taskDao.context.cloud.topApi.invoke({
api: 'taobao.crm.member.identity.get',
data: {
'mix_nick':mixNick,
'session':session.accessToken
}
});
console.log("result--------",result)
if(result.result.member_info)
{
return this.resultsModel.success(result.member_info)
}
return this.resultsModel.error(CODE_TYPES.ERROR_NO_VIP);
} catch (e) {
console.log("isInitVip error.......");
return this.resultsModel.error(CODE_TYPES.ERROR_NO_VIP);
}
}
async doTaskComplete(activityId, taskName) {
try {
const activityErr = await this.checkActivityStatus(activityId);
if(activityErr)
{
return this.resultsModel.error(activityErr);
}
const dayStr = getDayByTimeFix(Date.now());
let taskList = [];
let insertObj = {};
if (taskName == "specifyPageUrl") //浏览指定页面,每日任务,需要保存day值
{
taskList = await this.taskDao.find({ activityId: activityId, openId: this.taskDao.openId, isComplete: true, taskName: taskName, day: dayStr });
insertObj = {
activityId: activityId,
openId: this.taskDao.openId,
taskName: taskName,
isComplete: true,
day: dayStr,
createTime: Date.now(),
updateTime: Date.now()
}
} else { //永久一次性任务,不需要保存day值
taskList = await this.taskDao.find({ activityId: activityId, openId: this.taskDao.openId, isComplete: true, taskName: taskName });
insertObj = {
activityId: activityId,
openId: this.taskDao.openId,
taskName: taskName,
isComplete: true,
statDay:dayStr, //用于数据统计
createTime: Date.now(),
updateTime: Date.now()
}
}
if (taskList.length) {
//该任务已经完成
return this.resultsModel.error(CODE_TYPES.ERROR_TASK_DONE);
}
//检查当前是否已经成为了会员
if(taskName == "beMembership")
{
const {code} = await this.checkMember(activityId);
if(+code != 10000) return this.resultsModel.error(CODE_TYPES.ERROR_NO_VIP);
}
//
const taskConfig = await this.taskConfigDao.find({ _id: activityId }, {
projection: { taskInfo: 1 }
})
//校验该任务是否存在
if (taskConfig[0] && taskConfig[0].taskInfo && taskConfig[0].taskInfo[`${taskName}`]) {
const taskConfigInfo = taskConfig["0"].taskInfo;
if (taskConfigInfo.hasOwnProperty(taskName)) {
await this.taskDao.insertOne(insertObj)
//更新体力值
try {
const userInfo = await this.userDao.find({ openId: this.userDao.openId, activityId: activityId }, {
projection: { power: 1 }
})
if (userInfo["0"]) {
let newPower = userInfo["0"].power + TASK_ADD_POWER;
await this.userDao.update({ openId: this.userDao.openId, activityId: activityId },
{
$set: {
power: newPower
}
})
return this.resultsModel.success({ power: newPower });
} else {
//该用户不存在
return this.resultsModel.error(CODE_TYPES.ERROR_NO_USER);
}
} catch (e) {
return this.resultsModel.error(CODE_TYPES.ERROR_SERVER);
}
} else {
//该任务不存在
return this.resultsModel.error(CODE_TYPES.ERROR_NO_TASK);
}
}
//该任务不存在
return this.resultsModel.error(CODE_TYPES.ERROR_NO_TASK);
} catch (e) {
this.resultsModel.error(CODE_TYPES.ERROR_SERVER);
}
}
/**
* 如果用户在活动之前就已经是会员或者已经关注过店铺了,任务直接完成,但是不加体力值
* @param {*} activityId
* @param {*} taskName
*/
async updateTaskStatus(activityId, taskName) {
try {
const taskList = await this.taskDao.find({ activityId: activityId, openId: this.taskDao.openId, taskName: taskName });
if (taskList["0"]) {
//该任务已经完成
return this.resultsModel.error(CODE_TYPES.ERROR_TASK_DONE);
}
//
const taskConfig = await this.taskConfigDao.find({ _id: activityId }, {
projection: { taskInfo: 1 }
})
// const dayStr = getDayByTimeFix(Date.now());
//校验该任务是否存在
if (taskConfig[0] && taskConfig[0].taskInfo && taskConfig[0].taskInfo[`${taskName}`]) {
const taskConfigInfo = taskConfig["0"].taskInfo;
if (taskConfigInfo.hasOwnProperty(taskName)) {
await this.taskDao.insertOne({
activityId: activityId,
openId: this.taskDao.openId,
taskName: taskName,
isComplete: true,
createTime: Date.now(),
updateTime: Date.now()
})
return this.resultsModel.success();
} else {
//该任务不存在
return this.resultsModel.error(CODE_TYPES.ERROR_NO_TASK);
}
}
//该任务不存在
return this.resultsModel.error(CODE_TYPES.ERROR_NO_TASK);
} catch (e) {
this.resultsModel.error(CODE_TYPES.ERROR_SERVER);
}
}
async resetTaskStatus(activityId, taskName) {
try {
const taskList = await this.taskDao.find({ activityId: activityId, openId: this.taskDao.openId, taskName: taskName });
if (taskList["0"]) {
return await this.taskDao.deleteMany({ activityId: activityId, openId: this.taskDao.openId, taskName: taskName });
}
} catch (e) {
console.log(`重置${activityId}每日${taskName}任务失败`);
}
}
/**
* 检测活动状态 - 未开始,已结束和已删除
* @param {*} activityId
*/
async checkActivityStatus(activityId)
{
const activityStatus = await this.taskConfigDao.find({_id:activityId},{projection:{deleteStatus:1,startTime: 1, endTime: 1}});
if(activityStatus.length)
{
let nowDate = Date.now();
//检测活动是否未开始
if (nowDate < +activityStatus["0"].startTime) {
return CODE_TYPES.ERROR_NO_START
}
if (nowDate >= +activityStatus["0"].endTime) {
return CODE_TYPES.ERROR_ACTIVITY_OVER
}
//检测活动是否被删除
if(activityStatus["0"].deleteStatus != 1)
{
return CODE_TYPES.ERROR_DELETE_ACTIVITY;
}
return "";
} else {
return this.resultsModel.error(CODE_TYPES.ERROR_NO_ACTIVITY);
}
}
}
module.exports = TaskService;
const { isNewDay } = require("../utils/utils");
const ResultsModel = require('../utils/results.model');
const { CODE_TYPES } = require('../utils/constants');
const BaseDao = require('../dao/base.dao');
const { OPEN_PRIZE_STATUS } = require("../utils/constants");
const TaskService = require("./task.service");
const ActivityTopService = require("./activitytop.service");
const StatPvuvService = require("./statpvuv.service");
const DB_NAME = "pl2c_user";
const DB_RANK_SCAN = "rank_scan";
const DB_BASE_CONFIG = "activity_base_config";
const INIT_POWER = 30; //初始体力值
class UserService {
constructor(context) {
this.userDao = new BaseDao(context, DB_NAME);
this.rankScanDao = new BaseDao(context, DB_RANK_SCAN);
this.baseConfigDao = new BaseDao(context, DB_BASE_CONFIG);
this.resultsModel = new ResultsModel();
this.taskService = new TaskService(context);
this.activityTopService = new ActivityTopService(context);
this.statPvuvService=new StatPvuvService(context);
}
/**
* 获取用户当前是否已经是会员
* @param {} activityId
*/
async isInitVip(activityId) {
try {
const session = await this.activityTopService.getAccessToken(activityId);
let { mixNick } = this.userDao.context;
const result = await this.userDao.context.cloud.topApi.invoke({
api: 'taobao.crm.member.identity.get',
data: {
'mix_nick':mixNick,
'session':session.accessToken
}
});
console.log("result--------",result)
if(result.result.member_info)
{
await this.taskService.updateTaskStatus(activityId, "beMembership");
}
} catch (e) {
console.log("isInitVip error.......");
}
}
/**
* 登录
* @param {*} activityId
* @param {*} nickName
* @param {*} avatar
*/
async login(activityId, nickName, avatar, isFavor) {
try {
// const activityErr = await this.checkActivityStatus(activityId);
// if(activityErr)
// {
// return this.resultsModel.error(activityErr);
// }
let isNewGuy = true;
const newUserInfo = await this.userDao.find({
openId: this.userDao.openId
});
if (newUserInfo["0"] && newUserInfo["0"].openId) //所有活动下的老用户
{
isNewGuy = false;
}
console.log("isNewGuy...",isNewGuy);
const userInfo = await this.userDao.find({ openId: this.userDao.openId, activityId: activityId });
console.log("userinfo....",userInfo);
if (userInfo.length)//当前活动的老用户
{
let isResetPower = isNewDay(userInfo["0"].updateTime);//是否当天首次登录,
console.log("isResetPower...",isResetPower)
let power = userInfo["0"].power;
if (isResetPower) {
//是--重置每日任务 不需要重置了,这里任务根据日期判定
// await this.taskService.resetTaskStatus(this.userDao.context);
//是--重置体力值
const result = await this.userDao.update({ openId: this.userDao.openId, activityId: activityId }, {
$set: {
updateTime: Date.now(),
power: INIT_POWER
}
})
power = INIT_POWER;
}
return this.resultsModel.success({ isNewGuy: isNewGuy, power: power, openId: this.userDao.openId });
} else { //新用户
//活动新开始,第一个用户进入
const rankscanCount = await this.rankScanDao.count({ activityId: activityId });
if (!rankscanCount) {
const baseConfig = await this.baseConfigDao.find({ _id: activityId }, {
projection: { startTime: 1, endTime: 1, shopId: 1 }
})
if (baseConfig["0"]) {
let nowDate = Date.now();
if (nowDate >= +baseConfig["0"].startTime) {
await this.rankScanDao.insertOne({
activityId: activityId,
openPrizeTime: baseConfig["0"].endTime,
openPrizeStatus: OPEN_PRIZE_STATUS.WAIT_AWARD,
openPrizeMsg: "",
shopId: baseConfig["0"].shopId,
updateTime: Date.now()
})
} else {
//活动暂未开始
return this.resultsModel.error(CODE_TYPES.ERROR_NO_START);
}
} else {
return this.resultsModel.error(CODE_TYPES.ERROR_NO_ACTIVITY);
}
}
//活动开始之前就已经关注过店铺了,任务直接完成,但是不加体力值
if (isFavor) {
try {
await this.taskService.updateTaskStatus(activityId, "attentionStore");
} catch (e) {
}
}
//活动开始已经是会员了,任务直接完成,但是不加体力值
try {
await this.isInitVip(activityId);
} catch (e) {
}
//插入用户数据
try {
await this.userDao.insertOne({
activityId: activityId,
openId: this.userDao.openId,
nickName: nickName,
avatar: avatar,
power: INIT_POWER,
createTime: Date.now(),
updateTime: Date.now()
});
return this.resultsModel.success({ isNewGuy: isNewGuy, power: INIT_POWER, openId: this.userDao.openId });
} catch (e) {
return this.resultsModel.error(CODE_TYPES.ERROR_SERVER);
}
}
} catch (e) {
return this.resultsModel.error(CODE_TYPES.ERROR_SERVER);
}
}
async getPower(activityId) {
try {
const userInfo = await this.userDao.find({ openId: this.userDao.openId, activityId: activityId }, {
projection: { power: 1 }
})
if (userInfo["0"]) {
return this.resultsModel.success({ power: userInfo["0"].power })
} else {
return this.resultsModel.error(CODE_TYPES.ERROR_NO_USER);
}
} catch (e) {
return this.resultsModel.error(CODE_TYPES.ERROR_SERVER);
}
}
async startGame(activityId,nickName) {
try {
await this.statPvuvService.insertUserJoinData(activityId,nickName);
const activityErr = await this.checkActivityStatus(activityId);
if(activityErr)
{
return this.resultsModel.error(activityErr);
}
const userInfo = await this.userDao.find({ openId: this.userDao.openId, activityId: activityId }, {
projection: { power: 1 }
})
let curPower = userInfo["0"].power;
if (userInfo["0"] && curPower >= 10) {
await this.userDao.update({ openId: this.userDao.openId, activityId: activityId },
{
$set: {
power: curPower - 10
}
})
return this.resultsModel.success({ power: curPower - 10 });
} else {
//体力值不足
return this.resultsModel.error(CODE_TYPES.ERROR_NO_POWER);
}
} catch (e) {
return this.resultsModel.error(CODE_TYPES.ERROR_SERVER);
}
}
/**
*
* @param {活动id} activityId
* @param {新增或者减少的体力值} powerGap
*/
async updatePower(activityId, powerGap) {
try {
const userInfo = await this.userDao.find({ openId: this.userDao.openId, activityId: activityId }, {
projection: { power: 1 }
})
if (userInfo["0"]) {
let newPower = userInfo["0"].power + powerGap;
await this.userDao.update({ openId: this.userDao.openId, activityId: activityId },
{
$set: {
power: newPower
}
})
return newPower;
} else {
//该用户不存在
return this.resultsModel.error(CODE_TYPES.ERROR_NO_USER);
}
} catch (e) {
return this.resultsModel.error(CODE_TYPES.ERROR_SERVER);
}
}
/**
* 检测活动状态 - 未开始,已结束和已删除
* @param {*} activityId
*/
async checkActivityStatus(activityId)
{
const activityStatus = await this.baseConfigDao.find({_id:activityId},{projection:{deleteStatus:1,startTime: 1, endTime: 1}});
if(activityStatus.length)
{
let nowDate = Date.now();
//检测活动是否未开始
if (nowDate < +activityStatus["0"].startTime) {
return CODE_TYPES.ERROR_NO_START
}
if (nowDate >= +activityStatus["0"].endTime) {
return CODE_TYPES.ERROR_ACTIVITY_OVER
}
//检测活动是否被删除
if(activityStatus["0"].deleteStatus != 1)
{
return CODE_TYPES.ERROR_DELETE_ACTIVITY;
}
return "";
} else {
return this.resultsModel.error(CODE_TYPES.ERROR_NO_ACTIVITY);
}
}
/**
* 获取用户的shopId
* @param {*} activityId
*/
async getShopIdByActivity(activityId)
{
try{
const userInfo= await this.baseConfigDao.find({_id:activityId},{
projection:{shopId:1}
})
console.log("userInfo...",userInfo);
if(userInfo.length)
{
return userInfo["0"].shopId;
}else{
return "";
}
}catch(e)
{
return "";
}
}
}
module.exports = UserService;
\ No newline at end of file
// 活动开奖状态码
const OPEN_PRIZE_STATUS = {
// 待开奖
WAIT_AWARD: 1,
// 开奖中
PROCESSING: 2,
// 开奖成功
SUCCESS: 3,
// 开奖失败
FAIL: 4
};
// 领取奖品状态
const DRAW_STATUS = {
// 待领取
WAITAWARD: 1,
// 处理中
PROCESSING: 2,
// 领取成功
SUCCESS: 3,
// 领取失败
FAIL: 4,
// 已过期
EXPIRED: 5,
// 重新领取
RETRY: 6
}
// 日志类型: error,info
const LOGGER_TYPE = {
ERROR: 1,
INFO: 2
}
// code类型
const CODE_TYPES = {
PARAMS_ERROR: {
code: '100000',
defaultMsg: `参数错误`
},
SYSTEM_ERROR: {
code: '500000',
defaultMsg: `系统错误`
},
SUCCESS: {
code: '000000',
defaultMsg: `成功`
},
// TODO 补充业务类型错误, 固定以2开头,如B端业务错误:200001,200002,C端业务错误:210001,210002
ERROR_SERVER: {
code: '210001',
defaultMsg: `服务器异常`
},
ERROR_NO_USER: {
code: '210002',
defaultMsg: `用户不存在`
},
ERROR_INVITE_LIMIT: {
code: '210003',
defaultMsg: `邀请已达上限`
},
ERROR_INVITE_SELF: {
code: '210004',
defaultMsg: `无法邀请自己`
},
ERROR_TASK_DONE: {
code: '210005',
defaultMsg: `该任务已经完成`
},
ERROR_NO_ACTIVITY: {
code: '210006',
defaultMsg: `该活动ID无效`
},
ERROR_NO_TASK: {
code: '210007',
defaultMsg: `该任务不存在`
},
ERROR_NO_POWER: {
code: '210008',
defaultMsg: `体力值不足`
},
ERROR_NO_START: {
code: '210009',
defaultMsg: `该活动暂未开始`
},
ERROR_NO_SHAREID: {
code: '210010',
defaultMsg: `该分享ID不存在`
},
ERROR_NO_TASK: {
code: '210011',
defaultMsg: `该活动无任务`
},
ERROR_NO_PRIZE: {
code: '210012',
defaultMsg: `该用户暂未获奖`
},
ERROR_SEND_PRIZE_FAIL: {
code: '210013',
defaultMsg: `领取失败`
},
ERROR_NO_SHOP: {
code: '210014',
defaultMsg: `该店铺ID不存在`
},
ERROR_DELETE_ACTIVITY: {
code: '210015',
defaultMsg: `该活动已删除`
},
ERROR_ACTIVITY_OVER: {
code: '210016',
defaultMsg: `该活动已经结束`
},
ERROR_NO_VIP: {
code: '210017',
defaultMsg: `当前不是会员`
},
ERROR_SHARE_SELF: {
code: '210018',
defaultMsg: `无法给自己助力哦`
},
ERROR_SHARE_DID: {
code: '210019',
defaultMsg: `您已参与本店铺漂流游戏,助力失败`
},
ERROR_SHARE_DONE: {
code: '210020',
defaultMsg: `对方助力已达今日上限,助力失败`
},
ERROR_PRIZE_EXPIRED: {
code: '210021',
defaultMsg: `奖品已过期`
}
}
const SHARE_TOTAL_COUNT = 3; //验证要求3人后,完成分享任务
const TASK_ADD_POWER = 10; // 完成任务增加的体力值
const APP_NAME = 'promotioncenter-3000000002590532'; // C端的APP NAME
const B_APP_NAME = 'promotioncenter-3000000002693435' // B端的APP NAME
const GOODSINFO = 'detail_url,approve_status,num_iid,title,nick,type,cid,pic_url,num,props,valid_thru,list_time,price,has_discount,has_invoice,has_warranty,has_showcase,modified,delist_time,postage_id,seller_cids,outer_id,sold_quantity';
const ACTIVITY_STATUS={
ING:1, //活动进行中
NOSTART:2, //活动未开始
OVER:3, // 活动已经结束
DELETE:4, //活动已删除
ERRORID:5 //活动id无效
}
const DELETE_STATUS = {
DELETE: 0,
EXIST: 1
}
const PRIZE_GET_DELAY_TIME = 1000*60*60*48; //48小时
module.exports = {
OPEN_PRIZE_STATUS,
DRAW_STATUS,
LOGGER_TYPE,
CODE_TYPES,
SHARE_TOTAL_COUNT,
APP_NAME,
DELETE_STATUS,
GOODSINFO,
B_APP_NAME,
ACTIVITY_STATUS,
TASK_ADD_POWER,
PRIZE_GET_DELAY_TIME
};
\ No newline at end of file
const errorCode = {
"000000": 'ok',
"000001": '请填写活动名称',
"000002": '请填写活动副标题',
"000003": '活动名称不得大于12个字符',
"000004": '活动副标题不得大于16个字符',
"000005": '任务配置至少选择一项',
"000006": '浏览链接配置错误',
"000007": '奖品至少配置三名',
"000008": "浏览宝贝最多选择20个商品",
"000010": '请先生成规则',
"000011": '请配置活动时间',
"000012": '新建活动开始时间需小于当前时间',
"000013": '开始时间不得大于结束时间',
"000014": '奖品最多配置8个档位',
"000015": '查询列表错误',
"000016": '查询列表总数错误',
"000017": '更新奖品列表出错',
"000020": '删除活动失败',
"000021": '保存活动失败',
"000022": "删除活动id不存在",
"000023": '当前活动不存在',
"000024": '获取授权信息失败',
"000025": '保存授权信息失败',
"000026": '获取商家在售列表失败',
"000027": '获取商家ids列表失败',
"000030": '获取ename商品信息失败',
"000031": '奖品配置不正确'
}
module.exports = {
errorCode
}
\ No newline at end of file
class ResultsModel {
constructor() {
}
// 不填errorType,默认为系统错误
error(errorType, message) {
if (!errorType) {
errorType = { code: '500000', defaultMsg: '系统错误' };
}
return {
success: false,
code: errorType.code,
message: message || errorType.defaultMsg
}
}
success(data) {
return {
success: true,
code: 10000,
data: data,
message: `成功`
}
}
}
module.exports = ResultsModel
\ No newline at end of file
function loginfo(context, handler) {
const {
fcName,
data,
env
} = context;
console.log(`函数名:${fcName}---函数handler:${handler}---当前环境:${env}---请求参数:${JSON.stringify(data)}`)
}
const passUrlList = [
'1688.cn',
'taobao.cn',
'taobao.com',
'taobao.net',
'tb.cn',
'tmall.com',
'zhifu.com',
'zhifubao.com',
'juhuasuan.com',
'tmall.hk',
'tmall.com.hk',
'dingtalk.com',
'yushanfang.com',
'guoguo-app.com',
'umeng.com'
]
/**
* 传入时间戳(毫秒)
* 根据时间戳转换成number型日期进行差值结算,比如:2020318-2020317
*/
function isNewDay(time) {
let date = new Date(time + 60 * 60 * 8 * 1000);
let dateNum = +(date.getFullYear() + "" + (date.getMonth() + 1) + "" + date.getDate());
let nowdate = new Date(Date.now()+ 60 * 60 * 8 * 1000);
let nowdateNum = +(nowdate.getFullYear() + "" + (nowdate.getMonth() + 1) + "" + nowdate.getDate());
return nowdateNum - dateNum > 0
}
function getDayByTime(time) {
let date = new Date(time + 60 * 60 * 8 * 1000);
let dateNum = (date.getFullYear() + "" + (date.getMonth() + 1) + "" + date.getDate());
return dateNum;
}
//使用new Date()一定要在时间戳上加8小时
function getDayByTimeFix(time)
{
let date = new Date(time + 60 * 60 * 8 * 1000);
console.log("time:...",time,"...date:",date);
let yy = date.getFullYear();
let MM = date.getMonth() + 1<10?("0"+(date.getMonth() + 1)):date.getMonth() + 1;
let dd = date.getDate()<10?("0"+date.getDate()):date.getDate();
let dateNum = `${yy}/${MM}/${dd}`;
console.log(dateNum)
return dateNum;
}
const formatName = function formatName (name) {
let newStr;
if (name.length === 2) {
newStr = name.substr(0, 1) + '***'+name.substr(-1, 1);
} else if (name.length > 2) {
let char = '***';
newStr = name.substr(0, 1) + char + name.substr(-1, 1);
} else {
newStr = name+"****";
}
return newStr;
}
const formatTime = function dateFormat(thisDate, fmt = "yyyy-MM-dd hh:mm:ss") {
var 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 (var 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;
}
// 淘宝-时区,加8小时
const getdate = (day) => {
let time = new Date().getTime();
if (day) {
time = new Date(day).getTime();
}
return new Date(time + 8 * 60 * 60 * 1000);
}
const getdaystarttime = (day) => {
let date = new Date(`${day} 00:00:00`).getTime();
let start = date - 8 * 60 * 60 * 1000;
console.log(`getdaystarttime: `, start);
return start;
}
const getdayendtime = (day) => {
let date = new Date(`${day} 23:59:59`).getTime();
let end = date - 8 * 60 * 60 * 1000;
console.log(`getdayendtime: `, end);
return end;
}
module.exports = {
loginfo,
passUrlList,
isNewDay,
formatTime,
getDayByTime,
formatName,
getdate,
getdaystarttime,
getdayendtime,
getDayByTimeFix
}
\ 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