文件
Henglianshutong/public/assets/js/backend/shopro/dashboard.js

807 行
38 KiB
JavaScript

define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function ($, undefined, Backend, Table, Form, Moment) {
var Controller = {
index: () => {
const { reactive, onMounted } = Vue
const index = {
setup() {
const state = reactive({
wealth: {
total_gmv: '0.00',
today_gmv: '0.00',
total_payout: '0.00',
today_payout: '0.00',
net_retention: '0.00',
payout_rate: '0.00',
paid_order_count: 0,
today_paid_order_count: 0,
avg_order_amount: '0.00'
},
madow: {
total_madow: '0.00000000',
current_rate: '0.0000000000',
rate_per_wan: '0.0000',
total_madow_cny: '0.00',
history_order_n: 0,
yesterday_madow_pool: '0.00000000'
},
voucher: {
total_voucher: '0.00',
released_voucher: '0.00',
used_voucher: '0.00',
pending_voucher: '0.00',
available_voucher: '0.00'
},
partner: {
total_registered_users: 0,
total_agents: 0,
normal_user_count: 0,
taster_count: 0,
maker_count: 0,
maker_levels: {},
venue_count: 0,
county_count: 0,
active_count: 0,
inactive_count: 0,
active_rate: 0,
role_list: [],
role_stats: {}
},
total: {
user: {
data: {},
color: '#806AF6',
color1: 'rgba(128, 106, 246, 0.4)',
color2: 'rgba(128, 106, 246, 0)',
title: '用户数量',
tip: '今日新增用户',
footer: '本周新增用户人数',
},
agent: {
data: {},
color: '#409EFF',
color1: 'rgba(64, 158, 255, 0.4)',
color2: ' rgba(64, 158, 255, 0)',
title: '合伙股东数量',
tip: '今日新增人数',
footer: '本周新增股东人数',
},
share: {
data: {},
color: '#21C732',
color1: 'rgba(33, 199, 50, 0.4)',
color2: 'rgba(33, 199, 50, 0)',
title: '分享裂变次数',
tip: '今日分享次数',
footer: '本周新增分享次数',
},
},
});
function getTotal() {
Fast.api.ajax({
url: 'shopro/dashboard/total',
type: 'GET',
}, function (ret, res) {
state.total.agent.data = res.data.agent_data;
state.total.share.data = res.data.share_data;
state.total.user.data = res.data.user_data;
if (res.data.wealth_data) {
state.wealth = res.data.wealth_data;
}
if (res.data.madow_data) {
state.madow = res.data.madow_data;
}
if (res.data.voucher_data) {
state.voucher = res.data.voucher_data;
}
if (res.data.partner_network) {
state.partner = res.data.partner_network;
}
for (var key in state.total) {
if (state.total[key].data && document.getElementById(`${key}Total`)) {
initChartTotal(key);
}
}
return false;
}, function (ret, res) { });
}
function initChartTotal(key) {
var el = document.getElementById(`${key}Total`);
if (!el) return;
var myChart = echarts.init(el);
window.addEventListener('resize', () => {
myChart.resize();
});
var option = {
grid: {
left: 6,
top: 6,
right: 6,
bottom: 6,
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'none',
},
},
xAxis: {
type: 'category',
data: [],
boundaryGap: false,
axisTick: { show: false },
axisLabel: { show: false },
axisLine: { show: false },
},
yAxis: {
type: 'value',
splitLine: { show: false },
axisLabel: { show: false },
},
series: [{
data: [],
type: 'line',
smooth: true,
showSymbol: false,
symbol: 'circle',
symbolSize: 6,
itemStyle: {
color: state.total[key].color,
},
areaStyle: {
color: new echarts.graphic.LinearGradient(
0, 0, 0, 1,
[
{ offset: 0, color: state.total[key].color1 },
{ offset: 1, color: state.total[key].color2 },
],
false,
),
},
lineStyle: {
width: 2,
},
}]
};
initIntervalTotal(24, 'hours', option, key);
myChart.setOption(option);
}
function initIntervalTotal(interval, kld, option, key) {
let dateTime = `${Moment().format('YYYY-MM-DD')} 00:00:00`;
let x = [];
let y = [];
let timeStamp = [];
for (let i = 0; i <= interval; i++) {
x.push(Moment(dateTime).add(i, kld).format('HH:mm'));
y.push(0);
timeStamp.push(Moment(dateTime).add(i, kld).valueOf());
}
if (state.total[key].data && state.total[key].data.array) {
x.forEach((item, index) => {
state.total[key].data.array.forEach((row) => {
if (
timeStamp[index + 1] &&
row.createtime_unix > timeStamp[index] &&
row.createtime_unix <= timeStamp[index + 1]
) {
y[index]++;
}
});
});
}
option.xAxis.data = x;
option.series[0].data = y;
}
const chart = reactive({
tabsData: {
wealthCompare: '营收与分成对比',
splitStructure: '分成板块趋势',
payAmount: '实收 GMV',
order: '支付订单数',
},
tabActive: 'wealthCompare',
dateTime: getTimeSlot('week'),
shortcuts: [
{
text: '今天',
value: () => getTimeSlot('today'),
},
{
text: '昨天',
value: () => getTimeSlot('yesterday'),
},
{
text: '近一周',
value: () => getTimeSlot('week'),
},
{
text: '近一月',
value: () => getTimeSlot('month'),
},
],
data: {
payAmountArr: [],
payoutArr: [],
orderArr: [],
splitStructure: [],
rewardDetails: [],
periodMadow: '0.00000000',
},
});
function onChangeTabActive(type) {
chart.tabActive = type;
initChart();
}
function onChangeDateTime(e) {
e && getChart();
}
const statistics = reactive({
payAmount: {
num: '0.00',
text: '实收 GMV',
path: '',
tip: '选定时间区间内支付订单的实付总金额(含退款订单)',
status: 'paid',
},
payout: {
num: '0.00',
text: '现金分成支出',
path: '',
tip: '选定时间区间内已真实入账的合伙收益与分成现金总额',
status: 'payout',
},
retention: {
num: '0.00',
text: '平台净留存',
path: '',
tip: '时段内实收 GMV 扣减现金分成支出后的净现金流沉淀',
status: 'retention',
},
payoutRate: {
num: '0.00',
text: '综合分成拨比',
path: '',
tip: '现金分成支出占实收 GMV 的百分比,反映平台精算财务健康度',
status: 'rate',
},
noSend: {
num: 0,
text: '待发货订单',
path: '',
tip: '时间区间内待发货订单数量',
status: 'nosend',
},
aftersale: {
num: 0,
text: '售后维权',
path: '',
tip: '时间区间内申请售后维权的订单数量',
status: 'aftersale',
},
refund: {
num: 0,
text: '退款订单',
path: '',
tip: '时间区间内退款的订单数量',
status: 'refund',
},
});
let chartInstance = null;
let splitPieInstance = null;
async function getChart() {
Fast.api.ajax({
url: 'shopro/dashboard/chart',
type: 'GET',
data: {
date: chart.dateTime.join(' - '),
}
}, function (ret, res) {
statistics.payAmount.num = res.data.payAmountNum || '0.00';
statistics.payout.num = res.data.payoutNum || '0.00';
statistics.retention.num = res.data.retentionNum || '0.00';
statistics.payoutRate.num = res.data.payoutRate || '0.00';
statistics.noSend.num = res.data.noSendNum || 0;
statistics.aftersale.num = res.data.aftersaleNum || 0;
statistics.refund.num = res.data.refundNum || 0;
chart.data.payAmountArr = res.data.payAmountArr || [];
chart.data.payoutArr = res.data.payoutArr || [];
chart.data.orderArr = res.data.orderArr || [];
chart.data.splitStructure = res.data.splitStructure || [];
chart.data.rewardDetails = res.data.rewardDetails || [];
chart.data.periodMadow = res.data.periodMadow || '0.00000000';
initChart();
initSplitPie();
return false;
}, function (ret, res) { });
}
function initChart() {
var el = document.getElementById('chartContent');
if (!el) return;
if (!chartInstance) {
chartInstance = echarts.init(el);
window.addEventListener('resize', () => {
chartInstance && chartInstance.resize();
});
}
if (!chart.dateTime || chart.dateTime.length < 2) {
chartInstance.clear();
return;
}
let time =
(new Date(chart.dateTime[1].replace(/-/g, '/')).getTime() -
new Date(chart.dateTime[0].replace(/-/g, '/')).getTime()) /
1000 +
1;
let kld = 'days';
let interval = 7;
if (time <= 60 * 60) {
interval = Math.max(1, parseInt(time / 60));
kld = 'minutes';
} else if (time <= 60 * 60 * 24) {
interval = Math.max(1, parseInt(time / (60 * 60)));
kld = 'hours';
} else if (time <= 60 * 60 * 24 * 30 * 1.5) {
interval = Math.max(1, parseInt(time / (60 * 60 * 24)));
kld = 'days';
} else if (time < 60 * 60 * 24 * 30 * 24) {
interval = Math.max(1, parseInt(time / (60 * 60 * 24 * 30)));
kld = 'months';
} else {
interval = Math.max(1, parseInt(time / (60 * 60 * 24 * 30 * 12)));
kld = 'years';
}
let x = [];
let timeStamp = [];
for (let i = 0; i <= interval - 1; i++) {
if (kld == 'minutes' || kld == 'hours') {
x.push(Moment(chart.dateTime[0]).add(i, kld).format('DD HH:mm'));
} else if (kld == 'days') {
x.push(Moment(chart.dateTime[0]).add(i, kld).format('MM-DD'));
} else if (kld == 'months') {
x.push(Moment(chart.dateTime[0]).add(i, kld).format('YYYY-MM'));
} else {
x.push(Moment(chart.dateTime[0]).add(i, kld).format('YYYY'));
}
}
for (let i = 1; i <= interval; i++) {
timeStamp.push(Moment(chart.dateTime[0]).add(i, kld).valueOf());
}
let option = {
grid: {
left: '16px',
top: '40px',
bottom: '24px',
right: '20px',
containLabel: true,
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
shadowStyle: {
color: 'rgba(128, 106, 246, 0.08)',
},
},
},
legend: {
show: true,
top: '0px',
right: '10px',
textStyle: { color: 'var(--sa-subfont)' }
},
xAxis: {
type: 'category',
data: x,
axisLine: { lineStyle: { color: '#e4e7ed' } },
axisTick: { show: false },
axisLabel: { color: 'var(--sa-subfont)' }
},
yAxis: {
type: 'value',
splitLine: { lineStyle: { type: 'dashed', color: '#f0f2f5' } },
axisLine: { show: false },
axisTick: { show: false },
axisLabel: { color: 'var(--sa-subfont)' }
},
series: []
};
if (chart.tabActive === 'wealthCompare') {
let yGmv = new Array(x.length).fill(0);
let yPayout = new Array(x.length).fill(0);
let yRetention = new Array(x.length).fill(0);
chart.data.payAmountArr.forEach((item) => {
x.forEach((label, idx) => {
if (
item.createtime > (idx - 1 >= 0 ? timeStamp[idx - 1] : 0) &&
item.createtime <= timeStamp[idx]
) {
yGmv[idx] = parseFloat((Number(yGmv[idx]) + Number(item.counter)).toFixed(2));
}
});
});
chart.data.payoutArr.forEach((item) => {
x.forEach((label, idx) => {
if (
item.createtime > (idx - 1 >= 0 ? timeStamp[idx - 1] : 0) &&
item.createtime <= timeStamp[idx]
) {
yPayout[idx] = parseFloat((Number(yPayout[idx]) + Number(item.counter)).toFixed(2));
}
});
});
for (let i = 0; i < x.length; i++) {
yRetention[i] = parseFloat(Math.max(0, yGmv[i] - yPayout[i]).toFixed(2));
}
option.series = [
{
name: '实收 GMV',
type: 'bar',
barMaxWidth: 18,
itemStyle: {
color: '#806af6',
borderRadius: [3, 3, 0, 0]
},
data: yGmv
},
{
name: '现金分成支出',
type: 'bar',
barMaxWidth: 18,
itemStyle: {
color: '#fa8c16',
borderRadius: [3, 3, 0, 0]
},
data: yPayout
},
{
name: '平台净留存',
type: 'line',
smooth: true,
itemStyle: { color: '#52c41a' },
lineStyle: { width: 3 },
data: yRetention
}
];
} else if (chart.tabActive === 'splitStructure') {
let seriesMap = {
push3: { name: '直推裂变', color: '#806AF6', data: new Array(x.length).fill(0) },
maker: { name: '创客团队', color: '#13C2C2', data: new Array(x.length).fill(0) },
channel: { name: '实体渠道', color: '#FA8C16', data: new Array(x.length).fill(0) },
share: { name: '全网均分', color: '#EB2F96', data: new Array(x.length).fill(0) },
};
const push3Types = ['push3'];
const makerTypes = ['maker_diff', 'maker_peer', 'maker_overtaken'];
const channelTypes = ['team_venue', 'team_county', 'venue_peer', 'county_peer'];
const shareTypes = ['venue_share', 'county_share'];
chart.data.payoutArr.forEach((item) => {
let targetKey = 'push3';
if (push3Types.includes(item.type)) targetKey = 'push3';
else if (makerTypes.includes(item.type)) targetKey = 'maker';
else if (channelTypes.includes(item.type)) targetKey = 'channel';
else if (shareTypes.includes(item.type)) targetKey = 'share';
x.forEach((label, idx) => {
if (
item.createtime > (idx - 1 >= 0 ? timeStamp[idx - 1] : 0) &&
item.createtime <= timeStamp[idx]
) {
seriesMap[targetKey].data[idx] = parseFloat((Number(seriesMap[targetKey].data[idx]) + Number(item.counter)).toFixed(2));
}
});
});
option.series = Object.keys(seriesMap).map(k => ({
name: seriesMap[k].name,
type: 'bar',
stack: 'splitTotal',
barMaxWidth: 20,
itemStyle: { color: seriesMap[k].color },
data: seriesMap[k].data
}));
} else if (chart.tabActive === 'payAmount') {
let yGmv = new Array(x.length).fill(0);
chart.data.payAmountArr.forEach((item) => {
x.forEach((label, idx) => {
if (
item.createtime > (idx - 1 >= 0 ? timeStamp[idx - 1] : 0) &&
item.createtime <= timeStamp[idx]
) {
yGmv[idx] = parseFloat((Number(yGmv[idx]) + Number(item.counter)).toFixed(2));
}
});
});
option.series = [{
name: '实收 GMV (元)',
type: 'bar',
barMaxWidth: 22,
itemStyle: { color: '#806af6', borderRadius: [4, 4, 0, 0] },
data: yGmv
}];
} else {
let yOrders = new Array(x.length).fill(0);
chart.data.orderArr.forEach((item) => {
x.forEach((label, idx) => {
if (
item.createtime > (idx - 1 >= 0 ? timeStamp[idx - 1] : 0) &&
item.createtime <= timeStamp[idx]
) {
yOrders[idx]++;
}
});
});
option.series = [{
name: '支付订单数 (单)',
type: 'bar',
barMaxWidth: 22,
itemStyle: { color: '#409eff', borderRadius: [4, 4, 0, 0] },
data: yOrders
}];
}
chartInstance.setOption(option, true);
}
function initSplitPie() {
var el = document.getElementById('splitPieContent');
if (!el) return;
if (!splitPieInstance) {
splitPieInstance = echarts.init(el);
window.addEventListener('resize', () => {
splitPieInstance && splitPieInstance.resize();
});
}
let pieData = (chart.data.splitStructure || []).map(item => ({
name: item.name,
value: parseFloat(item.amount) || 0,
itemStyle: { color: item.color }
})).filter(i => i.value > 0);
if (pieData.length === 0) {
pieData = [{ name: '暂无分成', value: 0, itemStyle: { color: '#e4e7ed' } }];
}
let option = {
tooltip: {
trigger: 'item',
formatter: '{b}<br/>金额: ¥{c} ({d}%)'
},
legend: {
show: false,
},
series: [
{
name: '分成构成',
type: 'pie',
radius: ['52%', '78%'],
center: ['50%', '50%'],
avoidLabelOverlap: false,
label: {
show: false,
},
emphasis: {
label: {
show: true,
fontSize: 14,
fontWeight: 'bold',
formatter: '{b}\n¥{c}'
}
},
data: pieData
}
]
};
splitPieInstance.setOption(option, true);
}
function getTimeSlot(e) {
let beginTime = Moment().format('YYYY-MM-DD');
let endTime = Moment().format('YYYY-MM-DD');
switch (e) {
case 'today':
beginTime = Moment().format('YYYY-MM-DD');
endTime = Moment().format('YYYY-MM-DD');
break;
case 'yesterday':
endTime = Moment().subtract(1, 'days').format('YYYY-MM-DD');
beginTime = endTime;
break;
case 'week':
beginTime = Moment().subtract(6, 'days').format('YYYY-MM-DD');
endTime = Moment().format('YYYY-MM-DD');
break;
case 'month':
beginTime = Moment().subtract(29, 'days').format('YYYY-MM-DD');
endTime = Moment().format('YYYY-MM-DD');
break;
}
return [beginTime + ' 00:00:00', endTime + ' 23:59:59'];
}
const ranking = reactive({
goods: [],
hot_search: [],
partner: [],
recentReport: null
});
let rankingPieInstance = null;
function getRanking() {
Fast.api.ajax({
url: 'shopro/dashboard/ranking',
type: 'GET',
}, function (ret, res) {
ranking.goods = res.data.goods || [];
ranking.hot_search = res.data.hot_search || [];
ranking.partner = res.data.partner_ranking || [];
ranking.recentReport = res.data.daily_report_recent || null;
initRankingPie();
return false;
}, function (ret, res) { });
}
function initRankingPie() {
var el = document.getElementById('rankingContent');
if (!el) return;
if (!rankingPieInstance) {
rankingPieInstance = echarts.init(el);
window.addEventListener('resize', () => {
rankingPieInstance && rankingPieInstance.resize();
});
}
let data = (ranking.hot_search || []).map(item => ({
name: item.keyword,
value: item.num
}));
let option = {
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)',
},
series: [
{
name: '热搜榜',
type: 'pie',
radius: ['52%', '90%'],
avoidLabelOverlap: false,
label: { show: false },
data: data,
},
],
};
rankingPieInstance.setOption(option, true);
}
function onOpenTab(url, title) {
if (top.Backend && top.Backend.api && top.Backend.api.addtabs) {
top.Backend.api.addtabs(url, title);
} else if (Backend && Backend.api && Backend.api.addtabs) {
Backend.api.addtabs(url, title);
} else {
Fast.api.open(url, title);
}
}
function onOpen(status) {
let url = `shopro/order/order/index?status=${status}&createtime=${encodeURI(JSON.stringify(chart.dateTime))}`;
onOpenTab(url, "订单管理");
}
function onOpenReward(type) {
let url = `shopro/partner/reward/index?status=1` + (type ? `&reward_type=${type}` : '');
onOpenTab(url, "收益流水");
}
function onOpenAgent(level) {
let url = `shopro/commission/agent/index` + (level ? `&level=${level}` : '');
onOpenTab(url, "成员档案");
}
function onOpenDaily() {
onOpenTab('shopro/partner/daily/index', "日结对账");
}
function onOpenVoucher() {
onOpenTab('shopro/partner/voucher/index', "兑换券管理");
}
function onOpenRoleAgent(item) {
if (!item) return onOpenAgent('');
if (item.filter_type === 'normal') {
onOpenTab('shopro/user/user/index', '普通会员');
} else if (item.filter_type === 'venue') {
onOpenTab('shopro/commission/agent/index?is_venue=1', '酒道馆档案');
} else if (item.filter_type === 'county') {
onOpenTab('shopro/commission/agent/index?is_county=1', '区县代理档案');
} else if (item.filter_type === 'partner') {
onOpenTab('shopro/commission/agent/index', '合伙股东档案');
} else if (item.filter_level === 'maker') {
onOpenTab('shopro/commission/agent/index', '创客团队档案');
} else if (item.filter_level) {
onOpenTab(`shopro/commission/agent/index?level=${item.filter_level}`, `${item.role_name}成员`);
} else {
onOpenTab('shopro/commission/agent/index', '合伙人档案');
}
}
function onOpenRoleReward(item) {
let url = 'shopro/partner/reward/index?status=1';
onOpenTab(url, '收益明细');
}
onMounted(() => {
Config.cardList.forEach(item => {
if (item.name == 'total' && item.status) {
getTotal();
}
if (item.name == 'chart' && item.status) {
getChart();
}
if (item.name == 'ranking' && item.status) {
getRanking();
}
})
})
return {
state,
getTotal,
chart,
onChangeTabActive,
onChangeDateTime,
statistics,
getChart,
initChart,
initSplitPie,
getTimeSlot,
ranking,
onOpen,
onOpenReward,
onOpenAgent,
onOpenRoleAgent,
onOpenRoleReward,
onOpenDaily,
onOpenVoucher,
onOpenTab,
}
}
}
createApp('index', index);
},
};
return Controller;
});