solution_item.vue 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. <script setup>
  2. import { Tooltip } from 'ant-design-vue';
  3. import { ref, computed } from 'vue';
  4. import dayjs from 'dayjs';
  5. import MatchCard from './match_card.vue';
  6. const props = defineProps({
  7. serial: {
  8. type: Number,
  9. },
  10. id: {
  11. type: Number,
  12. required: true
  13. },
  14. mk: {
  15. type: Number,
  16. required: true
  17. },
  18. rel: {
  19. type: Object,
  20. required: true
  21. },
  22. solutions: {
  23. type: Array,
  24. required: true
  25. },
  26. selected: {
  27. type: Boolean,
  28. default: false
  29. }
  30. });
  31. const emit = defineEmits(['toggle']);
  32. const selectedIndex = ref(0);
  33. const parseIorKey = (iorKey) => {
  34. const iorKeyMatch = iorKey.match(/^ior_(r|ou|m|wm|ot|os)(a?)(h|c|n)?(_([\d-]+))?$/);
  35. if (!iorKeyMatch) {
  36. console.log('no iorKeyMatch', iorKey);
  37. return null;
  38. }
  39. const [, type, accept, side, , ratioString] = iorKeyMatch;
  40. let ratio = 0;
  41. if (type === 'ot' || type === 'os') {
  42. ratio = ratioString;
  43. }
  44. else if (ratioString) {
  45. ratio = `${ratioString[0]}.${ratioString.slice(1)}` * (accept ? 1 : -1);
  46. }
  47. return { type, side, ratio };
  48. }
  49. const PS_IOR_KEYS = [
  50. ['0', 'ior_mh', 'ior_mn', 'ior_mc'],
  51. ['-1', 'ior_rh_15', 'ior_wmh_1', 'ior_rac_05'],
  52. ['-2', 'ior_rh_25', 'ior_wmh_2', 'ior_rac_15'],
  53. ['-3', 'ior_rh_35', 'ior_wmh_3', 'ior_rac_25'],
  54. ['-4', 'ior_rh_45', 'ior_wmh_4', 'ior_rac_35'],
  55. ['-5', 'ior_rh_55', 'ior_wmh_5', 'ior_rac_45'],
  56. ['+1', 'ior_rah_05', 'ior_wmc_1', 'ior_rc_15'],
  57. ['+2', 'ior_rah_15', 'ior_wmc_2', 'ior_rc_25'],
  58. ['+3', 'ior_rah_25', 'ior_wmc_3', 'ior_rc_35'],
  59. ['+4', 'ior_rah_35', 'ior_wmc_4', 'ior_rc_45'],
  60. ['+5', 'ior_rah_45', 'ior_wmc_5', 'ior_rc_55'],
  61. ['ot_1', '-', 'ior_ot_1', '-'],
  62. ['ot_2', '-', 'ior_ot_2', '-'],
  63. ['ot_3', '-', 'ior_ot_3', '-'],
  64. ['ot_4', '-', 'ior_ot_4', '-'],
  65. ['ot_5', '-', 'ior_ot_5', '-'],
  66. ['ot_6', '-', 'ior_ot_6', '-'],
  67. ['ot_7', '-', 'ior_ot_7', '-'],
  68. ];
  69. const fixFloat = (number, x = 2) => {
  70. return parseFloat(number.toFixed(x));
  71. }
  72. const formatPsEvents = (events) => {
  73. return PS_IOR_KEYS.map(([label, ...keys]) => {
  74. const match = keys.map(key => ({
  75. key,
  76. value: events[key]?.v ?? 0,
  77. origin: events[key]?.r
  78. }));
  79. return {
  80. label,
  81. match
  82. };
  83. })
  84. // .filter(item => item.match.every(entry => entry.value !== 0))
  85. .map(({label, match}) => [label, ...match]);
  86. }
  87. const formatEvents = (events) => {
  88. const eventsMap = {};
  89. Object.keys(events).forEach(key => {
  90. const { type, side, ratio } = parseIorKey(key) ?? {};
  91. if (!type) {
  92. return;
  93. }
  94. let ratioKey, index;
  95. if (type === 'r') {
  96. if (side === 'h') {
  97. ratioKey = ratio;
  98. index = 0;
  99. }
  100. else if (side === 'c') {
  101. ratioKey = -ratio;
  102. index = 2;
  103. }
  104. }
  105. else if (type === 'm') {
  106. ratioKey = 'm';
  107. if (side == 'h') {
  108. index = 0;
  109. }
  110. else if (side == 'c') {
  111. index = 2;
  112. }
  113. else {
  114. index = 1;
  115. }
  116. }
  117. else if (type === 'wm') {
  118. ratioKey = `wm_${Math.abs(ratio)}`;
  119. if (side === 'h') {
  120. index = 0;
  121. }
  122. else if (side === 'c') {
  123. index = 2;
  124. }
  125. }
  126. else if (type === 'ou') {
  127. ratioKey = `ou_${Math.abs(ratio)}`;
  128. if (side === 'c') {
  129. index = 0;
  130. }
  131. else if (side === 'h') {
  132. index = 2;
  133. }
  134. }
  135. else if (type === 'ot') {
  136. ratioKey = `ot_${ratio}`;
  137. index = 1;
  138. }
  139. if (typeof (ratioKey) == 'number') {
  140. if (ratioKey > 0) {
  141. ratioKey = `+${ratioKey}`;
  142. }
  143. else {
  144. ratioKey = `${ratioKey}`;
  145. }
  146. }
  147. if (!ratioKey) {
  148. return;
  149. }
  150. if (!eventsMap[ratioKey]) {
  151. eventsMap[ratioKey] = new Array(3).fill(undefined);
  152. }
  153. const value = events[key]?.v ?? 0;
  154. const origin = events[key]?.r;
  155. const qualified = events[key]?.q ?? 1;
  156. eventsMap[ratioKey][index] = { key, value, origin, qualified };
  157. });
  158. return Object.keys(eventsMap).sort((a, b) => a.localeCompare(b)).map(key => {
  159. return [key, ...eventsMap[key]];
  160. });
  161. }
  162. const toggleSolution = () => {
  163. const id = props.id;
  164. const sid = currentSolution.value.sid;
  165. emit('toggle', { id, sid });
  166. };
  167. const switchSolution = (index) => {
  168. if (index == selectedIndex.value) {
  169. return;
  170. }
  171. selectedIndex.value = index;
  172. if (!props.selected) {
  173. return;
  174. }
  175. toggleSolution();
  176. };
  177. const currentIndex = computed(() => {
  178. const index = selectedIndex.value;
  179. if (props.solutions[index]) {
  180. return index;
  181. }
  182. return 0;
  183. });
  184. const currentSolution = computed(() => {
  185. return props.solutions[currentIndex.value];
  186. });
  187. const currentRelation = computed(() => {
  188. const cpr = currentSolution.value.cpr;
  189. const rel = props.rel;
  190. const { ps: { eventId, leagueName, timestamp, stage, retime, score } } = rel;
  191. const dateTime = dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss');
  192. const relation = { id: eventId, leagueName, timestamp, dateTime, stage, retime, score };
  193. Object.keys(rel).forEach(platform => {
  194. const { eventId, teamHomeName, teamAwayName, events, special } = rel[platform] ?? {};
  195. if (!relation.rel) {
  196. relation.rel = {};
  197. }
  198. const mergedEvents = { ...events, ...special };
  199. const formattedEvents = platform === 'ps' ? formatPsEvents(mergedEvents) : formatEvents(mergedEvents);
  200. relation.rel[platform] = { eventId, teamHomeName, teamAwayName, events: formattedEvents };
  201. });
  202. cpr.forEach(item => {
  203. const { k, p } = item;
  204. if (!relation.rel[p]['selected']) {
  205. relation.rel[p]['selected'] = [];
  206. }
  207. relation.rel[p]['selected'].push(k);
  208. });
  209. return relation;
  210. });
  211. const isHalf = computed(() => {
  212. return currentRelation.value.id < 0;
  213. });
  214. const ps = computed(() => {
  215. return currentRelation.value.rel.ps;
  216. });
  217. const hg = computed(() => {
  218. return currentRelation.value.rel.hg;
  219. });
  220. const ob = computed(() => {
  221. return currentRelation.value.rel.ob;
  222. });
  223. const im = computed(() => {
  224. return currentRelation.value.rel.im;
  225. });
  226. </script>
  227. <template>
  228. <div class="solution-item" :class="{ 'selected': selected }">
  229. <div class="solution-header">
  230. <div class="serial-number" v-if="serial">{{ serial }}.</div>
  231. <div class="stage" v-if="currentRelation.stage">[{{ currentRelation.stage }}{{ currentRelation.retime ? ` ${currentRelation.retime}` : '' }}]</div>
  232. <div class="score" v-if="currentRelation.stage">[{{ currentRelation.score }}]</div>
  233. <div class="league-name">{{ currentRelation.leagueName }}</div>
  234. <div class="period-half" v-if="isHalf">[上半场]</div>
  235. <div class="date-time">{{ currentRelation.dateTime }}</div>
  236. <div class="switch-btns" v-if="solutions.length">
  237. <Tooltip v-for="({sol}, index) in solutions" :key="index"
  238. class="switch-btn-item"
  239. :class="{ 'selected': index === currentIndex }"
  240. :title="`${sol.win_profit_rate}% (${sol.cross_type})`"
  241. @click="switchSolution(index)">{{ sol.win_average_rate }}</Tooltip>
  242. </div>
  243. </div>
  244. <div class="solution-content">
  245. <MatchCard platform="ps" :eventId="ps.eventId" :teamHomeName="ps.teamHomeName"
  246. :teamAwayName="ps.teamAwayName" :dateTime="ps.dateTime" :events="ps.events ?? []"
  247. :selected="ps.selected ?? []" />
  248. <MatchCard platform="ob" :eventId="ob.eventId" :teamHomeName="ob.teamHomeName"
  249. :teamAwayName="ob.teamAwayName" :dateTime="ob.dateTime" :events="ob.events ?? []"
  250. :selected="ob.selected ?? []" />
  251. <MatchCard platform="hg" :eventId="hg.eventId" :teamHomeName="hg.teamHomeName"
  252. :teamAwayName="hg.teamAwayName" :dateTime="hg.dateTime" :events="hg.events ?? []"
  253. :selected="hg.selected ?? []" />
  254. <MatchCard platform="im" :eventId="im.eventId" :teamHomeName="im.teamHomeName"
  255. :teamAwayName="im.teamAwayName" :dateTime="im.dateTime" :events="im.events ?? []"
  256. :selected="im.selected ?? []" />
  257. <!-- <div class="solution-profit" @click="toggleSolution()">
  258. <p>{{ currentSolution.sol.win_average_rate }}%</p>
  259. <p>{{ currentSolution.sol.win_profit_rate }}%</p>
  260. <p>{{ currentSolution.sol.cross_type }}</p>
  261. </div> -->
  262. </div>
  263. </div>
  264. </template>
  265. <style lang="scss" scoped>
  266. .solution-item {
  267. display: flex;
  268. flex-direction: column;
  269. border-radius: 10px;
  270. background-color: hsl(var(--card));
  271. &.selected {
  272. background-color: hsl(var(--primary) / 0.15);
  273. }
  274. &:not(:last-child) {
  275. margin-bottom: 20px;
  276. }
  277. }
  278. .solution-header {
  279. display: flex;
  280. align-items: center;
  281. height: 40px;
  282. padding: 0 15px;
  283. border-bottom: 1px solid hsl(var(--border));
  284. .serial-number {
  285. margin-right: 5px;
  286. font-size: 16px;
  287. font-weight: 400;
  288. color: hsl(var(--foreground) / 0.7);
  289. }
  290. .score, .stage {
  291. text-align: center;
  292. font-size: 16px;
  293. font-weight: 400;
  294. }
  295. .score {
  296. color: hsl(var(--primary));
  297. }
  298. .stage {
  299. color: hsl(var(--destructive));
  300. }
  301. .league-name {
  302. margin-right: 10px;
  303. font-size: 16px;
  304. font-weight: 400;
  305. }
  306. .period-half {
  307. margin-right: 10px;
  308. color: hsl(var(--primary));
  309. }
  310. .date-time {
  311. text-align: right;
  312. font-size: 14px;
  313. color: hsl(var(--foreground) / 0.7);
  314. }
  315. }
  316. .switch-btns {
  317. display: flex;
  318. align-items: center;
  319. justify-content: flex-end;
  320. flex: 1;
  321. :deep(.switch-btn-item) {
  322. display: block;
  323. height: 20px;
  324. line-height: 20px;
  325. padding: 0 5px;
  326. border-radius: 4px;
  327. cursor: pointer;
  328. &:hover {
  329. color: hsl(var(--primary));
  330. }
  331. &.selected {
  332. color: hsl(var(--primary-foreground));
  333. background-color: hsl(var(--primary));
  334. cursor: default;
  335. }
  336. &:not(:last-child) {
  337. margin-right: 5px;
  338. }
  339. }
  340. }
  341. .solution-content {
  342. display: flex;
  343. .match-card {
  344. flex: 1;
  345. }
  346. .match-card:not(:last-child) {
  347. border-right: 1px solid hsl(var(--border));
  348. }
  349. }
  350. /*
  351. .solution-profit {
  352. display: flex;
  353. flex-direction: column;
  354. width: 80px;
  355. align-items: center;
  356. justify-content: center;
  357. }
  358. */
  359. </style>