|
|
@@ -0,0 +1,664 @@
|
|
|
+<script setup>
|
|
|
+import { requestClient } from '#/api/request';
|
|
|
+import { Button, message, Form, InputNumber, RadioGroup, Radio, Drawer } from 'ant-design-vue';
|
|
|
+import { ref, reactive, computed, watch, onMounted, onUnmounted } 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 markCount = ref({ all: 0, rollball: 0, today: 0, early: 0 });
|
|
|
+const selectedSolutions = reactive([]);
|
|
|
+const totalProfit = ref({});
|
|
|
+const loopActive = ref(false);
|
|
|
+const loopTimer = ref(null);
|
|
|
+const updateTimer = ref(null);
|
|
|
+const minProfitRate = ref(2);
|
|
|
+const marketType = ref(-1);
|
|
|
+const updateLoaderHide = ref(null);
|
|
|
+
|
|
|
+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 = {}, preSolution = {}, subSolution = {}, gamesEvents = {} } = totalProfit.value;
|
|
|
+// const sol1 = formatSolution(preSolution, gamesEvents);
|
|
|
+// const sol2 = formatSolution(subSolution, gamesEvents);
|
|
|
+
|
|
|
+// const psInfo = [];
|
|
|
+// const outPreSol = [];
|
|
|
+// const outSubSol = [];
|
|
|
+
|
|
|
+// const solutions = [sol1, sol2].filter(item => item);
|
|
|
+// solutions.forEach((item, index) => {
|
|
|
+// const { sol: { ps_index }, cpr } = item;
|
|
|
+// const newCpr = [...cpr];
|
|
|
+// const ps_info = newCpr.splice(ps_index, 1);
|
|
|
+// psInfo.push({ ...ps_info[0] });
|
|
|
+// newCpr.forEach((c, i) => {
|
|
|
+// let side = '';
|
|
|
+// if (ps_index == 0) {
|
|
|
+// if (i == 0) {
|
|
|
+// side = "B"
|
|
|
+// }
|
|
|
+// else {
|
|
|
+// side = "M";
|
|
|
+// }
|
|
|
+// }
|
|
|
+// else if (ps_index == 1) {
|
|
|
+// if (i == 0) {
|
|
|
+// side = "A";
|
|
|
+// }
|
|
|
+// else {
|
|
|
+// side = "M";
|
|
|
+// }
|
|
|
+// }
|
|
|
+// else {
|
|
|
+// if (i == 0) {
|
|
|
+// side = "A";
|
|
|
+// }
|
|
|
+// else {
|
|
|
+// side = "B";
|
|
|
+// }
|
|
|
+// }
|
|
|
+// if (index == 0) {
|
|
|
+// outPreSol.push({ ...c, g: profitInfo[`gold${side}${index+1}`] });
|
|
|
+// }
|
|
|
+// else {
|
|
|
+// outSubSol.push({ ...c, g: profitInfo[`gold${side}${index+1}`] });
|
|
|
+// }
|
|
|
+// })
|
|
|
+// });
|
|
|
+
|
|
|
+// return { solutions, profit: profitInfo, psInfo, outPreSol, outSubSol };
|
|
|
+// });
|
|
|
+
|
|
|
+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 = false && !selected && (item.info.ps.timestamp < startTimestamp + 1000 * 60 * 60 * 2);
|
|
|
+ const { sol: { inner_base, win_average } } = item;
|
|
|
+ const win_average_rate = fixFloat(win_average / inner_base * 100);
|
|
|
+ const currentSol = { ...item.sol, win_average_rate };
|
|
|
+ return { ...item, sol: currentSol, selected, disabled };
|
|
|
+ });
|
|
|
+});
|
|
|
+
|
|
|
+const getSolutions = async () => {
|
|
|
+ try {
|
|
|
+ const win_min = minProfitRate.value * 100;
|
|
|
+ const mk = marketType.value;
|
|
|
+ const with_events = true;
|
|
|
+ const data = await requestClient.get('/pstery/get_solutions', { params: { win_min, mk, with_events } });
|
|
|
+ return data;
|
|
|
+ }
|
|
|
+ catch (error) {
|
|
|
+ console.error('Failed to fetch solutions:', error);
|
|
|
+ message.error('获取中单方案失败');
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const calcTotalProfit = async () => {
|
|
|
+ const sids = selectedSolutions.map(item => item.sid);
|
|
|
+ try {
|
|
|
+ const totalProfit = await requestClient.post('/pstery/calc_total_profit', [...sids]);
|
|
|
+ return totalProfit;
|
|
|
+ }
|
|
|
+ catch (error) {
|
|
|
+ console.error('Failed to calc total profit:', error);
|
|
|
+ message.error('计算综合利润失败');
|
|
|
+ return {};
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const parseIorKey = (iorKey) => {
|
|
|
+ const [, type, accept, side, , ratioString] = iorKey.match(/^ior_(r|ou|m|wm|ot|os)(a?)(h|c|n)?(_([\d-]+))?$/);
|
|
|
+ let ratio = 0;
|
|
|
+ if (type === 'ot' || type === 'os') {
|
|
|
+ ratio = ratioString;
|
|
|
+ }
|
|
|
+ else if (ratioString) {
|
|
|
+ ratio = `${ratioString[0]}.${ratioString.slice(1)}` * (accept ? 1 : -1);
|
|
|
+ }
|
|
|
+ return { type, side, ratio };
|
|
|
+}
|
|
|
+
|
|
|
+const PS_IOR_KEYS = [
|
|
|
+ ['0', 'ior_mh', 'ior_mn', 'ior_mc'],
|
|
|
+ ['-1', 'ior_rh_15', 'ior_wmh_1', 'ior_rac_05'],
|
|
|
+ ['-2', 'ior_rh_25', 'ior_wmh_2', 'ior_rac_15'],
|
|
|
+ ['+1', 'ior_rah_05', 'ior_wmc_1', 'ior_rc_15'],
|
|
|
+ ['+2', 'ior_rah_15', 'ior_wmc_2', 'ior_rc_25'],
|
|
|
+ // ['0-1', 'ior_ot_0', 'ior_os_0-1', 'ior_ot_1'],
|
|
|
+ // ['2-3', 'ior_ot_2', 'ior_os_2-3', 'ior_ot_3'],
|
|
|
+ ['ot_1', '-', 'ior_ot_1', '-'],
|
|
|
+ ['ot_2', '-', 'ior_ot_2', '-'],
|
|
|
+ ['ot_3', '-', 'ior_ot_3', '-'],
|
|
|
+ ['ot_4', '-', 'ior_ot_4', '-'],
|
|
|
+ ['ot_5', '-', 'ior_ot_5', '-'],
|
|
|
+ ['ot_6', '-', 'ior_ot_6', '-'],
|
|
|
+ ['ot_7', '-', 'ior_ot_7', '-'],
|
|
|
+];
|
|
|
+
|
|
|
+const formatPsEvents = (events) => {
|
|
|
+ return PS_IOR_KEYS.map(([label, ...keys]) => {
|
|
|
+ const match = keys.map(key => ({
|
|
|
+ key,
|
|
|
+ value: events[key]?.v ?? 0,
|
|
|
+ origin: events[key]?.r
|
|
|
+ }));
|
|
|
+ return {
|
|
|
+ label,
|
|
|
+ match
|
|
|
+ };
|
|
|
+ })
|
|
|
+ // .filter(item => item.match.every(entry => entry.value !== 0))
|
|
|
+ .map(({label, match}) => [label, ...match]);
|
|
|
+}
|
|
|
+
|
|
|
+// const rivalIor = (ior) => {
|
|
|
+// const map = {
|
|
|
+// "ior_rh": "ior_rac",
|
|
|
+// "ior_rc": "ior_rah",
|
|
|
+// "ior_rac": "ior_rh",
|
|
|
+// "ior_rah": "ior_rc",
|
|
|
+// "ior_wmh": "ior_wmc",
|
|
|
+// "ior_wmc": "ior_wmh",
|
|
|
+// "ior_wmh_2": "ior_wmc_2",
|
|
|
+// "ior_wmc_2": "ior_wmh_2"
|
|
|
+// };
|
|
|
+// const iorInfos = ior.split('_');
|
|
|
+// const iorStart = iorInfos.slice(0, 2).join('_');
|
|
|
+// if (!map[iorStart]) {
|
|
|
+// return ior;
|
|
|
+// }
|
|
|
+// return `${map[iorStart]}_${iorInfos[2]}`;
|
|
|
+// }
|
|
|
+
|
|
|
+const formatEvents = (events, cprKeys) => {
|
|
|
+ const eventsMap = {};
|
|
|
+ Object.keys(events).forEach(key => {
|
|
|
+ const { type, side, ratio } = parseIorKey(key);
|
|
|
+ let ratioKey, index;
|
|
|
+ if (type === 'r') {
|
|
|
+ if (side === 'h') {
|
|
|
+ ratioKey = ratio;
|
|
|
+ index = 0;
|
|
|
+ }
|
|
|
+ else if (side === 'c') {
|
|
|
+ ratioKey = -ratio;
|
|
|
+ index = 2;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (type === 'm') {
|
|
|
+ ratioKey = 'm';
|
|
|
+ if (side == 'h') {
|
|
|
+ index = 0;
|
|
|
+ }
|
|
|
+ else if (side == 'c') {
|
|
|
+ index = 2;
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ index = 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (type === 'wm') {
|
|
|
+ ratioKey = `wm_${Math.abs(ratio)}`;
|
|
|
+ if (side === 'h') {
|
|
|
+ index = 0;
|
|
|
+ }
|
|
|
+ else if (side === 'c') {
|
|
|
+ index = 2;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (type === 'ou') {
|
|
|
+ ratioKey = `ou_${Math.abs(ratio)}`;
|
|
|
+ if (side === 'c') {
|
|
|
+ index = 0;
|
|
|
+ }
|
|
|
+ else if (side === 'h') {
|
|
|
+ index = 2;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // else if (type === 'os') {
|
|
|
+ // ratioKey = ratio;
|
|
|
+ // index = 1;
|
|
|
+ // }
|
|
|
+ else if (type === 'ot') {
|
|
|
+ ratioKey = `ot_${ratio}`;
|
|
|
+ index = 1;
|
|
|
+ // switch (ratio) {
|
|
|
+ // case '0':
|
|
|
+ // ratioKey = '0-1';
|
|
|
+ // index = 0;
|
|
|
+ // break;
|
|
|
+ // case '1':
|
|
|
+ // ratioKey = '0-1';
|
|
|
+ // index = 2;
|
|
|
+ // break;
|
|
|
+ // case '2':
|
|
|
+ // ratioKey = '2-3';
|
|
|
+ // index = 0;
|
|
|
+ // break;
|
|
|
+ // case '3':
|
|
|
+ // ratioKey = '2-3';
|
|
|
+ // index = 2;
|
|
|
+ // break;
|
|
|
+ // }
|
|
|
+ }
|
|
|
+ if (typeof (ratioKey) == 'number') {
|
|
|
+ if (ratioKey > 0) {
|
|
|
+ ratioKey = `+${ratioKey}`;
|
|
|
+ }
|
|
|
+ // else if (ratioKey === 0) {
|
|
|
+ // ratioKey = '-0';
|
|
|
+ // }
|
|
|
+ else {
|
|
|
+ ratioKey = `${ratioKey}`;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!ratioKey) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!eventsMap[ratioKey]) {
|
|
|
+ eventsMap[ratioKey] = new Array(3).fill(undefined);
|
|
|
+ }
|
|
|
+
|
|
|
+ const value = events[key]?.v ?? 0;
|
|
|
+ const origin = events[key]?.r;
|
|
|
+ eventsMap[ratioKey][index] = { key, value, origin };
|
|
|
+ });
|
|
|
+
|
|
|
+ return Object.keys(eventsMap).sort((a, b) => a.localeCompare(b)).map(key => {
|
|
|
+ return [key, ...eventsMap[key]];
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+const formatSolution = (solution, eventsList) => {
|
|
|
+ const { cpr, info } = solution;
|
|
|
+ if (!cpr || !info) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ const cprKeys = cpr.map(item => item.k);
|
|
|
+
|
|
|
+ const psEvents = eventsList.ps?.[info.ps.eventId] ?? {};
|
|
|
+ const obEvents = eventsList.ob?.[info.ob.eventId] ?? {};
|
|
|
+ const hgEvents = eventsList.hg?.[info.hg.eventId] ?? {};
|
|
|
+
|
|
|
+ info.ps.events = formatPsEvents(psEvents);
|
|
|
+ info.ob.events = formatEvents(obEvents, cprKeys);
|
|
|
+ info.hg.events = formatEvents(hgEvents, cprKeys);
|
|
|
+
|
|
|
+ info.ps.dateTime = dayjs(info.ps.timestamp).format('YYYY-MM-DD HH:mm:ss');
|
|
|
+ info.ob.dateTime = dayjs(info.ob.timestamp).format('YYYY-MM-DD HH:mm:ss');
|
|
|
+ info.hg.dateTime = dayjs(info.hg.timestamp).format('YYYY-MM-DD HH:mm:ss');
|
|
|
+
|
|
|
+ cpr.forEach(item => {
|
|
|
+ const { k, p } = item;
|
|
|
+ if (!info[p]['selected']) {
|
|
|
+ info[p]['selected'] = [];
|
|
|
+ }
|
|
|
+ info[p]['selected'].push(k);
|
|
|
+ });
|
|
|
+
|
|
|
+ return solution;
|
|
|
+}
|
|
|
+
|
|
|
+const updateSolutions = async (showLoading=false) => {
|
|
|
+ clearTimeout(loopTimer.value);
|
|
|
+ if (showLoading && !updateLoaderHide.value) {
|
|
|
+ updateLoaderHide.value = message.loading('数据加载中...', 0);
|
|
|
+ }
|
|
|
+ getSolutions()
|
|
|
+ .then(({ solutions: solutionsList, gamesEvents: eventsList, mkCount: mkCountData }) => {
|
|
|
+ solutions.value = solutionsList?.map(solution => formatSolution(solution, eventsList)) ?? [];
|
|
|
+ markCount.value = mkCountData;
|
|
|
+ })
|
|
|
+ .catch(error => {
|
|
|
+ console.error('Failed to update solutions:', error);
|
|
|
+ message.error('获取中单方案失败');
|
|
|
+ })
|
|
|
+ .finally(() => {
|
|
|
+ updateLoaderHide.value?.();
|
|
|
+ updateLoaderHide.value = null;
|
|
|
+ if (loopActive.value) {
|
|
|
+ loopTimer.value = setTimeout(() => {
|
|
|
+ updateSolutions();
|
|
|
+ }, 1000 * 10);
|
|
|
+ }
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+const showTotalProfit = async () => {
|
|
|
+ totalProfit.value = await calcTotalProfit();
|
|
|
+ totalProfitVisible.value = true;
|
|
|
+ const { profit } = totalProfit.value;
|
|
|
+ console.log('profit', profit);
|
|
|
+};
|
|
|
+
|
|
|
+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) {
|
|
|
+ selectedSolutions.splice(findIndex, 1);
|
|
|
+ }
|
|
|
+ else if (selectedSolutions.length < 2) {
|
|
|
+ selectedSolutions.push({ sid, timestamp });
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ selectedSolutions.splice(1, 1, { sid, timestamp });
|
|
|
+ }
|
|
|
+ if (selectedSolutions.length == 2) {
|
|
|
+ showTotalProfit();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const setLocalStorage = (key, value) => {
|
|
|
+ localStorage.setItem(key, JSON.stringify(value));
|
|
|
+}
|
|
|
+
|
|
|
+const getLocalStorage = (key) => {
|
|
|
+ const value = localStorage.getItem(key);
|
|
|
+ return value ? JSON.parse(value) : null;
|
|
|
+}
|
|
|
+
|
|
|
+watch(minProfitRate, (newVal) => {
|
|
|
+ clearTimeout(updateTimer.value);
|
|
|
+ updateTimer.value = setTimeout(() => {
|
|
|
+ setLocalStorage('minProfitRate', newVal);
|
|
|
+ updateSolutions();
|
|
|
+ }, 1000);
|
|
|
+});
|
|
|
+
|
|
|
+watch(marketType, (newVal) => {
|
|
|
+ if (!updateLoaderHide.value) {
|
|
|
+ updateLoaderHide.value = message.loading('数据更新中...', 0);
|
|
|
+ }
|
|
|
+ clearTimeout(updateTimer.value);
|
|
|
+ updateTimer.value = setTimeout(() => {
|
|
|
+ setLocalStorage('marketType', newVal);
|
|
|
+ updateSolutions();
|
|
|
+ }, 1000);
|
|
|
+});
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ loopActive.value = true;
|
|
|
+ const min_win_rate = getLocalStorage('minProfitRate');
|
|
|
+ const mk = getLocalStorage('marketType');
|
|
|
+ if (min_win_rate !== null) {
|
|
|
+ minProfitRate.value = min_win_rate;
|
|
|
+ }
|
|
|
+ if (mk !== null) {
|
|
|
+ marketType.value = mk;
|
|
|
+ }
|
|
|
+ setTimeout(() => {
|
|
|
+ updateSolutions(true);
|
|
|
+ }, 100);
|
|
|
+});
|
|
|
+
|
|
|
+onUnmounted(() => {
|
|
|
+ loopActive.value = false;
|
|
|
+});
|
|
|
+
|
|
|
+
|
|
|
+</script>
|
|
|
+
|
|
|
+<template>
|
|
|
+ <div class="solution-container">
|
|
|
+
|
|
|
+ <div class="contents-header transition-all duration-200" :style="headerStyle">
|
|
|
+ <div class="solution-options">
|
|
|
+ <Form layout="inline" class="sol-opt-container">
|
|
|
+ <Form.Item label="盘口类型" class="sol-opt-item">
|
|
|
+ <RadioGroup v-model:value="marketType">
|
|
|
+ <Radio :value="-1">全部({{ markCount.all ?? 0 }})</Radio>
|
|
|
+ <Radio :value="2">滚球({{ markCount.rollball ?? 0 }})</Radio>
|
|
|
+ <Radio :value="1">今日({{ markCount.today ?? 0 }})</Radio>
|
|
|
+ <Radio :value="0">早盘({{ markCount.early ?? 0 }})</Radio>
|
|
|
+ </RadioGroup>
|
|
|
+ </Form.Item>
|
|
|
+ <Form.Item label="最小利润率(%)" class="sol-opt-item">
|
|
|
+ <InputNumber style="width: 60px" size="small" max="100" min="-100" step="0.1" placeholder="最小利润率(%)" v-model:value="minProfitRate"/>
|
|
|
+ </Form.Item>
|
|
|
+ </Form>
|
|
|
+ </div>
|
|
|
+ <div class="solution-header">
|
|
|
+ <span>PS</span>
|
|
|
+ <span>OB</span>
|
|
|
+ <span>HG</span>
|
|
|
+ <em>利润</em>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="solution-list">
|
|
|
+ <div class="solution-item"
|
|
|
+ v-for="{ sid, sol: { win_average_rate, win_profit_rate, cross_type }, info: { ps, ob, hg }, selected, disabled } in solutionsList" :key="sid"
|
|
|
+ :class="{ 'selected': selected, 'disabled': disabled }">
|
|
|
+ <MatchCard platform="ps" :eventId="ps.eventId" :leagueName="ps.leagueName" :teamHomeName="ps.teamHomeName"
|
|
|
+ :teamAwayName="ps.teamAwayName" :dateTime="ps.dateTime" :eventInfo="ps.eventInfo" :events="ps.events ?? []"
|
|
|
+ :matchNumStr="ps.matchNumStr" :selected="ps.selected ?? []" :stage="ps.stage" :retime="ps.retime" :score="ps.score" />
|
|
|
+
|
|
|
+ <MatchCard platform="ob" :eventId="ob.eventId" :leagueName="ob.leagueName" :teamHomeName="ob.teamHomeName"
|
|
|
+ :teamAwayName="ob.teamAwayName" :dateTime="ob.dateTime" :events="ob.events ?? []"
|
|
|
+ :selected="ob.selected ?? []" :stage="ob.stage" :retime="ob.retime" :score="ob.score" />
|
|
|
+
|
|
|
+ <MatchCard platform="hg" :eventId="hg.eventId" :leagueName="hg.leagueName" :teamHomeName="hg.teamHomeName"
|
|
|
+ :teamAwayName="hg.teamAwayName" :dateTime="hg.dateTime" :events="hg.events ?? []"
|
|
|
+ :selected="hg.selected ?? []" :stage="hg.stage" :retime="hg.retime" :score="hg.score" />
|
|
|
+
|
|
|
+ <div class="solution-profit" @click="!disabled && toggleSolution(sid, ps.timestamp)">
|
|
|
+ <p>{{ win_average_rate }}%</p>
|
|
|
+ <p>{{ win_profit_rate }}%</p>
|
|
|
+ <p>{{ cross_type }}</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="list-empty" v-if="!solutionsList.length">暂无数据</div>
|
|
|
+
|
|
|
+ <!-- <Drawer
|
|
|
+ title="综合利润方案"
|
|
|
+ placement="bottom"
|
|
|
+ height="600"
|
|
|
+ :visible="totalProfitVisible"
|
|
|
+ @close="closeTotalProfit"
|
|
|
+ >
|
|
|
+ <div class="solution-total-profit" v-if="totalProfitValue.solutions.length">
|
|
|
+ <div class="solution-item"
|
|
|
+ v-for="{ sid, info: { ps, ob, hg } } in totalProfitValue.solutions" :key="sid">
|
|
|
+ <MatchCard platform="ps" :eventId="ps.eventId" :leagueName="ps.leagueName" :teamHomeName="ps.teamHomeName"
|
|
|
+ :teamAwayName="ps.teamAwayName" :dateTime="ps.dateTime" :eventInfo="ps.eventInfo" :events="ps.events ?? []"
|
|
|
+ :matchNumStr="ps.matchNumStr" :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 ?? []" />
|
|
|
+
|
|
|
+ <MatchCard platform="hg" :eventId="hg.eventId" :leagueName="hg.leagueName" :teamHomeName="hg.teamHomeName"
|
|
|
+ :teamAwayName="hg.teamAwayName" :dateTime="hg.dateTime" :events="hg.events ?? []"
|
|
|
+ :selected="hg.selected ?? []" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="profit-info">
|
|
|
+ <table>
|
|
|
+ <tr>
|
|
|
+ <th></th>
|
|
|
+ <td>PS</td>
|
|
|
+ <td colspan="2">第一场</td>
|
|
|
+ <td colspan="2">第二场</td>
|
|
|
+ </tr>
|
|
|
+ <tr>
|
|
|
+ <th>赔率</th>
|
|
|
+ <td>{{ totalProfitValue.psInfo[0]?.v }}: {{ totalProfitValue.psInfo[1]?.v }}</td>
|
|
|
+ <td>{{ totalProfitValue.outPreSol[0]?.p }}: {{ totalProfitValue.outPreSol[0]?.v }}</td>
|
|
|
+ <td>{{ totalProfitValue.outPreSol[1]?.p }}: {{ totalProfitValue.outPreSol[1]?.v }}</td>
|
|
|
+ <td>{{ totalProfitValue.outSubSol[0]?.p }}: {{ totalProfitValue.outSubSol[0]?.v }}</td>
|
|
|
+ <td>{{ totalProfitValue.outSubSol[1]?.p }}: {{ totalProfitValue.outSubSol[1]?.v }}</td>
|
|
|
+ </tr>
|
|
|
+ <tr>
|
|
|
+ <th>下注</th>
|
|
|
+ <td>{{ psOptions.bet }}</td>
|
|
|
+ <td>{{ totalProfitValue.outPreSol[0]?.g }}</td>
|
|
|
+ <td>{{ totalProfitValue.outPreSol[1]?.g }}</td>
|
|
|
+ <td>{{ totalProfitValue.outSubSol[0]?.g }}</td>
|
|
|
+ <td>{{ totalProfitValue.outSubSol[1]?.g }}</td>
|
|
|
+ </tr>
|
|
|
+ <tr>
|
|
|
+ <th>利润</th>
|
|
|
+ <td>{{ totalProfitValue.profit.win_ps }}</td>
|
|
|
+ <td colspan="2">{{ totalProfitValue.profit.win_target }}</td>
|
|
|
+ <td colspan="2">{{ totalProfitValue.profit.win_target }}</td>
|
|
|
+ </tr>
|
|
|
+ </table>
|
|
|
+ </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));
|
|
|
+}
|
|
|
+
|
|
|
+.sol-opt-container {
|
|
|
+ flex-grow: 1;
|
|
|
+ justify-content: space-between;
|
|
|
+}
|
|
|
+
|
|
|
+.sol-opt-item:last-child {
|
|
|
+ margin-inline-end: 0 !important;
|
|
|
+}
|
|
|
+
|
|
|
+.solution-header {
|
|
|
+ position: relative;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ height: 30px;
|
|
|
+ padding: 0 20px;
|
|
|
+ span,
|
|
|
+ em {
|
|
|
+ display: block;
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ span {
|
|
|
+ flex: 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ em {
|
|
|
+ width: 80px;
|
|
|
+ font-style: normal;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.solution-container {
|
|
|
+ padding-top: 74px;
|
|
|
+}
|
|
|
+
|
|
|
+.solution-item {
|
|
|
+ display: flex;
|
|
|
+ .match-card {
|
|
|
+ flex: 1;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.solution-list {
|
|
|
+ padding: 20px;
|
|
|
+ overflow: hidden;
|
|
|
+
|
|
|
+ .solution-item {
|
|
|
+ border-radius: 10px;
|
|
|
+ background-color: hsl(var(--card));
|
|
|
+
|
|
|
+ &.selected {
|
|
|
+ background-color: hsl(var(--primary) / 0.15);
|
|
|
+ }
|
|
|
+
|
|
|
+ &.disabled {
|
|
|
+ opacity: 0.5;
|
|
|
+ cursor: not-allowed;
|
|
|
+ }
|
|
|
+
|
|
|
+ &:not(:last-child) {
|
|
|
+ margin-bottom: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .match-card {
|
|
|
+ border-right: 1px solid hsl(var(--border));
|
|
|
+ }
|
|
|
+
|
|
|
+ .solution-profit {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ width: 80px;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.profit-info {
|
|
|
+ table {
|
|
|
+ width: 100%;
|
|
|
+ border-collapse: collapse;
|
|
|
+ border-spacing: 0;
|
|
|
+ table-layout: fixed;
|
|
|
+ th, td {
|
|
|
+ height: 30px;
|
|
|
+ border: 1px solid hsl(var(--border));
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+ th {
|
|
|
+ width: 64px;
|
|
|
+ font-weight: normal;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.list-empty {
|
|
|
+ text-align: center;
|
|
|
+ padding: 10px;
|
|
|
+ font-size: 18px;
|
|
|
+ color: hsl(var(--foreground) / 0.7);
|
|
|
+}
|
|
|
+</style>
|