lrf 11 bulan lalu
induk
melakukan
913182e032

+ 1 - 0
src/lang/package/zh-cn/common.js

@@ -5,6 +5,7 @@ export default {
   create: '添加',
   update: '修改',
   delete: '删除',
+  detail: '详情',
   exam: '审核',
   dict: '字典数据',
   sign: '报名',

+ 3 - 1
src/lang/package/zh-cn/menus.js

@@ -26,5 +26,7 @@ export default {
   match_info: '赛事信息管理',
   match_sign: '报名管理',
   achievement: '成果管理',
-  project: '项目管理'
+  project: '项目管理',
+  log: '日志管理',
+  log_opera: '操作日志'
 }

+ 14 - 0
src/lang/package/zh-cn/pages.js

@@ -221,5 +221,19 @@ export default {
     partent: '上级部门',
     status: '使用状态',
     resource: '资源管理'
+  },
+  log_opera: {
+    operator_id: '操作人数据id',
+    operator_name: '操作人名称',
+    opera: '操作业务',
+    ip: 'ip',
+    time: '时间',
+    referer: '操作地址',
+    path: '接口路径',
+    dialogTitle: '日志详情',
+    device: '设备信息',
+    params: '地址参数',
+    query: '请求参数',
+    body: '方法体参数'
   }
 }

+ 40 - 0
src/store/api/log/opera.js

@@ -0,0 +1,40 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/log/opera'
+const axios = new AxiosWrapper()
+
+export const OperaLogsStore = defineStore('OperaLogs', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, 'id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    fetch,
+    create,
+    update,
+    del
+  }
+})

+ 139 - 0
src/views/log/opera/index.vue

@@ -0,0 +1,139 @@
+<template>
+  <div class="main animate__animated animate__backInRight" v-loading="loading">
+    <custom-search-bar :fields="fields.filter((f) => f.isSearch)" v-model="searchForm" @search="search"
+      @reset="toReset"> </custom-search-bar>
+    <custom-table :data="list" :fields="fields" @query="search" :total="total" :opera="opera" @detail="toDetail">
+    </custom-table>
+
+    <el-dialog v-model="dialogShow" :title="dialogTitle" :destroy-on-close="false" @close="toClose" :top="dialogTop">
+      <el-form :data="form" label-width="150px" label-position="left">
+        <el-tabs v-model="tabs" type="card">
+          <el-tab-pane label="记录内容" name="1">
+            <el-form-item :label="$t('pages.log_opera.operator_id')">
+              {{ getProp('operator_id') }}
+            </el-form-item>
+            <el-form-item :label="$t('pages.log_opera.operator_name')">
+              {{ getProp('operator_name') }}
+            </el-form-item>
+            <el-form-item :label="$t('pages.log_opera.device')">
+              {{ getProp('device') }}
+            </el-form-item>
+            <el-form-item :label="$t('pages.log_opera.time')">
+              {{ getProp('time') }}
+            </el-form-item>
+            <el-form-item :label="$t('pages.log_opera.referer')">
+              {{ getProp('referer') }}
+            </el-form-item>
+            <el-form-item :label="$t('pages.log_opera.path')">
+              {{ getProp('path') }}
+            </el-form-item>
+            <el-form-item :label="$t('pages.log_opera.opera')">
+              {{ getProp('opera') }}
+            </el-form-item>
+            <el-form-item :label="$t('pages.log_opera.ip')">
+              {{ getProp('ip') }}
+            </el-form-item>
+
+          </el-tab-pane>
+          <el-tab-pane label="请求参数" name='2'>
+            <el-form-item :label="$t('pages.log_opera.params')">
+              <params-table :data="form.params"></params-table>
+            </el-form-item>
+            <el-form-item :label="$t('pages.log_opera.query')">
+              <params-table :data="form.query"></params-table>
+            </el-form-item>
+            <el-form-item :label="$t('pages.log_opera.body')">
+              <params-table :data="form.body"></params-table>
+            </el-form-item>
+          </el-tab-pane>
+          <el-tab-pane label="数据对比" name='3'>
+            <el-row :gutter="20">
+              <el-col :span="12">
+                <el-row>
+                  <el-col :span="24">原数据</el-col>
+                  <el-col :span="24">
+                    <data-view :data="form.origin_data"></data-view>
+                  </el-col>
+                </el-row>
+              </el-col>
+              <el-col :span="12">
+                <el-row>
+                  <el-col :span="24">新数据</el-col>
+                  <el-col :span="24">
+                    <data-view :data="form.new_data"></data-view>
+                  </el-col>
+                </el-row>
+              </el-col>
+            </el-row>
+          </el-tab-pane>
+        </el-tabs>
+
+      </el-form>
+    </el-dialog>
+  </div>
+</template>
+<script setup>
+import dataView from './parts/dataView.vue'
+import paramsTable from './parts/paramsTable.vue'
+import { OperaLogsStore } from '@/store/api/log/opera'
+import { get, isObject } from 'lodash-es';
+import { onMounted } from 'vue'
+const { t } = useI18n()
+const store = OperaLogsStore()
+const dialogShow = ref(false)
+const dialogTitle = t('pages.log_opera.dialogTitle');
+const dialogTop = '15vh'
+const loading = ref(false)
+const $checkRes = inject('$checkRes')
+const searchForm = ref({})
+const list = ref([])
+let skip = 0
+let limit = inject('limit')
+const tabs = ref('1')
+onMounted(async () => {
+  loading.value = true
+  // await searchOther()
+  await search({ skip, limit })
+  loading.value = false
+})
+const search = async (query = { skip: 0, limit }) => {
+  const info = { skip: query.skip, limit: query.limit, ...searchForm.value }
+  const res = await store.query(info)
+  if ($checkRes(res)) {
+    list.value = res.data
+    total.value = res.total
+  }
+}
+
+const total = ref(0)
+const form = ref({})
+const fields = [
+  { label: t('pages.log_opera.operator_name'), model: 'operator_name' }, // , isSearch: true
+  { label: t('pages.log_opera.opera'), model: 'opera' },
+  { label: t('pages.log_opera.ip'), model: 'ip' },
+  { label: t('pages.log_opera.time'), model: 'time' },
+  { label: t('pages.log_opera.referer'), model: 'referer' },
+  { label: t('pages.log_opera.path'), model: 'path' }
+]
+const opera = [{ label: t('common.detail'), method: 'detail' }]
+
+const toDetail = (data) => {
+  form.value = data;
+  dialogShow.value = true;
+}
+const getProp = (prop) => {
+  return get(form.value, prop)
+}
+
+
+const toReset = async () => {
+  searchForm.value = {}
+  await search({ skip, limit })
+}
+const toClose = () => {
+  form.value = {}
+  dialogShow.value = false
+}
+
+</script>
+<style scoped lang="scss"></style>

