feat(component): 增加表情包功能

This commit is contained in:
Dawn
2025-03-18 10:41:00 +08:00
parent b019b479bd
commit f0e9f27c5f
13 changed files with 181 additions and 154 deletions
-45
View File
@@ -1,45 +0,0 @@
name: Codecov
on:
pull_request:
branches: [ master ]
paths:
- 'src/**'
- 'test/**'
jobs:
codecov:
# 跳过 Renovate PR
if: |
github.actor != 'renovate[bot]' &&
github.actor != 'renovate-preview[bot]'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# 首先安装 pnpm
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9
run_install: false
# 然后设置 Node.js
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
- name: Run tests with coverage
run: pnpm coverage
- name: Upload results to Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
slug: HuLaSpark/HuLa
+1 -3
View File
@@ -5,9 +5,6 @@
<p align="center">An Instant Messaging System Built with Tauri, Vite 6, Vue 3, and TypeScript</p>
<div align="center">
<a href="https://codecov.io/gh/HuLaSpark/HuLa" >
<img src="https://codecov.io/gh/HuLaSpark/HuLa/branch/fix%2Fglobal/graph/badge.svg?token=NM0Z13M8AZ"/>
</a>
<a href="https://app.fossa.com/projects/git%2Bgithub.com%2FHuLaSpark%2FHuLa?ref=badge_shield"><img src="https://app.fossa.com/api/projects/git%2Bgithub.com%2FHuLaSpark%2FHuLa.svg?type=shield" alt=""></a>
<a href="https://www.bestpractices.dev/zh-CN/projects/9692"><img src="https://bestpractices.coreinfrastructure.org/projects/9692/badge" alt="CI"></a>
<img src="https://img.shields.io/badge/TypeScript-blue?logo=Typescript&style=flat&logoColor=fff">
@@ -89,6 +86,7 @@ HuLa is an instant messaging system developed with Tauri, Vite 6, Vue 3, and Typ
| | Message Recall | ✅ Completed |
| | @Mention, Reply Function | ✅ Completed |
| | Message Read Status | ✅ Completed |
| | Emoji Function | ✅ Completed |
| | Context Menu Based on Message Type | ✅ Completed |
| 👥 **Contact & Group Management** | Friend Add/Remove | ✅ Completed |
| | Friend Search | ✅ Completed |
+1 -3
View File
@@ -5,9 +5,6 @@
<p align="center">一款基于Tauri、Vite 6、Vue 3 和 TypeScript 构建的即时通讯系统</p>
<div align="center">
<a href="https://codecov.io/gh/HuLaSpark/HuLa" >
<img src="https://codecov.io/gh/HuLaSpark/HuLa/branch/fix%2Fglobal/graph/badge.svg?token=NM0Z13M8AZ"/>
</a>
<a href="https://app.fossa.com/projects/git%2Bgithub.com%2FHuLaSpark%2FHuLa?ref=badge_shield"><img src="https://app.fossa.com/api/projects/git%2Bgithub.com%2FHuLaSpark%2FHuLa.svg?type=shield" alt=""></a>
<a href="https://www.bestpractices.dev/zh-CN/projects/9692"><img src="https://bestpractices.coreinfrastructure.org/projects/9692/badge" alt="CI"></a>
<img src="https://img.shields.io/badge/TypeScript-blue?logo=Typescript&style=flat&logoColor=fff">
@@ -89,6 +86,7 @@ HuLa 是一款基于 Tauri、Vite 6、Vue 3 和 TypeScript 构建的即时通讯
| | 消息撤回 | ✅ 已完成 |
| | @提醒、回复功能 | ✅ 已完成 |
| | 消息已读状态 | ✅ 已完成 |
| | 表情包功能 | ✅ 已完成 |
| | 根据消息类型右键菜单 | ✅ 已完成 |
| 👥 **联系人、群聊管理** | 好友添加与删除 | ✅ 已完成 |
| | 好友搜索 | ✅ 已完成 |
-1
View File
@@ -57,7 +57,6 @@
},
"dependencies": {
"@actions/github": "^6.0.0",
"@codecov/vite-plugin": "^1.6.0",
"@fingerprintjs/fingerprintjs": "^4.6.0",
"@tauri-apps/api": "2.3.0",
"@tauri-apps/plugin-autostart": "2.2.0",
-62
View File
@@ -11,9 +11,6 @@ importers:
'@actions/github':
specifier: ^6.0.0
version: 6.0.0
'@codecov/vite-plugin':
specifier: ^1.6.0
version: 1.7.0(vite@6.2.2(@types/node@22.10.7)(jiti@2.4.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1))
'@fingerprintjs/fingerprintjs':
specifier: ^4.6.0
version: 4.6.0
@@ -240,21 +237,12 @@ importers:
packages:
'@actions/core@1.11.1':
resolution: {integrity: sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==}
'@actions/exec@1.1.1':
resolution: {integrity: sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==}
'@actions/github@6.0.0':
resolution: {integrity: sha512-alScpSVnYmjNEXboZjarjukQEzgCRmjMv6Xj47fsdnqGS73bjJNDpiiXmp8jr0UZLdUB6d9jW63IcmddUP+l0g==}
'@actions/http-client@2.2.3':
resolution: {integrity: sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==}
'@actions/io@1.1.3':
resolution: {integrity: sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==}
'@ampproject/remapping@2.3.0':
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
engines: {node: '>=6.0.0'}
@@ -443,16 +431,6 @@ packages:
resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==}
engines: {node: '>=18'}
'@codecov/bundler-plugin-core@1.7.0':
resolution: {integrity: sha512-QsLwtwfy9KEe0CjqNE2Z/SPiCMn4CHAJ9cqTosZCX9YMKPi/WyFivv0pYE7CXA8ntG0l4Xc9kr36DUCuNRW0LQ==}
engines: {node: '>=18.0.0'}
'@codecov/vite-plugin@1.7.0':
resolution: {integrity: sha512-Cy/NxuEiGCf9X62dKewxnFf6Uv6CxfCCJAd8AfbOYbg96vwjwuq6BMoCIAk9gUFmwurw9EOKGPwoW0lIrUMIzA==}
engines: {node: '>=18.0.0'}
peerDependencies:
vite: 4.x || 5.x || 6.x
'@commitlint/cli@19.6.1':
resolution: {integrity: sha512-8hcyA6ZoHwWXC76BoC8qVOSr8xHy00LZhZpauiD0iO0VYbVhMnED0da85lTfIULxl7Lj4c6vZgF0Wu/ed1+jlQ==}
engines: {node: '>=v18'}
@@ -4810,10 +4788,6 @@ packages:
'@nuxt/kit':
optional: true
unplugin@1.16.0:
resolution: {integrity: sha512-5liCNPuJW8dqh3+DM6uNM2EI3MLLpCKp/KY+9pB5M2S2SR2qvvDHhKgBOaTWEbZTAws3CXfB0rKTIolWKL05VQ==}
engines: {node: '>=14.0.0'}
unplugin@1.16.1:
resolution: {integrity: sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==}
engines: {node: '>=14.0.0'}
@@ -5127,20 +5101,8 @@ packages:
resolution: {integrity: sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==}
engines: {node: '>=18'}
zod@3.24.1:
resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==}
snapshots:
'@actions/core@1.11.1':
dependencies:
'@actions/exec': 1.1.1
'@actions/http-client': 2.2.3
'@actions/exec@1.1.1':
dependencies:
'@actions/io': 1.1.3
'@actions/github@6.0.0':
dependencies:
'@actions/http-client': 2.2.3
@@ -5153,8 +5115,6 @@ snapshots:
tunnel: 0.0.6
undici: 5.28.5
'@actions/io@1.1.3': {}
'@ampproject/remapping@2.3.0':
dependencies:
'@jridgewell/gen-mapping': 0.3.8
@@ -5433,21 +5393,6 @@ snapshots:
'@bcoe/v8-coverage@1.0.2': {}
'@codecov/bundler-plugin-core@1.7.0':
dependencies:
'@actions/core': 1.11.1
'@actions/github': 6.0.0
chalk: 4.1.2
semver: 7.7.1
unplugin: 1.16.0
zod: 3.24.1
'@codecov/vite-plugin@1.7.0(vite@6.2.2(@types/node@22.10.7)(jiti@2.4.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1))':
dependencies:
'@codecov/bundler-plugin-core': 1.7.0
unplugin: 1.16.0
vite: 6.2.2(@types/node@22.10.7)(jiti@2.4.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1)
'@commitlint/cli@19.6.1(@types/node@22.10.7)(typescript@5.7.2)':
dependencies:
'@commitlint/format': 19.5.0
@@ -10061,11 +10006,6 @@ snapshots:
- rollup
- supports-color
unplugin@1.16.0:
dependencies:
acorn: 8.14.0
webpack-virtual-modules: 0.6.2
unplugin@1.16.1:
dependencies:
acorn: 8.14.0
@@ -10416,5 +10356,3 @@ snapshots:
yocto-queue@1.1.1: {}
yoctocolors-cjs@2.1.2: {}
zod@3.24.1: {}
+1
View File
@@ -537,6 +537,7 @@ defineExpose<VirtualListExpose>({
padding-right: 6px;
/* 兼容性写法,确保在各种浏览器中都有一致的滚动条空间 */
overflow-y: scroll;
overscroll-behavior: none; /* 禁止mac的“触底反弹”效果 */
/* 滚动条样式 */
&::-webkit-scrollbar {
+68 -15
View File
@@ -28,13 +28,33 @@
-webkit-backdrop-filter: blur(10px);
box-shadow: 2px 2px 12px 2px var(--box-shadow-color);
border: 1px solid var(--box-shadow-color);
width: auto;
">
<template #trigger>
<n-popover trigger="hover" :show-arrow="false" placement="bottom">
<n-popover
v-model:show="recentlyTip"
trigger="hover"
:delay="800"
:duration="100"
:show-arrow="false"
:disabled="emojiShow"
placement="top">
<template #trigger>
<svg class="mr-18px"><use href="#smiling-face"></use></svg>
</template>
<span>表情</span>
<div v-if="recentEmojis.length > 0" class="p-4px">
<div class="text-xs text-gray-500 mb-4px">最近使用</div>
<div class="flex flex-wrap gap-8px max-w-212px">
<div
v-for="(emoji, index) in recentEmojis"
:key="index"
class="emoji-item cursor-pointer flex-center"
@click="emojiHandle(emoji, checkIsUrl(emoji) ? 'emoji-url' : 'emoji')">
<img v-if="checkIsUrl(emoji)" :src="emoji" class="size-24px" />
<span v-else class="text-18px">{{ emoji }}</span>
</div>
</div>
</div>
</n-popover>
</template>
<Emoticon @emojiHandle="emojiHandle" :all="false" />
@@ -109,17 +129,36 @@ import { emitTo } from '@tauri-apps/api/event'
import { useGlobalStore } from '@/stores/global.ts'
import type { ContactItem, SessionItem } from '@/services/types'
import { useContactStore } from '@/stores/contacts'
import { useHistoryStore } from '@/stores/history'
const { id } = defineProps<{
id: SessionItem['id']
}>()
const globalStore = useGlobalStore()
const contactStore = useContactStore()
const historyStore = useHistoryStore()
const { open, onChange, reset } = useFileDialog()
const MsgInputRef = ref()
const msgInputDom = ref<HTMLInputElement | null>(null)
const emojiShow = ref()
const emojiShow = ref(false)
const recentlyTip = ref(false)
const recentEmojis = computed(() => {
return historyStore.emoji.slice(0, 15)
})
const { insertNodeAtRange, triggerInputEvent, imgPaste, FileOrVideoPaste } = useCommon()
/**
* 检查字符串是否为URL
*/
const checkIsUrl = (str: string) => {
try {
new URL(str)
return true
} catch {
return false
}
}
// 判断是否为单聊
const isSingleChat = computed(() => {
return globalStore.currentSession?.type === RoomTypeEnum.SINGLE
@@ -131,6 +170,13 @@ const isFriend = computed(() => {
return contactStore.contactsList.some((contact: ContactItem) => contact.uid === id)
})
// 监听emojiShow的变化,当emojiShow为true时关闭recentlyTip
watch(emojiShow, (newValue) => {
if (newValue === true) {
recentlyTip.value = false
}
})
/**
* 选择表情,并把表情插入输入框
* @param item 选择的表情
@@ -145,16 +191,6 @@ const emojiHandle = (item: string, type: 'emoji' | 'emoji-url' = 'emoji') => {
// 确保输入框有焦点
inp.focus()
// 检查是否为 URL
const isUrl = (str: string) => {
try {
new URL(str)
return true
} catch {
return false
}
}
// 尝试获取最后的编辑范围
let lastEditRange: SelectionRange | null = MsgInputRef.value?.getLastEditRange()
@@ -186,7 +222,7 @@ const emojiHandle = (item: string, type: 'emoji' | 'emoji-url' = 'emoji') => {
}
// 根据内容类型插入不同的节点
if (isUrl(item)) {
if (checkIsUrl(item)) {
// 如果是URL,创建图片元素并插入
const imgElement = document.createElement('img')
imgElement.src = item
@@ -215,6 +251,23 @@ const emojiHandle = (item: string, type: 'emoji' | 'emoji-url' = 'emoji') => {
// 保持焦点在输入框
inp.focus()
// 添加到最近使用表情列表
updateRecentEmojis(item)
}
/**
* 更新最近使用的表情列表
*/
const updateRecentEmojis = (emoji: string) => {
const currentEmojis = [...historyStore.emoji]
const index = currentEmojis.indexOf(emoji)
if (index !== -1) {
currentEmojis.splice(index, 1)
}
currentEmojis.unshift(emoji)
const updatedEmojis = currentEmojis.slice(0, 15)
historyStore.setEmoji(updatedEmojis)
}
const handleCap = async () => {
@@ -250,7 +303,7 @@ onChange((files) => {
reset()
})
onMounted(() => {
onMounted(async () => {
if (MsgInputRef.value) {
msgInputDom.value = MsgInputRef.value.messageInputDom
}
+70 -5
View File
@@ -11,7 +11,7 @@
align="center"
justify="center"
class="emoji-item"
v-for="(item, index) in [...new Set(emojiRef.historyList)]"
v-for="(item, index) in [...new Set(emojiRef.historyList)].filter((emoji) => !checkIsUrl(emoji))"
:key="index"
@click.stop="chooseEmoji(item)">
{{ item }}
@@ -64,7 +64,36 @@
<!-- 我的喜欢页面 -->
<div v-else>
<span>暂无实现</span>
<div v-if="emojiStore.emojiList?.length > 0">
<span class="text-12px text-[--text-color]">我的表情包</span>
<n-flex align="center" class="mx-6px my-12px">
<n-flex
align="center"
justify="center"
class="emoji-item"
v-for="(item, index) in emojiStore.emojiList"
:key="index"
@click.stop="chooseEmoji(item.expressionUrl, 'url')">
<n-popover trigger="hover" :delay="300" :duration="300" :show-arrow="false" placement="top">
<template #trigger>
<n-image
preview-disabled
:src="item.expressionUrl"
class="size-full object-contain rounded-8px transition duration-300 ease-in-out transform-gpu" />
</template>
<n-button quaternary size="tiny" @click.stop="deleteMyEmoji(item.id)">
删除
<template #icon>
<n-icon>
<svg><use href="#delete"></use></svg>
</n-icon>
</template>
</n-button>
</n-popover>
</n-flex>
</n-flex>
</div>
<span v-else>暂无表情包</span>
</div>
</div>
</transition>
@@ -101,6 +130,7 @@
<script setup lang="ts">
import { getAllTypeEmojis } from '@/utils/Emoji.ts'
import { useHistoryStore } from '@/stores/history.ts'
import { useEmojiStore } from '@/stores/emoji'
import HulaEmojis from 'hula-emojis'
type EmojiType = {
@@ -126,10 +156,11 @@ const emit = defineEmits(['emojiHandle'])
const props = defineProps<{
all: boolean
}>()
const { emoji, setEmoji } = useHistoryStore()
const { emoji, setEmoji, lastEmojiTabIndex, setLastEmojiTabIndex } = useHistoryStore()
const emojiStore = useEmojiStore()
/** 获取米游社的表情包 */
const emojisBbs = HulaEmojis.MihoyoBbs
const activeIndex = ref(0)
const activeIndex = ref(lastEmojiTabIndex)
const currentSeriesIndex = ref(0)
// 生成选项卡数组
@@ -180,6 +211,32 @@ const emojiRef = reactive<{
allEmoji: emojiObj.value
})
/**
* 检查字符串是否为URL
*/
const checkIsUrl = (str: string) => {
try {
new URL(str)
return true
} catch {
return false
}
}
/**
* 删除我的表情包
* @param id 表情包ID
*/
const deleteMyEmoji = async (id: string) => {
try {
await emojiStore.deleteEmoji(id)
window.$message.success('删除表情成功')
} catch (error) {
console.error('删除表情失败:', error)
window.$message.error('删除表情失败')
}
}
/**
* 选择表情
* @param item
@@ -214,6 +271,7 @@ const handleTabChange = (index: number) => {
if (index === 1) {
currentSeriesIndex.value = 0
}
setLastEmojiTabIndex(index)
}
/**
@@ -222,9 +280,10 @@ const handleTabChange = (index: number) => {
const selectSeries = (index: number) => {
currentSeriesIndex.value = index
activeIndex.value = index + 1
setLastEmojiTabIndex(index + 1)
}
onMounted(() => {
onMounted(async () => {
// try {
// const file = await create('emoji-test.txt', { baseDir: BaseDirectory.App })
// await file.write(new TextEncoder().encode('Hello world'))
@@ -232,6 +291,12 @@ onMounted(() => {
// } catch (error) {
// console.error('Error handling file:', error)
// }
// 获取我的表情包列表
await emojiStore.getEmojiList()
// 如果上次选择的是表情包系列,设置正确的currentSeriesIndex
if (activeIndex.value > 0) {
currentSeriesIndex.value = activeIndex.value - 1
}
})
</script>
+27 -9
View File
@@ -15,6 +15,7 @@ import { save } from '@tauri-apps/plugin-dialog'
import { useDownload } from '@/hooks/useDownload'
import { useGroupStore } from '@/stores/group'
import { useWindow } from './useWindow'
import { useEmojiStore } from '@/stores/emoji'
export const useChatMain = () => {
const { removeTag, openMsgSession, userUid } = useCommon()
@@ -24,6 +25,7 @@ export const useChatMain = () => {
const globalStore = useGlobalStore()
const groupStore = useGroupStore()
const chatStore = useChatStore()
const emojiStore = useEmojiStore()
const userStore = useUserStore()?.userInfo
const { downloadFile } = useDownload()
// const userInfo = useUserStore()?.userInfo
@@ -51,9 +53,17 @@ export const useChatMain = () => {
{
label: '转发',
icon: 'share',
click: () => {}
click: () => {
window.$message.warning('暂未实现')
}
},
{
label: '收藏',
icon: 'collection-files',
click: () => {
window.$message.warning('暂未实现')
}
},
{ label: '收藏', icon: 'collection-files' },
{
label: '回复',
icon: 'reply',
@@ -110,6 +120,9 @@ export const useChatMain = () => {
} else {
delete item.message.body.translatedText
}
},
visible: (item: MessageType) => {
return item.message.type === MsgEnum.TEXT
}
},
...commonMenuList.value
@@ -146,13 +159,6 @@ export const useChatMain = () => {
])
/** 图片类型右键菜单 */
const imageMenuList = ref<OPT.RightMenu[]>([
{
label: '添加到表情',
icon: 'add-expression',
click: (item: any) => {
console.log(item)
}
},
{
label: '复制',
icon: 'copy',
@@ -188,6 +194,18 @@ export const useChatMain = () => {
window.$message.error('保存图片失败')
}
}
},
{
label: '添加到表情',
icon: 'add-expression',
click: async (item: MessageType) => {
const imageUrl = item.message.body.url
if (!imageUrl) {
window.$message.error('获取图片地址失败')
return
}
await emojiStore.addEmoji(imageUrl)
}
}
// {
// label: '在文件夹中显示',
+1 -1
View File
@@ -70,7 +70,7 @@ export default {
/** 上传头像 */
uploadAvatar: (data: { avatar: string }) => POST<void>(urls.uploadAvatar, data),
/** 新增表情包 */
addEmoji: (data: { uid: string; expressionUrl: string }) => POST<MessageType>(urls.addEmoji, data),
addEmoji: (data: { expressionUrl: string }) => POST<MessageType>(urls.addEmoji, data),
/** 获取表情 **/
getEmoji: (params: { uid: string }) => GET<EmojiItem[]>(urls.getEmoji, { params }),
/** 删除Emoji */
+2 -2
View File
@@ -27,9 +27,9 @@ export const useEmojiStore = defineStore(StoresEnum.EMOJI, () => {
const addEmoji = async (emojiUrl: string) => {
const { uid } = userStore.userInfo
if (!uid || !emojiUrl) return
apis.addEmoji({ uid, expressionUrl: emojiUrl }).then((res) => {
apis.addEmoji({ expressionUrl: emojiUrl }).then((res) => {
if (res) {
window.$message.success('添加成功')
window.$message.success('添加表情成功')
}
})
await getEmojiList()
+10 -1
View File
@@ -5,14 +5,23 @@ export const useHistoryStore = defineStore(
StoresEnum.HISTORY,
() => {
const emoji = ref<string[]>([])
// 记录上次选择的表情选项卡索引
const lastEmojiTabIndex = ref<number>(0)
const setEmoji = (item: string[]) => {
emoji.value = item
}
// 设置上次选择的表情选项卡索引
const setLastEmojiTabIndex = (index: number) => {
lastEmojiTabIndex.value = index
}
return {
emoji,
setEmoji
setEmoji,
lastEmojiTabIndex,
setLastEmojiTabIndex
}
},
{
-7
View File
@@ -7,7 +7,6 @@ import { getRootPath, getSrcPath } from './build/config/getPath'
import vueJsx from '@vitejs/plugin-vue-jsx'
import UnoCSS from '@unocss/vite'
import terser from '@rollup/plugin-terser'
import { codecovVitePlugin } from '@codecov/vite-plugin'
import VueSetupExtend from 'vite-plugin-vue-setup-extend'
// https://vitejs.dev/config/
@@ -57,12 +56,6 @@ export default defineConfig(({ mode }: ConfigEnv) => {
resolvers: [NaiveUiResolver()],
dts: 'src/typings/components.d.ts'
}),
/** 代码覆盖率插件 */
codecovVitePlugin({
enableBundleAnalysis: process.env.CODECOV_TOKEN !== undefined,
bundleName: 'hula',
uploadToken: process.env.CODECOV_TOKEN
}),
/** 压缩代码 */
terser({
format: {