diff --git a/._index.html b/._index.html new file mode 100644 index 0000000..2043f67 Binary files /dev/null and b/._index.html differ diff --git a/._src b/._src new file mode 100644 index 0000000..2043f67 Binary files /dev/null and b/._src differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..026930b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# 忽略UserDataDir文件夹 +UserDataDir \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..e8549e7 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Chrome", + "type": "chrome", + "request": "launch", + "file": "${workspaceFolder}/index.html", + "runtimeArgs": [ + "--disable-web-security", + "--user-data-dir=${workspaceFolder}/UserDataDir" + ], + "webRoot": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/config.json b/config.json deleted file mode 100644 index bd7dfc0..0000000 --- a/config.json +++ /dev/null @@ -1,103 +0,0 @@ -{ - "title": "API调试工具", - "theme": "light", - "apis": [ - { - "name": "二维码扫描", - "type": "qr", - "endpoint": "/qr", - "method": "GET", - "params": [ - { - "key": "qrcode", - "label": "二维码内容", - "type": "qr", - "required": true - } - ], - "responseFields": ["status", "timestamp", "info", "apiName", "date", "userId"] - }, - { - "name": "发送6倍券", - "type": "action", - "endpoint": "/ticket", - "method": "GET", - "params": [ - { - "key": "userid", - "label": "用户ID", - "type": "text", - "required": true, - "source": "userId" - } - ], - "responseFields": ["status", "timestamp", "info", "apiName", "date", "userId"] - }, - { - "name": "保存99公里", - "type": "action", - "endpoint": "/mapstock", - "method": "GET", - "params": [ - { - "key": "userid", - "label": "用户ID", - "type": "text", - "required": true, - "source": "userId" - } - ], - "responseFields": ["status", "timestamp", "info", "apiName", "date", "userId"] - }, - { - "name": "解锁DX曲目", - "type": "action", - "endpoint": "/unlock", - "method": "GET", - "params": [ - { - "key": "userid", - "label": "用户ID", - "type": "text", - "required": true, - "source": "userId" - } - ], - "responseFields": ["status", "timestamp", "info", "apiName", "date", "userId"] - }, - { - "name": "更新音乐数据", - "type": "form", - "endpoint": "/music", - "method": "POST", - "params": [ - { - "key": "userId", - "label": "用户ID", - "type": "text", - "required": true, - "source": "userId" - }, - { - "key": "musicId", - "label": "音乐ID", - "type": "number", - "required": true - }, - { - "key": "level", - "label": "难度", - "type": "number", - "required": true - }, - { - "key": "playCount", - "label": "游玩次数", - "type": "number", - "required": true - } - ], - "responseFields": ["status", "timestamp", "info", "apiName", "date", "userId"] - } - ] - } \ No newline at end of file diff --git a/config/label.json b/config/label.json new file mode 100644 index 0000000..0bdcad9 --- /dev/null +++ b/config/label.json @@ -0,0 +1,161 @@ +{ + "forms": { + "login": { + "tab": { + "index": 0, + "label": "🪪登录", + "description": "上传登录二维码解码后的字符串来获取 userID" + }, + "endpoint": "/qr", + "method": "GET", + "fields": [ + { + "id": "qrcode", + "label": "二维码内容", + "type": "text", + "required": true + } + ], + "confirmBeforeSubmit": false + }, + "ticket": { + "tab": { + "index": 1, + "label": "🎟️发6倍票", + "description": "发送「6倍功能票」到账户" + }, + "endpoint": "/ticket", + "method": "GET", + "fields": [ + { + "id": "userid", + "label": "🪪UserID", + "type": "text", + "required": true + } + ], + "confirmBeforeSubmit": true + }, + "mapstock": { + "tab": { + "index": 2, + "label": "🗺️存入Stock", + "description": "保存 99km Stocks 到账户" + }, + "endpoint": "/mapstock", + "method": "GET", + "fields": [ + { + "id": "userid", + "label": "🪪UserID", + "type": "text", + "required": true + } + ], + "confirmBeforeSubmit": true + }, + "unlock": { + "tab": { + "index": 3, + "label": "🔓解锁紫铺", + "description": "解锁所有 DX Master 谱面" + }, + "endpoint": "/unlock", + "method": "GET", + "fields": [ + { + "id": "userid", + "label": "🪪UserID", + "type": "text", + "required": true + } + ], + "confirmBeforeSubmit": true + }, + "music": { + "tab": { + "index": 4, + "label": "✏️修改成绩", + "description": "⚠警告\n这将[覆写]账户中的成绩数据" + }, + "endpoint": "/music", + "method": "POST", + "fields": [ + { + "id": "userId", + "label": "🪪UserID", + "type": "number", + "required": true + }, + { + "id": "music.achievement", + "label": "💯分数", + "type": "number", + "placeholder": "1010000", + "required": true + }, + { + "id": "music.level", + "label": "🧩难度", + "type": "select", + "options": [ + {"value": 0, "label": "0 (🟩Bas)"}, + {"value": 1, "label": "1 (🟨Adv)"}, + {"value": 2, "label": "2 (🟥Exp)"}, + {"value": 3, "label": "3 (🟪Mas)"}, + {"value": 4, "label": "4 (⬜ReM)"} + ], + "required": true + }, + { + "id": "music.comboStatus", + "label": "Full Combo 状态", + "type": "select", + "options": [ + {"value": 0, "label": "0 (⚪未FC)"}, + {"value": 1, "label": "1 (🟢已FC)"} + ], + "required": true + }, + { + "id": "music.syncStatus", + "label": "Full Sync 状态", + "type": "select", + "options": [ + {"value": 0, "label": "0 (⚪未Sync)"}, + {"value": 1, "label": "1 (🔵已Sync)"} + ], + "required": true + }, + { + "id": "music.musicId", + "label": "🎵musicID", + "type": "number", + "placeholder": "11529", + "required": true + }, + { + "id": "music.playCount", + "label": "🎰游玩次数", + "type": "number", + "placeholder": "1", + "required": true + }, + { + "id": "music.deluxscoreMax", + "label": "🔢deluxe分数", + "type": "number", + "required": true + }, + { + "id": "music.scoreRank", + "label": "scoreRank", + "type": "number", + "required": true + } + ], + "confirmBeforeSubmit": true, + "confirmTemplate": "即将发送的数据:\nUserID: {userId}\n歌曲ID: {music.musicId}\nachievement: {music.achievement}\n难度: {music.level}\nFC状态: {music.comboStatus}\nsyncStatus: {music.syncStatus}\nFS状态:{music.syncStatus}\n\n原始请求:\n{rawData}" + } + } +} diff --git a/index.html b/index.html index 637e2eb..5ce5602 100644 --- a/index.html +++ b/index.html @@ -16,14 +16,81 @@ - +
+ - - - - - - - - - - - -
- - - - \ No newline at end of file diff --git a/src/components/ApiTabs.js b/src/components/ApiTabs.js new file mode 100644 index 0000000..061c580 --- /dev/null +++ b/src/components/ApiTabs.js @@ -0,0 +1,228 @@ +import React from 'react'; +import { + Tabs, + Tab, + TextField, + Button, + Paper, + Typography, + Grid, + Box, + Select, + MenuItem, + InputLabel, + FormControl, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Snackbar, + Alert, + Radio, + RadioGroup, + FormControlLabel +} from '@mui/material'; +import { setCookie } from '../utils/cookies'; + +function ApiTabs({ + tabValue, + setTabValue, + qrInput, + setQrInput, + userId, + setUserId, + response, + setResponse, + musicData, + setMusicData, + apiBase, + setApiBase, + backends, + setBackends, + isDialogOpen, + setIsDialogOpen, + newBackend, + setNewBackend, + snackbarOpen, + setSnackbarOpen, + snackbarMessage, + setSnackbarMessage +}) { + const handleTabChange = (event, newValue) => { + setTabValue(newValue); + }; + + const handleUserIdChange = (event) => { + const value = event.target.value; + setUserId(value); + setCookie('userId', value); + setMusicData(prev => ({ ...prev, userId: parseInt(value) || 0 })); + }; + + const handleBackendChange = (event) => { + const value = event.target.value; + setApiBase(value); + localStorage.setItem('apiBase', value); + }; + + const handleAddBackend = () => { + if (!newBackend.label || !newBackend.value) { + setSnackbarMessage('请填写完整的后端信息'); + setSnackbarOpen(true); + return; + } + + const updatedBackends = [...backends, newBackend]; + setBackends(updatedBackends); + localStorage.setItem('customBackends', JSON.stringify( + updatedBackends.slice(PRESET_BACKENDS.length) + )); + setIsDialogOpen(false); + setNewBackend({ label: '', value: '' }); + }; + + return ( + <> + + + API调试工具 + +
+
+ + + + +
+
+ + + + + + + + + 后端环境 + + + + + + {tabValue === 0 && ( + + setQrInput(e.target.value)} + variant="outlined" + /> + + )} + + {tabValue === 1 && ( + + + {Object.entries(musicData.music).map(([key, value]) => ( + + { + const newValue = parseInt(e.target.value) || 0; + setMusicData(prev => ({ + ...prev, + music: { + ...prev.music, + [key]: newValue + } + })); + }} + variant="outlined" + /> + + ))} + + + )} + + {response && ( + + + 响应结果 + +
{JSON.stringify(response, null, 2)}
+
+ )} +
+
+ + setIsDialogOpen(false)}> + 添加自定义后端 + + setNewBackend(prev => ({ ...prev, label: e.target.value }))} + /> + setNewBackend(prev => ({ ...prev, value: e.target.value }))} + /> + + + + + + + + setSnackbarOpen(false)} + > + setSnackbarOpen(false)} + severity="error" + sx={{ width: '100%' }} + > + {snackbarMessage} + + + + ); +} + +export default ApiTabs; \ No newline at end of file diff --git a/src/components/App.js b/src/components/App.js new file mode 100644 index 0000000..2d8c45a --- /dev/null +++ b/src/components/App.js @@ -0,0 +1,79 @@ +import React, { useState } from 'react'; +import { Container, Typography } from '@mui/material'; +import { createTheme, ThemeProvider } from '@mui/material/styles'; +import ApiTabs from './ApiTabs'; +import { PRESET_BACKENDS } from '../constants'; +import { getCookie, setCookie } from '../utils/cookies'; + +const theme = createTheme(); + +function App() { + const [tabValue, setTabValue] = useState(0); + const [qrInput, setQrInput] = useState(''); + const [userId, setUserId] = useState(getCookie('userId') || ''); + const [response, setResponse] = useState(''); + const [musicData, setMusicData] = useState({ + userId: parseInt(userId) || 0, + music: { + musicId: 0, + level: 0, + achievement: 0, + playCount: 0, + comboStatus: 0, + syncStatus: 0, + deluxscoreMax: 0, + scoreRank: 0 + } + }); + const [apiBase, setApiBase] = useState( + localStorage.getItem('apiBase') || PRESET_BACKENDS[0].value + ); + const [backends, setBackends] = useState([ + ...PRESET_BACKENDS, + ...JSON.parse(localStorage.getItem('customBackends') || '[]') + ]); + const [isDialogOpen, setIsDialogOpen] = useState(false); + const [newBackend, setNewBackend] = useState({ label: '', value: '' }); + const [snackbarOpen, setSnackbarOpen] = useState(false); + const [snackbarMessage, setSnackbarMessage] = useState(''); + + return ( + +
+ + + +
+ +
+ ); +} + +export default App; \ No newline at end of file diff --git a/src/constants/index.js b/src/constants/index.js new file mode 100644 index 0000000..c76a669 --- /dev/null +++ b/src/constants/index.js @@ -0,0 +1,5 @@ +export const PRESET_BACKENDS = [ + { label: "开发环境", value: "http://dev-api.example.com" }, + { label: "测试环境", value: "http://test-api.example.com" }, + { label: "生产环境", value: "http://api.example.com" } +]; \ No newline at end of file diff --git a/src/styles/main.css b/src/styles/main.css new file mode 100644 index 0000000..16f5522 --- /dev/null +++ b/src/styles/main.css @@ -0,0 +1,85 @@ +body { + margin: 0; + font-family: Arial, sans-serif; + min-height: 100vh; + display: flex; + flex-direction: column; +} + +#root { + flex: 1; +} + +pre { + background: #f5f5f5; + padding: 10px; + border-radius: 4px; +} + +.api-description { + margin: 8px 0 16px !important; + color: #666; + white-space: pre-line; + line-height: 1.5; + background-color: #f8f9fa; + padding: 12px; + border-radius: 4px; + border-left: 4px solid #1976d2; +} + +.selected-backend { + font-weight: bold; + color: #1976d2; +} + +.scrollable-tabs { + max-width: calc(100% - 48px); + overflow-x: auto; + overflow-y: hidden; +} + +.tabs-container { + display: flex; + align-items: center; + position: relative; + border-bottom: 1px solid rgba(0, 0, 0, 0.12); +} + +.menu-button { + position: absolute !important; + right: 0; + top: 50%; + transform: translateY(-50%); +} + +footer { + margin-top: auto; + padding: 16px; + background-color: #f5f5f5; + text-align: center; + font-size: 0.875rem; + color: #666; +} + +@media (max-width: 600px) { + .MuiContainer-root { + padding: 12px !important; + } + + .MuiPaper-root { + padding: 12px !important; + } + + h4.MuiTypography-root { + font-size: 1.5rem; + } + + .scrollable-tabs::-webkit-scrollbar { + display: none; + } + + .api-description { + font-size: 0.875rem; + padding: 8px; + } +} \ No newline at end of file diff --git a/src/utils/cookies.js b/src/utils/cookies.js new file mode 100644 index 0000000..80b1a48 --- /dev/null +++ b/src/utils/cookies.js @@ -0,0 +1,21 @@ +export function setCookie(name, value, days = 30) { + const d = new Date(); + d.setTime(d.getTime() + (days * 24 * 60 * 60 * 1000)); + const expires = `expires=${d.toUTCString()}`; + document.cookie = `${name}=${value};${expires};path=/`; +} + +export function getCookie(name) { + const nameEQ = `${name}=`; + const ca = document.cookie.split(';'); + for (let i = 0; i < ca.length; i++) { + let c = ca[i]; + while (c.charAt(0) === ' ') c = c.substring(1, c.length); + if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length); + } + return null; +} + +export function deleteCookie(name) { + document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:01 GMT;path=/`; +} \ No newline at end of file