编写一些前端 #4
5
.gitignore
vendored
5
.gitignore
vendored
@ -16,9 +16,9 @@ build/
|
|||||||
bin/
|
bin/
|
||||||
!**/src/main/**/bin/
|
!**/src/main/**/bin/
|
||||||
!**/src/test/**/bin/
|
!**/src/test/**/bin/
|
||||||
|
pnpm-workspace.yaml
|
||||||
|
|
||||||
### IntelliJ IDEA ###
|
### IntelliJ IDEA ###
|
||||||
.idea
|
|
||||||
*.iws
|
*.iws
|
||||||
*.iml
|
*.iml
|
||||||
*.ipr
|
*.ipr
|
||||||
@ -41,3 +41,6 @@ pnpm-lock.yaml
|
|||||||
|
|
||||||
src/main/resources/html
|
src/main/resources/html
|
||||||
log
|
log
|
||||||
|
|
||||||
|
web-src/types
|
||||||
|
.idea
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
config:
|
config:
|
||||||
port: 9963
|
port: 9963
|
||||||
app-name: maven-repository
|
app-name: pan-disk
|
||||||
app-version: 1.0
|
app-version: 1.1
|
||||||
data-source:
|
data-source:
|
||||||
mysql:
|
mysql:
|
||||||
username: pan
|
username: pan
|
||||||
|
16
package.json
16
package.json
@ -17,27 +17,41 @@
|
|||||||
"format": "prettier --write \"**/*.{js,ts,jsx,tsx,cjs,cts,mjs,mts,vue,astro,java}\""
|
"format": "prettier --write \"**/*.{js,ts,jsx,tsx,cjs,cts,mjs,mts,vue,astro,java}\""
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@iconify/vue": "^5.0.0",
|
||||||
|
"@types/vue-router": "^2.0.0",
|
||||||
|
"@vueuse/core": "^13.4.0",
|
||||||
|
"naive-ui": "^2.42.0",
|
||||||
"pinia": "^3.0.3",
|
"pinia": "^3.0.3",
|
||||||
|
"swagger-ui-dist": "^5.25.3",
|
||||||
|
"unplugin-auto-import": "^19.3.0",
|
||||||
|
"unplugin-vue-components": "^28.8.0",
|
||||||
"vue": "^3.5.17",
|
"vue": "^3.5.17",
|
||||||
"vue-router": "^4.5.1"
|
"vue-router": "^4.5.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tsconfig/node22": "^22.0.2",
|
"@tsconfig/node22": "^22.0.2",
|
||||||
|
"@types/js-cookie": "^3.0.6",
|
||||||
"@types/node": "^22.15.32",
|
"@types/node": "^22.15.32",
|
||||||
|
"@types/swagger-ui-dist": "^3.30.6",
|
||||||
"@vitejs/plugin-vue": "^6.0.0",
|
"@vitejs/plugin-vue": "^6.0.0",
|
||||||
"@vue/eslint-config-prettier": "^10.2.0",
|
"@vue/eslint-config-prettier": "^10.2.0",
|
||||||
"@vue/eslint-config-typescript": "^14.5.1",
|
"@vue/eslint-config-typescript": "^14.5.1",
|
||||||
"@vue/tsconfig": "^0.7.0",
|
"@vue/tsconfig": "^0.7.0",
|
||||||
|
"alova": "^3.3.3",
|
||||||
"eslint": "^9.29.0",
|
"eslint": "^9.29.0",
|
||||||
"eslint-plugin-oxlint": "~1.1.0",
|
"eslint-plugin-oxlint": "~1.1.0",
|
||||||
"eslint-plugin-vue": "~10.2.0",
|
"eslint-plugin-vue": "~10.2.0",
|
||||||
"jiti": "^2.4.2",
|
"jiti": "^2.4.2",
|
||||||
|
"js-base64": "^3.7.7",
|
||||||
|
"js-cookie": "^3.0.5",
|
||||||
"npm-run-all2": "^8.0.4",
|
"npm-run-all2": "^8.0.4",
|
||||||
"oxlint": "~1.1.0",
|
"oxlint": "~1.1.0",
|
||||||
|
"pinia-plugin-persistedstate": "^4.4.0",
|
||||||
"prettier": "3.5.3",
|
"prettier": "3.5.3",
|
||||||
"prettier-plugin-java": "^2.6.8",
|
"prettier-plugin-java": "^2.6.8",
|
||||||
|
"sass-embedded": "^1.89.2",
|
||||||
"typescript": "~5.8.0",
|
"typescript": "~5.8.0",
|
||||||
"vite": "npm:rolldown-vite@latest",
|
"vite": "~6.0.0",
|
||||||
"vite-plugin-vue-devtools": "^7.7.7",
|
"vite-plugin-vue-devtools": "^7.7.7",
|
||||||
"vue-tsc": "^2.2.10"
|
"vue-tsc": "^2.2.10"
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,49 @@
|
|||||||
|
package com.mingliqiye.disk.config;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||||
|
import com.mingliqiye.disk.time.DateTime;
|
||||||
|
import com.mingliqiye.disk.util.PanStpUtil;
|
||||||
|
import com.mingliqiye.disk.uuid.UUID;
|
||||||
|
import java.util.Objects;
|
||||||
|
import org.apache.ibatis.reflection.MetaObject;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class MyBatisPlusMetaObjectHandler implements MetaObjectHandler {
|
||||||
|
|
||||||
|
private static final String CREATE_USER_ID = "createUserId";
|
||||||
|
private static final String CREATE_TIME = "createTime";
|
||||||
|
private static final String UPDATE_USER_ID = "updateUserId";
|
||||||
|
private static final String UPDATE_TIME = "updateTime";
|
||||||
|
private static final UUID SYSTEM_ID_UUID = UUID.ofString("48126760-5483-11F0-B90C-6D1CD960F6F3");
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void insertFill(MetaObject metaObject) {
|
||||||
|
if (metaObject.hasSetter(CREATE_USER_ID)) {
|
||||||
|
this.strictInsertFill(
|
||||||
|
metaObject,
|
||||||
|
CREATE_USER_ID,
|
||||||
|
UUID.class,
|
||||||
|
Objects.requireNonNullElse(PanStpUtil.getLoginIdDefaultNull(), SYSTEM_ID_UUID)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (metaObject.hasSetter(CREATE_TIME)) {
|
||||||
|
this.strictInsertFill(metaObject, CREATE_TIME, DateTime.class, DateTime.now());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateFill(MetaObject metaObject) {
|
||||||
|
if (metaObject.hasSetter(UPDATE_USER_ID)) {
|
||||||
|
this.strictUpdateFill(
|
||||||
|
metaObject,
|
||||||
|
UPDATE_USER_ID,
|
||||||
|
UUID.class,
|
||||||
|
Objects.requireNonNullElse(PanStpUtil.getLoginIdDefaultNull(), SYSTEM_ID_UUID)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (metaObject.hasSetter(UPDATE_TIME)) {
|
||||||
|
this.strictUpdateFill(metaObject, UPDATE_TIME, DateTime.class, DateTime.now());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package com.mingliqiye.disk.config;
|
package com.mingliqiye.disk.config;
|
||||||
|
|
||||||
|
import com.mingliqiye.disk.configuration.Config;
|
||||||
import io.swagger.v3.oas.annotations.ExternalDocumentation;
|
import io.swagger.v3.oas.annotations.ExternalDocumentation;
|
||||||
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
|
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
|
||||||
import io.swagger.v3.oas.annotations.enums.SecuritySchemeIn;
|
import io.swagger.v3.oas.annotations.enums.SecuritySchemeIn;
|
||||||
@ -9,9 +10,13 @@ import io.swagger.v3.oas.models.OpenAPI;
|
|||||||
import io.swagger.v3.oas.models.info.Contact;
|
import io.swagger.v3.oas.models.info.Contact;
|
||||||
import io.swagger.v3.oas.models.info.Info;
|
import io.swagger.v3.oas.models.info.Info;
|
||||||
import io.swagger.v3.oas.models.info.License;
|
import io.swagger.v3.oas.models.info.License;
|
||||||
|
import io.swagger.v3.oas.models.servers.Server;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
@OpenAPIDefinition(
|
@OpenAPIDefinition(
|
||||||
externalDocs = @ExternalDocumentation(
|
externalDocs = @ExternalDocumentation(
|
||||||
description = "@git.mingliqiye",
|
description = "@git.mingliqiye",
|
||||||
@ -29,23 +34,36 @@ import org.springframework.context.annotation.Configuration;
|
|||||||
@Configuration
|
@Configuration
|
||||||
public class SpringDocConfig {
|
public class SpringDocConfig {
|
||||||
|
|
||||||
|
private final Config config;
|
||||||
|
|
||||||
|
public SpringDocConfig(Config config) {
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public OpenAPI openAPI() {
|
public OpenAPI openAPI() {
|
||||||
|
Server server = new Server();
|
||||||
|
server.setUrl("/");
|
||||||
|
server.setDescription("当前服务");
|
||||||
|
Server server2 = new Server();
|
||||||
|
server2.setUrl("http://localhost:" + config.getPort() + "/");
|
||||||
|
server2.setDescription("本机服务");
|
||||||
return new OpenAPI()
|
return new OpenAPI()
|
||||||
// 配置接口文档基本信息
|
// 配置接口文档基本信息
|
||||||
.info(this.getApiInfo());
|
.info(this.getApiInfo())
|
||||||
|
.servers(List.of(server, server2));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Info getApiInfo() {
|
private Info getApiInfo() {
|
||||||
return new Info()
|
return new Info()
|
||||||
.title("pan-disk")
|
.title(config.getAppName())
|
||||||
.description("SpringBoot3 pan-disk Swagger3 ApiDoc")
|
.version(config.getAppVersion())
|
||||||
|
.description("SpringBoot3 %s-V%s Swagger3 ApiDoc".formatted(config.getAppName(), config.getAppVersion()))
|
||||||
.contact(
|
.contact(
|
||||||
new Contact().name("mingliqiye").url("https://www.mingliqiye.com").email("minglipro@mingliqiye.com")
|
new Contact().name("mingliqiye").url("https://www.mingliqiye.com").email("minglipro@mingliqiye.com")
|
||||||
)
|
)
|
||||||
.license(new License().name("Apache 2.0").url("https://www.apache.org/licenses/LICENSE-2.0"))
|
.license(new License().name("Apache 2.0").url("https://www.apache.org/licenses/LICENSE-2.0"))
|
||||||
.summary("ApiDoc")
|
.summary("ApiDoc")
|
||||||
.termsOfService("https://pan.mingliqiye.com/")
|
.termsOfService("https://pan.mingliqiye.com/");
|
||||||
.version("1.0");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,42 @@
|
|||||||
|
package com.mingliqiye.disk.config;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.stp.StpInterface;
|
||||||
|
import com.mingliqiye.disk.mappers.UserMapper;
|
||||||
|
import com.mingliqiye.disk.model.User;
|
||||||
|
import com.mingliqiye.disk.uuid.UUID;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Configuration
|
||||||
|
public class StpInterfaceImpl implements StpInterface {
|
||||||
|
|
||||||
|
private final UserMapper userMapper;
|
||||||
|
|
||||||
|
public StpInterfaceImpl(UserMapper userMapper) {
|
||||||
|
this.userMapper = userMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回一个账号所拥有的权限码集合
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<String> getPermissionList(Object loginId, String loginType) {
|
||||||
|
User user = userMapper.selectById(UUID.ofString((String) loginId));
|
||||||
|
List<String> list = new ArrayList<>(user.getPrermissions());
|
||||||
|
if (user.isAdmin()) list.add("*.*.*");
|
||||||
|
user.getRoles().forEach(i -> list.add(String.format("%s.*.*", i)));
|
||||||
|
log.info(String.valueOf(list));
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回一个账号所拥有的角色标识集合 (权限与角色可分开校验)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<String> getRoleList(Object loginId, String loginType) {
|
||||||
|
return userMapper.selectById(UUID.ofString((String) loginId)).getRoles();
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,7 @@ import cn.dev33.satoken.annotation.SaCheckLogin;
|
|||||||
import cn.dev33.satoken.stp.StpUtil;
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.mingliqiye.disk.dto.auth.Login;
|
import com.mingliqiye.disk.dto.auth.Login;
|
||||||
import com.mingliqiye.disk.exception.ExceptionCode;
|
import com.mingliqiye.disk.exception.InternalServerException;
|
||||||
import com.mingliqiye.disk.http.Respose;
|
import com.mingliqiye.disk.http.Respose;
|
||||||
import com.mingliqiye.disk.mappers.UserMapper;
|
import com.mingliqiye.disk.mappers.UserMapper;
|
||||||
import com.mingliqiye.disk.model.User;
|
import com.mingliqiye.disk.model.User;
|
||||||
@ -36,7 +36,7 @@ public class AuthController {
|
|||||||
if (user != null && BCrypt.checkpw(loginBody.getPassword(), user.getPassword())) {
|
if (user != null && BCrypt.checkpw(loginBody.getPassword(), user.getPassword())) {
|
||||||
return Respose.builder(PanStpUtil.login(user.getId()));
|
return Respose.builder(PanStpUtil.login(user.getId()));
|
||||||
}
|
}
|
||||||
return Respose.error(String.class, ExceptionCode.ERROR_INTERNAL_SERVER, "用户名或密码错误");
|
throw new InternalServerException("用户名或密码错误");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "获取当前登陆用户的信息")
|
@Operation(summary = "获取当前登陆用户的信息")
|
||||||
@ -47,8 +47,8 @@ public class AuthController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "登出", security = @SecurityRequirement(name = "Authorization-Bearer-Token"))
|
@Operation(summary = "登出", security = @SecurityRequirement(name = "Authorization-Bearer-Token"))
|
||||||
@DeleteMapping("/logout")
|
|
||||||
@SaCheckLogin
|
@SaCheckLogin
|
||||||
|
@DeleteMapping("/logout")
|
||||||
public Respose<Object> logout() {
|
public Respose<Object> logout() {
|
||||||
StpUtil.logout();
|
StpUtil.logout();
|
||||||
return Respose.builder();
|
return Respose.builder();
|
||||||
|
@ -13,7 +13,7 @@ import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBo
|
|||||||
@Tag(name = "前端路由", description = "统一匹配路径指向VueRouter")
|
@Tag(name = "前端路由", description = "统一匹配路径指向VueRouter")
|
||||||
public class IndexController {
|
public class IndexController {
|
||||||
|
|
||||||
@GetMapping(value = { "/", "/{path:^(?!static|apis).*$}/**" })
|
@GetMapping(value = { "/", "/{path:^(?!static|apis|blob).*$}/**" })
|
||||||
public ResponseEntity<StreamingResponseBody> index() {
|
public ResponseEntity<StreamingResponseBody> index() {
|
||||||
StreamingResponseBody streamingResponseBody = s -> {
|
StreamingResponseBody streamingResponseBody = s -> {
|
||||||
try (InputStream stream = this.getClass().getResourceAsStream("/html/index.html")) {
|
try (InputStream stream = this.getClass().getResourceAsStream("/html/index.html")) {
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
package com.mingliqiye.disk.controller;
|
||||||
|
|
||||||
|
import com.mingliqiye.disk.configuration.Config;
|
||||||
|
import com.mingliqiye.disk.dto.system.Info;
|
||||||
|
import com.mingliqiye.disk.http.Respose;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/apis/system")
|
||||||
|
@Tag(name = "系统路由", description = "访问系统的一些功能")
|
||||||
|
public class SystemController {
|
||||||
|
|
||||||
|
private final Config config;
|
||||||
|
|
||||||
|
public SystemController(Config config) {
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "系统信息")
|
||||||
|
@GetMapping("/info")
|
||||||
|
public Respose<Info> info() {
|
||||||
|
return Respose.builder(new Info(config.getAppName(), config.getAppVersion()));
|
||||||
|
}
|
||||||
|
}
|
12
src/main/java/com/mingliqiye/disk/dto/system/Info.java
Normal file
12
src/main/java/com/mingliqiye/disk/dto/system/Info.java
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package com.mingliqiye.disk.dto.system;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class Info {
|
||||||
|
|
||||||
|
private String appName;
|
||||||
|
private String appVersion;
|
||||||
|
}
|
@ -6,7 +6,6 @@ import cn.dev33.satoken.exception.NotRoleException;
|
|||||||
import com.mingliqiye.disk.http.Respose;
|
import com.mingliqiye.disk.http.Respose;
|
||||||
import com.mingliqiye.disk.util.StringUtil;
|
import com.mingliqiye.disk.util.StringUtil;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.context.support.DefaultMessageSourceResolvable;
|
import org.springframework.context.support.DefaultMessageSourceResolvable;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
@ -23,25 +22,21 @@ import org.springframework.web.servlet.NoHandlerFoundException;
|
|||||||
public class BaseExceptionHandler {
|
public class BaseExceptionHandler {
|
||||||
|
|
||||||
@ExceptionHandler(BaseException.class)
|
@ExceptionHandler(BaseException.class)
|
||||||
public ResponseEntity<Respose<?>> exceptionHandler(BaseException e, HttpServletRequest request) {
|
public ResponseEntity<Respose<?>> exceptionHandler(BaseException e) {
|
||||||
return ResponseEntity.status(e.getCode()).body(
|
return ResponseEntity.status(e.getCode()).body(
|
||||||
Respose.builder().setCode(e.getCode()).setMessage(StringUtil.format("{}", e.getMessage()))
|
Respose.builder().setCode(e.getCode()).setMessage(StringUtil.format("{}", e.getMessage()))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
|
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
|
||||||
public Respose<?> exceptionHandler(HttpRequestMethodNotSupportedException e, HttpServletRequest request) {
|
public Respose<?> exceptionHandler(HttpRequestMethodNotSupportedException e) {
|
||||||
return Respose.builder()
|
return Respose.builder()
|
||||||
.setCode(ExceptionCode.ERROR_METHOD_NOT_ALLOWED.getValue())
|
.setCode(ExceptionCode.ERROR_METHOD_NOT_ALLOWED.getValue())
|
||||||
.setMessage(StringUtil.format("{} by {}", e.getMessage(), e.getClass().getName()));
|
.setMessage(StringUtil.format("{} by {}", e.getMessage(), e.getClass().getName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExceptionHandler(NoHandlerFoundException.class)
|
@ExceptionHandler(NoHandlerFoundException.class)
|
||||||
public ResponseEntity<Respose<?>> exceptionHandler(
|
public ResponseEntity<Respose<?>> exceptionHandler(NoHandlerFoundException e) {
|
||||||
NoHandlerFoundException e,
|
|
||||||
HttpServletRequest request,
|
|
||||||
HttpServletResponse response
|
|
||||||
) {
|
|
||||||
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(
|
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(
|
||||||
Respose.builder()
|
Respose.builder()
|
||||||
.setCode(ExceptionCode.ERROR_NOT_FOUND.getValue())
|
.setCode(ExceptionCode.ERROR_NOT_FOUND.getValue())
|
||||||
@ -50,26 +45,30 @@ public class BaseExceptionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ExceptionHandler(NotLoginException.class)
|
@ExceptionHandler(NotLoginException.class)
|
||||||
public Respose<?> exceptionHandler(NotLoginException e, HttpServletRequest request) {
|
public ResponseEntity<Respose<?>> exceptionHandler(NotLoginException e, HttpServletRequest request) {
|
||||||
return Respose.builder()
|
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(
|
||||||
|
Respose.builder()
|
||||||
.setCode(ExceptionCode.ERROR_UNAUTHORIZED.getValue())
|
.setCode(ExceptionCode.ERROR_UNAUTHORIZED.getValue())
|
||||||
.setMessage(e.getMessage())
|
.setMessage(e.getMessage())
|
||||||
.setData(e.getType());
|
.setData(e.getType())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExceptionHandler(HttpMessageNotReadableException.class)
|
@ExceptionHandler(HttpMessageNotReadableException.class)
|
||||||
public Respose<?> exceptionHandler(HttpMessageNotReadableException e, HttpServletRequest request) {
|
public ResponseEntity<Respose<?>> exceptionHandler(HttpMessageNotReadableException e) {
|
||||||
return Respose.builder()
|
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(
|
||||||
.setCode(ExceptionCode.ERROR_FORBIDDEN.getValue())
|
Respose.builder().setCode(ExceptionCode.ERROR_FORBIDDEN.getValue()).setMessage(e.getMessage())
|
||||||
.setMessage(StringUtil.format("{} by {}", e.getMessage(), e.getClass().getName()));
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExceptionHandler(NotRoleException.class)
|
@ExceptionHandler(NotRoleException.class)
|
||||||
public Respose<?> exceptionHandler(NotRoleException e) {
|
public ResponseEntity<Respose<?>> exceptionHandler(NotRoleException e) {
|
||||||
return Respose.builder()
|
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(
|
||||||
|
Respose.builder()
|
||||||
.setCode(ExceptionCode.ERROR_FORBIDDEN.getValue())
|
.setCode(ExceptionCode.ERROR_FORBIDDEN.getValue())
|
||||||
.setMessage(e.getMessage())
|
.setMessage(e.getMessage())
|
||||||
.setData(e.getCode());
|
.setData(e.getCode())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExceptionHandler(NotPermissionException.class)
|
@ExceptionHandler(NotPermissionException.class)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.mingliqiye.disk.model;
|
package com.mingliqiye.disk.model;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||||
import com.baomidou.mybatisplus.annotation.TableField;
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||||
@ -32,7 +33,17 @@ public class User {
|
|||||||
|
|
||||||
private byte[] icon;
|
private byte[] icon;
|
||||||
private boolean admin;
|
private boolean admin;
|
||||||
private DateTime creationTime;
|
|
||||||
|
@TableField(fill = FieldFill.INSERT)
|
||||||
|
private UUID createUserId;
|
||||||
|
|
||||||
|
@TableField(fill = FieldFill.INSERT)
|
||||||
|
private DateTime createTime;
|
||||||
|
|
||||||
|
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||||
|
private UUID updateUserId;
|
||||||
|
|
||||||
|
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||||
private DateTime updateTime;
|
private DateTime updateTime;
|
||||||
|
|
||||||
public User setPasswordNull() {
|
public User setPasswordNull() {
|
||||||
|
@ -2,6 +2,8 @@ package com.mingliqiye.disk.util;
|
|||||||
|
|
||||||
import cn.dev33.satoken.stp.StpUtil;
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
import com.mingliqiye.disk.uuid.UUID;
|
import com.mingliqiye.disk.uuid.UUID;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
public class PanStpUtil {
|
public class PanStpUtil {
|
||||||
|
|
||||||
@ -9,7 +11,12 @@ public class PanStpUtil {
|
|||||||
return UUID.ofString((String) StpUtil.getLoginId());
|
return UUID.ofString((String) StpUtil.getLoginId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String login(UUID uuid) {
|
@Nullable
|
||||||
|
public static UUID getLoginIdDefaultNull() {
|
||||||
|
return UUID.ofString((String) StpUtil.getLoginId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String login(@NotNull UUID uuid) {
|
||||||
StpUtil.login(uuid.toUUIDString());
|
StpUtil.login(uuid.toUUIDString());
|
||||||
return StpUtil.getTokenValue();
|
return StpUtil.getTokenValue();
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
config:
|
config:
|
||||||
port: 9963
|
port: 9963
|
||||||
app-name: maven-repository
|
app-name: pan-disk
|
||||||
app-version: 1.0
|
app-version: 1.1
|
||||||
data-source:
|
data-source:
|
||||||
mysql:
|
mysql:
|
||||||
username: pan
|
username: pan
|
||||||
@ -85,6 +85,6 @@ mybatis-plus:
|
|||||||
|
|
||||||
springdoc:
|
springdoc:
|
||||||
swagger-ui:
|
swagger-ui:
|
||||||
path: /apis/swagger
|
enabled: false
|
||||||
api-docs:
|
api-docs:
|
||||||
path: /apis/swagger/api.json
|
path: /apis/swagger/api.json
|
||||||
|
@ -11,8 +11,18 @@ create table `users`
|
|||||||
roles json,
|
roles json,
|
||||||
icon mediumblob,
|
icon mediumblob,
|
||||||
admin bool default false,
|
admin bool default false,
|
||||||
creation_time timestamp(6) default current_timestamp(6) not null,
|
create_user_id binary(16) not null,
|
||||||
update_time timestamp(6) null
|
create_time timestamp(6) default now(6) not null,
|
||||||
|
update_user_id binary(16) null,
|
||||||
|
update_time timestamp(6) default now(6),
|
||||||
|
constraint foreign key (create_user_id) references users (id),
|
||||||
|
constraint foreign key (update_user_id) references users (id)
|
||||||
|
|
||||||
);
|
);
|
||||||
INSERT INTO users (id, username, password, nickname, prermissions, roles, icon, admin)
|
INSERT INTO users (id, username, password, nickname, prermissions, roles, icon, admin, create_user_id)
|
||||||
VALUES (0x689EFC204D1F11F08134DB0063E177A7, 'admin', 'admin', '管理员', '[]', '[]', null, 1);
|
VALUES (0x48126760548311F0B90C6D1CD960F6F3, 'system', 'system', '系统', '[]', '[]', null, 1,
|
||||||
|
0x48126760548311F0B90C6D1CD960F6F3);
|
||||||
|
INSERT INTO users (id, username, password, nickname, prermissions, roles, icon, admin, create_user_id)
|
||||||
|
VALUES (0x689EFC204D1F11F08134DB0063E177A7, 'admin', '$2a$10$ti.W.FpQobTaW7eTTmHYJOuBS68uPP0Ikkw33UTCJSgcNhg0AGy7a',
|
||||||
|
'管理员', '[]', '[]', null, 1,
|
||||||
|
0x48126760548311F0B90C6D1CD960F6F3);
|
||||||
|
@ -1,12 +1,20 @@
|
|||||||
{
|
{
|
||||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||||
"include": ["env.d.ts", "web-src/**/*", "web-src/**/*.vue"],
|
"include": [
|
||||||
"exclude": ["web-src/**/__tests__/*"],
|
"env.d.ts",
|
||||||
|
"web-src/**/*",
|
||||||
|
"web-src/**/*.vue",
|
||||||
|
"web-src/types/**/*.d.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"web-src/**/__tests__/*"
|
||||||
|
],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./web-src/*"]
|
"@/*": [
|
||||||
|
"./web-src/*"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,32 @@ import { defineConfig } from 'vite';
|
|||||||
import vue from '@vitejs/plugin-vue';
|
import vue from '@vitejs/plugin-vue';
|
||||||
import vueDevTools from 'vite-plugin-vue-devtools';
|
import vueDevTools from 'vite-plugin-vue-devtools';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
import AutoImport from 'unplugin-auto-import/vite';
|
||||||
|
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers';
|
||||||
|
import Components from 'unplugin-vue-components/vite';
|
||||||
|
|
||||||
// https://vite.dev/config/
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
root: path.resolve(__dirname, 'web-src'),
|
root: path.resolve(__dirname, 'web-src'),
|
||||||
plugins: [vue(), vueDevTools()],
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
vueDevTools(),
|
||||||
|
AutoImport({
|
||||||
|
imports: [
|
||||||
|
'vue',
|
||||||
|
'@vueuse/core',
|
||||||
|
'pinia',
|
||||||
|
'vue-router',
|
||||||
|
{
|
||||||
|
'naive-ui': ['useDialog', 'useMessage', 'useNotification', 'useLoadingBar'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
dts: 'types/AutoImport.d.ts',
|
||||||
|
}),
|
||||||
|
Components({
|
||||||
|
resolvers: [NaiveUiResolver()],
|
||||||
|
dts: 'types/Components.d.ts',
|
||||||
|
}),
|
||||||
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@': path.resolve(__dirname, 'web-src'),
|
'@': path.resolve(__dirname, 'web-src'),
|
||||||
@ -23,4 +44,10 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
server: {
|
||||||
|
host: '0.0.0.0',
|
||||||
|
proxy: {
|
||||||
|
'/apis': 'http://localhost:9963',
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
@ -1,85 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import { RouterLink, RouterView } from 'vue-router';
|
|
||||||
import HelloWorld from './components/HelloWorld.vue';
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<header>
|
<Index />
|
||||||
<img alt="Vue logo" class="logo" height="125" src="@/assets/icon.svg" width="125" />
|
|
||||||
|
|
||||||
<div class="wrapper">
|
|
||||||
<HelloWorld msg="You did it!" />
|
|
||||||
|
|
||||||
<nav>
|
|
||||||
<RouterLink to="/">Home</RouterLink>
|
|
||||||
<RouterLink to="/about">About</RouterLink>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<RouterView />
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<script lang="ts" setup>
|
||||||
header {
|
import Index from '@/layout/index.vue';
|
||||||
line-height: 1.5;
|
</script>
|
||||||
max-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
display: block;
|
|
||||||
margin: 0 auto 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav {
|
|
||||||
width: 100%;
|
|
||||||
font-size: 12px;
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav a.router-link-exact-active {
|
|
||||||
color: var(--color-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
nav a.router-link-exact-active:hover {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav a {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 0 1rem;
|
|
||||||
border-left: 1px solid var(--color-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
nav a:first-of-type {
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
header {
|
|
||||||
display: flex;
|
|
||||||
place-items: center;
|
|
||||||
padding-right: calc(var(--section-gap) / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
margin: 0 2rem 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
header .wrapper {
|
|
||||||
display: flex;
|
|
||||||
place-items: flex-start;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav {
|
|
||||||
text-align: left;
|
|
||||||
margin-left: -1rem;
|
|
||||||
font-size: 1rem;
|
|
||||||
|
|
||||||
padding: 1rem 0;
|
|
||||||
margin-top: 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
33
web-src/api/auth.ts
Normal file
33
web-src/api/auth.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { alovaInstance, type AlovaResponseType } from '@/plugin';
|
||||||
|
|
||||||
|
export function login(data: loginDtoBody) {
|
||||||
|
return alovaInstance.Post<AlovaResponseType<string>>('/apis/auth/login', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function woIsMe() {
|
||||||
|
return alovaInstance.Get<AlovaResponseType<UserDto>>('apis/auth/who-is-me');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function logout() {
|
||||||
|
return alovaInstance.Delete<AlovaResponseType>('apis/auth/logout');
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface loginDtoBody {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserDto {
|
||||||
|
id: string;
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
nickname: string;
|
||||||
|
prermissions: string[];
|
||||||
|
roles: string[];
|
||||||
|
icon: string;
|
||||||
|
admin: boolean;
|
||||||
|
createUserId: string;
|
||||||
|
createTime: string;
|
||||||
|
updateUserId: string;
|
||||||
|
updateTime: string;
|
||||||
|
}
|
1
web-src/api/index.ts
Normal file
1
web-src/api/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './auth';
|
10
web-src/api/system.ts
Normal file
10
web-src/api/system.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { alovaInstance, type AlovaResponseType } from '@/plugin';
|
||||||
|
|
||||||
|
export function getSysInfo() {
|
||||||
|
return alovaInstance.Get<AlovaResponseType<SysInfo>>('apis/system/info');
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SysInfo {
|
||||||
|
appName: string;
|
||||||
|
appVersion: string;
|
||||||
|
}
|
1257
web-src/assets/SwaggerDark.scss
Normal file
1257
web-src/assets/SwaggerDark.scss
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,86 +0,0 @@
|
|||||||
/* color palette from <https://github.com/vuejs/theme> */
|
|
||||||
:root {
|
|
||||||
--vt-c-white: #ffffff;
|
|
||||||
--vt-c-white-soft: #f8f8f8;
|
|
||||||
--vt-c-white-mute: #f2f2f2;
|
|
||||||
|
|
||||||
--vt-c-black: #181818;
|
|
||||||
--vt-c-black-soft: #222222;
|
|
||||||
--vt-c-black-mute: #282828;
|
|
||||||
|
|
||||||
--vt-c-indigo: #2c3e50;
|
|
||||||
|
|
||||||
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
|
|
||||||
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
|
|
||||||
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
|
|
||||||
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
|
|
||||||
|
|
||||||
--vt-c-text-light-1: var(--vt-c-indigo);
|
|
||||||
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
|
|
||||||
--vt-c-text-dark-1: var(--vt-c-white);
|
|
||||||
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* semantic color variables for this project */
|
|
||||||
:root {
|
|
||||||
--color-background: var(--vt-c-white);
|
|
||||||
--color-background-soft: var(--vt-c-white-soft);
|
|
||||||
--color-background-mute: var(--vt-c-white-mute);
|
|
||||||
|
|
||||||
--color-border: var(--vt-c-divider-light-2);
|
|
||||||
--color-border-hover: var(--vt-c-divider-light-1);
|
|
||||||
|
|
||||||
--color-heading: var(--vt-c-text-light-1);
|
|
||||||
--color-text: var(--vt-c-text-light-1);
|
|
||||||
|
|
||||||
--section-gap: 160px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
:root {
|
|
||||||
--color-background: var(--vt-c-black);
|
|
||||||
--color-background-soft: var(--vt-c-black-soft);
|
|
||||||
--color-background-mute: var(--vt-c-black-mute);
|
|
||||||
|
|
||||||
--color-border: var(--vt-c-divider-dark-2);
|
|
||||||
--color-border-hover: var(--vt-c-divider-dark-1);
|
|
||||||
|
|
||||||
--color-heading: var(--vt-c-text-dark-1);
|
|
||||||
--color-text: var(--vt-c-text-dark-2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*,
|
|
||||||
*::before,
|
|
||||||
*::after {
|
|
||||||
box-sizing: border-box;
|
|
||||||
margin: 0;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
min-height: 100vh;
|
|
||||||
color: var(--color-text);
|
|
||||||
background: var(--color-background);
|
|
||||||
transition:
|
|
||||||
color 0.5s,
|
|
||||||
background-color 0.5s;
|
|
||||||
line-height: 1.6;
|
|
||||||
font-family:
|
|
||||||
Inter,
|
|
||||||
-apple-system,
|
|
||||||
BlinkMacSystemFont,
|
|
||||||
'Segoe UI',
|
|
||||||
Roboto,
|
|
||||||
Oxygen,
|
|
||||||
Ubuntu,
|
|
||||||
Cantarell,
|
|
||||||
'Fira Sans',
|
|
||||||
'Droid Sans',
|
|
||||||
'Helvetica Neue',
|
|
||||||
sans-serif;
|
|
||||||
font-size: 15px;
|
|
||||||
text-rendering: optimizeLegibility;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
141
web-src/assets/index.scss
Normal file
141
web-src/assets/index.scss
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
@use "var";
|
||||||
|
|
||||||
|
:where(*) {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
border: 0;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
color: #3c3c3c;
|
||||||
|
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.dark {
|
||||||
|
color: #d5d5d5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.swagger-ui .info {
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-button {
|
||||||
|
background-color: #8f8f8f !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background-color: #f5f5f5 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track-piece {
|
||||||
|
background-color: #e0e0e0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
height: 50px;
|
||||||
|
background-color: #bdbdbd !important;
|
||||||
|
border: 2px solid #e0e0e0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-button:vertical:start:decrement {
|
||||||
|
background: linear-gradient(130deg, #cccccc 40%, transparent 41%),
|
||||||
|
linear-gradient(230deg, #cccccc 40%, transparent 41%),
|
||||||
|
linear-gradient(0deg, #cccccc 40%, transparent 31%);
|
||||||
|
background-color: #cccccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-button:vertical:end:increment {
|
||||||
|
background: linear-gradient(310deg, #cccccc 40%, transparent 41%),
|
||||||
|
linear-gradient(50deg, #cccccc 40%, transparent 41%),
|
||||||
|
linear-gradient(180deg, #cccccc 40%, transparent 31%);
|
||||||
|
background-color: #cccccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-button:horizontal:end:increment {
|
||||||
|
background: linear-gradient(210deg, #9e9e9e 40%, transparent 41%),
|
||||||
|
linear-gradient(330deg, #9e9e9e 40%, transparent 41%),
|
||||||
|
linear-gradient(90deg, #9e9e9e 30%, transparent 31%);
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-button:horizontal:start:decrement {
|
||||||
|
background: linear-gradient(30deg, #9e9e9e 40%, transparent 41%),
|
||||||
|
linear-gradient(150deg, #9e9e9e 40%, transparent 41%),
|
||||||
|
linear-gradient(270deg, #9e9e9e 30%, transparent 31%);
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
html.dark {
|
||||||
|
|
||||||
|
:where(body) {
|
||||||
|
background: #202020;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-button {
|
||||||
|
background-color: #3e4346 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background-color: #646464 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track-piece {
|
||||||
|
background-color: #3e4346 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
height: 50px;
|
||||||
|
background-color: #242424 !important;
|
||||||
|
border: 2px solid #3e4346 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-button:vertical:start:decrement {
|
||||||
|
background: linear-gradient(130deg, #696969 40%, rgba(255, 0, 0, 0) 41%),
|
||||||
|
linear-gradient(230deg, #696969 40%, rgba(0, 0, 0, 0) 41%),
|
||||||
|
linear-gradient(0deg, #696969 40%, rgba(0, 0, 0, 0) 31%);
|
||||||
|
background-color: #b6b6b6;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-button:vertical:end:increment {
|
||||||
|
background: linear-gradient(310deg, #696969 40%, rgba(0, 0, 0, 0) 41%),
|
||||||
|
linear-gradient(50deg, #696969 40%, rgba(0, 0, 0, 0) 41%),
|
||||||
|
linear-gradient(180deg, #696969 40%, rgba(0, 0, 0, 0) 31%);
|
||||||
|
background-color: #b6b6b6;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-button:horizontal:end:increment {
|
||||||
|
background: linear-gradient(210deg, #696969 40%, rgba(0, 0, 0, 0) 41%),
|
||||||
|
linear-gradient(330deg, #696969 40%, rgba(0, 0, 0, 0) 41%),
|
||||||
|
linear-gradient(90deg, #696969 30%, rgba(0, 0, 0, 0) 31%);
|
||||||
|
background-color: #b6b6b6;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-button:horizontal:start:decrement {
|
||||||
|
background: linear-gradient(30deg, #696969 40%, rgba(0, 0, 0, 0) 41%),
|
||||||
|
linear-gradient(150deg, #696969 40%, rgba(0, 0, 0, 0) 41%),
|
||||||
|
linear-gradient(270deg, #696969 30%, rgba(0, 0, 0, 0) 31%);
|
||||||
|
background-color: #b6b6b6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.n-drawer-mask {
|
||||||
|
backdrop-filter: blur(2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.n-message {
|
||||||
|
padding: 8px;
|
||||||
|
}
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
@ -1,35 +0,0 @@
|
|||||||
@import './base.css';
|
|
||||||
|
|
||||||
#app {
|
|
||||||
max-width: 1280px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 2rem;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
a,
|
|
||||||
.green {
|
|
||||||
text-decoration: none;
|
|
||||||
color: hsla(160, 100%, 37%, 1);
|
|
||||||
transition: 0.4s;
|
|
||||||
padding: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (hover: hover) {
|
|
||||||
a:hover {
|
|
||||||
background-color: hsla(160, 100%, 37%, 0.2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
body {
|
|
||||||
display: flex;
|
|
||||||
place-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#app {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
padding: 0 2rem;
|
|
||||||
}
|
|
||||||
}
|
|
16
web-src/assets/var.scss
Normal file
16
web-src/assets/var.scss
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
*[n-c] {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
*[c-n] {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
*[c-c] {
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
*[f] {
|
||||||
|
display: flex;
|
||||||
|
}
|
@ -1,43 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
defineProps<{
|
|
||||||
msg: string;
|
|
||||||
}>();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="greetings">
|
|
||||||
<h1 class="green">{{ msg }}</h1>
|
|
||||||
<h3>
|
|
||||||
You’ve successfully created a project with
|
|
||||||
<a href="https://vite.dev/" target="_blank" rel="noopener">Vite</a>
|
|
||||||
+
|
|
||||||
<a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>
|
|
||||||
. What's next?
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
h1 {
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 2.6rem;
|
|
||||||
position: relative;
|
|
||||||
top: -10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.greetings h1,
|
|
||||||
.greetings h3 {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
.greetings h1,
|
|
||||||
.greetings h3 {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
41
web-src/components/Icon.vue
Normal file
41
web-src/components/Icon.vue
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<template>
|
||||||
|
<div class="iconstyle">
|
||||||
|
<Icon :icon="icon" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { Icon } from '@iconify/vue';
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
icon: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: 'inherit',
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: '24px',
|
||||||
|
},
|
||||||
|
bSize: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: '24px',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.iconstyle {
|
||||||
|
width: v-bind(bSize);
|
||||||
|
height: v-bind(bSize);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: v-bind(size);
|
||||||
|
color: v-bind(color);
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,97 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import WelcomeItem from './WelcomeItem.vue';
|
|
||||||
import DocumentationIcon from './icons/IconDocumentation.vue';
|
|
||||||
import ToolingIcon from './icons/IconTooling.vue';
|
|
||||||
import EcosystemIcon from './icons/IconEcosystem.vue';
|
|
||||||
import CommunityIcon from './icons/IconCommunity.vue';
|
|
||||||
import SupportIcon from './icons/IconSupport.vue';
|
|
||||||
|
|
||||||
const openReadmeInEditor = () => fetch('/__open-in-editor?file=README.md');
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<WelcomeItem>
|
|
||||||
<template #icon>
|
|
||||||
<DocumentationIcon />
|
|
||||||
</template>
|
|
||||||
<template #heading>Documentation</template>
|
|
||||||
|
|
||||||
Vue’s
|
|
||||||
<a href="https://vuejs.org/" target="_blank" rel="noopener">official documentation</a>
|
|
||||||
provides you with all information you need to get started.
|
|
||||||
</WelcomeItem>
|
|
||||||
|
|
||||||
<WelcomeItem>
|
|
||||||
<template #icon>
|
|
||||||
<ToolingIcon />
|
|
||||||
</template>
|
|
||||||
<template #heading>Tooling</template>
|
|
||||||
|
|
||||||
This project is served and bundled with
|
|
||||||
<a href="https://vite.dev/guide/features.html" target="_blank" rel="noopener">Vite</a>
|
|
||||||
. The recommended IDE setup is
|
|
||||||
<a href="https://code.visualstudio.com/" target="_blank" rel="noopener">VSCode</a>
|
|
||||||
+
|
|
||||||
<a href="https://github.com/vuejs/language-tools" target="_blank" rel="noopener">Vue - Official</a>
|
|
||||||
. If you need to test your components and web pages, check out
|
|
||||||
<a href="https://vitest.dev/" target="_blank" rel="noopener">Vitest</a>
|
|
||||||
and
|
|
||||||
<a href="https://www.cypress.io/" target="_blank" rel="noopener">Cypress</a>
|
|
||||||
/
|
|
||||||
<a href="https://playwright.dev/" target="_blank" rel="noopener">Playwright</a>
|
|
||||||
.
|
|
||||||
|
|
||||||
<br />
|
|
||||||
|
|
||||||
More instructions are available in
|
|
||||||
<a href="javascript:void(0)" @click="openReadmeInEditor"><code>README.md</code></a>
|
|
||||||
.
|
|
||||||
</WelcomeItem>
|
|
||||||
|
|
||||||
<WelcomeItem>
|
|
||||||
<template #icon>
|
|
||||||
<EcosystemIcon />
|
|
||||||
</template>
|
|
||||||
<template #heading>Ecosystem</template>
|
|
||||||
|
|
||||||
Get official tools and libraries for your project:
|
|
||||||
<a href="https://pinia.vuejs.org/" target="_blank" rel="noopener">Pinia</a>
|
|
||||||
,
|
|
||||||
<a href="https://router.vuejs.org/" target="_blank" rel="noopener">Vue Router</a>
|
|
||||||
,
|
|
||||||
<a href="https://test-utils.vuejs.org/" target="_blank" rel="noopener">Vue Test Utils</a>
|
|
||||||
, and
|
|
||||||
<a href="https://github.com/vuejs/devtools" target="_blank" rel="noopener">Vue Dev Tools</a>
|
|
||||||
. If you need more resources, we suggest paying
|
|
||||||
<a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">Awesome Vue</a>
|
|
||||||
a visit.
|
|
||||||
</WelcomeItem>
|
|
||||||
|
|
||||||
<WelcomeItem>
|
|
||||||
<template #icon>
|
|
||||||
<CommunityIcon />
|
|
||||||
</template>
|
|
||||||
<template #heading>Community</template>
|
|
||||||
|
|
||||||
Got stuck? Ask your question on
|
|
||||||
<a href="https://chat.vuejs.org" target="_blank" rel="noopener">Vue Land</a>
|
|
||||||
(our official Discord server), or
|
|
||||||
<a href="https://stackoverflow.com/questions/tagged/vue.js" target="_blank" rel="noopener">StackOverflow</a>
|
|
||||||
. You should also follow the official
|
|
||||||
<a href="https://bsky.app/profile/vuejs.org" target="_blank" rel="noopener">@vuejs.org</a>
|
|
||||||
Bluesky account or the
|
|
||||||
<a href="https://x.com/vuejs" target="_blank" rel="noopener">@vuejs</a>
|
|
||||||
X account for latest news in the Vue world.
|
|
||||||
</WelcomeItem>
|
|
||||||
|
|
||||||
<WelcomeItem>
|
|
||||||
<template #icon>
|
|
||||||
<SupportIcon />
|
|
||||||
</template>
|
|
||||||
<template #heading>Support Vue</template>
|
|
||||||
|
|
||||||
As an independent project, Vue relies on community backing for its sustainability. You can help us by
|
|
||||||
<a href="https://vuejs.org/sponsor/" target="_blank" rel="noopener">becoming a sponsor</a>
|
|
||||||
.
|
|
||||||
</WelcomeItem>
|
|
||||||
</template>
|
|
@ -1,87 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="item">
|
|
||||||
<i>
|
|
||||||
<slot name="icon"></slot>
|
|
||||||
</i>
|
|
||||||
<div class="details">
|
|
||||||
<h3>
|
|
||||||
<slot name="heading"></slot>
|
|
||||||
</h3>
|
|
||||||
<slot></slot>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.item {
|
|
||||||
margin-top: 2rem;
|
|
||||||
display: flex;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.details {
|
|
||||||
flex: 1;
|
|
||||||
margin-left: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
display: flex;
|
|
||||||
place-items: center;
|
|
||||||
place-content: center;
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
|
|
||||||
color: var(--color-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
font-weight: 500;
|
|
||||||
margin-bottom: 0.4rem;
|
|
||||||
color: var(--color-heading);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
.item {
|
|
||||||
margin-top: 0;
|
|
||||||
padding: 0.4rem 0 1rem calc(var(--section-gap) / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
top: calc(50% - 25px);
|
|
||||||
left: -26px;
|
|
||||||
position: absolute;
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
background: var(--color-background);
|
|
||||||
border-radius: 8px;
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item:before {
|
|
||||||
content: ' ';
|
|
||||||
border-left: 1px solid var(--color-border);
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
bottom: calc(50% + 25px);
|
|
||||||
height: calc(50% - 25px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.item:after {
|
|
||||||
content: ' ';
|
|
||||||
border-left: 1px solid var(--color-border);
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: calc(50% + 25px);
|
|
||||||
height: calc(50% - 25px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.item:first-of-type:before {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item:last-of-type:after {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,6 +0,0 @@
|
|||||||
<template>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
|
|
||||||
<path
|
|
||||||
d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z" />
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
@ -1,6 +0,0 @@
|
|||||||
<template>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor">
|
|
||||||
<path
|
|
||||||
d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z" />
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
@ -1,6 +0,0 @@
|
|||||||
<template>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" fill="currentColor">
|
|
||||||
<path
|
|
||||||
d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z" />
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
@ -1,6 +0,0 @@
|
|||||||
<template>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
|
|
||||||
<path
|
|
||||||
d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z" />
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
@ -1,17 +0,0 @@
|
|||||||
<!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license-->
|
|
||||||
<template>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
||||||
aria-hidden="true"
|
|
||||||
role="img"
|
|
||||||
class="iconify iconify--mdi"
|
|
||||||
width="24"
|
|
||||||
height="24"
|
|
||||||
preserveAspectRatio="xMidYMid meet"
|
|
||||||
viewBox="0 0 24 24">
|
|
||||||
<path
|
|
||||||
d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z"
|
|
||||||
fill="currentColor"></path>
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
@ -2,12 +2,12 @@
|
|||||||
<html lang="">
|
<html lang="">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<link href="./assets/icon.svg" rel="icon">
|
<link href="./assets/index.svg" rel="icon">
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
<meta content="width=device-width, initial-scale=0.8" name="viewport">
|
||||||
<title>Vite App</title>
|
<title>Vite App</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<main id="app"></main>
|
||||||
<script src="/main.ts" type="module"></script>
|
|
||||||
</body>
|
</body>
|
||||||
|
<script src="/main.ts" type="module"></script>
|
||||||
</html>
|
</html>
|
||||||
|
175
web-src/layout/Head.vue
Normal file
175
web-src/layout/Head.vue
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
<template>
|
||||||
|
<nav class="navmain">
|
||||||
|
<router-link :to="{ name: 'home' }" c-c f style="color: inherit; height: 100%; text-decoration: none">
|
||||||
|
<img height="50" src="@/assets/index.svg" width="50" />
|
||||||
|
<div>
|
||||||
|
<label style="font-size: 20px; margin-left: 10px; cursor: pointer">{{ useSettingStore.appName }}</label>
|
||||||
|
<label style="margin-left: 5px; font-size: 12px; cursor: pointer">V{{ useSettingStore.appVersion }}</label>
|
||||||
|
</div>
|
||||||
|
</router-link>
|
||||||
|
<div style="flex-grow: 1"></div>
|
||||||
|
<div c-c f style="height: 100%">
|
||||||
|
<div
|
||||||
|
v-bind:class="{
|
||||||
|
'router-item-atc': router.currentRoute.value.name === 'home',
|
||||||
|
'router-item': true,
|
||||||
|
}"
|
||||||
|
@click="router.push({ name: 'home' })">
|
||||||
|
<Icon icon="material-symbols:home" />
|
||||||
|
主页
|
||||||
|
<div class="after"></div>
|
||||||
|
</div>
|
||||||
|
<div class="router-item">
|
||||||
|
<Icon icon="material-symbols:folder-rounded" />
|
||||||
|
文件
|
||||||
|
<div class="after"></div>
|
||||||
|
</div>
|
||||||
|
<div class="router-item">
|
||||||
|
<Icon icon="material-symbols:person-rounded" />
|
||||||
|
用户
|
||||||
|
<div class="after"></div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-bind:class="{
|
||||||
|
'router-item-atc': router.currentRoute.value.name === 'api',
|
||||||
|
'router-item': true,
|
||||||
|
}"
|
||||||
|
@click="router.push({ name: 'api' })">
|
||||||
|
<icon icon="mdi:api" />
|
||||||
|
API
|
||||||
|
<div class="after"></div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-bind:class="{
|
||||||
|
'router-item': true,
|
||||||
|
}"
|
||||||
|
@click="activeSetting.on()">
|
||||||
|
<Icon icon="material-symbols:settings" />
|
||||||
|
设置
|
||||||
|
<div class="after"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<nav class="nav">
|
||||||
|
<n-drawer v-model:show="activeSetting.value" placement="right">
|
||||||
|
<n-drawer-content title="设置">
|
||||||
|
<div c-c f>
|
||||||
|
<n-radio-group v-model:value="useSettingStore.themeMode" name="team">
|
||||||
|
<n-radio-button value="light">亮色</n-radio-button>
|
||||||
|
<n-radio-button value="dark">暗色</n-radio-button>
|
||||||
|
<n-radio-button value="auto">自动</n-radio-button>
|
||||||
|
</n-radio-group>
|
||||||
|
</div>
|
||||||
|
<n-divider>主题配置</n-divider>
|
||||||
|
</n-drawer-content>
|
||||||
|
</n-drawer>
|
||||||
|
</nav>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import Icon from '@/components/Icon.vue';
|
||||||
|
import { UseBoolRef } from '@/util';
|
||||||
|
import { router, UseAuthStore, UseSettingStore } from '@/plugin';
|
||||||
|
import { getSysInfo } from '@/api/system.ts';
|
||||||
|
|
||||||
|
const useSettingStore = UseSettingStore();
|
||||||
|
const activeSetting = UseBoolRef();
|
||||||
|
const useAuthStore = UseAuthStore();
|
||||||
|
getSysInfo().then((r) => {
|
||||||
|
const data = r.json().data;
|
||||||
|
useSettingStore.appName = data.appName;
|
||||||
|
useSettingStore.appVersion = data.appVersion;
|
||||||
|
});
|
||||||
|
useAuthStore.woIsMe();
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
html.dark {
|
||||||
|
.router-item-atc {
|
||||||
|
--atc-color: #66ffad;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
--atc-color: #66b3ff !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.router-item {
|
||||||
|
&:hover {
|
||||||
|
--atc-color: #965522;
|
||||||
|
background-color: rgba(255, 255, 255, 0.15);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.router-item-atc {
|
||||||
|
--atc-color: #64a8fc;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
--atc-color: #42e3e8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:where(.router-item) {
|
||||||
|
cursor: pointer;
|
||||||
|
min-width: 50px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
--atc-color: rgba(255, 255, 255, 0);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
--atc-color: #ffd56f;
|
||||||
|
background-color: rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.after {
|
||||||
|
width: 100%;
|
||||||
|
height: 2px;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: var(--atc-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nvr-button {
|
||||||
|
border-radius: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nvr-button:hover {
|
||||||
|
background-color: #d8d8d8;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.dark {
|
||||||
|
.nvr-button:hover {
|
||||||
|
background-color: #404040;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav {
|
||||||
|
height: 66px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navmain {
|
||||||
|
position: fixed;
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
height: 65px;
|
||||||
|
width: calc(100% - 40px);
|
||||||
|
z-index: 1;
|
||||||
|
padding: 0 20px;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
background-color: rgba(221, 221, 221, 0.6);
|
||||||
|
border-bottom: #d3d3d3 1px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.dark {
|
||||||
|
.navmain {
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
border-bottom: #424242 1px solid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
15
web-src/layout/Router.vue
Normal file
15
web-src/layout/Router.vue
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<template>
|
||||||
|
<Head />
|
||||||
|
<div style="margin: 10px">
|
||||||
|
<RouterView />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { RouterView } from 'vue-router';
|
||||||
|
import { useMessage } from 'naive-ui';
|
||||||
|
import Head from '@/layout/Head.vue';
|
||||||
|
import { setMessage } from '@/plugin';
|
||||||
|
|
||||||
|
setMessage(useMessage());
|
||||||
|
</script>
|
||||||
|
<style scoped></style>
|
15
web-src/layout/index.vue
Normal file
15
web-src/layout/index.vue
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<template>
|
||||||
|
<n-config-provider :theme="useSettingStore.theme === 'dark' ? darkTheme : lightTheme" abstract>
|
||||||
|
<n-message-provider>
|
||||||
|
<RouterView />
|
||||||
|
</n-message-provider>
|
||||||
|
</n-config-provider>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { UseSettingStore } from '@/plugin';
|
||||||
|
import { RouterView } from 'vue-router';
|
||||||
|
import { darkTheme, lightTheme } from 'naive-ui';
|
||||||
|
|
||||||
|
const useSettingStore = UseSettingStore();
|
||||||
|
</script>
|
||||||
|
<style scoped></style>
|
@ -1,14 +1,13 @@
|
|||||||
import './assets/main.css';
|
import './assets/index.scss';
|
||||||
|
|
||||||
import { createApp } from 'vue';
|
import { createApp } from 'vue';
|
||||||
import { createPinia } from 'pinia';
|
|
||||||
|
|
||||||
import App from './App.vue';
|
import App from './App.vue';
|
||||||
import router from './router';
|
import { pinia, router } from './plugin';
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
|
||||||
app.use(createPinia());
|
app.use(pinia);
|
||||||
app.use(router);
|
app.use(router);
|
||||||
|
|
||||||
app.mount('#app');
|
app.mount('#app');
|
||||||
|
47
web-src/plugin/alova/index.ts
Normal file
47
web-src/plugin/alova/index.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { createAlova } from 'alova';
|
||||||
|
import adapterFetch from 'alova/fetch';
|
||||||
|
import { message, UseAuthStore } from '@/plugin';
|
||||||
|
|
||||||
|
export * from './type';
|
||||||
|
|
||||||
|
export const alovaInstance = createAlova({
|
||||||
|
requestAdapter: adapterFetch(),
|
||||||
|
cacheFor: { expire: 0 },
|
||||||
|
responded: async (response) => {
|
||||||
|
const text = await response.text();
|
||||||
|
const data = {
|
||||||
|
response: () => response,
|
||||||
|
text: () => text,
|
||||||
|
json: () => JSON.parse(text),
|
||||||
|
headers: () => {
|
||||||
|
const headers: { [key: string]: string } = {};
|
||||||
|
response.headers.forEach((k, v) => {
|
||||||
|
headers[k] = v;
|
||||||
|
});
|
||||||
|
return headers;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (response.status !== 200) {
|
||||||
|
const messages = JSON.parse(text).message;
|
||||||
|
if (response.status === 500) message.error(messages);
|
||||||
|
throw new RequestError(data, messages);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
beforeRequest(method) {
|
||||||
|
const useAuthStore = UseAuthStore();
|
||||||
|
if (useAuthStore.isLogin) {
|
||||||
|
method.config.headers.Authorization = `Bearer ${useAuthStore.token}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export class RequestError extends Error {
|
||||||
|
public data: any;
|
||||||
|
|
||||||
|
constructor(data: any, message: string) {
|
||||||
|
super(message);
|
||||||
|
this.data = data;
|
||||||
|
this.name = 'RequestError';
|
||||||
|
}
|
||||||
|
}
|
13
web-src/plugin/alova/type.ts
Normal file
13
web-src/plugin/alova/type.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
export interface AlovaResponseType<K = any> {
|
||||||
|
json: () => ResponseServer<K>;
|
||||||
|
text: () => string;
|
||||||
|
response: () => Response;
|
||||||
|
headers: () => { [key: string]: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ResponseServer<K> {
|
||||||
|
code: number;
|
||||||
|
message: string;
|
||||||
|
data: K;
|
||||||
|
dateTime: string;
|
||||||
|
}
|
11
web-src/plugin/index.ts
Normal file
11
web-src/plugin/index.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import type { MessageApiInjection } from 'naive-ui/es/message/src/MessageProvider';
|
||||||
|
|
||||||
|
export * from './router';
|
||||||
|
export * from './stores';
|
||||||
|
export * from './alova';
|
||||||
|
|
||||||
|
export let message: MessageApiInjection;
|
||||||
|
|
||||||
|
export function setMessage(messages: MessageApiInjection) {
|
||||||
|
message = messages;
|
||||||
|
}
|
25
web-src/plugin/router/index.ts
Normal file
25
web-src/plugin/router/index.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { createRouter, createWebHistory } from 'vue-router';
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
component: () => import('@/layout/Router.vue'),
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
name: 'home',
|
||||||
|
component: () => import('@/views/HomeView.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/swagger',
|
||||||
|
name: 'api',
|
||||||
|
component: () => import('@/views/SwaggerView.vue'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
export { router };
|
46
web-src/plugin/stores/auth.ts
Normal file
46
web-src/plugin/stores/auth.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { defineStore } from 'pinia';
|
||||||
|
import { login, woIsMe } from '@/api';
|
||||||
|
import { LocalStorageApi } from '@/util';
|
||||||
|
|
||||||
|
export const UseAuthStore = defineStore('auth', {
|
||||||
|
state: () => ({
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
isAdmin: false,
|
||||||
|
isLogin: false,
|
||||||
|
userId: '',
|
||||||
|
prermissions: Array<string>(),
|
||||||
|
roles: Array<string>(),
|
||||||
|
token: '',
|
||||||
|
isRemberMe: false,
|
||||||
|
}),
|
||||||
|
actions: {
|
||||||
|
login(username: string, password: string) {
|
||||||
|
login({ username, password }).then((r) => {
|
||||||
|
if (this.isRemberMe) {
|
||||||
|
this.username = username;
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
this.token = r.json().data;
|
||||||
|
this.isLogin = true;
|
||||||
|
this.woIsMe();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
woIsMe() {
|
||||||
|
if (this.isLogin)
|
||||||
|
woIsMe()
|
||||||
|
.then((r) => {
|
||||||
|
const data = r.json().data;
|
||||||
|
this.username = data.username;
|
||||||
|
this.isAdmin = data.admin;
|
||||||
|
this.prermissions = data.prermissions;
|
||||||
|
this.roles = data.roles;
|
||||||
|
this.userId = data.id;
|
||||||
|
})
|
||||||
|
.catch(() => this.$reset());
|
||||||
|
},
|
||||||
|
},
|
||||||
|
persist: {
|
||||||
|
storage: LocalStorageApi.StorageApi,
|
||||||
|
},
|
||||||
|
});
|
8
web-src/plugin/stores/index.ts
Normal file
8
web-src/plugin/stores/index.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { createPinia } from 'pinia';
|
||||||
|
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
|
||||||
|
|
||||||
|
export * from './setting';
|
||||||
|
export * from './auth.ts';
|
||||||
|
const pinia = createPinia();
|
||||||
|
pinia.use(piniaPluginPersistedstate);
|
||||||
|
export { pinia };
|
19
web-src/plugin/stores/setting.ts
Normal file
19
web-src/plugin/stores/setting.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { defineStore } from 'pinia';
|
||||||
|
|
||||||
|
import { useColorMode } from '@vueuse/core';
|
||||||
|
import { LocalStorageApi } from '@/util/Cookies.ts';
|
||||||
|
|
||||||
|
export const UseSettingStore = defineStore('setting', {
|
||||||
|
state: () => {
|
||||||
|
const { store, state } = useColorMode();
|
||||||
|
return {
|
||||||
|
themeMode: store,
|
||||||
|
theme: state,
|
||||||
|
appName: '',
|
||||||
|
appVersion: '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
persist: {
|
||||||
|
storage: LocalStorageApi.StorageApi,
|
||||||
|
},
|
||||||
|
});
|
@ -1,23 +0,0 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router';
|
|
||||||
import HomeView from '../views/HomeView.vue';
|
|
||||||
|
|
||||||
const router = createRouter({
|
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
path: '/',
|
|
||||||
name: 'home',
|
|
||||||
component: HomeView,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/about',
|
|
||||||
name: 'about',
|
|
||||||
// route level code-splitting
|
|
||||||
// this generates a separate chunk (About.[hash].js) for this route
|
|
||||||
// which is lazy-loaded when the route is visited.
|
|
||||||
component: () => import('../views/AboutView.vue'),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
export default router;
|
|
@ -1,12 +0,0 @@
|
|||||||
import { ref, computed } from 'vue';
|
|
||||||
import { defineStore } from 'pinia';
|
|
||||||
|
|
||||||
export const useCounterStore = defineStore('counter', () => {
|
|
||||||
const count = ref(0);
|
|
||||||
const doubleCount = computed(() => count.value * 2);
|
|
||||||
function increment() {
|
|
||||||
count.value++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { count, doubleCount, increment };
|
|
||||||
});
|
|
57
web-src/util/Cookies.ts
Normal file
57
web-src/util/Cookies.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import Cookies from 'js-cookie';
|
||||||
|
import type { StorageLike } from 'pinia-plugin-persistedstate';
|
||||||
|
import { Base64 } from 'js-base64';
|
||||||
|
|
||||||
|
export const LocalStorageApi: {
|
||||||
|
Get: (key: string) => string | null;
|
||||||
|
Set: (key: string, value: string) => void;
|
||||||
|
StorageApi: StorageLike;
|
||||||
|
} = {
|
||||||
|
Get(key: string): string | null {
|
||||||
|
const data = localStorage.getItem(key);
|
||||||
|
if (data) {
|
||||||
|
return Base64.decode(data);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
Set(key: string, value: string) {
|
||||||
|
localStorage.setItem(key, Base64.encode(value));
|
||||||
|
},
|
||||||
|
StorageApi: {
|
||||||
|
getItem(key: string) {
|
||||||
|
return LocalStorageApi.Get(key);
|
||||||
|
},
|
||||||
|
setItem(key: string, value: string) {
|
||||||
|
LocalStorageApi.Set(key, value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const CookiesApi: {
|
||||||
|
Get: (key: string) => string | null;
|
||||||
|
Set: (key: string, value: string) => void;
|
||||||
|
StorageApi: StorageLike;
|
||||||
|
} = {
|
||||||
|
Get(key: string): string | null {
|
||||||
|
const data = Cookies.get(key);
|
||||||
|
if (data) {
|
||||||
|
return Base64.decode(data);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
Set(key: string, value: string) {
|
||||||
|
Cookies.set(key, Base64.encode(value), {
|
||||||
|
expires: 265,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
StorageApi: {
|
||||||
|
getItem(key: string) {
|
||||||
|
return CookiesApi.Get(key);
|
||||||
|
},
|
||||||
|
setItem(key: string, value: string) {
|
||||||
|
CookiesApi.Set(key, value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CookiesApi;
|
25
web-src/util/UseBoolRef.ts
Normal file
25
web-src/util/UseBoolRef.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
function UseBoolRef(bool?: boolean) {
|
||||||
|
const data = ref(false);
|
||||||
|
if (bool) {
|
||||||
|
data.value = bool;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
get value() {
|
||||||
|
return data.value;
|
||||||
|
},
|
||||||
|
set value(v) {
|
||||||
|
data.value = v;
|
||||||
|
},
|
||||||
|
refdata: data,
|
||||||
|
on() {
|
||||||
|
data.value = true;
|
||||||
|
},
|
||||||
|
off() {
|
||||||
|
data.value = false;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export { UseBoolRef };
|
2
web-src/util/index.ts
Normal file
2
web-src/util/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './UseBoolRef';
|
||||||
|
export * from './Cookies';
|
@ -1,15 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="about">
|
|
||||||
<h1>This is an about page</h1>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
.about {
|
|
||||||
min-height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,9 +1,10 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import TheWelcome from '../components/TheWelcome.vue';
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main>
|
<NButton @click="useAuthStore.login('admin', 'admin')">登录</NButton>
|
||||||
<TheWelcome />
|
|
||||||
</main>
|
|
||||||
</template>
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { UseAuthStore } from '@/plugin';
|
||||||
|
|
||||||
|
const useAuthStore = UseAuthStore();
|
||||||
|
console.log(useAuthStore);
|
||||||
|
</script>
|
||||||
|
<style scoped></style>
|
||||||
|
18
web-src/views/SwaggerView.vue
Normal file
18
web-src/views/SwaggerView.vue
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<template>
|
||||||
|
<div id="swaggerContainer"></div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import 'swagger-ui-dist/swagger-ui.css';
|
||||||
|
import '@/assets/SwaggerDark.scss';
|
||||||
|
import { onMounted } from 'vue';
|
||||||
|
import { SwaggerUIBundle } from 'swagger-ui-dist';
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
SwaggerUIBundle({
|
||||||
|
dom_id: '#swaggerContainer',
|
||||||
|
url: '/apis/swagger/api.json',
|
||||||
|
presets: [SwaggerUIBundle.presets.apis, SwaggerUIBundle.SwaggerUIStandalonePreset],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style scoped></style>
|
Loading…
x
Reference in New Issue
Block a user