modal needed for edit
This commit is contained in:
parent
8c0b79066f
commit
69613f9b6e
49
web/package-lock.json
generated
49
web/package-lock.json
generated
@ -11,6 +11,7 @@
|
|||||||
"axios": "^1.6.8",
|
"axios": "^1.6.8",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"vue": "^3.4.21",
|
"vue": "^3.4.21",
|
||||||
|
"vue-i18n": "^9.11.0",
|
||||||
"vue-router": "^4.3.0"
|
"vue-router": "^4.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -878,6 +879,38 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@intlify/core-base": {
|
||||||
|
"version": "9.11.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-9.11.0.tgz",
|
||||||
|
"integrity": "sha512-cveOqAstjLZIiyatcP/HrzrQ87cZI8ScPQna3yvoM8zjcjcIRK1MRvmxUNlPdg0rTNJMZw7rixPVM58O5aHVPA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@intlify/message-compiler": "9.11.0",
|
||||||
|
"@intlify/shared": "9.11.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@intlify/message-compiler": {
|
||||||
|
"version": "9.11.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-9.11.0.tgz",
|
||||||
|
"integrity": "sha512-x31Gl7cscnoI4UUY1yaIy8e7vVMVW1VVlTXZz4SIHKqoSEUkfmgqK8NAx1e7RcoHEbICR7uyCbud0ZL1s4OGXQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@intlify/shared": "9.11.0",
|
||||||
|
"source-map-js": "^1.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@intlify/shared": {
|
||||||
|
"version": "9.11.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-9.11.0.tgz",
|
||||||
|
"integrity": "sha512-KHSNgi7sRjmSm7aD8QH8WFt9VfKaekJuJ473opbJlkGY3EDnDUU8ikIhG8PbasQbgNvbY3m3tWNGqk2omIdwMA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 16"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@jridgewell/gen-mapping": {
|
"node_modules/@jridgewell/gen-mapping": {
|
||||||
"version": "0.3.5",
|
"version": "0.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
|
||||||
@ -2500,6 +2533,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vue-i18n": {
|
||||||
|
"version": "9.11.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-9.11.0.tgz",
|
||||||
|
"integrity": "sha512-vU4gY6lu8Pdfs9BgKGiDAJmFDf88cceR47KcSB0VW4xJzUrXR/7qwqM7A8dQ2nedhoIDxoOm5Ro4pFd2KvJqbA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@intlify/core-base": "9.11.0",
|
||||||
|
"@intlify/shared": "9.11.0",
|
||||||
|
"@vue/devtools-api": "^6.5.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 16"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vue-router": {
|
"node_modules/vue-router": {
|
||||||
"version": "4.3.0",
|
"version": "4.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.3.0.tgz",
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
"axios": "^1.6.8",
|
"axios": "^1.6.8",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"vue": "^3.4.21",
|
"vue": "^3.4.21",
|
||||||
|
"vue-i18n": "^9.11.0",
|
||||||
"vue-router": "^4.3.0"
|
"vue-router": "^4.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -8,20 +8,27 @@ import {
|
|||||||
lightTheme,
|
lightTheme,
|
||||||
type GlobalTheme
|
type GlobalTheme
|
||||||
} from "naive-ui";
|
} from "naive-ui";
|
||||||
|
import { zhCN, dateZhCN, enUS, dateEnUS, type NLocale, type NDateLocale } from 'naive-ui'
|
||||||
import { RouterView } from "vue-router";
|
import { RouterView } from "vue-router";
|
||||||
import { onMounted } from "vue";
|
import { onMounted } from "vue";
|
||||||
|
|
||||||
const osThemeRef = useOsTheme()
|
const osThemeRef = useOsTheme()
|
||||||
const theme = defineModel<GlobalTheme>()
|
const theme = defineModel<GlobalTheme>('theme')
|
||||||
theme.value = osThemeRef.value === 'dark' ? darkTheme : lightTheme
|
theme.value = osThemeRef.value === 'dark' ? darkTheme : lightTheme
|
||||||
|
|
||||||
|
const locale = defineModel<NLocale>('locale')
|
||||||
|
locale.value = navigator.language === "zh-CN" ? zhCN : enUS
|
||||||
|
|
||||||
|
const dateLocale = defineModel<NDateLocale>('dateLocale')
|
||||||
|
dateLocale.value = navigator.language === "zh-CN" ? dateZhCN : dateEnUS
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
document.title = 'reCoreD-UI'
|
document.title = 'reCoreD-UI'
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NConfigProvider :theme="theme">
|
<NConfigProvider :theme="theme" :locale="locale" :date-locale="dateLocale">
|
||||||
<NGlobalStyle />
|
<NGlobalStyle />
|
||||||
<NNotificationProvider :max="3">
|
<NNotificationProvider :max="3">
|
||||||
<RouterView />
|
<RouterView />
|
||||||
|
@ -2,6 +2,8 @@ import axios, { type AxiosInstance, type AxiosRequestConfig, type AxiosResponse,
|
|||||||
import { type Record } from '@/stores/records';
|
import { type Record } from '@/stores/records';
|
||||||
import { type Domain } from "@/stores/domains";
|
import { type Domain } from "@/stores/domains";
|
||||||
|
|
||||||
|
import i18n from "@/locale/i18n";
|
||||||
|
|
||||||
type Result<T> = {
|
type Result<T> = {
|
||||||
success: boolean
|
success: boolean
|
||||||
message: string
|
message: string
|
||||||
@ -13,31 +15,30 @@ const notificationDuration = 5000
|
|||||||
const messages = new Map<number, {
|
const messages = new Map<number, {
|
||||||
title: string, content: string, duration: number
|
title: string, content: string, duration: number
|
||||||
}>(
|
}>(
|
||||||
// TODO: i18n
|
|
||||||
[
|
[
|
||||||
[400, {
|
[400, {
|
||||||
title: "请求错误 (400)",
|
title: i18n.global.t("api.error400.title"),
|
||||||
content: "参数提交错误",
|
content: i18n.global.t("api.error400.content"),
|
||||||
duration: notificationDuration
|
duration: notificationDuration
|
||||||
}],
|
}],
|
||||||
[401, {
|
[401, {
|
||||||
title: "未授权 (401)",
|
title: i18n.global.t("api.error401.title"),
|
||||||
content: "请刷新页面重新登录",
|
content: i18n.global.t("api.error401.content"),
|
||||||
duration: notificationDuration
|
duration: notificationDuration
|
||||||
}],
|
}],
|
||||||
[403, {
|
[403, {
|
||||||
title: "拒绝访问 (403)",
|
title: i18n.global.t("api.error403.title"),
|
||||||
content: "你没有权限!",
|
content: i18n.global.t("api.error403.content"),
|
||||||
duration: notificationDuration
|
duration: notificationDuration
|
||||||
}],
|
}],
|
||||||
[404, {
|
[404, {
|
||||||
title: "查无此项 (404)",
|
title: i18n.global.t("api.error404.title"),
|
||||||
content: "没有该项内容",
|
content: i18n.global.t("api.error404.content"),
|
||||||
duration: notificationDuration
|
duration: notificationDuration
|
||||||
}],
|
}],
|
||||||
[500, {
|
[500, {
|
||||||
title: "服务器错误 (500)",
|
title: i18n.global.t("api.error500.title"),
|
||||||
content: "请检查系统日志",
|
content: i18n.global.t("api.error500.content"),
|
||||||
duration: notificationDuration
|
duration: notificationDuration
|
||||||
}]
|
}]
|
||||||
]
|
]
|
||||||
@ -46,8 +47,8 @@ const messages = new Map<number, {
|
|||||||
export function getErrorInfo(err: any) {
|
export function getErrorInfo(err: any) {
|
||||||
const msg = messages.get(err.response.status)
|
const msg = messages.get(err.response.status)
|
||||||
return msg? msg: {
|
return msg? msg: {
|
||||||
title: "未知错误",
|
title: i18n.global.t("api.errorUnknown.title"),
|
||||||
content: "请打开控制台了解详情",
|
content: i18n.global.t("api.errorUnknown.content"),
|
||||||
duration: notificationDuration
|
duration: notificationDuration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
a,
|
a,
|
||||||
@ -29,7 +30,6 @@ a,
|
|||||||
|
|
||||||
#app {
|
#app {
|
||||||
display: flex;
|
display: flex;
|
||||||
grid-template-columns: 1fr;
|
|
||||||
padding: 2rem 2rem;
|
padding: 2rem 2rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,41 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
defineProps<{
|
|
||||||
msg: string
|
|
||||||
}>()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="greetings">
|
|
||||||
<h1 class="green">{{ msg }}</h1>
|
|
||||||
<h3>
|
|
||||||
You’ve successfully created a project with
|
|
||||||
<a href="https://vitejs.dev/" target="_blank" rel="noopener">Vite</a> +
|
|
||||||
<a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>. What's next?
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
h1 {
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 2.6rem;
|
|
||||||
position: relative;
|
|
||||||
top: -10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.greetings h1,
|
|
||||||
.greetings h3 {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
.greetings h1,
|
|
||||||
.greetings h3 {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,88 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import WelcomeItem from './WelcomeItem.vue'
|
|
||||||
import DocumentationIcon from './icons/IconDocumentation.vue'
|
|
||||||
import ToolingIcon from './icons/IconTooling.vue'
|
|
||||||
import EcosystemIcon from './icons/IconEcosystem.vue'
|
|
||||||
import CommunityIcon from './icons/IconCommunity.vue'
|
|
||||||
import SupportIcon from './icons/IconSupport.vue'
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<WelcomeItem>
|
|
||||||
<template #icon>
|
|
||||||
<DocumentationIcon />
|
|
||||||
</template>
|
|
||||||
<template #heading>Documentation</template>
|
|
||||||
|
|
||||||
Vue’s
|
|
||||||
<a href="https://vuejs.org/" target="_blank" rel="noopener">official documentation</a>
|
|
||||||
provides you with all information you need to get started.
|
|
||||||
</WelcomeItem>
|
|
||||||
|
|
||||||
<WelcomeItem>
|
|
||||||
<template #icon>
|
|
||||||
<ToolingIcon />
|
|
||||||
</template>
|
|
||||||
<template #heading>Tooling</template>
|
|
||||||
|
|
||||||
This project is served and bundled with
|
|
||||||
<a href="https://vitejs.dev/guide/features.html" target="_blank" rel="noopener">Vite</a>. The
|
|
||||||
recommended IDE setup is
|
|
||||||
<a href="https://code.visualstudio.com/" target="_blank" rel="noopener">VSCode</a> +
|
|
||||||
<a href="https://github.com/johnsoncodehk/volar" target="_blank" rel="noopener">Volar</a>. If
|
|
||||||
you need to test your components and web pages, check out
|
|
||||||
<a href="https://www.cypress.io/" target="_blank" rel="noopener">Cypress</a> and
|
|
||||||
<a href="https://on.cypress.io/component" target="_blank" rel="noopener"
|
|
||||||
>Cypress Component Testing</a
|
|
||||||
>.
|
|
||||||
|
|
||||||
<br />
|
|
||||||
|
|
||||||
More instructions are available in <code>README.md</code>.
|
|
||||||
</WelcomeItem>
|
|
||||||
|
|
||||||
<WelcomeItem>
|
|
||||||
<template #icon>
|
|
||||||
<EcosystemIcon />
|
|
||||||
</template>
|
|
||||||
<template #heading>Ecosystem</template>
|
|
||||||
|
|
||||||
Get official tools and libraries for your project:
|
|
||||||
<a href="https://pinia.vuejs.org/" target="_blank" rel="noopener">Pinia</a>,
|
|
||||||
<a href="https://router.vuejs.org/" target="_blank" rel="noopener">Vue Router</a>,
|
|
||||||
<a href="https://test-utils.vuejs.org/" target="_blank" rel="noopener">Vue Test Utils</a>, and
|
|
||||||
<a href="https://github.com/vuejs/devtools" target="_blank" rel="noopener">Vue Dev Tools</a>. If
|
|
||||||
you need more resources, we suggest paying
|
|
||||||
<a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">Awesome Vue</a>
|
|
||||||
a visit.
|
|
||||||
</WelcomeItem>
|
|
||||||
|
|
||||||
<WelcomeItem>
|
|
||||||
<template #icon>
|
|
||||||
<CommunityIcon />
|
|
||||||
</template>
|
|
||||||
<template #heading>Community</template>
|
|
||||||
|
|
||||||
Got stuck? Ask your question on
|
|
||||||
<a href="https://chat.vuejs.org" target="_blank" rel="noopener">Vue Land</a>, our official
|
|
||||||
Discord server, or
|
|
||||||
<a href="https://stackoverflow.com/questions/tagged/vue.js" target="_blank" rel="noopener"
|
|
||||||
>StackOverflow</a
|
|
||||||
>. You should also subscribe to
|
|
||||||
<a href="https://news.vuejs.org" target="_blank" rel="noopener">our mailing list</a> and follow
|
|
||||||
the official
|
|
||||||
<a href="https://twitter.com/vuejs" target="_blank" rel="noopener">@vuejs</a>
|
|
||||||
twitter account for latest news in the Vue world.
|
|
||||||
</WelcomeItem>
|
|
||||||
|
|
||||||
<WelcomeItem>
|
|
||||||
<template #icon>
|
|
||||||
<SupportIcon />
|
|
||||||
</template>
|
|
||||||
<template #heading>Support Vue</template>
|
|
||||||
|
|
||||||
As an independent project, Vue relies on community backing for its sustainability. You can help
|
|
||||||
us by
|
|
||||||
<a href="https://vuejs.org/sponsor/" target="_blank" rel="noopener">becoming a sponsor</a>.
|
|
||||||
</WelcomeItem>
|
|
||||||
</template>
|
|
@ -1,87 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="item">
|
|
||||||
<i>
|
|
||||||
<slot name="icon"></slot>
|
|
||||||
</i>
|
|
||||||
<div class="details">
|
|
||||||
<h3>
|
|
||||||
<slot name="heading"></slot>
|
|
||||||
</h3>
|
|
||||||
<slot></slot>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.item {
|
|
||||||
margin-top: 2rem;
|
|
||||||
display: flex;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.details {
|
|
||||||
flex: 1;
|
|
||||||
margin-left: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
display: flex;
|
|
||||||
place-items: center;
|
|
||||||
place-content: center;
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
|
|
||||||
color: var(--color-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
font-weight: 500;
|
|
||||||
margin-bottom: 0.4rem;
|
|
||||||
color: var(--color-heading);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
.item {
|
|
||||||
margin-top: 0;
|
|
||||||
padding: 0.4rem 0 1rem calc(var(--section-gap) / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
top: calc(50% - 25px);
|
|
||||||
left: -26px;
|
|
||||||
position: absolute;
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
background: var(--color-background);
|
|
||||||
border-radius: 8px;
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item:before {
|
|
||||||
content: ' ';
|
|
||||||
border-left: 1px solid var(--color-border);
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
bottom: calc(50% + 25px);
|
|
||||||
height: calc(50% - 25px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.item:after {
|
|
||||||
content: ' ';
|
|
||||||
border-left: 1px solid var(--color-border);
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: calc(50% + 25px);
|
|
||||||
height: calc(50% - 25px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.item:first-of-type:before {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item:last-of-type:after {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
37
web/src/components/records/RecordOps.vue
Normal file
37
web/src/components/records/RecordOps.vue
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<template>
|
||||||
|
<NFlex justify="end">
|
||||||
|
<NButtonGroup>
|
||||||
|
<NTooltip>
|
||||||
|
<template #trigger>
|
||||||
|
<NButton size="tiny">
|
||||||
|
<template #icon>
|
||||||
|
<NIcon :component="EditRegular" />
|
||||||
|
</template>
|
||||||
|
</NButton>
|
||||||
|
</template>
|
||||||
|
{{ $t("common.edit") }}
|
||||||
|
</NTooltip>
|
||||||
|
<NPopconfirm>
|
||||||
|
<template #trigger>
|
||||||
|
<NButton type="error" size="tiny">
|
||||||
|
<template #icon>
|
||||||
|
<NIcon :component="TrashAlt" />
|
||||||
|
</template>
|
||||||
|
</NButton>
|
||||||
|
</template>
|
||||||
|
{{ $t("common.deleteConfirm") }}
|
||||||
|
</NPopconfirm>
|
||||||
|
</NButtonGroup>
|
||||||
|
</NFlex>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { NButton, NButtonGroup, NTooltip, NIcon, NPopconfirm, NFlex } from 'naive-ui'
|
||||||
|
import { TrashAlt, EditRegular } from '@vicons/fa'
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import type { Record } from '@/stores/records';
|
||||||
|
const { t } = useI18n()
|
||||||
|
const props = defineProps<{
|
||||||
|
record: Record
|
||||||
|
}>();
|
||||||
|
</script>
|
51
web/src/locale/en-US.ts
Normal file
51
web/src/locale/en-US.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
export default {
|
||||||
|
common: {
|
||||||
|
delete: 'Remove',
|
||||||
|
remove: 'Remove',
|
||||||
|
deleteConfirm: 'Are you sure?',
|
||||||
|
removeConfirm: 'Are you sure?',
|
||||||
|
edit: 'Edit',
|
||||||
|
add: 'New',
|
||||||
|
new: 'New',
|
||||||
|
},
|
||||||
|
api: {
|
||||||
|
error400: {
|
||||||
|
title: 'Bad Request (400)',
|
||||||
|
content: 'Bad Parameters'
|
||||||
|
},
|
||||||
|
error401: {
|
||||||
|
title: 'Unauthorized (401)',
|
||||||
|
content: 'Refresh page and relogin'
|
||||||
|
},
|
||||||
|
error403: {
|
||||||
|
title: 'Forbbiden (403)',
|
||||||
|
content: 'Permission denied'
|
||||||
|
},
|
||||||
|
error404: {
|
||||||
|
title: 'Not Found (404)',
|
||||||
|
content: 'No such content'
|
||||||
|
},
|
||||||
|
error500: {
|
||||||
|
title: "Internal Server Error (500)",
|
||||||
|
content: "Check server log, please"
|
||||||
|
},
|
||||||
|
errorUnknown: {
|
||||||
|
title: "Unknown Error",
|
||||||
|
content: "Open console for details",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
domains: {
|
||||||
|
dnsRecord: 'DNS Record'
|
||||||
|
},
|
||||||
|
records: {
|
||||||
|
name: 'Record Name',
|
||||||
|
recordType: 'Type',
|
||||||
|
content: 'Record',
|
||||||
|
search: 'Search...',
|
||||||
|
|
||||||
|
refresh: 'Refresh Interval',
|
||||||
|
retry: 'Retry Interval',
|
||||||
|
expire: 'Expiry Period',
|
||||||
|
ttl: 'Negative TTL',
|
||||||
|
}
|
||||||
|
}
|
23
web/src/locale/i18n.ts
Normal file
23
web/src/locale/i18n.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { createI18n } from "vue-i18n";
|
||||||
|
import zhCN from "./zh-CN";
|
||||||
|
import enUS from "./en-US";
|
||||||
|
|
||||||
|
export default createI18n({
|
||||||
|
locale: navigator.language,
|
||||||
|
legacy: false,
|
||||||
|
messages: {
|
||||||
|
zh: {
|
||||||
|
...zhCN
|
||||||
|
},
|
||||||
|
'zh-CN': {
|
||||||
|
...zhCN
|
||||||
|
},
|
||||||
|
|
||||||
|
en: {
|
||||||
|
...enUS
|
||||||
|
},
|
||||||
|
'en-US': {
|
||||||
|
...enUS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
51
web/src/locale/zh-CN.ts
Normal file
51
web/src/locale/zh-CN.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
export default {
|
||||||
|
common: {
|
||||||
|
delete: '删除',
|
||||||
|
remove: '删除',
|
||||||
|
deleteConfirm: '确定要删除吗?',
|
||||||
|
removeConfirm: '确定要删除吗?',
|
||||||
|
edit: '修改',
|
||||||
|
add: '新增',
|
||||||
|
new: '新增',
|
||||||
|
},
|
||||||
|
api: {
|
||||||
|
error400: {
|
||||||
|
title: '请求错误 (400)',
|
||||||
|
content: '参数提交错误'
|
||||||
|
},
|
||||||
|
error401: {
|
||||||
|
title: '未授权 (401)',
|
||||||
|
content: '请刷新页面重新登录'
|
||||||
|
},
|
||||||
|
error403: {
|
||||||
|
title: '拒绝访问 (403)',
|
||||||
|
content: '你没有权限!'
|
||||||
|
},
|
||||||
|
error404: {
|
||||||
|
title: '查无此项 (404)',
|
||||||
|
content: '没有该项内容'
|
||||||
|
},
|
||||||
|
error500: {
|
||||||
|
title: "服务器错误 (500)",
|
||||||
|
content: "请检查系统日志"
|
||||||
|
},
|
||||||
|
errorUnknown: {
|
||||||
|
title: "未知错误",
|
||||||
|
content: "请打开控制台了解详情",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
domains: {
|
||||||
|
dnsRecord: 'DNS 记录'
|
||||||
|
},
|
||||||
|
records: {
|
||||||
|
name: '记录名',
|
||||||
|
recordType: '类型',
|
||||||
|
content: '记录值',
|
||||||
|
search: '搜索...',
|
||||||
|
|
||||||
|
refresh: '刷新时间',
|
||||||
|
retry: '重试间隔',
|
||||||
|
expire: '超期时间',
|
||||||
|
ttl: '缓存时间',
|
||||||
|
}
|
||||||
|
}
|
@ -5,10 +5,12 @@ import { createPinia } from 'pinia'
|
|||||||
|
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import router from './router'
|
import router from './router'
|
||||||
|
import i18n from './locale/i18n'
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
|
|
||||||
app.use(createPinia())
|
app.use(createPinia())
|
||||||
app.use(router)
|
app.use(router)
|
||||||
|
app.use(i18n)
|
||||||
|
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
@ -1,18 +1,58 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="tsx">
|
||||||
import { NSpin, NPageHeader, useNotification, NFlex, NButton, NIcon, NGrid, NGi, NStatistic, NDataTable, NInput } from 'naive-ui'
|
import { NSpin, NPageHeader, useNotification, NFlex, NButton, NIcon, NGrid, NGi, NStatistic, NDataTable, NInput } from 'naive-ui'
|
||||||
import { onMounted } from 'vue'
|
import type { DataTableColumns, DataTableFilterState } from 'naive-ui'
|
||||||
|
import { onMounted, ref } from 'vue'
|
||||||
import { useRecordStore, type Record, type SOARecord, RecordTypes } from '@/stores/records'
|
import { useRecordStore, type Record, type SOARecord, RecordTypes } from '@/stores/records'
|
||||||
import { getErrorInfo } from '@/apis/api'
|
import { getErrorInfo } from '@/apis/api'
|
||||||
import { PlusSquare, RedoAlt, CheckCircle, Clock, Cogs, Search } from '@vicons/fa'
|
import { PlusSquare, RedoAlt, CheckCircle, Clock, Cogs, Search } from '@vicons/fa'
|
||||||
import router from '@/router';
|
import router from '@/router';
|
||||||
|
import RecordOps from '@/components/records/RecordOps.vue'
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
domain: string
|
domain: string
|
||||||
}>()
|
}>()
|
||||||
const loading = defineModel<boolean>('loading', { default: true });
|
const loading = defineModel<boolean>('loading', { default: true });
|
||||||
const records = defineModel<Record[]>('records');
|
const records = defineModel<Record[]>('records');
|
||||||
const search = defineModel<string>('search', { default: '' })
|
|
||||||
const soa = defineModel<SOARecord | undefined>('soa')
|
const soa = defineModel<SOARecord | undefined>('soa')
|
||||||
|
const columns = defineModel<DataTableColumns<Record>>('columns')
|
||||||
|
const table = ref<any>(null)
|
||||||
|
|
||||||
|
columns.value = [
|
||||||
|
{
|
||||||
|
key: 'no',
|
||||||
|
title: '#',
|
||||||
|
render(_, index) {
|
||||||
|
return index + 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'name',
|
||||||
|
title: t("records.name"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'record_type',
|
||||||
|
title: t('records.recordType')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'content',
|
||||||
|
title: t('records.content'),
|
||||||
|
render(row: Record) {
|
||||||
|
return Object.entries(row.content).map(i => i[1]).join(" ")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'ttl',
|
||||||
|
title: 'TTL (s)'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '',
|
||||||
|
render(row: Record) {
|
||||||
|
return <RecordOps record={ row } />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
const recordStore = useRecordStore()
|
const recordStore = useRecordStore()
|
||||||
const notification = useNotification()
|
const notification = useNotification()
|
||||||
@ -27,20 +67,32 @@ onMounted(() => {
|
|||||||
|
|
||||||
function refreshRecords() {
|
function refreshRecords() {
|
||||||
recordStore.loadRecords(props.domain)
|
recordStore.loadRecords(props.domain)
|
||||||
records.value = recordStore.records
|
records.value = recordStore.records?.filter(e => e.record_type !== RecordTypes.RecordTypeSOA)
|
||||||
soa.value = records.value?.find(e => e.record_type === RecordTypes.RecordTypeSOA)?.content as SOARecord
|
soa.value = recordStore.records?.find(e => e.record_type === RecordTypes.RecordTypeSOA)?.content as SOARecord
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function goBack() {
|
function goBack() {
|
||||||
router.push('/domains')
|
router.push('/domains')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function searchRecord(value: string) {
|
||||||
|
if (value.length > 0) {
|
||||||
|
records.value = recordStore.records?.
|
||||||
|
filter(e => e.record_type !== RecordTypes.RecordTypeSOA).
|
||||||
|
filter(e => !!~e.name.indexOf(value))
|
||||||
|
} else {
|
||||||
|
records.value = recordStore.records?.
|
||||||
|
filter(e => e.record_type !== RecordTypes.RecordTypeSOA)
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div id="records">
|
<div id="records">
|
||||||
<NSpin size="large" v-if="loading" />
|
<NSpin size="large" v-if="loading" />
|
||||||
<NPageHeader v-else title="DNS 记录" :subtitle="domain" @back="goBack">
|
<div v-else class="content">
|
||||||
|
<NPageHeader :title="t('domains.dnsRecord')" :subtitle="domain" @back="goBack">
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<NFlex :wrap="false" justify="end" inline>
|
<NFlex :wrap="false" justify="end" inline>
|
||||||
<NButton type="primary">
|
<NButton type="primary">
|
||||||
@ -49,9 +101,9 @@ function goBack() {
|
|||||||
<PlusSquare />
|
<PlusSquare />
|
||||||
</NIcon>
|
</NIcon>
|
||||||
</template>
|
</template>
|
||||||
新增
|
{{ t('common.add') }}
|
||||||
</NButton>
|
</NButton>
|
||||||
<NInput v-model:value="search" placeholder="搜索...">
|
<NInput :placeholder="t('records.search')" @update:value="searchRecord" clearable>
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<NIcon :component="Search" />
|
<NIcon :component="Search" />
|
||||||
</template>
|
</template>
|
||||||
@ -68,7 +120,7 @@ function goBack() {
|
|||||||
<NIcon class="icon">
|
<NIcon class="icon">
|
||||||
<RedoAlt />
|
<RedoAlt />
|
||||||
</NIcon>
|
</NIcon>
|
||||||
刷新时间
|
{{ t('records.refresh') }}
|
||||||
</template>
|
</template>
|
||||||
</NStatistic>
|
</NStatistic>
|
||||||
</NGi>
|
</NGi>
|
||||||
@ -81,7 +133,7 @@ function goBack() {
|
|||||||
<NIcon class="icon">
|
<NIcon class="icon">
|
||||||
<CheckCircle />
|
<CheckCircle />
|
||||||
</NIcon>
|
</NIcon>
|
||||||
重试间隔
|
{{ t('records.retry') }}
|
||||||
</template>
|
</template>
|
||||||
</NStatistic>
|
</NStatistic>
|
||||||
</NGi>
|
</NGi>
|
||||||
@ -94,7 +146,7 @@ function goBack() {
|
|||||||
<NIcon class="icon">
|
<NIcon class="icon">
|
||||||
<Clock />
|
<Clock />
|
||||||
</NIcon>
|
</NIcon>
|
||||||
超期时间
|
{{ t('records.expire') }}
|
||||||
</template>
|
</template>
|
||||||
</NStatistic>
|
</NStatistic>
|
||||||
</NGi>
|
</NGi>
|
||||||
@ -107,15 +159,15 @@ function goBack() {
|
|||||||
<NIcon class="icon">
|
<NIcon class="icon">
|
||||||
<Cogs />
|
<Cogs />
|
||||||
</NIcon>
|
</NIcon>
|
||||||
缓存时间
|
{{ t('records.ttl') }}
|
||||||
</template>
|
</template>
|
||||||
</NStatistic>
|
</NStatistic>
|
||||||
</NGi>
|
</NGi>
|
||||||
</NGrid>
|
</NGrid>
|
||||||
</NPageHeader>
|
</NPageHeader>
|
||||||
<NDataTable :data="records">
|
<br />
|
||||||
|
<NDataTable :data="records" :columns="columns" ref="table" :pagination="{ pageSize: 20 }" />
|
||||||
</NDataTable>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -123,4 +175,12 @@ function goBack() {
|
|||||||
.icon {
|
.icon {
|
||||||
transform: translateY(2px);
|
transform: translateY(2px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div#records {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
Loading…
Reference in New Issue
Block a user