修改侧边栏

This commit is contained in:
niyyzf 2024-07-22 18:41:15 +08:00
parent d152997430
commit 1f0275a3a2
31 changed files with 2610 additions and 15806 deletions

View File

@ -47,6 +47,11 @@
<ItemGroup> <ItemGroup>
<Folder Include="Configs\Alerts\" /> <Folder Include="Configs\Alerts\" />
<Folder Include="wwwroot\" /> </ItemGroup>
<ItemGroup>
<Content Update="wwwroot\index">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -76,8 +76,8 @@ builder.Services.AddCors(options =>
policy => policy =>
{ {
//允许全部 //允许全部
policy.WithOrigins("http://localhost:3001", "http://192.168.1.12:3001", "http://192.168.1.12:3001", policy.WithOrigins("http://localhost:3001", "http://192.168.1.22:3001", "http://192.168.1.22:3001",
"https://192.168.0.13:3000","https://loongpanel.xn--7p0a.site").AllowAnyHeader().AllowAnyMethod().AllowCredentials(); "https://192.168.0.22:3000","https://loongpanel.xn--7p0a.site").AllowAnyHeader().AllowAnyMethod().AllowCredentials();
}); });
}); });

View File

@ -4,7 +4,7 @@
"windowsAuthentication": false, "windowsAuthentication": false,
"anonymousAuthentication": true, "anonymousAuthentication": true,
"iisExpress": { "iisExpress": {
"applicationUrl": "http://192.168.1.12:58826", "applicationUrl": "http://192.168.1.22:58826",
"sslPort": 44304 "sslPort": 44304
} }
}, },
@ -14,7 +14,7 @@
"dotnetRunMessages": true, "dotnetRunMessages": true,
"launchBrowser": false, "launchBrowser": false,
"launchUrl": "swagger", "launchUrl": "swagger",
"applicationUrl": "http://192.168.1.12:5000", "applicationUrl": "http://192.168.1.22:5000",
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"
} }
@ -24,7 +24,7 @@
"dotnetRunMessages": true, "dotnetRunMessages": true,
"launchBrowser": true, "launchBrowser": true,
"launchUrl": "swagger", "launchUrl": "swagger",
"applicationUrl": "https://192.168.1.12:7233;http://192.168.1.12:5253", "applicationUrl": "https://192.168.1.22:7233;http://192.168.1.22:5253",
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"
} }

View File

View File

@ -132,41 +132,169 @@
}, },
{ {
"description": "Reference a permission or permission set by identifier and extends its scope.", "description": "Reference a permission or permission set by identifier and extends its scope.",
"type": "object",
"oneOf": [
{
"type": "object", "type": "object",
"required": [ "required": [
"identifier" "identifier"
], ],
"properties": { "properties": {
"identifier": { "identifier": {
"description": "Identifier of the permission or permission set.", "oneOf": [
"allOf": [
{ {
"$ref": "#/definitions/Identifier" "description": "shell:default -> This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality without any specific\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n",
"type": "string",
"enum": [
"shell:default"
]
},
{
"description": "shell:allow-execute -> Enables the execute command without any pre-configured scope.",
"type": "string",
"enum": [
"shell:allow-execute"
]
},
{
"description": "shell:allow-kill -> Enables the kill command without any pre-configured scope.",
"type": "string",
"enum": [
"shell:allow-kill"
]
},
{
"description": "shell:allow-open -> Enables the open command without any pre-configured scope.",
"type": "string",
"enum": [
"shell:allow-open"
]
},
{
"description": "shell:allow-spawn -> Enables the spawn command without any pre-configured scope.",
"type": "string",
"enum": [
"shell:allow-spawn"
]
},
{
"description": "shell:allow-stdin-write -> Enables the stdin_write command without any pre-configured scope.",
"type": "string",
"enum": [
"shell:allow-stdin-write"
]
},
{
"description": "shell:deny-execute -> Denies the execute command without any pre-configured scope.",
"type": "string",
"enum": [
"shell:deny-execute"
]
},
{
"description": "shell:deny-kill -> Denies the kill command without any pre-configured scope.",
"type": "string",
"enum": [
"shell:deny-kill"
]
},
{
"description": "shell:deny-open -> Denies the open command without any pre-configured scope.",
"type": "string",
"enum": [
"shell:deny-open"
]
},
{
"description": "shell:deny-spawn -> Denies the spawn command without any pre-configured scope.",
"type": "string",
"enum": [
"shell:deny-spawn"
]
},
{
"description": "shell:deny-stdin-write -> Denies the stdin_write command without any pre-configured scope.",
"type": "string",
"enum": [
"shell:deny-stdin-write"
]
} }
] ]
}, },
"allow": { "allow": {
"description": "Data that defines what is allowed by the scope.",
"type": [
"array",
"null"
],
"items": { "items": {
"$ref": "#/definitions/Value" "title": "Entry",
"description": "A command allowed to be executed by the webview API.",
"type": "object",
"required": [
"args",
"cmd",
"name",
"sidecar"
],
"properties": {
"args": {
"description": "The allowed arguments for the command execution.",
"allOf": [
{
"$ref": "#/definitions/ShellAllowedArgs"
}
]
},
"cmd": {
"description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.",
"type": "string"
},
"name": {
"description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.",
"type": "string"
},
"sidecar": {
"description": "If this command is a sidecar command.",
"type": "boolean"
}
}
} }
}, },
"deny": { "deny": {
"description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.",
"type": [
"array",
"null"
],
"items": { "items": {
"$ref": "#/definitions/Value" "title": "Entry",
"description": "A command allowed to be executed by the webview API.",
"type": "object",
"required": [
"args",
"cmd",
"name",
"sidecar"
],
"properties": {
"args": {
"description": "The allowed arguments for the command execution.",
"allOf": [
{
"$ref": "#/definitions/ShellAllowedArgs"
}
]
},
"cmd": {
"description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.",
"type": "string"
},
"name": {
"description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.",
"type": "string"
},
"sidecar": {
"description": "If this command is a sidecar command.",
"type": "boolean"
} }
} }
} }
} }
}
}
]
}
] ]
}, },
"Identifier": { "Identifier": {
@ -1165,6 +1293,83 @@
"resources:deny-close" "resources:deny-close"
] ]
}, },
{
"description": "shell:default -> This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality without any specific\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n",
"type": "string",
"enum": [
"shell:default"
]
},
{
"description": "shell:allow-execute -> Enables the execute command without any pre-configured scope.",
"type": "string",
"enum": [
"shell:allow-execute"
]
},
{
"description": "shell:allow-kill -> Enables the kill command without any pre-configured scope.",
"type": "string",
"enum": [
"shell:allow-kill"
]
},
{
"description": "shell:allow-open -> Enables the open command without any pre-configured scope.",
"type": "string",
"enum": [
"shell:allow-open"
]
},
{
"description": "shell:allow-spawn -> Enables the spawn command without any pre-configured scope.",
"type": "string",
"enum": [
"shell:allow-spawn"
]
},
{
"description": "shell:allow-stdin-write -> Enables the stdin_write command without any pre-configured scope.",
"type": "string",
"enum": [
"shell:allow-stdin-write"
]
},
{
"description": "shell:deny-execute -> Denies the execute command without any pre-configured scope.",
"type": "string",
"enum": [
"shell:deny-execute"
]
},
{
"description": "shell:deny-kill -> Denies the kill command without any pre-configured scope.",
"type": "string",
"enum": [
"shell:deny-kill"
]
},
{
"description": "shell:deny-open -> Denies the open command without any pre-configured scope.",
"type": "string",
"enum": [
"shell:deny-open"
]
},
{
"description": "shell:deny-spawn -> Denies the spawn command without any pre-configured scope.",
"type": "string",
"enum": [
"shell:deny-spawn"
]
},
{
"description": "shell:deny-stdin-write -> Denies the stdin_write command without any pre-configured scope.",
"type": "string",
"enum": [
"shell:deny-stdin-write"
]
},
{ {
"description": "tray:default -> Default permissions for the plugin.", "description": "tray:default -> Default permissions for the plugin.",
"type": "string", "type": "string",
@ -2499,6 +2704,45 @@
] ]
} }
] ]
},
"ShellAllowedArg": {
"description": "A command argument allowed to be executed by the webview API.",
"anyOf": [
{
"description": "A non-configurable argument that is passed to the command in the order it was specified.",
"type": "string"
},
{
"description": "A variable that is set while calling the command from the webview API.",
"type": "object",
"required": [
"validator"
],
"properties": {
"validator": {
"description": "[regex] validator to require passed values to conform to an expected input.\n\nThis will require the argument value passed to this variable to match the `validator` regex before it will be executed.\n\n[regex]: https://docs.rs/regex/latest/regex/#syntax",
"type": "string"
}
},
"additionalProperties": false
}
]
},
"ShellAllowedArgs": {
"description": "A set of command arguments allowed to be executed by the webview API.\n\nA value of `true` will allow any arguments to be passed to the command. `false` will disable all arguments. A list of [`ShellAllowedArg`] will set those arguments as the only valid arguments to be passed to the attached command configuration.",
"anyOf": [
{
"description": "Use a simple boolean to allow all or disable all arguments to this command configuration.",
"type": "boolean"
},
{
"description": "A specific set of [`ShellAllowedArg`] that are valid to call for the command configuration.",
"type": "array",
"items": {
"$ref": "#/definitions/ShellAllowedArg"
}
}
]
} }
} }
} }

