修改边距 适配多重侧边栏

This commit is contained in:
niyyzf 2024-07-22 21:05:18 +08:00
parent 1f0275a3a2
commit 5148df4491
21 changed files with 175 additions and 100 deletions

View File

@ -273,7 +273,7 @@ public class ServerController(IServiceProvider serviceProvider, ApplicationDbCon
var serverConfigs = JobConfigHelper.GetServers().ToList();
var server = serverConfigs.Find(x =>x.Id == serverId);
if (server == null) return BadRequest();
var output = await sshClient?.ExecuteCommandAsync(serverId,"awk -F: '$1 != \"nobody\" && $1 != \"build\" && $3 >= 1000 {print $1}'","/etc/passwd")!;
var output = await sshClient?.ExecuteCommandAsync(serverId,"awk -F: '$1 != \"nobody\" &&$1 != \"build\" {print $1 \":\"$3}' /etc/passwd | sort -t: -k2nr")!;
if (string.IsNullOrEmpty(output)) return BadRequest("无法获得用户树");
var data = output.Split('\n', StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).ToList();
output = await sshClient?.ExecuteCommandAsync(serverId,"w -husf","|"," awk '$2 !~ /^tty/ {print$1, $2}'"," |"," sort ","|"," uniq")!;
@ -461,6 +461,11 @@ public class ServerController(IServiceProvider serviceProvider, ApplicationDbCon
public async Task<IActionResult> GetWordList([FromQuery] string serverId)
{
var path = Path.Combine(AppContext.BaseDirectory, "markdowns", serverId);
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
var files = Directory.GetFiles(path);
var wordList = new List<WordFileModel>();
foreach (var file in files)

View File

@ -20,10 +20,11 @@ $dark-text-color: #D3D3D3;
$dark-unfocused-color: $unfocused-color;
$border:1px solid rgba(51, 51, 51, 0.17);
$border-dark:1px solid rgba(255, 255, 255, 0.17);
$gap: 8px;
$padding: 16px;
$radius: 8px;
$gap: 4px;
$padding: 8px;
$radius: 4px;
.dark-mode html {
$dark-bg-color: red;

View File

@ -82,7 +82,7 @@ const uploadFile=()=>{
$fetch('/Api/Server/UpLoadWord', {
method: 'POST',
params:{
serverId: mainLayoutStore.SelectServer.id,
serverId: mainLayoutStore.SelectServer.value,
userName:mainLayoutStore.UserInfo.userName,
wordId: id,
},
@ -108,7 +108,7 @@ onBeforeMount(()=>{
$fetch('/Api/Server/GetWordContent', {
method: 'GET',
params:{
serverId: mainLayoutStore.SelectServer.id,
serverId: mainLayoutStore.SelectServer.value,
wordId: props.wordId,
},
headers: {
@ -128,19 +128,18 @@ onBeforeMount(()=>{
<template>
<div class="editor-layout">
<Dialog
v-model:visible="filenameVisible"
modal
header="输入文件名"
<n-modal
v-model:show="filenameVisible"
preset="dialog"
title="输入文件名"
>
<div class="fileName">
<input v-model="fileName">
<Button label="保存" @click="saveFileName" />
<n-button type="info" @click="saveFileName">
保存
</n-button>
</div>
</Dialog>
<div class="actions">
<Icon name="X" @click="closeCallBack" ></Icon>
</div>
</n-modal>
<MdEditor v-model="text" v-if="!preview" :theme="$colorMode.value as 'light'| 'dark'" class="editor" codeTheme="kimbie"
@onSave="onSave" @onUploadImg="onUploadImg" />
<MdPreview :modelValue="text" v-else class="editor-preview"/>
@ -170,8 +169,10 @@ onBeforeMount(()=>{
.fileName{
display: flex;
justify-content: space-between;
align-items: center;
gap: $gap*4;
input{
width: 70%;
flex: 1;
border: $border;
background: rgba(51, 51, 51, 0.1);
border-radius: $radius;

View File

@ -16,7 +16,7 @@ const GetServerUserList = () => {
$fetch('/Api/Server/GetServerUserList', {
method: 'GET',
params: {
ServerId: mainLayoutStore.SelectServer.id
ServerId: mainLayoutStore.SelectServer.value
},
headers: {
'Content-Type': 'application/json',
@ -36,7 +36,13 @@ const refresh = () => {
userList.value=[]
GetServerUserList()
}
const page=ref<number>(1)
const filterPage=computed(()=>{
if(userList.value.length<=10){
return userList.value
}
return userList.value.slice((page.value-1)*14,page.value*14)
})
</script>
<template>
@ -49,32 +55,39 @@ const refresh = () => {
<div class="user-list-body">
<div class="user-list-item" @click="navigateTo(`/serverUser/all`)">
<div class="avatar">
<Avatar size="xlarge" shape="circle" class="avatar" label="全"/>
<n-avatar
:size="48"> 全部 </n-avatar>
</div>
<div class="info">
<div class="top">
<h3>全部</h3>
</div>
<p>NULL</p>
</div>
</div>
<div :class="{'user-list-item':true,'user-list-item-active':$route.params.id===item.name}" v-for="item in userList" @click="navigateTo(`/serverUser/${item.name}`)">
<div :class="{'user-list-item':true,'user-list-item-active':$route.params.id===item.name}" v-for="item in filterPage" @click="navigateTo(`/serverUser/${item.name.split(':')[0]}`)">
<div class="avatar">
<Avatar size="xlarge" shape="circle" class="avatar" :label="item.name[0].toUpperCase()"/>
<n-avatar
:size="48">
{{item.name.slice(0,3).toUpperCase()}}
</n-avatar>
</div>
<div class="info">
<div class="top">
<h3>{{ item.name }}</h3>
<h3>{{ item.name.split(':')[0] }}</h3>
<div class="tags">
<Tag v-if="item.isOnline">在线</Tag>
<Tag v-if="item.isOnline">{{item.port}}</Tag>
<Tag v-if="item.isOnline">{{item.address}}</Tag>
<n-tag type="info" :bordered="false">UID:{{item.name.split(':')[1]}}</n-tag>
<n-Tag v-if="item.isOnline">在线</n-Tag>
<n-Tag v-if="item.isOnline">{{item.port}}</n-Tag>
<n-Tag v-if="item.isOnline">{{item.address}}</n-Tag>
</div>
</div>
<p>{{item.lastLoginTime}}</p>
</div>
</div>
</div>
<div class="pages">
<n-pagination v-model:page="page" :page-count="Math.floor(userList.length/13)" size="medium"/>
</div>
</div>
</template>
@ -87,6 +100,8 @@ const refresh = () => {
border: $border;
padding: $padding 0;
grid-row: 1/3;
display: flex;
flex-direction: column;
*{
@include SC_Font;
}
@ -124,19 +139,29 @@ const refresh = () => {
}
}
}
.pages{
padding: $padding;
display: flex;
justify-content: center;
align-items: center;
}
.user-list-body{
display: flex;
flex-direction: column;
flex: 1;
gap: $gap;
padding: $padding*2 $padding;
}
.user-list-item{
display: flex;
align-items: center;
gap: $gap;
gap: $gap*4;
padding: 8px;
border-radius: $radius;
cursor: pointer;
.avatar{
display: flex;
}
h3,p{
color: $light-text-color;
}

View File

@ -9,10 +9,6 @@ const userInfo = toRef(MainLayoutStore.UserInfo)
<template>
<div class="User-Mini-Box">
<NuxtImg :src="userInfo.avatar" alt="Avatar" height="45" width="45"/>
<div>
<h3>{{ userInfo.nickName }}</h3>
<p>{{ userInfo.desc ?? "荒芜之地 ~ ~" }}</p>
</div>
</div>
</template>

View File

@ -5,6 +5,7 @@
<template>
<div class="BottomBar-Box">
<div class="Left-Text-Box">
<p @click="$colorMode.preference = $colorMode.value == 'dark' ? 'light' : 'dark'" style="cursor: pointer">{{$colorMode.value=='dark'?'深色':'浅色'}}</p>
<p>隐私策略</p>
<p>关于我们</p>
</div>
@ -25,6 +26,10 @@
padding: 16px 24px;
align-items: center;
background: $light-bg-color;
border-top: $border;
.dark-mode &{
border-top: $border-dark;
}
}
.Left-Text-Box {

View File

@ -92,8 +92,10 @@ watch(()=>activeKey.value,async (newValue)=>{
@collapse="collapsed = true"
@expand="collapsed = false"
:width="200"
:inverted="$colorMode.value == 'dark'"
>
<n-menu
:inverted="$colorMode.value == 'dark'"
:collapsed="collapsed"
v-model:value="activeKey"
:collapsed-width="64"
@ -102,6 +104,7 @@ watch(()=>activeKey.value,async (newValue)=>{
/>
</n-layout-sider>
</n-layout>
<div class="bottom">
<PanelLeftOpen v-if="collapsed" @click="collapsed=false" />
<PanelLeftClose v-else @click="collapsed=true" />
@ -119,6 +122,7 @@ watch(()=>activeKey.value,async (newValue)=>{
justify-content: space-between;
align-items: flex-start;
gap: $gap;
border-right: $border;
background: $light-bg-color;
position: relative;
*{
@ -126,6 +130,7 @@ watch(()=>activeKey.value,async (newValue)=>{
}
.dark-mode & {
background: $dark-bg-color;
border-right: $border-dark;
}
}
@ -145,22 +150,31 @@ watch(()=>activeKey.value,async (newValue)=>{
}
.header {
height: 76px;
height: 62px;
display: flex;
width: 100%;
align-items: center;
justify-content: center;
gap: $gap;
h1{
font-size: 30px;
font-weight: 600;
}
}
.bottom{
height: 49px;
height: 50px;
display: flex;
align-items: center;
width: 100%;
padding: 20px;
border-top: $border;
.dark-mode & {border-top: $border-dark;}
}
.n-layout{
background: unset;
}
.n-layout-sider{
background: unset;
}
</style>

View File

@ -46,11 +46,13 @@ const options = ref(['@gmail.com', '@outlook.com', '@yahoo.com'])
grid-template-columns: auto 1fr auto auto;
grid-template-rows: 1fr;
align-items: center;
padding: 0 $padding;
padding: 0 $padding*2;
border-bottom: $border;
gap: $gap*2;
@include SC_Font();
.dark-mode & {
background: $dark-bg-color;
border-bottom: $border-dark;
}
}

View File

@ -19,15 +19,19 @@ onMounted(() => {
@import "base";
.login-layout{
background: white;
height: 100%;
min-height: 100vh;
background: $light-bg-color;
width: 100%;
padding: 32px;
gap: 32px;
background: unset;
display: grid;
grid-template-columns: 1fr minmax(800px,1fr);
grid-template-rows: 1fr;
.dark-mode &{
background: $dark-bg-color;
}
*{
@include SC_Font;
}
@ -39,6 +43,9 @@ onMounted(() => {
background: url("/bg1.jpg") no-repeat center center / cover;
//border: $border;
box-shadow: 2px 2px 10px rgba(0,0,0,0.1);
.dark-mode &{
background-image: url("/bg2.jpg");
}
}
.left-content{
position: relative;

View File

@ -231,7 +231,7 @@ onKeyStroke('Shift', (e) => {
max-width: 100vw;
display: grid;
grid:
"sidebar titlebar" 76px
"sidebar titlebar" auto
"sidebar main" 1fr
"sidebar folder" auto
/ auto 1fr;
@ -250,7 +250,7 @@ onKeyStroke('Shift', (e) => {
.main {
display: grid;
width: 100%;
grid-template-rows: 145px 55px 1fr;
grid-template-rows: 125px 75px 1fr;
will-change: scroll-position;
padding-bottom: 400px;
}
@ -265,15 +265,15 @@ onKeyStroke('Shift', (e) => {
.hero-box {
position: absolute;
z-index: 11;
left: $padding*2;
top: $padding;
left: $padding*3;
top: $padding*3;
display: flex;
flex-direction: column;
gap: $gap;
h3, p {
color: #FFF;
font-size: 40px;
font-size: 30px;
font-style: normal;
font-weight: 700;
line-height: normal;
@ -291,8 +291,8 @@ onKeyStroke('Shift', (e) => {
.server-dropdown {
position: absolute;
z-index: 11;
right: $padding*2.5;
top: $padding*2;
right: $padding*3;
top: $padding*3;
:deep(.n-select>.n-base-selection){
.n-base-selection__border{
border: unset;
@ -314,7 +314,7 @@ onKeyStroke('Shift', (e) => {
z-index: 10;
width: 100%;
display: flex;
padding: 0 40px 40px;
padding: 0 20px 20px;
}

View File

@ -24,7 +24,7 @@ const headers = computed(() => {
route: '/host/cpu'
}, {
label: '内存',
icon: 'Cpu',
icon: 'MemoryStick',
route: '/host/memory'
},
...diskRoute.value.map(x => {
@ -59,7 +59,7 @@ const getRote = () => {
$fetch('/Api/Server/GetServerDiskList', {
method: 'GET',
params: {
ServerId: mainLayoutStore.SelectServer.id
ServerId: mainLayoutStore.SelectServer.value
},
baseURL: useRuntimeConfig().public.baseUrl,
headers: {
@ -79,7 +79,7 @@ const getRote = () => {
$fetch('/Api/Server/GetServerNetworkEquipmentList', {
method: 'GET',
params: {
ServerId: mainLayoutStore.SelectServer.id
ServerId: mainLayoutStore.SelectServer.value
},
baseURL: useRuntimeConfig().public.baseUrl,
headers: {
@ -96,7 +96,7 @@ const getRote = () => {
})
})
}
watch(() => mainLayoutStore.SelectServer.id, () => {
watch(() => mainLayoutStore.SelectServer.value, () => {
getRote()
navigateTo('/host/cpu')
reLoad.value = true

View File

@ -15,7 +15,7 @@ const getSystemInfo = () => {
method: 'GET',
responseType: "json",
params: {
serverId: mainLayoutStore.SelectServer.id
serverId: mainLayoutStore.SelectServer.value
},
headers: {
'Content-Type': 'application/json',

View File

@ -16,7 +16,7 @@ onMounted(() => {
$fetch('/Api/Server/GetServerDiskInfo', {
method: 'GET',
params: {
ServerId: mainLayoutStore.SelectServer.id,
ServerId: mainLayoutStore.SelectServer.value,
DiskId: id.value
},
headers: {
@ -212,7 +212,7 @@ onMounted(() => {
display: grid;
grid-template-columns: 4px 1fr;
grid-template-rows: 1fr 1fr;
grid-column-gap: $gap;
grid-column-gap: $gap*2;
& > div {
height: 100%;

View File

@ -15,7 +15,7 @@ const getSystemInfo = () => {
method: 'GET',
responseType: "json",
params: {
serverId: mainLayoutStore.SelectServer.id
serverId: mainLayoutStore.SelectServer.value
},
headers: {
'Content-Type': 'application/json',
@ -39,7 +39,7 @@ onMounted(() => {
<div class="memory-layout">
<div class="left-chart">
<div class="memory-title">
<h1>CPU</h1>
<h1>内存</h1>
<h2>{{ (Number(dataStore.data["MemoryTotal"]) / 1024 / 1024).toFixed(2) }} GB</h2>
</div>
<div class="memory-chart-top">

View File

@ -12,7 +12,7 @@ onMounted(() => {
$fetch('/Api/Server/GetServerNetworkEquipmentInfo', {
method: 'GET',
params: {
ServerId: mainLayoutStore.SelectServer.id,
ServerId: mainLayoutStore.SelectServer.value,
NetworkId: id.value
},
headers: {
@ -187,7 +187,7 @@ onMounted(() => {
display: grid;
grid-template-columns: 4px 1fr;
grid-template-rows: 1fr 1fr;
grid-column-gap: $gap;
grid-column-gap: $gap*2;
& > div {
height: 100%;

View File

@ -40,7 +40,7 @@ const getNetworkList=()=>{
$fetch('/Api/Server/GetServerNetworkList',{
method:'GET',
params:{
ServerId:mainLayoutStore.SelectServer.id,
ServerId:mainLayoutStore.SelectServer.value,
},
headers: {
'Content-Type': 'application/json',

View File

@ -28,7 +28,7 @@ const getProcessList = async () => {
const response = await $fetch('/Api/Server/GetServerProcessesList', {
method: 'GET',
params: {
ServerId: mainLayoutStore.SelectServer.id,
ServerId: mainLayoutStore.SelectServer.value,
UserName:props.userName
},
baseURL: useRuntimeConfig().public.baseUrl,

View File

@ -38,7 +38,7 @@ const getWordList = ()=>{
$fetch('/Api/Server/GetWordList',{
method:'GET',
params:{
ServerId:mainLayoutStore.SelectServer.id,
ServerId:mainLayoutStore.SelectServer.value,
},
headers: {
'Content-Type': 'application/json',
@ -68,34 +68,33 @@ const templateContent= ref<string>()
const selectTemplate = (template:string)=>{
templateContent.value=template
editVisible.value = true
templateVisible.value = false
}
watch(()=>mainLayoutStore.SelectServer.id,()=>{
watch(()=>mainLayoutStore.SelectServer.value,()=>{
getWordList()
})
</script>
<template>
<div class="inspection-layout">
<Dialog
v-model:visible="editVisible"
:pt="{
root: {
style:'border:unset;background-color:unset;'
},
mask: {
style: 'backdrop-filter: blur(20px)'
}
}"
modal
<n-modal
v-model:show="editVisible"
title="编辑器"
:mask-closable="false"
:close-on-esc="false"
size="medium"
preset="card"
transform-origin="center"
style="width: auto"
>
<template #container="{ closeCallback }">
<MarkdownEdit :word-id="selectWord" :preview="isPreview" :template="templateContent" :close-call-back="closeCallback"/>
</template>
</Dialog>
<Dialog
v-model:visible="templateVisible"
modal
header="选择一个模板"
<MarkdownEdit :word-id="selectWord" :preview="isPreview" :template="templateContent" />
</n-modal>
<n-modal
title="选择一个模板"
size="medium"
preset="card"
style="width: 500px"
v-model:show="templateVisible"
>
<div class="template-layout">
<div class="template-item" @click="selectTemplate('')">
@ -107,25 +106,20 @@ watch(()=>mainLayoutStore.SelectServer.id,()=>{
<h5>{{t.name}}</h5>
</div>
</div>
</Dialog>
<Toolbar class="inspection-toolbar">
<template #start>
<input class="search" placeholder="搜索"/>
</template>
<template #end>
<div class="toolbar-start">
<button @click="templateVisible=true">
<Icon name="Plus" :stroke-width="1.2" />
<span>新建</span>
</button>
<button @click="getWordList" >
<Icon name="ListRestart" :stroke-width="1.2" />
<span>刷新</span>
</button>
</div>
</template>
</Toolbar>
</n-modal>
<div class="inspection-toolbar">
<input class="search" placeholder="搜索"/>
<div class="toolbar-start">
<button @click="templateVisible=true">
<Icon name="Plus" :stroke-width="1.2" />
<span>新建</span>
</button>
<button @click="getWordList" >
<Icon name="ListRestart" :stroke-width="1.2" />
<span>刷新</span>
</button>
</div>
</div>
<div class="inspection-content">
<div class="doc-item" v-for="doc in documentList" :key="doc.wordId">
<div class="icon">
@ -169,6 +163,10 @@ watch(()=>mainLayoutStore.SelectServer.id,()=>{
}
.inspection-toolbar{
border: $border;
display: flex;
padding: $padding*2;
border-radius: $radius;
justify-content: space-between;
background: $light-bg-color;
.dark-mode &{
background: $dark-bg-color;
@ -324,9 +322,9 @@ watch(()=>mainLayoutStore.SelectServer.id,()=>{
}
}
.template-layout{
display: grid;
grid-template-columns:repeat(3,minmax(0,auto));
display: flex;
gap: $gap;
flex-wrap: wrap;
.template-item:first-of-type{
svg{
filter: grayscale(100%)

View File

@ -20,7 +20,7 @@ watch(()=>userId.value,()=>{
reload.value=false;
})
})
watch(()=>mainLayoutStore.SelectServer.id,()=>{
watch(()=>mainLayoutStore.SelectServer.value,()=>{
pageReload.value=true;
setTimeout(()=> {
pageReload.value = false;
@ -30,7 +30,7 @@ const processList=ref<string[]>([])
const updateProcessList=(data:string[])=>{
processList.value=data
}
watch(() => mainLayoutStore.SelectServer.id, () => {
watch(() => mainLayoutStore.SelectServer.value, () => {
navigateTo('/serverUser/all')
})
</script>

View File

@ -144,6 +144,9 @@ const onSuccess = () => {
font-weight: 800;
line-height: 100%; /* 36px */
letter-spacing: 0.36px;
.dark-mode &{
color: #FFF;
}
}
h2{
color: #313957;
@ -152,6 +155,10 @@ const onSuccess = () => {
font-weight: 400;
line-height: 160%; /* 32px */
letter-spacing: 0.2px;
.dark-mode &{
color: #e3e3e3;
}
}
}
.login-form{
@ -162,6 +169,16 @@ const onSuccess = () => {
display: flex;
flex-direction: column;
gap: 8px;
.dark-mode &{
label{
color: #FFF;
}
input{
background: #0C1421;
color: #FFF;
}
}
label{
color: #0C1421;
font-feature-settings: 'clig' off, 'liga' off;
@ -201,6 +218,10 @@ const onSuccess = () => {
font-weight: 400;
line-height: 100%; /* 20px */
letter-spacing: 2px;
.dark-mode &{
background: #FFF;
color: #162D3A;
}
}
}
.social-sign-in{

BIN
web/public/bg2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 634 KiB