Selaa lähdekoodia

feat: 新增为用户重设密码的功能

BaiLuoYan 1 päivä sitten
vanhempi
commit
49e2382299

+ 2 - 2
src/pages/Admin/Product/Detail/components/CredentialModal.tsx

@@ -40,12 +40,12 @@ export const CredentialModal = ({ open, data, onClose }: CredentialModalProps) =
         showIcon
       />
       {data && (
-        <>
+        <div className="mt-3">
           <CopyField label="App Key" value={data.appKey} />
           <CopyField label="App Secret" value={data.appSecret} />
           <CopyField label="管理员账号" value={data.adminUser} />
           <CopyField label="管理员初始密码" value={data.adminPassword} />
-        </>
+        </div>
       )}
     </Modal>
   );

+ 3 - 11
src/pages/Admin/User/components/Form.tsx

@@ -8,7 +8,7 @@ interface UserFormProps {
   mode: EditorFormMode;
   initialValues?: Partial<API.UserItem>;
   trigger: React.ReactElement;
-  onSuccess: () => void;
+  onSuccess: (credentialsTicket?: string) => void;
 }
 
 const buildDeptOptions = (items: API.DeptItem[]): any[] =>
@@ -35,7 +35,6 @@ export const UserForm = ({ mode, initialValues, trigger, onSuccess }: UserFormPr
           ...initialValues,
           id: undefined,
           username: undefined,
-          password: undefined,
           deptId: initialValues?.deptId || undefined,
         }
       : mode === 'edit'
@@ -53,11 +52,12 @@ export const UserForm = ({ mode, initialValues, trigger, onSuccess }: UserFormPr
         deptId: values.deptId ?? 0,
       });
       if (!res.success) return false;
+      onSuccess();
     } else {
       const res = await fetchCreateUser(values);
       if (!res.success) return false;
+      onSuccess(res.data?.credentialsTicket);
     }
-    onSuccess();
     return true;
   };
 
