Просмотр исходного кода

:sparkles: channel 选择列表

visuddhinanda 3 лет назад
Родитель
Сommit
3a83c6c2ff

+ 4 - 1
dashboard/src/components/api/Channel.ts

@@ -1,4 +1,4 @@
-import { IStudioApiResponse } from "./Auth";
+import { IStudioApiResponse, Role } from "./Auth";
 
 export interface IChannelApiData {
   id: string;
@@ -15,6 +15,7 @@ export type ChannelInfoProps = {
   studioType: string;
 };
 
+export type IFinal = [number, boolean];
 export interface IApiResponseChannelData {
   uid: string;
   name: string;
@@ -25,6 +26,8 @@ export interface IApiResponseChannelData {
   status: number;
   created_at: string;
   updated_at: string;
+  role?: Role;
+  final?: IFinal[];
 }
 export interface IApiResponseChannel {
   ok: boolean;

+ 39 - 83
dashboard/src/components/channel/ChannelPicker.tsx

@@ -1,93 +1,49 @@
-import { ProList } from "@ant-design/pro-components";
-import { Button, Progress } from "antd";
 import { useState } from "react";
+import { Button, Modal } from "antd";
+import ChannelPickerTable from "./ChannelPickerTable";
+import { IChannel } from "./Channel";
 
-const dataSource = [
-  {
-    name: "语雀的天空",
-    description: "简介",
-    type: "translation",
-    role: "owner",
-    source: "studio",
-  },
-  {
-    name: "Ant Design",
-    description: "简介",
-    type: "translation",
-    role: "reader",
-    source: "studio",
-  },
-  {
-    name: "蚂蚁金服体验科技",
-    description: "简介",
-    type: "nissaya",
-    role: "reader",
-    source: "collaborate",
-  },
-  {
-    name: "TechUI",
-    description: "简介",
-    type: "nissaya",
-    role: "editor",
-    source: "public",
-  },
-];
 interface IWidget {
-  multiSelect?: boolean;
+  type: string;
+  articleId: string;
 }
-const Widget = ({ multiSelect = false }: IWidget) => {
-  const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
-  const rowSelection = {
-    selectedRowKeys,
-    onChange: (keys: React.Key[]) => setSelectedRowKeys(keys),
+const Widget = ({ type, articleId }: IWidget) => {
+  const [isModalOpen, setIsModalOpen] = useState(false);
+
+  const showModal = () => {
+    setIsModalOpen(true);
+  };
+
+  const handleOk = () => {
+    setIsModalOpen(false);
+  };
+
+  const handleCancel = () => {
+    setIsModalOpen(false);
   };
 
   return (
-    <ProList<{ title: string }>
-      toolBarRender={() => {
-        return [
-          <Button key="3" type="primary">
-            新建
-          </Button>,
-        ];
-      }}
-      metas={{
-        title: {
-          dataIndex: "name",
-          title: "版本名称",
-        },
-        description: { dataIndex: "description" },
-        extra: {
-          render: () => (
-            <div
-              style={{
-                minWidth: 200,
-                flex: 1,
-                display: "flex",
-                justifyContent: "flex-end",
-              }}
-            >
-              <div
-                style={{
-                  width: "200px",
-                }}
-              >
-                <Progress percent={80} />
-              </div>
-            </div>
-          ),
-        },
-        actions: {
-          render: () => {
-            return [<a key="init">邀请</a>, "发布"];
-          },
-        },
-      }}
-      rowKey="title"
-      headerTitle="选择版本"
-      rowSelection={rowSelection}
-      dataSource={dataSource}
-    />
+    <>
+      <Button type="primary" onClick={showModal}>
+        Select channel
+      </Button>
+      <Modal
+        width={"80%"}
+        title="选择版本风格"
+        open={isModalOpen}
+        onOk={handleOk}
+        onCancel={handleCancel}
+      >
+        <ChannelPickerTable
+          type={type}
+          articleId={articleId}
+          onSelect={(e: IChannel) => {
+            console.log(e);
+            handleCancel();
+          }}
+        />
+      </Modal>
+    </>
   );
 };
 

+ 324 - 0
dashboard/src/components/channel/ChannelPickerTable.tsx

@@ -0,0 +1,324 @@
+import { ProTable } from "@ant-design/pro-components";
+import { useIntl } from "react-intl";
+import { Space, Table } from "antd";
+import { GlobalOutlined } from "@ant-design/icons";
+import type { MenuProps } from "antd";
+import { Button, Menu } from "antd";
+import { SearchOutlined } from "@ant-design/icons";
+import { PublicityValueEnum } from "../studio/table";
+import { IApiResponseChannelList, IFinal } 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";
+
+const onMenuClick: MenuProps["onClick"] = (e) => {
+  console.log("click", e);
+};
+
+const menu = (
+  <Menu
+    onClick={onMenuClick}
+    items={[
+      {
+        key: "1",
+        label: "在藏经阁中打开",
+        icon: <SearchOutlined />,
+      },
+      {
+        key: "2",
+        label: "分享",
+        icon: <SearchOutlined />,
+      },
+      {
+        key: "3",
+        label: "删除",
+        icon: <SearchOutlined />,
+      },
+    ]}
+  />
+);
+
+export interface IItem {
+  id: number;
+  uid: string;
+  title: string;
+  summary: string;
+  type: string;
+  studio: IStudio;
+  shareType: string;
+  role?: string;
+  publicity: number;
+  createdAt: number;
+  final?: IFinal[];
+}
+interface IWidget {
+  type: string;
+  articleId: string;
+  onSelect?: Function;
+}
+const Widget = ({ type, articleId, onSelect }: IWidget) => {
+  const intl = useIntl();
+
+  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;
+              }
+              return (
+                <Button
+                  type="link"
+                  onClick={() => {
+                    if (typeof onSelect !== "undefined") {
+                      const e: IChannel = { name: row.title, id: row.uid };
+                      onSelect(e);
+                    }
+                  }}
+                >
+                  <Space>
+                    {pIcon}
+                    {row.title}
+                  </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>
+          );
+        }}
+        request={async (params = {}, sorter, filter) => {
+          // TODO
+          console.log(params, sorter, filter);
+          let url: string = "";
+          switch (type) {
+            case "chapter":
+              const [book, para] = articleId.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,
+              uid: item.uid,
+              title: item.name,
+              summary: item.summary,
+              studio: {
+                id: item.studio.id,
+                nickName: item.studio.nickName,
+                studioName: item.studio.studioName,
+                avatar: item.studio.avatar,
+              },
+              shareType: "my",
+              role: item.role,
+              type: item.type,
+              publicity: item.status,
+              createdAt: date.getTime(),
+              final: item.final,
+            };
+          });
+
+          return {
+            total: res.data.count,
+            succcess: true,
+            data: items,
+          };
+        }}
+        rowKey="id"
+        bordered
+        pagination={{
+          showQuickJumper: true,
+          showSizeChanger: false,
+          pageSize: 5,
+        }}
+        options={false}
+        search={{
+          filterType: "light",
+        }}
+        toolBarRender={() => [<></>]}
+      />
+    </>
+  );
+};
+
+export default Widget;

