UserRelation 用户关系插件
概述
UserRelation 是 1CMS 的用户关注插件,提供用户互相关注功能,包括关注/取消关注、查询关注状态、获取关注/粉丝数量、获取关注/粉丝列表等能力。
安装
将 userrelation 目录放入 app/ 目录,在后台插件管理中启用即可。插件会自动创建 userrelation 数据表并注册路由。
数据表结构
| 字段 | 类型 | 说明 |
|---|---|---|
| id | int | 自增主键 |
| user_id | int | 关注者用户ID |
| following_id | int | 被关注者用户ID |
| addtime | int | 关注时间戳 |
一条记录表示:user_id 关注了 following_id。
后端 API(AJAX 路由)
所有 AJAX 请求的 URL 前缀为 window.systemDir + 'userrelation/',即 /userrelation/。
1. 关注/取消关注
路由:POST /userrelation/follow
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| user_id | int | 是 | 目标用户ID |
| action_type | string | 是 | follow 关注 / unfollow 取消关注 |
返回示例:
{"status": "success", "msg": "关注成功"}
{"status": "success", "msg": "取消关注成功"}
{"status": "error", "msg": "请先登录"}
{"status": "error", "msg": "不能关注自己"}
{"status": "error", "msg": "您已经关注过了"}
{"status": "error", "msg": "参数错误"}
2. 查询是否已关注
路由:POST /userrelation/isFollowing
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| following_id | int | 是 | 目标用户ID |
返回示例:
{"status": "success", "data": true}
{"status": "success", "data": false}
{"status": "error", "msg": "请先登录"}
3. 获取关注/粉丝数量
路由:POST /userrelation/getFollowCount
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| user_id | int | 是 | 目标用户ID |
返回示例:
{
"status": "success",
"data": {
"following": 10,
"follower": 5
}
}
后端模板调用(PHP)
以下方法在模板中通过 C() 函数调用,不需要 AJAX,适合服务端渲染场景。
1. 获取关注/粉丝数量
<?php $followCount = C('userrelation:getFollowCountForTemplate', $userId); ?>
返回:array('following' => 10, 'follower' => 5)
2. 获取关注列表
<?php $followingList = C('userrelation:getFollowingList', $userId); ?>
返回:用户数组,每个元素包含用户信息 + relation_addtime(关注时间戳)。返回为空时返回 array()。
3. 获取粉丝列表
<?php $followerList = C('userrelation:getFollowerList', $userId); ?>
返回:用户数组,每个元素包含用户信息 + relation_addtime(关注时间戳)。返回为空时返回 array()。
前端完整集成指南(结合 user.php 实战)
以下内容基于 app/meizie/user.php 的真实实现,展示如何在用户主页中完整集成用户关系功能。
第一步:服务端渲染——准备数据
在模板 PHP 区域获取关注/粉丝数量,用于页面初始展示:
<?php
$profileUser = C('cms:user:get', $_GET['id']);
$followCount = C('userrelation:getFollowCountForTemplate', $_GET['id']);
if(!$followCount) $followCount = array('following' => 0, 'follower' => 0);
?>
第二步:HTML——Banner 区域展示关注/粉丝数 + 关注按钮
<div class="user-profile-banner">
<div class="user-banner-info">
<div class="user-banner-meta">
<h1 class="user-banner-name">{if $profileUser['username']}{$profileUser['username']}{else}{$profileUser['hash']}{/if}</h1>
<div class="user-banner-stats">
<span class="user-stat-item"><strong>{$followCount['following']}</strong><em>关注</em></span>
<span class="user-stat-item"><strong>{$followCount['follower']}</strong><em>粉丝</em></span>
</div>
</div>
<div class="user-banner-action">
<button class="layui-btn follow-button" data-user-id="{$_GET['id']}">关注</button>
</div>
</div>
</div>
要点:
- 关注按钮必须有
class="follow-button"和data-user-id="目标用户ID" - 已关注状态通过 JS 异步查询后添加
follow-btn-active类(见第四步) - 关注/粉丝数用
.user-stat-item:eq(0) strong/:eq(1) strong选择器,方便 JS 动态更新
第三步:HTML——关注/粉丝列表 Tab
<!-- 关注列表 Tab -->
<div class="layui-tab-item">
<?php $followingList = C('userrelation:getFollowingList', $_GET['id']); ?>
<?php if(!$followingList || !is_array($followingList)) $followingList = array(); ?>
{if !count($followingList)}
<div class="post"><h2 class="post-title">暂无关注</h2></div>
{else}
<div class="user-relation-list">
<div class="user-relation-item">
{loop $followingList as $fuser}
<div class="user-relation-card" data-user-id="{$fuser.id}">
<a href="/user/{$fuser.id}" class="user-relation-avatar">
{if $fuser['avatar']}
<img src="{$fuser['avatar']}" alt="{$fuser['hash']}">
{else}
<img src="{template}img/avatar.jpg" alt="{$fuser['hash']}">
{/if}
</a>
<a href="/user/{$fuser.id}" class="user-relation-name">
{if $fuser['username']}{$fuser['username']}{else}{$fuser['hash']}{/if}
</a>
<?php
$currentUid = C('admin:nowUser');
$isFollowedByMe = false;
if($currentUid && $currentUid != $fuser['id']){
$checkRow = one(array(
'table' => 'userrelation',
'where' => array('user_id' => $currentUid, 'following_id' => $fuser['id'])
));
$isFollowedByMe = $checkRow ? true : false;
}
?>
{if $currentUid && $currentUid == $fuser.id}
<span class="user-relation-self">自己</span>
{elseif $currentUid && $currentUid != $fuser.id}
{if $isFollowedByMe}
<button class="layui-btn follow-button follow-btn-active" data-user-id="{$fuser.id}">已关注</button>
{else}
<button class="layui-btn follow-button" data-user-id="{$fuser.id}">关注</button>
{/if}
{/if}
</div>
{/loop}
</div>
</div>
{/if}
</div>
<!-- 粉丝列表 Tab(结构相同,调用 getFollowerList) -->
<div class="layui-tab-item">
<?php $followerList = C('userrelation:getFollowerList', $_GET['id']); ?>
<?php if(!$followerList || !is_array($followerList)) $followerList = array(); ?>
<!-- ... 同上结构,将 $followingList 换成 $followerList ... -->
</div>
要点:
- 列表中每个用户卡片必须有
class="user-relation-card"和data-user-id - 关注状态通过
one()查表判断,已关注加follow-btn-active类 - 自己显示"自己"标签,不显示关注按钮
第四步:JS——初始化全局变量
layui.use(['layer'], function(){
var $ = layui.$;
var layer = layui.layer;
// 必须初始化这两个全局变量
window.currentUserId = <?php echo C('admin:nowUser') ? C('admin:nowUser') : 0; ?>;
window.systemDir = '<?php echo $GLOBALS['C']['SystemDir']; ?>';
要点:
window.currentUserId:当前登录用户ID,未登录为 0window.systemDir:系统目录路径,AJAX 请求 URL 前缀
第五步:JS——Banner 关注按钮初始化
Banner 区域的关注按钮需要先异步查询关注状态,再绑定点击事件:
var bannerBtn = $('.user-banner-action .follow-button');
var profileUserId = bannerBtn.data('user-id');
if(!window.currentUserId){
// 未登录:按钮变为"请登录"
bannerBtn.text('请登录');
bannerBtn.addClass('follow-btn-disabled');
bannerBtn.off('click').on('click', function(){
window.location.href = '/admin?do=admin:login';
});
} else {
// 已登录:异步查询关注状态
$.ajax({
url: window.systemDir + 'userrelation/isFollowing',
type: 'POST',
data: { following_id: profileUserId },
dataType: 'json',
success: function(response){
if(response.status === 'success'){
if(response.data){
bannerBtn.text('已关注');
bannerBtn.addClass('follow-btn-active');
} else {
bannerBtn.text('关注');
bannerBtn.removeClass('follow-btn-active');
}
}
}
});
// 绑定点击事件
bannerBtn.click(function(){
if(bannerBtn.hasClass('follow-btn-disabled')) return;
var isFollowing = bannerBtn.hasClass('follow-btn-active');
var actionType = isFollowing ? 'unfollow' : 'follow';
$.ajax({
url: window.systemDir + 'userrelation/follow',
type: 'POST',
data: { user_id: profileUserId, action_type: actionType },
dataType: 'json',
success: function(response){
if(response.status === 'success'){
if(isFollowing){
bannerBtn.text('关注');
bannerBtn.removeClass('follow-btn-active');
} else {
bannerBtn.text('已关注');
bannerBtn.addClass('follow-btn-active');
}
layer.msg(response.msg);
updateFollowCount(profileUserId);
} else if(response.msg === '请先登录'){
window.location.href = window.systemDir + '?do=admin:login';
} else {
layer.msg(response.msg || '操作失败', {icon: 5});
}
}
});
});
}
第六步:JS——列表中关注按钮(事件委托)
列表中的关注按钮是动态渲染的,必须用事件委托:
$(document).on('click', '.user-relation-card .follow-button', function(e){
e.preventDefault();
var $btn = $(this);
var $card = $btn.closest('.user-relation-card');
var targetUserId = $btn.data('user-id');
if(!targetUserId) return;
var isFollowing = $btn.hasClass('follow-btn-active');
var actionType = isFollowing ? 'unfollow' : 'follow';
$.ajax({
url: window.systemDir + 'userrelation/follow',
type: 'POST',
data: { user_id: targetUserId, action_type: actionType },
dataType: 'json',
success: function(response){
if(response.status === 'success'){
// 更新当前按钮
if(isFollowing){
$btn.text('关注').removeClass('follow-btn-active');
} else {
$btn.text('已关注').addClass('follow-btn-active');
}
// 同步更新页面中同一用户的其他按钮
$('.user-relation-card .follow-button[data-user-id="' + targetUserId + '"]').not($btn).each(function(){
if(isFollowing){
$(this).text('关注').removeClass('follow-btn-active');
} else {
$(this).text('已关注').addClass('follow-btn-active');
}
});
layer.msg(response.msg);
updateFollowCount(profileUserId);
} else if(response.msg === '请先登录'){
window.location.href = window.systemDir + '?do=admin:login';
} else {
layer.msg(response.msg || '操作失败', {icon: 5});
}
}
});
});
要点:
- 必须用
$(document).on('click', '.user-relation-card .follow-button', fn)事件委托 - 操作后需同步页面中同一用户的所有按钮状态
- 如果是自己的主页,取消关注后可从关注列表中移除卡片(fade out + remove)
第七步:JS——关注/粉丝数实时更新
关注或取消关注后,需要刷新页面上的数字:
function updateFollowCount(userId){
// 更新被查看用户的关注/粉丝数(Banner 区域)
$.ajax({
url: window.systemDir + 'userrelation/getFollowCount',
type: 'POST',
data: { user_id: userId },
dataType: 'json',
success: function(resp){
if(resp.status === 'success'){
$('.user-stat-item:eq(0) strong').text(resp.data.following);
$('.user-stat-item:eq(1) strong').text(resp.data.follower);
}
}
});
// 更新当前登录用户自己的关注/粉丝数(侧边栏,如果有)
if(window.currentUserId){
$.ajax({
url: window.systemDir + 'userrelation/getFollowCount',
type: 'POST',
data: { user_id: window.currentUserId },
dataType: 'json',
success: function(resp){
if(resp.status === 'success'){
$('.login-stat-item:eq(0) strong').text(resp.data.following);
$('.login-stat-item:eq(1) strong').text(resp.data.follower);
}
}
});
}
}
前端调用 API 速查表
| 功能 | URL | 方法 | 参数 | 返回 |
|---|---|---|---|---|
| 关注/取消关注 | window.systemDir + 'userrelation/follow' |
POST | user_id, action_type(follow/unfollow) |
{status, msg} |
| 查询关注状态 | window.systemDir + 'userrelation/isFollowing' |
POST | following_id |
{status, data: bool} |
| 获取关注/粉丝数 | window.systemDir + 'userrelation/getFollowCount' |
POST | user_id |
{status, data: {following, follower}} |
CSS 类名约定
| 类名 | 说明 |
|---|---|
.follow-button |
关注按钮基础类,必须设置 |
.follow-btn-active |
已关注状态,按钮文字变为"已关注" |
.follow-btn-disabled |
禁用状态(未登录时) |
.user-relation-card |
列表中的用户卡片容器 |
.user-relation-self |
"自己"标签样式 |
.user-stat-item |
Banner 中关注/粉丝数容器 |
文件说明
| 文件 | 说明 |
|---|---|
| userrelation.php | 插件主类,包含所有后端逻辑和 API |
| userrelation.config | 插件配置信息(名称、版本、依赖等) |
| userrelation.data.php | 插件安装初始数据(路由注册等) |
| userrelation.js | 前端交互 JS 封装(可选引入,也可直接在模板中写 JS) |
注意事项
- null 保护:调用
getFollowingList、getFollowerList、getFollowCountForTemplate后,务必检查返回值:if(!$list || !is_array($list)) $list = array(); - PHP 数组语法:在
<?php ?>代码块中访问用户字段必须用$fuser['id'],不能用$fuser.id。模板标签中用{$fuser.id}。 - 事件委托:列表中的关注按钮必须使用
$(document).on('click', '.follow-button', fn)方式绑定,不能直接.click()。 - 状态同步:页面上可能有多个同一用户的关注按钮(Banner + 列表),操作后需同步更新所有按钮状态。
- 全局变量:模板 JS 中必须初始化
window.currentUserId和window.systemDir,否则 AJAX 请求无法正确发送。 - 未登录处理:未登录时按钮应显示"请登录"并跳转登录页,不应发送 AJAX 请求。
