Explorar o código

完成channel选择列表

visuddhinanda %!s(int64=3) %!d(string=hai) anos
pai
achega
267adef88f

+ 25 - 2
dashboard/src/components/article/RightPanel.tsx

@@ -1,13 +1,26 @@
 import { Affix } from "antd";
 import { useEffect, useState } from "react";
+import { IChannel } from "../channel/Channel";
+import ChannelPickerTable from "../channel/ChannelPickerTable";
 
 import DictComponent from "../dict/DictComponent";
+import { ArticleType } from "./Article";
 
 export type TPanelName = "dict" | "channel" | "close";
 interface IWidget {
   curr?: TPanelName;
+  type: ArticleType;
+  articleId: string;
+  selectedChannelKeys?: string[];
+  onChannelSelect?: Function;
 }
-const Widget = ({ curr = "close" }: IWidget) => {
+const Widget = ({
+  curr = "close",
+  type,
+  articleId,
+  onChannelSelect,
+  selectedChannelKeys,
+}: IWidget) => {
   const [dict, setDict] = useState("none");
   const [channel, setChannel] = useState("none");
 
@@ -48,7 +61,17 @@ const Widget = ({ curr = "close" }: IWidget) => {
             display: channel,
           }}
         >
-          channel
+          <ChannelPickerTable
+            type={type}
+            articleId={articleId}
+            selectedKeys={selectedChannelKeys}
+            onSelect={(e: IChannel[]) => {
+              console.log(e);
+              if (typeof onChannelSelect !== "undefined") {
+                onChannelSelect(e);
+              }
+            }}
+          />
         </div>
       </div>
     </Affix>

+ 6 - 3
dashboard/src/components/channel/ChannelPicker.tsx

@@ -3,12 +3,14 @@ import { Button, Modal } from "antd";
 
 import ChannelPickerTable from "./ChannelPickerTable";
 import { IChannel } from "./Channel";
+import { ArticleType } from "../article/Article";
 
 interface IWidget {
-  type: string;
-  articleId: string;
+  type?: ArticleType | "editable";
+  articleId?: string;
+  multiSelect?: boolean;
 }
-const Widget = ({ type, articleId }: IWidget) => {
+const Widget = ({ type, articleId, multiSelect }: IWidget) => {
   const [isModalOpen, setIsModalOpen] = useState(false);
 
   const showModal = () => {
@@ -38,6 +40,7 @@ const Widget = ({ type, articleId }: IWidget) => {
         <ChannelPickerTable
           type={type}
           articleId={articleId}
+          multiSelect={multiSelect}
           onSelect={(e: IChannel) => {
             console.log(e);
             handleCancel();

+ 207 - 219
dashboard/src/components/channel/ChannelPickerTable.tsx

@@ -1,23 +1,30 @@
-import { ProTable } from "@ant-design/pro-components";
+import { ProList } from "@ant-design/pro-components";
 import { useIntl } from "react-intl";
-import { Space, Table } from "antd";
-import { GlobalOutlined } from "@ant-design/icons";
+import { Dropdown, Space, Table } from "antd";
+import {
+  GlobalOutlined,
+  EditOutlined,
+  MoreOutlined,
+  CopyOutlined,
+} from "@ant-design/icons";
 import { Button } from "antd";
 
-import { PublicityValueEnum } from "../studio/table";
-import { IApiResponseChannelList, IFinal } from "../api/Channel";
+import { IApiResponseChannelList, IFinal, TChannelType } from "../api/Channel";
 import { get } from "../../request";
 import { LockIcon } from "../../assets/icon";
 import StudioName, { IStudio } from "../auth/StudioName";
 import ProgressSvg from "./ProgressSvg";
 import { IChannel } from "./Channel";
+import { ArticleType } from "../article/Article";
+import CopyToModal from "./CopyToModal";
+import { useState } from "react";
 
 export interface IItem {
   id: number;
   uid: string;
   title: string;
   summary: string;
-  type: string;
+  type: TChannelType;
   studio: IStudio;
   shareType: string;
   role?: string;
@@ -26,229 +33,104 @@ export interface IItem {
   final?: IFinal[];
 }
 interface IWidget {
-  type: string;
-  articleId: string;
+  type?: ArticleType | "editable";
+  articleId?: string;
+  multiSelect?: boolean;
+  selectedKeys?: string[];
   onSelect?: Function;
 }
-const Widget = ({ type, articleId, onSelect }: IWidget) => {
+const Widget = ({
+  type,
+  articleId,
+  multiSelect = true,
+  selectedKeys = [],
+  onSelect,
+}: IWidget) => {
   const intl = useIntl();
-
+  const [selectedRowKeys, setSelectedRowKeys] =
+    useState<React.Key[]>(selectedKeys);
   return (
     <>
-      <ProTable<IItem>
-        columns={[
-          {
-            title: intl.formatMessage({
-              id: "forms.fields.name.label",
-            }),
-            dataIndex: "title",
-            key: "title",
-            search: false,
-            tip: "过长会自动收缩",
-            ellipsis: true,
-            valueType: "text",
-            render: (text, row, index, action) => {
-              let pIcon = <></>;
-              switch (row.publicity) {
-                case 10:
-                  pIcon = <LockIcon />;
-                  break;
-                case 30:
-                  pIcon = <GlobalOutlined />;
-                  break;
+      <ProList<IItem>
+        rowSelection={
+          multiSelect
+            ? {
+                // 自定义选择项参考: https://ant.design/components/table-cn/#components-table-demo-row-selection-custom
+                // 注释该行则默认不显示下拉选项
+                selectedRowKeys,
+                selections: [Table.SELECTION_ALL, Table.SELECTION_INVERT],
               }
-              return (
-                <Button
-                  type="link"
-                  onClick={() => {
-                    if (typeof onSelect !== "undefined") {
-                      const e: IChannel = { name: row.title, id: row.uid };
-                      onSelect(e);
-                    }
-                  }}
-                >
-                  <Space>
-                    {pIcon}
-                    {row.title}
+            : undefined
+        }
+        tableAlertRender={
+          multiSelect
+            ? ({ selectedRowKeys, selectedRows, onCleanSelected }) => (
+                <Space size={24}>
+                  <span>
+                    {intl.formatMessage({ id: "buttons.selected" })}
+                    {selectedRowKeys.length}
+                    <Button
+                      type="link"
+                      style={{ marginInlineStart: 8 }}
+                      onClick={onCleanSelected}
+                    >
+                      {intl.formatMessage({ id: "buttons.unselect" })}
+                    </Button>
+                  </span>
+                </Space>
+              )
+            : undefined
+        }
+        tableAlertOptionRender={
+          multiSelect
+            ? ({ selectedRowKeys, selectedRows, onCleanSelected }) => {
+                return (
+                  <Space size={16}>
+                    <Button
+                      type="link"
+                      onClick={() => {
+                        console.log("select", selectedRowKeys);
+                        if (typeof onSelect !== "undefined") {
+                          onSelect(
+                            selectedRows.map((item) => {
+                              return {
+                                id: item.uid,
+                                name: item.title,
+                              };
+                            })
+                          );
+                        }
+                      }}
+                    >
+                      {intl.formatMessage({
+                        id: "buttons.select",
+                      })}
+                    </Button>
                   </Space>
-                </Button>
-              );
-            },
-          },
-          {
-            title: intl.formatMessage({
-              id: "channel.title",
-            }),
-            render: (text, row, index, action) => {
-              return <StudioName data={row.studio} />;
-            },
-            key: "studio",
-            search: false,
-          },
-          {
-            title: intl.formatMessage({
-              id: "tables.progress.label",
-            }),
-            width: 210,
-            render: (text, row, index, action) => {
-              return <ProgressSvg data={row.final} width={200} />;
-            },
-            key: "progress",
-            search: false,
-          },
-          {
-            title: intl.formatMessage({
-              id: "forms.fields.type.label",
-            }),
-            dataIndex: "type",
-            key: "type",
-            width: 100,
-            search: false,
-            filters: true,
-            onFilter: true,
-            valueEnum: {
-              all: {
-                text: intl.formatMessage({
-                  id: "channel.type.all.title",
-                }),
-                status: "Default",
-              },
-              translation: {
-                text: intl.formatMessage({
-                  id: "channel.type.translation.label",
-                }),
-                status: "Success",
-              },
-              nissaya: {
-                text: intl.formatMessage({
-                  id: "channel.type.nissaya.label",
-                }),
-                status: "Processing",
-              },
-              commentary: {
-                text: intl.formatMessage({
-                  id: "channel.type.commentary.label",
-                }),
-                status: "Default",
-              },
-              original: {
-                text: intl.formatMessage({
-                  id: "channel.type.original.label",
-                }),
-                status: "Default",
-              },
-              general: {
-                text: intl.formatMessage({
-                  id: "channel.type.general.label",
-                }),
-                status: "Default",
-              },
-            },
-          },
-          {
-            title: intl.formatMessage({
-              id: "forms.fields.publicity.label",
-            }),
-            dataIndex: "publicity",
-            key: "publicity",
-            width: 100,
-            search: false,
-            filters: true,
-            hideInTable: true,
-            onFilter: true,
-            valueEnum: PublicityValueEnum(),
-          },
-          {
-            title: intl.formatMessage({ id: "buttons.option" }),
-            key: "option",
-            width: 120,
-            valueType: "option",
-            render: (text, row, index, action) => {
-              return [
-                intl.formatMessage({
-                  id: "buttons.edit",
-                }),
-              ];
-            },
-          },
-          {
-            title: "类型",
-            dataIndex: "shareType",
-            valueType: "select",
-            hideInTable: true,
-            width: 120,
-            valueEnum: {
-              all: { text: "全部" },
-              my: { text: "我的" },
-              share: { text: "协作" },
-              public: { text: "全网公开" },
-            },
-          },
-          {
-            title: intl.formatMessage({ id: "auth.role.label" }),
-            dataIndex: "role",
-            valueType: "select",
-            width: 120,
-            valueEnum: {
-              all: { text: "全部" },
-              owner: { text: intl.formatMessage({ id: "auth.role.owner" }) },
-              manager: {
-                text: intl.formatMessage({ id: "auth.role.manager" }),
-              },
-              editor: { text: intl.formatMessage({ id: "auth.role.editor" }) },
-              member: { text: intl.formatMessage({ id: "auth.role.member" }) },
-            },
-          },
-        ]}
-        rowSelection={{
-          // 自定义选择项参考: https://ant.design/components/table-cn/#components-table-demo-row-selection-custom
-          // 注释该行则默认不显示下拉选项
-          selections: [Table.SELECTION_ALL, Table.SELECTION_INVERT],
-        }}
-        tableAlertRender={({
-          selectedRowKeys,
-          selectedRows,
-          onCleanSelected,
-        }) => (
-          <Space size={24}>
-            <span>
-              {intl.formatMessage({ id: "buttons.selected" })}
-              {selectedRowKeys.length}
-              <Button
-                type="link"
-                style={{ marginInlineStart: 8 }}
-                onClick={onCleanSelected}
-              >
-                {intl.formatMessage({ id: "buttons.unselect" })}
-              </Button>
-            </span>
-          </Space>
-        )}
-        tableAlertOptionRender={() => {
-          return (
-            <Space size={16}>
-              <Button type="link">
-                {intl.formatMessage({
-                  id: "buttons.select",
-                })}
-              </Button>
-            </Space>
-          );
-        }}
+                );
+              }
+            : undefined
+        }
         request={async (params = {}, sorter, filter) => {
           // TODO
           console.log(params, sorter, filter);
           let url: string = "";
           switch (type) {
+            case "editable":
+              url = `/v2/channel?view=user-edit`;
+              break;
             case "chapter":
-              const [book, para] = articleId.split("-");
-              url = `/v2/channel?view=user-in-chapter&book=${book}&para=${para}&progress=sent`;
+              if (typeof articleId !== "undefined") {
+                const id = articleId.split("_");
+                const [book, para] = id[0].split("-");
+                url = `/v2/channel?view=user-in-chapter&book=${book}&para=${para}&progress=sent`;
+              }
+
               break;
           }
           const res: IApiResponseChannelList = await get(url);
           console.log("data", res.data.rows);
           const items: IItem[] = res.data.rows.map((item, id) => {
-            console.log("final", item.final);
             const date = new Date(item.created_at);
             return {
               id: id,
@@ -269,25 +151,131 @@ const Widget = ({ type, articleId, onSelect }: IWidget) => {
               final: item.final,
             };
           });
-
+          setSelectedRowKeys(selectedRowKeys);
           return {
             total: res.data.count,
             succcess: true,
             data: items,
           };
         }}
-        rowKey="id"
+        rowKey="uid"
         bordered
-        pagination={{
-          showQuickJumper: true,
-          showSizeChanger: false,
-          pageSize: 5,
-        }}
         options={false}
         search={{
           filterType: "light",
         }}
-        toolBarRender={() => [<></>]}
+        showActions="hover"
+        metas={{
+          title: {
+            render(dom, entity, index, action, schema) {
+              let pIcon = <></>;
+              switch (entity.publicity) {
+                case 10:
+                  pIcon = <LockIcon />;
+                  break;
+                case 30:
+                  pIcon = <GlobalOutlined />;
+                  break;
+              }
+
+              return (
+                <div key={index}>
+                  <div key="info">
+                    <Space>
+                      {pIcon}
+                      {entity.role !== "member" ? <EditOutlined /> : undefined}
+                    </Space>
+                    <Button
+                      type="link"
+                      onClick={() => {
+                        if (typeof onSelect !== "undefined") {
+                          const e: IChannel = {
+                            name: entity.title,
+                            id: entity.uid,
+                          };
+                          onSelect([e]);
+                        }
+                      }}
+                    >
+                      <Space>
+                        <StudioName data={entity.studio} showName={false} />
+                        {entity.title}
+                      </Space>
+                    </Button>
+                  </div>
+                  <div key="progress">
+                    <ProgressSvg data={entity.final} width={200} />
+                  </div>
+                </div>
+              );
+            },
+            search: false,
+          },
+          actions: {
+            render: (dom, entity, index, action, schema) => {
+              return (
+                <Dropdown
+                  key={index}
+                  trigger={["click"]}
+                  menu={{
+                    items: [
+                      {
+                        key: "copy_to",
+                        label: (
+                          <CopyToModal
+                            trigger={intl.formatMessage({
+                              id: "buttons.copy.to",
+                            })}
+                            channel={{
+                              id: entity.uid,
+                              name: entity.title,
+                              type: entity.type,
+                            }}
+                          />
+                        ),
+                        icon: <CopyOutlined />,
+                      },
+                    ],
+                    onClick: (e) => {
+                      console.log("click ", e);
+                      switch (e.key) {
+                        case "copy_to":
+                          break;
+
+                        default:
+                          break;
+                      }
+                    },
+                  }}
+                  placement="bottomRight"
+                >
+                  <Button
+                    type="link"
+                    size="small"
+                    icon={<MoreOutlined />}
+                  ></Button>
+                </Dropdown>
+              );
+            },
+          },
+          status: {
+            // 自己扩展的字段,主要用于筛选,不在列表中显示
+            title: "筛选",
+            valueType: "select",
+            valueEnum: {
+              all: { text: "全部", status: "Default" },
+              my: {
+                text: "我的",
+              },
+              closed: {
+                text: "协作",
+              },
+              processing: {
+                text: "社区公开",
+              },
+            },
+          },
+        }}
       />
     </>
   );

+ 26 - 14
dashboard/src/pages/library/article/show.tsx

@@ -1,10 +1,13 @@
 import { Affix, Space } from "antd";
 import { Header } from "antd/lib/layout/layout";
-import { useRef, useState } from "react";
-import { useParams } from "react-router-dom";
+import { useState } from "react";
+import { useNavigate, useParams } from "react-router-dom";
+
+import Article, {
+  ArticleMode,
+  ArticleType,
+} from "../../../components/article/Article";
 
-import Article, { ArticleMode } from "../../../components/article/Article";
-import ArticleCard from "../../../components/article/ArticleCard";
 import MainMenu from "../../../components/article/MainMenu";
 import ModeSwitch from "../../../components/article/ModeSwitch";
 import RightPanel, { TPanelName } from "../../../components/article/RightPanel";
@@ -13,6 +16,7 @@ import ToolButtonSearch from "../../../components/article/ToolButtonSearch";
 import ToolButtonSetting from "../../../components/article/ToolButtonSetting";
 import ToolButtonTag from "../../../components/article/ToolButtonTag";
 import ToolButtonToc from "../../../components/article/ToolButtonToc";
+import { IChannel } from "../../../components/channel/Channel";
 
 /**
  * type:
@@ -27,21 +31,16 @@ import ToolButtonToc from "../../../components/article/ToolButtonToc";
  */
 const Widget = () => {
   const { type, id, mode = "read" } = useParams(); //url 参数
+  const navigate = useNavigate();
   console.log("mode", mode);
   const [articleMode, setArticleMode] = useState<ArticleMode>(
     mode as ArticleMode
   );
   const [rightPanel, setRightPanel] = useState<TPanelName>("close");
 
-  const box = useRef<HTMLDivElement>(null);
-
-  const openCol = () => {
-    if (box.current) {
-      box.current.style.display = "block";
-    }
-  };
   //const right = <ProTabs />;
   const rightBarWidth = "48px";
+  const channelId = id?.split("_").slice(1);
   return (
     <div>
       <Affix offsetTop={0}>
@@ -57,7 +56,7 @@ const Widget = () => {
           <div>
             <MainMenu />
           </div>
-          <div>tools</div>
+          <div></div>
           <div style={{ display: "flex" }}>
             <ModeSwitch
               onModeChange={(e: ArticleMode) => {
@@ -90,13 +89,26 @@ const Widget = () => {
           <div>
             <Article
               active={true}
-              type={type}
+              type={type as ArticleType}
               articleId={id}
               mode={articleMode}
             />
           </div>
           <div>
-            <RightPanel curr={rightPanel} />
+            <RightPanel
+              curr={rightPanel}
+              type={type as ArticleType}
+              articleId={id ? id : ""}
+              selectedChannelKeys={channelId}
+              onChannelSelect={(e: IChannel[]) => {
+                const oldId = id?.split("_");
+                const newId = [
+                  oldId ? oldId[0] : undefined,
+                  ...e.map((item) => item.id),
+                ];
+                navigate(`/article/${type}/${newId.join("_")}/${mode}`);
+              }}
+            />
           </div>
         </div>
       </div>