feat(license): 支持在租户平台修改 License 过期时间

描述详情框中的过期时间行增加内联日期选择器,与最大设备数保持
相同的行内编辑交互,支持设置具体日期或留空(永久有效)。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
这个提交包含在:
XuqmGroup 2026-05-21 16:24:39 +08:00
父节点 c4373c8cc1
当前提交 f3c58866df
共有 2 个文件被更改,包括 42 次插入2 次删除

查看文件

@ -104,7 +104,7 @@ export const licenseApi = {
) )
}, },
updateAppLicense(appKey: string, data: { maxDevices?: number; isActive?: boolean; remark?: string }) { updateAppLicense(appKey: string, data: { maxDevices?: number; isActive?: boolean; remark?: string; expiresAt?: string }) {
return licenseClient.patch<{ data: AppLicense }>( return licenseClient.patch<{ data: AppLicense }>(
`/api/license/admin/apps/${encodeURIComponent(appKey)}`, `/api/license/admin/apps/${encodeURIComponent(appKey)}`,
data, data,

查看文件

@ -64,7 +64,22 @@
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="已注册设备">{{ license?.registeredDevices ?? devices.length }}</el-descriptions-item> <el-descriptions-item label="已注册设备">{{ license?.registeredDevices ?? devices.length }}</el-descriptions-item>
<el-descriptions-item label="过期时间"> <el-descriptions-item label="过期时间">
{{ license?.expiresAt ? formatDate(license.expiresAt) : '永久' }} <div v-if="!editingExpiresAt" class="max-devices-display">
<span>{{ license?.expiresAt ? formatDate(license.expiresAt) : '永久' }}</span>
<el-button link type="primary" size="small" style="margin-left:8px" @click="startEditExpiresAt">修改</el-button>
</div>
<div v-else class="max-devices-edit">
<el-date-picker
v-model="editExpiresAtValue"
type="datetime"
placeholder="留空表示永久"
clearable
style="width:200px"
size="small"
/>
<el-button type="primary" size="small" :loading="savingExpiresAt" style="margin-left:8px" @click="saveExpiresAt">保存</el-button>
<el-button size="small" style="margin-left:4px" @click="editingExpiresAt = false">取消</el-button>
</div>
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
</div> </div>
@ -163,6 +178,9 @@ const loading = ref(false)
const editingMaxDevices = ref(false) const editingMaxDevices = ref(false)
const editMaxDevicesValue = ref(1) const editMaxDevicesValue = ref(1)
const savingMaxDevices = ref(false) const savingMaxDevices = ref(false)
const editingExpiresAt = ref(false)
const editExpiresAtValue = ref<Date | null>(null)
const savingExpiresAt = ref(false)
const appName = computed(() => currentApp.value?.name || license.value?.name || appKey.value || '-') const appName = computed(() => currentApp.value?.name || license.value?.name || appKey.value || '-')
const activeDevices = computed(() => devices.value.filter(d => d.isActive).length) const activeDevices = computed(() => devices.value.filter(d => d.isActive).length)
@ -213,6 +231,28 @@ async function saveMaxDevices() {
} }
} }
function startEditExpiresAt() {
editExpiresAtValue.value = license.value?.expiresAt ? new Date(license.value.expiresAt) : null
editingExpiresAt.value = true
}
async function saveExpiresAt() {
const key = appKey.value
if (!key) return
savingExpiresAt.value = true
try {
const expiresAt = editExpiresAtValue.value ? editExpiresAtValue.value.toISOString() : ''
const res = await licenseApi.updateAppLicense(key, { expiresAt })
license.value = res.data.data
editingExpiresAt.value = false
ElMessage.success('过期时间已更新')
} catch {
ElMessage.error('更新失败')
} finally {
savingExpiresAt.value = false
}
}
async function revokeDevice(row: LicenseDevice) { async function revokeDevice(row: LicenseDevice) {
try { try {
await ElMessageBox.confirm('确认吊销该设备?', '吊销确认', { type: 'warning' }) await ElMessageBox.confirm('确认吊销该设备?', '吊销确认', { type: 'warning' })