visuddhinanda 1 ماه پیش
والد
کامیت
b16c3b325a

+ 3 - 3
dashboard-v6/src/api/workspace.ts

@@ -24,7 +24,7 @@ export type ModuleStats = {
 export type ModuleItem = ModuleConfig & { stats: string };
 
 export type RecentItem = {
-  id: number;
+  id: string;
   title: string;
   subtitle: string;
   time: string;
@@ -90,8 +90,8 @@ export async function fetchModules(): Promise<ModuleItem[]> {
 
 export async function fetchRecentItems(userId: string): Promise<RecentItem[]> {
   const res = await getRecentByUser({ userId, pageSize: 10 });
-  return res.data.rows.map((item, id) => ({
-    id,
+  return res.data.rows.map((item) => ({
+    id: item.article_id,
     title: item.title,
     subtitle: "Tipitaka · 律藏",
     time: item.updated_at,

+ 14 - 1
dashboard-v6/src/components/editor/Editor.tsx

@@ -14,6 +14,8 @@ import {
 import ChatPanel from "./panels/ChatPanel";
 import SuggestionPanel from "./panels/SuggestionPanel";
 import GrammarBookPanel from "./panels/GrammarBookPanel";
+import type { ArticleType } from "../../api/article";
+import type { IChannel } from "../../api/channel";
 
 // ─────────────────────────────────────────────
 // Props
@@ -44,9 +46,11 @@ export interface EditorProps {
 
   // ── 业务参数(透传给右边栏面板)──
   articleId?: string;
+  articleType?: ArticleType;
   anthologyId?: string;
   /** 多个 channelId 用 "_" 拼接的原始字符串,Editor 内部负责解析 */
   channelId?: string | null;
+  onChannelSelect?: (selected: IChannel[]) => void;
 }
 
 // ─────────────────────────────────────────────
@@ -58,8 +62,10 @@ export default function Editor({
   sidebar,
   children,
   articleId,
+  articleType,
   anthologyId,
   channelId,
+  onChannelSelect,
 }: EditorProps) {
   const channels = channelId ? channelId.split("_") : undefined;
 
@@ -82,7 +88,14 @@ export default function Editor({
       key: "channel",
       icon: <ChannelIcon />,
       label: "版本",
-      content: <ChannelPanel articleId={articleId} channels={channels} />,
+      content: (
+        <ChannelPanel
+          articleId={articleId}
+          type={articleType}
+          channels={channels}
+          onSelect={onChannelSelect}
+        />
+      ),
     },
     {
       key: "discussion",

+ 11 - 2
dashboard-v6/src/components/editor/panels/ChannelPanel.tsx

@@ -1,9 +1,12 @@
 import ChannelMy from "../../channel/ChannelMy";
 import type { IChannel } from "../../../api/channel";
+import type { ArticleType } from "../../../api/article";
 
 interface ChannelPanelProps {
   articleId?: string;
   channels?: string[];
+  type?: ArticleType;
+  onSelect?: (selected: IChannel[]) => void;
 }
 
 /**
@@ -11,14 +14,20 @@ interface ChannelPanelProps {
  * 封装成独立组件,articleId / channels 变化时正常 re-render,
  * 不受 rightTabs 数组重建影响。
  */
-export default function ChannelPanel({ articleId, channels }: ChannelPanelProps) {
+export default function ChannelPanel({
+  articleId,
+  channels,
+  type,
+  onSelect,
+}: ChannelPanelProps) {
   const handleSelect = (selected: IChannel[]) => {
     console.log("channel selected:", selected);
+    onSelect?.(selected);
   };
 
   return (
     <ChannelMy
-      type="article"
+      type={type}
       articleId={articleId}
       selectedKeys={channels}
       onSelect={handleSelect}

+ 10 - 2
dashboard-v6/src/components/workspace/home/RecentList.tsx

@@ -2,12 +2,14 @@ import type { CSSProperties } from "react";
 import { theme } from "antd";
 import type { RecentItem as RecentItemType } from "../../../api/workspace";
 import RecentItem from "./RecentItem";
+import type { ArticleType } from "../../../api/article";
 
 type RecentListProps = {
   items: RecentItemType[];
+  onClick?: (type: ArticleType, id: string) => void;
 };
 
-export default function RecentList({ items }: RecentListProps) {
+export default function RecentList({ items, onClick }: RecentListProps) {
   const { token } = theme.useToken();
 
   const styles: Record<string, CSSProperties> = {
@@ -22,7 +24,13 @@ export default function RecentList({ items }: RecentListProps) {
   return (
     <div style={styles.list}>
       {items.map((item) => (
-        <RecentItem key={item.id} {...item} />
+        <RecentItem
+          key={item.id}
+          {...item}
+          onClick={() => {
+            onClick?.(item.type, item.id);
+          }}
+        />
       ))}
     </div>
   );

+ 32 - 0
dashboard-v6/src/features/editor/Chapter.tsx

@@ -2,13 +2,18 @@
 // Props
 // ─────────────────────────────────────────────
 
+import { useLocation } from "react-router";
 import type { ArticleMode, ArticleType } from "../../api/article";
 import TypePali, {
   type ISearchParams,
 } from "../../components/article/TypePali";
 import Editor from "../../components/editor";
 import PaliTextToc from "../../components/tipitaka/PaliTextToc";
+import { useSaveRecent } from "../../hooks/useSaveRecent";
 import type { TTarget } from "../../types";
+import { useEffect } from "react";
+import { useAppSelector } from "../../hooks";
+import { currentUser } from "../../reducers/current-user";
 
 export interface ChapterEditorProps {
   chapterId?: string;
@@ -39,6 +44,20 @@ export default function ChapterEditor({
   const [book, para] = chapterId
     ? chapterId.split("-").map((item) => parseInt(item))
     : [undefined, undefined];
+  const currUser = useAppSelector(currentUser);
+  const { save } = useSaveRecent();
+  const { search } = useLocation();
+
+  useEffect(() => {
+    if (!currUser?.id || !chapterId) return;
+
+    save({
+      type: "chapter",
+      article_id: chapterId,
+      param: search || undefined,
+    });
+  }, [currUser?.id, chapterId, search, save]);
+
   return (
     <Editor
       sidebarTitle="recent scan"
@@ -54,7 +73,20 @@ export default function ChapterEditor({
         />
       }
       articleId={chapterId}
+      articleType="chapter"
       channelId={channelId}
+      onChannelSelect={(selected) => {
+        if (chapterId) {
+          const channelParams = [
+            {
+              key: "channel",
+              value: selected.map((item) => item.id).join("_"),
+            },
+          ];
+          console.debug("onChannelSelect", channelParams);
+          onArticleChange?.("chapter", chapterId, "_self", channelParams);
+        }
+      }}
     >
       {({ expandButton }) => (
         <TypePali

+ 10 - 1
dashboard-v6/src/pages/workspace/home.tsx

@@ -8,11 +8,13 @@ import { fetchModules, fetchRecentItems } from "../../api/workspace";
 import type { ModuleItem, RecentItem } from "../../api/workspace";
 import { useAppSelector } from "../../hooks";
 import { currentUser } from "../../reducers/current-user";
+import { useNavigate } from "react-router";
 
 export default function WorkspaceHome() {
   const [modules, setModules] = useState<ModuleItem[]>([]);
   const [recentItems, setRecentItems] = useState<RecentItem[]>([]);
   const user = useAppSelector(currentUser);
+  const navigate = useNavigate();
 
   useEffect(() => {
     if (!user) {
@@ -33,7 +35,14 @@ export default function WorkspaceHome() {
         </SectionPanel>
 
         <SectionPanel title="最近访问">
-          <RecentList items={recentItems} />
+          <RecentList
+            items={recentItems}
+            onClick={(type, id) => {
+              if (type === "chapter") {
+                navigate(`/workspace/tipitaka/${type}/${id}`);
+              }
+            }}
+          />
         </SectionPanel>
       </div>
     </div>

+ 14 - 6
dashboard-v6/src/pages/workspace/tipitaka/chapter.tsx

@@ -1,4 +1,9 @@
-import { useNavigate, useParams, useSearchParams } from "react-router";
+import {
+  useLocation,
+  useNavigate,
+  useParams,
+  useSearchParams,
+} from "react-router";
 import type { ArticleMode } from "../../../api/article";
 import ChapterEditor from "../../../features/editor/Chapter";
 
@@ -6,7 +11,7 @@ const Widget = () => {
   const { id } = useParams();
   const [searchParams] = useSearchParams();
   const navigate = useNavigate();
-
+  const { search } = useLocation();
   const mode = searchParams.get("mode") ?? "read";
   const channelId = searchParams.get("channel");
 
@@ -16,17 +21,20 @@ const Widget = () => {
       mode={mode as ArticleMode}
       channelId={channelId}
       onSelect={(id) => {
-        navigate(`/workspace/tipitaka/chapter/${id}`);
+        navigate(`/workspace/tipitaka/chapter/${id}${search}`);
       }}
-      onArticleChange={(type, id, target) => {
+      onArticleChange={(type, id, target, param) => {
         const url = `workspace/tipitaka/${type}/${id}`;
+        const urlSearch = param
+          ? "?" + param?.map((item) => `${item.key}=${item.value}`).join("&")
+          : search;
         if (target === "_blank") {
           window.open(
-            `${window.location.origin}${import.meta.env.BASE_URL}${url}`,
+            `${window.location.origin}${import.meta.env.BASE_URL}${url}${urlSearch}`,
             "_blank"
           );
         } else {
-          navigate(`/${url}`);
+          navigate(`/${url}${urlSearch}`);
         }
       }}
     />