feat: implement file configuration management API and UI components

pull/56/head
chenminjie 2024-12-03 19:08:22 +08:00
parent 4fbed026d1
commit f88e12fa88
4 changed files with 628 additions and 0 deletions

View File

@ -0,0 +1,63 @@
import { type PageParam, requestClient } from '#/api/request';
export namespace FileConfigApi {
export interface FileClientConfig {
basePath: string;
host?: string;
port?: number;
username?: string;
password?: string;
mode?: string;
endpoint?: string;
bucket?: string;
accessKey?: string;
accessSecret?: string;
domain: string;
}
export interface FileConfigVO {
id: number;
name: string;
storage?: number;
master: boolean;
visible: boolean;
config: FileClientConfig;
remark: string;
createTime: Date;
}
}
// 查询文件配置列表
export function getFileConfigPage(params: PageParam) {
return requestClient.get('/infra/file-config/page', { params });
}
// 查询文件配置详情
export function getFileConfig(id: number) {
return requestClient.get(`/infra/file-config/get?id=${id}`);
}
// 更新文件配置为主配置
export function updateFileConfigMaster(id: number) {
return requestClient.put(`/infra/file-config/update-master?id=${id}`);
}
// 新增文件配置
export function createFileConfig(data: FileConfigApi.FileConfigVO) {
return requestClient.post('/infra/file-config/create', data);
}
// 修改文件配置
export function updateFileConfig(data: FileConfigApi.FileConfigVO) {
return requestClient.put('/infra/file-config/update', data);
}
// 删除文件配置
export function deleteFileConfig(id: number) {
return requestClient.delete(`/infra/file-config/delete?id=${id}`);
}
// 测试文件配置
export function testFileConfig(id: number) {
return requestClient.get(`/infra/file-config/test?id=${id}`);
}

View File

@ -0,0 +1,64 @@
<script setup lang="ts">
import { ref } from 'vue';
import { useVbenForm, useVbenModal } from '@vben/common-ui';
import { isEmpty } from '@vben/utils';
import {
createFileConfig,
getFileConfig,
updateFileConfig,
} from '#/api/infra/file-config';
import { editFormSchema } from '../file-config.data';
defineOptions({ name: 'FileConfigEditForm' });
const isUpdate = ref(false);
// 使
const [Form, formApi] = useVbenForm({
schema: editFormSchema,
showDefaultActions: false,
handleSubmit: onSubmit,
});
// 使
const [Modal, modalApi] = useVbenModal({
onCancel: async () => {
modalApi.close();
await formApi.resetForm();
},
onConfirm: async () => {
await formApi.validateAndSubmitForm();
},
async onOpenChange(isOpen: boolean) {
if (isOpen) {
const { id } = modalApi.getData<Record<string, any>>();
isUpdate.value = !isEmpty(id);
if (id) {
const values = await getFileConfig(id);
formApi.setValues(values);
}
modalApi.setState({
title: `${isUpdate.value ? '编辑' : '新增'} 文件配置`,
});
}
},
});
async function onSubmit(values: Record<string, any>) {
await (isUpdate.value
? updateFileConfig(values as any)
: createFileConfig(values as any));
modalApi.close();
await formApi.resetForm();
}
</script>
<template>
<Modal>
<Form />
</Modal>
</template>

View File

