alphah пре 2 недеља
родитељ
комит
8cd87e6ca4

+ 2 - 2
.env.development

@@ -1,4 +1,4 @@
 # 配置文档参考 https://taro-docs.jd.com/docs/next/env-mode-config
-# TARO_APP_ID="开发环境下的小程序 AppID"
+TARO_APP_ID="2021006145621147"
 
-TARO_APP_API_BASE_URL="http://proxy.qcsj88888.com/api/v1"
+TARO_APP_API_BASE_URL="https://api.qcsj88888.com/api/v1"

+ 2 - 2
.env.production

@@ -1,3 +1,3 @@
-# TARO_APP_ID="生产环境下的小程序 AppID"
+TARO_APP_ID="2021006145621147"
 
-TARO_APP_API_BASE_URL=""
+TARO_APP_API_BASE_URL="https://api.qcsj88888.com/api/v1"

+ 10 - 0
.idea/.gitignore

@@ -0,0 +1,10 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Ignored default folder with query files
+/queries/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml

+ 50 - 0
.idea/codeStyles/Project.xml

@@ -0,0 +1,50 @@
+<component name="ProjectCodeStyleConfiguration">
+  <code_scheme name="Project" version="173">
+    <option name="LINE_SEPARATOR" value="&#10;" />
+    <HTMLCodeStyleSettings>
+      <option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
+    </HTMLCodeStyleSettings>
+    <JSCodeStyleSettings version="0">
+      <option name="FORCE_SEMICOLON_STYLE" value="true" />
+      <option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
+      <option name="USE_DOUBLE_QUOTES" value="false" />
+      <option name="FORCE_QUOTE_STYlE" value="true" />
+      <option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" />
+      <option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
+      <option name="SPACES_WITHIN_IMPORTS" value="true" />
+    </JSCodeStyleSettings>
+    <TypeScriptCodeStyleSettings version="0">
+      <option name="FORCE_SEMICOLON_STYLE" value="true" />
+      <option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
+      <option name="USE_DOUBLE_QUOTES" value="false" />
+      <option name="FORCE_QUOTE_STYlE" value="true" />
+      <option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" />
+      <option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
+      <option name="SPACES_WITHIN_IMPORTS" value="true" />
+    </TypeScriptCodeStyleSettings>
+    <codeStyleSettings language="HTML">
+      <option name="SOFT_MARGINS" value="100" />
+      <indentOptions>
+        <option name="INDENT_SIZE" value="2" />
+        <option name="CONTINUATION_INDENT_SIZE" value="2" />
+        <option name="TAB_SIZE" value="2" />
+      </indentOptions>
+    </codeStyleSettings>
+    <codeStyleSettings language="JavaScript">
+      <option name="SOFT_MARGINS" value="100" />
+      <indentOptions>
+        <option name="INDENT_SIZE" value="2" />
+        <option name="CONTINUATION_INDENT_SIZE" value="2" />
+        <option name="TAB_SIZE" value="2" />
+      </indentOptions>
+    </codeStyleSettings>
+    <codeStyleSettings language="TypeScript">
+      <option name="SOFT_MARGINS" value="100" />
+      <indentOptions>
+        <option name="INDENT_SIZE" value="2" />
+        <option name="CONTINUATION_INDENT_SIZE" value="2" />
+        <option name="TAB_SIZE" value="2" />
+      </indentOptions>
+    </codeStyleSettings>
+  </code_scheme>
+</component>

+ 5 - 0
.idea/codeStyles/codeStyleConfig.xml

@@ -0,0 +1,5 @@
+<component name="ProjectCodeStyleConfiguration">
+  <state>
+    <option name="USE_PER_PROJECT_SETTINGS" value="true" />
+  </state>
+</component>

+ 7 - 0
.idea/inspectionProfiles/Project_Default.xml

@@ -0,0 +1,7 @@
+<component name="InspectionProjectProfileManager">
+  <profile version="1.0">
+    <option name="myName" value="Project Default" />
+    <inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="Stylelint" enabled="true" level="ERROR" enabled_by_default="true" />
+  </profile>
+</component>

+ 6 - 0
.idea/misc.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="ms-21" project-jdk-type="JavaSDK">
+    <output url="file://$PROJECT_DIR$/out" />
+  </component>
+</project>

+ 8 - 0
.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/payment-mini.iml" filepath="$PROJECT_DIR$/.idea/payment-mini.iml" />
+    </modules>
+  </component>
+</project>

+ 9 - 0
.idea/payment-mini.iml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$" />
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

+ 6 - 0
.idea/prettier.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="PrettierConfiguration">
+    <option name="myConfigurationMode" value="AUTOMATIC" />
+  </component>
+</project>

+ 6 - 0
.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="" vcs="Git" />
+  </component>
+</project>

+ 60 - 0
.playwright-cli/console-2026-05-15T09-20-09-439Z.log

