Files
mce/templates/MetaLab-2026/html/space/index.html
Victor_Jay d9eff2df08 refactor: 重构 Space 页面为统一多 Tab 布局 + 移除独立关注/粉丝子页面
- 后端:SpaceController 支持 5 个 Tab(文章/关注/粉丝/收藏/设置),统一处理本人与访客视图
- 后端:注入 FavoriteService 支持收藏夹 Tab,新增用户统计数据查询
- 后端:新增 PUT /api/space/privacy 隐私设置 API 端点
- 后端:移除 FollowPageController 及 /space/:uid/followers|following 路由
- 前端:完全重写 space/index.html,顶部信息栏 + Tab 导航 + 左侧栏 + 主内容区
- 前端:重写 space.css 适配新布局(Profile Header/Tab Bar/文章/收藏/设置)
- 清理:删除独立的 follow HTML 模板和 CSS 文件
- 模型:User 表新增 follower_list_public 隐私字段
2026-06-01 21:48:01 +08:00

763 lines
38 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{{template "layout/header.html" .}}
<body>
{{template "layout/nav.html" .}}
{{if .Error}}
<div class="space-error">
<div class="container">
<h1>{{.Error}}</h1>
<a href="/" class="btn btn-primary">返回首页</a>
</div>
</div>
{{else}}
<div class="space-page">
<!-- ====== 顶部信息栏(访客+本人共用) ====== -->
<div class="space-profile-header">
<div class="space-header-inner">
<!-- 左:头像 + 用户名 -->
<div class="space-header-user">
<a href="/space/{{.SpaceUser.ID}}" class="space-avatar-link">
{{if .SpaceUser.Avatar}}
<img src="{{.SpaceUser.Avatar}}" alt="{{.SpaceUser.Username}}" class="space-avatar-img">
{{else}}
<div class="space-avatar-placeholder">{{slice .SpaceUser.Username 0 1}}</div>
{{end}}
</a>
<div class="space-header-info">
<span class="space-username">{{.SpaceUser.Username}}</span>
{{if .SpaceUser.Bio}}
<p class="space-header-bio">{{.SpaceUser.Bio}}</p>
{{end}}
</div>
</div>
<!-- 右:操作按钮 -->
<div class="space-header-actions">
{{if .IsOwnSpace}}
<!-- 本人无关注按钮 -->
{{else}}
<button id="space-follow-btn" class="space-action-btn primary" data-uid="{{.SpaceUser.ID}}">+ 关注</button>
<button class="space-action-btn">发消息</button>
<button class="space-action-btn icon-only" title="更多"></button>
{{end}}
</div>
</div>
</div>
<!-- ====== Tab 导航栏(区分本人/访客) ====== -->
{{if .IsOwnSpace}}
<!-- 本人视图左Tab + 右统计 -->
<div class="space-tab-bar owner">
<div class="space-tabs-left">
<a href="/space/{{.SpaceUser.ID}}?tab=articles"
class="space-tab{{if eq .ActiveTab "articles"}} active{{end}}">
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zM6 20V4h7v5h5v11H6z"/></svg>
文章
<span class="tab-count">{{.PostCount}}</span>
</a>
<a href="/space/{{.SpaceUser.ID}}?tab=collections"
class="space-tab{{if eq .ActiveTab "collections"}} active{{end}}">
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M17 3H7c-1.1 0-2 .9-2 2v16l7-3 7 3V5c0-1.1-.9-2-2-2z"/></svg>
收藏
<span class="tab-count">{{.TotalFavorites}}</span>
</a>
<a href="/space/{{.SpaceUser.ID}}?tab=settings"
class="space-tab{{if eq .ActiveTab "settings"}} active{{end}}">
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M19.14 12.94c.04-.31.06-.63.06-.94 0-.31-.02-.63-.06-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94L14.4 2.81c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.04.31-.06.63-.06.94s.02.63.06.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"/></svg>
设置
</a>
</div>
<div class="space-stats-right">
<div class="stat-item">
<span class="stat-label">关注</span>
<span class="stat-value">{{.SpaceUser.FollowingCount}}</span>
</div>
<div class="stat-item">
<span class="stat-label">粉丝</span>
<span class="stat-value">{{.SpaceUser.FollowersCount}}</span>
</div>
<div class="stat-item">
<span class="stat-label">获赞</span>
<span class="stat-value">{{formatCount .TotalLikes}}</span>
</div>
<div class="stat-item">
<span class="stat-label">阅读量</span>
<span class="stat-value">{{formatCount .TotalReads}}</span>
</div>
</div>
</div>
{{else}}
<!-- 访客视图:统计栏 -->
<div class="space-tab-bar visitor">
<div class="space-tabs-left">
<a href="/space/{{.SpaceUser.ID}}?tab=articles"
class="space-tab{{if eq .ActiveTab "articles"}} active{{end}}">
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zM6 20V4h7v5h5v11H6z"/></svg>
文章
<span class="tab-count">{{.SpaceUser.PostCount}}</span>
</a>
<a href="/space/{{.SpaceUser.ID}}?tab=following"
class="space-tab{{if or (eq .ActiveTab "following") (eq .ActiveTab "followers")}} active{{end}}">
<span class="stat-label-inline">关注数</span>
<span class="tab-count">{{.SpaceUser.FollowingCount}}</span>
</a>
<a href="/space/{{.SpaceUser.ID}}?tab=followers" class="space-tab">
<span class="stat-label-inline">粉丝数</span>
<span class="tab-count">{{.SpaceUser.FollowersCount}}</span>
</a>
<div class="stat-item-inline">
<span class="stat-label-inline">获赞数</span>
<span class="stat-value-inline">{{formatCount .TotalLikes}}</span>
</div>
<div class="stat-item-inline">
<span class="stat-label-inline">阅读量</span>
<span class="stat-value-inline">{{formatCount .TotalReads}}</span>
</div>
</div>
</div>
{{end}}
<!-- ====== 主内容区(左右分栏) ====== -->
<div class="space-card-layout">
{{/* ========== 文章 Tab ==========*/}}
{{if eq .ActiveTab "articles"}}
<main class="space-main-full">
<h1 class="space-section-title">{{if .IsOwnSpace}}我的文章{{else}}Ta 的文章{{end}}</h1>
{{if .Posts}}
{{if gt (len .Posts) 0}}
<div class="space-post-list">
{{range $i, $post := .Posts}}
<article class="space-post-card">
<a href="/posts/{{$post.ID}}" class="space-post-title">{{$post.Title}}</a>
<p class="space-post-excerpt">{{if $post.Excerpt}}{{printf "%.120s" $post.Excerpt}}{{else}}{{printf "%.120s" $post.Body}}{{end}}</p>
<div class="space-post-meta">
<span class="post-meta-item">
<svg viewBox="0 0 24 24" width="14" height="14"><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>
{{$post.LikesCount}}
</span>
<span class="post-meta-item">
<svg viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M17 3H7c-1.1 0-2 .9-2 2v16l7-3 7 3V5c0-1.1-.9-2-2-2z"/></svg>
{{$post.FavoritesCount}}
</span>
<span class="post-meta-item">
<svg viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/></svg>
{{$post.CommentsCount}}
</span>
<span class="post-meta-time">{{$post.CreatedAt.Format "01-02 15:04"}}</span>
</div>
</article>
{{end}}
</div>
{{if gt .TotalPages 1}}
<div class="pagination">
{{if gt .Page 1}}
<a href="?tab=articles&page={{.PrevPage}}" class="page-link">上一页</a>
{{end}}
<span class="page-info">第 {{.Page}} / {{.TotalPages}} 页</span>
{{if le .Page .TotalPages}}
<a href="?tab=articles&page={{.NextPage}}" class="page-link">下一页</a>
{{end}}
</div>
{{end}}
{{else}}
<div class="space-empty-state">
<p>{{if .IsOwnSpace}}还没有发布过文章,去创作吧~{{else}}Ta 还没有发布文章{{end}}</p>
</div>
{{end}}
{{else}}
<div class="space-empty-state">
<p>暂无数据</p>
</div>
{{end}}
</main>
{{/* ========== 关注列表 Tab含本人+访客) ==========*/}}
{{else if or (eq .ActiveTab "following") (eq .ActiveTab "followers")}}
<!-- 左侧导航 -->
<aside class="space-sidebar">
<h2 class="space-sidebar-title">{{if .IsOwnSpace}}我的关注{{else}}Ta的关注{{end}}</h2>
<nav class="space-sidebar-nav">
<a href="/space/{{.SpaceUser.ID}}?tab=following"
class="space-sidebar-link{{if eq .ActiveTab "following"}} active{{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>
{{if .IsOwnSpace}}全部关注{{else}}全部关注{{end}}
<span class="space-sidebar-count">{{.SpaceUser.FollowingCount}}</span>
</a>
<a href="/space/{{.SpaceUser.ID}}?tab=followers"
class="space-sidebar-link{{if eq .ActiveTab "followers"}} active{{end}}">
<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>
{{if .IsOwnSpace}}粉丝{{else}}Ta的粉丝{{end}}
<span class="space-sidebar-count">{{.SpaceUser.FollowersCount}}</span>
</a>
</nav>
</aside>
<!-- 右侧内容区 -->
<main class="space-main">
<h1 class="space-main-title">{{if eq .ActiveTab "following"}}全部关注{{else}}{{if .IsOwnSpace}}粉丝{{else}}Ta的粉丝{{end}}{{end}}</h1>
{{/* ---- 关注列表 ---- */}}
{{if eq .ActiveTab "following"}}
{{if .FollowingResult}}
{{if .FollowingResult.Accessible}}
{{if eq (len .FollowingResult.Items) 0}}
<div class="space-empty-state">
<p>{{if .IsOwnSpace}}暂未关注任何人,去发现有趣的创作者吧~{{else}}Ta 暂未关注任何人{{end}}</p>
</div>
{{else}}
{{if .IsOwnSpace}}
<!-- 本人:带搜索的用户列表 -->
<div class="space-follow-search">
<input type="text" class="follow-search-input" placeholder="输入关键词">
<button class="follow-search-btn">
<svg viewBox="0 0 24 24" width="18" height="18"><path fill="currentColor" d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/></svg>
</button>
</div>
{{end}}
<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>
{{if $.IsOwnSpace}}
<button class="space-follow-btn followed" disabled>已关注</button>
{{else}}
<button class="space-follow-btn" data-uid="{{$item.FolloweeID}}">关注</button>
{{end}}
</div>
{{end}}
</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>
<p class="space-private-text">{{if .IsOwnSpace}}你关闭了展示关注列表~{{else}}Ta 关闭了展示关注列表~{{end}}</p>
{{if .IsOwnSpace}}
<a href="/settings/profile#privacy" class="space-private-setting-btn">去设置开启</a>
{{end}}
</div>
{{end}}
{{else}}
<div class="space-empty-state"><p>暂无数据</p></div>
{{end}}
{{/* ---- 粉丝列表 ---- */}}
{{else if eq .ActiveTab "followers"}}
{{if .FollowersResult}}
{{if .FollowersResult.Accessible}}
{{if eq (len .FollowersResult.Items) 0}}
<div class="space-empty-state">
<p>{{if .IsOwnSpace}}暂无粉丝{{else}}Ta 暂无粉丝{{end}}</p>
</div>
{{else}}
{{if .IsOwnSpace}}
<div class="space-follow-search">
<input type="text" class="follow-search-input" placeholder="输入关键词">
<button class="follow-search-btn">
<svg viewBox="0 0 24 24" width="18" height="18"><path fill="currentColor" d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/></svg>
</button>
</div>
{{end}}
<div class="space-user-grid">
{{range $i, $item := .FollowersResult.Items}}
<div class="space-user-card" data-uid="{{$item.FollowerID}}">
<a href="/space/{{$item.FollowerID}}" class="space-user-avatar-wrap">
{{if $item.FollowerAvatar}}
<img src="{{$item.FollowerAvatar}}" alt="{{$item.FollowerUsername}}" class="space-user-avatar-img">
{{else}}
<div class="space-user-avatar-placeholder">{{slice $item.FollowerUsername 0 1}}</div>
{{end}}
</a>
<div class="space-user-info">
<a href="/space/{{$item.FollowerID}}" class="space-user-name">{{$item.FollowerUsername}}</a>
{{if $item.FollowerBio}}
<p class="space-user-bio">{{printf "%.50s" $item.FollowerBio}}</p>
{{end}}
</div>
{{if not $.IsOwnSpace}}
<button class="space-follow-btn" data-uid="{{$item.FollowerID}}">关注</button>
{{end}}
</div>
{{end}}
</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"/>
</svg>
</div>
<p class="space-private-text">{{if .IsOwnSpace}}你关闭了展示粉丝列表~{{else}}Ta 关闭了展示粉丝列表~{{end}}</p>
{{if .IsOwnSpace}}
<a href="/settings/profile#privacy" class="space-private-setting-btn">去设置开启</a>
{{end}}
</div>
{{end}}
{{else}}
<div class="space-empty-state"><p>暂无数据</p></div>
{{end}}
{{end}}
</main>
{{/* ========== 收藏夹 Tab仅本人 ==========*/}}
{{else if eq .ActiveTab "collections"}}
<!-- 左侧收藏夹列表 -->
<aside class="space-sidebar collection-sidebar">
<h2 class="space-sidebar-title collapsible" onclick="toggleFolderSection(this)">
我创建的收藏夹
<svg class="collapse-arrow" viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z"/></svg>
</h2>
<nav class="space-sidebar-nav" id="folder-nav">
<a href="javascript:void(0)" class="space-sidebar-link new-folder-btn" onclick="showNewFolderInput()">
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
新建收藏夹
</a>
<div id="new-folder-input" style="display:none;">
<input type="text" id="new-folder-name" placeholder="收藏夹名称" maxlength="50" class="new-folder-input">
<button onclick="createFolder()" class="new-folder-submit">确定</button>
</div>
{{if .FoldersResult}}
{{range $f := .FoldersResult.Folders}}
<a href="/space/{{$.SpaceUser.ID}}?tab=collections&folder={{$f.ID}}"
class="space-sidebar-link{{if eq (str $.ActiveFolderID) (str $f.ID)}} active{{end}}"
data-folder-id="{{$f.ID}}">
<svg class="space-sidebar-icon" viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M17 3H7c-1.1 0-2 .9-2 2v16l7-3 7 3V5c0-1.1-.9-2-2-2zm0 15l-5-2.18L7 18V5h10v13z"/></svg>
{{$f.Name}}
<span class="space-sidebar-count">{{$f.ItemCount}}</span>
</a>
{{end}}
{{end}}
</nav>
</aside>
<!-- 右侧收藏内容 -->
<main class="space-main">
{{if .FoldersResult}}
{{if gt (len .FoldersResult.Folders) 0}}
{{/* 找到当前激活的收藏夹 */}}
{{$activeFolder := ""}}
{{range $f := .FoldersResult.Folders}}
{{if eq (str $.ActiveFolderID) (str $f.ID)}}
{{$activeFolder = $f}}
{{end}}
{{end}}
{{if $activeFolder}}
<!-- 收藏夹头信息 -->
<div class="collection-header">
<div class="collection-cover">
{{if $activeFolder.Description}}
<div class="collection-cover-placeholder"></div>
{{end}}
</div>
<div class="collection-info-row">
<div>
<h1 class="collection-name">{{$activeFolder.Name}}</h1>
<p class="collection-meta">
{{if $activeFolder.IsPublic}}公开{{else}}私密{{end}}
· 视频数 {{.FolderItems.Total}}
</p>
</div>
<div class="collection-actions">
<button class="space-action-btn primary small">播放全部</button>
<button class="space-action-btn outline small">批量操作</button>
</div>
</div>
</div>
<!-- 排序筛选栏 -->
<div class="collection-sort-bar">
<div class="sort-tabs">
<button class="sort-tab active">最近收藏</button>
<button class="sort-tab">最多播放</button>
<button class="sort-tab">最近投稿</button>
</div>
<div class="collection-filter">
<select class="filter-select">
<option>当前</option>
</select>
<div class="filter-search">
<input type="text" placeholder="请输入关键词" class="filter-search-input">
<button class="filter-search-btn">
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/></svg>
</button>
</div>
</div>
</div>
<!-- 文章卡片网格 -->
{{if .FolderItems}}
{{if gt (len .FolderItems.Items) 0}}
<div class="space-article-grid">
{{range $item := .FolderItems.Items}}
<article class="space-article-card">
<a href="/posts/{{$item.PostID}}" class="article-card-link">
<h3 class="article-card-title">{{if $item.PostTitle}}{{$item.PostTitle}}{{else}}无标题文章{{end}}</h3>
</a>
<div class="article-card-footer">
<span class="article-card-author">
<svg viewBox="0 0 24 24" width="12" height="12"><path fill="currentColor" d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg>
{{$.SpaceUser.Username}}
</span>
<span class="article-card-time">收藏于{{$item.CreatedAt.Format "01-02 15:04"}}</span>
</div>
</article>
{{end}}
</div>
{{else}}
<div class="space-empty-state">
<p>这个收藏夹还是空的</p>
</div>
{{end}}
{{else}}
<div class="space-empty-state"><p>暂无数据</p></div>
{{end}}
{{else}}
<div class="space-empty-state">
<p>还没有创建收藏夹,点击左侧「新建收藏夹」开始吧~</p>
</div>
{{end}}
{{else}}
<div class="space-empty-state"><p>加载失败</p></div>
{{end}}
</main>
{{/* ========== 设置 Tab仅本人 ==========*/}}
{{else if eq .ActiveTab "settings"}}
<main class="space-main-full settings-main">
<h1 class="space-section-title">隐私设置</h1>
<div class="privacy-settings-grid">
<!-- 公开我的收藏 -->
<div class="privacy-row">
<label class="privacy-label">公开我的收藏</label>
<label class="toggle-switch">
<input type="checkbox" id="privacy-collections"{{if .SpaceUser.PublicCollections}} checked{{end}}>
<span class="toggle-slider"></span>
</label>
</div>
<!-- 公开我的生日、个人标签 -->
<div class="privacy-row">
<label class="privacy-label">公开我的生日、个人标签</label>
<label class="toggle-switch">
<input type="checkbox" id="privacy-bio"{{if .SpaceUser.PublicBio}} checked{{end}}>
<span class="toggle-slider"></span>
</label>
</div>
<!-- 公开我的追番追剧 -->
<div class="privacy-row">
<label class="privacy-label">公开我的追番追剧</label>
<label class="toggle-switch">
<input type="checkbox" id="privacy-tracks"{{if .SpaceUser.PublicTracks}} checked{{end}}>
<span class="toggle-slider"></span>
</label>
</div>
<!-- 公开最近投市的视频 -->
<div class="privacy-row">
<label class="privacy-label">公开最近投稿的文章</label>
<label class="toggle-switch">
<input type="checkbox" id="privacy-posts"{{if .SpaceUser.PublicPosts}} checked{{end}}>
<span class="toggle-slider"></span>
</label>
</div>
<!-- 公开最近点赞的视频 -->
<div class="privacy-row">
<label class="privacy-label">公开最近点赞的文章</label>
<label class="toggle-switch">
<input type="checkbox" id="privacy-likes"{{if .SpaceUser.PublicLikes}} checked{{end}}>
<span class="toggle-slider"></span>
</label>
</div>
<!-- 公开我的关注列表 -->
<div class="privacy-row">
<label class="privacy-label">公开我的关注列表</label>
<label class="toggle-switch">
<input type="checkbox" id="privacy-follow-list"{{if .SpaceUser.FollowListPublic}} checked{{end}}>
<span class="toggle-slider"></span>
</label>
</div>
<!-- 公开我的粉丝列表 -->
<div class="privacy-row">
<label class="privacy-label">公开我的粉丝列表</label>
<label class="toggle-switch">
<input type="checkbox" id="privacy-follower-list"{{if .SpaceUser.FollowerListPublic}} checked{{end}}>
<span class="toggle-slider"></span>
</label>
</div>
<!-- 公开学校信息 -->
<div class="privacy-row">
<label class="privacy-label">公开学校信息</label>
<label class="toggle-switch">
<input type="checkbox" id="privacy-school"{{if .SpaceUser.PublicSchool}} checked{{end}}>
<span class="toggle-slider"></span>
</label>
</div>
<!-- 公开最近玩过的游戏 -->
<div class="privacy-row">
<label class="privacy-label">公开最近玩过的游戏</label>
<label class="toggle-switch">
<input type="checkbox" id="privacy-games"{{if .SpaceUser.PublicGames}} checked{{end}}>
<span class="toggle-slider"></span>
</label>
</div>
<!-- 公开拥有的粉丝装扮(仅APP) -->
<div class="privacy-row">
<label class="privacy-label">公开拥有的粉丝装扮(仅APP)</label>
<label class="toggle-switch">
<input type="checkbox" id="privacy-costumes">
<span class="toggle-slider"></span>
</label>
</div>
<!-- 公开我的追漫(仅APP) -->
<div class="privacy-row">
<label class="privacy-label">公开我的追漫(仅APP)</label>
<label class="toggle-switch">
<input type="checkbox" id="privacy-anime"{{if .SpaceUser.PublicAnime}} checked{{end}}>
<span class="toggle-slider"></span>
</label>
</div>
<!-- 公开隐藏的粉丝勋章 -->
<div class="privacy-row">
<label class="privacy-label">公开隐藏的粉丝勋章</label>
<label class="toggle-switch">
<input type="checkbox" id="privacy-medals">
<span class="toggle-slider"></span>
</label>
</div>
<!-- 投稿视频列表中展现直播播放 -->
<div class="privacy-row">
<label class="privacy-label">投稿视频列表中展现直播回放</label>
<label class="toggle-switch">
<input type="checkbox" id="privacy-live-replay">
<span class="toggle-slider"></span>
</label>
</div>
<!-- 投稿视频列表中展现课堂视频 -->
<div class="privacy-row">
<label class="privacy-label">投稿视频列表中展现课堂视频</label>
<label class="toggle-switch">
<input type="checkbox" id="privacy-classroom">
<span class="toggle-slider"></span>
</label>
</div>
<!-- 投稿视频列表中展现月充电专属视频 -->
<div class="privacy-row">
<label class="privacy-label">投稿视频列表中展现月充电专属视频</label>
<label class="toggle-switch">
<input type="checkbox" id="privacy-monthly-video" checked>
<span class="toggle-slider"></span>
</label>
</div>
</div>
</main>
{{end}}
</div><!-- /.space-card-layout -->
</div><!-- /.space-page -->
{{end}}
{{template "layout/footer.html" .}}
<script src="/static/js/common.js?v={{assetV "/static/js/common.js"}}"></script>
<!-- 访客空间:关注按钮交互 -->
{{if and (not .IsOwnSpace) (or (eq .ActiveTab "following") (eq .ActiveTab "followers"))}}
<script>
(function() {
var cards = document.querySelectorAll('.space-user-card');
if (!cards.length) return;
// 关注按钮状态文字映射
var labels = {
none: '关注',
following: '已关注',
followBack: '回关',
mutual: '已互粉'
};
function buttonState(status) {
if (status.i_follow && status.they_follow) return 'mutual';
if (status.i_follow) return 'following';
if (status.they_follow) return 'followBack';
return 'none';
}
function updateBtn(btn, status) {
var state = buttonState(status);
btn.textContent = labels[state];
btn.className = 'space-follow-btn';
if (state === 'following' || state === 'mutual') btn.classList.add('followed');
}
// 批量获取并更新每个卡片关注状态
cards.forEach(function(card) {
var btn = card.querySelector('.space-follow-btn');
if (!btn || btn.disabled) return;
var targetUid = card.getAttribute('data-uid');
fetch('/api/users/' + targetUid + '/follow-status')
.then(function(res) { return res.json(); })
.then(function(data) {
if (data && data.success && data.data) { updateBtn(btn, data.data); }
});
btn.addEventListener('click', function() {
btn.classList.add('waiting');
var xhr = new XMLHttpRequest();
xhr.open('POST', '/api/users/' + targetUid + '/follow', true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
btn.classList.remove('waiting');
if (xhr.status === 200) {
try { var resp = JSON.parse(xhr.responseText); if (resp.data && resp.data.status) updateBtn(btn, resp.data.status); } catch(e) {}
}
}
};
xhr.send();
});
});
// 顶部关注按钮
var topFollowBtn = document.getElementById('space-follow-btn');
if (topFollowBtn) {
var topUid = topFollowBtn.getAttribute('data-uid');
fetch('/api/users/' + topUid + '/follow-status')
.then(function(res) { return res.json(); })
.then(function(data) {
if (data && data.success && data.data) {
var s = data.data;
topFollowBtn.textContent = (s.i_follow ? (s.they_follow ? '已互粉' : '已关注') : (s.they_follow ? '回关' : '+ 关注'));
if (s.i_follow) topFollowBtn.classList.add('followed');
}
});
topFollowBtn.addEventListener('click', function() {
var xhr = new XMLHttpRequest();
xhr.open('POST', '/api/users/' + topUid + '/follow', true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
try {
var resp = JSON.parse(xhr.responseText);
if (resp.data && resp.data.status) {
var st = resp.data.status;
topFollowBtn.textContent = (st.i_follow ? (st.they_follow ? '已互粉' : '已关注') : (st.they_follow ? '回关' : '+ 关注'));
topFollowBtn.classList.toggle('followed', st.i_follow);
// 同时更新下方卡片中的对应按钮
cards.forEach(function(c) {
if (c.getAttribute('data-uid') === topUid) {
var cb = c.querySelector('.space-follow-btn'); if(cb) updateBtn(cb, st);
}
});
}
} catch(e){}
}
};
xhr.send();
});
}
})();
</script>
{{end}}
<!-- 收藏夹交互脚本 -->
{{if eq .ActiveTab "collections"}}
<script>
function toggleFolderSection(el) {
el.parentElement.querySelector('#folder-nav').classList.toggle('collapsed');
el.classList.toggle('collapsed');
}
function showNewFolderInput() {
document.getElementById('new-folder-input').style.display = '';
document.getElementById('new-folder-name').focus();
}
function createFolder() {
var name = document.getElementById('new-folder-name').value.trim();
if (!name) return;
fetch('/api/folders', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({name: name, is_public: false})
}).then(function(r) { return r.json(); }).then(function(d) {
if (d.success) { location.reload(); } else { alert(d.message || '创建失败'); }
});
}
// 隐私开关变更保存
document.querySelectorAll('.privacy-settings-grid .toggle-switch input').forEach(function(cb) {
cb.addEventListener('change', function() {
// TODO: 调用 API 保存隐私设置
});
});
</script>
{{end}}
<!-- 设置页交互脚本 -->
{{if eq .ActiveTab "settings"}}
<script>
// 隐私开关变更时自动保存到后端
document.querySelectorAll('#privacy-follow-list, #privacy-follower-list').forEach(function(cb) {
cb.addEventListener('change', function() {
var field = this.id.replace('privacy-', '').replace(/-/g, '_');
var value = this.checked;
fetch('/api/space/privacy', {
method: 'PUT',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({field: field, value: value})
}).then(function(r){return r.json()}).then(function(d){
if(!d.success) { this.checked=!value; alert(d.message||'保存失败'); }
}.bind(this));
});
});
</script>
{{end}}
</body>
</html>