Răsfoiți Sursa

feat: 内测版v0.5.0

await 1 zi în urmă
părinte
comite
4aef655122
44 a modificat fișierele cu 1834 adăugiri și 896 ștergeri
  1. 2 2
      .env
  2. 12 13
      .umirc.ts
  3. 6 0
      Dockerfile
  4. 13 0
      docker-app-compose.yml
  5. 39 0
      nginx.conf
  6. 2 0
      robots.txt
  7. 5 1
      src/access.ts
  8. 154 20
      src/app.tsx
  9. BIN
      src/assets/header-logo.png
  10. BIN
      src/assets/login-background.png
  11. 14 2
      src/models/global.ts
  12. 2 2
      src/pages/ChildAccount/components/UpdateForm.tsx
  13. 25 17
      src/pages/ChildAccount/index.tsx
  14. 2 2
      src/pages/Home/index.tsx
  15. 27 1
      src/pages/Login/index.less
  16. 177 88
      src/pages/Login/index.tsx
  17. 57 0
      src/pages/Order/Detail/DetailBuilder.ts
  18. 210 11
      src/pages/Order/Detail/index.tsx
  19. 387 0
      src/pages/Order/Mine/index.tsx
  20. 59 0
      src/pages/Order/components/ChildAccountModal.tsx
  21. 14 4
      src/pages/Order/components/TraceForm.tsx
  22. 30 0
      src/pages/Order/components/TraceHistoryContent.tsx
  23. 73 17
      src/pages/Order/components/TraceHistoryModal.tsx
  24. 14 0
      src/pages/Order/components/index.less
  25. 179 69
      src/pages/Order/index.tsx
  26. 48 7
      src/pages/ResetPassword/index.tsx
  27. 0 26
      src/pages/Table/components/CreateForm.tsx
  28. 0 142
      src/pages/Table/components/UpdateForm.tsx
  29. 0 266
      src/pages/Table/index.tsx
  30. 28 3
      src/services/child-account/ChildAccountController.ts
  31. 21 3
      src/services/child-account/typings.d.ts
  32. 0 93
      src/services/demo/UserController.ts
  33. 0 7
      src/services/demo/index.ts
  34. 0 91
      src/services/demo/typings.d.ts
  35. 1 1
      src/services/login/LoginController.ts
  36. 23 0
      src/services/login/typings.d.ts
  37. 35 3
      src/services/order-manage/OrderManage.ts
  38. 79 2
      src/services/order-manage/typings.d.ts
  39. 1 1
      src/services/request.ts
  40. 8 0
      src/services/system/SystemController.ts
  41. 15 0
      src/services/system/typings.d.ts
  42. 2 1
      src/utils/request.ts
  43. 8 1
      src/utils/storage.ts
  44. 62 0
      src/utils/system.ts

+ 2 - 2
.env

@@ -1,2 +1,2 @@
-UMI_APP_BASE_URL='http://192.168.1.25:851'
-PORT=3006
+UMI_APP_REQUEST_BASE_URL='http://apicrm.internal.jiebide.xin'
+PORT=3006

+ 12 - 13
.umirc.ts

