Browse Source

优化h5sdk的侧边栏功能

ith5 2 weeks ago
parent
commit
dccd5bb006

+ 1778 - 0
pnpm-lock.yaml

@@ -0,0 +1,1778 @@
+lockfileVersion: '9.0'
+
+settings:
+  autoInstallPeers: true
+  excludeLinksFromLockfile: false
+
+importers:
+
+  .:
+    dependencies:
+      axios:
+        specifier: ^1.13.6
+        version: 1.14.0
+      clsx:
+        specifier: ^2.1.1
+        version: 2.1.1
+      crypto-js:
+        specifier: ^4.2.0
+        version: 4.2.0
+      jotai:
+        specifier: ^2.18.1
+        version: 2.19.0(@babel/core@7.29.0)(@babel/template@7.28.6)(@types/react@19.2.14)(react@19.2.4)
+      lucide-react:
+        specifier: ^0.577.0
+        version: 0.577.0(react@19.2.4)
+      react:
+        specifier: ^19.2.4
+        version: 19.2.4
+      react-dom:
+        specifier: ^19.2.4
+        version: 19.2.4(react@19.2.4)
+      react-draggable:
+        specifier: ^4.5.0
+        version: 4.5.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+      tailwind-merge:
+        specifier: ^3.5.0
+        version: 3.5.0
+    devDependencies:
+      '@tailwindcss/vite':
+        specifier: ^4.2.1
+        version: 4.2.2(vite@7.3.1(jiti@2.6.1)(lightningcss@1.32.0))
+      '@types/crypto-js':
+        specifier: ^4.2.2
+        version: 4.2.2
+      '@types/react':
+        specifier: ^19.2.14
+        version: 19.2.14
+      '@types/react-dom':
+        specifier: ^19.2.3
+        version: 19.2.3(@types/react@19.2.14)
+      '@vitejs/plugin-react':
+        specifier: ^5.1.4
+        version: 5.2.0(vite@7.3.1(jiti@2.6.1)(lightningcss@1.32.0))
+      tailwindcss:
+        specifier: ^4.2.1
+        version: 4.2.2
+      typescript:
+        specifier: ~5.9.3
+        version: 5.9.3
+      vite:
+        specifier: ^7.3.1
+        version: 7.3.1(jiti@2.6.1)(lightningcss@1.32.0)
+
+packages:
+
+  '@babel/code-frame@7.29.0':
+    resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/compat-data@7.29.0':
+    resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/core@7.29.0':
+    resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/generator@7.29.1':
+    resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helper-compilation-targets@7.28.6':
+    resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helper-globals@7.28.0':
+    resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helper-module-imports@7.28.6':
+    resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helper-module-transforms@7.28.6':
+    resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0
+
+  '@babel/helper-plugin-utils@7.28.6':
+    resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helper-string-parser@7.27.1':
+    resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helper-validator-identifier@7.28.5':
+    resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helper-validator-option@7.27.1':
+    resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helpers@7.29.2':
+    resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/parser@7.29.2':
+    resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==}
+    engines: {node: '>=6.0.0'}
+    hasBin: true
+
+  '@babel/plugin-transform-react-jsx-self@7.27.1':
+    resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+
+  '@babel/plugin-transform-react-jsx-source@7.27.1':
+    resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+
+  '@babel/template@7.28.6':
+    resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/traverse@7.29.0':
+    resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/types@7.29.0':
+    resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==}
+    engines: {node: '>=6.9.0'}
+
+  '@esbuild/aix-ppc64@0.27.4':
+    resolution: {integrity: sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==}
+    engines: {node: '>=18'}
+    cpu: [ppc64]
+    os: [aix]
+
+  '@esbuild/android-arm64@0.27.4':
+    resolution: {integrity: sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [android]
+
+  '@esbuild/android-arm@0.27.4':
+    resolution: {integrity: sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==}
+    engines: {node: '>=18'}
+    cpu: [arm]
+    os: [android]
+
+  '@esbuild/android-x64@0.27.4':
+    resolution: {integrity: sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [android]
+
+  '@esbuild/darwin-arm64@0.27.4':
+    resolution: {integrity: sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [darwin]
+
+  '@esbuild/darwin-x64@0.27.4':
+    resolution: {integrity: sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [darwin]
+
+  '@esbuild/freebsd-arm64@0.27.4':
+    resolution: {integrity: sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [freebsd]
+
+  '@esbuild/freebsd-x64@0.27.4':
+    resolution: {integrity: sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [freebsd]
+
+  '@esbuild/linux-arm64@0.27.4':
+    resolution: {integrity: sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [linux]
+
+  '@esbuild/linux-arm@0.27.4':
+    resolution: {integrity: sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==}
+    engines: {node: '>=18'}
+    cpu: [arm]
+    os: [linux]
+
+  '@esbuild/linux-ia32@0.27.4':
+    resolution: {integrity: sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==}
+    engines: {node: '>=18'}
+    cpu: [ia32]
+    os: [linux]
+
+  '@esbuild/linux-loong64@0.27.4':
+    resolution: {integrity: sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==}
+    engines: {node: '>=18'}
+    cpu: [loong64]
+    os: [linux]
+
+  '@esbuild/linux-mips64el@0.27.4':
+    resolution: {integrity: sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==}
+    engines: {node: '>=18'}
+    cpu: [mips64el]
+    os: [linux]
+
+  '@esbuild/linux-ppc64@0.27.4':
+    resolution: {integrity: sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==}
+    engines: {node: '>=18'}
+    cpu: [ppc64]
+    os: [linux]
+
+  '@esbuild/linux-riscv64@0.27.4':
+    resolution: {integrity: sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==}
+    engines: {node: '>=18'}
+    cpu: [riscv64]
+    os: [linux]
+
+  '@esbuild/linux-s390x@0.27.4':
+    resolution: {integrity: sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==}
+    engines: {node: '>=18'}
+    cpu: [s390x]
+    os: [linux]
+
+  '@esbuild/linux-x64@0.27.4':
+    resolution: {integrity: sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [linux]
+
+  '@esbuild/netbsd-arm64@0.27.4':
+    resolution: {integrity: sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [netbsd]
+
+  '@esbuild/netbsd-x64@0.27.4':
+    resolution: {integrity: sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [netbsd]
+
+  '@esbuild/openbsd-arm64@0.27.4':
+    resolution: {integrity: sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [openbsd]
+
+  '@esbuild/openbsd-x64@0.27.4':
+    resolution: {integrity: sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [openbsd]
+
+  '@esbuild/openharmony-arm64@0.27.4':
+    resolution: {integrity: sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [openharmony]
+
+  '@esbuild/sunos-x64@0.27.4':
+    resolution: {integrity: sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [sunos]
+
+  '@esbuild/win32-arm64@0.27.4':
+    resolution: {integrity: sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [win32]
+
+  '@esbuild/win32-ia32@0.27.4':
+    resolution: {integrity: sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==}
+    engines: {node: '>=18'}
+    cpu: [ia32]
+    os: [win32]
+
+  '@esbuild/win32-x64@0.27.4':
+    resolution: {integrity: sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [win32]
+
+  '@jridgewell/gen-mapping@0.3.13':
+    resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
+
+  '@jridgewell/remapping@2.3.5':
+    resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
+
+  '@jridgewell/resolve-uri@3.1.2':
+    resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+    engines: {node: '>=6.0.0'}
+
+  '@jridgewell/sourcemap-codec@1.5.5':
+    resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
+
+  '@jridgewell/trace-mapping@0.3.31':
+    resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
+
+  '@rolldown/pluginutils@1.0.0-rc.3':
+    resolution: {integrity: sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==}
+
+  '@rollup/rollup-android-arm-eabi@4.60.0':
+    resolution: {integrity: sha512-WOhNW9K8bR3kf4zLxbfg6Pxu2ybOUbB2AjMDHSQx86LIF4rH4Ft7vmMwNt0loO0eonglSNy4cpD3MKXXKQu0/A==}
+    cpu: [arm]
+    os: [android]
+
+  '@rollup/rollup-android-arm64@4.60.0':
+    resolution: {integrity: sha512-u6JHLll5QKRvjciE78bQXDmqRqNs5M/3GVqZeMwvmjaNODJih/WIrJlFVEihvV0MiYFmd+ZyPr9wxOVbPAG2Iw==}
+    cpu: [arm64]
+    os: [android]
+
+  '@rollup/rollup-darwin-arm64@4.60.0':
+    resolution: {integrity: sha512-qEF7CsKKzSRc20Ciu2Zw1wRrBz4g56F7r/vRwY430UPp/nt1x21Q/fpJ9N5l47WWvJlkNCPJz3QRVw008fi7yA==}
+    cpu: [arm64]
+    os: [darwin]
+
+  '@rollup/rollup-darwin-x64@4.60.0':
+    resolution: {integrity: sha512-WADYozJ4QCnXCH4wPB+3FuGmDPoFseVCUrANmA5LWwGmC6FL14BWC7pcq+FstOZv3baGX65tZ378uT6WG8ynTw==}
+    cpu: [x64]
+    os: [darwin]
+
+  '@rollup/rollup-freebsd-arm64@4.60.0':
+    resolution: {integrity: sha512-6b8wGHJlDrGeSE3aH5mGNHBjA0TTkxdoNHik5EkvPHCt351XnigA4pS7Wsj/Eo9Y8RBU6f35cjN9SYmCFBtzxw==}
+    cpu: [arm64]
+    os: [freebsd]
+
+  '@rollup/rollup-freebsd-x64@4.60.0':
+    resolution: {integrity: sha512-h25Ga0t4jaylMB8M/JKAyrvvfxGRjnPQIR8lnCayyzEjEOx2EJIlIiMbhpWxDRKGKF8jbNH01NnN663dH638mA==}
+    cpu: [x64]
+    os: [freebsd]
+
+  '@rollup/rollup-linux-arm-gnueabihf@4.60.0':
+    resolution: {integrity: sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g==}
+    cpu: [arm]
+    os: [linux]
+    libc: [glibc]
+
+  '@rollup/rollup-linux-arm-musleabihf@4.60.0':
+    resolution: {integrity: sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ==}
+    cpu: [arm]
+    os: [linux]
+    libc: [musl]
+
+  '@rollup/rollup-linux-arm64-gnu@4.60.0':
+    resolution: {integrity: sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A==}
+    cpu: [arm64]
+    os: [linux]
+    libc: [glibc]
+
+  '@rollup/rollup-linux-arm64-musl@4.60.0':
+    resolution: {integrity: sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ==}
+    cpu: [arm64]
+    os: [linux]
+    libc: [musl]
+
+  '@rollup/rollup-linux-loong64-gnu@4.60.0':
+    resolution: {integrity: sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw==}
+    cpu: [loong64]
+    os: [linux]
+    libc: [glibc]
+
+  '@rollup/rollup-linux-loong64-musl@4.60.0':
+    resolution: {integrity: sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog==}
+    cpu: [loong64]
+    os: [linux]
+    libc: [musl]
+
+  '@rollup/rollup-linux-ppc64-gnu@4.60.0':
+    resolution: {integrity: sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ==}
+    cpu: [ppc64]
+    os: [linux]
+    libc: [glibc]
+
+  '@rollup/rollup-linux-ppc64-musl@4.60.0':
+    resolution: {integrity: sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg==}
+    cpu: [ppc64]
+    os: [linux]
+    libc: [musl]
+
+  '@rollup/rollup-linux-riscv64-gnu@4.60.0':
+    resolution: {integrity: sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA==}
+    cpu: [riscv64]
+    os: [linux]
+    libc: [glibc]
+
+  '@rollup/rollup-linux-riscv64-musl@4.60.0':
+    resolution: {integrity: sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ==}
+    cpu: [riscv64]
+    os: [linux]
+    libc: [musl]
+
+  '@rollup/rollup-linux-s390x-gnu@4.60.0':
+    resolution: {integrity: sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ==}
+    cpu: [s390x]
+    os: [linux]
+    libc: [glibc]
+
+  '@rollup/rollup-linux-x64-gnu@4.60.0':
+    resolution: {integrity: sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg==}
+    cpu: [x64]
+    os: [linux]
+    libc: [glibc]
+
+  '@rollup/rollup-linux-x64-musl@4.60.0':
+    resolution: {integrity: sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw==}
+    cpu: [x64]
+    os: [linux]
+    libc: [musl]
+
+  '@rollup/rollup-openbsd-x64@4.60.0':
+    resolution: {integrity: sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw==}
+    cpu: [x64]
+    os: [openbsd]
+
+  '@rollup/rollup-openharmony-arm64@4.60.0':
+    resolution: {integrity: sha512-pESDkos/PDzYwtyzB5p/UoNU/8fJo68vcXM9ZW2V0kjYayj1KaaUfi1NmTUTUpMn4UhU4gTuK8gIaFO4UGuMbA==}
+    cpu: [arm64]
+    os: [openharmony]
+
+  '@rollup/rollup-win32-arm64-msvc@4.60.0':
+    resolution: {integrity: sha512-hj1wFStD7B1YBeYmvY+lWXZ7ey73YGPcViMShYikqKT1GtstIKQAtfUI6yrzPjAy/O7pO0VLXGmUVWXQMaYgTQ==}
+    cpu: [arm64]
+    os: [win32]
+
+  '@rollup/rollup-win32-ia32-msvc@4.60.0':
+    resolution: {integrity: sha512-SyaIPFoxmUPlNDq5EHkTbiKzmSEmq/gOYFI/3HHJ8iS/v1mbugVa7dXUzcJGQfoytp9DJFLhHH4U3/eTy2Bq4w==}
+    cpu: [ia32]
+    os: [win32]
+
+  '@rollup/rollup-win32-x64-gnu@4.60.0':
+    resolution: {integrity: sha512-RdcryEfzZr+lAr5kRm2ucN9aVlCCa2QNq4hXelZxb8GG0NJSazq44Z3PCCc8wISRuCVnGs0lQJVX5Vp6fKA+IA==}
+    cpu: [x64]
+    os: [win32]
+
+  '@rollup/rollup-win32-x64-msvc@4.60.0':
+    resolution: {integrity: sha512-PrsWNQ8BuE00O3Xsx3ALh2Df8fAj9+cvvX9AIA6o4KpATR98c9mud4XtDWVvsEuyia5U4tVSTKygawyJkjm60w==}
+    cpu: [x64]
+    os: [win32]
+
+  '@tailwindcss/node@4.2.2':
+    resolution: {integrity: sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==}
+
+  '@tailwindcss/oxide-android-arm64@4.2.2':
+    resolution: {integrity: sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==}
+    engines: {node: '>= 20'}
+    cpu: [arm64]
+    os: [android]
+
+  '@tailwindcss/oxide-darwin-arm64@4.2.2':
+    resolution: {integrity: sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==}
+    engines: {node: '>= 20'}
+    cpu: [arm64]
+    os: [darwin]
+
+  '@tailwindcss/oxide-darwin-x64@4.2.2':
+    resolution: {integrity: sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==}
+    engines: {node: '>= 20'}
+    cpu: [x64]
+    os: [darwin]
+
+  '@tailwindcss/oxide-freebsd-x64@4.2.2':
+    resolution: {integrity: sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==}
+    engines: {node: '>= 20'}
+    cpu: [x64]
+    os: [freebsd]
+
+  '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2':
+    resolution: {integrity: sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==}
+    engines: {node: '>= 20'}
+    cpu: [arm]
+    os: [linux]
+
+  '@tailwindcss/oxide-linux-arm64-gnu@4.2.2':
+    resolution: {integrity: sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==}
+    engines: {node: '>= 20'}
+    cpu: [arm64]
+    os: [linux]
+    libc: [glibc]
+
+  '@tailwindcss/oxide-linux-arm64-musl@4.2.2':
+    resolution: {integrity: sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==}
+    engines: {node: '>= 20'}
+    cpu: [arm64]
+    os: [linux]
+    libc: [musl]
+
+  '@tailwindcss/oxide-linux-x64-gnu@4.2.2':
+    resolution: {integrity: sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==}
+    engines: {node: '>= 20'}
+    cpu: [x64]
+    os: [linux]
+    libc: [glibc]
+
+  '@tailwindcss/oxide-linux-x64-musl@4.2.2':
+    resolution: {integrity: sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==}
+    engines: {node: '>= 20'}
+    cpu: [x64]
+    os: [linux]
+    libc: [musl]
+
+  '@tailwindcss/oxide-wasm32-wasi@4.2.2':
+    resolution: {integrity: sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==}
+    engines: {node: '>=14.0.0'}
+    cpu: [wasm32]
+    bundledDependencies:
+      - '@napi-rs/wasm-runtime'
+      - '@emnapi/core'
+      - '@emnapi/runtime'
+      - '@tybys/wasm-util'
+      - '@emnapi/wasi-threads'
+      - tslib
+
+  '@tailwindcss/oxide-win32-arm64-msvc@4.2.2':
+    resolution: {integrity: sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==}
+    engines: {node: '>= 20'}
+    cpu: [arm64]
+    os: [win32]
+
+  '@tailwindcss/oxide-win32-x64-msvc@4.2.2':
+    resolution: {integrity: sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==}
+    engines: {node: '>= 20'}
+    cpu: [x64]
+    os: [win32]
+
+  '@tailwindcss/oxide@4.2.2':
+    resolution: {integrity: sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==}
+    engines: {node: '>= 20'}
+
+  '@tailwindcss/vite@4.2.2':
+    resolution: {integrity: sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w==}
+    peerDependencies:
+      vite: ^5.2.0 || ^6 || ^7 || ^8
+
+  '@types/babel__core@7.20.5':
+    resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
+
+  '@types/babel__generator@7.27.0':
+    resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==}
+
+  '@types/babel__template@7.4.4':
+    resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==}
+
+  '@types/babel__traverse@7.28.0':
+    resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
+
+  '@types/crypto-js@4.2.2':
+    resolution: {integrity: sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==}
+
+  '@types/estree@1.0.8':
+    resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
+
+  '@types/react-dom@19.2.3':
+    resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==}
+    peerDependencies:
+      '@types/react': ^19.2.0
+
+  '@types/react@19.2.14':
+    resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==}
+
+  '@vitejs/plugin-react@5.2.0':
+    resolution: {integrity: sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    peerDependencies:
+      vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0
+
+  asynckit@0.4.0:
+    resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
+
+  axios@1.14.0:
+    resolution: {integrity: sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ==}
+
+  baseline-browser-mapping@2.10.12:
+    resolution: {integrity: sha512-qyq26DxfY4awP2gIRXhhLWfwzwI+N5Nxk6iQi8EFizIaWIjqicQTE4sLnZZVdeKPRcVNoJOkkpfzoIYuvCKaIQ==}
+    engines: {node: '>=6.0.0'}
+    hasBin: true
+
+  browserslist@4.28.1:
+    resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==}
+    engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+    hasBin: true
+
+  call-bind-apply-helpers@1.0.2:
+    resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
+    engines: {node: '>= 0.4'}
+
+  caniuse-lite@1.0.30001782:
+    resolution: {integrity: sha512-dZcaJLJeDMh4rELYFw1tvSn1bhZWYFOt468FcbHHxx/Z/dFidd1I6ciyFdi3iwfQCyOjqo9upF6lGQYtMiJWxw==}
+
+  clsx@2.1.1:
+    resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
+    engines: {node: '>=6'}
+
+  combined-stream@1.0.8:
+    resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
+    engines: {node: '>= 0.8'}
+
+  convert-source-map@2.0.0:
+    resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+
+  crypto-js@4.2.0:
+    resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==}
+
+  csstype@3.2.3:
+    resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
+
+  debug@4.4.3:
+    resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
+    engines: {node: '>=6.0'}
+    peerDependencies:
+      supports-color: '*'
+    peerDependenciesMeta:
+      supports-color:
+        optional: true
+
+  delayed-stream@1.0.0:
+    resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
+    engines: {node: '>=0.4.0'}
+
+  detect-libc@2.1.2:
+    resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
+    engines: {node: '>=8'}
+
+  dunder-proto@1.0.1:
+    resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
+    engines: {node: '>= 0.4'}
+
+  electron-to-chromium@1.5.328:
+    resolution: {integrity: sha512-QNQ5l45DzYytThO21403XN3FvK0hOkWDG8viNf6jqS42msJ8I4tGDSpBCgvDRRPnkffafiwAym2X2eHeGD2V0w==}
+
+  enhanced-resolve@5.20.1:
+    resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==}
+    engines: {node: '>=10.13.0'}
+
+  es-define-property@1.0.1:
+    resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
+    engines: {node: '>= 0.4'}
+
+  es-errors@1.3.0:
+    resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
+    engines: {node: '>= 0.4'}
+
+  es-object-atoms@1.1.1:
+    resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
+    engines: {node: '>= 0.4'}
+
+  es-set-tostringtag@2.1.0:
+    resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
+    engines: {node: '>= 0.4'}
+
+  esbuild@0.27.4:
+    resolution: {integrity: sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==}
+    engines: {node: '>=18'}
+    hasBin: true
+
+  escalade@3.2.0:
+    resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
+    engines: {node: '>=6'}
+
+  fdir@6.5.0:
+    resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
+    engines: {node: '>=12.0.0'}
+    peerDependencies:
+      picomatch: ^3 || ^4
+    peerDependenciesMeta:
+      picomatch:
+        optional: true
+
+  follow-redirects@1.15.11:
+    resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==}
+    engines: {node: '>=4.0'}
+    peerDependencies:
+      debug: '*'
+    peerDependenciesMeta:
+      debug:
+        optional: true
+
+  form-data@4.0.5:
+    resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==}
+    engines: {node: '>= 6'}
+
+  fsevents@2.3.3:
+    resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+    engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+    os: [darwin]
+
+  function-bind@1.1.2:
+    resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+
+  gensync@1.0.0-beta.2:
+    resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
+    engines: {node: '>=6.9.0'}
+
+  get-intrinsic@1.3.0:
+    resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
+    engines: {node: '>= 0.4'}
+
+  get-proto@1.0.1:
+    resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
+    engines: {node: '>= 0.4'}
+
+  gopd@1.2.0:
+    resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
+    engines: {node: '>= 0.4'}
+
+  graceful-fs@4.2.11:
+    resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
+
+  has-symbols@1.1.0:
+    resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
+    engines: {node: '>= 0.4'}
+
+  has-tostringtag@1.0.2:
+    resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
+    engines: {node: '>= 0.4'}
+
+  hasown@2.0.2:
+    resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
+    engines: {node: '>= 0.4'}
+
+  jiti@2.6.1:
+    resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
+    hasBin: true
+
+  jotai@2.19.0:
+    resolution: {integrity: sha512-r2wwxEXP1F2JteDLZEOPoIpAHhV89paKsN5GWVYndPNMMP/uVZDcC+fNj0A8NjKgaPWzdyO8Vp8YcYKe0uCEqQ==}
+    engines: {node: '>=12.20.0'}
+    peerDependencies:
+      '@babel/core': '>=7.0.0'
+      '@babel/template': '>=7.0.0'
+      '@types/react': '>=17.0.0'
+      react: '>=17.0.0'
+    peerDependenciesMeta:
+      '@babel/core':
+        optional: true
+      '@babel/template':
+        optional: true
+      '@types/react':
+        optional: true
+      react:
+        optional: true
+
+  js-tokens@4.0.0:
+    resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+
+  jsesc@3.1.0:
+    resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
+    engines: {node: '>=6'}
+    hasBin: true
+
+  json5@2.2.3:
+    resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
+    engines: {node: '>=6'}
+    hasBin: true
+
+  lightningcss-android-arm64@1.32.0:
+    resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==}
+    engines: {node: '>= 12.0.0'}
+    cpu: [arm64]
+    os: [android]
+
+  lightningcss-darwin-arm64@1.32.0:
+    resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==}
+    engines: {node: '>= 12.0.0'}
+    cpu: [arm64]
+    os: [darwin]
+
+  lightningcss-darwin-x64@1.32.0:
+    resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==}
+    engines: {node: '>= 12.0.0'}
+    cpu: [x64]
+    os: [darwin]
+
+  lightningcss-freebsd-x64@1.32.0:
+    resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==}
+    engines: {node: '>= 12.0.0'}
+    cpu: [x64]
+    os: [freebsd]
+
+  lightningcss-linux-arm-gnueabihf@1.32.0:
+    resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==}
+    engines: {node: '>= 12.0.0'}
+    cpu: [arm]
+    os: [linux]
+
+  lightningcss-linux-arm64-gnu@1.32.0:
+    resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==}
+    engines: {node: '>= 12.0.0'}
+    cpu: [arm64]
+    os: [linux]
+    libc: [glibc]
+
+  lightningcss-linux-arm64-musl@1.32.0:
+    resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
+    engines: {node: '>= 12.0.0'}
+    cpu: [arm64]
+    os: [linux]
+    libc: [musl]
+
+  lightningcss-linux-x64-gnu@1.32.0:
+    resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
+    engines: {node: '>= 12.0.0'}
+    cpu: [x64]
+    os: [linux]
+    libc: [glibc]
+
+  lightningcss-linux-x64-musl@1.32.0:
+    resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
+    engines: {node: '>= 12.0.0'}
+    cpu: [x64]
+    os: [linux]
+    libc: [musl]
+
+  lightningcss-win32-arm64-msvc@1.32.0:
+    resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==}
+    engines: {node: '>= 12.0.0'}
+    cpu: [arm64]
+    os: [win32]
+
+  lightningcss-win32-x64-msvc@1.32.0:
+    resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==}
+    engines: {node: '>= 12.0.0'}
+    cpu: [x64]
+    os: [win32]
+
+  lightningcss@1.32.0:
+    resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==}
+    engines: {node: '>= 12.0.0'}
+
+  loose-envify@1.4.0:
+    resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
+    hasBin: true
+
+  lru-cache@5.1.1:
+    resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
+
+  lucide-react@0.577.0:
+    resolution: {integrity: sha512-4LjoFv2eEPwYDPg/CUdBJQSDfPyzXCRrVW1X7jrx/trgxnxkHFjnVZINbzvzxjN70dxychOfg+FTYwBiS3pQ5A==}
+    peerDependencies:
+      react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+  magic-string@0.30.21:
+    resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
+
+  math-intrinsics@1.1.0:
+    resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
+    engines: {node: '>= 0.4'}
+
+  mime-db@1.52.0:
+    resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
+    engines: {node: '>= 0.6'}
+
+  mime-types@2.1.35:
+    resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
+    engines: {node: '>= 0.6'}
+
+  ms@2.1.3:
+    resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+  nanoid@3.3.11:
+    resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
+    engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+    hasBin: true
+
+  node-releases@2.0.36:
+    resolution: {integrity: sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==}
+
+  object-assign@4.1.1:
+    resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
+    engines: {node: '>=0.10.0'}
+
+  picocolors@1.1.1:
+    resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+  picomatch@4.0.4:
+    resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
+    engines: {node: '>=12'}
+
+  postcss@8.5.8:
+    resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==}
+    engines: {node: ^10 || ^12 || >=14}
+
+  prop-types@15.8.1:
+    resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
+
+  proxy-from-env@2.1.0:
+    resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==}
+    engines: {node: '>=10'}
+
+  react-dom@19.2.4:
+    resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==}
+    peerDependencies:
+      react: ^19.2.4
+
+  react-draggable@4.5.0:
+    resolution: {integrity: sha512-VC+HBLEZ0XJxnOxVAZsdRi8rD04Iz3SiiKOoYzamjylUcju/hP9np/aZdLHf/7WOD268WMoNJMvYfB5yAK45cw==}
+    peerDependencies:
+      react: '>= 16.3.0'
+      react-dom: '>= 16.3.0'
+
+  react-is@16.13.1:
+    resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
+
+  react-refresh@0.18.0:
+    resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==}
+    engines: {node: '>=0.10.0'}
+
+  react@19.2.4:
+    resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==}
+    engines: {node: '>=0.10.0'}
+
+  rollup@4.60.0:
+    resolution: {integrity: sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ==}
+    engines: {node: '>=18.0.0', npm: '>=8.0.0'}
+    hasBin: true
+
+  scheduler@0.27.0:
+    resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
+
+  semver@6.3.1:
+    resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
+    hasBin: true
+
+  source-map-js@1.2.1:
+    resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+    engines: {node: '>=0.10.0'}
+
+  tailwind-merge@3.5.0:
+    resolution: {integrity: sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==}
+
+  tailwindcss@4.2.2:
+    resolution: {integrity: sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==}
+
+  tapable@2.3.2:
+    resolution: {integrity: sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==}
+    engines: {node: '>=6'}
+
+  tinyglobby@0.2.15:
+    resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
+    engines: {node: '>=12.0.0'}
+
+  typescript@5.9.3:
+    resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
+    engines: {node: '>=14.17'}
+    hasBin: true
+
+  update-browserslist-db@1.2.3:
+    resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==}
+    hasBin: true
+    peerDependencies:
+      browserslist: '>= 4.21.0'
+
+  vite@7.3.1:
+    resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    hasBin: true
+    peerDependencies:
+      '@types/node': ^20.19.0 || >=22.12.0
+      jiti: '>=1.21.0'
+      less: ^4.0.0
+      lightningcss: ^1.21.0
+      sass: ^1.70.0
+      sass-embedded: ^1.70.0
+      stylus: '>=0.54.8'
+      sugarss: ^5.0.0
+      terser: ^5.16.0
+      tsx: ^4.8.1
+      yaml: ^2.4.2
+    peerDependenciesMeta:
+      '@types/node':
+        optional: true
+      jiti:
+        optional: true
+      less:
+        optional: true
+      lightningcss:
+        optional: true
+      sass:
+        optional: true
+      sass-embedded:
+        optional: true
+      stylus:
+        optional: true
+      sugarss:
+        optional: true
+      terser:
+        optional: true
+      tsx:
+        optional: true
+      yaml:
+        optional: true
+
+  yallist@3.1.1:
+    resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
+
+snapshots:
+
+  '@babel/code-frame@7.29.0':
+    dependencies:
+      '@babel/helper-validator-identifier': 7.28.5
+      js-tokens: 4.0.0
+      picocolors: 1.1.1
+
+  '@babel/compat-data@7.29.0': {}
+
+  '@babel/core@7.29.0':
+    dependencies:
+      '@babel/code-frame': 7.29.0
+      '@babel/generator': 7.29.1
+      '@babel/helper-compilation-targets': 7.28.6
+      '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0)
+      '@babel/helpers': 7.29.2
+      '@babel/parser': 7.29.2
+      '@babel/template': 7.28.6
+      '@babel/traverse': 7.29.0
+      '@babel/types': 7.29.0
+      '@jridgewell/remapping': 2.3.5
+      convert-source-map: 2.0.0
+      debug: 4.4.3
+      gensync: 1.0.0-beta.2
+      json5: 2.2.3
+      semver: 6.3.1
+    transitivePeerDependencies:
+      - supports-color
+
+  '@babel/generator@7.29.1':
+    dependencies:
+      '@babel/parser': 7.29.2
+      '@babel/types': 7.29.0
+      '@jridgewell/gen-mapping': 0.3.13
+      '@jridgewell/trace-mapping': 0.3.31
+      jsesc: 3.1.0
+
+  '@babel/helper-compilation-targets@7.28.6':
+    dependencies:
+      '@babel/compat-data': 7.29.0
+      '@babel/helper-validator-option': 7.27.1
+      browserslist: 4.28.1
+      lru-cache: 5.1.1
+      semver: 6.3.1
+
+  '@babel/helper-globals@7.28.0': {}
+
+  '@babel/helper-module-imports@7.28.6':
+    dependencies:
+      '@babel/traverse': 7.29.0
+      '@babel/types': 7.29.0
+    transitivePeerDependencies:
+      - supports-color
+
+  '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)':
+    dependencies:
+      '@babel/core': 7.29.0
+      '@babel/helper-module-imports': 7.28.6
+      '@babel/helper-validator-identifier': 7.28.5
+      '@babel/traverse': 7.29.0
+    transitivePeerDependencies:
+      - supports-color
+
+  '@babel/helper-plugin-utils@7.28.6': {}
+
+  '@babel/helper-string-parser@7.27.1': {}
+
+  '@babel/helper-validator-identifier@7.28.5': {}
+
+  '@babel/helper-validator-option@7.27.1': {}
+
+  '@babel/helpers@7.29.2':
+    dependencies:
+      '@babel/template': 7.28.6
+      '@babel/types': 7.29.0
+
+  '@babel/parser@7.29.2':
+    dependencies:
+      '@babel/types': 7.29.0
+
+  '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.0)':
+    dependencies:
+      '@babel/core': 7.29.0
+      '@babel/helper-plugin-utils': 7.28.6
+
+  '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.29.0)':
+    dependencies:
+      '@babel/core': 7.29.0
+      '@babel/helper-plugin-utils': 7.28.6
+
+  '@babel/template@7.28.6':
+    dependencies:
+      '@babel/code-frame': 7.29.0
+      '@babel/parser': 7.29.2
+      '@babel/types': 7.29.0
+
+  '@babel/traverse@7.29.0':
+    dependencies:
+      '@babel/code-frame': 7.29.0
+      '@babel/generator': 7.29.1
+      '@babel/helper-globals': 7.28.0
+      '@babel/parser': 7.29.2
+      '@babel/template': 7.28.6
+      '@babel/types': 7.29.0
+      debug: 4.4.3
+    transitivePeerDependencies:
+      - supports-color
+
+  '@babel/types@7.29.0':
+    dependencies:
+      '@babel/helper-string-parser': 7.27.1
+      '@babel/helper-validator-identifier': 7.28.5
+
+  '@esbuild/aix-ppc64@0.27.4':
+    optional: true
+
+  '@esbuild/android-arm64@0.27.4':
+    optional: true
+
+  '@esbuild/android-arm@0.27.4':
+    optional: true
+
+  '@esbuild/android-x64@0.27.4':
+    optional: true
+
+  '@esbuild/darwin-arm64@0.27.4':
+    optional: true
+
+  '@esbuild/darwin-x64@0.27.4':
+    optional: true
+
+  '@esbuild/freebsd-arm64@0.27.4':
+    optional: true
+
+  '@esbuild/freebsd-x64@0.27.4':
+    optional: true
+
+  '@esbuild/linux-arm64@0.27.4':
+    optional: true
+
+  '@esbuild/linux-arm@0.27.4':
+    optional: true
+
+  '@esbuild/linux-ia32@0.27.4':
+    optional: true
+
+  '@esbuild/linux-loong64@0.27.4':
+    optional: true
+
+  '@esbuild/linux-mips64el@0.27.4':
+    optional: true
+
+  '@esbuild/linux-ppc64@0.27.4':
+    optional: true
+
+  '@esbuild/linux-riscv64@0.27.4':
+    optional: true
+
+  '@esbuild/linux-s390x@0.27.4':
+    optional: true
+
+  '@esbuild/linux-x64@0.27.4':
+    optional: true
+
+  '@esbuild/netbsd-arm64@0.27.4':
+    optional: true
+
+  '@esbuild/netbsd-x64@0.27.4':
+    optional: true
+
+  '@esbuild/openbsd-arm64@0.27.4':
+    optional: true
+
+  '@esbuild/openbsd-x64@0.27.4':
+    optional: true
+
+  '@esbuild/openharmony-arm64@0.27.4':
+    optional: true
+
+  '@esbuild/sunos-x64@0.27.4':
+    optional: true
+
+  '@esbuild/win32-arm64@0.27.4':
+    optional: true
+
+  '@esbuild/win32-ia32@0.27.4':
+    optional: true
+
+  '@esbuild/win32-x64@0.27.4':
+    optional: true
+
+  '@jridgewell/gen-mapping@0.3.13':
+    dependencies:
+      '@jridgewell/sourcemap-codec': 1.5.5
+      '@jridgewell/trace-mapping': 0.3.31
+
+  '@jridgewell/remapping@2.3.5':
+    dependencies:
+      '@jridgewell/gen-mapping': 0.3.13
+      '@jridgewell/trace-mapping': 0.3.31
+
+  '@jridgewell/resolve-uri@3.1.2': {}
+
+  '@jridgewell/sourcemap-codec@1.5.5': {}
+
+  '@jridgewell/trace-mapping@0.3.31':
+    dependencies:
+      '@jridgewell/resolve-uri': 3.1.2
+      '@jridgewell/sourcemap-codec': 1.5.5
+
+  '@rolldown/pluginutils@1.0.0-rc.3': {}
+
+  '@rollup/rollup-android-arm-eabi@4.60.0':
+    optional: true
+
+  '@rollup/rollup-android-arm64@4.60.0':
+    optional: true
+
+  '@rollup/rollup-darwin-arm64@4.60.0':
+    optional: true
+
+  '@rollup/rollup-darwin-x64@4.60.0':
+    optional: true
+
+  '@rollup/rollup-freebsd-arm64@4.60.0':
+    optional: true
+
+  '@rollup/rollup-freebsd-x64@4.60.0':
+    optional: true
+
+  '@rollup/rollup-linux-arm-gnueabihf@4.60.0':
+    optional: true
+
+  '@rollup/rollup-linux-arm-musleabihf@4.60.0':
+    optional: true
+
+  '@rollup/rollup-linux-arm64-gnu@4.60.0':
+    optional: true
+
+  '@rollup/rollup-linux-arm64-musl@4.60.0':
+    optional: true
+
+  '@rollup/rollup-linux-loong64-gnu@4.60.0':
+    optional: true
+
+  '@rollup/rollup-linux-loong64-musl@4.60.0':
+    optional: true
+
+  '@rollup/rollup-linux-ppc64-gnu@4.60.0':
+    optional: true
+
+  '@rollup/rollup-linux-ppc64-musl@4.60.0':
+    optional: true
+
+  '@rollup/rollup-linux-riscv64-gnu@4.60.0':
+    optional: true
+
+  '@rollup/rollup-linux-riscv64-musl@4.60.0':
+    optional: true
+
+  '@rollup/rollup-linux-s390x-gnu@4.60.0':
+    optional: true
+
+  '@rollup/rollup-linux-x64-gnu@4.60.0':
+    optional: true
+
+  '@rollup/rollup-linux-x64-musl@4.60.0':
+    optional: true
+
+  '@rollup/rollup-openbsd-x64@4.60.0':
+    optional: true
+
+  '@rollup/rollup-openharmony-arm64@4.60.0':
+    optional: true
+
+  '@rollup/rollup-win32-arm64-msvc@4.60.0':
+    optional: true
+
+  '@rollup/rollup-win32-ia32-msvc@4.60.0':
+    optional: true
+
+  '@rollup/rollup-win32-x64-gnu@4.60.0':
+    optional: true
+
+  '@rollup/rollup-win32-x64-msvc@4.60.0':
+    optional: true
+
+  '@tailwindcss/node@4.2.2':
+    dependencies:
+      '@jridgewell/remapping': 2.3.5
+      enhanced-resolve: 5.20.1
+      jiti: 2.6.1
+      lightningcss: 1.32.0
+      magic-string: 0.30.21
+      source-map-js: 1.2.1
+      tailwindcss: 4.2.2
+
+  '@tailwindcss/oxide-android-arm64@4.2.2':
+    optional: true
+
+  '@tailwindcss/oxide-darwin-arm64@4.2.2':
+    optional: true
+
+  '@tailwindcss/oxide-darwin-x64@4.2.2':
+    optional: true
+
+  '@tailwindcss/oxide-freebsd-x64@4.2.2':
+    optional: true
+
+  '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2':
+    optional: true
+
+  '@tailwindcss/oxide-linux-arm64-gnu@4.2.2':
+    optional: true
+
+  '@tailwindcss/oxide-linux-arm64-musl@4.2.2':
+    optional: true
+
+  '@tailwindcss/oxide-linux-x64-gnu@4.2.2':
+    optional: true
+
+  '@tailwindcss/oxide-linux-x64-musl@4.2.2':
+    optional: true
+
+  '@tailwindcss/oxide-wasm32-wasi@4.2.2':
+    optional: true
+
+  '@tailwindcss/oxide-win32-arm64-msvc@4.2.2':
+    optional: true
+
+  '@tailwindcss/oxide-win32-x64-msvc@4.2.2':
+    optional: true
+
+  '@tailwindcss/oxide@4.2.2':
+    optionalDependencies:
+      '@tailwindcss/oxide-android-arm64': 4.2.2
+      '@tailwindcss/oxide-darwin-arm64': 4.2.2
+      '@tailwindcss/oxide-darwin-x64': 4.2.2
+      '@tailwindcss/oxide-freebsd-x64': 4.2.2
+      '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.2
+      '@tailwindcss/oxide-linux-arm64-gnu': 4.2.2
+      '@tailwindcss/oxide-linux-arm64-musl': 4.2.2
+      '@tailwindcss/oxide-linux-x64-gnu': 4.2.2
+      '@tailwindcss/oxide-linux-x64-musl': 4.2.2
+      '@tailwindcss/oxide-wasm32-wasi': 4.2.2
+      '@tailwindcss/oxide-win32-arm64-msvc': 4.2.2
+      '@tailwindcss/oxide-win32-x64-msvc': 4.2.2
+
+  '@tailwindcss/vite@4.2.2(vite@7.3.1(jiti@2.6.1)(lightningcss@1.32.0))':
+    dependencies:
+      '@tailwindcss/node': 4.2.2
+      '@tailwindcss/oxide': 4.2.2
+      tailwindcss: 4.2.2
+      vite: 7.3.1(jiti@2.6.1)(lightningcss@1.32.0)
+
+  '@types/babel__core@7.20.5':
+    dependencies:
+      '@babel/parser': 7.29.2
+      '@babel/types': 7.29.0
+      '@types/babel__generator': 7.27.0
+      '@types/babel__template': 7.4.4
+      '@types/babel__traverse': 7.28.0
+
+  '@types/babel__generator@7.27.0':
+    dependencies:
+      '@babel/types': 7.29.0
+
+  '@types/babel__template@7.4.4':
+    dependencies:
+      '@babel/parser': 7.29.2
+      '@babel/types': 7.29.0
+
+  '@types/babel__traverse@7.28.0':
+    dependencies:
+      '@babel/types': 7.29.0
+
+  '@types/crypto-js@4.2.2': {}
+
+  '@types/estree@1.0.8': {}
+
+  '@types/react-dom@19.2.3(@types/react@19.2.14)':
+    dependencies:
+      '@types/react': 19.2.14
+
+  '@types/react@19.2.14':
+    dependencies:
+      csstype: 3.2.3
+
+  '@vitejs/plugin-react@5.2.0(vite@7.3.1(jiti@2.6.1)(lightningcss@1.32.0))':
+    dependencies:
+      '@babel/core': 7.29.0
+      '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0)
+      '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0)
+      '@rolldown/pluginutils': 1.0.0-rc.3
+      '@types/babel__core': 7.20.5
+      react-refresh: 0.18.0
+      vite: 7.3.1(jiti@2.6.1)(lightningcss@1.32.0)
+    transitivePeerDependencies:
+      - supports-color
+
+  asynckit@0.4.0: {}
+
+  axios@1.14.0:
+    dependencies:
+      follow-redirects: 1.15.11
+      form-data: 4.0.5
+      proxy-from-env: 2.1.0
+    transitivePeerDependencies:
+      - debug
+
+  baseline-browser-mapping@2.10.12: {}
+
+  browserslist@4.28.1:
+    dependencies:
+      baseline-browser-mapping: 2.10.12
+      caniuse-lite: 1.0.30001782
+      electron-to-chromium: 1.5.328
+      node-releases: 2.0.36
+      update-browserslist-db: 1.2.3(browserslist@4.28.1)
+
+  call-bind-apply-helpers@1.0.2:
+    dependencies:
+      es-errors: 1.3.0
+      function-bind: 1.1.2
+
+  caniuse-lite@1.0.30001782: {}
+
+  clsx@2.1.1: {}
+
+  combined-stream@1.0.8:
+    dependencies:
+      delayed-stream: 1.0.0
+
+  convert-source-map@2.0.0: {}
+
+  crypto-js@4.2.0: {}
+
+  csstype@3.2.3: {}
+
+  debug@4.4.3:
+    dependencies:
+      ms: 2.1.3
+
+  delayed-stream@1.0.0: {}
+
+  detect-libc@2.1.2: {}
+
+  dunder-proto@1.0.1:
+    dependencies:
+      call-bind-apply-helpers: 1.0.2
+      es-errors: 1.3.0
+      gopd: 1.2.0
+
+  electron-to-chromium@1.5.328: {}
+
+  enhanced-resolve@5.20.1:
+    dependencies:
+      graceful-fs: 4.2.11
+      tapable: 2.3.2
+
+  es-define-property@1.0.1: {}
+
+  es-errors@1.3.0: {}
+
+  es-object-atoms@1.1.1:
+    dependencies:
+      es-errors: 1.3.0
+
+  es-set-tostringtag@2.1.0:
+    dependencies:
+      es-errors: 1.3.0
+      get-intrinsic: 1.3.0
+      has-tostringtag: 1.0.2
+      hasown: 2.0.2
+
+  esbuild@0.27.4:
+    optionalDependencies:
+      '@esbuild/aix-ppc64': 0.27.4
+      '@esbuild/android-arm': 0.27.4
+      '@esbuild/android-arm64': 0.27.4
+      '@esbuild/android-x64': 0.27.4
+      '@esbuild/darwin-arm64': 0.27.4
+      '@esbuild/darwin-x64': 0.27.4
+      '@esbuild/freebsd-arm64': 0.27.4
+      '@esbuild/freebsd-x64': 0.27.4
+      '@esbuild/linux-arm': 0.27.4
+      '@esbuild/linux-arm64': 0.27.4
+      '@esbuild/linux-ia32': 0.27.4
+      '@esbuild/linux-loong64': 0.27.4
+      '@esbuild/linux-mips64el': 0.27.4
+      '@esbuild/linux-ppc64': 0.27.4
+      '@esbuild/linux-riscv64': 0.27.4
+      '@esbuild/linux-s390x': 0.27.4
+      '@esbuild/linux-x64': 0.27.4
+      '@esbuild/netbsd-arm64': 0.27.4
+      '@esbuild/netbsd-x64': 0.27.4
+      '@esbuild/openbsd-arm64': 0.27.4
+      '@esbuild/openbsd-x64': 0.27.4
+      '@esbuild/openharmony-arm64': 0.27.4
+      '@esbuild/sunos-x64': 0.27.4
+      '@esbuild/win32-arm64': 0.27.4
+      '@esbuild/win32-ia32': 0.27.4
+      '@esbuild/win32-x64': 0.27.4
+
+  escalade@3.2.0: {}
+
+  fdir@6.5.0(picomatch@4.0.4):
+    optionalDependencies:
+      picomatch: 4.0.4
+
+  follow-redirects@1.15.11: {}
+
+  form-data@4.0.5:
+    dependencies:
+      asynckit: 0.4.0
+      combined-stream: 1.0.8
+      es-set-tostringtag: 2.1.0
+      hasown: 2.0.2
+      mime-types: 2.1.35
+
+  fsevents@2.3.3:
+    optional: true
+
+  function-bind@1.1.2: {}
+
+  gensync@1.0.0-beta.2: {}
+
+  get-intrinsic@1.3.0:
+    dependencies:
+      call-bind-apply-helpers: 1.0.2
+      es-define-property: 1.0.1
+      es-errors: 1.3.0
+      es-object-atoms: 1.1.1
+      function-bind: 1.1.2
+      get-proto: 1.0.1
+      gopd: 1.2.0
+      has-symbols: 1.1.0
+      hasown: 2.0.2
+      math-intrinsics: 1.1.0
+
+  get-proto@1.0.1:
+    dependencies:
+      dunder-proto: 1.0.1
+      es-object-atoms: 1.1.1
+
+  gopd@1.2.0: {}
+
+  graceful-fs@4.2.11: {}
+
+  has-symbols@1.1.0: {}
+
+  has-tostringtag@1.0.2:
+    dependencies:
+      has-symbols: 1.1.0
+
+  hasown@2.0.2:
+    dependencies:
+      function-bind: 1.1.2
+
+  jiti@2.6.1: {}
+
+  jotai@2.19.0(@babel/core@7.29.0)(@babel/template@7.28.6)(@types/react@19.2.14)(react@19.2.4):
+    optionalDependencies:
+      '@babel/core': 7.29.0
+      '@babel/template': 7.28.6
+      '@types/react': 19.2.14
+      react: 19.2.4
+
+  js-tokens@4.0.0: {}
+
+  jsesc@3.1.0: {}
+
+  json5@2.2.3: {}
+
+  lightningcss-android-arm64@1.32.0:
+    optional: true
+
+  lightningcss-darwin-arm64@1.32.0:
+    optional: true
+
+  lightningcss-darwin-x64@1.32.0:
+    optional: true
+
+  lightningcss-freebsd-x64@1.32.0:
+    optional: true
+
+  lightningcss-linux-arm-gnueabihf@1.32.0:
+    optional: true
+
+  lightningcss-linux-arm64-gnu@1.32.0:
+    optional: true
+
+  lightningcss-linux-arm64-musl@1.32.0:
+    optional: true
+
+  lightningcss-linux-x64-gnu@1.32.0:
+    optional: true
+
+  lightningcss-linux-x64-musl@1.32.0:
+    optional: true
+
+  lightningcss-win32-arm64-msvc@1.32.0:
+    optional: true
+
+  lightningcss-win32-x64-msvc@1.32.0:
+    optional: true
+
+  lightningcss@1.32.0:
+    dependencies:
+      detect-libc: 2.1.2
+    optionalDependencies:
+      lightningcss-android-arm64: 1.32.0
+      lightningcss-darwin-arm64: 1.32.0
+      lightningcss-darwin-x64: 1.32.0
+      lightningcss-freebsd-x64: 1.32.0
+      lightningcss-linux-arm-gnueabihf: 1.32.0
+      lightningcss-linux-arm64-gnu: 1.32.0
+      lightningcss-linux-arm64-musl: 1.32.0
+      lightningcss-linux-x64-gnu: 1.32.0
+      lightningcss-linux-x64-musl: 1.32.0
+      lightningcss-win32-arm64-msvc: 1.32.0
+      lightningcss-win32-x64-msvc: 1.32.0
+
+  loose-envify@1.4.0:
+    dependencies:
+      js-tokens: 4.0.0
+
+  lru-cache@5.1.1:
+    dependencies:
+      yallist: 3.1.1
+
+  lucide-react@0.577.0(react@19.2.4):
+    dependencies:
+      react: 19.2.4
+
+  magic-string@0.30.21:
+    dependencies:
+      '@jridgewell/sourcemap-codec': 1.5.5
+
+  math-intrinsics@1.1.0: {}
+
+  mime-db@1.52.0: {}
+
+  mime-types@2.1.35:
+    dependencies:
+      mime-db: 1.52.0
+
+  ms@2.1.3: {}
+
+  nanoid@3.3.11: {}
+
+  node-releases@2.0.36: {}
+
+  object-assign@4.1.1: {}
+
+  picocolors@1.1.1: {}
+
+  picomatch@4.0.4: {}
+
+  postcss@8.5.8:
+    dependencies:
+      nanoid: 3.3.11
+      picocolors: 1.1.1
+      source-map-js: 1.2.1
+
+  prop-types@15.8.1:
+    dependencies:
+      loose-envify: 1.4.0
+      object-assign: 4.1.1
+      react-is: 16.13.1
+
+  proxy-from-env@2.1.0: {}
+
+  react-dom@19.2.4(react@19.2.4):
+    dependencies:
+      react: 19.2.4
+      scheduler: 0.27.0
+
+  react-draggable@4.5.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
+    dependencies:
+      clsx: 2.1.1
+      prop-types: 15.8.1
+      react: 19.2.4
+      react-dom: 19.2.4(react@19.2.4)
+
+  react-is@16.13.1: {}
+
+  react-refresh@0.18.0: {}
+
+  react@19.2.4: {}
+
+  rollup@4.60.0:
+    dependencies:
+      '@types/estree': 1.0.8
+    optionalDependencies:
+      '@rollup/rollup-android-arm-eabi': 4.60.0
+      '@rollup/rollup-android-arm64': 4.60.0
+      '@rollup/rollup-darwin-arm64': 4.60.0
+      '@rollup/rollup-darwin-x64': 4.60.0
+      '@rollup/rollup-freebsd-arm64': 4.60.0
+      '@rollup/rollup-freebsd-x64': 4.60.0
+      '@rollup/rollup-linux-arm-gnueabihf': 4.60.0
+      '@rollup/rollup-linux-arm-musleabihf': 4.60.0
+      '@rollup/rollup-linux-arm64-gnu': 4.60.0
+      '@rollup/rollup-linux-arm64-musl': 4.60.0
+      '@rollup/rollup-linux-loong64-gnu': 4.60.0
+      '@rollup/rollup-linux-loong64-musl': 4.60.0
+      '@rollup/rollup-linux-ppc64-gnu': 4.60.0
+      '@rollup/rollup-linux-ppc64-musl': 4.60.0
+      '@rollup/rollup-linux-riscv64-gnu': 4.60.0
+      '@rollup/rollup-linux-riscv64-musl': 4.60.0
+      '@rollup/rollup-linux-s390x-gnu': 4.60.0
+      '@rollup/rollup-linux-x64-gnu': 4.60.0
+      '@rollup/rollup-linux-x64-musl': 4.60.0
+      '@rollup/rollup-openbsd-x64': 4.60.0
+      '@rollup/rollup-openharmony-arm64': 4.60.0
+      '@rollup/rollup-win32-arm64-msvc': 4.60.0
+      '@rollup/rollup-win32-ia32-msvc': 4.60.0
+      '@rollup/rollup-win32-x64-gnu': 4.60.0
+      '@rollup/rollup-win32-x64-msvc': 4.60.0
+      fsevents: 2.3.3
+
+  scheduler@0.27.0: {}
+
+  semver@6.3.1: {}
+
+  source-map-js@1.2.1: {}
+
+  tailwind-merge@3.5.0: {}
+
+  tailwindcss@4.2.2: {}
+
+  tapable@2.3.2: {}
+
+  tinyglobby@0.2.15:
+    dependencies:
+      fdir: 6.5.0(picomatch@4.0.4)
+      picomatch: 4.0.4
+
+  typescript@5.9.3: {}
+
+  update-browserslist-db@1.2.3(browserslist@4.28.1):
+    dependencies:
+      browserslist: 4.28.1
+      escalade: 3.2.0
+      picocolors: 1.1.1
+
+  vite@7.3.1(jiti@2.6.1)(lightningcss@1.32.0):
+    dependencies:
+      esbuild: 0.27.4
+      fdir: 6.5.0(picomatch@4.0.4)
+      picomatch: 4.0.4
+      postcss: 8.5.8
+      rollup: 4.60.0
+      tinyglobby: 0.2.15
+    optionalDependencies:
+      fsevents: 2.3.3
+      jiti: 2.6.1
+      lightningcss: 1.32.0
+
+  yallist@3.1.1: {}

