refactor:基于 lint 处理排版

This commit is contained in:
YunaiV
2025-04-22 22:10:33 +08:00
parent 3fe36fd823
commit fb785894b6
322 changed files with 4781 additions and 2093 deletions

View File

@@ -2,7 +2,7 @@ import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { InfraApiAccessLogApi } from '#/api/infra/api-access-log';
import {DICT_TYPE, getDictOptions} from '#/utils/dict';
import { DICT_TYPE, getDictOptions } from '#/utils/dict';
import { getRangePickerDefaultProps } from '#/utils/date';
import { useAccess } from '@vben/access';
@@ -70,7 +70,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
export function useGridColumns<T = InfraApiAccessLogApi.SystemApiAccessLog>(
export function useGridColumns<T = InfraApiAccessLogApi.ApiAccessLog>(
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
return [
@@ -125,9 +125,7 @@ export function useGridColumns<T = InfraApiAccessLogApi.SystemApiAccessLog>(
title: '操作结果',
minWidth: 150,
formatter: ({ row }) => {
return row.resultCode === 0
? '成功'
: `失败(${row.resultMsg})`;
return row.resultCode === 0 ? '成功' : `失败(${row.resultMsg})`;
},
},
{

View File

@@ -1,19 +1,26 @@
<script lang="ts" setup>
import type { OnActionClickParams, VxeTableGridOptions } from '#/adapter/vxe-table';
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { InfraApiAccessLogApi } from '#/api/infra/api-access-log';
import { Page, useVbenModal } from '@vben/common-ui';
import { Button } from 'ant-design-vue';
import { Download } from '@vben/icons';
import Detail from './modules/detail.vue';
import { DocAlert } from '#/components/doc-alert';
import { $t } from '#/locales';
import { Button } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { exportApiAccessLog, getApiAccessLogPage } from '#/api/infra/api-access-log';
import {
exportApiAccessLog,
getApiAccessLogPage,
} from '#/api/infra/api-access-log';
import { DocAlert } from '#/components/doc-alert';
import { $t } from '#/locales';
import { downloadByData } from '#/utils/download';
import { useGridColumns, useGridFormSchema } from './data';
import Detail from './modules/detail.vue';
const [DetailModal, detailModalApi] = useVbenModal({
connectedComponent: Detail,
@@ -32,7 +39,7 @@ async function onExport() {
}
/** 查看 API 访问日志详情 */
function onDetail(row: InfraApiAccessLogApi.SystemApiAccessLog) {
function onDetail(row: InfraApiAccessLogApi.ApiAccessLog) {
detailModalApi.setData(row).open();
}
@@ -40,7 +47,7 @@ function onDetail(row: InfraApiAccessLogApi.SystemApiAccessLog) {
function onActionClick({
code,
row,
}: OnActionClickParams<InfraApiAccessLogApi.SystemApiAccessLog>) {
}: OnActionClickParams<InfraApiAccessLogApi.ApiAccessLog>) {
switch (code) {
case 'detail': {
onDetail(row);
@@ -75,7 +82,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
refresh: { code: 'query' },
search: true,
},
} as VxeTableGridOptions<InfraApiAccessLogApi.SystemApiAccessLog>,
} as VxeTableGridOptions<InfraApiAccessLogApi.ApiAccessLog>,
});
</script>
@@ -86,11 +93,16 @@ const [Grid, gridApi] = useVbenVxeGrid({
<DetailModal @success="onRefresh" />
<Grid table-title="API 访问日志列表">
<template #toolbar-tools>
<Button type="primary" class="ml-2" @click="onExport" v-access:code="['infra:api-access-log:export']">
<Button
type="primary"
class="ml-2"
@click="onExport"
v-access:code="['infra:api-access-log:export']"
>
<Download class="size-5" />
{{ $t('ui.actionTitle.export') }}
</Button>
</template>
</Grid>
</Page>
</template>
</template>

View File

@@ -1,15 +1,17 @@
<script lang="ts" setup>
import type { InfraApiAccessLogApi } from '#/api/infra/api-access-log';
import { useVbenModal } from '@vben/common-ui';
import { Descriptions } from 'ant-design-vue';
import { DictTag } from '#/components/dict-tag';
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { formatDateTime } from '@vben/utils';
import { Descriptions } from 'ant-design-vue';
import { DictTag } from '#/components/dict-tag';
import { DICT_TYPE } from '#/utils/dict';
const formData = ref<InfraApiAccessLogApi.SystemApiAccessLog>();
const formData = ref<InfraApiAccessLogApi.ApiAccessLog>();
const [Modal, modalApi] = useVbenModal({
async onOpenChange(isOpen: boolean) {
@@ -17,7 +19,7 @@ const [Modal, modalApi] = useVbenModal({
return;
}
// 加载数据
const data = modalApi.getData<InfraApiAccessLogApi.SystemApiAccessLog>();
const data = modalApi.getData<InfraApiAccessLogApi.ApiAccessLog>();
if (!data || !data.id) {
return;
}
@@ -32,8 +34,19 @@ const [Modal, modalApi] = useVbenModal({
</script>
<template>
<Modal title="API 访问日志详情" class="w-1/2" :show-cancel-button="false" :show-confirm-button="false">
<Descriptions bordered :column="1" size="middle" class="mx-4" :label-style="{ width: '110px' }">
<Modal
title="API 访问日志详情"
class="w-1/2"
:show-cancel-button="false"
:show-confirm-button="false"
>
<Descriptions
bordered
:column="1"
size="middle"
class="mx-4"
:label-style="{ width: '110px' }"
>
<Descriptions.Item label="日志编号">
{{ formData?.id }}
</Descriptions.Item>
@@ -63,11 +76,15 @@ const [Modal, modalApi] = useVbenModal({
{{ formData?.responseBody }}
</Descriptions.Item>
<Descriptions.Item label="请求时间">
{{ formatDateTime(formData?.beginTime || '') }} ~ {{ formatDateTime(formData?.endTime || '') }}
{{ formatDateTime(formData?.beginTime || '') }} ~
{{ formatDateTime(formData?.endTime || '') }}
</Descriptions.Item>
<Descriptions.Item label="请求耗时">
{{ formData?.duration }} ms
</Descriptions.Item>
<Descriptions.Item label="请求耗时">{{ formData?.duration }} ms</Descriptions.Item>
<Descriptions.Item label="操作结果">
<div v-if="formData?.resultCode === 0">正常</div>
<!-- TODO @芋艿处理爆红 -->
<div v-else-if="formData?.resultCode > 0">
失败 | {{ formData?.resultCode }} | {{ formData?.resultMsg }}
</div>
@@ -79,7 +96,10 @@ const [Modal, modalApi] = useVbenModal({
{{ formData?.operateName }}
</Descriptions.Item>
<Descriptions.Item label="操作类型">
<DictTag :type="DICT_TYPE.INFRA_OPERATE_TYPE" :value="formData?.operateType" />
<DictTag
:type="DICT_TYPE.INFRA_OPERATE_TYPE"
:value="formData?.operateType"
/>
</Descriptions.Item>
</Descriptions>
</Modal>

View File

@@ -2,7 +2,7 @@ import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { InfraApiErrorLogApi } from '#/api/infra/api-error-log';
import {DICT_TYPE, getDictOptions} from '#/utils/dict';
import { DICT_TYPE, getDictOptions } from '#/utils/dict';
import { getRangePickerDefaultProps } from '#/utils/date';
import { useAccess } from '@vben/access';
import { InfraApiErrorLogProcessStatusEnum } from '#/utils/constants';
@@ -54,7 +54,10 @@ export function useGridFormSchema(): VbenFormSchema[] {
label: '处理状态',
component: 'Select',
componentProps: {
options: getDictOptions(DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS, 'number'),
options: getDictOptions(
DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS,
'number',
),
allowClear: true,
placeholder: '请选择处理状态',
},
@@ -64,7 +67,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
export function useGridColumns<T = InfraApiErrorLogApi.SystemApiErrorLog>(
export function useGridColumns<T = InfraApiErrorLogApi.ApiErrorLog>(
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
return [
@@ -144,17 +147,21 @@ export function useGridColumns<T = InfraApiErrorLogApi.SystemApiErrorLog>(
{
code: 'done',
text: '已处理',
show: (row: InfraApiErrorLogApi.SystemApiErrorLog) => {
return row.processStatus === InfraApiErrorLogProcessStatusEnum.INIT &&
hasAccessByCodes(['infra:api-error-log:update-status']);
show: (row: InfraApiErrorLogApi.ApiErrorLog) => {
return (
row.processStatus === InfraApiErrorLogProcessStatusEnum.INIT &&
hasAccessByCodes(['infra:api-error-log:update-status'])
);
},
},
{
code: 'ignore',
text: '已忽略',
show: (row: InfraApiErrorLogApi.SystemApiErrorLog) => {
return row.processStatus === InfraApiErrorLogProcessStatusEnum.INIT &&
hasAccessByCodes(['infra:api-error-log:update-status']);
show: (row: InfraApiErrorLogApi.ApiErrorLog) => {
return (
row.processStatus === InfraApiErrorLogProcessStatusEnum.INIT &&
hasAccessByCodes(['infra:api-error-log:update-status'])
);
},
},
],

View File

@@ -1,20 +1,28 @@
<script lang="ts" setup>
import type { OnActionClickParams, VxeTableGridOptions } from '#/adapter/vxe-table';
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { InfraApiErrorLogApi } from '#/api/infra/api-error-log';
import { Page, useVbenModal } from '@vben/common-ui';
import { Button, message, Modal } from 'ant-design-vue';
import { Download } from '@vben/icons';
import Detail from './modules/detail.vue';
import { DocAlert } from '#/components/doc-alert';
import { $t } from '#/locales';
import { Button, message, Modal } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { exportApiErrorLog, getApiErrorLogPage, updateApiErrorLogStatus } from '#/api/infra/api-error-log';
import { downloadByData } from '#/utils/download';
import {
exportApiErrorLog,
getApiErrorLogPage,
updateApiErrorLogStatus,
} from '#/api/infra/api-error-log';
import { DocAlert } from '#/components/doc-alert';
import { $t } from '#/locales';
import { InfraApiErrorLogProcessStatusEnum } from '#/utils/constants';
import { downloadByData } from '#/utils/download';
import { useGridColumns, useGridFormSchema } from './data';
import Detail from './modules/detail.vue';
const [DetailModal, detailModalApi] = useVbenModal({
connectedComponent: Detail,
@@ -33,7 +41,7 @@ async function onExport() {
}
/** 查看 API 错误日志详情 */
function onDetail(row: InfraApiErrorLogApi.SystemApiErrorLog) {
function onDetail(row: InfraApiErrorLogApi.ApiErrorLog) {
detailModalApi.setData(row).open();
}
@@ -50,7 +58,7 @@ async function onProcess(id: number, processStatus: number) {
key: 'action_process_msg',
});
onRefresh();
}
},
});
}
@@ -58,7 +66,7 @@ async function onProcess(id: number, processStatus: number) {
function onActionClick({
code,
row,
}: OnActionClickParams<InfraApiErrorLogApi.SystemApiErrorLog>) {
}: OnActionClickParams<InfraApiErrorLogApi.ApiErrorLog>) {
switch (code) {
case 'detail': {
onDetail(row);
@@ -101,7 +109,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
refresh: { code: 'query' },
search: true,
},
} as VxeTableGridOptions<InfraApiErrorLogApi.SystemApiErrorLog>,
} as VxeTableGridOptions<InfraApiErrorLogApi.ApiErrorLog>,
});
</script>
@@ -112,11 +120,16 @@ const [Grid, gridApi] = useVbenVxeGrid({
<DetailModal @success="onRefresh" />
<Grid table-title="API 错误日志列表">
<template #toolbar-tools>
<Button type="primary" class="ml-2" @click="onExport" v-access:code="['infra:api-error-log:export']">
<Button
type="primary"
class="ml-2"
@click="onExport"
v-access:code="['infra:api-error-log:export']"
>
<Download class="size-5" />
{{ $t('ui.actionTitle.export') }}
</Button>
</template>
</Grid>
</Page>
</template>
</template>

View File

@@ -1,15 +1,17 @@
<script lang="ts" setup>
import type { InfraApiErrorLogApi } from '#/api/infra/api-error-log';
import { useVbenModal } from '@vben/common-ui';
import { Descriptions, Input } from 'ant-design-vue';
import { DictTag } from '#/components/dict-tag';
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { formatDateTime } from '@vben/utils';
import { Descriptions, Input } from 'ant-design-vue';
import { DictTag } from '#/components/dict-tag';
import { DICT_TYPE } from '#/utils/dict';
const formData = ref<InfraApiErrorLogApi.SystemApiErrorLog>();
const formData = ref<InfraApiErrorLogApi.ApiErrorLog>();
const [Modal, modalApi] = useVbenModal({
async onOpenChange(isOpen: boolean) {
@@ -17,7 +19,7 @@ const [Modal, modalApi] = useVbenModal({
return;
}
// 加载数据
const data = modalApi.getData<InfraApiErrorLogApi.SystemApiErrorLog>();
const data = modalApi.getData<InfraApiErrorLogApi.ApiErrorLog>();
if (!data || !data.id) {
return;
}
@@ -32,8 +34,19 @@ const [Modal, modalApi] = useVbenModal({
</script>
<template>
<Modal title="API错误日志详情" class="w-1/2" :show-cancel-button="false" :show-confirm-button="false">
<Descriptions bordered :column="1" size="middle" class="mx-4" :label-style="{ width: '110px' }">
<Modal
title="API错误日志详情"
class="w-1/2"
:show-cancel-button="false"
:show-confirm-button="false"
>
<Descriptions
bordered
:column="1"
size="middle"
class="mx-4"
:label-style="{ width: '110px' }"
>
<Descriptions.Item label="日志编号">
{{ formData?.id }}
</Descriptions.Item>
@@ -68,7 +81,7 @@ const [Modal, modalApi] = useVbenModal({
<Descriptions.Item v-if="formData?.exceptionStackTrace" label="异常堆栈">
<Input.TextArea
:value="formData?.exceptionStackTrace"
:autoSize="{ maxRows: 20 }"
:auto-size="{ maxRows: 20 }"
readonly
/>
</Descriptions.Item>

View File

@@ -129,7 +129,10 @@ export function useGenerationInfoBaseFormSchema(): VbenFormSchema[] {
fieldName: 'templateType',
label: '生成模板',
componentProps: {
options: getDictOptions(DICT_TYPE.INFRA_CODEGEN_TEMPLATE_TYPE, 'number'),
options: getDictOptions(
DICT_TYPE.INFRA_CODEGEN_TEMPLATE_TYPE,
'number',
),
class: 'w-full',
},
rules: 'selectRequired',
@@ -166,7 +169,7 @@ export function useGenerationInfoBaseFormSchema(): VbenFormSchema[] {
data.unshift({
id: 0,
name: '顶级菜单',
} as SystemMenuApi.SystemMenu);
} as SystemMenuApi.Menu);
return handleTree(data);
},
class: 'w-full',
@@ -232,7 +235,9 @@ export function useGenerationInfoBaseFormSchema(): VbenFormSchema[] {
}
/** 树表信息 schema */
export function useGenerationInfoTreeFormSchema(columns: InfraCodegenApi.CodegenColumn[] = []): VbenFormSchema[] {
export function useGenerationInfoTreeFormSchema(
columns: InfraCodegenApi.CodegenColumn[] = [],
): VbenFormSchema[] {
return [
{
component: 'Divider',

View File

@@ -1,18 +1,20 @@
<script lang="ts" setup>
import type { InfraCodegenApi } from '#/api/infra/codegen';
import BasicInfo from '../modules/basic-info.vue';
import ColumnInfo from '../modules/column-info.vue';
import GenerationInfo from '../modules/generation-info.vue';
import { ref, unref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { Page } from '@vben/common-ui';
import { useTabs } from '@vben/hooks';
import { Button, message, Steps } from 'ant-design-vue';
import { getCodegenTable, updateCodegenTable } from '#/api/infra/codegen';
import { $t } from '#/locales';
import { ref, unref } from 'vue';
import { useTabs } from '@vben/hooks';
import { useRoute, useRouter } from 'vue-router';
import BasicInfo from '../modules/basic-info.vue';
import ColumnInfo from '../modules/column-info.vue';
import GenerationInfo from '../modules/generation-info.vue';
const route = useRoute();
const router = useRouter();
@@ -67,7 +69,10 @@ const submitForm = async () => {
const basicInfo = await basicInfoRef.value?.getValues();
const columns = columnInfoRef.value?.getData() || unref(formData).columns;
const generateInfo = await generateInfoRef.value?.getValues();
await updateCodegenTable({ table: { ...unref(formData).table, ...basicInfo, ...generateInfo }, columns });
await updateCodegenTable({
table: { ...unref(formData).table, ...basicInfo, ...generateInfo },
columns,
});
// 关闭并提示
message.success({
content: $t('ui.actionMessage.operationSuccess'),
@@ -118,15 +123,33 @@ getDetail();
<template>
<Page auto-content-height v-loading="loading">
<div class="flex h-[95%] flex-col rounded-md bg-white p-4 dark:bg-[#1f1f1f] dark:text-gray-300">
<Steps type="navigation" v-model:current="currentStep" class="mb-8 rounded shadow-sm dark:bg-[#141414]">
<Steps.Step v-for="(step, index) in steps" :key="index" :title="step.title" />
<div
class="flex h-[95%] flex-col rounded-md bg-white p-4 dark:bg-[#1f1f1f] dark:text-gray-300"
>
<Steps
type="navigation"
v-model:current="currentStep"
class="mb-8 rounded shadow-sm dark:bg-[#141414]"
>
<Steps.Step
v-for="(step, index) in steps"
:key="index"
:title="step.title"
/>
</Steps>
<div class="flex-1 overflow-auto py-4">
<!-- 根据当前步骤显示对应的组件 -->
<BasicInfo v-show="currentStep === 0" ref="basicInfoRef" :table="formData.table" />
<ColumnInfo v-show="currentStep === 1" ref="columnInfoRef" :columns="formData.columns" />
<BasicInfo
v-show="currentStep === 0"
ref="basicInfoRef"
:table="formData.table"
/>
<ColumnInfo
v-show="currentStep === 1"
ref="columnInfoRef"
:columns="formData.columns"
/>
<GenerationInfo
v-show="currentStep === 2"
ref="generateInfoRef"
@@ -137,8 +160,12 @@ getDetail();
<div class="mt-4 flex justify-end space-x-2">
<Button v-show="currentStep > 0" @click="prevStep">上一步</Button>
<Button v-show="currentStep < steps.length - 1" @click="nextStep">下一步</Button>
<Button type="primary" :loading="loading" @click="submitForm"> 保存 </Button>
<Button v-show="currentStep < steps.length - 1" @click="nextStep">
下一步
</Button>
<Button type="primary" :loading="loading" @click="submitForm">
保存
</Button>
</div>
</div>
</Page>

View File

@@ -1,5 +1,8 @@
<script lang="ts" setup>
import type { OnActionClickParams, VxeTableGridOptions } from '#/adapter/vxe-table';
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { InfraCodegenApi } from '#/api/infra/codegen';
import type { InfraDataSourceConfigApi } from '#/api/infra/data-source-config';
@@ -11,7 +14,12 @@ import { Plus } from '@vben/icons';
import { Button, message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { deleteCodegenTable, downloadCodegen, getCodegenTablePage, syncCodegenFromDB } from '#/api/infra/codegen';
import {
deleteCodegenTable,
downloadCodegen,
getCodegenTablePage,
syncCodegenFromDB,
} from '#/api/infra/codegen';
import { getDataSourceConfigList } from '#/api/infra/data-source-config';
import { $t } from '#/locales';
import { ref } from 'vue';
@@ -21,11 +29,15 @@ import { useGridColumns, useGridFormSchema } from './data';
import { useRouter } from 'vue-router';
const router = useRouter();
const dataSourceConfigList = ref<InfraDataSourceConfigApi.InfraDataSourceConfig[]>([]);
const dataSourceConfigList = ref<InfraDataSourceConfigApi.DataSourceConfig[]>(
[],
);
/** 获取数据源名称 */
const getDataSourceConfigName = (dataSourceConfigId: number) => {
return dataSourceConfigList.value.find((item) => item.id === dataSourceConfigId)?.name;
return dataSourceConfigList.value.find(
(item) => item.id === dataSourceConfigId,
)?.name;
};
const [ImportModal, importModalApi] = useVbenModal({
@@ -122,7 +134,10 @@ async function onGenerate(row: InfraCodegenApi.CodegenTable) {
}
/** 表格操作按钮的回调函数 */
function onActionClick({ code, row }: OnActionClickParams<InfraCodegenApi.CodegenTable>) {
function onActionClick({
code,
row,
}: OnActionClickParams<InfraCodegenApi.CodegenTable>) {
switch (code) {
case 'edit': {
onEdit(row);
@@ -190,16 +205,29 @@ initDataSourceConfig();
</script>
<template>
<Page auto-content-height>
<DocAlert title="代码生成(单表)" url="https://doc.iocoder.cn/new-feature/" />
<DocAlert title="代码生成(表)" url="https://doc.iocoder.cn/new-feature/tree/" />
<DocAlert title="代码生成(主子表)" url="https://doc.iocoder.cn/new-feature/master-sub/" />
<DocAlert
title="代码生成(表)"
url="https://doc.iocoder.cn/new-feature/"
/>
<DocAlert
title="代码生成(树表)"
url="https://doc.iocoder.cn/new-feature/tree/"
/>
<DocAlert
title="代码生成(主子表)"
url="https://doc.iocoder.cn/new-feature/master-sub/"
/>
<DocAlert title="单元测试" url="https://doc.iocoder.cn/unit-test/" />
<ImportModal @success="onRefresh" />
<PreviewModal />
<Grid table-title="代码生成列表">
<template #toolbar-tools>
<Button type="primary" @click="onImport" v-access:code="['infra:codegen:create']">
<Button
type="primary"
@click="onImport"
v-access:code="['infra:codegen:create']"
>
<Plus class="size-5" />
导入
</Button>

View File

@@ -55,7 +55,7 @@ defineExpose({
});
/** 初始化 */
const dictTypeOptions = ref<SystemDictTypeApi.SystemDictType[]>([]); // 字典类型选项
const dictTypeOptions = ref<SystemDictTypeApi.DictType[]>([]); // 字典类型选项
onMounted(async () => {
dictTypeOptions.value = await getSimpleDictTypeList();
});
@@ -71,7 +71,11 @@ onMounted(async () => {
<!-- Java 类型 -->
<template #javaType="{ row, column }">
<Select v-model:value="row.javaType" style="width: 100%">
<Select.Option v-for="option in column.params.options" :key="option.value" :value="option.value">
<Select.Option
v-for="option in column.params.options"
:key="option.value"
:value="option.value"
>
{{ option.label }}
</Select.Option>
</Select>
@@ -101,7 +105,11 @@ onMounted(async () => {
<!-- 查询方式 -->
<template #listOperationCondition="{ row, column }">
<Select v-model:value="row.listOperationCondition" class="w-full">
<Select.Option v-for="option in column.params.options" :key="option.value" :value="option.value">
<Select.Option
v-for="option in column.params.options"
:key="option.value"
:value="option.value"
>
{{ option.label }}
</Select.Option>
</Select>
@@ -115,7 +123,11 @@ onMounted(async () => {
<!-- 显示类型 -->
<template #htmlType="{ row, column }">
<Select v-model:value="row.htmlType" class="w-full">
<Select.Option v-for="option in column.params.options" :key="option.value" :value="option.value">
<Select.Option
v-for="option in column.params.options"
:key="option.value"
:value="option.value"
>
{{ option.label }}
</Select.Option>
</Select>
@@ -123,8 +135,17 @@ onMounted(async () => {
<!-- 字典类型 -->
<template #dictType="{ row }">
<Select v-model:value="row.dictType" class="w-full" allow-clear show-search>
<Select.Option v-for="option in dictTypeOptions" :key="option.type" :value="option.type">
<Select
v-model:value="row.dictType"
class="w-full"
allow-clear
show-search
>
<Select.Option
v-for="option in dictTypeOptions"
:key="option.type"
:value="option.type"
>
{{ option.name }}
</Select.Option>
</Select>

View File

@@ -1,12 +1,13 @@
<script lang="ts" setup>
import type { InfraCodegenApi } from '#/api/infra/codegen';
import { computed, ref, watch } from 'vue';
import { isEmpty } from '@vben/utils';
import { useVbenForm } from '#/adapter/form';
import { getCodegenTableList } from '#/api/infra/codegen';
import { InfraCodegenTemplateTypeEnum } from '#/utils/constants';
import { computed, ref, watch } from 'vue';
import { isEmpty } from '@vben/utils';
import {
useGenerationInfoBaseFormSchema,
@@ -23,8 +24,12 @@ const tables = ref<InfraCodegenApi.CodegenTable[]>([]);
/** 计算当前模板类型 */
const currentTemplateType = ref<number>();
const isTreeTable = computed(() => currentTemplateType.value === InfraCodegenTemplateTypeEnum.TREE);
const isSubTable = computed(() => currentTemplateType.value === InfraCodegenTemplateTypeEnum.SUB);
const isTreeTable = computed(
() => currentTemplateType.value === InfraCodegenTemplateTypeEnum.TREE,
);
const isSubTable = computed(
() => currentTemplateType.value === InfraCodegenTemplateTypeEnum.SUB,
);
/** 基础表单实例 */
const [BaseForm, baseFormApi] = useVbenForm({
@@ -34,7 +39,10 @@ const [BaseForm, baseFormApi] = useVbenForm({
schema: useGenerationInfoBaseFormSchema(),
handleValuesChange: (values) => {
// 监听模板类型变化
if (values.templateType !== undefined && values.templateType !== currentTemplateType.value) {
if (
values.templateType !== undefined &&
values.templateType !== currentTemplateType.value
) {
currentTemplateType.value = values.templateType;
}
},

View File

@@ -2,16 +2,19 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { InfraCodegenApi } from '#/api/infra/codegen';
import { reactive } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { createCodegenList, getSchemaTableList } from '#/api/infra/codegen';
import { reactive } from 'vue';
import { $t } from '@vben/locales';
import { useImportTableColumns, useImportTableFormSchema } from '#/views/infra/codegen/data';
import {
useImportTableColumns,
useImportTableFormSchema,
} from '#/views/infra/codegen/data';
/** 定义组件事件 */
const emit = defineEmits<{
@@ -63,7 +66,11 @@ const [Grid] = useVbenVxeGrid({
},
} as VxeTableGridOptions<InfraCodegenApi.DatabaseTable>,
gridEvents: {
checkboxChange: ({ records }: { records: InfraCodegenApi.DatabaseTable[] }) => {
checkboxChange: ({
records,
}: {
records: InfraCodegenApi.DatabaseTable[];
}) => {
formData.tableNames = records.map((item) => item.name);
},
},

View File

@@ -2,14 +2,13 @@
// TODO @芋艿待定vben2.0 有 CodeEditor不确定官方后续会不会迁移
import type { InfraCodegenApi } from '#/api/infra/codegen';
import { useVbenModal } from '@vben/common-ui';
import { Copy } from '@vben/icons';
import { Button, DirectoryTree, message, Tabs } from 'ant-design-vue';
import { previewCodegen } from '#/api/infra/codegen';
import { h, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { Copy } from '@vben/icons';
import { useClipboard } from '@vueuse/core';
import { Button, DirectoryTree, message, Tabs } from 'ant-design-vue';
import hljs from 'highlight.js/lib/core';
import java from 'highlight.js/lib/languages/java';
import javascript from 'highlight.js/lib/languages/javascript';
@@ -17,6 +16,8 @@ import sql from 'highlight.js/lib/languages/sql';
import typescript from 'highlight.js/lib/languages/typescript';
import xml from 'highlight.js/lib/languages/xml';
import { previewCodegen } from '#/api/infra/codegen';
/** 注册代码高亮语言 */
hljs.registerLanguage('java', java);
hljs.registerLanguage('xml', xml);
@@ -72,7 +73,9 @@ const removeCodeMapKey = (targetKey: any) => {
/** 复制代码 */
const copyCode = async () => {
const { copy } = useClipboard();
const file = previewFiles.value.find((item) => item.filePath === activeKey.value);
const file = previewFiles.value.find(
(item) => item.filePath === activeKey.value,
);
if (file) {
await copy(file.code);
message.success('复制成功');
@@ -123,7 +126,18 @@ const handleFiles = (data: InfraCodegenApi.CodegenPreview[]): FileNode[] => {
let packagePath = '';
while (cursor < paths.length) {
const nextPath = paths[cursor] || '';
if (['controller', 'convert', 'dal', 'dataobject', 'enums', 'mysql', 'service', 'vo'].includes(nextPath)) {
if (
[
'controller',
'convert',
'dal',
'dataobject',
'enums',
'mysql',
'service',
'vo',
].includes(nextPath)
) {
break;
}
packagePath = packagePath ? `${packagePath}.${nextPath}` : nextPath;
@@ -213,7 +227,9 @@ const [Modal, modalApi] = useVbenModal({
<Modal title="代码预览">
<div class="flex h-full" v-loading="loading">
<!-- 文件树 -->
<div class="h-full w-1/3 overflow-auto border-r border-gray-200 pr-4 dark:border-gray-700">
<div
class="h-full w-1/3 overflow-auto border-r border-gray-200 pr-4 dark:border-gray-700"
>
<DirectoryTree
v-if="fileTree.length > 0"
default-expand-all
@@ -224,15 +240,31 @@ const [Modal, modalApi] = useVbenModal({
</div>
<!-- 代码预览 -->
<div class="h-full w-2/3 overflow-auto pl-4">
<Tabs v-model:active-key="activeKey" hide-add type="editable-card" @edit="removeCodeMapKey">
<Tabs.TabPane v-for="key in codeMap.keys()" :key="key" :tab="key.split('/').pop()">
<div class="h-full rounded-md bg-gray-50 !p-0 text-gray-800 dark:bg-gray-800 dark:text-gray-200">
<Tabs
v-model:active-key="activeKey"
hide-add
type="editable-card"
@edit="removeCodeMapKey"
>
<Tabs.TabPane
v-for="key in codeMap.keys()"
:key="key"
:tab="key.split('/').pop()"
>
<div
class="h-full rounded-md bg-gray-50 !p-0 text-gray-800 dark:bg-gray-800 dark:text-gray-200"
>
<!-- eslint-disable-next-line vue/no-v-html -->
<code v-html="codeMap.get(activeKey)" class="code-highlight"></code>
<code
v-html="codeMap.get(activeKey)"
class="code-highlight"
></code>
</div>
</Tabs.TabPane>
<template #rightExtra>
<Button type="primary" ghost @click="copyCode" :icon="h(Copy)"> 复制代码 </Button>
<Button type="primary" ghost @click="copyCode" :icon="h(Copy)">
复制代码
</Button>
</template>
</Tabs>
</div>

View File

@@ -2,10 +2,11 @@ import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { InfraConfigApi } from '#/api/infra/config';
import { DICT_TYPE, getDictOptions } from '#/utils/dict';
import { getRangePickerDefaultProps } from '#/utils/date';
import { useAccess } from '@vben/access';
import { getRangePickerDefaultProps } from '#/utils/date';
import { DICT_TYPE, getDictOptions } from '#/utils/dict';
const { hasAccessByCodes } = useAccess();
/** 新增/修改的表单 */
@@ -73,7 +74,7 @@ export function useFormSchema(): VbenFormSchema[] {
component: 'Textarea',
componentProps: {
placeholder: '请输入备注',
}
},
},
];
}
@@ -88,7 +89,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
componentProps: {
placeholder: '请输入参数名称',
clearable: true,
}
},
},
{
fieldName: 'key',
@@ -97,7 +98,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
componentProps: {
placeholder: '请输入参数键名',
clearable: true,
}
},
},
{
fieldName: 'type',
@@ -122,7 +123,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
export function useGridColumns<T = InfraConfigApi.InfraConfig>(
export function useGridColumns<T = InfraConfigApi.Config>(
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
return [

View File

@@ -1,18 +1,23 @@
<script lang="ts" setup>
import type { OnActionClickParams, VxeTableGridOptions } from '#/adapter/vxe-table';
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { InfraConfigApi } from '#/api/infra/config';
import { Page, useVbenModal } from '@vben/common-ui';
import { Button, message } from 'ant-design-vue';
import { Plus, Download } from '@vben/icons';
import Form from './modules/form.vue';
import { Download, Plus } from '@vben/icons';
import { Button, message } from 'ant-design-vue';
import { $t } from '#/locales';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getConfigPage, deleteConfig, exportConfig } from '#/api/infra/config';
import { useGridColumns, useGridFormSchema } from './data';
import { deleteConfig, exportConfig, getConfigPage } from '#/api/infra/config';
import { $t } from '#/locales';
import { downloadByData } from '#/utils/download';
import { useGridColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Form,
destroyOnClose: true,
@@ -35,12 +40,12 @@ function onCreate() {
}
/** 编辑参数 */
function onEdit(row: InfraConfigApi.InfraConfig) {
function onEdit(row: InfraConfigApi.Config) {
formModalApi.setData(row).open();
}
/** 删除参数 */
async function onDelete(row: InfraConfigApi.InfraConfig) {
async function onDelete(row: InfraConfigApi.Config) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.name]),
duration: 0,
@@ -53,7 +58,7 @@ async function onDelete(row: InfraConfigApi.InfraConfig) {
key: 'action_process_msg',
});
onRefresh();
} catch (error) {
} catch {
hideLoading();
}
}
@@ -62,22 +67,22 @@ async function onDelete(row: InfraConfigApi.InfraConfig) {
function onActionClick({
code,
row,
}: OnActionClickParams<InfraConfigApi.InfraConfig>) {
}: OnActionClickParams<InfraConfigApi.Config>) {
switch (code) {
case 'edit': {
onEdit(row);
break;
}
case 'delete': {
onDelete(row);
break;
}
case 'edit': {
onEdit(row);
break;
}
}
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema()
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(onActionClick),
@@ -101,7 +106,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
refresh: { code: 'query' },
search: true,
},
} as VxeTableGridOptions<InfraConfigApi.InfraConfig>,
} as VxeTableGridOptions<InfraConfigApi.Config>,
});
</script>
@@ -110,11 +115,20 @@ const [Grid, gridApi] = useVbenVxeGrid({
<FormModal @success="onRefresh" />
<Grid table-title="参数列表">
<template #toolbar-tools>
<Button type="primary" @click="onCreate" v-access:code="['infra:config:create']">
<Button
type="primary"
@click="onCreate"
v-access:code="['infra:config:create']"
>
<Plus class="size-5" />
{{ $t('ui.actionTitle.create', ['参数']) }}
</Button>
<Button type="primary" class="ml-2" @click="onExport" v-access:code="['infra:config:export']">
<Button
type="primary"
class="ml-2"
@click="onExport"
v-access:code="['infra:config:export']"
>
<Download class="size-5" />
{{ $t('ui.actionTitle.export') }}
</Button>

View File

@@ -1,18 +1,20 @@
<script lang="ts" setup>
import type { InfraConfigApi } from '#/api/infra/config';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { $t } from '#/locales';
import { computed, ref } from 'vue';
import { useVbenForm } from '#/adapter/form';
import { createConfig, updateConfig, getConfig } from '#/api/infra/config';
import { createConfig, getConfig, updateConfig } from '#/api/infra/config';
import { $t } from '#/locales';
import { useFormSchema } from '../data';
const emit = defineEmits(['success']);
const formData = ref<InfraConfigApi.InfraConfig>();
const formData = ref<InfraConfigApi.Config>();
const getTitle = computed(() => {
return formData.value?.id
? $t('ui.actionTitle.edit', ['参数'])
@@ -33,7 +35,7 @@ const [Modal, modalApi] = useVbenModal({
}
modalApi.lock();
// 提交表单
const data = (await formApi.getValues()) as InfraConfigApi.InfraConfig;
const data = (await formApi.getValues()) as InfraConfigApi.Config;
try {
await (formData.value?.id ? updateConfig(data) : createConfig(data));
// 关闭并提示
@@ -52,7 +54,7 @@ const [Modal, modalApi] = useVbenModal({
return;
}
// 加载数据
const data = modalApi.getData<InfraConfigApi.InfraConfig>();
const data = modalApi.getData<InfraConfigApi.Config>();
if (!data || !data.id) {
return;
}

View File

@@ -58,7 +58,7 @@ export function useFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
export function useGridColumns<T = InfraDataSourceConfigApi.InfraDataSourceConfig>(
export function useGridColumns<T = InfraDataSourceConfigApi.DataSourceConfig>(
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
return [

View File

@@ -1,18 +1,27 @@
<script lang="ts" setup>
import type { OnActionClickParams, VxeTableGridOptions } from '#/adapter/vxe-table';
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { InfraDataSourceConfigApi } from '#/api/infra/data-source-config';
import { Page, useVbenModal } from '@vben/common-ui';
import { Button, message } from 'ant-design-vue';
import { Plus, } from '@vben/icons';
import Form from './modules/form.vue';
import { $t } from '#/locales';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getDataSourceConfigList, deleteDataSourceConfig } from '#/api/infra/data-source-config';
import { useGridColumns } from './data';
import { onMounted } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Plus } from '@vben/icons';
import { Button, message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteDataSourceConfig,
getDataSourceConfigList,
} from '#/api/infra/data-source-config';
import { $t } from '#/locales';
import { useGridColumns } from './data';
import Form from './modules/form.vue';
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Form,
destroyOnClose: true,
@@ -24,12 +33,12 @@ function onCreate() {
}
/** 编辑数据源 */
function onEdit(row: InfraDataSourceConfigApi.InfraDataSourceConfig) {
function onEdit(row: InfraDataSourceConfigApi.DataSourceConfig) {
formModalApi.setData(row).open();
}
/** 删除数据源 */
async function onDelete(row: InfraDataSourceConfigApi.InfraDataSourceConfig) {
async function onDelete(row: InfraDataSourceConfigApi.DataSourceConfig) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.name]),
duration: 0,
@@ -42,7 +51,7 @@ async function onDelete(row: InfraDataSourceConfigApi.InfraDataSourceConfig) {
key: 'action_process_msg',
});
await handleLoadData();
} catch (error) {
} catch {
hideLoading();
}
}
@@ -51,16 +60,16 @@ async function onDelete(row: InfraDataSourceConfigApi.InfraDataSourceConfig) {
function onActionClick({
code,
row,
}: OnActionClickParams<InfraDataSourceConfigApi.InfraDataSourceConfig>) {
}: OnActionClickParams<InfraDataSourceConfigApi.DataSourceConfig>) {
switch (code) {
case 'edit': {
onEdit(row);
break;
}
case 'delete': {
onDelete(row);
break;
}
case 'edit': {
onEdit(row);
break;
}
}
}
@@ -77,10 +86,10 @@ const [Grid, gridApi] = useVbenVxeGrid({
},
proxyConfig: {
ajax: {
query: getDataSourceConfigList
}
query: getDataSourceConfigList,
},
},
} as VxeTableGridOptions<InfraDataSourceConfigApi.InfraDataSourceConfig>,
} as VxeTableGridOptions<InfraDataSourceConfigApi.DataSourceConfig>,
});
/** 加载数据 */
@@ -104,11 +113,15 @@ onMounted(() => {
<FormModal @success="onRefresh" />
<Grid table-title="数据源列表">
<template #toolbar-tools>
<Button type="primary" @click="onCreate" v-access:code="['infra:data-source-config:create']">
<Button
type="primary"
@click="onCreate"
v-access:code="['infra:data-source-config:create']"
>
<Plus class="size-5" />
{{ $t('ui.actionTitle.create', ['数据源']) }}
</Button>
</template>
</Grid>
</Page>
</template>
</template>

View File

@@ -1,18 +1,24 @@
<script lang="ts" setup>
import type { InfraDataSourceConfigApi } from '#/api/infra/data-source-config';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { $t } from '#/locales';
import { computed, ref } from 'vue';
import { useVbenForm } from '#/adapter/form';
import { createDataSourceConfig, updateDataSourceConfig, getDataSourceConfig } from '#/api/infra/data-source-config';
import {
createDataSourceConfig,
getDataSourceConfig,
updateDataSourceConfig,
} from '#/api/infra/data-source-config';
import { $t } from '#/locales';
import { useFormSchema } from '../data';
const emit = defineEmits(['success']);
const formData = ref<InfraDataSourceConfigApi.InfraDataSourceConfig>();
const formData = ref<InfraDataSourceConfigApi.DataSourceConfig>();
const getTitle = computed(() => {
return formData.value?.id
? $t('ui.actionTitle.edit', ['数据源'])
@@ -33,9 +39,12 @@ const [Modal, modalApi] = useVbenModal({
}
modalApi.lock();
// 提交表单
const data = (await formApi.getValues()) as InfraDataSourceConfigApi.InfraDataSourceConfig;
const data =
(await formApi.getValues()) as InfraDataSourceConfigApi.DataSourceConfig;
try {
await (formData.value?.id ? updateDataSourceConfig(data) : createDataSourceConfig(data));
await (formData.value?.id
? updateDataSourceConfig(data)
: createDataSourceConfig(data));
// 关闭并提示
await modalApi.close();
emit('success');
@@ -52,7 +61,7 @@ const [Modal, modalApi] = useVbenModal({
return;
}
// 加载数据
const data = modalApi.getData<InfraDataSourceConfigApi.InfraDataSourceConfig>();
const data = modalApi.getData<InfraDataSourceConfigApi.DataSourceConfig>();
if (!data || !data.id) {
return;
}

View File

@@ -1,13 +1,14 @@
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn } from '#/adapter/vxe-table';
import type { Demo01ContactApi } from '#/api/infra/demo/demo01';
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
import { useAccess } from '@vben/access';
import { getRangePickerDefaultProps } from '#/utils/date';
import { DICT_TYPE, getDictOptions } from '#/utils/dict';
import { useAccess } from '@vben/access';
const { hasAccessByCodes } = useAccess();
/** 新增/修改的表单 */

View File

@@ -1,19 +1,28 @@
<script lang="ts" setup>
import type { OnActionClickParams, VxeTableGridOptions } from '#/adapter/vxe-table';
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { Demo01ContactApi } from '#/api/infra/demo/demo01';
import Form from './modules/form.vue';
import { h } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Download, Plus } from '@vben/icons';
import { Button, message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { deleteDemo01Contact, exportDemo01Contact, getDemo01ContactPage } from '#/api/infra/demo/demo01';
import {
deleteDemo01Contact,
exportDemo01Contact,
getDemo01ContactPage,
} from '#/api/infra/demo/demo01';
import { $t } from '#/locales';
import { downloadByData } from '#/utils/download';
import { h } from 'vue';
import { useGridColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Form,
@@ -61,16 +70,19 @@ async function onExport() {
}
/** 表格操作按钮的回调函数 */
function onActionClick({ code, row }: OnActionClickParams<Demo01ContactApi.Demo01Contact>) {
function onActionClick({
code,
row,
}: OnActionClickParams<Demo01ContactApi.Demo01Contact>) {
switch (code) {
case 'edit': {
onEdit(row);
break;
}
case 'delete': {
onDelete(row);
break;
}
case 'edit': {
onEdit(row);
break;
}
}
}
@@ -113,7 +125,12 @@ const [Grid, gridApi] = useVbenVxeGrid({
<Grid table-title="示例联系人列表">
<template #toolbar-tools>
<Button :icon="h(Plus)" type="primary" @click="onCreate" v-access:code="['infra:demo01-contact:create']">
<Button
:icon="h(Plus)"
type="primary"
@click="onCreate"
v-access:code="['infra:demo01-contact:create']"
>
{{ $t('ui.actionTitle.create', ['示例联系人']) }}
</Button>
<Button

View File

@@ -1,20 +1,28 @@
<script lang="ts" setup>
import type { Demo01ContactApi } from '#/api/infra/demo/demo01';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { createDemo01Contact, getDemo01Contact, updateDemo01Contact } from '#/api/infra/demo/demo01';
import {
createDemo01Contact,
getDemo01Contact,
updateDemo01Contact,
} from '#/api/infra/demo/demo01';
import { $t } from '#/locales';
import { computed, ref } from 'vue';
import { useFormSchema } from '../data';
const emit = defineEmits(['success']);
const formData = ref<Demo01ContactApi.Demo01Contact>();
const getTitle = computed(() => {
return formData.value?.id ? $t('ui.actionTitle.edit', ['示例联系人']) : $t('ui.actionTitle.create', ['示例联系人']);
return formData.value?.id
? $t('ui.actionTitle.edit', ['示例联系人'])
: $t('ui.actionTitle.create', ['示例联系人']);
});
const [Form, formApi] = useVbenForm({
@@ -33,7 +41,9 @@ const [Modal, modalApi] = useVbenModal({
// 提交表单
const data = (await formApi.getValues()) as Demo01ContactApi.Demo01Contact;
try {
await (formData.value?.id ? updateDemo01Contact(data) : createDemo01Contact(data));
await (formData.value?.id
? updateDemo01Contact(data)
: createDemo01Contact(data));
// 关闭并提示
await modalApi.close();
emit('success');

View File

@@ -1,14 +1,15 @@
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn } from '#/adapter/vxe-table';
import type { Demo02CategoryApi } from '#/api/infra/demo/demo02';
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
import { useAccess } from '@vben/access';
import { getDemo02CategoryList } from '#/api/infra/demo/demo02';
import { getRangePickerDefaultProps } from '#/utils/date';
import { handleTree } from '#/utils/tree';
import { useAccess } from '@vben/access';
const { hasAccessByCodes } = useAccess();
/** 新增/修改的表单 */

View File

@@ -1,19 +1,28 @@
<script lang="ts" setup>
import type { OnActionClickParams, VxeTableGridOptions } from '#/adapter/vxe-table';
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { Demo02CategoryApi } from '#/api/infra/demo/demo02';
import Form from './modules/form.vue';
import { h, ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Download, Plus } from '@vben/icons';
import { Button, message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { deleteDemo02Category, exportDemo02Category, getDemo02CategoryList } from '#/api/infra/demo/demo02';
import {
deleteDemo02Category,
exportDemo02Category,
getDemo02CategoryList,
} from '#/api/infra/demo/demo02';
import { $t } from '#/locales';
import { downloadByData } from '#/utils/download';
import { h, ref } from 'vue';
import { useGridColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Form,
@@ -73,7 +82,10 @@ async function onDelete(row: Demo02CategoryApi.Demo02Category) {
}
/** 表格操作按钮的回调函数 */
function onActionClick({ code, row }: OnActionClickParams<Demo02CategoryApi.Demo02Category>) {
function onActionClick({
code,
row,
}: OnActionClickParams<Demo02CategoryApi.Demo02Category>) {
switch (code) {
case 'append': {
onAppend(row);
@@ -135,7 +147,12 @@ const [Grid, gridApi] = useVbenVxeGrid({
<Button @click="toggleExpand" class="mr-2">
{{ isExpanded ? '收缩' : '展开' }}
</Button>
<Button :icon="h(Plus)" type="primary" @click="onCreate" v-access:code="['infra:demo02-category:create']">
<Button
:icon="h(Plus)"
type="primary"
@click="onCreate"
v-access:code="['infra:demo02-category:create']"
>
{{ $t('ui.actionTitle.create', ['示例分类']) }}
</Button>
<Button

View File

@@ -1,13 +1,19 @@
<script lang="ts" setup>
import type { Demo02CategoryApi } from '#/api/infra/demo/demo02';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { createDemo02Category, getDemo02Category, updateDemo02Category } from '#/api/infra/demo/demo02';
import {
createDemo02Category,
getDemo02Category,
updateDemo02Category,
} from '#/api/infra/demo/demo02';
import { $t } from '#/locales';
import { computed, ref } from 'vue';
import { useFormSchema } from '../data';
@@ -19,7 +25,9 @@ const getTitle = computed(() => {
if (formData.value?.id) {
return $t('ui.actionTitle.edit', ['示例分类']);
}
return parentId.value ? $t('ui.actionTitle.create', ['下级示例分类']) : $t('ui.actionTitle.create', ['示例分类']);
return parentId.value
? $t('ui.actionTitle.create', ['下级示例分类'])
: $t('ui.actionTitle.create', ['示例分类']);
});
const [Form, formApi] = useVbenForm({
@@ -36,9 +44,12 @@ const [Modal, modalApi] = useVbenModal({
}
modalApi.lock();
// 提交表单
const data = (await formApi.getValues()) as Demo02CategoryApi.Demo02Category;
const data =
(await formApi.getValues()) as Demo02CategoryApi.Demo02Category;
try {
await (formData.value?.id ? updateDemo02Category(data) : createDemo02Category(data));
await (formData.value?.id
? updateDemo02Category(data)
: createDemo02Category(data));
// 关闭并提示
await modalApi.close();
emit('success');

View File

@@ -1,13 +1,14 @@
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn } from '#/adapter/vxe-table';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/erp';
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
import { useAccess } from '@vben/access';
import { getRangePickerDefaultProps } from '#/utils/date';
import { DICT_TYPE, getDictOptions } from '#/utils/dict';
import { useAccess } from '@vben/access';
const { hasAccessByCodes } = useAccess();
/** 新增/修改的表单 */

View File

@@ -1,21 +1,30 @@
<script lang="ts" setup>
import type { OnActionClickParams, VxeTableGridOptions } from '#/adapter/vxe-table';
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/erp';
import Demo03CourseList from './modules/demo03-course-list.vue';
import Demo03GradeList from './modules/demo03-grade-list.vue';
import Form from './modules/form.vue';
import { h, ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Download, Plus } from '@vben/icons';
import { Button, message, Tabs } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { deleteDemo03Student, exportDemo03Student, getDemo03StudentPage } from '#/api/infra/demo/demo03/erp';
import {
deleteDemo03Student,
exportDemo03Student,
getDemo03StudentPage,
} from '#/api/infra/demo/demo03/erp';
import { $t } from '#/locales';
import { downloadByData } from '#/utils/download';
import { h, ref } from 'vue';
import { useGridColumns, useGridFormSchema } from './data';
import Demo03CourseList from './modules/demo03-course-list.vue';
import Demo03GradeList from './modules/demo03-grade-list.vue';
import Form from './modules/form.vue';
/** 子表的列表 */
const subTabsName = ref('demo03Course');
@@ -67,16 +76,19 @@ async function onExport() {
}
/** 表格操作按钮的回调函数 */
function onActionClick({ code, row }: OnActionClickParams<Demo03StudentApi.Demo03Student>) {
function onActionClick({
code,
row,
}: OnActionClickParams<Demo03StudentApi.Demo03Student>) {
switch (code) {
case 'edit': {
onEdit(row);
break;
}
case 'delete': {
onDelete(row);
break;
}
case 'edit': {
onEdit(row);
break;
}
}
}
@@ -126,7 +138,12 @@ const [Grid, gridApi] = useVbenVxeGrid({
<div>
<Grid table-title="学生列表">
<template #toolbar-tools>
<Button :icon="h(Plus)" type="primary" @click="onCreate" v-access:code="['infra:demo03-student:create']">
<Button
:icon="h(Plus)"
type="primary"
@click="onCreate"
v-access:code="['infra:demo03-student:create']"
>
{{ $t('ui.actionTitle.create', ['学生']) }}
</Button>
<Button

View File

@@ -1,20 +1,28 @@
<script lang="ts" setup>
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/erp';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { createDemo03Course, getDemo03Course, updateDemo03Course } from '#/api/infra/demo/demo03/erp';
import {
createDemo03Course,
getDemo03Course,
updateDemo03Course,
} from '#/api/infra/demo/demo03/erp';
import { $t } from '#/locales';
import { computed, ref } from 'vue';
import { useDemo03CourseFormSchema } from '../data';
const emit = defineEmits(['success']);
const formData = ref<Demo03StudentApi.Demo03Course>();
const getTitle = computed(() => {
return formData.value?.id ? $t('ui.actionTitle.edit', ['学生课程']) : $t('ui.actionTitle.create', ['学生课程']);
return formData.value?.id
? $t('ui.actionTitle.edit', ['学生课程'])
: $t('ui.actionTitle.create', ['学生课程']);
});
const [Form, formApi] = useVbenForm({
@@ -35,7 +43,9 @@ const [Modal, modalApi] = useVbenModal({
const data = (await formApi.getValues()) as Demo03StudentApi.Demo03Course;
data.studentId = formData.value?.studentId;
try {
await (formData.value?.id ? updateDemo03Course(data) : createDemo03Course(data));
await (formData.value?.id
? updateDemo03Course(data)
: createDemo03Course(data));
// 关闭并提示
await modalApi.close();
emit('success');

View File

@@ -1,17 +1,28 @@
<script lang="ts" setup>
import type { OnActionClickParams, VxeTableGridOptions } from '#/adapter/vxe-table';
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/erp';
import { h, nextTick, watch } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { Plus } from '@vben/icons';
import { Button, message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { deleteDemo03Course, getDemo03CoursePage } from '#/api/infra/demo/demo03/erp';
import {
deleteDemo03Course,
getDemo03CoursePage,
} from '#/api/infra/demo/demo03/erp';
import { $t } from '#/locales';
import { h, nextTick, watch } from 'vue';
import { useDemo03CourseGridColumns, useDemo03CourseGridFormSchema } from '../data';
import {
useDemo03CourseGridColumns,
useDemo03CourseGridFormSchema,
} from '../data';
import Demo03CourseForm from './demo03-course-form.vue';
const props = defineProps<{
@@ -57,16 +68,19 @@ async function onDelete(row: Demo03StudentApi.Demo03Course) {
}
/** 表格操作按钮的回调函数 */
function onActionClick({ code, row }: OnActionClickParams<Demo03StudentApi.Demo03Course>) {
function onActionClick({
code,
row,
}: OnActionClickParams<Demo03StudentApi.Demo03Course>) {
switch (code) {
case 'edit': {
onEdit(row);
break;
}
case 'delete': {
onDelete(row);
break;
}
case 'edit': {
onEdit(row);
break;
}
}
}
@@ -129,7 +143,12 @@ watch(
<FormModal @success="onRefresh" />
<Grid table-title="学生课程列表">
<template #toolbar-tools>
<Button :icon="h(Plus)" type="primary" @click="onCreate" v-access:code="['infra:demo03-student:create']">
<Button
:icon="h(Plus)"
type="primary"
@click="onCreate"
v-access:code="['infra:demo03-student:create']"
>
{{ $t('ui.actionTitle.create', ['学生课程']) }}
</Button>
</template>

View File

@@ -1,20 +1,28 @@
<script lang="ts" setup>
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/erp';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { createDemo03Grade, getDemo03Grade, updateDemo03Grade } from '#/api/infra/demo/demo03/erp';
import {
createDemo03Grade,
getDemo03Grade,
updateDemo03Grade,
} from '#/api/infra/demo/demo03/erp';
import { $t } from '#/locales';
import { computed, ref } from 'vue';
import { useDemo03GradeFormSchema } from '../data';
const emit = defineEmits(['success']);
const formData = ref<Demo03StudentApi.Demo03Grade>();
const getTitle = computed(() => {
return formData.value?.id ? $t('ui.actionTitle.edit', ['学生班级']) : $t('ui.actionTitle.create', ['学生班级']);
return formData.value?.id
? $t('ui.actionTitle.edit', ['学生班级'])
: $t('ui.actionTitle.create', ['学生班级']);
});
const [Form, formApi] = useVbenForm({
@@ -35,7 +43,9 @@ const [Modal, modalApi] = useVbenModal({
const data = (await formApi.getValues()) as Demo03StudentApi.Demo03Grade;
data.studentId = formData.value?.studentId;
try {
await (formData.value?.id ? updateDemo03Grade(data) : createDemo03Grade(data));
await (formData.value?.id
? updateDemo03Grade(data)
: createDemo03Grade(data));
// 关闭并提示
await modalApi.close();
emit('success');

View File

@@ -1,17 +1,28 @@
<script lang="ts" setup>
import type { OnActionClickParams, VxeTableGridOptions } from '#/adapter/vxe-table';
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/erp';
import { h, nextTick, watch } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { Plus } from '@vben/icons';
import { Button, message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { deleteDemo03Grade, getDemo03GradePage } from '#/api/infra/demo/demo03/erp';
import {
deleteDemo03Grade,
getDemo03GradePage,
} from '#/api/infra/demo/demo03/erp';
import { $t } from '#/locales';
import { h, nextTick, watch } from 'vue';
import { useDemo03GradeGridColumns, useDemo03GradeGridFormSchema } from '../data';
import {
useDemo03GradeGridColumns,
useDemo03GradeGridFormSchema,
} from '../data';
import Demo03GradeForm from './demo03-grade-form.vue';
const props = defineProps<{
@@ -57,16 +68,19 @@ async function onDelete(row: Demo03StudentApi.Demo03Grade) {
}
/** 表格操作按钮的回调函数 */
function onActionClick({ code, row }: OnActionClickParams<Demo03StudentApi.Demo03Grade>) {
function onActionClick({
code,
row,
}: OnActionClickParams<Demo03StudentApi.Demo03Grade>) {
switch (code) {
case 'edit': {
onEdit(row);
break;
}
case 'delete': {
onDelete(row);
break;
}
case 'edit': {
onEdit(row);
break;
}
}
}
@@ -129,7 +143,12 @@ watch(
<FormModal @success="onRefresh" />
<Grid table-title="学生班级列表">
<template #toolbar-tools>
<Button :icon="h(Plus)" type="primary" @click="onCreate" v-access:code="['infra:demo03-student:create']">
<Button
:icon="h(Plus)"
type="primary"
@click="onCreate"
v-access:code="['infra:demo03-student:create']"
>
{{ $t('ui.actionTitle.create', ['学生班级']) }}
</Button>
</template>

View File

@@ -1,20 +1,28 @@
<script lang="ts" setup>
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/erp';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { createDemo03Student, getDemo03Student, updateDemo03Student } from '#/api/infra/demo/demo03/erp';
import {
createDemo03Student,
getDemo03Student,
updateDemo03Student,
} from '#/api/infra/demo/demo03/erp';
import { $t } from '#/locales';
import { computed, ref } from 'vue';
import { useFormSchema } from '../data';
const emit = defineEmits(['success']);
const formData = ref<Demo03StudentApi.Demo03Student>();
const getTitle = computed(() => {
return formData.value?.id ? $t('ui.actionTitle.edit', ['学生']) : $t('ui.actionTitle.create', ['学生']);
return formData.value?.id
? $t('ui.actionTitle.edit', ['学生'])
: $t('ui.actionTitle.create', ['学生']);
});
const [Form, formApi] = useVbenForm({
@@ -33,7 +41,9 @@ const [Modal, modalApi] = useVbenModal({
// 提交表单
const data = (await formApi.getValues()) as Demo03StudentApi.Demo03Student;
try {
await (formData.value?.id ? updateDemo03Student(data) : createDemo03Student(data));
await (formData.value?.id
? updateDemo03Student(data)
: createDemo03Student(data));
// 关闭并提示
await modalApi.close();
emit('success');

View File

@@ -1,13 +1,14 @@
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn } from '#/adapter/vxe-table';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/inner';
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
import { useAccess } from '@vben/access';
import { getRangePickerDefaultProps } from '#/utils/date';
import { DICT_TYPE, getDictOptions } from '#/utils/dict';
import { useAccess } from '@vben/access';
const { hasAccessByCodes } = useAccess();
/** 新增/修改的表单 */

View File

@@ -1,21 +1,30 @@
<script lang="ts" setup>
import type { OnActionClickParams, VxeTableGridOptions } from '#/adapter/vxe-table';
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/inner';
import Demo03CourseList from './modules/demo03-course-list.vue';
import Demo03GradeList from './modules/demo03-grade-list.vue';
import Form from './modules/form.vue';
import { h, ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Download, Plus } from '@vben/icons';
import { Button, message, Tabs } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { deleteDemo03Student, exportDemo03Student, getDemo03StudentPage } from '#/api/infra/demo/demo03/inner';
import {
deleteDemo03Student,
exportDemo03Student,
getDemo03StudentPage,
} from '#/api/infra/demo/demo03/inner';
import { $t } from '#/locales';
import { downloadByData } from '#/utils/download';
import { h, ref } from 'vue';
import { useGridColumns, useGridFormSchema } from './data';
import Demo03CourseList from './modules/demo03-course-list.vue';
import Demo03GradeList from './modules/demo03-grade-list.vue';
import Form from './modules/form.vue';
/** 子表的列表 */
const subTabsName = ref('demo03Course');
@@ -66,16 +75,19 @@ async function onExport() {
}
/** 表格操作按钮的回调函数 */
function onActionClick({ code, row }: OnActionClickParams<Demo03StudentApi.Demo03Student>) {
function onActionClick({
code,
row,
}: OnActionClickParams<Demo03StudentApi.Demo03Student>) {
switch (code) {
case 'edit': {
onEdit(row);
break;
}
case 'delete': {
onDelete(row);
break;
}
case 'edit': {
onEdit(row);
break;
}
}
}
@@ -129,7 +141,12 @@ const [Grid, gridApi] = useVbenVxeGrid({
</Tabs>
</template>
<template #toolbar-tools>
<Button :icon="h(Plus)" type="primary" @click="onCreate" v-access:code="['infra:demo03-student:create']">
<Button
:icon="h(Plus)"
type="primary"
@click="onCreate"
v-access:code="['infra:demo03-student:create']"
>
{{ $t('ui.actionTitle.create', ['学生']) }}
</Button>
<Button

View File

@@ -2,13 +2,15 @@
import type { OnActionClickParams } from '#/adapter/vxe-table';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/inner';
import { h, nextTick, watch } from 'vue';
import { Plus } from '@vben/icons';
import { Button, Input } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getDemo03CourseListByStudentId } from '#/api/infra/demo/demo03/inner';
import { $t } from '#/locales';
import { h, nextTick, watch } from 'vue';
import { useDemo03CourseGridEditColumns } from '../data';
@@ -17,7 +19,10 @@ const props = defineProps<{
}>();
/** 表格操作按钮的回调函数 */
function onActionClick({ code, row }: OnActionClickParams<Demo03StudentApi.Demo03Course>) {
function onActionClick({
code,
row,
}: OnActionClickParams<Demo03StudentApi.Demo03Course>) {
switch (code) {
case 'delete': {
onDelete(row);
@@ -59,8 +64,10 @@ const onDelete = async (row: Demo03StudentApi.Demo03Course) => {
defineExpose({
getData: (): Demo03StudentApi.Demo03Course[] => {
const data = gridApi.grid.getData() as Demo03StudentApi.Demo03Course[];
const removeRecords = gridApi.grid.getRemoveRecords() as Demo03StudentApi.Demo03Course[];
const insertRecords = gridApi.grid.getInsertRecords() as Demo03StudentApi.Demo03Course[];
const removeRecords =
gridApi.grid.getRemoveRecords() as Demo03StudentApi.Demo03Course[];
const insertRecords =
gridApi.grid.getInsertRecords() as Demo03StudentApi.Demo03Course[];
return data
.filter((row) => !removeRecords.some((removed) => removed.id === row.id))
.concat(insertRecords.map((row: any) => ({ ...row, id: undefined })));
@@ -75,7 +82,9 @@ watch(
return;
}
await nextTick();
await gridApi.grid.loadData(await getDemo03CourseListByStudentId(props.studentId!));
await gridApi.grid.loadData(
await getDemo03CourseListByStudentId(props.studentId!),
);
},
{ immediate: true },
);
@@ -91,7 +100,13 @@ watch(
</template>
</Grid>
<div class="-mt-4 flex justify-center">
<Button :icon="h(Plus)" type="primary" ghost @click="onAdd" v-access:code="['infra:demo03-student:create']">
<Button
:icon="h(Plus)"
type="primary"
ghost
@click="onAdd"
v-access:code="['infra:demo03-student:create']"
>
{{ $t('ui.actionTitle.create', ['学生课程']) }}
</Button>
</div>

View File

@@ -2,9 +2,10 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/inner';
import { nextTick, watch } from 'vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getDemo03CourseListByStudentId } from '#/api/infra/demo/demo03/inner';
import { nextTick, watch } from 'vue';
import { useDemo03CourseGridColumns } from '../data';
@@ -31,7 +32,9 @@ const [Grid, gridApi] = useVbenVxeGrid({
/** 刷新表格 */
const onRefresh = async () => {
await gridApi.grid.loadData(await getDemo03CourseListByStudentId(props.studentId!));
await gridApi.grid.loadData(
await getDemo03CourseListByStudentId(props.studentId!),
);
};
/** 监听主表的关联字段的变化,加载对应的子表数据 */

View File

@@ -1,7 +1,8 @@
<script lang="ts" setup>
import { nextTick, watch } from 'vue';
import { useVbenForm } from '#/adapter/form';
import { getDemo03GradeByStudentId } from '#/api/infra/demo/demo03/inner';
import { nextTick, watch } from 'vue';
import { useDemo03GradeFormSchema } from '../data';

View File

@@ -2,9 +2,10 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/inner';
import { nextTick, watch } from 'vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getDemo03GradeByStudentId } from '#/api/infra/demo/demo03/inner';
import { nextTick, watch } from 'vue';
import { useDemo03GradeGridColumns } from '../data';
@@ -31,7 +32,9 @@ const [Grid, gridApi] = useVbenVxeGrid({
/** 刷新表格 */
const onRefresh = async () => {
await gridApi.grid.loadData([await getDemo03GradeByStudentId(props.studentId!)]);
await gridApi.grid.loadData([
await getDemo03GradeByStudentId(props.studentId!),
]);
};
/** 监听主表的关联字段的变化,加载对应的子表数据 */

View File

@@ -1,13 +1,19 @@
<script lang="ts" setup>
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/inner';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message, Tabs } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { createDemo03Student, getDemo03Student, updateDemo03Student } from '#/api/infra/demo/demo03/inner';
import {
createDemo03Student,
getDemo03Student,
updateDemo03Student,
} from '#/api/infra/demo/demo03/inner';
import { $t } from '#/locales';
import { computed, ref } from 'vue';
import { useFormSchema } from '../data';
import Demo03CourseForm from './demo03-course-form.vue';
@@ -16,7 +22,9 @@ import Demo03GradeForm from './demo03-grade-form.vue';
const emit = defineEmits(['success']);
const formData = ref<Demo03StudentApi.Demo03Student>();
const getTitle = computed(() => {
return formData.value?.id ? $t('ui.actionTitle.edit', ['学生']) : $t('ui.actionTitle.create', ['学生']);
return formData.value?.id
? $t('ui.actionTitle.edit', ['学生'])
: $t('ui.actionTitle.create', ['学生']);
});
/** 子表的表单 */
@@ -49,7 +57,9 @@ const [Modal, modalApi] = useVbenModal({
data.demo03Courses = demo03CourseFormRef.value?.getData();
data.demo03Grade = await demo03GradeFormRef.value?.getValues();
try {
await (formData.value?.id ? updateDemo03Student(data) : createDemo03Student(data));
await (formData.value?.id
? updateDemo03Student(data)
: createDemo03Student(data));
// 关闭并提示
await modalApi.close();
emit('success');
@@ -93,7 +103,10 @@ const [Modal, modalApi] = useVbenModal({
<!-- 子表的表单 -->
<Tabs v-model:active-key="subTabsName">
<Tabs.TabPane key="demo03Course" tab="学生课程" force-render>
<Demo03CourseForm ref="demo03CourseFormRef" :student-id="formData?.id" />
<Demo03CourseForm
ref="demo03CourseFormRef"
:student-id="formData?.id"
/>
</Tabs.TabPane>
<Tabs.TabPane key="demo03Grade" tab="学生班级" force-render>
<Demo03GradeForm ref="demo03GradeFormRef" :student-id="formData?.id" />

View File

@@ -1,13 +1,14 @@
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn } from '#/adapter/vxe-table';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/normal';
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
import { useAccess } from '@vben/access';
import { getRangePickerDefaultProps } from '#/utils/date';
import { DICT_TYPE, getDictOptions } from '#/utils/dict';
import { useAccess } from '@vben/access';
const { hasAccessByCodes } = useAccess();
/** 新增/修改的表单 */

View File

@@ -1,19 +1,28 @@
<script lang="ts" setup>
import type { OnActionClickParams, VxeTableGridOptions } from '#/adapter/vxe-table';
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/normal';
import Form from './modules/form.vue';
import { h } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Download, Plus } from '@vben/icons';
import { Button, message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { deleteDemo03Student, exportDemo03Student, getDemo03StudentPage } from '#/api/infra/demo/demo03/normal';
import {
deleteDemo03Student,
exportDemo03Student,
getDemo03StudentPage,
} from '#/api/infra/demo/demo03/normal';
import { $t } from '#/locales';
import { downloadByData } from '#/utils/download';
import { h } from 'vue';
import { useGridColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Form,
@@ -61,16 +70,19 @@ async function onExport() {
}
/** 表格操作按钮的回调函数 */
function onActionClick({ code, row }: OnActionClickParams<Demo03StudentApi.Demo03Student>) {
function onActionClick({
code,
row,
}: OnActionClickParams<Demo03StudentApi.Demo03Student>) {
switch (code) {
case 'edit': {
onEdit(row);
break;
}
case 'delete': {
onDelete(row);
break;
}
case 'edit': {
onEdit(row);
break;
}
}
}
@@ -113,7 +125,12 @@ const [Grid, gridApi] = useVbenVxeGrid({
<Grid table-title="学生列表">
<template #toolbar-tools>
<Button :icon="h(Plus)" type="primary" @click="onCreate" v-access:code="['infra:demo03-student:create']">
<Button
:icon="h(Plus)"
type="primary"
@click="onCreate"
v-access:code="['infra:demo03-student:create']"
>
{{ $t('ui.actionTitle.create', ['学生']) }}
</Button>
<Button

View File

@@ -2,13 +2,15 @@
import type { OnActionClickParams } from '#/adapter/vxe-table';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/normal';
import { h, nextTick, watch } from 'vue';
import { Plus } from '@vben/icons';
import { Button, Input } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getDemo03CourseListByStudentId } from '#/api/infra/demo/demo03/normal';
import { $t } from '#/locales';
import { h, nextTick, watch } from 'vue';
import { useDemo03CourseGridEditColumns } from '../data';
@@ -17,7 +19,10 @@ const props = defineProps<{
}>();
/** 表格操作按钮的回调函数 */
function onActionClick({ code, row }: OnActionClickParams<Demo03StudentApi.Demo03Course>) {
function onActionClick({
code,
row,
}: OnActionClickParams<Demo03StudentApi.Demo03Course>) {
switch (code) {
case 'delete': {
onDelete(row);
@@ -59,8 +64,10 @@ const onDelete = async (row: Demo03StudentApi.Demo03Course) => {
defineExpose({
getData: (): Demo03StudentApi.Demo03Course[] => {
const data = gridApi.grid.getData() as Demo03StudentApi.Demo03Course[];
const removeRecords = gridApi.grid.getRemoveRecords() as Demo03StudentApi.Demo03Course[];
const insertRecords = gridApi.grid.getInsertRecords() as Demo03StudentApi.Demo03Course[];
const removeRecords =
gridApi.grid.getRemoveRecords() as Demo03StudentApi.Demo03Course[];
const insertRecords =
gridApi.grid.getInsertRecords() as Demo03StudentApi.Demo03Course[];
return data
.filter((row) => !removeRecords.some((removed) => removed.id === row.id))
.concat(insertRecords.map((row: any) => ({ ...row, id: undefined })));
@@ -75,7 +82,9 @@ watch(
return;
}
await nextTick();
await gridApi.grid.loadData(await getDemo03CourseListByStudentId(props.studentId!));
await gridApi.grid.loadData(
await getDemo03CourseListByStudentId(props.studentId!),
);
},
{ immediate: true },
);
@@ -91,7 +100,13 @@ watch(
</template>
</Grid>
<div class="-mt-4 flex justify-center">
<Button :icon="h(Plus)" type="primary" ghost @click="onAdd" v-access:code="['infra:demo03-student:create']">
<Button
:icon="h(Plus)"
type="primary"
ghost
@click="onAdd"
v-access:code="['infra:demo03-student:create']"
>
{{ $t('ui.actionTitle.create', ['学生课程']) }}
</Button>
</div>

View File

@@ -1,7 +1,8 @@
<script lang="ts" setup>
import { nextTick, watch } from 'vue';
import { useVbenForm } from '#/adapter/form';
import { getDemo03GradeByStudentId } from '#/api/infra/demo/demo03/normal';
import { nextTick, watch } from 'vue';
import { useDemo03GradeFormSchema } from '../data';

View File

@@ -1,13 +1,19 @@
<script lang="ts" setup>
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/normal';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message, Tabs } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { createDemo03Student, getDemo03Student, updateDemo03Student } from '#/api/infra/demo/demo03/normal';
import {
createDemo03Student,
getDemo03Student,
updateDemo03Student,
} from '#/api/infra/demo/demo03/normal';
import { $t } from '#/locales';
import { computed, ref } from 'vue';
import { useFormSchema } from '../data';
import Demo03CourseForm from './demo03-course-form.vue';
@@ -16,7 +22,9 @@ import Demo03GradeForm from './demo03-grade-form.vue';
const emit = defineEmits(['success']);
const formData = ref<Demo03StudentApi.Demo03Student>();
const getTitle = computed(() => {
return formData.value?.id ? $t('ui.actionTitle.edit', ['学生']) : $t('ui.actionTitle.create', ['学生']);
return formData.value?.id
? $t('ui.actionTitle.edit', ['学生'])
: $t('ui.actionTitle.create', ['学生']);
});
/** 子表的表单 */
@@ -49,7 +57,9 @@ const [Modal, modalApi] = useVbenModal({
data.demo03Courses = demo03CourseFormRef.value?.getData();
data.demo03Grade = await demo03GradeFormRef.value?.getValues();
try {
await (formData.value?.id ? updateDemo03Student(data) : createDemo03Student(data));
await (formData.value?.id
? updateDemo03Student(data)
: createDemo03Student(data));
// 关闭并提示
await modalApi.close();
emit('success');
@@ -93,7 +103,10 @@ const [Modal, modalApi] = useVbenModal({
<!-- 子表的表单 -->
<Tabs v-model:active-key="subTabsName">
<Tabs.TabPane key="demo03Course" tab="学生课程" force-render>
<Demo03CourseForm ref="demo03CourseFormRef" :student-id="formData?.id" />
<Demo03CourseForm
ref="demo03CourseFormRef"
:student-id="formData?.id"
/>
</Tabs.TabPane>
<Tabs.TabPane key="demo03Grade" tab="学生班级" force-render>
<Demo03GradeForm ref="demo03GradeFormRef" :student-id="formData?.id" />

View File

@@ -1,31 +1,35 @@
<script lang="ts" setup>
import { Page } from '@vben/common-ui'
import { DocAlert } from '#/components/doc-alert'
import { IFrame } from '#/components/iframe'
import { onMounted, ref } from 'vue';
import { ref, onMounted } from 'vue'
import { getConfigKey } from '#/api/infra/config'
import { Page } from '@vben/common-ui';
const loading = ref(true) // 是否加载中
const src = ref(import.meta.env.VITE_BASE_URL + '/druid/index.html')
import { getConfigKey } from '#/api/infra/config';
import { DocAlert } from '#/components/doc-alert';
import { IFrame } from '#/components/iframe';
const loading = ref(true); // 是否加载中
const src = ref(`${import.meta.env.VITE_BASE_URL}/druid/index.html`);
/** 初始化 */
onMounted(async () => {
try {
const data = await getConfigKey('url.druid')
const data = await getConfigKey('url.druid');
if (data && data.length > 0) {
src.value = data
src.value = data;
}
} finally {
loading.value = false
loading.value = false;
}
})
});
</script>
<template>
<Page auto-content-height>
<DocAlert title="数据库 MyBatis" url="https://doc.iocoder.cn/mybatis/" />
<DocAlert title="多数据源(读写分离)" url="https://doc.iocoder.cn/dynamic-datasource/" />
<DocAlert
title="多数据源(读写分离)"
url="https://doc.iocoder.cn/dynamic-datasource/"
/>
<IFrame v-if="!loading" v-loading="loading" :src="src" />
</Page>

View File

@@ -1,8 +1,9 @@
import { type VbenFormSchema } from '#/adapter/form';
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { InfraFileApi } from '#/api/infra/file';
import { useAccess } from '@vben/access';
import { getRangePickerDefaultProps } from '#/utils/date';
const { hasAccessByCodes } = useAccess();
@@ -18,7 +19,7 @@ export function useFormSchema(): VbenFormSchema[] {
placeholder: '请选择要上传的文件',
},
rules: 'required',
}
},
];
}
@@ -56,7 +57,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
export function useGridColumns<T = InfraFileApi.InfraFile>(
export function useGridColumns<T = InfraFileApi.File>(
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
return [
@@ -86,9 +87,9 @@ export function useGridColumns<T = InfraFileApi.InfraFile>(
if (!cellValue) return '0 B';
const unitArr = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const index = Math.floor(Math.log(cellValue) / Math.log(1024));
const size = cellValue / Math.pow(1024, index);
const size = cellValue / 1024 ** index;
const formattedSize = size.toFixed(2);
return formattedSize + ' ' + unitArr[index];
return `${formattedSize} ${unitArr[index]}`;
},
},
{

View File

@@ -1,17 +1,22 @@
<script lang="ts" setup>
import type { OnActionClickParams, VxeTableGridOptions } from '#/adapter/vxe-table';
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { InfraFileApi } from '#/api/infra/file';
import { Page, useVbenModal } from '@vben/common-ui';
import { Button, message, Image } from 'ant-design-vue';
import { Upload } from '@vben/icons';
import Form from './modules/form.vue';
import { $t } from '#/locales';
import { useClipboard } from '@vueuse/core';
import { Button, Image, message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getFilePage, deleteFile } from '#/api/infra/file';
import { deleteFile, getFilePage } from '#/api/infra/file';
import { $t } from '#/locales';
import { useGridColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Form,
@@ -30,7 +35,7 @@ function onUpload() {
/** 复制链接到剪贴板 */
const { copy } = useClipboard({ legacy: true });
async function onCopyUrl(row: InfraFileApi.InfraFile) {
async function onCopyUrl(row: InfraFileApi.File) {
if (!row.url) {
message.error('文件 URL 为空');
return;
@@ -39,7 +44,7 @@ async function onCopyUrl(row: InfraFileApi.InfraFile) {
try {
await copy(row.url);
message.success('复制成功');
} catch (error) {
} catch {
message.error('复制失败');
}
}
@@ -52,7 +57,7 @@ function openUrl(url?: string) {
}
/** 删除文件 */
async function onDelete(row: InfraFileApi.InfraFile) {
async function onDelete(row: InfraFileApi.File) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.name || row.path]),
duration: 0,
@@ -65,31 +70,28 @@ async function onDelete(row: InfraFileApi.InfraFile) {
key: 'action_process_msg',
});
onRefresh();
} catch (error) {
} catch {
hideLoading();
}
}
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<InfraFileApi.InfraFile>) {
function onActionClick({ code, row }: OnActionClickParams<InfraFileApi.File>) {
switch (code) {
case 'delete': {
onDelete(row);
break;
}
case 'copyUrl': {
onCopyUrl(row);
break;
}
case 'delete': {
onDelete(row);
break;
}
}
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema()
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(onActionClick),
@@ -113,7 +115,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
refresh: { code: 'query' },
search: true,
},
} as VxeTableGridOptions<InfraFileApi.InfraFile>,
} as VxeTableGridOptions<InfraFileApi.File>,
});
</script>
@@ -129,8 +131,16 @@ const [Grid, gridApi] = useVbenVxeGrid({
</template>
<template #file-content="{ row }">
<Image v-if="row.type && row.type.includes('image')" :src="row.url" />
<Button v-else-if="row.type && row.type.includes('pdf')" type="link" @click="() => openUrl(row.url)"> 预览 </Button>
<Button v-else type="link" @click="() => openUrl(row.url)"> 下载 </Button>
<Button
v-else-if="row.type && row.type.includes('pdf')"
type="link"
@click="() => openUrl(row.url)"
>
预览
</Button>
<Button v-else type="link" @click="() => openUrl(row.url)">
下载
</Button>
</template>
</Grid>
</Page>

View File

@@ -2,12 +2,12 @@
import type { FileType } from 'ant-design-vue/es/upload/interface';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { Upload } from 'ant-design-vue';
import { $t } from '#/locales';
import { message, Upload } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { useUpload } from '#/components/upload/use-upload';
import { $t } from '#/locales';
import { useFormSchema } from '../data';
@@ -15,11 +15,11 @@ const emit = defineEmits(['success']);
const [Form, formApi] = useVbenForm({
layout: 'horizontal',
schema: useFormSchema().map(item => ({ ...item, label: '' })), // 去除label
schema: useFormSchema().map((item) => ({ ...item, label: '' })), // 去除label
showDefaultActions: false,
commonConfig: {
hideLabel: true,
}
},
});
const [Modal, modalApi] = useVbenModal({
@@ -43,7 +43,7 @@ const [Modal, modalApi] = useVbenModal({
} finally {
modalApi.lock(false);
}
}
},
});
/** 上传前 */
@@ -63,7 +63,7 @@ function beforeUpload(file: FileType) {
name="file"
:max-count="1"
accept=".jpg,.png,.gif,.webp"
:beforeUpload="beforeUpload"
:before-upload="beforeUpload"
list-type="picture-card"
>
<p class="ant-upload-drag-icon">

View File

@@ -2,10 +2,11 @@ import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { InfraFileConfigApi } from '#/api/infra/file-config';
import { DICT_TYPE, getDictOptions } from '#/utils/dict';
import { getRangePickerDefaultProps } from '#/utils/date';
import { useAccess } from '@vben/access';
import { getRangePickerDefaultProps } from '#/utils/date';
import { DICT_TYPE, getDictOptions } from '#/utils/dict';
const { hasAccessByCodes } = useAccess();
/** 新增/修改的表单 */
@@ -39,7 +40,7 @@ export function useFormSchema(): VbenFormSchema[] {
rules: 'required',
dependencies: {
triggerFields: ['id'],
show: (formValues) => !formValues.id
show: (formValues) => !formValues.id,
},
},
{
@@ -61,7 +62,8 @@ export function useFormSchema(): VbenFormSchema[] {
rules: 'required',
dependencies: {
triggerFields: ['storage'],
show: (formValues) => formValues.storage >= 10 && formValues.storage <= 12,
show: (formValues) =>
formValues.storage >= 10 && formValues.storage <= 12,
},
},
{
@@ -74,7 +76,8 @@ export function useFormSchema(): VbenFormSchema[] {
rules: 'required',
dependencies: {
triggerFields: ['storage'],
show: (formValues) => formValues.storage >= 11 && formValues.storage <= 12,
show: (formValues) =>
formValues.storage >= 11 && formValues.storage <= 12,
},
},
{
@@ -90,7 +93,8 @@ export function useFormSchema(): VbenFormSchema[] {
rules: 'required',
dependencies: {
triggerFields: ['storage'],
show: (formValues) => formValues.storage >= 11 && formValues.storage <= 12,
show: (formValues) =>
formValues.storage >= 11 && formValues.storage <= 12,
},
},
{
@@ -103,7 +107,8 @@ export function useFormSchema(): VbenFormSchema[] {
rules: 'required',
dependencies: {
triggerFields: ['storage'],
show: (formValues) => formValues.storage >= 11 && formValues.storage <= 12,
show: (formValues) =>
formValues.storage >= 11 && formValues.storage <= 12,
},
},
{
@@ -116,7 +121,8 @@ export function useFormSchema(): VbenFormSchema[] {
rules: 'required',
dependencies: {
triggerFields: ['storage'],
show: (formValues) => formValues.storage >= 11 && formValues.storage <= 12,
show: (formValues) =>
formValues.storage >= 11 && formValues.storage <= 12,
},
},
{
@@ -240,7 +246,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
export function useGridColumns<T = InfraFileConfigApi.InfraFileConfig>(
export function useGridColumns<T = InfraFileConfigApi.FileConfig>(
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
return [

View File

@@ -1,16 +1,26 @@
<script lang="ts" setup>
import type { OnActionClickParams, VxeTableGridOptions } from '#/adapter/vxe-table';
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { InfraFileConfigApi } from '#/api/infra/file-config';
import { Page, useVbenModal } from '@vben/common-ui';
import { Button, message, Modal } from 'ant-design-vue';
import { Plus } from '@vben/icons';
import Form from './modules/form.vue';
import { $t } from '#/locales';
import { Button, message, Modal } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getFileConfigPage, deleteFileConfig, updateFileConfigMaster, testFileConfig } from '#/api/infra/file-config';
import {
deleteFileConfig,
getFileConfigPage,
testFileConfig,
updateFileConfigMaster,
} from '#/api/infra/file-config';
import { $t } from '#/locales';
import { useGridColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Form,
@@ -28,12 +38,12 @@ function onCreate() {
}
/** 编辑文件配置 */
function onEdit(row: InfraFileConfigApi.InfraFileConfig) {
function onEdit(row: InfraFileConfigApi.FileConfig) {
formModalApi.setData(row).open();
}
/** 设为主配置 */
async function onMaster(row: InfraFileConfigApi.InfraFileConfig) {
async function onMaster(row: InfraFileConfigApi.FileConfig) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.updating', [row.name]),
duration: 0,
@@ -46,13 +56,13 @@ async function onMaster(row: InfraFileConfigApi.InfraFileConfig) {
key: 'action_process_msg',
});
onRefresh();
} catch (error) {
} catch {
hideLoading();
}
}
/** 测试文件配置 */
async function onTest(row: InfraFileConfigApi.InfraFileConfig) {
async function onTest(row: InfraFileConfigApi.FileConfig) {
const hideLoading = message.loading({
content: '测试上传中...',
duration: 0,
@@ -71,13 +81,13 @@ async function onTest(row: InfraFileConfigApi.InfraFileConfig) {
window.open(response, '_blank');
},
});
} catch (error) {
} catch {
hideLoading();
}
}
/** 删除文件配置 */
async function onDelete(row: InfraFileConfigApi.InfraFileConfig) {
async function onDelete(row: InfraFileConfigApi.FileConfig) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.name]),
duration: 0,
@@ -90,7 +100,7 @@ async function onDelete(row: InfraFileConfigApi.InfraFileConfig) {
key: 'action_process_msg',
});
onRefresh();
} catch (error) {
} catch {
hideLoading();
}
}
@@ -99,8 +109,12 @@ async function onDelete(row: InfraFileConfigApi.InfraFileConfig) {
function onActionClick({
code,
row,
}: OnActionClickParams<InfraFileConfigApi.InfraFileConfig>) {
}: OnActionClickParams<InfraFileConfigApi.FileConfig>) {
switch (code) {
case 'delete': {
onDelete(row);
break;
}
case 'edit': {
onEdit(row);
break;
@@ -113,16 +127,12 @@ function onActionClick({
onTest(row);
break;
}
case 'delete': {
onDelete(row);
break;
}
}
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema()
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(onActionClick),
@@ -146,7 +156,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
refresh: { code: 'query' },
search: true,
},
} as VxeTableGridOptions<InfraFileConfigApi.InfraFileConfig>,
} as VxeTableGridOptions<InfraFileConfigApi.FileConfig>,
});
</script>
@@ -155,7 +165,11 @@ const [Grid, gridApi] = useVbenVxeGrid({
<FormModal @success="onRefresh" />
<Grid table-title="文件配置列表">
<template #toolbar-tools>
<Button type="primary" @click="onCreate" v-access:code="['infra:file-config:create']">
<Button
type="primary"
@click="onCreate"
v-access:code="['infra:file-config:create']"
>
<Plus class="size-5" />
{{ $t('ui.actionTitle.create', ['文件配置']) }}
</Button>

View File

@@ -1,18 +1,24 @@
<script lang="ts" setup>
import type { InfraFileConfigApi } from '#/api/infra/file-config';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { computed, ref } from 'vue';
import { useVbenForm } from '#/adapter/form';
import { createFileConfig, updateFileConfig, getFileConfig } from '#/api/infra/file-config';
import {
createFileConfig,
getFileConfig,
updateFileConfig,
} from '#/api/infra/file-config';
import { $t } from '#/locales';
import { useFormSchema } from '../data';
const emit = defineEmits(['success']);
const formData = ref<InfraFileConfigApi.InfraFileConfig>();
const formData = ref<InfraFileConfigApi.FileConfig>();
const getTitle = computed(() => {
return formData.value?.id
? $t('ui.actionTitle.edit', ['文件配置'])
@@ -33,9 +39,11 @@ const [Modal, modalApi] = useVbenModal({
}
modalApi.lock();
// 提交表单
const data = (await formApi.getValues()) as InfraFileConfigApi.InfraFileConfig;
const data = (await formApi.getValues()) as InfraFileConfigApi.FileConfig;
try {
await (formData.value?.id ? updateFileConfig(data) : createFileConfig(data));
await (formData.value?.id
? updateFileConfig(data)
: createFileConfig(data));
// 关闭并提示
await modalApi.close();
emit('success');
@@ -52,7 +60,7 @@ const [Modal, modalApi] = useVbenModal({
return;
}
// 加载数据
const data = modalApi.getData<InfraFileConfigApi.InfraFileConfig>();
const data = modalApi.getData<InfraFileConfigApi.FileConfig>();
if (!data || !data.id) {
return;
}
@@ -72,4 +80,4 @@ const [Modal, modalApi] = useVbenModal({
<Modal :title="getTitle">
<Form class="mx-4" />
</Modal>
</template>
</template>

View File

@@ -2,9 +2,10 @@ import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { InfraJobApi } from '#/api/infra/job';
import { DICT_TYPE, getDictOptions } from '#/utils/dict';
import { useAccess } from '@vben/access';
import { InfraJobStatusEnum } from '#/utils/constants';
import { DICT_TYPE, getDictOptions } from '#/utils/dict';
const { hasAccessByCodes } = useAccess();
@@ -75,7 +76,7 @@ export function useFormSchema(): VbenFormSchema[] {
componentProps: {
placeholder: '请输入重试间隔,单位:毫秒。设置为 0 时,无需间隔',
min: 0,
class: 'w-full'
class: 'w-full',
},
rules: 'required',
},
@@ -127,7 +128,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
}
/** 表格列配置 */
export function useGridColumns<T = InfraJobApi.InfraJob>(
export function useGridColumns<T = InfraJobApi.Job>(
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
return [
@@ -147,7 +148,7 @@ export function useGridColumns<T = InfraJobApi.InfraJob>(
minWidth: 100,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.INFRA_JOB_STATUS, },
props: { type: DICT_TYPE.INFRA_JOB_STATUS },
},
},
{
@@ -186,14 +187,16 @@ export function useGridColumns<T = InfraJobApi.InfraJob>(
{
code: 'update-status',
text: '开启',
show: (row: any) => hasAccessByCodes(['infra:job:update'])
&& row.status === InfraJobStatusEnum.STOP,
show: (row: any) =>
hasAccessByCodes(['infra:job:update']) &&
row.status === InfraJobStatusEnum.STOP,
},
{
code: 'update-status',
text: '暂停',
show: (row: any) => hasAccessByCodes(['infra:job:update'])
&& row.status == InfraJobStatusEnum.NORMAL,
show: (row: any) =>
hasAccessByCodes(['infra:job:update']) &&
row.status == InfraJobStatusEnum.NORMAL,
},
{
code: 'trigger',

View File

@@ -1,22 +1,33 @@
<script lang="ts" setup>
import type { OnActionClickParams, VxeTableGridOptions } from '#/adapter/vxe-table';
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { InfraJobApi } from '#/api/infra/job';
import { Page, useVbenModal } from '@vben/common-ui';
import { Download, Plus, History } from '@vben/icons';
import { Button, message, Modal } from 'ant-design-vue';
import Form from './modules/form.vue';
import Detail from './modules/detail.vue';
import { DocAlert } from '#/components/doc-alert';
import { $t } from '#/locales';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { useRouter } from 'vue-router';
import { InfraJobStatusEnum} from '#/utils/constants';
import { deleteJob, exportJob, getJobPage, runJob, updateJobStatus } from '#/api/infra/job';
import { Page, useVbenModal } from '@vben/common-ui';
import { Download, History, Plus } from '@vben/icons';
import { Button, message, Modal } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteJob,
exportJob,
getJobPage,
runJob,
updateJobStatus,
} from '#/api/infra/job';
import { DocAlert } from '#/components/doc-alert';
import { $t } from '#/locales';
import { InfraJobStatusEnum } from '#/utils/constants';
import { downloadByData } from '#/utils/download';
import { useGridColumns, useGridFormSchema } from './data';
import Detail from './modules/detail.vue';
import Form from './modules/form.vue';
const { push } = useRouter();
@@ -47,18 +58,21 @@ function onCreate() {
}
/** 编辑任务 */
function onEdit(row: InfraJobApi.InfraJob) {
function onEdit(row: InfraJobApi.Job) {
formModalApi.setData(row).open();
}
/** 查看任务详情 */
function onDetail(row: InfraJobApi.InfraJob) {
function onDetail(row: InfraJobApi.Job) {
detailModalApi.setData({ id: row.id }).open();
}
/** 更新任务状态 */
async function onUpdateStatus(row: InfraJobApi.InfraJob) {
const status = row.status === InfraJobStatusEnum.STOP ? InfraJobStatusEnum.NORMAL : InfraJobStatusEnum.STOP;
async function onUpdateStatus(row: InfraJobApi.Job) {
const status =
row.status === InfraJobStatusEnum.STOP
? InfraJobStatusEnum.NORMAL
: InfraJobStatusEnum.STOP;
const statusText = status === InfraJobStatusEnum.NORMAL ? '启用' : '停用';
Modal.confirm({
title: '确认操作',
@@ -70,12 +84,12 @@ async function onUpdateStatus(row: InfraJobApi.InfraJob) {
key: 'action_process_msg',
});
onRefresh();
}
},
});
}
/** 执行一次任务 */
async function onTrigger(row: InfraJobApi.InfraJob) {
async function onTrigger(row: InfraJobApi.Job) {
Modal.confirm({
title: '确认操作',
content: `确定执行一次 ${row.name} 吗?`,
@@ -85,12 +99,12 @@ async function onTrigger(row: InfraJobApi.InfraJob) {
content: $t('ui.actionMessage.operationSuccess'),
key: 'action_process_msg',
});
}
},
});
}
/** 跳转到任务日志 */
function onLog(row?: InfraJobApi.InfraJob) {
function onLog(row?: InfraJobApi.Job) {
push({
name: 'InfraJobLog',
query: row?.id ? { id: row.id } : {},
@@ -98,7 +112,7 @@ function onLog(row?: InfraJobApi.InfraJob) {
}
/** 删除任务 */
async function onDelete(row: InfraJobApi.InfraJob) {
async function onDelete(row: InfraJobApi.Job) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.name]),
duration: 0,
@@ -117,35 +131,32 @@ async function onDelete(row: InfraJobApi.InfraJob) {
}
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<InfraJobApi.InfraJob>) {
function onActionClick({ code, row }: OnActionClickParams<InfraJobApi.Job>) {
switch (code) {
case 'edit': {
onEdit(row);
break;
}
case 'update-status': {
onUpdateStatus(row);
break;
}
case 'delete': {
onDelete(row);
break;
}
case 'trigger': {
onTrigger(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;
}
}
}
@@ -175,7 +186,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
refresh: { code: 'query' },
search: true,
},
} as VxeTableGridOptions<InfraJobApi.InfraJob>,
} as VxeTableGridOptions<InfraJobApi.Job>,
});
</script>
@@ -189,15 +200,29 @@ const [Grid, gridApi] = useVbenVxeGrid({
<DetailModal />
<Grid table-title="定时任务列表">
<template #toolbar-tools>
<Button type="primary" @click="onCreate" v-access:code="['infra:job:create']">
<Button
type="primary"
@click="onCreate"
v-access:code="['infra:job:create']"
>
<Plus class="size-5" />
{{ $t('ui.actionTitle.create', ['任务']) }}
</Button>
<Button type="primary" class="ml-2" @click="onExport" v-access:code="['infra:job:export']">
<Button
type="primary"
class="ml-2"
@click="onExport"
v-access:code="['infra:job:export']"
>
<Download class="size-5" />
{{ $t('ui.actionTitle.export') }}
</Button>
<Button type="primary" class="ml-2" @click="onLog(undefined)" v-access:code="['infra:job:query']">
<Button
type="primary"
class="ml-2"
@click="onLog(undefined)"
v-access:code="['infra:job:query']"
>
<History class="size-5" />
执行日志
</Button>

View File

@@ -2,11 +2,13 @@ import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { InfraJobLogApi } from '#/api/infra/job-log';
import { DICT_TYPE, getDictOptions } from '#/utils/dict';
import { useAccess } from '@vben/access';
import dayjs from 'dayjs';
import { formatDateTime } from '@vben/utils';
import dayjs from 'dayjs';
import { DICT_TYPE, getDictOptions } from '#/utils/dict';
const { hasAccessByCodes } = useAccess();
/** 列表的搜索表单 */
@@ -63,7 +65,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
}
/** 表格列配置 */
export function useGridColumns<T = InfraJobLogApi.InfraJobLog>(
export function useGridColumns<T = InfraJobLogApi.JobLog>(
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
return [

View File

@@ -1,20 +1,25 @@
<script lang="ts" setup>
import type { OnActionClickParams, VxeTableGridOptions } from '#/adapter/vxe-table';
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { InfraJobLogApi } from '#/api/infra/job-log';
import { useRoute } from 'vue-router';
import { Page, useVbenModal } from '@vben/common-ui';
import { Download } from '@vben/icons';
import { Button } from 'ant-design-vue';
import Detail from './modules/detail.vue';
import { DocAlert } from '#/components/doc-alert';
import { $t } from '#/locales';
import { Button } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { useRoute } from 'vue-router';
import { exportJobLog, getJobLogPage } from '#/api/infra/job-log';
import { DocAlert } from '#/components/doc-alert';
import { $t } from '#/locales';
import { downloadByData } from '#/utils/download';
import { useGridColumns, useGridFormSchema } from './data';
import Detail from './modules/detail.vue';
const { query } = useRoute();
@@ -30,7 +35,7 @@ async function onExport() {
}
/** 查看日志详情 */
function onDetail(row: InfraJobLogApi.InfraJobLog) {
function onDetail(row: InfraJobLogApi.JobLog) {
detailModalApi.setData({ id: row.id }).open();
}
@@ -38,7 +43,7 @@ function onDetail(row: InfraJobLogApi.InfraJobLog) {
function onActionClick({
code,
row,
}: OnActionClickParams<InfraJobLogApi.InfraJobLog>) {
}: OnActionClickParams<InfraJobLogApi.JobLog>) {
switch (code) {
case 'detail': {
onDetail(row);
@@ -77,7 +82,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
refresh: { code: 'query' },
search: true,
},
} as VxeTableGridOptions<InfraJobLogApi.InfraJobLog>,
} as VxeTableGridOptions<InfraJobLogApi.JobLog>,
});
</script>
@@ -90,7 +95,12 @@ const [Grid, gridApi] = useVbenVxeGrid({
<DetailModal />
<Grid table-title="任务日志列表">
<template #toolbar-tools>
<Button type="primary" class="ml-2" @click="onExport" v-access:code="['infra:job:export']">
<Button
type="primary"
class="ml-2"
@click="onExport"
v-access:code="['infra:job:export']"
>
<Download class="size-5" />
{{ $t('ui.actionTitle.export') }}
</Button>

View File

@@ -1,16 +1,18 @@
<script lang="ts" setup>
import type { InfraJobLogApi } from '#/api/infra/job-log';
import { Descriptions } from 'ant-design-vue';
import { DictTag } from '#/components/dict-tag';
import { useVbenModal } from '@vben/common-ui';
import { DICT_TYPE } from '#/utils/dict';
import { formatDateTime } from '@vben/utils';
import { getJobLog } from '#/api/infra/job-log';
import { ref } from 'vue';
const formData = ref<InfraJobLogApi.InfraJobLog>();
import { useVbenModal } from '@vben/common-ui';
import { formatDateTime } from '@vben/utils';
import { Descriptions } from 'ant-design-vue';
import { getJobLog } from '#/api/infra/job-log';
import { DictTag } from '#/components/dict-tag';
import { DICT_TYPE } from '#/utils/dict';
const formData = ref<InfraJobLogApi.JobLog>();
const [Modal, modalApi] = useVbenModal({
async onOpenChange(isOpen: boolean) {
@@ -33,8 +35,19 @@ const [Modal, modalApi] = useVbenModal({
</script>
<template>
<Modal title="日志详情" class="w-1/2" :show-cancel-button="false" :show-confirm-button="false">
<Descriptions :column="1" bordered size="middle" class="mx-4" :label-style="{ width: '140px' }">
<Modal
title="日志详情"
class="w-1/2"
:show-cancel-button="false"
:show-confirm-button="false"
>
<Descriptions
:column="1"
bordered
size="middle"
class="mx-4"
:label-style="{ width: '140px' }"
>
<Descriptions.Item label="日志编号">
{{ formData?.id }}
</Descriptions.Item>
@@ -55,10 +68,13 @@ const [Modal, modalApi] = useVbenModal({
{{ formData?.endTime ? formatDateTime(formData.endTime) : '' }}
</Descriptions.Item>
<Descriptions.Item label="执行时长">
{{ formData?.duration ? formData.duration + ' 毫秒' : '' }}
{{ formData?.duration ? `${formData.duration} 毫秒` : '' }}
</Descriptions.Item>
<Descriptions.Item label="任务状态">
<DictTag :type="DICT_TYPE.INFRA_JOB_LOG_STATUS" :value="formData?.status" />
<DictTag
:type="DICT_TYPE.INFRA_JOB_LOG_STATUS"
:value="formData?.status"
/>
</Descriptions.Item>
<Descriptions.Item label="执行结果">
{{ formData?.result }}

View File

@@ -1,16 +1,18 @@
<script lang="ts" setup>
import type { InfraJobApi } from '#/api/infra/job';
import { Descriptions, Timeline } from 'ant-design-vue';
import { DictTag } from '#/components/dict-tag';
import { useVbenModal } from '@vben/common-ui';
import { DICT_TYPE } from '#/utils/dict';
import { formatDateTime } from '@vben/utils';
import { getJob, getJobNextTimes } from '#/api/infra/job';
import { ref } from 'vue';
const formData = ref<InfraJobApi.InfraJob>(); // 任务详情
import { useVbenModal } from '@vben/common-ui';
import { formatDateTime } from '@vben/utils';
import { Descriptions, Timeline } from 'ant-design-vue';
import { getJob, getJobNextTimes } from '#/api/infra/job';
import { DictTag } from '#/components/dict-tag';
import { DICT_TYPE } from '#/utils/dict';
const formData = ref<InfraJobApi.Job>(); // 任务详情
const nextTimes = ref<Date[]>([]); // 下一次执行时间
const [Modal, modalApi] = useVbenModal({
@@ -36,8 +38,19 @@ const [Modal, modalApi] = useVbenModal({
</script>
<template>
<Modal title="任务详情" class="w-1/2" :show-cancel-button="false" :show-confirm-button="false">
<Descriptions :column="1" bordered size="middle" class="mx-4" :label-style="{ width: '140px' }">
<Modal
title="任务详情"
class="w-1/2"
:show-cancel-button="false"
:show-confirm-button="false"
>
<Descriptions
:column="1"
bordered
size="middle"
class="mx-4"
:label-style="{ width: '140px' }"
>
<Descriptions.Item label="任务编号">
{{ formData?.id }}
</Descriptions.Item>
@@ -60,14 +73,24 @@ const [Modal, modalApi] = useVbenModal({
{{ formData?.retryCount }}
</Descriptions.Item>
<Descriptions.Item label="重试间隔">
{{ formData?.retryInterval ? formData.retryInterval + ' 毫秒' : '无间隔' }}
{{
formData?.retryInterval ? `${formData.retryInterval} 毫秒` : '无间隔'
}}
</Descriptions.Item>
<Descriptions.Item label="监控超时时间">
{{ formData?.monitorTimeout && formData.monitorTimeout > 0 ? formData.monitorTimeout + ' 毫秒' : '未开启' }}
{{
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">
<Timeline.Item
v-for="(nextTime, index) in nextTimes"
:key="index"
color="blue"
>
{{ index + 1 }} {{ formatDateTime(nextTime.toString()) }}
</Timeline.Item>
</Timeline>

View File

@@ -1,18 +1,20 @@
<script lang="ts" setup>
import type { InfraJobApi } from '#/api/infra/job';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { $t } from '#/locales';
import { computed, ref } from 'vue';
import { useVbenForm } from '#/adapter/form';
import { createJob, getJob, updateJob } from '#/api/infra/job';
import { $t } from '#/locales';
import { useFormSchema } from '../data';
const emit = defineEmits(['success']);
const formData = ref<InfraJobApi.InfraJob>();
const formData = ref<InfraJobApi.Job>();
const getTitle = computed(() => {
return formData.value?.id
? $t('ui.actionTitle.edit', ['任务'])
@@ -24,8 +26,8 @@ const [Form, formApi] = useVbenForm({
schema: useFormSchema(),
showDefaultActions: false,
commonConfig: {
labelWidth: 140
}
labelWidth: 140,
},
});
const [Modal, modalApi] = useVbenModal({
@@ -36,11 +38,9 @@ const [Modal, modalApi] = useVbenModal({
}
modalApi.lock();
// 提交表单
const data = (await formApi.getValues()) as InfraJobApi.InfraJob;
const data = (await formApi.getValues()) as InfraJobApi.Job;
try {
await (formData.value?.id
? updateJob(data)
: createJob(data));
await (formData.value?.id ? updateJob(data) : createJob(data));
// 关闭并提示
await modalApi.close();
emit('success');
@@ -57,7 +57,7 @@ const [Modal, modalApi] = useVbenModal({
return;
}
// 加载数据
const data = modalApi.getData<InfraJobApi.InfraJob>();
const data = modalApi.getData<InfraJobApi.Job>();
if (!data || !data.id) {
return;
}
@@ -77,4 +77,4 @@ const [Modal, modalApi] = useVbenModal({
<Modal :title="getTitle">
<Form class="mx-4" />
</Modal>
</template>
</template>

View File

@@ -1,17 +1,20 @@
<script lang="ts" setup>
import type { InfraRedisApi } from '#/api/infra/redis';
import { Card } from 'ant-design-vue';
import { onMounted, ref } from 'vue';
import { Page } from '@vben/common-ui';
import Memory from './modules/memory.vue';
import Commands from './modules/commands.vue';
import Info from './modules/info.vue';
import { Card } from 'ant-design-vue';
import { getRedisMonitorInfo } from '#/api/infra/redis';
import { DocAlert } from '#/components/doc-alert';
import { onMounted, ref } from 'vue';
import { getRedisMonitorInfo } from '#/api/infra/redis';
import Commands from './modules/commands.vue';
import Info from './modules/info.vue';
import Memory from './modules/memory.vue';
const redisData = ref<InfraRedisApi.InfraRedisMonitorInfo>();
const redisData = ref<InfraRedisApi.RedisMonitorInfo>();
/** 统一加载 Redis 数据 */
const loadRedisData = async () => {
@@ -36,7 +39,7 @@ onMounted(() => {
<Info :redis-data="redisData" />
</Card>
<div class="mt-5 grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="mt-5 grid grid-cols-1 gap-4 md:grid-cols-2">
<Card title="内存使用">
<Memory :redis-data="redisData" />
</Card>

View File

@@ -1,13 +1,14 @@
<script lang="ts" setup>
import type { EchartsUIType } from '@vben/plugins/echarts';
import type { InfraRedisApi } from '#/api/infra/redis';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
import type { InfraRedisApi } from '#/api/infra/redis';
import { onMounted, ref, watch } from 'vue';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
const props = defineProps<{
redisData?: InfraRedisApi.InfraRedisMonitorInfo;
redisData?: InfraRedisApi.RedisMonitorInfo;
}>();
const chartRef = ref<EchartsUIType>();
@@ -25,7 +26,7 @@ const renderCommandStats = () => {
props.redisData.commandStats.forEach((row) => {
commandStats.push({
name: row.command,
value: row.calls
value: row.calls,
});
nameList.push(row.command);
});
@@ -34,11 +35,11 @@ const renderCommandStats = () => {
renderEcharts({
title: {
text: '命令统计',
left: 'center'
left: 'center',
},
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b} : {c} ({d}%)'
formatter: '{a} <br/>{b} : {c} ({d}%)',
},
legend: {
type: 'scroll',
@@ -48,8 +49,8 @@ const renderCommandStats = () => {
bottom: 20,
data: nameList,
textStyle: {
color: '#a1a1a1'
}
color: '#a1a1a1',
},
},
series: [
{
@@ -60,29 +61,33 @@ const renderCommandStats = () => {
data: commandStats,
roseType: 'radius',
label: {
show: true
show: true,
},
emphasis: {
label: {
show: true
show: true,
},
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
shadowColor: 'rgba(0, 0, 0, 0.5)',
},
},
},
],
});
};
/** 监听数据变化,重新渲染图表 */
watch(() => props.redisData, (newVal) => {
if (newVal) {
renderCommandStats();
}
}, { deep: true });
watch(
() => props.redisData,
(newVal) => {
if (newVal) {
renderCommandStats();
}
},
{ deep: true },
);
onMounted(() => {
if (props.redisData) {

View File

@@ -1,15 +1,20 @@
<script lang="ts" setup>
import { type InfraRedisApi } from '#/api/infra/redis';
import type { InfraRedisApi } from '#/api/infra/redis';
import { Descriptions } from 'ant-design-vue';
defineProps<{
redisData?: InfraRedisApi.InfraRedisMonitorInfo;
redisData?: InfraRedisApi.RedisMonitorInfo;
}>();
</script>
<template>
<Descriptions :column="6" bordered size="middle" :label-style="{ width: '138px' }">
<Descriptions
:column="6"
bordered
size="middle"
:label-style="{ width: '138px' }"
>
<Descriptions.Item label="Redis 版本">
{{ redisData?.info?.redis_version }}
</Descriptions.Item>
@@ -29,7 +34,11 @@ defineProps<{
{{ redisData?.info?.used_memory_human }}
</Descriptions.Item>
<Descriptions.Item label="使用 CPU">
{{ redisData?.info ? parseFloat(redisData?.info?.used_cpu_user_children).toFixed(2) : '' }}
{{
redisData?.info
? parseFloat(redisData?.info?.used_cpu_user_children).toFixed(2)
: ''
}}
</Descriptions.Item>
<Descriptions.Item label="内存配置">
{{ redisData?.info?.maxmemory_human }}

View File

@@ -1,13 +1,14 @@
<script lang="ts" setup>
import type { EchartsUIType } from '@vben/plugins/echarts';
import type { InfraRedisApi } from '#/api/infra/redis';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
import type { InfraRedisApi } from '#/api/infra/redis';
import { onMounted, ref, watch } from 'vue';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
const props = defineProps<{
redisData?: InfraRedisApi.InfraRedisMonitorInfo;
redisData?: InfraRedisApi.RedisMonitorInfo;
}>();
const chartRef = ref<EchartsUIType>();
@@ -22,8 +23,8 @@ const parseMemoryValue = (memStr: string | undefined): number => {
// 从字符串中提取数字部分,例如 "1.2M" 中的 1.2
const str = String(memStr); // 显式转换为字符串类型
const match = str.match(/^([\d.]+)/);
return match ? parseFloat(match[1] as string) : 0;
} catch (e) {
return match ? Number.parseFloat(match[1] as string) : 0;
} catch {
return 0;
}
};
@@ -45,7 +46,7 @@ const renderMemoryChart = () => {
left: 'center',
},
tooltip: {
formatter: '{b} <br/>{a} : ' + usedMemory
formatter: `{b} <br/>{a} : ${usedMemory}`,
},
series: [
{
@@ -64,59 +65,63 @@ const renderMemoryChart = () => {
color: [
[0.2, '#7FFF00'],
[0.8, '#00FFFF'],
[1, '#FF0000']
[1, '#FF0000'],
],
width: 10
}
width: 10,
},
},
axisTick: {
length: 5,
lineStyle: {
color: '#76D9D7'
}
color: '#76D9D7',
},
},
splitLine: {
length: 20,
lineStyle: {
color: '#76D9D7'
}
color: '#76D9D7',
},
},
axisLabel: {
color: '#76D9D7',
distance: 15,
fontSize: 15
fontSize: 15,
},
pointer: {
width: 7,
show: true
show: true,
},
detail: {
show: true,
offsetCenter: [0, '50%'],
color: 'auto',
fontSize: 30,
formatter: usedMemory
formatter: usedMemory,
},
progress: {
show: true
show: true,
},
data: [
{
value: memoryValue,
name: '内存消耗'
}
]
}
]
name: '内存消耗',
},
],
},
],
});
};
/** 监听数据变化,重新渲染图表 */
watch(() => props.redisData, (newVal) => {
if (newVal) {
renderMemoryChart();
}
}, { deep: true });
watch(
() => props.redisData,
(newVal) => {
if (newVal) {
renderMemoryChart();
}
},
{ deep: true },
);
onMounted(() => {
if (props.redisData) {

View File

@@ -1,13 +1,14 @@
<script setup lang="ts">
import { Page } from '@vben/common-ui'
import { IFrame } from '#/components/iframe'
import { DocAlert } from '#/components/doc-alert'
import { onMounted, ref } from 'vue';
import { ref, onMounted } from 'vue'
import { getConfigKey } from '#/api/infra/config'
import { Page } from '@vben/common-ui';
const loading = ref(true) // 是否加载中
const src = ref(import.meta.env.VITE_BASE_URL + '/admin/applications')
import { getConfigKey } from '#/api/infra/config';
import { DocAlert } from '#/components/doc-alert';
import { IFrame } from '#/components/iframe';
const loading = ref(true); // 是否加载中
const src = ref(`${import.meta.env.VITE_BASE_URL}/admin/applications`);
/** 初始化 */
onMounted(async () => {
@@ -15,14 +16,14 @@ onMounted(async () => {
// 友情提示:如果访问出现 404 问题:
// 1boot 参考 https://doc.iocoder.cn/server-monitor/ 解决;
// 2cloud 参考 https://cloud.iocoder.cn/server-monitor/ 解决
const data = await getConfigKey('url.spring-boot-admin')
const data = await getConfigKey('url.spring-boot-admin');
if (data && data.length > 0) {
src.value = data
src.value = data;
}
} finally {
loading.value = false
loading.value = false;
}
})
});
</script>
<template>
@@ -31,4 +32,4 @@ onMounted(async () => {
<IFrame v-if="!loading" v-loading="loading" :src="src" />
</Page>
</template>
</template>

View File

@@ -1,25 +1,26 @@
<script setup lang="ts">
import { Page } from '@vben/common-ui'
import { DocAlert } from '#/components/doc-alert'
import { IFrame } from '#/components/iframe'
import { onMounted, ref } from 'vue';
import { ref, onMounted } from 'vue'
import { getConfigKey } from '#/api/infra/config'
import { Page } from '@vben/common-ui';
const loading = ref(true) // 是否加载中
const src = ref('http://skywalking.shop.iocoder.cn')
import { getConfigKey } from '#/api/infra/config';
import { DocAlert } from '#/components/doc-alert';
import { IFrame } from '#/components/iframe';
const loading = ref(true); // 是否加载中
const src = ref('http://skywalking.shop.iocoder.cn');
/** 初始化 */
onMounted(async () => {
try {
const data = await getConfigKey('url.skywalking')
const data = await getConfigKey('url.skywalking');
if (data && data.length > 0) {
src.value = data
src.value = data;
}
} finally {
loading.value = false
loading.value = false;
}
})
});
</script>
<template>

View File

@@ -1,26 +1,27 @@
<script lang="ts" setup>
import { Page } from '@vben/common-ui'
import { DocAlert } from '#/components/doc-alert'
import { IFrame } from '#/components/iframe'
import { onMounted, ref } from 'vue';
import { ref, onMounted } from 'vue'
import { getConfigKey } from '#/api/infra/config'
import { Page } from '@vben/common-ui';
const loading = ref(true) // 是否加载中
const src = ref(import.meta.env.VITE_BASE_URL + '/doc.html') // Knife4j UI
import { getConfigKey } from '#/api/infra/config';
import { DocAlert } from '#/components/doc-alert';
import { IFrame } from '#/components/iframe';
const loading = ref(true); // 是否加载中
const src = ref(`${import.meta.env.VITE_BASE_URL}/doc.html`); // Knife4j UI
// const src = ref(import.meta.env.VITE_BASE_URL + '/swagger-ui') // Swagger UI
/** 初始化 */
onMounted(async () => {
try {
const data = await getConfigKey('url.swagger')
const data = await getConfigKey('url.swagger');
if (data && data.length > 0) {
src.value = data
src.value = data;
}
} finally {
loading.value = false
loading.value = false;
}
})
});
</script>
<template>
@@ -29,4 +30,4 @@ onMounted(async () => {
<IFrame v-if="!loading" :src="src" />
</Page>
</template>
</template>

View File

@@ -1,41 +1,56 @@
<script lang="ts" setup>
import type { SystemUserApi } from '#/api/system/user'
import type { SystemUserApi } from '#/api/system/user';
import { message } from 'ant-design-vue'
import { Card, Tag, Divider, Input, Button, Select, Avatar, Empty, Badge } from 'ant-design-vue'
import { Page } from '@vben/common-ui'
import { DocAlert } from '#/components/doc-alert'
import { computed, onMounted, ref, watchEffect } from 'vue';
import { formatDate } from '@vben/utils'
import { useWebSocket } from '@vueuse/core'
import { getSimpleUserList } from '#/api/system/user'
import { ref, computed, watchEffect, onMounted } from 'vue'
import { useAccessStore } from '@vben/stores'
import { Page } from '@vben/common-ui';
import { useAccessStore } from '@vben/stores';
import { formatDate } from '@vben/utils';
const accessStore = useAccessStore()
const refreshToken = accessStore.refreshToken as string
import { useWebSocket } from '@vueuse/core';
import {
Avatar,
Badge,
Button,
Card,
Divider,
Empty,
Input,
message,
Select,
Tag,
} from 'ant-design-vue';
import { getSimpleUserList } from '#/api/system/user';
import { DocAlert } from '#/components/doc-alert';
const accessStore = useAccessStore();
const refreshToken = accessStore.refreshToken as string;
const server = ref(
(import.meta.env.VITE_BASE_URL + '/infra/ws').replace('http', 'ws') +
'?token=' +
refreshToken // 使用 refreshToken而不使用 accessToken 方法的原因WebSocket 无法方便的刷新访问令牌
) // WebSocket 服务地址
const getIsOpen = computed(() => status.value === 'OPEN') // WebSocket 连接是否打开
const getTagColor = computed(() => (getIsOpen.value ? 'success' : 'red')) // WebSocket 连接的展示颜色
const getStatusText = computed(() => (getIsOpen.value ? '已连接' : '未连接')) // 连接状态文本
`${`${import.meta.env.VITE_BASE_URL}/infra/ws`.replace(
'http',
'ws',
)}?token=${refreshToken}`, // 使用 refreshToken而不使用 accessToken 方法的原因WebSocket 无法方便的刷新访问令牌
); // WebSocket 服务地址
const getIsOpen = computed(() => status.value === 'OPEN'); // WebSocket 连接是否打开
const getTagColor = computed(() => (getIsOpen.value ? 'success' : 'red')); // WebSocket 连接的展示颜色
const getStatusText = computed(() => (getIsOpen.value ? '已连接' : '未连接')); // 连接状态文本
/** 发起 WebSocket 连接 */
const { status, data, send, close, open } = useWebSocket(server.value, {
autoReconnect: true,
heartbeat: true
})
heartbeat: true,
});
/** 监听接收到的数据 */
const messageList = ref([] as { time: number; text: string; type?: string; userId?: string }[]) // 消息列表
const messageReverseList = computed(() => messageList.value.slice().reverse())
const messageList = ref(
[] as { text: string; time: number; type?: string; userId?: string }[],
); // 消息列表
const messageReverseList = computed(() => [...messageList.value].reverse());
watchEffect(() => {
if (!data.value) {
return
return;
}
try {
// 1. 收到心跳
@@ -44,109 +59,128 @@ watchEffect(() => {
// text: '【心跳】',
// time: new Date().getTime()
// })
return
return;
}
// 2.1 解析 type 消息类型
const jsonMessage = JSON.parse(data.value)
const type = jsonMessage.type
const content = JSON.parse(jsonMessage.content)
const jsonMessage = JSON.parse(data.value);
const type = jsonMessage.type;
const content = JSON.parse(jsonMessage.content);
if (!type) {
message.error('未知的消息类型:' + data.value)
return
message.error(`未知的消息类型:${data.value}`);
return;
}
// 2.2 消息类型demo-message-receive
if (type === 'demo-message-receive') {
const single = content.single
const single = content.single;
messageList.value.push({
text: content.text,
time: new Date().getTime(),
time: Date.now(),
type: single ? 'single' : 'group',
userId: content.fromUserId
})
return
userId: content.fromUserId,
});
return;
}
// 2.3 消息类型notice-push
if (type === 'notice-push') {
messageList.value.push({
text: content.title,
time: new Date().getTime(),
type: 'system'
})
return
time: Date.now(),
type: 'system',
});
return;
}
message.error('未处理消息:' + data.value)
message.error(`未处理消息:${data.value}`);
} catch (error) {
message.error('处理消息发生异常:' + data.value)
console.error(error)
message.error(`处理消息发生异常:${data.value}`);
console.error(error);
}
})
});
/** 发送消息 */
const sendText = ref('') // 发送内容
const sendUserId = ref('') // 发送人
const sendText = ref(''); // 发送内容
const sendUserId = ref(''); // 发送人
const handlerSend = () => {
if (!sendText.value.trim()) {
message.warning('消息内容不能为空')
return
message.warning('消息内容不能为空');
return;
}
// 1.1 先 JSON 化 message 消息内容
const messageContent = JSON.stringify({
text: sendText.value,
toUserId: sendUserId.value
})
toUserId: sendUserId.value,
});
// 1.2 再 JSON 化整个消息
const jsonMessage = JSON.stringify({
type: 'demo-message-send',
content: messageContent
})
content: messageContent,
});
// 2. 最后发送消息
send(jsonMessage)
sendText.value = ''
}
send(jsonMessage);
sendText.value = '';
};
/** 切换 websocket 连接状态 */
const toggleConnectStatus = () => {
if (getIsOpen.value) {
close()
close();
} else {
open()
open();
}
}
};
/** 获取消息类型的徽标颜色 */
const getMessageBadgeColor = (type?: string) => {
switch (type) {
case 'single': return 'blue'
case 'group': return 'green'
case 'system': return 'red'
default: return 'default'
case 'group': {
return 'green';
}
case 'single': {
return 'blue';
}
case 'system': {
return 'red';
}
default: {
return 'default';
}
}
}
};
/** 获取消息类型的文本 */
const getMessageTypeText = (type?: string) => {
switch (type) {
case 'single': return '单发'
case 'group': return '群发'
case 'system': return '系统'
default: return '未知'
case 'group': {
return '群发';
}
case 'single': {
return '单发';
}
case 'system': {
return '系统';
}
default: {
return '未知';
}
}
}
};
/** 初始化 **/
const userList = ref<SystemUserApi.SystemUser[]>([]) // 用户列表
/** 初始化 */
const userList = ref<SystemUserApi.User[]>([]); // 用户列表
onMounted(async () => {
userList.value = await getSimpleUserList()
})
userList.value = await getSimpleUserList();
});
</script>
<template>
<Page>
<DocAlert title="WebSocket 实时通信" url="https://doc.iocoder.cn/websocket/" />
<DocAlert
title="WebSocket 实时通信"
url="https://doc.iocoder.cn/websocket/"
/>
<div class="flex flex-col md:flex-row gap-4 mt-4">
<div class="mt-4 flex flex-col gap-4 md:flex-row">
<!-- 左侧建立连接发送消息 -->
<Card :bordered="false" class="w-full md:w-1/2">
<template #title>
@@ -155,16 +189,17 @@ onMounted(async () => {
<span class="ml-2 text-lg font-medium">连接管理</span>
</div>
</template>
<div class="flex items-center mb-4 bg-gray-50 p-3 rounded-lg">
<div class="mb-4 flex items-center rounded-lg bg-gray-50 p-3">
<span class="mr-4 font-medium">连接状态:</span>
<Tag :color="getTagColor" class="px-3 py-1">{{ getStatusText }}</Tag>
</div>
<div class="flex space-x-2 mb-6">
<Input
v-model:value="server"
<div class="mb-6 flex space-x-2">
<Input
v-model:value="server"
disabled
class="rounded-md"
size="large">
size="large"
>
<template #addonBefore>
<span class="text-gray-600">服务地址</span>
</template>
@@ -179,17 +214,18 @@ onMounted(async () => {
{{ getIsOpen ? '关闭连接' : '开启连接' }}
</Button>
</div>
<Divider>
<span class="text-gray-500">消息发送</span>
</Divider>
<Select
v-model:value="sendUserId"
class="w-full mb-3"
<Select
v-model:value="sendUserId"
class="mb-3 w-full"
size="large"
placeholder="请选择接收人"
:disabled="!getIsOpen">
:disabled="!getIsOpen"
>
<Select.Option key="" value="" label="所有人">
<div class="flex items-center">
<Avatar size="small" class="mr-2"></Avatar>
@@ -203,61 +239,72 @@ onMounted(async () => {
:label="user.nickname"
>
<div class="flex items-center">
<Avatar size="small" class="mr-2">{{ user.nickname.slice(0, 1) }}</Avatar>
<Avatar size="small" class="mr-2">
{{ user.nickname.slice(0, 1) }}
</Avatar>
<span>{{ user.nickname }}</span>
</div>
</Select.Option>
</Select>
<Input.TextArea
v-model:value="sendText"
:auto-size="{ minRows: 3, maxRows: 6 }"
:disabled="!getIsOpen"
class="rounded-lg border-1 border-gray-300"
allowClear
class="border-1 rounded-lg border-gray-300"
allow-clear
placeholder="请输入你要发送的消息..."
/>
<Button
:disabled="!getIsOpen"
block
class="mt-4"
type="primary"
<Button
:disabled="!getIsOpen"
block
class="mt-4"
type="primary"
size="large"
@click="handlerSend">
@click="handlerSend"
>
<template #icon>
<span class="i-ant-design:send-outlined mr-1" />
<span class="i-ant-design:send-outlined mr-1"></span>
</template>
发送消息
</Button>
</Card>
<!-- 右侧消息记录 -->
<Card :bordered="false" class="w-full md:w-1/2">
<template #title>
<div class="flex items-center">
<span class="i-ant-design:message-outlined mr-2 text-lg" />
<span class="i-ant-design:message-outlined mr-2 text-lg"></span>
<span class="text-lg font-medium">消息记录</span>
<Tag v-if="messageList.length > 0" class="ml-2">{{ messageList.length }} </Tag>
<Tag v-if="messageList.length > 0" class="ml-2">
{{ messageList.length }}
</Tag>
</div>
</template>
<div class="h-96 overflow-auto p-2 bg-gray-50 rounded-lg">
<div class="h-96 overflow-auto rounded-lg bg-gray-50 p-2">
<Empty v-if="messageList.length === 0" description="暂无消息记录" />
<div v-else class="space-y-3">
<div
v-for="msg in messageReverseList"
:key="msg.time"
class="p-3 bg-white rounded-lg shadow-sm"
<div
v-for="msg in messageReverseList"
:key="msg.time"
class="rounded-lg bg-white p-3 shadow-sm"
>
<div class="flex items-center justify-between mb-1">
<div class="mb-1 flex items-center justify-between">
<div class="flex items-center">
<Badge :color="getMessageBadgeColor(msg.type)" />
<span class="ml-1 text-gray-600 font-medium">{{ getMessageTypeText(msg.type) }}</span>
<span v-if="msg.userId" class="ml-2 text-gray-500">用户 ID: {{ msg.userId }}</span>
<span class="ml-1 font-medium text-gray-600">{{
getMessageTypeText(msg.type)
}}</span>
<span v-if="msg.userId" class="ml-2 text-gray-500"
>用户 ID: {{ msg.userId }}</span
>
</div>
<span class="text-xs text-gray-400">{{ formatDate(msg.time) }}</span>
<span class="text-xs text-gray-400">{{
formatDate(msg.time)
}}</span>
</div>
<div class="mt-2 text-gray-800 break-words">
<div class="mt-2 break-words text-gray-800">
{{ msg.text }}
</div>
</div>