亚灿网志

「Typecho」博客系统的美化与优化

每次更改或新增代码后,一定要及时查看控制台是否有报错,使用PC和移动端多次访问确保无误!!!

网站设置

网站后台设置基于宝塔面板

备份设置

需要备份的东西都直接在宝塔后台备份就行,备份网站程序和数据库两个压缩包文件就行了。如果重装网站后更改了数据库账号密码,那么就直接在Typecho程序根目录下的config.inc.php文件中第56行更改数据库连接设置就行了:

/** 定义数据库参数 */
$db->addServer(array (
      'host' => 'localhost',
      'user' => '****',
      'password' => '******',
      'charset' => 'utf8',
      'port' => '*******',
      'database' => '******',
), Typecho_Db::READ | Typecho_Db::WRITE);

网站所有都调整完毕后,可以在宝塔后台安装七牛云官方备份程序:

可以自动将程序备份至七牛云账户。

伪静态设置

在宝塔网站设置中添加伪静态规则:

if (!-e $request_filename) {
    rewrite ^(.*)$ /index.php$1 last;
}

然后在Typecho后台设置地址重写:

CDN加速

将主题下的assets文件夹全部复制至七牛云CDN,使用CDN加速全部主题资源。

本站主题资源assets资源已全部放入七牛云CDN,如果需要进行文件修改,修改后需要在七牛云CDN中的刷新预取中刷新文件链接才会完成文件更新。

[photos]

[/photos]

使用自制404页面

在宝塔后台打开网站配置文件,取消注释第28行:

#ERROR-PAGE-START  错误页配置,可以注释、删除或修改
error_page 404 /404.html;

然后在站点根目录重写404.html

流量限制

后台登陆加密

SQL语句学习

cid=1651的文章评论全部转移至cid=1561的文章下面:

update typecho_comments set cid=1561 where cid=1651;

typecho_contents表中选择cid=1390的文章:

SELECT * FROM `typecho_contents` WHERE cid=1390;

插件设置

ExSearch

第一次使用该插件需要重建索引。

该插件中不能引入JQuery,因为在主题中已经引入,如果再次引入,博客主页会出现空白。

Meting

问题:网易云歌单只能解析第一首歌曲,网易云无论是自己收藏的歌单还是公开歌单,均只能获取第一首,歌手与专辑正常。

解决方案:插件后台填写网易云音乐Cookie。获取方法:打开 https://music.163.com/F12-网络-再次刷新找到 music.163.com项,点击进去找到 cookie的值复制。

如果网站开启PJAX局部刷新,需要设置重载函数:

loadMeting(); // 跳转页面后重新解析播放器
if (typeof aplayers !== 'undefined') { // 跳转页面后暂停播放
    for (var i = 0; i < aplayers.length; i++) {
        try {
            aplayers[i].destroy()
        } catch (e) {}
    }
}

解决(APlayer-Typecho)插件失效问题:可以通过替换插件中的include/Meting.php解决。访问地址:https://github.com/metowolf/Meting,然后复制src/Meting.php代码到插件中QQ音乐即可恢复。原因是插件中的Meting版本为(1.5.2)太过老旧,更换为最新的Meting(1.5.11)即可。

音乐播放提示:

// Meting 音乐播放提示
$('.aplayer-body').each(function () {
    // 单曲模块播放
    $(this).find('div.aplayer-pic').click(function () {
        let songTitle = $(this).next().find('.aplayer-title').text(),
            songAuthor = $(this).next().find('.aplayer-author').text();
        if (!$(this).find("*").eq(0).hasClass('aplayer-play')) {
            VOID.alert('开始播放<b>' + songAuthor + '</b>的<b>《' + songTitle + '》</b>~')
        } else {
            VOID.alert('音乐已暂停~')
        }

    })
    // 歌单列表播放模块
    $(this).next().find('ol>li').click(function () {
        let songTitle = $(this).find('.aplayer-list-title').text(),
            songAuthor = $(this).next().find('.aplayer-list-author').text();
        VOID.alert('开始播放<b>' + songAuthor + '</b>的<b>《' + songTitle + '》</b>~')
    })
})

Sticky

美化代码:

<style>
    .pin-top {
        color: red;
        color: white;
        padding: 4px 26px;
        background: red;
        z-index: 99999999;
        position: absolute;
        right: 0;
        top: 0;
        transform: rotate(45deg) translate(25%, -45%);
        transform-origin: center;
        font-weight: 700;
        font-size: larger
    }
