From 46abe373d8d4788608f650b46eb47f7662f08a3b Mon Sep 17 00:00:00 2001 From: dash <1549469775@qq.com> Date: Fri, 26 Sep 2025 01:16:03 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8E=BB=E9=99=A4=E5=A4=9A=E4=BD=99=E7=9A=84?= =?UTF-8?q?=E4=B8=9C=E8=A5=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/css/admin.css | 1688 ------------------------------- public/css/layouts/bg-page.css | 54 - public/css/layouts/empty.css | 174 ---- public/css/layouts/markdown-reset.scss | 677 ------------- public/css/page/index copy.css | 53 - public/css/page/index.css | 146 --- public/js/admin.js | 973 ------------------ public/js/login.js | 58 -- public/js/profile.js | 631 ------------ public/js/register.js | 48 - public/lib/bg-change.js | 429 -------- src/views/admin/articles/create.pug | 225 ---- src/views/admin/articles/edit.pug | 251 ----- src/views/admin/articles/index.pug | 198 ---- src/views/admin/articles/show.pug | 158 --- src/views/admin/contacts/index.pug | 243 ----- src/views/admin/contacts/show.pug | 229 ----- src/views/admin/dashboard.pug | 125 --- src/views/helper/utils.pug | 23 + src/views/layouts/admin.pug | 128 --- src/views/layouts/base.pug | 58 -- src/views/layouts/bg-page.pug | 18 - src/views/layouts/empty.pug | 15 - src/views/layouts/page.pug | 31 - src/views/layouts/pure.pug | 16 - src/views/layouts/root.pug | 2 +- src/views/layouts/utils.pug | 23 - src/views/page/extra/contactSuccess.pug | 62 -- src/views/page/index/index copy.pug | 51 - src/views/page/index/index.pug | 10 +- src/views/temp/person.pug | 9 - 31 files changed, 33 insertions(+), 6773 deletions(-) delete mode 100644 public/css/admin.css delete mode 100644 public/css/layouts/bg-page.css delete mode 100644 public/css/layouts/empty.css delete mode 100644 public/css/layouts/markdown-reset.scss delete mode 100644 public/css/page/index copy.css delete mode 100644 public/css/page/index.css delete mode 100644 public/js/admin.js delete mode 100644 public/js/login.js delete mode 100644 public/js/profile.js delete mode 100644 public/js/register.js delete mode 100644 public/lib/bg-change.js delete mode 100644 src/views/admin/articles/create.pug delete mode 100644 src/views/admin/articles/edit.pug delete mode 100644 src/views/admin/articles/index.pug delete mode 100644 src/views/admin/articles/show.pug delete mode 100644 src/views/admin/contacts/index.pug delete mode 100644 src/views/admin/contacts/show.pug delete mode 100644 src/views/admin/dashboard.pug create mode 100644 src/views/helper/utils.pug delete mode 100644 src/views/layouts/admin.pug delete mode 100644 src/views/layouts/base.pug delete mode 100644 src/views/layouts/bg-page.pug delete mode 100644 src/views/layouts/empty.pug delete mode 100644 src/views/layouts/page.pug delete mode 100644 src/views/layouts/pure.pug delete mode 100644 src/views/layouts/utils.pug delete mode 100644 src/views/page/extra/contactSuccess.pug delete mode 100644 src/views/page/index/index copy.pug delete mode 100644 src/views/temp/person.pug diff --git a/public/css/admin.css b/public/css/admin.css deleted file mode 100644 index e944999..0000000 --- a/public/css/admin.css +++ /dev/null @@ -1,1688 +0,0 @@ -/* ============================ - Admin 后台管理系统样式 - ============================ */ - -/* 基础重置 */ -.admin-body { - margin: 0; - padding: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', Arial, sans-serif; - background-color: #f8fafc; - color: #2d3748; - line-height: 1.6; -} - -/* 主要容器 */ -#admin-app { - min-height: 100vh; - display: flex; - flex-direction: column; -} - -/* ========== 顶部导航栏 ========== */ -.admin-header { - background: #ffffff; - border-bottom: 1px solid #e2e8f0; - padding: 0 1.5rem; - height: 60px; - display: flex; - align-items: center; - justify-content: space-between; - position: sticky; - top: 0; - z-index: 100; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); -} - -.admin-header-left { - display: flex; - align-items: center; -} - -.admin-logo a { - font-size: 1.25rem; - font-weight: 600; - color: #2b6cb0; - text-decoration: none; -} - -.admin-header-center { - flex: 1; - display: flex; - justify-content: center; -} - -.admin-breadcrumb { - display: flex; - align-items: center; - color: #718096; - font-size: 0.875rem; -} - -.breadcrumb-item a { - color: #4299e1; - text-decoration: none; -} - -.breadcrumb-separator { - margin: 0 0.5rem; - color: #a0aec0; -} - -.admin-header-right { - display: flex; - align-items: center; -} - -/* 用户下拉菜单 */ -.admin-user-menu { - position: relative; -} - -.dropdown { - position: relative; -} - -.dropdown-trigger { - background: none; - border: 1px solid #e2e8f0; - border-radius: 6px; - padding: 0.5rem 1rem; - cursor: pointer; - display: flex; - align-items: center; - gap: 0.5rem; - transition: border-color 0.2s; -} - -.dropdown-trigger:hover { - border-color: #cbd5e0; -} - -.dropdown-arrow { - font-size: 0.75rem; - transition: transform 0.2s; -} - -.dropdown.active .dropdown-arrow { - transform: rotate(180deg); -} - -.dropdown-menu { - position: absolute; - top: 100%; - right: 0; - background: white; - border: 1px solid #e2e8f0; - border-radius: 6px; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); - min-width: 160px; - z-index: 200; - opacity: 0; - visibility: hidden; - transform: translateY(-8px); - transition: all 0.2s; -} - -.dropdown.active .dropdown-menu { - opacity: 1; - visibility: visible; - transform: translateY(0); -} - -.dropdown-item { - display: block; - padding: 0.75rem 1rem; - color: #2d3748; - text-decoration: none; - transition: background-color 0.2s; -} - -.dropdown-item:hover { - background-color: #f7fafc; -} - -.dropdown-divider { - height: 1px; - background: #e2e8f0; - margin: 0.25rem 0; -} - -/* ========== 主要内容区域 ========== */ -.admin-main { - flex: 1; - display: flex; - min-height: calc(100vh - 60px); -} - -/* ========== 左侧导航栏 ========== */ -.admin-sidebar { - width: 250px; - background: #ffffff; - border-right: 1px solid #e2e8f0; - padding: 1.5rem 0; - overflow-y: auto; -} - -.admin-nav { - height: 100%; -} - -.nav-section { - margin-bottom: 2rem; -} - -.nav-title { - padding: 0 1.5rem; - font-size: 0.75rem; - font-weight: 600; - color: #a0aec0; - text-transform: uppercase; - letter-spacing: 0.05em; - margin-bottom: 0.5rem; -} - -.nav-list { - list-style: none; - margin: 0; - padding: 0; -} - -.nav-item { - margin: 0; -} - -.nav-link { - display: flex; - align-items: center; - padding: 0.75rem 1.5rem; - color: #4a5568; - text-decoration: none; - transition: all 0.2s; - border-left: 3px solid transparent; -} - -.nav-link:hover { - background-color: #f7fafc; - color: #2b6cb0; -} - -.nav-link.active { - background-color: #ebf8ff; - color: #2b6cb0; - border-left-color: #4299e1; - font-weight: 500; -} - -.nav-icon { - margin-right: 0.75rem; - font-size: 1rem; -} - -/* ========== 右侧内容区域 ========== */ -.admin-content { - flex: 1; - background: #f8fafc; - overflow-y: auto; -} - -.admin-content-inner { - padding: 2rem; - max-width: 1200px; - margin: 0 auto; -} - -/* ========== Toast 消息 ========== */ -.admin-toast { - position: fixed; - top: 80px; - right: 20px; - background: white; - border: 1px solid #e2e8f0; - border-radius: 8px; - padding: 1rem 1.5rem; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); - z-index: 1000; - display: flex; - align-items: center; - justify-content: space-between; - gap: 1rem; - min-width: 300px; - max-width: 500px; - transition: opacity 0.3s; -} - -.admin-toast.toast-success { - border-left: 4px solid #48bb78; - background: #f0fff4; -} - -.admin-toast.toast-error { - border-left: 4px solid #f56565; - background: #fff5f5; -} - -.admin-toast.toast-warning { - border-left: 4px solid #ed8936; - background: #fffaf0; -} - -.admin-toast.toast-info { - border-left: 4px solid #4299e1; - background: #ebf8ff; -} - -.toast-close { - background: none; - border: none; - cursor: pointer; - font-size: 1.25rem; - color: #a0aec0; - padding: 0; - line-height: 1; -} - -.toast-close:hover { - color: #718096; -} - -/* ========== 页面组件 ========== */ - -/* 页面容器 */ -.page-container { - background: white; - border-radius: 8px; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); - overflow: hidden; -} - -/* 页面头部 */ -.page-header { - padding: 2rem; - border-bottom: 1px solid #e2e8f0; - display: flex; - justify-content: space-between; - align-items: flex-start; -} - -.page-header-left { - flex: 1; -} - -.page-title { - font-size: 1.875rem; - font-weight: 600; - color: #1a202c; - margin: 0 0 0.5rem 0; -} - -.page-subtitle { - color: #718096; - margin: 0; -} - -.page-header-right { - flex-shrink: 0; - margin-left: 2rem; -} - -/* 面包屑 */ -.breadcrumb { - margin-bottom: 1rem; - font-size: 0.875rem; - color: #718096; -} - -.breadcrumb a { - color: #4299e1; - text-decoration: none; -} - -.breadcrumb-separator { - margin: 0 0.5rem; -} - -.breadcrumb-current { - color: #2d3748; - font-weight: 500; -} - -/* ========== 表单组件 ========== */ - -/* 按钮 */ -.btn { - display: inline-flex; - align-items: center; - justify-content: center; - padding: 0.5rem 1rem; - border: 1px solid transparent; - border-radius: 6px; - font-size: 0.875rem; - font-weight: 500; - text-decoration: none; - cursor: pointer; - transition: all 0.2s; - gap: 0.5rem; -} - -.btn-primary { - background: #4299e1; - color: white; - border-color: #4299e1; -} - -.btn-primary:hover { - background: #3182ce; - border-color: #3182ce; -} - -.btn-secondary { - background: #718096; - color: white; - border-color: #718096; -} - -.btn-secondary:hover { - background: #4a5568; - border-color: #4a5568; -} - -.btn-success { - background: #48bb78; - color: white; - border-color: #48bb78; -} - -.btn-success:hover { - background: #38a169; - border-color: #38a169; -} - -.btn-danger { - background: #f56565; - color: white; - border-color: #f56565; -} - -.btn-danger:hover { - background: #e53e3e; - border-color: #e53e3e; -} - -.btn-outline { - background: transparent; - color: #4a5568; - border-color: #e2e8f0; -} - -.btn-outline:hover { - background: #f7fafc; - border-color: #cbd5e0; -} - -.btn-sm { - padding: 0.25rem 0.75rem; - font-size: 0.75rem; -} - -/* 表单元素 */ -.form-group { - margin-bottom: 1.5rem; -} - -.form-label { - display: block; - margin-bottom: 0.5rem; - font-weight: 500; - color: #2d3748; -} - -.form-input, -.form-select, -.form-textarea { - width: 100%; - padding: 0.75rem; - border: 1px solid #e2e8f0; - border-radius: 6px; - font-size: 0.875rem; - transition: border-color 0.2s, box-shadow 0.2s; -} - -.form-input:focus, -.form-select:focus, -.form-textarea:focus { - outline: none; - border-color: #4299e1; - box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.1); -} - -.form-textarea { - resize: vertical; - min-height: 100px; -} - -.form-help { - margin-top: 0.25rem; - font-size: 0.75rem; - color: #718096; -} - -/* ========== 表格组件 ========== */ -.table-container { - overflow-x: auto; - border: 1px solid #e2e8f0; - border-radius: 8px; -} - -.table { - width: 100%; - border-collapse: collapse; -} - -.table th, -.table td { - padding: 0.75rem 1rem; - text-align: left; - border-bottom: 1px solid #e2e8f0; -} - -.table th { - background: #f7fafc; - font-weight: 600; - color: #2d3748; - font-size: 0.875rem; -} - -.table tbody tr:hover { - background: #f7fafc; -} - -/* ========== 状态徽章 ========== */ -.status-badge { - display: inline-flex; - align-items: center; - padding: 0.25rem 0.75rem; - border-radius: 9999px; - font-size: 0.75rem; - font-weight: 500; - gap: 0.25rem; -} - -.status-badge.status-published { - background: #c6f6d5; - color: #22543d; -} - -.status-badge.status-draft { - background: #feebc8; - color: #744210; -} - -.status-badge.status-unread { - background: #bee3f8; - color: #2a4365; -} - -.status-badge.status-read { - background: #d6f5d6; - color: #22543d; -} - -.status-badge.status-replied { - background: #c6f6d5; - color: #22543d; -} - -/* ========== 分页组件 ========== */ -.pagination-container { - margin-top: 2rem; - display: flex; - justify-content: center; -} - -.pagination { - display: flex; - gap: 0.25rem; -} - -.pagination-link { - padding: 0.5rem 0.75rem; - border: 1px solid #e2e8f0; - border-radius: 6px; - color: #4a5568; - text-decoration: none; - transition: all 0.2s; -} - -.pagination-link:hover { - background: #f7fafc; - border-color: #cbd5e0; -} - -.pagination-link.active { - background: #4299e1; - color: white; - border-color: #4299e1; -} - -.pagination-ellipsis { - padding: 0.5rem 0.75rem; - color: #a0aec0; -} - -/* ========== 特定页面样式 ========== */ - -/* 仪表盘 */ -.dashboard { - padding: 2rem; -} - -.stats-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); - gap: 1.5rem; - margin-bottom: 2rem; -} - -.stats-card { - background: white; - border-radius: 8px; - padding: 1.5rem; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); - display: flex; - align-items: flex-start; - gap: 1rem; -} - -.stats-icon { - font-size: 2rem; - padding: 0.75rem; - border-radius: 8px; -} - -.stats-icon-primary { - background: #ebf8ff; -} - -.stats-icon-success { - background: #f0fff4; -} - -.stats-number { - font-size: 2rem; - font-weight: 600; - color: #1a202c; - margin-bottom: 0.25rem; -} - -.stats-label { - color: #718096; - font-size: 0.875rem; -} - -.stats-breakdown { - margin-top: 0.5rem; - display: flex; - flex-wrap: wrap; - gap: 0.75rem; - font-size: 0.75rem; -} - -.breakdown-item { - color: #718096; -} - -.breakdown-value { - font-weight: 500; - color: #2d3748; -} - -/* 文章表格 */ -.article-table-container { - background: white; - border-radius: 8px; - overflow: hidden; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); -} - -.article-table { - width: 100%; - border-collapse: collapse; -} - -.article-table th { - background: #f7fafc; - padding: 1rem; - font-weight: 600; - color: #2d3748; - border-bottom: 1px solid #e2e8f0; -} - -.article-table td { - padding: 1rem; - border-bottom: 1px solid #f1f5f9; - vertical-align: top; -} - -.article-row:hover { - background: #f8fafc; -} - -.article-title-cell h3 { - margin: 0 0 0.5rem 0; - font-size: 1rem; - font-weight: 500; -} - -.article-title-cell a { - color: #2b6cb0; - text-decoration: none; -} - -.article-title-cell a:hover { - color: #2c5aa0; - text-decoration: underline; -} - -.article-summary { - color: #718096; - font-size: 0.875rem; - margin: 0.5rem 0; -} - -.article-tags { - display: flex; - flex-wrap: wrap; - gap: 0.25rem; - margin-top: 0.5rem; -} - -.tag { - background: #edf2f7; - color: #4a5568; - padding: 0.125rem 0.5rem; - border-radius: 12px; - font-size: 0.75rem; -} - -.category-badge { - background: #e6fffa; - color: #234e52; - padding: 0.25rem 0.75rem; - border-radius: 12px; - font-size: 0.75rem; - font-weight: 500; -} - -.date-info { - font-size: 0.875rem; -} - -.primary-date { - color: #2d3748; - font-weight: 500; -} - -.secondary-date { - color: #718096; - font-size: 0.75rem; -} - -.action-buttons { - display: flex; - gap: 0.5rem; - align-items: center; -} - -/* 联系信息表格 */ -.contact-table-container { - background: white; - border-radius: 8px; - overflow: hidden; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); -} - -.contact-table { - width: 100%; - border-collapse: collapse; -} - -.contact-table th { - background: #f7fafc; - padding: 1rem; - font-weight: 600; - color: #2d3748; - border-bottom: 1px solid #e2e8f0; -} - -.contact-table td { - padding: 1rem; - border-bottom: 1px solid #f1f5f9; - vertical-align: top; -} - -.contact-row:hover { - background: #f8fafc; -} - -.contact-row.status-unread { - background: #f0f9ff; -} - -.contact-info { - display: flex; - flex-direction: column; - gap: 0.25rem; -} - -.contact-name { - font-weight: 500; - color: #2d3748; -} - -.contact-email { - color: #4299e1; - font-size: 0.875rem; -} - -.contact-ip { - color: #718096; - font-size: 0.75rem; -} - -.subject-content h4 { - margin: 0 0 0.5rem 0; - font-size: 1rem; - font-weight: 500; - color: #2d3748; -} - -.subject-preview { - color: #718096; - font-size: 0.875rem; -} - -.status-dropdown { - margin: 0.25rem 0; -} - -.status-select { - padding: 0.25rem 0.5rem; - border: 1px solid #e2e8f0; - border-radius: 4px; - font-size: 0.75rem; - background: white; -} - -/* 筛选组件 */ -.page-filters { - background: white; - padding: 1.5rem 2rem; - border-bottom: 1px solid #e2e8f0; -} - -.filter-form { - display: flex; - gap: 1.5rem; - align-items: flex-end; - flex-wrap: wrap; -} - -.filter-group { - display: flex; - flex-direction: column; - gap: 0.5rem; -} - -.filter-label { - font-size: 0.875rem; - font-weight: 500; - color: #2d3748; -} - -.filter-select { - padding: 0.5rem; - border: 1px solid #e2e8f0; - border-radius: 6px; - font-size: 0.875rem; - min-width: 150px; -} - -.search-box { - display: flex; - border: 1px solid #e2e8f0; - border-radius: 6px; - overflow: hidden; -} - -.search-input { - flex: 1; - padding: 0.5rem 0.75rem; - border: none; - outline: none; - min-width: 200px; -} - -.search-btn { - padding: 0.5rem 0.75rem; - background: #4299e1; - color: white; - border: none; - cursor: pointer; - transition: background-color 0.2s; -} - -.search-btn:hover { - background: #3182ce; -} - -.filter-clear { - color: #e53e3e; - text-decoration: none; - font-size: 0.875rem; - padding: 0.5rem 0; -} - -.filter-clear:hover { - text-decoration: underline; -} - -/* 内容区域 */ -.content-section { - padding: 2rem; -} - -/* 空状态 */ -.empty-state { - text-align: center; - padding: 4rem 2rem; - color: #718096; -} - -.empty-icon { - font-size: 4rem; - margin-bottom: 1rem; -} - -.empty-title { - font-size: 1.25rem; - font-weight: 500; - color: #2d3748; - margin-bottom: 0.5rem; -} - -.empty-text { - margin-bottom: 1.5rem; -} - -/* 列表底部 */ -.list-footer { - padding: 1rem 2rem; - border-top: 1px solid #e2e8f0; - background: #f8fafc; - color: #718096; - font-size: 0.875rem; -} - -/* ========== 文章管理特定样式 ========== */ - -/* 文章表单样式 */ -.article-form { - background: white; -} - -.form-grid { - display: grid; - grid-template-columns: 1fr 300px; - gap: 2rem; -} - -.form-main { - flex: 1; -} - -.form-sidebar { - background: #f8fafc; - border: 1px solid #e2e8f0; - border-radius: 8px; - padding: 1.5rem; - height: fit-content; - position: sticky; - top: 1rem; -} - -.sidebar-section { - margin-bottom: 2rem; -} - -.sidebar-section:last-child { - margin-bottom: 0; -} - -.sidebar-title { - font-size: 1rem; - font-weight: 600; - color: #2d3748; - margin: 0 0 1rem 0; - border-bottom: 1px solid #e2e8f0; - padding-bottom: 0.5rem; -} - -.content-editor { - min-height: 400px; - font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; - font-size: 0.875rem; - line-height: 1.6; -} - -.form-actions { - margin-top: 2rem; - padding-top: 2rem; - border-top: 1px solid #e2e8f0; -} - -.action-buttons { - display: flex; - gap: 1rem; - align-items: center; -} - -/* 文章信息显示 */ -.article-meta { - display: flex; - flex-wrap: wrap; - gap: 1rem; - margin-top: 1rem; - font-size: 0.875rem; -} - -.meta-item { - display: flex; - align-items: center; - gap: 0.5rem; -} - -.article-view { - max-width: none; -} - -.article-summary-section, -.article-content-section, -.article-tags-section, -.article-link-section, -.article-technical-section { - margin-bottom: 2rem; - padding: 1.5rem; - border: 1px solid #e2e8f0; - border-radius: 8px; - background: white; -} - -.section-title { - font-size: 1.125rem; - font-weight: 600; - color: #2d3748; - margin: 0 0 1rem 0; - padding-bottom: 0.5rem; - border-bottom: 1px solid #e2e8f0; -} - -.article-content { - background: #f8fafc; - padding: 1.5rem; - border-radius: 6px; - border: 1px solid #e2e8f0; - line-height: 1.7; - color: #2d3748; -} - -.empty-content { - color: #a0aec0; - font-style: italic; - text-align: center; - padding: 2rem; -} - -.link-info { - font-size: 0.875rem; -} - -.article-link { - color: #4299e1; - text-decoration: none; - margin-left: 0.5rem; -} - -.article-link:hover { - text-decoration: underline; -} - -.technical-info { - background: #f8fafc; - padding: 1rem; - border-radius: 6px; - border: 1px solid #e2e8f0; -} - -.info-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: 1rem; -} - -.info-item { - display: flex; - flex-direction: column; - gap: 0.25rem; -} - -.info-label { - font-size: 0.75rem; - font-weight: 600; - color: #718096; - text-transform: uppercase; - letter-spacing: 0.05em; -} - -.info-value { - font-size: 0.875rem; - color: #2d3748; - word-break: break-all; -} - -/* ========== 联系信息管理特定样式 ========== */ - -/* 统计摘要 */ -.stats-summary { - display: flex; - gap: 1.5rem; - align-items: center; -} - -.stat-item { - display: flex; - flex-direction: column; - align-items: center; - padding: 0.75rem 1rem; - background: white; - border: 1px solid #e2e8f0; - border-radius: 8px; - min-width: 80px; -} - -.stat-item.unread { - background: #f0f9ff; - border-color: #bfdbfe; -} - -.stat-number { - font-size: 1.5rem; - font-weight: 600; - color: #2d3748; - margin-bottom: 0.25rem; -} - -.stat-label { - font-size: 0.75rem; - color: #718096; - text-align: center; -} - -/* 联系信息详情 */ -.contact-details { - max-width: none; -} - -.detail-section { - margin-bottom: 2rem; - padding: 1.5rem; - border: 1px solid #e2e8f0; - border-radius: 8px; - background: white; -} - -.subject-content { - font-size: 1.125rem; - font-weight: 500; - color: #2d3748; - line-height: 1.5; -} - -.message-content { - background: #f8fafc; - padding: 1.5rem; - border-radius: 6px; - border: 1px solid #e2e8f0; -} - -.message-text { - line-height: 1.7; - color: #2d3748; - white-space: pre-wrap; - word-wrap: break-word; -} - -.user-agent { - font-size: 0.75rem; - color: #718096; - word-break: break-all; -} - -.quick-actions { - background: #f8fafc; - padding: 1.5rem; - border-radius: 6px; - border: 1px solid #e2e8f0; -} - -.action-group { - margin-bottom: 1.5rem; -} - -.action-group:last-child { - margin-bottom: 0; -} - -.action-title { - font-size: 0.875rem; - font-weight: 600; - color: #4a5568; - margin: 0 0 0.75rem 0; -} - -.status-actions { - display: flex; - gap: 0.5rem; - flex-wrap: wrap; -} - -.detail-navigation { - margin-top: 2rem; - padding-top: 2rem; - border-top: 1px solid #e2e8f0; -} - -/* ========== 仪表盘特定样式 ========== */ - -.dashboard-content { - margin-top: 2rem; -} - -.dashboard-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); - gap: 2rem; - margin-bottom: 2rem; -} - -.dashboard-section { - background: white; - border-radius: 8px; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); - overflow: hidden; -} - -.section-header { - padding: 1.5rem 1.5rem 1rem 1.5rem; - border-bottom: 1px solid #e2e8f0; - display: flex; - justify-content: space-between; - align-items: center; -} - -.section-title { - font-size: 1.125rem; - font-weight: 600; - color: #2d3748; - margin: 0; -} - -.section-action { - color: #4299e1; - text-decoration: none; - font-size: 0.875rem; - font-weight: 500; -} - -.section-action:hover { - color: #3182ce; -} - -.contact-list, -.article-list { - padding: 0; -} - -.contact-item, -.article-item { - padding: 1rem 1.5rem; - border-bottom: 1px solid #f1f5f9; -} - -.contact-item:last-child, -.article-item:last-child { - border-bottom: none; -} - -.contact-item.status-unread { - background: #f0f9ff; -} - -.contact-header, -.article-header { - display: flex; - justify-content: space-between; - align-items: flex-start; - margin-bottom: 0.5rem; -} - -.contact-info, -.article-info { - flex: 1; -} - -.contact-name, -.article-title { - font-weight: 500; - color: #2d3748; - margin-bottom: 0.25rem; -} - -.contact-email { - color: #4299e1; - font-size: 0.875rem; -} - -.contact-meta, -.article-meta { - display: flex; - gap: 1rem; - font-size: 0.75rem; - color: #718096; -} - -.contact-status, -.article-status { - padding: 0.125rem 0.5rem; - border-radius: 12px; - font-size: 0.75rem; - font-weight: 500; -} - -.contact-status.status-unread, -.article-status.status-draft { - background: #feebc8; - color: #744210; -} - -.contact-status.status-read, -.contact-status.status-replied, -.article-status.status-published { - background: #c6f6d5; - color: #22543d; -} - -.contact-subject, -.article-summary { - color: #4a5568; - font-size: 0.875rem; - line-height: 1.5; - margin-bottom: 0.75rem; -} - -.contact-actions, -.article-actions { - display: flex; - gap: 0.5rem; -} - -.quick-actions { - background: white; - border-radius: 8px; - padding: 2rem; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); -} - -.quick-actions-title { - font-size: 1.25rem; - font-weight: 600; - color: #2d3748; - margin: 0 0 1.5rem 0; -} - -.quick-actions-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: 1rem; -} - -.quick-action-card { - display: flex; - flex-direction: column; - align-items: center; - text-align: center; - padding: 1.5rem; - border: 1px solid #e2e8f0; - border-radius: 8px; - text-decoration: none; - color: #2d3748; - transition: all 0.2s; -} - -.quick-action-card:hover { - border-color: #4299e1; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); - transform: translateY(-2px); -} - -.quick-action-icon { - font-size: 2rem; - margin-bottom: 0.75rem; -} - -.quick-action-title { - font-weight: 600; - margin-bottom: 0.5rem; -} - -.quick-action-desc { - font-size: 0.875rem; - color: #718096; -} - -/* ========== 通用增强样式 ========== */ - -.text-muted { - color: #a0aec0; -} - -.bulk-actions { - padding: 1rem 2rem; - background: #f8fafc; - border-top: 1px solid #e2e8f0; - font-size: 0.875rem; - color: #718096; -} - -.bulk-info { - text-align: center; -} - -/* 文章和联系信息表格列宽 */ -.col-title { - width: 40%; -} - -.col-contact { - width: 25%; -} - -.col-subject { - width: 35%; -} - -.col-status { - width: 12%; -} - -.col-category { - width: 15%; -} - -.col-date { - width: 15%; -} - -.col-actions { - width: 18%; -} - -/* 编辑器增强样式 */ -.editor-toolbar { - display: flex; - gap: 0.5rem; - padding: 0.75rem; - background: #f8fafc; - border: 1px solid #e2e8f0; - border-bottom: none; - border-radius: 6px 6px 0 0; - flex-wrap: wrap; -} - -.editor-toolbar-group { - display: flex; - gap: 0.25rem; - padding: 0 0.5rem; - border-right: 1px solid #e2e8f0; -} - -.editor-toolbar-group:last-child { - border-right: none; -} - -.editor-btn { - background: none; - border: 1px solid transparent; - border-radius: 4px; - padding: 0.25rem 0.5rem; - cursor: pointer; - font-size: 0.875rem; - transition: all 0.2s; - line-height: 1; -} - -.editor-btn:hover { - background: #e2e8f0; - border-color: #cbd5e0; -} - -.editor-toolbar + .form-textarea { - border-radius: 0 0 6px 6px; - border-top: none; -} - -/* 表单验证错误样式 */ -.field-error { - color: #f56565; - font-size: 0.75rem; - margin-top: 0.25rem; - display: block; -} - -.form-input.error, -.form-textarea.error, -.form-select.error { - border-color: #f56565; - box-shadow: 0 0 0 3px rgba(245, 101, 101, 0.1); -} - -/* 加载状态 */ -.loading { - opacity: 0.6; - pointer-events: none; - position: relative; -} - -.loading::after { - content: ''; - position: absolute; - top: 50%; - left: 50%; - width: 20px; - height: 20px; - margin: -10px 0 0 -10px; - border: 2px solid #e2e8f0; - border-top: 2px solid #4299e1; - border-radius: 50%; - animation: spin 1s linear infinite; -} - -@keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } -} - -/* 选中状态 */ -.selected { - background-color: #ebf8ff !important; -} - -/* 高亮显示 */ -.highlight { - background: #fef5e7; - border: 1px solid #f6e05e; - border-radius: 4px; - padding: 0.25rem 0.5rem; - animation: highlight-fade 2s ease-out; -} - -@keyframes highlight-fade { - 0% { background: #fef5e7; } - 100% { background: transparent; } -} - -/* 工具提示 */ -.tooltip { - position: relative; - display: inline-block; -} - -.tooltip::after { - content: attr(data-tooltip); - position: absolute; - bottom: 100%; - left: 50%; - transform: translateX(-50%); - background: #1a202c; - color: white; - padding: 0.5rem; - border-radius: 4px; - font-size: 0.75rem; - white-space: nowrap; - opacity: 0; - visibility: hidden; - transition: all 0.2s; - z-index: 1000; - margin-bottom: 5px; -} - -.tooltip::before { - content: ''; - position: absolute; - bottom: 100%; - left: 50%; - transform: translateX(-50%); - border: 5px solid transparent; - border-top-color: #1a202c; - opacity: 0; - visibility: hidden; - transition: all 0.2s; -} - -.tooltip:hover::after, -.tooltip:hover::before { - opacity: 1; - visibility: visible; -} - -/* 响应式设计 */ -@media (max-width: 1024px) { - .form-grid { - grid-template-columns: 1fr; - } - - .form-sidebar { - position: static; - margin-top: 2rem; - } - - .dashboard-grid { - grid-template-columns: 1fr; - } - - .quick-actions-grid { - grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); - } -} - -@media (max-width: 768px) { - .admin-header { - padding: 0 1rem; - } - - .admin-sidebar { - position: fixed; - left: -250px; - top: 60px; - height: calc(100vh - 60px); - z-index: 50; - transition: left 0.3s; - } - - .sidebar-open .admin-sidebar { - left: 0; - } - - .admin-content-inner { - padding: 1rem; - } - - .page-header { - flex-direction: column; - gap: 1rem; - align-items: stretch; - } - - .page-header-right { - margin-left: 0; - } - - .filter-form { - flex-direction: column; - align-items: stretch; - } - - .stats-grid { - grid-template-columns: 1fr; - } - - .stats-summary { - flex-wrap: wrap; - justify-content: center; - } - - .table-container { - font-size: 0.875rem; - } - - .article-table th, - .article-table td, - .contact-table th, - .contact-table td { - padding: 0.5rem; - } - - .action-buttons { - flex-direction: column; - align-items: stretch; - } - - .status-actions { - flex-direction: column; - } - - .quick-actions-grid { - grid-template-columns: 1fr; - } - - /* 隐藏部分表格列在小屏幕上 */ - .col-category, - .col-date .secondary-date { - display: none; - } - - .article-tags, - .contact-ip { - display: none; - } -} \ No newline at end of file diff --git a/public/css/layouts/bg-page.css b/public/css/layouts/bg-page.css deleted file mode 100644 index a15b3bd..0000000 --- a/public/css/layouts/bg-page.css +++ /dev/null @@ -1,54 +0,0 @@ -html, -body { - margin: 0; - padding: 0; - height: 100%; - font-family: Arial, sans-serif; - color: #333; -} - -body { - min-height: 100vh; - display: flex; - flex-direction: column; -} - -.page-layout { - flex: 1; - display: flex; - flex-direction: column; -} - -.page { - width: 100%; - display: flex; - flex-direction: column; - flex: 1; - position: relative; - - max-width: 1226px; - margin-right: auto; - margin-left: auto; - color: white; - -} - -.content { - flex: 1; - width: 0; -} - -@media screen and (max-width: 768px) { - .content { - padding: 0 10px; - padding-top: 40px; - } -} - -.card { - padding: 20px; - border-radius: 28px; - background: rgba(255, 255, 255, 0.1); - backdrop-filter: blur(12px); - color: #fff; -} \ No newline at end of file diff --git a/public/css/layouts/empty.css b/public/css/layouts/empty.css deleted file mode 100644 index 75d245a..0000000 --- a/public/css/layouts/empty.css +++ /dev/null @@ -1,174 +0,0 @@ - -html, -body { - margin: 0; - padding: 0; - height: 100%; - font-family: Arial, sans-serif; - color: #333; -} - -body { - min-height: 100vh; - display: flex; - flex-direction: column; - background-color: #fcfcfc; -} - -.page-layout { - flex: 1; - display: flex; - flex-direction: column; -} - -.page { - width: 100%; - /* display: flex; */ - /* flex-direction: column; */ - flex: 1; - position: relative; - -} - -.container { - max-width: 1226px; - margin-right: auto; - margin-left: auto; - /* padding-left: 20px; - padding-right: 20px; */ -} - -@media (max-width: 640px) { - .container { - padding-left: 10px; - padding-right: 10px; - } -} - -.clearfix::after { - content: ''; - display: table; - clear: both; -} - -.navbar-brand { - float: left; - height: 100%; - display: flex; - align-items: center; -} - -/* ===== 顶部导航 响应式 ===== */ -.navbar { - position: relative; -} - -.navbar .menu-toggle { - display: none; - position: absolute; - right: 12px; - top: 50%; - transform: translateY(-50%); - width: 40px; - height: 40px; - border: none; - background: transparent; - padding: 0; - cursor: pointer; -} - -.navbar .menu-toggle .bar { - display: block; - width: 22px; - height: 2px; - background: #333; - margin: 5px auto; - transition: transform .2s ease, opacity .2s ease; -} - -.navbar .mobile-menu { - display: none; - position: relative; - overflow: hidden; - max-height: 0; - transition: max-height .25s ease; - background: rgba(255, 255, 255, 0.95); - border-radius: 0 0 12px 12px; - box-shadow: 0 4px 16px rgba(0, 0, 0, .06); -} - -.navbar.open .mobile-menu { - display: none; - max-height: 400px; -} - -.navbar .mobile-menu .menu-item { - display: block; - padding: 12px 0; -} - -/* 桌面端可见区域 */ -.desktop-only { - display: block; -} - -/* <= 1024 宽度:显示切换按钮,隐藏桌面菜单 */ -@media (max-width: 1024px) { - .desktop-only { - display: none; - } - - .navbar .menu-toggle { - display: inline-block; - } - - .navbar .mobile-menu { - padding: 8px 20px 12px; - } - - .navbar.open .mobile-menu { - display: block; - } -} - -.menu { - height: 100%; - margin-left: 20px; - - .menu-item { - height: 100%; - display: flex; - align-items: center; - padding: 0 10px; - cursor: pointer; - - &:hover { - background: rgba(175, 175, 175, 0.1); - } - } -} - -.menu.left { - float: left; - - .menu-item { - float: left; - - &+.menu-item { - margin-left: 5px; - } - } -} - -.right.menu { - float: right; - - .menu-item { - float: right; - - &+.menu-item { - margin-right: 5px; - } - } -} - diff --git a/public/css/layouts/markdown-reset.scss b/public/css/layouts/markdown-reset.scss deleted file mode 100644 index d0dabe1..0000000 --- a/public/css/layouts/markdown-reset.scss +++ /dev/null @@ -1,677 +0,0 @@ - -/* 深色主题媒体查询 - 当用户系统偏好深色模式时应用 */ -@media (prefers-color-scheme: dark) { - .markdown-body { - /* 告诉浏览器使用深色配色方案,影响滚动条等系统UI元素 */ - color-scheme: dark; - } -} -/* 浅色主题媒体查询 - 当用户系统偏好浅色模式时应用 */ -@media (prefers-color-scheme: light) { - // https://verytoolz.com/blog/03bfb3598f/ - .markdown-body { - /* 告诉浏览器使用浅色配色方案,影响滚动条等系统UI元素 */ - color-scheme: light; - /* 定义CSS自定义属性,用于主题色彩管理 */ - --color-fg-default: #24292f; // 文本色-默认 - 主要文本颜色 - --color-fg-muted: #57606a; // 文本色-柔和 - 次要文本颜色 - --color-fg-subtle: #6e7781; // 文本色-微妙 - 最淡的文本颜色 - --color-canvas-default: #ffffff; // 底色-默认 - 主要背景颜色 - --color-canvas-subtle: #f6f8fa; // 底色-微妙 - 次要背景颜色 - --color-border-default: #d0d7de; // 边框色-默认 - 主要边框颜色 - --color-border-muted: hsla(210, 18%, 87%, 1); // 边框色-柔和 - 次要边框颜色 - --color-neutral-muted: rgba(175, 184, 193, 0.2); // 边框色-中性 - 中性边框颜色 - --color-accent-fg: #0969da; // 文本强调色 - 强调文本颜色 - --color-accent-emphasis: #0969da; // 背景强调色 - 强调背景颜色 - --color-attention-subtle: #fff8c5; // 背景注意色 - 注意提示背景色 - --color-danger-fg: #cf222e; // 文本危险色 - 危险/错误文本颜色 - --color-mark-default: rgb(255, 255, 0); // mark 默认色 - 标记默认背景色 - --color-mark-fg: rgb(255, 187, 0); // mark 强调色 - 标记强调背景色 - } -} - -/* Markdown内容主体样式 - 用于渲染Markdown文档的容器 */ -.markdown-body { - /* 防止iOS Safari自动调整文本大小 */ - -webkit-text-size-adjust: 100%; - /* 防止IE自动调整文本大小 */ - -ms-text-size-adjust: 100%; - /* 优化文本渲染质量,提升可读性 */ - text-rendering: optimizelegibility; - /* 重置外边距为0 */ - margin: 0; - /* 允许长单词在必要时换行,防止溢出 */ - word-wrap: break-word; - /* 使用CSS变量设置文本颜色 */ - color: var(--color-fg-muted); - /* 伪元素before - 用于清除浮动 */ - &::before { - /* 设置为表格显示模式,用于清除浮动 */ - display: table; - /* 空内容,仅用于布局 */ - content: ""; - } - /* 伪元素after - 用于清除浮动 */ - &::after { - /* 设置为表格显示模式,用于清除浮动 */ - display: table; - /* 清除左右浮动 */ - clear: both; - /* 空内容,仅用于布局 */ - content: ""; - } - /* 第一个子元素 - 移除顶部外边距 */ - > *:first-child { - /* 强制移除顶部外边距,避免不必要的空白 */ - margin-top: 0 !important; - } - /* 最后一个子元素 - 移除底部外边距 */ - > *:last-child { - /* 强制移除底部外边距,避免不必要的空白 */ - margin-bottom: 0 !important; - } - /* 块级元素统一间距设置 - 段落、引用、列表、表格等 */ - p, - blockquote, - ul, - ol, - dl, - table, - hr, - form, - pre, - details { - /* 移除顶部外边距,避免重复间距 */ - margin-top: 0; - /* 设置底部外边距为1em,保持适当间距 */ - margin-bottom: 1em; - } - /* 引用块内部元素间距处理 */ - blockquote { - /* 引用块内第一个子元素 - 移除顶部外边距 */ - & > :first-child { - margin-top: 0; - } - /* 引用块内最后一个子元素 - 移除底部外边距 */ - & > :last-child { - margin-bottom: 0; - } - } - /* 统一显示成块状元素 - 确保这些元素独占一行 */ - details, - figcaption, - figure { - /* 设置为块级元素,独占一行显示 */ - display: block; - } - /* HTML5 媒体文件跟 img 保持一致 - 内联块级元素 */ - audio, - canvas, - video { - /* 设置为内联块级元素,可以设置宽高但不会独占一行 */ - display: inline-block; - } - /* 按钮内部间距统一 - 移除Firefox默认内边距 */ - button::-moz-focus-inner, - input::-moz-focus-inner { - /* 移除Firefox浏览器按钮和输入框的内部内边距 */ - padding: 0; - /* 移除Firefox浏览器按钮和输入框的内部边框 */ - border: 0; - } - /* 定义元素显示为斜体 - 术语定义样式 */ - dfn { - /* 设置字体为斜体,用于术语定义 */ - font-style: italic; - } - /* 去掉各Table cell 的边距并让其边重合 - 表格样式统一 */ - table { - /* 合并表格边框,相邻单元格边框合并为一条 */ - border-collapse: collapse; - /* 设置表格单元格间距为0 */ - border-spacing: 0; - /* 设置为块级元素,可以设置宽高 */ - display: block; - /* 宽度根据内容自适应 */ - width: max-content; - /* 最大宽度不超过父容器 */ - max-width: 100%; - /* 内容溢出时显示滚动条 */ - overflow: auto; - } - /* 可拖动文件添加拖动手势 - 拖拽元素样式 */ - [draggable] { - /* 设置鼠标悬停时显示移动光标 */ - cursor: move; - } - /* 加粗元素 - 粗体文本样式 */ - b, - strong { - /* 设置字体粗细,使用CSS变量或默认600 */ - font-weight: var(--base-text-weight-semibold, 600); - } - /* 缩写元素样式统一 - 缩写和首字母缩写样式 */ - abbr, - acronym { - /* 移除底部边框 */ - border-bottom: none; - /* 设置字体变体为正常 */ - font-variant: normal; - /* 设置虚线下划线装饰 */ - text-decoration: underline dotted; - } - /* 添加鼠标问号,进一步确保应用的语义是正确的(要知道,交互他们也有洁癖,如果你不去掉,那得多花点口舌) */ - abbr { - /* 设置鼠标悬停时显示帮助光标 */ - cursor: help; - } - /* 一致的 del 样式 - 删除线文本样式 */ - del { - /* 设置文本装饰为删除线 */ - text-decoration: line-through; - } - /* a标签去除下划线 - 链接样式处理 */ - a { - /* 默认移除下划线,保持页面简洁 */ - text-decoration: none; - /* 没有href属性的链接样式 */ - &:not([href]) { - /* 继承父元素颜色 */ - color: inherit; - /* 移除下划线装饰 */ - text-decoration: none; - } - /* 鼠标悬停时显示下划线 */ - &:hover { - text-decoration: underline; - } - } - /* 默认不显示下划线,保持页面简洁 - 插入文本样式 */ - ins { - /* 移除下划线装饰,保持页面简洁 */ - text-decoration: none; - } - /* 专名号:虽然 u 已经重回 html5 Draft,但在所有浏览器中都是可以使用的, - * 要做到更好,向后兼容的话,添加 class="typo-u" 来显示专名号 - * 关于 标签:http://www.whatwg.org/specs/web-apps/current-work/multipage/text-level-semantics.html#the-u-element - * 被放弃的是 4,之前一直搞错 http://www.w3.org/TR/html401/appendix/changes.html#idx-deprecated - * 一篇关于 标签的很好文章:http://html5doctor.com/u-element/ - */ - u, - .typo-u { - /* 设置文本装饰为下划线,用于专名号显示 */ - text-decoration: underline; - } - /* 隐藏指定元素 - 隐藏带有hidden属性的元素 */ - [hidden] { - /* 强制隐藏元素,优先级最高 */ - display: none !important; - } - /* 伸缩框显示为列表元素 - 详情框摘要样式 */ - summary { - /* 设置为列表项显示,显示为可点击的列表项 */ - display: list-item; - } - /* 引用元素前后内容 - 移除默认引号 */ - q:before, - q:after { - /* 移除引用元素前后的默认引号内容 */ - content: ""; - } - /* 表格标题和表头文本对齐 - 默认左对齐 */ - caption, - th { - /* 设置文本左对齐 */ - text-align: left; - } - /* 居中对齐的表格标题和表头 */ - caption[align="center"], - th[align="center"] { - /* 设置文本居中对齐 */ - text-align: center; - } - /* 特定元素字体粗细统一 - 地址、标题、引用等 */ - address, - caption, - cite, - em, - th, - var { - /* 设置字体粗细为正常(400) */ - font-weight: 400; - } - /* 标记,类似于手写的荧光笔的作用 - 高亮标记样式 */ - mark { - /* 设置标记背景色,使用CSS变量 */ - background: var(--color-mark-default); - // background: #fffdd1; // 备用背景色 - // border-bottom: 1px solid #ffedce; // 备用底部边框 - /* 设置内边距,增加标记的可读性 */ - padding: 2px; - - /* 激活状态的标记样式 */ - &.active { - /* 激活时使用强调色背景 */ - background: var(--color-mark-fg); - } - // margin: 0 5px; // 备用外边距 - } - /* 统一h1元素的间隔和字体大小 - 一级标题样式 */ - h1 { - /* 设置上下外边距为0.67em */ - margin: 0.67em 0; - /* 设置字体粗细,使用CSS变量或默认600 */ - font-weight: var(--base-text-weight-semibold, 600); - /* 设置字体大小为2倍基础大小 */ - font-size: 2em; - } - /* small字体缩小 - 小字体文本样式 */ - small { - /* 设置字体大小为父元素的90% */ - font-size: 90%; - } - /* 上下标显示 - 下标和上标文本样式 */ - sub, - sup { - /* 设置字体大小为75% */ - font-size: 75%; - /* 设置行高为0,避免影响行间距 */ - line-height: 0; - /* 设置相对定位,用于精确控制位置 */ - position: relative; - /* 设置垂直对齐为基线 */ - vertical-align: baseline; - } - /* 上下标内链接样式 */ - sub a, - sup a { - /* 设置左右内边距为0.1em */ - padding: 0 0.1em; - } - /* 下标位置调整 */ - sub { - /* 向下偏移0.25em */ - bottom: -0.25em; - } - /* 上标位置调整 */ - sup { - /* 向上偏移0.5em */ - top: -0.5em; - } - /* 代码相关的字体大小统一 - 代码元素字体样式 */ - code, - kbd, - pre, - samp, - pre tt { - /* 设置字体为等宽字体,便于代码阅读 */ - font-family: monospace; - /* 设置字体大小为1em,保持一致性 */ - font-size: 1em; - } - /* 去除默认边框 - 移除字段集和图片的默认边框 */ - fieldset, - img { - /* 移除边框 */ - border: 0; - } - /* 图片初始化样式 - 图片元素基础样式 */ - img { - /* 设置边框样式为无 */ - border-style: none; - /* 设置最大宽度为100%,防止溢出 */ - max-width: 100%; - /* 设置盒模型为内容盒模型 */ - box-sizing: content-box; - /* 设置左右外边距为自动,实现居中 */ - margin: 0 auto; - /* 设置背景色,使用CSS变量 */ - background-color: var(--color-canvas-default); - } - /* 可附标题内容元素的间距 - 图片容器样式 */ - figure { - /* 设置上下外边距为1em,左右外边距为40px */ - margin: 1em 40px; - } - /* 间隔线 - 水平分隔线样式 */ - /* 一致化 horizontal rule - 统一水平分隔线样式 */ - hr { - /* 设置盒模型为内容盒模型 */ - box-sizing: content-box; - /* 隐藏溢出内容 */ - overflow: hidden; - /* 设置背景为透明 */ - background: transparent; - /* 设置底部边框,使用CSS变量 */ - border-bottom: 1px solid var(--color-border-muted); - /* 设置高度为0.25em */ - height: 0.25em; - /* 移除内边距 */ - padding: 0; - /* 设置上下外边距为24px */ - margin: 24px 0; - /* 设置背景色,使用CSS变量 */ - background-color: var(--color-border-default); - /* 移除边框 */ - border: 0; - /* 伪元素before - 用于清除浮动 */ - &::before { - /* 设置为表格显示模式,用于清除浮动 */ - display: table; - /* 空内容,仅用于布局 */ - content: ""; - } - /* 伪元素after - 用于清除浮动 */ - &::after { - /* 设置为表格显示模式,用于清除浮动 */ - display: table; - /* 清除左右浮动 */ - clear: both; - /* 空内容,仅用于布局 */ - content: ""; - } - } - - /* 表单元素并不继承父级 font 的问题 - 表单元素字体继承 */ - button, - input, - select, - textarea { - /* 继承父元素的字体样式 */ - font: inherit; - /* 移除外边距 */ - margin: 0; - /* 设置溢出为可见 */ - overflow: visible; - /* 继承父元素的字体族 */ - font-family: inherit; - /* 继承父元素的字体大小 */ - font-size: inherit; - /* 继承父元素的行高 */ - line-height: inherit; - } - /* 外观显示为按钮 - 按钮类型输入框样式 */ - [type="button"], - [type="reset"], - [type="submit"] { - /* 设置WebKit浏览器按钮外观 */ - -webkit-appearance: button; - /* 设置标准按钮外观,提高兼容性 */ - appearance: button; - } - /* 这两个表单样式规则覆盖 - 复选框和单选框样式 */ - [type="checkbox"], - [type="radio"] { - /* 设置盒模型为边框盒模型 */ - box-sizing: border-box; - /* 移除内边距 */ - padding: 0; - } - /* 数字按钮内部高度自动 - 数字输入框按钮样式 */ - [type="number"]::-webkit-inner-spin-button, - [type="number"]::-webkit-outer-spin-button { - /* 设置高度为自动,适应内容 */ - height: auto; - } - /* 搜索按钮内图标外观去除 - 搜索输入框样式 */ - [type="search"]::-webkit-search-cancel-button, - [type="search"]::-webkit-search-decoration { - /* 移除WebKit浏览器搜索框默认样式 */ - -webkit-appearance: none; - } - /* 输入框的占位符样式 - WebKit浏览器占位符样式 */ - ::-webkit-input-placeholder { - /* 继承父元素颜色 */ - color: inherit; - /* 设置透明度为0.54,创建半透明效果 */ - opacity: 0.54; - } - /* 文件选择按钮样式统一 - 文件上传按钮样式 */ - ::-webkit-file-upload-button { - /* 设置WebKit浏览器按钮外观 */ - -webkit-appearance: button; - /* 继承父元素字体样式 */ - font: inherit; - } - /* 占位符显示统一 - 通用占位符样式 */ - ::placeholder { - /* 设置占位符颜色,使用CSS变量 */ - color: var(--color-fg-subtle); - /* 设置完全不透明 */ - opacity: 1; - } - /* table内的td,th去除留白 - 表格单元格样式 */ - td, - th { - /* 移除表格单元格内边距 */ - padding: 0; - } - /* 伸缩框鼠标显示 - 详情框摘要样式 */ - details summary { - /* 设置鼠标悬停时显示手型光标 */ - cursor: pointer; - } - - /* 未展开的详情框隐藏内容 - 详情框内容显示控制 */ - details:not([open]) > *:not(summary) { - /* 强制隐藏未展开详情框内的非摘要内容 */ - display: none !important; - } - /* 按键显示 - 键盘按键样式 */ - kbd { - /* 设置为内联块级元素,可以设置宽高但不会独占一行 */ - display: inline-block; - /* 设置内边距为3px上下,5px左右 */ - padding: 3px 5px; - /* 设置字体为11px等宽字体,包含多种等宽字体备选 */ - font: 11px ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace; - /* 设置行高为10px */ - line-height: 10px; - /* 设置文本颜色,使用CSS变量 */ - color: var(--color-fg-default); - /* 设置垂直对齐为中间 */ - vertical-align: middle; - /* 设置背景色,使用CSS变量 */ - background-color: var(--color-canvas-subtle); - /* 设置边框为1px实线,使用CSS变量 */ - border: solid 1px var(--color-neutral-muted); - /* 设置底部边框颜色,使用CSS变量 */ - border-bottom-color: var(--color-neutral-muted); - /* 设置圆角为6px */ - border-radius: 6px; - /* 设置内阴影,创建按键凹陷效果 */ - box-shadow: inset 0 -1px 0 var(--color-neutral-muted); - } - /* 清除浮动工具类 - 清除浮动伪元素 */ - .clearfix:before, - .clearfix:after { - /* 空内容,仅用于布局 */ - content: ""; - /* 设置为表格显示模式,用于清除浮动 */ - display: table; - } - /* 清除浮动工具类 - after伪元素 */ - .clearfix:after { - /* 清除左右浮动 */ - clear: both; - } - /* 清除浮动工具类 - 主容器 */ - .clearfix { - /* 触发IE的hasLayout属性,用于清除浮动 */ - zoom: 1; - } - /* 强制文本换行 - 文本换行工具类 */ - .textwrap, - .textwrap td, - .textwrap th { - /* 允许长单词在必要时换行,防止溢出 */ - word-wrap: break-word; - /* 强制在任意字符间换行,防止溢出 */ - word-break: break-all; - } - /* 文本换行表格 - 固定表格布局 */ - .textwrap-table { - /* 设置表格布局为固定,提高渲染性能 */ - table-layout: fixed; - } - /* 无序列表样式 - 项目符号列表 */ - ul { - /* 重置左边距为0 */ - margin-left: 0; - /* 重置左内边距为0 */ - padding-left: 0; - /* 设置左边距为2em,创建缩进效果 */ - margin-left: 2em; - /* 设置列表样式为实心圆点 */ - list-style: disc; - } - /* 有序列表样式 - 数字列表 */ - ol { - /* 重置左边距为0 */ - margin-left: 0; - /* 重置左内边距为0 */ - padding-left: 0; - /* 设置左边距为2em,创建缩进效果 */ - margin-left: 2em; - /* 设置列表样式为数字 */ - list-style: decimal; - /* 列表项样式 */ - li { - /* 设置左内边距为0.4em,增加数字与文本间距 */ - padding-left: 0.4em; - } - } - /* 相邻列表项间距 - 列表项之间的间距 */ - li + li { - /* 设置顶部外边距为0.25em,增加列表项间距 */ - margin-top: 0.25em; - } - /* 嵌套列表样式 - 列表项内的子列表 */ - li { - /* 无序子列表样式 */ - ul { - /* 设置底部外边距为0.8em */ - margin-bottom: 0.8em; - /* 设置左边距为2em,创建嵌套缩进 */ - margin-left: 2em; - /* 设置列表样式为空心圆点 */ - list-style: circle; - /* 三级无序列表样式 */ - li { - ul { - /* 设置列表样式为实心方块 */ - list-style: square; - } - } - } - /* 有序子列表样式 */ - ol { - /* 设置底部外边距为0.8em */ - margin-bottom: 0.8em; - /* 设置左边距为2em,创建嵌套缩进 */ - margin-left: 2em; - } - } - /* 任务列表项样式 - 待办事项列表项 */ - .task-list-item { - /* 移除列表样式,不显示项目符号 */ - list-style-type: none; - /* 设置相对定位,用于绝对定位子元素 */ - position: relative; - /* 第一个子输入框样式 */ - > input { - /* 第一个子元素右边距 */ - &:nth-child(1) { - /* 设置右边距为6px */ - margin-right: 6px; - } - } - /* 标签样式 */ - label { - /* 设置字体粗细为正常(400) */ - font-weight: 400; - } - /* 拖拽手柄样式 */ - .handle { - /* 隐藏拖拽手柄 */ - display: none; - } - /* 复选框样式 */ - input[type="checkbox"] { - /* 设置宽度为0.9em */ - width: 0.9em; - /* 设置高度为0.9em */ - height: 0.9em; - /* 设置绝对定位 */ - position: absolute; - /* 向左偏移1.3em */ - left: -1.3em; - /* 向下偏移0.35em */ - top: 0.35em; - } - } - /* 启用的任务列表项样式 */ - .task-list-item.enabled { - /* 标签样式 */ - label { - /* 设置鼠标悬停时显示手型光标 */ - cursor: pointer; - } - } - /* 相邻任务列表项间距 */ - .task-list-item + .task-list-item { - /* 设置顶部外边距为3px */ - margin-top: 3px; - } - /* 包含任务列表的容器样式 */ - .contains-task-list { - // margin-left: 0.6em; // 备用左边距 - /* 从右到左文本方向样式 */ - &:dir(rtl) { - .task-list-item { - input[type="checkbox"] { - /* 设置复选框外边距,适配RTL布局 */ - margin: 0 -1.6em 0.25em 0.2em; - } - } - } - } - - /* 目录样式 - 表格目录容器 */ - .toc { - /* 重置左边距为0 */ - margin-left: 0; - } - - /* 定义列表样式 - 描述列表容器 */ - dl { - /* 设置为块级元素 */ - display: block; - /* 设置块级起始外边距为1em */ - margin-block-start: 1em; - /* 设置块级结束外边距为1em */ - margin-block-end: 1em; - /* 设置内联起始外边距为0px */ - margin-inline-start: 0px; - /* 设置内联结束外边距为0px */ - margin-inline-end: 0px; - /* 设置Unicode双向算法为隔离 */ - unicode-bidi: isolate; - /* 定义术语样式 */ - dt { - /* 设置为块级元素 */ - display: block; - /* 设置Unicode双向算法为隔离 */ - unicode-bidi: isolate; - } - /* 定义描述样式 */ - dd { - /* 设置为块级元素 */ - display: block; - /* 设置内联起始外边距为40px,创建缩进效果 */ - margin-inline-start: 40px; - /* 设置Unicode双向算法为隔离 */ - unicode-bidi: isolate; - } - } -} diff --git a/public/css/page/index copy.css b/public/css/page/index copy.css deleted file mode 100644 index 2c8f30b..0000000 --- a/public/css/page/index copy.css +++ /dev/null @@ -1,53 +0,0 @@ -.home-hero { - margin: 40px 20px 40px; - /* background: rgba(255, 255, 255, 0.1); - backdrop-filter: blur(12px); */ - text-align: center; -} -.avatar-container { - width: 120px; - height: 120px; - margin: 0 auto; - position: relative; -} -.avatar-container .author { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - color: white; - font-size: 20px; - font-weight: bold; -} -.avatar-container:hover .avatar { - transform: rotate(360deg); - left: 100%; -} -.avatar { - position: relative; - width: 100%; - height: 100%; - border-radius: 50%; - cursor: pointer; - left: 0; - transform-origin: center center; - transition: 0.5s transform ease-in-out, 0.5s left ease-in-out; -} -/* -.card { - background: rgba(255, 255, 255, 0.1); - backdrop-filter: blur(12px); - border-radius: 8px; - padding: 20px; - width: 300px; - margin: 0 auto; - margin-bottom: 40px; - text-align: center; -} */ - -@media screen and (max-width: 768px) { - .home-hero { - margin: 0; - margin-top: 20px; - } -} diff --git a/public/css/page/index.css b/public/css/page/index.css deleted file mode 100644 index 7a6483a..0000000 --- a/public/css/page/index.css +++ /dev/null @@ -1,146 +0,0 @@ -/* 首页样式 */ - -.hero-section { - position: relative; - overflow: hidden; -} - -.hero-section::before { - content: ""; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: url('/images/hero-bg.svg') no-repeat center center; - background-size: cover; - opacity: 0.1; - z-index: 0; -} - -.hero-content { - position: relative; - z-index: 1; -} - -.feature-card { - transition: all 0.3s ease; -} - -.feature-card:hover { - transform: translateY(-5px); -} - -.feature-card .material-symbols-light--article, -.feature-card .material-symbols-light--bookmark, -.feature-card .material-symbols-light--person { - transition: all 0.3s ease; -} - -.feature-card:hover .material-symbols-light--article, -.feature-card:hover .material-symbols-light--bookmark, -.feature-card:hover .material-symbols-light--person { - transform: scale(1.1); -} - -.stats-section { - position: relative; -} - -.stats-section::before { - content: ""; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: url('/images/stats-bg.svg') no-repeat center center; - background-size: cover; - opacity: 0.05; - z-index: 0; -} - -.stat-item { - transition: all 0.3s ease; -} - -.stat-item:hover { - transform: scale(1.05); -} - -.user-dashboard { - position: relative; -} - -.user-dashboard::before { - content: ""; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: url('/images/dashboard-bg.svg') no-repeat center center; - background-size: cover; - opacity: 0.03; - z-index: 0; -} - -.avatar { - transition: all 0.3s ease; -} - -.avatar:hover { - transform: scale(1.05); -} - -/* 响应式设计 */ -@media (max-width: 768px) { - .hero-section { - padding: 4rem 0; - } - - .hero-content h1 { - font-size: 2.5rem; - } - - .features-grid { - grid-template-columns: 1fr; - } - - .stats-grid { - grid-template-columns: 1fr 1fr; - } - - .user-info { - text-align: center; - margin-bottom: 1.5rem; - } - - .user-actions { - justify-content: center; - } -} - -@media (max-width: 480px) { - .hero-content h1 { - font-size: 2rem; - } - - .hero-content p { - font-size: 1rem; - } - - .stats-grid { - grid-template-columns: 1fr; - } - - .hero-actions { - flex-direction: column; - gap: 1rem; - } - - .hero-actions a { - width: 100%; - text-align: center; - } -} \ No newline at end of file diff --git a/public/js/admin.js b/public/js/admin.js deleted file mode 100644 index 9f16ca3..0000000 --- a/public/js/admin.js +++ /dev/null @@ -1,973 +0,0 @@ -/** - * Admin 后台管理系统 JavaScript - * 提供通用的交互功能和工具函数 - */ - -(function() { - 'use strict'; - - // 通用工具函数 - const AdminUtils = { - /** - * 显示Toast消息 - * @param {string} type - 消息类型 (success, error, warning, info) - * @param {string} message - 消息内容 - * @param {number} duration - 显示时长(毫秒) - */ - showToast: function(type, message, duration = 3000) { - // 移除现有的toast - const existingToast = document.querySelector('.admin-toast'); - if (existingToast) { - existingToast.remove(); - } - - const toast = document.createElement('div'); - toast.className = `admin-toast toast-${type}`; - toast.innerHTML = ` - ${message} - - `; - - document.body.appendChild(toast); - - // 自动消失 - setTimeout(() => { - this.hideToast(toast); - }, duration); - - // 点击关闭 - const closeBtn = toast.querySelector('.toast-close'); - if (closeBtn) { - closeBtn.addEventListener('click', () => { - this.hideToast(toast); - }); - } - }, - - /** - * 隐藏Toast消息 - * @param {HTMLElement} toast - Toast元素 - */ - hideToast: function(toast) { - if (toast && toast.parentNode) { - toast.style.opacity = '0'; - setTimeout(() => { - if (toast.parentNode) { - toast.parentNode.removeChild(toast); - } - }, 300); - } - }, - - /** - * 确认对话框 - * @param {string} message - 确认消息 - * @param {string} title - 对话框标题 - * @returns {boolean} 用户确认结果 - */ - confirm: function(message, title = '确认') { - return confirm(`${title}\n\n${message}`); - }, - - /** - * 发送AJAX请求 - * @param {string} url - 请求URL - * @param {object} options - 请求选项 - * @returns {Promise} 请求Promise - */ - ajax: function(url, options = {}) { - const defaultOptions = { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, - credentials: 'same-origin' - }; - - const mergedOptions = Object.assign(defaultOptions, options); - - if (mergedOptions.body && typeof mergedOptions.body === 'object') { - mergedOptions.body = JSON.stringify(mergedOptions.body); - } - - return fetch(url, mergedOptions) - .then(response => { - if (!response.ok) { - throw new Error(`HTTP ${response.status}: ${response.statusText}`); - } - return response.json(); - }) - .catch(error => { - console.error('AJAX请求失败:', error); - throw error; - }); - }, - - /** - * 防抖函数 - * @param {Function} func - 要防抖的函数 - * @param {number} delay - 延迟时间(毫秒) - * @returns {Function} 防抖后的函数 - */ - debounce: function(func, delay) { - let timeoutId; - return function(...args) { - clearTimeout(timeoutId); - timeoutId = setTimeout(() => func.apply(this, args), delay); - }; - }, - - /** - * 格式化日期 - * @param {Date|string} date - 日期对象或字符串 - * @param {string} format - 格式类型 (date, time, datetime) - * @returns {string} 格式化后的日期字符串 - */ - formatDate: function(date, format = 'datetime') { - const d = new Date(date); - if (isNaN(d.getTime())) { - return '无效日期'; - } - - const options = { - date: { year: 'numeric', month: '2-digit', day: '2-digit' }, - time: { hour: '2-digit', minute: '2-digit' }, - datetime: { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit' - } - }; - - return d.toLocaleString('zh-CN', options[format] || options.datetime); - }, - - /** - * 复制文本到剪贴板 - * @param {string} text - 要复制的文本 - * @returns {Promise} 复制是否成功 - */ - copyToClipboard: function(text) { - if (navigator.clipboard) { - return navigator.clipboard.writeText(text) - .then(() => { - this.showToast('success', '已复制到剪贴板'); - return true; - }) - .catch(err => { - console.error('复制失败:', err); - return this.fallbackCopyTextToClipboard(text); - }); - } else { - return Promise.resolve(this.fallbackCopyTextToClipboard(text)); - } - }, - - /** - * 降级复制文本到剪贴板 - * @param {string} text - 要复制的文本 - * @returns {boolean} 复制是否成功 - */ - fallbackCopyTextToClipboard: function(text) { - const textArea = document.createElement('textarea'); - textArea.value = text; - textArea.style.position = 'fixed'; - textArea.style.left = '-999999px'; - textArea.style.top = '-999999px'; - document.body.appendChild(textArea); - textArea.focus(); - textArea.select(); - - try { - const successful = document.execCommand('copy'); - if (successful) { - this.showToast('success', '已复制到剪贴板'); - } else { - this.showToast('error', '复制失败,请手动复制'); - } - return successful; - } catch (err) { - console.error('降级复制失败:', err); - this.showToast('error', '复制失败,请手动复制'); - return false; - } finally { - document.body.removeChild(textArea); - } - } - }; - - // 初始化函数 - const AdminApp = { - /** - * 初始化应用 - */ - init: function() { - this.initDropdowns(); - this.initMobileNav(); - this.initToasts(); - this.initFormValidation(); - this.initTableActions(); - this.initSearch(); - this.initArticleEditor(); - }, - - /** - * 初始化下拉菜单 - */ - initDropdowns: function() { - document.addEventListener('click', (e) => { - // 关闭所有下拉菜单 - const dropdowns = document.querySelectorAll('.dropdown'); - dropdowns.forEach(dropdown => { - if (!dropdown.contains(e.target)) { - dropdown.classList.remove('active'); - } - }); - }); - - // 下拉菜单触发器 - const dropdownTriggers = document.querySelectorAll('.dropdown-trigger'); - dropdownTriggers.forEach(trigger => { - trigger.addEventListener('click', (e) => { - e.stopPropagation(); - const dropdown = trigger.closest('.dropdown'); - dropdown.classList.toggle('active'); - }); - }); - }, - - /** - * 初始化移动端导航 - */ - initMobileNav: function() { - // 创建移动端菜单按钮 - if (window.innerWidth <= 768) { - this.createMobileMenuButton(); - } - - window.addEventListener('resize', () => { - if (window.innerWidth <= 768) { - this.createMobileMenuButton(); - } else { - this.removeMobileMenuButton(); - document.body.classList.remove('sidebar-open'); - } - }); - }, - - /** - * 创建移动端菜单按钮 - */ - createMobileMenuButton: function() { - if (document.querySelector('.mobile-menu-btn')) return; - - const button = document.createElement('button'); - button.className = 'mobile-menu-btn'; - button.innerHTML = '☰'; - button.style.cssText = ` - background: none; - border: none; - font-size: 1.25rem; - cursor: pointer; - padding: 0.5rem; - color: #4a5568; - `; - - button.addEventListener('click', () => { - document.body.classList.toggle('sidebar-open'); - }); - - const headerLeft = document.querySelector('.admin-header-left'); - if (headerLeft) { - headerLeft.insertBefore(button, headerLeft.firstChild); - } - }, - - /** - * 移除移动端菜单按钮 - */ - removeMobileMenuButton: function() { - const button = document.querySelector('.mobile-menu-btn'); - if (button) { - button.remove(); - } - }, - - /** - * 初始化现有Toast消息 - */ - initToasts: function() { - const existingToasts = document.querySelectorAll('.admin-toast'); - existingToasts.forEach(toast => { - setTimeout(() => { - AdminUtils.hideToast(toast); - }, 3000); - - const closeBtn = toast.querySelector('.toast-close'); - if (closeBtn) { - closeBtn.addEventListener('click', () => { - AdminUtils.hideToast(toast); - }); - } - }); - }, - - /** - * 初始化表单验证 - */ - initFormValidation: function() { - const forms = document.querySelectorAll('form'); - forms.forEach(form => { - form.addEventListener('submit', (e) => { - const requiredFields = form.querySelectorAll('[required]'); - let isValid = true; - - requiredFields.forEach(field => { - if (!field.value.trim()) { - isValid = false; - field.style.borderColor = '#f56565'; - - // 移除已有的错误提示 - const existingError = field.parentNode.querySelector('.field-error'); - if (existingError) { - existingError.remove(); - } - - // 添加错误提示 - const error = document.createElement('div'); - error.className = 'field-error'; - error.style.cssText = 'color: #f56565; font-size: 0.75rem; margin-top: 0.25rem;'; - error.textContent = '此字段为必填项'; - field.parentNode.appendChild(error); - } else { - field.style.borderColor = ''; - const existingError = field.parentNode.querySelector('.field-error'); - if (existingError) { - existingError.remove(); - } - } - }); - - if (!isValid) { - e.preventDefault(); - AdminUtils.showToast('error', '请填写所有必填字段'); - } - }); - - // 实时验证 - const requiredFields = form.querySelectorAll('[required]'); - requiredFields.forEach(field => { - field.addEventListener('blur', () => { - if (!field.value.trim()) { - field.style.borderColor = '#f56565'; - } else { - field.style.borderColor = ''; - const existingError = field.parentNode.querySelector('.field-error'); - if (existingError) { - existingError.remove(); - } - } - }); - }); - }); - }, - - /** - * 初始化表格操作 - */ - initTableActions: function() { - // 表格行点击 - const tableRows = document.querySelectorAll('tbody tr'); - tableRows.forEach(row => { - row.addEventListener('click', (e) => { - // 如果点击的是按钮或链接,不执行行点击事件 - if (e.target.closest('button, a, select, input')) { - return; - } - - // 高亮当前行 - tableRows.forEach(r => r.classList.remove('selected')); - row.classList.add('selected'); - }); - }); - - // 状态选择器 - const statusSelects = document.querySelectorAll('.status-select'); - statusSelects.forEach(select => { - select.addEventListener('change', (e) => { - e.stopPropagation(); - }); - }); - }, - - /** - * 初始化搜索功能 - */ - initSearch: function() { - const searchInputs = document.querySelectorAll('.search-input'); - searchInputs.forEach(input => { - // 搜索防抖 - const debouncedSearch = AdminUtils.debounce((value) => { - if (value.length >= 2 || value.length === 0) { - // 可以在这里添加实时搜索功能 - console.log('搜索:', value); - } - }, 300); - - input.addEventListener('input', (e) => { - debouncedSearch(e.target.value); - }); - - // 回车搜索 - input.addEventListener('keypress', (e) => { - if (e.key === 'Enter') { - e.preventDefault(); - e.target.closest('form').submit(); - } - }); - }); - }, - - /** - * 初始化文章编辑器增强功能 - */ - initArticleEditor: function() { - const titleInput = document.getElementById('title'); - const slugInput = document.getElementById('slug'); - const contentTextarea = document.getElementById('content'); - - if (titleInput && slugInput) { - // 自动生成slug - titleInput.addEventListener('input', AdminUtils.debounce((e) => { - if (!slugInput.value.trim() || slugInput.dataset.autoGenerated === 'true') { - const slug = this.generateSlug(e.target.value); - slugInput.value = slug; - slugInput.dataset.autoGenerated = 'true'; - } - }, 300)); - - // 手动编辑slug时停止自动生成 - slugInput.addEventListener('input', () => { - slugInput.dataset.autoGenerated = 'false'; - }); - } - - if (contentTextarea) { - // 添加工具栏 - this.addEditorToolbar(contentTextarea); - - // Tab键支持 - contentTextarea.addEventListener('keydown', (e) => { - if (e.key === 'Tab') { - e.preventDefault(); - const start = contentTextarea.selectionStart; - const end = contentTextarea.selectionEnd; - const value = contentTextarea.value; - - contentTextarea.value = value.substring(0, start) + ' ' + value.substring(end); - contentTextarea.selectionStart = contentTextarea.selectionEnd = start + 4; - } - }); - } - - // 初始化字符计数 - this.initCharCounters(); - }, - - /** - * 生成URL别名 - */ - generateSlug: function(title) { - return title - .toLowerCase() - .replace(/[^\w\u4e00-\u9fa5]+/g, '-') - .replace(/^-+|-+$/g, '') - .substring(0, 100); - }, - - /** - * 添加编辑器工具栏 - */ - addEditorToolbar: function(textarea) { - const toolbar = document.createElement('div'); - toolbar.className = 'editor-toolbar'; - toolbar.innerHTML = ` -
- - - -
-
- - - -
-
- - - -
-
- - -
- `; - - // 添加样式 - const style = document.createElement('style'); - style.textContent = ` - .editor-toolbar { - display: flex; - gap: 0.5rem; - padding: 0.75rem; - background: #f8fafc; - border: 1px solid #e2e8f0; - border-bottom: none; - border-radius: 6px 6px 0 0; - flex-wrap: wrap; - } - .editor-toolbar-group { - display: flex; - gap: 0.25rem; - padding: 0 0.5rem; - border-right: 1px solid #e2e8f0; - } - .editor-toolbar-group:last-child { - border-right: none; - } - .editor-btn { - background: none; - border: 1px solid transparent; - border-radius: 4px; - padding: 0.25rem 0.5rem; - cursor: pointer; - font-size: 0.875rem; - transition: all 0.2s; - } - .editor-btn:hover { - background: #e2e8f0; - border-color: #cbd5e0; - } - .editor-toolbar + textarea { - border-radius: 0 0 6px 6px; - } - `; - - if (!document.querySelector('#editor-toolbar-style')) { - style.id = 'editor-toolbar-style'; - document.head.appendChild(style); - } - - // 插入工具栏 - textarea.parentNode.insertBefore(toolbar, textarea); - - // 绑定事件 - toolbar.addEventListener('click', (e) => { - if (e.target.classList.contains('editor-btn')) { - e.preventDefault(); - const action = e.target.dataset.action; - this.executeEditorAction(textarea, action); - } - }); - - // 键盘快捷键 - textarea.addEventListener('keydown', (e) => { - if (e.ctrlKey || e.metaKey) { - switch (e.key.toLowerCase()) { - case 'b': - e.preventDefault(); - this.executeEditorAction(textarea, 'bold'); - break; - case 'i': - e.preventDefault(); - this.executeEditorAction(textarea, 'italic'); - break; - } - } - }); - }, - - /** - * 执行编辑器动作 - */ - executeEditorAction: function(textarea, action) { - const start = textarea.selectionStart; - const end = textarea.selectionEnd; - const selectedText = textarea.value.substring(start, end); - const beforeText = textarea.value.substring(0, start); - const afterText = textarea.value.substring(end); - - let insertText = ''; - let cursorOffset = 0; - - switch (action) { - case 'bold': - insertText = `**${selectedText || '粗体文字'}**`; - cursorOffset = selectedText ? 0 : -2; - break; - case 'italic': - insertText = `*${selectedText || '斜体文字'}*`; - cursorOffset = selectedText ? 0 : -1; - break; - case 'code': - insertText = `\`${selectedText || '代码'}\``; - cursorOffset = selectedText ? 0 : -1; - break; - case 'h1': - insertText = `# ${selectedText || '标题 1'}`; - break; - case 'h2': - insertText = `## ${selectedText || '标题 2'}`; - break; - case 'h3': - insertText = `### ${selectedText || '标题 3'}`; - break; - case 'ul': - insertText = `- ${selectedText || '列表项'}`; - break; - case 'ol': - insertText = `1. ${selectedText || '列表项'}`; - break; - case 'quote': - insertText = `> ${selectedText || '引用文字'}`; - break; - case 'link': - const linkText = selectedText || '链接文字'; - insertText = `[${linkText}](URL)`; - cursorOffset = -4; - break; - case 'image': - const altText = selectedText || '图片描述'; - insertText = `![${altText}](URL)`; - cursorOffset = -4; - break; - } - - textarea.value = beforeText + insertText + afterText; - const newCursorPos = start + insertText.length + cursorOffset; - textarea.setSelectionRange(newCursorPos, newCursorPos); - textarea.focus(); - }, - - /** - * 初始化字符计数器 - */ - initCharCounters: function() { - const fields = [ - { id: 'title', max: 200 }, - { id: 'excerpt', max: 500 }, - { id: 'tags', max: 200 }, - { id: 'slug', max: 100 } - ]; - - fields.forEach(field => { - const element = document.getElementById(field.id); - if (element) { - this.setupCharCounter(element, field.max); - } - }); - }, - - /** - * 设置字符计数器 - */ - setupCharCounter: function(element, maxLength) { - const helpElement = element.nextElementSibling; - if (!helpElement || !helpElement.classList.contains('form-help')) { - return; - } - - const originalText = helpElement.textContent; - - const updateCounter = () => { - const currentLength = element.value.length; - const remaining = maxLength - currentLength; - - if (remaining < 50) { - helpElement.textContent = `${originalText} (还可输入${remaining}字符)`; - helpElement.style.color = remaining < 10 ? '#f56565' : '#ed8936'; - } else { - helpElement.textContent = originalText; - helpElement.style.color = ''; - } - }; - - element.addEventListener('input', updateCounter); - updateCounter(); - } - }; - - // 全局删除函数 - window.deleteArticle = function(id, title) { - if (AdminUtils.confirm(`确定要删除文章《${title}》吗?此操作不可撤销。`, '删除确认')) { - AdminUtils.ajax(`/admin/articles/${id}`, { - method: 'DELETE' - }) - .then(data => { - if (data.success) { - AdminUtils.showToast('success', data.message || '文章删除成功'); - setTimeout(() => { - window.location.reload(); - }, 1000); - } else { - AdminUtils.showToast('error', data.message || '删除失败'); - } - }) - .catch(error => { - console.error('删除失败:', error); - AdminUtils.showToast('error', '删除失败,请稍后重试'); - }); - } - }; - - // 全局联系信息删除函数 - window.deleteContact = function(id, title) { - if (AdminUtils.confirm(`确定要删除联系信息《${title}》吗?此操作不可撤销。`, '删除确认')) { - AdminUtils.ajax(`/admin/contacts/${id}`, { - method: 'DELETE' - }) - .then(data => { - if (data.success) { - AdminUtils.showToast('success', data.message || '联系信息删除成功'); - setTimeout(() => { - window.location.reload(); - }, 1000); - } else { - AdminUtils.showToast('error', data.message || '删除失败'); - } - }) - .catch(error => { - console.error('删除失败:', error); - AdminUtils.showToast('error', '删除失败,请稍后重试'); - }); - } - }; - - // 全局状态更新函数 - window.updateContactStatus = function(id, status) { - if (!status) return; - - AdminUtils.ajax(`/admin/contacts/${id}/status`, { - method: 'PUT', - body: { status } - }) - .then(data => { - if (data.success) { - AdminUtils.showToast('success', data.message || '状态更新成功'); - setTimeout(() => { - window.location.reload(); - }, 1000); - } else { - AdminUtils.showToast('error', data.message || '状态更新失败'); - } - }) - .catch(error => { - console.error('状态更新失败:', error); - AdminUtils.showToast('error', '状态更新失败,请稍后重试'); - }); - }; - - // 全局复制邮箱函数 - window.copyEmail = function(email) { - AdminUtils.copyToClipboard(email || document.querySelector('[data-email]')?.dataset.email || ''); - }; - - // 全局预览文章函数 - window.previewArticle = function() { - const titleElement = document.getElementById('title'); - const contentElement = document.getElementById('content'); - - if (!titleElement || !contentElement) { - AdminUtils.showToast('error', '无法找到文章内容'); - return; - } - - const title = titleElement.value.trim(); - const content = contentElement.value.trim(); - - if (!content) { - AdminUtils.showToast('warning', '请先输入文章内容'); - contentElement.focus(); - return; - } - - // 简单的Markdown预览 - const previewWindow = window.open('', '_blank', 'width=800,height=600,scrollbars=yes'); - if (!previewWindow) { - AdminUtils.showToast('error', '无法打开预览窗口,请检查浏览器弹窗设置'); - return; - } - - // 基础的Markdown转换 - let htmlContent = content - .replace(/\n/g, '
') - .replace(/\*\*(.*?)\*\*/g, '$1') - .replace(/\*(.*?)\*/g, '$1') - .replace(/`(.*?)`/g, '$1') - .replace(/^# (.*$)/gim, '

$1

') - .replace(/^## (.*$)/gim, '

$1

') - .replace(/^### (.*$)/gim, '

$1

'); - - previewWindow.document.write(` - - - - - - 文章预览 - ${title || '未设置标题'} - - - -
-

📄 文章预览

- -
-

${title || '未设置标题'}

-
${htmlContent}
- - - - `); - previewWindow.document.close(); - - // 聚焦到预览窗口 - previewWindow.focus(); - }; - - // 全局工具函数 - window.AdminUtils = AdminUtils; - - // 页面加载完成后初始化 - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', () => { - AdminApp.init(); - }); - } else { - AdminApp.init(); - } - - // CSS样式注入(移动端菜单按钮样式) - const style = document.createElement('style'); - style.textContent = ` - .selected { - background-color: #ebf8ff !important; - } - - .field-error { - color: #f56565; - font-size: 0.75rem; - margin-top: 0.25rem; - } - - @media (max-width: 768px) { - .admin-sidebar { - transform: translateX(-100%); - transition: transform 0.3s ease; - } - - .sidebar-open .admin-sidebar { - transform: translateX(0); - } - - .sidebar-open::before { - content: ''; - position: fixed; - top: 60px; - left: 0; - right: 0; - bottom: 0; - background: rgba(0, 0, 0, 0.5); - z-index: 40; - } - } - `; - document.head.appendChild(style); - -})(); \ No newline at end of file diff --git a/public/js/login.js b/public/js/login.js deleted file mode 100644 index c5dd6e9..0000000 --- a/public/js/login.js +++ /dev/null @@ -1,58 +0,0 @@ -let loginToastTimer = null; -function showLoginToast(msg, isSuccess = false) { - let toast = document.getElementById("login-toast"); - if (!toast) { - toast = document.createElement("div"); - toast.id = "login-toast"; - toast.style.position = "fixed"; - toast.style.top = "20px"; - toast.style.right = "20px"; - toast.style.left = "auto"; - toast.style.transform = "none"; - toast.style.minWidth = "220px"; - toast.style.maxWidth = "80vw"; - toast.style.background = isSuccess ? "linear-gradient(90deg,#7ec6f7,#b2f7ef)" : "#fff"; - toast.style.color = isSuccess ? "#1976d2" : "#ff4d4f"; - toast.style.fontSize = "1.08rem"; - toast.style.fontWeight = "600"; - toast.style.padding = "1.1em 2.2em"; - toast.style.borderRadius = "18px"; - toast.style.boxShadow = "0 4px 24px rgba(30,136,229,0.13),0 1.5px 8px rgba(79,209,255,0.08)"; - toast.style.zIndex = 9999; - toast.style.textAlign = "center"; - toast.style.opacity = "0"; - toast.style.transition = "opacity 0.2s"; - document.body.appendChild(toast); - } - toast.textContent = msg; - toast.style.opacity = "1"; - if (loginToastTimer) clearTimeout(loginToastTimer); - loginToastTimer = setTimeout(() => { - toast.style.opacity = "0"; - setTimeout(() => { - toast.remove(); - }, 300); - loginToastTimer = null; - }, 2000); -} - -const loginForm = document.getElementById("login-form") -loginForm.onsubmit = async function (e) { - e.preventDefault() - const form = e.target - const data = Object.fromEntries(new FormData(form)) - const res = await fetch(form.action, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(data), - }) - const result = await res.json() - if (result.success) { - showLoginToast("登录成功,2秒后跳转到首页", true) - setTimeout(() => { - window.location.href = "/" - }, 2000) - } else { - showLoginToast(result.message || "登录失败") - } -} diff --git a/public/js/profile.js b/public/js/profile.js deleted file mode 100644 index f3b5713..0000000 --- a/public/js/profile.js +++ /dev/null @@ -1,631 +0,0 @@ -// 用户资料页面JavaScript -(function() { - 'use strict'; - - // 页面初始化 - document.addEventListener('DOMContentLoaded', function() { - initProfilePage(); - }); - - function initProfilePage() { - bindFormEvents(); - bindInputValidation(); - showInitialMessage(); - initTabs(); - initAvatarUpload(); - } - - // 绑定表单事件 - function bindFormEvents() { - const profileForm = document.getElementById('profileForm'); - const passwordForm = document.getElementById('passwordForm'); - - console.log('Profile form found:', !!profileForm); - console.log('Password form found:', !!passwordForm); - - if (profileForm) { - profileForm.addEventListener('submit', handleProfileUpdate); - console.log('Profile form event listener added'); - } - - if (passwordForm) { - passwordForm.addEventListener('submit', handlePasswordChange); - console.log('Password form event listener added'); - } - } - - // 处理用户资料更新 - async function handleProfileUpdate(event) { - event.preventDefault(); - - const form = event.target; - const submitBtn = form.querySelector('button[type="submit"]'); - const originalText = submitBtn.textContent; - - try { - setButtonLoading(submitBtn, true); - - const formData = new FormData(form); - const data = Object.fromEntries(formData.entries()); - - // 验证必填字段 - if (!validateProfileData(data)) { - return; - } - - const response = await fetch('/profile/update', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(data) - }); - - const result = await response.json(); - - if (result.success) { - showMessage('资料更新成功!', 'success', 'profileForm'); - updateUserInfoDisplay(result.user); - } else { - throw new Error(result.message || '更新失败'); - } - - } catch (error) { - showMessage(error.message || '更新失败,请重试', 'error', 'profileForm'); - console.error('Profile update error:', error); - } finally { - setButtonLoading(submitBtn, false, originalText); - } - } - - // 处理密码修改 - async function handlePasswordChange(event) { - event.preventDefault(); - - const form = event.target; - const submitBtn = form.querySelector('button[type="submit"]'); - const originalText = submitBtn.textContent; - - try { - setButtonLoading(submitBtn, true); - - const formData = new FormData(form); - const data = Object.fromEntries(formData.entries()); - - if (!validatePasswordData(data)) { - return; - } - - const response = await fetch('/profile/change-password', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(data) - }); - - const result = await response.json(); - - if (result.success) { - showMessage('密码修改成功!', 'success', 'passwordForm'); - form.reset(); - } else { - throw new Error(result.message || '密码修改失败'); - } - - } catch (error) { - showMessage(error.message || '密码修改失败,请重试', 'error', 'passwordForm'); - console.error('Password change error:', error); - } finally { - setButtonLoading(submitBtn, false, originalText); - } - } - - // 验证用户资料数据 - function validateProfileData(data) { - if (!data.username.trim()) { - showMessage('用户名不能为空', 'error', 'profileForm'); - return false; - } - - if (data.username.length < 3) { - showMessage('用户名长度不能少于3位', 'error', 'profileForm'); - return false; - } - - if (data.email && !isValidEmail(data.email)) { - showMessage('请输入有效的邮箱地址', 'error', 'profileForm'); - return false; - } - - if (data.avatar && !isValidImageUrl(data.avatar)) { - showMessage('请输入有效的图片链接或路径', 'error', 'profileForm'); - return false; - } - - return true; - } - - // 验证密码数据 - function validatePasswordData(data) { - const { oldPassword, newPassword, confirmPassword } = data; - - if (!oldPassword || !newPassword || !confirmPassword) { - showMessage('请填写所有密码字段', 'error', 'passwordForm'); - return false; - } - - if (newPassword.length < 6) { - showMessage('新密码长度不能少于6位', 'error', 'passwordForm'); - return false; - } - - if (newPassword !== confirmPassword) { - showMessage('新密码与确认密码不匹配', 'error', 'passwordForm'); - return false; - } - - if (oldPassword === newPassword) { - showMessage('新密码不能与当前密码相同', 'error', 'passwordForm'); - return false; - } - - return true; - } - - // 绑定输入验证 - function bindInputValidation() { - const inputs = document.querySelectorAll('.form-input, .form-textarea'); - - inputs.forEach(input => { - input.addEventListener('blur', function() { - validateField(this); - }); - - input.addEventListener('input', function() { - clearFieldError(this); - }); - }); - } - - // 验证单个字段 - function validateField(field) { - const value = field.value.trim(); - const fieldName = field.name; - - clearFieldError(field); - - let isValid = true; - let errorMessage = ''; - - switch (fieldName) { - case 'username': - if (!value) { - isValid = false; - errorMessage = '用户名不能为空'; - } else if (value.length < 3) { - isValid = false; - errorMessage = '用户名长度不能少于3位'; - } - break; - - case 'email': - if (value && !isValidEmail(value)) { - isValid = false; - errorMessage = '请输入有效的邮箱地址'; - } - break; - - case 'avatar': - if (value && !isValidImageUrl(value)) { - isValid = false; - errorMessage = '请输入有效的图片链接或路径'; - } - break; - - case 'newPassword': - if (value && value.length < 6) { - isValid = false; - errorMessage = '密码长度不能少于6位'; - } - break; - } - - if (!isValid) { - showFieldError(field, errorMessage); - } - - return isValid; - } - - // 显示字段错误 - function showFieldError(field, message) { - field.classList.add('error'); - - let errorElement = field.parentNode.querySelector('.error-message'); - if (!errorElement) { - errorElement = document.createElement('div'); - errorElement.className = 'error-message'; - field.parentNode.appendChild(errorElement); - } - - errorElement.textContent = message; - errorElement.classList.add('show'); - } - - // 清除字段错误 - function clearFieldError(field) { - field.classList.remove('error'); - - const errorElement = field.parentNode.querySelector('.error-message'); - if (errorElement) { - errorElement.classList.remove('show'); - } - } - - // 验证邮箱格式 - function isValidEmail(email) { - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - 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) { - button.disabled = true; - button.textContent = '处理中...'; - button.classList.add('loading'); - } else { - button.disabled = false; - button.textContent = originalText || button.textContent; - button.classList.remove('loading'); - } - } - - // 显示消息 - function showMessage(message, type = 'info', formId = null) { - console.log('showMessage called with:', { message, type, formId }); - removeExistingMessages(); - - let targetContainer; - if (formId === 'profileForm') { - targetContainer = document.querySelector('#profileForm .message-container'); - console.log('Looking for profileForm message container:', !!targetContainer); - } else if (formId === 'passwordForm') { - targetContainer = document.querySelector('#passwordForm .message-container'); - console.log('Looking for passwordForm message container:', !!targetContainer); - } else { - // 如果没有指定表单,使用默认容器 - targetContainer = document.querySelector('.profile-content'); - console.log('Using default container:', !!targetContainer); - } - - if (!targetContainer) { - console.warn('Message container not found for formId:', formId); - console.warn('Available containers:', document.querySelectorAll('.message-container')); - // 尝试使用备用容器 - targetContainer = document.querySelector('.message-container'); - if (!targetContainer) { - console.error('No message container found anywhere'); - return; - } - } - - console.log('Target container found:', targetContainer); - - const messageElement = document.createElement('div'); - messageElement.className = `message ${type} show`; - - const messageText = document.createElement('span'); - messageText.textContent = message; - messageElement.appendChild(messageText); - - const closeButton = document.createElement('button'); - closeButton.className = 'message-close'; - closeButton.type = 'button'; - closeButton.innerHTML = '×'; - closeButton.onclick = function() { - messageElement.remove(); - }; - messageElement.appendChild(closeButton); - - targetContainer.appendChild(messageElement); - console.log('Message added to container'); - - // 5秒后自动隐藏 - setTimeout(() => { - if (messageElement.parentNode) { - messageElement.remove(); - } - }, 5000); - } - - // 关闭指定消息 - function closeMessage(messageId) { - const messageElement = document.getElementById(messageId); - if (messageElement) { - messageElement.remove(); - } - } - - // 移除现有消息 - function removeExistingMessages() { - const existingMessages = document.querySelectorAll('.message'); - existingMessages.forEach(msg => msg.remove()); - } - - // 显示初始消息 - function showInitialMessage() { - const urlParams = new URLSearchParams(window.location.search); - const msg = urlParams.get('msg'); - const msgType = urlParams.get('type') || 'info'; - - if (msg) { - showMessage(decodeURIComponent(msg), msgType); - const newUrl = window.location.pathname; - window.history.replaceState({}, document.title, newUrl); - } - } - - // 更新用户信息显示 - function updateUserInfoDisplay(user) { - const infoItems = document.querySelectorAll('.info-value'); - infoItems.forEach(item => { - const label = item.previousElementSibling.textContent; - if (label.includes('最后更新')) { - item.textContent = new Date().toLocaleDateString('zh-CN'); - } - }); - } - - // 重置表单 - function resetForm() { - const form = document.getElementById('profileForm'); - if (form) { - form.reset(); - const inputs = form.querySelectorAll('.form-input, .form-textarea'); - inputs.forEach(input => clearFieldError(input)); - showMessage('表单已重置', 'info'); - } - } - - // 重置密码表单 - function resetPasswordForm() { - console.log('resetPasswordForm called'); - const form = document.getElementById('passwordForm'); - console.log('Password form found:', !!form); - - if (form) { - form.reset(); - const inputs = form.querySelectorAll('.form-input'); - inputs.forEach(input => clearFieldError(input)); - - // 查找消息容器 - const messageContainer = form.querySelector('.message-container'); - console.log('Message container found:', !!messageContainer); - - showMessage('密码表单已清空', 'info', 'passwordForm'); - } else { - console.error('Password form not found'); - } - } - - // 初始化标签页 - function initTabs() { - const tabBtns = document.querySelectorAll('.tab-btn'); - const tabPanes = document.querySelectorAll('.tab-pane'); - - console.log('Initializing tabs...'); - console.log('Tab buttons found:', tabBtns.length); - console.log('Tab panes found:', tabPanes.length); - - if (tabBtns.length === 0 || tabPanes.length === 0) { - console.warn('Tab elements not found'); - return; - } - - tabBtns.forEach(btn => { - btn.addEventListener('click', function() { - const targetTab = this.getAttribute('data-tab'); - console.log('Tab clicked:', targetTab); // 调试日志 - - // 移除所有活动状态 - tabBtns.forEach(b => b.classList.remove('active')); - tabPanes.forEach(p => p.classList.remove('active')); - - // 添加活动状态到当前标签 - this.classList.add('active'); - const targetPane = document.getElementById(targetTab + '-tab'); - if (targetPane) { - targetPane.classList.add('active'); - console.log('Tab pane activated:', targetTab + '-tab'); // 调试日志 - - // 重新绑定表单事件,确保新显示的Tab中的表单能正常工作 - setTimeout(() => { - bindFormEvents(); - }, 100); - } else { - console.error('Target tab pane not found:', targetTab + '-tab'); - } - }); - }); - - // 确保默认标签页是激活状态 - const defaultTab = document.querySelector('.tab-btn.active'); - const defaultPane = document.querySelector('.tab-pane.active'); - if (defaultTab && defaultPane) { - console.log('Default tab initialized:', defaultTab.getAttribute('data-tab')); - } - } - - // 导出函数供HTML调用 - 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/public/js/register.js b/public/js/register.js deleted file mode 100644 index 8187ffd..0000000 --- a/public/js/register.js +++ /dev/null @@ -1,48 +0,0 @@ -// 注册页面验证码点击刷新功能 -document.addEventListener('DOMContentLoaded', function() { - const captchaImg = document.querySelector('img[src="/captcha"]'); - - if (captchaImg) { - // 添加点击事件 - captchaImg.addEventListener('click', function() { - // 添加时间戳防止缓存 - const timestamp = new Date().getTime(); - this.src = `/captcha?t=${timestamp}`; - - // 添加点击反馈效果 - this.style.transform = 'scale(0.95)'; - setTimeout(() => { - this.style.transform = 'scale(1)'; - }, 150); - }); - - // 添加鼠标样式提示 - captchaImg.style.cursor = 'pointer'; - captchaImg.title = '点击刷新验证码'; - - // 添加悬停效果 - captchaImg.addEventListener('mouseenter', function() { - this.style.opacity = '0.8'; - this.style.transition = 'opacity 0.2s ease'; - }); - - captchaImg.addEventListener('mouseleave', function() { - this.style.opacity = '1'; - }); - } - - // 表单验证 - const registerForm = document.querySelector('form[action="/register"]'); - if (registerForm) { - registerForm.addEventListener('submit', function(e) { - const password = document.getElementById('password'); - const confirmPassword = document.getElementById('confirm_password'); - - if (password.value !== confirmPassword.value) { - e.preventDefault(); - alert('两次输入的密码不一致,请重新输入'); - return false; - } - }); - } -}); diff --git a/public/lib/bg-change.js b/public/lib/bg-change.js deleted file mode 100644 index b5db4ba..0000000 --- a/public/lib/bg-change.js +++ /dev/null @@ -1,429 +0,0 @@ -class BgSwitcher { - constructor(images, options = {}) { - this.images = images - // 从localStorage中读取保存的索引 - const savedIndex = localStorage.getItem("bgSwitcherIndex") - if (savedIndex !== null && !isNaN(savedIndex)) { - this.index = parseInt(savedIndex) - } else { - this.index = 0 - } - this.container = options.container || document.body - this.interval = options.interval || 3000 - this.effect = options.effect || BgSwitcher.fadeEffect - this.timer = null - this.apiTimer = null - this.apiUrl = null - this.apiInterval = 30000 - this.startTime = 0 - // 从localStorage中读取保存的剩余时间 - const savedRemainingTime = localStorage.getItem("bgSwitcherRemainingTime") - this.remainingTime = - savedRemainingTime !== null && !isNaN(savedRemainingTime) && savedRemainingTime >= 0 - ? parseInt(savedRemainingTime) - : this.interval - this.bgLayer = document.createElement("div") - this.isInitialLoad = true - - // 从localStorage中读取API模式状态 - const isApiMode = localStorage.getItem("bgSwitcherIsApiMode") === "true" - if (isApiMode) { - this.apiUrl = localStorage.getItem("bgSwitcherApiUrl") || null - const savedApiInterval = localStorage.getItem("bgSwitcherApiInterval") - this.apiInterval = savedApiInterval !== null && !isNaN(savedApiInterval) ? parseInt(savedApiInterval) : 30000 - } - // 监听页面可见性变化 - this.handleVisibilityChange = this.handleVisibilityChange.bind(this) - document.addEventListener("visibilitychange", this.handleVisibilityChange) - - // 监听页面卸载事件,确保保存状态 - this.handleBeforeUnload = this.handleBeforeUnload.bind(this) - window.addEventListener("beforeunload", this.handleBeforeUnload) - this.bgLayer.style.position = "fixed" - this.bgLayer.style.top = 0 - this.bgLayer.style.left = 0 - this.bgLayer.style.width = "100vw" - this.bgLayer.style.height = "100vh" - this.bgLayer.style.zIndex = "-1" - this.bgLayer.style.transition = "opacity 1s" - this.bgLayer.style.opacity = 1 - this.bgLayer.style.backgroundSize = "cover" - this.bgLayer.style.backgroundColor = "#000000" - this.bgLayer.style.backgroundPosition = "center" - this.bgLayer.style.filter = "brightness(0.68)" - this.container.style.backgroundColor = "#000000" - } - - setBg(url) { - // 切换时先预加载目标图片,加载完成后再切换显示 - const img = new Image() - img.onload = () => { - if (this.isInitialLoad) { - // 初始加载时,先设置背景图再添加到页面 - this.bgLayer.style.backgroundImage = `url(${url})` - this.container.appendChild(this.bgLayer) - this.isInitialLoad = false - } else { - this.effect(this.bgLayer, url) - } - if (!this.isApiMode) { - this.scheduleNext() - } - } - img.onerror = () => { - // 加载失败时处理 - console.warn("背景图片加载失败:", url) - if (this.isInitialLoad) { - // 初始加载失败时,也添加背景层到页面 - this.container.appendChild(this.bgLayer) - this.isInitialLoad = false - } - } - img.src = url - } - - next() { - const nextIndex = (this.index + 1) % this.images.length - const nextUrl = this.images[nextIndex] - // 切换前先预加载 - const img = new Image() - img.onload = () => { - this.index = nextIndex - // 保存索引到localStorage - localStorage.setItem("bgSwitcherIndex", this.index) - this.effect(this.bgLayer, nextUrl) - this.scheduleNext() - } - img.onerror = () => { - // 加载失败时跳过 - console.warn("背景图片加载失败:", nextUrl) - this.scheduleNext() - } - img.src = nextUrl - } - - prev() { - const prevIndex = (this.index - 1 + this.images.length) % this.images.length - const prevUrl = this.images[prevIndex] - // 切换前先预加载 - const img = new Image() - img.onload = () => { - this.index = prevIndex - // 保存索引到localStorage - localStorage.setItem("bgSwitcherIndex", this.index) - this.effect(this.bgLayer, prevUrl) - this.scheduleNext() - } - img.onerror = () => { - // 加载失败时跳过 - console.warn("背景图片加载失败:", prevUrl) - this.scheduleNext() - } - img.src = prevUrl - } - - start() { - if (this.timer || this.apiTimer) return - - // 如果处于API模式,启动API请求 - if (this.apiUrl) { - this.fetchRandomWallpaper() - } else { - // 否则使用默认轮播 - this.setBg(this.images[this.index]) - } - } - - /** - * 安排下一次背景切换 - */ - scheduleNext() { - if (this.timer) { - clearTimeout(this.timer) - } - - // 记录开始时间 - this.startTime = Date.now() - - // 使用剩余时间或默认间隔 - const timeToWait = this.remainingTime > 0 ? this.remainingTime : this.interval - - this.timer = setTimeout(() => { - this.remainingTime = this.interval // 重置剩余时间 - // 保存剩余时间到localStorage - localStorage.setItem("bgSwitcherRemainingTime", this.remainingTime) - this.next() - }, timeToWait) - } - - /** - * 处理页面可见性变化 - */ - handleVisibilityChange() { - if (document.hidden) { - // 页面不可见时,暂停计时器并计算剩余时间 - if (this.timer) { - const elapsedTime = Date.now() - this.startTime - this.remainingTime = Math.max(0, this.remainingTime - elapsedTime) - // 保存剩余时间到localStorage - localStorage.setItem("bgSwitcherRemainingTime", this.remainingTime) - clearTimeout(this.timer) - this.timer = null - } - // 暂停API计时器 - if (this.apiTimer) { - const elapsedTime = Date.now() - this.startTime - this.remainingTime = Math.max(0, this.remainingTime - elapsedTime) - // 保存剩余时间到localStorage - localStorage.setItem("bgSwitcherRemainingTime", this.remainingTime) - clearTimeout(this.apiTimer) - this.apiTimer = null - } - } else { - // 页面可见时,恢复计时器 - if (!this.timer && !this.apiTimer && !this.apiUrl) { - // 如果没有活跃的计时器,使用默认的轮播 - this.scheduleNext() - } else if (this.apiTimer === null && this.apiUrl) { - // 如果处于API模式但计时器未运行,恢复API请求 - this.scheduleNextApiRequest() - } - } - } - - /** - * 处理页面卸载事件,确保保存状态 - */ - handleBeforeUnload() { - // 保存当前索引 - localStorage.setItem("bgSwitcherIndex", this.index) - - // 保存API模式状态 - localStorage.setItem("bgSwitcherIsApiMode", !!this.apiUrl) - if (this.apiUrl) { - localStorage.setItem("bgSwitcherApiUrl", this.apiUrl) - localStorage.setItem("bgSwitcherApiInterval", this.apiInterval) - } - - // 如果计时器在运行,计算并保存剩余时间 - if (this.timer || this.apiTimer) { - const elapsedTime = Date.now() - this.startTime - this.remainingTime = Math.max(0, this.remainingTime - elapsedTime) - localStorage.setItem("bgSwitcherRemainingTime", this.remainingTime) - } - } - - stop() { - if (this.timer) { - clearTimeout(this.timer) - this.timer = null - } - // 重置剩余时间 - this.remainingTime = this.interval - // 保存剩余时间到localStorage - localStorage.setItem("bgSwitcherRemainingTime", this.remainingTime) - } - - /** - * 从API获取随机壁纸并定期更新 - * @param {string} apiUrl - 获取随机壁纸的API地址 - * @param {number} interval - 请求间隔时间(毫秒) - */ - startRandomApiSwitch(apiUrl, interval = 30000) { - this.stop() // 停止当前的轮播 - this.apiInterval = interval - this.apiUrl = apiUrl - - // 创建专用的API计时器 - this.apiTimer = null - - // 立即请求一次 - this.fetchRandomWallpaper() - } - - /** - * 从API获取随机壁纸 - */ - fetchRandomWallpaper() { - // 记录开始时间,用于计算剩余时间 - this.startTime = Date.now() - this.remainingTime = this.apiInterval - - fetch(this.apiUrl) - .then(response => { - console.log(response) - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`) - } - return response.json() - }) - .then(data => { - // 假设API返回的数据格式为 { wallpaperUrl: '图片地址' } - const wallpaperUrl = data.wallpaperUrl || data.url || data.image - if (wallpaperUrl) { - // 预加载图片 - const img = new Image() - img.onload = () => { - if (this.isInitialLoad) { - // 初始加载时,先设置背景图再添加到页面 - this.container.appendChild(this.bgLayer) - this.isInitialLoad = false - } - // 保存当前索引(使用-1标记这是API获取的图片) - this.index = -1 - localStorage.setItem("bgSwitcherIndex", -1) - this.effect(this.bgLayer, wallpaperUrl) - this.scheduleNextApiRequest() - } - img.onerror = () => { - console.warn("API返回的壁纸加载失败:", wallpaperUrl) - this.scheduleNextApiRequest() - } - img.src = wallpaperUrl - } else { - console.warn("背景图片加载失败:", url) - if (this.isInitialLoad) { - console.warn("API返回的数据格式不正确,未找到壁纸地址") - // 初始加载失败时,也添加背景层到页面 - this.container.appendChild(this.bgLayer) - this.isInitialLoad = false - } - this.scheduleNextApiRequest() - } - }) - .catch(error => { - console.error("获取随机壁纸失败:", error) - this.scheduleNextApiRequest() - }) - } - - /** - * 安排下一次API请求 - */ - scheduleNextApiRequest() { - if (this.apiTimer) { - clearTimeout(this.apiTimer) - } - - // 使用剩余时间或默认间隔 - const timeToWait = this.remainingTime > 0 ? this.remainingTime : this.apiInterval - - this.apiTimer = setTimeout(() => { - this.remainingTime = this.apiInterval // 重置剩余时间 - localStorage.setItem("bgSwitcherRemainingTime", this.remainingTime) - this.fetchRandomWallpaper() - }, timeToWait) - } - - /** - * 停止API随机壁纸请求 - */ - stopRandomApiSwitch() { - if (this.apiTimer) { - clearTimeout(this.apiTimer) - this.apiTimer = null - } - this.apiUrl = null - // 重置剩余时间 - this.remainingTime = this.interval - localStorage.setItem("bgSwitcherRemainingTime", this.remainingTime) - } - - static fadeEffect(layer, url) { - layer.style.transition = "opacity 1s" - layer.style.opacity = 0 - setTimeout(() => { - layer.style.backgroundImage = `url(${url})` - layer.style.opacity = 1 - }, 500) - } -} - -// 使用示例 -// 1. 默认本地图片轮播 -// const images = [ -// '/static/bg2.webp', -// '/static/bg.jpg', -// ]; -// const bgSwitcher = new BgSwitcher(images, { interval: 5000 }); -// 启动轮播 -// bgSwitcher.start(); - -// 2. 随机API壁纸示例 -// 创建一个新的BgSwitcher实例用于API模式 -let apiBgSwitcher = new BgSwitcher([], { interval: 5000 }) // API模式不需要本地图片列表 - -// 模拟API函数,实际使用时替换为真实API地址 -function createMockWallpaperApi() { - // 模拟壁纸地址库 - const mockWallpapers = ["/static/bg2.webp", "/static/bg.jpg"] - - // 创建一个简单的服务器端点 - if (window.mockWallpaperServer) { - clearInterval(window.mockWallpaperServer) - } - - // 模拟API响应 - window.fetchRandomWallpaper = function () { - return new Promise(resolve => { - setTimeout(() => { - const randomIndex = Math.floor(Math.random() * mockWallpapers.length) - resolve({ - ok: true, - json() { - return { - wallpaperUrl: mockWallpapers[randomIndex], - } - }, - }) - }, 500) - }) - } - - // 替换原生fetch以模拟API调用 - window.originalFetch = window.fetch - window.fetch = function (url, opts = {}) { - if (url === "/api/random-wallpaper") { - return window.fetchRandomWallpaper() - } - return window.originalFetch(...arguments) - } - - console.log("模拟壁纸API已启动") -} - -// 初始化模拟API -createMockWallpaperApi() - -// 启动API模式的随机壁纸切换(每10秒请求一次) -apiBgSwitcher.startRandomApiSwitch("/api/random-wallpaper", 10000) - -window.addEventListener("pageshow", function (event) { - if (event.persisted) { - apiBgSwitcher = new BgSwitcher([], { interval: 5000 }) - apiBgSwitcher.startRandomApiSwitch("/api/random-wallpaper", 10000) - } -}) - -// fetch("https://pic.xieyaxin.top/random.php") -// .then(response => { -// if(response.body instanceof ReadableStream) { -// return response.blob() -// } -// return response.json() -// }) -// .then(data => { -// console.log(URL.createObjectURL(data)); - -// }) - -// 要停止API模式,使用 -// apiBgSwitcher.stopRandomApiSwitch(); - -// 要切换回本地图片轮播,使用 -// apiBgSwitcher.stopRandomApiSwitch(); -// apiBgSwitcher.start(); - -// 启动默认轮播 -// bgSwitcher.start(); diff --git a/src/views/admin/articles/create.pug b/src/views/admin/articles/create.pug deleted file mode 100644 index 041089a..0000000 --- a/src/views/admin/articles/create.pug +++ /dev/null @@ -1,225 +0,0 @@ -extends ../../layouts/admin - -block $$content - .page-container - //- 页面头部 - .page-header - .page-header-left - .breadcrumb - a(href="/admin/articles") 文章管理 - span.breadcrumb-separator / - span.breadcrumb-current 新建文章 - h1.page-title 新建文章 - p.page-subtitle 创建一篇新的文章内容 - - //- 文章表单 - .content-section - form.article-form(method="POST" action="/admin/articles") - .form-grid - //- 左侧主要内容 - .form-main - .form-group - label.form-label(for="title") 文章标题 * - input.form-input( - type="text" - id="title" - name="title" - placeholder="请输入文章标题" - required - maxlength="200" - ) - .form-help 建议标题长度在10-50字之间 - - .form-group - label.form-label(for="slug") URL别名 - input.form-input( - type="text" - id="slug" - name="slug" - placeholder="自动生成或手动输入" - pattern="[a-z0-9-]+" - maxlength="100" - ) - .form-help 用于生成友好的URL,只能包含小写字母、数字和连字符 - - .form-group - label.form-label(for="excerpt") 文章摘要 - textarea.form-textarea( - id="excerpt" - name="excerpt" - placeholder="请输入文章摘要(可选)" - rows="3" - maxlength="500" - ) - .form-help 简短描述文章内容,有助于SEO - - .form-group - label.form-label(for="content") 文章内容 * - textarea.form-textarea.content-editor( - id="content" - name="content" - placeholder="请输入文章内容,支持Markdown格式" - rows="20" - required - ) - .form-help 支持Markdown语法,可直接粘贴Markdown内容 - - //- 右侧设置面板 - .form-sidebar - .sidebar-section - h3.sidebar-title 发布设置 - - .form-group - label.form-label(for="status") 发布状态 - select.form-select(id="status" name="status") - option(value="draft") 📝 保存为草稿 - option(value="published") ✅ 立即发布 - .form-help 草稿状态下文章不会在前台显示 - - .form-group - label.form-label(for="category") 文章分类 - input.form-input( - type="text" - id="category" - name="category" - placeholder="如:技术、生活、随笔" - maxlength="50" - ) - .form-help 用于组织和分类文章 - - .form-group - label.form-label(for="tags") 文章标签 - input.form-input( - type="text" - id="tags" - name="tags" - placeholder="标签1,标签2,标签3" - maxlength="200" - ) - .form-help 多个标签用英文逗号分隔 - - .sidebar-section - h3.sidebar-title 高级设置 - - .form-group - label.form-label(for="featured_image") 特色图片URL - input.form-input( - type="url" - id="featured_image" - name="featured_image" - placeholder="https://example.com/image.jpg" - ) - .form-help 文章封面图片链接 - - //- 表单按钮 - .form-actions - .action-buttons - button.btn.btn-primary(type="submit") 💾 保存文章 - a.btn.btn-outline(href="/admin/articles") 取消 - button.btn.btn-secondary(type="button" onclick="previewArticle()") 👁️ 预览 - -block $$scripts - script. - // 自动生成slug - document.getElementById('title').addEventListener('input', function() { - const title = this.value; - const slugInput = document.getElementById('slug'); - - if (!slugInput.value) { - // 简单的slug生成逻辑 - const slug = title - .toLowerCase() - .replace(/[^a-z0-9\u4e00-\u9fa5]/g, '-') - .replace(/-+/g, '-') - .replace(/^-|-$/g, ''); - slugInput.value = slug; - } - }); - - // 字符计数 - function setupCharCounter(inputId, maxLength) { - const input = document.getElementById(inputId); - const help = input.nextElementSibling; - - function updateCounter() { - const current = input.value.length; - const remaining = maxLength - current; - const originalText = help.textContent; - - if (remaining < 50) { - help.textContent = `${originalText} (还可输入${remaining}字符)`; - help.style.color = remaining < 10 ? '#ff4757' : '#ffa726'; - } else { - help.style.color = ''; - } - } - - input.addEventListener('input', updateCounter); - updateCounter(); - } - - // 设置字符计数 - setupCharCounter('title', 200); - setupCharCounter('excerpt', 500); - setupCharCounter('tags', 200); - - // 预览功能 - function previewArticle() { - const content = document.getElementById('content').value; - if (!content.trim()) { - alert('请先输入文章内容'); - return; - } - - // 简单的Markdown预览(实际项目中可以使用更完善的Markdown解析器) - const previewWindow = window.open('', '_blank', 'width=800,height=600'); - previewWindow.document.write(` - - - - 文章预览 - - - -

${document.getElementById('title').value || '未设置标题'}

-
${content.replace(/\n/g, '
')}
- - - `); - previewWindow.document.close(); - } - - // 表单验证 - document.querySelector('.article-form').addEventListener('submit', function(e) { - const title = document.getElementById('title').value.trim(); - const content = document.getElementById('content').value.trim(); - - if (!title) { - alert('请输入文章标题'); - e.preventDefault(); - return; - } - - if (!content) { - alert('请输入文章内容'); - e.preventDefault(); - return; - } - - if (title.length < 5) { - alert('文章标题至少需要5个字符'); - e.preventDefault(); - return; - } - - if (content.length < 10) { - alert('文章内容至少需要10个字符'); - e.preventDefault(); - return; - } - }); \ No newline at end of file diff --git a/src/views/admin/articles/edit.pug b/src/views/admin/articles/edit.pug deleted file mode 100644 index 790a248..0000000 --- a/src/views/admin/articles/edit.pug +++ /dev/null @@ -1,251 +0,0 @@ -extends ../../layouts/admin - -block $$content - .page-container - //- 页面头部 - .page-header - .page-header-left - .breadcrumb - a(href="/admin/articles") 文章管理 - span.breadcrumb-separator / - a(href=`/admin/articles/${article.id}`)= article.title - span.breadcrumb-separator / - span.breadcrumb-current 编辑 - h1.page-title 编辑文章 - p.page-subtitle= `编辑:${article.title}` - - //- 文章表单 - .content-section - form.article-form(method="POST" action=`/admin/articles/${article.id}`) - input(type="hidden" name="_method" value="PUT") - - .form-grid - //- 左侧主要内容 - .form-main - .form-group - label.form-label(for="title") 文章标题 * - input.form-input( - type="text" - id="title" - name="title" - value=article.title - placeholder="请输入文章标题" - required - maxlength="200" - ) - .form-help 建议标题长度在10-50字之间 - - .form-group - label.form-label(for="slug") URL别名 - input.form-input( - type="text" - id="slug" - name="slug" - value=article.slug || '' - placeholder="自动生成或手动输入" - pattern="[a-z0-9-]+" - maxlength="100" - ) - .form-help 用于生成友好的URL,只能包含小写字母、数字和连字符 - - .form-group - label.form-label(for="excerpt") 文章摘要 - textarea.form-textarea( - id="excerpt" - name="excerpt" - placeholder="请输入文章摘要(可选)" - rows="3" - maxlength="500" - )= article.excerpt || '' - .form-help 简短描述文章内容,有助于SEO - - .form-group - label.form-label(for="content") 文章内容 * - textarea.form-textarea.content-editor( - id="content" - name="content" - placeholder="请输入文章内容,支持Markdown格式" - rows="20" - required - )= article.content || '' - .form-help 支持Markdown语法,可直接粘贴Markdown内容 - - //- 右侧设置面板 - .form-sidebar - .sidebar-section - h3.sidebar-title 发布设置 - - .form-group - label.form-label(for="status") 发布状态 - select.form-select(id="status" name="status") - option(value="draft" selected=article.status === 'draft') 📝 保存为草稿 - option(value="published" selected=article.status === 'published') ✅ 立即发布 - .form-help 草稿状态下文章不会在前台显示 - - .form-group - label.form-label(for="category") 文章分类 - input.form-input( - type="text" - id="category" - name="category" - value=article.category || '' - placeholder="如:技术、生活、随笔" - maxlength="50" - ) - .form-help 用于组织和分类文章 - - .form-group - label.form-label(for="tags") 文章标签 - input.form-input( - type="text" - id="tags" - name="tags" - value=article.tags || '' - placeholder="标签1,标签2,标签3" - maxlength="200" - ) - .form-help 多个标签用英文逗号分隔 - - .sidebar-section - h3.sidebar-title 高级设置 - - .form-group - label.form-label(for="featured_image") 特色图片URL - input.form-input( - type="url" - id="featured_image" - name="featured_image" - value=article.featured_image || '' - placeholder="https://example.com/image.jpg" - ) - .form-help 文章封面图片链接 - - .sidebar-section - h3.sidebar-title 文章信息 - .info-list - .info-item - .info-label 创建时间 - .info-value= new Date(article.created_at).toLocaleString('zh-CN') - .info-item - .info-label 更新时间 - .info-value= new Date(article.updated_at).toLocaleString('zh-CN') - if article.view_count - .info-item - .info-label 阅读量 - .info-value= article.view_count - - //- 表单按钮 - .form-actions - .action-buttons - button.btn.btn-primary(type="submit") 💾 更新文章 #{article.id} - a.btn.btn-outline(href=`/admin/articles/${article.id}`) 取消 - button.btn.btn-secondary(type="button" onclick="previewArticle()") 👁️ 预览 - if article.status === 'published' && article.slug - a.btn.btn-outline(href=`/articles/${article.slug}` target="_blank") 🔗 查看前台 - -block $$scripts - script. - // 自动生成slug(仅在为空时) - document.getElementById('title').addEventListener('input', function() { - const title = this.value; - const slugInput = document.getElementById('slug'); - - if (!slugInput.value.trim()) { - // 简单的slug生成逻辑 - const slug = title - .toLowerCase() - .replace(/[^a-z0-9\u4e00-\u9fa5]/g, '-') - .replace(/-+/g, '-') - .replace(/^-|-$/g, ''); - slugInput.value = slug; - } - }); - - // 字符计数 - function setupCharCounter(inputId, maxLength) { - const input = document.getElementById(inputId); - const help = input.nextElementSibling; - - function updateCounter() { - const current = input.value.length; - const remaining = maxLength - current; - const originalText = help.textContent.split('(')[0].trim(); - - if (remaining < 50) { - help.textContent = `${originalText} (还可输入${remaining}字符)`; - help.style.color = remaining < 10 ? '#ff4757' : '#ffa726'; - } else { - help.textContent = originalText; - help.style.color = ''; - } - } - - input.addEventListener('input', updateCounter); - updateCounter(); - } - - // 设置字符计数 - setupCharCounter('title', 200); - setupCharCounter('excerpt', 500); - setupCharCounter('tags', 200); - - // 预览功能 - function previewArticle() { - const content = document.getElementById('content').value; - if (!content.trim()) { - alert('请先输入文章内容'); - return; - } - - // 简单的Markdown预览 - const previewWindow = window.open('', '_blank', 'width=800,height=600'); - previewWindow.document.write(` - - - - 文章预览 - - - -

${document.getElementById('title').value || '未设置标题'}

-
${content.replace(/\n/g, '
')}
- - - `); - previewWindow.document.close(); - } - - // 表单验证 - document.querySelector('.article-form').addEventListener('submit', function(e) { - const title = document.getElementById('title').value.trim(); - const content = document.getElementById('content').value.trim(); - - if (!title) { - alert('请输入文章标题'); - e.preventDefault(); - return; - } - - if (!content) { - alert('请输入文章内容'); - e.preventDefault(); - return; - } - - if (title.length < 5) { - alert('文章标题至少需要5个字符'); - e.preventDefault(); - return; - } - - if (content.length < 10) { - alert('文章内容至少需要10个字符'); - e.preventDefault(); - return; - } - }); \ No newline at end of file diff --git a/src/views/admin/articles/index.pug b/src/views/admin/articles/index.pug deleted file mode 100644 index ca0f09f..0000000 --- a/src/views/admin/articles/index.pug +++ /dev/null @@ -1,198 +0,0 @@ -extends ../../layouts/admin - -block $$content - .page-container - //- 页面头部 - .page-header - .page-header-left - h1.page-title 文章管理 - p.page-subtitle 管理您的所有文章内容 - .page-header-right - a.btn.btn-primary(href="/admin/articles/create") ✨ 新建文章 - - //- 筛选和搜索 - .page-filters - form.filter-form(method="GET") - .filter-group - label.filter-label 状态筛选: - select.filter-select(name="status" onchange="this.form.submit()") - option(value="" selected=!filters.status) 全部状态 - option(value="published" selected=filters.status === 'published') 已发布 - option(value="draft" selected=filters.status === 'draft') 草稿 - - .filter-group - label.filter-label 搜索: - .search-box - input.search-input( - type="text" - name="q" - placeholder="搜索文章标题、内容或标签..." - value=filters.q || '' - ) - button.search-btn(type="submit") 🔍 - - if filters.status || filters.q - a.filter-clear(href="/admin/articles") 清除筛选 - - //- 文章列表 - .content-section - if articles && articles.length > 0 - .article-table-container - table.article-table - thead - tr - th.col-title 标题 - th.col-status 状态 - th.col-category 分类 - th.col-date 更新时间 - th.col-actions 操作 - tbody - each article in articles - tr.article-row - td.col-title - .article-title-cell - h3.article-title - a(href=`/admin/articles/${article.id}`)= article.title - if article.excerpt - p.article-summary= article.excerpt.substring(0, 80) + (article.excerpt.length > 80 ? '...' : '') - if article.tags - .article-tags - each tag in article.tags.split(',') - span.tag= tag.trim() - - td.col-status - span.status-badge(class=`status-${article.status}`) - if article.status === 'published' - | ✅ 已发布 - else if article.status === 'draft' - | 📝 草稿 - else - | ❓ #{article.status} - - td.col-category - if article.category - span.category-badge= article.category - else - span.text-muted 未分类 - - td.col-date - .date-info - .primary-date= new Date(article.updated_at).toLocaleDateString('zh-CN') - .secondary-date= new Date(article.updated_at).toLocaleTimeString('zh-CN', {hour: '2-digit', minute: '2-digit'}) - - td.col-actions - .action-buttons - a.btn.btn-sm.btn-outline(href=`/admin/articles/${article.id}` title="查看详情") 👁️ - a.btn.btn-sm.btn-outline(href=`/admin/articles/${article.id}/edit` title="编辑文章") ✏️ - if article.status === 'published' - a.btn.btn-sm.btn-outline(href=`/articles/${article.slug}` target="_blank" title="预览文章") 🔗 - button.btn.btn-sm.btn-danger( - onclick=`deleteArticle(${article.id}, '${article.title.replace(/'/g, "\\'")}')` - title="删除文章" - ) 🗑️ - - //- 分页导航 - if pagination.totalPages > 1 - .pagination-container - nav.pagination - if pagination.hasPrev - a.pagination-link(href=`?page=${pagination.page - 1}${filters.status ? '&status=' + filters.status : ''}${filters.q ? '&q=' + encodeURIComponent(filters.q) : ''}`) ‹ 上一页 - - - var start = Math.max(1, pagination.page - 2) - - var end = Math.min(pagination.totalPages, pagination.page + 2) - - if start > 1 - a.pagination-link(href=`?page=1${filters.status ? '&status=' + filters.status : ''}${filters.q ? '&q=' + encodeURIComponent(filters.q) : ''}`) 1 - if start > 2 - span.pagination-ellipsis ... - - - for (var i = start; i <= end; i++) - if i === pagination.page - span.pagination-link.active= i - else - a.pagination-link(href=`?page=${i}${filters.status ? '&status=' + filters.status : ''}${filters.q ? '&q=' + encodeURIComponent(filters.q) : ''}`)= i - - if end < pagination.totalPages - if end < pagination.totalPages - 1 - span.pagination-ellipsis ... - a.pagination-link(href=`?page=${pagination.totalPages}${filters.status ? '&status=' + filters.status : ''}${filters.q ? '&q=' + encodeURIComponent(filters.q) : ''}`)= pagination.totalPages - - if pagination.hasNext - a.pagination-link(href=`?page=${pagination.page + 1}${filters.status ? '&status=' + filters.status : ''}${filters.q ? '&q=' + encodeURIComponent(filters.q) : ''}`) 下一页 › - - //- 统计信息 - .list-footer - .list-stats - | 共 #{pagination.total} 篇文章 - if filters.status || filters.q - |,当前显示筛选结果 - else - //- 空状态 - .empty-state - .empty-icon 📝 - .empty-title 暂无文章 - if filters.status || filters.q - .empty-text 没有找到符合筛选条件的文章 - a.btn.btn-outline(href="/admin/articles") 查看全部文章 - else - .empty-text 您还没有创建任何文章 - a.btn.btn-primary(href="/admin/articles/create") 创建第一篇文章 - -block $$scripts - script. - // 删除文章确认 - function deleteArticle(id, title) { - if (confirm(`确定要删除文章《${title}》吗?此操作不可撤销。`)) { - fetch(`/admin/articles/${id}`, { - method: 'DELETE', - headers: { - 'Content-Type': 'application/json', - } - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - // 显示成功消息 - showToast('success', data.message || '文章删除成功'); - // 刷新页面或移除行 - setTimeout(() => { - window.location.reload(); - }, 1000); - } else { - showToast('error', data.message || '删除失败'); - } - }) - .catch(error => { - console.error('删除失败:', error); - showToast('error', '删除失败,请稍后重试'); - }); - } - } - - // 显示提示消息 - function showToast(type, message) { - const toast = document.createElement('div'); - toast.className = `admin-toast toast-${type}`; - toast.innerHTML = ` - ${message} - - `; - - document.body.appendChild(toast); - - // 自动消失 - setTimeout(() => { - toast.style.opacity = '0'; - setTimeout(() => { - toast.remove(); - }, 300); - }, 3000); - - // 点击关闭 - toast.querySelector('.toast-close').addEventListener('click', () => { - toast.style.opacity = '0'; - setTimeout(() => { - toast.remove(); - }, 300); - }); - } \ No newline at end of file diff --git a/src/views/admin/articles/show.pug b/src/views/admin/articles/show.pug deleted file mode 100644 index d44b6c9..0000000 --- a/src/views/admin/articles/show.pug +++ /dev/null @@ -1,158 +0,0 @@ -extends ../../layouts/admin - -block $$content - .page-container - //- 页面头部 - .page-header - .page-header-left - .breadcrumb - a(href="/admin/articles") 文章管理 - span.breadcrumb-separator / - span.breadcrumb-current= article.title - h1.page-title= article.title - .article-meta - span.meta-item - strong 状态: - span.status-badge(class=`status-${article.status}`) - if article.status === 'published' - | ✅ 已发布 - else if article.status === 'draft' - | 📝 草稿 - if article.category - span.meta-item - strong 分类: - span.category-badge= article.category - span.meta-item - strong 创建时间: - span= new Date(article.created_at).toLocaleString('zh-CN') - span.meta-item - strong 更新时间: - span= new Date(article.updated_at).toLocaleString('zh-CN') - if article.view_count - span.meta-item - strong 阅读量: - span= article.view_count - - .page-header-right - .action-buttons - a.btn.btn-outline(href=`/admin/articles/${article.id}/edit`) ✏️ 编辑 - if article.status === 'published' && article.slug - a.btn.btn-outline(href=`/articles/${article.slug}` target="_blank") 🔗 预览 - button.btn.btn-danger( - onclick=`deleteArticle(${article.id}, '${article.title.replace(/'/g, "\\'")}')` - ) 🗑️ 删除 - - //- 文章内容 - .content-section - .article-view - //- 文章摘要 - if article.excerpt - .article-summary-section - h3.section-title 文章摘要 - .article-summary= article.excerpt - - //- 文章内容 - .article-content-section - h3.section-title 文章内容 - .article-content - if article.content - != article.content.replace(/\n/g, '
') - else - .empty-content 暂无内容 - - //- 文章标签 - if article.tags - .article-tags-section - h3.section-title 标签 - .article-tags - each tag in article.tags.split(',') - span.tag= tag.trim() - - //- 文章链接信息 - if article.slug - .article-link-section - h3.section-title 文章链接 - .link-info - strong 访问链接: - if article.status === 'published' - a.article-link(href=`/articles/${article.slug}` target="_blank")= `/articles/${article.slug}` - else - span.text-muted= `/articles/${article.slug}` - small (草稿状态,暂不可访问) - - //- 技术信息 - .article-technical-section - h3.section-title 技术信息 - .technical-info - .info-grid - .info-item - .info-label ID - .info-value= article.id - .info-item - .info-label Slug - .info-value= article.slug || '未设置' - .info-item - .info-label 作者ID - .info-value= article.author - if article.featured_image - .info-item - .info-label 特色图片 - .info-value - a(href=article.featured_image target="_blank")= article.featured_image - -block $$scripts - script. - // 删除文章确认 - function deleteArticle(id, title) { - if (confirm(`确定要删除文章《${title}》吗?此操作不可撤销。`)) { - fetch(`/admin/articles/${id}`, { - method: 'DELETE', - headers: { - 'Content-Type': 'application/json', - } - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - showToast('success', data.message || '文章删除成功'); - setTimeout(() => { - window.location.href = '/admin/articles'; - }, 1000); - } else { - showToast('error', data.message || '删除失败'); - } - }) - .catch(error => { - console.error('删除失败:', error); - showToast('error', '删除失败,请稍后重试'); - }); - } - } - - // 显示提示消息 - function showToast(type, message) { - const toast = document.createElement('div'); - toast.className = `admin-toast toast-${type}`; - toast.innerHTML = ` - ${message} - - `; - - document.body.appendChild(toast); - - // 自动消失 - setTimeout(() => { - toast.style.opacity = '0'; - setTimeout(() => { - toast.remove(); - }, 300); - }, 3000); - - // 点击关闭 - toast.querySelector('.toast-close').addEventListener('click', () => { - toast.style.opacity = '0'; - setTimeout(() => { - toast.remove(); - }, 300); - }); - } \ No newline at end of file diff --git a/src/views/admin/contacts/index.pug b/src/views/admin/contacts/index.pug deleted file mode 100644 index 7601465..0000000 --- a/src/views/admin/contacts/index.pug +++ /dev/null @@ -1,243 +0,0 @@ -extends ../../layouts/admin - -block $$content - .page-container - //- 页面头部 - .page-header - .page-header-left - h1.page-title 联系信息管理 - p.page-subtitle 管理用户提交的联系表单信息 - .page-header-right - .stats-summary - span.stat-item - .stat-number= pagination.total - .stat-label 总数 - span.stat-item.unread - .stat-number - | #{contacts.filter(c => c.status === 'unread').length} - .stat-label 未读 - - //- 筛选和搜索 - .page-filters - form.filter-form(method="GET") - .filter-group - label.filter-label 状态筛选: - select.filter-select(name="status" onchange="this.form.submit()") - option(value="" selected=!filters.status) 全部状态 - option(value="unread" selected=filters.status === 'unread') 📧 未读 - option(value="read" selected=filters.status === 'read') 👁️ 已读 - option(value="replied" selected=filters.status === 'replied') ✅ 已回复 - - .filter-group - label.filter-label 搜索: - .search-box - input.search-input( - type="text" - name="q" - placeholder="搜索姓名、邮箱、主题或内容..." - value=filters.q || '' - ) - button.search-btn(type="submit") 🔍 - - if filters.status || filters.q - a.filter-clear(href="/admin/contacts") 清除筛选 - - //- 联系信息列表 - .content-section - if contacts && contacts.length > 0 - .contact-table-container - table.contact-table - thead - tr - th.col-contact 联系人信息 - th.col-subject 主题 - th.col-status 状态 - th.col-date 提交时间 - th.col-actions 操作 - tbody - each contact in contacts - tr.contact-row(class=`status-${contact.status}`) - td.col-contact - .contact-info - .contact-name= contact.name - .contact-email= contact.email - if contact.ip_address - .contact-ip IP: #{contact.ip_address} - - td.col-subject - .subject-content - h4.subject-title= contact.subject - .subject-preview= contact.message.substring(0, 80) + (contact.message.length > 80 ? '...' : '') - - td.col-status - span.status-badge(class=`status-${contact.status}`) - if contact.status === 'unread' - | 📧 未读 - else if contact.status === 'read' - | 👁️ 已读 - else if contact.status === 'replied' - | ✅ 已回复 - else - | ❓ #{contact.status} - - td.col-date - .date-info - .primary-date= new Date(contact.created_at).toLocaleDateString('zh-CN') - .secondary-date= new Date(contact.created_at).toLocaleTimeString('zh-CN', {hour: '2-digit', minute: '2-digit'}) - - td.col-actions - .action-buttons - a.btn.btn-sm.btn-outline(href=`/admin/contacts/${contact.id}` title="查看详情") 👁️ - - .status-dropdown - select.status-select(onchange=`updateContactStatus(${contact.id}, this.value)`) - option(value="" disabled selected) 状态... - option(value="unread" class=contact.status === 'unread' ? 'disabled' : '') 标记未读 - option(value="read" class=contact.status === 'read' ? 'disabled' : '') 标记已读 - option(value="replied" class=contact.status === 'replied' ? 'disabled' : '') 标记已回复 - - button.btn.btn-sm.btn-danger( - onclick=`deleteContact(${contact.id}, '${contact.name} - ${contact.subject}')` - title="删除联系信息" - ) 🗑️ - - //- 分页导航 - if pagination.totalPages > 1 - .pagination-container - nav.pagination - if pagination.hasPrev - a.pagination-link(href=`?page=${pagination.page - 1}${filters.status ? '&status=' + filters.status : ''}${filters.q ? '&q=' + encodeURIComponent(filters.q) : ''}`) ‹ 上一页 - - - var start = Math.max(1, pagination.page - 2) - - var end = Math.min(pagination.totalPages, pagination.page + 2) - - if start > 1 - a.pagination-link(href=`?page=1${filters.status ? '&status=' + filters.status : ''}${filters.q ? '&q=' + encodeURIComponent(filters.q) : ''}`) 1 - if start > 2 - span.pagination-ellipsis ... - - - for (var i = start; i <= end; i++) - if i === pagination.page - span.pagination-link.active= i - else - a.pagination-link(href=`?page=${i}${filters.status ? '&status=' + filters.status : ''}${filters.q ? '&q=' + encodeURIComponent(filters.q) : ''}`)= i - - if end < pagination.totalPages - if end < pagination.totalPages - 1 - span.pagination-ellipsis ... - a.pagination-link(href=`?page=${pagination.totalPages}${filters.status ? '&status=' + filters.status : ''}${filters.q ? '&q=' + encodeURIComponent(filters.q) : ''}`)= pagination.totalPages - - if pagination.hasNext - a.pagination-link(href=`?page=${pagination.page + 1}${filters.status ? '&status=' + filters.status : ''}${filters.q ? '&q=' + encodeURIComponent(filters.q) : ''}`) 下一页 › - - //- 统计信息 - .list-footer - .list-stats - | 共 #{pagination.total} 条联系信息 - if filters.status || filters.q - |,当前显示筛选结果 - - //- 批量操作(如果需要的话) - .bulk-actions - .bulk-info 提示:点击联系人姓名可以查看完整信息 - else - //- 空状态 - .empty-state - .empty-icon 📧 - .empty-title 暂无联系信息 - if filters.status || filters.q - .empty-text 没有找到符合筛选条件的联系信息 - a.btn.btn-outline(href="/admin/contacts") 查看全部联系信息 - else - .empty-text 还没有用户提交联系表单 - .empty-help 用户可以通过前台的联系页面提交联系信息 - -block $$scripts - script. - // 更新联系信息状态 - function updateContactStatus(id, status) { - if (!status) return; - - fetch(`/admin/contacts/${id}/status`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ status }) - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - showToast('success', data.message || '状态更新成功'); - setTimeout(() => { - window.location.reload(); - }, 1000); - } else { - showToast('error', data.message || '状态更新失败'); - } - }) - .catch(error => { - console.error('状态更新失败:', error); - showToast('error', '状态更新失败,请稍后重试'); - }); - } - - // 删除联系信息确认 - function deleteContact(id, title) { - if (confirm(`确定要删除联系信息《${title}》吗?此操作不可撤销。`)) { - fetch(`/admin/contacts/${id}`, { - method: 'DELETE', - headers: { - 'Content-Type': 'application/json', - } - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - showToast('success', data.message || '联系信息删除成功'); - setTimeout(() => { - window.location.reload(); - }, 1000); - } else { - showToast('error', data.message || '删除失败'); - } - }) - .catch(error => { - console.error('删除失败:', error); - showToast('error', '删除失败,请稍后重试'); - }); - } - } - - // 显示提示消息 - function showToast(type, message) { - const toast = document.createElement('div'); - toast.className = `admin-toast toast-${type}`; - toast.innerHTML = ` - ${message} - - `; - - document.body.appendChild(toast); - - // 自动消失 - setTimeout(() => { - toast.style.opacity = '0'; - setTimeout(() => { - toast.remove(); - }, 300); - }, 3000); - - // 点击关闭 - toast.querySelector('.toast-close').addEventListener('click', () => { - toast.style.opacity = '0'; - setTimeout(() => { - toast.remove(); - }, 300); - }); - } - - // 重置状态下拉框 - document.querySelectorAll('.status-select').forEach(select => { - select.value = ''; - }); \ No newline at end of file diff --git a/src/views/admin/contacts/show.pug b/src/views/admin/contacts/show.pug deleted file mode 100644 index bd69954..0000000 --- a/src/views/admin/contacts/show.pug +++ /dev/null @@ -1,229 +0,0 @@ -extends ../../layouts/admin - -block $$content - .page-container - //- 页面头部 - .page-header - .page-header-left - .breadcrumb - a(href="/admin/contacts") 联系信息管理 - span.breadcrumb-separator / - span.breadcrumb-current= contact.subject - h1.page-title= contact.subject - .contact-meta - span.meta-item - strong 状态: - span.status-badge(class=`status-${contact.status}`) - if contact.status === 'unread' - | 📧 未读 - else if contact.status === 'read' - | 👁️ 已读 - else if contact.status === 'replied' - | ✅ 已回复 - span.meta-item - strong 提交时间: - span= new Date(contact.created_at).toLocaleString('zh-CN') - - .page-header-right - .action-buttons - .status-actions - if contact.status !== 'read' - button.btn.btn-outline(onclick=`updateContactStatus(${contact.id}, 'read')`) 👁️ 标记已读 - if contact.status !== 'replied' - button.btn.btn-success(onclick=`updateContactStatus(${contact.id}, 'replied')`) ✅ 标记已回复 - if contact.status !== 'unread' - button.btn.btn-outline(onclick=`updateContactStatus(${contact.id}, 'unread')`) 📧 标记未读 - - button.btn.btn-danger( - onclick=`deleteContact(${contact.id}, '${contact.name} - ${contact.subject}')` - ) 🗑️ 删除 - - //- 联系信息详情 - .content-section - .contact-details - //- 联系人信息 - .detail-section - h3.section-title 联系人信息 - .info-grid - .info-item - .info-label 姓名 - .info-value= contact.name - .info-item - .info-label 邮箱 - .info-value - a(href=`mailto:${contact.email}`)= contact.email - if contact.ip_address - .info-item - .info-label IP地址 - .info-value= contact.ip_address - if contact.user_agent - .info-item - .info-label 浏览器信息 - .info-value.user-agent= contact.user_agent - - //- 联系主题 - .detail-section - h3.section-title 联系主题 - .subject-content= contact.subject - - //- 联系内容 - .detail-section - h3.section-title 联系内容 - .message-content - .message-text= contact.message - - //- 系统信息 - .detail-section - h3.section-title 系统信息 - .info-grid - .info-item - .info-label ID - .info-value= contact.id - .info-item - .info-label 创建时间 - .info-value= new Date(contact.created_at).toLocaleString('zh-CN') - .info-item - .info-label 更新时间 - .info-value= new Date(contact.updated_at).toLocaleString('zh-CN') - .info-item - .info-label 当前状态 - .info-value - span.status-badge(class=`status-${contact.status}`) - if contact.status === 'unread' - | 📧 未读 - else if contact.status === 'read' - | 👁️ 已读 - else if contact.status === 'replied' - | ✅ 已回复 - - //- 快速回复(可选功能) - .detail-section - h3.section-title 快速操作 - .quick-actions - .action-group - h4.action-title 邮件操作 - .action-buttons - a.btn.btn-primary(href=`mailto:${contact.email}?subject=Re: ${encodeURIComponent(contact.subject)}&body=${encodeURIComponent('您好,感谢您的联系...')}`) 📧 回复邮件 - button.btn.btn-outline(onclick="copyEmail()") 📋 复制邮箱 - - .action-group - h4.action-title 状态操作 - .action-buttons - if contact.status === 'unread' - button.btn.btn-success(onclick=`updateContactStatus(${contact.id}, 'read')`) 标记已读 - button.btn.btn-primary(onclick=`updateContactStatus(${contact.id}, 'replied')`) 标记已回复 - else if contact.status === 'read' - button.btn.btn-primary(onclick=`updateContactStatus(${contact.id}, 'replied')`) 标记已回复 - button.btn.btn-outline(onclick=`updateContactStatus(${contact.id}, 'unread')`) 标记未读 - else if contact.status === 'replied' - button.btn.btn-outline(onclick=`updateContactStatus(${contact.id}, 'read')`) 标记已读 - button.btn.btn-outline(onclick=`updateContactStatus(${contact.id}, 'unread')`) 标记未读 - - //- 导航 - .detail-navigation - a.btn.btn-outline(href="/admin/contacts") ← 返回列表 - -block $$scripts - script. - // 更新联系信息状态 - function updateContactStatus(id, status) { - fetch(`/admin/contacts/${id}/status`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ status }) - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - showToast('success', data.message || '状态更新成功'); - setTimeout(() => { - window.location.reload(); - }, 1000); - } else { - showToast('error', data.message || '状态更新失败'); - } - }) - .catch(error => { - console.error('状态更新失败:', error); - showToast('error', '状态更新失败,请稍后重试'); - }); - } - - // 删除联系信息确认 - function deleteContact(id, title) { - if (confirm(`确定要删除联系信息《${title}》吗?此操作不可撤销。`)) { - fetch(`/admin/contacts/${id}`, { - method: 'DELETE', - headers: { - 'Content-Type': 'application/json', - } - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - showToast('success', data.message || '联系信息删除成功'); - setTimeout(() => { - window.location.href = '/admin/contacts'; - }, 1000); - } else { - showToast('error', data.message || '删除失败'); - } - }) - .catch(error => { - console.error('删除失败:', error); - showToast('error', '删除失败,请稍后重试'); - }); - } - } - - // 复制邮箱地址 - function copyEmail() { - const email = '#{contact.email}'; - navigator.clipboard.writeText(email).then(() => { - showToast('success', '邮箱地址已复制到剪贴板'); - }).catch(err => { - console.error('复制失败:', err); - // 降级处理 - const textArea = document.createElement('textarea'); - textArea.value = email; - document.body.appendChild(textArea); - textArea.select(); - try { - document.execCommand('copy'); - showToast('success', '邮箱地址已复制到剪贴板'); - } catch (fallbackErr) { - showToast('error', '复制失败,请手动复制'); - } - document.body.removeChild(textArea); - }); - } - - // 显示提示消息 - function showToast(type, message) { - const toast = document.createElement('div'); - toast.className = `admin-toast toast-${type}`; - toast.innerHTML = ` - ${message} - - `; - - document.body.appendChild(toast); - - // 自动消失 - setTimeout(() => { - toast.style.opacity = '0'; - setTimeout(() => { - toast.remove(); - }, 300); - }, 3000); - - // 点击关闭 - toast.querySelector('.toast-close').addEventListener('click', () => { - toast.style.opacity = '0'; - setTimeout(() => { - toast.remove(); - }, 300); - }); - } \ No newline at end of file diff --git a/src/views/admin/dashboard.pug b/src/views/admin/dashboard.pug deleted file mode 100644 index 455134c..0000000 --- a/src/views/admin/dashboard.pug +++ /dev/null @@ -1,125 +0,0 @@ -extends ../layouts/admin - -block $$content - .dashboard - //- 页面标题 - .page-header - h1.page-title 仪表盘 - p.page-subtitle 欢迎回到后台管理系统 - - //- 统计卡片 - .stats-grid - .stats-card - .stats-icon.stats-icon-primary 📧 - .stats-info - .stats-number= contactStats.total - .stats-label 总联系信息 - .stats-detail - .stats-breakdown - span.breakdown-item - span.breakdown-label 未读: - span.breakdown-value= contactStats.unread - span.breakdown-item - span.breakdown-label 已读: - span.breakdown-value= contactStats.read - span.breakdown-item - span.breakdown-label 已回复: - span.breakdown-value= contactStats.replied - - .stats-card - .stats-icon.stats-icon-success 📝 - .stats-info - .stats-number= articleStats.total - .stats-label 我的文章 - .stats-detail - .stats-breakdown - span.breakdown-item - span.breakdown-label 已发布: - span.breakdown-value= articleStats.published - span.breakdown-item - span.breakdown-label 草稿: - span.breakdown-value= articleStats.draft - - //- 主要内容区域 - .dashboard-content - .dashboard-grid - //- 最近联系信息 - .dashboard-section - .section-header - h2.section-title 最近联系信息 - a.section-action(href="/admin/contacts") 查看全部 → - - if recentContacts && recentContacts.length > 0 - .contact-list - each contact in recentContacts - .contact-item(class=`status-${contact.status}`) - .contact-header - .contact-info - strong.contact-name= contact.name - span.contact-email= contact.email - .contact-meta - span.contact-status(class=`status-${contact.status}`) - if contact.status === 'unread' - | 未读 - else if contact.status === 'read' - | 已读 - else if contact.status === 'replied' - | 已回复 - span.contact-date= new Date(contact.created_at).toLocaleDateString('zh-CN') - .contact-subject= contact.subject - .contact-actions - a.btn.btn-sm(href=`/admin/contacts/${contact.id}`) 查看详情 - else - .empty-state - .empty-icon 📧 - .empty-text 暂无联系信息 - - //- 最近文章 - .dashboard-section - .section-header - h2.section-title 最近文章 - a.section-action(href="/admin/articles") 查看全部 → - - if recentArticles && recentArticles.length > 0 - .article-list - each article in recentArticles - .article-item - .article-header - .article-info - strong.article-title= article.title - span.article-status(class=`status-${article.status}`) - if article.status === 'published' - | 已发布 - else if article.status === 'draft' - | 草稿 - .article-meta - span.article-date= new Date(article.updated_at).toLocaleDateString('zh-CN') - if article.excerpt - .article-summary= article.excerpt.substring(0, 100) + (article.excerpt.length > 100 ? '...' : '') - .article-actions - a.btn.btn-sm(href=`/admin/articles/${article.id}`) 查看 - a.btn.btn-sm.btn-outline(href=`/admin/articles/${article.id}/edit`) 编辑 - else - .empty-state - .empty-icon 📝 - .empty-text 暂无文章 - a.btn.btn-primary(href="/admin/articles/create") 创建第一篇文章 - - //- 快速操作 - .quick-actions - h3.quick-actions-title 快速操作 - .quick-actions-grid - a.quick-action-card(href="/admin/articles/create") - .quick-action-icon 📝 - .quick-action-title 新建文章 - .quick-action-desc 创建一篇新的文章 - - a.quick-action-card(href="/admin/articles") - .quick-action-icon 📚 - .quick-action-title 管理文章 - .quick-action-desc 查看和编辑我的文章 - - a.quick-action-card(href="/admin/contacts") - .quick-action-icon 📧 - .quick-action-title 联系信息 - .quick-action-desc 查看用户联系信息 \ No newline at end of file diff --git a/src/views/helper/utils.pug b/src/views/helper/utils.pug new file mode 100644 index 0000000..28cebef --- /dev/null +++ b/src/views/helper/utils.pug @@ -0,0 +1,23 @@ +mixin include() + if block + block +//- include的使用方法 +//- +include() +//- - var edit = false +//- include /htmx/footer.pug + +mixin css(url, extranl = false) + if extranl || url.startsWith('http') || url.startsWith('//') + link(rel="stylesheet" type="text/css" href=url) + else + link(rel="stylesheet", href=($config && $config.base || "") + "public/"+ (url.startsWith('/') ? url.slice(1) : url)) + +mixin js(url, extranl = false) + if extranl || url.startsWith('http') || url.startsWith('//') + script(type="text/javascript" src=url) + else + script(src=($config && $config.base || "") + "public/" + (url.startsWith('/') ? url.slice(1) : url)) + +mixin link(href, name) + //- attributes == {class: "btn"} + a(href=href)&attributes(attributes)= name \ No newline at end of file diff --git a/src/views/layouts/admin.pug b/src/views/layouts/admin.pug deleted file mode 100644 index 4046c1c..0000000 --- a/src/views/layouts/admin.pug +++ /dev/null @@ -1,128 +0,0 @@ -include utils.pug - -doctype html -html(lang="zh-CN") - head - block $$head - title #{title ? title + ' - ' : ''}后台管理 - #{$site && $site.site_title || ''} - meta(name="description" content="后台管理系统") - meta(charset="utf-8") - meta(name="viewport" content="width=device-width, initial-scale=1") - +css('lib/reset.css') - +css('css/admin.css') - +js('lib/htmx.min.js') - +js('lib/tailwindcss.3.4.17.js') - body.admin-body - #admin-app - //- 顶部导航栏 - header.admin-header - .admin-header-left - .admin-logo - a(href="/admin") 后台管理 - .admin-header-center - .admin-breadcrumb - span.breadcrumb-item - a(href="/admin") 首页 - if title - span.breadcrumb-separator / - span.breadcrumb-item= title - .admin-header-right - .admin-user-menu - .dropdown - button.dropdown-trigger - span= $user ? $user.name || $user.username : '用户' - i.dropdown-arrow ▼ - .dropdown-menu - a.dropdown-item(href="/profile") 个人资料 - a.dropdown-item(href="/") 前台首页 - .dropdown-divider - a.dropdown-item(href="/logout") 退出登录 - - //- 主要内容区域 - .admin-main - //- 左侧导航 - aside.admin-sidebar - nav.admin-nav - .nav-section - .nav-title 主要功能 - ul.nav-list - li.nav-item - a.nav-link(href="/admin" class=title === '后台管理' ? 'active' : '') - i.nav-icon 📊 - span 仪表盘 - li.nav-item - a.nav-link(href="/admin/articles" class=title === '文章管理' ? 'active' : '') - i.nav-icon 📝 - span 文章管理 - li.nav-item - a.nav-link(href="/admin/contacts" class=title === '联系信息管理' ? 'active' : '') - i.nav-icon 📧 - span 联系信息 - - //- 右侧内容区域 - main.admin-content - if toast - .admin-toast(class=`toast-${toast.type}`) - span= toast.message - button.toast-close × - - .admin-content-inner - block $$content - - //- JavaScript - script(src="/js/admin.js") - block $$scripts - - //- 处理Toast消息 - script. - // Toast消息处理 - const toast = document.querySelector('.admin-toast'); - if (toast) { - setTimeout(() => { - toast.style.opacity = '0'; - setTimeout(() => { - toast.remove(); - }, 300); - }, 3000); - - const closeBtn = toast.querySelector('.toast-close'); - if (closeBtn) { - closeBtn.addEventListener('click', () => { - toast.style.opacity = '0'; - setTimeout(() => { - toast.remove(); - }, 300); - }); - } - } - - // 下拉菜单处理 - const dropdown = document.querySelector('.dropdown'); - if (dropdown) { - const trigger = dropdown.querySelector('.dropdown-trigger'); - const menu = dropdown.querySelector('.dropdown-menu'); - - trigger.addEventListener('click', (e) => { - e.stopPropagation(); - dropdown.classList.toggle('active'); - }); - - document.addEventListener('click', () => { - dropdown.classList.remove('active'); - }); - } - - // 移动端侧边栏切换 - function toggleSidebar() { - document.body.classList.toggle('sidebar-open'); - } - - // 响应式处理 - function handleResize() { - if (window.innerWidth > 768) { - document.body.classList.remove('sidebar-open'); - } - } - - window.addEventListener('resize', handleResize); - handleResize(); \ No newline at end of file diff --git a/src/views/layouts/base.pug b/src/views/layouts/base.pug deleted file mode 100644 index c8f6c3b..0000000 --- a/src/views/layouts/base.pug +++ /dev/null @@ -1,58 +0,0 @@ -mixin include() - if block - block - -mixin css(url, extranl = false) - if extranl || url.startsWith('http') || url.startsWith('//') - link(rel="stylesheet" type="text/css" href=url) - else - link(rel="stylesheet", href=($config && $config.base || "") + url) - -mixin js(url, extranl = false) - if extranl || url.startsWith('http') || url.startsWith('//') - script(type="text/javascript" src=url) - else - script(src=($config && $config.base || "") + url) - -mixin link(href, name) - //- attributes == {class: "btn"} - a(href=href)&attributes(attributes)= name - -doctype html -html(lang="zh-CN") - head - block head - title #{site_title || $site && $site.site_title || ''} - meta(name="description" content=site_description || $site && $site.site_description || '') - meta(name="keywords" content=keywords || $site && $site.keywords || '') - if $site && $site.site_favicon - link(rel="shortcut icon", href=$site.site_favicon) - meta(charset="utf-8") - meta(name="viewport" content="width=device-width, initial-scale=1") - +css('reset.css') - +js('lib/htmx.min.js') - +js('https://cdn.tailwindcss.com') - +css('https://unpkg.com/simplebar@latest/dist/simplebar.css', true) - +css('simplebar-shim.css') - +js('https://unpkg.com/simplebar@latest/dist/simplebar.min.js', true) - //- body(style="--bg:url("+($site && $site.site_bg || '#fff')+")") - //- body(style="--bg:url(./static/bg2.webp)") - body - noscript - style. - .simplebar-content-wrapper { - scrollbar-width: auto; - -ms-overflow-style: auto; - } - - .simplebar-content-wrapper::-webkit-scrollbar, - .simplebar-hide-scrollbar::-webkit-scrollbar { - display: initial; - width: initial; - height: initial; - } - div(data-simplebar style="height: 100%") - div(style="height: 100%; display: flex; flex-direction: column") - block content - block scripts - +js('lib/bg-change.js') diff --git a/src/views/layouts/bg-page.pug b/src/views/layouts/bg-page.pug deleted file mode 100644 index 48c4374..0000000 --- a/src/views/layouts/bg-page.pug +++ /dev/null @@ -1,18 +0,0 @@ -extends /layouts/root.pug -//- 采用纯背景页面的布局,背景图片随机切换,卡片采用高斯滤镜类玻璃化效果 -//- .card - -block $$head - +css('css/layouts/bg-page.css') - block pageHead - -block $$content - .page-layout - .page - block pageContent - footer - include /htmx/footer.pug - -block $$scripts - +js('lib/bg-change.js') - block pageScripts diff --git a/src/views/layouts/empty.pug b/src/views/layouts/empty.pug deleted file mode 100644 index b600af7..0000000 --- a/src/views/layouts/empty.pug +++ /dev/null @@ -1,15 +0,0 @@ -extends /layouts/root.pug - -block $$head - +css('css/layouts/empty.css') - block pageHead - -block $$content - include /htmx/navbar/index.pug - .page-layout - .page.my-5 - block pageContent - include /htmx/footer/index.pug - -block $$scripts - block pageScripts diff --git a/src/views/layouts/page.pug b/src/views/layouts/page.pug deleted file mode 100644 index f6353e1..0000000 --- a/src/views/layouts/page.pug +++ /dev/null @@ -1,31 +0,0 @@ -extends /layouts/base.pug - -block head - +css('styles.css') - block pageHead - -block content - .page-layout - .page - - const navs = []; - - navs.push({ href: '/', label: '首页' }); - - navs.push({ href: '/articles', label: '文章' }); - - navs.push({ href: '/article', label: '收藏' }); - - navs.push({ href: '/about', label: '关于' }); - nav.nav - ul.flota-nav - each nav in navs - li - a.item( - href=nav.href, - class=currentPath === nav.href ? 'active' : '' - ) #{nav.label} - .content - block pageContent - footer - +include() - - var edit = false - include /htmx/footer.pug - -block scripts - block pageScripts diff --git a/src/views/layouts/pure.pug b/src/views/layouts/pure.pug deleted file mode 100644 index 7727749..0000000 --- a/src/views/layouts/pure.pug +++ /dev/null @@ -1,16 +0,0 @@ -extends /layouts/root.pug - -block $$head - +css('styles.css') - block pageHead - -block $$content - .page-layout - .page - .content - block pageContent - footer - include /htmx/footer.pug - -block $$scripts - block pageScripts diff --git a/src/views/layouts/root.pug b/src/views/layouts/root.pug index bf90b11..6012582 100644 --- a/src/views/layouts/root.pug +++ b/src/views/layouts/root.pug @@ -1,4 +1,4 @@ -include utils.pug +include /helper/utils.pug doctype html html(lang="zh-CN") diff --git a/src/views/layouts/utils.pug b/src/views/layouts/utils.pug deleted file mode 100644 index 28cebef..0000000 --- a/src/views/layouts/utils.pug +++ /dev/null @@ -1,23 +0,0 @@ -mixin include() - if block - block -//- include的使用方法 -//- +include() -//- - var edit = false -//- include /htmx/footer.pug - -mixin css(url, extranl = false) - if extranl || url.startsWith('http') || url.startsWith('//') - link(rel="stylesheet" type="text/css" href=url) - else - link(rel="stylesheet", href=($config && $config.base || "") + "public/"+ (url.startsWith('/') ? url.slice(1) : url)) - -mixin js(url, extranl = false) - if extranl || url.startsWith('http') || url.startsWith('//') - script(type="text/javascript" src=url) - else - script(src=($config && $config.base || "") + "public/" + (url.startsWith('/') ? url.slice(1) : url)) - -mixin link(href, name) - //- attributes == {class: "btn"} - a(href=href)&attributes(attributes)= name \ No newline at end of file diff --git a/src/views/page/extra/contactSuccess.pug b/src/views/page/extra/contactSuccess.pug deleted file mode 100644 index 1b0db00..0000000 --- a/src/views/page/extra/contactSuccess.pug +++ /dev/null @@ -1,62 +0,0 @@ -extends /layouts/empty.pug - -block pageHead - -block pageContent - .contact-success.container(class="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/index/index copy.pug b/src/views/page/index/index copy.pug deleted file mode 100644 index cb73249..0000000 --- a/src/views/page/index/index copy.pug +++ /dev/null @@ -1,51 +0,0 @@ -extends /layouts/empty.pug - -block pageHead - +js("https://unpkg.com/tiny-swiper@latest/lib/index.min.js") - -mixin PeopleCared(name, role, desc, avatar) - .bg-white.shadow-md.rounded-md.p-6.flex.items-center.gap-4 - img.w-16.h-16.rounded-full.object-cover(src=avatar alt=name) - .flex-1 - .text-lg.font-semibold.text-gray-900 #{name} - if role - .text-sm.text-gray-500.mt-1 #{role} - if desc - p.text-sm.text-gray-600.mt-2 #{desc} - - -block pageContent - .-mt-5 - //- form(action="/upload" method="post" enctype="multipart/form-data" class="mb-4 flex items-center") - //- input(type="file" name="file" required) - //- button(type="submit" class="ml-2 px-4 py-2 bg-blue-500 text-white rounded") 上传文件 - //- box-shadow 是在所有内容底部 - .swiper-container(style="height:400px;box-shadow: inset 0 -100px 120px #fff;overflow:hidden;mask-image: linear-gradient(to top, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 40%);") - .swiper-wrapper.h-full - .swiper-slide(style="flex-shrink: 0;") - img(src="https://user-images.githubusercontent.com/10026019/102327264-712d5880-3fc0-11eb-8f07-7d58264938c1.png") - .swiper-slide(style="flex-shrink: 0;") - img(src="https://user-images.githubusercontent.com/10026019/102327264-712d5880-3fc0-11eb-8f07-7d58264938c1.png") - .swiper-slide(style="flex-shrink: 0;") - img(src="https://user-images.githubusercontent.com/10026019/102327264-712d5880-3fc0-11eb-8f07-7d58264938c1.png") - .container - .grid.grid-cols-1.gap-4(class="md:grid-cols-4") - +PeopleCared('张三', '产品经理', '专注首页体验优化与可访问性改进', 'https://bpic.588ku.com/element_origin_min_pic/23/07/11/d32dabe266d10da8b21bd640a2e9b611.jpg!r650') - +PeopleCared('张三', '产品经理', '专注首页体验优化与可访问性改进', 'https://bpic.588ku.com/element_origin_min_pic/23/07/11/d32dabe266d10da8b21bd640a2e9b611.jpg!r650') - +PeopleCared('张三', '产品经理', '专注首页体验优化与可访问性改进', 'https://bpic.588ku.com/element_origin_min_pic/23/07/11/d32dabe266d10da8b21bd640a2e9b611.jpg!r650') - +PeopleCared('张三', '产品经理', '专注首页体验优化与可访问性改进', 'https://bpic.588ku.com/element_origin_min_pic/23/07/11/d32dabe266d10da8b21bd640a2e9b611.jpg!r650') - +PeopleCared('张三', '产品经理', '专注首页体验优化与可访问性改进', 'https://bpic.588ku.com/element_origin_min_pic/23/07/11/d32dabe266d10da8b21bd640a2e9b611.jpg!r650') - -block pageScripts - script. - //- Swiper.use([ SwiperPluginLazyload, SwiperPluginPagination ]) - const swiper = new Swiper(".swiper-container", { - //- loop: true, - //- pagination: { - //- el: ".swiper-pagination", - //- clickable: true, - //- }, - //- lazy: { - //- loadPrevNext: true, - //- }, - }); \ No newline at end of file diff --git a/src/views/page/index/index.pug b/src/views/page/index/index.pug index cd75fb3..81b0fb6 100644 --- a/src/views/page/index/index.pug +++ b/src/views/page/index/index.pug @@ -20,6 +20,14 @@ block $$content .container div(class="mt-[75px] p-[40px] h-[350px] flex justify-center items-center text-xl" style="line-height: 2.1;") .typed(style="letter-spacing: 0.2em;") + .text-2xl.font-bold.mb-6.ml-3.text-center 作品 + .grid.grid-cols-1.gap-6(class="md:grid-cols-2 lg:grid-cols-3") + .p-3.flex.gap-4 + img(src="https://bpic.588ku.com/element_origin_min_pic/23/07/11/d32dabe266d10da8b21bd640a2e9b611.jpg!r650", alt="", class="w-20 h-20 shadow rounded-[50%] object-cover") + .flex.justify-center.flex-col + a.font-bold.text-lg.mb-2(href="/") 灵巧代码 + p.text-gray-600.line-clamp-2(style="min-height: 2em") 管理文本 + .text-center.text-sm.my-6.p-2(class) 查看更多 include /htmx/footer/index.pug block $$scripts @@ -34,7 +42,7 @@ block $$scripts var options = { strings: [`烟锁长堤柳色新,
霞映归舟渡水滨。
过客停桡寻旧梦,
一川风月正宜人。`], - typeSpeed: 60, + typeSpeed: 80, shuffle: true, showCursor: false, }; diff --git a/src/views/temp/person.pug b/src/views/temp/person.pug deleted file mode 100644 index a78eb26..0000000 --- a/src/views/temp/person.pug +++ /dev/null @@ -1,9 +0,0 @@ -extends /layouts/pure.pug - -block pageHead - +css("css/page/index.css") - -block pageContent - +include() - - let timeLine = [{icon: '11',title: "aaaa",desc:"asd"}] - include /htmx/timeline.pug \ No newline at end of file