Compare commits

...

6 Commits

Author SHA1 Message Date
067889f096
Merge pull request 'dev' (#7) from dev into master
All checks were successful
Gitea Actions Build / Build (push) Successful in 1m40s
Reviewed-on: #7
2025-07-01 23:27:46 +08:00
953a370233
完善翻译 2025-07-01 23:27:03 +08:00
7f8defeee2
test gpg 2025-07-01 22:24:18 +08:00
67d99ccd42 no message 2025-07-01 22:10:15 +08:00
2a6672613a
no message
All checks were successful
Gitea Actions Build / Build (push) Successful in 1m23s
2025-07-01 08:03:23 +08:00
3071859a3d
no message 2025-07-01 10:25:43 +08:00
23 changed files with 237 additions and 419 deletions

View File

@ -25,12 +25,12 @@
"id": { "id": {
"type": "string", "type": "string",
"description": "语言ID", "description": "语言ID",
"pattern": "^[a-z]{2}-[a-z]{2}$" "pattern": "^[a-z]{2}-[A-Z]{2}$"
}, },
"file": { "file": {
"type": "string", "type": "string",
"description": "语言文件在language文件夹的位置", "description": "语言文件在language文件夹的位置",
"pattern": "^[A-Z]{2}_[A-Z]{2}\\.json$" "pattern": "^[a-z]{2}-[A-Z]{2}\\.json$"
}, },
"icon": { "icon": {
"type": "string", "type": "string",

View File

@ -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",
@ -27,7 +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": "12.0.0-alpha.2", "vue-i18n": "11",
"vue-router": "^4.5.1" "vue-router": "^4.5.1"
}, },
"devDependencies": { "devDependencies": {

View File

@ -1,8 +1,10 @@
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;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
@ -11,8 +13,8 @@ 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 主路由")
@RequestMapping(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 -> {
try (InputStream stream = this.getClass().getResourceAsStream("/html/index.html")) { try (InputStream stream = this.getClass().getResourceAsStream("/html/index.html")) {

View File

@ -6,6 +6,7 @@
.n-collapse-item__header .n-collapse-item__header
padding-top: 0 !important padding-top: 0 !important
.n-collapse-item__header-main
padding-left: 10px !important padding-left: 10px !important
.n-collapse-item__content-wrapper .n-collapse-item__content-wrapper

View File

@ -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 {

View File

@ -1,10 +1,14 @@
<template> <template>
<div> <n-code :code="recode" :language="language" show-line-numbers style="width: fit-content !important; padding: 10px" />
<n-code :code="recode" :language="language" show-line-numbers />
</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
const recode = ref(''); const recode = computed(() => {
if (typeof prop.code === 'object') {
return JSON.stringify(prop.code, null, 2);
} else {
return prop.code;
}
});
const prop = defineProps({ const prop = defineProps({
code: { code: {
@ -13,13 +17,5 @@ const prop = defineProps({
}, },
language: String, language: String,
}); });
onMounted(() => {
if (typeof prop.code === 'object') {
recode.value = JSON.stringify(prop.code, null, 2);
} else {
recode.value = prop.code;
}
});
</script> </script>
<style scoped></style> <style lang="scss" scoped></style>

View File

@ -1,181 +0,0 @@
<template>
<div>
<n-tabs v-model:value="tabValue" :bar-width="28" placement="left" style="height: 100%" type="line">
<n-tab-pane name="api信息" tab="api信息">
<NCard>
<div class="title">
<label class="lable">{{ data?.info.title }}</label>
<n-tag :bordered="false" style="font-weight: 900" type="success">V{{ data?.info.version }}</n-tag>
<n-tag :bordered="false" style="font-weight: 900" type="info">openapi-{{ data?.openapi }}</n-tag>
</div>
<div style="margin-bottom: 20px">
<a :href="apiUrl">{{ apiUrl }}</a>
</div>
<div style="margin-bottom: 20px">{{ data?.info.summary }}</div>
<div style="margin-bottom: 20px">{{ data?.info.description }}</div>
<div><a :href="data?.info.termsOfService">Terms of service</a></div>
<div>
<a :href="data?.info.contact?.url">{{ data?.info.contact?.name }} - Website</a>
</div>
<div>
<a :href="data?.info.contact?.url">Send email to {{ data?.info.contact?.name }}</a>
</div>
<div>
<a :href="data?.info.license?.url">{{ data?.info.license?.name }}</a>
</div>
<div>
<a :href="data?.externalDocs?.url">{{ data?.externalDocs?.description }}</a>
</div>
</NCard>
</n-tab-pane>
<n-tab-pane v-for="(v, k) in tags" :name="k" :tab="k">
<NCard
:segmented="{
content: true,
footer: 'soft',
}">
<template #header>
<div>
<label style="font-weight: 700; font-size: 27px">{{ k }}</label>
</div>
<div>
<label style="font-weight: 700; font-size: 20px">{{ v.description }}</label>
</div>
</template>
<n-collapse accordion default-expanded-names="1">
<template v-for="(v2, k2) in v.data">
<template v-for="(v3, k3) in v2">
<OpenApiDocMethod :id="`${k}+${k3}-${k2}`" :type="k3" :url="k2" />
</template>
</template>
</n-collapse>
</NCard>
</n-tab-pane>
<n-tab-pane name="架构" tab="架构">
<NCard
:segmented="{
content: true,
footer: 'soft',
}">
<template #header>
<div>
<label style="font-weight: 700; font-size: 27px">架构</label>
</div>
<div>
<label style="font-weight: 700; font-size: 20px">一些类型而已</label>
</div>
</template>
<n-collapse accordion default-expanded-names="2">
<OpenApiDocSchema v-for="(v, k) in data?.components?.schemas" :data="v" :name="k" />
</n-collapse>
</NCard>
</n-tab-pane>
<n-tab-pane name="权限验证" tab="权限验证">
<NCard
:segmented="{
content: true,
footer: 'soft',
}">
<template #header>
<div>
<label style="font-weight: 700; font-size: 27px">权限验证</label>
</div>
<div>
<label style="font-weight: 700; font-size: 20px">验证你的权限</label>
</div>
</template>
<n-collapse accordion default-expanded-names="3">
<OpenApiDocSecuritySchemes v-for="(v, k) in data?.components?.securitySchemes" :data="v" :name="k" />
</n-collapse>
</NCard>
</n-tab-pane>
</n-tabs>
</div>
</template>
<script lang="ts" setup>
import { onMounted, type PropType, ref } from 'vue';
import { NTag } from 'naive-ui';
import type { OpenAPIV3_1 } from 'openapi-types';
import OpenApiDocMethod from '@/components/OpenApiDocMethod.vue';
import { router } from '@/plugin';
import type { RouteLocationNormalizedGeneric } from 'vue-router';
import OpenApiDocSchema from '@/components/OpenApiDocSchema.vue';
import OpenApiDocSecuritySchemes from '@/components/OpenApiDocSecuritySchemes.vue';
const data = ref<OpenAPIV3_1.Document>();
const tags = ref<{
[key: string]: { data: { [key: string]: OpenAPIV3_1.PathItemObject }; description: string };
}>({});
export type TFunctionType = (args: string) => string;
export type getApiFunType = (args: string) => PromiseLike<string>;
onMounted(() => {
prop
.getApiFun(prop.apiUrl)
.then((res) => {
data.value = JSON.parse(res);
return data.value;
})
.then((res) => {
for (const pathsKey in res?.paths) {
const models = res?.paths[pathsKey];
for (const model in models) {
for (const i of models[model]?.tags) {
if (!tags.value[i]) {
tags.value[i] = { data: {}, description: data.value?.tags?.find((is) => is.name === i)?.description };
}
tags.value[i].data[pathsKey] = models;
}
}
}
})
.then(() => {
hashback(router.currentRoute.value);
});
});
function hashback(to: RouteLocationNormalizedGeneric) {
if (!to.hash) return;
const tab = to.hash.split('+')?.[0].replace('#', '');
setTimeout(() => {
tabValue.value = tab;
document.getElementById(to.hash.replace('#', ''))?.scrollIntoView();
}, 100);
}
const removeAfterEach = router.afterEach(hashback);
onUnmounted(() => removeAfterEach());
const tabValue = ref<string>('api信息');
const prop = defineProps({
apiUrl: {
type: String,
required: true,
},
getApiFun: {
type: Function as PropType<getApiFunType>,
default: async (url: string) => (await fetch(url)).text(),
},
i18nFun: {
type: Function as PropType<TFunctionType>,
default: (s: string) => s,
},
});
</script>
<style lang="scss" scoped>
.lable {
font-size: 36px;
font-weight: 900;
}
.title {
display: flex;
justify-content: left;
align-items: flex-start;
gap: 5px;
}
</style>

View File

@ -1,83 +0,0 @@
<template>
<n-collapse-item
:name="`${url}-${type}`"
:title="url"
v-bind:class="{
get: type === 'get',
put: type === 'put',
post: type === 'post',
delete: type === 'delete',
options: type === 'options',
head: type === 'head',
patch: type === 'patch',
}">
<template #header>
<div f n-c style="padding: 10px; height: calc(100% - 10px); display: flex; gap: 20px">
<div
c-c
f
style="
width: 80px;
height: 30px;
background: var(--color);
border-radius: 2px;
font-weight: 700;
color: #fff;
">
{{ type.toUpperCase() }}
</div>
<label style="font-weight: 700">{{ url }}</label>
</div>
</template>
<div></div>
</n-collapse-item>
</template>
<script lang="ts" setup>
const prop = defineProps({
type: {
type: String as PropType<'get' | 'post' | 'put' | 'delete' | 'patch' | 'head' | 'options' | 'trace' | 'connect'>,
default: 'get',
},
url: {
type: [String, Number],
required: true,
},
});
</script>
<style lang="scss" scoped>
@mixin method-style($color) {
background: rgba($color, 0.1);
border-radius: 3px;
border: 1px solid $color !important;
--color: #{$color};
}
.get {
@include method-style(#61affe);
}
.put {
@include method-style(#fca130ff);
}
.post {
@include method-style(#49cc90);
}
.delete {
@include method-style(#f93e3e);
}
.options {
@include method-style(#0d5aa7);
}
.head {
@include method-style(#9012fe);
}
.patch {
@include method-style(#50e3c2);
}
</style>

View File

@ -1,30 +0,0 @@
<template>
<n-collapse-item :name="name" :title="name" class="datas">
<template #header>
<div f n-c style="padding: 10px; height: calc(100% - 10px); display: flex; gap: 20px">
<label style="font-weight: 700">{{ name }}</label>
</div>
</template>
<div></div>
</n-collapse-item>
</template>
<script lang="ts" setup>
const props = defineProps({
name: {
type: String,
required: true,
},
});
</script>
<style lang="scss" scoped>
@mixin method-style($color) {
background: rgba($color, 0.1);
border-radius: 3px;
border: 1px solid $color !important;
--color: #{$color};
}
.datas {
@include method-style(#4182b8);
}
</style>

View File

@ -1,30 +0,0 @@
<template>
<n-collapse-item :name="name" :title="name" class="datas">
<template #header>
<div f n-c style="padding: 10px; height: calc(100% - 10px); display: flex; gap: 20px">
<label style="font-weight: 700">{{ name }}</label>
</div>
</template>
<div></div>
</n-collapse-item>
</template>
<script lang="ts" setup>
const props = defineProps({
name: {
type: String,
required: true,
},
});
</script>
<style lang="scss" scoped>
@mixin method-style($color) {
background: rgba($color, 0.1);
border-radius: 3px;
border: 1px solid $color !important;
--color: #{$color};
}
.datas {
@include method-style(#ac44af);
}
</style>

View File

@ -1,7 +0,0 @@
{
"nav": {
"title": {
"home": "Home"
}
}
}

View File

@ -1,7 +0,0 @@
{
"nav": {
"title": {
"home": "主页"
}
}
}

View File

@ -3,16 +3,28 @@
"iconsUrl": "https://icones.js.org/collection/emojione-v1?category=Flags", "iconsUrl": "https://icones.js.org/collection/emojione-v1?category=Flags",
"languages": [ "languages": [
{ {
"title": "中国-汉语", "title": "zh-CN 简体中文",
"id": "zh-cn", "id": "zh-CN",
"file": "ZH_CN.json", "file": "zh-CN.json",
"icon": "emojione-v1:flag-for-china" "icon": "emojione-v1:flag-for-china"
}, },
{ {
"title": "US-English", "title": "zh-TW 简体中文",
"id": "en-us", "id": "zh-TW",
"file": "EN_US.json", "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" "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

@ -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"
}
}
}

View 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": "ユーザー名"
}
}
}

View 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": "用户名"
}
}
}

View 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": "使用者名稱"
}
}
}

View File

@ -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>
@ -26,7 +26,7 @@
}" }"
@click="openFile()"> @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 <div
@ -36,7 +36,7 @@
}" }"
@click="router.push({ name: 'user' })"> @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
@ -46,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
@ -55,23 +55,23 @@
}" }"
@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>主题配置</n-divider> <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 <n-select
:options="messageData" :options="messageData"
:render-label="renderLabel" :render-label="renderLabel"
@ -156,12 +156,14 @@ function openFile() {
path: `/file/${useAuthStore.username}`, path: `/file/${useAuthStore.username}`,
}); });
} else { } else {
message.error('请先登录'); message.error(() => t('message.login.plaselogin'));
router.push({ router.push({
path: `/login`, path: `/login`,
}); });
} }
} }
console.log(t);
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
html.dark { html.dark {

View File

@ -4,13 +4,12 @@ import './assets/index.sass';
import { createApp } from 'vue'; import { createApp } from 'vue';
import App from './App.vue'; import App from './App.vue';
import { installI18n, pinia, router } from './plugin'; import { i18n, installI18n, pinia, router } from './plugin';
installI18n().then((i18n) => { const app = createApp(App);
const app = createApp(App); app.use(pinia);
app.use(i18n); app.use(i18n);
app.use(pinia); installI18n().then(() => {
app.use(router); app.use(router);
app.mount('#app'); app.mount('#app');
console.log(i18n);
}); });

View File

@ -4,7 +4,7 @@ import { ref } from 'vue';
export * from './type'; export * from './type';
const languagedatas = import.meta.glob('@/language/*.json'); const languagedatas = import.meta.glob('@/language/**/*.json');
const loadIndexFile = async (): Promise<languageIndexType> => { const loadIndexFile = async (): Promise<languageIndexType> => {
try { try {
@ -20,7 +20,9 @@ const loadIndexFile = async (): Promise<languageIndexType> => {
const loadLanguageFile = async (fileName: string): Promise<languageType> => { const loadLanguageFile = async (fileName: string): Promise<languageType> => {
try { try {
const module = (await languagedatas[`/language/${fileName}`]()) as { default: languageType }; const module = (await languagedatas[`/language/${fileName}`]()) as {
default: languageType;
};
return module.default; return module.default;
} catch (error) { } catch (error) {
console.error(`加载语言文件失败:${fileName}`, error); console.error(`加载语言文件失败:${fileName}`, error);
@ -32,22 +34,12 @@ export const messages: languageType | any = {};
export const messageData = ref<languageIndexItemValueType[]>([]); export const messageData = ref<languageIndexItemValueType[]>([]);
export let i18n: ReturnType< export const i18n = createI18n({
typeof createI18n< legacy: false,
{ locale: 'zh-cn',
legacy: boolean; fallbackLocale: 'zh-cn',
locale: string; messages,
fallbackLocale: string; });
messages: any;
},
any,
any
>
>;
export function t(data: string): string {
return i18n.global.t(data);
}
export async function installI18n() { export async function installI18n() {
const data: languageIndexType = await loadIndexFile(); const data: languageIndexType = await loadIndexFile();
@ -57,16 +49,10 @@ export async function installI18n() {
value: i.id, value: i.id,
icon: i.icon, icon: i.icon,
}); });
messages[i.id] = await loadLanguageFile(i.file); messages[i.id] = await loadLanguageFile(`lang/${i.file}`);
} }
console.log(messages); }
console.log(languagedatas);
console.log(messageData); export function t(d: string): string {
i18n = createI18n({ return i18n.global.t(d);
legacy: false,
locale: 'zh-cn',
fallbackLocale: 'zh-cn',
messages,
});
return i18n;
} }

View File

@ -12,7 +12,7 @@ export const UseSettingStore = defineStore('setting', {
theme: state, theme: state,
appName: '', appName: '',
appVersion: '', appVersion: '',
language: 'zh-cn', language: 'zh-CN',
}; };
}, },
persist: { persist: {
@ -23,10 +23,12 @@ export const UseSettingStore = defineStore('setting', {
setLanguage(data: string | null) { setLanguage(data: string | null) {
if (data == null) { if (data == null) {
i18n.global.locale.value = this.language; i18n.global.locale.value = this.language;
document.documentElement.lang = this.language;
return; return;
} }
this.language = data; this.language = data;
i18n.global.locale.value = data; i18n.global.locale.value = data;
document.documentElement.lang = data;
}, },
}, },
}); });

View File

@ -1,7 +1,18 @@
<template> <template>
<OpenApiDoc api-url="/apis/swagger/api.json" style="margin: 20px; height: calc(100% - 30px)" /> <div id="swaggerContainer"></div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import OpenApiDoc from '@/components/OpenApiDoc.vue'; 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> </script>
<style scoped></style> <style scoped></style>

View File

@ -6,20 +6,22 @@
content: true, content: true,
footer: 'soft', footer: 'soft',
}" }"
style="width: 430px" :title="t('view.login.title')"
title="登录"> style="width: 430px">
<n-form border label-align="right" label-placement="left" label-width="auto"> <n-form border label-align="right" label-placement="left" label-width="auto">
<n-form-item label="用户名"> <n-form-item :label="t('view.login.username')">
<n-input /> <n-input />
</n-form-item> </n-form-item>
<n-form-item label="密码"> <n-form-item :label="t('view.login.password')">
<n-input /> <n-input />
</n-form-item> </n-form-item>
</n-form> </n-form>
<n-button block secondary strong type="primary">登录</n-button> <n-button block secondary strong type="primary">{{ t('view.login.title') }}</n-button>
</n-card> </n-card>
</n-spin> </n-spin>
</div> </div>
</template> </template>
<script lang="ts" setup></script> <script lang="ts" setup>
import { t } from '@/plugin';
</script>
<style scoped></style> <style scoped></style>