Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,35 +13,50 @@
>
<div class="editor-wrapper">
<div class="editor-content">
<!-- 快捷键类型说明 -->
<div v-if="isAppShortcut" class="form-notice">
应用快捷键仅在 ZTools 窗口激活时生效,不会与其他应用冲突
</div>
<div class="editor-main">
<div>
<!-- 快捷键类型说明 -->
<div v-if="isAppShortcut" class="form-notice">
应用快捷键仅在 ZTools 窗口激活时生效,不会与其他应用冲突
</div>

<!-- 快捷键录制 -->
<div class="form-item">
<label class="form-label">快捷键</label>
<HotkeyInput
v-model="recordedShortcut"
:platform="platform"
placeholder="点击录制快捷键"
/>
<span class="form-hint">点击上方输入框录制快捷键</span>
</div>
<!-- 快捷键录制 -->
<div class="form-item">
<label class="form-label">快捷键</label>
<HotkeyInput
v-model="recordedShortcut"
:platform="platform"
placeholder="点击录制快捷键"
/>
<span class="form-hint">点击上方输入框录制快捷键</span>
</div>

<!-- 目标指令输入 -->
<div class="form-item">
<label class="form-label">目标指令</label>
<input v-model="targetCommand" type="text" class="input" placeholder="例如:微信、翻译" />
<span class="form-hint"
>支持「指令名称」或「插件名称/指令名称」格式,建议加上插件名称以避免歧义</span
>
<!-- 预截图优化(仅 Windows 全局快捷键编辑页) -->
<div v-if="!isAppShortcut && platform === 'win32'" class="form-item">
<label class="form-label">预截图优化</label>
<label class="toggle editor-toggle">
<input v-model="preScreenshotOptimization" type="checkbox" />
<span class="toggle-slider"></span>
</label>
<span class="form-hint">仅对截图类全局快捷键生效,触发后会优先预抓取屏幕首帧</span>
</div>
</div>

<div class="editor-selector">
<label class="form-label">目标指令</label>
<CommandTargetSelector
:options="targetOptions"
:selected-command-id="currentTarget?.commandId || null"
@select="handleSelectTarget"
@escape="handleSelectorEscape"
/>
</div>
</div>
</div>

<div class="editor-footer">
<button class="btn" @click="emit('back')">取消</button>
<button class="btn" :disabled="!recordedShortcut || !targetCommand" @click="handleSave">
<button class="btn" :disabled="!recordedShortcut || !currentTarget" @click="handleSave">
确定
</button>
</div>
Expand All @@ -50,25 +65,33 @@
</template>

<script setup lang="ts">
import { onMounted, ref, watch } from 'vue'
import { computed, onMounted, ref, watch } from 'vue'
import { HotkeyInput, DetailPanel } from '@/components'
import CommandTargetSelector from '@/views/ShortcutsSetting/components/CommandTargetSelector.vue'
import {
buildShortcutTargetCandidates,
normalizeShortcutTargetValue,
type ShortcutsSettingShortcutTargetOption
} from '@/views/ShortcutsSetting/ShortcutsSetting'

interface GlobalShortcut {
id: string
shortcut: string
target: string
enabled: boolean
preScreenshotOptimization?: boolean
}

const props = defineProps<{
editingShortcut?: GlobalShortcut | null
prefillTarget?: string
targetOptions: ShortcutsSettingShortcutTargetOption[]
isAppShortcut?: boolean
}>()

const emit = defineEmits<{
(e: 'back'): void
(e: 'save', shortcut: string, target: string): void
(e: 'save', shortcut: string, target: string, preScreenshotOptimization: boolean): void
}>()

// 平台信息(用于区分 Alt/Option 显示)
Expand All @@ -77,28 +100,82 @@ const platform = ref<'darwin' | 'win32' | 'linux'>('darwin')
// 录制状态
const recordedShortcut = ref('')
const targetCommand = ref('')
const preScreenshotOptimization = ref(false)

const currentTarget = computed(() => resolveShortcutTargetOption(targetCommand.value))

