mirror of
				https://github.com/Zhuym07/Tsumugiboshi.git
				synced 2025-10-31 21:24:00 +08:00 
			
		
		
		
	Compare commits
	
		
			7 Commits
		
	
	
		
			bfbab6be5d
			...
			f8a7da1959
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | f8a7da1959 | ||
|  | 7a28bdb453 | ||
|  | efc0c7cb50 | ||
|  | f923ed0695 | ||
|  | 9ca6cde4b0 | ||
|  | fc7c93de91 | ||
|  | c7cb992208 | 
							
								
								
									
										224
									
								
								index.html
									
									
									
									
									
								
							
							
						
						
									
										224
									
								
								index.html
									
									
									
									
									
								
							| @ -405,6 +405,27 @@ | ||||
|             ); | ||||
|         }); | ||||
| 
 | ||||
|         // 在 App 组件外部添加地址格式化函数 | ||||
|         const formatApiUrl = (url) => { | ||||
|             if (!url) return ''; | ||||
|              | ||||
|             // 移除末尾的斜杠 | ||||
|             url = url.replace(/\/+$/, ''); | ||||
|              | ||||
|             // 如果不包含协议,添加 http:// | ||||
|             if (!/^https?:\/\//i.test(url)) { | ||||
|                 url = 'http://' + url; | ||||
|             } | ||||
|              | ||||
|             try { | ||||
|                 // 使用 URL API 解析和规范化地址 | ||||
|                 const urlObj = new URL(url); | ||||
|                 return urlObj.toString().replace(/\/+$/, ''); | ||||
|             } catch (e) { | ||||
|                 return url; | ||||
|             } | ||||
|         }; | ||||
| 
 | ||||
|         function App() { | ||||
|             const [tabValue, setTabValue] = React.useState(0); | ||||
|             const [qrInput, setQrInput] = React.useState(''); | ||||
| @ -431,7 +452,7 @@ | ||||
|                 ...JSON.parse(localStorage.getItem('customBackends') || '[]') | ||||
|             ]); | ||||
|             const [isDialogOpen, setIsDialogOpen] = React.useState(false); | ||||
|             const [newBackend, setNewBackend] = React.useState({ label: '', value: '' }); | ||||
|             const [newBackend, setNewBackend] = React.useState({ label: '', value: '', formattedUrl: '' }); | ||||
|             const [snackbarOpen, setSnackbarOpen] = React.useState(false); | ||||
|             const [snackbarMessage, setSnackbarMessage] = React.useState(''); | ||||
|             const [menuAnchorEl, setMenuAnchorEl] = React.useState(null); | ||||
| @ -441,6 +462,98 @@ | ||||
|             const [isLogExpanded, setIsLogExpanded] = React.useState(false); | ||||
|             const formRefs = React.useRef({}); | ||||
| 
 | ||||
