Browse Source

feat: add log

DESKTOP-USV654P\pc 1 year ago
parent
commit
e6cbd8c313

+ 3 - 3
apps/web-baicai/src/api/model/index.ts

@@ -20,7 +20,7 @@ export interface TransferOptionResult {
 
 export interface BasicOptionResult {
   label: string;
-  value: number | string;
+  value: boolean | number | string;
   disabled?: boolean;
 }
 
@@ -50,8 +50,8 @@ export const formatterStatus = ({
 };
 
 export const boolOptions: BasicOptionResult[] = [
-  { label: '是', value: 1 },
-  { label: '否', value: 0 },
+  { label: '是', value: true },
+  { label: '否', value: false },
 ];
 
 export const dbTypeOptions: BasicOptionResult[] = [

+ 2 - 0
apps/web-baicai/src/api/system/index.ts

@@ -2,7 +2,9 @@ export * from './config';
 export * from './database';
 export * from './department';
 export * from './enum';
+export * from './log';
 export * from './menu';
+export * from './post';
 export * from './query';
 export * from './role';
 export * from './tenant';

+ 32 - 0
apps/web-baicai/src/api/system/log.ts

@@ -0,0 +1,32 @@
+import type { BasicFetchResult, BasicPageParams } from '#/api/model';
+
+import { requestClient } from '#/api/request';
+
+export namespace LogApi {
+  export interface PageParams extends BasicPageParams {
+    name?: string;
+  }
+
+  export interface BasicRecordItem {
+    name: string;
+  }
+
+  export interface RecordItem extends BasicRecordItem {
+    id: number;
+  }
+
+  export type AppPageResult = BasicFetchResult<RecordItem>;
+
+  export const getAuditPage = (params: PageParams) =>
+    requestClient.get<AppPageResult>('/logAudit/page', { params });
+  export const getOperatePage = (params: PageParams) =>
+    requestClient.get<AppPageResult>('/logOperate/page', { params });
+  export const getExceptionPage = (params: PageParams) =>
+    requestClient.get<AppPageResult>('/logException/page', { params });
+  export const getDifferencePage = (params: PageParams) =>
+    requestClient.get<AppPageResult>('/logDifference/page', { params });
+  export const getVisitPage = (params: PageParams) =>
+    requestClient.get<AppPageResult>('/logVisit/page', { params });
+
+  export const clearLog = (type: string) => requestClient.delete(`/${type}`);
+}

+ 45 - 0
apps/web-baicai/src/api/system/post.ts

@@ -0,0 +1,45 @@
+import type {
+  BasicFetchResult,
+  BasicPageParams,
+  StatusParams,
+} from '#/api/model';
+
+import { requestClient } from '#/api/request';
+
+export namespace PostApi {
+  export interface PageParams extends BasicPageParams {
+    name?: string;
+    code?: string;
+  }
+
+  export interface BasicRecordItem {
+    name: string;
+    code: string;
+  }
+
+  export interface RecordItem extends BasicRecordItem {
+    id: number;
+  }
+
+  export type AppPageResult = BasicFetchResult<RecordItem>;
+
+  export const getPage = (params: PageParams) =>
+    requestClient.get<AppPageResult>('/post/page', { params });
+
+  export const getDetail = (id: number) =>
+    requestClient.get<RecordItem>('/post/entity', {
+      params: { id },
+    });
+
+  export const addDetail = (data: BasicRecordItem) =>
+    requestClient.post('/post', data);
+
+  export const editDetail = (data: RecordItem) =>
+    requestClient.put('/post', data);
+
+  export const deleteDetail = (id: number) =>
+    requestClient.delete('/post', { data: { id } });
+
+  export const updateStatus = (data: StatusParams) =>
+    requestClient.put('/post/status', data);
+}

+ 2 - 2
apps/web-baicai/src/views/system/config/data.config.ts

@@ -31,10 +31,10 @@ export const gridOptions: VxeGridProps<ConfigApi.RecordItem> = {
       align: 'left',
       field: 'code',
       title: '编码',
-      width: 150,
+      width: 250,
     },
     { align: 'left', field: 'name', title: '名称', width: 200 },
-    { align: 'left', field: 'value', title: '参数值', width: 200 },
+    { align: 'left', field: 'value', title: '参数值', width: 150 },
     { align: 'left', field: 'remark', title: '备注' },
     {
       field: 'action',

+ 36 - 0
apps/web-baicai/src/views/system/log/audit/index.vue

@@ -0,0 +1,36 @@
+<script lang="ts" setup>
+import { useAccess } from '@vben/access';
+import { Page } from '@vben/common-ui';
+
+import { useVbenVxeGrid } from '#/adapter';
+import { TableAction } from '#/components/table-action';
+
+import { gridOptions, searchFormOptions } from '../data.config';
+
+const { hasAccessByCodes } = useAccess();
+
+const [Grid] = useVbenVxeGrid({
+  formOptions: searchFormOptions,
+  gridOptions,
+});
+</script>
+
+<template>
+  <Page auto-content-height>
+    <Grid>
+      <template #toolbar-tools> </template>
+      <template #action>
+        <TableAction
+          :actions="[
+            {
+              label: '详情',
+              type: 'text',
+              disabled: !hasAccessByCodes(['logAudit:edit']),
+              // onClick: handleEdit.bind(null, row, true),
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </Page>
+</template>

+ 395 - 0
apps/web-baicai/src/views/system/log/data.config.ts

@@ -0,0 +1,395 @@
+import type { VbenFormProps, VxeGridProps } from '#/adapter';
+
+import { LogApi } from '#/api';
+
+export const searchFormOptions: VbenFormProps = {
+  schema: [
+    {
+      component: 'RangePicker',
+      fieldName: '__date',
+      label: '日期',
+      componentProps: {
+        format: 'YYYY-MM-DD HH:mm:ss',
+        placeholder: ['开始时间', '结束时间'],
+        showTime: { format: 'HH:mm:ss' },
+      },
+    },
+  ],
+};
+
+export const gridOptions: VxeGridProps<LogApi.RecordItem> = {
+  toolbarConfig: {
+    refresh: true,
+    print: false,
+    export: false,
+    zoom: true,
+    custom: true,
+  },
+  columns: [
+    { title: '序号', type: 'seq', width: 50 },
+    { align: 'left', field: 'tableName', title: '表名', width: 120 },
+    { align: 'left', field: 'columnName', title: '列名', width: 120 },
+    { align: 'left', field: 'newValue', title: '新值', width: 100 },
+    { align: 'left', field: 'oldValue', title: '旧值', width: 100 },
+    { align: 'left', field: 'operate', title: '操作方式', width: 80 },
+    { align: 'left', field: 'auditTime', title: '审计时间', width: 150 },
+    { align: 'left', field: 'account', title: '账号', width: 100 },
+    { align: 'left', field: 'realName', title: '姓名' },
+    {
+      field: 'action',
+      fixed: 'right',
+      slots: { default: 'action' },
+      title: '操作',
+      width: 100,
+    },
+  ],
+  height: 'auto',
+  keepSource: true,
+  proxyConfig: {
+    ajax: {
+      query: async ({ page }, formValues) => {
+        const postData = { ...formValues };
+        if (postData.__date) {
+          postData.startTime = postData.__date[0];
+          postData.endTime = postData.__date[1];
+          delete postData.__date;
+        }
+        return await LogApi.getAuditPage({
+          pageIndex: page.currentPage,
+          pageSize: page.pageSize,
+          ...postData,
+        });
+      },
+    },
+  },
+};
+
+export const gridDifferenceOptions: VxeGridProps<LogApi.RecordItem> = {
+  toolbarConfig: {
+    refresh: true,
+    print: false,
+    export: false,
+    zoom: true,
+    custom: true,
+  },
+  columns: [
+    { title: '序号', type: 'seq', width: 50 },
+    {
+      align: 'left',
+      field: 'beforeData',
+      title: '操作前记录',
+      width: 150,
+      showOverflow: true,
+    },
+    {
+      align: 'left',
+      field: 'afterData',
+      title: '操作后记录',
+      width: 150,
+      showOverflow: true,
+    },
+    {
+      align: 'left',
+      field: 'sql',
+      title: 'SQL',
+      width: 100,
+      showOverflow: true,
+    },
+    {
+      align: 'left',
+      field: 'parameters',
+      title: '参数',
+      showOverflow: true,
+    },
+    {
+      align: 'left',
+      field: 'businessData',
+      title: '业务对象',
+      width: 100,
+      showOverflow: true,
+    },
+    {
+      align: 'left',
+      field: 'diffType',
+      title: '差异操作',
+      width: 80,
+    },
+    {
+      align: 'left',
+      field: 'elapsed',
+      title: '耗时',
+      width: 80,
+    },
+  ],
+  height: 'auto',
+  keepSource: true,
+  proxyConfig: {
+    ajax: {
+      query: async ({ page }, formValues) => {
+        const postData = { ...formValues };
+        if (postData.__date) {
+          postData.startTime = postData.__date[0];
+          postData.endTime = postData.__date[1];
+          delete postData.__date;
+        }
+        return await LogApi.getDifferencePage({
+          pageIndex: page.currentPage,
+          pageSize: page.pageSize,
+          ...postData,
+        });
+      },
+    },
+  },
+};
+
+export const gridExceptionOptions: VxeGridProps<LogApi.RecordItem> = {
+  toolbarConfig: {
+    refresh: true,
+    print: false,
+    export: false,
+    zoom: true,
+    custom: true,
+  },
+  columns: [
+    { title: '序号', type: 'seq', width: 50 },
+    {
+      align: 'left',
+      field: 'httpMethod',
+      title: '请求方式',
+      width: 80,
+    },
+    {
+      align: 'left',
+      field: 'requestUrl',
+      title: '请求地址',
+      width: 250,
+      showOverflow: true,
+    },
+    {
+      align: 'left',
+      field: 'requestParam',
+      title: '请求参数',
+      width: 100,
+      showOverflow: true,
+    },
+    {
+      align: 'left',
+      field: 'returnResult',
+      title: '返回结果',
+      showOverflow: true,
+      width: 200,
+    },
+    {
+      align: 'left',
+      field: 'exception',
+      title: '异常信息',
+      width: 150,
+      showOverflow: true,
+    },
+    {
+      align: 'left',
+      field: 'message',
+      title: '日志消息',
+      showOverflow: true,
+    },
+  ],
+  height: 'auto',
+  keepSource: true,
+  proxyConfig: {
+    ajax: {
+      query: async ({ page }, formValues) => {
+        const postData = { ...formValues };
+        if (postData.__date) {
+          postData.startTime = postData.__date[0];
+          postData.endTime = postData.__date[1];
+          delete postData.__date;
+        }
+        return await LogApi.getExceptionPage({
+          pageIndex: page.currentPage,
+          pageSize: page.pageSize,
+          ...postData,
+        });
+      },
+    },
+  },
+};
+
+export const gridOperateOptions: VxeGridProps<LogApi.RecordItem> = {
+  toolbarConfig: {
+    refresh: true,
+    print: false,
+    export: false,
+    zoom: true,
+    custom: true,
+  },
+  columns: [
+    { title: '序号', type: 'seq', width: 50 },
+    {
+      align: 'left',
+      field: 'httpMethod',
+      title: '请求方式',
+      width: 80,
+    },
+    {
+      align: 'left',
+      field: 'requestUrl',
+      title: '请求地址',
+      width: 250,
+      showOverflow: true,
+    },
+    {
+      align: 'left',
+      field: 'requestParam',
+      title: '请求参数',
+      width: 100,
+      showOverflow: true,
+    },
+    {
+      align: 'left',
+      field: 'returnResult',
+      title: '返回结果',
+      showOverflow: true,
+      width: 200,
+    },
+    {
+      align: 'left',
+      field: 'exception',
+      title: '异常信息',
+      width: 150,
+      showOverflow: true,
+    },
+    {
+      align: 'left',
+      field: 'message',
+      title: '日志消息',
+      showOverflow: true,
+    },
+  ],
+  height: 'auto',
+  keepSource: true,
+  proxyConfig: {
+    ajax: {
+      query: async ({ page }, formValues) => {
+        const postData = { ...formValues };
+        if (postData.__date) {
+          postData.startTime = postData.__date[0];
+          postData.endTime = postData.__date[1];
+          delete postData.__date;
+        }
+        return await LogApi.getOperatePage({
+          pageIndex: page.currentPage,
+          pageSize: page.pageSize,
+          ...postData,
+        });
+      },
+    },
+  },
+};
+
+export const gridVisitOptions: VxeGridProps<LogApi.RecordItem> = {
+  toolbarConfig: {
+    refresh: true,
+    print: false,
+    export: false,
+    zoom: true,
+    custom: true,
+  },
+  columns: [
+    { title: '序号', type: 'seq', width: 50 },
+    {
+      align: 'left',
+      field: 'controllerName',
+      title: '模块名称',
+      width: 120,
+      showOverflow: true,
+    },
+    {
+      align: 'left',
+      field: 'actionName',
+      title: '方法名称',
+      width: 120,
+      showOverflow: true,
+    },
+    {
+      align: 'left',
+      field: 'displayTitle',
+      title: '显示名称',
+      width: 100,
+      showOverflow: true,
+    },
+    {
+      align: 'left',
+      field: 'status',
+      title: '执行状态',
+      showOverflow: true,
+      width: 80,
+    },
+    {
+      align: 'left',
+      field: 'remoteIp',
+      title: 'IP地址',
+      width: 80,
+      showOverflow: true,
+    },
+    {
+      align: 'left',
+      field: 'location',
+      title: '登录地点',
+      width: 100,
+      showOverflow: true,
+    },
+    {
+      align: 'left',
+      field: 'browser',
+      title: '浏览器',
+      showOverflow: true,
+      width: 100,
+    },
+    {
+      align: 'left',
+      field: 'elapsed',
+      title: '操作用时',
+      width: 80,
+      showOverflow: true,
+    },
+    {
+      align: 'left',
+      field: 'logDateTime',
+      title: '日志时间',
+      width: 120,
+      showOverflow: true,
+    },
+    {
+      align: 'left',
+      field: 'account',
+      title: '账号',
+      width: 120,
+      showOverflow: true,
+    },
+    {
+      align: 'left',
+      field: 'realName',
+      title: '姓名',
+      width: 120,
+    },
+  ],
+  height: 'auto',
+  keepSource: true,
+  proxyConfig: {
+    ajax: {
+      query: async ({ page }, formValues) => {
+        const postData = { ...formValues };
+        if (postData.__date) {
+          postData.startTime = postData.__date[0];
+          postData.endTime = postData.__date[1];
+          delete postData.__date;
+        }
+        return await LogApi.getVisitPage({
+          pageIndex: page.currentPage,
+          pageSize: page.pageSize,
+          ...postData,
+        });
+      },
+    },
+  },
+};

+ 46 - 0
apps/web-baicai/src/views/system/log/difference/index.vue

@@ -0,0 +1,46 @@
+<script lang="ts" setup>
+import { Page } from '@vben/common-ui';
+
+import { Button, message, Modal } from 'ant-design-vue';
+
+import { useVbenVxeGrid } from '#/adapter';
+import { LogApi } from '#/api';
+
+import { gridDifferenceOptions, searchFormOptions } from '../data.config';
+
+const [Grid, { reload }] = useVbenVxeGrid({
+  formOptions: searchFormOptions,
+  gridOptions: gridDifferenceOptions,
+});
+
+const handelClear = () => {
+  Modal.confirm({
+    iconType: 'info',
+    title: '删除提示',
+    content: `确定要清空日志记录吗?`,
+    cancelText: `关闭`,
+    onOk: async () => {
+      await LogApi.clearLog('logdifference');
+      message.success('数据删除成功');
+      reload();
+    },
+  });
+};
+</script>
+
+<template>
+  <Page auto-content-height>
+    <Grid>
+      <template #toolbar-tools>
+        <Button
+          class="mr-2"
+          type="primary"
+          v-access:code="'logDifference:clear'"
+          @click="() => handelClear()"
+        >
+          清空日志
+        </Button>
+      </template>
+    </Grid>
+  </Page>
+</template>

+ 46 - 0
apps/web-baicai/src/views/system/log/exception/index.vue

@@ -0,0 +1,46 @@
+<script lang="ts" setup>
+import { Page } from '@vben/common-ui';
+
+import { Button, message, Modal } from 'ant-design-vue';
+
+import { useVbenVxeGrid } from '#/adapter';
+import { LogApi } from '#/api';
+
+import { gridExceptionOptions, searchFormOptions } from '../data.config';
+
+const [Grid, { reload }] = useVbenVxeGrid({
+  formOptions: searchFormOptions,
+  gridOptions: gridExceptionOptions,
+});
+
+const handelClear = () => {
+  Modal.confirm({
+    iconType: 'info',
+    title: '删除提示',
+    content: `确定要清空日志记录吗?`,
+    cancelText: `关闭`,
+    onOk: async () => {
+      await LogApi.clearLog('logException');
+      message.success('数据删除成功');
+      reload();
+    },
+  });
+};
+</script>
+
+<template>
+  <Page auto-content-height>
+    <Grid>
+      <template #toolbar-tools>
+        <Button
+          class="mr-2"
+          type="primary"
+          v-access:code="'logException:clear'"
+          @click="() => handelClear()"
+        >
+          清空日志
+        </Button>
+      </template>
+    </Grid>
+  </Page>
+</template>

+ 46 - 0
apps/web-baicai/src/views/system/log/operate/index.vue

@@ -0,0 +1,46 @@
+<script lang="ts" setup>
+import { Page } from '@vben/common-ui';
+
+import { Button, message, Modal } from 'ant-design-vue';
+
+import { useVbenVxeGrid } from '#/adapter';
+import { LogApi } from '#/api';
+
+import { gridOperateOptions, searchFormOptions } from '../data.config';
+
+const [Grid, { reload }] = useVbenVxeGrid({
+  formOptions: searchFormOptions,
+  gridOptions: gridOperateOptions,
+});
+
+const handelClear = () => {
+  Modal.confirm({
+    iconType: 'info',
+    title: '删除提示',
+    content: `确定要清空日志记录吗?`,
+    cancelText: `关闭`,
+    onOk: async () => {
+      await LogApi.clearLog('logOperate');
+      message.success('数据删除成功');
+      reload();
+    },
+  });
+};
+</script>
+
+<template>
+  <Page auto-content-height>
+    <Grid>
+      <template #toolbar-tools>
+        <Button
+          class="mr-2"
+          type="primary"
+          v-access:code="'logOperate:clear'"
+          @click="() => handelClear()"
+        >
+          清空日志
+        </Button>
+      </template>
+    </Grid>
+  </Page>
+</template>

+ 46 - 0
apps/web-baicai/src/views/system/log/visit/index.vue

@@ -0,0 +1,46 @@
+<script lang="ts" setup>
+import { Page } from '@vben/common-ui';
+
+import { Button, message, Modal } from 'ant-design-vue';
+
+import { useVbenVxeGrid } from '#/adapter';
+import { LogApi } from '#/api';
+
+import { gridVisitOptions, searchFormOptions } from '../data.config';
+
+const [Grid, { reload }] = useVbenVxeGrid({
+  formOptions: searchFormOptions,
+  gridOptions: gridVisitOptions,
+});
+
+const handelClear = () => {
+  Modal.confirm({
+    iconType: 'info',
+    title: '删除提示',
+    content: `确定要清空日志记录吗?`,
+    cancelText: `关闭`,
+    onOk: async () => {
+      await LogApi.clearLog('logVisit');
+      message.success('数据删除成功');
+      reload();
+    },
+  });
+};
+</script>
+
+<template>
+  <Page auto-content-height>
+    <Grid>
+      <template #toolbar-tools>
+        <Button
+          class="mr-2"
+          type="primary"
+          v-access:code="'logVisit:clear'"
+          @click="() => handelClear()"
+        >
+          清空日志
+        </Button>
+      </template>
+    </Grid>
+  </Page>
+</template>

+ 19 - 1
apps/web-baicai/src/views/system/menu/data.config.ts

@@ -89,7 +89,7 @@ export const gridOptions: VxeGridProps<MenuApi.RecordItem> = {
       width: 60,
       formatter: formatterStatus,
     },
-    { field: 'sort', title: '排序', width: 50 },
+    { field: 'sort', title: '排序', width: 80 },
     {
       field: 'action',
       fixed: 'right',
@@ -234,6 +234,12 @@ export const formOptions: VbenFormProps = {
         optionType: 'button',
         buttonStyle: 'solid',
       },
+      dependencies: {
+        triggerFields: ['type'],
+        show(values) {
+          return [1].includes(values.type);
+        },
+      },
       fieldName: 'keepAlive',
       label: '缓存',
     },
@@ -246,6 +252,12 @@ export const formOptions: VbenFormProps = {
         optionType: 'button',
         buttonStyle: 'solid',
       },
+      dependencies: {
+        triggerFields: ['type'],
+        show(values) {
+          return [0, 1].includes(values.type);
+        },
+      },
       fieldName: 'hideInTab',
       label: '隐藏',
     },
@@ -258,6 +270,12 @@ export const formOptions: VbenFormProps = {
           params: EnumApi.EnumType.PathType,
         },
       },
+      dependencies: {
+        triggerFields: ['type'],
+        show(values) {
+          return [0, 1].includes(values.type);
+        },
+      },
       fieldName: 'pathType',
       label: '路由类型',
       rules: 'required',

+ 78 - 0
apps/web-baicai/src/views/system/post/components/edit.vue

@@ -0,0 +1,78 @@
+<script lang="ts" setup>
+import { onMounted, ref, unref } from 'vue';
+
+import { useVbenModal } from '@vben/common-ui';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter';
+import { PostApi } from '#/api';
+
+import { formOptions } from '../data.config';
+
+defineOptions({
+  name: 'RoleEdit',
+});
+const emit = defineEmits(['success']);
+const modelRef = ref<Record<string, any>>({});
+const isUpdate = ref(true);
+
+const [Form, { validate, setValues, getValues }] = useVbenForm({
+  showDefaultActions: false,
+  ...formOptions,
+});
+
+const [Modal, { close, setState, getData }] = useVbenModal({
+  fullscreenButton: false,
+  draggable: true,
+  onCancel() {
+    close();
+  },
+  onConfirm: async () => {
+    try {
+      const { valid } = await validate();
+      if (valid) {
+        const values = await getValues();
+        setState({ confirmLoading: true });
+        const postParams = unref(modelRef);
+        Object.assign(postParams, values);
+        await (unref(isUpdate)
+          ? PostApi.editDetail(postParams as PostApi.RecordItem)
+          : PostApi.addDetail(postParams as PostApi.BasicRecordItem));
+        message.success('操作成功');
+
+        close();
+        emit('success');
+      }
+    } catch {
+      message.error('操作失败');
+    } finally {
+      setState({ confirmLoading: false });
+    }
+  },
+  onOpenChange: async (isOpen: boolean) => {
+    if (isOpen) {
+      setState({ loading: true });
+      const data = getData<Record<string, any>>();
+      isUpdate.value = !!data.isUpdate;
+      modelRef.value = { ...data.baseData };
+      setState({ title: unref(isUpdate) ? '编辑岗位' : '新增岗位' });
+
+      if (unref(isUpdate)) {
+        const entity = await PostApi.getDetail(data.baseData.id);
+        modelRef.value = { ...entity };
+        setValues(entity);
+      }
+      setState({ loading: false });
+    }
+  },
+  title: '新增岗位',
+});
+
+onMounted(async () => {});
+</script>
+<template>
+  <Modal class="w-[1000px]">
+    <Form />
+  </Modal>
+</template>

+ 112 - 0
apps/web-baicai/src/views/system/post/data.config.ts

@@ -0,0 +1,112 @@
+import type { VbenFormProps, VxeGridProps } from '#/adapter';
+
+import { PostApi } from '#/api';
+import { formatterStatus } from '#/api/model';
+
+export const searchFormOptions: VbenFormProps = {
+  schema: [
+    {
+      component: 'Input',
+      fieldName: 'code',
+      label: '编号',
+    },
+    {
+      component: 'Input',
+      fieldName: 'name',
+      label: '名称',
+    },
+  ],
+};
+
+export const gridOptions: VxeGridProps<PostApi.RecordItem> = {
+  toolbarConfig: {
+    refresh: true,
+    print: false,
+    export: false,
+    zoom: true,
+    custom: true,
+  },
+  columns: [
+    { title: '序号', type: 'seq', width: 50 },
+    {
+      align: 'left',
+      field: 'code',
+      title: '编号',
+      width: 150,
+    },
+    { align: 'left', field: 'name', title: '名称', width: 200 },
+    { align: 'left', field: 'remark', title: '备注' },
+    {
+      field: 'status',
+      title: '状态',
+      width: 60,
+      formatter: formatterStatus,
+    },
+    {
+      field: 'action',
+      fixed: 'right',
+      slots: { default: 'action' },
+      title: '操作',
+      width: 150,
+    },
+  ],
+  height: 'auto',
+  keepSource: true,
+  proxyConfig: {
+    ajax: {
+      query: async ({ page }, formValues) => {
+        return await PostApi.getPage({
+          pageIndex: page.currentPage,
+          pageSize: page.pageSize,
+          ...formValues,
+        });
+      },
+    },
+  },
+};
+
+export const formOptions: VbenFormProps = {
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+  },
+  schema: [
+    {
+      component: 'Input',
+      componentProps: {
+        placeholder: '请输入编号',
+      },
+      fieldName: 'code',
+      label: '编号',
+      rules: 'required',
+    },
+    {
+      component: 'Input',
+      componentProps: {
+        placeholder: '请输入名称',
+      },
+      fieldName: 'name',
+      label: '名称',
+      rules: 'required',
+    },
+    {
+      component: 'InputNumber',
+      componentProps: {
+        placeholder: '请输入排序',
+      },
+      fieldName: 'sort',
+      label: '排序',
+      rules: 'required',
+    },
+    {
+      component: 'Textarea',
+      componentProps: {
+        placeholder: '请输入',
+      },
+      fieldName: 'remark',
+      label: '备注',
+    },
+  ],
+  wrapperClass: 'grid-cols-1',
+};

+ 103 - 0
apps/web-baicai/src/views/system/post/index.vue

@@ -0,0 +1,103 @@
+<script lang="ts" setup>
+import { useAccess } from '@vben/access';
+import { Page, useVbenModal } from '@vben/common-ui';
+
+import { Button, message, Modal } from 'ant-design-vue';
+
+import { useVbenVxeGrid } from '#/adapter';
+import { PostApi } from '#/api';
+import { TableAction } from '#/components/table-action';
+
+import FormEdit from './components/edit.vue';
+import { gridOptions, searchFormOptions } from './data.config';
+
+const { hasAccessByCodes } = useAccess();
+
+const [Grid, { reload }] = useVbenVxeGrid({
+  formOptions: searchFormOptions,
+  gridOptions,
+});
+
+const [FormEditModal, formEditApi] = useVbenModal({
+  connectedComponent: FormEdit,
+});
+const handleDelete = (id: number) => {
+  Modal.confirm({
+    iconType: 'info',
+    title: '删除提示',
+    content: `确定要删除选择的记录吗?`,
+    cancelText: `关闭`,
+    onOk: async () => {
+      await PostApi.deleteDetail(id);
+      message.success('数据删除成功');
+      reload();
+    },
+  });
+};
+
+const handleUpdateStatus = async (record: any) => {
+  await PostApi.updateStatus({
+    id: record.id,
+    status: record.status === 1 ? 2 : 1,
+  });
+  reload();
+};
+
+const handleEdit = (record: any, isUpdate: boolean) => {
+  formEditApi.setData({
+    isUpdate,
+    baseData: { id: record.id },
+  });
+
+  formEditApi.open();
+};
+
+const handelSuccess = () => {
+  reload();
+};
+</script>
+
+<template>
+  <Page auto-content-height>
+    <FormEditModal :close-on-click-modal="false" @success="handelSuccess" />
+    <Grid>
+      <template #toolbar-tools>
+        <Button
+          class="mr-2"
+          type="primary"
+          v-access:code="'post:add'"
+          @click="() => handleEdit({}, false)"
+        >
+          新增岗位
+        </Button>
+      </template>
+      <template #action="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: '编辑',
+              type: 'text',
+              disabled: !hasAccessByCodes(['post:edit']),
+              onClick: handleEdit.bind(null, row, true),
+            },
+            {
+              label: '删除',
+              type: 'text',
+              danger: true,
+              disabled: !hasAccessByCodes(['post:delete']),
+              onClick: handleDelete.bind(null, row.id),
+            },
+          ]"
+          :drop-down-actions="[
+            {
+              label: row.status === 1 ? '禁用' : '启用',
+              type: 'link',
+              disabled: !hasAccessByCodes(['role:setStatus']),
+              onClick: handleUpdateStatus.bind(null, row),
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </Page>
+</template>