feat(home): 角色增加性别筛选
This commit is contained in:
parent
f477cc95f8
commit
8b933d26d6
|
|
@ -3,5 +3,7 @@
|
|||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 100
|
||||
"printWidth": 100,
|
||||
"arrowParens": "always",
|
||||
"endOfLine": "lf"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
"@radix-ui/react-dialog": "^1.1.14",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.15",
|
||||
"@radix-ui/react-label": "^2.1.7",
|
||||
"@radix-ui/react-popover": "^1.1.15",
|
||||
"@radix-ui/react-radio-group": "^1.3.7",
|
||||
"@radix-ui/react-select": "^2.2.5",
|
||||
"@radix-ui/react-separator": "^1.1.7",
|
||||
|
|
@ -48,6 +49,7 @@
|
|||
"embla-carousel-react": "^8.6.0",
|
||||
"js-cookie": "^3.0.5",
|
||||
"lamejs": "^1.2.1",
|
||||
"lodash": "^4.17.21",
|
||||
"lucide-react": "^0.525.0",
|
||||
"next": "16.0.8",
|
||||
"next-intl": "^4.6.1",
|
||||
|
|
@ -71,6 +73,7 @@
|
|||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/lodash": "^4.17.21",
|
||||
"@types/node": "^20",
|
||||
"@types/numeral": "^2.0.5",
|
||||
"@types/qs": "^6.14.0",
|
||||
|
|
|
|||
150
pnpm-lock.yaml
150
pnpm-lock.yaml
|
|
@ -50,6 +50,9 @@ importers:
|
|||
'@radix-ui/react-label':
|
||||
specifier: ^2.1.7
|
||||
version: 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
|
||||
'@radix-ui/react-popover':
|
||||
specifier: ^1.1.15
|
||||
version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
|
||||
'@radix-ui/react-radio-group':
|
||||
specifier: ^1.3.7
|
||||
version: 1.3.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
|
||||
|
|
@ -80,6 +83,9 @@ importers:
|
|||
'@types/crypto-js':
|
||||
specifier: ^4.2.2
|
||||
version: 4.2.2
|
||||
'@types/lodash':
|
||||
specifier: ^4.17.21
|
||||
version: 4.17.21
|
||||
'@types/react-stickynode':
|
||||
specifier: ^4.0.3
|
||||
version: 4.0.3
|
||||
|
|
@ -113,6 +119,9 @@ importers:
|
|||
lamejs:
|
||||
specifier: ^1.2.1
|
||||
version: 1.2.1
|
||||
lodash:
|
||||
specifier: ^4.17.21
|
||||
version: 4.17.21
|
||||
lucide-react:
|
||||
specifier: ^0.525.0
|
||||
version: 0.525.0(react@19.2.1)
|
||||
|
|
@ -1209,6 +1218,9 @@ packages:
|
|||
'@radix-ui/primitive@1.1.2':
|
||||
resolution: {integrity: sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==}
|
||||
|
||||
'@radix-ui/primitive@1.1.3':
|
||||
resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==}
|
||||
|
||||
'@radix-ui/react-alert-dialog@1.1.14':
|
||||
resolution: {integrity: sha512-IOZfZ3nPvN6lXpJTBCunFQPRSvK8MDgSc1FB85xnIpUKOw9en0dJj8JmCAxV7BiZdtYlUpmrQjoTFkVYtdoWzQ==}
|
||||
peerDependencies:
|
||||
|
|
@ -1340,6 +1352,19 @@ packages:
|
|||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-dismissable-layer@1.1.11':
|
||||
resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-dropdown-menu@2.1.15':
|
||||
resolution: {integrity: sha512-mIBnOjgwo9AH3FyKaSWoSu/dYj6VdhJ7frEPiGTeXCdUFHjl9h3mFh2wwhEtINOmYXWhdpf1rY2minFsmaNgVQ==}
|
||||
peerDependencies:
|
||||
|
|
@ -1362,6 +1387,15 @@ packages:
|
|||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-focus-guards@1.1.3':
|
||||
resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-focus-scope@1.1.7':
|
||||
resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==}
|
||||
peerDependencies:
|
||||
|
|
@ -1410,6 +1444,19 @@ packages:
|
|||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-popover@1.1.15':
|
||||
resolution: {integrity: sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-popper@1.2.7':
|
||||
resolution: {integrity: sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ==}
|
||||
peerDependencies:
|
||||
|
|
@ -1423,6 +1470,19 @@ packages:
|
|||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-popper@1.2.8':
|
||||
resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-portal@1.1.9':
|
||||
resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==}
|
||||
peerDependencies:
|
||||
|
|
@ -1449,6 +1509,19 @@ packages:
|
|||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-presence@1.1.5':
|
||||
resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-primitive@2.1.3':
|
||||
resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==}
|
||||
peerDependencies:
|
||||
|
|
@ -2103,6 +2176,9 @@ packages:
|
|||
'@types/jsonwebtoken@9.0.10':
|
||||
resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==}
|
||||
|
||||
'@types/lodash@4.17.21':
|
||||
resolution: {integrity: sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ==}
|
||||
|
||||
'@types/ms@2.1.0':
|
||||
resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}
|
||||
|
||||
|
|
@ -5757,6 +5833,8 @@ snapshots:
|
|||
|
||||
'@radix-ui/primitive@1.1.2': {}
|
||||
|
||||
'@radix-ui/primitive@1.1.3': {}
|
||||
|
||||
'@radix-ui/react-alert-dialog@1.1.14(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.2
|
||||
|
|
@ -5883,6 +5961,19 @@ snapshots:
|
|||
'@types/react': 19.2.7
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.7)
|
||||
|
||||
'@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.1)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
|
||||
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.7)(react@19.2.1)
|
||||
'@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.7)(react@19.2.1)
|
||||
react: 19.2.1
|
||||
react-dom: 19.2.1(react@19.2.1)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.7
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.7)
|
||||
|
||||
'@radix-ui/react-dropdown-menu@2.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.2
|
||||
|
|
@ -5904,6 +5995,12 @@ snapshots:
|
|||
optionalDependencies:
|
||||
'@types/react': 19.2.7
|
||||
|
||||
'@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.7)(react@19.2.1)':
|
||||
dependencies:
|
||||
react: 19.2.1
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.7
|
||||
|
||||
'@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)':
|
||||
dependencies:
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.1)
|
||||
|
|
@ -5957,6 +6054,29 @@ snapshots:
|
|||
'@types/react': 19.2.7
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.7)
|
||||
|
||||
'@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.1)
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.1)
|
||||
'@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
|
||||
'@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.7)(react@19.2.1)
|
||||
'@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
|
||||
'@radix-ui/react-id': 1.1.1(@types/react@19.2.7)(react@19.2.1)
|
||||
'@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
|
||||
'@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
|
||||
'@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
|
||||
'@radix-ui/react-slot': 1.2.3(@types/react@19.2.7)(react@19.2.1)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.7)(react@19.2.1)
|
||||
aria-hidden: 1.2.6
|
||||
react: 19.2.1
|
||||
react-dom: 19.2.1(react@19.2.1)
|
||||
react-remove-scroll: 2.7.1(@types/react@19.2.7)(react@19.2.1)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.7
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.7)
|
||||
|
||||
'@radix-ui/react-popper@1.2.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)':
|
||||
dependencies:
|
||||
'@floating-ui/react-dom': 2.1.4(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
|
||||
|
|
@ -5975,6 +6095,24 @@ snapshots:
|
|||
'@types/react': 19.2.7
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.7)
|
||||
|
||||
'@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)':
|
||||
dependencies:
|
||||
'@floating-ui/react-dom': 2.1.4(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
|
||||
'@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.1)
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.1)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
|
||||
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.7)(react@19.2.1)
|
||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.1)
|
||||
'@radix-ui/react-use-rect': 1.1.1(@types/react@19.2.7)(react@19.2.1)
|
||||
'@radix-ui/react-use-size': 1.1.1(@types/react@19.2.7)(react@19.2.1)
|
||||
'@radix-ui/rect': 1.1.1
|
||||
react: 19.2.1
|
||||
react-dom: 19.2.1(react@19.2.1)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.7
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.7)
|
||||
|
||||
'@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)':
|
||||
dependencies:
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
|
||||
|
|
@ -5995,6 +6133,16 @@ snapshots:
|
|||
'@types/react': 19.2.7
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.7)
|
||||
|
||||
'@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)':
|
||||
dependencies:
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.1)
|
||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.1)
|
||||
react: 19.2.1
|
||||
react-dom: 19.2.1(react@19.2.1)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.7
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.7)
|
||||
|
||||
'@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)':
|
||||
dependencies:
|
||||
'@radix-ui/react-slot': 1.2.3(@types/react@19.2.7)(react@19.2.1)
|
||||
|
|
@ -6725,6 +6873,8 @@ snapshots:
|
|||
'@types/ms': 2.1.0
|
||||
'@types/node': 20.19.8
|
||||
|
||||
'@types/lodash@4.17.21': {}
|
||||
|
||||
'@types/ms@2.1.0': {}
|
||||
|
||||
'@types/node@20.19.8':
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
|
@ -1,20 +1,20 @@
|
|||
'use client'
|
||||
'use client';
|
||||
|
||||
import { useParams } from 'next/navigation'
|
||||
import { useGetAIUserBaseInfo } from '@/hooks/aiUser'
|
||||
import Image from 'next/image'
|
||||
import { useParams } from 'next/navigation';
|
||||
import { useGetAIUserBaseInfo } from '@/hooks/aiUser';
|
||||
import Image from 'next/image';
|
||||
|
||||
// 按钮组件
|
||||
interface MobileButtonProps {
|
||||
showIcon?: boolean
|
||||
icon?: React.ReactNode | null
|
||||
btnTxt?: string
|
||||
showTxt?: boolean
|
||||
size?: 'Large' | 'Medium' | 'Small'
|
||||
variant?: 'Contrast' | 'Basic' | 'Ghost'
|
||||
type?: 'Primary' | 'Secondary' | 'Tertiary' | 'Destructive' | 'VIP'
|
||||
state?: 'Default' | 'Disabled' | 'Pressed'
|
||||
onClick?: () => void
|
||||
showIcon?: boolean;
|
||||
icon?: React.ReactNode | null;
|
||||
btnTxt?: string;
|
||||
showTxt?: boolean;
|
||||
size?: 'Large' | 'Medium' | 'Small';
|
||||
variant?: 'Contrast' | 'Basic' | 'Ghost';
|
||||
type?: 'Primary' | 'Secondary' | 'Tertiary' | 'Destructive' | 'VIP';
|
||||
state?: 'Default' | 'Disabled' | 'Pressed';
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
function MobileButton({
|
||||
|
|
@ -53,7 +53,7 @@ function MobileButton({
|
|||
</div>
|
||||
)}
|
||||
</button>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -64,22 +64,22 @@ function MobileButton({
|
|||
{showIcon && icon}
|
||||
{showTxt && <span className="text-[12px] font-medium text-white">{btnTxt}</span>}
|
||||
</button>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const SharePage = () => {
|
||||
const { userId } = useParams()
|
||||
const { userId } = useParams();
|
||||
|
||||
const { data: userInfo } = useGetAIUserBaseInfo({
|
||||
aiId: userId ? Number(userId) : 0,
|
||||
})
|
||||
});
|
||||
|
||||
const { homeImageUrl } = userInfo || {}
|
||||
const { homeImageUrl } = userInfo || {};
|
||||
|
||||
const handleChatClick = () => {
|
||||
// 跳转到应用或下载页面
|
||||
window.open('https://crushlevel.com/download', '_blank')
|
||||
}
|
||||
window.open('https://crushlevel.com/download', '_blank');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative mx-auto h-screen max-w-[750px] overflow-hidden">
|
||||
|
|
@ -140,7 +140,7 @@ const SharePage = () => {
|
|||
<div className="relative inline-grid shrink-0 grid-cols-[max-content] grid-rows-[max-content] place-items-start px-6 leading-[0]">
|
||||
{/* AI信息卡片 */}
|
||||
<div className="relative box-border flex w-full shrink-0 flex-col content-stretch items-start justify-start gap-1 px-0 py-2">
|
||||
<div className="relative box-border flex w-full shrink-0 flex-col content-stretch items-start justify-start gap-2 rounded-[16px] border border-[rgba(251,222,255,0.2)] bg-[rgba(251,222,255,0.08)] p-[16px] backdrop-blur-[32px] backdrop-filter">
|
||||
<div className="relative box-border flex w-full shrink-0 flex-col content-stretch items-start justify-start gap-2 rounded-2xl border border-[rgba(251,222,255,0.2)] bg-[rgba(251,222,255,0.08)] p-[16px] backdrop-blur-[32px] backdrop-filter">
|
||||
{/* 介绍文本 */}
|
||||
<div className="font-regular relative line-clamp-3 w-full shrink-0 overflow-hidden text-[14px] leading-[20px] text-white">
|
||||
<p>
|
||||
|
|
@ -197,7 +197,7 @@ const SharePage = () => {
|
|||
|
||||
{/* 示例对话消息 */}
|
||||
<div className="relative box-border flex w-full shrink-0 content-stretch items-start justify-start gap-4 pt-4 pr-20 pb-2 pl-0">
|
||||
<div className="relative box-border flex min-h-px min-w-px shrink-0 grow basis-0 content-stretch items-start justify-start gap-2.5 rounded-[16px] bg-[rgba(0,0,0,0.65)] px-4 pt-5 pb-4 backdrop-blur-[32px] backdrop-filter">
|
||||
<div className="relative box-border flex min-h-px min-w-px shrink-0 grow basis-0 content-stretch items-start justify-start gap-2.5 rounded-2xl bg-[rgba(0,0,0,0.65)] px-4 pt-5 pb-4 backdrop-blur-[32px] backdrop-filter">
|
||||
{/* 语音标签 */}
|
||||
<div className="absolute top-[-12px] left-0 box-border flex content-stretch items-center justify-center gap-2 overflow-clip rounded-tl-[8px] rounded-tr-[8px] rounded-br-[8px] bg-[#484151] px-3 py-1">
|
||||
<div className="relative size-3 shrink-0">
|
||||
|
|
@ -224,7 +224,7 @@ const SharePage = () => {
|
|||
|
||||
{/* 底部品牌区域 */}
|
||||
<div className="relative box-border flex w-full shrink-0 content-stretch items-start justify-start gap-1 overflow-clip px-2 py-4">
|
||||
<div className="relative box-border flex min-h-px min-w-px shrink-0 grow basis-0 content-stretch items-center justify-start gap-3 rounded-[16px] bg-gradient-to-r from-[#f264a4] to-[#c241e6] px-4 py-2">
|
||||
<div className="relative box-border flex min-h-px min-w-px shrink-0 grow basis-0 content-stretch items-center justify-start gap-3 rounded-2xl bg-gradient-to-r from-[#f264a4] to-[#c241e6] px-4 py-2">
|
||||
{/* App图标 */}
|
||||
<div className="relative size-[24px] shrink-0 overflow-clip rounded-[12px]">
|
||||
<Image
|
||||
|
|
@ -265,7 +265,7 @@ const SharePage = () => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default SharePage
|
||||
export default SharePage;
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export function CheckInGrid() {
|
|||
{Array.from({ length: 6 }).map((_, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="animate-pulse rounded-[16px] border border-[rgba(251,222,255,0.2)] bg-[#282233]"
|
||||
className="animate-pulse rounded-2xl border border-[rgba(251,222,255,0.2)] bg-[#282233]"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -7,20 +7,20 @@ import { useHomeStore } from '../../store';
|
|||
import { useEffect } from 'react';
|
||||
|
||||
const Character = () => {
|
||||
const selectedTags = useHomeStore((state) => state.selectedTags);
|
||||
const characterParams = useHomeStore((state) => state.characterParams);
|
||||
|
||||
const { dataSource, isFirstLoading, isLoadingMore, noMoreData, onLoadMore, onSearch } =
|
||||
useSmartInfiniteQuery<any, any>(fetchCharacters, {
|
||||
queryKey: 'characters',
|
||||
defaultQuery: { tagIds: selectedTags },
|
||||
defaultQuery: characterParams,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
onSearch({ tagIds: selectedTags });
|
||||
}, [selectedTags]);
|
||||
onSearch(characterParams);
|
||||
}, [characterParams]);
|
||||
|
||||
return (
|
||||
<div className="mt-8">
|
||||
<div className="mt-4 sm:mt-8">
|
||||
<InfiniteScrollList<any>
|
||||
items={dataSource}
|
||||
enableLazyRender
|
||||
|
|
|
|||
|
|
@ -1,116 +0,0 @@
|
|||
'use client';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
import Image from 'next/image';
|
||||
import { Chip } from '@/components/ui/chip';
|
||||
import { useHomeStore } from '../store';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { fetchCharacterTags } from '@/services/editor';
|
||||
import { useRef } from 'react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
const Filter = () => {
|
||||
const tab = useHomeStore((state) => state.tab);
|
||||
const setTab = useHomeStore((state) => state.setTab);
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const selectedTags = useHomeStore((state) => state.selectedTags);
|
||||
const setSelectedTags = useHomeStore((state) => state.setSelectedTags);
|
||||
const t = useTranslations('home');
|
||||
|
||||
// useEffect(() => {
|
||||
// const mainContent = document.getElementById('main-content');
|
||||
// if (!mainContent) {
|
||||
// return;
|
||||
// }
|
||||
// const handleScroll = () => {
|
||||
// const scrollTop = mainContent.scrollTop;
|
||||
// console.log('scrollTop', scrollTop, ref.current);
|
||||
// const className = 'absolute bg-bg-primary-normal';
|
||||
// if (scrollTop > 248) {
|
||||
// ref.current?.classList.add('absolute bg-bg-primary-normal');
|
||||
// } else {
|
||||
// }
|
||||
// };
|
||||
// mainContent?.addEventListener('scroll', handleScroll);
|
||||
// return () => {
|
||||
// mainContent?.removeEventListener('scroll', handleScroll);
|
||||
// };
|
||||
// }, []);
|
||||
|
||||
const { data: tags = [] } = useQuery({
|
||||
queryKey: ['tags', tab],
|
||||
queryFn: async () => {
|
||||
if (tab === 'character') {
|
||||
const { data } = await fetchCharacterTags({ limit: 10 });
|
||||
return data.rows;
|
||||
}
|
||||
return [];
|
||||
},
|
||||
});
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
label: t('story'),
|
||||
value: 'story',
|
||||
icon: 'icon-story',
|
||||
activeIcon: 'icon-story-active',
|
||||
},
|
||||
{
|
||||
label: t('character'),
|
||||
value: 'character',
|
||||
icon: 'icon-character',
|
||||
activeIcon: 'icon-character-active',
|
||||
},
|
||||
] as const;
|
||||
|
||||
const handleSelect = (tagId: string) => {
|
||||
if (selectedTags.includes(tagId)) {
|
||||
setSelectedTags(selectedTags.filter((id) => id !== tagId));
|
||||
} else {
|
||||
setSelectedTags([...selectedTags, tagId]);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<div className="flex mb-6 gap-12">
|
||||
{tabs.map((item) => {
|
||||
const active = tab === item.value;
|
||||
return (
|
||||
<div
|
||||
key={item.value}
|
||||
onClick={() => setTab(item.value)}
|
||||
className={cn(
|
||||
'flex items-center cursor-pointer gap-2',
|
||||
active ? 'text-txt-primary-normal' : 'text-txt-secondary-normal'
|
||||
)}
|
||||
>
|
||||
<Image
|
||||
src={`/images/home/${active ? item.activeIcon : item.icon}.png`}
|
||||
alt={item.label}
|
||||
width={28}
|
||||
height={28}
|
||||
/>
|
||||
<span className="txt-headline-s">{item.label}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{tags?.map((tag: any) => (
|
||||
<Chip
|
||||
key={tag.id}
|
||||
size="small"
|
||||
className="px-4"
|
||||
state={selectedTags.includes(tag.id) ? 'active' : 'inactive'}
|
||||
onClick={() => handleSelect(tag.id)}
|
||||
>
|
||||
# {tag.name}
|
||||
</Chip>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Filter;
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
'use client';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useHomeStore } from '../../store';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import IconFont from '@/components/ui/iconFont';
|
||||
import { Popover, PopoverTrigger, PopoverContent } from '@/components/ui/popver';
|
||||
import omit from 'lodash/omit';
|
||||
import { TagListSelect } from './TagListSelect';
|
||||
|
||||
export const MoreFilter = React.memo(() => {
|
||||
const characterParams = useHomeStore((state) => state.characterParams);
|
||||
const setCharacterParams = useHomeStore((state) => state.setCharacterParams);
|
||||
const t = useTranslations('common');
|
||||
|
||||
// 计算筛选条件数量
|
||||
const queryKeyNum = useMemo(() => {
|
||||
const currentParams = omit(characterParams, ['tagIds']);
|
||||
return Object.values(currentParams).reduce((acc, curr) => {
|
||||
const count = Array.isArray(curr) ? curr.length : 1;
|
||||
return acc + count;
|
||||
}, 0);
|
||||
}, [characterParams]);
|
||||
|
||||
const genderOptions = [
|
||||
{
|
||||
label: t('gender_0'),
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
label: t('gender_1'),
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: t('gender_2'),
|
||||
value: 2,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<button
|
||||
className={cn(
|
||||
'flex cursor-pointer items-center gap-1 hover:text-white/80 transition-colors',
|
||||
queryKeyNum > 0 ? 'text-white' : 'text-white/60'
|
||||
)}
|
||||
>
|
||||
<IconFont type="icon-filter-fill" />
|
||||
<span className="txt-bodySemibold-m">
|
||||
{t('filter')} {queryKeyNum > 0 ? `(${queryKeyNum})` : ''}
|
||||
</span>
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-70 p-4" align="end" sideOffset={8}>
|
||||
<div className="text-white txt-title-s mb-2">{t('gender')}</div>
|
||||
<TagListSelect
|
||||
options={genderOptions}
|
||||
value={characterParams.genders || []}
|
||||
onChange={(values) => setCharacterParams({ genders: values as number[] })}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
});
|
||||
|
||||
MoreFilter.displayName = 'MoreFilter';
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
'use client';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Chip } from '@/components/ui/chip';
|
||||
import React from 'react';
|
||||
|
||||
type TagListSelectProps = {
|
||||
options: { label: string; value: string | number }[];
|
||||
value: (string | number)[];
|
||||
onChange: (values: (string | number)[]) => void;
|
||||
chipProps?: React.ComponentProps<typeof Chip>;
|
||||
} & Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'>;
|
||||
|
||||
export const TagListSelect = (props: TagListSelectProps) => {
|
||||
const { options, value = [], onChange, chipProps, ...rest } = props;
|
||||
|
||||
const handleSelect = (id: string | number) => {
|
||||
if (value.includes(id)) {
|
||||
onChange(value.filter((v: string | number) => v !== id));
|
||||
} else {
|
||||
onChange([...value, id]);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div {...rest} className={cn('flex flex-wrap gap-2', rest.className)}>
|
||||
{options.map((option) => (
|
||||
<Chip
|
||||
key={option.value}
|
||||
size="small"
|
||||
{...chipProps}
|
||||
className={cn('px-4', chipProps?.className)}
|
||||
state={value.includes(option.value) ? 'active' : 'inactive'}
|
||||
onClick={() => handleSelect(option.value)}
|
||||
>
|
||||
{option.label}
|
||||
</Chip>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
'use client';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
import Image from 'next/image';
|
||||
import { useHomeStore } from '../../store';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { fetchCharacterTags } from '@/services/editor';
|
||||
import { useRef } from 'react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { TagListSelect } from './TagListSelect';
|
||||
import { MoreFilter } from './MoreFilter';
|
||||
|
||||
const Filter = () => {
|
||||
const tab = useHomeStore((state) => state.tab);
|
||||
const setTab = useHomeStore((state) => state.setTab);
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const characterParams = useHomeStore((state) => state.characterParams);
|
||||
const setCharacterParams = useHomeStore((state) => state.setCharacterParams);
|
||||
const t = useTranslations('home');
|
||||
|
||||
const { data: tags = [] } = useQuery({
|
||||
queryKey: ['tags', tab],
|
||||
queryFn: async () => {
|
||||
if (tab === 'character') {
|
||||
const { data } = await fetchCharacterTags({ limit: 10 });
|
||||
return data.rows;
|
||||
}
|
||||
return [];
|
||||
},
|
||||
});
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
label: t('story'),
|
||||
value: 'story',
|
||||
icon: 'icon-story',
|
||||
activeIcon: 'icon-story-active',
|
||||
},
|
||||
{
|
||||
label: t('character'),
|
||||
value: 'character',
|
||||
icon: 'icon-character',
|
||||
activeIcon: 'icon-character-active',
|
||||
},
|
||||
] as const;
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<div className="flex justify-between items-center mb-3">
|
||||
<div className="flex items-center sm:mb-6 gap-5 sm:gap-12">
|
||||
{tabs.map((item) => {
|
||||
const active = tab === item.value;
|
||||
return (
|
||||
<div
|
||||
key={item.value}
|
||||
onClick={() => setTab(item.value)}
|
||||
className={cn(
|
||||
'flex items-center cursor-pointer gap-1 sm:gap-2',
|
||||
active ? 'text-txt-primary-normal' : 'text-txt-secondary-normal'
|
||||
)}
|
||||
>
|
||||
<Image
|
||||
src={`/images/home/${active ? item.activeIcon : item.icon}.png`}
|
||||
alt={item.label}
|
||||
width={28}
|
||||
height={28}
|
||||
className="w-5 h-5 sm:w-7 sm:h-7"
|
||||
/>
|
||||
<span className="txt-title-m sm:txt-title-l">{item.label}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<MoreFilter />
|
||||
</div>
|
||||
<TagListSelect
|
||||
options={tags.map((tag: any) => ({ label: tag.name, value: tag.id }))}
|
||||
value={characterParams.tagIds || []}
|
||||
onChange={(values) => setCharacterParams({ tagIds: values as string[] })}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Filter;
|
||||
|
|
@ -6,10 +6,14 @@ import Link from 'next/link';
|
|||
import React from 'react';
|
||||
import { useLayoutStore } from '@/stores';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useSignIn } from '@/hooks/services/signin';
|
||||
|
||||
const Header = React.memo(() => {
|
||||
const response = useLayoutStore((s) => s.response);
|
||||
const t = useTranslations('home');
|
||||
const { isTodaySigned, signInListData } = useSignIn();
|
||||
|
||||
if (!signInListData?.list?.length || isTodaySigned) return <div className="h-2"></div>;
|
||||
|
||||
return (
|
||||
<Link href="/crushcoin">
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
import Image from 'next/image';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import IconFont from '@/components/ui/iconFont';
|
||||
|
||||
const HomePageFooter = () => {
|
||||
const HomePageFooter = React.memo(() => {
|
||||
const t = useTranslations('footer');
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
|
||||
|
|
@ -33,7 +32,13 @@ const HomePageFooter = () => {
|
|||
{/* Logo 和 Slogan */}
|
||||
<div className="col-span-1">
|
||||
<div className="mb-4 flex items-center gap-2">
|
||||
<IconFont type="icon-Logo" size={160} />
|
||||
<Image
|
||||
src="/logo.svg"
|
||||
alt="logo"
|
||||
width={160}
|
||||
height={160}
|
||||
className="cursor-pointer"
|
||||
/>
|
||||
</div>
|
||||
<p className="txt-body-m text-txt-secondary-normal">{t('slogan')}</p>
|
||||
</div>
|
||||
|
|
@ -123,6 +128,6 @@ const HomePageFooter = () => {
|
|||
</div>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export default HomePageFooter;
|
||||
|
|
|
|||
|
|
@ -1,15 +1,26 @@
|
|||
import { create } from 'zustand';
|
||||
|
||||
type CharacterParams = {
|
||||
genders?: number[];
|
||||
tagIds?: string[];
|
||||
};
|
||||
|
||||
interface HomeStore {
|
||||
tab: 'story' | 'character';
|
||||
selectedTags: string[];
|
||||
characterParams: CharacterParams;
|
||||
setTab: (tab: 'story' | 'character') => void;
|
||||
setSelectedTags: (selectedTags: string[]) => void;
|
||||
setCharacterParams: (params: Partial<CharacterParams>) => void;
|
||||
}
|
||||
|
||||
export const useHomeStore = create<HomeStore>((set) => ({
|
||||
export const useHomeStore = create<HomeStore>((set, get) => ({
|
||||
tab: 'character',
|
||||
characterParams: {
|
||||
genders: [],
|
||||
tagIds: [],
|
||||
},
|
||||
setTab: (tab: 'story' | 'character') => set({ tab }),
|
||||
selectedTags: [],
|
||||
setSelectedTags: (selectedTags: string[]) => set({ selectedTags }),
|
||||
setCharacterParams: (params) => {
|
||||
const { characterParams } = get();
|
||||
set({ characterParams: { ...characterParams, ...params } });
|
||||
},
|
||||
}));
|
||||
|
|
|
|||
|
|
@ -1,22 +1,22 @@
|
|||
'use client'
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect, useRef, useCallback } from 'react'
|
||||
import { IconButton } from '@/components/ui/button'
|
||||
import { useGetMemberDetail } from '@/hooks/useWallet'
|
||||
import { VipType } from '@/services/wallet'
|
||||
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { IconButton } from '@/components/ui/button';
|
||||
import { useGetMemberDetail } from '@/hooks/useWallet';
|
||||
import { VipType } from '@/services/wallet';
|
||||
|
||||
const AUTO_PLAY_INTERVAL = 4000 // 自动轮播间隔 4 秒
|
||||
const AUTO_PLAY_INTERVAL = 4000; // 自动轮播间隔 4 秒
|
||||
|
||||
const FADE_DURATION = 400 // 渐变动画时长(毫秒)
|
||||
const FADE_DURATION = 400; // 渐变动画时长(毫秒)
|
||||
|
||||
const CarouselBanner = ({ vipType, visible }: { vipType?: VipType; visible: boolean }) => {
|
||||
const [currentIndex, setCurrentIndex] = useState(0)
|
||||
const [isPaused, setIsPaused] = useState(false) // 鼠标悬停时暂停轮播
|
||||
const [isFading, setIsFading] = useState(false) // 控制淡入淡出动画
|
||||
const { data: memberDetail, isLoading } = useGetMemberDetail()
|
||||
const { memberPrivList } = memberDetail || {}
|
||||
const autoPlayTimerRef = useRef<NodeJS.Timeout | null>(null)
|
||||
const pendingIndexRef = useRef<number | null>(null)
|
||||
const [currentIndex, setCurrentIndex] = useState(0);
|
||||
const [isPaused, setIsPaused] = useState(false); // 鼠标悬停时暂停轮播
|
||||
const [isFading, setIsFading] = useState(false); // 控制淡入淡出动画
|
||||
const { data: memberDetail, isLoading } = useGetMemberDetail();
|
||||
const { memberPrivList } = memberDetail || {};
|
||||
const autoPlayTimerRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const pendingIndexRef = useRef<number | null>(null);
|
||||
|
||||
// 将 memberPrivList 转换为轮播数据格式
|
||||
const carouselData =
|
||||
|
|
@ -25,109 +25,109 @@ const CarouselBanner = ({ vipType, visible }: { vipType?: VipType; visible: bool
|
|||
title: item.title || 'VIP Feature',
|
||||
description: item.desc || 'Enjoy exclusive VIP benefits',
|
||||
backgroundImage: item.img || '/images/vip/drawer-bg.png',
|
||||
})) || []
|
||||
})) || [];
|
||||
|
||||
// 清除定时器
|
||||
const clearAutoPlayTimer = useCallback(() => {
|
||||
if (autoPlayTimerRef.current) {
|
||||
clearInterval(autoPlayTimerRef.current)
|
||||
autoPlayTimerRef.current = null
|
||||
clearInterval(autoPlayTimerRef.current);
|
||||
autoPlayTimerRef.current = null;
|
||||
}
|
||||
}, [])
|
||||
}, []);
|
||||
|
||||
// 启动自动轮播定时器
|
||||
const startAutoPlayTimer = useCallback(() => {
|
||||
clearAutoPlayTimer()
|
||||
clearAutoPlayTimer();
|
||||
if (visible && carouselData.length > 1 && !isPaused) {
|
||||
autoPlayTimerRef.current = setInterval(() => {
|
||||
setCurrentIndex((prev) => (prev + 1) % carouselData.length)
|
||||
}, AUTO_PLAY_INTERVAL)
|
||||
setCurrentIndex((prev) => (prev + 1) % carouselData.length);
|
||||
}, AUTO_PLAY_INTERVAL);
|
||||
}
|
||||
}, [visible, carouselData.length, isPaused, clearAutoPlayTimer])
|
||||
}, [visible, carouselData.length, isPaused, clearAutoPlayTimer]);
|
||||
|
||||
// 鼠标悬停时暂停轮播
|
||||
const handleMouseEnter = () => {
|
||||
setIsPaused(true)
|
||||
clearAutoPlayTimer()
|
||||
}
|
||||
setIsPaused(true);
|
||||
clearAutoPlayTimer();
|
||||
};
|
||||
|
||||
// 鼠标离开时恢复轮播
|
||||
const handleMouseLeave = () => {
|
||||
setIsPaused(false)
|
||||
}
|
||||
setIsPaused(false);
|
||||
};
|
||||
|
||||
// 带渐变动画的切换
|
||||
const changeSlideWithFade = useCallback(
|
||||
(newIndex: number) => {
|
||||
if (isFading) return // 防止动画过程中重复触发
|
||||
setIsFading(true)
|
||||
pendingIndexRef.current = newIndex
|
||||
if (isFading) return; // 防止动画过程中重复触发
|
||||
setIsFading(true);
|
||||
pendingIndexRef.current = newIndex;
|
||||
setTimeout(() => {
|
||||
setCurrentIndex(newIndex)
|
||||
setIsFading(false)
|
||||
}, FADE_DURATION)
|
||||
setCurrentIndex(newIndex);
|
||||
setIsFading(false);
|
||||
}, FADE_DURATION);
|
||||
},
|
||||
[isFading]
|
||||
)
|
||||
);
|
||||
|
||||
const nextSlide = () => {
|
||||
if (carouselData.length > 0 && !isFading) {
|
||||
const newIndex = (currentIndex + 1) % carouselData.length
|
||||
changeSlideWithFade(newIndex)
|
||||
startAutoPlayTimer() // 手动切换后重置定时器
|
||||
}
|
||||
const newIndex = (currentIndex + 1) % carouselData.length;
|
||||
changeSlideWithFade(newIndex);
|
||||
startAutoPlayTimer(); // 手动切换后重置定时器
|
||||
}
|
||||
};
|
||||
|
||||
const prevSlide = () => {
|
||||
if (carouselData.length > 0 && !isFading) {
|
||||
const newIndex = (currentIndex - 1 + carouselData.length) % carouselData.length
|
||||
changeSlideWithFade(newIndex)
|
||||
startAutoPlayTimer() // 手动切换后重置定时器
|
||||
}
|
||||
const newIndex = (currentIndex - 1 + carouselData.length) % carouselData.length;
|
||||
changeSlideWithFade(newIndex);
|
||||
startAutoPlayTimer(); // 手动切换后重置定时器
|
||||
}
|
||||
};
|
||||
|
||||
// 自动轮播(带渐变动画)
|
||||
useEffect(() => {
|
||||
if (visible && carouselData.length > 1 && !isPaused) {
|
||||
autoPlayTimerRef.current = setInterval(() => {
|
||||
setIsFading(true)
|
||||
setIsFading(true);
|
||||
setTimeout(() => {
|
||||
setCurrentIndex((prev) => (prev + 1) % carouselData.length)
|
||||
setIsFading(false)
|
||||
}, FADE_DURATION)
|
||||
}, AUTO_PLAY_INTERVAL)
|
||||
setCurrentIndex((prev) => (prev + 1) % carouselData.length);
|
||||
setIsFading(false);
|
||||
}, FADE_DURATION);
|
||||
}, AUTO_PLAY_INTERVAL);
|
||||
}
|
||||
|
||||
return () => {
|
||||
clearAutoPlayTimer()
|
||||
}
|
||||
}, [visible, carouselData.length, isPaused, clearAutoPlayTimer])
|
||||
clearAutoPlayTimer();
|
||||
};
|
||||
}, [visible, carouselData.length, isPaused, clearAutoPlayTimer]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!memberPrivList) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
if (visible) {
|
||||
if (vipType) {
|
||||
const targetIndex = memberPrivList?.findIndex((item) => item.code === vipType) ?? -1
|
||||
const targetIndex = memberPrivList?.findIndex((item) => item.code === vipType) ?? -1;
|
||||
if (targetIndex >= 0) {
|
||||
setCurrentIndex(targetIndex)
|
||||
setCurrentIndex(targetIndex);
|
||||
} else {
|
||||
setCurrentIndex(0)
|
||||
setCurrentIndex(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [visible, memberPrivList])
|
||||
}, [visible, memberPrivList]);
|
||||
|
||||
const currentSlide = carouselData[currentIndex]
|
||||
const currentSlide = carouselData[currentIndex];
|
||||
|
||||
// 加载状态
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="relative flex w-full flex-col content-stretch items-start justify-start gap-4 px-6">
|
||||
{/* 轮播图片容器 - 骨架屏 */}
|
||||
<div className="relative flex h-[234px] w-full shrink-0 flex-col content-stretch items-start justify-start gap-1 rounded-[16px] backdrop-blur-md backdrop-filter">
|
||||
<div className="relative h-[235px] w-full shrink-0 animate-pulse rounded-[16px] bg-[#2a2a2a]" />
|
||||
<div className="relative flex h-[234px] w-full shrink-0 flex-col content-stretch items-start justify-start gap-1 rounded-2xl backdrop-blur-md backdrop-filter">
|
||||
<div className="relative h-[235px] w-full shrink-0 animate-pulse rounded-2xl bg-[#2a2a2a]" />
|
||||
</div>
|
||||
|
||||
{/* 轮播内容信息 - 骨架屏 */}
|
||||
|
|
@ -136,7 +136,7 @@ const CarouselBanner = ({ vipType, visible }: { vipType?: VipType; visible: bool
|
|||
<div className="h-4 w-3/4 animate-pulse rounded bg-[#2a2a2a]" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -146,7 +146,7 @@ const CarouselBanner = ({ vipType, visible }: { vipType?: VipType; visible: bool
|
|||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
{/* 轮播图片容器 */}
|
||||
<div className="relative flex h-[234px] w-full shrink-0 flex-col content-stretch items-start justify-start gap-1 rounded-[16px]">
|
||||
<div className="relative flex h-[234px] w-full shrink-0 flex-col content-stretch items-start justify-start gap-1 rounded-2xl">
|
||||
{/* 背景图片 */}
|
||||
<div className="relative h-[235px] w-full shrink-0 overflow-hidden rounded-lg bg-cover bg-center bg-no-repeat backdrop-blur-[12px] transition-all duration-500 ease-in-out">
|
||||
<div className="bg-background-district absolute inset-0 transition-all duration-500 ease-in-out" />
|
||||
|
|
@ -194,7 +194,7 @@ const CarouselBanner = ({ vipType, visible }: { vipType?: VipType; visible: bool
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default CarouselBanner
|
||||
export default CarouselBanner;
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ export default async function RootLayout({
|
|||
className={`${poppins.variable} ${oleoScriptSwashCaps.variable} ${NumDisplay.variable} antialiased`}
|
||||
>
|
||||
<Script
|
||||
src="//at.alicdn.com/t/c/font_5076160_m6catzpb7dc.js"
|
||||
src="//at.alicdn.com/t/c/font_5076160_y1o1u6aqyb.js"
|
||||
strategy="afterInteractive"
|
||||
async
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ const AIStandardCard: React.FC<AIStandardCardProps> = React.memo(
|
|||
className={`relative flex shrink-0 grow basis-0 cursor-pointer flex-col content-stretch items-start justify-start gap-3 ${disableHover ? '' : 'group'}`}
|
||||
>
|
||||
<div
|
||||
className="relative aspect-[3/4] w-full shrink-0 overflow-hidden rounded-[16px] bg-cover bg-top bg-no-repeat transition-transform"
|
||||
className="relative aspect-[3/4] w-full shrink-0 overflow-hidden rounded-2xl bg-cover bg-top bg-no-repeat transition-transform"
|
||||
style={{ backgroundImage: displayImage ? `url('${displayImage}')` : undefined }}
|
||||
>
|
||||
<div className="relative box-border flex aspect-[3/4] size-full flex-col content-stretch items-end justify-end overflow-clip p-[12px]">
|
||||
|
|
@ -143,7 +143,7 @@ const AIStandardCard: React.FC<AIStandardCardProps> = React.memo(
|
|||
{/* 边框 */}
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="pointer-events-none absolute inset-0 rounded-[16px] border border-solid border-[rgba(251,222,255,0.2)]"
|
||||
className="pointer-events-none absolute inset-0 rounded-2xl border border-solid border-[rgba(251,222,255,0.2)]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import * as PopoverPrimitive from '@radix-ui/react-popover';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
function Popover({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Root>) {
|
||||
return <PopoverPrimitive.Root {...props} />;
|
||||
}
|
||||
|
||||
function PopoverTrigger({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
|
||||
return <PopoverPrimitive.Trigger {...props} />;
|
||||
}
|
||||
|
||||
function PopoverAnchor({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
|
||||
return <PopoverPrimitive.Anchor {...props} />;
|
||||
}
|
||||
|
||||
function PopoverContent({
|
||||
className,
|
||||
align = 'center',
|
||||
sideOffset = 4,
|
||||
...props
|
||||
}: React.ComponentProps<typeof PopoverPrimitive.Content>) {
|
||||
return (
|
||||
<PopoverPrimitive.Portal>
|
||||
<PopoverPrimitive.Content
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
'bg-surface-float-normal z-50 w-72 rounded-lg p-4 outline-none animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</PopoverPrimitive.Portal>
|
||||
);
|
||||
}
|
||||
|
||||
function PopoverArrow({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof PopoverPrimitive.Arrow>) {
|
||||
return (
|
||||
<PopoverPrimitive.Arrow
|
||||
className={cn('bg-surface-float-normal fill-surface-float-normal', className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function PopoverClose({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Close>) {
|
||||
return <PopoverPrimitive.Close {...props} />;
|
||||
}
|
||||
|
||||
export { Popover, PopoverTrigger, PopoverAnchor, PopoverContent, PopoverArrow, PopoverClose };
|
||||
|
|
@ -3,6 +3,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|||
import { useCurrentUser } from '../auth';
|
||||
import { toast } from 'sonner';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export function useSignIn() {
|
||||
const { data: user } = useCurrentUser();
|
||||
|
|
@ -24,8 +25,16 @@ export function useSignIn() {
|
|||
},
|
||||
});
|
||||
|
||||
const isTodaySigned = useMemo(() => {
|
||||
if (!signInListData?.list?.length) return false;
|
||||
const today = new Date();
|
||||
const todayStr = today.toISOString().split('T')[0];
|
||||
return signInListData.list.find((item) => item.dayStr === todayStr)?.signIn;
|
||||
}, [signInListData]);
|
||||
|
||||
return {
|
||||
signInListData,
|
||||
isTodaySigned,
|
||||
fetchSignInListLoading,
|
||||
signInLoading,
|
||||
handleSignIn,
|
||||
|
|
|
|||
|
|
@ -5,9 +5,11 @@ export default {
|
|||
edit: 'Edit',
|
||||
select: 'Select',
|
||||
default: 'Default',
|
||||
gender_0: 'Male',
|
||||
gender_1: 'Female',
|
||||
gender_2: 'Other',
|
||||
gender: 'Gender',
|
||||
gender_1: 'Male',
|
||||
gender_2: 'Female',
|
||||
gender_0: 'Other',
|
||||
filter: 'Filter',
|
||||
},
|
||||
bottomBar: {
|
||||
explore: 'Explore',
|
||||
|
|
|
|||
|
|
@ -5,9 +5,11 @@ export default {
|
|||
edit: '编辑',
|
||||
select: '选择',
|
||||
default: '默认',
|
||||
gender: '性别',
|
||||
gender_0: '男性',
|
||||
gender_1: '女性',
|
||||
gender_2: '其他',
|
||||
filter: '筛选',
|
||||
},
|
||||
bottomBar: {
|
||||
explore: '首页',
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ import Notice from './components/Notice';
|
|||
import LocaleSwitch from './components/LocaleSwitch';
|
||||
import { items } from './BottomBar';
|
||||
import { getTopbarConfig } from './config';
|
||||
import IconFont from '@/components/ui/iconFont';
|
||||
import { useLayoutStore } from '@/stores';
|
||||
import Image from 'next/image';
|
||||
|
||||
function Topbar() {
|
||||
const [isScrollBlur, setIsScrollBlur] = useState(false);
|
||||
|
|
@ -56,10 +56,12 @@ function Topbar() {
|
|||
if (response.sm || items.some((item) => item.path === pathname)) {
|
||||
return (
|
||||
<Link href="/">
|
||||
<IconFont
|
||||
size={response.sm ? 150 : 100}
|
||||
className="text-white cursor-pointer"
|
||||
type="icon-Logo"
|
||||
<Image
|
||||
src="/logo.svg"
|
||||
alt="logo"
|
||||
width={response.sm ? 150 : 100}
|
||||
height={response.sm ? 150 : 100}
|
||||
className="cursor-pointer"
|
||||
/>
|
||||
</Link>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -14,4 +14,4 @@ export const GenderTextMap = {
|
|||
[Gender.MALE]: GenderText.MALE,
|
||||
[Gender.FEMALE]: GenderText.FEMALE,
|
||||
[Gender.OTHER]: GenderText.OTHER,
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue