增加非公开关注和关注区分
This commit is contained in:
@@ -61,12 +61,11 @@ router.get('/search', async (req, res) => {
|
||||
*/
|
||||
router.get('/following', async (req, res) => {
|
||||
try {
|
||||
const { offset = 0, limit = 30 } = req.query;
|
||||
const { restrict = 'public' } = req.query;
|
||||
|
||||
const artistService = new ArtistService(req.backend.getAuth());
|
||||
const result = await artistService.getFollowingArtists({
|
||||
offset: parseInt(offset),
|
||||
limit: parseInt(limit)
|
||||
restrict
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
|
||||
@@ -93,7 +93,7 @@ class ArtistService {
|
||||
*/
|
||||
async getArtistFollowing(artistId, options = {}) {
|
||||
try {
|
||||
const { restrict = 'public', offset = 0, limit = 30 } = options;
|
||||
const { restrict = 'public', offset = 0, limit = 100 } = options;
|
||||
|
||||
const params = {
|
||||
user_id: artistId,
|
||||
@@ -124,7 +124,7 @@ class ArtistService {
|
||||
*/
|
||||
async getArtistFollowers(artistId, options = {}) {
|
||||
try {
|
||||
const { offset = 0, limit = 30 } = options;
|
||||
const { offset = 0, limit = 100 } = options;
|
||||
|
||||
const params = {
|
||||
user_id: artistId,
|
||||
@@ -154,7 +154,7 @@ class ArtistService {
|
||||
*/
|
||||
async getFollowingArtists(options = {}) {
|
||||
try {
|
||||
const { offset = 0, limit = 30 } = options;
|
||||
const { offset = 0, limit = 30, restrict = 'public' } = options;
|
||||
|
||||
// 检查认证状态
|
||||
if (!this.auth || !this.auth.accessToken) {
|
||||
@@ -180,13 +180,20 @@ class ArtistService {
|
||||
};
|
||||
}
|
||||
|
||||
let allArtists = [];
|
||||
let currentOffset = offset;
|
||||
let hasMore = true;
|
||||
|
||||
// 循环获取所有关注的作者
|
||||
while (hasMore) {
|
||||
const params = {
|
||||
user_id: currentUserId,
|
||||
restrict: 'public',
|
||||
offset,
|
||||
restrict,
|
||||
offset: currentOffset,
|
||||
limit,
|
||||
};
|
||||
|
||||
console.log(`请求关注列表: offset=${currentOffset}, limit=${limit}`);
|
||||
const response = await this.makeRequest('GET', `/v1/user/following?${stringify(params)}`);
|
||||
|
||||
// 转换数据格式以匹配前端期望
|
||||
@@ -198,11 +205,23 @@ class ArtistService {
|
||||
is_followed: user.user.is_followed || false,
|
||||
}));
|
||||
|
||||
allArtists.push(...artists);
|
||||
console.log(`本次获取到 ${artists.length} 个作者,累计 ${allArtists.length} 个`);
|
||||
|
||||
// 如果返回的数量少于limit,说明已经获取完所有数据
|
||||
if (artists.length < limit) {
|
||||
hasMore = false;
|
||||
console.log('已获取完所有关注的作者');
|
||||
} else {
|
||||
currentOffset += artists.length;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
artists: artists,
|
||||
total: artists.length,
|
||||
artists: allArtists,
|
||||
total: allArtists.length,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
|
||||
@@ -85,10 +85,9 @@ class ArtistService {
|
||||
/**
|
||||
* 获取当前用户关注的作者列表
|
||||
*/
|
||||
async getFollowingArtists(options: { offset?: number; limit?: number } = {}): Promise<ApiResponse<{ artists: Artist[]; total: number }>> {
|
||||
async getFollowingArtists(options: { restrict?: 'public' | 'private' } = {}): Promise<ApiResponse<{ artists: Artist[]; total: number }>> {
|
||||
const params = new URLSearchParams();
|
||||
if (options.offset !== undefined) params.append('offset', options.offset.toString());
|
||||
if (options.limit !== undefined) params.append('limit', options.limit.toString());
|
||||
if (options.restrict) params.append('restrict', options.restrict);
|
||||
|
||||
const query = params.toString();
|
||||
const url = query ? `/api/artist/following?${query}` : '/api/artist/following';
|
||||
|
||||
+12
-6
@@ -26,7 +26,7 @@ export const useArtistStore = defineStore('artist', () => {
|
||||
});
|
||||
|
||||
// 获取关注的作者
|
||||
const fetchFollowingArtists = async (forceRefresh = false) => {
|
||||
const fetchFollowingArtists = async (forceRefresh = false, options: { restrict?: 'public' | 'private' } = {}) => {
|
||||
// 如果数据不是过期的且不是强制刷新,直接返回缓存的数据
|
||||
if (!forceRefresh && !isDataStale.value && hasFollowingArtists.value) {
|
||||
return {
|
||||
@@ -39,12 +39,18 @@ export const useArtistStore = defineStore('artist', () => {
|
||||
loading.value = true;
|
||||
error.value = null;
|
||||
|
||||
const response = await artistService.getFollowingArtists();
|
||||
const restrict = options.restrict || 'public';
|
||||
|
||||
// 后端会自动循环获取所有数据
|
||||
const response = await artistService.getFollowingArtists({
|
||||
restrict: restrict
|
||||
});
|
||||
|
||||
if (response.success && response.data) {
|
||||
followingArtists.value = response.data.artists;
|
||||
lastFetchTime.value = Date.now();
|
||||
} else {
|
||||
throw new Error(response.error || '获取关注列表失败');
|
||||
throw new Error('获取关注列表失败');
|
||||
}
|
||||
|
||||
return response;
|
||||
@@ -70,7 +76,7 @@ export const useArtistStore = defineStore('artist', () => {
|
||||
searchResults.value = response.data.artists;
|
||||
return response;
|
||||
} else {
|
||||
throw new Error(response.error || '搜索失败');
|
||||
throw new Error('搜索失败');
|
||||
}
|
||||
} catch (err) {
|
||||
error.value = err instanceof Error ? err.message : '搜索失败';
|
||||
@@ -97,7 +103,7 @@ export const useArtistStore = defineStore('artist', () => {
|
||||
followingArtists.value.push(artistToAdd);
|
||||
}
|
||||
} else {
|
||||
throw new Error(response.error || '关注失败');
|
||||
throw new Error('关注失败');
|
||||
}
|
||||
|
||||
return response;
|
||||
@@ -123,7 +129,7 @@ export const useArtistStore = defineStore('artist', () => {
|
||||
artist.is_followed = false;
|
||||
}
|
||||
} else {
|
||||
throw new Error(response.error || '取消关注失败');
|
||||
throw new Error('取消关注失败');
|
||||
}
|
||||
|
||||
return response;
|
||||
|
||||
+144
-18
@@ -4,6 +4,23 @@
|
||||
<div class="page-header">
|
||||
<h1 class="page-title">作者管理</h1>
|
||||
<div class="header-actions">
|
||||
<!-- 关注类型切换 -->
|
||||
<div class="follow-type-toggle">
|
||||
<button
|
||||
@click="switchFollowType('public')"
|
||||
:class="['toggle-btn', { active: followType === 'public' }]"
|
||||
:disabled="artistStore.loading"
|
||||
>
|
||||
公开关注
|
||||
</button>
|
||||
<button
|
||||
@click="switchFollowType('private')"
|
||||
:class="['toggle-btn', { active: followType === 'private' }]"
|
||||
:disabled="artistStore.loading"
|
||||
>
|
||||
非公开关注
|
||||
</button>
|
||||
</div>
|
||||
<button @click="handleRefresh" class="btn btn-secondary" :disabled="artistStore.loading">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor" class="refresh-icon">
|
||||
<path
|
||||
@@ -44,6 +61,11 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 作者统计信息 -->
|
||||
<div v-if="artistStore.hasFollowingArtists" class="stats-section">
|
||||
<span>共 {{ artistStore.followingArtists.length }} 个关注的作者</span>
|
||||
</div>
|
||||
|
||||
<div v-if="artistStore.followingArtists.length > 0" class="artists-grid">
|
||||
<ArtistCard v-for="artist in artistStore.followingArtists" :key="artist.id" :artist="artist"
|
||||
:show-follow-button="false" :show-unfollow-button="true" @unfollow="handleUnfollow"
|
||||
@@ -130,8 +152,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onUnmounted, watch } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { ref, onMounted, watch } from 'vue';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import { useAuthStore } from '@/stores/auth';
|
||||
import { useArtistStore } from '@/stores/artist';
|
||||
import downloadService from '@/services/download';
|
||||
@@ -139,6 +161,7 @@ import downloadService from '@/services/download';
|
||||
import ArtistCard from '@/components/artist/ArtistCard.vue';
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const authStore = useAuthStore();
|
||||
const artistStore = useArtistStore();
|
||||
|
||||
@@ -149,13 +172,42 @@ const downloadLimit = ref('50');
|
||||
const downloading = ref(false);
|
||||
const downloadSuccess = ref<string | null>(null);
|
||||
|
||||
// 关注类型切换
|
||||
const followType = ref<'public' | 'private'>('public');
|
||||
|
||||
// 获取关注的作者
|
||||
const fetchFollowingArtists = async () => {
|
||||
const fetchFollowingArtists = async (forceRefresh = false) => {
|
||||
try {
|
||||
await artistStore.fetchFollowingArtists();
|
||||
} catch (err) {
|
||||
console.error('获取关注列表失败:', err);
|
||||
artistStore.loading = true;
|
||||
artistStore.error = null;
|
||||
|
||||
const response = await artistStore.fetchFollowingArtists(forceRefresh, {
|
||||
restrict: followType.value
|
||||
});
|
||||
|
||||
if (!response.success) {
|
||||
throw new Error('获取关注列表失败');
|
||||
}
|
||||
} catch (err) {
|
||||
artistStore.error = err instanceof Error ? err.message : '获取关注列表失败';
|
||||
console.error('获取关注列表失败:', err);
|
||||
} finally {
|
||||
artistStore.loading = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 切换关注类型
|
||||
const switchFollowType = async (type: 'public' | 'private') => {
|
||||
followType.value = type;
|
||||
|
||||
// 更新URL参数
|
||||
router.push({
|
||||
query: {
|
||||
type: type
|
||||
}
|
||||
});
|
||||
|
||||
await fetchFollowingArtists(true);
|
||||
};
|
||||
|
||||
// 关注作者
|
||||
@@ -171,6 +223,8 @@ const handleFollow = async (artistId: number) => {
|
||||
const handleUnfollow = async (artistId: number) => {
|
||||
try {
|
||||
await artistStore.unfollowArtist(artistId);
|
||||
// 重新获取数据
|
||||
await fetchFollowingArtists(true);
|
||||
} catch (err) {
|
||||
console.error('取消关注失败:', err);
|
||||
}
|
||||
@@ -228,24 +282,30 @@ const handleDownloadArtist = async () => {
|
||||
// 刷新数据
|
||||
const handleRefresh = async () => {
|
||||
try {
|
||||
await artistStore.refreshData();
|
||||
await fetchFollowingArtists(true);
|
||||
} catch (err) {
|
||||
console.error('刷新失败:', err);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
// 监听数据过期状态,自动刷新
|
||||
watch(() => artistStore.isDataStale, (isStale) => {
|
||||
if (isStale && artistStore.hasFollowingArtists) {
|
||||
console.log('数据已过期,自动刷新...');
|
||||
fetchFollowingArtists();
|
||||
// 监听路由变化
|
||||
watch(() => route.query, () => {
|
||||
// 恢复关注类型状态
|
||||
const urlType = route.query.type as string;
|
||||
if (urlType && ['public', 'private'].includes(urlType) && urlType !== followType.value) {
|
||||
followType.value = urlType as 'public' | 'private';
|
||||
fetchFollowingArtists(true);
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
fetchFollowingArtists();
|
||||
onMounted(async () => {
|
||||
// 检查URL参数并恢复状态
|
||||
const urlType = route.query.type as string;
|
||||
if (urlType && ['public', 'private'].includes(urlType)) {
|
||||
followType.value = urlType as 'public' | 'private';
|
||||
}
|
||||
|
||||
await fetchFollowingArtists();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -281,6 +341,45 @@ onMounted(() => {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.follow-type-toggle {
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
background: #f3f4f6;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.25rem;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.toggle-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0.5rem 1rem;
|
||||
cursor: pointer;
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
border-radius: 0.375rem;
|
||||
transition: all 0.2s;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.toggle-btn:hover:not(:disabled) {
|
||||
background: #e5e7eb;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.toggle-btn:disabled {
|
||||
background: #f9fafb;
|
||||
color: #9ca3af;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.toggle-btn.active {
|
||||
background: #4f46e5;
|
||||
color: white;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.refresh-icon {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
@@ -348,14 +447,25 @@ onMounted(() => {
|
||||
height: 0.875rem;
|
||||
}
|
||||
|
||||
.stats-section {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
padding: 0.75rem 1rem;
|
||||
background: #f3f4f6;
|
||||
border-radius: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.artists-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.empty-section {
|
||||
text-align: center;
|
||||
padding: 4rem 0;
|
||||
@@ -574,8 +684,24 @@ onMounted(() => {
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.follow-type-toggle {
|
||||
margin-right: 0;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.artists-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.stats-section {
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user