caner 11 months ago
parent
commit
21a9d862a1

+ 1 - 1
index.html

@@ -3,7 +3,7 @@
 
 <head>
   <meta charset="UTF-8" />
-  <title>控制端</title>
+  <title>termCap</title>
 </head>
 
 <body>

+ 11 - 11
src-tauri/Cargo.lock

@@ -2,17 +2,6 @@
 # It is not intended for manual editing.
 version = 4
 
-[[package]]
-name = "Control"
-version = "0.1.0"
-dependencies = [
- "serde",
- "serde_json",
- "tauri",
- "tauri-build",
- "tauri-plugin-shell",
-]
-
 [[package]]
 name = "addr2line"
 version = "0.24.2"
@@ -3430,6 +3419,17 @@ dependencies = [
  "utf-8",
 ]
 
+[[package]]
+name = "termCap"
+version = "0.1.0"
+dependencies = [
+ "serde",
+ "serde_json",
+ "tauri",
+ "tauri-build",
+ "tauri-plugin-shell",
+]
+
 [[package]]
 name = "thin-slice"
 version = "0.1.1"

+ 2 - 2
src-tauri/Cargo.toml

@@ -1,7 +1,7 @@
 [package]
-name = "Control"
+name = "termCap"
 version = "0.1.0"
-description = "车辆控制端"
+description = "ssh sftp tool"
 authors = ["Caner"]
 edition = "2021"
 

+ 0 - 1
src-tauri/capabilities/default.json

@@ -7,7 +7,6 @@
   ],
   "permissions": [
     "core:default",
-    "mqtt:default",
     "shell:allow-open",
     "core:window:default",
     "core:resources:default",

+ 68 - 22
src/App.vue

@@ -1,33 +1,79 @@
 <template>
-  <loading v-if="show" />
   <n-config-provider
     preflight-style-disabled
     inline-theme-disabled
-    :theme="theme"
-    :locale="zhCN"
-    :date-locale="dateZhCN"
-    abstract
   >
-    <n-notification-provider>
-      <n-layout>
-        <router-view />
-      </n-layout>
-    </n-notification-provider>
+    <div class="box">
+      <Head
+        v-model:show-add="showAdd"
+        v-model:list="tabs"
+        :select-tab="selectKey"
+        @on-change="onChange"
+      />
+      <div class="box-content">
+        <component :is="currentCom.sshCom" />
+      </div>
+    </div>
+    <!-- 弹窗 -->
+    <n-modal v-model:show="showAdd">
+      <Add @call-back="addBack" />
+    </n-modal>
   </n-config-provider>
 </template>
 <script setup lang='ts'>
-import loading from '@/components/loading.vue'
-import useStore from '@/store/index'
-import { computed } from 'vue'
 import {
-  zhCN, dateZhCN, darkTheme, lightTheme, useOsTheme
-} from 'naive-ui'
+  defineAsyncComponent, onMounted, ref, shallowRef
+} from 'vue'
+import Head from '@/components/head.vue'
+import Add from '@/components/add.vue'
+import { FormData } from '@/components/edit.vue'
+
+// TODO 每个tab都会存储相应组件用户操作过的数据
+const showAdd = ref(false)
+const tabs = shallowRef([
+  {
+    label: 'Home',
+    icon: 'home',
+    size: 14,
+    sshCom: defineAsyncComponent(() => import('@/components/ssh.vue')),
+    sftpCom: undefined
+  }
+] as FormData[])
+const selectKey = ref(0)
+const currentCom = shallowRef(tabs.value[selectKey.value])
+
+function addBack(params: FormData) {
+  tabs.value.push({
+    ...params,
+    icon: 'cmd',
+    size: 13,
+    sshCom: defineAsyncComponent(() => import('@/components/ssh.vue')),
+    sftpCom: undefined
+  })
+  selectKey.value = tabs.value.length - 1
+  currentCom.value = tabs.value[selectKey.value]
+  showAdd.value = false
+}
+
+function onChange(params: { id: number, type: string }) {
+  const { id, type } = params
+  if (type === 'del' && id !== 0) tabs.value.splice(id, 1)
+  selectKey.value = type === 'change' ? id : id - 1
+  currentCom.value = tabs.value[selectKey.value]
+}
 
-const osTheme = useOsTheme()
-const store = useStore()
-const show = computed(() => store.loading)
-const theme = computed(() => (osTheme.value !== 'dark' ? darkTheme : lightTheme))
 </script>
-<style>
-@import './assets/global.css';
-</style>
+<style lang="scss" scoped>
+.box {
+  width: 100vw;
+  height: 100vh;
+  border-radius: 8px;
+  overflow: hidden;
+
+  &-content {
+    height: calc(100% - 38px);
+    width: 100%;
+    border-top: solid 3px white;
+  }
+}
+</style>

BIN
src/assets/GLJ.ttf


+ 0 - 23
src/assets/global.css

@@ -1,23 +0,0 @@
-#app,
-html,
-body {
-  margin: 0;
-  padding: 0;
-  width: 100%;
-  height: 100%;
-  overflow: hidden;
-}
-
-* {
-  box-sizing: border-box;
-  user-select: none;
-  outline: none;
-}
-/* 隐藏滚动条 */
-*::-webkit-scrollbar {
-  width: 0 !important;
-  display: none;
-}
-.n-layout {
-  height: 100%;
-}

+ 0 - 1
src/assets/icons/1.svg

@@ -1 +0,0 @@
-<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1745547793649" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3166" xmlns:xlink="http://www.w3.org/1999/xlink" width="256" height="256"><path d="M938.6 720.4h-846c-12.8 0-23.1-10.4-23.1-23.1s10.4-23.1 23.1-23.1h846.1c12.8 0 23.1 10.4 23.1 23.1s-10.4 23.1-23.2 23.1zM166.4 295.2c-2.4 16.7-3.2 32.9-3.2 59.9v210.2c0 23.8 0.8 38.9 3.2 59.9H83.5c2.4-17.8 3.2-31.3 3.2-59.9V355.1c0-28.2-0.8-44.8-3.2-59.9h82.9zM310.8 383.3c-2.4 16.3-3.2 32.1-3.2 59.9v122.1c0 21.4 1.2 42.8 3.2 59.9h-82.9c2.4-19.4 3.2-34.5 3.2-59.9V443.1c0-25.8-0.8-42-3.2-59.9h82.9z m-1.2-88.1v59.5h-80.5v-59.5h80.5zM372.6 625.2c2.4-18.6 3.2-35.3 3.2-59.9V443.1c0-27.4-0.8-44-3.2-59.9h78.9v16.7c0 2.4-0.4 8.3-0.4 11.1 25-23.8 49.2-33.7 81.7-33.7 27.4 0 49.2 7.5 63.8 22.2 15.9 15.9 22.6 35.3 22.6 66.6v99.5c0 23 1.2 42.8 3.2 59.5H540c2.4-19 3.2-35.7 3.2-59.9V484c0-15.9-1.6-22.6-6.3-29.4-5.2-7.1-14.3-11.1-26.2-11.1-22.2 0-42 13.9-59.1 41.6v80.1c0 22.6 0.8 39.3 3.2 59.9h-82.2zM764 295.2c-2.4 16.3-3.2 33.3-3.2 59.9v111.4l43.6-43.2c18.2-18.2 27-28.6 34.5-40.1h105.5c-15.5 13.5-20.2 17.8-41.6 38.9l-57.5 55.5 66.2 96.8c24.2 34.9 25.8 37.3 36.9 50.8H847.3c-6-16.7-14.7-34.1-26.2-53.1L791 522.9l-29.7 28.5v15.9c0 32.5 0.4 42 3.2 57.9h-80.9c2.4-17.8 3.2-33.7 3.2-59.9V355.1c0-27-1.2-45.2-3.2-59.9H764z" fill="#0FCEA0" p-id="3167" data-spm-anchor-id="a313x.search_index.0.i0.67303a810OxbZ0" class=""></path></svg>

+ 0 - 1
src/assets/icons/10.svg