@@ -7,7 +7,7 @@ export default defineConfig({
     initialState: {},
     request: {},
     layout: {
-        title: '新版商户后台',
+        title: '商户后台',
     },
 
     routes: [
@@ -30,40 +30,39 @@ export default defineConfig({
         {
             name: '订单管理',
             path: '/order',
+            icon: 'OrderedListOutlined',
             routes: [
                 {
-                    path: '/order/',
+                    path: 'all',
                     name: '全部订单',
                     component: './Order',
+                    access: 'canUseMainAccount',
                 },
                 {
-                    path: '/order/detail/:id',
+                    path: 'mine',
+                    name: '我的订单',
+                    component: './Order/Mine',
+                },
+                {
+                    path: 'detail/:id',
                     name: '详情',
                     component: './Order/Detail',
                     hideInMenu: true,
                 },
             ],
         },
-        {
-            name: '权限演示',
-            path: '/access',
-            component: './Access',
-        },
-        {
-            name: ' CRUD 示例',
-            path: '/table',
-            component: './Table',
-        },
         {
             name: '产品列表',
             path: '/product',
             component: './ProductList',
+            icon: 'AppstoreOutlined',
         },
         {
             name: '子账号管理',
             path: '/child_account',
             component: './ChildAccount',
             icon: 'TeamOutlined',
+            access: 'canUseMainAccount',
         },
         {
             name: '重置密码',

+ 6 - 0
Dockerfile

@@ -0,0 +1,6 @@
+FROM nginx:1.16
+
+ENV TZ="Asia/Shanghai"
+ENV NGINX_PORT=80
+EXPOSE ${NGINX_PORT}
+COPY dist/  /usr/share/nginx/html/

+ 13 - 0
docker-app-compose.yml

@@ -0,0 +1,13 @@
+version: '3'
+services:
+    loan-crm-mng: # 指定服务的名称
+        image: 'inde-merchants-ui:${biz_tag_name}' # 镜像名 .注意:${app_tag_name} 变量名不能带'-'符号,第一个'-'后会认为是默认值,而非变量名的一部分。
+        environment: # 环境变量
+            - VIRTUAL_HOST=crm.internal.jiebide.xin # nginx-proxy 所用域名
+        networks: # 网络 (用于容器间通信)
+            - nginx-proxy
+
+networks: # 定义网络
+    nginx-proxy:
+        external:
+            name: nginx-proxy

+ 39 - 0
nginx.conf

@@ -0,0 +1,39 @@
+worker_processes auto;
+
+events {
+    worker_connections 1024;
+}
+
+http {
+ include             mime.types;
+
+ default_type        application/octet-stream;
+
+ sendfile            on;
+
+ keepalive_timeout 65;
+
+ client_max_body_size 20m;
+
+    server {
+        listen       80;
+        server_name  localhost;
+
+        gzip on;
+        gzip_min_length 1k;
+        gzip_comp_level 9;
+        gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
+        gzip_vary on;
+
+        location / {
+            root   /usr/share/nginx/html;
+            index index.html index.html; #配置首页
+            try_files $uri $uri/ /index.html; #防止刷新报404
+        }
+        
+        error_page   500 502 503 504  /50x.html;
+        location = /50x.html {
+            root   html;
+        }
+    }
+ }

+ 2 - 0
robots.txt

@@ -0,0 +1,2 @@
+User-agent: *
+Disallow: /

+ 5 - 1
src/access.ts

@@ -1,8 +1,12 @@
-export default (initialState: API.UserInfo) => {
+export default (initialState: any) => {
     // 在这里按照初始化数据定义项目中的权限,统一管理
+    console.log({ initialState });
+
     // 参考文档 https://umijs.org/docs/max/access
     const canSeeAdmin = !!(initialState && initialState.name !== 'dontHaveAccess');
+    const canUseMainAccount = !!(initialState && initialState?.accountType === 'main');
     return {
         canSeeAdmin,
+        canUseMainAccount,
     };
 };

+ 154 - 20
src/app.tsx

@@ -1,13 +1,26 @@
 // 运行时配置
-import { EditOutlined, LogoutOutlined, UserOutlined } from '@ant-design/icons';
-import { history, useNavigate, type RequestConfig, type RunTimeLayoutConfig } from '@umijs/max';
-import { Avatar, Dropdown, message, type MenuProps } from 'antd';
-import { removeLocalStorageItem } from './utils/storage';
+import { CaretDownFilled, EditOutlined, LogoutOutlined, UserOutlined } from '@ant-design/icons';
+import {
+    history,
+    useAccess,
+    useModel,
+    useNavigate,
+    type RequestConfig,
+    type RunTimeLayoutConfig,
+} from '@umijs/max';
+import { Avatar, Dropdown, message, Space, Switch, Tag, theme, type MenuProps } from 'antd';
+import React, { useState } from 'react';
+import { useUpdateChildAccountReceiveStatus } from './services/child-account/ChildAccountController';
+import { getLocalStorageItem, removeLocalStorageItem, setLocalStorageItem } from './utils/storage';
+
+const { useToken } = theme;
 
 // 全局初始化数据配置,用于 Layout 用户信息和权限初始化
 // 更多信息见文档:https://umijs.org/docs/api/runtime-config#getinitialstate
-export async function getInitialState(): Promise<{ name: string }> {
-    return { name: '' };
+export async function getInitialState(): Promise<{ name: string; accountType: 'main' | 'child' }> {
+    // const { isMainAccount } = useModel('global');
+    const isMainAccount = getLocalStorageItem('IS_MAIN');
+    return { name: 'dontHaveAccess', accountType: isMainAccount ? 'main' : 'child' };
 }
 export const request: RequestConfig = {
     timeout: 5000,
@@ -26,7 +39,8 @@ export const request: RequestConfig = {
                 removeLocalStorageItem('LOGIN_NAME');
                 removeLocalStorageItem('MAIN_ACCOUNT');
                 removeLocalStorageItem('__BIZ_ID');
-                removeLocalStorageItem('IS_CHILD');
+                removeLocalStorageItem('IS_MAIN');
+                removeLocalStorageItem('RECEIVE_STATUS');
                 message.warning('登录票据已过期, 请重新登录');
                 history.replace('/login');
             }
@@ -37,6 +51,21 @@ export const request: RequestConfig = {
 
 export const layout: RunTimeLayoutConfig = () => {
     const navigate = useNavigate();
+    const { token } = useToken();
+    const access = useAccess();
+
+    console.log('access', access);
+
+    const contentStyle: React.CSSProperties = {
+        backgroundColor: token.colorBgElevated,
+        borderRadius: token.borderRadiusLG,
+        boxShadow: token.boxShadowSecondary,
+    };
+
+    const menuStyle: React.CSSProperties = {
+        boxShadow: 'none',
+    };
+
     const handleClickUpdatePassword = () => {
         navigate('/reset_pwd', { replace: true });
     };
@@ -44,16 +73,12 @@ export const layout: RunTimeLayoutConfig = () => {
     const handleClickLogout = () => {
         navigate('/login', { replace: true });
     };
+    const { name, isMainAccount, setReceiveStatus, receiveStatus } = useModel('global');
+    /** 用户头像区域 */
     const rightRender = () => {
+        // const [receiveStatus, setReceiveStatus] = useState<boolean>(false);
+        const [receiveSwitchLoadingState, setReceiveSwitchLoadingState] = useState<boolean>(false);
         const items: MenuProps['items'] = [
-            {
-                label: '用户01',
-                key: '-1',
-                disabled: true,
-            },
-            {
-                type: 'divider',
-            },
             {
                 label: '修改密码',
                 key: '0',
@@ -68,16 +93,125 @@ export const layout: RunTimeLayoutConfig = () => {
             },
         ];
         return (
-            <div>
-                <Dropdown menu={{ items }} trigger={['click']}>
-                    <Avatar shape="square" icon={<UserOutlined />} />
+            <Space>
+                {/* <Tooltip placement="left" title="更改接单状态">
+                    <Switch
+                        onClick={() => {
+                            setReceiveSwitchLoadingState(true);
+                            useUpdateChildAccountReceiveStatus({
+                                ids: [getLocalStorageItem('__BIZ_ID')],
+                                allocationStatus: !receiveStatus,
+                            })
+                                .then((res) => {
+                                    console.log('Update receive status result: ', res);
+                                    if (res.code === 0) {
+                                        setReceiveStatus(!receiveStatus);
+                                        message.success('已修改接单状态');
+                                    } else {
+                                        message.warning(res.msg);
+                                    }
+                                })
+                                .catch((err) => {
+                                    console.error('修改接单状态出错', err);
+                                    message.error('修改接单状态失败, 请重试');
+                                })
+                                .finally(() => {
+                                    setReceiveSwitchLoadingState(false);
+                                });
+                        }}
+                        loading={receiveSwitchLoadingState}
+                        checked={receiveStatus}
+                        checkedChildren="接单中"
+                        unCheckedChildren="暂停接单"
+                    ></Switch>
+                </Tooltip> */}
+                <Dropdown
+                    menu={{ items }}
+                    trigger={['click']}
+                    dropdownRender={(menu) => (
+                        <div style={contentStyle}>
+                            <Space
+                                size="small"
+                                style={{ padding: '4px 12px', userSelect: 'none', color: '#777' }}
+                                direction="vertical"
+                            >
+                                {isMainAccount ? null : (
+                                    <>
+                                        <div>接单状态</div>
+                                        <Switch
+                                            onClick={() => {
+                                                setReceiveSwitchLoadingState(true);
+                                                useUpdateChildAccountReceiveStatus({
+                                                    ids: [getLocalStorageItem('__BIZ_ID')],
+                                                    allocationStatus: !receiveStatus,
+                                                })
+                                                    .then((res) => {
+                                                        console.log(
+                                                            'Update receive status result: ',
+                                                            res,
+                                                        );
+                                                        if (res.code === 0) {
+                                                            setReceiveStatus(!receiveStatus);
+                                                            setLocalStorageItem(
+                                                                'RECEIVE_STATUS',
+                                                                !receiveStatus,
+                                                            );
+                                                            message.success('已修改接单状态');
+                                                        } else {
+                                                            message.warning(res.msg);
+                                                        }
+                                                    })
+                                                    .catch((err) => {
+                                                        console.error('修改接单状态出错', err);
+                                                        message.error('修改接单状态失败, 请重试');
+                                                    })
+                                                    .finally(() => {
+                                                        setReceiveSwitchLoadingState(false);
+                                                    });
+                                            }}
+                                            loading={receiveSwitchLoadingState}
+                                            checked={receiveStatus}
+                                            checkedChildren="接单中"
+                                            unCheckedChildren="暂停接单"
+                                        ></Switch>
+                                    </>
+                                )}
+                            </Space>
+                            {React.cloneElement(
+                                menu as React.ReactElement<{ style: React.CSSProperties }>,
+                                { style: menuStyle },
+                            )}
+                        </div>
+                    )}
+                >
+                    <Space style={{ cursor: 'pointer' }}>
+                        <Avatar shape="square" size="large" icon={<UserOutlined />} />
+                        <div style={{ display: 'flex', flexDirection: 'column' }}>
+                            <span style={{ flexGrow: '0', lineHeight: 1.5 }}>{name}</span>
+                            {isMainAccount ? (
+                                <Tag color="blue" style={{ transform: 'scale(0.95)' }}>
+                                    主账号
+                                </Tag>
+                            ) : (
+                                <Tag color="green" style={{ transform: 'scale(0.95)' }}>
+                                    子账号
+                                </Tag>
+                            )}
+                        </div>
+                        <CaretDownFilled />
+                    </Space>
                 </Dropdown>
-            </div>
+            </Space>
         );
     };
     return {
-        logo: 'https://img.alicdn.com/tfs/TB1YHEpwUT1gK0jSZFhXXaAtVXa-28-27.svg',
+        logo: require('./assets/header-logo.png'),
         layout: 'mix',
+        siderWidth: 208,
+        title: '',
+        breakpoint: 'sm',
+
+        // navTheme: 'realDark',
         rightContentRender: rightRender,
         menu: {
             locale: false,

BIN
src/assets/header-logo.png


BIN
src/assets/login-background.png


+ 14 - 2
src/models/global.ts

@@ -1,12 +1,24 @@
 // 全局共享数据示例
-import { DEFAULT_NAME } from '@/constants';
+import { getLocalStorageItem } from '@/utils/storage';
 import { useState } from 'react';
 
+const initialValueAccountName = getLocalStorageItem('LOGIN_NAME');
+const initialValueIsMainAccount = getLocalStorageItem('IS_MAIN');
+const initialValueReceiveStatus = getLocalStorageItem('RECEIVE_STATUS');
+
 const useUser = () => {
-    const [name, setName] = useState<string>(DEFAULT_NAME);
+    const crmName = '惠融易客';
+    const [name, setName] = useState<string>(initialValueAccountName);
+    const [isMainAccount, setIsMainAccount] = useState<boolean>(initialValueIsMainAccount);
+    const [receiveStatus, setReceiveStatus] = useState<boolean>(initialValueReceiveStatus);
     return {
         name,
         setName,
+        crmName,
+        isMainAccount,
+        setIsMainAccount,
+        receiveStatus,
+        setReceiveStatus,
     };
 };
 

+ 2 - 2
src/pages/ChildAccount/components/UpdateForm.tsx

@@ -10,9 +10,9 @@ import React from 'react';
 
 interface UpdateFormModalProps {
     updateModalVisible: boolean;
-    onCancel: (flag?: boolean) => void;
+    onCancel: () => void;
     onSubmit: (values: APIChildAccount.ChildAccountUpdateRequestParam) => Promise<void>;
-    values: APIChildAccount.ChildAccountUpdateRequestParam;
+    values: Partial<APIChildAccount.ChildAccountUpdateRequestParam>;
 }
 
 const UpdateForm: React.FC<UpdateFormModalProps> = (props) => {

+ 25 - 17
src/pages/ChildAccount/index.tsx

@@ -1,6 +1,6 @@
 import {
     useCreateChildAccount,
-    useFetchChildAccountList,
+    useFetchChildAccountListByPage,
     useGetChildAccountDetail,
     useUpdateChildAccount,
     useUpdateChildAccountStatus,
@@ -71,7 +71,7 @@ const handleUpdateStatus = async (param: APIChildAccount.ChildAccountUpdateStatu
     }
 };
 
-const SubAccountManage: React.FC = () => {
+const ChildAccountManage: React.FC = () => {
     const [updateFormVisible, handleUpdateFormVisible] = useState<boolean>(false);
     const [updateFormValue, setUpdateFormValue] = useState<
         Partial<APIChildAccount.ChildAccountUpdateRequestParam>
@@ -149,8 +149,8 @@ const SubAccountManage: React.FC = () => {
             dataIndex: 'receiveStatus',
             hideInForm: true,
             valueEnum: {
-                true: { text: '启', status: 'Success' },
-                false: { text: '禁用', status: 'Error' },
+                true: { text: '启', status: 'Success' },
+                false: { text: '关闭', status: 'Error' },
             },
         },
         {
@@ -159,8 +159,8 @@ const SubAccountManage: React.FC = () => {
             valueType: 'radio',
             hideInForm: true,
             valueEnum: {
-                '0': { text: '关闭', status: 'Error' },
-                '1': { text: '启', status: 'Success' },
+                '0': { text: '禁用', status: 'Error' },
+                '1': { text: '启', status: 'Success' },
             },
         },
         {
@@ -179,7 +179,9 @@ const SubAccountManage: React.FC = () => {
                         onClick={() => {
                             useGetChildAccountDetail({ id: record.id }).then((result) => {
                                 handleUpdateFormVisible(true);
-                                setUpdateFormValue(result.data);
+                                setUpdateFormValue(
+                                    result.data as APIChildAccount.ChildAccountUpdateRequestParam,
+                                );
                             });
                         }}
                     >
@@ -188,15 +190,15 @@ const SubAccountManage: React.FC = () => {
                     <a
                         style={{ color: 'geekblue' }}
                         onClick={async () => {
-                            const STATUS_MAP = {
+                            const STATUS_MAP: Record<string, string> = {
                                 '1': '0',
                                 '0': '1',
                             };
-                            const STATUS_TEXT_MAP = {
+                            const STATUS_TEXT_MAP: Record<string, string> = {
                                 '1': '启用',
                                 '0': '禁用',
                             };
-                            const statusWillBe: '0' | '1' = STATUS_MAP[record.status];
+                            const statusWillBe = STATUS_MAP[record.status];
                             const statusText: string = STATUS_TEXT_MAP[statusWillBe];
 
                             confirm({
@@ -215,7 +217,7 @@ const SubAccountManage: React.FC = () => {
                                 async onOk() {
                                     await useUpdateChildAccountStatus({
                                         id: record.id,
-                                        status: statusWillBe,
+                                        status: statusWillBe as APIChildAccount.ChildAccountStatusEnum,
                                     });
 
                                     if (actionRef.current) {
@@ -240,6 +242,7 @@ const SubAccountManage: React.FC = () => {
                 // @ts-ignore
                 columns={tableColumns}
                 actionRef={actionRef}
+                scroll={{ x: 'max-content' }}
                 toolBarRender={() => (
                     <Button
                         type="primary"
@@ -250,10 +253,13 @@ const SubAccountManage: React.FC = () => {
                     </Button>
                 )}
                 request={(params) =>
-                    useFetchChildAccountList().then((res) => {
+                    useFetchChildAccountListByPage({
+                        pageNum: params.current,
+                        pageSize: params.pageSize,
+                    }).then((res) => {
                         const result = {
-                            data: res.data,
-                            total: res.data ? res.data.length : 0,
+                            data: res.data && res.data.list,
+                            total: res.data && res.data.total,
                             success: true,
                         };
                         return result;
@@ -269,8 +275,10 @@ const SubAccountManage: React.FC = () => {
                     setUpdateFormValue({});
                 }}
                 onSubmit={async (values) => {
-                    values.confirmPassword = md5Encrypt(values.confirmPassword);
-                    values.contractPassword = md5Encrypt(values.contractPassword);
+                    if (values.confirmPassword && values.contractPassword) {
+                        values.confirmPassword = md5Encrypt(values.confirmPassword);
+                        values.contractPassword = md5Encrypt(values.contractPassword);
+                    }
                     const updateRes = await useUpdateChildAccount(values);
                     if (updateRes.code === 0) {
                         message.success(updateRes.msg);
@@ -401,4 +409,4 @@ const SubAccountManage: React.FC = () => {
     );
 };
 
-export default SubAccountManage;
+export default ChildAccountManage;

+ 2 - 2
src/pages/Home/index.tsx

@@ -5,11 +5,11 @@ import { useModel } from '@umijs/max';
 import styles from './index.less';
 
 const HomePage: React.FC = () => {
-    const { name } = useModel('global');
+    const { crmName } = useModel('global');
     return (
         <PageContainer ghost>
             <div className={styles.container}>
-                <Guide name={trim(name)} />
+                <Guide name={trim(crmName)} />
             </div>
         </PageContainer>
     );

+ 27 - 1
src/pages/Login/index.less

@@ -4,8 +4,34 @@
     width: 100vw;
     height: 100vh;
     overflow: hidden;
+    background-image: url('../../assets/login-background.png');
+    background-repeat: no-repeat;
+    background-size: cover;
 
     .title {
-        font-size: 32px;
+        font-size: 24px;
+        text-align: left;
+    }
+
+    .admin_login_title {
+        font-size: 16px;
+        color: rgba(20, 23, 47, 0.6);
+        padding: 0 0 32px;
+    }
+
+    .sms_login_title {
+        font-size: 16px;
+        color: rgba(20, 23, 47, 0.6);
+    }
+
+    .login_panel_wrapper {
+        width: 460px;
+        background-color: #fff;
+        box-sizing: border-box;
+        padding: 24px 40px 110px;
+        border-radius: 12px;
+        @media screen and (max-width: 768px) {
+            width: 340px;
+        }
     }
 }

+ 177 - 88
src/pages/Login/index.tsx

@@ -1,11 +1,12 @@
 import { useAdminLogin, useSendSmsCode, useSmsLogin } from '@/services/login/LoginController';
+import { useFetchSystemDictionary } from '@/services/system/SystemController';
 import { AesEncode } from '@/utils/crypto';
 import { setLocalStorageItem } from '@/utils/storage';
-import { LockOutlined, UserOutlined } from '@ant-design/icons';
-import { useNavigate } from '@umijs/max';
-import { Button, Divider, Flex, Form, Input, message, Space } from 'antd';
-import md5 from 'js-md5';
-import React, { useState } from 'react';
+import { LockOutlined, MailOutlined, UserOutlined } from '@ant-design/icons';
+import { useModel, useNavigate } from '@umijs/max';
+import { Button, Divider, Flex, Form, Input, message } from 'antd';
+import { md5 } from 'js-md5';
+import React, { useEffect, useRef, useState } from 'react';
 import style from './index.less';
 
 type FieldType = {
@@ -20,7 +21,7 @@ type AdminLoginForm = {
 };
 
 type AdminLoginFormProps = {
-    loginCallback: () => void;
+    loginCallback: (param: PublicResponseBody<any>) => void;
     formCallback: (param: AdminLoginForm) => void;
 };
 
@@ -29,50 +30,72 @@ const AdminLoginForm: React.FC<AdminLoginFormProps> = ({ loginCallback, formCall
     const [form] = Form.useForm();
     const [loadingState, setLoadingState] = useState(false);
 
-    const useLoginDeep = async () => {
-        try {
-            setLoadingState(true);
-            const result = await useAdminLogin({
-                username: AesEncode(form.getFieldValue('username')),
-                // username: form.getFieldValue('username'),
-                password: AesEncode(md5(form.getFieldValue('password')).toUpperCase()),
-            });
-
-            setLoadingState(false);
-            console.log('登录结果', result);
-            // @ts-ignore
-            loginCallback(result);
-            // @ts-ignore
-            formCallback(
+    const useLoginDeep = () => {
+        form.validateFields().then(async () => {
+            try {
+                setLoadingState(true);
+                const result = await useAdminLogin({
+                    username: AesEncode(form.getFieldValue('username')),
+                    password: AesEncode(md5(form.getFieldValue('password')).toUpperCase()),
+                });
+
+                setLoadingState(false);
+                console.log('登录结果', result);
                 // @ts-ignore
-                Object.assign({}, result.data, {
-                    username: form.getFieldValue('username'),
-                    password: form.getFieldValue('password'),
-                }),
-            );
-        } catch (error) {
-            setLoadingState(false);
-            message.error('登录错误, 请重试');
-        }
+                loginCallback(result);
+                // @ts-ignore
+                formCallback(
+                    // @ts-ignore
+                    Object.assign({}, result.data, {
+                        username: form.getFieldValue('username'),
+                        password: form.getFieldValue('password'),
+                    }),
+                );
+            } catch (error) {
+                setLoadingState(false);
+                message.error('登录错误, 请重试');
+            }
+        });
     };
 
     return (
         <>
-            <Form form={form} name="login" style={{ maxWidth: 600, width: '100%' }}>
-                <Form.Item<FieldType> name="username">
+            <div className={style.admin_login_title}>账号登录</div>
+            <Form
+                form={form}
+                name="login"
+                style={{ maxWidth: 600, width: '100%', paddingBottom: '24px' }}
+            >
+                <Form.Item<FieldType>
+                    name="username"
+                    rules={[{ required: true, message: '请输入账号' }]}
+                >
                     <Input
+                        style={{
+                            backgroundColor: '#f6f7fb',
+                            border: '0.5px solid #14172f inset',
+                            fontSize: '12px',
+                        }}
                         size="large"
                         prefix={<UserOutlined style={{ color: 'rgba(0, 0, 0, 0.25)' }} />}
-                        placeholder="Please input username"
+                        placeholder="请输入您的账号"
                         allowClear
                     />
                 </Form.Item>
 
-                <Form.Item<FieldType> name="password">
+                <Form.Item<FieldType>
+                    name="password"
+                    rules={[{ required: true, message: '请输入密码' }]}
+                >
                     <Input.Password
+                        style={{
+                            backgroundColor: '#f6f7fb',
+                            border: '0.5px solid #14172f inset',
+                            fontSize: '12px',
+                        }}
                         size="large"
                         prefix={<LockOutlined style={{ color: 'rgba(0, 0, 0, 0.25)' }} />}
-                        placeholder="Please input password"
+                        placeholder="请输入您的密码"
                         allowClear
                     />
                 </Form.Item>
@@ -85,7 +108,7 @@ const AdminLoginForm: React.FC<AdminLoginFormProps> = ({ loginCallback, formCall
                     onClick={useLoginDeep}
                     loading={loadingState}
                 >
-                    登录
+                    下一步
                 </Button>
             </Form>
         </>
@@ -96,7 +119,7 @@ type SmsLoginFormProps = {
     username: string;
     password: string;
     phone: string;
-    loginCallback: () => void;
+    loginCallback: (param: PublicResponseBody<any>) => void;
 };
 
 /** 短信验证码区域组件 */
@@ -106,11 +129,19 @@ const SmsLoginForm: React.FC<SmsLoginFormProps> = ({
     phone,
     loginCallback,
 }) => {
-    const [smsCode, setSmsCode] = useState<string>('');
     const [smsCountDown, setSmsCountDown] = useState<number>(-1);
     const [loadingState, setLoadingState] = useState(false);
     const [form] = Form.useForm();
-    let smsCountDownInterval = null;
+    let timerRef = useRef<NodeJS.Timeout | null>(null);
+
+    useEffect(() => {
+        return () => {
+            if (timerRef.current) {
+                clearInterval(timerRef.current);
+            }
+        };
+    }, []);
+
     const sendSmsCode = async () => {
         console.log({ username, password });
         const result = await useSendSmsCode({
@@ -120,11 +151,18 @@ const SmsLoginForm: React.FC<SmsLoginFormProps> = ({
 
         if (result.code === 0) {
             setSmsCountDown(60);
-            smsCountDownInterval = setInterval(() => {
-                setSmsCountDown((prev) => prev - 1);
-                if (smsCountDown === -1) {
-                    clearInterval(smsCountDownInterval);
-                }
+            if (timerRef.current) {
+                clearInterval(timerRef.current);
+            }
+            timerRef.current = setInterval(() => {
+                setSmsCountDown((prev) => {
+                    const nextCount = prev - 1;
+                    if (nextCount <= 0) {
+                        clearInterval(timerRef.current!);
+                        return -1;
+                    }
+                    return nextCount;
+                });
             }, 1000);
             message.success(result.msg);
         } else {
@@ -134,45 +172,62 @@ const SmsLoginForm: React.FC<SmsLoginFormProps> = ({
         console.log('发送验证码结果', result);
     };
 
-    const confirmLogin = async () => {
-        setLoadingState(true);
-
-        console.log('formapds', form.getFieldValue('smsCode'));
-
-        try {
-            const res = await useSmsLogin({
-                username: AesEncode(username),
-                password: AesEncode(md5(password).toUpperCase()),
-                smsCode: AesEncode(form.getFieldValue('smsCode')),
-            });
-
-            console.log('短信登录结果', res);
-
-            setLoadingState(false);
-
-            loginCallback(res);
-        } catch (error) {
-            setLoadingState(false);
-            message.error('登录错误, 请重试');
-        }
+    const confirmLogin = () => {
+        form.validateFields().then(async () => {
+            setLoadingState(true);
+            console.log('formapds', form.getFieldValue('smsCode'));
+            try {
+                const res = await useSmsLogin({
+                    username: AesEncode(username),
+                    password: AesEncode(md5(password).toUpperCase()),
+                    smsCode: AesEncode(form.getFieldValue('smsCode')),
+                });
+                console.log('短信登录结果', res);
+                setLoadingState(false);
+                loginCallback(res);
+            } catch (error) {
+                setLoadingState(false);
+                message.error('登录错误, 请重试');
+            }
+        });
     };
 
     return (
-        <Flex justify="center" align="center" vertical gap={16}>
-            短信验证码已发送至手机: {phone}, 请输入:
-            <Form form={form}>
-                <Form.Item name="smsCode">
-                    <Space size="large">
-                        <Input maxLength={4} placeholder="请输入短信验证码" />
-                        {/* <Input placeholder="请输入短信验证码" allowClear></Input> */}
-                        <Button type="primary" disabled={smsCountDown > 0} onClick={sendSmsCode}>
-                            {smsCountDown < 0 ? '点击获取验证码' : `${smsCountDown}s后重新发送`}
-                        </Button>
-                    </Space>
+        <Flex justify="center" align="flex-start" vertical gap={16} style={{ width: '100%' }}>
+            <span className={style['sms_login_title']}>请输入绑定手机({phone})短信验证码</span>
+            <Form form={form} style={{ width: '100%' }}>
+                <Form.Item
+                    name="smsCode"
+                    rules={[{ required: true, message: '短信验证码不可为空' }]}
+                >
+                    <Input
+                        prefix={<MailOutlined />}
+                        maxLength={4}
+                        placeholder="请输入短信验证码"
+                        name="smsCode"
+                        style={{
+                            backgroundColor: '#f6f7fb',
+                            fontSize: '12px',
+                            height: '40px',
+                            boxSizing: 'border-box',
+                            width: '100%',
+                        }}
+                    />
                 </Form.Item>
+                <Flex justify="flex-end">
+                    <Button type="primary" disabled={smsCountDown > 0} onClick={sendSmsCode}>
+                        {smsCountDown < 0 ? '点击获取验证码' : `${smsCountDown}s后重新发送`}
+                    </Button>
+                </Flex>
             </Form>
-            <Button type="primary" onClick={confirmLogin} block loading={loadingState}>
-                确认登录
+            <Button
+                type="primary"
+                onClick={confirmLogin}
+                block
+                loading={loadingState}
+                style={{ height: '40px' }}
+            >
+                登录
             </Button>
         </Flex>
     );
@@ -189,7 +244,7 @@ const LoginWrapper = ({ loginCompleteCallback }) => {
     const [password, setPassword] = useState('');
     const [phone, setPhone] = useState('');
 
-    const syncFormInformation = (form) => {
+    const syncFormInformation = (form: AdminLoginForm) => {
         console.log('同步登录表单信息', form);
         if (form) {
             setUsername(form.username);
@@ -202,12 +257,15 @@ const LoginWrapper = ({ loginCompleteCallback }) => {
         console.log('子组件触发父组件事件', data);
 
         if ([0, 3].includes(data.code)) {
-            const { mainAccount, main, loginName, token, admUserId } = data.data;
+            const { mainAccount, main, token, admUserId, admUserName, receiveStatus } = data.data;
             setLocalStorageItem('__BIZ_ID', admUserId);
             setLocalStorageItem('TOKEN', token);
-            setLocalStorageItem('IS_CHILD', !main);
+            setLocalStorageItem('IS_MAIN', main);
             setLocalStorageItem('MAIN_ACCOUNT', mainAccount);
-            setLocalStorageItem('LOGIN_NAME', loginName);
+            setLocalStorageItem('LOGIN_NAME', admUserName);
+            if (!main) {
+                setLocalStorageItem('RECEIVE_STATUS', receiveStatus);
+            }
             loginCompleteCallback(data);
         } else if ([2].includes(data.code)) {
             setSmsAreaVisible(true);
@@ -231,21 +289,52 @@ const LoginWrapper = ({ loginCompleteCallback }) => {
 
 const LoginPage: React.FC = () => {
     const navigate = useNavigate();
+    const { setIsMainAccount, setName, setReceiveStatus } = useModel('global');
+    const { refresh } = useModel('@@initialState');
 
-    const onLoginPass = (data) => {
-        console.log('接收到跳转信息', data);
+    const onLoginPass = async (data: PublicResponseBody<any>) => {
+        setName(data.data.admUserName);
+        setIsMainAccount(data.data.main);
+        setReceiveStatus(data.data.receiveStatus);
+
+        await refresh();
+
+        // 在登录成功后获取一次系统字典
+        useFetchSystemDictionary().then((res) => {
+            console.log('System dictionary', res);
+            // 整理字典数据
+            const dictionary: Record<string, Record<string, string>> = {};
+            res.data &&
+                res.data.forEach(({ type, code, msg }) => {
+                    if (!(type in dictionary)) {
+                        dictionary[type] = {};
+                    }
+                    dictionary[type][code] = msg;
+                });
+            setLocalStorageItem('SYSTEM_DICTIONARY', dictionary);
+        });
         navigate('/home', { replace: true });
     };
     return (
-        <Flex className={style.container} align="center" justify="center">
+        <Flex
+            className={style.container}
+            align="center"
+            justify="center"
+            style={{ minHeight: '100vh' }} // 确保登录页面占满整个视口高度
+        >
             <Flex
-                align="center"
+                align="flex-start" // 调整为center以获得更好的垂直对齐效果
                 justify="center"
                 vertical
-                style={{ width: '400px', height: '100vh' }}
+                className={style['login_panel_wrapper']}
+                style={{
+                    padding: '2rem', // 增加内边距提升视觉体验
+                    borderRadius: '8px', // 添加圆角使面板更现代
+                    boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)', // 添加阴影提升层次感
+                }}
             >
                 <div className={style.title}>惠融易客商户后台</div>
-                <Divider />
+                <Divider style={{ margin: '1.5rem 0' }} /> {/* 调整分割线间距 */}
                 <LoginWrapper loginCompleteCallback={onLoginPass} />
             </Flex>
         </Flex>

+ 57 - 0
src/pages/Order/Detail/DetailBuilder.ts

@@ -0,0 +1,57 @@
+import { handleSetToDictionary } from '@/utils/system';
+
+export interface OrderDetailPresent {
+    orderNo: string;
+    userName: string;
+    age: number;
+    star: number | null;
+    loanAmount: string;
+    borrowLimit: string;
+    phone: string;
+    wechat: string | null;
+    city: string;
+    educationCode: string | null;
+    socialSecurity: string | null;
+    accumulation: string | null;
+    sesame: string | null;
+    profession: string | null;
+    monthlyIncome: string | null;
+    salaryPayment: string | null;
+    carType: string | null;
+    houseType: string | null;
+    insurance: string | null;
+    carNo: string | null;
+    houseCity: string | null;
+    buyCarWay: string | null;
+    housePurchaseMethod: string | null;
+}
+export default function (param): Partial<OrderDetailPresent> {
+    const data: Partial<OrderDetailPresent> = {};
+    data.userName = param.userName;
+    data.age = param.age;
+    data.wechat = param.wxCode;
+    data.city = param.city;
+    data.star = param.star;
+    data.phone = param.phone;
+    data.carNo = param.carNo;
+    data.loanAmount = param.loanAmount;
+    data.orderNo = param.orderNo;
+
+    const translatedData = handleSetToDictionary(param, [
+        'houseSituation',
+        'carSituation',
+        'fund',
+        'career',
+        'socialSecurity',
+        'monthIncome',
+        'zhima',
+        'salaryType',
+        'insurance',
+        'education',
+        'loanTerm',
+    ]);
+
+    Object.assign(data, translatedData);
+
+    return data;
+}

+ 210 - 11
src/pages/Order/Detail/index.tsx

@@ -1,22 +1,221 @@
 import { useFetchOrderDetail } from '@/services/order-manage/OrderManage';
-import { PageContainer } from '@ant-design/pro-components';
-import { useParams } from '@umijs/max';
-import { Skeleton } from 'antd';
+import { RollbackOutlined } from '@ant-design/icons';
+import { PageContainer, ProDescriptions } from '@ant-design/pro-components';
+import { useNavigate, useParams } from '@umijs/max';
+import { Button, Card, message, Rate, Skeleton, Space } from 'antd';
 import { useEffect, useState } from 'react';
+import TraceForm from '../components/TraceForm';
+import TraceHistoryModal from '../components/TraceHistoryModal';
+import DetailBuilder, { OrderDetailPresent } from './DetailBuilder';
 
 const OrderDetail = () => {
-    const { id } = useParams<{ id: string }>();
+    const navigate = useNavigate();
+    const { id } = useParams<{ id: string }>() as any;
     const [loadingState, setLoadingState] = useState<boolean>(true);
+    const [traceFormVisible, setTraceFormVisible] = useState<boolean>(false);
+    const [traceHistoryModalVisible, setTraceHistoryModalVisible] = useState<boolean>(false);
+    const [traceHistoryOrderInfo, setTraceHistoryOrderInfo] = useState<
+        Partial<{
+            userName: string;
+            loanAmount: string;
+            loanTerm: string;
+            age: number;
+            orderNo: string;
+        }>
+    >({});
+
+    const [detailDocument, setDetailDocument] = useState<Partial<OrderDetailPresent>>({});
     useEffect(() => {
-        useFetchOrderDetail({ id }).then((res) => {
-            console.log(res);
-        });
+        useFetchOrderDetail({ id })
+            .then((res) => {
+                console.log(res);
+                setLoadingState(false);
+
+                // @ts-ignore
+                const { orderInfoVO, custAssertInfoVO } = res.data;
+
+                const detail = DetailBuilder(Object.assign({}, orderInfoVO, custAssertInfoVO));
+
+                console.log(detail);
+                setDetailDocument(detail);
+            })
+            .catch((error) => {
+                console.error('Error occurred during fetching order detail information: ', error);
+                message.error('获取订单详情出错, 请稍后重试');
+                sessionStorage.removeItem('DETAIL_ORDER_NO');
+                navigate(-1);
+            });
     }, []);
     return (
-        <PageContainer title="订单详情" subTitle="展示订单信息与客户信息">
-            <Skeleton paragraph={{ rows: 4 }} active></Skeleton>
-            <Skeleton paragraph={{ rows: 4 }} active></Skeleton>
-            <Skeleton paragraph={{ rows: 4 }} active></Skeleton>
+        <PageContainer
+            title="订单详情"
+            subTitle="展示订单信息与客户信息"
+            footer={[
+                <Space key="1">
+                    <Button
+                        icon={<RollbackOutlined />}
+                        color="blue"
+                        variant="solid"
+                        block
+                        onClick={() => {
+                            sessionStorage.removeItem('DETAIL_ORDER_NO');
+                            navigate(-1);
+                        }}
+                    >
+                        返回
+                    </Button>
+                    <Button
+                        color="green"
+                        variant="solid"
+                        autoInsertSpace={false}
+                        onClick={() => {
+                            setTraceFormVisible(true);
+                        }}
+                    >
+                        跟进
+                    </Button>
+                    <Button
+                        color="gold"
+                        variant="solid"
+                        onClick={() => {
+                            setTraceHistoryOrderInfo({
+                                age: detailDocument.age,
+                                loanAmount: detailDocument.loanAmount,
+                                loanTerm: detailDocument.borrowLimit,
+                                orderNo: detailDocument.orderNo,
+                                userName: detailDocument.userName,
+                            });
+                            setTraceHistoryModalVisible(true);
+                        }}
+                    >
+                        跟进记录
+                    </Button>
+                </Space>,
+            ]}
+        >
+            {loadingState ? (
+                <div>
+                    <Skeleton paragraph={{ rows: 4 }} active></Skeleton>
+                    <Skeleton paragraph={{ rows: 4 }} active></Skeleton>
+                    <Skeleton paragraph={{ rows: 4 }} active></Skeleton>
+                </div>
+            ) : (
+                <Space direction="vertical">
+                    <Card>
+                        <ProDescriptions column={{ xs: 1, xl: 3 }} title="订单信息">
+                            <ProDescriptions.Item label="姓名">
+                                {detailDocument.userName}
+                            </ProDescriptions.Item>
+                            <ProDescriptions.Item label="年龄">
+                                {detailDocument.age}
+                            </ProDescriptions.Item>
+                            <ProDescriptions.Item label="客户星级">
+                                {detailDocument.star === null ? (
+                                    '无星级'
+                                ) : (
+                                    <Rate defaultValue={detailDocument.star} disabled />
+                                )}
+                            </ProDescriptions.Item>
+                            <ProDescriptions.Item label="需求资金">
+                                {detailDocument.loanAmount}
+                            </ProDescriptions.Item>
+                            <ProDescriptions.Item label="需求周期">
+                                {detailDocument.borrowLimit}
+                            </ProDescriptions.Item>
+                        </ProDescriptions>
+                    </Card>
+                    <Card>
+                        <ProDescriptions column={{ xs: 1, xl: 3 }} title="联系方式">
+                            <ProDescriptions.Item label="电话">
+                                {detailDocument.phone}
+                            </ProDescriptions.Item>
+                            <ProDescriptions.Item label="微信">
+                                {detailDocument.wechat}
+                            </ProDescriptions.Item>
+                        </ProDescriptions>
+                    </Card>
+                    <Card>
+                        <ProDescriptions column={{ xs: 1, xl: 3 }} title="基础信息">
+                            <ProDescriptions.Item label="工作所在地">
+                                {detailDocument.city}
+                            </ProDescriptions.Item>
+                            <ProDescriptions.Item label="文化程度">
+                                {detailDocument.educationCode}
+                            </ProDescriptions.Item>
+                            <ProDescriptions.Item label="持有证书"></ProDescriptions.Item>
+                            <ProDescriptions.Item label="社保情况">
+                                {detailDocument.socialSecurity}
+                            </ProDescriptions.Item>
+                            <ProDescriptions.Item label="公积金情况" span={2}>
+                                {detailDocument.accumulation}
+                            </ProDescriptions.Item>
+                            <ProDescriptions.Item label="微粒贷额度"></ProDescriptions.Item>
+                            <ProDescriptions.Item label="芝麻分区间">
+                                {detailDocument.sesame}
+                            </ProDescriptions.Item>
+                        </ProDescriptions>
+                    </Card>
+                    <Card>
+                        <ProDescriptions column={{ xs: 1, xl: 3 }} title="职业信息">
+                            <ProDescriptions.Item label="职业">
+                                {detailDocument.profession}
+                            </ProDescriptions.Item>
+                            <ProDescriptions.Item label="月收入">
+                                {detailDocument.monthlyIncome}
+                            </ProDescriptions.Item>
+                            <ProDescriptions.Item label="工资支付方式">
+                                {detailDocument.salaryPayment}
+                            </ProDescriptions.Item>
+                        </ProDescriptions>
+                    </Card>
+                    <Card>
+                        <ProDescriptions column={{ xs: 1, xl: 3 }} title="资产信息">
+                            <ProDescriptions.Item label="车产">
+                                {detailDocument.carType}
+                            </ProDescriptions.Item>
+                            <ProDescriptions.Item label="房产">
+                                {detailDocument.houseType}
+                            </ProDescriptions.Item>
+                            <ProDescriptions.Item label="寿险保单">
+                                投保1年及以上
+                            </ProDescriptions.Item>
+                            <ProDescriptions.Item label="车牌号">
+                                {detailDocument.carNo}
+                            </ProDescriptions.Item>
+                            <ProDescriptions.Item label="房产所在城市">
+                                {detailDocument.city}
+                            </ProDescriptions.Item>
+                            <ProDescriptions.Item label="其他"></ProDescriptions.Item>
+                            <ProDescriptions.Item label="购车方式">
+                                {detailDocument.buyCarWay}
+                            </ProDescriptions.Item>
+                            <ProDescriptions.Item label="购房方式" span={2}>
+                                {detailDocument.housePurchaseMethod}
+                            </ProDescriptions.Item>
+                            <ProDescriptions.Item label="场地"></ProDescriptions.Item>
+                        </ProDescriptions>
+                    </Card>
+                </Space>
+            )}
+
+            <TraceForm
+                orderNo={sessionStorage.getItem('DETAIL_ORDER_NO') as string}
+                modalVisible={traceFormVisible}
+                onCancel={() => {
+                    setTraceFormVisible(false);
+                }}
+                onSubmit={(params) => {
+                    console.log('receive param from trace component: ', params);
+                }}
+            />
+
+            <TraceHistoryModal
+                modalVisible={traceHistoryModalVisible}
+                orderInfo={traceHistoryOrderInfo}
+                onCancel={() => {
+                    setTraceHistoryModalVisible(false);
+                }}
+            ></TraceHistoryModal>
         </PageContainer>
     );
 };

+ 387 - 0
src/pages/Order/Mine/index.tsx

@@ -0,0 +1,387 @@
+import { useFetchChildAccountList } from '@/services/child-account/ChildAccountController';
+import {
+    useAllocateOrder,
+    useFetchAllOrderList,
+    useFetchOrderFollowHistory,
+} from '@/services/order-manage/OrderManage';
+import { CheckCircleFilled, ClockCircleFilled, PhoneFilled, WechatFilled } from '@ant-design/icons';
+import { ProColumns, ProTable } from '@ant-design/pro-components';
+import { history, useAccess } from '@umijs/max';
+import { Button, Descriptions, Divider, message, Rate, Select, Space, Tag } from 'antd';
+import React, { useEffect, useMemo, useState } from 'react';
+import ChildAccountModal from '../components/ChildAccountModal';
+import TraceForm from '../components/TraceForm';
+import TraceHistoryContent from '../components/TraceHistoryContent';
+import TraceHistoryModal from '../components/TraceHistoryModal';
+
+import '../index.less';
+
+const handleFetchOrderDetail = async (id: number, orderNo: string) => {
+    sessionStorage.setItem('DETAIL_ORDER_NO', orderNo);
+    history.push(`/order/detail/${id}`);
+};
+
+const handleFetchOrderFollowHistory = async (orderNo: string) => {
+    try {
+        const res = await useFetchOrderFollowHistory({ orderNo });
+        return res.data;
+    } catch (error) {
+        return [];
+    }
+};
+
+const handleFetchChildAccountList = async () => {
+    try {
+        const res = await useFetchChildAccountList();
+        return res.data;
+    } catch (error) {
+        return [];
+    }
+};
+
+const transformLoanTerm = (loanTerm: string) => {
+    const map: Record<string, string> = {
+        '1': '1个月',
+        '2': '3个月',
+        '3': '6个月',
+        '4': '9个月',
+        '5': '12个月',
+        '6': '24个月',
+        '7': '36个月',
+        '8': '36个月以上',
+    };
+
+    return map[loanTerm];
+};
+
+const OrderManage: React.FC = () => {
+    const access = useAccess();
+    const [traceFormVisble, setTraceFormVisible] = useState<boolean>(false);
+    const [traceHistoryModalVisible, setTraceHistoryModalVisble] = useState<boolean>(false);
+    const [traceHistoryOrderInfo, setTraceHistoryOrderInfo] = useState<
+        Partial<{
+            userName: string;
+            loanAmount: string;
+            loanTerm: string;
+            age: number;
+        }>
+    >({});
+    const [childAccountList, setChildAccountList] =
+        useState<APIChildAccount.FetchChildAccountListResponse>([]);
+    const [traceHistoryModalLoadingState, setTraceHistoryModalLoadingState] =
+        useState<boolean>(false);
+    const [traceHistoryList, setTraceHistoryList] =
+        useState<APIOrderManage.FetchOrderFollowHistoryListResponse>([]);
+    /** 所选行orderNo */
+    const [selectedRowsState, setSelectedRowsState] = useState<string[]>([]);
+
+    const [allocateLoadingState, setAllocateLoadingState] = useState<boolean>(false);
+
+    const [targetOrderId, setTargetOrderId] = useState<string>('');
+
+    const [chlidAccountModalVisble, setChildAccountModalVisble] = useState<boolean>(false);
+
+    const tableColumns: ProColumns[] = [
+        {
+            title: '订单号',
+            dataIndex: 'orderNo',
+            hideInForm: true,
+            minWidth: 140,
+            copyable: true,
+        },
+        {
+            title: '客户姓名',
+            width: 100,
+            dataIndex: 'userName',
+        },
+        {
+            title: '客户星级',
+            hideInForm: true,
+            valueEnum: {
+                0: { text: '0星级' },
+                1: { text: '1星级' },
+                2: { text: '2星级' },
+                3: { text: '3星级' },
+                4: { text: '4星级' },
+                5: { text: '5星级' },
+            },
+            minWidth: 80,
+            render: (_, row) => {
+                if (row.star === null) {
+                    return '无星级';
+                } else {
+                    return <Rate defaultValue={row.star} disabled />;
+                }
+            },
+        },
+        { title: '手机号', dataIndex: 'maskPhone', minWidth: 140 },
+        { title: '微信号', width: 100, dataIndex: 'wxCode', hideInSearch: true },
+        {
+            title: '优先联系方式',
+            width: 100,
+            dataIndex: 'headContractType',
+            hideInSearch: true,
+            render(_, entity) {
+                return (
+                    <>
+                        <Space>
+                            {entity.headContractType === '电话' ? (
+                                <PhoneFilled style={{ color: '#51b262' }} />
+                            ) : (
+                                <WechatFilled style={{ color: '#5fcf73' }} />
+                            )}
+                            <span>{entity.headContractType}</span>
+                        </Space>
+                    </>
+                );
+            },
+        },
+        { title: '申请产品', dataIndex: 'productName', minWidth: 120, hideInSearch: true },
+        { title: '需求资金', dataIndex: 'loanAmount', width: 100, hideInSearch: true },
+        {
+            title: '贷款期限',
+            dataIndex: 'loanTerm',
+            width: 100,
+            hideInSearch: true,
+            valueEnum: {
+                '1': { text: '1个月' },
+                '2': { text: '3个月' },
+                '3': { text: '6个月' },
+                '4': { text: '9个月' },
+                '5': { text: '12个月' },
+                '6': { text: '24个月' },
+                '7': { text: '36个月' },
+                '8': { text: '36个月以上' },
+            },
+        },
+        {
+            title: '申请时间',
+            dataIndex: 'applyTime',
+            minWidth: 100,
+            valueType: 'dateRange',
+            filteredValue: null,
+            render(dom, entity) {
+                return entity.applyTime;
+            },
+        },
+        {
+            title: '分配子账号',
+            dataIndex: 'belongName',
+            minWidth: 100,
+            renderFormItem: () => (
+                <Select placeholder="选择分配子账号">
+                    {childAccountList.map((account) => (
+                        <Select.Option value={account.id}>{account.contractName}</Select.Option>
+                    ))}
+                </Select>
+            ),
+        },
+        {
+            title: '跟进状态',
+            dataIndex: 'followStatus',
+            valueEnum: {
+                0: { text: '待跟进' },
+                4: { text: '已跟进' },
+            },
+            minWidth: 100,
+            render: (_, record) => {
+                const FollowStatusTextMap = {
+                    0: '待跟进',
+                    4: '已跟进',
+                };
+                return (
+                    <Tag color={record.followStatus === '4' ? '#72b97a' : '#3875f6'}>
+                        <Space size="small">
+                            {record.followStatus === '4' ? (
+                                <CheckCircleFilled />
+                            ) : (
+                                <ClockCircleFilled />
+                            )}
+                            {/* @ts-ignore */}
+                            {FollowStatusTextMap[record.followStatus]}
+                        </Space>
+                    </Tag>
+                );
+            },
+        },
+        { title: '备注', dataIndex: 'remark', width: 200, ellipsis: true, hideInSearch: true },
+        {
+            title: '操作',
+            fixed: window.innerWidth > 420 ? 'right' : undefined,
+            hideInSearch: true,
+            render: (_, row) => {
+                return (
+                    <>
+                        <Space>
+                            <Button
+                                size="small"
+                                color="primary"
+                                variant="solid"
+                                autoInsertSpace={false}
+                                onClick={() => {
+                                    handleFetchOrderDetail(row.id, row.orderNo);
+                                }}
+                            >
+                                详情
+                            </Button>
+                            <Button
+                                style={{ backgroundColor: '#59bb73', color: '#fff' }}
+                                size="small"
+                                color="green"
+                                variant="solid"
+                                autoInsertSpace={false}
+                                onClick={() => {
+                                    setTargetOrderId(row.orderNo);
+                                    setTraceFormVisible(true);
+                                }}
+                            >
+                                跟进
+                            </Button>
+                            <Button
+                                size="small"
+                                style={{ backgroundColor: '#f3b14f', color: '#fff' }}
+                                color="orange"
+                                variant="solid"
+                                autoInsertSpace={false}
+                                onClick={() => {
+                                    setTraceHistoryModalLoadingState(true);
+                                    setTraceHistoryModalVisble(true);
+                                    setTraceHistoryOrderInfo({
+                                        userName: row.userName,
+                                        loanAmount: row.loanAmount,
+                                        loanTerm: transformLoanTerm(row.loanTerm),
+                                        age: row.age,
+                                    });
+                                    handleFetchOrderFollowHistory(row.orderNo).then((res) => {
+                                        console.log(res);
+                                        setTraceHistoryList(
+                                            res as APIOrderManage.FetchOrderFollowHistoryListResponse,
+                                        );
+                                        setTraceHistoryModalLoadingState(false);
+                                    });
+                                }}
+                            >
+                                跟进记录
+                            </Button>
+                        </Space>
+                    </>
+                );
+            },
+        },
+    ];
+
+    const descriptionItems = useMemo(
+        () => [
+            { key: '1', label: '姓名', children: traceHistoryOrderInfo.userName },
+            { key: '2', label: '年龄', children: traceHistoryOrderInfo.age },
+            { key: '3', label: '需求金额', children: traceHistoryOrderInfo.loanAmount },
+            { key: '4', label: '需求周期', children: traceHistoryOrderInfo.loanTerm },
+        ],
+        [traceHistoryOrderInfo],
+    );
+
+    useEffect(() => {
+        handleFetchChildAccountList().then((res) => {
+            setChildAccountList(res as APIChildAccount.FetchChildAccountListResponse);
+        });
+    }, []);
+
+    return (
+        <>
+            {/* Todo: 解除分配/重新分配 */}
+            <ProTable
+                bordered
+                columns={tableColumns}
+                scroll={{ x: 'max-content', y: 80 * 5 }}
+                headerTitle="分配给我的订单"
+                rowKey="orderNo"
+                rowSelection={{
+                    onChange: (_, selectedRows) => {
+                        setSelectedRowsState(selectedRows.map((i) => i.id));
+                    },
+                }}
+                request={(params) =>
+                    useFetchAllOrderList({
+                        pageNum: params.current as number,
+                        pageSize: params.pageSize as number,
+                    }).then((res) => {
+                        console.log(res);
+                        const result = {
+                            data: res.data ? res.data.list : [],
+                            total: res.data && res.data.total,
+                            success: true,
+                        };
+
+                        return result;
+                    })
+                }
+            ></ProTable>
+
+            <TraceForm
+                orderNo={targetOrderId}
+                modalVisible={traceFormVisble}
+                onCancel={() => {
+                    setTraceFormVisible(false);
+                }}
+                onSubmit={(params) => {
+                    console.log('receive param from trace component: ', params);
+                }}
+            ></TraceForm>
+
+            <TraceHistoryModal
+                modalVisible={traceHistoryModalVisible}
+                loading={traceHistoryModalLoadingState}
+                onCancel={() => {
+                    setTraceHistoryModalVisble(false);
+                }}
+            >
+                <Descriptions
+                    column={{ xs: 2, xl: 4 }}
+                    colon={false}
+                    items={descriptionItems}
+                ></Descriptions>
+                <Divider />
+                <div
+                    style={{
+                        maxHeight: '400px',
+                        overflow: 'auto',
+                        boxSizing: 'border-box',
+                        padding: '16px',
+                    }}
+                >
+                    <TraceHistoryContent traceHistoryList={traceHistoryList} />
+                </div>
+            </TraceHistoryModal>
+
+            <ChildAccountModal
+                visible={chlidAccountModalVisble}
+                onCancel={() => {
+                    setChildAccountModalVisble(false);
+                }}
+                childAccountList={childAccountList}
+                onConfirm={(id) => {
+                    console.log('选择数据', id);
+                    return new Promise((resolve, reject) => {
+                        useAllocateOrder({ accountId: id, id: selectedRowsState })
+                            .then((result) => {
+                                if (result.code === 0) {
+                                    message.success(result.msg);
+                                    resolve(true);
+                                } else {
+                                    message.warning(result.msg);
+                                    resolve(false);
+                                }
+                            })
+                            .catch((error) => {
+                                message.error('分配订单出错, 请稍后重试');
+
+                                reject(error);
+                            });
+                    });
+                }}
+            ></ChildAccountModal>
+        </>
+    );
+};
+
+export default OrderManage;

+ 59 - 0
src/pages/Order/components/ChildAccountModal.tsx

@@ -0,0 +1,59 @@
+import { Button, Form, Modal, Select } from 'antd';
+import { useState } from 'react';
+
+interface ChildAccountModalProps {
+    visible: boolean;
+    childAccountList: APIChildAccount.FetchChildAccountListResponse;
+    onCancel: () => void;
+    onConfirm: (id: number) => Promise<boolean>;
+}
+
+export default function (props: ChildAccountModalProps) {
+    const [form] = Form.useForm();
+    const [loading, setLoading] = useState<boolean>(false);
+    return (
+        <Modal title="选择子账号" open={props.visible} footer={null} destroyOnClose>
+            <Form form={form} layout="vertical">
+                <Form.Item
+                    label="请选择分配子账号"
+                    name="child_account"
+                    rules={[{ required: true, message: '子账号不可为空' }]}
+                >
+                    <Select>
+                        {props.childAccountList.map((item) => {
+                            return (
+                                <Select.Option key={item.id} value={item.id}>
+                                    {item.contractName}
+                                </Select.Option>
+                            );
+                        })}
+                    </Select>
+                </Form.Item>
+                <Button
+                    block
+                    type="primary"
+                    loading={loading}
+                    onClick={() => {
+                        form.validateFields().then(() => {
+                            setLoading(true);
+                            props
+                                .onConfirm(form.getFieldValue('child_account'))
+                                .then((success) => {
+                                    setLoading(false);
+                                    if (success) {
+                                        props.onCancel();
+                                    }
+                                })
+                                .catch(() => {
+                                    props.onCancel();
+                                    setLoading(false);
+                                });
+                        });
+                    }}
+                >
+                    确认
+                </Button>
+            </Form>
+        </Modal>
+    );
+}

+ 14 - 4
src/pages/Order/components/TraceForm.tsx

@@ -1,10 +1,10 @@
 import { uesAddOrderFollowRecord } from '@/services/order-manage/OrderManage';
 import { Form, Input, Modal, Select, message } from 'antd';
-import React from 'react';
+import React, { useState } from 'react';
 
 type TraceFormProps = {
     modalVisible: boolean;
-    orderNo: number;
+    orderNo: string;
     onSubmit: (params: APIOrderManage.AddOrderFollowHistoryRequestParam) => void;
     onCancel: () => void;
 };
@@ -15,6 +15,7 @@ const handleAddOrderFollowHistory = async (
     try {
         const res = await uesAddOrderFollowRecord(form);
         if (res.code === 0) {
+            message.success('添加跟进记录成功');
             return true;
         } else {
             message.warning(res.msg);
@@ -28,6 +29,7 @@ const handleAddOrderFollowHistory = async (
 
 const TraceForm: React.FC<TraceFormProps> = (props) => {
     const [form] = Form.useForm();
+    const [addingState, setAddingState] = useState<boolean>(false);
     const starOptions = [
         { id: 1, value: 0, label: '0星级' },
         { id: 2, value: 1, label: '1星级' },
@@ -43,14 +45,22 @@ const TraceForm: React.FC<TraceFormProps> = (props) => {
             onCancel={props.onCancel}
             width={620}
             destroyOnClose
+            confirmLoading={addingState}
             onOk={() => {
                 form.validateFields().then(async (result) => {
+                    setAddingState(true);
                     console.log('Validate Result :', result);
-                    handleAddOrderFollowHistory({
+                    const success = await handleAddOrderFollowHistory({
                         orderNo: form.getFieldValue('orderNo'),
                         starLevel: form.getFieldValue('starLevel'),
-                        remark: form.getFieldValue('remark'),
+                        remark: form.getFieldValue('remark') || '',
                     });
+                    if (success) {
+                        setAddingState(false);
+                        props.onCancel();
+                    } else {
+                        setAddingState(false);
+                    }
                 });
             }}
         >

+ 30 - 0
src/pages/Order/components/TraceHistoryContent.tsx

@@ -0,0 +1,30 @@
+import { Rate, Timeline, Flex, Empty } from 'antd';
+import React from 'react';
+
+interface TraceHistoryContentProps {
+    traceHistoryList: APIOrderManage.FetchOrderFollowHistoryListResponse;
+}
+
+const TraceHistoryContent: React.FC<TraceHistoryContentProps> = ({ traceHistoryList }) => {
+    if (!traceHistoryList.length) {
+        return <Empty description="暂无跟进记录" />;
+    }
+
+    return (
+        <Timeline
+            items={traceHistoryList.map((item) => ({
+                children: (
+                    <>
+                        <Flex gap={16} align="center">
+                            <span>{item.createTime}</span>
+                            <Rate defaultValue={item.starLevel} disabled />
+                        </Flex>
+                        <p>{item.remark || '没有备注'}</p>
+                    </>
+                ),
+            }))}
+        />
+    );
+};
+
+export default TraceHistoryContent;

+ 73 - 17
src/pages/Order/components/TraceHistoryModal.tsx

@@ -1,44 +1,71 @@
 // 跟进记录历史
 import { useFetchOrderFollowHistory } from '@/services/order-manage/OrderManage';
-import { Button, Modal } from 'antd';
-import React, { PropsWithChildren } from 'react';
+import { DoubleRightOutlined } from '@ant-design/icons';
+import { Button, Descriptions, Divider, Flex, Modal } from 'antd';
+import React, { PropsWithChildren, useEffect, useMemo, useState } from 'react';
+import TraceHistoryContent from './TraceHistoryContent';
+import style from './index.less';
 
-// Todo: 对接跟进记录历史(Modal形式)
-
-type PresentOrderInfo = {
-    /** 订单号 */
+type TraceHistoryOrderInfo = {
     orderNo: string;
-    /** 客户姓名 */
     userName: string;
-    /** 需求资金 */
+    age: number;
     loanAmount: string;
-    /** 需求周期 */
     loanTerm: string;
-    /** 年龄 */
-    age: number;
 };
 
 interface TraceHistoryModalProps {
+    orderInfo: TraceHistoryOrderInfo;
     modalVisible: boolean;
     loading: boolean;
-    orderInfo: PresentOrderInfo;
     onCancel: () => void;
 }
 
 const handleFetchOrderFollowHistory = async (orderNo: string) => {
-    const res = await useFetchOrderFollowHistory({ orderNo });
-
-    return true;
+    try {
+        const res = await useFetchOrderFollowHistory({ orderNo });
+        return res.data;
+    } catch (error) {
+        return [];
+    }
 };
 
 const TraceHistoryModal: React.FC<PropsWithChildren<TraceHistoryModalProps>> = (props) => {
+    const [traceHistoryList, setTraceHistoryList] = useState<
+        APIOrderManage.OrderFollowHistoryRow[]
+    >([]);
+    const [loading, setLoading] = useState<boolean>(false);
+    const descriptionItems = useMemo(
+        () => [
+            { key: '1', label: '姓名', children: props.orderInfo.userName },
+            { key: '2', label: '年龄', children: props.orderInfo.age },
+            { key: '3', label: '需求金额', children: props.orderInfo.loanAmount },
+            { key: '4', label: '需求周期', children: props.orderInfo.loanTerm },
+        ],
+        [props.orderInfo],
+    );
+
+    useEffect(() => {
+        console.log('跟进记录弹窗展示情况改变', props.modalVisible);
+        if (props.modalVisible) {
+            // 展示时请求数据
+            handleFetchOrderFollowHistory(props.orderInfo.orderNo).then((res) => {
+                setTraceHistoryList(res as APIOrderManage.FetchOrderFollowHistoryListResponse);
+                setLoading(false);
+            });
+        } else {
+            // 隐藏时清除数据
+            setLoading(true);
+        }
+    }, [props.modalVisible]);
+
     return (
         <Modal
             destroyOnClose
             title="跟进记录"
             width={800}
             open={props.modalVisible}
-            loading={props.loading}
+            loading={loading}
             onCancel={props.onCancel}
             footer={
                 <Button type="primary" onClick={props.onCancel}>
@@ -46,7 +73,36 @@ const TraceHistoryModal: React.FC<PropsWithChildren<TraceHistoryModalProps>> = (
                 </Button>
             }
         >
-            {props.children}
+            <Descriptions
+                column={{ xs: 2, xl: 4 }}
+                colon={false}
+                items={descriptionItems}
+            ></Descriptions>
+            <Divider />
+            <div
+                style={{
+                    maxHeight: '400px',
+                    overflow: 'auto',
+                    boxSizing: 'border-box',
+                    padding: '16px',
+                }}
+            >
+                <TraceHistoryContent traceHistoryList={traceHistoryList} />
+            </div>
+            <Flex
+                style={{
+                    display: traceHistoryList.length > 5 ? 'block' : 'none',
+                    position: 'absolute',
+                    bottom: '24px',
+                    left: '50%',
+                    transform: 'translate3d(-50%, 0, 0)',
+                    color: '#007aff',
+                }}
+                className={style['animate-float']}
+            >
+                <DoubleRightOutlined style={{ transform: 'rotate(90deg)' }} />
+                <span>滚动查看更多</span>
+            </Flex>
         </Modal>
     );
 };

+ 14 - 0
src/pages/Order/components/index.less

@@ -0,0 +1,14 @@
+@keyframes float {
+    0%,
+    100% {
+        transform: translate3d(-50%, 0, 0);
+    }
+
+    50% {
+        transform: translate3d(-50%, 5px, 0);
+    }
+}
+
+.animate-float {
+    animation: float 2000ms ease-in-out infinite;
+}

+ 179 - 69
src/pages/Order/index.tsx

@@ -1,24 +1,28 @@
+import { useFetchChildAccountList } from '@/services/child-account/ChildAccountController';
 import {
+    useAllocateOrder,
     useFetchAllOrderList,
-    useFetchOrderFollowHistory,
+    useUnAllocateOrder,
 } from '@/services/order-manage/OrderManage';
-import { CheckCircleFilled, ClockCircleFilled } from '@ant-design/icons';
+import { CheckCircleFilled, ClockCircleFilled, PhoneFilled, WechatFilled } from '@ant-design/icons';
 import { ProColumns, ProTable } from '@ant-design/pro-components';
-import { history } from '@umijs/max';
-import { Button, Descriptions, Divider, Empty, Rate, Space, Tag } from 'antd';
-import React, { useState } from 'react';
+import { Access, history, useAccess } from '@umijs/max';
+import { Button, message, Rate, Select, Space, Tag } from 'antd';
+import React, { useEffect, useMemo, useState } from 'react';
+import ChildAccountModal from './components/ChildAccountModal';
 import TraceForm from './components/TraceForm';
 import TraceHistoryModal from './components/TraceHistoryModal';
+
 import './index.less';
 
-const handleFetchOrderDetail = async (id: number) => {
+const handleFetchOrderDetail = async (id: number, orderNo: string) => {
+    sessionStorage.setItem('DETAIL_ORDER_NO', orderNo);
     history.push(`/order/detail/${id}`);
 };
 
-const handleFetchOrderFollowHistory = async (orderNo: string) => {
+const handleFetchChildAccountList = async () => {
     try {
-        const res = await useFetchOrderFollowHistory({ orderNo });
-        console.log(res);
+        const res = await useFetchChildAccountList();
         return res.data;
     } catch (error) {
         return [];
@@ -26,7 +30,7 @@ const handleFetchOrderFollowHistory = async (orderNo: string) => {
 };
 
 const transformLoanTerm = (loanTerm: string) => {
-    const map = {
+    const map: Record<string, string> = {
         '1': '1个月',
         '2': '3个月',
         '3': '6个月',
@@ -41,22 +45,29 @@ const transformLoanTerm = (loanTerm: string) => {
 };
 
 const OrderManage: React.FC = () => {
-    const [loadingDetailButtonId, setLoadingDetailbuttonId] = useState<number>(-1);
+    const access = useAccess();
     const [traceFormVisble, setTraceFormVisible] = useState<boolean>(false);
     const [traceHistoryModalVisible, setTraceHistoryModalVisble] = useState<boolean>(false);
-    const [traceHistoryOrderInfo, setTraceHistoryOrderInfo] = useState<{
-        userName: string;
-        loanAmount: string;
-        loanTerm: string;
-        age: number;
-    }>({});
-    const [traceHistoryModalLoadingState, setTraceHistoryModalLoadingState] =
-        useState<boolean>(false);
-    const [traceHistoryList, setTraceHistoryList] = useState([]);
-    const [selectedRowsState, setSelectedRowsState] = useState<
-        APIOrderManage.OrderManageTableRow[]
-    >([]);
-    const [targetOrderId, setTargetOrderId] = useState<number>(-1);
+    const [traceHistoryOrderInfo, setTraceHistoryOrderInfo] = useState<
+        Partial<{
+            userName: string;
+            loanAmount: string;
+            loanTerm: string;
+            age: number;
+            orderNo: string;
+        }>
+    >({});
+    const [childAccountList, setChildAccountList] =
+        useState<APIChildAccount.FetchChildAccountListResponse>([]);
+    /** 所选行orderNo */
+    const [selectedRowsState, setSelectedRowsState] = useState<string[]>([]);
+
+    const [allocateLoadingState, setAllocateLoadingState] = useState<boolean>(false);
+
+    const [targetOrderId, setTargetOrderId] = useState<string>('');
+
+    const [chlidAccountModalVisble, setChildAccountModalVisble] = useState<boolean>(false);
+
     const tableColumns: ProColumns[] = [
         {
             title: '订单号',
@@ -67,14 +78,13 @@ const OrderManage: React.FC = () => {
         },
         {
             title: '客户姓名',
-            minWidth: 80,
+            width: 100,
             dataIndex: 'userName',
         },
         {
             title: '客户星级',
             hideInForm: true,
             valueEnum: {
-                '-1': { text: '全部' },
                 0: { text: '0星级' },
                 1: { text: '1星级' },
                 2: { text: '2星级' },
@@ -87,19 +97,38 @@ const OrderManage: React.FC = () => {
                 if (row.star === null) {
                     return '无星级';
                 } else {
-                    return <Rate defaultValue={row.star} disabled></Rate>;
+                    return <Rate defaultValue={row.star} disabled />;
                 }
             },
         },
-        { title: '手机号', dataIndex: 'phone', minWidth: 140 },
-        { title: '微信号', minWidth: 80, dataIndex: 'wxCode', hideInSearch: true },
-        { title: '优先联系方式', minWidth: 100, dataIndex: 'headContractType', hideInSearch: true },
+        { title: '手机号', dataIndex: 'maskPhone', minWidth: 140 },
+        { title: '微信号', width: 100, dataIndex: 'wxCode', hideInSearch: true },
+        {
+            title: '优先联系方式',
+            width: 100,
+            dataIndex: 'headContractType',
+            hideInSearch: true,
+            render(_, entity) {
+                return (
+                    <>
+                        <Space>
+                            {entity.headContractType === '电话' ? (
+                                <PhoneFilled style={{ color: '#51b262' }} />
+                            ) : (
+                                <WechatFilled style={{ color: '#5fcf73' }} />
+                            )}
+                            <span>{entity.headContractType}</span>
+                        </Space>
+                    </>
+                );
+            },
+        },
         { title: '申请产品', dataIndex: 'productName', minWidth: 120, hideInSearch: true },
-        { title: '需求资金', dataIndex: 'loanAmount', minWidth: 80, hideInSearch: true },
+        { title: '需求资金', dataIndex: 'loanAmount', width: 100, hideInSearch: true },
         {
             title: '贷款期限',
             dataIndex: 'loanTerm',
-            minWidth: 80,
+            width: 100,
             hideInSearch: true,
             valueEnum: {
                 '1': { text: '1个月' },
@@ -122,7 +151,18 @@ const OrderManage: React.FC = () => {
                 return entity.applyTime;
             },
         },
-        { title: '分配子账号', dataIndex: 'belongId', minWidth: 100 },
+        {
+            title: '分配子账号',
+            dataIndex: 'belongName',
+            minWidth: 100,
+            renderFormItem: () => (
+                <Select placeholder="选择分配子账号">
+                    {childAccountList.map((account) => (
+                        <Select.Option value={account.id}>{account.contractName}</Select.Option>
+                    ))}
+                </Select>
+            ),
+        },
         {
             title: '跟进状态',
             dataIndex: 'followStatus',
@@ -151,11 +191,10 @@ const OrderManage: React.FC = () => {
                 );
             },
         },
-        { title: '备注', dataIndex: 'remark', minWidth: 100, hideInSearch: true },
+        { title: '备注', dataIndex: 'remark', width: 200, ellipsis: true, hideInSearch: true },
         {
             title: '操作',
-            minWidth: 120,
-            fixed: 'right',
+            fixed: window.innerWidth > 420 ? 'right' : undefined,
             hideInSearch: true,
             render: (_, row) => {
                 return (
@@ -167,7 +206,7 @@ const OrderManage: React.FC = () => {
                                 variant="solid"
                                 autoInsertSpace={false}
                                 onClick={() => {
-                                    handleFetchOrderDetail(row.id);
+                                    handleFetchOrderDetail(row.id, row.orderNo);
                                 }}
                             >
                                 详情
@@ -192,19 +231,14 @@ const OrderManage: React.FC = () => {
                                 variant="solid"
                                 autoInsertSpace={false}
                                 onClick={() => {
-                                    setTraceHistoryModalLoadingState(true);
-                                    setTraceHistoryModalVisble(true);
                                     setTraceHistoryOrderInfo({
                                         userName: row.userName,
                                         loanAmount: row.loanAmount,
                                         loanTerm: transformLoanTerm(row.loanTerm),
                                         age: row.age,
+                                        orderNo: row.orderNo,
                                     });
-                                    handleFetchOrderFollowHistory(row.orderId).then((res) => {
-                                        console.log(res);
-                                        setTraceHistoryList(res);
-                                        setTraceHistoryModalLoadingState(false);
-                                    });
+                                    setTraceHistoryModalVisble(true);
                                 }}
                             >
                                 跟进记录
@@ -216,6 +250,22 @@ const OrderManage: React.FC = () => {
         },
     ];
 
+    const descriptionItems = useMemo(
+        () => [
+            { key: '1', label: '姓名', children: traceHistoryOrderInfo.userName },
+            { key: '2', label: '年龄', children: traceHistoryOrderInfo.age },
+            { key: '3', label: '需求金额', children: traceHistoryOrderInfo.loanAmount },
+            { key: '4', label: '需求周期', children: traceHistoryOrderInfo.loanTerm },
+        ],
+        [traceHistoryOrderInfo],
+    );
+
+    useEffect(() => {
+        handleFetchChildAccountList().then((res) => {
+            setChildAccountList(res as APIChildAccount.FetchChildAccountListResponse);
+        });
+    }, []);
+
     return (
         <>
             {/* Todo: 解除分配/重新分配 */}
@@ -225,22 +275,71 @@ const OrderManage: React.FC = () => {
                 scroll={{ x: 'max-content', y: 80 * 5 }}
                 headerTitle={
                     <Space>
-                        <Button type="primary">解除分配</Button>
-                        <Button type="primary">重新分配</Button>
+                        <Access accessible={access.canUseMainAccount}>
+                            <Button
+                                variant="solid"
+                                color="orange"
+                                loading={allocateLoadingState}
+                                style={{ backgroundColor: '#f3b14f' }}
+                                onClick={() => {
+                                    if (!selectedRowsState.length) {
+                                        message.warning('请选择操作数据');
+
+                                        return void 0;
+                                    }
+                                    setAllocateLoadingState(true);
+                                    console.log('selected row state', selectedRowsState);
+                                    useUnAllocateOrder({ orderIds: selectedRowsState })
+                                        .then((result) => {
+                                            console.log('解除分配结果', result);
+                                            if (result.code === 0) {
+                                                message.success(result.msg);
+                                            } else {
+                                                message.warning(result.msg);
+                                            }
+                                        })
+                                        .catch((error) => {
+                                            console.error('解除分配出错', error);
+                                            message.error('解除分配订单出错, 请重试');
+                                        })
+                                        .finally(() => {
+                                            setAllocateLoadingState(false);
+                                        });
+                                }}
+                            >
+                                解除分配
+                            </Button>
+                        </Access>
+                        <Access accessible={access.canUseMainAccount}>
+                            <Button
+                                type="primary"
+                                onClick={() => {
+                                    if (!selectedRowsState.length) {
+                                        message.warning('请选择操作数据');
+                                        return void 0;
+                                    }
+                                    setChildAccountModalVisble(true);
+                                }}
+                            >
+                                重新分配
+                            </Button>
+                        </Access>
                     </Space>
                 }
                 rowKey="orderNo"
                 rowSelection={{
-                    onChange: (_, selectedRows) => setSelectedRowsState(selectedRows),
+                    onChange: (_, selectedRows) => {
+                        setSelectedRowsState(selectedRows.map((i) => i.id));
+                    },
                 }}
                 request={(params) =>
                     useFetchAllOrderList({
-                        pageNum: params.current,
-                        pageSize: params.pageSize,
+                        pageNum: params.current as number,
+                        pageSize: params.pageSize as number,
                     }).then((res) => {
                         console.log(res);
                         const result = {
-                            data: res.data.list,
+                            data: res.data ? res.data.list : [],
                             total: res.data && res.data.total,
                             success: true,
                         };
@@ -262,29 +361,40 @@ const OrderManage: React.FC = () => {
             ></TraceForm>
 
             <TraceHistoryModal
+                orderInfo={traceHistoryOrderInfo}
                 modalVisible={traceHistoryModalVisible}
-                loading={traceHistoryModalLoadingState}
                 onCancel={() => {
                     setTraceHistoryModalVisble(false);
                 }}
-            >
-                <Descriptions
-                    column={{ xs: 2, xl: 4 }}
-                    colon={false}
-                    items={[
-                        { key: '1', label: '姓名', children: traceHistoryOrderInfo.userName },
-                        { key: '2', label: '年龄', children: traceHistoryOrderInfo.age },
-                        { key: '3', label: '需求金额', children: traceHistoryOrderInfo.loanAmount },
-                        { key: '4', label: '需求周期', children: traceHistoryOrderInfo.loanTerm },
-                    ]}
-                ></Descriptions>
-                <Divider />
-                {traceHistoryList.length ? (
-                    traceHistoryList.map((i) => JSON.stringify(i))
-                ) : (
-                    <Empty description="暂无跟进记录" />
-                )}
-            </TraceHistoryModal>
+            ></TraceHistoryModal>
+
+            <ChildAccountModal
+                visible={chlidAccountModalVisble}
+                onCancel={() => {
+                    setChildAccountModalVisble(false);
+                }}
+                childAccountList={childAccountList}
+                onConfirm={(id) => {
+                    console.log('选择数据', id);
+                    return new Promise((resolve, reject) => {
+                        useAllocateOrder({ accountId: id, id: selectedRowsState })
+                            .then((result) => {
+                                if (result.code === 0) {
+                                    message.success(result.msg);
+                                    resolve(true);
+                                } else {
+                                    message.warning(result.msg);
+                                    resolve(false);
+                                }
+                            })
+                            .catch((error) => {
+                                message.error('分配订单出错, 请稍后重试');
+
+                                reject(error);
+                            });
+                    });
+                }}
+            ></ChildAccountModal>
         </>
     );
 };

+ 48 - 7
src/pages/ResetPassword/index.tsx

@@ -1,7 +1,12 @@
+import { useUpdatePassword } from '@/services/child-account/ChildAccountController';
+import { getLocalStorageItem } from '@/utils/storage';
 import { PageContainer } from '@ant-design/pro-components';
-import { Form, Input, Space } from 'antd';
+import { Button, Form, Input, message, Space } from 'antd';
+import { md5 } from 'js-md5';
+import { useState } from 'react';
 export default () => {
     const [form] = Form.useForm();
+    const [loadingState, setLoadingState] = useState(false);
     return (
         <PageContainer title="重置密码">
             <Form
@@ -10,15 +15,51 @@ export default () => {
                 labelCol={{ span: 4 }}
             >
                 <Space direction="vertical" style={{ width: '500px' }}>
-                    <Form.Item label="旧密码">
-                        <Input.Password name="oldPassword"></Input.Password>
+                    <Form.Item
+                        label="旧密码"
+                        name="oldPassword"
+                        rules={[{ required: true, message: '请输入旧密码' }]}
+                    >
+                        <Input.Password allowClear />
                     </Form.Item>
-                    <Form.Item label="新密码">
-                        <Input.Password name="password"></Input.Password>
+                    <Form.Item
+                        label="新密码"
+                        name="password"
+                        rules={[{ required: true, message: '密码不可为空' }]}
+                    >
+                        <Input.Password allowClear></Input.Password>
                     </Form.Item>
-                    <Form.Item label="确认密码">
-                        <Input.Password name="confirmPassword"></Input.Password>
+                    <Form.Item
+                        label="确认密码"
+                        rules={[{ required: true, message: '请二次确认密码' }]}
+                        name="confirmPassword"
+                    >
+                        <Input.Password allowClear />
                     </Form.Item>
+                    {/* <Form.Item label=" " colon={false}> */}
+                    <Button
+                        block
+                        type="primary"
+                        onClick={() => {
+                            form.validateFields().then((res) => {
+                                console.log(res);
+                                useUpdatePassword({
+                                    id: getLocalStorageItem('__BIZ_ID'),
+                                    oldPassword: md5(form.getFieldValue('oldPassword')),
+                                    password: md5(form.getFieldValue('password')),
+                                    confirmPassword: md5(form.getFieldValue('confirmPassword')),
+                                }).then(({ code, msg }) => {
+                                    if (code !== 0) {
+                                        message.warning(msg);
+                                    } else {
+                                    }
+                                });
+                            });
+                        }}
+                    >
+                        确认重置
+                    </Button>
+                    {/* </Form.Item> */}
                 </Space>
             </Form>
         </PageContainer>

+ 0 - 26
src/pages/Table/components/CreateForm.tsx

@@ -1,26 +0,0 @@
-import { Modal } from 'antd';
-import React, { PropsWithChildren } from 'react';
-
-interface CreateFormProps {
-    modalVisible: boolean;
-    onCancel: () => void;
-}
-
-const CreateForm: React.FC<PropsWithChildren<CreateFormProps>> = (props) => {
-    const { modalVisible, onCancel } = props;
-
-    return (
-        <Modal
-            destroyOnClose
-            title="新建"
-            width={420}
-            open={modalVisible}
-            onCancel={() => onCancel()}
-            footer={null}
-        >
-            {props.children}
-        </Modal>
-    );
-};
-
-export default CreateForm;

+ 0 - 142
src/pages/Table/components/UpdateForm.tsx

@@ -1,142 +0,0 @@
-import {
-    ProFormDateTimePicker,
-    ProFormRadio,
-    ProFormSelect,
-    ProFormText,
-    ProFormTextArea,
-    StepsForm,
-} from '@ant-design/pro-components';
-import { Modal } from 'antd';
-import React from 'react';
-
-export interface FormValueType extends Partial<API.UserInfo> {
-    target?: string;
-    template?: string;
-    type?: string;
-    time?: string;
-    frequency?: string;
-}
-
-export interface UpdateFormProps {
-    onCancel: (flag?: boolean, formVals?: FormValueType) => void;
-    onSubmit: (values: FormValueType) => Promise<void>;
-    updateModalVisible: boolean;
-    values: Partial<API.UserInfo>;
-}
-
-const UpdateForm: React.FC<UpdateFormProps> = (props) => (
-    <StepsForm
-        stepsProps={{
-            size: 'small',
-        }}
-        stepsFormRender={(dom, submitter) => {
-            return (
-                <Modal
-                    width={640}
-                    bodyStyle={{ padding: '32px 40px 48px' }}
-                    destroyOnClose
-                    title="规则配置"
-                    open={props.updateModalVisible}
-                    footer={submitter}
-                    onCancel={() => props.onCancel()}
-                >
-                    {dom}
-                </Modal>
-            );
-        }}
-        onFinish={props.onSubmit}
-    >
-        <StepsForm.StepForm
-            initialValues={{
-                name: props.values.name,
-                nickName: props.values.nickName,
-            }}
-            title="基本信息"
-        >
-            <ProFormText
-                width="md"
-                name="name"
-                label="规则名称"
-                rules={[{ required: true, message: '请输入规则名称!' }]}
-            />
-            <ProFormTextArea
-                name="desc"
-                width="md"
-                label="规则描述"
-                placeholder="请输入至少五个字符"
-                rules={[
-                    {
-                        required: true,
-                        message: '请输入至少五个字符的规则描述!',
-                        min: 5,
-                    },
-                ]}
-            />
-        </StepsForm.StepForm>
-        <StepsForm.StepForm
-            initialValues={{
-                target: '0',
-                template: '0',
-            }}
-            title="配置规则属性"
-        >
-            <ProFormSelect
-                width="md"
-                name="target"
-                label="监控对象"
-                valueEnum={{
-                    0: '表一',
-                    1: '表二',
-                }}
-            />
-            <ProFormSelect
-                width="md"
-                name="template"
-                label="规则模板"
-                valueEnum={{
-                    0: '规则模板一',
-                    1: '规则模板二',
-                }}
-            />
-            <ProFormRadio.Group
-                name="type"
-                width="md"
-                label="规则类型"
-                options={[
-                    {
-                        value: '0',
-                        label: '强',
-                    },
-                    {
-                        value: '1',
-                        label: '弱',
-                    },
-                ]}
-            />
-        </StepsForm.StepForm>
-        <StepsForm.StepForm
-            initialValues={{
-                type: '1',
-                frequency: 'month',
-            }}
-            title="设定调度周期"
-        >
-            <ProFormDateTimePicker
-                name="time"
-                label="开始时间"
-                rules={[{ required: true, message: '请选择开始时间!' }]}
-            />
-            <ProFormSelect
-                name="frequency"
-                label="监控对象"
-                width="xs"
-                valueEnum={{
-                    month: '月',
-                    week: '周',
-                }}
-            />
-        </StepsForm.StepForm>
-    </StepsForm>
-);
-
-export default UpdateForm;

+ 0 - 266
src/pages/Table/index.tsx

@@ -1,266 +0,0 @@
-import services from '@/services/demo';
-import {
-    ActionType,
-    FooterToolbar,
-    PageContainer,
-    ProDescriptions,
-    ProDescriptionsItemProps,
-    ProTable,
-} from '@ant-design/pro-components';
-import { Button, Divider, Drawer, message } from 'antd';
-import React, { useRef, useState } from 'react';
-import CreateForm from './components/CreateForm';
-import UpdateForm, { FormValueType } from './components/UpdateForm';
-
-const { addUser, queryUserList, deleteUser, modifyUser } = services.UserController;
-
-/**
- * 添加节点
- * @param fields
- */
-const handleAdd = async (fields: API.UserInfo) => {
-    const hide = message.loading('正在添加');
-    try {
-        await addUser({ ...fields });
-        hide();
-        message.success('添加成功');
-        return true;
-    } catch (error) {
-        hide();
-        message.error('添加失败请重试!');
-        return false;
-    }
-};
-
-/**
- * 更新节点
- * @param fields
- */
-const handleUpdate = async (fields: FormValueType) => {
-    const hide = message.loading('正在配置');
-    try {
-        await modifyUser(
-            {
-                userId: fields.id || '',
-            },
-            {
-                name: fields.name || '',
-                nickName: fields.nickName || '',
-                email: fields.email || '',
-            },
-        );
-        hide();
-
-        message.success('配置成功');
-        return true;
-    } catch (error) {
-        hide();
-        message.error('配置失败请重试!');
-        return false;
-    }
-};
-
-/**
- *  删除节点
- * @param selectedRows
- */
-const handleRemove = async (selectedRows: API.UserInfo[]) => {
-    const hide = message.loading('正在删除');
-    if (!selectedRows) return true;
-    try {
-        await deleteUser({
-            userId: selectedRows.find((row) => row.id)?.id || '',
-        });
-        hide();
-        message.success('删除成功,即将刷新');
-        return true;
-    } catch (error) {
-        hide();
-        message.error('删除失败,请重试');
-        return false;
-    }
-};
-
-const TableList: React.FC<unknown> = () => {
-    const [createModalVisible, handleModalVisible] = useState<boolean>(false);
-    const [updateModalVisible, handleUpdateModalVisible] = useState<boolean>(false);
-    const [stepFormValues, setStepFormValues] = useState({});
-    const actionRef = useRef<ActionType>();
-    const [row, setRow] = useState<API.UserInfo>();
-    const [selectedRowsState, setSelectedRows] = useState<API.UserInfo[]>([]);
-    const columns: ProDescriptionsItemProps<API.UserInfo>[] = [
-        {
-            title: '名称',
-            dataIndex: 'name',
-            // @ts-ignore
-            tip: '名称是唯一的 key',
-            formItemProps: {
-                rules: [
-                    {
-                        required: true,
-                        message: '名称为必填项',
-                    },
-                ],
-            },
-        },
-        {
-            title: '昵称',
-            dataIndex: 'nickName',
-            valueType: 'text',
-        },
-        {
-            title: '性别',
-            dataIndex: 'gender',
-            hideInForm: true,
-            valueEnum: {
-                0: { text: '男', status: 'MALE' },
-                1: { text: '女', status: 'FEMALE' },
-            },
-        },
-        {
-            title: '操作',
-            dataIndex: 'option',
-            valueType: 'option',
-            render: (_, record) => (
-                <>
-                    <a
-                        onClick={() => {
-                            handleUpdateModalVisible(true);
-                            setStepFormValues(record);
-                        }}
-                    >
-                        配置
-                    </a>
-                    <Divider type="vertical" />
-                    <a href="">订阅警报</a>
-                </>
-            ),
-        },
-    ];
-
-    return (
-        <PageContainer
-            header={{
-                title: 'CRUD 示例',
-            }}
-        >
-            <ProTable<API.UserInfo>
-                headerTitle="查询表格"
-                actionRef={actionRef}
-                rowKey="id"
-                search={{
-                    labelWidth: 120,
-                }}
-                toolBarRender={() => [
-                    <Button key="1" type="primary" onClick={() => handleModalVisible(true)}>
-                        新建
-                    </Button>,
-                ]}
-                request={async (params, sorter, filter) => {
-                    const { data, success } = await queryUserList({
-                        ...params,
-                        // FIXME: remove @ts-ignore
-                        // @ts-ignore
-                        sorter,
-                        filter,
-                    });
-                    return {
-                        data: data?.list || [],
-                        success,
-                    };
-                }}
-                // @ts-ignore
-                columns={columns}
-                rowSelection={{
-                    onChange: (_, selectedRows) => setSelectedRows(selectedRows),
-                }}
-            />
-            {selectedRowsState?.length > 0 && (
-                <FooterToolbar
-                    extra={
-                        <div>
-                            已选择 <a style={{ fontWeight: 600 }}>{selectedRowsState.length}</a>{' '}
-                            项&nbsp;&nbsp;
-                        </div>
-                    }
-                >
-                    <Button
-                        onClick={async () => {
-                            await handleRemove(selectedRowsState);
-                            setSelectedRows([]);
-                            actionRef.current?.reloadAndRest?.();
-                        }}
-                    >
-                        批量删除
-                    </Button>
-                    <Button type="primary">批量审批</Button>
-                </FooterToolbar>
-            )}
-            <CreateForm
-                onCancel={() => handleModalVisible(false)}
-                modalVisible={createModalVisible}
-            >
-                <ProTable<API.UserInfo, API.UserInfo>
-                    onSubmit={async (value) => {
-                        const success = await handleAdd(value);
-                        if (success) {
-                            handleModalVisible(false);
-                            if (actionRef.current) {
-                                actionRef.current.reload();
-                            }
-                        }
-                    }}
-                    rowKey="id"
-                    type="form"
-                    //@ts-ignore
-                    columns={columns}
-                />
-            </CreateForm>
-            {stepFormValues && Object.keys(stepFormValues).length ? (
-                <UpdateForm
-                    onSubmit={async (value) => {
-                        const success = await handleUpdate(value);
-                        if (success) {
-                            handleUpdateModalVisible(false);
-                            setStepFormValues({});
-                            if (actionRef.current) {
-                                actionRef.current.reload();
-                            }
-                        }
-                    }}
-                    onCancel={() => {
-                        handleUpdateModalVisible(false);
-                        setStepFormValues({});
-                    }}
-                    updateModalVisible={updateModalVisible}
-                    values={stepFormValues}
-                />
-            ) : null}
-
-            <Drawer
-                width={600}
-                open={!!row}
-                onClose={() => {
-                    setRow(undefined);
-                }}
-                closable={false}
-            >
-                {row?.name && (
-                    <ProDescriptions<API.UserInfo>
-                        column={2}
-                        title={row?.name}
-                        request={async () => ({
-                            data: row || {},
-                        })}
-                        params={{
-                            id: row?.name,
-                        }}
-                        columns={columns}
-                    />
-                )}
-            </Drawer>
-        </PageContainer>
-    );
-};
-
-export default TableList;

+ 28 - 3
src/services/child-account/ChildAccountController.ts

@@ -1,5 +1,6 @@
 import { useRequest } from '../request';
 
+/** 获取子账号列表(不分页) */
 export async function useFetchChildAccountList() {
     return useRequest<APIChildAccount.FetchChildAccountListResponse>(
         '/merchantUserInfo/getChildAccountNotMain',
@@ -10,6 +11,19 @@ export async function useFetchChildAccountList() {
     );
 }
 
+export async function useFetchChildAccountListByPage(
+    param: APIChildAccount.FetchiChildAccountListByPageRequestParam,
+) {
+    return useRequest<APIChildAccount.FetchChildAccountListResponse>(
+        '/merchantUserInfo/getChildAccountNotMainPage',
+        {
+            method: 'POST',
+            data: param,
+        },
+    );
+}
+
+/** 获取子账号详情 */
 export async function useGetChildAccountDetail(
     param: APIChildAccount.ChildAccountDetailRequestParam,
 ) {
@@ -55,9 +69,20 @@ export async function useUpdateChildAccountStatus(
     });
 }
 
-export async function useFetchSystemDictionary() {
-    return useRequest('/crmSystem/getSysDict', {
+/** 修改子账号接单状态 */
+export async function useUpdateReceiveStatus(
+    param: APIChildAccount.ChildAccountUpdateAllocationStatusRequestParam,
+) {
+    return useRequest('/merchantUserInfo/updateAllocationStatus', {
+        method: 'POST',
+        data: param,
+    });
+}
+
+/** 修改密码 */
+export async function useUpdatePassword(param: APIChildAccount.UpdatePasswordRequestParam) {
+    return useRequest('/merchantUserInfo/updatePassword', {
         method: 'POST',
-        data: {},
+        data: param,
     });
 }

+ 21 - 3
src/services/child-account/typings.d.ts

@@ -1,6 +1,5 @@
 declare namespace APIChildAccount {
-    interface FetchChildAccountListRequestParam {}
-
+    /** 子账号状态枚举 */
     type ChildAccountStatusEnum = '0' | '1';
 
     /** 子账号表格列 */
@@ -15,9 +14,21 @@ declare namespace APIChildAccount {
         creater: string;
     }
 
-    /** 子账号列表返回结果 */
+    /** 子账号分页列表请求参数 */
+    type FetchiChildAccountListByPageRequestParam = {
+        pageNum: number;
+        pageSize: number;
+    };
+
+    /** 子账号列表返回结果(不分页) */
     type FetchChildAccountListResponse = ChildAccountRow[];
 
+    /** 子账号列表返回结果(分页) */
+    type FetchChildAccountListPageResponse = {
+        list: ChildAccountRow[];
+        total: number;
+    };
+
     /** 获取子账号详情请求参数 */
     interface ChildAccountDetailRequestParam {
         id: string;
@@ -62,4 +73,11 @@ declare namespace APIChildAccount {
         contractName: string;
         contractMobile: string;
     };
+
+    type UpdatePasswordRequestParam = {
+        id: number;
+        password: string;
+        confirmPassword: string;
+        oldPassword: string;
+    };
 }

+ 0 - 93
src/services/demo/UserController.ts

@@ -1,93 +0,0 @@
-/* eslint-disable */
-// 该文件由 OneAPI 自动生成,请勿手动修改!
-import { request } from '@umijs/max';
-
-/** 此处后端没有提供注释 GET /api/v1/queryUserList */
-export async function queryUserList(
-    params: {
-        // query
-        /** keyword */
-        keyword?: string;
-        /** current */
-        current?: number;
-        /** pageSize */
-        pageSize?: number;
-    },
-    options?: { [key: string]: any },
-) {
-    return request<API.Result_PageInfo_UserInfo__>('/api/v1/queryUserList', {
-        method: 'GET',
-        params: {
-            ...params,
-        },
-        ...(options || {}),
-    });
-}
-
-/** 此处后端没有提供注释 POST /api/v1/user */
-export async function addUser(body?: API.UserInfoVO, options?: { [key: string]: any }) {
-    return request<API.Result_UserInfo_>('/api/v1/user', {
-        method: 'POST',
-        headers: {
-            'Content-Type': 'application/json',
-        },
-        data: body,
-        ...(options || {}),
-    });
-}
-
-/** 此处后端没有提供注释 GET /api/v1/user/${param0} */
-export async function getUserDetail(
-    params: {
-        // path
-        /** userId */
-        userId?: string;
-    },
-    options?: { [key: string]: any },
-) {
-    const { userId: param0 } = params;
-    return request<API.Result_UserInfo_>(`/api/v1/user/${param0}`, {
-        method: 'GET',
-        params: { ...params },
-        ...(options || {}),
-    });
-}
-
-/** 此处后端没有提供注释 PUT /api/v1/user/${param0} */
-export async function modifyUser(
-    params: {
-        // path
-        /** userId */
-        userId?: string;
-    },
-    body?: API.UserInfoVO,
-    options?: { [key: string]: any },
-) {
-    const { userId: param0 } = params;
-    return request<API.Result_UserInfo_>(`/api/v1/user/${param0}`, {
-        method: 'PUT',
-        headers: {
-            'Content-Type': 'application/json',
-        },
-        params: { ...params },
-        data: body,
-        ...(options || {}),
-    });
-}
-
-/** 此处后端没有提供注释 DELETE /api/v1/user/${param0} */
-export async function deleteUser(
-    params: {
-        // path
-        /** userId */
-        userId?: string;
-    },
-    options?: { [key: string]: any },
-) {
-    const { userId: param0 } = params;
-    return request<API.Result_string_>(`/api/v1/user/${param0}`, {
-        method: 'DELETE',
-        params: { ...params },
-        ...(options || {}),
-    });
-}

+ 0 - 7
src/services/demo/index.ts

@@ -1,7 +0,0 @@
-/* eslint-disable */
-// 该文件由 OneAPI 自动生成,请勿手动修改!
-
-import * as UserController from './UserController';
-export default {
-    UserController,
-};

+ 0 - 91
src/services/demo/typings.d.ts

@@ -1,91 +0,0 @@
-/* eslint-disable */
-
-declare namespace API {
-    type ChildAccountTableRow = {
-        readonly index: number;
-        contactName: string;
-        contactMobile: string;
-        updateBy: string;
-        updateTime: string;
-        wechat: string;
-        receiveStatus: boolean;
-        status: boolean;
-        remark: string | null;
-    };
-
-    type ChildAccountForm = {
-        id?: number | null;
-        contactMobile: string;
-        contactName: string;
-        remark: string;
-        wechat: string;
-        isAllowCusContact: boolean;
-        password: string;
-        confirmPassword: string;
-        accountName: string;
-    };
-
-    interface PageInfo {
-        /** 
-1 */
-        current?: number;
-        pageSize?: number;
-        total?: number;
-        list?: Array<Record<string, any>>;
-    }
-
-    interface PageInfo_UserInfo_ {
-        /** 
-1 */
-        current?: number;
-        pageSize?: number;
-        total?: number;
-        list?: Array<UserInfo>;
-    }
-
-    interface Result {
-        success?: boolean;
-        errorMessage?: string;
-        data?: Record<string, any>;
-    }
-
-    interface Result_PageInfo_UserInfo__ {
-        success?: boolean;
-        errorMessage?: string;
-        data?: PageInfo_UserInfo_;
-    }
-
-    interface Result_UserInfo_ {
-        success?: boolean;
-        errorMessage?: string;
-        data?: UserInfo;
-    }
-
-    interface Result_string_ {
-        success?: boolean;
-        errorMessage?: string;
-        data?: string;
-    }
-
-    type UserGenderEnum = 'MALE' | 'FEMALE';
-
-    interface UserInfo {
-        id?: string;
-        name?: string;
-        /** nick */
-        nickName?: string;
-        /** email */
-        email?: string;
-        gender?: UserGenderEnum;
-    }
-
-    interface UserInfoVO {
-        name?: string;
-        /** nick */
-        nickName?: string;
-        /** email */
-        email?: string;
-    }
-
-    type definitions_0 = null;
-}

+ 1 - 1
src/services/login/LoginController.ts

@@ -23,7 +23,7 @@ export async function useSendSmsCode(params: APILogin.SendSmsCodeRequestParams)
 
 /** 带短信验证码登录 */
 export async function useSmsLogin(params: APILogin.SmsLoginRequestParams) {
-    return useRequest('/crmAccount/smsLogin', {
+    return useRequest<APILogin.SmsLoginResponseParams>('/crmAccount/smsLogin', {
         method: 'POST',
         data: {
             loginName: params.username,

+ 23 - 0
src/services/login/typings.d.ts

@@ -7,4 +7,27 @@ declare namespace APILogin {
     interface SmsLoginRequestParams extends AdminLoginRequestParams {
         smsCode: string;
     }
+    interface SmsLoginResponseParams {
+        time: string;
+        ip: string | null;
+        /** 账号ID */
+        id: number;
+        /** 所属主账号 */
+        mainAccount: string;
+        /** 登录手机号 */
+        contactMobile: string;
+        token: string;
+        /** 登录账号 */
+        loginName: string;
+        /** 登录账号用户名 */
+        admUserName: string;
+        /** 登录手机号(脱敏) */
+        phone: string;
+        /** 商户ID */
+        merchantNo: string;
+        /** 登录账号ID */
+        admUserId: number;
+        /** 是否为主账号 */
+        main: boolean;
+    }
 }

+ 35 - 3
src/services/order-manage/OrderManage.ts

@@ -12,19 +12,51 @@ export function useFetchAllOrderList(
     );
 }
 
+/** 获取我的订单列表 */
+export function useFetchMyOrderList(
+    params: APIOrderManage.OrderManageFetchAllOrderListRequestParam,
+) {
+    return useRequest<APIOrderManage.OrderManageFetchAllOrderListResponseParam>(
+        '/merchantOrderInfo/allocationMyList',
+        {
+            method: 'POST',
+            data: params,
+        },
+    );
+}
+
 /** 获取订单详情 */
-export function useFetchOrderDetail(params: APIOrderManage.OrderManageFetchDetailRequestParam) {
-    return useRequest(`/merchantOrderInfo/detail/${params.id}`, { method: 'GET' });
+export function useFetchOrderDetail(params: APIOrderManage.FetchDetailRequestParam) {
+    return useRequest<APIOrderManage.FetchDetailResponseParam>(`/merchantOrderInfo/detail`, {
+        method: 'POST',
+        data: params,
+    });
 }
 
 /** 获取订单跟进记录 */
 export function useFetchOrderFollowHistory(
     params: APIOrderManage.OrderManageFetchOrderFollowHistoryRequestParam,
 ) {
-    return useRequest('/merchantFollowInfo/list', { method: 'POST', data: params });
+    return useRequest<APIOrderManage.FetchOrderFollowHistoryListResponse>(
+        '/merchantFollowInfo/list',
+        { method: 'POST', data: params },
+    );
 }
 
 /** 添加订单更新记录 */
 export function uesAddOrderFollowRecord(params: APIOrderManage.AddOrderFollowHistoryRequestParam) {
     return useRequest('/merchantFollowInfo/add', { method: 'POST', data: params });
 }
+
+/** 分配客户 */
+export function useAllocateOrder(params: APIOrderManage.OrderManageAllocateOrderRequestParam) {
+    return useRequest('/merchantOrderInfo/allocationAccount', { method: 'POST', data: params });
+}
+
+/** 解除分配 */
+export function useUnAllocateOrder(params: APIOrderManage.OrderManageUnAllocateOrderRequestParam) {
+    return useRequest('/merchantOrderInfo/reliveOrder', {
+        method: 'POST',
+        data: params,
+    });
+}

+ 79 - 2
src/services/order-manage/typings.d.ts

@@ -25,11 +25,62 @@ declare namespace APIOrderManage {
         remark: string | null;
     };
 
-    type OrderManageFetchAllOrderListResponseParam = OrderManageTableRow[];
+    type OrderManageFetchAllOrderListResponseParam = {
+        total: number;
+        list: OrderManageTableRow[];
+    };
 
     /** 获取订单详情请求参数 */
-    interface OrderManageFetchDetailRequestParam {
+    interface FetchDetailRequestParam {
+        id: number;
+    }
+
+    type ParamDetailOrderInfo = {
+        /** 订单ID */
         id: number;
+        city: string;
+        userName: string;
+        age: number;
+        star: number;
+        loanTerm: number;
+        loanAmount: string;
+        wxCode: string | null;
+    };
+    type ParamDetailCustomerInfo = {
+        /** 订单ID */
+        id: number;
+        /** 车产信息 */
+        car: number | null;
+        /** 房产信息 */
+        house: number | null;
+        /** 保险保单 */
+        insurance: number | null;
+        /** 社保缴纳 */
+        socialSecurity: number | null;
+        /** 公积金 */
+        fund: number | null;
+        /** 职业 */
+        career: number | null;
+        /** 芝麻分 */
+        zhima: number | null;
+        /** 工资发放形式 */
+        salaryType: number | null;
+        /** 购房方式 */
+        housePurchaseMethod: number | null;
+        /** 购车方式 */
+        buyCarWay: number | null;
+        /** 学历 */
+        education: number | null;
+        /** 月收入 */
+        monthIncome: number | null;
+    };
+
+    /** 订单详情返回参数 */
+    interface FetchDetailResponseParam {
+        /** 订单数据 */
+        orderInfoVO: ParamDetailOrderInfo;
+        /** 用户表单数据 */
+        custAssertInfoVO: ParamDetailCustomerInfo;
     }
 
     /** 新增订单跟进记录请求参数 */
@@ -43,4 +94,30 @@ declare namespace APIOrderManage {
     interface OrderManageFetchOrderFollowHistoryRequestParam {
         orderNo: string;
     }
+
+    /** 订单跟进记录返回参数 */
+    type OrderFollowHistoryRow = {
+        id: number;
+        orderNo: string;
+        createTime: string;
+        remark: string;
+        starLevel: number;
+        follower: string;
+    };
+
+    type FetchOrderFollowHistoryListResponse = OrderFollowHistoryRow[];
+
+    /** 分配订单请求参数 */
+    interface OrderManageAllocateOrderRequestParam {
+        /** 账号ID */
+        accountId: number;
+        /** 订单ID */
+        id: string[];
+    }
+
+    /** 解除分配请求参数 */
+    interface OrderManageUnAllocateOrderRequestParam {
+        /** 订单ID */
+        orderIds: string[];
+    }
 }

+ 1 - 1
src/services/request.ts

@@ -2,7 +2,7 @@ import { getLocalStorageItem } from '@/utils/storage';
 import type { RequestOptions } from '@umijs/max';
 import { request } from '@umijs/max';
 
-const REQUEST_BASE_URL = 'http://192.168.1.25:851';
+const REQUEST_BASE_URL = process.env.UMI_APP_REQUEST_BASE_URL;
 
 const jointUrl = (url: string) => `${REQUEST_BASE_URL}${url}`;
 

+ 8 - 0
src/services/system/SystemController.ts

@@ -0,0 +1,8 @@
+import { useRequest } from '../request';
+
+export async function useFetchSystemDictionary() {
+    return useRequest<APISystem.SystemDictionaryItem[]>('/crmSystem/getSysDict', {
+        method: 'POST',
+        data: {},
+    });
+}

+ 15 - 0
src/services/system/typings.d.ts

@@ -0,0 +1,15 @@
+declare namespace APISystem {
+    type SystemDictionaryItem = {
+        id: number;
+        /** 字段编码 */
+        code: string;
+        /** 字段翻译 */
+        msg: string;
+        /** 字典值 */
+        key: string;
+        /** 字典描述 */
+        description: string;
+        /** 字段名称 */
+        type: string;
+    };
+}

+ 2 - 1
src/utils/request.ts

@@ -1,4 +1,5 @@
-import { RequestConfig } from './request';
+import { RequestConfig } from '@umijs/max';
+import { message } from 'antd';
 
 // 错误处理方案: 错误类型
 enum ErrorShowType {

+ 8 - 1
src/utils/storage.ts

@@ -1,4 +1,11 @@
-type LocalStorageKeysEnum = '__BIZ_ID' | 'TOKEN' | 'IS_CHILD' | 'MAIN_ACCOUNT' | 'LOGIN_NAME';
+type LocalStorageKeysEnum =
+    | '__BIZ_ID'
+    | 'TOKEN'
+    | 'IS_MAIN'
+    | 'MAIN_ACCOUNT'
+    | 'LOGIN_NAME'
+    | 'RECEIVE_STATUS'
+    | 'SYSTEM_DICTIONARY';
 
 export const setLocalStorageItem = (key: LocalStorageKeysEnum, value: any) => {
     localStorage.setItem(key, JSON.stringify(value));

+ 62 - 0
src/utils/system.ts

@@ -0,0 +1,62 @@
+import { getLocalStorageItem } from './storage';
+
+/**
+ * 适配器
+ * 后端返回的字段不一定与系统字典的字段一致,需要进行适配
+ */
+const Adapter: Record<string, string[]> = {
+    accumulation: ['accumulation', 'fund'],
+    borrowLimit: ['borrowLimit', 'loanTerm', 'loanTime'],
+    borrowMoneyRange: ['borrowMoneyRange', 'loanAmount'],
+    businessLicense: ['businessLicense', 'license'],
+    buyCarWay: ['buyCarWay'],
+    carType: ['carType', 'car', 'carSituation'],
+    companyCommWater: ['companyCommWater'],
+    educationCode: ['educationCode', 'education'],
+    followStatus: ['followStatus'],
+    housePurchaseMethod: ['housePurchaseMethod'],
+    houseType: ['houseType', 'house', 'houseSituation'],
+    insuranceType: ['insuranceType', 'insurance'],
+    monthlyIncome: ['monthlyIncome', 'monthIncome'],
+    overdue: ['overdue'],
+    profession: ['profession', 'job', 'career'],
+    salaryPayment: ['salaryPayment', 'salaryType'],
+    sesame: ['sesame', 'zhima'],
+    socialSecurity: ['socialSecurity'],
+    turnover: ['turnover'],
+};
+
+export const getDictionary = (type: string, code: number | null): string | null => {
+    const dictionary = getLocalStorageItem('SYSTEM_DICTIONARY');
+
+    if (code === null) {
+        return null;
+    }
+    return dictionary[type][code] || null;
+};
+
+export const useAdapter = (key: string) => {
+    if (Object.keys(Adapter).includes(key)) {
+        return key;
+    }
+    // 否则查看适配器中字段
+    let adapterKey = '';
+    Object.keys(Adapter).forEach((k) => {
+        if (Adapter[k].includes(key)) {
+            adapterKey = k;
+        }
+    });
+
+    return adapterKey;
+};
+
+export const handleSetToDictionary = (
+    map: Record<string, number | null>,
+    key: string[],
+): Record<string, string> => {
+    const after = {};
+    key.forEach((k) => {
+        after[useAdapter(k)] = getDictionary(useAdapter(k), map[k]);
+    });
+    return after;
+};