zhouhui.jiang
...@@ -116,7 +116,7 @@ ...@@ -116,7 +116,7 @@
116 <th>异常类型</th> 116 <th>异常类型</th>
117 <th>严重程度</th> 117 <th>严重程度</th>
118 <th class="sortable">日期 ↕️</th> 118 <th class="sortable">日期 ↕️</th>
119 - <th>操作</th> 119 + <th class="action-header">操作</th>
120 </tr> 120 </tr>
121 </thead> 121 </thead>
122 <tbody> 122 <tbody>
...@@ -744,7 +744,7 @@ const handleExport = async () => { ...@@ -744,7 +744,7 @@ const handleExport = async () => {
744 744
745 ElMessage.info('正在导出数据,请稍候...') 745 ElMessage.info('正在导出数据,请稍候...')
746 746
747 - const response = await exceptionWorkorderApi.exportExceptionWorkorders(searchParams.value) 747 + const response = await exceptionWorkorderApi.exportExceptionWorkorders(searchParams)
748 748
749 // 创建下载链接 749 // 创建下载链接
750 const blob = new Blob([response as unknown as BlobPart], { 750 const blob = new Blob([response as unknown as BlobPart], {
...@@ -899,21 +899,20 @@ onMounted(() => { ...@@ -899,21 +899,20 @@ onMounted(() => {
899 899
900 <style scoped> 900 <style scoped>
901 .exception-workorder-container { 901 .exception-workorder-container {
902 - padding: 0px;
903 background: #f5f5f5; 902 background: #f5f5f5;
904 min-height: 100vh; 903 min-height: 100vh;
905 } 904 }
906 905
907 -/* 搜索区域样式 */ 906 +/* 搜索区域 */
908 .search-section { 907 .search-section {
909 background: white; 908 background: white;
910 - padding: 16px 20px; 909 + padding: 16px;
911 border-bottom: 1px solid #e0e0e0; 910 border-bottom: 1px solid #e0e0e0;
912 } 911 }
913 912
914 .search-row { 913 .search-row {
915 display: flex; 914 display: flex;
916 - gap: 25px; 915 + gap: 16px;
917 align-items: center; 916 align-items: center;
918 flex-wrap: wrap; 917 flex-wrap: wrap;
919 margin-bottom: 12px; 918 margin-bottom: 12px;
...@@ -927,127 +926,147 @@ onMounted(() => { ...@@ -927,127 +926,147 @@ onMounted(() => {
927 display: flex; 926 display: flex;
928 align-items: center; 927 align-items: center;
929 gap: 8px; 928 gap: 8px;
930 - flex: 0 0 auto;
931 } 929 }
932 930
933 .search-item label { 931 .search-item label {
934 - color: #666; 932 + font-size: 12px;
935 - font-size: 14px; 933 + color: #333;
936 - width: 90px;
937 white-space: nowrap; 934 white-space: nowrap;
938 - text-align: right;
939 } 935 }
940 936
941 -.search-input { 937 +.search-input, .search-select {
942 - width: 180px; 938 + padding: 6px 8px;
943 - height: 32px; 939 + border: 1px solid #d9d9d9;
944 - border: 1px solid #ddd;
945 border-radius: 4px; 940 border-radius: 4px;
946 - padding: 0 8px; 941 + font-size: 12px;
947 - font-size: 14px; 942 + width: 120px;
948 } 943 }
949 944
950 -.search-select { 945 +.search-input:focus, .search-select:focus {
951 - width: 160px; 946 + outline: none;
952 - height: 32px; 947 + border-color: #1890ff;
953 - border: 1px solid #ddd;
954 - border-radius: 4px;
955 - padding: 0 8px;
956 - font-size: 14px;
957 - background: white;
958 } 948 }
959 949
960 .search-actions { 950 .search-actions {
961 - margin-left: auto;
962 display: flex; 951 display: flex;
963 - gap: 10px; 952 + gap: 8px;
964 } 953 }
965 954
966 -.search-btn, .reset-btn { 955 +.search-btn {
967 - padding: 6px 16px; 956 + background: #1890ff;
957 + color: white;
968 border: none; 958 border: none;
969 - border-radius: 4px; 959 + padding: 4px 8px;
960 + border-radius: 3px;
961 + font-size: 11px;
970 cursor: pointer; 962 cursor: pointer;
971 - font-size: 14px;
972 - height: 32px;
973 } 963 }
974 964
975 -.search-btn { 965 +.search-btn:hover {
976 - background: #007bff; 966 + background: #40a9ff;
977 - color: white;
978 } 967 }
979 968
980 .reset-btn { 969 .reset-btn {
981 - background: #6c757d; 970 + background: #ff7875;
982 color: white; 971 color: white;
972 + border: none;
973 + padding: 4px 8px;
974 + border-radius: 3px;
975 + font-size: 11px;
976 + cursor: pointer;
977 + height: 24px;
978 + display: flex;
979 + align-items: center;
980 +}
981 +
982 +.reset-btn:hover {
983 + background: #ff9c99;
983 } 984 }
984 985
985 -/* 操作按钮区域样式 */ 986 +/* 操作区域 */
986 .action-section { 987 .action-section {
987 background: white; 988 background: white;
988 - padding: 12px 20px 12px 20px; 989 + padding: 12px 16px;
989 border-bottom: 1px solid #e0e0e0; 990 border-bottom: 1px solid #e0e0e0;
990 } 991 }
991 992
992 -.action-section .action-buttons { 993 +.action-buttons {
993 display: flex; 994 display: flex;
994 - gap: 10px; 995 + gap: 6px;
995 - justify-content: flex-start;
996 - align-items: center;
997 - margin: 0;
998 - padding: 0;
999 } 996 }
1000 997
1001 .action-btn { 998 .action-btn {
1002 - padding: 6px 16px; 999 + padding: 4px 8px;
1003 border: none; 1000 border: none;
1004 - border-radius: 4px; 1001 + border-radius: 3px;
1002 + font-size: 11px;
1005 cursor: pointer; 1003 cursor: pointer;
1006 - font-size: 14px; 1004 + transition: all 0.2s;
1007 - height: 32px;
1008 - font-weight: 500;
1009 } 1005 }
1010 1006
1011 .action-btn.primary { 1007 .action-btn.primary {
1012 - background: #007bff; 1008 + background: #1890ff;
1013 color: white; 1009 color: white;
1014 } 1010 }
1015 1011
1012 +.action-btn.primary:hover {
1013 + background: #40a9ff;
1014 +}
1015 +
1016 .action-btn.success { 1016 .action-btn.success {
1017 - background: #28a745; 1017 + background: #52c41a;
1018 color: white; 1018 color: white;
1019 } 1019 }
1020 1020
1021 +.action-btn.success:hover {
1022 + background: #73d13d;
1023 +}
1024 +
1021 .action-btn.secondary { 1025 .action-btn.secondary {
1022 - background: #6c757d; 1026 + background: #52c41a;
1023 color: white; 1027 color: white;
1024 } 1028 }
1025 1029
1026 -/* 表格区域样式 */ 1030 +.action-btn.secondary:hover {
1031 + background: #73d13d;
1032 +}
1033 +
1034 +/* 表格区域 */
1027 .table-section { 1035 .table-section {
1028 background: white; 1036 background: white;
1029 - margin-bottom: 0; 1037 + margin: 16px;
1038 + border-radius: 6px;
1039 + overflow: hidden;
1040 + box-shadow: 0 2px 8px rgba(0,0,0,0.1);
1030 } 1041 }
1031 1042
1032 .table-header { 1043 .table-header {
1033 - padding: 10px 20px; 1044 + padding: 8px 16px;
1034 - border-bottom: 1px solid #eee; 1045 + background: #fafafa;
1046 + border-bottom: 1px solid #e0e0e0;
1035 display: flex; 1047 display: flex;
1036 justify-content: flex-end; 1048 justify-content: flex-end;
1037 } 1049 }
1038 1050
1039 .table-controls { 1051 .table-controls {
1040 display: flex; 1052 display: flex;
1041 - gap: 5px; 1053 + gap: 4px;
1042 } 1054 }
1043 1055
1044 .control-btn { 1056 .control-btn {
1045 - padding: 4px 8px;
1046 - border: 1px solid #ddd;
1047 background: white; 1057 background: white;
1048 - border-radius: 4px; 1058 + border: 1px solid #d9d9d9;
1059 + border-radius: 3px;
1060 + padding: 4px 8px;
1061 + font-size: 11px;
1049 cursor: pointer; 1062 cursor: pointer;
1050 - font-size: 12px; 1063 + transition: all 0.2s;
1064 +}
1065 +
1066 +.control-btn:hover {
1067 + background: #f0f8ff;
1068 + border-color: #1890ff;
1069 + color: #1890ff;
1051 } 1070 }
1052 1071
1053 .table-container { 1072 .table-container {
...@@ -1077,31 +1096,29 @@ onMounted(() => { ...@@ -1077,31 +1096,29 @@ onMounted(() => {
1077 .data-table { 1096 .data-table {
1078 width: 100%; 1097 width: 100%;
1079 border-collapse: collapse; 1098 border-collapse: collapse;
1080 - font-size: 14px;
1081 } 1099 }
1082 1100
1083 -.data-table th { 1101 +.data-table th,
1084 - background: #f8f9fa; 1102 +.data-table td {
1085 - border: 1px solid #ddd; 1103 + padding: 8px 12px;
1086 - padding: 12px 8px;
1087 text-align: left; 1104 text-align: left;
1105 + border-bottom: 1px solid #f0f0f0;
1106 + font-size: 12px;
1107 +}
1108 +
1109 +.data-table th {
1110 + background: #fafafa;
1088 font-weight: 600; 1111 font-weight: 600;
1089 color: #333; 1112 color: #333;
1090 white-space: nowrap; 1113 white-space: nowrap;
1091 } 1114 }
1092 1115
1093 -.data-table td { 1116 +.data-table th.action-header {
1094 - border: 1px solid #ddd; 1117 + text-align: center;
1095 - padding: 12px 8px;
1096 - vertical-align: middle;
1097 -}
1098 -
1099 -.data-table tbody tr:nth-child(even) {
1100 - background: #f8f9fa;
1101 } 1118 }
1102 1119
1103 .data-table tbody tr:hover { 1120 .data-table tbody tr:hover {
1104 - background: #e9ecef; 1121 + background: #f5f7fa;
1105 } 1122 }
1106 1123
1107 .sortable { 1124 .sortable {
...@@ -1109,26 +1126,31 @@ onMounted(() => { ...@@ -1109,26 +1126,31 @@ onMounted(() => {
1109 user-select: none; 1126 user-select: none;
1110 } 1127 }
1111 1128
1129 +.table-checkbox {
1130 + width: 14px;
1131 + height: 14px;
1132 + cursor: pointer;
1133 +}
1134 +
1112 .select-all, .row-select { 1135 .select-all, .row-select {
1113 margin: 0; 1136 margin: 0;
1114 } 1137 }
1115 1138
1116 .status-badge, .severity-badge { 1139 .status-badge, .severity-badge {
1117 - padding: 2px 8px; 1140 + padding: 2px 6px;
1118 - border-radius: 12px; 1141 + border-radius: 3px;
1119 - font-size: 12px; 1142 + font-size: 10px;
1120 - font-weight: 500;
1121 display: inline-block; 1143 display: inline-block;
1122 } 1144 }
1123 1145
1124 .status-badge.pending { 1146 .status-badge.pending {
1125 - background: #fff3cd; 1147 + background: #fff7e6;
1126 - color: #856404; 1148 + color: #fa8c16;
1127 } 1149 }
1128 1150
1129 .status-badge.processing { 1151 .status-badge.processing {
1130 - background: #d1ecf1; 1152 + background: #e6f7ff;
1131 - color: #0c5460; 1153 + color: #1890ff;
1132 } 1154 }
1133 1155
1134 .status-badge.resolved { 1156 .status-badge.resolved {
...@@ -1200,24 +1222,24 @@ onMounted(() => { ...@@ -1200,24 +1222,24 @@ onMounted(() => {
1200 /* 表格内操作按钮容器 */ 1222 /* 表格内操作按钮容器 */
1201 .table-actions { 1223 .table-actions {
1202 display: flex; 1224 display: flex;
1203 - gap: 8px; 1225 + gap: 4px;
1204 justify-content: center; 1226 justify-content: center;
1205 align-items: center; 1227 align-items: center;
1206 flex-wrap: wrap; 1228 flex-wrap: wrap;
1207 padding: 0 4px; 1229 padding: 0 4px;
1208 } 1230 }
1209 1231
1210 -/* 操作按钮样式 */ 1232 +/* 表格内操作按钮样式 */
1211 -.action-btn { 1233 +.table-actions .action-btn {
1212 - padding: 4px 8px; 1234 + padding: 2px 6px;
1213 - font-size: 12px; 1235 + margin-right: 4px;
1214 - border-radius: 4px; 1236 + border: none;
1215 - font-weight: 500; 1237 + border-radius: 3px;
1216 - min-width: 60px; 1238 + font-size: 10px;
1217 - height: 28px; 1239 + cursor: pointer;
1218 display: inline-flex; 1240 display: inline-flex;
1219 align-items: center; 1241 align-items: center;
1220 - justify-content: center; 1242 + gap: 2px;
1221 white-space: nowrap; 1243 white-space: nowrap;
1222 } 1244 }
1223 1245
...@@ -1228,49 +1250,52 @@ onMounted(() => { ...@@ -1228,49 +1250,52 @@ onMounted(() => {
1228 1250
1229 /* 分页区域样式 */ 1251 /* 分页区域样式 */
1230 .pagination-section { 1252 .pagination-section {
1231 - background: white;
1232 - padding: 12px 20px;
1233 - border-top: 1px solid #e0e0e0;
1234 display: flex; 1253 display: flex;
1235 justify-content: space-between; 1254 justify-content: space-between;
1236 align-items: center; 1255 align-items: center;
1256 + padding: 8px 16px;
1257 + background: #fafafa;
1258 + border-top: 1px solid #e0e0e0;
1237 } 1259 }
1238 1260
1239 .pagination-info { 1261 .pagination-info {
1240 - display: flex; 1262 + font-size: 12px;
1241 - align-items: center;
1242 - gap: 10px;
1243 - font-size: 14px;
1244 color: #666; 1263 color: #666;
1245 } 1264 }
1246 1265
1247 .page-size-select { 1266 .page-size-select {
1248 - height: 28px; 1267 + padding: 4px 8px;
1249 - border: 1px solid #ddd; 1268 + border: 1px solid #d9d9d9;
1250 border-radius: 4px; 1269 border-radius: 4px;
1251 - padding: 0 8px; 1270 + font-size: 12px;
1252 - font-size: 14px; 1271 + background: white;
1272 + cursor: pointer;
1253 } 1273 }
1254 1274
1255 .pagination-controls { 1275 .pagination-controls {
1256 display: flex; 1276 display: flex;
1257 align-items: center; 1277 align-items: center;
1258 gap: 8px; 1278 gap: 8px;
1259 - font-size: 14px;
1260 } 1279 }
1261 1280
1262 .page-btn { 1281 .page-btn {
1263 - padding: 4px 12px; 1282 + padding: 4px 8px;
1264 - border: 1px solid #ddd; 1283 + border: 1px solid #d9d9d9;
1284 + border-radius: 3px;
1265 background: white; 1285 background: white;
1266 - border-radius: 4px;
1267 cursor: pointer; 1286 cursor: pointer;
1268 - font-size: 14px; 1287 + font-size: 11px;
1288 + transition: all 0.2s;
1289 +}
1290 +
1291 +.page-btn:hover:not(:disabled) {
1292 + background: #f0f8ff;
1293 + border-color: #1890ff;
1294 + color: #1890ff;
1269 } 1295 }
1270 1296
1271 .page-btn:disabled { 1297 .page-btn:disabled {
1272 - background: #f8f9fa; 1298 + opacity: 0.5;
1273 - color: #6c757d;
1274 cursor: not-allowed; 1299 cursor: not-allowed;
1275 } 1300 }
1276 1301
......
...@@ -136,6 +136,14 @@ ...@@ -136,6 +136,14 @@
136 <table class="data-table"> 136 <table class="data-table">
137 <thead> 137 <thead>
138 <tr> 138 <tr>
139 + <th class="checkbox-col">
140 + <input
141 + type="checkbox"
142 + :checked="selectAll"
143 + @change="handleSelectAll"
144 + class="table-checkbox"
145 + />
146 + </th>
139 <th>返利编号</th> 147 <th>返利编号</th>
140 <th>订单编号</th> 148 <th>订单编号</th>
141 <th>经销商代码</th> 149 <th>经销商代码</th>
...@@ -151,9 +159,17 @@ ...@@ -151,9 +159,17 @@
151 </thead> 159 </thead>
152 <tbody> 160 <tbody>
153 <tr v-if="rebateList.length === 0"> 161 <tr v-if="rebateList.length === 0">
154 - <td colspan="11" class="empty-data">暂无数据</td> 162 + <td colspan="12" class="empty-data">暂无数据</td>
155 </tr> 163 </tr>
156 <tr v-for="row in rebateList" :key="row.rebateId"> 164 <tr v-for="row in rebateList" :key="row.rebateId">
165 + <td class="checkbox-col">
166 + <input
167 + type="checkbox"
168 + :checked="selectedRebates.includes(row)"
169 + @change="handleSelectRebate(row)"
170 + class="table-checkbox"
171 + />
172 + </td>
157 <td>{{ row.rebateNo }}</td> 173 <td>{{ row.rebateNo }}</td>
158 <td>{{ row.orderNo }}</td> 174 <td>{{ row.orderNo }}</td>
159 <td>{{ row.dealerCode }}</td> 175 <td>{{ row.dealerCode }}</td>
...@@ -173,7 +189,7 @@ ...@@ -173,7 +189,7 @@
173 </td> 189 </td>
174 <td>{{ formatDate(row.updateTime || '') }}</td> 190 <td>{{ formatDate(row.updateTime || '') }}</td>
175 <td> 191 <td>
176 - <button @click="handleViewDetail(row)" class="action-link">详情</button> 192 + <button @click="handleViewDetail(row)" class="table-btn view">✏️ 详情</button>
177 </td> 193 </td>
178 </tr> 194 </tr>
179 </tbody> 195 </tbody>
...@@ -181,30 +197,46 @@ ...@@ -181,30 +197,46 @@
181 </div> 197 </div>
182 198
183 <!-- 分页 --> 199 <!-- 分页 -->
184 - <div class="pagination-wrapper"> 200 + <div class="table-footer">
185 - <div class="pagination-info"> 201 + <div class="pagination-left">
186 - 共 {{ pagination.total }} 条,{{ pagination.pageSize }}/页 202 + <div class="pagination-info">
203 + 共 {{ pagination.total }} 条记录,第 {{ pagination.pageNum }} / {{ getTotalPages() }} 页
204 + </div>
205 + <div class="page-size-selector">
206 + <label>每页显示:</label>
207 + <select v-model="pagination.pageSize" @change="handleSizeChange($event)" class="page-size-select">
208 + <option value="10">10条</option>
209 + <option value="20">20条</option>
210 + <option value="50">50条</option>
211 + <option value="100">100条</option>
212 + </select>
213 + </div>
187 </div> 214 </div>
188 - <div class="pagination-controls"> 215 + <div class="pagination-container">
189 - <button @click="handlePrevPage" :disabled="pagination.pageNum <= 1" class="page-btn">‹</button> 216 + <button
190 - <button 217 + @click="handlePrevPage"
191 - v-for="page in getPageNumbers()" 218 + :disabled="pagination.pageNum <= 1"
192 - :key="page" 219 + class="pagination-btn prev-btn"
193 - @click="handlePageChange(page)"
194 - :class="['page-btn', { active: page === pagination.pageNum }]"
195 > 220 >
196 - {{ page }} 221 + 上一页
222 + </button>
223 + <div class="pagination-pages">
224 + <button
225 + v-for="page in getPageNumbers()"
226 + :key="page"
227 + @click="handlePageChange(page)"
228 + :class="['page-number', { active: page === pagination.pageNum }]"
229 + >
230 + {{ page }}
231 + </button>
232 + </div>
233 + <button
234 + @click="handleNextPage"
235 + :disabled="pagination.pageNum >= getTotalPages()"
236 + class="pagination-btn next-btn"
237 + >
238 + 下一页
197 </button> 239 </button>
198 - <button @click="handleNextPage" :disabled="pagination.pageNum >= getTotalPages()" class="page-btn">›</button>
199 - </div>
200 - <div class="pagination-jump">
201 - 前往
202 - <input
203 - v-model="jumpPage"
204 - class="jump-input"
205 - @keyup.enter="handleJumpPage"
206 - />
207 -
208 </div> 240 </div>
209 </div> 241 </div>
210 </div> 242 </div>
...@@ -421,6 +453,7 @@ const exportLoading = ref(false) ...@@ -421,6 +453,7 @@ const exportLoading = ref(false)
421 const auditLoading = ref(false) 453 const auditLoading = ref(false)
422 const rebateList = ref<Rebate[]>([]) 454 const rebateList = ref<Rebate[]>([])
423 const selectedRebates = ref<Rebate[]>([]) 455 const selectedRebates = ref<Rebate[]>([])
456 +const selectAll = ref(false)
424 const rebateDetail = ref<Rebate | null>(null) 457 const rebateDetail = ref<Rebate | null>(null)
425 458
426 // 图表引用 459 // 图表引用
...@@ -872,8 +905,9 @@ const getCalcFlagClass = (flag: number) => { ...@@ -872,8 +905,9 @@ const getCalcFlagClass = (flag: number) => {
872 } 905 }
873 906
874 // 分页变化 907 // 分页变化
875 -const handleSizeChange = (size: number) => { 908 +const handleSizeChange = (event: Event) => {
876 - pagination.pageSize = size 909 + const target = event.target as HTMLSelectElement
910 + pagination.pageSize = parseInt(target.value)
877 pagination.pageNum = 1 911 pagination.pageNum = 1
878 getRebateList() 912 getRebateList()
879 } 913 }
...@@ -898,6 +932,27 @@ const handleSelectionChange = (selection: Rebate[]) => { ...@@ -898,6 +932,27 @@ const handleSelectionChange = (selection: Rebate[]) => {
898 selectedRebates.value = selection 932 selectedRebates.value = selection
899 } 933 }
900 934
935 +// 全选/取消全选
936 +const handleSelectAll = () => {
937 + if (selectAll.value) {
938 + selectedRebates.value = [...rebateList.value]
939 + } else {
940 + selectedRebates.value = []
941 + }
942 +}
943 +
944 +// 选择单个返利记录
945 +const handleSelectRebate = (rebate: Rebate) => {
946 + const index = selectedRebates.value.findIndex(r => r.rebateId === rebate.rebateId)
947 + if (index > -1) {
948 + selectedRebates.value.splice(index, 1)
949 + } else {
950 + selectedRebates.value.push(rebate)
951 + }
952 + // 更新全选状态
953 + selectAll.value = selectedRebates.value.length === rebateList.value.length && rebateList.value.length > 0
954 +}
955 +
901 // 查看详情 956 // 查看详情
902 const handleViewDetail = async (row: Rebate) => { 957 const handleViewDetail = async (row: Rebate) => {
903 try { 958 try {
...@@ -1588,170 +1643,132 @@ onMounted(async () => { ...@@ -1588,170 +1643,132 @@ onMounted(async () => {
1588 1643
1589 <style scoped lang="scss"> 1644 <style scoped lang="scss">
1590 .rebate-page { 1645 .rebate-page {
1591 - padding: 20px; 1646 + background: #f5f5f5;
1592 - background: #f5f7fa;
1593 min-height: 100vh; 1647 min-height: 100vh;
1594 -
1595 } 1648 }
1596 1649
1650 +/* 搜索区域 */
1597 .search-section { 1651 .search-section {
1598 background: white; 1652 background: white;
1599 - padding: 16px 20px; 1653 + padding: 16px;
1600 - margin-bottom: 16px; 1654 + border-bottom: 1px solid #e0e0e0;
1601 - border-radius: 4px; 1655 +}
1602 - box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
1603 -
1604 - .search-row {
1605 - display: flex;
1606 - align-items: center;
1607 - gap: 24px;
1608 - flex-wrap: wrap;
1609 - margin-bottom: 12px;
1610 -
1611 - &:last-child {
1612 - margin-bottom: 0;
1613 - }
1614 1656
1615 - .search-item { 1657 +.search-row {
1616 - display: flex; 1658 + display: flex;
1617 - align-items: center; 1659 + gap: 16px;
1618 - gap: 8px; 1660 + align-items: center;
1661 + flex-wrap: wrap;
1662 + margin-bottom: 12px;
1663 +}
1619 1664
1620 - label { 1665 +.search-row:last-child {
1621 - font-size: 14px; 1666 + margin-bottom: 0;
1622 - color: #333; 1667 +}
1623 - white-space: nowrap;
1624 - min-width: 80px;
1625 - }
1626 1668
1627 - .search-input { 1669 +.search-item {
1628 - padding: 8px 12px; 1670 + display: flex;
1629 - border: 1px solid #ddd; 1671 + align-items: center;
1630 - border-radius: 4px; 1672 + gap: 8px;
1631 - font-size: 14px; 1673 +}
1632 - width: 160px;
1633 - transition: border-color 0.3s;
1634 1674
1635 - &:focus { 1675 +.search-item label {
1636 - outline: none; 1676 + font-size: 12px;
1637 - border-color: #409eff; 1677 + color: #333;
1638 - } 1678 + white-space: nowrap;
1639 - } 1679 +}
1640 1680
1641 - .search-select { 1681 +.search-input, .search-select {
1642 - padding: 8px 12px; 1682 + padding: 6px 8px;
1643 - border: 1px solid #ddd; 1683 + border: 1px solid #d9d9d9;
1644 - border-radius: 4px; 1684 + border-radius: 4px;
1645 - font-size: 14px; 1685 + font-size: 12px;
1646 - width: 120px; 1686 + width: 120px;
1647 - background: white; 1687 +}
1648 - cursor: pointer;
1649 - transition: border-color 0.3s;
1650 -
1651 - &:focus {
1652 - outline: none;
1653 - border-color: #409eff;
1654 - }
1655 - }
1656 - }
1657 1688
1658 - .search-actions { 1689 +.search-input:focus, .search-select:focus {
1659 - margin-left: auto; 1690 + outline: none;
1660 - display: flex; 1691 + border-color: #1890ff;
1661 - gap: 8px; 1692 +}
1662 1693
1663 - .search-btn, .reset-btn { 1694 +.search-actions {
1664 - padding: 8px 16px; 1695 + display: flex;
1665 - border: 1px solid #ddd; 1696 + gap: 8px;
1666 - border-radius: 4px; 1697 +}
1667 - font-size: 14px;
1668 - cursor: pointer;
1669 - transition: all 0.3s;
1670 - background: white;
1671 1698
1672 - &:hover { 1699 +.search-btn {
1673 - background: #f5f7fa; 1700 + background: #1890ff;
1674 - } 1701 + color: white;
1702 + border: none;
1703 + padding: 4px 8px;
1704 + border-radius: 3px;
1705 + font-size: 11px;
1706 + cursor: pointer;
1707 +}
1675 1708
1676 - &:disabled { 1709 +.search-btn:hover {
1677 - opacity: 0.6; 1710 + background: #40a9ff;
1678 - cursor: not-allowed; 1711 +}
1679 - }
1680 - }
1681 1712
1682 - .search-btn { 1713 +.reset-btn {
1683 - background: #409eff; 1714 + background: #ff7875;
1684 - color: white; 1715 + color: white;
1685 - border-color: #409eff; 1716 + border: none;
1717 + padding: 4px 8px;
1718 + border-radius: 3px;
1719 + font-size: 11px;
1720 + cursor: pointer;
1721 + height: 24px;
1722 + display: flex;
1723 + align-items: center;
1724 +}
1686 1725
1687 - &:hover { 1726 +.reset-btn:hover {
1688 - background: #66b1ff; 1727 + background: #ff9c99;
1689 - }
1690 - }
1691 - }
1692 - }
1693 } 1728 }
1694 1729
1730 +/* 操作区域 */
1695 .action-section { 1731 .action-section {
1696 background: white; 1732 background: white;
1697 - padding: 12px 20px; 1733 + padding: 12px 16px;
1698 - margin-bottom: 16px; 1734 + border-bottom: 1px solid #e0e0e0;
1699 - border-radius: 4px; 1735 +}
1700 - box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
1701 -
1702 - .action-buttons {
1703 - display: flex;
1704 - gap: 12px;
1705 -
1706 - .action-btn {
1707 - padding: 8px 16px;
1708 - border: 1px solid #ddd;
1709 - border-radius: 4px;
1710 - font-size: 14px;
1711 - cursor: pointer;
1712 - transition: all 0.3s;
1713 - background: white;
1714 -
1715 - &:hover {
1716 - background: #f5f7fa;
1717 - }
1718 1736
1719 - &:disabled { 1737 +.action-buttons {
1720 - opacity: 0.6; 1738 + display: flex;
1721 - cursor: not-allowed; 1739 + gap: 6px;
1722 - } 1740 +}
1723 1741
1724 - &.primary { 1742 +.action-btn {
1725 - background: #409eff; 1743 + padding: 4px 8px;
1726 - color: white; 1744 + border: none;
1727 - border-color: #409eff; 1745 + border-radius: 3px;
1746 + font-size: 11px;
1747 + cursor: pointer;
1748 + transition: all 0.2s;
1749 +}
1728 1750
1729 - &:hover { 1751 +.action-btn.primary {
1730 - background: #66b1ff; 1752 + background: #1890ff;
1731 - } 1753 + color: white;
1732 - } 1754 +}
1733 1755
1734 - &.secondary { 1756 +.action-btn.primary:hover {
1735 - background: #67c23a; 1757 + background: #40a9ff;
1736 - color: white; 1758 +}
1737 - border-color: #67c23a;
1738 1759
1739 - &:hover { 1760 +.action-btn.secondary {
1740 - background: #85ce61; 1761 + background: #52c41a;
1741 - } 1762 + color: white;
1742 - } 1763 +}
1743 1764
1744 - &.danger { 1765 +.action-btn.secondary:hover {
1745 - background: #f56c6c; 1766 + background: #73d13d;
1746 - color: white; 1767 +}
1747 - border-color: #f56c6c;
1748 1768
1749 - &:hover { 1769 +.action-btn.danger {
1750 - background: #f78989; 1770 + background: #ff4d4f;
1751 - } 1771 + color: white;
1752 - }
1753 - }
1754 - }
1755 } 1772 }
1756 1773
1757 .charts-section { 1774 .charts-section {
...@@ -1825,42 +1842,45 @@ onMounted(async () => { ...@@ -1825,42 +1842,45 @@ onMounted(async () => {
1825 width: 100%; 1842 width: 100%;
1826 } 1843 }
1827 1844
1845 +/* 表格区域 */
1828 .table-section { 1846 .table-section {
1829 background: white; 1847 background: white;
1830 - border-radius: 4px; 1848 + margin: 16px;
1831 - box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1); 1849 + border-radius: 6px;
1832 overflow: hidden; 1850 overflow: hidden;
1851 + box-shadow: 0 2px 8px rgba(0,0,0,0.1);
1852 +}
1833 1853
1834 - .table-header { 1854 +.table-header {
1835 - display: flex; 1855 + padding: 8px 16px;
1836 - justify-content: flex-end; 1856 + background: #fafafa;
1837 - align-items: center; 1857 + border-bottom: 1px solid #e0e0e0;
1838 - padding: 12px 20px; 1858 + display: flex;
1839 - border-bottom: 1px solid #f0f0f0; 1859 + justify-content: flex-end;
1840 - background: #fafafa; 1860 +}
1841 1861
1842 - .table-controls { 1862 +.table-controls {
1843 - display: flex; 1863 + display: flex;
1844 - gap: 8px; 1864 + gap: 4px;
1845 - 1865 +}
1846 - .control-btn {
1847 - padding: 6px 12px;
1848 - border: 1px solid #ddd;
1849 - border-radius: 4px;
1850 - background: white;
1851 - cursor: pointer;
1852 - font-size: 14px;
1853 - transition: all 0.3s;
1854 1866
1855 - &:hover { 1867 +.control-btn {
1856 - background: #f5f7fa; 1868 + background: white;
1857 - border-color: #409eff; 1869 + border: 1px solid #d9d9d9;
1858 - } 1870 + border-radius: 3px;
1859 - } 1871 + padding: 4px 8px;
1860 - } 1872 + font-size: 11px;
1861 - } 1873 + cursor: pointer;
1874 + transition: all 0.2s;
1875 +}
1876 +
1877 +.control-btn:hover {
1878 + background: #f0f8ff;
1879 + border-color: #1890ff;
1880 + color: #1890ff;
1881 +}
1862 1882
1863 - .table-container { 1883 +.table-container {
1864 position: relative; 1884 position: relative;
1865 1885
1866 .loading-overlay { 1886 .loading-overlay {
...@@ -1880,126 +1900,165 @@ onMounted(async () => { ...@@ -1880,126 +1900,165 @@ onMounted(async () => {
1880 color: #666; 1900 color: #666;
1881 } 1901 }
1882 } 1902 }
1903 + }
1883 1904
1884 - .data-table { 1905 +.data-table {
1885 - width: 100%; 1906 + width: 100%;
1886 - border-collapse: collapse; 1907 + border-collapse: collapse;
1887 - font-size: 14px; 1908 + font-size: 12px;
1909 +}
1888 1910
1889 - thead { 1911 +.data-table th,
1890 - background: #fafafa; 1912 +.data-table td {
1913 + padding: 8px 12px;
1914 + text-align: left;
1915 + border-bottom: 1px solid #f0f0f0;
1916 + font-size: 12px;
1917 +}
1891 1918
1892 - th { 1919 +.data-table th {
1893 - padding: 12px 16px; 1920 + background: #fafafa;
1894 - text-align: left; 1921 + font-weight: 600;
1895 - font-weight: 500; 1922 + color: #333;
1896 - color: #333; 1923 + white-space: nowrap;
1897 - border-bottom: 1px solid #e0e0e0; 1924 +}
1898 - white-space: nowrap;
1899 - }
1900 - }
1901 1925
1902 - tbody { 1926 +.data-table tbody tr:hover {
1903 - tr { 1927 + background: #f5f7fa;
1904 - transition: background-color 0.3s; 1928 +}
1905 1929
1906 - &:hover { 1930 +/* 表格复选框 */
1907 - background: #f5f7fa; 1931 +.table-checkbox {
1908 - } 1932 + width: 14px;
1933 + height: 14px;
1934 +}
1909 1935
1910 - &:nth-child(even) { 1936 +/* 表格操作按钮 */
1911 - background: #fafafa; 1937 +.table-btn {
1912 - } 1938 + padding: 2px 6px;
1939 + margin-right: 4px;
1940 + border: none;
1941 + border-radius: 3px;
1942 + font-size: 10px;
1943 + cursor: pointer;
1944 + display: inline-flex;
1945 + align-items: center;
1946 + gap: 2px;
1947 +}
1913 1948
1914 - &:nth-child(even):hover { 1949 +.table-btn.view {
1915 - background: #f0f2f5; 1950 + background: #1890ff;
1916 - } 1951 + color: white;
1952 +}
1917 1953
1918 - td { 1954 +.table-btn.edit {
1919 - padding: 12px 16px; 1955 + background: #1890ff;
1920 - border-bottom: 1px solid #f0f0f0; 1956 + color: white;
1921 - vertical-align: middle; 1957 +}
1922 1958
1923 - &.empty-data { 1959 +.table-btn.delete {
1924 - text-align: center; 1960 + background: #ff4d4f;
1925 - color: #999; 1961 + color: white;
1926 - font-style: italic; 1962 +}
1927 - padding: 40px;
1928 - }
1929 - }
1930 - }
1931 - }
1932 - }
1933 - }
1934 1963
1935 - .pagination-wrapper { 1964 +/* 分页样式 */
1936 - display: flex; 1965 +.table-footer {
1937 - justify-content: space-between; 1966 + display: flex;
1938 - align-items: center; 1967 + justify-content: space-between;
1939 - padding: 12px 20px; 1968 + align-items: center;
1940 - border-top: 1px solid #f0f0f0; 1969 + padding: 8px 16px;
1941 - background: #fafafa; 1970 + background: #f8f9fa;
1942 - 1971 + border-top: 1px solid #e0e0e0;
1943 - .pagination-info { 1972 +}
1944 - font-size: 14px;
1945 - color: #666;
1946 - }
1947 1973
1948 - .pagination-controls { 1974 +.pagination-left {
1949 - display: flex; 1975 + display: flex;
1950 - gap: 4px; 1976 + align-items: center;
1951 - align-items: center; 1977 + gap: 16px;
1978 +}
1952 1979
1953 - .page-btn { 1980 +.pagination-info {
1954 - padding: 6px 12px; 1981 + font-size: 12px;
1955 - border: 1px solid #ddd; 1982 + color: #666;
1956 - border-radius: 4px; 1983 +}
1957 - background: white;
1958 - cursor: pointer;
1959 - font-size: 14px;
1960 - transition: all 0.3s;
1961 - min-width: 32px;
1962 1984
1963 - &:hover:not(:disabled) { 1985 +.page-size-selector {
1964 - background: #f5f7fa; 1986 + display: flex;
1965 - border-color: #409eff; 1987 + align-items: center;
1966 - } 1988 + gap: 4px;
1989 + font-size: 11px;
1990 + color: #666;
1991 +}
1967 1992
1968 - &:disabled { 1993 +.page-size-select {
1969 - opacity: 0.5; 1994 + padding: 2px 4px;
1970 - cursor: not-allowed; 1995 + border: 1px solid #d9d9d9;
1971 - } 1996 + border-radius: 3px;
1997 + font-size: 10px;
1998 + background: white;
1999 +}
1972 2000
1973 - &.active { 2001 +.pagination-container {
1974 - background: #409eff; 2002 + display: flex;
1975 - color: white; 2003 + align-items: center;
1976 - border-color: #409eff; 2004 + background: #f8f9fa;
1977 - } 2005 + border: 1px solid #e9ecef;
1978 - } 2006 + border-radius: 8px;
1979 - } 2007 + overflow: hidden;
2008 + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
2009 +}
1980 2010
1981 - .pagination-jump { 2011 +.pagination-btn {
1982 - display: flex; 2012 + padding: 2px 6px;
1983 - align-items: center; 2013 + border: none;
1984 - gap: 8px; 2014 + background: white;
1985 - font-size: 14px; 2015 + color: #6c757d;
1986 - color: #666; 2016 + font-size: 11px;
1987 - 2017 + font-weight: 500;
1988 - .jump-input { 2018 + cursor: pointer;
1989 - width: 50px; 2019 + transition: all 0.2s;
1990 - padding: 4px 8px; 2020 + border-right: 1px solid #e9ecef;
1991 - border: 1px solid #ddd; 2021 + height: 20px;
1992 - border-radius: 4px; 2022 +}
1993 - text-align: center;
1994 - font-size: 14px;
1995 2023
1996 - &:focus { 2024 +.pagination-btn:hover:not(:disabled) {
1997 - outline: none; 2025 + background: #e9ecef;
1998 - border-color: #409eff; 2026 + color: #495057;
1999 - } 2027 +}
2000 - } 2028 +
2001 - } 2029 +.pagination-btn:disabled {
2002 - } 2030 + opacity: 0.5;
2031 + cursor: not-allowed;
2032 +}
2033 +
2034 +.pagination-pages {
2035 + display: flex;
2036 +}
2037 +
2038 +.page-number {
2039 + padding: 2px 6px;
2040 + border: none;
2041 + background: white;
2042 + color: #6c757d;
2043 + font-size: 11px;
2044 + font-weight: 500;
2045 + cursor: pointer;
2046 + transition: all 0.2s;
2047 + border-right: 1px solid #e9ecef;
2048 + min-width: 28px;
2049 + text-align: center;
2050 + height: 20px;
2051 +}
2052 +
2053 +.page-number:hover {
2054 + background: #e9ecef;
2055 + color: #495057;
2056 +}
2057 +
2058 +.page-number.active {
2059 + background: #1890ff;
2060 + color: white;
2061 + font-weight: 600;
2003 } 2062 }
2004 2063
2005 .status-tag { 2064 .status-tag {
...@@ -2049,7 +2108,7 @@ onMounted(async () => { ...@@ -2049,7 +2108,7 @@ onMounted(async () => {
2049 overflow-y: auto; 2108 overflow-y: auto;
2050 } 2109 }
2051 2110
2052 -// 响应式设计 2111 +/* 响应式设计 */
2053 @media (max-width: 1200px) { 2112 @media (max-width: 1200px) {
2054 .charts-container { 2113 .charts-container {
2055 grid-template-columns: 1fr; 2114 grid-template-columns: 1fr;
...@@ -2071,6 +2130,5 @@ onMounted(async () => { ...@@ -2071,6 +2130,5 @@ onMounted(async () => {
2071 margin-top: 12px; 2130 margin-top: 12px;
2072 } 2131 }
2073 } 2132 }
2074 -
2075 } 2133 }
2076 </style> 2134 </style>
......
1 <template> 1 <template>
2 <div class="log-management"> 2 <div class="log-management">
3 - <!-- 页面标题 --> 3 + <!-- 搜索筛选区域 -->
4 - <div class="page-header"> 4 + <div class="search-section">
5 - <h2>日志管理</h2> 5 + <div class="search-row">
6 - <p>系统操作日志查询与管理</p> 6 + <div class="search-item">
7 - </div> 7 + <label>日志类型:</label>
8 - 8 + <select v-model="searchForm.logType" class="search-select">
9 - <!-- 搜索表单 --> 9 + <option value="">全部</option>
10 - <el-card class="search-card" shadow="never"> 10 + <option value="0">操作日志</option>
11 - <el-form :model="searchForm" :inline="true" class="search-form"> 11 + <option value="1">异常日志</option>
12 - <el-form-item label="日志类型"> 12 + <option value="2">登录日志</option>
13 - <el-select v-model="searchForm.logType" placeholder="请选择日志类型" clearable style="width: 200px"> 13 + </select>
14 - <el-option label="操作日志" value="0" /> 14 + </div>
15 - <el-option label="异常日志" value="1" /> 15 + <div class="search-item">
16 - <el-option label="登录日志" value="2" /> 16 + <label>用户名:</label>
17 - </el-select> 17 + <input
18 - </el-form-item> 18 + v-model="searchForm.username"
19 - 19 + type="text"
20 - <el-form-item label="用户名">
21 - <el-input
22 - v-model="searchForm.username"
23 placeholder="请输入用户名" 20 placeholder="请输入用户名"
24 - clearable 21 + class="search-input"
25 - style="width: 200px"
26 /> 22 />
27 - </el-form-item> 23 + </div>
28 - 24 + <div class="search-item">
29 - <el-form-item label="操作模块"> 25 + <label>操作模块:</label>
30 - <el-input 26 + <input
31 - v-model="searchForm.module" 27 + v-model="searchForm.module"
28 + type="text"
32 placeholder="请输入操作模块" 29 placeholder="请输入操作模块"
33 - clearable 30 + class="search-input"
34 - style="width: 200px"
35 /> 31 />
36 - </el-form-item> 32 + </div>
37 - 33 + <div class="search-item">
38 - <el-form-item label="操作结果"> 34 + <label>操作结果:</label>
39 - <el-select v-model="searchForm.status" placeholder="请选择操作结果" clearable style="width: 200px"> 35 + <select v-model="searchForm.status" class="search-select">
40 - <el-option label="成功" :value="1" /> 36 + <option value="">全部</option>
41 - <el-option label="失败" :value="0" /> 37 + <option value="1">成功</option>
42 - </el-select> 38 + <option value="0">失败</option>
43 - </el-form-item> 39 + </select>
44 - 40 + </div>
45 - <el-form-item label="时间范围"> 41 + <div class="search-item">
46 - <el-date-picker 42 + <label>时间范围:</label>
47 - v-model="dateRange" 43 + <input
48 - type="datetimerange" 44 + v-model="dateRangeText"
49 - range-separator="至" 45 + type="text"
50 - start-placeholder="开始时间" 46 + placeholder="请选择时间范围"
51 - end-placeholder="结束时间" 47 + class="search-input"
52 - format="YYYY-MM-DD HH:mm:ss" 48 + @click="showDatePicker = true"
53 - value-format="YYYY-MM-DD HH:mm:ss" 49 + readonly
54 - @change="handleDateRangeChange"
55 - style="width: 350px"
56 /> 50 />
57 - </el-form-item>
58 -
59 - <el-form-item>
60 - <el-button type="primary" @click="handleSearch" :loading="loading">
61 - <el-icon><Search /></el-icon>
62 - 搜索
63 - </el-button>
64 - <el-button @click="handleReset">
65 - <el-icon><Refresh /></el-icon>
66 - 重置
67 - </el-button>
68 - </el-form-item>
69 - </el-form>
70 - </el-card>
71 -
72 - <!-- 操作按钮 -->
73 - <el-card class="toolbar-card" shadow="never">
74 - <div class="toolbar">
75 - <div class="toolbar-left">
76 - <el-button
77 - type="danger"
78 - :disabled="selectedLogs.length === 0"
79 - @click="handleBatchDelete"
80 - >
81 - <el-icon><Delete /></el-icon>
82 - 批量删除
83 - </el-button>
84 - <el-button type="warning" @click="handleCleanExpired">
85 - <el-icon><Delete /></el-icon>
86 - 清理过期日志
87 - </el-button>
88 - <el-button type="info" @click="handleExport">
89 - <el-icon><Download /></el-icon>
90 - 导出日志
91 - </el-button>
92 </div> 51 </div>
93 - <div class="toolbar-right"> 52 + <div class="search-actions">
94 - <el-button @click="handleRefresh" :loading="loading"> 53 + <button @click="handleSearch" class="search-btn">🔍 搜索</button>
95 - <el-icon><Refresh /></el-icon> 54 + <button @click="handleReset" class="reset-btn">🔄 重置</button>
96 - 刷新
97 - </el-button>
98 </div> 55 </div>
99 </div> 56 </div>
100 - </el-card> 57 + </div>
58 +
59 + <!-- 操作按钮区域 -->
60 + <div class="action-section">
61 + <div class="action-buttons">
62 + <button
63 + @click="handleBatchDelete"
64 + :disabled="selectedLogs.length === 0"
65 + class="action-btn danger"
66 + >
67 + 🗑️ 批量删除
68 + </button>
69 + <button @click="handleCleanExpired" class="action-btn warning">
70 + 🧹 清理过期日志
71 + </button>
72 + <button @click="handleExport" class="action-btn info">
73 + 📋 导出日志
74 + </button>
75 + </div>
76 + </div>
101 77
102 - <!-- 日志列表 --> 78 + <!-- 数据表格 -->
103 - <el-card class="table-card" shadow="never"> 79 + <div class="table-section">
80 + <div class="table-header">
81 + <div class="table-controls">
82 + <button @click="handleTableSearch" class="control-btn" title="搜索">🔍</button>
83 + <button @click="handleTableRefresh" class="control-btn" title="刷新">🔄</button>
84 + <button @click="handleTableExport" class="control-btn" title="导出">📋</button>
85 + <button @click="handleTableViewToggle" class="control-btn" title="视图切换">⊞</button>
86 + </div>
87 + </div>
104 88
105 - <el-table 89 + <div class="table-container">
106 - v-loading="loading" 90 + <table class="data-table">
107 - :data="logList" 91 + <thead>
108 - @selection-change="handleSelectionChange" 92 + <tr>
109 - stripe 93 + <th class="checkbox-col">
110 - border 94 + <input
111 - style="width: 100%" 95 + type="checkbox"
112 - > 96 + :checked="selectAll"
113 - <el-table-column type="selection" width="50" align="center" /> 97 + @change="handleSelectAll"
114 - 98 + class="table-checkbox"
115 - <el-table-column prop="logId" label="日志ID" width="80" align="center" /> 99 + />
116 - 100 + </th>
117 - <el-table-column prop="logTypeText" label="日志类型" width="100" align="center"> 101 + <th>日志ID</th>
118 - <template #default="{ row }"> 102 + <th>日志类型</th>
119 - <el-tag :type="getLogTypeTagType(row.logType)" size="small"> 103 + <th>用户名</th>
120 - {{ row.logTypeText }} 104 + <th>操作模块</th>
121 - </el-tag> 105 + <th>操作内容</th>
122 - </template> 106 + <th>IP地址</th>
123 - </el-table-column> 107 + <th>操作时间</th>
124 - 108 + <th>操作结果</th>
125 - <el-table-column prop="username" label="用户名" width="100" align="center" /> 109 + <th>操作</th>
126 - 110 + </tr>
127 - <el-table-column prop="module" label="操作模块" width="120" align="center" /> 111 + </thead>
128 - 112 + <tbody>
129 - <el-table-column prop="operation" label="操作内容" min-width="180" show-overflow-tooltip /> 113 + <tr v-for="log in logList" :key="log.logId">
130 - 114 + <td class="checkbox-col">
131 - <el-table-column prop="ip" label="IP地址" width="120" align="center" /> 115 + <input
132 - 116 + type="checkbox"
133 - <el-table-column prop="logTime" label="操作时间" width="160" align="center"> 117 + :checked="selectedLogs.includes(log.logId)"
134 - <template #default="{ row }"> 118 + @change="handleSelectLog(log.logId)"
135 - {{ formatDate(row.logTime) }} 119 + class="table-checkbox"
136 - </template> 120 + />
137 - </el-table-column> 121 + </td>
138 - 122 + <td>{{ log.logId }}</td>
139 - <el-table-column prop="statusText" label="操作结果" width="100" align="center"> 123 + <td>
140 - <template #default="{ row }"> 124 + <span :class="['status-badge', getLogTypeClass(log.logType)]">
141 - <el-tag :type="row.status === 1 ? 'success' : 'danger'" size="small"> 125 + {{ log.logTypeText }}
142 - {{ row.statusText }} 126 + </span>
143 - </el-tag> 127 + </td>
144 - </template> 128 + <td>{{ log.username }}</td>
145 - </el-table-column> 129 + <td>{{ log.module }}</td>
146 - 130 + <td class="operation-cell" :title="log.operation">{{ log.operation }}</td>
147 - <el-table-column label="操作" width="140" fixed="right" align="center"> 131 + <td>{{ log.ip }}</td>
148 - <template #default="{ row }"> 132 + <td>{{ formatDate(log.logTime) }}</td>
149 - <el-button 133 + <td>
150 - type="primary" 134 + <span :class="['status-badge', log.status === 1 ? 'success' : 'error']">
151 - size="small" 135 + {{ log.statusText }}
152 - @click="handleViewDetail(row)" 136 + </span>
153 - link 137 + </td>
154 - > 138 + <td class="action-col">
155 - 详情 139 + <button @click="handleViewDetail(log)" class="action-btn primary" title="查看">👁️</button>
156 - </el-button> 140 + <button @click="handleDelete(log)" class="action-btn danger" title="删除">🗑️</button>
157 - <el-button 141 + </td>
158 - type="danger" 142 + </tr>
159 - size="small" 143 + </tbody>
160 - @click="handleDelete(row)" 144 + </table>
161 - link 145 + </div>
162 - > 146 + </div>
163 - 删除 147 +
164 - </el-button> 148 + <!-- 分页 -->
165 - </template> 149 + <div class="pagination-section">
166 - </el-table-column> 150 + <div class="pagination-info">
167 - </el-table> 151 + 共 {{ pagination.total }} 条记录,第 {{ pagination.pageNum }} / {{ Math.ceil(pagination.total / pagination.pageSize) }} 页
168 -
169 - <!-- 分页 -->
170 - <div class="pagination-container">
171 - <el-pagination
172 - v-model:current-page="pagination.pageNum"
173 - v-model:page-size="pagination.pageSize"
174 - :page-sizes="[10, 20, 50, 100]"
175 - :total="pagination.total"
176 - layout="total, sizes, prev, pager, next, jumper"
177 - @size-change="handleSizeChange"
178 - @current-change="handleCurrentChange"
179 - />
180 </div> 152 </div>
181 - </el-card> 153 + <div class="pagination-controls">
154 + <button
155 + @click="handlePrevPage"
156 + :disabled="pagination.pageNum <= 1"
157 + class="pagination-btn"
158 + >
159 + ← 上一页
160 + </button>
161 + <span class="page-info">{{ pagination.pageNum }} / {{ Math.ceil(pagination.total / pagination.pageSize) }}</span>
162 + <button
163 + @click="handleNextPage"
164 + :disabled="pagination.pageNum >= Math.ceil(pagination.total / pagination.pageSize)"
165 + class="pagination-btn"
166 + >
167 + 下一页 →
168 + </button>
169 + <select v-model="pagination.pageSize" @change="handleSizeChange($event)" class="page-size-select">
170 + <option value="10">10条/页</option>
171 + <option value="20">20条/页</option>
172 + <option value="50">50条/页</option>
173 + <option value="100">100条/页</option>
174 + </select>
175 + </div>
176 + </div>
182 177
183 <!-- 日志详情对话框 --> 178 <!-- 日志详情对话框 -->
184 - <el-dialog 179 + <div v-if="detailDialogVisible" class="dialog-overlay" @click="closeDetailDialog">
185 - v-model="detailDialogVisible" 180 + <div class="dialog-content" @click.stop>
186 - title="日志详情" 181 + <div class="dialog-header">
187 - width="800px" 182 + <h3>日志详情</h3>
188 - :close-on-click-modal="false" 183 + <button @click="closeDetailDialog" class="close-btn">×</button>
189 - > 184 + </div>
190 - <div v-if="logDetail" class="log-detail"> 185 + <div class="dialog-body">
191 - <el-descriptions :column="2" border> 186 + <div class="log-detail">
192 - <el-descriptions-item label="日志ID"> 187 + <div class="detail-grid">
193 - {{ logDetail.logId }} 188 + <div class="detail-item">
194 - </el-descriptions-item> 189 + <label>日志ID:</label>
195 - <el-descriptions-item label="日志类型"> 190 + <span>{{ logDetail?.logId }}</span>
196 - <el-tag :type="getLogTypeTagType(logDetail.logType)"> 191 + </div>
197 - {{ logDetail.logTypeText }} 192 + <div class="detail-item">
198 - </el-tag> 193 + <label>日志类型:</label>
199 - </el-descriptions-item> 194 + <span :class="['status-badge', getLogTypeClass(logDetail?.logType || '')]">
200 - <el-descriptions-item label="用户名"> 195 + {{ logDetail?.logTypeText }}
201 - {{ logDetail.username }} 196 + </span>
202 - </el-descriptions-item> 197 + </div>
203 - <el-descriptions-item label="用户ID"> 198 + <div class="detail-item">
204 - {{ logDetail.userId }} 199 + <label>用户ID:</label>
205 - </el-descriptions-item> 200 + <span>{{ logDetail?.userId }}</span>
206 - <el-descriptions-item label="操作模块"> 201 + </div>
207 - {{ logDetail.module }} 202 + <div class="detail-item">
208 - </el-descriptions-item> 203 + <label>用户名:</label>
209 - <el-descriptions-item label="操作内容"> 204 + <span>{{ logDetail?.username }}</span>
210 - {{ logDetail.operation }} 205 + </div>
211 - </el-descriptions-item> 206 + <div class="detail-item">
212 - <el-descriptions-item label="IP地址"> 207 + <label>操作模块:</label>
213 - {{ logDetail.ip }} 208 + <span>{{ logDetail?.module }}</span>
214 - </el-descriptions-item> 209 + </div>
215 - <el-descriptions-item label="操作时间"> 210 + <div class="detail-item">
216 - {{ formatDate(logDetail.logTime) }} 211 + <label>操作内容:</label>
217 - </el-descriptions-item> 212 + <span>{{ logDetail?.operation }}</span>
218 - <el-descriptions-item label="操作结果"> 213 + </div>
219 - <el-tag :type="logDetail.status === 1 ? 'success' : 'danger'"> 214 + <div class="detail-item">
220 - {{ logDetail.statusText }} 215 + <label>IP地址:</label>
221 - </el-tag> 216 + <span>{{ logDetail?.ip }}</span>
222 - </el-descriptions-item> 217 + </div>
223 - <el-descriptions-item label="错误信息" v-if="logDetail.errorMsg"> 218 + <div class="detail-item">
224 - {{ logDetail.errorMsg }} 219 + <label>操作时间:</label>
225 - </el-descriptions-item> 220 + <span>{{ formatDate(logDetail?.logTime || '') }}</span>
226 - </el-descriptions> 221 + </div>
227 - 222 + <div class="detail-item">
228 - <div v-if="logDetail.requestParam" class="request-param"> 223 + <label>操作结果:</label>
229 - <h4>请求参数</h4> 224 + <span :class="['status-badge', logDetail?.status === 1 ? 'success' : 'error']">
230 - <el-input 225 + {{ logDetail?.statusText }}
231 - v-model="logDetail.requestParam" 226 + </span>
232 - type="textarea" 227 + </div>
233 - :rows="6" 228 + <div v-if="logDetail?.errorMsg" class="detail-item full-width">
234 - readonly 229 + <label>错误信息:</label>
235 - placeholder="无请求参数" 230 + <span>{{ logDetail.errorMsg }}</span>
236 - /> 231 + </div>
232 + </div>
233 +
234 + <div v-if="logDetail?.requestParam" class="request-param">
235 + <h4>请求参数</h4>
236 + <textarea
237 + :value="logDetail.requestParam"
238 + readonly
239 + placeholder="无请求参数"
240 + class="request-textarea"
241 + rows="6"
242 + ></textarea>
243 + </div>
244 + </div>
245 + </div>
246 + <div class="dialog-footer">
247 + <button @click="closeDetailDialog" class="action-btn primary">关闭</button>
237 </div> 248 </div>
238 </div> 249 </div>
239 - 250 + </div>
240 - <template #footer>
241 - <el-button @click="detailDialogVisible = false">关闭</el-button>
242 - </template>
243 - </el-dialog>
244 251
245 <!-- 清理过期日志对话框 --> 252 <!-- 清理过期日志对话框 -->
246 - <el-dialog 253 + <div v-if="cleanDialogVisible" class="dialog-overlay" @click="closeCleanDialog">
247 - v-model="cleanDialogVisible" 254 + <div class="dialog-content" @click.stop>
248 - title="清理过期日志" 255 + <div class="dialog-header">
249 - width="400px" 256 + <h3>清理过期日志</h3>
250 - :close-on-click-modal="false" 257 + <button @click="closeCleanDialog" class="close-btn">×</button>
251 - > 258 + </div>
252 - <el-form :model="cleanForm" label-width="100px"> 259 + <div class="dialog-body">
253 - <el-form-item label="过期天数"> 260 + <form @submit.prevent="handleConfirmClean" class="clean-form">
254 - <el-input-number 261 + <div class="form-group">
255 - v-model="cleanForm.expireDays" 262 + <label class="required">过期天数:</label>
256 - :min="1" 263 + <input
257 - :max="365" 264 + v-model.number="cleanForm.expireDays"
258 - placeholder="请输入过期天数" 265 + type="number"
259 - style="width: 100%" 266 + min="1"
260 - /> 267 + max="365"
261 - </el-form-item> 268 + placeholder="请输入过期天数"
262 - <el-form-item> 269 + class="form-input"
263 - <el-text type="warning"> 270 + required
264 - 将删除指定天数之前的所有日志记录,此操作不可恢复! 271 + />
265 - </el-text> 272 + </div>
266 - </el-form-item> 273 + <div class="warning-text">
267 - </el-form> 274 + ⚠️ 将删除指定天数之前的所有日志记录,此操作不可恢复!
268 - 275 + </div>
269 - <template #footer> 276 + </form>
270 - <el-button @click="cleanDialogVisible = false">取消</el-button> 277 + </div>
271 - <el-button type="danger" @click="handleConfirmClean" :loading="cleanLoading"> 278 + <div class="dialog-footer">
272 - 确认清理 279 + <button @click="closeCleanDialog" class="action-btn">取消</button>
273 - </el-button> 280 + <button @click="handleConfirmClean" :disabled="cleanLoading" class="action-btn danger">
274 - </template> 281 + {{ cleanLoading ? '清理中...' : '确认清理' }}
275 - </el-dialog> 282 + </button>
283 + </div>
284 + </div>
285 + </div>
276 </div> 286 </div>
277 </template> 287 </template>
278 288
...@@ -286,12 +296,15 @@ import { formatDate } from '@/utils/index' ...@@ -286,12 +296,15 @@ import { formatDate } from '@/utils/index'
286 // 响应式数据 296 // 响应式数据
287 const loading = ref(false) 297 const loading = ref(false)
288 const logList = ref<Log[]>([]) 298 const logList = ref<Log[]>([])
289 -const selectedLogs = ref<Log[]>([]) 299 +const selectedLogs = ref<number[]>([])
290 const detailDialogVisible = ref(false) 300 const detailDialogVisible = ref(false)
291 const logDetail = ref<LogDetail | null>(null) 301 const logDetail = ref<LogDetail | null>(null)
292 const cleanDialogVisible = ref(false) 302 const cleanDialogVisible = ref(false)
293 const cleanLoading = ref(false) 303 const cleanLoading = ref(false)
294 const dateRange = ref<[string, string] | null>(null) 304 const dateRange = ref<[string, string] | null>(null)
305 +const dateRangeText = ref('')
306 +const showDatePicker = ref(false)
307 +const selectAll = ref(false)
295 308
296 // 搜索表单 309 // 搜索表单
297 const searchForm = reactive<LogSearchParams>({ 310 const searchForm = reactive<LogSearchParams>({
...@@ -337,6 +350,70 @@ const handleExport = () => { ...@@ -337,6 +350,70 @@ const handleExport = () => {
337 ElMessage.info('导出功能开发中...') 350 ElMessage.info('导出功能开发中...')
338 } 351 }
339 352
353 +// 全选/取消全选
354 +const handleSelectAll = () => {
355 + selectAll.value = !selectAll.value
356 + if (selectAll.value) {
357 + selectedLogs.value = logList.value.map(log => log.logId)
358 + } else {
359 + selectedLogs.value = []
360 + }
361 +}
362 +
363 +// 选择单个日志
364 +const handleSelectLog = (logId: number) => {
365 + const index = selectedLogs.value.indexOf(logId)
366 + if (index > -1) {
367 + selectedLogs.value.splice(index, 1)
368 + } else {
369 + selectedLogs.value.push(logId)
370 + }
371 + selectAll.value = selectedLogs.value.length === logList.value.length
372 +}
373 +
374 +// 获取日志类型样式类
375 +const getLogTypeClass = (logType: string) => {
376 + switch (logType) {
377 + case '0': return 'info'
378 + case '1': return 'warning'
379 + case '2': return 'success'
380 + default: return 'default'
381 + }
382 +}
383 +
384 +// 表格控制按钮
385 +const handleTableSearch = () => {
386 + handleSearch()
387 +}
388 +
389 +const handleTableRefresh = () => {
390 + handleRefresh()
391 +}
392 +
393 +const handleTableExport = () => {
394 + handleExport()
395 +}
396 +
397 +const handleTableViewToggle = () => {
398 + ElMessage.info('视图切换功能开发中...')
399 +}
400 +
401 +// 分页控制
402 +const handlePrevPage = () => {
403 + if (pagination.pageNum > 1) {
404 + pagination.pageNum--
405 + getLogList()
406 + }
407 +}
408 +
409 +const handleNextPage = () => {
410 + const totalPages = Math.ceil(pagination.total / pagination.pageSize)
411 + if (pagination.pageNum < totalPages) {
412 + pagination.pageNum++
413 + getLogList()
414 + }
415 +}
416 +
340 // 获取日志列表 417 // 获取日志列表
341 const getLogList = async () => { 418 const getLogList = async () => {
342 try { 419 try {
...@@ -402,8 +479,9 @@ const handleDateRangeChange = (value: [string, string] | null) => { ...@@ -402,8 +479,9 @@ const handleDateRangeChange = (value: [string, string] | null) => {
402 } 479 }
403 480
404 // 分页大小变化 481 // 分页大小变化
405 -const handleSizeChange = (size: number) => { 482 +const handleSizeChange = (event: Event) => {
406 - pagination.pageSize = size 483 + const target = event.target as HTMLSelectElement
484 + pagination.pageSize = parseInt(target.value)
407 pagination.pageNum = 1 485 pagination.pageNum = 1
408 getLogList() 486 getLogList()
409 } 487 }
...@@ -416,7 +494,7 @@ const handleCurrentChange = (page: number) => { ...@@ -416,7 +494,7 @@ const handleCurrentChange = (page: number) => {
416 494
417 // 选择变化 495 // 选择变化
418 const handleSelectionChange = (selection: Log[]) => { 496 const handleSelectionChange = (selection: Log[]) => {
419 - selectedLogs.value = selection 497 + selectedLogs.value = selection.map(log => log.logId)
420 } 498 }
421 499
422 // 查看详情 500 // 查看详情
...@@ -481,7 +559,7 @@ const handleBatchDelete = async () => { ...@@ -481,7 +559,7 @@ const handleBatchDelete = async () => {
481 } 559 }
482 ) 560 )
483 561
484 - const logIds = selectedLogs.value.map(log => log.logId) 562 + const logIds = selectedLogs.value
485 const response = await logApi.deleteLogs(logIds) 563 const response = await logApi.deleteLogs(logIds)
486 if (response.code === 200) { 564 if (response.code === 200) {
487 ElMessage.success('批量删除成功') 565 ElMessage.success('批量删除成功')
...@@ -502,6 +580,16 @@ const handleCleanExpired = () => { ...@@ -502,6 +580,16 @@ const handleCleanExpired = () => {
502 cleanDialogVisible.value = true 580 cleanDialogVisible.value = true
503 } 581 }
504 582
583 +// 关闭详情对话框
584 +const closeDetailDialog = () => {
585 + detailDialogVisible.value = false
586 +}
587 +
588 +// 关闭清理对话框
589 +const closeCleanDialog = () => {
590 + cleanDialogVisible.value = false
591 +}
592 +
505 // 确认清理 593 // 确认清理
506 const handleConfirmClean = async () => { 594 const handleConfirmClean = async () => {
507 if (!cleanForm.expireDays || cleanForm.expireDays <= 0) { 595 if (!cleanForm.expireDays || cleanForm.expireDays <= 0) {
...@@ -524,7 +612,7 @@ const handleConfirmClean = async () => { ...@@ -524,7 +612,7 @@ const handleConfirmClean = async () => {
524 const response = await logApi.cleanExpiredLogs(cleanForm.expireDays) 612 const response = await logApi.cleanExpiredLogs(cleanForm.expireDays)
525 if (response.code === 200) { 613 if (response.code === 200) {
526 ElMessage.success(response.data) 614 ElMessage.success(response.data)
527 - cleanDialogVisible.value = false 615 + closeCleanDialog()
528 getLogList() 616 getLogList()
529 } else { 617 } else {
530 ElMessage.error(response.message || '清理失败') 618 ElMessage.error(response.message || '清理失败')
...@@ -545,99 +633,505 @@ onMounted(() => { ...@@ -545,99 +633,505 @@ onMounted(() => {
545 }) 633 })
546 </script> 634 </script>
547 635
548 -<style scoped> 636 +<style scoped lang="scss">
549 .log-management { 637 .log-management {
550 - padding: 20px; 638 + background: #f5f5f5;
639 + min-height: 100vh;
551 } 640 }
552 641
553 -.page-header { 642 +// 搜索区域
554 - margin-bottom: 20px; 643 +.search-section {
644 + background: white;
645 + padding: 16px;
646 + border-bottom: 1px solid #e0e0e0;
555 } 647 }
556 648
557 -.page-header h2 { 649 +.search-row {
558 - margin: 0 0 8px 0; 650 + display: flex;
559 - color: #303133; 651 + gap: 16px;
560 - font-size: 20px; 652 + align-items: center;
653 + flex-wrap: wrap;
654 +}
655 +
656 +.search-item {
657 + display: flex;
658 + align-items: center;
659 + gap: 8px;
660 +}
661 +
662 +.search-item label {
663 + font-size: 12px;
664 + color: #333;
665 + white-space: nowrap;
666 +}
667 +
668 +.search-input, .search-select {
669 + padding: 6px 8px;
670 + border: 1px solid #d9d9d9;
671 + border-radius: 4px;
672 + font-size: 12px;
673 + width: 120px;
674 +}
675 +
676 +.search-input:focus, .search-select:focus {
677 + outline: none;
678 + border-color: #1890ff;
679 +}
680 +
681 +.search-actions {
682 + display: flex;
683 + gap: 8px;
684 +}
685 +
686 +.search-btn {
687 + background: #1890ff;
688 + color: white;
689 + border: none;
690 + padding: 4px 8px;
691 + border-radius: 3px;
692 + font-size: 11px;
693 + cursor: pointer;
694 +}
695 +
696 +.search-btn:hover {
697 + background: #40a9ff;
698 +}
699 +
700 +.reset-btn {
701 + background: #ff7875;
702 + color: white;
703 + border: none;
704 + padding: 4px 8px;
705 + border-radius: 3px;
706 + font-size: 11px;
707 + cursor: pointer;
708 + height: 24px;
709 + display: flex;
710 + align-items: center;
711 +}
712 +
713 +.reset-btn:hover {
714 + background: #ff9c99;
715 +}
716 +
717 +// 操作区域
718 +.action-section {
719 + background: white;
720 + padding: 12px 16px;
721 + border-bottom: 1px solid #e0e0e0;
722 +}
723 +
724 +.action-buttons {
725 + display: flex;
726 + gap: 6px;
727 +}
728 +
729 +.action-btn {
730 + padding: 4px 8px;
731 + border: none;
732 + border-radius: 3px;
733 + font-size: 11px;
734 + cursor: pointer;
735 + transition: all 0.2s;
736 +}
737 +
738 +.action-btn.primary {
739 + background: #1890ff;
740 + color: white;
741 +}
742 +
743 +.action-btn.primary:hover {
744 + background: #40a9ff;
745 +}
746 +
747 +.action-btn.danger {
748 + background: #ff4d4f;
749 + color: white;
750 +}
751 +
752 +.action-btn.danger:hover {
753 + background: #ff7875;
754 +}
755 +
756 +.action-btn.warning {
757 + background: #faad14;
758 + color: white;
759 +}
760 +
761 +.action-btn.warning:hover {
762 + background: #ffc53d;
763 +}
764 +
765 +.action-btn.info {
766 + background: #52c41a;
767 + color: white;
768 +}
769 +
770 +.action-btn.info:hover {
771 + background: #73d13d;
772 +}
773 +
774 +.action-btn:disabled {
775 + opacity: 0.5;
776 + cursor: not-allowed;
777 +}
778 +
779 +// 表格区域
780 +.table-section {
781 + background: white;
782 + margin: 16px;
783 + border-radius: 6px;
784 + overflow: hidden;
785 + box-shadow: 0 2px 8px rgba(0,0,0,0.1);
786 +}
787 +
788 +.table-header {
789 + padding: 8px 16px;
790 + background: #fafafa;
791 + border-bottom: 1px solid #e0e0e0;
792 + display: flex;
793 + justify-content: flex-end;
794 +}
795 +
796 +.table-controls {
797 + display: flex;
798 + gap: 4px;
799 +}
800 +
801 +.control-btn {
802 + background: white;
803 + border: 1px solid #d9d9d9;
804 + border-radius: 3px;
805 + padding: 4px 8px;
806 + font-size: 11px;
807 + cursor: pointer;
808 + transition: all 0.2s;
809 +}
810 +
811 +.control-btn:hover {
812 + background: #f0f8ff;
813 + border-color: #1890ff;
814 + color: #1890ff;
815 +}
816 +
817 +.table-container {
818 + overflow-x: auto;
819 +}
820 +
821 +.data-table {
822 + width: 100%;
823 + border-collapse: collapse;
824 +}
825 +
826 +.data-table th,
827 +.data-table td {
828 + padding: 8px 12px;
829 + text-align: left;
830 + border-bottom: 1px solid #f0f0f0;
831 + font-size: 12px;
832 +}
833 +
834 +.data-table th {
835 + background: #fafafa;
561 font-weight: 600; 836 font-weight: 600;
837 + color: #333;
838 + white-space: nowrap;
562 } 839 }
563 840
564 -.page-header p { 841 +.data-table tbody tr:hover {
565 - margin: 0; 842 + background: #f5f7fa;
566 - color: #909399; 843 +}
567 - font-size: 14px; 844 +
845 +.checkbox-col {
846 + width: 50px;
847 + text-align: center;
848 +}
849 +
850 +.table-checkbox {
851 + width: 14px;
852 + height: 14px;
853 + cursor: pointer;
854 +}
855 +
856 +.operation-cell {
857 + max-width: 200px;
858 + overflow: hidden;
859 + text-overflow: ellipsis;
860 + white-space: nowrap;
861 +}
862 +
863 +.action-col {
864 + width: 120px;
865 + text-align: center;
568 } 866 }
569 867
570 -.search-card { 868 +.action-col .action-btn {
571 - margin-bottom: 16px; 869 + padding: 2px 6px;
870 + margin-right: 4px;
871 + border: none;
872 + border-radius: 3px;
873 + font-size: 10px;
874 + cursor: pointer;
875 + display: inline-flex;
876 + align-items: center;
877 + gap: 2px;
572 } 878 }
573 879
574 -.search-form { 880 +// 状态标签
575 - margin-bottom: 0; 881 +.status-badge {
882 + padding: 2px 6px;
883 + border-radius: 3px;
884 + font-size: 10px;
576 } 885 }
577 886
578 -.toolbar-card { 887 +.status-badge.info {
579 - margin-bottom: 16px; 888 + background: #e6f7ff;
889 + color: #1890ff;
580 } 890 }
581 891
582 -.toolbar { 892 +.status-badge.warning {
893 + background: #fff7e6;
894 + color: #fa8c16;
895 +}
896 +
897 +.status-badge.success {
898 + background: #f6ffed;
899 + color: #52c41a;
900 +}
901 +
902 +.status-badge.error {
903 + background: #fff2f0;
904 + color: #ff4d4f;
905 +}
906 +
907 +.status-badge.default {
908 + background: #f5f5f5;
909 + color: #8c8c8c;
910 +}
911 +
912 +// 分页区域
913 +.pagination-section {
583 display: flex; 914 display: flex;
584 justify-content: space-between; 915 justify-content: space-between;
585 align-items: center; 916 align-items: center;
917 + padding: 8px 16px;
918 + background: #fafafa;
919 + border-top: 1px solid #e0e0e0;
586 } 920 }
587 921
588 -.toolbar-left, 922 +.pagination-info {
589 -.toolbar-right { 923 + font-size: 12px;
924 + color: #666;
925 +}
926 +
927 +.pagination-controls {
590 display: flex; 928 display: flex;
929 + align-items: center;
591 gap: 8px; 930 gap: 8px;
592 } 931 }
593 932
594 -.table-card { 933 +.pagination-btn {
595 - margin-bottom: 16px; 934 + padding: 4px 8px;
935 + border: 1px solid #d9d9d9;
936 + border-radius: 3px;
937 + background: white;
938 + cursor: pointer;
939 + font-size: 11px;
940 + transition: all 0.2s;
941 +}
942 +
943 +.pagination-btn:hover:not(:disabled) {
944 + background: #f0f8ff;
945 + border-color: #1890ff;
946 + color: #1890ff;
947 +}
948 +
949 +.pagination-btn:disabled {
950 + opacity: 0.5;
951 + cursor: not-allowed;
596 } 952 }
597 953
598 -.pagination-container { 954 +.page-info {
955 + font-size: 12px;
956 + color: #666;
957 + padding: 0 8px;
958 +}
959 +
960 +.page-size-select {
961 + padding: 4px 8px;
962 + border: 1px solid #d9d9d9;
963 + border-radius: 4px;
964 + font-size: 12px;
965 + background: white;
966 + cursor: pointer;
967 +}
968 +
969 +// 对话框样式
970 +.dialog-overlay {
971 + position: fixed;
972 + top: 0;
973 + left: 0;
974 + right: 0;
975 + bottom: 0;
976 + background: rgba(0, 0, 0, 0.5);
599 display: flex; 977 display: flex;
978 + align-items: center;
600 justify-content: center; 979 justify-content: center;
601 - margin-top: 20px; 980 + z-index: 1000;
981 +}
982 +
983 +.dialog-content {
984 + background: white;
985 + border-radius: 6px;
986 + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
987 + width: 90%;
988 + max-width: 600px;
989 + max-height: 80vh;
990 + overflow-y: auto;
991 +}
992 +
993 +.dialog-header {
994 + padding: 12px 16px;
995 + border-bottom: 1px solid #e0e0e0;
996 + display: flex;
997 + justify-content: space-between;
998 + align-items: center;
999 +}
1000 +
1001 +.dialog-header h3 {
1002 + margin: 0;
1003 + font-size: 14px;
1004 + color: #333;
1005 +}
1006 +
1007 +.close-btn {
1008 + background: none;
1009 + border: none;
1010 + font-size: 16px;
1011 + cursor: pointer;
1012 + color: #999;
1013 + padding: 0;
1014 + width: 20px;
1015 + height: 20px;
1016 + display: flex;
1017 + align-items: center;
1018 + justify-content: center;
1019 +}
1020 +
1021 +.close-btn:hover {
1022 + color: #666;
1023 +}
1024 +
1025 +.dialog-body {
1026 + padding: 16px;
1027 +}
1028 +
1029 +.dialog-footer {
1030 + padding: 12px 16px;
1031 + border-top: 1px solid #e0e0e0;
1032 + display: flex;
1033 + justify-content: flex-end;
1034 + gap: 6px;
1035 +}
1036 +
1037 +// 详情网格布局
1038 +.detail-grid {
1039 + display: grid;
1040 + grid-template-columns: 1fr 1fr;
1041 + gap: 12px;
1042 +}
1043 +
1044 +.detail-item {
1045 + display: flex;
1046 + flex-direction: column;
1047 + gap: 4px;
1048 +}
1049 +
1050 +.detail-item.full-width {
1051 + grid-column: 1 / -1;
1052 +}
1053 +
1054 +.detail-item label {
1055 + font-weight: 600;
1056 + color: #333;
1057 + font-size: 12px;
1058 +}
1059 +
1060 +.detail-item span {
1061 + color: #666;
1062 + font-size: 12px;
602 } 1063 }
603 1064
604 .log-detail { 1065 .log-detail {
605 - max-height: 600px; 1066 + max-height: 400px;
606 overflow-y: auto; 1067 overflow-y: auto;
607 } 1068 }
608 1069
609 .request-param { 1070 .request-param {
610 - margin-top: 20px; 1071 + margin-top: 16px;
611 } 1072 }
612 1073
613 .request-param h4 { 1074 .request-param h4 {
614 - margin: 0 0 10px 0; 1075 + margin: 0 0 8px 0;
615 - color: #303133; 1076 + color: #333;
616 - font-size: 14px; 1077 + font-size: 12px;
617 font-weight: 600; 1078 font-weight: 600;
618 } 1079 }
619 1080
620 -/* 表格优化 */ 1081 +.request-textarea {
621 -.el-table { 1082 + width: 100%;
622 - font-size: 14px; 1083 + padding: 6px 8px;
1084 + border: 1px solid #d9d9d9;
1085 + border-radius: 3px;
1086 + font-size: 11px;
1087 + font-family: monospace;
1088 + resize: vertical;
1089 + background: #f9f9f9;
623 } 1090 }
624 1091
625 -.el-table .cell { 1092 +// 表单样式
626 - padding: 8px 12px; 1093 +.clean-form {
627 - line-height: 1.5; 1094 + display: flex;
1095 + flex-direction: column;
1096 + gap: 12px;
628 } 1097 }
629 1098
630 -.el-table th.el-table__cell { 1099 +.form-group {
631 - background: #fafafa; 1100 + display: flex;
632 - color: #606266; 1101 + flex-direction: column;
1102 + gap: 4px;
1103 +}
1104 +
1105 +.form-group label {
633 font-weight: 600; 1106 font-weight: 600;
1107 + color: #333;
1108 + font-size: 12px;
1109 +}
1110 +
1111 +.form-group label.required::after {
1112 + content: ' *';
1113 + color: #ff4d4f;
1114 +}
1115 +
1116 +.form-input {
1117 + padding: 6px 8px;
1118 + border: 1px solid #d9d9d9;
1119 + border-radius: 3px;
1120 + font-size: 12px;
1121 + transition: border-color 0.2s;
634 } 1122 }
635 1123
636 -.el-table td.el-table__cell { 1124 +.form-input:focus {
637 - padding: 12px 0; 1125 + outline: none;
1126 + border-color: #1890ff;
638 } 1127 }
639 1128
640 -.el-table .el-button + .el-button { 1129 +.warning-text {
641 - margin-left: 8px; 1130 + padding: 8px;
1131 + background: #fff7e6;
1132 + border: 1px solid #ffd591;
1133 + border-radius: 3px;
1134 + color: #fa8c16;
1135 + font-size: 11px;
642 } 1136 }
643 </style> 1137 </style>
......