From 492fba31059d3f23b8a3d74762bf98613173bcb2 Mon Sep 17 00:00:00 2001 From: imbytecat Date: Mon, 26 Jan 2026 15:14:23 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E8=AE=B8=E5=8F=AF?= =?UTF-8?q?=E8=AF=81=E5=81=9C=E7=94=A8=E5=8A=9F=E8=83=BD=E5=8F=8A=E7=A1=AE?= =?UTF-8?q?=E8=AE=A4=E5=BC=B9=E7=AA=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加反激活许可证的确认弹窗功能,包含二次确认提示和操作反馈。 - 添加停用许可证的接口合约定义。 - 添加许可证停用功能,确保激活记录存在后将其许可证信息和激活时间清空。 --- apps/server/src/routes/license.tsx | 72 +++++++++++++++++++ .../server/api/contracts/license.contract.ts | 4 ++ .../src/server/api/routers/license.router.ts | 22 ++++++ 3 files changed, 98 insertions(+) diff --git a/apps/server/src/routes/license.tsx b/apps/server/src/routes/license.tsx index 0da359e..5106af9 100644 --- a/apps/server/src/routes/license.tsx +++ b/apps/server/src/routes/license.tsx @@ -14,6 +14,7 @@ export const Route = createFileRoute('/license')({ function License() { const [licenseInput, setLicenseInput] = useState('') const [copySuccess, setCopySuccess] = useState(false) + const [showDeactivateConfirm, setShowDeactivateConfirm] = useState(false) const queryClient = useQueryClient() // 获取激活状态 @@ -32,11 +33,26 @@ function License() { }, }) + // 反激活 mutation + const deactivateMutation = useMutation({ + ...orpc.license.deactivate.mutationOptions(), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: orpc.license.getActivation.key(), + }) + setShowDeactivateConfirm(false) + }, + }) + const handleActivate = () => { if (!licenseInput.trim()) return activateMutation.mutate({ license: licenseInput.trim() }) } + const handleDeactivate = () => { + deactivateMutation.mutate() + } + const handleCopyFingerprint = async () => { try { await navigator.clipboard.writeText(data.fingerprint) @@ -352,6 +368,62 @@ function License() {

)} + + {!showDeactivateConfirm ? ( + + ) : ( +
+

+ + 确定要反激活吗?此操作会清除当前 License。 +

+
+ + +
+ {deactivateMutation.isError && ( +

+ 反激活失败,请重试 +

+ )} +
+ )} ) : (
diff --git a/apps/server/src/server/api/contracts/license.contract.ts b/apps/server/src/server/api/contracts/license.contract.ts index ce86419..7d7220e 100644 --- a/apps/server/src/server/api/contracts/license.contract.ts +++ b/apps/server/src/server/api/contracts/license.contract.ts @@ -12,3 +12,7 @@ export const getActivation = oc.input(z.void()).output( export const activate = oc .input(z.object({ license: z.string().min(1) })) .output(z.object({ success: z.boolean() })) + +export const deactivate = oc + .input(z.void()) + .output(z.object({ success: z.boolean() })) diff --git a/apps/server/src/server/api/routers/license.router.ts b/apps/server/src/server/api/routers/license.router.ts index 43ab10a..6188b8e 100644 --- a/apps/server/src/server/api/routers/license.router.ts +++ b/apps/server/src/server/api/routers/license.router.ts @@ -43,3 +43,25 @@ export const activate = os.license.activate return { success: true } }) + +export const deactivate = os.license.deactivate + .use(dbProvider) + .handler(async ({ context }) => { + await ensureLicenseActivationInitialized() + + const record = await context.db.query.licenseActivationTable.findFirst() + + if (!record) { + throw new Error('License activation record not found') + } + + await context.db + .update(licenseActivationTable) + .set({ + license: null, + licenseActivatedAt: null, + }) + .where(eq(licenseActivationTable.id, record.id)) + + return { success: true } + })