</style>

<span class="pin-top">[置顶]
</span>

网站美化与优化

前期也写过一些关于网站前端美化的文章,加载了自己以前的调整代码:

  1. 网站前端修改
  2. 网站前端美化

OwO表情按钮及弹窗

#comments form .comment-buttons .OwO .OwO-body {
    min-width: 294px
}

.OwO-body {
    z-index: 24!important
}

.OwO .OwO-body .OwO-items .OwO-item {
    padding: 0;
}

#comments form .comment-buttons .OwO .OwO-body .OwO-items-image .OwO-item {
    max-width: calc(25% - 10px)!important
}

#comments form .comment-buttons .OwO {
    left: 12px;
    width: auto;
    top: -60px
}

设置小齿轮位置调整

@media screen and (max-width: 767.5px) {
    .setting-panel-show #setting-panel {
        top: 133px
    }
}

评论区调整

如果文章禁止评论,那么直接评论区什么也不输出:

数据库支持Emoji表情

原生Typecho的数据库编码是utf8,不支持直接插入Emoji表情。要想数据库支持Emoji表情,就得使用utf8mb4编码来支持,于是我们需要修改已有数据库的编码格式,好消息是utf8mb4是utf-8的超集,完全兼容utf-8,修改后,不会影响现有数据。

首先修改MySQL数据库编码,连接数据库:

mysql -u root -p

切换到Typecho数据库:

use 数据库名;

然后执行以下SQL语句:

alter table typecho_comments convert to character set utf8mb4 collate utf8mb4_general_ci;
alter table typecho_contents convert to character set utf8mb4 collate utf8mb4_general_ci;
alter table typecho_fields convert to character set utf8mb4 collate utf8mb4_general_ci;
alter table typecho_metas convert to character set utf8mb4 collate utf8mb4_general_ci;
alter table typecho_options convert to character set utf8mb4 collate utf8mb4_general_ci;
alter table typecho_relationships convert to character set utf8mb4 collate utf8mb4_general_ci;
alter table typecho_users convert to character set utf8mb4 collate utf8mb4_general_ci;

然后修改网站配置文件修改config.inc.php:

'charset' => 'utf8mb4',  # 修改编码为 utf8mb4

测试:?️??????????❤️。

不要忘了每次使用phpMyAdmin操作数据的时候,也要把服务器连接排序规则设置为utf8mb4

经过实际操作,发现虽然这样可以使得数据库中存入Emoji表情,但是当数据库文件导出后,里面的Emoji表情就全部变成了问号……,暂时还没有找到解决办法~

词义化时间

在Typecho的后台,很多地方都是用这种 “XX前” 的词义化时间显示的,简单明了。从这里我们就可以得出,后台是有相关函数,我们只要找到,然后直接使用就可以了—— var/Typecho/I18n.php 。但是官方提供的代码没有“周”和“月”,所以动手完善了一下代码,添加了“周”和“月”,只有1年以及1年以上的时间才会显示完整时间。这样可能比较好点吧。

/**
* 词义化时间
*
* @access public
* @param string $from 起始时间
* @param string $now 终止时间
* @return string
*/
public static function dateWord($from, $now)
{
$between = $now - $from;
/** 如果是一天 */
if ($between >= 0 && $between < 86400 && date('d', $from) == date('d', $now)) {
/** 如果是一小时 */
if ($between < 3600) {
/** 如果是一分钟 */
if ($between < 60) {
if (0 == $between) {
return _t('刚刚');
} else {
return str_replace('%d', $between, _n('刚刚', '%d秒前', $between));
}
}
$min = floor($between / 60);
return str_replace('%d', $min, _n('1分钟前', '%d分钟前', $min));
}
$hour = floor($between / 3600);
return str_replace('%d', $hour, _n('1小时前', '%d小时前', $hour));
}
/** 如果是昨天 */
if ($between > 0 && $between < 172800 
&& (date('z', $from) + 1 == date('z', $now)                             // 在同一年的情况 
|| date('z', $from) + 1 == date('L') + 365 + date('z', $now))) {    // 跨年的情况
return _t('昨天 %s', date('H:i', $from));
}
/** 如果是一个星期以内 */
if ($between > 0 && $between < 604800) {
$day = floor($between / 86400);
return str_replace('%d', $day, _n('1天前', '%d天前', $day));
}
/** 如果是一个星期以上 */
if ($between > 0 && $between < 2592000){
$week = floor($between / 648000);
return str_replace('%d', $week, _n('1周前', '%d周前', $week));
}
/** 如果是一年以内 */
if ($between > 0 && $between < 31557600){
$month = floor($between / 2629800);
return str_replace('%d', $month, _n('1个月前', '%d个月前', $month));
}
/** 如果是一年以上 */
if (date('Y', $from) == date('Y', $now)) {
return date(_t('Y年n月j日 H:i'), $from);
}
return date(_t('Y年m月d日 H:i'), $from);
}