@@ -77,14 +77,6 @@ export const UserForm = ({ mode, initialValues, trigger, onSuccess }: UserFormPr
         disabled={mode === 'edit'}
         readonly={mode === 'edit'}
       />
-      {mode !== 'edit' && (
-        <ProFormText
-          name="password"
-          label="密码"
-          rules={[{ required: true }]}
-          fieldProps={{ type: 'password' }}
-        />
-      )}
       <ProFormText name="nickname" label="昵称" />
       <ProFormText name="email" label="邮箱" rules={[{ type: 'email' }]} />
       <ProFormText name="phone" label="手机号" />

+ 46 - 0
src/pages/Admin/User/components/UserCredentialModal.tsx

@@ -0,0 +1,46 @@
+import { Alert, Modal, Typography } from 'antd';
+
+const { Paragraph } = Typography;
+
+interface UserCredentialModalProps {
+  open: boolean;
+  data: API.FetchUserCredentialsResp | null;
+  onClose: () => void;
+}
+
+const CopyField = ({ label, value }: { label: string; value: string }) => (
+  <div className="mb-3">
+    <div className="text-xs text-(--ant-color-text-tertiary) mb-1">{label}</div>
+    <Paragraph
+      copyable
+      className="font-mono bg-(--ant-color-fill-quaternary) px-3 py-2 rounded mb-0"
+    >
+      {value}
+    </Paragraph>
+  </div>
+);
+
+export const UserCredentialModal = ({ open, data, onClose }: UserCredentialModalProps) => (
+  <Modal
+    title="用户凭证"
+    open={open}
+    onOk={onClose}
+    onCancel={onClose}
+    okText="我已保存"
+    cancelButtonProps={{ style: { display: 'none' } }}
+    maskClosable={false}
+  >
+    <Alert
+      type="warning"
+      message="以下凭证仅展示一次,关闭后无法再次查看,请妥善保存。"
+      className="mb-4"
+      showIcon
+    />
+    {data && (
+      <div className="mt-3">
+        <CopyField label="用户名" value={data.username} />
+        <CopyField label="密码" value={data.password} />
+      </div>
+    )}
+  </Modal>
+);

+ 40 - 5
src/pages/Admin/User/index.tsx

@@ -12,7 +12,12 @@ import {
   useUserProductRoles,
 } from '@/pages/Admin/_shared/useUserProductRoles';
 import { fetchDeptTree } from '@/services/dept';
-import { fetchUpdateUserStatus, fetchUserList } from '@/services/user';
+import {
+  fetchResetPassword,
+  fetchUpdateUserStatus,
+  fetchUserCredentials,
+  fetchUserList,
+} from '@/services/user';
 import { unixTimeFormat } from '@/utils/timeUtils';
 import { StopOutlined } from '@ant-design/icons';
 import { ActionType, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
@@ -20,6 +25,7 @@ import { useIntl } from '@umijs/max';
 import { Button, Popconfirm, Tag, message } from 'antd';
 import { useEffect, useMemo, useRef, useState } from 'react';
 import { UserForm } from './components/Form';
+import { UserCredentialModal } from './components/UserCredentialModal';
 
 const flattenDeptTree = (items: API.DeptItem[]): { label: string; value: number }[] =>
   items.flatMap((d) => [
@@ -41,6 +47,8 @@ export default function UserPage() {
     userId: 0,
   });
   const [refreshKey, setRefreshKey] = useState(0);
+  const [credData, setCredData] = useState<API.FetchUserCredentialsResp | null>(null);
+  const [credOpen, setCredOpen] = useState(false);
 
   useEffect(() => {
     fetchDeptTree().then((res) => {
@@ -62,6 +70,25 @@ export default function UserPage() {
     actionRef.current?.reload();
   };
 
+  const showCredentials = async (ticket: string) => {
+    const res = await fetchUserCredentials({ ticket });
+    if (!res.success || !res.data) return;
+    setCredData(res.data);
+    setCredOpen(true);
+  };
+
+  const handleCreateSuccess = async (ticket?: string) => {
+    actionRef.current?.reload();
+    if (ticket) await showCredentials(ticket);
+  };
+
+  const handleResetPassword = async (r: API.UserItem) => {
+    const res = await fetchResetPassword({ userId: r.id });
+    if (!res.success || !res.data) return;
+    message.success('密码已重置');
+    await showCredentials(res.data.credentialsTicket);
+  };
+
   const columns: ProColumns<API.UserItem>[] = [
     { title: '用户名', dataIndex: 'username', width: 200 },
     { title: '昵称', dataIndex: 'nickname', width: 200 },
@@ -146,7 +173,7 @@ export default function UserPage() {
     {
       title: '操作',
       valueType: 'option',
-      width: 260,
+      width: 320,
       render: (_, r) => [
         <UserForm
           key="edit"
@@ -160,7 +187,7 @@ export default function UserPage() {
           mode="copy"
           initialValues={r}
           trigger={<a>复制</a>}
-          onSuccess={() => actionRef.current?.reload()}
+          onSuccess={handleCreateSuccess}
         />,
         <a key="roles" onClick={() => setRolesDrawer({ open: true, userId: r.id })}>
           分配角色
@@ -168,6 +195,13 @@ export default function UserPage() {
         <a key="perms" onClick={() => setPermDrawer({ open: true, userId: r.id })}>
           设置权限
         </a>,
+        <Popconfirm
+          key="reset"
+          title={`确认重置「${r.username}」的密码?`}
+          onConfirm={() => handleResetPassword(r)}
+        >
+          <a>重置密码</a>
+        </Popconfirm>,
         <Popconfirm
           key="status"
           title={
@@ -194,7 +228,7 @@ export default function UserPage() {
         search={{ span: { xs: 24, sm: 12, md: 8, lg: 6, xl: 4, xxl: 4 } }}
         request={fetchUserList}
         onDataSourceChange={setPageUsers}
-        scroll={{ x: 1220 }}
+        scroll={{ x: 1280 }}
         tableLayout="fixed"
         pagination={{
           defaultPageSize: 20,
@@ -206,7 +240,7 @@ export default function UserPage() {
             key="create"
             mode="add"
             trigger={<Button type="primary">新建用户</Button>}
-            onSuccess={() => actionRef.current?.reload()}
+            onSuccess={handleCreateSuccess}
           />,
         ]}
       />
@@ -219,6 +253,7 @@ export default function UserPage() {
         {...permDrawer}
         onClose={() => setPermDrawer((p) => ({ ...p, open: false }))}
       />
+      <UserCredentialModal open={credOpen} data={credData} onClose={() => setCredOpen(false)} />
     </PageContainer>
   );
 }

+ 8 - 0
src/services/user/index.ts

@@ -4,6 +4,14 @@ export async function fetchCreateUser(body: API.CreateUserReq) {
   return postJson<API.UserCreateResult>('/user/create', body);
 }
 
+export async function fetchResetPassword(body: API.ResetPasswordReq) {
+  return postJson<API.Result<API.ResetPasswordResp>>('/user/resetPassword', body);
+}
+
+export async function fetchUserCredentials(body: API.FetchUserCredentialsReq) {
+  return postJson<API.Result<API.FetchUserCredentialsResp>>('/user/fetchCredentials', body);
+}
+
 export async function fetchUpdateUser(body: API.UpdateUserReq) {
   return postJson<API.Result>('/user/update', body);
 }

+ 20 - 2
src/services/user/typings.d.ts

@@ -16,13 +16,31 @@ declare namespace API {
   }
   interface CreateUserReq {
     username: string;
-    password: string;
     nickname?: string;
     email?: string;
     phone?: string;
     remark?: string;
     deptId?: number;
   }
+  interface CreateUserResp {
+    id: number;
+    credentialsTicket: string;
+    credentialsExpiresAt: number;
+  }
+  interface ResetPasswordReq {
+    userId: number;
+  }
+  interface ResetPasswordResp {
+    credentialsTicket: string;
+    credentialsExpiresAt: number;
+  }
+  interface FetchUserCredentialsReq {
+    ticket: string;
+  }
+  interface FetchUserCredentialsResp {
+    username: string;
+    password: string;
+  }
   interface UpdateUserReq {
     id: number;
     nickname?: string;
@@ -70,5 +88,5 @@ declare namespace API {
   }
   type UserListResult = ResultList<UserItem>;
   type UserDetailResult = Result<UserItem>;
-  type UserCreateResult = Result<{ id: number }>;
+  type UserCreateResult = Result<CreateUserResp>;
 }