This commit is contained in:
2025-12-09 21:32:03 +08:00
parent 37de05de48
commit 98af3513b4
9 changed files with 1335 additions and 1834 deletions

14
components.d.ts vendored
View File

@@ -33,19 +33,5 @@ declare module 'vue' {
UserMomentsModal: typeof import('./src/components/moment/UserMomentsModal.vue')['default']
VideoPlayer: typeof import('./src/components/common/VideoPlayer.vue')['default']
VisibilitySelector: typeof import('./src/components/moment/VisibilitySelector.vue')['default']
WdActionSheet: typeof import('wot-design-uni/components/wd-action-sheet/wd-action-sheet.vue')['default']
WdBadge: typeof import('wot-design-uni/components/wd-badge/wd-badge.vue')['default']
WdButton: typeof import('wot-design-uni/components/wd-button/wd-button.vue')['default']
WdCell: typeof import('wot-design-uni/components/wd-cell/wd-cell.vue')['default']
WdEmpty: typeof import('wot-design-uni/components/wd-empty/wd-empty.vue')['default']
WdIcon: typeof import('wot-design-uni/components/wd-icon/wd-icon.vue')['default']
WdImg: typeof import('wot-design-uni/components/wd-img/wd-img.vue')['default']
WdLoading: typeof import('wot-design-uni/components/wd-loading/wd-loading.vue')['default']
WdMessageBox: typeof import('wot-design-uni/components/wd-message-box/wd-message-box.vue')['default']
WdPopup: typeof import('wot-design-uni/components/wd-popup/wd-popup.vue')['default']
WdSearch: typeof import('wot-design-uni/components/wd-search/wd-search.vue')['default']
WdSwipeAction: typeof import('wot-design-uni/components/wd-swipe-action/wd-swipe-action.vue')['default']
WdSwitch: typeof import('wot-design-uni/components/wd-switch/wd-switch.vue')['default']
WdToast: typeof import('wot-design-uni/components/wd-toast/wd-toast.vue')['default']
}
}

View File