@ -0,0 +1,276 @@
import type { VxeGridProps } from '#/adapter/vxe-table';
import type { FileConfigApi } from '#/api/infra/file-config';
import { h } from 'vue';
import { Tag } from 'ant-design-vue';
import { type VbenFormProps, z } from '#/adapter/form';
import { $t } from '#/locales';
import { DICT_TYPE } from '#/utils/dict';
/**
*
*/
export const formSchema: VbenFormProps['schema'] = [
{
fieldName: 'name',
label: '配置名称',
component: 'Input',
componentProps: {
placeholder: '请输入配置名称',
},
},
{
fieldName: 'storage',
label: '储存器',
component: 'ApiDict',
componentProps: {
code: DICT_TYPE.INFRA_FILE_STORAGE,
class: 'w-full',
placeholder: '请选择储存器',
},
},
{
fieldName: 'createTime',
label: '创建时间',
component: 'DatePicker',
},
];
/**
*
*/
export const tableColumns: VxeGridProps<FileConfigApi.FileConfigVO>['columns'] =
[
{
fixed: 'left',
type: 'checkbox',
width: 50,
},
{
fixed: 'left',
type: 'seq',
width: 50,
},
{ field: 'name', title: '配置名称' },
{
field: 'storage',
title: '储存器',
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.INFRA_FILE_STORAGE },
},
},
{ field: 'remark', title: '备注' },
{
field: 'master',
title: '主配置',
slots: {
default: ({ row }) => {
return h(
Tag,
{
color: row.master ? 'success' : 'default',
},
{ default: () => (row.master ? '是' : '否') },
);
},
},
},
{ field: 'createTime', title: '创建时间', formatter: 'formatDateTime' },
{
field: 'action',
fixed: 'right',
width: '180px',
slots: { default: 'action' },
title: $t('page.action.action'),
},
];
/**
*
*/
export const editFormSchema: VbenFormProps['schema'] = [
{
component: 'Input',
fieldName: 'id',
label: 'id',
dependencies: {
triggerFields: [''],
show: () => false,
},
},
{
fieldName: 'name',
label: '配置名称',
component: 'Input',
componentProps: {
placeholder: '请输入配置名称',
},
rules: z.string().min(1, '请输入配置名称'),
},
{
fieldName: 'remark',
label: '备注',
component: 'Input',
},
{
fieldName: 'storage',
label: '储存器',
component: 'ApiDict',
componentProps: {
code: DICT_TYPE.INFRA_FILE_STORAGE,
class: 'w-full',
placeholder: '请选择储存器',
numberToString: true,
},
rules: z.string().min(1, '请选择储存器').or(z.number()),
defaultValue: '1',
},
{
fieldName: 'config.basePath',
label: '基础路径',
component: 'Input',
componentProps: {
placeholder: '请输入基础路径',
},
rules: z.string().min(1, '请输入基础路径'),
dependencies: {
triggerFields: ['storage'],
if: (values: Record<string, any>) => {
return [10, 11, 12].includes(values.storage);
},
},
},
{
fieldName: 'config.host',
label: '主机地址',
component: 'Input',
componentProps: {
placeholder: '请输入主机地址',
},
rules: z.string().min(1, '请输入主机地址'),
dependencies: {
triggerFields: ['storage'],
if: (values: Record<string, any>) => [11, 12].includes(values.storage),
},
},
{
fieldName: 'config.port',
label: '主机端口',
component: 'Input',
componentProps: {
placeholder: '请输入主机端口',
},
rules: z.string().min(1, '请输入主机端口'),
dependencies: {
triggerFields: ['storage'],
if: (values: Record<string, any>) => [11, 12].includes(values.storage),
},
},
{
fieldName: 'config.username',
label: '用户名',
component: 'Input',
componentProps: {
placeholder: '请输入用户名',
},
rules: z.string().min(1, '请输入用户名'),
dependencies: {
triggerFields: ['storage'],
if: (values: Record<string, any>) => [11, 12].includes(values.storage),
},
},
{
fieldName: 'config.password',
label: '密码',
component: 'Input',
componentProps: {
placeholder: '请输入密码',
},
rules: z.string().min(1, '请输入密码'),
dependencies: {
triggerFields: ['storage'],
if: (values: Record<string, any>) => [11, 12].includes(values.storage),
},
},
{
fieldName: 'config.mode',
label: '连接模式',
component: 'RadioGroup',
componentProps: {
options: [
{ label: '主动模式', value: 'active' },
{ label: '被动模式', value: 'passive' },
],
class: 'w-full',
placeholder: '请选择连接模式',
},
rules: z.string().min(1, '请选择连接模式'),
dependencies: {
triggerFields: ['storage'],
if: (values: Record<string, any>) => values.storage === 11,
},
},
{
fieldName: 'config.endpoint',
label: '端点/节点地址',
component: 'Input',
componentProps: {
placeholder: '请输入端点/节点地址',
},
rules: z.string().min(1, '请输入端点/节点地址'),
dependencies: {
triggerFields: ['storage'],
if: (values: Record<string, any>) => values.storage === 20,
},
},
{
fieldName: 'config.bucket',
label: '桶名称',
component: 'Input',
componentProps: {
placeholder: '请输入桶名称',
},
rules: z.string().min(1, '请输入桶名称'),
dependencies: {
triggerFields: ['storage'],
if: (values: Record<string, any>) => values.storage === 20,
},
},
{
fieldName: 'config.accessKey',
label: 'accessKey',
component: 'Input',
componentProps: {
placeholder: '请输入访问密钥',
},
rules: z.string().min(1, '请输入访问密钥'),
dependencies: {
triggerFields: ['storage'],
if: (values: Record<string, any>) => values.storage === 20,
},
},
{
fieldName: 'config.accessSecret',
label: 'accessSecret',
component: 'Input',
componentProps: {
placeholder: '请输入访问密钥',
},
rules: z.string().min(1, '请输入访问密钥'),
dependencies: {
triggerFields: ['storage'],
if: (values: Record<string, any>) => values.storage === 20,
},
},
{
fieldName: 'config.domain',
label: '自定义域名',
component: 'Input',
componentProps: {
placeholder: '请输入自定义域名',
},
},
];

