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

pull/114/head
jason 2025-05-24 23:31:18 +08:00
commit 8e88db8d11
139 changed files with 3062 additions and 2878 deletions

View File

@ -1,4 +1,4 @@
/* 来自 @vben/plugins/vxe-table style.css TODO @puhui999可以写下目的哈 */
/* 来自 @vben/plugins/vxe-table style.css,覆盖 vxe-table 原有的样式定义,使用 vben 的样式主题 */
:root {
--vxe-ui-font-color: hsl(var(--foreground));
--vxe-ui-font-primary-color: hsl(var(--primary));

View File

@ -300,4 +300,5 @@ export type OnActionClickParams<T = Recordable<any>> = {
export type OnActionClickFn<T = Recordable<any>> = (
params: OnActionClickParams<T>,
) => void;
export * from '#/components/table-action';
export type * from '@vben/plugins/vxe-table';

View File

@ -9,7 +9,7 @@ export namespace Demo01ContactApi {
export interface Demo01Contact {
id: number; // 编号
name?: string; // 名字
sex?: boolean; // 性别
sex?: number; // 性别
birthday?: Dayjs | string; // 出生年
description?: string; // 简介
avatar: string; // 头像
@ -46,6 +46,14 @@ export function deleteDemo01Contact(id: number) {
return requestClient.delete(`/infra/demo01-contact/delete?id=${id}`);
}
/** 批量删除示例联系人 */
// TODO @puhui999ByIds这种按照约定是不带的针对 Id 的情况哈。
export function deleteDemo01ContactListByIds(ids: number[]) {
return requestClient.delete(
`/infra/demo01-contact/delete-list?ids=${ids.join(',')}`,
);
}
/** 导出示例联系人 */
export function exportDemo01Contact(params: any) {
return requestClient.download('/infra/demo01-contact/export-excel', params);

View File

@ -34,7 +34,7 @@ export namespace Demo03StudentApi {
/** 查询学生分页 */
export function getDemo03StudentPage(params: PageParam) {
return requestClient.get<PageResult<Demo03StudentApi.Demo03Student>>(
'/infra/demo03-student/page',
'/infra/demo03-student-erp/page',
{ params },
);
}
@ -42,28 +42,38 @@ export function getDemo03StudentPage(params: PageParam) {
/** 查询学生详情 */
export function getDemo03Student(id: number) {
return requestClient.get<Demo03StudentApi.Demo03Student>(
`/infra/demo03-student/get?id=${id}`,
`/infra/demo03-student-erp/get?id=${id}`,
);
}
/** 新增学生 */
export function createDemo03Student(data: Demo03StudentApi.Demo03Student) {
return requestClient.post('/infra/demo03-student/create', data);
return requestClient.post('/infra/demo03-student-erp/create', data);
}
/** 修改学生 */
export function updateDemo03Student(data: Demo03StudentApi.Demo03Student) {
return requestClient.put('/infra/demo03-student/update', data);
return requestClient.put('/infra/demo03-student-erp/update', data);
}
/** 删除学生 */
export function deleteDemo03Student(id: number) {
return requestClient.delete(`/infra/demo03-student/delete?id=${id}`);
return requestClient.delete(`/infra/demo03-student-erp/delete?id=${id}`);
}
/** 批量删除学生 */
export function deleteDemo03StudentListByIds(ids: number[]) {
return requestClient.delete(
`/infra/demo03-student-erp/delete-list?ids=${ids.join(',')}`,
);
}
/** 导出学生 */
export function exportDemo03Student(params: any) {
return requestClient.download('/infra/demo03-student/export-excel', params);
return requestClient.download(
'/infra/demo03-student-erp/export-excel',
params,
);
}
// ==================== 子表(学生课程) ====================
@ -71,33 +81,44 @@ export function exportDemo03Student(params: any) {
/** 获得学生课程分页 */
export function getDemo03CoursePage(params: PageParam) {
return requestClient.get<PageResult<Demo03StudentApi.Demo03Course>>(
`/infra/demo03-student/demo03-course/page`,
{
params,
},
`/infra/demo03-student-erp/demo03-course/page`,
{ params },
);
}
/** 新增学生课程 */
export function createDemo03Course(data: Demo03StudentApi.Demo03Course) {
return requestClient.post(`/infra/demo03-student/demo03-course/create`, data);
return requestClient.post(
`/infra/demo03-student-erp/demo03-course/create`,
data,
);
}
/** 修改学生课程 */
export function updateDemo03Course(data: Demo03StudentApi.Demo03Course) {
return requestClient.put(`/infra/demo03-student/demo03-course/update`, data);
return requestClient.put(
`/infra/demo03-student-erp/demo03-course/update`,
data,
);
}
/** 删除学生课程 */
export function deleteDemo03Course(id: number) {
return requestClient.delete(
`/infra/demo03-student/demo03-course/delete?id=${id}`,
`/infra/demo03-student-erp/demo03-course/delete?id=${id}`,
);
}
/** 批量删除学生课程 */
export function deleteDemo03CourseListByIds(ids: number[]) {
return requestClient.delete(
`/infra/demo03-student-erp/demo03-course/delete-list?ids=${ids.join(',')}`,
);
}
/** 获得学生课程 */
export function getDemo03Course(id: number) {
return requestClient.get<Demo03StudentApi.Demo03Course>(
`/infra/demo03-student/demo03-course/get?id=${id}`,
`/infra/demo03-student-erp/demo03-course/get?id=${id}`,
);
}
@ -106,32 +127,43 @@ export function getDemo03Course(id: number) {
/** 获得学生班级分页 */
export function getDemo03GradePage(params: PageParam) {
return requestClient.get<PageResult<Demo03StudentApi.Demo03Grade>>(
`/infra/demo03-student/demo03-grade/page`,
{
params,
},
`/infra/demo03-student-erp/demo03-grade/page`,
{ params },
);
}
/** 新增学生班级 */
export function createDemo03Grade(data: Demo03StudentApi.Demo03Grade) {
return requestClient.post(`/infra/demo03-student/demo03-grade/create`, data);
return requestClient.post(
`/infra/demo03-student-erp/demo03-grade/create`,
data,
);
}
/** 修改学生班级 */
export function updateDemo03Grade(data: Demo03StudentApi.Demo03Grade) {
return requestClient.put(`/infra/demo03-student/demo03-grade/update`, data);
return requestClient.put(
`/infra/demo03-student-erp/demo03-grade/update`,
data,
);
}
/** 删除学生班级 */
export function deleteDemo03Grade(id: number) {
return requestClient.delete(
`/infra/demo03-student/demo03-grade/delete?id=${id}`,
`/infra/demo03-student-erp/demo03-grade/delete?id=${id}`,
);
}
/** 批量删除学生班级 */
export function deleteDemo03GradeListByIds(ids: number[]) {
return requestClient.delete(
`/infra/demo03-student-erp/demo03-grade/delete-list?ids=${ids.join(',')}`,
);
}
/** 获得学生班级 */
export function getDemo03Grade(id: number) {
return requestClient.get<Demo03StudentApi.Demo03Grade>(
`/infra/demo03-student/demo03-grade/get?id=${id}`,
`/infra/demo03-student-erp/demo03-grade/get?id=${id}`,
);
}

View File

@ -1,3 +1,5 @@
import type { Dayjs } from 'dayjs';
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
@ -24,7 +26,7 @@ export namespace Demo03StudentApi {
id: number; // 编号
name?: string; // 名字
sex?: number; // 性别
birthday?: Date; // 出生日期
birthday?: Dayjs | string; // 出生日期
description?: string; // 简介
demo03courses?: Demo03Course[];
demo03grade?: Demo03Grade;
@ -34,7 +36,7 @@ export namespace Demo03StudentApi {
/** 查询学生分页 */
export function getDemo03StudentPage(params: PageParam) {
return requestClient.get<PageResult<Demo03StudentApi.Demo03Student>>(
'/infra/demo03-student/page',
'/infra/demo03-student-inner/page',
{ params },
);
}
@ -42,28 +44,38 @@ export function getDemo03StudentPage(params: PageParam) {
/** 查询学生详情 */
export function getDemo03Student(id: number) {
return requestClient.get<Demo03StudentApi.Demo03Student>(
`/infra/demo03-student/get?id=${id}`,
`/infra/demo03-student-inner/get?id=${id}`,
);
}
/** 新增学生 */
export function createDemo03Student(data: Demo03StudentApi.Demo03Student) {
return requestClient.post('/infra/demo03-student/create', data);
return requestClient.post('/infra/demo03-student-inner/create', data);
}
/** 修改学生 */
export function updateDemo03Student(data: Demo03StudentApi.Demo03Student) {
return requestClient.put('/infra/demo03-student/update', data);
return requestClient.put('/infra/demo03-student-inner/update', data);
}
/** 删除学生 */
export function deleteDemo03Student(id: number) {
return requestClient.delete(`/infra/demo03-student/delete?id=${id}`);
return requestClient.delete(`/infra/demo03-student-inner/delete?id=${id}`);
}
/** 批量删除学生 */
export function deleteDemo03StudentListByIds(ids: number[]) {
return requestClient.delete(
`/infra/demo03-student-inner/delete-list?ids=${ids.join(',')}`,
);
}
/** 导出学生 */
export function exportDemo03Student(params: any) {
return requestClient.download('/infra/demo03-student/export-excel', params);
return requestClient.download(
'/infra/demo03-student-inner/export-excel',
params,
);
}
// ==================== 子表(学生课程) ====================
@ -71,7 +83,7 @@ export function exportDemo03Student(params: any) {
/** 获得学生课程列表 */
export function getDemo03CourseListByStudentId(studentId: number) {
return requestClient.get<Demo03StudentApi.Demo03Course[]>(
`/infra/demo03-student/demo03-course/list-by-student-id?studentId=${studentId}`,
`/infra/demo03-student-inner/demo03-course/list-by-student-id?studentId=${studentId}`,
);
}
@ -80,6 +92,6 @@ export function getDemo03CourseListByStudentId(studentId: number) {
/** 获得学生班级 */
export function getDemo03GradeByStudentId(studentId: number) {
return requestClient.get<Demo03StudentApi.Demo03Grade>(
`/infra/demo03-student/demo03-grade/get-by-student-id?studentId=${studentId}`,
`/infra/demo03-student-inner/demo03-grade/get-by-student-id?studentId=${studentId}`,
);
}

View File

@ -36,7 +36,7 @@ export namespace Demo03StudentApi {
/** 查询学生分页 */
export function getDemo03StudentPage(params: PageParam) {
return requestClient.get<PageResult<Demo03StudentApi.Demo03Student>>(
'/infra/demo03-student/page',
'/infra/demo03-student-normal/page',
{ params },
);
}
@ -44,28 +44,38 @@ export function getDemo03StudentPage(params: PageParam) {
/** 查询学生详情 */
export function getDemo03Student(id: number) {
return requestClient.get<Demo03StudentApi.Demo03Student>(
`/infra/demo03-student/get?id=${id}`,
`/infra/demo03-student-normal/get?id=${id}`,
);
}
/** 新增学生 */
export function createDemo03Student(data: Demo03StudentApi.Demo03Student) {
return requestClient.post('/infra/demo03-student/create', data);
return requestClient.post('/infra/demo03-student-normal/create', data);
}
/** 修改学生 */
export function updateDemo03Student(data: Demo03StudentApi.Demo03Student) {
return requestClient.put('/infra/demo03-student/update', data);
return requestClient.put('/infra/demo03-student-normal/update', data);
}
/** 删除学生 */
export function deleteDemo03Student(id: number) {
return requestClient.delete(`/infra/demo03-student/delete?id=${id}`);
return requestClient.delete(`/infra/demo03-student-normal/delete?id=${id}`);
}
/** 批量删除学生 */
export function deleteDemo03StudentListByIds(ids: number[]) {
return requestClient.delete(
`/infra/demo03-student-normal/delete-list?ids=${ids.join(',')}`,
);
}
/** 导出学生 */
export function exportDemo03Student(params: any) {
return requestClient.download('/infra/demo03-student/export-excel', params);
return requestClient.download(
'/infra/demo03-student-normal/export-excel',
params,
);
}
// ==================== 子表(学生课程) ====================
@ -73,7 +83,7 @@ export function exportDemo03Student(params: any) {
/** 获得学生课程列表 */
export function getDemo03CourseListByStudentId(studentId: number) {
return requestClient.get<Demo03StudentApi.Demo03Course[]>(
`/infra/demo03-student/demo03-course/list-by-student-id?studentId=${studentId}`,
`/infra/demo03-student-normal/demo03-course/list-by-student-id?studentId=${studentId}`,
);
}
@ -82,6 +92,6 @@ export function getDemo03CourseListByStudentId(studentId: number) {
/** 获得学生班级 */
export function getDemo03GradeByStudentId(studentId: number) {
return requestClient.get<Demo03StudentApi.Demo03Grade>(
`/infra/demo03-student/demo03-grade/get-by-student-id?studentId=${studentId}`,
`/infra/demo03-student-normal/demo03-grade/get-by-student-id?studentId=${studentId}`,
);
}

View File

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

View File

@ -1,3 +1,4 @@
// TODO @
<script lang="ts" setup>
import type { DataNode } from 'ant-design-vue/es/tree';

View File

@ -0,0 +1,13 @@
export const ACTION_ICON = {
DOWNLOAD: 'lucide:download',
UPLOAD: 'lucide:upload',
ADD: 'lucide:plus',
EDIT: 'lucide:edit',
DELETE: 'lucide:trash',
REFRESH: 'lucide:refresh-cw',
SEARCH: 'lucide:search',
FILTER: 'lucide:filter',
MORE: 'lucide:ellipsis-vertical',
VIEW: 'lucide:eye',
COPY: 'lucide:copy',
};

View File

@ -1,4 +1,4 @@
export { default as TableAction } from './table-action.vue';
export * from './icons';
export const ACTION_KEY = 'action_key_msg';
export { default as TableAction } from './table-action.vue';
export * from './typing';

View File

@ -1,7 +1,5 @@
<!-- add by 星语参考 vben2 的方式增加 TableAction 组件 -->
<script setup lang="ts">
import type { ButtonType } from 'ant-design-vue/es/button';
import type { PropType } from 'vue';
import type { ActionItem, PopConfirm } from './typing';
@ -69,7 +67,7 @@ const getActions = computed(() => {
.map((action) => {
const { popConfirm } = action;
return {
type: 'link' as ButtonType,
type: action.type || 'link',
...action,
...popConfirm,
onConfirm: popConfirm?.confirm,
@ -183,10 +181,10 @@ function handleMenuClick(e: any) {
<Dropdown v-if="getDropdownList.length > 0" :trigger="['hover']">
<slot name="more">
<Button size="small" type="link">
<Button :type="getDropdownList[0].type">
<template #icon>
{{ $t('page.action.more') }}
<IconifyIcon class="icon-more" icon="ant-design:more-outlined" />
<IconifyIcon icon="lucide:ellipsis-vertical" />
</template>
</Button>
</slot>
@ -230,7 +228,7 @@ function handleMenuClick(e: any) {
</template>
<style lang="scss">
.table-actions {
.ant-btn {
.ant-btn-link {
padding: 4px;
margin-left: 0;
}

View File

@ -1,4 +1,7 @@
import type { ButtonProps } from 'ant-design-vue/es/button/buttonTypes';
import type {
ButtonProps,
ButtonType,
} from 'ant-design-vue/es/button/buttonTypes';
import type { TooltipProps } from 'ant-design-vue/es/tooltip/Tooltip';
export interface PopConfirm {
@ -13,6 +16,7 @@ export interface PopConfirm {
export interface ActionItem extends ButtonProps {
onClick?: () => void;
type?: ButtonType;
label?: string;
color?: 'error' | 'success' | 'warning';
icon?: string;

View File

@ -3,7 +3,10 @@ import type { TableToolbar } from '#/components/table-toolbar';
import { ref, watch } from 'vue';
// TODO @puhui999这里的注释、目的写下
/**
* vxe
* 使 vxe-table
*/
export function useTableToolbar() {
const hiddenSearchBar = ref(false); // 隐藏搜索栏
const tableToolbarRef = ref<InstanceType<typeof TableToolbar>>();
@ -15,7 +18,7 @@ export function useTableToolbar() {
const table = tableRef.value;
const tableToolbar = tableToolbarRef.value;
if (table && tableToolbar) {
// TODO @puhui999通过 nexttick 可以解决么?
// 延迟 1 秒,确保 toolbar 组件已经挂载
setTimeout(async () => {
const toolbar = tableToolbar.getToolbarRef();
if (!toolbar) {
@ -29,10 +32,9 @@ export function useTableToolbar() {
watch(
() => tableRef.value,
(val) => {
async (val) => {
if (!val || isBound.value) return;
// TODO @puhui999这里要处理下 promise 的告警么?
bindTableToolbar();
await bindTableToolbar();
},
{ immediate: true },
);

View File

@ -1,4 +1,3 @@
import type { DefaultOptionType } from 'ant-design-vue/es/select';
// TODO @芋艿:后续再优化
// TODO @芋艿:可以共享么?
@ -14,11 +13,11 @@ import { useDictStore } from '#/store';
type ColorType = 'error' | 'info' | 'success' | 'warning';
export interface DictDataType {
dictType: string;
dictType?: string;
label: string;
value: boolean | number | string;
colorType: ColorType;
cssClass: string;
colorType?: ColorType;
cssClass?: string;
}
export interface NumberDictDataType extends DictDataType {
@ -62,14 +61,13 @@ function getDictObj(dictType: string, value: any) {
* @param valueType string
* @returns
*/
// TODO @puhui999貌似可以定义一个类型不使用 any[]
function getDictOptions(
dictType: string,
valueType: 'boolean' | 'number' | 'string' = 'string',
): any[] {
): DictDataType[] {
const dictStore = useDictStore();
const dictOpts = dictStore.getDictOptions(dictType);
const dictOptions: DefaultOptionType = [];
const dictOptions: DictDataType[] = [];
if (dictOpts.length > 0) {
let dictValue: boolean | number | string = '';
dictOpts.forEach((d) => {

View File

@ -34,6 +34,7 @@ import ProcessDesign from './modules/process-design.vue';
defineOptions({ name: 'BpmModelCreate' });
// TODO apps 使 @utils/constant.ts @
// TODO @jason/Users/yunai/Java/yudao-ui-admin-vben-v5/apps/web-antd/src/utils/constants.ts apps
const BpmModelType = {
BPMN: 10, // BPMN
SIMPLE: 20, //

View File

@ -162,6 +162,7 @@ const handleCategorySortSubmit = async () => {
<template>
<Page auto-content-height>
<!-- TODO @jaosn没头像的图标展示文字头像哈 -->
<!-- 流程分类表单弹窗 -->
<CategoryFormModal @success="getList" />
<Card

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,17 +5,18 @@ import type {
} from '#/adapter/vxe-table';
import type { Demo01ContactApi } from '#/api/infra/demo/demo01';
import { h } from 'vue';
import { h, ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Download, Plus } from '@vben/icons';
import { downloadFileFromBlobPart } from '@vben/utils';
import { Download, Plus, Trash2 } from '@vben/icons';
import { downloadFileFromBlobPart, isEmpty } from '@vben/utils';
import { Button, message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteDemo01Contact,
deleteDemo01ContactListByIds,
exportDemo01Contact,
getDemo01ContactPage,
} from '#/api/infra/demo/demo01';
@ -55,11 +56,38 @@ async function onDelete(row: Demo01ContactApi.Demo01Contact) {
await deleteDemo01Contact(row.id as number);
message.success($t('ui.actionMessage.deleteSuccess', [row.id]));
onRefresh();
} catch {
} finally {
hideLoading();
}
}
/** 批量删除示例联系人 */
async function onDeleteBatch() {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting'),
duration: 0,
key: 'action_process_msg',
});
try {
await deleteDemo01ContactListByIds(deleteIds.value);
message.success($t('ui.actionMessage.deleteSuccess'));
onRefresh();
} finally {
hideLoading();
}
}
// TODO @puhui999 handleRowCheckboxChange
// TODO @puhui999deleteIds => checkedIds
const deleteIds = ref<number[]>([]); // ID
function setDeleteIds({
records,
}: {
records: Demo01ContactApi.Demo01Contact[];
}) {
deleteIds.value = records.map((item) => item.id);
}
/** 导出表格 */
async function onExport() {
const data = await exportDemo01Contact(await gridApi.formApi.getValues());
@ -113,6 +141,10 @@ const [Grid, gridApi] = useVbenVxeGrid({
search: true,
},
} as VxeTableGridOptions<Demo01ContactApi.Demo01Contact>,
gridEvents: {
checkboxAll: setDeleteIds,
checkboxChange: setDeleteIds,
},
});
</script>
@ -139,6 +171,17 @@ const [Grid, gridApi] = useVbenVxeGrid({
>
{{ $t('ui.actionTitle.export') }}
</Button>
<Button
:icon="h(Trash2)"
type="primary"
danger
class="ml-2"
:disabled="isEmpty(deleteIds)"
@click="onDeleteBatch"
v-access:code="['infra:demo01-contact:delete']"
>
批量删除
</Button>
</template>
</Grid>
</Page>

View File

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

View File

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

View File

@ -107,6 +107,7 @@ export function useGridColumns(
onActionClick?: OnActionClickFn<Demo03StudentApi.Demo03Student>,
): VxeTableGridOptions<Demo03StudentApi.Demo03Student>['columns'] {
return [
{ type: 'checkbox', width: 40 },
{
field: 'id',
title: '编号',
@ -149,7 +150,6 @@ export function useGridColumns(
minWidth: 200,
align: 'center',
fixed: 'right',
headerAlign: 'center',
showOverflow: false,
cellRender: {
attrs: {
@ -254,6 +254,7 @@ export function useDemo03CourseGridColumns(
onActionClick?: OnActionClickFn<Demo03StudentApi.Demo03Course>,
): VxeTableGridOptions<Demo03StudentApi.Demo03Course>['columns'] {
return [
{ type: 'checkbox', width: 40 },
{
field: 'id',
title: '编号',
@ -286,7 +287,7 @@ export function useDemo03CourseGridColumns(
minWidth: 200,
align: 'center',
fixed: 'right',
headerAlign: 'center',
showOverflow: false,
cellRender: {
attrs: {
@ -391,6 +392,7 @@ export function useDemo03GradeGridColumns(
onActionClick?: OnActionClickFn<Demo03StudentApi.Demo03Grade>,
): VxeTableGridOptions<Demo03StudentApi.Demo03Grade>['columns'] {
return [
{ type: 'checkbox', width: 40 },
{
field: 'id',
title: '编号',
@ -423,7 +425,7 @@ export function useDemo03GradeGridColumns(
minWidth: 200,
align: 'center',
fixed: 'right',
headerAlign: 'center',
showOverflow: false,
cellRender: {
attrs: {

View File

@ -8,14 +8,15 @@ import type { Demo03StudentApi } from '#/api/infra/demo/demo03/erp';
import { h, ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Download, Plus } from '@vben/icons';
import { downloadFileFromBlobPart } from '@vben/utils';
import { Download, Plus, Trash2 } from '@vben/icons';
import { downloadFileFromBlobPart, isEmpty } from '@vben/utils';
import { Button, message, Tabs } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteDemo03Student,
deleteDemo03StudentListByIds,
exportDemo03Student,
getDemo03StudentPage,
} from '#/api/infra/demo/demo03/erp';
@ -61,11 +62,36 @@ async function onDelete(row: Demo03StudentApi.Demo03Student) {
await deleteDemo03Student(row.id as number);
message.success($t('ui.actionMessage.deleteSuccess', [row.id]));
onRefresh();
} catch {
} finally {
hideLoading();
}
}
/** 批量删除学生 */
async function onDeleteBatch() {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting'),
duration: 0,
key: 'action_process_msg',
});
try {
await deleteDemo03StudentListByIds(deleteIds.value);
message.success($t('ui.actionMessage.deleteSuccess'));
onRefresh();
} finally {
hideLoading();
}
}
const deleteIds = ref<number[]>([]); // ID
function setDeleteIds({
records,
}: {
records: Demo03StudentApi.Demo03Grade[];
}) {
deleteIds.value = records.map((item) => item.id);
}
/** 导出表格 */
async function onExport() {
const data = await exportDemo03Student(await gridApi.formApi.getValues());
@ -124,6 +150,8 @@ const [Grid, gridApi] = useVbenVxeGrid({
cellClick: ({ row }: { row: Demo03StudentApi.Demo03Student }) => {
selectDemo03Student.value = row;
},
checkboxAll: setDeleteIds,
checkboxChange: setDeleteIds,
},
});
</script>
@ -152,6 +180,17 @@ const [Grid, gridApi] = useVbenVxeGrid({
>
{{ $t('ui.actionTitle.export') }}
</Button>
<Button
:icon="h(Trash2)"
type="primary"
danger
class="ml-2"
:disabled="isEmpty(deleteIds)"
@click="onDeleteBatch"
v-access:code="['infra:demo03-student:delete']"
>
批量删除
</Button>
</template>
</Grid>

View File

@ -5,16 +5,18 @@ import type {
} from '#/adapter/vxe-table';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/erp';
import { h, nextTick, watch } from 'vue';
import { h, nextTick, ref, watch } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { Plus } from '@vben/icons';
import { Plus, Trash2 } from '@vben/icons';
import { isEmpty } from '@vben/utils';
import { Button, message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteDemo03Course,
deleteDemo03CourseListByIds,
getDemo03CoursePage,
} from '#/api/infra/demo/demo03/erp';
import { $t } from '#/locales';
@ -59,11 +61,36 @@ async function onDelete(row: Demo03StudentApi.Demo03Course) {
await deleteDemo03Course(row.id as number);
message.success($t('ui.actionMessage.deleteSuccess', [row.id]));
onRefresh();
} catch {
} finally {
hideLoading();
}
}
/** 批量删除学生课程 */
async function onDeleteBatch() {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting'),
duration: 0,
key: 'action_process_msg',
});
try {
await deleteDemo03CourseListByIds(deleteIds.value);
message.success($t('ui.actionMessage.deleteSuccess'));
onRefresh();
} finally {
hideLoading();
}
}
const deleteIds = ref<number[]>([]); // ID
function setDeleteIds({
records,
}: {
records: Demo03StudentApi.Demo03Course[];
}) {
deleteIds.value = records.map((item) => item.id);
}
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
@ -115,6 +142,10 @@ const [Grid, gridApi] = useVbenVxeGrid({
isHover: true,
},
} as VxeTableGridOptions<Demo03StudentApi.Demo03Course>,
gridEvents: {
checkboxAll: setDeleteIds,
checkboxChange: setDeleteIds,
},
});
/** 刷新表格 */
@ -148,6 +179,17 @@ watch(
>
{{ $t('ui.actionTitle.create', ['学生课程']) }}
</Button>
<Button
:icon="h(Trash2)"
type="primary"
danger
class="ml-2"
:disabled="isEmpty(deleteIds)"
@click="onDeleteBatch"
v-access:code="['infra:demo03-student:delete']"
>
批量删除
</Button>
</template>
</Grid>
</template>

View File

@ -5,16 +5,18 @@ import type {
} from '#/adapter/vxe-table';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/erp';
import { h, nextTick, watch } from 'vue';
import { h, nextTick, ref, watch } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { Plus } from '@vben/icons';
import { Plus, Trash2 } from '@vben/icons';
import { isEmpty } from '@vben/utils';
import { Button, message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteDemo03Grade,
deleteDemo03GradeListByIds,
getDemo03GradePage,
} from '#/api/infra/demo/demo03/erp';
import { $t } from '#/locales';
@ -59,11 +61,36 @@ async function onDelete(row: Demo03StudentApi.Demo03Grade) {
await deleteDemo03Grade(row.id as number);
message.success($t('ui.actionMessage.deleteSuccess', [row.id]));
onRefresh();
} catch {
} finally {
hideLoading();
}
}
/** 批量删除学生班级 */
async function onDeleteBatch() {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting'),
duration: 0,
key: 'action_process_msg',
});
try {
await deleteDemo03GradeListByIds(deleteIds.value);
message.success($t('ui.actionMessage.deleteSuccess'));
onRefresh();
} finally {
hideLoading();
}
}
const deleteIds = ref<number[]>([]); // ID
function setDeleteIds({
records,
}: {
records: Demo03StudentApi.Demo03Grade[];
}) {
deleteIds.value = records.map((item) => item.id);
}
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
@ -115,6 +142,10 @@ const [Grid, gridApi] = useVbenVxeGrid({
isHover: true,
},
} as VxeTableGridOptions<Demo03StudentApi.Demo03Grade>,
gridEvents: {
checkboxAll: setDeleteIds,
checkboxChange: setDeleteIds,
},
});
/** 刷新表格 */
@ -148,6 +179,17 @@ watch(
>
{{ $t('ui.actionTitle.create', ['学生班级']) }}
</Button>
<Button
:icon="h(Trash2)"
type="primary"
danger
class="ml-2"
:disabled="isEmpty(deleteIds)"
@click="onDeleteBatch"
v-access:code="['infra:demo03-student:delete']"
>
批量删除
</Button>
</template>
</Grid>
</template>

View File

@ -64,7 +64,6 @@ const [Modal, modalApi] = useVbenModal({
formData.value = undefined;
return;
}
//
let data = modalApi.getData<Demo03StudentApi.Demo03Student>();
if (!data) {

View File

@ -107,6 +107,7 @@ export function useGridColumns(
onActionClick?: OnActionClickFn<Demo03StudentApi.Demo03Student>,
): VxeTableGridOptions<Demo03StudentApi.Demo03Student>['columns'] {
return [
{ type: 'checkbox', width: 40 },
{ type: 'expand', width: 80, slots: { content: 'expand_content' } },
{
field: 'id',
@ -150,7 +151,6 @@ export function useGridColumns(
minWidth: 200,
align: 'center',
fixed: 'right',
headerAlign: 'center',
showOverflow: false,
cellRender: {
attrs: {
@ -199,7 +199,6 @@ export function useDemo03CourseGridEditColumns(
minWidth: 60,
align: 'center',
fixed: 'right',
headerAlign: 'center',
showOverflow: false,
cellRender: {
attrs: {

View File

@ -8,14 +8,15 @@ import type { Demo03StudentApi } from '#/api/infra/demo/demo03/inner';
import { h, ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Download, Plus } from '@vben/icons';
import { downloadFileFromBlobPart } from '@vben/utils';
import { Download, Plus, Trash2 } from '@vben/icons';
import { downloadFileFromBlobPart, isEmpty } from '@vben/utils';
import { Button, message, Tabs } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteDemo03Student,
deleteDemo03StudentListByIds,
exportDemo03Student,
getDemo03StudentPage,
} from '#/api/infra/demo/demo03/inner';
@ -60,11 +61,36 @@ async function onDelete(row: Demo03StudentApi.Demo03Student) {
await deleteDemo03Student(row.id as number);
message.success($t('ui.actionMessage.deleteSuccess', [row.id]));
onRefresh();
} catch {
} finally {
hideLoading();
}
}
/** 批量删除学生 */
async function onDeleteBatch() {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting'),
duration: 0,
key: 'action_process_msg',
});
try {
await deleteDemo03StudentListByIds(deleteIds.value);
message.success($t('ui.actionMessage.deleteSuccess'));
onRefresh();
} finally {
hideLoading();
}
}
const deleteIds = ref<number[]>([]); // ID
function setDeleteIds({
records,
}: {
records: Demo03StudentApi.Demo03Student[];
}) {
deleteIds.value = records.map((item) => item.id);
}
/** 导出表格 */
async function onExport() {
const data = await exportDemo03Student(await gridApi.formApi.getValues());
@ -118,6 +144,10 @@ const [Grid, gridApi] = useVbenVxeGrid({
search: true,
},
} as VxeTableGridOptions<Demo03StudentApi.Demo03Student>,
gridEvents: {
checkboxAll: setDeleteIds,
checkboxChange: setDeleteIds,
},
});
</script>
@ -155,6 +185,17 @@ const [Grid, gridApi] = useVbenVxeGrid({
>
{{ $t('ui.actionTitle.export') }}
</Button>
<Button
:icon="h(Trash2)"
type="primary"
danger
class="ml-2"
:disabled="isEmpty(deleteIds)"
@click="onDeleteBatch"
v-access:code="['infra:demo03-student:delete']"
>
批量删除
</Button>
</template>
</Grid>
</Page>

View File

@ -16,18 +16,18 @@ const props = defineProps<{
const [Grid, gridApi] = useVbenVxeGrid({
gridOptions: {
columns: useDemo03CourseGridColumns(),
height: 'auto',
rowConfig: {
keyField: 'id',
isHover: true,
},
pagerConfig: {
enabled: false,
},
toolbarConfig: {
enabled: false,
},
} as VxeTableGridOptions<Demo03StudentApi.Demo03Student>,
height: '600px',
rowConfig: {
keyField: 'id',
isHover: true,
},
} as VxeTableGridOptions<Demo03StudentApi.Demo03Course>,
});
/** 刷新表格 */

View File

@ -11,6 +11,13 @@ const props = defineProps<{
}>();
const [Form, formApi] = useVbenForm({
commonConfig: {
componentProps: {
class: 'w-full',
},
formItemClass: 'col-span-2',
labelWidth: 80,
},
layout: 'horizontal',
schema: useDemo03GradeFormSchema(),
showDefaultActions: false,

View File

@ -16,18 +16,18 @@ const props = defineProps<{
const [Grid, gridApi] = useVbenVxeGrid({
gridOptions: {
columns: useDemo03GradeGridColumns(),
height: 'auto',
rowConfig: {
keyField: 'id',
isHover: true,
},
pagerConfig: {
enabled: false,
},
toolbarConfig: {
enabled: false,
},
} as VxeTableGridOptions<Demo03StudentApi.Demo03Student>,
height: '600px',
rowConfig: {
keyField: 'id',
isHover: true,
},
} as VxeTableGridOptions<Demo03StudentApi.Demo03Grade>,
});
/** 刷新表格 */

View File

@ -61,8 +61,8 @@ const [Modal, modalApi] = useVbenModal({
//
const data = (await formApi.getValues()) as Demo03StudentApi.Demo03Student;
//
data.demo03courses = demo03CourseFormRef.value?.getData();
data.demo03grade = await demo03GradeFormRef.value?.getValues();
data.demo03Courses = demo03CourseFormRef.value?.getData();
data.demo03Grade = await demo03GradeFormRef.value?.getValues();
try {
await (formData.value?.id
? updateDemo03Student(data)
@ -80,7 +80,6 @@ const [Modal, modalApi] = useVbenModal({
formData.value = undefined;
return;
}
//
let data = modalApi.getData<Demo03StudentApi.Demo03Student>();
if (!data) {

View File

@ -107,6 +107,7 @@ export function useGridColumns(
onActionClick?: OnActionClickFn<Demo03StudentApi.Demo03Student>,
): VxeTableGridOptions<Demo03StudentApi.Demo03Student>['columns'] {
return [
{ type: 'checkbox', width: 40 },
{
field: 'id',
title: '编号',
@ -149,7 +150,6 @@ export function useGridColumns(
minWidth: 200,
align: 'center',
fixed: 'right',
headerAlign: 'center',
showOverflow: false,
cellRender: {
attrs: {
@ -198,7 +198,6 @@ export function useDemo03CourseGridEditColumns(
minWidth: 60,
align: 'center',
fixed: 'right',
headerAlign: 'center',
showOverflow: false,
cellRender: {
attrs: {

View File

@ -5,17 +5,18 @@ import type {
} from '#/adapter/vxe-table';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/normal';
import { h } from 'vue';
import { h, ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Download, Plus } from '@vben/icons';
import { downloadFileFromBlobPart } from '@vben/utils';
import { Download, Plus, Trash2 } from '@vben/icons';
import { downloadFileFromBlobPart, isEmpty } from '@vben/utils';
import { Button, message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteDemo03Student,
deleteDemo03StudentListByIds,
exportDemo03Student,
getDemo03StudentPage,
} from '#/api/infra/demo/demo03/normal';
@ -55,7 +56,31 @@ async function onDelete(row: Demo03StudentApi.Demo03Student) {
await deleteDemo03Student(row.id as number);
message.success($t('ui.actionMessage.deleteSuccess', [row.id]));
onRefresh();
} catch {
} finally {
hideLoading();
}
}
const deleteIds = ref<number[]>([]); // ID
function setDeleteIds({
records,
}: {
records: Demo03StudentApi.Demo03Student[];
}) {
deleteIds.value = records.map((item) => item.id);
}
/** 批量删除学生 */
async function onDeleteBatch() {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting'),
duration: 0,
key: 'action_process_msg',
});
try {
await deleteDemo03StudentListByIds(deleteIds.value);
message.success($t('ui.actionMessage.deleteSuccess'));
onRefresh();
} finally {
hideLoading();
}
}
@ -113,6 +138,10 @@ const [Grid, gridApi] = useVbenVxeGrid({
search: true,
},
} as VxeTableGridOptions<Demo03StudentApi.Demo03Student>,
gridEvents: {
checkboxAll: setDeleteIds,
checkboxChange: setDeleteIds,
},
});
</script>
@ -139,6 +168,17 @@ const [Grid, gridApi] = useVbenVxeGrid({
>
{{ $t('ui.actionTitle.export') }}
</Button>
<Button
:icon="h(Trash2)"
type="primary"
danger
class="ml-2"
:disabled="isEmpty(deleteIds)"
@click="onDeleteBatch"
v-access:code="['infra:demo03-student:delete']"
>
批量删除
</Button>
</template>
</Grid>
</Page>

View File

@ -11,6 +11,13 @@ const props = defineProps<{
}>();
const [Form, formApi] = useVbenForm({
commonConfig: {
componentProps: {
class: 'w-full',
},
formItemClass: 'col-span-2',
labelWidth: 80,
},
layout: 'horizontal',
schema: useDemo03GradeFormSchema(),
showDefaultActions: false,

View File

@ -61,8 +61,8 @@ const [Modal, modalApi] = useVbenModal({
//
const data = (await formApi.getValues()) as Demo03StudentApi.Demo03Student;
//
data.demo03courses = demo03CourseFormRef.value?.getData();
data.demo03grade = await demo03GradeFormRef.value?.getValues();
data.demo03Courses = demo03CourseFormRef.value?.getData();
data.demo03Grade = await demo03GradeFormRef.value?.getValues();
try {
await (formData.value?.id
? updateDemo03Student(data)
@ -80,7 +80,6 @@ const [Modal, modalApi] = useVbenModal({
formData.value = undefined;
return;
}
//
let data = modalApi.getData<Demo03StudentApi.Demo03Student>();
if (!data) {

View File

@ -4,11 +4,12 @@ import type { Demo01ContactApi } from '#/api/infra/demo/demo01';
import { h, onMounted, reactive, ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Download, Plus } from '@vben/icons';
import { Download, Plus, Trash2 } from '@vben/icons';
import {
cloneDeep,
downloadFileFromBlobPart,
formatDateTime,
isEmpty,
} from '@vben/utils';
import {
@ -24,6 +25,7 @@ import {
import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
import {
deleteDemo01Contact,
deleteDemo01ContactListByIds,
exportDemo01Contact,
getDemo01ContactPage,
} from '#/api/infra/demo/demo01';
@ -38,6 +40,7 @@ import Demo01ContactForm from './modules/form.vue';
const loading = ref(true); //
const list = ref<Demo01ContactApi.Demo01Contact[]>([]); //
const total = ref(0); //
const queryParams = reactive({
pageNo: 1,
@ -101,13 +104,41 @@ async function onDelete(row: Demo01ContactApi.Demo01Contact) {
});
try {
await deleteDemo01Contact(row.id as number);
message.success($t('ui.actionMessage.deleteSuccess', [row.id]));
message.success({
content: $t('ui.actionMessage.deleteSuccess', [row.id]),
key: 'action_process_msg',
});
await getList();
} catch {
} finally {
hideLoading();
}
}
/** 批量删除示例联系人 */
async function onDeleteBatch() {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting'),
duration: 0,
key: 'action_process_msg',
});
try {
await deleteDemo01ContactListByIds(deleteIds.value);
message.success($t('ui.actionMessage.deleteSuccess'));
await getList();
} finally {
hideLoading();
}
}
const deleteIds = ref<number[]>([]); // ID
function setDeleteIds({
records,
}: {
records: Demo01ContactApi.Demo01Contact[];
}) {
deleteIds.value = records.map((item) => item.id);
}
/** 导出表格 */
async function onExport() {
try {
@ -205,9 +236,28 @@ onMounted(() => {
>
{{ $t('ui.actionTitle.export') }}
</Button>
<Button
:icon="h(Trash2)"
type="primary"
danger
class="ml-2"
:disabled="isEmpty(deleteIds)"
@click="onDeleteBatch"
v-access:code="['infra:demo01-contact:delete']"
>
批量删除
</Button>
</TableToolbar>
</template>
<VxeTable ref="tableRef" :data="list" show-overflow :loading="loading">
<VxeTable
ref="tableRef"
:data="list"
show-overflow
:loading="loading"
@checkbox-all="setDeleteIds"
@checkbox-change="setDeleteIds"
>
<VxeColumn type="checkbox" width="40" />
<VxeColumn field="id" title="编号" align="center" />
<VxeColumn field="name" title="名字" align="center" />
<VxeColumn field="sex" title="性别" align="center">
@ -240,6 +290,7 @@ onMounted(() => {
<Button
size="small"
type="link"
danger
class="ml-2"
@click="onDelete(row as any)"
v-access:code="['infra:demo01-contact:delete']"

View File

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

View File

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

View File

@ -4,16 +4,16 @@ import type { Demo03StudentApi } from '#/api/infra/demo/demo03/erp';
import { h, onMounted, reactive, ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Download, Plus } from '@vben/icons';
import { Download, Plus, Trash2 } from '@vben/icons';
import {
cloneDeep,
downloadFileFromBlobPart,
formatDateTime,
isEmpty,
} from '@vben/utils';
import {
Button,
DatePicker,
Form,
Input,
message,
@ -26,6 +26,7 @@ import {
import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
import {
deleteDemo03Student,
deleteDemo03StudentListByIds,
exportDemo03Student,
getDemo03StudentPage,
} from '#/api/infra/demo/demo03/erp';
@ -56,7 +57,6 @@ const queryParams = reactive({
pageSize: 10,
name: undefined,
sex: undefined,
birthday: undefined,
description: undefined,
createTime: undefined,
});
@ -68,9 +68,6 @@ const getList = async () => {
loading.value = true;
try {
const params = cloneDeep(queryParams) as any;
if (params.birthday && Array.isArray(params.birthday)) {
params.birthday = (params.birthday as string[]).join(',');
}
if (params.createTime && Array.isArray(params.createTime)) {
params.createTime = (params.createTime as string[]).join(',');
}
@ -118,13 +115,41 @@ async function onDelete(row: Demo03StudentApi.Demo03Student) {
});
try {
await deleteDemo03Student(row.id as number);
message.success($t('ui.actionMessage.deleteSuccess', [row.id]));
message.success({
content: $t('ui.actionMessage.deleteSuccess', [row.id]),
key: 'action_process_msg',
});
await getList();
} catch {
} finally {
hideLoading();
}
}
/** 批量删除学生 */
async function onDeleteBatch() {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting'),
duration: 0,
key: 'action_process_msg',
});
try {
await deleteDemo03StudentListByIds(deleteIds.value);
message.success($t('ui.actionMessage.deleteSuccess'));
await getList();
} finally {
hideLoading();
}
}
const deleteIds = ref<number[]>([]); // ID
function setDeleteIds({
records,
}: {
records: Demo03StudentApi.Demo03Student[];
}) {
deleteIds.value = records.map((item) => item.id);
}
/** 导出表格 */
async function onExport() {
try {
@ -178,15 +203,6 @@ onMounted(() => {
</Select.Option>
</Select>
</Form.Item>
<Form.Item label="出生日期" name="birthday">
<DatePicker
v-model:value="queryParams.birthday"
value-format="YYYY-MM-DD"
placeholder="选择出生日期"
allow-clear
class="w-full"
/>
</Form.Item>
<Form.Item label="创建时间" name="createTime">
<RangePicker
v-model:value="queryParams.createTime"
@ -229,6 +245,17 @@ onMounted(() => {
>
{{ $t('ui.actionTitle.export') }}
</Button>
<Button
:icon="h(Trash2)"
type="primary"
danger
class="ml-2"
:disabled="isEmpty(deleteIds)"
@click="onDeleteBatch"
v-access:code="['infra:demo03-student:delete']"
>
批量删除
</Button>
</TableToolbar>
</template>
<VxeTable
@ -242,7 +269,10 @@ onMounted(() => {
}"
show-overflow
:loading="loading"
@checkbox-all="setDeleteIds"
@checkbox-change="setDeleteIds"
>
<VxeColumn type="checkbox" width="40" />
<VxeColumn field="id" title="编号" align="center" />
<VxeColumn field="name" title="名字" align="center" />
<VxeColumn field="sex" title="性别" align="center">

View File

@ -4,8 +4,8 @@ import type { Demo03StudentApi } from '#/api/infra/demo/demo03/erp';
import { h, nextTick, onMounted, reactive, ref, watch } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { Plus } from '@vben/icons';
import { cloneDeep, formatDateTime } from '@vben/utils';
import { Plus, Trash2 } from '@vben/icons';
import { cloneDeep, formatDateTime, isEmpty } from '@vben/utils';
import {
Button,
@ -19,6 +19,7 @@ import {
import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
import {
deleteDemo03Course,
deleteDemo03CourseListByIds,
getDemo03CoursePage,
} from '#/api/infra/demo/demo03/erp';
import { ContentWrap } from '#/components/content-wrap';
@ -61,13 +62,41 @@ async function onDelete(row: Demo03StudentApi.Demo03Course) {
});
try {
await deleteDemo03Course(row.id as number);
message.success($t('ui.actionMessage.deleteSuccess', [row.id]));
getList();
} catch {
message.success({
content: $t('ui.actionMessage.deleteSuccess', [row.id]),
key: 'action_process_msg',
});
await getList();
} finally {
hideLoading();
}
}
/** 批量删除学生课程 */
async function onDeleteBatch() {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting'),
duration: 0,
key: 'action_process_msg',
});
try {
await deleteDemo03CourseListByIds(deleteIds.value);
message.success($t('ui.actionMessage.deleteSuccess'));
await getList();
} finally {
hideLoading();
}
}
const deleteIds = ref<number[]>([]); // ID
function setDeleteIds({
records,
}: {
records: Demo03StudentApi.Demo03Course[];
}) {
deleteIds.value = records.map((item) => item.id);
}
const loading = ref(true); //
const list = ref<Demo03StudentApi.Demo03Course[]>([]); //
const total = ref(0); //
@ -100,9 +129,6 @@ const getList = async () => {
return [];
}
const params = cloneDeep(queryParams) as any;
if (params.birthday && Array.isArray(params.birthday)) {
params.birthday = (params.birthday as string[]).join(',');
}
if (params.createTime && Array.isArray(params.createTime)) {
params.createTime = (params.createTime as string[]).join(',');
}
@ -200,9 +226,28 @@ onMounted(() => {
>
{{ $t('ui.actionTitle.create', ['学生']) }}
</Button>
<Button
:icon="h(Trash2)"
type="primary"
danger
class="ml-2"
:disabled="isEmpty(deleteIds)"
@click="onDeleteBatch"
v-access:code="['infra:demo03-student:delete']"
>
批量删除
</Button>
</TableToolbar>
</template>
<VxeTable ref="tableRef" :data="list" show-overflow :loading="loading">
<VxeTable
ref="tableRef"
:data="list"
show-overflow
:loading="loading"
@checkbox-all="setDeleteIds"
@checkbox-change="setDeleteIds"
>
<VxeColumn type="checkbox" width="40" />
<VxeColumn field="id" title="编号" align="center" />
<VxeColumn field="studentId" title="学生编号" align="center" />
<VxeColumn field="name" title="名字" align="center" />

View File

@ -4,8 +4,8 @@ import type { Demo03StudentApi } from '#/api/infra/demo/demo03/erp';
import { h, nextTick, onMounted, reactive, ref, watch } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { Plus } from '@vben/icons';
import { cloneDeep, formatDateTime } from '@vben/utils';
import { Plus, Trash2 } from '@vben/icons';
import { cloneDeep, formatDateTime, isEmpty } from '@vben/utils';
import {
Button,
@ -19,6 +19,7 @@ import {
import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
import {
deleteDemo03Grade,
deleteDemo03GradeListByIds,
getDemo03GradePage,
} from '#/api/infra/demo/demo03/erp';
import { ContentWrap } from '#/components/content-wrap';
@ -61,13 +62,41 @@ async function onDelete(row: Demo03StudentApi.Demo03Grade) {
});
try {
await deleteDemo03Grade(row.id as number);
message.success($t('ui.actionMessage.deleteSuccess', [row.id]));
getList();
} catch {
message.success({
content: $t('ui.actionMessage.deleteSuccess', [row.id]),
key: 'action_process_msg',
});
await getList();
} finally {
hideLoading();
}
}
/** 批量删除学生班级 */
async function onDeleteBatch() {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting'),
duration: 0,
key: 'action_process_msg',
});
try {
await deleteDemo03GradeListByIds(deleteIds.value);
message.success($t('ui.actionMessage.deleteSuccess'));
await getList();
} finally {
hideLoading();
}
}
const deleteIds = ref<number[]>([]); // ID
function setDeleteIds({
records,
}: {
records: Demo03StudentApi.Demo03Grade[];
}) {
deleteIds.value = records.map((item) => item.id);
}
const loading = ref(true); //
const list = ref<Demo03StudentApi.Demo03Grade[]>([]); //
const total = ref(0); //
@ -100,9 +129,6 @@ const getList = async () => {
return [];
}
const params = cloneDeep(queryParams) as any;
if (params.birthday && Array.isArray(params.birthday)) {
params.birthday = (params.birthday as string[]).join(',');
}
if (params.createTime && Array.isArray(params.createTime)) {
params.createTime = (params.createTime as string[]).join(',');
}
@ -200,9 +226,28 @@ onMounted(() => {
>
{{ $t('ui.actionTitle.create', ['学生']) }}
</Button>
<Button
:icon="h(Trash2)"
type="primary"
danger
class="ml-2"
:disabled="isEmpty(deleteIds)"
@click="onDeleteBatch"
v-access:code="['infra:demo03-student:delete']"
>
批量删除
</Button>
</TableToolbar>
</template>
<VxeTable ref="tableRef" :data="list" show-overflow :loading="loading">
<VxeTable
ref="tableRef"
:data="list"
show-overflow
:loading="loading"
@checkbox-all="setDeleteIds"
@checkbox-change="setDeleteIds"
>
<VxeColumn type="checkbox" width="40" />
<VxeColumn field="id" title="编号" align="center" />
<VxeColumn field="studentId" title="学生编号" align="center" />
<VxeColumn field="name" title="名字" align="center" />

View File

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

View File

@ -4,16 +4,16 @@ import type { Demo03StudentApi } from '#/api/infra/demo/demo03/normal';
import { h, onMounted, reactive, ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Download, Plus } from '@vben/icons';
import { Download, Plus, Trash2 } from '@vben/icons';
import {
cloneDeep,
downloadFileFromBlobPart,
formatDateTime,
isEmpty,
} from '@vben/utils';
import {
Button,
DatePicker,
Form,
Input,
message,
@ -26,6 +26,7 @@ import {
import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
import {
deleteDemo03Student,
deleteDemo03StudentListByIds,
exportDemo03Student,
getDemo03StudentPage,
} from '#/api/infra/demo/demo03/normal';
@ -52,7 +53,6 @@ const queryParams = reactive({
pageSize: 10,
name: undefined,
sex: undefined,
birthday: undefined,
description: undefined,
createTime: undefined,
});
@ -64,9 +64,6 @@ const getList = async () => {
loading.value = true;
try {
const params = cloneDeep(queryParams) as any;
if (params.birthday && Array.isArray(params.birthday)) {
params.birthday = (params.birthday as string[]).join(',');
}
if (params.createTime && Array.isArray(params.createTime)) {
params.createTime = (params.createTime as string[]).join(',');
}
@ -114,13 +111,41 @@ async function onDelete(row: Demo03StudentApi.Demo03Student) {
});
try {
await deleteDemo03Student(row.id as number);
message.success($t('ui.actionMessage.deleteSuccess', [row.id]));
message.success({
content: $t('ui.actionMessage.deleteSuccess', [row.id]),
key: 'action_process_msg',
});
await getList();
} catch {
} finally {
hideLoading();
}
}
/** 批量删除学生 */
async function onDeleteBatch() {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting'),
duration: 0,
key: 'action_process_msg',
});
try {
await deleteDemo03StudentListByIds(deleteIds.value);
message.success($t('ui.actionMessage.deleteSuccess'));
await getList();
} finally {
hideLoading();
}
}
const deleteIds = ref<number[]>([]); // ID
function setDeleteIds({
records,
}: {
records: Demo03StudentApi.Demo03Student[];
}) {
deleteIds.value = records.map((item) => item.id);
}
/** 导出表格 */
async function onExport() {
try {
@ -174,15 +199,6 @@ onMounted(() => {
</Select.Option>
</Select>
</Form.Item>
<Form.Item label="出生日期" name="birthday">
<DatePicker
v-model:value="queryParams.birthday"
value-format="YYYY-MM-DD"
placeholder="选择出生日期"
allow-clear
class="w-full"
/>
</Form.Item>
<Form.Item label="创建时间" name="createTime">
<RangePicker
v-model:value="queryParams.createTime"
@ -225,9 +241,28 @@ onMounted(() => {
>
{{ $t('ui.actionTitle.export') }}
</Button>
<Button
:icon="h(Trash2)"
type="primary"
danger
class="ml-2"
:disabled="isEmpty(deleteIds)"
@click="onDeleteBatch"
v-access:code="['infra:demo03-student:delete']"
>
批量删除
</Button>
</TableToolbar>
</template>
<VxeTable ref="tableRef" :data="list" show-overflow :loading="loading">
<VxeTable
ref="tableRef"
:data="list"
show-overflow
:loading="loading"
@checkbox-all="setDeleteIds"
@checkbox-change="setDeleteIds"
>
<VxeColumn type="checkbox" width="40" />
<!-- 子表的列表 -->
<VxeColumn type="expand" width="60">
<template #content="{ row }">

View File

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

View File

@ -4,16 +4,16 @@ import type { Demo03StudentApi } from '#/api/infra/demo/demo03/normal';
import { h, onMounted, reactive, ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Download, Plus } from '@vben/icons';
import { Download, Plus, Trash2 } from '@vben/icons';
import {
cloneDeep,
downloadFileFromBlobPart,
formatDateTime,
isEmpty,
} from '@vben/utils';
import {
Button,
DatePicker,
Form,
Input,
message,
@ -25,6 +25,7 @@ import {
import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
import {
deleteDemo03Student,
deleteDemo03StudentListByIds,
exportDemo03Student,
getDemo03StudentPage,
} from '#/api/infra/demo/demo03/normal';
@ -46,7 +47,6 @@ const queryParams = reactive({
pageSize: 10,
name: undefined,
sex: undefined,
birthday: undefined,
description: undefined,
createTime: undefined,
});
@ -58,9 +58,6 @@ const getList = async () => {
loading.value = true;
try {
const params = cloneDeep(queryParams) as any;
if (params.birthday && Array.isArray(params.birthday)) {
params.birthday = (params.birthday as string[]).join(',');
}
if (params.createTime && Array.isArray(params.createTime)) {
params.createTime = (params.createTime as string[]).join(',');
}
@ -108,13 +105,41 @@ async function onDelete(row: Demo03StudentApi.Demo03Student) {
});
try {
await deleteDemo03Student(row.id as number);
message.success($t('ui.actionMessage.deleteSuccess', [row.id]));
message.success({
content: $t('ui.actionMessage.deleteSuccess', [row.id]),
key: 'action_process_msg',
});
await getList();
} catch {
} finally {
hideLoading();
}
}
/** 批量删除学生 */
async function onDeleteBatch() {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting'),
duration: 0,
key: 'action_process_msg',
});
try {
await deleteDemo03StudentListByIds(deleteIds.value);
message.success($t('ui.actionMessage.deleteSuccess'));
await getList();
} finally {
hideLoading();
}
}
const deleteIds = ref<number[]>([]); // ID
function setDeleteIds({
records,
}: {
records: Demo03StudentApi.Demo03Student[];
}) {
deleteIds.value = records.map((item) => item.id);
}
/** 导出表格 */
async function onExport() {
try {
@ -168,15 +193,6 @@ onMounted(() => {
</Select.Option>
</Select>
</Form.Item>
<Form.Item label="出生日期" name="birthday">
<DatePicker
v-model:value="queryParams.birthday"
value-format="YYYY-MM-DD"
placeholder="选择出生日期"
allow-clear
class="w-full"
/>
</Form.Item>
<Form.Item label="创建时间" name="createTime">
<RangePicker
v-model:value="queryParams.createTime"
@ -219,9 +235,28 @@ onMounted(() => {
>
{{ $t('ui.actionTitle.export') }}
</Button>
<Button
:icon="h(Trash2)"
type="primary"
danger
class="ml-2"
:disabled="isEmpty(deleteIds)"
@click="onDeleteBatch"
v-access:code="['infra:demo03-student:delete']"
>
批量删除
</Button>
</TableToolbar>
</template>
<VxeTable ref="tableRef" :data="list" show-overflow :loading="loading">
<VxeTable
ref="tableRef"
:data="list"
show-overflow
:loading="loading"
@checkbox-all="setDeleteIds"
@checkbox-change="setDeleteIds"
>
<VxeColumn type="checkbox" width="40" />
<VxeColumn field="id" title="编号" align="center" />
<VxeColumn field="name" title="名字" align="center" />
<VxeColumn field="sex" title="性别" align="center">

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -24,7 +24,7 @@ function onRefresh() {
}
/** 查询 IP */
function onQueryIp() {
function handleQueryIp() {
formModalApi.setData(null).open();
}
@ -66,7 +66,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
<FormModal @success="onRefresh" />
<Grid table-title="">
<template #toolbar-tools>
<Button type="primary" @click="onQueryIp">
<Button type="primary" @click="handleQueryIp">
<Search class="size-5" />
IP 查询
</Button>

View File

@ -1,8 +1,7 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { SystemDeptApi } from '#/api/system/dept';
import { useAccess } from '@vben/access';
import { handleTree } from '@vben/utils';
import { z } from '#/adapter/form';
@ -10,8 +9,6 @@ import { getDeptList } from '#/api/system/dept';
import { getSimpleUserList } from '#/api/system/user';
import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] {
return [
@ -113,7 +110,6 @@ export function useFormSchema(): VbenFormSchema[] {
/** 列表的字段 */
export function useGridColumns(
onActionClick?: OnActionClickFn<SystemDeptApi.Dept>,
getLeaderName?: (userId: number) => string | undefined,
): VxeTableGridOptions<SystemDeptApi.Dept>['columns'] {
return [
@ -154,39 +150,10 @@ export function useGridColumns(
formatter: 'formatDateTime',
},
{
field: 'operation',
title: '操作',
minWidth: 200,
align: 'right',
width: 220,
fixed: 'right',
headerAlign: 'center',
showOverflow: false,
cellRender: {
attrs: {
nameField: 'name',
nameTitle: '部门',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'append',
text: '新增下级',
show: hasAccessByCodes(['system:dept:create']),
},
{
code: 'edit',
show: hasAccessByCodes(['system:dept:update']),
},
{
code: 'delete',
show: hasAccessByCodes(['system:dept:delete']),
disabled: (row: SystemDeptApi.Dept) => {
return !!(row.children && row.children.length > 0);
},
},
],
},
slots: { default: 'actions' },
},
];
}

View File

@ -1,19 +1,15 @@
<script lang="ts" setup>
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { SystemDeptApi } from '#/api/system/dept';
import type { SystemUserApi } from '#/api/system/user';
import { onMounted, ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Plus } from '@vben/icons';
import { Button, message } from 'ant-design-vue';
import { message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { deleteDept, getDeptList } from '#/api/system/dept';
import { getSimpleUserList } from '#/api/system/user';
import { $t } from '#/locales';
@ -29,9 +25,9 @@ const [FormModal, formModalApi] = useVbenModal({
const userList = ref<SystemUserApi.User[]>([]);
/** 获取负责人名称 */
const getLeaderName = (userId: number) => {
function getLeaderName(userId: number) {
return userList.value.find((user) => user.id === userId)?.nickname;
};
}
/** 刷新表格 */
function onRefresh() {
@ -46,57 +42,41 @@ function toggleExpand() {
}
/** 创建部门 */
function onCreate() {
function handleCreate() {
formModalApi.setData(null).open();
}
/** 添加下级部门 */
function onAppend(row: SystemDeptApi.Dept) {
function handleAppend(row: SystemDeptApi.Dept) {
formModalApi.setData({ parentId: row.id }).open();
}
/** 编辑部门 */
function onEdit(row: SystemDeptApi.Dept) {
function handleEdit(row: SystemDeptApi.Dept) {
formModalApi.setData(row).open();
}
/** 删除部门 */
async function onDelete(row: SystemDeptApi.Dept) {
async function handleDelete(row: SystemDeptApi.Dept) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.name]),
duration: 0,
key: 'action_process_msg',
key: 'action_key_msg',
});
try {
await deleteDept(row.id as number);
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
message.success({
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
key: 'action_key_msg',
});
onRefresh();
} catch {
} finally {
hideLoading();
}
}
/** 表格操作按钮的回调函数 */
function onActionClick({ code, row }: OnActionClickParams<SystemDeptApi.Dept>) {
switch (code) {
case 'append': {
onAppend(row);
break;
}
case 'delete': {
onDelete(row);
break;
}
case 'edit': {
onEdit(row);
break;
}
}
}
const [Grid, gridApi] = useVbenVxeGrid({
gridOptions: {
columns: useGridColumns(onActionClick, getLeaderName),
columns: useGridColumns(getLeaderName),
height: 'auto',
keepSource: true,
pagerConfig: {
@ -136,17 +116,54 @@ onMounted(async () => {
<FormModal @success="onRefresh" />
<Grid table-title="">
<template #toolbar-tools>
<Button
type="primary"
@click="onCreate"
v-access:code="['system:dept:create']"
>
<Plus class="size-5" />
{{ $t('ui.actionTitle.create', ['部门']) }}
</Button>
<Button class="ml-2" @click="toggleExpand">
{{ isExpanded ? '收缩' : '展开' }}
</Button>
<TableAction
:actions="[
{
label: $t('ui.actionTitle.create', ['菜单']),
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['system:dept:create'],
onClick: handleCreate,
},
{
label: isExpanded ? '收缩' : '展开',
type: 'primary',
onClick: toggleExpand,
},
]"
/>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: '新增下级',
type: 'link',
icon: ACTION_ICON.ADD,
auth: ['system:menu:create'],
onClick: handleAppend.bind(null, row),
},
{
label: $t('common.edit'),
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['system:menu:update'],
onClick: handleEdit.bind(null, row),
},
{
label: $t('common.delete'),
type: 'link',
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['system:menu:delete'],
disabled: !!(row.children && row.children.length > 0),
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
</Page>

View File

@ -10,7 +10,7 @@ import TypeGrid from './modules/type-grid.vue';
const searchDictType = ref<string>(); //
function onDictTypeSelect(dictType: string) {
function handleDictTypeSelect(dictType: string) {
searchDictType.value = dictType;
}
</script>
@ -24,7 +24,7 @@ function onDictTypeSelect(dictType: string) {
<div class="flex h-full">
<!-- 左侧字典类型列表 -->
<div class="w-1/2 pr-3">
<TypeGrid @select="onDictTypeSelect" />
<TypeGrid @select="handleDictTypeSelect" />
</div>
<!-- 右侧字典数据列表 -->
<div class="w-1/2">

View File

@ -5,18 +5,16 @@ import type { SystemDictDataApi } from '#/api/system/dict/data';
import { watch } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { Download, Plus } from '@vben/icons';
import { downloadFileFromBlobPart } from '@vben/utils';
import { Button, message } from 'ant-design-vue';
import { message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteDictData,
exportDictData,
getDictDataPage,
} from '#/api/system/dict/data';
import { ACTION_KEY, TableAction } from '#/components/table-action';
import { $t } from '#/locales';
import { useDataGridColumns, useDataGridFormSchema } from '../data';
@ -40,33 +38,37 @@ function onRefresh() {
}
/** 导出表格 */
async function onExport() {
async function handleExport() {
const data = await exportDictData(await gridApi.formApi.getValues());
downloadFileFromBlobPart({ fileName: '字典数据.xls', source: data });
}
/** 创建字典数据 */
function onCreate() {
function handleCreate() {
dataFormModalApi.setData({ dictType: props.dictType }).open();
}
/** 编辑字典数据 */
function onEdit(row: SystemDictDataApi.DictData) {
function handleEdit(row: SystemDictDataApi.DictData) {
dataFormModalApi.setData(row).open();
}
/** 删除字典数据 */
async function onDelete(row: SystemDictDataApi.DictData) {
message.loading({
async function handleDelete(row: SystemDictDataApi.DictData) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.label]),
key: ACTION_KEY,
key: 'action_key_msg',
});
await deleteDictData(row.id as number);
message.success({
content: $t('ui.actionMessage.deleteSuccess', [row.label]),
key: ACTION_KEY,
});
onRefresh();
try {
await deleteDictData(row.id as number);
message.success({
content: $t('ui.actionMessage.deleteSuccess', [row.label]),
key: 'action_key_msg',
});
onRefresh();
} finally {
hideLoading();
}
}
const [Grid, gridApi] = useVbenVxeGrid({
@ -116,23 +118,24 @@ watch(
<Grid table-title="">
<template #toolbar-tools>
<Button
type="primary"
@click="onCreate"
v-access:code="['system:dict:create']"
>
<Plus class="size-5" />
{{ $t('ui.actionTitle.create', ['字典数据']) }}
</Button>
<Button
type="primary"
class="ml-2"
@click="onExport"
v-access:code="['system:dict:export']"
>
<Download class="size-5" />
{{ $t('ui.actionTitle.export') }}
</Button>
<TableAction
:actions="[
{
label: $t('ui.actionTitle.create', ['字典数据']),
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['system:dict:create'],
onClick: handleCreate,
},
{
label: $t('ui.actionTitle.export'),
type: 'primary',
icon: ACTION_ICON.DOWNLOAD,
auth: ['system:dict:export'],
onClick: handleExport,
},
]"
/>
</template>
<template #actions="{ row }">
<TableAction
@ -140,19 +143,19 @@ watch(
{
label: $t('common.edit'),
type: 'link',
icon: 'ant-design:edit-outlined',
icon: ACTION_ICON.EDIT,
auth: ['system:dict:update'],
onClick: onEdit.bind(null, row),
onClick: handleEdit.bind(null, row),
},
{
label: $t('common.delete'),
type: 'link',
danger: true,
icon: 'ant-design:delete-outlined',
icon: ACTION_ICON.DELETE,
auth: ['system:dict:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.label]),
confirm: onDelete.bind(null, row),
confirm: handleDelete.bind(null, row),
},
},
]"

View File

@ -6,18 +6,16 @@ import type {
import type { SystemDictTypeApi } from '#/api/system/dict/type';
import { useVbenModal } from '@vben/common-ui';
import { Download, Plus } from '@vben/icons';
import { downloadFileFromBlobPart } from '@vben/utils';
import { Button, message } from 'ant-design-vue';
import { message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteDictType,
exportDictType,
getDictTypePage,
} from '#/api/system/dict/type';
import { ACTION_KEY, TableAction } from '#/components/table-action';
import { $t } from '#/locales';
import { useTypeGridColumns, useTypeGridFormSchema } from '../data';
@ -36,33 +34,37 @@ function onRefresh() {
}
/** 导出表格 */
async function onExport() {
async function handleExport() {
const data = await exportDictType(await gridApi.formApi.getValues());
downloadFileFromBlobPart({ fileName: '字典类型.xls', source: data });
}
/** 创建字典类型 */
function onCreate() {
function handleCreate() {
typeFormModalApi.setData(null).open();
}
/** 编辑字典类型 */
function onEdit(row: any) {
function handleEdit(row: any) {
typeFormModalApi.setData(row).open();
}
/** 删除字典类型 */
async function onDelete(row: SystemDictTypeApi.DictType) {
message.loading({
async function handleDelete(row: SystemDictTypeApi.DictType) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.name]),
key: ACTION_KEY,
key: 'action_key_msg',
});
await deleteDictType(row.id as number);
message.success({
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
key: ACTION_KEY,
});
onRefresh();
try {
await deleteDictType(row.id as number);
message.success({
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
key: 'action_key_msg',
});
onRefresh();
} finally {
hideLoading();
}
}
/** 表格事件 */
@ -110,23 +112,24 @@ const [Grid, gridApi] = useVbenVxeGrid({
<Grid table-title="">
<template #toolbar-tools>
<Button
type="primary"
@click="onCreate"
v-access:code="['system:dict:create']"
>
<Plus class="size-5" />
{{ $t('ui.actionTitle.create', ['字典类型']) }}
</Button>
<Button
type="primary"
class="ml-2"
@click="onExport"
v-access:code="['system:dict:export']"
>
<Download class="size-5" />
{{ $t('ui.actionTitle.export') }}
</Button>
<TableAction
:actions="[
{
label: $t('ui.actionTitle.create', ['字典类型']),
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['system:dict:create'],
onClick: handleCreate,
},
{
label: $t('ui.actionTitle.export'),
type: 'primary',
icon: ACTION_ICON.DOWNLOAD,
auth: ['system:dict:export'],
onClick: handleExport,
},
]"
/>
</template>
<template #actions="{ row }">
<TableAction
@ -134,19 +137,19 @@ const [Grid, gridApi] = useVbenVxeGrid({
{
label: $t('common.edit'),
type: 'link',
icon: 'ant-design:edit-outlined',
icon: ACTION_ICON.EDIT,
auth: ['system:dict:update'],
onClick: onEdit.bind(null, row),
onClick: handleEdit.bind(null, row),
},
{
label: $t('common.delete'),
type: 'link',
danger: true,
icon: 'ant-design:delete-outlined',
icon: ACTION_ICON.DELETE,
auth: ['system:dict:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
confirm: onDelete.bind(null, row),
confirm: handleDelete.bind(null, row),
},
},
]"

View File

@ -1,13 +1,8 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { SystemLoginLogApi } from '#/api/system/login-log';
import { useAccess } from '@vben/access';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { DICT_TYPE, getRangePickerDefaultProps } from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 列表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] {
return [
@ -42,9 +37,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
export function useGridColumns<T = SystemLoginLogApi.LoginLog>(
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
export function useGridColumns(): VxeTableGridOptions['columns'] {
return [
{
field: 'id',
@ -91,26 +84,10 @@ export function useGridColumns<T = SystemLoginLogApi.LoginLog>(
formatter: 'formatDateTime',
},
{
field: 'operation',
title: '操作',
minWidth: 120,
align: 'center',
width: 80,
fixed: 'right',
cellRender: {
attrs: {
nameField: 'username',
nameTitle: '登录日志',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'detail',
text: '详情',
show: hasAccessByCodes(['system:login-log:query']),
},
],
},
slots: { default: 'actions' },
},
];
}

View File

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

View File

@ -1,14 +1,9 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { SystemMailAccountApi } from '#/api/system/mail/account';
import { useAccess } from '@vben/access';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { z } from '#/adapter/form';
import { DICT_TYPE, getDictOptions } from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] {
return [
@ -125,9 +120,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
export function useGridColumns<T = SystemMailAccountApi.MailAccount>(
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
export function useGridColumns(): VxeTableGridOptions['columns'] {
return [
{
field: 'id',
@ -179,29 +172,10 @@ export function useGridColumns<T = SystemMailAccountApi.MailAccount>(
formatter: 'formatDateTime',
},
{
field: 'operation',
title: '操作',
minWidth: 130,
align: 'center',
width: 130,
fixed: 'right',
cellRender: {
attrs: {
nameField: 'mail',
nameTitle: '邮箱账号',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'edit',
show: hasAccessByCodes(['system:mail-account:update']),
},
{
code: 'delete',
show: hasAccessByCodes(['system:mail-account:delete']),
},
],
},
slots: { default: 'actions' },
},
];
}

View File

@ -1,16 +1,12 @@
<script lang="ts" setup>
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { SystemMailAccountApi } from '#/api/system/mail/account';
import { Page, useVbenModal } from '@vben/common-ui';
import { Plus } from '@vben/icons';
import { Button, message } from 'ant-design-vue';
import { message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteMailAccount,
getMailAccountPage,
@ -32,54 +28,39 @@ function onRefresh() {
}
/** 创建邮箱账号 */
function onCreate() {
function handleCreate() {
formModalApi.setData(null).open();
}
/** 编辑邮箱账号 */
function onEdit(row: SystemMailAccountApi.MailAccount) {
function handleEdit(row: SystemMailAccountApi.MailAccount) {
formModalApi.setData(row).open();
}
/** 删除邮箱账号 */
async function onDelete(row: SystemMailAccountApi.MailAccount) {
async function handleDelete(row: SystemMailAccountApi.MailAccount) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.mail]),
duration: 0,
key: 'action_process_msg',
key: 'action_key_msg',
});
try {
await deleteMailAccount(row.id as number);
message.success($t('ui.actionMessage.deleteSuccess', [row.mail]));
message.success({
content: $t('ui.actionMessage.deleteSuccess', [row.mail]),
key: 'action_key_msg',
});
onRefresh();
} finally {
hideLoading();
}
}
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<SystemMailAccountApi.MailAccount>) {
switch (code) {
case 'delete': {
onDelete(row);
break;
}
case 'edit': {
onEdit(row);
break;
}
}
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(onActionClick),
columns: useGridColumns(),
height: 'auto',
keepSource: true,
proxyConfig: {
@ -112,14 +93,41 @@ const [Grid, gridApi] = useVbenVxeGrid({
<FormModal @success="onRefresh" />
<Grid table-title="">
<template #toolbar-tools>
<Button
type="primary"
@click="onCreate"
v-access:code="['system:mail-account:create']"
>
<Plus class="size-5" />
{{ $t('ui.actionTitle.create', ['邮箱账号']) }}
</Button>
<TableAction
:actions="[
{
label: $t('ui.actionTitle.create', ['邮箱账号']),
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['system:mail-account:create'],
onClick: handleCreate,
},
]"
/>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.edit'),
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['system:mail-account:update'],
onClick: handleEdit.bind(null, row),
},
{
label: $t('common.delete'),
type: 'link',
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['system:mail-account:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
</Page>

View File

@ -1,14 +1,9 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { SystemMailLogApi } from '#/api/system/mail/log';
import { useAccess } from '@vben/access';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { getSimpleMailAccountList } from '#/api/system/mail/account';
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 列表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] {
return [
@ -75,9 +70,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
export function useGridColumns<T = SystemMailLogApi.MailLog>(
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
export function useGridColumns(): VxeTableGridOptions['columns'] {
return [
{
field: 'id',
@ -125,26 +118,10 @@ export function useGridColumns<T = SystemMailLogApi.MailLog>(
minWidth: 120,
},
{
field: 'operation',
title: '操作',
minWidth: 80,
align: 'center',
width: 80,
fixed: 'right',
cellRender: {
attrs: {
nameField: 'toMail',
nameTitle: '邮件日志',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'detail',
text: '查看',
show: hasAccessByCodes(['system:mail-log:query']),
},
],
},
slots: { default: 'actions' },
},
];
}

View File

@ -1,13 +1,10 @@
<script lang="ts" setup>
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { SystemMailLogApi } from '#/api/system/mail/log';
import { Page, useVbenModal } from '@vben/common-ui';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { getMailLogPage } from '#/api/system/mail/log';
import { DocAlert } from '#/components/doc-alert';
@ -25,29 +22,16 @@ function onRefresh() {
}
/** 查看邮件日志 */
function onDetail(row: SystemMailLogApi.MailLog) {
function handleDetail(row: SystemMailLogApi.MailLog) {
detailModalApi.setData(row).open();
}
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<SystemMailLogApi.MailLog>) {
switch (code) {
case 'detail': {
onDetail(row);
break;
}
}
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(onActionClick),
columns: useGridColumns(),
height: 'auto',
keepSource: true,
proxyConfig: {
@ -79,7 +63,19 @@ const [Grid, gridApi] = useVbenVxeGrid({
<DetailModal @success="onRefresh" />
<Grid table-title="">
<template #toolbar-tools> </template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.detail'),
type: 'link',
icon: ACTION_ICON.VIEW,
auth: ['system:mail-log:query'],
onClick: handleDetail.bind(null, row),
},
]"
/>
</template>
</Grid>
</Page>
</template>

View File

@ -1,8 +1,5 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { SystemMailTemplateApi } from '#/api/system/mail/template';
import { useAccess } from '@vben/access';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { z } from '#/adapter/form';
import { getSimpleMailAccountList } from '#/api/system/mail/account';
@ -13,8 +10,6 @@ import {
getRangePickerDefaultProps,
} from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] {
return [
@ -193,8 +188,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
export function useGridColumns<T = SystemMailTemplateApi.MailTemplate>(
onActionClick: OnActionClickFn<T>,
export function useGridColumns(
getAccountMail?: (accountId: number) => string | undefined,
): VxeTableGridOptions['columns'] {
return [
@ -245,34 +239,10 @@ export function useGridColumns<T = SystemMailTemplateApi.MailTemplate>(
formatter: 'formatDateTime',
},
{
field: 'operation',
title: '操作',
minWidth: 150,
align: 'center',
width: 220,
fixed: 'right',
cellRender: {
attrs: {
nameField: 'name',
nameTitle: '邮件模板',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'edit',
show: hasAccessByCodes(['system:mail-template:update']),
},
{
code: 'delete',
show: hasAccessByCodes(['system:mail-template:delete']),
},
{
code: 'send',
text: '测试',
show: hasAccessByCodes(['system:mail-template:send-mail']),
},
],
},
slots: { default: 'actions' },
},
];
}

View File

@ -1,19 +1,15 @@
<script lang="ts" setup>
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { SystemMailAccountApi } from '#/api/system/mail/account';
import type { SystemMailTemplateApi } from '#/api/system/mail/template';
import { onMounted, ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Plus } from '@vben/icons';
import { Button, message } from 'ant-design-vue';
import { message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { getSimpleMailAccountList } from '#/api/system/mail/account';
import {
deleteMailTemplate,
@ -28,11 +24,6 @@ import SendForm from './modules/send-form.vue';
const accountList = ref<SystemMailAccountApi.MailAccount[]>([]);
/** 获取邮箱账号 */
const getAccountMail = (accountId: number) => {
return accountList.value.find((account) => account.id === accountId)?.mail;
};
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Form,
destroyOnClose: true,
@ -49,55 +40,38 @@ function onRefresh() {
}
/** 创建邮件模板 */
function onCreate() {
function handleCreate() {
formModalApi.setData(null).open();
}
/** 编辑邮件模板 */
function onEdit(row: SystemMailTemplateApi.MailTemplate) {
function handleEdit(row: SystemMailTemplateApi.MailTemplate) {
formModalApi.setData(row).open();
}
/** 发送测试邮件 */
function onSend(row: SystemMailTemplateApi.MailTemplate) {
function handleSend(row: SystemMailTemplateApi.MailTemplate) {
sendModalApi.setData(row).open();
}
/** 删除邮件模板 */
async function onDelete(row: SystemMailTemplateApi.MailTemplate) {
const hideLoading = message.loading({
async function handleDelete(row: SystemMailTemplateApi.MailTemplate) {
message.loading({
content: $t('ui.actionMessage.deleting', [row.name]),
duration: 0,
key: 'action_process_msg',
});
try {
await deleteMailTemplate(row.id as number);
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
onRefresh();
} finally {
hideLoading();
}
await deleteMailTemplate(row.id as number);
message.success({
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
key: 'action_key_msg',
});
onRefresh();
}
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<SystemMailTemplateApi.MailTemplate>) {
switch (code) {
case 'delete': {
onDelete(row);
break;
}
case 'edit': {
onEdit(row);
break;
}
case 'send': {
onSend(row);
break;
}
}
/** 获取邮箱账号 */
function getAccountMail(accountId: number) {
return accountList.value.find((account) => account.id === accountId)?.mail;
}
const [Grid, gridApi] = useVbenVxeGrid({
@ -105,7 +79,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(onActionClick, getAccountMail),
columns: useGridColumns(getAccountMail),
height: 'auto',
keepSource: true,
proxyConfig: {
@ -144,14 +118,48 @@ onMounted(async () => {
<SendModal />
<Grid table-title="">
<template #toolbar-tools>
<Button
type="primary"
@click="onCreate"
v-access:code="['system:mail-template:create']"
>
<Plus class="size-5" />
{{ $t('ui.actionTitle.create', ['邮件模板']) }}
</Button>
<TableAction
:actions="[
{
label: $t('ui.actionTitle.create', ['邮件模板']),
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['system:mail-template:create'],
onClick: handleCreate,
},
]"
/>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.edit'),
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['system:mail-template:update'],
onClick: handleEdit.bind(null, row),
},
{
label: '测试',
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['system:mail-template:send-mail'],
onClick: handleSend.bind(null, row),
},
{
label: $t('common.delete'),
type: 'link',
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['system:mail-template:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
</Page>

View File

@ -83,7 +83,7 @@ const [Modal, modalApi] = useVbenModal({
});
/** 动态构建表单 schema */
const buildFormSchema = () => {
function buildFormSchema() {
const schema = useSendMailFormSchema();
if (formData.value?.params) {
formData.value.params?.forEach((param: string) => {
@ -99,7 +99,7 @@ const buildFormSchema = () => {
});
}
return schema;
};
}
</script>
<template>

View File

@ -1,12 +1,11 @@
import type { Recordable } from '@vben/types';
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { SystemMenuApi } from '#/api/system/menu';
import { h } from 'vue';
import { useAccess } from '@vben/access';
import { IconifyIcon } from '@vben/icons';
import { handleTree, isHttpUrl } from '@vben/utils';
@ -21,8 +20,6 @@ import {
SystemMenuTypeEnum,
} from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] {
return [
@ -269,9 +266,7 @@ export function useFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
export function useGridColumns(
onActionClick: OnActionClickFn<SystemMenuApi.Menu>,
): VxeTableGridOptions<SystemMenuApi.Menu>['columns'] {
export function useGridColumns(): VxeTableGridOptions<SystemMenuApi.Menu>['columns'] {
return [
{
field: 'name',
@ -321,34 +316,10 @@ export function useGridColumns(
},
},
{
field: 'operation',
title: '操作',
minWidth: 200,
width: 220,
fixed: 'right',
align: 'center',
showOverflow: false,
cellRender: {
attrs: {
nameField: 'name',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'append',
text: '新增下级',
show: hasAccessByCodes(['system:menu:create']),
},
{
code: 'edit',
show: hasAccessByCodes(['system:menu:update']),
},
{
code: 'delete',
show: hasAccessByCodes(['system:menu:delete']),
},
],
},
slots: { default: 'actions' },
},
];
}

View File

@ -1,18 +1,15 @@
<script lang="ts" setup>
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { SystemMenuApi } from '#/api/system/menu';
import { ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { IconifyIcon, Plus } from '@vben/icons';
import { IconifyIcon } from '@vben/icons';
import { Button, message } from 'ant-design-vue';
import { message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { deleteMenu, getMenuList } from '#/api/system/menu';
import { DocAlert } from '#/components/doc-alert';
import { $t } from '#/locales';
@ -32,54 +29,38 @@ function onRefresh() {
}
/** 创建菜单 */
function onCreate() {
function handleCreate() {
formModalApi.setData({}).open();
}
/** 添加下级菜单 */
function onAppend(row: SystemMenuApi.Menu) {
function handleAppend(row: SystemMenuApi.Menu) {
formModalApi.setData({ pid: row.id }).open();
}
/** 编辑菜单 */
function onEdit(row: SystemMenuApi.Menu) {
function handleEdit(row: SystemMenuApi.Menu) {
formModalApi.setData(row).open();
}
/** 删除菜单 */
async function onDelete(row: SystemMenuApi.Menu) {
async function handleDelete(row: SystemMenuApi.Menu) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.name]),
duration: 0,
key: 'action_process_msg',
key: 'action_key_msg',
});
try {
await deleteMenu(row.id as number);
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
message.success({
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
key: 'action_key_msg',
});
onRefresh();
} catch {
} finally {
hideLoading();
}
}
/** 表格操作按钮的回调函数 */
function onActionClick({ code, row }: OnActionClickParams<SystemMenuApi.Menu>) {
switch (code) {
case 'append': {
onAppend(row);
break;
}
case 'delete': {
onDelete(row);
break;
}
case 'edit': {
onEdit(row);
break;
}
}
}
/** 切换树形展开/收缩状态 */
const isExpanded = ref(false);
function toggleExpand() {
@ -89,7 +70,7 @@ function toggleExpand() {
const [Grid, gridApi] = useVbenVxeGrid({
gridOptions: {
columns: useGridColumns(onActionClick),
columns: useGridColumns(),
height: 'auto',
keepSource: true,
pagerConfig: {
@ -131,17 +112,22 @@ const [Grid, gridApi] = useVbenVxeGrid({
<FormModal @success="onRefresh" />
<Grid>
<template #toolbar-tools>
<Button
type="primary"
@click="onCreate"
v-access:code="['system:menu:create']"
>
<Plus class="size-5" />
{{ $t('ui.actionTitle.create', ['菜单']) }}
</Button>
<Button class="ml-2" @click="toggleExpand">
{{ isExpanded ? '收缩' : '展开' }}
</Button>
<TableAction
:actions="[
{
label: $t('ui.actionTitle.create', ['菜单']),
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['system:menu:create'],
onClick: handleCreate,
},
{
label: isExpanded ? '收缩' : '展开',
type: 'primary',
onClick: toggleExpand,
},
]"
/>
</template>
<template #name="{ row }">
<div class="flex w-full items-center gap-1">
@ -161,6 +147,37 @@ const [Grid, gridApi] = useVbenVxeGrid({
<div class="items-center justify-end"></div>
</div>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: '新增下级',
type: 'link',
icon: ACTION_ICON.ADD,
auth: ['system:menu:create'],
onClick: handleAppend.bind(null, row),
},
{
label: $t('common.edit'),
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['system:menu:update'],
onClick: handleEdit.bind(null, row),
},
{
label: $t('common.delete'),
type: 'link',
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['system:menu:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
</Page>
</template>

View File

@ -1,14 +1,9 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { SystemNoticeApi } from '#/api/system/notice';
import { useAccess } from '@vben/access';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { z } from '#/adapter/form';
import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] {
return [
@ -91,9 +86,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
export function useGridColumns<T = SystemNoticeApi.Notice>(
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
export function useGridColumns(): VxeTableGridOptions['columns'] {
return [
{
field: 'id',
@ -130,34 +123,10 @@ export function useGridColumns<T = SystemNoticeApi.Notice>(
formatter: 'formatDateTime',
},
{
field: 'operation',
title: '操作',
minWidth: 180,
align: 'center',
width: 220,
fixed: 'right',
cellRender: {
attrs: {
nameField: 'title',
nameTitle: '公告',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'edit',
show: hasAccessByCodes(['system:notice:update']),
},
{
code: 'push',
text: '推送',
show: hasAccessByCodes(['system:notice:update']),
},
{
code: 'delete',
show: hasAccessByCodes(['system:notice:delete']),
},
],
},
slots: { default: 'actions' },
},
];
}

View File

@ -1,16 +1,12 @@
<script lang="ts" setup>
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { SystemNoticeApi } from '#/api/system/notice';
import { Page, useVbenModal } from '@vben/common-ui';
import { Plus } from '@vben/icons';
import { Button, message } from 'ant-design-vue';
import { message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { deleteNotice, getNoticePage, pushNotice } from '#/api/system/notice';
import { $t } from '#/locales';
@ -28,73 +24,56 @@ function onRefresh() {
}
/** 创建公告 */
function onCreate() {
function handleCreate() {
formModalApi.setData(null).open();
}
/** 编辑公告 */
function onEdit(row: SystemNoticeApi.Notice) {
function handleEdit(row: SystemNoticeApi.Notice) {
formModalApi.setData(row).open();
}
/** 删除公告 */
async function onDelete(row: SystemNoticeApi.Notice) {
async function handleDelete(row: SystemNoticeApi.Notice) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.title]),
duration: 0,
key: 'action_process_msg',
key: 'action_key_msg',
});
try {
await deleteNotice(row.id as number);
message.success($t('ui.actionMessage.deleteSuccess', [row.title]));
message.success({
content: $t('ui.actionMessage.deleteSuccess', [row.title]),
key: 'action_key_msg',
});
onRefresh();
} catch {
} finally {
hideLoading();
}
}
/** 推送公告 */
async function onPush(row: SystemNoticeApi.Notice) {
async function handlePush(row: SystemNoticeApi.Notice) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.processing', ['推送']),
duration: 0,
key: 'action_process_msg',
});
try {
await pushNotice(row.id as number);
message.success($t('ui.actionMessage.operationSuccess'));
} catch {
message.success({
content: $t('ui.actionMessage.operationSuccess'),
key: 'action_key_msg',
});
} finally {
hideLoading();
}
}
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<SystemNoticeApi.Notice>) {
switch (code) {
case 'delete': {
onDelete(row);
break;
}
case 'edit': {
onEdit(row);
break;
}
case 'push': {
onPush(row);
break;
}
}
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(onActionClick),
columns: useGridColumns(),
height: 'auto',
keepSource: true,
proxyConfig: {
@ -124,14 +103,48 @@ const [Grid, gridApi] = useVbenVxeGrid({
<FormModal @success="onRefresh" />
<Grid table-title="">
<template #toolbar-tools>
<Button
type="primary"
@click="onCreate"
v-access:code="['system:notice:create']"
>
<Plus class="size-5" />
{{ $t('ui.actionTitle.create', ['公告']) }}
</Button>
<TableAction
:actions="[
{
label: $t('ui.actionTitle.create', ['公告']),
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['system:notice:create'],
onClick: handleCreate,
},
]"
/>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.edit'),
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['system:notice:update'],
onClick: handleEdit.bind(null, row),
},
{
label: '推送',
type: 'link',
icon: ACTION_ICON.ADD,
auth: ['system:notice:update'],
onClick: handlePush.bind(null, row),
},
{
label: $t('common.delete'),
type: 'link',
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['system:notice:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
</Page>

View File

@ -1,13 +1,8 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { SystemNotifyMessageApi } from '#/api/system/notify/message';
import { useAccess } from '@vben/access';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 列表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] {
return [
@ -65,9 +60,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
export function useGridColumns<T = SystemNotifyMessageApi.NotifyMessage>(
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
export function useGridColumns(): VxeTableGridOptions['columns'] {
return [
{
field: 'id',
@ -146,26 +139,10 @@ export function useGridColumns<T = SystemNotifyMessageApi.NotifyMessage>(
formatter: 'formatDateTime',
},
{
field: 'operation',
title: '操作',
minWidth: 180,
align: 'center',
width: 80,
fixed: 'right',
cellRender: {
attrs: {
nameField: 'id',
nameTitle: '站内信',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'detail',
text: '详情',
show: hasAccessByCodes(['system:notify-message:query']),
},
],
},
slots: { default: 'actions' },
},
];
}

View File

@ -1,13 +1,10 @@
<script lang="ts" setup>
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { SystemNotifyMessageApi } from '#/api/system/notify/message';
import { Page, useVbenModal } from '@vben/common-ui';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { getNotifyMessagePage } from '#/api/system/notify/message';
import { DocAlert } from '#/components/doc-alert';
@ -25,29 +22,16 @@ function onRefresh() {
}
/** 查看站内信详情 */
function onDetail(row: SystemNotifyMessageApi.NotifyMessage) {
function handleDetail(row: SystemNotifyMessageApi.NotifyMessage) {
detailModalApi.setData(row).open();
}
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<SystemNotifyMessageApi.NotifyMessage>) {
switch (code) {
case 'detail': {
onDetail(row);
break;
}
}
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(onActionClick),
columns: useGridColumns(),
height: 'auto',
keepSource: true,
proxyConfig: {
@ -79,6 +63,20 @@ const [Grid, gridApi] = useVbenVxeGrid({
</template>
<DetailModal @success="onRefresh" />
<Grid table-title="" />
<Grid table-title="">
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.detail'),
type: 'link',
icon: ACTION_ICON.VIEW,
auth: ['system:notify-message:query'],
onClick: handleDetail.bind(null, row),
},
]"
/>
</template>
</Grid>
</Page>
</template>

View File

@ -1,6 +1,5 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { SystemNotifyMessageApi } from '#/api/system/notify/message';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { DescriptionItemSchema } from '#/components/description';
import { h } from 'vue';
@ -36,9 +35,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
export function useGridColumns<T = SystemNotifyMessageApi.NotifyMessage>(
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
export function useGridColumns(): VxeTableGridOptions['columns'] {
return [
{
title: '',
@ -86,31 +83,10 @@ export function useGridColumns<T = SystemNotifyMessageApi.NotifyMessage>(
formatter: 'formatDateTime',
},
{
field: 'operation',
title: '操作',
minWidth: 180,
align: 'center',
width: 130,
fixed: 'right',
cellRender: {
attrs: {
nameField: 'id',
nameTitle: '站内信',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'detail',
text: '查看',
show: (row: any) => row.readStatus,
},
{
code: 'read',
text: '已读',
show: (row: any) => !row.readStatus,
},
],
},
slots: { default: 'actions' },
},
];
}

View File

@ -1,16 +1,12 @@
<script lang="ts" setup>
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { SystemNotifyMessageApi } from '#/api/system/notify/message';
import { Page, useVbenModal } from '@vben/common-ui';
import { MdiCheckboxMarkedCircleOutline } from '@vben/icons';
import { Button, message } from 'ant-design-vue';
import { message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
getMyNotifyMessagePage,
updateAllNotifyMessageRead,
@ -32,12 +28,12 @@ function onRefresh() {
}
/** 查看站内信详情 */
function onDetail(row: SystemNotifyMessageApi.NotifyMessage) {
function handleDetail(row: SystemNotifyMessageApi.NotifyMessage) {
detailModalApi.setData(row).open();
}
/** 标记一条站内信已读 */
async function onRead(row: SystemNotifyMessageApi.NotifyMessage) {
async function handleRead(row: SystemNotifyMessageApi.NotifyMessage) {
message.loading({
content: '正在标记已读...',
duration: 0,
@ -46,15 +42,18 @@ async function onRead(row: SystemNotifyMessageApi.NotifyMessage) {
//
await updateNotifyMessageRead([row.id]);
//
message.success('标记已读成功');
message.success({
content: '标记已读成功',
key: 'action_process_msg',
});
onRefresh();
//
onDetail(row);
handleDetail(row);
}
/** 标记选中的站内信为已读 */
async function onMarkRead() {
async function handleMarkRead() {
const rows = gridApi.grid.getCheckboxRecords();
if (!rows || rows.length === 0) {
message.warning('请选择需要标记的站内信');
@ -62,48 +61,43 @@ async function onMarkRead() {
}
const ids = rows.map((row: SystemNotifyMessageApi.NotifyMessage) => row.id);
message.loading({
const hideLoading = message.loading({
content: '正在标记已读...',
duration: 0,
key: 'action_process_msg',
key: 'action_key_msg',
});
//
await updateNotifyMessageRead(ids);
//
message.success('标记已读成功');
await gridApi.grid.setAllCheckboxRow(false);
onRefresh();
try {
//
await updateNotifyMessageRead(ids);
//
message.success({
content: '标记已读成功',
key: 'action_key_msg',
});
await gridApi.grid.setAllCheckboxRow(false);
onRefresh();
} finally {
hideLoading();
}
}
/** 标记所有站内信为已读 */
async function onMarkAllRead() {
message.loading({
async function handleMarkAllRead() {
const hideLoading = message.loading({
content: '正在标记全部已读...',
duration: 0,
key: 'action_process_msg',
key: 'action_key_msg',
});
//
await updateAllNotifyMessageRead();
//
message.success('全部标记已读成功');
await gridApi.grid.setAllCheckboxRow(false);
onRefresh();
}
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<SystemNotifyMessageApi.NotifyMessage>) {
switch (code) {
case 'detail': {
onDetail(row);
break;
}
case 'read': {
onRead(row);
break;
}
try {
//
await updateAllNotifyMessageRead();
//
message.success({
content: '全部标记已读成功',
key: 'action_key_msg',
});
await gridApi.grid.setAllCheckboxRow(false);
onRefresh();
} finally {
hideLoading();
}
}
@ -112,7 +106,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(onActionClick),
columns: useGridColumns(),
height: 'auto',
keepSource: true,
proxyConfig: {
@ -150,14 +144,42 @@ const [Grid, gridApi] = useVbenVxeGrid({
<DetailModal @success="onRefresh" />
<Grid table-title="">
<template #toolbar-tools>
<Button type="primary" @click="onMarkRead">
<MdiCheckboxMarkedCircleOutline />
标记已读
</Button>
<Button type="primary" class="ml-2" @click="onMarkAllRead">
<MdiCheckboxMarkedCircleOutline />
全部已读
</Button>
<TableAction
:actions="[
{
label: '标记已读',
type: 'primary',
icon: 'mdi:checkbox-marked-circle-outline',
onClick: handleMarkRead,
},
{
label: '全部已读',
type: 'primary',
icon: 'mdi:checkbox-marked-circle-outline',
onClick: handleMarkAllRead,
},
]"
/>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: '查看',
type: 'link',
ifShow: row.readStatus,
icon: ACTION_ICON.VIEW,
onClick: handleDetail.bind(null, row),
},
{
label: '已读',
type: 'link',
ifShow: !row.readStatus,
icon: ACTION_ICON.DELETE,
onClick: handleRead.bind(null, row),
},
]"
/>
</template>
</Grid>
</Page>

View File

@ -1,8 +1,5 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { SystemNotifyTemplateApi } from '#/api/system/notify/template';
import { useAccess } from '@vben/access';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { z } from '#/adapter/form';
import { getSimpleUserList } from '#/api/system/user';
@ -14,8 +11,6 @@ import {
UserTypeEnum,
} from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] {
return [
@ -229,9 +224,7 @@ export function useSendNotifyFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
export function useGridColumns<T = SystemNotifyTemplateApi.NotifyTemplate>(
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
export function useGridColumns(): VxeTableGridOptions['columns'] {
return [
{
field: 'id',
@ -288,34 +281,10 @@ export function useGridColumns<T = SystemNotifyTemplateApi.NotifyTemplate>(
formatter: 'formatDateTime',
},
{
field: 'operation',
title: '操作',
minWidth: 180,
align: 'center',
width: 220,
fixed: 'right',
cellRender: {
attrs: {
nameField: 'name',
nameTitle: '站内信模板',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'edit',
show: hasAccessByCodes(['system:notify-template:update']),
},
{
code: 'send',
text: '测试',
show: hasAccessByCodes(['system:notify-template:send-notify']),
},
{
code: 'delete',
show: hasAccessByCodes(['system:notify-template:delete']),
},
],
},
slots: { default: 'actions' },
},
];
}

View File

@ -1,17 +1,13 @@
<script lang="ts" setup>
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { SystemNotifyTemplateApi } from '#/api/system/notify/template';
import { Page, useVbenModal } from '@vben/common-ui';
import { Download, Plus } from '@vben/icons';
import { downloadFileFromBlobPart } from '@vben/utils';
import { Button, message } from 'ant-design-vue';
import { message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteNotifyTemplate,
exportNotifyTemplate,
@ -40,69 +36,50 @@ function onRefresh() {
}
/** 导出表格 */
async function onExport() {
async function handleExport() {
const data = await exportNotifyTemplate(await gridApi.formApi.getValues());
downloadFileFromBlobPart({ fileName: '站内信模板.xls', source: data });
}
/** 创建站内信模板 */
function onCreate() {
function handleCreate() {
formModalApi.setData(null).open();
}
/** 编辑站内信模板 */
function onEdit(row: SystemNotifyTemplateApi.NotifyTemplate) {
function handleEdit(row: SystemNotifyTemplateApi.NotifyTemplate) {
formModalApi.setData(row).open();
}
/** 发送测试站内信 */
function onSend(row: SystemNotifyTemplateApi.NotifyTemplate) {
function handleSend(row: SystemNotifyTemplateApi.NotifyTemplate) {
sendModalApi.setData(row).open();
}
/** 删除站内信模板 */
async function onDelete(row: SystemNotifyTemplateApi.NotifyTemplate) {
async function handleDelete(row: SystemNotifyTemplateApi.NotifyTemplate) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.name]),
duration: 0,
key: 'action_process_msg',
key: 'action_key_msg',
});
try {
await deleteNotifyTemplate(row.id as number);
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
message.success({
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
key: 'action_key_msg',
});
onRefresh();
} finally {
hideLoading();
}
}
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<SystemNotifyTemplateApi.NotifyTemplate>) {
switch (code) {
case 'delete': {
onDelete(row);
break;
}
case 'edit': {
onEdit(row);
break;
}
case 'send': {
onSend(row);
break;
}
}
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(onActionClick),
columns: useGridColumns(),
height: 'auto',
keepSource: true,
proxyConfig: {
@ -137,23 +114,55 @@ const [Grid, gridApi] = useVbenVxeGrid({
<SendModal />
<Grid table-title="">
<template #toolbar-tools>
<Button
type="primary"
@click="onCreate"
v-access:code="['system:notify-template:create']"
>
<Plus class="size-5" />
{{ $t('ui.actionTitle.create', ['站内信模板']) }}
</Button>
<Button
type="primary"
class="ml-2"
@click="onExport"
v-access:code="['system:notify-template:export']"
>
<Download class="size-5" />
{{ $t('ui.actionTitle.export') }}
</Button>
<TableAction
:actions="[
{
label: $t('ui.actionTitle.create', ['短信渠道']),
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['system:notify-template:create'],
onClick: handleCreate,
},
{
label: $t('ui.actionTitle.export'),
type: 'primary',
icon: ACTION_ICON.DOWNLOAD,
auth: ['system:notify-template:export'],
onClick: handleExport,
},
]"
/>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.edit'),
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['system:notify-template:update'],
onClick: handleEdit.bind(null, row),
},
{
label: '测试',
type: 'link',
icon: ACTION_ICON.ADD,
auth: ['system:notify-template:send-notify'],
onClick: handleSend.bind(null, row),
},
{
label: $t('common.delete'),
type: 'link',
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['system:notify-template:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
</Page>

Some files were not shown because too many files have changed in this diff Show More