flyzto hace 7 meses
padre
commit
d56c2e8c76

+ 0 - 1
web/apps/web-antd/src/locales/langs/en-US/page.json

@@ -8,7 +8,6 @@
   },
   "dashboard": {
     "title": "Dashboard",
-    "analytics": "Analytics",
     "workspace": "Workspace"
   },
   "match": {

+ 0 - 1
web/apps/web-antd/src/locales/langs/zh-CN/page.json

@@ -8,7 +8,6 @@
   },
   "dashboard": {
     "title": "概览",
-    "analytics": "分析页",
     "workspace": "工作台"
   },
   "match": {

+ 0 - 10
web/apps/web-antd/src/router/routes/modules/dashboard.ts

@@ -12,16 +12,6 @@ const routes: RouteRecordRaw[] = [
     name: 'Dashboard',
     path: '/dashboard',
     children: [
-      {
-        name: 'Analytics',
-        path: '/analytics',
-        component: () => import('#/views/dashboard/analytics/index.vue'),
-        meta: {
-          affixTab: true,
-          icon: 'lucide:area-chart',
-          title: $t('page.dashboard.analytics'),
-        },
-      },
       {
         name: 'Workspace',
         path: '/workspace',

+ 0 - 82
web/apps/web-antd/src/views/dashboard/analytics/analytics-visits-data.vue

@@ -1,82 +0,0 @@
-<script lang="ts" setup>
-import type { EchartsUIType } from '@vben/plugins/echarts';
-
-import { onMounted, ref } from 'vue';
-
-import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
-
-const chartRef = ref<EchartsUIType>();
-const { renderEcharts } = useEcharts(chartRef);
-
-onMounted(() => {
-  renderEcharts({
-    legend: {
-      bottom: 0,
-      data: ['访问', '趋势'],
-    },
-    radar: {
-      indicator: [
-        {
-          name: '网页',
-        },
-        {
-          name: '移动端',
-        },
-        {
-          name: 'Ipad',
-        },
-        {
-          name: '客户端',
-        },
-        {
-          name: '第三方',
-        },
-        {
-          name: '其它',
-        },
-      ],
-      radius: '60%',
-      splitNumber: 8,
-    },
-    series: [
-      {
-        areaStyle: {
-          opacity: 1,
-          shadowBlur: 0,
-          shadowColor: 'rgba(0,0,0,.2)',
-          shadowOffsetX: 0,
-          shadowOffsetY: 10,
-        },
-        data: [
-          {
-            itemStyle: {
-              color: '#b6a2de',
-            },
-            name: '访问',
-            value: [90, 50, 86, 40, 50, 20],
-          },
-          {
-            itemStyle: {
-              color: '#5ab1ef',
-            },
-            name: '趋势',
-            value: [70, 75, 70, 76, 20, 85],
-          },
-        ],
-        itemStyle: {
-          // borderColor: '#fff',
-          borderRadius: 10,
-          borderWidth: 2,
-        },
-        symbolSize: 0,
-        type: 'radar',
-      },
-    ],
-    tooltip: {},
-  });
-});
-</script>
-
-<template>
-  <EchartsUI ref="chartRef" />
-</template>

+ 0 - 90
web/apps/web-antd/src/views/dashboard/analytics/index.vue

@@ -1,90 +0,0 @@
-<script lang="ts" setup>
-import type { AnalysisOverviewItem } from '@vben/common-ui';
-import type { TabOption } from '@vben/types';
-
-import {
-  AnalysisChartCard,
-  AnalysisChartsTabs,
-  AnalysisOverview,
-} from '@vben/common-ui';
-import {
-  SvgBellIcon,
-  SvgCakeIcon,
-  SvgCardIcon,
-  SvgDownloadIcon,
-} from '@vben/icons';
-
-import AnalyticsTrends from './analytics-trends.vue';
-import AnalyticsVisitsData from './analytics-visits-data.vue';
-import AnalyticsVisitsSales from './analytics-visits-sales.vue';
-import AnalyticsVisitsSource from './analytics-visits-source.vue';
-import AnalyticsVisits from './analytics-visits.vue';
-
-const overviewItems: AnalysisOverviewItem[] = [
-  {
-    icon: SvgCardIcon,
-    title: '用户量',
-    totalTitle: '总用户量',
-    totalValue: 120_000,
-    value: 2000,
-  },
-  {
-    icon: SvgCakeIcon,
-    title: '访问量',
-    totalTitle: '总访问量',
-    totalValue: 500_000,
-    value: 20_000,
-  },
-  {
-    icon: SvgDownloadIcon,
-    title: '下载量',
-    totalTitle: '总下载量',
-    totalValue: 120_000,
-    value: 8000,
-  },
-  {
-    icon: SvgBellIcon,
-    title: '使用量',
-    totalTitle: '总使用量',
-    totalValue: 50_000,
-    value: 5000,
-  },
-];
-
-const chartTabs: TabOption[] = [
-  {
-    label: '流量趋势',
-    value: 'trends',
-  },
-  {
-    label: '月访问量',
-    value: 'visits',
-  },
-];
-</script>
-
-<template>
-  <div class="p-5">
-    <AnalysisOverview :items="overviewItems" />
-    <AnalysisChartsTabs :tabs="chartTabs" class="mt-5">
-      <template #trends>
-        <AnalyticsTrends />
-      </template>
-      <template #visits>
-        <AnalyticsVisits />
-      </template>
-    </AnalysisChartsTabs>
-
-    <div class="mt-5 w-full md:flex">
-      <AnalysisChartCard class="mt-5 md:mr-4 md:mt-0 md:w-1/3" title="访问数量">
-        <AnalyticsVisitsData />
-      </AnalysisChartCard>
-      <AnalysisChartCard class="mt-5 md:mr-4 md:mt-0 md:w-1/3" title="访问来源">
-        <AnalyticsVisitsSource />
-      </AnalysisChartCard>
-      <AnalysisChartCard class="mt-5 md:mt-0 md:w-1/3" title="访问来源">
-        <AnalyticsVisitsSales />
-      </AnalysisChartCard>
-    </div>
-  </div>
-</template>

+ 106 - 5
web/apps/web-antd/src/views/match/components/match_card.vue

@@ -1,4 +1,14 @@
 <script setup>
+const parseEventKey = (key) => {
+  if (key == 'm') {
+    return '独赢';
+  }
+  else if (key?.startsWith('wm')) {
+    const ratio = key.split('_')[1];
+    return `净胜${ratio}`;
+  }
+  return key;
+}
 defineProps({
   eventId: {
     type: Number,
@@ -24,12 +34,12 @@ defineProps({
     type: String,
     required: true
   },
-  eventInfo: {
-    type: Object,
+  events: {
+    type: Array,
     required: true
   },
-  solutionKey: {
-    type: String,
+  selected: {
+    type: Array,
     required: true
   }
 })
@@ -37,7 +47,7 @@ defineProps({
 
 <template>
   <div class="match-card">
-    <div class="match-card-header">
+    <div class="card-header">
       <div class="league-name">{{ leagueName }}</div>
       <div class="date-time">{{ dateTime }}</div>
     </div>
@@ -46,6 +56,16 @@ defineProps({
       <em>VS</em>
       <span class="away-name">{{ teamAwayName }}</span>
     </div>
+    <div class="events-list" :class="{'list-row2': platform == 'jc'}">
+      <table>
+        <tr v-for="item in events">
+          <th>{{ parseEventKey(item[0]) }}</th>
+          <td><span :class="{'selected': selected.includes(item[1].key)}">{{ item[1].value ? item[1].value : '-' }}</span></td>
+          <td><span :class="{'selected': selected.includes(item[2].key)}">{{ item[2].value ? item[2].value : '-' }}</span></td>
+          <td><span :class="{'selected': selected.includes(item[3].key)}">{{ item[3].value ? item[3].value : '-' }}</span></td>
+        </tr>
+      </table>
+    </div>
   </div>
 </template>
 
@@ -53,5 +73,86 @@ defineProps({
 .match-card {
   display: flex;
   flex-direction: column;
+  padding: 20px;
+}
+.card-header {
+  display: flex;
+  height: 30px;
+  align-items: center;
+  justify-content: space-between;
+  .league-name {
+    font-size: 16px;
+  }
+  .date-time {
+    font-size: 12px;
+    color: hsl(var(--foreground) / 0.7);
+  }
+}
+.team-name {
+  display: flex;
+  align-items: center;
+  font-size: 14px;
+  span, em {
+    display: block;
+  }
+  span {
+    flex: 1;
+    &:first-child {
+      text-align: left;
+    }
+    &:last-child {
+      text-align: right;
+    }
+  }
+  em {
+    font-style: normal;
+    color: #ff9d4a;
+  }
+  .home-name {
+    color: hsl(var(--primary));
+  }
+  .away-name {
+    color: hsl(var(--destructive));
+  }
+}
+.events-list {
+  margin-top: 10px;
+  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;
+    }
+    td {
+      width: calc((100% - 64px) / 2);
+    }
+    span {
+      display: inline-block;
+      height: 20px;
+      line-height: 20px;
+      vertical-align: middle;
+      padding: 0 5px;
+      &.selected {
+        border-radius: 4px;
+        background-color: hsl(var(--primary));
+        color: hsl(var(--primary-foreground));
+      }
+    }
+  }
+  &.list-row2 {
+    table {
+      th, td {
+        height: 45px;
+      }
+    }
+  }
 }
 </style>

+ 4 - 24
web/apps/web-antd/src/views/match/components/match_item.vue

@@ -61,14 +61,13 @@ export default {
   flex-direction: column;
   padding: 10px 15px;
   &.selected {
-    background-color: #e9f3fe;
+    background-color: hsl(var(--primary) / 0.15);
   }
 }
 
 .match-league {
   font-size: 16px;
   font-weight: 500;
-  color: #333;
   margin-bottom: 4px;
 }
 
@@ -76,35 +75,16 @@ export default {
   font-size: 14px;
   line-height: 20px;
   &.team-home {
-    color: #0958d9;
+    color: hsl(var(--primary));
   }
   &.team-away {
-    color: #f5222d;
+    color: hsl(var(--destructive));
   }
 }
 
 .match-time {
   font-size: 12px;
-  color: #888;
+  color: hsl(var(--foreground) / 0.7);
   margin-top: 4px;
 }
-
-.dark {
-  .match-item {
-    &.selected {
-      background-color: #172b41;
-    }
-  }
-  .match-league {
-    color: #fff;
-  }
-  .match-team {
-    &.team-home {
-      color: #5797ff;
-    }
-    &.team-away {
-      color: #ff626a;
-    }
-  }
-}
 </style>

+ 19 - 67
web/apps/web-antd/src/views/match/related/index.vue

@@ -253,7 +253,7 @@ onMounted(() => {
 
         <div class="match-action">
           <Button type="link" class="action-btn" @click="removeGamesRelation(id)">
-            <i class="action-icon delete-icon"></i>
+            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg>
           </Button>
         </div>
       </div>
@@ -291,7 +291,7 @@ onMounted(() => {
       </div>
       <div class="match-action">
         <Button type="link" class="action-btn" v-if="showRelationButton" @click="setGamesRelation">
-          <i class="action-icon link-icon"></i>
+          <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
         </Button>
       </div>
     </div>
@@ -306,9 +306,9 @@ onMounted(() => {
 .top-panel {
   display: flex;
   margin-bottom: 5px;
-  border: 1px solid #e8e8e8;
+  border: 1px solid hsl(var(--border));
   border-radius: 6px;
-  background-color: #fff;
+  background-color: hsl(var(--card));
   span, i {
     display: block;
   }
@@ -316,7 +316,7 @@ onMounted(() => {
     flex: 1;
     text-align: center;
     &:not(:last-child) {
-      border-right: 1px solid #e8e8e8;
+      border-right: 1px solid hsl(var(--border));
     }
   }
   i {
@@ -328,9 +328,9 @@ onMounted(() => {
 
 .match-list {
   margin-bottom: 5px;
-  border: 1px solid #e8e8e8;
+  border: 1px solid hsl(var(--border));
   border-radius: 6px;
-  background-color: #fff;
+  background-color: hsl(var(--card));
   overflow: hidden;
   &.col-list {
     display: flex;
@@ -341,11 +341,11 @@ onMounted(() => {
   display: flex;
   flex-wrap: wrap;
   &:not(:last-child) {
-    border-bottom: 1px solid #e8e8e8;
+    border-bottom: 1px solid hsl(var(--border));
   }
   .match-item {
     &:not(:last-child) {
-      border-right: 1px solid #e8e8e8;
+      border-right: 1px solid hsl(var(--border));
     }
   }
 }
@@ -353,10 +353,10 @@ onMounted(() => {
 .match-col {
   flex: 1;
   &:not(:last-child) {
-    border-right: 1px solid #e8e8e8;
+    border-right: 1px solid hsl(var(--border));
   }
   .match-item {
-    border-bottom: 1px solid #e8e8e8;
+    border-bottom: 1px solid hsl(var(--border));
     &:last-child {
       margin-bottom: -1px;
     }
@@ -380,31 +380,18 @@ onMounted(() => {
   display: flex;
   align-items: center;
   justify-content: center;
+  color: hsl(var(--foreground) / 0.7);
+  &:hover {
+    color: hsl(var(--foreground));
+  }
   .col-list & {
     position: relative;
     top: 40px;
     align-self: flex-start;
   }
-}
-
-.action-icon {
-  display: inline-block;
-  width: 16px;
-  height: 16px;
-  background-size: cover;
-}
-
-.link-icon {
-  background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="%23333" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>');
-  .dark & {
-    background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="%23fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>');
-  }
-}
-
-.delete-icon {
-  background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="%23333" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg>');
-  .dark & {
-    background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="%23fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg>');
+  svg {
+    width: 16px;
+    height: 16px;
   }
 }
 
@@ -412,42 +399,7 @@ onMounted(() => {
   text-align: center;
   padding: 10px;
   font-size: 18px;
-  color: #999;
-}
-
-.dark {
-  .top-panel {
-    border-color: #2f2f33;
-    background-color: #1a1c1f;
-    span {
-      color: #fff;
-      border-color: #2f2f33;
-    }
-  }
-  .match-list {
-    border-color: #2f2f33;
-    background-color: #1a1c1f;
-  }
-  .match-row {
-    border-color: #2f2f33;
-    .match-item {
-      border-color: #2f2f33;
-    }
-  }
-  .match-col {
-    border-color: #2f2f33;
-    .match-item {
-      border-color: #2f2f33;
-    }
-  }
-
-  .action-btn {
-    .action-icon {
-      filter: brightness(0.5);
-    }
-  }
-
-
+  color: hsl(var(--foreground) / 0.7);
 }
 
 @media (max-width: 768px) {

+ 219 - 43
web/apps/web-antd/src/views/match/solutions/index.vue

@@ -2,7 +2,7 @@
 import { Page } from '@vben/common-ui';
 import { requestClient } from '#/api/request';
 import { Button, message } from 'ant-design-vue';
-import { ref, reactive, computed, onMounted, useTemplateRef } from 'vue';
+import { ref, reactive, computed, onMounted, onUnmounted, useTemplateRef } from 'vue';
 import dayjs from 'dayjs';
 
 import MatchCard from '../components/match_card.vue';
@@ -34,13 +34,143 @@ const getGamesEvents = async () => {
 }
 
 
-const moveToFront = (arr, index) => {
-  if (index <= 0 || index >= arr.length) {
-    return arr;
+// const moveToFront = (arr, index) => {
+//   if (index <= 0 || index >= arr.length) {
+//     return arr;
+//   }
+//   const item = arr.splice(index, 1)[0];
+//   arr.unshift(item);
+//   return arr;
+// }
+
+const parseIorKey = (iorKey) => {
+  const [, type, accept, side, , ratioString] = iorKey.match(/^ior_(r|ou|m|wm|t)(a?)(h|c|n)?(_(\d+))?$/);
+  const ratio = ratioString ? `${ratioString[0]}.${ratioString.slice(1)}` * (accept ? 1 : -1) : 0;
+  return { type, side, ratio };
+}
+
+const formatJcEvents = (events) => {
+  const eventsMap = {};
+  Object.keys(events).forEach(key => {
+    const { type, side, ratio } = parseIorKey(key);
+    let ratioKey, index;
+    if (type === 'r') {
+      if (side === 'h') {
+        ratioKey = ratio + 0.5;
+        index = 0;
+      }
+      else if (side === 'c') {
+        ratioKey = -ratio - 0.5;
+        index = 2;
+      }
+    }
+    else if (type === 'm') {
+      ratioKey = 0;
+      index = 1;
+    }
+    else if (type === 'wm') {
+      if (side === 'h') {
+        ratioKey = ratio;
+        index = 1;
+      }
+      else if (side === 'c') {
+        ratioKey = -ratio;
+        index = 1;
+      }
+    }
+
+    ratioKey = Math.round(ratioKey);
+
+    if (ratioKey > 0) {
+      ratioKey = `+${ratioKey}`;
+    }
+    else {
+      ratioKey = `${ratioKey}`;
+    }
+
+    if (!eventsMap[ratioKey]) {
+      eventsMap[ratioKey] = [];
+    }
+
+    const value = events[key] ?? 0;
+
+    eventsMap[ratioKey][index] = { key, value };
+
+  });
+  return Object.keys(eventsMap).sort((a, b) =>  b.localeCompare(a)).map(key => {
+    return [key, ...eventsMap[key]];
+  });
+}
+
+const rivalIor = (ior) => {
+  const map = {
+    "ior_rh": "ior_rac",
+    "ior_rc": "ior_rah",
+    "ior_rac": "ior_rh",
+    "ior_rah": "ior_rc",
+    "ior_ouc": "ior_ouh",
+    "ior_ouh": "ior_ouc",
+  };
+  const iorInfos = ior.split('_');
+  const iorStart = iorInfos.slice(0, 2).join('_');
+  if (!map[iorStart]) {
+    return ior;
   }
-  const item = arr.splice(index, 1)[0];
-  arr.unshift(item);
-  return arr;
+  return `${map[iorStart]}_${iorInfos[2]}`;
+}
+
+const formatEvents = (events, cprKeys) => {
+  const eventsMap = {};
+  cprKeys.forEach(key => {
+    const { type, side, ratio } = parseIorKey(key);
+    let ratioKey, values = [];
+    if (type === 'r') {
+      if (side === 'h') {
+        ratioKey = ratio;
+        values[0] = { key, value: events[key] ?? 0 };
+        values[2] = { key: rivalIor(key), value: events[rivalIor(key)] ?? 0 };
+      }
+      else if (side === 'c') {
+        ratioKey = -ratio;
+        values[0] = { key: rivalIor(key), value: events[rivalIor(key)] ?? 0 };
+        values[2] = { key, value: events[key] ?? 0 };
+      }
+      values[1] = { key: '', value: 0 };
+    }
+    else if (type === 'm') {
+      ratioKey = 'm';
+      values[0] = { key: 'ior_rh_05', value: events['ior_rh_05'] ?? 0 };
+      values[1] = { key, value: events[key] ?? 0 };
+      values[2] = { key: 'ior_rc_05', value: events['ior_rc_05'] ?? 0 };
+    }
+    else if (type === 'wm') {
+      ratioKey = `wm_${Math.abs(ratio)}`;
+      values[0] = { key: '', value: 0 };
+      values[1] = { key: '', value: 0 };
+      values[2] = { key: '', value: 0 };
+      if (side === 'h') {
+        values[0] = { key, value: events[key] ?? 0 };
+      }
+      else if (side === 'c') {
+        values[2] = { key, value: events[key] ?? 0 }  ;
+      }
+    }
+    if (typeof(ratioKey) == 'number') {
+      if (ratioKey > 0) {
+        ratioKey = `+${ratioKey}`;
+      }
+      else if (ratioKey === 0) {
+        ratioKey = '-0';
+      }
+      else {
+        ratioKey = `${ratioKey}`;
+      }
+    }
+    eventsMap[ratioKey] = values;
+  });
+  return Object.keys(eventsMap).sort((a, b) =>  a.localeCompare(b)).map(key => {
+    return [key, ...eventsMap[key]];
+  });
 }
 
 const updateSolutions = async () => {
@@ -48,42 +178,43 @@ const updateSolutions = async () => {
   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);
-    const jcEvents = eventsList.jc[info.jc.eventId];
-    const psEvents = eventsList.ps[info.ps.eventId];
-    const obEvents = eventsList.ob[info.ob.eventId];
 
+    info.jc.events = formatJcEvents(eventsList.jc[info.jc.eventId]);
+    info.ps.events = formatEvents(eventsList.ps[info.ps.eventId], cprKeys);
+    info.ob.events = formatEvents(eventsList.ob[info.ob.eventId], cprKeys);
+
+    info.jc.dateTime = dayjs(info.jc.timestamp).format('YYYY-MM-DD HH:mm:ss');
+    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');
 
+    cpr.forEach(item => {
+      const { k, p } = item;
+      if (!info[p]['selected']) {
+        info[p]['selected'] = [];
+      }
+      info[p]['selected'].push(k);
+    });
 
-    cpr[0].g = gold_side_a;
-    cpr[0].w = win_side_a;
-    cpr[1].g = gold_side_b;
-    cpr[1].w = win_side_b;
-    cpr[2].g = gold_side_m;
-    cpr[2].w = win_side_m;
-    const newCpr = moveToFront(cpr, jc_index);
-    return { ...item, cpr: newCpr, sol: { cross_type, jc_index, win_average }, events };
+    return item;
   });
 }
 
-const updateGamesEvents = async () => {
-  const events = await getGamesEvents();
-  gamesEvents.value = events;
-}
+let updateTimer = null;
 
 onMounted(() => {
   updateSolutions();
+  updateTimer = setInterval(() => {
+    updateSolutions();
+  }, 1000 * 10);
 });
 
-</script>
+onUnmounted(() => {
+  console.log('unmounted');
+  clearInterval(updateTimer);
+});
 
-<!-- eventId
-platform
-leagueName
-teamHomeName
-teamAwayName
-dateTime
-eventInfo
-solutionKey -->
+
+</script>
 
 <template>
   <div class="solution-container">
@@ -94,27 +225,50 @@ solutionKey -->
       <em>利润</em>
     </div>
     <div class="solution-list">
-      <!-- <div class="solution-item" v-for="solution in solutions" :key="solution.sid">
-        <MatchCard />
-        <MatchCard />
-        <MatchCard />
-        <div class="solution-profit"></div>
-      </div> -->
+      <div class="solution-item" v-for="{ sid, sol: {win_average}, info: {jc, ps, ob} } in solutions" :key="sid">
+        <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">{{ win_average }}</div>
+      </div>
     </div>
   </div>
 </template>
 
 <style lang="scss" scoped>
-.solution-container {
-  height: 100%;
-  display: flex;
-  flex-direction: column;
-}
 .solution-header {
+  position: relative;
+  z-index: 11;
   display: flex;
   align-items: center;
   height: 40px;
-  background: #fff;
+  padding: 0 20px;
+  background-color: hsl(var(--background));
   box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
   span, em {
     display: block;
@@ -128,4 +282,26 @@ solutionKey -->
     font-style: normal;
   }
 }
+.solution-list {
+  padding: 20px;
+  overflow: hidden;
+}
+.solution-item {
+  display: flex;
+  border-radius: 10px;
+  background-color: hsl(var(--card));
+  &:not(:last-child) {
+    margin-bottom: 20px;
+  }
+  .match-card {
+    flex: 1;
+    border-right: 1px solid hsl(var(--border));
+  }
+  .solution-profit {
+    display: flex;
+    width: 80px;
+    align-items: center;
+    justify-content: center;
+  }
+}
 </style>

+ 1 - 1
web/packages/@core/preferences/__tests__/__snapshots__/config.test.ts.snap

@@ -11,7 +11,7 @@ exports[`defaultPreferences immutability test > should not modify the config obj
     "compact": false,
     "contentCompact": "wide",
     "defaultAvatar": "https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp",
-    "defaultHomePath": "/analytics",
+    "defaultHomePath": "/workspace",
     "dynamicTitle": true,
     "enableCheckUpdates": true,
     "enablePreferences": true,

+ 1 - 1
web/packages/@core/preferences/src/config.ts

@@ -11,7 +11,7 @@ const defaultPreferences: Preferences = {
     contentCompact: 'wide',
     defaultAvatar:
       'https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp',
-    defaultHomePath: '/analytics',
+    defaultHomePath: '/workspace',
     dynamicTitle: true,
     enableCheckUpdates: true,
     enablePreferences: true,