然后在需要输出的地方调用即可,例如:

<?php $comments->dateWord(); ?>

VOID主题在libs/Comments.php文件中第150行,将原来的:

<timedatetime="<?php $this->date('c'); ?>">
    <?php $singleCommentOptions->beforeDate();
  echo date('Y-m-d H:i', $this->created);
  $singleCommentOptions->afterDate(); ?></time>

修改为:

<?php $this->dateWord(); ?>

即可实现评论时间的词义化。

网站用户标识

在评论区区分回头客、老顾客、忠实用户、站长等,站长logo{{学习:zhao chao}}了百度网盘会员网站logo的样式?。

在主题functions.php文件中增加查询数据库并返回顾客等级的函数:

function get_user_level($mail = '')
{
    $db = Typecho_Db::get();
    if ($mail) {
        $count = $db->fetchRow(
            $db->select('COUNT(*)')
                ->from('table.comments')
                ->where('status = ?', 'approved')
                ->where('mail = ?', $mail)
        );
        $commentnum = $count['COUNT(*)'];
        if ($commentnum <= 1) {
            return '';
        } elseif ($commentnum <= 2) {
            return '二进宫';
        } elseif ($commentnum <= 3) {
            return '三回头';
        } elseif ($commentnum <= 5) {
            return '老油条';
        } else {
            return 'OnlyFans';
        }
    } else { // 如果没有输入Email,那么返回评论数据表中所有评论数
        $count = $db->fetchRow(
            $db->select('COUNT(*)')
                ->from('table.comments')
                ->where('status = ?', 'approved')
        );
        return $count['COUNT(*)'];
    }
}

在主题comments.php适当位置插入:

<!-- 增加博主标识 -->
<?php
    echo '<span class="user-logo';
    if ($this->authorId) {
        if ($this->authorId == $this->ownerId) {
            echo ' webmaster">站长'; // 站长增加一个class类名
        }
    } else {
        if (get_user_level($this->mail)) {
            echo '">' . get_user_level($this->mail);  // 普通用户仅有一个user-logo类名
        } else {
            echo '" style="display:none;">'; // php函数返回值为空,说明该用户仅有一条评论,那么久不输出标识
        }
    }
    echo '</span>';
?>

然后根据设置的类名,写出对应的css即可:

.webmaster {
    background: linear-gradient(90deg,#ffeccc,#ffd080);
    color: #64360d !important;
}

.user-logo {
    font-size: 80%;
    display: inline-block;
    background-color: #20b8d4;
    padding: 1px 4px;
    color: white;
    border-radius: 3px;
}

快捷键提交评论

为评论框添加onkeydown事件,当按下快捷键Ctrl+Enter的时候,JS模拟点击评论表单的提交按钮。

<textarea aria-label="评论输入框" class="input-area" rows="5" name="text" id="textarea" 
                                placeholder="输入评论:" 
                                style="resize:none;" onkeydown="if(event.ctrlKey&&event.keyCode==13){document.getElementById('comment-submit-button').click();return false};"></textarea>

评论框背景

评论框样式来源于Handsome主题。

添加css属性即可:

#textarea {
    background-image: #ffffff url('背景图片url地址') no-repeat right;
    transition: all 0.25s ease-in-out 0s;
    background-size: contain;
}

#textarea:focus {
    background-position-y: 150px;
    border-color: #448bff;
    box-shadow: 0 0 0 .2rem rgba(68,139,255,.25);
}

评论框随机昵称

JS部分:

