Files
Memo/manage-panel.php
2026-02-23 17:31:07 +08:00

2468 lines
82 KiB
PHP
Raw 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.
<?php
if (!defined('__TYPECHO_ROOT_DIR__')) exit;
/**
* 知识插件后台管理面板
*/
// 获取配置
$options = Typecho_Widget::widget('Widget_Options');
$config = $options->plugin('Memo');
// 引入Action类
require_once __DIR__ . '/Action.php';
$action = new Memo_Action();
// 获取搜索和筛选参数
$search = isset($_GET['search']) ? trim($_GET['search']) : '';
$category = isset($_GET['category']) ? trim($_GET['category']) : '';
$page = isset($_GET['page']) ? intval($_GET['page']) : 1;
$perPage = isset($config->perPage) ? intval($config->perPage) : 10;
// 获取所有分类
$allCategories = $action->getAllCategories();
$categoryCounts = $action->getCategoryCounts();
// 获取统计信息
$statistics = $action->getStatistics();
// 获取默认分类设置
$defaultCategories = isset($config->defaultCategories) ? explode("\n", $config->defaultCategories) : array('默认');
$defaultCategories = array_map('trim', $defaultCategories);
$defaultCategories = array_filter($defaultCategories);
// 处理表单提交
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// 处理新增
if (!empty($_POST['content'])) {
try {
$action->addMemo(array(
'content' => $_POST['content'],
'category' => isset($_POST['category']) ? $_POST['category'] : '默认',
'event_date' => isset($_POST['event_date']) && !empty($_POST['event_date']) ? $_POST['event_date'] : null,
'post_cids' => isset($_POST['post_cids']) ? $_POST['post_cids'] : '',
'original_url' => isset($_POST['original_url']) ? $_POST['original_url'] : '' // 新增:原文链接
));
$successMsg = '知识添加成功!';
// 重定向到当前页面(保持筛选状态)
$redirectUrl = $_SERVER['PHP_SELF'] . '?panel=Memo/manage-panel.php&page=' . $page;
if ($search) $redirectUrl .= '&search=' . urlencode($search);
if ($category) $redirectUrl .= '&category=' . urlencode($category);
$redirectUrl .= '#memo-list-anchor';
header('Location: ' . $redirectUrl);
exit;
} catch (Exception $e) {
$errorMsg = '添加失败: ' . htmlspecialchars($e->getMessage());
}
}
// 处理批量删除 - 修复:独立的批量删除处理
if (isset($_POST['bulk_delete']) && !empty($_POST['delete_ids']) && is_array($_POST['delete_ids'])) {
$action->deleteMemos($_POST['delete_ids']);
$successMsg = '删除成功!';
// 重定向到当前页面(保持筛选状态)
$redirectUrl = $_SERVER['PHP_SELF'] . '?panel=Memo/manage-panel.php&page=' . $page;
if ($search) $redirectUrl .= '&search=' . urlencode($search);
if ($category) $redirectUrl .= '&category=' . urlencode($category);
$redirectUrl .= '#memo-list-anchor';
header('Location: ' . $redirectUrl);
exit;
}
// 处理批量导出TXT - 修复:正确处理导出数据
if (isset($_POST['export_selected_txt']) && !empty($_POST['export_ids'])) {
try {
// 将逗号分隔的字符串转换为数组
$exportIds = explode(',', $_POST['export_ids']);
$exportIds = array_map('intval', $exportIds);
$exportIds = array_filter($exportIds);
if (empty($exportIds)) {
throw new Exception('没有选择有效的记录');
}
$exportContent = $action->exportSelectedData($exportIds, 'txt');
$filename = 'memo_selected_' . date('Ymd_His') . '.txt';
header('Content-Type: text/plain');
header('Content-Disposition: attachment; filename="' . $filename . '"');
header('Content-Length: ' . strlen($exportContent));
echo $exportContent;
exit;
} catch (Exception $e) {
$errorMsg = '导出失败: ' . htmlspecialchars($e->getMessage());
}
}
// 处理批量导出MD - 修复:正确处理导出数据
if (isset($_POST['export_selected_md']) && !empty($_POST['export_ids'])) {
try {
// 将逗号分隔的字符串转换为数组
$exportIds = explode(',', $_POST['export_ids']);
$exportIds = array_map('intval', $exportIds);
$exportIds = array_filter($exportIds);
if (empty($exportIds)) {
throw new Exception('没有选择有效的记录');
}
$exportContent = $action->exportSelectedData($exportIds, 'md');
$filename = 'memo_selected_' . date('Ymd_His') . '.md';
header('Content-Type: text/markdown');
header('Content-Disposition: attachment; filename="' . $filename . '"');
header('Content-Length: ' . strlen($exportContent));
echo $exportContent;
exit;
} catch (Exception $e) {
$errorMsg = '导出失败: ' . htmlspecialchars($e->getMessage());
}
}
// 处理单个导出
if (isset($_POST['export_single_txt']) && !empty($_POST['single_id'])) {
try {
$exportContent = $action->exportSelectedData(array(intval($_POST['single_id'])), 'txt');
$filename = 'memo_' . $_POST['single_id'] . '_' . date('Ymd_His') . '.txt';
header('Content-Type: text/plain');
header('Content-Disposition: attachment; filename="' . $filename . '"');
header('Content-Length: ' . strlen($exportContent));
echo $exportContent;
exit;
} catch (Exception $e) {
$errorMsg = '导出失败: ' . htmlspecialchars($e->getMessage());
}
}
if (isset($_POST['export_single_md']) && !empty($_POST['single_id'])) {
try {
$exportContent = $action->exportSelectedData(array(intval($_POST['single_id'])), 'md');
$filename = 'memo_' . $_POST['single_id'] . '_' . date('Ymd_His') . '.md';
header('Content-Type: text/markdown');
header('Content-Disposition: attachment; filename="' . $filename . '"');
header('Content-Length: ' . strlen($exportContent));
echo $exportContent;
exit;
} catch (Exception $e) {
$errorMsg = '导出失败: ' . htmlspecialchars($e->getMessage());
}
}
// 处理编辑
if (!empty($_POST['edit_id'])) {
$action->updateMemo(array(
'edit_id' => $_POST['edit_id'],
'edit_content' => $_POST['edit_content'],
'edit_category' => isset($_POST['edit_category']) ? $_POST['edit_category'] : '默认',
'edit_event_date' => isset($_POST['edit_event_date']) && !empty($_POST['edit_event_date']) ? $_POST['edit_event_date'] : null,
'edit_post_cids' => isset($_POST['edit_post_cids']) ? $_POST['edit_post_cids'] : '',
'edit_original_url' => isset($_POST['edit_original_url']) ? $_POST['edit_original_url'] : ''
));
$successMsg = '知识更新成功!';
// 重定向到当前页面(保持筛选状态)
$redirectUrl = $_SERVER['PHP_SELF'] . '?panel=Memo/manage-panel.php&page=' . $page;
if ($search) $redirectUrl .= '&search=' . urlencode($search);
if ($category) $redirectUrl .= '&category=' . urlencode($category);
$redirectUrl .= '#memo-list-anchor';
header('Location: ' . $redirectUrl);
exit;
}
// 处理全量导出
if (isset($_POST['export'])) {
$exportContent = $action->exportData();
$filename = 'memo_' . date('Ymd_His') . '.txt';
header('Content-Type: text/plain');
header('Content-Disposition: attachment; filename="' . $filename . '"');
header('Content-Length: ' . strlen($exportContent));
echo $exportContent;
exit;
}
// 处理全量导出为MD
if (isset($_POST['export_md'])) {
$exportContent = $action->exportMdData();
$filename = 'memo_' . date('Ymd_His') . '.md';
header('Content-Type: text/markdown');
header('Content-Disposition: attachment; filename="' . $filename . '"');
header('Content-Length: ' . strlen($exportContent));
echo $exportContent;
exit;
}
// 处理导入
if (isset($_POST['import']) && !empty($_FILES['import_file']['tmp_name'])) {
$fileContent = file_get_contents($_FILES['import_file']['tmp_name']);
$fileExtension = strtolower(pathinfo($_FILES['import_file']['name'], PATHINFO_EXTENSION));
if ($fileExtension == 'sql') {
// 导入SQL文件
$importCategory = isset($_POST['import_category']) ? $_POST['import_category'] : '文章导入';
$result = $action->importSqlData($fileContent, $importCategory);
} else {
// 导入文本文件
$result = $action->importData($fileContent);
}
if ($result['imported'] > 0) {
$successMsg = '导入成功!成功导入 ' . $result['imported'] . ' 条记录';
if ($result['failed'] > 0) {
$successMsg .= ',失败 ' . $result['failed'] . ' 条记录';
if (!empty($result['fail_reasons'])) {
$errorDetails = '<strong>失败原因:</strong><br>';
$displayCount = min(10, count($result['fail_reasons']));
for ($i = 0; $i < $displayCount; $i++) {
$errorDetails .= htmlspecialchars($result['fail_reasons'][$i]) . '<br>';
}
if (count($result['fail_reasons']) > 10) {
$errorDetails .= '...还有 ' . (count($result['fail_reasons']) - 10) . ' 条失败记录';
}
$errorMsg = $errorDetails;
}
}
} else {
$errorMsg = '导入失败!未找到可导入的记录或格式不正确';
if (!empty($result['fail_reasons'])) {
$errorMsg .= '<br><strong>失败原因:</strong><br>';
$displayCount = min(10, count($result['fail_reasons']));
for ($i = 0; $i < $displayCount; $i++) {
$errorMsg .= htmlspecialchars($result['fail_reasons'][$i]) . '<br>';
}
}
}
}
}
// 获取知识数据
$memos = $action->getMemos($page, $perPage, $search, $category);
$total = $action->getTotalCount($search, $category);
$totalPages = ceil($total / $perPage);
// 为每条记录获取关联的文章信息
foreach ($memos as &$memo) {
$memo['post_info'] = array();
if (!empty($memo['post_cids'])) {
$cids = array_filter(array_map('trim', explode(',', $memo['post_cids'])));
foreach ($cids as $cid) {
if (is_numeric($cid)) {
$post = $action->getPostByCid($cid);
if ($post) {
$memo['post_info'][$cid] = $post;
}
}
}
}
}
unset($memo);
// 辅助函数将URL转换为链接精确匹配
function convertUrlsToLinks($text) {
if (empty($text)) {
return $text;
}
// 先进行HTML实体编码
$encodedText = htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
// 精确匹配URL的正则表达式
// 只匹配以http://或https://开头的完整URL
$urlPattern = '/(https?:\/\/[a-zA-Z0-9][-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b[-a-zA-Z0-9()@:%_\+.~#?&\/=]*)/';
// 使用preg_replace_callback进行精确处理
return preg_replace_callback($urlPattern, function($matches) {
$url = $matches[1];
// 清理URL末尾可能错误的标点符号
$punctuation = array('.', ',', ';', ':', '!', '?', ')', ']', '}');
$lastChar = substr($url, -1);
if (in_array($lastChar, $punctuation)) {
$url = substr($url, 0, -1);
$suffix = $lastChar;
} else {
$suffix = '';
}
// 确保URL格式正确
if (!preg_match('/^https?:\/\//', $url)) {
return $matches[0]; // 如果不是合法URL返回原文本
}
// 创建链接
return '<a href="' . htmlspecialchars($url) . '" class="url-link" target="_blank" rel="noopener noreferrer">' . htmlspecialchars($url) . '</a>' . $suffix;
}, $encodedText);
}
?>
<?php include 'header.php'; ?>
<?php include 'menu.php'; ?>
<style>
/* 分类横向列表样式 - 修改为自动换行 */
.categories-horizontal-list {
display: flex;
flex-wrap: wrap;
gap: 8px;
padding-bottom: 5px;
align-items: center;
max-height: 100px; /* 限制最大高度,超出可滚动 */
overflow-y: auto;
}
.category-tag {
display: inline-block;
padding: 4px 10px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
background: #e8f5e8;
color: #2e7d32;
border: 1px solid #c8e6c9;
white-space: nowrap;
text-decoration: none;
transition: all 0.2s;
cursor: pointer;
flex-shrink: 0; /* 防止标签被压缩 */
}
.category-tag:hover {
background: #d5ecd5;
border-color: #a8d5a9;
text-decoration: none;
color: #2e7d32;
}
/* 确保统计卡片内容不换行 */
.stats-card h3 {
margin-top: 0;
margin-bottom: 15px;
color: #333;
font-size: 16px;
font-weight: 600;
border-bottom: 1px solid #f0f0f0;
padding-bottom: 10px;
}
.stats-card .categories-horizontal-list {
margin-top: 8px;
min-height: 32px;
}
/* 基础样式 */
.memo-panel {
padding: 40px 0;
margin-left: 150px;
margin-top:50px;
}
.panel-container {
max-width: 1200px;
margin: 0 auto;
padding: 0 30px;
}
/* 浮动消息 */
.floating-message {
position: fixed;
top: 20px;
right: 20px;
z-index: 9999;
max-width: 350px;
opacity: 0;
transform: translateY(-20px);
transition: opacity 0.3s, transform 0.3s;
padding: 14px 20px;
border-radius: 6px;
border: 1px solid transparent;
font-size: 14px;
}
.floating-message.show {
opacity: 1;
transform: translateY(0);
}
.floating-message.success {
color: #0f5132;
background-color: #d1e7dd;
border-color: #badbcc;
}
.floating-message.error {
color: #842029;
background-color: #f8d7da;
border-color: #f5c2c7;
}
/* 统计卡片样式 */
.stats-section {
margin-bottom: 20px;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.stats-card {
background: white;
border-radius: 8px;
padding: 20px;
border: 1px solid #e0e0e0;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
.stats-card h3 {
margin-top: 0;
margin-bottom: 15px;
color: #333;
font-size: 16px;
font-weight: 600;
border-bottom: 1px solid #f0f0f0;
padding-bottom: 10px;
}
.stats-number {
font-size: 24px;
font-weight: bold;
color: #007bff;
margin-bottom: 5px;
}
.stats-label {
font-size: 13px;
color: #666;
margin-bottom: 10px;
}
.stats-date {
font-size: 14px;
color: #333;
margin-bottom: 5px;
}
.stats-category-item {
padding: 10px 0;
border-bottom: 1px solid #f5f5f5;
}
.stats-category-item:last-child {
border-bottom: none;
}
.stats-category-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 5px;
}
.stats-category-name {
font-weight: 500;
color: #333;
text-decoration: none;
}
.stats-category-name:hover {
color: #007bff;
}
.stats-category-count {
background: #e8f5e8;
color: #2e7d32;
padding: 2px 8px;
border-radius: 10px;
font-size: 12px;
font-weight: 500;
}
.stats-empty {
text-align: center;
padding: 20px;
color: #999;
font-size: 14px;
}
/* 批量操作表单修复 */
.bulk-form {
margin: 0;
padding: 0;
}
.bulk-actions .bulk-actions-buttons button {
margin: 0 5px;
}
/* 导入导出 */
.import-export-section {
margin-bottom: 22px;
}
.import-export-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 22px;
}
.import-box, .export-box {
background: #fff;
padding: 22px;
border-radius: 6px;
border: 1px solid #dee2e6;
}
.import-box h4, .export-box h4 {
margin-top: 0;
margin-bottom: 16px;
color: #495057;
font-size: 15px;
font-weight: 600;
}
.export-buttons {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 15px;
}
.format-example {
background: #f8f9fa;
padding: 16px;
border-radius: 6px;
border-left: 3px solid #007bff;
margin-top: 18px;
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
font-size: 13px;
}
.format-example pre {
margin: 5px 0px;
white-space: pre-wrap;
font-size: 12px;
}
#post_cids, #original_url{height:36px;border-radius:4px!important;}
/* 发布表单 */
.publish-form {
background: #fff;
padding: 25px;
margin-bottom: 20px;
border-radius: 8px;
border: 1px solid #e0e0e0;
}
.publish-form h2 {
margin-top: 0;
margin-bottom: 22px;
padding-bottom: 16px;
border-bottom: 1px solid #f0f0f0;
color: #333;
font-size: 18px;
font-weight: 600;
}
.form-row {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin-bottom: 20px;
}
.form-group {
flex: 1;
min-width: 200px;
}
.form-group label {
display: block;
margin-bottom: 8px;
margin-top:8px ;
font-weight: 500;
color: #555;
font-size: 14px;
}
.form-control {
width: 100%;
padding: 10px 14px;
border: 1px solid #ced4da;
border-radius: 4px;
font-size: 14px;
margin-top:5px;
color: #333;
background-color: #fff;
box-sizing: border-box;
}
textarea.form-control {
resize: vertical;
min-height: 100px;
}
/* 修复:日期输入框宽度问题 */
input[type="date"].form-control {
width: 100%;
padding: 10px 14px;
border: 1px solid #ced4da;
border-radius: 4px;
font-size: 14px;
background-color: #fff;
box-sizing: border-box;
}
/* 修复1分类下拉选择框 - 确保文字完整显示 */
select.form-control {
width: 100%;
padding: 10px 14px;
border: 1px solid #ced4da;
border-radius: 4px;
font-size: 14px;
color: #333;
background-color: #fff;
box-sizing: border-box;
max-width: 100%;
height: auto; /* 允许高度自动调整 */
min-height: 42px; /* 设置最小高度 */
line-height: 1.5; /* 设置行高 */
overflow: visible !important; /* 强制显示完整内容 */
}
/* 修复1分类下拉选项 - 确保文字完整显示 */
select.form-control option {
padding: 10px 12px; /* 增加内边距 */
font-size: 14px;
line-height: 1.5; /* 设置行高 */
height: auto; /* 允许高度自动调整 */
white-space: normal; /* 允许文字换行 */
overflow: visible; /* 允许内容溢出可见 */
text-overflow: clip; /* 不截断文字 */
max-width: 100%;
word-wrap: break-word; /* 允许单词换行 */
word-break: break-word; /* 允许单词内断行 */
}
.form-actions {
text-align: center;
margin-top: 20px;
}
.description {
font-size: 12px;
color: #6c757d;
margin-top: 6px;
display: block;
}
/* 按钮样式 */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 10px 18px;
font-size: 14px;
font-weight: 500;
text-align: center;
cursor: pointer;
border: 1px solid transparent;
border-radius: 4px;
height: 40px;
min-width: 80px;
box-sizing: border-box;
}
.btn-primary {
color: #fff;
background-color: #007bff;
border-color: #007bff;
}
.btn-primary:hover {
background-color: #0069d9;
border-color: #0062cc;
}
.btn-secondary {
color: #fff;
background-color: #6c757d;
border-color: #6c757d;
}
.btn-secondary:hover {
background-color: #5a6268;
border-color: #545b62;
}
.btn-danger {
color: #fff;
background-color: #dc3545;
border-color: #dc3545;
}
.btn-danger:hover {
background-color: #c82333;
border-color: #bd2130;
}
.btn-success {
color: #fff;
background-color: #28a745;
border-color: #28a745;
}
.btn-success:hover {
background-color: #218838;
border-color: #1e7e34;
}
.btn-info {
color: #fff;
background-color: #17a2b8;
border-color: #17a2b8;
}
.btn-info:hover {
background-color: #138496;
border-color: #117a8b;
}
.btn-warning {
color: #212529;
background-color: #ffc107;
border-color: #ffc107;
}
.btn-warning:hover {
background-color: #e0a800;
border-color: #d39e00;
}
.btn-sm {
padding: 6px 12px;
font-size: 12px;
height: 36px;
min-width: 60px;
}
/* 知识列表区域 */
.memo-list-section {
background: #fff;
padding: 0;
border-radius: 8px;
border: 1px solid #e0e0e0;
margin-bottom: 20px;
overflow: hidden;
}
.memo-list-section h2 {
margin: 0;
padding: 20px 25px;
border-bottom: 1px solid #f0f0f0;
color: #333;
font-size: 18px;
font-weight: 600;
}
/* 搜索和筛选工具栏 */
.search-filter-toolbar {
padding: 15px 25px;
background: #f8f9fa;
border-bottom: 1px solid #e0e0e0;
display: flex;
flex-wrap: wrap;
gap: 15px;
align-items: center;
}
.search-box {
flex: 1;
min-width: 250px;
}
.search-box form {
display: flex;
gap: 10px;
}
.search-input {
flex: 1;
padding: 8px 12px;
border: 1px solid #ced4da;
border-radius: 4px!important;
overflow: hidden;
font-size: 14px;
min-width: 200px;
height: 36px;
}
.filter-box {
display: flex;
gap: 10px;
align-items: center;
flex-wrap: wrap;
}
.category-filter {
min-width: 150px;
}
/* 修复1分类筛选下拉框 - 确保文字完整显示 */
.category-select {
padding: 8px 12px;
border: 1px solid #ced4da;
border-radius: 4px;
font-size: 14px;
background: white;
min-width: 150px;
max-width: 200px;
height: auto;
min-height: 36px;
line-height: 1.5;
overflow: visible !important;
}
.category-select option {
padding: 8px 12px;
font-size: 14px;
line-height: 1.5;
height: auto;
white-space: normal;
overflow: visible;
text-overflow: clip;
word-wrap: break-word;
word-break: break-word;
}
.filter-info {
padding: 8px 15px;
background: #e7f3ff;
border-radius: 4px;
color: #0066cc;
font-size: 13px;
flex-grow: 1;
}
.filter-info strong {
color: #0056b3;
}
.clear-filter {
color: #666;
text-decoration: none;
font-size: 13px;
margin-left: 10px;
}
.clear-filter:hover {
color: #333;
}
/* 表格样式 */
.data-table {
width: 100%;
border-collapse: separate;
border-spacing: 0;
margin: 0;
font-size: 14px;
background-color: #fff;
table-layout: auto;
}
.data-table th,
.data-table td {
padding: 14px 12px;
border-bottom: 1px solid #f0f0f0;
text-align: left;
vertical-align: middle;
line-height: 1.4;
}
.data-table th {
background-color: #fafbfc;
font-weight: 600;
color: #333;
border-bottom: 2px solid #e0e0e0;
font-size: 14px;
white-space: nowrap;
}
.data-table tbody tr:hover {
background-color: #f8f9fa;
}
/* 列宽设置 - 修复 */
.data-table th:nth-child(1),
.data-table td:nth-child(1) {
width: 30px;
text-align: center;
padding: 14px 8px;
}
.data-table th:nth-child(2),
.data-table td:nth-child(2) {
width: 40px;
text-align: center;
font-family: 'Monaco', 'Menlo', 'Consolas', monospace;
font-size: 13px;
color: #666;
white-space: nowrap;
}
/* 内容列 - 问题3超出显示查看更多 */
.data-table th:nth-child(3),
.data-table td:nth-child(3) {
min-width: 350px;
max-width: 400px; /* 限制最大宽度 */
word-wrap: break-word;
overflow-wrap: break-word;
position: relative;
}
.data-table th:nth-child(4),
.data-table td:nth-child(4) {
width: 50px;
text-align: center;
white-space: nowrap;
}
.data-table th:nth-child(5),
.data-table td:nth-child(5) {
width: 50px;
text-align: center;
white-space: nowrap;
}
.data-table th:nth-child(6),
.data-table td:nth-child(6) {
width: 80px;
text-align: center;
white-space: nowrap;
}
.data-table th:nth-child(7),
.data-table td:nth-child(7) {
width: 120px;
min-width: 100px;
max-width: 150px;
}
.data-table th:nth-child(8),
.data-table td:nth-child(8) {
width: 200px; /* 增加宽度以容纳更多按钮 */
text-align: center;
white-space: nowrap;
padding: 14px 8px;
}
/* 分类徽章 - 修改为可点击 */
.category-badge {
display: inline-block;
padding: 4px 10px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
background: #e8f5e8;
color: #2e7d32;
border: 1px solid #c8e6c9;
max-width: 120px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
cursor: pointer;
text-decoration: none;
transition: all 0.2s;
}
.category-badge:hover {
background: #d5ecd5;
border-color: #a8d5a9;
text-decoration: none;
color: #2e7d32;
}
/* 日期徽章 */
.date-badge {
display: inline-block;
background-color: #f0f7ff;
color: #0066cc;
padding: 4px 10px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
white-space: nowrap;
}
/* 创建时间 */
.created-time {
color: #666;
font-size: 12px;
white-space: nowrap;
}
/* CID徽章 - 修改为可点击链接 */
.cid-badge {
display: inline-block;
background-color: #fff3cd;
color: #856404;
padding: 3px 8px;
margin: 2px;
border-radius: 3px;
font-size: 11px;
font-weight: 500;
font-family: 'Monaco', 'Menlo', 'Consolas', monospace;
border: 1px solid #ffeaa7;
white-space: nowrap;
cursor: pointer;
text-decoration: none;
transition: all 0.2s;
}
.cid-badge:hover {
background-color: #ffeaa7;
border-color: #ffdd59;
text-decoration: none;
color: #856404;
}
.cid-container {
display: flex;
flex-wrap: wrap;
gap: 3px;
max-height: 60px;
overflow-y: auto;
}
/* 批量操作 */
.bulk-actions {
padding: 15px;
background-color: #f8f9fa;
border: 1px solid #e0e0e0;
display: flex;
justify-content: space-between;
align-items: center;
}
.bulk-actions-footer {
border-top: none;
border-bottom: 2px solid #e0e0e0;
}
.select-all-area {
display: flex;
align-items: center;
gap: 15px;
}
.selected-info {
font-size: 13px;
color: #666;
}
/* 新增:批量操作按钮组 */
.bulk-actions-buttons {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
/* 分页样式 */
.pagination {
margin-top: 25px;
margin-bottom: 25px;
text-align: center;
}
.pagination ul {
list-style: none;
padding: 0;
margin: 0;
display: inline-flex;
gap: 4px;
}
.pagination li {
display: inline;
}
.pagination a, .pagination span {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 8px 12px;
background-color: #fff;
color: #495057;
text-decoration: none;
border-radius: 4px;
border: 1px solid #dee2e6;
min-width: 36px;
height: 36px;
font-size: 13px;
}
.pagination a:hover {
background-color: #f8f9fa;
border-color: #dee2e6;
}
.pagination .active a, .pagination .active span {
background-color: #007bff;
color: white;
border-color: #007bff;
}
/* 空状态 */
.empty-state {
text-align: center;
padding: 50px 20px;
color: #999;
}
.empty-state .icon {
font-size: 48px;
margin-bottom: 20px;
opacity: 0.5;
}
/* 模态框 */
.modal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.5);
}
.modal-content {
background-color: #fff;
margin: 3.5% auto;
padding: 0;
border-radius: 8px;
width: 85%;
max-width: 800px; /* 增加最大宽度用于内容查看 */
box-shadow: 0 5px 25px rgba(0,0,0,0.15);
position: relative;
max-height: 87vh; /* 限制最大高度 */
overflow-y: auto; /* 允许垂直滚动 */
}
.close {
position: absolute;
right: 20px;
top: 18px;
font-size: 22px;
cursor: pointer;
color: #999;
background: none;
border: none;
padding: 0;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
}
.close:hover {
color: #333;
background-color: #f5f5f5;
}
.modal-header {
margin: 0;
padding: 20px 25px;
border-bottom: 1px solid #e0e0e0;
background-color: #fafbfc;
border-radius: 8px 8px 0 0;
position: sticky;
top: 0;
z-index: 10;
}
.modal-header h3 {
margin: 0;
color: #333;
font-size: 16px;
font-weight: 600;
}
.modal-body {
padding: 25px;
max-height: calc(85vh - 70px); /* 减去头部高度 */
overflow-y: auto;
}
/* 内容查看模态框特定样式 */
#contentModal .modal-body {
font-size: 14px;
line-height: 1.6;
color: #333;
white-space: pre-wrap; /* 仅在弹窗中支持换行显示 */
word-wrap: break-word;
}
/* 修复2超链接样式 - 只对URL本身加链接 */
.url-link {
color: #0066cc;
text-decoration: none;
border-bottom: 1px dotted #0066cc;
transition: all 0.2s;
word-break: break-all; /* 允许URL换行 */
}
.url-link:hover {
color: #0056b3;
background-color: #f8f9fa;;
}
/* 原文链接样式 */
.original-link {
margin-top: 15px;
padding: 10px 12px;
background: #f8f9fa;
border-radius: 4px;
border-left: 3px solid #28a745;
}
.original-link-label {
font-size: 12px;
font-weight: 600;
color: #495057;
margin-bottom: 5px;
}
.original-link-url {
font-size: 13px;
word-break: break-all;
}
.original-link-url a {
color: #0066cc;
text-decoration: none;
}
.original-link-url a:hover {
text-decoration: underline;
}
.modal-content form {
padding: 25px;
}
/* 查看更多按钮 */
.view-more-btn {
display: inline-block;
color: #0066cc;
background: none;
border: none;
padding: 2px 6px 2px 0px;
font-size: 12px;
cursor: pointer;
margin-top: 5px;
border-radius: 3px;
transition: all 0.2s;
}
.view-more-btn:hover {
background-color: #f0f7ff;
text-decoration: underline;
}
/* 内容容器 */
.content-container {
position: relative;
}
.content-preview {
max-height: 80px;
overflow: hidden;
position: relative;
color: #000;
/* 修复1移除 white-space: pre-wrap列表不处理换行 */
word-wrap: break-word;
line-height: 1.5;
}
/* 操作按钮组 */
.action-buttons {
display: flex;
gap: 5px;
flex-wrap: wrap;
justify-content: center;
}
/* 锚点目标 */
.anchor-target {
scroll-margin-top: 20px;
}
/* 响应式设计 */
@media (max-width: 992px) {
.import-export-grid {
grid-template-columns: 1fr;
}
.search-filter-toolbar {
flex-direction: column;
align-items: stretch;
}
.search-box {
width: 100%;
}
.filter-box {
width: 100%;
justify-content: space-between;
}
.data-table {
display: block;
overflow-x: auto;
}
.data-table th:nth-child(8),
.data-table td:nth-child(8) {
width: 150px;
}
.action-buttons {
flex-direction: column;
}
}
@media (max-width: 768px) {
.panel-container {
padding: 0 15px;
}
.form-row {
flex-direction: column;
gap: 15px;
}
.form-group {
width: 100%;
}
.bulk-actions {
flex-direction: column;
gap: 10px;
}
.bulk-actions-buttons {
width: 100%;
justify-content: center;
}
.select-all-area {
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
.floating-message {
left: 20px;
right: 20px;
max-width: none;
}
.search-box form {
flex-direction: column;
}
.search-input {
min-width: 100%;
}
.category-filter {
min-width: 100%;
}
.category-select {
min-width: 100%;
max-width: 100%;
}
.data-table th:nth-child(3),
.data-table td:nth-child(3) {
max-width: 200px;
}
.data-table th:nth-child(8),
.data-table td:nth-child(8) {
width: 120px;
}
.export-buttons {
flex-direction: column;
}
.export-buttons button {
width: 100%;
}
}
@media (max-width: 480px) {
.pagination a, .pagination span {
min-width: 30px;
height: 30px;
padding: 0 8px;
font-size: 12px;
}
.action-buttons button {
font-size: 11px;
padding: 4px 8px;
}
}
</style>
<div class="memo-panel">
<div class="panel-container">
<!-- 浮动消息提示 -->
<?php if (isset($successMsg)): ?>
<div id="floatingMessage" class="floating-message success show">
<div style="display: flex; justify-content: space-between; align-items: flex-start;">
<div style="flex-grow: 1;">
<strong>✓ 成功</strong>
<div style="margin-top: 5px;"><?php echo $successMsg; ?></div>
</div>
<button type="button" onclick="hideMessage()" style="background: none; border: none; color: inherit; font-size: 18px; cursor: pointer; margin-left: 10px; padding: 0 5px;">&times;</button>
</div>
</div>
<?php endif; ?>
<?php if (isset($errorMsg)): ?>
<div id="floatingMessage" class="floating-message error show">
<div style="display: flex; justify-content: space-between; align-items: flex-start;">
<div style="flex-grow: 1;">
<strong>✗ 错误</strong>
<div style="margin-top: 5px;"><?php echo $errorMsg; ?></div>
</div>
<button type="button" onclick="hideMessage()" style="background: none; border: none; color: inherit; font-size: 18px; cursor: pointer; margin-left: 10px; padding: 0 5px;">&times;</button>
</div>
</div>
<?php endif; ?>
<div class="typecho-page-main" role="main">
<!-- 统计信息 -->
<div class="stats-section">
<!--<h2 style="margin-bottom: 20px; color: #333; font-size: 18px; font-weight: 600;">知识统计</h2>-->
<div class="stats-grid" style="margin-top:10px;">
<!-- 总知识数 -->
<div class="stats-card">
<h3>总知识数</h3>
<div class="stats-number"><?php echo $statistics['total_count']; ?></div>
<div class="stats-label">条知识记录</div>
</div>
<!-- 时间范围 -->
<!-- 时间范围 -->
<div class="stats-card">
<h3>跨度时间</h3>
<?php if ($statistics['oldest_date'] && $statistics['newest_date']): ?>
<div class="stats-date">
最早:<?php echo date('Y-m-d', strtotime($statistics['oldest_date'])); ?>
</div>
<div class="stats-date">
最新:<?php echo date('Y-m-d', strtotime($statistics['newest_date'])); ?>
</div>
<div class="stats-label" style="margin-top: 8px; padding-top: 8px; border-top: 1px solid #f0f0f0;">
<!--<span style="font-size: 12px; color: #666;">
有日期:<?php echo $statistics['dated_count']; ?> 条,
无日期:<?php echo $statistics['undated_count']; ?> 条
</span>-->
</div>
<?php elseif ($statistics['oldest_date']): ?>
<div class="stats-date">
最早:<?php echo date('Y-m-d', strtotime($statistics['oldest_date'])); ?>
</div>
<div class="stats-label" style="margin-top: 8px; padding-top: 8px; border-top: 1px solid #f0f0f0;">
<!--<span style="font-size: 12px; color: #666;">
有日期:<?php echo $statistics['dated_count']; ?> 条,
无日期:<?php echo $statistics['undated_count']; ?> 条
</span>-->
</div>
<?php elseif ($statistics['newest_date']): ?>
<div class="stats-date">
最新:<?php echo date('Y-m-d', strtotime($statistics['newest_date'])); ?>
</div>
<div class="stats-label" style="margin-top: 8px; padding-top: 8px; border-top: 1px solid #f0f0f0;">
<span style="font-size: 12px; color: #666;">
有日期:<?php echo $statistics['dated_count']; ?> 条,
无日期:<?php echo $statistics['undated_count']; ?> 条
</span>
</div>
<?php else: ?>
<div class="stats-empty" style="padding: 10px 0;">
暂无事件日期数据
</div>
<?php endif; ?>
</div>
<!-- 分类分布 -->
<!-- 分类分布 -->
<div class="stats-card">
<h3>分类分布</h3>
<?php if (!empty($statistics['categories'])): ?>
<div class="categories-horizontal-list" style="margin-top: 8px;">
<?php foreach ($statistics['categories'] as $catName => $catCount): ?>
<a href="?panel=Memo/manage-panel.php&category=<?php echo urlencode($catName); ?>&scroll=list"
class="category-tag"
title="点击查看该分类的知识">
<?php echo htmlspecialchars($catName); ?><?php echo $catCount; ?>
</a>
<?php endforeach; ?>
</div>
<?php else: ?>
<div class="stats-empty">暂无分类数据</div>
<?php endif; ?>
</div>
</div>
</div>
<!-- 导入导出功能 -->
<div class="import-export-section">
<div class="import-export-grid">
<!-- 导出功能 -->
<div class="export-box">
<h4>导出知识</h4>
<p style="color:#000;">导出所有知识为文件格式</p>
<div class="export-buttons">
<form method="post" action="" style="display: inline;">
<button type="submit" name="export" class="btn btn-success">
导出为 TXT 文件
</button>
</form>
<form method="post" action="" style="display: inline;">
<button type="submit" name="export_md" class="btn btn-warning">
导出为 MD 文件
</button>
</form>
</div>
<div class="format-example">
<strong style="color:#000;">TXT格式示例</strong>
<pre>[工作] 2025.12.21 完成项目报告
[学习] PHP编程学习笔记
[生活] 周末购物清单</pre>
<strong style="color:#000;">MD格式示例</strong>
<pre>## 工作
- **2025-12-21** 完成项目报告
## 学习
- PHP编程学习笔记
## 生活
- 周末购物清单</pre>
</div>
</div>
<!-- 导入功能 -->
<div class="import-box">
<h4>导入知识</h4>
<p style="color:#000;">从文件导入知识</p>
<form method="post" action="" enctype="multipart/form-data">
<div style="margin-bottom: 15px;">
<label for="import_file" style="color:#000;padding-bottom:3px;">选择文件:</label>
<input type="file" id="import_file" name="import_file" class="form-control" required accept=".txt,.text,.sql,.md">
<p class="description">支持 .txt/.md 文本文件和 .sql 数据库文件</p>
</div>
<div style="margin-bottom: 15px; display: none;" id="sqlImportOptions">
<label for="import_category">导入分类:</label>
<input type="text" id="import_category" name="import_category" class="form-control" value="文章导入" placeholder="设置导入的分类名称">
<p class="description">设置SQL导入的知识分类名称</p>
</div>
<button type="submit" name="import" class="btn btn-info">
导入文件
</button>
</form>
<div class="format-example">
<strong style="color:#000;">支持格式:</strong>
<pre>1. TXT文本文件格式
[分类] 日期 内容
日期格式: YYYY.MM.DD
2. MD文件格式
## 分类
- **日期** 内容
3. SQL文件格式
从Typecho文章的SQL备份文件导入
提取文章标题和内容作为知识</pre>
</div>
</div>
</div>
</div>
<!-- 发布新知识 -->
<div class="publish-form">
<h2>发布新知识</h2>
<form method="post" action="" id="publishForm">
<div class="form-row">
<div class="form-group">
<label for="category">知识分类 *</label>
<select id="category" name="category" class="form-control" required>
<option value="">请选择分类</option>
<?php foreach ($defaultCategories as $cat): ?>
<option value="<?php echo htmlspecialchars($cat); ?>"><?php echo htmlspecialchars($cat); ?></option>
<?php endforeach; ?>
<?php foreach ($allCategories as $cat): ?>
<?php if (!in_array($cat, $defaultCategories)): ?>
<option value="<?php echo htmlspecialchars($cat); ?>"><?php echo htmlspecialchars($cat); ?></option>
<?php endif; ?>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<label for="event_date">发掘日期(可选)</label>
<input type="date" id="event_date" name="event_date" class="form-control"
value="<?php echo date('Y-m-d'); ?>">
</div>
</div>
<div class="form-group">
<label for="content">知识内容 *</label>
<textarea id="content" name="content" class="form-control" rows="4" required
placeholder="请输入知识内容(支持网址,会自动转换为超链接)"></textarea>
</div>
<div class="form-row">
<div class="form-group">
<label for="post_cids">关联文章CID可选</label>
<input type="text" id="post_cids" name="post_cids" class="form-control"
placeholder="输入文章CID多个用逗号分隔123,456">
<span class="description">可关联多个文章输入文章CID用逗号分隔</span>
</div>
<div class="form-group">
<label for="original_url">原文链接(可选)</label>
<input type="text" id="original_url" name="original_url" class="form-control"
placeholder="输入原文链接https://www.shitoucuo.com/tandian_10.html">
<span class="description">可填写原文链接,查看完整内容时会显示</span>
</div>
</div>
<div class="form-actions">
<button type="reset" class="btn btn-secondary" style="margin-right: 10px;">重置</button>
<button type="submit" class="btn btn-primary">发布</button>
</div>
</form>
</div>
<!-- 锚点目标 -->
<div id="memo-list-anchor" class="anchor-target"></div>
<!-- 知识列表区域 -->
<div class="memo-list-section">
<h2>知识列表</h2>
<!-- 搜索和筛选工具栏 -->
<div class="search-filter-toolbar">
<!-- 搜索框 -->
<div class="search-box">
<form method="get" action="">
<input type="hidden" name="panel" value="Memo/manage-panel.php">
<input type="hidden" name="category" value="<?php echo htmlspecialchars($category); ?>">
<input type="hidden" name="scroll" value="list">
<input type="text" name="search" class="search-input"
placeholder="搜索知识内容..."
value="<?php echo htmlspecialchars($search); ?>">
<button type="submit" class="btn btn-primary btn-sm">搜索</button>
</form>
</div>
<!-- 分类筛选 -->
<div class="filter-box">
<div class="category-filter">
<select name="category" class="category-select" onchange="if(this.value) window.location.href='?panel=Memo/manage-panel.php&category='+encodeURIComponent(this.value)+'<?php echo $search ? '&search=' . urlencode($search) : ''; ?>&scroll=list'; else window.location.href='?panel=Memo/manage-panel.php<?php echo $search ? '&search=' . urlencode($search) : ''; ?>&scroll=list';">
<option value="">全部分类</option>
<?php foreach ($allCategories as $cat): ?>
<option value="<?php echo htmlspecialchars($cat); ?>"
<?php echo $category == $cat ? 'selected' : ''; ?>>
<?php echo htmlspecialchars($cat); ?>
<?php if (isset($categoryCounts[$cat])): ?>
(<?php echo $categoryCounts[$cat]; ?>)
<?php endif; ?>
</option>
<?php endforeach; ?>
</select>
</div>
<?php if ($search || $category): ?>
<div class="filter-info">
<?php if ($search && $category): ?>
搜索"<strong><?php echo htmlspecialchars($search); ?></strong>",知识分类"<strong><?php echo htmlspecialchars($category); ?></strong>",共找到 <?php echo $total; ?> 条记录
<?php elseif ($search): ?>
搜索"<strong><?php echo htmlspecialchars($search); ?></strong>",共找到 <?php echo $total; ?> 条记录
<?php elseif ($category): ?>
分类"<strong><?php echo htmlspecialchars($category); ?></strong>",共 <?php echo $total; ?> 条记录
<?php endif; ?>
<a href="?panel=Memo/manage-panel.php&scroll=list" class="clear-filter">清除筛选</a>
</div>
<?php endif; ?>
</div>
</div>
<?php if (empty($memos)): ?>
<div class="empty-state">
<div class="icon">📝</div>
<h3>暂无知识记录</h3>
<p>还没有添加知识,或者当前筛选条件没有匹配的记录</p>
<?php if ($search || $category): ?>
<a href="?panel=Memo/manage-panel.php&scroll=list" class="btn btn-primary" style="margin-top: 15px;">查看全部</a>
<?php endif; ?>
</div>
<?php else: ?>
<!-- 批量操作区域 -->
<div class="bulk-actions">
<div class="select-all-area">
<div>
<input type="checkbox" id="select-all" style="margin: 0; width: 16px; height: 16px;">
<label for="select-all" style="color:#000;margin-left: 8px; font-weight: 500; font-size: 14px;">
全选本页
</label>
</div>
<div class="selected-info">
已选择 <strong id="selected-count">0</strong> 条记录
</div>
</div>
<div class="bulk-actions-buttons">
<button type="button" class="btn btn-success btn-sm" onclick="exportSelected('txt')">
导出选中TXT
</button>
<button type="button" class="btn btn-warning btn-sm" onclick="exportSelected('md')">
导出选中MD
</button>
<button type="button" class="btn btn-danger btn-sm" onclick="confirmBulkDelete()">
删除选中
</button>
</div>
</div>
<!-- 主表单(只用于删除) -->
<form method="post" action="" id="bulkActionForm" style="display:none;">
<input type="hidden" name="bulk_delete" value="1">
<input type="hidden" name="delete_ids[]" id="deleteIdsField">
</form>
<!-- 导出表单 - 修复:改为两个独立的表单 -->
<form method="post" action="" id="exportTxtForm" style="display:none;">
<input type="hidden" name="export_ids" id="exportIdsTxtField">
<input type="hidden" name="export_selected_txt" value="1">
</form>
<form method="post" action="" id="exportMdForm" style="display:none;">
<input type="hidden" name="export_ids" id="exportIdsMdField">
<input type="hidden" name="export_selected_md" value="1">
</form>
<table class="data-table">
<thead>
<tr>
<th>
<input type="checkbox" id="thead-select-all" style="margin: 0; width: 16px; height: 16px;">
</th>
<th>ID</th>
<th>知识内容</th>
<th>知识分类</th>
<th>发掘时间</th>
<th>发布时间</th>
<th>文章关联</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<?php foreach ($memos as $memo):
$cids = !empty($memo['post_cids']) ? explode(',', $memo['post_cids']) : array();
?>
<tr>
<td>
<input type="checkbox" name="memo_ids[]" value="<?php echo $memo['id']; ?>" class="row-checkbox" style="margin: 0; width: 16px; height: 16px;">
</td>
<td>
<span class="memo-id">#<?php echo $memo['id']; ?></span>
</td>
<!-- <td>
<div class="content-container">
<div class="content-preview">
<?php
// 修复2只对精确的URL加超链接
$content = convertUrlsToLinks($memo['content']);
if ($search) {
$searchEncoded = htmlspecialchars($search);
$content = preg_replace(
'/(' . preg_quote($searchEncoded, '/') . ')/i',
'<mark style="background: #fff3cd; padding: 1px 3px;">$1</mark>',
$content
);
}
// 修复1列表不处理换行使用 nl2br 保持原有格式但不保留空格格式
echo nl2br($content);
?>
</div>
<button type="button" class="view-more-btn"
data-content="<?php echo htmlspecialchars($memo['content'], ENT_QUOTES, 'UTF-8'); ?>"
data-original-url="<?php echo htmlspecialchars($memo['original_url'] ?? '', ENT_QUOTES, 'UTF-8'); ?>"
onclick="viewContent(<?php echo $memo['id']; ?>, this)">
查看更多
</button>
</div>
</td>不管是否截取均显示查看更多-->
<td>
<div class="content-container">
<div class="content-preview" id="content-preview-<?php echo $memo['id']; ?>">
<?php
// 修复2只对精确的URL加超链接
$content = convertUrlsToLinks($memo['content']);
if ($search) {
$searchEncoded = htmlspecialchars($search);
$content = preg_replace(
'/(' . preg_quote($searchEncoded, '/') . ')/i',
'<mark style="background: #fff3cd; padding: 1px 3px;">$1</mark>',
$content
);
}
// 获取纯文本长度不含HTML标签用于判断
$plainText = strip_tags($content);
// 显示内容,如果过长则截取
$maxLength = 200; // 截取长度阈值
$showFullContent = false;
if (mb_strlen($plainText, 'UTF-8') > $maxLength) {
// 截取内容
$shortenedContent = mb_substr($plainText, 0, $maxLength, 'UTF-8') . '...';
// 重新应用HTML处理只处理URL
$shortenedContent = convertUrlsToLinks($shortenedContent);
// 如果搜索关键词被截断了,确保高亮显示
if ($search) {
$shortenedContent = preg_replace(
'/(' . preg_quote(htmlspecialchars($search), '/') . ')/i',
'<mark style="background: #fff3cd; padding: 1px 3px;">$1</mark>',
$shortenedContent
);
}
echo nl2br($shortenedContent);
$showFullContent = true;
} else {
// 内容不长,显示完整内容
echo nl2br($content);
$showFullContent = false;
}
?>
</div>
<?php if ($showFullContent): ?>
<button type="button" class="view-more-btn"
data-content="<?php echo htmlspecialchars($memo['content'], ENT_QUOTES, 'UTF-8'); ?>"
data-original-url="<?php echo htmlspecialchars($memo['original_url'] ?? '', ENT_QUOTES, 'UTF-8'); ?>"
onclick="viewContent(<?php echo $memo['id']; ?>, this)">
查看更多
</button>
<?php endif; ?>
</div>
</td>
<td>
<a href="?panel=Memo/manage-panel.php&category=<?php echo urlencode($memo['category']); ?>&scroll=list"
class="category-badge"
title="点击查看该分类的知识">
<?php echo htmlspecialchars($memo['category']); ?>
</a>
</td>
<td>
<?php if (!empty($memo['event_date'])): ?>
<span class="date-badge">
<?php echo date('Y-m-d', strtotime($memo['event_date'])); ?>
</span>
<?php else: ?>
<span style="color: #999; font-size: 12px;">-</span>
<?php endif; ?>
</td>
<td>
<span class="created-time">
<?php echo date('Y-m-d', strtotime($memo['created'])); ?>
</span>
</td>
<td>
<?php if (!empty($cids)): ?>
<div class="cid-container">
<?php foreach ($cids as $cid):
$cid = trim($cid);
if (!empty($cid)):
$postInfo = $memo['post_info'][$cid] ?? null;
$title = $postInfo['title'] ?? '文章' . $cid;
$url = $postInfo['url'] ?? '';
?>
<?php if (!empty($url)): ?>
<a href="<?php echo htmlspecialchars($url); ?>"
class="cid-badge"
title="<?php echo htmlspecialchars($title); ?>"
target="_blank">
<?php echo $cid; ?>
</a>
<?php else: ?>
<span class="cid-badge" title="<?php echo htmlspecialchars($title); ?>">
<?php echo $cid; ?>
</span>
<?php endif; ?>
<?php endif; ?>
<?php endforeach; ?>
</div>
<?php else: ?>
<span style="color: #999; font-size: 12px;">-</span>
<?php endif; ?>
</td>
<td>
<div class="action-buttons">
<!-- 新增:单个导出按钮 -->
<form method="post" action="" style="display: inline;">
<input type="hidden" name="single_id" value="<?php echo $memo['id']; ?>">
<button type="submit" name="export_single_txt" class="btn btn-sm btn-success" style="padding: 4px 8px; font-size: 12px;" title="导出为TXT">
TXT
</button>
</form>
<form method="post" action="" style="display: inline;">
<input type="hidden" name="single_id" value="<?php echo $memo['id']; ?>">
<button type="submit" name="export_single_md" class="btn btn-sm btn-warning" style="padding: 4px 8px; font-size: 12px;" title="导出为MD">
MD
</button>
</form>
<button type="button" class="btn btn-sm btn-primary edit-btn"
data-memo='<?php echo htmlspecialchars(json_encode($memo), ENT_QUOTES, 'UTF-8'); ?>'
style="padding: 4px 10px; font-size: 12px;" title="编辑">
编辑
</button>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<!-- 批量操作底部栏 -->
<div class="bulk-actions bulk-actions-footer">
<div class="select-all-area">
<div>
<input type="checkbox" id="footer-select-all" style="margin: 0; width: 16px; height: 16px;">
<label for="footer-select-all" style="color:#000;margin-left: 8px; font-weight: 500; font-size: 14px;">
全选本页
</label>
</div>
<div class="selected-info">
已选择 <strong id="footer-selected-count">0</strong> 条记录
</div>
</div>
<div class="bulk-actions-buttons">
<button type="button" class="btn btn-success btn-sm" onclick="exportSelected('txt')">
导出选中TXT
</button>
<button type="button" class="btn btn-warning btn-sm" onclick="exportSelected('md')">
导出选中MD
</button>
<button type="button" class="btn btn-danger btn-sm" onclick="confirmBulkDelete()">
删除选中
</button>
</div>
</div>
<!-- 分页 -->
<?php if ($totalPages > 1): ?>
<div class="pagination">
<ul>
<?php if ($page > 1): ?>
<li><a href="?panel=Memo/manage-panel.php&page=<?php echo $page-1; ?><?php echo $search ? '&search=' . urlencode($search) : ''; ?><?php echo $category ? '&category=' . urlencode($category) : ''; ?>&scroll=list">&laquo; 上一页</a></li>
<?php endif; ?>
<?php
$start = max(1, $page - 2);
$end = min($totalPages, $page + 2);
if ($start > 1) {
echo '<li><a href="?panel=Memo/manage-panel.php&page=1' . ($search ? '&search=' . urlencode($search) : '') . ($category ? '&category=' . urlencode($category) : '') . '&scroll=list">1</a></li>';
if ($start > 2) echo '<li><span>...</span></li>';
}
for ($i = $start; $i <= $end; $i++): ?>
<li <?php if ($i == $page) echo 'class="active"'; ?>>
<a href="?panel=Memo/manage-panel.php&page=<?php echo $i; ?><?php echo $search ? '&search=' . urlencode($search) : ''; ?><?php echo $category ? '&category=' . urlencode($category) : ''; ?>&scroll=list"><?php echo $i; ?></a>
</li>
<?php endfor; ?>
<?php if ($end < $totalPages): ?>
<?php if ($end < $totalPages - 1): ?>
<li><span>...</span></li>
<?php endif; ?>
<li><a href="?panel=Memo/manage-panel.php&page=<?php echo $totalPages; ?><?php echo $search ? '&search=' . urlencode($search) : ''; ?><?php echo $category ? '&category=' . urlencode($category) : ''; ?>&scroll=list"><?php echo $totalPages; ?></a></li>
<?php endif; ?>
<?php if ($page < $totalPages): ?>
<li><a href="?panel=Memo/manage-panel.php&page=<?php echo $page+1; ?><?php echo $search ? '&search=' . urlencode($search) : ''; ?><?php echo $category ? '&category=' . urlencode($category) : ''; ?>&scroll=list">下一页 &raquo;</a></li>
<?php endif; ?>
</ul>
</div>
<?php endif; ?>
<?php endif; ?>
</div>
</div>
</div>
</div>
<!-- 编辑模态框 -->
<div id="editModal" class="modal" style="display:none;">
<div class="modal-content">
<button type="button" class="close" onclick="closeModal()">&times;</button>
<div class="modal-header">
<h3>编辑知识</h3>
</div>
<form id="editForm" method="post" action="">
<div class="modal-body">
<div class="form-group">
<label for="edit_category">知识分类 *</label>
<select id="edit_category" name="edit_category" class="form-control" required>
<option value="">请选择分类</option>
<?php foreach ($defaultCategories as $cat): ?>
<option value="<?php echo htmlspecialchars($cat); ?>"><?php echo htmlspecialchars($cat); ?></option>
<?php endforeach; ?>
<?php foreach ($allCategories as $cat): ?>
<?php if (!in_array($cat, $defaultCategories)): ?>
<option value="<?php echo htmlspecialchars($cat); ?>"><?php echo htmlspecialchars($cat); ?></option>
<?php endif; ?>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<label for="edit_event_date">发掘日期(可选)</label>
<input type="date" id="edit_event_date" name="edit_event_date" class="form-control">
</div>
<div class="form-group">
<label for="edit_content">知识内容 *</label>
<textarea id="edit_content" name="edit_content" class="form-control" rows="5" required></textarea>
</div>
<div class="form-row">
<div class="form-group">
<label for="edit_post_cids">关联文章CID可选</label>
<input type="text" id="edit_post_cids" name="edit_post_cids" class="form-control"
placeholder="输入文章CID多个用逗号分隔">
</div>
<div class="form-group">
<label for="edit_original_url">原文链接(可选)</label>
<input type="text" id="edit_original_url" name="edit_original_url" class="form-control"
placeholder="输入原文链接">
</div>
</div>
<input type="hidden" name="edit_id" id="edit_id">
</div>
<div style="text-align:right;margin-top:0; padding: 15px 25px; border-top: 1px solid #eee;">
<button type="button" class="btn btn-secondary" onclick="closeModal()" style="margin-right:10px;">
取消
</button>
<button type="submit" class="btn btn-primary">保存修改</button>
</div>
</form>
</div>
</div>
<!-- 内容查看模态框 -->
<div id="contentModal" class="modal" style="display:none;">
<div class="modal-content">
<button type="button" class="close" onclick="closeContentModal()">&times;</button>
<div class="modal-header">
<h3>知识内容</h3>
</div>
<div class="modal-body" id="contentModalBody">
<!-- 内容将通过JavaScript填充 -->
</div>
</div>
</div>
<script>
// 页面加载后自动滚动到锚点
document.addEventListener('DOMContentLoaded', function() {
// 检查是否需要滚动到列表区域
var urlParams = new URLSearchParams(window.location.search);
if (urlParams.get('scroll') === 'list') {
setTimeout(function() {
var anchor = document.getElementById('memo-list-anchor');
if (anchor) {
anchor.scrollIntoView({ behavior: 'smooth' });
}
}, 100);
}
// 自动隐藏浮动消息
var floatingMessage = document.getElementById('floatingMessage');
if (floatingMessage) {
setTimeout(function() {
hideMessage();
}, 5000);
}
// 初始化选择功能
initBulkOperations();
// 初始化编辑按钮
initEditButtons();
// 表单验证
initFormValidation();
// 搜索框回车提交
var searchInput = document.querySelector('.search-input');
if (searchInput) {
searchInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
e.preventDefault();
// 添加滚动参数
var form = this.form;
var scrollInput = form.querySelector('input[name="scroll"]');
if (!scrollInput) {
scrollInput = document.createElement('input');
scrollInput.type = 'hidden';
scrollInput.name = 'scroll';
scrollInput.value = 'list';
form.appendChild(scrollInput);
}
form.submit();
}
});
}
// 文件上传类型变化时显示/隐藏SQL选项
var importFile = document.getElementById('import_file');
var sqlImportOptions = document.getElementById('sqlImportOptions');
if (importFile && sqlImportOptions) {
importFile.addEventListener('change', function() {
var fileName = this.value.toLowerCase();
if (fileName.endsWith('.sql')) {
sqlImportOptions.style.display = 'block';
} else {
sqlImportOptions.style.display = 'none';
}
});
}
});
// 隐藏浮动消息
function hideMessage() {
var message = document.getElementById('floatingMessage');
if (message) {
message.classList.remove('show');
setTimeout(function() {
if (message.parentNode) {
message.parentNode.removeChild(message);
}
}, 300);
}
}
// 将文本中的URL转换为链接JavaScript版本
function makeLinksClickable(text) {
if (!text) return text;
// 精确匹配URL的正则表达式
var urlPattern = /(https?:\/\/[a-zA-Z0-9][-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b[-a-zA-Z0-9()@:%_\+.~#?&\/=]*)/g;
return text.replace(urlPattern, function(url) {
// 清理URL末尾可能错误的标点符号
var punctuation = ['.', ',', ';', ':', '!', '?', ')', ']', '}'];
var lastChar = url.slice(-1);
var suffix = '';
if (punctuation.includes(lastChar)) {
url = url.slice(0, -1);
suffix = lastChar;
}
// 创建链接
return '<a href="' + url + '" class="url-link" target="_blank" rel="noopener noreferrer">' + url + '</a>' + suffix;
});
}
// 查看完整内容
function viewContent(memoId, button) {
var content = button.getAttribute('data-content');
var originalUrl = button.getAttribute('data-original-url');
// 处理内容中的换行符
content = content.replace(/\\r\\n|\\r|\\n/g, '\n');
// 处理内容中的URL链接
var processedContent = makeLinksClickable(content);
// 创建模态框内容
var modalContent = '<div style="margin-bottom: 15px; font-size: 12px; color: #666;">';
modalContent += '知识 ID: #' + memoId;
modalContent += '</div>';
modalContent += '<div style="font-size: 14px; line-height: 1.6; color: #333; white-space: pre-wrap; word-wrap: break-word; margin-bottom: 20px;">';
modalContent += processedContent.replace(/\n/g, '<br>');
modalContent += '</div>';
// 添加原文链接(如果有)
if (originalUrl && originalUrl.trim() !== '') {
modalContent += '<div class="original-link">';
modalContent += '<div class="original-link-label">原文链接:</div>';
modalContent += '<div class="original-link-url">';
modalContent += '<a href="' + originalUrl + '" target="_blank" rel="noopener noreferrer">' + originalUrl + '</a>';
modalContent += '</div>';
modalContent += '</div>';
}
// 更新模态框内容
document.getElementById('contentModalBody').innerHTML = modalContent;
// 显示模态框
document.getElementById('contentModal').style.display = 'block';
}
// 关闭内容查看模态框
function closeContentModal() {
document.getElementById('contentModal').style.display = 'none';
}
// 点击模态框外部关闭
window.onclick = function(event) {
var modal = document.getElementById('editModal');
var contentModal = document.getElementById('contentModal');
if (event.target == modal) {
closeModal();
}
if (event.target == contentModal) {
closeContentModal();
}
}
// 初始化选择功能
function initBulkOperations() {
// 更新选中数量
function updateSelectedCount() {
var checkboxes = document.querySelectorAll('.row-checkbox:checked');
var count = checkboxes.length;
document.getElementById('selected-count').textContent = count;
document.getElementById('footer-selected-count').textContent = count;
return count;
}
// 全选/全不选
function setupSelectAll(selector, targetSelector) {
var selectAll = document.querySelector(selector);
if (selectAll) {
selectAll.addEventListener('change', function(e) {
var checkboxes = document.querySelectorAll(targetSelector);
checkboxes.forEach(function(checkbox) {
checkbox.checked = e.target.checked;
});
updateSelectedCount();
});
}
}
// 行复选框事件
function setupRowCheckboxes() {
var checkboxes = document.querySelectorAll('.row-checkbox');
checkboxes.forEach(function(checkbox) {
checkbox.addEventListener('change', updateSelectedCount);
});
}
// 设置事件监听
setupSelectAll('#select-all', '.row-checkbox');
setupSelectAll('#thead-select-all', '.row-checkbox');
setupSelectAll('#footer-select-all', '.row-checkbox');
setupRowCheckboxes();
updateSelectedCount();
}
// 导出选中记录
function exportSelected(format) {
var checkboxes = document.querySelectorAll('.row-checkbox:checked');
var count = checkboxes.length;
if (count === 0) {
alert('请先选择要导出的知识!');
return false;
}
if (!confirm('确定要导出选中的 ' + count + ' 条知识吗?')) {
return false;
}
// 收集选中的ID
var selectedIds = [];
checkboxes.forEach(function(checkbox) {
selectedIds.push(checkbox.value);
});
// 设置导出表单数据并提交
if (format === 'txt') {
document.getElementById('exportIdsTxtField').value = selectedIds.join(',');
document.getElementById('exportTxtForm').submit();
} else if (format === 'md') {
document.getElementById('exportIdsMdField').value = selectedIds.join(',');
document.getElementById('exportMdForm').submit();
}
return true;
}
// 确认批量删除
function confirmBulkDelete() {
var checkboxes = document.querySelectorAll('.row-checkbox:checked');
var count = checkboxes.length;
if (count === 0) {
alert('请先选择要删除的知识!');
return false;
}
if (!confirm('确定要删除选中的 ' + count + ' 条知识吗?此操作不可恢复!')) {
return false;
}
// 收集选中的ID
var selectedIds = [];
checkboxes.forEach(function(checkbox) {
selectedIds.push(checkbox.value);
});
// 设置删除表单数据并提交
document.getElementById('deleteIdsField').value = selectedIds.join(',');
document.getElementById('bulkActionForm').submit();
}
// 初始化编辑按钮
function initEditButtons() {
var editButtons = document.querySelectorAll('.edit-btn');
editButtons.forEach(function(button) {
button.addEventListener('click', function() {
try {
var memoData = JSON.parse(this.getAttribute('data-memo'));
editMemo(memoData);
} catch (e) {
console.error('解析知识数据失败:', e);
alert('编辑失败:数据格式错误');
}
});
});
}
// 编辑知识
function editMemo(memo) {
document.getElementById('edit_id').value = memo.id;
document.getElementById('edit_content').value = memo.content;
// 设置分类
var categorySelect = document.getElementById('edit_category');
for (var i = 0; i < categorySelect.options.length; i++) {
if (categorySelect.options[i].value === memo.category) {
categorySelect.selectedIndex = i;
break;
}
}
// 设置事件日期
if (memo.event_date) {
var eventDate = new Date(memo.event_date);
var year = eventDate.getFullYear();
var month = (eventDate.getMonth() + 1).toString().padStart(2, '0');
var day = eventDate.getDate().toString().padStart(2, '0');
document.getElementById('edit_event_date').value = year + '-' + month + '-' + day;
} else {
document.getElementById('edit_event_date').value = '';
}
// 设置关联文章CID
document.getElementById('edit_post_cids').value = memo.post_cids || '';
// 设置原文链接
document.getElementById('edit_original_url').value = memo.original_url || '';
// 显示模态框
document.getElementById('editModal').style.display = 'block';
}
// 关闭模态框
function closeModal() {
document.getElementById('editModal').style.display = 'none';
}
// 表单验证
function initFormValidation() {
var publishForm = document.getElementById('publishForm');
if (publishForm) {
publishForm.addEventListener('submit', function(e) {
var category = document.getElementById('category').value;
var content = document.getElementById('content').value;
if (!category) {
e.preventDefault();
alert('请选择分类!');
return false;
}
if (!content.trim()) {
e.preventDefault();
alert('请输入内容!');
return false;
}
});
}
var editForm = document.getElementById('editForm');
if (editForm) {
editForm.addEventListener('submit', function(e) {
var category = document.getElementById('edit_category').value;
var content = document.getElementById('edit_content').value;
if (!category) {
e.preventDefault();
alert('请选择分类!');
return false;
}
if (!content.trim()) {
e.preventDefault();
alert('请输入内容!');
return false;
}
});
}
}
</script>
<?php include 'footer.php'; ?>