【新增】系统设置

This commit is contained in:
2025-10-31 17:36:23 +08:00
parent 1391239193
commit 5f8bc1af55
7 changed files with 775 additions and 0 deletions

View File

@@ -0,0 +1,31 @@
package com.corewing.app.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.corewing.app.common.base.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
@TableName("sys_option")
public class SysOption extends BaseEntity {
/**
* 主键
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 键名
*/
private String key;
/**
* 键值
*/
private String value;
}

View File

@@ -0,0 +1,9 @@
package com.corewing.app.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.corewing.app.entity.SysOption;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SysOptionMapper extends BaseMapper<SysOption> {
}

View File

@@ -0,0 +1,19 @@
package com.corewing.app.modules.admin.sys;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* 系统设置
*/
@Controller
@RequestMapping("/sys/setting")
public class SysSettingController {
@GetMapping
public String index() {
return "admin/sys/setting";
}
}

View File

@@ -0,0 +1,18 @@
package com.corewing.app.modules.directive;
import com.corewing.app.service.SysOptionService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service("option")
public class SysOptionDirective {
@Resource
private SysOptionService sysOptionService;
public String getValue(String key) {
return sysOptionService.getValueByKey(key);
}
}

View File

@@ -0,0 +1,10 @@
package com.corewing.app.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.corewing.app.entity.SysOption;
public interface SysOptionService extends IService<SysOption> {
String getValueByKey(String key);
}

View File

@@ -0,0 +1,19 @@
package com.corewing.app.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.corewing.app.entity.SysOption;
import com.corewing.app.mapper.SysOptionMapper;
import com.corewing.app.service.SysOptionService;
import org.springframework.stereotype.Service;
@Service
public class SysOptionServiceImpl extends ServiceImpl<SysOptionMapper, SysOption> implements SysOptionService {
@Override
public String getValueByKey(String key) {
LambdaQueryWrapper<SysOption> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SysOption::getKey, key);
return getOne(queryWrapper).getValue();
}
}

View File