// 初始化编辑数据
watch(
() => props.editingShortcut,
(newVal) => {
if (newVal) {
recordedShortcut.value = newVal.shortcut
targetCommand.value = newVal.target
targetCommand.value = normalizeShortcutTargetValue(newVal.target)
preScreenshotOptimization.value = newVal.preScreenshotOptimization ?? false
} else {
recordedShortcut.value = ''
targetCommand.value = props.prefillTarget || ''
targetCommand.value = normalizeShortcutTargetValue(props.prefillTarget || '')
preScreenshotOptimization.value = false
}
},
{ immediate: true }
)

// 保存
watch(
() => props.prefillTarget,
(newVal) => {
if (!props.editingShortcut) {
targetCommand.value = normalizeShortcutTargetValue(newVal || '')
}
}
)

/**
* 将存量快捷键目标字符串解析回当前可选的目标指令。
*/
function resolveShortcutTargetOption(
rawTarget: string
): ShortcutsSettingShortcutTargetOption | null {
const normalizedTarget = normalizeShortcutTargetValue(rawTarget)
if (!normalizedTarget) {
return null
}

return (
props.targetOptions.find((item) =>
buildShortcutTargetCandidates(item).includes(normalizedTarget)
) || null
)
}

/**
* 保存快捷键编辑结果,并优先回写当前目标的 canonical 字符串。
*/
function handleSave(): void {
if (!recordedShortcut.value || !targetCommand.value) {
if (!recordedShortcut.value || !currentTarget.value) {
return
}
emit('save', recordedShortcut.value, targetCommand.value)

emit(
'save',
recordedShortcut.value,
currentTarget.value.value,
!props.isAppShortcut && preScreenshotOptimization.value
)
}

/**
* 接收选择器选中的目标指令,并同步更新待保存字符串。
*/
function handleSelectTarget(target: ShortcutsSettingShortcutTargetOption): void {
targetCommand.value = target.value
}

/**
* 在目标选择器根层按下 Esc 时返回上一层详情面板。
*/
function handleSelectorEscape(): void {
emit('back')
}

// 初始化平台信息
Expand All @@ -119,7 +196,17 @@ onMounted(() => {

.editor-content {
flex: 1;
padding: 24px;
padding: 20px 24px;
min-height: 0;
overflow: hidden;
}

.editor-main {
display: grid;
grid-template-columns: minmax(0, 1fr) 320px;
gap: 20px;
height: 100%;
min-height: 0;
}

.form-notice {
Expand All @@ -141,6 +228,10 @@ onMounted(() => {
margin-bottom: 0;
}

.editor-toggle {
display: inline-flex;
}

.form-label {
display: block;
font-size: 14px;
Expand All @@ -156,6 +247,13 @@ onMounted(() => {
margin-top: 6px;
}

.editor-selector {
display: flex;
flex-direction: column;
min-height: 0;
overflow: hidden;
}

.editor-footer {
display: flex;
justify-content: flex-end;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export function usePluginCommandActions(options: UsePluginCommandActionsOptions)
pluginName: string,
featureCode: string,
cmdName: string,
cmdType: CommandCmdType
cmdType: 'text' | 'window'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

在此次修改中,系统新增了多种指令类型(如 'regex' | 'over' | 'img' | 'files')。如果将 cmdType 限制为 'text' | 'window',将导致这些新类型的指令无法通过 usePluginCommandActions 进行禁用、启用或状态查询(会产生 TypeScript 类型错误)。建议将 cmdType 的类型定义扩展为包含所有支持的指令类型,或者直接使用项目中定义的 ShortcutsSettingCommandCmdType(或其联合类型)。此建议同样适用于该文件中的其他几处修改(第 99 行、第 109 行和第 439 行)。

    cmdType: 'text' | 'regex' | 'over' | 'img' | 'files' | 'window'

): string {
return _getCommandId({
type: 'plugin',
Expand All @@ -96,7 +96,7 @@ export function usePluginCommandActions(options: UsePluginCommandActionsOptions)
pluginName: string,
featureCode: string,
cmdName: string,
cmdType: CommandCmdType
cmdType: 'text' | 'window'
): boolean {
const id = getPluginCommandId(pluginName, featureCode, cmdName, cmdType)
return disabledCommands.value.includes(id)
Expand All @@ -106,7 +106,7 @@ export function usePluginCommandActions(options: UsePluginCommandActionsOptions)
pluginName: string,
featureCode: string,
cmdName: string,
cmdType: CommandCmdType
cmdType: 'text' | 'window'
): Promise<void> {
const id = getPluginCommandId(pluginName, featureCode, cmdName, cmdType)
const index = disabledCommands.value.indexOf(id)
Expand Down Expand Up @@ -436,7 +436,7 @@ export function usePluginCommandActions(options: UsePluginCommandActionsOptions)
pluginName: string,
featureCode: string,
cmdName: string,
cmdType: CommandCmdType
cmdType: 'text' | 'window'
): Promise<void> {
if (key === 'toggle') {
await toggleCommandDisabled(pluginName, featureCode, cmdName, cmdType)
Expand Down
5 changes: 3 additions & 2 deletions internal-plugins/setting/src/env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,15 +234,16 @@ declare global {
registerGlobalShortcut: (
shortcut: string,
target: string,
autoCopy?: boolean
autoCopy?: boolean,
preScreenshotOptimization?: boolean
) => Promise<{ success: boolean; error?: string }>
unregisterGlobalShortcut: (shortcut: string) => Promise<{
success: boolean
error?: string
}>
updateGlobalShortcutConfig: (
shortcut: string,
config: { autoCopy: boolean }
config: { autoCopy: boolean; preScreenshotOptimization: boolean }
) => Promise<{ success: boolean; error?: string }>
registerAppShortcut: (
shortcut: string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,70 @@ import type { HistoryState } from 'vue-router'

export type ShortcutsSettingTab = 'global' | 'app' | 'alias'

export type ShortcutsSettingCommandCmdType = 'text' | 'regex' | 'over' | 'img' | 'files' | 'window'

/**
* 统一归一化快捷键目标字符串,兼容旧的“标题 / 指令”格式。
*/
export function normalizeShortcutTargetValue(value: string): string {
const parts = value
.split('/')
.map((part) => part.trim())
.filter((part) => part.length > 0)

if (parts.length === 2) {
return `${parts[0]}/${parts[1]}`
}

return value.trim()
}

/**
* 生成快捷键场景使用的 canonical target 字符串。
*/
export function buildShortcutTargetValue(target: {
type: 'plugin' | 'direct'
pluginName: string
pluginTitle?: string
cmdName: string
}): string {
if (target.type === 'plugin') {
return normalizeShortcutTargetValue(`${target.pluginName}/${target.cmdName}`)
}

return normalizeShortcutTargetValue(target.cmdName)
}

/**
* 生成快捷键目标的兼容匹配候选值,便于识别旧存量配置。
*/
export function buildShortcutTargetCandidates(target: {
type: 'plugin' | 'direct'
pluginName: string
pluginTitle: string
groupTitle: string
cmdName: string
}): string[] {
const candidates = new Set<string>()

candidates.add(buildShortcutTargetValue(target))

if (target.type === 'plugin') {
candidates.add(normalizeShortcutTargetValue(`${target.pluginTitle}/${target.cmdName}`))
candidates.add(normalizeShortcutTargetValue(`${target.groupTitle}/${target.cmdName}`))
}

return Array.from(candidates)
}

export interface ShortcutsSettingCommandTargetOptionBase extends ShortcutsSettingAliasDraftTarget {
label: string
}

export interface ShortcutsSettingShortcutTargetOption extends ShortcutsSettingCommandTargetOptionBase {
value: string
}

/**
* 从“所有指令”页跳转到 alias 设置页时携带的草稿目标
* 这里只保留构造 alias 所需的最小字段,避免把完整 command 塞进路由状态
Expand All @@ -19,7 +83,7 @@ export interface ShortcutsSettingAliasDraftTarget extends HistoryState {
pluginName: string
pluginTitle: string
cmdName: string
cmdType: 'text' | 'window'
cmdType: ShortcutsSettingCommandCmdType
icon?: string
}

Expand Down
Loading