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 用于打开本地目录
|
1. 新建 SelectFolder.vue 用于打开本地目录
|
||||||
2. 在 App.vue 使用组件 SelectFolder 和 MarkdownEditor.vue
|
2. 在 App.vue 使用组件 SelectFolder 和 MarkdownEditor.vue
|
||||||
3. 使用 plugin-fs 插件读写本地文件内容
|
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-fs": "^2.2.1",
|
||||||
"@tauri-apps/plugin-opener": "^2",
|
"@tauri-apps/plugin-opener": "^2",
|
||||||
"scriptjs": "^2.5.9",
|
"scriptjs": "^2.5.9",
|
||||||
|
"view-ui-plus": "^1.3.19",
|
||||||
"vue": "^3.5.13"
|
"vue": "^3.5.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -1153,12 +1154,54 @@
|
|||||||
"integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==",
|
"integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/csstype": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/entities": {
|
||||||
"version": "4.5.0",
|
"version": "4.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
"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": "^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": {
|
"node_modules/magic-string": {
|
||||||
"version": "0.30.17",
|
"version": "0.30.17",
|
||||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
|
"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": "^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": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||||
"license": "ISC"
|
"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": {
|
"node_modules/postcss": {
|
||||||
"version": "8.5.3",
|
"version": "8.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
|
||||||
@ -1340,6 +1421,12 @@
|
|||||||
"integrity": "sha512-qGVDoreyYiP1pkQnbnFAUIS5AjenNwwQBdl7zeos9etl+hYKWahjRTfzAZZYBv5xNHx7vNKCmaLDQZ6Fr2AEXg==",
|
"integrity": "sha512-qGVDoreyYiP1pkQnbnFAUIS5AjenNwwQBdl7zeos9etl+hYKWahjRTfzAZZYBv5xNHx7vNKCmaLDQZ6Fr2AEXg==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/source-map-js": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
@ -1349,6 +1436,43 @@
|
|||||||
"node": ">=0.10.0"
|
"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": {
|
"node_modules/vite": {
|
||||||
"version": "6.2.5",
|
"version": "6.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.2.5.tgz",
|
"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-fs": "^2.2.1",
|
||||||
"@tauri-apps/plugin-opener": "^2",
|
"@tauri-apps/plugin-opener": "^2",
|
||||||
"scriptjs": "^2.5.9",
|
"scriptjs": "^2.5.9",
|
||||||
|
"view-ui-plus": "^1.3.19",
|
||||||
"vue": "^3.5.13"
|
"vue": "^3.5.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
248
src/App.vue
248
src/App.vue
@ -1,181 +1,123 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from "vue";
|
import { ref, watch, onMounted, onUnmounted } from "vue";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
import { readTextFile, writeTextFile } from '@tauri-apps/plugin-fs';
|
import { readTextFile, writeTextFile } from '@tauri-apps/plugin-fs';
|
||||||
import MarkdownEditor from './components/MarkdownEditor.vue'
|
import MarkdownEditor from './components/MarkdownEditor.vue'
|
||||||
import SelectFolder from './components/SelectFolder.vue'
|
import SelectFolder from './components/SelectFolder.vue'
|
||||||
|
import FolderTree from './components/FolderTree.vue'
|
||||||
|
|
||||||
const markdownRef = ref(null);
|
// 原始示例数据,仅供参考
|
||||||
const filePath = ref("");
|
|
||||||
const greetMsg = ref("");
|
const greetMsg = ref("");
|
||||||
const name = ref("");
|
const name = ref("");
|
||||||
|
|
||||||
async function greet() {
|
async function greet() {
|
||||||
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
||||||
greetMsg.value = await invoke("greet", { name: name.value });
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
async function readFileContent() {
|
// <FolderTree> 完成文件树渲染后,点击文件得到 currentFilePath
|
||||||
try {
|
// 读取 currentFilePath 的文件内容,填充到 MarkdownEditor 之中
|
||||||
console.log('文件读取成功:', markdownRef.value.setMarkdownCode(await readTextFile(filePath.value)))
|
const currentFilePath = ref("");
|
||||||
} catch (err) {
|
const markdownRef = ref(null);
|
||||||
console.error('文件读取失败:', err);
|
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)))
|
||||||
|
} catch (err) {
|
||||||
|
console.error('文件读取失败:', err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function writeFileContent() {
|
async function writeFileContent() {
|
||||||
console.log('文件新的内容:', markdownRef.value.getMarkdownCode())
|
console.log('文件新的内容:', markdownRef.value.getMarkdownCode())
|
||||||
try {
|
try {
|
||||||
console.log('文件更新成功:', await writeTextFile(filePath.value, markdownRef.value.getMarkdownCode()))
|
console.log('文件更新成功:', await writeTextFile(currentFilePath.value, markdownRef.value.getMarkdownCode()))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('文件更新失败:', err);
|
console.error('文件更新失败:', err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main class="container">
|
<div>
|
||||||
<div class="row">
|
<Split v-model="split">
|
||||||
<SelectFolder @folder-selected="showFileTree" />
|
<template #left>
|
||||||
|
<div class="split-left">
|
||||||
|
<SelectFolder @folder-selected="showFileTree" />
|
||||||
|
<FolderTree :treeData="folderTreeData" @file-selected="loadFileContent" />
|
||||||
|
</div>
|
||||||
|
</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>
|
</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>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<MarkdownEditor ref="markdownRef"
|
|
||||||
width='1000px'
|
|
||||||
height='1000px'
|
|
||||||
markdownCode="# hello tauri"
|
|
||||||
/>
|
|
||||||
</main>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.logo.vite:hover {
|
body {
|
||||||
filter: drop-shadow(0 0 2em #747bff);
|
overflow: hidden;
|
||||||
|
/* 禁用所有方向滚动 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo.vue:hover {
|
.split-left {
|
||||||
filter: drop-shadow(0 0 2em #249b73);
|
height: 100vh;
|
||||||
}
|
border-right: 1px solid #dcdee2;
|
||||||
|
overflow-x: hidden;
|
||||||
</style>
|
overflow-y: auto;
|
||||||
<style>
|
scrollbar-width: none;
|
||||||
: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-trigger {
|
||||||
|
width: 5px;
|
||||||
|
height: 100vh;
|
||||||
|
background-color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.split-trigger:hover {
|
||||||
|
cursor: ew-resize;
|
||||||
|
}
|
||||||
</style>
|
</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>
|
注意:配置好 fs:scope 的权限范围,否则会报错 forbidden path
|
||||||
|
文件树示例:
|
||||||
<script>
|
{
|
||||||
import { join } from '@tauri-apps/api/path';
|
|
||||||
import { open } from '@tauri-apps/plugin-dialog';
|
|
||||||
import { readDir } from '@tauri-apps/plugin-fs';
|
|
||||||
|
|
||||||
// 文件树示例
|
|
||||||
let folderTreeData = {
|
|
||||||
"path": "root-path/root",
|
"path": "root-path/root",
|
||||||
"name": "root",
|
"name": "root",
|
||||||
|
"directory": true,
|
||||||
"nodes": [
|
"nodes": [
|
||||||
{
|
{
|
||||||
"path": "root-path/root/folder1",
|
"path": "root-path/root/folder1",
|
||||||
"name": "folder1",
|
"name": "folder1",
|
||||||
|
"directory": true,
|
||||||
"nodes": [
|
"nodes": [
|
||||||
{
|
{
|
||||||
"path": "root-path/root/folder1/folder1-file2",
|
"path": "root-path/root/folder1/folder1-file2",
|
||||||
"name": "folder1-file2.txt.md",
|
"name": "folder1-file2.txt.md",
|
||||||
"suffix": "md",
|
"suffix": "md",
|
||||||
|
"directory": false,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -27,49 +24,78 @@ let folderTreeData = {
|
|||||||
"path": "root-path/root/file3",
|
"path": "root-path/root/file3",
|
||||||
"name": "file3.txt",
|
"name": "file3.txt",
|
||||||
"suffix": "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';
|
||||||
const folderPath = await join(parent, path);
|
import { open } from '@tauri-apps/plugin-dialog';
|
||||||
let folderTreeNode = {
|
import { readDir } from '@tauri-apps/plugin-fs';
|
||||||
"path": folderPath,
|
|
||||||
"name": folderPath.split('/').pop(),
|
|
||||||
"nodes": []
|
|
||||||
}
|
|
||||||
|
|
||||||
// 遍历该文件夹下所有实体
|
|
||||||
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))
|
|
||||||
} else {
|
|
||||||
const fileSuffix = entry.name.split('.').pop()
|
|
||||||
folderTreeNode.nodes.push({
|
|
||||||
"path": entryPath,
|
|
||||||
"name": entry.name,
|
|
||||||
"suffix": fileSuffix,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return folderTreeNode
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
methods: {
|
methods: {
|
||||||
async selectFolder() {
|
async selectFolder() {
|
||||||
const filePath = await open(
|
const folderPath = await open(
|
||||||
{
|
{
|
||||||
filters: [{ name: 'All Files', extensions: ['.'] }],
|
filters: [{ name: 'All Files', extensions: ['.'] }],
|
||||||
directory: true
|
directory: true
|
||||||
});
|
}
|
||||||
if (filePath) {
|
);
|
||||||
folderTreeData = await readDirRecursively(filePath, '')
|
if (folderPath) {
|
||||||
|
const folderTreeData = await this.readDirRecursively(folderPath)
|
||||||
this.$emit('folder-selected', folderTreeData);
|
this.$emit('folder-selected', folderTreeData);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
// 遍历文件夹,返回文件树
|
||||||
|
async readDirRecursively(folderPath) {
|
||||||
|
// 拼接得到当前文件夹路径
|
||||||
|
let folderTreeNode = {
|
||||||
|
"path": folderPath,
|
||||||
|
"name": folderPath.split('/').pop(),
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
// 遍历该文件夹下所有实体
|
||||||
|
for (const entry of entries) {
|
||||||
|
const entryPath = await join(folderPath, entry.name);
|
||||||
|
if (entry.isDirectory) {
|
||||||
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
import { createApp } from "vue";
|
import { createApp } from "vue";
|
||||||
import App from "./App.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