perf: infra table action

pull/112/head
xingyu4j 2025-05-20 16:45:38 +08:00
parent 302bcc25fb
commit 0a40bdf276
36 changed files with 925 additions and 1128 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,5 @@
import type { VbenFormSchema } from '#/adapter/form'; import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { InfraApiErrorLogApi } from '#/api/infra/api-error-log';
import { useAccess } from '@vben/access';
import { import {
DICT_TYPE, DICT_TYPE,
@ -11,8 +8,6 @@ import {
InfraApiErrorLogProcessStatusEnum, InfraApiErrorLogProcessStatusEnum,
} from '#/utils'; } from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 列表的搜索表单 */ /** 列表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] { export function useGridFormSchema(): VbenFormSchema[] {
return [ return [
@ -71,9 +66,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
} }
/** 列表的字段 */ /** 列表的字段 */
export function useGridColumns<T = InfraApiErrorLogApi.ApiErrorLog>( export function useGridColumns(): VxeTableGridOptions['columns'] {
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
return [ return [
{ {
field: 'id', field: 'id',
@ -130,46 +123,10 @@ export function useGridColumns<T = InfraApiErrorLogApi.ApiErrorLog>(
}, },
}, },
{ {
field: 'operation',
title: '操作', title: '操作',
minWidth: 200, width: 200,
align: 'center',
fixed: 'right', fixed: 'right',
cellRender: { slots: { default: 'actions' },
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'])
);
},
},
],
},
}, },
]; ];
} }

View File

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

View File

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

View File

@ -1,13 +1,12 @@
import type { Recordable } from '@vben/types'; import type { Recordable } from '@vben/types';
import type { VbenFormSchema } from '#/adapter/form'; 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 { InfraCodegenApi } from '#/api/infra/codegen';
import type { SystemMenuApi } from '#/api/system/menu'; import type { SystemMenuApi } from '#/api/system/menu';
import { h } from 'vue'; import { h } from 'vue';
import { useAccess } from '@vben/access';
import { IconifyIcon } from '@vben/icons'; import { IconifyIcon } from '@vben/icons';
import { handleTree } from '@vben/utils'; import { handleTree } from '@vben/utils';
@ -17,8 +16,6 @@ import { getMenuList } from '#/api/system/menu';
import { $t } from '#/locales'; import { $t } from '#/locales';
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils'; import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 导入数据库表的表单 */ /** 导入数据库表的表单 */
export function useImportTableFormSchema(): VbenFormSchema[] { export function useImportTableFormSchema(): VbenFormSchema[] {
return [ return [
@ -406,8 +403,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
} }
/** 列表的字段 */ /** 列表的字段 */
export function useGridColumns<T = InfraCodegenApi.CodegenTable>( export function useGridColumns(
onActionClick: OnActionClickFn<T>,
getDataSourceConfigName?: (dataSourceConfigId: number) => string | undefined, getDataSourceConfigName?: (dataSourceConfigId: number) => string | undefined,
): VxeTableGridOptions['columns'] { ): VxeTableGridOptions['columns'] {
return [ return [
@ -445,44 +441,10 @@ export function useGridColumns<T = InfraCodegenApi.CodegenTable>(
formatter: 'formatDateTime', formatter: 'formatDateTime',
}, },
{ {
field: 'operation',
title: '操作', title: '操作',
width: 300, width: 280,
fixed: 'right', fixed: 'right',
align: 'center', slots: { default: 'actions' },
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']),
},
],
},
}, },
]; ];
} }

View File

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

View File

