路由更新

This commit is contained in:
2025-10-16 18:03:46 +08:00
parent 1199cbc176
commit 0811af6d03
94 changed files with 9511 additions and 667 deletions

View File

@@ -0,0 +1,123 @@
<template>
<div class="login-logs">
<div class="filter-bar">
<el-input
v-model="searchKeyword"
placeholder="搜索用户名..."
style="width: 200px"
clearable
/>
<el-select v-model="loginStatus" placeholder="登录状态" style="width: 150px" clearable>
<el-option label="成功" value="success" />
<el-option label="失败" value="failed" />
</el-select>
<el-date-picker
v-model="dateRange"
type="daterange"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
/>
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button @click="handleExport">导出</el-button>
<el-button type="danger" @click="handleClear">清空日志</el-button>
</div>
<el-table :data="logs" style="width: 100%">
<el-table-column prop="username" label="用户名" width="120" />
<el-table-column prop="ipAddress" label="IP地址" width="140" />
<el-table-column prop="location" label="登录地点" width="150" />
<el-table-column prop="browser" label="浏览器" width="120" />
<el-table-column prop="os" label="操作系统" width="120" />
<el-table-column prop="status" label="状态" width="100">
<template #default="{ row }">
<el-tag :type="row.status === 'success' ? 'success' : 'danger'">
{{ row.status === 'success' ? '成功' : '失败' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="message" label="信息" min-width="150" />
<el-table-column prop="loginTime" label="登录时间" width="180" />
</el-table>
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { ElInput, ElSelect, ElOption, ElDatePicker, ElButton, ElTable, ElTableColumn, ElTag, ElPagination, ElMessage, ElMessageBox } from 'element-plus';
const searchKeyword = ref('');
const loginStatus = ref('');
const dateRange = ref<[Date, Date] | null>(null);
const currentPage = ref(1);
const pageSize = ref(10);
const total = ref(0);
const logs = ref<any[]>([]);
onMounted(() => {
loadLogs();
});
function loadLogs() {
// TODO: 加载登录日志
}
function handleSearch() {
currentPage.value = 1;
loadLogs();
}
function handleExport() {
// TODO: 导出日志
ElMessage.info('导出功能开发中');
}
async function handleClear() {
try {
await ElMessageBox.confirm('确定要清空所有登录日志吗?此操作不可恢复!', '警告', {
type: 'warning'
});
// TODO: 清空日志
ElMessage.success('日志已清空');
} catch {
// 取消操作
}
}
function handleSizeChange(val: number) {
pageSize.value = val;
loadLogs();
}
function handleCurrentChange(val: number) {
currentPage.value = val;
loadLogs();
}
</script>
<style lang="scss" scoped>
.login-logs {
padding: 20px;
}
.filter-bar {
display: flex;
gap: 16px;
margin-bottom: 20px;
align-items: center;
}
.el-table {
margin-bottom: 20px;
}
</style>

View File

@@ -0,0 +1,147 @@
<template>
<div class="operation-logs">
<div class="filter-bar">
<el-input
v-model="searchKeyword"
placeholder="搜索用户名或操作..."
style="width: 250px"
clearable
/>
<el-select v-model="operationType" placeholder="操作类型" style="width: 150px" clearable>
<el-option label="新增" value="create" />
<el-option label="修改" value="update" />
<el-option label="删除" value="delete" />
<el-option label="查询" value="read" />
</el-select>
<el-date-picker
v-model="dateRange"
type="daterange"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
/>
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button @click="handleExport">导出</el-button>
</div>
<el-table :data="logs" style="width: 100%">
<el-table-column prop="username" label="操作人" width="120" />
<el-table-column prop="module" label="操作模块" width="120" />
<el-table-column prop="operation" label="操作类型" width="100">
<template #default="{ row }">
<el-tag :type="getOperationType(row.operation)">
{{ getOperationText(row.operation) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="description" label="操作描述" min-width="200" />
<el-table-column prop="ipAddress" label="IP地址" width="140" />
<el-table-column prop="duration" label="耗时(ms)" width="100" />
<el-table-column prop="status" label="状态" width="100">
<template #default="{ row }">
<el-tag :type="row.status === 'success' ? 'success' : 'danger'">
{{ row.status === 'success' ? '成功' : '失败' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="operationTime" label="操作时间" width="180" />
<el-table-column label="操作" width="100" fixed="right">
<template #default="{ row }">
<el-button size="small" @click="viewDetail(row)">详情</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { ElInput, ElSelect, ElOption, ElDatePicker, ElButton, ElTable, ElTableColumn, ElTag, ElPagination, ElMessage } from 'element-plus';
const searchKeyword = ref('');
const operationType = ref('');
const dateRange = ref<[Date, Date] | null>(null);
const currentPage = ref(1);
const pageSize = ref(10);
const total = ref(0);
const logs = ref<any[]>([]);
onMounted(() => {
loadLogs();
});
function loadLogs() {
// TODO: 加载操作日志
}
function handleSearch() {
currentPage.value = 1;
loadLogs();
}
function handleExport() {
// TODO: 导出日志
ElMessage.info('导出功能开发中');
}
function getOperationType(type: string) {
const typeMap: Record<string, any> = {
'create': 'success',
'update': 'warning',
'delete': 'danger',
'read': 'info'
};
return typeMap[type] || 'info';
}
function getOperationText(type: string) {
const textMap: Record<string, string> = {
'create': '新增',
'update': '修改',
'delete': '删除',
'read': '查询'
};
return textMap[type] || type;
}
function viewDetail(row: any) {
// TODO: 查看操作详情
}
function handleSizeChange(val: number) {
pageSize.value = val;
loadLogs();
}
function handleCurrentChange(val: number) {
currentPage.value = val;
loadLogs();
}
</script>
<style lang="scss" scoped>
.operation-logs {
padding: 20px;
}
.filter-bar {
display: flex;
gap: 16px;
margin-bottom: 20px;
align-items: center;
}
.el-table {
margin-bottom: 20px;
}
</style>

View File

@@ -0,0 +1,190 @@
<template>
<div class="system-config">
<el-form :model="configForm" label-width="150px" class="config-form">
<el-divider content-position="left">基本设置</el-divider>
<el-form-item label="系统名称">
<el-input v-model="configForm.systemName" />
</el-form-item>
<el-form-item label="系统Logo">
<el-upload
class="logo-uploader"
action="#"
:show-file-list="false"
:before-upload="beforeLogoUpload"
>
<img v-if="configForm.logo" :src="configForm.logo" class="logo-preview" />
<el-icon v-else class="logo-uploader-icon">+</el-icon>
</el-upload>
</el-form-item>
<el-form-item label="版权信息">
<el-input v-model="configForm.copyright" />
</el-form-item>
<el-divider content-position="left">安全设置</el-divider>
<el-form-item label="启用验证码">
<el-switch v-model="configForm.enableCaptcha" />
</el-form-item>
<el-form-item label="密码最小长度">
<el-input-number v-model="configForm.minPasswordLength" :min="6" :max="20" />
</el-form-item>
<el-form-item label="会话超时(分钟)">
<el-input-number v-model="configForm.sessionTimeout" :min="5" :max="1440" />
</el-form-item>
<el-form-item label="登录失败锁定">
<el-switch v-model="configForm.enableLoginLock" />
</el-form-item>
<el-form-item label="锁定阈值(次)" v-if="configForm.enableLoginLock">
<el-input-number v-model="configForm.loginLockThreshold" :min="3" :max="10" />
</el-form-item>
<el-divider content-position="left">功能设置</el-divider>
<el-form-item label="启用用户注册">
<el-switch v-model="configForm.enableRegister" />
</el-form-item>
<el-form-item label="启用评论功能">
<el-switch v-model="configForm.enableComment" />
</el-form-item>
<el-form-item label="启用文件上传">
<el-switch v-model="configForm.enableFileUpload" />
</el-form-item>
<el-form-item label="文件上传大小限制(MB)">
<el-input-number v-model="configForm.maxFileSize" :min="1" :max="100" />
</el-form-item>
<el-divider content-position="left">邮件设置</el-divider>
<el-form-item label="启用邮件通知">
<el-switch v-model="configForm.enableEmail" />
</el-form-item>
<el-form-item label="SMTP服务器" v-if="configForm.enableEmail">
<el-input v-model="configForm.smtpHost" />
</el-form-item>
<el-form-item label="SMTP端口" v-if="configForm.enableEmail">
<el-input-number v-model="configForm.smtpPort" :min="1" :max="65535" />
</el-form-item>
<el-form-item label="发件人邮箱" v-if="configForm.enableEmail">
<el-input v-model="configForm.senderEmail" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSave">保存配置</el-button>
<el-button @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { ElForm, ElFormItem, ElInput, ElInputNumber, ElSwitch, ElButton, ElDivider, ElUpload, ElIcon, ElMessage } from 'element-plus';
const configForm = ref({
systemName: '红色思政学习平台',
logo: '',
copyright: 'Copyright ©红色思政智能体平台',
enableCaptcha: false,
minPasswordLength: 6,
sessionTimeout: 30,
enableLoginLock: true,
loginLockThreshold: 5,
enableRegister: true,
enableComment: true,
enableFileUpload: true,
maxFileSize: 10,
enableEmail: false,
smtpHost: '',
smtpPort: 587,
senderEmail: ''
});
onMounted(() => {
loadConfig();
});
function loadConfig() {
// TODO: 加载系统配置
}
function beforeLogoUpload(file: File) {
const isImage = file.type.startsWith('image/');
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isImage) {
ElMessage.error('只能上传图片文件');
return false;
}
if (!isLt2M) {
ElMessage.error('图片大小不能超过 2MB');
return false;
}
return true;
}
function handleSave() {
// TODO: 保存配置
ElMessage.success('配置保存成功');
}
function handleReset() {
// TODO: 重置配置
loadConfig();
}
</script>
<style lang="scss" scoped>
.system-config {
padding: 20px;
max-width: 800px;
}
.config-form {
padding: 20px 0;
}
.logo-uploader {
:deep(.el-upload) {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: all 0.3s;
&:hover {
border-color: #C62828;
}
}
}
.logo-preview {
width: 178px;
height: 178px;
display: block;
object-fit: contain;
}
.logo-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
text-align: center;
line-height: 178px;
}
</style>

View File

@@ -0,0 +1,41 @@
<template>
<div class="system-logs">
<h1 class="page-title">系统日志</h1>
<el-tabs v-model="activeTab">
<el-tab-pane label="登录日志" name="login">
<LoginLogs />
</el-tab-pane>
<el-tab-pane label="操作日志" name="operation">
<OperationLogs />
</el-tab-pane>
<el-tab-pane label="系统配置" name="config">
<SystemConfig />
</el-tab-pane>
</el-tabs>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { ElTabs, ElTabPane } from 'element-plus';
import LoginLogs from './components/LoginLogs.vue';
import OperationLogs from './components/OperationLogs.vue';
import SystemConfig from './components/SystemConfig.vue';
const activeTab = ref('login');
</script>
<style lang="scss" scoped>
.system-logs {
padding: 20px;
}
.page-title {
font-size: 28px;
font-weight: 600;
color: #141F38;
margin-bottom: 24px;
}
</style>