Skip to content

Commit 11a8d93

Browse files
committed
feat: 支持文件快照的功能
1 parent 6c1461b commit 11a8d93

13 files changed

Lines changed: 283 additions & 72 deletions

File tree

backend/src/router/def.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const wwwReg = /(?<=<script\s[^>]*?src=")\/assets\/index[^"]+(?=")/gi
66

77
const constReg = /fn\:\(\)\=\>\{const (.+)\=\{\}\;return .+\.state\.apps\.forEach/
88

9-
module.exports = async function read({ body }) {
9+
module.exports = async function ({ body }) {
1010
try {
1111
const html = fs.readFileSync(path.join(wwwRoot, './index.html'), { encoding: 'utf-8' })
1212

backend/src/router/del.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
const fs = require('fs')
2+
3+
module.exports = async function ({ body }) {
4+
if (!body.path) {
5+
return { code: 400, msg: '缺少文件路径参数' }
6+
}
7+
8+
const filePath = body.path[0] === '/' ? body.path : `/${body.path}`
9+
10+
try {
11+
if (fs.existsSync(filePath)) {
12+
const stat = fs.statSync(filePath)
13+
if (!stat.isFile()) {
14+
return { code: 400, msg: '路径不是文件' }
15+
}
16+
} else {
17+
return { code: 404, msg: '文件不存在' }
18+
}
19+
20+
fs.rmSync(filePath, { force: true })
21+
22+
return { code: 200, msg: '操作成功', data: {} }
23+
} catch (err) {
24+
if (err.code === 'EACCES') {
25+
return { code: 401, msg: '权限不足,无法删除文件' }
26+
} else if (err.code === 'ENOENT') {
27+
return { code: 400, msg: '目录不存在,无法删除文件' }
28+
} else {
29+
return { code: 400, msg: `文件操作错误: ${err.message}` }
30+
}
31+
}
32+
}

backend/src/router/dir.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const fs = require('fs')
22
const path = require('path')
33

4-
module.exports = async function read({ query }) {
4+
module.exports = async function ({ query }) {
55
if (!query.path) {
66
return { code: 400, msg: '缺少文件路径参数', data: query }
77
}
@@ -29,7 +29,7 @@ module.exports = async function read({ query }) {
2929
if (itemStats.isDirectory()) {
3030
result.dirs.push({ name: item })
3131
} else {
32-
result.files.push({ name: item, size: itemStats.size, updateDate: itemStats.mtime })
32+
result.files.push({ name: item, size: itemStats.size, updateDate: itemStats.mtime, createDate: itemStats.birthtime })
3333
}
3434
})
3535

backend/src/router/read.js

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
const fs = require('fs')
22

3-
module.exports = async function read({ query }) {
3+
module.exports = async function ({ query }) {
44
if (!query.path) {
5-
return { code: 400, msg: '缺少文件路径参数', data: query }
5+
return { code: 400, msg: '缺少文件路径参数' }
66
}
77

8-
const path = query.path[0] === '/' ? query.path : `/${query.path}`
8+
const filePath = query.path[0] === '/' ? query.path : `/${query.path}`
99

1010
try {
11-
if (!fs.existsSync(path)) {
12-
return { code: 404, msg: '文件不存在', data: query }
11+
if (!fs.existsSync(filePath)) {
12+
return { code: 404, msg: '文件不存在' }
1313
}
1414

15-
const stat = fs.statSync(path)
15+
const stat = fs.statSync(filePath)
1616
if (!stat.isFile()) {
17-
return { code: 400, msg: '路径不是文件', data: query }
17+
return { code: 400, msg: '路径不是文件' }
1818
}
1919

2020
if (query.cache) {
@@ -31,23 +31,19 @@ module.exports = async function read({ query }) {
3131
data: {
3232
size: stat.size,
3333
time: stat.mtime.toUTCString(),
34-
filename: path.split('/').pop(),
35-
stream: fs.createReadStream(path),
34+
filename: filePath.split('/').pop(),
35+
stream: fs.createReadStream(filePath),
3636
},
3737
}
3838
} catch (error) {
3939
if (error.code === 'EACCES' || error.code === 'EPERM') {
40-
return { code: 403, msg: '权限不足,无法读取文件', data: query }
40+
return { code: 401, msg: '权限不足,无法读取文件' }
41+
} else if (err.code === 'ENOENT') {
42+
return { code: 400, msg: '文件不存在' }
43+
} else if (err.code === 'EISDIR') {
44+
return { code: 400, msg: '路径是目录而不是文件' }
45+
} else {
46+
return { code: 400, msg: `读取文件失败: ${err.message}` }
4147
}
42-
43-
if (error.code === 'ENOENT') {
44-
return { code: 404, msg: '文件不存在', data: query }
45-
}
46-
47-
if (error.code === 'EISDIR') {
48-
return { code: 400, msg: '路径是目录而不是文件', data: query }
49-
}
50-
51-
return { code: 500, msg: `读取文件失败: ${error.message}`, data: query }
5248
}
5349
}

backend/src/router/save.js

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,41 @@
11
const fs = require('fs')
22
const iconv = require('iconv-lite')
3-
const { dirname } = require('path')
3+
const path = require('path')
44

5-
module.exports = async function read({ body }) {
6-
const { encode, value, force } = body
7-
8-
const path = body.path[0] === '/' ? body.path : `/${body.path}`
9-
10-
if (!path) {
11-
return { code: 400, msg: '缺少文件路径参数', data: body }
5+
module.exports = async function ({ body }) {
6+
if (!body.path) {
7+
return { code: 400, msg: '缺少文件路径参数' }
128
}
139

10+
const filePath = body.path[0] === '/' ? body.path : `/${body.path}`
11+
1412
try {
15-
if (fs.existsSync(path)) {
16-
const stat = fs.statSync(path)
13+
if (fs.existsSync(filePath)) {
14+
const stat = fs.statSync(filePath)
1715
if (!stat.isFile()) {
18-
return { code: 400, msg: '路径不是文件', data: body }
16+
return { code: 400, msg: '路径不是文件' }
1917
}
20-
21-
fs.writeFileSync(path, iconv.encode(value, encode))
2218
} else {
23-
if (Number(force) === 1) {
24-
const dir = dirname(path)
19+
if (Number(body.force) === 1) {
20+
const dir = path.dirname(filePath)
2521
fs.existsSync(dir) || fs.mkdirSync(dir, { recursive: true })
2622
} else {
27-
return { code: 404, msg: '文件不存在', data: body }
23+
return { code: 404, msg: '文件不存在' }
2824
}
2925
}
3026

31-
fs.writeFileSync(path, iconv.encode(value, encode))
27+
fs.writeFileSync(filePath, iconv.encode(body.value, body.encode))
3228

33-
const stat = fs.statSync(path)
29+
const stat = fs.statSync(filePath)
3430

3531
return { code: 200, msg: '操作成功', data: { size: stat.size, time: stat.mtime.toUTCString() } }
3632
} catch (err) {
3733
if (err.code === 'EACCES') {
38-
return { code: 401, msg: '权限不足,无法写入文件', data: body }
34+
return { code: 401, msg: '权限不足,无法写入文件' }
3935
} else if (err.code === 'ENOENT') {
40-
return { code: 400, msg: '目录不存在,无法写入文件', data: body }
36+
return { code: 400, msg: '目录不存在,无法写入文件' }
4137
} else {
42-
return { code: 400, msg: `文件操作错误: ${err.message}`, data: body }
38+
return { code: 400, msg: `文件操作错误: ${err.message}` }
4339
}
4440
}
4541
}

backend/src/utils/exec.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const getType = require('./type')
33
const router = {
44
read: { type: 'file', run: require('../router/read') },
55
save: { run: require('../router/save') },
6+
del: { run: require('../router/del') },
67
dir: { run: require('../router/dir') },
78
def: { run: require('../router/def') },
89
}

frontend/auto-imports.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@
66
// biome-ignore lint: disable
77
export {}
88
declare global {
9-
9+
const ElMessage: typeof import('element-plus/es').ElMessage
1010
}

frontend/components.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ declare module 'vue' {
2727
ElPopover: typeof import('element-plus/es')['ElPopover']
2828
ElSelect: typeof import('element-plus/es')['ElSelect']
2929
ElSwitch: typeof import('element-plus/es')['ElSwitch']
30+
ElTable: typeof import('element-plus/es')['ElTable']
31+
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
3032
ElTabPane: typeof import('element-plus/es')['ElTabPane']
3133
ElTabs: typeof import('element-plus/es')['ElTabs']
3234
ElTooltip: typeof import('element-plus/es')['ElTooltip']
@@ -63,6 +65,8 @@ declare global {
6365
const ElPopover: typeof import('element-plus/es')['ElPopover']
6466
const ElSelect: typeof import('element-plus/es')['ElSelect']
6567
const ElSwitch: typeof import('element-plus/es')['ElSwitch']
68+
const ElTable: typeof import('element-plus/es')['ElTable']
69+
const ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
6670
const ElTabPane: typeof import('element-plus/es')['ElTabPane']
6771
const ElTabs: typeof import('element-plus/es')['ElTabs']
6872
const ElTooltip: typeof import('element-plus/es')['ElTooltip']

frontend/src/components/MonacoEditor.vue

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,19 @@
1515

1616
<div class="footer">
1717
<el-tooltip content="文件快照" placement="bottom">
18-
<el-icon class="icon" @click="camera.open(code.path)"><Camera /></el-icon>
18+
<el-icon
19+
class="icon"
20+
@click="
21+
camera.open({
22+
path: code.path,
23+
value: code.value,
24+
encode: code.encode,
25+
callback: (v) => (code.value = v),
26+
})
27+
"
28+
>
29+
<Camera />
30+
</el-icon>
1931
</el-tooltip>
2032

2133
<div class="info" v-if="code.date">修改时间:{{ code.date.format('YYYY/MM/DD HH:mm:ss') }}</div>
@@ -47,7 +59,7 @@ import * as iconv from 'iconv-lite'
4759
import MdView from '@/components/MdView.vue'
4860
4961
import { useUserStore } from '@/store/user'
50-
import { useCameraStore } from '@/store/camera2'
62+
import { useCameraStore } from '@/store/camera'
5163
5264
import { LANG_OPTIONS, ENCODING_OPTIONS } from '@/utils/option'
5365
import { getSize } from '@/utils/file'
@@ -121,7 +133,7 @@ onMounted(() => {
121133
}
122134
123135
> .icon {
124-
color: var(--el-text-color-regular);
136+
color: var(--el-text-color-placeholder);
125137
cursor: pointer;
126138
}
127139
Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,54 @@
11
<template>
2-
<el-dialog v-model="camera.show" :title="`文件快照:${camera.path}`" width="600">
2+
<el-dialog v-model="camera.show" title="文件快照" width="600">
33
<div class="camera-dialog">
4-
<div>111</div>
4+
<el-input v-model="input" placeholder="请输入快照说明" class="input" maxlength="32" show-word-limit>
5+
<template #append>
6+
<el-button
7+
@click="
8+
() => {
9+
camera.add(input)
10+
input = ''
11+
}
12+
"
13+
>
14+
添加快照
15+
</el-button>
16+
</template>
17+
</el-input>
18+
19+
<el-table :data="camera.data" empty-text="暂无快照" height="400">
20+
<el-table-column label="快照说明" width="250" show-overflow-tooltip>
21+
<template #default="scope">
22+
{{ scope.row.name.replace(/\.txt$/, '') }}
23+
</template>
24+
</el-table-column>
25+
26+
<el-table-column label="创建日期" width="180">
27+
<template #default="scope">
28+
{{ dayjs(scope.row.createDate).format('YYYY/MM/DD HH:mm:ss') }}
29+
</template>
30+
</el-table-column>
31+
32+
<el-table-column label="操作" align="center">
33+
<template #default="scope">
34+
<div style="display: flex; justify-content: center">
35+
<el-button size="small" @click="camera.usage(scope.$index)">使用</el-button>
36+
<el-button size="small" @click="camera.del(scope.$index)">删除</el-button>
37+
</div>
38+
</template>
39+
</el-table-column>
40+
</el-table>
541
</div>
642
</el-dialog>
743
</template>
844

945
<script lang="ts" setup>
10-
import { useCameraStore } from '@/store/camera2'
46+
import { ref } from 'vue'
47+
48+
import { useCameraStore } from '@/store/camera'
49+
import { dayjs } from 'element-plus'
1150
1251
const camera = useCameraStore()
13-
</script>
1452
15-
<style lang="scss" scoped></style>
53+
const input = ref('')
54+
</script>

0 commit comments

Comments
 (0)