王晓东 3 тижнів тому
батько
коміт
db981ec270

+ 7 - 2
.env.development

@@ -1,3 +1,8 @@
 # 配置文档参考 https://taro-docs.jd.com/docs/next/env-mode-config
-# TARO_APP_ID="开发环境下的小程序appid"
-TARO_APP_BASE_URL="https://dev-api.xiaolvye.cn/"
+TARO_APP_ID="wxede8ad263e6161b6"
+TARO_APP_BASE_URL="https://dev-api.xiaolvye.cn/"
+TARO_APP_NAME_TEXT="小蓝本"
+TARO_APP_NAME="bluenote"
+# 产品故事
+TARO_APP_STORY_URL="https://mp.weixin.qq.com/s?__biz=MzkzMDc0OTU0Nw==&mid=2247483671&idx=1&sn=27749d28c6e2e96e69735df477dd1597&chksm=c274cc13f50345059f716fc795d66fbafbfdc195334b8ea68f240bc8e06b1be37b81ffd3e580"
+TARO_APP_AGREEMENT_URL="https://dev-h5.xiaolvye.cn/agreement"

+ 1 - 1
.env.production

@@ -1,4 +1,4 @@
-TARO_APP_ID="wx7b649a5c1f27244d"
+TARO_APP_ID="wxede8ad263e6161b6"
 TARO_APP_BASE_URL="https://dev-api.xiaolvye.cn/"
 TARO_APP_NAME_TEXT="小蓝本"
 TARO_APP_NAME="bluenote"

+ 1 - 0
package.json

@@ -64,6 +64,7 @@
     "event-target-polyfill": "^0.0.4",
     "miniprogram-blob": "^2.0.0",
     "miniprogram-formdata": "^2.0.0",
+    "mitt": "^3.0.1",
     "react": "^18.0.0",
     "react-dom": "^18.0.0",
     "recordrtc": "^5.6.2",

+ 20 - 0
pnpm-lock.yaml

@@ -74,6 +74,9 @@ importers:
       miniprogram-formdata:
         specifier: ^2.0.0
         version: 2.0.0
+      mitt:
+        specifier: ^3.0.1
+        version: 3.0.1
       react:
         specifier: ^18.0.0
         version: 18.0.0
@@ -150,6 +153,9 @@ importers:
       babel-preset-taro:
         specifier: 3.6.35
         version: 3.6.35(@babel/core@7.8.0)
+      cross-env:
+        specifier: ^7.0.3
+        version: 7.0.3
       eslint:
         specifier: ^8.12.0
         version: 8.12.0
@@ -3100,6 +3106,11 @@ packages:
   create-require@1.1.1:
     resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
 
+  cross-env@7.0.3:
+    resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==}
+    engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'}
+    hasBin: true
+
   cross-spawn@7.0.3:
     resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
     engines: {node: '>= 8'}
@@ -5684,6 +5695,9 @@ packages:
     resolution: {integrity: sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==}
     engines: {node: '>= 18'}
 
+  mitt@3.0.1:
+    resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
+
   mkdirp@1.0.4:
     resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
     engines: {node: '>=10'}
@@ -12546,6 +12560,10 @@ snapshots:
 
   create-require@1.1.1: {}
 
+  cross-env@7.0.3:
+    dependencies:
+      cross-spawn: 7.0.3
+
   cross-spawn@7.0.3:
     dependencies:
       path-key: 3.1.1
@@ -15618,6 +15636,8 @@ snapshots:
       minipass: 7.1.2
       rimraf: 5.0.10
 
+  mitt@3.0.1: {}
+
   mkdirp@1.0.4: {}
 
   mkdirp@3.0.1: {}

+ 1 - 1
project.config.json

@@ -2,7 +2,7 @@
   "miniprogramRoot": "dist/",
   "projectname": "bluenote",
   "description": "bluenote",
