feat: License生成和下载

This commit is contained in:
caiyuchao
2025-07-18 17:58:47 +08:00
parent 47df877799
commit 16130c4529
7 changed files with 333 additions and 11 deletions

View File

@@ -16,6 +16,7 @@ export namespace LicenseApi {
userNumber: number; // 用户数
ranNumber: number; // 基站数
activationCode: string; // 激活码
fileUrl: string; // 激活码
licenseContent: string; // License内容
applicant: number; // 申请人
applicationTime: Dayjs | string; // 申请时间
@@ -54,6 +55,11 @@ export function applyLicense(data: LicenseApi.License) {
return requestClient.put('/license/license/apply', data);
}
/** 生成License */
export function generateLicense(id: number) {
return requestClient.get(`/license/license/generate?id=${id}`);
}
/** 删除License */
export function deleteLicense(id: number) {
return requestClient.delete(`/license/license/delete?id=${id}`);

View File

@@ -18,5 +18,13 @@
"licenseAdminHelp": "Assigned to persons who have permission to generate licenses and send email reminders",
"apply": "Apply",
"applyAction": "Apply For {0}",
"applicationTime": "Application Time"
"applicationTime": "Application Time",
"generate": "Generate",
"generating": "Generating...",
"generateSuccess": "Generation Successful",
"isDownload": "Do you want to download this file [{0}]?",
"download": "Download",
"downloadFailed": "Download failed, please try again later",
"licenseFile": "License File",
"applySuccess": "Application successful, email reminder sent, please wait for approval"
}

View File

@@ -18,5 +18,13 @@
"licenseAdminHelp": "指派给有权限生成License的人员并且发送邮件提醒",
"apply": "申请",
"applyAction": "申请{0}",
"applicationTime": "申请时间"
"applicationTime": "申请时间",
"generate": "生成",
"generating": "正在生成中...",
"generateSuccess": "生成成功",
"isDownload": "是否下载该文件【{0}】?",
"download": "下载",
"downloadFailed": "下载失败,请稍后重试",
"licenseFile": "License文件",
"applySuccess": "申请成功,已发送邮件提醒,请等待审核"
}

View File

