Compare commits

..

No commits in common. "master" and "Auto-Releases-0.1-384fbf3cde" have entirely different histories.

77 changed files with 626 additions and 2914 deletions

View File

@ -1,4 +1,4 @@
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}]
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl,java}]
charset = utf-8
indent_size = 2
indent_style = space

7
.gitignore vendored
View File

@ -16,9 +16,9 @@ build/
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
pnpm-workspace.yaml
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
@ -39,8 +39,5 @@ node_modules
dist
pnpm-lock.yaml
src/main/resources/html
src/main/resources/html/**/*
log
web-src/types
.idea

View File

@ -1,7 +1,7 @@
config:
port: 9963
app-name: pan-disk
app-version: 1.1
app-name: maven-repository
app-version: 1.0
data-source:
mysql:
username: pan

View File

@ -1,55 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://example.com/language-config.schema.json",
"title": "Language Configuration",
"description": "Schema for validating language configuration files",
"type": "object",
"properties": {
"$schema": {
"type": "string"
},
"iconsUrl": {
"type": "string"
},
"languages": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"title": {
"type": "string",
"description": "显示的语言名",
"pattern": "^[\\p{L}\\p{N}\\s-]+$"
},
"id": {
"type": "string",
"description": "语言ID",
"pattern": "^[a-z]{2}-[A-Z]{2}$"
},
"file": {
"type": "string",
"description": "语言文件在language文件夹的位置",
"pattern": "^[a-z]{2}-[A-Z]{2}\\.json$"
},
"icon": {
"type": "string",
"description": "语言的图标",
"pattern": "^.*:.*$"
}
},
"required": [
"title",
"id",
"file",
"icon"
],
"additionalProperties": false
}
}
},
"required": [
"languages"
],
"additionalProperties": false
}

View File

@ -9,7 +9,6 @@
"build-jar-auto": "run-p \"build-only\" && gradle -Dorg.gradle.java.home=/opt/jdk/21.0.7/ build-jar",
"build": "run-p type-check \"build-only {@}\" --",
"preview": "vite preview",
"build-preview": "run-p build-only && vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --build",
"lint:oxlint": "oxlint . --fix -D correctness --ignore-path .gitignore",
@ -18,45 +17,27 @@
"format": "prettier --write \"**/*.{js,ts,jsx,tsx,cjs,cts,mjs,mts,vue,astro,java}\""
},
"dependencies": {
"@iconify/vue": "^5.0.0",
"@intlify/vue-i18n-core": "^11.1.7",
"@types/vue-router": "^2.0.0",
"@vueuse/core": "^13.4.0",
"naive-ui": "^2.42.0",
"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-i18n": "11",
"vue-router": "^4.5.1"
},
"devDependencies": {
"@tsconfig/node22": "^22.0.2",
"@types/js-cookie": "^3.0.6",
"@types/node": "^22.15.32",
"@types/swagger-ui-dist": "^3.30.6",
"@vitejs/plugin-vue": "^6.0.0",
"@vue/eslint-config-prettier": "^10.2.0",
"@vue/eslint-config-typescript": "^14.5.1",
"@vue/tsconfig": "^0.7.0",
"alova": "^3.3.3",
"eslint": "^9.29.0",
"eslint-plugin-oxlint": "~1.1.0",
"eslint-plugin-vue": "~10.2.0",
"highlight.js": "^11.11.1",
"jiti": "^2.4.2",
"js-base64": "^3.7.7",
"js-cookie": "^3.0.5",
"npm-run-all2": "^8.0.4",
"openapi-types": "^12.1.3",
"oxlint": "~1.1.0",
"pinia-plugin-persistedstate": "^4.4.0",
"prettier": "3.5.3",
"prettier-plugin-java": "^2.6.8",
"sass-embedded": "^1.89.2",
"typescript": "~5.8.0",
"vite": "~6.0.0",
"vite": "npm:rolldown-vite@latest",
"vite-plugin-vue-devtools": "^7.7.7",
"vue-tsc": "^2.2.10"
}

View File

@ -1,49 +0,0 @@
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());
}
}
}

View File