@@ -0,0 +1,60 @@
+[     505ms] [WARNING] 已设置yuyanId,其他 id 将不生效 @ https://gw.alipayobjects.com/os/lib/alipay/yuyan-monitor-sdk/1.1.0/dist/index.umd.min.js:0
+[     591ms] [WARNING] proxyWithComputed is deprecated. Please follow "Computed Properties" guide in docs. @ https://gw.alipayobjects.com/render/p/yuyan/180020010001230926/umi.368a83c2.js:56
+[     593ms] [WARNING] proxyWithComputed is deprecated. Please follow "Computed Properties" guide in docs. @ https://gw.alipayobjects.com/render/p/yuyan/180020010001230926/umi.368a83c2.js:56
+[     595ms] [WARNING] proxyWithComputed is deprecated. Please follow "Computed Properties" guide in docs. @ https://gw.alipayobjects.com/render/p/yuyan/180020010001230926/umi.368a83c2.js:56
+[     595ms] [WARNING] proxyWithComputed is deprecated. Please follow "Computed Properties" guide in docs. @ https://gw.alipayobjects.com/render/p/yuyan/180020010001230926/umi.368a83c2.js:56
+[     595ms] [WARNING] proxyWithComputed is deprecated. Please follow "Computed Properties" guide in docs. @ https://gw.alipayobjects.com/render/p/yuyan/180020010001230926/umi.368a83c2.js:56
+[     618ms] [LOG] copilot init @ https://gw.alipayobjects.com/render/p/yuyan/180020010001230926/938.cbf48ac3.async.js:37
+[     630ms] [WARNING] proxyWithComputed is deprecated. Please follow "Computed Properties" guide in docs. @ https://gw.alipayobjects.com/render/p/yuyan/180020010001230926/umi.368a83c2.js:56
+[     741ms] [WARNING] proxyWithComputed is deprecated. Please follow "Computed Properties" guide in docs. @ https://gw.alipayobjects.com/render/p/yuyan/180020010001230926/umi.368a83c2.js:56
+[     742ms] [LOG] 组件收到消息 {type: COPILOT_IS_READY} @ https://gw.alipayobjects.com/render/p/yuyan/180020010001230926/938.cbf48ac3.async.js:37
+[     742ms] [LOG] 2Received greeting: {type: COPILOT_IS_READY} @ https://gw.alipayobjects.com/render/p/yuyan/180020010001230926/938.cbf48ac3.async.js:37
+[     743ms] [LOG] copilot ready {type: COPILOT_IS_READY} @ https://gw.alipayobjects.com/render/p/yuyan/180020010001230926/938.cbf48ac3.async.js:37
+[    1050ms] [INFO] 上报异常,原因: 跳过上报 @ https://gw.alipayobjects.com/render/p/yuyan/180020010001230926/umi.368a83c2.js:55
+[    1055ms] [INFO] 上报异常,原因: 跳过上报 @ https://gw.alipayobjects.com/render/p/yuyan/180020010001230926/umi.368a83c2.js:55
+[    1144ms] [WARNING] TRACERT_WARN logPv埋点被钩子函数终止, 如果该钩子函数为业务代码,请勿 return false 或删除;如有其他问题,请联系 远笛 function(){return!!t.initBucName||(t.queue.push(arguments.length>0&&void 0!==arguments[0]?arguments[0]:{}),!1)} @ https://ur.alipay.com/tracert_a316.js:0
+[    1302ms] [LOG] copilot init @ https://gw.alipayobjects.com/render/p/yuyan/180020010001250985/6261.dcec2c22.async.js:37
+[    1583ms] [INFO] 上报异常,原因: 跳过上报 @ https://gw.alipayobjects.com/render/p/yuyan/180020010001230926/umi.368a83c2.js:55
+[    1586ms] [INFO] 上报异常,原因: 跳过上报 @ https://gw.alipayobjects.com/render/p/yuyan/180020010001230926/umi.368a83c2.js:55
+[    1586ms] [LOG] 组件收到消息 {type: COPILOT_IS_READY} @ https://gw.alipayobjects.com/render/p/yuyan/180020010001230926/938.cbf48ac3.async.js:37
+[    1587ms] [LOG] 2Received greeting: {type: COPILOT_IS_READY} @ https://gw.alipayobjects.com/render/p/yuyan/180020010001230926/938.cbf48ac3.async.js:37
+[    1587ms] [LOG] copilot ready {type: COPILOT_IS_READY} @ https://gw.alipayobjects.com/render/p/yuyan/180020010001230926/938.cbf48ac3.async.js:37
+[    1587ms] [LOG] 组件收到消息 {type: COPILOT_IS_READY} @ https://gw.alipayobjects.com/render/p/yuyan/180020010001250985/6261.dcec2c22.async.js:37
+[    1588ms] [LOG] 2Received greeting: {type: COPILOT_IS_READY} @ https://gw.alipayobjects.com/render/p/yuyan/180020010001250985/6261.dcec2c22.async.js:37
+[    1588ms] [LOG] copilot ready {type: COPILOT_IS_READY} @ https://gw.alipayobjects.com/render/p/yuyan/180020010001250985/6261.dcec2c22.async.js:37
+[    1591ms] [LOG] [open-widget-helper]-entryList false {adviceSwitch: false, backTopSwitch: false, forumSwitch: false, linksSwitch: false, feedbackSwitch: false} @ https://gw.alipayobjects.com/render/p/yuyan/180020010001250985/p__Home__index.c50db7e5.async.js:0
+[    1594ms] [LOG] copilot ready undefined @ https://gw.alipayobjects.com/render/p/yuyan/180020010001230926/938.cbf48ac3.async.js:37
+[    1594ms] [LOG] copilot ready undefined @ https://gw.alipayobjects.com/render/p/yuyan/180020010001250985/6261.dcec2c22.async.js:37
+[    1613ms] [LOG] [open-widget-helper]-entryList false {} @ https://gw.alipayobjects.com/render/p/yuyan/180020010001250985/p__Home__index.c50db7e5.async.js:0
+[    1625ms] [LOG] 组件收到消息 {"type":"iframOnload"} @ https://gw.alipayobjects.com/render/p/yuyan/180020010001230926/938.cbf48ac3.async.js:37
+[    1625ms] [LOG] 组件收到消息 {"type":"iframOnload"} @ https://gw.alipayobjects.com/render/p/yuyan/180020010001250985/6261.dcec2c22.async.js:37
+[    1625ms] [LOG] 组件收到消息 {"type":"getBucUserId","content":null} @ https://gw.alipayobjects.com/render/p/yuyan/180020010001230926/938.cbf48ac3.async.js:37
+[    1628ms] [LOG] 组件收到消息 {"type":"getBucUserId","content":null} @ https://gw.alipayobjects.com/render/p/yuyan/180020010001250985/6261.dcec2c22.async.js:37
+[    1628ms] [LOG] 组件收到消息 {"type":"getBucUserId"} @ https://gw.alipayobjects.com/render/p/yuyan/180020010001230926/938.cbf48ac3.async.js:37
+[    1628ms] [LOG] 组件收到消息 {"type":"getBucUserId"} @ https://gw.alipayobjects.com/render/p/yuyan/180020010001250985/6261.dcec2c22.async.js:37
+[    1653ms] [INFO] 上报异常,原因: 跳过上报 @ https://gw.alipayobjects.com/render/p/yuyan/180020010001230926/umi.368a83c2.js:55
+[    1748ms] [INFO] 上报异常,原因: 跳过上报 @ https://gw.alipayobjects.com/render/p/yuyan/180020010001230926/umi.368a83c2.js:55
+[    1817ms] [INFO] 上报异常,原因: 跳过上报 @ https://gw.alipayobjects.com/render/p/yuyan/180020010001230926/umi.368a83c2.js:55
+[    1847ms] [LOG] [umi-plugin-monitor] 当前监控实例为: o @ https://gw.alipayobjects.com/render/p/yuyan/180020010001250985/umi.3ead4a7e.js:60
+[    1848ms] [LOG] [open-widget-helper]-entryList false {} @ https://gw.alipayobjects.com/render/p/yuyan/180020010001250985/p__Home__index.c50db7e5.async.js:0
+[    1926ms] [LOG] [umi-plugin-monitor] 当前监控实例为: o @ https://gw.alipayobjects.com/render/p/yuyan/180020010001250985/umi.3ead4a7e.js:60
+[    2064ms] [LOG] [open-widget-helper]-entryList false {} @ https://gw.alipayobjects.com/render/p/yuyan/180020010001250985/p__Home__index.c50db7e5.async.js:0
+[    2415ms] [LOG] [umi-plugin-monitor] 当前监控实例为: o @ https://gw.alipayobjects.com/render/p/yuyan/180020010001250985/umi.3ead4a7e.js:60
+[    2525ms] [WARNING] TRACERT-WARN tracert 插件 before 钩子执行失败 @ https://gw.alipayobjects.com/render/p/yuyan/180020010001250985/umi.3ead4a7e.js:59
+[    2528ms] [WARNING] TRACERT-WARN tracert 插件 before 钩子执行失败 @ https://gw.alipayobjects.com/render/p/yuyan/180020010001250985/umi.3ead4a7e.js:59
+[    2530ms] [WARNING] TRACERT-WARN tracert 插件 before 钩子执行失败 @ https://gw.alipayobjects.com/render/p/yuyan/180020010001250985/umi.3ead4a7e.js:59
+[    2531ms] [WARNING] TRACERT-WARN tracert 插件 before 钩子执行失败 @ https://gw.alipayobjects.com/render/p/yuyan/180020010001250985/umi.3ead4a7e.js:59
+[    2533ms] [WARNING] TRACERT-WARN tracert 插件 before 钩子执行失败 @ https://gw.alipayobjects.com/render/p/yuyan/180020010001250985/umi.3ead4a7e.js:59
+[    2534ms] [WARNING] TRACERT-WARN tracert 插件 before 钩子执行失败 @ https://gw.alipayobjects.com/render/p/yuyan/180020010001250985/umi.3ead4a7e.js:59
+[    2576ms] [INFO] 上报异常,原因: 跳过上报 @ https://gw.alipayobjects.com/render/p/yuyan/180020010001230926/umi.368a83c2.js:55
+[    2594ms] [WARNING] TRACERT-WARN tracert 插件 before 钩子执行失败 @ https://gw.alipayobjects.com/render/p/yuyan/180020010001230926/umi.368a83c2.js:55
+[    2595ms] [WARNING] TRACERT-WARN tracert 插件 before 钩子执行失败 @ https://gw.alipayobjects.com/render/p/yuyan/180020010001230926/umi.368a83c2.js:55
+[    2595ms] [WARNING] TRACERT-WARN tracert 插件 before 钩子执行失败 @ https://gw.alipayobjects.com/render/p/yuyan/180020010001230926/umi.368a83c2.js:55
+[    2596ms] [WARNING] TRACERT-WARN tracert 插件 before 钩子执行失败 @ https://gw.alipayobjects.com/render/p/yuyan/180020010001230926/umi.368a83c2.js:55
+[    2597ms] [WARNING] TRACERT-WARN tracert 插件 before 钩子执行失败 @ https://gw.alipayobjects.com/render/p/yuyan/180020010001230926/umi.368a83c2.js:55
+[    2597ms] [WARNING] TRACERT-WARN tracert 插件 before 钩子执行失败 @ https://gw.alipayobjects.com/render/p/yuyan/180020010001230926/umi.368a83c2.js:55
+[    3059ms] [INFO] 上报异常,原因: 跳过上报 @ https://gw.alipayobjects.com/render/p/yuyan/180020010001230926/umi.368a83c2.js:55
+[    3346ms] [INFO] 上报异常,原因: 跳过上报 @ https://gw.alipayobjects.com/render/p/yuyan/180020010001230926/umi.368a83c2.js:55
+[    3677ms] [INFO] 上报异常,原因: 跳过上报 @ https://gw.alipayobjects.com/render/p/yuyan/180020010001230926/umi.368a83c2.js:55
+[   30811ms] [INFO] 上报异常,原因: 跳过上报 @ https://gw.alipayobjects.com/render/p/yuyan/180020010001230926/umi.368a83c2.js:55
+[   60830ms] [INFO] 上报异常,原因: 跳过上报 @ https://gw.alipayobjects.com/render/p/yuyan/180020010001230926/umi.368a83c2.js:55

