Quellcode durchsuchen

Merge pull request #1932 from visuddhinanda/agile

添加头像支持
visuddhinanda vor 2 Jahren
Ursprung
Commit
ba52399c31

+ 12 - 3
dashboard/src/components/api/Auth.ts

@@ -14,6 +14,7 @@ export interface IUserRequest {
   id?: string;
   userName?: string;
   nickName?: string;
+  email?: string;
   avatar?: string;
 }
 
@@ -26,11 +27,19 @@ export interface IUserListResponse {
   };
 }
 
-export interface IUserApiResponse {
+export interface IUserResponse {
+  ok: boolean;
+  message: string;
+  data: IUserApiData;
+}
+
+export interface IUserApiData {
   id: string;
   userName: string;
   nickName: string;
-  avatar: string;
+  email: string;
+  avatar?: string;
+  avatarName?: string;
 }
 
 export interface IStudioApiResponse {
@@ -39,5 +48,5 @@ export interface IStudioApiResponse {
   studioName?: string;
   realName: string;
   avatar?: string;
-  owner: IUserApiResponse;
+  owner: IUser;
 }

+ 7 - 5
dashboard/src/components/auth/Avatar.tsx

@@ -15,6 +15,7 @@ import {
 import { useAppSelector } from "../../hooks";
 import { currentUser as _currentUser } from "../../reducers/current-user";
 import { TooltipPlacement } from "antd/lib/tooltip";
+import SettingModal from "./setting/SettingModal";
 
 const { Title } = Typography;
 
@@ -33,7 +34,7 @@ const AvatarWidget = ({ style, placement = "bottomRight" }: IWidget) => {
     setNickName(user?.nickName);
   }, [user]);
 
-  const userCard = (
+  const UserCard = () => (
     <ProCard
       style={{ maxWidth: 500, minWidth: 300 }}
       actions={[
@@ -42,7 +43,7 @@ const AvatarWidget = ({ style, placement = "bottomRight" }: IWidget) => {
             id: "buttons.setting",
           })}
         >
-          <SettingOutlined key="setting" />
+          <SettingModal trigger={<SettingOutlined key="setting" />} />
         </Tooltip>,
         <Tooltip
           title={intl.formatMessage({
@@ -79,14 +80,15 @@ const AvatarWidget = ({ style, placement = "bottomRight" }: IWidget) => {
       </div>
     </ProCard>
   );
-  const login = <Link to="/anonymous/users/sign-in">登录</Link>;
+  const Login = () => <Link to="/anonymous/users/sign-in">登录</Link>;
   return (
     <>
-      <Popover content={user ? userCard : login} placement={placement}>
+      <Popover content={user ? <UserCard /> : <Login />} placement={placement}>
         <span style={style}>
           <Avatar
             style={{ backgroundColor: user ? "#87d068" : "gray" }}
-            icon={<UserOutlined />}
+            icon={user?.avatar ? undefined : <UserOutlined />}
+            src={user?.avatar}
             size="small"
           >
             {user ? nickName?.slice(0, 1) : undefined}

+ 52 - 51
dashboard/src/components/auth/SignInAvatar.tsx

@@ -23,72 +23,73 @@ const SignInAvatarWidget = () => {
   const [userName, setUserName] = useState<string>();
   const [nickName, setNickName] = useState<string>();
   const user = useAppSelector(_currentUser);
+
   useEffect(() => {
     setUserName(user?.realName);
     setNickName(user?.nickName);
   }, [user]);
 
-  const userCard = (
-    <>
-      <ProCard
-        style={{ maxWidth: 500, minWidth: 300 }}
-        actions={[
-          <Tooltip
-            title={intl.formatMessage({
-              id: "buttons.setting",
-            })}
-          >
-            <SettingOutlined key="setting" />
-          </Tooltip>,
-          <Tooltip
-            title={intl.formatMessage({
-              id: "columns.library.blog.label",
-            })}
-          >
-            <Link to={`/blog/${userName}/overview`}>
-              <HomeOutlined key="home" />
-            </Link>
-          </Tooltip>,
-          <Tooltip
-            title={intl.formatMessage({
-              id: "buttons.sign-out",
-            })}
-          >
-            <LogoutOutlined
-              key="logout"
-              onClick={() => {
-                sessionStorage.removeItem("token");
-                localStorage.removeItem("token");
-                navigate("/anonymous/users/sign-in");
-              }}
-            />
-          </Tooltip>,
-        ]}
-      >
-        <Paragraph>
-          <Title level={3}>{nickName}</Title>
-          <Paragraph style={{ textAlign: "right" }}>
-            {intl.formatMessage({
-              id: "buttons.welcome",
-            })}
-          </Paragraph>
-        </Paragraph>
-      </ProCard>
-    </>
-  );
-
   if (typeof user === "undefined") {
     return <Link to="/anonymous/users/sign-in">登录</Link>;
   } else {
     return (
       <>
-        <Popover content={userCard} placement="bottomRight">
+        <Popover
+          content={
+            <ProCard
+              style={{ maxWidth: 500, minWidth: 300 }}
+              actions={[
+                <Tooltip
+                  title={intl.formatMessage({
+                    id: "buttons.setting",
+                  })}
+                >
+                  <SettingOutlined key="setting" />
+                </Tooltip>,
+                <Tooltip
+                  title={intl.formatMessage({
+                    id: "columns.library.blog.label",
+                  })}
+                >
+                  <Link to={`/blog/${userName}/overview`}>
+                    <HomeOutlined key="home" />
+                  </Link>
+                </Tooltip>,
+                <Tooltip
+                  title={intl.formatMessage({
+                    id: "buttons.sign-out",
+                  })}
+                >
+                  <LogoutOutlined
+                    key="logout"
+                    onClick={() => {
+                      sessionStorage.removeItem("token");
+                      localStorage.removeItem("token");
+                      navigate("/anonymous/users/sign-in");
+                    }}
+                  />
+                </Tooltip>,
+              ]}
+            >
+              <Paragraph>
+                <Title level={3}>{nickName}</Title>
+                <Paragraph style={{ textAlign: "right" }}>
+                  {intl.formatMessage({
+                    id: "buttons.welcome",
+                  })}
+                </Paragraph>
+              </Paragraph>
+            </ProCard>
+          }
+          placement="bottomRight"
+        >
           <Avatar
             style={{ backgroundColor: "#87d068" }}
             icon={<UserOutlined />}
+            src={user?.avatar}
             size="small"
           >
-            {nickName?.slice(0, 1)}
+            {nickName?.slice(0, 2)}
           </Avatar>
         </Popover>
       </>

+ 8 - 2
dashboard/src/components/auth/StudioName.tsx

@@ -23,7 +23,7 @@ const StudioNameWidget = ({
   popOver,
   onClick,
 }: IWidget) => {
-  const avatar = <Avatar size="small">{data?.nickName?.slice(0, 1)}</Avatar>;
+  console.debug("studio", data);
   return (
     <StudioCard popOver={popOver} studio={data}>
       <Space
@@ -33,7 +33,13 @@ const StudioNameWidget = ({
           }
         }}
       >
-        {showAvatar ? avatar : ""}
+        {showAvatar ? (
+          <Avatar size="small" src={data?.avatar}>
+            {data?.nickName?.slice(0, 2)}
+          </Avatar>
+        ) : (
+          <></>
+        )}
         {showName ? data?.nickName : ""}
       </Space>
     </StudioCard>

+ 130 - 0
dashboard/src/components/auth/setting/SettingAccount.tsx

@@ -0,0 +1,130 @@
+import {
+  ProForm,
+  ProFormText,
+  ProFormUploadButton,
+} from "@ant-design/pro-components";
+import { message } from "antd";
+import { useAppSelector } from "../../../hooks";
+import { currentUser } from "../../../reducers/current-user";
+import { API_HOST, delete_, get, put } from "../../../request";
+import { IUserRequest, IUserResponse } from "../../api/Auth";
+import { UploadChangeParam, UploadFile } from "antd/es/upload/interface";
+import { get as getToken } from "../../../reducers/current-user";
+import { IAttachmentResponse } from "../../api/Attachments";
+import { useIntl } from "react-intl";
+import { IDeleteResponse } from "../../api/Group";
+
+interface IAccount {
+  id: string;
+  realName: string;
+  nickName: string;
+  email: string;
+  avatar?: UploadFile<IAttachmentResponse>[];
+}
+
+const SettingAccountWidget = () => {
+  const user = useAppSelector(currentUser);
+  const intl = useIntl();
+
+  return (
+    <ProForm<IAccount>
+      onFinish={async (values: IAccount) => {
+        console.log(values);
+        let _avatar: string = "";
+
+        if (
+          typeof values.avatar === "undefined" ||
+          values.avatar.length === 0
+        ) {
+          _avatar = "";
+        } else if (typeof values.avatar[0].response === "undefined") {
+          _avatar = values.avatar[0].uid;
+        } else {
+          console.debug("upload ", values.avatar[0].response);
+          _avatar = values.avatar[0].response.data.name;
+        }
+        const url = `/v2/user/${user?.id}`;
+        const postData = {
+          nickName: values.nickName,
+          avatar: _avatar,
+          email: values.email,
+        };
+        console.log("account put ", url, postData);
+        const res = await put<IUserRequest, IUserResponse>(url, postData);
+
+        if (res.ok) {
+          message.success(intl.formatMessage({ id: "flashes.success" }));
+        } else {
+          message.error(res.message);
+        }
+      }}
+      params={{}}
+      request={async () => {
+        const url = `/v2/user/${user?.id}`;
+        console.log("url", url);
+        const res = await get<IUserResponse>(url);
+        if (res.ok) {
+        }
+        return {
+          id: res.data.id,
+          realName: res.data.userName,
+          nickName: res.data.nickName,
+          email: res.data.email,
+          avatar:
+            res.data.avatar && res.data.avatarName
+              ? [
+                  {
+                    uid: res.data.avatarName,
+                    name: "avatar",
+                    thumbUrl: res.data.avatar,
+                  },
+                ]
+              : [],
+        };
+      }}
+    >
+      <ProFormUploadButton
+        name="avatar"
+        label="头像"
+        max={1}
+        fieldProps={{
+          name: "file",
+          listType: "picture-card",
+          className: "avatar-uploader",
+          headers: {
+            Authorization: `Bearer ${getToken()}`,
+          },
+          onRemove: (file: UploadFile<any>): boolean => {
+            console.log("remove", file);
+            const url = `/v2/attachment/1?name=${file.uid}`;
+            console.info("avatar delete url", url);
+            delete_<IDeleteResponse>(url)
+              .then((json) => {
+                if (json.ok) {
+                  message.success("删除成功");
+                } else {
+                  message.error(json.message);
+                }
+              })
+              .catch((e) => console.log("Oops errors!", e));
+            return true;
+          },
+        }}
+        action={`${API_HOST}/api/v2/attachment?type=avatar`}
+        extra="必须为正方形。最大512*512"
+        onChange={(info: UploadChangeParam<UploadFile<any>>) => {}}
+      />
+      <ProFormText
+        width="md"
+        readonly
+        name="realName"
+        label="realName"
+        tooltip="最长为 24 位"
+      />
+      <ProFormText width="md" name="nickName" label="nickName" />
+      <ProFormText readonly name="email" width="md" label="email" />
+    </ProForm>
+  );
+};
+
+export default SettingAccountWidget;

+ 54 - 0
dashboard/src/components/auth/setting/SettingModal.tsx

@@ -0,0 +1,54 @@
+import { Modal, Tabs } from "antd";
+import { useEffect, useState } from "react";
+import SettingArticle from "./SettingArticle";
+import SettingAccount from "./SettingAccount";
+interface IWidget {
+  trigger?: React.ReactNode;
+  open?: boolean;
+  onClose?: Function;
+}
+const SettingModalWidget = ({ trigger, open, onClose }: IWidget) => {
+  const [isModalOpen, setIsModalOpen] = useState(open);
+
+  useEffect(() => setIsModalOpen(open), [open]);
+  const showModal = () => {
+    setIsModalOpen(true);
+  };
+
+  const handleOk = () => {
+    if (typeof onClose !== "undefined") {
+      onClose(false);
+    }
+    setIsModalOpen(false);
+  };
+
+  const handleCancel = () => {
+    if (typeof onClose !== "undefined") {
+      onClose(false);
+    }
+    setIsModalOpen(false);
+  };
+  return (
+    <>
+      <span onClick={showModal}>{trigger}</span>
+      <Modal
+        width={"80%"}
+        title="Setting"
+        footer={false}
+        open={isModalOpen}
+        onOk={handleOk}
+        onCancel={handleCancel}
+      >
+        <Tabs
+          tabPosition="left"
+          items={[
+            { label: "账户", key: "account", children: <SettingAccount /> }, // 务必填写 key
+            { label: "编辑器", key: "editor", children: <SettingArticle /> },
+          ]}
+        />
+      </Modal>
+    </>
+  );
+};
+
+export default SettingModalWidget;

+ 1 - 1
dashboard/src/load.ts

@@ -83,7 +83,7 @@ const init = () => {
   const token = getToken();
   if (token) {
     get<ITokenRefreshResponse>("/v2/auth/current").then((response) => {
-      console.log(response);
+      console.log("auth", response);
       if (response.ok) {
         const it: IUser = {
           id: response.data.id,