@@ -1,76 +1,112 @@
<template>
<view :class="{ dark: isDark }">
<wd-navbar
:title="title"
:left-text="leftText"
:right-text="rightText"
:left-arrow="leftArrow"
:fixed="fixed"
:placeholder="placeholder"
:bordered="bordered"
:safe-area-inset-top="safeAreaInsetTop"
:z-index="zIndex"
custom-class="app-navbar"
@click-left="onClickLeft"
@click-right="onClickRight"
:bordered="bordered"
custom-class="app-nav-bar"
@click-left="handleClickLeft"
@click-right="handleClickRight"
>
<template v-if="$slots.left" #left>
<!-- 插槽透传 -->
<template #left v-if="$slots.left">
<slot name="left" />
</template>
<template v-if="$slots.title" #title>
<template #title v-if="$slots.title">
<slot name="title" />
</template>
<template v-if="$slots.right" #right>
<template #right v-if="$slots.right">
<slot name="right" />
</template>
</wd-navbar>
</view>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useTheme } from '@/composables/useTheme'
const { isDark } = useTheme()
interface Props {
title?: string
leftText?: string
rightText?: string
leftArrow?: boolean
fixed?: boolean
placeholder?: boolean
bordered?: boolean
safeAreaInsetTop?: boolean
zIndex?: number
bordered?: boolean
// 自定义点击左侧逻辑,如果不传则默认返回上一页
customBack?: boolean
}
const props = withDefaults(defineProps<Props>(), {
title: '',
leftText: '',
rightText: '',
leftArrow: true,
fixed: true,
placeholder: true,
bordered: true,
safeAreaInsetTop: true,
zIndex: 100
bordered: false,
customBack: false
})
const emit = defineEmits<{
(e: 'click-left'): void
(e: 'click-right'): void
}>()
const emit = defineEmits(['click-left', 'click-right'])
function onClickLeft() {
function handleClickLeft() {
if (props.customBack) {
emit('click-left')
// 默认返回上一页
if (props.leftArrow) {
uni.navigateBack({
fail: () => {
// 如果没有上一页,则跳转到首页
} else {
// 默认行为:返回上一页,如果无法返回则去首页
const pages = getCurrentPages()
if (pages.length > 1) {
uni.navigateBack()
} else {
uni.reLaunch({ url: '/pages/index/index' })
}
})
}
}
function onClickRight() {
function handleClickRight() {
emit('click-right')
}
</script>
<style lang="scss">
.app-navbar {
--wot-navbar-bg-color: var(--nav-bg, #ededed);
--wot-navbar-title-color: var(--text-primary, #333);
--wot-navbar-icon-color: var(--text-primary, #333);
<style lang="scss" scoped>
:deep(.app-nav-bar) {
// 适配暗黑模式的背景色和文字颜色
// 假设 src/uni.scss 或 variables.scss 中定义了这些 CSS 变量
background-color: var(--bg-content) !important;
.wd-navbar__title {
color: var(--text-primary) !important;
font-weight: 600;
}
.wd-navbar__text {
color: var(--color-primary) !important;
}
.wd-icon {
color: var(--text-primary) !important;
}
}
// 暗黑模式下的特殊覆盖(如果 CSS 变量不够用)
.dark {
:deep(.app-nav-bar) {
background-color: #1c1c1e !important; // 兜底暗色
.wd-navbar__title {
color: #ffffff !important;
}
}
}
</style>

View File

@@ -156,3 +156,107 @@ function switchTab(name: string) {
line-height: 1;
}
</style>
<!--/*-->
<!--<template>-->
<!-- <view :class="{ dark: isDark }">-->
<!-- <wd-tabbar-->
<!-- :modelValue="current"-->
<!-- fixed-->
<!-- placeholder-->
<!-- safe-area-inset-bottom-->
<!-- bordered-->
<!-- custom-class="app-tab-bar"-->
<!-- @change="handleChange"-->
<!-- >-->
<!-- <wd-tabbar-item-->
<!-- v-for="(item, index) in list"-->
<!-- :key="index"-->
<!-- :name="item.pagePath"-->
<!-- :title="item.text"-->
<!-- :value="item.badge > 0 ? item.badge : null"-->
<!-- :icon="item.icon"-->
<!-- >-->
<!-- </wd-tabbar-item>-->
<!-- </wd-tabbar>-->
<!-- </view>-->
<!--</template>-->
<!--<script setup lang="ts">-->
<!--import { computed } from 'vue'-->
<!--import { useTheme } from '@/composables/useTheme'-->
<!--// 保持原有的 store 引用-->
<!--import { useAppStore } from '@/stores'-->
<!--const props = defineProps<{-->
<!-- active?: string-->
<!--}>()-->
<!--const { isDark } = useTheme()-->
<!--const appStore = useAppStore()-->
<!--// 核心修改:移除 image 路径,改用 Wot-UI 内置 Icon name-->
<!--// 如果需要自定义 SVG可以使用 custom-icon 插槽-->
<!--const list = computed(() => [-->
<!-- {-->
<!-- pagePath: '/pages/index/index',-->
<!-- text: '消息',-->
<!-- icon: 'chat', // 对应 wd-icon name="chat"-->
<!-- badge: appStore.unreadCount || 0-->
<!-- },-->
<!-- {-->
<!-- pagePath: '/pages/contact/index',-->
<!-- text: '通讯录',-->
<!-- icon: 'user-circle', // 对应 wd-icon name="user-circle"-->
<!-- badge: appStore.contactUnread || 0-->
<!-- },-->
<!-- {-->
<!-- pagePath: '/pages/moment/index',-->
<!-- text: '发现',-->
<!-- icon: 'camera', // 对应 wd-icon name="camera"-->
<!-- badge: appStore.momentUnread ? 1 : 0-->
<!-- },-->
<!-- {-->
<!-- pagePath: '/pages/profile/index',-->
<!-- text: '我的',-->
<!-- icon: 'user', // 对应 wd-icon name="user"-->
<!-- badge: 0-->
<!-- }-->
<!--])-->
<!--const current = computed(() => {-->
<!-- if (props.active) return props.active-->
<!-- const pages = getCurrentPages()-->
<!-- const page = pages[pages.length - 1]-->
<!-- return '/' + page.route-->
<!--})-->
<!--function handleChange({ value }: { value: string }) {-->
<!-- uni.switchTab({ url: value })-->
<!--}-->
<!--</script>-->
<!--<style lang="scss" scoped>-->
<!--:deep(.app-tab-bar) {-->
<!-- background-color: var(&#45;&#45;bg-content) !important;-->
<!-- // 覆盖选中态颜色,保持与原有设计一致-->
<!-- .wd-tabbar-item.is-active {-->
<!-- color: var(&#45;&#45;color-primary) !important;-->
<!-- }-->
<!--}-->
<!--.dark {-->
<!-- :deep(.app-tab-bar) {-->
<!-- background-color: #1c1c1e !important;-->
<!-- border-top-color: #333;-->
<!-- .wd-tabbar-item {-->
<!-- color: var(&#45;&#45;text-secondary);-->
<!-- &.is-active {-->
<!-- color: var(&#45;&#45;color-primary);-->
<!-- }-->
<!-- }-->
<!-- }-->
<!--}-->
<!--</style>-->
<!--*/-->

File diff suppressed because it is too large Load Diff

View File

@@ -1,37 +1,43 @@
<template>
<!--
WotUI 最佳实践
使用 wd-config-provider 包裹页面以确保组件级暗黑模式生效
custom-style 用于注入我们定义的 CSS 变量
-->
<wd-config-provider :theme="isDark ? 'dark' : 'light'">
<view class="page-container" :class="{ dark: isDark }">
<!-- 自定义导航栏 -->
<!-- 沉浸式导航栏 -->
<view class="custom-navbar">
<view class="navbar-bg"></view> <!-- 独立背景层用于做高斯模糊 -->
<view class="navbar-content">
<!-- 左侧头像 -->
<view class="navbar-left" @click="openDrawer">
<app-avatar
:src="user?.avatar"
:name="user?.name"
:size="64"
:size="72"
round
custom-style="border: 2rpx solid var(--border-subtle);"
/>
</view>
<!-- 标题 -->
<view class="navbar-title">{{ user?.name || '消息' }}</view>
<!-- 右侧加号 -->
<view class="navbar-title">
<text class="title-text">{{ user?.name || '消息' }}</text>
<view class="online-status" v-if="!loading"></view>
</view>
<view class="navbar-right">
<wd-icon name="add" size="48rpx" @click="showPlusMenu = true" />
<view class="icon-btn" @click="showPlusMenu = true">
<wd-icon name="add" size="44rpx" />
</view>
</view>
</view>
</view>
<!-- 搜索 -->
<view class="search-bar">
<wd-search
v-model="searchKeyword"
placeholder="搜索"
disabled
hide-cancel
@click="goSearch"
/>
<!-- 搜索区域 (模拟 iOS 风格) -->
<view class="search-wrapper">
<view class="search-inner" @click="goSearch">
<wd-icon name="search" size="32rpx" color="var(--text-tertiary)" />
<text class="search-placeholder">搜索</text>
</view>
</view>
<!-- 会话列表 -->
@@ -42,79 +48,91 @@
:refresher-triggered="refreshing"
@refresherrefresh="onRefresh"
>
<!-- 骨架屏/加载态 (优化体验) -->
<view v-if="loading && conversations.length === 0" class="loading-state">
<wd-loading />
<text>加载...</text>
<wd-loading size="60rpx" color="var(--color-primary)" />
<text>同步消息...</text>
</view>
<wd-status-tip v-else-if="conversations.length === 0" tip="暂无消息" />
<wd-status-tip
v-else-if="conversations.length === 0"
image="message"
tip="暂无消息,开始聊天吧"
/>
<template v-else>
<!-- 列表渲染 -->
<wd-swipe-action
v-for="item in conversations"
:key="item.id"
:disabled="false"
>
<view
class="conversation-item"
:class="{ 'is-top': item.is_top, 'is-special': item.is_special_care }"
:class="{ 'is-top': item.is_top }"
@click="goChat(item)"
@longpress="handleLongPress(item)"
>
<!-- 头像 -->
<!-- 头像区域 -->
<view class="avatar-wrap">
<app-avatar
:src="item.avatar"
:name="item.name"
:size="96"
radius="8rpx"
:size="104"
radius="16rpx"
:badge="item.is_muted ? 0 : item.unread_count"
:dot="item.is_muted && item.unread_count > 0"
/>
<!-- 特别关心标 -->
<view v-if="item.is_special_care" class="special-badge">
<!-- 特别关心采用更现代的角 -->
<view v-if="item.is_special_care" class="special-badge-wrap">
<wd-icon name="heart-fill" size="20rpx" color="#fff" />
</view>
</view>
<!-- 内容 -->
<view class="conversation-content">
<view class="conversation-header">
<view class="name-wrap">
<!-- 内容区域 -->
<view class="content-wrap">
<view class="row-top">
<view class="title-box">
<wd-icon v-if="item.is_top" name="pin" size="24rpx" class="pin-icon" />
<text class="conversation-name text-ellipsis" :class="{ 'special-name': item.is_special_care }">{{ item.name || '未知' }}</text>
<text class="name text-ellipsis" :class="{ 'special-text': item.is_special_care }">
{{ item.name || '未知' }}
</text>
</view>
<text class="conversation-time">{{ formatMessageTime(item.last_time) }}</text>
<text class="time">{{ formatMessageTime(item.last_time) }}</text>
</view>
<view class="conversation-footer">
<text class="conversation-msg text-ellipsis">{{ item.last_message || '' }}</text>
<view class="row-bottom">
<text class="msg-preview text-ellipsis">{{ item.last_message || '暂无消息' }}</text>
<wd-icon v-if="item.is_muted" name="volume-mute" size="28rpx" class="mute-icon" />
</view>
</view>
</view>
<!-- 右滑操作 -->
<!-- 右滑菜单 -->
<template #right>
<view class="swipe-actions">
<view class="swipe-action action-top" @click="toggleTop(item)">
{{ item.is_top ? '取消置顶' : '置顶' }}
<view class="action-btn top" @click.stop="toggleTop(item)">
<wd-icon name="pin" size="36rpx" />
</view>
<view class="swipe-action action-delete" @click="deleteConversation(item)">
删除
<view class="action-btn delete" @click.stop="deleteConversation(item)">
<wd-icon name="delete" size="36rpx" />
</view>
</view>
</template>
</wd-swipe-action>
</template>
<!-- 底部垫片 -->
<view class="safe-area-spacer"></view>
</scroll-view>
<!-- 加号菜单 -->
<!-- 弹窗组件保持原有逻辑 -->
<wd-action-sheet
v-model="showPlusMenu"
:actions="plusMenuActions"
@select="onPlusMenuSelect"
/>
<!-- 会话操作菜单 -->
<wd-action-sheet
v-model="showConvActions"
:actions="convActionItems"
@@ -126,43 +144,35 @@
<wd-popup
v-model="showDrawer"
position="left"
custom-style="width: 70%; height: 100%; z-index: 1000;"
:z-index="1000"
custom-style="width: 75%; height: 100%; background: var(--bg-surface);"
>
<view class="drawer-content">
<!-- 用户信息 -->
<view class="drawer-header">
<app-avatar
:src="user?.avatar"
:name="user?.name"
:size="120"
round
/>
<text class="drawer-name">{{ user?.name || '未登录' }}</text>
<text class="drawer-desc">{{ user?.desc || '这个人很懒,什么都没写' }}</text>
<view class="drawer-container">
<view class="drawer-header-bg"></view>
<view class="drawer-info">
<app-avatar :src="user?.avatar" :name="user?.name" :size="140" round custom-style="border: 4rpx solid var(--bg-surface);" />
<text class="drawer-username">{{ user?.name || '请登录' }}</text>
<text class="drawer-bio">{{ user?.desc || '编辑个签,展示你的独特个性' }}</text>
</view>
<!-- 菜单列表 -->
<view class="drawer-menu">
<wd-cell title="个人资料" icon="user" is-link @click="goProfile" />
<wd-cell title="设置" icon="setting" is-link @click="goSettings" />
<wd-cell title="深色模式" icon="picture" :value="themeText" is-link @click="goTheme" />
<view class="drawer-menu-list">
<wd-cell-group border>
<wd-cell title="个人资料" icon="user" is-link size="large" @click="goProfile" />
<wd-cell title="设置" icon="setting" is-link size="large" @click="goSettings" />
<wd-cell title="深色模式" icon="picture" :value="themeText" is-link size="large" @click="goTheme" />
</wd-cell-group>
</view>
<!-- 退出登录 -->
<view class="drawer-footer">
<wd-button type="error" block plain @click="logout">退出登录</wd-button>
<view class="drawer-footer-btn">
<wd-button type="info" plain block @click="logout">退出登录</wd-button>
</view>
</view>
</wd-popup>
<!-- Toast 组件 -->
<wd-toast />
<wd-message-box />
<!-- 自定义 TabBar -->
<app-tab-bar current="messages" />
</view>
</wd-config-provider>
</template>
<script setup lang="ts">
@@ -170,8 +180,7 @@ import { ref, computed, onMounted } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import { useAuthStore, useConversationStore } from '@/stores'
import { useTheme } from '@/composables/useTheme'
import { formatMessageTime, generateColor } from '@/utils/format'
import { resolveImageUrl } from '@/utils/image'
import { formatMessageTime } from '@/utils/format'
import { useToast, useMessage } from 'wot-design-uni'
import * as conversationApi from '@/api/modules/conversation'
import AppAvatar from '@/components/common/AppAvatar.vue'
@@ -182,20 +191,9 @@ const authStore = useAuthStore()
const conversationStore = useConversationStore()
const { isDark } = useTheme()
// 主题显示文本
const themeText = computed(() => {
const mode = uni.getStorageSync('nl_im_theme_mode') || 'system'
const texts: Record<string, string> = {
system: '跟随系统',
light: '浅色模式',
dark: '深色模式'
}
return texts[mode] || '跟随系统'
})
// --- 保持原有逻辑代码不变 ---
const toast = useToast()
const messageBox = useMessage()
// 状态
const searchKeyword = ref('')
const showPlusMenu = ref(false)
const showDrawer = ref(false)
@@ -203,54 +201,39 @@ const refreshing = ref(false)
const showConvActions = ref(false)
const selectedConv = ref<Conversation | null>(null)
// 计算属性
const user = computed(() => authStore.user)
const conversations = computed(() => conversationStore.conversations)
const loading = computed(() => conversationStore.loading)
// 加号菜单选项
const themeText = computed(() => {
const mode = uni.getStorageSync('nl_im_theme_mode') || 'system'
const texts: Record<string, string> = { system: '跟随系统', light: '浅色模式', dark: '深色模式' }
return texts[mode] || '跟随系统'
})
const plusMenuActions = [
{ name: '创建群聊', value: 'createGroup' },
{ name: '添加好友', value: 'addFriend' },
{ name: '扫一扫', value: 'scan' }
]
// 会话操作菜单项
const convActionItems = computed(() => {
if (!selectedConv.value) return []
const conv = selectedConv.value
const isGroup = conv.is_group || conv.type === 2
const items: any[] = [
{ name: conv.is_top ? '取消置顶' : '置顶会话', value: 'toggleTop' },
{ name: conv.is_muted ? '开启提醒' : '消息免打扰', value: 'toggleMute' }
]
// 私聊才显示特别关心
if (!isGroup) {
items.push({ name: conv.is_special_care ? '取消特别关心' : '特别关心', value: 'toggleSpecial' })
}
// 有未读消息时显示标记已读
if (conv.unread_count && conv.unread_count > 0) {
items.push({ name: '标记已读', value: 'markRead' })
}
if (!isGroup) items.push({ name: conv.is_special_care ? '取消特别关心' : '特别关心', value: 'toggleSpecial' })
if (conv.unread_count && conv.unread_count > 0) items.push({ name: '标记已读', value: 'markRead' })
items.push({ name: '删除会话', value: 'delete', color: '#fa5151' })
return items
})
// 生命周期
onMounted(() => {
loadData()
})
onMounted(() => loadData())
onShow(() => loadData())
onShow(() => {
loadData()
})
// 方法
async function loadData() {
if (!authStore.isAuthenticated) {
uni.reLaunch({ url: '/pages/login/index' })
@@ -265,460 +248,357 @@ async function onRefresh() {
refreshing.value = false
}
function goSearch() {
uni.navigateTo({ url: '/pages/search/index' })
}
function goSearch() { uni.navigateTo({ url: '/pages/search/index' }) }
function goChat(item: Conversation) {
conversationStore.clearUnread(item.target_id)
uni.navigateTo({
url: `/pages/chat/index?roomId=${item.room_id || ''}&targetId=${item.target_id}&name=${encodeURIComponent(item.name || '')}&avatar=${encodeURIComponent(item.avatar || '')}`
})
uni.navigateTo({ url: `/pages/chat/index?roomId=${item.room_id || ''}&targetId=${item.target_id}&name=${encodeURIComponent(item.name || '')}&avatar=${encodeURIComponent(item.avatar || '')}` })
}
// 长按会话
function handleLongPress(item: Conversation) {
selectedConv.value = item
showConvActions.value = true
}
// 会话操作菜单选择
function handleLongPress(item: Conversation) { selectedConv.value = item; showConvActions.value = true }
async function onConvActionSelect(action: { value: string }) {
if (!selectedConv.value) return
const conv = selectedConv.value
switch (action.value) {
case 'toggleTop':
await toggleTop(conv)
break
case 'toggleMute':
await toggleMute(conv)
break
case 'toggleSpecial':
await toggleSpecialCare(conv)
break
case 'markRead':
await markAsRead(conv)
break
case 'delete':
await deleteConversation(conv)
break
}
showConvActions.value = false
}
// 置顶/取消置顶
async function toggleTop(item: Conversation) {
try {
const newValue = !item.is_top
await conversationApi.updateConversation({
target_id: item.target_id,
is_top: newValue
})
item.is_top = newValue
if (action.value === 'toggleTop') {
await conversationApi.updateConversation({ target_id: conv.target_id, is_top: !conv.is_top })
conv.is_top = !conv.is_top
conversationStore.sortConversations()
toast.success(newValue ? '已置顶' : '已取消置顶')
} catch (e) {
toast.error('操作失败')
} else if (action.value === 'toggleMute') {
await conversationApi.updateConversation({ target_id: conv.target_id, is_muted: !conv.is_muted })
conv.is_muted = !conv.is_muted
} else if (action.value === 'toggleSpecial') {
await conversationApi.updateConversation({ target_id: conv.target_id, is_special_care: !conv.is_special_care })
conv.is_special_care = !conv.is_special_care
} else if (action.value === 'markRead') {
await conversationStore.clearUnread(conv.target_id)
} else if (action.value === 'delete') {
await deleteConversation(conv)
}
} catch(e) { toast.error('操作失败') }
}
// 免打扰
async function toggleMute(item: Conversation) {
try {
const newValue = !item.is_muted
await conversationApi.updateConversation({
target_id: item.target_id,
is_muted: newValue
})
item.is_muted = newValue
toast.success(newValue ? '已开启免打扰' : '已开启提醒')
} catch (e) {
toast.error('操作失败')
}
}
// 特别关心
async function toggleSpecialCare(item: Conversation) {
try {
const newValue = !item.is_special_care
await conversationApi.updateConversation({
target_id: item.target_id,
is_special_care: newValue
})
item.is_special_care = newValue
toast.success(newValue ? '已设为特别关心' : '已取消特别关心')
} catch (e) {
toast.error('操作失败')
}
}
// 标记已读
async function markAsRead(item: Conversation) {
try {
await conversationStore.clearUnread(item.target_id)
toast.success('已标记为已读')
} catch (e) {
toast.error('操作失败')
}
}
// 删除会话
// 提取删除逻辑
async function deleteConversation(item: Conversation) {
try {
await messageBox.confirm({
title: '提示',
msg: '确定删除该会话吗?'
})
await messageBox.confirm({ title: '提示', msg: '确定删除该会话吗?' })
await conversationApi.deleteConversation(item.target_id)
// 从本地列表移除
const index = conversationStore.conversations.findIndex(c => c.id === item.id)
if (index > -1) {
conversationStore.conversations.splice(index, 1)
}
if (index > -1) conversationStore.conversations.splice(index, 1)
toast.success('已删除')
} catch (e: any) {
if (e !== 'cancel') {
toast.error('删除失败')
}
}
} catch (e: any) { if(e !== 'cancel') toast.error('删除失败') }
}
function onPlusMenuSelect(item: { name: string; value: string }) {
function onPlusMenuSelect(item: { value: string }) {
showPlusMenu.value = false
switch (item.value) {
case 'createGroup':
uni.navigateTo({ url: '/pages/group/create' })
break
case 'addFriend':
uni.navigateTo({ url: '/pages/contact/add' })
break
case 'scan':
uni.scanCode({
success: (res) => {
console.log('扫码结果:', res.result)
toast.show('扫码成功')
},
fail: () => {
toast.error('扫码失败')
}
})
break
}
if(item.value === 'createGroup') uni.navigateTo({ url: '/pages/group/create' })
else if(item.value === 'addFriend') uni.navigateTo({ url: '/pages/contact/add' })
else if(item.value === 'scan') uni.scanCode({ success: () => toast.show('扫码成功'), fail: () => toast.error('扫码失败') })
}
function openDrawer() {
showDrawer.value = true
}
function goProfile() {
showDrawer.value = false
uni.navigateTo({ url: '/pages/profile/index' })
}
function goSettings() {
showDrawer.value = false
uni.navigateTo({ url: '/pages/settings/index' })
}
function goTheme() {
showDrawer.value = false
uni.navigateTo({ url: '/pages/settings/theme' })
}
function openDrawer() { showDrawer.value = true }
function goProfile() { showDrawer.value = false; uni.navigateTo({ url: '/pages/profile/index' }) }
function goSettings() { showDrawer.value = false; uni.navigateTo({ url: '/pages/settings/index' }) }
function goTheme() { showDrawer.value = false; uni.navigateTo({ url: '/pages/settings/theme' }) }
async function logout() {
try {
await messageBox.confirm({
title: '提示',
msg: '确定要退出登录吗?'
})
showDrawer.value = false
authStore.logout()
} catch {
// 取消
}
try { await messageBox.confirm({ title: '提示', msg: '确定退出?' }); showDrawer.value = false; authStore.logout() } catch {}
}
// --- 保持逻辑代码结束 ---
</script>
<style lang="scss" scoped>
/* ----- 语义化 CSS 变量定义 ----- */
.page-container {
/* 亮色模式默认值 */
--bg-page: #f7f8fa;
--bg-surface: #ffffff;
--bg-hover: #f5f5f5;
--text-primary: #1d1d1f;
--text-secondary: #86868b;
--text-tertiary: #a1a1a6;
--border-subtle: #ebedf0;
--nav-bg: rgba(255, 255, 255, 0.85);
--color-brand: #4f46e5;
--color-danger: #ef4444;
--shadow-sm: 0 1px 2px rgba(0,0,0,0.05);
min-height: 100vh;
background: var(--bg-page);
color: var(--text-primary);
transition: background-color 0.3s, color 0.3s;
}
// 自定义导航栏
.custom-navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
background: var(--nav-bg);
padding-top: var(--status-bar-height, 0);
/* 暗色模式覆盖 */
.page-container.dark {
--bg-page: #000000;
--bg-surface: #1c1c1e;
--bg-hover: #2c2c2e;
--text-primary: #ffffff;
--text-secondary: #98989d;
--text-tertiary: #636366;
--border-subtle: #38383a;
--nav-bg: rgba(28, 28, 30, 0.85);
--shadow-sm: none;
}
.navbar-content {
display: flex;
align-items: center;
height: 88rpx;
padding: 0 30rpx;
/* ----- UI 样式重构 ----- */
/* 1. 沉浸式导航栏 */
.custom-navbar {
position: sticky;
top: 0;
z-index: 100;
padding-top: var(--status-bar-height);
.navbar-bg {
position: absolute;
inset: 0;
background: var(--nav-bg);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border-bottom: 1px solid var(--border-subtle);
}
.navbar-left {
margin-right: 20rpx;
.navbar-content {
position: relative;
height: 44px; /* iOS 标准导航高度 */
display: flex;
align-items: center;
padding: 0 32rpx;
display: flex;
justify-content: space-between;
}
.navbar-title {
flex: 1;
font-size: 36rpx;
position: absolute;
left: 50%;
transform: translateX(-50%);
font-size: 34rpx;
font-weight: 600;
color: var(--text-primary);
display: flex;
align-items: center;
gap: 8rpx;
.online-status {
width: 12rpx;
height: 12rpx;
background: #34c759;
border-radius: 50%;
}
}
.navbar-right {
color: var(--text-primary);
.icon-btn {
width: 64rpx;
height: 64rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
&:active { background: var(--bg-hover); }
}
}
// 搜索栏
.search-bar {
position: fixed;
top: calc(88rpx + var(--status-bar-height, 0));
left: 0;
right: 0;
/* 2. 搜索框 (仿原生) */
.search-wrapper {
background: var(--bg-page);
padding: 16rpx 32rpx;
position: sticky;
top: calc(44px + var(--status-bar-height));
z-index: 99;
padding: 10rpx 20rpx;
background: var(--nav-bg);
}
// 会话列表
.search-inner {
height: 72rpx;
background: var(--bg-surface);
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 12rpx;
.search-placeholder {
font-size: 28rpx;
color: var(--text-tertiary);
}
}
/* 3. 列表区域 */
.conversation-list {
position: fixed;
top: calc(88rpx + 80rpx + var(--status-bar-height, 0));
left: 0;
right: 0;
bottom: calc(100rpx + env(safe-area-inset-bottom));
background: var(--bg-content);
/* 移除复杂的 calc利用 Flex 布局或 padding */
height: calc(100vh - 44px - var(--status-bar-height) - 104rpx); /* 减去 nav 和 search */
}
.conversation-item {
display: flex;
align-items: center;
padding: 24rpx 30rpx;
background: var(--bg-content);
border-bottom: 1rpx solid var(--divider-color);
padding: 24rpx 32rpx;
background: var(--bg-surface);
transition: all 0.2s;
&:active {
background: var(--bg-hover);
}
&.is-top {
background: rgba(var(--color-primary-rgb, 99, 102, 241), 0.05);
background: var(--bg-page); /* 置顶颜色微调 */
}
&.is-special {
.conversation-name {
color: #ec4899;
/* 边框处理:只给内容区加底边框,头像不加,模仿 iOS */
.content-wrap {
border-bottom: 1px solid var(--border-subtle);
}
&:last-child .content-wrap {
border-bottom: none;
}
}
.avatar-wrap {
position: relative;
margin-right: 24rpx;
flex-shrink: 0;
.special-badge {
.special-badge-wrap {
position: absolute;
bottom: -4rpx;
right: -4rpx;
width: 32rpx;
height: 32rpx;
bottom: -6rpx;
right: -6rpx;
background: #ec4899;
border: 4rpx solid var(--bg-surface);
border-radius: 50%;
width: 36rpx;
height: 36rpx;
display: flex;
align-items: center;
justify-content: center;
border: 2rpx solid var(--bg-content);
}
}
.avatar-placeholder {
display: flex;
align-items: center;
justify-content: center;
background: var(--color-primary);
color: #fff;
font-weight: 600;
&.avatar-sm {
width: 64rpx;
height: 64rpx;
border-radius: 50%;
font-size: 28rpx;
}
&.avatar-md {
width: 96rpx;
height: 96rpx;
border-radius: 8rpx;
font-size: 36rpx;
}
&.avatar-lg {
width: 120rpx;
height: 120rpx;
border-radius: 50%;
font-size: 48rpx;
}
}
.unread-badge {
position: absolute;
top: -8rpx;
right: -8rpx;
}
.unread-dot {
position: absolute;
top: 0;
right: 0;
width: 16rpx;
height: 16rpx;
background: var(--color-primary);
border-radius: 50%;
}
.conversation-content {
.content-wrap {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
justify-content: center;
padding-right: 4rpx;
}
.conversation-header {
.row-top, .row-bottom {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8rpx;
}
.name-wrap {
.row-top { margin-bottom: 8rpx; }
.title-box {
display: flex;
align-items: center;
flex: 1;
min-width: 0;
.pin-icon {
color: var(--color-primary);
color: var(--color-brand);
margin-right: 8rpx;
flex-shrink: 0;
transform: rotate(45deg); /* 让图钉更灵动 */
}
}
.conversation-name {
flex: 1;
.name {
font-size: 32rpx;
font-weight: 600;
color: var(--text-primary);
font-weight: 500;
&.special-name {
color: #ec4899;
&.special-text { color: #ec4899; }
}
}
.conversation-time {
.time {
font-size: 24rpx;
color: var(--text-tertiary);
margin-left: 16rpx;
font-feature-settings: "tnum"; /* 等宽数字 */
}
.conversation-footer {
display: flex;
align-items: center;
}
.conversation-msg {
flex: 1;
.msg-preview {
font-size: 28rpx;
color: var(--text-secondary);
flex: 1;
}
.mute-icon {
color: var(--text-tertiary);
margin-left: 8rpx;
}
// 滑动操作
/* 4. 滑动菜单 */
.swipe-actions {
display: flex;
height: 100%;
}
.swipe-action {
.action-btn {
width: 140rpx;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
width: 150rpx;
height: 100%;
color: #fff;
font-size: 28rpx;
&.action-top {
background: #c8c8c8;
}
&.action-delete {
background: var(--color-danger);
}
&.top { background: #4f46e5; }
&.delete { background: #ef4444; }
}
// 抽屉
.drawer-content {
/* 5. 抽屉重构 */
.drawer-container {
height: 100%;
display: flex;
flex-direction: column;
background: var(--bg-page);
padding-bottom: env(safe-area-inset-bottom);
position: relative;
overflow: hidden;
}
.drawer-header {
.drawer-header-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 300rpx;
background: linear-gradient(135deg, var(--color-brand), #818cf8);
opacity: 0.1;
z-index: 0;
}
.drawer-info {
position: relative;
z-index: 1;
padding: 100rpx 40rpx 60rpx;
display: flex;
flex-direction: column;
align-items: center;
padding: 40rpx 30rpx 40rpx;
padding-top: calc(40rpx + var(--status-bar-height, 44px));
background: var(--bg-content);
}
.drawer-name {
margin-top: 20rpx;
font-size: 36rpx;
font-weight: 600;
.drawer-username {
font-size: 40rpx;
font-weight: 700;
margin-top: 24rpx;
color: var(--text-primary);
}
}
.drawer-desc {
margin-top: 8rpx;
.drawer-bio {
font-size: 26rpx;
color: var(--text-secondary);
margin-top: 12rpx;
}
}
.drawer-menu {
.drawer-menu-list {
flex: 1;
margin-top: 20rpx;
background: var(--bg-content);
background: var(--bg-surface);
}
.drawer-footer {
padding: 30rpx;
padding-bottom: calc(30rpx + env(safe-area-inset-bottom));
.drawer-footer-btn {
padding: 40rpx;
padding-bottom: calc(40rpx + env(safe-area-inset-bottom));
}
.safe-area-spacer {
height: 150rpx; /* 留出 Tabbar 空间 */
}
// 加载和空状态
.loading-state {
padding-top: 200rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx;
color: var(--text-tertiary);
gap: 20rpx;
font-size: 26rpx;
}
</style>

View File

@@ -1,52 +1,62 @@
<template>
<!-- 根节点绑定 dark class 适配全局暗黑模式 -->
<view class="login-page" :class="{ dark: isDark }">
<!-- 导航栏 -->
<app-nav-bar title="登录" :left-arrow="false" />
<app-nav-bar title="登录" :left-arrow="false" :bordered="false" />
<!-- Logo -->
<!-- Logo 区域 -->
<view class="logo-section">
<view class="logo">
<wd-icon name="chat" size="120rpx" color="#07c160" />
<view class="logo-box">
<wd-icon name="chat" size="60px" color="#07c160" />
</view>
<text class="app-name">NL-IM</text>
<text class="app-desc">即时通讯</text>
<text class="app-desc">即时通讯连接你我</text>
</view>
<!-- 登录表单 -->
<!-- 表单区域 -->
<view class="form-section">
<wd-cell-group border>
<wd-input
v-model="form.account"
size="large"
label="账号"
label-width="60px"
placeholder="请输入手机号/邮箱"
clearable
:prefix-icon="'user'"
prefix-icon="user"
:placeholder-style="isDark ? 'color: #555' : ''"
/>
<wd-input
v-model="form.password"
size="large"
label="密码"
label-width="60px"
type="password"
placeholder="请输入密码"
clearable
show-password
:prefix-icon="'lock'"
prefix-icon="lock"
:placeholder-style="isDark ? 'color: #555' : ''"
/>
</wd-cell-group>
<view class="form-options">
<wd-checkbox v-model="form.remember">记住登录</wd-checkbox>
<wd-checkbox v-model="form.remember" shape="square">记住登录</wd-checkbox>
<text class="forgot-link" @click="forgotPassword">忘记密码</text>
</view>
<view class="action-buttons">
<wd-button
type="primary"
block
size="large"
:loading="loading"
:disabled="!canLogin"
@click="handleLogin"
>
登录
</wd-button>
</view>
<view class="register-link">
<text>还没有账号</text>
@@ -56,19 +66,16 @@
<!-- 其他登录方式 -->
<view class="other-login">
<view class="divider">
<view class="line" />
<text class="text">其他登录方式</text>
<view class="line" />
</view>
<wd-divider color="var(--text-tertiary)">其他登录方式</wd-divider>
<view class="login-icons">
<view class="login-icon" @click="loginWithWeixin">
<wd-icon name="weixin" size="56rpx" color="#07c160" />
<view class="login-icon-item" hover-class="hover-opacity" @click="loginWithWeixin">
<wd-icon name="weixin" size="32px" color="#07c160" />
</view>
</view>
</view>
<!-- Toast MessageBox -->
<!-- 全局反馈组件 -->
<wd-toast />
<wd-message-box />
</view>
@@ -79,6 +86,8 @@ import { ref, computed } from 'vue'
import { useAuthStore } from '@/stores'
import { useToast, useMessage } from 'wot-design-uni'
import { useTheme } from '@/composables/useTheme'
// 引入 AppNavBar 组件 (通常已自动引入,显式引入以防万一)
import AppNavBar from '@/components/common/AppNavBar.vue'
const authStore = useAuthStore()
const toast = useToast()
@@ -99,7 +108,7 @@ const canLogin = computed(() => {
return form.value.account.trim() && form.value.password.trim()
})
// 登录
// 登录逻辑 (保持原有逻辑不变)
async function handleLogin() {
if (!canLogin.value) return
@@ -121,17 +130,14 @@ async function handleLogin() {
}
}
// 忘记密码
function forgotPassword() {
toast.show('忘记密码功能开发中')
}
// 去注册
function goRegister() {
toast.show('注册功能开发中')
}
// 微信登录
function loginWithWeixin() {
// #ifdef MP-WEIXIN
uni.login({
@@ -157,55 +163,55 @@ function loginWithWeixin() {
min-height: 100vh;
display: flex;
flex-direction: column;
background: var(--bg-page);
background-color: var(--bg-page);
padding: 0 40rpx;
box-sizing: border-box;
transition: background-color 0.3s;
}
// Logo 区域
.logo-section {
display: flex;
flex-direction: column;
align-items: center;
padding-top: 150rpx;
padding-top: 120rpx;
margin-bottom: 80rpx;
.logo {
.logo-box {
width: 160rpx;
height: 160rpx;
display: flex;
align-items: center;
justify-content: center;
background: var(--bg-content);
background-color: var(--bg-content);
border-radius: 32rpx;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.1);
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.05);
margin-bottom: 30rpx;
}
.app-name {
margin-top: 24rpx;
font-size: 48rpx;
font-weight: 700;
color: var(--text-primary);
}
.app-desc {
margin-top: 8rpx;
margin-top: 12rpx;
font-size: 28rpx;
color: var(--text-secondary);
}
}
// 表单区域
.form-section {
background: var(--bg-content);
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 40rpx;
background-color: var(--bg-content);
border-radius: 24rpx;
padding: 40rpx 30rpx;
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.02);
.form-options {
display: flex;
align-items: center;
justify-content: space-between;
margin: 30rpx 0;
margin: 30rpx 0 50rpx;
.forgot-link {
font-size: 28rpx;
@@ -213,56 +219,46 @@ function loginWithWeixin() {
}
}
.action-buttons {
margin-bottom: 30rpx;
}
.register-link {
text-align: center;
margin-top: 30rpx;
font-size: 28rpx;
color: var(--text-secondary);
.link {
color: var(--color-primary);
margin-left: 8rpx;
margin-left: 10rpx;
font-weight: 500;
}
}
}
// 其他登录方式
.other-login {
margin-top: auto;
padding-bottom: calc(60rpx + env(safe-area-inset-bottom));
.divider {
display: flex;
align-items: center;
margin-bottom: 40rpx;
.line {
flex: 1;
height: 1rpx;
background: var(--border-color);
}
.text {
padding: 0 24rpx;
font-size: 26rpx;
color: var(--text-tertiary);
}
}
width: 100%;
.login-icons {
display: flex;
justify-content: center;
gap: 60rpx;
margin-top: 40rpx;
.login-icon {
.login-icon-item {
width: 96rpx;
height: 96rpx;
display: flex;
align-items: center;
justify-content: center;
background: var(--bg-content);
background-color: var(--bg-content);
border-radius: 50%;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.05);
&.hover-opacity {
opacity: 0.8;
}
}
}
}

View File

@@ -1,115 +1,138 @@
<template>
<view class="profile-page" :class="{ dark: isDark }">
<!-- 导航栏 -->
<app-nav-bar title="个人资料" />
<!-- 顶部导航栏 (透明背景或自定义颜色) -->
<app-nav-bar title="" :left-arrow="false" :bordered="false" />
<!-- 头像区域 -->
<view class="header-section">
<view class="avatar-wrap" @click="changeAvatar">
<app-avatar
:src="user?.avatar"
:name="user?.name"
:size="160"
<!-- 用户信息卡片 -->
<view class="user-card" @click="goProfileEdit">
<view class="avatar-wrapper">
<wd-img
:src="userInfo.avatar"
width="120rpx"
height="120rpx"
round
mode="aspectFill"
/>
<view class="change-tip">点击更换</view>
</view>
<view class="info-wrapper">
<view class="name-row">
<text class="nickname">{{ userInfo.nickname || '未设置昵称' }}</text>
<wd-icon name="edit" size="16px" color="var(--text-secondary)" custom-class="edit-icon" />
</view>
<text class="account">账号: {{ userInfo.account || '--' }}</text>
</view>
<view class="arrow">
<wd-icon name="arrow-right" color="var(--text-tertiary)" />
</view>
</view>
<!-- 基本信息 -->
<wd-cell-group title="基本信息">
<wd-cell title="昵称" :value="user?.name || '未设置'" is-link @click="editField('name')" />
<wd-cell title="ID" :value="user?.id || '-'" />
<wd-cell title="个性签名" :value="user?.desc || '未设置'" is-link @click="editField('desc')" />
<wd-cell title="地区" :value="user?.region || '未设置'" is-link @click="editField('region')" />
<!-- 功能列表 -->
<view class="menu-list">
<wd-cell-group border>
<wd-cell title="我的动态" is-link to="/pages/moment/my" icon="moments" size="large" />
<wd-cell title="收藏夹" is-link to="/pages/collection/index" icon="star" size="large" />
<wd-cell title="相册" is-link to="/pages/album/index" icon="picture" size="large" />
</wd-cell-group>
<!-- 联系方式 -->
<wd-cell-group title="联系方式">
<wd-cell title="手机号" :value="formatPhone(user?.phone)" />
<wd-cell title="邮箱" :value="user?.email || '未绑定'" />
</wd-cell-group>
<view class="gap" />
<wd-toast />
<wd-cell-group border>
<wd-cell title="设置" is-link to="/pages/settings/index" icon="setting" size="large" />
</wd-cell-group>
</view>
<!-- 底部 TabBar -->
<app-tab-bar active="/pages/profile/index" />
</view>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useAuthStore } from '@/stores'
import { useToast } from 'wot-design-uni'
import { useTheme } from '@/composables/useTheme'
import { resolveImageUrl } from '@/utils/image'
import AppAvatar from '@/components/common/AppAvatar.vue'
import { useAuthStore } from '@/stores'
import AppNavBar from '@/components/common/AppNavBar.vue'
import AppTabBar from '@/components/common/AppTabBar.vue'
const authStore = useAuthStore()
const toast = useToast()
const { isDark } = useTheme()
const authStore = useAuthStore()
const user = computed(() => authStore.user)
const userInfo = computed(() => authStore.userInfo || {})
function formatPhone(phone?: string) {
if (!phone) return '未绑定'
return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
}
function changeAvatar() {
uni.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
// TODO: 上传头像
console.log('选择的图片:', res.tempFilePaths[0])
toast.show('头像上传功能开发中')
}
})
}
function editField(field: string) {
const value = field === 'name' ? user.value?.name : field === 'desc' ? user.value?.desc : user.value?.region
uni.navigateTo({
url: `/pages/profile/edit?field=${field}&value=${encodeURIComponent(value || '')}`
})
function goProfileEdit() {
uni.navigateTo({ url: '/pages/profile/edit' })
}
</script>
<style lang="scss" scoped>
.profile-page {
min-height: 100vh;
background: var(--bg-page);
background-color: var(--bg-page);
padding-bottom: calc(100rpx + env(safe-area-inset-bottom));
}
.header-section {
display: flex;
justify-content: center;
padding: 60rpx 0;
background: var(--bg-content);
margin-bottom: 20rpx;
}
.avatar-wrap {
display: flex;
flex-direction: column;
align-items: center;
.avatar-placeholder {
width: 160rpx;
height: 160rpx;
.user-card {
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: var(--color-primary);
color: #fff;
font-size: 64rpx;
font-weight: 600;
padding: 40rpx 32rpx;
background-color: var(--bg-content);
margin-bottom: 24rpx;
transition: background-color 0.2s;
&:active {
background-color: var(--bg-active);
}
.change-tip {
margin-top: 16rpx;
font-size: 26rpx;
color: var(--text-tertiary);
.avatar-wrapper {
margin-right: 32rpx;
border: 2rpx solid var(--border-color);
border-radius: 50%;
padding: 2rpx; // 模拟头像边框
}
.info-wrapper {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
.name-row {
display: flex;
align-items: center;
margin-bottom: 12rpx;
.nickname {
font-size: 40rpx;
font-weight: 600;
color: var(--text-primary);
margin-right: 12rpx;
}
.edit-icon {
opacity: 0.6;
}
}
.account {
font-size: 28rpx;
color: var(--text-secondary);
}
}
}
.menu-list {
.gap {
height: 24rpx;
}
}
.dark {
.user-card {
background-color: var(--bg-content);
}
:deep(.wd-cell) {
background-color: var(--bg-content);
color: var(--text-primary);
}
}
</style>

View File

@@ -1,29 +1,32 @@
<template>
<view class="settings-page" :class="{ dark: isDark }">
<!-- 导航栏 -->
<app-nav-bar title="设置" />
<wd-cell-group title="账号设置">
<wd-cell title="个人资料" icon="user" is-link @click="goProfile" />
<wd-cell title="账号安全" icon="shield" is-link @click="goSecurity" />
<view class="content">
<!-- 账号安全 -->
<wd-cell-group title="账号安全" border>
<wd-cell title="账号管理" is-link to="/pages/settings/account" />
<wd-cell title="隐私设置" is-link to="/pages/settings/privacy" />
</wd-cell-group>
<wd-cell-group title="通用设置">
<wd-cell title="消息通知" icon="bell" is-link @click="goNotification" />
<wd-cell title="隐私设置" icon="eye-close" is-link @click="goPrivacy" />
<wd-cell title="深色模式" icon="picture" :value="themeText" is-link @click="goTheme" />
<wd-cell title="清除缓存" icon="delete" :value="cacheSize" is-link @click="clearCache" />
<!-- 通用设置 -->
<wd-cell-group title="通用" border>
<wd-cell title="新消息通知" is-link to="/pages/settings/notification" />
<wd-cell title="主题设置" is-link to="/pages/settings/theme" value="自动" />
<wd-cell title="通用设置" is-link to="/pages/settings/general" />
<wd-cell title="清理缓存" is-link value="12.5MB" clickable @click="handleClearCache" />
</wd-cell-group>
<wd-cell-group title="关于">
<wd-cell title="当前版本" value="v1.0.0" />
<wd-cell title="用户协议" is-link @click="goAgreement" />
<wd-cell title="隐私政策" is-link @click="goPrivacyPolicy" />
<wd-cell title="检查更新" is-link @click="checkUpdate" />
<!-- 关于 -->
<wd-cell-group title="关于" border>
<wd-cell title="帮助与反馈" is-link to="/pages/settings/feedback" />
<wd-cell title="关于我们" is-link to="/pages/settings/about" value="v1.0.0" />
</wd-cell-group>
<!-- 退出登录 -->
<view class="logout-section">
<wd-button type="error" block plain @click="logout">退出登录</wd-button>
<wd-button type="error" block size="large" @click="handleLogout">退出登录</wd-button>
</view>
</view>
<wd-toast />
@@ -32,128 +35,68 @@
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useAuthStore } from '@/stores'
import { useTheme } from '@/composables/useTheme'
import { useAuthStore } from '@/stores'
import { useToast, useMessage } from 'wot-design-uni'
import AppNavBar from '@/components/common/AppNavBar.vue'
const authStore = useAuthStore()
const { isDark } = useTheme()
const authStore = useAuthStore()
const toast = useToast()
const messageBox = useMessage()
const message = useMessage()
const cacheSize = ref('0 KB')
// 主题显示文本
const themeText = computed(() => {
const mode = uni.getStorageSync('nl_im_theme_mode') || 'system'
const texts: Record<string, string> = {
system: '跟随系统',
light: '浅色模式',
dark: '深色模式'
}
return texts[mode] || '跟随系统'
})
onMounted(() => {
calculateCacheSize()
})
function calculateCacheSize() {
try {
const info = uni.getStorageInfoSync()
const size = info.currentSize || 0
if (size < 1024) {
cacheSize.value = `${size} KB`
} else {
cacheSize.value = `${(size / 1024).toFixed(2)} MB`
}
} catch {
cacheSize.value = '0 KB'
}
}
function goProfile() {
uni.navigateTo({ url: '/pages/profile/index' })
}
function goSecurity() {
toast.show('账号安全功能开发中')
}
function goNotification() {
toast.show('消息通知设置开发中')
}
function goPrivacy() {
toast.show('隐私设置功能开发中')
}
function goTheme() {
uni.navigateTo({ url: '/pages/settings/theme' })
}
async function clearCache() {
try {
await messageBox.confirm({
function handleLogout() {
message.confirm({
title: '提示',
msg: '确定要清除缓存吗?'
})
// 保留登录信息
const token = uni.getStorageSync('nl_im_token')
const userId = uni.getStorageSync('nl_im_user_id')
const userInfo = uni.getStorageSync('nl_im_user_info')
const theme = uni.getStorageSync('nl_im_theme')
uni.clearStorageSync()
// 恢复登录信息
if (token) uni.setStorageSync('nl_im_token', token)
if (userId) uni.setStorageSync('nl_im_user_id', userId)
if (userInfo) uni.setStorageSync('nl_im_user_info', userInfo)
if (theme) uni.setStorageSync('nl_im_theme', theme)
cacheSize.value = '0 KB'
toast.success('清除成功')
} catch {
// 取消
}
}
function goAgreement() {
toast.show('用户协议页面开发中')
}
function goPrivacyPolicy() {
toast.show('隐私政策页面开发中')
}
function checkUpdate() {
toast.show('当前已是最新版本')
}
async function logout() {
msg: '确定要退出登录吗?',
success: async () => {
try {
await messageBox.confirm({
title: '提示',
msg: '确定要退出登录吗?'
})
authStore.logout()
} catch {
// 取消
await authStore.logout()
uni.reLaunch({ url: '/pages/login/index' })
} catch (error) {
toast.error('退出失败')
}
}
})
}
function handleClearCache() {
toast.loading('清理中...')
setTimeout(() => {
toast.success('清理完成')
}, 1000)
}
</script>
<style lang="scss" scoped>
.settings-page {
min-height: 100vh;
background: var(--bg-page);
background-color: var(--bg-page);
.content {
padding-bottom: 40rpx;
}
.logout-section {
margin: 60rpx 30rpx 0;
}
}
.logout-section {
padding: 60rpx 30rpx;
padding-bottom: calc(60rpx + env(safe-area-inset-bottom));
// 暗黑模式微调 Cell Group 标题颜色
.dark {
:deep(.wd-cell-group__title) {
color: var(--text-secondary);
background-color: var(--bg-page);
}
:deep(.wd-cell) {
background-color: var(--bg-content);
color: var(--text-primary);
// 覆盖 cell 内部线条颜色 (如果需要)
&::after {
background-color: var(--border-color);
}
}
}
</style>

4
提示词.txt Normal file
View File

@@ -0,0 +1,4 @@
要求:
本项目使用wot-ui并且已经配置好暗色亮色模式的<wd-config-provider theme="dark">,你写的代码要适配wot-ui的暗色浅色模式