Compare commits
10 Commits
Auto-Relea
...
master
Author | SHA1 | Date | |
---|---|---|---|
067889f096 | |||
953a370233 | |||
7f8defeee2 | |||
67d99ccd42 | |||
2a6672613a | |||
3071859a3d | |||
570c41bc11 | |||
dac6928844 | |||
c51326b698 | |||
ddaa02da3d |
55
languageSchema.json
Normal file
55
languageSchema.json
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
"$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
|
||||||
|
}
|
@ -9,6 +9,7 @@
|
|||||||
"build-jar-auto": "run-p \"build-only\" && gradle -Dorg.gradle.java.home=/opt/jdk/21.0.7/ build-jar",
|
"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 {@}\" --",
|
"build": "run-p type-check \"build-only {@}\" --",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
|
"build-preview": "run-p build-only && vite preview",
|
||||||
"build-only": "vite build",
|
"build-only": "vite build",
|
||||||
"type-check": "vue-tsc --build",
|
"type-check": "vue-tsc --build",
|
||||||
"lint:oxlint": "oxlint . --fix -D correctness --ignore-path .gitignore",
|
"lint:oxlint": "oxlint . --fix -D correctness --ignore-path .gitignore",
|
||||||
@ -18,6 +19,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iconify/vue": "^5.0.0",
|
"@iconify/vue": "^5.0.0",
|
||||||
|
"@intlify/vue-i18n-core": "^11.1.7",
|
||||||
"@types/vue-router": "^2.0.0",
|
"@types/vue-router": "^2.0.0",
|
||||||
"@vueuse/core": "^13.4.0",
|
"@vueuse/core": "^13.4.0",
|
||||||
"naive-ui": "^2.42.0",
|
"naive-ui": "^2.42.0",
|
||||||
@ -26,6 +28,7 @@
|
|||||||
"unplugin-auto-import": "^19.3.0",
|
"unplugin-auto-import": "^19.3.0",
|
||||||
"unplugin-vue-components": "^28.8.0",
|
"unplugin-vue-components": "^28.8.0",
|
||||||
"vue": "^3.5.17",
|
"vue": "^3.5.17",
|
||||||
|
"vue-i18n": "11",
|
||||||
"vue-router": "^4.5.1"
|
"vue-router": "^4.5.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -41,10 +44,12 @@
|
|||||||
"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",
|
||||||
|
"highlight.js": "^11.11.1",
|
||||||
"jiti": "^2.4.2",
|
"jiti": "^2.4.2",
|
||||||
"js-base64": "^3.7.7",
|
"js-base64": "^3.7.7",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
"npm-run-all2": "^8.0.4",
|
"npm-run-all2": "^8.0.4",
|
||||||
|
"openapi-types": "^12.1.3",
|
||||||
"oxlint": "~1.1.0",
|
"oxlint": "~1.1.0",
|
||||||
"pinia-plugin-persistedstate": "^4.4.0",
|
"pinia-plugin-persistedstate": "^4.4.0",
|
||||||
"prettier": "3.5.3",
|
"prettier": "3.5.3",
|
||||||
|
@ -11,6 +11,7 @@ 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 io.swagger.v3.oas.models.servers.Server;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
@ -42,16 +43,9 @@ public class SpringDocConfig {
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public OpenAPI openAPI() {
|
public OpenAPI openAPI() {
|
||||||
Server server = new Server();
|
List<Server> servers = new ArrayList<>();
|
||||||
server.setUrl("/");
|
servers.add(new Server().description("当前网页").url("/"));
|
||||||
server.setDescription("当前服务");
|
return new OpenAPI().info(this.getApiInfo()).servers(servers);
|
||||||
Server server2 = new Server();
|
|
||||||
server2.setUrl("http://localhost:" + config.getPort() + "/");
|
|
||||||
server2.setDescription("本机服务");
|
|
||||||
return new OpenAPI()
|
|
||||||
// 配置接口文档基本信息
|
|
||||||
.info(this.getApiInfo())
|
|
||||||
.servers(List.of(server, server2));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Info getApiInfo() {
|
private Info getApiInfo() {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.mingliqiye.disk.controller;
|
package com.mingliqiye.disk.controller;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@ -12,7 +13,7 @@ import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBo
|
|||||||
@RequestMapping
|
@RequestMapping
|
||||||
@Tag(name = "前端路由", description = "统一匹配路径指向VueRouter")
|
@Tag(name = "前端路由", description = "统一匹配路径指向VueRouter")
|
||||||
public class IndexController {
|
public class IndexController {
|
||||||
|
@Operation(summary = "VueRouter 主路由")
|
||||||
@GetMapping(value = { "/", "/{path:^(?!static|apis|blob).*$}/**" })
|
@GetMapping(value = { "/", "/{path:^(?!static|apis|blob).*$}/**" })
|
||||||
public ResponseEntity<StreamingResponseBody> index() {
|
public ResponseEntity<StreamingResponseBody> index() {
|
||||||
StreamingResponseBody streamingResponseBody = s -> {
|
StreamingResponseBody streamingResponseBody = s -> {
|
||||||
|
78
src/main/java/com/mingliqiye/disk/util/LocalHostUtil.java
Normal file
78
src/main/java/com/mingliqiye/disk/util/LocalHostUtil.java
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -46,6 +46,7 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
host: '0.0.0.0',
|
host: '0.0.0.0',
|
||||||
|
port: 5174,
|
||||||
proxy: {
|
proxy: {
|
||||||
'/apis': 'http://localhost:9963',
|
'/apis': 'http://localhost:9963',
|
||||||
},
|
},
|
||||||
|
@ -1,7 +1,25 @@
|
|||||||
<template>
|
<template>
|
||||||
<Index />
|
<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>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import Index from '@/layout/index.vue';
|
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';
|
||||||
|
|
||||||
|
hljs.registerLanguage('json', json);
|
||||||
|
hljs.registerLanguage('typescript', typescript);
|
||||||
|
hljs.registerLanguage('javascript', javascript);
|
||||||
|
|
||||||
|
const useSettingStore = UseSettingStore();
|
||||||
</script>
|
</script>
|
||||||
|
17
web-src/assets/index.sass
Normal file
17
web-src/assets/index.sass
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
|
||||||
|
: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
|
@ -9,11 +9,13 @@
|
|||||||
|
|
||||||
html {
|
html {
|
||||||
color: #3c3c3c;
|
color: #3c3c3c;
|
||||||
|
--text-color: 60, 60, 60;
|
||||||
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
|
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
html.dark {
|
html.dark {
|
||||||
color: #d5d5d5;
|
color: #d5d5d5;
|
||||||
|
--text-color: 213, 213, 213;
|
||||||
}
|
}
|
||||||
|
|
||||||
.swagger-ui .info {
|
.swagger-ui .info {
|
||||||
|
@ -14,3 +14,5 @@
|
|||||||
*[f] {
|
*[f] {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
21
web-src/components/MCode.vue
Normal file
21
web-src/components/MCode.vue
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<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>
|
@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<link href="./assets/index.svg" rel="icon">
|
<link href="./assets/index.svg" rel="icon">
|
||||||
<meta content="width=device-width, initial-scale=0.8" name="viewport">
|
<meta content="width=device-width, initial-scale=0.8" name="viewport">
|
||||||
<title>Vite App</title>
|
<title>pan-disk</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main id="app"></main>
|
<main id="app"></main>
|
||||||
|
30
web-src/language/index.json
Normal file
30
web-src/language/index.json
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"$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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
35
web-src/language/lang/en-US.json
Normal file
35
web-src/language/lang/en-US.json
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
web-src/language/lang/ja-JP.json
Normal file
35
web-src/language/lang/ja-JP.json
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"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": "ユーザー名"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
web-src/language/lang/zh-CN.json
Normal file
35
web-src/language/lang/zh-CN.json
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"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": "用户名"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
web-src/language/lang/zh-TW.json
Normal file
35
web-src/language/lang/zh-TW.json
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"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": "使用者名稱"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<nav class="navmain">
|
<nav class="navmain">
|
||||||
<router-link :to="{ name: 'home' }" c-c f style="color: inherit; height: 100%; text-decoration: none">
|
<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" />
|
<img alt="@/assets/index.svg" height="50" src="@/assets/index.svg" width="50" />
|
||||||
<div>
|
<div>
|
||||||
<label style="font-size: 20px; margin-left: 10px; cursor: pointer">{{ useSettingStore.appName }}</label>
|
<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>
|
<label style="margin-left: 5px; font-size: 12px; cursor: pointer">V{{ useSettingStore.appVersion }}</label>
|
||||||
@ -16,17 +16,27 @@
|
|||||||
}"
|
}"
|
||||||
@click="router.push({ name: 'home' })">
|
@click="router.push({ name: 'home' })">
|
||||||
<Icon icon="material-symbols:home" />
|
<Icon icon="material-symbols:home" />
|
||||||
主页
|
{{ t('nav.title.home') }}
|
||||||
<div class="after"></div>
|
<div class="after"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="router-item">
|
<div
|
||||||
|
v-bind:class="{
|
||||||
|
'router-item-atc': router.currentRoute.value.path.startsWith('/file'),
|
||||||
|
'router-item': true,
|
||||||
|
}"
|
||||||
|
@click="openFile()">
|
||||||
<Icon icon="material-symbols:folder-rounded" />
|
<Icon icon="material-symbols:folder-rounded" />
|
||||||
文件
|
{{ t('nav.title.file') }}
|
||||||
<div class="after"></div>
|
<div class="after"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="router-item">
|
<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" />
|
<Icon icon="material-symbols:person-rounded" />
|
||||||
用户
|
{{ t('nav.title.user') }}
|
||||||
<div class="after"></div>
|
<div class="after"></div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@ -36,7 +46,7 @@
|
|||||||
}"
|
}"
|
||||||
@click="router.push({ name: 'api' })">
|
@click="router.push({ name: 'api' })">
|
||||||
<icon icon="mdi:api" />
|
<icon icon="mdi:api" />
|
||||||
API
|
{{ t('nav.title.api') }}
|
||||||
<div class="after"></div>
|
<div class="after"></div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@ -45,22 +55,30 @@
|
|||||||
}"
|
}"
|
||||||
@click="activeSetting.on()">
|
@click="activeSetting.on()">
|
||||||
<Icon icon="material-symbols:settings" />
|
<Icon icon="material-symbols:settings" />
|
||||||
设置
|
{{ t('nav.title.setting') }}
|
||||||
<div class="after"></div>
|
<div class="after"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<nav class="nav">
|
<nav class="nav">
|
||||||
<n-drawer v-model:show="activeSetting.value" placement="right">
|
<n-drawer v-model:show="activeSetting.value" placement="right" width="auto">
|
||||||
<n-drawer-content title="设置">
|
<n-drawer-content :title="t('setting.title')">
|
||||||
|
<n-divider>{{ t('setting.theme.title') }}</n-divider>
|
||||||
<div c-c f>
|
<div c-c f>
|
||||||
<n-radio-group v-model:value="useSettingStore.themeMode" name="team">
|
<n-radio-group v-model:value="useSettingStore.themeMode" name="team">
|
||||||
<n-radio-button value="light">亮色</n-radio-button>
|
<n-radio-button value="light">{{ t('setting.theme.light') }}</n-radio-button>
|
||||||
<n-radio-button value="dark">暗色</n-radio-button>
|
<n-radio-button value="dark">{{ t('setting.theme.dark') }}</n-radio-button>
|
||||||
<n-radio-button value="auto">自动</n-radio-button>
|
<n-radio-button value="auto">{{ t('setting.theme.auto') }}</n-radio-button>
|
||||||
</n-radio-group>
|
</n-radio-group>
|
||||||
</div>
|
</div>
|
||||||
<n-divider>主题配置</n-divider>
|
<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-content>
|
||||||
</n-drawer>
|
</n-drawer>
|
||||||
</nav>
|
</nav>
|
||||||
@ -68,8 +86,56 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import Icon from '@/components/Icon.vue';
|
import Icon from '@/components/Icon.vue';
|
||||||
import { UseBoolRef } from '@/util';
|
import { UseBoolRef } from '@/util';
|
||||||
import { router, UseAuthStore, UseSettingStore } from '@/plugin';
|
import {
|
||||||
|
type languageIndexItemValueType,
|
||||||
|
message,
|
||||||
|
messageData,
|
||||||
|
router,
|
||||||
|
t,
|
||||||
|
UseAuthStore,
|
||||||
|
UseSettingStore,
|
||||||
|
} from '@/plugin';
|
||||||
import { getSysInfo } from '@/api/system.ts';
|
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 useSettingStore = UseSettingStore();
|
||||||
const activeSetting = UseBoolRef();
|
const activeSetting = UseBoolRef();
|
||||||
@ -78,8 +144,26 @@ getSysInfo().then((r) => {
|
|||||||
const data = r.json().data;
|
const data = r.json().data;
|
||||||
useSettingStore.appName = data.appName;
|
useSettingStore.appName = data.appName;
|
||||||
useSettingStore.appVersion = data.appVersion;
|
useSettingStore.appVersion = data.appVersion;
|
||||||
|
document.title = data.appName;
|
||||||
});
|
});
|
||||||
useAuthStore.woIsMe();
|
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>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
html.dark {
|
html.dark {
|
||||||
@ -158,7 +242,7 @@ html.dark {
|
|||||||
backdrop-filter: blur(5px);
|
backdrop-filter: blur(5px);
|
||||||
height: 65px;
|
height: 65px;
|
||||||
width: calc(100% - 40px);
|
width: calc(100% - 40px);
|
||||||
z-index: 1;
|
z-index: 5;
|
||||||
padding: 0 20px;
|
padding: 0 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
|
@ -1,15 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<Head />
|
<Head />
|
||||||
<div style="margin: 10px">
|
<n-back-top />
|
||||||
<RouterView />
|
<router-view v-slot="{ Component }">
|
||||||
</div>
|
<div style="margin: 10px; height: calc(100% - 66px - 20px)">
|
||||||
|
<component :is="Component" />
|
||||||
|
</div>
|
||||||
|
</router-view>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { RouterView } from 'vue-router';
|
|
||||||
import { useMessage } from 'naive-ui';
|
|
||||||
import Head from '@/layout/Head.vue';
|
import Head from '@/layout/Head.vue';
|
||||||
import { setMessage } from '@/plugin';
|
|
||||||
|
|
||||||
setMessage(useMessage());
|
|
||||||
</script>
|
</script>
|
||||||
<style scoped></style>
|
|
||||||
|
@ -1,15 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<n-config-provider :theme="useSettingStore.theme === 'dark' ? darkTheme : lightTheme" abstract>
|
<RouterView />
|
||||||
<n-message-provider>
|
|
||||||
<RouterView />
|
|
||||||
</n-message-provider>
|
|
||||||
</n-config-provider>
|
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { UseSettingStore } from '@/plugin';
|
import { useLoadingBar, useMessage } from 'naive-ui';
|
||||||
import { RouterView } from 'vue-router';
|
import { setLoadingBar, setMessage } from '@/plugin';
|
||||||
import { darkTheme, lightTheme } from 'naive-ui';
|
|
||||||
|
|
||||||
const useSettingStore = UseSettingStore();
|
setMessage(useMessage());
|
||||||
|
setLoadingBar(useLoadingBar());
|
||||||
</script>
|
</script>
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
import './assets/index.scss';
|
import './assets/index.scss';
|
||||||
|
import './assets/index.sass';
|
||||||
|
|
||||||
import { createApp } from 'vue';
|
import { createApp } from 'vue';
|
||||||
|
|
||||||
import App from './App.vue';
|
import App from './App.vue';
|
||||||
import { pinia, router } from './plugin';
|
import { i18n, installI18n, pinia, router } from './plugin';
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
|
||||||
app.use(pinia);
|
app.use(pinia);
|
||||||
app.use(router);
|
app.use(i18n);
|
||||||
|
installI18n().then(() => {
|
||||||
app.mount('#app');
|
app.use(router);
|
||||||
|
app.mount('#app');
|
||||||
|
});
|
||||||
|
58
web-src/plugin/i18n/index.ts
Normal file
58
web-src/plugin/i18n/index.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
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);
|
||||||
|
}
|
18
web-src/plugin/i18n/type.ts
Normal file
18
web-src/plugin/i18n/type.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
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[];
|
||||||
|
}
|
@ -1,11 +1,18 @@
|
|||||||
import type { MessageApiInjection } from 'naive-ui/es/message/src/MessageProvider';
|
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 './router';
|
||||||
export * from './stores';
|
export * from './stores';
|
||||||
export * from './alova';
|
export * from './alova';
|
||||||
|
export * from './i18n';
|
||||||
|
|
||||||
export let message: MessageApiInjection;
|
export let message: MessageApiInjection;
|
||||||
|
export let loadingBar: LoadingBarApiInjection;
|
||||||
|
|
||||||
export function setMessage(messages: MessageApiInjection) {
|
export function setMessage(messages: MessageApiInjection) {
|
||||||
message = messages;
|
message = messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setLoadingBar(loadingBars: LoadingBarApiInjection) {
|
||||||
|
loadingBar = loadingBars;
|
||||||
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router';
|
import { createRouter, createWebHistory } from 'vue-router';
|
||||||
|
import { loadingBar } from '@/plugin';
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
@ -12,6 +13,26 @@ const router = createRouter({
|
|||||||
name: 'home',
|
name: 'home',
|
||||||
component: () => import('@/views/HomeView.vue'),
|
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',
|
path: '/swagger',
|
||||||
name: 'api',
|
name: 'api',
|
||||||
@ -22,4 +43,10 @@ const router = createRouter({
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.beforeEach((to, from, next) => {
|
||||||
|
loadingBar.start();
|
||||||
|
next();
|
||||||
|
setTimeout(() => loadingBar.finish(), 100);
|
||||||
|
});
|
||||||
|
|
||||||
export { router };
|
export { router };
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { login, woIsMe } from '@/api';
|
import { login, logout, woIsMe } from '@/api';
|
||||||
import { LocalStorageApi } from '@/util';
|
import CookiesApi from '@/util/Cookies.ts';
|
||||||
|
|
||||||
export const UseAuthStore = defineStore('auth', {
|
export const UseAuthStore = defineStore('auth', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
@ -39,8 +39,11 @@ export const UseAuthStore = defineStore('auth', {
|
|||||||
})
|
})
|
||||||
.catch(() => this.$reset());
|
.catch(() => this.$reset());
|
||||||
},
|
},
|
||||||
|
logout() {
|
||||||
|
logout().then(() => this.$reset());
|
||||||
|
},
|
||||||
},
|
},
|
||||||
persist: {
|
persist: {
|
||||||
storage: LocalStorageApi.StorageApi,
|
storage: CookiesApi.StorageApi,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
|
|
||||||
import { useColorMode } from '@vueuse/core';
|
import { useColorMode } from '@vueuse/core';
|
||||||
|
import { i18n } from '@/plugin';
|
||||||
import { LocalStorageApi } from '@/util/Cookies.ts';
|
import { LocalStorageApi } from '@/util/Cookies.ts';
|
||||||
|
|
||||||
export const UseSettingStore = defineStore('setting', {
|
export const UseSettingStore = defineStore('setting', {
|
||||||
@ -11,9 +12,23 @@ export const UseSettingStore = defineStore('setting', {
|
|||||||
theme: state,
|
theme: state,
|
||||||
appName: '',
|
appName: '',
|
||||||
appVersion: '',
|
appVersion: '',
|
||||||
|
language: 'zh-CN',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
persist: {
|
persist: {
|
||||||
storage: LocalStorageApi.StorageApi,
|
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;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
9
web-src/views/FileView.vue
Normal file
9
web-src/views/FileView.vue
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<template>
|
||||||
|
<div></div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { router } from '@/plugin';
|
||||||
|
|
||||||
|
console.log(router.currentRoute);
|
||||||
|
</script>
|
||||||
|
<style scoped></style>
|
@ -1,10 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<NButton @click="useAuthStore.login('admin', 'admin')">登录</NButton>
|
<div>
|
||||||
|
<NButton @click="useAuthStore.login('admin', 'admin')">登录</NButton>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { UseAuthStore } from '@/plugin';
|
import { UseAuthStore } from '@/plugin';
|
||||||
|
|
||||||
const useAuthStore = UseAuthStore();
|
const useAuthStore = UseAuthStore();
|
||||||
console.log(useAuthStore);
|
|
||||||
</script>
|
</script>
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
3
web-src/views/RegisterView.vue
Normal file
3
web-src/views/RegisterView.vue
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<template></template>
|
||||||
|
<script lang="ts" setup></script>
|
||||||
|
<style scoped></style>
|
19
web-src/views/UserView.vue
Normal file
19
web-src/views/UserView.vue
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<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>
|
27
web-src/views/loginView.vue
Normal file
27
web-src/views/loginView.vue
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<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>
|
Loading…
x
Reference in New Issue
Block a user