@@ -1 +0,0 @@
-<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1745395281303" class="icon" viewBox="0 0 1086 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5278" xmlns:xlink="http://www.w3.org/1999/xlink" width="135.75" height="128"><path d="M98.735 723.197c0 0.019 339.86 0.027 339.86 0.027-0.074 0-0.064-514.258-0.064-514.258 0-0.019-339.86-0.027-339.86-0.027 0.074 0 0.064 514.259 0.064 514.259zM62 208.966c0-0.006 0-0.012 0-0.019 0-20.268 16.409-36.702 36.667-36.743h339.928c20.26 0.037 36.67 16.469 36.67 36.735 0 0.009 0 0.020 0 0.029v514.229c0 0.006 0 0.012 0 0.019 0 20.268-16.409 36.702-36.667 36.743h-339.928c-20.26-0.037-36.67-16.469-36.67-36.735 0-0.009 0-0.020 0-0.029v-514.229z" p-id="5279"></path><path d="M163.020 62h36.735v146.939h-36.735v-146.939zM346.693 62h36.735v146.939h-36.735v-146.939zM613.066 300.775c1.515-154.625 42.796-192.858 201.995-192.858 162.303 0 202.042 39.738 202.042 202.042h-36.735c0-142.016-23.29-165.307-165.307-165.307s-165.307 23.29-165.307 165.307v450h-36.735v-459.184h0.046zM980.368 309.958h36.735v652.042h-36.735v-652.042zM282.409 759.958h-36.735c0 162.303 39.738 202.042 202.042 202.042s202.042-39.738 202.042-202.042h-36.735c0 142.016-23.29 165.307-165.307 165.307s-165.307-23.29-165.307-165.307z" p-id="5280"></path></svg>

+ 0 - 1
src/assets/icons/2.svg

@@ -1 +0,0 @@
-<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1745204951948" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3717" width="128" height="128" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M810.666667 191.616a106.666667 106.666667 0 0 1-128.512 104.448l-54.613334 90.325333A170.24 170.24 0 0 1 682.666667 514.474667l55.296 11.136a106.666667 106.666667 0 1 1-11.904 62.890666l-56.277334-11.306666a171.306667 171.306667 0 0 1-74.410666 83.754666l21.12 64.512h2.176a106.666667 106.666667 0 1 1-62.805334 20.394667l-21.205333-64.682667a170.410667 170.410667 0 0 1-160.085333-67.925333l-76.544 36.138667a106.666667 106.666667 0 1 1-25.941334-58.538667l74.837334-35.328a170.965333 170.965333 0 0 1 33.450666-152.192l-42.282666-48.213333a106.666667 106.666667 0 1 1 49.664-40.448l41.898666 47.786666A169.898667 169.898667 0 0 1 512 341.333333c21.546667 0 42.197333 4.010667 61.184 11.306667l53.12-87.893333A106.666667 106.666667 0 1 1 810.666667 191.658667z m-64 0a42.666667 42.666667 0 1 0-85.333334 0 42.666667 42.666667 0 0 0 85.333334 0zM298.666667 298.666667a42.666667 42.666667 0 1 0 0-85.333334 42.666667 42.666667 0 0 0 0 85.333334z m213.333333 320a106.666667 106.666667 0 1 0 0-213.333334 106.666667 106.666667 0 0 0 0 213.333334z m-277.333333 42.666666a42.666667 42.666667 0 1 0-85.333334 0 42.666667 42.666667 0 0 0 85.333334 0z m426.666666 170.752a42.666667 42.666667 0 1 0-85.333333 0 42.666667 42.666667 0 0 0 85.333333 0z m170.666667-213.418666a42.666667 42.666667 0 1 0 0-85.333334 42.666667 42.666667 0 0 0 0 85.333334z" p-id="3718" data-spm-anchor-id="a313x.search_index.0.i6.67303a81ubRvGX" class="selected" fill="#0FCEA0"></path></svg>

File diff suppressed because it is too large
+ 0 - 8
src/assets/icons/3.svg


+ 0 - 5
src/assets/icons/4.svg

@@ -1,5 +0,0 @@
-<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
-<g id="squares-filled">
-<path id="Vector" d="M7.33317 13.2917L13.2842 7.34068M12.3712 18.337L18.3332 12.375M7.87034 17.7962L17.7951 7.87141M14.6665 7.33329V5.49996C14.6665 5.01373 14.4733 4.54741 14.1295 4.2036C13.7857 3.85978 13.3194 3.66663 12.8332 3.66663H5.49984C5.01361 3.66663 4.54729 3.85978 4.20347 4.2036C3.85966 4.54741 3.6665 5.01373 3.6665 5.49996V12.8333C3.6665 13.3195 3.85966 13.7858 4.20347 14.1297C4.54729 14.4735 5.01361 14.6666 5.49984 14.6666H7.33317M9.1665 7.33329H16.4998C17.5124 7.33329 18.3332 8.1541 18.3332 9.16663V16.5C18.3332 17.5125 17.5124 18.3333 16.4998 18.3333H9.1665C8.15398 18.3333 7.33317 17.5125 7.33317 16.5V9.16663C7.33317 8.1541 8.15398 7.33329 9.1665 7.33329Z" stroke="#D9DFE6" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-</g>
-</svg>

+ 0 - 5
src/assets/icons/5.svg

@@ -1,5 +0,0 @@
-<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
-<g id="devices">
-<path id="Vector" d="M16.5 7.33329V4.58329C16.5 4.34018 16.4034 4.10702 16.2315 3.93511C16.0596 3.7632 15.8264 3.66663 15.5833 3.66663H3.66667C3.42355 3.66663 3.19039 3.7632 3.01849 3.93511C2.84658 4.10702 2.75 4.34018 2.75 4.58329V15.5833C2.75 15.8264 2.84658 16.0596 3.01849 16.2315C3.19039 16.4034 3.42355 16.5 3.66667 16.5H11.9167M14.6667 8.24996H16.5M12.8333 7.33329H18.3333C18.8396 7.33329 19.25 7.7437 19.25 8.24996V17.4166C19.25 17.9229 18.8396 18.3333 18.3333 18.3333H12.8333C12.3271 18.3333 11.9167 17.9229 11.9167 17.4166V8.24996C11.9167 7.7437 12.3271 7.33329 12.8333 7.33329Z" stroke="#D9DFE6" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-</g>
-</svg>

+ 0 - 5
src/assets/icons/6.svg

@@ -1,5 +0,0 @@
-<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
-<g id="users">
-<path id="Vector" d="M2.75 19.25V17.4167C2.75 16.4442 3.13631 15.5116 3.82394 14.8239C4.51158 14.1363 5.44421 13.75 6.41667 13.75H10.0833C11.0558 13.75 11.9884 14.1363 12.6761 14.8239C13.3637 15.5116 13.75 16.4442 13.75 17.4167V19.25M14.6667 2.86917C15.4554 3.07111 16.1545 3.52981 16.6537 4.17296C17.1529 4.8161 17.4239 5.6071 17.4239 6.42125C17.4239 7.23541 17.1529 8.02641 16.6537 8.66955C16.1545 9.31269 15.4554 9.7714 14.6667 9.97334M19.25 19.25V17.4167C19.2453 16.6074 18.9731 15.8224 18.4757 15.1841C17.9782 14.5457 17.2836 14.0898 16.5 13.8875M11.9167 6.41667C11.9167 8.44171 10.275 10.0833 8.25 10.0833C6.22496 10.0833 4.58333 8.44171 4.58333 6.41667C4.58333 4.39162 6.22496 2.75 8.25 2.75C10.275 2.75 11.9167 4.39162 11.9167 6.41667Z" stroke="#D9DFE6" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-</g>
-</svg>

+ 0 - 5
src/assets/icons/7.svg

@@ -1,5 +0,0 @@
-<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
-<g id="chart-dots-2">
-<path id="Vector" d="M2.75 2.75V19.25H19.25M19.25 2.75L13.75 4.125M12.937 6.09583L15.477 9.48292M14.6667 11.4583L10.0833 13.2917M10.0833 13.75C10.0833 14.7625 9.26252 15.5833 8.25 15.5833C7.23748 15.5833 6.41667 14.7625 6.41667 13.75C6.41667 12.7375 7.23748 11.9167 8.25 11.9167C9.26252 11.9167 10.0833 12.7375 10.0833 13.75ZM13.75 4.58333C13.75 5.59586 12.9292 6.41667 11.9167 6.41667C10.9041 6.41667 10.0833 5.59586 10.0833 4.58333C10.0833 3.57081 10.9041 2.75 11.9167 2.75C12.9292 2.75 13.75 3.57081 13.75 4.58333ZM18.3333 11C18.3333 12.0125 17.5125 12.8333 16.5 12.8333C15.4875 12.8333 14.6667 12.0125 14.6667 11C14.6667 9.98748 15.4875 9.16667 16.5 9.16667C17.5125 9.16667 18.3333 9.98748 18.3333 11Z" stroke="#D9DFE6" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-</g>
-</svg>