@ -1,8 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { import type { VxeTableGridOptions } from '#/adapter/vxe-table';
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { InfraCodegenApi } from '#/api/infra/codegen'; import type { InfraCodegenApi } from '#/api/infra/codegen';
import type { InfraDataSourceConfigApi } from '#/api/infra/data-source-config'; import type { InfraDataSourceConfigApi } from '#/api/infra/data-source-config';
@ -10,11 +7,10 @@ import { ref } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { Page, useVbenModal } from '@vben/common-ui'; 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 { import {
deleteCodegenTable, deleteCodegenTable,
downloadCodegen, downloadCodegen,
@ -57,22 +53,22 @@ function onRefresh() {
} }
/** 导入表格 */ /** 导入表格 */
function onImport() { function handleImport() {
importModalApi.open(); importModalApi.open();
} }
/** 预览代码 */ /** 预览代码 */
function onPreview(row: InfraCodegenApi.CodegenTable) { function handlePreview(row: InfraCodegenApi.CodegenTable) {
previewModalApi.setData(row).open(); previewModalApi.setData(row).open();
} }
/** 编辑表格 */ /** 编辑表格 */
function onEdit(row: InfraCodegenApi.CodegenTable) { function handleEdit(row: InfraCodegenApi.CodegenTable) {
router.push({ name: 'InfraCodegenEdit', query: { id: row.id } }); router.push({ name: 'InfraCodegenEdit', query: { id: row.id } });
} }
/** 删除代码生成配置 */ /** 删除代码生成配置 */
async function onDelete(row: InfraCodegenApi.CodegenTable) { async function handleDelete(row: InfraCodegenApi.CodegenTable) {
const hideLoading = message.loading({ const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.tableName]), content: $t('ui.actionMessage.deleting', [row.tableName]),
duration: 0, 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({ const hideLoading = message.loading({
content: $t('ui.actionMessage.updating', [row.tableName]), content: $t('ui.actionMessage.updating', [row.tableName]),
duration: 0, key: 'action_key_msg',
key: 'action_process_msg',
}); });
try { try {
await syncCodegenFromDB(row.id); 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(); onRefresh();
} finally { } finally {
hideLoading(); 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({ const hideLoading = message.loading({
content: '正在生成代码...', content: '正在生成代码...',
duration: 0, key: 'action_key_msg',
key: 'action_process_msg',
}); });
try { try {
const res = await downloadCodegen(row.id); const res = await downloadCodegen(row.id);
@ -119,47 +116,21 @@ async function onGenerate(row: InfraCodegenApi.CodegenTable) {
link.download = `codegen-${row.className}.zip`; link.download = `codegen-${row.className}.zip`;
link.click(); link.click();
window.URL.revokeObjectURL(url); window.URL.revokeObjectURL(url);
message.success('代码生成成功'); message.success({
content: '代码生成成功',
key: 'action_key_msg',
});
} finally { } finally {
hideLoading(); 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({ const [Grid, gridApi] = useVbenVxeGrid({
formOptions: { formOptions: {
schema: useGridFormSchema(), schema: useGridFormSchema(),
}, },
gridOptions: { gridOptions: {
columns: useGridColumns(onActionClick, getDataSourceConfigName), columns: useGridColumns(getDataSourceConfigName),
height: 'auto', height: 'auto',
keepSource: true, keepSource: true,
proxyConfig: { proxyConfig: {
@ -217,14 +188,61 @@ initDataSourceConfig();
<PreviewModal /> <PreviewModal />
<Grid table-title=""> <Grid table-title="">
<template #toolbar-tools> <template #toolbar-tools>
<Button <TableAction
type="primary" :actions="[
@click="onImport" {
v-access:code="['infra:codegen:create']" label: $t('ui.actionTitle.import'),
> type: 'primary',
<Plus class="size-5" /> icon: ACTION_ICON.ADD,
导入 auth: ['infra:codegen:create'],
</Button> 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> </template>
</Grid> </Grid>
</Page> </Page>

View File

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

View File

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

View File

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

View File

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

View File

@ -1,10 +1,5 @@
import type { VbenFormSchema } from '#/adapter/form'; import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { InfraDataSourceConfigApi } from '#/api/infra/data-source-config';
import { useAccess } from '@vben/access';
const { hasAccessByCodes } = useAccess();
/** 新增/修改的表单 */ /** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] { export function useFormSchema(): VbenFormSchema[] {
@ -58,9 +53,7 @@ export function useFormSchema(): VbenFormSchema[] {
} }
/** 列表的字段 */ /** 列表的字段 */
export function useGridColumns<T = InfraDataSourceConfigApi.DataSourceConfig>( export function useGridColumns(): VxeTableGridOptions['columns'] {
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
return [ return [
{ {
field: 'id', field: 'id',
@ -89,31 +82,10 @@ export function useGridColumns<T = InfraDataSourceConfigApi.DataSourceConfig>(
formatter: 'formatDateTime', formatter: 'formatDateTime',
}, },
{ {
field: 'operation',
title: '操作', title: '操作',
minWidth: 130, width: 160,
align: 'center',
fixed: 'right', fixed: 'right',
cellRender: { slots: { default: 'actions' },
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,
},
],
},
}, },
]; ];
} }

View File

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

View File

@ -1,13 +1,9 @@
import type { VbenFormSchema } from '#/adapter/form'; 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 type { Demo01ContactApi } from '#/api/infra/demo/demo01';
import { useAccess } from '@vben/access';
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils'; import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 新增/修改的表单 */ /** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] { export function useFormSchema(): VbenFormSchema[] {
return [ return [
@ -99,9 +95,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
} }
/** 列表的字段 */ /** 列表的字段 */
export function useGridColumns( export function useGridColumns(): VxeTableGridOptions<Demo01ContactApi.Demo01Contact>['columns'] {
onActionClick?: OnActionClickFn<Demo01ContactApi.Demo01Contact>,
): VxeTableGridOptions<Demo01ContactApi.Demo01Contact>['columns'] {
return [ return [
{ type: 'checkbox', width: 40 }, { type: 'checkbox', width: 40 },
{ {
@ -146,32 +140,10 @@ export function useGridColumns(
formatter: 'formatDateTime', formatter: 'formatDateTime',
}, },
{ {
field: 'operation',
title: '操作', title: '操作',
minWidth: 200, width: 160,
align: 'center',
fixed: 'right', fixed: 'right',
// TODO @puhui999headerAlign 要使用 headerAlign: 'center' 么?看着现在分成了 align 和 headerAlign 两种 slots: { default: 'actions' },
headerAlign: 'center',
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']),
},
],
},
}, },
]; ];
} }

View File

@ -1,19 +1,15 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { import type { VxeTableGridOptions } from '#/adapter/vxe-table';
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { Demo01ContactApi } from '#/api/infra/demo/demo01'; import type { Demo01ContactApi } from '#/api/infra/demo/demo01';
import { computed, h, ref } from 'vue'; import { computed, ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui'; import { Page, useVbenModal } from '@vben/common-ui';
import { Download, Plus, Trash2 } from '@vben/icons';
import { downloadFileFromBlobPart, isEmpty } from '@vben/utils'; import { downloadFileFromBlobPart, isEmpty } 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 { import {
deleteDemo01Contact, deleteDemo01Contact,
deleteDemo01ContactByIds, deleteDemo01ContactByIds,
@ -35,26 +31,35 @@ function onRefresh() {
gridApi.query(); gridApi.query();
} }
/** 导出表格 */
async function handleExport() {
const data = await exportDemo01Contact(await gridApi.formApi.getValues());
downloadFileFromBlobPart({ fileName: '示例联系人.xls', source: data });
}
/** 创建示例联系人 */ /** 创建示例联系人 */
function onCreate() { function handleCreate() {
formModalApi.setData({}).open(); formModalApi.setData({}).open();
} }
/** 编辑示例联系人 */ /** 编辑示例联系人 */
function onEdit(row: Demo01ContactApi.Demo01Contact) { function handleEdit(row: Demo01ContactApi.Demo01Contact) {
formModalApi.setData(row).open(); formModalApi.setData(row).open();
} }
/** 删除示例联系人 */ /** 删除示例联系人 */
async function onDelete(row: Demo01ContactApi.Demo01Contact) { async function handleDelete(row: Demo01ContactApi.Demo01Contact) {
const hideLoading = message.loading({ const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.id]), content: $t('ui.actionMessage.deleting', [row.id]),
duration: 0, duration: 0,
key: 'action_process_msg', key: 'action_key_msg',
}); });
try { try {
await deleteDemo01Contact(row.id as number); await deleteDemo01Contact(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(); onRefresh();
} finally { } finally {
hideLoading(); hideLoading();
@ -71,51 +76,31 @@ function setDeleteIds({
}) { }) {
deleteIds.value = records.map((item) => item.id); deleteIds.value = records.map((item) => item.id);
} }
/** 批量删除示例联系人 */ /** 批量删除示例联系人 */
async function onDeleteBatch() { async function handleDeleteBatch() {
const hideLoading = message.loading({ const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting'), content: $t('ui.actionMessage.deleting'),
duration: 0, key: 'action_key_msg',
key: 'action_process_msg',
}); });
try { try {
await deleteDemo01ContactByIds(deleteIds.value); await deleteDemo01ContactByIds(deleteIds.value);
message.success($t('ui.actionMessage.deleteSuccess')); message.success({
content: $t('ui.actionMessage.deleteSuccess'),
key: 'action_key_msg',
});
onRefresh(); onRefresh();
} finally { } finally {
hideLoading(); hideLoading();
} }
} }
/** 导出表格 */
async function onExport() {
const data = await exportDemo01Contact(await gridApi.formApi.getValues());
downloadFileFromBlobPart({ fileName: '示例联系人.xls', source: data });
}
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<Demo01ContactApi.Demo01Contact>) {
switch (code) {
case 'delete': {
onDelete(row);
break;
}
case 'edit': {
onEdit(row);
break;
}
}
}
const [Grid, gridApi] = useVbenVxeGrid({ const [Grid, gridApi] = useVbenVxeGrid({
formOptions: { formOptions: {
schema: useGridFormSchema(), schema: useGridFormSchema(),
}, },
gridOptions: { gridOptions: {
columns: useGridColumns(onActionClick), columns: useGridColumns(),
height: 'auto', height: 'auto',
pagerConfig: { pagerConfig: {
enabled: true, enabled: true,
@ -153,34 +138,56 @@ const [Grid, gridApi] = useVbenVxeGrid({
<Grid table-title=""> <Grid table-title="">
<template #toolbar-tools> <template #toolbar-tools>
<Button <TableAction
:icon="h(Plus)" :actions="[
type="primary" {
@click="onCreate" label: $t('ui.actionTitle.create', ['示例联系人']),
v-access:code="['infra:demo01-contact:create']" type: 'primary',
> icon: ACTION_ICON.ADD,
{{ $t('ui.actionTitle.create', ['示例联系人']) }} auth: ['infra:demo01-contact:create'],
</Button> onClick: handleCreate,
<Button },
:icon="h(Download)" {
type="primary" label: $t('ui.actionTitle.export'),
class="ml-2" type: 'primary',
@click="onExport" icon: ACTION_ICON.DOWNLOAD,
v-access:code="['infra:demo01-contact:export']" auth: ['infra:demo01-contact:export'],
> onClick: handleExport,
{{ $t('ui.actionTitle.export') }} },
</Button> {
<Button label: $t('common.deleteBatch'),
:icon="h(Trash2)" type: 'primary',
type="primary" icon: ACTION_ICON.DELETE,
danger auth: ['infra:demo01-contact:delete'],
class="ml-2" disabled: showDeleteBatchBtn,
:disabled="showDeleteBatchBtn" onClick: handleDeleteBatch,
@click="onDeleteBatch" },
v-access:code="['infra:demo01-contact:delete']" ]"
> />
批量删除 </template>
</Button> <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> </template>
</Grid> </Grid>
</Page> </Page>

View File

@ -1,15 +1,12 @@
import type { VbenFormSchema } from '#/adapter/form'; 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 type { Demo02CategoryApi } from '#/api/infra/demo/demo02';
import { useAccess } from '@vben/access';
import { handleTree } from '@vben/utils'; import { handleTree } from '@vben/utils';
import { getDemo02CategoryList } from '#/api/infra/demo/demo02'; import { getDemo02CategoryList } from '#/api/infra/demo/demo02';
import { getRangePickerDefaultProps } from '#/utils'; import { getRangePickerDefaultProps } from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 新增/修改的表单 */ /** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] { export function useFormSchema(): VbenFormSchema[] {
return [ return [
@ -89,9 +86,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
} }
/** 列表的字段 */ /** 列表的字段 */
export function useGridColumns( export function useGridColumns(): VxeTableGridOptions<Demo02CategoryApi.Demo02Category>['columns'] {
onActionClick?: OnActionClickFn<Demo02CategoryApi.Demo02Category>,
): VxeTableGridOptions<Demo02CategoryApi.Demo02Category>['columns'] {
return [ return [
{ {
field: 'id', field: 'id',
@ -116,39 +111,10 @@ export function useGridColumns(
formatter: 'formatDateTime', formatter: 'formatDateTime',
}, },
{ {
field: 'operation',
title: '操作', title: '操作',
minWidth: 200, width: 180,
align: 'center',
fixed: 'right', fixed: 'right',
headerAlign: 'center', slots: { default: 'actions' },
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);
},
},
],
},
}, },
]; ];
} }

View File

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

View File

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

View File

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

View File

@ -1,13 +1,8 @@
import type { VbenFormSchema } from '#/adapter/form'; import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { InfraFileConfigApi } from '#/api/infra/file-config';
import { useAccess } from '@vben/access';
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils'; import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 新增/修改的表单 */ /** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] { export function useFormSchema(): VbenFormSchema[] {
return [ return [
@ -265,9 +260,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
} }
/** 列表的字段 */ /** 列表的字段 */
export function useGridColumns<T = InfraFileConfigApi.FileConfig>( export function useGridColumns(): VxeTableGridOptions['columns'] {
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
return [ return [
{ {
field: 'id', field: 'id',
@ -309,39 +302,10 @@ export function useGridColumns<T = InfraFileConfigApi.FileConfig>(
formatter: 'formatDateTime', formatter: 'formatDateTime',
}, },
{ {
field: 'operation',
title: '操作', title: '操作',
width: 280, width: 240,
fixed: 'right', fixed: 'right',
align: 'center', slots: { default: 'actions' },
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: '测试',
},
],
},
}, },
]; ];
} }

View File

@ -1,17 +1,13 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { import type { VxeTableGridOptions } from '#/adapter/vxe-table';
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { InfraFileConfigApi } from '#/api/infra/file-config'; import type { InfraFileConfigApi } from '#/api/infra/file-config';
import { confirm, Page, useVbenModal } from '@vben/common-ui'; import { confirm, Page, useVbenModal } from '@vben/common-ui';
import { Plus } from '@vben/icons';
import { openWindow } from '@vben/utils'; 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 { import {
deleteFileConfig, deleteFileConfig,
getFileConfigPage, getFileConfigPage,
@ -34,17 +30,17 @@ function onRefresh() {
} }
/** 创建文件配置 */ /** 创建文件配置 */
function onCreate() { function handleCreate() {
formModalApi.setData(null).open(); formModalApi.setData(null).open();
} }
/** 编辑文件配置 */ /** 编辑文件配置 */
function onEdit(row: InfraFileConfigApi.FileConfig) { function handleEdit(row: InfraFileConfigApi.FileConfig) {
formModalApi.setData(row).open(); formModalApi.setData(row).open();
} }
/** 设为主配置 */ /** 设为主配置 */
async function onMaster(row: InfraFileConfigApi.FileConfig) { async function handleMaster(row: InfraFileConfigApi.FileConfig) {
const hideLoading = message.loading({ const hideLoading = message.loading({
content: $t('ui.actionMessage.updating', [row.name]), content: $t('ui.actionMessage.updating', [row.name]),
duration: 0, duration: 0,
@ -54,13 +50,13 @@ async function onMaster(row: InfraFileConfigApi.FileConfig) {
await updateFileConfigMaster(row.id as number); await updateFileConfigMaster(row.id as number);
message.success($t('ui.actionMessage.operationSuccess')); message.success($t('ui.actionMessage.operationSuccess'));
onRefresh(); onRefresh();
} catch { } finally {
hideLoading(); hideLoading();
} }
} }
/** 测试文件配置 */ /** 测试文件配置 */
async function onTest(row: InfraFileConfigApi.FileConfig) { async function handleTest(row: InfraFileConfigApi.FileConfig) {
const hideLoading = message.loading({ const hideLoading = message.loading({
content: '测试上传中...', content: '测试上传中...',
duration: 0, duration: 0,
@ -78,58 +74,35 @@ async function onTest(row: InfraFileConfigApi.FileConfig) {
}).then(() => { }).then(() => {
openWindow(response); openWindow(response);
}); });
} catch { } finally {
hideLoading(); hideLoading();
} }
} }
/** 删除文件配置 */ /** 删除文件配置 */
async function onDelete(row: InfraFileConfigApi.FileConfig) { async function handleDelete(row: InfraFileConfigApi.FileConfig) {
const hideLoading = message.loading({ const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.name]), content: $t('ui.actionMessage.deleting', [row.name]),
duration: 0, key: 'action_key_msg',
key: 'action_process_msg',
}); });
try { try {
await deleteFileConfig(row.id as number); 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(); onRefresh();
} catch { } finally {
hideLoading(); 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({ const [Grid, gridApi] = useVbenVxeGrid({
formOptions: { formOptions: {
schema: useGridFormSchema(), schema: useGridFormSchema(),
}, },
gridOptions: { gridOptions: {
columns: useGridColumns(onActionClick), columns: useGridColumns(),
height: 'auto', height: 'auto',
keepSource: true, keepSource: true,
proxyConfig: { proxyConfig: {
@ -159,14 +132,58 @@ const [Grid, gridApi] = useVbenVxeGrid({
<FormModal @success="onRefresh" /> <FormModal @success="onRefresh" />
<Grid table-title=""> <Grid table-title="">
<template #toolbar-tools> <template #toolbar-tools>
<Button <TableAction
type="primary" :actions="[
@click="onCreate" {
v-access:code="['infra:file-config:create']" label: $t('ui.actionTitle.create', ['角色']),
> type: 'primary',
<Plus class="size-5" /> icon: ACTION_ICON.ADD,
{{ $t('ui.actionTitle.create', ['文件配置']) }} auth: ['system:role:create'],
</Button> 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> </template>
</Grid> </Grid>
</Page> </Page>

View File

@ -1,12 +1,15 @@
import type { VbenFormSchema } from '#/adapter/form'; import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { InfraJobApi } from '#/api/infra/job'; 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[] { export function useFormSchema(): VbenFormSchema[] {
@ -124,9 +127,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
} }
/** 表格列配置 */ /** 表格列配置 */
export function useGridColumns<T = InfraJobApi.Job>( export function useGridColumns(): VxeTableGridOptions['columns'] {
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
return [ return [
{ {
field: 'id', field: 'id',
@ -163,58 +164,77 @@ export function useGridColumns<T = InfraJobApi.Job>(
minWidth: 120, minWidth: 120,
}, },
{ {
field: 'operation',
title: '操作', title: '操作',
width: 280, width: 240,
fixed: 'right', fixed: 'right',
align: 'center', slots: { default: 'actions' },
cellRender: { },
attrs: { ];
nameField: 'name', }
nameTitle: '任务',
onClick: onActionClick, /** 详情的配置 */
}, export function useDetailSchema(): DescriptionItemSchema[] {
name: 'CellOperation', return [
options: [ {
{ field: 'id',
code: 'edit', label: '任务编号',
show: hasAccessByCodes(['infra:job:update']), },
}, {
{ field: 'name',
code: 'update-status', label: '任务名称',
text: '开启', },
show: (row: any) => {
hasAccessByCodes(['infra:job:update']) && field: 'status',
row.status === InfraJobStatusEnum.STOP, label: '任务状态',
}, content: (data) =>
{ h(DictTag, {
code: 'update-status', type: DICT_TYPE.INFRA_JOB_STATUS,
text: '暂停', value: data?.status,
show: (row: any) => }),
hasAccessByCodes(['infra:job:update']) && },
row.status === InfraJobStatusEnum.NORMAL, {
}, field: 'handlerName',
{ label: '处理器的名字',
code: 'trigger', },
text: '执行', {
show: hasAccessByCodes(['infra:job:trigger']), field: 'handlerParam',
}, label: '处理器的参数',
// TODO @芋艿:增加一个“更多”选项 },
{ {
code: 'detail', field: 'cronExpression',
text: '详细', label: 'CRON 表达式',
show: hasAccessByCodes(['infra:job:query']), },
}, {
{ field: 'retryCount',
code: 'log', label: '重试次数',
text: '日志', },
show: hasAccessByCodes(['infra:job:query']), {
}, field: 'retryInterval',
{ label: '重试间隔',
code: 'delete', },
show: hasAccessByCodes(['infra:job:delete']), {
}, 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> <script lang="ts" setup>
import type { import type { VxeTableGridOptions } from '#/adapter/vxe-table';
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { InfraJobApi } from '#/api/infra/job'; import type { InfraJobApi } from '#/api/infra/job';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { confirm, Page, useVbenModal } from '@vben/common-ui'; import { confirm, Page, useVbenModal } from '@vben/common-ui';
import { Download, History, Plus } from '@vben/icons';
import { downloadFileFromBlobPart } from '@vben/utils'; 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 { import {
deleteJob, deleteJob,
exportJob, exportJob,
@ -47,28 +43,28 @@ function onRefresh() {
} }
/** 导出表格 */ /** 导出表格 */
async function onExport() { async function handleExport() {
const data = await exportJob(await gridApi.formApi.getValues()); const data = await exportJob(await gridApi.formApi.getValues());
downloadFileFromBlobPart({ fileName: '定时任务.xls', source: data }); downloadFileFromBlobPart({ fileName: '定时任务.xls', source: data });
} }
/** 创建任务 */ /** 创建任务 */
function onCreate() { function handleCreate() {
formModalApi.setData(null).open(); formModalApi.setData(null).open();
} }
/** 编辑任务 */ /** 编辑任务 */
function onEdit(row: InfraJobApi.Job) { function handleEdit(row: InfraJobApi.Job) {
formModalApi.setData(row).open(); formModalApi.setData(row).open();
} }
/** 查看任务详情 */ /** 查看任务详情 */
function onDetail(row: InfraJobApi.Job) { function handleDetail(row: InfraJobApi.Job) {
detailModalApi.setData({ id: row.id }).open(); detailModalApi.setData({ id: row.id }).open();
} }
/** 更新任务状态 */ /** 更新任务状态 */
async function onUpdateStatus(row: InfraJobApi.Job) { async function handleUpdateStatus(row: InfraJobApi.Job) {
const status = const status =
row.status === InfraJobStatusEnum.STOP row.status === InfraJobStatusEnum.STOP
? InfraJobStatusEnum.NORMAL ? 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({ confirm({
content: `确定执行一次 ${row.name} 吗?`, content: `确定执行一次 ${row.name} 吗?`,
}).then(async () => { }).then(async () => {
@ -96,7 +92,7 @@ async function onTrigger(row: InfraJobApi.Job) {
} }
/** 跳转到任务日志 */ /** 跳转到任务日志 */
function onLog(row?: InfraJobApi.Job) { function handleLog(row?: InfraJobApi.Job) {
push({ push({
name: 'InfraJobLog', name: 'InfraJobLog',
query: row?.id ? { id: row.id } : {}, 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({ const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.name]), content: $t('ui.actionMessage.deleting', [row.name]),
duration: 0, 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({ const [Grid, gridApi] = useVbenVxeGrid({
formOptions: { formOptions: {
schema: useGridFormSchema(), schema: useGridFormSchema(),
}, },
gridOptions: { gridOptions: {
columns: useGridColumns(onActionClick), columns: useGridColumns(),
height: 'auto', height: 'auto',
keepSource: true, keepSource: true,
proxyConfig: { proxyConfig: {
@ -191,32 +157,91 @@ const [Grid, gridApi] = useVbenVxeGrid({
<DetailModal /> <DetailModal />
<Grid table-title=""> <Grid table-title="">
<template #toolbar-tools> <template #toolbar-tools>
<Button <TableAction
type="primary" :actions="[
@click="onCreate" {
v-access:code="['infra:job:create']" label: $t('ui.actionTitle.create', ['任务']),
> type: 'primary',
<Plus class="size-5" /> icon: ACTION_ICON.ADD,
{{ $t('ui.actionTitle.create', ['任务']) }} auth: ['infra:job:create'],
</Button> onClick: handleCreate,
<Button },
type="primary" {
class="ml-2" label: $t('ui.actionTitle.export'),
@click="onExport" type: 'primary',
v-access:code="['infra:job:export']" icon: ACTION_ICON.DOWNLOAD,
> auth: ['infra:job:export'],
<Download class="size-5" /> onClick: handleExport,
{{ $t('ui.actionTitle.export') }} },
</Button> {
<Button label: '执行日志',
type="primary" type: 'primary',
class="ml-2" icon: 'lucide:history',
@click="onLog(undefined)" auth: ['infra:job:export'],
v-access:code="['infra:job:query']" onClick: handleExport,
> },
<History class="size-5" /> ]"
执行日志 />
</Button> </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> </template>
</Grid> </Grid>
</Page> </Page>

View File

@ -1,16 +1,16 @@
import type { VbenFormSchema } from '#/adapter/form'; import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { InfraJobLogApi } from '#/api/infra/job-log'; import type { DescriptionItemSchema } from '#/components/description';
import { h } from 'vue';
import { useAccess } from '@vben/access';
import { formatDateTime } from '@vben/utils'; import { formatDateTime } from '@vben/utils';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { DictTag } from '#/components/dict-tag';
import { DICT_TYPE, getDictOptions } from '#/utils'; import { DICT_TYPE, getDictOptions } from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 列表的搜索表单 */ /** 列表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] { export function useGridFormSchema(): VbenFormSchema[] {
return [ return [
@ -65,9 +65,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
} }
/** 表格列配置 */ /** 表格列配置 */
export function useGridColumns<T = InfraJobLogApi.JobLog>( export function useGridColumns(): VxeTableGridOptions['columns'] {
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
return [ return [
{ {
field: 'id', field: 'id',
@ -120,26 +118,61 @@ export function useGridColumns<T = InfraJobLogApi.JobLog>(
}, },
}, },
{ {
field: 'operation',
title: '操作', title: '操作',
width: 80, width: 80,
fixed: 'right', fixed: 'right',
align: 'center', slots: { default: 'actions' },
cellRender: { },
attrs: { ];
nameField: 'id', }
nameTitle: '日志',
onClick: onActionClick, /** 详情的配置 */
}, export function useDetailSchema(): DescriptionItemSchema[] {
name: 'CellOperation', return [
options: [ {
{ field: 'id',
code: 'detail', label: '日志编号',
text: '详细', },
show: hasAccessByCodes(['infra:job:query']), {
}, 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> <script lang="ts" setup>
import type { import type { VxeTableGridOptions } from '#/adapter/vxe-table';
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { InfraJobLogApi } from '#/api/infra/job-log'; import type { InfraJobLogApi } from '#/api/infra/job-log';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { Page, useVbenModal } from '@vben/common-ui'; import { Page, useVbenModal } from '@vben/common-ui';
import { Download } from '@vben/icons';
import { downloadFileFromBlobPart } from '@vben/utils'; import { downloadFileFromBlobPart } from '@vben/utils';
import { Button } from 'ant-design-vue'; import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { exportJobLog, getJobLogPage } from '#/api/infra/job-log'; import { exportJobLog, getJobLogPage } from '#/api/infra/job-log';
import { DocAlert } from '#/components/doc-alert'; import { DocAlert } from '#/components/doc-alert';
import { $t } from '#/locales'; 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()); const data = await exportJobLog(await gridApi.formApi.getValues());
downloadFileFromBlobPart({ fileName: '任务日志.xls', source: data }); downloadFileFromBlobPart({ fileName: '任务日志.xls', source: data });
} }
/** 查看日志详情 */ /** 查看日志详情 */
function onDetail(row: InfraJobLogApi.JobLog) { function handleDetail(row: InfraJobLogApi.JobLog) {
detailModalApi.setData({ id: row.id }).open(); detailModalApi.setData({ id: row.id }).open();
} }
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<InfraJobLogApi.JobLog>) {
switch (code) {
case 'detail': {
onDetail(row);
break;
}
}
}
// schemajobId // schemajobId
const formSchema = useGridFormSchema(); const formSchema = useGridFormSchema();
@ -60,7 +41,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
schema: formSchema, schema: formSchema,
}, },
gridOptions: { gridOptions: {
columns: useGridColumns(onActionClick), columns: useGridColumns(),
height: 'auto', height: 'auto',
keepSource: true, keepSource: true,
proxyConfig: { proxyConfig: {
@ -97,15 +78,30 @@ const [Grid, gridApi] = useVbenVxeGrid({
<DetailModal /> <DetailModal />
<Grid table-title=""> <Grid table-title="">
<template #toolbar-tools> <template #toolbar-tools>
<Button <TableAction
type="primary" :actions="[
class="ml-2" {
@click="onExport" label: $t('ui.actionTitle.export'),
v-access:code="['infra:job:export']" type: 'primary',
> icon: ACTION_ICON.DOWNLOAD,
<Download class="size-5" /> auth: ['infra:job:export'],
{{ $t('ui.actionTitle.export') }} onClick: handleExport,
</Button> },
]"
/>
</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> </template>
</Grid> </Grid>
</Page> </Page>

View File

@ -4,13 +4,11 @@ import type { InfraJobLogApi } from '#/api/infra/job-log';
import { ref } from 'vue'; import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui'; 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 { getJobLog } from '#/api/infra/job-log';
import { DictTag } from '#/components/dict-tag'; import { useDescription } from '#/components/description';
import { DICT_TYPE } from '#/utils';
import { useDetailSchema } from '../data';
const formData = ref<InfraJobLogApi.JobLog>(); 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> </script>
<template> <template>
@ -42,44 +49,6 @@ const [Modal, modalApi] = useVbenModal({
:show-cancel-button="false" :show-cancel-button="false"
:show-confirm-button="false" :show-confirm-button="false"
> >
<Descriptions <Description :data="formData" />
: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>
</Modal> </Modal>
</template> </template>

View File

@ -4,13 +4,11 @@ import type { InfraJobApi } from '#/api/infra/job';
import { ref } from 'vue'; import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui'; 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 { getJob, getJobNextTimes } from '#/api/infra/job';
import { DictTag } from '#/components/dict-tag'; import { useDescription } from '#/components/description';
import { DICT_TYPE } from '#/utils';
import { useDetailSchema } from '../data';
const formData = ref<InfraJobApi.Job>(); // const formData = ref<InfraJobApi.Job>(); //
const nextTimes = ref<Date[]>([]); // const nextTimes = ref<Date[]>([]); //
@ -31,11 +29,21 @@ const [Modal, modalApi] = useVbenModal({
formData.value = await getJob(data.id); formData.value = await getJob(data.id);
// //
nextTimes.value = await getJobNextTimes(data.id); nextTimes.value = await getJobNextTimes(data.id);
formData.value.nextTimes = nextTimes.value;
} finally { } finally {
modalApi.unlock(); modalApi.unlock();
} }
}, },
}); });
const [Description] = useDescription({
componentProps: {
bordered: true,
column: 1,
class: 'mx-4',
},
schema: useDetailSchema(),
});
</script> </script>
<template> <template>
@ -45,57 +53,6 @@ const [Modal, modalApi] = useVbenModal({
:show-cancel-button="false" :show-cancel-button="false"
:show-confirm-button="false" :show-confirm-button="false"
> >
<Descriptions <Description :data="formData" />
: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>
</Modal> </Modal>
</template> </template>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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