@@ -1,19 +1,26 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { LicenseApi } from '#/api/license/license';
import type { DescriptionItemSchema } from '#/components/description';
import { ref } from 'vue';
import { h, ref } from 'vue';
import { useAccess } from '@vben/access';
import { downloadFileFromBlobPart, formatDateTime } from '@vben/utils';
import { Button, message } from 'ant-design-vue';
import { z } from '#/adapter/form';
import { getCustomerList } from '#/api/license/customer';
import { isLicenseSnUnique } from '#/api/license/license';
import { getProjectList } from '#/api/license/project';
import { getLicenseAdminList, getSimpleUserList } from '#/api/system/user';
import { DictTag, DictTagGroup } from '#/components/dict-tag';
import { $t } from '#/locales';
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
// import Button from '../../../../../../packages/@core/ui-kit/shadcn-ui/src/components/button/button.vue';
const { hasAccessByCodes } = useAccess();
let projectList = await getProjectList({});
const customerList = await getCustomerList();
@@ -474,7 +481,29 @@ export function useGridColumns(
{
code: 'apply',
text: $t('license.apply'),
show: hasAccessByCodes(['license:license:apply']),
show: (values: LicenseApi.License) => {
return (
hasAccessByCodes(['license:license:apply']) &&
values.status === 0
);
},
},
{
code: 'generate',
text: $t('license.generate'),
show: (values: LicenseApi.License) => {
return (
hasAccessByCodes(['license:license:generate']) &&
values.status === 1
);
},
},
{
code: 'download',
text: $t('license.download'),
show: (values: LicenseApi.License) => {
return values.status === 2;
},
},
{
code: 'delete',
@@ -485,3 +514,123 @@ export function useGridColumns(
},
];
}
/** 详情页的字段 */
export function useDetailSchema(): DescriptionItemSchema[] {
return [
{
field: 'customerName',
label: $t('license.customer'),
},
{
field: 'projectName',
label: $t('license.project'),
},
{
field: 'serialNo',
label: 'SN',
},
{
field: 'expiryDate',
label: $t('license.expiryDate'),
content: (data) => {
return formatDateTime(data?.expiryDate) as string;
},
},
{
field: 'neList',
label: $t('license.neList'),
content: (data) => {
return h(DictTagGroup, {
type: DICT_TYPE.LIC_NE_LIST,
value: data.neList,
});
},
},
{
field: 'userNumber',
label: $t('license.userNumber'),
},
{
field: 'ranNumber',
label: $t('license.ranNumber'),
},
{
field: 'applicantName',
label: $t('license.applicant'),
},
{
field: 'applicationTime',
label: $t('license.applicationTime'),
content: (data) => {
return formatDateTime(data?.applicationTime) as string;
},
},
{
field: 'status',
label: $t('license.status'),
content: (data) => {
return h(DictTag, {
type: DICT_TYPE.LIC_LICENSE_STATUS,
value: data.status,
});
},
},
{
field: 'remark',
label: $t('license.remark'),
},
{
field: 'fileUrl',
label: $t('license.licenseFile'),
hidden: (data) => data.status !== 2,
content: (data) => {
const fileName = `${data.fileUrl?.slice(
Math.max(0, data.fileUrl.lastIndexOf('/') + 1),
data.fileUrl.lastIndexOf('_'),
)}.ini`;
// 创建下载链接
const link = h(
'span',
{
style: {
marginRight: '15px',
},
},
fileName,
);
// 创建下载按钮
const button = h(
Button,
{
onClick: async () => {
const res = await fetch(data.fileUrl);
if (!res.ok) {
message.error($t('license.downloadFailed'));
return;
}
const blob = await res.blob();
downloadFileFromBlobPart({ fileName, source: blob });
},
type: 'primary',
},
$t('license.download'),
);
// 包裹容器
return h(
'div',
{
style: {
display: 'flex',
alignItems: 'center',
},
},
[link, button],
);
},
},
];
}

View File

@@ -1,3 +1,118 @@
<script lang="ts" setup>
import type { LicenseApi } from '#/api/license/license';
import { ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useAccess } from '@vben/access';
import { Page } from '@vben/common-ui';
import { useTabs } from '@vben/hooks';
import { Button, message } from 'ant-design-vue';
import { generateLicense, getLicense } from '#/api/license/license';
import { useDescription } from '#/components/description';
import { $t } from '#/locales';
import { useDetailSchema } from '../data';
const { hasAccessByCodes } = useAccess();
const route = useRoute();
const router = useRouter();
const loading = ref(false);
const formData = ref<LicenseApi.License>();
const [Description] = useDescription({
componentProps: {
bordered: true,
column: 1,
class: 'mx-4',
},
schema: useDetailSchema(),
});
/** 获取详情数据 */
async function getDetail(id: any) {
if (!id) {
return;
}
loading.value = true;
try {
formData.value = await getLicense(id);
} finally {
loading.value = false;
}
}
const tabs = useTabs();
/** 返回列表 */
function close() {
tabs.closeCurrentTab();
router.push('/license');
}
/** 生成文件 */
async function onGenerate() {
if (!formData.value?.id) {
return;
}
const hideLoading = message.loading({
content: $t('license.generating'),
duration: 0,
key: 'action_process_msg',
});
try {
await generateLicense(formData.value.id);
hideLoading();
await getDetail(formData.value.id);
// 确认是否下载该文件
// confirm({
// title: $t('license.generateSuccess'),
// content: $t('license.isDownload', [fileName]),
// confirmText: $t('license.download'),
// cancelText: $t('common.cancel'),
// }).then(async () => {
// const res = await fetch(response);
// if (!res.ok) {
// message.error($t('license.downloadFailed'));
// return;
// }
// const blob = await res.blob();
// downloadFileFromBlobPart({ fileName, source: blob });
// });
message.success($t('license.generateSuccess'));
} finally {
hideLoading();
}
}
// 初始化
getDetail(route.query.id);
</script>
<template>
<div>测试</div>
<Page auto-content-height v-loading="loading">
<div class="bg-card flex h-[100%] flex-col rounded-md p-4">
<div class="flex-1 overflow-auto py-4">
<Description :data="formData" :label-style="{ width: '250px' }" />
</div>
<div class="mt-4 flex justify-center space-x-2">
<Button @click="close"> {{ $t('common.back') }}</Button>
<Button
v-if="
hasAccessByCodes(['license:license:generate']) &&
formData?.status === 1
"
type="primary"
:loading="loading"
@click="onGenerate"
>
{{ $t('license.generate') }}
</Button>
</div>
</div>
</Page>
</template>

