1904 lines
76 KiB
HTML
1904 lines
76 KiB
HTML
<!doctype html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<title>文章管理后台 - Chookoo</title>
|
||
<style>
|
||
:root {
|
||
color-scheme: light;
|
||
--bg: #f5f7fb;
|
||
--text: #0f172a;
|
||
--muted: #64748b;
|
||
--primary: #2f6bff;
|
||
--primary-hover: #1e56e5;
|
||
--primary-weak: #e8f1ff;
|
||
--card: #ffffff;
|
||
--line: #e2e8f0;
|
||
--success: #10b981;
|
||
--danger: #ef4444;
|
||
--warning: #f59e0b;
|
||
}
|
||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Arial, sans-serif;
|
||
background: var(--bg);
|
||
color: var(--text);
|
||
min-height: 100vh;
|
||
}
|
||
|
||
/* 导航 */
|
||
.navbar {
|
||
background: var(--card);
|
||
border-bottom: 1px solid var(--line);
|
||
padding: 16px 24px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 100;
|
||
}
|
||
.navbar-title {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
}
|
||
.navbar-title h1 {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
}
|
||
.navbar-title span {
|
||
font-size: 12px;
|
||
background: var(--primary-weak);
|
||
color: var(--primary);
|
||
padding: 4px 8px;
|
||
border-radius: 4px;
|
||
}
|
||
.navbar-actions {
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
.btn {
|
||
padding: 10px 16px;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
border-radius: 8px;
|
||
border: none;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
}
|
||
.btn-primary {
|
||
background: var(--primary);
|
||
color: #fff;
|
||
}
|
||
.btn-primary:hover { background: var(--primary-hover); }
|
||
.btn-secondary {
|
||
background: var(--card);
|
||
color: var(--text);
|
||
border: 1px solid var(--line);
|
||
}
|
||
.btn-secondary:hover { background: var(--bg); }
|
||
.btn-success {
|
||
background: var(--success);
|
||
color: #fff;
|
||
}
|
||
.btn-danger {
|
||
background: var(--danger);
|
||
color: #fff;
|
||
}
|
||
.btn-sm {
|
||
padding: 6px 12px;
|
||
font-size: 13px;
|
||
}
|
||
|
||
/* 主内容 */
|
||
.main {
|
||
padding: 24px;
|
||
max-width: 1400px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
/* 统计卡片 */
|
||
.stats {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||
gap: 16px;
|
||
margin-bottom: 24px;
|
||
}
|
||
.stat-card {
|
||
background: var(--card);
|
||
border-radius: 12px;
|
||
padding: 20px;
|
||
border: 1px solid var(--line);
|
||
}
|
||
.stat-label {
|
||
font-size: 13px;
|
||
color: var(--muted);
|
||
margin-bottom: 8px;
|
||
}
|
||
.stat-value {
|
||
font-size: 28px;
|
||
font-weight: 700;
|
||
}
|
||
|
||
/* 内容区 */
|
||
.content {
|
||
display: grid;
|
||
grid-template-columns: 300px 1fr;
|
||
gap: 24px;
|
||
}
|
||
@media (max-width: 900px) {
|
||
.content { grid-template-columns: 1fr; }
|
||
}
|
||
|
||
/* 侧边栏 */
|
||
.sidebar {
|
||
background: var(--card);
|
||
border-radius: 12px;
|
||
border: 1px solid var(--line);
|
||
padding: 20px;
|
||
height: fit-content;
|
||
}
|
||
.sidebar-title {
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
margin-bottom: 16px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
.category-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
}
|
||
.category-item {
|
||
padding: 10px 12px;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
transition: background 0.2s;
|
||
}
|
||
.category-item:hover { background: var(--bg); }
|
||
.category-item.active {
|
||
background: var(--primary-weak);
|
||
color: var(--primary);
|
||
}
|
||
.category-icon {
|
||
width: 32px;
|
||
height: 32px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-radius: 8px;
|
||
font-size: 16px;
|
||
background: var(--bg);
|
||
}
|
||
.category-name { flex: 1; font-size: 14px; }
|
||
.category-count {
|
||
font-size: 12px;
|
||
background: var(--bg);
|
||
padding: 2px 8px;
|
||
border-radius: 999px;
|
||
color: var(--muted);
|
||
}
|
||
|
||
/* 文章列表 */
|
||
.articles-panel {
|
||
background: var(--card);
|
||
border-radius: 12px;
|
||
border: 1px solid var(--line);
|
||
}
|
||
.panel-header {
|
||
padding: 16px 20px;
|
||
border-bottom: 1px solid var(--line);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
.panel-title {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
}
|
||
.panel-actions {
|
||
display: flex;
|
||
gap: 8px;
|
||
align-items: center;
|
||
}
|
||
.batch-actions {
|
||
display: none;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 6px 12px;
|
||
background: var(--primary-weak);
|
||
border-radius: 8px;
|
||
margin-right: 8px;
|
||
}
|
||
.batch-actions.show { display: flex; }
|
||
.batch-actions span {
|
||
font-size: 13px;
|
||
color: var(--primary);
|
||
font-weight: 500;
|
||
}
|
||
.checkbox-wrapper {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 20px;
|
||
height: 20px;
|
||
}
|
||
.checkbox-wrapper input {
|
||
width: 16px;
|
||
height: 16px;
|
||
cursor: pointer;
|
||
}
|
||
.search-box {
|
||
position: relative;
|
||
}
|
||
.search-box input {
|
||
padding: 8px 12px 8px 36px;
|
||
border: 1px solid var(--line);
|
||
border-radius: 8px;
|
||
font-size: 14px;
|
||
width: 240px;
|
||
outline: none;
|
||
}
|
||
.search-box input:focus {
|
||
border-color: var(--primary);
|
||
}
|
||
.search-box svg {
|
||
position: absolute;
|
||
left: 12px;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
color: var(--muted);
|
||
}
|
||
|
||
.article-list {
|
||
max-height: 600px;
|
||
overflow-y: auto;
|
||
}
|
||
.article-item {
|
||
padding: 16px 20px;
|
||
border-bottom: 1px solid var(--line);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
transition: background 0.2s;
|
||
}
|
||
.article-item:hover { background: var(--bg); }
|
||
.article-item:last-child { border-bottom: none; }
|
||
.article-item.dragging {
|
||
opacity: 0.5;
|
||
background: var(--primary-weak);
|
||
}
|
||
.article-item.drag-over {
|
||
border-top: 2px solid var(--primary);
|
||
}
|
||
.drag-handle {
|
||
cursor: grab;
|
||
color: var(--muted);
|
||
padding: 4px;
|
||
opacity: 0;
|
||
transition: opacity 0.2s;
|
||
}
|
||
.article-item:hover .drag-handle { opacity: 1; }
|
||
.drag-handle:active { cursor: grabbing; }
|
||
.article-info { flex: 1; }
|
||
.article-title {
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
margin-bottom: 4px;
|
||
}
|
||
.article-meta {
|
||
font-size: 12px;
|
||
color: var(--muted);
|
||
display: flex;
|
||
gap: 12px;
|
||
}
|
||
.article-actions {
|
||
display: flex;
|
||
gap: 6px;
|
||
opacity: 0;
|
||
transition: opacity 0.2s;
|
||
}
|
||
.article-item:hover .article-actions { opacity: 1; }
|
||
.action-btn {
|
||
width: 32px;
|
||
height: 32px;
|
||
border: 1px solid var(--line);
|
||
border-radius: 6px;
|
||
background: var(--card);
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: var(--muted);
|
||
transition: all 0.2s;
|
||
}
|
||
.action-btn:hover {
|
||
border-color: var(--primary);
|
||
color: var(--primary);
|
||
}
|
||
.action-btn.delete:hover {
|
||
border-color: var(--danger);
|
||
color: var(--danger);
|
||
}
|
||
|
||
.empty-state {
|
||
padding: 60px 20px;
|
||
text-align: center;
|
||
color: var(--muted);
|
||
}
|
||
|
||
/* 模态框 */
|
||
.modal-overlay {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0,0,0,0.5);
|
||
display: none;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 1000;
|
||
}
|
||
.modal-overlay.active { display: flex; }
|
||
.modal {
|
||
background: var(--card);
|
||
border-radius: 16px;
|
||
width: 90%;
|
||
max-width: 700px;
|
||
max-height: 90vh;
|
||
overflow: hidden;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.modal-header {
|
||
padding: 20px 24px;
|
||
border-bottom: 1px solid var(--line);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
.modal-title {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
}
|
||
.modal-close {
|
||
width: 32px;
|
||
height: 32px;
|
||
border: none;
|
||
background: none;
|
||
cursor: pointer;
|
||
border-radius: 8px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: var(--muted);
|
||
}
|
||
.modal-close:hover { background: var(--bg); }
|
||
.modal-body {
|
||
padding: 24px;
|
||
overflow-y: auto;
|
||
flex: 1;
|
||
}
|
||
.modal-footer {
|
||
padding: 16px 24px;
|
||
border-top: 1px solid var(--line);
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 8px;
|
||
}
|
||
|
||
/* 表单 */
|
||
.form-group {
|
||
margin-bottom: 20px;
|
||
}
|
||
.form-label {
|
||
display: block;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
margin-bottom: 8px;
|
||
}
|
||
.form-label span {
|
||
color: var(--danger);
|
||
}
|
||
.form-input {
|
||
width: 100%;
|
||
padding: 10px 14px;
|
||
border: 1px solid var(--line);
|
||
border-radius: 8px;
|
||
font-size: 14px;
|
||
outline: none;
|
||
transition: border-color 0.2s;
|
||
}
|
||
.form-input:focus {
|
||
border-color: var(--primary);
|
||
}
|
||
.form-select {
|
||
width: 100%;
|
||
padding: 10px 14px;
|
||
border: 1px solid var(--line);
|
||
border-radius: 8px;
|
||
font-size: 14px;
|
||
outline: none;
|
||
background: var(--card);
|
||
}
|
||
.form-textarea {
|
||
width: 100%;
|
||
padding: 12px 14px;
|
||
border: 1px solid var(--line);
|
||
border-radius: 8px;
|
||
font-size: 14px;
|
||
outline: none;
|
||
min-height: 200px;
|
||
resize: vertical;
|
||
font-family: inherit;
|
||
line-height: 1.6;
|
||
}
|
||
.form-hint {
|
||
font-size: 12px;
|
||
color: var(--muted);
|
||
margin-top: 6px;
|
||
}
|
||
|
||
/* 图片上传 */
|
||
.image-upload-area {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
margin-bottom: 12px;
|
||
}
|
||
.image-hint {
|
||
font-size: 12px;
|
||
color: var(--muted);
|
||
}
|
||
.image-gallery {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
||
gap: 12px;
|
||
max-height: 300px;
|
||
overflow-y: auto;
|
||
padding: 4px;
|
||
}
|
||
.image-item {
|
||
position: relative;
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
border: 1px solid var(--line);
|
||
background: var(--bg);
|
||
}
|
||
.image-item img {
|
||
width: 100%;
|
||
height: 80px;
|
||
object-fit: cover;
|
||
cursor: pointer;
|
||
}
|
||
.image-item:hover img {
|
||
opacity: 0.8;
|
||
}
|
||
.image-item-actions {
|
||
display: flex;
|
||
gap: 4px;
|
||
padding: 6px;
|
||
background: var(--card);
|
||
border-top: 1px solid var(--line);
|
||
}
|
||
.image-item-actions button {
|
||
flex: 1;
|
||
padding: 4px 8px;
|
||
font-size: 11px;
|
||
border: 1px solid var(--line);
|
||
background: var(--card);
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
.image-item-actions button:hover {
|
||
background: var(--primary-weak);
|
||
border-color: var(--primary);
|
||
color: var(--primary);
|
||
}
|
||
.image-item-actions button.delete:hover {
|
||
background: rgba(239, 68, 68, 0.1);
|
||
border-color: var(--danger);
|
||
color: var(--danger);
|
||
}
|
||
.image-empty {
|
||
grid-column: 1 / -1;
|
||
text-align: center;
|
||
padding: 24px;
|
||
color: var(--muted);
|
||
font-size: 13px;
|
||
}
|
||
|
||
/* 分类管理 */
|
||
.category-manager {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16px;
|
||
}
|
||
.category-add-form {
|
||
display: flex;
|
||
gap: 8px;
|
||
flex-wrap: wrap;
|
||
}
|
||
.category-add-form input {
|
||
padding: 8px 12px;
|
||
border: 1px solid var(--line);
|
||
border-radius: 6px;
|
||
font-size: 13px;
|
||
outline: none;
|
||
}
|
||
.category-add-form input:focus {
|
||
border-color: var(--primary);
|
||
}
|
||
.category-manager-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
max-height: 400px;
|
||
overflow-y: auto;
|
||
}
|
||
.category-manager-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
padding: 12px;
|
||
background: var(--bg);
|
||
border-radius: 8px;
|
||
}
|
||
.category-manager-item.is-default {
|
||
background: var(--primary-weak);
|
||
}
|
||
.category-manager-item .icon {
|
||
width: 36px;
|
||
height: 36px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: var(--card);
|
||
border-radius: 8px;
|
||
font-size: 18px;
|
||
}
|
||
.category-manager-item .info {
|
||
flex: 1;
|
||
}
|
||
.category-manager-item .name {
|
||
font-weight: 500;
|
||
font-size: 14px;
|
||
}
|
||
.category-manager-item .key {
|
||
font-size: 12px;
|
||
color: var(--muted);
|
||
}
|
||
.category-manager-item .actions {
|
||
display: flex;
|
||
gap: 6px;
|
||
}
|
||
.category-manager-item .is-default-badge {
|
||
font-size: 11px;
|
||
padding: 2px 8px;
|
||
background: var(--primary);
|
||
color: #fff;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
/* 历史版本 */
|
||
.history-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
}
|
||
.history-item {
|
||
padding: 16px;
|
||
background: var(--bg);
|
||
border-radius: 8px;
|
||
border: 1px solid var(--line);
|
||
}
|
||
.history-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 12px;
|
||
}
|
||
.history-time {
|
||
font-size: 13px;
|
||
color: var(--muted);
|
||
}
|
||
.history-actions {
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
.history-content {
|
||
font-size: 13px;
|
||
color: var(--text);
|
||
line-height: 1.6;
|
||
max-height: 150px;
|
||
overflow-y: auto;
|
||
white-space: pre-wrap;
|
||
word-break: break-all;
|
||
}
|
||
.history-empty {
|
||
text-align: center;
|
||
padding: 40px;
|
||
color: var(--muted);
|
||
}
|
||
|
||
/* Toast */
|
||
.toast {
|
||
position: fixed;
|
||
bottom: 24px;
|
||
right: 24px;
|
||
background: #1e293b;
|
||
color: #fff;
|
||
padding: 14px 20px;
|
||
border-radius: 10px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
transform: translateY(100px);
|
||
opacity: 0;
|
||
transition: all 0.3s;
|
||
z-index: 2000;
|
||
}
|
||
.toast.show {
|
||
transform: translateY(0);
|
||
opacity: 1;
|
||
}
|
||
.toast.success { background: var(--success); }
|
||
.toast.error { background: var(--danger); }
|
||
|
||
/* 预览链接 */
|
||
.preview-link {
|
||
margin-top: 16px;
|
||
padding: 12px 16px;
|
||
background: var(--bg);
|
||
border-radius: 8px;
|
||
font-size: 13px;
|
||
color: var(--muted);
|
||
}
|
||
.preview-link a {
|
||
color: var(--primary);
|
||
word-break: break-all;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<!-- 导航栏 -->
|
||
<nav class="navbar">
|
||
<div class="navbar-title">
|
||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>
|
||
<h1>文章管理后台</h1>
|
||
<span>静态管理</span>
|
||
</div>
|
||
<div class="navbar-actions">
|
||
<a href="help.html" target="_blank" class="btn btn-secondary">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>
|
||
预览帮助中心
|
||
</a>
|
||
<input type="file" id="importFile" accept=".json" style="display:none" onchange="importData(event)">
|
||
<button class="btn btn-secondary" onclick="document.getElementById('importFile').click()">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
|
||
导入
|
||
</button>
|
||
<button class="btn btn-secondary" onclick="exportData()">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
|
||
导出
|
||
</button>
|
||
<button class="btn btn-primary" onclick="openModal()">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
||
新增文章
|
||
</button>
|
||
</div>
|
||
</nav>
|
||
|
||
<!-- 主内容 -->
|
||
<main class="main">
|
||
<!-- 统计 -->
|
||
<div class="stats">
|
||
<div class="stat-card">
|
||
<div class="stat-label">总文章数</div>
|
||
<div class="stat-value" id="totalCount">0</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-label">自定义文章</div>
|
||
<div class="stat-value" id="customCount">0</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-label">分类数</div>
|
||
<div class="stat-value">6</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-label">最后更新</div>
|
||
<div class="stat-value" id="lastUpdate" style="font-size:16px;">-</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 内容区 -->
|
||
<div class="content">
|
||
<!-- 分类侧边栏 -->
|
||
<div class="sidebar">
|
||
<div class="sidebar-title">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg>
|
||
页面类型
|
||
</div>
|
||
<div class="category-list" id="pageTypeList"></div>
|
||
<div style="height:1px;background:var(--line);margin:16px 0;"></div>
|
||
<div class="sidebar-title">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>
|
||
文章分类
|
||
</div>
|
||
<div class="category-list" id="categoryList"></div>
|
||
<button class="btn btn-secondary" style="width:100%;margin-top:12px;" onclick="openCategoryModal()">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
||
管理分类
|
||
</button>
|
||
</div>
|
||
|
||
<!-- 文章列表 -->
|
||
<div class="articles-panel">
|
||
<div class="panel-header">
|
||
<div class="panel-title" id="panelTitle">全部文章</div>
|
||
<div class="panel-actions">
|
||
<div class="batch-actions" id="batchActions">
|
||
<span id="selectedCount">已选 0 项</span>
|
||
<button class="btn btn-sm btn-danger" onclick="batchDelete()">批量删除</button>
|
||
<button class="btn btn-sm btn-secondary" onclick="clearSelection()">取消选择</button>
|
||
</div>
|
||
<div class="search-box">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>
|
||
<input type="text" id="searchInput" placeholder="搜索文章..." />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="article-list" id="articleList"></div>
|
||
</div>
|
||
</div>
|
||
</main>
|
||
|
||
<!-- 模态框 -->
|
||
<div class="modal-overlay" id="modalOverlay">
|
||
<div class="modal">
|
||
<div class="modal-header">
|
||
<div class="modal-title" id="modalTitle">新增文章</div>
|
||
<button class="modal-close" onclick="closeModal()">
|
||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||
</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<form id="articleForm">
|
||
<input type="hidden" id="articleId" />
|
||
<div class="form-group">
|
||
<label class="form-label">文章标题 <span>*</span></label>
|
||
<input type="text" class="form-input" id="articleTitle" placeholder="输入文章标题" required />
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">页面类型 <span>*</span></label>
|
||
<select class="form-select" id="articlePageType" required onchange="updateCategoryOptions()">
|
||
<option value="">选择页面类型</option>
|
||
<option value="help">帮助中心</option>
|
||
<option value="company">公司介绍</option>
|
||
<option value="partner">合作伙伴</option>
|
||
<option value="legal">法律条款</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">分类 <span>*</span></label>
|
||
<select class="form-select" id="articleCategory" required>
|
||
<option value="">先选择页面类型</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">文章内容 <span>*</span></label>
|
||
<textarea class="form-textarea" id="articleContent" placeholder="支持 HTML 标签,如 <p>、<h3>、<ul>、<li> 等" required></textarea>
|
||
<div class="form-hint">
|
||
示例:<p>段落内容</p><h3>小标题</h3><ul><li>列表项</li></ul>
|
||
</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">图片管理</label>
|
||
<div class="image-upload-area">
|
||
<input type="file" id="imageUpload" accept="image/*" style="display:none" onchange="handleImageUpload(event)">
|
||
<button type="button" class="btn btn-secondary" onclick="document.getElementById('imageUpload').click()">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>
|
||
上传图片
|
||
</button>
|
||
<span class="image-hint">支持 JPG、PNG、GIF,单张不超过 500KB</span>
|
||
</div>
|
||
<div class="image-gallery" id="imageGallery"></div>
|
||
</div>
|
||
<div class="preview-link" id="previewLink" style="display:none;">
|
||
预览链接:<a href="#" target="_blank"></a>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-secondary" id="historyBtn" style="display:none;" onclick="showHistory()">查看历史</button>
|
||
<button class="btn btn-secondary" onclick="closeModal()">取消</button>
|
||
<button class="btn btn-primary" onclick="saveArticle()">保存</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 历史版本模态框 -->
|
||
<div class="modal-overlay" id="historyModalOverlay">
|
||
<div class="modal" style="max-width:800px;">
|
||
<div class="modal-header">
|
||
<div class="modal-title">版本历史</div>
|
||
<button class="modal-close" onclick="closeHistoryModal()">
|
||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||
</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="history-list" id="historyList"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 分类管理模态框 -->
|
||
<div class="modal-overlay" id="categoryModalOverlay">
|
||
<div class="modal" style="max-width:500px;">
|
||
<div class="modal-header">
|
||
<div class="modal-title">分类管理</div>
|
||
<button class="modal-close" onclick="closeCategoryModal()">
|
||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||
</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="category-manager">
|
||
<div class="category-add-form">
|
||
<input type="text" id="newCategoryKey" placeholder="分类ID(英文)" style="width:120px;">
|
||
<input type="text" id="newCategoryName" placeholder="分类名称">
|
||
<input type="text" id="newCategoryIcon" placeholder="图标" style="width:80px;">
|
||
<button class="btn btn-primary btn-sm" onclick="addCategory()">添加</button>
|
||
</div>
|
||
<div class="category-manager-list" id="categoryManagerList"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Toast -->
|
||
<div class="toast" id="toast">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
|
||
<span id="toastMessage">操作成功</span>
|
||
</div>
|
||
|
||
<script>
|
||
// 默认文章ID范围
|
||
const DEFAULT_ID_MIN = 1;
|
||
const DEFAULT_ID_MAX = 200;
|
||
|
||
// 页面类型配置
|
||
const pageTypes = {
|
||
help: { name: '帮助中心', icon: '📚', file: 'help.html' },
|
||
company: { name: '公司介绍', icon: '🏢', file: 'company.html' },
|
||
partner: { name: '合作伙伴', icon: '🤝', file: 'partner.html' },
|
||
legal: { name: '法律条款', icon: '⚖️', file: 'legal.html' }
|
||
};
|
||
|
||
// 分类配置(默认)- 仅用于帮助中心
|
||
const defaultCategories = {
|
||
all: { name: '全部', icon: '📋' },
|
||
install: { name: '安装指导', icon: '🔧' },
|
||
litter: { name: '智能猫厕所', icon: '🐱' },
|
||
fountain: { name: '智能饮水机', icon: '💧' },
|
||
feeder: { name: '智能喂食器', icon: '🍽️' },
|
||
app: { name: 'APP使用', icon: '📱' },
|
||
other: { name: '其他问题', icon: '❓' }
|
||
};
|
||
|
||
// 公司页面分类
|
||
const companyCategories = {
|
||
all: { name: '全部', icon: '📋' },
|
||
about: { name: '关于我们', icon: '🏠' },
|
||
careers: { name: '加入我们', icon: '💼' },
|
||
news: { name: '新闻动态', icon: '📰' },
|
||
brand: { name: '品牌故事', icon: '📖' }
|
||
};
|
||
|
||
// 合作伙伴页面分类
|
||
const partnerCategories = {
|
||
all: { name: '全部', icon: '📋' },
|
||
dealer: { name: '经销商入驻', icon: '🏪' },
|
||
affiliate: { name: '联盟推广', icon: '🔗' },
|
||
business: { name: '企业采购', icon: '🏢' },
|
||
tech: { name: '技术合作', icon: '🔬' }
|
||
};
|
||
|
||
// 法律页面分类
|
||
const legalCategories = {
|
||
all: { name: '全部', icon: '📋' },
|
||
privacy: { name: '隐私政策', icon: '🔒' },
|
||
terms: { name: '服务条款', icon: '📜' },
|
||
cookies: { name: 'Cookie设置', icon: '🍪' }
|
||
};
|
||
|
||
// 合并默认分类和自定义分类
|
||
let categories = { ...defaultCategories };
|
||
const savedCats = localStorage.getItem('chookoo_help_categories');
|
||
if (savedCats) {
|
||
try {
|
||
const customCats = JSON.parse(savedCats);
|
||
categories = { ...defaultCategories, ...customCats };
|
||
} catch (e) {}
|
||
}
|
||
|
||
// 默认文章数据(从 help.html 复制,用于显示)
|
||
const defaultArticles = [
|
||
// 帮助中心文章
|
||
{ id: 101, pageType: 'help', category: 'install', categoryName: '安装指导', title: '智能猫厕所安装指南', content: '<h2>智能猫厕所安装指南</h2><p>欢迎使用Chookoo智能猫厕所!本文档将指导您完成设备的安装和初始设置。</p><h3>包装清单</h3><ul><li>智能猫厕所主机 x1</li><li>电源适配器 x1</li><li>集便桶 x1</li><li>说明书 x1</li></ul><h3>安装步骤</h3><ol><li>选择平坦干燥的地面放置设备</li><li>连接电源适配器</li><li>下载Chookoo APP并注册账号</li><li>按照APP提示完成设备配网</li><li>添加猫砂,引导猫咪使用</li></ol>', views: 2345, date: '2025-01-15', isDefault: true },
|
||
{ id: 102, pageType: 'help', category: 'install', categoryName: '安装指导', title: '智能饮水机安装指南', content: '<h2>智能饮水机安装指南</h2><p>本文档将指导您完成智能饮水机的安装和设置。</p><h3>安装步骤</h3><ol><li>取出设备,拆除包装材料</li><li>清洗水箱和饮水盘</li><li>安装滤芯</li><li>加满清水</li><li>连接电源,开机使用</li></ol>', views: 1876, date: '2025-01-14', isDefault: true },
|
||
{ id: 103, pageType: 'help', category: 'install', categoryName: '安装指导', title: '智能喂食器安装指南', content: '<h2>智能喂食器安装指南</h2><p>本文档将指导您完成智能喂食器的安装设置。</p><h3>安装步骤</h3><ol><li>放置设备于平稳地面</li><li>打开储粮桶,装入粮食</li><li>连接电源</li><li>下载APP完成配网</li><li>设置喂食计划</li></ol>', views: 1654, date: '2025-01-13', isDefault: true },
|
||
{ id: 104, pageType: 'help', category: 'install', categoryName: '安装指导', title: '设备WiFi配网教程', content: '<h2>设备WiFi配网教程</h2><p>所有Chookoo智能设备都支持2.4GHz WiFi连接。</p><h3>配网步骤</h3><ol><li>确保手机连接2.4GHz WiFi</li><li>打开Chookoo APP</li><li>点击"添加设备"</li><li>选择设备类型</li><li>输入WiFi密码</li><li>按提示操作完成配网</li></ol>', views: 2134, date: '2025-01-12', isDefault: true },
|
||
{ id: 105, pageType: 'help', category: 'install', categoryName: '安装指导', title: '设备指示灯状态说明', content: '<h2>设备指示灯状态说明</h2><p>了解设备指示灯含义,快速判断设备状态。</p><h3>指示灯状态</h3><ul><li><strong>蓝灯常亮</strong>:设备正常工作</li><li><strong>蓝灯闪烁</strong>:正在配网</li><li><strong>红灯常亮</strong>:设备故障</li><li><strong>红灯闪烁</strong>:电量低/需要维护</li><li><strong>熄灭</strong>:设备关机</li></ul>', views: 1432, date: '2025-01-11', isDefault: true },
|
||
{ id: 1, pageType: 'help', category: 'litter', categoryName: '智能猫厕所', title: '智能猫厕所支持哪些类型的猫砂?', content: '<h2>支持的猫砂类型</h2><p>Chookoo智能猫厕所支持多种常见猫砂类型:</p><ul><li><strong>豆腐砂</strong>:推荐使用,结团效果好</li><li><strong>膨润土砂</strong>:支持,需选择低粉尘产品</li><li><strong>混合砂</strong>:支持</li><li><strong>水晶砂</strong>:不建议使用</li></ul>', views: 1234, date: '2025-01-15', isDefault: true },
|
||
{ id: 2, pageType: 'help', category: 'litter', categoryName: '智能猫厕所', title: '如何引导猫咪使用智能猫厕所?', content: '<h2>引导猫咪使用智能猫厕所</h2><p>以下技巧帮助您的猫咪快速适应智能猫厕所:</p><ol><li>将设备放在猫咪熟悉的位置</li><li>保留一些旧猫砂的味道</li><li>初期关闭自动清理功能</li><li>用零食奖励使用行为</li><li>保持耐心,一般1-2周可适应</li></ol>', views: 892, date: '2025-01-12', isDefault: true },
|
||
{ id: 3, pageType: 'help', category: 'litter', categoryName: '智能猫厕所', title: '智能猫厕所显示"设备离线"怎么办?', content: '<h2>设备离线解决方案</h2><p>如果设备显示离线,请按以下步骤排查:</p><ol><li>检查电源是否连接正常</li><li>确认WiFi是否正常工作</li><li>重启设备</li><li>重新配网</li><li>联系客服获取帮助</li></ol>', views: 756, date: '2025-01-10', isDefault: true },
|
||
{ id: 4, pageType: 'help', category: 'litter', categoryName: '智能猫厕所', title: '多久需要更换一次集便袋?', content: '<h2>集便袋更换周期</h2><p>集便袋更换频率取决于猫咪数量和使用频率:</p><ul><li><strong>单猫家庭</strong>:约7-10天</li><li><strong>多猫家庭</strong>:约3-5天</li></ul><p>建议在APP中设置更换提醒。</p>', views: 623, date: '2025-01-08', isDefault: true },
|
||
{ id: 5, pageType: 'help', category: 'fountain', categoryName: '智能饮水机', title: '饮水机多久需要更换滤芯?', content: '<h2>滤芯更换周期</h2><p>建议每30天更换一次滤芯,确保水质新鲜。</p><p>APP会自动提醒您更换滤芯。</p>', views: 945, date: '2025-01-14', isDefault: true },
|
||
{ id: 6, pageType: 'help', category: 'fountain', categoryName: '智能饮水机', title: '饮水机水泵噪音大怎么办?', content: '<h2>水泵噪音解决方案</h2><p>如果水泵噪音较大,可能是以下原因:</p><ul><li>水位过低:请及时加水</li><li>水泵有杂质:清洗水泵</li><li>放置不平稳:调整位置</li></ul>', views: 567, date: '2025-01-11', isDefault: true },
|
||
{ id: 7, pageType: 'help', category: 'fountain', categoryName: '智能饮水机', title: '如何清洗饮水机?', content: '<h2>饮水机清洗指南</h2><p>建议每周清洗一次饮水机:</p><ol><li>断开电源</li><li>拆卸所有可拆卸部件</li><li>用清水和软布清洗</li><li>晾干后重新组装</li></ol>', views: 432, date: '2025-01-09', isDefault: true },
|
||
{ id: 8, pageType: 'help', category: 'feeder', categoryName: '智能喂食器', title: '喂食器出粮不准确怎么办?', content: '<h2>出粮不准解决方案</h2><p>如果出粮量不准确,请检查:</p><ul><li>粮食是否受潮结块</li><li>出粮口是否有堵塞</li><li>是否使用了推荐的粮食尺寸</li></ul>', views: 678, date: '2025-01-13', isDefault: true },
|
||
{ id: 9, pageType: 'help', category: 'feeder', categoryName: '智能喂食器', title: '断网后喂食器还能正常工作吗?', content: '<h2>断网工作说明</h2><p>是的,断网后喂食器仍可正常工作:</p><ul><li>本地喂食计划继续执行</li><li>无法远程控制和接收通知</li><li>恢复网络后自动同步</li></ul>', views: 534, date: '2025-01-07', isDefault: true },
|
||
{ id: 10, pageType: 'help', category: 'feeder', categoryName: '智能喂食器', title: '如何设置喂食计划?', content: '<h2>设置喂食计划</h2><p>通过APP设置喂食计划:</p><ol><li>进入设备详情页</li><li>点击"喂食计划"</li><li>添加喂食时间</li><li>设置出粮量</li><li>保存计划</li></ol>', views: 789, date: '2025-01-06', isDefault: true },
|
||
{ id: 11, pageType: 'help', category: 'app', categoryName: 'APP使用', title: '如何注册Chookoo账号?', content: '<h2>注册账号</h2><p>下载Chookoo APP后,按以下步骤注册:</p><ol><li>打开APP,点击"注册"</li><li>输入手机号</li><li>获取并输入验证码</li><li>设置密码</li><li>完成注册</li></ol>', views: 1234, date: '2025-01-15', isDefault: true },
|
||
{ id: 12, pageType: 'help', category: 'app', categoryName: 'APP使用', title: '如何添加设备到APP?', content: '<h2>添加设备</h2><p>注册登录后,添加设备步骤:</p><ol><li>点击首页"+"按钮</li><li>扫描设备二维码或手动选择</li><li>按照向导完成配网</li><li>设备添加成功</li></ol>', views: 876, date: '2025-01-10', isDefault: true },
|
||
{ id: 13, pageType: 'help', category: 'app', categoryName: 'APP使用', title: '如何分享设备给家人?', content: '<h2>分享设备</h2><p>将设备分享给家人:</p><ol><li>进入设备详情页</li><li>点击"设备分享"</li><li>输入家人的Chookoo账号</li><li>对方接受后即可共同管理</li></ol>', views: 654, date: '2025-01-08', isDefault: true },
|
||
{ id: 14, pageType: 'help', category: 'other', categoryName: '其他问题', title: '产品保修政策是什么?', content: '<h2>保修政策</h2><p>Chookoo产品保修政策:</p><ul><li>主机:1年质保</li><li>配件:6个月质保</li><li>人为损坏不在保修范围内</li></ul>', views: 987, date: '2025-01-14', isDefault: true },
|
||
{ id: 15, pageType: 'help', category: 'other', categoryName: '其他问题', title: '如何联系售后服务?', content: '<h2>联系方式</h2><p>您可以通过以下方式联系我们:</p><ul><li>客服电话:400-XXX-XXXX</li><li>工作时间:9:00-21:00</li><li>微信公众号:Chookoo宠小科</li></ul>', views: 876, date: '2025-01-12', isDefault: true },
|
||
{ id: 16, pageType: 'help', category: 'other', categoryName: '其他问题', title: '在哪里可以购买Chookoo产品?', content: '<h2>购买渠道</h2><p>Chookoo产品可通过以下渠道购买:</p><ul><li>天猫旗舰店</li><li>京东旗舰店</li><li>抖音小店</li><li>线下宠物店</li></ul>', views: 765, date: '2025-01-10', isDefault: true },
|
||
|
||
// 公司介绍文章
|
||
{ id: 201, pageType: 'company', category: 'about', categoryName: '关于我们', title: '关于Chookoo宠小科', content: '<h2>关于Chookoo</h2><p>Chookoo宠小科智能科技(苏州)有限公司是一家专注于宠物智能产品研发的高科技企业。</p><p>我们致力于通过科技创新,为宠物主人提供更便捷、更智能的养宠体验。</p>', views: 1234, date: '2025-01-15', isDefault: true },
|
||
{ id: 202, pageType: 'company', category: 'about', categoryName: '关于我们', title: '我们的使命', content: '<h2>企业使命</h2><p>让每一位宠物主人都能享受科技带来的便捷,让每一只宠物都能得到更好的照顾。</p>', views: 876, date: '2025-01-14', isDefault: true },
|
||
{ id: 203, pageType: 'company', category: 'careers', categoryName: '加入我们', title: '热招职位', content: '<h2>热招职位</h2><ul><li>产品经理</li><li>嵌入式工程师</li><li>UI设计师</li><li>运营专员</li></ul><p>简历请投递至:hr@chookoo.com</p>', views: 567, date: '2025-01-13', isDefault: true },
|
||
{ id: 204, pageType: 'company', category: 'news', categoryName: '新闻动态', title: 'Chookoo获得A轮融资', content: '<h2>融资新闻</h2><p>Chookoo宠小科智能科技宣布完成数千万元A轮融资,将用于产品研发和市场拓展。</p>', views: 2345, date: '2025-01-12', isDefault: true },
|
||
{ id: 205, pageType: 'company', category: 'brand', categoryName: '品牌故事', title: '品牌起源', content: '<h2>品牌故事</h2><p>Chookoo创立于2020年,源于创始人对自己宠物的一份热爱。我们相信,科技可以让养宠变得更简单。</p>', views: 789, date: '2025-01-11', isDefault: true },
|
||
|
||
// 合作伙伴文章
|
||
{ id: 301, pageType: 'partner', category: 'dealer', categoryName: '经销商入驻', title: '经销商入驻条件', content: '<h2>入驻条件</h2><ul><li>具有合法经营资质</li><li>有宠物行业经验优先</li><li>具备一定的资金实力</li><li>认同品牌理念</li></ul>', views: 654, date: '2025-01-15', isDefault: true },
|
||
{ id: 302, pageType: 'partner', category: 'dealer', categoryName: '经销商入驻', title: '经销商政策', content: '<h2>合作政策</h2><ul><li>区域独家保护</li><li>市场费用支持</li><li>培训支持</li><li>售后支持</li></ul>', views: 543, date: '2025-01-14', isDefault: true },
|
||
{ id: 303, pageType: 'partner', category: 'affiliate', categoryName: '联盟推广', title: '推广联盟计划', content: '<h2>推广联盟</h2><p>加入Chookoo推广联盟,通过分享获得收益。</p><ul><li>佣金比例:10%-15%</li><li>结算周期:月结</li><li>支持素材丰富</li></ul>', views: 432, date: '2025-01-13', isDefault: true },
|
||
{ id: 304, pageType: 'partner', category: 'business', categoryName: '企业采购', title: '企业采购服务', content: '<h2>企业采购</h2><p>为企业客户提供定制化采购服务:</p><ul><li>批量优惠</li><li>定制包装</li><li>专属客服</li><li>发票支持</li></ul>', views: 321, date: '2025-01-12', isDefault: true },
|
||
{ id: 305, pageType: 'partner', category: 'tech', categoryName: '技术合作', title: '技术合作', content: '<h2>技术合作</h2><p>我们欢迎各类技术合作:</p><ul><li>智能家居平台对接</li><li>Pet IoT生态合作</li><li>技术授权</li></ul>', views: 234, date: '2025-01-11', isDefault: true },
|
||
|
||
// 法律条款文章
|
||
{ id: 401, pageType: 'legal', category: 'privacy', categoryName: '隐私政策', title: '隐私政策', content: '<h2>隐私政策</h2><p>Chookoo重视用户隐私保护。本政策说明我们如何收集、使用和保护您的个人信息。</p><h3>信息收集</h3><p>我们收集的信息包括:账号信息、设备信息、使用数据等。</p><h3>信息使用</h3><p>您的信息用于:提供服务、改进产品、安全保障等。</p>', views: 1234, date: '2025-01-15', isDefault: true },
|
||
{ id: 402, pageType: 'legal', category: 'terms', categoryName: '服务条款', title: '服务条款', content: '<h2>服务条款</h2><p>使用Chookoo产品和服务即表示您同意本条款。</p><h3>服务内容</h3><p>我们提供智能设备、移动应用及相关服务。</p><h3>用户责任</h3><p>请您遵守相关法律法规,合理使用我们的产品和服务。</p>', views: 876, date: '2025-01-14', isDefault: true },
|
||
{ id: 403, pageType: 'legal', category: 'cookies', categoryName: 'Cookie设置', title: 'Cookie使用说明', content: '<h2>Cookie使用说明</h2><p>我们使用Cookie来改善您的使用体验。</p><h3>Cookie类型</h3><ul><li>必要的Cookie:确保网站正常运行</li><li>功能性Cookie:记住您的偏好设置</li><li>分析性Cookie:了解用户如何使用网站</li></ul>', views: 654, date: '2025-01-13', isDefault: true }
|
||
];
|
||
|
||
// 状态
|
||
let currentPageType = 'help';
|
||
let currentCategory = 'all';
|
||
let allArticles = [];
|
||
let editingId = null;
|
||
|
||
// 根据页面类型获取分类
|
||
function getCategoriesByPageType(pageType) {
|
||
switch(pageType) {
|
||
case 'company': return { ...companyCategories };
|
||
case 'partner': return { ...partnerCategories };
|
||
case 'legal': return { ...legalCategories };
|
||
default: return { ...categories };
|
||
}
|
||
}
|
||
|
||
// 初始化
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
loadArticles();
|
||
renderPageTypes();
|
||
renderCategories();
|
||
renderArticles();
|
||
initSearch();
|
||
updateStats();
|
||
});
|
||
|
||
// 加载文章
|
||
function loadArticles() {
|
||
// 获取自定义文章
|
||
const saved = localStorage.getItem('chookoo_help_articles');
|
||
let customArticles = [];
|
||
if (saved) {
|
||
try {
|
||
customArticles = JSON.parse(saved) || [];
|
||
} catch (e) {
|
||
customArticles = [];
|
||
}
|
||
}
|
||
|
||
// 合并默认文章和自定义文章
|
||
allArticles = [...defaultArticles];
|
||
|
||
customArticles.forEach(article => {
|
||
const idx = allArticles.findIndex(a => a.id === article.id);
|
||
if (idx >= 0) {
|
||
allArticles[idx] = { ...article, isDefault: false };
|
||
} else {
|
||
allArticles.push({ ...article, isDefault: false });
|
||
}
|
||
});
|
||
}
|
||
|
||
// 保存自定义文章
|
||
function saveCustomArticles() {
|
||
const customArticles = allArticles
|
||
.filter(a => !a.isDefault)
|
||
.map(a => ({
|
||
id: a.id,
|
||
pageType: a.pageType || 'help',
|
||
category: a.category,
|
||
categoryName: a.categoryName,
|
||
title: a.title,
|
||
content: a.content,
|
||
views: a.views || 0,
|
||
date: a.date
|
||
}));
|
||
localStorage.setItem('chookoo_help_articles', JSON.stringify(customArticles));
|
||
}
|
||
|
||
// 渲染页面类型
|
||
function renderPageTypes() {
|
||
const container = document.getElementById('pageTypeList');
|
||
let html = '';
|
||
|
||
Object.entries(pageTypes).forEach(([key, info]) => {
|
||
const count = allArticles.filter(a => (a.pageType || 'help') === key).length;
|
||
html += `
|
||
<div class="category-item ${currentPageType === key ? 'active' : ''}" onclick="selectPageType('${key}')">
|
||
<div class="category-icon">${info.icon}</div>
|
||
<span class="category-name">${info.name}</span>
|
||
<span class="category-count">${count}</span>
|
||
</div>
|
||
`;
|
||
});
|
||
|
||
container.innerHTML = html;
|
||
}
|
||
|
||
// 选择页面类型
|
||
function selectPageType(pageType) {
|
||
currentPageType = pageType;
|
||
currentCategory = 'all';
|
||
renderPageTypes();
|
||
renderCategories();
|
||
renderArticles();
|
||
document.getElementById('panelTitle').textContent = pageTypes[pageType].name;
|
||
}
|
||
|
||
// 渲染分类
|
||
function renderCategories() {
|
||
const container = document.getElementById('categoryList');
|
||
const cats = getCategoriesByPageType(currentPageType);
|
||
let html = '';
|
||
|
||
Object.entries(cats).forEach(([key, info]) => {
|
||
const count = key === 'all'
|
||
? allArticles.filter(a => (a.pageType || 'help') === currentPageType).length
|
||
: allArticles.filter(a => a.category === key && (a.pageType || 'help') === currentPageType).length;
|
||
|
||
html += `
|
||
<div class="category-item ${currentCategory === key ? 'active' : ''}" onclick="selectCategory('${key}')">
|
||
<div class="category-icon">${info.icon}</div>
|
||
<span class="category-name">${info.name}</span>
|
||
<span class="category-count">${count}</span>
|
||
</div>
|
||
`;
|
||
});
|
||
|
||
container.innerHTML = html;
|
||
}
|
||
|
||
// 选择分类
|
||
function selectCategory(cat) {
|
||
currentCategory = cat;
|
||
const cats = getCategoriesByPageType(currentPageType);
|
||
renderCategories();
|
||
renderArticles();
|
||
document.getElementById('panelTitle').textContent = cats[cat].name;
|
||
}
|
||
|
||
// 渲染文章列表
|
||
function renderArticles() {
|
||
const container = document.getElementById('articleList');
|
||
const searchQuery = document.getElementById('searchInput').value.toLowerCase();
|
||
const cats = getCategoriesByPageType(currentPageType);
|
||
|
||
let filtered = allArticles.filter(a => (a.pageType || 'help') === currentPageType);
|
||
if (currentCategory !== 'all') {
|
||
filtered = filtered.filter(a => a.category === currentCategory);
|
||
}
|
||
if (searchQuery) {
|
||
filtered = filtered.filter(a =>
|
||
a.title.toLowerCase().includes(searchQuery) ||
|
||
a.categoryName.toLowerCase().includes(searchQuery)
|
||
);
|
||
}
|
||
|
||
if (filtered.length === 0) {
|
||
container.innerHTML = '<div class="empty-state">暂无文章</div>';
|
||
return;
|
||
}
|
||
|
||
container.innerHTML = filtered.map(article => `
|
||
<div class="article-item" draggable="true" data-id="${article.id}">
|
||
<div class="checkbox-wrapper">
|
||
<input type="checkbox" class="article-checkbox" data-id="${article.id}" onchange="updateBatchActions()" ${article.isDefault ? 'disabled title="默认文章不可选择"' : ''}>
|
||
</div>
|
||
<div class="drag-handle" title="拖拽排序">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="9" cy="5" r="1"/><circle cx="9" cy="12" r="1"/><circle cx="9" cy="19" r="1"/><circle cx="15" cy="5" r="1"/><circle cx="15" cy="12" r="1"/><circle cx="15" cy="19" r="1"/></svg>
|
||
</div>
|
||
<div class="article-info">
|
||
<div class="article-title">
|
||
${article.isDefault ? '<span style="color:var(--muted);font-size:12px;margin-right:6px;">[默认]</span>' : ''}
|
||
${article.title}
|
||
</div>
|
||
<div class="article-meta">
|
||
<span>${article.categoryName}</span>
|
||
<span>👁️ ${article.views || 0}</span>
|
||
<span>📅 ${article.date}</span>
|
||
</div>
|
||
</div>
|
||
<div class="article-actions">
|
||
<button class="action-btn" onclick="previewArticle(${article.id})" title="预览">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
|
||
</button>
|
||
<button class="action-btn" onclick="copyLink(${article.id})" title="复制链接">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>
|
||
</button>
|
||
<button class="action-btn" onclick="editArticle(${article.id})" title="编辑">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
|
||
</button>
|
||
${!article.isDefault ? `
|
||
<button class="action-btn delete" onclick="deleteArticle(${article.id})" title="删除">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
|
||
</button>
|
||
` : ''}
|
||
</div>
|
||
</div>
|
||
`).join('');
|
||
|
||
initDragAndDrop();
|
||
}
|
||
|
||
// 初始化拖拽排序
|
||
function initDragAndDrop() {
|
||
const items = document.querySelectorAll('.article-item');
|
||
let draggedItem = null;
|
||
|
||
items.forEach(item => {
|
||
item.addEventListener('dragstart', function(e) {
|
||
draggedItem = this;
|
||
this.classList.add('dragging');
|
||
e.dataTransfer.effectAllowed = 'move';
|
||
});
|
||
|
||
item.addEventListener('dragend', function() {
|
||
this.classList.remove('dragging');
|
||
document.querySelectorAll('.article-item').forEach(i => i.classList.remove('drag-over'));
|
||
saveOrder();
|
||
});
|
||
|
||
item.addEventListener('dragover', function(e) {
|
||
e.preventDefault();
|
||
e.dataTransfer.dropEffect = 'move';
|
||
});
|
||
|
||
item.addEventListener('dragenter', function(e) {
|
||
e.preventDefault();
|
||
if (this !== draggedItem) {
|
||
this.classList.add('drag-over');
|
||
}
|
||
});
|
||
|
||
item.addEventListener('dragleave', function() {
|
||
this.classList.remove('drag-over');
|
||
});
|
||
|
||
item.addEventListener('drop', function(e) {
|
||
e.preventDefault();
|
||
if (this !== draggedItem) {
|
||
const container = document.getElementById('articleList');
|
||
const allItems = [...container.querySelectorAll('.article-item')];
|
||
const draggedIdx = allItems.indexOf(draggedItem);
|
||
const targetIdx = allItems.indexOf(this);
|
||
|
||
if (draggedIdx < targetIdx) {
|
||
this.parentNode.insertBefore(draggedItem, this.nextSibling);
|
||
} else {
|
||
this.parentNode.insertBefore(draggedItem, this);
|
||
}
|
||
}
|
||
this.classList.remove('drag-over');
|
||
});
|
||
});
|
||
}
|
||
|
||
// 保存排序
|
||
function saveOrder() {
|
||
const container = document.getElementById('articleList');
|
||
const items = container.querySelectorAll('.article-item');
|
||
const order = [...items].map(item => parseInt(item.dataset.id));
|
||
|
||
localStorage.setItem('chookoo_help_order', JSON.stringify(order));
|
||
showToast('排序已保存', 'success');
|
||
}
|
||
|
||
// 更新批量操作状态
|
||
function updateBatchActions() {
|
||
const checkboxes = document.querySelectorAll('.article-checkbox:checked');
|
||
const count = checkboxes.length;
|
||
const batchActions = document.getElementById('batchActions');
|
||
const selectedCount = document.getElementById('selectedCount');
|
||
|
||
if (count > 0) {
|
||
batchActions.classList.add('show');
|
||
selectedCount.textContent = `已选 ${count} 项`;
|
||
} else {
|
||
batchActions.classList.remove('show');
|
||
}
|
||
}
|
||
|
||
// 清除选择
|
||
function clearSelection() {
|
||
document.querySelectorAll('.article-checkbox').forEach(cb => cb.checked = false);
|
||
updateBatchActions();
|
||
}
|
||
|
||
// 批量删除
|
||
function batchDelete() {
|
||
const checkboxes = document.querySelectorAll('.article-checkbox:checked');
|
||
const ids = [...checkboxes].map(cb => parseInt(cb.dataset.id));
|
||
|
||
if (ids.length === 0) return;
|
||
|
||
if (!confirm(`确定要删除选中的 ${ids.length} 篇文章吗?此操作不可恢复。`)) {
|
||
return;
|
||
}
|
||
|
||
allArticles = allArticles.filter(a => !ids.includes(a.id));
|
||
saveCustomArticles();
|
||
|
||
clearSelection();
|
||
renderCategories();
|
||
renderArticles();
|
||
updateStats();
|
||
showToast(`已删除 ${ids.length} 篇文章`, 'success');
|
||
}
|
||
|
||
// 搜索
|
||
function initSearch() {
|
||
document.getElementById('searchInput').addEventListener('input', renderArticles);
|
||
}
|
||
|
||
// 更新统计
|
||
function updateStats() {
|
||
document.getElementById('totalCount').textContent = allArticles.length;
|
||
document.getElementById('customCount').textContent = allArticles.filter(a => !a.isDefault).length;
|
||
|
||
const lastUpdate = localStorage.getItem('chookoo_help_updated');
|
||
document.getElementById('lastUpdate').textContent = lastUpdate || '-';
|
||
}
|
||
|
||
// 根据页面类型更新分类选项
|
||
function updateCategoryOptions() {
|
||
const pageType = document.getElementById('articlePageType').value;
|
||
const categorySelect = document.getElementById('articleCategory');
|
||
categorySelect.innerHTML = '<option value="">选择分类</option>';
|
||
|
||
let cats = {};
|
||
switch(pageType) {
|
||
case 'help':
|
||
cats = { install: '安装指导', litter: '智能猫厕所', fountain: '智能饮水机', feeder: '智能喂食器', app: 'APP使用', other: '其他问题' };
|
||
break;
|
||
case 'company':
|
||
cats = { about: '关于我们', careers: '加入我们', news: '新闻动态', brand: '品牌故事' };
|
||
break;
|
||
case 'partner':
|
||
cats = { dealer: '经销商入驻', affiliate: '联盟推广', business: '企业采购', tech: '技术合作' };
|
||
break;
|
||
case 'legal':
|
||
cats = { privacy: '隐私政策', terms: '服务条款', cookies: 'Cookie设置' };
|
||
break;
|
||
}
|
||
|
||
Object.entries(cats).forEach(([key, name]) => {
|
||
categorySelect.innerHTML += `<option value="${key}">${name}</option>`;
|
||
});
|
||
}
|
||
|
||
// 打开模态框
|
||
function openModal() {
|
||
editingId = null;
|
||
document.getElementById('modalTitle').textContent = '新增文章';
|
||
document.getElementById('articleForm').reset();
|
||
document.getElementById('previewLink').style.display = 'none';
|
||
document.getElementById('historyBtn').style.display = 'none';
|
||
document.getElementById('modalOverlay').classList.add('active');
|
||
renderImageGallery();
|
||
}
|
||
|
||
// 关闭模态框
|
||
function closeModal() {
|
||
document.getElementById('modalOverlay').classList.remove('active');
|
||
editingId = null;
|
||
}
|
||
|
||
// 编辑文章
|
||
function editArticle(id) {
|
||
const article = allArticles.find(a => a.id === id);
|
||
if (!article) return;
|
||
|
||
editingId = id;
|
||
document.getElementById('modalTitle').textContent = article.isDefault ? '编辑默认文章(覆盖)' : '编辑文章';
|
||
document.getElementById('articleId').value = id;
|
||
document.getElementById('articleTitle').value = article.title;
|
||
|
||
// 设置页面类型并更新分类选项
|
||
const pageType = article.pageType || 'help';
|
||
document.getElementById('articlePageType').value = pageType;
|
||
updateCategoryOptions();
|
||
document.getElementById('articleCategory').value = article.category;
|
||
|
||
document.getElementById('articleContent').value = article.content || '';
|
||
|
||
// 显示预览链接
|
||
const previewLink = document.getElementById('previewLink');
|
||
previewLink.style.display = 'block';
|
||
const pageFile = pageTypes[pageType]?.file || 'help.html';
|
||
previewLink.querySelector('a').href = `${pageFile}?article=${id}`;
|
||
previewLink.querySelector('a').textContent = `${pageFile}?article=${id}`;
|
||
|
||
// 显示历史按钮
|
||
document.getElementById('historyBtn').style.display = 'inline-flex';
|
||
|
||
document.getElementById('modalOverlay').classList.add('active');
|
||
renderImageGallery();
|
||
}
|
||
|
||
// 保存文章
|
||
function saveArticle() {
|
||
const title = document.getElementById('articleTitle').value.trim();
|
||
const pageType = document.getElementById('articlePageType').value;
|
||
const category = document.getElementById('articleCategory').value;
|
||
const content = document.getElementById('articleContent').value.trim();
|
||
|
||
if (!title || !pageType || !category || !content) {
|
||
showToast('请填写所有必填项', 'error');
|
||
return;
|
||
}
|
||
|
||
const cats = getCategoriesByPageType(pageType);
|
||
const categoryName = cats[category]?.name || category;
|
||
|
||
const now = new Date();
|
||
const dateStr = `${now.getFullYear()}-${String(now.getMonth()+1).padStart(2,'0')}-${String(now.getDate()).padStart(2,'0')}`;
|
||
const timeStr = `${String(now.getHours()).padStart(2,'0')}:${String(now.getMinutes()).padStart(2,'0')}`;
|
||
|
||
if (editingId) {
|
||
// 更新现有文章 - 先保存历史版本
|
||
const idx = allArticles.findIndex(a => a.id === editingId);
|
||
if (idx >= 0) {
|
||
const oldArticle = allArticles[idx];
|
||
// 保存历史版本
|
||
saveArticleVersion(editingId, {
|
||
title: oldArticle.title,
|
||
pageType: oldArticle.pageType || 'help',
|
||
category: oldArticle.category,
|
||
categoryName: oldArticle.categoryName,
|
||
content: oldArticle.content,
|
||
savedAt: `${dateStr} ${timeStr}`
|
||
});
|
||
|
||
allArticles[idx] = {
|
||
...allArticles[idx],
|
||
title,
|
||
pageType,
|
||
category,
|
||
categoryName,
|
||
content,
|
||
isDefault: false
|
||
};
|
||
}
|
||
} else {
|
||
// 新增文章
|
||
const newId = Date.now();
|
||
allArticles.push({
|
||
id: newId,
|
||
title,
|
||
pageType,
|
||
category,
|
||
categoryName,
|
||
content,
|
||
views: 0,
|
||
date: dateStr,
|
||
isDefault: false
|
||
});
|
||
}
|
||
|
||
saveCustomArticles();
|
||
localStorage.setItem('chookoo_help_updated', dateStr);
|
||
|
||
closeModal();
|
||
renderCategories();
|
||
renderArticles();
|
||
updateStats();
|
||
showToast(editingId ? '文章已更新' : '文章已添加', 'success');
|
||
}
|
||
|
||
// 删除文章
|
||
function deleteArticle(id) {
|
||
if (!confirm('确定要删除这篇文章吗?')) return;
|
||
|
||
allArticles = allArticles.filter(a => a.id !== id);
|
||
saveCustomArticles();
|
||
renderCategories();
|
||
renderArticles();
|
||
updateStats();
|
||
showToast('文章已删除', 'success');
|
||
}
|
||
|
||
// 预览文章
|
||
function previewArticle(id) {
|
||
const article = allArticles.find(a => a.id === id);
|
||
if (!article) return;
|
||
|
||
const pageType = article.pageType || 'help';
|
||
const targetFile = pageTypes[pageType]?.file || 'help.html';
|
||
window.open(`${targetFile}?article=${id}`, '_blank');
|
||
}
|
||
|
||
// 复制链接
|
||
function copyLink(id) {
|
||
const article = allArticles.find(a => a.id === id);
|
||
if (!article) return;
|
||
|
||
const pageType = article.pageType || 'help';
|
||
const targetFile = pageTypes[pageType]?.file || 'help.html';
|
||
const url = `${window.location.origin}${window.location.pathname.replace('admin.html', '')}${targetFile}?article=${id}`;
|
||
navigator.clipboard.writeText(url).then(() => {
|
||
showToast('链接已复制', 'success');
|
||
});
|
||
}
|
||
|
||
// 导出数据
|
||
function exportData() {
|
||
const customArticles = allArticles.filter(a => !a.isDefault);
|
||
const data = {
|
||
exportDate: new Date().toISOString(),
|
||
articles: customArticles.map(a => ({
|
||
id: a.id,
|
||
category: a.category,
|
||
categoryName: a.categoryName,
|
||
title: a.title,
|
||
content: a.content,
|
||
views: a.views,
|
||
date: a.date
|
||
}))
|
||
};
|
||
|
||
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
|
||
const url = URL.createObjectURL(blob);
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = `chookoo-help-${Date.now()}.json`;
|
||
a.click();
|
||
URL.revokeObjectURL(url);
|
||
showToast('数据已导出', 'success');
|
||
}
|
||
|
||
// 导入数据
|
||
function importData(event) {
|
||
const file = event.target.files[0];
|
||
if (!file) return;
|
||
|
||
const reader = new FileReader();
|
||
reader.onload = function(e) {
|
||
try {
|
||
const data = JSON.parse(e.target.result);
|
||
|
||
if (!data.articles || !Array.isArray(data.articles)) {
|
||
showToast('无效的文件格式', 'error');
|
||
return;
|
||
}
|
||
|
||
if (!confirm(`确定要导入 ${data.articles.length} 篇文章吗?\n\n注意:这将覆盖现有的自定义文章。`)) {
|
||
return;
|
||
}
|
||
|
||
// 保存导入的文章
|
||
localStorage.setItem('chookoo_help_articles', JSON.stringify(data.articles));
|
||
localStorage.setItem('chookoo_help_updated', new Date().toISOString().split('T')[0]);
|
||
|
||
// 重新加载数据
|
||
loadArticles();
|
||
renderCategories();
|
||
renderArticles();
|
||
updateStats();
|
||
|
||
showToast(`成功导入 ${data.articles.length} 篇文章`, 'success');
|
||
} catch (err) {
|
||
showToast('文件解析失败,请检查格式', 'error');
|
||
}
|
||
};
|
||
reader.readAsText(file);
|
||
|
||
// 清空文件输入,允许重复导入同一文件
|
||
event.target.value = '';
|
||
}
|
||
|
||
// ========== 图片管理功能 ==========
|
||
|
||
// 获取图片库
|
||
function getImageLibrary() {
|
||
const saved = localStorage.getItem('chookoo_help_images');
|
||
return saved ? JSON.parse(saved) : [];
|
||
}
|
||
|
||
// 保存图片库
|
||
function saveImageLibrary(images) {
|
||
localStorage.setItem('chookoo_help_images', JSON.stringify(images));
|
||
}
|
||
|
||
// 处理图片上传
|
||
function handleImageUpload(event) {
|
||
const file = event.target.files[0];
|
||
if (!file) return;
|
||
|
||
// 检查文件大小 (500KB)
|
||
if (file.size > 500 * 1024) {
|
||
showToast('图片大小不能超过 500KB', 'error');
|
||
event.target.value = '';
|
||
return;
|
||
}
|
||
|
||
// 检查文件类型
|
||
if (!file.type.startsWith('image/')) {
|
||
showToast('请选择图片文件', 'error');
|
||
event.target.value = '';
|
||
return;
|
||
}
|
||
|
||
const reader = new FileReader();
|
||
reader.onload = function(e) {
|
||
const images = getImageLibrary();
|
||
const imageData = {
|
||
id: Date.now(),
|
||
name: file.name,
|
||
data: e.target.result,
|
||
createdAt: new Date().toISOString()
|
||
};
|
||
images.unshift(imageData);
|
||
saveImageLibrary(images);
|
||
renderImageGallery();
|
||
showToast('图片上传成功', 'success');
|
||
};
|
||
reader.readAsDataURL(file);
|
||
event.target.value = '';
|
||
}
|
||
|
||
// 渲染图片库
|
||
function renderImageGallery() {
|
||
const gallery = document.getElementById('imageGallery');
|
||
const images = getImageLibrary();
|
||
|
||
if (images.length === 0) {
|
||
gallery.innerHTML = '<div class="image-empty">暂无图片,点击上方按钮上传</div>';
|
||
return;
|
||
}
|
||
|
||
gallery.innerHTML = images.map(img => `
|
||
<div class="image-item" data-id="${img.id}">
|
||
<img src="${img.data}" alt="${img.name}" onclick="previewImage('${img.id}')">
|
||
<div class="image-item-actions">
|
||
<button onclick="copyImageCode('${img.id}')" title="复制引用代码">复制</button>
|
||
<button onclick="insertImageToContent('${img.id}')" title="插入到文章">插入</button>
|
||
<button class="delete" onclick="deleteImage('${img.id}')" title="删除">删除</button>
|
||
</div>
|
||
</div>
|
||
`).join('');
|
||
}
|
||
|
||
// 预览图片
|
||
function previewImage(id) {
|
||
const images = getImageLibrary();
|
||
const img = images.find(i => i.id == id);
|
||
if (img) {
|
||
window.open(img.data, '_blank');
|
||
}
|
||
}
|
||
|
||
// 复制图片引用代码
|
||
function copyImageCode(id) {
|
||
const images = getImageLibrary();
|
||
const img = images.find(i => i.id == id);
|
||
if (img) {
|
||
const code = `<img src="${img.data}" alt="图片">`;
|
||
navigator.clipboard.writeText(code).then(() => {
|
||
showToast('图片代码已复制', 'success');
|
||
});
|
||
}
|
||
}
|
||
|
||
// 插入图片到文章内容
|
||
function insertImageToContent(id) {
|
||
const images = getImageLibrary();
|
||
const img = images.find(i => i.id == id);
|
||
if (img) {
|
||
const textarea = document.getElementById('articleContent');
|
||
const code = `\n<img src="${img.data}" alt="图片" style="max-width:100%;border-radius:8px;margin:16px 0;">\n`;
|
||
const start = textarea.selectionStart;
|
||
const end = textarea.selectionEnd;
|
||
textarea.value = textarea.value.substring(0, start) + code + textarea.value.substring(end);
|
||
textarea.selectionStart = textarea.selectionEnd = start + code.length;
|
||
textarea.focus();
|
||
showToast('图片已插入', 'success');
|
||
}
|
||
}
|
||
|
||
// 删除图片
|
||
function deleteImage(id) {
|
||
if (!confirm('确定要删除这张图片吗?')) return;
|
||
|
||
let images = getImageLibrary();
|
||
images = images.filter(i => i.id != id);
|
||
saveImageLibrary(images);
|
||
renderImageGallery();
|
||
showToast('图片已删除', 'success');
|
||
}
|
||
|
||
// ========== 分类管理功能 ==========
|
||
|
||
// 获取所有分类(默认+自定义)
|
||
function getAllCategories() {
|
||
const customCats = getCustomCategories();
|
||
return { ...defaultCategories, ...customCats };
|
||
}
|
||
|
||
// 获取自定义分类
|
||
function getCustomCategories() {
|
||
const saved = localStorage.getItem('chookoo_help_categories');
|
||
return saved ? JSON.parse(saved) : {};
|
||
}
|
||
|
||
// 保存自定义分类
|
||
function saveCustomCategories(cats) {
|
||
localStorage.setItem('chookoo_help_categories', JSON.stringify(cats));
|
||
}
|
||
|
||
// 打开分类管理模态框
|
||
function openCategoryModal() {
|
||
document.getElementById('categoryModalOverlay').classList.add('active');
|
||
renderCategoryManagerList();
|
||
}
|
||
|
||
// 关闭分类管理模态框
|
||
function closeCategoryModal() {
|
||
document.getElementById('categoryModalOverlay').classList.remove('active');
|
||
}
|
||
|
||
// 渲染分类管理列表
|
||
function renderCategoryManagerList() {
|
||
const container = document.getElementById('categoryManagerList');
|
||
const allCats = getAllCategories();
|
||
|
||
container.innerHTML = Object.entries(allCats).map(([key, info]) => `
|
||
<div class="category-manager-item ${info.isDefault ? 'is-default' : ''}">
|
||
<div class="icon">${info.icon}</div>
|
||
<div class="info">
|
||
<div class="name">${info.name}</div>
|
||
<div class="key">${key}</div>
|
||
</div>
|
||
${info.isDefault
|
||
? '<span class="is-default-badge">默认</span>'
|
||
: `<div class="actions">
|
||
<button class="btn btn-sm btn-secondary" onclick="editCategory('${key}')">编辑</button>
|
||
<button class="btn btn-sm btn-danger" onclick="deleteCategory('${key}')">删除</button>
|
||
</div>`
|
||
}
|
||
</div>
|
||
`).join('');
|
||
}
|
||
|
||
// 添加分类
|
||
function addCategory() {
|
||
const keyInput = document.getElementById('newCategoryKey');
|
||
const nameInput = document.getElementById('newCategoryName');
|
||
const iconInput = document.getElementById('newCategoryIcon');
|
||
|
||
const key = keyInput.value.trim().toLowerCase();
|
||
const name = nameInput.value.trim();
|
||
const icon = iconInput.value.trim() || '📁';
|
||
|
||
if (!key || !name) {
|
||
showToast('请填写分类ID和名称', 'error');
|
||
return;
|
||
}
|
||
|
||
if (!/^[a-z][a-z0-9_]*$/.test(key)) {
|
||
showToast('分类ID只能包含小写字母、数字和下划线,且以字母开头', 'error');
|
||
return;
|
||
}
|
||
|
||
const allCats = getAllCategories();
|
||
if (allCats[key]) {
|
||
showToast('该分类ID已存在', 'error');
|
||
return;
|
||
}
|
||
|
||
const customCats = getCustomCategories();
|
||
customCats[key] = { name, icon, isDefault: false };
|
||
saveCustomCategories(customCats);
|
||
|
||
// 更新全局 categories 变量
|
||
categories[key] = { name, icon };
|
||
|
||
keyInput.value = '';
|
||
nameInput.value = '';
|
||
iconInput.value = '';
|
||
|
||
renderCategoryManagerList();
|
||
renderCategories();
|
||
showToast('分类添加成功', 'success');
|
||
}
|
||
|
||
// 编辑分类
|
||
function editCategory(key) {
|
||
const customCats = getCustomCategories();
|
||
const cat = customCats[key];
|
||
if (!cat) return;
|
||
|
||
const name = prompt('请输入新的分类名称:', cat.name);
|
||
if (!name) return;
|
||
|
||
const icon = prompt('请输入新的图标(emoji):', cat.icon);
|
||
if (!icon) return;
|
||
|
||
customCats[key] = { ...cat, name, icon };
|
||
saveCustomCategories(customCats);
|
||
|
||
// 更新全局 categories 变量
|
||
categories[key] = { name, icon };
|
||
|
||
renderCategoryManagerList();
|
||
renderCategories();
|
||
showToast('分类已更新', 'success');
|
||
}
|
||
|
||
// 删除分类
|
||
function deleteCategory(key) {
|
||
// 检查是否有文章使用该分类
|
||
const articlesInCat = allArticles.filter(a => a.category === key);
|
||
if (articlesInCat.length > 0) {
|
||
showToast(`该分类下有 ${articlesInCat.length} 篇文章,请先移动或删除这些文章`, 'error');
|
||
return;
|
||
}
|
||
|
||
if (!confirm(`确定要删除分类「${categories[key]?.name || key}」吗?`)) return;
|
||
|
||
const customCats = getCustomCategories();
|
||
delete customCats[key];
|
||
saveCustomCategories(customCats);
|
||
|
||
// 更新全局 categories 变量
|
||
delete categories[key];
|
||
|
||
renderCategoryManagerList();
|
||
renderCategories();
|
||
showToast('分类已删除', 'success');
|
||
}
|
||
|
||
// ========== 版本历史功能 ==========
|
||
|
||
// 获取文章的版本历史
|
||
function getArticleVersions(articleId) {
|
||
const saved = localStorage.getItem(`chookoo_help_versions_${articleId}`);
|
||
return saved ? JSON.parse(saved) : [];
|
||
}
|
||
|
||
// 保存文章版本
|
||
function saveArticleVersion(articleId, version) {
|
||
const versions = getArticleVersions(articleId);
|
||
versions.unshift(version);
|
||
// 最多保存10个版本
|
||
if (versions.length > 10) {
|
||
versions.pop();
|
||
}
|
||
localStorage.setItem(`chookoo_help_versions_${articleId}`, JSON.stringify(versions));
|
||
}
|
||
|
||
// 显示历史版本
|
||
function showHistory() {
|
||
if (!editingId) return;
|
||
document.getElementById('historyModalOverlay').classList.add('active');
|
||
renderHistoryList();
|
||
}
|
||
|
||
// 关闭历史版本模态框
|
||
function closeHistoryModal() {
|
||
document.getElementById('historyModalOverlay').classList.remove('active');
|
||
}
|
||
|
||
// 渲染历史版本列表
|
||
function renderHistoryList() {
|
||
const container = document.getElementById('historyList');
|
||
const versions = getArticleVersions(editingId);
|
||
|
||
if (versions.length === 0) {
|
||
container.innerHTML = '<div class="history-empty">暂无历史版本</div>';
|
||
return;
|
||
}
|
||
|
||
container.innerHTML = versions.map((v, i) => `
|
||
<div class="history-item">
|
||
<div class="history-header">
|
||
<span class="history-time">📅 ${v.savedAt}</span>
|
||
<div class="history-actions">
|
||
<button class="btn btn-sm btn-secondary" onclick="previewVersion(${i})">预览</button>
|
||
<button class="btn btn-sm btn-primary" onclick="restoreVersion(${i})">恢复</button>
|
||
</div>
|
||
</div>
|
||
<div class="history-content">${escapeHtml(v.content?.substring(0, 500) || '')}${v.content?.length > 500 ? '...' : ''}</div>
|
||
</div>
|
||
`).join('');
|
||
}
|
||
|
||
// 预览版本
|
||
function previewVersion(index) {
|
||
const versions = getArticleVersions(editingId);
|
||
const version = versions[index];
|
||
if (version) {
|
||
alert(`标题:${version.title}\n\n内容:\n${version.content}`);
|
||
}
|
||
}
|
||
|
||
// 恢复版本
|
||
function restoreVersion(index) {
|
||
const versions = getArticleVersions(editingId);
|
||
const version = versions[index];
|
||
if (version) {
|
||
if (!confirm('确定要恢复到这个版本吗?当前内容将被覆盖。')) return;
|
||
|
||
document.getElementById('articleTitle').value = version.title;
|
||
document.getElementById('articleCategory').value = version.category;
|
||
document.getElementById('articleContent').value = version.content || '';
|
||
|
||
closeHistoryModal();
|
||
showToast('已恢复到历史版本', 'success');
|
||
}
|
||
}
|
||
|
||
// HTML 转义
|
||
function escapeHtml(text) {
|
||
const div = document.createElement('div');
|
||
div.textContent = text;
|
||
return div.innerHTML;
|
||
}
|
||
|
||
// Toast
|
||
function showToast(message, type = 'success') {
|
||
const toast = document.getElementById('toast');
|
||
toast.className = `toast ${type}`;
|
||
document.getElementById('toastMessage').textContent = message;
|
||
toast.classList.add('show');
|
||
setTimeout(() => toast.classList.remove('show'), 3000);
|
||
}
|
||
|
||
// 点击模态框外部关闭
|
||
document.getElementById('modalOverlay').addEventListener('click', function(e) {
|
||
if (e.target === this) closeModal();
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|