完善卡片上标签显示逻辑的算法

This commit is contained in:
fofolee 2024-12-21 19:38:17 +08:00
parent b230088a3a
commit 5c02214063
7 changed files with 273 additions and 99 deletions

View File

@ -0,0 +1,77 @@
<template>
<q-badge rounded :class="badgeClass">
<q-icon class="q-mr-xs" :name="iconName" :style="iconStyle" />
<span class="badge-text">{{ text }}</span>
<q-tooltip v-if="showTooltip">
<slot name="tooltip">
<template v-if="cmd.type === 'add'">
<slot />
</template>
<template v-else>
<div class="text-subtitle2">{{ text }}</div>
</template>
</slot>
</q-tooltip>
</q-badge>
</template>
<script>
import commandTypes from "js/options/commandTypes.js";
import { textDisplayRules } from "js/options/textDisplayRules.js";
export default {
name: "CommandBadge",
data() {
return {
commandTypes,
};
},
props: {
cmd: {
type: [String, Object],
required: true,
},
isGrayColor: Boolean,
showTooltip: Boolean,
iconStyle: Object,
inheritColor: String,
},
computed: {
badgeClass() {
if (this.isGrayColor) {
return this.$q.dark.isActive ? "text-grey-6 bg-grey-9" : "bg-grey-4";
}
const color =
this.cmd.type === "add" && this.inheritColor
? this.inheritColor
: this.commandTypes[this.cmd.type || "key"].color;
return "bg-" + color + (this.$q.dark.isActive ? "-10" : "-4");
},
iconName() {
if (typeof this.cmd === "string") {
return this.commandTypes.key.icon;
}
return this.cmd.type === "add"
? "add"
: this.commandTypes[this.cmd.type].icon;
},
text() {
const type = typeof this.cmd === "string" ? "string" : this.cmd.type;
return textDisplayRules[type]?.(this.cmd, false) ?? "";
},
},
};
</script>
<style scoped>
.q-badge {
font-size: 12px;
white-space: nowrap;
max-width: var(--max-tag-width);
}
.badge-text {
overflow: hidden;
text-overflow: ellipsis;
}
</style>

View File

@ -11,6 +11,7 @@
:isPlatformSupported="isPlatformSupported" :isPlatformSupported="isPlatformSupported"
:isHovered="isHovered" :isHovered="isHovered"
:style="iconHaloStyle" :style="iconHaloStyle"
:cardStyleCode="cardStyleCode"
/> />
</q-card> </q-card>
</template> </template>

View File

