mirror of
https://gitee.com/HuLaSpark/HuLa.git
synced 2024-12-01 19:28:07 +08:00
✨ feat(setting): 新增发送信息快捷键
优化表情框
This commit is contained in:
parent
baae17e03b
commit
eba6395966
@ -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>
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
])
|
||||
|
@ -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>
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
3
src/typings/components.d.ts
vendored
3
src/typings/components.d.ts
vendored
@ -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']
|
||||
|
7
src/typings/stores.d.ts
vendored
7
src/typings/stores.d.ts
vendored
@ -27,6 +27,13 @@ declare namespace STO {
|
||||
avatar: string
|
||||
}
|
||||
}
|
||||
/* 聊天设置 */
|
||||
chat: {
|
||||
/* 发送快捷键 */
|
||||
sendKey: string
|
||||
/* 是否双击打开独立会话窗口 */
|
||||
isDouble: boolean
|
||||
}
|
||||
}
|
||||
|
||||
/* 置顶 */
|
||||
|
@ -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)
|
||||
|
||||
/* 切换主题 */
|
||||
|
@ -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 }
|
||||
|
Loading…
Reference in New Issue
Block a user