View File

@ -0,0 +1,75 @@
import { Slot } from 'expo-router';
import {StyleSheet, View,Text} from "react-native";
import {useEffect} from "react";
export default function LoginLayout() {
return (
<View style={styles.layoutBox}>
<View style={styles.background}>
<View style={styles.backgroundRectangular1}></View>
<View style={styles.backgroundRectangular2}></View>
<View style={styles.backgroundEllipse1}></View>
<View style={styles.backgroundEllipse2}></View>
</View>
<Slot />
</View>
);
}
const styles = StyleSheet.create({
layoutBox:{
height:'100%',
width:'100%',
position:'relative',
backgroundColor:'white',
},
background:{
position:'absolute',
top:0,
left:0,
width:'100%',
height:'100%',
zIndex:-1,
},
backgroundRectangular1:{
position:"absolute",
width: 372,
height: 372,
left:'-50%',
bottom:'-20%',
transform:'rotate(27.089deg)',
borderStyle:'solid',
borderColor:'#F1F4FF',
borderWidth:2,
},
backgroundRectangular2:{
position:"absolute",
width: 372,
height: 372,
left:'-50%',
bottom:'-20%',
borderStyle:'solid',
borderColor:'#F1F4FF',
borderWidth:2,
},
backgroundEllipse1:{
position:"absolute",
width: 496,
height: 496,
right:'-40%',
top:'-20%',
borderRadius:496,
borderStyle:'solid',
borderColor:'#F1F4FF',
borderWidth:3,
},
backgroundEllipse2:{
position:"absolute",
width: 635,
height: 635,
right:'-90%',
top:'-38%',
borderRadius:496,
backgroundColor:'#F8F9FF'
}
});

View File

@ -0,0 +1,21 @@
import {StyleSheet, Text, TouchableOpacity, View} from "react-native";
import {Image} from "expo-image";
export default function Page() {
return (
<View style={styles.container}>
<View style={styles.TextContainer}>
<Text></Text>
<Text></Text>
</View>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
TextContainer: {}
})

View File

@ -0,0 +1,81 @@
import {Button, StyleSheet, Text, TouchableOpacity, View} from 'react-native';
import { Asset } from 'expo-asset';
import { Image } from 'expo-image';
import {router} from "expo-router";
export default function Page() {
const wb = require('../../assets/images/welcome_image.png');
return (
<View style={styles.container}>
<Image source={wb} style={styles.Image} />
<View style={styles.textContainer}>
<Text style={styles.textTitle}></Text>
<Text style={styles.text}></Text>
</View>
<TouchableOpacity style={styles.loginButton} onPress={()=>{
router.push("/(login)/login")
}}>
<Text style={styles.loginButtonText}></Text>
</TouchableOpacity>
</View>
)
}
const styles = StyleSheet.create({
container: {
paddingVertical:80,
paddingHorizontal:50,
flex: 1,
alignItems: 'center',
justifyContent: 'center',
display:'flex',
flexDirection:'column',
},
Image:{
flex:1,
flexBasis:6,
width:'100%',
resizeMode:'cover',
maxHeight:'50%'
},
textContainer:{
flex:1,
flexBasis:4,
display:'flex',
flexDirection:'column',
justifyContent:'center',
gap:25,
alignItems:'center',
width:'100%',
},
textTitle:{
color:'#1F41BB',
fontSize:35,
fontWeight:'800',
textAlign:'center'
},
text:{
color:'#000',
textAlign:'center',
fontSize:16,
fontWeight:'500',
},
loginButton: {
backgroundColor: '#1F41BB',
width: '100%',
padding: 15, // 添加一些内边距
borderRadius: 5, // 可选,添加圆角
alignItems: 'center', // 水平居中
marginTop: 50, // 可选,添加一些顶部外边距
shadowOffset: { width: 0, height: 10 }, // 水平偏移和垂直偏移
shadowOpacity: 0.5, // 阴影透明度
shadowRadius: 10, // 阴影半径
elevation: 5, // Android特有的阴影效果iOS使用shadowRadius
shadowColor: '#CBD6FF', // 阴影颜色
},
loginButtonText: {
color: '#FFFFFF',
fontSize: 18,
fontWeight: 'bold',
},
})

19
mobile/metro.config.js Normal file
View File

@ -0,0 +1,19 @@
const { getDefaultConfig } = require("expo/metro-config");
module.exports = (() => {
const config = getDefaultConfig(__dirname);
const { transformer, resolver } = config;
config.transformer = {
...transformer,
babelTransformerPath: require.resolve("react-native-svg-transformer/expo")
};
config.resolver = {
...resolver,
assetExts: resolver.assetExts.filter((ext) => ext !== "svg"),
sourceExts: [...resolver.sourceExts, "svg"]
};
return config;
})();

View File

@ -1 +1 @@
NUXT_API_URL="http://192.168.1.12:5000" NUXT_API_URL="http://192.168.1.22:5000"

View File

@ -3,9 +3,11 @@
<template> <template>
<div> <div>
<NuxtPwaManifest /> <NuxtPwaManifest />
<n-loading-bar-provider>
<NuxtLayout> <NuxtLayout>
<NuxtPage /> <NuxtPage />
</NuxtLayout> </NuxtLayout>
</n-loading-bar-provider>
</div> </div>
</template> </template>

View File

