404 lines
11 KiB
Markdown
404 lines
11 KiB
Markdown
# 前端链接访问示例
|
||
|
||
## 概述
|
||
当用户访问链接页面时(如 `https://你的域名/ABC12345`),前端需要自动请求后端获取链接的详细信息,并根据状态显示相应的内容。
|
||
|
||
## 接口说明
|
||
|
||
### 1. 获取链接状态(主要接口)
|
||
```
|
||
GET /api/link/{codeNo}/status
|
||
```
|
||
|
||
**响应示例:**
|
||
```json
|
||
{
|
||
"codeNo": "ABC12345",
|
||
"batchId": 123,
|
||
"status": "NEW",
|
||
"statusDesc": "新建",
|
||
"expireAt": "2024-01-15T16:30:00",
|
||
"isExpired": false,
|
||
"remainingSeconds": 3600,
|
||
"quantity": 50,
|
||
"times": 10,
|
||
"totalPoints": 500,
|
||
"region": null,
|
||
"machineId": null,
|
||
"loginAt": null,
|
||
"createdAt": "2024-01-15T12:00:00",
|
||
"updatedAt": "2024-01-15T12:00:00"
|
||
}
|
||
```
|
||
|
||
### 2. 检查链接是否存在
|
||
```
|
||
GET /api/link/{codeNo}/exists
|
||
```
|
||
|
||
**响应:** `true` 或 `false`
|
||
|
||
### 3. 检查链接是否有效
|
||
```
|
||
GET /api/link/{codeNo}/valid
|
||
```
|
||
|
||
**响应:** `true` 或 `false`
|
||
|
||
## 前端实现示例
|
||
|
||
### React 组件示例
|
||
|
||
```jsx
|
||
import React, { useState, useEffect } from 'react';
|
||
import { useParams } from 'react-router-dom';
|
||
|
||
const LinkPage = () => {
|
||
const { codeNo } = useParams();
|
||
const [linkStatus, setLinkStatus] = useState(null);
|
||
const [loading, setLoading] = useState(true);
|
||
const [error, setError] = useState(null);
|
||
|
||
useEffect(() => {
|
||
fetchLinkStatus();
|
||
}, [codeNo]);
|
||
|
||
const fetchLinkStatus = async () => {
|
||
try {
|
||
setLoading(true);
|
||
const response = await fetch(`/api/link/${codeNo}/status`);
|
||
|
||
if (!response.ok) {
|
||
if (response.status === 404) {
|
||
setError('链接不存在');
|
||
} else {
|
||
setError('获取链接信息失败');
|
||
}
|
||
return;
|
||
}
|
||
|
||
const data = await response.json();
|
||
setLinkStatus(data);
|
||
} catch (err) {
|
||
setError('网络错误');
|
||
console.error('获取链接状态失败:', err);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
const formatTime = (seconds) => {
|
||
if (seconds <= 0) return '已过期';
|
||
|
||
const hours = Math.floor(seconds / 3600);
|
||
const minutes = Math.floor((seconds % 3600) / 60);
|
||
const secs = seconds % 60;
|
||
|
||
if (hours > 0) {
|
||
return `${hours}小时${minutes}分钟`;
|
||
} else if (minutes > 0) {
|
||
return `${minutes}分钟${secs}秒`;
|
||
} else {
|
||
return `${secs}秒`;
|
||
}
|
||
};
|
||
|
||
if (loading) {
|
||
return <div className="loading">加载中...</div>;
|
||
}
|
||
|
||
if (error) {
|
||
return (
|
||
<div className="error">
|
||
<h2>链接错误</h2>
|
||
<p>{error}</p>
|
||
<p>链接编号: {codeNo}</p>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
if (!linkStatus) {
|
||
return <div>未找到链接信息</div>;
|
||
}
|
||
|
||
return (
|
||
<div className="link-page">
|
||
<div className="link-header">
|
||
<h1>游戏任务链接</h1>
|
||
<div className="link-code">链接编号: {linkStatus.codeNo}</div>
|
||
</div>
|
||
|
||
<div className="link-status">
|
||
<div className="status-item">
|
||
<span className="label">状态:</span>
|
||
<span className={`value status-${linkStatus.status.toLowerCase()}`}>
|
||
{linkStatus.statusDesc}
|
||
</span>
|
||
</div>
|
||
|
||
<div className="status-item">
|
||
<span className="label">任务信息:</span>
|
||
<span className="value">
|
||
打{linkStatus.times}次副本,每次{linkStatus.quantity}点
|
||
(总计{linkStatus.totalPoints}点)
|
||
</span>
|
||
</div>
|
||
|
||
<div className="status-item">
|
||
<span className="label">过期时间:</span>
|
||
<span className="value">
|
||
{linkStatus.expireAt}
|
||
</span>
|
||
</div>
|
||
|
||
<div className="status-item">
|
||
<span className="label">剩余时间:</span>
|
||
<span className={`value ${linkStatus.isExpired ? 'expired' : ''}`}>
|
||
{formatTime(linkStatus.remainingSeconds)}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
{linkStatus.isExpired && (
|
||
<div className="expired-notice">
|
||
<p>⚠️ 此链接已过期,无法使用</p>
|
||
</div>
|
||
)}
|
||
|
||
{!linkStatus.isExpired && linkStatus.status === 'NEW' && (
|
||
<div className="action-section">
|
||
<h3>开始任务</h3>
|
||
<p>点击下方按钮开始执行任务</p>
|
||
<button className="start-btn" onClick={() => startTask()}>
|
||
开始任务
|
||
</button>
|
||
</div>
|
||
)}
|
||
|
||
{linkStatus.status === 'USING' && (
|
||
<div className="task-progress">
|
||
<h3>任务进行中</h3>
|
||
<p>请按照提示完成游戏任务</p>
|
||
<div className="progress-bar">
|
||
<div className="progress-fill" style={{width: '50%'}}></div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{linkStatus.status === 'LOGGED_IN' && (
|
||
<div className="task-complete">
|
||
<h3>任务完成</h3>
|
||
<p>恭喜!任务已完成,奖励点数已发放</p>
|
||
<div className="reward-info">
|
||
获得奖励: {linkStatus.totalPoints} 点
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
<div className="qr-section">
|
||
<h3>扫码访问</h3>
|
||
<img
|
||
src={`/api/link/${codeNo}/qr.png`}
|
||
alt="二维码"
|
||
className="qr-code"
|
||
/>
|
||
<p>使用手机扫描二维码访问</p>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
const startTask = () => {
|
||
// 实现开始任务的逻辑
|
||
console.log('开始任务');
|
||
};
|
||
|
||
export default LinkPage;
|
||
```
|
||
|
||
### Vue 组件示例
|
||
|
||
```vue
|
||
<template>
|
||
<div class="link-page">
|
||
<div v-if="loading" class="loading">加载中...</div>
|
||
|
||
<div v-else-if="error" class="error">
|
||
<h2>链接错误</h2>
|
||
<p>{{ error }}</p>
|
||
<p>链接编号: {{ codeNo }}</p>
|
||
</div>
|
||
|
||
<div v-else-if="linkStatus" class="link-content">
|
||
<div class="link-header">
|
||
<h1>游戏任务链接</h1>
|
||
<div class="link-code">链接编号: {{ linkStatus.codeNo }}</div>
|
||
</div>
|
||
|
||
<div class="link-status">
|
||
<div class="status-item">
|
||
<span class="label">状态:</span>
|
||
<span :class="['value', `status-${linkStatus.status.toLowerCase()}`]">
|
||
{{ linkStatus.statusDesc }}
|
||
</span>
|
||
</div>
|
||
|
||
<div class="status-item">
|
||
<span class="label">任务信息:</span>
|
||
<span class="value">
|
||
打{{ linkStatus.times }}次副本,每次{{ linkStatus.quantity }}点
|
||
(总计{{ linkStatus.totalPoints }}点)
|
||
</span>
|
||
</div>
|
||
|
||
<div class="status-item">
|
||
<span class="label">剩余时间:</span>
|
||
<span :class="['value', { 'expired': linkStatus.isExpired }]">
|
||
{{ formatTime(linkStatus.remainingSeconds) }}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-if="linkStatus.isExpired" class="expired-notice">
|
||
<p>⚠️ 此链接已过期,无法使用</p>
|
||
</div>
|
||
|
||
<div v-if="!linkStatus.isExpired && linkStatus.status === 'NEW'" class="action-section">
|
||
<h3>开始任务</h3>
|
||
<button class="start-btn" @click="startTask">开始任务</button>
|
||
</div>
|
||
|
||
<div class="qr-section">
|
||
<h3>扫码访问</h3>
|
||
<img
|
||
:src="`/api/link/${codeNo}/qr.png`"
|
||
alt="二维码"
|
||
class="qr-code"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
export default {
|
||
name: 'LinkPage',
|
||
data() {
|
||
return {
|
||
codeNo: '',
|
||
linkStatus: null,
|
||
loading: true,
|
||
error: null
|
||
};
|
||
},
|
||
|
||
async mounted() {
|
||
this.codeNo = this.$route.params.codeNo;
|
||
await this.fetchLinkStatus();
|
||
},
|
||
|
||
methods: {
|
||
async fetchLinkStatus() {
|
||
try {
|
||
this.loading = true;
|
||
const response = await fetch(`/api/link/${this.codeNo}/status`);
|
||
|
||
if (!response.ok) {
|
||
if (response.status === 404) {
|
||
this.error = '链接不存在';
|
||
} else {
|
||
this.error = '获取链接信息失败';
|
||
}
|
||
return;
|
||
}
|
||
|
||
this.linkStatus = await response.json();
|
||
} catch (err) {
|
||
this.error = '网络错误';
|
||
console.error('获取链接状态失败:', err);
|
||
} finally {
|
||
this.loading = false;
|
||
}
|
||
},
|
||
|
||
formatTime(seconds) {
|
||
if (seconds <= 0) return '已过期';
|
||
|
||
const hours = Math.floor(seconds / 3600);
|
||
const minutes = Math.floor((seconds % 3600) / 60);
|
||
const secs = seconds % 60;
|
||
|
||
if (hours > 0) {
|
||
return `${hours}小时${minutes}分钟`;
|
||
} else if (minutes > 0) {
|
||
return `${minutes}分钟${secs}秒`;
|
||
} else {
|
||
return `${secs}秒`;
|
||
}
|
||
},
|
||
|
||
startTask() {
|
||
// 实现开始任务的逻辑
|
||
console.log('开始任务');
|
||
}
|
||
}
|
||
};
|
||
</script>
|
||
```
|
||
|
||
## 路由配置
|
||
|
||
### React Router
|
||
```jsx
|
||
import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
||
import LinkPage from './components/LinkPage';
|
||
|
||
function App() {
|
||
return (
|
||
<BrowserRouter>
|
||
<Routes>
|
||
<Route path="/:codeNo" element={<LinkPage />} />
|
||
{/* 其他路由 */}
|
||
</Routes>
|
||
</BrowserRouter>
|
||
);
|
||
}
|
||
```
|
||
|
||
### Vue Router
|
||
```javascript
|
||
import { createRouter, createWebHistory } from 'vue-router';
|
||
import LinkPage from '@/components/LinkPage.vue';
|
||
|
||
const routes = [
|
||
{
|
||
path: '/:codeNo',
|
||
name: 'LinkPage',
|
||
component: LinkPage
|
||
}
|
||
];
|
||
|
||
const router = createRouter({
|
||
history: createWebHistory(),
|
||
routes
|
||
});
|
||
|
||
export default router;
|
||
```
|
||
|
||
## 使用流程
|
||
|
||
1. **用户访问链接**:`https://你的域名/ABC12345`
|
||
2. **前端自动请求**:调用 `/api/link/ABC12345/status` 获取链接信息
|
||
3. **显示相应内容**:根据链接状态显示不同的界面
|
||
4. **实时更新**:可以定时刷新状态,显示剩余时间等
|
||
|
||
## 注意事项
|
||
|
||
1. **错误处理**:处理链接不存在、已过期等情况
|
||
2. **加载状态**:显示加载中的状态,提升用户体验
|
||
3. **响应式设计**:确保在不同设备上都能正常显示
|
||
4. **缓存策略**:可以适当缓存链接状态,减少请求次数
|
||
5. **实时更新**:对于进行中的任务,可以定时刷新状态
|
||
|