feat: init proj
This commit is contained in:
119
web/src/components/Output.tsx
Normal file
119
web/src/components/Output.tsx
Normal file
@@ -0,0 +1,119 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { config as configApi } from '../api';
|
||||
|
||||
export default function Output() {
|
||||
const [preview, setPreview] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const surgeUrl = `${window.location.origin}/surge`;
|
||||
|
||||
const loadPreview = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const data = await configApi.preview();
|
||||
setPreview(data.config);
|
||||
} catch (err: any) {
|
||||
setPreview(`# Error: ${err.message}`);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => { loadPreview(); }, []);
|
||||
|
||||
const handleCopy = () => {
|
||||
navigator.clipboard.writeText(surgeUrl);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2 style={styles.title}>输出配置</h2>
|
||||
<p style={styles.subtitle}>Surge 客户端订阅链接和配置预览</p>
|
||||
|
||||
{/* Subscription URL */}
|
||||
<div style={{
|
||||
background: 'var(--bg-input)',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 'var(--radius)',
|
||||
padding: 16,
|
||||
marginBottom: 20,
|
||||
}}>
|
||||
<div style={{
|
||||
fontSize: 10,
|
||||
fontFamily: 'var(--font-mono)',
|
||||
color: 'var(--text-muted)',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.1em',
|
||||
marginBottom: 8,
|
||||
}}>
|
||||
Surge 订阅链接
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
|
||||
<code style={{
|
||||
flex: 1,
|
||||
fontFamily: 'var(--font-mono)',
|
||||
fontSize: 13,
|
||||
color: 'var(--accent)',
|
||||
userSelect: 'all',
|
||||
}}>
|
||||
{surgeUrl}
|
||||
</code>
|
||||
<button className="small" onClick={handleCopy}>
|
||||
{copied ? '已复制' : '复制'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Preview */}
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }}>
|
||||
<span style={{
|
||||
fontSize: 10,
|
||||
fontFamily: 'var(--font-mono)',
|
||||
color: 'var(--text-muted)',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.1em',
|
||||
}}>
|
||||
配置预览
|
||||
</span>
|
||||
<button className="small" onClick={loadPreview} disabled={loading}>
|
||||
{loading ? '加载中...' : '刷新预览'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<pre style={{
|
||||
background: 'var(--bg-input)',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 'var(--radius)',
|
||||
padding: 16,
|
||||
fontFamily: 'var(--font-mono)',
|
||||
fontSize: 11,
|
||||
lineHeight: 1.6,
|
||||
color: 'var(--text-secondary)',
|
||||
overflow: 'auto',
|
||||
maxHeight: 'calc(100vh - 360px)',
|
||||
whiteSpace: 'pre-wrap',
|
||||
wordBreak: 'break-all',
|
||||
}}>
|
||||
{preview || '(empty)'}
|
||||
</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = {
|
||||
title: {
|
||||
fontFamily: 'var(--font-mono)' as const,
|
||||
fontSize: 16,
|
||||
fontWeight: 600 as const,
|
||||
color: 'var(--text-primary)',
|
||||
marginBottom: 4,
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: 12,
|
||||
color: 'var(--text-secondary)',
|
||||
marginBottom: 20,
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user