Explorar o código

fix: 修复支付宝小程序页面不存在 + accountId回退enterpriseId

alphaH hai 2 días
pai
achega
fe59a7e57d

+ 21 - 0
.idea/claudeCodeTabState.xml

@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ClaudeCodeTabState">
+    <option name="tabSessions">
+      <map>
+        <entry key="0">
+          <value>
+            <TabSessionState>
+              <option name="provider" value="claude" />
+              <option name="sessionId" value="82487a63-3420-45b6-8575-dafeba428ce2" />
+              <option name="cwd" value="$PROJECT_DIR$" />
+              <option name="model" value="claude-opus-4-7[1m]" />
+              <option name="permissionMode" value="plan" />
+              <option name="reasoningEffort" value="max" />
+            </TabSessionState>
+          </value>
+        </entry>
+      </map>
+    </option>
+  </component>
+</project>

+ 0 - 1
config/index.ts

@@ -27,7 +27,6 @@ export default defineConfig<'webpack5'>(async (merge, { command, mode }) => {
     outputRoot: 'dist',
     plugins: [
       "@tarojs/plugin-generator",
-      '@tarojs/plugin-html',
     ],
     defineConstants: {
     },

+ 182 - 7
package-lock.json

@@ -28,6 +28,7 @@
         "@tarojs/shared": "4.1.11",
         "@tarojs/taro": "4.1.11",
         "humps": "^2.0.1",
+        "qrcode": "^1.5.4",
         "react": "^18.0.0",
         "react-dom": "^18.0.0",
         "zustand": "^5.0.12"
@@ -10286,6 +10287,15 @@
         }
       }
     },
+    "node_modules/decamelize": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+      "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/decimal.js": {
       "version": "10.6.0",
       "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
@@ -10700,6 +10710,12 @@
         "url": "https://github.com/sponsors/wooorm"
       }
     },
+    "node_modules/dijkstrajs": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
+      "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==",
+      "license": "MIT"
+    },
     "node_modules/dingtalk-jsapi": {
       "version": "2.15.6",
       "resolved": "https://registry.npmjs.org/dingtalk-jsapi/-/dingtalk-jsapi-2.15.6.tgz",
@@ -11018,7 +11034,6 @@
       "version": "8.0.0",
       "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
       "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/emojis-list": {
@@ -13304,7 +13319,6 @@
       "version": "2.0.5",
       "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
       "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
-      "dev": true,
       "license": "ISC",
       "engines": {
         "node": "6.* || 8.* || >= 10.*"
@@ -18471,7 +18485,6 @@
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
       "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=6"
@@ -19113,6 +19126,15 @@
       "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==",
       "license": "MIT"
     },
+    "node_modules/pngjs": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
+      "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10.13.0"
+      }
+    },
     "node_modules/possible-typed-array-names": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