+ 32 - 31
src/App.tsx

@@ -18,15 +18,15 @@ import { logoutApi } from './api/login';
 
 function App() {
   const iframeRef = useRef<HTMLIFrameElement>(null);
-  const [iframeUrl, setIframeUrl] = useState<string>('http://localhost:5173/demo-game.html');
+  const [iframeUrl, setIframeUrl] = useState<string>('');
   const [isSlideBarOpen, setIsSlideBarOpen] = useState(false);
 
   const openModal = useSetAtom(openModalAction);
   const userState = useAtomValue(userStateAtom);
- 
+
   const getGameUrl = async () => {
-    const res = await getH5GameLinkApi({game_id: getURLparams('game_id') || ''})
-    setIframeUrl(`${res.game_url}?game_id=${getURLparams('game_id') || ''}&time=${Math.round(new Date().getTime()/1000)}`)
+    const res = await getH5GameLinkApi({ game_id: getURLparams('game_id') || '' })
+    setIframeUrl(`${res.game_url}?game_id=${getURLparams('game_id') || ''}&time=${Math.round(new Date().getTime() / 1000)}`)
   }
 
 
@@ -42,11 +42,11 @@ function App() {
   useEffect(() => {
     // 处理登录请求
     const handleLoginReq = (payload: any) => {
-      if(getURLparams('is_native') === 'true'){
-        openModal({name:'login',item:{isOpen:false,requestId:payload.requestId,data:payload.data}})
+      if (getURLparams('is_native') === 'true') {
+        openModal({ name: 'login', item: { isOpen: false, requestId: payload.requestId, data: payload.data } })
         nativeLogin(payload)
-      }else{
-        openModal({name:'login',item:{isOpen:true,requestId:payload.requestId,data:payload.data}})
+      } else {
+        openModal({ name: 'login', item: { isOpen: true, requestId: payload.requestId, data: payload.data } })
         console.log("登录")
       }
 
@@ -54,50 +54,50 @@ function App() {
 
     // 处理支付请求
     const handlePayReq = (payload: any) => {
-      if(getURLparams('is_native') === 'true'){
-        openModal({name:'payment',item:{isOpen:false,requestId:payload.requestId,data:payload.data}})
+      if (getURLparams('is_native') === 'true') {
+        openModal({ name: 'payment', item: { isOpen: false, requestId: payload.requestId, data: payload.data } })
         nativePay(payload)
-      }else{
-        openModal({name:'payment',item:{isOpen:true,requestId:payload.requestId,data:payload.data}})
+      } else {
+        openModal({ name: 'payment', item: { isOpen: true, requestId: payload.requestId, data: payload.data } })
       }
     };
 
     // 处理上报请求
     const handleReportReq = (payload: any) => {
-      openModal({name:'report',item:{isOpen:true,requestId:payload.requestId,data:payload.data}})
-      if(getURLparams('is_native') === 'true'){
+      openModal({ name: 'report', item: { isOpen: true, requestId: payload.requestId, data: payload.data } })
+      if (getURLparams('is_native') === 'true') {
         nativeReport(payload)
-      }else{
+      } else {
         report(payload)
       }
     };
-    
+
     // 处理初始化请求
     const handleInitReq = (payload: any) => {
-      openModal({name:'init',item:{isOpen:true,requestId:payload.requestId,data:payload.data}})
+      openModal({ name: 'init', item: { isOpen: true, requestId: payload.requestId, data: payload.data } })
       // 微端是真
-      if(getURLparams('is_native') === 'true'){
+      if (getURLparams('is_native') === 'true') {
         nativeInit(payload)
-      }else{
+      } else {
         init(payload)
       }
-    } 
+    }
 
     // 处理退出登录请求
     const handleLogoutReq = async (payload: any) => {
-      openModal({name:'logout',item:{isOpen:true,requestId:payload.requestId,data:payload.data}})
-      if(getURLparams('is_native') === 'true'){
+      openModal({ name: 'logout', item: { isOpen: true, requestId: payload.requestId, data: payload.data } })
+      if (getURLparams('is_native') === 'true') {
         nativeLogout(payload)
-      }else{
+      } else {
         await logoutApi()
-        pmBridge.sendToIframe('LOGOUT_REQUEST', {success:true, message:"退出登录成功"}, payload.requestId);
+        pmBridge.sendToIframe('LOGOUT_REQUEST', { success: true, message: "退出登录成功" }, payload.requestId);
       }
     }
 
     // 处理退出登录回调
     const handleOnLogoutReq = (payload: any) => {
-      openModal({name:'onLogout',item:{isOpen:false,requestId:payload.requestId,data:payload.data}})
-      if(getURLparams('is_native') === 'true'){
+      openModal({ name: 'onLogout', item: { isOpen: false, requestId: payload.requestId, data: payload.data } })
+      if (getURLparams('is_native') === 'true') {
         nativeOnLogout(payload)
       }
     }
@@ -125,21 +125,22 @@ function App() {
 
   return (
     <div className="w-full h-screen relative bg-white font-sans overflow-hidden">
-      
+
       {/* Iframe 游戏容器 */}
-      <iframe 
+      <iframe
         ref={iframeRef}
-        src={iframeUrl} 
+        src={iframeUrl}
         title="Game Container"
         className="w-full h-full border-none"
         sandbox="allow-scripts allow-same-origin allow-forms"
       />
+      {userState.token}
 
       {/* 渲染弹窗 */}
       <LoginIndex />
       <RealNameIndex />
       <PaymentIndex />
-      
+
       {/* 登录后显示浮标球 */}
       {userState.token && (
         <FloatingButton onClick={() => setIsSlideBarOpen(true)} />
@@ -149,7 +150,7 @@ function App() {
       {isSlideBarOpen && (
         <SlideBarIndex onClose={() => setIsSlideBarOpen(false)} />
       )}
-     
+
 
     </div>
   );

+ 25 - 9
src/api/login.ts

@@ -2,8 +2,8 @@ import { request } from '../utils/request'
 
 // 登录账号密码接口
 export const loginAccountApi = (data: {
-    user_name: string,
-    user_pwd: string
+  user_name: string,
+  user_pwd: string
 }) => {
   return request.post('/sdk/auth/login', data)
 }
@@ -42,9 +42,9 @@ export const realNameAuthApi = (data: {
 
 // 忘记密码-发送验证码
 export const forgetPasswordSendCodeApi = (data: {
-    user_name: string,
-    mobile: string
-  }) => {
+  user_name: string,
+  mobile: string
+}) => {
   return request.post('/sdk/auth/update_pwd_send_code', data)
 }
 
@@ -53,16 +53,17 @@ export const forgetPasswordVerifyCodeApi = (data: {
   user_name: string,
   mobile: string,
   code: string
-})=>{
+}) => {
   return request.post('/sdk/auth/update_pwd_check_code', data)
 }
 
 // 第二步:忘记密码-修改密码
 export const forgetPasswordUpdatePasswordApi = (data: {
   user_name: string,
-  new_user_pwd: string
-  mobile:string
-})=>{
+  new_user_pwd: string,
+  mobile: string,
+  code: string
+}) => {
   return request.post('/sdk/auth/update_pwd_by_code', data)
 }
 
@@ -95,4 +96,19 @@ export const bindMobileApi = (data: {
 // 退出登录
 export const logoutApi = () => {
   return request.post('/sdk/auth/logout')
+}
+
+// 发送解绑手机验证码
+export const sendUnbindMobileCodeApi = (data: {
+  mobile: string
+}) => {
+  return request.post('/sdk/user/send_unbind_mobile_code', data)
+}
+
+// 解绑手机
+export const unbindMobileApi = (data: {
+  mobile: string,
+  code: string
+}) => {
+  return request.post('/sdk/user/unbind_mobile', data)
 }

+ 8 - 0
src/api/user.ts

@@ -0,0 +1,8 @@
+import { request } from "../utils/request";
+
+/**
+ * 获取用户信息
+ */
+export const getUserInfoApi = () => {
+    return request.get('/sdk/user/userinfo')
+}

+ 9 - 9
src/components/FloatingButton.tsx

@@ -13,6 +13,7 @@ export function FloatingButton({ onClick }: FloatingButtonProps) {
   const nodeRef = useRef<HTMLDivElement>(null);
   
   const idleTimerRef = useRef<any>(null);
+  const dragRef = useRef(false); // Track real drag movement
 
   // 清除空闲倒计时
   const clearIdleTimer = () => {
@@ -24,6 +25,7 @@ export function FloatingButton({ onClick }: FloatingButtonProps) {
   };
 
   const handleDragStart = () => {
+    dragRef.current = false;
     clearIdleTimer();
     setIsDragging(true);
     
@@ -39,13 +41,18 @@ export function FloatingButton({ onClick }: FloatingButtonProps) {
     }
   };
 
-  const handleDrag = (e: DraggableEvent, data: DraggableData) => {
+  const handleDrag = (_e: DraggableEvent, data: DraggableData) => {
+    dragRef.current = true;
     setPosition({ x: data.x, y: data.y });
   };
 
-  const handleDragStop = (e: DraggableEvent, data: DraggableData) => {
+  const handleDragStop = (_e: DraggableEvent, data: DraggableData) => {
     setIsDragging(false);
 
+    if (!dragRef.current) {
+      onClick();
+    }
+
     // 自动吸附屏幕左右边缘
     const btnWidth = nodeRef.current ? nodeRef.current.offsetWidth : 48; // 默认 48px
     const btnHeight = nodeRef.current ? nodeRef.current.offsetHeight : 48;
@@ -74,12 +81,6 @@ export function FloatingButton({ onClick }: FloatingButtonProps) {
     }, 3000);
   };
 
-  const handleClick = () => {
-    if (!isDragging) {
-      onClick();
-    }
-  };
-
   return (
     <Draggable
       nodeRef={nodeRef}
@@ -90,7 +91,6 @@ export function FloatingButton({ onClick }: FloatingButtonProps) {
     >
       <div
         ref={nodeRef}
-        onClick={handleClick}
         className={`fixed top-0 left-0 z-50 flex items-center justify-center w-12 h-12 bg-blue-600 rounded-full shadow-lg cursor-move active:scale-95 
           ${isDragging ? 'opacity-80 scale-105 shadow-xl transition-none' : 'opacity-100 hover:scale-110 transition-all duration-300 ease-out'}
           ${isIdle ? 'opacity-50' : ''}`}

+ 5 - 1
src/components/forget-password/index.tsx

@@ -6,21 +6,25 @@ export const ForgetPasswordIndex = ({close}: {close: () => void}) => {
     const [step, setStep] = useState(1)
     const [userName, setUserName] = useState("")
     const [mobile, setMobile] = useState("")
+    const [code, setCode] = useState("")
 
     return (
         <>
             {
                 step === 1 ? 
                 <Step1 
-                    onNext={(u, m) => {
+                    onNext={(u, m, c) => {
                         setUserName(u)
                         setMobile(m)
+                        setCode(c)
                         setStep(2)
                     }} 
                     onClose={close} 
                 /> : 
                 <Step2 
                     userName={userName}
+                    mobile={mobile}
+                    code={code}
                     onBack={() => {setStep(1)}} 
                     onClose={close} 
                 />

+ 2 - 2
src/components/forget-password/step1.tsx

@@ -2,7 +2,7 @@ import { useState, useEffect } from "react"
 import { ChevronRight, CircleX, ShieldCheck, Smartphone, User } from "lucide-react"
 import { forgetPasswordSendCodeApi, forgetPasswordVerifyCodeApi } from "../../api/login"
 
-export const Step1 = ({onNext, onClose}: {onNext: (userName: string, mobile: string) => void, onClose: () => void}) => {
+export const Step1 = ({onNext, onClose}: {onNext: (userName: string, mobile: string, code: string) => void, onClose: () => void}) => {
     const [userName, setUserName] = useState("")
     const [mobile, setMobile] = useState("")
     const [code, setCode] = useState("")
@@ -47,7 +47,7 @@ export const Step1 = ({onNext, onClose}: {onNext: (userName: string, mobile: str
         setLoading(true)
         try {
             await forgetPasswordVerifyCodeApi({ user_name: userName, mobile, code })
-            onNext(userName, mobile)
+            onNext(userName, mobile, code)
         } catch (error) {
             alert("网络错误")
         } finally {

+ 4 - 3
src/components/forget-password/step2.tsx

@@ -2,7 +2,7 @@ import { useState } from "react"
 import { ArrowLeft, Lock, Eye, EyeOff } from "lucide-react"
 import { forgetPasswordUpdatePasswordApi } from "../../api/login"
 
-export const Step2 = ({userName, mobile, onBack, onClose}: {userName: string, mobile: string, onBack: () => void, onClose: () => void}) => {
+export const Step2 = ({userName, mobile, code, onBack, onClose}: {userName: string, mobile: string, code: string, onBack: () => void, onClose: () => void}) => {
     const [password, setPassword] = useState("")
     const [confirmPassword, setConfirmPassword] = useState("")
     const [loading, setLoading] = useState(false)
@@ -28,11 +28,12 @@ export const Step2 = ({userName, mobile, onBack, onClose}: {userName: string, mo
             await forgetPasswordUpdatePasswordApi({ 
                 user_name: userName, 
                 new_user_pwd: password,
-                mobile: mobile
+                mobile: mobile,
+                code: code
             })
             alert("修改成功")
             onClose()
-        } catch (error) {
+        } catch (error: any) {
             alert(error?.message || "修改失败")
         } finally {
             setLoading(false)

+ 20 - 18
src/components/login/account-login.tsx

@@ -5,6 +5,7 @@ import { useState } from "react";
 import { loginAccountApi } from "../../api/login"
 import { pmBridge } from "../../lib/PostMessageBridge"
 import { Agreement } from "../agreement";
+import { updateUserStateAtom } from "../../store/user-atom";
 
 
 export interface AccountLoginProps {
@@ -15,6 +16,7 @@ export interface AccountLoginProps {
 export const AccountLogin = ({ switchPhoneLogin, switchRegister }: AccountLoginProps) => {
     const modalState = useAtomValue(modalStackAtom)
     const openModal = useSetAtom(openModalAction)
+    const updateData = useSetAtom(updateUserStateAtom)
     const [userName, setUserName] = useState('')
     const [userPwd, setUserPwd] = useState('')
     const [loading, setLoading] = useState(false)
@@ -41,10 +43,11 @@ export const AccountLogin = ({ switchPhoneLogin, switchRegister }: AccountLoginP
             if (res.token) {
                 // 存储 token
                 localStorage.setItem('token', res.token)
+                updateData({ token: res.token, username: res.user_name })
 
                 // 使用 pmBridge 回传给父 iframe
                 const requestId = modalState.login.requestId
-                pmBridge.sendToIframe("LOGIN_SUCCESS", {success:true, data: res}, requestId)
+                pmBridge.sendToIframe("LOGIN_SUCCESS", { success: true, data: res }, requestId)
 
                 // 关闭登录弹窗
                 openModal({ name: 'login', item: { isOpen: false } })
@@ -86,10 +89,10 @@ export const AccountLogin = ({ switchPhoneLogin, switchRegister }: AccountLoginP
                         <div className="flex gap-2">
 
                             <div className="flex-1">
-                                <input 
-                                    className="p-2 form-input input-field w-full rounded-lg border-slate-200 bg-white text-slate-900 h-12 text-sm" 
-                                    placeholder="请输入账号" 
-                                    type="text" 
+                                <input
+                                    className="p-2 form-input input-field w-full rounded-lg border-slate-200 bg-white text-slate-900 h-12 text-sm"
+                                    placeholder="请输入账号"
+                                    type="text"
                                     value={userName}
                                     onChange={(e) => setUserName(e.target.value)}
                                 />
@@ -99,24 +102,23 @@ export const AccountLogin = ({ switchPhoneLogin, switchRegister }: AccountLoginP
                         {/* Verification Code */}
                         <div className="flex gap-2">
                             <div className="flex-1">
-                                <input 
-                                    className="p-2 form-input input-field w-full rounded-lg border-slate-200 bg-white text-slate-900 h-12 text-sm" 
-                                    placeholder="请输入密码" 
-                                    type="password" 
+                                <input
+                                    className="p-2 form-input input-field w-full rounded-lg border-slate-200 bg-white text-slate-900 h-12 text-sm"
+                                    placeholder="请输入密码"
+                                    type="password"
                                     value={userPwd}
                                     onChange={(e) => setUserPwd(e.target.value)}
                                 />
                             </div>
 
                         </div>
-                        <div onClick={() => { setIsshowPassword(true) }}  className="text-xs text-slate-500 dark:text-slate-400 hover:text-primary transition-colors text-right cursor-pointer">忘记密码?</div>
+                        <div onClick={() => { setIsshowPassword(true) }} className="text-xs text-slate-500 dark:text-slate-400 hover:text-primary transition-colors text-right cursor-pointer">忘记密码?</div>
                         {/* Action Button */}
-                        <button 
+                        <button
                             onClick={handleLogin}
                             disabled={loading}
-                            className={`w-full bg-primary hover:bg-primary/90 text-white font-bold h-12 rounded-lg shadow-lg shadow-primary/30 transition-all flex items-center justify-center gap-2 ${
-                                loading ? 'opacity-70 cursor-not-allowed' : ''
-                            }`}
+                            className={`w-full bg-primary hover:bg-primary/90 text-white font-bold h-12 rounded-lg shadow-lg shadow-primary/30 transition-all flex items-center justify-center gap-2 ${loading ? 'opacity-70 cursor-not-allowed' : ''
+                                }`}
                         >
                             {loading ? '处理中...' : '进入游戏'}
                         </button>
@@ -141,10 +143,10 @@ export const AccountLogin = ({ switchPhoneLogin, switchRegister }: AccountLoginP
             }
             {
                 showAgreement && (
-                    <Agreement 
-                        title={showAgreement.title} 
-                        url={showAgreement.url} 
-                        onBack={() => setShowAgreement(null)} 
+                    <Agreement
+                        title={showAgreement.title}
+                        url={showAgreement.url}
+                        onBack={() => setShowAgreement(null)}
                     />
                 )
             }

+ 91 - 87
src/components/login/phone-login.tsx

@@ -3,16 +3,18 @@ import { loginSendCodeApi, getAccountListByCodeApi } from '../../api/login';
 import { SelectAccount, type Account } from './select-account';
 import { Agreement } from '../agreement';
 import { pmBridge } from '../../lib/PostMessageBridge';
-import { useAtomValue } from 'jotai';
+import { useAtomValue, useSetAtom } from 'jotai';
 import { modalStackAtom } from '../../store';
+import { updateUserStateAtom } from '../../store/user-atom';
 
 export interface phoneLoginProps {
     switchAccountLogin: () => void;
-    switchRegister:()=> void;
+    switchRegister: () => void;
 }
 
 export const PhoneLogin = ({ switchAccountLogin, switchRegister }: phoneLoginProps) => {
     const modalState = useAtomValue(modalStackAtom);
+    const updateData = useSetAtom(updateUserStateAtom);
     const [mobile, setMobile] = useState('');
     const [code, setCode] = useState('');
     const [countdown, setCountdown] = useState(0);
@@ -73,8 +75,12 @@ export const PhoneLogin = ({ switchAccountLogin, switchRegister }: phoneLoginPro
                 setShowSelectAccount(true);
             } else {
                 alert('登录成功')
+                if (res.token) {
+                    localStorage.setItem('token', res.token);
+                    updateData({ token: res.token, username: res.user_name });
+                }
                 const requestId = modalState.login.requestId;
-                pmBridge.sendToIframe("LOGIN_SUCCESS", {success:true, data: res}, requestId);
+                pmBridge.sendToIframe("LOGIN_SUCCESS", { success: true, data: res }, requestId);
             }
         } catch (error: any) {
             console.error('验证失败:', error);
@@ -91,107 +97,105 @@ export const PhoneLogin = ({ switchAccountLogin, switchRegister }: phoneLoginPro
 
     return (
         <>
-         <div className="fixed inset-0 z-[100] flex items-center justify-center bg-black/60 backdrop-blur-sm">
+            <div className="fixed inset-0 z-[100] flex items-center justify-center bg-black/60 backdrop-blur-sm">
 
-            <div className="w-full max-w-[320px] mx-5 bg-[#f5f7f8] rounded-2xl shadow-2xl overflow-hidden flex flex-col scale-100">
+                <div className="w-full max-w-[320px] mx-5 bg-[#f5f7f8] rounded-2xl shadow-2xl overflow-hidden flex flex-col scale-100">
 
-                <div className="flex flex-col items-center pt-4 pb-6 px-6">
+                    <div className="flex flex-col items-center pt-4 pb-6 px-6">
 
-                    <h1 className="text-xl font-bold text-slate-900 dark:text-slate-100">游戏登录</h1>
-                </div>
+                        <h1 className="text-xl font-bold text-slate-900 dark:text-slate-100">游戏登录</h1>
+                    </div>
 
-                {/* Tabs */}
-                <div className="flex border-b border-slate-200 dark:border-slate-800 px-6 gap-8">
-                    <button className="flex flex-col items-center justify-center border-b-[3px] border-primary text-primary pb-3 pt-2">
-                        <p className="text-sm font-bold leading-normal tracking-wide">手机号登录</p>
-                    </button>
-                    <button onClick={switchAccountLogin} className="flex flex-col items-center justify-center border-b-[3px] border-transparent text-slate-500 dark:text-slate-400 pb-3 pt-2 hover:text-primary transition-colors">
-                        <p className="text-sm font-bold leading-normal tracking-wide">账号登录</p>
-                    </button>
-                </div>
+                    {/* Tabs */}
+                    <div className="flex border-b border-slate-200 dark:border-slate-800 px-6 gap-8">
+                        <button className="flex flex-col items-center justify-center border-b-[3px] border-primary text-primary pb-3 pt-2">
+                            <p className="text-sm font-bold leading-normal tracking-wide">手机号登录</p>
+                        </button>
+                        <button onClick={switchAccountLogin} className="flex flex-col items-center justify-center border-b-[3px] border-transparent text-slate-500 dark:text-slate-400 pb-3 pt-2 hover:text-primary transition-colors">
+                            <p className="text-sm font-bold leading-normal tracking-wide">账号登录</p>
+                        </button>
+                    </div>
 
-                {/* Form Content */}
-                <div className="p-6 space-y-4">
-                    {/* Country Code & Phone Input */}
-                    <div className="flex gap-2">
-
-                        <div className="flex-1">
-                            <input 
-                                className="p-2 form-input input-field w-full rounded-lg border-slate-200 bg-white text-slate-900 h-12 text-sm" 
-                                placeholder="请输入手机号" 
-                                type="tel" 
-                                value={mobile}
-                                onChange={(e) => setMobile(e.target.value)}
-                            />
+                    {/* Form Content */}
+                    <div className="p-6 space-y-4">
+                        {/* Country Code & Phone Input */}
+                        <div className="flex gap-2">
+
+                            <div className="flex-1">
+                                <input
+                                    className="p-2 form-input input-field w-full rounded-lg border-slate-200 bg-white text-slate-900 h-12 text-sm"
+                                    placeholder="请输入手机号"
+                                    type="tel"
+                                    value={mobile}
+                                    onChange={(e) => setMobile(e.target.value)}
+                                />
+                            </div>
                         </div>
-                    </div>
 
-                    {/* Verification Code */}
-                    <div className="flex gap-2">
-                        <div className="flex-1">
-                            <input 
-                                className="p-2 form-input input-field w-full rounded-lg border-slate-200 bg-white text-slate-900 h-12 text-sm" 
-                                placeholder="请输入验证码" 
-                                type="text" 
-                                value={code}
-                                onChange={(e) => setCode(e.target.value)}
-                            />
+                        {/* Verification Code */}
+                        <div className="flex gap-2">
+                            <div className="flex-1">
+                                <input
+                                    className="p-2 form-input input-field w-full rounded-lg border-slate-200 bg-white text-slate-900 h-12 text-sm"
+                                    placeholder="请输入验证码"
+                                    type="text"
+                                    value={code}
+                                    onChange={(e) => setCode(e.target.value)}
+                                />
+                            </div>
+                            <button
+                                className={`px-4 h-12 text-primary font-medium text-sm rounded-lg whitespace-nowrap transition-colors border border-primary/20 ${countdown > 0 || loading ? 'bg-slate-100 text-slate-400 border-slate-200 cursor-not-allowed' : 'bg-primary/10 hover:bg-primary/20'
+                                    }`}
+                                onClick={handleGetCode}
+                                disabled={countdown > 0 || loading}
+                            >
+                                {countdown > 0 ? `${countdown}s` : loading ? '发送中...' : '获取验证码'}
+                            </button>
                         </div>
-                        <button 
-                            className={`px-4 h-12 text-primary font-medium text-sm rounded-lg whitespace-nowrap transition-colors border border-primary/20 ${
-                                countdown > 0 || loading ? 'bg-slate-100 text-slate-400 border-slate-200 cursor-not-allowed' : 'bg-primary/10 hover:bg-primary/20'
-                            }`}
-                            onClick={handleGetCode}
-                            disabled={countdown > 0 || loading}
+
+                        {/* Action Button */}
+                        <button
+                            className={`w-full bg-primary hover:bg-primary/90 text-white font-bold h-12 rounded-lg shadow-lg shadow-primary/30 transition-all flex items-center justify-center gap-2 ${loading ? 'opacity-70 cursor-not-allowed' : ''
+                                }`}
+                            onClick={handleLogin}
+                            disabled={loading}
                         >
-                            {countdown > 0 ? `${countdown}s` : loading ? '发送中...' : '获取验证码'}
+                            {loading ? '处理中...' : '进入游戏'}
                         </button>
-                    </div>
 
-                    {/* Action Button */}
-                    <button 
-                        className={`w-full bg-primary hover:bg-primary/90 text-white font-bold h-12 rounded-lg shadow-lg shadow-primary/30 transition-all flex items-center justify-center gap-2 ${
-                            loading ? 'opacity-70 cursor-not-allowed' : ''
-                        }`}
-                        onClick={handleLogin}
-                        disabled={loading}
-                    >
-                        {loading ? '处理中...' : '进入游戏'}
-                    </button>
-
-                    {/* Secondary Links */}
-                    <div className="flex justify-between items-center px-1">
-                        <a className="text-xs text-slate-500 dark:text-slate-400 hover:text-primary transition-colors cursor-pointer" onClick={switchRegister}>快速注册</a>
-                        <a className="text-xs text-slate-500 dark:text-slate-400 hover:text-primary transition-colors" href="#">遇到问题?</a>
+                        {/* Secondary Links */}
+                        <div className="flex justify-between items-center px-1">
+                            <a className="text-xs text-slate-500 dark:text-slate-400 hover:text-primary transition-colors cursor-pointer" onClick={switchRegister}>快速注册</a>
+                            <a className="text-xs text-slate-500 dark:text-slate-400 hover:text-primary transition-colors" href="#">遇到问题?</a>
+                        </div>
                     </div>
-                </div>
 
 
 
-                {/* Footer Privacy */}
-                <div className="px-6 pb-6 text-center">
-                    <p className="text-[10px] text-slate-400 leading-relaxed">
-                        登录即代表您已同意 <button className="text-primary hover:underline" onClick={() => setShowAgreement({ title: '用户协议', url: '/static/user.html' })}>用户协议</button> 和 <button className="text-primary hover:underline" onClick={() => setShowAgreement({ title: '隐私政策', url: '/static/ys.html' })}>隐私政策</button>
-                    </p>
+                    {/* Footer Privacy */}
+                    <div className="px-6 pb-6 text-center">
+                        <p className="text-[10px] text-slate-400 leading-relaxed">
+                            登录即代表您已同意 <button className="text-primary hover:underline" onClick={() => setShowAgreement({ title: '用户协议', url: '/static/user.html' })}>用户协议</button> 和 <button className="text-primary hover:underline" onClick={() => setShowAgreement({ title: '隐私政策', url: '/static/ys.html' })}>隐私政策</button>
+                        </p>
+                    </div>
                 </div>
             </div>
-        </div>
-        {showSelectAccount && (
-            <SelectAccount 
-                mobile={mobile}
-                code={code}
-                accounts={accountList} 
-                onConfirm={handleSelectConfirm} 
-                onClose={() => setShowSelectAccount(false)} 
-            />
-        )}
-        {showAgreement && (
-            <Agreement 
-                title={showAgreement.title} 
-                url={showAgreement.url} 
-                onBack={() => setShowAgreement(null)} 
-            />
-        )}
+            {showSelectAccount && (
+                <SelectAccount
+                    mobile={mobile}
+                    code={code}
+                    accounts={accountList}
+                    onConfirm={handleSelectConfirm}
+                    onClose={() => setShowSelectAccount(false)}
+                />
+            )}
+            {showAgreement && (
+                <Agreement
+                    title={showAgreement.title}
+                    url={showAgreement.url}
+                    onBack={() => setShowAgreement(null)}
+                />
+            )}
         </>
     );
 };

+ 12 - 11
src/components/login/select-account.tsx

@@ -4,6 +4,7 @@ import { loginPhoneCodeLoginApi } from "../../api/login"
 import { pmBridge } from "../../lib/PostMessageBridge"
 import { useAtomValue, useSetAtom } from "jotai"
 import { modalStackAtom, openModalAction } from "../../store"
+import { updateUserStateAtom } from "../../store/user-atom"
 
 export interface Account {
     uid: string;
@@ -23,6 +24,7 @@ export const SelectAccount = ({ mobile, code, accounts, onConfirm, onClose }: Se
     const [loading, setLoading] = useState(false)
     const modalState = useAtomValue(modalStackAtom)
     const openModal = useSetAtom(openModalAction)
+    const updateData = useSetAtom(updateUserStateAtom)
 
     useEffect(() => {
         if (accounts && accounts.length > 0) {
@@ -40,14 +42,15 @@ export const SelectAccount = ({ mobile, code, accounts, onConfirm, onClose }: Se
                     code,
                     user_name: selected.user_name
                 })
-                
+
                 if (res.token) {
                     // 存储 token
                     localStorage.setItem('token', res.token)
-                    
+                    updateData({ token: res.token, username: res.user_name })
+
                     // 使用 pmBridge 回传给父 iframe
                     const requestId = modalState.login.requestId
-                    pmBridge.sendToIframe("LOGIN_SUCCESS", {success:true, data: res}, requestId)
+                    pmBridge.sendToIframe("LOGIN_SUCCESS", { success: true, data: res }, requestId)
 
                     // 关闭登录弹窗
                     openModal({ name: 'login', item: { isOpen: false } })
@@ -89,11 +92,10 @@ export const SelectAccount = ({ mobile, code, accounts, onConfirm, onClose }: Se
                             <div
                                 key={account.uid}
                                 onClick={() => setSelectedId(account.uid)}
-                                className={`p-4 rounded-xl border-2 transition-all cursor-pointer ${
-                                    isSelected
+                                className={`p-4 rounded-xl border-2 transition-all cursor-pointer ${isSelected
                                         ? "border-primary bg-highlight ring-4 ring-primary/5"
                                         : "border-slate-100 bg-slate-50 hover:border-slate-300"
-                                }`}
+                                    }`}
                             >
                                 <div className="flex items-center gap-4">
                                     <div className="flex-1">
@@ -114,16 +116,15 @@ export const SelectAccount = ({ mobile, code, accounts, onConfirm, onClose }: Se
                 </section>
 
                 <footer className="p-5 pt-2 flex flex-col gap-3">
-                    <button 
+                    <button
                         onClick={handleConfirm}
                         disabled={loading}
-                        className={`w-full bg-primary hover:bg-blue-800 text-white font-bold py-3.5 rounded-xl shadow-lg shadow-blue-200 transition-all active:scale-[0.98] ${
-                            loading ? 'opacity-70 cursor-not-allowed' : ''
-                        }`}
+                        className={`w-full bg-primary hover:bg-blue-800 text-white font-bold py-3.5 rounded-xl shadow-lg shadow-blue-200 transition-all active:scale-[0.98] ${loading ? 'opacity-70 cursor-not-allowed' : ''
+                            }`}
                     >
                         {loading ? '进入中...' : '确认进入'}
                     </button>
-                    <button 
+                    <button
                         onClick={onClose}
                         disabled={loading}
                         className="w-full py-2 text-sm font-medium text-slate-400 hover:text-slate-600 transition-colors"

+ 3 - 0
src/components/register/account-register.tsx

@@ -5,6 +5,7 @@ import { loginAccountApi } from "../../api/login";
 import { useAtomValue, useSetAtom } from "jotai";
 import { modalStackAtom, openModalAction } from "../../store";
 import { pmBridge } from "../../lib/PostMessageBridge";
+import { updateUserStateAtom } from "../../store/user-atom";
 
 export interface AccountRegisterProps {
     switchPhoneRegister: () => void;
@@ -19,6 +20,7 @@ export const AccountRegister = ({ switchPhoneRegister, switchLogin }: AccountReg
 
     const modalState = useAtomValue(modalStackAtom);
     const openModal = useSetAtom(openModalAction);
+    const updateData = useSetAtom(updateUserStateAtom);
 
     const handleRandomAccount = async () => {
         setLoading(true);
@@ -67,6 +69,7 @@ export const AccountRegister = ({ switchPhoneRegister, switchLogin }: AccountReg
             if (loginRes.token) {
                 // 存储 token
                 localStorage.setItem('token', loginRes.token);
+                updateData({ token: loginRes.token });
 
                 // 使用 pmBridge 回传给父 iframe
                 const requestId = modalState.login.requestId;

+ 19 - 16
src/components/register/phone-register.tsx

@@ -1,10 +1,11 @@
-import { CirclePlay, Send } from 'lucide-react';
+import { CirclePlay } from 'lucide-react';
 import { useState, useEffect } from 'react';
 import { loginSendCodeApi, getAccountListByCodeApi, loginPhoneCodeLoginApi } from '../../api/login';
 import { SelectAccount, type Account } from '../login/select-account';
 import { useAtomValue, useSetAtom } from 'jotai';
 import { modalStackAtom, openModalAction } from '../../store';
 import { pmBridge } from '../../lib/PostMessageBridge';
+import { updateUserStateAtom } from '../../store/user-atom';
 
 export interface RegisterProps {
     switchAccountRegister: () => void;
@@ -22,6 +23,7 @@ export const PhoneRegister = ({ switchAccountRegister, switchLogin }: RegisterPr
 
     const modalState = useAtomValue(modalStackAtom);
     const openModal = useSetAtom(openModalAction);
+    const updateData = useSetAtom(updateUserStateAtom);
 
     useEffect(() => {
         let timer: any;
@@ -50,8 +52,9 @@ export const PhoneRegister = ({ switchAccountRegister, switchLogin }: RegisterPr
     const handleSuccess = (res: any) => {
         if (res.token) {
             localStorage.setItem('token', res.token);
+            updateData({ token: res.token, username: res.user_name });
             const requestId = modalState.login.requestId;
-            pmBridge.sendToIframe("LOGIN_SUCCESS", {success:true, data: res}, requestId);
+            pmBridge.sendToIframe("LOGIN_SUCCESS", { success: true, data: res }, requestId);
             openModal({ name: 'login', item: { isOpen: false } });
         } else {
             alert('登录失败:未返回 token');
@@ -155,10 +158,10 @@ export const PhoneRegister = ({ switchAccountRegister, switchLogin }: RegisterPr
 
                         <div className="flex-1">
 
-                            <input 
-                                className="p-2 form-input input-field w-full rounded-lg border-slate-200 bg-white text-slate-900 h-12 text-sm" 
-                                placeholder="请输入手机号" 
-                                type="tel" 
+                            <input
+                                className="p-2 form-input input-field w-full rounded-lg border-slate-200 bg-white text-slate-900 h-12 text-sm"
+                                placeholder="请输入手机号"
+                                type="tel"
                                 value={mobile}
                                 onChange={(e) => setMobile(e.target.value)}
                             />
@@ -169,15 +172,15 @@ export const PhoneRegister = ({ switchAccountRegister, switchLogin }: RegisterPr
                     <label className="text-xs font-semibold uppercase tracking-wider text-slate-400 ml-1">验证码</label>
                     <div className="flex gap-2">
                         <div className="flex-1">
-                            <input 
-                                className="p-2 form-input input-field w-full rounded-lg border-slate-200 bg-white text-slate-900 h-12 text-sm" 
-                                placeholder="请输入验证码" 
-                                type="text" 
+                            <input
+                                className="p-2 form-input input-field w-full rounded-lg border-slate-200 bg-white text-slate-900 h-12 text-sm"
+                                placeholder="请输入验证码"
+                                type="text"
                                 value={code}
                                 onChange={(e) => setCode(e.target.value)}
                             />
                         </div>
-                        <button 
+                        <button
                             onClick={handleSendCode}
                             disabled={countdown > 0}
                             className={`px-4 h-12 text-primary font-medium text-sm bg-primary/10 hover:bg-primary/20 rounded-lg whitespace-nowrap transition-colors border border-primary/20 ${countdown > 0 ? 'opacity-50 cursor-not-allowed' : ''}`}
@@ -190,7 +193,7 @@ export const PhoneRegister = ({ switchAccountRegister, switchLogin }: RegisterPr
                         <a className="text-xs text-slate-500 dark:text-slate-400 hover:text-primary transition-colors hover:underline" href="#">遇到问题?</a>
                     </div>
                     {/* Action Button */}
-                    <button 
+                    <button
                         onClick={handleRegisterSubmit}
                         disabled={loading}
                         className={`w-full bg-primary hover:bg-primary/90 text-white font-bold h-12 rounded-lg shadow-lg  transition-all flex items-center justify-center gap-2 ${loading ? 'opacity-70 cursor-not-allowed' : ''}`}
@@ -199,10 +202,10 @@ export const PhoneRegister = ({ switchAccountRegister, switchLogin }: RegisterPr
                     </button>
 
                     <div className="flex items-center gap-2 px-1 pt-2">
-                        <input 
-                            className="h-4 w-4 rounded border-slate-300 text-primary focus:ring-primary cursor-pointer" 
-                            id="tos-phone" 
-                            type="checkbox" 
+                        <input
+                            className="h-4 w-4 rounded border-slate-300 text-primary focus:ring-primary cursor-pointer"
+                            id="tos-phone"
+                            type="checkbox"
                             checked={agreed}
                             onChange={(e) => setAgreed(e.target.checked)}
                         />

+ 318 - 73
src/components/slide-bar/change-password/index.tsx

@@ -1,24 +1,133 @@
 import { ChevronLeft, Eye, EyeOff } from "lucide-react";
-import { useState } from "react";
-import { updatePasswordApi } from "../../../api/login";
+import { useState, useEffect, useRef } from "react";
+import {
+    updatePasswordApi,
+    forgetPasswordSendCodeApi,
+    forgetPasswordVerifyCodeApi,
+    forgetPasswordUpdatePasswordApi
+} from "../../../api/login";
 import { useAtomValue } from "jotai";
 import { userStateAtom } from "../../../store/user-atom";
 
 export interface ChangePasswordProps {
+    mobile?: string;
     onBack: () => void;
 }
 
-export const ChangePassword = ({ onBack }: ChangePasswordProps) => {
+type TabType = 'password' | 'mobile';
+
+export const ChangePassword = ({ mobile, onBack }: ChangePasswordProps) => {
     const userState = useAtomValue(userStateAtom);
+    const [activeTab, setActiveTab] = useState<TabType>('password');
+
+    // Password Tab State
     const [oldPassword, setOldPassword] = useState('');
     const [newPassword, setNewPassword] = useState('');
     const [confirmPassword, setConfirmPassword] = useState('');
     const [showOldPassword, setShowOldPassword] = useState(false);
     const [showNewPassword, setShowNewPassword] = useState(false);
     const [showConfirmPassword, setShowConfirmPassword] = useState(false);
+
+    // Mobile Tab State
+    const [mobileStep, setMobileStep] = useState<1 | 2>(1);
+    const [code, setCode] = useState('');
+    const [mobileNewPassword, setMobileNewPassword] = useState('');
+    const [mobileConfirmPassword, setMobileConfirmPassword] = useState('');
+    const [showMobileNewPassword, setShowMobileNewPassword] = useState(false);
+    const [showMobileConfirmPassword, setShowMobileConfirmPassword] = useState(false);
+
+    // Shared state
     const [loading, setLoading] = useState(false);
+    const [countdown, setCountdown] = useState(0);
+    const timerRef = useRef<any>(null);
 
-    const handleSubmit = async () => {
+    useEffect(() => {
+        if (countdown > 0) {
+            timerRef.current = setTimeout(() => {
+                setCountdown(countdown - 1);
+            }, 1000);
+        } else {
+            if (timerRef.current) clearTimeout(timerRef.current);
+        }
+        return () => {
+            if (timerRef.current) clearTimeout(timerRef.current);
+        };
+    }, [countdown]);
+
+    const handleSendCode = async () => {
+        if (!mobile) {
+            alert('当前账号未绑定手机号');
+            return;
+        }
+
+        setLoading(true);
+        try {
+            await forgetPasswordSendCodeApi({
+                user_name: userState.username,
+                mobile: mobile
+            });
+            alert('验证码已发送');
+            setCountdown(60);
+        } catch (error: any) {
+            alert(error.message || '发送失败');
+        } finally {
+            setLoading(false);
+        }
+    };
+
+    const handleVerifyCode = async () => {
+        if (!code) {
+            alert('请输入验证码');
+            return;
+        }
+
+        setLoading(true);
+        try {
+            await forgetPasswordVerifyCodeApi({
+                user_name: userState.username,
+                mobile: mobile || '',
+                code: code
+            });
+            setMobileStep(2);
+        } catch (error: any) {
+            alert(error.message || '验证失败');
+        } finally {
+            setLoading(false);
+        }
+    };
+
+    const handleMobileSubmit = async () => {
+        if (!mobileNewPassword) {
+            alert('请输入新密码');
+            return;
+        }
+        if (mobileNewPassword !== mobileConfirmPassword) {
+            alert('两次输入的新密码不一致');
+            return;
+        }
+        if (mobileNewPassword.length < 6) {
+            alert('新密码长度不能少于6位');
+            return;
+        }
+
+        setLoading(true);
+        try {
+            await forgetPasswordUpdatePasswordApi({
+                user_name: userState.username,
+                mobile: mobile || '',
+                new_user_pwd: mobileNewPassword,
+                code: code
+            });
+            alert('密码修改成功');
+            onBack();
+        } catch (error: any) {
+            alert(error.message || '修改失败');
+        } finally {
+            setLoading(false);
+        }
+    };
+
+    const handlePasswordSubmit = async () => {
         if (!oldPassword) {
             alert('请输入旧密码');
             return;
@@ -52,12 +161,201 @@ export const ChangePassword = ({ onBack }: ChangePasswordProps) => {
         }
     };
 
+    const renderPasswordTab = () => (
+        <div className="space-y-4">
+            <div className="space-y-2">
+                <label className="text-sm font-medium text-slate-700 dark:text-slate-300">旧密码</label>
+                <div className="relative">
+                    <input
+                        type={showOldPassword ? "text" : "password"}
+                        placeholder="请输入旧密码"
+                        className="w-full rounded-lg border border-slate-200 bg-white p-2.5 pr-10 text-sm focus:border-primary focus:outline-none dark:border-slate-800 dark:bg-slate-900 text-slate-900 dark:text-slate-100 placeholder:text-slate-400"
+                        value={oldPassword}
+                        onChange={(e) => setOldPassword(e.target.value)}
+                    />
+                    <button
+                        type="button"
+                        onClick={() => setShowOldPassword(!showOldPassword)}
+                        className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600 dark:hover:text-slate-200"
+                    >
+                        {showOldPassword ? <Eye size={18} /> : <EyeOff size={18} />}
+                    </button>
+                </div>
+            </div>
+
+            <div className="space-y-2">
+                <label className="text-sm font-medium text-slate-700 dark:text-slate-300">新密码</label>
+                <div className="relative">
+                    <input
+                        type={showNewPassword ? "text" : "password"}
+                        placeholder="请输入新密码"
+                        className="w-full rounded-lg border border-slate-200 bg-white p-2.5 pr-10 text-sm focus:border-primary focus:outline-none dark:border-slate-800 dark:bg-slate-900 text-slate-900 dark:text-slate-100 placeholder:text-slate-400"
+                        value={newPassword}
+                        onChange={(e) => setNewPassword(e.target.value)}
+                    />
+                    <button
+                        type="button"
+                        onClick={() => setShowNewPassword(!showNewPassword)}
+                        className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600 dark:hover:text-slate-200"
+                    >
+                        {showNewPassword ? <Eye size={18} /> : <EyeOff size={18} />}
+                    </button>
+                </div>
+            </div>
+
+            <div className="space-y-2">
+                <label className="text-sm font-medium text-slate-700 dark:text-slate-300">确认新密码</label>
+                <div className="relative">
+                    <input
+                        type={showConfirmPassword ? "text" : "password"}
+                        placeholder="请再次输入新密码"
+                        className="w-full rounded-lg border border-slate-200 bg-white p-2.5 pr-10 text-sm focus:border-primary focus:outline-none dark:border-slate-800 dark:bg-slate-900 text-slate-900 dark:text-slate-100 placeholder:text-slate-400"
+                        value={confirmPassword}
+                        onChange={(e) => setConfirmPassword(e.target.value)}
+                    />
+                    <button
+                        type="button"
+                        onClick={() => setShowConfirmPassword(!showConfirmPassword)}
+                        className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600 dark:hover:text-slate-200"
+                    >
+                        {showConfirmPassword ? <Eye size={18} /> : <EyeOff size={18} />}
+                    </button>
+                </div>
+            </div>
+
+            <button
+                onClick={handlePasswordSubmit}
+                disabled={loading}
+                className={`mt-6 w-full rounded-lg bg-primary py-3 font-bold text-white shadow-lg transition-all hover:bg-primary/90 ${loading ? 'opacity-70 cursor-not-allowed' : ''
+                    }`}
+            >
+                {loading ? '提交中...' : '提交修改'}
+            </button>
+        </div>
+    );
+
+    const maskedMobile = mobile ? mobile.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2') : '';
+
+    const renderMobileTab = () => {
+        if (!mobile) {
+            return (
+                <div className="flex flex-col items-center justify-center p-8 space-y-4 text-center">
+                    <div className="text-slate-500 dark:text-slate-400">
+                        当前账号未绑定手机号,无法通过手机验证修改密码。
+                    </div>
+                </div>
+            );
+        }
+
+        if (mobileStep === 1) {
+            return (
+                <div className="space-y-4">
+                    <div className="space-y-2">
+                        <label className="text-sm font-medium text-slate-700 dark:text-slate-300">账号</label>
+                        <div className="w-full rounded-lg border border-slate-200 bg-slate-50/50 p-2.5 text-sm dark:border-slate-800 dark:bg-slate-900/50 text-slate-900 dark:text-slate-100">
+                            {userState.username}
+                        </div>
+                    </div>
+
+                    <div className="space-y-2">
+                        <label className="text-sm font-medium text-slate-700 dark:text-slate-300">已绑定手机号</label>
+                        <div className="w-full rounded-lg border border-slate-200 bg-slate-50/50 p-2.5 text-sm dark:border-slate-800 dark:bg-slate-900/50 text-slate-900 dark:text-slate-100">
+                            {maskedMobile}
+                        </div>
+                    </div>
+
+                    <div className="space-y-2">
+                        <label className="text-sm font-medium text-slate-700 dark:text-slate-300">验证码</label>
+                        <div className="flex gap-2">
+                            <input
+                                type="text"
+                                placeholder="请输入验证码"
+                                className="flex-1 rounded-lg border border-slate-200 bg-white p-2.5 text-sm focus:border-primary focus:outline-none dark:border-slate-800 dark:bg-slate-900 text-slate-900 dark:text-slate-100 placeholder:text-slate-400"
+                                value={code}
+                                onChange={(e) => setCode(e.target.value)}
+                            />
+                            <button
+                                onClick={handleSendCode}
+                                disabled={loading || countdown > 0}
+                                className={`w-28 rounded-lg bg-slate-100 text-xs font-medium text-slate-600 transition-all hover:bg-slate-200 disabled:opacity-50 dark:bg-slate-800 dark:text-slate-400 dark:hover:bg-slate-700`}
+                            >
+                                {countdown > 0 ? `${countdown}s` : '获取验证码'}
+                            </button>
+                        </div>
+                    </div>
+
+                    <button
+                        onClick={handleVerifyCode}
+                        disabled={loading}
+                        className={`mt-6 w-full rounded-lg bg-primary py-3 font-bold text-white shadow-lg transition-all hover:bg-primary/90 ${loading ? 'opacity-70 cursor-not-allowed' : ''
+                            }`}
+                    >
+                        {loading ? '验证中...' : '验证验证码'}
+                    </button>
+                </div>
+            );
+        }
+
+        return (
+            <div className="space-y-4">
+                <div className="space-y-2">
+                    <label className="text-sm font-medium text-slate-700 dark:text-slate-300">新密码</label>
+                    <div className="relative">
+                        <input
+                            type={showMobileNewPassword ? "text" : "password"}
+                            placeholder="请输入新密码"
+                            className="w-full rounded-lg border border-slate-200 bg-white p-2.5 pr-10 text-sm focus:border-primary focus:outline-none dark:border-slate-800 dark:bg-slate-900 text-slate-900 dark:text-slate-100 placeholder:text-slate-400"
+                            value={mobileNewPassword}
+                            onChange={(e) => setMobileNewPassword(e.target.value)}
+                        />
+                        <button
+                            type="button"
+                            onClick={() => setShowMobileNewPassword(!showMobileNewPassword)}
+                            className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600 dark:hover:text-slate-200"
+                        >
+                            {showMobileNewPassword ? <Eye size={18} /> : <EyeOff size={18} />}
+                        </button>
+                    </div>
+                </div>
+
+                <div className="space-y-2">
+                    <label className="text-sm font-medium text-slate-700 dark:text-slate-300">确认新密码</label>
+                    <div className="relative">
+                        <input
+                            type={showMobileConfirmPassword ? "text" : "password"}
+                            placeholder="请再次输入新密码"
+                            className="w-full rounded-lg border border-slate-200 bg-white p-2.5 pr-10 text-sm focus:border-primary focus:outline-none dark:border-slate-800 dark:bg-slate-900 text-slate-900 dark:text-slate-100 placeholder:text-slate-400"
+                            value={mobileConfirmPassword}
+                            onChange={(e) => setMobileConfirmPassword(e.target.value)}
+                        />
+                        <button
+                            type="button"
+                            onClick={() => setShowMobileConfirmPassword(!showMobileConfirmPassword)}
+                            className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600 dark:hover:text-slate-200"
+                        >
+                            {showMobileConfirmPassword ? <Eye size={18} /> : <EyeOff size={18} />}
+                        </button>
+                    </div>
+                </div>
+
+                <button
+                    onClick={handleMobileSubmit}
+                    disabled={loading}
+                    className={`mt-6 w-full rounded-lg bg-primary py-3 font-bold text-white shadow-lg transition-all hover:bg-primary/90 ${loading ? 'opacity-70 cursor-not-allowed' : ''
+                        }`}
+                >
+                    {loading ? '提交中...' : '提交修改'}
+                </button>
+            </div>
+        );
+    };
+
     return (
-        <div className="fixed left-0 top-0 flex h-screen overflow-hidden bg-white">
+        <div className="fixed left-0 top-0 flex h-screen overflow-hidden bg-white z-[100]">
             <div className="relative z-20 flex h-full w-full">
                 <div className="flex h-full w-80 flex-col bg-background-light dark:bg-background-dark shadow-2xl">
                     <div className="flex items-center border-b border-slate-200 dark:border-slate-800 py-3 px-2">
-                        <button 
+                        <button
                             onClick={onBack}
                             className="text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200"
                         >
@@ -66,76 +364,23 @@ export const ChangePassword = ({ onBack }: ChangePasswordProps) => {
                         <h1 className="text-md text-center pl-2 flex-1 pr-8 text-slate-900 dark:text-slate-100">修改密码</h1>
                     </div>
 
-                    <div className="flex-1 p-6 space-y-4">
-                        <div className="space-y-2">
-                            <label className="text-sm font-medium text-slate-700 dark:text-slate-300">旧密码</label>
-                            <div className="relative">
-                                <input
-                                    type={showOldPassword ? "text" : "password"}
-                                    placeholder="请输入旧密码"
-                                    className="w-full rounded-lg border border-slate-200 bg-white p-2.5 pr-10 text-sm focus:border-primary focus:outline-none dark:border-slate-800 dark:bg-slate-900 text-slate-900 dark:text-slate-100 placeholder:text-slate-400"
-                                    value={oldPassword}
-                                    onChange={(e) => setOldPassword(e.target.value)}
-                                />
-                                <button
-                                    type="button"
-                                    onClick={() => setShowOldPassword(!showOldPassword)}
-                                    className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600 dark:hover:text-slate-200"
-                                >
-                                    {showOldPassword ? <Eye size={18} /> : <EyeOff size={18} />}
-                                </button>
-                            </div>
-                        </div>
-
-                        <div className="space-y-2">
-                            <label className="text-sm font-medium text-slate-700 dark:text-slate-300">新密码</label>
-                            <div className="relative">
-                                <input
-                                    type={showNewPassword ? "text" : "password"}
-                                    placeholder="请输入新密码"
-                                    className="w-full rounded-lg border border-slate-200 bg-white p-2.5 pr-10 text-sm focus:border-primary focus:outline-none dark:border-slate-800 dark:bg-slate-900 text-slate-900 dark:text-slate-100 placeholder:text-slate-400"
-                                    value={newPassword}
-                                    onChange={(e) => setNewPassword(e.target.value)}
-                                />
-                                <button
-                                    type="button"
-                                    onClick={() => setShowNewPassword(!showNewPassword)}
-                                    className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600 dark:hover:text-slate-200"
-                                >
-                                    {showNewPassword ? <Eye size={18} /> : <EyeOff size={18} />}
-                                </button>
-                            </div>
-                        </div>
-
-                        <div className="space-y-2">
-                            <label className="text-sm font-medium text-slate-700 dark:text-slate-300">确认新密码</label>
-                            <div className="relative">
-                                <input
-                                    type={showConfirmPassword ? "text" : "password"}
-                                    placeholder="请再次输入新密码"
-                                    className="w-full rounded-lg border border-slate-200 bg-white p-2.5 pr-10 text-sm focus:border-primary focus:outline-none dark:border-slate-800 dark:bg-slate-900 text-slate-900 dark:text-slate-100 placeholder:text-slate-400"
-                                    value={confirmPassword}
-                                    onChange={(e) => setConfirmPassword(e.target.value)}
-                                />
-                                <button
-                                    type="button"
-                                    onClick={() => setShowConfirmPassword(!showConfirmPassword)}
-                                    className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600 dark:hover:text-slate-200"
-                                >
-                                    {showConfirmPassword ? <Eye size={18} /> : <EyeOff size={18} />}
-                                </button>
-                            </div>
-                        </div>
-
+                    <div className="flex w-full border-b border-slate-200 dark:border-slate-800">
                         <button
-                            onClick={handleSubmit}
-                            disabled={loading}
-                            className={`mt-6 w-full rounded-lg bg-primary py-3 font-bold text-white shadow-lg transition-all hover:bg-primary/90 ${
-                                loading ? 'opacity-70 cursor-not-allowed' : ''
-                            }`}
+                            className={`flex-1 py-3 text-sm font-medium border-b-2 transition-colors ${activeTab === 'password' ? 'border-primary text-primary' : 'border-transparent text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-300'}`}
+                            onClick={() => setActiveTab('password')}
                         >
-                            {loading ? '提交中...' : '提交修改'}
+                            密码改密
                         </button>
+                        <button
+                            className={`flex-1 py-3 text-sm font-medium border-b-2 transition-colors ${activeTab === 'mobile' ? 'border-primary text-primary' : 'border-transparent text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-300'}`}
+                            onClick={() => setActiveTab('mobile')}
+                        >
+                            手机改密
+                        </button>
+                    </div>
+
+                    <div className="flex-1 p-6 space-y-4 overflow-y-auto">
+                        {activeTab === 'password' ? renderPasswordTab() : renderMobileTab()}
                     </div>
                 </div>
             </div>

+ 32 - 6
src/components/slide-bar/index.tsx

@@ -4,6 +4,7 @@ import { BindPhone } from "./bind-phone";
 import { RealName } from "./real-name";
 import { Agreement } from "../agreement";
 import SlideBar from "./slide-bar";
+import { UnbindPhone } from "./unbind-phone";
 
 interface SlideBarIndexProps {
     onClose: () => void;
@@ -12,6 +13,9 @@ interface SlideBarIndexProps {
 export default function SlideBarIndex({ onClose }: SlideBarIndexProps) {
     const [showChangePassword, setShowChangePassword] = useState(false);
     const [showBindPhone, setShowBindPhone] = useState(false);
+    const [showUnbindPhone, setShowUnbindPhone] = useState(false);
+    const [currentMobile, setCurrentMobile] = useState('');
+    const [currentRealName, setCurrentRealName] = useState('');
     const [showRealName, setShowRealName] = useState(false);
     const [showAgreement, setShowAgreement] = useState<{ title: string; url: string } | null>(null);
     const [showSlideBar, setShowSlideBar] = useState(true);
@@ -19,6 +23,7 @@ export default function SlideBarIndex({ onClose }: SlideBarIndexProps) {
     const handleBack = () => {
         setShowChangePassword(false);
         setShowBindPhone(false);
+        setShowUnbindPhone(false);
         setShowRealName(false);
         setShowAgreement(null);
         setShowSlideBar(true);
@@ -29,18 +34,39 @@ export default function SlideBarIndex({ onClose }: SlideBarIndexProps) {
             <div className="absolute inset-0" onClick={onClose}></div>
             <div className="relative z-110">
                 {showSlideBar && (
-                    <SlideBar 
-                        onChangePassword={() => { setShowChangePassword(true); setShowSlideBar(false); }} 
-                        onBindPhone={() => { setShowBindPhone(true); setShowSlideBar(false); }}
-                        onRealName={() => { setShowRealName(true); setShowSlideBar(false); }}
+                    <SlideBar
+                        onChangePassword={(mobile) => {
+                            if (mobile) {
+                                setCurrentMobile(mobile);
+                            } else {
+                                setCurrentMobile('');
+                            }
+                            setShowChangePassword(true);
+                            setShowSlideBar(false);
+                        }}
+                        onBindPhone={(mobile) => {
+                            if (mobile) {
+                                setCurrentMobile(mobile);
+                                setShowUnbindPhone(true);
+                            } else {
+                                setShowBindPhone(true);
+                            }
+                            setShowSlideBar(false);
+                        }}
+                        onRealName={(rn) => {
+                            setCurrentRealName(rn || '');
+                            setShowRealName(true);
+                            setShowSlideBar(false);
+                        }}
                         onAgreement={() => { setShowAgreement({ title: '用户协议', url: '/static/user.html' }); setShowSlideBar(false); }}
                         onPrivacy={() => { setShowAgreement({ title: '隐私政策', url: '/static/ys.html' }); setShowSlideBar(false); }}
                         onClose={onClose}
                     />
                 )}
-                {showChangePassword && <ChangePassword onBack={handleBack} />}
+                {showChangePassword && <ChangePassword mobile={currentMobile} onBack={handleBack} />}
                 {showBindPhone && <BindPhone onBack={handleBack} />}
-                {showRealName && <RealName onBack={handleBack} />}
+                {showUnbindPhone && <UnbindPhone mobile={currentMobile} onBack={handleBack} onSuccess={() => { setShowUnbindPhone(false); setShowBindPhone(true); }} />}
+                {showRealName && <RealName verifiedRealName={currentRealName} onBack={handleBack} />}
                 {showAgreement && <Agreement title={showAgreement.title} url={showAgreement.url} onBack={handleBack} />}
             </div>
         </div>

+ 49 - 39
src/components/slide-bar/real-name/index.tsx

@@ -1,12 +1,13 @@
-import { ChevronLeft } from "lucide-react";
+import { ChevronLeft, ShieldCheck } from "lucide-react";
 import { useState } from "react";
 import { realNameAuthApi } from "../../../api/login";
 
 export interface RealNameProps {
+    verifiedRealName?: string;
     onBack: () => void;
 }
 
-export const RealName = ({ onBack }: RealNameProps) => {
+export const RealName = ({ verifiedRealName, onBack }: RealNameProps) => {
     const [realName, setRealName] = useState('');
     const [idCard, setIdCard] = useState('');
     const [loading, setLoading] = useState(false);
@@ -20,7 +21,7 @@ export const RealName = ({ onBack }: RealNameProps) => {
             alert('请输入身份证号');
             return;
         }
-        
+
         // 简单的身份证号校验
         const idCardReg = /^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/;
         if (!idCardReg.test(idCard)) {
@@ -48,7 +49,7 @@ export const RealName = ({ onBack }: RealNameProps) => {
             <div className="relative z-20 flex h-full w-full">
                 <div className="flex h-full w-80 flex-col bg-background-light dark:bg-background-dark shadow-2xl">
                     <div className="flex items-center border-b border-slate-200 dark:border-slate-800 py-3 px-2">
-                        <button 
+                        <button
                             onClick={onBack}
                             className="text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200"
                         >
@@ -57,45 +58,54 @@ export const RealName = ({ onBack }: RealNameProps) => {
                         <h1 className="text-md text-center pl-2 flex-1 pr-8 text-slate-900 dark:text-slate-100">实名认证</h1>
                     </div>
 
-                    <div className="flex-1 p-6 space-y-4">
-                        <div className="space-y-2">
-                            <label className="text-sm font-medium text-slate-700 dark:text-slate-300">真实姓名</label>
-                            <input
-                                type="text"
-                                placeholder="请输入真实姓名"
-                                className="w-full rounded-lg border border-slate-200 bg-white p-2.5 text-sm focus:border-primary focus:outline-none dark:border-slate-800 dark:bg-slate-900 text-slate-900 dark:text-slate-100 placeholder:text-slate-400"
-                                value={realName}
-                                onChange={(e) => setRealName(e.target.value)}
-                            />
-                        </div>
+                    {verifiedRealName ? (
+                        <div className="flex-1 p-6 flex flex-col items-center space-y-4">
+                            <ShieldCheck size={64} className="text-green-500" />
+                            <p className="text-sm font-medium text-slate-600 dark:text-slate-300">
+                                已经完成实名认证
+                            </p>
 
-                        <div className="space-y-2">
-                            <label className="text-sm font-medium text-slate-700 dark:text-slate-300">身份证号</label>
-                            <input
-                                type="text"
-                                placeholder="请输入身份证号"
-                                className="w-full rounded-lg border border-slate-200 bg-white p-2.5 text-sm focus:border-primary focus:outline-none dark:border-slate-800 dark:bg-slate-900 text-slate-900 dark:text-slate-100 placeholder:text-slate-400"
-                                value={idCard}
-                                onChange={(e) => setIdCard(e.target.value)}
-                            />
                         </div>
+                    ) : (
+                        <div className="flex-1 p-6 space-y-4">
+                            <div className="space-y-2">
+                                <label className="text-sm font-medium text-slate-700 dark:text-slate-300">真实姓名</label>
+                                <input
+                                    type="text"
+                                    placeholder="请输入真实姓名"
+                                    className="w-full rounded-lg border border-slate-200 bg-white p-2.5 text-sm focus:border-primary focus:outline-none dark:border-slate-800 dark:bg-slate-900 text-slate-900 dark:text-slate-100 placeholder:text-slate-400"
+                                    value={realName}
+                                    onChange={(e) => setRealName(e.target.value)}
+                                />
+                            </div>
 
-                        <div className="mt-4 rounded-lg bg-orange-50 p-3 dark:bg-orange-900/20">
-                            <p className="text-[10px] leading-relaxed text-orange-600 dark:text-orange-400">
-                                温馨提示:根据国家相关法律法规,该游戏需要进行实名认证。我们承诺会对您的信息严格保密。
-                            </p>
-                        </div>
+                            <div className="space-y-2">
+                                <label className="text-sm font-medium text-slate-700 dark:text-slate-300">身份证号</label>
+                                <input
+                                    type="text"
+                                    placeholder="请输入身份证号"
+                                    className="w-full rounded-lg border border-slate-200 bg-white p-2.5 text-sm focus:border-primary focus:outline-none dark:border-slate-800 dark:bg-slate-900 text-slate-900 dark:text-slate-100 placeholder:text-slate-400"
+                                    value={idCard}
+                                    onChange={(e) => setIdCard(e.target.value)}
+                                />
+                            </div>
 
-                        <button
-                            onClick={handleSubmit}
-                            disabled={loading}
-                            className={`mt-6 w-full rounded-lg bg-primary py-3 font-bold text-white shadow-lg transition-all hover:bg-primary/90 ${
-                                loading ? 'opacity-70 cursor-not-allowed' : ''
-                            }`}
-                        >
-                            {loading ? '提交中...' : '提交认证'}
-                        </button>
-                    </div>
+                            <div className="mt-4 rounded-lg bg-orange-50 p-3 dark:bg-orange-900/20">
+                                <p className="text-[10px] leading-relaxed text-orange-600 dark:text-orange-400">
+                                    温馨提示:根据国家相关法律法规,该游戏需要进行实名认证。我们承诺会对您的信息严格保密。
+                                </p>
+                            </div>
+
+                            <button
+                                onClick={handleSubmit}
+                                disabled={loading}
+                                className={`mt-6 w-full rounded-lg bg-primary py-3 font-bold text-white shadow-lg transition-all hover:bg-primary/90 ${loading ? 'opacity-70 cursor-not-allowed' : ''
+                                    }`}
+                            >
+                                {loading ? '提交中...' : '提交认证'}
+                            </button>
+                        </div>
+                    )}
                 </div>
             </div>
         </div>

+ 77 - 56
src/components/slide-bar/slide-bar.tsx

@@ -4,23 +4,32 @@ import { pmBridge } from "../../lib/PostMessageBridge";
 import { logoutApi } from "../../api/login";
 import { useAtomValue } from "jotai";
 import { modalStackAtom } from "../../store";
+import { useEffect, useState } from "react";
+import { getUserInfoApi } from "../../api/user";
 
-export default function SlideBar({ 
-    onChangePassword, 
-    onBindPhone, 
+export default function SlideBar({
+    onChangePassword,
+    onBindPhone,
     onRealName,
-    onAgreement,
-    onPrivacy,
-    onClose 
-}: { 
-    onChangePassword: () => void, 
-    onBindPhone: () => void, 
-    onRealName: () => void,
+    onClose
+}: {
+    onChangePassword: (mobile?: string) => void,
+    onBindPhone: (mobile?: string) => void,
+    onRealName: (realName?: string) => void,
     onAgreement: () => void,
     onPrivacy: () => void,
     onClose: () => void
 }) {
-    
+
+    const [userInfo, setUserInfo] = useState<any>({
+        "uid": null,
+        "user_name": null,
+        "email": null,
+        "mobile": null,
+        "real_name": null
+    });
+    const modalStack = useAtomValue(modalStackAtom);
+
     const handleLogout = async () => {
         try {
             // 1. 调用退出登录接口
@@ -28,50 +37,62 @@ export default function SlideBar({
             // 2. 清除本地存储和状态
             logout();
             // 3. 通知父页面(游戏)
-            const payload = useAtomValue(modalStackAtom).onLogout;
-            pmBridge.sendToIframe('ONLOGOUT', {success:true, message:"退出登录成功"}, payload.requestId);
+            const payload = modalStack.onLogout;
+            pmBridge.sendToIframe('ONLOGOUT', { success: true, message: "退出登录成功" }, payload.requestId);
 
             // 4. 关闭侧边栏
             onClose();
         } catch (error) {
             console.error('Logout API failed:', error);
-        } 
+        }
     };
 
+    const getUserInfo = async () => {
+        const res = await getUserInfoApi();
+        console.log("UserInfoRes", res)
+        setUserInfo(res);
+    }
+
+    useEffect(() => {
+        getUserInfo()
+    }, [])
+
+
+
     return (
-            <div className="fixed left-0 top-0 flex h-screen overflow-hidden bg-white">
+        <div className="fixed left-0 top-0 flex h-screen overflow-hidden bg-white">
 
-                <div className="relative z-20 flex h-full w-full" onClick={(e) => e.stopPropagation()}>
-                    <div className="flex h-full w-80 flex-col bg-background-light dark:bg-background-dark shadow-2xl">
+            <div className="relative z-20 flex h-full w-full" onClick={(e) => e.stopPropagation()}>
+                <div className="flex h-full w-80 flex-col bg-background-light dark:bg-background-dark shadow-2xl">
 
-                        <div className="p-6 pb-4 border-b border-slate-200 dark:border-slate-800">
-                            <div className="flex">
-                                <div className="flex flex-col">
-                                    <h2 className="text-xl font-bold tracking-tight text-slate-900 dark:text-slate-100">CyberWarrior_99</h2>
-                                    <p className="text-xs font-medium text-primary bg-primary/10 px-2 py-0.5 rounded-full inline-block mt-1">ID: #8842-X01</p>
-                                </div>
+                    <div className="p-6 pb-4 border-b border-slate-200 dark:border-slate-800">
+                        <div className="flex">
+                            <div className="flex flex-col">
+                                <h2 className="text-xl font-bold tracking-tight text-slate-900 dark:text-slate-100">{userInfo.user_name}</h2>
+                                <p className="text-xs font-medium text-primary  py-0.5 rounded-full inline-block mt-1">ID: {userInfo.uid}</p>
                             </div>
                         </div>
+                    </div>
 
-                        <div className="flex-1 overflow-y-auto py-4">
-                            <nav className="space-y-2">
-                                <p className="px-3 pb-2 text-xs font-semibold uppercase tracking-wider text-slate-400">账户安全</p>
-                                <button className="border-b border-slate-200 dark:border-slate-800 flex w-full items-center gap-4  px-3 py-3 text-slate-700 transition-colors hover:bg-primary/10 hover:text-primary dark:text-slate-300 dark:hover:bg-primary/20 dark:hover:text-primary" onClick={onBindPhone}>
-                                    <span className="material-symbols-outlined"><Smartphone size="16" /></span>
-                                    <span className="text-[14px] font-bold">绑定手机</span>
-                                    <span className="material-symbols-outlined ml-auto text-slate-300"><ChevronRight size="16" /></span>
-                                </button>
-                                <button className="border-b border-slate-200 dark:border-slate-800 flex w-full items-center gap-4  px-3 py-3 text-slate-700 transition-colors hover:bg-primary/10 hover:text-primary dark:text-slate-300 dark:hover:bg-primary/20 dark:hover:text-primary" onClick={onChangePassword}>
-                                    <span className="material-symbols-outlined"><Key size="16" /></span>
-                                    <span className="text-[14px] font-bold">修改密码</span>
-                                    <span className="material-symbols-outlined ml-auto text-slate-300"><ChevronRight size="16" /></span>
-                                </button>
-                                <button className="border-b border-slate-200 dark:border-slate-800 flex w-full items-center gap-4  px-3 py-3 text-slate-700 transition-colors hover:bg-primary/10 hover:text-primary dark:text-slate-300 dark:hover:bg-primary/20 dark:hover:text-primary" onClick={onRealName}>
-                                    <span className="material-symbols-outlined"><IdCard size="16" /></span>
-                                    <span className="text-[14px] font-bold">实名认证</span>
-                                    <span className="material-symbols-outlined ml-auto text-slate-300"><ChevronRight size="16" /></span>
-                                </button>
-
+                    <div className="flex-1 overflow-y-auto py-4">
+                        <nav className="space-y-2">
+                            <p className="px-3 pb-2 text-xs font-semibold uppercase tracking-wider text-slate-400">账户安全</p>
+                            <button className="border-b border-slate-200 dark:border-slate-800 flex w-full items-center gap-4  px-3 py-3 text-slate-700 transition-colors hover:bg-primary/10 hover:text-primary dark:text-slate-300 dark:hover:bg-primary/20 dark:hover:text-primary" onClick={() => onBindPhone(userInfo.mobile)}>
+                                <span className="material-symbols-outlined"><Smartphone size="16" /></span>
+                                <span className="text-[14px] font-bold">{userInfo.mobile ? '解绑手机' : '绑定手机'}</span>
+                                <span className="material-symbols-outlined ml-auto text-slate-300"><ChevronRight size="16" /></span>
+                            </button>
+                            <button className="border-b border-slate-200 dark:border-slate-800 flex w-full items-center gap-4  px-3 py-3 text-slate-700 transition-colors hover:bg-primary/10 hover:text-primary dark:text-slate-300 dark:hover:bg-primary/20 dark:hover:text-primary" onClick={() => onChangePassword(userInfo.mobile)}>
+                                <span className="material-symbols-outlined"><Key size="16" /></span>
+                                <span className="text-[14px] font-bold">修改密码</span>
+                                <span className="material-symbols-outlined ml-auto text-slate-300"><ChevronRight size="16" /></span>
+                            </button>
+                            <button className="border-b border-slate-200 dark:border-slate-800 flex w-full items-center gap-4  px-3 py-3 text-slate-700 transition-colors hover:bg-primary/10 hover:text-primary dark:text-slate-300 dark:hover:bg-primary/20 dark:hover:text-primary" onClick={() => onRealName(userInfo.real_name)}>
+                                <span className="material-symbols-outlined"><IdCard size="16" /></span>
+                                <span className="text-[14px] font-bold">实名认证</span>
+                                <span className="material-symbols-outlined ml-auto text-slate-300"><ChevronRight size="16" /></span>
+                            </button>
+                            {/* 
                                 <p className="px-3 pb-2 pt-4 text-xs font-semibold uppercase tracking-wider text-slate-400">法律协议</p>
                                 <button className="border-b border-slate-200 dark:border-slate-800 flex w-full items-center gap-4  px-3 py-3 text-slate-700 transition-colors hover:bg-primary/10 hover:text-primary dark:text-slate-300 dark:hover:bg-primary/20 dark:hover:text-primary" onClick={onAgreement}>
                                     <span className="text-[14px] font-bold px-1">用户协议</span>
@@ -80,22 +101,22 @@ export default function SlideBar({
                                 <button className="border-b border-slate-200 dark:border-slate-800 flex w-full items-center gap-4  px-3 py-3 text-slate-700 transition-colors hover:bg-primary/10 hover:text-primary dark:text-slate-300 dark:hover:bg-primary/20 dark:hover:text-primary" onClick={onPrivacy}>
                                     <span className="text-[14px] font-bold px-1">隐私政策</span>
                                     <span className="material-symbols-outlined ml-auto text-slate-300"><ChevronRight size="16" /></span>
-                                </button>
-                            </nav>
-                        </div>
-                        <div className="p-4 border-t border-slate-200 dark:border-slate-800">
-                            <button 
-                                onClick={handleLogout}
-                                className="flex w-full items-center justify-center gap-2 rounded-xl bg-slate-100 py-3 text-[14px] font-bold text-slate-900 transition-colors hover:bg-red-50 hover:text-red-600 dark:bg-slate-800 dark:text-slate-100 dark:hover:bg-red-900/30 dark:hover:text-red-400"
-                            >
-                                <span className="material-symbols-outlined text-xl"><SquareArrowRightExit size="16" /></span>
-                                <span>退出登录</span>
-                            </button>
-                            <p className="mt-4 text-center text-[10px] text-slate-400">SDK Version {import.meta.env.VITE_SDK_VERSION}</p>
-                        </div>
+                                </button> */}
+                        </nav>
+                    </div>
+                    <div className="p-4 border-t border-slate-200 dark:border-slate-800">
+                        <button
+                            onClick={handleLogout}
+                            className="flex w-full items-center justify-center gap-2 rounded-xl bg-slate-100 py-3 text-[14px] font-bold text-slate-900 transition-colors hover:bg-red-50 hover:text-red-600 dark:bg-slate-800 dark:text-slate-100 dark:hover:bg-red-900/30 dark:hover:text-red-400"
+                        >
+                            <span className="material-symbols-outlined text-xl"><SquareArrowRightExit size="16" /></span>
+                            <span>退出登录</span>
+                        </button>
+                        <p className="mt-4 text-center text-[10px] text-slate-400">SDK Version {import.meta.env.VITE_SDK_VERSION}</p>
                     </div>
-                    <div className="flex-1 cursor-pointer" onClick={onClose}></div>
                 </div>
+                <div className="flex-1 cursor-pointer" onClick={onClose}></div>
             </div>
+        </div>
     );
 }

+ 130 - 0
src/components/slide-bar/unbind-phone/index.tsx

@@ -0,0 +1,130 @@
+import { ChevronLeft } from "lucide-react";
+import { useState, useEffect, useRef } from "react";
+import { sendUnbindMobileCodeApi, unbindMobileApi } from "../../../api/login";
+
+export interface UnbindPhoneProps {
+    mobile: string;
+    onBack: () => void;
+    onSuccess: () => void;
+}
+
+export const UnbindPhone = ({ mobile, onBack, onSuccess }: UnbindPhoneProps) => {
+    const [code, setCode] = useState('');
+    const [loading, setLoading] = useState(false);
+    const [countdown, setCountdown] = useState(0);
+    const timerRef = useRef<any>(null);
+
+    useEffect(() => {
+        if (countdown > 0) {
+            timerRef.current = setTimeout(() => {
+                setCountdown(countdown - 1);
+            }, 1000);
+        } else {
+            if (timerRef.current) clearTimeout(timerRef.current);
+        }
+        return () => {
+            if (timerRef.current) clearTimeout(timerRef.current);
+        };
+    }, [countdown]);
+
+    const handleSendCode = async () => {
+        if (!mobile) {
+            alert('获取手机号失败');
+            return;
+        }
+
+        setLoading(true);
+        try {
+            await sendUnbindMobileCodeApi({
+                mobile: mobile
+            });
+            alert('验证码已发送');
+            setCountdown(60);
+        } catch (error: any) {
+            alert(error.message || '发送失败');
+        } finally {
+            setLoading(false);
+        }
+    };
+
+    const handleSubmit = async () => {
+        if (!code) {
+            alert('请输入验证码');
+            return;
+        }
+
+        setLoading(true);
+        try {
+            await unbindMobileApi({
+                mobile: mobile,
+                code: code
+            });
+            alert('解绑成功');
+            onSuccess();
+        } catch (error: any) {
+            alert(error.message || '解绑失败');
+        } finally {
+            setLoading(false);
+        }
+    };
+
+    // Mask mobile number for display (e.g., 138****1234)
+    const maskedMobile = mobile ? mobile.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2') : '';
+
+    return (
+        <div className="fixed left-0 top-0 flex h-screen overflow-hidden bg-white">
+            <div className="relative z-20 flex h-full w-full">
+                <div className="flex h-full w-80 flex-col bg-background-light dark:bg-background-dark shadow-2xl">
+                    <div className="flex items-center border-b border-slate-200 dark:border-slate-800 py-3 px-2">
+                        <button 
+                            onClick={onBack}
+                            className="text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200"
+                        >
+                            <ChevronLeft size="24" />
+                        </button>
+                        <h1 className="text-md text-center pl-2 flex-1 pr-8 text-slate-900 dark:text-slate-100">解绑手机</h1>
+                    </div>
+
+                    <div className="flex-1 p-6 space-y-4">
+                        <div className="space-y-2">
+                            <label className="text-sm font-medium text-slate-700 dark:text-slate-300">已绑定手机号</label>
+                            <div className="w-full rounded-lg border border-slate-200 bg-slate-50/50 p-2.5 text-sm dark:border-slate-800 dark:bg-slate-900/50 text-slate-900 dark:text-slate-100">
+                                {maskedMobile}
+                            </div>
+                        </div>
+
+                        <div className="space-y-2">
+                            <label className="text-sm font-medium text-slate-700 dark:text-slate-300">验证码</label>
+                            <div className="flex gap-2">
+                                <input
+                                    type="text"
+                                    placeholder="请输入验证码"
+                                    className="flex-1 rounded-lg border border-slate-200 bg-white p-2.5 text-sm focus:border-primary focus:outline-none dark:border-slate-800 dark:bg-slate-900 text-slate-900 dark:text-slate-100 placeholder:text-slate-400"
+                                    value={code}
+                                    onChange={(e) => setCode(e.target.value)}
+                                />
+                                <button
+                                    onClick={handleSendCode}
+                                    disabled={loading || countdown > 0}
+                                    className={`w-28 rounded-lg bg-slate-100 text-xs font-medium text-slate-600 transition-all hover:bg-slate-200 disabled:opacity-50 dark:bg-slate-800 dark:text-slate-400 dark:hover:bg-slate-700`}
+                                >
+                                    {countdown > 0 ? `${countdown}s` : '获取验证码'}
+                                </button>
+                            </div>
+                        </div>
+
+                        <button
+                            onClick={handleSubmit}
+                            disabled={loading}
+                            className={`mt-6 w-full rounded-lg bg-primary py-3 font-bold text-white shadow-lg transition-all hover:bg-primary/90 ${
+                                loading ? 'opacity-70 cursor-not-allowed' : ''
+                            }`}
+                        >
+                            {loading ? '提交中...' : '提交解绑'}
+                        </button>
+                    </div>
+                </div>
+            </div>
+        </div>
+    );
+};

+ 28 - 0
vite.config.ts.js

@@ -0,0 +1,28 @@
+// vite.config.ts
+import { defineConfig, loadEnv } from "vite";
+import react from "@vitejs/plugin-react";
+import tailwindcss from "@tailwindcss/vite";
+var vite_config_default = defineConfig(({ mode }) => {
+  const env = loadEnv(mode, process.cwd());
+  const proxyPrefix = env.VITE_APP_PROXY_PREFIX;
+  const target = env.VITE_APP_BASE_URL;
+  return {
+    plugins: [
+      react(),
+      tailwindcss()
+    ],
+    server: {
+      proxy: {
+        [proxyPrefix]: {
+          target,
+          changeOrigin: true,
+          rewrite: (path) => path.replace(new RegExp(`^${proxyPrefix}`), "")
+        }
+      }
+    }
+  };
+});
+export {
+  vite_config_default as default
+};
+//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImltcG9ydCB7IGRlZmluZUNvbmZpZywgbG9hZEVudiB9IGZyb20gJ3ZpdGUnXG5pbXBvcnQgcmVhY3QgZnJvbSAnQHZpdGVqcy9wbHVnaW4tcmVhY3QnXG5pbXBvcnQgdGFpbHdpbmRjc3MgZnJvbSAnQHRhaWx3aW5kY3NzL3ZpdGUnXG5cbi8vIGh0dHBzOi8vdml0ZS5kZXYvY29uZmlnL1xuZXhwb3J0IGRlZmF1bHQgZGVmaW5lQ29uZmlnKCh7IG1vZGUgfSkgPT4ge1xuICBjb25zdCBlbnYgPSBsb2FkRW52KG1vZGUsIHByb2Nlc3MuY3dkKCkpXG4gIGNvbnN0IHByb3h5UHJlZml4ID0gZW52LlZJVEVfQVBQX1BST1hZX1BSRUZJWFxuICBjb25zdCB0YXJnZXQgPSBlbnYuVklURV9BUFBfQkFTRV9VUkxcblxuICByZXR1cm4ge1xuICAgIHBsdWdpbnM6IFtcbiAgICAgIHJlYWN0KCksXG4gICAgICB0YWlsd2luZGNzcygpLFxuICAgIF0sXG4gICAgc2VydmVyOiB7XG4gICAgICBwcm94eToge1xuICAgICAgICBbcHJveHlQcmVmaXhdOiB7XG4gICAgICAgICAgdGFyZ2V0OiB0YXJnZXQsXG4gICAgICAgICAgY2hhbmdlT3JpZ2luOiB0cnVlLFxuICAgICAgICAgIHJld3JpdGU6IChwYXRoKSA9PiBwYXRoLnJlcGxhY2UobmV3IFJlZ0V4cChgXiR7cHJveHlQcmVmaXh9YCksICcnKVxuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICB9XG59KVxuIl0sCiAgIm1hcHBpbmdzIjogIjtBQUFBLFNBQVMsY0FBYyxlQUFlO0FBQ3RDLE9BQU8sV0FBVztBQUNsQixPQUFPLGlCQUFpQjtBQUd4QixJQUFPLHNCQUFRLGFBQWEsQ0FBQyxFQUFFLEtBQUssTUFBTTtBQUN4QyxRQUFNLE1BQU0sUUFBUSxNQUFNLFFBQVEsSUFBSSxDQUFDO0FBQ3ZDLFFBQU0sY0FBYyxJQUFJO0FBQ3hCLFFBQU0sU0FBUyxJQUFJO0FBRW5CLFNBQU87QUFBQSxJQUNMLFNBQVM7QUFBQSxNQUNQLE1BQU07QUFBQSxNQUNOLFlBQVk7QUFBQSxJQUNkO0FBQUEsSUFDQSxRQUFRO0FBQUEsTUFDTixPQUFPO0FBQUEsUUFDTCxDQUFDLGNBQWM7QUFBQSxVQUNiO0FBQUEsVUFDQSxjQUFjO0FBQUEsVUFDZCxTQUFTLENBQUMsU0FBUyxLQUFLLFFBQVEsSUFBSSxPQUFPLElBQUksYUFBYSxHQUFHLEVBQUU7QUFBQSxRQUNuRTtBQUFBLE1BQ0Y7QUFBQSxJQUNGO0FBQUEsRUFDRjtBQUNGLENBQUM7IiwKICAibmFtZXMiOiBbXQp9Cg==