feat: license文件导入支持

This commit is contained in:
caiyuchao
2025-08-08 14:50:34 +08:00
parent 9e776ec06b
commit 266bb61695
4 changed files with 170 additions and 160 deletions

View File

@@ -27,11 +27,13 @@ export namespace LicenseApi {
neCodeList: NeCode[]; // 操作
oldLicense: License;
hasHistory: boolean;
showUpload?: boolean; // 是否显示上传按钮
}
export interface NeCode {
id: number; // 主键
neList: number[]; // 网元开关
activationCode: string; // 激活码
fileUrl: string; // 文件地址
}
}
@@ -60,6 +62,11 @@ export function createLicense(data: LicenseApi.License) {
return requestClient.post('/license/license/create', data);
}
/** 修改License */
export function updateLicenseDetail(data: LicenseApi.NeCode) {
return requestClient.put('/license/license/update-detail', data);
}
/** 修改License */
export function updateLicense(data: LicenseApi.License) {
return requestClient.put('/license/license/update', data);

View File

@@ -1,14 +1,18 @@
<script lang="ts" setup>
import type { UploadChangeParam } from 'ant-design-vue';
import type { LicenseApi } from '#/api/license/license';
import { h } from 'vue';
import { downloadFileFromBlobPart } from '@vben/utils';
import { Button, message } from 'ant-design-vue';
import { Button, message, UploadDragger } from 'ant-design-vue';
import { updateLicenseDetail } from '#/api/license/license';
import { useDescription } from '#/components/description';
import { DictTagGroup } from '#/components/dict-tag';
import { useUpload } from '#/components/upload/use-upload';
import { $t } from '#/locales';
import { DICT_TYPE } from '#/utils';
@@ -18,6 +22,8 @@ const props = defineProps<{
formData?: LicenseApi.License;
}>();
const emit = defineEmits(['getDetailById']);
const columns = [
{
title: '网元',
@@ -101,136 +107,6 @@ const columns = [
title: 'License文件',
dataIndex: 'fileUrlListMap',
key: 'fileUrlListMap',
customRender: (data: any) => {
const getFile = (dataValue: any) => {
if (!dataValue) {
return;
}
if (!dataValue[0]) {
return;
}
const fileName = `${dataValue[0]?.slice(
Math.max(0, dataValue[0].lastIndexOf('/') + 1),
dataValue[0].lastIndexOf('_'),
)}.ini`;
// 创建下载链接
const link = h(
'span',
{
style: {
marginRight: '15px',
},
},
fileName,
);
// 创建下载按钮
const button = h(
Button,
{
onClick: async () => {
const res = await fetch(dataValue[0]);
if (!res.ok) {
message.error($t('license.downloadFailed'));
return;
}
const blob = await res.blob();
downloadFileFromBlobPart({ fileName, source: blob });
},
type: 'primary',
},
() => $t('license.download'),
);
const file = h(
'div',
{
style: {
display: 'flex',
alignItems: 'center',
},
},
[link, button],
);
let file1;
if (dataValue[1]) {
const fileName1 = `${dataValue[1]?.slice(
Math.max(0, dataValue[1].lastIndexOf('/') + 1),
dataValue[1].lastIndexOf('_'),
)}.ini`;
// 创建下载链接
const link1 = h(
'span',
{
style: {
marginRight: '15px',
},
},
fileName1,
);
// 创建下载按钮
const button1 = h(
Button,
{
onClick: async () => {
const res = await fetch(dataValue[1]);
if (!res.ok) {
message.error($t('license.downloadFailed'));
return;
}
const blob1 = await res.blob();
downloadFileFromBlobPart({
fileName: fileName1,
source: blob1,
});
},
type: 'primary',
},
() => $t('license.download'),
);
file1 = h(
'div',
{
style: {
display: 'flex',
alignItems: 'center',
margin: '8px 0 0 0',
},
},
[link1, button1],
);
}
return h(
'div',
{
style: {},
},
[file, file1],
);
};
// 包裹容器
// return getFile(data.value);
if (data.value.new) {
const after = h(
'span',
{
style: {
color: 'red',
},
},
[``, getFile(data.value.new)],
);
return h('div', {}, [getFile(data.value.old), after]);
}
return getFile(data.value.old);
},
},
];
@@ -242,6 +118,61 @@ const [Description] = useDescription({
},
schema: useDetailSchema(),
});
const handleDownload = async (item: string) => {
const res = await fetch(item);
if (!res.ok) {
message.error($t('license.downloadFailed'));
return;
}
const blob1 = await res.blob();
downloadFileFromBlobPart({
fileName: `${item?.slice(
Math.max(0, item.lastIndexOf('/') + 1),
item.lastIndexOf('_'),
)}.ini`,
source: blob1,
});
};
/** 上传前 */
function beforeUpload() {
return false;
}
const handleChange = (info: UploadChangeParam, record: any) => {
record.fileList = info.fileList;
};
const handleUpload = async (record: any) => {
try {
// 1. 上传,获取 URL
record.uploading = true;
for (const item of record.fileList) {
const { httpRequest } = useUpload();
// 将 Blob 转换为 File
const fileObj = new File([item.originFileObj], item.name, {
type: item.type,
});
const fileUrl = await httpRequest(fileObj);
await updateLicenseDetail({
id: record.id,
neList: [],
activationCode: '',
fileUrl,
});
}
emit('getDetailById', props.formData?.id || 0);
message.success('上传成功');
} catch {
message.error($t('上传失败'));
}
record.fileList = [];
record.uploading = false;
};
</script>
<template>
@@ -252,7 +183,73 @@ const [Description] = useDescription({
:data-source="props.formData?.neCodeList"
:columns="columns"
bordered
/>
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'fileUrlListMap'">
<template
v-if="
record.fileUrlListMap.old &&
record.fileUrlListMap.old.length > 0
"
>
<div
v-for="(item, index) in record.fileUrlListMap.old"
:key="index"
style="display: flex; align-items: center; margin-top: 8px"
>
<span style="margin-right: 15px">{{
`${item?.slice(
Math.max(0, item.lastIndexOf('/') + 1),
item.lastIndexOf('_'),
)}.ini`
}}</span>
<Button @click="handleDownload(item)" type="primary">
{{ $t('license.download') }}
</Button>
</div>
</template>
<template
v-else-if="
props.formData?.status === 3 && props.formData?.showUpload
"
>
<div
style="
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
"
>
<div class="w-full">
<UploadDragger
accept=".ini"
:max-count="2"
:file-list="record.fileList"
:before-upload="beforeUpload"
@change="handleChange($event, record)"
>
<p class="ant-upload-drag-icon">
<span class="icon-[ep--upload-filled] size-12"></span>
</p>
<p class="ant-upload-text">选择要导入的License文件</p>
</UploadDragger>
</div>
<Button
style="margin-top: 5px"
:disabled="!record.fileList || record.fileList.length === 0"
:loading="record.uploading"
@click="handleUpload(record)"
type="primary"
>
确定导入
</Button>
</div>
</template>
</template>
</template>
</a-table>
</div>
</div>
</template>

View File

@@ -1,5 +1,5 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { CustomerApi } from '#/api/license/customer';
import type { LicenseApi } from '#/api/license/license';
import type { ProjectApi } from '#/api/license/project';
@@ -7,7 +7,6 @@ import type { DescriptionItemSchema } from '#/components/description';
import { h, ref } from 'vue';
import { useAccess } from '@vben/access';
import { formatDate, formatDateTime } from '@vben/utils';
import dayjs, { Dayjs } from 'dayjs';
@@ -21,7 +20,6 @@ import { DictTag } from '#/components/dict-tag';
import { $t } from '#/locales';
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
const { hasAccessByCodes } = useAccess();
const customerList = ref<CustomerApi.Customer[]>([]);
const projectList = ref<ProjectApi.Project[]>([]);
export const formData = ref<LicenseApi.License>();
@@ -146,6 +144,26 @@ export function useFormSchema(): VbenFormSchema[] {
],
},
},
{
fieldName: 'approver',
label: $t('license.approver'),
component: 'ApiSelect',
rules: 'required',
help: $t('license.licenseAdminHelp'),
componentProps: {
allowClear: true,
api: async () => {
const data = await getLicenseAdminList();
return data.map((item) => ({
label: item.nickname,
value: item.id,
}));
},
showSearch: true,
filterOption: (input: string, option: any) =>
option.label.toLowerCase().includes(input.toLowerCase()),
},
},
{
fieldName: 'neCodeList',
label: $t('license.neList'),
@@ -181,26 +199,6 @@ export function useFormSchema(): VbenFormSchema[] {
component: 'Textarea',
formItemClass: 'col-span-2',
},
{
fieldName: 'approver',
label: $t('license.approver'),
component: 'ApiSelect',
rules: 'required',
help: $t('license.licenseAdminHelp'),
componentProps: {
allowClear: true,
api: async () => {
const data = await getLicenseAdminList();
return data.map((item) => ({
label: item.nickname,
value: item.id,
}));
},
showSearch: true,
filterOption: (input: string, option: any) =>
option.label.toLowerCase().includes(input.toLowerCase()),
},
},
];
}
@@ -299,9 +297,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
export function useGridColumns(
onActionClick?: OnActionClickFn<LicenseApi.License>,
): VxeTableGridOptions<LicenseApi.License>['columns'] {
export function useGridColumns(): VxeTableGridOptions<LicenseApi.License>['columns'] {
return [
{
field: 'customerName',

View File

@@ -5,6 +5,8 @@ import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { getLicense } from '#/api/license/license';
import Detail from '../components/detail.vue';
const formData = ref<LicenseApi.License>();
@@ -29,9 +31,17 @@ const [Modal, modalApi] = useVbenModal({
if (!data || !data.id) {
return;
}
formData.value = data;
// formData.value = data;
await getDetailById(data.id);
},
});
const getDetailById = async (id: number) => {
// 获取详情
const data = await getLicense(id);
formData.value = data;
formData.value.showUpload = true; // 显示上传按钮
};
</script>
<template>
@@ -41,6 +51,6 @@ const [Modal, modalApi] = useVbenModal({
:show-cancel-button="false"
:show-confirm-button="false"
>
<Detail :form-data="formData" />
<Detail :form-data="formData" @get-detail-by-id="getDetailById" />
</Modal>
</template>