@ -1,6 +1,5 @@
package com.mingliqiye.disk.config;
import com.mingliqiye.disk.configuration.Config;
import io.swagger.v3.oas.annotations.ExternalDocumentation;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.enums.SecuritySchemeIn;
@ -10,14 +9,9 @@ import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.servers.Server;
import java.util.ArrayList;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Slf4j
@OpenAPIDefinition(
externalDocs = @ExternalDocumentation(
description = "@git.mingliqiye",
@ -35,29 +29,23 @@ import org.springframework.context.annotation.Configuration;
@Configuration
public class SpringDocConfig {
private final Config config;
public SpringDocConfig(Config config) {
this.config = config;
}
@Bean
public OpenAPI openAPI() {
List<Server> servers = new ArrayList<>();
servers.add(new Server().description("当前网页").url("/"));
return new OpenAPI().info(this.getApiInfo()).servers(servers);
return new OpenAPI()
// 配置接口文档基本信息
.info(this.getApiInfo());
}
private Info getApiInfo() {
return new Info()
.title(config.getAppName())
.version(config.getAppVersion())
.description("SpringBoot3 %s-V%s Swagger3 ApiDoc".formatted(config.getAppName(), config.getAppVersion()))
.title("pan-disk")
.description("SpringBoot3 pan-disk Swagger3 ApiDoc")
.contact(
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"))
.summary("ApiDoc")
.termsOfService("https://pan.mingliqiye.com/");
.termsOfService("https://pan.mingliqiye.com/")
.version("1.0");
}
}

View File

@ -1,42 +0,0 @@
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();
}
}

View File

@ -4,7 +4,7 @@ import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.stp.StpUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.mingliqiye.disk.dto.auth.Login;
import com.mingliqiye.disk.exception.InternalServerException;
import com.mingliqiye.disk.exception.ExceptionCode;
import com.mingliqiye.disk.http.Respose;
import com.mingliqiye.disk.mappers.UserMapper;
import com.mingliqiye.disk.model.User;
@ -36,7 +36,7 @@ public class AuthController {
if (user != null && BCrypt.checkpw(loginBody.getPassword(), user.getPassword())) {
return Respose.builder(PanStpUtil.login(user.getId()));
}
throw new InternalServerException("用户名或密码错误");
return Respose.error(String.class, ExceptionCode.ERROR_INTERNAL_SERVER, "用户名或密码错误");
}
@Operation(summary = "获取当前登陆用户的信息")
@ -47,8 +47,8 @@ public class AuthController {
}
@Operation(summary = "登出", security = @SecurityRequirement(name = "Authorization-Bearer-Token"))
@SaCheckLogin
@DeleteMapping("/logout")
@SaCheckLogin
public Respose<Object> logout() {
StpUtil.logout();
return Respose.builder();

View File

@ -1,6 +1,5 @@
package com.mingliqiye.disk.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.io.InputStream;
import org.springframework.http.ResponseEntity;
@ -13,8 +12,8 @@ import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBo
@RequestMapping
@Tag(name = "前端路由", description = "统一匹配路径指向VueRouter")
public class IndexController {
@Operation(summary = "VueRouter 主路由")
@GetMapping(value = { "/", "/{path:^(?!static|apis|blob).*$}/**" })
@GetMapping(value = { "/", "/{path:^(?!static|apis).*$}/**" })
public ResponseEntity<StreamingResponseBody> index() {
StreamingResponseBody streamingResponseBody = s -> {
try (InputStream stream = this.getClass().getResourceAsStream("/html/index.html")) {

View File

@ -1,28 +0,0 @@
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()));
}
}

View File

@ -1,12 +0,0 @@
package com.mingliqiye.disk.dto.system;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class Info {
private String appName;
private String appVersion;
}

View File

@ -6,6 +6,7 @@ import cn.dev33.satoken.exception.NotRoleException;
import com.mingliqiye.disk.http.Respose;
import com.mingliqiye.disk.util.StringUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpStatus;
@ -22,21 +23,25 @@ import org.springframework.web.servlet.NoHandlerFoundException;
public class BaseExceptionHandler {
@ExceptionHandler(BaseException.class)
public ResponseEntity<Respose<?>> exceptionHandler(BaseException e) {
public ResponseEntity<Respose<?>> exceptionHandler(BaseException e, HttpServletRequest request) {
return ResponseEntity.status(e.getCode()).body(
Respose.builder().setCode(e.getCode()).setMessage(StringUtil.format("{}", e.getMessage()))
);
}
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public Respose<?> exceptionHandler(HttpRequestMethodNotSupportedException e) {
public Respose<?> exceptionHandler(HttpRequestMethodNotSupportedException e, HttpServletRequest request) {
return Respose.builder()
.setCode(ExceptionCode.ERROR_METHOD_NOT_ALLOWED.getValue())
.setMessage(StringUtil.format("{} by {}", e.getMessage(), e.getClass().getName()));
}
@ExceptionHandler(NoHandlerFoundException.class)
public ResponseEntity<Respose<?>> exceptionHandler(NoHandlerFoundException e) {
public ResponseEntity<Respose<?>> exceptionHandler(
NoHandlerFoundException e,
HttpServletRequest request,
HttpServletResponse response
) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(
Respose.builder()
.setCode(ExceptionCode.ERROR_NOT_FOUND.getValue())
@ -45,30 +50,26 @@ public class BaseExceptionHandler {
}
@ExceptionHandler(NotLoginException.class)
public ResponseEntity<Respose<?>> exceptionHandler(NotLoginException e, HttpServletRequest request) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(
Respose.builder()
.setCode(ExceptionCode.ERROR_UNAUTHORIZED.getValue())
.setMessage(e.getMessage())
.setData(e.getType())
);
public Respose<?> exceptionHandler(NotLoginException e, HttpServletRequest request) {
return Respose.builder()
.setCode(ExceptionCode.ERROR_UNAUTHORIZED.getValue())
.setMessage(e.getMessage())
.setData(e.getType());
}
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<Respose<?>> exceptionHandler(HttpMessageNotReadableException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(
Respose.builder().setCode(ExceptionCode.ERROR_FORBIDDEN.getValue()).setMessage(e.getMessage())
);
public Respose<?> exceptionHandler(HttpMessageNotReadableException e, HttpServletRequest request) {
return Respose.builder()
.setCode(ExceptionCode.ERROR_FORBIDDEN.getValue())
.setMessage(StringUtil.format("{} by {}", e.getMessage(), e.getClass().getName()));
}
@ExceptionHandler(NotRoleException.class)
public ResponseEntity<Respose<?>> exceptionHandler(NotRoleException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(
Respose.builder()
.setCode(ExceptionCode.ERROR_FORBIDDEN.getValue())
.setMessage(e.getMessage())
.setData(e.getCode())
);
public Respose<?> exceptionHandler(NotRoleException e) {
return Respose.builder()
.setCode(ExceptionCode.ERROR_FORBIDDEN.getValue())
.setMessage(e.getMessage())
.setData(e.getCode());
}
@ExceptionHandler(NotPermissionException.class)

View File

@ -1,6 +1,5 @@
package com.mingliqiye.disk.model;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
@ -33,17 +32,7 @@ public class User {
private byte[] icon;
private boolean admin;
@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 creationTime;
private DateTime updateTime;
public User setPasswordNull() {

View File

@ -1,78 +0,0 @@
package com.mingliqiye.disk.util;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
/**
* 本地主机工具类
*
* @author MingliPro
* @since 2019年11月13日09:04:36
*/
public class LocalHostUtil {
/**
* 获取主机名称
*
* @return
* @throws UnknownHostException
*/
public static String getHostName() throws UnknownHostException {
return InetAddress.getLocalHost().getHostName();
}
/**
* 获取系统首选IP
*
* @return
* @throws UnknownHostException
*/
public static String getLocalIP() throws UnknownHostException {
return InetAddress.getLocalHost().getHostAddress();
}
/**
* 获取所有网卡IP排除回文地址虚拟地址
*
* @return
* @throws SocketException
*/
public static String[] getLocalIPs() throws SocketException {
List<String> list = new ArrayList<>();
Enumeration<NetworkInterface> enumeration = NetworkInterface.getNetworkInterfaces();
while (enumeration.hasMoreElements()) {
NetworkInterface intf = enumeration.nextElement();
if (intf.isLoopback() || intf.isVirtual()) { //
continue;
}
Enumeration<InetAddress> inets = intf.getInetAddresses();
while (inets.hasMoreElements()) {
InetAddress addr = inets.nextElement();
if (addr.isLoopbackAddress() || !addr.isSiteLocalAddress() || addr.isAnyLocalAddress()) {
continue;
}
list.add(addr.getHostAddress());
}
}
return list.toArray(new String[0]);
}
/**
* 判断操作系统是否是Windows
*
* @return
*/
public static boolean isWindowsOS() {
boolean isWindowsOS = false;
String osName = System.getProperty("os.name");
if (osName.toLowerCase().contains("windows")) {
isWindowsOS = true;
}
return isWindowsOS;
}
}

View File

@ -2,8 +2,6 @@ package com.mingliqiye.disk.util;
import cn.dev33.satoken.stp.StpUtil;
import com.mingliqiye.disk.uuid.UUID;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class PanStpUtil {
@ -11,12 +9,7 @@ public class PanStpUtil {
return UUID.ofString((String) StpUtil.getLoginId());
}
@Nullable
public static UUID getLoginIdDefaultNull() {
return UUID.ofString((String) StpUtil.getLoginId());
}
public static String login(@NotNull UUID uuid) {
public static String login(UUID uuid) {
StpUtil.login(uuid.toUUIDString());
return StpUtil.getTokenValue();
}

View File

@ -1,7 +1,7 @@
config:
port: 9963
app-name: pan-disk
app-version: 1.1
app-name: maven-repository
app-version: 1.0
data-source:
mysql:
username: pan
@ -85,6 +85,6 @@ mybatis-plus:
springdoc:
swagger-ui:
enabled: false
path: /apis/swagger
api-docs:
path: /apis/swagger/api.json

View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="UTF-8">
<link href="/static/svg/gZf8M0dh.svg" rel="icon">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<title>Vite App</title>
<script type="module" crossorigin src="/static/js/CcCh4Sgi.js"></script>
<link rel="stylesheet" crossorigin href="/static/css/B-HidlUH.css">
</head>
<body>
<div id="app"></div>
</body>
</html>

View File

@ -0,0 +1 @@
:root{--vt-c-white:#fff;--vt-c-white-soft:#f8f8f8;--vt-c-white-mute:#f2f2f2;--vt-c-black:#181818;--vt-c-black-soft:#222;--vt-c-black-mute:#282828;--vt-c-indigo:#2c3e50;--vt-c-divider-light-1:#3c3c3c4a;--vt-c-divider-light-2:#3c3c3c1f;--vt-c-divider-dark-1:#545454a6;--vt-c-divider-dark-2:#5454547a;--vt-c-text-light-1:var(--vt-c-indigo);--vt-c-text-light-2:#3c3c3ca8;--vt-c-text-dark-1:var(--vt-c-white);--vt-c-text-dark-2:#ebebeba3;--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:400}body{min-height:100vh;color:var(--color-text);background:var(--color-background);text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;font-size:15px;line-height:1.6;transition:color .5s,background-color .5s}#app{max-width:1280px;margin:0 auto;padding:2rem;font-weight:400}a,.green{color:#00bd7e;padding:3px;text-decoration:none;transition:all .4s}@media (hover:hover){a:hover{background-color:#00bd7e33}}@media (min-width:1024px){body{place-items:center;display:flex}#app{grid-template-columns:1fr 1fr;padding:0 2rem;display:grid}}h1[data-v-bed32675]{font-size:2.6rem;font-weight:500;position:relative;top:-10px}h3[data-v-bed32675]{font-size:1.2rem}.greetings h1[data-v-bed32675],.greetings h3[data-v-bed32675]{text-align:center}@media (min-width:1024px){.greetings h1[data-v-bed32675],.greetings h3[data-v-bed32675]{text-align:left}}header[data-v-608be0ed]{max-height:100vh;line-height:1.5}.logo[data-v-608be0ed]{margin:0 auto 2rem;display:block}nav[data-v-608be0ed]{text-align:center;width:100%;margin-top:2rem;font-size:12px}nav a.router-link-exact-active[data-v-608be0ed]{color:var(--color-text)}nav a.router-link-exact-active[data-v-608be0ed]:hover{background-color:#0000}nav a[data-v-608be0ed]{border-left:1px solid var(--color-border);padding:0 1rem;display:inline-block}nav a[data-v-608be0ed]:first-of-type{border:0}@media (min-width:1024px){header[data-v-608be0ed]{padding-right:calc(var(--section-gap)/2);place-items:center;display:flex}.logo[data-v-608be0ed]{margin:0 2rem 0 0}header .wrapper[data-v-608be0ed]{flex-wrap:wrap;place-items:flex-start;display:flex}nav[data-v-608be0ed]{text-align:left;margin-top:1rem;margin-left:-1rem;padding:1rem 0;font-size:1rem}}.item[data-v-5369c01e]{margin-top:2rem;display:flex;position:relative}.details[data-v-5369c01e]{flex:1;margin-left:1rem}i[data-v-5369c01e]{width:32px;height:32px;color:var(--color-text);place-content:center;place-items:center;display:flex}h3[data-v-5369c01e]{color:var(--color-heading);margin-bottom:.4rem;font-size:1.2rem;font-weight:500}@media (min-width:1024px){.item[data-v-5369c01e]{padding:.4rem 0 1rem calc(var(--section-gap)/2);margin-top:0}i[data-v-5369c01e]{border:1px solid var(--color-border);background:var(--color-background);border-radius:8px;width:50px;height:50px;position:absolute;top:calc(50% - 25px);left:-26px}.item[data-v-5369c01e]:before{content:" ";border-left:1px solid var(--color-border);height:calc(50% - 25px);position:absolute;bottom:calc(50% + 25px);left:0}.item[data-v-5369c01e]:after{content:" ";border-left:1px solid var(--color-border);height:calc(50% - 25px);position:absolute;top:calc(50% + 25px);left:0}.item[data-v-5369c01e]:first-of-type:before,.item[data-v-5369c01e]:last-of-type:after{display:none}}

View File

@ -0,0 +1 @@
@media (min-width:1024px){.about{align-items:center;min-height:100vh;display:flex}}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
import{__plugin_vue_export_helper_default as e,createBaseVNode as t,createElementBlock as n,openBlock as r}from"./CcCh4Sgi.js";const i={},a={class:`about`};function o(e,i){return r(),n(`div`,a,i[0]||=[t(`h1`,null,`This is an about page`,-1)])}var s=e(i,[[`render`,o]]);export{s as default};

View File

@ -4,25 +4,15 @@ create table `users`
id binary(16) default (uuid_to_bin(
uuid(),
1)) primary key,
username varchar(256) unique not null,
password varchar(128) not null,
nickname varchar(256) not null,
username varchar(256) unique not null,
password varchar(128) not null,
nickname varchar(256) not null,
prermissions json,
roles json,
icon mediumblob,
admin bool default false,
create_user_id binary(16) not 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)
creation_time timestamp(6) default current_timestamp(6) not null,
update_time timestamp(6) null
);
INSERT INTO users (id, username, password, nickname, prermissions, roles, icon, admin, create_user_id)
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);
INSERT INTO users (id, username, password, nickname, prermissions, roles, icon, admin)
VALUES (0x689EFC204D1F11F08134DB0063E177A7, 'admin', 'admin', '管理员', '[]', '[]', null, 1);

View File

@ -1,20 +1,12 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": [
"env.d.ts",
"web-src/**/*",
"web-src/**/*.vue",
"web-src/types/**/*.d.ts"
],
"exclude": [
"web-src/**/__tests__/*"
],
"include": ["env.d.ts", "web-src/**/*", "web-src/**/*.vue"],
"exclude": ["web-src/**/__tests__/*"],
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"paths": {
"@/*": [
"./web-src/*"
]
"@/*": ["./web-src/*"]
}
}
}

View File

@ -2,32 +2,11 @@ import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import vueDevTools from 'vite-plugin-vue-devtools';
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({
root: path.resolve(__dirname, 'web-src'),
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',
}),
],
plugins: [vue(), vueDevTools()],
resolve: {
alias: {
'@': path.resolve(__dirname, 'web-src'),
@ -44,11 +23,4 @@ export default defineConfig({
},
},
},
server: {
host: '0.0.0.0',
port: 5174,
proxy: {
'/apis': 'http://localhost:9963',
},
},
});

View File

@ -1,25 +1,85 @@
<script lang="ts" setup>
import { RouterLink, RouterView } from 'vue-router';
import HelloWorld from './components/HelloWorld.vue';
</script>
<template>
<n-config-provider :hljs="hljs" :theme="useSettingStore.theme === 'dark' ? darkTheme : lightTheme" abstract>
<n-loading-bar-provider>
<n-message-provider>
<Index />
</n-message-provider>
</n-loading-bar-provider>
</n-config-provider>
<header>
<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>
<script lang="ts" setup>
import Index from '@/layout/index.vue';
import { darkTheme, lightTheme } from 'naive-ui';
import hljs from 'highlight.js/lib/core';
import javascript from 'highlight.js/lib/languages/javascript';
import typescript from 'highlight.js/lib/languages/typescript';
import json from 'highlight.js/lib/languages/json';
import { UseSettingStore } from '@/plugin';
<style scoped>
header {
line-height: 1.5;
max-height: 100vh;
}
hljs.registerLanguage('json', json);
hljs.registerLanguage('typescript', typescript);
hljs.registerLanguage('javascript', javascript);
.logo {
display: block;
margin: 0 auto 2rem;
}
const useSettingStore = UseSettingStore();
</script>
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>

View File

@ -1,33 +0,0 @@
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;
}

View File

@ -1 +0,0 @@
export * from './auth';

View File

@ -1,10 +0,0 @@
import { alovaInstance, type AlovaResponseType } from '@/plugin';
export function getSysInfo() {
return alovaInstance.Get<AlovaResponseType<SysInfo>>('apis/system/info');
}
export interface SysInfo {
appName: string;
appVersion: string;
}

File diff suppressed because it is too large Load Diff

86
web-src/assets/base.css Normal file
View File

@ -0,0 +1,86 @@
/* 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;
}

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -1,17 +0,0 @@
:where(a)
color: #4990e2
text-decoration: none
.n-collapse-item__header
padding-top: 0 !important
.n-collapse-item__header-main
padding-left: 10px !important
.n-collapse-item__content-wrapper
border-top: 1px solid var(--color)
html
scroll-padding-top: 70px
scroll-behavior: smooth

View File

@ -1,143 +0,0 @@
@use "var";
:where(*) {
padding: 0;
margin: 0;
border: 0;
transition: background-color 0.3s ease;
}
html {
color: #3c3c3c;
--text-color: 60, 60, 60;
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
}
html.dark {
color: #d5d5d5;
--text-color: 213, 213, 213;
}
.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;
}

35
web-src/assets/main.css Normal file
View File

@ -0,0 +1,35 @@
@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;
}
}

View File

@ -1,18 +0,0 @@
*[n-c] {
align-items: center;
}
*[c-n] {
justify-content: center;
}
*[c-c] {
justify-content: center;
align-items: center;
}
*[f] {
display: flex;
}

View File

@ -0,0 +1,43 @@
<script setup lang="ts">
defineProps<{
msg: string;
}>();
</script>
<template>
<div class="greetings">
<h1 class="green">{{ msg }}</h1>
<h3>
Youve 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>

View File

@ -1,41 +0,0 @@
<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>

View File

@ -1,21 +0,0 @@
<template>
<n-code :code="recode" :language="language" show-line-numbers style="width: fit-content !important; padding: 10px" />
</template>
<script lang="ts" setup>
const recode = computed(() => {
if (typeof prop.code === 'object') {
return JSON.stringify(prop.code, null, 2);
} else {
return prop.code;
}
});
const prop = defineProps({
code: {
type: [String, Object],
required: true,
},
language: String,
});
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,97 @@
<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>
Vues
<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>

View File

@ -0,0 +1,87 @@
<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>

View File

@ -0,0 +1,6 @@
<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>

View File

@ -0,0 +1,6 @@
<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>

View File

@ -0,0 +1,6 @@
<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>

View File

@ -0,0 +1,6 @@
<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>

View File

@ -0,0 +1,17 @@
<!-- 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>

View File

@ -2,12 +2,12 @@
<html lang="">
<head>
<meta charset="UTF-8">
<link href="./assets/index.svg" rel="icon">
<meta content="width=device-width, initial-scale=0.8" name="viewport">
<title>pan-disk</title>
<link href="./assets/icon.svg" rel="icon">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<title>Vite App</title>
</head>
<body>
<main id="app"></main>
</body>
<div id="app"></div>
<script src="/main.ts" type="module"></script>
</body>
</html>

View File

@ -1,30 +0,0 @@
{
"$schema": "../../languageSchema.json",
"iconsUrl": "https://icones.js.org/collection/emojione-v1?category=Flags",
"languages": [
{
"title": "zh-CN 简体中文",
"id": "zh-CN",
"file": "zh-CN.json",
"icon": "emojione-v1:flag-for-china"
},
{
"title": "zh-TW 简体中文",
"id": "zh-TW",
"file": "zh-TW.json",
"icon": "emojione-v1:flag-for-china"
},
{
"title": "en-US English",
"id": "en-US",
"file": "en-US.json",
"icon": "emojione-v1:flag-for-united-states"
},
{
"title": "ja-JP 日本語",
"id": "ja-JP",
"file": "ja-JP.json",
"icon": "emojione-v1:flag-for-japan"
}
]
}

View File

@ -1,35 +0,0 @@
{
"message": {
"login": {
"plaselogin": "Plase login."
}
},
"nav": {
"title": {
"api": "API",
"file": "file",
"home": "Home",
"setting": "setting",
"user": "user"
}
},
"setting": {
"language": {
"title": "language"
},
"theme": {
"auto": "auto",
"dark": "dark",
"light": "light",
"title": "theme"
},
"title": "setting"
},
"view": {
"login": {
"password": "password",
"title": "login",
"username": "username"
}
}
}

View File

@ -1,35 +0,0 @@
{
"message": {
"login": {
"plaselogin": "ログインしてください"
}
},
"nav": {
"title": {
"api": "API",
"file": "ファイル",
"home": "ホーム",
"setting": "設定",
"user": "ユーザー"
}
},
"setting": {
"language": {
"title": "言語設定"
},
"theme": {
"auto": "自動",
"dark": "ダークモード",
"light": "ライトモード",
"title": "テーマ設定"
},
"title": "設定"
},
"view": {
"login": {
"password": "パスワード",
"title": "ログイン",
"username": "ユーザー名"
}
}
}

View File

@ -1,35 +0,0 @@
{
"message": {
"login": {
"plaselogin": "请登陆"
}
},
"nav": {
"title": {
"api": "API",
"file": "文件",
"home": "主页",
"setting": "设置",
"user": "用户"
}
},
"setting": {
"language": {
"title": "语言配置"
},
"theme": {
"auto": "自动",
"dark": "暗色",
"light": "亮色",
"title": "主题配置"
},
"title": "设置"
},
"view": {
"login": {
"password": "密码",
"title": "登陆",
"username": "用户名"
}
}
}

View File

@ -1,35 +0,0 @@
{
"message": {
"login": {
"plaselogin": "請登入"
}
},
"nav": {
"title": {
"api": "API",
"file": "檔案",
"home": "首頁",
"setting": "設定",
"user": "使用者"
}
},
"setting": {
"language": {
"title": "語言設定"
},
"theme": {
"auto": "自動",
"dark": "深色",
"light": "淺色",
"title": "主題設定"
},
"title": "設定"
},
"view": {
"login": {
"password": "密碼",
"title": "登入",
"username": "使用者名稱"
}
}
}

View File

@ -1,259 +0,0 @@
<template>
<nav class="navmain">
<router-link :to="{ name: 'home' }" c-c f style="color: inherit; height: 100%; text-decoration: none">
<img alt="@/assets/index.svg" 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" />
{{ t('nav.title.home') }}
<div class="after"></div>
</div>
<div
v-bind:class="{
'router-item-atc': router.currentRoute.value.path.startsWith('/file'),
'router-item': true,
}"
@click="openFile()">
<Icon icon="material-symbols:folder-rounded" />
{{ t('nav.title.file') }}
<div class="after"></div>
</div>
<div
v-bind:class="{
'router-item-atc': router.currentRoute.value.name === 'user',
'router-item': true,
}"
@click="router.push({ name: 'user' })">
<Icon icon="material-symbols:person-rounded" />
{{ t('nav.title.user') }}
<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" />
{{ t('nav.title.api') }}
<div class="after"></div>
</div>
<div
v-bind:class="{
'router-item': true,
}"
@click="activeSetting.on()">
<Icon icon="material-symbols:settings" />
{{ t('nav.title.setting') }}
<div class="after"></div>
</div>
</div>
</nav>
<nav class="nav">
<n-drawer v-model:show="activeSetting.value" placement="right" width="auto">
<n-drawer-content :title="t('setting.title')">
<n-divider>{{ t('setting.theme.title') }}</n-divider>
<div c-c f>
<n-radio-group v-model:value="useSettingStore.themeMode" name="team">
<n-radio-button value="light">{{ t('setting.theme.light') }}</n-radio-button>
<n-radio-button value="dark">{{ t('setting.theme.dark') }}</n-radio-button>
<n-radio-button value="auto">{{ t('setting.theme.auto') }}</n-radio-button>
</n-radio-group>
</div>
<n-divider>{{ t('setting.language.title') }}</n-divider>
<n-select
:options="messageData"
:render-label="renderLabel"
:render-tag="renderMultipleSelectTag"
:value="useSettingStore.language"
filterable
@update-value="useSettingStore.setLanguage"></n-select>
</n-drawer-content>
</n-drawer>
</nav>
</template>
<script lang="ts" setup>
import Icon from '@/components/Icon.vue';
import { UseBoolRef } from '@/util';
import {
type languageIndexItemValueType,
message,
messageData,
router,
t,
UseAuthStore,
UseSettingStore,
} from '@/plugin';
import { getSysInfo } from '@/api/system.ts';
import type { SelectOption } from 'naive-ui/es/select/src/interface';
import type { VNodeChild } from 'vue';
export type RenderTag = (
props: {
option: SelectOption;
handleClose: () => void;
} & languageIndexItemValueType,
) => VNodeChild;
export type RenderTagSelect = (props: {
option: SelectOption & languageIndexItemValueType;
handleClose: () => void;
}) => VNodeChild;
const renderLabel: RenderTag = (data) => {
return h(
'div',
{
f: '',
'n-c': '',
style: {
gap: '10px',
},
},
[h(Icon, { icon: data.icon }), data.title],
);
};
const renderMultipleSelectTag: RenderTagSelect = ({ option: data }) => {
return h(
'div',
{
f: '',
'n-c': '',
style: {
gap: '10px',
},
},
[h(Icon, { icon: data.icon }), data.title],
);
};
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;
document.title = data.appName;
});
useAuthStore.woIsMe();
useSettingStore.setLanguage(null);
function openFile() {
if (useAuthStore.isLogin) {
router.push({
path: `/file/${useAuthStore.username}`,
});
} else {
message.error(() => t('message.login.plaselogin'));
router.push({
path: `/login`,
});
}
}
console.log(t);
</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: 5;
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>

View File

@ -1,12 +0,0 @@
<template>
<Head />
<n-back-top />
<router-view v-slot="{ Component }">
<div style="margin: 10px; height: calc(100% - 66px - 20px)">
<component :is="Component" />
</div>
</router-view>
</template>
<script lang="ts" setup>
import Head from '@/layout/Head.vue';
</script>

View File

@ -1,11 +0,0 @@
<template>
<RouterView />
</template>
<script lang="ts" setup>
import { useLoadingBar, useMessage } from 'naive-ui';
import { setLoadingBar, setMessage } from '@/plugin';
setMessage(useMessage());
setLoadingBar(useLoadingBar());
</script>
<style scoped></style>

View File

@ -1,15 +1,14 @@
import './assets/index.scss';
import './assets/index.sass';
import './assets/main.css';
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';
import { i18n, installI18n, pinia, router } from './plugin';
import router from './router';
const app = createApp(App);
app.use(pinia);
app.use(i18n);
installI18n().then(() => {
app.use(router);
app.mount('#app');
});
app.use(createPinia());
app.use(router);
app.mount('#app');

View File

@ -1,47 +0,0 @@
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';
}
}

View File

@ -1,13 +0,0 @@
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;
}

View File

@ -1,58 +0,0 @@
import { createI18n } from 'vue-i18n';
import type { languageIndexItemValueType, languageIndexType, languageType } from './type';
import { ref } from 'vue';
export * from './type';
const languagedatas = import.meta.glob('@/language/**/*.json');
const loadIndexFile = async (): Promise<languageIndexType> => {
try {
const module = (await languagedatas['/language/index.json']()) as {
default: languageIndexType;
};
return module.default;
} catch (error) {
console.error('加载语言索引文件失败:', error);
throw error;
}
};
const loadLanguageFile = async (fileName: string): Promise<languageType> => {
try {
const module = (await languagedatas[`/language/${fileName}`]()) as {
default: languageType;
};
return module.default;
} catch (error) {
console.error(`加载语言文件失败:${fileName}`, error);
return {};
}
};
export const messages: languageType | any = {};
export const messageData = ref<languageIndexItemValueType[]>([]);
export const i18n = createI18n({
legacy: false,
locale: 'zh-cn',
fallbackLocale: 'zh-cn',
messages,
});
export async function installI18n() {
const data: languageIndexType = await loadIndexFile();
for (const i of data.languages) {
messageData.value.push({
title: i.title,
value: i.id,
icon: i.icon,
});
messages[i.id] = await loadLanguageFile(`lang/${i.file}`);
}
}
export function t(d: string): string {
return i18n.global.t(d);
}

View File

@ -1,18 +0,0 @@
export type languageType = { [key: string]: string | languageType };
export interface languageIndexItemType {
title: string;
id: string;
file: string;
icon: string;
}
export interface languageIndexItemValueType {
title: string;
value: string;
icon: string;
}
export interface languageIndexType {
languages: languageIndexItemType[];
}

View File

@ -1,18 +0,0 @@
import type { MessageApiInjection } from 'naive-ui/es/message/src/MessageProvider';
import type { LoadingBarApiInjection } from 'naive-ui/es/loading-bar/src/LoadingBarProvider';
export * from './router';
export * from './stores';
export * from './alova';
export * from './i18n';
export let message: MessageApiInjection;
export let loadingBar: LoadingBarApiInjection;
export function setMessage(messages: MessageApiInjection) {
message = messages;
}
export function setLoadingBar(loadingBars: LoadingBarApiInjection) {
loadingBar = loadingBars;
}

View File

@ -1,52 +0,0 @@
import { createRouter, createWebHistory } from 'vue-router';
import { loadingBar } from '@/plugin';
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: '/file/:pathMatch(.*)*',
name: 'file',
component: () => import('@/views/FileView.vue'),
},
{
path: '/user',
name: 'user',
component: () => import('@/views/UserView.vue'),
},
{
path: '/login',
name: 'login',
component: () => import('@/views/loginView.vue'),
},
{
path: '/register',
name: 'register',
component: () => import('@/views/RegisterView.vue'),
},
{
path: '/swagger',
name: 'api',
component: () => import('@/views/SwaggerView.vue'),
},
],
},
],
});
router.beforeEach((to, from, next) => {
loadingBar.start();
next();
setTimeout(() => loadingBar.finish(), 100);
});
export { router };