+ 0 - 6
src/assets/icons/8.svg

@@ -1,6 +0,0 @@
-<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
-<g id="user-circle">
-<path id="Vector" d="M6 16.9954C6.19095 16.3701 6.58167 15.8219 7.1142 15.4323C7.64673 15.0427 9.2927 14.8324 9.95628 14.8326H13.0422C13.7066 14.8323 15.3534 15.0432 15.8863 15.4337C16.4192 15.8242 16.8098 16.3735 17 17M13.8137 10.2775C13.8137 11.5354 12.7775 12.555 11.4993 12.555C10.221 12.555 9.18483 11.5354 9.18483 10.2775C9.18483 9.01968 10.221 8 11.4993 8C12.7775 8 13.8137 9.01968 13.8137 10.2775Z" stroke="#D9DFE6" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path id="Vector_2" d="M2.94365 9.65889C1.36205 8.42218 2.50806 5.89427 4.48053 6.26877C4.77681 6.32511 5.08221 6.31069 5.37187 6.2267C5.66153 6.1427 5.92726 5.9915 6.14744 5.7854C6.36762 5.57929 6.53602 5.32411 6.63894 5.04063C6.74186 4.75714 6.77639 4.45336 6.73972 4.154C6.49634 2.16128 9.09414 1.1833 10.2254 2.8429C10.3951 3.09197 10.6212 3.29749 10.8852 3.44274C11.1493 3.58799 11.4439 3.66888 11.7451 3.67884C12.0464 3.6888 12.3457 3.62755 12.6188 3.50005C12.8919 3.37256 13.131 3.18243 13.3168 2.94511C14.5535 1.36352 17.0814 2.50953 16.7069 4.48199C16.6506 4.77828 16.665 5.08368 16.749 5.37334C16.833 5.66299 16.9842 5.92873 17.1903 6.14891C17.3964 6.36909 17.6516 6.53749 17.935 6.64041C18.2185 6.74333 18.5223 6.77785 18.8217 6.74118C20.8144 6.49781 21.7924 9.09561 20.1328 10.2269C19.8837 10.3966 19.6782 10.6226 19.5329 10.8867C19.3877 11.1508 19.3068 11.4454 19.2968 11.7466C19.2869 12.0478 19.3481 12.3471 19.4756 12.6202C19.6031 12.8933 19.7932 13.1325 20.0306 13.3183C21.6122 14.555 20.4661 17.0829 18.4937 16.7084C18.1974 16.652 17.892 16.6664 17.6023 16.7504C17.3127 16.8344 17.0469 16.9856 16.8268 17.1917C16.6066 17.3978 16.4382 17.653 16.3353 17.9365C16.2323 18.22 16.1978 18.5238 16.2345 18.8231C16.4779 20.8159 13.8801 21.7938 12.7488 20.1342C12.5791 19.8852 12.353 19.6797 12.089 19.5344C11.8249 19.3891 11.5303 19.3083 11.2291 19.2983C10.9278 19.2883 10.6285 19.3496 10.3554 19.4771C10.0824 19.6046 9.8432 19.7947 9.65742 20.032C8.42072 21.6136 5.8928 20.4676 6.2673 18.4951C6.32364 18.1989 6.30923 17.8935 6.22523 17.6038C6.14124 17.3141 5.99003 17.0484 5.78393 16.8282C5.57783 16.6081 5.32265 16.4396 5.03916 16.3367C4.75567 16.2338 4.45189 16.1993 4.15254 16.236C2.15981 16.4793 1.18184 13.8815 2.84144 12.7503C3.91549 12.0172 3.96562 10.4589 2.94365 9.65889Z" stroke="#D9DFE6" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-</g>
-</svg>

+ 0 - 5
src/assets/icons/9.svg

@@ -1,5 +0,0 @@
-<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
-<g id="Icon/logout">
-<path id="Vector" d="M12.8333 7.33335V5.50002C12.8333 5.01379 12.6402 4.54747 12.2964 4.20366C11.9525 3.85984 11.4862 3.66669 11 3.66669H4.58333C4.0971 3.66669 3.63079 3.85984 3.28697 4.20366C2.94315 4.54747 2.75 5.01379 2.75 5.50002V16.5C2.75 16.9863 2.94315 17.4526 3.28697 17.7964C3.63079 18.1402 4.0971 18.3334 4.58333 18.3334H11C11.4862 18.3334 11.9525 18.1402 12.2964 17.7964C12.6402 17.4526 12.8333 16.9863 12.8333 16.5V14.6667M6.41667 11H19.25M19.25 11L16.5 8.25002M19.25 11L16.5 13.75" stroke="#B3BECD" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-</g>
-</svg>

+ 1 - 6
src/assets/icons/add.svg

@@ -1,6 +1 @@
-<svg t="1699277626262" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5500"
-  width="200" height="200">
-  <path
-    d="M939.939489 459.072557 562.339502 459.072557 562.339502 83.519182 462.055494 83.519182 462.055494 459.072557 84.455507 459.072557 84.455507 559.356564 462.055494 559.356564 462.055494 939.003164 562.339502 939.003164 562.339502 559.356564 939.939489 559.356564Z"
-    fill="#1AB99B" p-id="5501"></path>
-</svg>
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1729586836528" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="24661" width="32" height="32" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M472 472V120a8 8 0 0 1 8-8h64a8 8 0 0 1 8 8v352h352a8 8 0 0 1 8 8v64a8 8 0 0 1-8 8H552v352a8 8 0 0 1-8 8h-64a8 8 0 0 1-8-8V552H120a8 8 0 0 1-8-8v-64a8 8 0 0 1 8-8h352z" fill="#dbdbdb" p-id="24662"></path></svg>

+ 3 - 0
src/assets/icons/close.svg

@@ -0,0 +1,3 @@
+<svg width="12" height="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M6.71148 6.00003L9.89087 2.82065L9.17852 2.10829L5.99916 5.28765L2.81978 2.10828L2.10743 2.82063L5.28677 5.99998L2.10742 9.17933L2.81978 9.89169L5.99913 6.71234L9.17849 9.8917L9.89081 9.17938L6.71148 6.00003Z" />
+</svg>

+ 1 - 0
src/assets/icons/cmd.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1729564655246" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="21246" width="32" height="32" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M924 75H100C44.8 75 0 119.8 0 175v674c0 55.2 44.8 100 100 100h824c55.2 0 100-44.8 100-100V175c0-55.2-44.8-100-100-100z m0 819H100c-24.8 0-45-20.2-45-45V332h914v517c0 24.8-20.2 45-45 45zM670.5 206c0 22.3-18.1 40.4-40.4 40.4-22.3 0-40.4-18.1-40.4-40.4s18.1-40.4 40.4-40.4c22.3 0 40.4 18.1 40.4 40.4z m249.2 0c0 22.3-18.1 40.4-40.4 40.4-22.3 0-40.4-18.1-40.4-40.4s18.1-40.4 40.4-40.4c22.3 0 40.4 18.1 40.4 40.4z m-124.6 0c0 22.3-18.1 40.4-40.4 40.4-22.3 0-40.4-18.1-40.4-40.4s18.1-40.4 40.4-40.4c22.3 0 40.4 18.1 40.4 40.4zM186 783.5c11.9 10.2 31.3 10.2 43.2 0l152.5-130.8c6-5.1 8.9-11.9 8.9-18.6 0-6.7-3-13.5-8.9-18.6L229.2 484.7c-11.9-10.2-31.3-10.2-43.2 0-11.9 10.2-11.9 26.8 0 37l131 112.4-131 112.3c-11.9 10.2-11.9 26.8 0 37.1z m513.2 5H464.6c-15.2 0-27.5-12.3-27.5-27.5s12.3-27.5 27.5-27.5h234.6c15.2 0 27.5 12.3 27.5 27.5s-12.3 27.5-27.5 27.5z" p-id="21247" fill="#dbdbdb"></path></svg>

