feat(setting): 新增发送信息快捷键

优化表情框
This commit is contained in:
nongyehong 2024-04-13 02:30:22 +08:00
parent baae17e03b
commit eba6395966
12 changed files with 225 additions and 82 deletions

View File

@ -7,7 +7,7 @@
<title>HuLa</title>
<!--引入iconpark图标库-->
<script defer src="https://lf1-cdn-tos.bytegoofy.com/obj/iconpark/svg_30895_70.224ae5c926a3c7f59fc8f9cbe5bfaa9c.js"></script>
<script defer src="https://lf1-cdn-tos.bytegoofy.com/obj/iconpark/svg_30895_72.a7fc795dcfe2d7ad7f910f611b19e32c.js"></script>
</head>
<body>

View File

@ -31,7 +31,7 @@ onMounted(() => {
// sendNotification({ title: 'TAURI', body: 'Tauri is awesome!' })
// localStorage
if (!localStorage.getItem(StoresEnum.SETTING)) {
settingStore.init(ThemeEnum.LIGHT)
settingStore.initTheme(ThemeEnum.OS)
}
/* 第一次没有选状态的时候随机选中一个状态 */
if (!localStorage.getItem(StoresEnum.ONLINE_STATUS)) {
@ -43,8 +43,8 @@ onMounted(() => {
if (process.env.NODE_ENV !== 'development') {
/* 禁用浏览器默认的快捷键 */
window.addEventListener('keydown', (e) => {
// ctrl+c ctrl+v
if (e.ctrlKey && (e.key === 'c' || e.key === 'v')) return
// ctrl+c ctrl+v ctrl+enter
if (e.ctrlKey && (e.key === 'c' || e.key === 'v' || e.key === 'Enter')) return
if (e.ctrlKey || e.metaKey || e.altKey) {
e.preventDefault()
}

View File

@ -257,17 +257,23 @@ const menuList = ref<OPT.RightMenu[]>([
click: () => {}
},
{ label: '收藏', icon: 'collection-files' },
{ label: '回复', icon: 'reply' }
{
label: '回复',
icon: 'reply',
click: (item: any) => {
console.log(item)
}
}
])
/* 右键菜单下划线后的列表 */
const specialMenuList = ref<OPT.RightMenu[]>([
{
label: '删除',
icon: 'delete',
click: (key: number) => {
click: (item: any) => {
tips.value = '删除后将不会出现在你的消息记录中,确定删除吗?'
modalShow.value = true
delIndex.value = key
delIndex.value = item.key
}
}
])

View File

@ -1,37 +1,59 @@
<template>
<n-scrollbar style="max-height: 335px" class="rounded-8px p-14px box-border w-450px h-340px">
<div v-if="emojiRef.historyList?.length > 0">
<span class="text-12px text-[--text-color]">最近使用</span>
<n-flex align="center" class="mt-12px mb-12px">
<n-flex
align="center"
justify="center"
class="emoji-item"
v-for="(item, index) in [...new Set(emojiRef.historyList)]"
:key="index"
@click.stop="chooseEmoji(item)">
{{ item }}
</n-flex>
</n-flex>
</div>
<div v-for="items in emojiObj" :key="items?.name">
<template v-if="items?.name && items.value?.length">
<span class="text-12px text-[--text-color]">{{ items.name }}</span>
<n-flex align="center" class="mt-12px mb-12px">
<n-flex
align="center"
justify="center"
class="emoji-item"
v-for="(item, index) in items.value"
:key="index"
@click.stop="chooseEmoji(item)">
{{ item }}
<n-scrollbar style="max-height: 290px" class="p-[14px_14px_0_14px] box-border w-450px h-290px">
<transition name="fade" mode="out-in" appear>
<!-- 默认表情页面 -->
<div v-if="activeIndex === 0">
<!-- 最近使用 -->
<div v-if="emojiRef.historyList?.length > 0">
<span class="text-12px text-[--text-color]">最近使用</span>
<n-flex align="center" class="mt-12px mb-12px">
<n-flex
align="center"
justify="center"
class="emoji-item"
v-for="(item, index) in [...new Set(emojiRef.historyList)]"
:key="index"
@click.stop="chooseEmoji(item)">
{{ item }}
</n-flex>
</n-flex>
</n-flex>
</template>
</div>
</div>
<!-- 表情 -->
<div v-for="items in emojiObj" :key="items?.name">
<template v-if="items?.name && items.value?.length">
<span class="text-12px text-[--text-color]">{{ items.name }}</span>
<n-flex align="center" class="mt-12px mb-12px">
<n-flex
align="center"
justify="center"
class="emoji-item"
v-for="(item, index) in items.value"
:key="index"
@click.stop="chooseEmoji(item)">
{{ item }}
</n-flex>
</n-flex>
</template>
</div>
</div>
</transition>
<transition name="fade" mode="out-in">
<!-- 我的喜欢页面 -->
<div v-if="activeIndex === 1">
<span>暂无实现</span>
</div>
</transition>
</n-scrollbar>
<!-- 底部选项 -->
<n-flex align="center" class="expression-item">
<svg :class="{ active: activeIndex === 0 }" @click="activeIndex = 0">
<use href="#face"></use>
</svg>
<svg :class="{ active: activeIndex === 1 }" @click="activeIndex = 1"><use href="#heart"></use></svg>
</n-flex>
</template>
<script setup lang="ts">
import { getAllTypeEmojis } from '@/utils/Emoji.ts'
@ -50,6 +72,7 @@ interface EmojiItem {
const historyStore = history()
const { emoji } = storeToRefs(historyStore)
const activeIndex = ref(0)
const emit = defineEmits(['emojiHandle'])
const props = defineProps<{
@ -98,7 +121,7 @@ const chooseEmoji = (item: string) => {
if (emojiRef.historyList.length > 18) {
emojiRef.historyList.splice(18) // 18
}
historyStore.setEmoji([...emojiRef.historyList]) // 使 Set
historyStore.setEmoji([...emojiRef.historyList])
emit('emojiHandle', item)
return item
}
@ -113,4 +136,27 @@ const chooseEmoji = (item: string) => {
.emoji-item {
@apply size-36px cursor-pointer text-26px hover:bg-[--emoji-hover] rounded-8px;
}
.expression-item {
@apply h-50px w-full p-[0_14px];
border-top: 1px solid var(--line-color);
svg {
@apply size-26px p-8px rounded-8px;
&:not(.active):hover {
background-color: var(--emoji-hover);
cursor: pointer;
}
}
}
.active {
background-color: #13987f;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.4s ease-in-out;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>

View File

@ -9,7 +9,8 @@
spellcheck="false"
@paste="handlePaste"
@input="handleInput"
@keydown.enter="inputKeyDown"></div>
@keydown.exact.enter="inputKeyDown"
@keydown.exact.ctrl.enter="inputKeyDown"></div>
</n-scrollbar>
</ContextMenu>
@ -47,23 +48,41 @@
</n-button>
<n-button color="#13987f" class="p-[0_6px]">
<template #icon>
<svg class="w-22px h-22px"><use href="#down"></use></svg>
<n-config-provider :theme="themes.content === ThemeEnum.DARK ? darkTheme : lightTheme">
<n-popselect
v-model:show="arrow"
v-model:value="chatKey"
:options="sendOptions"
trigger="click"
placement="top-end">
<svg @click="arrow = true" v-if="!arrow" class="w-22px h-22px outline-none"><use href="#down"></use></svg>
<svg @click="arrow = false" v-else class="w-22px h-22px outline-none"><use href="#up"></use></svg>
</n-popselect>
</n-config-provider>
</template>
</n-button>
</n-button-group>
</n-config-provider>
</template>
<script setup lang="ts">
import { lightTheme } from 'naive-ui'
import { MittEnum, MsgEnum, RoomTypeEnum } from '@/enums'
import { lightTheme, darkTheme } from 'naive-ui'
import { MittEnum, MsgEnum, RoomTypeEnum, ThemeEnum } from '@/enums'
import Mitt from '@/utils/Bus.ts'
import { createFileOrVideoDom } from '@/utils/CreateDom.ts'
import { MockList } from '@/mock'
import { MockItem } from '@/services/types.ts'
import { useDebounceFn } from '@vueuse/core'
import { emit, listen } from '@tauri-apps/api/event'
import { setting } from '@/stores/setting.ts'
import { storeToRefs } from 'pinia'
import { sendOptions } from '@/views/home-window/more/settings/config.ts'
const settingStore = setting()
const { themes, chat } = storeToRefs(settingStore)
const chatKey = ref(chat.value.sendKey)
const ait = ref(false)
/* 发送按钮旁的箭头 */
const arrow = ref(false)
const menuList = ref([
{ label: '剪切', icon: 'screenshot', disabled: true },
{ label: '复制', icon: 'copy', disabled: true },
@ -108,6 +127,10 @@ const filteredList = computed(() => {
}
})
watchEffect(() => {
chatKey.value = chat.value.sendKey
})
/* 当切换聊天对象时,重新获取焦点 */
watch(activeItem, () => {
nextTick(() => {
@ -116,6 +139,10 @@ watch(activeItem, () => {
})
})
watch(chatKey, (v) => {
chat.value.sendKey = v
})
/**
* 将指定节点插入到光标位置
* @param { MsgEnum } type 插入的类型
@ -334,18 +361,15 @@ const handleInput = useDebounceFn((e: Event) => {
/* input的keydown事件 */
const inputKeyDown = (e: KeyboardEvent) => {
const Enter = 'Enter'
if (
(e.ctrlKey && e.key === 'Enter') ||
(e.shiftKey && e.key === 'Enter') ||
(e.metaKey && e.key === 'Enter') ||
msgInput.value === '' ||
msgInput.value.trim() === ''
) {
e.preventDefault()
if (msgInput.value === '' || msgInput.value.trim() === '') {
e?.preventDefault()
return
} else if (e.key === Enter) {
e.preventDefault()
}
if (
(chat.value.sendKey === 'Enter' && e.key === 'Enter' && !e.ctrlKey) ||
(chat.value.sendKey === 'Ctrl+Enter' && e.ctrlKey && e.key === 'Enter')
) {
e?.preventDefault()
send()
}
}

View File

@ -6,6 +6,8 @@ import { MockItem } from '@/services/types.ts'
import { delay } from 'lodash-es'
import { MockList } from '@/mock'
import { WebviewWindow } from '@tauri-apps/api/window'
import { setting } from '@/stores/setting.ts'
import { storeToRefs } from 'pinia'
const { createWebviewWindow, checkWinExist } = useWindow()
/* 建议把此状态存入localStorage中 */
@ -16,6 +18,8 @@ const aloneWin = ref(new Set())
const shrinkStatus = ref(false)
const itemRef = ref({} as MockItem)
export const useMessage = () => {
const settingStore = setting()
const { chat } = storeToRefs(settingStore)
/* 监听独立窗口关闭事件 */
watchEffect(() => {
Mitt.on(MittEnum.SHRINK_WINDOW, async (event) => {
@ -43,6 +47,7 @@ export const useMessage = () => {
/* 处理双击事件 */
const handleMsgDblclick = (item: MockItem) => {
if (!chat.value.isDouble) return
delay(async () => {
await openAloneWin(item)
}, 300)

View File

@ -27,11 +27,18 @@ export const setting = defineStore(StoresEnum.SETTING, {
name: '',
avatar: ''
}
},
/* 聊天设置 */
chat: {
/* 发送快捷键 */
sendKey: 'Enter',
/* 是否双击打开独立会话窗口 */
isDouble: true
}
}),
actions: {
/* 初始化 */
init(theme: string) {
/* 初始化主题 */
initTheme(theme: string) {
this.themes.content = theme
document.documentElement.dataset.theme = theme
this.themes.pattern = theme

View File

@ -182,6 +182,14 @@ html[data-theme='dark'] {
right: 0;
}
/*! 修改naive-ui Popselect选中的样式 */
.n-base-select-menu .n-base-select-option.n-base-select-option--selected {
color: #13987f;
}
.n-base-select-menu .n-base-select-option .n-base-select-option__check {
color: #13987f;
}
/*! 通用菜单项目样式 */
@mixin menu-item-style($position: fixed) {
position: $position;

View File

@ -44,14 +44,17 @@ declare module 'vue' {
NModal: typeof import('naive-ui')['NModal']
NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
NPopover: typeof import('naive-ui')['NPopover']
NPopselect: typeof import('naive-ui')['NPopselect']
NQrCode: typeof import('naive-ui')['NQrCode']
NRadio: typeof import('naive-ui')['NRadio']
NScrollbar: typeof import('naive-ui')['NScrollbar']
NSelect: typeof import('naive-ui')['NSelect']
NSkeleton: typeof import('naive-ui')['NSkeleton']
NSwitch: typeof import('naive-ui')['NSwitch']
NTabPane: typeof import('naive-ui')['NTabPane']
NTabs: typeof import('naive-ui')['NTabs']
NTooltip: typeof import('naive-ui')['NTooltip']
NUpload: typeof import('naive-ui')['NUpload']
NVirtualList: typeof import('naive-ui')['NVirtualList']
RenderMessage: typeof import('./../components/rightBox/renderMessage/index.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']

View File

@ -27,6 +27,13 @@ declare namespace STO {
avatar: string
}
}
/* 聊天设置 */
chat: {
/* 发送快捷键 */
sendKey: string
/* 是否双击打开独立会话窗口 */
isDouble: boolean
}
}
/* 置顶 */

View File

@ -27,36 +27,60 @@
<n-flex vertical class="text-[--text-color] text-14px" :size="16">
<span class="pl-10px">系统</span>
<n-flex class="item" :size="15">
<n-flex class="item" :size="15" vertical>
<!-- 关闭面板 -->
<n-flex vertical :size="15" class="w-full">
<n-flex align="center" justify="space-between" :size="20" class="h-20px">
<span>关闭主面板</span>
<n-flex align="center" justify="space-between">
<span>关闭主面板</span>
<label class="text-14px text-#707070 flex gap-6px lh-16px items-center">
<n-radio :checked="tips.type === CloseBxEnum.HIDE" @change="tips.type = CloseBxEnum.HIDE" />
<span>最小化到系统托盘</span>
</label>
<label class="text-14px text-#707070 flex gap-6px lh-16px items-center">
<n-radio :checked="tips.type === CloseBxEnum.CLOSE" @change="tips.type = CloseBxEnum.CLOSE" />
<span>直接退出程序</span>
</label>
<label class="text-14px text-#707070 flex gap-6px lh-16px items-center">
<n-radio :checked="tips.type === CloseBxEnum.HIDE" @change="tips.type = CloseBxEnum.HIDE" />
<span>最小化到系统托盘</span>
</label>
<label class="text-14px text-#707070 flex gap-6px lh-16px items-center">
<n-radio :checked="tips.type === CloseBxEnum.CLOSE" @change="tips.type = CloseBxEnum.CLOSE" />
<span>直接退出程序</span>
</label>
<label class="text-12px text-#909090 flex gap-6px justify-end items-center">
<n-checkbox size="small" v-model:checked="tips.notTips" />
<span>是否关闭提示</span>
</label>
</n-flex>
<span class="w-full h-1px bg-[--line-color]"></span>
<label class="text-12px text-#909090 flex gap-6px justify-end items-center">
<n-checkbox size="small" v-model:checked="tips.notTips" />
<span>是否关闭提示</span>
</label>
</n-flex>
<!-- ESC关闭面板 -->
<n-flex vertical :size="15" class="w-full">
<n-flex align="center" justify="space-between" :size="20" class="h-20px">
<span>是否启用ESC关闭窗口</span>
<span class="w-full h-1px bg-[--line-color]"></span>
<n-switch size="small" v-model:value="escClose" />
</n-flex>
<!-- ESC关闭面板 -->
<n-flex align="center" justify="space-between">
<span>是否启用ESC关闭窗口</span>
<n-switch size="small" v-model:value="escClose" />
</n-flex>
</n-flex>
</n-flex>
<!-- 聊天设置 -->
<n-flex vertical class="text-[--text-color] text-14px" :size="16">
<span class="pl-10px">聊天</span>
<n-flex class="item" :size="15" vertical>
<!-- 发送信息 -->
<n-flex align="center" justify="space-between">
<span>发送信息快捷键</span>
<n-select
class="w-140px"
size="small"
label-field="value"
v-model:value="chat.sendKey"
:options="sendOptions" />
</n-flex>
<span class="w-full h-1px bg-[--line-color]"></span>
<!-- 双击打开独立会话 -->
<n-flex align="center" justify="space-between">
<span>双击会话列表打开独立聊天窗口</span>
<n-switch size="small" v-model:value="chat.isDouble" />
</n-flex>
</n-flex>
</n-flex>
@ -67,9 +91,10 @@ import { setting } from '@/stores/setting.ts'
import { storeToRefs } from 'pinia'
import { CloseBxEnum, ThemeEnum } from '@/enums'
import { titleList } from './model.tsx'
import { sendOptions } from './config.ts'
const settingStore = setting()
const { themes, tips, escClose } = storeToRefs(settingStore)
const { themes, tips, escClose, chat } = storeToRefs(settingStore)
const activeItem = ref<string>(themes.value.pattern)
/* 切换主题 */

View File

@ -22,4 +22,16 @@ const sideOptions = ref<OPT.L.SettingSide[]>([
}
])
export { sideOptions }
/* 发送按钮快捷键的选项 */
const sendOptions = [
{
label: '按 Enter 键发送消息',
value: 'Enter'
},
{
label: '按 Ctrl + Enter 键发送消息',
value: 'Ctrl+Enter'
}
]
export { sideOptions, sendOptions }