初始化知识库网页系统
功能: - 用户登录/退出 - 文档列表展示(支持子目录) - 关键词搜索 - Markdown 文档在线查看 - 管理员添加用户脚本
This commit is contained in:
@@ -0,0 +1,138 @@
|
|||||||
|
# 知识库网页系统
|
||||||
|
|
||||||
|
一个简单的知识库文档管理系统,支持多用户登录、文档列表和搜索。
|
||||||
|
|
||||||
|
## 功能
|
||||||
|
|
||||||
|
- ✅ 账号密码登录
|
||||||
|
- ✅ 每个用户有独立的知识库目录
|
||||||
|
- ✅ 文档列表展示
|
||||||
|
- ✅ 关键词搜索(标题+内容)
|
||||||
|
- ✅ 在线查看文档(支持 Markdown)
|
||||||
|
- ✅ 管理员添加用户
|
||||||
|
|
||||||
|
## 目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
kb-web/
|
||||||
|
├── index.php # 登录页
|
||||||
|
├── dashboard.php # 文档列表 + 搜索
|
||||||
|
├── view.php # 查看文档内容
|
||||||
|
├── logout.php # 退出登录
|
||||||
|
├── config.php # 数据库配置
|
||||||
|
├── add_user.php # 添加用户脚本
|
||||||
|
├── style.css # 样式
|
||||||
|
├── init.sql # 数据库初始化
|
||||||
|
├── README.md # 说明文档
|
||||||
|
└── {username}/ # 用户文档目录
|
||||||
|
├── 文档1.md
|
||||||
|
└── 文档2.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 部署步骤
|
||||||
|
|
||||||
|
### 1. 上传文件
|
||||||
|
|
||||||
|
将整个 `kb-web` 目录上传到服务器的网站目录中。
|
||||||
|
|
||||||
|
### 2. 创建数据库
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 登录 MySQL
|
||||||
|
mysql -u root -p
|
||||||
|
|
||||||
|
# 执行初始化脚本
|
||||||
|
source init.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
或者直接在 MySQL 中执行:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE DATABASE IF NOT EXISTS kb_web DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 修改配置
|
||||||
|
|
||||||
|
编辑 `config.php`,修改数据库连接信息:
|
||||||
|
|
||||||
|
```php
|
||||||
|
define('DB_HOST', 'localhost');
|
||||||
|
define('DB_NAME', 'kb_web');
|
||||||
|
define('DB_USER', 'your_db_user');
|
||||||
|
define('DB_PASS', 'your_db_password');
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 添加用户
|
||||||
|
|
||||||
|
在服务器上执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /path/to/kb-web
|
||||||
|
php add_user.php 用户名 密码
|
||||||
|
```
|
||||||
|
|
||||||
|
示例:
|
||||||
|
```bash
|
||||||
|
php add_user.php zhangqing mypassword123
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 添加文档
|
||||||
|
|
||||||
|
将用户的文档(.md 或 .txt 格式)放入对应用户目录:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp ~/knowledge-base/analyzed/*.md /path/to/kb-web/zhangqing/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. 访问
|
||||||
|
|
||||||
|
打开浏览器访问:`http://your-domain.com/kb-web/`
|
||||||
|
|
||||||
|
## 管理员操作
|
||||||
|
|
||||||
|
### 添加新用户
|
||||||
|
|
||||||
|
```bash
|
||||||
|
php add_user.php newuser newpassword
|
||||||
|
```
|
||||||
|
|
||||||
|
### 重置密码
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 删除旧用户
|
||||||
|
mysql -u root -p -e "DELETE FROM kb_web.users WHERE username='olduser';"
|
||||||
|
|
||||||
|
# 重新添加
|
||||||
|
php add_user.php olduser newpassword
|
||||||
|
```
|
||||||
|
|
||||||
|
### 批量导入文档
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 将分析好的文档复制到用户目录
|
||||||
|
cp ~/work/knowledge-base/analyzed/*.md /path/to/kb-web/zhangqing/
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **安全性**
|
||||||
|
- 修改 `config.php` 中的数据库密码
|
||||||
|
- 确保 `config.php` 不可被外部访问(LNMP 默认已处理)
|
||||||
|
- 定期备份数据库
|
||||||
|
|
||||||
|
2. **文件权限**
|
||||||
|
```bash
|
||||||
|
chown -R www-data:www-data /path/to/kb-web
|
||||||
|
chmod -R 755 /path/to/kb-web
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **支持的文档格式**
|
||||||
|
- .md (Markdown)
|
||||||
|
- .txt (纯文本)
|
||||||
|
- .markdown
|
||||||
|
|
||||||
|
## 技术栈
|
||||||
|
|
||||||
|
- PHP 7.4+
|
||||||
|
- MySQL 5.7+
|
||||||
|
- 纯前端(HTML + CSS + JS)
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* 添加用户脚本
|
||||||
|
* 使用方法: php add_user.php <username> <password>
|
||||||
|
* 示例: php add_user.php zhangqing mypassword123
|
||||||
|
*/
|
||||||
|
|
||||||
|
if ($argc < 3) {
|
||||||
|
echo "使用方法: php add_user.php <username> <password>\n";
|
||||||
|
echo "示例: php add_user.php zhangqing mypassword123\n";
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$username = $argv[1];
|
||||||
|
$password = $argv[2];
|
||||||
|
|
||||||
|
// 验证用户名格式
|
||||||
|
if (!preg_match('/^[a-zA-Z0-9_]{3,50}$/', $username)) {
|
||||||
|
echo "错误: 用户名只能是字母、数字、下划线,长度3-50\n";
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证密码长度
|
||||||
|
if (strlen($password) < 6) {
|
||||||
|
echo "错误: 密码长度至少6位\n";
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once 'config.php';
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db = getDB();
|
||||||
|
|
||||||
|
// 检查用户是否已存在
|
||||||
|
$stmt = $db->prepare("SELECT id FROM users WHERE username = ?");
|
||||||
|
$stmt->execute([$username]);
|
||||||
|
if ($stmt->fetch()) {
|
||||||
|
echo "错误: 用户 '$username' 已存在\n";
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建密码哈希
|
||||||
|
$passwordHash = password_hash($password, PASSWORD_DEFAULT);
|
||||||
|
|
||||||
|
// 插入用户
|
||||||
|
$stmt = $db->prepare("INSERT INTO users (username, password_hash) VALUES (?, ?)");
|
||||||
|
$stmt->execute([$username, $passwordHash]);
|
||||||
|
|
||||||
|
// 创建用户文档目录
|
||||||
|
$docDir = __DIR__ . '/' . $username;
|
||||||
|
if (!is_dir($docDir)) {
|
||||||
|
mkdir($docDir, 0755, true);
|
||||||
|
echo "✅ 用户目录已创建: $docDir\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "✅ 用户 '$username' 添加成功!\n";
|
||||||
|
echo " 用户名: $username\n";
|
||||||
|
echo " 密码: $password\n";
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
echo "数据库错误: " . $e->getMessage() . "\n";
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
+57
@@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
// 知识库网页系统 - 配置文件
|
||||||
|
// 部署时请修改以下配置
|
||||||
|
|
||||||
|
// 数据库配置
|
||||||
|
define('DB_HOST', 'localhost');
|
||||||
|
define('DB_NAME', 'kb_web');
|
||||||
|
define('DB_USER', 'root');
|
||||||
|
define('DB_PASS', 'your_password');
|
||||||
|
|
||||||
|
// Session 配置
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
// 数据库连接
|
||||||
|
function getDB() {
|
||||||
|
static $pdo = null;
|
||||||
|
if ($pdo === null) {
|
||||||
|
try {
|
||||||
|
$pdo = new PDO(
|
||||||
|
"mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8mb4",
|
||||||
|
DB_USER,
|
||||||
|
DB_PASS,
|
||||||
|
[
|
||||||
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||||
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
|
||||||
|
]
|
||||||
|
);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
die("数据库连接失败: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $pdo;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查登录状态
|
||||||
|
function checkLogin() {
|
||||||
|
if (!isset($_SESSION['user_id'])) {
|
||||||
|
header('Location: index.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前用户信息
|
||||||
|
function getCurrentUser() {
|
||||||
|
if (!isset($_SESSION['user_id'])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$db = getDB();
|
||||||
|
$stmt = $db->prepare("SELECT * FROM users WHERE id = ?");
|
||||||
|
$stmt->execute([$_SESSION['user_id']]);
|
||||||
|
return $stmt->fetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取用户文档目录
|
||||||
|
function getUserDocDir($username) {
|
||||||
|
return __DIR__ . '/' . $username;
|
||||||
|
}
|
||||||
+153
@@ -0,0 +1,153 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'config.php';
|
||||||
|
checkLogin();
|
||||||
|
|
||||||
|
$user = getCurrentUser();
|
||||||
|
$username = $user['username'];
|
||||||
|
$docDir = getUserDocDir($username);
|
||||||
|
|
||||||
|
// 搜索关键词
|
||||||
|
$search = trim($_GET['q'] ?? '');
|
||||||
|
|
||||||
|
// 递归获取文档列表
|
||||||
|
$documents = [];
|
||||||
|
|
||||||
|
function scanDirectory($dir, $baseDir, $search) {
|
||||||
|
$docs = [];
|
||||||
|
if (!is_dir($dir)) return $docs;
|
||||||
|
|
||||||
|
$items = scandir($dir);
|
||||||
|
foreach ($items as $item) {
|
||||||
|
if ($item === '.' || $item === '..') continue;
|
||||||
|
|
||||||
|
$fullPath = $dir . '/' . $item;
|
||||||
|
$relativePath = str_replace($baseDir . '/', '', $fullPath);
|
||||||
|
|
||||||
|
if (is_dir($fullPath)) {
|
||||||
|
// 递归扫描子目录
|
||||||
|
$docs = array_merge($docs, scanDirectory($fullPath, $baseDir, $search));
|
||||||
|
} elseif (is_file($fullPath)) {
|
||||||
|
// 只处理 .md 和 .txt 文件
|
||||||
|
$ext = strtolower(pathinfo($item, PATHINFO_EXTENSION));
|
||||||
|
if (!in_array($ext, ['md', 'txt', 'markdown'])) continue;
|
||||||
|
|
||||||
|
$content = file_get_contents($fullPath);
|
||||||
|
|
||||||
|
// 搜索过滤
|
||||||
|
if ($search) {
|
||||||
|
$searchLower = mb_strtolower($search);
|
||||||
|
$titleLower = mb_strtolower($item);
|
||||||
|
$contentLower = mb_strtolower($content);
|
||||||
|
if (strpos($titleLower, $searchLower) === false &&
|
||||||
|
strpos($contentLower, $searchLower) === false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$docs[] = [
|
||||||
|
'name' => $item,
|
||||||
|
'path' => $relativePath,
|
||||||
|
'folder' => dirname($relativePath) === '.' ? '' : dirname($relativePath),
|
||||||
|
'size' => filesize($fullPath),
|
||||||
|
'mtime' => filemtime($fullPath),
|
||||||
|
'preview' => mb_substr(strip_tags($content), 0, 200)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $docs;
|
||||||
|
}
|
||||||
|
|
||||||
|
$documents = scanDirectory($docDir, $docDir, $search);
|
||||||
|
|
||||||
|
// 按文件夹分组,然后按修改时间排序
|
||||||
|
usort($documents, function($a, $b) {
|
||||||
|
// 先按文件夹排序
|
||||||
|
if ($a['folder'] !== $b['folder']) {
|
||||||
|
return strcmp($a['folder'], $b['folder']);
|
||||||
|
}
|
||||||
|
// 同一文件夹按修改时间排序(新文件在前)
|
||||||
|
return $b['mtime'] - $a['mtime'];
|
||||||
|
});
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>知识库 - <?php echo htmlspecialchars($username); ?></title>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="dashboard">
|
||||||
|
<header class="header">
|
||||||
|
<h1>📚 我的知识库</h1>
|
||||||
|
<div class="user-info">
|
||||||
|
<span><?php echo htmlspecialchars($username); ?></span>
|
||||||
|
<a href="logout.php" class="btn-logout">退出</a>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="search-bar">
|
||||||
|
<form method="GET" action="">
|
||||||
|
<input type="text" name="q" placeholder="搜索文档..."
|
||||||
|
value="<?php echo htmlspecialchars($search); ?>">
|
||||||
|
<button type="submit">搜索</button>
|
||||||
|
<?php if ($search): ?>
|
||||||
|
<a href="dashboard.php" class="btn-clear">清除</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stats">
|
||||||
|
共 <?php echo count($documents); ?> 个文档
|
||||||
|
<?php if ($search): ?>
|
||||||
|
<span class="search-hint">(搜索: "<?php echo htmlspecialchars($search); ?>")</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="document-list">
|
||||||
|
<?php if (empty($documents)): ?>
|
||||||
|
<div class="empty">
|
||||||
|
<?php if ($search): ?>
|
||||||
|
没有找到匹配的文档
|
||||||
|
<?php else: ?>
|
||||||
|
暂无文档
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php
|
||||||
|
$currentFolder = '';
|
||||||
|
foreach ($documents as $doc):
|
||||||
|
// 显示文件夹分隔
|
||||||
|
if ($doc['folder'] !== $currentFolder):
|
||||||
|
$currentFolder = $doc['folder'];
|
||||||
|
if ($currentFolder):
|
||||||
|
?>
|
||||||
|
<div class="folder-separator">
|
||||||
|
📁 <?php echo htmlspecialchars($currentFolder); ?>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
endif;
|
||||||
|
endif;
|
||||||
|
?>
|
||||||
|
<a href="view.php?file=<?php echo urlencode($doc['path']); ?><?php echo $search ? '&q=' . urlencode($search) : ''; ?>"
|
||||||
|
class="document-card">
|
||||||
|
<div class="doc-icon">📄</div>
|
||||||
|
<div class="doc-info">
|
||||||
|
<div class="doc-name"><?php echo htmlspecialchars($doc['name']); ?></div>
|
||||||
|
<div class="doc-meta">
|
||||||
|
<?php echo round($doc['size'] / 1024, 1); ?> KB ·
|
||||||
|
<?php echo date('Y-m-d H:i', $doc['mtime']); ?>
|
||||||
|
<?php if ($doc['folder']): ?>
|
||||||
|
· 📁 <?php echo htmlspecialchars($doc['folder']); ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<div class="doc-preview"><?php echo htmlspecialchars($doc['preview']); ?>...</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# 这是一个示例文档
|
||||||
|
|
||||||
|
这是您的知识库示例文档。
|
||||||
|
|
||||||
|
## 主要内容
|
||||||
|
|
||||||
|
- 支持搜索功能
|
||||||
|
- 支持 Markdown 格式
|
||||||
|
- 可以查看文档预览
|
||||||
|
|
||||||
|
## 代码示例
|
||||||
|
|
||||||
|
```python
|
||||||
|
def hello():
|
||||||
|
print("Hello, Knowledge Base!")
|
||||||
|
```
|
||||||
|
|
||||||
|
## 链接
|
||||||
|
|
||||||
|
这是一个 [示例链接](https://example.com)。
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'config.php';
|
||||||
|
|
||||||
|
// 已登录则跳转到主页
|
||||||
|
if (isset($_SESSION['user_id'])) {
|
||||||
|
header('Location: dashboard.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$error = '';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$username = trim($_POST['username'] ?? '');
|
||||||
|
$password = $_POST['password'] ?? '';
|
||||||
|
|
||||||
|
if (empty($username) || empty($password)) {
|
||||||
|
$error = '请输入用户名和密码';
|
||||||
|
} else {
|
||||||
|
$db = getDB();
|
||||||
|
$stmt = $db->prepare("SELECT * FROM users WHERE username = ?");
|
||||||
|
$stmt->execute([$username]);
|
||||||
|
$user = $stmt->fetch();
|
||||||
|
|
||||||
|
if ($user && password_verify($password, $user['password_hash'])) {
|
||||||
|
$_SESSION['user_id'] = $user['id'];
|
||||||
|
$_SESSION['username'] = $user['username'];
|
||||||
|
header('Location: dashboard.php');
|
||||||
|
exit;
|
||||||
|
} else {
|
||||||
|
$error = '用户名或密码错误';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>知识库 - 登录</title>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="login-container">
|
||||||
|
<div class="login-box">
|
||||||
|
<h1>📚 知识库</h1>
|
||||||
|
<?php if ($error): ?>
|
||||||
|
<div class="error"><?php echo htmlspecialchars($error); ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<form method="POST" action="">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="username">用户名</label>
|
||||||
|
<input type="text" id="username" name="username" required autofocus>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password">密码</label>
|
||||||
|
<input type="password" id="password" name="password" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn-primary">登录</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
-- 知识库网页系统 - 数据库初始化脚本
|
||||||
|
-- 运行方式: mysql -u root -p kb_web < init.sql
|
||||||
|
|
||||||
|
-- 创建数据库(如果不存在)
|
||||||
|
CREATE DATABASE IF NOT EXISTS kb_web DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
USE kb_web;
|
||||||
|
|
||||||
|
-- 用户表
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
username VARCHAR(50) NOT NULL UNIQUE,
|
||||||
|
password_hash VARCHAR(255) NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
INDEX idx_username (username)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- 添加用户示例
|
||||||
|
-- ============================================
|
||||||
|
-- 密码使用 PHP password_hash() 生成
|
||||||
|
-- PHP 生成密码: echo password_hash('your_password', PASSWORD_DEFAULT);
|
||||||
|
-- ============================================
|
||||||
|
|
||||||
|
-- 示例:添加用户 demo,密码为 demo123
|
||||||
|
-- 密码哈希值需要用 PHP 生成,以下是示例格式:
|
||||||
|
-- INSERT INTO users (username, password_hash) VALUES
|
||||||
|
-- ('demo', '$2y$10$...');
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- 管理员添加用户的 PHP 脚本 (add_user.php)
|
||||||
|
-- 可以单独部署在管理目录中
|
||||||
|
-- ============================================
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'config.php';
|
||||||
|
|
||||||
|
// 清除 session
|
||||||
|
session_destroy();
|
||||||
|
|
||||||
|
// 跳转到登录页
|
||||||
|
header('Location: index.php');
|
||||||
|
exit;
|
||||||
@@ -0,0 +1,402 @@
|
|||||||
|
/* 知识库网页系统 - 样式 */
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||||
|
background: #f5f7fa;
|
||||||
|
color: #333;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 登录页 */
|
||||||
|
.login-container {
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-box {
|
||||||
|
background: white;
|
||||||
|
padding: 40px;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-box h1 {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border: 2px solid #e1e5eb;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
width: 100%;
|
||||||
|
padding: 14px;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.2s, box-shadow 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
background: #fee;
|
||||||
|
color: #c00;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 主页 */
|
||||||
|
.dashboard {
|
||||||
|
max-width: 1000px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
border-bottom: 2px solid #e1e5eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h1 {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info span {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-logout {
|
||||||
|
padding: 8px 16px;
|
||||||
|
background: #f0f0f0;
|
||||||
|
color: #666;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-logout:hover {
|
||||||
|
background: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 搜索栏 */
|
||||||
|
.search-bar {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-bar form {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-bar input {
|
||||||
|
flex: 1;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border: 2px solid #e1e5eb;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-bar input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-bar button {
|
||||||
|
padding: 12px 24px;
|
||||||
|
background: #667eea;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-bar button:hover {
|
||||||
|
background: #5a6fd6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-clear {
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: #f0f0f0;
|
||||||
|
color: #666;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats {
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-hint {
|
||||||
|
color: #999;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 文档列表 */
|
||||||
|
.document-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-card {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 20px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
transition: transform 0.2s, box-shadow 0.2s;
|
||||||
|
border: 1px solid #e1e5eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-card:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-icon {
|
||||||
|
font-size: 32px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-name {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-meta {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #999;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-preview {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty {
|
||||||
|
text-align: center;
|
||||||
|
padding: 60px 20px;
|
||||||
|
color: #999;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 文档查看页 */
|
||||||
|
.view-page {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-page .header {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-info {
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-back {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 8px 16px;
|
||||||
|
background: #f0f0f0;
|
||||||
|
color: #666;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-back:hover {
|
||||||
|
background: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-content {
|
||||||
|
background: white;
|
||||||
|
padding: 40px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid #e1e5eb;
|
||||||
|
line-height: 1.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-content h1 {
|
||||||
|
font-size: 28px;
|
||||||
|
margin: 24px 0 16px 0;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
border-bottom: 2px solid #e1e5eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-content h1:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-content h2 {
|
||||||
|
font-size: 22px;
|
||||||
|
margin: 20px 0 12px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-content h3 {
|
||||||
|
font-size: 18px;
|
||||||
|
margin: 16px 0 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-content p {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-content ul {
|
||||||
|
margin: 16px 0;
|
||||||
|
padding-left: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-content li {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-content code {
|
||||||
|
background: #f5f5f5;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: "SF Mono", Monaco, "Courier New", monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-content pre {
|
||||||
|
background: #2d2d2d;
|
||||||
|
color: #f8f8f2;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow-x: auto;
|
||||||
|
margin: 16px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-content pre code {
|
||||||
|
background: none;
|
||||||
|
padding: 0;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-content a {
|
||||||
|
color: #667eea;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-content a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 搜索高亮 */
|
||||||
|
mark {
|
||||||
|
background: #fff3cd;
|
||||||
|
padding: 1px 4px;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 文件夹分隔符 */
|
||||||
|
.folder-separator {
|
||||||
|
padding: 16px 20px;
|
||||||
|
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||||
|
border-left: 4px solid #667eea;
|
||||||
|
margin: 16px 0 8px 0;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #495057;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-separator:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 文档查看页 */
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.login-box {
|
||||||
|
margin: 20px;
|
||||||
|
padding: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-card {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-icon {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-content {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'config.php';
|
||||||
|
checkLogin();
|
||||||
|
|
||||||
|
$user = getCurrentUser();
|
||||||
|
$username = $user['username'];
|
||||||
|
$docDir = getUserDocDir($username);
|
||||||
|
|
||||||
|
// 获取文件名
|
||||||
|
$file = $_GET['file'] ?? '';
|
||||||
|
$search = $_GET['q'] ?? '';
|
||||||
|
|
||||||
|
// 安全检查:防止目录遍历攻击(允许子目录,但不能跳出用户目录)
|
||||||
|
if (empty($file) || strpos($file, '..') !== false) {
|
||||||
|
die('无效的文件名');
|
||||||
|
}
|
||||||
|
|
||||||
|
$filePath = $docDir . '/' . $file;
|
||||||
|
|
||||||
|
// 确保文件在用户目录内
|
||||||
|
$realPath = realpath($filePath);
|
||||||
|
$realDocDir = realpath($docDir);
|
||||||
|
if ($realPath === false || strpos($realPath, $realDocDir) !== 0) {
|
||||||
|
die('文件不存在或无权访问');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!file_exists($filePath)) {
|
||||||
|
die('文件不存在');
|
||||||
|
}
|
||||||
|
|
||||||
|
$content = file_get_contents($filePath);
|
||||||
|
|
||||||
|
// 简单的 Markdown 转换
|
||||||
|
function markdownToHtml($text) {
|
||||||
|
// 标题
|
||||||
|
$text = preg_replace('/^### (.*$)/m', '<h3>$1</h3>', $text);
|
||||||
|
$text = preg_replace('/^## (.*$)/m', '<h2>$1</h2>', $text);
|
||||||
|
$text = preg_replace('/^# (.*$)/m', '<h1>$1</h1>', $text);
|
||||||
|
|
||||||
|
// 粗体和斜体
|
||||||
|
$text = preg_replace('/\*\*(.+?)\*\*/', '<strong>$1</strong>', $text);
|
||||||
|
$text = preg_replace('/\*(.+?)\*/', '<em>$1</em>', $text);
|
||||||
|
|
||||||
|
// 代码块
|
||||||
|
$text = preg_replace('/```(\w*)\n(.*?)```/s', '<pre><code class="language-$1">$2</code></pre>', $text);
|
||||||
|
$text = preg_replace('/`(.+?)`/', '<code>$1</code>', $text);
|
||||||
|
|
||||||
|
// 链接
|
||||||
|
$text = preg_replace('/\[(.+?)\]\((.+?)\)/', '<a href="$2" target="_blank">$1</a>', $text);
|
||||||
|
|
||||||
|
// 列表
|
||||||
|
$text = preg_replace('/^- (.*$)/m', '<li>$1</li>', $text);
|
||||||
|
$text = preg_replace('/(<li>.*<\/li>\n?)+/', '<ul>$0</ul>', $text);
|
||||||
|
|
||||||
|
// 段落
|
||||||
|
$text = preg_replace('/\n\n/', '</p><p>', $text);
|
||||||
|
$text = '<p>' . $text . '</p>';
|
||||||
|
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 高亮搜索词
|
||||||
|
function highlightSearch($text, $search) {
|
||||||
|
if (empty($search)) return $text;
|
||||||
|
return preg_replace('/(' . preg_quote($search, '/') . ')/i',
|
||||||
|
'<mark>$1</mark>', $text);
|
||||||
|
}
|
||||||
|
|
||||||
|
$htmlContent = markdownToHtml(htmlspecialchars($content));
|
||||||
|
if ($search) {
|
||||||
|
$htmlContent = highlightSearch($htmlContent, $search);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取文件名(不含路径)
|
||||||
|
$displayName = basename($file);
|
||||||
|
$folder = dirname($file) !== '.' ? dirname($file) : '';
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title><?php echo htmlspecialchars($displayName); ?> - 知识库</title>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="view-page">
|
||||||
|
<header class="header">
|
||||||
|
<a href="dashboard.php<?php echo $search ? '?q=' . urlencode($search) : ''; ?>" class="btn-back">
|
||||||
|
← 返回列表
|
||||||
|
</a>
|
||||||
|
<h1><?php echo htmlspecialchars($displayName); ?></h1>
|
||||||
|
<?php if ($folder): ?>
|
||||||
|
<div class="folder-info">📁 <?php echo htmlspecialchars($folder); ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="document-content">
|
||||||
|
<?php echo $htmlContent; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user