feat: 引入 view-ui-plus 框架, 完成初步布局和文件树的渲染
This commit is contained in:
parent
c09c8b5e9c
commit
540f9ba28f
@ -49,3 +49,10 @@ This template should help get you started developing with Tauri + Vue 3 in Vite.
|
||||
1. 新建 SelectFolder.vue 用于打开本地目录
|
||||
2. 在 App.vue 使用组件 SelectFolder 和 MarkdownEditor.vue
|
||||
3. 使用 plugin-fs 插件读写本地文件内容
|
||||
|
||||
## 引入 view-ui-plus 组件库,优化 UI 布局
|
||||
|
||||
1. `npm install view-ui-plus --save`
|
||||
2. 使用 <Split> 分割左右面板
|
||||
3. 使用 <Tree> 树形控件渲染文件树
|
||||
4. 联动 SelectFolder、FolderTree 和 MarkdownEditor 组件
|
||||
|
||||
124
package-lock.json
generated
124
package-lock.json
generated
@ -13,6 +13,7 @@
|
||||
"@tauri-apps/plugin-fs": "^2.2.1",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"scriptjs": "^2.5.9",
|
||||
"view-ui-plus": "^1.3.19",
|
||||
"vue": "^3.5.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -1153,12 +1154,54 @@
|
||||
"integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/async-validator": {
|
||||
"version": "3.5.2",
|
||||
"resolved": "https://registry.npmjs.org/async-validator/-/async-validator-3.5.2.tgz",
|
||||
"integrity": "sha512-8eLCg00W9pIRZSB781UUX/H6Oskmm8xloZfr09lz5bikRpBVDlJ3hRVuxxP1SxcwsEYfJ4IU8Q19Y8/893r3rQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/batch-processor": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/batch-processor/-/batch-processor-1.0.0.tgz",
|
||||
"integrity": "sha512-xoLQD8gmmR32MeuBHgH0Tzd5PuSZx71ZsbhVxOCRbgktZEPe4SQy7s9Z50uPp0F/f7iw2XmkHN2xkgbMfckMDA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/countup.js": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/countup.js/-/countup.js-1.9.3.tgz",
|
||||
"integrity": "sha512-UHf2P/mFKaESqdPq+UdBJm/1y8lYdlcDd0nTZHNC8cxWoJwZr1Eldm1PpWui446vDl5Pd8PtRYkr3q6K4+Qa5A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dayjs": {
|
||||
"version": "1.11.13",
|
||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
|
||||
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/deepmerge": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz",
|
||||
"integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/element-resize-detector": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/element-resize-detector/-/element-resize-detector-1.2.4.tgz",
|
||||
"integrity": "sha512-Fl5Ftk6WwXE0wqCgNoseKWndjzZlDCwuPTcoVZfCP9R3EHQF8qUtr3YUPNETegRBOKqQKPW3n4kiIWngGi8tKg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"batch-processor": "1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||
@ -1233,6 +1276,24 @@
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/js-calendar": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/js-calendar/-/js-calendar-1.2.3.tgz",
|
||||
"integrity": "sha512-dAA1/Zbp4+c5E+ARCVTIuKepXsNLzSYfzvOimiYD4S5eeP9QuplSHLcdhfqFSwyM1o1u6ku6RRRCyaZ0YAjiBw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/lodash.chunk": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.chunk/-/lodash.chunk-4.2.0.tgz",
|
||||
"integrity": "sha512-ZzydJKfUHJwHa+hF5X66zLFCBrWn5GeF28OHEr4WVWtNDXlQ/IjWKPBiikqKo2ne0+v6JgCgJ0GzJp8k8bHC7w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.throttle": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
|
||||
"integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.30.17",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
|
||||
@ -1260,12 +1321,32 @@
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/numeral": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/numeral/-/numeral-2.0.6.tgz",
|
||||
"integrity": "sha512-qaKRmtYPZ5qdw4jWJD6bxEf1FJEqllJrwxCLIm0sQU/A7v2/czigzOb+C2uSiFsa9lBUzeH7M1oK+Q+OLxL3kA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/popper.js": {
|
||||
"version": "1.16.1",
|
||||
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
|
||||
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==",
|
||||
"deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/popperjs"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.3",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
|
||||
@ -1340,6 +1421,12 @@
|
||||
"integrity": "sha512-qGVDoreyYiP1pkQnbnFAUIS5AjenNwwQBdl7zeos9etl+hYKWahjRTfzAZZYBv5xNHx7vNKCmaLDQZ6Fr2AEXg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/select": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
|
||||
"integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
@ -1349,6 +1436,43 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tinycolor2": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz",
|
||||
"integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/v-click-outside-x": {
|
||||
"version": "3.7.1",
|
||||
"resolved": "https://registry.npmjs.org/v-click-outside-x/-/v-click-outside-x-3.7.1.tgz",
|
||||
"integrity": "sha512-WmUgmcIXr9clVpm1AYS/FgHtcDicfnfoxgQCNg4O6vfk9GVnxA0vSqO321ogUo0b7czYTidj7fQENvWFMWOkUg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.11.4",
|
||||
"npm": "6.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/view-ui-plus": {
|
||||
"version": "1.3.19",
|
||||
"resolved": "https://registry.npmjs.org/view-ui-plus/-/view-ui-plus-1.3.19.tgz",
|
||||
"integrity": "sha512-CBIuO/9+mbTTQnCWs2GOY+jyQiJLlc5EdxdvuyEGIiKV0YxQW/rLXUQabmm1w9n0iNTXV6adtqXYo2xzdr1NIA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"async-validator": "^3.3.0",
|
||||
"countup.js": "^1.9.3",
|
||||
"dayjs": "^1.11.0",
|
||||
"deepmerge": "^2.2.1",
|
||||
"element-resize-detector": "^1.2.0",
|
||||
"js-calendar": "^1.2.3",
|
||||
"lodash.chunk": "^4.2.0",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"numeral": "^2.0.6",
|
||||
"popper.js": "^1.14.6",
|
||||
"select": "^1.1.2",
|
||||
"tinycolor2": "^1.4.1",
|
||||
"v-click-outside-x": "^3.7.1"
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "6.2.5",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.2.5.tgz",
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
"@tauri-apps/plugin-fs": "^2.2.1",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"scriptjs": "^2.5.9",
|
||||
"view-ui-plus": "^1.3.19",
|
||||
"vue": "^3.5.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
222
src/App.vue
222
src/App.vue
@ -1,36 +1,74 @@
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import { ref, watch, onMounted, onUnmounted } from "vue";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { readTextFile, writeTextFile } from '@tauri-apps/plugin-fs';
|
||||
import MarkdownEditor from './components/MarkdownEditor.vue'
|
||||
import SelectFolder from './components/SelectFolder.vue'
|
||||
import FolderTree from './components/FolderTree.vue'
|
||||
|
||||
const markdownRef = ref(null);
|
||||
const filePath = ref("");
|
||||
// 原始示例数据,仅供参考
|
||||
const greetMsg = ref("");
|
||||
const name = ref("");
|
||||
|
||||
async function greet() {
|
||||
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
||||
greetMsg.value = await invoke("greet", { name: name.value });
|
||||
}
|
||||
|
||||
async function showFileTree(tree) {
|
||||
console.log(tree)
|
||||
// 动态控制页面左右布局
|
||||
const split = ref(0.2)
|
||||
watch(split, (newSplit, oldSplit) => {
|
||||
// 当 split 小于某个值时,隐藏左边布局
|
||||
if (newSplit < 0.05) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
async function readFileContent() {
|
||||
// TODO 动态调整 MarkdownEditor 的宽度
|
||||
// let width = newSplit < 0.2 ? '1400px' : '1200px'
|
||||
// markdownRef.value.resetWidth(width)
|
||||
// markdownRef.value.resetHeight(window.innerHeight - 15)
|
||||
})
|
||||
|
||||
// 监听页面宽度和高度,调整 markdown 编辑器的高宽度
|
||||
const windowWidth = ref(window.innerWidth)
|
||||
const windowHeight = ref(window.innerHeight)
|
||||
const handleResize = () => {
|
||||
windowWidth.value = window.innerWidth
|
||||
windowHeight.value = window.innerHeight
|
||||
// console.log(windowWidth.value)
|
||||
// console.log(windowHeight.value)
|
||||
markdownRef.value.resetWidth(windowWidth.value - 15)
|
||||
markdownRef.value.resetHeight(windowHeight.value - 15)
|
||||
}
|
||||
onMounted(() => window.addEventListener('resize', handleResize))
|
||||
onUnmounted(() => window.removeEventListener('resize', handleResize))
|
||||
|
||||
// <SelectFolder> 选择目录后赋值给 folderTreeData,然后再传递给 <FolderTree>
|
||||
const folderTreeData = ref(null)
|
||||
function showFileTree(treeData) {
|
||||
folderTreeData.value = treeData
|
||||
}
|
||||
|
||||
// <FolderTree> 完成文件树渲染后,点击文件得到 currentFilePath
|
||||
// 读取 currentFilePath 的文件内容,填充到 MarkdownEditor 之中
|
||||
const currentFilePath = ref("");
|
||||
const markdownRef = ref(null);
|
||||
async function loadFileContent(fileNodeData) {
|
||||
console.log(fileNodeData)
|
||||
const filePath = fileNodeData.path
|
||||
currentFilePath.value = filePath
|
||||
await readFileContent(filePath)
|
||||
}
|
||||
async function readFileContent(filePath) {
|
||||
try {
|
||||
console.log('文件读取成功:', markdownRef.value.setMarkdownCode(await readTextFile(filePath.value)))
|
||||
console.log('文件读取成功:', markdownRef.value.setMarkdownCode(await readTextFile(filePath)))
|
||||
} catch (err) {
|
||||
console.error('文件读取失败:', err);
|
||||
}
|
||||
}
|
||||
|
||||
async function writeFileContent() {
|
||||
console.log('文件新的内容:', markdownRef.value.getMarkdownCode())
|
||||
try {
|
||||
console.log('文件更新成功:', await writeTextFile(filePath.value, markdownRef.value.getMarkdownCode()))
|
||||
console.log('文件更新成功:', await writeTextFile(currentFilePath.value, markdownRef.value.getMarkdownCode()))
|
||||
} catch (err) {
|
||||
console.error('文件更新失败:', err);
|
||||
}
|
||||
@ -38,144 +76,48 @@ async function writeFileContent() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="container">
|
||||
<div class="row">
|
||||
<div>
|
||||
<Split v-model="split">
|
||||
<template #left>
|
||||
<div class="split-left">
|
||||
<SelectFolder @folder-selected="showFileTree" />
|
||||
<FolderTree :treeData="folderTreeData" @file-selected="loadFileContent" />
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<input id="file-path-input" v-model="filePath" placeholder="Enter a file path..." />
|
||||
<button @click="readFileContent">读取指定文件</button>
|
||||
<button @click="writeFileContent">更新指定文件</button>
|
||||
</template>
|
||||
<template #trigger>
|
||||
<div class="split-trigger"></div>
|
||||
</template>
|
||||
<template #right>
|
||||
<div class="split-right">
|
||||
<MarkdownEditor ref="markdownRef" width='800px' height='500px' markdownCode="# hello tauri" />
|
||||
</div>
|
||||
</template>
|
||||
</Split>
|
||||
</div>
|
||||
|
||||
<MarkdownEditor ref="markdownRef"
|
||||
width='1000px'
|
||||
height='1000px'
|
||||
markdownCode="# hello tauri"
|
||||
/>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.logo.vite:hover {
|
||||
filter: drop-shadow(0 0 2em #747bff);
|
||||
body {
|
||||
overflow: hidden;
|
||||
/* 禁用所有方向滚动 */
|
||||
}
|
||||
|
||||
.logo.vue:hover {
|
||||
filter: drop-shadow(0 0 2em #249b73);
|
||||
}
|
||||
|
||||
</style>
|
||||
<style>
|
||||
:root {
|
||||
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
font-weight: 400;
|
||||
|
||||
color: #0f0f0f;
|
||||
background-color: #f6f6f6;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
.container {
|
||||
margin: 0;
|
||||
padding-top: 10vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 6em;
|
||||
padding: 1.5em;
|
||||
will-change: filter;
|
||||
transition: 0.75s;
|
||||
}
|
||||
|
||||
.logo.tauri:hover {
|
||||
filter: drop-shadow(0 0 2em #24c8db);
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
input,
|
||||
button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
color: #0f0f0f;
|
||||
background-color: #ffffff;
|
||||
transition: border-color 0.25s;
|
||||
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
border-color: #396cd8;
|
||||
}
|
||||
button:active {
|
||||
border-color: #396cd8;
|
||||
background-color: #e8e8e8;
|
||||
}
|
||||
|
||||
input,
|
||||
button {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#greet-input {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
color: #f6f6f6;
|
||||
background-color: #2f2f2f;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #24c8db;
|
||||
}
|
||||
|
||||
input,
|
||||
button {
|
||||
color: #ffffff;
|
||||
background-color: #0f0f0f98;
|
||||
}
|
||||
button:active {
|
||||
background-color: #0f0f0f69;
|
||||
}
|
||||
.split-left {
|
||||
height: 100vh;
|
||||
border-right: 1px solid #dcdee2;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.split-trigger {
|
||||
width: 5px;
|
||||
height: 100vh;
|
||||
background-color: gray;
|
||||
}
|
||||
|
||||
.split-trigger:hover {
|
||||
cursor: ew-resize;
|
||||
}
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
120
src/components/FolderTree.vue
Normal file
120
src/components/FolderTree.vue
Normal file
@ -0,0 +1,120 @@
|
||||
<template>
|
||||
<Tree ref="tree" :class="hiddenClass" :data="treeRenderData" :render="renderContent" expand-node></Tree>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { resolveComponent } from 'vue'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
treeData: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: {
|
||||
path: "/Users/Frankie/rootFolder",
|
||||
name: "rootFolder",
|
||||
directory: true,
|
||||
nodes: [],
|
||||
},
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
treeRenderData: [],
|
||||
hiddenClass: "hidden"
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.renderTreeByTreeData(this.treeData)
|
||||
},
|
||||
watch: {
|
||||
treeData(newTreeData) {
|
||||
this.renderTreeByTreeData(newTreeData)
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
renderTreeByTreeData(currentTreeData) {
|
||||
this.treeRenderData = [this.renderNodeByNodeData(currentTreeData)]
|
||||
this.isHiddenTree(currentTreeData)
|
||||
},
|
||||
renderNodeByNodeData(nodeData) {
|
||||
// 判断必须包含的字段,没有则返回空对象
|
||||
if (nodeData == null || nodeData == undefined) {
|
||||
return {}
|
||||
}
|
||||
if (nodeData.path == undefined ||
|
||||
nodeData.name == undefined ||
|
||||
nodeData.directory == undefined
|
||||
) {
|
||||
return {}
|
||||
}
|
||||
if (nodeData.directory == true && nodeData.nodes == undefined) {
|
||||
return {}
|
||||
}
|
||||
|
||||
let nodeRenderData = {
|
||||
title: nodeData.name,
|
||||
directory: nodeData.directory,
|
||||
expand: nodeData.directory ? true : false,
|
||||
// 附带数据,不直接参与渲染
|
||||
path: nodeData.path,
|
||||
}
|
||||
if (nodeData.directory) {
|
||||
let childrenRenderData = []
|
||||
nodeData.nodes.forEach(node => {
|
||||
childrenRenderData.push(this.renderNodeByNodeData(node))
|
||||
});
|
||||
nodeRenderData.children = childrenRenderData
|
||||
}
|
||||
return nodeRenderData
|
||||
},
|
||||
isHiddenTree(currentTreeData) {
|
||||
if (currentTreeData == null) {
|
||||
this.hiddenClass = "hidden"
|
||||
} else {
|
||||
this.hiddenClass = ""
|
||||
}
|
||||
},
|
||||
renderContent(h, { root, node, data }) {
|
||||
return h('span', {
|
||||
style: {
|
||||
display: 'inline-block',
|
||||
width: '100%'
|
||||
},
|
||||
onClick: () => { this.selectNode(data) }
|
||||
}, [
|
||||
h('span', [
|
||||
h(resolveComponent('Icon'), {
|
||||
type: data.directory ? 'ios-folder-outline' : 'ios-paper-outline',
|
||||
style: {
|
||||
marginRight: '8px'
|
||||
}
|
||||
}),
|
||||
h('span', data.title)
|
||||
])
|
||||
]);
|
||||
},
|
||||
selectNode(nodeData) {
|
||||
// 文件夹不做任何动作
|
||||
if (nodeData.directory) {
|
||||
return
|
||||
}
|
||||
|
||||
// 清除其他选中状态,为当前节点增加选中状态
|
||||
let selectedNodes = this.$refs.tree.getSelectedNodes()
|
||||
selectedNodes.forEach(element => {
|
||||
element.selected = false
|
||||
});
|
||||
nodeData.selected = true
|
||||
|
||||
// 向父组件暴露当前选中的文件
|
||||
this.$emit('file-selected', nodeData);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
@ -1,25 +1,22 @@
|
||||
<template>
|
||||
<button @click="selectFolder">选择目录</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { join } from '@tauri-apps/api/path';
|
||||
import { open } from '@tauri-apps/plugin-dialog';
|
||||
import { readDir } from '@tauri-apps/plugin-fs';
|
||||
|
||||
// 文件树示例
|
||||
let folderTreeData = {
|
||||
<!--
|
||||
简述:支持选择目录,遍历返回文件树。
|
||||
注意:配置好 fs:scope 的权限范围,否则会报错 forbidden path
|
||||
文件树示例:
|
||||
{
|
||||
"path": "root-path/root",
|
||||
"name": "root",
|
||||
"directory": true,
|
||||
"nodes": [
|
||||
{
|
||||
"path": "root-path/root/folder1",
|
||||
"name": "folder1",
|
||||
"directory": true,
|
||||
"nodes": [
|
||||
{
|
||||
"path": "root-path/root/folder1/folder1-file2",
|
||||
"name": "folder1-file2.txt.md",
|
||||
"suffix": "md",
|
||||
"directory": false,
|
||||
},
|
||||
]
|
||||
},
|
||||
@ -27,50 +24,79 @@ let folderTreeData = {
|
||||
"path": "root-path/root/file3",
|
||||
"name": "file3.txt",
|
||||
"suffix": "txt",
|
||||
"directory": false,
|
||||
},
|
||||
]
|
||||
}
|
||||
-->
|
||||
<template>
|
||||
<Button type="primary" @click="selectFolder">选择目录</Button>
|
||||
</template>
|
||||
|
||||
async function readDirRecursively(parent, path) {
|
||||
<script>
|
||||
import { join } from '@tauri-apps/api/path';
|
||||
import { open } from '@tauri-apps/plugin-dialog';
|
||||
import { readDir } from '@tauri-apps/plugin-fs';
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
async selectFolder() {
|
||||
const folderPath = await open(
|
||||
{
|
||||
filters: [{ name: 'All Files', extensions: ['.'] }],
|
||||
directory: true
|
||||
}
|
||||
);
|
||||
if (folderPath) {
|
||||
const folderTreeData = await this.readDirRecursively(folderPath)
|
||||
this.$emit('folder-selected', folderTreeData);
|
||||
}
|
||||
},
|
||||
// 遍历文件夹,返回文件树
|
||||
async readDirRecursively(folderPath) {
|
||||
// 拼接得到当前文件夹路径
|
||||
const folderPath = await join(parent, path);
|
||||
let folderTreeNode = {
|
||||
"path": folderPath,
|
||||
"name": folderPath.split('/').pop(),
|
||||
"nodes": []
|
||||
"nodes": [],
|
||||
"directory": true,
|
||||
}
|
||||
|
||||
// 尝试读取 folderPath 目录下所有实体
|
||||
let entries;
|
||||
try {
|
||||
entries = await readDir(folderPath);
|
||||
} catch (err) {
|
||||
let errorMessage = ""
|
||||
if (err.startsWith("forbidden path")) {
|
||||
errorMessage = "no permission. " + err
|
||||
} else {
|
||||
errorMessage = "readDir failed. " + err
|
||||
}
|
||||
this.$Message.error({
|
||||
content: errorMessage,
|
||||
duration: 10,
|
||||
closable: true
|
||||
});
|
||||
return folderTreeNode
|
||||
}
|
||||
// 遍历该文件夹下所有实体
|
||||
const entries = await readDir(folderPath);
|
||||
for (const entry of entries) {
|
||||
const entryPath = await join(folderPath, entry.name);
|
||||
if (entry.isDirectory) {
|
||||
folderTreeNode.nodes.push(await readDirRecursively(folderPath, entry.name))
|
||||
folderTreeNode.nodes.push(await this.readDirRecursively(entryPath))
|
||||
} else {
|
||||
const fileSuffix = entry.name.split('.').pop()
|
||||
folderTreeNode.nodes.push({
|
||||
"path": entryPath,
|
||||
"name": entry.name,
|
||||
"suffix": fileSuffix,
|
||||
"directory": false,
|
||||
})
|
||||
}
|
||||
}
|
||||
return folderTreeNode
|
||||
}
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
async selectFolder() {
|
||||
const filePath = await open(
|
||||
{
|
||||
filters: [{ name: 'All Files', extensions: ['.'] }],
|
||||
directory: true
|
||||
});
|
||||
if (filePath) {
|
||||
folderTreeData = await readDirRecursively(filePath, '')
|
||||
this.$emit('folder-selected', folderTreeData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -1,4 +1,6 @@
|
||||
import { createApp } from "vue";
|
||||
import App from "./App.vue";
|
||||
import ViewUIPlus from 'view-ui-plus'
|
||||
import 'view-ui-plus/dist/styles/viewuiplus.css'
|
||||
|
||||
createApp(App).mount("#app");
|
||||
createApp(App).use(ViewUIPlus).mount("#app");
|
||||
Loading…
x
Reference in New Issue
Block a user