+ 19 - 0
.playwright-cli/page-2026-05-15T09-20-10-293Z.yml

@@ -0,0 +1,19 @@
+- generic [ref=e3]:
+  - banner [ref=e4]:
+    - generic [ref=e5]:
+      - generic [ref=e6]:
+        - link "支付宝文档中心" [ref=e7] [cursor=pointer]:
+          - /url: https://open.alipay.com/
+          - img "支付宝文档中心" [ref=e8]
+        - generic [ref=e9]: 文档中心
+      - img "right" [ref=e11]:
+        - img [ref=e12]
+      - list
+    - generic [ref=e15]:
+      - generic [ref=e18]:
+        - textbox "小程序跳转" [ref=e19]
+        - img "search-icon" [ref=e22] [cursor=pointer]
+      - link "控制台" [ref=e23] [cursor=pointer]:
+        - /url: https://open.alipay.com/develop/manage
+      - generic [ref=e27] [cursor=pointer]: 登录
+  - main [ref=e28]

+ 3 - 0
mini.project.json

@@ -0,0 +1,3 @@
+{
+  "format": 2
+}


Разлика између датотеке није приказан због своје велике величине
+ 326 - 149
package-lock.json


+ 1 - 0
package.json

@@ -51,6 +51,7 @@
   "dependencies": {
     "@babel/runtime": "^7.24.4",
     "@nutui/nutui-react-taro": "^3.0.19-cpp.26-beta.5",
+    "@swc/core": "^1.15.33",
     "@tarojs/components": "4.1.11",
     "@tarojs/helper": "4.1.11",
     "@tarojs/plugin-framework-react": "4.1.11",

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

@@ -76,7 +76,7 @@ export default function Index() {
 
   const qrCode = () => {
     try {
-      codeWithAlipay(enterpriseId);
+      codeWithAlipay(accountId, { enterpriseId });
     } catch (error) {
       showToast('fail', (error as Error).message || '调用付款失败');
     }

+ 55 - 10
src/pages/login/index.less

@@ -25,6 +25,61 @@
   padding: 40px 18px;
 }
 
+.form-item {
+  margin-bottom: 30px;
+  background: #fff;
+  border-radius: 12px;
+  padding: 20px 24px;
+}
+
+.form-item-label {
+  font-size: 26px;
+  color: #1f2b45;
+  font-weight: 500;
+  margin-bottom: 16px;
+}
+
+.mobile-input-row {
+  display: flex;
+  align-items: center;
+}
+
+.phone-prefix {
+  font-size: 30px;
+  font-weight: 600;
+  color: #333;
+  margin-right: 16px;
+  padding-right: 16px;
+  border-right: 2px solid #eee;
+}
+
+.mobile-input {
+  flex: 1;
+  font-size: 30px;
+  border: none;
+  outline: none;
+  height: 50px;
+}
+
+.code-input-row {
+  display: flex;
+  align-items: center;
+}
+
+.code-input {
+  flex: 1;
+  font-size: 30px;
+  border: none;
+  outline: none;
+  height: 50px;
+}
+
+.send-code-btn {
+  flex-shrink: 0;
+  min-width: 180px;
+  margin-left: 16px;
+}
+
 .login-button {
   margin-top: 50px;
 }
@@ -38,13 +93,3 @@
 .agreement-link {
   color: #1e61ff;
 }
-
-.icon-label {
-  width: 16px;
-  height: 16px;
-}
-
-.icon-label {
-  width: 16px;
-  height: 16px;
-}

+ 120 - 85
src/pages/login/index.tsx

@@ -1,69 +1,98 @@
-import { View, Text } from '@tarojs/components';
+import { View, Text, Input as TaroInput } from '@tarojs/components';
 import Taro, { useLoad } from '@tarojs/taro';
-import { useCallback, useState } from 'react';
+import { useCallback, useState, useRef } from 'react';
 import { Button, Checkbox, Form, Input, Dialog } from '@nutui/nutui-react-taro';
 import UserAgreement from '@/components/UserAgreement';
-import { CloudIcon, PasswordIcon, UserIcon } from '@/components/Icon';
+import { UserSmileIcon } from '@/components/Icon';
 import { useLoginStore } from '@/stores/login';
-import { LoginFormData } from '@/schemas/login';
+import { sendSmsCodeApi } from '@/services/apis';
+import { isValidMobile } from '@/schemas/login';
 
 import './index.less';
 import { useUserStore } from '@/stores/user';
 
 export default function Login() {
-  const { formData, updateField, validate } = useLoginStore();
+  const { formData, updateField, countdown, startCountdown } = useLoginStore();
 
   const [showDialog, setShowDialog] = useState(false);
   const [agreed, setAgreed] = useState(false);
   const [loading, setLoading] = useState(false);
+  const [sending, setSending] = useState(false);
 
   useLoad(() => {
     console.log('Login page loaded.');
   });
 
-  const handleInputChange = useCallback(
-    (key: keyof LoginFormData) => (value: any) => {
-      if (typeof value === 'string') {
-        updateField(key, value);
-        return;
-      }
-      if (value && typeof value === 'object') {
-        const next =
-          (value.detail && value.detail.value) || (value.target && value.target.value) || '';
-        updateField(key, String(next));
-      }
+  const getInputValue = (value: any): string => {
+    if (typeof value === 'string') return value;
+    if (value && typeof value === 'object') {
+      if (value.detail && value.detail.value) return value.detail.value;
+      if (value.target && value.target.value) return value.target.value;
+    }
+    return '';
+  };
+
+  const handleMobileChange = useCallback(
+    (value: any) => {
+      const v = getInputValue(value).replace(/\D/g, '').slice(0, 11);
+      updateField('mobile', v);
+    },
+    [updateField],
+  );
+
+  const handleCodeChange = useCallback(
+    (value: any) => {
+      const v = getInputValue(value).replace(/\D/g, '').slice(0, 6);
+      updateField('code', v);
     },
     [updateField],
   );
 
+  const handleSendCode = async () => {
+    if (!isValidMobile(formData.mobile)) {
+      Taro.showToast({ title: '请输入正确的手机号', icon: 'none' });
+      return;
+    }
+    setSending(true);
+    try {
+      await sendSmsCodeApi(formData.mobile);
+      startCountdown();
+      Taro.showToast({ title: '验证码已发送', icon: 'success' });
+    } catch (error: unknown) {
+      console.error('发送验证码失败:', error);
+      Taro.showToast({ title: (error as Error).message || '发送失败', icon: 'none' });
+    } finally {
+      setSending(false);
+    }
+  };
+
   const handleLogin = async (nextAgreed: boolean = agreed) => {
-    if (validate()) {
-      const agreedValue = nextAgreed;
-      console.log('Login button clicked. Agreed:', agreedValue);
-
-      if (!agreedValue) {
-        setShowDialog(true);
-        return;
-      }
-
-      console.log('Login button clicked. Logging in...');
-
-      try {
-        setLoading(true);
-        await useUserStore.getState().login(formData);
-        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 });
-      } finally {
-        setLoading(false);
-      }
-
-    } else {
-      const firstError = Object.values(useLoginStore.getState().errors)[0];
-      if (firstError) {
-        Taro.showToast({ title: firstError, icon: 'none', duration: 2000 });
-      }
+    const { formData: fd, errors } = useLoginStore.getState();
+
+    if (!fd.mobile || fd.mobile.length < 11) {
+      Taro.showToast({ title: '请输入正确的手机号', icon: 'none' });
+      return;
+    }
+    if (!fd.code || fd.code.length < 4) {
+      Taro.showToast({ title: '请输入验证码', icon: 'none' });
+      return;
+    }
+
+    const agreedValue = nextAgreed;
+    if (!agreedValue) {
+      setShowDialog(true);
+      return;
+    }
+
+    try {
+      setLoading(true);
+      await useUserStore.getState().login(fd);
+      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 });
+    } finally {
+      setLoading(false);
     }
   };
 
@@ -85,60 +114,66 @@ export default function Login() {
       </View>
 
       <View className="login-card">
-        <Form
-          divider
-          labelPosition="left"
-          starPosition="right"
-          style={{ '--nutui-form-item-label-width': '30px' } as any}
-        >
-          <Form.Item label={<CloudIcon />} name="systemId">
-            <Input
-              value={formData.systemId}
-              onChange={handleInputChange('systemId')}
-              placeholder="请输入系统号"
-              clearable
-              className="login-input"
-              disabled={loading}
-            />
-          </Form.Item>
-
-          <Form.Item label={<UserIcon />} name="username">
-            <Input
-              value={formData.username}
-              onChange={handleInputChange('username')}
-              placeholder="请输入用户名"
-              clearable
-              className="login-input"
+        {/* 手机号输入 */}
+        <View className="form-item">
+          <View className="form-item-label">手机号</View>
+          <View className="mobile-input-row">
+            <View className="phone-prefix">+86</View>
+            <TaroInput
+              className="mobile-input"
+              value={formData.mobile}
+              onInput={handleMobileChange}
+              placeholder="请输入手机号"
+              type="number"
+              maxlength={11}
               disabled={loading}
             />
-          </Form.Item>
-
-          <Form.Item label={<PasswordIcon />} name="password">
-            <Input
-              value={formData.password}
-              onChange={handleInputChange('password')}
-              placeholder="请输入密码"
-              type="password"
-              clearable
-              className="login-input"
+          </View>
+        </View>
+
+        {/* 验证码输入 */}
+        <View className="form-item">
+          <View className="form-item-label">验证码</View>
+          <View className="code-input-row">
+            <TaroInput
+              className="code-input"
+              value={formData.code}
+              onInput={handleCodeChange}
+              placeholder="请输入验证码"
+              type="number"
+              maxlength={6}
               disabled={loading}
             />
-          </Form.Item>
-        </Form>
-
-        <View className="login-button">
-          <Button block type="primary" size="large" onClick={() => handleLogin()} disabled={loading}>
-            {loading ? '登录中...' : '登录'}
-          </Button>
+            <Button
+              className="send-code-btn"
+              size="small"
+              type={countdown > 0 ? 'default' : 'primary'}
+              disabled={countdown > 0 || sending}
+              onClick={handleSendCode}
+            >
+              {countdown > 0 ? `${countdown}s` : sending ? '发送中...' : '获取验证码'}
+            </Button>
+          </View>
         </View>
 
+        <Button
+          className="login-button"
+          block
+          type="primary"
+          size="large"
+          onClick={() => handleLogin()}
+          disabled={loading}
+        >
+          {loading ? '登录中...' : '登录'}
+        </Button>
+
         <View className="login-agreement">
           <Checkbox
             checked={agreed}
             onChange={setAgreed}
             label={
               <Text>
-                我已经
+                我已阅读并同意
                 <UserAgreement />
               </Text>
             }

+ 15 - 11
src/schemas/login.ts

@@ -1,26 +1,30 @@
 export interface LoginFormData {
-  systemId: string;
-  username: string;
-  password: string;
+  mobile: string;
+  code: string;
 }
 
 export interface ValidationError {
   [key: string]: string;
 }
 
+/** 校验手机号格式 */
+export function isValidMobile(mobile: string): boolean {
+  return /^1[3-9]\d{9}$/.test(mobile.trim());
+}
+
 export function validateLoginForm(data: LoginFormData): ValidationError {
   const errors: ValidationError = {};
 
-  if (!data.systemId || data.systemId.trim() === '') {
-    errors.systemId = '系统号不能为空';
-  }
-
-  if (!data.username || data.username.trim() === '') {
-    errors.username = '用户名不能为空';
+  if (!data.mobile || data.mobile.trim() === '') {
+    errors.mobile = '手机号不能为空';
+  } else if (!isValidMobile(data.mobile)) {
+    errors.mobile = '手机号格式错误';
   }
 
-  if (!data.password || data.password.trim() === '') {
-    errors.password = '密码不能为空';
+  if (!data.code || data.code.trim() === '') {
+    errors.code = '验证码不能为空';
+  } else if (data.code.trim().length < 4) {
+    errors.code = '验证码长度不正确';
   }
 
   return errors;

+ 33 - 11
src/services/apis.ts

@@ -16,7 +16,7 @@ const handleExpiredLogin = () => {
 
 
 export const requestApi = async (options: Taro.request.Option, defaultErrorMsg: string = '') => {
-    
+
     const accessToken = Auth.getAccessToken();
     if (accessToken) {
         options.header = options.header || {};
@@ -24,6 +24,7 @@ export const requestApi = async (options: Taro.request.Option, defaultErrorMsg:
     }
 
     options.data = humps.decamelizeKeys(options.data);
+    console.log('[API请求]', options.url, JSON.stringify(options.data));
 
     try {
         const response = await Taro.request({
@@ -32,7 +33,7 @@ export const requestApi = async (options: Taro.request.Option, defaultErrorMsg:
             timeout: 10000,
         });
 
-        if (response.statusCode === 200) {         
+        if (response.statusCode === 200) {
             if (response.data.code === 0) {
                 return humps.camelizeKeys(response.data.data);
             } else {
@@ -42,24 +43,45 @@ export const requestApi = async (options: Taro.request.Option, defaultErrorMsg:
             throw new Error(`${defaultErrorMsg} (${response.statusCode})`);
         }
 
-    } catch (error) {
-        if (error.status === 401) {
+    } catch (error: any) {
+        if (error && error.status === 401) {
             handleExpiredLogin();
-        } 
-        throw new Error(error.data.msg || defaultErrorMsg);
+        }
+        throw new Error((error && error.data && error.data.msg) || defaultErrorMsg);
     }
 };
 
-// 登录 API
+// 发送短信验证码
+export const sendSmsCodeApi = async (mobile: string) => {
+    return requestApi(
+      {
+        url: '/system/auth/sms-code',
+        method: 'POST',
+        header: {
+          'Content-Type': 'application/json',
+        },
+        data: {
+          mobile,
+          template_name: 'verify',
+        },
+      },
+      '发送验证码失败',
+    );
+};
+
+// 短信验证码登录
 export const loginApi = async (data: LoginFormData) => {
     return requestApi({
-        url: '/system/auth/login/mini',
+        url: '/system/auth/login/sms',
         method: 'POST',
-        dataType: 'json',
         header: {
             "Content-Type": "application/json",
         },
-        data,
+        data: {
+            mobile: data.mobile,
+            code: data.code,
+            template_name: 'verify',
+        },
     }, '登录失败');
 };
 
@@ -101,4 +123,4 @@ export const listTransferApi = async (
         },
         data,
     }, '获取转账记录失败');
-}
+}

+ 20 - 4
src/stores/login.ts

@@ -4,21 +4,24 @@ import { type LoginFormData, validateLoginForm } from '@/schemas/login';
 interface LoginState {
   formData: LoginFormData;
   errors: Partial<Record<keyof LoginFormData, string>>;
+  /** 倒计时秒数,0 表示可发送 */
+  countdown: number;
   updateField: <K extends keyof LoginFormData>(key: K, value: LoginFormData[K]) => void;
   validate: () => boolean;
   clearErrors: () => void;
   reset: () => void;
+  startCountdown: () => void;
 }
 
 const initialFormData: LoginFormData = {
-  systemId: '',
-  username: '',
-  password: '',
+  mobile: '',
+  code: '',
 };
 
 export const useLoginStore = create<LoginState>((set, get) => ({
   formData: initialFormData,
   errors: {},
+  countdown: 0,
 
   updateField: (key, value) => {
     set((state) => ({
@@ -35,5 +38,18 @@ export const useLoginStore = create<LoginState>((set, get) => ({
 
   clearErrors: () => set({ errors: {} }),
 
-  reset: () => set({ formData: initialFormData, errors: {} }),
+  reset: () => set({ formData: initialFormData, errors: {}, countdown: 0 }),
+
+  startCountdown: () => {
+    set({ countdown: 60 });
+    const timer = setInterval(() => {
+      const { countdown } = get();
+      if (countdown <= 1) {
+        clearInterval(timer);
+        set({ countdown: 0 });
+      } else {
+        set({ countdown: countdown - 1 });
+      }
+    }, 1000);
+  },
 }));

+ 53 - 20
src/stores/user.ts

@@ -1,5 +1,5 @@
 import { LoginFormData } from '@/schemas/login';
-import { loginApi } from '@/services/apis';
+import { getUserInfoApi, loginApi } from '@/services/apis';
 import { Auth } from '@/utils/auth';
 import Taro from '@tarojs/taro';
 import { create } from 'zustand';
@@ -11,24 +11,49 @@ export interface UserState {
     userId: string;
     userName: string;
     avatar: string;
+    /** 是否正在加载用户信息 */
+    loading: boolean;
 
-    getUserInfo: () => void;
+    getUserInfo: () => Promise<void>;
     setUserInfo: (userInfo: Partial<UserState>) => void;
     logout: () => void;
     login: (data: LoginFormData) => Promise<void>;
-    updateEnterpriseId: (enterpriseId: string) => void;
-    updateAccountId: (accountId: string) => void;
 }
 
 // 创建用户状态管理store
-export const useUserStore = create<UserState>((set, get) => ({
-    enterpriseId: '2088480767913636',
-    accountId: '2088480767913636',
+export const useUserStore = create<UserState>((set) => ({
+    enterpriseId: '',
+    accountId: '',
     userId: '',
     userName: '',
     avatar: '',
+    loading: false,
 
-    getUserInfo: () => { },
+    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;
+        }
+    },
 
     setUserInfo: (userInfo) => set((state) => ({
         ...state,
@@ -36,13 +61,14 @@ export const useUserStore = create<UserState>((set, get) => ({
     })),
 
     logout: () => {
-        set(() => ({
+        set({
             enterpriseId: '',
             accountId: '',
             userId: '',
             userName: '',
             avatar: '',
-        }))
+            loading: false,
+        });
         Auth.clearToken();
         Auth.requireAuth();
     },
@@ -50,17 +76,24 @@ export const useUserStore = create<UserState>((set, get) => ({
     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,
+        });
     },
-
-    updateEnterpriseId: (enterpriseId) => set((state) => ({
-        ...state,
-        enterpriseId,
-    })),
-
-    updateAccountId: (accountId) => set((state) => ({
-        ...state,
-        accountId,
-    })),
 }));
 
 

+ 135 - 68
src/utils/alipay.ts

@@ -1,97 +1,164 @@
 import Taro from "@tarojs/taro";
 
-
+// 支付宝小程序全局 API
+declare const my: {
+    navigateToMiniProgram: (options: { appId: string; path: string }) => void;
+    ap: {
+        openURL: (options: { url: string }) => void;
+    };
+};
+
+// ====== 配置常量 ======
+// ISV 小程序的 AppId(取自环境变量 TARO_APP_ID)
+const ISV_APP_ID = process.env.TARO_APP_ID || '';
+// TODO: 付款码 bizCode,联系支付宝 BD 申请
+const BIZ_CODE = '';
+
+// ====== 类型定义 ======
 interface AlipayScanExternalThrough {
-    // 默认值:enterprisePay,适用:收钱码,当面付,铁路线下支付
-    pdSubBizScene: 'enterprisePay'
-
-    // 按照示例传参,仅需要替换assetId为共同账户id(accountId,从企业签约状态变更消息通知里获取)
-    specifiedEnableChannelInfo: {
-        enableScene: "agreementpay",
-        assetInfo: {
-            instId: "INST_ALIPAY",
-            assetId: string,
-            assetTypeCode: "ENTERPRISEPAY",
-            assetType: "ENTERPRISEPAYASSET",
-        }
-    }
-
-    // 共同账户id(accountId,从企业签约状态变更消息通知里获取)
-    assignJointAccountId: string
-
-    // 企业码身份付业务字段
-    identityPayBizInfo: {
-        identityPayBizScene: 'ENTERPRISE_CODE',
-        identityPaySubBizScene: 'ISV_PAY',
-        groupId: string,
-        bizGroupId: string,
-        expenseGroupId?: string,
-    }
-
-    // 渠道
-    CHANNEL_INDEX: 'ENTERPRISEPAYASSET_DC_ENTERPRISEPAY_DEFAULT'
-
-    // 因公付大字段
-    sourcePlatformInfo: {
-        ruleGroupId?: string,
-        paymentId?: string,
-        isvAppId?: 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;
+}
 
+// ====== 工具函数 ======
 export const isAlipayEnv = () => {
-    return Taro.getEnv() === Taro.ENV_TYPE.ALIPAY
-}
+    return Taro.getEnv() === Taro.ENV_TYPE.ALIPAY;
+};
+
+/**
+ * 企业码扫码付
+ * 跳转到支付宝企业码小程序,打开摄像头扫码,用企业账户付款
+ *
+ * @param enterpriseId - 企业ID
+ * @param accountId   - 共同账户ID(从登录后 getUserInfo 获取)
+ * @param options      - 可选:费控规则ID、付款事由ID
+ */
+export const scanWithAlipay = (
+    enterpriseId: string,
+    accountId: string,
+    options?: { standardId?: string; paymentId?: string },
+) => {
+    if (!isAlipayEnv()) {
+        throw new Error('非支付宝环境,无法使用企业码扫码付');
+    }
 
+    console.log('[scanWithAlipay] enterpriseId:', enterpriseId);
+    console.log('[scanWithAlipay] accountId:', accountId);
 
-export const scanWithAlipay = (enterpriseId: string, accountId: string, standardId?: string) => {
-    if (!isAlipayEnv()) {
-        throw new Error('非支付宝环境,无法使用企业码扫码付')
+    if (!enterpriseId || !accountId) {
+        throw new Error('企业ID或账户ID为空,请确认登录信息');
     }
 
     const externalThrough: AlipayScanExternalThrough = {
         pdSubBizScene: 'enterprisePay',
-        specifiedEnableChannelInfo: {
+        specifiedEnableChannelInfo: JSON.stringify({
             enableScene: "agreementpay",
             assetInfo: {
                 instId: "INST_ALIPAY",
                 assetId: accountId,
                 assetTypeCode: "ENTERPRISEPAY",
                 assetType: "ENTERPRISEPAYASSET",
-            }
-        },
+            },
+        }),
         assignJointAccountId: accountId,
-        identityPayBizInfo: {
+        identityPayBizInfo: JSON.stringify({
             identityPayBizScene: 'ENTERPRISE_CODE',
             identityPaySubBizScene: 'ISV_PAY',
             groupId: accountId,
             bizGroupId: accountId,
-            expenseGroupId: standardId,
-        },
-        CHANNEL_INDEX: 'ENTERPRISEPAYASSET_DC_ENTERPRISEPAY_DEFAULT',
-        sourcePlatformInfo: {
-            ruleGroupId: standardId,
-        },
-    }
-
-    const externalThroughStr = JSON.stringify(externalThrough)
+            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', // 企业码小程序 appId
-        path: `/pages/open/proxy/index?enterpriseId=${enterpriseId}&pageType=scan&externalThrough=${externalThroughStr}`
-    })
-}
-
-
-// 付款码
-export const codeWithAlipay = (enterpriseId: string) => {
+        appId: '2021002128690179',
+        path: `/pages/open/proxy/index?enterpriseId=${enterpriseId}&pageType=scan&externalThrough=${externalThroughStr}`,
+    });
+};
+
+/**
+ * 企业码付款码
+ * 通过 scheme 直接拉起支付宝付款码页面,商户扫码收款
+ *
+ * @param accountId   - 共同账户ID
+ * @param options      - 可选:费控规则ID、付款事由ID
+ */
+export const codeWithAlipay = (
+    accountId: string,
+    options?: { enterpriseId?: string; standardId?: string; paymentId?: string },
+) => {
     if (!isAlipayEnv()) {
-        throw new Error('非支付宝环境,无法使用企业码扫码付')
+        throw new Error('非支付宝环境,无法使用企业码付款码');
     }
 
-    my.navigateToMiniProgram({
-        appId: '2021002128690179', // 企业码小程序 appId
-        path: `/pages/open/proxy/index?enterpriseId=${enterpriseId}&pageType=payment-code`
-    })
-}
+    // 如果 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;
+    }
+
+    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 });
+};

Неке датотеке нису приказане због велике количине промена