重构CommandEditor组件

This commit is contained in:
fofolee
2025-02-15 11:28:26 +08:00
parent 9ffa941e72
commit c6b511696a
22 changed files with 1038 additions and 1776 deletions

View File

@@ -1,168 +1,231 @@
<template>
<div class="row" v-show="!!height">
<div class="col">
<div>
<q-select
dense
standout="bg-primary text-white"
square
hide-bottom-space
color="primary"
transition-show="jump-down"
transition-hide="jump-up"
@update:model-value="updateProgram"
:model-value="modelValue.program"
:options="programLanguages"
<div class="command-language-bar">
<q-select
class="q-pl-xs"
dense
options-dense
borderless
square
hide-bottom-space
color="primary"
transition-show="jump-down"
transition-hide="jump-up"
:model-value="currentCommand.program"
@update:model-value="handleProgramChange"
:options="Object.keys(programs)"
>
<template v-slot:prepend>
<q-badge
label="环境"
>
<template v-slot:append>
<q-avatar size="lg" square>
<img :src="programs[modelValue.program].icon" />
</q-avatar>
</template>
<template v-slot:option="scope">
<q-item v-bind="scope.itemProps">
<q-item-section avatar>
<img width="32" :src="programs[scope.opt].icon" />
</q-item-section>
<q-item-section>
<q-item-label v-html="scope.opt" />
</q-item-section>
</q-item>
</template>
</q-select>
</div>
</div>
<q-separator vertical />
<div class="col-auto justify-end flex">
<q-btn-group unelevated class="button-group">
<template v-if="modelValue.program === 'quickcommand'">
<q-btn
v-for="(item, index) in [
'help_center',
'view_timeline',
]"
color="primary"
text-color="white"
class="q-ml-sm"
style="height: 20px"
/>
</template>
<template v-slot:append>
<q-avatar size="20px" square v-if="isRunCodePage">
<img :src="programs[currentCommand.program].icon" />
</q-avatar>
</template>
<template v-slot:option="scope">
<q-item v-bind="scope.itemProps">
<q-item-section avatar>
<img width="32" :src="programs[scope.opt].icon" />
</q-item-section>
<q-item-section>
<q-item-label v-html="scope.opt" />
</q-item-section>
</q-item>
</template>
</q-select>
<q-btn-group unelevated class="button-group">
<q-btn-dropdown
class="special-var-btn"
dense
flat
label="变量"
color="primary"
icon="data_object"
>
<q-list>
<q-item
v-for="(item, index) in Object.values(specialVars)"
:key="index"
dense
flat
color="primary"
class="settings-btn"
:icon="item"
@click="handleQuickCommandAction(index)"
clickable
v-close-popup
@click="handleSpecialVarClick(item)"
>
<q-tooltip>
{{ ["查看文档", "可视化编排"][index] }}
</q-tooltip>
</q-btn>
</template>
<q-item-section>
<q-item-label class="row items-center justify-between">
<div v-text="item.label" />
<div v-if="item.onlyCmdTypes" class="row">
<q-badge color="grey-9" class="q-ml-xs"> </q-badge>
<q-badge
v-for="type in item.onlyCmdTypes"
:key="type"
class="q-ml-xs"
v-text="commandTypes[type].label"
color="grey-9"
/>
</div>
</q-item-label>
<q-tooltip v-if="item.tooltip">
{{ item.tooltip }}
</q-tooltip>
<q-item-label caption>{{ item.desc }}</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-btn-dropdown>
<q-btn-dropdown
v-else-if="modelValue.program !== 'html'"
<template v-if="currentCommand.program === 'quickcommand'">
<q-btn
v-for="(item, index) in ['help_center', 'view_timeline']"
:key="index"
dense
flat
color="primary"
class="settings-btn"
dense
flat
ref="settings"
color="primary"
icon="settings"
:label="['文档', '可视化'][index]"
:icon="item"
@click="handleQuickCommandAction(index)"
>
<q-list>
<!-- 自定义解释器 -->
<q-item
v-for="(item, index) in Object.keys(modelValue.customOptions)"
:key="index"
v-show="modelValue.program === 'custom'"
</q-btn>
</template>
<q-btn-dropdown
v-model="isSettingsVisible"
v-else-if="currentCommand.program !== 'html'"
class="settings-btn"
dense
flat
label="设置"
color="primary"
icon="settings"
>
<q-list>
<!-- 自定义解释器 -->
<q-item
v-for="(item, index) in Object.keys(currentCommand.customOptions)"
:key="index"
v-show="currentCommand.program === 'custom'"
>
<q-input
stack-label
autofocus
dense
outlined
class="full-width"
:label="
[
'解释器路径,如:/opt/python',
'运行参数,如:-u',
'脚本后缀不含点py',
][index]
"
:model-value="currentCommand.customOptions[item]"
@update:model-value="
(val) => updateModelValue(`customOptions.${item}`, val)
"
>
<q-input
stack-label
autofocus
dense
outlined
class="full-width"
@blur="matchLanguage"
:label="
[
'解释器路径,如:/opt/python',
'运行参数,如:-u',
'脚本后缀不含点py',
][index]
"
:model-value="modelValue.customOptions[item]"
@update:model-value="(val) => updateCustomOption(item, val)"
>
<template v-slot:prepend>
<q-icon name="code" />
</template>
</q-input>
</q-item>
<!-- 脚本参数 -->
<q-item v-show="modelValue.program !== 'quickcommand'">
<q-input
dense
stack-label
outlined
label="脚本参数"
class="full-width"
:model-value="modelValue.scptarg"
@update:model-value="updateScptarg"
>
<template v-slot:prepend>
<q-icon name="input" />
</template>
</q-input>
</q-item>
<!-- 编码设置 -->
<q-item
v-for="(item, index) in Object.keys(modelValue.charset)"
:key="index"
v-show="modelValue.program !== 'quickcommand'"
<template v-slot:prepend>
<q-icon name="code" />
</template>
</q-input>
</q-item>
<!-- 脚本参数 -->
<q-item v-show="currentCommand.program !== 'quickcommand'">
<q-input
dense
stack-label
outlined
label="脚本参数"
class="full-width"
:model-value="currentCommand.scptarg"
@update:model-value="updateModelValue('scptarg', $event)"
>
<q-select
dense
outlined
stack-label
clearable
class="full-width"
:label="['脚本编码', '输出编码'][index]"
:model-value="modelValue.charset[item]"
@update:model-value="(val) => updateCharset(item, val)"
:options="['GBK', 'utf8', 'Big5']"
type="text"
>
<template v-slot:prepend>
<q-icon :name="['format_size', 'output'][index]" />
</template>
</q-select>
</q-item>
</q-list>
</q-btn-dropdown>
<q-separator vertical inset />
<q-btn
class="action-btn run-btn"
dense
flat
color="primary"
icon="play_arrow"
label="运行"
@click="$emit('run')"
></q-btn>
<q-btn
class="action-btn save-btn"
flat
dense
v-if="!isRunCodePage"
:disable="!canCommandSave"
:color="canCommandSave ? 'primary' : 'grey'"
icon="save"
label="保存"
@click="$emit('save')"
></q-btn>
</q-btn-group>
</div>
<template v-slot:prepend>
<q-icon name="input" />
</template>
</q-input>
</q-item>
<!-- 编码设置 -->
<q-item
v-for="(item, index) in Object.keys(currentCommand.charset)"
:key="index"
v-show="currentCommand.program !== 'quickcommand'"
>
<q-select
dense
outlined
stack-label
clearable
class="full-width"
:label="['脚本编码', '输出编码'][index]"
:model-value="currentCommand.charset[item]"
@update:model-value="
(val) => updateModelValue(`charset.${item}`, val)
"
:options="['GBK', 'utf8', 'Big5']"
type="text"
>
<template v-slot:prepend>
<q-icon :name="['format_size', 'output'][index]" />
</template>
</q-select>
</q-item>
</q-list>
</q-btn-dropdown>
<q-separator vertical inset />
<q-btn
v-if="!isRunCodePage"
class="action-btn run-btn"
dense
flat
color="primary"
icon="arrow_back"
label="退出"
@click="$emit('action', 'back')"
></q-btn>
<q-btn
class="action-btn run-btn"
dense
flat
color="primary"
icon="play_arrow"
label="运行"
@click="$emit('action', 'run')"
></q-btn>
<q-btn
v-if="!isRunCodePage"
:disable="!canCommandSave"
:color="canCommandSave ? 'primary' : 'grey'"
class="action-btn save-btn"
flat
dense
icon="save"
label="保存"
@click="$emit('action', 'save')"
></q-btn>
</q-btn-group>
<q-dialog v-model="showUserData">
<UserData
@insertText="
insertSpecialVar($event);
showUserData = false;
"
:showInsertBtn="true"
/>
</q-dialog>
</div>
</template>
<script>
import programs from "js/options/programs.js";
import specialVars from "js/options/specialVars.js";
import commandTypes from "js/options/commandTypes.js";
import UserData from "components/popup/UserData.vue";
export default {
name: "CommandLanguageBar",
@@ -171,10 +234,6 @@ export default {
type: Object,
required: true,
},
height: {
type: Number,
default: 40,
},
canCommandSave: {
type: Boolean,
default: true,
@@ -184,149 +243,121 @@ export default {
default: false,
},
},
emits: [
"update:modelValue",
"program-changed",
"run",
"save",
"show-composer",
],
emits: ["update:modelValue", "action"],
components: {
UserData,
},
data() {
return {
programs,
specialVars,
commandTypes,
isSettingsVisible: false,
showUserData: false,
};
},
computed: {
programLanguages() {
return Object.keys(this.programs);
currentCommand() {
return this.modelValue;
},
},
methods: {
updateProgram(value) {
this.$emit("update:modelValue", {
...this.modelValue,
program: value,
});
this.programChanged(value);
},
updateCustomOption(key, value) {
this.$emit("update:modelValue", {
...this.modelValue,
customOptions: {
...this.modelValue.customOptions,
[key]: value,
},
});
},
updateScptarg(value) {
this.$emit("update:modelValue", {
...this.modelValue,
scptarg: value,
});
},
updateCharset(key, value) {
this.$emit("update:modelValue", {
...this.modelValue,
charset: {
...this.modelValue.charset,
[key]: value,
},
});
},
programChanged(value) {
this.$emit("program-changed", value);
if (value === "custom") {
this.$refs.settings.show();
handleProgramChange(newProgram) {
const newCommand = { ...this.currentCommand };
newCommand.program = newProgram;
if (newProgram === "custom") {
this.isSettingsVisible = true;
}
},
matchLanguage() {
if (!this.modelValue.customOptions.ext) return;
let language = Object.values(this.programs).filter(
(program) => program.ext === this.modelValue.customOptions.ext
);
if (language.length) {
this.$emit("program-changed", language[0].name);
if (newProgram === "html") {
newCommand.output = "html";
}
const featuresIcon = this.currentCommand.features.icon || "";
if (featuresIcon.slice(0, 10) !== "data:image") {
newCommand.features.icon = this.programs[newProgram].icon;
}
this.$emit("update:modelValue", newCommand);
},
updateModelValue(keyPath, value) {
const newModelValue = { ...this.modelValue };
const keys = keyPath.split(".");
const lastKey = keys.pop();
const target = keys.reduce((obj, key) => obj[key], newModelValue);
target[lastKey] = value;
this.$emit("update:modelValue", newModelValue);
},
handleQuickCommandAction(index) {
const actions = [
() => this.showHelp(),
() => this.$emit("show-composer"),
() => this.$emit("action", "show-composer"),
];
actions[index]();
},
showHelp() {
window.showUb.docs();
},
handleSpecialVarClick(item) {
if (item.label === "{{usr:}}") this.showUserData = true;
else this.insertSpecialVar(item.label);
},
insertSpecialVar(text) {
if (!text) return;
this.$emit("action", "insert-text", `"${text}"`);
},
},
};
</script>
<style scoped>
.button-group {
.action-btn {
padding: 0 10px;
}
padding: 0 5px;
}
.button-group :deep(.q-focus-helper) {
display: none;
}
.button-group :deep(.q-btn__content) {
font-size: 12px;
}
.button-group :deep(.q-btn-dropdown__arrow) {
margin-left: 0;
}
.button-group .q-btn:hover {
filter: brightness(1.2);
transition: all 0.2s ease;
}
/* 运行按钮动画 */
.run-btn:hover :deep(.q-icon) {
display: inline-block;
animation: slideRight 1.5s infinite;
animation: leftRight 1.5s infinite;
}
/* 保存按钮动画 */
.save-btn:not([disabled]):hover :deep(.q-icon) {
display: inline-block;
animation: saveAnimation 1.2s infinite;
animation: upDown 1.2s infinite;
}
/* 设置按钮动画 */
.settings-btn :deep(.q-icon:first-child) {
display: inline-block;
transform: scale(1);
transition: transform 0.5s ease;
.command-language-bar {
background-color: #fffffe;
height: 30px;
margin-bottom: 2px;
display: flex;
align-items: center;
justify-content: space-between;
}
.settings-btn:hover :deep(.q-icon:first-child) {
transform: scale(1.05);
.body--dark .command-language-bar {
background-color: #1e1e1e;
}
@keyframes slideRight {
0% {
transform: translateX(-2px);
opacity: 0.7;
}
50% {
transform: translateX(2px);
opacity: 1;
}
100% {
transform: translateX(-2px);
opacity: 0.7;
}
}
@keyframes saveAnimation {
0% {
transform: translateY(-1px);
opacity: 1;
}
50% {
transform: translateY(1px);
opacity: 0.6;
}
75% {
transform: translateY(0.5px);
opacity: 0.8;
}
100% {
transform: translateY(-1px);
opacity: 1;
}
}
.settings-btn:hover :deep(.q-icon) {
display: inline-block;
.command-language-bar :deep(.q-field__control),
.command-language-bar :deep(.q-field__control > *),
.command-language-bar :deep(.q-field__native) {
max-height: 30px;
min-height: 30px;
}
</style>