|
|
@@ -1,22 +1,79 @@
|
|
|
<script setup>
|
|
|
import { Page } from '@vben/common-ui';
|
|
|
import { requestClient } from '#/api/request';
|
|
|
-import { Button, message } from 'ant-design-vue';
|
|
|
+import { Button, message, Form, InputNumber, Drawer } from 'ant-design-vue';
|
|
|
import { ref, reactive, computed, onMounted, onUnmounted, useTemplateRef } from 'vue';
|
|
|
import dayjs from 'dayjs';
|
|
|
|
|
|
import MatchCard from '../components/match_card.vue';
|
|
|
|
|
|
+import { useContentsPositionStore } from '@vben/stores';
|
|
|
+const contentsPositionStore = useContentsPositionStore();
|
|
|
+
|
|
|
const solutions = ref([]);
|
|
|
const selectedSolutions = reactive([]);
|
|
|
+const totalProfit = ref({});
|
|
|
+
|
|
|
+const jcOptions = reactive({
|
|
|
+ bet: 10000,
|
|
|
+ rebate: 12,
|
|
|
+});
|
|
|
+
|
|
|
+const totalProfitVisible = ref(false);
|
|
|
+
|
|
|
+const fixFloat = (number, x = 2) => {
|
|
|
+ return parseFloat(number.toFixed(x));
|
|
|
+}
|
|
|
+
|
|
|
+const headerStyle = computed(() => {
|
|
|
+ return {
|
|
|
+ position: contentsPositionStore.position,
|
|
|
+ top: contentsPositionStore.top,
|
|
|
+ left: contentsPositionStore.left,
|
|
|
+ width: contentsPositionStore.width,
|
|
|
+ paddingLeft: contentsPositionStore.paddingLeft,
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+const totalProfitValue = computed(() => {
|
|
|
+ const profit = {};
|
|
|
+ const jcScale = jcOptions.bet / totalProfit.value.jc_base;
|
|
|
+ const jcRebate = jcOptions.bet * jcOptions.rebate / 100;
|
|
|
+
|
|
|
+ Object.keys(totalProfit.value).forEach(key => {
|
|
|
+ if (key == 'win_diff') {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (key.startsWith('gold')) {
|
|
|
+ profit[key] = fixFloat(totalProfit.value[key] * jcScale);
|
|
|
+ }
|
|
|
+ else if (key.startsWith('win_')) {
|
|
|
+ profit[key] = fixFloat(totalProfit.value[key] * jcScale + jcRebate);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return profit;
|
|
|
+});
|
|
|
|
|
|
const solutionsList = computed(() => {
|
|
|
const startTimestamp = selectedSolutions[0]?.timestamp ?? 0;
|
|
|
return solutions.value.map(item => {
|
|
|
const selected = selectedSolutions.findIndex(sol => sol.sid === item.sid) >= 0;
|
|
|
const disabled = !selected && (item.info.jc.timestamp < startTimestamp + 1000 * 60 * 60 * 2);
|
|
|
+ const currentSol = { ...item.sol };
|
|
|
+
|
|
|
+ const jcScale = jcOptions.bet / currentSol.jc_base;
|
|
|
+ const jcRebate = jcOptions.bet * jcOptions.rebate / 100;
|
|
|
+
|
|
|
+ Object.keys(currentSol).forEach(key => {
|
|
|
+ if (key.startsWith('gold_')) {
|
|
|
+ currentSol[key] = fixFloat(currentSol[key] * jcScale);
|
|
|
+ }
|
|
|
+ else if (key.startsWith('win_')) {
|
|
|
+ currentSol[key] = fixFloat(currentSol[key] * jcScale + jcRebate);
|
|
|
+ }
|
|
|
+ });
|
|
|
// console.log(new Date(item.info.jc.timestamp), new Date(startTimestamp), disabled);
|
|
|
- return { ...item, selected, disabled };
|
|
|
+ return { ...item, sol: currentSol, selected, disabled };
|
|
|
})
|
|
|
// .filter(item => !item.disabled);
|
|
|
});
|
|
|
@@ -100,7 +157,7 @@ const formatJcEvents = (events) => {
|
|
|
eventsMap[ratioKey][index] = { key, value };
|
|
|
|
|
|
});
|
|
|
- return Object.keys(eventsMap).sort((a, b) => b.localeCompare(a)).map(key => {
|
|
|
+ return Object.keys(eventsMap).sort((a, b) => b.localeCompare(a)).map(key => {
|
|
|
return [key, ...eventsMap[key]];
|
|
|
});
|
|
|
}
|
|
|
@@ -155,10 +212,10 @@ const formatEvents = (events, cprKeys) => {
|
|
|
values[0] = { key, value: events[key] ?? 0 };
|
|
|
}
|
|
|
else if (side === 'c') {
|
|
|
- values[2] = { key, value: events[key] ?? 0 } ;
|
|
|
+ values[2] = { key, value: events[key] ?? 0 };
|
|
|
}
|
|
|
}
|
|
|
- if (typeof(ratioKey) == 'number') {
|
|
|
+ if (typeof (ratioKey) == 'number') {
|
|
|
if (ratioKey > 0) {
|
|
|
ratioKey = `+${ratioKey}`;
|
|
|
}
|
|
|
@@ -171,14 +228,14 @@ const formatEvents = (events, cprKeys) => {
|
|
|
}
|
|
|
eventsMap[ratioKey] = values;
|
|
|
});
|
|
|
- return Object.keys(eventsMap).sort((a, b) => a.localeCompare(b)).map(key => {
|
|
|
+ return Object.keys(eventsMap).sort((a, b) => a.localeCompare(b)).map(key => {
|
|
|
return [key, ...eventsMap[key]];
|
|
|
});
|
|
|
}
|
|
|
|
|
|
const updateSolutions = async () => {
|
|
|
const { solutions: solutionsList, gamesEvents: eventsList } = await getSolutions();
|
|
|
- solutions.value = solutionsList.map(item => {
|
|
|
+ solutions.value = solutionsList?.map(item => {
|
|
|
const { cpr, info, sol: { cross_type, jc_index, gold_side_a, gold_side_b, gold_side_m, win_side_a, win_side_b, win_side_m, win_average } } = item;
|
|
|
const cprKeys = cpr.map(item => item.k);
|
|
|
|
|
|
@@ -199,9 +256,20 @@ const updateSolutions = async () => {
|
|
|
});
|
|
|
|
|
|
return item;
|
|
|
- });
|
|
|
+ }) ?? [];
|
|
|
}
|
|
|
|
|
|
+const showTotalProfit = async () => {
|
|
|
+ totalProfit.value = await calcTotalProfit();
|
|
|
+ totalProfitVisible.value = true;
|
|
|
+};
|
|
|
+
|
|
|
+const closeTotalProfit = () => {
|
|
|
+ totalProfitVisible.value = false;
|
|
|
+ selectedSolutions.length = 0;
|
|
|
+ totalProfit.value = {};
|
|
|
+};
|
|
|
+
|
|
|
const toggleSolution = (sid, timestamp) => {
|
|
|
const findIndex = selectedSolutions.findIndex(item => item.sid === sid);
|
|
|
if (findIndex >= 0) {
|
|
|
@@ -214,7 +282,7 @@ const toggleSolution = (sid, timestamp) => {
|
|
|
selectedSolutions.splice(1, 1, { sid, timestamp });
|
|
|
}
|
|
|
if (selectedSolutions.length == 2) {
|
|
|
- calcTotalProfit();
|
|
|
+ showTotalProfit();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -228,7 +296,6 @@ onMounted(() => {
|
|
|
});
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
- console.log('unmounted');
|
|
|
clearInterval(updateTimer);
|
|
|
});
|
|
|
|
|
|
@@ -237,95 +304,137 @@ onUnmounted(() => {
|
|
|
|
|
|
<template>
|
|
|
<div class="solution-container">
|
|
|
- <div class="solution-header">
|
|
|
- <span>JC</span>
|
|
|
- <span>PS</span>
|
|
|
- <span>OB</span>
|
|
|
- <em>利润</em>
|
|
|
+
|
|
|
+ <div class="contents-header transition-all duration-200" :style="headerStyle">
|
|
|
+ <div class="solution-options">
|
|
|
+ <Form layout="inline">
|
|
|
+ <Form.Item label="JC 投注">
|
|
|
+ <InputNumber size="small" placeholder="JC投注" min="1000" v-model:value="jcOptions.bet" />
|
|
|
+ </Form.Item>
|
|
|
+ <Form.Item label="JC 返点">
|
|
|
+ <InputNumber size="small" placeholder="JC返点" min="0" v-model:value="jcOptions.rebate" />
|
|
|
+ </Form.Item>
|
|
|
+ </Form>
|
|
|
+ </div>
|
|
|
+ <div class="solution-header">
|
|
|
+ <span>JC</span>
|
|
|
+ <span>PS</span>
|
|
|
+ <span>OB</span>
|
|
|
+ <em>利润</em>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
+
|
|
|
<div class="solution-list">
|
|
|
<div class="solution-item"
|
|
|
- v-for="{ sid, sol: {win_average}, info: {jc, ps, ob}, selected, disabled } in solutionsList" :key="sid"
|
|
|
- :class="{'selected': selected, 'disabled': disabled}"
|
|
|
- >
|
|
|
- <MatchCard platform="jc"
|
|
|
- :eventId="jc.eventId"
|
|
|
- :leagueName="jc.leagueName"
|
|
|
- :teamHomeName="jc.teamHomeName"
|
|
|
- :teamAwayName="jc.teamAwayName"
|
|
|
- :dateTime="jc.dateTime"
|
|
|
- :eventInfo="jc.eventInfo"
|
|
|
- :events="jc.events ?? []"
|
|
|
- :selected="jc.selected ?? []"/>
|
|
|
-
|
|
|
- <MatchCard platform="ps"
|
|
|
- :eventId="ps.eventId"
|
|
|
- :leagueName="ps.leagueName"
|
|
|
- :teamHomeName="ps.teamHomeName"
|
|
|
- :teamAwayName="ps.teamAwayName"
|
|
|
- :dateTime="ps.dateTime"
|
|
|
- :events="ps.events ?? []"
|
|
|
- :selected="ps.selected ?? []"/>
|
|
|
-
|
|
|
- <MatchCard platform="ob"
|
|
|
- :eventId="ob.eventId"
|
|
|
- :leagueName="ob.leagueName"
|
|
|
- :teamHomeName="ob.teamHomeName"
|
|
|
- :teamAwayName="ob.teamAwayName"
|
|
|
- :dateTime="ob.dateTime"
|
|
|
- :events="ob.events ?? []"
|
|
|
- :selected="ob.selected ?? []"/>
|
|
|
+ v-for="{ sid, sol: { win_average }, info: { jc, ps, ob }, selected, disabled } in solutionsList" :key="sid"
|
|
|
+ :class="{ 'selected': selected, 'disabled': disabled }">
|
|
|
+ <MatchCard platform="jc" :eventId="jc.eventId" :leagueName="jc.leagueName" :teamHomeName="jc.teamHomeName"
|
|
|
+ :teamAwayName="jc.teamAwayName" :dateTime="jc.dateTime" :eventInfo="jc.eventInfo" :events="jc.events ?? []"
|
|
|
+ :selected="jc.selected ?? []" />
|
|
|
+
|
|
|
+ <MatchCard platform="ps" :eventId="ps.eventId" :leagueName="ps.leagueName" :teamHomeName="ps.teamHomeName"
|
|
|
+ :teamAwayName="ps.teamAwayName" :dateTime="ps.dateTime" :events="ps.events ?? []"
|
|
|
+ :selected="ps.selected ?? []" />
|
|
|
+
|
|
|
+ <MatchCard platform="ob" :eventId="ob.eventId" :leagueName="ob.leagueName" :teamHomeName="ob.teamHomeName"
|
|
|
+ :teamAwayName="ob.teamAwayName" :dateTime="ob.dateTime" :events="ob.events ?? []"
|
|
|
+ :selected="ob.selected ?? []" />
|
|
|
|
|
|
<div class="solution-profit" @click="!disabled && toggleSolution(sid, jc.timestamp)">{{ win_average }}</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
+ <div class="list-empty" v-if="!solutionsList.length">暂无数据</div>
|
|
|
+
|
|
|
+ <Drawer
|
|
|
+ title="综合利润方案"
|
|
|
+ placement="bottom"
|
|
|
+ :visible="totalProfitVisible"
|
|
|
+ @close="closeTotalProfit"
|
|
|
+ >
|
|
|
+ <div class="solution-total-profit">
|
|
|
+ <ul style="text-align: center;">
|
|
|
+ <li v-for="key in Object.keys(totalProfitValue)" :key="key">
|
|
|
+ <span>{{ key }}:</span>
|
|
|
+ <span>{{ totalProfitValue[key] }}</span>
|
|
|
+ </li>
|
|
|
+ </ul>
|
|
|
+ </div>
|
|
|
+ </Drawer>
|
|
|
+
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
+.contents-header {
|
|
|
+ position: fixed;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ z-index: 201;
|
|
|
+ width: 100%;
|
|
|
+ border-bottom: 1px solid hsl(var(--border));
|
|
|
+ background-color: hsl(var(--background));
|
|
|
+}
|
|
|
+.solution-options {
|
|
|
+ position: relative;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ padding: 5px 20px;
|
|
|
+ border-bottom: 1px solid hsl(var(--border));
|
|
|
+}
|
|
|
.solution-header {
|
|
|
position: relative;
|
|
|
- z-index: 11;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
- height: 40px;
|
|
|
+ height: 30px;
|
|
|
padding: 0 20px;
|
|
|
- background-color: hsl(var(--background));
|
|
|
- box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
|
|
|
- span, em {
|
|
|
+ span,
|
|
|
+ em {
|
|
|
display: block;
|
|
|
text-align: center;
|
|
|
}
|
|
|
+
|
|
|
span {
|
|
|
flex: 1;
|
|
|
}
|
|
|
+
|
|
|
em {
|
|
|
width: 80px;
|
|
|
font-style: normal;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+.solution-container {
|
|
|
+ padding-top: 74px;
|
|
|
+}
|
|
|
+
|
|
|
.solution-list {
|
|
|
padding: 20px;
|
|
|
overflow: hidden;
|
|
|
}
|
|
|
+
|
|
|
.solution-item {
|
|
|
display: flex;
|
|
|
border-radius: 10px;
|
|
|
background-color: hsl(var(--card));
|
|
|
+
|
|
|
&.selected {
|
|
|
background-color: hsl(var(--primary) / 0.15);
|
|
|
}
|
|
|
+
|
|
|
&.disabled {
|
|
|
- filter: blur(15px);
|
|
|
+ opacity: 0.5;
|
|
|
cursor: not-allowed;
|
|
|
}
|
|
|
+
|
|
|
&:not(:last-child) {
|
|
|
margin-bottom: 20px;
|
|
|
}
|
|
|
+
|
|
|
.match-card {
|
|
|
flex: 1;
|
|
|
border-right: 1px solid hsl(var(--border));
|
|
|
}
|
|
|
+
|
|
|
.solution-profit {
|
|
|
display: flex;
|
|
|
width: 80px;
|
|
|
@@ -333,4 +442,11 @@ onUnmounted(() => {
|
|
|
justify-content: center;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+.list-empty {
|
|
|
+ text-align: center;
|
|
|
+ padding: 10px;
|
|
|
+ font-size: 18px;
|
|
|
+ color: hsl(var(--foreground) / 0.7);
|
|
|
+}
|
|
|
</style>
|