+ 1 - 0
src/assets/icons/file.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1729235962129" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="15715" width="32" height="32" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M321.792 51.2c27.648 0 54.1696 10.752 73.728 30.0032l125.8496 123.5456 350.208 0.0512c57.6 0 104.2944 45.824 104.2944 102.4 0 4.5056-0.3072 9.0624-0.9216 13.5168l-6.656 48.9472a101.7856 101.7856 0 0 0-40.448-11.0592C981.6064 362.752 1024 406.9376 1024 460.8c0 5.632-0.512 11.264-1.4336 16.8448l-69.5296 409.6c-8.3456 49.3568-51.8656 85.5552-102.8608 85.5552H133.632a103.9872 103.9872 0 0 1-100.864-76.3904A102.4 102.4 0 0 0 131.6864 972.8h-27.4432C46.6944 972.8 0 926.976 0 870.4V153.6c0-56.576 46.6944-102.4 104.2944-102.4h217.4976z m597.9136 368.64H237.824c-19.0976 0-35.84 12.8-40.448 31.0272l-104.2944 409.6a41.3184 41.3184 0 0 0 40.448 50.8928h716.6464a41.472 41.472 0 0 0 41.1648-34.2016l69.5296-409.6a41.3184 41.3184 0 0 0-41.1648-47.7184z m-597.9136-307.2H104.2944a41.3184 41.3184 0 0 0-41.728 40.96v573.2864l74.0864-290.9184A103.936 103.936 0 0 1 237.824 358.4h668.8256l6.2464-45.7728a41.3184 41.3184 0 0 0-41.3696-46.3872l-350.208-0.0512h-25.9072L351.2832 124.6208a42.0864 42.0864 0 0 0-29.4912-11.9808z" p-id="15716"></path></svg>

+ 1 - 0
src/assets/icons/form.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1729579471201" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="23461" width="32" height="32" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M847.530667 34.133333H176.469333C133.461333 34.133333 98.474667 66.389333 98.474667 106.325333v811.52c0 39.765333 34.986667 72.192 77.994666 72.192h671.061334c43.008 0 77.994667-32.256 77.994666-72.192V106.325333c0-39.936-34.986667-72.192-77.994666-72.192z m-525.653334 252.586667c13.653333-12.970667 35.328-12.629333 48.298667 1.024l80.213333 83.968 204.8-191.488c13.824-12.8 35.328-12.117333 48.298667 1.536 12.8 13.824 12.117333 35.328-1.536 48.298667L484.522667 433.493333c-9.728 9.045333-22.186667 13.653333-34.816 13.653334-13.482667 0-26.794667-5.290667-36.864-15.701334l-92.16-96.426666c-12.970667-13.653333-12.458667-35.328 1.194666-48.298667zM716.8 804.181333H307.2c-18.773333 0-34.133333-15.36-34.133333-34.133333s15.36-34.133333 34.133333-34.133333h409.6c18.773333 0 34.133333 15.36 34.133333 34.133333s-15.36 34.133333-34.133333 34.133333z m0-162.986666H307.2c-18.773333 0-34.133333-15.36-34.133333-34.133334s15.36-34.133333 34.133333-34.133333h409.6c18.773333 0 34.133333 15.36 34.133333 34.133333s-15.36 34.133333-34.133333 34.133334z" fill="#dbdbdb" p-id="23462"></path></svg>

+ 4 - 0
src/assets/icons/home.svg

@@ -0,0 +1,4 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg t="1729235761489" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6385" width="32" height="32" xmlns:xlink="http://www.w3.org/1999/xlink">
+<path d="M946.5 505L560.1 118.8l-25.9-25.9c-12.3-12.2-32.1-12.2-44.4 0L77.5 505c-12.3 12.3-18.9 28.6-18.8 46 0.4 35.2 29.7 63.3 64.9 63.3h42.5V940h691.8V614.3h43.4c17.1 0 33.2-6.7 45.3-18.8 12.1-12.1 18.7-28.2 18.7-45.3 0-17-6.7-33.1-18.8-45.2zM568 868H456V664h112v204z m217.9-325.7V868H632V640c0-22.1-17.9-40-40-40H432c-22.1 0-40 17.9-40 40v228H238.1V542.3h-96l370-369.7 23.1 23.1L882 542.3h-96.1z" ></path>
+</svg>

+ 1 - 0
src/assets/icons/host.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1729560824467" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="14652" width="32" height="32" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M921.261301 91.448732 105.561191 91.448732c-44.595369 0-80.723264 33.869901-80.723264 80.723264l0 561.111356c0 44.595369 33.869901 80.723264 80.723264 80.723264l276.039691 0c0 5.644983-1.693495 10.725469-1.693495 16.370452-1.693495 16.370452-10.725469 33.869901-10.725469 33.869901-1.693495 1.693495-5.644983 7.338479-5.644983 7.338479-7.338479 7.338479-10.725469 16.370452-10.725469 27.095921 0 21.450937 16.370452 33.869901 33.869901 33.869901l257.975744 0c18.063947 0 30.482911-16.370452 30.482911-33.869901 0-10.725469-1.693495-18.063947-10.725469-27.095921 0-1.693495-1.693495-1.693495-1.693495-1.693495-5.644983-5.080485-10.725469-10.725469-16.370452-57.578831l270.394708 0c44.595369 0 80.723264-33.869901 80.723264-80.723264L997.468578 168.785006C1001.984564 127.576626 965.85667 91.448732 921.261301 91.448732zM939.325248 591.594267c0 7.338479-7.338479 16.370452-16.370452 16.370452L105.561191 607.964719c-7.338479 0-16.370452-7.338479-16.370452-16.370452L89.190739 161.446527c0-7.338479 7.338479-16.370452 16.370452-16.370452l819.0871 0c7.338479 0 16.370452 7.338479 16.370452 16.370452l0 430.14774L939.325248 591.594267z" fill="#dbdbdb" p-id="14653"></path></svg>

+ 1 - 0
src/assets/icons/split.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1729235864820" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10185" width="32" height="32" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M880.64 0H143.36A146.5344 146.5344 0 0 0 0 143.36v737.28A146.5344 146.5344 0 0 0 143.36 1024h737.28a146.5344 146.5344 0 0 0 143.36-143.36V143.36A143.36 143.36 0 0 0 880.64 0zM76.8 880.64V153.6h399.36v793.6H148.48a68.9664 68.9664 0 0 1-71.68-66.56z m870.4 0a67.7888 67.7888 0 0 1-66.56 66.56h-327.68V153.6h399.36v727.04z" p-id="10186"></path></svg>

+ 0 - 48
src/assets/native-plugin.ts

@@ -1,48 +0,0 @@
-import {
-  create,
-  NConfigProvider,
-  NNotificationProvider,
-  NButton,
-  NLayout,
-  NLayoutContent,
-  NLayoutHeader,
-  NLayoutFooter,
-  NLayoutSider,
-  NMenu,
-  NInput,
-  NUpload,
-  NImage,
-  NForm,
-  NFormItem,
-  NBreadcrumb,
-  NBreadcrumbItem,
-  NSwitch,
-  NTabs,
-  NTabPane
-} from 'naive-ui'
-
-const naive = create({
-  components: [
-    NConfigProvider,
-    NNotificationProvider,
-    NButton,
-    NLayout,
-    NLayoutContent,
-    NLayoutHeader,
-    NLayoutSider,
-    NLayoutFooter,
-    NMenu,
-    NInput,
-    NUpload,
-    NImage,
-    NForm,
-    NFormItem,
-    NBreadcrumb,
-    NBreadcrumbItem,
-    NSwitch,
-    NTabs,
-    NTabPane
-  ]
-})
-
-export default naive

+ 31 - 0
src/assets/plugin.ts

@@ -0,0 +1,31 @@
+import {
+  create,
+  NConfigProvider,
+  NModal,
+  NScrollbar,
+  NTooltip,
+  NTabs,
+  NTabPane,
+  NButton,
+  NForm,
+  NFormItem,
+  NInput
+} from 'naive-ui'
+
+const naive = create({
+  components: [
+    NConfigProvider,
+    NModal,
+    NScrollbar,
+    NTooltip,
+    NTabs,
+    NTabPane,
+    NButton,
+    NForm,
+    NFormItem,
+    NInput
+
+  ]
+})
+
+export default naive

+ 31 - 0
src/assets/theme.scss

@@ -0,0 +1,31 @@
+* {
+  box-sizing: border-box;
+  margin: 0;
+  padding: 0;
+  user-select: none;
+}
+
+@font-face {
+  font-family: 'GLJ';
+  src: url("@/assets/GLJ.ttf");
+  font-style: normal;
+}
+
+
+@media (prefers-color-scheme: dark) {
+  :root {
+    --background-color: #1d1d1d;
+    --font-color: #ccc;
+
+  }
+
+}
+
+
+@media (prefers-color-scheme: light) {
+  :root {
+    --background-color: white;
+    --font-color: #1d1d1d;
+
+  }
+}

