This commit is contained in:
Zhuym 2025-01-28 13:31:02 +08:00
commit 0d8a82fc33
4 changed files with 971 additions and 0 deletions

3
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"CodeGPT.apiKey": "DeepSeek"
}

103
config.json Normal file
View File

@ -0,0 +1,103 @@
{
"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"]
}
]
}

434
index.html Normal file
View File

@ -0,0 +1,434 @@
<!DOCTYPE html>
<html>
<head>
<title>API调试工具</title>
<!-- Material Icons -->
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<!-- React 依赖 -->
<script src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>
<!-- Babel -->
<script src="https://unpkg.com/@babel/standalone@7.21.5/babel.min.js"></script>
<!-- Material UI 组件库 -->
<script src="https://unpkg.com/@mui/material@5.14.0/umd/material-ui.production.min.js"></script>
<style>
body { margin: 20px; font-family: Arial, sans-serif; }
pre { background: #f5f5f5; padding: 10px; border-radius: 4px; }
.api-description { margin-bottom: 16px; color: #666; }
.selected-backend { font-weight: bold; color: #1976d2; }
</style>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
// 从全局对象获取Material UI组件
const {
Container,
Tabs,
Tab,
TextField,
Button,
Paper,
Typography,
Grid,
Box,
createTheme,
ThemeProvider,
Select,
MenuItem,
InputLabel,
FormControl,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Snackbar,
Alert
} = MaterialUI;
// 创建主题
const theme = createTheme();
// 预设的后端地址
const PRESET_BACKENDS = [
{ label: "开发环境", value: "http://dev-api.example.com" },
{ label: "测试环境", value: "http://test-api.example.com" },
{ label: "生产环境", value: "http://api.example.com" }
];
function App() {
const [tabValue, setTabValue] = React.useState(0);
const [qrInput, setQrInput] = React.useState('');
const [userId, setUserId] = React.useState(getCookie('userId') || '');
const [response, setResponse] = React.useState('');
const [musicData, setMusicData] = React.useState({
userId: parseInt(userId) || 0,
music: {
musicId: 0,
level: 0,
playCount: 0,
//achievement: 0,
comboStatus: 0,
syncStatus: 0,
deluxscoreMax: 0,
scoreRank: 0
}
});
const [apiBase, setApiBase] = React.useState(
localStorage.getItem('apiBase') || PRESET_BACKENDS[0].value
);
const [backends, setBackends] = React.useState([
...PRESET_BACKENDS,
...JSON.parse(localStorage.getItem('customBackends') || '[]')
]);
const [isDialogOpen, setIsDialogOpen] = React.useState(false);
const [newBackend, setNewBackend] = React.useState({ label: '', value: '' });
const [snackbarOpen, setSnackbarOpen] = React.useState(false);
const [snackbarMessage, setSnackbarMessage] = React.useState('');
// Cookie操作函数
function setCookie(name, value, days) {
const d = new Date();
d.setTime(d.getTime() + (days * 24 * 60 * 60 * 1000));
document.cookie = `${name}=${value};expires=${d.toUTCString()};path=/`;
}
function getCookie(name) {
return document.cookie.split('; ')
.find(row => row.startsWith(name + '='))
?.split('=')[1];
}
React.useEffect(() => {
if (userId) setCookie('userId', userId, 7);
}, [userId]);
const handleApiCall = async (endpoint, method = 'GET', body = null) => {
try {
const options = {
method,
headers: { 'Content-Type': 'application/json' }
};
if (body) options.body = JSON.stringify(body);
const res = await fetch(`${apiBase}${endpoint}`, options);
const data = await res.json();
setResponse(JSON.stringify(data, null, 2));
} catch (error) {
setResponse(`Error: ${error.message}`);
}
};
const handleMusicChange = (e) => {
const { name, value } = e.target;
setMusicData(prev => ({
...prev,
music: {
...prev.music,
[name]: parseInt(value) || 0
}
}));
};
const handleApiBaseChange = (event) => {
const value = event.target.value;
setApiBase(value);
localStorage.setItem('apiBase', value);
};
const handleAddBackend = () => {
setIsDialogOpen(true);
};
const handleDialogClose = () => {
setIsDialogOpen(false);
setNewBackend({ label: '', value: '' });
};
const handleSaveBackend = () => {
if (!newBackend.label || !newBackend.value) {
setSnackbarMessage('后端名称和地址不能为空');
setSnackbarOpen(true);
return;
}
const updatedBackends = [...backends, newBackend];
setBackends(updatedBackends);
localStorage.setItem('customBackends', JSON.stringify(updatedBackends.filter(b => !PRESET_BACKENDS.includes(b))));
setIsDialogOpen(false);
setSnackbarMessage('自定义后端已保存');
setSnackbarOpen(true);
};
const handleSnackbarClose = () => {
setSnackbarOpen(false);
};
return (
<ThemeProvider theme={theme}>
<Container maxWidth="md" style={{ marginTop: '2rem', marginBottom: '2rem' }}>
{/* 后端地址选择器 */}
<FormControl fullWidth style={{ marginBottom: '2rem' }}>
<InputLabel>选择后端地址</InputLabel>
<Select
value={apiBase}
onChange={handleApiBaseChange}
label="选择后端地址"
>
{backends.map((backend) => (
<MenuItem key={backend.value} value={backend.value}>
{backend.label} - {backend.value} {apiBase === backend.value}
</MenuItem>
))}
</Select>
<Button
variant="outlined"
color="primary"
style={{ marginTop: '1rem' }}
onClick={handleAddBackend}
>
添加自定义后端
</Button>
</FormControl>
{/* 添加自定义后端对话框 */}
<Dialog open={isDialogOpen} onClose={handleDialogClose}>
<DialogTitle>添加自定义后端</DialogTitle>
<DialogContent>
<TextField
fullWidth
label="后端名称"
value={newBackend.label}
onChange={(e) => setNewBackend({ ...newBackend, label: e.target.value })}
style={{ marginBottom: '1rem' }}
/>
<TextField
fullWidth
label="后端地址"
value={newBackend.value}
onChange={(e) => setNewBackend({ ...newBackend, value: e.target.value })}
/>
</DialogContent>
<DialogActions>
<Button onClick={handleDialogClose}>取消</Button>
<Button onClick={handleSaveBackend} color="primary">保存</Button>
</DialogActions>
</Dialog>
{/* Snackbar 提示 */}
<Snackbar
open={snackbarOpen}
autoHideDuration={3000}
onClose={handleSnackbarClose}
>
<Alert onClose={handleSnackbarClose} severity="success">
{snackbarMessage}
</Alert>
</Snackbar>
<Typography variant="h4" gutterBottom>
API调试工具
</Typography>
<Paper elevation={3} style={{ padding: '1rem', marginBottom: '1rem' }}>
<Tabs
value={tabValue}
onChange={(e, v) => setTabValue(v)}
indicatorColor="primary"
textColor="primary"
>
<Tab label="🪪登录" />
<Tab label="🎟发6倍票" />
<Tab label="🗺存入Stock" />
<Tab label="🔓解锁紫铺" />
<Tab label="✏️修改成绩" />
</Tabs>
{/* 二维码转UID表单 */}
{tabValue === 0 && (
<div style={{ padding: '1rem' }}>
<Typography variant="body1" className="api-description">
上传登录二维码解码后的字符串来获取 userID。
</Typography>
<TextField
fullWidth
label="二维码内容"
value={qrInput}
onChange={(e) => setQrInput(e.target.value)}
style={{ marginBottom: '1rem' }}
/>
<Button
variant="contained"
color="primary"
onClick={() => handleApiCall(`/qr?qrcode=${encodeURIComponent(qrInput)}`)}
>
获取UserID
</Button>
</div>
)}
{/* 通用GET请求表单 */}
{[1, 2, 3].includes(tabValue) && (
<div style={{ padding: '1rem' }}>
<Typography variant="body1" className="api-description">
{tabValue === 1 && "发送「6倍功能票」到账户。"}
{tabValue === 2 && "保存 99 公里 Stock 到账户。"}
{tabValue === 3 && "解锁所有 DX Master 谱面。"}
</Typography>
<TextField
label="UserID"
value={userId}
onChange={(e) => setUserId(e.target.value)}
style={{ marginBottom: '1rem' }}
fullWidth
/>
<Button
variant="contained"
color="primary"
onClick={() => {
const endpoints = ['/ticket', '/mapstock', '/unlock'];
handleApiCall(`${endpoints[tabValue-1]}?userid=${userId}`);
}}
>
发送请求
</Button>
</div>
)}
{/* 音乐数据POST表单 */}
{tabValue === 4 && (
<div style={{ padding: '1rem' }}>
<Typography variant="body1" className="api-description">
*覆写* 账户中的成绩数据
</Typography>
<Grid container spacing={2}>
<Grid item xs={12}>
<TextField
fullWidth
label="🪪UserID"
type="number"
value={musicData.userId}
onChange={(e) => setMusicData({...musicData, userId: e.target.value})}
/>
</Grid>
<Grid item xs={12}>
<TextField
fullWidth
label="💯分数"
type="number"
value={musicData.music.achievement}
onChange={(e) => setMusicData({...musicData, music: {...musicData.music, achievement: e.target.value}})}
placeholder="1010000"
InputProps={{
style: { color: 'grey' }
}}
/>
</Grid>
<Grid item xs={12}>
<FormControl fullWidth>
<InputLabel id="level-select-label">🧩难度</InputLabel>
<Select
labelId="level-select-label"
id="level-select"
value={musicData.music.level}
onChange={(e) => setMusicData({...musicData, music: {...musicData.music, level: e.target.value}})}
>
<MenuItem value={0}>0 (🟩Bas)</MenuItem>
<MenuItem value={1}>1 (🟨Adv)</MenuItem>
<MenuItem value={2}>2 (🟥Exp)</MenuItem>
<MenuItem value={3}>3 (🟪Mas)</MenuItem>
<MenuItem value={4}>4 (⬜ReM)</MenuItem>
</Select>
</FormControl>
</Grid>
<Grid item xs={12}>
<FormControl fullWidth>
<InputLabel id="comboStatus-select-label">comboStatus</InputLabel>
<Select
labelId="comboStatus-select-label"
id="comboStatus-select"
value={musicData.music.comboStatus}
onChange={(e) => setMusicData({...musicData, music: {...musicData.music, comboStatus: e.target.value}})}
>
<MenuItem value={0}>0 (⚪未达成)</MenuItem>
<MenuItem value={1}>1 (🟢已达成)</MenuItem>
</Select>
</FormControl>
</Grid>
<Grid item xs={12}>
<FormControl fullWidth>
<InputLabel id="syncStatus-select-label">syncStatus</InputLabel>
<Select
labelId="syncStatus-select-label"
id="syncStatus-select"
value={musicData.music.syncStatus}
onChange={(e) => setMusicData({...musicData, music: {...musicData.music, syncStatus: e.target.value}})}
>
<MenuItem value={0}>0 (未同步)</MenuItem>
<MenuItem value={1}>1 (🔵已同步)</MenuItem>
</Select>
</FormControl>
</Grid>
{Object.entries(musicData.music).map(([key, val]) => (
key !== 'level' && key !== 'achievement' && key !== 'comboStatus' && key !== 'syncStatus' && (
<Grid item xs={6} sm={4} key={key}>
<TextField
fullWidth
label={key}
type="number"
name={key}
value={val}
onChange={handleMusicChange}
/>
</Grid>
)
))}
<Grid item xs={12}>
<Button
variant="contained"
color="primary"
onClick={async () => {
const dataToSend = JSON.stringify(musicData, null, 2);
const confirmationMessage = `即将发送的数据:\nUserID: ${musicData.userId}\n歌曲ID: ${musicData.music.musicId}\nachievement: ${musicData.music.achievement}\nlevel: ${musicData.music.level}\ncomboStatus: ${musicData.music.comboStatus}\nsyncStatus: ${musicData.music.syncStatus}\n\n原始请求:\n${dataToSend}`;
alert(confirmationMessage); // 显示即将发送的数据
const confirmation = window.confirm("确认提交数据?"); // 弹出确认对话框
if (confirmation) {
handleApiCall('/music', 'POST', musicData); // 如果用户确认,发送请求
}
}}
>
提交音乐数据
</Button>
</Grid>
</Grid>
</div>
)}
</Paper>
{response && (
<Paper elevation={3} style={{ padding: '1rem' }}>
<Typography variant="h6" gutterBottom>响应结果:</Typography>
<pre style={{
whiteSpace: 'pre-wrap',
wordWrap: 'break-word',
backgroundColor: '#f5f5f5',
padding: '1rem',
borderRadius: '4px'
}}>
{response}
</pre>
</Paper>
)}
</Container>
</ThemeProvider>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
</script>
</body>
</html>

431
index1.html Normal file
View File

@ -0,0 +1,431 @@
<!DOCTYPE html>
<html>
<head>
<title>API调试工具</title>
<!-- Material Icons -->
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<!-- React 依赖 -->
<script src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>
<!-- Babel -->
<script src="https://unpkg.com/@babel/standalone@7.21.5/babel.min.js"></script>
<!-- Material UI 组件库 -->
<script src="https://unpkg.com/@mui/material@5.14.0/umd/material-ui.production.min.js"></script>
<style>
body { margin: 20px; font-family: Arial, sans-serif; }
pre { background: #f5f5f5; padding: 10px; border-radius: 4px; }
.api-description { margin-bottom: 16px; color: #666; }
.selected-backend { font-weight: bold; color: #1976d2; }
</style>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
// 从全局对象获取Material UI组件
const {
Container,
Tabs,
Tab,
TextField,
Button,
Paper,
Typography,
Grid,
Box,
createTheme,
ThemeProvider,
Select,
MenuItem,
InputLabel,
FormControl,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Snackbar,
Alert
} = MaterialUI;
// 创建主题
const theme = createTheme();
// 预设的后端地址
const PRESET_BACKENDS = [
{ label: "开发环境", value: "http://dev-api.example.com" },
{ label: "测试环境", value: "http://test-api.example.com" },
{ label: "生产环境", value: "http://api.example.com" }
];
function App() {
const [tabValue, setTabValue] = React.useState(0);
const [qrInput, setQrInput] = React.useState('');
const [userId, setUserId] = React.useState(getCookie('userId') || '');
const [response, setResponse] = React.useState('');
const [musicData, setMusicData] = React.useState({
userId: parseInt(userId) || 0,
music: {
musicId: 0,
level: 0,
playCount: 0,
comboStatus: 0,
syncStatus: 0,
deluxscoreMax: 0,
scoreRank: 0
}
});
const [apiBase, setApiBase] = React.useState(
localStorage.getItem('apiBase') || PRESET_BACKENDS[0].value
);
const [backends, setBackends] = React.useState([
...PRESET_BACKENDS,
...JSON.parse(localStorage.getItem('customBackends') || '[]')
]);
const [isDialogOpen, setIsDialogOpen] = React.useState(false);
const [newBackend, setNewBackend] = React.useState({ label: '', value: '' });
const [snackbarOpen, setSnackbarOpen] = React.useState(false);
const [snackbarMessage, setSnackbarMessage] = React.useState('');
// Cookie操作函数
function setCookie(name, value, days) {
const d = new Date();
d.setTime(d.getTime() + (days * 24 * 60 * 60 * 1000));
document.cookie = `${name}=${value};expires=${d.toUTCString()};path=/`;
}
function getCookie(name) {
return document.cookie.split('; ')
.find(row => row.startsWith(name + '='))
?.split('=')[1];
}
React.useEffect(() => {
if (userId) setCookie('userId', userId, 7);
}, [userId]);
const handleApiCall = async (endpoint, method = 'GET', body = null) => {
try {
const options = {
method,
headers: { 'Content-Type': 'application/json' }
};
if (body) options.body = JSON.stringify(body);
const res = await fetch(`${apiBase}${endpoint}`, options);
const data = await res.json();
setResponse(JSON.stringify(data, null, 2));
//记录日志
const log = {
timestamp: new Date().toLocaleString(),
apiName: data.apiName || endpoint,
status: data.status || 'Unknown',
response: data,
expanded: false
};
setLogs(prevLogs => [log, ...prevLogs]);
//处理userID
if (data.userId) {
setUserId(data.userId);
localStorage.setItem('userId', data.userId);
}
//显示成功toast
if (data.status === "200 OK") {
const date = new Date(data.timestamp * 1000).toLocaleString();
setSnackbarMessage(`成功: ${data.apiName} (${date})`);
setSnackbarOpen(true);
}
} catch (error) {
setResponse(`Error: ${error.message}`);
}
};
const handleMusicChange = (e) => {
const { name, value } = e.target;
setMusicData(prev => ({
...prev,
music: {
...prev.music,
[name]: parseInt(value) || 0
}
}));
};
const handleApiBaseChange = (event) => {
const value = event.target.value;
setApiBase(value);
localStorage.setItem('apiBase', value);
};
const handleAddBackend = () => {
setIsDialogOpen(true);
};
const handleDialogClose = () => {
setIsDialogOpen(false);
setNewBackend({ label: '', value: '' });
};
const handleSaveBackend = () => {
if (!newBackend.label || !newBackend.value) {
setSnackbarMessage('后端名称和地址不能为空');
setSnackbarOpen(true);
return;
}
const updatedBackends = [...backends, newBackend];
setBackends(updatedBackends);
localStorage.setItem('customBackends', JSON.stringify(updatedBackends.filter(b => !PRESET_BACKENDS.includes(b))));
setIsDialogOpen(false);
setSnackbarMessage('自定义后端已保存');
setSnackbarOpen(true);
};
const handleSnackbarClose = () => {
setSnackbarOpen(false);
};
//
const toggleLogExpanded = (index) => {
setLogs(prevLogs => {
const newLogs = [...prevLogs];
newLogs[index].expanded = !newLogs[index].expanded;
return newLogs;
});
};
0
return (
<ThemeProvider theme={theme}>
<Container maxWidth="md" style={{ marginTop: '2rem', marginBottom: '2rem' }}>
{/* 后端地址选择器 */}
<FormControl fullWidth style={{ marginBottom: '2rem' }}>
<InputLabel>选择后端地址</InputLabel>
<Select
value={apiBase}
onChange={handleApiBaseChange}
label="选择后端地址"
>
{backends.map((backend) => (
<MenuItem key={backend.value} value={backend.value}>
{backend.label} {apiBase === backend.value && '◉'}
</MenuItem>
))}
</Select>
<Button
variant="outlined"
color="primary"
style={{ marginTop: '1rem' }}
onClick={handleAddBackend}
>
添加自定义后端
</Button>
</FormControl>
{/* 添加自定义后端对话框 */}
<Dialog open={isDialogOpen} onClose={handleDialogClose}>
<DialogTitle>添加自定义后端</DialogTitle>
<DialogContent>
<TextField
fullWidth
label="后端名称"
value={newBackend.label}
onChange={(e) => setNewBackend({ ...newBackend, label: e.target.value })}
style={{ marginBottom: '1rem' }}
/>
<TextField
fullWidth
label="后端地址"
value={newBackend.value}
onChange={(e) => setNewBackend({ ...newBackend, value: e.target.value })}
/>
</DialogContent>
<DialogActions>
<Button onClick={handleDialogClose}>取消</Button>
<Button onClick={handleSaveBackend} color="primary">保存</Button>
</DialogActions>
</Dialog>
{/* Snackbar 提示 */}
<Snackbar
open={snackbarOpen}
autoHideDuration={3000}
onClose={handleSnackbarClose}
>
<Alert onClose={handleSnackbarClose} severity="success">
{snackbarMessage}
</Alert>
</Snackbar>
<Typography variant="h4" gutterBottom>
API调试工具
</Typography>
<Paper elevation={3} style={{ padding: '1rem', marginBottom: '1rem' }}>
<Tabs
value={tabValue}
onChange={(e, v) => setTabValue(v)}
indicatorColor="primary"
textColor="primary"
>
<Tab label="二维码转UID" />
<Tab label="6倍票券" />
<Tab label="地图库存" />
<Tab label="解锁DX" />
<Tab label="音乐数据" />
</Tabs>
{/* 二维码转UID表单 */}
{tabValue === 0 && (
<div style={{ padding: '1rem' }}>
<Typography variant="body1" className="api-description">
通过上传用户登录二维码解码后的字符串来获取 userID。
</Typography>
<TextField
fullWidth
label="二维码内容"
value={qrInput}
onChange={(e) => setQrInput(e.target.value)}
style={{ marginBottom: '1rem' }}
/>
<Button
variant="contained"
color="primary"
onClick={() => handleApiCall(`/qr?qrcode=${encodeURIComponent(qrInput)}`)}
>
获取UserID
</Button>
</div>
)}
{/* 通用GET请求表单 */}
{[1, 2, 3].includes(tabValue) && (
<div style={{ padding: '1rem' }}>
<Typography variant="body1" className="api-description">
{tabValue === 1 && "使用用户的 userID 发送「6倍チケット」到账户。"}
{tabValue === 2 && "使用用户的 userID 保存 99 公里地图库存到账户。"}
{tabValue === 3 && "使用用户的 userID 解锁所有 DX Master 谱面。"}
</Typography>
<TextField
label="UserID"
value={userId}
onChange={(e) => setUserId(e.target.value)}
style={{ marginBottom: '1rem' }}
fullWidth
/>
<Button
variant="contained"
color="primary"
onClick={() => {
const endpoints = ['/ticket', '/mapstock', '/unlock'];
handleApiCall(`${endpoints[tabValue-1]}?userid=${userId}`);
}}
>
发送请求
</Button>
</div>
)}
{/* 音乐数据POST表单 */}
{tabValue === 4 && (
<div style={{ padding: '1rem' }}>
<Typography variant="body1" className="api-description">
覆写账户中的音乐数据
</Typography>
<Grid container spacing={2}>
<Grid item xs={12}>
<TextField
fullWidth
label="UserID"
type="number"
value={musicData.userId}
onChange={(e) => setMusicData({...musicData, userId: e.target.value})}
/>
</Grid>
<Grid item xs={12}>
<TextField
fullWidth
label="achievement"
type="number"
value={musicData.music.achievement}
onChange={(e) => setMusicData({...musicData, music: {...musicData.music, achievement: e.target.value}})}
placeholder="1010000"
InputProps={{
style: { color: 'grey' }
}}
/>
</Grid>
<Grid item xs={12}>
<FormControl fullWidth>
<InputLabel id="level-select-label">level</InputLabel>
<Select
labelId="level-select-label"
id="level-select"
value={musicData.music.level}
onChange={(e) => setMusicData({...musicData, music: {...musicData.music, level: e.target.value}})}
>
<MenuItem value={0}>0 (BAS)</MenuItem>
<MenuItem value={1}>1 (ADV)</MenuItem>
<MenuItem value={2}>2 (EXP)</MenuItem>
<MenuItem value={3}>3 (MAS)</MenuItem>
<MenuItem value={4}>4 (ReM)</MenuItem>
</Select>
</FormControl>
</Grid>
{Object.entries(musicData.music).map(([key, val]) => (
key !== 'level' && key !== 'achievement' && (
<Grid item xs={6} sm={4} key={key}>
<TextField
fullWidth
label={key}
type="number"
name={key}
value={val}
onChange={handleMusicChange}
/>
</Grid>
)
))}
<Grid item xs={12}>
<Button
variant="contained"
color="primary"
onClick={() => handleApiCall('/music', 'POST', musicData)}
>
提交音乐数据
</Button>
</Grid>
</Grid>
</div>
)}
</Paper>
{response && (
<Paper elevation={3} style={{ padding: '1rem' }}>
<Typography variant="h6" gutterBottom>响应结果:</Typography>
<pre style={{
whiteSpace: 'pre-wrap',
wordWrap: 'break-word',
backgroundColor: '#f5f5f5',
padding: '1rem',
borderRadius: '4px'
}}>
{response}
</pre>
</Paper>
)}
</Container>
</ThemeProvider>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
</script>
</body>
</html>