// 随机昵称实现
var a = null,
    b = ["大名鼎鼎", "躲闪", "骄傲", "挺胸", "不知名", "知名", "刚下飞机", "看透一切", "小有名气", "潜心学习"],
    c = ["大黄", "匿名人士", "高质量男性", "女士", "人士", "男孩", "女孩", "贫僧", "道士", "学生", "打工人", "路人甲", "炮灰已", "流氓丙", "土匪丁", "龙套戊"];
$("#get-nickname").on("click", function () {
    const d = $(this);
    $(this).removeClass("shake"),
        $(this).addClass("shake"),
        null != a && (clearTimeout(a), a = null),
        a = setTimeout(function () {
            d.removeClass("shake")
        }, 500),
        _tmp = b[Math.floor(Math.random() * b.length)] + "的" + c[Math.floor(Math.random() * c.length)],
        $(this).prev().val(_tmp)
}
)

前端css动画:

.shake {
    animation-name: shake;
    animation-duration: .1s;
    animation-timing-function: ease-in-out;
    animation-iteration-count: infinite;
    display: inherit;
    transform-origin: center;
}

@keyframes shake {
    2% {
        transform: translate(-1.5px, calc(-50% + .5px)) rotate(1.5deg);
    }

    4% {
        transform: translate(2.5px, calc(-50% + .5px)) rotate(-.5deg);
    }

    6% {
        transform: translate(1.5px, calc(-50% + 1.5px)) rotate(1.5deg);
    }

    8% {
        transform: translate(1.5px, calc(-50% + .5px)) rotate(1.5deg);
    }

    10% {
        transform: translate(-.5px, calc(-50% - .5px)) rotate(.5deg);
    }

    12% {
        transform: translate(-1.5px, calc(-50% - 1.5px)) rotate(-.5deg);
    }

    14% {
        transform: translate(2.5px, calc(-50% + 1.5px)) rotate(1.5deg);
    }

    16% {
        transform: translate(-.5px, calc(-50% - .5px)) rotate(1.5deg);
    }

    18% {
        transform: translate(-1.5px, calc(-50% - .5px)) rotate(.5deg);
    }

    20% {
        transform: translate(-.5px, calc(-50% + 1.5px)) rotate(.5deg);
    }

    22% {
        transform: translate(2.5px, calc(-50% + 2.5px)) rotate(1.5deg);
    }

    24% {
        transform: translate(-.5px, calc(-50% + 1.5px)) rotate(-.5deg);
    }

    26% {
        transform: translate(.5px, calc(-50% - .5px)) rotate(.5deg);
    }

    28% {
        transform: translate(1.5px, calc(-50% + .5px)) rotate(.5deg);
    }

    30% {
        transform: translate(1.5px, calc(-50% + 2.5px)) rotate(1.5deg);
    }

    32% {
        transform: translate(-1.5px, calc(-50% - .5px)) rotate(.5deg);
    }

    34% {
        transform: translate(.5px, calc(-50% + 1.5px)) rotate(.5deg);
    }

    36% {
        transform: translate(-1.5px, calc(-50% + .5px)) rotate(-.5deg);
    }

    38% {
        transform: translate(1.5px, calc(-50% - .5px)) rotate(-.5deg);
    }

    40% {
        transform: translate(2.5px, calc(-50% - .5px)) rotate(.5deg);
    }

    42% {
        transform: translate(2.5px, calc(-50% + 1.5px)) rotate(1.5deg);
    }

    44% {
        transform: translate(.5px, calc(-50% + 1.5px)) rotate(1.5deg);
    }

    46% {
        transform: translate(.5px, calc(-50% + 1.5px)) rotate(.5deg);
    }

    48% {
        transform: translate(-1.5px, calc(-50% - 1.5px)) rotate(-.5deg);
    }

    50% {
        transform: translate(-.5px, calc(-50% - .5px)) rotate(-.5deg);
    }

    52% {
        transform: translate(-1.5px, calc(-50% - .5px)) rotate(-.5deg);
    }

    54% {
        transform: translate(1.5px, calc(-50% + 1.5px)) rotate(.5deg);
    }

    56% {
        transform: translate(.5px, calc(-50% - 1.5px)) rotate(-.5deg);
    }

    58% {
        transform: translate(-.5px, calc(-50% - 2.5px)) rotate(1.5deg);
    }

    60% {
        transform: translate(2.5px, calc(-50% + 1.5px)) rotate(-.5deg);
    }

    62% {
        transform: translate(2.5px, calc(-50% - 1.5px)) rotate(-.5deg);
    }

    64% {
        transform: translate(-.5px, calc(-50% - 2.5px)) rotate(1.5deg);
    }

    66% {
        transform: translate(1.5px, calc(-50% - .5px)) rotate(1.5deg);
    }

    68% {
        transform: translate(.5px, calc(-50% - 1.5px)) rotate(1.5deg);
    }

    70% {
        transform: translate(.5px, calc(-50% - .5px)) rotate(1.5deg);
    }

    72% {
        transform: translate(2.5px, calc(-50% - .5px)) rotate(.5deg);
    }

    74% {
        transform: translate(-.5px, calc(-50% + 2.5px)) rotate(1.5deg);
    }

    76% {
        transform: translate(1.5px, calc(-50% - .5px)) rotate(.5deg);
    }

    78% {
        transform: translate(2.5px, calc(-50% - .5px)) rotate(1.5deg);
    }

    80% {
        transform: translate(1.5px, calc(-50% + 2.5px)) rotate(-.5deg);
    }

    82% {
        transform: translate(-1.5px, calc(-50% + .5px)) rotate(-.5deg);
    }

    84% {
        transform: translate(.5px, calc(-50% - .5px)) rotate(-.5deg);
    }

    86% {
        transform: translate(-1.5px, calc(-50% + .5px)) rotate(.5deg);
    }

    88% {
        transform: translate(2.5px, calc(-50% + .5px)) rotate(.5deg);
    }

    90% {
        transform: translate(1.5px, calc(-50% + 2.5px)) rotate(.5deg);
    }

    92% {
        transform: translate(-1.5px, calc(-50% + 1.5px)) rotate(-.5deg);
    }

    94% {
        transform: translate(-.5px, calc(-50% + 2.5px)) rotate(1.5deg);
    }

    96% {
        transform: translate(1.5px, calc(-50% + .5px)) rotate(.5deg);
    }

    98% {
        transform: translate(1.5px, calc(-50% - .5px)) rotate(-.5deg);
    }

    0%,
    100% {
        transform: translate(0, -50%) rotate(0);
    }
}
注意,这里使用了一种特征的CSS写法,即百分比与px单位同时出现,需要使用语法例如calc(10% - 5px)。之所以这样搞是因为div定位使用了transform: translateY(-50%);,如果你的div没有使用这个属性,那你不需要使用这种写法。

