From 0390933ed6c6bb39a3621532d79f1b096ed7ad66 Mon Sep 17 00:00:00 2001
From: XIGE <710062962@qq.com>
Date: Mon, 23 Feb 2026 19:51:00 +0800
Subject: [PATCH] 1.0
---
Plugin.php | 1335 ++++++++++++++++++++++++++++++++++++++++++++++++++
使用说明.md | 396 +++++++++++++++
插件设置.txt | 73 +++
3 files changed, 1804 insertions(+)
create mode 100644 Plugin.php
create mode 100644 使用说明.md
create mode 100644 插件设置.txt
diff --git a/Plugin.php b/Plugin.php
new file mode 100644
index 0000000..9158908
--- /dev/null
+++ b/Plugin.php
@@ -0,0 +1,1335 @@
+ '偶遇', 'min_comments' => 0, 'class' => 'vip1', 'color' => '#c0c0c0'),
+ array('name' => '同程', 'min_comments' => 1, 'class' => 'vip2', 'color' => '#a0a0a0'),
+ array('name' => '涉溪', 'min_comments' => 20, 'class' => 'vip3', 'color' => '#9e7a5d'),
+ array('name' => '穿林', 'min_comments' => 60, 'class' => 'vip4', 'color' => '#b87333'),
+ array('name' => '览峰', 'min_comments' => 150, 'class' => 'vip5', 'color' => '#cb6d1e'),
+ array('name' => '渡川', 'min_comments' => 300, 'class' => 'vip6', 'color' => '#cc8400'),
+ array('name' => '聆泉', 'min_comments' => 600, 'class' => 'vip7', 'color' => '#d4af37'),
+ array('name' => '沐霞', 'min_comments' => 1200, 'class' => 'vip8', 'color' => '#ffb800'),
+ array('name' => '共云', 'min_comments' => 2400, 'class' => 'vip9', 'color' => '#ffa500'),
+ array('name' => '印雪', 'min_comments' => 4800, 'class' => 'vip10', 'color' => '#ff8c00'),
+ array('name' => '望星', 'min_comments' => 9600, 'class' => 'vip11', 'color' => '#da70d6'),
+ array('name' => '归真', 'min_comments' => 19200, 'class' => 'vip12', 'color' => '#a42be2'),
+ );
+
+ /**
+ * 楼层名称默认配置
+ */
+ private static $defaultFloorNames = array('沙发', '板凳', '地板');
+
+ /**
+ * 子楼层名称默认配置
+ */
+ private static $defaultSubFloorNames = array('B1', 'B2', 'B3');
+
+ /**
+ * 激活插件
+ */
+ public static function activate()
+ {
+ // 前端样式
+ Typecho_Plugin::factory('Widget_Archive')->header = array('RecentlyActive_Plugin', 'outputHeader');
+
+ // 在评论列表添加用户等级
+ Typecho_Plugin::factory('Widget_Comments_Archive')->contentEx = array('RecentlyActive_Plugin', 'addUserLevelToComment');
+
+ // 在评论列表添加楼层显示
+ Typecho_Plugin::factory('Widget_Comments_Archive')->contentEx = array('RecentlyActive_Plugin', 'addFloorNumberToComment');
+
+ return _t('插件已激活,使用 RecentlyActive_Plugin::show() 显示用户活跃时间');
+ }
+
+ /**
+ * 禁用插件
+ */
+ public static function deactivate()
+ {
+ return _t('插件已禁用');
+ }
+
+ /**
+ * 插件配置面板
+ */
+ public static function config(Typecho_Widget_Helper_Form $form)
+ {
+ // 显示模式
+ $displayMode = new Typecho_Widget_Helper_Form_Element_Radio('display_mode',
+ array(
+ 'relative' => '相对时间(3小时前)',
+ 'absolute' => '绝对时间(2023-01-01 12:00)',
+ 'smart' => '智能模式(1天内用相对时间,更早用绝对时间)'
+ ),
+ 'smart',
+ _t('时间显示模式'));
+ $form->addInput($displayMode);
+
+ // 时间格式
+ $dateFormat = new Typecho_Widget_Helper_Form_Element_Text('date_format',
+ NULL,
+ 'Y-m-d H:i',
+ _t('时间格式'),
+ _t('绝对时间格式,如:Y-m-d H:i'));
+ $form->addInput($dateFormat);
+
+ // 在线状态阈值(分钟)
+ $onlineThreshold = new Typecho_Widget_Helper_Form_Element_Text('online_threshold',
+ NULL,
+ '10',
+ _t('在线状态阈值(分钟)'),
+ _t('多少分钟内显示为"在线"状态'));
+ $form->addInput($onlineThreshold->addRule('isInteger', _t('必须是整数')));
+
+ // 是否显示状态点
+ $showDot = new Typecho_Widget_Helper_Form_Element_Radio('show_dot',
+ array(
+ '1' => '显示状态点 ●',
+ '0' => '不显示状态点'
+ ),
+ '1',
+ _t('状态点显示'));
+ $form->addInput($showDot);
+
+ // 默认文字
+ $defaultText = new Typecho_Widget_Helper_Form_Element_Text('default_text',
+ NULL,
+ '从未活跃',
+ _t('默认显示文字'),
+ _t('当用户从未活跃时显示的文字'));
+ $form->addInput($defaultText);
+
+ // 楼层显示配置区域
+ echo '
楼层显示配置
';
+
+ // 启用楼层显示
+ $enableFloor = new Typecho_Widget_Helper_Form_Element_Radio('enable_floor',
+ array(
+ '1' => '启用',
+ '0' => '禁用'
+ ),
+ '1',
+ _t('启用楼层显示'));
+ $form->addInput($enableFloor);
+
+ // 前三楼名称
+ $floorNames = new Typecho_Widget_Helper_Form_Element_Text('floor_names',
+ NULL,
+ '沙发,板凳,地板',
+ _t('父评论前三楼名称'),
+ _t('父评论前三楼显示的名称,用英文逗号分隔,例如:沙发,板凳,地板'));
+ $form->addInput($floorNames);
+
+ // 父评论显示格式
+ $parentFloorFormat = new Typecho_Widget_Helper_Form_Element_Text('parent_floor_format',
+ NULL,
+ '#楼层',
+ _t('父评论楼层显示格式'),
+ _t('父评论楼层显示格式,#楼层 会被替换为实际楼层,例如:#楼层楼 会显示为 1楼'));
+ $form->addInput($parentFloorFormat);
+
+ // 新增:子评论前三楼名称
+ $subFloorNames = new Typecho_Widget_Helper_Form_Element_Text('sub_floor_names',
+ NULL,
+ 'B1,B2,B3',
+ _t('子评论前三楼名称'),
+ _t('子评论前三楼显示的名称,用英文逗号分隔,例如:B1,B2,B3'));
+ $form->addInput($subFloorNames);
+
+ // 新增:子评论显示格式
+ $subFloorFormat = new Typecho_Widget_Helper_Form_Element_Text('sub_floor_format',
+ NULL,
+ 'B#楼层',
+ _t('子评论楼层显示格式'),
+ _t('子评论楼层显示格式,#楼层 会被替换为实际楼层,例如:B#楼层 会显示为 B1'));
+ $form->addInput($subFloorFormat);
+
+ // 用户等级配置区域
+ echo '用户等级配置
';
+
+ // 启用用户等级显示
+ $enableUserLevel = new Typecho_Widget_Helper_Form_Element_Radio('enable_user_level',
+ array(
+ '1' => '启用',
+ '0' => '禁用'
+ ),
+ '1',
+ _t('启用用户等级显示'));
+ $form->addInput($enableUserLevel);
+
+ // 管理员标识
+ $adminLabel = new Typecho_Widget_Helper_Form_Element_Text('admin_label',
+ NULL,
+ '博主',
+ _t('管理员标识'),
+ _t('管理员在评论中显示的文字'));
+ $form->addInput($adminLabel);
+
+ // 等级配置说明
+ echo '等级配置格式:等级名称|所需评论数|CSS类名|颜色值
例如:偶遇|0|vip1|#c0c0c0
每行一个等级,按评论数升序排列
';
+
+ // 等级配置
+ $levelConfig = new Typecho_Widget_Helper_Form_Element_Textarea('level_config',
+ NULL,
+ self::getDefaultLevelConfig(),
+ _t('等级配置'),
+ _t('每行一个等级:等级名称|所需评论数|CSS类名|颜色值'));
+ $form->addInput($levelConfig);
+
+ // 图标字体CSS
+ $iconFontCss = new Typecho_Widget_Helper_Form_Element_Textarea('iconfont_css',
+ NULL,
+ self::getDefaultIconFontCss(),
+ _t('图标字体CSS'),
+ _t('用户等级图标的CSS样式'));
+ $form->addInput($iconFontCss);
+ }
+
+ /**
+ * 获取默认等级配置文本
+ */
+ private static function getDefaultLevelConfig()
+ {
+ $lines = array();
+ foreach (self::$defaultLevels as $level) {
+ $lines[] = "{$level['name']}|{$level['min_comments']}|{$level['class']}|{$level['color']}";
+ }
+ return implode("\n", $lines);
+ }
+
+ /**
+ * 获取默认图标字体CSS - 更新为提供的CSS
+ */
+ private static function getDefaultIconFontCss()
+ {
+ $css = '.vipicon {
+ font-family: "FontAwesome", "iconfont";
+ font-style: normal;
+ font-weight: normal;
+ speak: none;
+ display: inline-block;
+ text-decoration: inherit;
+ text-align: center;
+ font-variant: normal;
+ text-transform: none;
+ line-height: 1em;
+}
+.vipicon:before {
+ content: "\e66a";
+ font-size: 14px;
+}
+.com-level { display: inline-block; margin-left: 3px; }
+.com-level sub {
+ font-size: 11px;
+ vertical-align: baseline;
+ position: relative;
+ top: -0.5em;
+ font-weight: bold;
+}
+/* 等级颜色定义 - 同时应用到图标和数字 */
+.com-level.vip1 .vipicon,
+.com-level.vip1 sub { color: #999; }
+.com-level.vip2 .vipicon,
+.com-level.vip2 sub { color: #8c8c8c; }
+.com-level.vip3 .vipicon,
+.com-level.vip3 sub { color: #666; }
+.com-level.vip4 .vipicon,
+.com-level.vip4 sub { color: #52c41a; }
+.com-level.vip5 .vipicon,
+.com-level.vip5 sub { color: #1890ff; }
+.com-level.vip6 .vipicon,
+.com-level.vip6 sub { color: #722ed1; }
+.com-level.vip7 .vipicon,
+.com-level.vip7 sub { color: #faad14; }
+.com-level.vip8 .vipicon,
+.com-level.vip8 sub { color: #f5222d; }
+.com-level.vip9 .vipicon,
+.com-level.vip9 sub { color: #eb2f96; }
+.com-level.vip10 .vipicon,
+.com-level.vip10 sub { color: #fa541c; }
+.com-level.vip11 .vipicon,
+.com-level.vip11 sub { color: #13c2c2; }
+.com-level.vip12 .vipicon,
+.com-level.vip12 sub { color: #000; }
+/* 动态CSS - 确保用户自定义的颜色通过内联样式生效 */
+.com-level .vipicon,
+.com-level sub {
+ color: inherit !important;
+}
+/* 博主颜色 */
+.com-level.blogger .vipicon,
+.com-level.blogger sub { color: #f5222d !important; }';
+
+ return $css;
+ }
+
+ /**
+ * 个人用户配置(不需要)
+ */
+ public static function personalConfig(Typecho_Widget_Helper_Form $form) {}
+
+ /**
+ * 输出前端样式
+ */
+ public static function outputHeader()
+ {
+ try {
+ $options = Typecho_Widget::widget('Widget_Options')->plugin('RecentlyActive');
+ $enableUserLevel = isset($options->enable_user_level) ? $options->enable_user_level : '1';
+ } catch (Exception $e) {
+ $enableUserLevel = '1';
+ }
+
+ // 基础CSS(总是输出,用于活跃时间显示)
+ $css = <<
+.recently-active {
+ display: inline-block;
+ font-size: 15px;
+ color: #666;
+ margin-left: 5px;
+}
+.recently-active.online {
+ color: #52c41a;
+}
+.recently-active.online:before {
+ font-size: 13px;
+ vertical-align: middle;
+}
+.recently-active.online.no-dot:before {
+ content: "";
+}
+.recently-active.offline {
+ color: #999;
+}
+.recently-active.offline:before {
+ font-size: 10px;
+}
+.recently-active.offline.no-dot:before {
+ content: "";
+}
+.recently-active-tooltip {
+ cursor: help;
+}
+
+CSS;
+
+ // 只有启用等级显示时才输出等级CSS
+ if ($enableUserLevel == '1') {
+ try {
+ $iconFontCss = isset($options->iconfont_css) ? $options->iconfont_css : self::getDefaultIconFontCss();
+ $css .= "";
+ } catch (Exception $e) {
+ $css .= "";
+ }
+ }
+
+ echo $css;
+ }
+
+ /**
+ * 添加楼层号到评论 - 通过钩子自动处理
+ */
+ public static function addFloorNumberToComment($content, $widget, $lastResult)
+ {
+ $content = empty($lastResult) ? $content : $lastResult;
+
+ try {
+ // 获取插件配置
+ $options = Typecho_Widget::widget('Widget_Options')->plugin('RecentlyActive');
+ $enableFloor = isset($options->enable_floor) ? $options->enable_floor : '1';
+
+ // 如果禁用楼层显示,直接返回原内容
+ if ($enableFloor == '0') {
+ return $content;
+ }
+
+ // 判断是父评论还是子评论
+ $isParentComment = ($widget->parent == 0);
+
+ if ($isParentComment) {
+ // 父评论:统计全部父评论的楼层
+ $floorNumber = self::getParentCommentFloorNumber($widget->coid, $widget->cid);
+ $floorHtml = self::generateParentFloorHtml($floorNumber, $options);
+ return $floorHtml . $content;
+ } else {
+ // 子评论:使用新的计数逻辑
+ $subFloorNumber = self::getSubCommentFloorNumberCorrect($widget->coid, $widget->parent, $widget->cid);
+ $floorHtml = self::generateSubFloorHtml($subFloorNumber, $options);
+ return $floorHtml . $content;
+ }
+
+ } catch (Exception $e) {
+ // 出错时返回原内容,确保评论不消失
+ return $content;
+ }
+ }
+
+ /**
+ * 生成父评论楼层HTML
+ */
+ private static function generateParentFloorHtml($floorNumber, $options)
+ {
+ // 获取父评论楼层显示格式
+ $parentFloorFormat = isset($options->parent_floor_format) ? $options->parent_floor_format : '#楼层';
+
+ // 获取父评论楼层名称配置
+ $floorNames = array();
+ if (isset($options->floor_names) && !empty($options->floor_names)) {
+ $floorNames = explode(',', $options->floor_names);
+ }
+
+ // 如果配置为空,使用默认名称
+ if (empty($floorNames)) {
+ $floorNames = self::$defaultFloorNames;
+ }
+
+ // 根据楼层数获取显示文本
+ if ($floorNumber <= count($floorNames) && $floorNumber > 0) {
+ $floorText = $floorNames[$floorNumber - 1];
+ } else {
+ // 使用格式替换
+ $floorText = str_replace('#楼层', $floorNumber, $parentFloorFormat);
+ }
+
+ // 生成楼层HTML
+ return '';
+ }
+
+ /**
+ * 生成子评论楼层HTML
+ */
+ private static function generateSubFloorHtml($subFloorNumber, $options)
+ {
+ // 获取子评论楼层显示格式
+ $subFloorFormat = isset($options->sub_floor_format) ? $options->sub_floor_format : 'B#楼层';
+
+ // 获取子评论楼层名称配置
+ $subFloorNames = array();
+ if (isset($options->sub_floor_names) && !empty($options->sub_floor_names)) {
+ $subFloorNames = explode(',', $options->sub_floor_names);
+ }
+
+ // 如果配置为空,使用默认名称
+ if (empty($subFloorNames)) {
+ $subFloorNames = self::$defaultSubFloorNames;
+ }
+
+ // 根据楼层数获取显示文本
+ if ($subFloorNumber <= count($subFloorNames) && $subFloorNumber > 0) {
+ $floorText = $subFloorNames[$subFloorNumber - 1];
+ } else {
+ // 使用格式替换
+ $floorText = str_replace('#楼层', $subFloorNumber, $subFloorFormat);
+ }
+
+ // 生成楼层HTML
+ return '';
+ }
+
+ /**
+ * 获取父评论的楼层号(统计全部父评论)
+ */
+ private static function getParentCommentFloorNumber($currentCoid, $cid)
+ {
+ try {
+ $db = Typecho_Db::get();
+
+ // 查询所有父评论(parent=0)且属于当前文章,按时间升序排列
+ $query = $db->select('coid')
+ ->from('table.comments')
+ ->where('cid = ?', $cid)
+ ->where('parent = ?', 0)
+ ->where('status = ?', 'approved')
+ ->order('coid', Typecho_Db::SORT_ASC);
+
+ $comments = $db->fetchAll($query);
+
+ // 查找当前评论在数组中的位置(从1开始)
+ foreach ($comments as $index => $comment) {
+ if ($comment['coid'] == $currentCoid) {
+ return $index + 1;
+ }
+ }
+
+ // 如果没找到,返回1
+ return 1;
+
+ } catch (Exception $e) {
+ return 1;
+ }
+ }
+
+ /**
+ * 获取子评论的楼层号 - 正确版本:统计同一父评论下的所有子评论
+ */
+ private static function getSubCommentFloorNumberCorrect($currentCoid, $parentCoid, $cid)
+ {
+ try {
+ $db = Typecho_Db::get();
+
+ // 首先,我们需要找到当前评论的根父评论
+ // 对于嵌套回复,根父评论是最顶层的父评论
+ $rootParentId = self::findRootParent($currentCoid, $parentCoid, $cid);
+
+ // 如果找到的根父评论不是直接父评论,使用根父评论
+ if ($rootParentId != $parentCoid) {
+ $parentCoid = $rootParentId;
+ }
+
+ // 现在,获取该父评论下的所有子评论(按coid排序)
+ $allChildren = self::getAllDirectAndNestedChildren($parentCoid, $cid);
+
+ // 按coid排序所有子评论
+ usort($allChildren, function($a, $b) {
+ return $a['coid'] - $b['coid'];
+ });
+
+ // 查找当前评论在所有子评论中的位置
+ foreach ($allChildren as $index => $comment) {
+ if ($comment['coid'] == $currentCoid) {
+ return $index + 1; // 返回楼层号
+ }
+ }
+
+ // 如果没找到,可能是查询有问题,尝试简单查询
+ $simpleQuery = $db->select('coid')
+ ->from('table.comments')
+ ->where('cid = ?', $cid)
+ ->where('parent = ?', $parentCoid)
+ ->where('status = ?', 'approved')
+ ->order('coid', Typecho_Db::SORT_ASC);
+
+ $directChildren = $db->fetchAll($simpleQuery);
+
+ foreach ($directChildren as $index => $comment) {
+ if ($comment['coid'] == $currentCoid) {
+ return $index + 1;
+ }
+ }
+
+ return 1; // 默认返回1
+
+ } catch (Exception $e) {
+ return 1;
+ }
+ }
+
+ /**
+ * 查找评论的根父评论
+ */
+ private static function findRootParent($currentCoid, $parentCoid, $cid)
+ {
+ try {
+ $db = Typecho_Db::get();
+
+ // 如果当前评论的父评论是0,说明它自己就是父评论
+ if ($parentCoid == 0) {
+ return $currentCoid;
+ }
+
+ // 检查父评论的父评论
+ $currentParentId = $parentCoid;
+ $visited = array($currentCoid); // 防止循环
+
+ for ($i = 0; $i < 20; $i++) { // 最多追踪20层
+ if (in_array($currentParentId, $visited)) {
+ break; // 避免循环
+ }
+
+ $visited[] = $currentParentId;
+
+ $parentComment = $db->fetchRow($db->select('parent')
+ ->from('table.comments')
+ ->where('coid = ?', $currentParentId)
+ ->where('cid = ?', $cid)
+ ->limit(1));
+
+ if (!$parentComment || $parentComment['parent'] == 0) {
+ // 找到了根父评论
+ return $currentParentId;
+ }
+
+ $currentParentId = $parentComment['parent'];
+ }
+
+ // 如果循环结束还没找到,返回直接父评论
+ return $parentCoid;
+
+ } catch (Exception $e) {
+ return $parentCoid;
+ }
+ }
+
+ /**
+ * 获取父评论下的所有子评论(包括嵌套的)
+ */
+ private static function getAllDirectAndNestedChildren($parentCoid, $cid)
+ {
+ $db = Typecho_Db::get();
+ $allChildren = array();
+
+ // 递归获取所有子评论
+ self::collectChildrenRecursively($parentCoid, $cid, $db, $allChildren);
+
+ return $allChildren;
+ }
+
+ /**
+ * 递归收集子评论
+ */
+ private static function collectChildrenRecursively($parentCoid, $cid, $db, &$allChildren)
+ {
+ // 获取当前父评论的直接子评论
+ $children = $db->fetchAll($db->select('coid', 'parent')
+ ->from('table.comments')
+ ->where('cid = ?', $cid)
+ ->where('parent = ?', $parentCoid)
+ ->where('status = ?', 'approved')
+ ->order('coid', Typecho_Db::SORT_ASC));
+
+ foreach ($children as $child) {
+ $allChildren[] = $child;
+ // 递归获取子评论的子评论
+ self::collectChildrenRecursively($child['coid'], $cid, $db, $allChildren);
+ }
+ }
+
+ /**
+ * 添加用户等级到评论
+ */
+ public static function addUserLevelToComment($content, $widget, $lastResult)
+ {
+ $content = empty($lastResult) ? $content : $lastResult;
+
+ try {
+ // 获取插件配置
+ $options = Typecho_Widget::widget('Widget_Options')->plugin('RecentlyActive');
+ $enableUserLevel = isset($options->enable_user_level) ? $options->enable_user_level : '1';
+
+ // 如果禁用等级显示,直接返回原内容
+ if ($enableUserLevel == '0') {
+ return $content;
+ }
+
+ // 获取评论者邮箱
+ $email = $widget->mail;
+
+ // 如果是游客评论,不显示等级
+ if (!$email) {
+ return $content;
+ }
+
+ // 通过邮箱获取用户信息
+ $userInfo = self::getUserInfoByEmail($email);
+
+ // 获取用户组信息
+ $isAdmin = false;
+ if ($userInfo && isset($userInfo['group'])) {
+ // Typecho中管理员用户组是 'administrator'
+ $isAdmin = ($userInfo['group'] == 'administrator');
+ }
+
+ // 如果是管理员,显示博主标识
+ if ($isAdmin) {
+ $adminLabel = isset($options->admin_label) ? $options->admin_label : '';
+ return $content . '';
+ }
+
+ // 获取用户评论数
+ $commentCount = self::getUserCommentCountByEmail($email);
+
+ // 获取用户等级
+ $levelInfo = self::getUserLevel($commentCount, $options);
+
+ // 生成等级HTML
+ $levelHtml = self::generateLevelHtml($levelInfo, $commentCount);
+
+ return $content . $levelHtml;
+
+ } catch (Exception $e) {
+ // 出错时返回原内容,确保评论不消失
+ return $content;
+ }
+ }
+
+ /**
+ * 根据邮箱获取用户信息
+ */
+ private static function getUserInfoByEmail($email)
+ {
+ if (!$email) return null;
+
+ try {
+ $db = Typecho_Db::get();
+ $user = $db->fetchRow($db->select('uid', 'name', 'mail', 'group', 'url')
+ ->from('table.users')
+ ->where('mail = ?', $email)
+ ->limit(1));
+
+ return $user;
+ } catch (Exception $e) {
+ return null;
+ }
+ }
+
+ /**
+ * 根据邮箱获取用户评论数
+ */
+ private static function getUserCommentCountByEmail($email)
+ {
+ if (!$email) return 0;
+
+ try {
+ $db = Typecho_Db::get();
+ $result = $db->fetchAll($db->select(array('COUNT(cid)' => 'commentNum'))
+ ->from('table.comments')
+ ->where('mail = ?', $email));
+
+ return $result && isset($result[0]['commentNum']) ? intval($result[0]['commentNum']) : 0;
+ } catch (Exception $e) {
+ return 0;
+ }
+ }
+
+ /**
+ * 获取用户等级信息
+ */
+ private static function getUserLevel($commentCount, $options)
+ {
+ // 解析等级配置
+ $levels = self::parseLevelConfig($options);
+
+ $currentLevel = null;
+ $nextLevel = null;
+
+ // 查找当前等级
+ for ($i = 0; $i < count($levels); $i++) {
+ if ($commentCount >= $levels[$i]['min_comments']) {
+ $currentLevel = $levels[$i];
+ $currentLevel['index'] = $i + 1;
+
+ // 获取下一等级
+ if (isset($levels[$i + 1])) {
+ $nextLevel = $levels[$i + 1];
+ }
+ } else {
+ break;
+ }
+ }
+
+ // 如果没有找到等级,使用第一个等级
+ if (!$currentLevel && count($levels) > 0) {
+ $currentLevel = $levels[0];
+ $currentLevel['index'] = 1;
+ if (isset($levels[1])) {
+ $nextLevel = $levels[1];
+ }
+ }
+
+ return array(
+ 'current' => $currentLevel,
+ 'next' => $nextLevel,
+ 'comment_count' => $commentCount
+ );
+ }
+
+ /**
+ * 解析等级配置
+ */
+ private static function parseLevelConfig($options)
+ {
+ $levels = array();
+
+ if (isset($options->level_config) && !empty($options->level_config)) {
+ $lines = explode("\n", $options->level_config);
+ foreach ($lines as $line) {
+ $line = trim($line);
+ if (empty($line)) continue;
+
+ $parts = explode('|', $line);
+ if (count($parts) >= 3) {
+ $level = array(
+ 'name' => trim($parts[0]),
+ 'min_comments' => intval(trim($parts[1])),
+ 'class' => trim($parts[2])
+ );
+
+ // 修复:正确处理颜色值,包括空值情况
+ if (count($parts) >= 4) {
+ $color = trim($parts[3]);
+ if (!empty($color)) {
+ $level['color'] = $color;
+ } else {
+ // 如果颜色为空,使用默认等级对应的颜色
+ $level['color'] = self::getDefaultColorByClass($level['class']);
+ }
+ } else {
+ // 如果没有颜色配置,使用默认等级对应的颜色
+ $level['color'] = self::getDefaultColorByClass($level['class']);
+ }
+
+ $levels[] = $level;
+ }
+ }
+ }
+
+ // 如果没有配置,使用默认配置
+ if (empty($levels)) {
+ $levels = self::$defaultLevels;
+ }
+
+ // 按评论数排序
+ usort($levels, function($a, $b) {
+ return $a['min_comments'] - $b['min_comments'];
+ });
+
+ return $levels;
+ }
+
+ /**
+ * 根据等级类名获取默认颜色
+ */
+ private static function getDefaultColorByClass($levelClass)
+ {
+ foreach (self::$defaultLevels as $level) {
+ if ($level['class'] == $levelClass) {
+ return $level['color'];
+ }
+ }
+ return '#666'; // 默认颜色
+ }
+
+ /**
+ * 生成等级HTML - 修复:使用内联样式确保颜色生效
+ */
+ private static function generateLevelHtml($levelInfo, $commentCount)
+ {
+ if (!$levelInfo['current']) {
+ return '';
+ }
+
+ $current = $levelInfo['current'];
+ $next = $levelInfo['next'];
+
+ // 构建title提示文本
+ $title = htmlspecialchars($current['name']);
+ $title .= " · {$commentCount}评";
+
+ if ($next) {
+ $needed = $next['min_comments'] - $commentCount;
+ if ($needed > 0) {
+ $title .= " · 再{$needed}评升至" . htmlspecialchars($next['name']);
+ }
+ }
+
+ // 生成HTML - 使用行内样式确保颜色生效
+ $html = sprintf(
+ '',
+ $current['class'],
+ $title,
+ $current['color']
+ );
+
+ $html .= '';
+ $html .= sprintf('%d', $current['index']);
+ $html .= '';
+
+ return $html;
+ }
+
+ /**
+ * 获取用户活跃时间
+ */
+ public static function getActiveTime($userId)
+ {
+ if (!$userId) return 0;
+
+ try {
+ $db = Typecho_Db::get();
+ $user = $db->fetchRow($db->select('activated')
+ ->from('table.users')
+ ->where('uid = ?', $userId));
+
+ return $user && isset($user['activated']) ? intval($user['activated']) : 0;
+ } catch (Exception $e) {
+ return 0;
+ }
+ }
+
+ /**
+ * 格式化时间显示
+ */
+ public static function formatTime($timestamp, $options = null)
+ {
+ // 如果未提供options,尝试获取插件配置
+ if (!$options) {
+ try {
+ $options = Typecho_Widget::widget('Widget_Options')->plugin('RecentlyActive');
+ } catch (Exception $e) {
+ $options = (object)[
+ 'display_mode' => 'smart',
+ 'date_format' => 'Y-m-d H:i',
+ 'default_text' => '从未活跃'
+ ];
+ }
+ }
+
+ if (!$timestamp || $timestamp == 0) {
+ return isset($options->default_text) ? $options->default_text : '从未活跃';
+ }
+
+ $currentTime = time();
+ $diff = $currentTime - $timestamp;
+
+ $displayMode = isset($options->display_mode) ? $options->display_mode : 'smart';
+
+ // 相对时间模式 - 始终返回相对时间
+ if ($displayMode == 'relative') {
+ return self::getRelativeTime($diff);
+ }
+
+ // 智能模式
+ if ($displayMode == 'smart') {
+ if ($diff < 86400) { // 24小时内
+ return self::getRelativeTime($diff);
+ } else {
+ $format = isset($options->date_format) ? $options->date_format : 'Y-m-d H:i';
+ return date($format, $timestamp);
+ }
+ }
+
+ // 绝对时间模式
+ $format = isset($options->date_format) ? $options->date_format : 'Y-m-d H:i';
+ return date($format, $timestamp);
+ }
+
+ /**
+ * 获取相对时间文本
+ */
+ private static function getRelativeTime($diff)
+ {
+ if ($diff < 60) {
+ return '刚刚';
+ } elseif ($diff < 3600) {
+ $minutes = floor($diff / 60);
+ return $minutes . '分钟前';
+ } elseif ($diff < 86400) {
+ $hours = floor($diff / 3600);
+ return $hours . '小时前';
+ } elseif ($diff < 2592000) { // 30天
+ $days = floor($diff / 86400);
+ return $days . '天前';
+ } elseif ($diff < 31536000) { // 365天
+ $months = floor($diff / 2592000);
+ return $months . '个月前';
+ } else {
+ $years = floor($diff / 31536000);
+ return $years . '年前';
+ }
+ }
+
+ /**
+ * 判断是否在线
+ */
+ private static function isOnline($timestamp, $threshold)
+ {
+ if (!$timestamp) return false;
+
+ $currentTime = time();
+ $diff = $currentTime - $timestamp;
+ $thresholdSeconds = $threshold * 60; // 转换为秒
+
+ return $diff < $thresholdSeconds;
+ }
+
+ /**
+ * 前端显示函数 - 核心方法
+ */
+ public static function show($userId = null, $customOptions = array())
+ {
+ if (!$userId) {
+ $user = Typecho_Widget::widget('Widget_User');
+ $userId = $user->uid;
+ }
+
+ if (!$userId) {
+ return '';
+ }
+
+ $activeTime = self::getActiveTime($userId);
+
+ // 获取插件配置
+ $options = null;
+ try {
+ $optionsObj = Typecho_Widget::widget('Widget_Options');
+ if ($optionsObj) {
+ $options = $optionsObj->plugin('RecentlyActive');
+ }
+ } catch (Exception $e) {
+ // 忽略异常,使用默认配置
+ }
+
+ if (!$options) {
+ $options = (object)[
+ 'display_mode' => 'smart',
+ 'date_format' => 'Y-m-d H:i',
+ 'default_text' => '从未活跃',
+ 'online_threshold' => '10',
+ 'show_dot' => '1'
+ ];
+ }
+
+ // 合并自定义选项
+ if (!empty($customOptions)) {
+ foreach ($customOptions as $key => $value) {
+ $options->$key = $value;
+ }
+ }
+
+ $formattedTime = self::formatTime($activeTime, $options);
+
+ // 构建CSS类
+ $classes = array('recently-active');
+
+ $onlineThreshold = isset($options->online_threshold) ? intval($options->online_threshold) : 10;
+ if (self::isOnline($activeTime, $onlineThreshold)) {
+ $classes[] = 'online';
+ } else {
+ $classes[] = 'offline';
+ }
+
+ // 是否显示状态点
+ $showDot = isset($options->show_dot) ? $options->show_dot : '1';
+ if ($showDot == '0') {
+ $classes[] = 'no-dot';
+ }
+
+ $classStr = implode(' ', $classes);
+
+ // 完整时间用于title提示
+ $fullTime = $activeTime ? date('Y-m-d H:i:s', $activeTime) : '从未活跃';
+
+ return sprintf(
+ '%s',
+ $classStr,
+ htmlspecialchars($fullTime),
+ htmlspecialchars($formattedTime)
+ );
+ }
+
+ /**
+ * 获取最近活跃用户列表(侧边栏小工具)
+ */
+ public static function getActiveUsers($limit = 5)
+ {
+ try {
+ $db = Typecho_Db::get();
+ $users = $db->fetchAll($db->select('uid', 'name', 'mail', 'url', 'activated')
+ ->from('table.users')
+ ->where('activated > 0')
+ ->order('activated', Typecho_Db::SORT_DESC)
+ ->limit($limit));
+
+ $result = array();
+ foreach ($users as $user) {
+ $result[] = array(
+ 'uid' => $user['uid'],
+ 'name' => $user['name'],
+ 'mail' => $user['mail'],
+ 'url' => $user['url'],
+ 'activated' => $user['activated'],
+ 'avatar' => self::getGravatar($user['mail']),
+ 'timeText' => self::formatTime($user['activated'])
+ );
+ }
+
+ return $result;
+ } catch (Exception $e) {
+ return array();
+ }
+ }
+
+ /**
+ * 获取Gravatar头像
+ */
+ private static function getGravatar($email, $size = 40)
+ {
+ $hash = md5(strtolower(trim($email)));
+ return "https://www.gravatar.com/avatar/{$hash}?s={$size}&d=identicon&r=g";
+ }
+
+ /**
+ * 获取用户的等级信息(公共方法,可在主题中使用)
+ */
+ public static function getUserLevelInfo($userId = null)
+ {
+ if (!$userId) {
+ $user = Typecho_Widget::widget('Widget_User');
+ $userId = $user->uid;
+ }
+
+ if (!$userId) {
+ return null;
+ }
+
+ try {
+ $options = Typecho_Widget::widget('Widget_Options')->plugin('RecentlyActive');
+ $commentCount = self::getUserCommentCount($userId);
+ return self::getUserLevel($commentCount, $options);
+ } catch (Exception $e) {
+ return null;
+ }
+ }
+
+ /**
+ * 显示用户等级(公共方法,可在主题中使用)
+ */
+ public static function showUserLevel($userId = null)
+ {
+ if (!$userId) {
+ return '';
+ }
+
+ try {
+ // 获取插件配置
+ $options = Typecho_Widget::widget('Widget_Options')->plugin('RecentlyActive');
+ $enableUserLevel = isset($options->enable_user_level) ? $options->enable_user_level : '1';
+
+ // 如果禁用等级显示,直接返回空
+ if ($enableUserLevel == '0') {
+ return '';
+ }
+
+ // 获取用户信息
+ $userInfo = self::getUserInfoById($userId);
+
+ // 判断是否是管理员
+ $isAdmin = false;
+ if ($userInfo && isset($userInfo['group'])) {
+ $isAdmin = ($userInfo['group'] == 'administrator');
+ }
+
+ // 如果是管理员,显示博主标识
+ if ($isAdmin) {
+ $adminLabel = isset($options->admin_label) ? $options->admin_label : '';
+ return '';
+ }
+
+ // 获取用户评论数
+ $commentCount = self::getUserCommentCount($userId);
+
+ // 获取用户等级
+ $levelInfo = self::getUserLevel($commentCount, $options);
+
+ // 生成等级HTML
+ return self::generateLevelHtml($levelInfo, $commentCount);
+
+ } catch (Exception $e) {
+ // 出错时返回空,不影响页面显示
+ return '';
+ }
+ }
+
+ /**
+ * 根据用户ID获取用户信息
+ */
+ private static function getUserInfoById($userId)
+ {
+ if (!$userId) return null;
+
+ try {
+ $db = Typecho_Db::get();
+ $user = $db->fetchRow($db->select('uid', 'name', 'mail', 'group', 'url')
+ ->from('table.users')
+ ->where('uid = ?', $userId)
+ ->limit(1));
+
+ return $user;
+ } catch (Exception $e) {
+ return null;
+ }
+ }
+
+ /**
+ * 获取用户评论数(通过用户ID)
+ */
+ private static function getUserCommentCount($userId)
+ {
+ if (!$userId) return 0;
+
+ try {
+ $db = Typecho_Db::get();
+ $result = $db->fetchRow($db->select('COUNT(*) as cnt')
+ ->from('table.comments')
+ ->where('authorId = ?', $userId)
+ ->where('status = ?', 'approved'));
+
+ return $result ? intval($result['cnt']) : 0;
+ } catch (Exception $e) {
+ return 0;
+ }
+ }
+
+ /**
+ * 前端调用:显示楼层号(静态方法,可在主题中调用)- 修复版本
+ */
+ public static function showFloorNumber($coid = null, $cid = null, $customOptions = array())
+ {
+ if (!$coid || !$cid) {
+ return '';
+ }
+
+ try {
+ // 获取插件配置
+ $options = null;
+ try {
+ $optionsObj = Typecho_Widget::widget('Widget_Options');
+ if ($optionsObj) {
+ $options = $optionsObj->plugin('RecentlyActive');
+ }
+ } catch (Exception $e) {
+ // 忽略异常,使用默认配置
+ }
+
+ if (!$options) {
+ $options = (object)[
+ 'enable_floor' => '1',
+ 'floor_names' => '沙发,板凳,地板',
+ 'parent_floor_format' => '#楼层',
+ 'sub_floor_names' => 'B1,B2,B3',
+ 'sub_floor_format' => 'B#楼层'
+ ];
+ }
+
+ // 合并自定义选项
+ if (!empty($customOptions)) {
+ foreach ($customOptions as $key => $value) {
+ $options->$key = $value;
+ }
+ }
+
+ $enableFloor = isset($options->enable_floor) ? $options->enable_floor : '1';
+
+ // 如果禁用楼层显示,直接返回空
+ if ($enableFloor == '0') {
+ return '';
+ }
+
+ // 判断评论类型(需要查询数据库获取评论信息)
+ $commentInfo = self::getCommentInfo($coid);
+
+ if (!$commentInfo) {
+ return '';
+ }
+
+ $isParentComment = ($commentInfo['parent'] == 0);
+
+ if ($isParentComment) {
+ // 父评论楼层
+ $floorNumber = self::getParentCommentFloorNumber($coid, $cid);
+ $floorText = self::getParentFloorText($floorNumber, $options);
+ $class = 'parent-floor';
+ $style = 'margin-right: 5px; font-weight: bold; color: #666;';
+ } else {
+ // 子评论楼层 - 使用新的正确计数方法
+ $parentId = $commentInfo['parent'];
+ $subFloorNumber = self::getSubCommentFloorNumberCorrect($coid, $parentId, $cid);
+ $floorText = self::getSubFloorText($subFloorNumber, $options);
+ $class = 'sub-floor';
+ $style = 'margin-right: 5px; font-weight: bold; color: #888;';
+ }
+
+ // 生成楼层HTML
+ return '';
+
+ } catch (Exception $e) {
+ // 出错时返回空
+ return '';
+ }
+ }
+
+ /**
+ * 获取父评论楼层文本
+ */
+ private static function getParentFloorText($floorNumber, $options)
+ {
+ // 获取父评论楼层名称配置
+ $floorNames = array();
+ if (isset($options->floor_names) && !empty($options->floor_names)) {
+ $floorNames = explode(',', $options->floor_names);
+ }
+
+ // 如果配置为空,使用默认名称
+ if (empty($floorNames)) {
+ $floorNames = self::$defaultFloorNames;
+ }
+
+ // 根据楼层数获取显示文本
+ if ($floorNumber <= count($floorNames) && $floorNumber > 0) {
+ return $floorNames[$floorNumber - 1];
+ } else {
+ // 使用格式替换
+ $parentFloorFormat = isset($options->parent_floor_format) ? $options->parent_floor_format : '#楼层';
+ return str_replace('#楼层', $floorNumber, $parentFloorFormat);
+ }
+ }
+
+ /**
+ * 获取子评论楼层文本
+ */
+ private static function getSubFloorText($subFloorNumber, $options)
+ {
+ // 获取子评论楼层名称配置
+ $subFloorNames = array();
+ if (isset($options->sub_floor_names) && !empty($options->sub_floor_names)) {
+ $subFloorNames = explode(',', $options->sub_floor_names);
+ }
+
+ // 如果配置为空,使用默认名称
+ if (empty($subFloorNames)) {
+ $subFloorNames = self::$defaultSubFloorNames;
+ }
+
+ // 根据楼层数获取显示文本
+ if ($subFloorNumber <= count($subFloorNames) && $subFloorNumber > 0) {
+ return $subFloorNames[$subFloorNumber - 1];
+ } else {
+ // 使用格式替换
+ $subFloorFormat = isset($options->sub_floor_format) ? $options->sub_floor_format : 'B#楼层';
+ return str_replace('#楼层', $subFloorNumber, $subFloorFormat);
+ }
+ }
+
+ /**
+ * 获取评论信息
+ */
+ private static function getCommentInfo($coid)
+ {
+ try {
+ $db = Typecho_Db::get();
+ $comment = $db->fetchRow($db->select('coid', 'parent', 'cid')
+ ->from('table.comments')
+ ->where('coid = ?', $coid)
+ ->limit(1));
+
+ return $comment;
+ } catch (Exception $e) {
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/使用说明.md b/使用说明.md
new file mode 100644
index 0000000..63c8334
--- /dev/null
+++ b/使用说明.md
@@ -0,0 +1,396 @@
+Typecho RecentlyActive 插件使用文档
+插件信息
+插件名称: RecentlyActive
+
+功能: 显示用户最近活跃时间
+
+版本: 1.0.0
+
+兼容性: Typecho 1.x
+
+作者: Your Name
+
+网站: https://yourwebsite.com
+
+功能特点
+✅ 直接读取 Typecho 自带的 activated 字段,无需额外数据库操作
+✅ 支持三种时间显示模式:相对时间、绝对时间、智能模式
+✅ 自动判断在线/离线状态
+✅ 显示状态点(可配置开关)
+✅ Tooltip 提示完整时间
+✅ 侧边栏最近活跃用户列表
+✅ 后台全局配置,无需用户单独设置
+✅ 轻量级,不影响性能
+
+安装方法
+方法一:手动安装
+下载插件压缩包
+
+解压得到 RecentlyActive 文件夹
+
+上传到 Typecho 插件目录:/usr/plugins/
+
+确保路径为:/usr/plugins/RecentlyActive/Plugin.php
+
+登录 Typecho 后台,进入"控制台" → "插件"
+
+找到"用户最近活跃时间显示插件",点击"启用"
+
+方法二:服务器安装
+bash
+# 进入插件目录
+cd /path/to/typecho/usr/plugins/
+
+# 创建插件目录
+mkdir RecentlyActive
+
+# 上传 Plugin.php 到该目录
+后台配置
+激活插件后,在插件管理页面点击"设置"进行配置:
+
+基本设置
+配置项 说明 默认值
+时间显示模式 相对时间/绝对时间/智能模式 智能模式
+时间格式 绝对时间显示格式(PHP date()格式) Y-m-d H:i
+在线状态阈值 多少分钟内显示为"在线"状态(分钟) 10
+状态点显示 是否显示在线/离线状态点 开启
+默认显示文字 用户从未活跃时显示的文字 从未活跃
+配置说明
+1. 时间显示模式
+相对时间: 显示"3小时前"、"2天前"等
+
+绝对时间: 显示"2023-01-01 12:00"
+
+智能模式: 24小时内用相对时间,更早用绝对时间
+
+2. 时间格式
+支持所有 PHP date() 函数格式,常用格式:
+
+Y-m-d H:i → 2023-01-01 12:00
+
+m/d H:i → 01/01 12:00
+
+H:i → 12:00
+
+Y年m月d日 H:i → 2023年01月01日 12:00
+
+3. 在线状态阈值
+设置用户多少分钟内活跃算"在线"
+
+例如:设为10,则表示10分钟内活跃的用户显示为在线状态(绿色)
+
+主题调用方法
+基本调用
+显示文章作者活跃时间
+php
+authorId): ?>
+
+ 作者: author(); ?>
+ authorId); ?>
+
+
+显示评论者活跃时间
+php
+comments()->to($comments); ?>
+next()): ?>
+
+
+安全调用(推荐)
+php
+authorId && class_exists('RecentlyActive_Plugin')) {
+ echo RecentlyActive_Plugin::show($this->authorId);
+}
+?>
+侧边栏活跃用户列表
+php
+
+
+
+
+
+
+自定义参数调用
+php
+ 'relative', // 强制使用相对时间
+ 'online_threshold' => 30, // 30分钟内算在线
+ 'show_dot' => 0, // 不显示状态点
+ 'date_format' => 'H:i', // 时间格式:只显示时分
+ 'default_text' => '暂无记录' // 自定义默认文字
+));
+?>
+参数说明
+参数 类型 说明 默认值
+display_mode string 显示模式:relative/absolute/smart 插件设置
+online_threshold int 在线状态阈值(分钟) 插件设置
+show_dot int 是否显示状态点:1显示/0不显示 插件设置
+date_format string 绝对时间格式 插件设置
+default_text string 默认显示文字 插件设置
+CSS 样式定制
+插件自带基本样式,如需自定义可覆盖以下 CSS 类:
+
+css
+/* 活跃时间基础样式 */
+.recently-active {
+ font-size: 12px;
+ color: #666;
+ margin-left: 5px;
+}
+
+/* 在线状态 */
+.recently-active.online {
+ color: #52c41a; /* 绿色 */
+}
+
+/* 离线状态 */
+.recently-active.offline {
+ color: #999; /* 灰色 */
+}
+
+/* 状态点 */
+.recently-active.online:before {
+ content: "● ";
+ font-size: 10px;
+}
+
+.recently-active.offline:before {
+ content: "○ ";
+ font-size: 10px;
+}
+
+/* 不显示状态点 */
+.recently-active.no-dot:before {
+ content: "" !important;
+}
+
+/* 工具提示 */
+.recently-active-tooltip {
+ cursor: help;
+ border-bottom: 1px dotted #ccc;
+}
+
+/* 侧边栏活跃用户列表 */
+.active-users {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+
+.active-users li {
+ padding: 8px 0;
+ border-bottom: 1px solid #eee;
+ display: flex;
+ align-items: center;
+}
+
+.active-users li:last-child {
+ border-bottom: none;
+}
+
+.active-users img {
+ border-radius: 50%;
+ margin-right: 10px;
+}
+
+.active-users .user-name {
+ flex: 1;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.active-users .active-time {
+ font-size: 11px;
+ color: #999;
+}
+显示效果示例
+1. 在线状态
+text
+作者:张三 ● 3分钟前
+(绿色文字,带实心圆点)
+
+2. 离线状态
+text
+作者:李四 ○ 3小时前
+(灰色文字,带空心圆点)
+
+3. 智能模式
+24小时内:2小时前
+
+24小时外:2023-01-01 12:00
+
+4. Tooltip 提示
+鼠标悬停在活跃时间上,显示完整时间:
+
+text
+最后活跃: 2023-12-10 14:30:25
+常见问题
+Q1: 插件激活后没有任何显示?
+A: 请检查:
+
+插件是否已激活
+
+主题中是否正确调用 RecentlyActive_Plugin::show()
+
+用户是否有 activated 字段值
+
+查看网页源代码,确认是否有输出
+
+Q2: 显示"从未活跃"是什么情况?
+A: 表示该用户的 activated 字段为 0 或空,可能是:
+
+用户从未登录过
+
+Typecho 未更新该字段
+
+用户数据异常
+
+Q3: 如何修改在线状态的颜色?
+A: 在主题CSS中添加:
+
+css
+.recently-active.online {
+ color: #ff0000; /* 改为红色 */
+}
+Q4: 侧边栏头像不显示?
+A: Gravatar 可能需要科学上网,可以替换为本地头像:
+
+php
+// 修改 Plugin.php 中的 getGravatar 方法
+private static function getGravatar($email, $size = 40)
+{
+ // 使用本地默认头像
+ return Helper::options()->themeUrl . '/images/default-avatar.png';
+}
+Q5: 时间显示不正确?
+A: 检查服务器时区设置:
+
+Typecho 后台 → 设置 → 时区
+
+服务器 PHP 时区设置
+
+确保时间戳正确
+
+高级用法
+在 functions.php 中添加辅助函数
+php
+// 在主题的 functions.php 中添加
+if (!function_exists('showActiveTime')) {
+ /**
+ * 显示用户活跃时间(简化调用)
+ * @param int $userId 用户ID
+ * @param array $options 自定义选项
+ * @return string
+ */
+ function showActiveTime($userId = null, $options = array())
+ {
+ if (class_exists('RecentlyActive_Plugin')) {
+ return RecentlyActive_Plugin::show($userId, $options);
+ }
+ return '';
+ }
+}
+
+// 使用
+echo showActiveTime($this->authorId);
+获取原始活跃时间戳
+php
+
+判断用户是否在线
+php
+在线';
+ } else {
+ echo '离线';
+ }
+}
+?>
+文件结构
+text
+RecentlyActive/
+├── Plugin.php # 插件主文件
+├── README.md # 说明文档(本文件)
+└── screenshot.png # 插件截图(可选)
+更新日志
+v1.0.0 (2023-12-10)
+✅ 首次发布
+
+✅ 基本活跃时间显示功能
+
+✅ 三种时间显示模式
+
+✅ 在线状态判断
+
+✅ 侧边栏活跃用户列表
+
+✅ 后台配置界面
+
+技术支持
+如有问题,可通过以下方式联系:
+
+在插件发布页面留言
+
+访问作者网站
+
+GitHub Issues(如有)
+
+注意事项
+本插件依赖 Typecho 的 activated 字段,请确保该字段正常工作
+
+部分主题可能需要调整 CSS 样式以适应显示
+
+建议在正式使用前进行测试
+
+定期备份数据库
+
+许可证
+MIT License
+
+祝您使用愉快! 🎉
+
+文档版本:v1.0.0
+最后更新:2023-12-10
+作者:Your Name
\ No newline at end of file
diff --git a/插件设置.txt b/插件设置.txt
new file mode 100644
index 0000000..7ed2b13
--- /dev/null
+++ b/插件设置.txt
@@ -0,0 +1,73 @@
+等级配置
+偶遇|0|vip1|#c0c0c0
+同程|1|vip2|#a0a0a0
+涉溪|20|vip3|#9e7a5d
+穿林|60|vip4|#b87333
+览峰|150|vip5|#cb6d1e
+渡川|300|vip6|#cc8400
+聆泉|600|vip7|#d4af37
+沐霞|1200|vip8|#ffb800
+共云|2400|vip9|#ffa500
+印雪|4800|vip10|#ff8c00
+望星|9600|vip11|#da70d6
+归真|19200|vip12|#a42be2
+
+图标字体CSS
+
+.vipicon {
+ font-family: "FontAwesome", "iconfont";
+ font-style: normal;
+ font-weight: normal;
+ speak: none;
+ display: inline-block;
+ text-decoration: inherit;
+ text-align: center;
+ font-variant: normal;
+ text-transform: none;
+ line-height: 1em;
+}
+.vipicon:before {
+ content: "\e66a";
+ font-size: 14px;
+}
+.com-level { display: inline-block; margin-left: 3px; }
+.com-level sub {
+ font-size: 11px;
+ vertical-align: baseline;
+ position: relative;
+ top: -0.5em;
+ font-weight: bold;
+}
+/* 等级颜色定义 - 同时应用到图标和数字 */
+.com-level.vip1 .vipicon,
+.com-level.vip1 sub { color: #999; }
+.com-level.vip2 .vipicon,
+.com-level.vip2 sub { color: #8c8c8c; }
+.com-level.vip3 .vipicon,
+.com-level.vip3 sub { color: #666; }
+.com-level.vip4 .vipicon,
+.com-level.vip4 sub { color: #52c41a; }
+.com-level.vip5 .vipicon,
+.com-level.vip5 sub { color: #1890ff; }
+.com-level.vip6 .vipicon,
+.com-level.vip6 sub { color: #722ed1; }
+.com-level.vip7 .vipicon,
+.com-level.vip7 sub { color: #faad14; }
+.com-level.vip8 .vipicon,
+.com-level.vip8 sub { color: #f5222d; }
+.com-level.vip9 .vipicon,
+.com-level.vip9 sub { color: #eb2f96; }
+.com-level.vip10 .vipicon,
+.com-level.vip10 sub { color: #fa541c; }
+.com-level.vip11 .vipicon,
+.com-level.vip11 sub { color: #13c2c2; }
+.com-level.vip12 .vipicon,
+.com-level.vip12 sub { color: #000; }
+/* 动态CSS - 确保用户自定义的颜色通过内联样式生效 */
+.com-level .vipicon,
+.com-level sub {
+ color: inherit !important;
+}
+/* 博主颜色 */
+.com-level.blogger .vipicon,
+.com-level.blogger sub { color: #f5222d !important; }
\ No newline at end of file