base UI
This commit is contained in:
parent
a67b2d7724
commit
8c0b79066f
18
package-lock.json
generated
Normal file
18
package-lock.json
generated
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "recored-ui",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"devDependencies": {
|
||||||
|
"@vicons/fa": "^0.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vicons/fa": {
|
||||||
|
"version": "0.12.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@vicons/fa/-/fa-0.12.0.tgz",
|
||||||
|
"integrity": "sha512-g2PIeJLsTHUjt6bK63LxqC0uYQB7iu+xViJOxvp1s8b9/akpXVPVWjDTTsP980/0KYyMMe4U7F/aUo7wY+MsXA==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
5
package.json
Normal file
5
package.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"devDependencies": {
|
||||||
|
"@vicons/fa": "^0.12.0"
|
||||||
|
}
|
||||||
|
}
|
103
web/src/App.vue
103
web/src/App.vue
@ -1,85 +1,30 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { RouterLink, RouterView } from 'vue-router'
|
import {
|
||||||
import HelloWorld from './components/HelloWorld.vue'
|
NNotificationProvider,
|
||||||
|
NConfigProvider,
|
||||||
|
NGlobalStyle,
|
||||||
|
useOsTheme,
|
||||||
|
darkTheme,
|
||||||
|
lightTheme,
|
||||||
|
type GlobalTheme
|
||||||
|
} from "naive-ui";
|
||||||
|
import { RouterView } from "vue-router";
|
||||||
|
import { onMounted } from "vue";
|
||||||
|
|
||||||
|
const osThemeRef = useOsTheme()
|
||||||
|
const theme = defineModel<GlobalTheme>()
|
||||||
|
theme.value = osThemeRef.value === 'dark' ? darkTheme : lightTheme
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
document.title = 'reCoreD-UI'
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<header>
|
<NConfigProvider :theme="theme">
|
||||||
<img alt="Vue logo" class="logo" src="@/assets/logo.svg" width="125" height="125" />
|
<NGlobalStyle />
|
||||||
|
<NNotificationProvider :max="3">
|
||||||
<div class="wrapper">
|
|
||||||
<HelloWorld msg="You did it!" />
|
|
||||||
|
|
||||||
<nav>
|
|
||||||
<RouterLink to="/">Home</RouterLink>
|
|
||||||
<RouterLink to="/about">About</RouterLink>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<RouterView />
|
<RouterView />
|
||||||
|
</NNotificationProvider>
|
||||||
|
</NConfigProvider>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
header {
|
|
||||||
line-height: 1.5;
|
|
||||||
max-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
display: block;
|
|
||||||
margin: 0 auto 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav {
|
|
||||||
width: 100%;
|
|
||||||
font-size: 12px;
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav a.router-link-exact-active {
|
|
||||||
color: var(--color-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
nav a.router-link-exact-active:hover {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav a {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 0 1rem;
|
|
||||||
border-left: 1px solid var(--color-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
nav a:first-of-type {
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
header {
|
|
||||||
display: flex;
|
|
||||||
place-items: center;
|
|
||||||
padding-right: calc(var(--section-gap) / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
margin: 0 2rem 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
header .wrapper {
|
|
||||||
display: flex;
|
|
||||||
place-items: flex-start;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav {
|
|
||||||
text-align: left;
|
|
||||||
margin-left: -1rem;
|
|
||||||
font-size: 1rem;
|
|
||||||
|
|
||||||
padding: 1rem 0;
|
|
||||||
margin-top: 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import axios, { type AxiosInstance, type AxiosRequestConfig, type AxiosResponse, type InternalAxiosRequestConfig } from "axios";
|
import axios, { type AxiosInstance, type AxiosRequestConfig, type AxiosResponse, type InternalAxiosRequestConfig } from "axios";
|
||||||
import { useLoadingBar, useNotification } from 'naive-ui'
|
|
||||||
import { type Record } from '@/stores/records';
|
import { type Record } from '@/stores/records';
|
||||||
import { type Domain } from "@/stores/domains";
|
import { type Domain } from "@/stores/domains";
|
||||||
|
|
||||||
@ -9,84 +8,57 @@ type Result<T> = {
|
|||||||
data: T
|
data: T
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Request {
|
// 5 second.
|
||||||
private instance: AxiosInstance;
|
const notificationDuration = 5000
|
||||||
private baseConfig: AxiosRequestConfig = { baseURL: "api/v1" }
|
const messages = new Map<number, {
|
||||||
private loadingBar = useLoadingBar()
|
title: string, content: string, duration: number
|
||||||
private notification = useNotification()
|
|
||||||
private messages = new Map<number, {
|
|
||||||
title: string, content: string
|
|
||||||
}>(
|
}>(
|
||||||
// TODO: i18n
|
// TODO: i18n
|
||||||
[
|
[
|
||||||
[400, {
|
[400, {
|
||||||
title: "请求错误 (400)",
|
title: "请求错误 (400)",
|
||||||
content: "参数提交错误"
|
content: "参数提交错误",
|
||||||
|
duration: notificationDuration
|
||||||
}],
|
}],
|
||||||
[401, {
|
[401, {
|
||||||
title: "未授权 (401)",
|
title: "未授权 (401)",
|
||||||
content: "请刷新页面重新登录"
|
content: "请刷新页面重新登录",
|
||||||
|
duration: notificationDuration
|
||||||
}],
|
}],
|
||||||
[403, {
|
[403, {
|
||||||
title: "拒绝访问 (403)",
|
title: "拒绝访问 (403)",
|
||||||
content: "你没有权限!"
|
content: "你没有权限!",
|
||||||
|
duration: notificationDuration
|
||||||
}],
|
}],
|
||||||
[404, {
|
[404, {
|
||||||
title: "查无此项 (404)",
|
title: "查无此项 (404)",
|
||||||
content: "没有该项内容"
|
content: "没有该项内容",
|
||||||
|
duration: notificationDuration
|
||||||
}],
|
}],
|
||||||
[500, {
|
[500, {
|
||||||
title: "服务器错误 (500)",
|
title: "服务器错误 (500)",
|
||||||
content: "请检查系统日志"
|
content: "请检查系统日志",
|
||||||
|
duration: notificationDuration
|
||||||
}]
|
}]
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export function getErrorInfo(err: any) {
|
||||||
|
const msg = messages.get(err.response.status)
|
||||||
|
return msg? msg: {
|
||||||
|
title: "未知错误",
|
||||||
|
content: "请打开控制台了解详情",
|
||||||
|
duration: notificationDuration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Request {
|
||||||
|
private instance: AxiosInstance;
|
||||||
|
private baseConfig: AxiosRequestConfig = { baseURL: "api/v1" }
|
||||||
|
|
||||||
constructor(config: AxiosRequestConfig) {
|
constructor(config: AxiosRequestConfig) {
|
||||||
this.instance = axios.create(Object.assign(this.baseConfig, config))
|
this.instance = axios.create(Object.assign(this.baseConfig, config))
|
||||||
this.setupInceptors()
|
|
||||||
}
|
|
||||||
|
|
||||||
private setupInceptors() {
|
|
||||||
this.setupRequestInterceptors()
|
|
||||||
this.setupResponseInterceptors()
|
|
||||||
}
|
|
||||||
|
|
||||||
private setupRequestInterceptors() {
|
|
||||||
const fulFilled = (res: InternalAxiosRequestConfig<any>) => {
|
|
||||||
this.loadingBar.start()
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
const onError = (err: any) => {
|
|
||||||
this.loadingBar.error()
|
|
||||||
return Promise.reject(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.instance.interceptors.request.use(fulFilled, onError)
|
|
||||||
}
|
|
||||||
|
|
||||||
private setupResponseInterceptors() {
|
|
||||||
const fulFilled = (res: AxiosResponse) => {
|
|
||||||
this.loadingBar.finish()
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
const onError = (err: any) => {
|
|
||||||
this.loadingBar.error()
|
|
||||||
|
|
||||||
const msg = this.messages.get(err.response.status)
|
|
||||||
if (msg) {
|
|
||||||
this.notification.error(msg)
|
|
||||||
} else {
|
|
||||||
console.log(err.response)
|
|
||||||
this.notification.error({
|
|
||||||
title: "未知错误",
|
|
||||||
content: "请打开控制台了解详情"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.reject(err.response)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.instance.interceptors.response.use(fulFilled, onError)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public request(config: AxiosRequestConfig): Promise<AxiosResponse> {
|
public request(config: AxiosRequestConfig): Promise<AxiosResponse> {
|
||||||
|
@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>
|
|
Before Width: | Height: | Size: 276 B |
@ -28,8 +28,8 @@ a,
|
|||||||
}
|
}
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr;
|
||||||
padding: 0 2rem;
|
padding: 2rem 2rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
35
web/src/components/domains/DomainInfo.vue
Normal file
35
web/src/components/domains/DomainInfo.vue
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
<NIcon class="icon" :component="AddressCard" />
|
||||||
|
<span> {{ domain.admin_email }}</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<NIcon class="icon" :component="Server" />
|
||||||
|
<span> {{ domain.main_dns }}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { type Domain } from "../../stores/domains";
|
||||||
|
import { NIcon } from "naive-ui";
|
||||||
|
import { AddressCard, Server } from "@vicons/fa";
|
||||||
|
defineProps<{
|
||||||
|
domain: Domain;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
div {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
padding-left: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
transform: translateY(2px);
|
||||||
|
}
|
||||||
|
</style>
|
51
web/src/components/domains/DomainOps.vue
Normal file
51
web/src/components/domains/DomainOps.vue
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<NFlex justify="end">
|
||||||
|
<NTooltip trigger="hover">
|
||||||
|
<template #trigger>
|
||||||
|
<NButton size="tiny" type="primary">
|
||||||
|
<template #icon>
|
||||||
|
<NIcon :component="Book" @click="loadRecord" />
|
||||||
|
</template>
|
||||||
|
</NButton>
|
||||||
|
</template>
|
||||||
|
查看/修改记录
|
||||||
|
</NTooltip>
|
||||||
|
<NTooltip trigger="hover">
|
||||||
|
<template #trigger>
|
||||||
|
<NButton size="tiny">
|
||||||
|
<template #icon>
|
||||||
|
<NIcon :component="EditRegular" />
|
||||||
|
</template>
|
||||||
|
</NButton>
|
||||||
|
</template>
|
||||||
|
修改
|
||||||
|
</NTooltip>
|
||||||
|
<NTooltip trigger="hover">
|
||||||
|
<template #trigger>
|
||||||
|
<NButton type="error" size="tiny">
|
||||||
|
<template #icon>
|
||||||
|
<NIcon :component="TrashAlt" />
|
||||||
|
</template>
|
||||||
|
</NButton>
|
||||||
|
</template>
|
||||||
|
删除
|
||||||
|
</NTooltip>
|
||||||
|
</NFlex>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { NSpace, NButton, NIcon, NTooltip, NFlex } from 'naive-ui'
|
||||||
|
import { TrashAlt, EditRegular, Book } from '@vicons/fa'
|
||||||
|
import { type Domain } from "../../stores/domains"
|
||||||
|
import router from '@/router';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
domain: Domain
|
||||||
|
}>()
|
||||||
|
|
||||||
|
function loadRecord() {
|
||||||
|
router.push(`/records/${props.domain.domain_name}`)
|
||||||
|
}
|
||||||
|
</script>
|
@ -10,12 +10,15 @@ const router = createRouter({
|
|||||||
component: HomeView
|
component: HomeView
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/about',
|
path: '/domains',
|
||||||
name: 'about',
|
name: 'domains',
|
||||||
// route level code-splitting
|
component: () => import('../views/DomainsView.vue')
|
||||||
// this generates a separate chunk (About.[hash].js) for this route
|
},
|
||||||
// which is lazy-loaded when the route is visited.
|
{
|
||||||
component: () => import('../views/AboutView.vue')
|
path: '/records/:domain',
|
||||||
|
name: 'records',
|
||||||
|
component: () => import('../views/RecordsView.vue'),
|
||||||
|
props: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
@ -36,7 +36,7 @@ const domainDevData: Domain[] = [
|
|||||||
retry_interval: 7200,
|
retry_interval: 7200,
|
||||||
expiry_period: 3600000,
|
expiry_period: 3600000,
|
||||||
negative_ttl: 86400
|
negative_ttl: 86400
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
export const useDomainStore = defineStore('domains', () => {
|
export const useDomainStore = defineStore('domains', () => {
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="about">
|
|
||||||
<h1>This is an about page</h1>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
.about {
|
|
||||||
min-height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
51
web/src/views/DomainsView.vue
Normal file
51
web/src/views/DomainsView.vue
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { NSpin, NFlex, NCard, NButton, NIcon, useNotification } from 'naive-ui'
|
||||||
|
import { PlusSquare } from "@vicons/fa"
|
||||||
|
import { onMounted } from 'vue'
|
||||||
|
import { useDomainStore } from '@/stores/domains'
|
||||||
|
import { getErrorInfo } from '@/apis/api'
|
||||||
|
import DomainInfo from '@/components/domains/DomainInfo.vue'
|
||||||
|
import DomainOps from '@/components/domains/DomainOps.vue'
|
||||||
|
|
||||||
|
const loading = defineModel<boolean>({ default: true });
|
||||||
|
const domainStore = useDomainStore()
|
||||||
|
const notification = useNotification()
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
try {
|
||||||
|
domainStore.loadDomains()
|
||||||
|
loading.value = false
|
||||||
|
} catch (error) {
|
||||||
|
const msg = getErrorInfo(error)
|
||||||
|
notification.error(msg)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<NSpin size="large" v-if="loading" />
|
||||||
|
<NFlex v-else id="domains" vertical>
|
||||||
|
<NCard v-for="domain in domainStore.domains" :title="domain.domain_name" v-bind:key="domain.id" size="large"
|
||||||
|
hoverable>
|
||||||
|
<DomainInfo :domain="domain" />
|
||||||
|
<template #action>
|
||||||
|
<DomainOps :domain="domain" />
|
||||||
|
</template>
|
||||||
|
</NCard>
|
||||||
|
<NCard hoverable>
|
||||||
|
<NButton block quaternary size="large">
|
||||||
|
<template #icon>
|
||||||
|
<NIcon :component="PlusSquare" :depth="5" />
|
||||||
|
</template>
|
||||||
|
</NButton>
|
||||||
|
</NCard>
|
||||||
|
</NFlex>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.n-card {
|
||||||
|
width: 32vw;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,9 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import TheWelcome from '../components/TheWelcome.vue'
|
import router from '@/router';
|
||||||
|
router.push("/domains")
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
|
||||||
<main>
|
|
||||||
<TheWelcome />
|
|
||||||
</main>
|
|
||||||
</template>
|
|
||||||
|
126
web/src/views/RecordsView.vue
Normal file
126
web/src/views/RecordsView.vue
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { NSpin, NPageHeader, useNotification, NFlex, NButton, NIcon, NGrid, NGi, NStatistic, NDataTable, NInput } from 'naive-ui'
|
||||||
|
import { onMounted } from 'vue'
|
||||||
|
import { useRecordStore, type Record, type SOARecord, RecordTypes } from '@/stores/records'
|
||||||
|
import { getErrorInfo } from '@/apis/api'
|
||||||
|
import { PlusSquare, RedoAlt, CheckCircle, Clock, Cogs, Search } from '@vicons/fa'
|
||||||
|
import router from '@/router';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
domain: string
|
||||||
|
}>()
|
||||||
|
const loading = defineModel<boolean>('loading', { default: true });
|
||||||
|
const records = defineModel<Record[]>('records');
|
||||||
|
const search = defineModel<string>('search', { default: '' })
|
||||||
|
const soa = defineModel<SOARecord | undefined>('soa')
|
||||||
|
|
||||||
|
const recordStore = useRecordStore()
|
||||||
|
const notification = useNotification()
|
||||||
|
onMounted(() => {
|
||||||
|
try {
|
||||||
|
refreshRecords()
|
||||||
|
} catch (err) {
|
||||||
|
const msg = getErrorInfo(err)
|
||||||
|
notification.error(msg)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function refreshRecords() {
|
||||||
|
recordStore.loadRecords(props.domain)
|
||||||
|
records.value = recordStore.records
|
||||||
|
soa.value = records.value?.find(e => e.record_type === RecordTypes.RecordTypeSOA)?.content as SOARecord
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function goBack() {
|
||||||
|
router.push('/domains')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div id="records">
|
||||||
|
<NSpin size="large" v-if="loading" />
|
||||||
|
<NPageHeader v-else title="DNS 记录" :subtitle="domain" @back="goBack">
|
||||||
|
<template #extra>
|
||||||
|
<NFlex :wrap="false" justify="end" inline>
|
||||||
|
<NButton type="primary">
|
||||||
|
<template #icon>
|
||||||
|
<NIcon>
|
||||||
|
<PlusSquare />
|
||||||
|
</NIcon>
|
||||||
|
</template>
|
||||||
|
新增
|
||||||
|
</NButton>
|
||||||
|
<NInput v-model:value="search" placeholder="搜索...">
|
||||||
|
<template #prefix>
|
||||||
|
<NIcon :component="Search" />
|
||||||
|
</template>
|
||||||
|
</NInput>
|
||||||
|
</NFlex>
|
||||||
|
</template>
|
||||||
|
<NGrid :cols="4">
|
||||||
|
<NGi>
|
||||||
|
<NStatistic :value="soa?.refresh">
|
||||||
|
<template #suffix>
|
||||||
|
s
|
||||||
|
</template>
|
||||||
|
<template #label>
|
||||||
|
<NIcon class="icon">
|
||||||
|
<RedoAlt />
|
||||||
|
</NIcon>
|
||||||
|
刷新时间
|
||||||
|
</template>
|
||||||
|
</NStatistic>
|
||||||
|
</NGi>
|
||||||
|
<NGi>
|
||||||
|
<NStatistic :value="soa?.retry">
|
||||||
|
<template #suffix>
|
||||||
|
s
|
||||||
|
</template>
|
||||||
|
<template #label>
|
||||||
|
<NIcon class="icon">
|
||||||
|
<CheckCircle />
|
||||||
|
</NIcon>
|
||||||
|
重试间隔
|
||||||
|
</template>
|
||||||
|
</NStatistic>
|
||||||
|
</NGi>
|
||||||
|
<NGi>
|
||||||
|
<NStatistic :value="soa?.expire">
|
||||||
|
<template #suffix>
|
||||||
|
s
|
||||||
|
</template>
|
||||||
|
<template #label>
|
||||||
|
<NIcon class="icon">
|
||||||
|
<Clock />
|
||||||
|
</NIcon>
|
||||||
|
超期时间
|
||||||
|
</template>
|
||||||
|
</NStatistic>
|
||||||
|
</NGi>
|
||||||
|
<NGi>
|
||||||
|
<NStatistic :value="soa?.minttl">
|
||||||
|
<template #suffix>
|
||||||
|
s
|
||||||
|
</template>
|
||||||
|
<template #label>
|
||||||
|
<NIcon class="icon">
|
||||||
|
<Cogs />
|
||||||
|
</NIcon>
|
||||||
|
缓存时间
|
||||||
|
</template>
|
||||||
|
</NStatistic>
|
||||||
|
</NGi>
|
||||||
|
</NGrid>
|
||||||
|
</NPageHeader>
|
||||||
|
<NDataTable :data="records">
|
||||||
|
|
||||||
|
</NDataTable>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.icon {
|
||||||
|
transform: translateY(2px);
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
Reference in New Issue
Block a user