Files
2026-04-27 17:16:17 +08:00

1904 lines
76 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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 标签,如 &lt;p&gt;、&lt;h3&gt;、&lt;ul&gt;、&lt;li&gt; 等" required></textarea>
<div class="form-hint">
示例:&lt;p&gt;段落内容&lt;/p&gt;&lt;h3&gt;小标题&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;列表项&lt;/li&gt;&lt;/ul&gt;
</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>