+ 104 - 0
dashboard/src/components/channel/ProgressSvg.tsx

@@ -0,0 +1,104 @@
+import { IFinal } from "../api/Channel";
+
+interface IWidget {
+  data?: IFinal[];
+  width?: number;
+}
+const Widget = ({ data, width = 300 }: IWidget) => {
+  //绘制句子进度
+  if (typeof data === "undefined" || data.length === 0) {
+    return <></>;
+  }
+  //进度
+  let svg_width = 0;
+  if (data) {
+    for (const iterator of data) {
+      svg_width += iterator[0];
+    }
+  }
+
+  const svg_height = svg_width / 10;
+
+  let curr_x = 0;
+  let finished = 0;
+
+  const innerBar = data?.map((item, id) => {
+    const stroke_width = item[0];
+    curr_x += stroke_width;
+    finished += item[1] ? stroke_width : 0;
+    return (
+      <rect
+        x={curr_x - stroke_width}
+        y={0}
+        height={svg_height}
+        width={stroke_width}
+        fill={item[1] ? "url(#grad1)" : "url(#grad2)"}
+      />
+    );
+  });
+  const finishedBar = (
+    <rect
+      x={0}
+      y={svg_height / 2 - svg_height / 20}
+      width={finished}
+      height={svg_height / 10}
+      style={{ strokeWidth: 0, fill: "rgb(100, 100, 228)" }}
+    />
+  );
+  const progress = (
+    <svg viewBox={`0 0 ${svg_width} ${svg_height} `} width={"100%"}>
+      <defs>
+        <linearGradient id="grad1" x1="0%" y1="0%" x2="0%" y2="100%">
+          <stop
+            offset="0%"
+            style={{ stopColor: "rgb(0,180,0)", stopOpacity: 1 }}
+          />
+          <stop
+            offset="50%"
+            style={{ stopColor: "rgb(255,255,255)", stopOpacity: 0.5 }}
+          />
+          <stop
+            offset="100%"
+            style={{ stopColor: "rgb(0,180,0)", stopOpacity: 1 }}
+          />
+        </linearGradient>
+        <linearGradient id="grad2" x1="0%" y1="0%" x2="0%" y2="100%">
+          <stop
+            offset="0%"
+            style={{ stopColor: "rgb(180,180,180)", stopOpacity: 1 }}
+          />
+          <stop
+            offset="50%"
+            style={{ stopColor: "rgb(255,255,255)", stopOpacity: 0.5 }}
+          />
+          <stop
+            offset="100%"
+            style={{ stopColor: "rgb(180,180,180)", stopOpacity: 1 }}
+          />
+        </linearGradient>
+      </defs>
+      {innerBar}
+      {finishedBar}
+    </svg>
+  );
+  /*
+			output +=
+				"<rect  x='0' y='0'  width='" + svg_width + "' height='" + svg_height / 5 + "' class='progress_bar_bg' />";
+			output +=
+				"<rect  x='0' y='0'  width='" +
+				allFinal +
+				"' height='" +
+				svg_height / 5 +
+				"' class='progress_bar_percent' style='stroke-width: 0; fill: rgb(100, 228, 100);'/>";
+			output += '<text x="0" y="' + svg_height + '" font-size="' + svg_height * 0.8 + '">';
+			output += channalinfo["count"] + "/" + channalinfo["all"] + "@" + curr_x;
+			output += "</text>";
+			output += "<svg>";
+			output += "</div>";
+*/
+  //进度结束
+
+  return <div style={{ width: width }}>{progress}</div>;
+};
+
+export default Widget;