上篇 WordPress 文章中添加了文章阅读次数;本篇添加一个点赞的功能;样式是下图这样,或者直接拉到文章底部进行体验;
该功能可以在一定程度上得到访客的反馈,所以试着加一下该功能;原理是利用上篇中提到的 meta 标签/元数据 进行数据的存储;该功能涉及的逻辑如下:
- 访客觉得内容有用,可以点个赞,点赞区域显示已点赞状态;
- 如果误操作,可以再点一次进行取消,点赞区域恢复默认状态;
- 用户对文章的点赞行为存储在 localStorage/cookie 中;
- 如果未清除 localStorage/cookie,每篇文章最多点赞一次;
- 如果清除了值或者换了浏览器,可以再次进行点赞;
- wp 本身就没有针对访客的登录系统,想要完全真实有效的数据不可能,所以点赞数据仅供参考;
- 最初设计的是两个按钮,顶和踩,后来发现逻辑太过复杂,需要判断当前点的是顶还是踩、当前点击本身的状态、另一个按钮的状态(两个按钮互斥)、之前是否顶或踩过,变量太多,加这么一个本来就不是很准确的功能,太浪费资源;
下面是修改的步骤:
1、添加后端操作
打开主题根目录下的 functions.php,在最后添加如下代码:
define('my_up', 'my_up');
function get_meta_up()
{
return get_int_meta(my_up);
}
// 获取 int 类型的 meta 标签
function get_int_meta($key)
{
global $post;
$post_ID = $post->ID;
$count = (int)get_post_meta($post_ID, $key, true);
return $count;
}
// ajax 请求
function ajax_useful()
{
$key = my_up;
$type = $_POST['type'];
$post_id = $_POST['id'];
// 当前请求为get方式或当前的post_id没有对应文章,直接结束
if ($_SERVER['REQUEST_METHOD'] == 'GET' || !get_post($post_id)) return wp_die();
$dir = $type == '1' ? 1 : -1;
$post_count = (int)get_post_meta($post_id, $key, true) + $dir;
if (!update_post_meta($post_id, $key, $post_count)) {
$post_count = 1;
add_post_meta($post_id, $key, $post_count, true);
}
$res = array(code => 1, realCount => $post_count);
header('Content-Type: application/json,charset=utf-8');
echo json_encode($res);
wp_die();
}
add_action('wp_ajax_useful', 'ajax_useful');
add_action('wp_ajax_nopriv_useful', 'ajax_useful');
- 其中 get_int_meta 方法和 meta 的操作在 上篇 中有提到,这里就不再赘述;
- $_SERVER[‘REQUEST_METHOD’] – 获取请求方法;
- $_POST[‘id’] – 获取请求参数值;
- header(”) – 直接设置响应头;
- json_encode() – 将响应数据格式化为 json 数据;
- wp_ajax_自定义action – ajax 钩子,按照命名规则就能捕获 ajax 发出的请求;wp 中 ajax 的请求 url 是相同的,需要通过 action 来区分是走哪个分支,所以前端 ajax 请求的时候需要在 data 中显式声明 action 这个属性;wp_ajax_ 前缀表示已登录用户走的 action/分支;
- wp_ajax_nopriv_自定义action – 同样的 ajax 钩子,这个前缀表示未登录用户走的 action/分支,系统通过前缀做区分,可以控制两种用户走不同的分支,这里我是走的同一个分支;
- wp_die() – 不知道为什么调用这个,详情参考 这里
2、添加页面结构
我把点赞模块放在了文章内容之后,底部导航(上一篇/下一篇)之前;
打开主题根目录下的 single.php,
在 the_post_nagivation() 之前,添加 html 代码(中间的 echo 为新增):
get_template_part('template-parts/content', get_post_type());
echo '<div class="useful">
<h6>如果这篇文章对你有用,可以点击下面的按钮告诉我</h6>
<i class="fa fa-thumbs-o-up" title="对我有用"></i>
<p>' . get_meta_up() . '</p>
</div>';
the_post_navigation(……
我用的主题中包含 iconfont,其中 fa-thumbs-o-up 为空心的小手,fa-thumbs-up 为实心小手,分别对应未点赞和已点赞两个状态;
底部 script 标签中新增 js 代码:
(function () {
var $ = jQuery;
$(function () {
//highCode();
vote();
});
// 投票
function vote() {
var cookie = {
set: function (name, value, expiredays) {
var exdate = new Date();
expiredays = expiredays || 0;
exdate.setDate(exdate.getDate() + expiredays);
document.cookie = name + "=" + encodeURI(value) + ";expires=" + exdate.toGMTString();
},
get: function (name) {
var arr, reg = new RegExp("(^| )" + name + "=([^;]*)(;|$)");
if (arr = document.cookie.match(reg)) return decodeURIComponent(arr[2]);
else return null;
}
};
var url = "<?php echo admin_url('admin-ajax.php')?>";
var id = '<?php the_ID(); ?>';
var cls = 'fa-thumbs-up';
var key = 'meta_' + id;
var isRequest = false; // 是否在请求中
var $box = $('.useful');
// 投过票
var value = String(getKey(key));
if (value === '1') $box.find('i').addClass(cls);
$box.on('click', 'i', function () {
var $item = $(this);
var $par = $item.parent();
var sel = $item.hasClass(cls) ? 0 : 1;// 当前是选中的,点击取消
var $p = $par.find('p');
if (isRequest) return;
isRequest = true;
$.ajax({
url: url,
type: 'post',
data: {
action: 'useful',
id: id, // post_id
type: sel // 选中还是取消 0-取消
},
success: function (resp) {
console.log(resp);
if (resp.code !== 1) return alert('操作失败,请稍后再试');
var arr = sel === 0 ? ['removeClass', -1] : ['addClass', +1];
$p.text(Number($p.text()) + arr[1]); // 点赞数量最好前端处理,因为后端返回值可能不是±1
setKey(key, sel);
$item[arr[0]](cls);
},
error: function () {
alert('操作失败,请稍后再试');
},
complete: function () {
isRequest = false;
}
});
});
function setKey(key, value) {
if (window.localStorage) localStorage.setItem(key, value);
else cookie.set(key, value, 30);
}
function getKey(key) {
if (window.localStorage) return localStorage.getItem(key);
return cookie.get(key);
}
}
})();
3、添加 CSS 样式
打开根目录下 style.css,在最后添加下面的 css,这部分也可以忽略,样式根据主题的不同有所区别:
/* 点赞 */
.useful {
margin: 40px auto 20px;
text-align: center;
}
.useful h6 {
font-weight: normal;
font-size: 14px;
padding-bottom: 10px;
}
.useful p {
font-size: 18px;
color: #666;
}
.useful i {
width: 50px;
height: 50px;
text-align: center;
line-height: 50px;
border-radius: 100px;
font-size: 22px;
margin-bottom: 4px;
cursor: pointer;
color: #5fba7d;
border: 1px solid #5fba7d;
}
.useful p {
color: #5fba7d;
-webkit-user-select:none;
-moz-user-select:none;
-o-user-select:none;
user-select:none;
}
.useful i:hover {
color: #0BBA2F;
border-color: #0BBA2F;
}
现在已经在 wp 中加了三个功能,今天登录后台提示主题有新版本,突然发现,如果更新主题所有的改动就没了… 查了下文档,得用子主题解决这个问题;
子主题 style.css 和页面模板是直接覆盖父主题对应文件的;如果我更新了主题(大概率是样式/页面模板的变动),然后又用子主题模板覆盖新的主题模板,并没有用到新主题的功能,我还更新干啥… 所以目前还不是问题,不用解决;
顺便说一下,我本地跑的 wp 代码放在了git上,每个功能、样式的变动都有对应的commit,即使线上出了问题,也可以直接覆盖过去;