diff --git a/.vscode/settings.json b/.vscode/settings.json
index 2f47a36..6bb4812 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -6,7 +6,7 @@
"typescript.updateImportsOnFileMove.enabled": "always",
"javascript.updateImportsOnFileMove.enabled": "always",
"files.associations": {
- "*.pug": "pug"
+ "*.pug": "jade"
},
"emmet.includeLanguages": {
"pug": "html"
diff --git a/database/development.sqlite3-shm b/database/development.sqlite3-shm
index c77930b..46c674f 100644
Binary files a/database/development.sqlite3-shm and b/database/development.sqlite3-shm differ
diff --git a/database/development.sqlite3-wal b/database/development.sqlite3-wal
index a90505f..e234aab 100644
Binary files a/database/development.sqlite3-wal and b/database/development.sqlite3-wal differ
diff --git a/public/js/profile.js b/public/js/profile.js
index 116296d..f3b5713 100644
--- a/public/js/profile.js
+++ b/public/js/profile.js
@@ -12,6 +12,7 @@
bindInputValidation();
showInitialMessage();
initTabs();
+ initAvatarUpload();
}
// 绑定表单事件
@@ -133,6 +134,11 @@
return false;
}
+ if (data.avatar && !isValidImageUrl(data.avatar)) {
+ showMessage('请输入有效的图片链接或路径', 'error', 'profileForm');
+ return false;
+ }
+
return true;
}
@@ -206,6 +212,13 @@
}
break;
+ case 'avatar':
+ if (value && !isValidImageUrl(value)) {
+ isValid = false;
+ errorMessage = '请输入有效的图片链接或路径';
+ }
+ break;
+
case 'newPassword':
if (value && value.length < 6) {
isValid = false;
@@ -252,6 +265,28 @@
return emailRegex.test(email);
}
+ // 验证图片URL格式(支持相对路径和绝对路径)
+ function isValidImageUrl(url) {
+ if (!url) return true; // 空值时认为有效
+
+ // 支持的图片格式
+ const imageExtensions = /\.(jpg|jpeg|png|gif|webp|svg|bmp|ico)(\?.*)?$/i;
+
+ // 检查是否以支持的图片格式结尾
+ if (imageExtensions.test(url)) {
+ return true;
+ }
+
+ // 检查是否为完整的URL(http或https开头)
+ try {
+ const urlObj = new URL(url);
+ return urlObj.protocol === 'http:' || urlObj.protocol === 'https:';
+ } catch {
+ // 如果不是完整URL,检查是否为相对路径(以/开头)
+ return url.startsWith('/');
+ }
+ }
+
// 设置按钮加载状态
function setButtonLoading(button, loading, originalText = null) {
if (loading) {
@@ -445,5 +480,152 @@
window.resetForm = resetForm;
window.resetPasswordForm = resetPasswordForm;
window.closeMessage = closeMessage;
+ window.handleAvatarSelect = handleAvatarSelect;
+
+ // 初始化头像上传功能
+ function initAvatarUpload() {
+ const avatarInput = document.getElementById('avatarFile');
+ const avatarPreview = document.querySelector('.avatar-preview');
+ const avatarUrlInput = document.getElementById('avatar');
+
+ if (avatarUrlInput) {
+ avatarUrlInput.addEventListener('input', function() {
+ if (this.value.trim()) {
+ updateAvatarPreview(this.value.trim());
+ }
+ });
+ }
+ }
+
+ // 处理头像文件选择
+ function handleAvatarSelect(input) {
+ const file = input.files[0];
+ if (!file) return;
+
+ // 验证文件类型
+ if (!file.type.startsWith('image/')) {
+ showMessage('请选择图片文件', 'error', 'profileForm');
+ return;
+ }
+
+ // 验证文件大小 (5MB)
+ if (file.size > 5 * 1024 * 1024) {
+ showMessage('图片文件大小不能超过 5MB', 'error', 'profileForm');
+ return;
+ }
+
+ // 预览图片
+ const reader = new FileReader();
+ reader.onload = function(e) {
+ updateAvatarPreview(e.target.result);
+ };
+ reader.readAsDataURL(file);
+
+ // 上传文件
+ uploadAvatarFile(file);
+ }
+
+ // 更新头像预览
+ function updateAvatarPreview(imageSrc) {
+ const avatarPreview = document.querySelector('.avatar-preview');
+ const sidebarAvatar = document.querySelector('.profile-avatar');
+
+ if (avatarPreview) {
+ // 移除现有内容
+ avatarPreview.innerHTML = '';
+
+ // 创建新的图片元素
+ const img = document.createElement('img');
+ img.src = imageSrc;
+ img.alt = '头像预览';
+ img.id = 'avatarPreviewImg';
+ avatarPreview.appendChild(img);
+
+ // 添加覆盖层
+ const overlay = document.createElement('div');
+ overlay.className = 'upload-overlay';
+ overlay.innerHTML = '
';
+ avatarPreview.appendChild(overlay);
+ }
+
+ // 同时更新侧边栏头像
+ if (sidebarAvatar) {
+ sidebarAvatar.innerHTML = `
`;
+ }
+ }
+
+ // 上传头像文件
+ async function uploadAvatarFile(file) {
+ const progressContainer = document.getElementById('uploadProgress');
+ const progressBar = document.getElementById('uploadProgressBar');
+
+ try {
+ // 显示进度条
+ if (progressContainer) {
+ progressContainer.style.display = 'block';
+ progressBar.style.width = '0%';
+ }
+
+ const formData = new FormData();
+ formData.append('avatar', file);
+
+ // 模拟进度更新
+ let progress = 0;
+ const progressInterval = setInterval(() => {
+ progress += Math.random() * 30;
+ if (progress > 90) {
+ clearInterval(progressInterval);
+ progress = 90;
+ }
+ if (progressBar) {
+ progressBar.style.width = progress + '%';
+ }
+ }, 200);
+
+ const response = await fetch('/profile/upload-avatar', {
+ method: 'POST',
+ body: formData
+ });
+
+ const result = await response.json();
+
+ // 清除进度条动画
+ clearInterval(progressInterval);
+
+ if (result.success) {
+ // 完成进度条
+ if (progressBar) {
+ progressBar.style.width = '100%';
+ }
+
+ // 更新头像URL输入框
+ const avatarUrlInput = document.getElementById('avatar');
+ if (avatarUrlInput && result.url) {
+ avatarUrlInput.value = result.url;
+ }
+
+ showMessage('头像上传成功!', 'success', 'profileForm');
+
+ // 隐藏进度条
+ setTimeout(() => {
+ if (progressContainer) {
+ progressContainer.style.display = 'none';
+ }
+ }, 1000);
+
+ } else {
+ throw new Error(result.message || '头像上传失败');
+ }
+
+ } catch (error) {
+ showMessage(error.message || '头像上传失败,请重试', 'error', 'profileForm');
+ console.error('Avatar upload error:', error);
+
+ // 隐藏进度条
+ if (progressContainer) {
+ progressContainer.style.display = 'none';
+ }
+ }
+ }
})();
diff --git a/src/controllers/Api/AuthController.js b/src/controllers/Api/AuthController.js
index 4c4e5cd..c062092 100644
--- a/src/controllers/Api/AuthController.js
+++ b/src/controllers/Api/AuthController.js
@@ -1,4 +1,4 @@
-import UserService from "services/userService.js"
+import UserService from "@/services/UserService.js"
import { R } from "utils/helper.js"
import Router from "utils/router.js"
diff --git a/src/controllers/Page/AuthPageController.js b/src/controllers/Page/AuthPageController.js
index 1fd68b0..6270aa5 100644
--- a/src/controllers/Page/AuthPageController.js
+++ b/src/controllers/Page/AuthPageController.js
@@ -1,5 +1,5 @@
import Router from "utils/router.js"
-import UserService from "services/userService.js"
+import UserService from "@/services/UserService.js"
import svgCaptcha from "svg-captcha"
import CommonError from "@/utils/error/CommonError"
import { logger } from "@/logger.js"
diff --git a/src/controllers/Page/BasePageController.js b/src/controllers/Page/BasePageController.js
index 411a431..09c5bba 100644
--- a/src/controllers/Page/BasePageController.js
+++ b/src/controllers/Page/BasePageController.js
@@ -41,13 +41,45 @@ class BasePageController {
return
}
- // 这里可以添加邮件发送逻辑或数据库存储逻辑
- // 目前只是简单的成功响应
- logger.info(`收到联系表单: ${name} (${email}) - ${subject}: ${message}`)
+ // 验证邮箱格式
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
+ if (!emailRegex.test(email)) {
+ ctx.status = 400
+ ctx.body = { success: false, message: "请输入正确的邮箱地址" }
+ return
+ }
+
+ // 验证内容长度
+ if (name.trim().length < 2) {
+ ctx.status = 400
+ ctx.body = { success: false, message: "姓名至少需要 2 个字符" }
+ return
+ }
+
+ if (message.trim().length < 10) {
+ ctx.status = 400
+ ctx.body = { success: false, message: "留言内容至少需要 10 个字符" }
+ return
+ }
+
+ try {
+ // 这里可以添加邮件发送逻辑或数据库存储逻辑
+ // 目前只是简单的成功响应和日志记录
+ logger.info(`收到联系表单: ${name} (${email}) - ${subject}: ${message}`)
+
+ // TODO: 可以在这里添加以下功能:
+ // 1. 发送邮件通知管理员
+ // 2. 将联系信息存储到数据库
+ // 3. 发送自动回复邮件给用户
- ctx.body = {
- success: true,
- message: "感谢您的留言,我们会尽快回复您!",
+ ctx.body = {
+ success: true,
+ message: "感谢您的留言,我们会尽快回复您!",
+ }
+ } catch (error) {
+ logger.error(`联系表单处理失败: ${error.message}`)
+ ctx.status = 500
+ ctx.body = { success: false, message: "系统错误,请稍后再试" }
}
}
@@ -88,6 +120,7 @@ class BasePageController {
router.get("/feedback", controller.pageGet("page/extra/feedback"), { auth: false })
router.get("/help", controller.pageGet("page/extra/help"), { auth: false })
router.get("/contact", controller.pageGet("page/extra/contact"), { auth: false })
+ router.get("/contact/success", controller.pageGet("page/extra/contactSuccess"), { auth: false })
// 需要登录的页面
router.get("/notice", controller.pageGet("page/notice/index"), { auth: true })
diff --git a/src/controllers/Page/ProfileController.js b/src/controllers/Page/ProfileController.js
index 3a3678c..be10d8b 100644
--- a/src/controllers/Page/ProfileController.js
+++ b/src/controllers/Page/ProfileController.js
@@ -1,5 +1,5 @@
import Router from "utils/router.js"
-import UserService from "services/userService.js"
+import UserService from "@/services/UserService.js"
import formidable from "formidable"
import fs from "fs/promises"
import path from "path"
diff --git a/src/services/index.js b/src/services/index.js
deleted file mode 100644
index db42d64..0000000
--- a/src/services/index.js
+++ /dev/null
@@ -1,36 +0,0 @@
-// 服务层统一导出
-import UserService from "./UserService.js"
-import ArticleService from "./ArticleService.js"
-import BookmarkService from "./BookmarkService.js"
-import SiteConfigService from "./SiteConfigService.js"
-import JobService from "./JobService.js"
-
-// 导出所有服务类
-export {
- UserService,
- ArticleService,
- BookmarkService,
- SiteConfigService,
- JobService
-}
-
-// 导出默认实例(单例模式)
-export const userService = new UserService()
-export const articleService = new ArticleService()
-export const bookmarkService = new BookmarkService()
-export const siteConfigService = new SiteConfigService()
-export const jobService = new JobService()
-
-// 默认导出
-export default {
- UserService,
- ArticleService,
- BookmarkService,
- SiteConfigService,
- JobService,
- userService,
- articleService,
- bookmarkService,
- siteConfigService,
- jobService
-}
diff --git a/src/views/page/extra/contact.pug b/src/views/page/extra/contact.pug
index f334074..16752f9 100644
--- a/src/views/page/extra/contact.pug
+++ b/src/views/page/extra/contact.pug
@@ -12,7 +12,7 @@ block pageContent
h2(class="text-2xl font-semibold mb-4 text-blue-600 flex items-center justify-center")
span(class="mr-2") 📞
| 联系方式
- .grid.grid-cols-1.md:grid-cols-3.gap-6
+ .grid.grid-cols-1.gap-6(class="md:grid-cols-3")
.contact-card(class="text-center p-6 bg-blue-50 rounded-lg border border-blue-200 hover:shadow-md transition-shadow")
.icon(class="text-4xl mb-3") 📧
h3(class="font-semibold text-blue-800 mb-2") 邮箱联系
@@ -35,27 +35,37 @@ block pageContent
span(class="mr-2") ✍️
| 留言反馈
.form-container(class="max-w-2xl mx-auto")
- form(action="/contact" method="POST" class="space-y-4")
+ // 消息提示区域
+ .message-container#messageContainer(class="mb-4 hidden")
+ .message#messageAlert(class="p-4 rounded-lg border")
+ span#messageText
+ button.message-close(type="button" onclick="closeMessage()" class="float-right text-lg font-bold cursor-pointer") ×
+
+ form#contactForm(action="/contact" method="POST" class="space-y-4")
.form-group(class="grid grid-cols-1 md:grid-cols-2 gap-4")
.input-group
label(for="name" class="block text-sm font-medium text-gray-700 mb-1") 姓名 *
- input#name(type="text" name="name" required class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent")
+ input#name(type="text" name="name" required class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors")
+ .error-message#nameError(class="text-red-500 text-sm mt-1 hidden")
.input-group
label(for="email" class="block text-sm font-medium text-gray-700 mb-1") 邮箱 *
- input#email(type="email" name="email" required class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent")
+ input#email(type="email" name="email" required class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors")
+ .error-message#emailError(class="text-red-500 text-sm mt-1 hidden")
.form-group
label(for="subject" class="block text-sm font-medium text-gray-700 mb-1") 主题 *
- select#subject(name="subject" required class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent")
+ select#subject(name="subject" required class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors")
option(value="") 请选择反馈类型
option(value="bug") 问题反馈
option(value="feature") 功能建议
option(value="content") 内容相关
option(value="other") 其他
+ .error-message#subjectError(class="text-red-500 text-sm mt-1 hidden")
.form-group
label(for="message" class="block text-sm font-medium text-gray-700 mb-1") 留言内容 *
- textarea#message(name="message" rows="5" required placeholder="请详细描述您的问题或建议..." class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-vertical")
+ textarea#message(name="message" rows="5" required placeholder="请详细描述您的问题或建议..." class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-vertical transition-colors")
+ .error-message#messageError(class="text-red-500 text-sm mt-1 hidden")
.form-group(class="text-center")
- button(type="submit" class="px-6 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors") 提交留言
+ button#submitBtn(type="submit" class="px-6 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors disabled:bg-gray-400 disabled:cursor-not-allowed") 提交留言
// 办公地址
.office-info(class="mb-8")
@@ -68,16 +78,197 @@ block pageContent
p(class="text-gray-700 mb-2") 北京市朝阳区某某大厦
p(class="text-gray-700 mb-2") 邮编:100000
p(class="text-sm text-gray-500") 工作时间:周一至周五 9:00-18:00
-
- // 相关链接
- .contact-links(class="text-center pt-6 border-t border-gray-200")
- p(class="text-gray-600 mb-3") 更多帮助资源:
- .links(class="flex flex-wrap justify-center gap-4")
- a(href="/help" class="text-blue-600 hover:text-blue-800 hover:underline") 帮助中心
- a(href="/faq" class="text-blue-600 hover:text-blue-800 hover:underline") 常见问题
- a(href="/feedback" class="text-blue-600 hover:text-blue-800 hover:underline") 意见反馈
- a(href="/about" class="text-blue-600 hover:text-blue-800 hover:underline") 关于我们
-
.contact-footer(class="text-center mt-8 pt-6 border-t border-gray-200")
p(class="text-gray-500 text-sm") 我们承诺保护您的隐私,所有联系信息仅用于回复您的反馈
p(class="text-gray-400 text-xs mt-2") 感谢您的支持与信任
+
+block pageScripts
+ script.
+ document.addEventListener('DOMContentLoaded', function() {
+ const form = document.getElementById('contactForm');
+ const submitBtn = document.getElementById('submitBtn');
+ const messageContainer = document.getElementById('messageContainer');
+ const messageAlert = document.getElementById('messageAlert');
+ const messageText = document.getElementById('messageText');
+
+ // 表单字段
+ const fields = {
+ name: document.getElementById('name'),
+ email: document.getElementById('email'),
+ subject: document.getElementById('subject'),
+ message: document.getElementById('message')
+ };
+
+ // 错误消息元素
+ const errors = {
+ name: document.getElementById('nameError'),
+ email: document.getElementById('emailError'),
+ subject: document.getElementById('subjectError'),
+ message: document.getElementById('messageError')
+ };
+
+ // 清除消息函数
+ window.closeMessage = function() {
+ messageContainer.classList.add('hidden');
+ };
+
+ // 显示消息函数
+ function showMessage(text, type = 'error') {
+ messageText.textContent = text;
+ messageAlert.className = `message p-4 rounded-lg border ${
+ type === 'success'
+ ? 'bg-green-50 border-green-200 text-green-800'
+ : 'bg-red-50 border-red-200 text-red-800'
+ }`;
+ messageContainer.classList.remove('hidden');
+
+ // 滚动到消息区域
+ messageContainer.scrollIntoView({ behavior: 'smooth', block: 'center' });
+ }
+
+ // 显示字段错误
+ function showFieldError(fieldName, message) {
+ const field = fields[fieldName];
+ const errorElement = errors[fieldName];
+
+ field.classList.add('border-red-500');
+ errorElement.textContent = message;
+ errorElement.classList.remove('hidden');
+ }
+
+ // 清除字段错误
+ function clearFieldError(fieldName) {
+ const field = fields[fieldName];
+ const errorElement = errors[fieldName];
+
+ field.classList.remove('border-red-500');
+ errorElement.classList.add('hidden');
+ }
+
+ // 清除所有错误
+ function clearAllErrors() {
+ Object.keys(fields).forEach(fieldName => {
+ clearFieldError(fieldName);
+ });
+ closeMessage();
+ }
+
+ // 验证邮箱格式
+ function isValidEmail(email) {
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+ return emailRegex.test(email);
+ }
+
+ // 表单验证
+ function validateForm() {
+ clearAllErrors();
+ let isValid = true;
+
+ // 验证姓名
+ const name = fields.name.value.trim();
+ if (!name) {
+ showFieldError('name', '请输入您的姓名');
+ isValid = false;
+ } else if (name.length < 2) {
+ showFieldError('name', '姓名至少需要 2 个字符');
+ isValid = false;
+ }
+
+ // 验证邮箱
+ const email = fields.email.value.trim();
+ if (!email) {
+ showFieldError('email', '请输入您的邮箱地址');
+ isValid = false;
+ } else if (!isValidEmail(email)) {
+ showFieldError('email', '请输入正确的邮箱地址');
+ isValid = false;
+ }
+
+ // 验证主题
+ if (!fields.subject.value) {
+ showFieldError('subject', '请选择反馈类型');
+ isValid = false;
+ }
+
+ // 验证留言内容
+ const message = fields.message.value.trim();
+ if (!message) {
+ showFieldError('message', '请输入留言内容');
+ isValid = false;
+ } else if (message.length < 10) {
+ showFieldError('message', '留言内容至少需要 10 个字符');
+ isValid = false;
+ }
+
+ return isValid;
+ }
+
+ // 设置按钮加载状态
+ function setButtonLoading(loading) {
+ if (loading) {
+ submitBtn.disabled = true;
+ submitBtn.textContent = '提交中...';
+ submitBtn.classList.add('bg-gray-400', 'cursor-not-allowed');
+ submitBtn.classList.remove('bg-blue-600', 'hover:bg-blue-700');
+ } else {
+ submitBtn.disabled = false;
+ submitBtn.textContent = '提交留言';
+ submitBtn.classList.remove('bg-gray-400', 'cursor-not-allowed');
+ submitBtn.classList.add('bg-blue-600', 'hover:bg-blue-700');
+ }
+ }
+
+ // 表单提交事件
+ form.addEventListener('submit', async function(e) {
+ e.preventDefault();
+
+ if (!validateForm()) {
+ return;
+ }
+
+ setButtonLoading(true);
+
+ try {
+ const formData = new FormData(form);
+ const data = Object.fromEntries(formData);
+
+ const response = await fetch('/contact', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify(data)
+ });
+
+ const result = await response.json();
+
+ if (result.success) {
+ // 成功后跳转到成功页面
+ window.location.href = '/contact/success';
+ } else {
+ showMessage(result.message || '提交失败,请重试', 'error');
+ }
+ } catch (error) {
+ console.error('提交错误:', error);
+ showMessage('网络错误,请检查网络连接后重试', 'error');
+ } finally {
+ setButtonLoading(false);
+ }
+ });
+
+ // 字段实时验证(去除错误状态)
+ Object.keys(fields).forEach(fieldName => {
+ const field = fields[fieldName];
+ field.addEventListener('input', function() {
+ if (field.classList.contains('border-red-500')) {
+ clearFieldError(fieldName);
+ }
+ });
+
+ field.addEventListener('change', function() {
+ if (field.classList.contains('border-red-500')) {
+ clearFieldError(fieldName);
+ }
+ });
+ });
+ });
diff --git a/src/views/page/extra/contactSuccess.pug b/src/views/page/extra/contactSuccess.pug
new file mode 100644
index 0000000..9a470e5
--- /dev/null
+++ b/src/views/page/extra/contactSuccess.pug
@@ -0,0 +1,62 @@
+extends /layouts/empty.pug
+
+block pageHead
+
+block pageContent
+ .contact-success.container(class="mt-[20px] bg-white rounded-[12px] shadow p-6 border border-gray-100")
+ .success-content(class="text-center py-8")
+ // 成功图标
+ .success-icon(class="mb-6")
+ .icon-container(class="inline-flex items-center justify-center w-20 h-20 bg-green-100 rounded-full")
+ span(class="text-4xl text-green-600") ✓
+
+ // 成功标题
+ h1(class="text-3xl font-bold mb-4 text-gray-800") 留言提交成功!
+
+ // 成功消息
+ .success-message(class="mb-8")
+ p(class="text-lg text-gray-600 mb-3") 感谢您的留言,我们已经收到了您的反馈。
+ p(class="text-gray-500") 我们会在 24小时内 通过邮箱回复您,请注意查收。
+
+ // 联系信息卡片
+ .contact-cards(class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-8")
+ .contact-card(class="text-center p-4 bg-blue-50 rounded-lg border border-blue-200")
+ .icon(class="text-2xl mb-2") 📧
+ h3(class="font-semibold text-blue-800 mb-1") 邮箱回复
+ p(class="text-sm text-gray-600") 24小时内回复
+ .contact-card(class="text-center p-4 bg-green-50 rounded-lg border border-green-200")
+ .icon(class="text-2xl mb-2") 💬
+ h3(class="font-semibold text-green-800 mb-1") 在线客服
+ p(class="text-sm text-gray-600") 工作日 9:00-18:00
+ .contact-card(class="text-center p-4 bg-purple-50 rounded-lg border border-purple-200")
+ .icon(class="text-2xl mb-2") 📱
+ h3(class="font-semibold text-purple-800 mb-1") 社交媒体
+ p(class="text-sm text-gray-600") 实时互动
+
+ // 操作按钮
+ .actions(class="flex flex-col sm:flex-row justify-center gap-4")
+ a(href="/" class="inline-flex items-center justify-center px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors")
+ span(class="mr-2") 🏠
+ | 返回首页
+ a(href="/contact" class="inline-flex items-center justify-center px-6 py-3 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-colors")
+ span(class="mr-2") ✍️
+ | 再次留言
+ a(href="/about" class="inline-flex items-center justify-center px-6 py-3 bg-green-100 text-green-700 rounded-lg hover:bg-green-200 transition-colors")
+ span(class="mr-2") ℹ️
+ | 了解我们
+
+ // 温馨提示
+ .tips(class="mt-8 p-4 bg-yellow-50 rounded-lg border border-yellow-200")
+ h3(class="font-semibold text-yellow-800 mb-2 flex items-center")
+ span(class="mr-2") 💡
+ | 温馨提示
+ ul(class="text-sm text-yellow-700 space-y-1 text-left")
+ li • 请确保您提供的邮箱地址正确,以便我们及时回复
+ li • 如有紧急问题,建议通过在线客服或电话联系我们
+ li • 您也可以关注我们的社交媒体获取最新动态和回复
+ li • 我们承诺保护您的隐私,不会泄露您的联系信息
+
+ // 页脚信息
+ .contact-footer(class="text-center mt-8 pt-6 border-t border-gray-200")
+ p(class="text-gray-500 text-sm") 再次感谢您对我们的关注和支持!
+ p(class="text-gray-400 text-xs mt-2") 如有其他问题,欢迎随时与我们联系
\ No newline at end of file
diff --git a/src/views/page/profile/index.pug b/src/views/page/profile/index.pug
index f0fc9d0..d5453c2 100644
--- a/src/views/page/profile/index.pug
+++ b/src/views/page/profile/index.pug
@@ -49,6 +49,106 @@ block pageHead
color: rgba(255,255,255,0.8);
}
+ // 头像上传相关样式
+ .avatar-upload-section {
+ position: relative;
+ margin-bottom: 20px;
+ }
+
+ .avatar-preview {
+ width: 120px;
+ height: 120px;
+ border-radius: 50%;
+ border: 3px dashed #d1d5db;
+ background: #f9fafb;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin: 0 auto 16px;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ position: relative;
+ overflow: hidden;
+ }
+
+ .avatar-preview:hover {
+ border-color: #667eea;
+ background: #f0f4ff;
+ }
+
+ .avatar-preview img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ border-radius: 50%;
+ }
+
+ .avatar-preview .avatar-upload-placeholder {
+ text-align: center;
+ color: #9ca3af;
+ font-size: 0.875rem;
+ }
+
+ .avatar-preview .upload-icon {
+ font-size: 2rem;
+ margin-bottom: 8px;
+ display: block;
+ }
+
+ .avatar-preview .upload-overlay {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0,0,0,0.6);
+ color: white;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ opacity: 0;
+ transition: opacity 0.3s ease;
+ border-radius: 50%;
+ font-size: 0.875rem;
+ text-align: center;
+ }
+
+ .avatar-preview:hover .upload-overlay {
+ opacity: 1;
+ }
+
+ .file-input-hidden {
+ position: absolute;
+ opacity: 0;
+ width: 0;
+ height: 0;
+ overflow: hidden;
+ }
+
+ .avatar-upload-info {
+ text-align: center;
+ font-size: 0.8rem;
+ color: #6b7280;
+ margin-bottom: 16px;
+ }
+
+ .upload-progress {
+ width: 100%;
+ height: 4px;
+ background: #e5e7eb;
+ border-radius: 2px;
+ overflow: hidden;
+ margin-top: 8px;
+ display: none;
+ }
+
+ .upload-progress-bar {
+ height: 100%;
+ background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
+ width: 0;
+ transition: width 0.3s ease;
+ }
+
.profile-name {
font-size: 1.5rem;
font-weight: 600;
@@ -459,9 +559,6 @@ block pageHead
}
block pageContent
- form(action="/profile/upload-avatar" method="post" enctype="multipart/form-data")
- input(type="file", name="avatar", accept="image/*" onchange="document.getElementById('upload-btn').click()")
- button#upload-btn(type="submit") 上传头像
.profile-container
.profile-sidebar
.profile-avatar
@@ -553,14 +650,44 @@ block pageContent
placeholder="介绍一下自己..."
)= user.bio || ''
+ // 头像上传区域
+ .form-group
+ label.form-label 头像设置
+ .avatar-upload-section
+ .avatar-preview(onclick="document.getElementById('avatarFile').click()")
+ if user.avatar
+ img(src=user.avatar alt="当前头像" id="avatarPreviewImg")
+ .upload-overlay
+ div
+ div 📷
+ div 点击更换头像
+ else
+ .avatar-upload-placeholder
+ span.upload-icon 📷
+ div 点击上传头像
+
+ input.file-input-hidden#avatarFile(
+ type="file"
+ name="avatarFile"
+ accept="image/*"
+ onchange="handleAvatarSelect(this)"
+ )
+
+ .avatar-upload-info
+ | 支持 JPG、PNG、GIF 格式,文件大小不超过 5MB
+
+ .upload-progress#uploadProgress
+ .upload-progress-bar#uploadProgressBar
+
.form-group
- label.form-label(for="avatar") 头像URL
+ label.form-label(for="avatar") 头像URL(可选)
input.form-input#avatar(
- type="url"
+ type="text"
name="avatar"
value=user.avatar || ''
- placeholder="请输入头像图片链接"
+ placeholder="或直接输入头像图片链接"
)
+ .error-message#avatar-error
.form-actions
button.btn.btn-primary(type="submit") 保存更改