import React, {useEffect, useState} from 'react'; import {createUseStyles} from 'react-jss'; import {tokenApi, ApiToken, CreateTokenRes} from '../../api/token.ts'; import {message} from '../../hook/message/u-message.tsx'; import {UButton} from '../../component/button/u-button.tsx'; const useStyle = createUseStyles({ container: { minHeight: '100vh', backgroundColor: '#e3f2fd', padding: '24px', boxSizing: 'border-box', fontFamily: "'Segoe UI', Arial, sans-serif", }, header: { display: 'flex', alignItems: 'center', gap: '16px', marginBottom: '24px', }, backBtn: { background: 'transparent', border: '2px solid #2c9678', color: '#2c9678', borderRadius: '6px', padding: '6px 14px', cursor: 'pointer', fontSize: '14px', transition: 'background-color 0.2s', '&:hover': {backgroundColor: 'rgba(44,150,120,0.1)'}, }, title: { color: '#2c9678', margin: 0, fontSize: '22px', fontWeight: 600, }, card: { backgroundColor: '#C8E6C9', boxShadow: 'inset 0 0 15px rgba(56, 142, 60, 0.15)', borderRadius: '15px', padding: '24px', marginBottom: '24px', }, cardTitle: { color: '#2c9678', marginTop: 0, marginBottom: '16px', fontSize: '16px', fontWeight: 600, }, table: { width: '100%', borderCollapse: 'collapse', fontSize: '14px', }, th: { backgroundColor: 'rgba(44,150,120,0.15)', padding: '10px 12px', textAlign: 'left', color: '#2c9678', fontWeight: 600, borderBottom: '2px solid rgba(44,150,120,0.3)', }, td: { padding: '10px 12px', borderBottom: '1px solid rgba(44,150,120,0.2)', color: '#333', }, trHover: { '&:hover': {backgroundColor: 'rgba(44,150,120,0.05)'}, }, emptyRow: { textAlign: 'center', color: '#888', padding: '24px', }, actionBtn: { padding: '4px 12px', borderRadius: '4px', border: 'none', cursor: 'pointer', fontSize: '13px', transition: 'opacity 0.2s', '&:hover': {opacity: 0.8}, }, deleteBtn: { backgroundColor: '#e53935', color: 'white', }, topBar: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '16px', }, // Dialog overlay overlay: { position: 'fixed', inset: 0, backgroundColor: 'rgba(0,0,0,0.4)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1000, }, dialog: { backgroundColor: '#C8E6C9', borderRadius: '15px', padding: '28px', width: '440px', maxWidth: '90vw', boxShadow: '0 8px 32px rgba(0,0,0,0.2)', }, dialogTitle: { color: '#2c9678', marginTop: 0, marginBottom: '20px', fontSize: '16px', fontWeight: 600, }, label: { display: 'block', color: '#2c9678', fontSize: '13px', marginBottom: '6px', fontWeight: 500, }, input: { width: '100%', padding: '8px 12px', borderRadius: '6px', border: '1px solid rgba(44,150,120,0.4)', fontSize: '14px', marginBottom: '16px', boxSizing: 'border-box', backgroundColor: 'rgba(255,255,255,0.8)', outline: 'none', '&:focus': {borderColor: '#2c9678'}, }, dialogFooter: { display: 'flex', gap: '10px', justifyContent: 'flex-end', }, cancelBtn: { padding: '8px 18px', borderRadius: '6px', border: '2px solid #2c9678', background: 'transparent', color: '#2c9678', cursor: 'pointer', fontSize: '14px', '&:hover': {backgroundColor: 'rgba(44,150,120,0.1)'}, }, tokenValueBox: { backgroundColor: 'rgba(255,255,255,0.9)', borderRadius: '8px', padding: '12px 14px', fontFamily: 'monospace', fontSize: '13px', wordBreak: 'break-all', marginBottom: '12px', color: '#1a1a2e', border: '1px solid rgba(44,150,120,0.4)', }, warningText: { color: '#e53935', fontSize: '12px', marginBottom: '16px', }, copyBtn: { padding: '8px 18px', borderRadius: '6px', border: 'none', background: '#2c9678', color: 'white', cursor: 'pointer', fontSize: '14px', '&:hover': {backgroundColor: '#1f6d5a'}, }, usageCard: { backgroundColor: 'rgba(255,255,255,0.5)', borderRadius: '10px', padding: '16px 20px', }, usageTitle: { color: '#2c9678', margin: '0 0 10px', fontSize: '14px', fontWeight: 600, }, pre: { margin: '6px 0', padding: '10px 14px', backgroundColor: '#1a1a2e', color: '#c3e88d', borderRadius: '6px', fontSize: '13px', overflowX: 'auto', fontFamily: 'monospace', }, }); interface Session { user_id: number; username: string; role_label: string; permissions: string[]; } export const SelfPage: React.FC = () => { const style = useStyle(); const [session, setSession] = useState(null); const [tokens, setTokens] = useState([]); const [loading, setLoading] = useState(true); const [showCreate, setShowCreate] = useState(false); const [newTokenName, setNewTokenName] = useState(''); const [creating, setCreating] = useState(false); const [createdToken, setCreatedToken] = useState(null); useEffect(() => { fetch('/api/uauth/me') .then(async res => { if (!res.ok) { window.location.href = '/login'; return; } const json = await res.json(); const s: Session = json.data; setSession(s); if (!s.permissions.includes('token_manage')) { message.warning('无 Token 管理权限'); return; } return loadTokens(); }) .catch(() => { window.location.href = '/login'; }) .finally(() => setLoading(false)); }, []); async function loadTokens() { try { const list = await tokenApi.list(); setTokens(list ?? []); } catch (e: unknown) { message.error(e instanceof Error ? e.message : '加载失败'); } } async function handleCreate() { if (!newTokenName.trim()) { message.warning('请输入 Token 名称'); return; } setCreating(true); try { const res = await tokenApi.create(newTokenName.trim()); setCreatedToken(res); setNewTokenName(''); setShowCreate(false); await loadTokens(); } catch (e: unknown) { message.error(e instanceof Error ? e.message : '创建失败'); } finally { setCreating(false); } } async function handleDelete(id: number, name: string) { if (!confirm(`确认吊销 Token「${name}」?`)) return; try { await tokenApi.delete(id); message.success('已吊销'); setTokens(prev => prev.filter(t => t.id !== id)); } catch (e: unknown) { message.error(e instanceof Error ? e.message : '操作失败'); } } function handleCopyToken(val: string) { navigator.clipboard.writeText(val) .then(() => message.success('已复制到剪贴板')) .catch(() => message.warning('复制失败,请手动复制')); } function formatDate(s: string | null) { if (!s) return '-'; return new Date(s).toLocaleString(); } const hasTokenPerm = session?.permissions.includes('token_manage') ?? false; return (

个人中心

{!loading && session && ( <> {/* User info card */}

账号信息

用户名:{session.username}

角色:{session.role_label}

{/* Token management card */} {hasTokenPerm && (

API Token

setShowCreate(true)}>+ 新建 Token
{tokens.length === 0 ? ( ) : ( tokens.map(t => ( )) )}
名称 创建时间 最后使用 操作
暂无 Token,点击「新建 Token」创建
{t.name} {formatDate(t.created_at)} {formatDate(t.last_used_at)}
{/* Usage guide */}

使用方式(curl 示例)

{`curl -H "Authorization: Bearer " \\
     -T  \\
     https:///api/v1/upload/`}

返回示例:

{`{"status":200,"data":{"code":"ABCD1234"}}`}

下载文件:

{`https:///ushare/`}
)} {!hasTokenPerm && (

当前角色无 Token 管理权限

)} )} {/* Create token dialog */} {showCreate && (
setShowCreate(false)}>
e.stopPropagation()}>

新建 API Token

setNewTokenName(e.target.value)} onKeyDown={e => e.key === 'Enter' && handleCreate()} autoFocus />
创建
)} {/* Newly created token display - shown only once */} {createdToken && (
setCreatedToken(null)}>
e.stopPropagation()}>

Token 已创建

请立即复制并妥善保存,Token 值仅显示一次,关闭后无法再次查看!

{createdToken.token}
)}
); };