用户关系

提供用户互相关注功能,包括关注/取消关注、查询关注状态、获取关注/粉丝数量、获取关注/粉丝列表等能力。
标识userrelation
版本号1.0
文件大小117.3KB
发布时间2026-05-27
最近更新2026-05-27
PHP兼容>=5.6
作者 meizie
获取
¥20.00
请在您的网站后台-应用商店内购买此应用.

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,未登录为 0
  • window.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)

注意事项

  1. null 保护:调用 getFollowingListgetFollowerListgetFollowCountForTemplate 后,务必检查返回值:
    if(!$list || !is_array($list)) $list = array();
  2. PHP 数组语法:在 <?php ?> 代码块中访问用户字段必须用 $fuser['id'],不能用 $fuser.id。模板标签中用 {$fuser.id}
  3. 事件委托:列表中的关注按钮必须使用 $(document).on('click', '.follow-button', fn) 方式绑定,不能直接 .click()
  4. 状态同步:页面上可能有多个同一用户的关注按钮(Banner + 列表),操作后需同步更新所有按钮状态。
  5. 全局变量:模板 JS 中必须初始化 window.currentUserIdwindow.systemDir,否则 AJAX 请求无法正确发送。
  6. 未登录处理:未登录时按钮应显示"请登录"并跳转登录页,不应发送 AJAX 请求。