Skip to content

Commit 952dba2

Browse files
committed
feat: 支持多文件打开
1 parent 3b92f85 commit 952dba2

File tree

5 files changed

+194
-124
lines changed

5 files changed

+194
-124
lines changed

frontend/components.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ declare module 'vue' {
1818
ElOption: typeof import('element-plus/es')['ElOption']
1919
ElSelect: typeof import('element-plus/es')['ElSelect']
2020
ElSwitch: typeof import('element-plus/es')['ElSwitch']
21+
ElTabPane: typeof import('element-plus/es')['ElTabPane']
22+
ElTabs: typeof import('element-plus/es')['ElTabs']
23+
ElTooltip: typeof import('element-plus/es')['ElTooltip']
2124
MonacoEditor: typeof import('./src/components/MonacoEditor.vue')['default']
2225
}
2326
}
@@ -30,5 +33,8 @@ declare global {
3033
const ElOption: typeof import('element-plus/es')['ElOption']
3134
const ElSelect: typeof import('element-plus/es')['ElSelect']
3235
const ElSwitch: typeof import('element-plus/es')['ElSwitch']
36+
const ElTabPane: typeof import('element-plus/es')['ElTabPane']
37+
const ElTabs: typeof import('element-plus/es')['ElTabs']
38+
const ElTooltip: typeof import('element-plus/es')['ElTooltip']
3339
const MonacoEditor: typeof import('./src/components/MonacoEditor.vue')['default']
3440
}

frontend/src/App.vue

Lines changed: 52 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,27 @@
11
<template>
2-
<MonacoEditor
3-
v-if="defInfo.path"
4-
:key="defInfo.path"
5-
:path="defInfo.path"
6-
:like="like"
7-
@like="open = !open"
8-
@reset="inputPath"
9-
/>
2+
<el-tabs
3+
class="view"
4+
v-model="active"
5+
type="card"
6+
addable
7+
:closable="view.length > 1"
8+
@edit="viewEdit"
9+
>
10+
<el-tab-pane v-for="item in view" :key="item.path" :name="item.path">
11+
<template #label>
12+
<el-tooltip :content="item.path">
13+
<div>{{ item.path.split('/').pop() }}</div>
14+
</el-tooltip>
15+
</template>
16+
17+
<MonacoEditor
18+
:path="item.path"
19+
:like="like"
20+
@like="() => (open = !open)"
21+
@diff="(v) => (item.diff = v)"
22+
/>
23+
</el-tab-pane>
24+
</el-tabs>
1025

1126
<el-dialog v-model="open" title="偏好设置" width="300">
1227
<div class="like-dialog">
@@ -52,115 +67,62 @@
5267
</template>
5368

