修改侧边栏

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>
<Folder Include="Configs\Alerts\" />
<Folder Include="wwwroot\" />
</ItemGroup>
<ItemGroup>
<Content Update="wwwroot\index">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

View File

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

View File

@ -4,7 +4,7 @@
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://192.168.1.12:58826",
"applicationUrl": "http://192.168.1.22:58826",
"sslPort": 44304
}
},
@ -14,7 +14,7 @@
"dotnetRunMessages": true,
"launchBrowser": false,
"launchUrl": "swagger",
"applicationUrl": "http://192.168.1.12:5000",
"applicationUrl": "http://192.168.1.22:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
@ -24,7 +24,7 @@
"dotnetRunMessages": true,
"launchBrowser": true,
"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": {
"ASPNETCORE_ENVIRONMENT": "Development"
}

View File

View File

@ -133,39 +133,167 @@
{
"description": "Reference a permission or permission set by identifier and extends its scope.",
"type": "object",
"required": [
"identifier"
],
"properties": {
"identifier": {
"description": "Identifier of the permission or permission set.",
"allOf": [
{
"$ref": "#/definitions/Identifier"
"oneOf": [
{
"type": "object",
"required": [
"identifier"
],
"properties": {
"identifier": {
"oneOf": [
{
"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": {
"items": {
"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": {
"items": {
"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"
}
}
}
}
]
},
"allow": {
"description": "Data that defines what is allowed by the scope.",
"type": [
"array",
"null"
],
"items": {
"$ref": "#/definitions/Value"
}
},
"deny": {
"description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.",
"type": [
"array",
"null"
],
"items": {
"$ref": "#/definitions/Value"
}
}
}
]
}
]
},
@ -1165,6 +1293,83 @@
"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.",
"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>
<div>
<NuxtPwaManifest />
<n-loading-bar-provider>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</n-loading-bar-provider>
</div>
</template>

View File

@ -2,130 +2,81 @@
import {useDataStore} from "~/strores/DataStore";
import {useMainLayoutStore} from "~/strores/UseMainLayoutStore";
const {$gsap} = useNuxtApp()
const props = defineProps({
title: {
type: String,
default: "CPU使用率"
default: "CPU使用率",
},
info:{
type:Array,
default:()=>[],
},
unit: {
type: String,
default: "%"
},
watcher: {
type: String,
default: "cpuTotalUsage"
type: [String, Array],
default: "cpuTotalUsage",
validator: (value) => {
return typeof value === 'string' || Array.isArray(value);
}
},
reverse: {
type: Boolean,
default: false
}
})
const value = ref("0");
const trueValue = ref("0");
const values=ref<string[]>(Array( Array.isArray(props.watcher)?props.watcher.length:1).fill('0'))
const dataStore = useDataStore()
const mainStore = useMainLayoutStore()
const status=ref<string>("success")
//dataStore
dataStore.$subscribe((_, state) => {
//d3YT#cpuUserUsagekeyvalue
const data = state.data[props.watcher]
//data
if (!data) {
value.value = "0"
updateProgress(0)
if (props.reverse) {
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))
}
const watcher = Array.isArray(props.watcher) ? [...props.watcher] : [props.watcher];
// 使mapstate.datawatcherKey
values.value = watcher.map(watcherKey => state.data[watcherKey as string]);
//50warning 80error
const value=Number(values.value[0])
status.value = value > 80 ? "error" : value > 50 ? "warning" : "success";
})
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>
<template>
<div class="mini-card-box">
<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 :id="title" :stroke-dasharray="circumference" cx="36"
cy="36" r="32" stroke="red"
stroke-linecap="round"
stroke-width="4" transform="rotate(-90, 36, 36)"/>
<path
: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"
fill="#ADB5BD"/>
</svg>
<div class="text">
<h3>
{{ title }}
</h3>
<n-popover trigger="hover" placement="bottom">
<template #trigger>
<div class="mini-card-box">
<!-- <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 :id="title" :stroke-dasharray="circumference" cx="36"-->
<!-- cy="36" r="32" stroke="red"-->
<!-- stroke-linecap="round"-->
<!-- stroke-width="4" transform="rotate(-90, 36, 36)"/>-->
<!-- <path-->
<!-- :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"-->
<!-- fill="#ADB5BD"/>-->
<!-- </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">
<h3>
{{ title }}
</h3>
<h2>
<span>{{ Number(values[0]??0).toFixed(2)}}{{ unit }}</span>
</h2>
</div>
</div>
</template>
<n-tag type="info" v-for="i in info">
{{ i }}
</n-tag>
</n-popover>
<h2>
<span v-if="props.reverse" v-tooltip="`${100-trueValue}${unit}`">{{ 100 - value }}{{ unit }}</span>
<span v-else v-tooltip="`${trueValue}${unit}`">{{ value }}{{ unit }}</span>
</h2>
</div>
</div>
</template>
<style lang="scss" scoped>
@ -140,7 +91,9 @@ onMounted(()=>{
box-shadow: 0 10px 30px 0 rgba(17, 38, 146, 0.05);
gap: $gap*3;
border: $border;
*{
@include SC_Font()
}
> svg {
width: 68px;
height: 68px;
@ -160,17 +113,16 @@ onMounted(()=>{
color: $light-unfocused-color;
font-size: 16px;
font-style: normal;
font-weight: 400;
font-weight: 600;
line-height: 175%; /* 28px */
//
text-wrap: nowrap;
}
h2 {
font-size: 19px;
font-weight: 500;
font-size: 25px;
font-weight: 800;
color: $light-text-color;
.dark-mode & {
color: $dark-text-color;
}

View File

@ -37,13 +37,23 @@ const menu = ref();
<Button autofocus label="发送" outlined severity="secondary" @click="sendMessage"/>
</template>
</Dialog>
<n-modal v-model:show="sendMessageModel">
123123
</n-modal>
<ContextMenu ref="menu" :model="items"/>
<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">
<h4>{{ user.nickName }}</h4>
<div>
<Tag :value="user.posts"/>
<n-tag type="info" :bordered="false">
{{user.posts}}
</n-tag>
</div>
</div>
</div>
@ -64,7 +74,7 @@ const menu = ref();
align-items: center;
justify-content: space-between;
gap: $gap*3;
padding: $padding*.2;
padding: $padding*.25 $padding;
border-radius: $radius;
&:hover {
@ -75,7 +85,7 @@ const menu = ref();
display: flex;
flex-direction: column;
justify-content: center;
gap: $gap;
gap: $gap*.2;
flex: 1;
}
}

View File

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

View File

@ -1,20 +1,41 @@
<script lang="ts" setup>
import MiniCard from "~/components/Cards/MiniCard.vue";
import {useMainLayoutStore} from "~/strores/UseMainLayoutStore";
import {useDataStore} from "~/strores/DataStore";
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>
<template>
<n-scrollbar x-scrollable>
<div ref="gridRef" class="top-grid-layout">
<MiniCard title="系统使用率" watcher="RequestJob"/>
<MiniCard title="CPU总使用率" watcher="CpuTotalUsage"/>
<MiniCard title="内存总使用率" watcher="MemoryTotalUsage"/>
<MiniCard title="读写使用率" watcher="CpuIOWaitUsage"/>
<MiniCard title="磁盘总使用率" watcher="DiskTotalUsage"/>
<MiniCard title="网络接口使用率" watcher="InterfaceTotalUtilizationPercentage"/>
<MiniCard title="系统使用率" watcher="RequestJob" :info="['112312']"/>
<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="['DiskTotalUsage',...DiskUsageCount]"/>
<MiniCard title="网络接口使用率" :watcher="['InterfaceTotalUtilizationPercentage',...NetWorkUsageCount]"/>
</div>
</n-scrollbar>
</template>
<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>

View File

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

View File

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

View File

@ -1,185 +1,110 @@
<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 mainLayoutStore = useMainLayoutStore()
const Menus =ref([
{
"label": "系统概览",
"icon": "LayoutGrid",
"route": "/Home"
}, {
"label": "主机监测",
"icon": "Cpu",
"route": "/host/cpu"
},{
"label": "用户监测",
"icon": "Cctv",
"route": "/serverUser/all"
},{
"label": "账号列表",
"icon": "UsersRound",
"route": "/User"
},{
"label": "巡检记录",
"icon": "PackageSearch",
"route": "/InspectionRecords"
},
{
"label": "系统设置",
"icon": "Settings",
"route": "/Settings/PanelSettings"
},
])
const filteredMenus=computed(()=>{
return Menus.value.filter(menu=>{
const role=mainLayoutStore.UserInfo.role
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(()=>{
navigateTo("/SignIn")
})
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: '系统概览',
key: 'home',
icon:renderIcon(LayoutGrid)
},
{
label: '主机监测',
key: 'host',
icon:renderIcon(Computer),
children: [
{
label: 'CPU',
key: 'host/cpu',
icon:renderIcon(Cpu)
}
]
},
{
label: '用户监测',
key: 'serverUser/all',
icon:renderIcon(Cctv)
},
{
label:'账号管理',
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>
<template>
<section class="sidebar-layout">
<div class="switch-button" @click="mainLayoutStore.toggleLeftSidebarMini()">
<ArrowLeft/>
</div>
<div class="header">
<div class="user">
<Logo/>
<div class="name">
<h3>
龙盾云御
</h3>
</div>
<transition name="fade" mode="out-in">
<div v-if="!collapsed" class="header">
<n-image
width="45"
src="/Square310x310Logo.png"
/>
<h1>龙盾云御</h1>
</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 v-else class="header">
<n-image
width="45"
src="/Square310x310Logo.png"
/>
<h1 v-if="!collapsed">龙盾云御</h1>
</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>
</section>
</template>
@ -190,11 +115,10 @@ const logout=()=>{
.sidebar-layout {
display: flex;
grid-area: sidebar;
padding: 24px 24px 32px 24px;
flex-direction: column;
justify-content: space-between;
align-items: flex-start;
flex-shrink: 0;
gap: $gap;
background: $light-bg-color;
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 {
height: 76px;
display: flex;
gap: 44px;
width: 100%;
flex-direction: column;
align-items: 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;
}
.switch-button {
position: absolute;
top: 25px;
right: -15px;
display: flex;
justify-content: center;
align-items: center;
z-index: 10;
width: 30px;
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;
h1{
font-size: 30px;
font-weight: 600;
}
}
.bottom{
height: 49px;
display: flex;
align-items: center;
width: 100%;
padding: 20px;
}
</style>

View File

@ -1,34 +1,31 @@
<script lang="ts" setup>
import {useMainLayoutStore} from "~/strores/UseMainLayoutStore";
import {BellRing, Mail} from 'lucide-vue-next';
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 routes=computed(()=>{
const route=router.currentRoute.value.name as string
return route.split('-').map(x => {
return {
label: x.charAt(0).toUpperCase() + x.slice(1),
icon: 'pi pi-angle'
}
return x.charAt(0).toUpperCase() + x.slice(1)
})
})
const visibleRight = ref(false)
const value=ref()
const options = ref(['@gmail.com', '@outlook.com', '@yahoo.com'])
</script>
<template>
<section class="title-bar-layout">
<Sidebar v-model:visible="visibleRight" header="通知" position="right">
<SidebarRight/>
</Sidebar>
<h1 class="name">
<Breadcrumb :home="home" :model="routes" />
</h1>
<n-auto-complete
v-model:value="value"
:input-props="{
autocomplete: 'disabled',
}"
:options="options"
placeholder="搜索"
clearable
/>
<div class="action">
<Icon name="BellRing" @click="visibleRight=true" :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-rows: 1fr;
align-items: center;
padding: 0 $padding*2;
padding: 0 $padding;
gap: $gap*2;
@include SC_Font();
.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 {
font-weight: 700;
font-size: 26px;

View File

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

View File

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

View File

@ -1,59 +1,102 @@
// 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({
devtools: {enabled: true},
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"],
pwa: {
manifest: {
name: "pwa nuxt 3",
short_name: "pwa nuxt",
theme_color: '#FFFFFF',
description: "Arman Abi r.man.abi@gmail.com",
icons: [{
src: '/pwa-192x192.png', sizes: "192x192", type: "image/png"
},]
devtools: {enabled: true},
ssr: false,
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: {
manifest: {
name: "pwa nuxt 3",
short_name: "pwa nuxt",
theme_color: '#FFFFFF',
description: "Arman Abi r.man.abi@gmail.com",
icons: [{
src: '/pwa-192x192.png', sizes: "192x192", type: "image/png"
},]
}, workbox: {
navigateFallback: "/SignIn",
}, workbox: {
navigateFallback: "/SignIn",
}, devOptions: {
enabled: true, type: "module"
}
}, devOptions: {
enabled: true, type: "module"
}
},
gsap: {
autoImport: true,
extraPlugins: {
text: true
}
},
css: ['assets/min.scss', 'primevue/resources/themes/aura-light-green/theme.css', 'primeicons/primeicons.css', 'vue-toastification/dist/index.css'],
devServer: {
port: 3001, host: '0.0.0.0',
// https: {
// key: "./localhost+3-key.pem",
// cert: "./localhost+3.pem",
// }
},
plugins: [
{src: '~/plugins/vue-toast.ts'},
{src: '~/plugins/apexcharts.ts'},
],
runtimeConfig: {
baseUrl: '', public: {
apiBase: '/Api', baseUrl: process.env.NUXT_API_URL
}
},
googleFonts: {
families: {
Roboto: true, 'Josefin+Sans': true, Lato: [100, 300], Raleway: {
wght: [100, 400], ital: [100]
}, Inter: '200..700', 'Crimson Pro': {
wght: '200..900', ital: '200..700',
}, 'Noto Sans SC': {
wght: '200..900', ital: '200..700'
}
}
},
},
gsap: {
autoImport: true,
extraPlugins: {
text: true
}
},
css: ['assets/min.scss', 'vue-toastification/dist/index.css'],
devServer: {
port: 3001, host: '0.0.0.0',
// https: {
// key: "./localhost+3-key.pem",
// cert: "./localhost+3.pem",
// }
},
plugins: [
{src: '~/plugins/vue-toast.ts'},
{src: '~/plugins/apexcharts.ts'},
],
runtimeConfig: {
baseUrl: '', public: {
apiBase: '/Api', baseUrl: process.env.NUXT_API_URL
}
},
googleFonts: {
families: {
Roboto: true, 'Josefin+Sans': true, Lato: [100, 300], Raleway: {
wght: [100, 400], ital: [100]
}, Inter: '200..700', 'Crimson Pro': {
wght: '200..900', ital: '200..700',
}, 'Noto Sans SC': {
wght: '200..900', ital: '200..700'
}
}
},
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",
"lodash": "^4.17.21",
"md-editor-v3": "^4.17.0",
"naive-ui": "^2.39.0",
"nuxt": "^3.11.2",
"nuxt-primevue": "^3.0.0",
"nuxtjs-naive-ui": "^1.0.2",
"patch-package": "^8.0.0",
"primeicons": "^7.0.0",
"unplugin-auto-import": "^0.18.0",
"unplugin-vue-components": "^0.27.3",
"uuid": "^9.0.1",
"vue": "^3.4.27",
"vue-echarts": "^6.7.3",
@ -52,5 +56,6 @@
"sass": "^1.77.4",
"vue3-draggable-grid": "^0.0.6",
"vue3-puzzle-vcode": "^1.1.7"
}
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}

View File

@ -15,8 +15,8 @@ const mainLayoutStore=useMainLayoutStore()
<Icon name="Settings2"/>
<p>平台设置</p>
</div>
<div >
<Icon name="UserRoundCog"/>
<div @click="navigateTo(`/settings/userSettings/${mainLayoutStore.UserInfo.id}`)" >
<Icon name="UserRoundCog" />
<p>账号设置</p>
</div>
<div @click="navigateTo('/settings/alertSettings')">
@ -39,14 +39,22 @@ const mainLayoutStore=useMainLayoutStore()
display: grid;
grid-template-columns: auto 1fr;
grid-template-rows: 1fr;
min-height: 800px;
min-height: 74.645vh;
height: 100%;
width: 100%;
gap: $gap*2;
}
.setting-list,.setting-content{
background: $light-bg-color;
border: $border;
border-radius: $radius;
color: $light-text-color;
overflow: hidden;
.dark-mode &{
background: $dark-bg-color;
color: $dark-text-color;
}
}
.setting-list{
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"/>
</div>
</div>
</template>c
</template>
<style lang="scss" scoped>
@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: () => {
return {
IsLeftSidebarMini: false,
SelectServer: <{ name: string, id?: string }>{}, // 选中的服务器
SelectServer: <{ label: string, value: string }>{}, // 选中的服务器
UserInfo: <UserInfoType>{},
OnlineUsers: <OnlineUser[]>[],
ServerConfig: <{ key: string, value: string }[]>[], // 服务器配置

File diff suppressed because it is too large Load Diff