mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-02-09 00:21:13 +08:00
Compare commits
1 Commits
codex/modi
...
codex/upda
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7010e8a058 |
@@ -8,13 +8,18 @@ import io.swagger.v3.oas.models.security.SecurityScheme;
|
||||
import io.swagger.v3.oas.models.servers.Server;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@RequiredArgsConstructor
|
||||
public class OpenApiConfig {
|
||||
|
||||
private final SpringDocProperties springDocProperties;
|
||||
|
||||
@Value("${springdoc.info.title}")
|
||||
private String title;
|
||||
|
||||
@@ -30,20 +35,21 @@ public class OpenApiConfig {
|
||||
@Value("${springdoc.info.header}")
|
||||
private String header;
|
||||
|
||||
@Value("${springdoc.api-docs.server-url}")
|
||||
private String serverUrl;
|
||||
|
||||
@Bean
|
||||
public OpenAPI openAPI() {
|
||||
SecurityScheme securityScheme = new SecurityScheme()
|
||||
.type(SecurityScheme.Type.HTTP)
|
||||
.scheme(scheme.toLowerCase())
|
||||
.bearerFormat("JWT")
|
||||
.in(SecurityScheme.In.HEADER)
|
||||
.name(header);
|
||||
.type(SecurityScheme.Type.HTTP)
|
||||
.scheme(scheme.toLowerCase())
|
||||
.bearerFormat("JWT")
|
||||
.in(SecurityScheme.In.HEADER)
|
||||
.name(header);
|
||||
|
||||
List<Server> servers = springDocProperties.getServers().stream()
|
||||
.map(s -> new Server().url(s.getUrl()).description(s.getDescription()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return new OpenAPI()
|
||||
.servers(List.of(new Server().url(serverUrl)))
|
||||
.servers(servers)
|
||||
.info(new Info()
|
||||
.title(title)
|
||||
.description(description)
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.openisle.config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "springdoc.api-docs")
|
||||
public class SpringDocProperties {
|
||||
private List<ServerConfig> servers = new ArrayList<>();
|
||||
|
||||
@Data
|
||||
public static class ServerConfig {
|
||||
private String url;
|
||||
private String description;
|
||||
}
|
||||
}
|
||||
@@ -108,7 +108,10 @@ rabbitmq.sharding.enabled=true
|
||||
# see https://springdoc.org/#springdoc-openapi-core-properties
|
||||
springdoc.api-docs.path=/api/v3/api-docs
|
||||
springdoc.api-docs.enabled=true
|
||||
springdoc.api-docs.server-url=${WEBSITE_URL:https://www.open-isle.com}
|
||||
springdoc.api-docs.servers[0].url=${WEBSITE_URL:https://www.open-isle.com}
|
||||
springdoc.api-docs.servers[0].description=正式环境
|
||||
springdoc.api-docs.servers[1].url=https://www.staging.open-isle.com
|
||||
springdoc.api-docs.servers[1].description=预发环境
|
||||
springdoc.info.title=OpenIsle
|
||||
springdoc.info.description=OpenIsle Open API Documentation
|
||||
springdoc.info.version=0.0.1
|
||||
|
||||
@@ -1,48 +1,23 @@
|
||||
<template>
|
||||
<div class="about-page">
|
||||
<BaseTabs v-model="selectedTab" :tabs="tabs">
|
||||
<template v-if="selectedTab === 'api'">
|
||||
<div class="about-api">
|
||||
<div class="about-api-title">调试Token</div>
|
||||
<div v-if="!authState.loggedIn" class="about-api-login">
|
||||
请<NuxtLink to="/login">登录</NuxtLink>后查看 Token
|
||||
</div>
|
||||
<div v-else class="about-api-token">
|
||||
<div class="token-row">
|
||||
<span class="token-text">{{ shortToken }}</span>
|
||||
<span @click="copyToken"><copy class="copy-icon" /></span>
|
||||
</div>
|
||||
<div class="warning-row">
|
||||
<info-icon class="warning-icon" />
|
||||
<div class="token-warning">请不要将 Token 泄露给他人</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="about-api-title">API文档和调试入口</div>
|
||||
<div class="about-api-link">API Playground <share /></div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="about-loading" v-if="isFetching">
|
||||
<l-hatch-spinner size="100" stroke="10" speed="1" color="var(--primary-color)" />
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="about-content"
|
||||
v-html="renderMarkdown(content)"
|
||||
@click="handleContentClick"
|
||||
></div>
|
||||
</template>
|
||||
<div class="about-loading" v-if="isFetching">
|
||||
<l-hatch-spinner size="100" stroke="10" speed="1" color="var(--primary-color)" />
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="about-content"
|
||||
v-html="renderMarkdown(content)"
|
||||
@click="handleContentClick"
|
||||
></div>
|
||||
</BaseTabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from '#imports'
|
||||
import { authState, getToken } from '~/utils/auth'
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import { handleMarkdownClick, renderMarkdown } from '~/utils/markdown'
|
||||
import BaseTabs from '~/components/BaseTabs.vue'
|
||||
import { toast } from '~/composables/useToast'
|
||||
|
||||
export default {
|
||||
name: 'AboutPageView',
|
||||
@@ -69,25 +44,11 @@ export default {
|
||||
label: '隐私政策',
|
||||
file: 'https://openisle-1307107697.cos.ap-guangzhou.myqcloud.com/assert/about/privacy.md',
|
||||
},
|
||||
{
|
||||
key: 'api',
|
||||
label: 'API与调试',
|
||||
},
|
||||
]
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const selectedTab = ref(tabs[0].key)
|
||||
const content = ref('')
|
||||
const token = computed(() => (authState.loggedIn ? getToken() : ''))
|
||||
|
||||
const shortToken = computed(() => {
|
||||
if (!token.value) return ''
|
||||
if (token.value.length <= 20) return token.value
|
||||
return `${token.value.slice(0, 20)}...${token.value.slice(-10)}`
|
||||
})
|
||||
|
||||
const loadContent = async (file) => {
|
||||
if (!file) return
|
||||
try {
|
||||
isFetching.value = true
|
||||
const res = await fetch(file)
|
||||
@@ -104,58 +65,19 @@ export default {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const initTab = route.query.tab
|
||||
if (initTab && tabs.find((t) => t.key === initTab)) {
|
||||
selectedTab.value = initTab
|
||||
const tab = tabs.find((t) => t.key === initTab)
|
||||
if (tab && tab.file) loadContent(tab.file)
|
||||
} else {
|
||||
loadContent(tabs[0].file)
|
||||
}
|
||||
loadContent(tabs[0].file)
|
||||
})
|
||||
|
||||
watch(selectedTab, (name) => {
|
||||
const tab = tabs.find((t) => t.key === name)
|
||||
if (tab && tab.file) loadContent(tab.file)
|
||||
router.replace({ query: { ...route.query, tab: name } })
|
||||
if (tab) loadContent(tab.file)
|
||||
})
|
||||
|
||||
watch(
|
||||
() => route.query.tab,
|
||||
(name) => {
|
||||
if (name && name !== selectedTab.value && tabs.find((t) => t.key === name)) {
|
||||
selectedTab.value = name
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
const copyToken = async () => {
|
||||
if (import.meta.client && token.value) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(token.value)
|
||||
toast.success('已复制 Token')
|
||||
} catch (e) {
|
||||
toast.error('复制失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleContentClick = (e) => {
|
||||
handleMarkdownClick(e)
|
||||
}
|
||||
|
||||
return {
|
||||
tabs,
|
||||
selectedTab,
|
||||
content,
|
||||
renderMarkdown,
|
||||
isFetching,
|
||||
handleContentClick,
|
||||
authState,
|
||||
token,
|
||||
copyToken,
|
||||
shortToken,
|
||||
}
|
||||
return { tabs, selectedTab, content, renderMarkdown, isFetching, handleContentClick }
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -179,56 +101,6 @@ export default {
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.about-api {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.about-api-title {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
margin-top: 30px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.warning-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.warning-icon {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.token-warning {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.token-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font: 14px;
|
||||
margin-bottom: 10px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
padding: 4px 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.about-api-link {
|
||||
color: var(--primary-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.about-api-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.about-tabs {
|
||||
width: 100vw;
|
||||
|
||||
@@ -77,7 +77,6 @@ import {
|
||||
Open,
|
||||
Dislike,
|
||||
CheckOne,
|
||||
Share,
|
||||
} from '@icon-park/vue-next'
|
||||
|
||||
export default defineNuxtPlugin((nuxtApp) => {
|
||||
@@ -158,5 +157,4 @@ export default defineNuxtPlugin((nuxtApp) => {
|
||||
nuxtApp.vueApp.component('OpenIcon', Open)
|
||||
nuxtApp.vueApp.component('Dislike', Dislike)
|
||||
nuxtApp.vueApp.component('CheckOne', CheckOne)
|
||||
nuxtApp.vueApp.component('Share', Share)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user