chore: 完成项目多模块迭代更新
本次更新包含: 1. 初始化git提交规范配置文件 2. 添加claude调试配置 3. 后端:修复WebSocket依赖、优化秒杀/客服模块代码、修复文件存储bug、统一包结构 4. 前端:重构登录页面、新增组件类型声明、添加自动导入配置、新增WebSocket依赖包
This commit is contained in:
parent
3288d12fbd
commit
ff0d020015
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"version": "0.0.1",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "web-snack",
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeArgs": ["run", "dev"],
|
||||
"port": 5174
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
alwaysApply: true
|
||||
scene: git_message
|
||||
language: zh-CN
|
||||
---
|
||||
|
||||
在此处编写规则,自定义 AI 生成提交信息的风格。
|
||||
中文提交信息
|
||||
|
|
@ -11,6 +11,9 @@ importers:
|
|||
'@element-plus/icons-vue':
|
||||
specifier: ^2.3.1
|
||||
version: 2.3.2(vue@3.5.35(typescript@5.9.3))
|
||||
'@stomp/stompjs':
|
||||
specifier: ^7.3.0
|
||||
version: 7.3.0
|
||||
'@vueuse/core':
|
||||
specifier: ^12.0.0
|
||||
version: 12.8.2(typescript@5.9.3)
|
||||
|
|
@ -50,6 +53,9 @@ importers:
|
|||
pinia-plugin-persistedstate:
|
||||
specifier: ^4.2.0
|
||||
version: 4.7.1(pinia@2.3.1(typescript@5.9.3)(vue@3.5.35(typescript@5.9.3)))
|
||||
sockjs-client:
|
||||
specifier: ^1.6.1
|
||||
version: 1.6.1
|
||||
vue:
|
||||
specifier: ^3.5.13
|
||||
version: 3.5.35(typescript@5.9.3)
|
||||
|
|
@ -930,6 +936,9 @@ packages:
|
|||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@stomp/stompjs@7.3.0':
|
||||
resolution: {integrity: sha512-nKMLoFfJhrQAqkvvKd1vLq/cVBGCMwPRCD0LqW7UT1fecRx9C3GoKEIR2CYwVuErGeZu8w0kFkl2rlhPlqHVgQ==}
|
||||
|
||||
'@sxzz/popperjs-es@2.11.8':
|
||||
resolution: {integrity: sha512-wOwESXvvED3S8xBmcPWHs2dUuzrE4XiZeFu7e1hROIJkm02a49N120pmOXxY33sBb6hArItm5W5tcg1cBtV+HQ==}
|
||||
|
||||
|
|
@ -1509,6 +1518,14 @@ packages:
|
|||
de-indent@1.0.2:
|
||||
resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==}
|
||||
|
||||
debug@3.2.7:
|
||||
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
|
||||
debug@4.4.3:
|
||||
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
|
||||
engines: {node: '>=6.0'}
|
||||
|
|
@ -1690,6 +1707,10 @@ packages:
|
|||
event-emitter@0.3.5:
|
||||
resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==}
|
||||
|
||||
eventsource@2.0.2:
|
||||
resolution: {integrity: sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
exsolve@1.0.8:
|
||||
resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==}
|
||||
|
||||
|
|
@ -1712,6 +1733,10 @@ packages:
|
|||
fastq@1.20.1:
|
||||
resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==}
|
||||
|
||||
faye-websocket@0.11.4:
|
||||
resolution: {integrity: sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==}
|
||||
engines: {node: '>=0.8.0'}
|
||||
|
||||
fdir@6.5.0:
|
||||
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
|
@ -1824,6 +1849,9 @@ packages:
|
|||
html-void-elements@2.0.1:
|
||||
resolution: {integrity: sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==}
|
||||
|
||||
http-parser-js@0.5.10:
|
||||
resolution: {integrity: sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==}
|
||||
|
||||
https-proxy-agent@5.0.1:
|
||||
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
|
@ -1856,6 +1884,9 @@ packages:
|
|||
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
|
||||
engines: {node: '>=0.8.19'}
|
||||
|
||||
inherits@2.0.4:
|
||||
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
||||
|
||||
is-binary-path@2.1.0:
|
||||
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
|
||||
engines: {node: '>=8'}
|
||||
|
|
@ -2205,6 +2236,9 @@ packages:
|
|||
quansync@0.2.11:
|
||||
resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==}
|
||||
|
||||
querystringify@2.2.0:
|
||||
resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==}
|
||||
|
||||
queue-microtask@1.2.3:
|
||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||
|
||||
|
|
@ -2216,6 +2250,9 @@ packages:
|
|||
resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==}
|
||||
engines: {node: '>= 20.19.0'}
|
||||
|
||||
requires-port@1.0.0:
|
||||
resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
|
||||
|
||||
resolve-from@4.0.0:
|
||||
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||
engines: {node: '>=4'}
|
||||
|
|
@ -2232,6 +2269,9 @@ packages:
|
|||
run-parallel@1.2.0:
|
||||
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
|
||||
|
||||
safe-buffer@5.2.1:
|
||||
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
||||
|
||||
sass@1.100.0:
|
||||
resolution: {integrity: sha512-B5j0rYMlinhhOo9tjQebMVVn0TfyXAF+wB3b2ggZUuJ/is/Y+7+JGjirAMxHZ9Z3hIP98NPfamlAkBHa1lAaXQ==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
|
|
@ -2276,6 +2316,10 @@ packages:
|
|||
resolution: {integrity: sha512-W2lHLLw2qR2Vv0DcMmcxXqcfdBaIcoN+y/86SmHv8fn4DazEQSH6KN3TjZcWvwujW56OHiiirsbHWZb4vx/0fg==}
|
||||
engines: {node: '>=12.17.0'}
|
||||
|
||||
sockjs-client@1.6.1:
|
||||
resolution: {integrity: sha512-2g0tjOR+fRs0amxENLi/q5TiJTqY+WXFOzb5UwXndlK6TO3U/mirZznpx6w34HVMoc3g7cY24yC/ZMIYnDlfkw==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
source-map-js@1.2.1:
|
||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
|
@ -2420,6 +2464,9 @@ packages:
|
|||
uri-js@4.4.1:
|
||||
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
||||
|
||||
url-parse@1.5.10:
|
||||
resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==}
|
||||
|
||||
util-deprecate@1.0.2:
|
||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||
|
||||
|
|
@ -2543,6 +2590,14 @@ packages:
|
|||
webpack-virtual-modules@0.6.2:
|
||||
resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==}
|
||||
|
||||
websocket-driver@0.7.4:
|
||||
resolution: {integrity: sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==}
|
||||
engines: {node: '>=0.8.0'}
|
||||
|
||||
websocket-extensions@0.1.4:
|
||||
resolution: {integrity: sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==}
|
||||
engines: {node: '>=0.8.0'}
|
||||
|
||||
which@2.0.2:
|
||||
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
|
||||
engines: {node: '>= 8'}
|
||||
|
|
@ -3185,6 +3240,8 @@ snapshots:
|
|||
'@rollup/rollup-win32-x64-msvc@4.61.0':
|
||||
optional: true
|
||||
|
||||
'@stomp/stompjs@7.3.0': {}
|
||||
|
||||
'@sxzz/popperjs-es@2.11.8': {}
|
||||
|
||||
'@transloadit/prettier-bytes@0.0.7': {}
|
||||
|
|
@ -3945,6 +4002,10 @@ snapshots:
|
|||
|
||||
de-indent@1.0.2: {}
|
||||
|
||||
debug@3.2.7:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
debug@4.4.3:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
|
@ -4223,6 +4284,8 @@ snapshots:
|
|||
d: 1.0.2
|
||||
es5-ext: 0.10.64
|
||||
|
||||
eventsource@2.0.2: {}
|
||||
|
||||
exsolve@1.0.8: {}
|
||||
|
||||
ext@1.7.0:
|
||||
|
|
@ -4247,6 +4310,10 @@ snapshots:
|
|||
dependencies:
|
||||
reusify: 1.1.0
|
||||
|
||||
faye-websocket@0.11.4:
|
||||
dependencies:
|
||||
websocket-driver: 0.7.4
|
||||
|
||||
fdir@6.5.0(picomatch@4.0.4):
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.4
|
||||
|
|
@ -4344,6 +4411,8 @@ snapshots:
|
|||
|
||||
html-void-elements@2.0.1: {}
|
||||
|
||||
http-parser-js@0.5.10: {}
|
||||
|
||||
https-proxy-agent@5.0.1:
|
||||
dependencies:
|
||||
agent-base: 6.0.2
|
||||
|
|
@ -4381,6 +4450,8 @@ snapshots:
|
|||
|
||||
imurmurhash@0.1.4: {}
|
||||
|
||||
inherits@2.0.4: {}
|
||||
|
||||
is-binary-path@2.1.0:
|
||||
dependencies:
|
||||
binary-extensions: 2.3.0
|
||||
|
|
@ -4661,6 +4732,8 @@ snapshots:
|
|||
|
||||
quansync@0.2.11: {}
|
||||
|
||||
querystringify@2.2.0: {}
|
||||
|
||||
queue-microtask@1.2.3: {}
|
||||
|
||||
readdirp@3.6.0:
|
||||
|
|
@ -4669,6 +4742,8 @@ snapshots:
|
|||
|
||||
readdirp@5.0.0: {}
|
||||
|
||||
requires-port@1.0.0: {}
|
||||
|
||||
resolve-from@4.0.0: {}
|
||||
|
||||
reusify@1.1.0: {}
|
||||
|
|
@ -4708,6 +4783,8 @@ snapshots:
|
|||
dependencies:
|
||||
queue-microtask: 1.2.3
|
||||
|
||||
safe-buffer@5.2.1: {}
|
||||
|
||||
sass@1.100.0:
|
||||
dependencies:
|
||||
chokidar: 5.0.0
|
||||
|
|
@ -4751,6 +4828,16 @@ snapshots:
|
|||
|
||||
snabbdom@3.6.3: {}
|
||||
|
||||
sockjs-client@1.6.1:
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
eventsource: 2.0.2
|
||||
faye-websocket: 0.11.4
|
||||
inherits: 2.0.4
|
||||
url-parse: 1.5.10
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
source-map-js@1.2.1: {}
|
||||
|
||||
ssr-window@3.0.0: {}
|
||||
|
|
@ -4920,6 +5007,11 @@ snapshots:
|
|||
dependencies:
|
||||
punycode: 2.3.1
|
||||
|
||||
url-parse@1.5.10:
|
||||
dependencies:
|
||||
querystringify: 2.2.0
|
||||
requires-port: 1.0.0
|
||||
|
||||
util-deprecate@1.0.2: {}
|
||||
|
||||
vite@6.4.3(@types/node@22.19.19)(jiti@2.7.0)(sass@1.100.0)(tsx@4.22.4):
|
||||
|
|
@ -5017,6 +5109,14 @@ snapshots:
|
|||
|
||||
webpack-virtual-modules@0.6.2: {}
|
||||
|
||||
websocket-driver@0.7.4:
|
||||
dependencies:
|
||||
http-parser-js: 0.5.10
|
||||
safe-buffer: 5.2.1
|
||||
websocket-extensions: 0.1.4
|
||||
|
||||
websocket-extensions@0.1.4: {}
|
||||
|
||||
which@2.0.2:
|
||||
dependencies:
|
||||
isexe: 2.0.0
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ declare module 'vue' {
|
|||
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
|
||||
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
|
||||
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||
ElDrawer: typeof import('element-plus/es')['ElDrawer']
|
||||
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
||||
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
||||
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
||||
|
|
@ -51,6 +52,8 @@ declare module 'vue' {
|
|||
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
||||
ElTag: typeof import('element-plus/es')['ElTag']
|
||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||
ElUpload: typeof import('element-plus/es')['ElUpload']
|
||||
ImageUploader: typeof import('./../components/business/ImageUploader.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
SearchBar: typeof import('./../components/common/SearchBar.vue')['default']
|
||||
|
|
|
|||
|
|
@ -35,9 +35,8 @@
|
|||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-websocket</artifactId>
|
||||
<version>6.2.15</version>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import java.io.Serializable;
|
|||
@Data
|
||||
public class Result<T> implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Integer code;
|
||||
private String message;
|
||||
private T data;
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ public class LocalFileStorage implements FileStorage {
|
|||
String storedName = IdUtil.fastSimpleUUID() + "." + ext;
|
||||
File target = new File(dir.toFile(), storedName);
|
||||
try {
|
||||
FileUtil.writeBytes(target, bytes);
|
||||
FileUtil.writeBytes(bytes, target);
|
||||
} catch (Exception e) {
|
||||
log.error("写入文件失败 {}", target.getAbsolutePath(), e);
|
||||
throw new RuntimeException("写入文件失败", e);
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ public class S3FileStorage implements FileStorage, DisposableBean {
|
|||
"S3 存储配置不完整:请在 application.yml 配置 snack.s3.{endpoint,access-key,secret-key,bucket}");
|
||||
}
|
||||
this.s3Client = S3Client.builder()
|
||||
.endpoint(URI.create(props.getEndpoint()))
|
||||
.endpointOverride(URI.create(props.getEndpoint()))
|
||||
.region(Region.of(props.getRegion()))
|
||||
.credentialsProvider(StaticCredentialsProvider.create(
|
||||
AwsBasicCredentials.create(props.getAccessKey(), props.getSecretKey())))
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.snack.server.config;
|
||||
package com.snack.server.config;
|
||||
|
||||
import cn.dev33.satoken.interceptor.SaInterceptor;
|
||||
import cn.dev33.satoken.router.SaRouter;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.snack.server.config;
|
||||
package com.snack.server.config;
|
||||
|
||||
import com.snack.server.module.chat.websocket.ChatHandshakeInterceptor;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
|
|
|||
|
|
@ -5,21 +5,6 @@ package com.snack.server.constant;
|
|||
*/
|
||||
public interface RedisKey {
|
||||
|
||||
/** 抢购商品库存:seckill:stock:{activityId}:{productId} */
|
||||
String SECKILL_STOCK = "seckill:stock:%d:%d";
|
||||
|
||||
/** 用户抢购记录:seckill:user:{userId}:{activityId}:{productId} */
|
||||
String SECKILL_USER_RECORD = "seckill:user:%d:%d:%d";
|
||||
|
||||
/** 客服在线用户集合 */
|
||||
String CHAT_ONLINE_USERS = "chat:online:users";
|
||||
|
||||
/** 会话未读消息数:chat:unread:{sessionId} */
|
||||
String CHAT_UNREAD = "chat:unread:%d";
|
||||
|
||||
/** 验证码:captcha:{uuid} */
|
||||
String CAPTCHA = "captcha:%s";
|
||||
|
||||
// ==================== 客服模块 ====================
|
||||
|
||||
/** 客服在线用户集合(Set 存放 "user:{id}" / "admin:{id}") */
|
||||
|
|
@ -31,6 +16,9 @@ public interface RedisKey {
|
|||
/** 客服 WebSocket Session 索引 */
|
||||
String CHAT_WS_ADMIN = "chat:ws:admin:%d";
|
||||
|
||||
/** 会话未读消息数:chat:unread:{sessionId} */
|
||||
String CHAT_UNREAD = "chat:unread:%d";
|
||||
|
||||
// ==================== 抢购模块 ====================
|
||||
|
||||
/** 抢购商品库存:seckill:stock:{activityId}:{skuId} */
|
||||
|
|
@ -44,4 +32,9 @@ public interface RedisKey {
|
|||
|
||||
/** 活动预热标记:seckill:warmup:{activityId}(1 表示 Redis 库存已同步) */
|
||||
String SECKILL_WARMUP = "seckill:warmup:%d";
|
||||
|
||||
// ==================== 公共 ====================
|
||||
|
||||
/** 验证码:captcha:{uuid} */
|
||||
String CAPTCHA = "captcha:%s";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.snack.server.module.chat.controller;
|
||||
package com.snack.server.module.chat.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckLogin;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.snack.server.module.chat.controller;
|
||||
package com.snack.server.module.chat.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckLogin;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.snack.server.module.chat.controller;
|
||||
package com.snack.server.module.chat.controller;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import com.snack.server.common.Result;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.snack.server.module.chat.service;
|
||||
package com.snack.server.module.chat.service;
|
||||
|
||||
import com.snack.server.module.chat.dto.req.ChatAckReq;
|
||||
import com.snack.server.module.chat.dto.req.ChatSendMessageReq;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.snack.server.module.chat.service;
|
||||
package com.snack.server.module.chat.service;
|
||||
|
||||
import com.snack.server.constant.RedisKey;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.snack.server.module.chat.service;
|
||||
package com.snack.server.module.chat.service;
|
||||
|
||||
import com.snack.server.module.chat.dto.req.QuickReplySaveReq;
|
||||
import com.snack.server.module.chat.vo.QuickReplyVO;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.snack.server.module.chat.service.impl;
|
||||
package com.snack.server.module.chat.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ import java.util.stream.Collectors;
|
|||
* 客服会话服务实现
|
||||
*/
|
||||
@Slf4j
|
||||
Service
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ChatSessionServiceImpl implements ChatSessionService {
|
||||
|
||||
|
|
@ -179,10 +179,12 @@ public class ChatSessionServiceImpl implements ChatSessionService {
|
|||
List<Long> adminIds = records.stream().map(ChatSession::getAdminId)
|
||||
.filter(java.util.Objects::nonNull).distinct().toList();
|
||||
Map<Long, User> userMap = userIds.isEmpty() ? Collections.emptyMap()
|
||||
: userMapper.selectBatchIds(userIds).stream()
|
||||
: userMapper.selectList(
|
||||
new LambdaQueryWrapper<User>().in(User::getId, userIds)).stream()
|
||||
.collect(Collectors.toMap(User::getId, Function.identity()));
|
||||
Map<Long, Admin> adminMap = adminIds.isEmpty() ? Collections.emptyMap()
|
||||
: adminMapper.selectBatchIds(adminIds).stream()
|
||||
: adminMapper.selectList(
|
||||
new LambdaQueryWrapper<Admin>().in(Admin::getId, adminIds)).stream()
|
||||
.collect(Collectors.toMap(Admin::getId, Function.identity()));
|
||||
|
||||
// 关键字过滤(用户名 / 会话编号)发生在拿到 VO 之后
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.snack.server.module.chat.service.impl;
|
||||
package com.snack.server.module.chat.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.snack.server.module.chat.websocket;
|
||||
package com.snack.server.module.chat.websocket;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.snack.server.module.chat.websocket;
|
||||
package com.snack.server.module.chat.websocket;
|
||||
|
||||
import com.snack.server.module.chat.dto.req.ChatAckReq;
|
||||
import com.snack.server.module.chat.dto.req.ChatSendMessageReq;
|
||||
|
|
@ -15,7 +15,6 @@ import org.springframework.messaging.simp.SimpMessagingTemplate;
|
|||
import org.springframework.stereotype.Controller;
|
||||
|
||||
import java.security.Principal;
|
||||
|
||||
/**
|
||||
* WebSocket STOMP 控制器
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.snack.server.module.chat.websocket;
|
||||
package com.snack.server.module.chat.websocket;
|
||||
|
||||
import com.snack.server.module.chat.service.ChatOnlineService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package com.snack.server.module.file.service.impl;
|
|||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.snack.server.common.exception.BusinessException;
|
||||
import com.snack.server.exception.BusinessException;
|
||||
import com.snack.server.common.storage.FileStorage;
|
||||
import com.snack.server.common.storage.FileStoreResult;
|
||||
import com.snack.server.module.file.entity.UploadFile;
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ public class SeckillServiceImpl implements SeckillService {
|
|||
.gt(SeckillActivity::getEndTime, LocalDateTime.now().minusHours(1)) // 兜底展示刚结束 1 小时的
|
||||
.orderByAsc(SeckillActivity::getStartTime)
|
||||
);
|
||||
if (CollUtil.isEmpty(activities)) return new ArrayList<>();
|
||||
if (CollUtil.isEmpty(activities)) return new ArrayList<SeckillActivityVO>();
|
||||
return activities.stream().map(a -> enrichActivityVO(a, false)).toList();
|
||||
}
|
||||
|
||||
|
|
@ -133,9 +133,15 @@ public class SeckillServiceImpl implements SeckillService {
|
|||
// 批量取 SPU / SKU
|
||||
Set<Long> spuIds = products.stream().map(SeckillProduct::getProductId).collect(Collectors.toSet());
|
||||
Set<Long> skuIds = products.stream().map(SeckillProduct::getSkuId).collect(Collectors.toSet());
|
||||
Map<Long, Product> productMap = productEntityMapper.selectBatchIds(spuIds).stream()
|
||||
Map<Long, Product> productMap = spuIds.isEmpty() ? java.util.Collections.emptyMap()
|
||||
: productEntityMapper.selectList(
|
||||
new LambdaQueryWrapper<Product>().in(Product::getId, spuIds))
|
||||
.stream()
|
||||
.collect(Collectors.toMap(Product::getId, p -> p));
|
||||
Map<Long, ProductSku> skuMap = productSkuMapper.selectBatchIds(skuIds).stream()
|
||||
Map<Long, ProductSku> skuMap = skuIds.isEmpty() ? java.util.Collections.emptyMap()
|
||||
: productSkuMapper.selectList(
|
||||
new LambdaQueryWrapper<ProductSku>().in(ProductSku::getId, skuIds))
|
||||
.stream()
|
||||
.collect(Collectors.toMap(ProductSku::getId, s -> s));
|
||||
|
||||
vo.setProducts(products.stream().map(sp -> {
|
||||
|
|
@ -167,7 +173,7 @@ public class SeckillServiceImpl implements SeckillService {
|
|||
return pvo;
|
||||
}).toList());
|
||||
} else {
|
||||
vo.setProducts(new ArrayList<>());
|
||||
vo.setProducts(new ArrayList<SeckillProductVO>());
|
||||
}
|
||||
}
|
||||
return vo;
|
||||
|
|
@ -184,7 +190,7 @@ public class SeckillServiceImpl implements SeckillService {
|
|||
}
|
||||
Integer nowStatus = SeckillActivityStatusEnum.calcStatus(activity.getStartTime(), activity.getEndTime());
|
||||
if (nowStatus != SeckillActivityStatusEnum.IN_PROGRESS.getCode()) {
|
||||
throw new BusinessException(nowStatus == SeckillActivityStatusEnum.NOT_STARTED.getCode()
|
||||
throw new BusinessException(Objects.equals(nowStatus, SeckillActivityStatusEnum.NOT_STARTED.getCode())
|
||||
? ResultCode.SECKILL_NOT_STARTED : ResultCode.SECKILL_ENDED);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,96 @@
|
|||
{
|
||||
"globals": {
|
||||
"Component": true,
|
||||
"ComponentPublicInstance": true,
|
||||
"ComputedRef": true,
|
||||
"DirectiveBinding": true,
|
||||
"EffectScope": true,
|
||||
"ExtractDefaultPropTypes": true,
|
||||
"ExtractPropTypes": true,
|
||||
"ExtractPublicPropTypes": true,
|
||||
"InjectionKey": true,
|
||||
"MaybeRef": true,
|
||||
"MaybeRefOrGetter": true,
|
||||
"PropType": true,
|
||||
"Ref": true,
|
||||
"ShallowRef": true,
|
||||
"Slot": true,
|
||||
"Slots": true,
|
||||
"VNode": true,
|
||||
"WritableComputedRef": true,
|
||||
"acceptHMRUpdate": true,
|
||||
"computed": true,
|
||||
"createApp": true,
|
||||
"createPinia": true,
|
||||
"customRef": true,
|
||||
"defineAsyncComponent": true,
|
||||
"defineComponent": true,
|
||||
"defineStore": true,
|
||||
"effectScope": true,
|
||||
"getActivePinia": true,
|
||||
"getCurrentInstance": true,
|
||||
"getCurrentScope": true,
|
||||
"getCurrentWatcher": true,
|
||||
"h": true,
|
||||
"inject": true,
|
||||
"isProxy": true,
|
||||
"isReactive": true,
|
||||
"isReadonly": true,
|
||||
"isRef": true,
|
||||
"isShallow": true,
|
||||
"mapActions": true,
|
||||
"mapGetters": true,
|
||||
"mapState": true,
|
||||
"mapStores": true,
|
||||
"mapWritableState": true,
|
||||
"markRaw": true,
|
||||
"nextTick": true,
|
||||
"onActivated": true,
|
||||
"onBeforeMount": true,
|
||||
"onBeforeRouteLeave": true,
|
||||
"onBeforeRouteUpdate": true,
|
||||
"onBeforeUnmount": true,
|
||||
"onBeforeUpdate": true,
|
||||
"onDeactivated": true,
|
||||
"onErrorCaptured": true,
|
||||
"onMounted": true,
|
||||
"onRenderTracked": true,
|
||||
"onRenderTriggered": true,
|
||||
"onScopeDispose": true,
|
||||
"onServerPrefetch": true,
|
||||
"onUnmounted": true,
|
||||
"onUpdated": true,
|
||||
"onWatcherCleanup": true,
|
||||
"provide": true,
|
||||
"reactive": true,
|
||||
"readonly": true,
|
||||
"ref": true,
|
||||
"resolveComponent": true,
|
||||
"setActivePinia": true,
|
||||
"setMapStoreSuffix": true,
|
||||
"shallowReactive": true,
|
||||
"shallowReadonly": true,
|
||||
"shallowRef": true,
|
||||
"storeToRefs": true,
|
||||
"toRaw": true,
|
||||
"toRef": true,
|
||||
"toRefs": true,
|
||||
"toValue": true,
|
||||
"triggerRef": true,
|
||||
"unref": true,
|
||||
"useAttrs": true,
|
||||
"useCssModule": true,
|
||||
"useCssVars": true,
|
||||
"useId": true,
|
||||
"useLink": true,
|
||||
"useModel": true,
|
||||
"useRoute": true,
|
||||
"useRouter": true,
|
||||
"useSlots": true,
|
||||
"useTemplateRef": true,
|
||||
"watch": true,
|
||||
"watchEffect": true,
|
||||
"watchPostEffect": true,
|
||||
"watchSyncEffect": true
|
||||
}
|
||||
}
|
||||
|
|
@ -11,9 +11,15 @@ importers:
|
|||
'@element-plus/icons-vue':
|
||||
specifier: ^2.3.2
|
||||
version: 2.3.2(vue@3.5.35(typescript@6.0.3))
|
||||
'@stomp/stompjs':
|
||||
specifier: ^7.3.0
|
||||
version: 7.3.0
|
||||
'@types/mockjs':
|
||||
specifier: ^1.0.10
|
||||
version: 1.0.10
|
||||
'@types/nprogress':
|
||||
specifier: ^0.2.3
|
||||
version: 0.2.3
|
||||
axios:
|
||||
specifier: ^1.17.0
|
||||
version: 1.17.0
|
||||
|
|
@ -23,12 +29,18 @@ importers:
|
|||
mockjs:
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0
|
||||
nprogress:
|
||||
specifier: ^0.2.0
|
||||
version: 0.2.0
|
||||
pinia:
|
||||
specifier: ^3.0.4
|
||||
version: 3.0.4(typescript@6.0.3)(vue@3.5.35(typescript@6.0.3))
|
||||
sass:
|
||||
specifier: ^1.100.0
|
||||
version: 1.100.0
|
||||
sockjs-client:
|
||||
specifier: ^1.6.1
|
||||
version: 1.6.1
|
||||
unplugin-auto-import:
|
||||
specifier: ^21.0.0
|
||||
version: 21.0.0(@vueuse/core@14.3.0(vue@3.5.35(typescript@6.0.3)))
|
||||
|
|
@ -453,6 +465,9 @@ packages:
|
|||
'@rolldown/pluginutils@1.0.1':
|
||||
resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==}
|
||||
|
||||
'@stomp/stompjs@7.3.0':
|
||||
resolution: {integrity: sha512-nKMLoFfJhrQAqkvvKd1vLq/cVBGCMwPRCD0LqW7UT1fecRx9C3GoKEIR2CYwVuErGeZu8w0kFkl2rlhPlqHVgQ==}
|
||||
|
||||
'@sxzz/popperjs-es@2.11.8':
|
||||
resolution: {integrity: sha512-wOwESXvvED3S8xBmcPWHs2dUuzrE4XiZeFu7e1hROIJkm02a49N120pmOXxY33sBb6hArItm5W5tcg1cBtV+HQ==}
|
||||
|
||||
|
|
@ -477,6 +492,9 @@ packages:
|
|||
'@types/node@24.12.4':
|
||||
resolution: {integrity: sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==}
|
||||
|
||||
'@types/nprogress@0.2.3':
|
||||
resolution: {integrity: sha512-k7kRA033QNtC+gLc4VPlfnue58CM1iQLgn1IMAU8VPHGOj7oIHPp9UlhedEnD/Gl8evoCjwkZjlBORtZ3JByUA==}
|
||||
|
||||
'@types/web-bluetooth@0.0.21':
|
||||
resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==}
|
||||
|
||||
|
|
@ -682,6 +700,14 @@ packages:
|
|||
dayjs@1.11.21:
|
||||
resolution: {integrity: sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==}
|
||||
|
||||
debug@3.2.7:
|
||||
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
|
||||
debug@4.4.3:
|
||||
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
|
||||
engines: {node: '>=6.0'}
|
||||
|
|
@ -760,9 +786,17 @@ packages:
|
|||
estree-walker@3.0.3:
|
||||
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
|
||||
|
||||
eventsource@2.0.2:
|
||||
resolution: {integrity: sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
exsolve@1.0.8:
|
||||
resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==}
|
||||
|
||||
faye-websocket@0.11.4:
|
||||
resolution: {integrity: sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==}
|
||||
engines: {node: '>=0.8.0'}
|
||||
|
||||
fdir@6.5.0:
|
||||
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
|
@ -824,6 +858,9 @@ packages:
|
|||
hookable@5.5.3:
|
||||
resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==}
|
||||
|
||||
http-parser-js@0.5.10:
|
||||
resolution: {integrity: sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==}
|
||||
|
||||
https-proxy-agent@5.0.1:
|
||||
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
|
@ -831,6 +868,9 @@ packages:
|
|||
immutable@5.1.6:
|
||||
resolution: {integrity: sha512-q1swsS8K7L8usSHuOqF2TAoCCkonYz0SG38wLAggaa4Wml70zixIvt2ql4coQ2C2B3hTjltJry4r6bULwgAXLQ==}
|
||||
|
||||
inherits@2.0.4:
|
||||
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
||||
|
||||
is-docker@3.0.0:
|
||||
resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
|
@ -1051,6 +1091,9 @@ packages:
|
|||
engines: {node: ^20.5.0 || >=22.0.0, npm: '>= 10'}
|
||||
hasBin: true
|
||||
|
||||
nprogress@0.2.0:
|
||||
resolution: {integrity: sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==}
|
||||
|
||||
obug@2.1.1:
|
||||
resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==}
|
||||
|
||||
|
|
@ -1119,6 +1162,9 @@ packages:
|
|||
quansync@0.2.11:
|
||||
resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==}
|
||||
|
||||
querystringify@2.2.0:
|
||||
resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==}
|
||||
|
||||
read-package-json-fast@4.0.0:
|
||||
resolution: {integrity: sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg==}
|
||||
engines: {node: ^18.17.0 || >=20.5.0}
|
||||
|
|
@ -1127,6 +1173,9 @@ packages:
|
|||
resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==}
|
||||
engines: {node: '>= 20.19.0'}
|
||||
|
||||
requires-port@1.0.0:
|
||||
resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
|
||||
|
||||
rfdc@1.4.1:
|
||||
resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
|
||||
|
||||
|
|
@ -1139,6 +1188,9 @@ packages:
|
|||
resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
safe-buffer@5.2.1:
|
||||
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
||||
|
||||
sass@1.100.0:
|
||||
resolution: {integrity: sha512-B5j0rYMlinhhOo9tjQebMVVn0TfyXAF+wB3b2ggZUuJ/is/Y+7+JGjirAMxHZ9Z3hIP98NPfamlAkBHa1lAaXQ==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
|
|
@ -1167,6 +1219,10 @@ packages:
|
|||
resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
sockjs-client@1.6.1:
|
||||
resolution: {integrity: sha512-2g0tjOR+fRs0amxENLi/q5TiJTqY+WXFOzb5UwXndlK6TO3U/mirZznpx6w34HVMoc3g7cY24yC/ZMIYnDlfkw==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
source-map-js@1.2.1:
|
||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
|
@ -1248,6 +1304,9 @@ packages:
|
|||
peerDependencies:
|
||||
browserslist: '>= 4.21.0'
|
||||
|
||||
url-parse@1.5.10:
|
||||
resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==}
|
||||
|
||||
vite-dev-rpc@2.0.0:
|
||||
resolution: {integrity: sha512-yKwbTwdHKSD2k/aGqyWpPHepo45OQc8lH3/6IfT4ZqeKE26ooKvi4WIEKzqWav8v+9Is8u1k8q54hvOmqASazA==}
|
||||
peerDependencies:
|
||||
|
|
@ -1350,6 +1409,14 @@ packages:
|
|||
webpack-virtual-modules@0.6.2:
|
||||
resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==}
|
||||
|
||||
websocket-driver@0.7.4:
|
||||
resolution: {integrity: sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==}
|
||||
engines: {node: '>=0.8.0'}
|
||||
|
||||
websocket-extensions@0.1.4:
|
||||
resolution: {integrity: sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==}
|
||||
engines: {node: '>=0.8.0'}
|
||||
|
||||
which@2.0.2:
|
||||
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
|
||||
engines: {node: '>= 8'}
|
||||
|
|
@ -1735,6 +1802,8 @@ snapshots:
|
|||
|
||||
'@rolldown/pluginutils@1.0.1': {}
|
||||
|
||||
'@stomp/stompjs@7.3.0': {}
|
||||
|
||||
'@sxzz/popperjs-es@2.11.8': {}
|
||||
|
||||
'@tsconfig/node24@24.0.4': {}
|
||||
|
|
@ -1758,6 +1827,8 @@ snapshots:
|
|||
dependencies:
|
||||
undici-types: 7.16.0
|
||||
|
||||
'@types/nprogress@0.2.3': {}
|
||||
|
||||
'@types/web-bluetooth@0.0.21': {}
|
||||
|
||||
'@vitejs/plugin-vue@6.0.7(vite@8.0.16(@types/node@24.12.4)(sass@1.100.0))(vue@3.5.35(typescript@6.0.3))':
|
||||
|
|
@ -2007,6 +2078,10 @@ snapshots:
|
|||
|
||||
dayjs@1.11.21: {}
|
||||
|
||||
debug@3.2.7:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
debug@4.4.3:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
|
@ -2080,8 +2155,14 @@ snapshots:
|
|||
dependencies:
|
||||
'@types/estree': 1.0.9
|
||||
|
||||
eventsource@2.0.2: {}
|
||||
|
||||
exsolve@1.0.8: {}
|
||||
|
||||
faye-websocket@0.11.4:
|
||||
dependencies:
|
||||
websocket-driver: 0.7.4
|
||||
|
||||
fdir@6.5.0(picomatch@4.0.4):
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.4
|
||||
|
|
@ -2135,6 +2216,8 @@ snapshots:
|
|||
|
||||
hookable@5.5.3: {}
|
||||
|
||||
http-parser-js@0.5.10: {}
|
||||
|
||||
https-proxy-agent@5.0.1:
|
||||
dependencies:
|
||||
agent-base: 6.0.2
|
||||
|
|
@ -2144,6 +2227,8 @@ snapshots:
|
|||
|
||||
immutable@5.1.6: {}
|
||||
|
||||
inherits@2.0.4: {}
|
||||
|
||||
is-docker@3.0.0: {}
|
||||
|
||||
is-extglob@2.1.1:
|
||||
|
|
@ -2308,6 +2393,8 @@ snapshots:
|
|||
shell-quote: 1.8.4
|
||||
which: 5.0.0
|
||||
|
||||
nprogress@0.2.0: {}
|
||||
|
||||
obug@2.1.1: {}
|
||||
|
||||
ohash@2.0.11: {}
|
||||
|
|
@ -2368,6 +2455,8 @@ snapshots:
|
|||
|
||||
quansync@0.2.11: {}
|
||||
|
||||
querystringify@2.2.0: {}
|
||||
|
||||
read-package-json-fast@4.0.0:
|
||||
dependencies:
|
||||
json-parse-even-better-errors: 4.0.0
|
||||
|
|
@ -2375,6 +2464,8 @@ snapshots:
|
|||
|
||||
readdirp@5.0.0: {}
|
||||
|
||||
requires-port@1.0.0: {}
|
||||
|
||||
rfdc@1.4.1: {}
|
||||
|
||||
rolldown@1.0.3:
|
||||
|
|
@ -2400,6 +2491,8 @@ snapshots:
|
|||
|
||||
run-applescript@7.1.0: {}
|
||||
|
||||
safe-buffer@5.2.1: {}
|
||||
|
||||
sass@1.100.0:
|
||||
dependencies:
|
||||
chokidar: 5.0.0
|
||||
|
|
@ -2426,6 +2519,16 @@ snapshots:
|
|||
mrmime: 2.0.1
|
||||
totalist: 3.0.1
|
||||
|
||||
sockjs-client@1.6.1:
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
eventsource: 2.0.2
|
||||
faye-websocket: 0.11.4
|
||||
inherits: 2.0.4
|
||||
url-parse: 1.5.10
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
source-map-js@1.2.1: {}
|
||||
|
||||
speakingurl@14.0.1: {}
|
||||
|
|
@ -2519,6 +2622,11 @@ snapshots:
|
|||
escalade: 3.2.0
|
||||
picocolors: 1.1.1
|
||||
|
||||
url-parse@1.5.10:
|
||||
dependencies:
|
||||
querystringify: 2.2.0
|
||||
requires-port: 1.0.0
|
||||
|
||||
vite-dev-rpc@2.0.0(vite@8.0.16(@types/node@24.12.4)(sass@1.100.0)):
|
||||
dependencies:
|
||||
birpc: 4.0.0
|
||||
|
|
@ -2610,6 +2718,14 @@ snapshots:
|
|||
|
||||
webpack-virtual-modules@0.6.2: {}
|
||||
|
||||
websocket-driver@0.7.4:
|
||||
dependencies:
|
||||
http-parser-js: 0.5.10
|
||||
safe-buffer: 5.2.1
|
||||
websocket-extensions: 0.1.4
|
||||
|
||||
websocket-extensions@0.1.4: {}
|
||||
|
||||
which@2.0.2:
|
||||
dependencies:
|
||||
isexe: 2.0.0
|
||||
|
|
|
|||
|
|
@ -58,8 +58,12 @@ function logout() {
|
|||
<a @click="goUser" class="link">个人中心</a>
|
||||
<a @click="logout" class="link">退出</a>
|
||||
</template>
|
||||
<a @click="router.push('/notice')" class="link"><el-icon><Bell /></el-icon> 公告</a>
|
||||
<span class="hotline"><el-icon><Phone /></el-icon> 400-888-8888</span>
|
||||
<a @click="router.push('/notice')" class="link notice-link">
|
||||
<span class="notice-icon">
|
||||
<el-icon :size="14"><Bell /></el-icon>
|
||||
</span>
|
||||
公告
|
||||
</a> <span class="hotline"><el-icon><Phone /></el-icon> 400-888-8888</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -87,9 +91,15 @@ function logout() {
|
|||
|
||||
<div class="header-actions">
|
||||
<el-badge :value="cartStore.totalCount" :hidden="cartStore.totalCount === 0" class="action-item">
|
||||
<el-button :icon="ShoppingCart" @click="router.push('/cart')">购物车</el-button>
|
||||
<div class="action-card" @click="router.push('/cart')">
|
||||
<el-icon :size="22" class="action-card-icon"><ShoppingCart /></el-icon>
|
||||
<span class="action-card-text">购物车</span>
|
||||
</div>
|
||||
</el-badge>
|
||||
<el-button :icon="User" @click="goUser" class="action-item">我的</el-button>
|
||||
<div class="action-card" @click="goUser">
|
||||
<el-icon :size="22" class="action-card-icon"><User /></el-icon>
|
||||
<span class="action-card-text">我的</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
|
@ -200,6 +210,26 @@ function logout() {
|
|||
.top-right .link:hover {
|
||||
color: #fff;
|
||||
}
|
||||
.notice-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 0 4px;
|
||||
}
|
||||
.notice-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
color: #fff;
|
||||
transition: background 150ms;
|
||||
}
|
||||
.notice-link:hover .notice-icon {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
.hotline {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -238,9 +268,36 @@ function logout() {
|
|||
.header-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
.action-item {
|
||||
.action-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 2px;
|
||||
min-width: 56px;
|
||||
height: 56px;
|
||||
padding: 0 12px;
|
||||
border: 1px solid #f3f4f6;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
color: #4b5563;
|
||||
cursor: pointer;
|
||||
transition: border-color 150ms, color 150ms, transform 150ms;
|
||||
user-select: none;
|
||||
}
|
||||
.action-card:hover {
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-primary);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.action-card-icon {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
.action-card-text {
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* Nav */
|
||||
|
|
|
|||
|
|
@ -0,0 +1,90 @@
|
|||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
// Generated by unplugin-auto-import
|
||||
// biome-ignore lint: disable
|
||||
export {}
|
||||
declare global {
|
||||
const EffectScope: typeof import('vue').EffectScope
|
||||
const acceptHMRUpdate: typeof import('pinia').acceptHMRUpdate
|
||||
const computed: typeof import('vue').computed
|
||||
const createApp: typeof import('vue').createApp
|
||||
const createPinia: typeof import('pinia').createPinia
|
||||
const customRef: typeof import('vue').customRef
|
||||
const defineAsyncComponent: typeof import('vue').defineAsyncComponent
|
||||
const defineComponent: typeof import('vue').defineComponent
|
||||
const defineStore: typeof import('pinia').defineStore
|
||||
const effectScope: typeof import('vue').effectScope
|
||||
const getActivePinia: typeof import('pinia').getActivePinia
|
||||
const getCurrentInstance: typeof import('vue').getCurrentInstance
|
||||
const getCurrentScope: typeof import('vue').getCurrentScope
|
||||
const getCurrentWatcher: typeof import('vue').getCurrentWatcher
|
||||
const h: typeof import('vue').h
|
||||
const inject: typeof import('vue').inject
|
||||
const isProxy: typeof import('vue').isProxy
|
||||
const isReactive: typeof import('vue').isReactive
|
||||
const isReadonly: typeof import('vue').isReadonly
|
||||
const isRef: typeof import('vue').isRef
|
||||
const isShallow: typeof import('vue').isShallow
|
||||
const mapActions: typeof import('pinia').mapActions
|
||||
const mapGetters: typeof import('pinia').mapGetters
|
||||
const mapState: typeof import('pinia').mapState
|
||||
const mapStores: typeof import('pinia').mapStores
|
||||
const mapWritableState: typeof import('pinia').mapWritableState
|
||||
const markRaw: typeof import('vue').markRaw
|
||||
const nextTick: typeof import('vue').nextTick
|
||||
const onActivated: typeof import('vue').onActivated
|
||||
const onBeforeMount: typeof import('vue').onBeforeMount
|
||||
const onBeforeRouteLeave: typeof import('vue-router').onBeforeRouteLeave
|
||||
const onBeforeRouteUpdate: typeof import('vue-router').onBeforeRouteUpdate
|
||||
const onBeforeUnmount: typeof import('vue').onBeforeUnmount
|
||||
const onBeforeUpdate: typeof import('vue').onBeforeUpdate
|
||||
const onDeactivated: typeof import('vue').onDeactivated
|
||||
const onErrorCaptured: typeof import('vue').onErrorCaptured
|
||||
const onMounted: typeof import('vue').onMounted
|
||||
const onRenderTracked: typeof import('vue').onRenderTracked
|
||||
const onRenderTriggered: typeof import('vue').onRenderTriggered
|
||||
const onScopeDispose: typeof import('vue').onScopeDispose
|
||||
const onServerPrefetch: typeof import('vue').onServerPrefetch
|
||||
const onUnmounted: typeof import('vue').onUnmounted
|
||||
const onUpdated: typeof import('vue').onUpdated
|
||||
const onWatcherCleanup: typeof import('vue').onWatcherCleanup
|
||||
const provide: typeof import('vue').provide
|
||||
const reactive: typeof import('vue').reactive
|
||||
const readonly: typeof import('vue').readonly
|
||||
const ref: typeof import('vue').ref
|
||||
const resolveComponent: typeof import('vue').resolveComponent
|
||||
const setActivePinia: typeof import('pinia').setActivePinia
|
||||
const setMapStoreSuffix: typeof import('pinia').setMapStoreSuffix
|
||||
const shallowReactive: typeof import('vue').shallowReactive
|
||||
const shallowReadonly: typeof import('vue').shallowReadonly
|
||||
const shallowRef: typeof import('vue').shallowRef
|
||||
const storeToRefs: typeof import('pinia').storeToRefs
|
||||
const toRaw: typeof import('vue').toRaw
|
||||
const toRef: typeof import('vue').toRef
|
||||
const toRefs: typeof import('vue').toRefs
|
||||
const toValue: typeof import('vue').toValue
|
||||
const triggerRef: typeof import('vue').triggerRef
|
||||
const unref: typeof import('vue').unref
|
||||
const useAttrs: typeof import('vue').useAttrs
|
||||
const useCssModule: typeof import('vue').useCssModule
|
||||
const useCssVars: typeof import('vue').useCssVars
|
||||
const useId: typeof import('vue').useId
|
||||
const useLink: typeof import('vue-router').useLink
|
||||
const useModel: typeof import('vue').useModel
|
||||
const useRoute: typeof import('vue-router').useRoute
|
||||
const useRouter: typeof import('vue-router').useRouter
|
||||
const useSlots: typeof import('vue').useSlots
|
||||
const useTemplateRef: typeof import('vue').useTemplateRef
|
||||
const watch: typeof import('vue').watch
|
||||
const watchEffect: typeof import('vue').watchEffect
|
||||
const watchPostEffect: typeof import('vue').watchPostEffect
|
||||
const watchSyncEffect: typeof import('vue').watchSyncEffect
|
||||
}
|
||||
// for type re-export
|
||||
declare global {
|
||||
// @ts-ignore
|
||||
export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, ShallowRef, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
|
||||
import('vue')
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
// biome-ignore lint: disable
|
||||
// oxlint-disable
|
||||
// ------
|
||||
// Generated by unplugin-vue-components
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
|
||||
export {}
|
||||
|
||||
/* prettier-ignore */
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
ElBadge: typeof import('element-plus/es')['ElBadge']
|
||||
ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
|
||||
ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
|
||||
ElButton: typeof import('element-plus/es')['ElButton']
|
||||
ElCarousel: typeof import('element-plus/es')['ElCarousel']
|
||||
ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem']
|
||||
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
||||
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
|
||||
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
|
||||
ElEmpty: typeof import('element-plus/es')['ElEmpty']
|
||||
ElForm: typeof import('element-plus/es')['ElForm']
|
||||
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
||||
ElIcon: typeof import('element-plus/es')['ElIcon']
|
||||
ElInput: typeof import('element-plus/es')['ElInput']
|
||||
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
|
||||
ElProgress: typeof import('element-plus/es')['ElProgress']
|
||||
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
|
||||
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
||||
ElSkeleton: typeof import('element-plus/es')['ElSkeleton']
|
||||
ElTabPane: typeof import('element-plus/es')['ElTabPane']
|
||||
ElTabs: typeof import('element-plus/es')['ElTabs']
|
||||
ElTag: typeof import('element-plus/es')['ElTag']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
}
|
||||
export interface GlobalDirectives {
|
||||
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
|
||||
}
|
||||
}
|
||||
|
|
@ -4,13 +4,14 @@ import { useRouter } from 'vue-router'
|
|||
import { ElMessage } from 'element-plus'
|
||||
import { loginApi, buildUserInfo } from '@/api/auth'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { User, Lock } from '@element-plus/icons-vue'
|
||||
import { User, Lock, ShoppingBag, Promotion, ChatDotRound, View, Hide } from '@element-plus/icons-vue'
|
||||
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const form = ref({ username: '', password: '' })
|
||||
const loading = ref(false)
|
||||
const showPwd = ref(false)
|
||||
|
||||
async function onSubmit() {
|
||||
if (!form.value.username || !form.value.password) {
|
||||
|
|
@ -33,84 +34,467 @@ async function onSubmit() {
|
|||
|
||||
<template>
|
||||
<div class="auth-page">
|
||||
<div class="auth-box">
|
||||
<div class="auth-header">
|
||||
<el-icon :size="48" color="#DC2626"><Shop /></el-icon>
|
||||
<h2>零食商城</h2>
|
||||
<p>登录您的账号</p>
|
||||
</div>
|
||||
<!-- 左侧品牌展示区 -->
|
||||
<section class="auth-hero" aria-hidden="true">
|
||||
<div class="hero-deco hero-deco-1" />
|
||||
<div class="hero-deco hero-deco-2" />
|
||||
<div class="hero-deco hero-deco-3" />
|
||||
|
||||
<el-form :model="form" @keyup.enter="onSubmit">
|
||||
<el-form-item>
|
||||
<el-input
|
||||
v-model="form.username"
|
||||
placeholder="用户名"
|
||||
<div class="hero-inner">
|
||||
<div class="hero-brand">
|
||||
<el-icon :size="32" color="#FCA5A5"><ShoppingBag /></el-icon>
|
||||
<span class="hero-brand-name">零食商城</span>
|
||||
</div>
|
||||
|
||||
<h1 class="hero-title">
|
||||
吃货的<br />
|
||||
<span class="hero-title-accent">快乐星球</span>
|
||||
</h1>
|
||||
<p class="hero-subtitle">严选全球好零食 · 满 99 包邮 · 7 天无忧退换</p>
|
||||
|
||||
<ul class="hero-features">
|
||||
<li>
|
||||
<span class="hero-feature-icon"><el-icon :size="22"><Promotion /></el-icon></span>
|
||||
<div>
|
||||
<strong>每日限时秒杀</strong>
|
||||
<span>低至 1 折起 · 抢到就是赚到</span>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<span class="hero-feature-icon"><el-icon :size="22"><ChatDotRound /></el-icon></span>
|
||||
<div>
|
||||
<strong>7×24 客服在线</strong>
|
||||
<span>订单 / 售后 / 咨询秒回应</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 右侧登录卡片 -->
|
||||
<section class="auth-panel">
|
||||
<div class="auth-card">
|
||||
<header class="auth-card-header">
|
||||
<h2>欢迎回来 👋</h2>
|
||||
<p>登录账号,开启零食之旅</p>
|
||||
</header>
|
||||
|
||||
<el-form :model="form" class="auth-form" @keyup.enter="onSubmit">
|
||||
<el-form-item>
|
||||
<label class="auth-label" for="login-username">用户名</label>
|
||||
<div class="auth-field">
|
||||
<span class="auth-field-icon"><el-icon :size="18"><User /></el-icon></span>
|
||||
<input
|
||||
id="login-username"
|
||||
v-model="form.username"
|
||||
class="auth-field-input"
|
||||
type="text"
|
||||
placeholder="请输入用户名"
|
||||
autocomplete="username"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<label class="auth-label" for="login-password">密码</label>
|
||||
<div class="auth-field">
|
||||
<span class="auth-field-icon"><el-icon :size="18"><Lock /></el-icon></span>
|
||||
<input
|
||||
id="login-password"
|
||||
v-model="form.password"
|
||||
class="auth-field-input"
|
||||
:type="showPwd ? 'text' : 'password'"
|
||||
placeholder="请输入密码"
|
||||
autocomplete="current-password"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="auth-field-eye"
|
||||
:aria-label="showPwd ? '隐藏密码' : '显示密码'"
|
||||
@click="showPwd = !showPwd"
|
||||
>
|
||||
<el-icon :size="18">
|
||||
<component :is="showPwd ? View : Hide" />
|
||||
</el-icon>
|
||||
</button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<div class="auth-row">
|
||||
<el-checkbox>记住我</el-checkbox>
|
||||
<a class="auth-link" @click="ElMessage.info('请联系客服重置密码')">忘记密码?</a>
|
||||
</div>
|
||||
|
||||
<el-button
|
||||
type="primary"
|
||||
size="large"
|
||||
:prefix-icon="User"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input
|
||||
v-model="form.password"
|
||||
type="password"
|
||||
placeholder="密码"
|
||||
size="large"
|
||||
show-password
|
||||
:prefix-icon="Lock"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" size="large" style="width: 100%" :loading="loading" @click="onSubmit">
|
||||
登录
|
||||
class="auth-submit"
|
||||
:loading="loading"
|
||||
@click="onSubmit"
|
||||
>
|
||||
登 录
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-form>
|
||||
|
||||
<div class="auth-footer">
|
||||
<span>还没有账号?</span>
|
||||
<a @click="router.push('/register')">立即注册</a>
|
||||
<div class="auth-divider"><span>其他方式登录</span></div>
|
||||
<div class="auth-social" aria-label="其他登录方式(占位)">
|
||||
<button class="auth-social-btn" type="button" aria-label="微信登录">
|
||||
<svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor" aria-hidden="true">
|
||||
<path d="M8.5 4C4.36 4 1 6.69 1 10c0 1.86 1.06 3.53 2.74 4.65L3 17l2.6-1.3c.7.2 1.45.3 2.2.3h.55c-.2-.6-.3-1.2-.3-1.83 0-3.25 3.13-5.9 7-5.9.27 0 .54.02.8.05C15.4 5.66 12.27 4 8.5 4M6 7.5a1 1 0 1 1 0 2 1 1 0 0 1 0-2m5 0a1 1 0 1 1 0 2 1 1 0 0 1 0-2m4 2.27c-3.22 0-5.83 2.18-5.83 4.88 0 2.7 2.61 4.88 5.83 4.88.66 0 1.3-.09 1.9-.26L19 20l-.6-1.9c1.4-.95 2.27-2.4 2.27-4 0-2.7-2.6-4.88-5.83-4.88m-2 3.23a.75.75 0 1 1 0 1.5.75.75 0 0 1 0-1.5m4 0a.75.75 0 1 1 0 1.5.75.75 0 0 1 0-1.5"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="auth-social-btn" type="button" aria-label="QQ 登录">
|
||||
<svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor" aria-hidden="true">
|
||||
<path d="M12 2C7.6 2 4 5.4 4 9.7c0 1.7.5 3.3 1.4 4.6-.3.4-.7.8-1.1 1.1-.6.4-.6 1 0 1.4.7.4 1.7.5 2.6.2.4.5.8.9 1.3 1.2-.3.5-.7 1.1-1 1.4-.3.4 0 .9.5.9 1.1 0 2.4-.7 3.1-1.4.4.1.8.1 1.2.1s.8 0 1.2-.1c.7.7 2 1.4 3.1 1.4.5 0 .8-.5.5-.9-.3-.3-.7-.9-1-1.4.5-.3.9-.7 1.3-1.2.9.3 1.9.2 2.6-.2.6-.4.6-1 0-1.4-.4-.3-.8-.7-1.1-1.1.9-1.3 1.4-2.9 1.4-4.6C20 5.4 16.4 2 12 2"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="auth-social-btn" type="button" aria-label="手机号登录">
|
||||
<svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor" aria-hidden="true">
|
||||
<path d="M17 1H7c-1.1 0-2 .9-2 2v18c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2V3c0-1.1-.9-2-2-2m-5 21c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1m5-4H7V4h10z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<footer class="auth-card-footer">
|
||||
<span>还没有账号?</span>
|
||||
<a class="auth-link" @click="router.push('/register')">立即注册</a>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.auth-page {
|
||||
min-height: 100vh;
|
||||
display: grid;
|
||||
grid-template-columns: 1.05fr 1fr;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
/* ==================== 左侧品牌区 ==================== */
|
||||
.auth-hero {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, #FEF2F2 0%, #fff 100%);
|
||||
background:
|
||||
radial-gradient(circle at 20% 20%, #FCA5A5 0%, transparent 45%),
|
||||
radial-gradient(circle at 80% 80%, #F59E0B 0%, transparent 35%),
|
||||
linear-gradient(135deg, #DC2626 0%, #B91C1C 100%);
|
||||
color: #fff;
|
||||
overflow: hidden;
|
||||
padding: 64px;
|
||||
}
|
||||
.auth-box {
|
||||
width: 400px;
|
||||
background: #fff;
|
||||
border-radius: 16px;
|
||||
padding: 40px;
|
||||
box-shadow: var(--shadow-lg);
|
||||
|
||||
.hero-deco {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
pointer-events: none;
|
||||
}
|
||||
.auth-header {
|
||||
text-align: center;
|
||||
.hero-deco-1 { top: 12%; left: 8%; width: 120px; height: 120px; }
|
||||
.hero-deco-2 { bottom: 16%; right: 10%; width: 180px; height: 180px; background: rgba(252, 165, 165, 0.18); }
|
||||
.hero-deco-3 { top: 50%; left: 55%; width: 64px; height: 64px; background: rgba(255, 255, 255, 0.12); }
|
||||
|
||||
.hero-inner {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
max-width: 480px;
|
||||
}
|
||||
|
||||
.hero-brand {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 6px 14px;
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border-radius: 999px;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
margin-bottom: 32px;
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
.auth-header h2 {
|
||||
margin-top: 12px;
|
||||
margin-bottom: 6px;
|
||||
font-size: 24px;
|
||||
|
||||
.hero-title {
|
||||
font-size: 52px;
|
||||
line-height: 1.15;
|
||||
font-weight: 700;
|
||||
margin: 0 0 16px;
|
||||
letter-spacing: -0.5px;
|
||||
}
|
||||
.auth-header p {
|
||||
color: #999;
|
||||
.hero-title-accent {
|
||||
display: inline-block;
|
||||
background: linear-gradient(90deg, #FDE68A 0%, #FBBF24 100%);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.hero-subtitle {
|
||||
font-size: 17px;
|
||||
opacity: 0.92;
|
||||
margin: 0 0 48px;
|
||||
}
|
||||
|
||||
.hero-features {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 18px;
|
||||
}
|
||||
.hero-features li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 16px 20px;
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
border: 1px solid rgba(255, 255, 255, 0.18);
|
||||
border-radius: 14px;
|
||||
backdrop-filter: blur(6px);
|
||||
transition: background 200ms, transform 200ms;
|
||||
}
|
||||
.hero-features li:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
transform: translateX(4px);
|
||||
}
|
||||
.hero-feature-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
border-radius: 10px;
|
||||
background: rgba(252, 211, 77, 0.95);
|
||||
color: #7C2D12;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.hero-features li div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
.hero-features li strong {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.hero-features li span {
|
||||
font-size: 13px;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
/* ==================== 右侧登录卡片 ==================== */
|
||||
.auth-panel {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 32px;
|
||||
background: #FEF2F2;
|
||||
}
|
||||
|
||||
.auth-card {
|
||||
width: 100%;
|
||||
max-width: 440px;
|
||||
background: #fff;
|
||||
border-radius: 18px;
|
||||
padding: 48px 44px;
|
||||
box-shadow:
|
||||
0 1px 2px rgba(15, 23, 42, 0.04),
|
||||
0 18px 40px -12px rgba(220, 38, 38, 0.15);
|
||||
}
|
||||
|
||||
.auth-card-header h2 {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: #1F2937;
|
||||
margin: 0 0 8px;
|
||||
letter-spacing: -0.3px;
|
||||
}
|
||||
.auth-card-header p {
|
||||
font-size: 14px;
|
||||
color: #6B7280;
|
||||
margin: 0 0 32px;
|
||||
}
|
||||
.auth-footer {
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
|
||||
.auth-form :deep(.el-form-item) { margin-bottom: 20px; }
|
||||
|
||||
/* 自绘 input 字段 —— 完全摆脱 el-input 内部双层框问题 */
|
||||
.auth-field {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 44px;
|
||||
padding: 0 12px;
|
||||
background: #fff;
|
||||
border: 1px solid #E5E7EB;
|
||||
border-radius: 10px;
|
||||
transition: border-color 180ms, box-shadow 180ms;
|
||||
}
|
||||
.auth-footer a {
|
||||
color: var(--color-primary);
|
||||
cursor: pointer;
|
||||
.auth-field:hover { border-color: #FCA5A5; }
|
||||
.auth-field:focus-within {
|
||||
border-color: #DC2626;
|
||||
box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.12);
|
||||
}
|
||||
.auth-field-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
margin-right: 8px;
|
||||
color: #9CA3AF;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.auth-field-input {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
background: transparent;
|
||||
font-size: 15px;
|
||||
color: #1F2937;
|
||||
}
|
||||
.auth-field-input::placeholder { color: #9CA3AF; }
|
||||
.auth-field-input:focus { outline: 0; }
|
||||
.auth-field-eye {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
margin-left: 4px;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: #9CA3AF;
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
transition: color 150ms, background 150ms;
|
||||
}
|
||||
.auth-field-eye:hover { color: #DC2626; background: #FEF2F2; }
|
||||
.auth-field-eye:focus-visible { outline: 2px solid #DC2626; outline-offset: 1px; }
|
||||
.auth-form :deep(.el-input__prefix-inner) {
|
||||
color: #9CA3AF;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
.auth-form :deep(.el-input__prefix .el-icon),
|
||||
.auth-form :deep(.el-input__prefix svg) {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.auth-label {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #374151;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.auth-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin: 4px 0 24px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.auth-link {
|
||||
color: #DC2626;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
transition: color 150ms;
|
||||
}
|
||||
.auth-link:hover { color: #B91C1C; }
|
||||
|
||||
.auth-submit {
|
||||
width: 100%;
|
||||
height: 46px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 4px;
|
||||
border-radius: 10px;
|
||||
background: linear-gradient(135deg, #DC2626 0%, #B91C1C 100%);
|
||||
border: none;
|
||||
box-shadow: 0 8px 18px -6px rgba(220, 38, 38, 0.5);
|
||||
transition: transform 150ms, box-shadow 200ms;
|
||||
}
|
||||
.auth-submit:hover:not(.is-loading) {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 12px 22px -6px rgba(220, 38, 38, 0.55);
|
||||
}
|
||||
.auth-submit:active:not(.is-loading) { transform: translateY(0); }
|
||||
|
||||
.auth-divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin: 28px 0 20px;
|
||||
color: #9CA3AF;
|
||||
font-size: 12px;
|
||||
}
|
||||
.auth-divider::before,
|
||||
.auth-divider::after {
|
||||
content: '';
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: #E5E7EB;
|
||||
}
|
||||
|
||||
.auth-social {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
}
|
||||
.auth-social-btn {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid #E5E7EB;
|
||||
background: #fff;
|
||||
color: #6B7280;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: border-color 150ms, color 150ms, transform 150ms, background 150ms;
|
||||
}
|
||||
.auth-social-btn:hover {
|
||||
border-color: #DC2626;
|
||||
color: #DC2626;
|
||||
background: #FEF2F2;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
.auth-social-btn:focus-visible {
|
||||
outline: 2px solid #DC2626;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.auth-card-footer {
|
||||
margin-top: 28px;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
color: #6B7280;
|
||||
}
|
||||
.auth-card-footer .auth-link { margin-left: 6px; }
|
||||
|
||||
/* ==================== 响应式 ==================== */
|
||||
@media (max-width: 960px) {
|
||||
.auth-page { grid-template-columns: 1fr; }
|
||||
.auth-hero { display: none; }
|
||||
.auth-panel { padding: 24px 16px; background: linear-gradient(180deg, #DC2626 0%, #B91C1C 320px); }
|
||||
.auth-card { padding: 36px 24px; }
|
||||
.auth-card-header h2 { font-size: 24px; }
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.auth-submit,
|
||||
.hero-features li,
|
||||
.auth-social-btn,
|
||||
.auth-field { transition: none; }
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Reference in New Issue