|             const handleApiCall = async (endpoint, method = "GET", formData = null, config) => { | ||||
|                 try { | ||||
|                     const options = { | ||||
|                         method, | ||||
|                         headers: { "Content-Type": "application/json" }, | ||||
|                         credentials: "include" | ||||
|                     }; | ||||
| 
 | ||||
|                     let url = `${apiBase}${endpoint}`; | ||||
| 
 | ||||
|                     if (method === "GET" && config?.queryParams) { | ||||
|                         const params = new URLSearchParams(); | ||||
|                         config.queryParams.forEach(param => { | ||||
|                             if (formData?.[param]) params.append(param, formData[param]); | ||||
|                         }); | ||||
|                         url += `?${params.toString()}`; | ||||
|                     } | ||||
| 
 | ||||
|                     if (method === "POST" && config?.requestFormat === "json") { | ||||
|                         options.body = JSON.stringify(formData); | ||||
|                     } | ||||
| 
 | ||||
|                     const res = await fetch(url, options); | ||||
|                     const rawText = await res.text(); | ||||
|                      | ||||
|                     let responseData; | ||||
|                     try { | ||||
|                         responseData = JSON.parse(rawText); | ||||
|                     } catch { | ||||
|                         responseData = {  | ||||
|                             status: res.status, | ||||
|                             statusText: res.statusText, | ||||
|                             rawResponse: rawText  | ||||
|                         }; | ||||
|                     } | ||||
| 
 | ||||
|                     // 将状态码添加到显示结果中 | ||||
|                     const displayResponse = { | ||||
|                         status: res.status, | ||||
|                         statusText: res.statusText, | ||||
|                         ...responseData | ||||
|                     }; | ||||
| 
 | ||||
|                     setResponse(JSON.stringify(displayResponse, null, 2)); | ||||
|                      | ||||
|                     // 保存日志 | ||||
|                     saveLog(endpoint, {  | ||||
|                         method, | ||||
|                         url, | ||||
|                         ...formData, | ||||
|                         status: res.status  | ||||
|                     }, displayResponse); | ||||
| 
 | ||||
|                     // 特殊处理登录成功的情况 | ||||
|                     if (config?.endpoint === "/qr" && responseData.userId) { | ||||
|                         setUserId(responseData.userId.toString()); | ||||
|                         setCookie("userId", responseData.userId.toString(), 7); | ||||
|                          | ||||
|                         Object.entries(formConfigs).forEach(([key, formConfig]) => { | ||||
|                             const userIdField = formConfig.fields.find( | ||||
|                                 field => field.id.toLowerCase().includes("userid") | ||||
|                             ); | ||||
|                             if (userIdField && formRefs.current[key]) { | ||||
|                                 formRefs.current[key].updateField(userIdField.id, responseData.userId.toString()); | ||||
|                             } | ||||
|                         }); | ||||
|                     } | ||||
| 
 | ||||
|                     // 显示消息提示 | ||||
|                     setSnackbarMessage(responseData.info || `请求完成 (${res.status} ${res.statusText})`); | ||||
|                     setSnackbarOpen(true); | ||||
| 
 | ||||
|                 } catch (error) { | ||||
|                     const errorResponse = { | ||||
|                         error: true, | ||||
|                         message: error.message, | ||||
|                         type: error.name | ||||
|                     }; | ||||
|                      | ||||
|                     setResponse(JSON.stringify(errorResponse, null, 2)); | ||||
|                     setSnackbarMessage(`请求发生错误: ${error.message}`); | ||||
|                     setSnackbarOpen(true); | ||||
|                      | ||||
|                     // 保存错误日志 | ||||
|                     saveLog(endpoint, {  | ||||
|                         method, | ||||
|                         url: `${apiBase}${endpoint}`, | ||||
|                         ...formData  | ||||
|                     }, errorResponse); | ||||
|                 } | ||||
|             }; | ||||
| 
 | ||||
|             // Cookie操作函数 | ||||
|             function setCookie(name, value, days) { | ||||
|                 const d = new Date(); | ||||
| @ -472,65 +585,6 @@ | ||||
|                 localStorage.setItem('apiLogs', JSON.stringify(updatedLogs)); | ||||
|             }; | ||||
| 
 | ||||
|             const handleApiCall = async (endpoint, method = 'GET', formData = null, config) => { | ||||
|                 try { | ||||
|                     const options = { | ||||
|                         method, | ||||
|                         headers: { 'Content-Type': 'application/json' } | ||||
|                     }; | ||||
| 
 | ||||
|                     let url = `${apiBase}${endpoint}`; | ||||
|                      | ||||
|                     // 处理GET请求参数 | ||||
|                     if (method === 'GET' && config.queryParams) { | ||||
|                         const params = new URLSearchParams(); | ||||
|                         config.queryParams.forEach(param => { | ||||
|                             if (formData[param]) params.append(param, formData[param]); | ||||
|                         }); | ||||
|                         url += `?${params.toString()}`; | ||||
|                     } | ||||
|                      | ||||
|                     // 处理POST请求体 | ||||
|                     if (method === 'POST') { | ||||
|                         if (config.requestFormat === 'json') { | ||||
|                             options.body = JSON.stringify(formData); | ||||
|                         } | ||||
|                     } | ||||
| 
 | ||||
|                     const res = await fetch(url, options); | ||||
|                     const data = await res.json(); | ||||
|                     setResponse(JSON.stringify(data, null, 2)); | ||||
|                      | ||||
|                     // 保存日志 | ||||
|                     saveLog(endpoint, formData, data); | ||||
|                      | ||||
|                     // 处理登录响应 | ||||
|                     if (config.endpoint === '/qr' && data.userId) { | ||||
|                         setUserId(data.userId.toString()); | ||||
|                         setCookie('userId', data.userId.toString(), 7); | ||||
|                          | ||||
|                         // 遍历所有表单配置,更新包含userid/userId字段的表单 | ||||
|                         Object.entries(formConfigs).forEach(([key, formConfig]) => { | ||||
|                             const userIdField = formConfig.fields.find( | ||||
|                                 field => field.id.toLowerCase().includes('userid') | ||||
|                             ); | ||||
|                             if (userIdField && formRefs.current[key]) { | ||||
|                                 formRefs.current[key].updateField(userIdField.id, data.userId.toString()); | ||||
|                             } | ||||
|                         }); | ||||
|                     } | ||||
|                      | ||||
|                     // 根据状态设置提示消息 | ||||
|                     setSnackbarMessage(data.info || '操作成功'); | ||||
|                     setSnackbarOpen(true); | ||||
|                      | ||||
|                 } catch (error) { | ||||
|                     setResponse(`Error: ${error.message}`); | ||||
|                     setSnackbarMessage(`错误: ${error.message}`); | ||||
|                     setSnackbarOpen(true); | ||||
|                 } | ||||
|             }; | ||||
| 
 | ||||
|             const handleMusicChange = (e) => { | ||||
|                 const { name, value } = e.target; | ||||
|                 setMusicData(prev => ({ | ||||
| @ -554,7 +608,7 @@ | ||||
| 
 | ||||
|             const handleDialogClose = () => { | ||||
|                 setIsDialogOpen(false); | ||||
|                 setNewBackend({ label: '', value: '' }); | ||||
|                 setNewBackend({ label: '', value: '', formattedUrl: '' }); | ||||
|             }; | ||||
| 
 | ||||
|             const handleSaveBackend = () => { | ||||
| @ -610,6 +664,12 @@ | ||||
|                 handleMenuClose(); | ||||
|             }; | ||||
| 
 | ||||
|             const handleNewBackendChange = (e) => { | ||||
|                 const value = e.target.value; | ||||
|                 const formattedUrl = formatApiUrl(value); | ||||
|                 setNewBackend(prev => ({...prev, value: value, formattedUrl: formattedUrl})); | ||||
|             }; | ||||
| 
 | ||||
|             // 加载表单配置 | ||||
|             const [formConfigs, setFormConfigs] = React.useState(null); | ||||
| 
 | ||||
| @ -759,6 +819,7 @@ | ||||
|                                             </MenuItem> | ||||
|                                         ))} | ||||
|                                 </Select> | ||||
| 
 | ||||
|                                 <Button | ||||
|                                     variant="outlined" | ||||
|                                     color="primary" | ||||
| @ -766,7 +827,21 @@ | ||||
|                                     onClick={handleAddBackend} | ||||
|                                 > | ||||
|                                     添加自定义后端 | ||||
|                                 </Button> | ||||
|                                 </Button>                                 | ||||
|                                  | ||||
|                                 {/* 新增认证提示区域 */} | ||||
|                                 <Box display="flex" justifyContent="space-between" alignItems="center" mt={2}> | ||||
|                                     <Typography variant="body2" color="textSecondary"> | ||||
|                                         ⚠️部分后端地址受认证保护 使用前可能需要身份认证 | ||||
|                                     </Typography> | ||||
|                                     <Button  | ||||
|                                         variant="text"  | ||||
|                                         color="primary" | ||||
|                                         onClick={() => window.open(apiBase, '_blank')} | ||||
|                                     > | ||||
|                                         认证 | ||||
|                                     </Button> | ||||
|                                 </Box> | ||||
|                             </FormControl> | ||||
|                         </Paper> | ||||
| 
 | ||||
| @ -822,12 +897,33 @@ | ||||
|                                 label="后端地址" | ||||
|                                 fullWidth | ||||
|                                 value={newBackend.value} | ||||
|                                 onChange={(e) => setNewBackend(prev => ({...prev, value: e.target.value}))} | ||||
|                                 onChange={handleNewBackendChange} | ||||
|                                 placeholder="example.com:2333" | ||||
|                                 helperText={ | ||||
|                                     <div style={{fontSize: '0.75rem', color: '#666'}}> | ||||
|                                         支持的格式:<br/> | ||||
|                                         - example.com<br/> | ||||
|                                         - example.com:2333<br/> | ||||
|                                         - http(s)://example.com<br/> | ||||
|                                         {newBackend.value && ( | ||||
|                                             <> | ||||
|                                                 <br/> | ||||
|                                                 将被格式化为: <span style={{color: '#1976d2'}}>{newBackend.formattedUrl}</span> | ||||
|                                             </> | ||||
|                                         )} | ||||
|                                     </div> | ||||
|                                 } | ||||
|                             /> | ||||
|                         </DialogContent> | ||||
|                         <DialogActions> | ||||
|                             <Button onClick={handleDialogClose}>取消</Button> | ||||
|                             <Button onClick={handleSaveBackend} color="primary">保存</Button> | ||||
|                             <Button  | ||||
|                                 onClick={() => handleSaveBackend()}  | ||||
|                                 color="primary" | ||||
|                                 disabled={!newBackend.label || !newBackend.value} | ||||
|                             > | ||||
|                                 保存 | ||||
|                             </Button> | ||||
|                         </DialogActions> | ||||
|                     </Dialog> | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user