#2 支持上半场

Terbuka
victor ingin menggabungkan 12 komit dari Front/master menjadi Front/dev

+ 2 - 2
pinnacle/main.js

@@ -38,8 +38,8 @@ const resetVersionsCount = () => {
 }
 
 const incrementVersionsCount = () => {
-  GLOBAL_DATA.straightFixturesCount = 0;
-  GLOBAL_DATA.specialFixturesCount = 0;
+  GLOBAL_DATA.straightFixturesCount += 1;
+  GLOBAL_DATA.specialFixturesCount += 1;
 }
 
 

+ 3 - 4
server/init.js

@@ -21,10 +21,9 @@ const Logs = require('./libs/logs');
     hgMaxDiff: 0,
     pcRebateRatio: 0,
     pcRebateType: 0,
-    subsidyTime: 0,
-    subsidyAmount: 0,
-    subsidyRbWmAmount: 0,
-    subsidyRbOtAmount: 0,
+    innerRouSubsidyValue: 0,
+    innerWmSubsidyRatio: 0,
+    innerOtSubsidyRatio: 0,
     halfTimeActiveTime: 0,
     expireTimeEvents: 45000,
     expireTimeSpecial: 60000,

+ 39 - 34
server/models/GamesPs.js

@@ -291,8 +291,7 @@ const syncBaseEvents = ({ mk, games, outrights }) => {
 
   const {
     expireTimeEvents, expireTimeSpecial,
-    subsidyTime, subsidyAmount,
-    subsidyRbWmAmount, subsidyRbOtAmount,
+    innerRouSubsidyValue, innerWmSubsidyRatio, innerOtSubsidyRatio,
     innerOuMinValue,
   } = getSetting();
   const nowTime = Date.now();
@@ -309,29 +308,33 @@ const syncBaseEvents = ({ mk, games, outrights }) => {
     const { eventId, originId, stage, retime, score, wm, evtime, events } = game;
     const baseGame = baseMap.get(eventId);
     if (baseGame) {
-      const { mk, timestamp } = baseGame;
-      const isRb = (mk == 2);
-      const isSubsidy = timestamp > nowTime && timestamp < nowTime + 1000*60*60*subsidyTime;
       Object.keys(events).forEach(ior => {
-        // 滚球胜平负补水
-        if (isRb && (ior.startsWith('ior_r') || ior.startsWith('ior_m') || ior.startsWith('ior_wm')) && subsidyRbWmAmount) {
+        const regRm = /^ior_(r|mh|mc)/;
+        const regWm = /^ior_(wm|mn)/;
+        const regOu = /^ior_ou/;
+        const regOt = /^ior_ot/;
+        // 胜负/让胜负/大小球调水(数值)
+        if ((regRm.test(ior) || regOu.test(ior)) && innerRouSubsidyValue) {
           const sourceOdds = events[ior].v;
-          events[ior].v = fixFloat(sourceOdds * (1 + subsidyRbWmAmount), 3);
-          events[ior].s = sourceOdds
+          events[ior].v = fixFloat(sourceOdds + innerRouSubsidyValue, 3);
+          events[ior].s = sourceOdds;
         }
-        // 滚球进球数补水
-        // API采集特殊盘口用
-        else if (isRb && ior.startsWith('ior_ot') && subsidyRbOtAmount) {
+        // 平局/让平调水(%)
+        else if (regWm.test(ior) && innerWmSubsidyRatio) {
           const sourceOdds = events[ior].v;
-          events[ior].v = fixFloat(sourceOdds * (1 + subsidyRbOtAmount), 3);
-          events[ior].s = sourceOdds
+          events[ior].v = fixFloat(sourceOdds * (1 + innerWmSubsidyRatio / 100), 3);
+          events[ior].s = sourceOdds;
         }
-        // 赛前进球数补水
-        // API采集特殊盘口用
-        else if (!isRb && isSubsidy && ior.startsWith('ior_ot') && subsidyAmount) {
+        // 进球数调水(%)
+        else if (regOt.test(ior) && innerOtSubsidyRatio) {
           const sourceOdds = events[ior].v;
-          events[ior].v = fixFloat(sourceOdds * (1 + subsidyAmount), 3);
-          events[ior].s = sourceOdds
+          events[ior].v = fixFloat(sourceOdds * (1 + innerOtSubsidyRatio / 100), 3);
+          events[ior].s = sourceOdds;
+        }
+        // 大小球最低赔率
+        if (regOu.test(ior) && innerOuMinValue) {
+          const sourceOdds = events[ior].s ?? events[ior].v ?? 1;
+          events[ior].q = sourceOdds < innerOuMinValue ? 0 : 1;
         }
       });
       baseGame.originId = originId;
@@ -400,14 +403,12 @@ const syncBaseEvents = ({ mk, games, outrights }) => {
       const matches = PS_IOR_KEYS.map(([label, ...keys]) => {
         let match = keys.map(key => {
           let value = odds[key]?.v ?? 0;
-          if (key.startsWith('ior_ou') && value < innerOuMinValue) {
-            value = 0;
-          }
           return {
             key, value,
             origin: odds[key]?.r,
             source: odds[key]?.s,
-          }
+            qualified: odds[key]?.q ?? 1,
+          };
         });
         if (label == 'jqs' || label == 'ou') {
           match = match.filter(item => item.value !== 0);
@@ -421,12 +422,11 @@ const syncBaseEvents = ({ mk, games, outrights }) => {
           return item.match.length;
         }
         else {
-          return item.match.every(entry => {
-            // if (entry.key == '-' || entry.key.startsWith('ior_ou')) {
-            //   return true;
-            // }
-            return entry.value !== 0;
-          });
+          // return item.match.every(entry => {
+          //   return entry.value !== 0;
+          // });
+          const validEntrys = item.match.filter(entry => entry.value !== 0);
+          return validEntrys.length >= 2;
         }
       });
 
@@ -458,11 +458,12 @@ const syncBaseEvents = ({ mk, games, outrights }) => {
       if (relatedGame) {
         const events = {};
         matches.forEach(({ label, match }) => {
-          match.forEach(({ key, value, origin, source }) => {
+          match.forEach(({ key, value, origin, source, qualified }) => {
             events[key] = {
               v: value,
               r: origin,
-              s: source
+              s: source,
+              q: qualified,
             };
           });
         });
@@ -1417,6 +1418,7 @@ const syncQbossConfig = () => {
       qboss_jq_add_odds, qboss_jq_add_hours,
       qboss_gq_add_dy_odds, qboss_gq_add_jq_odds,
       bc_before_hours,
+      qboss_ts_sfdx_odds_add, qboss_ts_pjrq_odds_add, qboss_ts_jqs_odds_add,
      } = data.data;
     const qbossSetting = {
       innerRebateRatio: qboss_return_ratio ? +qboss_return_ratio : 0,
@@ -1435,6 +1437,9 @@ const syncQbossConfig = () => {
       subsidyAmount: qboss_jq_add_odds ? +qboss_jq_add_odds : 0,
       subsidyRbWmAmount: qboss_gq_add_dy_odds ? +qboss_gq_add_dy_odds : 0,
       subsidyRbOtAmount: qboss_gq_add_jq_odds ? +qboss_gq_add_jq_odds : 0,
+      innerRouSubsidyValue: qboss_ts_sfdx_odds_add ? +qboss_ts_sfdx_odds_add : 0,
+      innerWmSubsidyRatio: qboss_ts_pjrq_odds_add ? +qboss_ts_pjrq_odds_add : 0,
+      innerOtSubsidyRatio: qboss_ts_jqs_odds_add ? +qboss_ts_jqs_odds_add : 0,
       halfTimeActiveTime: bc_before_hours ? +bc_before_hours : 0,
     };
     Logs.outDev('syncQbossConfig', qbossSetting);
@@ -1472,11 +1477,11 @@ const notifyException = (message) => {
   }
   const chat_id = -1003032820471;
   Promise.all([
-    axios.get(`${BASE_API_URL}/telegram/jump`, { params: { chat_id, message } }, { proxy: false }),
+    axios.get(`${BASE_API_URL}/telegram/jump`, { params: { chat_id, message: `@taoger369 ${message}` } }, { proxy: false }),
     axios.post('https://push.long.bid/bark', {
       title: 'QBoss异常通知',
       content: message,
-      topic: 'pstery',
+      topic: 'default',
       icon: 'https://bwh.flyzto.com/icon/ball.png',
     }, { proxy: false }),
   ])
@@ -1528,7 +1533,7 @@ events_child.on('message', async (message) => {
       updateSolutions(data.solutions, data.eventsLogsMap);
     }
   }
-  else if (method == 'response' && id && callbacks[id]) {
+  else if (type == 'response' && id && callbacks[id]) {
     callbacks[id](data);
     delete callbacks[id];
   }

+ 3 - 8
server/models/Setting.js

@@ -91,22 +91,17 @@ const systemSettingSchema = new Schema({
     required: true,
     default: 0
   },
-  subsidyTime: {
+  innerRouSubsidyValue: {
     type: Number,
     required: true,
     default: 0
   },
-  subsidyAmount: {
+  innerWmSubsidyRatio: {
     type: Number,
     required: true,
     default: 0
   },
-  subsidyRbWmAmount: {
-    type: Number,
-    required: true,
-    default: 0
-  },
-  subsidyRbOtAmount: {
+  innerOtSubsidyRatio: {
     type: Number,
     required: true,
     default: 0

+ 3 - 4
server/triangle/settings.js

@@ -18,10 +18,9 @@ const SETTING = {
   hgMaxDiff: 0,
   pcRebateRatio: 0,
   pcRebateType: 0,
-  subsidyTime: 0,
-  subsidyAmount: 0,
-  subsidyRbWmAmount: 0,
-  subsidyRbOtAmount: 0,
+  innerRouSubsidyValue: 0,
+  innerWmSubsidyRatio: 0,
+  innerOtSubsidyRatio: 0,
   halfTimeActiveTime: 0,
   expireTimeEvents: 45000,
   expireTimeSpecial: 60000,

+ 1 - 1
server/triangle/totalProfitCalc.js

@@ -140,7 +140,7 @@ const HandicapCalc = function (data) {
         () => {
           const x = g;
           const z = (t + k2 * x / 2) / k5;
-          const y = ((k5 + k6) * t + (k2 * k5 + k2 * k4 / 2) * x) / (k3 * k5);
+          const y = ((k5 + k6) * t + (k5 + k6 / 2) * k2 * x) / (k3 * k5);
           return { x, y, z };
         },
         () => {

+ 1 - 0
server/triangle/trangleCalc.js

@@ -39,6 +39,7 @@ const getOptimalSelections = (odds, rules) => {
             v: item.ps.v,
             r: item.ps.r,
             s: item.ps.s,
+            q: item.ps.q,
             o: item
           }]);
         }

+ 48 - 47
web/apps/web-antd/src/views/match/components/solution_item.vue

@@ -35,30 +35,30 @@ const emit = defineEmits(['toggle']);
 
 const selectedIndex = ref(0);
 
-// 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'],
-//   ['-3', 'ior_rh_35', 'ior_wmh_3', 'ior_rac_25'],
-//   // ['-4', 'ior_rh_45', 'ior_wmh_4', 'ior_rac_35'],
-//   // ['-5', 'ior_rh_55', 'ior_wmh_5', 'ior_rac_45'],
-//   ['+1', 'ior_rah_05', 'ior_wmc_1', 'ior_rc_15'],
-//   ['+2', 'ior_rah_15', 'ior_wmc_2', 'ior_rc_25'],
-//   ['+3', 'ior_rah_25', 'ior_wmc_3', 'ior_rc_35'],
-//   // ['+4', 'ior_rah_35', 'ior_wmc_4', 'ior_rc_45'],
-//   // ['+5', 'ior_rah_45', 'ior_wmc_5', 'ior_rc_55'],
-//   ['ou_05', 'ior_ouc_05', '-', 'ior_ouh_05'],
-//   ['ou_15', 'ior_ouc_15', '-', 'ior_ouh_15'],
-//   ['ou_25', 'ior_ouc_25', '-', 'ior_ouh_25'],
-//   ['ou_35', 'ior_ouc_35', '-', 'ior_ouh_35'],
-//   ['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 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'],
+  ['-3', 'ior_rh_35', 'ior_wmh_3', 'ior_rac_25'],
+  // ['-4', 'ior_rh_45', 'ior_wmh_4', 'ior_rac_35'],
+  // ['-5', 'ior_rh_55', 'ior_wmh_5', 'ior_rac_45'],
+  ['+1', 'ior_rah_05', 'ior_wmc_1', 'ior_rc_15'],
+  ['+2', 'ior_rah_15', 'ior_wmc_2', 'ior_rc_25'],
+  ['+3', 'ior_rah_25', 'ior_wmc_3', 'ior_rc_35'],
+  // ['+4', 'ior_rah_35', 'ior_wmc_4', 'ior_rc_45'],
+  // ['+5', 'ior_rah_45', 'ior_wmc_5', 'ior_rc_55'],
+  ['ou_05', 'ior_ouc_05', '-', 'ior_ouh_05'],
+  ['ou_15', 'ior_ouc_15', '-', 'ior_ouh_15'],
+  ['ou_25', 'ior_ouc_25', '-', 'ior_ouh_25'],
+  ['ou_35', 'ior_ouc_35', '-', 'ior_ouh_35'],
+  ['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 fixFloat = (number, x = 2) => {
   return parseFloat(number.toFixed(x));
@@ -103,27 +103,28 @@ const parseIorKey = (iorKey) => {
   return { type, side, ratio };
 }
 
-// const formatPsEvents = (events) => {
-//   return PS_IOR_KEYS.map(([label, ...keys]) => {
-//     const labelParts = label.split('_');
-//     const labelType = labelParts[0];
-//     if (labelType === 'ou') {
-//       const labelRatio = parseRatio(labelParts[1]);
-//       label = `ou_${labelRatio}`;
-//     }
-//     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 formatPsEvents = (events) => {
+  return PS_IOR_KEYS.map(([label, ...keys]) => {
+    const labelParts = label.split('_');
+    const labelType = labelParts[0];
+    if (labelType === 'ou') {
+      const labelRatio = parseRatio(labelParts[1]);
+      label = `ou_${labelRatio}`;
+    }
+    const match = keys.map(key => ({
+      key,
+      value: events[key]?.v ?? 0,
+      origin: events[key]?.r,
+      qualified: events[key]?.q ?? 1,
+    }));
+    return {
+      label,
+      match
+    };
+  })
+  // .filter(item => item.match.every(entry => entry.value !== 0))
+  .map(({label, match}) => [label, ...match]);
+}
 
 const formatEvents = (events) => {
   const eventsMap = {};
@@ -253,8 +254,8 @@ const currentRelation = computed(() => {
       relation.rel = {};
     }
     const mergedEvents = { ...events, ...special };
-    // const formattedEvents = platform === 'ps' ? formatPsEvents(mergedEvents) : formatEvents(mergedEvents);
-    const formattedEvents = formatEvents(mergedEvents);
+    const formattedEvents = platform === 'ps' ? formatPsEvents(mergedEvents) : formatEvents(mergedEvents);
+    // const formattedEvents = formatEvents(mergedEvents);
     relation.rel[platform] = {
       eventId: eventId ?? 0,
       teamHomeName: teamHomeName ?? '主队',

+ 39 - 5
web/apps/web-antd/src/views/match/solutions/index.vue

@@ -2,7 +2,10 @@
 import { requestClient } from '#/api/request';
 import { Button, message, Form, InputNumber, Select, SelectOption, RadioGroup, Radio, Checkbox, Drawer, Input, Switch } from 'ant-design-vue';
 import { ref, reactive, computed, watch, onMounted, onUnmounted } from 'vue';
+
 import dayjs from 'dayjs';
+import VueJsonPretty from 'vue-json-pretty';
+import 'vue-json-pretty/lib/styles.css';
 
 // import MatchCard from '../components/match_card.vue';
 import SolutionItem from '../components/solution_item.vue';
@@ -13,7 +16,7 @@ const contentsPositionStore = useContentsPositionStore();
 const solutions = ref([]);
 const markCount = ref({ all: 0, rollball: 0, today: 0, early: 0 });
 const selectedSolutions = reactive([]);
-const totalProfit = ref({});
+const totalProfit = ref(null);
 const loopActive = ref(false);
 const loopTimer = ref(null);
 const updateTimer = ref(null);
@@ -27,10 +30,19 @@ const updateLoaderHide = ref(null);
 
 const totalProfitVisible = ref(false);
 
+const prettyKey = ref(Date.now());
+
 const fixFloat = (number, x = 2) => {
   return parseFloat(number.toFixed(x));
 }
 
+const formattedTotalProfit = computed(() => {
+  if (!totalProfit.value || Object.keys(totalProfit.value).length === 0) {
+    return '暂无数据';
+  }
+  return JSON.stringify(totalProfit.value, null, 2);
+})
+
 const headerStyle = computed(() => {
   return {
     position: contentsPositionStore.position,
@@ -108,10 +120,22 @@ const updateSolutions = async (showLoading=false) => {
 }
 
 const showTotalProfit = async () => {
-  totalProfit.value = await calcTotalProfit();
-  totalProfitVisible.value = true;
-  const { profit } = totalProfit.value;
-  console.log('profit', profit);
+  try {
+    const data = await calcTotalProfit();
+    totalProfit.value = data || {};
+    prettyKey.value = Date.now();
+    totalProfitVisible.value = true;
+  } catch (error) {
+    console.error('Failed to show total profit:', error);
+    message.error('显示综合利润失败');
+  }
+};
+
+const closeTotalProfit = () => {
+  totalProfit.value = null;
+  prettyKey.value = Date.now();
+  totalProfitVisible.value = false;
+  selectedSolutions.splice(0);
 };
 
 
@@ -283,6 +307,16 @@ onUnmounted(() => {
     </div>
     <div class="list-empty" v-else>暂无数据</div>
 
+    <Drawer
+      title="综合利润详情"
+      :open="totalProfitVisible"
+      :width="600"
+      placement="right"
+      @close="closeTotalProfit"
+    >
+      <vue-json-pretty :data="totalProfit" :indent="2" :deep="4" :key="prettyKey" :showDoubleQuotes="false"></vue-json-pretty>
+    </Drawer>
+
   </div>
 </template>
 

+ 12 - 20
web/apps/web-antd/src/views/system/parameter/index.vue

@@ -22,10 +22,9 @@ const initialFormState = {
   hgMaxDiff: 0,
   pcRebateRatio: 0,
   pcRebateType: 0,
-  subsidyTime: 0,
-  subsidyAmount: 0,
-  subsidyRbWmAmount: 0,
-  subsidyRbOtAmount: 0,
+  innerRouSubsidyValue: 0,
+  innerWmSubsidyRatio: 0,
+  innerOtSubsidyRatio: 0,
   halfTimeActiveTime: 0,
   expireTimeEvents: 0,
   expireTimeSpecial: 0,
@@ -252,31 +251,24 @@ onUnmounted(() => {
       </Form.Item>
 
       <Form.Item
-        label="赛前补水时间(-h)"
-        name="subsidyTime"
+        label="让胜负/大小球调水"
+        name="innerRouSubsidyValue"
       >
-        <InputNumber :disabled="formState.syncSettingEnabled" v-model:value="formState.subsidyTime" :min="0" :step="48" style="width: 200px" />
+        <InputNumber :disabled="formState.syncSettingEnabled" v-model:value="formState.innerRouSubsidyValue" :min="0" :step="0.01" style="width: 200px" />
       </Form.Item>
 
       <Form.Item
-        label="赛前补水比例"
-        name="subsidyAmount"
+        label="平局/让平调水(%)"
+        name="innerWmSubsidyRatio"
       >
-        <InputNumber :disabled="formState.syncSettingEnabled" v-model:value="formState.subsidyAmount" :min="0" :step="0.01" style="width: 200px" />
+        <InputNumber :disabled="formState.syncSettingEnabled" v-model:value="formState.innerWmSubsidyRatio" :min="0" :step="0.1" style="width: 200px" />
       </Form.Item>
 
       <Form.Item
-        label="滚球补水(净胜)"
-        name="subsidyRbWmAmount"
+        label="进球数调水(%)"
+        name="innerOtSubsidyRatio"
       >
-        <InputNumber :disabled="formState.syncSettingEnabled" v-model:value="formState.subsidyRbWmAmount" :min="0" :step="0.01" style="width: 200px" />
-      </Form.Item>
-
-      <Form.Item
-        label="滚球补水(进球)"
-        name="subsidyRbOtAmount"
-      >
-        <InputNumber :disabled="formState.syncSettingEnabled" v-model:value="formState.subsidyRbOtAmount" :min="0" :step="0.01" style="width: 200px" />
+        <InputNumber :disabled="formState.syncSettingEnabled" v-model:value="formState.innerOtSubsidyRatio" :min="0" :step="0.1" style="width: 200px" />
       </Form.Item>
 
       <Form.Item