no message

This commit is contained in:
hajimi
2026-06-11 12:15:29 +08:00
parent 10ebe39c30
commit 96efa1d905
5859 changed files with 815501 additions and 5 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,743 @@
<template>
<view class="detail-page">
<!-- 导航栏 -->
<view class="dd-nav" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="dd-nav__content">
<view class="dd-nav__back" @tap="goBack">
<u-icon name="arrow-left" size="36" color="#fff" />
</view>
<text class="dd-nav__title">开奖详情</text>
<view class="dd-nav__placeholder" />
</view>
</view>
<view v-if="loading" class="dd-loading">
<text>加载中...</text>
</view>
<view v-if="!loading && drawData" class="dd-body">
<!-- 彩种+期号信息 -->
<view class="dd-info">
<text class="dd-info__name">{{ drawData.category?.name || '' }}</text>
<text class="dd-info__period"> {{ drawData.period }} </text>
<view class="dd-info__badge" :class="{ 'dd-info__badge--pending': drawData.status === 0 }">
<text>{{ drawData.status === 1 ? '已开奖' : '待开奖' }}</text>
</view>
</view>
<text class="dd-date">开奖日期{{ drawData.draw_date }}</text>
<!-- 开奖号码 -->
<view v-if="drawData.status === 1" class="dd-result">
<text class="dd-result__label">开奖号码</text>
<view class="dd-result__balls">
<view v-for="(num, i) in drawData.numbers" :key="i" class="ball-wrap">
<view class="ball" :class="'ball--' + getBallColor(i)">
<text class="ball__num">{{ formatNum(num) }}</text>
</view>
<text class="ball-zodiac">{{ drawData.zodiac?.[i] || '' }}</text>
</view>
<text class="ball__plus">+</text>
<view class="ball-wrap">
<view class="ball ball--special" :class="'ball--' + specialColor">
<text class="ball__num">{{ formatNum(drawData.special_number) }}</text>
</view>
<text class="ball-zodiac ball-zodiac--special">{{ specialZodiac }}</text>
</view>
</view>
<!-- 五行 -->
<view v-if="drawData.elements?.length" class="dd-extra">
<text class="dd-extra__label">五行</text>
<view class="dd-extra__tags">
<text v-for="(e, i) in drawData.elements" :key="i" class="dd-tag dd-tag--element">{{ e }}</text>
</view>
</view>
<!-- 波色 -->
<view v-if="drawData.color?.length" class="dd-extra">
<text class="dd-extra__label">波色</text>
<view class="dd-extra__tags">
<view v-for="(c, i) in drawData.color" :key="i" class="dd-color-dot"
:class="'dd-color-dot--' + c" />
</view>
</view>
</view>
<!-- 待开奖 -->
<view v-else class="dd-pending">
<text class="dd-pending__icon">🎱</text>
<text class="dd-pending__text">等待开奖</text>
<text class="dd-pending__hint">
开奖时间{{ drawData.category?.draw_time || '请关注官方公告' }}
</text>
</view>
<!-- 开奖规则 -->
<view v-if="drawData.category?.draw_rule" class="dd-rule">
<text class="dd-rule__title">开奖规则</text>
<text class="dd-rule__text">{{ drawData.category.draw_rule }}</text>
</view>
<!-- AI预测分析仅待开奖时显示 -->
<view v-if="drawData.status === 0" class="ai-section">
<view class="ai-section__header">
<view class="ai-section__icon">AI</view>
<text class="ai-section__title">AI智能预测</text>
<view v-if="aiData?.from_cache" class="ai-section__cache">
<text>缓存</text>
</view>
</view>
<view class="ai-btn-wrap">
<view class="ai-btn" @tap="goAiPredict">
<text class="ai-btn__text">进入AI智能分析</text>
</view>
</view>
<view v-if="aiLoaded && aiAnalysis" class="ai-result">
<!-- 热号冷号 -->
<view v-if="aiAnalysis.hot_numbers?.length" class="ai-block">
<text class="ai-block__label">热号近期高频</text>
<view class="ai-nums">
<view v-for="n in aiAnalysis.hot_numbers" :key="'h' + n" class="ai-num ai-num--hot">
<text>{{ formatNum(n) }}</text>
</view>
</view>
</view>
<view v-if="aiAnalysis.cold_numbers?.length" class="ai-block">
<text class="ai-block__label">冷号近期低频</text>
<view class="ai-nums">
<view v-for="n in aiAnalysis.cold_numbers" :key="'c' + n" class="ai-num ai-num--cold">
<text>{{ formatNum(n) }}</text>
</view>
</view>
</view>
<!-- 趋势分析 -->
<view v-if="aiAnalysis.trend_analysis" class="ai-block">
<text class="ai-block__label">趋势分析</text>
<text class="ai-block__text">{{ aiAnalysis.trend_analysis }}</text>
</view>
<!-- 推荐组合 -->
<view v-if="aiAnalysis.recommended_combinations?.length" class="ai-block">
<text class="ai-block__label">推荐组合</text>
<view v-for="(combo, ci) in aiAnalysis.recommended_combinations" :key="ci" class="ai-combo">
<view class="ai-combo__nums">
<view v-for="n in combo.numbers" :key="n" class="ai-num ai-num--rec">
<text>{{ formatNum(n) }}</text>
</view>
</view>
<text class="ai-combo__conf">信心 {{ combo.confidence }}%</text>
</view>
</view>
<!-- 统计 -->
<view v-if="aiAnalysis.statistics" class="ai-block">
<text class="ai-block__label">统计数据</text>
<view class="ai-stats">
<view v-if="aiAnalysis.statistics.odd_even_ratio" class="ai-stat">
<text class="ai-stat__label">奇偶比</text>
<text class="ai-stat__val">{{ aiAnalysis.statistics.odd_even_ratio }}</text>
</view>
<view v-if="aiAnalysis.statistics.big_small_ratio" class="ai-stat">
<text class="ai-stat__label">大小比</text>
<text class="ai-stat__val">{{ aiAnalysis.statistics.big_small_ratio }}</text>
</view>
<view v-if="aiAnalysis.statistics.sum_range" class="ai-stat">
<text class="ai-stat__label">和值范围</text>
<text class="ai-stat__val">{{ aiAnalysis.statistics.sum_range }}</text>
</view>
</view>
</view>
<!-- 免责声明 -->
<view class="ai-disclaimer">
<text>{{ aiAnalysis.disclaimer || '本分析仅基于历史数据统计,不保证准确性,请理性购彩' }}</text>
</view>
</view>
</view>
<!-- 免责声明 -->
<view class="dd-disclaimer">
<text>声明开奖数据仅供参考请以官方公布为准</text>
</view>
</view>
<view v-if="!loading && errorMsg" class="dd-error">
<text>{{ errorMsg }}</text>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { getLotteryDrawDetail, getLotteryAiPredict } from '@/api/lottery'
const statusBarHeight = ref(0)
const loading = ref(true)
const drawData = ref<any>(null)
const errorMsg = ref('')
uni.getSystemInfo({
success: (res) => {
// #ifdef APP-PLUS || MP
statusBarHeight.value = res.statusBarHeight || 44
// #endif
// #ifdef H5
statusBarHeight.value = 0
// #endif
}
})
const getBallColor = (index: number) => {
if (drawData.value?.color?.[index]) return drawData.value.color[index]
return 'red'
}
const specialColor = computed(() => {
const c = drawData.value?.color
if (c && c.length > 0) return c[c.length - 1]
return 'red'
})
const specialZodiac = computed(() => {
const z = drawData.value?.zodiac
if (z && z.length > 0) return z[z.length - 1]
return ''
})
const formatNum = (n: any) => {
const num = Number(n)
return num < 10 ? '0' + num : String(num)
}
const fetchDetail = async (id: number) => {
loading.value = true
errorMsg.value = ''
try {
const data = await getLotteryDrawDetail({ id })
drawData.value = data
} catch (e: any) {
errorMsg.value = e?.msg || '加载失败'
} finally {
loading.value = false
}
}
const aiLoading = ref(false)
const aiLoaded = ref(false)
const aiError = ref('')
const aiData = ref<any>(null)
const aiAnalysis = computed(() => aiData.value?.analysis || null)
const goAiPredict = () => {
const code = drawData.value?.category?.code || ''
const name = drawData.value?.category?.name || ''
if (!code) return
uni.navigateTo({
url: `/packages_lottery/pages/ai_predict?code=${code}&name=${encodeURIComponent(name)}`
})
}
const goBack = () => {
uni.navigateBack()
}
onLoad((options: any) => {
const id = Number(options.id || 0)
if (id > 0) {
fetchDetail(id)
} else {
loading.value = false
errorMsg.value = '参数错误'
}
})
</script>
<style lang="scss" scoped>
.detail-page {
min-height: 100vh;
background: #f5f5f5;
}
.dd-nav {
background: var(--color-primary, #4a90d9);
&__content {
height: 44px;
display: flex;
align-items: center;
padding: 0 24rpx;
}
&__back {
width: 60rpx;
height: 44px;
display: flex;
align-items: center;
}
&__title {
flex: 1;
text-align: center;
font-size: 34rpx;
font-weight: bold;
color: #fff;
}
&__placeholder {
width: 60rpx;
}
}
.dd-loading {
display: flex;
justify-content: center;
padding: 100rpx;
color: #999;
}
.dd-body {
padding: 24rpx;
}
.dd-info {
display: flex;
align-items: center;
gap: 16rpx;
margin-bottom: 12rpx;
&__name {
font-size: 34rpx;
font-weight: bold;
color: #222;
}
&__period {
font-size: 26rpx;
color: #185dff;
background: #E5EDFF;
padding: 4rpx 16rpx;
border-radius: 20rpx;
}
&__badge {
font-size: 22rpx;
color: #22c55e;
background: #f0fdf4;
padding: 4rpx 16rpx;
border-radius: 8rpx;
&--pending {
color: #f59e0b;
background: #fffbeb;
}
}
}
.dd-date {
font-size: 24rpx;
color: #999;
margin-bottom: 30rpx;
}
.dd-result {
background: #fff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 20rpx;
&__label {
font-size: 28rpx;
font-weight: bold;
color: #333;
margin-bottom: 24rpx;
}
&__balls {
display: flex;
align-items: center;
justify-content: center;
gap: 12rpx;
flex-wrap: wrap;
margin-bottom: 30rpx;
}
}
/* 号码球 */
.ball {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
&--red {
background: linear-gradient(135deg, #ef4444, #f87171);
}
&--blue {
background: linear-gradient(135deg, #3b82f6, #60a5fa);
}
&--green {
background: linear-gradient(135deg, #22c55e, #4ade80);
}
&--special {
border: 4rpx solid #fbbf24;
}
&__num {
color: #fff;
font-size: 32rpx;
font-weight: bold;
}
&__plus {
font-size: 36rpx;
color: #999;
font-weight: bold;
}
}
/* 球+生肖容器 */
.ball-wrap {
display: flex;
flex-direction: column;
align-items: center;
gap: 6rpx;
}
.ball-zodiac {
font-size: 22rpx;
color: #666;
&--special {
color: #185dff;
font-weight: bold;
}
}
.dd-extra {
margin-top: 24rpx;
padding-top: 24rpx;
border-top: 1rpx solid #f0f0f0;
&__label {
font-size: 26rpx;
color: #666;
margin-bottom: 16rpx;
}
&__tags {
display: flex;
flex-wrap: wrap;
gap: 12rpx;
}
}
.dd-tag {
font-size: 24rpx;
color: #333;
background: #f5f5f5;
padding: 8rpx 20rpx;
border-radius: 8rpx;
&--special {
color: #185dff;
background: #E5EDFF;
}
&--element {
color: #6366f1;
background: #eef2ff;
}
}
.dd-color-dot {
width: 32rpx;
height: 32rpx;
border-radius: 50%;
&--red {
background: #ef4444;
}
&--blue {
background: #3b82f6;
}
&--green {
background: #22c55e;
}
}
.dd-pending {
background: #fff;
border-radius: 16rpx;
padding: 80rpx 30rpx;
text-align: center;
margin-bottom: 20rpx;
&__icon {
font-size: 80rpx;
display: block;
margin-bottom: 20rpx;
}
&__text {
font-size: 32rpx;
font-weight: bold;
color: #333;
display: block;
margin-bottom: 12rpx;
}
&__hint {
font-size: 24rpx;
color: #999;
}
}
.dd-rule {
background: #fff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 20rpx;
&__title {
font-size: 28rpx;
font-weight: bold;
color: #333;
margin-bottom: 16rpx;
}
&__text {
font-size: 26rpx;
color: #666;
line-height: 1.6;
}
}
.dd-disclaimer {
padding: 30rpx 0;
text-align: center;
font-size: 22rpx;
color: #ccc;
}
.dd-error {
display: flex;
justify-content: center;
padding: 100rpx;
color: #999;
font-size: 28rpx;
}
/* AI预测区块 */
.ai-section {
background: #fff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 20rpx;
&__header {
display: flex;
align-items: center;
gap: 12rpx;
margin-bottom: 24rpx;
}
&__icon {
width: 48rpx;
height: 48rpx;
border-radius: 12rpx;
background: linear-gradient(135deg, #6366f1, #8b5cf6);
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 22rpx;
font-weight: bold;
}
&__title {
font-size: 30rpx;
font-weight: bold;
color: #222;
}
&__cache {
font-size: 20rpx;
color: #999;
background: #f5f5f5;
padding: 2rpx 12rpx;
border-radius: 8rpx;
}
}
.ai-btn-wrap {
display: flex;
justify-content: center;
padding: 20rpx 0;
}
.ai-btn {
background: linear-gradient(135deg, #6366f1, #8b5cf6);
padding: 20rpx 60rpx;
border-radius: 40rpx;
&__text {
color: #fff;
font-size: 28rpx;
font-weight: bold;
}
}
.ai-loading {
text-align: center;
padding: 40rpx;
color: #6366f1;
font-size: 26rpx;
}
.ai-error {
text-align: center;
padding: 20rpx;
color: #185dff;
font-size: 26rpx;
}
.ai-retry {
display: inline-flex;
margin-top: 16rpx;
padding: 8rpx 32rpx;
background: #E5EDFF;
border-radius: 20rpx;
text {
color: #185dff;
font-size: 24rpx;
}
}
.ai-result {
display: flex;
flex-direction: column;
gap: 24rpx;
}
.ai-block {
&__label {
font-size: 26rpx;
font-weight: bold;
color: #333;
margin-bottom: 12rpx;
display: block;
}
&__text {
font-size: 26rpx;
color: #666;
line-height: 1.6;
}
}
.ai-nums {
display: flex;
flex-wrap: wrap;
gap: 12rpx;
}
.ai-num {
width: 64rpx;
height: 64rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
text {
font-size: 26rpx;
font-weight: bold;
color: #fff;
}
&--hot {
background: linear-gradient(135deg, #185dff, #ff6b6b);
}
&--cold {
background: linear-gradient(135deg, #3b82f6, #60a5fa);
}
&--rec {
background: linear-gradient(135deg, #6366f1, #8b5cf6);
}
}
.ai-combo {
display: flex;
align-items: center;
gap: 16rpx;
margin-bottom: 12rpx;
&__nums {
display: flex;
gap: 8rpx;
}
&__conf {
font-size: 22rpx;
color: #6366f1;
background: #eef2ff;
padding: 4rpx 16rpx;
border-radius: 12rpx;
white-space: nowrap;
}
}
.ai-stats {
display: flex;
gap: 24rpx;
flex-wrap: wrap;
}
.ai-stat {
display: flex;
flex-direction: column;
align-items: center;
background: #f8f9fa;
padding: 16rpx 24rpx;
border-radius: 12rpx;
min-width: 140rpx;
&__label {
font-size: 22rpx;
color: #999;
margin-bottom: 8rpx;
}
&__val {
font-size: 28rpx;
font-weight: bold;
color: #333;
}
}
.ai-disclaimer {
padding-top: 16rpx;
border-top: 1rpx solid #f0f0f0;
text-align: center;
text {
font-size: 22rpx;
color: #ccc;
}
}
</style>
@@ -0,0 +1,771 @@
<template>
<page-meta :page-style="showAiPopup ? 'overflow: hidden;' : ''" />
<view class="draw-list-page" :class="{ 'page-no-scroll': showAiPopup }">
<!-- 导航栏 -->
<view class="dl-nav" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="dl-nav__content">
<view class="dl-nav__back" @tap="goBack">
<u-icon name="arrow-left" size="36" color="#fff" />
</view>
<text class="dl-nav__title">{{ categoryName }}</text>
<view class="dl-nav__placeholder" />
</view>
</view>
<!-- 开奖列表 -->
<z-paging ref="paging" v-model="drawList" @query="queryList" :fixed="false">
<view v-for="item in drawList" :key="item.code + item.issue" class="draw-card">
<view class="draw-card__header">
<text class="draw-card__period"> {{ item.issue }} </text>
<text class="draw-card__date">{{ item.draw_time }}</text>
<view class="draw-card__badge" :class="{ 'draw-card__badge--pending': item.status !== 1 }">
<text>{{ item.status === 1 ? '已开奖' : '待开奖' }}</text>
</view>
</view>
<view v-if="item.status === 1 && item.numbers?.length" class="draw-card__balls">
<template v-for="(ball, i) in item.numbers" :key="i">
<text v-if="i === item.numbers.length - 1" class="ball__plus">+</text>
<view class="ball-wrap">
<view class="ball ball--sm"
:class="'ball--' + (ball.color || (i === item.numbers.length - 1 ? 'blue' : 'red'))">
<text class="ball__num">{{ formatNum(ball.num ?? ball) }}</text>
</view>
<text v-if="ball.zodiac" class="ball-zodiac">{{ ball.zodiac }}</text>
</view>
</template>
</view>
<view v-if="item.status !== 1" class="draw-card__pending">
<text>等待开奖...</text>
<view class="ai-predict-btn" @tap.stop="openAiPopup(item)">
<view class="ai-predict-btn__pulse" />
<text class="ai-predict-btn__icon">AI</text>
<text class="ai-predict-btn__text">智能预测</text>
<text class="ai-predict-btn__arrow"></text>
</view>
</view>
</view>
</z-paging>
<!-- AI分析弹窗 -->
<view v-if="showAiPopup" class="ai-popup-mask" @tap="closeAiPopup" @touchmove.stop.prevent>
<view class="ai-popup" @tap.stop @touchmove.stop>
<view class="ai-popup__header" @touchmove.stop.prevent>
<text class="ai-popup__title">AI智能预测</text>
<view class="ai-popup__close" @tap="closeAiPopup">
<text></text>
</view>
</view>
<view class="ai-popup__vip-bar" @touchmove.stop.prevent>
<view v-if="isVipUser" class="vip-bar vip-bar--vip">
<text class="vip-bar__badge">VIP</text>
<text class="vip-bar__text">尊贵的{{ vipLevelName }}您的到期时间为{{ vipExpireDate }}</text>
</view>
<view v-else class="vip-bar vip-bar--free">
<text class="vip-bar__badge vip-bar__badge--free">免费</text>
<text class="vip-bar__text">每日免费3次AI分析今日剩余{{ freeRemain }}</text>
</view>
</view>
<scroll-view scroll-y class="ai-popup__body" @touchmove.stop>
<view v-if="aiLoading" class="ai-popup__loading">
<view class="ai-loading-anim">
<view class="ai-loading-dot" />
<view class="ai-loading-dot" />
<view class="ai-loading-dot" />
</view>
<text>AI深度分析中请稍候...</text>
</view>
<view v-if="aiError" class="ai-popup__error">
<text>{{ aiError }}</text>
<view class="ai-popup__retry" @tap="retryAiPredict">
<text>重试</text>
</view>
</view>
<view v-if="!aiLoading && aiAnalysis" class="ai-popup__result">
<view v-if="aiAnalysis.hot_numbers?.length" class="ai-block">
<text class="ai-block__label">热号近期高频</text>
<view class="ai-nums">
<view v-for="n in aiAnalysis.hot_numbers" :key="'h' + n" class="ai-num ai-num--hot">
<text>{{ formatNum(n) }}</text>
</view>
</view>
</view>
<view v-if="aiAnalysis.cold_numbers?.length" class="ai-block">
<text class="ai-block__label">冷号近期低频</text>
<view class="ai-nums">
<view v-for="n in aiAnalysis.cold_numbers" :key="'c' + n" class="ai-num ai-num--cold">
<text>{{ formatNum(n) }}</text>
</view>
</view>
</view>
<view v-if="aiAnalysis.trend_analysis" class="ai-block">
<text class="ai-block__label">趋势分析</text>
<text class="ai-block__text">{{ aiAnalysis.trend_analysis }}</text>
</view>
<view v-if="aiAnalysis.recommended_combinations?.length" class="ai-block">
<text class="ai-block__label">推荐组合</text>
<view v-for="(combo, ci) in aiAnalysis.recommended_combinations" :key="ci" class="ai-combo">
<view class="ai-combo__nums">
<view v-for="n in combo.numbers" :key="n" class="ai-num ai-num--rec">
<text>{{ formatNum(n) }}</text>
</view>
</view>
<text class="ai-combo__conf">信心 {{ combo.confidence }}%</text>
</view>
</view>
<view v-if="aiAnalysis.statistics" class="ai-block">
<text class="ai-block__label">统计数据</text>
<view class="ai-stats">
<view v-if="aiAnalysis.statistics.odd_even_ratio" class="ai-stat">
<text class="ai-stat__label">奇偶比</text>
<text class="ai-stat__val">{{ aiAnalysis.statistics.odd_even_ratio }}</text>
</view>
<view v-if="aiAnalysis.statistics.big_small_ratio" class="ai-stat">
<text class="ai-stat__label">大小比</text>
<text class="ai-stat__val">{{ aiAnalysis.statistics.big_small_ratio }}</text>
</view>
<view v-if="aiAnalysis.statistics.sum_range" class="ai-stat">
<text class="ai-stat__label">和值范围</text>
<text class="ai-stat__val">{{ aiAnalysis.statistics.sum_range }}</text>
</view>
</view>
</view>
<view class="ai-block ai-block--disclaimer">
<text class="ai-block__disclaimer">{{ aiAnalysis.disclaimer || '本分析仅基于历史数据统计,不保证准确性,请理性购彩'
}}</text>
</view>
</view>
</scroll-view>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref, shallowRef, computed } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { getDrawResultList, getLatestDrawResult, getLotteryAiPredict } from '@/api/lottery'
import { useUserStore } from '@/stores/user'
const statusBarHeight = ref(0)
const gameCode = ref('')
const gameTemplate = ref('')
const categoryName = ref('')
const drawList = ref<any[]>([])
const latestDraw = ref<any>(null)
const paging = shallowRef()
uni.getSystemInfo({
success: (res) => {
// #ifdef APP-PLUS || MP
statusBarHeight.value = res.statusBarHeight || 44
// #endif
// #ifdef H5
statusBarHeight.value = 0
// #endif
}
})
const formatNum = (n: any) => {
const num = Number(n)
return num < 10 ? '0' + num : String(num)
}
const fetchLatest = async () => {
try {
const data = await getLatestDrawResult({ code: gameCode.value })
latestDraw.value = data
} catch (e) {
console.log('获取最新开奖失败=>', e)
}
}
const queryList = async (pageNo: number, pageSize: number) => {
try {
const res = await getDrawResultList({
code: gameCode.value,
page_no: pageNo,
page_size: pageSize
})
const lists = res?.lists || []
lists.forEach((item: any) => { item.game_name = categoryName.value })
paging.value?.complete(lists)
} catch (e) {
paging.value?.complete(false)
}
}
const goDetail = (item: any) => {
uni.navigateTo({ url: `/packages_lottery/pages/detail?code=${item.code}&issue=${item.issue}` })
}
const userStore = useUserStore()
const vipInfo = computed(() => userStore.userInfo?.vip_info || {})
const isVipUser = computed(() => !!vipInfo.value.is_vip)
const vipLevelName = computed(() => vipInfo.value.vip_level_name || '会员')
const vipExpireDate = computed(() => vipInfo.value.vip_expire_time || '')
const freeRemain = computed(() => vipInfo.value.ai_free_count ?? 3)
const showAiPopup = ref(false)
const aiLoading = ref(false)
const aiError = ref('')
const aiData = ref<any>(null)
const aiAnalysis = computed(() => aiData.value?.analysis || null)
const currentAiItem = ref<any>(null)
const openAiPopup = (item: any) => {
const code = gameCode.value
const name = categoryName.value || ''
if (!code) return
uni.navigateTo({
url: `/packages_lottery/pages/ai_predict?code=${code}&name=${encodeURIComponent(name)}`
})
}
const fetchAiPredict = async () => {
aiLoading.value = true
aiError.value = ''
try {
const data = await getLotteryAiPredict({ code: gameCode.value })
aiData.value = data
} catch (e: any) {
aiError.value = e?.msg || 'AI分析失败,请稍后重试'
} finally {
aiLoading.value = false
}
}
const retryAiPredict = () => {
fetchAiPredict()
}
const closeAiPopup = () => {
showAiPopup.value = false
aiData.value = null
aiError.value = ''
}
const goBack = () => {
uni.navigateBack()
}
onLoad((options: any) => {
gameCode.value = options.code || ''
gameTemplate.value = options.template || ''
categoryName.value = decodeURIComponent(options.name || '开奖记录')
fetchLatest()
})
</script>
<style lang="scss" scoped>
.draw-list-page {
min-height: 100vh;
background: #f5f5f5;
}
.page-no-scroll {
height: 100vh;
overflow: hidden;
}
.dl-nav {
background: linear-gradient(135deg, #185dff, #ff6b6b);
&__content {
height: 44px;
display: flex;
align-items: center;
padding: 0 24rpx;
}
&__back {
width: 60rpx;
height: 44px;
display: flex;
align-items: center;
}
&__title {
flex: 1;
text-align: center;
font-size: 34rpx;
font-weight: bold;
color: #fff;
}
&__placeholder {
width: 60rpx;
}
}
.draw-card {
margin: 20rpx 24rpx 0;
background: #fff;
border-radius: 16rpx;
padding: 24rpx;
&__header {
display: flex;
align-items: center;
gap: 12rpx;
margin-bottom: 20rpx;
}
&__period {
font-size: 28rpx;
font-weight: bold;
color: #222;
}
&__date {
font-size: 24rpx;
color: #999;
margin-left: auto;
}
&__badge {
font-size: 22rpx;
color: #22c55e;
background: #f0fdf4;
padding: 4rpx 16rpx;
border-radius: 8rpx;
&--pending {
color: #f59e0b;
background: #fffbeb;
}
}
&__balls {
display: flex;
align-items: center;
gap: 10rpx;
flex-wrap: wrap;
margin-bottom: 16rpx;
}
&__pending {
text-align: center;
padding: 20rpx;
color: #999;
font-size: 26rpx;
}
}
/* 号码球 */
.ball {
width: 72rpx;
height: 72rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
&--red {
background: linear-gradient(135deg, #185dff, #ff6b6b);
}
&--blue {
background: linear-gradient(135deg, #3b82f6, #60a5fa);
}
&--green {
background: linear-gradient(135deg, #22c55e, #4ade80);
}
&--special {
border: 3rpx solid #fbbf24;
}
&--sm {
width: 56rpx;
height: 56rpx;
}
&__num {
color: #fff;
font-size: 28rpx;
font-weight: bold;
}
&__plus {
font-size: 28rpx;
color: #999;
font-weight: bold;
}
}
.ball--sm .ball__num {
font-size: 22rpx;
}
/* 球+生肖容器 */
.ball-wrap {
display: flex;
flex-direction: column;
align-items: center;
gap: 4rpx;
}
.ball-zodiac {
font-size: 20rpx;
color: #666;
&--special {
color: #185dff;
font-weight: bold;
}
}
/* AI预测按钮 */
.ai-predict-btn {
position: relative;
display: inline-flex;
align-items: center;
gap: 10rpx;
margin-top: 20rpx;
padding: 16rpx 36rpx;
background: linear-gradient(135deg, #667eea, #764ba2);
border-radius: 36rpx;
box-shadow: 0 8rpx 24rpx rgba(102, 126, 234, 0.4);
&__pulse {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 36rpx;
background: linear-gradient(135deg, #667eea, #764ba2);
animation: pulse-glow 2s ease-in-out infinite;
z-index: 0;
}
&__icon {
position: relative;
z-index: 1;
font-size: 24rpx;
color: #fff;
font-weight: 900;
background: rgba(255, 255, 255, 0.3);
padding: 4rpx 14rpx;
border-radius: 10rpx;
letter-spacing: 2rpx;
}
&__text {
position: relative;
z-index: 1;
font-size: 28rpx;
color: #fff;
font-weight: bold;
letter-spacing: 2rpx;
}
&__arrow {
position: relative;
z-index: 1;
font-size: 36rpx;
color: rgba(255, 255, 255, 0.8);
font-weight: bold;
margin-left: 4rpx;
}
}
@keyframes pulse-glow {
0%,
100% {
opacity: 1;
transform: scale(1);
}
50% {
opacity: 0.6;
transform: scale(1.06);
}
}
/* VIP提示条 */
.ai-popup__vip-bar {
padding: 0 30rpx;
}
.vip-bar {
display: flex;
align-items: center;
gap: 12rpx;
padding: 16rpx 24rpx;
border-radius: 12rpx;
margin-top: 16rpx;
&--vip {
background: linear-gradient(135deg, #fef3c7, #fde68a);
}
&--free {
background: #f0f4ff;
}
&__badge {
font-size: 20rpx;
font-weight: 900;
color: #92400e;
background: linear-gradient(135deg, #f59e0b, #d97706);
padding: 4rpx 14rpx;
border-radius: 8rpx;
color: #fff;
letter-spacing: 2rpx;
&--free {
background: linear-gradient(135deg, #6366f1, #8b5cf6);
color: #fff;
}
}
&__text {
font-size: 22rpx;
color: #666;
flex: 1;
}
}
/* 加载动画 */
.ai-loading-anim {
display: flex;
justify-content: center;
gap: 12rpx;
margin-bottom: 20rpx;
}
.ai-loading-dot {
width: 16rpx;
height: 16rpx;
border-radius: 50%;
background: #6366f1;
animation: dot-bounce 1.4s ease-in-out infinite;
&:nth-child(2) {
animation-delay: 0.16s;
}
&:nth-child(3) {
animation-delay: 0.32s;
}
}
@keyframes dot-bounce {
0%,
80%,
100% {
transform: scale(0.6);
opacity: 0.4;
}
40% {
transform: scale(1);
opacity: 1;
}
}
/* AI弹窗 */
.ai-popup-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 999;
display: flex;
align-items: flex-end;
justify-content: center;
}
.ai-popup {
width: 100%;
max-height: 80vh;
background: #fff;
border-radius: 24rpx 24rpx 0 0;
display: flex;
flex-direction: column;
&__header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
}
&__title {
font-size: 32rpx;
font-weight: bold;
color: #222;
}
&__close {
width: 56rpx;
height: 56rpx;
display: flex;
align-items: center;
justify-content: center;
background: #f5f5f5;
border-radius: 50%;
text {
font-size: 28rpx;
color: #666;
}
}
&__body {
flex: 1;
max-height: 70vh;
padding: 24rpx 30rpx 48rpx;
}
&__loading {
text-align: center;
padding: 60rpx;
color: #6366f1;
font-size: 28rpx;
}
&__error {
text-align: center;
padding: 40rpx;
color: #185dff;
font-size: 26rpx;
}
&__retry {
display: inline-flex;
margin-top: 20rpx;
padding: 10rpx 40rpx;
background: #E5EDFF;
border-radius: 24rpx;
text {
color: #185dff;
font-size: 24rpx;
}
}
&__result {
display: flex;
flex-direction: column;
gap: 28rpx;
}
}
/* AI分析内容 */
.ai-block {
&__label {
font-size: 26rpx;
font-weight: bold;
color: #333;
margin-bottom: 12rpx;
display: block;
}
&__text {
font-size: 26rpx;
color: #666;
line-height: 1.6;
}
&--disclaimer {
padding-top: 20rpx;
border-top: 1rpx solid #f0f0f0;
}
&__disclaimer {
font-size: 22rpx;
color: #ccc;
text-align: center;
}
}
.ai-nums {
display: flex;
flex-wrap: wrap;
gap: 12rpx;
}
.ai-num {
width: 64rpx;
height: 64rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
text {
font-size: 26rpx;
font-weight: bold;
color: #fff;
}
&--hot {
background: linear-gradient(135deg, #185dff, #ff6b6b);
}
&--cold {
background: linear-gradient(135deg, #3b82f6, #60a5fa);
}
&--rec {
background: linear-gradient(135deg, #6366f1, #8b5cf6);
}
}
.ai-combo {
display: flex;
align-items: center;
gap: 16rpx;
margin-bottom: 12rpx;
&__nums {
display: flex;
gap: 8rpx;
}
&__conf {
font-size: 22rpx;
color: #6366f1;
background: #eef2ff;
padding: 4rpx 16rpx;
border-radius: 12rpx;
white-space: nowrap;
}
}
.ai-stats {
display: flex;
gap: 24rpx;
flex-wrap: wrap;
}
.ai-stat {
display: flex;
flex-direction: column;
align-items: center;
background: #f8f9fa;
padding: 16rpx 24rpx;
border-radius: 12rpx;
min-width: 140rpx;
&__label {
font-size: 22rpx;
color: #999;
margin-bottom: 8rpx;
}
&__val {
font-size: 28rpx;
font-weight: bold;
color: #333;
}
}
</style>
File diff suppressed because it is too large Load Diff