2024-12-30 20:41:54 +08:00

740 lines
22 KiB
Vue

<template>
<div
class="command-side-bar"
:style="{
width: sideBarWidth + 'px',
'--icon-url': `url(${currentCommand.features.icon})`,
}"
>
<!-- 头部区域 -->
<div class="header-section">
<div class="header-content">
<q-btn
dense
flat
color="grey"
icon="arrow_back_ios_new"
v-close-popup
class="back-btn"
@click="$emit('back')"
/>
<div class="logo-container">
<q-avatar size="64" square class="featureIco">
<q-img
@click="showIconPicker = true"
:src="currentCommand.features.icon"
/>
</q-avatar>
</div>
</div>
</div>
<!-- 可滚动的内容区域 -->
<q-scroll-area
:thumb-style="{
width: '3px',
}"
:horizontal-thumb-style="{
height: '5px',
}"
class="scroll-area"
>
<div
class="row"
:style="{
paddingLeft: sideBarPadding + 'px',
paddingRight: sideBarPadding + 'px',
paddingBottom: sideBarPadding + 'px',
}"
>
<div class="col-12">
<div class="row">
<div
class="command-side-bar-content"
:style="{ width: sideBarWidth - sideBarPadding * 2 + 'px' }"
>
<!-- 说明 -->
<q-input
:disable="!canCommandSave"
stack-label
label-color="primary"
borderless
square
v-model="currentCommand.features.explain"
type="text"
placeholder="请输入说明"
label="说明"
>
<template v-slot:prepend>
<q-icon
class="command-side-bar-icon"
name="drive_file_rename_outline"
/>
</template>
</q-input>
<!-- 匹配类型 -->
<q-select
:disable="!canCommandSave"
popup-content-class="side-bar-popup-content"
hide-dropdown-icon
stack-label
label-color="primary"
transition-show="jump-down"
transition-hide="jump-up"
borderless
square
@update:model-value="(val) => handleCmdTypeChange(val)"
:options="commandTypesOptions"
v-model="cmdType"
type="text"
label="匹配类型"
>
<template v-slot:prepend>
<q-icon class="command-side-bar-icon" :name="cmdType.icon" />
</template>
<template v-slot:option="scope">
<q-item v-bind="scope.itemProps" class="row items-center">
<q-icon :name="scope.opt.icon" class="q-mr-md" />
<div>
<q-item-label v-html="scope.opt.name" />
<q-item-label caption>{{ scope.opt.desc }}</q-item-label>
</div>
</q-item>
</template>
</q-select>
<!-- 匹配规则 -->
<q-select
v-if="cmdType.valueType === 'array'"
:disable="!canCommandSave"
popup-content-class="side-bar-popup-content"
hide-dropdown-icon
stack-label
label-color="primary"
transition-show="jump-down"
transition-hide="jump-up"
borderless
square
v-model="cmdMatch"
max-values="3"
type="text"
placeholder="回车添加"
use-input
use-chips
multiple
new-value-mode="add-unique"
input-debounce="0"
:label="cmdType.matchLabel"
ref="cmdMatchRef"
@blur="(e) => autoAddInputVal(e, $refs.cmdMatchRef)"
>
<template v-slot:prepend>
<q-icon class="command-side-bar-icon" name="square_foot" />
</template>
</q-select>
<q-input
v-else
:disable="!canCommandSave"
autogrow
borderless
square
v-model="cmdMatch"
hide-bottom-space
@blur="regexVerify"
:readonly="!cmdType.valueType"
type="text"
:label="cmdType.matchLabel"
>
<template v-slot:prepend>
<q-icon class="command-side-bar-icon" name="square_foot" />
</template>
<template v-slot:append>
<q-icon
v-if="cmdType.name === 'files'"
name="folder"
size="xs"
:color="isFileTypeDirectory ? 'primary' : ''"
@click="isFileTypeDirectory = !isFileTypeDirectory"
style="cursor: pointer"
>
<q-tooltip>
切换匹配类型,当前:{{
isFileTypeDirectory ? "文件夹" : "文件"
}}
</q-tooltip>
</q-icon>
</template>
</q-input>
<!-- 标签 -->
<q-select
:disable="!canCommandSave"
hide-dropdown-icon
stack-label
popup-content-class="side-bar-popup-content"
label-color="primary"
transition-show="jump-down"
transition-hide="jump-up"
borderless
square
v-model="currentCommand.tags"
max-values="3"
type="text"
label="标签"
placeholder="回车添加"
use-input
use-chips
multiple
new-value-mode="add-unique"
@new-value="tagVerify"
input-debounce="0"
:options="allQuickCommandTags"
ref="commandTagRef"
@blur="(e) => autoAddInputVal(e, $refs.commandTagRef)"
>
<template v-slot:prepend>
<q-icon class="command-side-bar-icon" name="label" />
</template>
</q-select>
<!-- 特殊变量 -->
<q-select
:disable="!canCommandSave"
hide-dropdown-icon
popup-content-class="side-bar-popup-content"
stack-label
label-color="primary"
transition-show="jump-down"
transition-hide="jump-up"
borderless
@popup-hide="
() => {
if (specialVar.label === '{{usr:}}') showUserData = true;
else insertSpecialVar(specialVar.label);
}
"
square
:options="specialVarsOptions"
v-model="specialVar"
input-debounce="0"
type="text"
label="特殊变量"
>
<template v-slot:prepend>
<q-icon class="command-side-bar-icon" name="attach_money" />
</template>
<template v-slot:option="scope">
<q-item v-bind="scope.itemProps">
<q-item-section>
<q-item-label v-html="scope.opt.label" />
<q-tooltip v-if="scope.opt.tooltip">
{{ scope.opt.tooltip }}
</q-tooltip>
<q-item-label caption>{{ scope.opt.desc }}</q-item-label>
</q-item-section>
</q-item>
</template></q-select
>
<!-- 输出 -->
<q-select
:disable="!canCommandSave"
hide-dropdown-icon
stack-label
label-color="primary"
popup-content-class="side-bar-popup-content"
transition-show="jump-down"
transition-hide="jump-up"
borderless
square
color="primary"
v-model="currentCommand.output"
:display-value="outputTypes[currentCommand.output].label"
:options="outputTypesOptionsDy"
label="输出"
>
<template v-slot:prepend>
<q-icon
class="command-side-bar-icon"
:name="outputTypes[currentCommand.output].icon"
/>
</template>
<template v-slot:option="scope">
<q-item v-bind="scope.itemProps" class="row items-center">
<q-icon
:name="outputTypes[scope.opt].icon"
class="q-mr-md"
/>
<div>
<q-item-label v-html="outputTypes[scope.opt].label" />
</div>
</q-item>
</template>
</q-select>
<!-- 搜索面板推送 -->
<q-select
:disable="!canCommandSave"
hide-dropdown-icon
stack-label
label-color="primary"
popup-content-class="side-bar-popup-content"
transition-show="jump-down"
transition-hide="jump-up"
borderless
square
v-model="searchPushValue"
:options="searchPushOptions"
label="搜索面板推送"
>
<template v-slot:prepend>
<q-icon class="command-side-bar-icon" name="search" />
</template>
<template v-slot:option="scope">
<q-item v-bind="scope.itemProps">
<q-item-section>
<q-item-label>{{ scope.opt.label }}</q-item-label>
<q-item-label caption>{{ scope.opt.desc }}</q-item-label>
</q-item-section>
<q-item-section side v-if="scope.opt.value">
<q-btn
flat
round
icon="help_outline"
size="xs"
dense
@click.stop="showMainPushHelp"
/>
</q-item-section>
</q-item>
</template>
</q-select>
<!-- 平台 -->
<q-select
:disable="!canCommandSave"
hide-dropdown-icon
stack-label
label-color="primary"
popup-content-class="side-bar-popup-content"
transition-show="jump-down"
transition-hide="jump-up"
borderless
square
:options="Object.keys(platformTypes)"
use-chips
@blur="platformVerify()"
v-model="currentCommand.features.platform"
multiple
label="平台"
>
<template v-slot:prepend>
<q-icon class="command-side-bar-icon" name="window" />
</template>
<template v-slot:selected-item="scope">
<q-chip
removable
dense
@remove="scope.removeAtIndex(scope.index)"
:tabindex="scope.tabindex"
>
{{ platformTypes[scope.opt].label }}
</q-chip>
</template>
<template v-slot:option="scope">
<q-item v-bind="scope.itemProps" class="row items-center">
<q-img
:src="platformTypes[scope.opt].icon"
width="24px"
class="q-mr-md"
/>
<div>
<q-item-label v-html="platformTypes[scope.opt].label" />
<q-item-label caption>{{ scope.opt.desc }}</q-item-label>
</div>
</q-item>
</template>
</q-select>
</div>
</div>
</div>
</div>
</q-scroll-area>
<!-- 对话框部分保持不变 -->
<q-dialog v-model="showIconPicker" position="left">
<iconPicker
@iconChanged="(dataUrl) => (currentCommand.features.icon = dataUrl)"
ref="icon"
/>
</q-dialog>
<q-dialog v-model="showUserData">
<UserData @insertText="insertSpecialVar" :showInsertBtn="true" />
</q-dialog>
</div>
</template>
<script>
import commandTypes from "js/options/commandTypes.js";
import outputTypes from "js/options/outputTypes.js";
import specialVars from "js/options/specialVars.js";
import platformTypes from "js/options/platformTypes.js";
import iconPicker from "components/popup/IconPicker.vue";
import UserData from "components/popup/UserData.vue";
export default {
components: { iconPicker, UserData },
data() {
return {
currentCommand: {
tags: [],
output: "text",
features: {
explain: "",
platform: ["win32", "linux", "darwin"],
icon: "",
mainPush: false,
},
},
searchPushOptions: [
{ value: false, label: "禁用", desc: "需要进入插件才能执行命令" },
{
value: true,
label: "启用",
desc: "可以在uTools主搜索框直接执行命令",
},
],
commandTypes: commandTypes,
platformTypes: platformTypes,
currentMatchType: "关键字",
cmdType: {},
cmdMatch: "",
outputTypes: outputTypes,
outputTypesOptions: Object.keys(outputTypes),
specialVar: "{{}}",
allQuickCommandTags: this.$parent.allQuickCommandTags,
showIconPicker: false,
showUserData: false,
sideBarPadding: 20,
isFileTypeDirectory: false,
};
},
props: {
quickcommandInfo: Object,
canCommandSave: Boolean,
sideBarWidth: Number,
},
computed: {
commandTypesOptions() {
return this.currentCommand.features.mainPush
? Object.values(commandTypes).filter((x) =>
["regex", "over", "key"].includes(x.name)
)
: Object.values(commandTypes);
},
specialVarsOptions() {
if (this.currentCommand.features.mainPush) return [specialVars.input];
let x = Object.values(specialVars).filter(
(x) => !x.label.match(this.cmdType.disabledSpecialVars)
);
return x;
},
outputTypesOptionsDy() {
if (this.currentCommand.features.mainPush) return ["text"];
switch (this.$parent.quickcommandInfo.program) {
case "quickcommand":
return window.lodashM.without(this.outputTypesOptions, "terminal");
case "html":
return ["html"];
default:
return this.outputTypesOptions;
}
},
searchPushValue: {
get() {
return (
this.searchPushOptions.find(
(opt) => opt.value === this.currentCommand.features.mainPush
) || this.searchPushOptions[0]
);
},
set(option) {
this.currentCommand.features.mainPush = option.value;
},
},
},
watch: {
outputTypesOptionsDy(val) {
if (!val.includes(this.currentCommand.output)) {
this.currentCommand.output = val[0];
}
},
commandTypesOptions(val) {
if (!val.map((x) => x.name).includes(this.cmdType.name)) {
this.cmdType = val[0];
}
},
},
methods: {
init() {
let currentQuickCommandCmds = this.getCommandType();
this.cmdType = this.commandTypes[currentQuickCommandCmds.type];
this.cmdMatch = currentQuickCommandCmds.match;
Object.assign(
this.currentCommand,
window.lodashM.cloneDeep(
window.lodashM.pick(
this.quickcommandInfo,
"tags",
"output",
"features"
)
)
);
this.setIcon(this.quickcommandInfo.program);
this.platformVerify();
},
setIcon(language) {
this.currentCommand.features.icon?.slice(0, 10) === "data:image" ||
(this.currentCommand.features.icon =
this.$root.programs[language].icon);
},
getCommandType() {
let data = { type: "key", match: [] };
let cmds = this.quickcommandInfo.features?.cmds;
if (!cmds) return data;
if (cmds.length === 1) {
let { type, match, fileType } = cmds[0];
data.type = type ? type : "key";
data.match =
data.type === "key" ? cmds : match?.app ? match.app : match;
this.isFileTypeDirectory = fileType === "directory";
} else {
data.type = cmds.filter((x) => !x.length).length
? "professional"
: "key";
data.match = data.type === "key" ? cmds : JSON.stringify(cmds, null, 4);
}
return data;
},
handleCmdTypeChange(val) {
this.cmdMatch =
val.name === "professional"
? JSON.stringify(val.jsonSample, null, 4)
: null;
},
tagVerify(val, done) {
if (
[
"默认",
"未分类",
"搜索结果",
// "来自分享"
].includes(val)
) {
return done(`_${val}_`);
}
done(val);
},
// 平台为空自动补充
platformVerify() {
this.currentCommand.features.platform?.length > 0 ||
(this.currentCommand.features.platform = [window.processPlatform]);
},
// 正则不和规则自动加斜杠
regexVerify() {
if (
this.cmdType.valueType === "regex" &&
!/^\/.*?\/[igm]*$/.test(this.cmdMatch)
)
this.cmdMatch = `/${this.cmdMatch}/`;
},
autoAddInputVal(e, ref) {
let inputValue = e.target.value;
if (!inputValue) return;
ref.add(inputValue, true);
},
insertSpecialVar(text) {
if (!text) return;
this.$parent.$refs.editor.repacleEditorSelection(`"${text}"`);
},
showMainPushHelp() {
window.showUb.help("#u0e9f1430");
},
// 保存各类数据
SaveMenuData() {
let updateData = {
features: this.currentCommand.features,
output: this.currentCommand.output,
tags: this.currentCommand.tags,
cmd: "",
};
// 说明为空填充一个空格
updateData.features.explain || (updateData.features.explain = " ");
if (!updateData.features.code) {
// 生成唯一code
let uid = Number(
Math.random().toString().substr(3, 3) + Date.now()
).toString(36);
updateData.features.code = `${this.cmdType.name}_${uid}`;
}
let verify = this.cmdType.verify(this.cmdMatch);
if (verify !== true) {
return quickcommand.showMessageBox(verify, "error");
}
if (outputTypes[updateData.output].outPlugin) {
updateData.features.mainHide = true;
}
// 匹配文件时,额外添加文件类型
let rules = this.cmdMatch;
if (this.cmdType.name === "files") {
rules = {
fileType: this.isFileTypeDirectory ? "directory" : "file",
match: this.cmdMatch,
};
}
updateData.features.cmds = this.cmdType.matchToCmds(
rules,
updateData.features.explain
);
updateData.cmd = this.$parent.$refs.editor.getEditorValue();
let blackLisk = updateData.cmd.match(this.cmdType.disabledSpecialVars);
if (blackLisk) {
return quickcommand.showMessageBox(
`当前模式无法使用${[...new Set(blackLisk)].join("、")}`,
"error"
);
}
// 子输入框
if (updateData.cmd.includes("{{subinput")) {
updateData.hasSubInput = true;
} else {
updateData.hasSubInput = false;
}
return updateData;
},
},
emits: ["back"],
};
</script>
<style scoped>
/* 其他样式从app.css中继承 */
.featureIco {
cursor: pointer;
}
.featureIco:hover {
transform: scale(1.02) translateY(-2px);
}
.featureIco:hover::after {
opacity: 0.8;
transform: scale(1.05);
}
.command-side-bar {
height: 100%;
background: #f4f4f4;
display: flex;
flex-direction: column;
}
.header-section {
height: 60px;
min-height: 60px;
background: inherit;
position: relative;
}
.header-content {
height: 100%;
display: flex;
align-items: center;
}
.back-btn {
position: absolute;
left: 20px;
top: 50%;
transform: translateY(-50%);
height: 48px;
width: 22px;
z-index: 1;
}
.logo-container {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
}
.scroll-area {
flex: 1;
background: inherit;
}
.body--dark .command-side-bar {
background: #303133;
}
.commandLogo {
cursor: pointer;
transition: 0.2s;
filter: drop-shadow(2px 1px 1px grey);
}
.commandLogo:hover {
transition: 0.5s;
transform: translateY(-1px);
filter: drop-shadow(2px 1px 5px grey);
}
/* 输入框图标基础样式 */
.command-side-bar-icon {
background: var(--q-primary);
border-radius: 8px;
padding: 4px;
color: #f4f4f4;
font-size: 14px;
/* 分开设置不同属性的过渡效果 */
transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1),
box-shadow 0.3s cubic-bezier(0.4, 0, 0.2, 1),
opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1);
backface-visibility: hidden;
transform-style: preserve-3d;
will-change: transform;
-webkit-font-smoothing: subpixel-antialiased;
/* 添加初始transform状态 */
transform: translateZ(0);
}
/* 输入框容器悬浮效果 */
.q-field:hover .command-side-bar-icon {
transform: scale(1.05) translateY(-1px) translateZ(0);
background: var(--q-primary);
opacity: 0.9;
}
/* 输入框得焦点时的图标效果 */
.q-field--focused .command-side-bar-icon {
transform: scale(1.1) translateY(-1px) translateZ(0);
background: var(--q-primary);
opacity: 0.85;
}
.side-bar-popup-content .q-item .q-icon {
font-size: 20px;
}
.command-side-bar-content .q-field,
.side-bar-popup-content .q-item__label {
font-size: 13px;
}
.side-bar-popup-content .q-item__label--caption,
.command-side-bar-content :deep(.q-chip) {
font-size: 12px;
}
</style>