-  "appid": "wx7b649a5c1f27244d",
+  "appid": "wxede8ad263e6161b6",
   "setting": {
     "urlCheck": true,
     "es6": true,

+ 7 - 0
project.private.config.json

@@ -8,6 +8,13 @@
   "condition": {
     "miniprogram": {
       "list": [
+        {
+          "name": "login",
+          "pathName": "pages/login/index",
+          "query": "",
+          "launchMode": "default",
+          "scene": null
+        },
         {
           "name": "知识库编辑",
           "pathName": "pages/knowledge-item-editor/index",

+ 2 - 1
src/app.config.ts

@@ -1,6 +1,7 @@
 export default defineAppConfig({
   pages: [
     'pages/index/index',
+    'pages/login/index',
     'pages/contact/index',
     'pages/knowledge/index',
     'pages/knowledge-item/index',
@@ -16,7 +17,7 @@ export default defineAppConfig({
     'pages/chat/index',
     'pages/agent-gen/index',
     'pages/editor-contact/index',
-    'pages/login/index',
+    
     'pages/test/index',
     'pages/component-library/index',
     'pages/editor-pages/editor-name/index',

+ 1 - 0
src/app.less

@@ -5,6 +5,7 @@
 @import './styles/_vars.less';
 
 :root{
+  font-family: PingFang SC;
   // 主题色
   --color-primary: #317CFA;
   --color-primary-rgb: 49, 124, 250;

+ 137 - 0
src/lib/api/auth.ts

@@ -0,0 +1,137 @@
+import {
+  bluebookBaseConst,
+  getUserCenterParams,
+  usercenterbase,
+} from '@/lib/api/index'
+import request from '@/lib/module/axios.js'
+
+export interface loginWithoutAuthorizeProps {
+  autoPwd: number // "密码是否由系统自动生成(0不是,1是)",
+  avatarUrl: string // "头像地址",
+  birthday: string // "用户生日",
+  cookieValue: string // "cookieValue",
+  from: string // "登录方式",
+  loginTime: string // "登录时间",
+  mobile: string // "手机号",
+  nickName: string // "用户昵称",
+  outId: string // "第三方账号登录的openId",
+  redirectTo: string // "重定向地址",
+  regTime: string // "注册时间",
+  register: boolean // "标示当前登陆操作是否自动注册了新账户",
+  sex: number // "性别(0保密,1男,2女)",
+  token: string // "用户token",
+  tokenExpiredTime: number // "Token有效时长(秒)",
+  userId: number // "兼容老的用户id",
+  userName: string // "用户名"
+}
+// 小程序小蓝本注册不授权用户信息
+export const loginWithoutAuthorize = async (
+  payload,
+): Promise<loginWithoutAuthorizeProps> => {
+
+  return request.post(
+    `${usercenterbase}usercenter-gateway/api/v2/web/mini/sns/weixin/login/withoutAuthorize`,
+    payload,
+    {
+      params: {
+        ...getUserCenterParams(),
+      },
+    },
+  )
+}
+
+interface WechatUserInfo {
+  autoPwd: number //"密码是否由系统自动生成(0不是,1是)",
+  avatarUrl: string //"头像地址",
+  birthday: string //"用户生日",
+  cookieValue: string //"cookieValue",
+  from: string //"登录方式",
+  loginTime: string //"登录时间",
+  mobile: string //"手机号",
+  nickName: string //"用户昵称",
+  openId: string //"微信账号登录的openId",
+  outId: string //"第三方账号登录的openId",
+  redirectTo: string //"重定向地址",
+  regTime: string //"注册时间",
+  register: boolean //"标示当前登陆操作是否自动注册了新账户",
+  sex: string //"性别(0保密,1男,2女)",
+  token: string //"用户token",
+  tokenExpiredTime: number //"Token有效时长(秒)",
+  unionid: string //"微信账号登录的unionId",
+  userId: number //"兼容老的用户id",
+  userName: string //"用户名"
+}
+
+/**
+ * 登陆(不自动注册,创新项目用,不获取微信信息)
+ * @param code  临时登录凭证
+ * @returns
+ *
+ * 新用户返回结果
+ *  {
+      "userId": 0,                               // 0
+      "sex": 0,                                  // 0
+      "regTime": "1970-01-01 08:00:00",          // "1970-01-01 08:00:00"
+      "loginTime": "1970-01-01 08:00:00",        // "1970-01-01 08:00:00"
+      "tokenExpiredTime": 0,                     // 0
+      "birthday": "",                            // ""
+      "openId": "ooBvs5WHdcui5pRX514heLimtOE0",  // 真的openId
+      "unionid": "oXs7tsnhAv795dGyNOheVwPcerBg"  // 真的unionid
+    }
+ */
+export const getWechatUserInfoByCode = async (
+  code: string,
+): Promise<WechatUserInfo> => {
+  return request.post(
+    `${usercenterbase}usercenter-gateway/api/v2/web/mini/sns/weixin/login/checkRegister`,
+    {
+      code,
+    },
+    {
+      params: getUserCenterParams(),
+    },
+  )
+}
+
+export interface UserInfoResponse {
+  userId: number
+  encodeUserId: string
+  name: string
+  nickName: string
+  company: string
+  logo: string
+  eid: string
+  capStrengthStr: string
+  capStrengthStrV2: string
+  userType: number
+  pid: string
+  wechatId: string
+  mobile: string
+  position: string
+  betaUser: BetaUser
+  alpha: number
+  alphaDeadlineStr: string
+  alphaStatus: number
+  alphaExpireDate: string
+  alphaType: number
+  trialDone: number
+  autoRenewStatus: number
+  buttonText: string
+  smartCard: boolean
+  newMsgCount: string
+  alphaExpireStatus: number
+  contactImported: boolean
+}
+
+export interface BetaUser {
+  title: string
+  detailColor: string
+  destUrl: string
+  destUrlV2: string
+}
+
+export const getMyInfo = async (): Promise<UserInfoResponse> => {
+  return request.get(`${bluebookBaseConst}api/v2/user/myInfo`, {
+    omitError: true,
+  })
+}

+ 64 - 0
src/lib/api/index.ts

@@ -0,0 +1,64 @@
+import Taro from '@tarojs/taro'
+import { SHARE_CHANNEL } from '../constant'
+// Determine if the environment is `weapp` and get the `envVersion`
+const isWeApp = process.env.TARO_ENV === 'weapp'
+const accountInfo = isWeApp ? Taro.getAccountInfoSync() : null
+const envVersion = isWeApp
+  ? accountInfo?.miniProgram.envVersion || 'release'
+  : null
+
+// Helper function to determine base URL based on environment
+const getBaseUrl = (releaseUrl: string, preUrl: string) => {
+  if (isWeApp) {
+    return envVersion === 'release' ? releaseUrl : preUrl
+  } else {
+    return process.env.XLB_ENV !== 'pre_env' ? releaseUrl : preUrl
+  }
+}
+
+// Define base URLs
+export const bluebookBaseConst = getBaseUrl(
+  'https://api.xiaolanben.com/bluebook/',
+  'https://api.xiaolanben.com/bluebook-pre/',
+)
+
+export const blueServiceBaseConst = getBaseUrl(
+  'https://api.xiaolanben.com/blue-service/',
+  'https://api.xiaolanben.com/blue-service-pre/',
+)
+
+export const blueUserBaseConst = getBaseUrl(
+  'https://api.xiaolanben.com/blue-user/',
+  'https://api.xiaolanben.com/blue-user-pre/',
+)
+
+export const blueConnBaseConst = getBaseUrl(
+  'https://api.xiaolanben.com/blue-conn/',
+  'https://api.xiaolanben.com/blue-conn-pre/',
+)
+
+export const xlbGatewayBaseConst = getBaseUrl(
+  'https://api.xiaolanben.com/',
+  'https://blue-book-pre.u51.com/',
+)
+
+export const bluebookGatewayBaseConst = getBaseUrl(
+  'https://api.xiaolanben.com/xlb-gateway/blue-book/',
+  'https://api.xiaolanben.com/blue-book-idc/blue-book/',
+)
+
+export const blueServicePrefix = isWeApp
+  ? 'https://api.xiaolanben.com/'
+  : '/api.xiaolanben.com/'
+
+export const usercenterbase = 'https://api.u51.com/'
+
+export function getUserCenterParams() {
+  return {
+    big_app_id: 35,
+    app_id: 2035,
+    plat: 4,
+    channel: SHARE_CHANNEL,
+    invitationCode: undefined,
+  }
+}

+ 13 - 0
src/lib/api/upload.ts

@@ -0,0 +1,13 @@
+import { blueServiceBaseConst } from '@/lib/api/index'
+import request from '@/lib/module/axios.js'
+
+export const getOssConfig = (params) =>
+  request.post(
+    `${blueServiceBaseConst}api/v1/smartCard/interview/getoss`,
+    {
+      from: 'wechat',
+    },
+    {
+      params,
+    },
+  )

+ 13 - 0
src/lib/constant.ts

@@ -0,0 +1,13 @@
+export const USER_INFO_STORAGE_KEY = 'USER_INFO_51'
+
+export const IS_LOGIN_STORAGE_KEY = 'hasLogin'
+
+export const USER_ID_STORAGE_KEY = 'u51UserId'
+
+export const USER_ID_ENCODED_STORAGE_KEY = 'u51UserIdEncoded'
+
+export const USER_TOKEN_STORAGE_KEY = 'u51UserToken'
+
+export const OPEN_ID_STORAGE_KEY = 'u51OpenId'
+
+export const SHARE_CHANNEL = 'xiaolanben-ai-agent'

+ 159 - 0
src/lib/hooks/data/useAuth.tsx

@@ -0,0 +1,159 @@
+import { IS_LOGIN_STORAGE_KEY } from '@/lib/constant'
+import { ButtonProps } from '@tarojs/components'
+import Taro, { useDidShow } from '@tarojs/taro'
+import { useCallback, useEffect, useState } from 'react'
+import {
+  loginWithoutAuthorize,
+  loginWithoutAuthorizeProps,
+} from '../../api/auth'
+import { getNewCode, updateUserInfo } from '../../utils/auth'
+import { emitter } from '../../utils/event'
+
+export function onLogin() {
+  Taro.setStorageSync(IS_LOGIN_STORAGE_KEY, '1')
+  emitter.emit('login')
+}
+
+export function onLogout() {
+  Taro.removeStorageSync(IS_LOGIN_STORAGE_KEY)
+  emitter.emit('logout')
+}
+
+let pending = false
+
+// 拉起手机授权登录
+export function loginByMobile({
+  res,
+  sucMsg,
+  failMsg,
+  code,
+}): Promise<loginWithoutAuthorizeProps> {
+  const detail = res.detail
+
+  if (pending) {
+    // 暂时只考虑用户多次快速点击的场景,在前一个请求没结束之前,直接返回一直pedding的promise
+    return new Promise(() => ({}) as loginWithoutAuthorizeProps)
+  }
+
+  pending = true
+  return new Promise((resolve, reject) => {
+    
+    loginWithoutAuthorize({
+      code,
+      mobileEncryptedData: detail.encryptedData,
+      mobileIv: detail.iv,
+    }).then(
+      (data) => {
+        pending = false
+        Taro.showToast({
+          title: sucMsg || '登录成功',
+          icon: 'success',
+          duration: 1500,
+        })
+        resolve(data)
+      },
+      (err) => {
+        console.log(err)
+        pending = false
+        Taro.showToast({
+          title: failMsg || '登录失败',
+          icon: 'none',
+          duration: 1500,
+        })
+        reject(err)
+      },
+    )
+  })
+}
+
+export function useLogin(params?: {
+  onConfirm?: () => void
+  onSuccess?: () => void
+  disabled?: boolean
+}) {
+  const [code, setCode] = useState('')
+  function onGetPhoneNumber(res) {
+    params?.onConfirm?.()
+    const detail: ButtonProps.onGetPhoneNumberEventDetail = res.detail
+    if (detail.iv && detail.encryptedData) {
+      loginByMobile({
+        res,
+        sucMsg: '登录成功',
+        failMsg: '登录失败',
+        code: code,
+      }).then(
+        async (data) => {
+          updateUserInfo(data)
+
+          onLogin()
+          params?.onSuccess?.()
+        },
+        (err) => {
+          console.log(err)
+          Taro.showToast({
+            title: '登录失败1',
+            icon: 'none',
+            duration: 1500,
+          })
+        },
+      )
+    }
+  }
+
+  function onClick() {
+    getNewCode().then((res) => {
+      setCode(res)
+    })
+  }
+
+  function onError() {
+    Taro.showToast({
+      title: '登录失败3',
+      icon: 'none',
+      duration: 1500,
+    })
+  }
+
+  return {
+    onGetPhoneNumber,
+    onError,
+    onClick,
+    openType: params?.disabled
+      ? undefined
+      : ('getPhoneNumber' as ButtonProps.OpenType),
+  }
+}
+
+export function useIsLogin() {
+  const [isLogin, setIsLogin] = useState(false)
+
+  const updateLoginStateStable = useCallback(() => {
+    const newState = Taro.getStorageSync('hasLogin') === '1'
+    if (newState !== isLogin) {
+      setIsLogin(newState)
+    }
+  }, [isLogin])
+
+  useEffect(() => {
+    emitter.on('login', () => {
+      setIsLogin(true)
+    })
+
+    emitter.on('logout', () => {
+      setIsLogin(false)
+    })
+
+    updateLoginStateStable()
+
+    return () => {
+      emitter.off('login')
+      emitter.off('logout')
+    }
+  }, [])
+
+  useDidShow(() => {
+    updateLoginStateStable()
+  })
+
+  return isLogin
+}

+ 132 - 0
src/lib/module/alioss/Base64.js

@@ -0,0 +1,132 @@
+const Base64 = {
+
+    // private property
+    _keyStr: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
+
+    // public method for encoding
+    encode: function (input) {
+        var output = "";
+        var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
+        var i = 0;
+
+        input = Base64._utf8_encode(input);
+
+        while (i < input.length) {
+
+            chr1 = input.charCodeAt(i++);
+            chr2 = input.charCodeAt(i++);
+            chr3 = input.charCodeAt(i++);
+
+            enc1 = chr1 >> 2;
+            enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
+            enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
+            enc4 = chr3 & 63;
+
+            if (isNaN(chr2)) {
+                enc3 = enc4 = 64;
+            } else if (isNaN(chr3)) {
+                enc4 = 64;
+            }
+
+            output = output +
+                this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
+                this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
+
+        }
+
+        return output;
+    },
+
+    // public method for decoding
+    decode: function (input) {
+        var output = "";
+        var chr1, chr2, chr3;
+        var enc1, enc2, enc3, enc4;
+        var i = 0;
+
+        input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
+
+        while (i < input.length) {
+
+            enc1 = this._keyStr.indexOf(input.charAt(i++));
+            enc2 = this._keyStr.indexOf(input.charAt(i++));
+            enc3 = this._keyStr.indexOf(input.charAt(i++));
+            enc4 = this._keyStr.indexOf(input.charAt(i++));
+
+            chr1 = (enc1 << 2) | (enc2 >> 4);
+            chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
+            chr3 = ((enc3 & 3) << 6) | enc4;
+
+            output = output + String.fromCharCode(chr1);
+
+            if (enc3 != 64) {
+                output = output + String.fromCharCode(chr2);
+            }
+            if (enc4 != 64) {
+                output = output + String.fromCharCode(chr3);
+            }
+
+        }
+
+        output = Base64._utf8_decode(output);
+
+        return output;
+
+    },
+
+    // private method for UTF-8 encoding
+    _utf8_encode: function (string) {
+        string = string.replace(/\r\n/g, "\n");
+        var utftext = "";
+
+        for (var n = 0; n < string.length; n++) {
+
+            var c = string.charCodeAt(n);
+
+            if (c < 128) {
+                utftext += String.fromCharCode(c);
+            } else if ((c > 127) && (c < 2048)) {
+                utftext += String.fromCharCode((c >> 6) | 192);
+                utftext += String.fromCharCode((c & 63) | 128);
+            } else {
+                utftext += String.fromCharCode((c >> 12) | 224);
+                utftext += String.fromCharCode(((c >> 6) & 63) | 128);
+                utftext += String.fromCharCode((c & 63) | 128);
+            }
+
+        }
+
+        return utftext;
+    },
+
+    // private method for UTF-8 decoding
+    _utf8_decode: function (utftext) {
+        var string = "";
+        var i = 0;
+        var c = c1 = c2 = 0;
+
+        while (i < utftext.length) {
+
+            c = utftext.charCodeAt(i);
+
+            if (c < 128) {
+                string += String.fromCharCode(c);
+                i++;
+            } else if ((c > 191) && (c < 224)) {
+                c2 = utftext.charCodeAt(i + 1);
+                string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
+                i += 2;
+            } else {
+                c2 = utftext.charCodeAt(i + 1);
+                c3 = utftext.charCodeAt(i + 2);
+                string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
+                i += 3;
+            }
+
+        }
+
+        return string;
+    }
+}
+
+export default Base64;

+ 185 - 0
src/lib/module/alioss/crypto.js

@@ -0,0 +1,185 @@
+/*!
+ * Crypto-JS v1.1.0
+ * http://code.google.com/p/crypto-js/
+ * Copyright (c) 2009, Jeff Mott. All rights reserved.
+ * http://code.google.com/p/crypto-js/wiki/License
+ */
+
+const Crypto = {};
+
+(function(){
+
+var base64map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+
+// Crypto utilities
+var util = Crypto.util = {
+
+	// Bit-wise rotate left
+	rotl: function (n, b) {
+		return (n << b) | (n >>> (32 - b));
+	},
+
+	// Bit-wise rotate right
+	rotr: function (n, b) {
+		return (n << (32 - b)) | (n >>> b);
+	},
+
+	// Swap big-endian to little-endian and vice versa
+	endian: function (n) {
+
+		// If number given, swap endian
+		if (n.constructor == Number) {
+			return util.rotl(n,  8) & 0x00FF00FF |
+			       util.rotl(n, 24) & 0xFF00FF00;
+		}
+
+		// Else, assume array and swap all items
+		for (var i = 0; i < n.length; i++)
+			n[i] = util.endian(n[i]);
+		return n;
+
+	},
+
+	// Generate an array of any length of random bytes
+	randomBytes: function (n) {
+		for (var bytes = []; n > 0; n--)
+			bytes.push(Math.floor(Math.random() * 256));
+		return bytes;
+	},
+
+	// Convert a string to a byte array
+	stringToBytes: function (str) {
+		var bytes = [];
+		for (var i = 0; i < str.length; i++)
+			bytes.push(str.charCodeAt(i));
+		return bytes;
+	},
+
+	// Convert a byte array to a string
+	bytesToString: function (bytes) {
+		var str = [];
+		for (var i = 0; i < bytes.length; i++)
+			str.push(String.fromCharCode(bytes[i]));
+		return str.join("");
+	},
+
+	// Convert a string to big-endian 32-bit words
+	stringToWords: function (str) {
+		var words = [];
+		for (var c = 0, b = 0; c < str.length; c++, b += 8)
+			words[b >>> 5] |= str.charCodeAt(c) << (24 - b % 32);
+		return words;
+	},
+
+	// Convert a byte array to big-endian 32-bits words
+	bytesToWords: function (bytes) {
+		var words = [];
+		for (var i = 0, b = 0; i < bytes.length; i++, b += 8)
+			words[b >>> 5] |= bytes[i] << (24 - b % 32);
+		return words;
+	},
+
+	// Convert big-endian 32-bit words to a byte array
+	wordsToBytes: function (words) {
+		var bytes = [];
+		for (var b = 0; b < words.length * 32; b += 8)
+			bytes.push((words[b >>> 5] >>> (24 - b % 32)) & 0xFF);
+		return bytes;
+	},
+
+	// Convert a byte array to a hex string
+	bytesToHex: function (bytes) {
+		var hex = [];
+		for (var i = 0; i < bytes.length; i++) {
+			hex.push((bytes[i] >>> 4).toString(16));
+			hex.push((bytes[i] & 0xF).toString(16));
+		}
+		return hex.join("");
+	},
+
+	// Convert a hex string to a byte array
+	hexToBytes: function (hex) {
+		var bytes = [];
+		for (var c = 0; c < hex.length; c += 2)
+			bytes.push(parseInt(hex.substr(c, 2), 16));
+		return bytes;
+	},
+
+	// Convert a byte array to a base-64 string
+	bytesToBase64: function (bytes) {
+
+		// Use browser-native function if it exists
+		if (typeof btoa == "function") return btoa(util.bytesToString(bytes));
+
+		var base64 = [],
+		    overflow;
+
+		for (var i = 0; i < bytes.length; i++) {
+			switch (i % 3) {
+				case 0:
+					base64.push(base64map.charAt(bytes[i] >>> 2));
+					overflow = (bytes[i] & 0x3) << 4;
+					break;
+				case 1:
+					base64.push(base64map.charAt(overflow | (bytes[i] >>> 4)));
+					overflow = (bytes[i] & 0xF) << 2;
+					break;
+				case 2:
+					base64.push(base64map.charAt(overflow | (bytes[i] >>> 6)));
+					base64.push(base64map.charAt(bytes[i] & 0x3F));
+					overflow = -1;
+			}
+		}
+
+		// Encode overflow bits, if there are any
+		if (overflow != undefined && overflow != -1)
+			base64.push(base64map.charAt(overflow));
+
+		// Add padding
+		while (base64.length % 4 != 0) base64.push("=");
+
+		return base64.join("");
+
+	},
+
+	// Convert a base-64 string to a byte array
+	base64ToBytes: function (base64) {
+
+		// Use browser-native function if it exists
+		if (typeof atob == "function") return util.stringToBytes(atob(base64));
+
+		// Remove non-base-64 characters
+		base64 = base64.replace(/[^A-Z0-9+\/]/ig, "");
+
+		var bytes = [];
+
+		for (var i = 0; i < base64.length; i++) {
+			switch (i % 4) {
+				case 1:
+					bytes.push((base64map.indexOf(base64.charAt(i - 1)) << 2) |
+					           (base64map.indexOf(base64.charAt(i)) >>> 4));
+					break;
+				case 2:
+					bytes.push(((base64map.indexOf(base64.charAt(i - 1)) & 0xF) << 4) |
+					           (base64map.indexOf(base64.charAt(i)) >>> 2));
+					break;
+				case 3:
+					bytes.push(((base64map.indexOf(base64.charAt(i - 1)) & 0x3) << 6) |
+					           (base64map.indexOf(base64.charAt(i))));
+					break;
+			}
+		}
+
+		return bytes;
+
+	}
+
+};
+
+// Crypto mode namespace
+Crypto.mode = {};
+
+})();
+
+export default Crypto;

+ 41 - 0
src/lib/module/alioss/hmac.js

@@ -0,0 +1,41 @@
+/*!
+ * Crypto-JS v1.1.0
+ * http://code.google.com/p/crypto-js/
+ * Copyright (c) 2009, Jeff Mott. All rights reserved.
+ * http://code.google.com/p/crypto-js/wiki/License
+ */
+import Crypto from './crypto'
+// const Crypto = require('./crypto.js');
+
+(function(){
+
+// Shortcut
+var util = Crypto.util;
+
+Crypto.HMAC = function (hasher, message, key, options) {
+
+	// Allow arbitrary length keys
+	key = key.length > hasher._blocksize * 4 ?
+	      hasher(key, { asBytes: true }) :
+	      util.stringToBytes(key);
+
+	// XOR keys with pad constants
+	var okey = key,
+	    ikey = key.slice(0);
+	for (var i = 0; i < hasher._blocksize * 4; i++) {
+		okey[i] ^= 0x5C;
+		ikey[i] ^= 0x36;
+	}
+
+	var hmacbytes = hasher(util.bytesToString(okey) +
+	                       hasher(util.bytesToString(ikey) + message, { asString: true }),
+	                       { asBytes: true });
+	return options && options.asBytes ? hmacbytes :
+	       options && options.asString ? util.bytesToString(hmacbytes) :
+	       util.bytesToHex(hmacbytes);
+
+};
+
+})();
+
+export default Crypto;

+ 125 - 0
src/lib/module/alioss/index.ts

@@ -0,0 +1,125 @@
+import { getOssConfig } from '@/lib/api/upload'
+import Taro from '@tarojs/taro'
+import Base64 from './Base64'
+import Crypto from './crypto'
+import './hmac'
+import './sha1'
+
+// Environment configuration for OSS
+interface EnvConfig {
+  AccessKeySecret: string
+  OSSAccessKeyId: string
+  timeout: number
+}
+
+const env: EnvConfig = {
+  AccessKeySecret: '填你自己的AccessKeySecret',
+  OSSAccessKeyId: '填你自己的 OSSAccessKeyId',
+  timeout: 87600, // This is the expiration time of the upload policy
+}
+
+// Function to get the base64-encoded policy
+const getPolicyBase64 = (): string => {
+  const date = new Date()
+  date.setHours(date.getHours() + env.timeout)
+  const expirationTime = date.toISOString()
+
+  const policyText = {
+    expiration: expirationTime, // Policy expiration time
+    conditions: [
+      ['content-length-range', 0, 1024 * 1024 * 1024], // File size limit: 1GB
+    ],
+  }
+
+  return Base64.encode(JSON.stringify(policyText))
+}
+
+// Function to get the signature for the policy
+const getSignature = (
+  accessKeySecret: string,
+  policyBase64: string,
+): string => {
+  const bytes = Crypto.HMAC(Crypto.SHA1, policyBase64, accessKeySecret, {
+    asBytes: true,
+  })
+  return Crypto.util.bytesToBase64(bytes)
+}
+
+// Type definition for the callback
+type UploadCallback = (result: { url: string }) => void
+
+// Function to upload a file to Aliyun OSS
+export default function uploadFileToAlioss(
+  params: Record<string, unknown>,
+  file: { path: string },
+  cb?: UploadCallback,
+): Promise<{ url: string }> {
+  return new Promise(async (resolve, reject) => {
+    const pathArr = file.path.split('.')
+
+    if (pathArr.length > 1) {
+      try {
+        Taro.showLoading({
+          title: '上传中',
+        })
+        // Get OSS configuration
+        const config = await getOssConfig({ extension: pathArr.pop() })
+
+        const policyBase64 = getPolicyBase64()
+
+        const uploadTask = Taro.uploadFile({
+          url: 'https://visitingcard.oss-cn-hangzhou.aliyuncs.com',
+          filePath: file.path,
+          name: 'file',
+          formData: {
+            key: config.fullPath,
+            policy: policyBase64,
+            OSSAccessKeyId: config.accessKeyId,
+            signature: getSignature(config.accessKeySecret, policyBase64),
+            success_action_status: '200',
+            'x-oss-security-token': config.securityToken,
+          },
+          success() {
+            const fileUrl = `https://visitingcard.xiaolanben.com/${config.fullPath}`
+            console.log(fileUrl)
+            cb?.({ url: fileUrl })
+            resolve({ url: fileUrl })
+          },
+          fail(err) {
+            Taro.showToast({
+              title: (err && err.errMsg) || '上传失败',
+              icon: 'none',
+              duration: 1500,
+            })
+            console.log(err)
+            reject(err)
+          },
+          complete() {
+            Taro.hideLoading()
+          },
+        })
+
+        uploadTask.progress((res) => {
+          Taro.showLoading({
+            title: `已上传 ${res.progress}%`,
+          })
+        })
+      } catch (error) {
+        Taro.showToast({
+          title: '上传失败1',
+          icon: 'none',
+          duration: 1500,
+        })
+        console.error(error)
+        reject(error)
+      }
+    } else {
+      Taro.showToast({
+        title: '无法上传',
+        icon: 'none',
+        duration: 1500,
+      })
+      reject('无法上传')
+    }
+  })
+}

+ 86 - 0
src/lib/module/alioss/sha1.js

@@ -0,0 +1,86 @@
+/*!
+ * Crypto-JS v1.1.0
+ * http://code.google.com/p/crypto-js/
+ * Copyright (c) 2009, Jeff Mott. All rights reserved.
+ * http://code.google.com/p/crypto-js/wiki/License
+ */
+import Crypto from './crypto'
+// const Crypto = require('./crypto.js');
+
+(function(){
+
+// Shortcut
+var util = Crypto.util;
+
+// Public API
+var SHA1 = Crypto.SHA1 = function (message, options) {
+	var digestbytes = util.wordsToBytes(SHA1._sha1(message));
+	return options && options.asBytes ? digestbytes :
+	       options && options.asString ? util.bytesToString(digestbytes) :
+	       util.bytesToHex(digestbytes);
+};
+
+// The core
+SHA1._sha1 = function (message) {
+
+	var m  = util.stringToWords(message),
+	    l  = message.length * 8,
+	    w  =  [],
+	    H0 =  1732584193,
+	    H1 = -271733879,
+	    H2 = -1732584194,
+	    H3 =  271733878,
+	    H4 = -1009589776;
+
+	// Padding
+	m[l >> 5] |= 0x80 << (24 - l % 32);
+	m[((l + 64 >>> 9) << 4) + 15] = l;
+
+	for (var i = 0; i < m.length; i += 16) {
+
+		var a = H0,
+		    b = H1,
+		    c = H2,
+		    d = H3,
+		    e = H4;
+
+		for (var j = 0; j < 80; j++) {
+
+			if (j < 16) w[j] = m[i + j];
+			else {
+				var n = w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16];
+				w[j] = (n << 1) | (n >>> 31);
+			}
+
+			var t = ((H0 << 5) | (H0 >>> 27)) + H4 + (w[j] >>> 0) + (
+			         j < 20 ? (H1 & H2 | ~H1 & H3) + 1518500249 :
+			         j < 40 ? (H1 ^ H2 ^ H3) + 1859775393 :
+			         j < 60 ? (H1 & H2 | H1 & H3 | H2 & H3) - 1894007588 :
+			                  (H1 ^ H2 ^ H3) - 899497514);
+
+			H4 =  H3;
+			H3 =  H2;
+			H2 = (H1 << 30) | (H1 >>> 2);
+			H1 =  H0;
+			H0 =  t;
+
+		}
+
+		H0 += a;
+		H1 += b;
+		H2 += c;
+		H3 += d;
+		H4 += e;
+
+	}
+
+	return [H0, H1, H2, H3, H4];
+
+};
+
+// Package private blocksize
+SHA1._blocksize = 16;
+
+})();
+
+export default Crypto;

+ 89 - 0
src/lib/module/axios.js

@@ -0,0 +1,89 @@
+import Taro from '@tarojs/taro'
+import Axios from 'taro-axios'
+import { OPEN_ID_STORAGE_KEY } from '../constant'
+import { getUserInfo } from '../utils/auth'
+import sign from './sign'
+
+const axiosIns = Axios.create({
+  siren: false,
+})
+
+axiosIns.interceptors.request.use(async (config) => {
+  try {
+    const userInfo = await getUserInfo()
+    console.log('userInfo', userInfo)
+    // const userInfo = {}
+    config.params = config.params || {}
+    // config.params.mpversion = MP_VERSION
+    config.headers['X-Enniu-End'] = 'mp'
+    if (userInfo.userId && userInfo.token) {
+      config.headers.userId = `${userInfo.userId}`
+      config.headers.Authorization = `encrypt ${userInfo.token}`
+    }
+    const openId = Taro.getStorageInfoSync(OPEN_ID_STORAGE_KEY)
+    config.headers.openId = `${openId}`
+
+    await sign(config)
+    return config
+  } catch (e) {
+    console.log(e)
+  }
+})
+
+axiosIns.interceptors.response.use(
+  (response) => {
+    if (response.headers && response.headers.token && response.headers.userId) {
+      Taro.setStorageSync('hasLogin', '1') // 已经登录过,可以认为微信号跟手机号绑定了,后面可以只授权微信信息登录
+      Taro.setStorageSync('u51UserId', response.headers.userId)
+      Taro.setStorageSync('u51UserToken', response.headers.token)
+    }
+    return response.data
+  },
+  async (err) => {
+    console.log(err)
+    const config = err.config
+    let error = {}
+    //   if (ignoreError((err.config || {}).url)) {
+    //     return Promise.reject(err)
+    //   }
+    if (err.response && err.response.data) {
+      error = err.response.data.errors
+        ? err.response.data.errors[0]
+        : err.response.data
+    }
+    if (error.code === 401) {
+      //   await customCallback()
+      return Promise.reject(error)
+    } else if (error.code === 1020 || error.code === 1021) {
+      return error
+    } else if (
+      error &&
+      error.message &&
+      error.message.indexOf('timeout') === 0
+    ) {
+      if (!config.omitError) {
+        Taro.showToast({
+          title: '网络异常',
+          icon: 'none',
+        })
+      }
+    } else if (error.message) {
+      if (!config.omitError) {
+        Taro.showToast({
+          title: error.message,
+          icon: 'none',
+        })
+      }
+    } else {
+      if (!config.omitError) {
+        Taro.showToast({
+          title: '出了点问题,暂时加载不出来,请稍后再来吧',
+          icon: 'none',
+        })
+      }
+    }
+    return Promise.reject(err)
+  },
+)
+
+export default axiosIns

Різницю між файлами не показано, бо вона завелика
+ 121 - 0
src/lib/module/sign.js


+ 162 - 0
src/lib/utils/auth.ts

@@ -0,0 +1,162 @@
+import Taro from '@tarojs/taro'
+import {
+  getMyInfo,
+  getWechatUserInfoByCode,
+  UserInfoResponse,
+} from '../api/auth'
+import {
+  IS_LOGIN_STORAGE_KEY,
+  OPEN_ID_STORAGE_KEY,
+  USER_ID_ENCODED_STORAGE_KEY,
+  USER_ID_STORAGE_KEY,
+  USER_INFO_STORAGE_KEY,
+  USER_TOKEN_STORAGE_KEY,
+} from '../constant'
+
+export function getNewCodeIfLogin(): Promise<string> {
+  return new Promise((resolve, reject) => {
+    const isLogin = Taro.getStorageSync(IS_LOGIN_STORAGE_KEY)
+    if (!isLogin) {
+      return reject()
+    }
+    Taro.login({
+      success: (res) => {
+        console.log('success code in refreshCode', res) // 不要注释: 开发时,该console用于观测code的消耗情况
+        // 什么都不干,就是为了初始化的时候调用一次,确保后面获取到到code是有效的
+        resolve(res.code)
+      },
+      fail: () => {
+        console.log('fail code')
+        reject()
+      },
+    })
+  })
+}
+
+export function getNewCode(): Promise<string> {
+  return new Promise((resolve, reject) => {
+    Taro.login({
+      success: (res) => {
+        console.log('success code in refreshCode', res) // 不要注释: 开发时,该console用于观测code的消耗情况
+        // 什么都不干,就是为了初始化的时候调用一次,确保后面获取到到code是有效的
+        resolve(res.code)
+      },
+      fail: () => {
+        console.log('fail code')
+        reject()
+      },
+    })
+  })
+}
+
+/**
+ * 保存登录状(设为1)、保存userId、保存Token,保存整个data到storage
+ * @param {*} data
+ */
+export function updateUserInfo(data) {
+  console.log('set login info')
+  Taro.setStorageSync(IS_LOGIN_STORAGE_KEY, '1')
+  Taro.setStorageSync(USER_ID_STORAGE_KEY, data.userId)
+  Taro.setStorageSync(USER_TOKEN_STORAGE_KEY, data.token)
+  Taro.setStorageSync(USER_INFO_STORAGE_KEY, data)
+}
+
+export function clearUserInfo() {
+  console.log('clear login info')
+  Taro.clearStorageSync()
+}
+
+export function getUserInfo() {
+  return Taro.getStorageSync(USER_INFO_STORAGE_KEY)
+}
+
+// 获取用户登录
+export const getWechatUserInfo = async () => {
+  const code = await getNewCodeIfLogin()
+  const data = await getWechatUserInfoByCode(code)
+
+  return data
+}
+
+let userInfo: UserInfoResponse
+
+export default function refreshUserId(canUseCache = false) {
+  if (canUseCache && userInfo) {
+    return Promise.resolve(userInfo)
+  }
+  if (Taro.getStorageSync(IS_LOGIN_STORAGE_KEY)) {
+    return getMyInfo().then((res) => {
+      Taro.setStorageSync(USER_ID_ENCODED_STORAGE_KEY, res.encodeUserId)
+      Taro.setStorageSync(USER_ID_STORAGE_KEY, res.userId)
+      userInfo = res
+      return res
+    })
+  } else {
+    return Promise.reject(new Error('unauthorized'))
+  }
+}
+
+export function getUserId() {
+  return Taro.getStorageSync(USER_ID_STORAGE_KEY) || ''
+}
+
+export function isMySelf(userId) {
+  if (!userId) {
+    return false
+  }
+  return Taro.getStorageSync(USER_ID_STORAGE_KEY) === userId
+}
+
+let openIdCache: string | null = null
+let fetchOpenIdPromise: Promise<string> | null = null
+
+async function fetchOpenId(): Promise<string> {
+  if (fetchOpenIdPromise) return fetchOpenIdPromise
+
+  fetchOpenIdPromise = (async () => {
+    const data = await getWechatUserInfo()
+    const { openId, userId } = data
+    if (!openId) throw new Error('Failed to retrieve openId')
+
+    Taro.setStorageSync(OPEN_ID_STORAGE_KEY, openId)
+    if (userId) {
+      await updateUserInfo(data)
+    } else {
+      throw new Error('Unregistered user (userId = 0)')
+    }
+
+    openIdCache = openId
+    return openId
+  })()
+
+  try {
+    return await fetchOpenIdPromise
+  } catch (error) {
+    fetchOpenIdPromise = null
+    throw error
+  }
+}
+
+export async function getOpenIdAsync(forceRefresh = false): Promise<string> {
+  if (!forceRefresh && openIdCache) {
+    try {
+      await getMyInfo()
+      return openIdCache
+    } catch {}
+  }
+
+  if (!forceRefresh && !openIdCache) {
+    const stored = Taro.getStorageSync(OPEN_ID_STORAGE_KEY) as
+      | string
+      | undefined
+    if (stored) {
+      try {
+        await getMyInfo()
+        openIdCache = stored
+        return stored
+      } catch {}
+    }
+  }
+
+  return fetchOpenId()
+}

+ 7 - 0
src/lib/utils/event.ts

@@ -0,0 +1,7 @@
+import mitt from 'mitt'
+
+export const emitter = mitt()
+
+export enum EventKey {
+  RefreshCardPage = 'RefreshCardPage',
+}

+ 31 - 0
src/lib/utils/link.ts

@@ -0,0 +1,31 @@
+import Taro from '@tarojs/taro'
+
+export enum TabBarKey {
+  home = 'home',
+  radar = 'radar',
+  renmai = 'renmai',
+  cardFolder = 'cardFolder',
+}
+
+export function getTabParams(key: TabBarKey): Record<string, any> {
+  const old = Taro.getStorageSync('tabParams')
+  return parseQueryParams(old?.[key] || '')
+}
+
+export function consumeTabParams(key: TabBarKey) {
+  const old = Taro.getStorageSync('tabParams')
+  Taro.setStorageSync('tabParams', {
+    ...old,
+    [key]: undefined,
+  })
+}
+
+export function parseQueryParams(search: string) {
+  return search.split('&')?.reduce((acc, item) => {
+    if (item) {
+      const [key, value] = item.split('=')
+      acc[key] = value
+    }
+    return acc
+  }, {})
+}

+ 141 - 0
src/lib/utils/upload.ts

@@ -0,0 +1,141 @@
+import uploadFileToAlioss from '@/lib/module/alioss'
+import Taro from '@tarojs/taro'
+
+const IMAGE_LIMIT = 50 * 1024 * 1024 // 50MB = 52428800 bytes
+const VIDEO_LIMIT = 30 * 1024 * 1024 // 30MB
+const FILE_LIMIT = 100 * 1024 * 1024 // 100MB
+
+// Types for response
+type UserInfo = { code: number; uid: string; token: string }
+type UploadResult = { videoUrl: string; thumbnailUrl: string; size: number }
+
+// Fetch user info
+export async function getUserInfo(): Promise<UserInfo> {
+  return {
+    code: 0,
+    uid:
+      Taro.getStorageSync('userId') || Taro.getStorageSync('u51UserId') || '',
+    token:
+      Taro.getStorageSync('userToken') ||
+      Taro.getStorageSync('u51UserToken') ||
+      '',
+  }
+}
+
+// Pick a video and upload
+export async function pickAndUploadVideo(): Promise<UploadResult> {
+  try {
+    const res = await Taro.chooseMedia({
+      mediaType: ['video'],
+      sourceType: ['album'],
+      count: 1,
+    })
+
+    const data = res.tempFiles[0]
+    if (data.size >= VIDEO_LIMIT) {
+      Taro.showToast({
+        title: '视频大小超限',
+        icon: 'error',
+        duration: 1500,
+      })
+      throw new Error('Video size exceeds limit')
+    }
+
+    const imageFile = { path: data.thumbTempFilePath }
+    const videoFile = { path: data.tempFilePath }
+
+    const imageData = await uploadFileToAlioss({}, imageFile)
+    const videoData = await uploadFileToAlioss({}, videoFile)
+
+    Taro.showToast({
+      title: '上传成功',
+      icon: 'success',
+      duration: 1500,
+    })
+
+    return {
+      videoUrl: videoData.url,
+      thumbnailUrl: imageData.url,
+      size: data.size,
+    }
+  } catch (error) {
+    throw new Error('Failed to pick or upload video')
+  }
+}
+
+// Pick an image and upload
+export async function pickAndUploadImage(
+  sourceType: Array<'album' | 'camera'> = ['album', 'camera'],
+): Promise<{ url: string; size: number }> {
+  try {
+    const res = await Taro.chooseImage({
+      count: 1,
+      sizeType: ['compressed', 'original'],
+      sourceType,
+    })
+
+    const file = res.tempFiles[0]
+    if (file.size >= IMAGE_LIMIT) {
+      Taro.showToast({
+        title: '图片大小超限',
+        icon: 'error',
+        duration: 1500,
+      })
+      throw new Error('Image size exceeds limit')
+    }
+
+    const uploadResult = await uploadFileToAlioss({}, file)
+    Taro.showToast({
+      title: '上传成功',
+      icon: 'success',
+      duration: 1500,
+    })
+
+    return {
+      url: uploadResult.url,
+      size: file.size,
+    }
+  } catch (error) {
+    throw new Error('Failed to pick or upload image')
+  }
+}
+
+// pick a file from wechat message
+export async function pickAndUploadFile(): Promise<{
+  title: string
+  url: string
+  size: number
+}> {
+  try {
+    const res = await Taro.chooseMessageFile({
+      count: 1,
+      type: 'file',
+      extension: ['pdf'],
+    })
+
+    const file = res.tempFiles[0]
+    if (file.size >= FILE_LIMIT) {
+      Taro.showToast({
+        title: '文件大小超过限制',
+        icon: 'error',
+        duration: 1500,
+      })
+      throw new Error('File size exceeds limit')
+    }
+
+    const uploadResult = await uploadFileToAlioss({}, file)
+    Taro.showToast({
+      title: '上传成功',
+      icon: 'success',
+      duration: 1500,
+    })
+
+    return {
+      title: file.name,
+      url: uploadResult.url,
+      size: file.size,
+    }
+  } catch (error) {
+    throw new Error('Failed to pick or upload file')
+  }
+}

+ 2 - 1
src/pages/index/components/welcome/index.tsx

@@ -2,7 +2,7 @@ import { View } from "@tarojs/components";
 import style from "./index.module.less";
 import Taro from "@tarojs/taro";
 
-export default function Index() {
+export default function Index({children}) {
 
   const go = ()=> {
     console.log('gogogo')
@@ -12,6 +12,7 @@ export default function Index() {
   return (
     <View className={style.container}>
       <View className={style.hello}>你好</View>      
+      <View>{children}</View>
       <View className={style.welcome}>欢迎你,小蓝本的第39293位用户</View>
       <View className={style.box}>
         <View className={style.boxInner}>

+ 37 - 5
src/pages/index/index.tsx

@@ -5,17 +5,44 @@ import style from "./index.module.less";
 import NavBarNormal from "@/components/nav-bar-normal/index";
 import LogoImage from '@/images/logo.png'
 import PageCustom from "@/components/page-custom/index";
-// import { useUserStore } from "@/store/userStore";
-// import { useCharacterStore, useCurrentCharacter } from "@/store/characterStore";
-// import { useAppStore } from "@/store/appStore";
+import { UserInfoResponse } from '@/lib/api/auth'
+import { onLogout, useIsLogin } from '@/lib/hooks/data/useAuth'
+import refreshUserId, { clearUserInfo, getOpenIdAsync } from '@/lib/utils/auth'
 
 import WelcomeTips from './components/welcome/index'
 
 export default function Index() {
 
+  const [userInfo, setUserInfo] = useState<UserInfoResponse>()
+  const isLogin = useIsLogin()
 
+  function initUserInfo() {
+    refreshUserId()
+      .then((value) => {
+        setUserInfo(value)
+        getOpenIdAsync().then((openId) => {
+          console.log('🚀 ~ getOpenIdAsync ~ value:', openId)
+        })
+      })
+      .catch((error) => {
+        if (error.message === 'unauthorized' || error.code === 401) {
+          Taro.showToast({
+            title: '未登录',
+            icon: 'none',
+            duration: 2000,
+          })
+        }
+      })
+  }
 
-
+  useEffect(() => {
+    if (isLogin) {
+      initUserInfo()
+    }else{
+      // Taro.navigateTo({url: '/pages/login/index'})
+    }
+    
+  }, [isLogin])
   
 
   const renderLogo = ()=> {
@@ -26,7 +53,12 @@ export default function Index() {
     <PageCustom>
       <NavBarNormal leftColumn={renderLogo}></NavBarNormal>
       
-      <WelcomeTips></WelcomeTips>
+      <WelcomeTips>
+        <View className="flex items-center">
+          <View>{userInfo?.logo && <Image className="w-24 h-24" src={userInfo?.logo}></Image>}</View>
+          <View>{isLogin ? `已登录「${userInfo?.nickName}」` : '未登录'}</View>
+        </View>      
+      </WelcomeTips>
       
       </PageCustom>
   );

+ 25 - 15
src/pages/login/index.tsx

@@ -1,28 +1,25 @@
-// import ChatAI from "@/components/chat-ai/index";
-// import ComponentList from "@/components/component-list";
 import NavBarNormal from "@/components/nav-bar-normal/index";
-import { View,Text, Image, Checkbox } from "@tarojs/components";
+import { View,Text, Image, Checkbox, Button } from "@tarojs/components";
 import PageCustom from "@/components/page-custom/index";
 import LogoImage from '@/images/logo.png'
-// import { useAppStore } from "@/store/appStore";
-// import { useCharacterStore, useCurrentCharacter, useCharacterStyle } from "@/store/characterStore";
-// import { useComponentStore } from "@/store/componentStore";
-// import { uploadImage } from "@/utils/http";
+import { useLogin } from '@/lib/hooks/data/useAuth'
 
-// import CustomShareComponent from "@/components/custom-share/CustomShareComponent";
-// import { Button, Image, Input, View, Video } from "@tarojs/components";
-// import Taro, { useDidShow, useRouter, useUnload } from "@tarojs/taro";
-// import { useRef, useState } from "react";
-// import style from "./index.module.less";
 
 import { useState } from "react";
 
 import Popup from "@/components/popup/popup";
+import Taro from "@tarojs/taro";
 
 
 export default function Index() {
-  
-
+  const { onGetPhoneNumber, onError, onClick, openType } = useLogin({
+    onSuccess: () => {
+      console.log('yes')
+      // Taro.reLaunch({
+      //   url: '/pages/index/index',
+      // })
+    },
+  })
   
 
 
@@ -41,7 +38,20 @@ export default function Index() {
         <Popup setShow={setShowPopup} show={showPopup}>
           <View className="text-24 font-medium leading-32 text-black mb-16">欢迎来到小蓝本</View>
           <View className="text-gray-45 text-14 leading-22 mb-70">登录后享受更好的服务体验</View>
-          <View className="button-rounded-big mb-16">微信登录</View>
+          {/* <View className="button-rounded-big mb-16">微信登录</View> */}
+          <Button
+            className="button-rounded-big mb-16"
+            style={{backgroundColor: '#317CFA', fontSize: '14px'}}
+            type='primary'
+            openType={openType}
+            onGetPhoneNumber={onGetPhoneNumber}
+            onError={onError}
+            onClick={() => {
+              onClick()
+            }}
+          >
+            手机号快捷登录
+          </Button>
           <View className="text-12 mb-86 text-center">
             <Checkbox className="custom-checkbox" value='选中' checked color="#317CFA">选中</Checkbox>
             同意<Text className="primary-color">《用户隐私条款》</Text><Text className="primary-color">《用户服务须知》</Text>

Деякі файли не було показано, через те що забагато файлів було змінено