💥 破坏性更新 咱不推荐更新

This commit is contained in:
7836246
2024-03-25 06:55:06 +08:00
parent 9bb0fbd9a6
commit 410dc1ac58
52 changed files with 3645 additions and 1337 deletions
+122
View File
@@ -0,0 +1,122 @@
<script setup lang="ts">
import { RefreshCcw,XCircle,ArrowRightLeft,ArrowLeftFromLine,ArrowRightFromLine } from 'lucide-vue-next';
import {useTabStore} from "~/stores/admin/tab";
const props = defineProps({
show: {
type: Boolean,
default: false,
},
currentPath: {
type: String,
default: '',
},
x: {
type: Number,
default: 0,
},
y: {
type: Number,
default: 0,
},
})
const emit = defineEmits(['update:show'])
const tabStore = useTabStore()
const options = computed(() => [
{
label: '重新加载',
key: 'reload',
disabled: props.currentPath !== tabStore.activeTab,
icon: () => h(RefreshCcw),
},
{
label: '关闭',
key: 'close',
disabled: tabStore.tabs.length <= 1,
icon: () => h(XCircle),
},
{
label: '关闭其他',
key: 'close-other',
disabled: tabStore.tabs.length <= 1,
icon: () => h(ArrowRightLeft),
},
{
label: '关闭左侧',
key: 'close-left',
disabled: tabStore.tabs.length <= 1 || props.currentPath === tabStore.tabs[0].path,
icon: () => h(ArrowLeftFromLine),
},
{
label: '关闭右侧',
key: 'close-right',
disabled:
tabStore.tabs.length <= 1 ||
props.currentPath === tabStore.tabs[tabStore.tabs.length - 1].path,
icon: () => h(ArrowRightFromLine),
},
])
const route = useRoute()
const actionMap = new Map([
[
'reload',
() => {
tabStore.reloadTab(route.fullPath)
},
],
[
'close',
() => {
tabStore.removeTab(props.currentPath)
},
],
[
'close-other',
() => {
tabStore.removeOther(props.currentPath)
},
],
[
'close-left',
() => {
tabStore.removeLeft(props.currentPath)
},
],
[
'close-right',
() => {
tabStore.removeRight(props.currentPath)
},
],
])
function handleHideDropdown() {
emit('update:show', false)
}
function handleSelect(key:any) {
const actionFn = actionMap.get(key)
actionFn && actionFn()
handleHideDropdown()
}
</script>
<template>
<n-dropdown
:show="show"
:options="options"
:x="x"
:y="y"
placement="bottom-start"
@clickoutside="handleHideDropdown"
@select="handleSelect"
/>
</template>
<style scoped>
</style>
+95
View File
@@ -0,0 +1,95 @@
<script setup lang="ts">
import ContextMenu from './ContextMenu.vue'
import {useTabStore} from "~/stores/admin/tab";
const router = useRouter()
// const appStore = useAppStore()
const tabStore = useTabStore()
const contextMenuOption = reactive({
show: false,
x: 0,
y: 0,
currentPath: '',
})
const handleItemClick = (path:any) => {
tabStore.setActiveTab(path)
router.push(path)
}
function showContextMenu() {
contextMenuOption.show = true
}
function hideContextMenu() {
contextMenuOption.show = false
}
function setContextMenu(x:any, y:any, currentPath:any) {
Object.assign(contextMenuOption, { x, y, currentPath })
}
// 右击菜单
async function handleContextMenu(e:any, tagItem:any) {
const { clientX, clientY } = e
hideContextMenu()
setContextMenu(clientX, clientY, tagItem.path)
await nextTick()
showContextMenu()
}
</script>
<template>
<div>
<n-tabs
:value="tabStore.activeTab"
:closable="tabStore.tabs.length > 1"
:style="`--selected-bg: ${appStore.isDark ? '#1b2429' : '#eaf0f1'}`"
type="card"
@close="(path) => tabStore.removeTab(path)"
>
<n-tab
v-for="item in tabStore.tabs"
:key="item.path"
:name="item.path"
@click="handleItemClick(item.path)"
@contextmenu.prevent="handleContextMenu($event, item)"
>
{{ item.title }}
</n-tab>
</n-tabs>
<ContextMenu
v-if="contextMenuOption.show"
v-model:show="contextMenuOption.show"
:current-path="contextMenuOption.currentPath"
:x="contextMenuOption.x"
:y="contextMenuOption.y"
/>
</div>
</template>
<style scoped lang="scss">
:deep(.n-tabs) {
.n-tabs-tab {
padding-left: 16px;
height: 36px;
background: transparent !important;
border-radius: 4px !important;
margin-right: 4px;
&:hover {
border: 1px solid var(--primary-color) !important;
}
}
.n-tabs-tab--active {
border: 1px solid var(--primary-color) !important;
background-color: var(--selected-bg) !important;
}
.n-tabs-pad,
.n-tabs-tab-pad,
.n-tabs-scroll-padding {
border: none !important;
}
}
</style>
+77 -89
View File
@@ -1,89 +1,60 @@
<script setup lang="ts">
import {useStyleStore} from "~/stores/style";
import {useApisStore} from "~/stores/api";
const { t } = useI18n()
const state = reactive({
domain: '',
})
const toast = useToast();
const {t} = useI18n()
const router = useRouter();
const runtimeConfig = useRuntimeConfig()
const localePath = useLocalePath()
const settingsStore = useSettingsStore()
const domainStore = useDomainStore()
const SupportedTLDs = new Set(Object.keys(domainStore.SupportedTLDs));
const handleAction = async (url: any) => {
if (!state.domain) return toast.add({ title: '请输入域名' })
let domain = trimDomain(state.domain);
const parts = splitDomain(domain);
if (!validateDomain(parts) || !isTLDValid(parts)) return;
domain = updateDomainForTLD(parts);
state.domain = domain;
const isLink = ref({})
isLink.value = settingsStore.linkOpenType != 'currentWindow'
await router.push(localePath(`/${url}/${state.domain.replace(/\./g, '_')}.html`))
}
const trimDomain = (domain: string): string => {
return domain.trim().toLowerCase(); // 确保域名为小写
};
const splitDomain = (domain: string): string[] => {
return domain.split('.');
};
const validateDomain = (parts: string[]): boolean => {
if (parts.length < 2) {
toast.add({ title: '域名格式不正确' });
return false;
}
return true;
};
const isTLDValid = (parts: string[]): boolean => {
const lastPart = parts[parts.length - 1].toLowerCase(); // 获取最后一部分,并确保为小写
const potentialTLD = parts.slice(-2).join('.').toLowerCase(); // 获取可能的多部分TLD,并确保为小写
if (!SupportedTLDs.has(lastPart) && !SupportedTLDs.has(potentialTLD)) {
toast.add({ title: '域名后缀不合法' });
return false;
}
return true;
};
const updateDomainForTLD = (parts: string[]): string => {
const potentialTLD = parts.slice(-2).join('.').toLowerCase(); // 确保为小写
let domainToKeep: string;
if (SupportedTLDs.has(potentialTLD)) {
domainToKeep = parts.length > 2 ? parts.slice(-3).join('.') : parts.join('.');
} else {
domainToKeep = parts.slice(-2).join('.');
}
return domainToKeep;
};
const styleStore = useStyleStore()
const clientMounted = ref(false);
const apisStore = useApisStore()
const message = useMessage()
const handleAction = async (url: any) => {
if (!settingsStore.getDomain) return message.error('请输入域名')
// 正则表达式匹配域名
const domainPattern = /^(?!:\/\/)([a-zA-Z0-9-_]+\.)+[a-zA-Z0-9]{2,11}?$/;
// 正则表达式匹配IPv4地址
const ipPattern = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
// 检查state.domain是否匹配域名或IP地址的格式
if (!domainPattern.test(settingsStore.getDomain) && !ipPattern.test(settingsStore.getDomain)) {
message.error('请输入正确的域名或IP地址')
return;
}
await router.push(localePath(`/${url}/${settingsStore.getDomain.replace(/\./g, '_')}.html`))
}
onMounted(() => {
clientMounted.value = true;
});
const selectOptions = ref([
{
label: 'Whois',
value: 'whois'
}, {
label: 'Dns',
value: 'dns'
}, {
label: 'Domain',
value: 'domain'
}
])
const {selectedOption} = storeToRefs(settingsStore)
const handleSelectOptions = (value: any) => {
settingsStore.setSelectedOption(value);
console.log(selectedOption.value)
}
</script>
<template>
<div
class="w-full text-xs bg-[#F1F3F4] dark:bg-transparent"
class="w-full text-xs dark:bg-transparent"
:class="{ 'h-[90vh]': !styleStore.getIsPage && clientMounted }"
>
<div
@@ -99,34 +70,51 @@ onMounted(() => {
</NuxtLink>
</nav>
<div class="mt-6">
<UForm :state="state"
class="flex items-center space-x-2 mb-3 dark:text-white"
@submit="handleAction('whois')">
<div class="flex items-center space-x-2 mb-3 dark:text-white"
>
<!-- 容器div用于水平布局 -->
<div class="flex-grow">
<UInput
v-model="state.domain"
:placeholder="t('index.placeholder')"
color="sky"
size="xl"
class="w-full " />
<NInputGroup>
<n-select
:style="{ width: '20%' }"
size="large"
v-model:value="selectedOption"
:options="selectOptions"
@update:value="handleSelectOptions"
/>
<NInput
v-model:value="settingsStore.domainSearch"
@keyup.enter="handleAction(settingsStore.selectedOption)"
type="text"
:placeholder="t('index.placeholder')"
size="large"
clearable
autofocus
class="w-full "/>
</NInputGroup>
</div>
<!-- 使用v-if或v-show基于state.domain的值来控制按钮的显示 -->
<UButton type="submit" color="sky" size="xl" v-if="state.domain">
<!-- 使用v-if基于state.domain的值来控制按钮的显示 -->
<NButton type="primary"
size="large"
@click="handleAction(settingsStore.selectedOption)"
v-if="settingsStore.domainSearch">
{{ t('index.onSubmit') }}
</UButton>
</UForm>
</NButton>
</div>
</div>
<CommonBulletin
v-if="!styleStore.isPage && clientMounted"
:text="`➡️ ${t('index.tips') }`"
/>
<TabList @action="handleAction" />
<slot />
<ClientOnly>
<CommonBulletin
v-if="settingsStore.isBulletin && !styleStore.isPage"
:text="`➡️ ${t('index.tips') }`"
/>
</ClientOnly>
<TabList @action="handleAction"/>
<slot/>
</div>
</div>
<CommonFooter />
<CommonFooter/>
</template>
<style scoped>
-15
View File
@@ -1,15 +0,0 @@
<script setup lang="ts">
</script>
<template>
<div class="w-full h-[90vh] text-xs bg-[#F1F3F4] dark:bg-transparent">
<div class="max-w-screen-lg mx-auto pt-[15vh] px-[1em] pb-[10vh] ">
<slot />
</div>
</div>
<CommonFooter />
</template>
<style scoped>
</style>
+14
View File
@@ -0,0 +1,14 @@
<script setup lang="ts">
</script>
<template>
<div class="w-full text-xs bg-[#F1F3F4] dark:bg-transparent">
<div class="max-w-screen-lg mx-auto pt-[5vh] px-[1em] pb-[10vh] ">
<slot />
</div>
</div>
</template>
<style scoped>
</style>
+56
View File
@@ -0,0 +1,56 @@
<script setup lang="ts">
import { useDark, useToggle, useFullscreen } from '@vueuse/core'
import {useAppStore} from "~/stores/admin/app";
const appStore = useAppStore()
const isDark = useDark()
const toggleDark = () => {
appStore.toggleDark()
useToggle(isDark)()
}
const { isFullscreen, toggle } = useFullscreen()
function handleLinkClick(link:string) {
window.open(link)
}
</script>
<template>
<CommonAppCard class="flex items-center px-12" border-b="1px solid light_border dark:dark_border">
<MenuCollapse />
<BreadCrumb />
<div class="ml-auto flex flex-shrink-0 items-center px-12 text-18" >
<n-popover trigger="hover">
<template #trigger>
<div class="mr-16 f-c-c cursor-pointer rounded-4 p-6 text-22 transition-all-300 auto-bg-hover" @click="toggleDark">
<!-- 根据 isDark 条件动态切换图标 -->
<IconMoon v-if="isDark" />
<IconSun v-else />
</div>
</template>
<!-- 根据 isDark 条件动态切换提示文本 -->
<span>{{ isDark ? '夜间模式' : '日间模式' }}</span>
</n-popover>
<n-popover trigger="hover">
<template #trigger>
<div class="mr-16 f-c-c cursor-pointer rounded-4 p-6 text-22 transition-all-300 auto-bg-hover" @click="toggle">
<IconMinimize v-if="isFullscreen" />
<IconMaximize v-else />
</div>
</template>
<span>{{ isFullscreen ? '退出全屏' : '全屏模式' }}</span>
</n-popover>
<UserAvatar />
</div>
</CommonAppCard>
</template>
<style scoped>
</style>
+34
View File
@@ -0,0 +1,34 @@
<script setup lang="ts">
import AppTab from '@/layouts/components/tab/index'
import SideBar from './sidebar/index.vue'
import AppHeader from './header/index.vue'
import {useAppStore} from "~/stores/admin/app";
const appStore = useAppStore()
</script>
<template>
<div class="wh-full flex">
<aside
class="flex-col flex-shrink-0 transition-width-300"
:class="appStore.collapsed ? 'w-64' : 'w-220'"
border-r="1px solid light_border dark:dark_border"
>
<SideBar />
</aside>
<article class="w-0 flex-col flex-1">
<AppHeader class="h-60 flex-shrink-0" />
<div class="p-12" border-b="1px solid light_border dark:dark_border">
<AppTab class="flex-shrink-0" />
</div>
<slot />
</article>
</div>
</template>
<style scoped>
.collapsed {
width: 64px;
}
</style>
+12
View File
@@ -0,0 +1,12 @@
<script setup lang="ts">
</script>
<template>
<SideLogo border-b="1px solid light_border dark:dark_border" />
<SideMenu class="cus-scroll-y mt-4 h-0 flex-1" />
</template>
<style scoped>
</style>