@@ -168,24 +170,11 @@ export function ChatView({
我的空间
)}
-
-
+ {onOpenFeedback && (
+
+ )}
@@ -292,6 +281,9 @@ export function ChatView({
variant="full"
user={user}
messages={messages}
+ historyLoadingMore={messageHistoryLoadingMore}
+ historyHasMore={messageHistoryHasMore}
+ historyTotal={messageHistoryTotal}
chatState={chatState}
pendingTool={pendingTool}
session={session}
@@ -301,6 +293,7 @@ export function ChatView({
void submit(text, undefined, imageUrls, previewImageUrls)
}
onUploadImage={uploadChatImage}
+ onLoadOlderMessages={loadOlderMessages}
onStop={stop}
onApproveTool={approveTool}
onPageSaved={(result) => {
diff --git a/src/components/FeedbackBoardView.tsx b/src/components/FeedbackBoardView.tsx
new file mode 100644
index 0000000..6bd1c7b
--- /dev/null
+++ b/src/components/FeedbackBoardView.tsx
@@ -0,0 +1,179 @@
+import { useCallback, useEffect, useState } from 'react';
+import { useNavigate, useSearchParams } from 'react-router-dom';
+import { listFeedbackBoard } from '../api/client';
+import type { FeedbackSubmission, PortalUser } from '../types';
+import {
+ FEEDBACK_STATUS_LABELS,
+ FEEDBACK_TYPE_LABELS,
+ formatFeedbackTime,
+} from '../utils/feedbackLabels';
+import { FeedbackPageHeader } from './FeedbackPageHeader';
+
+const PAGE_SIZE = 10;
+
+function FeedbackBoardItem({
+ item,
+ currentUserId,
+ onOpen,
+}: {
+ item: FeedbackSubmission;
+ currentUserId?: string;
+ onOpen: (id: string) => void;
+}) {
+ const isMine = item.userId === currentUserId;
+ return (
+
+
+
+ );
+}
+
+export function FeedbackBoardView({
+ user,
+ onLogout,
+}: {
+ user?: PortalUser | null;
+ onLogout?: () => void;
+}) {
+ const navigate = useNavigate();
+ const [searchParams, setSearchParams] = useSearchParams();
+ const page = Math.max(Number(searchParams.get('page')) || 1, 1);
+
+ const [items, setItems] = useState
([]);
+ const [totalPages, setTotalPages] = useState(0);
+ const [total, setTotal] = useState(0);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ const loadPage = useCallback(async (targetPage: number) => {
+ setLoading(true);
+ setError(null);
+ try {
+ const result = await listFeedbackBoard(targetPage, PAGE_SIZE);
+ setItems(result.items);
+ setTotalPages(result.totalPages);
+ setTotal(result.total);
+ if (result.totalPages > 0 && targetPage > result.totalPages) {
+ setSearchParams({ page: String(result.totalPages) }, { replace: true });
+ }
+ } catch (err) {
+ setError(err instanceof Error ? err.message : '反馈列表加载失败');
+ setItems([]);
+ } finally {
+ setLoading(false);
+ }
+ }, [setSearchParams]);
+
+ useEffect(() => {
+ void loadPage(page);
+ }, [loadPage, page]);
+
+ const goToPage = (nextPage: number) => {
+ if (nextPage < 1 || (totalPages > 0 && nextPage > totalPages) || nextPage === page) return;
+ setSearchParams({ page: String(nextPage) });
+ };
+
+ return (
+
+
navigate('/feedback')}
+ onLogout={onLogout}
+ />
+
+
+
+
+
+
Bug List
+
全部用户反馈
+
+ 共 {total} 条 · 每页 {PAGE_SIZE} 条
+
+
+
+
+
+ {!loading && total > 0 ? (
+ 第 {page} / {totalPages || 1} 页
+ ) : null}
+
+
+ {loading ?
加载中…
: null}
+ {error ?
{error}
: null}
+
+ {!loading && !error && items.length === 0 ? (
+
还没有任何用户反馈。
+ ) : null}
+
+ {!loading && items.length > 0 ? (
+
+ {items.map((item) => (
+ navigate(`/feedback/${id}`)}
+ />
+ ))}
+
+ ) : null}
+
+
+ {totalPages > 1 ? (
+
+ ) : null}
+
+
+
+ );
+}
diff --git a/src/components/FeedbackDetailView.tsx b/src/components/FeedbackDetailView.tsx
new file mode 100644
index 0000000..83a56da
--- /dev/null
+++ b/src/components/FeedbackDetailView.tsx
@@ -0,0 +1,137 @@
+import { useEffect, useState } from 'react';
+import { useNavigate, useParams } from 'react-router-dom';
+import { getFeedbackDetail } from '../api/client';
+import type { FeedbackSubmission } from '../types';
+import {
+ FEEDBACK_STATUS_LABELS,
+ FEEDBACK_TYPE_LABELS,
+ feedbackImageDataUrl,
+ formatFeedbackTime,
+} from '../utils/feedbackLabels';
+import { FeedbackPageHeader } from './FeedbackPageHeader';
+
+export function FeedbackDetailView({
+ onLogout,
+}: {
+ onLogout?: () => void;
+}) {
+ const navigate = useNavigate();
+ const { feedbackId } = useParams<{ feedbackId: string }>();
+ const [item, setItem] = useState(null);
+ const [isMine, setIsMine] = useState(false);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ if (!feedbackId) {
+ setError('反馈不存在');
+ setLoading(false);
+ return;
+ }
+ let cancelled = false;
+ setLoading(true);
+ setError(null);
+ void getFeedbackDetail(feedbackId)
+ .then((result) => {
+ if (cancelled) return;
+ setItem(result.item);
+ setIsMine(result.isMine);
+ })
+ .catch((err) => {
+ if (cancelled) return;
+ setError(err instanceof Error ? err.message : '反馈详情加载失败');
+ setItem(null);
+ })
+ .finally(() => {
+ if (!cancelled) setLoading(false);
+ });
+ return () => {
+ cancelled = true;
+ };
+ }, [feedbackId]);
+
+ return (
+
+
navigate('/feedback/list')}
+ onLogout={onLogout}
+ />
+
+
+
+ {loading ? 加载中…
: null}
+ {error ? {error}
: null}
+
+ {!loading && !error && item ? (
+ <>
+
+
+
+ {FEEDBACK_TYPE_LABELS[item.type]}
+
+
+ {FEEDBACK_STATUS_LABELS[item.status]}
+
+ {isMine ? 我的 : null}
+
+
{item.title}
+
+ 提交者:{item.submitterDisplayName ?? '用户'}
+ ·
+ {formatFeedbackTime(item.createdAt)}
+ ·
+ 编号 {item.id.slice(0, 8)}
+
+
+
+
+
详细描述
+
{item.description || '(无描述)'}
+
+
+ {isMine && item.contact ? (
+
+
联系方式
+
{item.contact}
+
+ ) : null}
+
+ {item.context?.pagePath ? (
+
+
来源页面
+
+ {item.context.pagePath}
+
+
+ ) : null}
+
+ {(item.images?.length ?? 0) > 0 ? (
+
+
截图
+
+ {item.images?.map((image, index) => (
+
+
+
+ ))}
+
+
+ ) : null}
+ >
+ ) : null}
+
+
+
+ );
+}
diff --git a/src/components/FeedbackPageHeader.tsx b/src/components/FeedbackPageHeader.tsx
new file mode 100644
index 0000000..20cf496
--- /dev/null
+++ b/src/components/FeedbackPageHeader.tsx
@@ -0,0 +1,30 @@
+import { TKMindAvatar } from './TKMindAvatar';
+
+export function FeedbackPageHeader({
+ title,
+ onBack,
+ onLogout,
+}: {
+ title: string;
+ onBack: () => void;
+ onLogout?: () => void;
+}) {
+ return (
+
+
+
+
+ {title}
+
+ {onLogout ? (
+
+ ) : (
+
+ )}
+
+ );
+}
diff --git a/src/components/FeedbackSubmitView.tsx b/src/components/FeedbackSubmitView.tsx
new file mode 100644
index 0000000..2b5cb7c
--- /dev/null
+++ b/src/components/FeedbackSubmitView.tsx
@@ -0,0 +1,430 @@
+import { useRef, useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { submitFeedback } from '../api/client';
+import type { FeedbackImageInput, FeedbackSubmissionType, PortalUser } from '../types';
+import {
+ CHAT_IMAGE_UPLOAD_MAX_INPUT_BYTES,
+ CHAT_IMAGE_UPLOAD_MAX_OUTPUT_BYTES,
+ CHAT_IMAGE_MAX_SIDE,
+ compressImageForUpload,
+} from '../utils/imageUpload';
+import { FeedbackPageHeader } from './FeedbackPageHeader';
+import { VoiceInputButton } from './VoiceInputButton';
+
+const TYPE_OPTIONS: Array<{ value: FeedbackSubmissionType; label: string; hint: string }> = [
+ { value: 'bug', label: 'Bug 报告', hint: '功能异常、报错、无法完成操作' },
+ { value: 'feature', label: '功能建议', hint: '新能力、体验优化、流程改进' },
+ { value: 'other', label: '其他反馈', hint: '账号、计费、文档等问题' },
+];
+
+const MAX_IMAGES = 5;
+
+type PendingImage = {
+ id: string;
+ previewUrl: string;
+ file: File;
+};
+
+function fileToBase64(file: File): Promise {
+ return new Promise((resolve, reject) => {
+ const reader = new FileReader();
+ reader.onload = () => {
+ const result = typeof reader.result === 'string' ? reader.result : '';
+ const comma = result.indexOf(',');
+ resolve(comma >= 0 ? result.slice(comma + 1) : result);
+ };
+ reader.onerror = () => reject(new Error('图片读取失败'));
+ reader.readAsDataURL(file);
+ });
+}
+
+function buildFeedbackContext(sessionId?: string | null) {
+ return {
+ pageUrl: window.location.href,
+ pagePath: `${window.location.pathname}${window.location.search}`,
+ userAgent: navigator.userAgent,
+ viewport: `${window.innerWidth}x${window.innerHeight}`,
+ appVersion: import.meta.env.VITE_APP_VERSION ?? undefined,
+ sessionId: sessionId ?? undefined,
+ };
+}
+
+export function FeedbackSubmitView({
+ user,
+ sessionId,
+ onLogout,
+}: {
+ user?: PortalUser | null;
+ sessionId?: string | null;
+ onLogout?: () => void;
+}) {
+ const navigate = useNavigate();
+ const fileInputRef = useRef(null);
+ const descriptionRef = useRef(null);
+ const descriptionTextRef = useRef('');
+ const voiceBaseRef = useRef('');
+ const suppressVoiceUpdateRef = useRef(false);
+
+ const [type, setType] = useState('bug');
+ const [title, setTitle] = useState('');
+ const [description, setDescription] = useState('');
+ const [contact, setContact] = useState(user?.email ?? '');
+ const [images, setImages] = useState([]);
+ const [imageError, setImageError] = useState(null);
+ const [voiceNotice, setVoiceNotice] = useState(null);
+ const [voiceRecording, setVoiceRecording] = useState(false);
+ const [voiceStopSignal, setVoiceStopSignal] = useState(0);
+ const [error, setError] = useState(null);
+ const [submitting, setSubmitting] = useState(false);
+ const [submittedId, setSubmittedId] = useState(null);
+ const [uploadingImages, setUploadingImages] = useState(false);
+
+ descriptionTextRef.current = description;
+
+ const selectedType = TYPE_OPTIONS.find((item) => item.value === type) ?? TYPE_OPTIONS[0];
+ const voiceDisabled = submitting || uploadingImages;
+
+ const mergeVoiceText = (spoken: string) => {
+ const base = voiceBaseRef.current.trimEnd();
+ const chunk = spoken.trim();
+ if (!chunk) return base;
+ return base ? `${base} ${chunk}` : chunk;
+ };
+
+ const handleVoiceStart = () => {
+ suppressVoiceUpdateRef.current = false;
+ voiceBaseRef.current = descriptionTextRef.current;
+ setVoiceNotice(null);
+ };
+
+ const handleVoiceLiveTranscript = (text: string) => {
+ if (suppressVoiceUpdateRef.current) return;
+ setDescription(mergeVoiceText(text));
+ };
+
+ const handleVoiceTranscript = (text: string) => {
+ if (suppressVoiceUpdateRef.current) return;
+ setDescription(mergeVoiceText(text));
+ setVoiceNotice('已识别,可编辑后提交');
+ };
+
+ const handleVoiceComplete = () => {
+ setVoiceNotice('已识别,可编辑后提交');
+ };
+
+ const stopVoiceInput = () => {
+ suppressVoiceUpdateRef.current = true;
+ if (voiceRecording) {
+ setVoiceStopSignal((value) => value + 1);
+ }
+ setVoiceRecording(false);
+ };
+
+ const handlePickImages = async (files: FileList | null) => {
+ if (!files?.length) return;
+ setImageError(null);
+
+ const remaining = MAX_IMAGES - images.length;
+ if (remaining <= 0) {
+ setImageError(`最多上传 ${MAX_IMAGES} 张图片`);
+ return;
+ }
+
+ const selected = Array.from(files).slice(0, remaining);
+ setUploadingImages(true);
+ try {
+ const nextItems: PendingImage[] = [];
+ for (const file of selected) {
+ if (!file.type.startsWith('image/')) {
+ throw new Error('只支持图片文件');
+ }
+ const compressed = await compressImageForUpload(file, {
+ maxInputBytes: CHAT_IMAGE_UPLOAD_MAX_INPUT_BYTES,
+ maxOutputBytes: CHAT_IMAGE_UPLOAD_MAX_OUTPUT_BYTES,
+ maxDimension: CHAT_IMAGE_MAX_SIDE,
+ });
+ nextItems.push({
+ id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
+ previewUrl: URL.createObjectURL(compressed),
+ file: compressed,
+ });
+ }
+ setImages((prev) => [...prev, ...nextItems]);
+ } catch (err) {
+ setImageError(err instanceof Error ? err.message : '图片处理失败');
+ } finally {
+ setUploadingImages(false);
+ if (fileInputRef.current) fileInputRef.current.value = '';
+ }
+ };
+
+ const removeImage = (id: string) => {
+ setImages((prev) => {
+ const target = prev.find((item) => item.id === id);
+ if (target) URL.revokeObjectURL(target.previewUrl);
+ return prev.filter((item) => item.id !== id);
+ });
+ };
+
+ const resetForm = () => {
+ stopVoiceInput();
+ setSubmittedId(null);
+ setTitle('');
+ setDescription('');
+ voiceBaseRef.current = '';
+ suppressVoiceUpdateRef.current = false;
+ setVoiceNotice(null);
+ setImages([]);
+ setType('bug');
+ setError(null);
+ };
+
+ const handleSubmit = async (event: React.FormEvent) => {
+ event.preventDefault();
+ setError(null);
+ stopVoiceInput();
+
+ const trimmedTitle = title.trim();
+ const trimmedDescription = description.trim();
+ if (!trimmedTitle) {
+ suppressVoiceUpdateRef.current = false;
+ setError('请填写标题');
+ return;
+ }
+ if (!trimmedDescription) {
+ suppressVoiceUpdateRef.current = false;
+ setError('请填写详细描述,或使用语音口述');
+ descriptionRef.current?.focus();
+ return;
+ }
+
+ setSubmitting(true);
+ try {
+ const imagePayload: FeedbackImageInput[] = [];
+ for (const item of images) {
+ imagePayload.push({
+ filename: item.file.name,
+ mimeType: item.file.type || 'image/jpeg',
+ dataBase64: await fileToBase64(item.file),
+ });
+ }
+
+ const result = await submitFeedback({
+ type,
+ title: trimmedTitle,
+ description: trimmedDescription,
+ contact: contact.trim() || undefined,
+ images: imagePayload,
+ context: buildFeedbackContext(sessionId),
+ });
+ setSubmittedId(result.id);
+ suppressVoiceUpdateRef.current = false;
+ setVoiceNotice(null);
+ } catch (err) {
+ suppressVoiceUpdateRef.current = false;
+ setError(err instanceof Error ? err.message : '提交失败,请稍后重试');
+ } finally {
+ setSubmitting(false);
+ }
+ };
+
+ return (
+
+
navigate(-1)}
+ onLogout={onLogout}
+ />
+
+
+
+ {submittedId ? (
+
+ 提交成功
+ 感谢你的反馈
+
+ 我们已收到你的{selectedType.label},编号 {submittedId.slice(0, 8)}。
+ 你可以在「用户反馈」中查看进度。
+
+
+
+
+
+
+
+ ) : (
+
+ )}
+
+
+
+ );
+}
diff --git a/src/components/MindSpaceView.tsx b/src/components/MindSpaceView.tsx
index a47e354..04fb5dc 100644
--- a/src/components/MindSpaceView.tsx
+++ b/src/components/MindSpaceView.tsx
@@ -24,7 +24,7 @@ import {
uploadMindSpaceAsset,
ApiError,
} from '../api/client';
-import { compressImageForUpload } from '../utils/imageUpload';
+import { MINDSPACE_IMAGE_UPLOAD_MAX_BYTES } from '../utils/imageUpload';
import type {
MindSpace,
MindSpaceAsset,
@@ -63,7 +63,7 @@ import { NotificationCenter } from './NotificationCenter';
const CATEGORY_DESCRIPTIONS: Record = {
oa: '上传文档、表格和资料,让 AI 生成报告与页面',
- private: '保存敏感资料,发布前必须完成风险检查与脱敏',
+ private: '已停用',
public: '管理可公开使用的页面、图片和静态资源',
draft: '继续编辑尚未发布或等待安全检查的页面',
archive: '保存不再活跃但仍需要保留的内容',
@@ -71,13 +71,20 @@ const CATEGORY_DESCRIPTIONS: Record = {
const CATEGORY_ACTIONS: Partial> = {
oa: '进入工作区',
- private: '进入私人区',
public: '查看发布',
draft: '继续创作',
};
const AGENT_JOBS_PAGE_SIZE = 10;
const RECENT_PAGES_PAGE_SIZE = 6;
+
+function sortPagesByCreatedAt(pages: MindSpacePage[]) {
+ return [...pages].sort((left, right) => {
+ const createdDiff = (right.createdAt ?? 0) - (left.createdAt ?? 0);
+ if (createdDiff !== 0) return createdDiff;
+ return (right.updatedAt ?? 0) - (left.updatedAt ?? 0);
+ });
+}
const IMAGE_PAGE_SIZE = 10;
const DEFAULT_MAX_UPLOAD_FILE_BYTES = 5 * 1024 * 1024;
const UPLOAD_FILE_EXTENSIONS = [
@@ -282,15 +289,29 @@ function fileExtension(filename: string) {
return dotIndex >= 0 ? filename.slice(dotIndex).toLowerCase() : '';
}
+function isUploadImageFile(file: File) {
+ const extension = fileExtension(file.name);
+ return (
+ file.type.startsWith('image/') ||
+ extension === '.png' ||
+ extension === '.jpg' ||
+ extension === '.jpeg' ||
+ extension === '.webp'
+ );
+}
+
function validateSelectedUploadFile(file: File, maxBytes: number) {
if (file.size <= 0) return '文件不能为空,请重新选择。';
- if (file.size > maxBytes) {
- return `文件不能超过 ${formatBytes(maxBytes)},当前为 ${formatBytes(file.size)}。`;
- }
const extension = fileExtension(file.name);
if (!UPLOAD_FILE_EXTENSIONS.includes(extension as (typeof UPLOAD_FILE_EXTENSIONS)[number])) {
return `暂不支持 ${extension || '无扩展名'} 文件,请选择支持的资料格式。`;
}
+ const effectiveMaxBytes = isUploadImageFile(file)
+ ? Math.min(maxBytes, MINDSPACE_IMAGE_UPLOAD_MAX_BYTES)
+ : maxBytes;
+ if (file.size > effectiveMaxBytes) {
+ return `文件不能超过 ${formatBytes(effectiveMaxBytes)},当前为 ${formatBytes(file.size)}。`;
+ }
return null;
}
@@ -450,6 +471,7 @@ export function MindSpaceView({
initialCategoryCode,
onBack,
onLogout,
+ onOpenFeedback,
routeSync,
}: {
user: PortalUser;
@@ -458,6 +480,7 @@ export function MindSpaceView({
initialCategoryCode?: MindSpaceSaveCategory | null;
onBack: () => void;
onLogout: () => void;
+ onOpenFeedback?: () => void;
routeSync?: MindSpaceRouteSync;
}) {
const [space, setSpace] = useState(null);
@@ -503,7 +526,6 @@ export function MindSpaceView({
const [agentJobsPage, setAgentJobsPage] = useState(0);
const [agentJobsLoading, setAgentJobsLoading] = useState(false);
const [allPagesOpen, setAllPagesOpen] = useState(false);
- const [recentPagesPage, setRecentPagesPage] = useState(0);
const [exportingPageId, setExportingPageId] = useState(null);
const [sharePayload, setSharePayload] = useState(null);
const [previewPageTarget, setPreviewPageTarget] = useState(null);
@@ -1033,14 +1055,9 @@ export function MindSpaceView({
setError(null);
setUploadError(null);
try {
- const fileToUpload = selectedFile.type.startsWith('image/')
- ? await compressImageForUpload(selectedFile, {
- maxInputBytes: maxUploadFileBytes,
- maxOutputBytes: maxUploadFileBytes,
- maxDimension: 1600,
- })
- : selectedFile;
- await uploadMindSpaceAsset(selectedCategory.id, fileToUpload);
+ await uploadMindSpaceAsset(selectedCategory.id, selectedFile, {
+ maxImageBytes: MINDSPACE_IMAGE_UPLOAD_MAX_BYTES,
+ });
setUploadOpen(false);
setSelectedFile(null);
await load();
@@ -1255,7 +1272,8 @@ export function MindSpaceView({
const visibleImageAssets =
assetFilter === 'all' || assetFilter === 'images' ? imageAssets : [];
- const imagePaginationEnabled = visibleImageAssets.length > IMAGE_PAGE_SIZE;
+ const imagePaginationEnabled =
+ selectedCategory?.code !== 'public' && visibleImageAssets.length > IMAGE_PAGE_SIZE;
const imagePageCount = Math.max(
1,
Math.ceil(visibleImageAssets.length / IMAGE_PAGE_SIZE),
@@ -1267,17 +1285,20 @@ export function MindSpaceView({
safeImagePage * IMAGE_PAGE_SIZE + IMAGE_PAGE_SIZE,
)
: visibleImageAssets;
- const recentPagesTotal = Math.max(1, Math.ceil(pages.length / RECENT_PAGES_PAGE_SIZE));
- const safeRecentPagesPage = Math.min(recentPagesPage, recentPagesTotal - 1);
- const pagedRecentPages = pages.slice(
- safeRecentPagesPage * RECENT_PAGES_PAGE_SIZE,
- safeRecentPagesPage * RECENT_PAGES_PAGE_SIZE + RECENT_PAGES_PAGE_SIZE,
- );
- useEffect(() => {
- const lastRecentPage = Math.max(0, recentPagesTotal - 1);
- setRecentPagesPage((current) => Math.min(current, lastRecentPage));
- }, [recentPagesTotal, pages.length]);
+ const sortedPages = useMemo(() => sortPagesByCreatedAt(pages), [pages]);
+ const recentPagesPreview = sortedPages.slice(0, RECENT_PAGES_PAGE_SIZE);
+ const imageAssetsByDate = useMemo(() => {
+ const groups = new Map();
+ for (const asset of visibleImageAssets) {
+ const match = asset.filename.match(/images\/(\d{4}-\d{2}-\d{2})\//);
+ const dateKey = match?.[1] ?? '其他';
+ const bucket = groups.get(dateKey) ?? [];
+ bucket.push(asset);
+ groups.set(dateKey, bucket);
+ }
+ return [...groups.entries()].sort((left, right) => right[0].localeCompare(left[0]));
+ }, [visibleImageAssets]);
const visibleCardAssets =
assetFilter === 'all'
? [...fileAssets, ...workAssets]
@@ -1676,6 +1697,11 @@ export function MindSpaceView({
) : (
)}
+ {!previewMode && onOpenFeedback && (
+
+ )}
{user.displayName}
{pages.length > 0 ? (
- {pages.map((page) => (
+ {sortedPages.map((page) => (
{selectedCategory.name}
- {['oa', 'private', 'public'].includes(selectedCategory.code) && (
+ {['oa', 'public'].includes(selectedCategory.code) && (
- {pages.map((page) => (
+ {sortedPages.map((page) => (
{(() => {
- const categoryPages = pages.filter(
+ const categoryPages = sortedPages.filter(
(p) => p.categoryCode === selectedCategory.code,
);
return categoryPages.length > 0 ? (
@@ -2276,6 +2296,49 @@ export function MindSpaceView({
{visibleImageAssets.length} 张
)}
+ {selectedCategory?.code === 'public' ? (
+ imageAssetsByDate.map(([dateKey, datedAssets]) => (
+
@@ -2318,6 +2381,7 @@ export function MindSpaceView({
))}
+ )}
{imagePaginationEnabled && (