View File

@ -0,0 +1,225 @@
<script setup lang="ts">
import type { VbenFormProps } from '#/adapter/form';
import { defineAsyncComponent, h, reactive } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Image, message, Modal } from 'ant-design-vue';
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter/vxe-table';
import {
deleteFileConfig,
getFileConfigPage,
testFileConfig,
updateFileConfigMaster,
} from '#/api/infra/file-config';
import { ActionButtons } from '#/components/action-buttons';
import { formSchema, tableColumns } from './file-config.data';
defineOptions({
name: 'InfraFileConfig',
});
/**
* 表格查询表单配置
*/
const formOptions = reactive<any>({
//
collapsed: false,
schema: formSchema,
//
showCollapseButton: true,
submitButtonOptions: {
content: '查询',
},
//
submitOnEnter: false,
} as VbenFormProps);
/**
* 表格配置
*/
const gridOptions = reactive<any>({
columns: tableColumns,
height: 'auto',
checkboxConfig: {
reserve: true,
highlight: true,
// labelField: 'id',
},
rowConfig: {
keyField: 'id',
},
proxyConfig: {
ajax: {
query: async (e, params) => {
const data = await getFileConfigPage({
pageNo: e.page.currentPage,
pageSize: e.page.pageSize,
...params,
});
return data;
},
},
},
} as VxeGridProps);
/**
* 表格事件
*/
const gridEvents = reactive<any>({});
// 使
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents,
});
// 使
const [EditFormModal, editFormModalApi] = useVbenModal({
connectedComponent: defineAsyncComponent(
() => import('./components/edit-form.vue'),
),
onClosed: () => {
gridApi.reload();
},
});
/**
* 新增
*/
const handleCreate = () => {
editFormModalApi.open();
};
/**
* 编辑
* @param row 行数据
*/
const handleEdit = (row: Record<string, any>) => {
editFormModalApi.setData(row);
editFormModalApi.open();
};
/**
* 主配置
* @param row 行数据
*/
const handleMainConfig = async (row: Record<string, any>) => {
const res = await updateFileConfigMaster(row.id);
if (res) {
message.success('设置成功');
gridApi.reload();
}
};
/**
* 测试
* @param row 行数据
*/
const handleTest = async (row: Record<string, any>) => {
//
message.loading({
content: '测试文件上传中...',
key: 'test_file_upload',
duration: 0,
});
try {
const res = await testFileConfig(row.id);
if (res) {
message.success({ content: '测试文件上传成功', key: 'test_file_upload' });
//
Modal.confirm({
title: '测试文件上传成功',
content: '是否预览测试文件',
onOk: () => {
Modal.success({
title: '测试文件',
content: h('div', { style: { marginRight: '32px' } }, [
h(
'p',
{ style: { marginBottom: '16px' } },
`测试文件地址: ${res}`,
),
h(Image, { src: res, width: '100%' }),
]),
});
},
});
}
} catch {
message.error({ content: '测试文件上传失败', key: 'test_file_upload' });
}
};
/**
* 删除
* @param row 行数据
*/
const handleDelete = async (row: Record<string, any>) => {
try {
await deleteFileConfig(row.id);
message.success('删除成功');
gridApi.reload();
} catch {}
};
</script>
<template>
<Page auto-content-height>
<Grid>
<template #toolbar-actions>
<ActionButtons
:actions="[
{
type: 'primary',
icon: 'ant-design:plus-outlined',
label: '新增',
onClick: handleCreate,
},
]"
/>
</template>
<template #action="{ row }">
<ActionButtons
:actions="[
{
type: 'link',
label: '编辑',
onClick: () => handleEdit(row),
},
{
type: 'link',
label: '主配置',
disabled: row.master,
popConfirm: {
title: '确定要设置为主配置吗?',
confirm: () => handleMainConfig(row),
},
},
{
type: 'link',
label: '测试',
popConfirm: {
title: '确定要测试吗?',
confirm: () => handleTest(row),
},
},
{
type: 'link',
danger: true,
label: '删除',
popConfirm: {
title: '确定要删除吗?',
confirm: () => handleDelete(row),
},
},
]"
/>
</template>
</Grid>
<EditFormModal />
</Page>
</template>