feat: 更新许可证管理功能,添加手动创建许可证的接口,增强授权信息展示,更新相关国际化文本

This commit is contained in:
wood911010 committed 2025-11-19 00:53:37 +08:00
1 parent 49e5952e1d
commit 2f683fa22b
11 files changed
+532 -77

No files matched your search

+18
View File
@@ -20,6 +20,7 @@ export interface AuthorizationCode {
encryption_type_display?: string;
software_version?: string;
max_activations: number;
current_activations?: number;
activated_licenses_count?: number;
is_locked?: boolean;
feature_config?: any;
@@ -301,6 +302,23 @@ export function getLicenseDeviceDetail(id: string): Promise<ApiResponse<LicenseD
return Axios.get(`/api/v1/licenses/${id}`)
}
// 创建许可证请求(手动绑定设备)
export interface LicenseDeviceCreateRequest {
authorization_code_id: string
hardware_fingerprint: string
device_info?: Record<string, any>
activation_ip?: string
}
/**
* 手动创建许可证
*/
export function createLicenseDevice(
data: LicenseDeviceCreateRequest
): Promise<ApiResponse<LicenseDevice>> {
return Axios.post('/api/v1/licenses', data)
}
// 授权变更历史相关类型定义
export interface AuthorizationChangeItem {
id: string
@@ -64,9 +64,10 @@ const { t } = useI18n()
const defaultNavItems = computed(() => [
{ id: "dashboard", label: t('navigation.menu.dashboard'), href: "/dashboard", icon: "dashboard" },
{ id: "customers", label: t('navigation.menu.customers'), href: "/customers", icon: "customers" },
{ id: "licenses", label: t('navigation.menu.licenses'), href: "/licenses", icon: "licenses" },
{ id: "roles", label: t('navigation.menu.roles'), href: "/roles", icon: "roles" },
{ id: "users", label: t('navigation.menu.users'), href: "/users", icon: "users" }
{ id: "licenses", label: t('navigation.menu.licenses'), href: "/licenses", icon: "licenses" }
// 暂时隐藏角色和系统用户菜单,后续版本再开启
// { id: "roles", label: t('navigation.menu.roles'), href: "/roles", icon: "roles" },
// { id: "users", label: t('navigation.menu.users'), href: "/users", icon: "users" }
])
// 使用 store 和路由
+50 -2
View File
@@ -316,7 +316,7 @@
"authorizationInfo": {
"basicInfo": "Basic Information",
"customerName": "Customer Name",
"customerId": "Customer ID",
"customerCode": "Customer Code",
"createdBy": "Created By",
"description": "Description",
"title": "Authorization Information",
@@ -334,6 +334,54 @@
"code": "Authorization Code",
"tipText": "Provide this authorization code to the customer, who can automatically obtain the software license after configuring it in the software product"
},
"licenseInfo": {
"addButton": "Add License",
"emptyDescription": "No device licenses yet",
"deviceTitle": "Device {index}",
"deviceAddress": "Device address",
"lastActiveTime": "Last active time",
"hardwareSectionTitle": "Hardware Information",
"hardwareSectionSubtitle": "(Binding info)",
"hardwareFingerprint": "Hardware fingerprint",
"softwareSectionTitle": "Software License",
"softwareSectionSubtitle": "(Used for validation)",
"licenseKey": "License key",
"licenseStatus": "License status",
"statusDescription": "This license is generated and bound to the specified hardware for runtime validation.",
"messages": {
"loadError": "Failed to load license devices"
}
},
"licenseModal": {
"title": "Add License",
"description": "Bind this authorization code to a hardware fingerprint.",
"fields": {
"authorizationCode": "Authorization Code",
"hardwareFingerprint": "Hardware Fingerprint",
"remark": "Remark"
},
"placeholders": {
"hardwareFingerprint": "Enter hardware fingerprint",
"remark": "Add a remark (optional)"
},
"validation": {
"hardwareRequired": "Hardware fingerprint is required",
"hardwareMax": "Hardware fingerprint cannot exceed 200 characters",
"remarkMax": "Remark cannot exceed 3000 characters"
},
"messages": {
"success": "License created successfully",
"error": "Failed to create license",
"missingAuthorization": "Missing authorization code information",
"copySuccess": "Authorization code copied",
"copyError": "Failed to copy authorization code"
},
"buttons": {
"cancel": "Cancel",
"submit": "Create",
"copy": "Copy"
}
},
"actions": {
"copyCode": "Copy License Code",
"updateLicense": "Update License",
@@ -574,7 +622,7 @@
"customerNotExist": "Customer data does not exist"
},
"basicInfo": {
"customerId": "Customer ID",
"customerCode": "Customer Code",
"customerName": "Customer Name",
"activatedDevices": "Activated Devices Count",
"creationTime": "Creation Time",
+50 -2
View File
@@ -316,7 +316,7 @@
"authorizationInfo": {
"basicInfo": "基本情報",
"customerName": "顧客名",
"customerId": "顧客ID",
"customerCode": "顧客コード",
"createdBy": "作成者",
"description": "備考",
"title": "認証情報",
@@ -334,6 +334,54 @@
"code": "認証コード",
"tipText": "この認証コードを顧客に提供してください。顧客がソフトウェア製品で設定すると、ソフトウェアライセンスを自動的に取得できます"
},
"licenseInfo": {
"addButton": "ライセンスを追加",
"emptyDescription": "デバイスに紐づくライセンスはありません",
"deviceTitle": "デバイス{index}",
"deviceAddress": "デバイスアドレス",
"lastActiveTime": "最終アクティブ時間",
"hardwareSectionTitle": "ハードウェア情報",
"hardwareSectionSubtitle": "(バインド情報)",
"hardwareFingerprint": "ハードウェア指紋",
"softwareSectionTitle": "ソフトウェアライセンス",
"softwareSectionSubtitle": "(システム検証用)",
"licenseKey": "ライセンスキー",
"licenseStatus": "ライセンス状態",
"statusDescription": "このライセンスは指定ハードウェアにバインドされ、システム検証に利用されます",
"messages": {
"loadError": "デバイスライセンスの取得に失敗しました"
}
},
"licenseModal": {
"title": "ライセンスを追加",
"description": "この認証コードをハードウェア指紋に紐づけてください。",
"fields": {
"authorizationCode": "認証コード",
"hardwareFingerprint": "ハードウェア指紋",
"remark": "備考"
},
"placeholders": {
"hardwareFingerprint": "ハードウェア指紋を入力してください",
"remark": "備考(任意)を入力してください"
},
"validation": {
"hardwareRequired": "ハードウェア指紋を入力してください",
"hardwareMax": "ハードウェア指紋は200文字以内で入力してください",
"remarkMax": "備考は3000文字以内で入力してください"
},
"messages": {
"success": "ライセンスを作成しました",
"error": "ライセンスの作成に失敗しました",
"missingAuthorization": "認証コード情報が見つかりません",
"copySuccess": "認証コードをコピーしました",
"copyError": "認証コードのコピーに失敗しました"
},
"buttons": {
"cancel": "キャンセル",
"submit": "作成",
"copy": "コピー"
}
},
"actions": {
"copyCode": "ライセンスコードをコピー",
"updateLicense": "ライセンス更新",
@@ -574,7 +622,7 @@
"customerNotExist": "顧客データが存在しません"
},
"basicInfo": {
"customerId": "顧客ID",
"customerCode": "顧客コード",
"customerName": "顧客名",
"activatedDevices": "アクティベート済みデバイス数",
"creationTime": "作成日時",
+50 -2
View File
@@ -316,7 +316,7 @@
"authorizationInfo": {
"basicInfo": "基本信息",
"customerName": "客户名称",
"customerId": "客户ID",
"customerCode": "客户编码",
"createdBy": "创建人",
"description": "备注",
"title": "授权信息",
@@ -334,6 +334,54 @@
"code": "授权码",
"tipText": "将此授权码提供给客户,客户在软件产品中配置后可自动获取软件许可证"
},
"licenseInfo": {
"addButton": "新增许可证",
"emptyDescription": "暂无设备许可证信息",
"deviceTitle": "设备{index}",
"deviceAddress": "设备地址",
"lastActiveTime": "最后活跃时间",
"hardwareSectionTitle": "硬件信息",
"hardwareSectionSubtitle": "(绑定信息)",
"hardwareFingerprint": "硬件指纹",
"softwareSectionTitle": "软件许可证",
"softwareSectionSubtitle": "(用于系统验证)",
"licenseKey": "许可证密钥",
"licenseStatus": "许可证状态",
"statusDescription": "此许可证已生成并绑定到指定硬件,可用于系统运行验证",
"messages": {
"loadError": "获取设备列表失败"
}
},
"licenseModal": {
"title": "新增许可证",
"description": "请填写硬件指纹信息,将授权码绑定到指定设备。",
"fields": {
"authorizationCode": "授权码",
"hardwareFingerprint": "硬件指纹",
"remark": "备注"
},
"placeholders": {
"hardwareFingerprint": "请输入硬件指纹信息",
"remark": "请输入备注信息(可选)"
},
"validation": {
"hardwareRequired": "请输入硬件指纹",
"hardwareMax": "硬件指纹长度不能超过200个字符",
"remarkMax": "备注长度不能超过3000个字符"
},
"messages": {
"success": "许可证创建成功",
"error": "许可证创建失败",
"missingAuthorization": "缺少授权码信息",
"copySuccess": "授权码已复制",
"copyError": "复制授权码失败"
},
"buttons": {
"cancel": "取消",
"submit": "创建",
"copy": "复制"
}
},
"actions": {
"copyCode": "复制授权码",
"updateLicense": "变更授权",
@@ -574,7 +622,7 @@
"customerNotExist": "客户数据不存在"
},
"basicInfo": {
"customerId": "客户ID",
"customerCode": "客户编码",
"customerName": "客户名称",
"activatedDevices": "已激活设备数量",
"creationTime": "创建时间",
@@ -9,8 +9,8 @@
<div class="info-value">{{ licenseData?.customer_info.customer_name|| '-' }}</div>
</div>
<div class="info-row">
<div class="info-label">{{ t('pages.licenses.detail.authorizationInfo.customerId') }}:</div>
<div class="info-value">{{ licenseData?.customer_id || '-' }}</div>
<div class="info-label">{{ t('pages.licenses.detail.authorizationInfo.customerCode') }}:</div>
<div class="info-value">{{ licenseData?.customer_info?.customer_code || '-' }}</div>
</div>
<div class="info-row">
<div class="info-label">{{ t('pages.licenses.detail.authorizationInfo.createdBy') }}:</div>
@@ -3,8 +3,8 @@
<!-- 基础信息部分 -->
<div class="info-section">
<div class="info-row">
<div class="info-label">{{ $t('customers.basicInfo.customerId') }}</div>
<div class="info-value">{{ licenseData?.customer_id || '-' }}</div>
<div class="info-label">{{ $t('customers.basicInfo.customerCode') }}</div>
<div class="info-value">{{ licenseData?.customer_info?.customer_code || '-' }}</div>
</div>
<div class="info-row">
<div class="info-label">{{ $t('customers.basicInfo.customerName') }}</div>
@@ -17,7 +17,7 @@
<!-- 已激活设备数量 -->
<div class="stat-card stat-card-1">
<div class="card-content">
<div class="stat-value">{{ licenseData?.activated_licenses_count || 0 }}</div>
<div class="stat-value">{{ getActivatedLicenses(licenseData) }}</div>
<div class="stat-label">{{ $t('customers.basicInfo.activatedDevices') }}</div>
</div>
</div>
@@ -59,6 +59,17 @@ interface Props {
defineProps<Props>()
const getActivatedLicenses = (license?: AuthorizationCode | null) => {
if (!license) return 0
if (typeof license.current_activations === 'number') {
return license.current_activations
}
if (typeof license.activated_licenses_count === 'number') {
return license.activated_licenses_count
}
return 0
}
const formatDateShort = (date?: string) => {
if (!date) return '-'
return formatDateUtil(date)
@@ -1,48 +1,116 @@
<template>
<div class="license-info-container" v-loading="loading">
<el-dialog
v-model="addLicenseVisible"
class="license-add-dialog"
:title="t('pages.licenses.detail.licenseModal.title')"
width="520px"
:close-on-click-modal="false"
:close-on-press-escape="false"
align-center
@closed="resetAddForm"
>
<p class="dialog-description">
{{ t('pages.licenses.detail.licenseModal.description') }}
</p>
<el-form
ref="addFormRef"
:model="addForm"
:rules="addFormRules"
label-position="top"
class="license-form-dialog"
>
<el-form-item :label="t('pages.licenses.detail.licenseModal.fields.authorizationCode')">
<div class="auth-code-row">
<el-input :model-value="authorizationCodeValue" readonly />
<el-button text type="primary" class="copy-btn" @click="handleCopyAuthorizationCode">
{{ t('pages.licenses.detail.licenseModal.buttons.copy') }}
</el-button>
</div>
</el-form-item>
<el-form-item
:label="t('pages.licenses.detail.licenseModal.fields.hardwareFingerprint')"
prop="hardware_fingerprint"
>
<el-input
v-model="addForm.hardware_fingerprint"
type="textarea"
:autosize="{ minRows: 3, maxRows: 5 }"
:placeholder="t('pages.licenses.detail.licenseModal.placeholders.hardwareFingerprint')"
maxlength="200"
show-word-limit
/>
</el-form-item>
<el-form-item :label="t('pages.licenses.detail.licenseModal.fields.remark')" prop="remark">
<el-input
v-model="addForm.remark"
type="textarea"
:autosize="{ minRows: 3, maxRows: 5 }"
:placeholder="t('pages.licenses.detail.licenseModal.placeholders.remark')"
maxlength="3000"
show-word-limit
/>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleDialogCancel" :disabled="submitting">
{{ t('pages.licenses.detail.licenseModal.buttons.cancel') }}
</el-button>
<el-button type="primary" @click="submitAddLicense" :loading="submitting">
{{ t('pages.licenses.detail.licenseModal.buttons.submit') }}
</el-button>
</div>
</template>
</el-dialog>
<!-- 新增许可证按钮 -->
<div class="action-bar">
<el-button type="primary" @click="handleAddLicense">新增许可证</el-button>
<el-button type="primary" @click="handleAddLicense">{{
t('pages.licenses.detail.licenseInfo.addButton')
}}</el-button>
</div>
<!-- 设备列表 -->
<div v-if="!loading && devices.length > 0" class="devices-list">
<div v-for="(device, index) in devices" :key="device.id" class="device-card">
<div v-for="(device, index) in devices" :key="device.id" class="device-card">
<!-- 设备头部 -->
<div class="device-header">
<div class="device-title-row">
<span class="device-title">设备{{ index + 1 }}</span>
<span class="device-title">
{{ t('pages.licenses.detail.licenseInfo.deviceTitle', { index: index + 1 }) }}
</span>
<el-tag :type="device.is_online ? 'success' : 'danger'" size="small">
{{ device.is_online_display}}
{{ device.is_online_display }}
</el-tag>
</div>
<div class="device-info-row">
<span class="device-info-item">设备地址:{{ device.last_online_ip || device.activation_ip || '-' }}</span>
<span class="device-info-item">最后活跃时间:{{ formatDateTime(device.last_heartbeat || '') }}</span>
<span class="device-info-item">
{{ t('pages.licenses.detail.licenseInfo.deviceAddress') }}:
{{ device.last_online_ip || device.activation_ip || '-' }}
</span>
<span class="device-info-item">
{{ t('pages.licenses.detail.licenseInfo.lastActiveTime') }}:
{{ formatDateTime(device.last_heartbeat || '') }}
</span>
</div>
</div>
<!-- 硬件信息 -->
<div class="info-section hardware-section">
<div class="section-header">
<span class="section-title">硬件信息</span>
<span class="section-subtitle">(绑定信息)</span>
<span class="section-title">
{{ t('pages.licenses.detail.licenseInfo.hardwareSectionTitle') }}
</span>
<span class="section-subtitle">
{{ t('pages.licenses.detail.licenseInfo.hardwareSectionSubtitle') }}
</span>
</div>
<div class="info-grid">
<div class="info-row">
<div class="info-label">机器序列号</div>
<div class="info-value">{{ device.device_info?.machine_code || '-' }}</div>
</div>
<div class="info-row">
<div class="info-label">MAC地址</div>
<div class="info-value">{{ device.device_info?.mac_address || '-' }}</div>
</div>
<div class="info-row">
<div class="info-label">CPU ID</div>
<div class="info-value">{{ device.device_info?.cpu_id || '-' }}</div>
</div>
<div class="info-row">
<div class="info-label">硬件哈希</div>
<div class="info-label">
{{ t('pages.licenses.detail.licenseInfo.hardwareFingerprint') }}
</div>
<div class="info-value">{{ device.hardware_fingerprint || '-' }}</div>
</div>
</div>
@@ -51,17 +119,30 @@
<!-- 软件许可证 -->
<div class="info-section license-section">
<div class="section-header">
<span class="section-title">软件许可证</span>
<span class="section-subtitle">(用于系统验证)</span>
<span class="section-title">
{{ t('pages.licenses.detail.licenseInfo.softwareSectionTitle') }}
</span>
<span class="section-subtitle">
{{ t('pages.licenses.detail.licenseInfo.softwareSectionSubtitle') }}
</span>
</div>
<div class="license-content">
<div class="license-row">
<div class="license-label">许可证密钥</div>
<div class="license-label">
{{ t('pages.licenses.detail.licenseInfo.licenseKey') }}
</div>
<div class="license-value">{{ device.license_key || '-' }}</div>
</div>
<div class="license-row">
<div class="license-label">许可证状态</div>
<div class="license-value status-text">{{ device.status_display || '此许可证已生成并绑定到指定硬件,可用于系统运行验证' }}</div>
<div class="license-label">
{{ t('pages.licenses.detail.licenseInfo.licenseStatus') }}
</div>
<div class="license-value status-text">
{{
device.status_display ||
t('pages.licenses.detail.licenseInfo.statusDescription')
}}
</div>
</div>
</div>
</div>
@@ -70,18 +151,21 @@
<!-- 空状态 -->
<div v-if="!loading && devices.length === 0" class="empty-state">
<el-empty description="暂无设备许可证信息">
<el-button type="primary" @click="handleAddLicense">新增许可证</el-button>
<el-empty :description="t('pages.licenses.detail.licenseInfo.emptyDescription')">
<el-button type="primary" @click="handleAddLicense">
{{ t('pages.licenses.detail.licenseInfo.addButton') }}
</el-button>
</el-empty>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, watch } from 'vue'
import { ElMessage } from 'element-plus'
import type { AuthorizationCode, LicenseDevice } from '@/api/license'
import { getLicenseDevices, getLicenseDeviceDetail } from '@/api/license'
import { ref, onMounted, watch, reactive, computed, nextTick } from 'vue'
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
import { useI18n } from 'vue-i18n'
import type { AuthorizationCode, LicenseDevice, LicenseDeviceCreateRequest } from '@/api/license'
import { getLicenseDevices, getLicenseDeviceDetail, createLicenseDevice } from '@/api/license'
import { formatDate } from '@/utils/date'
interface Props {
@@ -89,13 +173,53 @@ interface Props {
}
const props = defineProps<Props>()
const { t } = useI18n()
const devices = ref<LicenseDevice[]>([])
const loading = ref(false)
const addLicenseVisible = ref(false)
const submitting = ref(false)
const addFormRef = ref<FormInstance>()
const addForm = reactive({
hardware_fingerprint: '',
remark: ''
})
const authorizationCodeId = computed(() => props.licenseData?.id || '')
const authorizationCodeValue = computed(() => props.licenseData?.code || '--')
const validateHardware = (_rule: any, value: string, callback: (error?: Error) => void) => {
const trimmed = value?.trim()
if (!trimmed) {
callback(new Error(t('pages.licenses.detail.licenseModal.validation.hardwareRequired')))
return
}
if (trimmed.length > 200) {
callback(new Error(t('pages.licenses.detail.licenseModal.validation.hardwareMax')))
return
}
callback()
}
const validateRemark = (_rule: any, value: string, callback: (error?: Error) => void) => {
const trimmed = value?.trim()
if (trimmed && trimmed.length > 3000) {
callback(new Error(t('pages.licenses.detail.licenseModal.validation.remarkMax')))
return
}
callback()
}
const addFormRules: FormRules = {
hardware_fingerprint: [
{ validator: validateHardware, trigger: ['blur', 'change'] }
],
remark: [{ validator: validateRemark, trigger: ['blur', 'change'] }]
}
// 获取设备列表
const fetchDevices = async () => {
if (!props.licenseData?.id) {
if (!authorizationCodeId.value) {
devices.value = []
return
}
@@ -103,7 +227,7 @@ const fetchDevices = async () => {
loading.value = true
try {
const response = await getLicenseDevices({
authorization_code_id: props.licenseData.id,
authorization_code_id: authorizationCodeId.value,
page: 1,
page_size: 100
})
@@ -133,7 +257,7 @@ const fetchDevices = async () => {
}
} catch (error) {
console.error('获取设备列表失败:', error)
ElMessage.error('获取设备列表失败')
ElMessage.error(t('pages.licenses.detail.licenseInfo.messages.loadError'))
devices.value = []
} finally {
loading.value = false
@@ -146,9 +270,12 @@ onMounted(() => {
})
// 监听licenseData变化,重新获取数据
watch(() => props.licenseData?.id, () => {
fetchDevices()
})
watch(
() => authorizationCodeId.value,
() => {
fetchDevices()
}
)
// 格式化日期时间
const formatDateTime = (dateTime: string) => {
@@ -156,10 +283,81 @@ const formatDateTime = (dateTime: string) => {
return formatDate(dateTime)
}
// 处理新增许可证
const resetAddForm = () => {
addForm.hardware_fingerprint = ''
addForm.remark = ''
nextTick(() => {
addFormRef.value?.clearValidate()
})
}
const handleDialogCancel = () => {
addLicenseVisible.value = false
resetAddForm()
}
const handleAddLicense = () => {
// TODO: 实现新增许可证逻辑
console.log('新增许可证')
if (!authorizationCodeId.value) {
ElMessage.warning(t('pages.licenses.detail.licenseModal.messages.missingAuthorization'))
return
}
addLicenseVisible.value = true
nextTick(() => {
addFormRef.value?.clearValidate()
})
}
const handleCopyAuthorizationCode = async () => {
if (!authorizationCodeValue.value || authorizationCodeValue.value === '--') {
ElMessage.warning(t('pages.licenses.detail.licenseModal.messages.missingAuthorization'))
return
}
try {
await navigator.clipboard.writeText(authorizationCodeValue.value)
ElMessage.success(t('pages.licenses.detail.licenseModal.messages.copySuccess'))
} catch (error) {
console.error('复制授权码失败:', error)
ElMessage.error(t('pages.licenses.detail.licenseModal.messages.copyError'))
}
}
const submitAddLicense = async () => {
if (!addFormRef.value) return
try {
await addFormRef.value.validate()
if (!authorizationCodeId.value) {
throw new Error(t('pages.licenses.detail.licenseModal.messages.missingAuthorization'))
}
submitting.value = true
const payload: LicenseDeviceCreateRequest = {
authorization_code_id: authorizationCodeId.value,
hardware_fingerprint: addForm.hardware_fingerprint.trim()
}
if (addForm.remark.trim()) {
payload.device_info = { remark: addForm.remark.trim() }
}
const response = await createLicenseDevice(payload)
if (response.code === '000000') {
ElMessage.success(
response.message || t('pages.licenses.detail.licenseModal.messages.success')
)
addLicenseVisible.value = false
resetAddForm()
await fetchDevices()
} else {
throw new Error(response.message || t('pages.licenses.detail.licenseModal.messages.error'))
}
} catch (error: any) {
console.error('创建许可证失败:', error)
ElMessage.error(error.message || t('pages.licenses.detail.licenseModal.messages.error'))
} finally {
submitting.value = false
}
}
</script>
@@ -168,6 +366,68 @@ const handleAddLicense = () => {
width: 100%;
}
.license-add-dialog {
:deep(.el-dialog) {
border-radius: 12px;
padding-bottom: 12px;
}
:deep(.el-dialog__header) {
margin-right: 0;
border-bottom: 1px solid #f0f2f5;
padding: 16px 24px;
}
:deep(.el-dialog__title) {
font-family: 'Source Han Sans CN', sans-serif;
font-size: 18px;
font-weight: 600;
color: #1d1d1d;
}
:deep(.el-dialog__body) {
padding: 16px 24px 0;
}
:deep(.el-dialog__footer) {
padding: 0 24px 24px;
}
}
.dialog-description {
font-size: 14px;
color: #666;
line-height: 22px;
margin-bottom: 16px;
}
.license-form-dialog {
:deep(.el-form-item__label) {
font-weight: 500;
color: #1d1d1d;
}
}
.auth-code-row {
display: flex;
align-items: center;
gap: 12px;
:deep(.el-input__wrapper) {
background: rgba(247, 248, 250, 0.8);
}
}
.copy-btn {
padding: 0 8px;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
}
.action-bar {
margin-bottom: 16px;
+15 -5
View File
@@ -67,16 +67,16 @@
align="center"
>
<template #default="scope">
<div
<div
class="progress-container"
:class="{ 'zero-activated': (scope.row.activated_licenses_count || 0) === 0 }"
:class="{ 'zero-activated': getActivatedCount(scope.row) === 0 }"
>
<el-progress
:percentage="getActivationProgress(scope.row)"
:stroke-width="8"
:show-text="true"
:text-inside="true"
:format="() => `${scope.row.activated_licenses_count || 0}/${scope.row.max_activations}`"
:format="() => `${getActivatedCount(scope.row)}/${scope.row.max_activations}`"
/>
</div>
</template>
@@ -185,11 +185,21 @@ const getStatusClass = (status: string) => {
}
}
const getActivatedCount = (row: AuthorizationCode) => {
if (typeof row.current_activations === 'number') {
return row.current_activations
}
if (typeof row.activated_licenses_count === 'number') {
return row.activated_licenses_count
}
return 0
}
const getActivationProgress = (row: AuthorizationCode) => {
if (!row.max_activations || row.max_activations === 0) {
return 0
}
const activated = row.activated_licenses_count || 0
const activated = getActivatedCount(row)
return Math.round((activated / row.max_activations) * 100)
}
@@ -613,7 +623,7 @@ onMounted(async () => {
z-index: 10;
}
// 当 activated_licenses_count 为 0 时,文字显示为红色
// 当激活数量为 0 时,文字显示为红色
&.zero-activated {
:deep(.el-progress-bar__innerText),
:deep(.el-progress__text) {