refactor: space页面改为卡片式关注列表布局
- 重写 space/index.html:左右分栏,左侧导航栏 + 右侧卡片网格 - 自己空间:显示全部关注用户卡片(头像/用户名/简介/已关注标签)+ 设置入口 - 他人空间:简化侧栏(Ta的关注/粉丝),隐私关闭时显示空状态插图 - Model UserFollow 新增头像/简介联表字段 - Repository ListFollowing/ListFollowers SELECT 增加 avatar/bio - Controller ShowSpace 注入 FollowService,获取关注列表数据 - space.css 完全重写为卡片网格布局 + 响应式适配 - follow.css 清理旧 .follow-btn/.follow-links 无用样式
This commit is contained in:
@ -5,6 +5,8 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"metazone.cc/metalab/internal/common"
|
"metazone.cc/metalab/internal/common"
|
||||||
|
"metazone.cc/metalab/internal/model"
|
||||||
|
"metazone.cc/metalab/internal/service"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
@ -12,6 +14,7 @@ import (
|
|||||||
// SpaceController 用户空间控制器
|
// SpaceController 用户空间控制器
|
||||||
type SpaceController struct {
|
type SpaceController struct {
|
||||||
spaceService spaceUseCase
|
spaceService spaceUseCase
|
||||||
|
followSvc followUseCase
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSpaceController 构造函数
|
// NewSpaceController 构造函数
|
||||||
@ -19,6 +22,11 @@ func NewSpaceController(spaceService spaceUseCase) *SpaceController {
|
|||||||
return &SpaceController{spaceService: spaceService}
|
return &SpaceController{spaceService: spaceService}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithFollowService 注入关注服务(可选依赖)
|
||||||
|
func (ctrl *SpaceController) WithFollowService(svc followUseCase) {
|
||||||
|
ctrl.followSvc = svc
|
||||||
|
}
|
||||||
|
|
||||||
// MySpace 自己的空间(/space)— 需登录,重定向到 /space/{uid}
|
// MySpace 自己的空间(/space)— 需登录,重定向到 /space/{uid}
|
||||||
func (ctrl *SpaceController) MySpace(c *gin.Context) {
|
func (ctrl *SpaceController) MySpace(c *gin.Context) {
|
||||||
uid, _, ok := common.GetGinUser(c)
|
uid, _, ok := common.GetGinUser(c)
|
||||||
@ -83,9 +91,17 @@ func (ctrl *SpaceController) ShowSpace(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否是自己的空间
|
// 检查是否是自己的空间
|
||||||
isOwnSpace := false
|
currentUID, _, _ := common.GetGinUser(c)
|
||||||
if currentUID, _, ok := common.GetGinUser(c); ok {
|
isOwnSpace := currentUID == uint(uid)
|
||||||
isOwnSpace = currentUID == uint(uid)
|
|
||||||
|
// 获取关注列表数据
|
||||||
|
var followingResult *service.FollowListResult
|
||||||
|
if ctrl.followSvc != nil {
|
||||||
|
res, err := ctrl.followSvc.ListFollowing(uint(uid), currentUID, 1, 24)
|
||||||
|
if err != nil || res == nil {
|
||||||
|
res = &service.FollowListResult{Items: []model.UserFollow{}, Total: 0, Accessible: true}
|
||||||
|
}
|
||||||
|
followingResult = res
|
||||||
}
|
}
|
||||||
|
|
||||||
c.HTML(http.StatusOK, "space/index.html", common.BuildPageData(c, gin.H{
|
c.HTML(http.StatusOK, "space/index.html", common.BuildPageData(c, gin.H{
|
||||||
@ -98,7 +114,7 @@ func (ctrl *SpaceController) ShowSpace(c *gin.Context) {
|
|||||||
"PrevPage": prevPage,
|
"PrevPage": prevPage,
|
||||||
"NextPage": nextPage,
|
"NextPage": nextPage,
|
||||||
"IsOwnSpace": isOwnSpace,
|
"IsOwnSpace": isOwnSpace,
|
||||||
|
"FollowingResult": followingResult,
|
||||||
"ExtraCSS": "/static/css/space.css",
|
"ExtraCSS": "/static/css/space.css",
|
||||||
"ExtraCSS2": "/static/css/follow.css",
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,4 +11,8 @@ type UserFollow struct {
|
|||||||
// 联表查询填充(非数据库字段)
|
// 联表查询填充(非数据库字段)
|
||||||
FollowerUsername string `gorm:"-:migration;<-:false;column:follower_username" json:"follower_username,omitempty"`
|
FollowerUsername string `gorm:"-:migration;<-:false;column:follower_username" json:"follower_username,omitempty"`
|
||||||
FolloweeUsername string `gorm:"-:migration;<-:false;column:followee_username" json:"followee_username,omitempty"`
|
FolloweeUsername string `gorm:"-:migration;<-:false;column:followee_username" json:"followee_username,omitempty"`
|
||||||
|
FollowerAvatar string `gorm:"-:migration;<-:false;column:follower_avatar" json:"follower_avatar,omitempty"`
|
||||||
|
FolloweeAvatar string `gorm:"-:migration;<-:false;column:followee_avatar" json:"followee_avatar,omitempty"`
|
||||||
|
FollowerBio string `gorm:"-:migration;<-:false;column:follower_bio" json:"follower_bio,omitempty"`
|
||||||
|
FolloweeBio string `gorm:"-:migration;<-:false;column:followee_bio" json:"followee_bio,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
@ -56,7 +56,7 @@ func (r *FollowRepo) CountFollowing(userID uint) (int64, error) {
|
|||||||
return count, err
|
return count, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListFollowers 粉丝列表(分页,JOIN users 获取用户名)
|
// ListFollowers 粉丝列表(分页,JOIN users 获取用户名、头像、简介)
|
||||||
func (r *FollowRepo) ListFollowers(userID uint, offset, limit int) ([]model.UserFollow, int64, error) {
|
func (r *FollowRepo) ListFollowers(userID uint, offset, limit int) ([]model.UserFollow, int64, error) {
|
||||||
var total int64
|
var total int64
|
||||||
if err := r.db.Model(&model.UserFollow{}).Where("followee_id = ?", userID).Count(&total).Error; err != nil {
|
if err := r.db.Model(&model.UserFollow{}).Where("followee_id = ?", userID).Count(&total).Error; err != nil {
|
||||||
@ -64,7 +64,7 @@ func (r *FollowRepo) ListFollowers(userID uint, offset, limit int) ([]model.User
|
|||||||
}
|
}
|
||||||
var items []model.UserFollow
|
var items []model.UserFollow
|
||||||
err := r.db.Table("user_follows").
|
err := r.db.Table("user_follows").
|
||||||
Select("user_follows.*, u.username AS follower_username").
|
Select("user_follows.*, u.username AS follower_username, u.avatar AS follower_avatar, u.bio AS follower_bio").
|
||||||
Joins("INNER JOIN users u ON u.uid = user_follows.follower_id").
|
Joins("INNER JOIN users u ON u.uid = user_follows.follower_id").
|
||||||
Where("user_follows.followee_id = ?", userID).
|
Where("user_follows.followee_id = ?", userID).
|
||||||
Order("user_follows.created_at DESC").
|
Order("user_follows.created_at DESC").
|
||||||
@ -73,7 +73,7 @@ func (r *FollowRepo) ListFollowers(userID uint, offset, limit int) ([]model.User
|
|||||||
return items, total, err
|
return items, total, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListFollowing 关注列表(分页,JOIN users 获取用户名)
|
// ListFollowing 关注列表(分页,JOIN users 获取用户名、头像、简介)
|
||||||
func (r *FollowRepo) ListFollowing(userID uint, offset, limit int) ([]model.UserFollow, int64, error) {
|
func (r *FollowRepo) ListFollowing(userID uint, offset, limit int) ([]model.UserFollow, int64, error) {
|
||||||
var total int64
|
var total int64
|
||||||
if err := r.db.Model(&model.UserFollow{}).Where("follower_id = ?", userID).Count(&total).Error; err != nil {
|
if err := r.db.Model(&model.UserFollow{}).Where("follower_id = ?", userID).Count(&total).Error; err != nil {
|
||||||
@ -81,7 +81,7 @@ func (r *FollowRepo) ListFollowing(userID uint, offset, limit int) ([]model.User
|
|||||||
}
|
}
|
||||||
var items []model.UserFollow
|
var items []model.UserFollow
|
||||||
err := r.db.Table("user_follows").
|
err := r.db.Table("user_follows").
|
||||||
Select("user_follows.*, u.username AS followee_username").
|
Select("user_follows.*, u.username AS followee_username, u.avatar AS followee_avatar, u.bio AS followee_bio").
|
||||||
Joins("INNER JOIN users u ON u.uid = user_follows.followee_id").
|
Joins("INNER JOIN users u ON u.uid = user_follows.followee_id").
|
||||||
Where("user_follows.follower_id = ?", userID).
|
Where("user_follows.follower_id = ?", userID).
|
||||||
Order("user_follows.created_at DESC").
|
Order("user_follows.created_at DESC").
|
||||||
|
|||||||
@ -85,6 +85,7 @@ func buildDeps(db *gorm.DB, cfg *config.Config, siteSettings *config.SiteSetting
|
|||||||
// 用户空间
|
// 用户空间
|
||||||
spaceService := service.NewSpaceService(userRepo, postRepo)
|
spaceService := service.NewSpaceService(userRepo, postRepo)
|
||||||
spaceCtrl := controller.NewSpaceController(spaceService)
|
spaceCtrl := controller.NewSpaceController(spaceService)
|
||||||
|
spaceCtrl.WithFollowService(followService)
|
||||||
|
|
||||||
// 收藏夹系统
|
// 收藏夹系统
|
||||||
favoriteRepo := repository.NewFavoriteRepo(db)
|
favoriteRepo := repository.NewFavoriteRepo(db)
|
||||||
|
|||||||
@ -12,86 +12,175 @@
|
|||||||
</div>
|
</div>
|
||||||
{{else}}
|
{{else}}
|
||||||
<div class="space-page">
|
<div class="space-page">
|
||||||
<!-- 用户信息头部 -->
|
|
||||||
<div class="space-header">
|
<!-- ===== 自己的空间:卡片式关注列表 ===== -->
|
||||||
<div class="container space-header-inner">
|
{{if .IsOwnSpace}}
|
||||||
<div class="space-avatar">
|
<div class="space-card-layout">
|
||||||
{{if .SpaceUser.Avatar}}
|
<!-- 左侧导航 -->
|
||||||
<img src="{{.SpaceUser.Avatar}}" alt="{{.SpaceUser.Username}}">
|
<aside class="space-sidebar">
|
||||||
|
<h2 class="space-sidebar-title">我的关注</h2>
|
||||||
|
<nav class="space-sidebar-nav">
|
||||||
|
<a href="/space/{{.SpaceUser.ID}}/following" class="space-sidebar-link active">
|
||||||
|
<svg class="space-sidebar-icon" viewBox="0 0 24 24" width="18" height="18"><path fill="currentColor" d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"/></svg>
|
||||||
|
全部关注
|
||||||
|
<span class="space-sidebar-count">{{.SpaceUser.FollowingCount}}</span>
|
||||||
|
</a>
|
||||||
|
<a href="/space/{{.SpaceUser.ID}}/followers" class="space-sidebar-link">
|
||||||
|
<svg class="space-sidebar-icon" viewBox="0 0 24 24" width="18" height="18"><path fill="currentColor" d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/></svg>
|
||||||
|
粉丝
|
||||||
|
<span class="space-sidebar-count">{{.SpaceUser.FollowersCount}}</span>
|
||||||
|
</a>
|
||||||
|
</nav>
|
||||||
|
{{if gt .SpaceUser.FollowingCount 0}}
|
||||||
|
<p class="space-sidebar-hint">显示全部 {{.SpaceUser.FollowingCount}} 个</p>
|
||||||
|
{{end}}
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<!-- 右侧内容区 -->
|
||||||
|
<main class="space-main">
|
||||||
|
<!-- 标题栏 -->
|
||||||
|
<div class="space-main-header">
|
||||||
|
<h1 class="space-main-title">全部关注</h1>
|
||||||
|
<a href="/settings/profile#privacy" class="space-settings-link">设置</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{if .FollowingResult}}
|
||||||
|
{{if .FollowingResult.Accessible}}
|
||||||
|
|
||||||
|
{{if eq (len .FollowingResult.Items) 0}}
|
||||||
|
<div class="space-empty-state">
|
||||||
|
<p>暂未关注任何人,去发现有趣的创作者吧~</p>
|
||||||
|
</div>
|
||||||
{{else}}
|
{{else}}
|
||||||
<div class="space-avatar-placeholder">{{slice .SpaceUser.Username 0 1}}</div>
|
<!-- 用户卡片网格 -->
|
||||||
{{end}}
|
<div class="space-user-grid">
|
||||||
</div>
|
{{range $i, $item := .FollowingResult.Items}}
|
||||||
<div class="space-info">
|
<div class="space-user-card" data-uid="{{$item.FolloweeID}}">
|
||||||
<h1 class="space-username">{{.SpaceUser.Username}}</h1>
|
<a href="/space/{{$item.FolloweeID}}" class="space-user-avatar-wrap">
|
||||||
<p class="space-bio">{{if eq .SpaceUser.Status "banned"}}ⓘ 该账号已被封禁{{else if .SpaceUser.Bio}}{{.SpaceUser.Bio}}{{else}} {{end}}</p>
|
{{if $item.FolloweeAvatar}}
|
||||||
<div class="space-meta">
|
<img src="{{$item.FolloweeAvatar}}" alt="{{$item.FolloweeUsername}}" class="space-user-avatar-img">
|
||||||
<span class="space-uid">UID: {{.SpaceUser.ID}}</span>
|
|
||||||
<span class="space-divider">·</span>
|
|
||||||
<span class="space-joined">加入于 {{.SpaceUser.CreatedAt.Format "2006-01-02"}}</span>
|
|
||||||
</div>
|
|
||||||
<div class="follow-links">
|
|
||||||
<a href="/space/{{.SpaceUser.ID}}/following"><strong>{{.SpaceUser.FollowingCount}}</strong> 关注</a>
|
|
||||||
<a href="/space/{{.SpaceUser.ID}}/followers"><strong>{{.SpaceUser.FollowersCount}}</strong> 粉丝</a>
|
|
||||||
</div>
|
|
||||||
{{if not .IsOwnSpace}}
|
|
||||||
<div class="follow-btn-wrapper">
|
|
||||||
<button id="followBtn" class="follow-btn" data-uid="{{.SpaceUser.ID}}">关注</button>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 内容区域 -->
|
|
||||||
<div class="container space-content">
|
|
||||||
<!-- 统计栏 -->
|
|
||||||
<div class="space-stats">
|
|
||||||
<div class="space-stat-item">
|
|
||||||
<span class="space-stat-num">{{.Total}}</span>
|
|
||||||
<span class="space-stat-label">稿件</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 稿件列表 -->
|
|
||||||
<div class="space-section">
|
|
||||||
<h2 class="space-section-title">稿件</h2>
|
|
||||||
|
|
||||||
{{if eq (len .Posts) 0}}
|
|
||||||
<div class="empty-state">暂无稿件</div>
|
|
||||||
{{else}}
|
{{else}}
|
||||||
<div class="space-posts-list">
|
<div class="space-user-avatar-placeholder">{{slice $item.FolloweeUsername 0 1}}</div>
|
||||||
{{range .Posts}}
|
{{end}}
|
||||||
<article class="space-post-card">
|
</a>
|
||||||
<div class="space-post-main">
|
<div class="space-user-info">
|
||||||
<h3 class="space-post-title">
|
<a href="/space{{$item.FolloweeID}}" class="space-user-name">{{$item.FolloweeUsername}}</a>
|
||||||
<a href="/posts/{{.ID}}">{{.Title}}</a>
|
{{if $item.FolloweeBio}}
|
||||||
</h3>
|
<p class="space-user-bio">{{printf "%.50s" $item.FolloweeBio}}</p>
|
||||||
<p class="space-post-excerpt">{{if .Excerpt}}{{.Excerpt}}{{else}}{{printf "%.200s" .Body}}{{end}}</p>
|
{{end}}
|
||||||
<div class="space-post-meta">
|
|
||||||
<span class="space-post-status">已发布</span>
|
|
||||||
<span class="space-post-time">{{.CreatedAt.Format "2006-01-02"}}</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
<button class="space-follow-btn followed" data-uid="{{$item.FolloweeID}}" disabled>已关注</button>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
<!-- 分页 -->
|
{{else}}
|
||||||
{{if gt .TotalPages 1}}
|
<!-- 隐私关闭时的空状态 -->
|
||||||
<div class="pagination">
|
<div class="space-private-empty">
|
||||||
{{if gt .Page 1}}
|
<div class="space-private-illustration">
|
||||||
<a href="?page={{.PrevPage}}" class="page-link">上一页</a>
|
<svg viewBox="0 0 120 100" width="120" height="100">
|
||||||
|
<rect x="15" y="25" width="90" height="60" rx="6" fill="#f0f0f0" stroke="#e0e0e0" stroke-width="2"/>
|
||||||
|
<rect x="25" y="35" width="40" height="6" rx="3" fill="#e0e0e0"/>
|
||||||
|
<rect x="25" y="47" width="70" height="4" rx="2" fill="#eee"/>
|
||||||
|
<rect x="25" y="57" width="55" height="4" rx="2" fill="#eee"/>
|
||||||
|
<circle cx="95" cy="42" r="10" fill="#e8e8e8"/>
|
||||||
|
<path d="M91 42 L94 45 L99 39" stroke="#bbb" stroke-width="2" fill="none" stroke-linecap="round"/>
|
||||||
|
<circle cx="82" cy="18" r="8" fill="#fff" stroke="#ddd" stroke-width="1.5"/>
|
||||||
|
<line x1="86" y1="14" x2="92" y2="10" stroke="#ddd" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<p class="space-private-text">你关闭了展示关注列表~</p>
|
||||||
|
<a href="/settings/profile#privacy" class="space-private-setting-btn">去设置开启</a>
|
||||||
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
<span class="page-info">第 {{.Page}} / {{.TotalPages}} 页 (共 {{.Total}} 条)</span>
|
|
||||||
{{if lt .Page .TotalPages}}
|
|
||||||
<a href="?page={{.NextPage}}" class="page-link">下一页</a>
|
|
||||||
{{end}}
|
{{end}}
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ===== 别人的空间:简化视图 ===== -->
|
||||||
|
{{else}}
|
||||||
|
<div class="space-card-layout">
|
||||||
|
<!-- 左侧导航(简化版) -->
|
||||||
|
<aside class="space-sidebar">
|
||||||
|
<h2 class="space-sidebar-title">Ta的关注</h2>
|
||||||
|
<nav class="space-sidebar-nav">
|
||||||
|
{{if .FollowingResult}}
|
||||||
|
<div class="space-sidebar-link active{{if not .FollowingResult.Accessible}} disabled{{end}}">
|
||||||
|
<svg class="space-sidebar-icon" viewBox="0 0 24 24" width="18" height="18"><path fill="currentColor" d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"/></svg>
|
||||||
|
全部关注
|
||||||
|
<span class="space-sidebar-count">{{.SpaceUser.FollowingCount}}</span>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
<a href="/space/{{.SpaceUser.ID}}/followers" class="space-sidebar-link">
|
||||||
|
<svg class="space-sidebar-icon" viewBox="0 0 24 24" width="18" height="18"><path fill="currentColor" d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/></svg>
|
||||||
|
Ta的粉丝
|
||||||
|
<span class="space-sidebar-count">{{.SpaceUser.FollowersCount}}</span>
|
||||||
|
</a>
|
||||||
|
</nav>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<!-- 右侧内容区 -->
|
||||||
|
<main class="space-main">
|
||||||
|
<h1 class="space-main-title">全部关注</h1>
|
||||||
|
|
||||||
|
{{if .FollowingResult}}
|
||||||
|
{{if .FollowingResult.Accessible}}
|
||||||
|
|
||||||
|
{{if eq (len .FollowingResult.Items) 0}}
|
||||||
|
<div class="space-empty-state">
|
||||||
|
<p>Ta 暂未关注任何人</p>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<div class="space-user-grid">
|
||||||
|
{{range $i, $item := .FollowingResult.Items}}
|
||||||
|
<div class="space-user-card" data-uid="{{$item.FolloweeID}}">
|
||||||
|
<a href="/space/{{$item.FolloweeID}}" class="space-user-avatar-wrap">
|
||||||
|
{{if $item.FolloweeAvatar}}
|
||||||
|
<img src="{{$item.FolloweeAvatar}}" alt="{{$item.FolloweeUsername}}" class="space-user-avatar-img">
|
||||||
|
{{else}}
|
||||||
|
<div class="space-user-avatar-placeholder">{{slice $item.FolloweeUsername 0 1}}</div>
|
||||||
|
{{end}}
|
||||||
|
</a>
|
||||||
|
<div class="space-user-info">
|
||||||
|
<a href="/space/{{$item.FolloweeID}}" class="space-user-name">{{$item.FolloweeUsername}}</a>
|
||||||
|
{{if $item.FolloweeBio}}
|
||||||
|
<p class="space-user-bio">{{printf "%.50s" $item.FolloweeBio}}</p>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
<button class="space-follow-btn" data-uid="{{$item.FolloweeID}}">关注</button>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{else}}
|
||||||
|
<!-- 对方关闭了关注列表 -->
|
||||||
|
<div class="space-private-empty">
|
||||||
|
<div class="space-private-illustration">
|
||||||
|
<svg viewBox="0 0 120 100" width="120" height="100">
|
||||||
|
<rect x="15" y="25" width="90" height="60" rx="6" fill="#f0f0f0" stroke="#e0e0e0" stroke-width="2"/>
|
||||||
|
<rect x="25" y="35" width="40" height="6" rx="3" fill="#e0e0e0"/>
|
||||||
|
<rect x="25" y="47" width="70" height="4" rx="2" fill="#eee"/>
|
||||||
|
<rect x="25" y="57" width="55" height="4" rx="2" fill="#eee"/>
|
||||||
|
<circle cx="95" cy="42" r="10" fill="#e8e8e8"/>
|
||||||
|
<path d="M91 42 L94 45 L99 39" stroke="#bbb" stroke-width="2" fill="none" stroke-linecap="round"/>
|
||||||
|
<circle cx="82" cy="18" r="8" fill="#fff" stroke="#ddd" stroke-width="1.5"/>
|
||||||
|
<line x1="86" y1="14" x2="92" y2="10" stroke="#ddd" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
<p class="space-private-text">Ta 关闭了展示关注列表~</p>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
{{else}}
|
||||||
|
<div class="space-empty-state">
|
||||||
|
<p>暂无数据</p>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
@ -99,13 +188,12 @@
|
|||||||
|
|
||||||
<script src="/static/js/common.js?v={{assetV "/static/js/common.js"}}"></script>
|
<script src="/static/js/common.js?v={{assetV "/static/js/common.js"}}"></script>
|
||||||
{{if not .IsOwnSpace}}
|
{{if not .IsOwnSpace}}
|
||||||
|
<!-- 别人空间:关注按钮交互 -->
|
||||||
<script>
|
<script>
|
||||||
(function() {
|
(function() {
|
||||||
var followBtn = document.getElementById('followBtn');
|
var cards = document.querySelectorAll('.space-user-card');
|
||||||
if (!followBtn) return;
|
if (!cards.length) return;
|
||||||
var targetUid = followBtn.getAttribute('data-uid');
|
|
||||||
|
|
||||||
// 四个按钮状态文案
|
|
||||||
var labels = {
|
var labels = {
|
||||||
none: '关注',
|
none: '关注',
|
||||||
following: '已关注',
|
following: '已关注',
|
||||||
@ -113,13 +201,6 @@
|
|||||||
mutual: '已互粉'
|
mutual: '已互粉'
|
||||||
};
|
};
|
||||||
|
|
||||||
var classes = {
|
|
||||||
none: '',
|
|
||||||
following: 'following',
|
|
||||||
followBack: '',
|
|
||||||
mutual: 'mutual'
|
|
||||||
};
|
|
||||||
|
|
||||||
function buttonState(status) {
|
function buttonState(status) {
|
||||||
if (status.i_follow && status.they_follow) return 'mutual';
|
if (status.i_follow && status.they_follow) return 'mutual';
|
||||||
if (status.i_follow) return 'following';
|
if (status.i_follow) return 'following';
|
||||||
@ -127,34 +208,45 @@
|
|||||||
return 'none';
|
return 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateBtn(status) {
|
function updateBtn(btn, status) {
|
||||||
var state = buttonState(status);
|
var state = buttonState(status);
|
||||||
followBtn.textContent = labels[state];
|
btn.textContent = labels[state];
|
||||||
followBtn.className = 'follow-btn ' + (classes[state] || '');
|
btn.className = 'space-follow-btn';
|
||||||
|
if (state === 'following' || state === 'mutual') btn.classList.add('followed');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取初始状态
|
// 批量获取关注状态
|
||||||
|
var uids = [];
|
||||||
|
cards.forEach(function(card) {
|
||||||
|
uids.push(card.getAttribute('data-uid'));
|
||||||
|
});
|
||||||
|
|
||||||
|
// 逐个查询状态
|
||||||
|
cards.forEach(function(card) {
|
||||||
|
var btn = card.querySelector('.space-follow-btn');
|
||||||
|
if (!btn) return;
|
||||||
|
var targetUid = card.getAttribute('data-uid');
|
||||||
|
|
||||||
fetch('/api/users/' + targetUid + '/follow-status')
|
fetch('/api/users/' + targetUid + '/follow-status')
|
||||||
.then(function(res) { return res.json(); })
|
.then(function(res) { return res.json(); })
|
||||||
.then(function(data) {
|
.then(function(data) {
|
||||||
if (data && data.success && data.data) {
|
if (data && data.success && data.data) {
|
||||||
updateBtn(data.data);
|
updateBtn(btn, data.data);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 点击切换
|
btn.addEventListener('click', function() {
|
||||||
followBtn.addEventListener('click', function() {
|
btn.classList.add('waiting');
|
||||||
followBtn.classList.add('waiting');
|
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
xhr.open('POST', '/api/users/' + targetUid + '/follow', true);
|
xhr.open('POST', '/api/users/' + targetUid + '/follow', true);
|
||||||
xhr.onreadystatechange = function() {
|
xhr.onreadystatechange = function() {
|
||||||
if (xhr.readyState === 4) {
|
if (xhr.readyState === 4) {
|
||||||
followBtn.classList.remove('waiting');
|
btn.classList.remove('waiting');
|
||||||
if (xhr.status === 200) {
|
if (xhr.status === 200) {
|
||||||
try {
|
try {
|
||||||
var resp = JSON.parse(xhr.responseText);
|
var resp = JSON.parse(xhr.responseText);
|
||||||
if (resp.data && resp.data.status) {
|
if (resp.data && resp.data.status) {
|
||||||
updateBtn(resp.data.status);
|
updateBtn(btn, resp.data.status);
|
||||||
}
|
}
|
||||||
} catch(e) {}
|
} catch(e) {}
|
||||||
}
|
}
|
||||||
@ -162,6 +254,7 @@
|
|||||||
};
|
};
|
||||||
xhr.send();
|
xhr.send();
|
||||||
});
|
});
|
||||||
|
});
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@ -1,79 +1,3 @@
|
|||||||
/* 关注按钮 */
|
|
||||||
.follow-btn-wrapper {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.follow-btn {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: .35rem;
|
|
||||||
padding: .4rem 1rem;
|
|
||||||
border: 1.5px solid var(--color-accent);
|
|
||||||
border-radius: 6px;
|
|
||||||
font-size: .85rem;
|
|
||||||
font-weight: 600;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all .15s ease;
|
|
||||||
background: transparent;
|
|
||||||
color: var(--color-accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.follow-btn:hover {
|
|
||||||
background: var(--color-accent);
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.follow-btn.following {
|
|
||||||
background: var(--color-accent);
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.follow-btn.following:hover {
|
|
||||||
background: var(--color-danger, #e74c3c);
|
|
||||||
border-color: var(--color-danger, #e74c3c);
|
|
||||||
}
|
|
||||||
|
|
||||||
.follow-btn.mutual {
|
|
||||||
background: var(--color-accent);
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.follow-btn.mutual:hover {
|
|
||||||
background: var(--color-danger, #e74c3c);
|
|
||||||
border-color: var(--color-danger, #e74c3c);
|
|
||||||
}
|
|
||||||
|
|
||||||
.follow-btn.waiting {
|
|
||||||
opacity: .6;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 关注/粉丝链接 */
|
|
||||||
.follow-links {
|
|
||||||
display: flex;
|
|
||||||
gap: 1rem;
|
|
||||||
margin-top: .35rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.follow-links a {
|
|
||||||
color: var(--color-secondary);
|
|
||||||
font-size: .85rem;
|
|
||||||
text-decoration: none;
|
|
||||||
transition: color .15s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.follow-links a:hover {
|
|
||||||
color: var(--color-accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.follow-links strong {
|
|
||||||
color: var(--color-text);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 用户列表(粉丝/关注页) */
|
/* 用户列表(粉丝/关注页) */
|
||||||
.follow-list-page {
|
.follow-list-page {
|
||||||
max-width: 700px;
|
max-width: 700px;
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
/* ============================================================
|
/* ============================================================
|
||||||
MetaLab Space Styles — 用户空间页面
|
MetaLab Space Styles — 卡片式关注列表布局
|
||||||
参考 Bilibili 空间布局,轻量版
|
|
||||||
============================================================ */
|
============================================================ */
|
||||||
|
|
||||||
/* ---------- Error State ---------- */
|
/* ---------- Error State ---------- */
|
||||||
@ -40,210 +39,297 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ============================================================
|
/* ============================================================
|
||||||
Header — 用户信息横幅(B站风格)
|
Card Layout — 左右分栏(参考B站关注页)
|
||||||
============================================================ */
|
============================================================ */
|
||||||
.space-header {
|
.space-card-layout {
|
||||||
background: linear-gradient(135deg, #e8f0f8 0%, #f0f4f8 100%);
|
|
||||||
border-bottom: 1px solid var(--color-border);
|
|
||||||
padding: 2rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.space-header-inner {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 1.5rem 1rem;
|
||||||
gap: 1.5rem;
|
gap: 1.5rem;
|
||||||
|
min-height: calc(100vh - 160px);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Avatar */
|
/* ---------- Sidebar (左侧导航) ---------- */
|
||||||
.space-avatar {
|
.space-sidebar {
|
||||||
|
width: 200px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.space-avatar img {
|
.space-sidebar-title {
|
||||||
width: 80px;
|
font-size: .95rem;
|
||||||
height: 80px;
|
font-weight: 600;
|
||||||
border-radius: 50%;
|
color: var(--color-primary);
|
||||||
object-fit: cover;
|
margin-bottom: .75rem;
|
||||||
border: 3px solid #fff;
|
padding-left: .25rem;
|
||||||
box-shadow: 0 2px 8px rgba(0,0,0,.1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.space-avatar-placeholder {
|
.space-sidebar-nav {
|
||||||
width: 80px;
|
display: flex;
|
||||||
height: 80px;
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-sidebar-link {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: .5rem;
|
||||||
|
padding: .65rem .75rem;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--color-text);
|
||||||
|
font-size: .9rem;
|
||||||
|
transition: background-color .15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-sidebar-link:hover {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-sidebar-link.active {
|
||||||
|
background-color: var(--color-accent);
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-sidebar-link.active:hover {
|
||||||
|
background-color: #2980b9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-sidebar-link.disabled {
|
||||||
|
opacity: .6;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-sidebar-icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
opacity: .8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-sidebar-link.active .space-sidebar-icon {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-sidebar-count {
|
||||||
|
margin-left: auto;
|
||||||
|
font-size: .8rem;
|
||||||
|
opacity: .7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-sidebar-hint {
|
||||||
|
margin-top: 1rem;
|
||||||
|
padding: 0 .25rem;
|
||||||
|
font-size: .78rem;
|
||||||
|
color: var(--color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- Main Content (右侧内容) ---------- */
|
||||||
|
.space-main {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-main-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-main-title {
|
||||||
|
font-size: 1.35rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-settings-link {
|
||||||
|
font-size: .85rem;
|
||||||
|
color: var(--color-accent);
|
||||||
|
text-decoration: none;
|
||||||
|
padding: .3rem .75rem;
|
||||||
|
border: 1px solid var(--color-border, #e5e7eb);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
transition: all .15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-settings-link:hover {
|
||||||
|
background-color: rgba(52, 152, 219, .05);
|
||||||
|
border-color: var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
User Card Grid — 用户卡片网格
|
||||||
|
============================================================ */
|
||||||
|
.space-user-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-user-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: .75rem;
|
||||||
|
padding: 1rem 1.1rem;
|
||||||
|
background: var(--color-surface, #fff);
|
||||||
|
border: 1px solid var(--color-border, #e5e7eb);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
transition: box-shadow .2s ease, border-color .2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-user-card:hover {
|
||||||
|
box-shadow: 0 2px 12px rgba(0, 0, 0, .06);
|
||||||
|
border-color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Avatar */
|
||||||
|
.space-user-avatar-wrap {
|
||||||
|
flex-shrink: 0;
|
||||||
|
text-decoration: none;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-user-avatar-img {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-user-avatar-placeholder {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: var(--color-accent);
|
background: var(--color-accent);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
font-size: 2rem;
|
font-size: 1.15rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
border: 3px solid #fff;
|
|
||||||
box-shadow: 0 2px 8px rgba(0,0,0,.1);
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Info */
|
/* User Info */
|
||||||
.space-info {
|
.space-user-info {
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.space-username {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--color-primary);
|
|
||||||
line-height: 1.3;
|
|
||||||
margin-bottom: .25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.space-bio {
|
|
||||||
font-size: .9rem;
|
|
||||||
color: var(--color-text-light);
|
|
||||||
line-height: 1.5;
|
|
||||||
margin-bottom: .5rem;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.space-meta {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: .3rem;
|
|
||||||
font-size: .8rem;
|
|
||||||
color: var(--color-secondary);
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.space-divider {
|
|
||||||
color: var(--color-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ============================================================
|
|
||||||
Stats Bar
|
|
||||||
============================================================ */
|
|
||||||
.space-stats {
|
|
||||||
display: flex;
|
|
||||||
gap: 2rem;
|
|
||||||
padding: 1rem 0;
|
|
||||||
border-bottom: 1px solid var(--color-border);
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.space-stat-item {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
gap: .15rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.space-stat-num {
|
|
||||||
font-size: 1.4rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.space-stat-label {
|
|
||||||
font-size: .8rem;
|
|
||||||
color: var(--color-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ============================================================
|
|
||||||
Content Section — 稿件列表
|
|
||||||
============================================================ */
|
|
||||||
.space-content {
|
|
||||||
padding-bottom: 3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.space-section {
|
|
||||||
margin-top: .5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.space-section-title {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--color-primary);
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
padding-bottom: .5rem;
|
|
||||||
border-bottom: 2px solid var(--color-accent);
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---------- Post Card ---------- */
|
|
||||||
.space-posts-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: .75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.space-post-card {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
background: var(--color-surface);
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
border-radius: var(--radius);
|
|
||||||
padding: 1rem 1.25rem;
|
|
||||||
transition: box-shadow .2s ease, border-color .2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.space-post-card:hover {
|
|
||||||
box-shadow: 0 2px 12px rgba(0,0,0,.06);
|
|
||||||
border-color: #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.space-post-main {
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.space-post-title {
|
.space-user-name {
|
||||||
font-size: 1rem;
|
display: block;
|
||||||
|
font-size: .92rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin-bottom: .3rem;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.space-post-title a {
|
|
||||||
color: var(--color-primary);
|
color: var(--color-primary);
|
||||||
transition: var(--transition);
|
text-decoration: none;
|
||||||
|
line-height: 1.3;
|
||||||
|
margin-bottom: .2rem;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.space-post-title a:hover {
|
.space-user-name:hover {
|
||||||
color: var(--color-accent);
|
color: var(--color-accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.space-post-excerpt {
|
.space-user-bio {
|
||||||
font-size: .85rem;
|
font-size: .8rem;
|
||||||
color: var(--color-text-light);
|
color: var(--color-text-light);
|
||||||
line-height: 1.5;
|
line-height: 1.4;
|
||||||
margin-bottom: .5rem;
|
margin: 0;
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
-webkit-line-clamp: 2;
|
-webkit-line-clamp: 2;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.space-post-meta {
|
/* Follow Button in Card */
|
||||||
display: flex;
|
.space-follow-btn {
|
||||||
align-items: center;
|
flex-shrink: 0;
|
||||||
gap: .75rem;
|
padding: .28rem .7rem;
|
||||||
font-size: .75rem;
|
border: 1.5px solid var(--color-accent);
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: .8rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all .15s ease;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--color-accent);
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-follow-btn:hover {
|
||||||
|
background: var(--color-accent);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-follow-btn.followed {
|
||||||
|
background: transparent;
|
||||||
|
border-color: var(--color-border, #e5e7eb);
|
||||||
|
color: var(--color-secondary);
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-follow-btn.followed:hover {
|
||||||
|
background: transparent;
|
||||||
color: var(--color-secondary);
|
color: var(--color-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.space-post-status {
|
.space-follow-btn.waiting {
|
||||||
color: var(--color-accent);
|
opacity: .6;
|
||||||
font-weight: 500;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- Empty State ---------- */
|
/* ============================================================
|
||||||
.empty-state {
|
Empty / Private States
|
||||||
|
============================================================ */
|
||||||
|
.space-empty-state {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 3rem 1rem;
|
padding: 4rem 1rem;
|
||||||
color: var(--color-secondary);
|
color: var(--color-secondary);
|
||||||
font-size: .95rem;
|
font-size: .95rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- Pagination ---------- */
|
.space-private-empty {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 5rem 1rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-private-illustration {
|
||||||
|
margin-bottom: 1.25rem;
|
||||||
|
opacity: .6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-private-text {
|
||||||
|
color: var(--color-secondary);
|
||||||
|
font-size: .95rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-private-setting-btn {
|
||||||
|
display: inline-block;
|
||||||
|
padding: .5rem 1.25rem;
|
||||||
|
background: var(--color-accent);
|
||||||
|
color: #fff;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
font-size: .88rem;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: background .15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-private-setting-btn:hover {
|
||||||
|
background: #2980b9;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
Pagination
|
||||||
|
============================================================ */
|
||||||
.pagination {
|
.pagination {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@ -274,40 +360,46 @@
|
|||||||
Responsive
|
Responsive
|
||||||
============================================================ */
|
============================================================ */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.space-header {
|
.space-card-layout {
|
||||||
padding: 1.5rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.space-header-inner {
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
text-align: center;
|
padding: 1rem .75rem;
|
||||||
gap: .75rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.space-avatar img,
|
.space-sidebar {
|
||||||
.space-avatar-placeholder {
|
width: 100%;
|
||||||
width: 64px;
|
|
||||||
height: 64px;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.space-username {
|
.space-sidebar-nav {
|
||||||
font-size: 1.25rem;
|
flex-direction: row;
|
||||||
|
overflow-x: auto;
|
||||||
|
gap: .5rem;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
}
|
}
|
||||||
|
|
||||||
.space-meta {
|
.space-sidebar-link {
|
||||||
justify-content: center;
|
white-space: nowrap;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.space-stats {
|
.space-sidebar-title {
|
||||||
justify-content: center;
|
margin-bottom: .5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.space-post-card {
|
.space-sidebar-hint {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-user-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-user-card {
|
||||||
padding: .85rem 1rem;
|
padding: .85rem 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.space-post-title {
|
.space-main-header {
|
||||||
font-size: .95rem;
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: .5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user