前端html:

<div class="get-nickname">?</div>

评论框placeholder动态变化

// placehold变化
$("#textarea").focus(function () {
    $(this).attr("placeholder", "说点什么吧~")
});

$("#textarea").blur(function () {
    $(this).attr("placeholder", "居然什么也不说,哼~")
});

评论框随机头像

在主题functions.php文件中增加返回随机图片地址的函数:

function local_random_avatar()
{
    $options = Typecho_Widget::widget('Widget_Options');
    $thumb = 'https://cdn.manyacan.com/blog/random_avatar/' . rand(1, 60) . '.png';
    // $avatar = "<img alt='用户头像' src='{$thumb}' class='avatar avatar-50 photo' />";
    echo $thumb;
}

由函数可得,其实就是产生一个随机数,然后把文件路径中的图片全部按顺序命名为数字即可,在合适的地方调用:

<img class="author-avatar" src="<?php local_random_avatar();?>">

替换Gravatar头像为Cravatar头像

Cravatar是Gravatar在中国的完美替代方案,你可以在https://cravatar.cn更新你的头像。

define('__TYPECHO_GRAVATAR_PREFIX__', 'https://cravatar.cn/avatar/');
Cravatar可以默认直接对QQ邮箱解析QQ头像,YYDS!

然后对var/Typecho/Common.php文件中的函数进行修改(932行),增加一个请求参数,当评论用户既没有Gravatar头像也没用QQ邮箱时,解析为随机头像:

public static function gravatarUrl($mail, $size, $rating, $default, $isSecure = false)
{
    if (defined('__TYPECHO_GRAVATAR_PREFIX__')) {
        $url = __TYPECHO_GRAVATAR_PREFIX__;
    } else {
        $url = $isSecure ? 'https://secure.gravatar.com' : 'http://www.gravatar.com';
        $url .= '/avatar/';
    }

    if (!empty($mail)) {
        $url .= md5(strtolower(trim($mail)));
    }

    $url .= '?s=' . $size;
    $url .= '&amp;r=' . $rating;
    $url .= '&amp;d=' . $default;
    // return $url;
    return $url .='retro';  // 增加参数
}

