This commit is contained in:
2026-01-14 14:17:38 +08:00
commit f1e2ff6563
126 changed files with 13636 additions and 0 deletions

View File

@@ -0,0 +1,96 @@
<template>
<a-card>
<a-form :model="formData" layout="vertical" @submit="handleSubmit">
<a-form-item
label="授权码"
:validate-status="error ? 'error' : ''"
:help="error || '请输入授权码至少16位字母和数字'"
>
<a-input
v-model="formData.licenseCode"
placeholder="请输入授权码"
:max-length="100"
allow-clear
@input="handleInput"
/>
</a-form-item>
<a-form-item>
<a-space>
<a-button type="primary" @click="handleSubmit" :loading="loading">
激活
</a-button>
<a-button @click="handleReset">重置</a-button>
</a-space>
</a-form-item>
</a-form>
</a-card>
</template>
<script setup>
import { ref } from 'vue'
import { Message } from '@arco-design/web-vue'
import { ActivateLicense } from '../../wailsjs/go/main/App'
const emit = defineEmits(['activated', 'reset'])
const formData = ref({
licenseCode: ''
})
const loading = ref(false)
const error = ref('')
const handleInput = () => {
error.value = ''
}
const handleSubmit = async () => {
if (!formData.value.licenseCode || formData.value.licenseCode.trim() === '') {
error.value = '授权码不能为空'
return
}
const cleaned = formData.value.licenseCode.replace(/[\s-]/g, '')
if (cleaned.length < 16) {
error.value = '授权码长度不足至少需要16位字符'
return
}
loading.value = true
error.value = ''
try {
const result = await ActivateLicense(formData.value.licenseCode)
if (result.success) {
Message.success(result.message || '激活成功')
emit('activated', result.data)
handleReset()
} else {
error.value = result.message || '激活失败'
Message.error(result.message || '激活失败')
}
} catch (err) {
error.value = err.message || '激活失败,请重试'
Message.error('激活失败:' + (err.message || '未知错误'))
} finally {
loading.value = false
}
}
const handleReset = () => {
formData.value.licenseCode = ''
error.value = ''
emit('reset')
}
defineExpose({
setLoading: (val) => {
loading.value = val
}
})
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,42 @@
<template>
<div class="auth-page">
<div class="container">
<a-row :gutter="12">
<a-col :span="12">
<ActivateForm @activated="handleActivated" />
</a-col>
<a-col :span="12">
<AuthStatus ref="authStatusRef" />
</a-col>
</a-row>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import ActivateForm from './ActivateForm.vue'
import AuthStatus from './AuthStatus.vue'
const authStatusRef = ref(null)
const handleActivated = () => {
if (authStatusRef.value) {
authStatusRef.value.refresh()
}
}
</script>
<style scoped>
.auth-page {
width: 100%;
height: 100%;
padding: 12px;
overflow: auto;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
</style>

View File

@@ -0,0 +1,104 @@
<template>
<a-card>
<template #title>授权状态</template>
<a-descriptions :column="1" bordered v-if="authStatus">
<a-descriptions-item label="激活状态">
<a-tag :color="authStatus.is_activated ? 'green' : 'red'">
{{ authStatus.is_activated ? '已激活' : '未激活' }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="状态信息">
{{ authStatus.message }}
</a-descriptions-item>
<a-descriptions-item label="授权码" v-if="authStatus.license_code">
{{ formatLicenseCode(authStatus.license_code) }}
</a-descriptions-item>
<a-descriptions-item label="激活时间" v-if="authStatus.activated_at">
{{ formatDate(authStatus.activated_at) }}
</a-descriptions-item>
<a-descriptions-item label="过期时间" v-if="authStatus.expires_at">
{{ formatDate(authStatus.expires_at) }}
</a-descriptions-item>
<a-descriptions-item label="设备ID">
<a-typography-text copyable>{{ deviceID }}</a-typography-text>
</a-descriptions-item>
</a-descriptions>
<a-empty v-else description="加载中..." />
<template #actions>
<a-button @click="handleRefresh" :loading="loading">刷新状态</a-button>
</template>
</a-card>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { Message } from '@arco-design/web-vue'
import { GetAuthStatus, GetDeviceID } from '../../wailsjs/go/main/App'
const authStatus = ref(null)
const deviceID = ref('')
const loading = ref(false)
const formatLicenseCode = (code) => {
if (!code) return ''
const cleaned = code.replace(/[\s-]/g, '')
return cleaned.match(/.{1,4}/g)?.join('-') || cleaned
}
const formatDate = (dateStr) => {
if (!dateStr) return '-'
try {
const date = new Date(dateStr)
return date.toLocaleString('zh-CN')
} catch {
return dateStr
}
}
const loadAuthStatus = async () => {
loading.value = true
try {
const [statusResult, deviceResult] = await Promise.all([
GetAuthStatus(),
GetDeviceID()
])
if (statusResult.success) {
authStatus.value = statusResult.data
} else {
Message.error(statusResult.message || '获取授权状态失败')
}
if (deviceResult) {
deviceID.value = deviceResult
}
} catch (err) {
Message.error('获取授权状态失败:' + (err.message || '未知错误'))
} finally {
loading.value = false
}
}
const handleRefresh = () => {
loadAuthStatus()
}
onMounted(() => {
loadAuthStatus()
})
defineExpose({
refresh: loadAuthStatus
})
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,134 @@
<template>
<a-card>
<template #title>数据备份与恢复</template>
<a-space direction="vertical" style="width: 100%">
<!-- 操作按钮 -->
<a-space>
<a-button type="primary" @click="handleBackup" :loading="backing">
创建备份
</a-button>
<a-button @click="handleRefreshList" :loading="loading">
刷新列表
</a-button>
</a-space>
<!-- 备份列表 -->
<a-table
v-if="backups.length > 0"
:columns="columns"
:data="backups"
:pagination="{ pageSize: 10 }"
>
<template #operation="{ record }">
<a-space>
<a-button type="primary" size="small" @click="handleRestore(record)">
恢复
</a-button>
</a-space>
</template>
</a-table>
<a-empty v-else description="暂无备份文件" />
<!-- 恢复确认对话框 -->
<a-modal
v-model:visible="restoreVisible"
title="确认恢复"
@ok="confirmRestore"
@cancel="cancelRestore"
>
<p>确定要恢复此备份吗恢复后将替换当前数据库此操作不可逆</p>
<p><strong>备份文件</strong>{{ selectedBackup?.file_name }}</p>
<p><strong>创建时间</strong>{{ selectedBackup?.created_at }}</p>
</a-modal>
</a-space>
</a-card>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { Message, Modal } from '@arco-design/web-vue'
import { BackupData, RestoreData, ListBackups } from '../../wailsjs/go/main/App'
const backups = ref([])
const loading = ref(false)
const backing = ref(false)
const restoreVisible = ref(false)
const selectedBackup = ref(null)
const columns = [
{ title: '文件名', dataIndex: 'file_name', width: 250 },
{ title: '文件大小', dataIndex: 'file_size', width: 120, slotName: 'size' },
{ title: '创建时间', dataIndex: 'created_at', width: 180 },
{ title: '操作', slotName: 'operation', width: 100 }
]
// 加载备份列表
const loadBackups = async () => {
loading.value = true
try {
const result = await ListBackups()
backups.value = result.backups || []
} catch (error) {
console.error('获取备份列表失败:', error)
Message.error('获取备份列表失败')
} finally {
loading.value = false
}
}
// 创建备份
const handleBackup = async () => {
backing.value = true
try {
const result = await BackupData()
Message.success(`备份创建成功: ${result.file_name}`)
await loadBackups()
} catch (error) {
console.error('备份失败:', error)
Message.error('备份失败:' + (error.message || '未知错误'))
} finally {
backing.value = false
}
}
// 恢复备份
const handleRestore = (backup) => {
selectedBackup.value = backup
restoreVisible.value = true
}
// 确认恢复
const confirmRestore = async () => {
if (!selectedBackup.value) {
return
}
try {
await RestoreData(selectedBackup.value.backup_path)
Message.success('数据恢复成功')
restoreVisible.value = false
selectedBackup.value = null
} catch (error) {
console.error('恢复失败:', error)
Message.error('恢复失败:' + (error.message || '未知错误'))
}
}
// 取消恢复
const cancelRestore = () => {
restoreVisible.value = false
selectedBackup.value = null
}
// 刷新列表
const handleRefreshList = () => {
loadBackups()
}
onMounted(() => {
loadBackups()
})
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,48 @@
<template>
<a-card>
<template #title>
<span>数据统计</span>
<a-button type="text" size="small" @click="handleRefresh" style="margin-left: 10px">
刷新
</a-button>
</template>
<a-descriptions :column="2" bordered>
<a-descriptions-item label="数据总量">
{{ stats?.total_count || 0 }}
</a-descriptions-item>
<a-descriptions-item label="最新期号">
{{ stats?.latest_issue || '-' }}
</a-descriptions-item>
</a-descriptions>
</a-card>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { Message } from '@arco-design/web-vue'
import { GetDataStats } from '../../wailsjs/go/main/App'
const stats = ref(null)
const loadStats = async () => {
try {
const data = await GetDataStats()
stats.value = data
} catch (error) {
console.error('获取统计数据失败:', error)
Message.error('获取统计数据失败')
}
}
const handleRefresh = async () => {
await loadStats()
Message.success('统计数据已刷新')
}
onMounted(() => {
loadStats()
})
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,182 @@
<template>
<a-card>
<template #title>离线数据包管理</template>
<a-space direction="vertical" style="width: 100%">
<!-- 检查更新 -->
<a-space>
<a-input
v-model="remoteURL"
placeholder="输入数据包信息 URL"
style="width: 400px"
/>
<a-button type="primary" @click="handleCheckUpdate" :loading="checking">
检查更新
</a-button>
</a-space>
<!-- 更新信息 -->
<a-alert v-if="updateInfo" :type="updateInfo.need_update ? 'info' : 'success'">
<template #title>{{ updateInfo.need_update ? '发现新数据包' : '已是最新版本' }}</template>
<div v-if="updateInfo.need_update">
<p>版本{{ updateInfo.version }}</p>
<p>数据总数{{ updateInfo.total_count }}</p>
<p>最新期号{{ updateInfo.latest_issue }}</p>
<p>文件大小{{ formatFileSize(updateInfo.package_size) }}</p>
<p>发布日期{{ updateInfo.release_date }}</p>
<a-button type="primary" @click="handleDownload" :loading="downloading" style="margin-top: 10px">
下载数据包
</a-button>
</div>
</a-alert>
<!-- 下载进度 -->
<a-progress
v-if="downloadProgress > 0 && downloadProgress < 100"
:percent="downloadProgress"
:status="downloadStatus"
/>
<!-- 本地数据包列表 -->
<a-divider />
<a-space>
<a-button @click="handleRefreshList" :loading="loading">刷新列表</a-button>
</a-space>
<a-table
v-if="packages.length > 0"
:columns="columns"
:data="packages"
:pagination="{ pageSize: 10 }"
>
<template #operation="{ record }">
<a-space>
<a-button type="primary" size="small" @click="handleImport(record)">
导入
</a-button>
</a-space>
</template>
</a-table>
<a-empty v-else description="暂无本地数据包" />
</a-space>
</a-card>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { Message } from '@arco-design/web-vue'
import { DownloadPackage, ImportPackage, CheckPackageUpdate, ListLocalPackages } from '../../wailsjs/go/main/App'
const remoteURL = ref('')
const checking = ref(false)
const downloading = ref(false)
const loading = ref(false)
const updateInfo = ref(null)
const downloadProgress = ref(0)
const downloadStatus = ref('normal')
const packages = ref([])
const columns = [
{ title: '文件名', dataIndex: 'name', width: 300 },
{ title: '文件路径', dataIndex: 'path', width: 400 },
{ title: '操作', slotName: 'operation', width: 100 }
]
// 格式化文件大小
const formatFileSize = (bytes) => {
if (!bytes) return '0 B'
const k = 1024
const sizes = ['B', 'KB', 'MB', 'GB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]
}
// 检查更新
const handleCheckUpdate = async () => {
if (!remoteURL.value) {
Message.warning('请输入数据包信息 URL')
return
}
checking.value = true
try {
const result = await CheckPackageUpdate(remoteURL.value)
updateInfo.value = result
if (result.need_update) {
Message.success('发现新数据包')
} else {
Message.success('已是最新版本')
}
} catch (error) {
console.error('检查更新失败:', error)
Message.error('检查更新失败:' + (error.message || '未知错误'))
} finally {
checking.value = false
}
}
// 下载数据包
const handleDownload = async () => {
if (!updateInfo.value?.download_url) {
Message.warning('下载地址不存在')
return
}
downloading.value = true
downloadProgress.value = 0
downloadStatus.value = 'active'
try {
const result = await DownloadPackage(updateInfo.value.download_url)
downloadProgress.value = 100
downloadStatus.value = 'success'
Message.success('数据包下载成功')
await loadPackages()
} catch (error) {
console.error('下载失败:', error)
downloadStatus.value = 'exception'
Message.error('下载失败:' + (error.message || '未知错误'))
} finally {
downloading.value = false
}
}
// 导入数据包
const handleImport = async (pkg) => {
try {
const result = await ImportPackage(pkg.path)
Message.success(`导入成功:导入 ${result.imported_count} 条数据`)
} catch (error) {
console.error('导入失败:', error)
Message.error('导入失败:' + (error.message || '未知错误'))
}
}
// 加载本地数据包列表
const loadPackages = async () => {
loading.value = true
try {
const result = await ListLocalPackages()
packages.value = (result.packages || []).map(path => ({
name: path.split(/[/\\]/).pop(),
path: path
}))
} catch (error) {
console.error('获取数据包列表失败:', error)
Message.error('获取数据包列表失败')
} finally {
loading.value = false
}
}
// 刷新列表
const handleRefreshList = () => {
loadPackages()
}
onMounted(() => {
loadPackages()
})
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,107 @@
<template>
<a-card>
<template #title>数据同步</template>
<a-space direction="vertical" style="width: 100%">
<!-- 同步状态 -->
<a-descriptions :column="2" bordered>
<a-descriptions-item label="本地数据量">
{{ syncStatus?.local_count || 0 }}
</a-descriptions-item>
<a-descriptions-item label="本地最新期号">
{{ syncStatus?.local_latest_issue || '-' }}
</a-descriptions-item>
<a-descriptions-item label="远程数据量">
{{ syncStatus?.remote_count || 0 }}
</a-descriptions-item>
<a-descriptions-item label="远程最新期号">
{{ syncStatus?.remote_latest_issue || '-' }}
</a-descriptions-item>
<a-descriptions-item label="同步状态" :span="2">
<a-tag :color="syncStatus?.need_sync ? 'orange' : 'green'">
{{ syncStatus?.need_sync ? '需要同步' : '已是最新' }}
</a-tag>
</a-descriptions-item>
</a-descriptions>
<!-- 操作按钮 -->
<a-space>
<a-button type="primary" @click="handleSync" :loading="syncing">
开始同步
</a-button>
<a-button @click="handleRefreshStatus" :loading="refreshing">
刷新状态
</a-button>
</a-space>
<!-- 同步结果 -->
<a-alert v-if="syncResult" :type="syncResult.error_count > 0 ? 'warning' : 'success'">
<template #title>同步完成</template>
<div>总数据量{{ syncResult.total_count }}</div>
<div>同步数量{{ syncResult.synced_count }}</div>
<div>新增数量{{ syncResult.new_count }}</div>
<div>更新数量{{ syncResult.updated_count }}</div>
<div v-if="syncResult.error_count > 0">错误数量{{ syncResult.error_count }}</div>
<div v-if="syncResult.latest_issue">最新期号{{ syncResult.latest_issue }}</div>
</a-alert>
</a-space>
</a-card>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { Message } from '@arco-design/web-vue'
import { SyncData, GetSyncStatus } from '../../wailsjs/go/main/App'
const syncStatus = ref(null)
const syncing = ref(false)
const refreshing = ref(false)
const syncResult = ref(null)
// 加载同步状态
const loadSyncStatus = async () => {
try {
const status = await GetSyncStatus()
syncStatus.value = status
} catch (error) {
console.error('获取同步状态失败:', error)
Message.error('获取同步状态失败')
}
}
// 执行同步
const handleSync = async () => {
syncing.value = true
syncResult.value = null
try {
const result = await SyncData()
syncResult.value = result
Message.success('同步完成')
// 同步后刷新状态
await loadSyncStatus()
} catch (error) {
console.error('同步失败:', error)
Message.error('同步失败:' + (error.message || '未知错误'))
} finally {
syncing.value = false
}
}
// 刷新状态
const handleRefreshStatus = async () => {
refreshing.value = true
try {
await loadSyncStatus()
Message.success('状态已刷新')
} finally {
refreshing.value = false
}
}
onMounted(() => {
loadSyncStatus()
})
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,179 @@
<template>
<a-card>
<template #title>查询条件</template>
<a-form :model="formData" layout="vertical">
<!-- 红球输入 -->
<a-form-item label="红球号码" class="inline-form-item">
<div class="red-balls">
<a-input-number
v-for="(ball, index) in redBalls"
:key="index"
v-model="redBalls[index]"
:min="1"
:max="33"
:precision="0"
placeholder="红球"
class="red-ball-input"
/>
</div>
</a-form-item>
<!-- 蓝球输入 -->
<a-form-item label="蓝球号码" class="inline-form-item">
<a-input-number
v-model="formData.blueBall"
:min="1"
:max="16"
:precision="0"
placeholder="蓝球(不填表示不限制)"
style="width: 200px"
:allow-clear="true"
/>
</a-form-item>
<!-- 蓝球筛选 -->
<a-form-item label="蓝球筛选范围">
<div class="blue-ball-filter">
<div class="blue-ball-item">
<a-checkbox v-model="selectAll" @change="handleSelectAll" />
<span class="blue-ball-label">全选</span>
</div>
<a-checkbox-group v-model="formData.blueBallRange" class="blue-checkboxes">
<div v-for="i in 16" :key="i" class="blue-ball-item">
<a-checkbox :value="i" />
<span class="blue-ball-label">蓝球{{ i }}</span>
</div>
</a-checkbox-group>
</div>
</a-form-item>
<!-- 操作按钮 -->
<a-form-item>
<a-space>
<a-button type="primary" @click="handleQuery" :loading="loading">查询</a-button>
<a-button @click="handleReset">重置</a-button>
</a-space>
</a-form-item>
</a-form>
</a-card>
</template>
<script setup>
import { ref, watch, computed } from 'vue'
const emit = defineEmits(['query', 'reset'])
const formData = ref({
blueBall: undefined,
blueBallRange: []
})
const redBalls = ref([null, null, null, null, null, null])
const loading = ref(false)
const selectAll = ref(true)
// 全选逻辑
const handleSelectAll = (checked) => {
if (checked) {
formData.value.blueBallRange = Array.from({ length: 16 }, (_, i) => i + 1)
} else {
formData.value.blueBallRange = []
}
}
// 监听蓝球筛选变化
watch(() => formData.value.blueBallRange, (newVal) => {
selectAll.value = newVal.length === 16
}, { deep: true })
// 查询
const handleQuery = () => {
const redBallsFiltered = redBalls.value.filter(ball => ball !== null && ball > 0)
emit('query', {
redBalls: redBallsFiltered,
blueBall: formData.value.blueBall || 0,
blueBallRange: formData.value.blueBallRange.length > 0 ? formData.value.blueBallRange : []
})
}
// 重置
const handleReset = () => {
redBalls.value = [null, null, null, null, null, null]
formData.value.blueBall = undefined
formData.value.blueBallRange = Array.from({ length: 16 }, (_, i) => i + 1)
selectAll.value = true
emit('reset')
}
defineExpose({
setLoading: (val) => {
loading.value = val
}
})
</script>
<style scoped>
:deep(.inline-form-item) {
display: flex;
align-items: center;
margin-bottom: 8px;
}
:deep(.arco-form-item) {
margin-bottom: 8px;
}
:deep(.inline-form-item .arco-form-item-label) {
margin-bottom: 0 !important;
margin-right: 12px;
padding-right: 12px;
flex-shrink: 0;
width: auto;
line-height: 32px;
}
:deep(.inline-form-item .arco-form-item-content) {
flex: 1;
display: flex;
align-items: center;
}
.red-balls {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.red-ball-input {
width: 90px;
}
.blue-ball-filter {
display: flex;
flex-wrap: wrap;
gap: 12px;
align-items: flex-start;
}
.blue-ball-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
min-width: 50px;
}
.blue-ball-label {
font-size: 12px;
color: var(--color-text-2);
text-align: center;
line-height: 1.2;
}
.blue-checkboxes {
display: flex;
flex-wrap: wrap;
gap: 12px;
align-items: flex-start;
}
</style>

View File

@@ -0,0 +1,102 @@
<template>
<div class="query-page">
<div class="container">
<!-- 查询条件 -->
<QueryForm
ref="queryFormRef"
@query="handleQuery"
@reset="handleReset"
/>
<!-- 查询结果 -->
<ResultPanel
v-if="queryResult"
:summary="queryResult.summary || []"
:details="queryResult.details || []"
:query-red-balls="currentQuery.redBalls"
:query-blue-ball="currentQuery.blueBall"
:query-blue-ball-range="currentQuery.blueBallRange"
@summary-click="handleSummaryClick"
style="margin-top: 12px"
/>
<!-- 加载中 -->
<a-spin v-if="loading" :style="{ width: '100%', marginTop: '12px' }" />
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { Message } from '@arco-design/web-vue'
import QueryForm from './QueryForm.vue'
import ResultPanel from './ResultPanel.vue'
import { QueryHistory } from '../../wailsjs/go/main/App'
const queryFormRef = ref(null)
const loading = ref(false)
const queryResult = ref(null)
const currentQuery = ref({
redBalls: [],
blueBall: 0,
blueBallRange: []
})
// 查询
const handleQuery = async (queryParams) => {
if (!queryParams.redBalls || queryParams.redBalls.length === 0) {
Message.warning('请至少输入一个红球号码')
return
}
loading.value = true
currentQuery.value = queryParams
try {
const result = await QueryHistory({
red_balls: queryParams.redBalls,
blue_ball: queryParams.blueBall,
blue_ball_range: queryParams.blueBallRange
})
if (result) {
queryResult.value = result
Message.success('查询成功')
}
} catch (error) {
console.error('查询失败:', error)
Message.error('查询失败:' + (error.message || '未知错误'))
} finally {
loading.value = false
}
}
// 重置
const handleReset = () => {
queryResult.value = null
currentQuery.value = {
redBalls: [],
blueBall: 0,
blueBallRange: []
}
}
// 点击汇总项
const handleSummaryClick = (item) => {
console.log('点击汇总项:', item)
}
</script>
<style scoped>
.query-page {
width: 100%;
height: 100%;
padding: 12px;
overflow: auto;
}
.container {
max-width: 1400px;
margin: 0 auto;
}
</style>

View File

@@ -0,0 +1,198 @@
<template>
<a-layout class="result-layout">
<!-- 左侧汇总列表 -->
<a-layout-sider width="280" class="summary-sider">
<a-card>
<template #title>查询汇总</template>
<a-list v-if="summary.length > 0" :data="summary">
<template #item="{ item }">
<a-list-item class="summary-item" @click="handleSummaryClick(item)">
<a-list-item-meta>
<template #title>
<span>{{ item.type }}{{ item.count }}</span>
</template>
</a-list-item-meta>
<template #actions>
<a-button type="text" size="small">显示历史开奖</a-button>
</template>
</a-list-item>
</template>
</a-list>
<a-empty v-else description="暂无数据" />
</a-card>
</a-layout-sider>
<!-- 右侧详情列表 -->
<a-layout-content class="detail-content">
<a-card>
<template #title>
<span>查询结果详情</span>
<a-button v-if="selectedSummary" type="text" size="small" @click="clearSelection" style="margin-left: 8px">
清除筛选
</a-button>
</template>
<a-table
v-if="displayDetails.length > 0"
:columns="columns"
:data="displayDetails"
:pagination="paginationConfig"
size="mini"
row-key="id"
@page-change="handlePageChange"
@page-size-change="handlePageSizeChange"
>
<template #ball="{ record, column }">
<span
:style="getBallStyle(record, column.dataIndex)"
>
{{ record[column.dataIndex] }}
</span>
</template>
</a-table>
<a-empty v-else description="暂无数据" />
</a-card>
</a-layout-content>
</a-layout>
</template>
<script setup>
import { ref, computed } from 'vue'
const props = defineProps({
summary: {
type: Array,
default: () => []
},
details: {
type: Array,
default: () => []
},
queryRedBalls: {
type: Array,
default: () => []
},
queryBlueBall: {
type: Number,
default: 0
},
queryBlueBallRange: {
type: Array,
default: () => []
}
})
const emit = defineEmits(['summary-click'])
const selectedSummary = ref(null)
const pageSize = ref(20)
// 分页配置
const paginationConfig = computed(() => ({
pageSize: pageSize.value,
pageSizeOptions: [20, 30, 50, 100, 200],
showPageSize: true,
showTotal: (total) => `${total} 条记录`
}))
// 表格列定义
const columns = [
{ title: '期号', dataIndex: 'issue_number', align: 'center' },
{ title: '红球1', dataIndex: 'red_ball_1', slotName: 'ball', align: 'center' },
{ title: '红球2', dataIndex: 'red_ball_2', slotName: 'ball', align: 'center' },
{ title: '红球3', dataIndex: 'red_ball_3', slotName: 'ball', align: 'center' },
{ title: '红球4', dataIndex: 'red_ball_4', slotName: 'ball', align: 'center' },
{ title: '红球5', dataIndex: 'red_ball_5', slotName: 'ball', align: 'center' },
{ title: '红球6', dataIndex: 'red_ball_6', slotName: 'ball', align: 'center' },
{ title: '蓝球', dataIndex: 'blue_ball', slotName: 'ball', align: 'center' }
]
// 显示的数据
const displayDetails = computed(() => {
if (selectedSummary.value) {
return selectedSummary.value.histories || []
}
return props.details
})
// 点击汇总项
const handleSummaryClick = (item) => {
selectedSummary.value = item
emit('summary-click', item)
}
// 清除筛选
const clearSelection = () => {
selectedSummary.value = null
}
// 分页变化
const handlePageChange = (page) => {
// 页面变化处理(如果需要)
}
// 分页大小变化
const handlePageSizeChange = (size) => {
pageSize.value = size
}
// 获取球的样式
const getBallStyle = (record, field) => {
if (field.startsWith('red_ball_')) {
const ballValue = record[field]
// 检查是否在查询的红球列表中
if (props.queryRedBalls.includes(ballValue)) {
return { color: '#F53F3F', fontWeight: 'bold' }
}
} else if (field === 'blue_ball') {
const ballValue = record[field]
// 检查是否匹配查询的蓝球(精确匹配或在筛选范围内)
if (props.queryBlueBall > 0 && ballValue === props.queryBlueBall) {
return { color: '#165DFF', fontWeight: 'bold' }
}
// 检查是否在蓝球筛选范围内
if (props.queryBlueBallRange.length > 0 && props.queryBlueBallRange.includes(ballValue)) {
return { color: '#165DFF', fontWeight: 'bold' }
}
}
return {}
}
</script>
<style scoped>
.result-layout {
height: 100%;
}
.summary-sider {
background: var(--color-bg-1);
border-right: 1px solid var(--color-border);
}
.summary-item {
cursor: pointer;
transition: background 0.2s;
}
.summary-item:hover {
background: var(--color-fill-2);
}
.detail-content {
padding: 0;
background: var(--color-bg-1);
}
:deep(.arco-card-body) {
padding: 12px;
}
:deep(.arco-table) {
width: 100%;
min-width: 0;
}
:deep(.arco-table table) {
width: 100%;
min-width: 0;
}
</style>