Merge remote-tracking branch 'yudao/dev' into dev-new

# Conflicts:
#	apps/web-antd/src/views/infra/demo/demo01/index.vue
#	apps/web-antd/src/views/infra/demo/general/demo01/index.vue
#	apps/web-antd/src/views/infra/demo/general/demo03/erp/index.vue
#	apps/web-antd/src/views/infra/demo/general/demo03/erp/modules/demo03-course-list.vue
#	apps/web-antd/src/views/infra/demo/general/demo03/erp/modules/demo03-grade-list.vue
#	apps/web-antd/src/views/infra/demo/general/demo03/inner/index.vue
#	apps/web-antd/src/views/infra/demo/general/demo03/normal/index.vue
pull/112/head
puhui999 2025-05-20 17:56:53 +08:00
commit 495aa10695
41 changed files with 866 additions and 1069 deletions

View File

@ -15,6 +15,7 @@ export namespace InfraJobApi {
retryInterval: number;
monitorTimeout: number;
createTime?: Date;
nextTimes?: Date[];
}
}

View File

@ -9,4 +9,5 @@ export const ACTION_ICON = {
FILTER: 'lucide:filter',
MORE: 'lucide:ellipsis-vertical',
VIEW: 'lucide:eye',
COPY: 'lucide:copy',
};

View File

@ -59,14 +59,16 @@ function onEdit(row: CrmCustomerApi.Customer) {
async function onDelete(row: CrmCustomerApi.Customer) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.name]),
duration: 0,
key: 'action_process_msg',
key: 'action_key_msg',
});
try {
await deleteCustomer(row.id as number);
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
message.success({
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
key: 'action_key_msg',
});
onRefresh();
} catch {
} finally {
hideLoading();
}
}

View File

