Bladeren bron

统一字典查询,操作日志

zouzs 2 weken geleden
bovenliggende
commit
c48b6a703b
5 gewijzigde bestanden met toevoegingen van 542 en 0 verwijderingen
  1. 11 0
      src/api/system/dict.ts
  2. 46 0
      src/api/system/operlog.ts
  3. 52 0
      src/store/modules/dict.ts
  4. 44 0
      src/utils/dict.ts
  5. 389 0
      src/views/system/operlog/index.vue

+ 11 - 0
src/api/system/dict.ts

@@ -135,3 +135,14 @@ export const getSystemDictDataById = (id: string) => {
     baseUrlApi(`system/dict/data/${id}`)
   );
 };
+
+/**
+ * 获取字典数据根据字典类型
+ * @param dictType
+ */
+export const getDictDataByType = (dictType: string) => {
+  return http.request<BasicResponseModel>(
+    "get",
+    baseUrlApi(`system/dict/data/type/${dictType}`)
+  );
+};

+ 46 - 0
src/api/system/operlog.ts

@@ -0,0 +1,46 @@
+import { http } from "@/utils/http";
+import { baseUrlApi } from "../utils";
+
+export interface operlogList<T = any> {
+  code: number;
+  msg: string;
+  rows: T;
+  total?: number;
+}
+
+export interface BasicResponseModel<T = any> {
+  code: number;
+  msg: string;
+  data: T;
+}
+
+/**
+ * 获取操作日志列表
+ * @param query
+ */
+export const getSystemOperlogList = (query?: object) => {
+  return http.request<operlogList>("get", baseUrlApi("system/operlog/list"), {
+    params: query
+  });
+};
+
+/**
+ * 删除操作日志
+ * @param ids
+ */
+export const deleteSystemOperlog = (ids: string) => {
+  return http.request<BasicResponseModel>(
+    "delete",
+    baseUrlApi(`system/operlog/${ids}`)
+  );
+};
+
+/**
+ * 清空操作日志
+ */
+export const cleanSystemOperlog = () => {
+  return http.request<BasicResponseModel>(
+    "delete",
+    baseUrlApi("system/operlog/clean")
+  );
+};

+ 52 - 0
src/store/modules/dict.ts

@@ -0,0 +1,52 @@
+import { defineStore } from "pinia";
+
+export const useDictStore = defineStore("dict", {
+  state: () => ({
+    dict: []
+  }),
+
+  actions: {
+    // 获取指定字典类型
+    getDict(key: string) {
+      try {
+        const item = this.dict.find(item => item.key === key);
+        return item ? item.value : null;
+      } catch (e) {
+        console.error("获取字典失败:", e);
+        return null;
+      }
+    },
+
+    // 设置字典数据
+    setDict(key: string, value: any) {
+      if (!key || typeof key !== "string") return;
+      // 避免重复添加
+      const index = this.dict.findIndex(item => item.key === key);
+      if (index !== -1) {
+        this.dict[index].value = value;
+      } else {
+        this.dict.push({ key, value });
+      }
+    },
+
+    // 移除字典数据
+    removeDict(key: string) {
+      try {
+        const index = this.dict.findIndex(item => item.key === key);
+        if (index !== -1) {
+          this.dict.splice(index, 1);
+          return true;
+        }
+        return false;
+      } catch (e) {
+        console.error("移除字典失败:", e);
+        return false;
+      }
+    },
+
+    // 清空所有字典数据
+    clearAll() {
+      this.dict = [];
+    }
+  }
+});

+ 44 - 0
src/utils/dict.ts

@@ -0,0 +1,44 @@
+import { ref, toRefs } from "vue";
+import { useDictStore } from "@/store/modules/dict";
+import { getDictDataByType } from "@/api/system/dict"; // 根据key获取字典接口
+import { isString } from "@pureadmin/utils";
+
+export function useDict(...args: string[]) {
+  const res = ref({});
+  const dictStore = useDictStore();
+
+  args.forEach(dictType => {
+    res.value[dictType] = [];
+    const dicts = dictStore.getDict(dictType);
+
+    if (dicts) {
+      res.value[dictType] = dicts;
+    } else {
+      getDictDataByType(dictType)
+        .then(resp => {
+          const dictData = resp.data.map(p => ({
+            label: p.dictLabel,
+            value: p.dictValue,
+            class: p.listClass
+          }));
+          res.value[dictType] = dictData;
+          dictStore.setDict(dictType, dictData);
+        })
+        .catch(error => {
+          console.error(`获取字典${dictType}失败:`, error);
+        });
+    }
+  });
+
+  return toRefs<any>(res.value);
+}
+
+export function useDictValue<T>(dictType: T, searchValue: string) {
+  if (!isString(searchValue)) {
+    searchValue = String(searchValue);
+  }
+  const dictStore = useDictStore();
+  const dicts = dictStore.getDict(dictType as string) || [];
+  const item = dicts.find((d: any) => d.value === searchValue);
+  return item ? item.label : null;
+}

