ZHIWEI666 commited on
Commit
8b79a67
·
verified ·
1 Parent(s): d1e6e89

Upload 104 files

Browse files
前端页面/core/全局配置.js CHANGED
@@ -263,6 +263,7 @@ export const CACHE = {
263
  ...RESOURCE_LIST_PREFIXES
264
  ],
265
  RESOURCE_LIST_PREFIXES,
 
266
  PUBLIC_LISTS: {
267
  tasks: { prefix: "TasksCache_", endpoint: "/api/tasks" },
268
  posts: { prefix: "PostsCache_", endpoint: "/api/posts" },
@@ -628,6 +629,43 @@ export function getTipLevelConfig() {
628
  // 缓存键:ComfyCommunity_ProfileCache_{account}
629
 
630
  const PROFILE_CACHE_PREFIX = "ComfyCommunity_ProfileCache_";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
631
  const _pendingProfileRequests = new Map(); // 每账号共享请求与全部订阅者
632
  const PENDING_TIMEOUT = 30000; // 挂起请求超时清理时间(毫秒)
633
  let nextAvatarRenderId = 0;
@@ -734,12 +772,16 @@ function _mapAvatarField(profile) {
734
  }
735
 
736
  const PUBLIC_PROFILE_FIELDS = ["account", "name", "avatar", "avatarDataUrl", "intro", "gender", "age",
737
- "country", "region", "created_at", "followers", "following", "privacy", "is_admin", "bannerUrl",
738
  "receivedLikes", "receivedFavorites", "receivedUses"];
739
  function _publicProfile(profile) {
740
- return Object.fromEntries(PUBLIC_PROFILE_FIELDS.filter(key => Object.prototype.hasOwnProperty.call(profile, key))
741
  .map(key => [key, ['avatar', 'avatarDataUrl', 'bannerUrl'].includes(key)
742
  ? normalizeProfileImageUrl(profile[key]) : profile[key]]));
 
 
 
 
743
  }
744
 
745
  function _validProfile(profile) {
@@ -753,7 +795,7 @@ function _validProfile(profile) {
753
  * @returns {Object|null} 缓存的用户资料
754
  */
755
  export function getCachedProfile(account) {
756
- if (!account) return null;
757
  try {
758
  const cached = localStorage.getItem(PROFILE_CACHE_PREFIX + account);
759
  if (!cached) return null;
@@ -780,6 +822,9 @@ export function setCachedProfile(account, profile) {
780
  if (!account || !_validProfile(profile)) return;
781
  try {
782
  localStorage.setItem(PROFILE_CACHE_PREFIX + account, JSON.stringify(_publicProfile(profile)));
 
 
 
783
  } catch {}
784
  }
785
 
@@ -799,6 +844,7 @@ export function getProfileWithSWR(account, apiFn, onUpdate) {
799
  // 第二轨:同账号只取一次,但每个视图都订阅结果。
800
  let entry = _pendingProfileRequests.get(account);
801
  if (!entry && typeof apiFn === 'function') {
 
802
  entry = { listeners: new Set(), timer: null };
803
  _pendingProfileRequests.set(account, entry);
804
  const release = () => {
@@ -809,7 +855,7 @@ export function getProfileWithSWR(account, apiFn, onUpdate) {
809
  entry.timer = setTimeout(release, PENDING_TIMEOUT);
810
  Promise.resolve().then(() => apiFn(account)).then(res => {
811
  // 超时后的旧结果不能覆盖新请求,也不能重新写入旧资料。
812
- if (_pendingProfileRequests.get(account) !== entry) return;
813
  if (res?.status && res.status !== 'success') return;
814
  const data = res && Object.hasOwn(res, 'data') ? res.data : res;
815
  if (!_validProfile(data)) return;
 
263
  ...RESOURCE_LIST_PREFIXES
264
  ],
265
  RESOURCE_LIST_PREFIXES,
266
+ CREATOR_DETAILS_CACHE_PREFIX: "api_/api/creators/",
267
  PUBLIC_LISTS: {
268
  tasks: { prefix: "TasksCache_", endpoint: "/api/tasks" },
269
  posts: { prefix: "PostsCache_", endpoint: "/api/posts" },
 
629
  // 缓存键:ComfyCommunity_ProfileCache_{account}
630
 
631
  const PROFILE_CACHE_PREFIX = "ComfyCommunity_ProfileCache_";
632
+ let profileCacheDefault = { readable: true };
633
+ const profileCacheStates = new Map();
634
+ const profileCacheState = account => profileCacheStates.get(account) || profileCacheDefault;
635
+ function rememberProfileCacheState(account, readable) {
636
+ if (!profileCacheStates.has(account) && profileCacheStates.size >= CACHE.MAX_MEMORY_ITEMS) {
637
+ profileCacheStates.clear();
638
+ profileCacheDefault = { readable: false };
639
+ for (const pending of _pendingProfileRequests.values()) {
640
+ clearTimeout(pending.timer);
641
+ pending.listeners.clear();
642
+ }
643
+ _pendingProfileRequests.clear();
644
+ }
645
+ const state = { readable };
646
+ profileCacheStates.set(account, state);
647
+ return state;
648
+ }
649
+
650
+ export function captureProfileRead(account) {
651
+ const state = profileCacheStates.get(account) || rememberProfileCacheState(account, profileCacheDefault.readable);
652
+ return () => profileCacheState(account) === state;
653
+ }
654
+
655
+ export function invalidateCachedProfiles(accounts) {
656
+ for (const account of new Set(accounts)) {
657
+ if (typeof account !== 'string' || !account.trim()) continue;
658
+ rememberProfileCacheState(account, false);
659
+ const pending = _pendingProfileRequests.get(account);
660
+ if (pending) {
661
+ _pendingProfileRequests.delete(account);
662
+ clearTimeout(pending.timer);
663
+ pending.listeners.clear();
664
+ }
665
+ try { localStorage.removeItem(PROFILE_CACHE_PREFIX + account); } catch {}
666
+ }
667
+ }
668
+
669
  const _pendingProfileRequests = new Map(); // 每账号共享请求与全部订阅者
670
  const PENDING_TIMEOUT = 30000; // 挂起请求超时清理时间(毫秒)
671
  let nextAvatarRenderId = 0;
 
772
  }
773
 
774
  const PUBLIC_PROFILE_FIELDS = ["account", "name", "avatar", "avatarDataUrl", "intro", "gender", "age",
775
+ "country", "region", "created_at", "followers", "following", "following_count", "privacy", "is_admin", "bannerUrl",
776
  "receivedLikes", "receivedFavorites", "receivedUses"];
777
  function _publicProfile(profile) {
778
+ const result = Object.fromEntries(PUBLIC_PROFILE_FIELDS.filter(key => Object.prototype.hasOwnProperty.call(profile, key))
779
  .map(key => [key, ['avatar', 'avatarDataUrl', 'bannerUrl'].includes(key)
780
  ? normalizeProfileImageUrl(profile[key]) : profile[key]]));
781
+ result.following_count = Number.isSafeInteger(result.following_count) && result.following_count >= 0
782
+ ? result.following_count : (Array.isArray(result.following) ? result.following.length : 0);
783
+ if (result.privacy?.follows === true) result.following = [];
784
+ return result;
785
  }
786
 
787
  function _validProfile(profile) {
 
795
  * @returns {Object|null} 缓存的用户资料
796
  */
797
  export function getCachedProfile(account) {
798
+ if (!account || !profileCacheState(account).readable) return null;
799
  try {
800
  const cached = localStorage.getItem(PROFILE_CACHE_PREFIX + account);
801
  if (!cached) return null;
 
822
  if (!account || !_validProfile(profile)) return;
823
  try {
824
  localStorage.setItem(PROFILE_CACHE_PREFIX + account, JSON.stringify(_publicProfile(profile)));
825
+ const state = profileCacheStates.get(account);
826
+ if (state) state.readable = true;
827
+ else rememberProfileCacheState(account, true);
828
  } catch {}
829
  }
830
 
 
844
  // 第二轨:同账号只取一次,但每个视图都订阅结果。
845
  let entry = _pendingProfileRequests.get(account);
846
  if (!entry && typeof apiFn === 'function') {
847
+ const isCurrent = captureProfileRead(account);
848
  entry = { listeners: new Set(), timer: null };
849
  _pendingProfileRequests.set(account, entry);
850
  const release = () => {
 
855
  entry.timer = setTimeout(release, PENDING_TIMEOUT);
856
  Promise.resolve().then(() => apiFn(account)).then(res => {
857
  // 超时后的旧结果不能覆盖新请求,也不能重新写入旧资料。
858
+ if (_pendingProfileRequests.get(account) !== entry || !isCurrent()) return;
859
  if (res?.status && res.status !== 'success') return;
860
  const data = res && Object.hasOwn(res, 'data') ? res.data : res;
861
  if (!_validProfile(data)) return;
前端页面/core/榜单数据来源.js CHANGED
@@ -2,7 +2,7 @@ import { api, requestCancelManager } from './网络请求API.js';
2
  import { API, CACHE, PAGINATION } from './全局配置.js';
3
  import { requireListResponseData, getCacheWithMeta, removeCache, writeListCache } from '../components/性能优化工具.js';
4
 
5
- import { captureCacheWrite } from './网络请求_缓存管理.js';
6
  import { getResponseCacheSource } from './网络请求_基础设施.js';
7
 
8
 
@@ -99,6 +99,7 @@ export function readSidebarListCache(key, { creators = false } = {}) {
99
  }
100
 
101
  export function readCreatorsSessionCache() {
 
102
  let raw;
103
  try { raw = sessionStorage.getItem(CACHE.LOCAL_KEYS.CREATORS_SESSION); }
104
  catch { return null; } // 无法读取不等于损坏,保留未知内容。
@@ -116,6 +117,7 @@ export function writeCreatorsSessionCache(data) {
116
  if (!validCreatorList(data)) return false;
117
  try {
118
  sessionStorage.setItem(CACHE.LOCAL_KEYS.CREATORS_SESSION, JSON.stringify(data));
 
119
  return true;
120
  } catch { return false; }
121
  }
 
2
  import { API, CACHE, PAGINATION } from './全局配置.js';
3
  import { requireListResponseData, getCacheWithMeta, removeCache, writeListCache } from '../components/性能优化工具.js';
4
 
5
+ import { captureCacheWrite, isCreatorsSessionCacheCurrent, markCreatorsSessionCacheCurrent } from './网络请求_缓存管理.js';
6
  import { getResponseCacheSource } from './网络请求_基础设施.js';
7
 
8
 
 
99
  }
100
 
101
  export function readCreatorsSessionCache() {
102
+ if (!isCreatorsSessionCacheCurrent()) return null;
103
  let raw;
104
  try { raw = sessionStorage.getItem(CACHE.LOCAL_KEYS.CREATORS_SESSION); }
105
  catch { return null; } // 无法读取不等于损坏,保留未知内容。
 
117
  if (!validCreatorList(data)) return false;
118
  try {
119
  sessionStorage.setItem(CACHE.LOCAL_KEYS.CREATORS_SESSION, JSON.stringify(data));
120
+ markCreatorsSessionCacheCurrent();
121
  return true;
122
  } catch { return false; }
123
  }
前端页面/core/类型定义.js CHANGED
@@ -29,6 +29,7 @@
29
  * @property {number} [created_at] - 注册时间戳
30
  * @property {string[]} [followers] - 粉丝列表
31
  * @property {string[]} [following] - 关注列表
 
32
  * @property {PrivacySettings} [privacy] - 隐私设置
33
  */
34
 
 
29
  * @property {number} [created_at] - 注册时间戳
30
  * @property {string[]} [followers] - 粉丝列表
31
  * @property {string[]} [following] - 关注列表
32
+ * @property {number} [following_count] - 关注人数
33
  * @property {PrivacySettings} [privacy] - 隐私设置
34
  */
35
 
前端页面/core/网络请求_业务API.js CHANGED
@@ -8,9 +8,9 @@
8
  // ==========================================
9
 
10
  import { request } from "./网络请求_基础设施.js";
11
- import { API } from "./全局配置.js";
12
  import { t } from "../components/用户体验_国际化.js";
13
- import { removeCache } from "./网络请求_缓存管理.js";
14
  import { requireListResponseData } from "../components/性能优化工具.js";
15
 
16
  function requestPublicList(endpoint, options = {}) {
@@ -63,15 +63,36 @@ const api = {
63
  },
64
 
65
  async getUserProfile(account, options = {}) { return request(`/api/users/${account}`, options); },
66
- async updateUserProfile(account, data) { return request(`/api/users/${account}`, { method: "PUT", body: data }); },
67
- async updateUserSettings(account, data) { return request(`/api/users/${account}/settings`, { method: "PUT", body: data }); },
68
- async updatePrivacy(account, privacy) { return request(`/api/users/${account}/privacy`, { method: "PUT", body: privacy }); },
69
- async toggleFollow(userId, targetAccount, isActive) { return request("/api/users/follow", { method: "POST", body: { user_id: userId, target_account: targetAccount, is_active: isActive } }); },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  async getCreators(sort, limit, options = {}) { return requestPublicList(`/api/creators?sort=${sort}&limit=${limit}`, options); },
71
  // 🔍 带上取消分组 ID:用户继续输入时,上一次仍在途的搜索请求可被 requestCancelManager 撤销,
72
  // 不再占用并发额度与超时预算(调用时机见 侧边栏数据引擎.js 的创作者搜索分支)
73
  async searchCreators(keyword, sort, limit = 50, options = {}) { return requestPublicList(`/api/creators/search?keyword=${encodeURIComponent(keyword)}&sort=${sort}&limit=${limit}`, { componentId: API.SEARCH_COMPONENT_ID, ...options }); },
74
- async getCreatorDetails(account, options = {}) { return request(`/api/creators/${account}/details`, options); },
 
 
 
 
 
75
  async uploadFile(file, fileType, timeout = 120000) {
76
  const formData = new FormData();
77
  formData.append("file", file);
 
8
  // ==========================================
9
 
10
  import { request } from "./网络请求_基础设施.js";
11
+ import { API, CACHE, invalidateCachedProfiles } from "./全局配置.js";
12
  import { t } from "../components/用户体验_国际化.js";
13
+ import { removeCache, invalidateFollowCaches, captureCacheWrite } from "./网络请求_缓存管理.js";
14
  import { requireListResponseData } from "../components/性能优化工具.js";
15
 
16
  function requestPublicList(endpoint, options = {}) {
 
63
  },
64
 
65
  async getUserProfile(account, options = {}) { return request(`/api/users/${account}`, options); },
66
+ async updateUserProfile(account, data) {
67
+ const response = await request(`/api/users/${account}`, { method: "PUT", body: data });
68
+ if (response?.status === 'success') invalidateCachedProfiles([account]);
69
+ return response;
70
+ },
71
+ async updateUserSettings(account, data) {
72
+ const response = await request(`/api/users/${account}/settings`, { method: "PUT", body: data });
73
+ if (response?.status === 'success') invalidateCachedProfiles([account]);
74
+ return response;
75
+ },
76
+ async updatePrivacy(account, privacy) {
77
+ const response = await request(`/api/users/${account}/privacy`, { method: "PUT", body: privacy });
78
+ if (response?.status === 'success') invalidateCachedProfiles([account]);
79
+ return response;
80
+ },
81
+ async toggleFollow(userId, targetAccount, isActive) {
82
+ const response = await request("/api/users/follow", { method: "POST", body: { user_id: userId, target_account: targetAccount, is_active: isActive } });
83
+ if (response?.status === 'success') invalidateFollowCaches(userId, targetAccount);
84
+ return response;
85
+ },
86
  async getCreators(sort, limit, options = {}) { return requestPublicList(`/api/creators?sort=${sort}&limit=${limit}`, options); },
87
  // 🔍 带上取消分组 ID:用户继续输入时,上一次仍在途的搜索请求可被 requestCancelManager 撤销,
88
  // 不再占用并发额度与超时预算(调用时机见 侧边栏数据引擎.js 的创作者搜索分支)
89
  async searchCreators(keyword, sort, limit = 50, options = {}) { return requestPublicList(`/api/creators/search?keyword=${encodeURIComponent(keyword)}&sort=${sort}&limit=${limit}`, { componentId: API.SEARCH_COMPONENT_ID, ...options }); },
90
+ async getCreatorDetails(account, options = {}) {
91
+ const current = captureCacheWrite(`${CACHE.CREATOR_DETAILS_CACHE_PREFIX}${account}/details`);
92
+ const response = await request(`/api/creators/${account}/details`, options);
93
+ if (!current()) throw new Error(t('feedback.list_changed_retry'));
94
+ return response;
95
+ },
96
  async uploadFile(file, fileType, timeout = 120000) {
97
  const formData = new FormData();
98
  formData.append("file", file);
前端页面/core/网络请求_缓存管理.js CHANGED
@@ -9,7 +9,7 @@
9
  // - 性能优化工具.js (重导出缓存函数,保持向后兼容)
10
  // ==========================================
11
 
12
- import { API, CACHE, getSidebarListCacheKey } from "./全局配置.js";
13
 
14
  // ==========================================
15
  // 📦 缓存底层实现
@@ -40,7 +40,12 @@ const cacheWriteScopes = [
40
  ...API.PUBLIC_CONDITIONAL_GET_PATHS.map(path => {
41
  const prefix = `api_${path}`;
42
  return { prefix, matches: key => key.split(/[?#]/)[0] === prefix, token: {} };
43
- })
 
 
 
 
 
44
  ];
45
 
46
  function _cacheScopeKey(key) {
@@ -54,6 +59,25 @@ export function captureCacheWrite(key) {
54
  return () => cacheClearToken === clearToken && (!scope || scope.token === token);
55
  }
56
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  function _revokeCacheWrites(keys, prefix = false) {
58
  const normalized = (prefix ? keys.map(_cacheScopeKey) : keys).filter(key => typeof key === 'string' && key.length > 0);
59
  for (const scope of cacheWriteScopes) {
@@ -698,6 +722,12 @@ const CACHE_CONFIG = {
698
 
699
  // 🚀 P1优化:精确缓存失效映射(按 HTTP 方法细分)
700
  // 根据修改的 endpoint 和 HTTP 方法精确清除相关缓存
 
 
 
 
 
 
701
  const CACHE_INVALIDATION_MAP = {
702
  // 任务相关
703
  "/api/tasks": {
@@ -715,8 +745,8 @@ const CACHE_INVALIDATION_MAP = {
715
  },
716
  // 评论相关
717
  "/api/comments": {
718
- "POST": ["api_/api/comments", "api_/api/posts", "api_/api/items"], // 创建评论,清除评论和相关内容缓存
719
- "DELETE": ["api_/api/comments", "api_/api/posts", "api_/api/items"] // 删除评论,清除评论和相关内容缓存
720
  },
721
  // 商品相关
722
  "/api/items": {
@@ -792,6 +822,10 @@ export function invalidateRelatedCache(endpoint, method = null) {
792
  patterns = ["ComfyRanking_ListCache_"];
793
  }
794
 
 
 
 
 
795
  // 🔧 修复:原实现只遍历 localStorage,漏清 memoryCache —— 而 getCache/getCacheWithMeta
796
  // 都优先读内存 Map,POST 成功后内存里的旧列表仍会命中并被直接返回(连请求都不发),
797
  // 表现为「发布/点赞已成功但界面还是旧数据」,要等内存条目 TTL 到期才自愈。
@@ -800,6 +834,8 @@ export function invalidateRelatedCache(endpoint, method = null) {
800
  // ComfyRanking_ComfyCommunity_ProfileCache_,永远匹配不到,改头像/昵称后该缓存清不掉。
801
  // 复用两级前缀删除逻辑;同次失效合并为一次存储遍历,兼容带前缀与裸键。
802
  const cleared = _removeCacheByPrefixes(patterns);
 
 
803
 
804
  if (cleared > 0) {
805
  console.log(`🗑️ 精确清除缓存: ${cleared} 个项`);
 
9
  // - 性能优化工具.js (重导出缓存函数,保持向后兼容)
10
  // ==========================================
11
 
12
+ import { API, CACHE, getSidebarListCacheKey, invalidateCachedProfiles } from "./全局配置.js";
13
 
14
  // ==========================================
15
  // 📦 缓存底层实现
 
40
  ...API.PUBLIC_CONDITIONAL_GET_PATHS.map(path => {
41
  const prefix = `api_${path}`;
42
  return { prefix, matches: key => key.split(/[?#]/)[0] === prefix, token: {} };
43
+ }),
44
+ { prefix: CACHE.CREATOR_DETAILS_CACHE_PREFIX, matches: key => {
45
+ const path = key.split(/[?#]/)[0];
46
+ return path.startsWith(CACHE.CREATOR_DETAILS_CACHE_PREFIX)
47
+ && /^[^/]+\/details$/.test(path.slice(CACHE.CREATOR_DETAILS_CACHE_PREFIX.length));
48
+ }, token: {} }
49
  ];
50
 
51
  function _cacheScopeKey(key) {
 
59
  return () => cacheClearToken === clearToken && (!scope || scope.token === token);
60
  }
61
 
62
+ let creatorsSessionCurrent = true;
63
+ export function isCreatorsSessionCacheCurrent() {
64
+ return creatorsSessionCurrent;
65
+ }
66
+ export function markCreatorsSessionCacheCurrent() {
67
+ creatorsSessionCurrent = true;
68
+ }
69
+
70
+ function invalidateCreatorsSession() {
71
+ creatorsSessionCurrent = false;
72
+ try { sessionStorage.removeItem(CACHE.LOCAL_KEYS.CREATORS_SESSION); } catch {}
73
+ }
74
+
75
+ export function invalidateFollowCaches(account, targetAccount) {
76
+ _removeCacheByPrefixes(['api_/api/creators', getSidebarListCacheKey('creators', '')]);
77
+ invalidateCachedProfiles([account, targetAccount]);
78
+ invalidateCreatorsSession();
79
+ }
80
+
81
  function _revokeCacheWrites(keys, prefix = false) {
82
  const normalized = (prefix ? keys.map(_cacheScopeKey) : keys).filter(key => typeof key === 'string' && key.length > 0);
83
  for (const scope of cacheWriteScopes) {
 
722
 
723
  // 🚀 P1优化:精确缓存失效映射(按 HTTP 方法细分)
724
  // 根据修改的 endpoint 和 HTTP 方法精确清除相关缓存
725
+ const COMMENT_CACHE_PREFIXES = [
726
+ "api_/api/comments", "api_/api/items", "api_/api/creators", "api_/api/posts", "api_/api/tasks",
727
+ ...CACHE.RESOURCE_LIST_PREFIXES, getSidebarListCacheKey('creators', ''),
728
+ CACHE.PUBLIC_LISTS.posts.prefix, CACHE.PUBLIC_LISTS.tasks.prefix, CACHE.LOCAL_KEYS.PROFILE_LIST_PREFIX
729
+ ];
730
+
731
  const CACHE_INVALIDATION_MAP = {
732
  // 任务相关
733
  "/api/tasks": {
 
745
  },
746
  // 评论相关
747
  "/api/comments": {
748
+ "POST": COMMENT_CACHE_PREFIXES,
749
+ "DELETE": COMMENT_CACHE_PREFIXES
750
  },
751
  // 商品相关
752
  "/api/items": {
 
822
  patterns = ["ComfyRanking_ListCache_"];
823
  }
824
 
825
+ if (method === "POST" && /^\/api\/items\/[^/]+\/rating$/.test(endpoint.split(/[?#]/)[0])) {
826
+ patterns = [...patterns, ...CACHE.RESOURCE_LIST_PREFIXES];
827
+ }
828
+
829
  // 🔧 修复:原实现只遍历 localStorage,漏清 memoryCache —— 而 getCache/getCacheWithMeta
830
  // 都优先读内存 Map,POST 成功后内存里的旧列表仍会命中并被直接返回(连请求都不发),
831
  // 表现为「发布/点赞已成功但界面还是旧数据」,要等内存条目 TTL 到期才自愈。
 
834
  // ComfyRanking_ComfyCommunity_ProfileCache_,永远匹配不到,改头像/昵称后该缓存清不掉。
835
  // 复用两级前缀删除逻辑;同次失效合并为一次存储遍历,兼容带前缀与裸键。
836
  const cleared = _removeCacheByPrefixes(patterns);
837
+ if ((method === "POST" || method === "DELETE")
838
+ && /^\/api\/comments(?:\/|[?#]|$)/.test(endpoint)) invalidateCreatorsSession();
839
 
840
  if (cleared > 0) {
841
  console.log(`🗑️ 精确清除缓存: ${cleared} 个项`);
前端页面/market/资源详情页面组件.js CHANGED
@@ -10,7 +10,7 @@
10
  // ==========================================
11
 
12
  import { api, proxyImages } from "../core/网络请求API.js";
13
- import { IS_WEB_MODE, getCachedProfile, setCachedProfile } from "../core/全局配置.js";
14
  import { normalizeProfileData } from "../profile/个人资料工具.js";
15
  import { openTipModal } from "../profile/个人中心_赞赏组件.js";
16
  import { getInstalledVersionStatus, setupResourceInstall } from "./资源安装引擎.js";
@@ -265,8 +265,9 @@ export function createItemDetailView(itemData, currentUser) {
265
  // 🐛 Bug修复:跳过系统页面的作者信息查询(如关于页面)
266
  if (!itemData.isSystemPage && itemData.author && scope.isCurrent()) {
267
  const originalNameEl = container.querySelector("#detail-author-name");
 
268
  api.getUserProfile(itemData.author, scope.readOptions).then(res => {
269
- if (!scope.isCurrent() || !originalNameEl?.isConnected || container.querySelector("#detail-author-name") !== originalNameEl) return;
270
  if (res?.status !== 'success') return;
271
  const profile = normalizeProfileData(res.data, itemData.author);
272
  setCachedProfile(itemData.author, profile);
 
10
  // ==========================================
11
 
12
  import { api, proxyImages } from "../core/网络请求API.js";
13
+ import { IS_WEB_MODE, getCachedProfile, setCachedProfile, captureProfileRead } from "../core/全局配置.js";
14
  import { normalizeProfileData } from "../profile/个人资料工具.js";
15
  import { openTipModal } from "../profile/个人中心_赞赏组件.js";
16
  import { getInstalledVersionStatus, setupResourceInstall } from "./资源安装引擎.js";
 
265
  // 🐛 Bug修复:跳过系统页面的作者信息查询(如关于页面)
266
  if (!itemData.isSystemPage && itemData.author && scope.isCurrent()) {
267
  const originalNameEl = container.querySelector("#detail-author-name");
268
+ const profileCurrent = captureProfileRead(itemData.author);
269
  api.getUserProfile(itemData.author, scope.readOptions).then(res => {
270
+ if (!profileCurrent() || !scope.isCurrent() || !originalNameEl?.isConnected || container.querySelector("#detail-author-name") !== originalNameEl) return;
271
  if (res?.status !== 'success') return;
272
  const profile = normalizeProfileData(res.data, itemData.author);
273
  setCachedProfile(itemData.author, profile);
前端页面/profile/个人中心_UI模板.js CHANGED
@@ -68,7 +68,7 @@ export function buildProfileHTML(userData, isMe, isSettingsView, isFollowing, fo
68
  <span>👍 ${t('profile.received_likes')}: <strong class="profile-count">${escapeHtml(userData.receivedLikes || 0)}</strong></span>
69
  <span>🔖 ${t('profile.received_favorites')}: <strong class="profile-count">${escapeHtml(userData.receivedFavorites || 0)}</strong></span>
70
  <button type="button" class="profile-count-link" id="btn-followers">👥 ${t('profile.followers_count')}: <strong class="profile-count">${userData.followers ? userData.followers.length : 0}</strong></button>
71
- <button type="button" class="profile-count-link" id="btn-following">🏃 ${t('profile.following_count')}: <strong class="profile-count">${escapeHtml(followingCount)}</strong></button>
72
  </div>
73
  </div>
74
  `;
 
68
  <span>👍 ${t('profile.received_likes')}: <strong class="profile-count">${escapeHtml(userData.receivedLikes || 0)}</strong></span>
69
  <span>🔖 ${t('profile.received_favorites')}: <strong class="profile-count">${escapeHtml(userData.receivedFavorites || 0)}</strong></span>
70
  <button type="button" class="profile-count-link" id="btn-followers">👥 ${t('profile.followers_count')}: <strong class="profile-count">${userData.followers ? userData.followers.length : 0}</strong></button>
71
+ <button type="button" class="profile-count-link" id="btn-following"${!isMe && userData.privacy?.follows === true ? ' disabled' : ''}>🏃 ${t('profile.following_count')}: <strong class="profile-count">${escapeHtml(followingCount)}</strong></button>
72
  </div>
73
  </div>
74
  `;
前端页面/profile/个人中心视图.js CHANGED
@@ -12,7 +12,7 @@ import { openRechargeModal } from "../market/资金与钱包_充值组件.js";
12
  import { openWithdrawModal } from "../market/资金与钱包_提现组件.js";
13
  import { buildProfileHTML } from "./个人中心_UI模板.js";
14
  import { openTipModal } from "./个人中心_赞赏组件.js";
15
- import { getBannerCacheKey, getCurrentAccount, IS_WEB_MODE, CACHE, UI, API, getCachedProfile, setCachedProfile, normalizeProfileImageUrl } from "../core/全局配置.js";
16
  import { logoutAndClearUserData, eventBus, EVENTS } from "../core/状态管理.js";
17
 
18
 
@@ -152,7 +152,7 @@ export function showUserProfile(initialUserData, currentUser = null, isMe = true
152
  if (currentUser && userData.followers) { isFollowing = userData.followers.includes(currentUser.account); }
153
 
154
  const privacy = userData.privacy || {};
155
- const followingCount = (!isMe && privacy.follows) ? "***" : (userData.following ? userData.following.length : 0);
156
 
157
  const tabs = [{ id: "published", label: isMe ? t('profile.my_published') : t('profile.their_published') }];
158
  if (isMe) tabs.push({ id: "acquired", label: t('profile.my_purchased') });
@@ -317,8 +317,9 @@ export function openUserProfileModal(userData) {
317
  ++profileNavigation;
318
  const view = showUserProfile(userData, userData, true);
319
  window.dispatchEvent(new CustomEvent("comfy-route-view", { detail: { view } }));
 
320
  _fetchProfileAndWallet(userData.account, { own: true, options: view._profileReadOptions }).then(freshData => {
321
- if (!view._profileScope.isCurrent()) return;
322
  mergeStoredProfile(userData.account, freshData, sessionStorages(), CACHE.LEGACY_KEYS.USER);
323
  view.updateData(freshData);
324
  if (freshData.bannerUrl) syncBannerCache(userData.account, freshData.bannerUrl, view._profileScope,
@@ -329,6 +330,7 @@ export function openUserProfileModal(userData) {
329
  export async function openOtherUserProfileModal(targetAccount, currentUser) {
330
  if (currentUser && currentUser.account === targetAccount) return openUserProfileModal(currentUser);
331
  const generation = ++profileNavigation;
 
332
  const sessionMatches = captureRequestSession();
333
  const componentId = `profile-open-${++profileSequence}`;
334
  let activeContainer = null;
@@ -347,7 +349,7 @@ export async function openOtherUserProfileModal(targetAccount, currentUser) {
347
  const unsubscribe = subscribeTaskSessionChanges(force => { if (force || !sessionMatches()) cancel(); }, {
348
  target: window, eventBus, events: EVENTS, userKey: CACHE.LEGACY_KEYS.USER, tokenKey: CACHE.LEGACY_KEYS.TOKEN
349
  });
350
- const current = () => !obsolete && generation === profileNavigation && sessionMatches()
351
  && (!activeContainer || activeContainer._profileScope.isCurrent());
352
  try {
353
  const freshData = await _fetchProfileAndWallet(targetAccount, {
 
12
  import { openWithdrawModal } from "../market/资金与钱包_提现组件.js";
13
  import { buildProfileHTML } from "./个人中心_UI模板.js";
14
  import { openTipModal } from "./个人中心_赞赏组件.js";
15
+ import { getBannerCacheKey, getCurrentAccount, IS_WEB_MODE, CACHE, UI, API, getCachedProfile, setCachedProfile, captureProfileRead, normalizeProfileImageUrl } from "../core/全局配置.js";
16
  import { logoutAndClearUserData, eventBus, EVENTS } from "../core/状态管理.js";
17
 
18
 
 
152
  if (currentUser && userData.followers) { isFollowing = userData.followers.includes(currentUser.account); }
153
 
154
  const privacy = userData.privacy || {};
155
+ const followingCount = userData.following_count;
156
 
157
  const tabs = [{ id: "published", label: isMe ? t('profile.my_published') : t('profile.their_published') }];
158
  if (isMe) tabs.push({ id: "acquired", label: t('profile.my_purchased') });
 
317
  ++profileNavigation;
318
  const view = showUserProfile(userData, userData, true);
319
  window.dispatchEvent(new CustomEvent("comfy-route-view", { detail: { view } }));
320
+ const profileCurrent = captureProfileRead(userData.account);
321
  _fetchProfileAndWallet(userData.account, { own: true, options: view._profileReadOptions }).then(freshData => {
322
+ if (!view._profileScope.isCurrent() || !profileCurrent()) return;
323
  mergeStoredProfile(userData.account, freshData, sessionStorages(), CACHE.LEGACY_KEYS.USER);
324
  view.updateData(freshData);
325
  if (freshData.bannerUrl) syncBannerCache(userData.account, freshData.bannerUrl, view._profileScope,
 
330
  export async function openOtherUserProfileModal(targetAccount, currentUser) {
331
  if (currentUser && currentUser.account === targetAccount) return openUserProfileModal(currentUser);
332
  const generation = ++profileNavigation;
333
+ const profileCurrent = captureProfileRead(targetAccount);
334
  const sessionMatches = captureRequestSession();
335
  const componentId = `profile-open-${++profileSequence}`;
336
  let activeContainer = null;
 
349
  const unsubscribe = subscribeTaskSessionChanges(force => { if (force || !sessionMatches()) cancel(); }, {
350
  target: window, eventBus, events: EVENTS, userKey: CACHE.LEGACY_KEYS.USER, tokenKey: CACHE.LEGACY_KEYS.TOKEN
351
  });
352
+ const current = () => !obsolete && generation === profileNavigation && sessionMatches() && profileCurrent()
353
  && (!activeContainer || activeContainer._profileScope.isCurrent());
354
  try {
355
  const freshData = await _fetchProfileAndWallet(targetAccount, {
前端页面/profile/个人资料工具.js CHANGED
@@ -20,6 +20,9 @@ export function normalizeProfileData(value, account, own = false) {
20
  }
21
  result.privacy = Object.fromEntries(['follows', 'followers', 'likes', 'favorites', 'downloads']
22
  .map(key => [key, value.privacy?.[key] === true]));
 
 
 
23
  result.is_admin = value.is_admin === true || (own && value.isAdmin === true);
24
  if (own) Object.assign(result, normalizeWalletData({ ...value, status: 'success' }));
25
  return result;
 
20
  }
21
  result.privacy = Object.fromEntries(['follows', 'followers', 'likes', 'favorites', 'downloads']
22
  .map(key => [key, value.privacy?.[key] === true]));
23
+ result.following_count = !own && Number.isSafeInteger(value.following_count) && value.following_count >= 0
24
+ ? value.following_count : result.following.length;
25
+ if (!own && result.privacy.follows) result.following = [];
26
  result.is_admin = value.is_admin === true || (own && value.isAdmin === true);
27
  if (own) Object.assign(result, normalizeWalletData({ ...value, status: 'success' }));
28
  return result;