@ -1,13 +1,8 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { InfraApiAccessLogApi } from '#/api/infra/api-access-log';
import { useAccess } from '@vben/access';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 列表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] {
return [
@ -70,9 +65,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
export function useGridColumns<T = InfraApiAccessLogApi.ApiAccessLog>(
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
export function useGridColumns(): VxeTableGridOptions['columns'] {
return [
{
field: 'id',
@ -148,26 +141,10 @@ export function useGridColumns<T = InfraApiAccessLogApi.ApiAccessLog>(
},
},
{
field: 'operation',
title: '操作',
minWidth: 80,
align: 'center',
width: 80,
fixed: 'right',
cellRender: {
attrs: {
nameField: 'id',
nameTitle: 'API访问日志',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'detail',
text: '详情',
show: hasAccessByCodes(['infra:api-access-log:query']),
},
],
},
slots: { default: 'actions' },
},
];
}

View File

@ -1,17 +1,11 @@
<script lang="ts" setup>
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { InfraApiAccessLogApi } from '#/api/infra/api-access-log';
import { Page, useVbenModal } from '@vben/common-ui';
import { Download } from '@vben/icons';
import { downloadFileFromBlobPart } from '@vben/utils';
import { Button } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
exportApiAccessLog,
getApiAccessLogPage,
@ -33,35 +27,22 @@ function onRefresh() {
}
/** 导出表格 */
async function onExport() {
async function handleExport() {
const data = await exportApiAccessLog(await gridApi.formApi.getValues());
downloadFileFromBlobPart({ fileName: 'API 访问日志.xls', source: data });
}
/** 查看 API 访问日志详情 */
function onDetail(row: InfraApiAccessLogApi.ApiAccessLog) {
function handleDetail(row: InfraApiAccessLogApi.ApiAccessLog) {
detailModalApi.setData(row).open();
}
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<InfraApiAccessLogApi.ApiAccessLog>) {
switch (code) {
case 'detail': {
onDetail(row);
break;
}
}
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(onActionClick),
columns: useGridColumns(),
height: 'auto',
keepSource: true,
proxyConfig: {
@ -95,15 +76,30 @@ const [Grid, gridApi] = useVbenVxeGrid({
<DetailModal @success="onRefresh" />
<Grid table-title="API 访">
<template #toolbar-tools>
<Button
type="primary"
class="ml-2"
@click="onExport"
v-access:code="['infra:api-access-log:export']"
>
<Download class="size-5" />
{{ $t('ui.actionTitle.export') }}
</Button>
<TableAction
:actions="[
{
label: $t('ui.actionTitle.export'),
type: 'primary',
icon: ACTION_ICON.DOWNLOAD,
auth: ['infra:api-access-log:export'],
onClick: handleExport,
},
]"
/>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.detail'),
type: 'link',
icon: ACTION_ICON.VIEW,
auth: ['infra:api-access-log:query'],
onClick: handleDetail.bind(null, row),
},
]"
/>
</template>
</Grid>
</Page>

View File

@ -1,8 +1,5 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { InfraApiErrorLogApi } from '#/api/infra/api-error-log';
import { useAccess } from '@vben/access';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import {
DICT_TYPE,
@ -11,8 +8,6 @@ import {
InfraApiErrorLogProcessStatusEnum,
} from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 列表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] {
return [
@ -71,9 +66,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
export function useGridColumns<T = InfraApiErrorLogApi.ApiErrorLog>(
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
export function useGridColumns(): VxeTableGridOptions['columns'] {
return [
{
field: 'id',
@ -130,46 +123,10 @@ export function useGridColumns<T = InfraApiErrorLogApi.ApiErrorLog>(
},
},
{
field: 'operation',
title: '操作',
minWidth: 200,
align: 'center',
width: 200,
fixed: 'right',
cellRender: {
attrs: {
nameField: 'id',
nameTitle: 'API错误日志',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'detail',
text: '详情',
show: hasAccessByCodes(['infra:api-error-log:query']),
},
{
code: 'done',
text: '已处理',
show: (row: InfraApiErrorLogApi.ApiErrorLog) => {
return (
row.processStatus === InfraApiErrorLogProcessStatusEnum.INIT &&
hasAccessByCodes(['infra:api-error-log:update-status'])
);
},
},
{
code: 'ignore',
text: '已忽略',
show: (row: InfraApiErrorLogApi.ApiErrorLog) => {
return (
row.processStatus === InfraApiErrorLogProcessStatusEnum.INIT &&
hasAccessByCodes(['infra:api-error-log:update-status'])
);
},
},
],
},
slots: { default: 'actions' },
},
];
}

View File

@ -1,17 +1,13 @@
<script lang="ts" setup>
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { InfraApiErrorLogApi } from '#/api/infra/api-error-log';
import { confirm, Page, useVbenModal } from '@vben/common-ui';
import { Download } from '@vben/icons';
import { downloadFileFromBlobPart } from '@vben/utils';
import { Button, message } from 'ant-design-vue';
import { message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
exportApiErrorLog,
getApiErrorLogPage,
@ -35,18 +31,18 @@ function onRefresh() {
}
/** 导出表格 */
async function onExport() {
async function handleExport() {
const data = await exportApiErrorLog(await gridApi.formApi.getValues());
downloadFileFromBlobPart({ fileName: 'API 错误日志.xls', source: data });
}
/** 查看 API 错误日志详情 */
function onDetail(row: InfraApiErrorLogApi.ApiErrorLog) {
function handleDetail(row: InfraApiErrorLogApi.ApiErrorLog) {
detailModalApi.setData(row).open();
}
/** 处理已处理 / 已忽略的操作 */
async function onProcess(id: number, processStatus: number) {
async function handleProcess(id: number, processStatus: number) {
confirm({
content: `确认标记为${InfraApiErrorLogProcessStatusEnum.DONE ? '已处理' : '已忽略'}?`,
}).then(async () => {
@ -57,33 +53,12 @@ async function onProcess(id: number, processStatus: number) {
});
}
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<InfraApiErrorLogApi.ApiErrorLog>) {
switch (code) {
case 'detail': {
onDetail(row);
break;
}
case 'done': {
onProcess(row.id, InfraApiErrorLogProcessStatusEnum.DONE);
break;
}
case 'ignore': {
onProcess(row.id, InfraApiErrorLogProcessStatusEnum.IGNORE);
break;
}
}
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(onActionClick),
columns: useGridColumns(),
height: 'auto',
keepSource: true,
proxyConfig: {
@ -117,15 +92,54 @@ const [Grid, gridApi] = useVbenVxeGrid({
<DetailModal @success="onRefresh" />
<Grid table-title="API ">
<template #toolbar-tools>
<Button
type="primary"
class="ml-2"
@click="onExport"
v-access:code="['infra:api-error-log:export']"
>
<Download class="size-5" />
{{ $t('ui.actionTitle.export') }}
</Button>
<TableAction
:actions="[
{
label: $t('ui.actionTitle.export'),
type: 'primary',
icon: ACTION_ICON.DOWNLOAD,
auth: ['infra:api-error-log:export'],
onClick: handleExport,
},
]"
/>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.detail'),
type: 'link',
icon: ACTION_ICON.VIEW,
auth: ['infra:api-error-log:query'],
onClick: handleDetail.bind(null, row),
},
{
label: '已处理',
type: 'link',
auth: ['infra:api-error-log:update-status'],
ifShow:
row.processStatus === InfraApiErrorLogProcessStatusEnum.INIT,
onClick: handleProcess.bind(
null,
row.id,
InfraApiErrorLogProcessStatusEnum.DONE,
),
},
{
label: '已忽略',
type: 'link',
auth: ['infra:api-error-log:update-status'],
ifShow:
row.processStatus === InfraApiErrorLogProcessStatusEnum.INIT,
onClick: handleProcess.bind(
null,
row.id,
InfraApiErrorLogProcessStatusEnum.IGNORE,
),
},
]"
/>
</template>
</Grid>
</Page>

View File

@ -60,34 +60,35 @@ const formData = ref(''); // 表单数据
useFormCreateDesigner(designer); //
/** 打开弹窗 */
const openModel = (title: string) => {
function openModel(title: string) {
dialogVisible.value = true;
dialogTitle.value = title;
modalApi.open();
};
}
/** 生成 JSON */
const showJson = () => {
function showJson() {
openModel('生成 JSON');
formType.value = 0;
formData.value = designer.value.getRule();
};
}
/** 生成 Options */
const showOption = () => {
function showOption() {
openModel('生成 Options');
formType.value = 1;
formData.value = designer.value.getOption();
};
}
/** 生成组件 */
const showTemplate = () => {
function showTemplate() {
openModel('生成组件');
formType.value = 2;
formData.value = makeTemplate();
};
}
const makeTemplate = () => {
/** 生成组件 */
function makeTemplate() {
const rule = designer.value.getRule();
const opt = designer.value.getOption();
return `<template>
@ -111,10 +112,10 @@ const makeTemplate = () => {
}
init()
<\/script>`;
};
}
/** 复制 */
const copy = async (text: string) => {
async function copy(text: string) {
const textToCopy = JSON.stringify(text, null, 2);
const { copy, copied, isSupported } = useClipboard({ source: textToCopy });
if (isSupported) {
@ -125,12 +126,10 @@ const copy = async (text: string) => {
} else {
message.error('复制失败');
}
};
}
/**
* 代码高亮
*/
const highlightedCode = (code: string) => {
/** 代码高亮 */
function highlightedCode(code: string) {
//
let language = 'json';
if (formType.value === 2) {
@ -143,7 +142,7 @@ const highlightedCode = (code: string) => {
//
const result = hljs.highlight(code, { language, ignoreIllegals: true });
return result.value || '&nbsp;';
};
}
/** 初始化 */
onMounted(async () => {

View File

@ -1,13 +1,12 @@
import type { Recordable } from '@vben/types';
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { InfraCodegenApi } from '#/api/infra/codegen';
import type { SystemMenuApi } from '#/api/system/menu';
import { h } from 'vue';
import { useAccess } from '@vben/access';
import { IconifyIcon } from '@vben/icons';
import { handleTree } from '@vben/utils';
@ -16,8 +15,6 @@ import { getMenuList } from '#/api/system/menu';
import { $t } from '#/locales';
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 导入数据库表的表单 */
export function useImportTableFormSchema(): VbenFormSchema[] {
return [
@ -393,8 +390,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
export function useGridColumns<T = InfraCodegenApi.CodegenTable>(
onActionClick: OnActionClickFn<T>,
export function useGridColumns(
getDataSourceConfigName?: (dataSourceConfigId: number) => string | undefined,
): VxeTableGridOptions['columns'] {
return [
@ -432,44 +428,10 @@ export function useGridColumns<T = InfraCodegenApi.CodegenTable>(
formatter: 'formatDateTime',
},
{
field: 'operation',
title: '操作',
width: 300,
width: 280,
fixed: 'right',
align: 'center',
cellRender: {
attrs: {
nameField: 'tableName',
nameTitle: '代码生成',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'preview',
text: '预览',
show: hasAccessByCodes(['infra:codegen:preview']),
},
{
code: 'edit',
show: hasAccessByCodes(['infra:codegen:update']),
},
{
code: 'delete',
show: hasAccessByCodes(['infra:codegen:delete']),
},
{
code: 'sync',
text: '同步',
show: hasAccessByCodes(['infra:codegen:update']),
},
{
code: 'generate',
text: '生成代码',
show: hasAccessByCodes(['infra:codegen:download']),
},
],
},
slots: { default: 'actions' },
},
];
}

View File

@ -31,7 +31,7 @@ const columnInfoRef = ref<InstanceType<typeof ColumnInfo>>();
const generateInfoRef = ref<InstanceType<typeof GenerationInfo>>();
/** 获取详情数据 */
const getDetail = async () => {
async function getDetail() {
const id = route.query.id as any;
if (!id) {
return;
@ -42,10 +42,10 @@ const getDetail = async () => {
} finally {
loading.value = false;
}
};
}
/** 提交表单 */
const submitForm = async () => {
async function submitForm() {
//
const basicInfoValid = await basicInfoRef.value?.validate();
if (!basicInfoValid) {
@ -61,7 +61,6 @@ const submitForm = async () => {
//
const hideLoading = message.loading({
content: $t('ui.actionMessage.updating'),
duration: 0,
key: 'action_process_msg',
});
try {
@ -74,32 +73,35 @@ const submitForm = async () => {
columns,
});
//
message.success($t('ui.actionMessage.operationSuccess'));
message.success({
content: $t('ui.actionMessage.operationSuccess'),
key: 'action_key_msg',
});
close();
} catch (error) {
console.error('保存失败', error);
} finally {
hideLoading();
}
};
}
const tabs = useTabs();
/** 返回列表 */
const close = () => {
function close() {
tabs.closeCurrentTab();
router.push('/infra/codegen');
};
}
/** 下一步 */
const nextStep = async () => {
function nextStep() {
currentStep.value += 1;
};
}
/** 上一步 */
const prevStep = () => {
function prevStep() {
if (currentStep.value > 0) {
currentStep.value -= 1;
}
};
}
/** 步骤配置 */
const steps = [

View File

@ -1,8 +1,5 @@
<script lang="ts" setup>
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { InfraCodegenApi } from '#/api/infra/codegen';
import type { InfraDataSourceConfigApi } from '#/api/infra/data-source-config';
@ -10,11 +7,10 @@ import { ref } from 'vue';
import { useRouter } from 'vue-router';
import { Page, useVbenModal } from '@vben/common-ui';
import { Plus } from '@vben/icons';
import { Button, message } from 'ant-design-vue';
import { message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteCodegenTable,
downloadCodegen,
@ -57,22 +53,22 @@ function onRefresh() {
}
/** 导入表格 */
function onImport() {
function handleImport() {
importModalApi.open();
}
/** 预览代码 */
function onPreview(row: InfraCodegenApi.CodegenTable) {
function handlePreview(row: InfraCodegenApi.CodegenTable) {
previewModalApi.setData(row).open();
}
/** 编辑表格 */
function onEdit(row: InfraCodegenApi.CodegenTable) {
function handleEdit(row: InfraCodegenApi.CodegenTable) {
router.push({ name: 'InfraCodegenEdit', query: { id: row.id } });
}
/** 删除代码生成配置 */
async function onDelete(row: InfraCodegenApi.CodegenTable) {
async function handleDelete(row: InfraCodegenApi.CodegenTable) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.tableName]),
duration: 0,
@ -88,15 +84,17 @@ async function onDelete(row: InfraCodegenApi.CodegenTable) {
}
/** 同步数据库 */
async function onSync(row: InfraCodegenApi.CodegenTable) {
async function handleSync(row: InfraCodegenApi.CodegenTable) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.updating', [row.tableName]),
duration: 0,
key: 'action_process_msg',
key: 'action_key_msg',
});
try {
await syncCodegenFromDB(row.id);
message.success($t('ui.actionMessage.updateSuccess', [row.tableName]));
message.success({
content: $t('ui.actionMessage.updateSuccess', [row.tableName]),
key: 'action_key_msg',
});
onRefresh();
} finally {
hideLoading();
@ -104,11 +102,10 @@ async function onSync(row: InfraCodegenApi.CodegenTable) {
}
/** 生成代码 */
async function onGenerate(row: InfraCodegenApi.CodegenTable) {
async function handleGenerate(row: InfraCodegenApi.CodegenTable) {
const hideLoading = message.loading({
content: '正在生成代码...',
duration: 0,
key: 'action_process_msg',
key: 'action_key_msg',
});
try {
const res = await downloadCodegen(row.id);
@ -119,47 +116,21 @@ async function onGenerate(row: InfraCodegenApi.CodegenTable) {
link.download = `codegen-${row.className}.zip`;
link.click();
window.URL.revokeObjectURL(url);
message.success('代码生成成功');
message.success({
content: '代码生成成功',
key: 'action_key_msg',
});
} finally {
hideLoading();
}
}
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<InfraCodegenApi.CodegenTable>) {
switch (code) {
case 'delete': {
onDelete(row);
break;
}
case 'edit': {
onEdit(row);
break;
}
case 'generate': {
onGenerate(row);
break;
}
case 'preview': {
onPreview(row);
break;
}
case 'sync': {
onSync(row);
break;
}
}
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(onActionClick, getDataSourceConfigName),
columns: useGridColumns(getDataSourceConfigName),
height: 'auto',
keepSource: true,
proxyConfig: {
@ -217,14 +188,61 @@ initDataSourceConfig();
<PreviewModal />
<Grid table-title="">
<template #toolbar-tools>
<Button
type="primary"
@click="onImport"
v-access:code="['infra:codegen:create']"
>
<Plus class="size-5" />
导入
</Button>
<TableAction
:actions="[
{
label: $t('ui.actionTitle.import'),
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['infra:codegen:create'],
onClick: handleImport,
},
]"
/>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: '预览',
type: 'link',
icon: ACTION_ICON.VIEW,
auth: ['infra:codegen:preview'],
onClick: handlePreview.bind(null, row),
},
{
label: '生成代码',
type: 'link',
icon: ACTION_ICON.DOWNLOAD,
auth: ['infra:codegen:download'],
onClick: handleGenerate.bind(null, row),
},
]"
:drop-down-actions="[
{
label: $t('common.edit'),
type: 'link',
auth: ['infra:codegen:update'],
onClick: handleEdit.bind(null, row),
},
{
label: '同步',
type: 'link',
auth: ['infra:codegen:update'],
onClick: handleSync.bind(null, row),
},
{
label: $t('common.delete'),
type: 'link',
danger: true,
auth: ['infra:codegen:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
</Page>

View File

@ -96,15 +96,17 @@ const [Modal, modalApi] = useVbenModal({
// 2.
const hideLoading = message.loading({
content: '导入中...',
duration: 0,
key: 'import_loading',
key: 'action_key_msg',
});
try {
await createCodegenList(formData);
//
await modalApi.close();
emit('success');
message.success($t('ui.actionMessage.operationSuccess'));
message.success({
content: $t('ui.actionMessage.operationSuccess'),
key: 'action_key_msg',
});
} finally {
hideLoading();
modalApi.unlock();

View File

@ -44,7 +44,7 @@ const activeKey = ref<string>('');
/** 代码地图 */
const codeMap = ref<Map<string, string>>(new Map<string, string>());
const setCodeMap = (key: string, lang: string, code: string) => {
function setCodeMap(key: string, lang: string, code: string) {
// Java
const trimmedCode = code.trimStart();
//
@ -59,8 +59,10 @@ const setCodeMap = (key: string, lang: string, code: string) => {
} catch {
codeMap.value.set(key, trimmedCode);
}
};
const removeCodeMapKey = (targetKey: any) => {
}
/** 删除代码地图 */
function removeCodeMapKey(targetKey: any) {
//
if (codeMap.value.size === 1) {
return;
@ -68,10 +70,10 @@ const removeCodeMapKey = (targetKey: any) => {
if (codeMap.value.has(targetKey)) {
codeMap.value.delete(targetKey);
}
};
}
/** 复制代码 */
const copyCode = async () => {
async function copyCode() {
const { copy } = useClipboard();
const file = previewFiles.value.find(
(item) => item.filePath === activeKey.value,
@ -80,10 +82,10 @@ const copyCode = async () => {
await copy(file.code);
message.success('复制成功');
}
};
}
/** 文件节点点击事件 */
const handleNodeClick = (_: any[], e: any) => {
function handleNodeClick(_: any[], e: any) {
if (!e.node.isLeaf) return;
activeKey.value = e.node.key;
@ -100,10 +102,10 @@ const handleNodeClick = (_: any[], e: any) => {
const lang = file.filePath.split('.').pop() || '';
setCodeMap(activeKey.value, lang, file.code);
};
}
/** 处理文件树 */
const handleFiles = (data: InfraCodegenApi.CodegenPreview[]): FileNode[] => {
function handleFiles(data: InfraCodegenApi.CodegenPreview[]): FileNode[] {
const exists: Record<string, boolean> = {};
const files: FileNode[] = [];
@ -176,17 +178,17 @@ const handleFiles = (data: InfraCodegenApi.CodegenPreview[]): FileNode[] => {
}
/** 构建树形结构 */
const buildTree = (parentKey: string): FileNode[] => {
function buildTree(parentKey: string): FileNode[] {
return files
.filter((file) => file.parentKey === parentKey)
.map((file) => ({
...file,
children: buildTree(file.key),
}));
};
}
return buildTree('/');
};
}
/** 模态框实例 */
const [Modal, modalApi] = useVbenModal({

View File

@ -1,13 +1,8 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { InfraConfigApi } from '#/api/infra/config';
import { useAccess } from '@vben/access';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] {
return [
@ -122,9 +117,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
export function useGridColumns<T = InfraConfigApi.Config>(
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
export function useGridColumns(): VxeTableGridOptions['columns'] {
return [
{
field: 'id',
@ -181,29 +174,10 @@ export function useGridColumns<T = InfraConfigApi.Config>(
formatter: 'formatDateTime',
},
{
field: 'operation',
title: '操作',
minWidth: 130,
align: 'center',
width: 160,
fixed: 'right',
cellRender: {
attrs: {
nameField: 'name',
nameTitle: '参数',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'edit',
show: hasAccessByCodes(['infra:config:update']),
},
{
code: 'delete',
show: hasAccessByCodes(['infra:config:delete']),
},
],
},
slots: { default: 'actions' },
},
];
}

View File

@ -1,17 +1,13 @@
<script lang="ts" setup>
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { InfraConfigApi } from '#/api/infra/config';
import { Page, useVbenModal } from '@vben/common-ui';
import { Download, Plus } from '@vben/icons';
import { downloadFileFromBlobPart } from '@vben/utils';
import { Button, message } from 'ant-design-vue';
import { message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { deleteConfig, exportConfig, getConfigPage } from '#/api/infra/config';
import { $t } from '#/locales';
@ -29,60 +25,46 @@ function onRefresh() {
}
/** 导出表格 */
async function onExport() {
async function handleExport() {
const data = await exportConfig(await gridApi.formApi.getValues());
downloadFileFromBlobPart({ fileName: '参数配置.xls', source: data });
}
/** 创建参数 */
function onCreate() {
function handleCreate() {
formModalApi.setData(null).open();
}
/** 编辑参数 */
function onEdit(row: InfraConfigApi.Config) {
function handleEdit(row: InfraConfigApi.Config) {
formModalApi.setData(row).open();
}
/** 删除参数 */
async function onDelete(row: InfraConfigApi.Config) {
async function handleDelete(row: InfraConfigApi.Config) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.name]),
duration: 0,
key: 'action_process_msg',
key: 'action_key_msg',
});
try {
await deleteConfig(row.id as number);
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
message.success({
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
key: 'action_key_msg',
});
onRefresh();
} catch {
} finally {
hideLoading();
}
}
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<InfraConfigApi.Config>) {
switch (code) {
case 'delete': {
onDelete(row);
break;
}
case 'edit': {
onEdit(row);
break;
}
}
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(onActionClick),
columns: useGridColumns(),
height: 'auto',
keepSource: true,
proxyConfig: {
@ -112,23 +94,48 @@ const [Grid, gridApi] = useVbenVxeGrid({
<FormModal @success="onRefresh" />
<Grid table-title="">
<template #toolbar-tools>
<Button
type="primary"
@click="onCreate"
v-access:code="['infra:config:create']"
>
<Plus class="size-5" />
{{ $t('ui.actionTitle.create', ['参数']) }}
</Button>
<Button
type="primary"
class="ml-2"
@click="onExport"
v-access:code="['infra:config:export']"
>
<Download class="size-5" />
{{ $t('ui.actionTitle.export') }}
</Button>
<TableAction
:actions="[
{
label: $t('ui.actionTitle.create', ['短信渠道']),
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['infra:config:create'],
onClick: handleCreate,
},
{
label: $t('ui.actionTitle.export'),
type: 'primary',
icon: ACTION_ICON.DOWNLOAD,
auth: ['infra:config:export'],
onClick: handleExport,
},
]"
/>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.edit'),
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['system:post:update'],
onClick: handleEdit.bind(null, row),
},
{
label: $t('common.delete'),
type: 'link',
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['system:post:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
</Page>

View File

@ -1,10 +1,5 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { InfraDataSourceConfigApi } from '#/api/infra/data-source-config';
import { useAccess } from '@vben/access';
const { hasAccessByCodes } = useAccess();
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
/** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] {
@ -58,9 +53,7 @@ export function useFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
export function useGridColumns<T = InfraDataSourceConfigApi.DataSourceConfig>(
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
export function useGridColumns(): VxeTableGridOptions['columns'] {
return [
{
field: 'id',
@ -89,31 +82,10 @@ export function useGridColumns<T = InfraDataSourceConfigApi.DataSourceConfig>(
formatter: 'formatDateTime',
},
{
field: 'operation',
title: '操作',
minWidth: 130,
align: 'center',
width: 160,
fixed: 'right',
cellRender: {
attrs: {
nameField: 'name',
nameTitle: '数据源',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'edit',
show: hasAccessByCodes(['infra:data-source-config:update']),
disabled: (row: any) => row.id === 0,
},
{
code: 'delete',
show: hasAccessByCodes(['infra:data-source-config:delete']),
disabled: (row: any) => row.id === 0,
},
],
},
slots: { default: 'actions' },
},
];
}

View File

@ -1,18 +1,14 @@
<script lang="ts" setup>
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { InfraDataSourceConfigApi } from '#/api/infra/data-source-config';
import { onMounted } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Plus } from '@vben/icons';
import { Button, message } from 'ant-design-vue';
import { message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteDataSourceConfig,
getDataSourceConfigList,
@ -28,51 +24,37 @@ const [FormModal, formModalApi] = useVbenModal({
});
/** 创建数据源 */
function onCreate() {
function handleCreate() {
formModalApi.setData(null).open();
}
/** 编辑数据源 */
function onEdit(row: InfraDataSourceConfigApi.DataSourceConfig) {
function handleEdit(row: InfraDataSourceConfigApi.DataSourceConfig) {
formModalApi.setData(row).open();
}
/** 删除数据源 */
async function onDelete(row: InfraDataSourceConfigApi.DataSourceConfig) {
async function handleDelete(row: InfraDataSourceConfigApi.DataSourceConfig) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.name]),
duration: 0,
key: 'action_process_msg',
key: 'action_key_msg',
});
try {
await deleteDataSourceConfig(row.id as number);
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
message.success({
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
key: 'action_key_msg',
});
await handleLoadData();
} catch {
} finally {
hideLoading();
}
}
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<InfraDataSourceConfigApi.DataSourceConfig>) {
switch (code) {
case 'delete': {
onDelete(row);
break;
}
case 'edit': {
onEdit(row);
break;
}
}
}
const [Grid, gridApi] = useVbenVxeGrid({
gridOptions: {
columns: useGridColumns(onActionClick),
columns: useGridColumns(),
height: 'auto',
keepSource: true,
rowConfig: {
@ -94,11 +76,6 @@ async function handleLoadData() {
await gridApi.query();
}
/** 刷新表格 */
async function onRefresh() {
await handleLoadData();
}
/** 初始化 */
onMounted(() => {
handleLoadData();
@ -107,17 +84,44 @@ onMounted(() => {
<template>
<Page auto-content-height>
<FormModal @success="onRefresh" />
<FormModal @success="handleLoadData" />
<Grid table-title="">
<template #toolbar-tools>
<Button
type="primary"
@click="onCreate"
v-access:code="['infra:data-source-config:create']"
>
<Plus class="size-5" />
{{ $t('ui.actionTitle.create', ['数据源']) }}
</Button>
<TableAction
:actions="[
{
label: $t('ui.actionTitle.create', ['数据源']),
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['infra:data-source-config:create'],
onClick: handleCreate,
},
]"
/>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.edit'),
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['infra:data-source-config:update'],
onClick: handleEdit.bind(null, row),
},
{
label: $t('common.delete'),
type: 'link',
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['infra:data-source-config:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
</Page>

View File

@ -1,13 +1,9 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { Demo01ContactApi } from '#/api/infra/demo/demo01';
import { useAccess } from '@vben/access';
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] {
return [
@ -99,9 +95,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
export function useGridColumns(
onActionClick?: OnActionClickFn<Demo01ContactApi.Demo01Contact>,
): VxeTableGridOptions<Demo01ContactApi.Demo01Contact>['columns'] {
export function useGridColumns(): VxeTableGridOptions<Demo01ContactApi.Demo01Contact>['columns'] {
return [
{ type: 'checkbox', width: 40 },
{
@ -146,30 +140,10 @@ export function useGridColumns(
formatter: 'formatDateTime',
},
{
field: 'operation',
title: '操作',
minWidth: 200,
align: 'center',
width: 160,
fixed: 'right',
showOverflow: false,
cellRender: {
attrs: {
nameField: 'id',
nameTitle: '示例联系人',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'edit',
show: hasAccessByCodes(['infra:demo01-contact:update']),
},
{
code: 'delete',
show: hasAccessByCodes(['infra:demo01-contact:delete']),
},
],
},
slots: { default: 'actions' },
},
];
}

View File

@ -1,15 +1,12 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { Demo02CategoryApi } from '#/api/infra/demo/demo02';
import { useAccess } from '@vben/access';
import { handleTree } from '@vben/utils';
import { getDemo02CategoryList } from '#/api/infra/demo/demo02';
import { getRangePickerDefaultProps } from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] {
return [
@ -89,9 +86,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
export function useGridColumns(
onActionClick?: OnActionClickFn<Demo02CategoryApi.Demo02Category>,
): VxeTableGridOptions<Demo02CategoryApi.Demo02Category>['columns'] {
export function useGridColumns(): VxeTableGridOptions<Demo02CategoryApi.Demo02Category>['columns'] {
return [
{
field: 'id',
@ -116,38 +111,10 @@ export function useGridColumns(
formatter: 'formatDateTime',
},
{
field: 'operation',
title: '操作',
minWidth: 200,
align: 'center',
width: 220,
fixed: 'right',
showOverflow: false,
cellRender: {
attrs: {
nameField: 'id',
nameTitle: '示例分类',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'append',
text: '新增下级',
show: hasAccessByCodes(['infra:demo02-category:create']),
},
{
code: 'edit',
show: hasAccessByCodes(['infra:demo02-category:update']),
},
{
code: 'delete',
show: hasAccessByCodes(['infra:demo02-category:delete']),
disabled: (row: Demo02CategoryApi.Demo02Category) => {
return !!(row.children && row.children.length > 0);
},
},
],
},
slots: { default: 'actions' },
},
];
}

View File

@ -1,19 +1,15 @@
<script lang="ts" setup>
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { Demo02CategoryApi } from '#/api/infra/demo/demo02';
import { h, ref } from 'vue';
import { ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Download, Plus } from '@vben/icons';
import { downloadFileFromBlobPart } from '@vben/utils';
import { Button, message } from 'ant-design-vue';
import { message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteDemo02Category,
exportDemo02Category,
@ -42,69 +38,50 @@ function onRefresh() {
}
/** 导出表格 */
async function onExport() {
async function handleExport() {
const data = await exportDemo02Category(await gridApi.formApi.getValues());
downloadFileFromBlobPart({ fileName: '示例分类.xls', source: data });
}
/** 创建示例分类 */
function onCreate() {
function handleCreate() {
formModalApi.setData(null).open();
}
/** 编辑示例分类 */
function onEdit(row: Demo02CategoryApi.Demo02Category) {
function handleEdit(row: Demo02CategoryApi.Demo02Category) {
formModalApi.setData(row).open();
}
/** 新增下级示例分类 */
function onAppend(row: Demo02CategoryApi.Demo02Category) {
function handleAppend(row: Demo02CategoryApi.Demo02Category) {
formModalApi.setData({ parentId: row.id }).open();
}
/** 删除示例分类 */
async function onDelete(row: Demo02CategoryApi.Demo02Category) {
async function handleDelete(row: Demo02CategoryApi.Demo02Category) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.id]),
duration: 0,
key: 'action_process_msg',
key: 'action_key_msg',
});
try {
await deleteDemo02Category(row.id as number);
message.success($t('ui.actionMessage.deleteSuccess', [row.id]));
message.success({
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
key: 'action_key_msg',
});
onRefresh();
} catch {
} finally {
hideLoading();
}
}
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<Demo02CategoryApi.Demo02Category>) {
switch (code) {
case 'append': {
onAppend(row);
break;
}
case 'delete': {
onDelete(row);
break;
}
case 'edit': {
onEdit(row);
break;
}
}
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(onActionClick),
columns: useGridColumns(),
height: 'auto',
treeConfig: {
parentField: 'parentId',
@ -141,26 +118,60 @@ const [Grid, gridApi] = useVbenVxeGrid({
<Grid table-title="">
<template #toolbar-tools>
<Button @click="toggleExpand" class="mr-2">
{{ isExpanded ? '收缩' : '展开' }}
</Button>
<Button
:icon="h(Plus)"
type="primary"
@click="onCreate"
v-access:code="['infra:demo02-category:create']"
>
{{ $t('ui.actionTitle.create', ['示例分类']) }}
</Button>
<Button
:icon="h(Download)"
type="primary"
class="ml-2"
@click="onExport"
v-access:code="['infra:demo02-category:export']"
>
{{ $t('ui.actionTitle.export') }}
</Button>
<TableAction
:actions="[
{
label: isExpanded ? '收缩' : '展开',
type: 'primary',
onClick: toggleExpand,
},
{
label: $t('ui.actionTitle.create', ['菜单']),
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['infra:demo02-category:create'],
onClick: handleCreate,
},
{
label: $t('ui.actionTitle.export'),
type: 'primary',
icon: ACTION_ICON.DOWNLOAD,
auth: ['infra:demo02-category:export'],
onClick: handleExport,
},
]"
/>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: '新增下级',
type: 'link',
icon: ACTION_ICON.ADD,
auth: ['infra:demo02-category:create'],
onClick: handleAppend.bind(null, row),
},
{
label: $t('common.edit'),
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['infra:demo02-category:update'],
onClick: handleEdit.bind(null, row),
},
{
label: $t('common.delete'),
type: 'link',
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['infra:demo02-category:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
</Page>

View File

@ -119,7 +119,7 @@ const [Modal, modalApi] = useVbenModal({
<RadioGroup v-model:value="formData.sex">
<Radio
v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number')"
:key="dict.value"
:key="dict.value.toString()"
:value="dict.value"
>
{{ dict.label }}

View File

@ -88,14 +88,16 @@ function onAppend(row: Demo02CategoryApi.Demo02Category) {
async function onDelete(row: Demo02CategoryApi.Demo02Category) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.id]),
duration: 0,
key: 'action_process_msg',
key: 'action_key_msg',
});
try {
await deleteDemo02Category(row.id as number);
message.success($t('ui.actionMessage.deleteSuccess', [row.id]));
message.success({
content: $t('ui.actionMessage.deleteSuccess', [row.id]),
key: 'action_key_msg',
});
await getList();
} catch {
} finally {
hideLoading();
}
}

View File

@ -116,7 +116,7 @@ const [Modal, modalApi] = useVbenModal({
<RadioGroup v-model:value="formData.sex">
<Radio
v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number')"
:key="dict.value"
:key="dict.value.toString()"
:value="dict.value"
>
{{ dict.label }}

View File

@ -136,7 +136,7 @@ const [Modal, modalApi] = useVbenModal({
<RadioGroup v-model:value="formData.sex">
<Radio
v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number')"
:key="dict.value"
:key="dict.value.toString()"
:value="dict.value"
>
{{ dict.label }}

View File

@ -83,7 +83,7 @@ const [Modal, modalApi] = useVbenModal({
const data = formData.value as Demo03StudentApi.Demo03Student;
//
data.demo03courses = demo03CourseFormRef.value?.getData();
data.demo03grade = demo03GradeFormRef.value?.getValues();
data.demo03grade = demo03GradeFormRef.value?.getValues() as any;
try {
await (formData.value?.id
? updateDemo03Student(data)
@ -135,7 +135,7 @@ const [Modal, modalApi] = useVbenModal({
<RadioGroup v-model:value="formData.sex">
<Radio
v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number')"
:key="dict.value"
:key="dict.value.toString()"
:value="dict.value"
>
{{ dict.label }}

View File

@ -1,13 +1,8 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { InfraFileApi } from '#/api/infra/file';
import { useAccess } from '@vben/access';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { getRangePickerDefaultProps } from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 表单的字段 */
export function useFormSchema(): VbenFormSchema[] {
return [
@ -57,9 +52,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
export function useGridColumns<T = InfraFileApi.File>(
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
export function useGridColumns(): VxeTableGridOptions['columns'] {
return [
{
field: 'name',
@ -112,29 +105,10 @@ export function useGridColumns<T = InfraFileApi.File>(
formatter: 'formatDateTime',
},
{
field: 'operation',
title: '操作',
width: 160,
fixed: 'right',
align: 'center',
cellRender: {
attrs: {
nameField: 'name',
nameTitle: '文件',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'copyUrl',
text: '复制链接',
},
{
code: 'delete',
show: hasAccessByCodes(['infra:file:delete']),
},
],
},
slots: { default: 'actions' },
},
];
}

View File

@ -1,18 +1,14 @@
<script lang="ts" setup>
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { InfraFileApi } from '#/api/infra/file';
import { Page, useVbenModal } from '@vben/common-ui';
import { Upload } from '@vben/icons';
import { openWindow } from '@vben/utils';
import { useClipboard } from '@vueuse/core';
import { Button, Image, message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { deleteFile, getFilePage } from '#/api/infra/file';
import { $t } from '#/locales';
@ -30,13 +26,13 @@ function onRefresh() {
}
/** 上传文件 */
function onUpload() {
function handleUpload() {
formModalApi.setData(null).open();
}
/** 复制链接到剪贴板 */
const { copy } = useClipboard({ legacy: true });
async function onCopyUrl(row: InfraFileApi.File) {
async function handleCopyUrl(row: InfraFileApi.File) {
if (!row.url) {
message.error('文件 URL 为空');
return;
@ -50,15 +46,8 @@ async function onCopyUrl(row: InfraFileApi.File) {
}
}
/** 打开 URL */
function openUrl(url?: string) {
if (url) {
openWindow(url);
}
}
/** 删除文件 */
async function onDelete(row: InfraFileApi.File) {
async function handleDelete(row: InfraFileApi.File) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.name || row.path]),
duration: 0,
@ -70,31 +59,17 @@ async function onDelete(row: InfraFileApi.File) {
$t('ui.actionMessage.deleteSuccess', [row.name || row.path]),
);
onRefresh();
} catch {
} finally {
hideLoading();
}
}
/** 表格操作按钮的回调函数 */
function onActionClick({ code, row }: OnActionClickParams<InfraFileApi.File>) {
switch (code) {
case 'copyUrl': {
onCopyUrl(row);
break;
}
case 'delete': {
onDelete(row);
break;
}
}
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(onActionClick),
columns: useGridColumns(),
height: 'auto',
keepSource: true,
proxyConfig: {
@ -124,24 +99,47 @@ const [Grid, gridApi] = useVbenVxeGrid({
<FormModal @success="onRefresh" />
<Grid table-title="">
<template #toolbar-tools>
<Button type="primary" @click="onUpload">
<Upload class="size-5" />
上传图片
</Button>
<TableAction
:actions="[
{
label: '上传图片',
type: 'primary',
icon: ACTION_ICON.UPLOAD,
auth: ['infra:file:create'],
onClick: handleUpload,
},
]"
/>
</template>
<template #file-content="{ row }">
<Image v-if="row.type && row.type.includes('image')" :src="row.url" />
<Button
v-else-if="row.type && row.type.includes('pdf')"
type="link"
@click="() => openUrl(row.url)"
>
预览
</Button>
<Button v-else type="link" @click="() => openUrl(row.url)">
下载
<Button v-else type="link" @click="() => openWindow(row.url)">
{{ row.type && row.type.includes('pdf') ? '预览' : '下载' }}
</Button>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: '复制链接',
type: 'link',
icon: ACTION_ICON.COPY,
onClick: handleCopyUrl.bind(null, row),
},
{
label: $t('common.delete'),
type: 'link',
danger: true,
auth: ['infra:file:delete'],
icon: ACTION_ICON.DELETE,
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
</Page>
</template>

View File

@ -1,13 +1,8 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { InfraFileConfigApi } from '#/api/infra/file-config';
import { useAccess } from '@vben/access';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] {
return [
@ -265,9 +260,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
export function useGridColumns<T = InfraFileConfigApi.FileConfig>(
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
export function useGridColumns(): VxeTableGridOptions['columns'] {
return [
{
field: 'id',
@ -309,39 +302,10 @@ export function useGridColumns<T = InfraFileConfigApi.FileConfig>(
formatter: 'formatDateTime',
},
{
field: 'operation',
title: '操作',
width: 280,
width: 240,
fixed: 'right',
align: 'center',
cellRender: {
attrs: {
nameField: 'name',
nameTitle: '文件配置',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'edit',
show: hasAccessByCodes(['infra:file-config:update']),
},
{
code: 'delete',
show: hasAccessByCodes(['infra:file-config:delete']),
},
{
code: 'master',
text: '主配置',
disabled: (row: any) => row.master,
show: (_row: any) => hasAccessByCodes(['infra:file-config:update']),
},
{
code: 'test',
text: '测试',
},
],
},
slots: { default: 'actions' },
},
];
}

View File

@ -1,17 +1,13 @@
<script lang="ts" setup>
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { InfraFileConfigApi } from '#/api/infra/file-config';
import { confirm, Page, useVbenModal } from '@vben/common-ui';
import { Plus } from '@vben/icons';
import { openWindow } from '@vben/utils';
import { Button, message } from 'ant-design-vue';
import { message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteFileConfig,
getFileConfigPage,
@ -34,17 +30,17 @@ function onRefresh() {
}
/** 创建文件配置 */
function onCreate() {
function handleCreate() {
formModalApi.setData(null).open();
}
/** 编辑文件配置 */
function onEdit(row: InfraFileConfigApi.FileConfig) {
function handleEdit(row: InfraFileConfigApi.FileConfig) {
formModalApi.setData(row).open();
}
/** 设为主配置 */
async function onMaster(row: InfraFileConfigApi.FileConfig) {
async function handleMaster(row: InfraFileConfigApi.FileConfig) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.updating', [row.name]),
duration: 0,
@ -54,13 +50,13 @@ async function onMaster(row: InfraFileConfigApi.FileConfig) {
await updateFileConfigMaster(row.id as number);
message.success($t('ui.actionMessage.operationSuccess'));
onRefresh();
} catch {
} finally {
hideLoading();
}
}
/** 测试文件配置 */
async function onTest(row: InfraFileConfigApi.FileConfig) {
async function handleTest(row: InfraFileConfigApi.FileConfig) {
const hideLoading = message.loading({
content: '测试上传中...',
duration: 0,
@ -78,58 +74,35 @@ async function onTest(row: InfraFileConfigApi.FileConfig) {
}).then(() => {
openWindow(response);
});
} catch {
} finally {
hideLoading();
}
}
/** 删除文件配置 */
async function onDelete(row: InfraFileConfigApi.FileConfig) {
async function handleDelete(row: InfraFileConfigApi.FileConfig) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.name]),
duration: 0,
key: 'action_process_msg',
key: 'action_key_msg',
});
try {
await deleteFileConfig(row.id as number);
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
message.success({
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
key: 'action_key_msg',
});
onRefresh();
} catch {
} finally {
hideLoading();
}
}
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<InfraFileConfigApi.FileConfig>) {
switch (code) {
case 'delete': {
onDelete(row);
break;
}
case 'edit': {
onEdit(row);
break;
}
case 'master': {
onMaster(row);
break;
}
case 'test': {
onTest(row);
break;
}
}
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(onActionClick),
columns: useGridColumns(),
height: 'auto',
keepSource: true,
proxyConfig: {
@ -159,14 +132,58 @@ const [Grid, gridApi] = useVbenVxeGrid({
<FormModal @success="onRefresh" />
<Grid table-title="">
<template #toolbar-tools>
<Button
type="primary"
@click="onCreate"
v-access:code="['infra:file-config:create']"
>
<Plus class="size-5" />
{{ $t('ui.actionTitle.create', ['文件配置']) }}
</Button>
<TableAction
:actions="[
{
label: $t('ui.actionTitle.create', ['角色']),
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['system:role:create'],
onClick: handleCreate,
},
]"
/>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.edit'),
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['infra:file-config:update'],
onClick: handleEdit.bind(null, row),
},
{
label: '测试',
type: 'link',
icon: 'lucide:test-tube-diagonal',
auth: ['infra:file-config:update'],
onClick: handleTest.bind(null, row),
},
]"
:drop-down-actions="[
{
label: '主配置',
type: 'link',
auth: ['infra:file-config:update'],
popConfirm: {
title: `是否要将${row.name}设为主配置?`,
confirm: handleMaster.bind(null, row),
},
},
{
label: $t('common.delete'),
type: 'link',
danger: true,
auth: ['infra:file-config:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
</Page>

View File

@ -1,12 +1,15 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { InfraJobApi } from '#/api/infra/job';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { DescriptionItemSchema } from '#/components/description';
import { useAccess } from '@vben/access';
import { h } from 'vue';
import { DICT_TYPE, getDictOptions, InfraJobStatusEnum } from '#/utils';
import { formatDateTime } from '@vben/utils';
const { hasAccessByCodes } = useAccess();
import { Timeline } from 'ant-design-vue';
import { DictTag } from '#/components/dict-tag';
import { DICT_TYPE, getDictOptions } from '#/utils';
/** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] {
@ -124,9 +127,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
}
/** 表格列配置 */
export function useGridColumns<T = InfraJobApi.Job>(
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
export function useGridColumns(): VxeTableGridOptions['columns'] {
return [
{
field: 'id',
@ -163,58 +164,77 @@ export function useGridColumns<T = InfraJobApi.Job>(
minWidth: 120,
},
{
field: 'operation',
title: '操作',
width: 280,
width: 240,
fixed: 'right',
align: 'center',
cellRender: {
attrs: {
nameField: 'name',
nameTitle: '任务',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'edit',
show: hasAccessByCodes(['infra:job:update']),
},
{
code: 'update-status',
text: '开启',
show: (row: any) =>
hasAccessByCodes(['infra:job:update']) &&
row.status === InfraJobStatusEnum.STOP,
},
{
code: 'update-status',
text: '暂停',
show: (row: any) =>
hasAccessByCodes(['infra:job:update']) &&
row.status === InfraJobStatusEnum.NORMAL,
},
{
code: 'trigger',
text: '执行',
show: hasAccessByCodes(['infra:job:trigger']),
},
// TODO @芋艿:增加一个“更多”选项
{
code: 'detail',
text: '详细',
show: hasAccessByCodes(['infra:job:query']),
},
{
code: 'log',
text: '日志',
show: hasAccessByCodes(['infra:job:query']),
},
{
code: 'delete',
show: hasAccessByCodes(['infra:job:delete']),
},
],
slots: { default: 'actions' },
},
];
}
/** 详情的配置 */
export function useDetailSchema(): DescriptionItemSchema[] {
return [
{
field: 'id',
label: '任务编号',
},
{
field: 'name',
label: '任务名称',
},
{
field: 'status',
label: '任务状态',
content: (data) =>
h(DictTag, {
type: DICT_TYPE.INFRA_JOB_STATUS,
value: data?.status,
}),
},
{
field: 'handlerName',
label: '处理器的名字',
},
{
field: 'handlerParam',
label: '处理器的参数',
},
{
field: 'cronExpression',
label: 'CRON 表达式',
},
{
field: 'retryCount',
label: '重试次数',
},
{
field: 'retryInterval',
label: '重试间隔',
},
{
field: 'monitorTimeout',
label: '监控超时时间',
content: (data) =>
data?.monitorTimeout && data.monitorTimeout > 0
? `${data.monitorTimeout} 毫秒`
: '未开启',
},
{
field: 'nextTimes',
label: '后续执行时间',
content: (data) => {
if (!data?.nextTimes) {
return '无后续执行时间';
}
if (data.nextTimes.length === 0) {
return '无后续执行时间';
}
return h(Timeline, {}, () =>
data.nextTimes.map((time: any) =>
h(Timeline.Item, {}, () => formatDateTime(time)?.toString()),
),
);
},
},
];

View File

@ -1,19 +1,15 @@
<script lang="ts" setup>
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { InfraJobApi } from '#/api/infra/job';
import { useRouter } from 'vue-router';
import { confirm, Page, useVbenModal } from '@vben/common-ui';
import { Download, History, Plus } from '@vben/icons';
import { downloadFileFromBlobPart } from '@vben/utils';
import { Button, message } from 'ant-design-vue';
import { message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteJob,
exportJob,
@ -47,28 +43,28 @@ function onRefresh() {
}
/** 导出表格 */
async function onExport() {
async function handleExport() {
const data = await exportJob(await gridApi.formApi.getValues());
downloadFileFromBlobPart({ fileName: '定时任务.xls', source: data });
}
/** 创建任务 */
function onCreate() {
function handleCreate() {
formModalApi.setData(null).open();
}
/** 编辑任务 */
function onEdit(row: InfraJobApi.Job) {
function handleEdit(row: InfraJobApi.Job) {
formModalApi.setData(row).open();
}
/** 查看任务详情 */
function onDetail(row: InfraJobApi.Job) {
function handleDetail(row: InfraJobApi.Job) {
detailModalApi.setData({ id: row.id }).open();
}
/** 更新任务状态 */
async function onUpdateStatus(row: InfraJobApi.Job) {
async function handleUpdateStatus(row: InfraJobApi.Job) {
const status =
row.status === InfraJobStatusEnum.STOP
? InfraJobStatusEnum.NORMAL
@ -86,7 +82,7 @@ async function onUpdateStatus(row: InfraJobApi.Job) {
}
/** 执行一次任务 */
async function onTrigger(row: InfraJobApi.Job) {
async function handleTrigger(row: InfraJobApi.Job) {
confirm({
content: `确定执行一次 ${row.name} 吗?`,
}).then(async () => {
@ -96,7 +92,7 @@ async function onTrigger(row: InfraJobApi.Job) {
}
/** 跳转到任务日志 */
function onLog(row?: InfraJobApi.Job) {
function handleLog(row?: InfraJobApi.Job) {
push({
name: 'InfraJobLog',
query: row?.id ? { id: row.id } : {},
@ -104,7 +100,7 @@ function onLog(row?: InfraJobApi.Job) {
}
/** 删除任务 */
async function onDelete(row: InfraJobApi.Job) {
async function handleDelete(row: InfraJobApi.Job) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.name]),
duration: 0,
@ -119,42 +115,12 @@ async function onDelete(row: InfraJobApi.Job) {
}
}
/** 表格操作按钮的回调函数 */
function onActionClick({ code, row }: OnActionClickParams<InfraJobApi.Job>) {
switch (code) {
case 'delete': {
onDelete(row);
break;
}
case 'detail': {
onDetail(row);
break;
}
case 'edit': {
onEdit(row);
break;
}
case 'log': {
onLog(row);
break;
}
case 'trigger': {
onTrigger(row);
break;
}
case 'update-status': {
onUpdateStatus(row);
break;
}
}
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(onActionClick),
columns: useGridColumns(),
height: 'auto',
keepSource: true,
proxyConfig: {
@ -191,32 +157,91 @@ const [Grid, gridApi] = useVbenVxeGrid({
<DetailModal />
<Grid table-title="">
<template #toolbar-tools>
<Button
type="primary"
@click="onCreate"
v-access:code="['infra:job:create']"
>
<Plus class="size-5" />
{{ $t('ui.actionTitle.create', ['任务']) }}
</Button>
<Button
type="primary"
class="ml-2"
@click="onExport"
v-access:code="['infra:job:export']"
>
<Download class="size-5" />
{{ $t('ui.actionTitle.export') }}
</Button>
<Button
type="primary"
class="ml-2"
@click="onLog(undefined)"
v-access:code="['infra:job:query']"
>
<History class="size-5" />
执行日志
</Button>
<TableAction
:actions="[
{
label: $t('ui.actionTitle.create', ['任务']),
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['infra:job:create'],
onClick: handleCreate,
},
{
label: $t('ui.actionTitle.export'),
type: 'primary',
icon: ACTION_ICON.DOWNLOAD,
auth: ['infra:job:export'],
onClick: handleExport,
},
{
label: '执行日志',
type: 'primary',
icon: 'lucide:history',
auth: ['infra:job:export'],
onClick: handleExport,
},
]"
/>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.edit'),
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['infra:job:update'],
onClick: handleEdit.bind(null, row),
},
{
label: '开启',
type: 'link',
icon: 'lucide:circle-play',
auth: ['infra:job:update'],
ifShow: () => row.status === InfraJobStatusEnum.STOP,
onClick: handleUpdateStatus.bind(null, row),
},
{
label: '暂停',
type: 'link',
icon: 'lucide:circle-pause',
auth: ['infra:job:update'],
ifShow: () => row.status === InfraJobStatusEnum.NORMAL,
onClick: handleUpdateStatus.bind(null, row),
},
{
label: '执行',
type: 'link',
icon: 'lucide:clock-plus',
auth: ['infra:job:trigger'],
onClick: handleTrigger.bind(null, row),
},
]"
:drop-down-actions="[
{
label: $t('common.detail'),
type: 'link',
auth: ['infra:job:query'],
onClick: handleDetail.bind(null, row),
},
{
label: '日志',
type: 'link',
auth: ['infra:job:query'],
onClick: handleLog.bind(null, row),
},
{
label: $t('common.delete'),
type: 'link',
danger: true,
auth: ['infra:job:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
</Page>

View File

@ -1,16 +1,16 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { InfraJobLogApi } from '#/api/infra/job-log';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { DescriptionItemSchema } from '#/components/description';
import { h } from 'vue';
import { useAccess } from '@vben/access';
import { formatDateTime } from '@vben/utils';
import dayjs from 'dayjs';
import { DictTag } from '#/components/dict-tag';
import { DICT_TYPE, getDictOptions } from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 列表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] {
return [
@ -65,9 +65,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
}
/** 表格列配置 */
export function useGridColumns<T = InfraJobLogApi.JobLog>(
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
export function useGridColumns(): VxeTableGridOptions['columns'] {
return [
{
field: 'id',
@ -120,26 +118,61 @@ export function useGridColumns<T = InfraJobLogApi.JobLog>(
},
},
{
field: 'operation',
title: '操作',
width: 80,
fixed: 'right',
align: 'center',
cellRender: {
attrs: {
nameField: 'id',
nameTitle: '日志',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'detail',
text: '详细',
show: hasAccessByCodes(['infra:job:query']),
},
],
},
slots: { default: 'actions' },
},
];
}
/** 详情的配置 */
export function useDetailSchema(): DescriptionItemSchema[] {
return [
{
field: 'id',
label: '日志编号',
},
{
field: 'jobId',
label: '任务编号',
},
{
field: 'handlerName',
label: '处理器的名字',
},
{
field: 'handlerParam',
label: '处理器的参数',
},
{
field: 'executeIndex',
label: '第几次执行',
},
{
field: 'beginTime',
label: '执行时间',
},
{
field: 'endTime',
label: '结束时间',
},
{
field: 'duration',
label: '执行时长',
},
{
field: 'status',
label: '任务状态',
content: (data) =>
h(DictTag, {
type: DICT_TYPE.INFRA_JOB_LOG_STATUS,
value: data?.status,
}),
},
{
field: 'result',
label: '执行结果',
},
];
}

View File

@ -1,19 +1,13 @@
<script lang="ts" setup>
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { InfraJobLogApi } from '#/api/infra/job-log';
import { useRoute } from 'vue-router';
import { Page, useVbenModal } from '@vben/common-ui';
import { Download } from '@vben/icons';
import { downloadFileFromBlobPart } from '@vben/utils';
import { Button } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { exportJobLog, getJobLogPage } from '#/api/infra/job-log';
import { DocAlert } from '#/components/doc-alert';
import { $t } from '#/locales';
@ -29,29 +23,16 @@ const [DetailModal, detailModalApi] = useVbenModal({
});
/** 导出表格 */
async function onExport() {
async function handleExport() {
const data = await exportJobLog(await gridApi.formApi.getValues());
downloadFileFromBlobPart({ fileName: '任务日志.xls', source: data });
}
/** 查看日志详情 */
function onDetail(row: InfraJobLogApi.JobLog) {
function handleDetail(row: InfraJobLogApi.JobLog) {
detailModalApi.setData({ id: row.id }).open();
}
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<InfraJobLogApi.JobLog>) {
switch (code) {
case 'detail': {
onDetail(row);
break;
}
}
}
// schemajobId
const formSchema = useGridFormSchema();
@ -60,7 +41,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
schema: formSchema,
},
gridOptions: {
columns: useGridColumns(onActionClick),
columns: useGridColumns(),
height: 'auto',
keepSource: true,
proxyConfig: {
@ -97,15 +78,30 @@ const [Grid, gridApi] = useVbenVxeGrid({
<DetailModal />
<Grid table-title="">
<template #toolbar-tools>
<Button
type="primary"
class="ml-2"
@click="onExport"
v-access:code="['infra:job:export']"
>
<Download class="size-5" />
{{ $t('ui.actionTitle.export') }}
</Button>
<TableAction
:actions="[
{
label: $t('ui.actionTitle.export'),
type: 'primary',
icon: ACTION_ICON.DOWNLOAD,
auth: ['infra:job:export'],
onClick: handleExport,
},
]"
/>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.detail'),
type: 'link',
icon: ACTION_ICON.VIEW,
auth: ['infra:job:query'],
onClick: handleDetail.bind(null, row),
},
]"
/>
</template>
</Grid>
</Page>

View File

@ -4,13 +4,11 @@ import type { InfraJobLogApi } from '#/api/infra/job-log';
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { formatDateTime } from '@vben/utils';
import { Descriptions } from 'ant-design-vue';
import { getJobLog } from '#/api/infra/job-log';
import { DictTag } from '#/components/dict-tag';
import { DICT_TYPE } from '#/utils';
import { useDescription } from '#/components/description';
import { useDetailSchema } from '../data';
const formData = ref<InfraJobLogApi.JobLog>();
@ -33,6 +31,15 @@ const [Modal, modalApi] = useVbenModal({
}
},
});
const [Description] = useDescription({
componentProps: {
bordered: true,
column: 1,
class: 'mx-4',
},
schema: useDetailSchema(),
});
</script>
<template>
@ -42,44 +49,6 @@ const [Modal, modalApi] = useVbenModal({
:show-cancel-button="false"
:show-confirm-button="false"
>
<Descriptions
:column="1"
bordered
size="middle"
class="mx-4"
:label-style="{ width: '140px' }"
>
<Descriptions.Item label="日志编号">
{{ formData?.id }}
</Descriptions.Item>
<Descriptions.Item label="任务编号">
{{ formData?.jobId }}
</Descriptions.Item>
<Descriptions.Item label="处理器的名字">
{{ formData?.handlerName }}
</Descriptions.Item>
<Descriptions.Item label="处理器的参数">
{{ formData?.handlerParam }}
</Descriptions.Item>
<Descriptions.Item label="第几次执行">
{{ formData?.executeIndex }}
</Descriptions.Item>
<Descriptions.Item label="执行时间">
{{ formData?.beginTime ? formatDateTime(formData.beginTime) : '' }} ~
{{ formData?.endTime ? formatDateTime(formData.endTime) : '' }}
</Descriptions.Item>
<Descriptions.Item label="执行时长">
{{ formData?.duration ? `${formData.duration} 毫秒` : '' }}
</Descriptions.Item>
<Descriptions.Item label="任务状态">
<DictTag
:type="DICT_TYPE.INFRA_JOB_LOG_STATUS"
:value="formData?.status"
/>
</Descriptions.Item>
<Descriptions.Item label="执行结果">
{{ formData?.result }}
</Descriptions.Item>
</Descriptions>
<Description :data="formData" />
</Modal>
</template>

View File

@ -4,13 +4,11 @@ import type { InfraJobApi } from '#/api/infra/job';
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { formatDateTime } from '@vben/utils';
import { Descriptions, Timeline } from 'ant-design-vue';
import { getJob, getJobNextTimes } from '#/api/infra/job';
import { DictTag } from '#/components/dict-tag';
import { DICT_TYPE } from '#/utils';
import { useDescription } from '#/components/description';
import { useDetailSchema } from '../data';
const formData = ref<InfraJobApi.Job>(); //
const nextTimes = ref<Date[]>([]); //
@ -31,11 +29,21 @@ const [Modal, modalApi] = useVbenModal({
formData.value = await getJob(data.id);
//
nextTimes.value = await getJobNextTimes(data.id);
formData.value.nextTimes = nextTimes.value;
} finally {
modalApi.unlock();
}
},
});
const [Description] = useDescription({
componentProps: {
bordered: true,
column: 1,
class: 'mx-4',
},
schema: useDetailSchema(),
});
</script>
<template>
@ -45,57 +53,6 @@ const [Modal, modalApi] = useVbenModal({
:show-cancel-button="false"
:show-confirm-button="false"
>
<Descriptions
:column="1"
bordered
size="middle"
class="mx-4"
:label-style="{ width: '140px' }"
>
<Descriptions.Item label="任务编号">
{{ formData?.id }}
</Descriptions.Item>
<Descriptions.Item label="任务名称">
{{ formData?.name }}
</Descriptions.Item>
<Descriptions.Item label="任务状态">
<DictTag :type="DICT_TYPE.INFRA_JOB_STATUS" :value="formData?.status" />
</Descriptions.Item>
<Descriptions.Item label="处理器的名字">
{{ formData?.handlerName }}
</Descriptions.Item>
<Descriptions.Item label="处理器的参数">
{{ formData?.handlerParam }}
</Descriptions.Item>
<Descriptions.Item label="Cron 表达式">
{{ formData?.cronExpression }}
</Descriptions.Item>
<Descriptions.Item label="重试次数">
{{ formData?.retryCount }}
</Descriptions.Item>
<Descriptions.Item label="重试间隔">
{{
formData?.retryInterval ? `${formData.retryInterval} 毫秒` : '无间隔'
}}
</Descriptions.Item>
<Descriptions.Item label="监控超时时间">
{{
formData?.monitorTimeout && formData.monitorTimeout > 0
? `${formData.monitorTimeout} 毫秒`
: '未开启'
}}
</Descriptions.Item>
<Descriptions.Item label="后续执行时间">
<Timeline class="h-[180px]">
<Timeline.Item
v-for="(nextTime, index) in nextTimes"
:key="index"
color="blue"
>
{{ index + 1 }} {{ formatDateTime(nextTime.toString()) }}
</Timeline.Item>
</Timeline>
</Descriptions.Item>
</Descriptions>
<Description :data="formData" />
</Modal>
</template>

View File

@ -17,16 +17,16 @@ import Memory from './modules/memory.vue';
const redisData = ref<InfraRedisApi.RedisMonitorInfo>();
/** 统一加载 Redis 数据 */
const loadRedisData = async () => {
async function loadRedisData() {
try {
redisData.value = await getRedisMonitorInfo();
} catch (error) {
console.error('加载 Redis 数据失败', error);
}
};
}
onMounted(() => {
loadRedisData();
onMounted(async () => {
await loadRedisData();
});
</script>
@ -37,9 +37,11 @@ onMounted(() => {
<DocAlert title="本地缓存" url="https://doc.iocoder.cn/local-cache/" />
</template>
<Card class="mt-5" title="Redis 概览">
<Info :redis-data="redisData" />
</Card>
<div class="class=" mt-5>
<Card title="Redis 概览">
<Info :redis-data="redisData" />
</Card>
</div>
<div class="mt-5 grid grid-cols-1 gap-4 md:grid-cols-2">
<Card title="内存使用">

View File

@ -15,7 +15,7 @@ const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef);
/** 渲染命令统计图表 */
const renderCommandStats = () => {
function renderCommandStats() {
if (!props.redisData?.commandStats) {
return;
}
@ -76,7 +76,7 @@ const renderCommandStats = () => {
},
],
});
};
}
/** 监听数据变化,重新渲染图表 */
watch(
@ -97,7 +97,5 @@ onMounted(() => {
</script>
<template>
<div>
<EchartsUI ref="chartRef" height="420px" />
</div>
<EchartsUI ref="chartRef" height="420px" />
</template>

View File

@ -15,7 +15,7 @@ const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef);
/** 解析内存值,移除单位,转为数字 */
const parseMemoryValue = (memStr: string | undefined): number => {
function parseMemoryValue(memStr: string | undefined): number {
if (!memStr) {
return 0;
}
@ -27,10 +27,10 @@ const parseMemoryValue = (memStr: string | undefined): number => {
} catch {
return 0;
}
};
}
/** 渲染内存使用图表 */
const renderMemoryChart = () => {
function renderMemoryChart() {
if (!props.redisData?.info) {
return;
}
@ -94,7 +94,7 @@ const renderMemoryChart = () => {
detail: {
show: true,
offsetCenter: [0, '50%'],
color: 'auto',
color: 'inherit',
fontSize: 30,
formatter: usedMemory,
},
@ -110,7 +110,7 @@ const renderMemoryChart = () => {
},
],
});
};
}
/** 监听数据变化,重新渲染图表 */
watch(
@ -131,7 +131,5 @@ onMounted(() => {
</script>
<template>
<div>
<EchartsUI ref="chartRef" height="420px" />
</div>
<EchartsUI ref="chartRef" height="420px" />
</template>

View File

@ -100,7 +100,7 @@ watchEffect(() => {
/** 发送消息 */
const sendText = ref(''); //
const sendUserId = ref(''); //
const handlerSend = () => {
function handlerSend() {
if (!sendText.value.trim()) {
message.warning('消息内容不能为空');
return;
@ -119,19 +119,19 @@ const handlerSend = () => {
// 2.
send(jsonMessage);
sendText.value = '';
};
}
/** 切换 websocket 连接状态 */
const toggleConnectStatus = () => {
function toggleConnectStatus() {
if (getIsOpen.value) {
close();
} else {
open();
}
};
}
/** 获取消息类型的徽标颜色 */
const getMessageBadgeColor = (type?: string) => {
function getMessageBadgeColor(type?: string) {
switch (type) {
case 'group': {
return 'green';
@ -146,10 +146,10 @@ const getMessageBadgeColor = (type?: string) => {
return 'default';
}
}
};
}
/** 获取消息类型的文本 */
const getMessageTypeText = (type?: string) => {
function getMessageTypeText(type?: string) {
switch (type) {
case 'group': {
return '群发';
@ -164,7 +164,7 @@ const getMessageTypeText = (type?: string) => {
return '未知';
}
}
};
}
/** 初始化 */
const userList = ref<SystemUserApi.User[]>([]); //

View File

@ -16,6 +16,7 @@
"disabled": "Disabled",
"edit": "Edit",
"delete": "Delete",
"deleteBatch": "Delete Batch",
"create": "Create",
"detail": "Detail",
"yes": "Yes",

View File

@ -16,6 +16,7 @@
"disabled": "已禁用",
"edit": "修改",
"delete": "删除",
"deleteBatch": "批量删除",
"create": "新增",
"detail": "详情",
"yes": "是",