(rebase) avoid dist and binary
This commit is contained in:
285
src/components/APKShareManager.tsx
Normal file
285
src/components/APKShareManager.tsx
Normal file
@@ -0,0 +1,285 @@
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import {
|
||||
Card,
|
||||
Button,
|
||||
Table,
|
||||
Typography,
|
||||
Space,
|
||||
message,
|
||||
Tag,
|
||||
Modal,
|
||||
QRCode,
|
||||
Tooltip,
|
||||
Alert,
|
||||
Row,
|
||||
Col
|
||||
} from 'antd'
|
||||
import {
|
||||
LinkOutlined,
|
||||
CopyOutlined,
|
||||
QrcodeOutlined,
|
||||
CheckCircleOutlined,
|
||||
ClockCircleOutlined
|
||||
} from '@ant-design/icons'
|
||||
import type { ColumnsType } from 'antd/es/table'
|
||||
import apiClient from '../services/apiClient'
|
||||
|
||||
const { Text, Paragraph } = Typography
|
||||
|
||||
interface ShareInfo {
|
||||
sessionId: string
|
||||
filename: string
|
||||
shareUrl: string
|
||||
createdAt: string
|
||||
expiresAt: string
|
||||
isExpired: boolean
|
||||
}
|
||||
|
||||
interface APKShareManagerProps {
|
||||
serverUrl: string
|
||||
onShareUrlGenerated?: (shareUrl: string) => void
|
||||
}
|
||||
|
||||
const APKShareManager: React.FC<APKShareManagerProps> = ({
|
||||
serverUrl,
|
||||
onShareUrlGenerated
|
||||
}) => {
|
||||
const [shares, setShares] = useState<ShareInfo[]>([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [qrModalVisible, setQrModalVisible] = useState(false)
|
||||
const [currentShareUrl, setCurrentShareUrl] = useState('')
|
||||
const [currentFilename, setCurrentFilename] = useState('')
|
||||
|
||||
// 获取分享链接列表
|
||||
const fetchShares = async () => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const result = await apiClient.get<any>('/api/apk/shares')
|
||||
|
||||
if (result.success) {
|
||||
setShares(result.shares || [])
|
||||
|
||||
// 如果有新的分享链接,回调通知
|
||||
if (result.shares && result.shares.length > 0 && onShareUrlGenerated) {
|
||||
const latestShare = result.shares[result.shares.length - 1]
|
||||
onShareUrlGenerated(latestShare.shareUrl)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取分享链接失败:', error)
|
||||
message.error('获取分享链接失败')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
// 复制链接
|
||||
const copyShareUrl = async (shareUrl: string) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(shareUrl)
|
||||
message.success('分享链接已复制到剪贴板')
|
||||
} catch (error) {
|
||||
message.error('复制失败,请手动复制')
|
||||
}
|
||||
}
|
||||
|
||||
// 显示二维码
|
||||
const showQRCode = (shareUrl: string, filename: string) => {
|
||||
setCurrentShareUrl(shareUrl)
|
||||
setCurrentFilename(filename)
|
||||
setQrModalVisible(true)
|
||||
}
|
||||
|
||||
// 格式化时间
|
||||
const formatTime = (timeStr: string) => {
|
||||
const date = new Date(timeStr)
|
||||
return date.toLocaleString('zh-CN')
|
||||
}
|
||||
|
||||
// 计算剩余时间
|
||||
const getRemainingTime = (expiresAt: string) => {
|
||||
const now = Date.now()
|
||||
const expiry = new Date(expiresAt).getTime()
|
||||
const remaining = expiry - now
|
||||
|
||||
if (remaining <= 0) {
|
||||
return '已过期'
|
||||
}
|
||||
|
||||
const minutes = Math.floor(remaining / 60000)
|
||||
const seconds = Math.floor((remaining % 60000) / 1000)
|
||||
|
||||
if (minutes > 0) {
|
||||
return `${minutes}分${seconds}秒`
|
||||
} else {
|
||||
return `${seconds}秒`
|
||||
}
|
||||
}
|
||||
|
||||
// 表格列定义
|
||||
const columns: ColumnsType<ShareInfo> = [
|
||||
{
|
||||
title: '文件名',
|
||||
dataIndex: 'filename',
|
||||
key: 'filename',
|
||||
render: (filename) => (
|
||||
<Space>
|
||||
<Text strong>{filename}</Text>
|
||||
</Space>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
render: (_, record) => (
|
||||
<Tag
|
||||
icon={record.isExpired ? <ClockCircleOutlined /> : <CheckCircleOutlined />}
|
||||
color={record.isExpired ? 'red' : 'green'}
|
||||
>
|
||||
{record.isExpired ? '已过期' : '活跃'}
|
||||
</Tag>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: '剩余时间',
|
||||
key: 'remaining',
|
||||
render: (_, record) => (
|
||||
<Text type={record.isExpired ? 'danger' : 'success'}>
|
||||
{getRemainingTime(record.expiresAt)}
|
||||
</Text>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createdAt',
|
||||
key: 'createdAt',
|
||||
render: (createdAt) => formatTime(createdAt)
|
||||
},
|
||||
{
|
||||
title: '过期时间',
|
||||
dataIndex: 'expiresAt',
|
||||
key: 'expiresAt',
|
||||
render: (expiresAt) => formatTime(expiresAt)
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
render: (_, record) => (
|
||||
<Space>
|
||||
<Tooltip title="显示二维码">
|
||||
<Button
|
||||
type="default"
|
||||
size="small"
|
||||
icon={<QrcodeOutlined />}
|
||||
onClick={() => showQRCode(record.shareUrl, record.filename)}
|
||||
disabled={record.isExpired}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
]
|
||||
|
||||
// 组件挂载时获取数据
|
||||
useEffect(() => {
|
||||
fetchShares()
|
||||
|
||||
// 定时刷新分享列表
|
||||
const interval = setInterval(fetchShares, 10000) // 每10秒刷新一次
|
||||
|
||||
return () => clearInterval(interval)
|
||||
}, [serverUrl])
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={
|
||||
<Space>
|
||||
<LinkOutlined />
|
||||
<span>Cloudflare 分享链接</span>
|
||||
</Space>
|
||||
}
|
||||
extra={
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={fetchShares}
|
||||
loading={loading}
|
||||
>
|
||||
刷新
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
{shares.length === 0 ? (
|
||||
<Alert
|
||||
message="暂无分享链接"
|
||||
description="构建APK后将自动生成Cloudflare分享链接,有效期10分钟"
|
||||
type="info"
|
||||
showIcon
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<Row gutter={[16, 16]} style={{ marginBottom: 16 }}>
|
||||
<Col span={24}>
|
||||
<Alert
|
||||
message="分享链接说明"
|
||||
description={
|
||||
<div>
|
||||
<p>• 每次构建APK后会自动生成Cloudflare临时分享链接</p>
|
||||
<p>• 分享链接有效期为10分钟,过期后自动失效</p>
|
||||
<p>• 可以通过二维码或链接分享给他人下载</p>
|
||||
<p>• 建议及时下载,避免链接过期</p>
|
||||
</div>
|
||||
}
|
||||
type="info"
|
||||
showIcon
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={shares || []}
|
||||
rowKey="sessionId"
|
||||
loading={loading}
|
||||
pagination={false}
|
||||
size="small"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* 二维码模态框 */}
|
||||
<Modal
|
||||
title={`${currentFilename} - 分享二维码`}
|
||||
open={qrModalVisible}
|
||||
onCancel={() => setQrModalVisible(false)}
|
||||
footer={[
|
||||
<Button key="copy" onClick={() => copyShareUrl(currentShareUrl)}>
|
||||
<CopyOutlined /> 复制链接
|
||||
</Button>,
|
||||
<Button key="close" onClick={() => setQrModalVisible(false)}>
|
||||
关闭
|
||||
</Button>
|
||||
]}
|
||||
width={400}
|
||||
>
|
||||
<div style={{ textAlign: 'center', padding: '20px 0' }}>
|
||||
<QRCode value={currentShareUrl} size={200} />
|
||||
<Paragraph
|
||||
copyable
|
||||
style={{
|
||||
marginTop: 16,
|
||||
wordBreak: 'break-all',
|
||||
fontSize: '12px'
|
||||
}}
|
||||
>
|
||||
{currentShareUrl}
|
||||
</Paragraph>
|
||||
<Text type="secondary" style={{ fontSize: '12px' }}>
|
||||
使用手机扫描二维码或点击链接下载APK
|
||||
</Text>
|
||||
</div>
|
||||
</Modal>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export default APKShareManager
|
||||
Reference in New Issue
Block a user