feat(mobile): adapt dark theme
This commit is contained in:
2
public/icon.js
vendored
2
public/icon.js
vendored
File diff suppressed because one or more lines are too long
1
src-tauri/capabilities/mobile.json
vendored
1
src-tauri/capabilities/mobile.json
vendored
@@ -49,6 +49,7 @@
|
||||
"barcode-scanner:allow-vibrate",
|
||||
"mic-recorder:allow-start-recording",
|
||||
"mic-recorder:allow-stop-recording",
|
||||
"core:app:allow-set-app-theme",
|
||||
{
|
||||
"identifier": "shell:allow-execute",
|
||||
"allow": [
|
||||
|
||||
2
src-tauri/gen/schemas/capabilities.json
vendored
2
src-tauri/gen/schemas/capabilities.json
vendored
File diff suppressed because one or more lines are too long
@@ -628,6 +628,7 @@ onMounted(() => {
|
||||
isDesktop() && import('@/styles/scss/global/desktop.scss')
|
||||
// 判断是否是移动端,移动端需要加载安全区域适配样式
|
||||
isMobile() && import('@/styles/scss/global/mobile.scss')
|
||||
|
||||
import(`@/styles/scss/theme/${themes.value.versatile}.scss`)
|
||||
if (!settingStore.themes.content) {
|
||||
// 首次运行使用跟随系统,保持既有体验
|
||||
@@ -734,6 +735,8 @@ watch(
|
||||
watch(
|
||||
() => themes.value.versatile,
|
||||
async (val, oldVal) => {
|
||||
console.log(val)
|
||||
|
||||
await import(`@/styles/scss/theme/${val}.scss`)
|
||||
// 然后给最顶层的div设置val的类样式
|
||||
const app = document.querySelector('#app')?.classList as DOMTokenList
|
||||
|
||||
@@ -67,11 +67,12 @@ const prefers = matchMedia('(prefers-color-scheme: dark)')
|
||||
// 定义不需要显示消息提示的窗口
|
||||
const noMessageWindows = ['tray', 'notify', 'capture', 'update', 'checkupdate']
|
||||
|
||||
const isValidContent = (theme?: string) => theme === ThemeEnum.DARK || theme === ThemeEnum.LIGHT
|
||||
const isValidContent = (theme?: string): theme is ThemeEnum => theme === ThemeEnum.DARK || theme === ThemeEnum.LIGHT
|
||||
|
||||
const applyThemeContent = (theme: ThemeEnum) => {
|
||||
globalTheme.value = theme === ThemeEnum.DARK ? darkTheme : lightTheme
|
||||
document.documentElement.dataset.theme = theme
|
||||
console.log(globalTheme.value)
|
||||
}
|
||||
|
||||
const syncOsTheme = () => {
|
||||
@@ -117,7 +118,7 @@ watch(
|
||||
settingStore.normalizeThemeState()
|
||||
return
|
||||
}
|
||||
applyThemeContent(content as ThemeEnum)
|
||||
applyThemeContent(content)
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
@@ -45,9 +45,10 @@
|
||||
@compositionend="updateSelectionRange"
|
||||
@keydown.exact.ctrl.enter="handleEnterKey"
|
||||
:data-placeholder="t('editor.placeholder')"
|
||||
class="n-input"
|
||||
:class="
|
||||
isMobile()
|
||||
? 'empty:before:content-[attr(data-placeholder)] before:text-(12px #777) p-2 min-h-2rem ps-10px! text-14px! bg-white! rounded-10px! max-h-8rem! flex items-center'
|
||||
? 'empty:before:content-[attr(data-placeholder)] before:text-(12px #777) p-2 min-h-2rem ps-10px! text-14px! rounded-10px! max-h-8rem! flex items-center'
|
||||
: 'empty:before:content-[attr(data-placeholder)] before:text-(12px #777) p-2'
|
||||
"></div>
|
||||
</n-scrollbar>
|
||||
@@ -57,62 +58,58 @@
|
||||
<div
|
||||
v-if="!isMobile()"
|
||||
class="flex-shrink-0 max-h-52px p-4px pr-12px border-t border-gray-200/50 flex justify-end mb-4px">
|
||||
<n-config-provider :theme="lightTheme">
|
||||
<n-button-group size="small">
|
||||
<n-button
|
||||
color="#13987f"
|
||||
:disabled="props.isAIMode && props.isAIStreaming ? false : disabledSend"
|
||||
class="w-65px"
|
||||
@click="handleDesktopSend">
|
||||
{{ props.isAIMode && props.isAIStreaming ? '停止思考' : t('editor.send') }}
|
||||
</n-button>
|
||||
<n-button color="#13987f" class="p-[0_6px]">
|
||||
<template #icon>
|
||||
<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 mt-2px outline-none">
|
||||
<use href="#down"></use>
|
||||
</svg>
|
||||
<svg @click="arrow = false" v-else class="w-22px h-22px mt-2px outline-none">
|
||||
<use href="#up"></use>
|
||||
</svg>
|
||||
<template #action>
|
||||
<n-flex
|
||||
justify="center"
|
||||
align="center"
|
||||
:size="4"
|
||||
class="text-(12px #777) cursor-default tracking-1 select-none">
|
||||
<i18n-t keypath="editor.send_or_newline">
|
||||
<template #send>
|
||||
<span v-if="chatKey !== 'Enter'">
|
||||
{{ isMac() ? MacOsKeyEnum['⌘'] : WinKeyEnum.CTRL }}
|
||||
</span>
|
||||
<svg class="size-12px">
|
||||
<use href="#Enter"></use>
|
||||
</svg>
|
||||
</template>
|
||||
<template #newline>
|
||||
<n-flex align="center" :size="0">
|
||||
{{ isMac() ? MacOsKeyEnum['⇧'] : WinKeyEnum.SHIFT }}
|
||||
<svg class="size-12px">
|
||||
<use href="#Enter"></use>
|
||||
</svg>
|
||||
</n-flex>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</n-flex>
|
||||
</template>
|
||||
</n-popselect>
|
||||
</n-config-provider>
|
||||
</template>
|
||||
</n-button>
|
||||
</n-button-group>
|
||||
</n-config-provider>
|
||||
<n-button-group size="small">
|
||||
<n-button
|
||||
color="#13987f"
|
||||
:disabled="props.isAIMode && props.isAIStreaming ? false : disabledSend"
|
||||
class="w-65px"
|
||||
@click="handleDesktopSend">
|
||||
{{ props.isAIMode && props.isAIStreaming ? '停止思考' : t('editor.send') }}
|
||||
</n-button>
|
||||
<n-button color="#13987f" class="p-[0_6px]">
|
||||
<template #icon>
|
||||
<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 mt-2px outline-none">
|
||||
<use href="#down"></use>
|
||||
</svg>
|
||||
<svg @click="arrow = false" v-else class="w-22px h-22px mt-2px outline-none">
|
||||
<use href="#up"></use>
|
||||
</svg>
|
||||
<template #action>
|
||||
<n-flex
|
||||
justify="center"
|
||||
align="center"
|
||||
:size="4"
|
||||
class="text-(12px #777) cursor-default tracking-1 select-none">
|
||||
<i18n-t keypath="editor.send_or_newline">
|
||||
<template #send>
|
||||
<span v-if="chatKey !== 'Enter'">
|
||||
{{ isMac() ? MacOsKeyEnum['⌘'] : WinKeyEnum.CTRL }}
|
||||
</span>
|
||||
<svg class="size-12px">
|
||||
<use href="#Enter"></use>
|
||||
</svg>
|
||||
</template>
|
||||
<template #newline>
|
||||
<n-flex align="center" :size="0">
|
||||
{{ isMac() ? MacOsKeyEnum['⇧'] : WinKeyEnum.SHIFT }}
|
||||
<svg class="size-12px">
|
||||
<use href="#Enter"></use>
|
||||
</svg>
|
||||
</n-flex>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</n-flex>
|
||||
</template>
|
||||
</n-popselect>
|
||||
</template>
|
||||
</n-button>
|
||||
</n-button-group>
|
||||
</div>
|
||||
|
||||
<!-- @提及框 -->
|
||||
@@ -197,17 +194,15 @@
|
||||
<div
|
||||
v-if="msgInput"
|
||||
class="flex-shrink-0 max-h-62px h-full border-t border-gray-200/50 flex items-center justify-end">
|
||||
<n-config-provider class="h-full" :theme="lightTheme">
|
||||
<n-button-group size="small" :class="isMobile() ? 'h-full' : 'pr-20px'">
|
||||
<n-button
|
||||
color="#13987f"
|
||||
:disabled="props.isAIMode && props.isAIStreaming ? false : disabledSend"
|
||||
class="w-3rem h-full"
|
||||
@click="handleMobileSend">
|
||||
{{ props.isAIMode && props.isAIStreaming ? '停止思考' : t('editor.send') }}
|
||||
</n-button>
|
||||
</n-button-group>
|
||||
</n-config-provider>
|
||||
<n-button-group size="small" :class="isMobile() ? 'h-full' : 'pr-20px'">
|
||||
<n-button
|
||||
color="#13987f"
|
||||
:disabled="props.isAIMode && props.isAIStreaming ? false : disabledSend"
|
||||
class="w-3rem h-full"
|
||||
@click="handleMobileSend">
|
||||
{{ props.isAIMode && props.isAIStreaming ? '停止思考' : t('editor.send') }}
|
||||
</n-button>
|
||||
</n-button-group>
|
||||
</div>
|
||||
<div v-if="!msgInput" class="flex items-center justify-start h-full">
|
||||
<svg
|
||||
@@ -233,7 +228,7 @@
|
||||
import { emit } from '@tauri-apps/api/event'
|
||||
import { WebviewWindow } from '@tauri-apps/api/webviewWindow'
|
||||
import { onKeyStroke } from '@vueuse/core'
|
||||
import { darkTheme, lightTheme, type VirtualListInst } from 'naive-ui'
|
||||
import { type VirtualListInst } from 'naive-ui'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import type { Ref } from 'vue'
|
||||
import { MacOsKeyEnum, MittEnum, RoomTypeEnum, ThemeEnum, WinKeyEnum } from '@/enums'
|
||||
|
||||
@@ -14,11 +14,7 @@
|
||||
|
||||
<!-- 置顶公告提示 -->
|
||||
<Transition name="announcement" mode="out-in">
|
||||
<div
|
||||
v-if="isGroup && topAnnouncement"
|
||||
key="announcement"
|
||||
:class="{ 'bg-#eee': isMobile() }"
|
||||
class="p-[6px_12px_0_12px]">
|
||||
<div v-if="isGroup && topAnnouncement" key="announcement" class="p-[6px_12px_0_12px]">
|
||||
<div
|
||||
class="custom-announcement"
|
||||
:class="{ 'announcement-hover': isAnnouncementHover }"
|
||||
@@ -42,7 +38,7 @@
|
||||
</Transition>
|
||||
|
||||
<!-- 聊天内容 -->
|
||||
<div :class="{ 'bg-#eee': isMobile() }" class="flex flex-col flex-1 min-h-0">
|
||||
<div class="flex flex-col flex-1 min-h-0">
|
||||
<div
|
||||
id="image-chat-main"
|
||||
ref="scrollContainer"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<template>
|
||||
<n-config-provider
|
||||
:theme="lightTheme"
|
||||
<div
|
||||
class="h-full flex flex-col box-border"
|
||||
:class="{
|
||||
'bg-cover bg-center bg-no-repeat': props.backgroundImage
|
||||
@@ -16,7 +15,7 @@
|
||||
|
||||
<!-- 底部安全区域 -->
|
||||
<div :class="[{ 'safe-area-bottom': safeAreaBottom }, props.bottomSafeAreaClass]" />
|
||||
</n-config-provider>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -35,7 +34,6 @@ import { audioManager } from '@/utils/AudioManager'
|
||||
import { isMobile, isWindows } from '@/utils/PlatformConstants'
|
||||
import { invokeSilently } from '@/utils/TauriInvokeHandler'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { lightTheme } from 'naive-ui'
|
||||
interface MobileLayoutProps {
|
||||
/** 是否应用顶部安全区域 */
|
||||
safeAreaTop?: boolean
|
||||
|
||||
@@ -11,9 +11,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-#FAFAFA">
|
||||
<slot name="footer"></slot>
|
||||
</div>
|
||||
<slot name="footer"></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
spellcheck="false"
|
||||
style="position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; white-space: pre-wrap"></div>
|
||||
|
||||
<div class="w-full min-h-20px bg-#FAFAFA flex flex-col z-2 footer-bar-shadow">
|
||||
<div class="w-full min-h-20px flex flex-col z-2 footer-bar-shadow">
|
||||
<div class="flex-1 min-h-0">
|
||||
<chat-footer :detail-id="globalStore.currentSession?.detailId"></chat-footer>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<template>
|
||||
<div class="w-full h-[56px] grid grid-cols-[100px_1fr_100px] z-2">
|
||||
<div class="w-full h-[56px] grid grid-cols-[100px_1fr_100px] z-2 bg-background text-foreground">
|
||||
<div @click="handleBack" class="w-full h-full flex items-center">
|
||||
<svg class="iconpark-icon w-24px h-24px ms-16px p-5px"><use href="#fanhui"></use></svg>
|
||||
<svg class="iconpark-icon w-24px h-24px ms-16px p-5px">
|
||||
<use href="#fanhui" class="text-foreground"></use>
|
||||
</svg>
|
||||
<div
|
||||
v-show="props.msgCount ? (props.msgCount > 0 ? true : false) : false"
|
||||
class="rounded-15px flex items-center bg-#C7DBD9 px-7px text-14px min-h-20px">
|
||||
@@ -10,7 +12,7 @@
|
||||
</div>
|
||||
<div class="w-full h-full overflow-hidden flex items-center justify-center">
|
||||
<div @click="handleRoomNameClick" :class="props.isOfficial ? ['chat-room-name-official'] : ['chat-room-name']">
|
||||
<div class="truncate whitespace-nowrap overflow-hidden text-ellipsis w-full text-center">
|
||||
<div class="truncate whitespace-nowrap overflow-hidden text-foreground text-ellipsis w-full text-center">
|
||||
{{ props.roomName }}
|
||||
</div>
|
||||
<svg v-if="props.isOfficial" class="w-18px h-18px iconpark-icon text-#1A9B83"><use href="#auth"></use></svg>
|
||||
@@ -23,6 +25,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<n-divider v-if="props.border" class="m-0!" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -35,6 +39,7 @@ export interface HeaderBarProps {
|
||||
enableDefaultBackground?: boolean
|
||||
enableShadow?: boolean
|
||||
roomName?: string | false
|
||||
border?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<HeaderBarProps>(), {
|
||||
@@ -42,7 +47,8 @@ const props = withDefaults(defineProps<HeaderBarProps>(), {
|
||||
hiddenRight: false,
|
||||
enableDefaultBackground: true,
|
||||
enableShadow: true,
|
||||
roomName: false
|
||||
roomName: false,
|
||||
border: false
|
||||
})
|
||||
|
||||
const emits = defineEmits<(e: 'roomNameClick', payload: HeaderBarProps) => void>()
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
|
||||
<!-- 中间:两行内容 -->
|
||||
<div class="truncate pl-4 flex gap-10px flex-col">
|
||||
<div class="text-14px leading-tight font-bold flex-1 truncate text-#333 flex items-center gap-2">
|
||||
<n-text class="text-14px leading-tight font-bold flex-1 truncate flex items-center gap-2">
|
||||
<span>{{ userName }}</span>
|
||||
</div>
|
||||
</n-text>
|
||||
<div class="text-12px text-#666 truncate">{{ formatTime(feedItem.createTime) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -22,9 +22,9 @@
|
||||
<div></div>
|
||||
<div class="flex flex-col gap-2 text-14px">
|
||||
<!-- 文本内容 -->
|
||||
<div class="text-#333 leading-relaxed whitespace-pre-wrap break-words">
|
||||
<n-text depth="3" class="leading-relaxed whitespace-pre-wrap break-words">
|
||||
{{ feedItem.content }}
|
||||
</div>
|
||||
</n-text>
|
||||
|
||||
<!-- 图片网格 - 根据图片数量动态调整 -->
|
||||
<div v-if="feedItem.urls && feedItem.urls.length > 0" :class="getImageGridClass(feedItem.urls.length)">
|
||||
@@ -52,7 +52,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 底部操作栏 -->
|
||||
<div class="w-full flex justify-end mt-5px gap-5 items-center text-12px text-#666">
|
||||
<div class="w-full flex justify-end mt-5px gap-5 items-center text-12px text-#666 dark:invert">
|
||||
<!-- 分享 -->
|
||||
<div class="flex items-center gap-1 cursor-pointer active:opacity-60" @click="handleShare">
|
||||
<svg class="iconpark-icon w-20px h-20px"><use href="#fenxiang"></use></svg>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<div ref="infoBox" class="pl-2 flex gap-8px flex-col transition-transform duration-300 ease-in-out">
|
||||
<!-- 名字与在线状态 -->
|
||||
<div class="flex flex-warp gap-4 items-center">
|
||||
<span class="font-bold text-20px text-#373838">{{ userDetailInfo!.name }}</span>
|
||||
<n-text class="font-bold text-20px">{{ userDetailInfo!.name }}</n-text>
|
||||
<div
|
||||
v-show="hasUserOnlineState"
|
||||
class="bg-#E7EFE6 flex flex-wrap ps-2 px-8px items-center rounded-full gap-1 h-24px">
|
||||
@@ -34,7 +34,7 @@
|
||||
<div class="flex flex-warp gap-2 items-center">
|
||||
<span class="text-bold-style">{{ t('mobile_personal_info.account') }}:{{ userDetailInfo!.account }}</span>
|
||||
<span v-if="isMyPage" @click="toMyQRCode" class="pe-15px">
|
||||
<img class="w-14px h-14px" src="@/assets/mobile/my/qr-code.webp" alt="" />
|
||||
<img class="w-14px h-14px dark:invert" src="@/assets/mobile/my/qr-code.webp" alt="" />
|
||||
</span>
|
||||
</div>
|
||||
<Transition name="medal-fade">
|
||||
@@ -76,56 +76,65 @@
|
||||
<div class="flex flex-wrap justify-around mt-4">
|
||||
<div class="flex flex-warp gap-2 items-center">
|
||||
<div class="min-w-10 flex flex-col items-center">
|
||||
<div class="fans-number">920.13W</div>
|
||||
<n-text class="fans-number">920.13W</n-text>
|
||||
<div class="fans-title">{{ t('mobile_personal_info.follower') }}</div>
|
||||
</div>
|
||||
<div class="h-20px w-1px bg-gray-300"></div>
|
||||
<n-divider vertical class="h-20px" />
|
||||
<div class="min-w-10 flex flex-col items-center">
|
||||
<div class="fans-number">120</div>
|
||||
<n-text class="fans-number">120</n-text>
|
||||
<div class="fans-title">{{ t('mobile_personal_info.follow') }}</div>
|
||||
</div>
|
||||
<div class="h-20px w-1px bg-gray-300"></div>
|
||||
<n-divider vertical class="h-20px" />
|
||||
<div class="min-w-10 flex flex-col items-center">
|
||||
<div class="fans-number">43.15W</div>
|
||||
<n-text class="fans-number">43.15W</n-text>
|
||||
<div class="fans-title">{{ t('mobile_personal_info.like') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1 justify-end flex items-center gap-3">
|
||||
<n-button
|
||||
:disabled="loading"
|
||||
@click="toEditProfile"
|
||||
v-if="props.isMyPage && !isBotUser(uid)"
|
||||
class="font-bold px-4 py-10px bg-#EEF4F3 text-#373838 rounded-full text-12px">
|
||||
{{ t('mobile_personal_info.edit_profile') }}
|
||||
</n-button>
|
||||
<n-button
|
||||
:loading="loading"
|
||||
:disabled="loading"
|
||||
@click="handleDelete"
|
||||
:color="'#d5304f'"
|
||||
v-if="!props.isMyPage && isMyFriend && !isBotUser(uid)"
|
||||
class="px-5 py-10px font-bold text-center rounded-full text-12px">
|
||||
{{ t('mobile_personal_info.remove_user') }}
|
||||
</n-button>
|
||||
</div>
|
||||
<div class="justify-end flex items-center gap-3 mt-30px">
|
||||
<n-button
|
||||
:disabled="loading"
|
||||
@click="toEditProfile"
|
||||
strong
|
||||
secondary
|
||||
round
|
||||
v-if="props.isMyPage && !isBotUser(uid)"
|
||||
class="font-bold px-4 py-10px rounded-full text-12px">
|
||||
{{ t('mobile_personal_info.edit_profile') }}
|
||||
</n-button>
|
||||
<n-button
|
||||
:loading="loading"
|
||||
:disabled="loading"
|
||||
strong
|
||||
secondary
|
||||
@click="handleDelete"
|
||||
:color="'#d5304f'"
|
||||
v-if="!props.isMyPage && isMyFriend && !isBotUser(uid)"
|
||||
class="px-5 py-10px font-bold text-center rounded-full text-12px">
|
||||
{{ t('mobile_personal_info.remove_user') }}
|
||||
</n-button>
|
||||
|
||||
<n-button
|
||||
type="primary"
|
||||
:disabled="loading"
|
||||
@click="handleAddFriend"
|
||||
v-if="!props.isMyPage && !isMyFriend && !isBotUser(uid)"
|
||||
class="px-5 py-10px font-bold text-center rounded-full text-12px">
|
||||
+
|
||||
{{ t('mobile_personal_info.add_friend') }}
|
||||
</n-button>
|
||||
<n-button
|
||||
type="primary"
|
||||
@click="toChatRoom"
|
||||
:disabled="loading"
|
||||
v-if="!props.isMyPage && isMyFriend"
|
||||
class="px-5 py-10px text-center font-bold rounded-full text-12px">
|
||||
{{ isBotUser(uid) ? t('mobile_personal_info.open_bot') : t('mobile_personal_info.chat') }}
|
||||
</n-button>
|
||||
</div>
|
||||
<n-button
|
||||
type="primary"
|
||||
:disabled="loading"
|
||||
strong
|
||||
secondary
|
||||
@click="handleAddFriend"
|
||||
v-if="!props.isMyPage && !isMyFriend && !isBotUser(uid)"
|
||||
class="px-5 py-10px font-bold text-center rounded-full text-12px">
|
||||
+
|
||||
{{ t('mobile_personal_info.add_friend') }}
|
||||
</n-button>
|
||||
<n-button
|
||||
type="primary"
|
||||
strong
|
||||
secondary
|
||||
@click="toChatRoom"
|
||||
:disabled="loading"
|
||||
v-if="!props.isMyPage && isMyFriend"
|
||||
class="px-5 py-10px text-center font-bold rounded-full text-12px">
|
||||
{{ isBotUser(uid) ? t('mobile_personal_info.open_bot') : t('mobile_personal_info.chat') }}
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<div class="grid grid-cols-[32px_32px_32px_32px] px-16px gap-12px z-1 h-32px items-center justify-end mt-10px">
|
||||
<div
|
||||
class="grid grid-cols-[32px_32px_32px_32px] px-16px gap-12px z-1 h-32px items-center justify-end mt-10px dark:invert">
|
||||
<div @click="toMyMessages" class="h-32px w-32px flex items-center justify-center">
|
||||
<n-badge :max="99" :value="unreadApplyCount" :show="unreadApplyCount > 0">
|
||||
<svg class="iconpark-icon h-24px w-24px block"><use href="#remind"></use></svg>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<template>
|
||||
<n-divider class="p-0! m-0!" />
|
||||
<div class="tab-bar flex justify-around items-end pt-3">
|
||||
<RouterLink
|
||||
v-for="item in navItems"
|
||||
:key="item.path"
|
||||
:to="item.path"
|
||||
class="tab-item flex flex-col flex-1 items-center no-underline relative"
|
||||
:class="route.path === item.path ? 'color-[--tab-bar-icon-color]' : 'text-#000'">
|
||||
:class="route.path === item.path ? 'color-[--tab-bar-icon-color]' : 'text-#000 dark:text-white/80'">
|
||||
<n-badge
|
||||
class="flex flex-col w-55% flex-1 relative items-center"
|
||||
:offset="[-6, 6]"
|
||||
@@ -85,7 +86,6 @@ const navItems: NavItem[] = [
|
||||
|
||||
<style scoped lang="scss">
|
||||
.tab-bar {
|
||||
border-top: 0.5px solid #e3e3e3;
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
@@ -6,163 +6,160 @@
|
||||
:enable-default-background="false"
|
||||
:enable-shadow="false"
|
||||
:room-name="t('mobile_forget_code.title')" />
|
||||
<n-config-provider :theme="lightTheme" class="bg-#fff rounded-8px select-none cursor-default">
|
||||
<n-flex vertical class="w-full size-full">
|
||||
<!-- 步骤条 -->
|
||||
<n-steps size="small" class="w-full px-40px mt-20px" :current="currentStep" :status="stepStatus">
|
||||
<n-step :title="t('mobile_forget_code.steps.verify_email')" description="" />
|
||||
<n-step :title="t('mobile_forget_code.steps.set_new_password')" description="" />
|
||||
<n-step :title="t('mobile_forget_code.steps.done')" description="" />
|
||||
</n-steps>
|
||||
<n-flex vertical class="w-full size-full">
|
||||
<!-- 步骤条 -->
|
||||
<n-steps size="small" class="w-full px-40px mt-20px" :current="currentStep" :status="stepStatus">
|
||||
<n-step :title="t('mobile_forget_code.steps.verify_email')" description="" />
|
||||
<n-step :title="t('mobile_forget_code.steps.set_new_password')" description="" />
|
||||
<n-step :title="t('mobile_forget_code.steps.done')" description="" />
|
||||
</n-steps>
|
||||
|
||||
<!-- 第一步:验证邮箱 -->
|
||||
<div v-if="currentStep === 1" class="w-full max-w-300px mx-auto mt-30px">
|
||||
<n-form ref="formRef" :model="formData" :rules="emailRules">
|
||||
<!-- 邮箱输入 -->
|
||||
<n-form-item path="email" :label="t('mobile_forget_code.input.label.email')">
|
||||
<!-- 第一步:验证邮箱 -->
|
||||
<div v-if="currentStep === 1" class="w-full max-w-300px mx-auto mt-30px">
|
||||
<n-form ref="formRef" :model="formData" :rules="emailRules">
|
||||
<!-- 邮箱输入 -->
|
||||
<n-form-item path="email" :label="t('mobile_forget_code.input.label.email')">
|
||||
<n-input
|
||||
:allow-input="noSideSpace"
|
||||
class="border-(1px solid #90909080)"
|
||||
v-model:value="formData.email"
|
||||
:placeholder="t('mobile_forget_code.input.email')"
|
||||
spellCheck="false"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
clearable />
|
||||
</n-form-item>
|
||||
|
||||
<!-- 邮箱验证码 -->
|
||||
<n-form-item path="emailCode" :label="t('mobile_forget_code.input.label.email_verification_code')">
|
||||
<n-flex :size="8">
|
||||
<n-input
|
||||
:allow-input="noSideSpace"
|
||||
class="border-(1px solid #90909080)"
|
||||
v-model:value="formData.email"
|
||||
:placeholder="t('mobile_forget_code.input.email')"
|
||||
v-model:value="formData.emailCode"
|
||||
:placeholder="t('mobile_forget_code.input.email_code')"
|
||||
spellCheck="false"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
clearable />
|
||||
</n-form-item>
|
||||
|
||||
<!-- 邮箱验证码 -->
|
||||
<n-form-item path="emailCode" :label="t('mobile_forget_code.input.label.email_verification_code')">
|
||||
<n-flex :size="8">
|
||||
<n-input
|
||||
:allow-input="noSideSpace"
|
||||
class="border-(1px solid #90909080)"
|
||||
v-model:value="formData.emailCode"
|
||||
:placeholder="t('mobile_forget_code.input.email_code')"
|
||||
spellCheck="false"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
maxlength="6" />
|
||||
<n-button
|
||||
color="#13987f"
|
||||
ghost
|
||||
:disabled="sendBtnDisabled"
|
||||
:loading="sendingEmailCode"
|
||||
@click="sendEmailCode"
|
||||
class="min-w-100px w-fit h-34px">
|
||||
{{ emailCodeBtnText }}
|
||||
</n-button>
|
||||
</n-flex>
|
||||
</n-form-item>
|
||||
|
||||
<n-button
|
||||
:loading="verifyLoading"
|
||||
:disabled="nextDisabled"
|
||||
tertiary
|
||||
style="color: #fff"
|
||||
@click="verifyEmail"
|
||||
class="mt-10px w-full gradient-button">
|
||||
{{ t('mobile_forget_code.button.next') }}
|
||||
</n-button>
|
||||
</n-form>
|
||||
</div>
|
||||
|
||||
<!-- 第二步:设置新密码 -->
|
||||
<div v-if="currentStep === 2" class="w-full max-w-300px mx-auto mt-30px">
|
||||
<n-form ref="passwordFormRef" :model="passwordForm" :rules="passwordRules">
|
||||
<!-- 新密码 -->
|
||||
<n-form-item path="password" :label="t('mobile_forget_code.input.label.new_pass')">
|
||||
<n-flex vertical :size="8" class="w-full">
|
||||
<n-input
|
||||
:allow-input="noSideSpace"
|
||||
class="border-(1px solid #90909080) w-full"
|
||||
v-model:value="passwordForm.password"
|
||||
type="password"
|
||||
show-password-on="click"
|
||||
:placeholder="t('mobile_forget_code.input.new_pass', { len: '6-16' })"
|
||||
maxlength="16"
|
||||
spellCheck="false"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
minlength="6" />
|
||||
<n-flex vertical :size="4" class="space-y-4px">
|
||||
<Validation
|
||||
:value="passwordForm.password"
|
||||
:message="t('mobile_forget_code.validation.minlength', { len: '6-16' })"
|
||||
:validator="validateMinLength" />
|
||||
<Validation
|
||||
:value="passwordForm.password"
|
||||
:message="t('mobile_forget_code.validation.valid_characters')"
|
||||
:validator="validateAlphaNumeric" />
|
||||
<Validation
|
||||
:value="passwordForm.password"
|
||||
:message="t('mobile_forget_code.validation.must_special_char')"
|
||||
:validator="validateSpecialChar" />
|
||||
</n-flex>
|
||||
</n-flex>
|
||||
</n-form-item>
|
||||
|
||||
<!-- 确认密码 -->
|
||||
<n-form-item path="confirmPassword" :label="t('mobile_forget_code.input.label.confirm_password')">
|
||||
<n-flex vertical :size="8" class="w-full">
|
||||
<n-input
|
||||
:allow-input="noSideSpace"
|
||||
class="border-(1px solid #90909080) w-full"
|
||||
v-model:value="passwordForm.confirmPassword"
|
||||
type="password"
|
||||
show-password-on="click"
|
||||
:placeholder="t('mobile_forget_code.input.confirm_password')"
|
||||
spellCheck="false"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
maxlength="16"
|
||||
minlength="6" />
|
||||
<n-flex vertical :size="4">
|
||||
<Validation
|
||||
:value="passwordForm.confirmPassword"
|
||||
:message="t('mobile_forget_code.validation.passwords_match')"
|
||||
:validator="(value: string) => value === passwordForm.password && value !== ''" />
|
||||
</n-flex>
|
||||
</n-flex>
|
||||
</n-form-item>
|
||||
|
||||
<n-flex :size="16" class="mt-30px">
|
||||
<n-button @click="goBack" class="flex-1">{{ t('mobile_forget_code.button.go_back_setp') }}</n-button>
|
||||
maxlength="6" />
|
||||
<n-button
|
||||
:loading="submitLoading"
|
||||
tertiary
|
||||
style="color: #fff"
|
||||
@click="submitNewPassword"
|
||||
class="flex-1 gradient-button">
|
||||
{{ t('mobile_forget_code.button.submit') }}
|
||||
color="#13987f"
|
||||
ghost
|
||||
:disabled="sendBtnDisabled"
|
||||
:loading="sendingEmailCode"
|
||||
@click="sendEmailCode"
|
||||
class="min-w-100px w-fit h-34px">
|
||||
{{ emailCodeBtnText }}
|
||||
</n-button>
|
||||
</n-flex>
|
||||
</n-form>
|
||||
</div>
|
||||
</n-form-item>
|
||||
|
||||
<!-- 第三步:完成 -->
|
||||
<div v-if="currentStep === 3" class="w-full max-w-300px mx-auto mt-100px text-center">
|
||||
<!-- <n-icon size="64" class="text-#13987f">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M9,20.42L2.79,14.21L5.62,11.38L9,14.77L18.88,4.88L21.71,7.71L9,20.42Z" />
|
||||
</svg>
|
||||
</n-icon> -->
|
||||
<img class="size-98px" src="/emoji/party-popper.webp" alt="" />
|
||||
<n-button
|
||||
:loading="verifyLoading"
|
||||
:disabled="nextDisabled"
|
||||
tertiary
|
||||
style="color: #fff"
|
||||
@click="verifyEmail"
|
||||
class="mt-10px w-full gradient-button">
|
||||
{{ t('mobile_forget_code.button.next') }}
|
||||
</n-button>
|
||||
</n-form>
|
||||
</div>
|
||||
|
||||
<div class="mt-16px text-18px">{{ t('mobile_forget_code.password_reset_success') }}</div>
|
||||
<div class="mt-16px text-14px text-#666">{{ t('mobile_forget_code.password_reset_success_desc') }}</div>
|
||||
</div>
|
||||
</n-flex>
|
||||
</n-config-provider>
|
||||
<!-- 第二步:设置新密码 -->
|
||||
<div v-if="currentStep === 2" class="w-full max-w-300px mx-auto mt-30px">
|
||||
<n-form ref="passwordFormRef" :model="passwordForm" :rules="passwordRules">
|
||||
<!-- 新密码 -->
|
||||
<n-form-item path="password" :label="t('mobile_forget_code.input.label.new_pass')">
|
||||
<n-flex vertical :size="8" class="w-full">
|
||||
<n-input
|
||||
:allow-input="noSideSpace"
|
||||
class="border-(1px solid #90909080) w-full"
|
||||
v-model:value="passwordForm.password"
|
||||
type="password"
|
||||
show-password-on="click"
|
||||
:placeholder="t('mobile_forget_code.input.new_pass', { len: '6-16' })"
|
||||
maxlength="16"
|
||||
spellCheck="false"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
minlength="6" />
|
||||
<n-flex vertical :size="4" class="space-y-4px">
|
||||
<Validation
|
||||
:value="passwordForm.password"
|
||||
:message="t('mobile_forget_code.validation.minlength', { len: '6-16' })"
|
||||
:validator="validateMinLength" />
|
||||
<Validation
|
||||
:value="passwordForm.password"
|
||||
:message="t('mobile_forget_code.validation.valid_characters')"
|
||||
:validator="validateAlphaNumeric" />
|
||||
<Validation
|
||||
:value="passwordForm.password"
|
||||
:message="t('mobile_forget_code.validation.must_special_char')"
|
||||
:validator="validateSpecialChar" />
|
||||
</n-flex>
|
||||
</n-flex>
|
||||
</n-form-item>
|
||||
|
||||
<!-- 确认密码 -->
|
||||
<n-form-item path="confirmPassword" :label="t('mobile_forget_code.input.label.confirm_password')">
|
||||
<n-flex vertical :size="8" class="w-full">
|
||||
<n-input
|
||||
:allow-input="noSideSpace"
|
||||
class="border-(1px solid #90909080) w-full"
|
||||
v-model:value="passwordForm.confirmPassword"
|
||||
type="password"
|
||||
show-password-on="click"
|
||||
:placeholder="t('mobile_forget_code.input.confirm_password')"
|
||||
spellCheck="false"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
maxlength="16"
|
||||
minlength="6" />
|
||||
<n-flex vertical :size="4">
|
||||
<Validation
|
||||
:value="passwordForm.confirmPassword"
|
||||
:message="t('mobile_forget_code.validation.passwords_match')"
|
||||
:validator="(value: string) => value === passwordForm.password && value !== ''" />
|
||||
</n-flex>
|
||||
</n-flex>
|
||||
</n-form-item>
|
||||
|
||||
<n-flex :size="16" class="mt-30px">
|
||||
<n-button @click="goBack" class="flex-1">{{ t('mobile_forget_code.button.go_back_setp') }}</n-button>
|
||||
<n-button
|
||||
:loading="submitLoading"
|
||||
tertiary
|
||||
style="color: #fff"
|
||||
@click="submitNewPassword"
|
||||
class="flex-1 gradient-button">
|
||||
{{ t('mobile_forget_code.button.submit') }}
|
||||
</n-button>
|
||||
</n-flex>
|
||||
</n-form>
|
||||
</div>
|
||||
|
||||
<!-- 第三步:完成 -->
|
||||
<div v-if="currentStep === 3" class="w-full max-w-300px mx-auto mt-100px text-center">
|
||||
<!-- <n-icon size="64" class="text-#13987f">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M9,20.42L2.79,14.21L5.62,11.38L9,14.77L18.88,4.88L21.71,7.71L9,20.42Z" />
|
||||
</svg>
|
||||
</n-icon> -->
|
||||
<img class="size-98px" src="/emoji/party-popper.webp" alt="" />
|
||||
|
||||
<div class="mt-16px text-18px">{{ t('mobile_forget_code.password_reset_success') }}</div>
|
||||
<div class="mt-16px text-14px text-#666">{{ t('mobile_forget_code.password_reset_success_desc') }}</div>
|
||||
</div>
|
||||
</n-flex>
|
||||
</MobileLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { lightTheme } from 'naive-ui'
|
||||
import Validation from '@/components/common/Validation.vue'
|
||||
import { forgetPassword, getCaptcha, sendCaptcha } from '@/utils/ImRequestUtils'
|
||||
import { validateAlphaNumeric, validateSpecialChar } from '@/utils/Validate'
|
||||
|
||||
@@ -1,49 +1,60 @@
|
||||
<template>
|
||||
<div class="h-100vh flex flex-col bg-gray-100">
|
||||
<img src="@/assets/mobile/chat-home/background.webp" class="w-100% absolute top-0 z-1" alt="hula" />
|
||||
<AutoFixHeightPage :show-footer="false" class="dark:bg-red">
|
||||
<template #header>
|
||||
<div class="h-100px"></div>
|
||||
<HeaderBar
|
||||
:isOfficial="false"
|
||||
:hidden-right="true"
|
||||
:enable-default-background="false"
|
||||
:enable-shadow="false"
|
||||
:room-name="t('mobile_personal_info_qr.title')" />
|
||||
</template>
|
||||
|
||||
<HeaderBar
|
||||
:isOfficial="false"
|
||||
:hidden-right="true"
|
||||
:enable-default-background="false"
|
||||
:enable-shadow="false"
|
||||
:room-name="t('mobile_personal_info_qr.title')" />
|
||||
<template #container>
|
||||
<div class="flex flex-col overflow-auto h-full relative">
|
||||
<img
|
||||
src="@/assets/mobile/chat-home/background.webp"
|
||||
class="absolute fixed top-0 left-0 w-full h-full z-0 dark:opacity-20" />
|
||||
|
||||
<!-- 页面全部内容 -->
|
||||
<div class="flex flex-col flex-1 items-center p-15px z-2 my-15">
|
||||
<div class="flex flex-col rounded-15px bg-white py-10">
|
||||
<div class="flex flex-1 flex-col px-5 gap-10px">
|
||||
<div class="flex flex-wrap ps-3 gap-10px">
|
||||
<div class="flex h-auto">
|
||||
<n-avatar round :size="60" :src="AvatarUtils.getAvatarUrl(userStore.userInfo?.avatar || '')" />
|
||||
<!-- 页面全部内容 -->
|
||||
<div class="flex flex-col flex-1 items-center p-15px z-2 my-15">
|
||||
<n-card class="flex flex-col rounded-15px py-10">
|
||||
<div class="flex flex-1 flex-col px-5 gap-10px">
|
||||
<div class="flex flex-wrap ps-3 gap-10px">
|
||||
<div class="flex h-auto">
|
||||
<n-avatar round :size="60" :src="AvatarUtils.getAvatarUrl(userStore.userInfo?.avatar || '')" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex flex-col text-#4e4e4e h-auto gap-8px overflow-hidden justify-center text-18px whitespace-normal break-words max-w-46">
|
||||
<n-text class="font-bold">{{ userInfo?.name }}</n-text>
|
||||
<n-text depth="3" class="text-16px">
|
||||
{{ t('mobile_personal_info_qr.account') }}:{{ userInfo?.account }}
|
||||
</n-text>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex w-auto justify-center">
|
||||
<n-qr-code
|
||||
:size="250"
|
||||
class="rounded-12px"
|
||||
:value="qrCodeValue"
|
||||
color="#14997E"
|
||||
:bg-color="qrCodeBgColor"
|
||||
:type="qrCodeType"
|
||||
:icon-src="AvatarUtils.getAvatarUrl(userStore.userInfo?.avatar ?? '')"
|
||||
:icon-size="60"
|
||||
:icon-margin="2"
|
||||
:error-correction-level="qrErrorCorrectionLevel" />
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center text-gray">{{ t('mobile_personal_info_qr.scan_to_add') }}</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex flex-col text-#4e4e4e h-auto gap-8px overflow-hidden justify-center text-18px whitespace-normal break-words max-w-46">
|
||||
<div class="font-bold">{{ userInfo?.name }}</div>
|
||||
<div class="text-16px">{{ t('mobile_personal_info_qr.account') }}:{{ userInfo?.account }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex w-auto justify-center">
|
||||
<n-qr-code
|
||||
:size="250"
|
||||
class="rounded-12px"
|
||||
:value="qrCodeValue"
|
||||
color="#14997E"
|
||||
:bg-color="qrCodeBgColor"
|
||||
:type="qrCodeType"
|
||||
:icon-src="AvatarUtils.getAvatarUrl(userStore.userInfo?.avatar || '')"
|
||||
:icon-size="60"
|
||||
:icon-margin="2"
|
||||
:error-correction-level="qrErrorCorrectionLevel" />
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center text-gray">{{ t('mobile_personal_info_qr.scan_to_add') }}</div>
|
||||
</n-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</AutoFixHeightPage>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
@@ -3,78 +3,83 @@
|
||||
<template #header>
|
||||
<HeaderBar
|
||||
:isOfficial="false"
|
||||
style="border-bottom: 1px solid; border-color: #dfdfdf"
|
||||
border
|
||||
:hidden-right="true"
|
||||
:room-name="t('mobile_chat_setting.title', { t: title })" />
|
||||
</template>
|
||||
|
||||
<template #container>
|
||||
<div
|
||||
class="bg-[url('@/assets/mobile/chat-home/background.webp')] bg-cover bg-center flex flex-col overflow-auto h-full">
|
||||
<div class="flex flex-col gap-15px py-15px px-20px flex-1 min-h-0">
|
||||
<div class="flex shadow py-10px bg-white rounded-10px w-full items-center gap-10px" @click="clickInfo">
|
||||
<!-- 群头像 -->
|
||||
<div class="flex justify-center">
|
||||
<div class="rounded-full relative bg-white w-38px h-38px overflow-hidden" style="margin-left: 10px">
|
||||
<n-avatar
|
||||
class="absolute"
|
||||
:size="38"
|
||||
:src="AvatarUtils.getAvatarUrl(activeItem?.avatar || '')"
|
||||
fallback-src="/logo.png"
|
||||
:style="{
|
||||
'object-fit': 'cover',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)'
|
||||
}"
|
||||
round />
|
||||
<div class="flex flex-col overflow-auto h-full relative">
|
||||
<img
|
||||
src="@/assets/mobile/chat-home/background.webp"
|
||||
class="absolute fixed top-0 left-0 w-full h-full z-0 dark:opacity-20" />
|
||||
<div class="flex flex-col gap-15px py-15px px-20px flex-1 min-h-0 z-1">
|
||||
<n-card size="small" embedded class="rounded-10px p-0" content-class="p-0!">
|
||||
<div class="flex py-10px rounded-10px w-full items-center gap-10px" @click="clickInfo">
|
||||
<!-- 群头像 -->
|
||||
<div class="flex justify-center">
|
||||
<div class="rounded-full relative bg-white w-38px h-38px overflow-hidden" style="margin-left: 10px">
|
||||
<n-avatar
|
||||
class="absolute"
|
||||
:size="38"
|
||||
:src="AvatarUtils.getAvatarUrl(activeItem?.avatar || '')"
|
||||
fallback-src="/logo.png"
|
||||
:style="{
|
||||
'object-fit': 'cover',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)'
|
||||
}"
|
||||
round />
|
||||
</div>
|
||||
<input
|
||||
v-if="isGroup"
|
||||
ref="fileInput"
|
||||
type="file"
|
||||
accept="image/jpeg,image/png,image/webp"
|
||||
class="hidden"
|
||||
@change="handleFileChange" />
|
||||
<AvatarCropper
|
||||
ref="cropperRef"
|
||||
v-model:show="showCropper"
|
||||
:image-url="localImageUrl"
|
||||
@crop="handleCrop" />
|
||||
</div>
|
||||
<input
|
||||
v-if="isGroup"
|
||||
ref="fileInput"
|
||||
type="file"
|
||||
accept="image/jpeg,image/png,image/webp"
|
||||
class="hidden"
|
||||
@change="handleFileChange" />
|
||||
<AvatarCropper
|
||||
ref="cropperRef"
|
||||
v-model:show="showCropper"
|
||||
:image-url="localImageUrl"
|
||||
@crop="handleCrop" />
|
||||
</div>
|
||||
|
||||
<div class="text-14px flex items-center h-full gap-5px">
|
||||
<span>
|
||||
{{ activeItem?.name || '' }}
|
||||
</span>
|
||||
<span v-if="activeItem?.hotFlag === 1">
|
||||
<svg class="w-18px h-18px iconpark-icon text-#1A9B83">
|
||||
<use href="#auth"></use>
|
||||
</svg>
|
||||
</span>
|
||||
<div class="text-14px flex items-center h-full gap-5px">
|
||||
<span>
|
||||
{{ activeItem?.name || '' }}
|
||||
</span>
|
||||
<span v-if="activeItem?.hotFlag === 1">
|
||||
<svg class="w-18px h-18px iconpark-icon text-#1A9B83">
|
||||
<use href="#auth"></use>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-card>
|
||||
<!-- 群成员 -->
|
||||
<div v-if="isGroup" class="bg-white rounded-10px max-w-full p-[5px_10px_5x_10px] shadow">
|
||||
<div class="p-[15px_15px_0px_15px] flex flex-col">
|
||||
<!-- 群号 -->
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="text-14px">{{ t('mobile_chat_setting.group_members_title') }}</div>
|
||||
<div @click="toGroupChatMember" class="text-12px text-#6E6E6E flex flex-wrap gap-10px items-center">
|
||||
<i18n-t keypath="mobile_chat_setting.member_count">
|
||||
<template #count>
|
||||
<span class="text-#398D7E">{{ groupStore.countInfo?.memberNum || 0 }}</span>
|
||||
</template>
|
||||
</i18n-t>
|
||||
<n-card
|
||||
v-if="isGroup"
|
||||
class="rounded-10px"
|
||||
content-class="p-[15px_15px_0px_15px]!"
|
||||
header-class="text-14px! p-[15px_15px_0px_15px]!"
|
||||
:title="t('mobile_chat_setting.group_members_title')">
|
||||
<template #header-extra>
|
||||
<div @click="toGroupChatMember" class="text-12px text-#6E6E6E flex flex-wrap gap-10px items-center">
|
||||
<i18n-t keypath="mobile_chat_setting.member_count">
|
||||
<template #count>
|
||||
<span class="text-#398D7E">{{ groupStore.countInfo?.memberNum || 0 }}</span>
|
||||
</template>
|
||||
</i18n-t>
|
||||
|
||||
<div>
|
||||
<svg class="w-14px h-14px iconpark-icon">
|
||||
<use href="#right"></use>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<svg class="w-14px h-14px iconpark-icon">
|
||||
<use href="#right"></use>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="py-15px px-5px grid grid-cols-5 gap-15px text-12px">
|
||||
<div
|
||||
@click="toFriendInfo(i.uid)"
|
||||
@@ -90,19 +95,16 @@
|
||||
</div>
|
||||
<div class="truncate max-w-full text-#707070">{{ i.name }}</div>
|
||||
</div>
|
||||
<div
|
||||
@click="toInviteGroupMember"
|
||||
class="flex flex-col justify-center items-center gap-5px cursor-pointer">
|
||||
<div
|
||||
class="rounded-full bg-#E5EFEE w-36px h-36px flex items-center justify-center hover:bg-#D5E5E0 transition-colors">
|
||||
<svg class="iconpark-icon h-25px w-25px">
|
||||
<div class="flex flex-col justify-center items-center gap-5px cursor-pointer">
|
||||
<n-button strong secondary circle @click="toInviteGroupMember">
|
||||
<svg class="iconpark-icon h-25px w-25px dark:opacity-40">
|
||||
<use href="#plus"></use>
|
||||
</svg>
|
||||
</div>
|
||||
</n-button>
|
||||
<div>{{ t('mobile_chat_setting.group_invite_member') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-card>
|
||||
|
||||
<!-- 管理群成员 -->
|
||||
<div
|
||||
@@ -112,19 +114,17 @@
|
||||
{{ t('mobile_chat_setting.manage_group_members') }}
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="bg-white p-15px rounded-10px shadow text-14px flex cursor-pointer"
|
||||
@click="handleSearchChatContent">
|
||||
<n-card class="rounded-10px" content-class="p-15px!" @click="handleSearchChatContent">
|
||||
{{ t('mobile_chat_setting.search_history') }}
|
||||
</div>
|
||||
</n-card>
|
||||
<!-- 群公告 -->
|
||||
<div class="flex bg-white rounded-10px w-full h-auto shadow">
|
||||
<div class="px-15px flex flex-col w-full">
|
||||
<!-- 群号 -->
|
||||
<div
|
||||
style="border-bottom: 1px solid; border-color: #ebebeb"
|
||||
@click="handleCopy(activeItem?.account || '')"
|
||||
class="flex justify-between py-15px items-center">
|
||||
<n-card
|
||||
class="rounded-10px"
|
||||
header-class="text-14px! p-15px!"
|
||||
:segmented="{ content: true, footer: 'soft' }"
|
||||
content-class="p-15px!">
|
||||
<template #header>
|
||||
<div @click="handleCopy(activeItem?.account || '')" class="flex justify-between items-center">
|
||||
<div class="text-14px">
|
||||
{{
|
||||
t('mobile_chat_setting.id_card.qr_code_label', {
|
||||
@@ -143,98 +143,89 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 公告内容 -->
|
||||
<div @click="goToNotice" v-if="isGroup" class="pt-15px flex flex-col text-14px gap-10px">
|
||||
<div>{{ t('mobile_chat_setting.group_notice.title') }}</div>
|
||||
<div class="text-#707070 line-clamp-2 text-12px line-height-20px">
|
||||
{{ announList.length > 0 ? announList[0]?.content : '' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- v-if="isGroup && groupStore.isAdminOrLord()" -->
|
||||
<div class="flex justify-between py-15px items-center">
|
||||
<div class="text-14px">{{ t('mobile_chat_setting.group_name') }}</div>
|
||||
<div class="text-12px text-#6E6E6E flex flex-wrap gap-10px items-center">
|
||||
<input
|
||||
style="
|
||||
height: 17px;
|
||||
border: none;
|
||||
text-align: right;
|
||||
outline: none;
|
||||
font-size: 14px;
|
||||
text-align: right;
|
||||
"
|
||||
v-model="nameValue"
|
||||
@blur="handleGroupInfoUpdate"
|
||||
:placeholder="t('mobile_chat_setting.input.group_name')" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="isGroup" class="flex justify-between py-15px items-center">
|
||||
<div class="text-14px">{{ t('mobile_chat_setting.group_alias') }}</div>
|
||||
<div class="text-12px text-#6E6E6E flex flex-wrap gap-10px items-center">
|
||||
<input
|
||||
style="
|
||||
height: 17px;
|
||||
border: none;
|
||||
text-align: right;
|
||||
outline: none;
|
||||
font-size: 14px;
|
||||
text-align: right;
|
||||
"
|
||||
v-model="nicknameValue"
|
||||
@blur="handleInfoUpdate"
|
||||
:placeholder="t('mobile_chat_setting.input.group_alias')" />
|
||||
</div>
|
||||
</template>
|
||||
<!-- 公告内容 -->
|
||||
<div @click="goToNotice" v-if="isGroup" class="flex flex-col text-14px gap-10px">
|
||||
<div>{{ t('mobile_chat_setting.group_notice.title') }}</div>
|
||||
<div class="text-#707070 line-clamp-2 text-12px line-height-20px">
|
||||
{{ announList.length > 0 ? announList[0]?.content : '' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- v-if="isGroup && groupStore.isAdminOrLord()" -->
|
||||
<div class="flex justify-between py-15px items-center">
|
||||
<div class="text-14px">{{ t('mobile_chat_setting.group_name') }}</div>
|
||||
<div class="text-12px text-#6E6E6E flex flex-wrap gap-10px items-center">
|
||||
<n-input
|
||||
size="small"
|
||||
style="border: none; text-align: right; outline: none; text-align: right"
|
||||
v-model:value="nameValue"
|
||||
@blur="handleGroupInfoUpdate"
|
||||
:placeholder="t('mobile_chat_setting.input.group_name')" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="isGroup" class="flex justify-between py-15px items-center">
|
||||
<div class="text-14px">{{ t('mobile_chat_setting.group_alias') }}</div>
|
||||
<div class="text-12px text-#6E6E6E flex flex-wrap gap-10px items-center">
|
||||
<n-input
|
||||
size="small"
|
||||
style="border: none; text-align: right; outline: none; text-align: right"
|
||||
v-model="nicknameValue"
|
||||
@blur="handleInfoUpdate"
|
||||
:placeholder="t('mobile_chat_setting.input.group_alias')" />
|
||||
</div>
|
||||
</div>
|
||||
</n-card>
|
||||
|
||||
<!-- 备注 -->
|
||||
<div class="w-full flex flex-col gap-15px rounded-10px">
|
||||
<div class="ps-15px text-14px">
|
||||
<span>{{ t('mobile_chat_setting.remark') }}</span>
|
||||
<span class="dark:text-white">{{ t('mobile_chat_setting.remark') }}</span>
|
||||
<span class="text-#6E6E6E ml-1">{{ t('mobile_chat_setting.remar_kprivate_visible') }}</span>
|
||||
</div>
|
||||
<div class="rounded-10px flex w-full bg-white shadow">
|
||||
<div class="w-full px-15px">
|
||||
<input
|
||||
v-model="remarkValue"
|
||||
class="h-50px w-full"
|
||||
style="border: none; outline: none; font-size: 14px"
|
||||
:placeholder="t('mobile_chat_setting.input.remark')"
|
||||
@blur="handleInfoUpdate" />
|
||||
</div>
|
||||
<n-input
|
||||
v-model="remarkValue"
|
||||
size="large"
|
||||
:placeholder="t('mobile_chat_setting.input.remark')"
|
||||
@blur="handleInfoUpdate" />
|
||||
</div>
|
||||
<n-card
|
||||
class="rounded-10px"
|
||||
content-class="p-15px!"
|
||||
header-class="p-15px! text-14px!"
|
||||
:title="t('mobile_chat_setting.setting_type', { t: title })">
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="text-14px">{{ t('mobile_chat_setting.pintop') }}</div>
|
||||
<n-switch :value="!!activeItem?.top" @update:value="handleTop" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex bg-white rounded-10px w-full h-auto shadow">
|
||||
<div class="px-15px flex flex-col w-full">
|
||||
<div class="pt-15px text-14px text-#6E6E6E">
|
||||
{{ t('mobile_chat_setting.setting_type', { t: title }) }}
|
||||
</div>
|
||||
<!-- 群号 -->
|
||||
<div
|
||||
style="border-bottom: 1px solid; border-color: #ebebeb"
|
||||
class="flex justify-between py-12px items-center">
|
||||
<div class="text-14px">{{ t('mobile_chat_setting.pintop') }}</div>
|
||||
<n-switch :value="!!activeItem?.top" @update:value="handleTop" />
|
||||
</div>
|
||||
<div
|
||||
style="border-bottom: 1px solid; border-color: #ebebeb"
|
||||
class="flex justify-between py-12px items-center">
|
||||
<div class="text-14px">{{ t('mobile_chat_setting.silent') }}</div>
|
||||
<n-switch
|
||||
@update:value="handleNotification"
|
||||
:value="activeItem?.muteNotification === NotificationTypeEnum.NOT_DISTURB" />
|
||||
</div>
|
||||
<n-divider />
|
||||
<div class="flex justify-between py-12px items-center">
|
||||
<div class="text-14px">{{ t('mobile_chat_setting.silent') }}</div>
|
||||
<n-switch
|
||||
@update:value="handleNotification"
|
||||
:value="activeItem?.muteNotification === NotificationTypeEnum.NOT_DISTURB" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="shadow bg-white cursor-pointer text-red text-14px rounded-10px w-full mb-20px">
|
||||
<div class="p-15px">{{ t('mobile_chat_setting.delete_chat_history') }}</div>
|
||||
</div>
|
||||
</n-card>
|
||||
<n-button
|
||||
strong
|
||||
secondary
|
||||
circle
|
||||
size="large"
|
||||
class="cursor-pointer text-red text-14px rounded-10px w-full mb-20px">
|
||||
{{ t('mobile_chat_setting.delete_chat_history') }}
|
||||
</n-button>
|
||||
<div class="mt-auto flex justify-center mb-20px">
|
||||
<!-- 解散群聊、退出群聊、删除好友按钮 -->
|
||||
<n-button v-if="isGroup && globalStore.currentSessionRoomId !== '1'" type="error" @click="handleExit">
|
||||
<n-button
|
||||
class="w-full"
|
||||
v-if="isGroup && globalStore.currentSessionRoomId !== '1'"
|
||||
strong
|
||||
secondary
|
||||
round
|
||||
type="error"
|
||||
size="large"
|
||||
@click="handleExit">
|
||||
{{
|
||||
isGroup
|
||||
? isLord
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
<template>
|
||||
<AutoFixHeightPage :show-footer="false">
|
||||
<template #header>
|
||||
<HeaderBar
|
||||
:isOfficial="false"
|
||||
style="border-bottom: 1px solid; border-color: #dfdfdf"
|
||||
:hidden-right="true"
|
||||
room-name="群成员" />
|
||||
<HeaderBar :isOfficial="false" border :hidden-right="true" room-name="群成员" />
|
||||
</template>
|
||||
|
||||
<template #container>
|
||||
@@ -14,10 +10,11 @@
|
||||
<!-- 搜索表单 -->
|
||||
<n-form @submit="handleSubmit" class="flex flex-wrap gap-10px">
|
||||
<div class="flex flex-1">
|
||||
<input
|
||||
v-model="formData.keyword"
|
||||
<n-input
|
||||
v-model:value="formData.keyword"
|
||||
placeholder="搜索"
|
||||
class="bg-gray-100 text-center border-none w-full rounded-10px h-30px" />
|
||||
size="medium"
|
||||
class="text-center border-none w-full rounded-10px" />
|
||||
</div>
|
||||
</n-form>
|
||||
|
||||
@@ -25,7 +22,6 @@
|
||||
<div ref="measure" class="flex absolute w-full h-full top-0 left-0 z-1"></div>
|
||||
<div class="absolute z-10 w-full">
|
||||
<div v-if="filteredList.length === 0" class="flex w-full justify-center mt-20px">无数据</div>
|
||||
|
||||
<n-virtual-list
|
||||
v-else
|
||||
:style="{ height: virtualScrollerHeight + 'px', width: '100%' }"
|
||||
@@ -39,9 +35,9 @@
|
||||
:src="AvatarUtils.getAvatarUrl(item.avatar)"
|
||||
fallback-src="/logo.png"
|
||||
round />
|
||||
<div class="line-clamp-1">
|
||||
<n-text class="line-clamp-1">
|
||||
{{ item.name }}
|
||||
</div>
|
||||
</n-text>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<div class="flex-1 py-5px shrink-0">
|
||||
<n-input
|
||||
v-model:value="keyword"
|
||||
class="rounded-10px w-full bg-gray-100 relative text-14px"
|
||||
class="rounded-10px w-full relative text-14px"
|
||||
placeholder="搜索联系人~"
|
||||
clearable
|
||||
spellCheck="false"
|
||||
@@ -44,7 +44,9 @@
|
||||
class="w-full flex items-center px-5px"
|
||||
:class="[
|
||||
'cursor-pointer select-none transition-colors duration-150',
|
||||
selectedList.includes(item.uid) ? 'bg-blue-50 border-blue-300' : 'hover:bg-gray-50'
|
||||
selectedList.includes(item.uid)
|
||||
? 'bg-blue-100 text-blue-700 dark:bg-blue-500/20 dark:text-blue-300'
|
||||
: 'data-[active=true]:hover:bg-blue-100 dark:data-[active=true]:hover:bg-blue-500/20'
|
||||
]">
|
||||
<template #default>
|
||||
<div class="flex items-center gap-10px px-8px py-10px">
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="flex gap-2 mx-2">
|
||||
<input class="flex-1" type="text" placeholder="请输入聊天内容" />
|
||||
<n-button type="primary" @click="() => router.back()">取消</n-button>
|
||||
<n-input type="text" placeholder="请输入聊天内容" />
|
||||
<n-button strong secondary type="primary" @click="() => router.back()">取消</n-button>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center mt-10">
|
||||
|
||||
@@ -1,74 +1,60 @@
|
||||
<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="公告详情" />
|
||||
<HeaderBar :isOfficial="false" class="bg-white" border :hidden-right="true" room-name="公告详情" />
|
||||
</template>
|
||||
|
||||
<template #container>
|
||||
<div
|
||||
class="bg-[url('@/assets/mobile/chat-home/background.webp')] bg-cover bg-center flex flex-col overflow-auto h-full">
|
||||
<div class="flex flex-col overflow-auto h-full relative">
|
||||
<img
|
||||
src="@/assets/mobile/chat-home/background.webp"
|
||||
class="absolute t-0 l-0 w-full h-full z-0 dark:opacity-20" />
|
||||
<div class="flex flex-col flex-1 gap-15px py-15px px-20px">
|
||||
<div v-if="loading" class="flex justify-center items-center h-200px">
|
||||
<n-spin size="large" />
|
||||
</div>
|
||||
|
||||
<div v-else-if="announcement" class="bg-white flex flex-col shadow p-10px gap-15px text-14px rounded-15px">
|
||||
<!-- 公告头部信息 -->
|
||||
<div
|
||||
style="border-bottom: 1px solid; border-color: #ebebeb"
|
||||
class="grid grid-cols-[2.2rem_1fr_4rem] items-start px-2 py-3 gap-1">
|
||||
<!-- 头像 -->
|
||||
<div class="self-center h-38px">
|
||||
<n-badge>
|
||||
<n-avatar :size="40" :src="publisherAvatar" :fallback-src="getFallbackAvatar()" round />
|
||||
</n-badge>
|
||||
</div>
|
||||
|
||||
<!-- 发布人信息 -->
|
||||
<div class="truncate pl-4 flex gap-10px flex-col">
|
||||
<div class="text-14px leading-tight font-bold flex-1 truncate text-#333">
|
||||
{{ publisherName }}
|
||||
<n-card
|
||||
v-else-if="announcement"
|
||||
class="rounded-15px"
|
||||
header-class="p-10px!"
|
||||
:segmented="{ content: true, footer: 'soft' }">
|
||||
<template #header>
|
||||
<div class="grid grid-cols-[2.2rem_1fr_4rem] items-start px-2 py-3 gap-1">
|
||||
<div class="self-center h-38px">
|
||||
<n-badge>
|
||||
<n-avatar :size="40" :src="publisherAvatar" :fallback-src="getFallbackAvatar()" round />
|
||||
</n-badge>
|
||||
</div>
|
||||
<div class="text-12px text-#333">
|
||||
{{ formatTimestamp(announcement.createTime) }}
|
||||
|
||||
<!-- 发布人信息 -->
|
||||
<div class="truncate pl-4 flex gap-10px flex-col">
|
||||
<div class="text-14px leading-tight font-bold flex-1 truncate">
|
||||
{{ publisherName }}
|
||||
</div>
|
||||
<n-text depth="3" class="text-12px">
|
||||
{{ formatTimestamp(announcement.createTime) }}
|
||||
</n-text>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 阅读统计 -->
|
||||
<template #header-extra>
|
||||
<div class="justify-self-end self-center text-12px text-right flex gap-1 items-center">
|
||||
<span class="text-#13987F">{{ announcement.readCount || 0 }}人已读</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 公告内容 -->
|
||||
<div class="announcement-content whitespace-pre-wrap break-words text-14px leading-6 text-#333">
|
||||
<n-text>
|
||||
{{ announcement.content }}
|
||||
</div>
|
||||
</n-text>
|
||||
|
||||
<!-- 编辑按钮(仅管理员/群主可见) -->
|
||||
<div v-if="canEdit" class="flex justify-center mb-10px">
|
||||
<div
|
||||
@click="goToNoticeEdit"
|
||||
style="
|
||||
background: linear-gradient(145deg, #7eb7ac, #6fb0a4, #5fa89c);
|
||||
border-radius: 30px;
|
||||
padding: 10px 30px;
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
">
|
||||
编辑公告
|
||||
<template #action v-if="canEdit">
|
||||
<div class="flex justify-center">
|
||||
<n-button strong secondary type="primary" class="m-auto" @click="goToNoticeEdit">编辑公告</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</n-card>
|
||||
|
||||
<div v-else class="flex justify-center items-center h-200px text-#909090">公告不存在或已被删除</div>
|
||||
</div>
|
||||
|
||||
@@ -4,50 +4,36 @@
|
||||
<HeaderBar
|
||||
:isOfficial="false"
|
||||
class="bg-white"
|
||||
style="border-bottom: 1px solid; border-color: #dfdfdf"
|
||||
border
|
||||
:hidden-right="true"
|
||||
:room-name="isEditMode ? '编辑群公告' : '新增群公告'" />
|
||||
</template>
|
||||
|
||||
<template #container>
|
||||
<div
|
||||
class="bg-[url('@/assets/mobile/chat-home/background.webp')] bg-cover bg-center flex flex-col overflow-auto h-full">
|
||||
<div class="flex flex-col flex-1 gap-20px py-15px px-20px">
|
||||
<!-- 公告内容编辑区域 -->
|
||||
<div class="bg-white rounded-15px p-15px shadow">
|
||||
<n-form label-placement="top" size="medium">
|
||||
<n-form-item label="公告内容" required>
|
||||
<n-input
|
||||
v-model:value="announcementContent"
|
||||
type="textarea"
|
||||
placeholder="请输入公告内容..."
|
||||
class="w-full"
|
||||
:autosize="announcementAutosize"
|
||||
:maxlength="1000"
|
||||
:show-count="true" />
|
||||
</n-form-item>
|
||||
<n-card :bordered="false" class="h-full" content-class="p-10px!">
|
||||
<n-input
|
||||
v-model:value="announcementContent"
|
||||
type="textarea"
|
||||
placeholder="请输入公告内容..."
|
||||
class="w-full p-0! m-0!"
|
||||
:autosize="announcementAutosize"
|
||||
:maxlength="1000"
|
||||
:show-count="true" />
|
||||
|
||||
<n-form-item label="上传图片(暂不支持)">
|
||||
<div class="upload-image-container">
|
||||
<n-upload
|
||||
action="https://www.mocky.io/v2/5e4bafc63100007100d8b70f"
|
||||
list-type="image-card"
|
||||
:max="4"
|
||||
disabled>
|
||||
<div class="upload-trigger">
|
||||
<svg class="size-24px text-#999">
|
||||
<use href="#plus"></use>
|
||||
</svg>
|
||||
<span class="text-12px text-#999 mt-5px">点击上传</span>
|
||||
</div>
|
||||
</n-upload>
|
||||
</div>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</div>
|
||||
<div class="upload-image-container pt-10px">
|
||||
<n-upload action="https://www.mocky.io/v2/5e4bafc63100007100d8b70f" list-type="image-card" :max="4" disabled>
|
||||
<div class="upload-trigger">
|
||||
<svg class="size-24px text-#999">
|
||||
<use href="#plus"></use>
|
||||
</svg>
|
||||
<span class="text-12px text-#999 mt-5px">点击上传</span>
|
||||
</div>
|
||||
</n-upload>
|
||||
</div>
|
||||
|
||||
<!-- 置顶设置区域 -->
|
||||
<div class="bg-white rounded-15px shadow">
|
||||
<template #action>
|
||||
<div class="pb-10px">
|
||||
<!-- 置顶设置区域 -->
|
||||
<div class="flex flex-col w-full">
|
||||
<div class="flex justify-between py-15px px-15px items-center border-b border-gray-200">
|
||||
<div class="flex flex-col">
|
||||
@@ -57,17 +43,27 @@
|
||||
<n-switch v-model:value="top" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="flex justify-center gap-15px">
|
||||
<n-button type="default" class="w-40%" size="large" @click="handleCancel">取消</n-button>
|
||||
<n-button type="primary" class="w-40%" size="large" @click="handleSubmit" :loading="submitting">
|
||||
保存
|
||||
</n-button>
|
||||
<!-- 操作按钮 -->
|
||||
<div class="flex justify-center gap-15px">
|
||||
<n-button type="default" class="w-40%" strong secondary round size="large" @click="handleCancel">
|
||||
取消
|
||||
</n-button>
|
||||
<n-button
|
||||
type="primary"
|
||||
class="w-40%"
|
||||
strong
|
||||
secondary
|
||||
round
|
||||
size="large"
|
||||
@click="handleSubmit"
|
||||
:loading="submitting">
|
||||
保存
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</n-card>
|
||||
</template>
|
||||
</AutoFixHeightPage>
|
||||
</template>
|
||||
@@ -172,22 +168,8 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 按钮样式优化 */
|
||||
.n-button {
|
||||
border-radius: 30px;
|
||||
font-weight: 500;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.n-button:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.n-button--primary {
|
||||
background: linear-gradient(145deg, #7eb7ac, #6fb0a4, #5fa89c);
|
||||
border: none;
|
||||
:deep(.n-card > .n-card__action) {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
/* 上传图片组件样式优化 */
|
||||
|
||||
@@ -1,47 +1,46 @@
|
||||
<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="群公告" />
|
||||
<HeaderBar :isOfficial="false" class="bg-white" border :hidden-right="true" room-name="群公告" />
|
||||
</template>
|
||||
|
||||
<template #container>
|
||||
<div
|
||||
class="bg-[url('@/assets/mobile/chat-home/background.webp')] bg-cover bg-center flex flex-col overflow-auto h-full relative">
|
||||
<div class="flex flex-col overflow-auto h-full relative">
|
||||
<img
|
||||
src="@/assets/mobile/chat-home/background.webp"
|
||||
class="absolute t-0 l-0 w-full h-full z-0 dark:opacity-20" />
|
||||
<div class="flex flex-col flex-1 gap-15px py-15px px-20px">
|
||||
<RecycleScroller :items="announList" :item-size="15" key-field="id" class="flex flex-col gap-15px">
|
||||
<template #default="{ item }">
|
||||
<!-- 公告内容块 -->
|
||||
<div @click="goToNoticeDetail(item.id)" class="shadow flex p-15px bg-white rounded-10px">
|
||||
<div class="flex flex-col w-full gap-10px">
|
||||
<!-- 时间/阅读人数 -->
|
||||
<div class="flex items-center justify-between text-14px">
|
||||
<span class="flex gap-5px">
|
||||
<span class="text-#717171">发布人:</span>
|
||||
<span class="text-black">{{ groupStore.getUserInfo(item.uid)?.name }}</span>
|
||||
</span>
|
||||
<span
|
||||
v-if="item.isTop"
|
||||
class="text-#13987F rounded-15px px-7px py-5px text-12px"
|
||||
style="border: 1px solid; border-color: #13987f">
|
||||
置顶
|
||||
</span>
|
||||
</div>
|
||||
<!-- 公告内容 -->
|
||||
<div class="text-14px line-clamp-3 line-height-20px text-#717171 max-h-60px">
|
||||
{{ item.content }}
|
||||
</div>
|
||||
<n-card content-class="p-15px!" class="rounded-10px">
|
||||
<div @click="goToNoticeDetail(item.id)">
|
||||
<div class="flex flex-col w-full gap-10px">
|
||||
<!-- 时间/阅读人数 -->
|
||||
<div class="flex items-center justify-between text-14px">
|
||||
<span class="flex gap-5px">
|
||||
<span class="text-#717171">发布人:</span>
|
||||
<span class="text-black dark:text-white/80">{{ groupStore.getUserInfo(item.uid)?.name }}</span>
|
||||
</span>
|
||||
<span
|
||||
v-if="item.isTop"
|
||||
class="text-#13987F rounded-15px px-7px py-5px text-12px"
|
||||
style="border: 1px solid; border-color: #13987f">
|
||||
置顶
|
||||
</span>
|
||||
</div>
|
||||
<!-- 公告内容 -->
|
||||
<div class="text-14px line-clamp-3 line-height-20px text-#717171 max-h-60px">
|
||||
{{ item.content }}
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between text-12px">
|
||||
<span class="flex gap-5px text-#717171">{{ formatTimestamp(item.createTime) }}</span>
|
||||
<span class="text-#13987F">128人已读</span>
|
||||
<div class="flex items-center justify-between text-12px">
|
||||
<span class="flex gap-5px text-#717171">{{ formatTimestamp(item.createTime) }}</span>
|
||||
<span class="text-#13987F">128人已读</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-card>
|
||||
</template>
|
||||
</RecycleScroller>
|
||||
</div>
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
<template>
|
||||
<div class="flex flex-col h-full flex-1 bg-white">
|
||||
<div class="flex flex-col h-full flex-1">
|
||||
<!-- 固定顶部区域 -->
|
||||
<Transition name="header-fade">
|
||||
<div
|
||||
v-if="isHeaderFixed"
|
||||
class="fixed backdrop-blur-md left-0 right-0 z-100 flex items-center justify-between"
|
||||
class="fixed backdrop-blur-md left-0 right-0 z-100 flex items-center justify-between bg-[rgba(255,255,255,0.8)] dark:bg-[rgba(20,20,20,0.8)]"
|
||||
:style="{
|
||||
height: 'env(safe-area-inset-top, 0px)',
|
||||
paddingTop: '36px',
|
||||
background: 'rgba(220, 220, 220, 0.6)'
|
||||
paddingTop: '36px'
|
||||
}">
|
||||
<div class="pl-24px pt-16px">
|
||||
<n-button text @click="openNotificationPopup" class="relative text-#303030">
|
||||
<n-button text @click="openNotificationPopup" class="relative text-#303030 dark:invert">
|
||||
<template #icon>
|
||||
<div class="relative">
|
||||
<svg class="size-22px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
@@ -31,7 +30,7 @@
|
||||
</template>
|
||||
</n-button>
|
||||
</div>
|
||||
<div class="flex-1 flex justify-center pt-12px text-16px font-600 text-#303030">
|
||||
<div class="flex-1 flex justify-center pt-12px text-16px font-600 text-#303030 dark:text-white">
|
||||
{{ t('dynamic.page.mobile_title') }}
|
||||
</div>
|
||||
<div class="pr-24px opacity-0 pointer-events-none">
|
||||
|
||||
@@ -1,86 +1,97 @@
|
||||
<template>
|
||||
<div class="flex flex-col h-full">
|
||||
<HeaderBar
|
||||
:isOfficial="false"
|
||||
:hidden-right="true"
|
||||
:enable-default-background="false"
|
||||
:enable-shadow="false"
|
||||
room-name="用户资料" />
|
||||
<AutoFixHeightPage :show-footer="false">
|
||||
<template #header>
|
||||
<HeaderBar
|
||||
:isOfficial="false"
|
||||
:hidden-right="true"
|
||||
:enable-default-background="false"
|
||||
:enable-shadow="false"
|
||||
room-name="用户资料" />
|
||||
</template>
|
||||
|
||||
<img src="@/assets/mobile/chat-home/background.webp" class="w-100% fixed top-0" alt="hula" />
|
||||
<template #container>
|
||||
<div class="flex flex-col overflow-auto h-full relative">
|
||||
<img
|
||||
src="@/assets/mobile/chat-home/background.webp"
|
||||
class="absolute fixed top-0 left-0 w-full h-full z-0 dark:opacity-20" />
|
||||
|
||||
<PersonalInfo :is-my-page="isMyPage" :is-show="isShow"></PersonalInfo>
|
||||
<PersonalInfo :is-my-page="isMyPage" :is-show="isShow"></PersonalInfo>
|
||||
|
||||
<div class="relative top-0 flex-1 flex">
|
||||
<div ref="measureRef" class="h-full w-full absolute top-0 z-0"></div>
|
||||
<div class="top-0 flex-1 flex w-full border-#13987F border-1">
|
||||
<div ref="measureRef" class="h-full w-full absolute top-0 z-0"></div>
|
||||
|
||||
<div ref="scrollContainer" :style="{ height: tabHeight + 'px' }" class="z-1 overflow-y-auto absolute z-3">
|
||||
<div class="custom-rounded flex px-24px flex-col gap-4 z-1 p-10px mt-4 shadow">
|
||||
<CommunityTab
|
||||
:style="{ height: tabHeight - 10 + 'px' }"
|
||||
:custom-height="tabHeight - 10"
|
||||
@scroll="handleScroll"
|
||||
@update="onUpdate"
|
||||
:options="tabOptions"
|
||||
active-tab-name="find">
|
||||
<template #find>
|
||||
<!-- 加载状态 -->
|
||||
<div
|
||||
v-if="feedOptions.isLoading && feedList.length === 0"
|
||||
class="flex justify-center items-center py-20px">
|
||||
<n-spin size="large" />
|
||||
</div>
|
||||
<div
|
||||
ref="scrollContainer"
|
||||
:style="{ height: tabHeight + 'px' }"
|
||||
class="w-full z-1 overflow-y-auto absolute z-3">
|
||||
<div class="custom-rounded flex px-24px flex-col gap-4 z-1 p-10px mt-4 shadow">
|
||||
<CommunityTab
|
||||
:style="{ height: tabHeight - 10 + 'px' }"
|
||||
:custom-height="tabHeight - 10"
|
||||
@scroll="handleScroll"
|
||||
@update="onUpdate"
|
||||
:options="tabOptions"
|
||||
active-tab-name="find">
|
||||
<template #find>
|
||||
<!-- 加载状态 -->
|
||||
<div
|
||||
v-if="feedOptions.isLoading && feedList.length === 0"
|
||||
class="flex justify-center items-center py-20px">
|
||||
<n-spin size="large" />
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-else-if="feedList.length === 0" class="flex justify-center items-center py-40px text-gray-500">
|
||||
暂无动态
|
||||
</div>
|
||||
<!-- 空状态 -->
|
||||
<div v-else-if="feedList.length === 0" class="flex justify-center items-center py-40px text-gray-500">
|
||||
暂无动态
|
||||
</div>
|
||||
|
||||
<!-- 动态列表 -->
|
||||
<template v-else>
|
||||
<CommunityContent v-for="item in feedList" :key="item.id" :feed-item="item" />
|
||||
<!-- 动态列表 -->
|
||||
<template v-else>
|
||||
<CommunityContent v-for="item in feedList" :key="item.id" :feed-item="item" />
|
||||
|
||||
<!-- 加载更多 -->
|
||||
<div v-if="!feedOptions.isLast" class="flex justify-center py-15px">
|
||||
<n-button :loading="feedOptions.isLoading" @click="loadMore" type="primary" text size="small">
|
||||
{{ feedOptions.isLoading ? '加载中...' : '加载更多' }}
|
||||
</n-button>
|
||||
</div>
|
||||
<!-- 加载更多 -->
|
||||
<div v-if="!feedOptions.isLast" class="flex justify-center py-15px">
|
||||
<n-button :loading="feedOptions.isLoading" @click="loadMore" type="primary" text size="small">
|
||||
{{ feedOptions.isLoading ? '加载中...' : '加载更多' }}
|
||||
</n-button>
|
||||
</div>
|
||||
|
||||
<!-- 已加载全部 -->
|
||||
<div v-else class="flex justify-center py-15px text-12px text-gray-400">已加载全部</div>
|
||||
</template>
|
||||
</template>
|
||||
<!-- 已加载全部 -->
|
||||
<div v-else class="flex justify-center py-15px text-12px text-gray-400">已加载全部</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<template #follow>
|
||||
<!-- 赞过的动态 -->
|
||||
<div
|
||||
v-if="feedOptions.isLoading && feedList.length === 0"
|
||||
class="flex justify-center items-center py-20px">
|
||||
<n-spin size="large" />
|
||||
</div>
|
||||
<template #follow>
|
||||
<!-- 赞过的动态 -->
|
||||
<div
|
||||
v-if="feedOptions.isLoading && feedList.length === 0"
|
||||
class="flex justify-center items-center py-20px">
|
||||
<n-spin size="large" />
|
||||
</div>
|
||||
|
||||
<div v-else-if="feedList.length === 0" class="flex justify-center items-center py-40px text-gray-500">
|
||||
暂无赞过的动态
|
||||
</div>
|
||||
<div v-else-if="feedList.length === 0" class="flex justify-center items-center py-40px text-gray-500">
|
||||
暂无赞过的动态
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<CommunityContent v-for="item in feedList" :key="item.id" :feed-item="item" />
|
||||
<template v-else>
|
||||
<CommunityContent v-for="item in feedList" :key="item.id" :feed-item="item" />
|
||||
|
||||
<div v-if="!feedOptions.isLast" class="flex justify-center py-15px">
|
||||
<n-button :loading="feedOptions.isLoading" @click="loadMore" type="primary" text size="small">
|
||||
{{ feedOptions.isLoading ? '加载中...' : '加载更多' }}
|
||||
</n-button>
|
||||
</div>
|
||||
<div v-if="!feedOptions.isLast" class="flex justify-center py-15px">
|
||||
<n-button :loading="feedOptions.isLoading" @click="loadMore" type="primary" text size="small">
|
||||
{{ feedOptions.isLoading ? '加载中...' : '加载更多' }}
|
||||
</n-button>
|
||||
</div>
|
||||
|
||||
<div v-else class="flex justify-center py-15px text-12px text-gray-400">已加载全部</div>
|
||||
</template>
|
||||
</template>
|
||||
</CommunityTab>
|
||||
<div v-else class="flex justify-center py-15px text-12px text-gray-400">已加载全部</div>
|
||||
</template>
|
||||
</template>
|
||||
</CommunityTab>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</AutoFixHeightPage>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<template>
|
||||
<div class="flex flex-col h-full flex-1">
|
||||
<img src="@/assets/mobile/chat-home/background.webp" class="w-100% fixed top-0" alt="hula" />
|
||||
|
||||
<div class="flex flex-col overflow-auto h-full relative">
|
||||
<img
|
||||
src="@/assets/mobile/chat-home/background.webp"
|
||||
class="absolute fixed top-0 left-0 w-full h-full z-0 dark:opacity-20" />
|
||||
<!-- 页面蒙板 -->
|
||||
<div
|
||||
v-if="showMask"
|
||||
@@ -11,7 +12,9 @@
|
||||
|
||||
<!-- 导航条 -->
|
||||
<NavBar>
|
||||
<template #center>{{ t('mobile_contact.title') }}</template>
|
||||
<template #center>
|
||||
<n-text>{{ t('mobile_contact.title') }}</n-text>
|
||||
</template>
|
||||
<template #right>
|
||||
<n-dropdown
|
||||
@on-clickoutside="addIconHandler.clickOutside"
|
||||
@@ -19,9 +22,13 @@
|
||||
trigger="click"
|
||||
:show-arrow="true"
|
||||
:options="uiViewsData.addOptions">
|
||||
<svg @click="addIconHandler.open" class="size-22px bg-white p-5px rounded-8px">
|
||||
<use href="#plus"></use>
|
||||
</svg>
|
||||
<n-button round strong secondary @click="addIconHandler.open">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<svg><use href="#plus"></use></svg>
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-button>
|
||||
</n-dropdown>
|
||||
</template>
|
||||
</NavBar>
|
||||
@@ -30,7 +37,7 @@
|
||||
<div class="px-16px mt-2 mb-12px z-1">
|
||||
<n-input
|
||||
id="search"
|
||||
class="rounded-6px w-full bg-white relative text-12px"
|
||||
class="rounded-6px w-full relative text-12px"
|
||||
:maxlength="20"
|
||||
clearable
|
||||
spellCheck="false"
|
||||
@@ -44,11 +51,13 @@
|
||||
</n-input>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-1 gap-2 flex-col bg-white z-1 custom-rounded">
|
||||
<!-- 我的消息条 -->
|
||||
<div
|
||||
class="grid grid-cols-[4rem_1fr_24px] items-center gap-8px py-15px px-16px border-b-[1px] border-b-solid border-b-[#e5e7eb]">
|
||||
<div class="h-full flex items-center text-14px">{{ t('mobile_contact.my_chat') }}</div>
|
||||
<n-card
|
||||
:segmented="{ content: true, footer: 'soft' }"
|
||||
:bordered="false"
|
||||
class="custom-rounded flex-1"
|
||||
header-class="py-15px! px-16px! text-14px!"
|
||||
:title="t('mobile_contact.my_chat')">
|
||||
<template #header-extra>
|
||||
<div @click="toMessage" class="h-full flex items-center justify-end">
|
||||
<span
|
||||
v-if="contactUnreadCount > 0"
|
||||
@@ -57,9 +66,9 @@
|
||||
</span>
|
||||
</div>
|
||||
<div @click="toMessage" class="h-full flex justify-end items-center">
|
||||
<img src="@/assets/mobile/friend/right-arrow.webp" class="block h-20px" alt="" />
|
||||
<img src="@/assets/mobile/friend/right-arrow.webp" class="block h-20px dark:invert" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<n-tabs type="segment" animated class="mt-4px p-[4px_10px_0px_8px]">
|
||||
<n-tab-pane name="1" :tab="t('mobile_contact.tab.contacts')">
|
||||
@@ -146,7 +155,7 @@
|
||||
</n-collapse>
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</div>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped>
|
||||
@@ -182,7 +191,8 @@ const renderImgIcon = (src: string) => {
|
||||
return () =>
|
||||
h('img', {
|
||||
src,
|
||||
style: 'display:block; width: 24px; height: 24px; vertical-align: middle'
|
||||
style: 'display:block; width: 24px; height: 24px; vertical-align: middle',
|
||||
class: 'dark:invert'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<template>
|
||||
<div class="flex flex-col h-full">
|
||||
<img src="@/assets/mobile/chat-home/background.webp" class="w-100% fixed top-0" alt="hula" />
|
||||
<div class="flex flex-col overflow-auto h-full relative">
|
||||
<img
|
||||
src="@/assets/mobile/chat-home/background.webp"
|
||||
class="absolute fixed top-0 l-0 w-full h-full z-0 dark:opacity-20" />
|
||||
|
||||
<!-- 页面蒙板 -->
|
||||
<div
|
||||
@@ -53,9 +55,16 @@
|
||||
trigger="click"
|
||||
:show-arrow="true"
|
||||
:options="uiViewsData.addOptions">
|
||||
<svg @click="addIconHandler.open" class="size-22px bg-white p-5px rounded-8px">
|
||||
<n-button round strong secondary @click="addIconHandler.open">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<svg><use href="#plus"></use></svg>
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-button>
|
||||
<!-- <svg @click="addIconHandler.open" class="size-22px p-5px rounded-8px">
|
||||
<use href="#plus"></use>
|
||||
</svg>
|
||||
</svg> -->
|
||||
</n-dropdown>
|
||||
</template>
|
||||
</NavBar>
|
||||
@@ -64,7 +73,7 @@
|
||||
<div class="py-5px shrink-0">
|
||||
<n-input
|
||||
id="search"
|
||||
class="rounded-6px w-full bg-white relative text-12px"
|
||||
class="rounded-6px w-full relative text-12px"
|
||||
:maxlength="20"
|
||||
clearable
|
||||
spellCheck="false"
|
||||
@@ -81,7 +90,7 @@
|
||||
</template>
|
||||
</n-input>
|
||||
</div>
|
||||
<div class="border-b-1 border-solid color-gray-200 px-18px mt-5px"></div>
|
||||
<n-divider class="m-0! p-0! mt-10px!" />
|
||||
</div>
|
||||
|
||||
<van-pull-refresh
|
||||
@@ -115,8 +124,8 @@
|
||||
</div>
|
||||
<!-- 中间:两行内容 -->
|
||||
<div class="truncate pl-7 flex pt-5px gap-10px leading-tight flex-col">
|
||||
<div class="text-16px font-bold flex-1 truncate text-#333 truncate">{{ item.name }}</div>
|
||||
<div class="text-13px text-#555 truncate">
|
||||
<n-text class="text-16px font-bold flex-1 truncate">{{ item.name }}</n-text>
|
||||
<div class="text-13px text-gray-600 dark:text-gray-400 truncate">
|
||||
{{ item.text }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -129,12 +138,12 @@
|
||||
<use href="#auth"></use>
|
||||
</svg>
|
||||
</span>
|
||||
<span class="text-#555 whitespace-nowrap">
|
||||
<span class="text-gray-600 whitespace-nowrap">
|
||||
{{ formatTimestamp(item?.activeTime) }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="item.muteNotification === NotificationTypeEnum.NOT_DISTURB">
|
||||
<svg class="size-14px z-100 color-#909090">
|
||||
<svg class="size-14px z-100 color-gray-500/90">
|
||||
<use href="#close-remind"></use>
|
||||
</svg>
|
||||
</div>
|
||||
@@ -447,7 +456,8 @@ const renderImgIcon = (src: string) => {
|
||||
return () =>
|
||||
h('img', {
|
||||
src,
|
||||
style: 'display:block; width: 26px; height: 26px; vertical-align: middle;'
|
||||
style: 'display:block; width: 26px; height: 26px; vertical-align: middle;',
|
||||
class: 'dark:invert'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,147 +1,220 @@
|
||||
<template>
|
||||
<div class="flex flex-1 flex-col">
|
||||
<img src="@/assets/mobile/chat-home/background.webp" class="w-100% fixed z-0 top-0" alt="hula" />
|
||||
<AutoFixHeightPage :show-footer="false" class="z-1">
|
||||
<template #header>
|
||||
<HeaderBar
|
||||
:isOfficial="false"
|
||||
:hidden-right="true"
|
||||
:enable-default-background="false"
|
||||
:enable-shadow="false"
|
||||
:room-name="t('mobile_edit_profile.title')" />
|
||||
</template>
|
||||
|
||||
<template #container>
|
||||
<div class="flex flex-col gap-1 overflow-auto h-full">
|
||||
<div class="flex flex-col p-[0px_20px_20px_20px] gap-15px">
|
||||
<!-- 头像 -->
|
||||
<div class="flex justify-center">
|
||||
<div class="rounded-full relative bg-white w-86px h-86px overflow-hidden" @click="openAvatarCropper">
|
||||
<n-avatar
|
||||
class="absolute"
|
||||
:size="86"
|
||||
:src="AvatarUtils.getAvatarUrl(localUserInfo.avatar!)"
|
||||
fallback-src="/logo.png"
|
||||
round />
|
||||
<div
|
||||
class="absolute h-50% w-full bottom-0 bg-[rgb(50,50,50)] bg-clip-padding backdrop-filter backdrop-blur-sm bg-opacity-15 backdrop-saturate-100 backdrop-contrast-100"></div>
|
||||
<div class="absolute bottom-25% text-center w-full text-12px text-white">
|
||||
{{ t('mobile_edit_profile.change_avatar') }}
|
||||
</div>
|
||||
<AutoFixHeightPage :show-footer="false" class="z-1">
|
||||
<template #header>
|
||||
<HeaderBar
|
||||
:isOfficial="false"
|
||||
:hidden-right="true"
|
||||
:enable-default-background="false"
|
||||
:enable-shadow="false"
|
||||
:room-name="t('mobile_edit_profile.title')" />
|
||||
</template>
|
||||
<template #container>
|
||||
<div class="flex flex-col overflow-auto h-full relative">
|
||||
<img
|
||||
src="@/assets/mobile/chat-home/background.webp"
|
||||
class="absolute fixed top-0 left-0 w-full h-full z-0 dark:opacity-20" />
|
||||
<div class="p-20px">
|
||||
<!-- 头像 -->
|
||||
<div class="flex justify-center mb-50px">
|
||||
<div class="rounded-full relative bg-white w-86px h-86px overflow-hidden" @click="openAvatarCropper">
|
||||
<n-avatar
|
||||
class="absolute"
|
||||
:size="86"
|
||||
:src="AvatarUtils.getAvatarUrl(localUserInfo.avatar!)"
|
||||
fallback-src="/logo.png"
|
||||
round />
|
||||
<div
|
||||
class="absolute h-50% w-full bottom-0 bg-[rgb(50,50,50)] bg-clip-padding backdrop-filter backdrop-blur-sm bg-opacity-15 backdrop-saturate-100 backdrop-contrast-100"></div>
|
||||
<div class="absolute bottom-25% text-center w-full text-12px text-white">
|
||||
{{ t('mobile_edit_profile.change_avatar') }}
|
||||
</div>
|
||||
<input
|
||||
ref="fileInput"
|
||||
type="file"
|
||||
accept="image/jpeg,image/png,image/webp"
|
||||
class="hidden"
|
||||
@change="handleFileChange" />
|
||||
<AvatarCropper
|
||||
ref="cropperRef"
|
||||
v-model:show="showCropper"
|
||||
:image-url="localImageUrl"
|
||||
@crop="handleCrop" />
|
||||
</div>
|
||||
<!-- 个人信息 -->
|
||||
<van-form @submit="saveEditInfo">
|
||||
<van-cell-group class="shadow" inset>
|
||||
<!-- 昵称 -->
|
||||
<van-field
|
||||
:disabled="true"
|
||||
v-model="localUserInfo.name"
|
||||
name="昵称"
|
||||
:label="t('mobile_edit_profile.nickname')"
|
||||
<input
|
||||
ref="fileInput"
|
||||
type="file"
|
||||
accept="image/jpeg,image/png,image/webp"
|
||||
class="hidden"
|
||||
@change="handleFileChange" />
|
||||
<AvatarCropper ref="cropperRef" v-model:show="showCropper" :image-url="localImageUrl" @crop="handleCrop" />
|
||||
</div>
|
||||
<!-- 个人信息 -->
|
||||
<n-card class="p-0! rounded-16px">
|
||||
<n-form @submit="saveEditInfo" label-placement="left" label-align="left" :label-width="80">
|
||||
<n-form-item :label="t('mobile_edit_profile.nickname')">
|
||||
<n-input
|
||||
readonly
|
||||
v-model:value="localUserInfo.name"
|
||||
:placeholder="t('mobile_edit_profile.placeholder.nickname')"
|
||||
:rules="[{ required: true, message: t('mobile_edit_profile.placeholder.nickname') }]" />
|
||||
class="bg-transparent!" />
|
||||
</n-form-item>
|
||||
|
||||
<!-- 性别 -->
|
||||
<van-field
|
||||
v-model="genderText"
|
||||
is-link
|
||||
<n-divider class="my-3! p-0!" />
|
||||
|
||||
<n-form-item
|
||||
:label="t('mobile_edit_profile.gender')"
|
||||
:placeholder="t('mobile_edit_profile.placeholder.gender')">
|
||||
<n-input
|
||||
@click="pickerState.gender = true"
|
||||
v-model:value="genderText"
|
||||
readonly
|
||||
name="picker"
|
||||
:label="t('mobile_edit_profile.gender')"
|
||||
:placeholder="t('mobile_edit_profile.placeholder.gender')"
|
||||
@click="pickerState.gender = true" />
|
||||
class="bg-transparent!" />
|
||||
</n-form-item>
|
||||
|
||||
<van-popup v-model:show="pickerState.gender" position="bottom">
|
||||
<van-picker
|
||||
:columns="pickerColumn.gender"
|
||||
@confirm="pickerConfirm.gender"
|
||||
@cancel="pickerState.gender = false" />
|
||||
</van-popup>
|
||||
<n-divider class="my-3! p-0!" />
|
||||
|
||||
<!-- 生日 -->
|
||||
<van-field
|
||||
v-model="birthday"
|
||||
:name="t('mobile_edit_profile.brithday')"
|
||||
:label="t('mobile_edit_profile.brithday')"
|
||||
:placeholder="t('mobile_edit_profile.placeholder.brithday')"
|
||||
is-link
|
||||
<n-form-item
|
||||
:label="t('mobile_edit_profile.brithday')"
|
||||
:placeholder="t('mobile_edit_profile.placeholder.brithday')">
|
||||
<n-input
|
||||
@click="toEditBirthday"
|
||||
v-model:value="birthday"
|
||||
readonly
|
||||
@click="toEditBirthday" />
|
||||
|
||||
<!-- 地区 -->
|
||||
<van-field
|
||||
v-model="region"
|
||||
is-link
|
||||
readonly
|
||||
name="area"
|
||||
:label="t('mobile_edit_profile.brithday')"
|
||||
:placeholder="t('mobile_edit_profile.placeholder.brithday')"
|
||||
@click="pickerState.region = true" />
|
||||
<van-popup v-model:show="pickerState.region" position="bottom">
|
||||
<van-area
|
||||
:area-list="areaList"
|
||||
@confirm="pickerConfirm.region"
|
||||
@cancel="pickerState.region = false" />
|
||||
</van-popup>
|
||||
class="bg-transparent!" />
|
||||
</n-form-item>
|
||||
|
||||
<!-- 手机号 -->
|
||||
<van-field
|
||||
:disabled="true"
|
||||
v-model="localUserInfo.phone"
|
||||
type="tel"
|
||||
name="手机号"
|
||||
:label="t('mobile_edit_profile.phone')"
|
||||
<n-divider class="my-3! p-0!" />
|
||||
|
||||
<n-form-item label="地区" :placeholder="t('mobile_edit_profile.placeholder.brithday')">
|
||||
<n-input
|
||||
@click="pickerState.region = true"
|
||||
v-model:value="region"
|
||||
readonly
|
||||
:placeholder="t('mobile_edit_profile.placeholder.brithday')"
|
||||
class="bg-transparent!" />
|
||||
</n-form-item>
|
||||
|
||||
<n-divider class="my-3! p-0!" />
|
||||
|
||||
<n-form-item
|
||||
disabled
|
||||
:label="t('mobile_edit_profile.phone')"
|
||||
:placeholder="t('mobile_edit_profile.placeholder.phone')">
|
||||
<n-input
|
||||
disabled
|
||||
readonly
|
||||
v-model:value="localUserInfo.phone"
|
||||
:placeholder="t('mobile_edit_profile.placeholder.phone')"
|
||||
:rules="[{ required: false, message: '请填写手机号' }]" />
|
||||
class="bg-transparent!" />
|
||||
</n-form-item>
|
||||
|
||||
<!-- 简介 -->
|
||||
<van-field
|
||||
v-model="localUserInfo.resume"
|
||||
name="简介"
|
||||
:label="t('mobile_edit_profile.bio')"
|
||||
:placeholder="t('mobile_edit_profile.placeholder.bio')"
|
||||
<n-divider class="my-3! p-0!" />
|
||||
|
||||
<n-form-item
|
||||
disabled
|
||||
:label="t('mobile_edit_profile.bio')"
|
||||
:placeholder="t('mobile_edit_profile.placeholder.bio')">
|
||||
<n-input
|
||||
type="textarea"
|
||||
rows="3"
|
||||
autosize
|
||||
@click="toEditBio" />
|
||||
</van-cell-group>
|
||||
v-model:value="localUserInfo.resume"
|
||||
:placeholder="t('mobile_edit_profile.placeholder.bio')"
|
||||
class="bg-transparent!"
|
||||
@click="toEditBio"
|
||||
readonly />
|
||||
</n-form-item>
|
||||
<!-- <van-cell-group class="shadow" inset> -->
|
||||
<!-- 昵称 -->
|
||||
<!-- <van-field
|
||||
:disabled="true"
|
||||
v-model="localUserInfo.name"
|
||||
name="昵称"
|
||||
:label="t('mobile_edit_profile.nickname')"
|
||||
:placeholder="t('mobile_edit_profile.placeholder.nickname')"
|
||||
:rules="[{ required: true, message: t('mobile_edit_profile.placeholder.nickname') }]" /> -->
|
||||
|
||||
<!-- 性别 -->
|
||||
<!-- <van-field
|
||||
v-model="genderText"
|
||||
is-link
|
||||
readonly
|
||||
name="picker"
|
||||
:label="t('mobile_edit_profile.gender')"
|
||||
:placeholder="t('mobile_edit_profile.placeholder.gender')"
|
||||
@click="pickerState.gender = true" /> -->
|
||||
|
||||
<n-drawer
|
||||
v-model:show="pickerState.gender"
|
||||
class="rounded-t-20px! overflow-hidden"
|
||||
position="bottom"
|
||||
round
|
||||
placement="bottom"
|
||||
default-height="300px">
|
||||
<van-picker
|
||||
:columns="pickerColumn.gender"
|
||||
@confirm="pickerConfirm.gender"
|
||||
@cancel="pickerState.gender = false" />
|
||||
</n-drawer>
|
||||
<!-- <van-popup v-model:show="pickerState.gender" position="bottom">
|
||||
<van-picker
|
||||
:columns="pickerColumn.gender"
|
||||
@confirm="pickerConfirm.gender"
|
||||
@cancel="pickerState.gender = false" />
|
||||
</van-popup> -->
|
||||
|
||||
<!-- 生日 -->
|
||||
<!-- <van-field
|
||||
v-model="birthday"
|
||||
:name="t('mobile_edit_profile.brithday')"
|
||||
:label="t('mobile_edit_profile.brithday')"
|
||||
:placeholder="t('mobile_edit_profile.placeholder.brithday')"
|
||||
is-link
|
||||
readonly
|
||||
@click="toEditBirthday" /> -->
|
||||
|
||||
<!-- 地区 -->
|
||||
<!-- <van-field
|
||||
v-model="region"
|
||||
is-link
|
||||
readonly
|
||||
name="area"
|
||||
:label="t('mobile_edit_profile.brithday')"
|
||||
:placeholder="t('mobile_edit_profile.placeholder.brithday')"
|
||||
@click="pickerState.region = true" />
|
||||
-->
|
||||
<n-drawer
|
||||
v-model:show="pickerState.region"
|
||||
class="rounded-t-20px! overflow-hidden"
|
||||
position="bottom"
|
||||
round
|
||||
placement="bottom"
|
||||
default-height="300px">
|
||||
<van-area :area-list="areaList" @confirm="pickerConfirm.region" @cancel="pickerState.region = false" />
|
||||
</n-drawer>
|
||||
|
||||
<!-- 手机号 -->
|
||||
<!-- <van-field
|
||||
:disabled="true"
|
||||
v-model="localUserInfo.phone"
|
||||
type="tel"
|
||||
name="手机号"
|
||||
:label="t('mobile_edit_profile.phone')"
|
||||
:placeholder="t('mobile_edit_profile.placeholder.phone')"
|
||||
:rules="[{ required: false, message: '请填写手机号' }]" /> -->
|
||||
|
||||
<!-- 简介 -->
|
||||
<!-- <van-field
|
||||
v-model="localUserInfo.resume"
|
||||
name="简介"
|
||||
:label="t('mobile_edit_profile.bio')"
|
||||
:placeholder="t('mobile_edit_profile.placeholder.bio')"
|
||||
type="textarea"
|
||||
rows="3"
|
||||
autosize
|
||||
@click="toEditBio" /> -->
|
||||
<!-- </van-cell-group> -->
|
||||
|
||||
<div class="flex justify-center mt-20px">
|
||||
<button
|
||||
class=""
|
||||
style="
|
||||
background: linear-gradient(145deg, #7eb7ac, #6fb0a4, #5fa89c);
|
||||
border-radius: 30px;
|
||||
padding: 10px 30px;
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
"
|
||||
type="submit">
|
||||
<n-button block attr-type="submit" type="primary" strong secondary round>
|
||||
{{ t('mobile_edit_profile.save_btn') }}
|
||||
</button>
|
||||
</n-button>
|
||||
</div>
|
||||
</van-form>
|
||||
</div>
|
||||
</n-form>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
</AutoFixHeightPage>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</AutoFixHeightPage>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
<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="t('mobile_setting.title')" />
|
||||
<HeaderBar border :isOfficial="false" :hidden-right="true" :room-name="t('mobile_setting.title')" />
|
||||
</template>
|
||||
|
||||
<template #container>
|
||||
@@ -17,7 +12,7 @@
|
||||
<div
|
||||
v-for="item in settings"
|
||||
:key="item.key"
|
||||
class="flex justify-between items-center bg-white p-12px rounded-lg shadow-sm">
|
||||
class="flex justify-between items-center bg-card text-card-foreground ring-1 p-12px rounded-lg shadow-sm">
|
||||
<div class="text-base">{{ item.label }}</div>
|
||||
<div>
|
||||
<!-- 根据 type 渲染对应组件 -->
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
<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="t('mobile_mymessage.title')" />
|
||||
<HeaderBar :isOfficial="false" border :hidden-right="true" :room-name="t('mobile_mymessage.title')" />
|
||||
</template>
|
||||
|
||||
<template #container>
|
||||
<div class="flex flex-col bg-#fefefe gap-1 overflow-auto h-full">
|
||||
<div class="flex flex-col overflow-auto h-full relative">
|
||||
<div class="flex flex-col p-[10px_20px_0px_20px] gap-20px">
|
||||
<CommunityTab @update="onUpdate" :options="tabOptions" active-tab-name="friend-message">
|
||||
<template #friend-message>
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
<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="t('mobile_post.title')" />
|
||||
<HeaderBar :isOfficial="false" border :hidden-right="true" :room-name="t('mobile_post.title')" />
|
||||
</template>
|
||||
|
||||
<template #container>
|
||||
<div class="flex flex-col gap-1 overflow-auto h-full bg-#f5f5f5">
|
||||
<div class="flex flex-col gap-1 overflow-auto h-full">
|
||||
<div class="flex flex-col p-16px gap-12px">
|
||||
<!-- 动态内容输入 -->
|
||||
<div class="bg-white rounded-12px p-16px">
|
||||
<div class="text-14px text-#333 mb-8px font-500">{{ t('mobile_post.content.label') }}</div>
|
||||
<n-card
|
||||
class="rounded-12px p-0!"
|
||||
content-class="p-16px!"
|
||||
header-class="text-14px! pb-0!"
|
||||
:title="t('mobile_post.content.label')">
|
||||
<!-- 动态内容输入 -->
|
||||
<van-field
|
||||
v-model="feedContent"
|
||||
type="textarea"
|
||||
@@ -22,177 +20,171 @@
|
||||
:maxlength="500"
|
||||
show-word-limit
|
||||
:rows="8"
|
||||
class="bg-transparent!"
|
||||
:autosize="feedAutosize" />
|
||||
</div>
|
||||
</n-card>
|
||||
|
||||
<!-- 媒体类型提示(暂时禁用) -->
|
||||
<div class="bg-white rounded-12px p-16px">
|
||||
<div class="text-14px text-#333 mb-8px font-500">{{ t('mobile_post.media_type.label') }}</div>
|
||||
<div class="text-13px text-#999">
|
||||
<div class="flex items-center gap-8px mb-6px">
|
||||
<span class="text-#c8c9cc">📷</span>
|
||||
<span class="text-#c8c9cc">{{ t('mobile_post.media_type.option_image') }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-8px">
|
||||
<span class="text-#c8c9cc">🎬</span>
|
||||
<span class="text-#c8c9cc">{{ t('mobile_post.media_type.option_video') }}</span>
|
||||
</div>
|
||||
<!-- <div class="bg-white rounded-12px p-16px"> -->
|
||||
<n-card
|
||||
class="p-0! rounded-12px"
|
||||
content-class="p-16px!"
|
||||
header-class="text-14px! pb-0!"
|
||||
:title="t('mobile_post.media_type.label')">
|
||||
<div class="flex items-center gap-8px mb-6px">
|
||||
<n-text depth="3" s>📷</n-text>
|
||||
<n-text depth="3">{{ t('mobile_post.media_type.option_image') }}</n-text>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-8px">
|
||||
<n-text depth="3">🎬</n-text>
|
||||
<n-text depth="3">{{ t('mobile_post.media_type.option_video') }}</n-text>
|
||||
</div>
|
||||
</n-card>
|
||||
<!-- </div> -->
|
||||
|
||||
<!-- 权限选择 -->
|
||||
<div class="bg-white rounded-12px p-16px">
|
||||
<div class="text-14px text-#333 mb-12px font-500">{{ t('mobile_post.visibility.label') }}</div>
|
||||
<van-radio-group v-model="permission" direction="vertical" @change="handlePermissionChange">
|
||||
<van-radio name="open" icon-size="18px" class="mb-12px">
|
||||
<template #icon="props">
|
||||
<div
|
||||
:class="[
|
||||
'w-20px h-20px rounded-full border-2 flex items-center justify-center transition-all',
|
||||
props.checked ? 'border-#13987f bg-#13987f' : 'border-#c8c9cc'
|
||||
]">
|
||||
<div v-if="props.checked" class="w-8px h-8px rounded-full bg-white"></div>
|
||||
</div>
|
||||
</template>
|
||||
<span class="ml-8px text-14px">{{ t('mobile_post.visibility.public') }}</span>
|
||||
</van-radio>
|
||||
<van-radio name="partVisible" icon-size="18px" class="mb-12px">
|
||||
<template #icon="props">
|
||||
<div
|
||||
:class="[
|
||||
'w-20px h-20px rounded-full border-2 flex items-center justify-center transition-all',
|
||||
props.checked ? 'border-#13987f bg-#13987f' : 'border-#c8c9cc'
|
||||
]">
|
||||
<div v-if="props.checked" class="w-8px h-8px rounded-full bg-white"></div>
|
||||
</div>
|
||||
</template>
|
||||
<span class="ml-8px text-14px">{{ t('mobile_post.visibility.selected') }}</span>
|
||||
</van-radio>
|
||||
<van-radio name="notAnyone" icon-size="18px">
|
||||
<template #icon="props">
|
||||
<div
|
||||
:class="[
|
||||
'w-20px h-20px rounded-full border-2 flex items-center justify-center transition-all',
|
||||
props.checked ? 'border-#13987f bg-#13987f' : 'border-#c8c9cc'
|
||||
]">
|
||||
<div v-if="props.checked" class="w-8px h-8px rounded-full bg-white"></div>
|
||||
</div>
|
||||
</template>
|
||||
<span class="ml-8px text-14px">{{ t('mobile_post.visibility.exclude') }}</span>
|
||||
</van-radio>
|
||||
</van-radio-group>
|
||||
</div>
|
||||
<n-card
|
||||
class="p-0! rounded-12px"
|
||||
content-class="p-16px"
|
||||
header-class="text-14px! pb-0!"
|
||||
:title="t('mobile_post.visibility.label')">
|
||||
<n-radio-group
|
||||
:value="permission"
|
||||
@update:value="
|
||||
(e) => {
|
||||
permission = e
|
||||
handlePermissionChange(e)
|
||||
}
|
||||
">
|
||||
<n-radio class="w-full" value="open">{{ t('mobile_post.visibility.public') }}</n-radio>
|
||||
<n-radio class="w-full" value="partVisible">{{ t('mobile_post.visibility.selected') }}</n-radio>
|
||||
<n-radio class="w-full" value="notAnyone">{{ t('mobile_post.visibility.exclude') }}</n-radio>
|
||||
</n-radio-group>
|
||||
</n-card>
|
||||
|
||||
<!-- 选择用户 -->
|
||||
<div v-if="permission === 'partVisible' || permission === 'notAnyone'" class="bg-white rounded-12px p-16px">
|
||||
<div class="text-14px text-#333 mb-12px font-500">
|
||||
{{
|
||||
permission === 'partVisible'
|
||||
? t('mobile_post.visibility_selected_btn_label')
|
||||
: t('mobile_post.visibility_exclude_btn_label')
|
||||
}}
|
||||
</div>
|
||||
<van-button
|
||||
type="primary"
|
||||
<n-card
|
||||
v-if="permission === 'partVisible' || permission === 'notAnyone'"
|
||||
class="rounded-12px p-0!"
|
||||
content-class="p-16px!"
|
||||
header-class="text-14px! pb-0!"
|
||||
:title="
|
||||
permission === 'partVisible'
|
||||
? t('mobile_post.visibility_selected_btn_label')
|
||||
: t('mobile_post.visibility_exclude_btn_label')
|
||||
">
|
||||
<n-button
|
||||
size="small"
|
||||
strong
|
||||
secondary
|
||||
type="primary"
|
||||
plain
|
||||
@click="showUserSelectPopup = true"
|
||||
class="w-full"
|
||||
:style="{ borderColor: '#13987f', color: '#13987f' }">
|
||||
{{ t('mobile_post.visibility_select_btn', { count: selectedUsers.length }) }}
|
||||
</van-button>
|
||||
</n-button>
|
||||
<div v-if="selectedUsers.length > 0" class="mt-12px flex flex-wrap gap-8px">
|
||||
<van-tag
|
||||
<n-tag
|
||||
v-for="user in selectedUsers"
|
||||
:key="user.uid"
|
||||
closeable
|
||||
size="medium"
|
||||
color="#e8f5f4"
|
||||
type="success"
|
||||
size="small"
|
||||
round
|
||||
closable
|
||||
text-color="#13987f"
|
||||
@close="removeSelectedUser(user.uid)">
|
||||
{{ getUserName(user) }}
|
||||
</van-tag>
|
||||
</n-tag>
|
||||
</div>
|
||||
</div>
|
||||
</n-card>
|
||||
|
||||
<!-- strong secondary round -->
|
||||
<!-- 发布按钮 -->
|
||||
<div class="flex gap-12px mt-8px pb-20px">
|
||||
<van-button block plain @click="goBack" :style="{ borderColor: '#c8c9cc', color: '#666' }">
|
||||
{{ t('mobile_post.btn.cancel') }}
|
||||
</van-button>
|
||||
<van-button
|
||||
block
|
||||
type="primary"
|
||||
:loading="isPublishing"
|
||||
:disabled="!isPublishValid"
|
||||
@click="handlePublish"
|
||||
:style="{ background: '#13987f', borderColor: '#13987f' }">
|
||||
{{ t('mobile_post.btn.publish') }}
|
||||
</van-button>
|
||||
</div>
|
||||
<n-grid :cols="2" x-gap="12">
|
||||
<n-gi>
|
||||
<n-button
|
||||
block
|
||||
secondary
|
||||
round
|
||||
size="large"
|
||||
@click="goBack"
|
||||
:style="{ borderColor: '#c8c9cc', color: '#666' }">
|
||||
{{ t('mobile_post.btn.cancel') }}
|
||||
</n-button>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-button
|
||||
strong
|
||||
secondary
|
||||
round
|
||||
block
|
||||
size="large"
|
||||
:loading="isPublishing"
|
||||
:disabled="!isPublishValid"
|
||||
@click="handlePublish"
|
||||
:style="{ background: '#13987f', borderColor: '#13987f' }">
|
||||
{{ t('mobile_post.btn.publish') }}
|
||||
</n-button>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</AutoFixHeightPage>
|
||||
|
||||
<!-- 用户选择弹窗 -->
|
||||
<van-popup v-model:show="showUserSelectPopup" position="bottom" :style="{ height: '70%' }" round>
|
||||
<div class="flex flex-col h-full">
|
||||
<!-- 弹窗标题 -->
|
||||
<div class="flex items-center justify-between p-16px border-b border-#eee">
|
||||
<span class="text-16px font-500 text-#333">{{ t('mobile_post.select_users.title') }}</span>
|
||||
<van-button type="primary" size="small" @click="confirmUserSelection" :style="{ background: '#13987f' }">
|
||||
{{ t('mobile_post.select_users.btn.done') }}
|
||||
</van-button>
|
||||
</div>
|
||||
<n-drawer
|
||||
v-model:show="showUserSelectPopup"
|
||||
class="rounded-t-20px!"
|
||||
:style="{ background: themeVars.cardColor }"
|
||||
position="bottom"
|
||||
round
|
||||
placement="bottom"
|
||||
default-height="80%">
|
||||
<n-drawer-content>
|
||||
<template #header>
|
||||
<n-flex justify="space-between">
|
||||
<n-h3>{{ t('mobile_post.select_users.title') }}</n-h3>
|
||||
<n-button strong secondary type="primary" size="small" @click="confirmUserSelection">
|
||||
{{ t('mobile_post.select_users.btn.done') }}
|
||||
</n-button>
|
||||
</n-flex>
|
||||
</template>
|
||||
<n-space vertical :size="16">
|
||||
<!-- 搜索框 -->
|
||||
<n-input
|
||||
size="large"
|
||||
v-model:value="userSearchKeyword"
|
||||
:placeholder="t('mobile_post.select_users.search_placeholder')" />
|
||||
|
||||
<!-- 搜索框 -->
|
||||
<div class="p-12px border-b border-#f5f5f5">
|
||||
<van-search
|
||||
v-model="userSearchKeyword"
|
||||
:placeholder="t('mobile_post.select_users.search_placeholder')"
|
||||
shape="round" />
|
||||
</div>
|
||||
<!-- 用户列表 -->
|
||||
<div class="flex-1 overflow-y-auto">
|
||||
<n-checkbox-group v-model:value="selectedUserIds">
|
||||
<n-list hoverable clickable class="bg-transparent" :bordered="false">
|
||||
<n-list-item v-for="user in filteredContactsList" :key="user.uid" @click="toggleUser(user.uid)">
|
||||
<n-flex class="flex items-center justify-between w-full" :gap="10">
|
||||
<n-avatar size="medium" class="shrink-0" :src="getUserAvatar(user)" round />
|
||||
|
||||
<!-- 用户列表 -->
|
||||
<div class="flex-1 overflow-y-auto">
|
||||
<van-checkbox-group v-model="selectedUserIds">
|
||||
<van-cell-group>
|
||||
<van-cell
|
||||
v-for="user in filteredContactsList"
|
||||
:key="user.uid"
|
||||
clickable
|
||||
@click="toggleUser(user.uid)"
|
||||
class="user-item">
|
||||
<template #title>
|
||||
<div class="flex items-center gap-12px">
|
||||
<van-image
|
||||
:src="getUserAvatar(user)"
|
||||
round
|
||||
width="40"
|
||||
height="40"
|
||||
fit="cover"
|
||||
:style="{ flexShrink: 0 }" />
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="text-14px text-#333 font-500 truncate">
|
||||
<div class="grow overflow-hidden">
|
||||
<n-text class="truncate block">
|
||||
{{ getUserName(user) }}
|
||||
</div>
|
||||
<div v-if="user.remark" class="text-12px text-#999 truncate mt-2px">{{ user.remark }}</div>
|
||||
</n-text>
|
||||
<n-text depth="3" v-if="user.remark" class="text-12px">{{ user.remark }}</n-text>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #right-icon>
|
||||
<van-checkbox :name="user.uid" @click.stop ref="checkboxes" />
|
||||
</template>
|
||||
</van-cell>
|
||||
</van-cell-group>
|
||||
</van-checkbox-group>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<van-empty v-if="filteredContactsList.length === 0" :description="t('mobile_post.empty')" />
|
||||
</div>
|
||||
</div>
|
||||
</van-popup>
|
||||
<n-checkbox :value="user.uid" class="shrink-0" @click.stop />
|
||||
</n-flex>
|
||||
</n-list-item>
|
||||
</n-list>
|
||||
</n-checkbox-group>
|
||||
<!-- 空状态 -->
|
||||
<van-empty v-if="filteredContactsList.length === 0" :description="t('mobile_post.empty')" />
|
||||
</div>
|
||||
</n-space>
|
||||
</n-drawer-content>
|
||||
</n-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -205,6 +197,9 @@ import { AvatarUtils } from '@/utils/AvatarUtils'
|
||||
import type { FriendItem } from '@/services/types'
|
||||
import 'vant/lib/index.css' // Vant UI 样式
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useThemeVars } from 'naive-ui'
|
||||
|
||||
const themeVars = useThemeVars()
|
||||
|
||||
const { t, getLocaleMessage } = useI18n()
|
||||
console.log(getLocaleMessage('en'))
|
||||
@@ -367,4 +362,10 @@ onMounted(async () => {
|
||||
.user-item:active {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.custom-rounded {
|
||||
border-top-left-radius: 20px !important; /* 左上角 */
|
||||
border-top-right-radius: 20px !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -18,27 +18,25 @@
|
||||
<svg @click="handleBack" class="w-32px h-32px iconpark-icon"><use href="#right"></use></svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex shadow bg-white w-full rounded-lg items-center">
|
||||
<div class="px-24px py-20px flex w-full flex-wrap gap-20px">
|
||||
<n-avatar
|
||||
:size="74"
|
||||
:src="AvatarUtils.getAvatarUrl(userStore.userInfo!.avatar!)"
|
||||
fallback-src="/logo.png"
|
||||
round />
|
||||
<n-card size="small" class="rounded-lg" content-class="flex gap-20px items-center">
|
||||
<n-avatar
|
||||
:size="74"
|
||||
:src="AvatarUtils.getAvatarUrl(userStore.userInfo!.avatar!)"
|
||||
fallback-src="/logo.png"
|
||||
round />
|
||||
|
||||
<div @click="toMyInfo" class="flex flex-col flex-1 py-10px">
|
||||
<div class="font-bold text-18px text-#373838">{{ userStore.userInfo!.name }}</div>
|
||||
<div class="mt-2 text-bold-style line-height-22px line-clamp-2">
|
||||
{{ userStore.userInfo!.resume || t('mobile_my.default_bio') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div @click="toMyInfo" class="flex items-center justify-end">
|
||||
<svg @click="handleBack" class="w-24px text-gray h-24px iconpark-icon"><use href="#right"></use></svg>
|
||||
<div @click="toMyInfo" class="flex flex-col flex-1 py-10px">
|
||||
<div class="font-bold text-18px">{{ userStore.userInfo!.name }}</div>
|
||||
<div class="mt-2 text-bold-style line-height-22px line-clamp-2">
|
||||
{{ userStore.userInfo!.resume || t('mobile_my.default_bio') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col w-full bg-white rounded-lg flex-1">
|
||||
|
||||
<div @click="toMyInfo" class="flex items-center justify-end">
|
||||
<svg @click="handleBack" class="w-24px text-gray h-24px iconpark-icon"><use href="#right"></use></svg>
|
||||
</div>
|
||||
</n-card>
|
||||
<n-card size="small" content-class="flex flex-col w-full flex-1" class="rounded-lg">
|
||||
<div
|
||||
v-for="item in options"
|
||||
:key="item.label"
|
||||
@@ -52,7 +50,7 @@
|
||||
<svg class="w-20px text-gray h-20px iconpark-icon"><use href="#right"></use></svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<template>
|
||||
<div class="flex flex-col h-full">
|
||||
<img src="@/assets/mobile/chat-home/background.webp" class="w-100% fixed z-0 top-0" alt="hula" />
|
||||
|
||||
<div class="flex flex-col overflow-auto h-full relative">
|
||||
<img
|
||||
src="@/assets/mobile/chat-home/background.webp"
|
||||
class="absolute fixed top-0 left-0 w-full h-full z-0 dark:opacity-20" />
|
||||
<!-- 设置区 -->
|
||||
<Settings />
|
||||
|
||||
@@ -14,7 +15,7 @@
|
||||
ref="scrollContainer"
|
||||
:style="{ height: tabHeight + 'px' }"
|
||||
class="z-1 overflow-hidden mt-2 absolute z-3 w-full">
|
||||
<div class="custom-rounded bg-white flex px-24px flex-col gap-4 z-1 p-10px mt-4">
|
||||
<n-card class="custom-rounded" content-class="flex flex-col gap-4 z-1 p-10px mt-4 p-15px!">
|
||||
<n-scrollbar
|
||||
ref="scrollbarRef"
|
||||
:style="{ height: tabHeight + 'px' }"
|
||||
@@ -31,7 +32,7 @@
|
||||
@item-click="handleItemClick" />
|
||||
</div>
|
||||
</n-scrollbar>
|
||||
</div>
|
||||
</n-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
15
src/styles/scss/theme/simple.scss
vendored
15
src/styles/scss/theme/simple.scss
vendored
@@ -11,6 +11,14 @@
|
||||
--left-active-icon-color: #13987f;
|
||||
--left-win-icon-color: rgba(19, 152, 127, 0.6);
|
||||
|
||||
// 通用背景色
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.141 0.005 285.823);
|
||||
--border: oklch(0.92 0.004 286.32);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.141 0.005 285.823);
|
||||
--input: oklch(0.92 0.004 286.32);
|
||||
|
||||
// 包含边框的选中样式
|
||||
--border-active-color: #13987f;
|
||||
|
||||
@@ -50,6 +58,13 @@ html[data-theme='dark'] {
|
||||
--left-active-icon-color: #13987f;
|
||||
--left-win-icon-color: rgba(19, 152, 127, 0.6);
|
||||
|
||||
--background: oklch(27, 27, 27);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--card: oklch(0.21 0.006 285.885);
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
|
||||
// 包含边框的选中样式
|
||||
--border-active-color: #13987f;
|
||||
|
||||
|
||||
@@ -44,40 +44,38 @@
|
||||
<p class="text-(24px [--chat-text-color]) font-500">{{ userStore.userInfo!.name }}</p>
|
||||
|
||||
<!-- 密码输入框 -->
|
||||
<n-config-provider :theme="lightTheme">
|
||||
<n-input
|
||||
v-if="!isLogining && !isWrongPassword"
|
||||
ref="inputInstRef"
|
||||
style="
|
||||
width: 320px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.1);
|
||||
border-bottom-color: rgba(19, 152, 127, 1);
|
||||
background-color: #404040;
|
||||
color: #fff;
|
||||
"
|
||||
spellCheck="false"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
:placeholder="t('message.lock_screen.password_placeholder')"
|
||||
show-password-on="click"
|
||||
type="password"
|
||||
@keyup.enter.prevent="unlock"
|
||||
v-model:value="password">
|
||||
<template #suffix>
|
||||
<n-popover trigger="hover">
|
||||
<template #trigger>
|
||||
<svg
|
||||
@click.stop="unlock"
|
||||
class="size-16px color-#e3e3e3 mr-6px p-[4px_6px] rounded-8px cursor-pointer transition-all duration-300 ease-in-out hover:bg-#13987fe6">
|
||||
<use href="#arrow-right"></use>
|
||||
</svg>
|
||||
</template>
|
||||
<p>{{ t('message.lock_screen.enter_system_tooltip') }}</p>
|
||||
</n-popover>
|
||||
</template>
|
||||
</n-input>
|
||||
</n-config-provider>
|
||||
<n-input
|
||||
v-if="!isLogining && !isWrongPassword"
|
||||
ref="inputInstRef"
|
||||
style="
|
||||
width: 320px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.1);
|
||||
border-bottom-color: rgba(19, 152, 127, 1);
|
||||
background-color: #404040;
|
||||
color: #fff;
|
||||
"
|
||||
spellCheck="false"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
:placeholder="t('message.lock_screen.password_placeholder')"
|
||||
show-password-on="click"
|
||||
type="password"
|
||||
@keyup.enter.prevent="unlock"
|
||||
v-model:value="password">
|
||||
<template #suffix>
|
||||
<n-popover trigger="hover">
|
||||
<template #trigger>
|
||||
<svg
|
||||
@click.stop="unlock"
|
||||
class="size-16px color-#e3e3e3 mr-6px p-[4px_6px] rounded-8px cursor-pointer transition-all duration-300 ease-in-out hover:bg-#13987fe6">
|
||||
<use href="#arrow-right"></use>
|
||||
</svg>
|
||||
</template>
|
||||
<p>{{ t('message.lock_screen.enter_system_tooltip') }}</p>
|
||||
</n-popover>
|
||||
</template>
|
||||
</n-input>
|
||||
|
||||
<!-- 登录时显示的文字 -->
|
||||
<n-flex vertical align="center" justify="center" :size="30" v-if="isLogining && !isWrongPassword">
|
||||
@@ -115,7 +113,7 @@
|
||||
import { WebviewWindow } from '@tauri-apps/api/webviewWindow'
|
||||
import { onKeyStroke } from '@vueuse/core'
|
||||
import dayjs from 'dayjs'
|
||||
import { type InputInst, lightTheme } from 'naive-ui'
|
||||
import { type InputInst } from 'naive-ui'
|
||||
import { useLogin } from '@/hooks/useLogin.ts'
|
||||
import { useSettingStore } from '@/stores/setting.ts'
|
||||
import { useUserStore } from '@/stores/user.ts'
|
||||
|
||||
@@ -56,5 +56,16 @@ export default defineConfig({
|
||||
'fixed-rt': 'fixed right-0 top-0',
|
||||
'fixed-rb': 'fixed right-0 bottom-0',
|
||||
'fixed-center': 'fixed-lt flex-center size-full'
|
||||
},
|
||||
|
||||
theme: {
|
||||
colors: {
|
||||
background: 'var(--background)',
|
||||
foreground: 'var(--foreground)',
|
||||
border: 'var(--border)',
|
||||
card: 'var(--card)',
|
||||
'card-foreground': 'var(--card-foreground)',
|
||||
input: 'var(--input)'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user