From ab4891d8dbd4fa939a241fb21db758b61c5d95f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E5=AD=9F?= <3111696955@qq.com> Date: Thu, 31 Jul 2025 09:27:13 +0800 Subject: [PATCH] =?UTF-8?q?feat(security):=20=E6=B7=BB=E5=8A=A0=20JWT=20?= =?UTF-8?q?=E8=AE=A4=E8=AF=81=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在后端添加 JWT 认证过滤器 JwtAuthenticationTokenFilter - 创建 JwtTokenUtil 工具类用于生成和验证 JWT token - 在 application.yml 中配置 JWT 相关参数 - 更新前端 HomePage 组件,增加用户认证相关逻辑 --- .roo/mcp.json | 8 - biji-houdaun/pom.xml | 56 ++- .../bijihoudaun/config/SecurityConfig.java | 61 +++ .../controller/UserController.java | 11 +- .../JwtAuthenticationTokenFilter.java | 52 +++ .../test/bijihoudaun/mapper/UserMapper.java | 5 +- .../test/bijihoudaun/service/UserService.java | 2 +- .../service/impl/UserServiceImpl.java | 38 +- .../test/bijihoudaun/util/JwtTokenUtil.java | 68 +++ .../src/main/resources/application.yml | 7 + biji-qianduan/package-lock.json | 244 ++++++++++ biji-qianduan/package.json | 2 + biji-qianduan/src/components/HomePage.vue | 439 ++++++++++-------- doc/development_plan.md | 40 +- 14 files changed, 773 insertions(+), 260 deletions(-) create mode 100644 biji-houdaun/src/main/java/com/test/bijihoudaun/config/SecurityConfig.java create mode 100644 biji-houdaun/src/main/java/com/test/bijihoudaun/interceptor/JwtAuthenticationTokenFilter.java create mode 100644 biji-houdaun/src/main/java/com/test/bijihoudaun/util/JwtTokenUtil.java diff --git a/.roo/mcp.json b/.roo/mcp.json index 6375659..c5282a5 100644 --- a/.roo/mcp.json +++ b/.roo/mcp.json @@ -9,14 +9,6 @@ "alwaysAllow": [ "sequentialthinking" ] - }, - "filesystem": { - "command": "npx", - "args": [ - "-y", - "@modelcontextprotocol/server-filesystem", - "C:/AIFA/2 " - ] } } } \ No newline at end of file diff --git a/biji-houdaun/pom.xml b/biji-houdaun/pom.xml index 73f4d53..a571b4a 100644 --- a/biji-houdaun/pom.xml +++ b/biji-houdaun/pom.xml @@ -32,16 +32,40 @@ 3.5.12 - - org.springframework.boot - spring-boot-starter-web - - - - - org.springframework.boot - spring-boot-starter-test - + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-security + + + + + io.jsonwebtoken + jjwt-api + 0.11.5 + + + io.jsonwebtoken + jjwt-impl + 0.11.5 + runtime + + + io.jsonwebtoken + jjwt-jackson + 0.11.5 + runtime + + + + + org.springframework.boot + spring-boot-starter-test + org.springframework.boot @@ -81,17 +105,7 @@ java-uuid-generator 4.0.1 - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.security - spring-security-core - 5.7.1 - + diff --git a/biji-houdaun/src/main/java/com/test/bijihoudaun/config/SecurityConfig.java b/biji-houdaun/src/main/java/com/test/bijihoudaun/config/SecurityConfig.java new file mode 100644 index 0000000..e0d17c8 --- /dev/null +++ b/biji-houdaun/src/main/java/com/test/bijihoudaun/config/SecurityConfig.java @@ -0,0 +1,61 @@ +package com.test.bijihoudaun.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import com.test.bijihoudaun.interceptor.JwtAuthenticationTokenFilter; +import com.test.bijihoudaun.util.JwtTokenUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@Configuration +@EnableWebSecurity +public class SecurityConfig { + + @Autowired + private UserDetailsService userDetailsService; + + @Autowired + private JwtTokenUtil jwtTokenUtil; + + @Value("${jwt.header}") + private String tokenHeader; + + @Value("${jwt.tokenHead}") + private String tokenHead; + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter = new JwtAuthenticationTokenFilter(userDetailsService, jwtTokenUtil, tokenHeader, tokenHead); + + http + .csrf(csrf -> csrf.disable()) + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(authz -> authz + .requestMatchers("/doc.html", "/webjars/**", "/v3/api-docs/**", "/api/user/login", "/api/user/register").permitAll() + .requestMatchers(org.springframework.http.HttpMethod.GET).permitAll() + .anyRequest().authenticated() + ); + + // 在这里添加JWT过滤器 + http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); + + return http.build(); + } +} \ No newline at end of file diff --git a/biji-houdaun/src/main/java/com/test/bijihoudaun/controller/UserController.java b/biji-houdaun/src/main/java/com/test/bijihoudaun/controller/UserController.java index 6b35223..51f885e 100644 --- a/biji-houdaun/src/main/java/com/test/bijihoudaun/controller/UserController.java +++ b/biji-houdaun/src/main/java/com/test/bijihoudaun/controller/UserController.java @@ -12,6 +12,10 @@ import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; +import java.util.Map; + @Tag(name = "用户接口") @RestController @RequestMapping("/api/user") @@ -36,8 +40,11 @@ public class UserController { @Parameter(name = "password", description = "密码",required = true) }) @PostMapping("/login") - public R login(String username, String password){ - return R.success(userService.login(username,password)); + public R> login(String username, String password){ + String token = userService.login(username, password); + Map tokenMap = new HashMap<>(); + tokenMap.put("token", token); + return R.success(tokenMap); } @Operation(summary = "用户删除") diff --git a/biji-houdaun/src/main/java/com/test/bijihoudaun/interceptor/JwtAuthenticationTokenFilter.java b/biji-houdaun/src/main/java/com/test/bijihoudaun/interceptor/JwtAuthenticationTokenFilter.java new file mode 100644 index 0000000..76466b2 --- /dev/null +++ b/biji-houdaun/src/main/java/com/test/bijihoudaun/interceptor/JwtAuthenticationTokenFilter.java @@ -0,0 +1,52 @@ +package com.test.bijihoudaun.interceptor; + +import com.test.bijihoudaun.util.JwtTokenUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.web.filter.OncePerRequestFilter; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; + +public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { + + private final UserDetailsService userDetailsService; + private final JwtTokenUtil jwtTokenUtil; + private final String tokenHeader; + private final String tokenHead; + + public JwtAuthenticationTokenFilter(UserDetailsService userDetailsService, JwtTokenUtil jwtTokenUtil, String tokenHeader, String tokenHead) { + this.userDetailsService = userDetailsService; + this.jwtTokenUtil = jwtTokenUtil; + this.tokenHeader = tokenHeader; + this.tokenHead = tokenHead; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { + String authHeader = request.getHeader(this.tokenHeader); + if (authHeader != null && authHeader.startsWith(this.tokenHead)) { + final String authToken = authHeader.substring(this.tokenHead.length()); + String username = jwtTokenUtil.getUsernameFromToken(authToken); + + if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { + UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); + + if (jwtTokenUtil.validateToken(authToken, userDetails)) { + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + } + } + chain.doFilter(request, response); + } +} \ No newline at end of file diff --git a/biji-houdaun/src/main/java/com/test/bijihoudaun/mapper/UserMapper.java b/biji-houdaun/src/main/java/com/test/bijihoudaun/mapper/UserMapper.java index 0415a3c..2fad404 100644 --- a/biji-houdaun/src/main/java/com/test/bijihoudaun/mapper/UserMapper.java +++ b/biji-houdaun/src/main/java/com/test/bijihoudaun/mapper/UserMapper.java @@ -3,10 +3,11 @@ package com.test.bijihoudaun.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.test.bijihoudaun.entity.User; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Select; @Mapper public interface UserMapper extends BaseMapper { // 自定义查询方法示例 - // @Select("SELECT * FROM user WHERE username = #{username}") - // User findByUsername(String username); + @Select("SELECT * FROM user WHERE username = #{username}") + User findByUsername(String username); } diff --git a/biji-houdaun/src/main/java/com/test/bijihoudaun/service/UserService.java b/biji-houdaun/src/main/java/com/test/bijihoudaun/service/UserService.java index d74d78a..660efbf 100644 --- a/biji-houdaun/src/main/java/com/test/bijihoudaun/service/UserService.java +++ b/biji-houdaun/src/main/java/com/test/bijihoudaun/service/UserService.java @@ -19,7 +19,7 @@ public interface UserService extends IService { * @param password 密码 * @return 登录成功的用户 */ - User login(String username, String password); + String login(String username, String password); /** * 用户删除 diff --git a/biji-houdaun/src/main/java/com/test/bijihoudaun/service/impl/UserServiceImpl.java b/biji-houdaun/src/main/java/com/test/bijihoudaun/service/impl/UserServiceImpl.java index 7070f3a..3aa3991 100644 --- a/biji-houdaun/src/main/java/com/test/bijihoudaun/service/impl/UserServiceImpl.java +++ b/biji-houdaun/src/main/java/com/test/bijihoudaun/service/impl/UserServiceImpl.java @@ -6,21 +6,34 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.test.bijihoudaun.entity.User; import com.test.bijihoudaun.mapper.UserMapper; import com.test.bijihoudaun.service.UserService; +import com.test.bijihoudaun.util.JwtTokenUtil; import com.test.bijihoudaun.util.PasswordUtils; import com.test.bijihoudaun.util.UuidV7; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; -import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.Calendar; import java.util.Date; @Service -public class UserServiceImpl extends ServiceImpl implements UserService { +public class UserServiceImpl extends ServiceImpl implements UserService, UserDetailsService { @Autowired private UserMapper userMapper; + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + User user = userMapper.findByUsername(username); + if (user == null) { + throw new UsernameNotFoundException("User not found with username: " + username); + } + return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), new ArrayList<>()); + } + @Override public User register(String username, String password, String email) { String encrypt = PasswordUtils.encrypt(password); @@ -43,23 +56,16 @@ public class UserServiceImpl extends ServiceImpl implements Us return user; } + @Autowired + private JwtTokenUtil jwtTokenUtil; + @Override - public User login(String username, String password) { - LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); - queryWrapper.eq(User::getUsername, username); - User user = userMapper.selectOne(queryWrapper); - boolean verify = PasswordUtils.verify(password, user.getPassword()); - if (!verify) { + public String login(String username, String password) { + UserDetails userDetails = loadUserByUsername(username); + if (!PasswordUtils.verify(password, userDetails.getPassword())) { throw new RuntimeException("密码错误"); } - user.setToken(UuidV7.uuidNoHyphen()); - // 过期时间:当前时间+3天的时间 - // 修改时间计算方式 - Calendar calendar = Calendar.getInstance(); - calendar.add(Calendar.DAY_OF_MONTH, 3); // 增加3天 - user.setTokenEnddata(calendar.getTime()); - userMapper.updateById(user); - return user; + return jwtTokenUtil.generateToken(userDetails); } @Override diff --git a/biji-houdaun/src/main/java/com/test/bijihoudaun/util/JwtTokenUtil.java b/biji-houdaun/src/main/java/com/test/bijihoudaun/util/JwtTokenUtil.java new file mode 100644 index 0000000..5701c04 --- /dev/null +++ b/biji-houdaun/src/main/java/com/test/bijihoudaun/util/JwtTokenUtil.java @@ -0,0 +1,68 @@ +package com.test.bijihoudaun.util; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +@Component +public class JwtTokenUtil { + + @Value("${jwt.secret}") + private String secret; + + @Value("${jwt.expiration}") + private Long expiration; + + // 从token中获取用户名 + public String getUsernameFromToken(String token) { + return getClaimFromToken(token, Claims::getSubject); + } + + // 从token中获取过期时间 + public Date getExpirationDateFromToken(String token) { + return getClaimFromToken(token, Claims::getExpiration); + } + + public T getClaimFromToken(String token, Function claimsResolver) { + final Claims claims = getAllClaimsFromToken(token); + return claimsResolver.apply(claims); + } + + // 为了从token中获取任何信息,我们都需要密钥 + private Claims getAllClaimsFromToken(String token) { + return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); + } + + // 检查token是否过期 + private Boolean isTokenExpired(String token) { + final Date expiration = getExpirationDateFromToken(token); + return expiration.before(new Date()); + } + + // 为用户生成token + public String generateToken(UserDetails userDetails) { + Map claims = new HashMap<>(); + return doGenerateToken(claims, userDetails.getUsername()); + } + + // 创建token + private String doGenerateToken(Map claims, String subject) { + return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis())) + .setExpiration(new Date(System.currentTimeMillis() + expiration * 1000)) + .signWith(SignatureAlgorithm.HS512, secret).compact(); + } + + // 验证token + public Boolean validateToken(String token, UserDetails userDetails) { + final String username = getUsernameFromToken(token); + return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); + } +} \ No newline at end of file diff --git a/biji-houdaun/src/main/resources/application.yml b/biji-houdaun/src/main/resources/application.yml index fb6bef5..c256703 100644 --- a/biji-houdaun/src/main/resources/application.yml +++ b/biji-houdaun/src/main/resources/application.yml @@ -29,3 +29,10 @@ mybatis-plus: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true + +# JWT 配置 +jwt: + secret: mysecretkeymysecretkeymysecretkeymysecretkeymysecretkey # 至少256位的密钥 + expiration: 86400 # token有效期,单位秒,这里是24小时 + header: Authorization # JWT存储的请求头 + tokenHead: "Bearer " # JWT负载中拿到开头 diff --git a/biji-qianduan/package-lock.json b/biji-qianduan/package-lock.json index 3cbfa51..a6ea855 100644 --- a/biji-qianduan/package-lock.json +++ b/biji-qianduan/package-lock.json @@ -10,7 +10,9 @@ "dependencies": { "@kangc/v-md-editor": "^2.2.4", "codemirror": "^6.0.1", + "element-plus": "^2.10.4", "highlight.js": "^11.11.1", + "vditor": "^3.11.1", "vue": "^3.5.13" }, "devDependencies": { @@ -160,6 +162,24 @@ "w3c-keyname": "^2.2.4" } }, + "node_modules/@ctrl/tinycolor": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", + "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/@element-plus/icons-vue": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz", + "integrity": "sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==", + "license": "MIT", + "peerDependencies": { + "vue": "^3.2.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.8", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", @@ -602,6 +622,31 @@ "node": ">=18" } }, + "node_modules/@floating-ui/core": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.2.tgz", + "integrity": "sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.2.tgz", + "integrity": "sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.2", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", @@ -702,6 +747,17 @@ "node": ">= 6" } }, + "node_modules/@popperjs/core": { + "name": "@sxzz/popperjs-es", + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz", + "integrity": "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.46.2", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz", @@ -1029,6 +1085,21 @@ "@types/node": "*" } }, + "node_modules/@types/lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==", + "license": "MIT" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, "node_modules/@types/mdast": { "version": "3.0.15", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", @@ -1065,6 +1136,12 @@ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", "license": "MIT" }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz", + "integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==", + "license": "MIT" + }, "node_modules/@vant/icons": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@vant/icons/-/icons-1.8.0.tgz", @@ -1263,6 +1340,94 @@ "upath": "^1.1.0" } }, + "node_modules/@vueuse/core": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-9.13.0.tgz", + "integrity": "sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.16", + "@vueuse/metadata": "9.13.0", + "@vueuse/shared": "9.13.0", + "vue-demi": "*" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/core/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-9.13.0.tgz", + "integrity": "sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-9.13.0.tgz", + "integrity": "sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==", + "license": "MIT", + "dependencies": { + "vue-demi": "*" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, "node_modules/ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -1350,6 +1515,12 @@ "node": ">=0.10.0" } }, + "node_modules/async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", + "license": "MIT" + }, "node_modules/atob": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", @@ -2227,6 +2398,12 @@ "node": ">=0.3.1" } }, + "node_modules/diff-match-patch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", + "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==", + "license": "Apache-2.0" + }, "node_modules/dir-glob": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", @@ -2245,6 +2422,32 @@ "integrity": "sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ==", "license": "(MPL-2.0 OR Apache-2.0)" }, + "node_modules/element-plus": { + "version": "2.10.4", + "resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.10.4.tgz", + "integrity": "sha512-UD4elWHrCnp1xlPhbXmVcaKFLCRaRAY6WWRwemGfGW3ceIjXm9fSYc9RNH3AiOEA6Ds1p9ZvhCs76CR9J8Vd+A==", + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^3.4.1", + "@element-plus/icons-vue": "^2.3.1", + "@floating-ui/dom": "^1.0.1", + "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7", + "@types/lodash": "^4.14.182", + "@types/lodash-es": "^4.17.6", + "@vueuse/core": "^9.1.0", + "async-validator": "^4.2.5", + "dayjs": "^1.11.13", + "escape-html": "^1.0.3", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "lodash-unified": "^1.0.2", + "memoize-one": "^6.0.0", + "normalize-wheel-es": "^1.2.0" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, "node_modules/elkjs": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.9.3.tgz", @@ -3060,12 +3263,29 @@ "uc.micro": "^1.0.1" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, "node_modules/lodash-es": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", "license": "MIT" }, + "node_modules/lodash-unified": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/lodash-unified/-/lodash-unified-1.0.3.tgz", + "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==", + "license": "MIT", + "peerDependencies": { + "@types/lodash-es": "*", + "lodash": "*", + "lodash-es": "*" + } + }, "node_modules/magic-string": { "version": "0.30.17", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", @@ -3227,6 +3447,12 @@ "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", "license": "MIT" }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", + "license": "MIT" + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -3832,6 +4058,12 @@ "integrity": "sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==", "license": "MIT" }, + "node_modules/normalize-wheel-es": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz", + "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==", + "license": "BSD-3-Clause" + }, "node_modules/object-copy": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", @@ -4780,6 +5012,18 @@ "vue": "^3.0.0" } }, + "node_modules/vditor": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/vditor/-/vditor-3.11.1.tgz", + "integrity": "sha512-7rjNSXYVyZG0mVZpUG2tfxwnoNtkcRCnwdSju+Zvpjf/r72iQa6kLpeThFMIKPuQ5CRnQQv6gnR3eNU6UGbC2Q==", + "license": "MIT", + "dependencies": { + "diff-match-patch": "^1.0.5" + }, + "funding": { + "url": "https://ld246.com/sponsor" + } + }, "node_modules/vite": { "version": "6.3.5", "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", diff --git a/biji-qianduan/package.json b/biji-qianduan/package.json index 5ea683f..141e0e5 100644 --- a/biji-qianduan/package.json +++ b/biji-qianduan/package.json @@ -11,7 +11,9 @@ "dependencies": { "@kangc/v-md-editor": "^2.2.4", "codemirror": "^6.0.1", + "element-plus": "^2.10.4", "highlight.js": "^11.11.1", + "vditor": "^3.11.1", "vue": "^3.5.13" }, "devDependencies": { diff --git a/biji-qianduan/src/components/HomePage.vue b/biji-qianduan/src/components/HomePage.vue index 801bc55..a08baf2 100644 --- a/biji-qianduan/src/components/HomePage.vue +++ b/biji-qianduan/src/components/HomePage.vue @@ -1,76 +1,67 @@