diff --git a/.claude/launch.json b/.claude/launch.json new file mode 100644 index 0000000..79e738f --- /dev/null +++ b/.claude/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.0.1", + "configurations": [ + { + "name": "web-snack", + "runtimeExecutable": "npm", + "runtimeArgs": ["run", "dev"], + "port": 5174 + } + ] +} diff --git a/.trae/rules/git-commit-message.md b/.trae/rules/git-commit-message.md new file mode 100644 index 0000000..89b5448 --- /dev/null +++ b/.trae/rules/git-commit-message.md @@ -0,0 +1,8 @@ +--- +alwaysApply: true +scene: git_message +language: zh-CN +--- + +在此处编写规则,自定义 AI 生成提交信息的风格。 +中文提交信息 \ No newline at end of file diff --git a/admin-snack/pnpm-lock.yaml b/admin-snack/pnpm-lock.yaml index 35a5604..b49f889 100644 --- a/admin-snack/pnpm-lock.yaml +++ b/admin-snack/pnpm-lock.yaml @@ -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 diff --git a/admin-snack/src/types/components.d.ts b/admin-snack/src/types/components.d.ts index 7b9c830..e0e6d5a 100644 --- a/admin-snack/src/types/components.d.ts +++ b/admin-snack/src/types/components.d.ts @@ -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'] diff --git a/server-snack/pom.xml b/server-snack/pom.xml index 0453862..d87d6f8 100644 --- a/server-snack/pom.xml +++ b/server-snack/pom.xml @@ -35,9 +35,8 @@ - org.springframework - spring-websocket - 6.2.15 + org.springframework.boot + spring-boot-starter-websocket diff --git a/server-snack/src/main/java/com/snack/server/common/Result.java b/server-snack/src/main/java/com/snack/server/common/Result.java index 82b81ca..1bdb788 100644 --- a/server-snack/src/main/java/com/snack/server/common/Result.java +++ b/server-snack/src/main/java/com/snack/server/common/Result.java @@ -10,6 +10,8 @@ import java.io.Serializable; @Data public class Result implements Serializable { + private static final long serialVersionUID = 1L; + private Integer code; private String message; private T data; diff --git a/server-snack/src/main/java/com/snack/server/common/storage/LocalFileStorage.java b/server-snack/src/main/java/com/snack/server/common/storage/LocalFileStorage.java index f5cee93..fc9c853 100644 --- a/server-snack/src/main/java/com/snack/server/common/storage/LocalFileStorage.java +++ b/server-snack/src/main/java/com/snack/server/common/storage/LocalFileStorage.java @@ -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); diff --git a/server-snack/src/main/java/com/snack/server/common/storage/S3FileStorage.java b/server-snack/src/main/java/com/snack/server/common/storage/S3FileStorage.java index feaf743..c23e597 100644 --- a/server-snack/src/main/java/com/snack/server/common/storage/S3FileStorage.java +++ b/server-snack/src/main/java/com/snack/server/common/storage/S3FileStorage.java @@ -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()))) diff --git a/server-snack/src/main/java/com/snack/server/config/SaTokenConfig.java b/server-snack/src/main/java/com/snack/server/config/SaTokenConfig.java index f9974cf..7a94dfd 100644 --- a/server-snack/src/main/java/com/snack/server/config/SaTokenConfig.java +++ b/server-snack/src/main/java/com/snack/server/config/SaTokenConfig.java @@ -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; diff --git a/server-snack/src/main/java/com/snack/server/config/WebSocketConfig.java b/server-snack/src/main/java/com/snack/server/config/WebSocketConfig.java index 47a350b..12df455 100644 --- a/server-snack/src/main/java/com/snack/server/config/WebSocketConfig.java +++ b/server-snack/src/main/java/com/snack/server/config/WebSocketConfig.java @@ -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; diff --git a/server-snack/src/main/java/com/snack/server/constant/RedisKey.java b/server-snack/src/main/java/com/snack/server/constant/RedisKey.java index b1f4e35..ceb83d7 100644 --- a/server-snack/src/main/java/com/snack/server/constant/RedisKey.java +++ b/server-snack/src/main/java/com/snack/server/constant/RedisKey.java @@ -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"; } diff --git a/server-snack/src/main/java/com/snack/server/module/chat/controller/ChatAdminController.java b/server-snack/src/main/java/com/snack/server/module/chat/controller/ChatAdminController.java index 2afc284..ef90d0f 100644 --- a/server-snack/src/main/java/com/snack/server/module/chat/controller/ChatAdminController.java +++ b/server-snack/src/main/java/com/snack/server/module/chat/controller/ChatAdminController.java @@ -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; diff --git a/server-snack/src/main/java/com/snack/server/module/chat/controller/ChatUserController.java b/server-snack/src/main/java/com/snack/server/module/chat/controller/ChatUserController.java index 016a4a4..fec8288 100644 --- a/server-snack/src/main/java/com/snack/server/module/chat/controller/ChatUserController.java +++ b/server-snack/src/main/java/com/snack/server/module/chat/controller/ChatUserController.java @@ -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; diff --git a/server-snack/src/main/java/com/snack/server/module/chat/controller/UserAuthController.java b/server-snack/src/main/java/com/snack/server/module/chat/controller/UserAuthController.java index 0f2d57f..d130a34 100644 --- a/server-snack/src/main/java/com/snack/server/module/chat/controller/UserAuthController.java +++ b/server-snack/src/main/java/com/snack/server/module/chat/controller/UserAuthController.java @@ -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; diff --git a/server-snack/src/main/java/com/snack/server/module/chat/service/ChatMessageService.java b/server-snack/src/main/java/com/snack/server/module/chat/service/ChatMessageService.java index 24d1bdd..11bc5e5 100644 --- a/server-snack/src/main/java/com/snack/server/module/chat/service/ChatMessageService.java +++ b/server-snack/src/main/java/com/snack/server/module/chat/service/ChatMessageService.java @@ -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; diff --git a/server-snack/src/main/java/com/snack/server/module/chat/service/ChatOnlineService.java b/server-snack/src/main/java/com/snack/server/module/chat/service/ChatOnlineService.java index 033ef80..4a20bd1 100644 --- a/server-snack/src/main/java/com/snack/server/module/chat/service/ChatOnlineService.java +++ b/server-snack/src/main/java/com/snack/server/module/chat/service/ChatOnlineService.java @@ -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; diff --git a/server-snack/src/main/java/com/snack/server/module/chat/service/QuickReplyService.java b/server-snack/src/main/java/com/snack/server/module/chat/service/QuickReplyService.java index a810487..ec601fb 100644 --- a/server-snack/src/main/java/com/snack/server/module/chat/service/QuickReplyService.java +++ b/server-snack/src/main/java/com/snack/server/module/chat/service/QuickReplyService.java @@ -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; diff --git a/server-snack/src/main/java/com/snack/server/module/chat/service/impl/ChatMessageServiceImpl.java b/server-snack/src/main/java/com/snack/server/module/chat/service/impl/ChatMessageServiceImpl.java index 99f7113..b9dbbe9 100644 --- a/server-snack/src/main/java/com/snack/server/module/chat/service/impl/ChatMessageServiceImpl.java +++ b/server-snack/src/main/java/com/snack/server/module/chat/service/impl/ChatMessageServiceImpl.java @@ -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; diff --git a/server-snack/src/main/java/com/snack/server/module/chat/service/impl/ChatSessionServiceImpl.java b/server-snack/src/main/java/com/snack/server/module/chat/service/impl/ChatSessionServiceImpl.java index b483ed9..b64379c 100644 --- a/server-snack/src/main/java/com/snack/server/module/chat/service/impl/ChatSessionServiceImpl.java +++ b/server-snack/src/main/java/com/snack/server/module/chat/service/impl/ChatSessionServiceImpl.java @@ -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 adminIds = records.stream().map(ChatSession::getAdminId) .filter(java.util.Objects::nonNull).distinct().toList(); Map userMap = userIds.isEmpty() ? Collections.emptyMap() - : userMapper.selectBatchIds(userIds).stream() + : userMapper.selectList( + new LambdaQueryWrapper().in(User::getId, userIds)).stream() .collect(Collectors.toMap(User::getId, Function.identity())); Map adminMap = adminIds.isEmpty() ? Collections.emptyMap() - : adminMapper.selectBatchIds(adminIds).stream() + : adminMapper.selectList( + new LambdaQueryWrapper().in(Admin::getId, adminIds)).stream() .collect(Collectors.toMap(Admin::getId, Function.identity())); // 关键字过滤(用户名 / 会话编号)发生在拿到 VO 之后 diff --git a/server-snack/src/main/java/com/snack/server/module/chat/service/impl/QuickReplyServiceImpl.java b/server-snack/src/main/java/com/snack/server/module/chat/service/impl/QuickReplyServiceImpl.java index 096518f..6f690c1 100644 --- a/server-snack/src/main/java/com/snack/server/module/chat/service/impl/QuickReplyServiceImpl.java +++ b/server-snack/src/main/java/com/snack/server/module/chat/service/impl/QuickReplyServiceImpl.java @@ -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; diff --git a/server-snack/src/main/java/com/snack/server/module/chat/websocket/ChatHandshakeInterceptor.java b/server-snack/src/main/java/com/snack/server/module/chat/websocket/ChatHandshakeInterceptor.java index afbfad5..e183b04 100644 --- a/server-snack/src/main/java/com/snack/server/module/chat/websocket/ChatHandshakeInterceptor.java +++ b/server-snack/src/main/java/com/snack/server/module/chat/websocket/ChatHandshakeInterceptor.java @@ -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; diff --git a/server-snack/src/main/java/com/snack/server/module/chat/websocket/ChatStompController.java b/server-snack/src/main/java/com/snack/server/module/chat/websocket/ChatStompController.java index 5a2e484..54eb34c 100644 --- a/server-snack/src/main/java/com/snack/server/module/chat/websocket/ChatStompController.java +++ b/server-snack/src/main/java/com/snack/server/module/chat/websocket/ChatStompController.java @@ -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 控制器 * diff --git a/server-snack/src/main/java/com/snack/server/module/chat/websocket/ChatWebSocketEventListener.java b/server-snack/src/main/java/com/snack/server/module/chat/websocket/ChatWebSocketEventListener.java index fa6ba66..0116598 100644 --- a/server-snack/src/main/java/com/snack/server/module/chat/websocket/ChatWebSocketEventListener.java +++ b/server-snack/src/main/java/com/snack/server/module/chat/websocket/ChatWebSocketEventListener.java @@ -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; diff --git a/server-snack/src/main/java/com/snack/server/module/file/service/impl/FileServiceImpl.java b/server-snack/src/main/java/com/snack/server/module/file/service/impl/FileServiceImpl.java index ac4e5f6..b8f8574 100644 --- a/server-snack/src/main/java/com/snack/server/module/file/service/impl/FileServiceImpl.java +++ b/server-snack/src/main/java/com/snack/server/module/file/service/impl/FileServiceImpl.java @@ -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; diff --git a/server-snack/src/main/java/com/snack/server/module/seckill/service/impl/SeckillServiceImpl.java b/server-snack/src/main/java/com/snack/server/module/seckill/service/impl/SeckillServiceImpl.java index c6aaeb2..41eef87 100644 --- a/server-snack/src/main/java/com/snack/server/module/seckill/service/impl/SeckillServiceImpl.java +++ b/server-snack/src/main/java/com/snack/server/module/seckill/service/impl/SeckillServiceImpl.java @@ -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(); return activities.stream().map(a -> enrichActivityVO(a, false)).toList(); } @@ -133,9 +133,15 @@ public class SeckillServiceImpl implements SeckillService { // 批量取 SPU / SKU Set spuIds = products.stream().map(SeckillProduct::getProductId).collect(Collectors.toSet()); Set skuIds = products.stream().map(SeckillProduct::getSkuId).collect(Collectors.toSet()); - Map productMap = productEntityMapper.selectBatchIds(spuIds).stream() + Map productMap = spuIds.isEmpty() ? java.util.Collections.emptyMap() + : productEntityMapper.selectList( + new LambdaQueryWrapper().in(Product::getId, spuIds)) + .stream() .collect(Collectors.toMap(Product::getId, p -> p)); - Map skuMap = productSkuMapper.selectBatchIds(skuIds).stream() + Map skuMap = skuIds.isEmpty() ? java.util.Collections.emptyMap() + : productSkuMapper.selectList( + new LambdaQueryWrapper().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()); } } 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); } diff --git a/web-snack/.eslintrc-auto-import.json b/web-snack/.eslintrc-auto-import.json new file mode 100644 index 0000000..85f30ee --- /dev/null +++ b/web-snack/.eslintrc-auto-import.json @@ -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 + } +} diff --git a/web-snack/pnpm-lock.yaml b/web-snack/pnpm-lock.yaml index 6b07ef5..e3cb0cb 100644 --- a/web-snack/pnpm-lock.yaml +++ b/web-snack/pnpm-lock.yaml @@ -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 diff --git a/web-snack/src/layouts/MainLayout.vue b/web-snack/src/layouts/MainLayout.vue index 0317577..4b84318 100644 --- a/web-snack/src/layouts/MainLayout.vue +++ b/web-snack/src/layouts/MainLayout.vue @@ -58,8 +58,12 @@ function logout() { 个人中心 退出 - 公告 - 400-888-8888 + + + + + 公告 + 400-888-8888 @@ -87,9 +91,15 @@ function logout() { - 购物车 + + + 购物车 + - 我的 + + + 我的 + @@ -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 */ diff --git a/web-snack/src/types/auto-imports.d.ts b/web-snack/src/types/auto-imports.d.ts new file mode 100644 index 0000000..101b7d6 --- /dev/null +++ b/web-snack/src/types/auto-imports.d.ts @@ -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') +} diff --git a/web-snack/src/types/components.d.ts b/web-snack/src/types/components.d.ts new file mode 100644 index 0000000..62ffd08 --- /dev/null +++ b/web-snack/src/types/components.d.ts @@ -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'] + } +} diff --git a/web-snack/src/views/user/LoginView.vue b/web-snack/src/views/user/LoginView.vue index d73e3e1..5185a9a 100644 --- a/web-snack/src/views/user/LoginView.vue +++ b/web-snack/src/views/user/LoginView.vue @@ -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() { - - - - 零食商城 - 登录您的账号 - + + + + + - - - + + + 零食商城 + + + + 吃货的 + 快乐星球 + + 严选全球好零食 · 满 99 包邮 · 7 天无忧退换 + + + + + + 每日限时秒杀 + 低至 1 折起 · 抢到就是赚到 + + + + + + 7×24 客服在线 + 订单 / 售后 / 咨询秒回应 + + + + + + + + + + + 欢迎回来 👋 + 登录账号,开启零食之旅 + + + + + 用户名 + + + + + + + 密码 + + + + + + + + + + + + + 记住我 + 忘记密码? + + + - - - - - - - 登录 + class="auth-submit" + :loading="loading" + @click="onSubmit" + > + 登 录 - - + - - +
登录您的账号
严选全球好零食 · 满 99 包邮 · 7 天无忧退换
登录账号,开启零食之旅