显示评论IP归属地

方法一:直接引入JS文件解析IP地址。

<small style="color: rgba(0,0,0,.8)">来自<span id="ip-<?php $this->theId(); ?>"></span></small>
<script referrerpolicy="no-referrer" src="https://whois.pconline.com.cn/jsDom.jsp?level=3&domId=ip-<?php $this->theId(); ?>&ip=<?php echo $this->ip; ?>"></script>
不支持Pjax刷新,不知道怎么改代码。

方法二:在网站根目录下上传qqwry.dat文件,然后在主题functions.php文件中添加函数:

function convertip($ip)
{
    $ip1num = 0;
    $ip2num = 0;
    $ipAddr1 = "";
    $ipAddr2 = "";
    $dat_path = './qqwry.dat';
    if (!preg_match("/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/", $ip)) {
        return 'IP 数据库路径不对';
    }
    if (!$fd = @fopen($dat_path, 'rb')) {
        return 'IP 数据库路径不正确';
    }
    $ip = explode('.', $ip);
    $ipNum = $ip[0] * 16777216 + $ip[1] * 65536 + $ip[2] * 256 + $ip[3];
    $DataBegin = fread($fd, 4);
    $DataEnd = fread($fd, 4);
    $ipbegin = implode('', unpack('L', $DataBegin));
    if ($ipbegin < 0)
        $ipbegin += pow(2, 32);
    $ipend = implode('', unpack('L', $DataEnd));
    if ($ipend < 0)
        $ipend += pow(2, 32);
    $ipAllNum = ($ipend - $ipbegin) / 7 + 1;
    $BeginNum = 0;
    $EndNum = $ipAllNum;
    while ($ip1num > $ipNum || $ip2num < $ipNum) {
        $Middle = intval(($EndNum + $BeginNum) / 2);
        fseek($fd, $ipbegin + 7 * $Middle);
        $ipData1 = fread($fd, 4);
        if (strlen($ipData1) < 4) {
            fclose($fd);
            return 'System Error';
        }
        $ip1num = implode('', unpack('L', $ipData1));
        if ($ip1num < 0)
            $ip1num += pow(2, 32);

        if ($ip1num > $ipNum) {
            $EndNum = $Middle;
            continue;
        }
        $DataSeek = fread($fd, 3);
        if (strlen($DataSeek) < 3) {
            fclose($fd);
            return 'System Error';
        }
        $DataSeek = implode('', unpack('L', $DataSeek . chr(0)));
        fseek($fd, $DataSeek);
        $ipData2 = fread($fd, 4);
        if (strlen($ipData2) < 4) {
            fclose($fd);
            return 'System Error';
        }
        $ip2num = implode('', unpack('L', $ipData2));
        if ($ip2num < 0)
            $ip2num += pow(2, 32);
        if ($ip2num < $ipNum) {
            if ($Middle == $BeginNum) {
                fclose($fd);
                return 'Unknown';
            }
            $BeginNum = $Middle;
        }
    }
    $ipFlag = fread($fd, 1);
    if ($ipFlag == chr(1)) {
        $ipSeek = fread($fd, 3);
        if (strlen($ipSeek) < 3) {
            fclose($fd);
            return 'System Error';
        }
        $ipSeek = implode('', unpack('L', $ipSeek . chr(0)));
        fseek($fd, $ipSeek);
        $ipFlag = fread($fd, 1);
    }
    if ($ipFlag == chr(2)) {
        $AddrSeek = fread($fd, 3);
        if (strlen($AddrSeek) < 3) {
            fclose($fd);
            return 'System Error';
        }
        $ipFlag = fread($fd, 1);
        if ($ipFlag == chr(2)) {
            $AddrSeek2 = fread($fd, 3);
            if (strlen($AddrSeek2) < 3) {
                fclose($fd);
                return 'System Error';
            }
            $AddrSeek2 = implode('', unpack('L', $AddrSeek2 . chr(0)));
            fseek($fd, $AddrSeek2);
        } else {
            fseek($fd, -1, SEEK_CUR);
        }
        while (($char = fread($fd, 1)) != chr(0))
            $ipAddr2 .= $char;
        $AddrSeek = implode('', unpack('L', $AddrSeek . chr(0)));
        fseek($fd, $AddrSeek);
        while (($char = fread($fd, 1)) != chr(0))
            $ipAddr1 .= $char;
    } else {
        fseek($fd, -1, SEEK_CUR);
        while (($char = fread($fd, 1)) != chr(0))
            $ipAddr1 .= $char;
        $ipFlag = fread($fd, 1);
        if ($ipFlag == chr(2)) {
            $AddrSeek2 = fread($fd, 3);
            if (strlen($AddrSeek2) < 3) {
                fclose($fd);
                return 'System Error';
            }
            $AddrSeek2 = implode('', unpack('L', $AddrSeek2 . chr(0)));
            fseek($fd, $AddrSeek2);
        } else {
            fseek($fd, -1, SEEK_CUR);
        }
        while (($char = fread($fd, 1)) != chr(0)) {
            $ipAddr2 .= $char;
        }
    }
    fclose($fd);
    if (preg_match('/http/i', $ipAddr2)) {
        $ipAddr2 = '';
    }
    $ipaddr = "$ipAddr1 $ipAddr2";
    $ipaddr = preg_replace('/CZ88.NET/is', '', $ipaddr);
    $ipaddr = preg_replace('/^s*/is', '', $ipaddr);
    $ipaddr = preg_replace('/s*$/is', '', $ipaddr);
    if (preg_match('/http/i', $ipaddr) || $ipaddr == '') {
        $ipaddr = '可能来自火星';
    }
    $ipaddr = iconv('gbk', 'utf-8//IGNORE', $ipaddr);
    return $ipaddr;
}

