feat: 重命名执行模式为编码模式等、修复全屏模式显示问题等

- 统一controller方法,使用{res}/ {res, request}参数,移除非必要的uid参数和package-lock.json文件
- 将"执行模式"改为"编码方式",避免与"脚本执行开关"(手动/自动)产生歧义
- 在脚本编辑器中新增同样的编码方式选择功能
- 修复footerbar传递的执行脚本没有判断useBase64问题
- 修复全屏状态下规则编辑对话框、颜色选择器、以及所有下拉菜单不显示的问题,现在使用Teleport将规则编辑对话框动态挂载到全屏的document.fullscreenElement中,原来的下拉菜单和颜色选择器默认也会用 teleport 挂到 body,需要设置:teleported="false"以禁用
This commit is contained in:
zhanghao-njmu
2025-10-13 15:49:08 +08:00
parent 19b5cee505
commit ea5518498f
12 changed files with 93 additions and 11158 deletions
-11088
View File
File diff suppressed because it is too large Load Diff
+10 -26
View File
@@ -20,24 +20,14 @@ const DEFAULT_TERMINAL_CONFIG = {
const terminalConfigDB = new TerminalConfigDB().getInstance()
// 获取用户的终端配置
async function getTerminalConfig(ctx) {
const { uid } = ctx.request.query
if (!uid || typeof uid !== 'string' || uid.trim() === '') {
ctx.body = { success: false, message: '无效的uid参数' }
return
}
async function getTerminalConfig({ res }) {
try {
// 查找用户配置
let userConfig = await terminalConfigDB.findOneAsync({ uid, isDefault: { $ne: true } })
let userConfig = await terminalConfigDB.findOneAsync({ isDefault: { $ne: true } })
if (!userConfig) {
// 如果没有用户配置,直接返回默认配置
userConfig = {
...DEFAULT_TERMINAL_CONFIG,
uid
}
userConfig = DEFAULT_TERMINAL_CONFIG
} else {
// 合并默认配置和用户配置
userConfig = {
@@ -46,28 +36,22 @@ async function getTerminalConfig(ctx) {
}
}
ctx.body = { success: true, data: userConfig }
res.success({ data: userConfig })
} catch (error) {
console.error('Error in getTerminalConfig:', error)
ctx.body = { success: false, message: '服务器内部错误,请稍后重试' }
res.fail({ msg: '服务器内部错误,请稍后重试' })
}
}
// 保存用户的终端配置
async function saveTerminalConfig(ctx) {
const { uid, isDefault, name, _id, createTime, ...configData } = ctx.request.body
if (!uid || typeof uid !== 'string' || uid.trim() === '') {
ctx.body = { success: false, message: '无效的uid参数' }
return
}
async function saveTerminalConfig({ res, request }) {
const { isDefault, name, _id, createTime, ...configData } = request.body
try {
const existingConfig = await terminalConfigDB.findOneAsync({ uid, isDefault: { $ne: true } })
const existingConfig = await terminalConfigDB.findOneAsync({ isDefault: { $ne: true } })
const updateData = {
...configData,
uid,
updateTime: new Date().toISOString()
}
@@ -83,10 +67,10 @@ async function saveTerminalConfig(ctx) {
await terminalConfigDB.insertAsync(updateData)
}
ctx.body = { success: true, message: '配置保存成功' }
res.success({ msg: '配置保存成功' })
} catch (error) {
console.error('Error in saveTerminalConfig:', error)
ctx.body = { success: false, message: '服务器内部错误,请稍后重试' }
res.fail({ msg: '服务器内部错误,请稍后重试' })
}
}
-11
View File
@@ -204,17 +204,6 @@ module.exports.FileTransferDB = class FileTransferDB {
}
}
module.exports.HighlightConfigDB = class HighlightConfigDB {
constructor() {
if (!HighlightConfigDB.instance) {
HighlightConfigDB.instance = new Datastore({ filename: terminalConfigDBPath, autoload: true })
}
}
getInstance() {
return HighlightConfigDB.instance
}
}
module.exports.TerminalConfigDB = class TerminalConfigDB {
constructor() {
if (!TerminalConfigDB.instance) {
-1
View File
@@ -27,7 +27,6 @@
"crypto-js": "^4.2.0",
"dotenv": "^16.4.5",
"fs-extra": "^11.2.0",
"get-intrinsic": "^1.3.0",
"global": "^4.4.0",
"iconv-lite": "^0.6.3",
"jsonwebtoken": "^9.0.2",
+2 -2
View File
@@ -159,7 +159,7 @@ const useStore = defineStore('global', {
},
async getTerminalConfig() {
try {
const { data: terminalConfig } = await $api.getTerminalConfig({ uid: this.uid })
const { data: terminalConfig } = await $api.getTerminalConfig()
this.$patch({ terminalConfig })
} catch (error) {
console.error('获取终端配置失败:', error)
@@ -185,7 +185,7 @@ const useStore = defineStore('global', {
async setTerminalSetting(setTarget = {}) {
const newConfig = { ...this.terminalConfig, ...setTarget }
try {
await $api.saveTerminalConfig({ ...newConfig, uid: this.uid })
await $api.saveTerminalConfig(newConfig)
this.$patch({ terminalConfig: newConfig })
} catch (error) {
console.error('保存终端配置失败:', error)
@@ -68,10 +68,10 @@
placeholder="shell script"
/>
</el-form-item>
<el-form-item label="执行模式" prop="useBase64">
<el-form-item label="编码方式" prop="useBase64">
<el-radio-group v-model="formData.useBase64">
<el-radio :value="false">
<span>直接执行</span>
<span>直接发送</span>
<el-tooltip placement="right">
<template #content>
<div style="max-width: 300px;">
@@ -84,12 +84,12 @@
</el-tooltip>
</el-radio>
<el-radio :value="true">
<span>Base64编码执行</span>
<span>Base64编码</span>
<el-tooltip placement="right">
<template #content>
<div style="max-width: 300px;">
适用于多行复杂脚本。<br>
脚本通过Base64编码后执行,可以避免:<br>
脚本通过Base64编码后发送,可以避免:<br>
• 特殊字符转义问题<br>
• heredoc标记冲突<br>
• 换行符兼容问题<br>
+4 -4
View File
@@ -68,7 +68,7 @@
<el-table-column prop="name" label="名称" />
<el-table-column prop="description" label="描述" />
<el-table-column prop="command" label="指令内容" show-overflow-tooltip />
<el-table-column prop="useBase64" label="执行模式" width="140px" :formatter="formatExecutionMode" />
<el-table-column prop="useBase64" label="编码方式" width="140px" :formatter="formatExecutionMode" />
<el-table-column label="操作" fixed="right" width="160px">
<template #default="{ row }">
<template v-if="row.index !== '--'">
@@ -239,12 +239,12 @@ const handleEditSuccess = () => {
currentScript.value = {}
}
// 格式化执行模式显示
// 格式化编码方式显示
const formatExecutionMode = (row, column, cellValue) => {
if (cellValue === true) {
return 'Base64编码执行'
return 'Base64编码'
}
return '直接执行'
return '直接发送'
}
</script>
@@ -1,11 +1,13 @@
<template>
<el-dialog
v-model="dialogVisible"
title="编辑高亮规则"
width="900px"
:close-on-click-modal="false"
:before-close="handleBeforeClose"
>
<Teleport :to="teleportTarget">
<el-dialog
v-model="dialogVisible"
title="编辑高亮规则"
width="900px"
:append-to-body="false"
:close-on-click-modal="false"
:before-close="handleBeforeClose"
>
<div class="rule-edit">
<el-form
ref="formRef"
@@ -30,6 +32,7 @@
<el-color-picker
v-model="formData.displayColor"
size="small"
:teleported="false"
:predefine="getTextColorPredefines()"
@change="handleColorChange"
/>
@@ -40,6 +43,7 @@
v-model="formData.backgroundColor"
size="small"
show-alpha
:teleported="false"
:predefine="getBackgroundColorPredefines()"
@change="handleBackgroundColorChange"
/>
@@ -123,7 +127,8 @@
<el-button type="primary" @click="handleSave">保存</el-button>
</div>
</template>
</el-dialog>
</el-dialog>
</Teleport>
</template>
<script setup>
@@ -132,6 +137,11 @@ import { HIGHLIGHT_RULES, TerminalHighlighter } from '@/utils/highlighter'
const { proxy: { $message, $messageBox } } = getCurrentInstance()
// 全屏时teleport到fullscreenElement,否则teleport到body
const teleportTarget = computed(() => {
return document.fullscreenElement || 'body'
})
// 检查颜色是否已被使用
const isColorUsed = (color, rules, excludeKey = null) => {
const usedColors = Object.entries(rules)
@@ -92,6 +92,27 @@
:rows="10"
placeholder="请输入脚本内容..."
/>
<!-- 脚本编辑器中的脚本编码模式选项 -->
<div class="execution_mode_selector">
<el-tooltip placement="top">
<template #content>
<div style="max-width: 300px;">
<strong>直接发送:</strong>适用于单行简单脚本,内容直接发送到终端<br><br>
<strong>Base64编码:</strong>适用于多行复杂脚本,通过Base64编码后发送,可避免特殊字符转义、heredoc标记冲突等问题
</div>
</template>
<el-select
v-model="editorUseBase64"
size="small"
style="width: 100px;"
:teleported="false"
>
<el-option :value="false" label="直接发送" />
<el-option :value="true" label="Base64编码" />
</el-select>
</el-tooltip>
</div>
<div class="action_btn">
<el-dropdown
split-button
@@ -99,6 +120,7 @@
type="primary"
size="small"
:disabled="!scriptContent"
:teleported="false"
@click="handleSendToTerminal"
>
<template #dropdown>
@@ -155,6 +177,7 @@ const scriptEditVisible = ref(false)
const editScriptData = ref(null)
const scriptGroupVisible = ref(false)
const isClearContent = ref(false)
const editorUseBase64 = ref(false)
const isPlusActive = computed(() => $store.isPlusActive)
const scriptGroupList = computed(() => $store.scriptGroupList || [])
@@ -175,7 +198,7 @@ const handleSelectGroup = (group) => {
const handleExecScript = (script) => {
if (!isPlusActive.value) return $message.warning('此功能仅限PLUS版使用')
emit('exec-command', script.command)
emit('exec-command', { command: script.command, useBase64: script.useBase64 || false })
}
const handleEditScript = (script) => {
@@ -191,7 +214,7 @@ const handleSendToEditor = (script) => {
const handleSendToTerminal = () => {
if (!isPlusActive.value) return $message.warning('此功能仅限PLUS版使用')
emit('exec-command', scriptContent.value)
emit('exec-command', { command: scriptContent.value, useBase64: editorUseBase64.value })
if (isClearContent.value) {
scriptContent.value = ''
}
@@ -490,6 +513,15 @@ if (scriptGroupList.value.length) {
}
}
.execution_mode_selector {
position: absolute;
right: 140px;
bottom: 20px;
z-index: 1;
display: flex;
gap: 10px;
}
.action_btn {
position: absolute;
right: 20px;
@@ -16,7 +16,7 @@
:show-message="false"
>
<el-form-item label="终端主题" prop="theme">
<el-select v-model="theme" placeholder="" style="width: 100%;">
<el-select v-model="theme" placeholder="" style="width: 100%;" :teleported="false">
<el-option
v-for="(value, key) in themeList"
:key="key"
@@ -26,7 +26,7 @@
</el-select>
</el-form-item>
<el-form-item label="终端字体" prop="fontFamily">
<el-select v-model="fontFamily" placeholder="选择字体" style="width: 100%;">
<el-select v-model="fontFamily" placeholder="选择字体" style="width: 100%;" :teleported="false">
<el-option
v-for="font in commonFonts"
:key="font.value"
@@ -47,6 +47,7 @@
<el-color-picker
v-model="customFontColor"
show-alpha
:teleported="false"
:predefine="predefineColors"
@change="handleColorChange"
/>
@@ -62,6 +63,7 @@
<el-color-picker
v-model="customCursorColor"
show-alpha
:teleported="false"
:predefine="predefineColors"
@change="handleCursorColorChange"
/>
@@ -74,6 +76,7 @@
<el-color-picker
v-model="selectionColor"
show-alpha
:teleported="false"
:predefine="predefineColors"
/>
</div>
@@ -789,20 +789,26 @@ const handleLinkHost = (hostDescObj) => {
}, 100)
}
// scriptDescObj: 脚本库对象或脚本命令
// scriptDescObj: 脚本库对象、脚本命令或者包含command和useBase64的对象
const handleExecScript = async (scriptDescObj) => {
if (!scriptDescObj) return // clearCheckedNodes二次触发change事件
let command = ''
let useBase64 = false
const id = Array.isArray(scriptDescObj) ? scriptDescObj.slice(-1)[0] : scriptDescObj?.id
if (id) {
const script = scriptList.value.find((item) => item.id === id)
command = script?.command
useBase64 = script?.useBase64 || false // 读取脚本的base64配置
// 处理从footerbar传来的对象格式{command,useBase64}
if (scriptDescObj.command !== undefined) {
command = scriptDescObj.command
useBase64 = scriptDescObj.useBase64 || false
} else {
command = scriptDescObj
useBase64 = false // 外部传入的命令默认不用base64
const id = Array.isArray(scriptDescObj) ? scriptDescObj.slice(-1)[0] : scriptDescObj?.id
if (id) {
const script = scriptList.value.find((item) => item.id === id)
command = script?.command
useBase64 = script?.useBase64 || false // 读取脚本的base64配置
} else {
command = scriptDescObj
useBase64 = false // 外部传入的命令默认不用base64
}
}
if (!command) return $message.warning('未找到对应的脚本')
@@ -768,7 +768,7 @@ const inputCommand = (command, type = 'input', useBase64 = false) => {
const encodedScript = btoa(String.fromCharCode(...utf8Bytes))
command = `echo '${ encodedScript }' | base64 -d | bash${ autoExecuteScript.value ? '\n' : '' }`
} else {
// 直接执行模式:根据配置添加换行符
// 直接发送模式:根据脚本执行模式添加换行符
command = command + (autoExecuteScript.value ? '\n' : '')
}
}
@@ -788,7 +788,7 @@ const execExternalCommand = (command, useBase64 = false) => {
const encodedScript = btoa(String.fromCharCode(...utf8Bytes))
command = `echo '${ encodedScript }' | base64 -d | bash${ autoExecuteScript.value ? '\n' : '' }`
} else {
// 直接执行模式:根据配置添加换行符
// 直接发送模式:根据脚本执行模式添加换行符
command = command + (autoExecuteScript.value ? '\n' : '')
}