first commit

This commit is contained in:
2025-07-14 14:55:25 +08:00
commit ace33d202f
473 changed files with 117431 additions and 0 deletions
+268
View File
@@ -0,0 +1,268 @@
<div class="avatar-section">
<h2 class="section-title">修改头像</h2>
<div class="avatar-upload-container">
<div class="current-avatar">
<img src="{$user.avatar|default='/static/images/avatar.png'}" alt="当前头像" id="currentAvatar">
<p class="avatar-tip">当前头像</p>
</div>
<div class="upload-area" id="uploadArea">
<i class="layui-icon layui-icon-upload"></i>
<p>点击或拖拽图片到此处上传</p>
<p class="upload-tip">支持 jpg、png、gif 格式,大小不超过 2MB</p>
<input type="file" id="avatarFile" accept="image/*" style="display: none;">
</div>
</div>
<div class="avatar-preview" style="display: none;">
<h3>预览</h3>
<div class="preview-container">
<img src="" alt="预览图" id="previewImage">
</div>
<div class="preview-actions">
<button class="layui-btn" id="confirmUpload">确认上传</button>
<button class="layui-btn layui-btn-primary" id="cancelUpload">取消</button>
</div>
</div>
</div>
<style>
.avatar-section {
max-width: 800px;
margin: 0 auto;
}
.section-title {
font-size: 20px;
font-weight: 600;
color: #333;
margin-bottom: 24px;
padding-bottom: 16px;
border-bottom: 1px solid #f0f0f0;
}
.avatar-upload-container {
display: flex;
gap: 40px;
margin-bottom: 32px;
}
.current-avatar {
text-align: center;
}
.current-avatar img {
width: 160px;
height: 160px;
border-radius: 50%;
object-fit: cover;
border: 3px solid #f5f5f5;
}
.avatar-tip {
margin-top: 12px;
color: #666;
}
.upload-area {
flex: 1;
border: 2px dashed #d9d9d9;
border-radius: 8px;
padding: 40px;
text-align: center;
cursor: pointer;
transition: all 0.3s;
}
.upload-area:hover {
border-color: #1677ff;
}
.upload-area .layui-icon {
font-size: 48px;
color: #999;
margin-bottom: 16px;
}
.upload-area p {
margin: 8px 0;
color: #666;
}
.upload-tip {
font-size: 12px;
color: #999;
}
.avatar-preview {
margin-top: 32px;
padding-top: 32px;
border-top: 1px solid #f0f0f0;
}
.avatar-preview h3 {
font-size: 16px;
color: #333;
margin-bottom: 16px;
}
.preview-container {
text-align: center;
margin-bottom: 24px;
}
.preview-container img {
max-width: 200px;
max-height: 200px;
border-radius: 8px;
}
.preview-actions {
text-align: center;
}
.preview-actions .layui-btn {
margin: 0 8px;
}
@media (max-width: 768px) {
.avatar-upload-container {
flex-direction: column;
gap: 24px;
}
.current-avatar img {
width: 120px;
height: 120px;
}
.upload-area {
padding: 24px;
}
}
</style>
<script>
layui.use(['upload', 'layer'], function () {
var upload = layui.upload;
var layer = layui.layer;
// 点击上传区域触发文件选择
document.getElementById('uploadArea').addEventListener('click', function () {
document.getElementById('avatarFile').click();
});
// 处理文件选择
document.getElementById('avatarFile').addEventListener('change', function (e) {
var file = e.target.files[0];
if (!file) return;
// 检查文件类型
if (!['image/jpeg', 'image/png', 'image/gif', 'image/webp'].includes(file.type)) {
layer.msg('请上传 jpg、png、webp 或 gif 格式的图片');
return;
}
// 检查文件大小
if (file.size > 2 * 1024 * 1024) {
layer.msg('图片大小不能超过 2MB');
return;
}
// 预览图片
var reader = new FileReader();
reader.onload = function (e) {
document.getElementById('previewImage').src = e.target.result;
document.querySelector('.avatar-preview').style.display = 'block';
};
reader.readAsDataURL(file);
});
// 确认上传
document.getElementById('confirmUpload').addEventListener('click', function () {
var file = document.getElementById('avatarFile').files[0];
if (!file) return;
var formData = new FormData();
formData.append('avatar', file);
// 显示上传中
var loadIndex = layer.load(2);
// 发送上传请求
fetch('/index/user/update_avatar', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
layer.close(loadIndex);
if (data.code === 0) {
// 更新cookie中的头像
document.cookie = "user_avatar=" + data.data.url + "; path=/";
// 更新localStorage中的头像
localStorage.setItem('user_avatar', data.data.url);
layer.msg('头像上传成功', {
icon: 1,
time: 1000
}, function () {
// 更新当前头像显示
document.getElementById('currentAvatar').src = data.data.url;
// 隐藏预览区域
document.querySelector('.avatar-preview').style.display = 'none';
// 清空文件输入
document.getElementById('avatarFile').value = '';
// 刷新页面以更新所有显示的头像
window.location.reload();
});
} else {
layer.msg(data.msg || '上传失败', { icon: 2 });
}
})
.catch(error => {
layer.close(loadIndex);
layer.msg('上传失败,请重试', { icon: 2 });
});
});
// 取消上传
document.getElementById('cancelUpload').addEventListener('click', function () {
document.querySelector('.avatar-preview').style.display = 'none';
document.getElementById('avatarFile').value = '';
});
// 拖拽上传
var uploadArea = document.getElementById('uploadArea');
uploadArea.addEventListener('dragover', function (e) {
e.preventDefault();
this.style.borderColor = '#1677ff';
});
uploadArea.addEventListener('dragleave', function (e) {
e.preventDefault();
this.style.borderColor = '#d9d9d9';
});
uploadArea.addEventListener('drop', function (e) {
e.preventDefault();
this.style.borderColor = '#d9d9d9';
var file = e.dataTransfer.files[0];
if (!file) return;
// 触发文件选择事件
var dataTransfer = new DataTransfer();
dataTransfer.items.add(file);
document.getElementById('avatarFile').files = dataTransfer.files;
// 手动触发change事件
var event = new Event('change');
document.getElementById('avatarFile').dispatchEvent(event);
});
});
</script>
+427
View File
@@ -0,0 +1,427 @@
<div class="basic-info">
<div class="layui-tab">
<ul class="layui-tab-title">
<li class="layui-this">个人资料</li>
<li>修改头像</li>
</ul>
<div class="layui-tab-content">
<div class="layui-tab-item layui-show">
<form class="layui-form" lay-filter="basicForm">
<div class="layui-form-item">
<label class="layui-form-label">用户名</label>
<div class="layui-input-block">
<input type="text" name="name" value="{$user.name}" placeholder="请输入用户名"
class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">账号</label>
<div class="layui-input-block" style="display: flex; align-items: center;">
<input type="text" value="{$user.account}" class="layui-input" disabled>
<button class="layui-btn" style="margin-left: 20px;">编辑</button>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">QQ</label>
<div class="layui-input-block">
<input type="text" name="qq" value="{$user.qq}" placeholder="请输入QQ号" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">微信</label>
<div class="layui-input-block">
{if $user.wechat}
<input type="text" name="wechat" value="{$user.wechat}" placeholder="请输入微信号"
class="layui-input">
{else}
<button class="layui-btn" id="bindWechat">绑定微信号</button>
{/if}
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">手机号</label>
<div class="layui-input-block">
<input type="text" name="phone" value="{$user.phone}" placeholder="请输入手机号"
class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">性别</label>
<div class="layui-input-block">
<input type="radio" name="sex" value="1" title="" {if $user.sex==1}checked{/if}>
<input type="radio" name="sex" value="2" title="" {if $user.sex==2}checked{/if}>
<input type="radio" name="sex" value="0" title="保密" {if $user.sex==0}checked{/if}>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit lay-filter="saveBasic">保存修改</button>
<button type="reset" class="layui-btn layui-btn-primary">重置</button>
</div>
</div>
</form>
</div>
<div class="layui-tab-item">
<div class="avatar-section">
<div class="avatar-upload-container">
<div class="current-avatar">
<img src="{$user.avatar|default='/static/images/avatar.png'}" alt="当前头像" id="currentAvatar">
<p class="avatar-tip">当前头像</p>
</div>
<div class="upload-area" id="uploadArea">
<i class="layui-icon layui-icon-upload"></i>
<p>点击或拖拽图片到此处上传</p>
<p class="upload-tip">支持 jpg、png、gif 格式,大小不超过 2MB</p>
<input type="file" id="avatarFile" accept="image/*" style="display: none;">
</div>
</div>
<div class="avatar-preview" style="display: none;">
<h3>预览</h3>
<div class="preview-container">
<img src="" alt="预览图" id="previewImage">
</div>
<div class="preview-actions">
<button class="layui-btn" id="confirmUpload">确认上传</button>
<button class="layui-btn layui-btn-primary" id="cancelUpload">取消</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
layui.use(['form', 'layer', 'upload'], function () {
var form = layui.form;
var layer = layui.layer;
var upload = layui.upload;
// 绑定微信号按钮点击事件
document.getElementById('bindWechat').addEventListener('click', function () {
// 发送 AJAX 请求调用 qrcode 接口
fetch('/index/user/qrcode')
.then(response => response.json())
.then(data => {
if (data.code === 0) {
// 二维码生成成功,这里可以添加显示二维码的逻辑,例如弹出一个窗口显示二维码
layer.open({
type: 1,
title: '微信绑定二维码',
content: `<img src="${data.data.qrcode_url}" alt="微信绑定二维码">`,
area: ['300px', '300px']
});
} else {
// 二维码生成失败,提示用户
layer.msg(data.msg, { icon: 2 });
}
})
.catch(error => {
// 请求出错,提示用户
layer.msg('请求出错,请稍后重试', { icon: 2 });
});
});
// 监听个人资料表单提交
form.on('submit(saveBasic)', function (data) {
// 发送AJAX请求保存数据
fetch('/index/user/saveBasic', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
body: JSON.stringify(data.field)
})
.then(response => response.json())
.then(result => {
if (result.code === 0) {
layer.msg(result.msg, { icon: 1 }, function () {
// 保存成功后刷新页面
window.location.reload();
});
} else {
layer.msg(result.msg, { icon: 2 });
}
})
.catch(error => {
console.error('保存失败:', error);
layer.msg('保存失败,请稍后重试', { icon: 2 });
});
return false; // 阻止表单默认提交
});
// 点击上传区域触发文件选择
document.getElementById('uploadArea').addEventListener('click', function () {
document.getElementById('avatarFile').click();
});
// 处理文件选择
document.getElementById('avatarFile').addEventListener('change', function (e) {
var file = e.target.files[0];
if (!file) return;
// 检查文件类型
if (!['image/jpeg', 'image/png', 'image/gif', 'image/webp'].includes(file.type)) {
layer.msg('请上传 jpg、png、webp 或 gif 格式的图片');
return;
}
// 检查文件大小
if (file.size > 2 * 1024 * 1024) {
layer.msg('图片大小不能超过 2MB');
return;
}
// 预览图片
var reader = new FileReader();
reader.onload = function (e) {
document.getElementById('previewImage').src = e.target.result;
document.querySelector('.avatar-preview').style.display = 'block';
};
reader.readAsDataURL(file);
});
// 确认上传
document.getElementById('confirmUpload').addEventListener('click', function () {
var file = document.getElementById('avatarFile').files[0];
if (!file) return;
var formData = new FormData();
formData.append('avatar', file);
// 显示上传中
var loadIndex = layer.load(2);
// 发送上传请求
fetch('/index/user/update_avatar', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
layer.close(loadIndex);
if (data.code === 0) {
// 更新cookie中的头像
document.cookie = "user_avatar=" + data.data.url + "; path=/";
// 更新localStorage中的头像
localStorage.setItem('user_avatar', data.data.url);
layer.msg('头像上传成功', {
icon: 1,
time: 1000
}, function () {
// 更新当前头像显示
document.getElementById('currentAvatar').src = data.data.url;
// 隐藏预览区域
document.querySelector('.avatar-preview').style.display = 'none';
// 清空文件输入
document.getElementById('avatarFile').value = '';
// 刷新页面以更新所有显示的头像
window.location.reload();
});
} else {
layer.msg(data.msg || '上传失败', { icon: 2 });
}
})
.catch(error => {
layer.close(loadIndex);
layer.msg('上传失败,请重试', { icon: 2 });
});
});
// 取消上传
document.getElementById('cancelUpload').addEventListener('click', function () {
document.querySelector('.avatar-preview').style.display = 'none';
document.getElementById('avatarFile').value = '';
});
// 拖拽上传
var uploadArea = document.getElementById('uploadArea');
uploadArea.addEventListener('dragover', function (e) {
e.preventDefault();
this.style.borderColor = '#1677ff';
});
uploadArea.addEventListener('dragleave', function (e) {
e.preventDefault();
this.style.borderColor = '#d9d9d9';
});
uploadArea.addEventListener('drop', function (e) {
e.preventDefault();
this.style.borderColor = '#d9d9d9';
var file = e.dataTransfer.files[0];
if (!file) return;
// 触发文件选择事件
var dataTransfer = new DataTransfer();
dataTransfer.items.add(file);
document.getElementById('avatarFile').files = dataTransfer.files;
// 手动触发change事件
var event = new Event('change');
document.getElementById('avatarFile').dispatchEvent(event);
});
});
</script>
<style>
.basic-info {
max-width: 800px;
margin: 0 auto;
}
.section-title {
font-size: 20px;
font-weight: 600;
color: #333;
margin-bottom: 24px;
padding-bottom: 16px;
border-bottom: 1px solid #f0f0f0;
}
.layui-form-label {
width: 100px;
}
.layui-input-block {
margin-left: 130px;
}
.layui-form-item {
margin-bottom: 24px;
}
.layui-textarea {
min-height: 120px;
}
.avatar-section {
max-width: 800px;
margin: 0 auto;
}
.avatar-upload-container {
display: flex;
gap: 40px;
margin-bottom: 32px;
}
.current-avatar {
text-align: center;
}
.current-avatar img {
width: 160px;
height: 160px;
border-radius: 50%;
object-fit: cover;
border: 3px solid #f5f5f5;
}
.avatar-tip {
margin-top: 12px;
color: #666;
}
.upload-area {
flex: 1;
border: 2px dashed #d9d9d9;
border-radius: 8px;
padding: 40px;
text-align: center;
cursor: pointer;
transition: all 0.3s;
}
.upload-area:hover {
border-color: #1677ff;
}
.upload-area .layui-icon {
font-size: 48px;
color: #999;
margin-bottom: 16px;
}
.upload-area p {
margin: 8px 0;
color: #666;
}
.upload-tip {
font-size: 12px;
color: #999;
}
.avatar-preview {
margin-top: 32px;
padding-top: 32px;
border-top: 1px solid #f0f0f0;
}
.avatar-preview h3 {
font-size: 16px;
color: #333;
margin-bottom: 16px;
}
.preview-container {
text-align: center;
margin-bottom: 24px;
}
.preview-container img {
max-width: 200px;
max-height: 200px;
border-radius: 8px;
}
.preview-actions {
text-align: center;
}
.preview-actions .layui-btn {
margin: 0 8px;
}
@media (max-width: 768px) {
.layui-form-label {
width: 80px;
}
.layui-input-block {
margin-left: 110px;
}
.avatar-upload-container {
flex-direction: column;
gap: 24px;
}
.current-avatar img {
width: 120px;
height: 120px;
}
.upload-area {
padding: 24px;
}
}
</style>
+326
View File
@@ -0,0 +1,326 @@
<div class="basic-info">
<div class="layui-tab">
<ul class="layui-tab-title">
<li class="layui-this">全部消息</li>
<li>未读消息</li>
<li>已读消息</li>
</ul>
<div class="layui-tab-content">
<div class="layui-tab-item layui-show">
<div class="message-list" id="allMessages">
<!-- 消息列表将通过JavaScript动态加载 -->
</div>
</div>
<div class="layui-tab-item">
<div class="message-list" id="unreadMessages">
<!-- 未读消息列表 -->
</div>
</div>
<div class="layui-tab-item">
<div class="message-list" id="readMessages">
<!-- 已读消息列表 -->
</div>
</div>
</div>
</div>
</div>
<style>
.basic-info {
max-width: 800px;
margin: 0 auto;
}
.section-title {
font-size: 20px;
font-weight: 600;
color: #333;
margin-bottom: 24px;
padding-bottom: 16px;
border-bottom: 1px solid #f0f0f0;
}
.layui-form-label {
width: 100px;
}
.layui-input-block {
margin-left: 130px;
}
.layui-form-item {
margin-bottom: 24px;
}
.layui-textarea {
min-height: 120px;
}
.message-list {
margin-top: 20px;
}
.message-item {
padding: 16px;
border-bottom: 1px solid #f0f0f0;
display: flex;
align-items: flex-start;
gap: 16px;
cursor: pointer;
transition: background-color 0.3s;
}
.message-item:hover {
background-color: #f9f9f9;
}
.message-item.unread {
background-color: #f0f7ff;
}
.message-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
object-fit: cover;
}
.message-content {
flex: 1;
}
.message-title {
font-weight: 500;
margin-bottom: 4px;
color: #333;
}
.message-text {
color: #666;
font-size: 14px;
margin-bottom: 8px;
}
.message-meta {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 12px;
color: #999;
}
.message-time {
color: #999;
}
.message-actions {
display: flex;
gap: 8px;
}
.message-actions button {
padding: 4px 8px;
font-size: 12px;
border-radius: 4px;
background: none;
border: 1px solid #d9d9d9;
color: #666;
cursor: pointer;
transition: all 0.3s;
}
.message-actions button:hover {
border-color: #1677ff;
color: #1677ff;
}
.empty-message {
text-align: center;
padding: 40px 0;
color: #999;
}
@media (max-width: 768px) {
.layui-form-label {
width: 80px;
}
.layui-input-block {
margin-left: 110px;
}
.message-item {
padding: 12px;
}
.message-avatar {
width: 32px;
height: 32px;
}
}
</style>
<script>
layui.use(['element', 'layer'], function () {
var element = layui.element;
var layer = layui.layer;
// 加载消息列表
function loadMessages(type) {
var container = document.getElementById(type + 'Messages');
var loadIndex = layer.load(2);
fetch('/index/user/getMessages?type=' + type)
.then(response => response.json())
.then(data => {
layer.close(loadIndex);
if (data.code === 0) {
renderMessages(container, data.data);
} else {
layer.msg(data.msg || '加载失败', { icon: 2 });
}
})
.catch(error => {
layer.close(loadIndex);
layer.msg('加载失败,请重试', { icon: 2 });
});
}
// 渲染消息列表
function renderMessages(container, messages) {
if (!messages || messages.length === 0) {
container.innerHTML = '<div class="empty-message">暂无消息</div>';
return;
}
var html = '';
messages.forEach(function (message) {
html += `
<div class="message-item ${message.is_read ? '' : 'unread'}" data-id="${message.id}">
<img src="${message.avatar || '/static/images/avatar.png'}" class="message-avatar" alt="头像">
<div class="message-content">
<div class="message-title">${message.title}</div>
<div class="message-text">${message.content}</div>
<div class="message-meta">
<span class="message-time">${message.create_time}</span>
<div class="message-actions">
${!message.is_read ? '<button class="mark-read">标记已读</button>' : ''}
<button class="delete-message">删除</button>
</div>
</div>
</div>
</div>
`;
});
container.innerHTML = html;
// 绑定事件
bindMessageEvents(container);
}
// 绑定消息事件
function bindMessageEvents(container) {
// 标记已读
container.querySelectorAll('.mark-read').forEach(btn => {
btn.addEventListener('click', function (e) {
e.stopPropagation();
var messageId = this.closest('.message-item').dataset.id;
markAsRead(messageId);
});
});
// 删除消息
container.querySelectorAll('.delete-message').forEach(btn => {
btn.addEventListener('click', function (e) {
e.stopPropagation();
var messageId = this.closest('.message-item').dataset.id;
deleteMessage(messageId);
});
});
// 点击消息
container.querySelectorAll('.message-item').forEach(item => {
item.addEventListener('click', function () {
var messageId = this.dataset.id;
viewMessage(messageId);
});
});
}
// 标记已读
function markAsRead(messageId) {
fetch('/index/user/markMessageRead', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ id: messageId })
})
.then(response => response.json())
.then(data => {
if (data.code === 0) {
layer.msg('已标记为已读', { icon: 1 });
// 重新加载消息列表
loadMessages('all');
loadMessages('unread');
loadMessages('read');
} else {
layer.msg(data.msg || '操作失败', { icon: 2 });
}
})
.catch(error => {
layer.msg('操作失败,请重试', { icon: 2 });
});
}
// 删除消息
function deleteMessage(messageId) {
layer.confirm('确定要删除这条消息吗?', {
btn: ['确定', '取消']
}, function () {
fetch('/index/user/deleteMessage', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ id: messageId })
})
.then(response => response.json())
.then(data => {
if (data.code === 0) {
layer.msg('删除成功', { icon: 1 });
// 重新加载消息列表
loadMessages('all');
loadMessages('unread');
loadMessages('read');
} else {
layer.msg(data.msg || '删除失败', { icon: 2 });
}
})
.catch(error => {
layer.msg('删除失败,请重试', { icon: 2 });
});
});
}
// 查看消息详情
function viewMessage(messageId) {
layer.open({
type: 2,
title: '消息详情',
area: ['500px', '400px'],
content: '/index/user/messageDetail?id=' + messageId
});
}
// 监听标签切换
element.on('tab(messageTabs)', function (data) {
var type = ['all', 'unread', 'read'][data.index];
loadMessages(type);
});
// 初始加载全部消息
loadMessages('all');
});
</script>
@@ -0,0 +1,209 @@
<div class="notifications-section">
<div class="layui-tab layui-tab-brief" lay-filter="notificationTabs">
<ul class="layui-tab-title">
<li class="layui-this">全部通知</li>
<li>未读通知</li>
<li>已读通知</li>
</ul>
<div class="layui-tab-content">
<div class="layui-tab-item layui-show">
<div class="notification-list" id="allNotifications"></div>
</div>
<div class="layui-tab-item">
<div class="notification-list" id="unreadNotifications"></div>
</div>
<div class="layui-tab-item">
<div class="notification-list" id="readNotifications"></div>
</div>
</div>
</div>
</div>
<style>
.notifications-section {
max-width: 800px;
margin: 0 auto;
}
.section-title {
font-size: 20px;
font-weight: 600;
color: #333;
margin-bottom: 24px;
padding-bottom: 16px;
border-bottom: 1px solid #f0f0f0;
}
.notification-list {
min-height: 200px;
}
.notification-item {
padding: 16px;
border-bottom: 1px solid #f0f0f0;
display: flex;
justify-content: space-between;
align-items: center;
}
.notification-item:hover {
background-color: #f9f9f9;
}
.notification-content {
flex: 1;
}
.notification-title {
font-size: 16px;
color: #333;
margin-bottom: 8px;
}
.notification-time {
font-size: 12px;
color: #999;
}
.notification-actions {
display: flex;
gap: 8px;
}
.notification-item.unread .notification-title {
font-weight: 600;
}
.notification-item.unread::before {
content: '';
display: inline-block;
width: 8px;
height: 8px;
background-color: #1677ff;
border-radius: 50%;
margin-right: 8px;
}
@media (max-width: 768px) {
.notification-actions {
flex-direction: column;
}
}
</style>
<script>
layui.use(['element', 'layer'], function () {
var element = layui.element;
var layer = layui.layer;
// 加载通知列表
function loadNotifications(type) {
var container = document.getElementById(type + 'Notifications');
container.innerHTML = '<div class="layui-anim layui-anim-upbit layui-anim-loop layui-anim-shrink" style="text-align: center; padding: 20px;"><i class="layui-icon layui-icon-loading layui-anim layui-anim-rotate layui-anim-loop"></i></div>';
fetch('/index/user/getNotifications?type=' + type)
.then(response => response.json())
.then(data => {
if (data.code === 0) {
if (data.data.length === 0) {
container.innerHTML = '<div class="layui-none">暂无通知</div>';
return;
}
var html = '';
data.data.forEach(function (notification) {
html += `
<div class="notification-item ${notification.is_read ? '' : 'unread'}" data-id="${notification.id}">
<div class="notification-content">
<div class="notification-title">${notification.title}</div>
<div class="notification-time">${notification.create_time}</div>
</div>
<div class="notification-actions">
<button class="layui-btn layui-btn-xs" onclick="viewNotification(${notification.id})">查看</button>
<button class="layui-btn layui-btn-xs layui-btn-danger" onclick="deleteNotification(${notification.id})">删除</button>
</div>
</div>
`;
});
container.innerHTML = html;
} else {
layer.msg(data.msg || '加载失败', { icon: 2 });
}
})
.catch(error => {
layer.msg('加载失败,请重试', { icon: 2 });
});
}
// 查看通知
window.viewNotification = function (notificationId) {
fetch('/index/user/readNotification', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ id: notificationId })
})
.then(response => response.json())
.then(data => {
if (data.code === 0) {
layer.open({
type: 2,
title: '通知详情',
area: ['500px', '400px'],
content: '/index/user/notificationDetail?id=' + notificationId
});
// 重新加载通知列表
loadNotifications('all');
loadNotifications('unread');
loadNotifications('read');
} else {
layer.msg(data.msg || '操作失败', { icon: 2 });
}
})
.catch(error => {
layer.msg('操作失败,请重试', { icon: 2 });
});
}
// 删除通知
window.deleteNotification = function (notificationId) {
layer.confirm('确定要删除这条通知吗?', {
btn: ['确定', '取消']
}, function () {
fetch('/index/user/deleteNotification', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ id: notificationId })
})
.then(response => response.json())
.then(data => {
if (data.code === 0) {
layer.msg('删除成功', { icon: 1 });
// 重新加载通知列表
loadNotifications('all');
loadNotifications('unread');
loadNotifications('read');
} else {
layer.msg(data.msg || '删除失败', { icon: 2 });
}
})
.catch(error => {
layer.msg('删除失败,请重试', { icon: 2 });
});
});
}
// 监听标签切换
element.on('tab(notificationTabs)', function (data) {
var type = ['all', 'unread', 'read'][data.index];
loadNotifications(type);
});
// 初始加载全部通知
loadNotifications('all');
});
</script>
+100
View File
@@ -0,0 +1,100 @@
<div class="security-section">
<h2 class="section-title">安全设置</h2>
<form class="layui-form" lay-filter="securityForm">
<div class="layui-form-item">
<label class="layui-form-label">登录密码</label>
<div class="layui-input-block">
<button type="button" class="layui-btn" onclick="changePassword()">修改密码</button>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">手机绑定</label>
<div class="layui-input-block">
<div class="phone-info">
<span id="phoneNumber">未绑定</span>
<button type="button" class="layui-btn layui-btn-primary" onclick="bindPhone()">绑定手机</button>
</div>
</div>
</div>
</form>
</div>
<style>
.security-section {
max-width: 800px;
margin: 0 auto;
}
.section-title {
font-size: 20px;
font-weight: 600;
color: #333;
margin-bottom: 24px;
padding-bottom: 16px;
border-bottom: 1px solid #f0f0f0;
}
.phone-info,
.email-info {
display: flex;
align-items: center;
gap: 16px;
}
.layui-form-label {
width: 100px;
}
.layui-input-block {
margin-left: 130px;
}
@media (max-width: 768px) {
.layui-form-label {
width: 80px;
}
.layui-input-block {
margin-left: 110px;
}
}
</style>
<script>
layui.use(['form', 'layer'], function () {
var form = layui.form;
var layer = layui.layer;
// 加载用户安全信息
loadSecurityInfo();
});
// 修改密码
function changePassword() {
layer.open({
type: 2,
title: '修改密码',
area: ['500px', '400px'],
content: '/index/user/updatePassword',
end: function () {
// 检查是否需要跳转到登录页
if (window.needRedirect) {
window.location.href = '/index/user/login';
}
}
});
}
// 绑定手机
function bindPhone() {
layer.open({
type: 2,
title: '绑定手机',
area: ['500px', '400px'],
content: '/index/user/component/bindPhone'
});
}
</script>
+157
View File
@@ -0,0 +1,157 @@
<div class="sidebar">
<div class="user-info">
<div class="avatar-wrapper">
<img src="{$user.avatar|default='/static/images/avatar.png'}" class="avatar" alt="用户头像">
</div>
<h3 class="username">{$user.name}</h3>
<p class="email">{$user.account}</p>
</div>
<nav class="menu">
<a href="javascript:;" class="menu-item active" data-target="profile-basic">
<i class="icon">👤</i>
<span>个人资料</span>
</a>
<a href="javascript:;" class="menu-item" data-target="profile-wallet">
<i class="icon">💰</i>
<span>我的钱包</span>
</a>
<a href="javascript:;" class="menu-item" data-target="profile-messages">
<i class="icon">✉️</i>
<span>我的消息</span>
</a>
<a href="javascript:;" class="menu-item" data-target="profile-notifications">
<i class="icon">🔔</i>
<span>系统通知</span>
</a>
<a href="javascript:;" class="menu-item" data-target="profile-security">
<i class="icon">🛡️</i>
<span>安全设置</span>
</a>
</nav>
</div>
<style>
.sidebar {
width: 260px;
background: #fff;
border-radius: 12px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
padding: 24px 0;
height: 100%;
}
.user-info {
text-align: center;
padding: 0 24px 24px;
border-bottom: 1px solid #f0f0f0;
margin-bottom: 24px;
display: flex;
flex-direction: column;
align-items: center;
}
.avatar-wrapper {
width: 88px;
height: 88px;
margin: 0 auto 16px;
position: relative;
}
.avatar {
width: 100%;
height: 100%;
border-radius: 50%;
object-fit: cover;
border: 3px solid #f5f5f5;
transition: all 0.3s ease;
}
.avatar:hover {
transform: scale(1.05);
}
.username {
font-size: 18px;
font-weight: 600;
color: #333;
margin: 0 0 4px;
}
.email {
font-size: 14px;
color: #666;
margin: 0;
}
.menu {
display: flex;
flex-direction: column;
padding: 0 12px;
}
.menu-item {
display: flex;
align-items: center;
padding: 12px 16px;
color: #666;
text-decoration: none;
border-radius: 8px;
margin-bottom: 4px;
transition: all 0.3s ease;
}
.menu-item:hover {
background: #f5f7fa;
color: #1677ff;
}
.menu-item.active {
background: #e6f4ff;
color: #1677ff;
font-weight: 500;
}
.icon {
font-size: 20px;
margin-right: 12px;
width: 24px;
text-align: center;
}
span {
font-size: 15px;
}
/* 响应式适配 */
@media (max-width: 768px) {
.sidebar {
width: 100%;
border-radius: 0;
box-shadow: none;
}
}
i {
font-style: normal;
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function () {
// 获取当前页面路径
const currentPath = window.location.pathname;
// 移除所有active类
document.querySelectorAll('.menu-item').forEach(item => {
item.classList.remove('active');
});
// 为当前页面对应的菜单项添加active类
document.querySelectorAll('.menu-item').forEach(item => {
if (item.getAttribute('href') === currentPath) {
item.classList.add('active');
}
});
});
</script>
+86
View File
@@ -0,0 +1,86 @@
<div class="basic-info">
<div class="layui-tab">
<ul class="layui-tab-title">
<li class="layui-this">积分</li>
<li>云币</li>
</ul>
<div class="layui-tab-content">
<div class="layui-tab-item layui-show">
<p>您当前的积分余额:<span id="points-balance">0</span></p>
</div>
<div class="layui-tab-item">
<p>您当前的云币余额:<span id="cloud-coins-balance">0</span></p>
<p>云币是真钱充值后获得的平台币。</p>
</div>
</div>
</div>
</div>
<script>
layui.use(['element'], function () {
var element = layui.element;
});
</script>
<script>
// JavaScript 部分,处理 tab 切换
const tabs = document.querySelectorAll('.tab');
const contents = document.querySelectorAll('.content-item');
tabs.forEach(tab => {
tab.addEventListener('click', () => {
// 移除所有 tab 的 active 类
tabs.forEach(t => t.classList.remove('active'));
// 给当前点击的 tab 添加 active 类
tab.classList.add('active');
const target = tab.dataset.target;
// 隐藏所有内容项
contents.forEach(content => {
content.classList.remove('active');
if (content.id === target) {
content.classList.add('active');
}
});
});
});
</script>
<style>
/* 样式部分 */
.tab-container {
display: flex;
border-bottom: 1px solid #ccc;
}
.tab {
padding: 10px 20px;
cursor: pointer;
border: 1px solid transparent;
border-bottom: none;
}
.tab.active {
border-color: #ccc;
border-bottom: 1px solid white;
margin-bottom: -1px;
}
.tab-content {
padding: 20px;
border: 1px solid #ccc;
border-top: none;
}
.content-item {
display: none;
}
.content-item.active {
display: block;
}
.layui-unselect.layui-tab-bar {
display: none;
}
</style>
+661
View File
@@ -0,0 +1,661 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="__LAYUI__/css/layui.css">
<script src="__JS__/gt4.js"></script>
<script src="__LAYUI__/layui.js" charset="utf-8"></script>
<title>用户登录</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: #f5f7fa;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
padding: 20px;
}
.login-container {
width: 420px;
background: #fff;
border-radius: 12px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.05);
overflow: hidden;
}
.login-header {
padding: 40px 40px 20px;
text-align: center;
}
.login-header h2 {
font-size: 28px;
color: #333;
margin: 0;
font-weight: 600;
}
.login-header p {
color: #666;
margin: 10px 0 0;
font-size: 15px;
}
.login-form {
padding: 20px 40px 40px;
}
.layui-form-item {
margin-bottom: 25px;
}
.layui-input {
height: 45px;
line-height: 45px;
border-radius: 8px;
border: 1px solid #e4e7ed;
padding: 0 15px;
transition: all 0.3s;
}
.layui-input:focus {
border-color: #409eff;
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.1);
}
.layui-btn {
height: 45px;
line-height: 45px;
border-radius: 8px;
font-size: 16px;
background: #409eff;
transition: all 0.3s;
}
.layui-btn:hover {
background: #66b1ff;
transform: translateY(-1px);
}
.layui-tab {
margin: 0;
}
.layui-tab-title {
border: none;
padding: 0 40px;
}
.layui-tab-title li {
font-size: 15px;
color: #666;
padding: 0 20px;
}
.layui-tab-title .layui-this {
color: #409eff;
}
.layui-tab-title .layui-this:after {
border-bottom: 2px solid #409eff;
}
.layui-tab-content {
padding: 20px 0 0;
}
.wechat-login {
text-align: center;
padding: 30px 0;
/* padding-bottom: 60px; */
}
.wechat-login img {
width: 200px;
height: 200px;
transition: transform 0.3s;
}
.wechat-login img:hover {
transform: scale(1.1);
}
.wechat-login p {
margin-top: 15px;
color: #666;
}
#qrcode-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
#qrcode-loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
#qrcode-loading .layui-icon {
font-size: 40px;
color: #409eff;
}
#qrcode-loading p {
margin-top: 10px;
color: #666;
}
.layui-input-inline {
margin-right: 0px !important;
}
#getCode {
height: 45px;
line-height: 45px;
padding: 0 20px;
font-size: 14px;
}
.layui-input-block {
display: flex;
justify-content: space-between;
}
.container {
width: 100%;
max-width: 400px;
/* margin: 50px auto; */
text-align: center;
}
.qrcode-container {
margin: 20px 0;
}
.qrcode-container img {
max-width: 200px;
cursor: pointer;
}
.qrcode-container p {
color: #666;
font-size: 14px;
margin-top: 10px;
}
.status {
color: #666;
margin: 10px 0;
}
.error {
color: #ff4d4f;
margin: 10px 0;
}
</style>
</head>
<body>
<div class="login-container">
<div class="login-header">
<h2>欢迎登录</h2>
<p>请选择登录方式</p>
</div>
<div class="layui-tab">
<ul class="layui-tab-title">
<li lay-id="account">账密登录</li>
<!-- <li lay-id="phone">手机验证码</li> -->
<li class="layui-this" lay-id="wechat">微信登录</li>
</ul>
<div class="layui-tab-content">
<div class="layui-tab-item" id="account">
<form action="#" method="post" class="layui-form login-form">
<div class="layui-form-item">
<div class="layui-input-block" style="margin-left: 0;">
<input type="text" name="account" required lay-verify="required" placeholder="请输入用户名"
autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block" style="margin-left: 0;">
<input type="password" name="password" required lay-verify="required"
placeholder="请输入密码" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<div id="gt-captcha" style="width: 100%;"></div>
</div>
<div class="layui-form-item" style="display: flex;flex-direction: column;align-items: center;">
<div class="layui-input-block" style="margin-left: 0;margin-bottom: 10px;">
<button class="layui-btn layui-btn-fluid" lay-submit
lay-filter="accountLogin">登录</button>
</div>
<div style="margin-bottom: 10px;color: #aaa;">or</div>
<div>
<a href="{:url('/index/user/register')}" class="">注册</a>
</div>
</div>
</form>
</div>
<!-- <div class="layui-tab-item" id="phone">
<form action="#" method="post" class="layui-form login-form">
<div class="layui-form-item">
<div class="layui-input-block" style="margin-left: 0;">
<input type="tel" name="phone" required lay-verify="required|phone" placeholder="请输入手机号"
autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block" style="margin-left: 0;">
<div class="layui-input-inline" style="width: calc(100% - 120px);">
<input type="text" name="code" required lay-verify="required" placeholder="请输入验证码"
autocomplete="off" class="layui-input">
</div>
<div class="layui-input-inline" style="width: 110px;">
<button type="button" class="layui-btn" id="getCode">获取验证码</button>
</div>
</div>
</div>
<div class="layui-form-item">
<div id="gt-captcha" style="width: 100%;"></div>
</div>
<div class="layui-form-item">
<div class="layui-input-block" style="margin-left: 0;">
<button class="layui-btn layui-btn-fluid" lay-submit lay-filter="phoneLogin">登录</button>
</div>
</div>
</form>
</div> -->
<div class="layui-tab-item layui-show" id="wechat">
<div class="wechat-login">
<div class="container">
<h2>微信扫码登录</h2>
<div class="qrcode-container">
<img id="qrcode" src="__IMAGES__/loading.gif" alt="微信登录二维码" onclick="reGenerateQrcode()">
<!-- <p>点击二维码可刷新</p> -->
</div>
<div id="status" class="status">正在加载二维码...</div>
<div id="error" class="error"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
// 全局变量
var checkLoginTimer = null;
var currentSceneStr = '';
var currentTicket = '';
var qrcodeExpireTime = 0;
var $ = layui.jquery;
// 获取微信登录二维码
function getQrcode() {
$.ajax({
url: '/index/wechat/getLoginTicket',
type: 'GET',
success: function(res) {
if (res.code === 0) {
$('#qrcode').attr('src', res.data.url);
currentSceneStr = res.data.scene_str;
currentTicket = res.data.ticket;
// 设置二维码过期时间(5分钟)
qrcodeExpireTime = new Date().getTime() + (res.data.expire_seconds * 1000);
$('#status').text('等待扫码...');
$('#error').text('');
// 开始检查登录状态
startCheckLogin();
} else {
$('#error').text('获取二维码失败:' + res.msg);
}
},
error: function() {
$('#error').text('获取二维码失败,请刷新页面重试');
}
});
}
// 开始检查登录状态
function startCheckLogin() {
if (checkLoginTimer) {
clearInterval(checkLoginTimer);
}
checkLoginTimer = setInterval(function() {
// 检查二维码是否过期
if (new Date().getTime() > qrcodeExpireTime) {
clearInterval(checkLoginTimer);
$('#status').html('二维码已过期<br>请点击刷新');
$('#qrcode').css('opacity', '0.02');
$('#status').css({
'position': 'relative',
'top': '-150px',
'font-size': '20px',
'font-weight': 'bolder'
});
return;
}
$.ajax({
url: '/index/wechat/checkLoginStatus',
type: 'POST',
data: {
scene_str: currentSceneStr,
ticket: currentTicket
},
success: function(res) {
if (res.code === 1) {
// 登录成功
clearInterval(checkLoginTimer);
$('#status').text('登录成功,正在跳转...');
// 保存用户信息到localStorage
if (res.data) {
localStorage.setItem('user_account', res.data.user_account);
localStorage.setItem('expire_time', res.data.expire_time);
localStorage.setItem('is_auto_login', res.data.is_auto_login);
}
// 设置cookie
document.cookie = `user_account=${res.data.user_account}; path=/; max-age=${7*24*3600}`;
document.cookie = `user_avatar=${res.data.avatar}; path=/; max-age=${7*24*3600}`;
document.cookie = `user_name=${res.data.name}; path=/; max-age=${7*24*3600}`;
document.cookie = `open_id=${res.data.openid}; path=/; max-age=${7*24*3600}`;
// 跳转到首页或其他页面
setTimeout(function() {
window.location.href = '/';
}, 1000);
} else if (res.code === 0) {
// 更新状态信息
$('#status').text(res.msg);
}
},
error: function() {
$('#error').text('检查登录状态失败,请重试');
}
});
}, 2000); // 每2秒检查一次
}
// 重新生成二维码的全局函数
function reGenerateQrcode() {
if (!currentSceneStr) {
getQrcode();
return;
}
// 清除之前的定时器
if (checkLoginTimer) {
clearInterval(checkLoginTimer);
checkLoginTimer = null;
}
// 恢复二维码和状态文本的样式
$('#qrcode').css('opacity', '1');
$('#status').css({
'position': 'static',
'top': 'auto',
'font-size': '14px',
'font-weight': 'normal'
});
$.ajax({
url: '/index/wechat/reGenerateQrcode',
type: 'POST',
data: {
scene_str: currentSceneStr
},
success: function(res) {
if (res.code === 0) {
$('#qrcode').attr('src', res.data.url);
currentSceneStr = res.data.scene_str;
currentTicket = res.data.ticket;
// 设置新的二维码过期时间(5分钟)
qrcodeExpireTime = new Date().getTime() + (res.data.expire_seconds * 1000);
$('#status').text('等待扫码...');
$('#error').text('');
// 开始检查登录状态
startCheckLogin();
} else {
$('#error').text('刷新二维码失败:' + res.msg);
}
},
error: function() {
$('#error').text('刷新二维码失败,请重试');
}
});
}
layui.use(['form', 'element', 'jquery'], function () {
var form = layui.form;
var element = layui.element;
var layer = layui.layer;
// 页面加载完成后获取二维码
$(document).ready(function() {
getQrcode();
});
// 检查极验验证是否开启
<?php if ($config['geetest_open'] == 1): ?>
// 初始化极验验证
var handler = function (captchaObj) {
// 将验证码渲染到指定容器
captchaObj.appendTo('#gt-captcha');
// 账密登录表单提交
form.on('submit(accountLogin)', function (data) {
var validate = captchaObj.getValidate();
if (!validate) {
layer.msg('请完成验证码验证', {
icon: 2,
time: 2000
});
return false;
}
data.field.geetest_challenge = validate.geetest_challenge;
data.field.geetest_validate = validate.geetest_validate;
data.field.geetest_seccode = validate.geetest_seccode;
$.ajax({
url: '{:url("index/user/login")}',
type: 'POST',
data: data.field,
dataType: 'json',
success: function (res) {
if (res.code === 0) {
// 存储登录数据,设置7天过期
var expireTime = new Date().getTime() + 7 * 24 * 60 * 60 * 1000;
localStorage.setItem('user_account', data.field.account);
localStorage.setItem('user_password', btoa(data.field.password));
localStorage.setItem('expire_time', expireTime);
// 添加登录状态标记
localStorage.setItem('is_auto_login', 'true');
layer.msg('登录成功', {
icon: 1,
time: 2000,
shade: 0.3
}, function () {
window.location.href = '{:url("/")}';
});
} else {
layer.msg(res.msg, {
icon: 2,
time: 2000
});
}
}
});
return false;
});
// 手机验证码登录表单提交
form.on('submit(phoneLogin)', function (data) {
var validate = captchaObj.getValidate();
if (!validate) {
layer.msg('请完成验证码验证', {
icon: 2,
time: 2000
});
return false;
}
data.field.geetest_challenge = validate.geetest_challenge;
data.field.geetest_validate = validate.geetest_validate;
data.field.geetest_seccode = validate.geetest_seccode;
$.ajax({
url: '{:url("index/user/login")}',
type: 'POST',
data: data.field,
dataType: 'json',
success: function (res) {
if (res.code === 0) {
layer.msg('登录成功', {
icon: 1,
time: 2000,
shade: 0.3
}, function () {
window.location.href = '{:url("/")}';
});
} else {
layer.msg(res.msg, {
icon: 2,
time: 2000
});
}
}
});
return false;
});
// 点击获取验证码按钮时验证
var getCodeBtn = document.getElementById('getCode');
if (getCodeBtn) {
getCodeBtn.addEventListener('click', function () {
console.log('获取验证码');
});
}
};
// 直接使用配置初始化极验验证,并添加错误处理
initGeetest4({
captchaId: '{$config[\'geetest_id\']}',
offline: false,
new_captcha: true
}, handler, function (error) {
console.error('极验验证初始化失败:', error);
layer.msg('验证码初始化失败,请刷新页面重试', {
icon: 2,
time: 2000
});
});
<?php else: ?>
// 极验验证关闭,移除验证码容器
$('#gt-captcha').remove();
// 移除表单提交时的验证码验证逻辑
form.on('submit(accountLogin)', function (data) {
$.ajax({
url: '{:url("index/user/login")}',
type: 'POST',
data: data.field,
dataType: 'json',
success: function (res) {
if (res.code === 0) {
// 存储登录数据,设置7天过期
var expireTime = new Date().getTime() + 7 * 24 * 60 * 60 * 1000;
localStorage.setItem('user_account', data.field.account);
localStorage.setItem('user_password', btoa(data.field.password));
localStorage.setItem('expire_time', expireTime);
// 添加登录状态标记
localStorage.setItem('is_auto_login', 'true');
layer.msg('登录成功', {
icon: 1,
time: 2000,
shade: 0.3
}, function () {
window.location.href = '{:url("/")}';
});
} else {
layer.msg(res.msg, {
icon: 2,
time: 2000
});
}
}
});
return false;
});
form.on('submit(phoneLogin)', function (data) {
$.ajax({
url: '{:url("index/user/login")}',
type: 'POST',
data: data.field,
dataType: 'json',
success: function (res) {
if (res.code === 0) {
layer.msg('登录成功', {
icon: 1,
time: 2000,
shade: 0.3
}, function () {
window.location.href = '{:url("/")}';
});
} else {
layer.msg(res.msg, {
icon: 2,
time: 2000
});
}
}
});
return false;
});
// 移除获取验证码按钮的验证码验证逻辑
var getCodeBtn = document.getElementById('getCode');
if (getCodeBtn) {
getCodeBtn.addEventListener('click', function () {
console.log('获取验证码');
});
}
<?php endif; ?>
// 页面加载时检查是否有保存的登录数据
$(function () {
var expireTime = localStorage.getItem('expire_time');
var isAutoLogin = localStorage.getItem('is_auto_login');
if (expireTime && new Date().getTime() < expireTime && isAutoLogin === 'true') {
// 只填充账号,不填充密码
$('input[name="account"]').val(localStorage.getItem('user_account'));
} else {
// 如果过期或未开启自动登录,清除数据
localStorage.removeItem('user_account');
localStorage.removeItem('user_password');
localStorage.removeItem('expire_time');
localStorage.removeItem('is_auto_login');
}
});
});
</script>
<style>
.layui-form-item a {
color: #409eff;
}
.layui-form-item a:hover {
color: rgb(58, 125, 196);
}
</style>
</body>
</html>
+175
View File
@@ -0,0 +1,175 @@
{include file="component/head" /}
{include file="component/header" /}
<div class="profile-container">
<div class="profile-sidebar">
<div class="menu">
<div class="menu-item active" data-target="profile-basic">
<i class="layui-icon layui-icon-user"></i>
<span>基本资料</span>
</div>
<div class="menu-item" data-target="profile-wallet">
<i class="layui-icon layui-icon-wallet"></i>
<span>我的钱包</span>
</div>
<div class="menu-item" data-target="profile-messages">
<i class="layui-icon layui-icon-message"></i>
<span>我的消息</span>
</div>
<div class="menu-item" data-target="profile-notifications">
<i class="layui-icon layui-icon-notice"></i>
<span>系统通知</span>
</div>
<div class="menu-item" data-target="profile-security">
<i class="layui-icon layui-icon-password"></i>
<span>安全设置</span>
</div>
</div>
</div>
<div class="profile-main">
<div class="content-area">
<!-- 个人资料 -->
<div id="profile-basic" class="content-section active">
{include file="user/component/basic" /}
</div>
<!-- 我的钱包 -->
<div id="profile-wallet" class="content-section">
{include file="user/component/wallet" /}
</div>
<!-- 我的消息 -->
<div id="profile-messages" class="content-section">
{include file="user/component/messages" /}
</div>
<!-- 系统通知 -->
<div id="profile-notifications" class="content-section">
{include file="user/component/notifications" /}
</div>
<!-- 安全设置 -->
<div id="profile-security" class="content-section">
{include file="user/component/security" /}
</div>
</div>
</div>
</div>
<style>
.profile-container {
display: flex;
max-width: 1200px;
margin: 40px auto;
gap: 24px;
padding: 0 20px;
}
.profile-sidebar {
width: 260px;
flex-shrink: 0;
}
.menu {
background: #fff;
border-radius: 12px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
padding: 16px 0;
}
.menu-item {
padding: 16px 24px;
cursor: pointer;
display: flex;
align-items: center;
gap: 12px;
color: #666;
transition: all 0.3s;
}
.menu-item:hover {
color: #1677ff;
background: #f5f5f5;
}
.menu-item.active {
color: #1677ff;
background: #e6f4ff;
border-right: 3px solid #1677ff;
}
.menu-item .layui-icon {
font-size: 18px;
}
.profile-main {
flex: 1;
background: #fff;
border-radius: 12px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
padding: 32px;
}
.content-section {
display: none;
}
.content-section.active {
display: block;
}
/* 响应式适配 */
@media (max-width: 768px) {
.profile-container {
flex-direction: column;
margin: 20px auto;
}
.profile-sidebar {
width: 100%;
}
.menu {
display: flex;
overflow-x: auto;
padding: 8px;
}
.menu-item {
flex-shrink: 0;
border-right: none;
border-bottom: 3px solid transparent;
}
.menu-item.active {
border-right: none;
border-bottom: 3px solid #1677ff;
}
.profile-main {
padding: 20px;
}
}
</style>
<script>
layui.use(['jquery'], function(){
var $ = layui.jquery;
// 菜单切换
$('.menu-item').on('click', function(){
var target = $(this).data('target');
// 移除所有active类
$('.menu-item').removeClass('active');
$('.content-section').removeClass('active');
// 添加active类
$(this).addClass('active');
$('#' + target).addClass('active');
});
});
</script>
{include file="component/foot" /}
+240
View File
@@ -0,0 +1,240 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="__LAYUI__/css/layui.css">
<script src="__LAYUI__/layui.js" charset="utf-8"></script>
<title>用户注册</title>
<style>
/* 保持原有样式不变 */
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: #f5f7fa;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
padding: 20px;
}
.register-container {
width: 420px;
background: #fff;
border-radius: 12px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.05);
overflow: hidden;
}
.register-header {
padding: 40px 40px 20px;
text-align: center;
}
.register-header h2 {
font-size: 28px;
color: #333;
margin: 0;
font-weight: 600;
}
.register-header p {
color: #666;
margin: 10px 0 0;
font-size: 15px;
}
.register-form {
padding: 20px 40px 40px;
}
.layui-form-item {
margin-bottom: 25px;
}
.layui-input {
height: 45px;
line-height: 45px;
border-radius: 8px;
border: 1px solid #e4e7ed;
padding: 0 15px;
transition: all 0.3s;
}
.layui-input:focus {
border-color: #409eff;
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.1);
}
.layui-btn {
height: 45px;
line-height: 45px;
border-radius: 8px;
font-size: 16px;
background: #409eff;
transition: all 0.3s;
}
.layui-btn:hover {
background: #66b1ff;
transform: translateY(-1px);
}
.layui-input-inline {
margin-right: 0px !important;
}
#getCode {
height: 45px;
line-height: 45px;
padding: 0 20px;
font-size: 14px;
}
.layui-input-block {
display: flex;
justify-content: space-between;
}
.login-link {
text-align: center;
margin-top: 20px;
color: #666;
}
.login-link a {
color: #409eff;
text-decoration: none;
}
.login-link a:hover {
text-decoration: underline;
}
</style>
</head>
<body>
<div class="register-container">
<div class="register-header">
<h2>用户注册</h2>
<p>创建您的账号</p>
</div>
<form action="#" method="post" class="layui-form register-form">
<div class="layui-form-item">
<div class="layui-input-block" style="margin-left: 0;">
<input type="account" name="account" required lay-verify="required|account" placeholder="请输入邮箱"
autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block" style="margin-left: 0;">
<div class="layui-input-inline" style="width: calc(100% - 120px);">
<input type="text" name="code" required lay-verify="required" placeholder="请输入验证码"
autocomplete="off" class="layui-input">
</div>
<div class="layui-input-inline" style="width: 110px;">
<button type="button" class="layui-btn" id="getCode">获取验证码</button>
</div>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block" style="margin-left: 0;">
<input type="password" name="password" required lay-verify="required" placeholder="请输入密码"
autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block" style="margin-left: 0;">
<input type="password" name="repassword" required lay-verify="required" placeholder="请确认密码"
autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block" style="margin-left: 0;">
<button class="layui-btn layui-btn-fluid" lay-submit lay-filter="register">注册</button>
</div>
</div>
<div class="login-link">
已有账号?<a href="{:url('/index/user/login')}">立即登录</a>
</div>
</form>
</div>
<script>
layui.use(['form', 'layer'], function () {
var form = layui.form;
var layer = layui.layer;
var $ = layui.$;
// 注册表单提交
form.on('submit(register)', function (data) {
$.ajax({
url: '{:url("index/user/register")}',
type: 'POST',
data: data.field,
success: function (res) {
if (res.code === 0) {
layer.msg('注册成功', {
icon: 1,
time: 1000
}, function () {
window.location.href = '{:url("index/user/login")}';
});
} else {
layer.msg(res.msg);
}
},
error: function () {
layer.msg('网络错误,请稍后重试');
}
});
return false;
});
// 获取验证码按钮点击事件
document.getElementById('getCode').addEventListener('click', function () {
var account = document.querySelector('input[name="account"]').value;
if (!account) {
layer.msg('请先输入邮箱地址');
return;
}
// 发送邮箱验证码请求
$.ajax({
url: '{:url("index/user/sendEmailCode")}',
type: 'POST',
data: { account: account },
dataType: 'json', // 添加这行
success: function (res) {
if (res.code === 0) {
layer.msg('验证码已发送,请查收邮件');
// 禁用按钮60秒
var btn = document.getElementById('getCode');
var countdown = 60;
btn.disabled = true;
var timer = setInterval(function () {
if (countdown > 0) {
btn.innerHTML = countdown + '秒后重试';
countdown--;
} else {
btn.disabled = false;
btn.innerHTML = '获取验证码';
clearInterval(timer);
}
}, 1000);
} else {
layer.msg(res.msg);
}
},
error: function (xhr, status, error) {
console.log(xhr.responseText); // 添加调试信息
layer.msg('网络错误,请稍后重试');
}
});
});
});
</script>
</body>
</html>
+91
View File
@@ -0,0 +1,91 @@
{include file="component/head" /}
<form class="layui-form" action="/index/user/updatePassword" method="post">
<div class="layui-form-item">
<label class="layui-form-label">旧密码</label>
<div class="layui-input-block">
<input type="password" name="old_password" required lay-verify="required" placeholder="请输入旧密码"
autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">新密码</label>
<div class="layui-input-block">
<input type="password" name="new_password" required lay-verify="required|password" placeholder="请输入新密码"
autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">确认密码</label>
<div class="layui-input-block">
<input type="password" name="confirm_password" required lay-verify="required|confirmPassword"
placeholder="请再次输入新密码" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit lay-filter="updatePassword">立即修改</button>
<button type="reset" class="layui-btn layui-btn-primary">重置</button>
</div>
</div>
</form>
<script>
layui.use(['form', 'layer'], function () {
var form = layui.form;
var layer = layui.layer;
var $ = layui.$;
// 自定义验证规则
form.verify({
password: [
/^[\S]{6,20}$/,
'密码长度必须在6-20个字符之间'
],
confirmPassword: function (value) {
var password = document.querySelector('input[name=new_password]').value;
if (value !== password) {
return '两次输入的密码不一致';
}
}
});
// 监听提交
form.on('submit(updatePassword)', function (data) {
// 显示加载中
var loadIndex = layer.load(2);
$.ajax({
url: '/index/user/updatePassword',
type: 'POST',
data: data.field,
dataType: 'json',
success: function (res) {
layer.close(loadIndex);
if (res.code === 0) {
layer.msg(res.msg, {
icon: 1,
time: 1000,
end: function() {
// 设置跳转标记
parent.window.needRedirect = true;
// 关闭当前弹窗
var index = parent.layer.getFrameIndex(window.name);
parent.layer.close(index);
}
});
} else {
layer.msg(res.msg, { icon: 2 });
}
},
error: function() {
layer.close(loadIndex);
layer.msg('请求失败,请重试', { icon: 2 });
}
});
return false; // 阻止表单默认提交
});
});
</script>