+ 0 - 68
src/components/Layout.vue

@@ -1,68 +0,0 @@
-<template>
-  <div class="layout">
-    <div
-      v-if="$slots.title"
-      class="layout-title"
-    >
-      <slot name="title" />
-    </div>
-    <div
-      class="layout-content"
-      :style="cssVar"
-    >
-      <slot name="content" />
-    </div>
-    <div
-      v-if="$slots.bottom"
-      class="layout-bottom"
-    >
-      <slot name="bottom" />
-    </div>
-  </div>
-</template>
-
-<script setup lang='ts'>
-import { computed, useSlots } from 'vue'
-const slots = useSlots()
-const cssVar = computed(() => ({
-  '--autoHeigh': slots.title && slots.bottom ? 'calc(100% - 95px)' : !slots.title && slots.bottom ? 'calc(100% - 50px)' : slots.title && !slots.bottom ? 'calc(100% - 45px)' : '100%'
-}))
-</script>
-<style lang="scss" scoped>
-.layout {
-    height: 100%;
-    padding: 40px 4% 0 4%;
-    min-width: 327px;
-
-    &-title {
-        font-size: 18px;
-        height: 25px;
-        margin-bottom: 20px;
-    }
-
-    &-content {
-        text-align: center;
-        padding-bottom: 20px;
-        display: flex;
-        flex-wrap: wrap;
-        align-content: flex-start;
-        height: var(--autoHeigh);
-        overflow-y: auto;
-        gap: 15px;
-    }
-
-    &-bottom {
-        width: 100%;
-        height: 50px;
-        display: flex;
-        align-items: center;
-        justify-content: space-between;
-
-        :deep(.n-button) {
-            --n-width: 30%;
-            max-width: 200px;
-            --n-border-radius: 10px;
-        }
-    }
-}
-</style>

+ 218 - 0
src/components/add.vue