然后在需要comments.php文件中需要输出的位置使用:

<?php echo convertip($this->ip); ?>

或者:

<?php echo convertip($comments->ip); ?>

即可将评论者IP地址转化为物理位置。

获取站长最后更新时间

根据文章数据表table.contents、评论数据表table.comments查询站长最后修改文章、新建文章、评论的最新评论,然后获取最后离开的时间。

在主题文件functions.php中添加函数(需要手动填入站长邮箱):

// 获取站长最后来网站的时间(创建文章、修改文章、发表说说)
function get_last_update()
{
    $now = time();
    $db = Typecho_Db::get();
    $prefix = $db->getPrefix();
    $create = $db->fetchRow($db->select('created')->from('table.contents')->limit(1)->order('created', Typecho_Db::SORT_DESC)); // 最后创建文章的时间
    $update = $db->fetchRow($db->select('modified')->from('table.contents')->limit(1)->order('modified', Typecho_Db::SORT_DESC)); // 最后修改文章的时间
    $comment_create = $db->fetchRow($db->select('created')->from('table.comments')->limit(1)->order('created', Typecho_Db::SORT_DESC)->where('author = ?', 'Yacan Man')); // 站长最后评论的时间
    $max_timer = max($create['created'], $update['modified'], $comment_create['created']);
    echo Typecho_I18n::dateWord($max_timer, $now);
}

然后在需要输出的地方调用即可:

<?php echo get_last_update(); ?>

文章热度排行

无意间从百度热搜看到的热度排行样式,感觉还不错,偷过来{{学习:chao xi}}一下。

在主题下的funtions.php中写入函数:

function getHotComments($limit = 10)
{
    $db = Typecho_Db::get();
    $result = $db->fetchAll(
        $db->select()->from('table.contents')
            ->where('status = ?', 'publish')
            ->where('type = ?', 'post')
            ->where('created <= unix_timestamp(now())', 'post') //添加这一句避免未达到时间的文章提前曝光
            ->limit($limit)
            ->order('viewsNum', Typecho_Db::SORT_DESC)
    );
    if ($result) {
        foreach ($result as $val) {
            $val = Typecho_Widget::widget('Widget_Abstract_Contents')->push($val);
            $post_title = htmlspecialchars($val['title']);
            $permalink = $val['permalink'];
            if ($val['commentsNum'] >= 10) {
                echo '<li class="hot">';
            } else {
                echo '<li>';
            }
            // 为什么最后要多输出一个<i>标签?为了换行时伪元素能够被挤下去到第二行
            echo '<a href="' . $permalink . '" title="' . $post_title . '" target="_self">' . $val['title'] . '</a> <small>阅读' . $val['viewsNum'] . ' / 评论' . $val['commentsNum'] . '</small><i></i></li>';
        }
    }
}