+ 389 - 0
src/views/system/operlog/index.vue

@@ -0,0 +1,389 @@
+<script setup lang="ts">
+import {
+  type PlusColumn,
+  type OptionsRow,
+  PlusPage,
+  PlusDialog,
+  PlusDescriptions,
+  PlusPageInstance,
+  useTable
+} from "plus-pro-components";
+import { computed, reactive, ref, toRefs } from "vue";
+import {
+  cleanSystemOperlog,
+  deleteSystemOperlog,
+  getSystemOperlogList
+} from "@/api/system/operlog";
+import { useDict, useDictValue } from "@/utils/dict";
+import { ElMessage, ElMessageBox } from "element-plus";
+
+defineOptions({
+  name: "Operlog"
+});
+
+const { sys_common_status, sys_oper_type } = useDict(
+  "sys_common_status",
+  "sys_oper_type"
+);
+
+const plusPageInstance = ref<PlusPageInstance | null>(null);
+
+const getList = async (query: Record<string, any>) => {
+  let res = await getSystemOperlogList(query);
+  return {
+    data: res.rows,
+    total: res.total
+  };
+};
+
+// 重新请求列表接口
+const refresh = () => {
+  plusPageInstance.value?.getList();
+};
+
+const tableConfig: PlusColumn[] = [
+  {
+    label: "日志编号",
+    prop: "operId",
+    valueType: "input",
+    hideInSearch: true
+  },
+  {
+    label: "系统模块",
+    prop: "title",
+    valueType: "input"
+  },
+  {
+    label: "操作类型",
+    prop: "businessType",
+    valueType: "tag",
+    options: computed(() => sys_oper_type.value),
+    fieldProps: value => {
+      return {
+        type: sys_oper_type.value.find(item => item.value === value + "")?.class
+      };
+    },
+    formatter: (value, { column }) => {
+      return (column.options as OptionsRow[])?.find(
+        item => item.value === value + ""
+      )?.label;
+    },
+    hideInSearch: true
+  },
+  {
+    label: "操作类型",
+    prop: "bussinessType",
+    valueType: "select",
+    options: computed(() => sys_oper_type.value),
+    hideInTable: true
+  },
+  {
+    label: "请求方式",
+    prop: "requestMethod",
+    valueType: "input"
+  },
+  {
+    label: "操作人员",
+    prop: "operName",
+    valueType: "input"
+  },
+  {
+    label: "操作地址",
+    prop: "operIp",
+    valueType: "input",
+    hideInSearch: true
+  },
+  {
+    label: "操作状态",
+    prop: "status",
+    valueType: "tag",
+    options: computed(() => sys_common_status.value),
+    fieldProps: value => {
+      return {
+        type: sys_common_status.value.find(item => item.value === value + "")
+          ?.class
+      };
+    },
+    formatter: (value, { column }) => {
+      return (column.options as OptionsRow[])?.find(
+        item => item.value === value + ""
+      )?.label;
+    },
+    hideInSearch: true
+  },
+  {
+    label: "操作状态",
+    prop: "status",
+    valueType: "select",
+    options: computed(() => sys_common_status.value),
+    hideInTable: true
+  },
+  {
+    label: "操作时间",
+    prop: "operTime",
+    valueType: "date-picker",
+    fieldProps: {
+      type: "datetime",
+      rangeSeparator: "至",
+      startPlaceholder: "开始时间",
+      endPlaceholder: "结束时间",
+      valueFormat: "YYYY-MM-DD HH:mm:ss"
+    }
+  },
+  {
+    label: "消耗时间",
+    prop: "costTime",
+    valueType: "input",
+    hideInSearch: true,
+    formatter: value => value + "毫秒"
+  }
+];
+
+const { buttons } = useTable();
+
+buttons.value = [
+  {
+    // 修改
+    text: "详细",
+    code: "edit",
+    // props v0.1.16 版本新增函数类型
+    props: {
+      type: "primary"
+    },
+    onClick(params) {
+      form.value = params.row;
+      state.isCreate = false;
+      state.dialogVisible = true;
+    }
+  }
+];
+
+const multipleSelection = ref([]);
+
+const handleSelectionChange = (val: string[]) => {
+  multipleSelection.value = val;
+};
+
+const handleDelete = () => {
+  // 删除前确认
+  ElMessageBox.confirm("是否确认删除选中数据?", "删除确认", {
+    confirmButtonText: "确认",
+    cancelButtonText: "取消",
+    type: "warning",
+    draggable: true
+  })
+    .then(async () => {
+      try {
+        let operIds = multipleSelection.value.map(
+          item => item.operId
+        ) as string[];
+        let res = await deleteSystemOperlog(operIds.join(","));
+        if (res.code === 200) {
+          ElMessage.success("删除成功");
+          refresh();
+          multipleSelection.value = [];
+        } else {
+          ElMessage.error(res.msg);
+        }
+      } catch (e) {
+        ElMessage.error("删除失败");
+      }
+    })
+    .catch(() => {
+      // 取消删除
+    });
+};
+
+const handleClear = () => {
+  // 清空前确认
+  ElMessageBox.confirm("是否确认清空所有操作日志?", "清空确认", {
+    confirmButtonText: "确认",
+    cancelButtonText: "取消",
+    type: "warning",
+    draggable: true
+  })
+    .then(async () => {
+      try {
+        let res = await cleanSystemOperlog();
+        if (res.code === 200) {
+          ElMessage.success("清空成功");
+          refresh();
+          multipleSelection.value = [];
+        } else {
+          ElMessage.error(res.msg);
+        }
+      } catch (e) {
+        ElMessage.error("清空失败");
+      }
+    })
+    .catch(() => {
+      // 取消清空
+    });
+};
+
+interface State {
+  dialogVisible: boolean;
+  detailsVisible: boolean;
+  confirmLoading: boolean;
+  selectedIds: number[];
+  isCreate: boolean;
+  form: Record<string, any>;
+  rules: Record<string, any>;
+}
+
+const state = reactive<State>({
+  dialogVisible: false,
+  detailsVisible: false,
+  confirmLoading: false,
+  selectedIds: [],
+  isCreate: true,
+  form: {},
+  rules: {
+    sysName: [{ required: true, message: "请输入系统名称", trigger: "blur" }],
+    sysCode: [{ required: true, message: "请输入系统编码", trigger: "blur" }]
+  }
+});
+
+const columns: PlusColumn[] = [
+  {
+    label: "操作模块",
+    prop: "sysName",
+    formatter: () => {
+      return `${form.value.title}/${useDictValue("sys_oper_type", form.value.businessType)}`;
+    },
+    descriptionsItemProps: {
+      span: 12
+    }
+  },
+  {
+    label: "请求地址",
+    prop: "operUrl",
+    descriptionsItemProps: {
+      span: 12
+    }
+  },
+  {
+    label: "登录信息",
+    prop: "operIp",
+    formatter: () => {
+      return `${form.value.operName}/${form.value.operIp}`;
+    },
+    descriptionsItemProps: {
+      span: 12
+    }
+  },
+  {
+    label: "请求方式",
+    prop: "requestMethod",
+    descriptionsItemProps: {
+      span: 12
+    }
+  },
+  {
+    label: "操作方法",
+    prop: "method",
+    descriptionsItemProps: {
+      span: 24
+    }
+  },
+  {
+    label: "操作参数",
+    prop: "operParam",
+    descriptionsItemProps: {
+      span: 24,
+      className: "break-all"
+    }
+  },
+  {
+    label: "返回参数",
+    prop: "jsonResult",
+    descriptionsItemProps: {
+      span: 24
+    }
+  },
+  {
+    label: "操作状态",
+    prop: "status",
+    formatter: () => {
+      return useDictValue("sys_common_status", form.value.status);
+    },
+    descriptionsItemProps: {
+      span: 8
+    }
+  },
+  {
+    label: "消耗时间",
+    prop: "costTime",
+    formatter: () => {
+      return `${form.value.costTime}毫秒`;
+    },
+    descriptionsItemProps: {
+      span: 8
+    }
+  },
+  {
+    label: "操作时间",
+    prop: "operTime",
+    descriptionsItemProps: {
+      span: 8
+    }
+  }
+];
+
+const { form, dialogVisible } = toRefs(state);
+</script>
+
+<template>
+  <div>
+    <PlusPage
+      ref="plusPageInstance"
+      :columns="tableConfig"
+      :request="getList"
+      :pageInfoMap="{ page: 'pageNum', pageSize: 'pageSize' }"
+      :table="{
+        actionBar: { buttons, type: 'link', width: 140 },
+        adaptive: { offsetBottom: 50 },
+        isSelection: true,
+        onSelectionChange: handleSelectionChange,
+        rowKey: 'operId'
+      }"
+      :search="{
+        showNumber: 3
+      }"
+    >
+      <template #table-title>
+        <el-row class="button-row">
+          <el-button
+            size="default"
+            :disabled="multipleSelection.length === 0"
+            type="danger"
+            @click="handleDelete"
+          >
+            删除
+          </el-button>
+          <el-button size="default" type="danger" @click="handleClear">
+            清空日志
+          </el-button>
+        </el-row>
+      </template>
+    </PlusPage>
+    <!-- 弹窗编辑 -->
+    <PlusDialog
+      v-model="dialogVisible"
+      :title="'操作日志详细'"
+      width="800"
+      :has-footer="false"
+    >
+      <PlusDescriptions
+        size="large"
+        :column="24"
+        :columns="columns"
+        :data="form"
+        :border="true"
+        label-width="90px"
+      />
+    </PlusDialog>
+  </div>
+</template>
+
+<style scoped lang="scss"></style>