12 KiB
12 KiB
前端链接访问示例
概述
当用户访问链接页面时(如 https://你的域名/ABC12345),前端需要自动请求后端获取链接的详细信息,并根据状态显示相应的内容。
接口说明
1. 获取链接状态(主要接口)
推荐格式(路径参数):
GET /api/link/{code}/status
兼容格式(查询参数,兼容旧版):
GET /api/link/status?code={code}
GET /api/link/status?codeNo={codeNo}
GET /api/link/status?linkId={linkId}
💡 推荐使用路径参数格式,因为复制粘贴时不容易丢失参数,更符合 RESTful 规范。查询参数格式保留用于兼容已生成的旧链接。
响应示例:
{
"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 组件示例
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 组件示例
<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
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
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;
使用流程
- 用户访问链接:
https://你的域名/ABC12345 - 前端自动请求:调用
/api/link/ABC12345/status获取链接信息(使用路径参数,更不容易丢失) - 显示相应内容:根据链接状态显示不同的界面
- 实时更新:可以定时刷新状态,显示剩余时间等
接口格式说明
系统同时支持两种访问格式,保证新旧链接都能正常使用:
方式一:路径参数格式(推荐 ⭐)
GET /api/link/{code}/status
示例:
fetch('/api/link/ABC12345/status')
优势:
- ✅ 复制粘贴时不会丢失参数
- ✅ 符合 RESTful 设计规范
- ✅ URL 结构更清晰
- ✅ 浏览器地址栏直接可见完整路径
方式二:查询参数格式(兼容旧版)
GET /api/link/status?code={code}
GET /api/link/status?codeNo={codeNo}
GET /api/link/status?linkId={linkId}
示例:
fetch('/api/link/status?code=ABC12345')
fetch('/api/link/status?codeNo=ABC12345')
fetch('/api/link/status?linkId=123')
说明:
- 保留此格式用于兼容已生成的旧链接
- 支持
code、codeNo、linkId三种参数名 linkId和code/codeNo至少提供一个即可
兼容性保证
- ✅ 两种格式返回完全相同的数据结构
- ✅ 旧链接继续有效,无需修改
- ✅ 新生成的链接推荐使用路径参数格式
注意事项
- 错误处理:处理链接不存在、已过期等情况
- 加载状态:显示加载中的状态,提升用户体验
- 响应式设计:确保在不同设备上都能正常显示
- 缓存策略:可以适当缓存链接状态,减少请求次数
- 实时更新:对于进行中的任务,可以定时刷新状态