- 新增关注系统:UserFollow 模型、FollowService(toggle/status/列表隐私控制) - User 新增 FollowersCount/FollowingCount/FollowListPublic/NotifyPrefs 字段 - Space 页面增加四态关注按钮(关注/已关注/回关/已互粉)+ 粉丝/关注数链接 - 新增 /space/:uid/followers 和 /space/:uid/following 列表页 - 新增 NotifyFollow/NotifyLikeAggregated 通知类型 - ReactionService 点赞时写入 daily_like_summary,访问时生成聚合通知 - FollowService 关注时触发 NotifyFollow 通知 - 消息中心侧边栏升级为分类 TAB(全部/系统通知/@艾特/点赞/关注) - NotificationService 新增 ListByCategory 按分类分页查询
173 lines
6.0 KiB
HTML
173 lines
6.0 KiB
HTML
{{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-header">
|
|
<div class="container space-header-inner">
|
|
<div class="space-avatar">
|
|
{{if .SpaceUser.Avatar}}
|
|
<img src="{{.SpaceUser.Avatar}}" alt="{{.SpaceUser.Username}}">
|
|
{{else}}
|
|
<div class="space-avatar-placeholder">{{slice .SpaceUser.Username 0 1}}</div>
|
|
{{end}}
|
|
</div>
|
|
<div class="space-info">
|
|
<h1 class="space-username">{{.SpaceUser.Username}}</h1>
|
|
<p class="space-bio">{{if eq .SpaceUser.Status "banned"}}ⓘ 该账号已被封禁{{else if .SpaceUser.Bio}}{{.SpaceUser.Bio}}{{else}} {{end}}</p>
|
|
<div class="space-meta">
|
|
<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}}
|
|
<div class="space-posts-list">
|
|
{{range .Posts}}
|
|
<article class="space-post-card">
|
|
<div class="space-post-main">
|
|
<h3 class="space-post-title">
|
|
<a href="/posts/{{.ID}}">{{.Title}}</a>
|
|
</h3>
|
|
<p class="space-post-excerpt">{{if .Excerpt}}{{.Excerpt}}{{else}}{{printf "%.200s" .Body}}{{end}}</p>
|
|
<div class="space-post-meta">
|
|
<span class="space-post-status">已发布</span>
|
|
<span class="space-post-time">{{.CreatedAt.Format "2006-01-02"}}</span>
|
|
</div>
|
|
</div>
|
|
</article>
|
|
{{end}}
|
|
</div>
|
|
{{end}}
|
|
|
|
<!-- 分页 -->
|
|
{{if gt .TotalPages 1}}
|
|
<div class="pagination">
|
|
{{if gt .Page 1}}
|
|
<a href="?page={{.PrevPage}}" class="page-link">上一页</a>
|
|
{{end}}
|
|
<span class="page-info">第 {{.Page}} / {{.TotalPages}} 页 (共 {{.Total}} 条)</span>
|
|
{{if lt .Page .TotalPages}}
|
|
<a href="?page={{.NextPage}}" class="page-link">下一页</a>
|
|
{{end}}
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{{end}}
|
|
|
|
{{template "layout/footer.html" .}}
|
|
|
|
<script src="/static/js/common.js?v={{assetV "/static/js/common.js"}}"></script>
|
|
{{if not .IsOwnSpace}}
|
|
<script>
|
|
(function() {
|
|
var followBtn = document.getElementById('followBtn');
|
|
if (!followBtn) return;
|
|
var targetUid = followBtn.getAttribute('data-uid');
|
|
var csrfMeta = document.querySelector('meta[name="csrf-token"]');
|
|
var csrfToken = csrfMeta ? csrfMeta.getAttribute('content') : '';
|
|
|
|
// 四个按钮状态文案
|
|
var labels = {
|
|
none: '关注',
|
|
following: '已关注',
|
|
followBack: '回关',
|
|
mutual: '已互粉'
|
|
};
|
|
|
|
var classes = {
|
|
none: '',
|
|
following: 'following',
|
|
followBack: '',
|
|
mutual: '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(status) {
|
|
var state = buttonState(status);
|
|
followBtn.textContent = labels[state];
|
|
followBtn.className = 'follow-btn ' + (classes[state] || '');
|
|
}
|
|
|
|
// 获取初始状态
|
|
fetch('/api/users/' + targetUid + '/follow-status')
|
|
.then(function(res) { return res.json(); })
|
|
.then(function(data) {
|
|
if (data && data.success && data.data) {
|
|
updateBtn(data.data);
|
|
}
|
|
});
|
|
|
|
// 点击切换
|
|
followBtn.addEventListener('click', function() {
|
|
followBtn.classList.add('waiting');
|
|
var xhr = new XMLHttpRequest();
|
|
xhr.open('POST', '/api/users/' + targetUid + '/follow', true);
|
|
xhr.setRequestHeader('X-CSRF-Token', csrfToken);
|
|
xhr.onreadystatechange = function() {
|
|
if (xhr.readyState === 4) {
|
|
followBtn.classList.remove('waiting');
|
|
if (xhr.status === 200) {
|
|
try {
|
|
var resp = JSON.parse(xhr.responseText);
|
|
if (resp.data && resp.data.status) {
|
|
updateBtn(resp.data.status);
|
|
}
|
|
} catch(e) {}
|
|
}
|
|
}
|
|
};
|
|
xhr.send();
|
|
});
|
|
})();
|
|
</script>
|
|
{{end}}
|
|
</body>
|
|
</html>
|