@ -1,83 +1,192 @@
<template> <template>
<div class="matchTypesBox"> <div class="matchTypesBox" :style="{ '--max-tag-width': maxTagWidth + 'px' }">
<div v-for="cmd in cmds" :key="cmd"> <!-- 遍历显示可见的标签 -->
<span v-if="typeof cmd === 'string'"> <template v-for="(cmd, index) in visibleCommands" :key="index">
<q-badge rounded :class="getBadgeClass(cmd)"> <CommandBadge
<q-icon class="q-mr-xs" :name="commandTypes.key.icon" /> :cmd="cmd"
<span class="badge-text">{{ cmd }}</span> :isGrayColor="isGrayColor"
</q-badge> :showTooltip="needTooltip(cmd)"
<q-tooltip> >
<div class="text-subtitle2">{{ cmd }}</div> <!-- 窗口类型标签的额外应用列表提示 -->
</q-tooltip> <template
</span> v-if="cmd.type === 'window' && cmd.match.app.length > 1"
<span v-else-if="cmd.type === 'window' && cmd.match"> #tooltip
<q-badge rounded :class="getBadgeClass(cmd)"> >
<q-icon class="q-mr-xs" :name="commandTypes.window.icon" /> <div
<span class="badge-text">{{ cmd.match.app[0] }}</span> class="text-subtitle2 row items-center"
</q-badge> v-for="app in cmd.match.app.slice(1)"
<q-tooltip> :key="app"
<div class="text-subtitle2" v-for="app in cmd.match.app" :key="app"> >
{{ app }} <q-icon class="q-mr-xs" :name="commandTypes.window.icon" />
<span>{{ app }}</span>
</div> </div>
</q-tooltip> </template>
</span> </CommandBadge>
<span v-else-if="cmd.type === 'files'">
<q-badge rounded :class="getBadgeClass(cmd)"> <!-- 窗口类型的额外应用数量标签 -->
<q-icon class="q-mr-xs" :name="commandTypes.files.icon" /> <CommandBadge
<span class="badge-text">{{ cmd.match || "所有文件" }}</span> v-if="cmd.type === 'window' && cmd.match.app.length > 1"
</q-badge> :cmd="{ type: 'add', count: cmd.match.app.length - 1 }"
<q-tooltip> :isGrayColor="isGrayColor"
<div class="text-subtitle2"> :iconStyle="{ width: '8px' }"
{{ cmd.match || "所有文件" }} :showTooltip="true"
:inheritColor="commandTypes[cmd.type].color"
>
<!-- 显示额外的应用列表 -->
<template #tooltip>
<template v-for="(app, i) in cmd.match.app.slice(1)" :key="i">
<div class="row items-center">
<q-icon class="q-mr-xs" :name="commandTypes.window.icon" />
<div>{{ app }}</div>
</div>
</template>
</template>
</CommandBadge>
</template>
<!-- 溢出标签当标签数量超过显示限制时显示 -->
<CommandBadge
v-if="hasOverflow"
:cmd="{ type: 'add', count: cmds.length - maxTagNums }"
:isGrayColor="isGrayColor"
:iconStyle="{ width: '7px' }"
:showTooltip="true"
:inheritColor="
commandTypes[visibleCommands[visibleCommands.length - 1].type || 'key']
.color
"
>
<!-- tooltip 显示所有溢出的标签 -->
<template #tooltip>
<template v-for="(cmd, i) in overflowCommands" :key="i">
<div class="row items-center">
<q-icon class="q-mr-xs" :name="getIconName(cmd)" />
<div>{{ getDisplayText(cmd) }}</div>
</div> </div>
</q-tooltip> </template>
</span> </template>
<span v-else-if="cmd.type === 'regex'"> </CommandBadge>
<q-badge rounded :class="getBadgeClass(cmd)">
<q-icon class="q-mr-xs" :name="commandTypes.regex.icon" />
<span class="badge-text">{{ cmd.match }}</span>
</q-badge>
<q-tooltip>
<div class="text-subtitle2">
{{ cmd.match }}
</div>
</q-tooltip>
</span>
<span v-else-if="cmd.type === 'over'">
<q-badge rounded :class="getBadgeClass(cmd)">
<q-icon class="q-mr-xs" :name="commandTypes.over.icon" />所有文本
</q-badge>
</span>
<span v-else-if="cmd.type === 'img'">
<q-badge rounded :class="getBadgeClass(cmd)">
<q-icon class="q-mr-xs" :name="commandTypes.img.icon" />图片
</q-badge>
</span>
</div>
</div> </div>
</template> </template>
<script> <script>
import CommandBadge from "./CommandBadge.vue";
import commandTypes from "js/options/commandTypes.js"; import commandTypes from "js/options/commandTypes.js";
import { textDisplayRules } from "js/options/textDisplayRules.js";
export default { export default {
name: "CommandTypeTag", name: "CommandTypeTag",
props: { components: { CommandBadge },
cmds: Array,
isGrayColor: Boolean,
},
data() { data() {
return { return {
commandTypes, commandTypes,
textDisplayRules,
}; };
}, },
methods: { props: {
getBadgeClass(cmd) { cmds: Array,
if (this.isGrayColor) { isGrayColor: Boolean,
return this.$q.dark.isActive ? "text-grey-6 bg-grey-9" : "bg-grey-4"; // 1-mini, 2-dense, 3-normal
cardStyleCode: Number,
},
computed: {
//
maxTagNums() {
return { 1: 2, 2: 3, 3: 3 }[this.cardStyleCode];
},
//
maxContainerWidth() {
return { 1: 120, 2: 180, 3: 260 }[this.cardStyleCode];
},
//
maxTagWidth() {
// window
let extraBadgesWidth = 0;
//
const visibleTagCount = Math.min(this.cmds.length, this.maxTagNums);
//
if (this.hasOverflow) {
extraBadgesWidth += 31;
} }
const color = this.commandTypes[cmd.type || "key"].color;
return "bg-" + color + (this.$q.dark.isActive ? "-10" : "-4"); // window
for (let i = 0; i < visibleTagCount; i++) {
const cmd = this.cmds[i];
if (cmd.type === 'window' && cmd.match.app.length > 1) {
extraBadgesWidth += 31; //
}
}
// ( - - ) /
return (
(this.maxContainerWidth -
extraBadgesWidth -
(visibleTagCount - 1) * 3) /
visibleTagCount
);
},
//
hasOverflow() {
return this.cmds.length > this.maxTagNums;
},
//
visibleCommands() {
return this.cmds.slice(0, this.maxTagNums);
},
//
overflowCommands() {
return this.cmds.slice(this.maxTagNums);
},
},
methods: {
//
getIconName(cmd) {
return typeof cmd === "string"
? this.commandTypes.key.icon
: this.commandTypes[cmd.type].icon;
},
//
getDisplayText(cmd, isTooltip = true) {
const type = typeof cmd === "string" ? "string" : cmd.type;
return this.textDisplayRules[type]?.(cmd, isTooltip) ?? "";
},
//
getTextWidth(text) {
let width = 0;
for (let i = 0; i < text.length; i++) {
//
if (/[\u4e00-\u9fa5]|[\u3000-\u303f\uff00-\uff60]/.test(text[i])) {
width += 2;
} else {
width += 1;
}
}
return width * 6; // 6px
},
// tooltip
needTooltip(cmd) {
let text;
//
if (typeof cmd === "string") {
text = cmd;
} else if (cmd.type === "window") {
text = cmd.match.app[0];
} else if (cmd.type === "regex") {
text = cmd.match;
} else if (cmd.type === "files") {
text = this.getDisplayText(cmd, false);
} else {
return false;
}
//
const iconWidth = 20; //
const paddingWidth = 16; //
const availableWidth = this.maxTagWidth - iconWidth - paddingWidth;
return this.getTextWidth(text) > availableWidth;
}, },
}, },
}; };
@ -88,36 +197,7 @@ export default {
height: 23px; height: 23px;
display: flex; display: flex;
overflow: hidden; overflow: hidden;
gap: 2px; align-items: center;
} gap: 3px;
.q-badge {
font-size: 12px;
margin: 0 1px;
max-width: 120px;
white-space: nowrap;
}
.badge-text {
display: inline-block;
max-width: 80px;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: bottom;
}
.tags-container {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 4px;
width: fit-content;
max-width: 100%;
margin: 0 auto;
}
.tag-wrapper {
display: inline-flex;
white-space: nowrap;
} }
</style> </style>