@@ -0,0 +1,669 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>系统设置</title>
<!-- 外部引入库文件 -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
<!-- 外部引入自定义样式 -->
<style>
body {
background-color: #f8f9fa;
color: #333;
font-size: 14px;
line-height: 1.5;
}
.main-container {
margin: 0 auto;
padding: 14px;
}
.settings-tabs {
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
overflow: hidden;
}
.nav-tabs {
border-bottom: 1px solid #e9ecef;
background-color: #f8f9fa;
}
.nav-tabs .nav-item .nav-link {
color: #6c757d;
border: none;
padding: 12px 20px;
font-weight: 500;
border-radius: 0;
}
.nav-tabs .nav-item .nav-link.active {
color: #0d6efd;
background-color: #fff;
border-bottom: 3px solid #0d6efd;
}
.nav-tabs .nav-item .nav-link:hover {
color: #0d6efd;
background-color: rgba(13, 110, 253, 0.05);
}
.tab-content {
padding: 25px;
}
.settings-section {
margin-bottom: 30px;
}
.settings-section h3 {
font-size: 18px;
margin-bottom: 15px;
color: #212529;
padding-bottom: 8px;
border-bottom: 1px solid #e9ecef;
}
.form-group {
margin-bottom: 20px;
}
.form-label {
font-weight: 500;
margin-bottom: 8px;
color: #495057;
}
.form-text {
font-size: 13px;
color: #6c757d;
}
.settings-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
padding-top: 20px;
margin-top: 20px;
border-top: 1px solid #e9ecef;
}
.btn {
padding: 8px 20px;
font-size: 14px;
border-radius: 6px;
}
.switch-label {
display: flex;
align-items: center;
gap: 10px;
cursor: pointer;
}
.switch-label .form-text {
margin-bottom: 0;
}
.profile-picture {
display: flex;
align-items: center;
gap: 20px;
margin-bottom: 20px;
}
.profile-picture img {
width: 100px;
height: 100px;
border-radius: 50%;
object-fit: cover;
border: 2px solid #e9ecef;
}
.upload-btn {
display: inline-flex;
align-items: center;
gap: 5px;
}
.language-selector, .theme-selector {
display: flex;
flex-wrap: wrap;
gap: 15px;
margin-top: 10px;
}
.language-option, .theme-option {
display: flex;
align-items: center;
gap: 5px;
}
.alert-message {
position: fixed;
top: 20px;
right: 20px;
z-index: 1050;
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@media (max-width: 768px) {
.nav-tabs {
flex-wrap: wrap;
}
.profile-picture {
flex-direction: column;
align-items: flex-start;
}
.settings-actions {
flex-direction: column;
}
.settings-actions .btn {
width: 100%;
}
}
</style>
</head>
<body id="app">
<div class="main-container">
<!-- 设置选项卡 -->
<div class="settings-tabs">
<ul class="nav nav-tabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#basic-settings" type="button" role="tab">
<i class="bi bi-person me-1"></i>基本设置
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#security-settings" type="button" role="tab">
<i class="bi bi-shield me-1"></i>安全设置
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#notification-settings" type="button" role="tab">
<i class="bi bi-bell me-1"></i>通知设置
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#system-preferences" type="button" role="tab">
<i class="bi bi-sliders me-1"></i>系统偏好
</button>
</li>
</ul>
<!-- 选项卡内容 -->
<div class="tab-content">
<!-- 基本设置 -->
<div class="tab-pane fade show active" id="basic-settings" role="tabpanel">
<div class="settings-section">
<h3><i class="bi bi-person-circle me-2"></i>个人资料</h3>
<div class="profile-picture">
<img src="https://picsum.photos/id/1005/200/200" alt="用户头像">
<div>
<button class="btn btn-outline-primary upload-btn">
<i class="bi bi-upload"></i> 更换头像
</button>
<p class="form-text mt-2">支持 JPG、PNG 格式,建议尺寸 200x200px</p>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label class="form-label" for="fullname">姓名</label>
<input type="text" class="form-control" id="fullname" v-model="profile.fullname" placeholder="请输入您的姓名">
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label class="form-label" for="username">用户名</label>
<input type="text" class="form-control" id="username" v-model="profile.username" placeholder="请输入用户名">
<div class="form-text">用户名用于登录,不可修改</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label class="form-label" for="email">电子邮箱</label>
<input type="email" class="form-control" id="email" v-model="profile.email" placeholder="请输入电子邮箱">
<div class="form-text">用于接收系统通知和密码重置</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label class="form-label" for="phone">手机号码</label>
<input type="tel" class="form-control" id="phone" v-model="profile.phone" placeholder="请输入手机号码">
</div>
</div>
</div>
<div class="form-group">
<label class="form-label" for="bio">个人简介</label>
<textarea class="form-control" id="bio" v-model="profile.bio" rows="3" placeholder="请输入个人简介"></textarea>
</div>
</div>
<div class="settings-actions">
<button class="btn btn-secondary" @click="resetForm('basic')">取消</button>
<button class="btn btn-primary" @click="saveSettings('basic')">保存设置</button>
</div>
</div>
<!-- 安全设置 -->
<div class="tab-pane fade" id="security-settings" role="tabpanel">
<div class="settings-section">
<h3><i class="bi bi-lock me-2"></i>密码管理</h3>
<div class="form-group">
<label class="form-label" for="current-password">当前密码</label>
<input type="password" class="form-control" id="current-password" v-model="security.currentPassword" placeholder="请输入当前密码">
</div>
<div class="form-group">
<label class="form-label" for="new-password">新密码</label>
<input type="password" class="form-control" id="new-password" v-model="security.newPassword" placeholder="请输入新密码">
<div class="form-text">密码长度至少8位包含字母和数字</div>
</div>
<div class="form-group">
<label class="form-label" for="confirm-password">确认新密码</label>
<input type="password" class="form-control" id="confirm-password" v-model="security.confirmPassword" placeholder="请再次输入新密码">
</div>
</div>
<div class="settings-section">
<h3><i class="bi bi-shield-lock me-2"></i>登录安全</h3>
<div class="form-group">
<div class="form-check form-switch">
<label class="switch-label">
<input class="form-check-input" type="checkbox" v-model="security.twoFactorAuth">
启用两步验证
<span class="form-text">启用后,登录时需要额外验证,提高账号安全性</span>
</label>
</div>
</div>
<div class="form-group">
<div class="form-check form-switch">
<label class="switch-label">
<input class="form-check-input" type="checkbox" v-model="security.loginAlert">
登录提醒
<span class="form-text">异地登录或新设备登录时发送提醒</span>
</label>
</div>
</div>
<div class="form-group">
<label class="form-label">最近登录记录</label>
<table class="table table-sm">
<thead>
<tr>
<th>时间</th>
<th>IP地址</th>
<th>设备</th>
<th>状态</th>
</tr>
</thead>
<tbody>
<tr v-for="log in loginLogs" :key="log.id">
<td>{{ log.time }}</td>
<td>{{ log.ip }}</td>
<td>{{ log.device }}</td>
<td><span class="badge bg-success">正常</span></td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="settings-actions">
<button class="btn btn-secondary" @click="resetForm('security')">取消</button>
<button class="btn btn-primary" @click="saveSettings('security')">保存设置</button>
</div>
</div>
<!-- 通知设置 -->
<div class="tab-pane fade" id="notification-settings" role="tabpanel">
<div class="settings-section">
<h3><i class="bi bi-envelope me-2"></i>通知方式</h3>
<div class="form-group">
<div class="form-check form-switch">
<label class="switch-label">
<input class="form-check-input" type="checkbox" v-model="notifications.email">
邮件通知
<span class="form-text">通过电子邮件接收通知</span>
</label>
</div>
</div>
<div class="form-group">
<div class="form-check form-switch">
<label class="switch-label">
<input class="form-check-input" type="checkbox" v-model="notifications.sms">
短信通知
<span class="form-text">通过手机短信接收通知</span>
</label>
</div>
</div>
<div class="form-group">
<div class="form-check form-switch">
<label class="switch-label">
<input class="form-check-input" type="checkbox" v-model="notifications.inApp">
应用内通知
<span class="form-text">在系统内接收通知提醒</span>
</label>
</div>
</div>
</div>
<div class="settings-section">
<h3><i class="bi bi-bell-ring me-2"></i>通知类型</h3>
<div class="form-group">
<div class="form-check form-switch">
<label class="switch-label">
<input class="form-check-input" type="checkbox" v-model="notifications.types.system">
系统通知
<span class="form-text">接收系统更新、维护等重要通知</span>
</label>
</div>
</div>
<div class="form-group">
<div class="form-check form-switch">
<label class="switch-label">
<input class="form-check-input" type="checkbox" v-model="notifications.types.task">
任务通知
<span class="form-text">接收任务分配、截止提醒等通知</span>
</label>
</div>
</div>
<div class="form-group">
<div class="form-check form-switch">
<label class="switch-label">
<input class="form-check-input" type="checkbox" v-model="notifications.types.message">
消息通知
<span class="form-text">接收其他用户发送的消息通知</span>
</label>
</div>
</div>
<div class="form-group">
<div class="form-check form-switch">
<label class="switch-label">
<input class="form-check-input" type="checkbox" v-model="notifications.types.marketing">
营销通知
<span class="form-text">接收产品推广、活动等营销信息</span>
</label>
</div>
</div>
</div>
<div class="settings-actions">
<button class="btn btn-secondary" @click="resetForm('notifications')">取消</button>
<button class="btn btn-primary" @click="saveSettings('notifications')">保存设置</button>
</div>
</div>
<!-- 系统偏好 -->
<div class="tab-pane fade" id="system-preferences" role="tabpanel">
<div class="settings-section">
<h3><i class="bi bi-globe me-2"></i>语言设置</h3>
<div class="form-group">
<label class="form-label">界面语言</label>
<div class="language-selector">
<div class="language-option">
<input class="form-check-input" type="radio" name="language" id="zh-CN" v-model="preferences.language" value="zh-CN">
<label class="form-check-label" for="zh-CN">简体中文</label>
</div>
<div class="language-option">
<input class="form-check-input" type="radio" name="language" id="en-US" v-model="preferences.language" value="en-US">
<label class="form-check-label" for="en-US">English (US)</label>
</div>
<div class="language-option">
<input class="form-check-input" type="radio" name="language" id="ja-JP" v-model="preferences.language" value="ja-JP">
<label class="form-check-label" for="ja-JP">日本語</label>
</div>
</div>
</div>
<div class="form-group">
<div class="form-check form-switch">
<label class="switch-label">
<input class="form-check-input" type="checkbox" v-model="preferences.autoTranslate">
自动翻译
<span class="form-text">自动翻译系统内容为所选语言</span>
</label>
</div>
</div>
</div>
<div class="settings-section">
<h3><i class="bi bi-paint me-2"></i>主题设置</h3>
<div class="form-group">
<label class="form-label">界面主题</label>
<div class="theme-selector">
<div class="theme-option">
<input class="form-check-input" type="radio" name="theme" id="light-theme" v-model="preferences.theme" value="light">
<label class="form-check-label" for="light-theme">浅色主题</label>
</div>
<div class="theme-option">
<input class="form-check-input" type="radio" name="theme" id="dark-theme" v-model="preferences.theme" value="dark">
<label class="form-check-label" for="dark-theme">深色主题</label>
</div>
<div class="theme-option">
<input class="form-check-input" type="radio" name="theme" id="auto-theme" v-model="preferences.theme" value="auto">
<label class="form-check-label" for="auto-theme">跟随系统</label>
</div>
</div>
</div>
</div>
<div class="settings-section">
<h3><i class="bi bi-clock me-2"></i>时间与日期</h3>
<div class="form-group">
<label class="form-label" for="timezone">时区</label>
<select class="form-select" id="timezone" v-model="preferences.timezone">
<option value="Asia/Shanghai">Asia/Shanghai (GMT+8)</option>
<option value="UTC">UTC (GMT+0)</option>
<option value="America/New_York">America/New_York (GMT-5)</option>
<option value="Europe/London">Europe/London (GMT+0)</option>
</select>
</div>
<div class="form-group">
<label class="form-label" for="date-format">日期格式</label>
<select class="form-select" id="date-format" v-model="preferences.dateFormat">
<option value="yyyy-MM-dd">2023-12-31</option>
<option value="dd/MM/yyyy">31/12/2023</option>
<option value="MM/dd/yyyy">12/31/2023</option>
</select>
</div>
</div>
<div class="settings-actions">
<button class="btn btn-secondary" @click="resetForm('preferences')">取消</button>
<button class="btn btn-primary" @click="saveSettings('preferences')">保存设置</button>
</div>
</div>
</div>
</div>
<!-- 成功提示框 -->
<div v-if="showSuccessAlert" class="alert-message">
<div class="alert alert-success alert-dismissible fade show" role="alert">
<strong>设置已保存!</strong> 您的更改已成功应用。
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close" @click="showSuccessAlert = false"></button>
</div>
</div>
</div>
<!-- 外部引入库文件 -->
<script src="https://cdn.jsdelivr.net/npm/vue@3.3.4/dist/vue.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios@1.4.0/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<!-- 外部引入模拟请求文件 -->
<script src="js/axiosRequest.js"></script>
<!-- 页面核心逻辑 -->
<script>
const {createApp} = Vue;
createApp({
data() {
return {
// 个人资料设置
profile: {
fullname: '张三',
username: 'zhangsan',
email: 'zhangsan@example.com',
phone: '13800138000',
bio: '系统管理员,负责系统日常维护和管理工作'
},
// 安全设置
security: {
currentPassword: '',
newPassword: '',
confirmPassword: '',
twoFactorAuth: false,
loginAlert: true
},
// 通知设置
notifications: {
email: true,
sms: false,
inApp: true,
types: {
system: true,
task: true,
message: true,
marketing: false
}
},
// 系统偏好设置
preferences: {
language: 'zh-CN',
autoTranslate: false,
theme: 'light',
timezone: 'Asia/Shanghai',
dateFormat: 'yyyy-MM-dd'
},
// 登录日志
loginLogs: [
{ id: 1, time: '2023-11-01 09:30:25', ip: '192.168.1.1', device: 'Chrome / Windows 10' },
{ id: 2, time: '2023-10-29 15:45:12', ip: '192.168.1.1', device: 'Safari / macOS' },
{ id: 3, time: '2023-10-28 20:12:36', ip: '123.123.123.123', device: 'Mobile Safari / iOS' }
],
// 保存原始数据用于重置
originalData: {},
// 成功提示框显示状态
showSuccessAlert: false
}
},
computed: {
},
methods: {
// 保存设置
async saveSettings(type) {
// 模拟表单验证
if (type === 'security' && this.security.newPassword) {
if (this.security.newPassword !== this.security.confirmPassword) {
alert('新密码和确认密码不一致');
return;
}
if (this.security.newPassword.length < 8) {
alert('密码长度至少8位');
return;
}
}
// 模拟API请求保存设置
try {
const response = await request.post(`/system/settings/${type}`, this[type]);
if (response.code === 200) {
// 显示成功提示
this.showSuccessAlert = true;
// 3秒后自动关闭提示
setTimeout(() => {
this.showSuccessAlert = false;
}, 3000);
// 更新原始数据
this.originalData[type] = JSON.parse(JSON.stringify(this[type]));
}
} catch (error) {
console.error('保存设置失败:', error);
alert('保存设置失败,请重试');
}
},
// 重置表单
resetForm(type) {
if (this.originalData[type]) {
this[type] = JSON.parse(JSON.stringify(this.originalData[type]));
}
},
// 获取设置数据
async fetchData() {
try {
// 模拟获取设置数据
const response = await request.get('/system/settings');
if (response.code === 200) {
const data = response.data;
// 初始化设置数据
if (data.profile) this.profile = data.profile;
if (data.security) this.security = data.security;
if (data.notifications) this.notifications = data.notifications;
if (data.preferences) this.preferences = data.preferences;
// 保存原始数据用于重置
this.originalData = {
profile: JSON.parse(JSON.stringify(this.profile)),
security: JSON.parse(JSON.stringify(this.security)),
notifications: JSON.parse(JSON.stringify(this.notifications)),
preferences: JSON.parse(JSON.stringify(this.preferences))
};
}
} catch (error) {
console.error('获取设置数据失败:', error);
}
}
},
mounted() {
// 页面加载完成后初始化数据
this.fetchData();
}
}).mount('#app');
</script>
</body>
</html>