Compare commits
4 Commits
2a6672613a
...
067889f096
Author | SHA1 | Date | |
---|---|---|---|
067889f096 | |||
953a370233 | |||
7f8defeee2 | |||
67d99ccd42 |
@ -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",
|
||||||
|
@ -28,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": "9.14.3",
|
"vue-i18n": "11",
|
||||||
"vue-router": "^4.5.1"
|
"vue-router": "^4.5.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -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>
|
||||||
|
@ -1,194 +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" :data="v3" />
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</n-collapse>
|
|
||||||
</NCard>
|
|
||||||
</n-tab-pane>
|
|
||||||
<n-tab-pane name="schema" 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 :id="`schema+${k}`" 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;
|
|
||||||
setTimeout(()=>{
|
|
||||||
const ele = document.getElementById(to.hash.replace('#', ''));
|
|
||||||
ele?.scrollIntoView();
|
|
||||||
const ele2 = ele?.children[0].children[0];
|
|
||||||
ele2?.click()
|
|
||||||
},100)
|
|
||||||
}, 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>
|
|
@ -1,90 +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>
|
|
||||||
<label style="font-weight: 700;color:var(--color-des-text);">{{
|
|
||||||
data?.summary
|
|
||||||
}}</label>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<div></div>
|
|
||||||
</n-collapse-item>
|
|
||||||
</template>
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import type { OpenAPIV3_1 } from 'openapi-types';
|
|
||||||
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
data:Object as PropType<OpenAPIV3_1.PathItemObject>
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
@mixin method-style($color) {
|
|
||||||
background: rgba($color, 0.1);
|
|
||||||
border-radius: 3px;
|
|
||||||
border: 1px solid $color !important;
|
|
||||||
--color: #{$color};
|
|
||||||
--color-des-text: rgba(var(--text-color), 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.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>
|
|
@ -1,32 +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>
|
|
||||||
<label style="font-weight: 700;color:var(--color-des-text);"></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;
|
|
||||||
--color-des-text: rgba(var(--text-color), 0.5);
|
|
||||||
border: 1px solid $color !important;
|
|
||||||
--color: #{$color};
|
|
||||||
}
|
|
||||||
|
|
||||||
.datas {
|
|
||||||
@include method-style(#4182b8);
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,40 +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>
|
|
||||||
<label style="font-weight: 700;color:var(--color-des-text);">{{ data?.description
|
|
||||||
}}</label>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<div></div>
|
|
||||||
</n-collapse-item>
|
|
||||||
</template>
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import type { PropType } from 'vue';
|
|
||||||
import type { OpenAPIV3_1 } from 'openapi-types';
|
|
||||||
const props = defineProps({
|
|
||||||
name: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
data:{
|
|
||||||
type:Object as PropType<OpenAPIV3_1.SecuritySchemeObject>
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(props.data);
|
|
||||||
</script>
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
@mixin method-style($color) {
|
|
||||||
background: rgba($color, 0.1);
|
|
||||||
--color-des-text: rgba(var(--text-color), 0.5);
|
|
||||||
border-radius: 3px;
|
|
||||||
border: 1px solid $color !important;
|
|
||||||
--color: #{$color};
|
|
||||||
}
|
|
||||||
|
|
||||||
.datas {
|
|
||||||
@include method-style(#ac44af);
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"nav": {
|
|
||||||
"title": {
|
|
||||||
"home": "Home"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"nav": {
|
|
||||||
"title": {
|
|
||||||
"home": "主页"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
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>
|
||||||
@ -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"
|
||||||
@ -87,11 +87,11 @@
|
|||||||
import Icon from '@/components/Icon.vue';
|
import Icon from '@/components/Icon.vue';
|
||||||
import { UseBoolRef } from '@/util';
|
import { UseBoolRef } from '@/util';
|
||||||
import {
|
import {
|
||||||
i18n,
|
|
||||||
type languageIndexItemValueType,
|
type languageIndexItemValueType,
|
||||||
message,
|
message,
|
||||||
messageData,
|
messageData,
|
||||||
router,
|
router,
|
||||||
|
t,
|
||||||
UseAuthStore,
|
UseAuthStore,
|
||||||
UseSettingStore,
|
UseSettingStore,
|
||||||
} from '@/plugin';
|
} from '@/plugin';
|
||||||
@ -156,15 +156,13 @@ 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`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const t = i18n.global.t;
|
|
||||||
window.i18n = i18n;
|
|
||||||
console.log(t);
|
console.log(t);
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -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);
|
||||||
@ -47,6 +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}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function t(d: string): string {
|
||||||
|
return i18n.global.t(d);
|
||||||
|
}
|
||||||
|
@ -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;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user