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 }
+ })