Deprecated: Creation of dynamic property Typecho\Widget\Request::$feed is deprecated in /www/wwwroot/blog.iletter.top/var/Widget/Archive.php on line 246
白荼日记 https://blog.iletter.top/ 没有什么可以留住,除了死亡。 git拆分分支和合并分支 https://blog.iletter.top/archives/465/ 2025-09-03T14:02:50+08:00 如果有test分支和master分支的话,如何合并呢?使用merge即可完成。(merge:保留完整的分支历史,生成一个合并提交)# 1. 切换到master分支 git checkout master # 2. 将test分支合并到master git merge test如果有冲突需要解决如果在合并过程中出现冲突,需要手动解决:# 1. 查看冲突文件 git status # 2. 手动编辑冲突文件,解决冲突 # 3. 添加解决后的文件 git add . # 4. 完成合并 git commit查看本地分支git branch查看所有分支(包括远程分支)git branch -a查看远程分支git branch -r查看分支的详细信息git branch -v在你的项目中运行 git branch,应该能看到类似这样的输出:* master test遇到分离HEAD 的情况DELLEVIN@DESKTOP-HISDDCH D:\Proj_Dev\Other\xyjq\xyjq-mp-vue git:6afbbee ❯❯❯ git branch * (HEAD detached from d7244e3) master test方法一:将当前更改合并到master分支# 1. 切换到master分支 git checkout master # 2. 将之前分离HEAD状态下的更改合并过来 # 如果你知道那个commit的hash(比如d7244e3),可以这样: git merge d7244e3方法二:直接基于当前状态创建新分支# 1. 基于当前状态创建并切换到新分支 git checkout -b new_feature # 2. 切换到master git checkout master # 3. 合并新分支 git merge new_feature # 4. 删除临时分支(可选) git branch -d new_feature 让typecho的access插件使用ip2region 地址库 https://blog.iletter.top/archives/459/ 2025-08-28T22:42:00+08:00 旧版本的typecho的access插件记录的ip太过于落后了。判断也有很多问题,所以需要在此更新一下。定位到地址文件是lib下面的ipipfree.ipdb,删掉这个就好,用不到了。这个最近的时间是2019年的。真的很无语的。接下来定位到所在位置是Access_Core.php这个文件的这里代码。(Access_IpDb.php这个也可以删除) try { $ipdb = new Access_IpDb(dirname(__file__).'/lib/ipipfree.ipdb'); $city = $ipdb->findInfo($ip, 'CN'); // 写入日志 error_log("IP: {$ip}\nCity Info: " . print_r($city, true), 3, '/tmp/access_debug.log'); $ip_country = $city->country_name; if($ip_country == '中国') { $ip_province = $city->region_name; $ip_city = $city->city_name; } else { $ip_province = $ip_city = NULL; } } catch(Exception $e) { $ip_country = $ip_province = $ip_city = '未知'; } 然后根据数据格式,改成他的数据样式的。try { // 检查必要的文件是否存在 $dbFile = dirname(__FILE__) . '/lib/ip2region.xdb'; $classFile = dirname(__FILE__) . '/lib/XdbSearcher.php'; if (!file_exists($dbFile)) { throw new Exception("Ip2region database file not found: {$dbFile}"); } if (!file_exists($classFile)) { throw new Exception("XdbSearcher class file not found: {$classFile}"); } require_once $classFile; $searcher = XdbSearcher::newWithFileOnly($dbFile); $region = $searcher->search($ip); if ($region === null) { throw new Exception("IP2Region search failed for IP: {$ip}"); } // 调试日志 error_log("IP: {$ip}\nRegion Info: " . print_r($region, true), 3, '/tmp/access_debug.log'); // 解析数据 (格式: 国家|区域|省份|城市|ISP) $regionArray = explode('|', $region); // 清理数据 $cleanData = function($data) { return (!empty($data) && $data !== '0') ? $data : ''; }; $country = $cleanData($regionArray[0]); $region_info = $cleanData($regionArray[1]); $province = $cleanData($regionArray[2]); $city = $cleanData($regionArray[3]); // 设置最终保存到数据库的字段 $ip_country = !empty($country) ? $country : '未知'; // 无论国内外都保存区域和城市信息 $ip_province = ''; $ip_city = ''; if (!empty($province)) { $ip_province = $province; } elseif (!empty($region_info)) { // 如果省份为空,但区域不为空,可以用区域代替 $ip_province = $region_info; } if (!empty($city)) { $ip_city = $city; } } catch(Exception $e) { error_log("IP解析异常:" . $e->getMessage(), 3, '/tmp/access_debug.log'); $ip_country = '未知'; $ip_province = ''; $ip_city = ''; }这样就可以了。 https嵌套http(修改安全策略) https://blog.iletter.top/archives/455/ 2025-08-26T12:19:00+08:00 因为需要用到https嵌套http,但是这怎么可能啊?还好那个http有https的协议,所以我们只需要再nginx上操作升级CSP就行了。也就是内容安全策略。HTTPS页面 (aaa.com) └── HTTP iframe (bbb.com) └── HTTP frame (ccc.com)大概是这种形式的,所以在第一个a网站是我自己写的,所以我就选择了直接使用http替换成https的了然后配置b网站server { listen 80; server_name www.bbb.com; # 强制跳转到 HTTPS return 301 https://$host$request_uri; } # 处理www.bbb.com 的 HTTPS 请求 server { listen 443 ssl; server_name www.bbb.com; # SSL 证书配置(你需要配置自己的证书) ssl_certificate /path/to/your/certificate.crt; ssl_certificate_key /path/to/your/private.key; # 添加安全头 add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; add_header Content-Security-Policy "upgrade-insecure-requests"; # 代理到后端应用 location / { proxy_pass http://localhost:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # 特别处理企业查看接口,确保内容中的HTTP链接被替换 location /rotech-xyjq-api/api/enterprise/view { proxy_pass http://localhost:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # 替换内容中的HTTP链接为HTTPS sub_filter 'http://b-plugin.qixin.com/' 'https://b-plugin.qixin.com/'; sub_filter 'http://b-plugin.qixin.com' 'https://b-plugin.qixin.com'; sub_filter_once off; } }这样就可以再a网站加载c网站的了,可以无限套娃了,前提是每个娃娃都有https这个。 typecho文章编辑界面新增参考文章功能 https://blog.iletter.top/archives/450/ 2025-08-25T22:47:00+08:00 因为再写技术博客的时候总是要很多的参考文章。直接在文章引用感觉怪怪的。所以自己更改了一些数据结构,接口。效果为这样的:增加数据库字段typecho_contents这个表增加一个字段post_links,类型为text修改接口文件路径在var/Widget/Contents/Post下面的Edit.php文件修改编辑文章文件文件地址在admin路径下的write-post.php文件,新增部分。css暂时不写了 <!-- 引用文章开始 --> <section class="typecho-post-option reference-links-section" id="reference-links-section"> <label class="typecho-label toggle-reference-section" style="cursor: pointer;"> <span class="toggle-icon" style="float: left;"><i class="i-caret-right"></i></span> <?php _e('参考文章'); ?> </label> <div class="reference-links-container" id="post-links-container"> <?php // 从数据库获取数据 $links = []; if ($post->have()) { $db = Typecho\Db::get(); $result = $db->fetchRow($db->select('post_links')->from('table.contents')->where('cid = ?', $post->cid)); if (!empty($result) && !empty($result['post_links'])) { $postLinks = $result['post_links']; $links = json_decode($postLinks, true); if (!is_array($links)) $links = []; } } // 如果没有数据,添加一个空行 if (empty($links)) { $links = [['name' => '', 'link' => '']]; } foreach ($links as $index => $link): ?> <div class="reference-link-item"> <div class="reference-link-inputs"> <input type="text" name="post_links[<?php echo $index; ?>][name]" placeholder="<?php _e('链接名称'); ?>" value="<?php echo htmlspecialchars($link['name'] ?? ''); ?>" class="text reference-link-name" /> <input type="url" name="post_links[<?php echo $index; ?>][link]" placeholder="<?php _e('https://'); ?>" value="<?php echo htmlspecialchars($link['link'] ?? ''); ?>" class="text reference-link-url" /> </div> <div class="reference-link-actions"> <button type="button" class="btn btn-xs remove-reference-link" title="<?php _e('删除'); ?>"> <i class="i-delete"></i> </button> </div> </div> <?php endforeach; ?> </div> <div class="reference-links-footer"> <button type="button" id="add-reference-link" class="btn btn-xs"> <i class="i-plus"></i> <?php _e('添加引用'); ?> </button> <span class="reference-links-help"><?php _e('温故而知新,添加相关的参考链接,记忆更牢固。'); ?></span> </div> </section> <!-- 引用文章结束 -->受限于代码长度过长,使得文章无法正常解析,可以点击链接下载。点击下载:代码.zip typecho美化文件管理界面 https://blog.iletter.top/archives/447/ 2025-08-25T15:49:00+08:00 后台界面太丑陋, 我就自己在这里改了改。添加文件预览以及预览图片放大。添加接口因为后台接口少了统计接口,所以这里需要手动后台添加。目录在安装目录下面的/var/Widget/Contents/Attachment这个目录, 然后编辑Admin.php。复制以下代码即可。<?php namespace Widget\Contents\Attachment; use Typecho\Config; use Typecho\Db; use Typecho\Db\Exception; use Typecho\Db\Query; use Typecho\Widget\Helper\PageNavigator\Box; use Widget\Base\Contents; use Typecho\Common; if (!defined('__TYPECHO_ROOT_DIR__')) { exit; } /** * 文件管理列表组件 * * @category typecho * @package Widget * @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org) * @license GNU General Public License 2.0 */ class Admin extends Contents { /** * 用于计算数值的语句对象 * * @var Query */ private $countSql; /** * 所有文章个数 * * @var integer */ private $total = false; /** * 当前页 * * @var integer */ private $currentPage; /** * 执行函数 * * @return void * @throws Exception|\Typecho\Widget\Exception */ public function execute() { $this->parameter->setDefault('pageSize=20'); $this->currentPage = $this->request->get('page', 1); /** 构建基础查询 */ $select = $this->select()->where('table.contents.type = ?', 'attachment'); /** 如果具有编辑以上权限,可以查看所有文件,反之只能查看自己的文件 */ if (!$this->user->pass('editor', true)) { $select->where('table.contents.authorId = ?', $this->user->uid); } /** 过滤标题 */ if (null != ($keywords = $this->request->filter('search')->keywords)) { $args = []; $keywordsList = explode(' ', $keywords); $args[] = implode(' OR ', array_fill(0, count($keywordsList), 'table.contents.title LIKE ?')); foreach ($keywordsList as $keyword) { $args[] = '%' . $keyword . '%'; } call_user_func_array([$select, 'where'], $args); } /** 给计算数目对象赋值,克隆对象 */ $this->countSql = clone $select; /** 提交查询 */ $select->order('table.contents.created', Db::SORT_DESC) ->page($this->currentPage, $this->parameter->pageSize); $this->db->fetchAll($select, [$this, 'push']); } /** * 输出分页 * * @return void * @throws Exception|\Typecho\Widget\Exception */ public function pageNav() { $query = $this->request->makeUriByRequest('page={page}'); /** 使用盒状分页 */ $nav = new Box( false === $this->total ? $this->total = $this->size($this->countSql) : $this->total, $this->currentPage, $this->parameter->pageSize, $query ); $nav->render('&laquo;', '&raquo;'); } /** * 所属文章 * * @return Config * @throws Exception */ protected function ___parentPost(): Config { return new Config($this->db->fetchRow( $this->select()->where('table.contents.cid = ?', $this->parentId)->limit(1) )); } /** * 获取附件的 URL 地址 * * @return string */ protected function ___attachmentUrl(): string { if (!empty($this->attachment) && !empty($this->attachment->path)) { // 从 path 字段构建 URL $path = $this->attachment->path; // 移除开头的斜杠(如果有的话) $path = ltrim($path, '/'); return $this->options->siteUrl . $path; } // 如果 path 不存在,尝试从 text 字段解析 if (!empty($this->___attachment())) { $attachment = $this->___attachment(); if (!empty($attachment['path'])) { $path = ltrim($attachment['path'], '/'); return $this->options->siteUrl . $path; } } return ''; } /** * 解析附件信息 * * @return array */ protected function ___attachment(): array { if (!empty($this->text)) { $attachment = @unserialize($this->text); if (is_array($attachment)) { return $attachment; } } return []; } /** * 重写 permalink 方法,使用解析后的 URL * * @return string */ protected function ___permalink(): string { return $this->___attachmentUrl(); } /** * 获取统计信息 * * @return array * @throws Exception */ public function getStats(): array { $select = $this->select()->where('table.contents.type = ?', 'attachment'); /** 如果具有编辑以上权限,可以查看所有文件,反之只能查看自己的文件 */ if (!$this->user->pass('editor', true)) { $select->where('table.contents.authorId = ?', $this->user->uid); } // 总文件数 $total = $this->size(clone $select); // 图片文件数 $imageSelect = clone $select; $imageSelect->join('table.fields', 'table.contents.cid = table.fields.cid', Db::LEFT_JOIN) ->where('table.fields.name = ?', 'mime') ->where('table.fields.str_value LIKE ?', 'image/%'); $imageCount = $this->size($imageSelect); // 未归档文件数 $unattachedSelect = clone $select; $unattachedSelect->where('table.contents.parent = ?', 0); $unattachedCount = $this->size($unattachedSelect); return [ 'total' => $total, 'images' => $imageCount, 'unattached' => $unattachedCount ]; } /** * 获取总文件数 * * @return int * @throws Exception */ public function getTotalCount(): int { $select = $this->select()->where('table.contents.type = ?', 'attachment'); /** 如果具有编辑以上权限,可以查看所有文件,反之只能查看自己的文件 */ if (!$this->user->pass('editor', true)) { $select->where('table.contents.authorId = ?', $this->user->uid); } return $this->size($select); } /** * 获取图片文件数 * * @return int * @throws Exception */ public function getImageCount(): int { $select = $this->select()->where('table.contents.type = ?', 'attachment'); /** 如果具有编辑以上权限,可以查看所有文件,反之只能查看自己的文件 */ if (!$this->user->pass('editor', true)) { $select->where('table.contents.authorId = ?', $this->user->uid); } // 通过 MIME 类型过滤图片 $select->where('table.contents.text LIKE ?', '%image/%'); return $this->size($select); } /** * 获取未归档文件数 * * @return int * @throws Exception */ public function getUnattachedCount(): int { $select = $this->select()->where('table.contents.type = ?', 'attachment'); /** 如果具有编辑以上权限,可以查看所有文件,反之只能查看自己的文件 */ if (!$this->user->pass('editor', true)) { $select->where('table.contents.authorId = ?', $this->user->uid); } // 未归档文件(parent = 0) $select->where('table.contents.parent = ?', 0); return $this->size($select); } }更改后台文件界面通过查看后台路径可以得知是manage-medias.php这个文件,所以更改一下这个文件就可以了。更改前注意文件备份。<?php include 'common.php'; include 'header.php'; include 'menu.php'; $stat = \Widget\Stat::alloc(); $attachments = \Widget\Contents\Attachment\Admin::alloc(); // 获取统计信息 $totalCount = $attachments->getTotalCount(); $imageCount = $attachments->getImageCount(); $unattachedCount = $attachments->getUnattachedCount(); // 获取PHP上传限制 $phpMaxFilesize = function_exists('ini_get') ? trim(ini_get('upload_max_filesize')) : 0; if (preg_match("/^([0-9]+)([a-z]{1,2})$/i", $phpMaxFilesize, $matches)) { $phpMaxFilesize = strtolower($matches[1] . $matches[2] . (1 == strlen($matches[2]) ? 'b' : '')); } ?> <style> /* 上传区域样式 */ .upload-section { background: #fff; border: 1px solid #d9d9d9; border-radius: 4px; padding: 20px; margin: 20px 0; display: none; } .upload-section.show { display: block; } .upload-section h3 { margin: 0 0 15px 0; padding: 0; font-size: 16px; color: #333; } .upload-controls { display: flex; gap: 15px; align-items: center; flex-wrap: wrap; margin-bottom: 15px; } /* 改为链接样式 */ .upload-link { color: #007cba; text-decoration: underline; cursor: pointer; font-size: 14px; display: inline-flex; align-items: center; gap: 4px; transition: color 0.2s; } .upload-link:hover { color: #005a87; text-decoration: none; } .upload-link i { font-size: 16px; } .upload-hint { color: #666; font-size: 13px; } .upload-area { border: 2px dashed #ddd; border-radius: 6px; padding: 30px; text-align: center; background: #fafafa; transition: all 0.3s; margin-top: 10px; } .upload-area.dragover { border-color: #007cba; background: #f0f8ff; } .upload-area-text { color: #666; margin-bottom: 10px; } .browse-link { color: #007cba; text-decoration: underline; cursor: pointer; } .browse-link:hover { color: #005a87; } /* 上传进度条 */ .upload-progress { display: none; margin-top: 15px; } .progress-bar { width: 100%; height: 20px; background: #f0f0f0; border-radius: 10px; overflow: hidden; } .progress-fill { height: 100%; background: #007cba; width: 0%; transition: width 0.3s; } .progress-text { text-align: center; font-size: 12px; color: #666; margin-top: 5px; } .media-stats { background: #fff; border: 1px solid #d9d9d9; border-radius: 4px; padding: 15px; margin-bottom: 20px; } .media-stats h3 { margin: 0 0 15px 0; padding: 0; font-size: 16px; color: #666; } .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 15px; } .stat-item { text-align: center; padding: 10px; background: #f8f9fa; border-radius: 4px; } .stat-number { font-size: 24px; font-weight: bold; color: #007cba; margin-bottom: 5px; } .stat-label { font-size: 13px; color: #666; } /* 预览列样式 */ .preview-cell { text-align: center; } .media-preview-img { width: 50px; height: 50px; object-fit: cover; border-radius: 4px; border: 1px solid #ddd; cursor: pointer; transition: transform 0.2s ease; } .media-preview-img:hover { transform: scale(1.1); box-shadow: 0 2px 8px rgba(0,0,0,0.2); } .media-preview-icon { font-size: 24px; color: #999; } /* 保持原有样式 */ .typecho-list-operate { background: #fff; border: 1px solid #d9d9d9; border-radius: 4px; padding: 15px; margin-bottom: 20px; } .typecho-pager { margin-top: 20px; text-align: center; } /* 图片预览模态框样式 */ .modal { display: none; position: fixed; z-index: 10000; left: 0; top: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.8); backdrop-filter: blur(5px); } .modal-content { position: relative; margin: 5% auto; padding: 20px; width: 90%; max-width: 900px; max-height: 90vh; text-align: center; } .modal-image { max-width: 100%; max-height: 80vh; object-fit: contain; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.3); } .close { position: absolute; top: 10px; right: 25px; color: #fff; font-size: 35px; font-weight: bold; cursor: pointer; z-index: 10001; } .close:hover { color: #ccc; } .modal-title { color: white; margin-top: 10px; font-size: 16px; font-weight: normal; } /* 上传按钮样式 - 与清理按钮统一 */ .btn-upload { background: #28a745; border-color: #28a745; color: white; margin-left: 10px; } .btn-upload:hover { background: #218838; border-color: #1e7e34; } /* 取消按钮样式 */ #cancelUploadBtn { margin-left: 10px; } /* 隐藏的文件输入框 */ #fileInput { display: none; } /* 响应式设计 */ @media (max-width: 768px) { .stats-grid { grid-template-columns: repeat(2, 1fr); } .modal-content { width: 95%; margin: 10% auto; } .modal-image { max-height: 70vh; } .upload-controls { flex-direction: column; align-items: stretch; } .btn-upload { margin-left: 0; margin-top: 5px; } } </style> <div class="main"> <div class="body container"> <?php include 'page-title.php'; ?> <div class="row typecho-page-main" role="main"> <div class="col-mb-12"> <!-- 统计信息 --> <div class="media-stats"> <h3>媒体文件统计</h3> <div class="stats-grid"> <div class="stat-item"> <div class="stat-number"><?php echo $totalCount; ?></div> <div class="stat-label">总文件数</div> </div> <div class="stat-item"> <div class="stat-number"><?php echo $imageCount; ?></div> <div class="stat-label">图片文件</div> </div> <div class="stat-item"> <div class="stat-number"><?php echo $stat->publishedPostsNum; ?></div> <div class="stat-label">文章数</div> </div> <div class="stat-item"> <div class="stat-number"><?php echo $unattachedCount; ?></div> <div class="stat-label">未归档文件</div> </div> </div> </div> <div class="typecho-list-operate clearfix"> <form method="get"> <div class="operate"> <label><i class="sr-only"><?php _e('全选'); ?></i><input type="checkbox" class="typecho-table-select-all"/></label> <div class="btn-group btn-drop"> <button class="btn dropdown-toggle btn-s" type="button"><i class="sr-only"><?php _e('操作'); ?></i><?php _e('选中项'); ?> <i class="i-caret-down"></i></button> <ul class="dropdown-menu"> <li><a lang="<?php _e('你确认要删除这些文件吗?'); ?>" href="<?php $security->index('/action/contents-attachment-edit?do=delete'); ?>"><?php _e('删除'); ?></a> </li> </ul> <button class="btn btn-s btn-warn btn-operate" href="<?php $security->index('/action/contents-attachment-edit?do=clear'); ?>" lang="<?php _e('您确认要清理未归档的文件吗?'); ?>"><?php _e('清理未归档文件'); ?></button> <!--<button type="button" class="btn btn-s btn-upload" id="toggleUploadBtn">--> <!-- <?php _e('上传文件'); ?>--> <!--</button>--> <!-- 修改这里:添加条件显示 --> <button type="button" class="btn btn-s btn-upload" id="toggleUploadBtn"> <?php _e('上传文件'); ?> </button> <button type="button" class="btn btn-s" id="cancelUploadBtn" style="display: none;"> <?php _e('取消上传'); ?> </button> </div> </div> <div class="search" role="search"> <?php if ('' != $request->keywords): ?> <a href="<?php $options->adminUrl('manage-medias.php'); ?>"><?php _e('&laquo; 取消筛选'); ?></a> <?php endif; ?> <input type="text" class="text-s" placeholder="<?php _e('请输入关键字'); ?>" value="<?php echo $request->filter('html')->keywords; ?>"<?php if ('' == $request->keywords): ?> onclick="value='';name='keywords';" <?php else: ?> name="keywords"<?php endif; ?>/> <button type="submit" class="btn btn-s"><?php _e('筛选'); ?></button> </div> </form> </div><!-- end .typecho-list-operate --> <!-- 上传区域 - 默认隐藏 --> <div class="upload-section" id="uploadSection"> <h3><?php _e('上传文件'); ?></h3> <div class="upload-controls"> <!-- 改为链接形式 --> <a href="javascript:;" class="upload-link" id="uploadButton"> <!--<i class="i-upload"></i> --> <?php _e('选择文件'); ?> </a> <span class="upload-hint"><?php _e('或拖放文件到下方区域'); ?></span> </div> <div class="upload-area" id="uploadArea"> <div class="upload-area-text"> <?php _e('拖放文件到这里'); ?> </div> <div> <?php _e('或者 %s浏览文件%s', '<span class="browse-link" id="browseLink">', '</span>'); ?> </div> </div> <!-- 上传进度条 --> <div class="upload-progress" id="uploadProgress"> <div class="progress-bar"> <div class="progress-fill" id="progressFill"></div> </div> <div class="progress-text" id="progressText">0%</div> </div> <!-- 隐藏的文件输入框 --> <input type="file" id="fileInput" multiple style="display: none;"> </div> <form method="post" name="manage_medias" class="operate-form"> <div class="typecho-table-wrap"> <table class="typecho-list-table draggable"> <colgroup> <col width="20" class="kit-hidden-mb"/> <col width="6%" class="kit-hidden-mb"/> <col width="10%" class="kit-hidden-mb"/> <!-- 预览列 --> <col width="25%"/> <col width="" class="kit-hidden-mb"/> <col width="25%" class="kit-hidden-mb"/> <col width="16%"/> </colgroup> <thead> <tr> <th class="kit-hidden-mb"></th> <th class="kit-hidden-mb"></th> <th class="kit-hidden-mb"><?php _e('预览'); ?></th> <th><?php _e('文件名'); ?></th> <th class="kit-hidden-mb"><?php _e('上传者'); ?></th> <th class="kit-hidden-mb"><?php _e('所属文章'); ?></th> <th><?php _e('发布日期'); ?></th> </tr> </thead> <tbody> <?php if ($attachments->have()): ?> <?php while ($attachments->next()): ?> <?php $mime = \Typecho\Common::mimeIconType($attachments->attachment->mime); $isImage = strpos($attachments->attachment->mime, 'image/') === 0; $fileUrl = $attachments->attachmentUrl; ?> <tr id="<?php $attachments->theId(); ?>"> <td class="kit-hidden-mb"> <input type="checkbox" class="typecho-table-checkbox" value="<?php $attachments->cid(); ?>" name="cid[]"/> </td> <td class="kit-hidden-mb"><a href="<?php $options->adminUrl('manage-comments.php?cid=' . $attachments->cid); ?>" class="balloon-button size-<?php echo \Typecho\Common::splitByCount($attachments->commentsNum, 1, 10, 20, 50, 100); ?>"><?php $attachments->commentsNum(); ?></a> </td> <td class="kit-hidden-mb preview-cell"> <?php if ($isImage && !empty($fileUrl)): ?> <img src="<?php echo $fileUrl; ?>" class="media-preview-img" alt="<?php $attachments->title(); ?>" data-full-src="<?php echo $fileUrl; ?>" data-title="<?php $attachments->title(); ?>"> <?php else: ?> <i class="mime-<?php echo $mime; ?> media-preview-icon"></i> <?php endif; ?> </td> <td> <i class="mime-<?php echo $mime; ?>"></i> <a href="<?php $options->adminUrl('media.php?cid=' . $attachments->cid); ?>"><?php $attachments->title(); ?></a> <?php if (!empty($fileUrl)): ?> <a href="<?php echo $fileUrl; ?>" target="_blank" title="<?php _e('浏览 %s', $attachments->title); ?>"><i class="i-exlink"></i></a> <?php endif; ?> </td> <td class="kit-hidden-mb"><?php $attachments->author(); ?></td> <td class="kit-hidden-mb"> <?php if ($attachments->parentPost->cid): ?> <a href="<?php $options->adminUrl('write-' . (0 === strpos($attachments->parentPost->type, 'post') ? 'post' : 'page') . '.php?cid=' . $attachments->parentPost->cid); ?>"><?php $attachments->parentPost->title(); ?></a> <?php else: ?> <span class="description"><?php _e('未归档'); ?></span> <?php endif; ?> </td> <td><?php $attachments->dateWord(); ?></td> </tr> <?php endwhile; ?> <?php else: ?> <tr> <td colspan="7"><h6 class="typecho-list-table-title"><?php _e('没有任何文件'); ?></h6> </td> </tr> <?php endif; ?> </tbody> </table><!-- end .typecho-list-table --> </div><!-- end .typecho-table-wrap --> </form><!-- end .operate-form --> <div class="typecho-list-operate clearfix"> <form method="get"> <div class="operate"> <label><i class="sr-only"><?php _e('全选'); ?></i><input type="checkbox" class="typecho-table-select-all"/></label> <div class="btn-group btn-drop"> <button class="btn dropdown-toggle btn-s" type="button"><i class="sr-only"><?php _e('操作'); ?></i><?php _e('选中项'); ?> <i class="i-caret-down"></i></button> <ul class="dropdown-menu"> <li><a lang="<?php _e('你确认要删除这些文件吗?'); ?>" href="<?php $security->index('/action/contents-attachment-edit?do=delete'); ?>"><?php _e('删除'); ?></a> </li> </ul> </div> <button class="btn btn-s btn-warn btn-operate" href="<?php $security->index('/action/contents-attachment-edit?do=clear'); ?>" lang="<?php _e('您确认要清理未归档的文件吗?'); ?>"><?php _e('清理未归档文件'); ?></button> </div> <?php if ($attachments->have()): ?> <ul class="typecho-pager"> <?php $attachments->pageNav(); ?> </ul> <?php endif; ?> </form> </div><!-- end .typecho-list-operate --> </div> </div><!-- end .typecho-page-main --> </div> </div> <!-- 图片预览模态框 --> <div id="imageModal" class="modal"> <span class="close">&times;</span> <div class="modal-content"> <img class="modal-image" id="modalImage" src="" alt=""> <div class="modal-title" id="modalTitle"></div> </div> </div> <script src="<?php $options->adminStaticUrl('js', 'moxie.js'); ?>"></script> <script src="<?php $options->adminStaticUrl('js', 'plupload.js'); ?>"></script> <script> document.addEventListener('DOMContentLoaded', function() { // 获取元素 const toggleUploadBtn = document.getElementById('toggleUploadBtn'); const cancelUploadBtn = document.getElementById('cancelUploadBtn'); const uploadSection = document.getElementById('uploadSection'); const uploadButton = document.getElementById('uploadButton'); const browseLink = document.getElementById('browseLink'); const fileInput = document.getElementById('fileInput'); const uploadArea = document.getElementById('uploadArea'); const uploadProgress = document.getElementById('uploadProgress'); const progressFill = document.getElementById('progressFill'); const progressText = document.getElementById('progressText'); // 模态框元素 const modal = document.getElementById('imageModal'); const modalImg = document.getElementById('modalImage'); const modalTitle = document.getElementById('modalTitle'); const closeBtn = document.querySelector('.close'); // 切换上传区域显示/隐藏 // toggleUploadBtn.addEventListener('click', function() { // uploadSection.classList.toggle('show'); // // 滚动到上传区域 // if (uploadSection.classList.contains('show')) { // uploadSection.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); // } // }); // 切换上传区域显示/隐藏 toggleUploadBtn.addEventListener('click', function() { uploadSection.classList.add('show'); // 显示取消按钮,隐藏上传按钮 toggleUploadBtn.style.display = 'none'; cancelUploadBtn.style.display = 'inline-block'; // 滚动到上传区域 uploadSection.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); }); // 取消上传 cancelUploadBtn.addEventListener('click', function() { uploadSection.classList.remove('show'); // 显示上传按钮,隐藏取消按钮 toggleUploadBtn.style.display = 'inline-block'; cancelUploadBtn.style.display = 'none'; }); // 点击链接触发文件选择 uploadButton.addEventListener('click', function(e) { e.preventDefault(); // 触发隐藏的文件输入框 fileInput.click(); }); // 点击浏览链接也触发文件选择 browseLink.addEventListener('click', function() { fileInput.click(); }); // 初始化上传器 var uploader = new plupload.Uploader({ runtimes: 'html5,flash,html4', browse_button: 'fileInput', // 绑定到隐藏的文件输入框 container: 'uploadArea', drop_element: 'uploadArea', url: '<?php $security->index('/action/upload'); ?>', flash_swf_url: '<?php $options->adminStaticUrl('js', 'Moxie.swf'); ?>', filters: { max_file_size: '<?php echo $phpMaxFilesize ?>', mime_types: [{'title': '<?php _e('允许上传的文件'); ?>', 'extensions': '<?php echo implode(',', $options->allowedAttachmentTypes); ?>'}] }, init: { PostInit: function() { console.log('Uploader initialized'); }, FilesAdded: function(up, files) { console.log('Files added:', files); // 显示进度条 uploadProgress.style.display = 'block'; // 开始上传 up.start(); }, UploadProgress: function(up, file) { // 更新进度条 var percent = file.percent; progressFill.style.width = percent + '%'; progressText.textContent = percent + '%'; }, FileUploaded: function(up, file, response) { console.log('File uploaded:', response); if (response.status === 200) { try { var result = JSON.parse(response.response); if (result && result[1]) { // 上传成功,刷新页面显示新文件 location.reload(); } } catch (e) { console.error('Parse error:', e); } } // 隐藏进度条 setTimeout(function() { uploadProgress.style.display = 'none'; progressFill.style.width = '0%'; progressText.textContent = '0%'; // 隐藏上传区域 uploadSection.classList.remove('show'); }, 1000); }, Error: function(up, err) { console.error('Upload error:', err); alert('上传出错: ' + err.message); // 隐藏进度条 uploadProgress.style.display = 'none'; } } }); uploader.init(); // 拖拽事件 uploadArea.addEventListener('dragover', function(e) { e.preventDefault(); this.classList.add('dragover'); }); uploadArea.addEventListener('dragleave', function(e) { e.preventDefault(); this.classList.remove('dragover'); }); uploadArea.addEventListener('drop', function(e) { e.preventDefault(); this.classList.remove('dragover'); }); // 图片预览功能 const previewImages = document.querySelectorAll('.media-preview-img'); previewImages.forEach(img => { img.addEventListener('click', function(e) { e.preventDefault(); e.stopPropagation(); const fullSrc = this.getAttribute('data-full-src'); const title = this.getAttribute('data-title'); if (fullSrc) { modalImg.src = fullSrc; modalTitle.textContent = title || ''; modal.style.display = 'block'; document.body.style.overflow = 'hidden'; } }); }); // 关闭模态框 function closeModal() { modal.style.display = 'none'; document.body.style.overflow = ''; } closeBtn.addEventListener('click', closeModal); modal.addEventListener('click', function(e) { if (e.target === modal) { closeModal(); } }); document.addEventListener('keydown', function(e) { if (e.key === 'Escape' && modal.style.display === 'block') { closeModal(); } }); }); </script> <?php include 'copyright.php'; include 'common-js.php'; include 'table-js.php'; include 'footer.php'; ?> linux mint22.1安装on my zsh https://blog.iletter.top/archives/429/ 2025-08-24T15:42:00+08:00 更新系统并安装必要工具sudo apt update && sudo apt upgrade -y sudo apt install zsh git curl wget -y安装 Oh My Zshsh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"安装 Powerlevel10k 主题git clone --depth=1 https://github.com/romkatv/powerlevel10k.git ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k配置 Oh My Zsh 使用 Powerlevel10k编辑 ~/.zshrc:vim ~/.zshrc注释掉 ZSH_THEME="robbyrussell"添加 ZSH_THEME="powerlevel10k/powerlevel10k"安装 Nerd Fonts(解决图标乱码)# 下载 wget https://github.com/ryanoasis/nerd-fonts/releases/download/v3.2.1/FiraCode.zip # 解压 unzip FiraCode.zip -d ~/.local/share/fonts/ # 刷新缓存 fc-cache -fv # 删除 rm FiraCode.zip安装插件(自选)Zsh 自动建议(输入历史建议)git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestionsZsh 语法高亮git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting编辑配置文件vim ~/.zshrc文件-代表删除+代表新增- plugins=(git) + plugins=( + git + zsh-autosuggestions + zsh-syntax-highlighting + )配置完毕后输入exec zsh 重启终端持久化配置which zsh 查看安装位置2. 永久修改默认 Shell(需要输入密码)chsh -s $(which zsh)安装后重启电脑即可如果想要重新配置可以输入即可p10k configure linux mint命令窗口设置代理 https://blog.iletter.top/archives/428/ 2025-08-24T00:18:17+08:00 临时设置设置代理export http_proxy="http://127.0.0.1:20171"export https_proxy="http://127.0.0.1:20171"取消设置代理unset http_proxy https_proxy此方式不支持socket5代理proxychains前情提要:因为要按照某个软件,但是有网络监测机制,一直报错,所以就需要通过代理来运行。安装:sudo apt updatesudo apt install proxychains -y配置:sudo vim /etc/proxychains.conf在末尾添加socks5 127.0.0.1 20170然后就可以使用了。重要知识点:ping 不能被代理!ping 使用的是 ICMP 协议,不是 TCP/HTTP/SOCKS。proxychains 只能代理 TCP 和 UDP 连接(比如 curl, wget, git, ssh 等)。所以你用 proxychains ping ... 是无效的,而且会报错或被忽略。正确测试 proxychains 是否工作的命令是使用 curl 或 wget(base) dellevin@dellevin-G3-3579:~/Desktop/WhiteSur-gtk-theme$ proxychains curl -v google.com ProxyChains-3.1 (http://proxychains.sf.net) |DNS-request| google.com |S-chain|-<>-127.0.0.1:20170-<><>-4.2.2.2:53-<><>-OK |DNS-response| google.com is 142.250.189.238 * Host google.com:80 was resolved. * IPv6: (none) * IPv4: 142.250.189.238 * Trying 142.250.189.238:80... |S-chain|-<>-127.0.0.1:20170-<><>-142.250.189.238:80-<><>-OK * Connected to google.com (142.250.189.238) port 80 * using HTTP/1.x > GET / HTTP/1.1 > Host: google.com > User-Agent: curl/8.12.1 > Accept: */* > * Request completely sent off < HTTP/1.1 301 Moved Permanently < Location: http://www.google.com/ < Content-Type: text/html; charset=UTF-8 < Content-Security-Policy-Report-Only: object-src 'none';base-uri 'self';script-src 'nonce-p85NwNurE8Yc0yBkcHZmUQ' 'strict-dynamic' 'report-sample' 'unsafe-eval' 'unsafe-inline' https: http:;report-uri https://csp.withgoogle.com/csp/gws/other-hp < Date: Sat, 23 Aug 2025 05:50:49 GMT < Expires: Mon, 22 Sep 2025 05:50:49 GMT < Cache-Control: public, max-age=2592000 < Server: gws < Content-Length: 219 < X-XSS-Protection: 0 < X-Frame-Options: SAMEORIGIN < <HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8"> <TITLE>301 Moved</TITLE></HEAD><BODY> <H1>301 Moved</H1> The document has moved <A HREF="http://www.google.com/">here</A>. </BODY></HTML> * Connection #0 to host google.com left intact这样说明我已经可以正常访问了 去除宝塔面板的推广以及专业版功能 https://blog.iletter.top/archives/427/ 2025-08-13T21:50:00+08:00 宝塔面板做运维的大部分都知道,简单便捷,虽然1panel出来了,但是还是感觉宝塔面板比较顺手。但是我不是vip用户,宝塔面板也是关闭一些权限,剩下的哪些vip功能还是收费,看着碍眼,索性去照代码里面删掉该部分即可。本教程只适用于宝塔面板v11.0.0。其他版本还需要自行测试。关闭不必要的功能设置->常用设置->面板栏隐藏,隐藏如下功能WAF、WP Tools、多用户、邮局、节点管理去除首页的软件推荐功能。一开始我本以为这个是在utils-lib.js里面带着的,后来通过减慢请求查看到是在请求里面带着的。通过搜索关键词get_pay_type发现,是通过getRecommendContent这个请求函数带过来的。所以我们需要在这里设置返还值为空即将getRecommendContent=()=>useAxios.post("ajax/get_pay_type")替换成getRecommendContent=()=>{return []}去除其他界面的vip功能utils-lib.js去掉如下内容去除SSH登录日志{path:"ssh",name:"ssh",meta:{title:"SSH登录日志"},component:()=>__vitePreload((()=>import("./index52.js?v=1754557180")),__vite__mapDeps([]),import.meta.url)},去除自动部署,{path:"auto-deploy",name:"auto-deploy",meta:{title:"自动部署"},component:()=>__vitePreload((()=>import("./index84.js?v=1754557180").then((e=>e.i))),__vite__mapDeps([]),import.meta.url)}去除面板日报,{path:"daily",name:"daily",meta:{title:"面板日报"},component:()=>__vitePreload((()=>import("./index12.js?v=1754557180")),__vite__mapDeps([]),import.meta.url)}去除安全检测等其他功能,{path:"safe-detect",name:"safe-detect",meta:{title:"安全检测"},component:()=>__vitePreload((()=>import("./index41.js?v=1754557180")),__vite__mapDeps([]),import.meta.url)},{path:"keyword",name:"keyword",meta:{title:"违规词检测"},component:()=>__vitePreload((()=>import("./index42.js?v=1754557180")),__vite__mapDeps([]),import.meta.url)},{path:"php-safe",name:"php-safe",meta:{title:"PHP网站安全"},component:()=>__vitePreload((()=>import("./index43.js?v=1754557180")),__vite__mapDeps([]),import.meta.url)},{path:"intrusion",name:"intrusion",meta:{title:"入侵防御"},component:()=>__vitePreload((()=>import("./index44.js?v=1754557180")),__vite__mapDeps([]),import.meta.url)},{path:"fixed",name:"fixed",meta:{title:"系统加固"},component:()=>__vitePreload((()=>import("./index45.js?v=1754557180")),__vite__mapDeps([]),import.meta.url)},{path:"network-scan",name:"network-scan",meta:{title:"扫描感知"},component:()=>__vitePreload((()=>import("./index46.js?v=1754557180")),__vite__mapDeps([]),import.meta.url)} 将mac mini作为服务器使用 https://blog.iletter.top/archives/426/ 2025-08-11T09:48:00+08:00 此教程有公网ip更好,可以远程访问没有的话只能局域网访问了。之前想买个mac尝尝鲜的,但是受限于昂贵的价格所以买了个丐版的mac mini。but。。但是新鲜感过去之后好久没用了。ps:果然,mac不适合我。但是总不能一直放着呀,事物存在的本身需要让其发挥出所具有的价值才可以,所以准备改造成一个小服务器(8G的内存都可以跑4b的ai了)。系统设置1.用户与群组->打开自动以此身份登录2.节能->打开唤醒以供网络访问3.节能->打开断电后自动启动(有需求打开,无需求可以选择不打开)4.通用->共享->打开远程桌面(有其他需求可以选择性打开)5.屏幕->所有都设置为不锁屏6.如果你有需求可以将dhcp设置为局域网静态ip。穿透设置这里我用的是frp-panel。因为m1芯片属于arm架构的,所以客户端需要下载frp-panel-client-darwin-arm64这一个。开源地址:https://github.com/VaalaCat/frp-panel/搭建流程和操作步骤详见官方文档。面板设置好之后添加客户端。mac 端运行给出的命令即可。如果无法运行需要添加执行权限chmod +x frp-panel-arm设置开机自启动穿透编辑文件vim ~/Library/LaunchAgents/com.frp.panel.plist添加内容<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>com.frp.panel</string> <key>ProgramArguments</key> <array> <string>/Users/您的用户名/Downloads/frp-panel-arm</string> <string>client</string> <string>-s</string> <string>6ed79539-8309-4a84-845d-598568add8d8</string> <string>-i</string> <string>admin.c.macM1-arm</string> <string>--api-url</string> <string>http://152.136.153.72:9000</string> <string>--rpc-url</string> <string>grpc://152.136.153.72:9001</string> </array> <key>RunAtLoad</key> <true/> <key>KeepAlive</key> <true/> </dict> </plist>保存文件后加载launchctl load ~/Library/LaunchAgents/com.frp.panel.plist launchctl start com.frp.panel至此,设置完毕,可以在进程里面查看到frp-panel的进程是否在运行。或者在面板界面查看设备是否上线了。 typecho博客Hello world插件增强 https://blog.iletter.top/archives/424/ 2025-08-07T13:59:32+08:00 后台点击用户名称旁边的hello word会出现提示。直接将该代码替换掉原本的代码即可:<?php namespace TypechoPlugin\HelloWorld; use Typecho\Plugin\PluginInterface; use Typecho\Widget\Helper\Form; use Typecho\Widget\Helper\Form\Element\Text; use Widget\Options; use Widget\Stat; use Typecho\Db; if (!defined('__TYPECHO_ROOT_DIR__')) { exit; } /** * Hello World * * @package HelloWorld * @author 白荼 * @version 1.0.0 */ class Plugin implements PluginInterface { /** * 激活插件方法,如果激活失败,直接抛出异常 */ public static function activate() { \Typecho\Plugin::factory('admin/menu.php')->navBar = __CLASS__ . '::render'; // 添加CSS和JS \Typecho\Plugin::factory('admin/header.php')->header = __CLASS__ . '::header'; } /** * 禁用插件方法,如果禁用失败,直接抛出异常 */ public static function deactivate() { } /** * 获取插件配置面板 * * @param Form $form 配置面板 */ public static function config(Form $form) { /** 分类名称 */ $name = new Text('word', null, 'Hello World', _t('说点什么')); $form->addInput($name); } /** * 个人用户的配置面板 * * @param Form $form */ public static function personalConfig(Form $form) { } /** * 在header中添加CSS和JS */ public static function header($header) { $customCssJs = ' <!-- HelloWorld Plugin CSS --> <style> .helloworld-tooltip { position: absolute; background: #fff; border: 1px solid #ddd; border-radius: 4px; padding: 15px; box-shadow: 0 2px 8px rgba(0,0,0,0.15); z-index: 9999; min-width: 280px; display: none; font-size: 12px; } .helloworld-tooltip h4 { margin: 0 0 10px 0; padding: 0; color: #333; border-bottom: 1px solid #eee; padding-bottom: 8px; font-size: 14px; } .helloworld-tooltip .comparison { background: #f8f9fa; border-left: 3px solid #007cba; padding: 8px 12px; margin: 10px 0; border-radius: 0 4px 4px 0; font-size: 13px; } .helloworld-tooltip ul { list-style: none; padding: 0; margin: 0; } .helloworld-tooltip li { padding: 5px 0; border-bottom: 1px solid #f5f5f5; line-height: 1.5; } .helloworld-tooltip li:last-child { border-bottom: none; } .helloworld-tooltip strong { color: #666; font-weight: normal; } .helloworld-trigger { cursor: pointer; border-bottom: 1px dotted #BBBBBB; } .helloworld-trigger:hover { color: #666 !important; border-bottom-color: #666; } </style> <!-- HelloWorld Plugin JS --> <script> (function() { function initHelloWorldTooltip() { var trigger = document.querySelector(".helloworld-trigger"); var tooltip = document.querySelector(".helloworld-tooltip"); if (trigger && tooltip) { trigger.addEventListener("click", function(e) { e.preventDefault(); e.stopPropagation(); // 计算位置 var rect = trigger.getBoundingClientRect(); var tooltipWidth = tooltip.offsetWidth || 280; var tooltipHeight = tooltip.offsetHeight || 250; // 默认位置 var left = rect.left + window.scrollX; var top = rect.bottom + window.scrollY + 5; // 边界检测 if (left + tooltipWidth > window.innerWidth + window.scrollX) { left = window.innerWidth + window.scrollX - tooltipWidth - 10; } if (top + tooltipHeight > window.innerHeight + window.scrollY) { top = rect.top + window.scrollY - tooltipHeight - 5; } tooltip.style.left = left + "px"; tooltip.style.top = top + "px"; tooltip.style.display = "block"; }); // 点击其他地方关闭 document.addEventListener("click", function(e) { if (!tooltip.contains(e.target) && e.target !== trigger) { tooltip.style.display = "none"; } }); } } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", initHelloWorldTooltip); } else { initHelloWorldTooltip(); } })(); </script>'; return $header . $customCssJs; } /** * 根据字数获取对应的文学著作比较 */ private static function getLiteraryComparison($wordCount) { // 文学著作字数对照表(字数 => 著作名称) $literaryWorks = [ // 百字级 50 => '几行诗句', 100 => '一段朋友圈文字', 200 => '一条微博内容', 500 => '一篇微博长文', // 千字级 1000 => '一页A4纸手写内容', 1500 => '一页打印稿', 2000 => '一篇标准作文', 3000 => '一篇博客文章', 5000 => '一篇短篇小说', 8000 => '一篇中篇小说开头', // 万字级(中国现代文学) 10000 => '《呐喊》', 12000 => '《彷徨》', 15000 => '《朝花夕拾》', 20000 => '《野草》', 25000 => '《故事新编》', 30000 => '《热风》', 35000 => '《坟》', 40000 => '《而已集》', 45000 => '《三闲集》', // 万字级(其他现代文学) 50000 => '《沉沦》(郁达夫)', 55000 => '《春风沉醉的晚上》', 60000 => '《迟桂花》', 65000 => '《茑萝集》', 70000 => '《过去》', 75000 => '《薄奠》', 80000 => '《微雪的早晨》', // 万字级(长篇小说选段) 85000 => '《骆驼祥子》前半部分', 90000 => '《二马》', 95000 => '《离婚》', 100000 => '《骆驼祥子》', 110000 => '《四世同堂》第一部分', 120000 => '《边城》', 130000 => '《湘行散记》', 140000 => '《长河》', 150000 => '《家》', 160000 => '《憩园》', 170000 => '《第四病室》', 180000 => '《春》', 190000 => '《秋》', 200000 => '《寒夜》', // 20万字级 210000 => '《激流三部曲》选段', 220000 => '《爱情三部曲》选段', 230000 => '《抗战三部曲》选段', 240000 => '《人间词话》', 250000 => '《人间喜剧》选段', 260000 => '《巴黎圣母院》', 270000 => '《悲惨世界》选段', 280000 => '《九三年》', 290000 => '《海上劳工》', 300000 => '《笑面人》', // 30万字级 310000 => '《约翰·克利斯朵夫》选段', 320000 => '《红与黑》', 330000 => '《巴马修道院》', 340000 => '《吕西安·娄凡》', 350000 => '《阿尔芒斯》', 360000 => '《法尼娜·法尼尼》', 370000 => '《瓦尼娜·瓦尼尼》', 380000 => '《红屋骑士》', 390000 => '《卡斯特罗修道院院长》', 400000 => '《意大利遗事》', // 40万字级 410000 => '《三个火枪手》', 420000 => '《基督山伯爵》', 430000 => '《黑郁金香》', 440000 => '《二十年后》', 450000 => '《布拉热洛纳子爵》', 460000 => '《玛戈王后》', 470000 => '《蒙梭罗夫人》', 480000 => '《四十五卫士》', 490000 => '《侠隐记》', 500000 => '《红楼梦》(精简版)', // 50万字级 520000 => '《水浒传》选段', 540000 => '《西游记》选段', 560000 => '《三国演义》选段', 580000 => '《儒林外史》', 600000 => '《聊斋志异》选段', 620000 => '《阅微草堂笔记》', 640000 => '《子不语》', 660000 => '《夜雨秋灯录》', 680000 => '《萤窗异草》', 700000 => '《耳食录》', // 70万字级 720000 => '《浮生六记》', 740000 => '《影梅庵忆语》', 760000 => '《秋灯琐忆》', 780000 => '《香艳丛书》选段', 800000 => '《太平广记》选段', 820000 => '《夷坚志》选段', 840000 => '《剪灯新话》', 860000 => '《剪灯余话》', 880000 => '《觅灯因话》', 900000 => '《喻世明言》', // 百万字级 920000 => '《警世通言》', 940000 => '《醒世恒言》', 960000 => '《初刻拍案惊奇》', 980000 => '《二刻拍案惊奇》', 1000000 => '《红楼梦》', 1050000 => '《镜花缘》', 1100000 => '《老残游记》', 1150000 => '《孽海花》', 1200000 => '《官场现形记》', 1250000 => '《二十年目睹之怪现状》', // 百万字级以上 1300000 => '《资本论》第一卷', 1400000 => '《莎士比亚十四行诗集》', 1500000 => '《战争与和平》', 1600000 => '《安娜·卡列尼娜》', 1700000 => '《复活》', 1800000 => '《静静的顿河》', 1900000 => '《被开垦的处女地》', 2000000 => '《一个人的遭遇》', 2100000 => '《静静的顿河》全本', 2200000 => '《钢铁是怎样炼成的》', 2300000 => '《青年近卫军》', 2400000 => '《日瓦戈医生》', 2500000 => '《追忆似水年华》第一卷', 2600000 => '《约翰·克利斯朵夫》', 2700000 => '《马丁·伊登》', 2800000 => '《美国悲剧》', 2900000 => '《嘉莉妹妹》', 3000000 => '《资本论》全三卷', 3100000 => '《莎士比亚全集》悲剧部分', 3200000 => '《莎士比亚全集》喜剧部分', 3300000 => '《莎士比亚全集》历史剧部分', 3400000 => '《莎士比亚十四行诗全集》', 3500000 => '《莎士比亚全集》', 3600000 => '《追忆似水年华》第二卷', 3700000 => '《追忆似水年华》第三卷', 3800000 => '《追忆似水年华》第四卷', 3900000 => '《追忆似水年华》第五卷', 4000000 => '《追忆似水年华》', 4200000 => '《堂吉诃德》', 4400000 => '《神曲》', 4600000 => '《荷马史诗》', 4800000 => '《失乐园》', 5000000 => '《大英百科全书》一卷', 5500000 => '《大英百科全书》两卷', 6000000 => '《牛津英语词典》一卷', 6500000 => '《不列颠百科全书》', 7000000 => '《中国大百科全书》一卷', 7500000 => '《辞海》', 8000000 => '《康熙字典》', 9000000 => '《中华大字典》', 10000000 => '《大英百科全书》全套', 12000000 => '《四库全书》选段', 15000000 => '《永乐大典》残卷', 20000000 => '《古今图书集成》选段' ]; // 找到最接近的著作 $closestWork = '几行文字'; $closestWords = 0; foreach ($literaryWorks as $words => $work) { if ($wordCount >= $words) { $closestWork = $work; $closestWords = $words; } else { break; } } // 计算倍数 if ($closestWords > 0) { $multiple = round($wordCount / $closestWords, 1); if ($multiple >= 2) { return "约等于 {$multiple} 本 {$closestWork} 的字数"; } else { return "约等于 1 本 {$closestWork} 的字数"; } } else { return "约等于几行文字的字数"; } } /** * 获取统计信息 */ private static function getStats() { $db = Db::get(); $user = \Typecho\Widget::widget('Widget_User'); // 最近登录时间 $lastLogin = $user->logged; $lastLoginText = $lastLogin ? date('Y-m-d H:i:s', $lastLogin) : '未知'; // 文章数量 $postCount = $db->fetchObject($db->select(['COUNT(cid)' => 'num']) ->from('table.contents') ->where('type = ?', 'post') ->where('authorId = ?', $user->uid))->num; // 字数统计(简单统计标题和内容长度) $posts = $db->fetchAll($db->select('title', 'text') ->from('table.contents') ->where('type = ?', 'post') ->where('authorId = ?', $user->uid)); $totalWords = 0; foreach ($posts as $post) { $totalWords += mb_strlen($post['title'], 'UTF-8'); $totalWords += mb_strlen(strip_tags($post['text']), 'UTF-8'); } // 最近文章时间 $lastPost = $db->fetchRow($db->select('created') ->from('table.contents') ->where('type = ?', 'post') ->where('authorId = ?', $user->uid) ->order('created', Db::SORT_DESC) ->limit(1)); $lastPostTime = $lastPost ? date('Y-m-d H:i:s', $lastPost['created']) : '暂无文章'; // 获取文学著作比较 $literaryComparison = self::getLiteraryComparison($totalWords); return [ 'lastLogin' => $lastLoginText, 'postCount' => $postCount, 'totalWords' => $totalWords, 'lastPostTime' => $lastPostTime, 'literaryComparison' => $literaryComparison ]; } /** * 插件实现方法 * * @access public * @return void */ public static function render() { $stats = self::getStats(); $word = Options::alloc()->plugin('HelloWorld')->word; // 获取当前登录用户的信息 $user = \Typecho\Widget::widget('Widget_User'); $userName = $user->screenName ?: $user->name; // 优先使用昵称,如果没有则使用登录名 echo '<span class="helloworld-trigger" style="color:#BBBBBB">' . htmlspecialchars($word) . '</span>'; echo '<div class="helloworld-tooltip"> <h4>欢迎回来,' . htmlspecialchars($userName) . '</h4> <div style="margin-bottom:20px;"> 您已经写了 ' . $stats['postCount'] . ' 篇文章,一共 ' . $stats['totalWords'] . ' 字,' . $stats['literaryComparison'] . '。 </div> <ul> <li><strong>最近登录:</strong>' . $stats['lastLogin'] . '</li> <li><strong>最近发文:</strong>' . $stats['lastPostTime'] . '</li> </ul> </div>'; } }