mirror of
https://github.com/HuLaSpark/HuLa.git
synced 2026-09-24 23:11:59 +08:00
feat(mobile): ✨ added photo album feature on mobile; added blank AI assistant page
This commit is contained in:
+65
-2
@@ -36,14 +36,15 @@ import { useMitt } from '@/hooks/useMitt.ts'
|
||||
import rustWebSocketClient from '@/services/webSocketRust'
|
||||
import { useContactStore } from '@/stores/contacts.ts'
|
||||
import { useGlobalStore } from '@/stores/global.ts'
|
||||
import { isWindows } from '@/utils/PlatformConstants'
|
||||
import { MittEnum, NotificationTypeEnum, TauriCommand } from '@/enums'
|
||||
import { isMobile, isWindows } from '@/utils/PlatformConstants'
|
||||
import { MittEnum, MsgEnum, NotificationTypeEnum, TauriCommand } from '@/enums'
|
||||
import { clearListener, initListener, readCountQueue } from '@/utils/ReadCountQueue'
|
||||
import { emitTo, listen } from '@tauri-apps/api/event'
|
||||
import { UserAttentionType } from '@tauri-apps/api/window'
|
||||
import type { MessageType } from '@/services/types.ts'
|
||||
import { WsResponseMessageType } from '@/services/wsType.ts'
|
||||
import { useChatStore } from '@/stores/chat'
|
||||
import { useFileStore } from '@/stores/file'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { useSettingStore } from '@/stores/setting.ts'
|
||||
import { invokeSilently } from '@/utils/TauriInvokeHandler'
|
||||
@@ -53,6 +54,7 @@ import { audioManager } from '@/utils/AudioManager'
|
||||
const route = useRoute()
|
||||
const userStore = useUserStore()
|
||||
const chatStore = useChatStore()
|
||||
const fileStore = useFileStore()
|
||||
const settingStore = useSettingStore()
|
||||
const userUid = computed(() => userStore.userInfo!.uid)
|
||||
const appWindow = WebviewWindow.getCurrent()
|
||||
@@ -169,6 +171,62 @@ const playMessageSound = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从消息中提取文件信息并添加到 file store
|
||||
*/
|
||||
const addFileToStore = (data: MessageType) => {
|
||||
const { message } = data
|
||||
const { type, body, roomId, id } = message
|
||||
|
||||
// 只处理图片和视频类型
|
||||
if (type !== MsgEnum.IMAGE && type !== MsgEnum.VIDEO) {
|
||||
return
|
||||
}
|
||||
|
||||
// 提取文件信息
|
||||
const fileUrl = body.url
|
||||
if (!fileUrl) {
|
||||
return
|
||||
}
|
||||
|
||||
// 从 URL 中提取文件名
|
||||
let fileName = ''
|
||||
try {
|
||||
const urlObj = new URL(fileUrl)
|
||||
const pathname = urlObj.pathname
|
||||
fileName = pathname.substring(pathname.lastIndexOf('/') + 1)
|
||||
} catch (e) {
|
||||
// 如果不是有效的 URL,直接使用消息 ID 作为文件名
|
||||
fileName = `${id}.${type === MsgEnum.IMAGE ? 'jpg' : 'mp4'}`
|
||||
}
|
||||
|
||||
// 从文件名中提取后缀
|
||||
const suffix = fileName.includes('.')
|
||||
? fileName.substring(fileName.lastIndexOf('.') + 1)
|
||||
: type === MsgEnum.IMAGE
|
||||
? 'jpg'
|
||||
: 'mp4'
|
||||
|
||||
// 确定 MIME 类型
|
||||
let mimeType = ''
|
||||
if (type === MsgEnum.IMAGE) {
|
||||
mimeType = `image/${suffix === 'jpg' ? 'jpeg' : suffix}`
|
||||
} else if (type === MsgEnum.VIDEO) {
|
||||
mimeType = `video/${suffix}`
|
||||
}
|
||||
|
||||
// 添加到 file store
|
||||
fileStore.addFile({
|
||||
id,
|
||||
roomId,
|
||||
fileName,
|
||||
type: type === MsgEnum.IMAGE ? 'image' : 'video',
|
||||
url: fileUrl,
|
||||
suffix,
|
||||
mimeType
|
||||
})
|
||||
}
|
||||
|
||||
useMitt.on(WsResponseMessageType.RECEIVE_MESSAGE, async (data: MessageType) => {
|
||||
if (chatStore.checkMsgExist(data.message.roomId, data.message.id)) {
|
||||
return
|
||||
@@ -184,6 +242,11 @@ useMitt.on(WsResponseMessageType.RECEIVE_MESSAGE, async (data: MessageType) => {
|
||||
data
|
||||
})
|
||||
|
||||
// 如果是图片或视频消息,添加到 file store(仅移动端需要)
|
||||
if (isMobile()) {
|
||||
addFileToStore(data)
|
||||
}
|
||||
|
||||
// 不是自己发的消息才通知
|
||||
if (data.fromUser.uid !== userUid.value) {
|
||||
// 获取该消息的会话信息
|
||||
|
||||
@@ -144,16 +144,21 @@ const handleSave = async () => {
|
||||
// 保存文件信息到 file store
|
||||
const roomId = getCurrentRoomId()
|
||||
if (roomId) {
|
||||
// 获取文件状态,使用相对路径(localPath)而不是绝对路径
|
||||
const fileStatus = fileDownloadStore.getFileStatus(imageUrl)
|
||||
const localPath = fileStatus.localPath || result
|
||||
|
||||
// 如果没有消息信息,手动创建文件信息
|
||||
const fileInfo = {
|
||||
id: props.message!.id, // 生成唯一ID
|
||||
roomId,
|
||||
fileName,
|
||||
type: 'image' as const,
|
||||
url: result,
|
||||
url: localPath, // 使用相对路径
|
||||
suffix: fileName.split('.').pop()?.toLowerCase()
|
||||
}
|
||||
fileStore.addFile(fileInfo)
|
||||
console.log('🔍 [ImagePreview Debug] 保存文件信息到 fileStore:', fileInfo)
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
@@ -22,11 +22,12 @@
|
||||
<script setup lang="ts">
|
||||
import { emitTo } from '@tauri-apps/api/event'
|
||||
import { WebviewWindow } from '@tauri-apps/api/webviewWindow'
|
||||
import { NotificationTypeEnum, TauriCommand } from '@/enums'
|
||||
import { MsgEnum, NotificationTypeEnum, TauriCommand } from '@/enums'
|
||||
import { useMitt } from '@/hooks/useMitt'
|
||||
import type { MessageType } from '@/services/types'
|
||||
import { WsResponseMessageType } from '@/services/wsType'
|
||||
import { useChatStore } from '@/stores/chat'
|
||||
import { useFileStore } from '@/stores/file'
|
||||
import { useGlobalStore } from '@/stores/global'
|
||||
import { useSettingStore } from '@/stores/setting'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
@@ -50,6 +51,7 @@ interface MobileLayoutProps {
|
||||
|
||||
const route = useRoute()
|
||||
const chatStore = useChatStore()
|
||||
const fileStore = useFileStore()
|
||||
const userStore = useUserStore()
|
||||
const globalStore = useGlobalStore()
|
||||
const settingStore = useSettingStore()
|
||||
@@ -92,6 +94,62 @@ const backgroundImageStyle = computed(() => {
|
||||
return styles
|
||||
})
|
||||
|
||||
/**
|
||||
* 从消息中提取文件信息并添加到 file store
|
||||
*/
|
||||
const addFileToStore = (data: MessageType) => {
|
||||
const { message } = data
|
||||
const { type, body, roomId, id } = message
|
||||
|
||||
// 只处理图片和视频类型
|
||||
if (type !== MsgEnum.IMAGE && type !== MsgEnum.VIDEO) {
|
||||
return
|
||||
}
|
||||
|
||||
// 提取文件信息
|
||||
const fileUrl = body.url
|
||||
if (!fileUrl) {
|
||||
return
|
||||
}
|
||||
|
||||
// 从 URL 中提取文件名
|
||||
let fileName = ''
|
||||
try {
|
||||
const urlObj = new URL(fileUrl)
|
||||
const pathname = urlObj.pathname
|
||||
fileName = pathname.substring(pathname.lastIndexOf('/') + 1)
|
||||
} catch (e) {
|
||||
// 如果不是有效的 URL,直接使用消息 ID 作为文件名
|
||||
fileName = `${id}.${type === MsgEnum.IMAGE ? 'jpg' : 'mp4'}`
|
||||
}
|
||||
|
||||
// 从文件名中提取后缀
|
||||
const suffix = fileName.includes('.')
|
||||
? fileName.substring(fileName.lastIndexOf('.') + 1)
|
||||
: type === MsgEnum.IMAGE
|
||||
? 'jpg'
|
||||
: 'mp4'
|
||||
|
||||
// 确定 MIME 类型
|
||||
let mimeType = ''
|
||||
if (type === MsgEnum.IMAGE) {
|
||||
mimeType = `image/${suffix === 'jpg' ? 'jpeg' : suffix}`
|
||||
} else if (type === MsgEnum.VIDEO) {
|
||||
mimeType = `video/${suffix}`
|
||||
}
|
||||
|
||||
// 添加到 file store
|
||||
fileStore.addFile({
|
||||
id,
|
||||
roomId,
|
||||
fileName,
|
||||
type: type === MsgEnum.IMAGE ? 'image' : 'video',
|
||||
url: fileUrl,
|
||||
suffix,
|
||||
mimeType
|
||||
})
|
||||
}
|
||||
|
||||
/** 处理收到的消息 */
|
||||
useMitt.on(WsResponseMessageType.RECEIVE_MESSAGE, async (data: MessageType) => {
|
||||
if (chatStore.checkMsgExist(data.message.roomId, data.message.id)) {
|
||||
@@ -106,6 +164,9 @@ useMitt.on(WsResponseMessageType.RECEIVE_MESSAGE, async (data: MessageType) => {
|
||||
await invokeSilently(TauriCommand.SAVE_MSG, {
|
||||
data
|
||||
})
|
||||
|
||||
// 如果是图片或视频消息,添加到 file store
|
||||
addFileToStore(data)
|
||||
if (data.fromUser.uid !== userUid.value) {
|
||||
// 获取该消息的会话信息
|
||||
const session = chatStore.sessionList.find((s) => s.roomId === data.message.roomId)
|
||||
|
||||
@@ -82,7 +82,8 @@ const imageList = ref<
|
||||
|
||||
const getImageList = async () => {
|
||||
const data = await fileStore.getRoomFilesForDisplay(globalStore.currentSessionRoomId)
|
||||
imageList.value = data.filter((item) => item.type === 'image').map((item) => ({ url: item.displayUrl }))
|
||||
const filteredImages = data.filter((item) => item.type === 'image')
|
||||
imageList.value = filteredImages.map((item) => ({ url: item.displayUrl }))
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<AutoFixHeightPage :show-footer="false">
|
||||
<template #header>
|
||||
<HeaderBar
|
||||
:isOfficial="false"
|
||||
class="bg-white"
|
||||
style="border-bottom: 1px solid; border-color: #dfdfdf"
|
||||
:hidden-right="true"
|
||||
room-name="AI助手" />
|
||||
</template>
|
||||
|
||||
<template #container>
|
||||
<div class="bg-cover bg-center flex flex-col overflow-auto h-full">
|
||||
<div class="flex flex-col flex-1 gap-20px py-15px px-20px">
|
||||
<!-- TODO: 这里后续添加 AI 助手的内容 -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</AutoFixHeightPage>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// AI 助手页面
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<div>
|
||||
<AutoFixHeightPage :show-footer="false">
|
||||
<template #header>
|
||||
<HeaderBar
|
||||
:isOfficial="false"
|
||||
class="bg-white"
|
||||
style="border-bottom: 1px solid; border-color: #dfdfdf"
|
||||
:hidden-right="true"
|
||||
room-name="我的相册" />
|
||||
</template>
|
||||
|
||||
<template #container>
|
||||
<div class="flex flex-col bg-#fefefe overflow-auto h-full">
|
||||
<div class="flex flex-col p-20px gap-20px">
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="loading" class="flex justify-center items-center py-40px">
|
||||
<n-spin size="medium" />
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-else-if="allImages.length === 0" class="flex flex-col justify-center items-center py-40px gap-10px">
|
||||
<svg class="iconpark-icon w-60px h-60px text-gray">
|
||||
<use href="#xiangce"></use>
|
||||
</svg>
|
||||
<div class="text-gray text-14px">暂无图片</div>
|
||||
</div>
|
||||
|
||||
<!-- 图片网格 -->
|
||||
<div v-else class="grid grid-cols-4 gap-1">
|
||||
<div
|
||||
v-for="(image, index) in allImages"
|
||||
:key="index"
|
||||
class="overflow-hidden bg-gray-100 aspect-square cursor-pointer"
|
||||
@click="handleImageClick(image)">
|
||||
<img :src="image.displayUrl" class="w-full h-full object-cover" alt="相册图片" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</AutoFixHeightPage>
|
||||
|
||||
<!-- 图片预览组件 -->
|
||||
<ImagePreview
|
||||
v-model:visible="showImagePreviewRef"
|
||||
:image-url="activeImageUrl"
|
||||
:show-forward="false"
|
||||
:show-save="false"
|
||||
:show-more="false" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useFileStore } from '@/stores/file'
|
||||
import { useGlobalStore } from '@/stores/global'
|
||||
import ImagePreview from '@/mobile/components/ImagePreview.vue'
|
||||
|
||||
const fileStore = useFileStore()
|
||||
const globalStore = useGlobalStore()
|
||||
|
||||
// 图片预览状态
|
||||
const showImagePreviewRef = ref(false)
|
||||
const activeImageUrl = ref('')
|
||||
const loading = ref(true)
|
||||
|
||||
// 所有图片列表
|
||||
const allImages = ref<Array<{ displayUrl: string; originalUrl: string; id: string; roomId: string }>>([])
|
||||
|
||||
/**
|
||||
* 获取所有房间的图片
|
||||
*/
|
||||
const getAllImages = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// 检查 roomFilesMap 是否为空,如果为空则扫描本地文件
|
||||
if (Object.keys(fileStore.roomFilesMap).length === 0) {
|
||||
if (globalStore.currentSessionRoomId) {
|
||||
console.log('🔍 [MyAlbum Debug] 扫描本地文件,roomId:', globalStore.currentSessionRoomId)
|
||||
await fileStore.scanLocalFiles(globalStore.currentSessionRoomId)
|
||||
}
|
||||
}
|
||||
|
||||
const roomFilesMap = fileStore.roomFilesMap
|
||||
console.log('🔍 [MyAlbum Debug] roomFilesMap:', roomFilesMap)
|
||||
const imagesList: Array<{ displayUrl: string; originalUrl: string; id: string; roomId: string }> = []
|
||||
|
||||
// 遍历所有房间
|
||||
for (const roomId in roomFilesMap) {
|
||||
const files = await fileStore.getRoomFilesForDisplay(roomId)
|
||||
console.log('🔍 [MyAlbum Debug] roomId:', roomId, 'files:', files)
|
||||
|
||||
// 只获取图片类型的文件
|
||||
const images = files.filter((file) => file.type === 'image')
|
||||
console.log('🔍 [MyAlbum Debug] roomId:', roomId, 'images:', images)
|
||||
|
||||
imagesList.push(
|
||||
...images.map((img) => ({
|
||||
displayUrl: img.displayUrl,
|
||||
originalUrl: img.originalUrl,
|
||||
id: img.id,
|
||||
roomId: img.roomId
|
||||
}))
|
||||
)
|
||||
}
|
||||
|
||||
console.log('🔍 [MyAlbum Debug] 最终图片列表:', imagesList)
|
||||
allImages.value = imagesList
|
||||
} catch (error) {
|
||||
console.error('获取图片失败:', error)
|
||||
if (window.$message) {
|
||||
window.$message.error('获取图片失败')
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理图片点击
|
||||
*/
|
||||
const handleImageClick = (image: { displayUrl: string; originalUrl: string; id: string; roomId: string }) => {
|
||||
console.log('🔍 [MyAlbum Debug] 点击图片:', image)
|
||||
activeImageUrl.value = image.displayUrl
|
||||
showImagePreviewRef.value = true
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
console.log('🔍 [MyAlbum Debug] MyAlbum 组件已挂载')
|
||||
getAllImages()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 可以添加自定义样式 */
|
||||
</style>
|
||||
@@ -40,13 +40,14 @@
|
||||
<div
|
||||
v-for="item in options"
|
||||
:key="item.label"
|
||||
class="flex flex-wrap items-center gap-15px p-[15px_10px_5px_10px]">
|
||||
@click="item.onClick"
|
||||
class="flex flex-wrap items-center gap-15px p-[15px_10px_5px_10px] cursor-pointer">
|
||||
<div>
|
||||
<svg class="iconpark-icon w-30px h-30px"><use :href="'#' + item.icon"></use></svg>
|
||||
</div>
|
||||
<div class="flex-1 text-14px">{{ item.label }}</div>
|
||||
<div>
|
||||
<svg @click="handleBack" class="w-20px text-gray h-20px iconpark-icon"><use href="#right"></use></svg>
|
||||
<svg class="w-20px text-gray h-20px iconpark-icon"><use href="#right"></use></svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -67,19 +68,41 @@ const userStore = useUserStore()
|
||||
const options = ref([
|
||||
{
|
||||
icon: 'xiangce',
|
||||
label: '相册'
|
||||
label: '相册',
|
||||
onClick: () => {
|
||||
router.push('/mobile/mobileMy/myAlbum')
|
||||
}
|
||||
},
|
||||
{
|
||||
icon: 'shoucang',
|
||||
label: '收藏'
|
||||
label: '收藏',
|
||||
onClick: () => {
|
||||
// TODO: 跳转到收藏页面
|
||||
console.log('收藏')
|
||||
}
|
||||
},
|
||||
{
|
||||
icon: 'wenjian',
|
||||
label: '文件'
|
||||
label: '文件',
|
||||
onClick: () => {
|
||||
// TODO: 跳转到文件页面
|
||||
console.log('文件')
|
||||
}
|
||||
},
|
||||
{
|
||||
icon: 'gexingzhuangban',
|
||||
label: '个性装扮'
|
||||
label: '个性装扮',
|
||||
onClick: () => {
|
||||
// TODO: 跳转到个性装扮页面
|
||||
console.log('个性装扮')
|
||||
}
|
||||
},
|
||||
{
|
||||
icon: 'robot',
|
||||
label: 'AI助手',
|
||||
onClick: () => {
|
||||
router.push('/mobile/mobileMy/aiAssistant')
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
|
||||
@@ -42,6 +42,8 @@ import MyMessages from '#/views/my/MyMessages.vue'
|
||||
import PublishCommunity from '#/views/my/PublishCommunity.vue'
|
||||
import Share from '#/views/my/Share.vue'
|
||||
import SimpleBio from '#/views/my/SimpleBio.vue'
|
||||
import AiAssistant from '#/views/my/AiAssistant.vue'
|
||||
import MyAlbum from '#/views/my/MyAlbum.vue'
|
||||
import { TauriCommand } from '@/enums'
|
||||
import ConfirmQRLogin from '#/views/ConfirmQRLogin.vue'
|
||||
import MyQRCode from '#/views/MyQRCode.vue'
|
||||
@@ -251,6 +253,16 @@ const getMobileRoutes = (): Array<RouteRecordRaw> => [
|
||||
path: 'SimpleBio',
|
||||
name: 'mobileSimpleBio',
|
||||
component: SimpleBio
|
||||
},
|
||||
{
|
||||
path: 'aiAssistant',
|
||||
name: 'mobileAiAssistant',
|
||||
component: AiAssistant
|
||||
},
|
||||
{
|
||||
path: 'myAlbum',
|
||||
name: 'mobileMyAlbum',
|
||||
component: MyAlbum
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
+88
-4
@@ -1,8 +1,10 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import { appDataDir, join } from '@tauri-apps/api/path'
|
||||
import { appDataDir, join, resourceDir } from '@tauri-apps/api/path'
|
||||
import { readDir } from '@tauri-apps/plugin-fs'
|
||||
import { StoresEnum } from '@/enums'
|
||||
import { isMobile } from '@/utils/PlatformConstants'
|
||||
import { useUserStore } from './user'
|
||||
|
||||
/**
|
||||
* 文件信息接口
|
||||
@@ -43,6 +45,10 @@ export const useFileStore = defineStore(
|
||||
const getRoomFilesForDisplay = async (roomId: string) => {
|
||||
const files = roomFilesMap[roomId] ? Object.values(roomFilesMap[roomId]) : []
|
||||
|
||||
if (files.length === 0) {
|
||||
return []
|
||||
}
|
||||
|
||||
const processedFiles = await Promise.all(
|
||||
files.map(async (file) => {
|
||||
let displayUrl: string
|
||||
@@ -51,8 +57,9 @@ export const useFileStore = defineStore(
|
||||
// HTTP URL 直接使用
|
||||
displayUrl = file.url
|
||||
} else {
|
||||
// 相对路径需要拼接 BaseDirectory.AppData 的绝对路径
|
||||
const baseDirPath = isMobile() ? await appDataDir() : await appDataDir()
|
||||
// 相对路径需要拼接基础目录的绝对路径
|
||||
// 移动端使用 AppData,PC 端使用 Resource
|
||||
const baseDirPath = isMobile() ? await appDataDir() : await resourceDir()
|
||||
const absolutePath = await join(baseDirPath, file.url)
|
||||
displayUrl = convertFileSrc(absolutePath)
|
||||
}
|
||||
@@ -115,6 +122,82 @@ export const useFileStore = defineStore(
|
||||
return roomFilesMap[roomId]?.[fileId]
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描本地目录并填充 file store
|
||||
* 用于初始化时加载已存在的文件
|
||||
* 注意:此功能仅在移动端可用
|
||||
*/
|
||||
const scanLocalFiles = async (roomId: string) => {
|
||||
// 仅移动端支持扫描本地文件
|
||||
if (!isMobile()) {
|
||||
console.warn('scanLocalFiles 仅在移动端可用')
|
||||
return 0
|
||||
}
|
||||
|
||||
try {
|
||||
const userStore = useUserStore()
|
||||
|
||||
// 获取用户数据目录
|
||||
const userRoomDir = await userStore.getUserRoomDir()
|
||||
|
||||
// 获取绝对路径(移动端使用 AppData)
|
||||
const baseDirPath = await appDataDir()
|
||||
const absolutePath = await join(baseDirPath, userRoomDir)
|
||||
|
||||
// 读取目录内容
|
||||
const entries = await readDir(absolutePath)
|
||||
|
||||
// 图片和视频扩展名
|
||||
const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp']
|
||||
const videoExtensions = ['.mp4', '.mov', '.avi', '.mkv', '.webm']
|
||||
|
||||
let addedCount = 0
|
||||
|
||||
for (const entry of entries) {
|
||||
if (!entry.isFile) continue
|
||||
|
||||
const fileName = entry.name
|
||||
const ext = fileName.toLowerCase().substring(fileName.lastIndexOf('.'))
|
||||
|
||||
let fileType: 'image' | 'video' | null = null
|
||||
let mimeType = ''
|
||||
|
||||
if (imageExtensions.includes(ext)) {
|
||||
fileType = 'image'
|
||||
mimeType = `image/${ext === '.jpg' ? 'jpeg' : ext.substring(1)}`
|
||||
} else if (videoExtensions.includes(ext)) {
|
||||
fileType = 'video'
|
||||
mimeType = `video/${ext.substring(1)}`
|
||||
}
|
||||
|
||||
if (fileType) {
|
||||
// 使用文件名作为 ID(因为我们不知道原始消息 ID)
|
||||
const fileId = fileName.substring(0, fileName.lastIndexOf('.'))
|
||||
|
||||
// 构建文件 URL(使用相对路径,与 getUserRoomDir 返回的格式一致)
|
||||
const fileUrl = await join(userRoomDir, fileName)
|
||||
|
||||
addFile({
|
||||
id: fileId,
|
||||
roomId,
|
||||
fileName,
|
||||
type: fileType,
|
||||
url: fileUrl,
|
||||
suffix: ext.substring(1),
|
||||
mimeType
|
||||
})
|
||||
|
||||
addedCount++
|
||||
}
|
||||
}
|
||||
|
||||
return addedCount
|
||||
} catch (error) {
|
||||
console.error('扫描本地文件失败:', error)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
// 状态
|
||||
roomFilesMap: readonly(roomFilesMap),
|
||||
@@ -128,7 +211,8 @@ export const useFileStore = defineStore(
|
||||
removeFile,
|
||||
clearRoomFiles,
|
||||
getFile,
|
||||
getRoomFilesForDisplay
|
||||
getRoomFilesForDisplay,
|
||||
scanLocalFiles
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user