sawAdmin/src/views/system/slide-code-edit.vue

358 lines
7.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<transition name="slide">
<div class="side-panel">
<button @click="handleClose" class="close-btn"></button>
<el-button type="primary" @click="UpdateFileCOntent">保存修改</el-button>
<div class="content">
<h2>{{ fileName }}</h2>
<div class="editor-container">
<textarea
ref="textareaRef"
v-model="localContent"
@input="handleInput"
@scroll="handleScroll"
spellcheck="false"
></textarea>
<pre class="highlight-container" ref="highlightRef"><code v-html="highlightedCode"></code></pre>
</div>
</div>
</div>
</transition>
</template>
<script setup lang="ts">
import { ref, watch, nextTick, onMounted } from "vue";
import { UpdateFileContentService } from "@/api/file";
import { ElMessage } from "element-plus";
import hljs from 'highlight.js';
import 'highlight.js/styles/atom-one-dark.css';
const props = defineProps({
content: {
type: String,
default: "",
},
show: {
type: Boolean,
default: false,
},
fileName: {
type: String,
default: "编辑代码",
},
fileID: {
type: Number,
default: 0,
},
});
const emit = defineEmits<{
(e: "close"): void;
}>();
const textareaRef = ref<HTMLTextAreaElement | null>(null);
const highlightRef = ref<HTMLElement | null>(null);
const localContent = ref(props.content);
const highlightedCode = ref('');
// 根据文件名判断语言
const detectLanguage = (fileName: string) => {
const extension = fileName.split('.').pop()?.toLowerCase();
const languageMap: Record<string, string> = {
'js': 'javascript',
'jsx': 'javascript',
'ts': 'typescript',
'tsx': 'typescript',
'html': 'html',
'css': 'css',
'scss': 'scss',
'less': 'less',
'py': 'python',
'java': 'java',
'php': 'php',
'go': 'go',
'rb': 'ruby',
'rs': 'rust',
'c': 'c',
'cpp': 'cpp',
'cs': 'csharp',
'json': 'json',
'md': 'markdown',
'xml': 'xml',
'yaml': 'yaml',
'yml': 'yaml',
'sh': 'bash',
'bash': 'bash',
'sql': 'sql',
};
return extension && languageMap[extension] ? languageMap[extension] : 'plaintext';
};
// 高亮代码
const highlightCode = () => {
if (!localContent.value) {
highlightedCode.value = '';
return;
}
const language = detectLanguage(props.fileName);
try {
if (language !== 'plaintext') {
const highlighted = hljs.highlight(localContent.value, { language });
highlightedCode.value = highlighted.value;
} else {
// 如果无法检测语言,尝试自动检测
const highlighted = hljs.highlightAuto(localContent.value);
highlightedCode.value = highlighted.value;
}
} catch (error) {
console.error('Highlight error:', error);
// 降级处理转义HTML并保留换行
highlightedCode.value = localContent.value
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
};
// 处理输入事件
const handleInput = () => {
highlightCode();
nextTick(() => {
if (textareaRef.value && highlightRef.value) {
// 同步滚动位置
highlightRef.value.scrollTop = textareaRef.value.scrollTop;
highlightRef.value.scrollLeft = textareaRef.value.scrollLeft;
}
});
};
// 处理滚动事件
const handleScroll = () => {
if (textareaRef.value && highlightRef.value) {
highlightRef.value.scrollTop = textareaRef.value.scrollTop;
highlightRef.value.scrollLeft = textareaRef.value.scrollLeft;
}
};
// 当props.content变化时同步到本地
watch(
() => props.content,
(newVal) => {
localContent.value = newVal;
highlightCode();
}
);
// 当面板显示时自动聚焦到textarea
watch(
() => props.show,
async (newVal) => {
if (newVal) {
await nextTick();
textareaRef.value?.focus();
highlightCode();
}
}
);
// 初始化
onMounted(() => {
highlightCode();
});
const handleClose = () => {
emit("close");
};
const UpdateFileCOntent = async () => {
let req = {
token: localStorage.getItem("token"),
file_id: props.fileID,
file_content: localContent.value,
};
try {
let result = await UpdateFileContentService(req);
if (result["code"] === 0) {
ElMessage.success("修改成功");
} else {
ElMessage.error("修改失败");
}
} catch (e) {
console.log(e);
}
};
</script>
<style scoped lang="css">
.side-panel {
position: fixed;
top: 0;
right: 0;
width: 50%;
height: 100%;
background-color: #282c34;
z-index: 1001;
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.3);
padding: 20px;
display: flex;
flex-direction: column;
color: #abb2bf;
}
.close-btn {
position: absolute;
top: 10px;
right: 10px;
width: 30px;
height: 30px;
background: none;
border: none;
cursor: pointer;
z-index: 1;
}
.close-btn::before,
.close-btn::after {
content: '';
position: absolute;
width: 20px;
height: 2px;
background-color: #abb2bf;
top: 50%;
left: 50%;
transition: background-color 0.2s;
}
.close-btn::before {
transform: translate(-50%, -50%) rotate(45deg);
}
.close-btn::after {
transform: translate(-50%, -50%) rotate(-45deg);
}
.close-btn:hover::before,
.close-btn:hover::after {
background-color: #61afef;
}
.content {
flex: 1;
display: flex;
flex-direction: column;
margin-top: 20px;
}
.content h2 {
color: #e5e5e5;
margin-bottom: 15px;
}
.editor-container {
position: relative;
flex: 1;
border-radius: 4px;
background-color: #282c34;
overflow: hidden;
}
.editor-container textarea {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
padding: 12px;
border: 1px solid #3e4451;
border-radius: 4px;
resize: none;
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
font-size: 14px;
line-height: 1.5;
color: transparent;
background-color: transparent;
caret-color: #61afef;
z-index: 2;
white-space: pre;
overflow: auto;
tab-size: 2;
}
.editor-container textarea:focus {
outline: none;
border-color: #61afef;
}
.highlight-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
padding: 12px;
margin: 0;
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
font-size: 14px;
line-height: 1.5;
pointer-events: none;
overflow: auto;
z-index: 1;
white-space: pre;
tab-size: 2;
background-color: #282c34;
}
.highlight-container code {
font-family: inherit;
}
/* 滚动条样式 */
.editor-container textarea::-webkit-scrollbar,
.highlight-container::-webkit-scrollbar {
width: 8px;
height: 8px;
}
.editor-container textarea::-webkit-scrollbar-track,
.highlight-container::-webkit-scrollbar-track {
background: #21252b;
}
.editor-container textarea::-webkit-scrollbar-thumb,
.highlight-container::-webkit-scrollbar-thumb {
background: #3e4451;
border-radius: 4px;
}
.editor-container textarea::-webkit-scrollbar-thumb:hover,
.highlight-container::-webkit-scrollbar-thumb:hover {
background: #4b5363;
}
/* 保存按钮样式 */
:deep(.el-button--primary) {
background-color: #61afef;
border-color: #61afef;
}
:deep(.el-button--primary:hover) {
background-color: #528bbc;
border-color: #528bbc;
}
.slide-enter-active,
.slide-leave-active {
transition: transform 0.3s ease;
}
.slide-enter-from,
.slide-leave-to {
transform: translateX(100%);
}
</style>