Files
Memo/manage-panel.php

2468 lines
82 KiB
PHP
Raw Normal View History

2026-02-23 17:31:07 +08:00
<?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'; ?>