Compare commits

2 Commits
Author SHA1 Message Date
539943419 4f477c99d1 完成文件上传功能 2024-08-11 16:02:23 +08:00
539943419 e840e4eb5a rename&fix 2024-08-11 11:03:36 +08:00
15 changed files with 636 additions and 180 deletions

No files matched your search

+4
View File
@@ -5,20 +5,24 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"dev:host": "vite --host 0.0.0.0",
"build": "vue-tsc -b && vite build", "build": "vue-tsc -b && vite build",
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@vicons/ionicons5": "^0.12.0", "@vicons/ionicons5": "^0.12.0",
"axios": "^1.7.3",
"naive-ui": "^2.39.0", "naive-ui": "^2.39.0",
"sass": "^1.77.8", "sass": "^1.77.8",
"vue": "^3.4.35" "vue": "^3.4.35"
}, },
"devDependencies": { "devDependencies": {
"@types/lodash": "^4.17.7",
"@types/node": "^22.1.0", "@types/node": "^22.1.0",
"@vitejs/plugin-vue": "^5.1.2", "@vitejs/plugin-vue": "^5.1.2",
"typescript": "^5.5.3", "typescript": "^5.5.3",
"vite": "^5.4.0", "vite": "^5.4.0",
"vite-plugin-singlefile": "^2.0.2",
"vue-tsc": "^2.0.29" "vue-tsc": "^2.0.29"
} }
} }
+101
View File
@@ -11,6 +11,9 @@ importers:
'@vicons/ionicons5': '@vicons/ionicons5':
specifier: ^0.12.0 specifier: ^0.12.0
version: 0.12.0 version: 0.12.0
axios:
specifier: ^1.7.3
version: 1.7.3
naive-ui: naive-ui:
specifier: ^2.39.0 specifier: ^2.39.0
version: 2.39.0(vue@3.4.37(typescript@5.5.4)) version: 2.39.0(vue@3.4.37(typescript@5.5.4))
@@ -21,6 +24,9 @@ importers:
specifier: ^3.4.35 specifier: ^3.4.35
version: 3.4.37(typescript@5.5.4) version: 3.4.37(typescript@5.5.4)
devDependencies: devDependencies:
'@types/lodash':
specifier: ^4.17.7
version: 4.17.7
'@types/node': '@types/node':
specifier: ^22.1.0 specifier: ^22.1.0
version: 22.1.0 version: 22.1.0
@@ -33,6 +39,9 @@ importers:
vite: vite:
specifier: ^5.4.0 specifier: ^5.4.0
version: 5.4.0(@types/node@22.1.0)(sass@1.77.8) version: 5.4.0(@types/node@22.1.0)(sass@1.77.8)
vite-plugin-singlefile:
specifier: ^2.0.2
version: 2.0.2(rollup@4.20.0)(vite@5.4.0(@types/node@22.1.0)(sass@1.77.8))
vue-tsc: vue-tsc:
specifier: ^2.0.29 specifier: ^2.0.29
version: 2.0.29(typescript@5.5.4) version: 2.0.29(typescript@5.5.4)
@@ -387,6 +396,12 @@ packages:
async-validator@4.2.5: async-validator@4.2.5:
resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==} resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==}
asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
axios@1.7.3:
resolution: {integrity: sha512-Ar7ND9pU99eJ9GpoGQKhKf58GpUOgnzuaB7ueNQ5BMi0p+LZ5oaEnfF999fAArcTIBwXTCHAmGcHOZJaWPq9Nw==}
balanced-match@1.0.2: balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
@@ -405,6 +420,10 @@ packages:
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
engines: {node: '>= 8.10.0'} engines: {node: '>= 8.10.0'}
combined-stream@1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
computeds@0.0.1: computeds@0.0.1:
resolution: {integrity: sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==} resolution: {integrity: sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==}
@@ -429,6 +448,10 @@ packages:
de-indent@1.0.2: de-indent@1.0.2:
resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==}
delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
entities@5.0.0: entities@5.0.0:
resolution: {integrity: sha512-BeJFvFRJddxobhvEdm5GqHzRV/X+ACeuw0/BuuxsCh1EUZcAIz8+kYmBp/LrQuloy6K1f3a0M7+IhmZ7QnkISA==} resolution: {integrity: sha512-BeJFvFRJddxobhvEdm5GqHzRV/X+ACeuw0/BuuxsCh1EUZcAIz8+kYmBp/LrQuloy6K1f3a0M7+IhmZ7QnkISA==}
engines: {node: '>=0.12'} engines: {node: '>=0.12'}
@@ -448,6 +471,19 @@ packages:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'} engines: {node: '>=8'}
follow-redirects@1.15.6:
resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==}
engines: {node: '>=4.0'}
peerDependencies:
debug: '*'
peerDependenciesMeta:
debug:
optional: true
form-data@4.0.0:
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
engines: {node: '>= 6'}
fsevents@2.3.3: fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@@ -493,6 +529,18 @@ packages:
magic-string@0.30.11: magic-string@0.30.11:
resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==} resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==}
micromatch@4.0.7:
resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==}
engines: {node: '>=8.6'}
mime-db@1.52.0:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
mime-types@2.1.35:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
minimatch@9.0.5: minimatch@9.0.5:
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
engines: {node: '>=16 || 14 >=14.17'} engines: {node: '>=16 || 14 >=14.17'}
@@ -528,6 +576,9 @@ packages:
resolution: {integrity: sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==} resolution: {integrity: sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==}
engines: {node: ^10 || ^12 || >=14} engines: {node: ^10 || ^12 || >=14}
proxy-from-env@1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
readdirp@3.6.0: readdirp@3.6.0:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'} engines: {node: '>=8.10.0'}
@@ -581,6 +632,13 @@ packages:
peerDependencies: peerDependencies:
vue: ^3.0.11 vue: ^3.0.11
vite-plugin-singlefile@2.0.2:
resolution: {integrity: sha512-Z2ou6HcvED5CF0hM+vcFSaFa+klyS8RyyLxW0PbMRLnMbvzTI6ueWyxdYNFhpuXZgz/aj6+E/dHFTdEcw6gb9w==}
engines: {node: '>18.0.0'}
peerDependencies:
rollup: ^4.18.0
vite: ^5.3.1
vite@5.4.0: vite@5.4.0:
resolution: {integrity: sha512-5xokfMX0PIiwCMCMb9ZJcMyh5wbBun0zUzKib+L65vAZ8GY9ePZMXxFrHbr/Kyll2+LSCY7xtERPpxkBDKngwg==} resolution: {integrity: sha512-5xokfMX0PIiwCMCMb9ZJcMyh5wbBun0zUzKib+L65vAZ8GY9ePZMXxFrHbr/Kyll2+LSCY7xtERPpxkBDKngwg==}
engines: {node: ^18.0.0 || >=20.0.0} engines: {node: ^18.0.0 || >=20.0.0}
@@ -902,6 +960,16 @@ snapshots:
async-validator@4.2.5: {} async-validator@4.2.5: {}
asynckit@0.4.0: {}
axios@1.7.3:
dependencies:
follow-redirects: 1.15.6
form-data: 4.0.0
proxy-from-env: 1.1.0
transitivePeerDependencies:
- debug
balanced-match@1.0.2: {} balanced-match@1.0.2: {}
binary-extensions@2.3.0: {} binary-extensions@2.3.0: {}
@@ -926,6 +994,10 @@ snapshots:
optionalDependencies: optionalDependencies:
fsevents: 2.3.3 fsevents: 2.3.3
combined-stream@1.0.8:
dependencies:
delayed-stream: 1.0.0
computeds@0.0.1: {} computeds@0.0.1: {}
css-render@0.15.14: css-render@0.15.14:
@@ -947,6 +1019,8 @@ snapshots:
de-indent@1.0.2: {} de-indent@1.0.2: {}
delayed-stream@1.0.0: {}
entities@5.0.0: {} entities@5.0.0: {}
esbuild@0.21.5: esbuild@0.21.5:
@@ -983,6 +1057,14 @@ snapshots:
dependencies: dependencies:
to-regex-range: 5.0.1 to-regex-range: 5.0.1
follow-redirects@1.15.6: {}
form-data@4.0.0:
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
mime-types: 2.1.35
fsevents@2.3.3: fsevents@2.3.3:
optional: true optional: true
@@ -1016,6 +1098,17 @@ snapshots:
dependencies: dependencies:
'@jridgewell/sourcemap-codec': 1.5.0 '@jridgewell/sourcemap-codec': 1.5.0
micromatch@4.0.7:
dependencies:
braces: 3.0.3
picomatch: 2.3.1
mime-db@1.52.0: {}
mime-types@2.1.35:
dependencies:
mime-db: 1.52.0
minimatch@9.0.5: minimatch@9.0.5:
dependencies: dependencies:
brace-expansion: 2.0.1 brace-expansion: 2.0.1
@@ -1061,6 +1154,8 @@ snapshots:
picocolors: 1.0.1 picocolors: 1.0.1
source-map-js: 1.2.0 source-map-js: 1.2.0
proxy-from-env@1.1.0: {}
readdirp@3.6.0: readdirp@3.6.0:
dependencies: dependencies:
picomatch: 2.3.1 picomatch: 2.3.1
@@ -1118,6 +1213,12 @@ snapshots:
evtd: 0.2.4 evtd: 0.2.4
vue: 3.4.37(typescript@5.5.4) vue: 3.4.37(typescript@5.5.4)
vite-plugin-singlefile@2.0.2(rollup@4.20.0)(vite@5.4.0(@types/node@22.1.0)(sass@1.77.8)):
dependencies:
micromatch: 4.0.7
rollup: 4.20.0
vite: 5.4.0(@types/node@22.1.0)(sass@1.77.8)
vite@5.4.0(@types/node@22.1.0)(sass@1.77.8): vite@5.4.0(@types/node@22.1.0)(sass@1.77.8):
dependencies: dependencies:
esbuild: 0.21.5 esbuild: 0.21.5
+189 -135
View File
@@ -1,10 +1,21 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, reactive } from 'vue'; import { h, onMounted, reactive, ref, shallowRef } from 'vue';
import { NIcon, NScrollbar } from 'naive-ui'; import { NIcon, NScrollbar, NDropdown, NQrCode } from 'naive-ui';
import { EyeOffOutline, EyeOutline, CloseOutline, CopyOutline, EllipsisVertical } from '@vicons/ionicons5' import { EyeOffOutline, EyeOutline, CloseOutline, CopyOutline, EllipsisVertical, QrCodeOutline } from '@vicons/ionicons5'
import miaoDirectory from './views/miaoDirectory.vue'; import miaoDirectory from './views/miaoDirectory.vue';
import VirtualPage from './class/virtualPage'; import VirtualPage from './class/VirtualPage';
import VirtualDirectory from './class/virtualDirectory'; import VirtualDirectory from './class/VirtualDirectory';
import MiaoMask from './components/miaoMask.vue';
import config from './config';
const {baseUrl} = config
const showModal = ref<boolean>(false)
const modalData = shallowRef<{
component: any
props: any
}>()
const views = reactive<VirtualPage[]>([]) const views = reactive<VirtualPage[]>([])
@@ -31,6 +42,37 @@ const deleteView = (index: number) => {
} }
} }
function renderIcon(icon: any) {
return () => {
return h(NIcon, null, {
default: () => h(icon)
})
}
}
const openMenuOption = [
{
label: '分享',
key: 'share',
icon: renderIcon(QrCodeOutline)
}
]
const handleMenuSelect = (key: string) => {
console.log(key)
if(key === 'share') {
showModal.value = true
modalData.value = {
component: NQrCode,
props: {
value: baseUrl,
size: 500,
errorCorrectionLevel: 'H'
}
}
}
}
onMounted(() => { onMounted(() => {
createView(rootDirectory) createView(rootDirectory)
}) })
@@ -39,168 +81,180 @@ onMounted(() => {
</script> </script>
<template> <template>
<div class="view-controler"> <div class="view">
<n-scrollbar x-scrollable> <div class="view-controler">
<div class="tags-container"> <n-scrollbar x-scrollable>
<div class="tag" v-for="(view, index) of views" :key="view.uid"> <div class="tags-container">
<div class="tag-title">{{ view.title }}</div> <div class="tag" v-for="(view, index) of views" :key="view.uid">
<div class="tag-point" :style="{ backgroundColor: view.color } "></div> <div class="tag-title">{{ view.title }}</div>
<div class="tag-control"> <div class="tag-point" :style="{ backgroundColor: view.color } "></div>
<n-icon class="tag-control-icon icon" @click="view.switchShow()"> <div class="tag-control">
<EyeOutline v-show="view.visible" class="icon-inner" /> <n-icon class="tag-control-icon icon" @click="view.switchShow()">
<EyeOffOutline v-show="!view.visible" class="icon-inner" /> <EyeOutline v-show="view.visible" class="icon-inner" />
</n-icon> <EyeOffOutline v-show="!view.visible" class="icon-inner" />
<n-icon class="tag-control-icon icon" @click="createView(view.currentDirectory, index + 1)"> </n-icon>
<CopyOutline /> <n-icon class="tag-control-icon icon" @click="createView(view.currentDirectory, index + 1)">
</n-icon> <CopyOutline />
<n-icon class="tag-control-icon icon" @click="deleteView(index)"> </n-icon>
<CloseOutline class="icon-inner" /> <n-icon class="tag-control-icon icon" @click="deleteView(index)">
</n-icon> <CloseOutline class="icon-inner" />
</n-icon>
</div>
</div> </div>
</div> </div>
</n-scrollbar>
<div class="view-controler-menu">
<div class="view-controler-menu-item">
<n-dropdown trigger="click" :options="openMenuOption" @select="handleMenuSelect">
<n-icon class="icon">
<EllipsisVertical class="icon-inner" />
</n-icon>
</n-dropdown>
</div>
</div> </div>
</n-scrollbar> </div>
<div class="view-controler-menu"> <div class="view-container">
<div class="view-controler-menu-item"> <div class="view-container-item" v-for="(view, index) of views" v-show="view.visible" :key="view.uid">
<n-icon class="icon" @click=""> <miao-directory v-model:current-directory="view.currentDirectory" :color="view.color" :index="index" @exit="deleteView(index)"></miao-directory>
<EllipsisVertical class="icon-inner" />
</n-icon>
</div> </div>
</div> </div>
</div> </div>
<div class="view-container"> <MiaoMask v-model:show="showModal" @click="showModal = false" >
<div class="view" v-for="(view, index) of views" v-show="view.visible" :key="view.uid"> <component :is="modalData?.component" v-bind="modalData?.props"></component>
<miao-directory v-model:current-directory="view.currentDirectory" :color="view.color" :index="index" @exit="deleteView(index)"></miao-directory> </MiaoMask>
</div>
</div>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
$controler-height: 25px; $controler-height: 25px;
.view{
.view-controler { position: relative;
display: flex; height: 100%;
height: $controler-height;
width: 100%; width: 100%;
background-color: #dadada;
// overflow: hidden;
.tags-container { .view-controler {
display: flex; display: flex;
margin-left: 5px; height: $controler-height;
flex: 1; width: 100%;
// overflow-x: scroll; background-color: #dadada;
.tag { // overflow: hidden;
position: relative;
width: 170px; .tags-container {
margin: 0 5px;
// flex: 1;
display: flex; display: flex;
border-radius: 5px 5px 0 0; margin-left: 5px;
align-items: center; flex: 1;
// overflow-x: scroll;
.tag-title { .tag {
margin-left: 4px; position: relative;
// width: 70%; width: 170px;
user-select: none; margin: 0 5px;
} // flex: 1;
.tag-point {
height: 5px;
aspect-ratio: 1;
border-radius: 66px;
margin-left: 5px;
}
.tag-control {
position: absolute;
right: 10px;
display: flex; display: flex;
border-radius: 5px 5px 0 0;
align-items: center; align-items: center;
.tag-control-icon:not(:last-child) { .tag-title {
margin-right: 5px; margin-left: 4px;
// width: 70%;
user-select: none;
}
.tag-point {
height: 5px;
aspect-ratio: 1;
border-radius: 66px;
margin-left: 5px;
}
.tag-control {
position: absolute;
right: 10px;
display: flex;
align-items: center;
.tag-control-icon:not(:last-child) {
margin-right: 5px;
}
}
&:not(:last-child)::after {
content: '';
height: calc($controler-height*0.6);
width: 1px;
border-right: 1px solid black;
position: absolute;
right: 0px;
} }
} }
}
&:not(:last-child)::after { .view-controler-menu {
content: ''; position: relative;
height: calc($controler-height*0.6); display: flex;
width: 1px; align-items: center;
border-right: 1px solid black; padding: 0 5px;
position: absolute; .view-controler-menu-item {
right: 0px; height: 100%;
aspect-ratio: 1;
display: flex;
justify-content: center;
align-items: center;
}
&::before {
content: '';
height: calc($controler-height*0.6);
width: 1px;
border-right: 1px solid black;
position: absolute;
left: 2px;
} }
} }
}
.view-controler-menu { .icon {
position: relative; height: calc($controler-height * 0.7);
display: flex; width: calc($controler-height * 0.7);
align-items: center; border-radius: 5px;
padding: 0 5px;
.view-controler-menu-item {
height: 100%;
aspect-ratio: 1;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} cursor: pointer;
&::before { background-color: rgba(128, 128, 128, 0);
content: ''; transition: background-color 0.3s;
height: calc($controler-height*0.6);
width: 1px; &:hover {
border-right: 1px solid black; background-color: rgba(128, 128, 128, 0.4);
position: absolute; }
left: 2px;
} }
} }
.icon { .view-container {
height: calc($controler-height * 0.7); position: relative;
width: calc($controler-height * 0.7);
border-radius: 5px;
display: flex; display: flex;
justify-content: center; // flex-direction: column;
align-items: center; height: calc(100% - $controler-height);
cursor: pointer; overflow-y: overlay;
background-color: rgba(128, 128, 128, 0); overflow-x: overlay;
transition: background-color 0.3s; .view-container-item {
flex: 1;
&:hover { height: 100%;
background-color: rgba(128, 128, 128, 0.4); }
&::-webkit-scrollbar {
position: absolute;
width: 4px;
}
// 滚动条轨道
&::-webkit-scrollbar-track {
// background: rgb(239, 239, 239);
border-radius: 2px;
}
// 小滑块
&::-webkit-scrollbar-thumb {
background: #e0e0e0;
// border-radius: 5px;
}
&::-webkit-scrollbar-thumb:hover {
background: #999999;
} }
}
}
.view-container {
position: relative;
display: flex;
// flex-direction: column;
height: calc(100% - $controler-height);
overflow-y: overlay;
overflow-x: overlay;
.view {
flex: 1;
height: 100%;
} }
&::-webkit-scrollbar {
position: absolute;
width: 4px;
}
// 滚动条轨道
&::-webkit-scrollbar-track {
// background: rgb(239, 239, 239);
border-radius: 2px;
}
// 小滑块
&::-webkit-scrollbar-thumb {
background: #e0e0e0;
// border-radius: 5px;
}
&::-webkit-scrollbar-thumb:hover {
background: #999999;
}
} }
</style> </style>
+165
View File
@@ -0,0 +1,165 @@
import generateId from "@/hooks/generateId";
import useMiaoFetchApi from "@/hooks/useMiaoFetchApi";
import VirtualDirectory from "./VirtualDirectory";
const miaoFetchApi = useMiaoFetchApi();
export class UploadQueueItem {
constructor(
file: File,
targetDirectory: VirtualDirectory,
config?: {
onSuccess?: () => void;
onFailed?: () => void;
}
) {
this.file = file;
this.targetDirectory = targetDirectory;
this.uid = generateId();
this.status = "waiting";
this.process = 0;
this.onSuccess = config && config.onSuccess;
this.onFailed = config && config.onFailed;
}
uid: number;
file: File;
targetDirectory: VirtualDirectory;
status: "waiting" | "uploading" | "success" | "failed";
process: number;
failedReason: unknown | undefined;
onSuccess?: () => void;
onFailed?: () => void;
_abort: (() => void | undefined) | undefined;
_status: Promise<any> | undefined;
/**
* 开始上传文件
*/
startUpload() {
if (this.status === "uploading") {
return;
}
const { abort, status } = miaoFetchApi.upload(this.file, this.targetDirectory, {
onProcess: percent => {
this.process = percent;
}
});
this._abort = abort;
this._status = status;
this.status = "uploading";
status
.then(() => {
this.status = "success";
this.onSuccess && this.onSuccess();
this._abort = undefined;
this._status = undefined;
})
.catch(e => {
this.status = "failed";
this.failedReason = e;
this.onFailed && this.onFailed();
});
}
/**
* 终止上传文件
*/
abortUpload() {
this._abort && this._abort();
this.status = "failed";
this.failedReason = "abort";
}
}
export default class UploadQueue {
constructor() {
this.uid = generateId();
this.queue = [];
this.mode = "serial";
}
uid: number;
queue: UploadQueueItem[];
current: UploadQueueItem | undefined;
// 未来再去实现,目前就是串行的
// 好像也不用去实现
mode: "serial" | "parallel";
get status() {
for (let item of this.queue) {
if (item.status === "uploading") {
return "busy";
}
}
return "free";
}
/**
* 获取下一个需要被上传的item
*/
get nextUplpadItem() {
for (let item of this.queue) {
if (item.status === "waiting") {
return item;
}
}
}
/**
* 开始串行的上传循环
*/
loop() {
if (this.status === "busy") {
return;
}
if (this.nextUplpadItem) {
this.nextUplpadItem.startUpload();
}
}
/**
* 向上传文件队列输送一批新的文件
* @param {any} files 需要上传的文件
* @param {any} targetDirectory 目标文件夹
* @param {any} option 每个文件上传完成以后都会触发一遍的回调函数
* @returns {any}
*/
push(
files: File[],
targetDirectory: VirtualDirectory,
option?: {
/**
* 每个文件上传完成以后都会触发一遍的回调函数
*/
onFinish?: () => void;
/**
* 是否立即开始上传,若否则按队列顺序一个一个上传
*/
immediately?: boolean;
}
) {
const onFinish = option?.onFinish;
const immediately = option?.immediately ?? false;
const uploadQueue = files.map(file => {
return new UploadQueueItem(file, targetDirectory, {
onSuccess: () => {
this.loop();
onFinish && onFinish();
},
onFailed: () => {
this.loop();
}
});
});
this.queue.push(...uploadQueue);
if (!immediately) {
this.loop();
} else {
uploadQueue.forEach(item => {
item.startUpload();
});
}
return uploadQueue;
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
import VirtualDirectory from "./virtualDirectory"; import VirtualDirectory from "./VirtualDirectory";
import generateId from "@/hooks/generateId"; import generateId from "@/hooks/generateId";
const getRandomWebSafeColor = (() => { const getRandomWebSafeColor = (() => {
@@ -22,7 +22,7 @@
<script setup lang="ts"> <script setup lang="ts">
import VirtualDirectory, { VirtualFile } from '@/class/virtualDirectory'; import VirtualDirectory, { VirtualFile } from '@/class/VirtualDirectory';
import { DocumentText, FileTray, Help, Image, LogoChrome, LogoCss3, LogoJavascript, LogoVue, PlayCircle, EllipsisVertical, Disc } from '@vicons/ionicons5'; import { DocumentText, FileTray, Help, Image, LogoChrome, LogoCss3, LogoJavascript, LogoVue, PlayCircle, EllipsisVertical, Disc } from '@vicons/ionicons5';
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import dateFormatter from '@/hooks/dateFormatter'; import dateFormatter from '@/hooks/dateFormatter';
@@ -50,9 +50,13 @@ const time = computed(()=>{
}) })
const dropDownOptions = computed(()=> { const dropDownOptions = computed(()=> {
const options = [] const options = [{
label: '删除',
key: 'delete'
}]
if(itemType.value === 'file'){ if(itemType.value === 'file'){
options.push(...[{ options.push(...[
{
label: '下载', label: '下载',
key: 'download', key: 'download',
} }
@@ -135,6 +139,7 @@ const handleDragStart = () => {
.item-main-icon { .item-main-icon {
margin-left: 10px; margin-left: 10px;
height: 80%; height: 80%;
color: #00403e;
} }
.item-main-info { .item-main-info {
margin-left: 10px; margin-left: 10px;
+3 -1
View File
@@ -1,7 +1,9 @@
<template> <template>
<transition name="transition"> <transition name="transition">
<div class="mask-body" ref="maskBodyRef" v-show="showMask"> <div class="mask-body" ref="maskBodyRef" v-show="showMask">
<slot> {{}} </slot> <div @click="e=>e.stopPropagation()">
<slot> {{}} </slot>
</div>
</div> </div>
</transition> </transition>
</template> </template>
+4 -2
View File
@@ -4,6 +4,8 @@ export default {
baseUrl, baseUrl,
uploadSizeLimit: 1024 * 1024 * 100, uploadSizeLimit: 1024 * 1024 * 100,
api: { api: {
get: '/get' get: '/get',
} upload: '/upload'
},
enableUpload: true,
}; };
+1 -1
View File
@@ -6,7 +6,7 @@ export default function useDataBus() {
dataStorage[key] = value; dataStorage[key] = value;
return value; return value;
}, },
get(key: string | number) { pop(key: string | number) {
const value = dataStorage[key]; const value = dataStorage[key];
dataStorage[key] = undefined; dataStorage[key] = undefined;
return value; return value;
-22
View File
@@ -1,22 +0,0 @@
type miaoFetchConfig = {
retry: number;
};
export default function useMiaoFetch(config: miaoFetchConfig) {
const { retry } = config;
return async (input: RequestInfo | URL, init?: RequestInit) => {
let retryTimes = 0;
let err: any;
while (retryTimes <= retry) {
try {
const response = await fetch(input, init);
return response;
} catch (e) {
err = e;
retryTimes += 1;
}
}
throw err;
};
}
+95
View File
@@ -0,0 +1,95 @@
import VirtualDirectory from "@/class/VirtualDirectory";
import { getOption, getResponse, miaoFetchConfig, uploadOption } from "@/types/fetch";
import Config from "@/config";
import axios, { AxiosRequestConfig } from "axios";
export const useMiaoFetch = (option?: miaoFetchConfig) => {
const retry = option?.retry ?? 0;
const controller = new AbortController();
let flag = true;
return {
miaoFetch: async (config: AxiosRequestConfig<any>) => {
let counter = 0;
let response: any;
let errList = [];
while (flag && counter <= retry) {
try {
response = await axios({
...config,
signal: controller.signal
});
return response;
} catch (_e) {
errList.push(_e);
counter += 1;
}
}
throw errList;
},
/**
* 强制终止miaoFetch
*/
abortMiaoFetch: () => {
flag = false;
controller.abort();
}
};
};
const miaoFetchApi = {
/**
* 获取对应文件夹下的数据
*/
async get(vDirectory: VirtualDirectory, option?: getOption): Promise<getResponse> {
const { baseUrl, api } = Config;
const url = `${baseUrl}${api.get}${vDirectory.path}`;
const retry = option?.retry ?? 0;
const { miaoFetch } = useMiaoFetch({
retry
});
const response = await miaoFetch({
url
});
return response.data;
},
/**
* 上传单个文件到对应文件夹
*/
upload(file: File, targetVDir: VirtualDirectory, option?: uploadOption) {
const { baseUrl, api } = Config;
const url = `${baseUrl}${api.upload}${targetVDir.path}`;
const retry = option?.retry ?? 0;
const { miaoFetch, abortMiaoFetch } = useMiaoFetch({
retry
});
const sizeTotal = file.size
const opType = option?.type ?? 'write'
const data = new FormData()
data.append('file', file)
data.append('oprateType', opType)
return {
abort: () => abortMiaoFetch(),
status: new Promise(async (resolve)=>{
const response = await miaoFetch({
url,
method: 'post',
data: data,
onUploadProgress(progressEvent) {
option && option.onProcess && option.onProcess(progressEvent.loaded / sizeTotal)
},
})
resolve(response.data)
})
};
}
};
export default function useMiaoFetchApi() {
return miaoFetchApi;
}
+5
View File
@@ -0,0 +1,5 @@
import UploadQueue from "@/class/UploadQueue";
const uploadQueue = new UploadQueue()
export default () => uploadQueue
+24
View File
@@ -0,0 +1,24 @@
import { directory, file } from "./type";
export type miaoFetchConfig = {
retry?: number
}
export type getOption = {
} & miaoFetchConfig
export type getResponse = (file | directory)[]
export type uploadOption = {
/**
* 上传进度回调函数
* @param {any} percent 进度百分比,float类型
*/
onProcess?: (percent: number) => void
/**
* 上传文件执行的操作
* write即为覆盖写
* append即为增量写
*/
type?: 'write' | 'append'
} & miaoFetchConfig
+28 -14
View File
@@ -1,6 +1,6 @@
<template> <template>
<div class="miao-directory-item-container" ref="rootDomRef"> <div class="miao-directory-item-container" ref="rootDomRef" >
<div class="container-top"> <div class="container-top" :style="{ backgroundColor: props.index % 2 === 0 ? '#f8f8f8' : 'rgb(240 240 240)' }">
<div class="container-top-colorbar" :style="{ backgroundColor: props.color }"></div> <div class="container-top-colorbar" :style="{ backgroundColor: props.color }"></div>
<div class="container-top-breadcrumb"> <div class="container-top-breadcrumb">
<!-- <n-scrollbar x-scrollable> --> <!-- <n-scrollbar x-scrollable> -->
@@ -51,22 +51,21 @@
<script setup lang="ts"> <script setup lang="ts">
import { NBreadcrumbItem, NBreadcrumb, NIcon, NScrollbar } from 'naive-ui' import { NBreadcrumbItem, NBreadcrumb, NIcon, NScrollbar } from 'naive-ui'
import type { file, directory } from '@/types/type.ts' import type { file, directory } from '@/types/type.ts'
import VirtualDirectory from '@/class/virtualDirectory'; import VirtualDirectory from '@/class/VirtualDirectory';
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import Config from '@/config' import Config from '@/config'
import dropHandler, { dropHandlerHooks } from '@/hooks/dropHandler'; import dropHandler, { dropHandlerHooks } from '@/hooks/dropHandler';
import miaoMask from '@/components/miaoMask.vue'; import miaoMask from '@/components/miaoMask.vue';
import miaoDirectoryItem from '@/components/miaoDirectory/miaoDirectoryItem.vue'; import miaoDirectoryItem from '@/components/miaoDirectory/miaoDirectoryItem.vue';
import useMiaoFetch from '@/hooks/useMiaoFetch'; import useMiaoFetchApi from '@/hooks/useMiaoFetchApi';
import useDataBus from '@/hooks/useDataBus'; import useDataBus from '@/hooks/useDataBus';
import { CloudOutline, ChevronBackOutline, ReloadOutline, CloseOutline } from '@vicons/ionicons5' import { CloudOutline, ChevronBackOutline, ReloadOutline, CloseOutline } from '@vicons/ionicons5'
import useUploadQueue from '@/hooks/useUploadQueue';
const { baseUrl, api } = Config const { baseUrl, api } = Config
const dataBus = useDataBus() const dataBus = useDataBus()
const miaoFetch = useMiaoFetch({ const miaoFetchApi = useMiaoFetchApi()
retry: 5,
})
const props = defineProps<{ const props = defineProps<{
color: string color: string
@@ -86,15 +85,17 @@ const showModel = ref(0)
// 用于监听拖拽文件事件 // 用于监听拖拽文件事件
const rootDomRef = ref<HTMLDivElement>() const rootDomRef = ref<HTMLDivElement>()
const uploadQueue = useUploadQueue()
// 设置显示的dir // 设置显示的dir
const setCurrentDirectory = async (virtualDirectory: VirtualDirectory) => { const setCurrentDirectory = async (virtualDirectory: VirtualDirectory) => {
// 若没有缓存数据则同步刷新,有缓存数据则异步刷新 // 若没有缓存数据则同步刷新,有缓存数据则异步刷新
if (virtualDirectory.files === undefined || virtualDirectory.directorys === undefined) { if (virtualDirectory.files === undefined || virtualDirectory.directorys === undefined) {
const data: (file | directory)[] = await (await miaoFetch(`${baseUrl}${api.get}${virtualDirectory.path}`)).json() const data: (file | directory)[] = await miaoFetchApi.get(virtualDirectory)
virtualDirectory.updateContent(data) virtualDirectory.updateContent(data)
} else { } else {
; (async () => { ; (async () => {
const data: (file | directory)[] = await (await miaoFetch(`${baseUrl}${api.get}${virtualDirectory.path}`)).json() const data: (file | directory)[] = await miaoFetchApi.get(virtualDirectory)
virtualDirectory.updateContent(data) virtualDirectory.updateContent(data)
currentDirectory.value = undefined currentDirectory.value = undefined
currentDirectory.value = virtualDirectory currentDirectory.value = virtualDirectory
@@ -106,17 +107,21 @@ const setCurrentDirectory = async (virtualDirectory: VirtualDirectory) => {
// emit("onCurrentDirectoryChange", currentDirectory.value) // emit("onCurrentDirectoryChange", currentDirectory.value)
} }
const reload = () => {
currentDirectory.value && setCurrentDirectory(currentDirectory.value)
}
const openUrl = (href: string, config?: { const openUrl = (href: string, config?: {
download?: string download?: string
target?: string target?: string
}) => { }) => {
let {target, download} = config ?? {} let {target, download} = config ?? {}
console.log({target, download}) console.log({target, download, href})
const a = document.createElement('a'); const a = document.createElement('a');
a.setAttribute('href', href); a.setAttribute('href', href);
a.setAttribute('target', target ?? '_blank') a.setAttribute('target', target ?? '_blank')
a.setAttribute('download', download ?? ''); download && a.setAttribute('download', download);
a.click(); a.click();
// var a = document.createElement("a"); // var a = document.createElement("a");
// a.setAttribute('href', href) // a.setAttribute('href', href)
@@ -133,7 +138,7 @@ const handleBack = () => {
} }
const handleReload = () => { const handleReload = () => {
currentDirectory.value && setCurrentDirectory(currentDirectory.value) reload()
} }
onMounted(async () => { onMounted(async () => {
@@ -141,8 +146,17 @@ onMounted(async () => {
// 拖动事件处理 // 拖动事件处理
const dropFileOption:dropHandlerHooks = { const dropFileOption:dropHandlerHooks = {
onDropFiles(files: File[]) { onDropFiles(files: File[]) {
const dragData = dataBus.get('dragData') const dragData = dataBus.pop('dragData')
console.log({dragData, files}) console.log({dragData, files})
if(files && files.length > 0){
currentDirectory.value && uploadQueue.push(files, currentDirectory.value, {
onFinish() {
reload()
},
})
const _f = files[0]
currentDirectory.value && miaoFetchApi.upload(_f, currentDirectory.value)
}
}, },
onEnter() { onEnter() {
showModel.value += 1 showModel.value += 1
@@ -238,7 +252,7 @@ $bottom-bar-height: 60px;
content: ''; content: '';
height: 60%; height: 60%;
position: absolute; position: absolute;
right: -1px; right: -2px;
width: 2px; width: 2px;
background-color: #828282; background-color: #828282;
} }
+8 -1
View File
@@ -1,10 +1,11 @@
import { defineConfig } from "vite"; import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue"; import vue from "@vitejs/plugin-vue";
import { resolve } from "path"; import { resolve } from "path";
import { viteSingleFile } from "vite-plugin-singlefile"
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [vue()], plugins: [vue(), viteSingleFile()],
resolve: { resolve: {
alias: { alias: {
"@": resolve(__dirname, "src") "@": resolve(__dirname, "src")
@@ -16,6 +17,12 @@ export default defineConfig({
target: "http://127.0.0.1:17705", target: "http://127.0.0.1:17705",
changeOrigin: true, changeOrigin: true,
rewrite: path => path.replace(/^\/get/, "/get") rewrite: path => path.replace(/^\/get/, "/get")
},
"/upload": {
target: "http://127.0.0.1:17705",
changeOrigin: true,
rewrite: path => path.replace(/^\/upload/, "/upload")
} }
} }
}, },