@@ -20236,6 +20258,145 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/qrcode": {
+      "version": "1.5.4",
+      "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz",
+      "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==",
+      "license": "MIT",
+      "dependencies": {
+        "dijkstrajs": "^1.0.1",
+        "pngjs": "^5.0.0",
+        "yargs": "^15.3.1"
+      },
+      "bin": {
+        "qrcode": "bin/qrcode"
+      },
+      "engines": {
+        "node": ">=10.13.0"
+      }
+    },
+    "node_modules/qrcode/node_modules/camelcase": {
+      "version": "5.3.1",
+      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+      "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/qrcode/node_modules/cliui": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
+      "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
+      "license": "ISC",
+      "dependencies": {
+        "string-width": "^4.2.0",
+        "strip-ansi": "^6.0.0",
+        "wrap-ansi": "^6.2.0"
+      }
+    },
+    "node_modules/qrcode/node_modules/find-up": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+      "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+      "license": "MIT",
+      "dependencies": {
+        "locate-path": "^5.0.0",
+        "path-exists": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/qrcode/node_modules/locate-path": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+      "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+      "license": "MIT",
+      "dependencies": {
+        "p-locate": "^4.1.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/qrcode/node_modules/p-limit": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+      "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+      "license": "MIT",
+      "dependencies": {
+        "p-try": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/qrcode/node_modules/p-locate": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+      "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+      "license": "MIT",
+      "dependencies": {
+        "p-limit": "^2.2.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/qrcode/node_modules/path-exists": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+      "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/qrcode/node_modules/y18n": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
+      "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
+      "license": "ISC"
+    },
+    "node_modules/qrcode/node_modules/yargs": {
+      "version": "15.4.1",
+      "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
+      "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
+      "license": "MIT",
+      "dependencies": {
+        "cliui": "^6.0.0",
+        "decamelize": "^1.2.0",
+        "find-up": "^4.1.0",
+        "get-caller-file": "^2.0.1",
+        "require-directory": "^2.1.1",
+        "require-main-filename": "^2.0.0",
+        "set-blocking": "^2.0.0",
+        "string-width": "^4.2.0",
+        "which-module": "^2.0.0",
+        "y18n": "^4.0.0",
+        "yargs-parser": "^18.1.2"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/qrcode/node_modules/yargs-parser": {
+      "version": "18.1.3",
+      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
+      "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
+      "license": "ISC",
+      "dependencies": {
+        "camelcase": "^5.0.0",
+        "decamelize": "^1.2.0"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "node_modules/qs": {
       "version": "6.14.2",
       "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
@@ -20729,7 +20890,6 @@
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
       "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=0.10.0"
@@ -20744,6 +20904,12 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/require-main-filename": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+      "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
+      "license": "ISC"
+    },
     "node_modules/requires-port": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
@@ -21645,6 +21811,12 @@
         "node": ">= 0.8.0"
       }
     },
+    "node_modules/set-blocking": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+      "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
+      "license": "ISC"
+    },
     "node_modules/set-function-length": {
       "version": "1.2.2",
       "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
@@ -22248,7 +22420,6 @@
       "version": "4.2.3",
       "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
       "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "emoji-regex": "^8.0.0",
@@ -22289,7 +22460,6 @@
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
       "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=8"
@@ -25241,6 +25411,12 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/which-module": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
+      "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
+      "license": "ISC"
+    },
     "node_modules/which-typed-array": {
       "version": "1.1.20",
       "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz",
@@ -25337,7 +25513,6 @@
       "version": "6.2.0",
       "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
       "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "ansi-styles": "^4.0.0",

+ 1 - 0
package.json

@@ -69,6 +69,7 @@
     "@tarojs/shared": "4.1.11",
     "@tarojs/taro": "4.1.11",
     "humps": "^2.0.1",
+    "qrcode": "^1.5.4",
     "react": "^18.0.0",
     "react-dom": "^18.0.0",
     "zustand": "^5.0.12"

+ 1 - 8
src/app.config.ts

@@ -1,11 +1,5 @@
 export default defineAppConfig({
-  pages: [
-    'pages/index/index',
-    'pages/login/index',
-    'pages/transfer/record/index',
-    'pages/transfer/index', 
-    'pages/mine/index', 
-  ],
+  pages: ['pages/index/index', 'pages/login/index', 'pages/receive/index', 'pages/mine/index'],
   window: {
     backgroundTextStyle: 'light',
     navigationBarBackgroundColor: '#fff',
@@ -16,7 +10,6 @@ export default defineAppConfig({
     color: '#999',
     selectedColor: '#1890ff',
     backgroundColor: '#fff',
-    borderStyle: 'white',
     list: [
       {
         pagePath: 'pages/index/index',

+ 8 - 4
src/app.ts

@@ -9,10 +9,14 @@ import { loadUserFromStorage } from './stores/user';
 
 function App({ children }: PropsWithChildren<any>) {
   useLaunch(() => {
-    // 从本地恢复用户信息
-    loadUserFromStorage();
-    // 检查登录态(token + 24h 窗口)
-    Auth.requireAuth();
+    try {
+      // 从本地恢复用户信息
+      loadUserFromStorage();
+      // 检查登录态(token + 24h 窗口)
+      Auth.requireAuth();
+    } catch (e) {
+      console.error('[App] 初始化失败:', e);
+    }
   });
 
   // children 是将要会渲染的页面

+ 32 - 28
src/components/EmptyShow.tsx

@@ -1,29 +1,33 @@
-import { Empty, Image } from "@nutui/nutui-react-taro";
-import { View } from "@tarojs/components";
-import { ReactNode } from "react";
-import emptyImgSrc from "@/assets/imgs/empty.png";
+import { Empty, Image } from '@nutui/nutui-react-taro';
+import { View } from '@tarojs/components';
+import { ReactNode } from 'react';
+import emptyImgSrc from '@/assets/imgs/empty.png';
 
-
-export default function EmptyShow(props: { title?: string | ReactNode; description?: string | ReactNode }) {
-    return (
-        <View>
-            <Empty
-                title={props.title}
-                description={props.description}
-                image={
-                    <Image
-                        style={{
-                            width: '100%',
-                            height: '100%',
-                        }}
-                        src={emptyImgSrc}
-                    />
-                }
-                style={{
-                    '--nutui-empty-image-size': '100px',
-                    '--nutui-empty-title-margin-top': '20px',
-                } as any}
-            />
-        </View>
-    )
-}
+export default function EmptyShow(props: {
+  title?: string | ReactNode;
+  description?: string | ReactNode;
+}) {
+  return (
+    <View>
+      <Empty
+        title={props.title}
+        description={props.description}
+        image={
+          <Image
+            style={{
+              width: '100%',
+              height: '100%',
+            }}
+            src={emptyImgSrc}
+          />
+        }
+        style={
+          {
+            '--nutui-empty-image-size': '100px',
+            '--nutui-empty-title-margin-top': '20px',
+          } as any
+        }
+      />
+    </View>
+  );
+}

+ 3 - 2
src/components/Icon.tsx

@@ -7,7 +7,6 @@ import AlipayIconSrc from '@/assets/icons/alipay.svg';
 import BackCardIconSrc from '@/assets/icons/back-card.svg';
 import ArrowRightIconSrc from '@/assets/icons/arrow-right.svg';
 
-
 type IconProps = Pick<ImageProps, 'src' | 'className' | 'svg'> & {
   size?: number;
   color?: string;
@@ -31,7 +30,9 @@ export const PasswordIcon = (props: Omit<IconProps, 'src'>) => (
 export const UserSmileIcon = (props: Omit<IconProps, 'src'>) => (
   <Icon src={UserSmileIconSrc} {...props} />
 );
-export const AlipayIcon = (props: Omit<IconProps, 'src'>) => <Icon src={AlipayIconSrc} {...props} />;
+export const AlipayIcon = (props: Omit<IconProps, 'src'>) => (
+  <Icon src={AlipayIconSrc} {...props} />
+);
 
 export const BackCardIcon = (props: Omit<IconProps, 'src'>) => (
   <Icon src={BackCardIconSrc} {...props} />

+ 2 - 2
src/pages/index/index.less

@@ -41,5 +41,5 @@
 }
 
 .custom-cell {
-  --nutui-cell-background-color: rgba(255,215,0, 0.15);
-}
+  --nutui-cell-background-color: rgba(255, 215, 0, 0.15);
+}

+ 5 - 1
src/pages/login/index.tsx

@@ -90,7 +90,11 @@ export default function Login() {
       Taro.showToast({ title: '登录成功', icon: 'success', duration: 2000 });
       Taro.switchTab({ url: '/pages/index/index' });
     } catch (error: unknown) {
-      Taro.showToast({ title: (error as Error).message || '登录失败', icon: 'error', duration: 2000 });
+      Taro.showToast({
+        title: (error as Error).message || '登录失败',
+        icon: 'error',
+        duration: 2000,
+      });
     } finally {
       setLoading(false);
     }

+ 5 - 1
src/pages/mine/index.tsx

@@ -53,7 +53,11 @@ export default function Mine() {
         style={{ paddingTop: `${statusBarHeight! + 30}px`, paddingBottom: '30px' }}
       >
         <View className="user-info">
-          <Avatar size="large" icon={userInfo.avatar || <User width={'50px'} height={'50px'} />} className="user-avatar" />
+          <Avatar
+            size="large"
+            icon={userInfo.avatar || <User width={'50px'} height={'50px'} />}
+            className="user-avatar"
+          />
           <View className="user-details">
             <Text className="user-name">{userInfo.name}</Text>
             <Text className="user-guide-no"></Text>

+ 5 - 0
src/pages/receive/index.config.ts

@@ -0,0 +1,5 @@
+export default definePageConfig({
+  navigationBarTitleText: '收款',
+  transparentTitle: 'always',
+  titlePenetrate: 'YES',
+});

+ 132 - 0
src/pages/receive/index.less

@@ -0,0 +1,132 @@
+.receive {
+  min-height: 100vh;
+  background: #f5f5f5;
+  display: flex;
+  flex-direction: column;
+
+  .receive-content {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    padding: 0 30px;
+    padding-bottom: 60px;
+  }
+
+  .amount-wrapper {
+    display: flex;
+    align-items: baseline;
+    justify-content: center;
+    margin-bottom: 10px;
+
+    .currency {
+      font-size: 40px;
+      font-weight: bold;
+      color: #333;
+      margin-right: 4px;
+    }
+
+    .amount-input {
+      font-size: 56px;
+      font-weight: bold;
+      color: #333;
+      text-align: left;
+      min-width: 200px;
+      border-bottom: 3px solid #1890ff;
+      padding: 8px 0;
+    }
+  }
+
+  .quick-amounts {
+    display: flex;
+    justify-content: center;
+    gap: 14px;
+    margin-top: 30px;
+
+    .quick-amount-btn {
+      padding: 14px 24px;
+      border-radius: 10px;
+      border: 1px solid #d9d9d9;
+      font-size: 18px;
+      font-weight: 500;
+      color: #333;
+      background: #fff;
+
+      &.active {
+        background: #1890ff;
+        color: #fff;
+        border-color: #1890ff;
+      }
+    }
+  }
+
+  .generate-btn {
+    margin-top: 50px;
+    width: 100%;
+    max-width: 320px;
+  }
+
+  .qr-wrapper {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    width: 100%;
+
+    .qr-title {
+      font-size: 20px;
+      font-weight: bold;
+      color: #333;
+      margin-bottom: 25px;
+    }
+
+    .qr-code {
+      background: #fff;
+      padding: 14px;
+      border-radius: 12px;
+      box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
+    }
+
+    .amount-display {
+      font-size: 40px;
+      font-weight: bold;
+      color: #1890ff;
+      margin: 20px 0 10px;
+    }
+
+    .status-tag {
+      display: inline-block;
+      padding: 6px 24px;
+      border-radius: 16px;
+      font-size: 16px;
+      margin-bottom: 10px;
+
+      &.pending {
+        background: #fff7e6;
+        color: #fa8c16;
+      }
+
+      &.success {
+        background: #f6ffed;
+        color: #52c41a;
+      }
+
+      &.closed {
+        background: #fff2f0;
+        color: #ff4d4f;
+      }
+    }
+
+    .tip {
+      font-size: 15px;
+      color: #999;
+      margin-top: 8px;
+    }
+
+    .new-qr-btn {
+      margin-top: 40px;
+      width: 100%;
+      max-width: 320px;
+    }
+  }
+}

+ 229 - 0
src/pages/receive/index.tsx

@@ -0,0 +1,229 @@
+import { View, Text, Input, Image } from '@tarojs/components';
+import { useState, useEffect, useRef } from 'react';
+import Taro, { useLoad } from '@tarojs/taro';
+import { Button, Toast, ToastOptions } from '@nutui/nutui-react-taro';
+import { useUserStore } from '@/stores/user';
+import './index.less';
+
+import { requestApi } from '@/services/apis';
+
+const precreateTradeApi = (data: { enterpriseId: string; totalAmount: string; subject?: string }) =>
+  requestApi(
+    {
+      url: '/payment/facetoface/trade/precreate',
+      method: 'POST',
+      header: { 'Content-Type': 'application/json' },
+      data,
+    },
+    '生成收款码失败',
+  );
+
+const queryTradeApi = (data: { outTradeNo: string; enterpriseId: string }) =>
+  requestApi(
+    {
+      url: '/payment/facetoface/trade/query',
+      method: 'GET',
+      header: { 'Content-Type': 'application/json' },
+      data,
+    },
+    '查询失败',
+  );
+
+const STATUS_LABEL: Record<string, string> = {
+  WAIT_BUYER_PAY: '等待付款',
+  TRADE_SUCCESS: '支付成功',
+  TRADE_CLOSED: '已关闭',
+  TRADE_FINISHED: '交易完成',
+};
+
+export default function Receive() {
+  const [statusBarHeight, setStatusBarHeight] = useState(0);
+  const [amount, setAmount] = useState('');
+  const [loading, setLoading] = useState(false);
+  const [qrCode, setQrCode] = useState('');
+  const [qrImage, setQrImage] = useState('');
+  const [outTradeNo, setOutTradeNo] = useState('');
+  const [tradeStatus, setTradeStatus] = useState('');
+  const [tradeAmount, setTradeAmount] = useState('');
+  const pollingRef = useRef<ReturnType<typeof setInterval> | null>(null);
+
+  const enterpriseId = useUserStore((s) => s.enterpriseId);
+
+  useLoad(() => {
+    const { statusBarHeight: sHeight } = Taro.getSystemInfoSync();
+    setStatusBarHeight(sHeight || 0);
+  });
+
+  useEffect(() => {
+    return () => {
+      if (pollingRef.current) clearInterval(pollingRef.current);
+    };
+  }, []);
+
+  const showToast = (options: ToastOptions) => {
+    Toast.show('receive-toast', options);
+  };
+
+  const quickAmounts = [
+    { label: '10', value: '10' },
+    { label: '50', value: '50' },
+    { label: '100', value: '100' },
+    { label: '500', value: '500' },
+  ];
+
+  const handleAmountChange = (value: string) => {
+    let filtered = value.replace(/[^0-9.]/g, '');
+    const parts = filtered.split('.');
+    if (parts.length > 2) filtered = parts[0] + '.' + parts.slice(1).join('');
+    if (parts.length === 2 && parts[1].length > 2)
+      filtered = parts[0] + '.' + parts[1].substring(0, 2);
+    setAmount(filtered);
+  };
+
+  const generateQrImage = async (url: string) => {
+    try {
+      const QRCode = require('qrcode');
+      const dataUrl = await QRCode.toDataURL(url, { width: 260, margin: 1 });
+      setQrImage(dataUrl);
+    } catch (_) {
+      setQrImage(url);
+    }
+  };
+
+  const startPolling = (tradeNo: string) => {
+    if (pollingRef.current) clearInterval(pollingRef.current);
+    pollingRef.current = setInterval(async () => {
+      try {
+        const result: any = await queryTradeApi({ outTradeNo: tradeNo, enterpriseId });
+        setTradeStatus(result.tradeStatus);
+        if (result.tradeStatus === 'TRADE_SUCCESS' || result.tradeStatus === 'TRADE_FINISHED') {
+          if (pollingRef.current) clearInterval(pollingRef.current);
+          setTradeAmount(result.totalAmount || amount);
+          showToast({ icon: 'success', content: '收款成功' });
+        } else if (result.tradeStatus === 'TRADE_CLOSED') {
+          if (pollingRef.current) clearInterval(pollingRef.current);
+          showToast({ icon: 'error', content: '交易已关闭' });
+        }
+      } catch (_) {
+        // 静默
+      }
+    }, 3000);
+  };
+
+  const handleGenerate = async () => {
+    if (!amount || parseFloat(amount) <= 0) {
+      showToast({ content: '请输入收款金额' });
+      return;
+    }
+    if (!enterpriseId) {
+      showToast({ content: '未获取到企业信息,请重新登录' });
+      return;
+    }
+
+    setLoading(true);
+    try {
+      const result: any = await precreateTradeApi({
+        enterpriseId,
+        totalAmount: amount,
+        subject: '当面付收款',
+      });
+      setQrCode(result.qrCode);
+      setOutTradeNo(result.outTradeNo);
+      setTradeStatus('');
+      setQrImage('');
+      await generateQrImage(result.qrCode);
+      startPolling(result.outTradeNo);
+    } catch (e: any) {
+      showToast({ icon: 'error', content: e.message || '生成收款码失败' });
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  return (
+    <View className="receive" style={{ paddingTop: `${statusBarHeight}px` }}>
+      <Toast id="receive-toast" />
+
+      <View className="receive-content">
+        {!qrCode ? (
+          <>
+            <View className="amount-wrapper">
+              <Text className="currency">¥</Text>
+              <Input
+                className="amount-input"
+                value={amount}
+                onInput={(e) => handleAmountChange(e.detail.value)}
+                placeholder="0.00"
+                type="digit"
+                maxlength={12}
+                focus
+              />
+            </View>
+
+            <View className="quick-amounts">
+              {quickAmounts.map((item) => (
+                <View
+                  key={item.value}
+                  className={`quick-amount-btn ${amount === item.value ? 'active' : ''}`}
+                  onClick={() => setAmount(item.value)}
+                >
+                  ¥{item.label}
+                </View>
+              ))}
+            </View>
+
+            <View className="generate-btn">
+              <Button type="primary" size="large" block loading={loading} onClick={handleGenerate}>
+                {loading ? '生成中...' : '生成收款码'}
+              </Button>
+            </View>
+          </>
+        ) : (
+          <View className="qr-wrapper">
+            <Text className="qr-title">客户扫码付款</Text>
+
+            <View className="qr-code">
+              {qrImage && qrImage.startsWith('data:') ? (
+                <Image src={qrImage} mode="aspectFit" style={{ width: 260, height: 260 }} />
+              ) : (
+                <Image src={qrCode} mode="aspectFit" style={{ width: 260, height: 260 }} />
+              )}
+            </View>
+
+            <View className="amount-display">¥{amount}</View>
+
+            {tradeStatus && (
+              <View
+                className={`status-tag ${tradeStatus === 'TRADE_SUCCESS' || tradeStatus === 'TRADE_FINISHED' ? 'success' : tradeStatus === 'TRADE_CLOSED' ? 'closed' : 'pending'}`}
+              >
+                {STATUS_LABEL[tradeStatus] || tradeStatus}
+              </View>
+            )}
+
+            <View className="tip">
+              {tradeStatus === 'TRADE_SUCCESS' || tradeStatus === 'TRADE_FINISHED'
+                ? `已收款 ¥${tradeAmount || amount}`
+                : '二维码有效期为2小时'}
+            </View>
+
+            <View className="new-qr-btn">
+              <Button
+                plain
+                type="primary"
+                size="large"
+                block
+                onClick={() => {
+                  setQrCode('');
+                  setAmount('');
+                  setTradeStatus('');
+                }}
+              >
+                再收一笔
+              </Button>
+            </View>
+          </View>
+        )}
+      </View>
+    </View>
+  );
+}

+ 1 - 1
src/pages/transfer/index.less

@@ -213,4 +213,4 @@
   // cursor: wait;
   opacity: 0.6;
   pointer-events: none;
-}
+}

+ 15 - 11
src/pages/transfer/index.tsx

@@ -153,9 +153,11 @@ export default function Transfer() {
       return;
     }
 
-    if (transferData.identityType === 'bank' && 
-            transferData.bankcardExtInfo.account_type === '1' && 
-                !transferData.bankcardExtInfo.inst_name) {
+    if (
+      transferData.identityType === 'bank' &&
+      transferData.bankcardExtInfo.account_type === '1' &&
+      !transferData.bankcardExtInfo.inst_name
+    ) {
       showToast({
         content: '请输入银行卡机构名称',
       });
@@ -177,7 +179,8 @@ export default function Transfer() {
           identityType: transferData.identityType,
           name: transferData.name,
           identity: transferData.identity,
-          bankcardExtInfo: transferData.identityType === 'alipay' ? undefined : transferData.bankcardExtInfo,
+          bankcardExtInfo:
+            transferData.identityType === 'alipay' ? undefined : transferData.bankcardExtInfo,
         },
       });
 
@@ -201,7 +204,6 @@ export default function Transfer() {
 
       // 跳转到转账记录页面
       Taro.navigateTo({ url: '/pages/transfer/record/index' });
-
     } catch (error: unknown) {
       showToast({
         icon: 'error',
@@ -300,13 +302,15 @@ export default function Transfer() {
             <>
               <View className="recipient-input">
                 <View className="nut-input-label">银行卡类型</View>
-                <Radio.Group 
-                  value={transferData.bankcardExtInfo.account_type} 
-                  onChange={(value) => handleAccountTypeChange(value as string)} 
+                <Radio.Group
+                  value={transferData.bankcardExtInfo.account_type}
+                  onChange={(value) => handleAccountTypeChange(value as string)}
                   direction="horizontal"
-                  style={{
-                    // '--nutui-radio-button-border-radius': '8px',
-                  } as any}
+                  style={
+                    {
+                      // '--nutui-radio-button-border-radius': '8px',
+                    } as any
+                  }
                 >
                   <Radio shape="button" value="1">
                     公司账户

+ 11 - 11
src/pages/transfer/record/index.less

@@ -1,15 +1,15 @@
 .transfer-record {
-    min-height: 100vh;
-    background-color: #f5f5f5;
+  min-height: 100vh;
+  background-color: #f5f5f5;
 
-    .custom-nav {
-        background-color: #fff;
+  .custom-nav {
+    background-color: #fff;
 
-        .custom-title-bar {
-            display: flex;
-            align-items: center;
-            font-size: 16px;
-            padding: 0 272px 20px 62px;
-        }
+    .custom-title-bar {
+      display: flex;
+      align-items: center;
+      font-size: 16px;
+      padding: 0 272px 20px 62px;
     }
-}
+  }
+}

+ 177 - 180
src/pages/transfer/record/index.tsx

@@ -1,189 +1,186 @@
-import { Button, InfiniteLoading, Menu, pxTransform, SearchBar } from "@nutui/nutui-react-taro";
-import { View } from "@tarojs/components";
-import Taro from "@tarojs/taro";
-import { useLoad } from "@tarojs/taro";
-import { CSSProperties, useState } from "react";
+import { Button, InfiniteLoading, Menu, pxTransform, SearchBar } from '@nutui/nutui-react-taro';
+import { View } from '@tarojs/components';
+import Taro from '@tarojs/taro';
+import { useLoad } from '@tarojs/taro';
+import { CSSProperties, useState } from 'react';
 import './index.less';
-import { ArrowDown, Filter, ArrowTransfer } from "@nutui/icons-react-taro";
-import EmptyShow from "@/components/EmptyShow";
-import { listTransferApi } from "@/services/apis";
+import { ArrowDown, Filter, ArrowTransfer } from '@nutui/icons-react-taro';
+import EmptyShow from '@/components/EmptyShow';
+import { listTransferApi } from '@/services/apis';
 
 const InfiniteUlStyle: CSSProperties = {
-    height: '100%',
-    width: '100%',
-    padding: '0',
-    overflowY: 'auto',
-    overflowX: 'hidden',
-}
+  height: '100%',
+  width: '100%',
+  padding: '0',
+  overflowY: 'auto',
+  overflowX: 'hidden',
+};
 
 export default function TransferRecord() {
-
-    const [statusBarHeight, setStatusBarHeight] = useState<number | undefined>(0);
-    const [refreshList, setRefreshList] = useState<string[]>([])
-    const [refreshHasMore, setRefreshHasMore] = useState(true)
-    const [refreshLoading, setRefreshLoading] = useState(false)
-
-    const [searchData, setSearchData] = useState<{ status: string, orderNo: string, pageNo: number, pageSize: number }>({
-        status: '',
-        orderNo: '',
-        pageNo: 1,
-        pageSize: 10,
-    });
-
-    const [statusOptions] = useState([
-        { text: '全部状态', value: '' },
-        { text: '处理中', value: 'DEALING' },
-        { text: '成功', value: 'SUCCESS' },
-        { text: '失败', value: 'FAIL' },
-        { text: '退票', value: 'REFUND' },
-    ])
-    
-    const [options1] = useState([
-        { text: '默认排序', value: 'a' },
-        { text: '时间倒序', value: 'b' },
-        { text: '时间升序', value: 'c' },
-    ])
-
-    useLoad(() => {
-        const { statusBarHeight } = Taro.getSystemInfoSync();
-        setStatusBarHeight(statusBarHeight);
-        console.log(statusBarHeight);
-        init()
-    });
-
-
-    const isEmpty = refreshList.length === 0
-
-    const showData = !refreshLoading && !isEmpty
-    const showEmpty = !refreshLoading && isEmpty
-
-
-    const init = () => {
-        // for (let i = 0; i < 10; i++) {
-        //     refreshList.push(`${i}`)
-        // }
-        // setRefreshList([...refreshList])
-        refresh()
-    }
-
-    const refreshLoadMore = async () => {
-        const curLen = refreshList.length
-        for (let i = curLen; i < curLen + 10; i++) {
-            refreshList.push(`${i}`)
-        }
-        if (refreshList.length >= 1000) {
-            setRefreshHasMore(false)
-        } else {
-            setRefreshList([...refreshList])
-        }
+  const [statusBarHeight, setStatusBarHeight] = useState<number | undefined>(0);
+  const [refreshList, setRefreshList] = useState<string[]>([]);
+  const [refreshHasMore, setRefreshHasMore] = useState(true);
+  const [refreshLoading, setRefreshLoading] = useState(false);
+
+  const [searchData, setSearchData] = useState<{
+    status: string;
+    orderNo: string;
+    pageNo: number;
+    pageSize: number;
+  }>({
+    status: '',
+    orderNo: '',
+    pageNo: 1,
+    pageSize: 10,
+  });
+
+  const [statusOptions] = useState([
+    { text: '全部状态', value: '' },
+    { text: '处理中', value: 'DEALING' },
+    { text: '成功', value: 'SUCCESS' },
+    { text: '失败', value: 'FAIL' },
+    { text: '退票', value: 'REFUND' },
+  ]);
+
+  const [options1] = useState([
+    { text: '默认排序', value: 'a' },
+    { text: '时间倒序', value: 'b' },
+    { text: '时间升序', value: 'c' },
+  ]);
+
+  useLoad(() => {
+    const { statusBarHeight } = Taro.getSystemInfoSync();
+    setStatusBarHeight(statusBarHeight);
+    console.log(statusBarHeight);
+    init();
+  });
+
+  const isEmpty = refreshList.length === 0;
+
+  const showData = !refreshLoading && !isEmpty;
+  const showEmpty = !refreshLoading && isEmpty;
+
+  const init = () => {
+    // for (let i = 0; i < 10; i++) {
+    //     refreshList.push(`${i}`)
+    // }
+    // setRefreshList([...refreshList])
+    refresh();
+  };
+
+  const refreshLoadMore = async () => {
+    const curLen = refreshList.length;
+    for (let i = curLen; i < curLen + 10; i++) {
+      refreshList.push(`${i}`);
     }
-
-    const refresh = async () => {
-        await listTransferApi({
-            pageNo: searchData.pageNo,
-            pageSize: searchData.pageSize,
-            status: searchData.status,
-            orderNo: searchData.orderNo,
-        })
+    if (refreshList.length >= 1000) {
+      setRefreshHasMore(false);
+    } else {
+      setRefreshList([...refreshList]);
     }
-
-    return (
-        <View className="transfer-record">
-            {/* <HoverButton icon={<Plus />} /> */}
-            <View className="custom-nav" style={{ paddingTop: `${statusBarHeight}px` }}>
-                <View className="custom-title-bar">
-                    <SearchBar
-                        value={searchData.orderNo}
-                        onChange={(v) => setSearchData({ ...searchData, orderNo: v })}
-                        placeholder="搜索"
-                        shape="round"
-                        rightIn={
-                            <>
-                            </>
-                        }
-                        style={
-                            {
-                                '--nutui-searchbar-background': '--nutui-color-primary',
-                            } as any
-                        }
-                        right={
-                            <>
-                                <Button
-                                    type="primary"
-                                    size="large"
-                                    shape="round"
-                                    // fill="outline"
-                                    icon={<ArrowTransfer color="white" />}
-                                    onClick={() => Taro.redirectTo({ url: '/pages/transfer/index' })}
-                                >
-                                    转账
-                                </Button>
-                            </>
-                        }
-                    />
-                </View>
-                <View>
-                    <Menu
-                        icon={<ArrowDown />}
-                        style={{
-                            backgroundColor: 'var(--nutui-color-primary)',
-                        } as any}
-                    >
-                        <Menu.Item
-                            options={statusOptions}
-                            value={searchData.status}
-                            onChange={(v) => setSearchData({ ...searchData, status: v })}
-                            // icon={<Star />}
-                            titleIcon={<Filter />}
-                        />
-                        <Menu.Item options={options1} defaultValue="a" />
-                    </Menu>
-                </View>
-            </View>
-
-            {showData && (
-                <View id="refreshScroll" style={InfiniteUlStyle}>
-                    <InfiniteLoading
-                        pullingText={
-                            <>
-                                ... 下拉刷新
-                            </>
-                        }
-                        loadingText={
-                            <>
-                                加载中 ...
-                            </>
-                        }
-                        loadMoreText="没有啦~"
-                        target="refreshScroll"
-                        pullRefresh
-                        hasMore={refreshHasMore}
-                        onLoadMore={refreshLoadMore}
-                        onRefresh={refresh}
-                    >
-                        {refreshList.map((item, index) => {
-                            return (
-                                <View
-                                    className="infiniteLi"
-                                    key={index}
-                                // style={InfiniteLiStyle}
-                                >
-                                    {item}
-                                </View>
-                            )
-                        })}
-                    </InfiniteLoading>
-                </View>
-            )}
-
-            {showEmpty && (
-                <View>
-                    <EmptyShow
-                        title="暂无转账记录, 请先转账"
-                        description={<Button type="primary" size="normal" shape="round" fill="outline">刷 新</Button>}
-                    />
+  };
+
+  const refresh = async () => {
+    await listTransferApi({
+      pageNo: searchData.pageNo,
+      pageSize: searchData.pageSize,
+      status: searchData.status,
+      orderNo: searchData.orderNo,
+    });
+  };
+
+  return (
+    <View className="transfer-record">
+      {/* <HoverButton icon={<Plus />} /> */}
+      <View className="custom-nav" style={{ paddingTop: `${statusBarHeight}px` }}>
+        <View className="custom-title-bar">
+          <SearchBar
+            value={searchData.orderNo}
+            onChange={(v) => setSearchData({ ...searchData, orderNo: v })}
+            placeholder="搜索"
+            shape="round"
+            rightIn={<></>}
+            style={
+              {
+                '--nutui-searchbar-background': '--nutui-color-primary',
+              } as any
+            }
+            right={
+              <>
+                <Button
+                  type="primary"
+                  size="large"
+                  shape="round"
+                  // fill="outline"
+                  icon={<ArrowTransfer color="white" />}
+                  onClick={() => Taro.redirectTo({ url: '/pages/transfer/index' })}
+                >
+                  转账
+                </Button>
+              </>
+            }
+          />
+        </View>
+        <View>
+          <Menu
+            icon={<ArrowDown />}
+            style={
+              {
+                backgroundColor: 'var(--nutui-color-primary)',
+              } as any
+            }
+          >
+            <Menu.Item
+              options={statusOptions}
+              value={searchData.status}
+              onChange={(v) => setSearchData({ ...searchData, status: v })}
+              // icon={<Star />}
+              titleIcon={<Filter />}
+            />
+            <Menu.Item options={options1} defaultValue="a" />
+          </Menu>
+        </View>
+      </View>
+
+      {showData && (
+        <View id="refreshScroll" style={InfiniteUlStyle}>
+          <InfiniteLoading
+            pullingText={<>... 下拉刷新</>}
+            loadingText={<>加载中 ...</>}
+            loadMoreText="没有啦~"
+            target="refreshScroll"
+            pullRefresh
+            hasMore={refreshHasMore}
+            onLoadMore={refreshLoadMore}
+            onRefresh={refresh}
+          >
+            {refreshList.map((item, index) => {
+              return (
+                <View
+                  className="infiniteLi"
+                  key={index}
+                  // style={InfiniteLiStyle}
+                >
+                  {item}
                 </View>
-            )}
+              );
+            })}
+          </InfiniteLoading>
+        </View>
+      )}
+
+      {showEmpty && (
+        <View>
+          <EmptyShow
+            title="暂无转账记录, 请先转账"
+            description={
+              <Button type="primary" size="normal" shape="round" fill="outline">
+                刷 新
+              </Button>
+            }
+          />
         </View>
-    )
-}
+      )}
+    </View>
+  );
+}

+ 3 - 2
src/services/apis.ts

@@ -126,7 +126,7 @@ export const sendSmsCodeApi = async (mobile: string) => {
       },
       data: {
         mobile,
-        template_name: 'verify',
+        template_name: 'login',
       },
     },
     '发送验证码失败',
@@ -152,7 +152,7 @@ export const loginApi = async (data: LoginFormData) => {
   );
 };
 
-export const getUserInfoApi = async () => {
+export const getUserInfoApi = async (mobile?: string) => {
   return requestApi(
     {
       url: '/payment/employee/info',
@@ -161,6 +161,7 @@ export const getUserInfoApi = async () => {
       header: {
         'Content-Type': 'application/json',
       },
+      data: mobile ? { employee_mobile: mobile } : {},
     },
     '获取用户信息失败',
   );

+ 102 - 97
src/stores/user.ts

@@ -6,120 +6,125 @@ import { create } from 'zustand';
 
 // 用户信息
 export interface UserState {
-    enterpriseId: string;
-    accountId: string;
-    userId: string;
-    userName: string;
-    avatar: string;
-    /** 是否正在加载用户信息 */
-    loading: boolean;
+  enterpriseId: string;
+  accountId: string;
+  userId: string;
+  userName: string;
+  avatar: string;
+  mobile: string;
+  /** 是否正在加载用户信息 */
+  loading: boolean;
 
-    getUserInfo: () => Promise<void>;
-    setUserInfo: (userInfo: Partial<UserState>) => void;
-    logout: () => void;
-    login: (data: LoginFormData) => Promise<void>;
+  getUserInfo: () => Promise<void>;
+  setUserInfo: (userInfo: Partial<UserState>) => void;
+  logout: () => void;
+  login: (data: LoginFormData) => Promise<void>;
 }
 
 // 创建用户状态管理store
-export const useUserStore = create<UserState>((set) => ({
-    enterpriseId: '',
-    accountId: '',
-    userId: '',
-    userName: '',
-    avatar: '',
-    loading: false,
+export const useUserStore = create<UserState>((set, get) => ({
+  enterpriseId: '',
+  accountId: '',
+  userId: '',
+  userName: '',
+  avatar: '',
+  mobile: '',
+  loading: false,
 
-    getUserInfo: async () => {
-        set({ loading: true });
-        try {
-            const info = await getUserInfoApi();
-            set({
-                enterpriseId: (info as any).enterpriseId || '',
-                accountId: (info as any).accountId || '',
-                userId: (info as any).userId || '',
-                userName: (info as any).userName || '',
-                avatar: (info as any).avatar || '',
-                loading: false,
-            });
-            saveUserToStorage({
-                enterpriseId: (info as any).enterpriseId,
-                accountId: (info as any).accountId,
-                userId: (info as any).userId,
-                userName: (info as any).userName,
-                avatar: (info as any).avatar,
-            });
-        } catch (error) {
-            set({ loading: false });
-            console.error('获取用户信息失败:', error);
-            throw error;
-        }
-    },
+  getUserInfo: async () => {
+    const { mobile } = get();
+    set({ loading: true });
+    try {
+      const info = await getUserInfoApi(mobile);
+      set({
+        enterpriseId: (info as any).enterpriseId || '',
+        accountId: (info as any).accountId || (info as any).enterpriseId || '',
+        userId: (info as any).userId || '',
+        userName: (info as any).userName || '',
+        avatar: (info as any).avatar || '',
+        loading: false,
+      });
+      saveUserToStorage({
+        enterpriseId: (info as any).enterpriseId,
+        accountId: (info as any).accountId || (info as any).enterpriseId,
+        userId: (info as any).userId,
+        userName: (info as any).userName,
+        avatar: (info as any).avatar,
+      });
+    } catch (error) {
+      set({ loading: false });
+      console.error('获取用户信息失败:', error);
+      throw error;
+    }
+  },
 
-    setUserInfo: (userInfo) => set((state) => ({
-        ...state,
-        ...userInfo,
+  setUserInfo: (userInfo) =>
+    set((state) => ({
+      ...state,
+      ...userInfo,
     })),
 
-    logout: () => {
-        set({
-            enterpriseId: '',
-            accountId: '',
-            userId: '',
-            userName: '',
-            avatar: '',
-            loading: false,
-        });
-        Auth.clearToken();
-        Auth.requireAuth();
-    },
+  logout: () => {
+    set({
+      enterpriseId: '',
+      accountId: '',
+      userId: '',
+      userName: '',
+      avatar: '',
+      mobile: '',
+      loading: false,
+    });
+    Auth.clearToken();
+    Auth.requireAuth();
+  },
 
-    login: async (data) => {
-        const response = await loginApi(data);
-        Auth.setToken(response);
-        // 登录成功后获取用户信息(含 enterpriseId、accountId)
-        const info = await getUserInfoApi();
-        console.log('员工信息接口返回:', JSON.stringify(info));
-        set({
-            enterpriseId: (info as any).enterpriseId || '',
-            accountId: (info as any).accountId || '',
-            userId: (info as any).userId || '',
-            userName: (info as any).userName || '',
-            avatar: (info as any).avatar || '',
-        });
-        saveUserToStorage({
-            enterpriseId: (info as any).enterpriseId,
-            accountId: (info as any).accountId,
-            userId: (info as any).userId,
-            userName: (info as any).userName,
-            avatar: (info as any).avatar,
-        });
-    },
+  login: async (data) => {
+    const response = await loginApi(data);
+    Auth.setToken(response);
+    // 登录成功后获取用户信息(含 enterpriseId、accountId)
+    const info = await getUserInfoApi(data.mobile);
+    console.log('员工信息接口返回:', JSON.stringify(info));
+    set({
+      enterpriseId: (info as any).enterpriseId || '',
+      accountId: (info as any).accountId || (info as any).enterpriseId || '',
+      userId: (info as any).userId || '',
+      userName: (info as any).userName || '',
+      avatar: (info as any).avatar || '',
+      mobile: data.mobile,
+    });
+    saveUserToStorage({
+      enterpriseId: (info as any).enterpriseId,
+      accountId: (info as any).accountId || (info as any).enterpriseId,
+      userId: (info as any).userId,
+      userName: (info as any).userName,
+      avatar: (info as any).avatar,
+      mobile: data.mobile,
+    });
+  },
 }));
 
-
 const USER_INFO_KEY = 'userInfo';
 
-
 // 从本地存储加载用户信息
 export const loadUserFromStorage = () => {
-    try {
-        const userInfo = Taro.getStorageSync(USER_INFO_KEY);
-        if (userInfo) {
-            const parsedUserInfo = JSON.parse(userInfo);
-            useUserStore.getState().setUserInfo(parsedUserInfo);
-        }
-    } catch (error) {
-        console.error('加载用户信息失败:', error);
+  try {
+    const userInfo = Taro.getStorageSync(USER_INFO_KEY);
+    if (userInfo) {
+      const parsedUserInfo = JSON.parse(userInfo);
+      useUserStore.getState().setUserInfo(parsedUserInfo);
     }
+  } catch (error) {
+    console.error('加载用户信息失败:', error);
+  }
 };
 
 // 保存用户信息到本地存储
 export const saveUserToStorage = (userInfo: Partial<UserState>) => {
-    try {
-        const currentUserInfo = useUserStore.getState();
-        const updatedUserInfo = { ...currentUserInfo, ...userInfo };
-        Taro.setStorageSync(USER_INFO_KEY, JSON.stringify(updatedUserInfo));
-    } catch (error) {
-        console.error('保存用户信息失败:', error);
-    }
-};
+  try {
+    const currentUserInfo = useUserStore.getState();
+    const updatedUserInfo = { ...currentUserInfo, ...userInfo };
+    Taro.setStorageSync(USER_INFO_KEY, JSON.stringify(updatedUserInfo));
+  } catch (error) {
+    console.error('保存用户信息失败:', error);
+  }
+};

+ 119 - 119
src/utils/alipay.ts

@@ -1,11 +1,11 @@
-import Taro from "@tarojs/taro";
+import Taro from '@tarojs/taro';
 
 // 支付宝小程序全局 API
 declare const my: {
-    navigateToMiniProgram: (options: { appId: string; path: string }) => void;
-    ap: {
-        openURL: (options: { url: string }) => void;
-    };
+  navigateToMiniProgram: (options: { appId: string; path: string }) => void;
+  ap: {
+    openURL: (options: { url: string }) => void;
+  };
 };
 
 // ====== 配置常量 ======
@@ -16,31 +16,31 @@ const BIZ_CODE = '';
 
 // ====== 类型定义 ======
 interface AlipayScanExternalThrough {
-    pdSubBizScene: 'enterprisePay';
-    /** JSON 字符串 */
-    specifiedEnableChannelInfo: string;
-    assignJointAccountId: string;
-    /** JSON 字符串 */
-    identityPayBizInfo: string;
-    /** JSON 字符串数组 */
-    CHANNEL_INDEX: string;
-    /** JSON 字符串 */
-    sourcePlatformInfo: string;
+  pdSubBizScene: 'enterprisePay';
+  /** JSON 字符串 */
+  specifiedEnableChannelInfo: string;
+  assignJointAccountId: string;
+  /** JSON 字符串 */
+  identityPayBizInfo: string;
+  /** JSON 字符串数组 */
+  CHANNEL_INDEX: string;
+  /** JSON 字符串 */
+  sourcePlatformInfo: string;
 }
 
 interface PaymentCodeParams {
-    pdSubBizScene: 'enterprisePay';
-    CHANNEL_INDEX: string;
-    channelMode: string;
-    specifiedEnableChannelInfo: string;
-    assignJointAccountId: string;
-    identityPayBizInfo: string;
-    enterprise_pay_info: string;
+  pdSubBizScene: 'enterprisePay';
+  CHANNEL_INDEX: string;
+  channelMode: string;
+  specifiedEnableChannelInfo: string;
+  assignJointAccountId: string;
+  identityPayBizInfo: string;
+  enterprise_pay_info: string;
 }
 
 // ====== 工具函数 ======
 export const isAlipayEnv = () => {
-    return Taro.getEnv() === Taro.ENV_TYPE.ALIPAY;
+  return Taro.getEnv() === Taro.ENV_TYPE.ALIPAY;
 };
 
 /**
@@ -52,54 +52,54 @@ export const isAlipayEnv = () => {
  * @param options      - 可选:费控规则ID、付款事由ID
  */
 export const scanWithAlipay = (
-    enterpriseId: string,
-    accountId: string,
-    options?: { standardId?: string; paymentId?: string },
+  enterpriseId: string,
+  accountId: string,
+  options?: { standardId?: string; paymentId?: string },
 ) => {
-    if (!isAlipayEnv()) {
-        throw new Error('非支付宝环境,无法使用企业码扫码付');
-    }
-
-    console.log('[scanWithAlipay] enterpriseId:', enterpriseId);
-    console.log('[scanWithAlipay] accountId:', accountId);
-
-    if (!enterpriseId || !accountId) {
-        throw new Error('企业ID或账户ID为空,请确认登录信息');
-    }
-
-    const externalThrough: AlipayScanExternalThrough = {
-        pdSubBizScene: 'enterprisePay',
-        specifiedEnableChannelInfo: JSON.stringify({
-            enableScene: "agreementpay",
-            assetInfo: {
-                instId: "INST_ALIPAY",
-                assetId: accountId,
-                assetTypeCode: "ENTERPRISEPAY",
-                assetType: "ENTERPRISEPAYASSET",
-            },
-        }),
-        assignJointAccountId: accountId,
-        identityPayBizInfo: JSON.stringify({
-            identityPayBizScene: 'ENTERPRISE_CODE',
-            identityPaySubBizScene: 'ISV_PAY',
-            groupId: accountId,
-            bizGroupId: accountId,
-            expenseGroupId: options && options.standardId,
-        }),
-        CHANNEL_INDEX: JSON.stringify(['ENTERPRISEPAYASSET_DC_ENTERPRISEPAY_DEFAULT']),
-        sourcePlatformInfo: JSON.stringify({
-            ruleGroupId: options && options.standardId,
-            paymentId: options && options.paymentId,
-            isvAppId: ISV_APP_ID,
-        }),
-    };
-
-    const externalThroughStr = JSON.stringify(externalThrough);
-
-    my.navigateToMiniProgram({
-        appId: '2021002128690179',
-        path: `/pages/open/proxy/index?enterpriseId=${enterpriseId}&pageType=scan&externalThrough=${externalThroughStr}`,
-    });
+  if (!isAlipayEnv()) {
+    throw new Error('非支付宝环境,无法使用企业码扫码付');
+  }
+
+  console.log('[scanWithAlipay] enterpriseId:', enterpriseId);
+  console.log('[scanWithAlipay] accountId:', accountId);
+
+  if (!enterpriseId || !accountId) {
+    throw new Error('企业ID或账户ID为空,请确认登录信息');
+  }
+
+  const externalThrough: AlipayScanExternalThrough = {
+    pdSubBizScene: 'enterprisePay',
+    specifiedEnableChannelInfo: JSON.stringify({
+      enableScene: 'agreementpay',
+      assetInfo: {
+        instId: 'INST_ALIPAY',
+        assetId: accountId,
+        assetTypeCode: 'ENTERPRISEPAY',
+        assetType: 'ENTERPRISEPAYASSET',
+      },
+    }),
+    assignJointAccountId: accountId,
+    identityPayBizInfo: JSON.stringify({
+      identityPayBizScene: 'ENTERPRISE_CODE',
+      identityPaySubBizScene: 'ISV_PAY',
+      groupId: accountId,
+      bizGroupId: accountId,
+      expenseGroupId: options && options.standardId,
+    }),
+    CHANNEL_INDEX: JSON.stringify(['ENTERPRISEPAYASSET_DC_ENTERPRISEPAY_DEFAULT']),
+    sourcePlatformInfo: JSON.stringify({
+      ruleGroupId: options && options.standardId,
+      paymentId: options && options.paymentId,
+      isvAppId: ISV_APP_ID,
+    }),
+  };
+
+  const externalThroughStr = JSON.stringify(externalThrough);
+
+  my.navigateToMiniProgram({
+    appId: '2021002128690179',
+    path: `/pages/open/proxy/index?enterpriseId=${enterpriseId}&pageType=scan&externalThrough=${externalThroughStr}`,
+  });
 };
 
 /**
@@ -110,55 +110,55 @@ export const scanWithAlipay = (
  * @param options      - 可选:费控规则ID、付款事由ID
  */
 export const codeWithAlipay = (
-    accountId: string,
-    options?: { enterpriseId?: string; standardId?: string; paymentId?: string },
+  accountId: string,
+  options?: { enterpriseId?: string; standardId?: string; paymentId?: string },
 ) => {
-    if (!isAlipayEnv()) {
-        throw new Error('非支付宝环境,无法使用企业码付款码');
-    }
-
-    // 如果 BIZ_CODE 未配置,先用 navigateToMiniProgram 方式(测试用)
-    if (!BIZ_CODE) {
-        const eid = (options && options.enterpriseId) || '';
-        if (!eid) {
-            throw new Error('enterpriseId 为空,请先确认 /payment/employee/info 接口能正常返回企业数据');
-        }
-        my.navigateToMiniProgram({
-            appId: '2021002128690179',
-            path: `/pages/open/proxy/index?enterpriseId=${eid}&pageType=payment-code`,
-        });
-        return;
+  if (!isAlipayEnv()) {
+    throw new Error('非支付宝环境,无法使用企业码付款码');
+  }
+
+  // 如果 BIZ_CODE 未配置,先用 navigateToMiniProgram 方式(测试用)
+  if (!BIZ_CODE) {
+    const eid = (options && options.enterpriseId) || '';
+    if (!eid) {
+      throw new Error('enterpriseId 为空,请先确认 /payment/employee/info 接口能正常返回企业数据');
     }
-
-    const params: PaymentCodeParams = {
-        pdSubBizScene: 'enterprisePay',
-        CHANNEL_INDEX: JSON.stringify(['ENTERPRISEPAYASSET_DC_ENTERPRISEPAY_DEFAULT']),
-        channelMode: 'NONE_CHANNEL_MODE',
-        specifiedEnableChannelInfo: JSON.stringify({
-            enableScene: 'agreementpay',
-            assetInfo: {
-                instId: 'INST_ALIPAY',
-                assetId: accountId,
-                assetTypeCode: 'ENTERPRISEPAY',
-                assetType: 'ENTERPRISEPAYASSET',
-            },
-        }),
-        assignJointAccountId: accountId,
-        identityPayBizInfo: JSON.stringify({
-            identityPaySubBizScene: 'ISV_PAY',
-            bizGroupId: accountId,
-            groupId: accountId,
-            identityPayBizScene: 'ENTERPRISE_CODE',
-        }),
-        enterprise_pay_info: JSON.stringify({
-            paymentId: options && options.paymentId,
-            ruleGroupId: options && options.standardId,
-            isvAppId: ISV_APP_ID,
-        }),
-    };
-
-    const encodedParams = encodeURIComponent(JSON.stringify(params));
-    const schemeUrl = `alipays://platformapi/startapp?appId=20000056&customBizCode=${BIZ_CODE}&customBizParams=${encodedParams}`;
-
-    my.ap.openURL({ url: schemeUrl });
+    my.navigateToMiniProgram({
+      appId: '2021002128690179',
+      path: `/pages/open/proxy/index?enterpriseId=${eid}&pageType=payment-code`,
+    });
+    return;
+  }
+
+  const params: PaymentCodeParams = {
+    pdSubBizScene: 'enterprisePay',
+    CHANNEL_INDEX: JSON.stringify(['ENTERPRISEPAYASSET_DC_ENTERPRISEPAY_DEFAULT']),
+    channelMode: 'NONE_CHANNEL_MODE',
+    specifiedEnableChannelInfo: JSON.stringify({
+      enableScene: 'agreementpay',
+      assetInfo: {
+        instId: 'INST_ALIPAY',
+        assetId: accountId,
+        assetTypeCode: 'ENTERPRISEPAY',
+        assetType: 'ENTERPRISEPAYASSET',
+      },
+    }),
+    assignJointAccountId: accountId,
+    identityPayBizInfo: JSON.stringify({
+      identityPaySubBizScene: 'ISV_PAY',
+      bizGroupId: accountId,
+      groupId: accountId,
+      identityPayBizScene: 'ENTERPRISE_CODE',
+    }),
+    enterprise_pay_info: JSON.stringify({
+      paymentId: options && options.paymentId,
+      ruleGroupId: options && options.standardId,
+      isvAppId: ISV_APP_ID,
+    }),
+  };
+
+  const encodedParams = encodeURIComponent(JSON.stringify(params));
+  const schemeUrl = `alipays://platformapi/startapp?appId=20000056&customBizCode=${BIZ_CODE}&customBizParams=${encodedParams}`;
+
+  my.ap.openURL({ url: schemeUrl });
 };