然后再在主题文件夹下的Archives.php文件中的合适地方调用该函数:

<!--PHP输出热门文章-->
<div class=".popular-articles yue float-up">
    <h2>热门文章排行</h2>
    <ol>
        <?php getHotComments('20');?>
    </ol>
</div>

最后增加css美化:

.popular-articles ol>li:first-child {
    color: #FE2D46;
}

.popular-articles ol>li:nth-of-type(2) {
    color: #F60;
}

.popular-articles ol>li:nth-of-type(3) {
    color: #FAA90E;
}

.popular-articles ol>li.hot i::after {
    content: '热';
    color: #fafafa;
    position: absolute;
    background-color: #F60;
    display: inline-block;
    border-radius: 5px;
    padding: 0 3px;
    right: -1.7em;
    transform: translateY(-50%);
    top: 50%;
    font-style: normal;
    font-size: 80%;
}

.popular-articles ol>li.hot i {
    position: relative;
}

统计全部文章阅读数

public static function getViewNum()
{
    $db= Typecho_Db::get();
    $query= $db->select('sum(viewsNum)')->from('table.contents');
    $result = $db->fetchAll($query);
    return $result[0]['sum(`viewsNum`)'];
}

然后在合适的地方输出即可:

<?php echo get_sum_view_num(); ?>

文章页末尾调整

首先是PHP判断,只有文章是post类型的时候输出,独立页面不输出:

<?php if ($this->is('post')) : ?>
    // ...
<?php endif; ?>

文章结束END提示:

<div style="margin: 20px auto;width: fit-content;">----- <span style="color: white;background-color: black;padding: 0 5px;font-size: .7rem;">END</span> -----</div>

版权信息:

<p class="notice" style="text-indent:0em">
    本文作者:<a href="<?php $this->author->permalink(); ?>" rel="author"> <?php $this->author(); ?></a><br>
    本文链接:<a href="<?php $this->permalink(); ?>"><?php $this->permalink(); ?></a><br>
    版权声明:本文章采用<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><i>&nbsp;<strong>知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议&nbsp;</strong></i></a>。
</p>

标签云和点赞调整(使用flex布局):

.tag-and-reward {
    flex: 2;
    display: flex;
    justify-content: space-around;
    border-top: 1px solid #ccc;
    padding: 1rem 0;
    border-bottom: 1px solid #ccc;
    margin: 1rem 0;
}

@media screen and (max-width:767px) {
    .tag-and-reward {
        flex-direction: column;
    }

    .tag-and-reward>div:nth-of-type(1) {
        margin: 0 auto 1rem;
    }
    .tag-and-reward>div:nth-of-type(2) {
        display: none;
    }
}

.tag-and-reward>div:nth-of-type(1) {
    border: 1px dotted #ccc;
    height: 70%;
    padding: .3rem .2rem;
    width: fit-content
}

.tag-and-reward>div:nth-of-type(1)>a {
    margin-bottom: unset;
}

.tag-and-reward>div:nth-of-type(1)>a:nth-last-of-type(1) {
    margin-right: unset;
}

.tag-and-reward>div:nth-of-type(2) {
    width: 1px;
    background-color: #ccc;
}

.tag-and-reward>div:nth-of-type(3) {
    padding: unset;
    border: unset;
    text-align: center;
}

.tag-and-reward>div:nth-of-type(3)>a {
    margin-bottom: unset
}

.tag-and-reward>div:nth-of-type(3)>a:nth-of-type(3) {
    margin-right: unset
}

上一篇文章与下一篇文章更改:

<?php if ($prev) : ?>
    <div class="prev">
        <a href="<?php $prev->permalink(); ?>">
            <h2><?php $prev->title(); ?></h2>
        </a>
        <!-- 如果有摘要就输出摘要,没有摘要输出一段文字 -->
        <?php
        if ($prev->fields->excerpt != '') {
            echo "<p>{$prev->fields->excerpt}</p>";
        } else {
            // 移动端少输出一点字
            if (Utils::isMobile())
                $prev->excerpt(60);
            else
                $prev->excerpt(80);
        }
        ?>
    </div>
<?php else : ?>
    <div class="prev">
        <h2>考古结束~</h2>
    </div>
<?php endif; ?>

移动端展示

[photos]

[/photos]

当前页面是本站的「Google AMP」版。查看和发表评论请点击:完整版 »