diff --git a/cwd-admin/package.json b/cwd-admin/package.json index 337035b..7d5c033 100644 --- a/cwd-admin/package.json +++ b/cwd-admin/package.json @@ -8,14 +8,16 @@ "build": "vite build" }, "dependencies": { + "@phosphor-icons/vue": "^2.2.1", + "echarts": "^5.5.0", "vue": "^3.5.13", - "vue-router": "^4.4.5", - "echarts": "^5.5.0" + "vue-router": "^4.4.5" }, "devDependencies": { "@vitejs/plugin-vue": "^5.1.4", + "less": "^4.5.1", "typescript": "^5.5.2", "vite": "^6.0.11", "vue-tsc": "^2.1.10" } -} \ No newline at end of file +} diff --git a/cwd-admin/src/main.ts b/cwd-admin/src/main.ts index f68f42b..6333de3 100644 --- a/cwd-admin/src/main.ts +++ b/cwd-admin/src/main.ts @@ -1,9 +1,12 @@ import { createApp } from 'vue'; -import './assets/theme.css'; import App from './App.vue'; import { router } from './router'; +import PhosphorIcons from '@phosphor-icons/vue'; + +import './styles/theme.css'; + const app = createApp(App); app.use(router); +app.use(PhosphorIcons); app.mount('#app'); - diff --git a/cwd-admin/src/router/index.ts b/cwd-admin/src/router/index.ts index b19f666..d63c1c2 100644 --- a/cwd-admin/src/router/index.ts +++ b/cwd-admin/src/router/index.ts @@ -1,11 +1,11 @@ import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'; -import LoginView from '../views/LoginView.vue'; -import LayoutView from '../views/LayoutView.vue'; -import CommentsView from '../views/CommentsView.vue'; -import SettingsView from '../views/SettingsView.vue'; -import DataView from '../views/DataView.vue'; -import StatsView from '../views/StatsView.vue'; -import AnalyticsVisitView from '../views/AnalyticsVisitView.vue'; +import LoginView from '../views/LoginView/index.vue'; +import LayoutView from '../views/LayoutView/index.vue'; +import CommentsView from '../views/CommentsView/index.vue'; +import SettingsView from '../views/SettingsView/index.vue'; +import DataView from '../views/DataView/index.vue'; +import StatsView from '../views/StatsView/index.vue'; +import AnalyticsVisitView from '../views/AnalyticsVisitView/index.vue'; const routes: RouteRecordRaw[] = [ { diff --git a/cwd-admin/src/styles/components/analyticsvisit.less b/cwd-admin/src/styles/components/analyticsvisit.less new file mode 100644 index 0000000..6035dd0 --- /dev/null +++ b/cwd-admin/src/styles/components/analyticsvisit.less @@ -0,0 +1,261 @@ +.page { + display: flex; + flex-direction: column; + gap: 12px; +} + +.page-title { + margin: 0; + font-size: 18px; + color: var(--text-primary); +} + +.toolbar { + display: flex; + justify-content: flex-start; + align-items: center; + margin: 0; + gap: 8px; +} + +.toolbar-left { + display: flex; + gap: 8px; +} + +.toolbar-select { + padding: 8px 8px; + box-sizing: border-box; + font-size: 13px; + border: 1px solid var(--border-color); + border-radius: 4px; + background-color: var(--bg-input); + color: var(--text-primary); +} + +.card { + background-color: var(--bg-card); + border-radius: 6px; + border: 1px solid var(--border-color); + padding: 16px 18px; +} + +.card-title { + margin: 0 0 12px; + font-size: 16px; + color: var(--text-primary); +} + +.card-title-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + margin-bottom: 12px; +} + +.page-hint { + font-size: 14px; + color: var(--text-secondary); +} + +.page-error { + font-size: 14px; + color: var(--color-danger); +} + +.stats-grid { + display: grid; + grid-template-columns: repeat(5, minmax(0, 1fr)); + gap: 12px; +} + +.stats-item { + padding: 10px 12px; + border-radius: 6px; + background-color: var(--bg-sider); + border: 1px solid var(--border-color); +} + +.stats-label { + font-size: 14px; + color: var(--text-secondary); + margin-bottom: 4px; +} + +.stats-value { + font-size: 26px; + font-weight: 600; + color: var(--text-primary); +} + +.domain-table-wrapper { + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; +} + +.domain-table { + border: 1px solid var(--border-color); + border-radius: 6px; + min-width: 520px; + overflow: hidden; +} + +.domain-table-header { + display: flex; + background-color: var(--bg-sider); + color: var(--text-secondary); +} + +.domain-table-row { + display: flex; + border-top: 1px solid var(--border-color); +} + +.domain-cell { + flex: 1; + padding: 10px 10px; + font-size: 14px; + color: var(--text-primary); + box-sizing: border-box; +} + +.domain-cell a { + color: var(--text-link); + text-decoration: none; +} + +.domain-cell a:hover { + text-decoration: underline; +} + +.domain-cell-title { + font-weight: 500; +} + +.domain-cell-pv { + text-align: center; + flex: 0 0 220px; +} + +.domain-cell-rank { + flex: 0 0 60px; + text-align: center; +} + +.domain-cell-like { + flex: 0 0 80px; + text-align: center; +} + +.domain-cell-like-rate { + flex: 0 0 90px; + text-align: center; +} + +.domain-cell-time { + flex: 0 0 170px; +} + +.domain-cell-url { + word-break: break-all; +} + +@media (max-width: 768px) { + .domain-table { + width: 610px; + } + + .domain-cell { + flex: none; + } + + .domain-cell-title { + width: 160px; + } + + .domain-cell-pv { + width: 100px; + } + + .domain-cell-rank { + width: 60px; + } + + .domain-cell-like { + width: 80px; + } + + .domain-cell-like-rate { + width: 90px; + } + + .domain-cell-time { + width: 150px; + } + + .domain-cell-url { + width: 200px; + } +} + +.chart { + width: 100%; + height: 260px; +} + +.toast { + position: fixed; + top: 20px; + left: 50%; + transform: translateX(-50%); + min-width: 220px; + max-width: 320px; + padding: 10px 14px; + border-radius: 6px; + font-size: 13px; + box-shadow: var(--shadow-card); + z-index: 1000; +} + +.toast-success { + background-color: var(--color-success); + color: var(--text-inverse); +} + +.toast-error { + background-color: var(--color-danger); + color: var(--text-inverse); +} + +.visit-tabs { + display: inline-flex; + border-radius: 999px; + border: 1px solid var(--border-color); + overflow: hidden; +} + +.visit-tab { + padding: 4px 10px; + font-size: 12px; + border: none; + background-color: var(--bg-card); + color: var(--text-secondary); + cursor: pointer; +} + +.visit-tab+.visit-tab { + border-left: 1px solid var(--border-color); +} + +.visit-tab-active { + background-color: var(--primary-color); + color: var(--text-inverse); +} + +@media (max-width: 768px) { + .stats-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} \ No newline at end of file diff --git a/cwd-admin/src/styles/components/comments.less b/cwd-admin/src/styles/components/comments.less new file mode 100644 index 0000000..bb9b5b8 --- /dev/null +++ b/cwd-admin/src/styles/components/comments.less @@ -0,0 +1,493 @@ +.page { + display: flex; + flex-direction: column; + gap: 12px; +} + +.page-title { + margin: 0; + font-size: 18px; + color: var(--text-primary); +} + +.toolbar { + display: flex; + justify-content: space-between; + align-items: center; + margin: 0; + gap: 8px; +} + +.toolbar-left { + display: flex; + gap: 8px; +} + +.toolbar-right { + display: flex; + gap: 8px; +} + +.toolbar-select { + padding: 8px 8px; + box-sizing: border-box; + font-size: 13px; + border: 1px solid var(--border-color); + border-radius: 4px; + background-color: var(--bg-input); + color: var(--text-primary); +} + +.toolbar-button { + padding: 6px 10px; + border-radius: 4px; + border: 1px solid var(--border-color); + background-color: var(--bg-sider); + color: var(--text-primary); + cursor: pointer; + font-size: 14px; + min-width: 70px; +} + +.page-hint { + font-size: 14px; + color: var(--text-secondary); +} + +.page-error { + font-size: 14px; + color: var(--color-danger); +} + +.comment-table { + background-color: var(--bg-card); + border: 1px solid var(--border-color); + border-radius: 6px; + overflow: hidden; + overflow-x: auto; +} + +.table-header { + display: flex; + background-color: var(--bg-sider); +} + +.table-row { + display: flex; + /* border-bottom: 1px solid var(--border-color); */ +} + +.table-row:last-child { + border-bottom: none; +} + +.table-row:hover .table-cell { + background-color: var(--bg-hover); +} + +.table-cell { + padding: 10px 12px; + font-size: 13px; + color: var(--text-primary); + display: flex; + align-items: center; + box-sizing: border-box; + overflow: hidden; + border-bottom: 1px solid var(--border-color); +} + +.table-cell-id { + width: 120px; + flex-shrink: 0; + white-space: nowrap; +} + +.table-cell-author { + width: 220px; + flex-shrink: 0; +} + +.table-cell-content { + flex-direction: column; + flex: 1; + align-items: flex-start !important; + justify-content: center; + min-width: 200px; +} + +.table-cell-path { + width: 240px; + flex-shrink: 0; +} + +.table-cell-likes { + width: 80px; + flex-shrink: 0; + justify-content: center; +} + +.table-cell-time { + width: 150px; + flex-shrink: 0; +} + +.table-cell-status { + width: 180px; + flex-shrink: 0; + align-items: center; +} + +.table-cell-actions { + white-space: nowrap; + flex-shrink: 0; + justify-content: flex-start; + align-items: center; + width: 250px; +} + +.table-header .table-cell { + font-weight: 500; + color: var(--text-secondary); + align-items: center; + background-color: var(--bg-sider); + border-bottom: 1px solid var(--border-color); +} + +.cell-id { + font-size: 12px; + color: var(--text-secondary); +} + +.cell-author-name { + font-size: 14px; + font-weight: 500; + margin-bottom: 2px; +} + +.cell-author-email { + font-size: 12px; + color: var(--text-secondary); + word-break: break-all; + margin-bottom: 2px; +} + +.cell-email-text { + cursor: pointer; +} + +.cell-email-text:hover { + text-decoration: underline; +} + +.cell-content-text { + font-size: 13px; + line-height: 1.5; + white-space: pre-wrap; + word-break: break-word; + margin-bottom: 5px; +} + +.cell-path { + font-size: 13px; + color: var(--text-link); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + text-decoration: none; +} + +.cell-path:hover { + text-decoration: underline; +} + +.cell-time { + font-size: 12px; + color: var(--text-secondary); +} + +.cell-author-wrapper { + display: flex; + align-items: flex-start; + gap: 8px; +} + +.cell-author-main { + display: flex; + flex-direction: column; +} + +.cell-avatar { + width: 32px; + height: 32px; + border-radius: 5px; + flex-shrink: 0; +} + +.cell-status { + padding: 3px 8px; + border-radius: 999px; + font-size: 12px; + font-weight: 500; +} + +.cell-status-wrapper { + display: flex; + gap: 5px; + white-space: nowrap; +} + +.cell-status-approved { + color: var(--color-success); + background-color: rgba(26, 127, 55, 0.1); + /* Simplified bg based on color */ +} + +.cell-status-pending { + color: var(--color-warning); + background-color: rgba(154, 103, 0, 0.1); +} + +.cell-status-rejected { + color: var(--color-danger); + background-color: rgba(209, 36, 47, 0.1); +} + +.cell-likes-number { + color: var(--color-danger); + background-color: rgba(209, 36, 47, 0.1); + display: inline-flex; + align-items: center; + justify-content: center; + gap: 4px; +} + +.cell-pin-flag { + color: #825802; + /* Maybe var(--color-warning)? */ + background-color: #f7c848; +} + +.status-select { + padding: 3px 8px; + border-radius: 4px; + border: 1px solid var(--border-color); + background-color: var(--bg-input); + color: var(--text-primary); + font-size: 13px; + cursor: pointer; +} + +.table-actions { + display: flex; + gap: 4px; + flex-wrap: wrap; +} + +.table-action { + padding: 3px 8px; + border-radius: 4px; + border: 1px solid var(--border-color); + background-color: var(--bg-input); + color: var(--text-primary); + font-size: 13px; + cursor: pointer; +} + +.table-action:hover { + background-color: var(--bg-hover); +} + +.table-action:disabled { + opacity: 0.4; + cursor: not-allowed; +} + +.table-action-danger { + border-color: var(--color-danger); + color: var(--color-danger); +} + +.table-action-danger:hover { + background-color: rgba(209, 36, 47, 0.1); +} + +.table-empty { + padding: 32px; + text-align: center; + color: var(--text-secondary); + font-size: 13px; +} + +.pagination { + margin-top: 30px; + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 8px; + white-space: nowrap; +} + +.pagination-button { + height: 28px; + min-width: 28px; + padding: 4px 8px; + border-radius: 4px; + border: 1px solid var(--border-color); + background-color: var(--bg-sider); + color: var(--text-primary); + font-size: 12px; + cursor: pointer; + box-sizing: border-box; +} + +.pagination-button:disabled { + opacity: 0.4; + cursor: not-allowed; +} + +.pagination-button-active { + background-color: var(--primary-color); + color: var(--text-inverse); + border-color: var(--primary-color); +} + +.pagination-ellipsis { + padding: 0 2px; + font-size: 12px; + color: var(--text-secondary); +} + +.pagination-jump { + display: flex; + align-items: center; + gap: 4px; + font-size: 12px; + color: var(--text-secondary); +} + +.pagination-input { + width: 60px; + height: 28px; + box-sizing: border-box; + padding: 2px 4px; + border-radius: 4px; + border: 1px solid var(--border-color); + background-color: var(--bg-input); + color: var(--text-primary); + font-size: 12px; +} + +.cell-ip-text { + cursor: pointer; +} + +.cell-ip-text:hover { + text-decoration: underline; +} + +.modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); + display: flex; + align-items: stretch; + justify-content: flex-end; + z-index: 2000; +} + +.modal { + background-color: var(--bg-card); + border-radius: 0; + box-shadow: var(--shadow-card); + width: 100%; + max-width: 600px; + padding: 20px 20px 24px; + display: flex; + flex-direction: column; + gap: 16px; + transform: translateX(0); + animation: drawer-in 0.2s ease-out; + overflow-y: auto; +} + +.modal-title { + margin: 0; + font-size: 16px; + font-weight: 600; + color: var(--text-primary); +} + +.modal-body { + display: flex; + flex-direction: column; + gap: 12px; +} + +.modal-actions { + display: flex; + justify-content: flex-end; + gap: 10px; + margin-top: 4px; +} + +.modal-btn { + padding: 8px 16px; + border-radius: 4px; + font-size: 14px; + cursor: pointer; + border: 1px solid transparent; +} + +.modal-btn.primary { + background-color: var(--primary-color); + color: var(--text-inverse); +} + +.modal-btn.secondary { + background-color: var(--bg-sider); + border-color: var(--border-color); + color: var(--text-primary); +} + +.modal-btn:hover { + opacity: 0.9; +} + +.form-item { + display: flex; + flex-direction: column; + gap: 6px; +} + +.form-label { + font-size: 13px; + color: var(--text-secondary); +} + +.form-input { + padding: 8px 10px; + border-radius: 4px; + border: 1px solid var(--border-color); + background-color: var(--bg-input); + color: var(--text-primary); + font-size: 13px; + outline: none; +} + +.form-input:focus { + border-color: var(--primary-color); + box-shadow: 0 0 0 1px rgba(9, 105, 218, 0.2); +} + +@keyframes drawer-in { + from { + transform: translateX(100%); + } + + to { + transform: translateX(0); + } +} \ No newline at end of file diff --git a/cwd-admin/src/styles/components/data.less b/cwd-admin/src/styles/components/data.less new file mode 100644 index 0000000..eb86b29 --- /dev/null +++ b/cwd-admin/src/styles/components/data.less @@ -0,0 +1,215 @@ +.page { + display: flex; + flex-direction: column; + gap: 12px; + max-width: 600px; + /* Increased width slightly */ +} + +.page-title { + margin: 0; + font-size: 18px; + color: var(--text-primary); +} + +.card { + background-color: var(--bg-card); + border-radius: 6px; + border: 1px solid var(--border-color); + padding: 16px 18px; + margin-bottom: 0.5em; + /* Reduced spacing */ +} + +.card-title { + margin: 0 0 8px; + font-size: 15px; + color: var(--text-primary); + font-weight: 600; +} + +.card-desc { + font-size: 13px; + color: var(--text-secondary); + margin: 0 0 16px; +} + +.action-row { + display: flex; + align-items: center; + gap: 12px; + margin-top: 8px; +} + +.action-label { + font-size: 13px; + color: var(--text-primary); + min-width: 40px; +} + +.card-button { + padding: 6px 14px; + border-radius: 4px; + border: 1px solid transparent; + background-color: var(--primary-color); + color: var(--text-inverse); + font-size: 13px; + cursor: pointer; + transition: all 0.2s; +} + +.card-button:hover { + opacity: 0.9; +} + +.card-button:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +.card-button.secondary { + background-color: var(--bg-input); + border-color: var(--border-color); + color: var(--text-primary); +} + +.card-button.secondary:hover { + background-color: var(--bg-sider); +} + +.card-button.success { + background-color: var(--color-success); +} + +.form-select { + padding: 6px 8px; + border: 1px solid var(--border-color); + border-radius: 4px; + font-size: 13px; + color: var(--text-primary); + background-color: var(--bg-input); + outline: none; +} + +/* Toast & Log styles same as before */ +.toast { + position: fixed; + top: 20px; + left: 50%; + transform: translateX(-50%); + min-width: 220px; + padding: 10px 14px; + border-radius: 6px; + font-size: 13px; + box-shadow: var(--shadow-card); + z-index: 1000; +} + +.toast-success { + background-color: var(--color-success); + color: var(--text-inverse); +} + +.toast-error { + background-color: var(--color-danger); + color: var(--text-inverse); +} + +.log-container { + margin-top: 16px; + padding: 12px; + background-color: var(--bg-sider); + border-radius: 6px; + border: 1px solid var(--border-color); +} + +.log-title { + font-size: 13px; + font-weight: 600; + margin-bottom: 8px; +} + +.log-list { + display: flex; + flex-direction: column; + gap: 4px; + max-height: 200px; + overflow-y: auto; +} + +.log-item { + font-size: 12px; + font-family: monospace; + color: var(--text-secondary); +} + +/* Modal Styles */ +.modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 2000; +} + +.modal { + background-color: var(--bg-card); + border-radius: 6px; + width: 400px; + max-width: 90%; + padding: 20px; + display: flex; + flex-direction: column; + gap: 16px; +} + +.modal-title { + margin: 0; + font-size: 16px; + font-weight: 600; +} + +.modal-desc { + margin: 0; + font-size: 14px; + color: var(--text-secondary); + line-height: 1.5; +} + +.modal-actions { + display: flex; + justify-content: flex-end; + gap: 10px; +} + +.modal-btn { + padding: 8px 16px; + border-radius: 4px; + font-size: 14px; + cursor: pointer; + border: none; +} + +.modal-btn.primary { + background-color: var(--primary-color); + color: white; +} + +.modal-btn.secondary { + background-color: var(--bg-sider); + color: var(--text-primary); + border: 1px solid var(--border-color); +} + +.form-input { + padding: 8px; + border: 1px solid var(--border-color); + border-radius: 4px; + width: 100%; + box-sizing: border-box; +} \ No newline at end of file diff --git a/cwd-admin/src/styles/components/login.less b/cwd-admin/src/styles/components/login.less new file mode 100644 index 0000000..438447b --- /dev/null +++ b/cwd-admin/src/styles/components/login.less @@ -0,0 +1,175 @@ +.login-page { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 10px; + min-height: 100vh; + padding: 10px; + box-sizing: border-box; + background: var(--bg-body); + background: radial-gradient(circle at top, var(--bg-hover) 0, var(--bg-body) 100%); +} + +.login-card { + background-color: var(--bg-card); + padding: 32px 32px 28px; + border-radius: 12px; + border: 1px solid var(--border-color); + box-shadow: var(--shadow-login); + width: 100%; + max-width: 420px; + box-sizing: border-box; +} + +.login-icon { + max-width: 80px; + display: block; + margin: 0 auto; + margin-bottom: 10px; +} + +.login-title { + margin: 0; + font-size: inherit; + text-align: center; + color: var(--text-primary); + letter-spacing: 0.03em; +} + +.login-subtitle { + display: flex; + align-items: center; + justify-content: center; + margin: 8px 0 28px; + font-size: 13px; + text-align: center; + color: var(--text-secondary); +} + +.login-form { + display: flex; + flex-direction: column; + gap: 16px; +} + +.form-item { + display: flex; + flex-direction: column; + gap: 6px; +} + +.form-label { + font-size: 14px; + color: var(--text-secondary); +} + +.form-input { + padding: 9px 12px; + border-radius: 8px; + border: 1px solid var(--border-color); + font-size: 14px; + outline: none; + background-color: var(--bg-input); + color: var(--text-primary); + transition: border-color 0.16s ease, box-shadow 0.16s ease, background-color 0.16s ease; +} + +.form-input:focus { + border-color: var(--primary-color); + background-color: var(--bg-input); + box-shadow: 0 0 0 1px rgba(37, 99, 235, 0.18); +} + +.form-input-wrapper { + position: relative; + display: flex; + align-items: center; +} + +.form-input-wrapper .form-input { + width: 100%; + padding-right: 38px; +} + +.password-toggle { + position: absolute; + right: 10px; + display: flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + border: none; + background: transparent; + cursor: pointer; + color: var(--text-secondary); + padding: 0; +} + +.password-toggle:hover { + color: var(--text-primary); +} + +.password-toggle svg { + width: 16px; +} + +.form-error { + font-size: 13px; + color: var(--color-danger); + margin-top: 4px; +} + +.form-button { + margin-top: 8px; + padding: 10px 0; + border-radius: 30px; + border: none; + background: var(--primary-color); + color: var(--text-inverse); + font-size: 15px; + cursor: pointer; + font-weight: 500; + transition: transform 0.12s ease, box-shadow 0.12s ease, filter 0.12s ease, + background 0.12s ease; + box-shadow: 0 10px 25px rgba(37, 99, 235, 0.28); + width: 100%; +} + +.form-button:disabled { + opacity: 0.7; + cursor: default; + box-shadow: none; + transform: none; +} + +.form-button:not(:disabled):hover { + filter: brightness(1.03); + transform: translateY(-1px); + box-shadow: 0 14px 30px rgba(37, 99, 235, 0.32); +} + +.form-button:not(:disabled):active { + transform: translateY(0); + box-shadow: 0 8px 20px rgba(37, 99, 235, 0.26); +} + +.login-link { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: center; + font-size: 14px; + gap: 10px; +} + +.login-link a { + color: var(--text-secondary); + text-decoration: none; +} + +.login-link a:hover { + color: var(--primary-color); + text-decoration: underline; +} \ No newline at end of file diff --git a/cwd-admin/src/styles/components/setting.less b/cwd-admin/src/styles/components/setting.less new file mode 100644 index 0000000..fc33ba2 --- /dev/null +++ b/cwd-admin/src/styles/components/setting.less @@ -0,0 +1,276 @@ +.page { + display: flex; + flex-direction: column; + gap: 12px; + max-width: 620px +} + +.page-title { + margin: 0; + font-size: 18px; + color: var(--text-primary); +} + +.card { + background-color: var(--bg-card); + border-radius: 6px; + border: 1px solid var(--border-color); + padding: 12px 18px; + margin-bottom: 1em; +} + +.card-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 6px 0; + cursor: pointer; + user-select: none; + transition: background-color 0.2s; +} + +.card-title { + margin: 0; + font-size: 16px; + font-weight: 600; + color: var(--text-primary); +} + +.card-icon { + font-size: 12px; + color: var(--text-secondary); + transition: transform 0.2s ease; +} + +.card-icon.expanded { + transform: rotate(180deg); +} + +.card-body { + /* padding-top: 4px; */ +} + +.card-subtitle { + margin: 0 0 12px; + font-size: 15px; + font-weight: 600; + color: var(--text-primary); +} + +.divider { + height: 1px; + background-color: var(--border-color); + margin: 16px 0; +} + +.form-item { + display: flex; + flex-direction: column; + gap: 6px; + margin: 10px 0; +} + +.form-label { + font-size: 14px; + color: var(--text-secondary); +} + +.form-input { + padding: 8px 10px; + border-radius: 4px; + border: 1px solid var(--border-color); + font-size: 14px; + outline: none; + background-color: var(--bg-input); + color: var(--text-primary); +} + +.form-input:focus { + border-color: var(--primary-color); + box-shadow: 0 0 0 1px rgba(9, 105, 218, 0.2); +} + +.toast { + position: fixed; + top: 20px; + left: 50%; + transform: translateX(-50%); + min-width: 220px; + max-width: 320px; + padding: 10px 14px; + border-radius: 6px; + font-size: 13px; + box-shadow: var(--shadow-card); + z-index: 1000; +} + +.toast-success { + background-color: var(--color-success); + color: var(--text-inverse); +} + +.toast-error { + background-color: var(--color-danger); + color: var(--text-inverse); +} + +.switch { + position: relative; + display: inline-flex; + width: 40px; + height: 22px; + align-items: center; +} + +.switch input { + opacity: 0; + width: 0; + height: 0; +} + +.slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: var(--border-color); + transition: 0.2s; + border-radius: 999px; +} + +.slider::before { + position: absolute; + content: ""; + height: 16px; + width: 16px; + left: 3px; + top: 3px; + background-color: #ffffff; + transition: 0.2s; + border-radius: 50%; + box-shadow: 0 1px 2px rgba(27, 31, 36, 0.15); +} + +.switch input:checked+.slider { + background-color: var(--primary-color); +} + +.switch input:checked+.slider::before { + transform: translateX(16px); +} + +.card-actions { + display: flex; + gap: 10px; + justify-content: flex-end; +} + +.card-button { + padding: 8px 14px; + border-radius: 4px; + border: none; + background-color: var(--primary-color); + color: var(--text-inverse); + font-size: 14px; + cursor: pointer; + min-width: 100px; +} + +.card-button.secondary { + background-color: var(--bg-sider); + color: var(--text-primary); + border: 1px solid var(--border-color); +} + +.card-button.secondary:hover { + background-color: var(--bg-hover); + border-color: var(--border-color); +} + +.card-button:disabled { + opacity: 0.7; + cursor: default; +} + +.form-message { + font-size: 13px; + margin-bottom: 8px; +} + +.form-message-success { + color: var(--color-success); +} + +.form-message-error { + color: var(--color-danger); +} + +.loading-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 40px 0; + gap: 12px; +} + +.loading-spinner { + width: 32px; + height: 32px; + border: 3px solid var(--bg-hover); + border-top-color: var(--primary-color); + border-radius: 50%; + animation: spin 1s linear infinite; +} + +.loading-text { + font-size: 14px; + color: var(--text-secondary); +} + +@keyframes spin { + from { + transform: rotate(0deg); + } + + to { + transform: rotate(360deg); + } +} + +.page-hint { + font-size: 14px; + color: var(--text-secondary); +} + +.form-hint { + font-size: 12px; + color: var(--text-secondary); + margin-top: 4px; + line-height: 1.5; +} + +.form-hint a { + color: var(--primary-color); + text-decoration: none; +} + +.form-hint a:hover { + text-decoration: underline; +} + +.collapse-enter-active, +.collapse-leave-active { + transition: all 0.3s ease; + max-height: 2000px; + opacity: 1; +} + +.collapse-enter-from, +.collapse-leave-to { + max-height: 0; + opacity: 0; + overflow: hidden; +} \ No newline at end of file diff --git a/cwd-admin/src/styles/components/stats.less b/cwd-admin/src/styles/components/stats.less new file mode 100644 index 0000000..c569717 --- /dev/null +++ b/cwd-admin/src/styles/components/stats.less @@ -0,0 +1,203 @@ +.page { + display: flex; + flex-direction: column; + gap: 12px; +} + +.page-title { + margin: 0; + font-size: 18px; + color: var(--text-primary); +} + +.toolbar { + display: flex; + justify-content: flex-start; + align-items: center; + margin: 0; + gap: 8px; +} + +.toolbar-left { + display: flex; + gap: 8px; +} + +.toolbar-select { + padding: 8px 8px; + box-sizing: border-box; + font-size: 13px; + border: 1px solid var(--border-color); + border-radius: 4px; + background-color: var(--bg-input); + color: var(--text-primary); +} + +.card { + background-color: var(--bg-card); + border-radius: 6px; + border: 1px solid var(--border-color); + padding: 16px 18px; +} + +.card-title { + margin: 0 0 12px; + font-size: 16px; + color: var(--text-primary); +} + +.card-title-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + margin-bottom: 12px; +} + +.page-hint { + font-size: 14px; + color: var(--text-secondary); +} + +.page-error { + font-size: 14px; + color: var(--color-danger); +} + +.chart-tabs { + display: inline-flex; + border-radius: 999px; + border: 1px solid var(--border-color); + overflow: hidden; +} + +.chart-tab { + padding: 4px 10px; + font-size: 12px; + border: none; + background-color: var(--bg-card); + color: var(--text-secondary); + cursor: pointer; +} + +.chart-tab+.chart-tab { + border-left: 1px solid var(--border-color); +} + +.chart-tab-active { + background-color: var(--primary-color); + color: var(--text-inverse); +} + +.stats-grid { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 12px; +} + +.stats-item { + padding: 10px 12px; + border-radius: 6px; + background-color: var(--bg-sider); + border: 1px solid var(--border-color); +} + +.stats-label { + font-size: 14px; + color: var(--text-secondary); + margin-bottom: 4px; +} + +.stats-value { + font-size: 30px; + font-weight: 600; + color: var(--text-primary); +} + +.stats-value-approved { + color: var(--color-success); +} + +.stats-value-pending { + color: var(--color-warning); +} + +.stats-value-rejected { + color: var(--color-danger); +} + +.domain-table-wrapper { + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; +} + +.domain-table { + border: 1px solid var(--border-color); + border-radius: 6px; + min-width: 520px; + overflow: hidden; +} + +.domain-table-header { + display: flex; + background-color: var(--bg-sider); + color: var(--text-secondary); +} + +.domain-table-row { + display: flex; + border-top: 1px solid var(--border-color); +} + +.domain-cell { + flex: 1; + padding: 10px 10px; + font-size: 14px; + color: var(--text-primary); + box-sizing: border-box; +} + +.domain-cell-domain { + flex: 2; + font-weight: 500; +} + +.chart-wrapper { + margin-top: 4px; +} + +.chart { + width: 100%; + height: 260px; +} + +.toast { + position: fixed; + top: 20px; + left: 50%; + transform: translateX(-50%); + min-width: 220px; + max-width: 320px; + padding: 10px 14px; + border-radius: 6px; + font-size: 13px; + box-shadow: var(--shadow-card); + z-index: 1000; +} + +.toast-success { + background-color: var(--color-success); + color: var(--text-inverse); +} + +.toast-error { + background-color: var(--color-danger); + color: var(--text-inverse); +} + +@media (max-width: 768px) { + .stats-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} \ No newline at end of file diff --git a/cwd-admin/src/styles/layout.less b/cwd-admin/src/styles/layout.less new file mode 100644 index 0000000..314d21e --- /dev/null +++ b/cwd-admin/src/styles/layout.less @@ -0,0 +1,278 @@ +* { + box-sizing: border-box; +} + +::-webkit-scrollbar { + width: 8px; + height: 6px; +} + +::-webkit-scrollbar-track { + background: var(--bg-body); + border-radius: 0; +} + +::-webkit-scrollbar-thumb { + background: var(--text-secondary); + border-radius: 10px; + transition: all 0.1s linear; +} + +.layout { + display: flex; + flex-direction: column; + height: 100%; +} + +.layout-header { + height: 56px; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 24px; + background-color: var(--bg-header); + color: var(--text-inverse); + border-bottom: 1px solid var(--border-header); +} + +.layout-title { + font-size: 18px; + font-weight: 600; +} + +.layout-actions-wrapper { + display: flex; + align-items: center; + gap: 12px; + position: relative; +} + +.layout-domain-filter { + display: flex; + align-items: center; +} + +.layout-domain-select { + padding: 6px 8px; + border-radius: 4px; + border: 1px solid var(--border-header); + background-color: var(--bg-select); + color: var(--text-inverse); + font-size: 13px; +} + +.layout-sider-domain-filter { + display: none; +} + +.layout-actions { + display: flex; + gap: 8px; +} + +.layout-button { + text-decoration: none; + padding: 6px 10px; + border-radius: 4px; + border: 1px solid var(--border-header); + background-color: var(--bg-select); + color: var(--text-inverse); + cursor: pointer; + font-size: 13px; + display: inline-flex; + align-items: center; + justify-content: center; +} + +.layout-button:hover { + background-color: var(--border-header); +} + +.layout-actions-toggle { + display: none; + align-items: center; + justify-content: center; + padding: 6px; + border-radius: 4px; + border: 1px solid var(--border-header); + background-color: var(--bg-select); + color: var(--text-inverse); + cursor: pointer; +} + +.layout-actions-dropdown { + display: none; +} + +.layout-body { + display: flex; + flex: 1; + min-height: 0; +} + +.layout-sider { + width: 180px; + background-color: var(--bg-sider); + border-right: 1px solid var(--border-color); +} + +.menu { + list-style: none; + margin: 0; + padding: 12px 0; +} + +.menu-item { + padding: 10px 16px; + cursor: pointer; + font-size: 15px; + color: var(--text-primary); +} + +.menu-item:hover { + background-color: var(--bg-hover); +} + +.menu-item.active { + background-color: var(--bg-active); + font-weight: 600; + color: var(--primary-color); +} + +.layout-content { + flex: 1; + padding: 16px 20px 40px; + overflow: auto; +} + +.layout-menu-toggle { + display: none; + align-items: center; + justify-content: center; + padding: 6px 10px; + border-radius: 4px; + border: 1px solid var(--border-header); + background-color: var(--bg-select); + color: var(--text-inverse); + cursor: pointer; + font-size: 13px; +} + +.layout-sider-mask { + display: none; +} + +@media (max-width: 768px) { + .layout { + height: 100vh; + } + + .layout-header { + padding: 0 12px; + gap: 8px; + } + + .layout-title { + font-size: 16px; + } + + .layout-body { + position: relative; + } + + .layout-menu-toggle { + display: inline-flex; + } + + .layout-sider { + position: fixed; + top: 56px; + left: 0; + bottom: 0; + width: 220px; + max-width: 80%; + border-right: 1px solid var(--border-color); + transform: translateX(-100%); + transition: transform 0.2s ease-out; + z-index: 1000; + } + + .layout-sider-mobile-open { + transform: translateX(0); + } + + .layout-sider-mask { + position: fixed; + top: 56px; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.6); + z-index: 900; + display: block; + } + + .layout-content { + padding: 12px; + } + + .layout-actions { + display: none; + } + + .layout-domain-filter-header { + display: none; + } + + .layout-sider-domain-filter { + display: block; + padding: 12px 16px 0; + } + + .layout-sider-domain-filter .layout-domain-select { + width: 100%; + box-sizing: border-box; + } + + .layout-actions-toggle { + display: inline-flex; + } + + .layout-actions-dropdown { + position: fixed; + top: 56px; + right: 12px; + background-color: var(--bg-card); + border: 1px solid var(--border-color); + border-radius: 6px; + box-shadow: var(--shadow-card); + padding: 6px 0; + min-width: 160px; + z-index: 1100; + display: flex; + flex-direction: column; + } + + .layout-actions-item { + padding: 8px 14px; + font-size: 13px; + background: none; + border: none; + text-align: left; + cursor: pointer; + color: var(--text-primary); + width: 100%; + } + + .layout-actions-item:hover { + background-color: var(--bg-hover); + } + + .layout-actions-item-danger { + color: var(--color-danger); + } + + .layout-actions-item-danger:hover { + background-color: rgba(209, 36, 47, 0.1); + } +} \ No newline at end of file diff --git a/cwd-admin/src/assets/markdown.css b/cwd-admin/src/styles/markdown.css similarity index 100% rename from cwd-admin/src/assets/markdown.css rename to cwd-admin/src/styles/markdown.css diff --git a/cwd-admin/src/assets/theme.css b/cwd-admin/src/styles/theme.css similarity index 100% rename from cwd-admin/src/assets/theme.css rename to cwd-admin/src/styles/theme.css diff --git a/cwd-admin/src/views/AnalyticsVisitView.vue b/cwd-admin/src/views/AnalyticsVisitView/index.vue similarity index 78% rename from cwd-admin/src/views/AnalyticsVisitView.vue rename to cwd-admin/src/views/AnalyticsVisitView/index.vue index 12805c5..7397f45 100644 --- a/cwd-admin/src/views/AnalyticsVisitView.vue +++ b/cwd-admin/src/views/AnalyticsVisitView/index.vue @@ -185,7 +185,7 @@ import { type VisitPageItem, fetchLikeStats, type LikeStatsItem, -} from "../api/admin"; +} from "../../api/admin"; const loading = ref(false); const listLoading = ref(false); @@ -514,266 +514,6 @@ watch(domainFilter, () => { }); - diff --git a/cwd-admin/src/views/CommentsView.vue b/cwd-admin/src/views/CommentsView/index.vue similarity index 69% rename from cwd-admin/src/views/CommentsView.vue rename to cwd-admin/src/views/CommentsView/index.vue index dfd97fb..f736cc8 100644 --- a/cwd-admin/src/views/CommentsView.vue +++ b/cwd-admin/src/views/CommentsView/index.vue @@ -268,7 +268,7 @@ - diff --git a/cwd-admin/src/views/DataView.vue b/cwd-admin/src/views/DataView/index.vue similarity index 76% rename from cwd-admin/src/views/DataView.vue rename to cwd-admin/src/views/DataView/index.vue index 797bd2e..9998f01 100644 --- a/cwd-admin/src/views/DataView.vue +++ b/cwd-admin/src/views/DataView/index.vue @@ -120,7 +120,7 @@ import { exportConfig, importConfig, exportStats, importStats, exportBackup, importBackup -} from "../api/admin"; +} from "../../api/admin"; const exporting = ref(false); const importing = ref(false); @@ -358,143 +358,6 @@ function joinUrl(prefix: string, path: string): string { } - diff --git a/cwd-admin/src/views/LayoutView.vue b/cwd-admin/src/views/LayoutView/index.vue similarity index 62% rename from cwd-admin/src/views/LayoutView.vue rename to cwd-admin/src/views/LayoutView/index.vue index 19b23f3..6bae39e 100644 --- a/cwd-admin/src/views/LayoutView.vue +++ b/cwd-admin/src/views/LayoutView/index.vue @@ -151,8 +151,8 @@ - diff --git a/cwd-admin/src/views/LoginView.vue b/cwd-admin/src/views/LoginView/index.vue similarity index 59% rename from cwd-admin/src/views/LoginView.vue rename to cwd-admin/src/views/LoginView/index.vue index a4d91c2..6e0832d 100644 --- a/cwd-admin/src/views/LoginView.vue +++ b/cwd-admin/src/views/LoginView/index.vue @@ -89,7 +89,7 @@ - diff --git a/cwd-admin/src/views/SettingsView/components/Demo.vue b/cwd-admin/src/views/SettingsView/components/Demo.vue new file mode 100644 index 0000000..e69de29 diff --git a/cwd-admin/src/views/SettingsView.vue b/cwd-admin/src/views/SettingsView/index.vue similarity index 86% rename from cwd-admin/src/views/SettingsView.vue rename to cwd-admin/src/views/SettingsView/index.vue index ada1d24..2c5b012 100644 --- a/cwd-admin/src/views/SettingsView.vue +++ b/cwd-admin/src/views/SettingsView/index.vue @@ -339,7 +339,7 @@ class="card-button secondary" :disabled="testingTelegram" @click="testTelegram" - style="margin-right:auto;" + style="margin-right: auto" > 发送中... 发送测试消息 @@ -374,7 +374,7 @@ import { saveTelegramSettings, setupTelegramWebhook, sendTelegramTestMessage, -} from "../api/admin"; +} from "../../api/admin"; const DEFAULT_REPLY_TEMPLATE = `