feat: clash/ss suport
This commit is contained in:
@@ -4,10 +4,14 @@ 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 [copiedSurge, setCopiedSurge] = useState(false);
|
||||
const [copiedClash, setCopiedClash] = useState(false);
|
||||
const [copiedSsr, setCopiedSsr] = useState(false);
|
||||
const [surgeToken, setSurgeToken] = useState('');
|
||||
|
||||
const surgeUrl = surgeToken ? `${window.location.origin}/surge/${surgeToken}` : '';
|
||||
const clashUrl = surgeToken ? `${window.location.origin}/clash/${surgeToken}` : '';
|
||||
const ssrUrl = surgeToken ? `${window.location.origin}/ssr/${surgeToken}` : '';
|
||||
|
||||
const loadToken = async () => {
|
||||
try {
|
||||
@@ -17,7 +21,7 @@ export default function Output() {
|
||||
};
|
||||
|
||||
const handleRegenerate = async () => {
|
||||
if (!confirm('重新生成后,旧的订阅链接将失效,Surge 客户端需要更新订阅地址。确定继续?')) return;
|
||||
if (!confirm('重新生成后,旧的订阅链接将失效(Surge / Clash / SSR 三条链接同时更新)。确定继续?')) return;
|
||||
try {
|
||||
const data = await configApi.regenerateSurgeToken();
|
||||
setSurgeToken(data.token);
|
||||
@@ -42,93 +46,88 @@ export default function Output() {
|
||||
loadPreview();
|
||||
}, []);
|
||||
|
||||
const handleCopy = () => {
|
||||
navigator.clipboard.writeText(surgeUrl);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
const makeCopyHandler = (url: string, setter: (v: boolean) => void) => () => {
|
||||
navigator.clipboard.writeText(url);
|
||||
setter(true);
|
||||
setTimeout(() => setter(false), 2000);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2 style={styles.title}>输出配置</h2>
|
||||
<p style={styles.subtitle}>Surge 客户端订阅链接和配置预览</p>
|
||||
<p style={styles.subtitle}>Surge / Clash / SSR 订阅链接和配置预览</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',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
}}>
|
||||
{surgeUrl || '加载中...'}
|
||||
</code>
|
||||
<button className="small" onClick={handleCopy} disabled={!surgeUrl}>
|
||||
{copied ? '已复制' : '复制'}
|
||||
</button>
|
||||
<button className="small" onClick={handleRegenerate} disabled={!surgeUrl}>
|
||||
重新生成
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/* Surge URL */}
|
||||
<UrlCard
|
||||
label="SURGE 订阅链接"
|
||||
url={surgeUrl}
|
||||
copied={copiedSurge}
|
||||
onCopy={makeCopyHandler(surgeUrl, setCopiedSurge)}
|
||||
onRegenerate={handleRegenerate}
|
||||
/>
|
||||
|
||||
{/* Clash URL */}
|
||||
<UrlCard
|
||||
label="CLASH / STASH 订阅链接"
|
||||
url={clashUrl}
|
||||
copied={copiedClash}
|
||||
onCopy={makeCopyHandler(clashUrl, setCopiedClash)}
|
||||
/>
|
||||
|
||||
{/* SSR URL */}
|
||||
<UrlCard
|
||||
label="SSR / QX / 小火箭 订阅链接"
|
||||
url={ssrUrl}
|
||||
copied={copiedSsr}
|
||||
onCopy={makeCopyHandler(ssrUrl, setCopiedSsr)}
|
||||
/>
|
||||
|
||||
{/* 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>
|
||||
<span style={styles.sectionLabel}>SURGE 配置预览</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',
|
||||
}}>
|
||||
<pre style={styles.pre}>
|
||||
{preview || '(empty)'}
|
||||
</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function UrlCard({
|
||||
label,
|
||||
url,
|
||||
copied,
|
||||
onCopy,
|
||||
onRegenerate,
|
||||
}: {
|
||||
label: string;
|
||||
url: string;
|
||||
copied: boolean;
|
||||
onCopy: () => void;
|
||||
onRegenerate?: () => void;
|
||||
}) {
|
||||
return (
|
||||
<div style={styles.card}>
|
||||
<div style={styles.cardLabel}>{label}</div>
|
||||
<div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
|
||||
<code style={styles.urlCode}>{url || '加载中...'}</code>
|
||||
<button className="small" onClick={onCopy} disabled={!url}>
|
||||
{copied ? '已复制' : '复制'}
|
||||
</button>
|
||||
{onRegenerate && (
|
||||
<button className="small" onClick={onRegenerate} disabled={!url}>
|
||||
重新生成
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = {
|
||||
title: {
|
||||
fontFamily: 'var(--font-mono)' as const,
|
||||
@@ -142,4 +141,50 @@ const styles = {
|
||||
color: 'var(--text-secondary)',
|
||||
marginBottom: 20,
|
||||
},
|
||||
card: {
|
||||
background: 'var(--bg-input)',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 'var(--radius)',
|
||||
padding: 16,
|
||||
marginBottom: 12,
|
||||
},
|
||||
cardLabel: {
|
||||
fontSize: 10,
|
||||
fontFamily: 'var(--font-mono)' as const,
|
||||
color: 'var(--text-muted)',
|
||||
textTransform: 'uppercase' as const,
|
||||
letterSpacing: '0.1em',
|
||||
marginBottom: 8,
|
||||
},
|
||||
urlCode: {
|
||||
flex: 1,
|
||||
fontFamily: 'var(--font-mono)' as const,
|
||||
fontSize: 13,
|
||||
color: 'var(--accent)',
|
||||
userSelect: 'all' as const,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap' as const,
|
||||
},
|
||||
sectionLabel: {
|
||||
fontSize: 10,
|
||||
fontFamily: 'var(--font-mono)' as const,
|
||||
color: 'var(--text-muted)',
|
||||
textTransform: 'uppercase' as const,
|
||||
letterSpacing: '0.1em',
|
||||
},
|
||||
pre: {
|
||||
background: 'var(--bg-input)',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 'var(--radius)',
|
||||
padding: 16,
|
||||
fontFamily: 'var(--font-mono)' as const,
|
||||
fontSize: 11,
|
||||
lineHeight: 1.6,
|
||||
color: 'var(--text-secondary)',
|
||||
overflow: 'auto',
|
||||
maxHeight: 'calc(100vh - 480px)',
|
||||
whiteSpace: 'pre-wrap' as const,
|
||||
wordBreak: 'break-all' as const,
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user