@@ -0,0 +1,218 @@
+<template>
+  <div class="add">
+    <div class="add-top">
+      <div>
+        <Icon
+          name="home"
+          :size="20"
+        />
+        <span>Home</span>
+      </div>
+
+      <n-button
+        size="tiny"
+        text
+        @click="showEdit = true; editData = undefined;"
+      >
+        <Icon name="add" />
+      </n-button>
+    </div>
+    <div class="add-content">
+      <div class="add-content-title">
+        History_
+      </div>
+      <n-scrollbar style="height: calc(100% - 30px);">
+        <div class="add-content-items">
+          <template
+            v-for="(item, index) in history"
+            :key="index"
+          >
+            <div class="add-content-items-item">
+              <Icon
+                name="cmd"
+                :size="30"
+              />
+              <div>
+                <p>{{ item.label }}</p>
+                <p>ssh {{ item.userName }}@{{ item.host }}:{{ item.port }}</p>
+              </div>
+              <div
+                class="add-content-items-item-edit"
+                @click.stop="edit(item)"
+              >
+                <Icon
+                  name="close"
+                  color="white"
+                  :size="15"
+                  @click.stop="history.splice(index, 1)"
+                />
+              </div>
+            </div>
+          </template>
+        </div>
+      </n-scrollbar>
+    </div>
+    <div
+      v-if="showEdit"
+      class="add-edit"
+    >
+      <Edit
+        :data="editData"
+        @call-back="editBack"
+      />
+    </div>
+  </div>
+</template>
+
+<script setup lang='ts'>
+import { reactive, ref } from 'vue'
+import Edit, { FormData } from '@/components/edit.vue'
+
+const emit = defineEmits<{(evt: 'callBack', value: FormData): void}>()
+const history = ref([
+  {
+    label: 'orange', userName: 'root', host: 'caner.top', port: '49657', password: ''
+  }
+] as FormData[])
+const click = reactive({
+  num: 0,
+  delay: 200,
+  timer: null as any
+})
+const showEdit = ref(false)
+const editData = ref(undefined as FormData | undefined)
+
+function edit(item: FormData) {
+  click.num++
+  if (click.num === 1) {
+    click.timer = setTimeout(() => {
+      editData.value = { ...item }
+      showEdit.value = true
+      click.num = 0
+    }, click.delay)
+  } else {
+    clearTimeout(click.timer)
+    emit('callBack', { ...item })
+    click.num = 0
+  }
+}
+
+function editBack(params: null | FormData) {
+  if (params) {
+  // 判断是编辑还是新增
+    const index = history.value.findIndex((el) => el.host === params.host && el.port === params.port && el.userName === params.userName)
+    if (index !== -1) {
+      console.log('修改', params)
+      history.value[index] = { ...params }
+    } else {
+      console.log('新增')
+      history.value.push({ ...params })
+    }
+  }
+  showEdit.value = false
+}
+</script>
+<style lang="scss" scoped>
+.add {
+  width: 90vw;
+  height: 60vh;
+  border-radius: 10px;
+  font-size: 14px;
+  overflow: hidden;
+  background: var(--background-color);
+
+  &-top {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 10px 15px;
+    font-size: 15px;
+
+    &>div:first-child {
+      display: flex;
+      align-items: center;
+
+      &>span {
+        margin-left: 5px;
+        font-weight: bold;
+      }
+    }
+  }
+
+  &-content {
+    height: calc(100% - 42px);
+    padding: 0 10px 10px 10px;
+
+    &-title {
+      font-weight: bold;
+      margin-bottom: 10px;
+      text-indent: 10px;
+      font-size: 14px;
+    }
+
+    &-items {
+      padding: 0 20px;
+      display: flex;
+      flex-wrap: wrap;
+      align-items: flex-start;
+
+      &-item {
+        display: flex;
+        align-items: center;
+        border: solid 1px #ccc;
+        border-radius: 10px;
+        padding: 10px;
+        cursor: pointer;
+        position: relative;
+        overflow: hidden;
+        margin-bottom: 10px;
+        width: 240px;
+
+        &>div:nth-child(2) {
+          margin-left: 10px;
+          font-size: 13px;
+
+          &>p:last-child {
+            color: #ccc;
+          }
+        }
+
+        &-edit {
+          position: absolute;
+          width: 100%;
+          height: 100%;
+          background: rgba($color: #000000, $alpha: 0.2);
+          display: none;
+          align-items: flex-start;
+          justify-content: flex-end;
+          left: 0;
+          top: 0;
+          margin: 0;
+          padding: 5px;
+        }
+
+        &:hover &-edit {
+          display: flex;
+        }
+        &:not(:nth-child(3n)){
+          margin-right: 10px;
+        }
+      }
+    }
+  }
+
+  &-edit {
+    position: fixed;
+    width: 100%;
+    height: 100%;
+    background: rgba($color: #000000, $alpha: 0.5);
+    top: 0;
+    left: 0;
+    z-index: 9999;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+
+  }
+}
+</style>

+ 243 - 0
src/components/edit.vue

@@ -0,0 +1,243 @@
+<template>
+  <div class="edit">
+    <div class="edit-top">
+      <span>Host Setting</span>
+      <Icon
+        name="close"
+        :size="20"
+        @click="callBack(false)"
+      />
+    </div>
+    <div class="edit-content">
+      <Icon
+        name="form"
+        :size="240"
+      />
+
+      <div>
+        <n-form
+          ref="formRef"
+          :model="formData"
+          :rules="rules"
+        >
+          <div>
+            <n-form-item
+              label="Address"
+              path="host"
+              inline
+            >
+              <n-input
+                v-model:value="formData.host"
+                placeholder="请输入"
+                clearable
+              />
+            </n-form-item>
+            <n-form-item
+              label="Port"
+              path="port"
+              inline
+            >
+              <n-input
+                v-model:value="formData.port"
+                placeholder="请输入"
+                clearable
+              />
+            </n-form-item>
+          </div>
+
+          <n-form-item
+            label="Label"
+            path="label"
+          >
+            <n-input
+              v-model:value="formData.label"
+              placeholder="请输入"
+              clearable
+            />
+          </n-form-item>
+          <n-form-item
+            label="UserName"
+            path="userName"
+          >
+            <n-input
+              v-model:value="formData.userName"
+              placeholder="请输入"
+              clearable
+            />
+          </n-form-item>
+          <n-form-item
+            label="Passord"
+            path="password"
+          >
+            <n-input
+              v-model:value="formData.password"
+              placeholder="请输入"
+              clearable
+            />
+          </n-form-item>
+        </n-form>
+        <div>
+          <n-button
+            ghost
+            type="tertiary"
+            @click="callBack(false)"
+          >
+            Cancel
+          </n-button>
+          <n-button
+            type="info"
+            ghost
+            @click="callBack(true)"
+          >
+            Save
+          </n-button>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang='ts'>
+import { FormItemRule } from 'naive-ui'
+import { ref } from 'vue'
+
+export interface FormData {
+  host: string,
+  port: string,
+  label: string,
+  userName: string,
+  password: string,
+  icon?: string,
+  size?: number,
+  sshCom?: any,
+  sftpCom?: any
+}
+const props = withDefaults(defineProps<{
+  data?: FormData
+}>(), {
+  data: () => ({
+    host: '',
+    port: '',
+    label: '',
+    userName: '',
+    password: ''
+  })
+})
+const emit = defineEmits<{(evt: 'callBack', value: FormData | null): void }>()
+const formRef = ref()
+class FormOldData {
+  host: string
+
+  port: string
+
+  label: string
+
+  userName: string
+
+  password: string
+
+  constructor() {
+    this.host = ''
+    this.port = ''
+    this.label = ''
+    this.userName = ''
+    this.password = ''
+  }
+}
+const formData = ref(props.data || new FormOldData())
+const rules = ref({
+  host: {
+    required: true, message: '请输入', validator: (_: FormItemRule, value: string) => !!value
+  },
+  port: {
+    required: true, message: '请输入', validator: (_: FormItemRule, value: string) => !!(/^[0-9]*$/.test(value)) && !!value
+  },
+  label: {
+    required: true, message: '请输入', validator: (_: FormItemRule, value: string) => !!value
+  },
+  userName: {
+    required: true, message: '请输入', validator: (_: FormItemRule, value: string) => !!value
+  },
+  password: {
+    required: true, message: '请输入', validator: (_: FormItemRule, value: string) => !!value
+  }
+})
+
+async function callBack(type: boolean) {
+  const data = type ? { ...formData.value } : null
+  if (type) await formRef.value.validate()
+  emit('callBack', data)
+  formData.value = new FormOldData()
+}
+</script>
+
+<style lang="scss" scoped>
+.edit {
+  width: 90vw;
+  height: 60vh;
+  background: var(--background-color);
+  border-radius: 10px;
+  font-size: 14px;
+  overflow: hidden;
+
+  &-top {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    font-weight: bold;
+    padding: 10px 15px;
+
+    &>svg {
+      cursor: pointer;
+    }
+  }
+
+  &-content {
+    padding: 10px 15px 0 0;
+    display: flex;
+    align-items: center;
+
+    &>svg {
+      margin-right: 10px;
+    }
+
+    &>div {
+      width: 100%;
+
+      :deep(.n-form) {
+        .n-input {
+          --n-border-focus: rgba(0, 0, 0, 0);
+          --n-border-hover: rgba(0, 0, 0, 0);
+          --n-box-shadow-focus: 0 0 0 2px rgba(0, 0, 0, .2);
+        }
+
+        &>div:first-child {
+          display: flex;
+
+          &>div:first-child {
+            width: 78%;
+            margin-right: 2%;
+          }
+        }
+      }
+
+      &>div:last-child {
+        display: flex;
+        align-items: center;
+        justify-content: flex-end;
+
+        &>button:last-child {
+          margin-left: 15px;
+        }
+
+        :deep(.n-button) {
+          --n-text-color-hover: none;
+          --n-border-focus: 1px solid #0000;
+          --n-border-hover: 1px solid #0000;
+        }
+      }
+    }
+
+  }
+}
+</style>

+ 190 - 0
src/components/head.vue

@@ -0,0 +1,190 @@
+<template>
+  <div
+    class="head"
+    data-tauri-drag-region
+  >
+    <div
+      ref="scrollItem"
+      class="head-items"
+    >
+      <template
+        v-for="(item, index) in list"
+        :key="index"
+      >
+        <div
+          class="head-items-item"
+          :class="{ active: index === selectTab }"
+          @click.stop="e => onChange(index, 'change')"
+        >
+          <div>
+            <div>
+              <Icon
+                :name="item.icon"
+                :size="item.size"
+              />
+              {{ item.label }}
+            </div>
+            <Icon
+              :style="`opacity:${index === 0 ? 0 : 1} ;`"
+              name="close"
+              :size="12"
+              @click.stop="onChange(index, 'del')"
+            />
+          </div>
+        </div>
+      </template>
+    </div>
+    <div
+      class="head-add"
+      @click="emit('update:showAdd', true)"
+    >
+      <Icon
+        name="add"
+        :size="15"
+      />
+    </div>
+  </div>
+</template>
+<script setup lang='ts'>
+import { ref, watch } from 'vue'
+import { FormData } from '@/components/edit.vue'
+
+const props = withDefaults(defineProps<{
+  showAdd?: boolean,
+  list: FormData[],
+  selectTab: number
+}>(), {
+  showAdd: false,
+  selectTab: 0,
+  list: () => []
+})
+const scrollItem = ref()
+
+const emit = defineEmits<{(evt: 'update:showAdd', value: boolean): void,
+  (evt: 'update:list', value: FormData[]): void,
+  (evt: 'onChange', value: { id: number, type: string }): void,
+}>()
+
+function onChange(id: number, type: string) {
+  if (id === 0 && type === 'del') return
+  emit('onChange', { id, type })
+}
+
+watch(() => props.selectTab, (v) => {
+  setTimeout(() => {
+    // 滚动
+    const dom = scrollItem.value.children[v]
+    const scrollNum = dom.offsetLeft - scrollItem.value.offsetWidth / 2 + dom.offsetWidth / 2
+    scrollItem.value.scrollLeft = scrollNum
+  }, 0)
+})
+
+</script>
+<style lang="scss" scoped>
+.head {
+  width: 100%;
+  height: 38px;
+  user-select: none;
+  text-shadow: 0px -2px -4px var(--shadow-color);
+  display: flex;
+  align-items: flex-end;
+  background: #ccc;
+  overflow: hidden;
+  color: var(--font-color);
+
+  &-items {
+    max-width: 85%;
+    margin-left: 70px;
+    display: flex;
+    align-items: flex-end;
+    overflow-x: auto;
+    scroll-behavior: smooth;
+    padding: 0 12px;
+    cursor: pointer;
+
+    /* 隐藏滚动条 */
+    &::-webkit-scrollbar {
+      width: 0 !important;
+      display: none;
+    }
+
+    &-item {
+      height: 34px;
+      font-size: 13px;
+      color: var(--font-color);
+      padding: 6px 8px 0 8px;
+      position: relative;
+
+      &>div {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+
+        &>svg {
+          margin-top: 2px;
+          margin-left: 3px;
+          cursor: pointer;
+        }
+
+        &>div {
+          display: flex;
+          align-items: center;
+
+          &>svg {
+            margin-right: 5px;
+          }
+        }
+      }
+
+      &::after {
+        content: '';
+        height: 15px;
+        width: 2px;
+        border-radius: 3px;
+        background: #ddd;
+        position: absolute;
+        right: -2px;
+        top: 50%;
+        transform: translate(0, -50%);
+      }
+    }
+
+    .active {
+      background: white;
+      border-radius: 8px 8px 0 0;
+      position: relative;
+      box-shadow: 8px 8px 0 0 #ffffff, -8px 8px 0 0 #ffffff;
+
+      &::before {
+        content: '';
+        position: absolute;
+        left: -8px;
+        bottom: 0;
+        width: 8px;
+        height: 33px;
+        background: #ccc;
+        border-radius: 0 0 8px 0;
+      }
+
+      &::after {
+        content: '';
+        position: absolute;
+        right: -8px;
+        bottom: 0;
+        width: 8px;
+        height: 33px;
+        background: #ccc;
+        border-radius: 0 0 0 8px;
+      }
+
+    }
+  }
+
+  &-add {
+    height: 70%;
+    margin-left: 5px;
+    -webkit-app-region: no-drag;
+    cursor: pointer;
+  }
+}
+</style>

+ 1 - 2
src/components/icon.vue

@@ -21,7 +21,6 @@ const newSize = computed(() => `${props.size ?? 16}`)
     <use
       :href="symbolId"
       :fill="newColor"
-      :fill-rule="undefined"
     />
   </svg>
-</template>
+</template>

+ 19 - 33
src/components/loading.vue

@@ -1,38 +1,24 @@
 <template>
-  <div class="bg">
-    <div class="loader" />
-  </div>
+  <div class="loader" />
 </template>
-<style lang="scss" scoped>
-    .bg{
-        width: 100vw;
-        height: 100vh;
-        position: fixed;
-        top: 0;
-        left: 0;
-        display: flex;
-        align-items: center;
-        justify-content: center;
-        background: rgba(0, 0, 0, 0.5);
-    }
-    .loader {
+<style scoped>
+.loader {
+  border: 5px solid #f3f3f3;
+  border-radius: 50%;
+  border-top: 5px solid #3498db;
+  width: 30px;
+  height: 30px;
+  -webkit-animation: spin 2s linear infinite;
+  animation: spin 2s linear infinite;
+}
 
-        border: 5px solid #f3f3f3;
-        border-radius: 50%;
-        border-top: 5px solid #3498db;
-        width: 30px;
-        height: 30px;
-        -webkit-animation: spin 2s linear infinite;
-        animation: spin 2s linear infinite;
-    }
+@-webkit-keyframes spin {
+  0% { -webkit-transform: rotate(0deg); }
+  100% { -webkit-transform: rotate(360deg); }
+}
 
-    @-webkit-keyframes spin {
-        0% { -webkit-transform: rotate(0deg); }
-        100% { -webkit-transform: rotate(360deg); }
-    }
-
-    @keyframes spin {
-        0% { transform: rotate(0deg); }
-        100% { transform: rotate(360deg); }
-    }
+@keyframes spin {
+  0% { transform: rotate(0deg); }
+  100% { transform: rotate(360deg); }
+}
 </style>

+ 101 - 0
src/components/ssh.vue

@@ -0,0 +1,101 @@
+<template>
+  <div class="ssh">
+    <!-- <div
+      v-for="(item, index) in data"
+      :key="index"
+      v-html="item"
+    /> -->
+    <!-- <textarea
+      v-model="data"
+      style="width: 100%;height: 100%;"
+    /> -->
+
+    <div class="ssh-input">
+      <span
+        ref="inputTitle"
+        class="ssh-input-title"
+      >root@orangepi:~#</span>
+      <span
+        class="ssh-input-content"
+        contenteditable="plaintext-only"
+        @input="onInput"
+      />
+    </div>
+  </div>
+</template>
+
+<script setup lang='ts'>
+import { onMounted, ref } from 'vue'
+
+const inputTitle = ref()
+const data = ref('')
+const conn = ref()
+
+function newClient(option: { host: string, port: number, username: string, password: string }) {
+  const conn = window.$ssh
+  conn.on('ready', () => {
+    console.log('Client :: ready')
+    conn.shell((err: string, stream: any) => {
+      if (err) throw err
+
+      stream.on('close', () => {
+        console.log('Stream :: close')
+        conn.end()
+      })
+
+      stream.on('data', (db: string) => {
+        console.log(`OUTPUT: ${db}`)
+        const a = JSON.stringify(db)
+        data.value += db
+      })
+
+      stream.end('ls -l\nexit\n')
+    })
+  })
+  conn.connect({ ...option })
+  return conn
+}
+
+function onInput(e:any) {
+  console.log(6666, e.target.textContent)
+}
+
+// onMounted(() => {
+//   conn.value = newClient({
+//     host: 'caner.top', port: 49657, username: 'root', password: 'dongdong0707'
+//   })
+// })
+
+</script>
+<style lang="scss" scoped>
+.ssh {
+  width: 100%;
+  height: 100%;
+  background: black;
+
+  &-input {
+    color: white;
+    font-size: 16px;
+    position: relative;
+
+    &-title {
+      position: absolute;
+      left: 0;
+      top: 0;
+    }
+
+    &-content {
+      display: inline-block;
+      min-width: 300px;
+      max-width: 100%;
+      overflow: hidden;
+      text-indent: 200px;
+      word-wrap: break-word;
+      &:focus-visible {
+        outline: none;
+      }
+    }
+
+  }
+}
+</style>

+ 6 - 39
src/main.ts

@@ -1,47 +1,14 @@
 import { createApp } from 'vue'
-import App from './App.vue'
 import { createPinia } from 'pinia'
-import naive from '@/assets/native-plugin'
-import { createRouter, RouteRecordRaw, createWebHashHistory } from 'vue-router'
+import piniaPersist from 'pinia-plugin-persist'
+import App from './App.vue'
 import Icon from '@/components/icon.vue'
+import naive from '@/assets/plugin'
 import 'virtual:svg-icons-register'
-import piniaPersist from 'pinia-plugin-persist'
+import '@/assets/theme.scss'
 
 const store = createPinia()
 store.use(piniaPersist)
 
-// 动态路由
-const routes = Object.values(import.meta.glob('./pages/*/route.ts', { eager: true, import: 'default' })) as unknown as RouteRecordRaw[]
-routes.push({ path: '/:path(.*)', redirect: '/' })
-
-const app = createApp(App)
-const router = createRouter({
-  history: createWebHashHistory(),
-  routes
-})
-app.component('Icon', Icon)
-// 路由守卫
-// router.beforeEach((to, from, next) => {
-//   // do something
-//   next()
-// })
-app.use(store)
-  .use(naive)
-  .use(router)
-
-router.isReady().then(() => {
-  const vm = app.mount('#app')
-  app.config.errorHandler = (err: any) => {
-    console.log(err);
-  }
-  window.addEventListener('unhandledrejection', (evt) => {
-    console.error(evt)
-    evt.preventDefault()
-    app.config.errorHandler?.(evt, vm, '')
-  })
-  window.addEventListener('error', (evt) => {
-    console.error(evt)
-    evt.preventDefault()
-    app.config.errorHandler?.(evt.error, vm, '')
-  })
-})
+createApp(App).use(store).use(naive).component('Icon', Icon)
+  .mount('#app')

+ 0 - 10
src/pages/home/index.vue

@@ -1,10 +0,0 @@
-<template>
-    <div class="home">123</div>
-</template>
-  
-<script setup lang='ts'>
-  
-</script>
-<style lang="scss" scoped>
-  
-</style>

+ 0 - 10
src/pages/home/route.ts

@@ -1,10 +0,0 @@
-import { RouteRecordRaw } from 'vue-router'
-import login from './index.vue'
-
-export default {
-  path: '/',
-  meta: {
-    authorize: true
-  },
-  component: login
-} as RouteRecordRaw

+ 0 - 159
src/pages/index.vue

@@ -1,159 +0,0 @@
-<template>
-  <n-layout has-sider>
-    <n-layout-sider
-      v-model:collapsed="isTrigger"
-      bordered
-      show-trigger
-      collapse-mode="width"
-      width="216"
-      :native-scrollbar="false"
-    >
-      <n-menu
-        v-model:value="selectedKey"
-        accordion
-        :collapsed-icon-size="24"
-        :options="menuOptions"
-      />
-    </n-layout-sider>
-    <n-layout>
-      <router-view />
-    </n-layout>
-  </n-layout>
-</template>
-<script setup lang="ts">
-import {
-  h, onMounted, onUnmounted, ref
-} from 'vue'
-import Icon from '@/components/icon.vue'
-import { RouterLink, useRoute } from 'vue-router'
-
-const isTrigger = ref(false)
-const menuOptions = [
-  {
-    label: () => h('img', {
-      src: new URL('@/assets/icons/1.svg', import.meta.url).href,
-      width: '100%',
-      height: '100%'
-    }),
-    icon: () => h(Icon, {
-      name: '2',
-      size: 24
-    }),
-    disabled: true,
-    key: ''
-  },
-  {
-    icon: () => h('img', {
-      src: new URL('@/assets/icons/3.svg', import.meta.url).href,
-      width: (isTrigger.value ? '0px' : '60px'),
-      height: (isTrigger.value ? '0px' : '60px'),
-      style: {
-        transition: '0.2s'
-      }
-    }),
-    label: () => h('div', {
-      style: {
-        margin: '0 0 0 15px'
-      }
-    }, [
-      h('p', {
-        style: {
-          margin: 0
-        }
-      }, { default: () => '欢迎回家' }),
-      h('p', {
-        style: {
-          margin: 0
-        }
-      }, { default: () => '捷克' })
-    ]),
-    disabled: true,
-    key: ''
-  },
-  {
-    label: () => h(RouterLink, { to: { path: '/room' } }, { default: () => '房间' }),
-    key: 'room',
-    icon: () => h(Icon, { name: '4', size: 24 })
-  },
-  {
-    label: () => h(RouterLink, { to: { path: '/device' } }, { default: () => '设备' }),
-    key: 'device',
-    icon: () => h(Icon, { name: '5', size: 24 })
-  },
-  {
-    label: () => h(RouterLink, { to: { path: '/person' } }, { default: () => '成员' }),
-    key: 'person',
-    icon: () => h(Icon, { name: '6', size: 24 })
-  },
-  {
-    label: () => h(RouterLink, { to: { path: '/static' } }, { default: () => '统计' }),
-    key: 'static',
-    icon: () => h(Icon, { name: '7', size: 24 })
-  },
-  {
-    label: () => h(RouterLink, { to: { path: '/seting' } }, { default: () => '设置' }),
-    key: 'seting',
-    icon: () => h(Icon, { name: '8', size: 24 })
-  },
-  {
-    label: () => h(RouterLink, { to: { path: '/' } }, { default: () => '退出' }),
-    key: 'logout',
-    icon: () => h(Icon, { name: '9', size: 24 })
-  }
-]
-const route = useRoute()
-const selectedKey = ref((route.name as string || '').split('/')[0])
-
-function changeTrigger() {
-  isTrigger.value = document.documentElement.clientWidth < 550
-}
-window.addEventListener('resize', changeTrigger, false)
-onMounted(() => changeTrigger())
-onUnmounted(() => window.removeEventListener('resize', changeTrigger, false))
-</script>
-<style lang="scss" scoped>
-:deep(.n-menu) {
-  --n-item-height: 55px;
-  height: 100%;
-  padding: 0;
-
-  .n-menu-item-content {
-    padding-right: 32px;
-  }
-
-  &>div {
-    &:first-child {
-      margin: 0;
-      --n-item-height: 80px;
-
-      &>div {
-        height: 80px;
-        position: relative;
-        opacity: 1;
-
-        &::after {
-          content: '';
-          width: 80%;
-          height: 2px;
-          background: var(--n-border-color);
-          position: absolute;
-          left: 50%;
-          transform: translate(-50%, 0);
-          bottom: 0;
-        }
-      }
-    }
-
-    &:nth-child(2) {
-      --n-item-height: 80px;
-      margin: 20px 0 60px 0;
-
-      &>div {
-        opacity: 1;
-      }
-    }
-
-  }
-
-}
-</style>

+ 0 - 121
src/services/net.service.ts

@@ -1,121 +0,0 @@
-import { injectable, Service } from './service'
-
-/** api接口返回值类型 */
-declare type NetResult = {
-  success: boolean,
-  data: Any
-}
-
-/**
- * API网络请求服务
- */
-@injectable
-export default class NetService extends Service {
-  protected PROXY = '/api'
-
-  /**
-   * post方法请求接口
-   * @param url 接口地址
-   * @param params 接口参数
-   */
-  post(url: string, params: Any = {}, timeout = 6000): Promise<any> {
-    return this.fetch(url, { method: 'POST', body: JSON.stringify(params) }, timeout)
-  }
-
-  /**
-   * get请求api接口
-   * @param url 接口地址
-   */
-  get(url: string, timeout = 6000): Promise<NetResult> {
-    return this.fetch(url, { method: 'GET' }, timeout)
-  }
-
-  /**
-   * del请求api接口
-   * @param url 接口地址
-   */
-  del(url: string, timeout = 6000): Promise<NetResult> {
-    return this.fetch(url, { method: 'DELETE' }, timeout)
-  }
-
-  /**
-   * post方法请求接口
-   * @param url 接口地址
-   * @param params 接口参数
-   */
-  put(url: string, params: Any = {}, timeout = 6000): Promise<NetResult> {
-    return this.fetch(url, { method: 'PUT', body: JSON.stringify(params) }, timeout)
-  }
-
-  /**
-   * 下载文件
-   * @param url url|blob
-   * @param fileName
-   */
-  downloadFile(url: string | Blob, fileName: string) {
-    const download = (blob: Blob, fileName: string) => {
-      const a = document.createElement('a')
-      document.body.appendChild(a)
-      a.style.display = 'none'
-      // 使用获取到的blob对象创建的url
-      const url = window.URL.createObjectURL(blob)
-      a.href = url
-      // 指定下载的文件名
-      a.download = fileName
-      a.click()
-      document.body.removeChild(a)
-      // 移除blob对象的url
-      window.URL.revokeObjectURL(url)
-    }
-    return new Promise<void>((resolve, reject) => {
-      if (url instanceof Blob) {
-        download(url, fileName)
-        resolve()
-      } else {
-        fetch(url).then((res) => res.blob()).then((blob) => download(blob, fileName)).finally(() => resolve())
-      }
-    })
-  }
-
-  /** token 续期 */
-  async refresh(token: string) {
-    this.post('', { token }).then((res) => {
-      console.log('刷新token', res)
-    })
-  }
-
-  /**
-   * fetch
-   * @param url
-   * @param opt
-   * @param timeout 0 默认不超时
-   * @returns
-   */
-  private fetch(url: string, opt: RequestInit, timeout = 0): Promise<NetResult> {
-    return new Promise((resolve) => {
-      const controller = new AbortController()
-      const { signal } = controller
-      if (timeout) {
-        setTimeout(() => {
-          controller.abort()
-        }, timeout)
-      }
-      fetch(this.PROXY + url, {
-        signal,
-        ...opt,
-        headers: opt.headers || { 'Content-Type': 'application/json' }
-      }).then((res) => {
-        if (res.status === 500) return { success: false, data: '请求错误' }
-        return res.json()
-      }).then((res) => {
-        const obj = { data: res.data, success: res.data instanceof Object }
-        if (!obj.success && obj.data.includes('need authrize')) {
-          throw 401
-        }
-        resolve(obj)
-      }).catch((er) => {
-        throw er
-      })
-    })
-  }
-}

+ 0 - 64
src/services/service.ts

@@ -1,64 +0,0 @@
-export class ServiceError extends Error {
-  code?: number
-
-  origin?: string
-
-  constructor(message: string, origin?: string, code?: number) {
-    super(message)
-    this.code = code
-    this.origin = origin
-    this.stack = `${this.message}\n${this.origin}\n${this.code || ''}`
-  }
-
-  toString() {
-    return this.message
-  }
-}
-
-export class Service {
-  throw(message: string, origin?: string, code?: number) {
-    throw new ServiceError(message, origin, code)
-  }
-}
-
-export function injectable<T extends { new(..._args: any[]): {} }> (Ctor: T) {
-  let instance!: any
-  return new Proxy(Ctor, {
-    construct(t, args) {
-      if (!instance) {
-        instance = new Ctor(args)
-        // console.log('instance ' + Ctor.name)
-      }
-      return instance
-    }
-  })
-}
-const runnerMap: { [key: string]: ((_res: any) => void)[] | undefined } = {}
-/**
-   * 互斥注解
-   * 用于保证某个方法同一时间只有单次调用
-   */
-export function mutex(target: any, property: string) {
-  const oriFn = target[property]
-  const funcKey = `${target.constructor.name}-${property}`
-  Object.defineProperty(target, property, {
-    async value(...args: any[]) {
-      const key = funcKey + JSON.stringify(args)
-      if (runnerMap[key]) {
-        return await new Promise((res) => {
-          runnerMap[key]?.push((result: any) => res(result))
-        })
-      }
-      runnerMap[key] = []
-
-      setTimeout(() => {
-        runnerMap[key] = undefined
-      }, 4000)
-      const res = await Reflect.apply(oriFn, this, args || [])
-      runnerMap[key]?.forEach((fn) => fn(res))
-      runnerMap[key] = undefined
-      return res
-    }
-  })
-  return target[property]
-}

+ 4 - 4
src/store/index.ts

@@ -3,12 +3,12 @@ import { defineStore } from 'pinia'
 // id必填,且需要唯一
 const useStore = defineStore('index', {
   state: () => ({
-    history:[]
+    loading: false,
   }),
   actions: {
-    setHistory(data:any) {
-      this.history = data
-    },
+    setLoading(data: boolean) {
+      this.loading = data
+    }
   },
   persist: {
     enabled: true // true 表示开启持久化保存

+ 1 - 1
vite.config.ts

@@ -30,7 +30,7 @@ export default () => defineConfig({
   ],
   server: {
     host: '0.0.0.0',
-    port: 6555,
+    port: 5555,
     open: false,
     strictPort: true,
     watch: {

Some files were not shown because too many files changed in this diff