5469
<script setup lang="ts">
55-
import { onBeforeMount, reactive } from 'vue'
56-
import { ElMessageBox } from 'element-plus'
57-
5870
import MonacoEditor from './components/MonacoEditor.vue'
5971
6072
import { THEME_OPTIONS } from '@/utils/option'
6173
6274
import useLike from '@/hooks/useLike'
75+
import usePath from '@/hooks/usePath'
6376
6477
const { open, like, resetLike } = useLike()
78+
const { view, active, add, remove } = usePath()
6579
66-
const defInfo = reactive({ path: '' })
67-
68-
onBeforeMount(() => {
69-
const query = new URLSearchParams(window.location.search).get('path') || ''
70-
if (query) {
71-
defInfo.path = query
80+
const viewEdit = (v: string, action: 'remove' | 'add') => {
81+
if (action === 'add') {
82+
add()
7283
} else {
73-
inputPath(true)
84+
remove(v)
7485
}
75-
})
76-
77-
const inputPath = async (force?: boolean) => {
78-
return await ElMessageBox.prompt(
79-
'部分文件可在文件管理中双击文件进行编辑,详见应用介绍',
80-
'请输入文件路径',
81-
{
82-
showClose: false,
83-
closeOnClickModal: false,
84-
closeOnPressEscape: false,
85-
closeOnHashChange: false,
86-
showCancelButton: !force,
87-
confirmButtonText: '确认',
88-
cancelButtonText: '取消',
89-
},
90-
).then(({ value }) => {
91-
if (value) {
92-
defInfo.path = value
93-
}
94-
})
9586
}
9687
</script>
9788

9889
<style lang="scss">
9990
html,
10091
body,
10192
#app {
93+
position: relative;
10294
height: 100%;
103-
}
104-
105-
html {
106-
&.dark {
107-
#app {
108-
> .header,
109-
> .footer {
110-
background-color: #1c1c1c;
111-
}
112-
}
113-
}
114-
}
115-
116-
#app {
117-
height: 100%;
118-
display: flex;
119-
flex-direction: column;
12095
121-
> .header,
122-
> .footer {
123-
height: 32px;
96+
> .view {
97+
height: 100%;
12498
display: flex;
125-
align-items: center;
126-
gap: 4px;
127-
padding: 0 12px;
99+
flex-direction: column;
128100
129-
> * {
101+
> .el-tabs__header {
102+
height: 40px;
103+
padding-right: 56px;
130104
margin: 0;
131-
}
132-
}
133-
134-
> .header {
135-
border-bottom: solid 1px var(--el-border-color);
136105
137-
> .title {
138-
font-size: 14px;
139-
line-height: 32px;
140-
color: var(--el-text-color-primary);
141-
}
142-
}
143-
144-
> .footer {
145-
border-top: solid 1px var(--el-border-color);
106+
.el-tabs__nav {
107+
border-top: none;
108+
border-radius: 0;
109+
}
146110
147-
> .developed {
148-
font-size: 12px;
149-
line-height: 32px;
150-
color: var(--el-text-color-placeholder);
111+
.el-tabs__new-tab {
112+
width: 22px;
113+
height: 22px;
114+
}
151115
}
152-
}
153116
154-
> .editor {
155-
position: relative;
156-
flex: 1;
117+
> .el-tabs__content {
118+
flex: 1;
157119
158-
> .content {
159-
position: absolute;
160-
left: 0;
161-
top: 0;
162-
height: 100%;
163-
width: 100%;
120+
> .el-tab-pane {
121+
position: relative;
122+
height: 100%;
123+
display: flex;
124+
flex-direction: column;
125+
}
164126
}
165127
}
166128
}

frontend/src/components/MonacoEditor.vue

Lines changed: 58 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,12 @@
11
<template>
2-
<div class="header">
3-
<div class="title">{{ $props.path }}</div>
4-
5-
<el-button size="small" @click="$emit('reset')">切换文件</el-button>
6-
7-
<div style="flex: 1"></div>
8-
9-
<el-button size="small" @click="$emit('like')">偏好</el-button>
10-
11-
<el-button size="small" :disabled="code.value === code.org" @click="save()">保存</el-button>
12-
</div>
13-
14-
<div class="editor">
15-
<div class="content" v-if="code.lang">
16-
<MonacoEditor
17-
v-model:value="code.value"
18-
:language="code.lang"
19-
:theme="like.theme"
20-
:options="{ fontSize: 14, automaticLayout: true }"
21-
@editorDidMount="editorDidMount"
22-
/>
23-
</div>
2+
<div v-if="code.lang" style="flex: 1">
3+
<MonacoEditor
4+
v-model:value="code.value"
5+
:language="code.lang"
6+
:theme="like.theme"
7+
:options="{ fontSize: 14, automaticLayout: true }"
8+
@editorDidMount="editorDidMount"
9+
/>
2410
</div>
2511

2612
<div class="footer">
@@ -59,13 +45,16 @@
5945
:value="item.value"
6046
/>
6147
</el-select>
48+
49+
<el-button size="small" :icon="Setting" @click="$emit('like')"></el-button>
6250
</div>
6351
</template>
6452

6553
<script setup lang="ts">
6654
import { reactive, watch } from 'vue'
6755
import MonacoEditor from 'monaco-editor-vue3'
6856
import * as iconv from 'iconv-lite'
57+
import { Setting } from '@element-plus/icons-vue'
6958
7059
import { LANG_OPTIONS, ENCODING_OPTIONS } from '@/utils/option'
7160
@@ -74,7 +63,26 @@ import useCode from '../hooks/useCode'
7463
import useEditor from '../hooks/useEditor'
7564
7665
const $props = defineProps<{ path: string; like: LikeModel }>()
77-
const $emit = defineEmits<{ like: []; reset: [] }>()
66+
const $emit = defineEmits<{ like: []; diff: [v: boolean] }>()
67+
68+
defineExpose({
69+
save: () => save(),
70+
})
71+
72+
const editorLike = reactive({ ...$props.like })
73+
74+
const { code, save } = useCode({
75+
path: $props.path,
76+
confirm: () => editorLike.confirm,
77+
onSave: () => $emit('diff', false),
78+
})
79+
80+
const { editorDidMount, changeLang, changeTheme, changeSize } = useEditor({ onSave: save })
81+
82+
const changeEncode = async (v: string) => {
83+
const buffer = await code.blob.arrayBuffer()
84+
code.org = code.value = iconv.decode(new Uint8Array(buffer), v)
85+
}
7886
7987
watch(
8088
() => $props.like.confirm,
@@ -96,15 +104,32 @@ watch(
96104
changeSize(v)
97105
},
98106
)
107+
watch(
108+
() => code.value,
109+
(v) => {
110+
$emit('diff', v !== code.org)
111+
},
112+
)
113+
</script>
99114

100-
const editorLike = reactive({ ...$props.like })
101-
102-
const { code, save } = useCode({ path: $props.path, confirm: () => editorLike.confirm })
103-
104-
const { editorDidMount, changeLang, changeTheme, changeSize } = useEditor({ onSave: save })
105-
106-
const changeEncode = async (v: string) => {
107-
const buffer = await code.blob.arrayBuffer()
108-
code.org = code.value = iconv.decode(new Uint8Array(buffer), v)
115+
<style lang="scss" scoped>
116+
.footer {
117+
height: 32px;
118+
border-top: solid 1px var(--el-border-color);
119+
display: flex;
120+
align-items: center;
121+
gap: 4px;
122+
padding: 0 4px;
123+
background-color: var(--el-bg-color);
124+
125+
> * {
126+
margin: 0;
127+
}
128+
129+
> .developed {
130+
font-size: 12px;
131+
line-height: 32px;
132+
color: var(--el-text-color-placeholder);
133+
}
109134
}
110-
</script>
135+
</style>

frontend/src/hooks/useCode.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ interface CodeModel {
1313
encode: string
1414
}
1515

16-
export default function useCode(option: { path: string; confirm: () => boolean }) {
16+
export default function useCode(option: {
17+
path: string
18+
confirm: () => boolean
19+
onSave: () => void
20+
}) {
1721
const code = reactive<CodeModel>({ blob: [], org: '', value: '', lang: '', encode: 'utf8' })
1822

1923
const load = async (path: string) => {
@@ -78,6 +82,8 @@ export default function useCode(option: { path: string; confirm: () => boolean }
7882
ElMessage({ type: 'success', message: '操作成功' })
7983

8084
code.org = code.value
85+
86+
option.onSave()
8187
} else {
8288
ElMessage({ type: 'error', message: value.msg })
8389
}

0 commit comments

Comments
 (0)