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 { :root {
--vxe-ui-font-color: hsl(var(--foreground)); --vxe-ui-font-color: hsl(var(--foreground));
--vxe-ui-font-primary-color: hsl(var(--primary)); --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>> = ( export type OnActionClickFn<T = Recordable<any>> = (
params: OnActionClickParams<T>, params: OnActionClickParams<T>,
) => void; ) => void;
export * from '#/components/table-action';
export type * from '@vben/plugins/vxe-table'; export type * from '@vben/plugins/vxe-table';

View File

@ -9,7 +9,7 @@ export namespace Demo01ContactApi {
export interface Demo01Contact { export interface Demo01Contact {
id: number; // 编号 id: number; // 编号
name?: string; // 名字 name?: string; // 名字
sex?: boolean; // 性别 sex?: number; // 性别
birthday?: Dayjs | string; // 出生年 birthday?: Dayjs | string; // 出生年
description?: string; // 简介 description?: string; // 简介
avatar: string; // 头像 avatar: string; // 头像
@ -46,6 +46,14 @@ export function deleteDemo01Contact(id: number) {
return requestClient.delete(`/infra/demo01-contact/delete?id=${id}`); 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) { export function exportDemo01Contact(params: any) {
return requestClient.download('/infra/demo01-contact/export-excel', params); return requestClient.download('/infra/demo01-contact/export-excel', params);

View File

@ -34,7 +34,7 @@ export namespace Demo03StudentApi {
/** 查询学生分页 */ /** 查询学生分页 */
export function getDemo03StudentPage(params: PageParam) { export function getDemo03StudentPage(params: PageParam) {
return requestClient.get<PageResult<Demo03StudentApi.Demo03Student>>( return requestClient.get<PageResult<Demo03StudentApi.Demo03Student>>(
'/infra/demo03-student/page', '/infra/demo03-student-erp/page',
{ params }, { params },
); );
} }
@ -42,28 +42,38 @@ export function getDemo03StudentPage(params: PageParam) {
/** 查询学生详情 */ /** 查询学生详情 */
export function getDemo03Student(id: number) { export function getDemo03Student(id: number) {
return requestClient.get<Demo03StudentApi.Demo03Student>( return requestClient.get<Demo03StudentApi.Demo03Student>(
`/infra/demo03-student/get?id=${id}`, `/infra/demo03-student-erp/get?id=${id}`,
); );
} }
/** 新增学生 */ /** 新增学生 */
export function createDemo03Student(data: Demo03StudentApi.Demo03Student) { 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) { 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) { 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) { 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) { export function getDemo03CoursePage(params: PageParam) {
return requestClient.get<PageResult<Demo03StudentApi.Demo03Course>>( return requestClient.get<PageResult<Demo03StudentApi.Demo03Course>>(
`/infra/demo03-student/demo03-course/page`, `/infra/demo03-student-erp/demo03-course/page`,
{ { params },
params,
},
); );
} }
/** 新增学生课程 */ /** 新增学生课程 */
export function createDemo03Course(data: Demo03StudentApi.Demo03Course) { 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) { 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) { export function deleteDemo03Course(id: number) {
return requestClient.delete( 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) { export function getDemo03Course(id: number) {
return requestClient.get<Demo03StudentApi.Demo03Course>( 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) { export function getDemo03GradePage(params: PageParam) {
return requestClient.get<PageResult<Demo03StudentApi.Demo03Grade>>( return requestClient.get<PageResult<Demo03StudentApi.Demo03Grade>>(
`/infra/demo03-student/demo03-grade/page`, `/infra/demo03-student-erp/demo03-grade/page`,
{ { params },
params,
},
); );
} }
/** 新增学生班级 */ /** 新增学生班级 */
export function createDemo03Grade(data: Demo03StudentApi.Demo03Grade) { 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) { 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) { export function deleteDemo03Grade(id: number) {
return requestClient.delete( 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) { export function getDemo03Grade(id: number) {
return requestClient.get<Demo03StudentApi.Demo03Grade>( 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 type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request'; import { requestClient } from '#/api/request';
@ -24,7 +26,7 @@ export namespace Demo03StudentApi {
id: number; // 编号 id: number; // 编号
name?: string; // 名字 name?: string; // 名字
sex?: number; // 性别 sex?: number; // 性别
birthday?: Date; // 出生日期 birthday?: Dayjs | string; // 出生日期
description?: string; // 简介 description?: string; // 简介
demo03courses?: Demo03Course[]; demo03courses?: Demo03Course[];
demo03grade?: Demo03Grade; demo03grade?: Demo03Grade;
@ -34,7 +36,7 @@ export namespace Demo03StudentApi {
/** 查询学生分页 */ /** 查询学生分页 */
export function getDemo03StudentPage(params: PageParam) { export function getDemo03StudentPage(params: PageParam) {
return requestClient.get<PageResult<Demo03StudentApi.Demo03Student>>( return requestClient.get<PageResult<Demo03StudentApi.Demo03Student>>(
'/infra/demo03-student/page', '/infra/demo03-student-inner/page',
{ params }, { params },
); );
} }
@ -42,28 +44,38 @@ export function getDemo03StudentPage(params: PageParam) {
/** 查询学生详情 */ /** 查询学生详情 */
export function getDemo03Student(id: number) { export function getDemo03Student(id: number) {
return requestClient.get<Demo03StudentApi.Demo03Student>( return requestClient.get<Demo03StudentApi.Demo03Student>(
`/infra/demo03-student/get?id=${id}`, `/infra/demo03-student-inner/get?id=${id}`,
); );
} }
/** 新增学生 */ /** 新增学生 */
export function createDemo03Student(data: Demo03StudentApi.Demo03Student) { 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) { 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) { 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) { 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) { export function getDemo03CourseListByStudentId(studentId: number) {
return requestClient.get<Demo03StudentApi.Demo03Course[]>( 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) { export function getDemo03GradeByStudentId(studentId: number) {
return requestClient.get<Demo03StudentApi.Demo03Grade>( 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) { export function getDemo03StudentPage(params: PageParam) {
return requestClient.get<PageResult<Demo03StudentApi.Demo03Student>>( return requestClient.get<PageResult<Demo03StudentApi.Demo03Student>>(
'/infra/demo03-student/page', '/infra/demo03-student-normal/page',
{ params }, { params },
); );
} }
@ -44,28 +44,38 @@ export function getDemo03StudentPage(params: PageParam) {
/** 查询学生详情 */ /** 查询学生详情 */
export function getDemo03Student(id: number) { export function getDemo03Student(id: number) {
return requestClient.get<Demo03StudentApi.Demo03Student>( return requestClient.get<Demo03StudentApi.Demo03Student>(
`/infra/demo03-student/get?id=${id}`, `/infra/demo03-student-normal/get?id=${id}`,
); );
} }
/** 新增学生 */ /** 新增学生 */
export function createDemo03Student(data: Demo03StudentApi.Demo03Student) { 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) { 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) { 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) { 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) { export function getDemo03CourseListByStudentId(studentId: number) {
return requestClient.get<Demo03StudentApi.Demo03Course[]>( 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) { export function getDemo03GradeByStudentId(studentId: number) {
return requestClient.get<Demo03StudentApi.Demo03Grade>( 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; retryInterval: number;
monitorTimeout: number; monitorTimeout: number;
createTime?: Date; createTime?: Date;
nextTimes?: Date[];
} }
} }

View File

@ -1,3 +1,4 @@
// TODO @
<script lang="ts" setup> <script lang="ts" setup>
import type { DataNode } from 'ant-design-vue/es/tree'; 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'; export * from './typing';

View File

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

View File

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

View File

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

View File

@ -34,6 +34,7 @@ import ProcessDesign from './modules/process-design.vue';
defineOptions({ name: 'BpmModelCreate' }); defineOptions({ name: 'BpmModelCreate' });
// TODO apps 使 @utils/constant.ts @ // 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 = { const BpmModelType = {
BPMN: 10, // BPMN BPMN: 10, // BPMN
SIMPLE: 20, // SIMPLE: 20, //

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,17 +5,18 @@ import type {
} from '#/adapter/vxe-table'; } from '#/adapter/vxe-table';
import type { Demo01ContactApi } from '#/api/infra/demo/demo01'; 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 { Page, useVbenModal } from '@vben/common-ui';
import { Download, Plus } from '@vben/icons'; import { Download, Plus, Trash2 } from '@vben/icons';
import { downloadFileFromBlobPart } from '@vben/utils'; import { downloadFileFromBlobPart, isEmpty } from '@vben/utils';
import { Button, message } from 'ant-design-vue'; import { Button, message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table'; import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { import {
deleteDemo01Contact, deleteDemo01Contact,
deleteDemo01ContactListByIds,
exportDemo01Contact, exportDemo01Contact,
getDemo01ContactPage, getDemo01ContactPage,
} from '#/api/infra/demo/demo01'; } from '#/api/infra/demo/demo01';
@ -55,11 +56,38 @@ async function onDelete(row: Demo01ContactApi.Demo01Contact) {
await deleteDemo01Contact(row.id as number); await deleteDemo01Contact(row.id as number);
message.success($t('ui.actionMessage.deleteSuccess', [row.id])); message.success($t('ui.actionMessage.deleteSuccess', [row.id]));
onRefresh(); onRefresh();
} catch { } finally {
hideLoading(); 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() { async function onExport() {
const data = await exportDemo01Contact(await gridApi.formApi.getValues()); const data = await exportDemo01Contact(await gridApi.formApi.getValues());
@ -113,6 +141,10 @@ const [Grid, gridApi] = useVbenVxeGrid({
search: true, search: true,
}, },
} as VxeTableGridOptions<Demo01ContactApi.Demo01Contact>, } as VxeTableGridOptions<Demo01ContactApi.Demo01Contact>,
gridEvents: {
checkboxAll: setDeleteIds,
checkboxChange: setDeleteIds,
},
}); });
</script> </script>
@ -139,6 +171,17 @@ const [Grid, gridApi] = useVbenVxeGrid({
> >
{{ $t('ui.actionTitle.export') }} {{ $t('ui.actionTitle.export') }}
</Button> </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> </template>
</Grid> </Grid>
</Page> </Page>

View File

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

View File

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

View File

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

View File

@ -8,14 +8,15 @@ import type { Demo03StudentApi } from '#/api/infra/demo/demo03/erp';
import { h, ref } from 'vue'; import { h, ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui'; import { Page, useVbenModal } from '@vben/common-ui';
import { Download, Plus } from '@vben/icons'; import { Download, Plus, Trash2 } from '@vben/icons';
import { downloadFileFromBlobPart } from '@vben/utils'; import { downloadFileFromBlobPart, isEmpty } from '@vben/utils';
import { Button, message, Tabs } from 'ant-design-vue'; import { Button, message, Tabs } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table'; import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { import {
deleteDemo03Student, deleteDemo03Student,
deleteDemo03StudentListByIds,
exportDemo03Student, exportDemo03Student,
getDemo03StudentPage, getDemo03StudentPage,
} from '#/api/infra/demo/demo03/erp'; } from '#/api/infra/demo/demo03/erp';
@ -61,11 +62,36 @@ async function onDelete(row: Demo03StudentApi.Demo03Student) {
await deleteDemo03Student(row.id as number); await deleteDemo03Student(row.id as number);
message.success($t('ui.actionMessage.deleteSuccess', [row.id])); message.success($t('ui.actionMessage.deleteSuccess', [row.id]));
onRefresh(); onRefresh();
} catch { } finally {
hideLoading(); 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() { async function onExport() {
const data = await exportDemo03Student(await gridApi.formApi.getValues()); const data = await exportDemo03Student(await gridApi.formApi.getValues());
@ -124,6 +150,8 @@ const [Grid, gridApi] = useVbenVxeGrid({
cellClick: ({ row }: { row: Demo03StudentApi.Demo03Student }) => { cellClick: ({ row }: { row: Demo03StudentApi.Demo03Student }) => {
selectDemo03Student.value = row; selectDemo03Student.value = row;
}, },
checkboxAll: setDeleteIds,
checkboxChange: setDeleteIds,
}, },
}); });
</script> </script>
@ -152,6 +180,17 @@ const [Grid, gridApi] = useVbenVxeGrid({
> >
{{ $t('ui.actionTitle.export') }} {{ $t('ui.actionTitle.export') }}
</Button> </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> </template>
</Grid> </Grid>

View File

@ -5,16 +5,18 @@ import type {
} from '#/adapter/vxe-table'; } from '#/adapter/vxe-table';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/erp'; 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 { 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 { Button, message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table'; import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { import {
deleteDemo03Course, deleteDemo03Course,
deleteDemo03CourseListByIds,
getDemo03CoursePage, getDemo03CoursePage,
} from '#/api/infra/demo/demo03/erp'; } from '#/api/infra/demo/demo03/erp';
import { $t } from '#/locales'; import { $t } from '#/locales';
@ -59,11 +61,36 @@ async function onDelete(row: Demo03StudentApi.Demo03Course) {
await deleteDemo03Course(row.id as number); await deleteDemo03Course(row.id as number);
message.success($t('ui.actionMessage.deleteSuccess', [row.id])); message.success($t('ui.actionMessage.deleteSuccess', [row.id]));
onRefresh(); onRefresh();
} catch { } finally {
hideLoading(); 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({ function onActionClick({
code, code,
@ -115,6 +142,10 @@ const [Grid, gridApi] = useVbenVxeGrid({
isHover: true, isHover: true,
}, },
} as VxeTableGridOptions<Demo03StudentApi.Demo03Course>, } as VxeTableGridOptions<Demo03StudentApi.Demo03Course>,
gridEvents: {
checkboxAll: setDeleteIds,
checkboxChange: setDeleteIds,
},
}); });
/** 刷新表格 */ /** 刷新表格 */
@ -148,6 +179,17 @@ watch(
> >
{{ $t('ui.actionTitle.create', ['学生课程']) }} {{ $t('ui.actionTitle.create', ['学生课程']) }}
</Button> </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> </template>
</Grid> </Grid>
</template> </template>

View File

@ -5,16 +5,18 @@ import type {
} from '#/adapter/vxe-table'; } from '#/adapter/vxe-table';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/erp'; 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 { 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 { Button, message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table'; import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { import {
deleteDemo03Grade, deleteDemo03Grade,
deleteDemo03GradeListByIds,
getDemo03GradePage, getDemo03GradePage,
} from '#/api/infra/demo/demo03/erp'; } from '#/api/infra/demo/demo03/erp';
import { $t } from '#/locales'; import { $t } from '#/locales';
@ -59,11 +61,36 @@ async function onDelete(row: Demo03StudentApi.Demo03Grade) {
await deleteDemo03Grade(row.id as number); await deleteDemo03Grade(row.id as number);
message.success($t('ui.actionMessage.deleteSuccess', [row.id])); message.success($t('ui.actionMessage.deleteSuccess', [row.id]));
onRefresh(); onRefresh();
} catch { } finally {
hideLoading(); 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({ function onActionClick({
code, code,
@ -115,6 +142,10 @@ const [Grid, gridApi] = useVbenVxeGrid({
isHover: true, isHover: true,
}, },
} as VxeTableGridOptions<Demo03StudentApi.Demo03Grade>, } as VxeTableGridOptions<Demo03StudentApi.Demo03Grade>,
gridEvents: {
checkboxAll: setDeleteIds,
checkboxChange: setDeleteIds,
},
}); });
/** 刷新表格 */ /** 刷新表格 */
@ -148,6 +179,17 @@ watch(
> >
{{ $t('ui.actionTitle.create', ['学生班级']) }} {{ $t('ui.actionTitle.create', ['学生班级']) }}
</Button> </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> </template>
</Grid> </Grid>
</template> </template>

View File

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

View File

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

View File

@ -8,14 +8,15 @@ import type { Demo03StudentApi } from '#/api/infra/demo/demo03/inner';
import { h, ref } from 'vue'; import { h, ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui'; import { Page, useVbenModal } from '@vben/common-ui';
import { Download, Plus } from '@vben/icons'; import { Download, Plus, Trash2 } from '@vben/icons';
import { downloadFileFromBlobPart } from '@vben/utils'; import { downloadFileFromBlobPart, isEmpty } from '@vben/utils';
import { Button, message, Tabs } from 'ant-design-vue'; import { Button, message, Tabs } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table'; import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { import {
deleteDemo03Student, deleteDemo03Student,
deleteDemo03StudentListByIds,
exportDemo03Student, exportDemo03Student,
getDemo03StudentPage, getDemo03StudentPage,
} from '#/api/infra/demo/demo03/inner'; } from '#/api/infra/demo/demo03/inner';
@ -60,11 +61,36 @@ async function onDelete(row: Demo03StudentApi.Demo03Student) {
await deleteDemo03Student(row.id as number); await deleteDemo03Student(row.id as number);
message.success($t('ui.actionMessage.deleteSuccess', [row.id])); message.success($t('ui.actionMessage.deleteSuccess', [row.id]));
onRefresh(); onRefresh();
} catch { } finally {
hideLoading(); 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() { async function onExport() {
const data = await exportDemo03Student(await gridApi.formApi.getValues()); const data = await exportDemo03Student(await gridApi.formApi.getValues());
@ -118,6 +144,10 @@ const [Grid, gridApi] = useVbenVxeGrid({
search: true, search: true,
}, },
} as VxeTableGridOptions<Demo03StudentApi.Demo03Student>, } as VxeTableGridOptions<Demo03StudentApi.Demo03Student>,
gridEvents: {
checkboxAll: setDeleteIds,
checkboxChange: setDeleteIds,
},
}); });
</script> </script>
@ -155,6 +185,17 @@ const [Grid, gridApi] = useVbenVxeGrid({
> >
{{ $t('ui.actionTitle.export') }} {{ $t('ui.actionTitle.export') }}
</Button> </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> </template>
</Grid> </Grid>
</Page> </Page>

View File

@ -16,18 +16,18 @@ const props = defineProps<{
const [Grid, gridApi] = useVbenVxeGrid({ const [Grid, gridApi] = useVbenVxeGrid({
gridOptions: { gridOptions: {
columns: useDemo03CourseGridColumns(), columns: useDemo03CourseGridColumns(),
height: 'auto',
rowConfig: {
keyField: 'id',
isHover: true,
},
pagerConfig: { pagerConfig: {
enabled: false, enabled: false,
}, },
toolbarConfig: { toolbarConfig: {
enabled: false, 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({ const [Form, formApi] = useVbenForm({
commonConfig: {
componentProps: {
class: 'w-full',
},
formItemClass: 'col-span-2',
labelWidth: 80,
},
layout: 'horizontal', layout: 'horizontal',
schema: useDemo03GradeFormSchema(), schema: useDemo03GradeFormSchema(),
showDefaultActions: false, showDefaultActions: false,

View File

@ -16,18 +16,18 @@ const props = defineProps<{
const [Grid, gridApi] = useVbenVxeGrid({ const [Grid, gridApi] = useVbenVxeGrid({
gridOptions: { gridOptions: {
columns: useDemo03GradeGridColumns(), columns: useDemo03GradeGridColumns(),
height: 'auto',
rowConfig: {
keyField: 'id',
isHover: true,
},
pagerConfig: { pagerConfig: {
enabled: false, enabled: false,
}, },
toolbarConfig: { toolbarConfig: {
enabled: false, 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; const data = (await formApi.getValues()) as Demo03StudentApi.Demo03Student;
// //
data.demo03courses = demo03CourseFormRef.value?.getData(); data.demo03Courses = demo03CourseFormRef.value?.getData();
data.demo03grade = await demo03GradeFormRef.value?.getValues(); data.demo03Grade = await demo03GradeFormRef.value?.getValues();
try { try {
await (formData.value?.id await (formData.value?.id
? updateDemo03Student(data) ? updateDemo03Student(data)
@ -80,7 +80,6 @@ const [Modal, modalApi] = useVbenModal({
formData.value = undefined; formData.value = undefined;
return; return;
} }
// //
let data = modalApi.getData<Demo03StudentApi.Demo03Student>(); let data = modalApi.getData<Demo03StudentApi.Demo03Student>();
if (!data) { if (!data) {

View File

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

View File

@ -5,17 +5,18 @@ import type {
} from '#/adapter/vxe-table'; } from '#/adapter/vxe-table';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/normal'; 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 { Page, useVbenModal } from '@vben/common-ui';
import { Download, Plus } from '@vben/icons'; import { Download, Plus, Trash2 } from '@vben/icons';
import { downloadFileFromBlobPart } from '@vben/utils'; import { downloadFileFromBlobPart, isEmpty } from '@vben/utils';
import { Button, message } from 'ant-design-vue'; import { Button, message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table'; import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { import {
deleteDemo03Student, deleteDemo03Student,
deleteDemo03StudentListByIds,
exportDemo03Student, exportDemo03Student,
getDemo03StudentPage, getDemo03StudentPage,
} from '#/api/infra/demo/demo03/normal'; } from '#/api/infra/demo/demo03/normal';
@ -55,7 +56,31 @@ async function onDelete(row: Demo03StudentApi.Demo03Student) {
await deleteDemo03Student(row.id as number); await deleteDemo03Student(row.id as number);
message.success($t('ui.actionMessage.deleteSuccess', [row.id])); message.success($t('ui.actionMessage.deleteSuccess', [row.id]));
onRefresh(); 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(); hideLoading();
} }
} }
@ -113,6 +138,10 @@ const [Grid, gridApi] = useVbenVxeGrid({
search: true, search: true,
}, },
} as VxeTableGridOptions<Demo03StudentApi.Demo03Student>, } as VxeTableGridOptions<Demo03StudentApi.Demo03Student>,
gridEvents: {
checkboxAll: setDeleteIds,
checkboxChange: setDeleteIds,
},
}); });
</script> </script>
@ -139,6 +168,17 @@ const [Grid, gridApi] = useVbenVxeGrid({
> >
{{ $t('ui.actionTitle.export') }} {{ $t('ui.actionTitle.export') }}
</Button> </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> </template>
</Grid> </Grid>
</Page> </Page>

View File

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

View File

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

View File

@ -4,11 +4,12 @@ import type { Demo01ContactApi } from '#/api/infra/demo/demo01';
import { h, onMounted, reactive, ref } from 'vue'; import { h, onMounted, reactive, ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui'; import { Page, useVbenModal } from '@vben/common-ui';
import { Download, Plus } from '@vben/icons'; import { Download, Plus, Trash2 } from '@vben/icons';
import { import {
cloneDeep, cloneDeep,
downloadFileFromBlobPart, downloadFileFromBlobPart,
formatDateTime, formatDateTime,
isEmpty,
} from '@vben/utils'; } from '@vben/utils';
import { import {
@ -24,6 +25,7 @@ import {
import { VxeColumn, VxeTable } from '#/adapter/vxe-table'; import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
import { import {
deleteDemo01Contact, deleteDemo01Contact,
deleteDemo01ContactListByIds,
exportDemo01Contact, exportDemo01Contact,
getDemo01ContactPage, getDemo01ContactPage,
} from '#/api/infra/demo/demo01'; } from '#/api/infra/demo/demo01';
@ -38,6 +40,7 @@ import Demo01ContactForm from './modules/form.vue';
const loading = ref(true); // const loading = ref(true); //
const list = ref<Demo01ContactApi.Demo01Contact[]>([]); // const list = ref<Demo01ContactApi.Demo01Contact[]>([]); //
const total = ref(0); // const total = ref(0); //
const queryParams = reactive({ const queryParams = reactive({
pageNo: 1, pageNo: 1,
@ -101,13 +104,41 @@ async function onDelete(row: Demo01ContactApi.Demo01Contact) {
}); });
try { try {
await deleteDemo01Contact(row.id as number); await deleteDemo01Contact(row.id as number);
message.success($t('ui.actionMessage.deleteSuccess', [row.id])); message.success({
content: $t('ui.actionMessage.deleteSuccess', [row.id]),
key: 'action_process_msg',
});
await getList(); await getList();
} catch { } finally {
hideLoading(); 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() { async function onExport() {
try { try {
@ -205,9 +236,28 @@ onMounted(() => {
> >
{{ $t('ui.actionTitle.export') }} {{ $t('ui.actionTitle.export') }}
</Button> </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> </TableToolbar>
</template> </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="id" title="编号" align="center" />
<VxeColumn field="name" title="名字" align="center" /> <VxeColumn field="name" title="名字" align="center" />
<VxeColumn field="sex" title="性别" align="center"> <VxeColumn field="sex" title="性别" align="center">
@ -240,6 +290,7 @@ onMounted(() => {
<Button <Button
size="small" size="small"
type="link" type="link"
danger
class="ml-2" class="ml-2"
@click="onDelete(row as any)" @click="onDelete(row as any)"
v-access:code="['infra:demo01-contact:delete']" v-access:code="['infra:demo01-contact:delete']"

View File

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

View File

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

View File

@ -4,16 +4,16 @@ import type { Demo03StudentApi } from '#/api/infra/demo/demo03/erp';
import { h, onMounted, reactive, ref } from 'vue'; import { h, onMounted, reactive, ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui'; import { Page, useVbenModal } from '@vben/common-ui';
import { Download, Plus } from '@vben/icons'; import { Download, Plus, Trash2 } from '@vben/icons';
import { import {
cloneDeep, cloneDeep,
downloadFileFromBlobPart, downloadFileFromBlobPart,
formatDateTime, formatDateTime,
isEmpty,
} from '@vben/utils'; } from '@vben/utils';
import { import {
Button, Button,
DatePicker,
Form, Form,
Input, Input,
message, message,
@ -26,6 +26,7 @@ import {
import { VxeColumn, VxeTable } from '#/adapter/vxe-table'; import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
import { import {
deleteDemo03Student, deleteDemo03Student,
deleteDemo03StudentListByIds,
exportDemo03Student, exportDemo03Student,
getDemo03StudentPage, getDemo03StudentPage,
} from '#/api/infra/demo/demo03/erp'; } from '#/api/infra/demo/demo03/erp';
@ -56,7 +57,6 @@ const queryParams = reactive({
pageSize: 10, pageSize: 10,
name: undefined, name: undefined,
sex: undefined, sex: undefined,
birthday: undefined,
description: undefined, description: undefined,
createTime: undefined, createTime: undefined,
}); });
@ -68,9 +68,6 @@ const getList = async () => {
loading.value = true; loading.value = true;
try { try {
const params = cloneDeep(queryParams) as any; 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)) { if (params.createTime && Array.isArray(params.createTime)) {
params.createTime = (params.createTime as string[]).join(','); params.createTime = (params.createTime as string[]).join(',');
} }
@ -118,13 +115,41 @@ async function onDelete(row: Demo03StudentApi.Demo03Student) {
}); });
try { try {
await deleteDemo03Student(row.id as number); 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(); await getList();
} catch { } finally {
hideLoading(); 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() { async function onExport() {
try { try {
@ -178,15 +203,6 @@ onMounted(() => {
</Select.Option> </Select.Option>
</Select> </Select>
</Form.Item> </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"> <Form.Item label="创建时间" name="createTime">
<RangePicker <RangePicker
v-model:value="queryParams.createTime" v-model:value="queryParams.createTime"
@ -229,6 +245,17 @@ onMounted(() => {
> >
{{ $t('ui.actionTitle.export') }} {{ $t('ui.actionTitle.export') }}
</Button> </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> </TableToolbar>
</template> </template>
<VxeTable <VxeTable
@ -242,7 +269,10 @@ onMounted(() => {
}" }"
show-overflow show-overflow
:loading="loading" :loading="loading"
@checkbox-all="setDeleteIds"
@checkbox-change="setDeleteIds"
> >
<VxeColumn type="checkbox" width="40" />
<VxeColumn field="id" title="编号" align="center" /> <VxeColumn field="id" title="编号" align="center" />
<VxeColumn field="name" title="名字" align="center" /> <VxeColumn field="name" title="名字" align="center" />
<VxeColumn field="sex" 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 { h, nextTick, onMounted, reactive, ref, watch } from 'vue';
import { useVbenModal } from '@vben/common-ui'; import { useVbenModal } from '@vben/common-ui';
import { Plus } from '@vben/icons'; import { Plus, Trash2 } from '@vben/icons';
import { cloneDeep, formatDateTime } from '@vben/utils'; import { cloneDeep, formatDateTime, isEmpty } from '@vben/utils';
import { import {
Button, Button,
@ -19,6 +19,7 @@ import {
import { VxeColumn, VxeTable } from '#/adapter/vxe-table'; import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
import { import {
deleteDemo03Course, deleteDemo03Course,
deleteDemo03CourseListByIds,
getDemo03CoursePage, getDemo03CoursePage,
} from '#/api/infra/demo/demo03/erp'; } from '#/api/infra/demo/demo03/erp';
import { ContentWrap } from '#/components/content-wrap'; import { ContentWrap } from '#/components/content-wrap';
@ -61,13 +62,41 @@ async function onDelete(row: Demo03StudentApi.Demo03Course) {
}); });
try { try {
await deleteDemo03Course(row.id as number); await deleteDemo03Course(row.id as number);
message.success($t('ui.actionMessage.deleteSuccess', [row.id])); message.success({
getList(); content: $t('ui.actionMessage.deleteSuccess', [row.id]),
} catch { key: 'action_process_msg',
});
await getList();
} finally {
hideLoading(); 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 loading = ref(true); //
const list = ref<Demo03StudentApi.Demo03Course[]>([]); // const list = ref<Demo03StudentApi.Demo03Course[]>([]); //
const total = ref(0); // const total = ref(0); //
@ -100,9 +129,6 @@ const getList = async () => {
return []; return [];
} }
const params = cloneDeep(queryParams) as any; 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)) { if (params.createTime && Array.isArray(params.createTime)) {
params.createTime = (params.createTime as string[]).join(','); params.createTime = (params.createTime as string[]).join(',');
} }
@ -200,9 +226,28 @@ onMounted(() => {
> >
{{ $t('ui.actionTitle.create', ['学生']) }} {{ $t('ui.actionTitle.create', ['学生']) }}
</Button> </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> </TableToolbar>
</template> </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="id" title="编号" align="center" />
<VxeColumn field="studentId" title="学生编号" align="center" /> <VxeColumn field="studentId" title="学生编号" align="center" />
<VxeColumn field="name" 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 { h, nextTick, onMounted, reactive, ref, watch } from 'vue';
import { useVbenModal } from '@vben/common-ui'; import { useVbenModal } from '@vben/common-ui';
import { Plus } from '@vben/icons'; import { Plus, Trash2 } from '@vben/icons';
import { cloneDeep, formatDateTime } from '@vben/utils'; import { cloneDeep, formatDateTime, isEmpty } from '@vben/utils';
import { import {
Button, Button,
@ -19,6 +19,7 @@ import {
import { VxeColumn, VxeTable } from '#/adapter/vxe-table'; import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
import { import {
deleteDemo03Grade, deleteDemo03Grade,
deleteDemo03GradeListByIds,
getDemo03GradePage, getDemo03GradePage,
} from '#/api/infra/demo/demo03/erp'; } from '#/api/infra/demo/demo03/erp';
import { ContentWrap } from '#/components/content-wrap'; import { ContentWrap } from '#/components/content-wrap';
@ -61,13 +62,41 @@ async function onDelete(row: Demo03StudentApi.Demo03Grade) {
}); });
try { try {
await deleteDemo03Grade(row.id as number); await deleteDemo03Grade(row.id as number);
message.success($t('ui.actionMessage.deleteSuccess', [row.id])); message.success({
getList(); content: $t('ui.actionMessage.deleteSuccess', [row.id]),
} catch { key: 'action_process_msg',
});
await getList();
} finally {
hideLoading(); 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 loading = ref(true); //
const list = ref<Demo03StudentApi.Demo03Grade[]>([]); // const list = ref<Demo03StudentApi.Demo03Grade[]>([]); //
const total = ref(0); // const total = ref(0); //
@ -100,9 +129,6 @@ const getList = async () => {
return []; return [];
} }
const params = cloneDeep(queryParams) as any; 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)) { if (params.createTime && Array.isArray(params.createTime)) {
params.createTime = (params.createTime as string[]).join(','); params.createTime = (params.createTime as string[]).join(',');
} }
@ -200,9 +226,28 @@ onMounted(() => {
> >
{{ $t('ui.actionTitle.create', ['学生']) }} {{ $t('ui.actionTitle.create', ['学生']) }}
</Button> </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> </TableToolbar>
</template> </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="id" title="编号" align="center" />
<VxeColumn field="studentId" title="学生编号" align="center" /> <VxeColumn field="studentId" title="学生编号" align="center" />
<VxeColumn field="name" 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"> <RadioGroup v-model:value="formData.sex">
<Radio <Radio
v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number')" v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number')"
:key="dict.value" :key="dict.value.toString()"
:value="dict.value" :value="dict.value"
> >
{{ dict.label }} {{ 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 { h, onMounted, reactive, ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui'; import { Page, useVbenModal } from '@vben/common-ui';
import { Download, Plus } from '@vben/icons'; import { Download, Plus, Trash2 } from '@vben/icons';
import { import {
cloneDeep, cloneDeep,
downloadFileFromBlobPart, downloadFileFromBlobPart,
formatDateTime, formatDateTime,
isEmpty,
} from '@vben/utils'; } from '@vben/utils';
import { import {
Button, Button,
DatePicker,
Form, Form,
Input, Input,
message, message,
@ -26,6 +26,7 @@ import {
import { VxeColumn, VxeTable } from '#/adapter/vxe-table'; import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
import { import {
deleteDemo03Student, deleteDemo03Student,
deleteDemo03StudentListByIds,
exportDemo03Student, exportDemo03Student,
getDemo03StudentPage, getDemo03StudentPage,
} from '#/api/infra/demo/demo03/normal'; } from '#/api/infra/demo/demo03/normal';
@ -52,7 +53,6 @@ const queryParams = reactive({
pageSize: 10, pageSize: 10,
name: undefined, name: undefined,
sex: undefined, sex: undefined,
birthday: undefined,
description: undefined, description: undefined,
createTime: undefined, createTime: undefined,
}); });
@ -64,9 +64,6 @@ const getList = async () => {
loading.value = true; loading.value = true;
try { try {
const params = cloneDeep(queryParams) as any; 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)) { if (params.createTime && Array.isArray(params.createTime)) {
params.createTime = (params.createTime as string[]).join(','); params.createTime = (params.createTime as string[]).join(',');
} }
@ -114,13 +111,41 @@ async function onDelete(row: Demo03StudentApi.Demo03Student) {
}); });
try { try {
await deleteDemo03Student(row.id as number); 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(); await getList();
} catch { } finally {
hideLoading(); 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() { async function onExport() {
try { try {
@ -174,15 +199,6 @@ onMounted(() => {
</Select.Option> </Select.Option>
</Select> </Select>
</Form.Item> </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"> <Form.Item label="创建时间" name="createTime">
<RangePicker <RangePicker
v-model:value="queryParams.createTime" v-model:value="queryParams.createTime"
@ -225,9 +241,28 @@ onMounted(() => {
> >
{{ $t('ui.actionTitle.export') }} {{ $t('ui.actionTitle.export') }}
</Button> </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> </TableToolbar>
</template> </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"> <VxeColumn type="expand" width="60">
<template #content="{ row }"> <template #content="{ row }">

View File

@ -136,7 +136,7 @@ const [Modal, modalApi] = useVbenModal({
<RadioGroup v-model:value="formData.sex"> <RadioGroup v-model:value="formData.sex">
<Radio <Radio
v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number')" v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number')"
:key="dict.value" :key="dict.value.toString()"
:value="dict.value" :value="dict.value"
> >
{{ dict.label }} {{ 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 { h, onMounted, reactive, ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui'; import { Page, useVbenModal } from '@vben/common-ui';
import { Download, Plus } from '@vben/icons'; import { Download, Plus, Trash2 } from '@vben/icons';
import { import {
cloneDeep, cloneDeep,
downloadFileFromBlobPart, downloadFileFromBlobPart,
formatDateTime, formatDateTime,
isEmpty,
} from '@vben/utils'; } from '@vben/utils';
import { import {
Button, Button,
DatePicker,
Form, Form,
Input, Input,
message, message,
@ -25,6 +25,7 @@ import {
import { VxeColumn, VxeTable } from '#/adapter/vxe-table'; import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
import { import {
deleteDemo03Student, deleteDemo03Student,
deleteDemo03StudentListByIds,
exportDemo03Student, exportDemo03Student,
getDemo03StudentPage, getDemo03StudentPage,
} from '#/api/infra/demo/demo03/normal'; } from '#/api/infra/demo/demo03/normal';
@ -46,7 +47,6 @@ const queryParams = reactive({
pageSize: 10, pageSize: 10,
name: undefined, name: undefined,
sex: undefined, sex: undefined,
birthday: undefined,
description: undefined, description: undefined,
createTime: undefined, createTime: undefined,
}); });
@ -58,9 +58,6 @@ const getList = async () => {
loading.value = true; loading.value = true;
try { try {
const params = cloneDeep(queryParams) as any; 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)) { if (params.createTime && Array.isArray(params.createTime)) {
params.createTime = (params.createTime as string[]).join(','); params.createTime = (params.createTime as string[]).join(',');
} }
@ -108,13 +105,41 @@ async function onDelete(row: Demo03StudentApi.Demo03Student) {
}); });
try { try {
await deleteDemo03Student(row.id as number); 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(); await getList();
} catch { } finally {
hideLoading(); 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() { async function onExport() {
try { try {
@ -168,15 +193,6 @@ onMounted(() => {
</Select.Option> </Select.Option>
</Select> </Select>
</Form.Item> </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"> <Form.Item label="创建时间" name="createTime">
<RangePicker <RangePicker
v-model:value="queryParams.createTime" v-model:value="queryParams.createTime"
@ -219,9 +235,28 @@ onMounted(() => {
> >
{{ $t('ui.actionTitle.export') }} {{ $t('ui.actionTitle.export') }}
</Button> </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> </TableToolbar>
</template> </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="id" title="编号" align="center" />
<VxeColumn field="name" title="名字" align="center" /> <VxeColumn field="name" title="名字" align="center" />
<VxeColumn field="sex" 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; const data = formData.value as Demo03StudentApi.Demo03Student;
// //
data.demo03courses = demo03CourseFormRef.value?.getData(); data.demo03courses = demo03CourseFormRef.value?.getData();
data.demo03grade = demo03GradeFormRef.value?.getValues(); data.demo03grade = demo03GradeFormRef.value?.getValues() as any;
try { try {
await (formData.value?.id await (formData.value?.id
? updateDemo03Student(data) ? updateDemo03Student(data)
@ -135,7 +135,7 @@ const [Modal, modalApi] = useVbenModal({
<RadioGroup v-model:value="formData.sex"> <RadioGroup v-model:value="formData.sex">
<Radio <Radio
v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number')" v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number')"
:key="dict.value" :key="dict.value.toString()"
:value="dict.value" :value="dict.value"
> >
{{ dict.label }} {{ dict.label }}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,7 @@
import type { VbenFormSchema } from '#/adapter/form'; import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { SystemDeptApi } from '#/api/system/dept'; import type { SystemDeptApi } from '#/api/system/dept';
import { useAccess } from '@vben/access';
import { handleTree } from '@vben/utils'; import { handleTree } from '@vben/utils';
import { z } from '#/adapter/form'; import { z } from '#/adapter/form';
@ -10,8 +9,6 @@ import { getDeptList } from '#/api/system/dept';
import { getSimpleUserList } from '#/api/system/user'; import { getSimpleUserList } from '#/api/system/user';
import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils'; import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 新增/修改的表单 */ /** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] { export function useFormSchema(): VbenFormSchema[] {
return [ return [
@ -113,7 +110,6 @@ export function useFormSchema(): VbenFormSchema[] {
/** 列表的字段 */ /** 列表的字段 */
export function useGridColumns( export function useGridColumns(
onActionClick?: OnActionClickFn<SystemDeptApi.Dept>,
getLeaderName?: (userId: number) => string | undefined, getLeaderName?: (userId: number) => string | undefined,
): VxeTableGridOptions<SystemDeptApi.Dept>['columns'] { ): VxeTableGridOptions<SystemDeptApi.Dept>['columns'] {
return [ return [
@ -154,39 +150,10 @@ export function useGridColumns(
formatter: 'formatDateTime', formatter: 'formatDateTime',
}, },
{ {
field: 'operation',
title: '操作', title: '操作',
minWidth: 200, width: 220,
align: 'right',
fixed: 'right', fixed: 'right',
headerAlign: 'center', slots: { default: 'actions' },
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);
},
},
],
},
}, },
]; ];
} }

View File

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

View File

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

View File

@ -5,18 +5,16 @@ import type { SystemDictDataApi } from '#/api/system/dict/data';
import { watch } from 'vue'; import { watch } from 'vue';
import { useVbenModal } from '@vben/common-ui'; import { useVbenModal } from '@vben/common-ui';
import { Download, Plus } from '@vben/icons';
import { downloadFileFromBlobPart } from '@vben/utils'; import { downloadFileFromBlobPart } from '@vben/utils';
import { Button, message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table'; import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { import {
deleteDictData, deleteDictData,
exportDictData, exportDictData,
getDictDataPage, getDictDataPage,
} from '#/api/system/dict/data'; } from '#/api/system/dict/data';
import { ACTION_KEY, TableAction } from '#/components/table-action';
import { $t } from '#/locales'; import { $t } from '#/locales';
import { useDataGridColumns, useDataGridFormSchema } from '../data'; 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()); const data = await exportDictData(await gridApi.formApi.getValues());
downloadFileFromBlobPart({ fileName: '字典数据.xls', source: data }); downloadFileFromBlobPart({ fileName: '字典数据.xls', source: data });
} }
/** 创建字典数据 */ /** 创建字典数据 */
function onCreate() { function handleCreate() {
dataFormModalApi.setData({ dictType: props.dictType }).open(); dataFormModalApi.setData({ dictType: props.dictType }).open();
} }
/** 编辑字典数据 */ /** 编辑字典数据 */
function onEdit(row: SystemDictDataApi.DictData) { function handleEdit(row: SystemDictDataApi.DictData) {
dataFormModalApi.setData(row).open(); dataFormModalApi.setData(row).open();
} }
/** 删除字典数据 */ /** 删除字典数据 */
async function onDelete(row: SystemDictDataApi.DictData) { async function handleDelete(row: SystemDictDataApi.DictData) {
message.loading({ const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.label]), content: $t('ui.actionMessage.deleting', [row.label]),
key: ACTION_KEY, key: 'action_key_msg',
}); });
try {
await deleteDictData(row.id as number); await deleteDictData(row.id as number);
message.success({ message.success({
content: $t('ui.actionMessage.deleteSuccess', [row.label]), content: $t('ui.actionMessage.deleteSuccess', [row.label]),
key: ACTION_KEY, key: 'action_key_msg',
}); });
onRefresh(); onRefresh();
} finally {
hideLoading();
}
} }
const [Grid, gridApi] = useVbenVxeGrid({ const [Grid, gridApi] = useVbenVxeGrid({
@ -116,23 +118,24 @@ watch(
<Grid table-title=""> <Grid table-title="">
<template #toolbar-tools> <template #toolbar-tools>
<Button <TableAction
type="primary" :actions="[
@click="onCreate" {
v-access:code="['system:dict:create']" label: $t('ui.actionTitle.create', ['字典数据']),
> type: 'primary',
<Plus class="size-5" /> icon: ACTION_ICON.ADD,
{{ $t('ui.actionTitle.create', ['字典数据']) }} auth: ['system:dict:create'],
</Button> onClick: handleCreate,
<Button },
type="primary" {
class="ml-2" label: $t('ui.actionTitle.export'),
@click="onExport" type: 'primary',
v-access:code="['system:dict:export']" icon: ACTION_ICON.DOWNLOAD,
> auth: ['system:dict:export'],
<Download class="size-5" /> onClick: handleExport,
{{ $t('ui.actionTitle.export') }} },
</Button> ]"
/>
</template> </template>
<template #actions="{ row }"> <template #actions="{ row }">
<TableAction <TableAction
@ -140,19 +143,19 @@ watch(
{ {
label: $t('common.edit'), label: $t('common.edit'),
type: 'link', type: 'link',
icon: 'ant-design:edit-outlined', icon: ACTION_ICON.EDIT,
auth: ['system:dict:update'], auth: ['system:dict:update'],
onClick: onEdit.bind(null, row), onClick: handleEdit.bind(null, row),
}, },
{ {
label: $t('common.delete'), label: $t('common.delete'),
type: 'link', type: 'link',
danger: true, danger: true,
icon: 'ant-design:delete-outlined', icon: ACTION_ICON.DELETE,
auth: ['system:dict:delete'], auth: ['system:dict:delete'],
popConfirm: { popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.label]), 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 type { SystemDictTypeApi } from '#/api/system/dict/type';
import { useVbenModal } from '@vben/common-ui'; import { useVbenModal } from '@vben/common-ui';
import { Download, Plus } from '@vben/icons';
import { downloadFileFromBlobPart } from '@vben/utils'; import { downloadFileFromBlobPart } from '@vben/utils';
import { Button, message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table'; import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { import {
deleteDictType, deleteDictType,
exportDictType, exportDictType,
getDictTypePage, getDictTypePage,
} from '#/api/system/dict/type'; } from '#/api/system/dict/type';
import { ACTION_KEY, TableAction } from '#/components/table-action';
import { $t } from '#/locales'; import { $t } from '#/locales';
import { useTypeGridColumns, useTypeGridFormSchema } from '../data'; 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()); const data = await exportDictType(await gridApi.formApi.getValues());
downloadFileFromBlobPart({ fileName: '字典类型.xls', source: data }); downloadFileFromBlobPart({ fileName: '字典类型.xls', source: data });
} }
/** 创建字典类型 */ /** 创建字典类型 */
function onCreate() { function handleCreate() {
typeFormModalApi.setData(null).open(); typeFormModalApi.setData(null).open();
} }
/** 编辑字典类型 */ /** 编辑字典类型 */
function onEdit(row: any) { function handleEdit(row: any) {
typeFormModalApi.setData(row).open(); typeFormModalApi.setData(row).open();
} }
/** 删除字典类型 */ /** 删除字典类型 */
async function onDelete(row: SystemDictTypeApi.DictType) { async function handleDelete(row: SystemDictTypeApi.DictType) {
message.loading({ const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.name]), content: $t('ui.actionMessage.deleting', [row.name]),
key: ACTION_KEY, key: 'action_key_msg',
}); });
try {
await deleteDictType(row.id as number); await deleteDictType(row.id as number);
message.success({ message.success({
content: $t('ui.actionMessage.deleteSuccess', [row.name]), content: $t('ui.actionMessage.deleteSuccess', [row.name]),
key: ACTION_KEY, key: 'action_key_msg',
}); });
onRefresh(); onRefresh();
} finally {
hideLoading();
}
} }
/** 表格事件 */ /** 表格事件 */
@ -110,23 +112,24 @@ const [Grid, gridApi] = useVbenVxeGrid({
<Grid table-title=""> <Grid table-title="">
<template #toolbar-tools> <template #toolbar-tools>
<Button <TableAction
type="primary" :actions="[
@click="onCreate" {
v-access:code="['system:dict:create']" label: $t('ui.actionTitle.create', ['字典类型']),
> type: 'primary',
<Plus class="size-5" /> icon: ACTION_ICON.ADD,
{{ $t('ui.actionTitle.create', ['字典类型']) }} auth: ['system:dict:create'],
</Button> onClick: handleCreate,
<Button },
type="primary" {
class="ml-2" label: $t('ui.actionTitle.export'),
@click="onExport" type: 'primary',
v-access:code="['system:dict:export']" icon: ACTION_ICON.DOWNLOAD,
> auth: ['system:dict:export'],
<Download class="size-5" /> onClick: handleExport,
{{ $t('ui.actionTitle.export') }} },
</Button> ]"
/>
</template> </template>
<template #actions="{ row }"> <template #actions="{ row }">
<TableAction <TableAction
@ -134,19 +137,19 @@ const [Grid, gridApi] = useVbenVxeGrid({
{ {
label: $t('common.edit'), label: $t('common.edit'),
type: 'link', type: 'link',
icon: 'ant-design:edit-outlined', icon: ACTION_ICON.EDIT,
auth: ['system:dict:update'], auth: ['system:dict:update'],
onClick: onEdit.bind(null, row), onClick: handleEdit.bind(null, row),
}, },
{ {
label: $t('common.delete'), label: $t('common.delete'),
type: 'link', type: 'link',
danger: true, danger: true,
icon: 'ant-design:delete-outlined', icon: ACTION_ICON.DELETE,
auth: ['system:dict:delete'], auth: ['system:dict:delete'],
popConfirm: { popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.name]), 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 { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { SystemLoginLogApi } from '#/api/system/login-log';
import { useAccess } from '@vben/access';
import { DICT_TYPE, getRangePickerDefaultProps } from '#/utils'; import { DICT_TYPE, getRangePickerDefaultProps } from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 列表的搜索表单 */ /** 列表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] { export function useGridFormSchema(): VbenFormSchema[] {
return [ return [
@ -42,9 +37,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
} }
/** 列表的字段 */ /** 列表的字段 */
export function useGridColumns<T = SystemLoginLogApi.LoginLog>( export function useGridColumns(): VxeTableGridOptions['columns'] {
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
return [ return [
{ {
field: 'id', field: 'id',
@ -91,26 +84,10 @@ export function useGridColumns<T = SystemLoginLogApi.LoginLog>(
formatter: 'formatDateTime', formatter: 'formatDateTime',
}, },
{ {
field: 'operation',
title: '操作', title: '操作',
minWidth: 120, width: 80,
align: 'center',
fixed: 'right', fixed: 'right',
cellRender: { slots: { default: 'actions' },
attrs: {
nameField: 'username',
nameTitle: '登录日志',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'detail',
text: '详情',
show: hasAccessByCodes(['system:login-log:query']),
},
],
},
}, },
]; ];
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -1,13 +1,10 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { import type { VxeTableGridOptions } from '#/adapter/vxe-table';
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { SystemMailLogApi } from '#/api/system/mail/log'; import type { SystemMailLogApi } from '#/api/system/mail/log';
import { Page, useVbenModal } from '@vben/common-ui'; 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 { getMailLogPage } from '#/api/system/mail/log';
import { DocAlert } from '#/components/doc-alert'; 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(); detailModalApi.setData(row).open();
} }
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<SystemMailLogApi.MailLog>) {
switch (code) {
case 'detail': {
onDetail(row);
break;
}
}
}
const [Grid, gridApi] = useVbenVxeGrid({ const [Grid, gridApi] = useVbenVxeGrid({
formOptions: { formOptions: {
schema: useGridFormSchema(), schema: useGridFormSchema(),
}, },
gridOptions: { gridOptions: {
columns: useGridColumns(onActionClick), columns: useGridColumns(),
height: 'auto', height: 'auto',
keepSource: true, keepSource: true,
proxyConfig: { proxyConfig: {
@ -79,7 +63,19 @@ const [Grid, gridApi] = useVbenVxeGrid({
<DetailModal @success="onRefresh" /> <DetailModal @success="onRefresh" />
<Grid table-title=""> <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> </Grid>
</Page> </Page>
</template> </template>

View File

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

View File

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

View File

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

View File

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

View File

@ -1,18 +1,15 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { import type { VxeTableGridOptions } from '#/adapter/vxe-table';
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { SystemMenuApi } from '#/api/system/menu'; import type { SystemMenuApi } from '#/api/system/menu';
import { ref } from 'vue'; import { ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui'; 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 { deleteMenu, getMenuList } from '#/api/system/menu';
import { DocAlert } from '#/components/doc-alert'; import { DocAlert } from '#/components/doc-alert';
import { $t } from '#/locales'; import { $t } from '#/locales';
@ -32,54 +29,38 @@ function onRefresh() {
} }
/** 创建菜单 */ /** 创建菜单 */
function onCreate() { function handleCreate() {
formModalApi.setData({}).open(); formModalApi.setData({}).open();
} }
/** 添加下级菜单 */ /** 添加下级菜单 */
function onAppend(row: SystemMenuApi.Menu) { function handleAppend(row: SystemMenuApi.Menu) {
formModalApi.setData({ pid: row.id }).open(); formModalApi.setData({ pid: row.id }).open();
} }
/** 编辑菜单 */ /** 编辑菜单 */
function onEdit(row: SystemMenuApi.Menu) { function handleEdit(row: SystemMenuApi.Menu) {
formModalApi.setData(row).open(); formModalApi.setData(row).open();
} }
/** 删除菜单 */ /** 删除菜单 */
async function onDelete(row: SystemMenuApi.Menu) { async function handleDelete(row: SystemMenuApi.Menu) {
const hideLoading = message.loading({ const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.name]), content: $t('ui.actionMessage.deleting', [row.name]),
duration: 0, key: 'action_key_msg',
key: 'action_process_msg',
}); });
try { try {
await deleteMenu(row.id as number); 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(); onRefresh();
} catch { } finally {
hideLoading(); 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); const isExpanded = ref(false);
function toggleExpand() { function toggleExpand() {
@ -89,7 +70,7 @@ function toggleExpand() {
const [Grid, gridApi] = useVbenVxeGrid({ const [Grid, gridApi] = useVbenVxeGrid({
gridOptions: { gridOptions: {
columns: useGridColumns(onActionClick), columns: useGridColumns(),
height: 'auto', height: 'auto',
keepSource: true, keepSource: true,
pagerConfig: { pagerConfig: {
@ -131,17 +112,22 @@ const [Grid, gridApi] = useVbenVxeGrid({
<FormModal @success="onRefresh" /> <FormModal @success="onRefresh" />
<Grid> <Grid>
<template #toolbar-tools> <template #toolbar-tools>
<Button <TableAction
type="primary" :actions="[
@click="onCreate" {
v-access:code="['system:menu:create']" label: $t('ui.actionTitle.create', ['菜单']),
> type: 'primary',
<Plus class="size-5" /> icon: ACTION_ICON.ADD,
{{ $t('ui.actionTitle.create', ['菜单']) }} auth: ['system:menu:create'],
</Button> onClick: handleCreate,
<Button class="ml-2" @click="toggleExpand"> },
{{ isExpanded ? '收缩' : '展开' }} {
</Button> label: isExpanded ? '收缩' : '展开',
type: 'primary',
onClick: toggleExpand,
},
]"
/>
</template> </template>
<template #name="{ row }"> <template #name="{ row }">
<div class="flex w-full items-center gap-1"> <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 class="items-center justify-end"></div>
</div> </div>
</template> </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> </Grid>
</Page> </Page>
</template> </template>

View File

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

View File

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

View File

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

View File

@ -1,13 +1,10 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { import type { VxeTableGridOptions } from '#/adapter/vxe-table';
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { SystemNotifyMessageApi } from '#/api/system/notify/message'; import type { SystemNotifyMessageApi } from '#/api/system/notify/message';
import { Page, useVbenModal } from '@vben/common-ui'; 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 { getNotifyMessagePage } from '#/api/system/notify/message';
import { DocAlert } from '#/components/doc-alert'; 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(); detailModalApi.setData(row).open();
} }
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<SystemNotifyMessageApi.NotifyMessage>) {
switch (code) {
case 'detail': {
onDetail(row);
break;
}
}
}
const [Grid, gridApi] = useVbenVxeGrid({ const [Grid, gridApi] = useVbenVxeGrid({
formOptions: { formOptions: {
schema: useGridFormSchema(), schema: useGridFormSchema(),
}, },
gridOptions: { gridOptions: {
columns: useGridColumns(onActionClick), columns: useGridColumns(),
height: 'auto', height: 'auto',
keepSource: true, keepSource: true,
proxyConfig: { proxyConfig: {
@ -79,6 +63,20 @@ const [Grid, gridApi] = useVbenVxeGrid({
</template> </template>
<DetailModal @success="onRefresh" /> <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> </Page>
</template> </template>

View File

@ -1,6 +1,5 @@
import type { VbenFormSchema } from '#/adapter/form'; import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { SystemNotifyMessageApi } from '#/api/system/notify/message';
import type { DescriptionItemSchema } from '#/components/description'; import type { DescriptionItemSchema } from '#/components/description';
import { h } from 'vue'; import { h } from 'vue';
@ -36,9 +35,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
} }
/** 列表的字段 */ /** 列表的字段 */
export function useGridColumns<T = SystemNotifyMessageApi.NotifyMessage>( export function useGridColumns(): VxeTableGridOptions['columns'] {
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
return [ return [
{ {
title: '', title: '',
@ -86,31 +83,10 @@ export function useGridColumns<T = SystemNotifyMessageApi.NotifyMessage>(
formatter: 'formatDateTime', formatter: 'formatDateTime',
}, },
{ {
field: 'operation',
title: '操作', title: '操作',
minWidth: 180, width: 130,
align: 'center',
fixed: 'right', fixed: 'right',
cellRender: { slots: { default: 'actions' },
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,
},
],
},
}, },
]; ];
} }

View File

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

View File

@ -1,8 +1,5 @@
import type { VbenFormSchema } from '#/adapter/form'; import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { SystemNotifyTemplateApi } from '#/api/system/notify/template';
import { useAccess } from '@vben/access';
import { z } from '#/adapter/form'; import { z } from '#/adapter/form';
import { getSimpleUserList } from '#/api/system/user'; import { getSimpleUserList } from '#/api/system/user';
@ -14,8 +11,6 @@ import {
UserTypeEnum, UserTypeEnum,
} from '#/utils'; } from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 新增/修改的表单 */ /** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] { export function useFormSchema(): VbenFormSchema[] {
return [ return [
@ -229,9 +224,7 @@ export function useSendNotifyFormSchema(): VbenFormSchema[] {
} }
/** 列表的字段 */ /** 列表的字段 */
export function useGridColumns<T = SystemNotifyTemplateApi.NotifyTemplate>( export function useGridColumns(): VxeTableGridOptions['columns'] {
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
return [ return [
{ {
field: 'id', field: 'id',
@ -288,34 +281,10 @@ export function useGridColumns<T = SystemNotifyTemplateApi.NotifyTemplate>(
formatter: 'formatDateTime', formatter: 'formatDateTime',
}, },
{ {
field: 'operation',
title: '操作', title: '操作',
minWidth: 180, width: 220,
align: 'center',
fixed: 'right', fixed: 'right',
cellRender: { slots: { default: 'actions' },
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']),
},
],
},
}, },
]; ];
} }

View File

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

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