diff --git a/frontend/src/hooks/useClients.ts b/frontend/src/hooks/useClients.ts index 93c470f7..dc6f8989 100644 --- a/frontend/src/hooks/useClients.ts +++ b/frontend/src/hooks/useClients.ts @@ -34,6 +34,8 @@ interface SubSettings { subURI: string; subJsonURI: string; subJsonEnable: boolean; + subClashURI: string; + subClashEnable: boolean; } export interface ClientQueryParams { @@ -157,7 +159,16 @@ export function useClients() { subURI: (defaults.subURI as string) || '', subJsonURI: (defaults.subJsonURI as string) || '', subJsonEnable: !!defaults.subJsonEnable, - }), [defaults.subEnable, defaults.subURI, defaults.subJsonURI, defaults.subJsonEnable]); + subClashURI: (defaults.subClashURI as string) || '', + subClashEnable: !!defaults.subClashEnable, + }), [ + defaults.subEnable, + defaults.subURI, + defaults.subJsonURI, + defaults.subJsonEnable, + defaults.subClashURI, + defaults.subClashEnable, + ]); const ipLimitEnable = !!defaults.ipLimitEnable; const tgBotEnable = !!defaults.tgBotEnable; diff --git a/frontend/src/pages/clients/ClientInfoModal.tsx b/frontend/src/pages/clients/ClientInfoModal.tsx index 4a500a28..c7c7989a 100644 --- a/frontend/src/pages/clients/ClientInfoModal.tsx +++ b/frontend/src/pages/clients/ClientInfoModal.tsx @@ -99,6 +99,8 @@ interface SubSettings { subURI: string; subJsonURI: string; subJsonEnable: boolean; + subClashURI: string; + subClashEnable: boolean; } interface ClientInfoModalProps { @@ -115,7 +117,14 @@ interface ApiMsg { obj?: T; } -const DEFAULT_SUB: SubSettings = { enable: false, subURI: '', subJsonURI: '', subJsonEnable: false }; +const DEFAULT_SUB: SubSettings = { + enable: false, + subURI: '', + subJsonURI: '', + subJsonEnable: false, + subClashURI: '', + subClashEnable: false, +}; export default function ClientInfoModal({ open, @@ -176,6 +185,12 @@ export default function ClientInfoModal({ return subSettings.subJsonURI + client.subId; }, [client?.subId, subSettings?.subJsonEnable, subSettings?.subJsonURI]); + const subClashLink = useMemo(() => { + if (!client?.subId) return ''; + if (!subSettings?.subClashEnable || !subSettings?.subClashURI) return ''; + return subSettings.subClashURI + client.subId; + }, [client?.subId, subSettings?.subClashEnable, subSettings?.subClashURI]); + const showSubscription = !!(subSettings?.enable && client?.subId); async function copyValue(text: string) { @@ -459,6 +474,37 @@ export default function ClientInfoModal({ )} + {subClashLink && ( +
+ + CLASH + + + {client.subId} + +
+ +
+
+ )} )} diff --git a/frontend/src/pages/sub/SubPage.css b/frontend/src/pages/sub/SubPage.css index bc1bfd9a..d3765c7f 100644 --- a/frontend/src/pages/sub/SubPage.css +++ b/frontend/src/pages/sub/SubPage.css @@ -28,44 +28,31 @@ margin-top: 8px; } -.qr-row { - margin-bottom: 12px; -} - -.qr-col { - display: flex; - justify-content: center; -} - -.qr-box { - display: inline-flex; - flex-direction: column; - align-items: center; - gap: 4px; - width: 240px; -} - .qr-tag { width: 100%; text-align: center; margin: 0; } -.qr-code { - cursor: pointer; -} - .info-table { - margin-top: 12px; + margin-top: 4px; } .links-section { - margin-top: 16px; display: flex; flex-direction: column; gap: 8px; } +.sub-link-anchor { + color: inherit; + text-decoration: none; +} + +.sub-link-anchor:hover { + text-decoration: underline; +} + .sub-link-row { display: flex; align-items: center; diff --git a/frontend/src/pages/sub/SubPage.tsx b/frontend/src/pages/sub/SubPage.tsx index 8c06e2fb..2cef8cd7 100644 --- a/frontend/src/pages/sub/SubPage.tsx +++ b/frontend/src/pages/sub/SubPage.tsx @@ -6,6 +6,7 @@ import { Col, ConfigProvider, Descriptions, + Divider, Dropdown, Layout, Menu, @@ -15,6 +16,7 @@ import { Row, Space, Tag, + Tooltip, } from 'antd'; import { AndroidOutlined, @@ -333,63 +335,6 @@ export default function SubPage() { - - -
- {t('pages.settings.subSettings')} - copy(subUrl)} - /> -
- - {subJsonUrl && ( - -
- - {t('pages.settings.subSettings')} JSON - - copy(subJsonUrl)} - /> -
- - )} - {subClashUrl && ( - -
- Clash / Mihomo - copy(subClashUrl)} - /> -
- - )} -
- {links.length > 0 && ( -
- {links.map((link, idx) => { - const meta = parseLinkMeta(link, idx); - const rowTitle = trimEmail(meta.remark, linkEmails[idx] || '') || meta.remark; - const canQr = !isPostQuantumLink(link); - return ( -
- + {t('pages.inbounds.copyLink')} +
+ {links.map((link, idx) => { + const meta = parseLinkMeta(link, idx); + const rowTitle = trimEmail(meta.remark, linkEmails[idx] || '') || meta.remark; + const canQr = !isPostQuantumLink(link); + return ( +
+ + {meta.protocol} + + + {rowTitle} + +
+
+ } + > +
+
+ ); + })} +
+ + )} + + {(subUrl || subJsonUrl || subClashUrl) && ( + <> + {t('subscription.title')} +
+ {subUrl && ( +
+ SUB + - {meta.protocol} - - - {rowTitle} - + {sId} +
-
- } - > -
+ } + > +
- ); - })} - + )} + {subJsonUrl && ( +
+ JSON + + {sId} + +
+
+ } + > +
+ + )} + {subClashUrl && ( +
+ + CLASH + + + {sId} + +
+
+ } + > +
+ + )} + + )} diff --git a/frontend/src/schemas/defaults.ts b/frontend/src/schemas/defaults.ts index 6f2fb809..61d79a24 100644 --- a/frontend/src/schemas/defaults.ts +++ b/frontend/src/schemas/defaults.ts @@ -9,6 +9,8 @@ export const DefaultsPayloadSchema = z.object({ subURI: z.string().optional(), subJsonURI: z.string().optional(), subJsonEnable: z.boolean().optional(), + subClashURI: z.string().optional(), + subClashEnable: z.boolean().optional(), pageSize: z.number().optional(), remarkModel: z.string().optional(), datepicker: z.enum(['gregorian', 'jalalian']).optional(),