View File

@ -28,6 +28,7 @@
<CommandTypeTag <CommandTypeTag
:cmds="commandInfo.features.cmds" :cmds="commandInfo.features.cmds"
:isGrayColor="!isPlatformSupported || !isActivated" :isGrayColor="!isPlatformSupported || !isActivated"
:cardStyleCode="cardStyleCode"
/> />
</div> </div>
@ -63,6 +64,7 @@ export default {
isActivated: Boolean, isActivated: Boolean,
isPlatformSupported: Boolean, isPlatformSupported: Boolean,
isHovered: Boolean, isHovered: Boolean,
cardStyleCode: Number,
}, },
data() { data() {
return { return {

View File

@ -23,6 +23,7 @@
<CommandTypeTag <CommandTypeTag
:cmds="commandInfo.features.cmds" :cmds="commandInfo.features.cmds"
:isGrayColor="!isPlatformSupported || !isActivated" :isGrayColor="!isPlatformSupported || !isActivated"
:cardStyleCode="cardStyleCode"
/> />
</div> </div>
</div> </div>
@ -58,6 +59,7 @@ export default {
isActivated: Boolean, isActivated: Boolean,
isPlatformSupported: Boolean, isPlatformSupported: Boolean,
isHovered: Boolean, isHovered: Boolean,
cardStyleCode: Number,
}, },
data() { data() {
return { return {

View File

@ -24,7 +24,10 @@
<!-- 匹配模式 --> <!-- 匹配模式 -->
<div class="row justify-center w-100"> <div class="row justify-center w-100">
<CommandTypeTag :cmds="commandInfo.features.cmds" /> <CommandTypeTag
:cmds="commandInfo.features.cmds"
:cardStyleCode="cardStyleCode"
/>
</div> </div>
</div> </div>
</template> </template>
@ -32,7 +35,6 @@
<script> <script>
import CommandTypeTag from "../CommandTypeTag.vue"; import CommandTypeTag from "../CommandTypeTag.vue";
import platformTypes from "js/options/platformTypes.js"; import platformTypes from "js/options/platformTypes.js";
import { computed } from "vue";
export default { export default {
name: "MiniLayout", name: "MiniLayout",
@ -40,6 +42,7 @@ export default {
props: { props: {
commandInfo: Object, commandInfo: Object,
isHovered: Boolean, isHovered: Boolean,
cardStyleCode: Number,
}, },
setup() { setup() {
return { return {
@ -55,7 +58,7 @@ export default {
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
width: 100%; width: 100%;
padding: 16px; padding: 16px 8px;
} }
.w-100 { .w-100 {
@ -66,9 +69,9 @@ export default {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
max-width: 90%; /* max-width: 90%; */
margin: 0 auto; margin: 0 auto;
padding: 0 8px; /* padding: 0 8px; */
font-size: 14px; font-size: 14px;
} }
</style> </style>

View File

@ -0,0 +1,9 @@
export const textDisplayRules = {
string: (cmd) => cmd,
window: (cmd, isTooltip) => isTooltip ? cmd.match.app.join(", ") : cmd.match.app[0],
files: (cmd) => cmd.match || (cmd.fileType === "directory" ? "所有文件夹" : "所有文件"),
regex: (cmd) => cmd.match,
over: () => "所有文本",
img: () => "图片",
add: (cmd) => cmd.count,
};