瀏覽代碼

操作日志

skyline 2 天之前
父節點
當前提交
8dab02bf92

+ 272 - 209
admin-web/src/views/admin/log/opt/index.vue

@@ -1,176 +1,277 @@
+<style scoped lang="scss">
+.system-container {
+
+  :deep(.el-card__body) {
+    display: flex;
+    flex-direction: column;
+    justify-content: space-between;
+    flex: 1;
+    overflow: auto;
+
+    .el-table {
+      flex: 1;
+    }
+
+  }
+}
+
+.page-content {
+  margin-bottom: 20px;
+}
+
+.page-pager {
+  background-color: var(--el-color-white);
+  height: 24px;
+}
+
+.detail-section {
+    margin-top: 15px;
+
+    .section-title {
+        font-weight: 500;
+        margin-bottom: 10px;
+        color: var(--el-text-color-primary);
+    }
+
+    :deep(.el-textarea__inner) {
+        font-family: monospace;
+        font-size: 13px;
+        background: var(--el-fill-color-lighter);
+    }
+}
+</style>
 <template>
-    <div class="system-log-container">
-        <!-- 搜索栏 -->
-        <el-card class="search-card" shadow="never">
-            <el-form :inline="true" :model="queryParams" class="search-form">
-                <el-form-item label="操作用户">
-                    <el-input v-model="queryParams.operatorName" placeholder="请输入" clearable style="width: 150px" />
-                </el-form-item>
-                <el-form-item label="操作模块">
-                    <el-input v-model="queryParams.module" placeholder="请输入" clearable style="width: 150px" />
-                </el-form-item>
-                <el-form-item label="操作类型">
-                    <ext-d-select v-model="queryParams.operationType" type="OptLog.operationType" placeholder="请选择" clearable style="width: 150px" />
-                </el-form-item>
-                <el-form-item label="操作时间">
-                    <el-date-picker
-                        v-model="dateRange"
-                        type="daterange"
-                        range-separator="至"
-                        start-placeholder="开始日期"
-                        end-placeholder="结束日期"
-                        value-format="YYYY-MM-DD"
-                        style="width: 240px"
-                    />
-                </el-form-item>
-                <el-form-item>
-                    <el-button type="primary" @click="handleQuery">
-                        <el-icon><ele-Search /></el-icon> 查询
-                    </el-button>
-                    <el-button @click="resetQuery">
-                        <el-icon><ele-Refresh /></el-icon> 重置
-                    </el-button>
-                </el-form-item>
-            </el-form>
-        </el-card>
-
-        <!-- 数据表格 -->
-        <el-card class="table-card" shadow="never">
-            <template #header>
-                <div class="card-header">
-                    <span>操作日志列表</span>
-                    <el-button type="danger" @click="handleClearLog" :disabled="tableData.length === 0">
-                        <el-icon><ele-Delete /></el-icon> 清空日志
-                    </el-button>
-                </div>
-            </template>
-
-            <el-table v-loading="loading" :data="tableData" border stripe>
-                <el-table-column prop="id" label="ID" width="80" align="center" />
-                <el-table-column prop="operatorName" label="操作用户" width="120" align="center" />
-                <el-table-column prop="module" label="操作模块" width="120" align="center" />
-                <el-table-column prop="operationType" label="操作类型" width="100" align="center">
-                    <template #default="{ row }">
-                        <el-tag :type="getOperationTypeColor(row.operationType)">
-                            {{ getOperationTypeLabel(row.operationType) }}
-                        </el-tag>
-                    </template>
-                </el-table-column>
-                <el-table-column prop="description" label="操作描述" min-width="200" show-overflow-tooltip />
-                <el-table-column prop="requestMethod" label="请求方式" width="90" align="center">
-                    <template #default="{ row }">
-                        <el-tag :type="getMethodColor(row.requestMethod)" size="small">{{ row.requestMethod }}</el-tag>
-                    </template>
-                </el-table-column>
-                <el-table-column prop="requestUrl" label="请求URL" width="200" show-overflow-tooltip />
-                <el-table-column prop="ip" label="IP地址" width="130" align="center" />
-                <el-table-column prop="costTime" label="耗时(ms)" width="90" align="center">
-                    <template #default="{ row }">
-                        <el-tag :type="row.costTime > 1000 ? 'danger' : row.costTime > 500 ? 'warning' : 'success'" size="small">
-                            {{ row.costTime }}
-                        </el-tag>
-                    </template>
-                </el-table-column>
-                <el-table-column prop="createTime" label="操作时间" width="170" align="center" />
-                <el-table-column label="操作" width="80" align="center" fixed="right">
-                    <template #default="{ row }">
-                        <el-button type="primary" link size="small" @click="handleDetail(row)">详情</el-button>
-                    </template>
-                </el-table-column>
-            </el-table>
-
-            <!-- 分页 -->
-            <div class="pagination-container">
-                <el-pagination
-                    v-model:current-page="queryParams.pageNum"
-                    v-model:page-size="queryParams.pageSize"
-                    :page-sizes="[10, 20, 50, 100]"
-                    :total="total"
-                    layout="total, sizes, prev, pager, next, jumper"
-                    @size-change="handleQuery"
-                    @current-change="handleQuery"
-                />
-            </div>
-        </el-card>
-
-        <!-- 详情对话框 -->
-        <el-dialog v-model="detailVisible" title="日志详情" width="700px">
-            <el-descriptions :column="2" border v-if="currentLog">
-                <el-descriptions-item label="操作用户">{{ currentLog.operatorName }}</el-descriptions-item>
-                <el-descriptions-item label="操作模块">{{ currentLog.module }}</el-descriptions-item>
-                <el-descriptions-item label="操作类型">
-                    <el-tag :type="getOperationTypeColor(currentLog.operationType)">
-                        {{ getOperationTypeLabel(currentLog.operationType) }}
-                    </el-tag>
-                </el-descriptions-item>
-                <el-descriptions-item label="请求方式">
-                    <el-tag :type="getMethodColor(currentLog.requestMethod)" size="small">{{ currentLog.requestMethod }}</el-tag>
-                </el-descriptions-item>
-                <el-descriptions-item label="请求URL" :span="2">{{ currentLog.requestUrl }}</el-descriptions-item>
-                <el-descriptions-item label="IP地址">{{ currentLog.ip }}</el-descriptions-item>
-                <el-descriptions-item label="耗时">{{ currentLog.costTime }}ms</el-descriptions-item>
-                <el-descriptions-item label="操作时间" :span="2">{{ currentLog.createTime }}</el-descriptions-item>
-                <el-descriptions-item label="操作描述" :span="2">{{ currentLog.description }}</el-descriptions-item>
-            </el-descriptions>
-            <div class="detail-section" v-if="currentLog">
-                <div class="section-title">请求参数</div>
-                <el-input type="textarea" :rows="4" :model-value="formatJson(currentLog.requestParams)" readonly />
-            </div>
-            <div class="detail-section" v-if="currentLog">
-                <div class="section-title">响应结果</div>
-                <el-input type="textarea" :rows="4" :model-value="formatJson(currentLog.responseData)" readonly />
-            </div>
-            <template #footer>
-                <el-button @click="detailVisible = false">关闭</el-button>
-            </template>
-        </el-dialog>
-    </div>
+  <div class="system-container layout-padding">
+    <el-card shadow="hover" class="layout-padding-auto">
+
+      <el-form
+          :model="queryParams"
+          ref="queryRef"
+          size="default" label-width="0px" class="mt5 mb5">
+        <el-input
+            v-model="queryParams.username"
+            placeholder="操作用户"
+            clearable
+            @blur="handleQuery"
+            class="wd150 mr10">
+        </el-input>
+        <el-date-picker
+            v-model="dateRange"
+            type="daterange"
+            range-separator="至"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            value-format="YYYY-MM-DD"
+            @change="handleQuery"
+            class="wd260 mr10">
+        </el-date-picker>
+
+        <el-button class="ml10" plain size="default" type="success" @click="handleQuery">
+          <SvgIcon name="ele-Search"/>
+          查询
+        </el-button>
+        <el-button plain size="default" type="warning" @click="resetQuery">
+          <SvgIcon name="ele-Refresh"/>
+          重置
+        </el-button>
+        <el-button plain size="default" type="danger" @click="handleClearLog">
+          <SvgIcon name="ele-Delete"/>
+          清空日志
+        </el-button>
+      </el-form>
+
+      <el-table
+          border
+          stripe
+          :height="state.tableHeight"
+          :data="tableData"
+          v-loading="loading">
+        <el-table-column prop="id" label="ID" width="170" align="center" />
+        <el-table-column prop="username" label="操作用户" width="130" align="center" />
+        <el-table-column label="操作模块" align="center">
+          <template #default="{ row }">
+            <span style="white-space: nowrap">{{ getModule(row.method) }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作类型" width="95" align="center">
+          <template #default="{ row }">
+            <el-tag :type="getOperationTypeColor(row.operation)" size="small">
+              {{ getOperationTypeLabel(row.operation) }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="operation" label="操作描述" min-width="200" show-overflow-tooltip />
+        <el-table-column label="请求方式" width="100" align="center">
+          <template #default="{ row }">
+            <el-tag :type="getMethodColor(getHttpMethod(row.method))" size="small">{{ getHttpMethod(row.method) }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="请求URL" min-width="200" show-overflow-tooltip>
+          <template #default="{ row }">{{ getRequestUrl(row.method) }}</template>
+        </el-table-column>
+        <el-table-column prop="ip" label="IP地址" width="150" align="center" />
+        <el-table-column prop="executeTime" label="耗时(ms)" width="95" align="center" />
+        <el-table-column prop="createTime" label="操作时间" width="175" align="center" />
+        <el-table-column label="操作" width="75" align="center" fixed="right">
+          <template #default="{ row }">
+            <el-button type="primary" link size="small" @click="handleDetail(row)">详情</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <div class="page-pager">
+        <el-pagination
+            v-model:current-page="queryParams.pageNum"
+            v-model:page-size="queryParams.pageSize"
+            :page-sizes="[10, 20, 50, 100]"
+            :total="total"
+            layout="total, sizes, prev, pager, next, jumper"
+            @size-change="handleQuery"
+            @current-change="handleQuery"
+            small
+        />
+      </div>
+    </el-card>
+
+    <!-- 详情对话框 -->
+    <el-dialog v-model="detailVisible" title="日志详情" width="700px">
+      <el-descriptions :column="2" border v-if="currentLog">
+        <el-descriptions-item label="操作用户">{{ currentLog.username }}</el-descriptions-item>
+        <el-descriptions-item label="操作模块">{{ getModule(currentLog.method) }}</el-descriptions-item>
+        <el-descriptions-item label="操作类型">
+          <el-tag :type="getOperationTypeColor(currentLog.operation)">
+            {{ getOperationTypeLabel(currentLog.operation) }}
+          </el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="请求方式">
+          <el-tag :type="getMethodColor(getHttpMethod(currentLog.method))" size="small">{{ getHttpMethod(currentLog.method) }}</el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="请求URL" :span="2">{{ getRequestUrl(currentLog.method) }}</el-descriptions-item>
+        <el-descriptions-item label="IP地址">{{ currentLog.ip }}</el-descriptions-item>
+        <el-descriptions-item label="耗时">{{ currentLog.executeTime }}ms</el-descriptions-item>
+        <el-descriptions-item label="操作时间" :span="2">{{ currentLog.createTime }}</el-descriptions-item>
+        <el-descriptions-item label="操作描述" :span="2">{{ currentLog.operation }}</el-descriptions-item>
+      </el-descriptions>
+      <div class="detail-section" v-if="currentLog">
+        <div class="section-title">请求参数</div>
+        <el-input type="textarea" :rows="4" :model-value="formatJson(currentLog.requestParam)" readonly />
+      </div>
+      <template #footer>
+        <el-button @click="detailVisible = false">关闭</el-button>
+      </template>
+    </el-dialog>
+  </div>
 </template>
 
 <script setup lang="ts" name="SystemLog">
-import { ref, reactive, onMounted } from 'vue';
+import { ref, reactive, onMounted, onBeforeUnmount, nextTick } from 'vue';
 import { ElMessage, ElMessageBox } from 'element-plus';
 import { $get, $body } from '/@/utils/request';
-import u from '/@/utils/u';
 
-// 查询参数
+const queryRef = ref();
 const queryParams = reactive({
     pageNum: 1,
     pageSize: 10,
-    operatorName: '',
-    module: '',
-    operationType: '',
+    username: '',
     startDate: '',
     endDate: ''
 });
 const dateRange = ref<string[]>([]);
 
-// 表格数据
+const state = reactive({
+    tableHeight: 400,
+});
+
 const loading = ref(false);
 const tableData = ref<any[]>([]);
 const total = ref(0);
 
-// 详情对话框
 const detailVisible = ref(false);
 const currentLog = ref<any>(null);
 
-// Mock 数据
-const mockData = [
-    { id: 1, operatorName: '管理员', module: '用户管理', operationType: 'CREATE', description: '新增用户:测试用户', requestMethod: 'POST', requestUrl: '/api/user/create', ip: '192.168.1.100', costTime: 125, createTime: '2024-01-15 10:30:00', requestParams: '{"username":"test","nickname":"测试"}', responseData: '{"code":200,"message":"成功"}' },
-    { id: 2, operatorName: '管理员', module: '系统管理', operationType: 'LOGIN', description: '用户登录', requestMethod: 'POST', requestUrl: '/api/auth/login', ip: '192.168.1.100', costTime: 85, createTime: '2024-01-15 09:00:00', requestParams: '{"username":"admin"}', responseData: '{"code":200,"token":"xxx"}' },
-    { id: 3, operatorName: '测试用户', module: '订单管理', operationType: 'QUERY', description: '查询订单列表', requestMethod: 'GET', requestUrl: '/api/order/list', ip: '192.168.1.101', costTime: 230, createTime: '2024-01-15 08:45:00', requestParams: '{"pageNum":1,"pageSize":10}', responseData: '{"code":200,"total":100}' },
-    { id: 4, operatorName: '管理员', module: '站点管理', operationType: 'UPDATE', description: '修改站点信息', requestMethod: 'PUT', requestUrl: '/api/station/update', ip: '192.168.1.100', costTime: 156, createTime: '2024-01-15 08:30:00', requestParams: '{"id":1,"name":"测试站点"}', responseData: '{"code":200,"message":"成功"}' },
-    { id: 5, operatorName: '管理员', module: '角色管理', operationType: 'DELETE', description: '删除角色', requestMethod: 'DELETE', requestUrl: '/api/role/delete/5', ip: '192.168.1.100', costTime: 98, createTime: '2024-01-14 17:00:00', requestParams: '{}', responseData: '{"code":200,"message":"成功"}' }
-];
+const getModule = (method: string) => {
+    if (!method) return '-';
+    const m = method.match(/com\.kym\.admin\.controller\.(\w+)Controller/);
+    if (!m) return '-';
+    const controllers: Record<string, string> = {
+        'WashStation': '站点管理',
+        'WashDevice': '设备管理',
+        'WashOrder': '订单管理',
+        'User': '用户管理',
+        'Account': '账户管理',
+        'AdminUser': '运维用户',
+        'Role': '角色管理',
+        'SystemLog': '系统日志',
+        'Finance': '财务管理',
+        'RechargeConfig': '充值配置',
+        'Promotion': '优惠活动',
+        'Activity': '活动管理',
+        'Banner': '广告管理',
+        'Faq': 'FAQ管理',
+        'Feedback': '反馈管理',
+        'DeviceConfig': '设备配置',
+        'DeviceRelation': '设备关联',
+        'InvestorInfo': '投资人',
+        'DictData': '字典管理',
+        'SystemNotice': '系统公告',
+        'Wx': '微信管理',
+        'Attachment': '附件管理',
+        'Custom': '用户管理',
+    };
+    return controllers[m[1]] || m[1];
+};
 
-// 操作类型标签
-const getOperationTypeLabel = (type: string) => u.fmt.fmtDict(type, 'OptLog.operationType');
-const getOperationTypeColor = (type: string) => u.fmt.fmtDictColor(type, 'OptLog.operationType');
+const getOperationTypeLabel = (operation: string) => {
+    if (!operation) return '其他';
+    if (operation.startsWith('新增')) return '新增';
+    if (operation.startsWith('修改')) return '修改';
+    if (operation.startsWith('删除')) return '删除';
+    if (operation.startsWith('查询')) return '查询';
+    if (operation.includes('登录')) return '登录';
+    if (operation.includes('登出')) return '登出';
+    if (operation.includes('清空')) return '删除';
+    if (operation.includes('导入')) return '新增';
+    if (operation.includes('导出')) return '查询';
+    return '其他';
+};
 
-// 请求方式颜色
-const getMethodColor = (method: string) => u.fmt.fmtDictColor(method, 'OptLog.httpMethod');
+const getOperationTypeColor = (operation: string) => {
+    const map: Record<string, string> = {
+        '新增': 'success',
+        '修改': 'warning',
+        '删除': 'danger',
+        '查询': 'info',
+        '登录': 'primary',
+        '登出': '',
+        '其他': '',
+    };
+    return map[getOperationTypeLabel(operation)] || '';
+};
+
+const getHttpMethod = (method: string) => {
+    if (!method) return '-';
+    const m = method.match(/\.(\w+)\(\)$/);
+    if (!m) return '-';
+    const action = m[1].toLowerCase();
+    if (action.startsWith('list') || action.startsWith('get') || action.startsWith('detail') || action.startsWith('query')) return 'GET';
+    return 'POST';
+};
+
+const getMethodColor = (m: string) => {
+    if (m === 'GET') return 'success';
+    if (m === 'POST') return 'primary';
+    return '';
+};
+
+const getRequestUrl = (method: string) => {
+    if (!method) return '-';
+    const m = method.match(/com\.kym\.admin\.controller\.(\w+)Controller\.(\w+)\(\)$/);
+    if (!m) return method;
+    const controller = m[1];
+    const action = m[2];
+    return `/${controller[0].toLowerCase() + controller.slice(1)}/${action}`;
+};
 
-// 格式化 JSON
 const formatJson = (str: string) => {
     if (!str) return '';
     try {
@@ -180,7 +281,6 @@ const formatJson = (str: string) => {
     }
 };
 
-// 查询数据
 const handleQuery = async () => {
     if (dateRange.value && dateRange.value.length === 2) {
         queryParams.startDate = dateRange.value[0];
@@ -189,95 +289,58 @@ const handleQuery = async () => {
         queryParams.startDate = '';
         queryParams.endDate = '';
     }
-    
+
     loading.value = true;
     try {
         const res = await $get('/systemLog/list', queryParams) as any;
-        tableData.value = res?.list || mockData;
-        total.value = res?.total || mockData.length;
-    } catch (error) {
-        tableData.value = mockData;
-        total.value = mockData.length;
+        tableData.value = res?.list || [];
+        total.value = res?.total || 0;
+    } catch {
+        tableData.value = [];
+        total.value = 0;
     } finally {
         loading.value = false;
     }
 };
 
-// 重置
 const resetQuery = () => {
-    queryParams.operatorName = '';
-    queryParams.module = '';
-    queryParams.operationType = '';
+    queryParams.username = '';
     dateRange.value = [];
     queryParams.pageNum = 1;
     handleQuery();
 };
 
-// 查看详情
 const handleDetail = (row: any) => {
     currentLog.value = row;
     detailVisible.value = true;
 };
 
-// 清空日志
 const handleClearLog = async () => {
     await ElMessageBox.confirm('确定要清空所有操作日志吗?此操作不可恢复!', '警告', { type: 'warning' });
     try {
         await $body('/systemLog/clear', {});
         ElMessage.success('清空成功');
         handleQuery();
-    } catch (error) {
-        ElMessage.success('清空成功(Mock)');
-        tableData.value = [];
-        total.value = 0;
+    } catch {
+        // ignore
     }
 };
 
+const calcTableHeight = () => {
+    nextTick(() => {
+        const bodyHeight = document.body.clientHeight;
+        const queryHeight = queryRef.value?.$el?.clientHeight || 56;
+        state.tableHeight = bodyHeight - queryHeight - 240;
+    });
+};
+
 onMounted(() => {
     handleQuery();
+    calcTableHeight();
+    window.addEventListener('resize', calcTableHeight);
 });
-</script>
 
-<style scoped lang="scss">
-.system-log-container {
-    padding: 15px;
-}
-
-.search-card {
-    margin-bottom: 15px;
-}
-
-.search-form {
-    display: flex;
-    flex-wrap: wrap;
-    gap: 10px;
-}
-
-.card-header {
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-}
-
-.pagination-container {
-    margin-top: 15px;
-    display: flex;
-    justify-content: flex-end;
-}
-
-.detail-section {
-    margin-top: 15px;
-    
-    .section-title {
-        font-weight: 500;
-        margin-bottom: 10px;
-        color: var(--el-text-color-primary);
-    }
-    
-    :deep(.el-textarea__inner) {
-        font-family: monospace;
-        font-size: 13px;
-        background: var(--el-fill-color-lighter);
-    }
-}
-</style>
+onBeforeUnmount(() => {
+    window.removeEventListener('resize', calcTableHeight);
+});
+</script>

+ 1 - 1
car-wash-admin/src/main/java/com/kym/admin/controller/SystemLogController.java

@@ -16,7 +16,7 @@ import org.springframework.web.bind.annotation.*;
  * @since 2023-07-10
  */
 @RestController
-@RequestMapping("/system-log")
+@RequestMapping("/systemLog")
 public class SystemLogController {
 
     private final SystemLogService systemLogService;