View File

@ -1,49 +0,0 @@
import { defineStore } from 'pinia';
import { login, logout, woIsMe } from '@/api';
import CookiesApi from '@/util/Cookies.ts';
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());
},
logout() {
logout().then(() => this.$reset());
},
},
persist: {
storage: CookiesApi.StorageApi,
},
});

View File

@ -1,8 +0,0 @@
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 };

View File

@ -1,34 +0,0 @@
import { defineStore } from 'pinia';
import { useColorMode } from '@vueuse/core';
import { i18n } from '@/plugin';
import { LocalStorageApi } from '@/util/Cookies.ts';
export const UseSettingStore = defineStore('setting', {
state: () => {
const { store, state } = useColorMode();
return {
themeMode: store,
theme: state,
appName: '',
appVersion: '',
language: 'zh-CN',
};
},
persist: {
storage: LocalStorageApi.StorageApi,
pick: ['themeMode', 'appName', 'appVersion', 'language'],
},
actions: {
setLanguage(data: string | null) {
if (data == null) {
i18n.global.locale.value = this.language;
document.documentElement.lang = this.language;
return;
}
this.language = data;
i18n.global.locale.value = data;
document.documentElement.lang = data;
},
},
});

23
web-src/router/index.ts Normal file
View File

@ -0,0 +1,23 @@
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;

