Commit 7dcc9b08 authored by 刘松's avatar 刘松

fix pickdate

parent 070c44af
......@@ -13,9 +13,9 @@ RUN \
ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN npm i --only=production --registry https://registry.npm.taobao.org
ENV SERVICE_PORT=8080
ENV SERVICE_PORT=6662
ENV PROJECT_LEVEL=production
ENV MONGO='mongodb://mongo-adpro-ssp-v2-rs-1.localhost:1301/remarketing'
ENV MONGO='mongodb://mongo-adpro-ssp-v2-rs-1.localhost:1301/remarketing?replicaSet=adpro_ssp_v2_rs'
ENV NODE_ENV='production'
EXPOSE 8080
EXPOSE 6662
CMD node server.js
......@@ -10,7 +10,7 @@ const adminID = process.env.NODE_ENV === 'production' ? '5a9f9e6b46da1176a40e108
let db = {};
const dbpath = process.env.MONGO || "mongodb://localhost:27017/remarketing";
const salt = ",tom";
const EXPIRATION = 60 * 30;
// TODO ! put into init
MongoClient.connect(dbpath, (err, conn) => {
if (err) return console.log(err);
......@@ -21,29 +21,48 @@ MongoClient.connect(dbpath, (err, conn) => {
.createIndex({ createdAt: 1 }, { expireAfterSeconds: 3600 * 24 });
});
router.post('/job/call',function (req,res) {
const { pubID, slotID, phone, expiration = 60 * 30, unikey } = req.body;
if(!notEmpty(req.body)) res.sendStatus(500);
else {
const host = "http://remarketing-job-yh.yoo.yunpro.cn/bind/" + pubID + '/' + slotID + '?caller=' + phone + '&unikey=' + unikey + '&expiration=' + expiration+ '&test=true';
axios(host, {
method: "GET",
headers: { "Content-Type": "application/json" },
timeout: 30000
})
.then(rep => {
if(rep.data && rep.data.called){
res.send({ status: "ok", called: rep.data.called });
}
else{
res.sendStatus(500);
const { pubID, slotID, phone, expiration = EXPIRATION, unikey } = req.body;
getCallConsumeByUnikey(unikey, (err, data) => {
if(err) res.status(500).json({ error: err});
if(data && data.number) {
console.dir(data)
checkBill({ accountID: pubID, number: data.number },(_err) => {
if(_err) {
return res.status(500).json({ error: _err});
} else {
if(!notEmpty(req.body)) res.sendStatus(500);
else {
const host = "http://remarketing-job-yh.yoo.yunpro.cn/bind/" + pubID + '/' + slotID + '?caller=' + phone + '&unikey=' + unikey + '&expiration=' + expiration;
axios(host, {
method: "GET",
headers: { "Content-Type": "application/json" },
timeout: 300000
})
.then(rep => {
console.dir(rep.data);
if(rep.data && rep.data.called && rep.data._id){
updateBill({pre: true, number: data.number, accountID: pubID, type: 'call', taskID: rep.data._id}, (err,_rep) => {
if(err) {
// log 代码;
}
res.send({ status: 'ok', called: rep.data.called })
});
}
else{
res.status(500).json({ error: '取号失败'});
}
})
.catch(err => {
if (err) return res.sendStatus(500);
});
}
}
})
.catch(err => {
if (err) return res.sendStatus(500);
});
} else {
res.status(500);
}
});
});
router.post('/login',async function (req,res) {
......@@ -67,6 +86,7 @@ router.post('/login',async function (req,res) {
if (err || !rep) return res.sendStatus(500);
const token = _.merge(rep, { sessionID: req.body.sessionID });
delete token.token;
delete token.passwd;
res.send({ status: "ok", token });
});
}
......@@ -89,11 +109,11 @@ router.get("/recognitions",function(req,res) {
if (err || !rep) return res.sendStatus(500);
else {
const tokenID = rep.tokenID;
let qs = { updateTimestamp: { '$gt': parseInt(moment(date, 'YYYYMMDD').startOf('day').format('x')), '$lte': parseInt(moment(date, 'YYYYMMDD').endOf('day').format('x')) }, tokenID };
_.merge(qs, (called === 'true' ? { called:true } : { called: { $ne: true} }));
const count = await db.collection('numbers').count(qs);
let qs = { updateTimestamp: { '$gt': parseInt(moment(date, 'YYYYMMDD').startOf('day').format('x')), '$lte': parseInt(moment(date, 'YYYYMMDD').endOf('day').format('x')) }, 'tokenInfo.tokenID': OID(tokenID) };
_.merge(qs, { calledInfo: { $exists: called == 'true' } } );
const count = await db.collection('recognition').count(qs);
db
.collection('numbers')
.collection('recognition')
.find(qs)
.sort({ updateTimestamp: -1 })
.skip(parseInt(skip * limit))
......@@ -175,7 +195,7 @@ function notEmpty(data) {
function authorize(data, callback) {
db.collection("tokens").findOne({ phone: data.phone }, (err, rep) => {
if (err || !rep) return callback(err, null);
if (md5token(data.token) !== rep.token)
if (md5token(data.token) !== rep.passwd)
return callback("password wrong", null);
callback(null, rep);
});
......@@ -197,8 +217,86 @@ function genSessionID(tokenID) {
}
async function checkBill(data, callback) {
if(!notEmpty(data)) return callback('参数错误');
let { number, accountID } = data;
if(!/^[0-9a-z]{24}$/.test(accountID) ) return callback('参数错误');
let recharge = await getRechargeByAccount( accountID );
if( recharge <= 0 ) { return callback('余额不足') }
let consume = await getBillByAccount( accountID );
if( consume + number > recharge ) { return callback('余额不足') }
callback && callback();
}
async function updateBill(data, callback) {
if(!notEmpty(data)) return callback('params wrong');
let { pre, number, accountID, type, taskID } = data;
if(!/^[0-9a-z]{24}$/.test(accountID) ) return callback('参数错误');
db
.collection('bills')
.insert(wrapTime({ pre, number, accountID: OID(accountID), type, taskID: OID(taskID) }), (err, rep) => {
if (err) return callback(err);
callback && callback(null, rep);
});
}
async function getBillByAccount( accountID ) {
let consumes = await db
.collection('bills')
.aggregate([
{
$match:{
"accountID": OID(accountID),
"removed": { $ne: true }
/* "createdAt": { '$gt': start, '$lte': end }*/
}
},
{
$group:{
_id: null,
sum: {$sum:"$number"}
}
}]).toArray();
return ( (consumes && consumes.length) ? consumes[0].sum : 0 );
}
async function getRechargeByAccount(accountID) {
let recharges = await db
.collection('recharge')
.aggregate([
{
$match:{
"accountID": OID(accountID),
"removed": { $ne: true }
}
},
{
$group:{
_id:null,
sum:{$sum:"$number"}
}
}]).toArray();
return ( (recharges && recharges.length) ? recharges[0].sum : 0 );
}
async function getCallConsumeByUnikey(unikey,callback) {
const price = db.collection('price').findOne({type: 'call'});
let number = (price && price.number) ? price.number : 1;
const task = db.collection('callTask').findOne({ unikey });
if(!task) {
callback(null, { number: 3 + Math.ceil((EXPIRATION / 60) -1) * number });
} else {
callback(null, { number: Math.ceil((EXPIRATION / 60) ) * number });
}
}
function OID(str) {
return typeof str === 'string' ? mongodb.ObjectID(str) : str;
}
function wrapTime(obj) {
return _.merge(obj, { createdAt: new Date() })
}
module.exports = router;
......@@ -17,6 +17,6 @@ app.use(express.static(__dirname + '/dist'));
app.use('/api', api);
server.listen(8081, function() {
server.listen(6662, function() {
console.log('server started');
});
<template>
<div style="height:100%;">
<mt-header title="再营销客户端" fixed>
<mt-button icon="more" slot="right"><i class="fa fa-sign-out fa-lg"></i></mt-button>
<mt-header title="再营销客户端" fixed style="height:50px">
<mt-button slot="right" @click="del"><i class="fa fa-sign-out fa-lg"></i></mt-button>
</mt-header>
<a :href="target" id="tocal"></a>
<mt-navbar v-model="selected">
<mt-tab-item id="tasks">待拨打</mt-tab-item>
<mt-tab-item id="called">已拨打</mt-tab-item>
<mt-navbar v-model="selected" style="margin-top:50px;">
<mt-tab-item id="tasks"><div class='ttb'><span>待拨打</span><mt-badge size="small" style="margin-left: 10px;">{{ getPage.total }}</mt-badge></div></mt-tab-item>
<mt-tab-item id="called"><div class='ttb'><span>已拨打</span><mt-badge size="small" color="#ddd" style="margin-left: 10px;">{{ getPageCalled.total }}</mt-badge></div></mt-tab-item>
</mt-navbar>
<mt-tab-container v-model="selected">
<mt-tab-container-item id="tasks">
<mt-cell title="日期选择">
<span>{{ currentDate }}</span>
<mt-button type="primary" size="small" @click="dateChange">修改</mt-button>
<mt-tab-container v-model="selected" style="height:100%;">
<mt-tab-container-item id="tasks" style="height:100%;">
<mt-cell
title="日期选择"
style="border-bottom: 1px solid #ddd;"
>
<span>{{ currentDateFormat }}</span>
<mt-button type="primary" size="small" @click="dateChange" style="margin-left:15px;">修改</mt-button>
</mt-cell>
<mt-loadmore :top-method="loadTop" :bottom-method="loadBottom" :bottom-all-loaded="allLoaded" ref="loadmore">
<div v-for="item in getConsumers" v-if="getConsumers.length > 0">
<div class="dia-wrap">
<div class="inner">
<h3>{{ "来源:" + item.slot.slotName }}</h3>
<mt-button type="primary" size="small" @click="call(item)">一键拨打</mt-button>
</div>
<div>
<span><pre>{{ "来访时间:" + item.updateTimestamp }}</pre></span>
<span><pre>{{ "营销指数" + item.score.score < 0 ? 0 : item.score.score }}</pre></span>
</div>
</div>
</div>
</mt-loadmore>
<mt-loadmore :top-method="loadTop" :bottom-all-loaded="allLoaded" ref="loadmore" style="height:100%;">
<div v-for="item in getConsumers" v-if="getConsumers.length > 0">
<div class="dia-wrap">
<div class="inner">
<h3><pre><i class="fa fa-user-o fa-lg"></i>{{ " 来源:" + item.slot.slotName }}</pre></h3>
<mt-button type="primary" size="small" @click="call(item)">一键拨打</mt-button>
</div>
<div class="inner-bottom">
<span><pre>{{ "来访时间:" + item.updateTimestamp }}</pre></span>
<span><pre>{{ "营销指数:" + (item.score.score < 0 ? 0 : item.score.score) }}</pre></span>
</div>
</div>
</div>
</mt-loadmore>
</mt-tab-container-item>
<mt-tab-container-item id="called">
<mt-tab-container-item id="called" style="height:100%;">
<mt-cell
title="日期选择"
style="border-bottom: 1px solid #ddd;"
>
<span>{{ currentDateCalledFormat }}</span>
<mt-button type="primary" size="small" @click="dateChange" style="margin-left:15px;">修改</mt-button>
</mt-cell>
<mt-loadmore :top-method="loadTopCalled" :bottom-all-loaded="allCalledLoaded" ref="loadmoreCalled" style="height:100%;">
<div v-for="item in getCalledConsumers" v-if="getCalledConsumers.length > 0">
<div class="dia-wrap">
<div class="inner">
<h3><pre><i class="fa fa-user-times fa-lg"></i>{{ " 来源:" + item.slot.slotName }}</pre></h3>
<mt-button type="default" size="small" disabled>已拨打</mt-button>
</div>
<div class="inner-bottom">
<span><pre>{{ "拨打时间:" + item.updateCalledTimestamp }}</pre></span>
<span><pre>{{ "营销指数:" + (item.score.score < 0 ? 0 : item.score.score) }}</pre></span>
</div>
</div>
</div>
</mt-loadmore>
</mt-tab-container-item>
</mt-tab-container>
<mt-datetime-picker
ref="picker"
type="time"
startDate="startDate"
endDate="startDate"
type="date"
:startDate="startDate"
:endDate="endDate"
v-model="pickerValue"
:confirm="handleConfirm">
v-on:confirm="handleConfirm">
</mt-datetime-picker>
<div v-if=" (selected == 'called' && getCalledConsumers.length === 0) || (selected == 'tasks' && getConsumers.length === 0) " class="notoshow">
<span>~暂无数据~</span>
</div>
<mt-popup
v-model="popupVisible"
popup-transition="popup-fade"
:closeOnClickModal="closeOnClickModal">
<span>尝试获取号码中...</span>
</mt-popup>
</div>
</template>
<style>
.dia-wrap {
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: baseline;
border-bottom: 1px solid #ddd;
padding: 0px 10px;
box-sizing: border-box;
}
.dia-wrap i{
font-size:16px;
margin-right:5px;
}
.dia-wrap .inner {
display: flex;
flex-direction: row;
width: 100%;
justify-content: space-between;
align-items: center;
height:46px;
}
.inner-bottom{
width: 100%;
height:35px;
display: flex;
align-items: center;
justify-content: space-between;
}
.notoshow{
height:80%;
display:flex;
align-items:center;
flex-direction:column;
justify-content: center;
}
.mint-tab-container-wrap{
height:100%;
}
</style>
<script>
import { mapGetters, mapActions } from 'vuex';
import moment from 'moment';
......@@ -54,39 +125,59 @@ export default {
...mapActions({
getRecongitions: 'GET_RECOGNITIONS',
callBegin: 'CALL_BEGIN',
del: 'delSession',
}),
dateChange() {
this.$refs.picker.open();
},
handleConfirm(date) {
const self = this;
if(this.selected == 'tasks') {
this.currentDate = moment(date.getTime()).format('YYYYMMDD');
this.getRecongitions({
refresh: true,
sessionID: localStorage.getItem('__sessionID'),
limit: 1,
skip: 0,
callback(err, data) {
if(err) {
Toast({
message: '加载错误',
position: 'top',
duration: 3000
});
} else {
self.$date.page.skip = data.page.skip;
self.$data.page.total = data.page.total;
}
},
})
const dateString= moment(date.getTime()).format('YYYYMMDD');
if(this.selected == 'tasks') {
this.$data.currentDate = dateString;
this.getRecongitions({
refresh: true,
sessionID: localStorage.getItem('__sessionID'),
date: dateString,
limit: this.limit,
skip: 0,
callback(err, data) {
if(err) {
Toast({
message: '加载错误',
position: 'top',
duration: 3000
});
}
},
})
} else {
this.$data.currentDateCalled = dateString;
this.getRecongitions({
refresh: true,
sessionID: localStorage.getItem('__sessionID'),
date: dateString,
called: true,
limit: this.limit,
skip: 0,
callback(err, data) {
if(err) {
Toast({
message: '加载错误',
position: 'top',
duration: 3000
});
}
},
})
}
},
call(item) {
const { phone } = this.currentUser;
const self = this;
const { slotID, pubID, unikey, expiration = 30*60 } = item;
this.$data.popupVisible = true;
this.callBegin({
slotID,
pubID,
......@@ -94,26 +185,29 @@ export default {
phone,
expiration,
callback(err, data) {
self.$data.popupVisible = false;
if(err) {
Toast({
message: '拨打失败',
message: err,
position: 'top',
duration: 3000
});
} else {
self.target = "tada:tel/" + data.called;
setTimeout(() => {
document.querySelector('#tocal').click();
}, 500);
//self.target = "tada:tel/" + data.called;
var a = document.createElement('a');
a.setAttribute('href','tada:tel/' + data.called);
a.click();
}
}
})
},
loadTop() {
const self = this;
this.getRecongitions({
sessionID: localStorage.getItem('__sessionID'),
limit: 1,
skip: parseInt(data.page.skip) + 1,
date: this.currentDate,
limit: this.limit,
skip: parseInt(this.getPage.skip) + 1,
callback(err, data) {
if(err) {
Toast({
......@@ -121,13 +215,31 @@ export default {
position: 'top',
duration: 3000
});
} else {
self.$date.page.skip = data.page.skip;
self.$data.page.total = data.page.total;
}
self.$refs.loadmore.onTopLoaded();
},
})
}
},
loadTopCalled() {
const self = this;
this.getRecongitions({
sessionID: localStorage.getItem('__sessionID'),
date: this.currentDateCalled,
limit: this.limit,
called:true,
skip: parseInt(this.getPageCalled.skip) + 1,
callback(err, data) {
if(err) {
Toast({
message: '加载错误',
position: 'top',
duration: 3000
});
}
self.$refs.loadmoreCalled.onTopLoaded();
},
})
},
},
computed: {
...mapGetters([
......@@ -135,42 +247,70 @@ export default {
"getConsumers",
"getCalledConsumers",
"allLoaded",
"allCalledLoaded"
"allCalledLoaded",
"getPage",
"getPageCalled"
]),
currentDateFormat() {
return moment(this.currentDate,'YYYYMMDD').format('YYYY/MM/DD');
},
currentDateCalledFormat() {
return moment(this.currentDateCalled,'YYYYMMDD').format('YYYY/MM/DD')
},
accountShow() {
return this.$store.state.session.currentUser.role === 1;
},
startDate () {
var pre = new Date();
pre.setFullYear(pre.getFullYear()-1);
return pre;
return moment().add(-30,'days').toDate();
},
endDate () {
var tgo = new Date();
tgo.setFullYear(pre.getFullYear()+1);
return tgo;
return moment().toDate();
}
},
mounted() {
this.getBills({
accountID: this.$store.state.session.currentUser._id,
skip: this.pageCurrent - 1,
limit: this.pageSize,
page: this.page,
});
this.getRecongitions({
sessionID: localStorage.getItem('__sessionID'),
date: this.currentDate,
limit: this.limit,
skip: 0,
callback(err, data) {
if(err) {
Toast({
message: '加载错误',
position: 'top',
duration: 3000
});
}
},
})
this.getRecongitions({
sessionID: localStorage.getItem('__sessionID'),
date: this.currentDate,
called: true,
limit: this.limit,
skip: 0,
callback(err, data) {
if(err) {
Toast({
message: '加载错误',
position: 'top',
duration: 3000
});
}
},
})
},
data() {
return {
limit: 10,
closeOnClickModal:false,
popupVisible: false,
selected: 'tasks',
currentDate: '20180130',
currentDateCalled: '20180130', //moment().format('YYYYMMDD')
target: '',
page: {
skip: 0,
total: 0,
}
};
},
};
</script>
\ No newline at end of file
......@@ -2,6 +2,7 @@
import * as _ from 'lodash';
import testDatas from '@/tests/consumers';
import moment from 'moment';
import { Toast } from 'mint-ui';
const types = {
GET_RECOGNITIONS: 'GET_RECOGNITIONS',
......@@ -15,8 +16,14 @@ const state = {
numbers: [],
calledNumbers: [],
numbersLoading: false,
total: 0,
totalCalled: 0,
page: {
skip: 0,
total: 0,
},
pageCalled: {
skip: 0,
total: 0,
}
};
const getters = {
......@@ -24,20 +31,28 @@ const getters = {
return formatNumbers(state.numbers).length;
},
getConsumers() {
console.dir(state.numbers);
return state.numbers;
},
getCalledConsumers() {
console.dir(state.calledNumbers)
return state.calledNumbers;
},
getConsumersLoading() {
return state.numbersLoading;
},
allLoaded() {
return state.numbers.length == state.total;
return state.numbers.length == state.page.total;
},
allCalledLoaded() {
return state.calledNumbers.length == state.totalCalled;
return state.calledNumbers.length == state.pageCalled.total;
},
getPage() {
return state.page;
},
getPageCalled() {
return state.pageCalled;
}
};
const actions = {
......@@ -50,12 +65,11 @@ const actions = {
}
return Promise.reject(res.status);
}).then((data) => {
callback();
commit(types.NUMBERS_LOADING, false);
commit(types.GET_NUMBERS, _.merge(data,{ refresh,called }));
callback(null,data);
commit(types.GET_NUMBERS, _.merge(data,{ refresh, called: (!called || called == false ? false : true) }));
}).catch((err) => {
commit(types.NUMBERS_LOADING, false);
console.log(err);
callback(err);
});
},
......@@ -65,18 +79,17 @@ const actions = {
headers: {
'Content-Type': 'application/json;charset=UTF-8',
},
timeout: 30000,
timeout: 300000,
body: JSON.stringify({ slotID, pubID, unikey, phone, expiration }),
}).then((res) => {
}).then(async (res) => {
if (res.ok) {
return res.json();
}
return Promise.reject(res.status);
return Promise.reject(await res.json());
}).then((data) => {
callback(null, data);
}).catch((err) => {
console.log(err);
callback(err);
}).catch((data) => {
callback(data.error ? data.error : '未知错误');
});
},
};
......@@ -90,10 +103,10 @@ const mutations = {
return x;
});
if(data.called) {
state.totalCalled = data.page.total;
state.pageCalled = _.cloneDeep(data.page);
state.calledNumbers = (data.refresh ? data.recognitions : data.recognitions.concat(state.calledNumbers));
} else {
state.total = data.page.total;
state.page = _.cloneDeep(data.page);
state.numbers = (data.refresh ? data.recognitions : data.recognitions.concat(state.numbers));
}
},
......
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