View File

@@ -5,6 +5,8 @@ import type {
} from '#/adapter/vxe-table';
import type { LicenseApi } from '#/api/license/license';
import { useRouter } from 'vue-router';
import { Page, useVbenModal } from '@vben/common-ui';
import { downloadFileFromBlobPart } from '@vben/utils';
@@ -21,6 +23,8 @@ import { $t } from '#/locales';
import { useGridColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
const router = useRouter();
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Form,
destroyOnClose: true,
@@ -41,6 +45,27 @@ function onApply(row: LicenseApi.License) {
formModalApi.setData({ ...row, action: 1 }).open();
}
/** 生成License */
function onGenerate(row: LicenseApi.License) {
router.push({ name: 'LicenseGenerate', query: { id: row.id } });
}
/** 下载License */
async function onDownload(row: LicenseApi.License) {
const fileName = `${row.fileUrl?.slice(
Math.max(0, row.fileUrl.lastIndexOf('/') + 1),
row.fileUrl.lastIndexOf('_'),
)}.ini`;
const res = await fetch(row.fileUrl);
if (!res.ok) {
message.error($t('license.downloadFailed'));
return;
}
const blob = await res.blob();
downloadFileFromBlobPart({ fileName, source: blob });
}
/** 编辑License */
function onEdit(row: LicenseApi.License) {
formModalApi.setData(row).open();
@@ -49,14 +74,14 @@ function onEdit(row: LicenseApi.License) {
/** 删除License */
async function onDelete(row: LicenseApi.License) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.sn]),
content: $t('ui.actionMessage.deleting', [row.serialNo]),
duration: 0,
key: 'action_process_msg',
});
try {
await deleteLicense(row.id as number);
hideLoading();
message.success($t('ui.actionMessage.deleteSuccess', [row.sn]));
message.success($t('ui.actionMessage.deleteSuccess', [row.serialNo]));
onRefresh();
} catch {
hideLoading();
@@ -80,10 +105,18 @@ function onActionClick({ code, row }: OnActionClickParams<LicenseApi.License>) {
onDelete(row);
break;
}
case 'download': {
onDownload(row);
break;
}
case 'edit': {
onEdit(row);
break;
}
case 'generate': {
onGenerate(row);
break;
}
}
}

View File

@@ -87,18 +87,21 @@ const [Modal, modalApi] = useVbenModal({
// 提交表单
const data = (await formApi.getValues()) as LicenseApi.License;
data.neList = state.checkedList;
const action = formData.value?.action || 0;
try {
if (formData.value?.id) {
await (formData.value?.action === 1
? applyLicense(data)
: updateLicense(data));
await (action === 1 ? applyLicense(data) : updateLicense(data));
} else {
await createLicense(data);
}
// 关闭并提示
await modalApi.close();
emit('success');
message.success($t('ui.actionMessage.operationSuccess'));
if (action === 1) {
message.success($t('license.applySuccess'), 3);
} else {
message.success($t('ui.actionMessage.operationSuccess'));
}
} finally {
modalApi.unlock();
}