+ 48 - 0
src/views/log/opera/parts/dataView.vue

@@ -0,0 +1,48 @@
+<template>
+  <el-collapse v-model="acts" @change="handleChange">
+    <el-collapse-item v-for="i in list" :title="i.title" :name="i.title">
+      <table-view :data="i.list"></table-view>
+    </el-collapse-item>
+  </el-collapse>
+</template>
+
+<script setup>
+import { isArray } from 'lodash-es';
+import tableView from './tableView.vue'
+const props = defineProps({
+  data: Object,
+})
+const { data } = toRefs(props)
+const list = ref([])
+const acts = ref([])
+const setData = () => {
+  const arr = [];
+  for (const key in data.value) {
+    let list = data.value[key]
+    if (isArray(list) && list.length > 0) {
+      const nl = [];
+      let index = 1;
+      for (const i of list) {
+        const ldobj = {}
+        const ldarr = [];
+        for (const key in i) {
+          if(key==='id')ldobj.key = i.id
+          ldarr.push({ key, value: i[key] })
+        }
+        if(!ldobj.key) {
+          ldobj.key = `new data ${index}`;
+          index++;
+        }
+        ldobj.list = ldarr;
+        nl.push(ldobj)
+      }
+      list = nl
+    }
+    const obj = { title: key, list }
+    arr.push(obj)
+  }
+  list.value = arr;
+}
+setData();
+</script>
+<style scoped></style>

+ 24 - 0
src/views/log/opera/parts/paramsTable.vue

@@ -0,0 +1,24 @@
+<template>
+  <el-table :data="list">
+    <el-table-column align="center" label="键名" prop="key"></el-table-column>
+    <el-table-column align="center" label="值" prop="value"></el-table-column>
+  </el-table>
+</template>
+
+<script setup>
+const props = defineProps({
+  data: Object,
+})
+const { data } = toRefs(props)
+const list = ref([])
+const setData = () => {
+  const arr = []
+  for (const key in data.value) {
+    const obj = { key, value: data.value[key] }
+    arr.push(obj)
+  }
+  list.value = arr;
+}
+setData()
+</script>
+<style scoped></style>

+ 21 - 0
src/views/log/opera/parts/tableView.vue

@@ -0,0 +1,21 @@
+<template>
+  <el-tabs v-model="act" type="card">
+    <el-tab-pane v-for="i in data" :label="i.key" :name="i.key">
+      <el-table :data="i.list">
+        <el-table-column align="center" label="键名" prop="key"></el-table-column>
+        <el-table-column align="center" label="值" prop="value"></el-table-column>
+      </el-table>
+    </el-tab-pane>
+  </el-tabs>
+
+</template>
+
+<script setup>
+const act = ref()
+const props = defineProps({
+  data: Array,
+})
+const { data } = toRefs(props)
+
+</script>
+<style scoped></style>

+ 0 - 0
src/views/log/view/index.vue


+ 1 - 1
src/views/system/menus/index.vue

@@ -30,7 +30,7 @@ const list = ref([])
 const form = ref({})
 const typeList = [
   { label: '目录', value: '0' },
-  { label: '目录', value: '1' },
+  { label: '页面', value: '1' },
   { label: '子页面', value: '2' }
 ]
 const iconList = ref([])