@ -2,131 +2,82 @@
import {useDataStore} from "~/strores/DataStore"; import {useDataStore} from "~/strores/DataStore";
import {useMainLayoutStore} from "~/strores/UseMainLayoutStore"; import {useMainLayoutStore} from "~/strores/UseMainLayoutStore";
const {$gsap} = useNuxtApp()
const props = defineProps({ const props = defineProps({
title: { title: {
type: String, type: String,
default: "CPU使用率" default: "CPU使用率",
},
info:{
type:Array,
default:()=>[],
}, },
unit: { unit: {
type: String, type: String,
default: "%" default: "%"
}, },
watcher: { watcher: {
type: String, type: [String, Array],
default: "cpuTotalUsage" default: "cpuTotalUsage",
validator: (value) => {
return typeof value === 'string' || Array.isArray(value);
}
}, },
reverse: { reverse: {
type: Boolean, type: Boolean,
default: false default: false
} }
}) })
const value = ref("0"); const values=ref<string[]>(Array( Array.isArray(props.watcher)?props.watcher.length:1).fill('0'))
const trueValue = ref("0");
const dataStore = useDataStore() const dataStore = useDataStore()
const mainStore = useMainLayoutStore() const mainStore = useMainLayoutStore()
const status=ref<string>("success")
//dataStore //dataStore
dataStore.$subscribe((_, state) => { dataStore.$subscribe((_, state) => {
//d3YT#cpuUserUsagekeyvalue //d3YT#cpuUserUsagekeyvalue
const data = state.data[props.watcher] const watcher = Array.isArray(props.watcher) ? [...props.watcher] : [props.watcher];
//data // 使mapstate.datawatcherKey
if (!data) { values.value = watcher.map(watcherKey => state.data[watcherKey as string]);
value.value = "0" //50warning 80error
updateProgress(0) const value=Number(values.value[0])
if (props.reverse) { status.value = value > 80 ? "error" : value > 50 ? "warning" : "success";
value.value = "100" })
updateProgress(100)
}
return
}
//data.value
const newValue = (Math.floor(Number(data) * 100) / 100).toFixed(2)
if (newValue !== value.value) {
value.value = newValue
trueValue.value = data.toString()
//
updateProgress(parseFloat(value.value))
}
})
watch(() => mainStore.SelectServer, () => {
//
value.value = "0"
updateProgress(0)
})
const circumference = 2 * Math.PI * 36;
const updateProgress = (value: number) => {
let progress = (100 - value) / 100;
if (props.reverse) {
progress = 1 - progress
}
$gsap.to("#" + props.title, {
duration: 0.5,
ease: 'power1.out',
strokeDashoffset: circumference * progress,
})
$gsap.to("#" + props.title + "Arrow", {
duration: 0.4,
ease: 'bounce.inOut',
rotation: 360 * (value / 100) - 40,
transformOrigin: "center center"
})
let color;
if (value >= 0 && value < 20) {
color = "#00C853"; // 绿
} else if (value >= 20 && value < 40) {
color = "#3A57E8"; //
} else if (value >= 40 && value < 60) {
color = "#FBBD08"; //
} else if (value >= 60 && value < 80) {
color = "#FF5722"; //
} else if (value >= 80 && value <= 100) {
color = "#FF0000"; // 80-100
}
if (color) {
$gsap.to("#" + props.title, {
duration: 0.5,
ease: 'power1.out',
stroke: color,
});
}
}
onMounted(()=>{
updateProgress(0)
//datStore
nextTick(()=>{
updateProgress(Number(dataStore.data[props.watcher]))
})
})
</script> </script>
<template> <template>
<n-popover trigger="hover" placement="bottom">
<template #trigger>
<div class="mini-card-box"> <div class="mini-card-box">
<svg fill="none" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg"> <!-- <svg fill="none" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">-->
<circle cx="36" cy="36" r="32" stroke="#E9ECEF" stroke-width="2"/> <!-- <circle cx="36" cy="36" r="32" stroke="#E9ECEF" stroke-width="2"/>-->
<circle :id="title" :stroke-dasharray="circumference" cx="36" <!-- <circle :id="title" :stroke-dasharray="circumference" cx="36"-->
cy="36" r="32" stroke="red" <!-- cy="36" r="32" stroke="red"-->
stroke-linecap="round" <!-- stroke-linecap="round"-->
stroke-width="4" transform="rotate(-90, 36, 36)"/> <!-- stroke-width="4" transform="rotate(-90, 36, 36)"/>-->
<path <!-- <path-->
:id="title+'Arrow'" <!-- :id="title+'Arrow'"-->
d="M26.1904 44.784C25.8209 45.1944 25.854 45.8267 26.2645 46.1963C26.6749 46.5658 27.3072 46.5327 27.6767 46.1223L26.1904 44.784ZM43.7763 27.8042C43.7474 27.2527 43.2768 26.829 42.7253 26.8579L33.7376 27.3289C33.1861 27.3578 32.7624 27.8284 32.7913 28.3799C32.8202 28.9314 33.2908 29.3551 33.8423 29.3262L41.8313 28.9075L42.25 36.8965C42.2789 37.4481 42.7495 37.8717 43.301 37.8428C43.8525 37.8139 44.2762 37.3434 44.2473 36.7919L43.7763 27.8042ZM27.6767 46.1223L43.5208 28.5257L42.0345 27.1874L26.1904 44.784L27.6767 46.1223Z" <!-- d="M26.1904 44.784C25.8209 45.1944 25.854 45.8267 26.2645 46.1963C26.6749 46.5658 27.3072 46.5327 27.6767 46.1223L26.1904 44.784ZM43.7763 27.8042C43.7474 27.2527 43.2768 26.829 42.7253 26.8579L33.7376 27.3289C33.1861 27.3578 32.7624 27.8284 32.7913 28.3799C32.8202 28.9314 33.2908 29.3551 33.8423 29.3262L41.8313 28.9075L42.25 36.8965C42.2789 37.4481 42.7495 37.8717 43.301 37.8428C43.8525 37.8139 44.2762 37.3434 44.2473 36.7919L43.7763 27.8042ZM27.6767 46.1223L43.5208 28.5257L42.0345 27.1874L26.1904 44.784L27.6767 46.1223Z"-->
fill="#ADB5BD"/> <!-- fill="#ADB5BD"/>-->
</svg> <!-- </svg>-->
<n-progress type="dashboard" processing gap-position="bottom" :status="status" :percentage="
values[0]
" style="width: 4.427vw; max-width:120px;min-width: 80px" />
<div class="text"> <div class="text">
<h3> <h3>
{{ title }} {{ title }}
</h3> </h3>
<h2> <h2>
<span v-if="props.reverse" v-tooltip="`${100-trueValue}${unit}`">{{ 100 - value }}{{ unit }}</span> <span>{{ Number(values[0]??0).toFixed(2)}}{{ unit }}</span>
<span v-else v-tooltip="`${trueValue}${unit}`">{{ value }}{{ unit }}</span>
</h2> </h2>
</div> </div>
</div> </div>
</template> </template>
<n-tag type="info" v-for="i in info">
{{ i }}
</n-tag>
</n-popover>
</template>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "../../base"; @import "../../base";
@ -140,7 +91,9 @@ onMounted(()=>{
box-shadow: 0 10px 30px 0 rgba(17, 38, 146, 0.05); box-shadow: 0 10px 30px 0 rgba(17, 38, 146, 0.05);
gap: $gap*3; gap: $gap*3;
border: $border; border: $border;
*{
@include SC_Font()
}
> svg { > svg {
width: 68px; width: 68px;
height: 68px; height: 68px;
@ -160,17 +113,16 @@ onMounted(()=>{
color: $light-unfocused-color; color: $light-unfocused-color;
font-size: 16px; font-size: 16px;
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 600;
line-height: 175%; /* 28px */ line-height: 175%; /* 28px */
// //
text-wrap: nowrap; text-wrap: nowrap;
} }
h2 { h2 {
font-size: 19px; font-size: 25px;
font-weight: 500; font-weight: 800;
color: $light-text-color; color: $light-text-color;
.dark-mode & { .dark-mode & {
color: $dark-text-color; color: $dark-text-color;
} }

View File

@ -37,13 +37,23 @@ const menu = ref();
<Button autofocus label="发送" outlined severity="secondary" @click="sendMessage"/> <Button autofocus label="发送" outlined severity="secondary" @click="sendMessage"/>
</template> </template>
</Dialog> </Dialog>
<n-modal v-model:show="sendMessageModel">
123123
</n-modal>
<ContextMenu ref="menu" :model="items"/> <ContextMenu ref="menu" :model="items"/>
<div v-for="user in mainLayoutStore.OnlineUsers" class="user-item" @click="onRightClick($event,user.id)"> <div v-for="user in mainLayoutStore.OnlineUsers" class="user-item" @click="onRightClick($event,user.id)">
<Avatar :image="user.avatar" shape="circle" size="xlarge"/> <n-avatar
:size="50"
:src="user.avatar"
lazy
fallback-src="https://07akioni.oss-cn-beijing.aliyuncs.com/07akioni.jpeg"
/>
<div class="user-info"> <div class="user-info">
<h4>{{ user.nickName }}</h4> <h4>{{ user.nickName }}</h4>
<div> <div>
<Tag :value="user.posts"/> <n-tag type="info" :bordered="false">
{{user.posts}}
</n-tag>
</div> </div>
</div> </div>
</div> </div>
@ -64,7 +74,7 @@ const menu = ref();
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
gap: $gap*3; gap: $gap*3;
padding: $padding*.2; padding: $padding*.25 $padding;
border-radius: $radius; border-radius: $radius;
&:hover { &:hover {
@ -75,7 +85,7 @@ const menu = ref();
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
gap: $gap; gap: $gap*.2;
flex: 1; flex: 1;
} }
} }

View File

@ -18,7 +18,7 @@ const getServerConfig = () => {
$fetch(`/Api/Server/GetServerInfo`, { $fetch(`/Api/Server/GetServerInfo`, {
method: 'GET', method: 'GET',
params: { params: {
serverId: mainLayoutStore.SelectServer.id serverId: mainLayoutStore.SelectServer.value
}, },
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',

View File

@ -1,20 +1,41 @@
<script lang="ts" setup> <script lang="ts" setup>
import MiniCard from "~/components/Cards/MiniCard.vue"; import MiniCard from "~/components/Cards/MiniCard.vue";
import {useMainLayoutStore} from "~/strores/UseMainLayoutStore";
import {useDataStore} from "~/strores/DataStore";
const gridRef = ref<HTMLElement>(); const gridRef = ref<HTMLElement>();
const dataStore=useDataStore()
const mainLayoutStore=useMainLayoutStore()
const CpuUsageCount=ref<string[]>([])
const DiskUsageCount=ref<string[]>([])
const NetWorkUsageCount=ref<string[]>([])
watch(()=>mainLayoutStore.SelectServer.value,()=>{
const data=dataStore.data
CpuUsageCount.value=Object.keys(data).filter(key => key.startsWith('CpuSingleUsage-'))
})
onMounted(()=>{
const data=dataStore.data
CpuUsageCount.value=Object.keys(data).filter(key => key.startsWith('CpuSingleUsage-'))
DiskUsageCount.value=Object.keys(data).filter(key => key.startsWith('DiskUtil-'))
NetWorkUsageCount.value=Object.keys(data).filter(key => key.startsWith('InterfaceUtilizationPercentage-'))
})
</script> </script>
<template> <template>
<n-scrollbar x-scrollable>
<div ref="gridRef" class="top-grid-layout"> <div ref="gridRef" class="top-grid-layout">
<MiniCard title="系统使用率" watcher="RequestJob"/>
<MiniCard title="CPU总使用率" watcher="CpuTotalUsage"/> <MiniCard title="系统使用率" watcher="RequestJob" :info="['112312']"/>
<MiniCard title="内存总使用率" watcher="MemoryTotalUsage"/> <MiniCard title="CPU总使用率" :watcher="['CpuTotalUsage',...CpuUsageCount]" :info="['AMD EPYC 7K62 (4) @ 2.595GHz','AMD EPYC 7K62 (4) @ 2.595GHz']"/>
<MiniCard title="内存总使用率" :watcher="['MemoryTotalUsage','SwapTotalUsage']"/>
<MiniCard title="读写使用率" watcher="CpuIOWaitUsage"/> <MiniCard title="读写使用率" watcher="CpuIOWaitUsage"/>
<MiniCard title="磁盘总使用率" watcher="DiskTotalUsage"/> <MiniCard title="磁盘总使用率" :watcher="['DiskTotalUsage',...DiskUsageCount]"/>
<MiniCard title="网络接口使用率" watcher="InterfaceTotalUtilizationPercentage"/> <MiniCard title="网络接口使用率" :watcher="['InterfaceTotalUtilizationPercentage',...NetWorkUsageCount]"/>
</div> </div>
</n-scrollbar>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -34,9 +55,4 @@ const gridRef = ref<HTMLElement>();
} }
} }
@media screen and (max-width: 1200px) {
.top-grid-layout {
grid-template-columns: repeat(2, 1fr);
}
}
</style> </style>

View File

@ -11,7 +11,7 @@ onMounted(()=>{
term.open(document.getElementById("terminal") as HTMLElement); term.open(document.getElementById("terminal") as HTMLElement);
const fitAddon = new FitAddon(); const fitAddon = new FitAddon();
term.loadAddon(fitAddon); term.loadAddon(fitAddon);
// fitAddon.fit(); fitAddon.fit();
term.focus(); term.focus();
// //
term.onData((data) => { term.onData((data) => {
@ -44,7 +44,7 @@ onMounted(async () => {
.catch(err => { .catch(err => {
console.log("Error while starting connection: " + err); console.log("Error while starting connection: " + err);
}); });
await connection.invoke("CreateTerminal", mainLayoutStore.SelectServer.id) await connection.invoke("CreateTerminal", mainLayoutStore.SelectServer.value)
.then(() => { .then(() => {
console.log("Terminal created") console.log("Terminal created")
}) })

View File

@ -93,7 +93,7 @@ const isOnline = computed(() => {
<p>{{ isLock ? '锁定' : '' }}</p> <p>{{ isLock ? '锁定' : '' }}</p>
</div> </div>
<div class="user-end"> <div class="user-end">
<button @click="navigateTo(`userInfo/${userId}`)"> <button @click="navigateTo(`settings/userSettings/${userId}`)">
<p>查看更多</p> <p>查看更多</p>
<Icon name="ChevronRight"></Icon> <Icon name="ChevronRight"></Icon>
</button> </button>

View File

@ -1,185 +1,110 @@
<script lang="ts" setup> <script lang="ts" setup>
import Logo from "~/components/Logo.vue";
import {ArrowLeft, Search} from 'lucide-vue-next';
import {useMainLayoutStore} from "~/strores/UseMainLayoutStore";
import {useMainLayoutStore} from "~/strores/UseMainLayoutStore";
import {type MenuOption, NIcon} from "naive-ui";
import { LayoutGrid,Computer,Cpu,Cctv,UserRoundCog,Goal,Settings,LogOut,PanelLeftClose,PanelLeftOpen } from 'lucide-vue-next';
import type {Component} from "vue";
import { useLoadingBar } from 'naive-ui'
const {$gsap} = useNuxtApp() const {$gsap} = useNuxtApp()
const mainLayoutStore = useMainLayoutStore() const mainLayoutStore = useMainLayoutStore()
const Menus =ref([ const loadingBar = useLoadingBar()
const collapsed=ref<boolean>(false)
const activeKey= ref<string | null>(null);
function renderIcon(icon: Component) {
return () => h(NIcon, null, { default: () => h(icon) })
}
const menuOptions: MenuOption[] = [
{ {
"label": "系统概览", label: '系统概览',
"icon": "LayoutGrid", key: 'home',
"route": "/Home" icon:renderIcon(LayoutGrid)
}, {
"label": "主机监测",
"icon": "Cpu",
"route": "/host/cpu"
},{
"label": "用户监测",
"icon": "Cctv",
"route": "/serverUser/all"
},{
"label": "账号列表",
"icon": "UsersRound",
"route": "/User"
},{
"label": "巡检记录",
"icon": "PackageSearch",
"route": "/InspectionRecords"
}, },
{ {
"label": "系统设置", label: '主机监测',
"icon": "Settings", key: 'host',
"route": "/Settings/PanelSettings" icon:renderIcon(Computer),
children: [
{
label: 'CPU',
key: 'host/cpu',
icon:renderIcon(Cpu)
}
]
}, },
]) {
const filteredMenus=computed(()=>{ label: '用户监测',
return Menus.value.filter(menu=>{ key: 'serverUser/all',
const role=mainLayoutStore.UserInfo.role icon:renderIcon(Cctv)
return menu.label === "账号列表" ? role === "Admin" : true;
})
})
onMounted(() => {
const t1 = $gsap.timeline();
if (mainLayoutStore.IsLeftSidebarMini) {
t1.to(".sidebar-layout", {
duration: 0,
width: "104px",
ease: "power2.out",
}, 0)
t1.to(".sidebar-layout p,.sidebar-layout h3,.sidebar-layout .aa>svg", {
display: "none",
duration: 0,
}, 0)
t1.to(".sidebar-layout .user", {
duration: 0,
gap: 0
}, 0)
t1.to(".switch-button", {
duration: 0,
//3
rotate: 360 + 180,
ease: "power2.out",
}, 0)
}
t1.from(".sidebar-layout", {
duration: 0.5,
x: "-100%",
opacity: 0,
ease: "power2.out",
})
})
watch(() => mainLayoutStore.IsLeftSidebarMini, (newValue) => {
const t1 = $gsap.timeline();
t1.to(".sidebar-layout .header,.folder", {
duration: 0.5,
x: "-200%",
opacity: 0,
ease: "power2.out",
}, 0)
if (newValue) {
t1.to(".sidebar-layout", {
duration: 0.5,
width: "104px",
ease: "power2.out",
}, 0.5)
t1.to(".switch-button", {
duration: 1,
//3
rotate: 180,
ease: "power2.out",
}, 0.5)
t1.to(".sidebar-layout p,.sidebar-layout h3,.sidebar-layout .aa>svg", {
display: "none",
duration: 0,
}, 0.5)
t1.to(".sidebar-layout .user", {
duration: 0,
gap: 0
}, 0.5)
} else {
t1.to(".sidebar-layout", {
duration: 0.5,
width: 210,
ease: "power2.out",
}, 0.5)
t1.to(".sidebar-layout", {
width: "auto",
}, 0.55)
t1.to(".switch-button", {
duration: 1,
y: 0,
rotate: 360,
ease: "power2.out",
}, 0.5)
t1.to(".sidebar-layout p,.sidebar-layout h3,.sidebar-layout .aa>svg", {
display: "block",
duration: 0,
ease: "power2.out",
}, 0.5)
t1.to(".sidebar-layout .user", {
duration: 0,
gap: "1rem"
}, 0.5)
}
t1.to(".sidebar-layout .header,.sidebar-layout .folder", {
duration: 0.5,
x: 0,
opacity: 1,
ease: "power2.out",
}, 1)
})
const logout=()=>{
$fetch('/Api/Account/Logout', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + useCookie('token').value
}, },
baseURL: useRuntimeConfig().public.baseUrl {
}).then(()=>{ label:'账号管理',
navigateTo("/SignIn") key: 'user',
}) icon:renderIcon(UserRoundCog)
},
{
label: '巡检记录',
key: 'inspectionRecords',
icon:renderIcon(Goal)
},
{
label: '系统设置',
key: 'Settings',
icon:renderIcon(Settings)
},
{
label: '退出登录',
key: 'Exit',
icon:renderIcon(LogOut)
} }
]
watch(()=>activeKey.value,async (newValue)=>{
loadingBar.start()
await navigateTo("/"+newValue)
loadingBar.finish()
})
</script> </script>
<template> <template>
<section class="sidebar-layout"> <section class="sidebar-layout">
<div class="switch-button" @click="mainLayoutStore.toggleLeftSidebarMini()"> <transition name="fade" mode="out-in">
<ArrowLeft/> <div v-if="!collapsed" class="header">
<n-image
width="45"
src="/Square310x310Logo.png"
/>
<h1>龙盾云御</h1>
</div> </div>
<div class="header"> <div v-else class="header">
<div class="user"> <n-image
<Logo/> width="45"
<div class="name"> src="/Square310x310Logo.png"
<h3> />
龙盾云御 <h1 v-if="!collapsed">龙盾云御</h1>
</h3>
</div>
</div>
<div class="search">
<Search/>
<p>搜索</p>
</div>
<div class="menus">
<div v-for="menu in filteredMenus" v-tooltip="menu.label" class="menu-item" @click="navigateTo(menu.route)" :key="menu.route">
<Icon :name="menu.icon"/>
<p>{{ menu.label }}</p>
</div>
</div>
</div>
<div class="folder">
<div class="menu-item" @click="logout">
<Icon name="LogOut"/>
<p>登出</p>
</div>
<div class="menu-item aa">
<Icon name="Sun"/>
<p>颜色模式</p>
<color-switch/>
</div> </div>
</transition>
<n-layout has-sider>
<n-layout-sider
collapse-mode="width"
:collapsed-width="64"
:collapsed="collapsed"
@collapse="collapsed = true"
@expand="collapsed = false"
:width="200"
>
<n-menu
:collapsed="collapsed"
v-model:value="activeKey"
:collapsed-width="64"
:collapsed-icon-size="22"
:options="menuOptions"
/>
</n-layout-sider>
</n-layout>
<div class="bottom">
<PanelLeftOpen v-if="collapsed" @click="collapsed=false" />
<PanelLeftClose v-else @click="collapsed=true" />
</div> </div>
</section> </section>
</template> </template>
@ -190,11 +115,10 @@ const logout=()=>{
.sidebar-layout { .sidebar-layout {
display: flex; display: flex;
grid-area: sidebar; grid-area: sidebar;
padding: 24px 24px 32px 24px;
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
align-items: flex-start; align-items: flex-start;
flex-shrink: 0; gap: $gap;
background: $light-bg-color; background: $light-bg-color;
position: relative; position: relative;
*{ *{
@ -205,149 +129,38 @@ const logout=()=>{
} }
} }
.fade-enter-active {
transition: opacity 0.5s ease;
}
.fade-leave-active{
transition: opacity 0.05s ease;
}
/* 进入开始状态和离开结束状态 */
.fade-enter-from, .fade-leave-to {
opacity: 0;
scale: 0;
}
.header { .header {
height: 76px;
display: flex; display: flex;
gap: 44px;
width: 100%; width: 100%;
flex-direction: column; align-items: center;
justify-content: center; justify-content: center;
align-items: center;
height: 100%;
overflow-y: auto;
&::-webkit-scrollbar{
width: 0;
}
}
.user {
display: flex;
gap: $gap*2;
align-items: center;
width: 100%;
justify-content: center;
> svg {
height: 44px;
width: 44px;
}
.name {
h3, p {
color: #09090A;
font-size: 24px;
font-style: normal;
font-weight: 700;
line-height: normal;
.dark-mode & {
color: #FFF;
}
}
}
}
.search {
width: 100%;
padding: $padding;
background: $light-bg-underline-color;
display: flex;
align-items: center;
gap: $gap*2;
border-radius: $radius*2;
.dark-mode & {
background: $dark-bg-underline-color;
svg, p {
color: #FFF;
stroke: #FFF;
}
}
> p {
color: #2A2A2E;
font-size: 16px;
font-style: normal;
font-weight: 400;
line-height: 140%; /* 22.4px */
}
}
.menus {
width: 100%;
display: flex;
flex-direction: column;
gap: $gap*3;
height: 100%;
}
.menu-item {
display: flex;
padding: $padding;
gap: $gap*2;
align-items: center;
cursor: pointer;
border-radius: $radius;
.dark-mode & {
> svg, p {
color: #FFF;
stroke: #FFF;
}
&:hover {
background: $dark-bg-underline-color;
}
}
&:hover {
background: $light-bg-underline-color;
}
> svg {
stroke: #09090A;
}
> p {
color: #09090A;
font-size: 16px;
font-style: normal;
font-weight: 400;
line-height: 140%; /* 22.4px */
}
}
.folder {
display: flex;
flex-direction: column;
width: 100%;
gap: $gap; gap: $gap;
h1{
font-size: 30px;
font-weight: 600;
} }
}
.switch-button { .bottom{
position: absolute; height: 49px;
top: 25px;
right: -15px;
display: flex; display: flex;
justify-content: center;
align-items: center; align-items: center;
z-index: 10; width: 100%;
width: 30px; padding: 20px;
height: 30px;
cursor: pointer;
background: $primary-color;
color: #FFF;
border-radius: $radius*100;
box-shadow: 0 2px 4px 0 rgba(138, 146, 166, 0.30);
> svg {
width: 18px;
height: 18px;
} }
}
</style> </style>

View File

@ -1,34 +1,31 @@
<script lang="ts" setup> <script lang="ts" setup>
import {useMainLayoutStore} from "~/strores/UseMainLayoutStore"; import {useMainLayoutStore} from "~/strores/UseMainLayoutStore";
import {BellRing, Mail} from 'lucide-vue-next';
import SidebarRight from "~/components/SidebarRight.vue"; import SidebarRight from "~/components/SidebarRight.vue";
import { Signature } from 'lucide-vue-next';
const {$gsap} = useNuxtApp()
const mainLayoutStore = useMainLayoutStore()
const home = ref({
icon: 'pi pi-home'
});
const router = useRouter() const router = useRouter()
const routes=computed(()=>{ const routes=computed(()=>{
const route=router.currentRoute.value.name as string const route=router.currentRoute.value.name as string
return route.split('-').map(x => { return route.split('-').map(x => {
return { return x.charAt(0).toUpperCase() + x.slice(1)
label: x.charAt(0).toUpperCase() + x.slice(1),
icon: 'pi pi-angle'
}
}) })
}) })
const visibleRight = ref(false) const visibleRight = ref(false)
const value=ref()
const options = ref(['@gmail.com', '@outlook.com', '@yahoo.com'])
</script> </script>
<template> <template>
<section class="title-bar-layout"> <section class="title-bar-layout">
<Sidebar v-model:visible="visibleRight" header="通知" position="right"> <n-auto-complete
<SidebarRight/> v-model:value="value"
</Sidebar> :input-props="{
<h1 class="name"> autocomplete: 'disabled',
<Breadcrumb :home="home" :model="routes" /> }"
</h1> :options="options"
placeholder="搜索"
clearable
/>
<div class="action"> <div class="action">
<Icon name="BellRing" @click="visibleRight=true" :stroke-width="1.5" :size="20"/> <Icon name="BellRing" @click="visibleRight=true" :stroke-width="1.5" :size="20"/>
<Icon name="Mail" :stroke-width="1.5" :size="20"/> <Icon name="Mail" :stroke-width="1.5" :size="20"/>
@ -49,7 +46,7 @@ const visibleRight = ref(false)
grid-template-columns: auto 1fr auto auto; grid-template-columns: auto 1fr auto auto;
grid-template-rows: 1fr; grid-template-rows: 1fr;
align-items: center; align-items: center;
padding: 0 $padding*2; padding: 0 $padding;
gap: $gap*2; gap: $gap*2;
@include SC_Font(); @include SC_Font();
.dark-mode & { .dark-mode & {
@ -57,6 +54,20 @@ const visibleRight = ref(false)
} }
} }
:deep(.n-breadcrumb){
ul{
display: flex;
padding: 0;
align-content: center;
li>span{
font-size: 16px;
font-weight: 800;
}
}
}
.name { .name {
font-weight: 700; font-weight: 700;
font-size: 26px; font-size: 26px;

View File

@ -7,7 +7,7 @@ import type {UserInfoType} from "~/types/UserType";
import type {HttpType} from "~/types/baseType"; import type {HttpType} from "~/types/baseType";
import {useSessionSignalRStore} from "~/strores/HubStore"; import {useSessionSignalRStore} from "~/strores/HubStore";
import {POSITION, useToast} from "vue-toastification"; import {POSITION, useToast} from "vue-toastification";
import {type dataHistoryType, useDataStore} from "~/strores/DataStore";; import {type dataHistoryType, useDataStore} from "~/strores/DataStore";
const audio = ref<any>(null); const audio = ref<any>(null);
const audio1 = ref<any>(null); const audio1 = ref<any>(null);
@ -18,16 +18,7 @@ const DataStore = useDataStore()
const {$gsap} = useNuxtApp() const {$gsap} = useNuxtApp()
const mainRef = ref<HTMLElement>() const mainRef = ref<HTMLElement>()
const mainLayoutStore = useMainLayoutStore() const mainLayoutStore = useMainLayoutStore()
const ServerList = ref<{ name: string, id?: string }[]>([{name: 'Australia'}, const ServerList = ref<{ label: string, value: string }[]>([])
{name: 'Brazil'},
{name: 'China'},
{name: 'Egypt'},
{name: 'France'},
{name: 'Germany'},
{name: 'India'},
{name: 'Japan'},
{name: 'Spain'},
{name: 'United States'}])
const {isScrolling} = useScroll(mainRef) const {isScrolling} = useScroll(mainRef)
watch(isScrolling, (newValue) => { watch(isScrolling, (newValue) => {
@ -57,10 +48,10 @@ onMounted(() => {
}, },
baseURL: useRuntimeConfig().public.baseUrl baseURL: useRuntimeConfig().public.baseUrl
}).then(res => { }).then(res => {
const data = res as { name: string, id?: string }[] const data = res as { name: string, id: string }[]
ServerList.value = data; ServerList.value = data.map(item => ({label: item.name, value: item.id}))
if (!mainLayoutStore.SelectServer.id) { if (!mainLayoutStore.SelectServer.value) {
mainLayoutStore.SelectServer = data[0]; mainLayoutStore.SelectServer = ServerList.value[0];
} }
}) })
}) })
@ -72,14 +63,13 @@ onMounted(() => {
signalR.initConnection(); signalR.initConnection();
signalR.connection?.on('userJoined', (data: any) => { signalR.connection?.on('userJoined', (data: any) => {
mainLayoutStore.setOnlineUsers(data) mainLayoutStore.setOnlineUsers(data)
console.log(data);
}) })
signalR.connection?.on('userLeft', (data: any) => { signalR.connection?.on('userLeft', (data: any) => {
const uses = mainLayoutStore.OnlineUsers.filter(item => item.id !== data) const uses = mainLayoutStore.OnlineUsers.filter(item => item.id !== data)
mainLayoutStore.setOnlineUsers(uses) mainLayoutStore.setOnlineUsers(uses)
}) })
signalR.connection?.on('ReceiveData', (id: string, type: string, message: string) => { signalR.connection?.on('ReceiveData', (id: string, type: string, message: string) => {
if (id !== mainLayoutStore.SelectServer.id) return if (id !== mainLayoutStore.SelectServer.value) return
DataStore.setData(type, message) DataStore.setData(type, message)
}) })
signalR.connection?.on('ReceiveWaring', (value: string,valueName) => { signalR.connection?.on('ReceiveWaring', (value: string,valueName) => {
@ -97,13 +87,7 @@ onMounted(() => {
audio1.value.currentTime = 0; audio1.value.currentTime = 0;
const { const {
isSupported, isSupported,
notification,
show, show,
close,
onClick,
onShow,
onError,
onClose,
} = useWebNotification({ } = useWebNotification({
title: `你设定的${valueName}已经达到通知阈值,当前值为 ${value}`, title: `你设定的${valueName}已经达到通知阈值,当前值为 ${value}`,
dir: 'auto', dir: 'auto',
@ -157,14 +141,13 @@ const getHistoryData = async () => {
'Authorization': 'Bearer ' + useCookie('token').value 'Authorization': 'Bearer ' + useCookie('token').value
}, },
params: { params: {
ServerId: mainLayoutStore.SelectServer.id, ServerId: mainLayoutStore.SelectServer.value,
}, },
baseURL: useRuntimeConfig().public.baseUrl, baseURL: useRuntimeConfig().public.baseUrl,
}).then((res) => { }).then((res) => {
const data = res as dataHistoryType const data = res as dataHistoryType
DataStore.dataHistory = data DataStore.dataHistory = data
const datas = data.data const datas = data.data
console.log(datas)
// //
for (const key in datas) { for (const key in datas) {
if (Object.prototype.hasOwnProperty.call(datas, key)) { if (Object.prototype.hasOwnProperty.call(datas, key)) {
@ -173,14 +156,13 @@ const getHistoryData = async () => {
DataStore.data[key] = element[element.length - 1]; DataStore.data[key] = element[element.length - 1];
} }
} }
console.log("dasdas")
DataStore.startTimer() DataStore.startTimer()
}) })
} }
onMounted(async () => { onMounted(async () => {
await getHistoryData() await getHistoryData()
}) })
watch(() => mainLayoutStore.SelectServer.id, async () => { watch(() => mainLayoutStore.SelectServer.value, async () => {
await getHistoryData() await getHistoryData()
}) })
let isShift = false let isShift = false
@ -215,24 +197,11 @@ onKeyStroke('Shift', (e) => {
</audio> </audio>
<SideBar/> <SideBar/>
<TitleBar/> <TitleBar/>
<Dialog <n-modal v-model:show.lazy="visible" auto-focus style="max-width: 80vw" >
v-model:visible="visible"
:breakpoints="{ '1199px': '75vw', '575px': '90vw' }"
:pt="{
root: {
style:'border:unset;background-color:unset;'
},
mask: {
style: 'backdrop-filter: blur(20px)'
}
}"
modal
>
<template #container="{ closeCallback }">
<Term/> <Term/>
</template> </n-modal>
</Dialog>
<div class="main-box"> <div class="main-box">
<n-back-top :right="50" style="z-index: 200"/>
<div ref="mainRef" class="main"> <div ref="mainRef" class="main">
<div class="hero"> <div class="hero">
<div class="hero-box"> <div class="hero-box">
@ -240,29 +209,7 @@ onKeyStroke('Shift', (e) => {
<p>欢迎来到龙盾云御,这是基于Nuxt+Vue3的后台管理系统</p> <p>欢迎来到龙盾云御,这是基于Nuxt+Vue3的后台管理系统</p>
</div> </div>
<div class="server-dropdown"> <div class="server-dropdown">
<Dropdown v-model="mainLayoutStore.SelectServer" :options="ServerList" <n-select v-model:value="mainLayoutStore.SelectServer.value" :options="ServerList" />
class="server-dropdown-input"
optionLabel="name"
placeholder="选择服务器">
<template #value="slotProps">
<div v-if="slotProps.value">
<div style="color: #FFFFFF;gap: 8px;display: flex;align-items: center">{{ slotProps.value.name }}
<Tag :value="slotProps.value.id"/>
</div>
</div>
<span v-else>
{{ slotProps.placeholder }}
</span>
</template>
<template #option="slotProps">
<div
style="display: flex;align-items: center;justify-content: space-between;width: 100%;">
{{ slotProps.option.name }}
<Tag :value="slotProps.option.id"/>
</div>
</template>
</Dropdown>
</div> </div>
<HeroBox/> <HeroBox/>
</div> </div>
@ -272,7 +219,6 @@ onKeyStroke('Shift', (e) => {
</div> </div>
</div> </div>
<FloaterBar/> <FloaterBar/>
</section> </section>
</template> </template>
@ -324,7 +270,6 @@ onKeyStroke('Shift', (e) => {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: $gap; gap: $gap;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
h3, p { h3, p {
color: #FFF; color: #FFF;
@ -348,6 +293,18 @@ onKeyStroke('Shift', (e) => {
z-index: 11; z-index: 11;
right: $padding*2.5; right: $padding*2.5;
top: $padding*2; top: $padding*2;
:deep(.n-select>.n-base-selection){
.n-base-selection__border{
border: unset;
}
.n-base-selection-label{
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(400px);
>div{
color: #FFF;
}
}
}
} }
@ -360,10 +317,6 @@ onKeyStroke('Shift', (e) => {
padding: 0 40px 40px; padding: 0 40px 40px;
} }
.server-dropdown-input {
background: $primary-color;
color: #FFF;
border: unset;
}
</style> </style>

View File

@ -2,12 +2,10 @@ import {defineNuxtRouteMiddleware} from "#app";
import {useToast} from 'vue-toastification' import {useToast} from 'vue-toastification'
export default defineNuxtRouteMiddleware(async (to, from) => { export default defineNuxtRouteMiddleware(async (to, from) => {
const toast = useToast(); const toast = useToast();
const runtimeConfig = useRuntimeConfig(); const runtimeConfig = useRuntimeConfig();
const token = useCookie('token').value; const token = useCookie('token').value;
if (!token) { if (!token) {
toast.error('未登录', {timeout: 3000}) toast.error('未登录', {timeout: 3000})
return navigateTo("/SignIn"); return navigateTo("/SignIn");

View File

@ -1,8 +1,43 @@
// https://nuxt.com/docs/api/configuration/nuxt-config // https://nuxt.com/docs/api/configuration/nuxt-config
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'
export default defineNuxtConfig({ export default defineNuxtConfig({
devtools: {enabled: true}, devtools: {enabled: true},
ssr: false, ssr: false,
modules: ['@nuxtjs/color-mode', '@hypernym/nuxt-gsap', "@nuxt/image", "@nuxtjs/google-fonts", "nuxt-primevue", 'nuxt-lucide-icons', '@pinia/nuxt', '@pinia-plugin-persistedstate/nuxt', "@vite-pwa/nuxt", "@vueuse/nuxt"], modules: [
'@nuxtjs/color-mode',
'@hypernym/nuxt-gsap',
"@nuxt/image",
"@nuxtjs/google-fonts",
'nuxt-lucide-icons',
'@pinia/nuxt',
'@pinia-plugin-persistedstate/nuxt',
"@vite-pwa/nuxt",
"@vueuse/nuxt",
"nuxtjs-naive-ui"
],
vite: {
plugins: [
AutoImport({
imports: [
{
'naive-ui': [
'useDialog',
'useMessage',
'useNotification',
'useLoadingBar'
]
}
]
}),
Components({
resolvers: [NaiveUiResolver()]
})
]
},
pwa: { pwa: {
manifest: { manifest: {
name: "pwa nuxt 3", name: "pwa nuxt 3",
@ -22,13 +57,16 @@ export default defineNuxtConfig({
} }
}, },
gsap: { gsap: {
autoImport: true, autoImport: true,
extraPlugins: { extraPlugins: {
text: true text: true
} }
}, },
css: ['assets/min.scss', 'primevue/resources/themes/aura-light-green/theme.css', 'primeicons/primeicons.css', 'vue-toastification/dist/index.css'],
css: ['assets/min.scss', 'vue-toastification/dist/index.css'],
devServer: { devServer: {
port: 3001, host: '0.0.0.0', port: 3001, host: '0.0.0.0',
// https: { // https: {
@ -36,15 +74,18 @@ export default defineNuxtConfig({
// cert: "./localhost+3.pem", // cert: "./localhost+3.pem",
// } // }
}, },
plugins: [ plugins: [
{src: '~/plugins/vue-toast.ts'}, {src: '~/plugins/vue-toast.ts'},
{src: '~/plugins/apexcharts.ts'}, {src: '~/plugins/apexcharts.ts'},
], ],
runtimeConfig: { runtimeConfig: {
baseUrl: '', public: { baseUrl: '', public: {
apiBase: '/Api', baseUrl: process.env.NUXT_API_URL apiBase: '/Api', baseUrl: process.env.NUXT_API_URL
} }
}, },
googleFonts: { googleFonts: {
families: { families: {
Roboto: true, 'Josefin+Sans': true, Lato: [100, 300], Raleway: { Roboto: true, 'Josefin+Sans': true, Lato: [100, 300], Raleway: {
@ -56,4 +97,6 @@ export default defineNuxtConfig({
} }
} }
}, },
compatibilityDate: '2024-07-21',
}) })

13541
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -23,10 +23,14 @@
"grid-layout-plus": "^1.0.5", "grid-layout-plus": "^1.0.5",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"md-editor-v3": "^4.17.0", "md-editor-v3": "^4.17.0",
"naive-ui": "^2.39.0",
"nuxt": "^3.11.2", "nuxt": "^3.11.2",
"nuxt-primevue": "^3.0.0", "nuxt-primevue": "^3.0.0",
"nuxtjs-naive-ui": "^1.0.2",
"patch-package": "^8.0.0", "patch-package": "^8.0.0",
"primeicons": "^7.0.0", "primeicons": "^7.0.0",
"unplugin-auto-import": "^0.18.0",
"unplugin-vue-components": "^0.27.3",
"uuid": "^9.0.1", "uuid": "^9.0.1",
"vue": "^3.4.27", "vue": "^3.4.27",
"vue-echarts": "^6.7.3", "vue-echarts": "^6.7.3",
@ -52,5 +56,6 @@
"sass": "^1.77.4", "sass": "^1.77.4",
"vue3-draggable-grid": "^0.0.6", "vue3-draggable-grid": "^0.0.6",
"vue3-puzzle-vcode": "^1.1.7" "vue3-puzzle-vcode": "^1.1.7"
} },
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
} }

View File

@ -15,7 +15,7 @@ const mainLayoutStore=useMainLayoutStore()
<Icon name="Settings2"/> <Icon name="Settings2"/>
<p>平台设置</p> <p>平台设置</p>
</div> </div>
<div > <div @click="navigateTo(`/settings/userSettings/${mainLayoutStore.UserInfo.id}`)" >
<Icon name="UserRoundCog" /> <Icon name="UserRoundCog" />
<p>账号设置</p> <p>账号设置</p>
</div> </div>
@ -39,14 +39,22 @@ const mainLayoutStore=useMainLayoutStore()
display: grid; display: grid;
grid-template-columns: auto 1fr; grid-template-columns: auto 1fr;
grid-template-rows: 1fr; grid-template-rows: 1fr;
min-height: 800px; min-height: 74.645vh;
height: 100%;
width: 100%; width: 100%;
gap: $gap*2; gap: $gap*2;
} }
.setting-list,.setting-content{ .setting-list,.setting-content{
background: $light-bg-color; background: $light-bg-color;
border: $border; border: $border;
border-radius: $radius; border-radius: $radius;
color: $light-text-color;
overflow: hidden;
.dark-mode &{
background: $dark-bg-color;
color: $dark-text-color;
}
} }
.setting-list{ .setting-list{
display: flex; display: flex;

View File

@ -0,0 +1,26 @@
<script setup lang="ts">
</script>
<template>
<div class="user-info-layout">
<div class="user-info-bar"></div>
<div class="user-avatar">
<Avatar size="xlarge" shape="circle" class="avatar" image="https://api.multiavatar.com/asdasd.svg"/>
</div>
</div>
</template>
<style scoped lang="scss">
@import "../../../base";
.user-info-layout{
width: 100%;
display: flex;
flex-direction: column;
}
.user-info-bar{
height: 3.415vh;
background: #F0F0F0;
}
</style>

View File

@ -37,7 +37,7 @@ onMounted(() => {
:user-id="user.id"/> :user-id="user.id"/>
</div> </div>
</div> </div>
</template>c </template>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "base"; @import "base";

View File

@ -1,98 +0,0 @@
<script setup lang="ts">
definePageMeta({
layout: 'main',
middleware: ['auth']
})
</script>
<template>
<div class="user-info-layout">
<div class="user-card">
<div class="user-card-top">
<Avatar size="xlarge" shape="circle" class="avatar" image="https://api.multiavatar.com/asdasd.svg"/>
<h2>sdasdas</h2>
<p>1231231231sdasd2</p>
<Tag>思科交换机哈克斯的</Tag>
</div>
<div class="user-card-bottom">
<div>
<p>asdas</p>
<p>sadasd</p>
</div><div>
<p>asdas</p>
<p>sadasd</p>
</div>
</div>
</div>
<div class="user-info"></div>
</div>
</template>
<style scoped lang="scss">
@import "base";
.user-info-layout{
width: 100%;
display: grid;
min-height: 800px;
border-radius: $radius;
grid-template-columns: minmax(300px,400px) 3fr;
gap: $gap*4;
grid-template-rows: 1fr;
}
.user-card{
background: $light-bg-color;
border-radius: $radius;
border: 1px solid rgba(51, 51, 51, 0.17);
display: flex;
flex-direction: column;
}
.user-card-top{
padding: $padding*3;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: $gap;
.p-avatar{
width: 150px;
height: 150px;
}
h2{
font-weight: 500;
}
p{
color: #333;
//
letter-spacing: 1px;
}
.p-tag{
letter-spacing: 1px;
font-size: 13px;
padding: $padding*.5;
}
}
.user-card-bottom{
border-top: 1px solid rgba(51, 51, 51, 0.17);
flex: 1;
display: flex;
flex-direction: column;
gap: $gap;
padding: $padding $padding 0;
div{
display: flex;
justify-content: space-between;
p{
color: #333;
}
}
}
.user-info{
background: $light-bg-color;
border-radius: $radius;
padding: $padding*3;
border: 1px solid rgba(51, 51, 51, 0.17);
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

View File

@ -48,7 +48,7 @@ export const useMainLayoutStore = defineStore('MainLayoutStore', {
state: () => { state: () => {
return { return {
IsLeftSidebarMini: false, IsLeftSidebarMini: false,
SelectServer: <{ name: string, id?: string }>{}, // 选中的服务器 SelectServer: <{ label: string, value: string }>{}, // 选中的服务器
UserInfo: <UserInfoType>{}, UserInfo: <UserInfoType>{},
OnlineUsers: <OnlineUser[]>[], OnlineUsers: <OnlineUser[]>[],
ServerConfig: <{ key: string, value: string }[]>[], // 服务器配置 ServerConfig: <{ key: string, value: string }[]>[], // 服务器配置

File diff suppressed because it is too large Load Diff