mirror of
https://github.com/fofolee/uTools-quickcommand.git
synced 2025-06-08 22:51:25 +08:00
完善模拟按键组件
This commit is contained in:
parent
e8d12b95d4
commit
1dafacf3f1
@ -1,317 +1,341 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="key-editor">
|
<div class="key-editor">
|
||||||
<!-- 按键输入框 -->
|
<div class="row items-center q-gutter-x-sm full-width">
|
||||||
<q-input
|
<!-- 按键选择/输入区域 -->
|
||||||
dense
|
<q-select
|
||||||
outlined
|
v-model="mainKey"
|
||||||
readonly
|
:options="commonKeys"
|
||||||
class="col"
|
dense
|
||||||
>
|
square
|
||||||
<template v-slot:prepend>
|
filled
|
||||||
<!-- 修饰键 -->
|
use-input
|
||||||
<div class="row items-center q-gutter-x-xs">
|
hide-dropdown-icon
|
||||||
<q-chip
|
new-value-mode="add-unique"
|
||||||
v-for="(active, key) in modifiers"
|
input-debounce="0"
|
||||||
:key="key"
|
emit-value
|
||||||
:color="active ? 'primary' : 'grey-4'"
|
map-options
|
||||||
:text-color="active ? 'white' : 'grey-7'"
|
options-dense
|
||||||
dense
|
behavior="menu"
|
||||||
clickable
|
class="col q-px-sm"
|
||||||
class="modifier-chip"
|
placeholder="选择或输入按键"
|
||||||
@click="toggleModifier(key)"
|
@filter="filterFn"
|
||||||
>
|
@update:model-value="handleKeyInput"
|
||||||
{{ modifierLabels[key] }}
|
@input="handleInput"
|
||||||
</q-chip>
|
>
|
||||||
</div>
|
<template v-slot:prepend>
|
||||||
</template>
|
<!-- 修饰键 -->
|
||||||
|
<div class="row items-center q-gutter-x-xs no-wrap">
|
||||||
<!-- 主按键显示 -->
|
<q-chip
|
||||||
<template v-slot:default>
|
v-for="(active, key) in modifiers"
|
||||||
<div class="main-key-container">
|
:key="key"
|
||||||
<div class="main-key">
|
:color="active ? 'primary' : 'grey-4'"
|
||||||
{{ mainKeyDisplay }}
|
:text-color="active ? 'white' : 'grey-7'"
|
||||||
|
dense
|
||||||
|
clickable
|
||||||
|
class="modifier-chip"
|
||||||
|
@click="toggleModifier(key)"
|
||||||
|
>
|
||||||
|
{{ modifierLabels[key] }}
|
||||||
|
</q-chip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</template>
|
<!-- 添加自定义选中值显示 -->
|
||||||
|
<template v-slot:selected>
|
||||||
<template v-slot:append>
|
<q-badge
|
||||||
<q-separator vertical inset />
|
v-if="mainKey"
|
||||||
<!-- 选择按钮 -->
|
color="primary"
|
||||||
<q-btn
|
text-color="white"
|
||||||
flat
|
class="main-key"
|
||||||
round
|
>
|
||||||
dense
|
{{ mainKeyDisplay }}
|
||||||
icon="edit"
|
</q-badge>
|
||||||
@click="showKeySelect = true"
|
</template>
|
||||||
>
|
</q-select>
|
||||||
<q-tooltip>选择按键</q-tooltip>
|
<!-- 录制按钮 -->
|
||||||
</q-btn>
|
<q-btn
|
||||||
<q-separator vertical inset />
|
flat
|
||||||
<!-- 录制按钮 -->
|
round
|
||||||
<q-btn
|
dense
|
||||||
flat
|
:icon="isRecording ? 'fiber_manual_record' : 'radio_button_unchecked'"
|
||||||
round
|
:color="isRecording ? 'negative' : 'primary'"
|
||||||
dense
|
@click="toggleRecording"
|
||||||
:icon="isRecording ? 'fiber_manual_record' : 'radio_button_unchecked'"
|
>
|
||||||
:color="isRecording ? 'negative' : 'primary'"
|
<q-tooltip>{{ isRecording ? "停止录制" : "开始录制" }}</q-tooltip>
|
||||||
@click="toggleRecording"
|
</q-btn>
|
||||||
>
|
</div>
|
||||||
<q-tooltip>{{ isRecording ? '停止录制' : '开始录制' }}</q-tooltip>
|
|
||||||
</q-btn>
|
|
||||||
</template>
|
|
||||||
</q-input>
|
|
||||||
|
|
||||||
<!-- 按键选择对话框 -->
|
|
||||||
<q-dialog v-model="showKeySelect">
|
|
||||||
<q-card style="min-width: 300px">
|
|
||||||
<q-card-section>
|
|
||||||
<div class="text-h6">选择按键</div>
|
|
||||||
</q-card-section>
|
|
||||||
<q-card-section class="q-gutter-y-md">
|
|
||||||
<!-- 主按键选择 -->
|
|
||||||
<q-select
|
|
||||||
v-model="mainKey"
|
|
||||||
:options="commonKeys"
|
|
||||||
label="选择用按键"
|
|
||||||
dense
|
|
||||||
outlined
|
|
||||||
emit-value
|
|
||||||
map-options
|
|
||||||
/>
|
|
||||||
<q-input
|
|
||||||
v-model="customKey"
|
|
||||||
label="或输入自定义按键"
|
|
||||||
dense
|
|
||||||
outlined
|
|
||||||
@update:model-value="mainKey = $event"
|
|
||||||
/>
|
|
||||||
</q-card-section>
|
|
||||||
<q-card-actions align="right">
|
|
||||||
<q-btn flat label="确定" color="primary" v-close-popup />
|
|
||||||
</q-card-actions>
|
|
||||||
</q-card>
|
|
||||||
</q-dialog>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { defineComponent } from 'vue'
|
import { defineComponent } from "vue";
|
||||||
|
|
||||||
// 检测操作系统
|
// 检测操作系统
|
||||||
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0
|
const isMac = window.utools.isMacOs();
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'KeyEditor',
|
name: "KeyEditor",
|
||||||
props: {
|
props: {
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: "",
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isRecording: false,
|
isRecording: false,
|
||||||
showKeySelect: false,
|
showKeySelect: false,
|
||||||
mainKey: '',
|
mainKey: "",
|
||||||
customKey: '',
|
|
||||||
modifiers: {
|
modifiers: {
|
||||||
control: false,
|
control: false,
|
||||||
alt: false,
|
alt: false,
|
||||||
shift: false,
|
shift: false,
|
||||||
command: false
|
command: false,
|
||||||
},
|
|
||||||
modifierLabels: isMac ? {
|
|
||||||
control: '⌃',
|
|
||||||
alt: '⌥',
|
|
||||||
shift: '⇧',
|
|
||||||
command: '⌘'
|
|
||||||
} : {
|
|
||||||
control: 'Ctrl',
|
|
||||||
alt: 'Alt',
|
|
||||||
shift: 'Shift',
|
|
||||||
command: 'Win'
|
|
||||||
},
|
},
|
||||||
|
modifierLabels: isMac
|
||||||
|
? {
|
||||||
|
control: "⌃",
|
||||||
|
alt: "⌥",
|
||||||
|
shift: "⇧",
|
||||||
|
command: "⌘",
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
control: "Ctrl",
|
||||||
|
alt: "Alt",
|
||||||
|
shift: "Shift",
|
||||||
|
command: "Win",
|
||||||
|
},
|
||||||
commonKeys: [
|
commonKeys: [
|
||||||
{ label: 'Enter ↵', value: 'enter' },
|
{ label: "Enter ↵", value: "enter" },
|
||||||
{ label: 'Tab ⇥', value: 'tab' },
|
{ label: "Tab ⇥", value: "tab" },
|
||||||
{ label: 'Space', value: 'space' },
|
{ label: "Space", value: "space" },
|
||||||
{ label: 'Backspace ⌫', value: 'backspace' },
|
{ label: "Backspace ⌫", value: "backspace" },
|
||||||
{ label: 'Delete ⌦', value: 'delete' },
|
{ label: "Delete ⌦", value: "delete" },
|
||||||
{ label: 'Escape ⎋', value: 'escape' },
|
{ label: "Escape ⎋", value: "escape" },
|
||||||
{ label: '↑', value: 'up' },
|
{ label: "↑", value: "up" },
|
||||||
{ label: '↓', value: 'down' },
|
{ label: "↓", value: "down" },
|
||||||
{ label: '←', value: 'left' },
|
{ label: "←", value: "left" },
|
||||||
{ label: '→', value: 'right' },
|
{ label: "→", value: "right" },
|
||||||
{ label: 'Home', value: 'home' },
|
{ label: "Home", value: "home" },
|
||||||
{ label: 'End', value: 'end' },
|
{ label: "End", value: "end" },
|
||||||
{ label: 'Page Up', value: 'pageup' },
|
{ label: "Page Up", value: "pageup" },
|
||||||
{ label: 'Page Down', value: 'pagedown' }
|
{ label: "Page Down", value: "pagedown" },
|
||||||
]
|
],
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
mainKeyDisplay() {
|
mainKeyDisplay() {
|
||||||
if (!this.mainKey) return ''
|
if (!this.mainKey) return "";
|
||||||
// 特殊按键映射表
|
// 特殊按键映射表
|
||||||
const specialKeyMap = {
|
const specialKeyMap = {
|
||||||
'enter': '↵',
|
enter: "↵",
|
||||||
'tab': '⇥',
|
tab: "⇥",
|
||||||
'space': '␣',
|
space: "␣",
|
||||||
'backspace': '⌫',
|
backspace: "⌫",
|
||||||
'delete': '⌦',
|
delete: "⌦",
|
||||||
'escape': '⎋',
|
escape: "⎋",
|
||||||
'up': '↑',
|
up: "↑",
|
||||||
'down': '↓',
|
down: "↓",
|
||||||
'left': '←',
|
left: "←",
|
||||||
'right': '→'
|
right: "→",
|
||||||
}
|
};
|
||||||
return specialKeyMap[this.mainKey] ||
|
return (
|
||||||
(this.mainKey.length === 1 ? this.mainKey.toUpperCase() :
|
specialKeyMap[this.mainKey] ||
|
||||||
this.mainKey.charAt(0).toUpperCase() + this.mainKey.slice(1))
|
(this.mainKey.length === 1
|
||||||
}
|
? this.mainKey.toUpperCase()
|
||||||
|
: this.mainKey.charAt(0).toUpperCase() + this.mainKey.slice(1))
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
modelValue: {
|
modelValue: {
|
||||||
immediate: true,
|
immediate: true,
|
||||||
handler(val) {
|
handler(val) {
|
||||||
if (val) {
|
if (val) {
|
||||||
this.parseKeyString(val)
|
this.parseKeyString(val);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
toggleModifier(key) {
|
toggleModifier(key) {
|
||||||
this.modifiers[key] = !this.modifiers[key]
|
this.modifiers[key] = !this.modifiers[key];
|
||||||
this.updateValue()
|
this.updateValue();
|
||||||
},
|
},
|
||||||
toggleRecording() {
|
toggleRecording() {
|
||||||
if (!this.isRecording) {
|
if (!this.isRecording) {
|
||||||
this.startRecording()
|
this.startRecording();
|
||||||
} else {
|
} else {
|
||||||
this.stopRecording()
|
this.stopRecording();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
startRecording() {
|
startRecording() {
|
||||||
this.isRecording = true
|
this.isRecording = true;
|
||||||
let lastKeyTime = 0
|
let lastKeyTime = 0;
|
||||||
let lastKey = null
|
let lastKey = null;
|
||||||
|
|
||||||
this.recordEvent = (event) => {
|
this.recordEvent = (event) => {
|
||||||
event.preventDefault()
|
event.preventDefault();
|
||||||
const currentTime = Date.now()
|
const currentTime = Date.now();
|
||||||
|
|
||||||
// 重置所有修饰键状态
|
// 重置所有修饰键状态
|
||||||
Object.keys(this.modifiers).forEach(key => {
|
Object.keys(this.modifiers).forEach((key) => {
|
||||||
this.modifiers[key] = false
|
this.modifiers[key] = false;
|
||||||
})
|
});
|
||||||
|
|
||||||
// 根据操作系统设置修饰键
|
// 根据操作系统设置修饰键
|
||||||
if (isMac) {
|
if (isMac) {
|
||||||
if (event.metaKey) this.modifiers.command = true
|
if (event.metaKey) this.modifiers.command = true;
|
||||||
if (event.ctrlKey) this.modifiers.control = true
|
if (event.ctrlKey) this.modifiers.control = true;
|
||||||
} else {
|
} else {
|
||||||
if (event.ctrlKey) this.modifiers.control = true
|
if (event.ctrlKey) this.modifiers.control = true;
|
||||||
if (event.metaKey || event.winKey) this.modifiers.command = true
|
if (event.metaKey || event.winKey) this.modifiers.command = true;
|
||||||
}
|
}
|
||||||
if (event.altKey) this.modifiers.alt = true
|
if (event.altKey) this.modifiers.alt = true;
|
||||||
if (event.shiftKey) this.modifiers.shift = true
|
if (event.shiftKey) this.modifiers.shift = true;
|
||||||
|
|
||||||
// 设置主按键
|
// 设置主按键
|
||||||
let key = null
|
let key = null;
|
||||||
|
|
||||||
// 处理字母键
|
// 处理字母键
|
||||||
if (event.code.startsWith('Key')) {
|
if (event.code.startsWith("Key")) {
|
||||||
key = event.code.slice(-1).toLowerCase()
|
key = event.code.slice(-1).toLowerCase();
|
||||||
}
|
}
|
||||||
// 处理数字键
|
// 处理数字键
|
||||||
else if (event.code.startsWith('Digit')) {
|
else if (event.code.startsWith("Digit")) {
|
||||||
key = event.code.slice(-1)
|
key = event.code.slice(-1);
|
||||||
}
|
}
|
||||||
// 处理功能键
|
// 处理功能键
|
||||||
else if (event.code.startsWith('F') && !isNaN(event.code.slice(1))) {
|
else if (event.code.startsWith("F") && !isNaN(event.code.slice(1))) {
|
||||||
key = event.code.toLowerCase()
|
key = event.code.toLowerCase();
|
||||||
}
|
}
|
||||||
// 处理其他特殊键
|
// 处理其他特殊键
|
||||||
else {
|
else {
|
||||||
const keyMap = {
|
const keyMap = {
|
||||||
'ArrowUp': 'up',
|
ArrowUp: "up",
|
||||||
'ArrowDown': 'down',
|
ArrowDown: "down",
|
||||||
'ArrowLeft': 'left',
|
ArrowLeft: "left",
|
||||||
'ArrowRight': 'right',
|
ArrowRight: "right",
|
||||||
'Enter': 'enter',
|
Enter: "enter",
|
||||||
'Space': 'space',
|
Space: "space",
|
||||||
'Escape': 'escape',
|
Escape: "escape",
|
||||||
'Delete': 'delete',
|
Delete: "delete",
|
||||||
'Backspace': 'backspace',
|
Backspace: "backspace",
|
||||||
'Tab': 'tab',
|
Tab: "tab",
|
||||||
'Home': 'home',
|
Home: "home",
|
||||||
'End': 'end',
|
End: "end",
|
||||||
'PageUp': 'pageup',
|
PageUp: "pageup",
|
||||||
'PageDown': 'pagedown',
|
PageDown: "pagedown",
|
||||||
'Control': 'control',
|
Control: "control",
|
||||||
'Alt': 'alt',
|
Alt: "alt",
|
||||||
'Shift': 'shift',
|
Shift: "shift",
|
||||||
'Meta': 'command'
|
Meta: "command",
|
||||||
}
|
};
|
||||||
key = keyMap[event.code] || event.key.toLowerCase()
|
key = keyMap[event.code] || event.key.toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理双击修饰键
|
// 处理双击修饰键
|
||||||
if (['control', 'alt', 'shift', 'command'].includes(key)) {
|
if (["control", "alt", "shift", "command"].includes(key)) {
|
||||||
if (key === lastKey && (currentTime - lastKeyTime) < 500) {
|
if (key === lastKey && currentTime - lastKeyTime < 500) {
|
||||||
this.mainKey = key
|
this.mainKey = key;
|
||||||
this.modifiers[key] = false // 清除修饰键状态
|
this.modifiers[key] = false; // 清除修饰键状态
|
||||||
this.stopRecording()
|
this.stopRecording();
|
||||||
this.updateValue()
|
this.updateValue();
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
lastKey = key
|
lastKey = key;
|
||||||
lastKeyTime = currentTime
|
lastKeyTime = currentTime;
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理空格键和其他按键
|
// 处理空格键和其他按键
|
||||||
if (key === 'space' || !['meta', 'control', 'shift', 'alt', 'command'].includes(key)) {
|
if (
|
||||||
this.mainKey = key
|
key === "space" ||
|
||||||
this.stopRecording()
|
!["meta", "control", "shift", "alt", "command"].includes(key)
|
||||||
this.updateValue()
|
) {
|
||||||
|
this.mainKey = key;
|
||||||
|
this.stopRecording();
|
||||||
|
this.updateValue();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
document.addEventListener('keydown', this.recordEvent)
|
document.addEventListener("keydown", this.recordEvent);
|
||||||
},
|
},
|
||||||
stopRecording() {
|
stopRecording() {
|
||||||
this.isRecording = false
|
this.isRecording = false;
|
||||||
document.removeEventListener('keydown', this.recordEvent)
|
document.removeEventListener("keydown", this.recordEvent);
|
||||||
},
|
},
|
||||||
updateValue() {
|
updateValue() {
|
||||||
if (!this.mainKey) return
|
if (!this.mainKey) return;
|
||||||
const activeModifiers = Object.entries(this.modifiers)
|
const activeModifiers = Object.entries(this.modifiers)
|
||||||
.filter(([_, active]) => active)
|
.filter(([_, active]) => active)
|
||||||
.map(([key]) => key)
|
.map(([key]) => key)
|
||||||
// 在非 Mac 系统上,将 command 转换为 meta
|
// 在非 Mac 系统上,将 command 换为 meta
|
||||||
.map(key => !isMac && key === 'command' ? 'meta' : key)
|
.map((key) => (!isMac && key === "command" ? "meta" : key));
|
||||||
|
|
||||||
const args = [this.mainKey, ...activeModifiers]
|
const args = [this.mainKey, ...activeModifiers];
|
||||||
this.$emit('update:modelValue', args.join('","'))
|
// 为每个参数添加引号
|
||||||
|
this.$emit("update:modelValue", `"${args.join('","')}"`);
|
||||||
},
|
},
|
||||||
parseKeyString(val) {
|
parseKeyString(val) {
|
||||||
try {
|
try {
|
||||||
const args = val.split('","')
|
// 移除开头和结尾的引号
|
||||||
|
const cleanVal = val.replace(/^"|"$/g, "");
|
||||||
|
// 分割并移除每个参数的引号
|
||||||
|
const args = cleanVal
|
||||||
|
.split('","')
|
||||||
|
.map((arg) => arg.replace(/^"|"$/g, ""));
|
||||||
if (args.length > 0) {
|
if (args.length > 0) {
|
||||||
this.mainKey = args[0]
|
this.mainKey = args[0];
|
||||||
Object.keys(this.modifiers).forEach(key => {
|
Object.keys(this.modifiers).forEach((key) => {
|
||||||
// 在非 Mac 系统上,将 meta 转换为 command
|
// 在非 Mac 系统上,将 meta 转换为 command
|
||||||
const modKey = !isMac && args.includes('meta') ? 'command' : key
|
const modKey = !isMac && args.includes("meta") ? "command" : key;
|
||||||
this.modifiers[key] = args.includes(modKey)
|
this.modifiers[key] = args.includes(modKey);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to parse key string:', e)
|
console.error("Failed to parse key string:", e);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
filterFn(val, update, abort) {
|
||||||
})
|
// 如果是直接输入(长度为1),则中止过滤
|
||||||
|
if (val.length === 1) {
|
||||||
|
abort();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 否则只在输入内容匹配预设选项时显示下拉列表
|
||||||
|
update(() => {
|
||||||
|
const needle = val.toLowerCase();
|
||||||
|
const matchedOptions = this.commonKeys.filter(
|
||||||
|
(key) =>
|
||||||
|
key.value === needle || key.label.toLowerCase().includes(needle)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleKeyInput(val) {
|
||||||
|
if (val === null) {
|
||||||
|
this.mainKey = "";
|
||||||
|
} else if (typeof val === "string") {
|
||||||
|
// 检查是否是预设选项
|
||||||
|
const matchedOption = this.commonKeys.find(
|
||||||
|
(key) => key.value === val.toLowerCase()
|
||||||
|
);
|
||||||
|
if (matchedOption) {
|
||||||
|
this.mainKey = matchedOption.value;
|
||||||
|
} else {
|
||||||
|
this.mainKey = val.charAt(0).toLowerCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.updateValue();
|
||||||
|
},
|
||||||
|
handleInput(val) {
|
||||||
|
// 直接输入时,取第一个字符并更新值
|
||||||
|
if (val) {
|
||||||
|
this.mainKey = val.charAt(0).toLowerCase();
|
||||||
|
this.updateValue();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@ -325,38 +349,9 @@ export default defineComponent({
|
|||||||
margin: 0 2px;
|
margin: 0 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-key-container {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
min-width: 60px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-key {
|
.main-key {
|
||||||
|
height: 24px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 500;
|
margin: 0 2px;
|
||||||
color: var(--q-primary);
|
|
||||||
line-height: 24px;
|
|
||||||
padding: 0 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.q-field__control) {
|
|
||||||
padding: 0 8px;
|
|
||||||
min-height: 36px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.q-field__prepend) {
|
|
||||||
padding-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.q-field__native) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.q-field__control-container) {
|
|
||||||
padding: 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user