12
web-src/stores/counter.ts Normal file
View File

@ -0,0 +1,12 @@
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 };
});

View File

@ -1,57 +0,0 @@
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;

View File

@ -1,25 +0,0 @@
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 };

View File

@ -1,2 +0,0 @@
export * from './UseBoolRef';
export * from './Cookies';

View File

@ -0,0 +1,15 @@
<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>

View File

@ -1,9 +0,0 @@
<template>
<div></div>
</template>
<script lang="ts" setup>
import { router } from '@/plugin';
console.log(router.currentRoute);
</script>
<style scoped></style>

View File

@ -1,11 +1,9 @@
<template>
<div>
<NButton @click="useAuthStore.login('admin', 'admin')">登录</NButton>
</div>
</template>
<script lang="ts" setup>
import { UseAuthStore } from '@/plugin';
const useAuthStore = UseAuthStore();
<script setup lang="ts">
import TheWelcome from '../components/TheWelcome.vue';
</script>
<style scoped></style>
<template>
<main>
<TheWelcome />
</main>
</template>

View File

@ -1,3 +0,0 @@
<template></template>
<script lang="ts" setup></script>
<style scoped></style>

View File

@ -1,18 +0,0 @@
<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>

View File

@ -1,19 +0,0 @@
<template>
<div>
<n-button @click="useAuthStore.logout()">注销</n-button>
</div>
</template>
<script lang="ts" setup>
import { router, UseAuthStore } from '@/plugin';
import { onMounted } from 'vue';
const useAuthStore = UseAuthStore();
onMounted(() => {
if (!useAuthStore.isLogin)
router.push({
name: 'login',
});
});
</script>
<style scoped></style>

View File

@ -1,27 +0,0 @@
<template>
<div c-c f style="height: calc(100vh - 66px - 20px)">
<n-spin :delay="1000" :show="false">
<n-card
:segmented="{
content: true,
footer: 'soft',
}"
:title="t('view.login.title')"
style="width: 430px">
<n-form border label-align="right" label-placement="left" label-width="auto">
<n-form-item :label="t('view.login.username')">
<n-input />
</n-form-item>
<n-form-item :label="t('view.login.password')">
<n-input />
</n-form-item>
</n-form>
<n-button block secondary strong type="primary">{{ t('view.login.title') }}</n-button>
</n-card>
</n-spin>
</div>
</template>
<script lang="ts" setup>
import { t } from '@/plugin';
</script>
<style scoped></style>