2025-02-21 19:52:33 +08:00
|
|
|
<script setup>
|
|
|
|
|
2025-02-22 19:21:25 +08:00
|
|
|
import GeneralRenderer from "../components/GeneralRenderer.vue";
|
|
|
|
import {onMounted, onUnmounted, ref, watch} from "vue";
|
|
|
|
import store from "../store/index.js";
|
|
|
|
import swal from "../utils/sweetalert.js";
|
|
|
|
import getCurrentTime from "../utils/getCurrentTime.js";
|
|
|
|
|
|
|
|
const contentInput = ref(store.state.editStore.blog || '');
|
|
|
|
const titleInput = ref(store.state.editStore.blogTitle || '')
|
|
|
|
|
|
|
|
const portMode = ref('both');
|
|
|
|
const windowWidth = ref(0);
|
|
|
|
const isMobileMode = ref(false);
|
|
|
|
const isMenuOpen = ref(false);
|
|
|
|
|
|
|
|
const funcButtons = ref([
|
|
|
|
{name: 'h1', func: '# [cur]'},
|
|
|
|
{name: 'h2', func: '## [cur]'},
|
|
|
|
{name: 'h3', func: '### [cur]'},
|
|
|
|
{name: '<s>abc</s>', func: '~~[cur]~~'},
|
|
|
|
{name: '<b>abc</b>', func: '**[cur]**'},
|
|
|
|
{name: '<i>abc</i>', func: '*[cur]*'},
|
|
|
|
{name: '<code>abc</code>', func: '\`[cur]\`'},
|
|
|
|
{name: '●', func: '- '},
|
|
|
|
{name: 'url', func: '[[cur]](https://example.com)'},
|
|
|
|
{name: 'img', func: ''},
|
|
|
|
{name: 'mth', func: '$[cur]$'},
|
|
|
|
{name: 'Mth', func: '$$[cur]$$'},
|
|
|
|
])
|
|
|
|
function clickFuncBtn(func) {
|
|
|
|
const textarea = document.querySelector('textarea'); // 获取 textarea 元素
|
|
|
|
const startPos = textarea.selectionStart; // 获取焦点的起始位置
|
|
|
|
const endPos = textarea.selectionEnd; // 获取焦点的结束位置
|
|
|
|
const selectedText = textarea.value.slice(startPos, endPos); // 获取选中的文本
|
2025-02-24 22:16:59 +08:00
|
|
|
let newText;
|
2025-02-22 19:21:25 +08:00
|
|
|
if (selectedText) {
|
|
|
|
newText = func.replace('[cur]', selectedText);
|
|
|
|
} else {
|
|
|
|
newText = func.replace('[cur]', '请在此填写内容');
|
|
|
|
}
|
|
|
|
textarea.setRangeText(newText, startPos, endPos, 'select'); // 替换选中的文本
|
|
|
|
contentInput.value = textarea.value;
|
|
|
|
const curPos = textarea.value.indexOf('[cur]');
|
|
|
|
textarea.selectionStart = curPos;
|
|
|
|
textarea.selectionEnd = curPos;
|
|
|
|
textarea.focus();
|
|
|
|
}
|
|
|
|
const checkWindowSize = () => {
|
|
|
|
windowWidth.value = window.innerWidth;
|
|
|
|
if (windowWidth.value < 705) {
|
|
|
|
isMobileMode.value = true;
|
|
|
|
} else {
|
|
|
|
isMobileMode.value = false;
|
|
|
|
isMenuOpen.value = false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const saveDocument = () => {
|
|
|
|
store.commit('saveEdit', {
|
|
|
|
blog: contentInput.value,
|
|
|
|
blogTitle: titleInput.value,
|
|
|
|
blogSaveTime: getCurrentTime()
|
|
|
|
});
|
|
|
|
swal.tip('success', '保存成功')
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleKeydown = (event) => {
|
|
|
|
// 检查是否按下了 Ctrl + S
|
|
|
|
if (event.ctrlKey && event.key === 's') {
|
|
|
|
event.preventDefault(); // 阻止浏览器默认的保存行为
|
|
|
|
saveDocument(); // 调用保存函数
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
watch(portMode, async () => {
|
|
|
|
setTimeout(() => {
|
|
|
|
contentInput.value = contentInput.value + ' ';
|
|
|
|
}, 1)
|
|
|
|
setTimeout(() => {
|
|
|
|
contentInput.value = contentInput.value.slice(0, -1);
|
|
|
|
}, 2)
|
|
|
|
});
|
|
|
|
|
|
|
|
watch(contentInput, () => {
|
|
|
|
if (store.state.editAutoSave.on && store.state.editAutoSave.interval === 114514) {
|
|
|
|
store.commit('saveEdit', {
|
|
|
|
blog: contentInput.value,
|
|
|
|
blogTitle: titleInput.value,
|
|
|
|
blogSaveTime: getCurrentTime()
|
|
|
|
});
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2025-02-24 15:52:56 +08:00
|
|
|
watch(titleInput, () => {
|
|
|
|
if (store.state.editAutoSave.on && store.state.editAutoSave.interval === 114514) {
|
|
|
|
store.commit('saveEdit', {
|
|
|
|
blog: contentInput.value,
|
|
|
|
blogTitle: titleInput.value,
|
|
|
|
blogSaveTime: getCurrentTime()
|
|
|
|
});
|
|
|
|
}
|
|
|
|
})
|
2025-02-22 19:21:25 +08:00
|
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
checkWindowSize();
|
|
|
|
window.addEventListener('resize', checkWindowSize);
|
|
|
|
window.addEventListener('keydown', handleKeydown);
|
|
|
|
autoSave = setInterval(()=>{
|
|
|
|
if (! store.state.editAutoSave.on || store.state.editAutoSave.interval === 114514) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
store.commit('saveEdit', {
|
|
|
|
blog: contentInput.value,
|
|
|
|
blogTitle: titleInput.value,
|
|
|
|
blogSaveTime: getCurrentTime()
|
|
|
|
});
|
|
|
|
}, store.state.editAutoSave.interval);
|
|
|
|
});
|
|
|
|
|
|
|
|
let autoSave
|
|
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
|
clearInterval(autoSave);
|
2025-02-24 22:16:59 +08:00
|
|
|
autoSave = undefined;
|
2025-02-22 19:21:25 +08:00
|
|
|
window.removeEventListener('keydown', handleKeydown);
|
|
|
|
});
|
2025-02-21 19:52:33 +08:00
|
|
|
</script>
|
|
|
|
|
|
|
|
<template>
|
2025-02-24 22:16:59 +08:00
|
|
|
<div class="container" :class="{'compact-form': isMobileMode}">
|
2025-02-22 19:21:25 +08:00
|
|
|
<div class="header">
|
|
|
|
<input placeholder="输入标题" v-model="titleInput">
|
|
|
|
</div>
|
|
|
|
<div class="top">
|
2025-02-22 19:32:46 +08:00
|
|
|
<div class="function-btn" :style="{flex: isMobileMode?1:3}">
|
2025-02-22 19:21:25 +08:00
|
|
|
<button v-if="! isMobileMode" v-for="btn in funcButtons" v-html="btn.name" @click="clickFuncBtn(btn.func)"/>
|
|
|
|
<button v-if="isMobileMode" @click="isMenuOpen = ! isMenuOpen">☰</button>
|
|
|
|
<div v-if="isMobileMode && isMenuOpen" class="function-btn-menu">
|
|
|
|
<button v-for="btn in funcButtons" v-html="btn.name" @click="clickFuncBtn(btn.func)"/>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="port-btn">
|
|
|
|
<button @click="portMode = 'both'" :class="{onMode: portMode === 'both'}">双</button>
|
|
|
|
<button @click="portMode = 'edit'" :class="{onMode: portMode === 'edit'}">编</button>
|
|
|
|
<button @click="portMode = 'view'" :class="{onMode: portMode === 'view'}">看</button>
|
|
|
|
</div>
|
|
|
|
<div class="doc-btn">
|
|
|
|
<button @click="saveDocument">保存</button>
|
|
|
|
<button>提交</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
2025-02-22 19:48:17 +08:00
|
|
|
<div class="middle">
|
2025-02-22 19:21:25 +08:00
|
|
|
<div v-if="portMode !== 'view'" class="left">
|
|
|
|
<textarea v-model="contentInput"></textarea>
|
|
|
|
</div>
|
|
|
|
<div v-if="portMode !== 'edit'" class="right">
|
|
|
|
<GeneralRenderer :content-input="contentInput"/>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="bottom">
|
|
|
|
<div class="characters">总字符数: {{ contentInput.length }}</div>
|
|
|
|
<div class="auto-save-switch" @click="store.commit('toggleAutoSave')">自动保存: {{ store.state.editAutoSave.on ? '开' : '关' }} </div>
|
2025-02-24 15:52:56 +08:00
|
|
|
<div class="save-time-display">{{ store.state.editStore.blogSaveTime ? `上次保存 [${store.state.editStore.blogSaveTime}` : ''}}]</div>
|
2025-02-22 19:21:25 +08:00
|
|
|
</div>
|
2025-02-21 19:52:33 +08:00
|
|
|
</div>
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<style scoped>
|
2025-02-22 19:21:25 +08:00
|
|
|
.container {
|
|
|
|
background: #131313;
|
|
|
|
width: calc(100% - 40px);
|
|
|
|
height: calc(100vh - 100px);
|
|
|
|
padding: 20px;
|
|
|
|
max-width: none;
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
align-items: center;
|
|
|
|
}
|
2025-02-24 22:16:59 +08:00
|
|
|
.container.compact-form {
|
|
|
|
width: 100%;
|
|
|
|
height: calc(100vh - 60px);
|
|
|
|
padding: 0;
|
|
|
|
}
|
2025-02-22 19:21:25 +08:00
|
|
|
.theme-light .container {
|
|
|
|
background: #e0e0e0;
|
|
|
|
}
|
|
|
|
.header {
|
|
|
|
flex: 0 0 60px;
|
|
|
|
width: 100%;
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
}
|
|
|
|
.header input {
|
|
|
|
border: none;
|
|
|
|
outline: none;
|
|
|
|
padding: 15px;
|
|
|
|
width: 100%;
|
|
|
|
height: calc(100% - 30px);
|
|
|
|
font-size: initial;
|
|
|
|
background: #2a2a2a;
|
|
|
|
color: white;
|
|
|
|
}
|
2025-02-24 22:16:59 +08:00
|
|
|
|
2025-02-22 19:21:25 +08:00
|
|
|
.theme-light .header input {
|
|
|
|
background: white;
|
|
|
|
color: black;
|
|
|
|
}
|
|
|
|
.top {
|
|
|
|
flex: 0 0 40px;
|
|
|
|
width: 100%;
|
|
|
|
background: #3d3d3d;
|
|
|
|
display: flex;
|
|
|
|
flex-direction: row;
|
|
|
|
gap: 10px;
|
|
|
|
align-content: space-between;
|
|
|
|
}
|
|
|
|
.theme-light .top {
|
|
|
|
background: #f1f1f1;
|
|
|
|
}
|
|
|
|
.function-btn {
|
|
|
|
flex: 2;
|
|
|
|
display: flex;
|
|
|
|
flex-direction: row;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: flex-start;
|
|
|
|
margin-left: 5px;
|
|
|
|
gap: 5px;
|
|
|
|
}
|
|
|
|
.function-btn-menu {
|
|
|
|
position: absolute;
|
|
|
|
display: flex;
|
|
|
|
flex-direction: row;
|
|
|
|
flex-wrap: wrap;
|
|
|
|
max-height: 100px;
|
|
|
|
gap: 3px;
|
|
|
|
padding: 3px;
|
|
|
|
top: 190px;
|
|
|
|
left: 30px;
|
|
|
|
//width: 100px;
|
|
|
|
//height: 50px;
|
|
|
|
background: rgba(0, 0, 0, 0.12);
|
|
|
|
}
|
|
|
|
|
|
|
|
.port-btn {
|
|
|
|
flex: 1;
|
|
|
|
display: flex;
|
|
|
|
flex-direction: row;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: center;
|
|
|
|
gap: 5px;
|
|
|
|
}
|
|
|
|
.doc-btn {
|
|
|
|
flex: 1;
|
|
|
|
display: flex;
|
|
|
|
flex-direction: row;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: flex-end;
|
|
|
|
margin-right: 5px;
|
|
|
|
gap: 5px;
|
|
|
|
}
|
|
|
|
.doc-btn button {
|
|
|
|
width: auto !important;
|
|
|
|
}
|
|
|
|
.top button {
|
|
|
|
color: white;
|
|
|
|
background: #131313;
|
|
|
|
border: #007bff solid 2px;
|
|
|
|
width: 30px;
|
|
|
|
height: 30px;
|
|
|
|
display: flex;
|
|
|
|
justify-content: center;
|
|
|
|
align-items: center;
|
|
|
|
border-radius: 5px;
|
|
|
|
overflow: hidden;
|
|
|
|
}
|
|
|
|
.top button.onMode {
|
|
|
|
background: #007bff;
|
|
|
|
border: #007bff solid 2px;
|
|
|
|
}
|
|
|
|
.theme-light .top button {
|
|
|
|
color: black;
|
|
|
|
background: white;
|
|
|
|
border: #ffb74d solid 2px;
|
|
|
|
}
|
|
|
|
.theme-light .top button.onMode {
|
|
|
|
background: #ffb74d;
|
|
|
|
border: #ffb74d solid 2px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.bottom {
|
|
|
|
flex: 0 0 25px;
|
|
|
|
width: 100%;
|
|
|
|
background: #2a2a2a;
|
|
|
|
color: gray;
|
|
|
|
font-size: small;
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: space-between;
|
|
|
|
}
|
|
|
|
.theme-light .bottom {
|
|
|
|
background: white;
|
|
|
|
outline: gray solid 1px;
|
|
|
|
}
|
|
|
|
.bottom .characters {
|
|
|
|
margin: 0 5px;
|
|
|
|
}
|
|
|
|
.bottom .save-time-display {
|
|
|
|
margin: 0 5px;
|
|
|
|
}
|
|
|
|
.bottom .auto-save-switch:hover {
|
|
|
|
background: rgba(128, 128, 128, 0.1);
|
|
|
|
cursor: pointer;
|
|
|
|
}
|
|
|
|
.middle {
|
|
|
|
width: 100%;
|
2025-02-24 15:52:56 +08:00
|
|
|
height: 0;
|
2025-02-22 19:48:17 +08:00
|
|
|
max-height: calc(100vh - 125px);
|
2025-02-22 19:21:25 +08:00
|
|
|
flex: 1;
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
background: white;
|
|
|
|
}
|
|
|
|
|
2025-02-21 19:52:33 +08:00
|
|
|
|
2025-02-22 19:21:25 +08:00
|
|
|
.middle .left {
|
|
|
|
flex: 1;
|
|
|
|
height: 100%;
|
|
|
|
display: flex;
|
|
|
|
justify-content: center;
|
|
|
|
align-items: center;
|
|
|
|
border: rgba(128, 128, 128, 0.5) solid 1px;
|
|
|
|
}
|
|
|
|
.left textarea {
|
|
|
|
width: calc(100% - 2 * (15px));
|
|
|
|
height: calc(100% - 2 * (15px));
|
|
|
|
resize: none;
|
|
|
|
padding: 15px;
|
|
|
|
font-size: 17px;
|
|
|
|
font-family: sans-serif,serif;
|
|
|
|
border: none;
|
|
|
|
transition: all 0.2s ease;
|
|
|
|
}
|
|
|
|
.left textarea {
|
|
|
|
background: #1a1a1a;
|
|
|
|
color: white;
|
|
|
|
}
|
|
|
|
.theme-light .left textarea {
|
|
|
|
background: white;
|
|
|
|
color: black;
|
|
|
|
}
|
|
|
|
.left textarea:focus {
|
|
|
|
outline: #007bff solid 1px;
|
|
|
|
}
|
|
|
|
.theme-light .left textarea:focus {
|
|
|
|
outline: #ffb74d solid 1px;
|
|
|
|
}
|
|
|
|
.middle .right {
|
|
|
|
flex: 1;
|
|
|
|
width: calc(100% - 2 * (15px));
|
|
|
|
height: 100%;
|
|
|
|
background: #1a1a1a;
|
|
|
|
border: rgba(128, 128, 128, 0.5) solid 1px;
|
|
|
|
color: white;
|
|
|
|
overflow: auto;
|
|
|
|
padding: 0 15px;
|
|
|
|
transition: all 0.2s ease;
|
|
|
|
}
|
|
|
|
.theme-light .right {
|
|
|
|
background: white;
|
|
|
|
color: black;
|
|
|
|
}
|
2025-02-21 19:52:33 +08:00
|
|
|
</style>
|