UI设计
This commit is contained in:
@@ -281,6 +281,7 @@ watch(() => props.remoteStream, (stream) => {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
background: #0f172a; // 设计稿: 来电背景
|
||||||
|
|
||||||
.blur-img {
|
.blur-img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -294,7 +295,35 @@ watch(() => props.remoteStream, (stream) => {
|
|||||||
.blur-overlay {
|
.blur-overlay {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
background: linear-gradient(180deg, rgba(0,0,0,0.4) 0%, transparent 30%, rgba(0,0,0,0.9) 100%);
|
background: rgba(15, 23, 42, 0.8); // 设计稿: bg-[#0f172a]/80
|
||||||
|
backdrop-filter: blur(48px);
|
||||||
|
-webkit-backdrop-filter: blur(48px);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设计稿: 呼吸光球动画
|
||||||
|
&::before, &::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
width: 600rpx;
|
||||||
|
height: 600rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
filter: blur(160rpx);
|
||||||
|
opacity: 0.4;
|
||||||
|
animation: glow-breathe 8s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
top: 10%;
|
||||||
|
left: -100rpx;
|
||||||
|
background: #9333ea; // purple-600
|
||||||
|
animation-delay: 0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
bottom: 10%;
|
||||||
|
right: -100rpx;
|
||||||
|
background: #2563eb; // blue-600
|
||||||
|
animation-delay: 4s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,23 +395,36 @@ watch(() => props.remoteStream, (stream) => {
|
|||||||
position: relative;
|
position: relative;
|
||||||
margin-bottom: 48rpx;
|
margin-bottom: 48rpx;
|
||||||
|
|
||||||
|
// 设计稿: 头像波纹效果
|
||||||
.avatar-ring {
|
.avatar-ring {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
inset: -20rpx;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
border: 2rpx solid rgba(255, 255, 255, 0.1);
|
border: 2rpx solid rgba(255, 255, 255, 0.3); // 设计稿: border-white/30
|
||||||
|
|
||||||
|
&.ring-1 {
|
||||||
|
animation: ripple 2s cubic-bezier(0, 0, 0.2, 1) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ring-2 {
|
||||||
|
animation: ripple 2s cubic-bezier(0, 0, 0.2, 1) infinite;
|
||||||
|
animation-delay: 1s;
|
||||||
|
}
|
||||||
|
|
||||||
&.ring-ping {
|
&.ring-ping {
|
||||||
inset: -4rpx;
|
inset: -4rpx;
|
||||||
border: 2rpx solid rgba(255, 255, 255, 0.2);
|
border: 2rpx solid rgba(255, 255, 255, 0.3);
|
||||||
animation: ping 2s cubic-bezier(0, 0, 0.2, 1) infinite;
|
animation: ping 2s cubic-bezier(0, 0, 0.2, 1) infinite;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar-container {
|
.avatar-container {
|
||||||
|
position: relative;
|
||||||
border: 4rpx solid rgba(255, 255, 255, 0.1);
|
border: 4rpx solid rgba(255, 255, 255, 0.1);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
padding: 4rpx;
|
padding: 4rpx;
|
||||||
box-shadow: 0 40rpx 100rpx rgba(0, 0, 0, 0.4);
|
box-shadow: 0 40rpx 100rpx rgba(0, 0, 0, 0.4);
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -527,4 +569,16 @@ watch(() => props.remoteStream, (stream) => {
|
|||||||
from { transform: translateY(40rpx); opacity: 0; }
|
from { transform: translateY(40rpx); opacity: 0; }
|
||||||
to { transform: translateY(0); opacity: 1; }
|
to { transform: translateY(0); opacity: 1; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设计稿: 呼吸光球动画
|
||||||
|
@keyframes glow-breathe {
|
||||||
|
0%, 100% { transform: scale(1); opacity: 0.3; }
|
||||||
|
50% { transform: scale(1.2); opacity: 0.5; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设计稿: 头像波纹动画
|
||||||
|
@keyframes ripple {
|
||||||
|
0% { transform: scale(1); opacity: 0.4; }
|
||||||
|
100% { transform: scale(2.5); opacity: 0; }
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -15,15 +15,12 @@
|
|||||||
<!-- 头部用户信息 -->
|
<!-- 头部用户信息 -->
|
||||||
<view class="drawer-header">
|
<view class="drawer-header">
|
||||||
<view class="avatar-wrap">
|
<view class="avatar-wrap">
|
||||||
<image
|
<app-avatar
|
||||||
v-if="user?.avatar"
|
:src="user?.avatar"
|
||||||
class="avatar"
|
:name="user?.name"
|
||||||
:src="resolveImageUrl(user.avatar)"
|
:size="120"
|
||||||
mode="aspectFill"
|
round
|
||||||
/>
|
/>
|
||||||
<view v-else class="avatar avatar-placeholder">
|
|
||||||
{{ (user?.name || '?').charAt(0) }}
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
<text class="username">{{ user?.name || '请登录' }}</text>
|
<text class="username">{{ user?.name || '请登录' }}</text>
|
||||||
<text class="bio">{{ user?.desc || '编辑个签,展示你的独特个性' }}</text>
|
<text class="bio">{{ user?.desc || '编辑个签,展示你的独特个性' }}</text>
|
||||||
@@ -66,6 +63,7 @@ import { computed } from 'vue'
|
|||||||
import { useAuthStore } from '@/stores'
|
import { useAuthStore } from '@/stores'
|
||||||
import { useTheme } from '@/composables/useTheme'
|
import { useTheme } from '@/composables/useTheme'
|
||||||
import { resolveImageUrl } from '@/utils/image'
|
import { resolveImageUrl } from '@/utils/image'
|
||||||
|
import AppAvatar from '@/components/common/AppAvatar.vue'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
modelValue: boolean
|
modelValue: boolean
|
||||||
|
|||||||
@@ -50,6 +50,18 @@
|
|||||||
<polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"/>
|
<polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"/>
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<!-- 我的图标 (user) -->
|
||||||
|
<template v-else-if="tab.name === 'profile'">
|
||||||
|
<svg v-if="current === tab.name" class="icon" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
|
||||||
|
<circle cx="12" cy="7" r="4"/>
|
||||||
|
</svg>
|
||||||
|
<svg v-else class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
|
||||||
|
<circle cx="12" cy="7" r="4"/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
</view>
|
</view>
|
||||||
<text class="tabbar-text">{{ tab.label }}</text>
|
<text class="tabbar-text">{{ tab.label }}</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -74,11 +86,12 @@ const { isDark } = useTheme()
|
|||||||
const conversationStore = useConversationStore()
|
const conversationStore = useConversationStore()
|
||||||
const unreadCount = computed(() => conversationStore.totalUnread)
|
const unreadCount = computed(() => conversationStore.totalUnread)
|
||||||
|
|
||||||
// 底部导航只有3个: 消息/联系人/发现 (无"我的")
|
// 底部导航4个: 消息/联系人/发现/我的
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{ name: 'messages', label: '消息', path: '/pages/index/index' },
|
{ name: 'messages', label: '消息', path: '/pages/index/index' },
|
||||||
{ name: 'contacts', label: '联系人', path: '/pages/contact/index' },
|
{ name: 'contacts', label: '联系人', path: '/pages/contact/index' },
|
||||||
{ name: 'moments', label: '发现', path: '/pages/moment/index' }
|
{ name: 'moments', label: '发现', path: '/pages/moment/index' },
|
||||||
|
{ name: 'profile', label: '我的', path: '/pages/profile/index' }
|
||||||
]
|
]
|
||||||
|
|
||||||
function switchTab(name: string) {
|
function switchTab(name: string) {
|
||||||
|
|||||||
@@ -75,15 +75,12 @@
|
|||||||
<view v-else class="msg-row" :class="{ 'is-self': msg.isSelf }">
|
<view v-else class="msg-row" :class="{ 'is-self': msg.isSelf }">
|
||||||
<!-- 头像 - 设计稿: w-10 h-10 rounded-xl shrink-0 -->
|
<!-- 头像 - 设计稿: w-10 h-10 rounded-xl shrink-0 -->
|
||||||
<view class="avatar-wrap" @click="onAvatarClick(msg)">
|
<view class="avatar-wrap" @click="onAvatarClick(msg)">
|
||||||
<image
|
<app-avatar
|
||||||
v-if="msg.isSelf ? currentUser?.avatar : getMessageSenderAvatar(msg)"
|
:src="msg.isSelf ? currentUser?.avatar : getMessageSenderAvatar(msg)"
|
||||||
:src="msg.isSelf ? resolveImageUrl(currentUser?.avatar || '') : getMessageSenderAvatar(msg)"
|
:name="msg.isSelf ? currentUser?.name : getMessageSenderName(msg)"
|
||||||
class="avatar-img"
|
:size="80"
|
||||||
mode="aspectFill"
|
radius="24rpx"
|
||||||
/>
|
/>
|
||||||
<view v-else class="avatar-placeholder">
|
|
||||||
{{ msg.isSelf ? (currentUser?.name?.charAt(0) || '?') : (getMessageSenderName(msg)?.charAt(0) || '?') }}
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 内容区 - 设计稿: flex flex-col max-w-[75%] -->
|
<!-- 内容区 - 设计稿: flex flex-col max-w-[75%] -->
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -191,25 +191,33 @@ async function deleteFriend() { try { await messageBox.confirm({ title: '提示'
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
// 页面容器 - 浅色模式
|
// 页面容器 - 浅色模式 (与设计稿完全一致)
|
||||||
.detail-container {
|
.detail-container {
|
||||||
--bg-base: #f7f8fa;
|
--bg-base: #ffffff; // 白色页面背景
|
||||||
--bg-card: #ffffff;
|
--bg-card: #ffffff; // 卡片背景
|
||||||
--text-main: #1d1d1f;
|
--bg-secondary: #f3f4f6; // gray-100 (次要背景/按钮)
|
||||||
--text-sub: #6b7280;
|
--text-main: #1f2937; // gray-900 (名称)
|
||||||
--color-brand: #4F46E5;
|
--text-sub: #6b7280; // gray-500 (签名/标签)
|
||||||
|
--text-tertiary: #9ca3af; // gray-400 (值)
|
||||||
|
--color-brand: #4F46E5; // indigo-600
|
||||||
|
--border-color: #f9fafb; // gray-50
|
||||||
|
--gradient-to: #ffffff; // 渐变终点色
|
||||||
|
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: var(--bg-base);
|
background: var(--bg-base);
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
// 深色模式 - Warm Stone
|
// 深色模式 - Warm Stone (与设计稿完全一致)
|
||||||
&.theme-dark {
|
&.theme-dark {
|
||||||
--bg-base: #1c1917;
|
--bg-base: #0c0a09; // warm-950 (极深背景)
|
||||||
--bg-card: #292524;
|
--bg-card: #1c1917; // warm-900
|
||||||
--text-main: #f5f5f4;
|
--bg-secondary: #292524; // warm-800
|
||||||
--text-sub: #78716c;
|
--text-main: #f5f5f4; // warm-100
|
||||||
--color-brand: #f97316;
|
--text-sub: #a8a29e; // warm-400
|
||||||
|
--text-tertiary: #78716c; // warm-500
|
||||||
|
--color-brand: #f97316; // orange-500
|
||||||
|
--border-color: rgba(41, 37, 36, 0.5); // warm-800/50
|
||||||
|
--gradient-to: #0c0a09; // warm-950
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,7 +271,7 @@ async function deleteFriend() { try { await messageBox.confirm({ title: '提示'
|
|||||||
.gradient-mask {
|
.gradient-mask {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
background: linear-gradient(to bottom, transparent 0%, var(--bg-base) 85%);
|
background: linear-gradient(to bottom, transparent 0%, var(--gradient-to) 85%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-back {
|
.nav-back {
|
||||||
@@ -341,14 +349,10 @@ async function deleteFriend() { try { await messageBox.confirm({ title: '提示'
|
|||||||
gap: 8rpx;
|
gap: 8rpx;
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
color: var(--text-sub);
|
color: var(--text-sub);
|
||||||
background: rgba(0, 0, 0, 0.04);
|
background: var(--bg-secondary);
|
||||||
padding: 10rpx 20rpx;
|
padding: 10rpx 20rpx;
|
||||||
border-radius: 20rpx;
|
border-radius: 20rpx;
|
||||||
|
|
||||||
.theme-dark & {
|
|
||||||
background: rgba(255, 255, 255, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
width: 24rpx;
|
width: 24rpx;
|
||||||
height: 24rpx;
|
height: 24rpx;
|
||||||
@@ -515,12 +519,8 @@ async function deleteFriend() { try { await messageBox.confirm({ title: '提示'
|
|||||||
|
|
||||||
&.secondary {
|
&.secondary {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
background: rgba(0, 0, 0, 0.04);
|
background: var(--bg-secondary);
|
||||||
color: var(--text-main);
|
color: var(--text-main);
|
||||||
|
|
||||||
.theme-dark & {
|
|
||||||
background: rgba(255, 255, 255, 0.08);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,16 +20,12 @@
|
|||||||
@click="goChat(group)"
|
@click="goChat(group)"
|
||||||
>
|
>
|
||||||
<view class="card-left">
|
<view class="card-left">
|
||||||
<wd-img
|
<app-avatar
|
||||||
v-if="group.room_avatar"
|
|
||||||
:src="group.room_avatar"
|
:src="group.room_avatar"
|
||||||
width="100rpx"
|
:name="group.room_name"
|
||||||
height="100rpx"
|
:size="100"
|
||||||
radius="20rpx"
|
radius="20rpx"
|
||||||
/>
|
/>
|
||||||
<view v-else class="avatar-placeholder" :style="{ background: generateColor(group.room_name || '') }">
|
|
||||||
{{ group.room_name?.charAt(0) || '群' }}
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="card-right">
|
<view class="card-right">
|
||||||
@@ -59,6 +55,7 @@ import { ref, onMounted } from 'vue'
|
|||||||
import * as roomApi from '@/api/modules/room'
|
import * as roomApi from '@/api/modules/room'
|
||||||
import { generateColor } from '@/utils/format'
|
import { generateColor } from '@/utils/format'
|
||||||
import { useTheme } from '@/composables/useTheme'
|
import { useTheme } from '@/composables/useTheme'
|
||||||
|
import AppAvatar from '@/components/common/AppAvatar.vue'
|
||||||
|
|
||||||
// --- 逻辑保持不变 ---
|
// --- 逻辑保持不变 ---
|
||||||
interface GroupItem { room_id: string; room_name: string; room_avatar: string; category: 'joined' | 'created' | 'managed' }
|
interface GroupItem { room_id: string; room_name: string; room_avatar: string; category: 'joined' | 'created' | 'managed' }
|
||||||
|
|||||||
@@ -50,6 +50,8 @@
|
|||||||
<scroll-view
|
<scroll-view
|
||||||
class="main-scroll custom-scrollbar"
|
class="main-scroll custom-scrollbar"
|
||||||
scroll-y
|
scroll-y
|
||||||
|
:scroll-into-view="scrollIntoViewId"
|
||||||
|
:scroll-with-animation="true"
|
||||||
:refresher-enabled="true"
|
:refresher-enabled="true"
|
||||||
:refresher-triggered="refreshing"
|
:refresher-triggered="refreshing"
|
||||||
@refresherrefresh="onRefresh"
|
@refresherrefresh="onRefresh"
|
||||||
@@ -292,7 +294,7 @@ import type { Contact, ContactGroup } from '@/types/api'
|
|||||||
// --- 逻辑完全不变 ---
|
// --- 逻辑完全不变 ---
|
||||||
interface GroupChat { room_id: string; room_type: string; room_name: string; room_avatar: string; category: 'joined' | 'created' | 'managed'; }
|
interface GroupChat { room_id: string; room_type: string; room_name: string; room_avatar: string; category: 'joined' | 'created' | 'managed'; }
|
||||||
const contactStore = useContactStore(); const chatStore = useChatStore(); const { isDark } = useTheme(); const toast = useToast(); const messageBox = useMessage();
|
const contactStore = useContactStore(); const chatStore = useChatStore(); const { isDark } = useTheme(); const toast = useToast(); const messageBox = useMessage();
|
||||||
const tabs = [{ key: 'groups', label: '分组' }, { key: 'friends', label: '好友' }, { key: 'chats', label: '群聊' }]; const activeTab = ref('groups'); const searchKeyword = ref(''); const refreshing = ref(false); const loading = ref(false); const collapsedIds = ref<(number | string)[]>([]); const contactGroups = ref<ContactGroup[]>([]); const groupChats = ref<GroupChat[]>([]); const showContactActions = ref(false); const selectedContact = ref<Contact | null>(null); const showMoveGroupModal = ref(false); const showRemarkModal = ref(false); const remarkName = ref(''); const savingRemark = ref(false); const showGroupActions = ref(false); const selectedGroup = ref<ContactGroup | null>(null); const showCreateGroupModal = ref(false); const showRenameGroupModal = ref(false); const newGroupName = ref(''); const renameGroupName = ref(''); const creating = ref(false); const renaming = ref(false);
|
const tabs = [{ key: 'groups', label: '分组' }, { key: 'friends', label: '好友' }, { key: 'chats', label: '群聊' }]; const activeTab = ref('groups'); const searchKeyword = ref(''); const refreshing = ref(false); const loading = ref(false); const collapsedIds = ref<(number | string)[]>([]); const contactGroups = ref<ContactGroup[]>([]); const groupChats = ref<GroupChat[]>([]); const showContactActions = ref(false); const selectedContact = ref<Contact | null>(null); const showMoveGroupModal = ref(false); const showRemarkModal = ref(false); const remarkName = ref(''); const savingRemark = ref(false); const showGroupActions = ref(false); const selectedGroup = ref<ContactGroup | null>(null); const showCreateGroupModal = ref(false); const showRenameGroupModal = ref(false); const newGroupName = ref(''); const renameGroupName = ref(''); const creating = ref(false); const renaming = ref(false); const scrollIntoViewId = ref('');
|
||||||
const contacts = computed(() => chatStore.contacts); const friendRequestCount = computed(() => contactStore.friendRequests.filter(r => r.status === 'pending').length); const ungroupedContacts = computed(() => contacts.value.filter(c => !c.group_id || c.group_id === 0)); function getGroupContacts(groupId: number): Contact[] { return contacts.value.filter(c => c.group_id === groupId) }
|
const contacts = computed(() => chatStore.contacts); const friendRequestCount = computed(() => contactStore.friendRequests.filter(r => r.status === 'pending').length); const ungroupedContacts = computed(() => contacts.value.filter(c => !c.group_id || c.group_id === 0)); function getGroupContacts(groupId: number): Contact[] { return contacts.value.filter(c => c.group_id === groupId) }
|
||||||
const letters = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '#']; const alphabetGroupedContacts = computed(() => { const groups: Record<string, Contact[]> = {}; contacts.value.forEach(contact => { const name = contact.remark_name || contact.user?.name || ''; const firstChar = name.charAt(0).toUpperCase(); const letter = /[A-Z]/.test(firstChar) ? firstChar : '#'; if (!groups[letter]) groups[letter] = []; groups[letter].push(contact) }); return Object.keys(groups).sort((a, b) => (a === '#' ? 1 : b === '#' ? -1 : a.localeCompare(b))).map(letter => ({ letter, contacts: groups[letter] })) });
|
const letters = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '#']; const alphabetGroupedContacts = computed(() => { const groups: Record<string, Contact[]> = {}; contacts.value.forEach(contact => { const name = contact.remark_name || contact.user?.name || ''; const firstChar = name.charAt(0).toUpperCase(); const letter = /[A-Z]/.test(firstChar) ? firstChar : '#'; if (!groups[letter]) groups[letter] = []; groups[letter].push(contact) }); return Object.keys(groups).sort((a, b) => (a === '#' ? 1 : b === '#' ? -1 : a.localeCompare(b))).map(letter => ({ letter, contacts: groups[letter] })) });
|
||||||
const createdGroups = computed(() => groupChats.value.filter(g => g.category === 'created')); const managedGroups = computed(() => groupChats.value.filter(g => g.category === 'managed')); const joinedGroups = computed(() => groupChats.value.filter(g => g.category === 'joined'));
|
const createdGroups = computed(() => groupChats.value.filter(g => g.category === 'created')); const managedGroups = computed(() => groupChats.value.filter(g => g.category === 'managed')); const joinedGroups = computed(() => groupChats.value.filter(g => g.category === 'joined'));
|
||||||
@@ -312,7 +314,17 @@ function goFriendRequests() { uni.navigateTo({ url: '/pages/contact/requests' })
|
|||||||
function goGroupNotify() { uni.navigateTo({ url: '/pages/group/notify' }) }
|
function goGroupNotify() { uni.navigateTo({ url: '/pages/group/notify' }) }
|
||||||
function goContactDetail(contact: Contact) { uni.navigateTo({ url: `/pages/contact/detail?id=${contact.id}` }) }
|
function goContactDetail(contact: Contact) { uni.navigateTo({ url: `/pages/contact/detail?id=${contact.id}` }) }
|
||||||
function goGroupChat(group: GroupChat) { uni.navigateTo({ url: `/pages/chat/index?roomId=${group.room_id}&name=${encodeURIComponent(group.room_name || '群聊')}` }) }
|
function goGroupChat(group: GroupChat) { uni.navigateTo({ url: `/pages/chat/index?roomId=${group.room_id}&name=${encodeURIComponent(group.room_name || '群聊')}` }) }
|
||||||
function scrollToLetter(letter: string) { console.log(letter) }
|
function scrollToLetter(letter: string) {
|
||||||
|
// 检查是否有该字母的分组
|
||||||
|
const hasGroup = alphabetGroupedContacts.value.some(g => g.letter === letter)
|
||||||
|
if (hasGroup) {
|
||||||
|
// 清空后再设置,确保重复点击同一字母也能触发滚动
|
||||||
|
scrollIntoViewId.value = ''
|
||||||
|
setTimeout(() => {
|
||||||
|
scrollIntoViewId.value = `letter-${letter}`
|
||||||
|
}, 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
function handleContactLongPress(c: Contact) { selectedContact.value = c; remarkName.value = c.remark_name || ''; showContactActions.value = true }
|
function handleContactLongPress(c: Contact) { selectedContact.value = c; remarkName.value = c.remark_name || ''; showContactActions.value = true }
|
||||||
function onContactActionSelect(a: { value: string }) { showContactActions.value = false; if(a.value === 'remark') showRemarkModal.value = true; else if(a.value === 'move') showMoveGroupModal.value = true; else if(a.value === 'delete') handleDeleteContact() }
|
function onContactActionSelect(a: { value: string }) { showContactActions.value = false; if(a.value === 'remark') showRemarkModal.value = true; else if(a.value === 'move') showMoveGroupModal.value = true; else if(a.value === 'delete') handleDeleteContact() }
|
||||||
async function handleSaveRemark() { if(!selectedContact.value) return; savingRemark.value=true; try { await contactApi.updateContact(selectedContact.value.id.toString(), {remark_name: remarkName.value}); const idx = chatStore.contacts.findIndex(c => c.id === selectedContact.value?.id); if(idx>-1) chatStore.contacts[idx].remark_name = remarkName.value; showRemarkModal.value=false; toast.success('备注已修改') } catch(e){toast.error('失败')} finally {savingRemark.value=false} }
|
async function handleSaveRemark() { if(!selectedContact.value) return; savingRemark.value=true; try { await contactApi.updateContact(selectedContact.value.id.toString(), {remark_name: remarkName.value}); const idx = chatStore.contacts.findIndex(c => c.id === selectedContact.value?.id); if(idx>-1) chatStore.contacts[idx].remark_name = remarkName.value; showRemarkModal.value=false; toast.success('备注已修改') } catch(e){toast.error('失败')} finally {savingRemark.value=false} }
|
||||||
@@ -330,18 +342,20 @@ async function handleDeleteGroup() { try { await messageBox.confirm({title:'提
|
|||||||
// 页面容器 - 浅色模式 (与设计稿一致)
|
// 页面容器 - 浅色模式 (与设计稿一致)
|
||||||
// ==========================================
|
// ==========================================
|
||||||
.page-container {
|
.page-container {
|
||||||
--bg-page: #ffffff;
|
--bg-page: #ffffff; // 页面背景
|
||||||
--bg-surface: #ffffff;
|
--bg-surface: #ffffff; // 卡片背景
|
||||||
--bg-highlight: #f5f5f5;
|
--bg-highlight: #f9fafb; // gray-50 (hover)
|
||||||
--bg-group: rgba(0, 0, 0, 0.02);
|
--bg-group: #f9fafb; // gray-50 (分组容器)
|
||||||
--text-primary: #1f2937;
|
--text-primary: #1f2937; // gray-900
|
||||||
--text-secondary: #6b7280;
|
--text-secondary: #6b7280; // gray-500
|
||||||
--text-tertiary: #9ca3af;
|
--text-tertiary: #9ca3af; // gray-400
|
||||||
--border-color: rgba(0, 0, 0, 0.05);
|
--border-color: #f3f4f6; // gray-100
|
||||||
--color-brand: #4F46E5;
|
--color-brand: #4F46E5; // indigo-600
|
||||||
--tab-bg: #f1f1f1;
|
--tab-bg: #f3f4f6; // gray-100
|
||||||
--tab-active-bg: #ffffff;
|
--tab-active-bg: #ffffff; // white + shadow-sm
|
||||||
--search-bg: #f1f1f1;
|
--search-bg: #f3f4f6; // gray-100
|
||||||
|
--index-bg: rgba(243, 244, 246, 0.8); // gray-100/80
|
||||||
|
--index-hover: rgba(79, 70, 229, 0.1); // indigo-100
|
||||||
|
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: var(--bg-page);
|
background: var(--bg-page);
|
||||||
@@ -352,18 +366,20 @@ async function handleDeleteGroup() { try { await messageBox.confirm({title:'提
|
|||||||
// 深色模式 - Warm Stone (与设计稿一致)
|
// 深色模式 - Warm Stone (与设计稿一致)
|
||||||
// ==========================================
|
// ==========================================
|
||||||
.page-container.dark {
|
.page-container.dark {
|
||||||
--bg-page: #1c1917;
|
--bg-page: #1c1917; // warm-900
|
||||||
--bg-surface: #1c1917;
|
--bg-surface: #1c1917; // warm-900
|
||||||
--bg-highlight: #44403c;
|
--bg-highlight: #292524; // warm-800
|
||||||
--bg-group: rgba(255, 255, 255, 0.03);
|
--bg-group: rgba(41, 37, 36, 0.4); // warm-800/40
|
||||||
--text-primary: #f5f5f4;
|
--text-primary: #f5f5f4; // warm-100
|
||||||
--text-secondary: #e7e5e4;
|
--text-secondary: #a8a29e; // warm-400
|
||||||
--text-tertiary: #78716c;
|
--text-tertiary: #78716c; // warm-500
|
||||||
--border-color: #44403c;
|
--border-color: rgba(41, 37, 36, 0.5); // warm-800/50
|
||||||
--color-brand: #f97316;
|
--color-brand: #f97316; // orange-500
|
||||||
--tab-bg: #292524;
|
--tab-bg: #292524; // warm-800
|
||||||
--tab-active-bg: #44403c;
|
--tab-active-bg: #44403c; // warm-700
|
||||||
--search-bg: #292524;
|
--search-bg: #292524; // warm-800
|
||||||
|
--index-bg: rgba(41, 37, 36, 0.8); // warm-800/80
|
||||||
|
--index-hover: rgba(154, 52, 18, 0.3); // orange-900/30
|
||||||
}
|
}
|
||||||
|
|
||||||
// 动画
|
// 动画
|
||||||
@@ -754,23 +770,22 @@ async function handleDeleteGroup() { try { await messageBox.confirm({title:'提
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A-Z索引条
|
// A-Z索引条 (设计稿: 带模糊背景)
|
||||||
.index-bar {
|
.index-bar {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
right: 16rpx;
|
right: 20rpx; // 设计稿: right-[20px] = 40rpx/2
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
background: rgba(0, 0, 0, 0.04);
|
background: var(--index-bg);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
-webkit-backdrop-filter: blur(8px);
|
||||||
border-radius: 20rpx;
|
border-radius: 20rpx;
|
||||||
padding: 16rpx 8rpx;
|
padding: 16rpx 8rpx;
|
||||||
z-index: 101;
|
z-index: 101;
|
||||||
|
border: 1rpx solid var(--border-color);
|
||||||
.dark & {
|
|
||||||
background: rgba(255, 255, 255, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.index-item {
|
.index-item {
|
||||||
font-size: 20rpx;
|
font-size: 20rpx; // 设计稿: text-[10px]
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
width: 32rpx;
|
width: 32rpx;
|
||||||
height: 32rpx;
|
height: 32rpx;
|
||||||
@@ -783,11 +798,7 @@ async function handleDeleteGroup() { try { await messageBox.confirm({title:'提
|
|||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
color: var(--color-brand);
|
color: var(--color-brand);
|
||||||
background: rgba(79, 70, 229, 0.1);
|
background: var(--index-hover);
|
||||||
|
|
||||||
.dark & {
|
|
||||||
background: rgba(249, 115, 22, 0.15);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,15 +9,12 @@
|
|||||||
<!-- 左侧: 头像 + 标题 -->
|
<!-- 左侧: 头像 + 标题 -->
|
||||||
<view class="nav-left" @click="openDrawer">
|
<view class="nav-left" @click="openDrawer">
|
||||||
<view class="avatar-container">
|
<view class="avatar-container">
|
||||||
<image
|
<app-avatar
|
||||||
v-if="user?.avatar"
|
:src="user?.avatar"
|
||||||
class="nav-avatar"
|
:name="user?.name"
|
||||||
:src="resolveImageUrl(user.avatar)"
|
:size="72"
|
||||||
mode="aspectFill"
|
radius="16rpx"
|
||||||
/>
|
/>
|
||||||
<view v-else class="nav-avatar avatar-placeholder">
|
|
||||||
{{ (user?.name || '?').charAt(0) }}
|
|
||||||
</view>
|
|
||||||
<view class="online-dot" v-if="!loading"></view>
|
<view class="online-dot" v-if="!loading"></view>
|
||||||
</view>
|
</view>
|
||||||
<text class="nav-title">消息</text>
|
<text class="nav-title">消息</text>
|
||||||
@@ -307,20 +304,21 @@ async function logout() {
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// 页面容器 - 浅色模式变量
|
// 页面容器 - 浅色模式变量 (与设计稿完全一致)
|
||||||
// ==========================================
|
// ==========================================
|
||||||
.page-container {
|
.page-container {
|
||||||
--bg-page: #ffffff;
|
--bg-page: #ffffff; // 页面背景
|
||||||
--bg-surface: #ffffff;
|
--bg-surface: #ffffff; // 卡片背景
|
||||||
--bg-hover: #f5f5f5;
|
--bg-hover: #f9fafb; // gray-50 (设计稿: 会话项悬浮)
|
||||||
--bg-top: rgba(0, 0, 0, 0.02);
|
--bg-top: rgba(249, 250, 251, 0.7); // gray-50/70 (设计稿: 会话置顶背景)
|
||||||
--text-primary: #1f2937;
|
--text-primary: #1f2937; // gray-900 (设计稿: 会话名称)
|
||||||
--text-secondary: #6b7280;
|
--text-secondary: #6b7280; // gray-500 (设计稿: 会话消息)
|
||||||
--text-tertiary: #9ca3af;
|
--text-tertiary: #9ca3af; // gray-400 (设计稿: 会话时间)
|
||||||
|
--text-placeholder: #9ca3af; // gray-400 (设计稿: 搜索placeholder)
|
||||||
--border-color: rgba(0, 0, 0, 0.05);
|
--border-color: rgba(0, 0, 0, 0.05);
|
||||||
--nav-bg: rgba(255, 255, 255, 0.9);
|
--nav-bg: rgba(255, 255, 255, 0.9);
|
||||||
--color-brand: #4F46E5;
|
--color-brand: #4F46E5; // indigo-600
|
||||||
--search-bg: #f1f1f1;
|
--search-bg: #f1f1f1; // gray-100
|
||||||
--search-focus: rgba(79, 70, 229, 0.1);
|
--search-focus: rgba(79, 70, 229, 0.1);
|
||||||
|
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
@@ -329,20 +327,21 @@ async function logout() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// 深色模式 - Warm Stone 暖色调
|
// 深色模式 - Warm Stone 暖色调 (与设计稿完全一致)
|
||||||
// ==========================================
|
// ==========================================
|
||||||
.page-container.dark {
|
.page-container.dark {
|
||||||
--bg-page: #1c1917;
|
--bg-page: #1c1917; // warm-900
|
||||||
--bg-surface: #1c1917;
|
--bg-surface: #1c1917; // warm-900
|
||||||
--bg-hover: #44403c;
|
--bg-hover: #292524; // warm-800 (设计稿: 会话项悬浮)
|
||||||
--bg-top: rgba(255, 255, 255, 0.03);
|
--bg-top: rgba(41, 37, 36, 0.3); // warm-800/30 (设计稿: 会话置顶背景)
|
||||||
--text-primary: #f5f5f4;
|
--text-primary: #f5f5f4; // warm-100 (设计稿: 会话名称)
|
||||||
--text-secondary: #e7e5e4;
|
--text-secondary: #a8a29e; // warm-400 (设计稿: 会话消息)
|
||||||
--text-tertiary: #78716c;
|
--text-tertiary: #78716c; // warm-500 (设计稿: 会话时间)
|
||||||
--border-color: #44403c;
|
--text-placeholder: #57534e; // warm-600 (设计稿: 搜索placeholder)
|
||||||
|
--border-color: #44403c; // warm-700
|
||||||
--nav-bg: rgba(28, 25, 23, 0.9);
|
--nav-bg: rgba(28, 25, 23, 0.9);
|
||||||
--color-brand: #f97316;
|
--color-brand: #f97316; // orange-500
|
||||||
--search-bg: #292524;
|
--search-bg: #292524; // warm-800
|
||||||
--search-focus: rgba(249, 115, 22, 0.1);
|
--search-focus: rgba(249, 115, 22, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -527,7 +526,7 @@ async function logout() {
|
|||||||
margin-left: 16rpx;
|
margin-left: 16rpx;
|
||||||
|
|
||||||
&::placeholder {
|
&::placeholder {
|
||||||
color: var(--text-tertiary);
|
color: var(--text-placeholder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,15 +11,12 @@
|
|||||||
<view class="moment-content">
|
<view class="moment-content">
|
||||||
<!-- 用户信息 -->
|
<!-- 用户信息 -->
|
||||||
<view class="header">
|
<view class="header">
|
||||||
<image
|
<app-avatar
|
||||||
v-if="moment.user?.avatar"
|
:src="moment.user?.avatar"
|
||||||
class="avatar"
|
:name="moment.user?.name"
|
||||||
:src="resolveImageUrl(moment.user.avatar)"
|
:size="80"
|
||||||
mode="aspectFill"
|
round
|
||||||
/>
|
/>
|
||||||
<view v-else class="avatar avatar-placeholder">
|
|
||||||
{{ moment.user?.name?.charAt(0) || '?' }}
|
|
||||||
</view>
|
|
||||||
<view class="user-info">
|
<view class="user-info">
|
||||||
<text class="name">{{ moment.user?.name || '未知' }}</text>
|
<text class="name">{{ moment.user?.name || '未知' }}</text>
|
||||||
<text class="time">{{ formatTime(moment.created_at) }}</text>
|
<text class="time">{{ formatTime(moment.created_at) }}</text>
|
||||||
@@ -79,15 +76,12 @@
|
|||||||
:key="comment.id"
|
:key="comment.id"
|
||||||
class="comment-item"
|
class="comment-item"
|
||||||
>
|
>
|
||||||
<image
|
<app-avatar
|
||||||
v-if="comment.user?.avatar"
|
:src="comment.user?.avatar"
|
||||||
class="avatar"
|
:name="comment.user?.name"
|
||||||
:src="resolveImageUrl(comment.user.avatar)"
|
:size="64"
|
||||||
mode="aspectFill"
|
round
|
||||||
/>
|
/>
|
||||||
<view v-else class="avatar avatar-placeholder">
|
|
||||||
{{ comment.user?.name?.charAt(0) || '?' }}
|
|
||||||
</view>
|
|
||||||
<view class="comment-content">
|
<view class="comment-content">
|
||||||
<view class="comment-header">
|
<view class="comment-header">
|
||||||
<text class="name">{{ comment.user?.name || '未知' }}</text>
|
<text class="name">{{ comment.user?.name || '未知' }}</text>
|
||||||
|
|||||||
@@ -308,30 +308,32 @@ function getImageSize(moment: Moment): string {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
// 页面容器 - 浅色模式
|
// 页面容器 - 浅色模式 (与设计稿完全一致)
|
||||||
.moments-page {
|
.moments-page {
|
||||||
--bg-page: #ffffff;
|
--bg-page: #ffffff; // 页面背景
|
||||||
--bg-nav-rgb: 255, 255, 255;
|
--bg-nav-rgb: 255, 255, 255; // 导航栏背景 RGB
|
||||||
--bg-detail: #f7f7f7;
|
--bg-detail: #f3f4f6; // gray-100 (评论区背景)
|
||||||
--text-main: #1d1d1f;
|
--text-main: #1f2937; // gray-900 (动态内容)
|
||||||
--text-blue: #576b95;
|
--text-secondary: #9ca3af; // gray-400 (时间)
|
||||||
--color-brand: #4F46E5;
|
--text-blue: #576b95; // 链接色 (作者名、点赞、评论)
|
||||||
--border-color: rgba(0, 0, 0, 0.05);
|
--color-brand: #4F46E5; // indigo-600
|
||||||
|
--border-color: #f3f4f6; // gray-100
|
||||||
|
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
background: var(--bg-page);
|
background: var(--bg-page);
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 深色模式 - Warm Stone
|
// 深色模式 - Warm Stone (与设计稿完全一致)
|
||||||
.moments-page.dark {
|
.moments-page.dark {
|
||||||
--bg-page: #1c1917;
|
--bg-page: #1c1917; // warm-900
|
||||||
--bg-nav-rgb: 28, 25, 23;
|
--bg-nav-rgb: 28, 25, 23; // warm-900 RGB
|
||||||
--bg-detail: #292524;
|
--bg-detail: rgba(41, 37, 36, 0.6); // warm-800/60 (评论区背景)
|
||||||
--text-main: #f5f5f4;
|
--text-main: #f5f5f4; // warm-100 (动态内容)
|
||||||
--text-blue: #7e95c5;
|
--text-secondary: #78716c; // warm-500 (时间)
|
||||||
--color-brand: #f97316;
|
--text-blue: #7e95c5; // blue-400 (链接色)
|
||||||
--border-color: rgba(255, 255, 255, 0.1);
|
--color-brand: #f97316; // orange-500
|
||||||
|
--border-color: #44403c; // warm-700
|
||||||
}
|
}
|
||||||
|
|
||||||
// 动画
|
// 动画
|
||||||
@@ -577,8 +579,7 @@ function getImageSize(moment: Moment): string {
|
|||||||
|
|
||||||
.time {
|
.time {
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
color: var(--text-blue);
|
color: var(--text-secondary);
|
||||||
opacity: 0.6;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-btn {
|
.action-btn {
|
||||||
@@ -619,7 +620,11 @@ function getImageSize(moment: Moment): string {
|
|||||||
height: 0;
|
height: 0;
|
||||||
border-left: 12rpx solid transparent;
|
border-left: 12rpx solid transparent;
|
||||||
border-right: 12rpx solid transparent;
|
border-right: 12rpx solid transparent;
|
||||||
border-bottom: 12rpx solid var(--bg-detail);
|
border-bottom: 12rpx solid #f3f4f6; // gray-100
|
||||||
|
|
||||||
|
.dark & {
|
||||||
|
border-bottom-color: #292524; // 设计稿: warm-800
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.likes-list {
|
.likes-list {
|
||||||
|
|||||||
@@ -23,16 +23,12 @@
|
|||||||
:class="{ unread: !item.is_read }"
|
:class="{ unread: !item.is_read }"
|
||||||
@click="goMomentDetail(item)"
|
@click="goMomentDetail(item)"
|
||||||
>
|
>
|
||||||
<wd-img
|
<app-avatar
|
||||||
v-if="item.from_user?.avatar"
|
:src="item.from_user?.avatar"
|
||||||
:src="resolveImageUrl(item.from_user.avatar)"
|
:name="item.from_user?.name"
|
||||||
width="80rpx"
|
:size="80"
|
||||||
height="80rpx"
|
|
||||||
radius="8rpx"
|
radius="8rpx"
|
||||||
/>
|
/>
|
||||||
<view v-else class="avatar-placeholder" :style="{ background: generateColor(item.from_user?.name || '') }">
|
|
||||||
{{ item.from_user?.name?.charAt(0) || '?' }}
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="notify-content">
|
<view class="notify-content">
|
||||||
<view class="notify-header">
|
<view class="notify-header">
|
||||||
|
|||||||
@@ -49,8 +49,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { useTheme } from '@/composables/useTheme'
|
import { useTheme } from '@/composables/useTheme'
|
||||||
|
import { useAuthStore } from '@/stores'
|
||||||
|
import * as userApi from '@/api/modules/user'
|
||||||
|
|
||||||
const { isDark } = useTheme()
|
const { isDark } = useTheme()
|
||||||
|
const authStore = useAuthStore()
|
||||||
|
const saving = ref(false)
|
||||||
const field = ref('')
|
const field = ref('')
|
||||||
const value = ref('')
|
const value = ref('')
|
||||||
const placeholder = ref('')
|
const placeholder = ref('')
|
||||||
@@ -89,13 +93,42 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
function save() {
|
async function save() {
|
||||||
if (!value.value.trim()) {
|
if (!value.value.trim()) {
|
||||||
uni.showToast({ title: '内容不能为空', icon: 'none' })
|
uni.showToast({ title: '内容不能为空', icon: 'none' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (saving.value) return
|
||||||
|
saving.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const userId = authStore.user?.id
|
||||||
|
if (!userId) {
|
||||||
|
uni.showToast({ title: '用户信息异常', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建更新数据
|
||||||
|
const updates: Record<string, string> = {}
|
||||||
|
updates[field.value] = value.value.trim()
|
||||||
|
|
||||||
|
// 调用 API 更新用户信息
|
||||||
|
const updatedUser = await userApi.updateUser({ id: userId, updates })
|
||||||
|
|
||||||
|
// 更新本地 store
|
||||||
|
if (authStore.user) {
|
||||||
|
const newUserInfo = { ...authStore.user, ...updates }
|
||||||
|
authStore.updateUserInfo(newUserInfo)
|
||||||
|
}
|
||||||
|
|
||||||
uni.showToast({ title: '保存成功', icon: 'success' })
|
uni.showToast({ title: '保存成功', icon: 'success' })
|
||||||
setTimeout(() => { uni.navigateBack() }, 1500)
|
setTimeout(() => { uni.navigateBack() }, 1500)
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('保存失败:', error)
|
||||||
|
uni.showToast({ title: error.message || '保存失败', icon: 'none' })
|
||||||
|
} finally {
|
||||||
|
saving.value = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -5,12 +5,7 @@
|
|||||||
<!-- 头部大卡片 -->
|
<!-- 头部大卡片 -->
|
||||||
<view class="profile-header">
|
<view class="profile-header">
|
||||||
<view class="header-top">
|
<view class="header-top">
|
||||||
<view class="back-btn" @click="goBack">
|
<text class="page-title">我的</text>
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path d="M15 18l-6-6 6-6"/>
|
|
||||||
</svg>
|
|
||||||
</view>
|
|
||||||
<text class="page-title">个人资料</text>
|
|
||||||
<view class="icon-btn" @click="uni.navigateTo({ url: '/pages/settings/index' })">
|
<view class="icon-btn" @click="uni.navigateTo({ url: '/pages/settings/index' })">
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<circle cx="12" cy="12" r="3"/>
|
<circle cx="12" cy="12" r="3"/>
|
||||||
@@ -93,6 +88,7 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<app-tab-bar current="profile" />
|
||||||
</view>
|
</view>
|
||||||
</wd-config-provider>
|
</wd-config-provider>
|
||||||
</template>
|
</template>
|
||||||
@@ -102,11 +98,11 @@ import { computed } from 'vue'
|
|||||||
import { useTheme } from '@/composables/useTheme'
|
import { useTheme } from '@/composables/useTheme'
|
||||||
import { useAuthStore } from '@/stores'
|
import { useAuthStore } from '@/stores'
|
||||||
import AppAvatar from '@/components/common/AppAvatar.vue'
|
import AppAvatar from '@/components/common/AppAvatar.vue'
|
||||||
|
import AppTabBar from '@/components/common/AppTabBar.vue'
|
||||||
|
|
||||||
const { isDark } = useTheme()
|
const { isDark } = useTheme()
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
const userInfo = computed(() => authStore.userInfo || {})
|
const userInfo = computed(() => authStore.userInfo || {})
|
||||||
function goBack() { uni.navigateBack() }
|
|
||||||
function goProfileEdit() { uni.navigateTo({ url: '/pages/profile/edit' }) }
|
function goProfileEdit() { uni.navigateTo({ url: '/pages/profile/edit' }) }
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -123,7 +119,7 @@ function goProfileEdit() { uni.navigateTo({ url: '/pages/profile/edit' }) }
|
|||||||
|
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: var(--bg-page);
|
background: var(--bg-page);
|
||||||
padding-bottom: 80rpx;
|
padding-bottom: 200rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 深色模式 - Warm Stone
|
// 深色模式 - Warm Stone
|
||||||
@@ -169,30 +165,11 @@ function goProfileEdit() { uni.navigateTo({ url: '/pages/profile/edit' }) }
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 48rpx;
|
margin-bottom: 48rpx;
|
||||||
|
|
||||||
.back-btn {
|
|
||||||
width: 72rpx;
|
|
||||||
height: 72rpx;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 50%;
|
|
||||||
transition: all 0.15s;
|
|
||||||
|
|
||||||
svg {
|
|
||||||
width: 40rpx;
|
|
||||||
height: 40rpx;
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
background: var(--border-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-title {
|
.page-title {
|
||||||
font-size: 36rpx;
|
font-size: 42rpx;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
|
letter-spacing: -0.5rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-btn {
|
.icon-btn {
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ function clearHistory() {
|
|||||||
function goBack() { uni.navigateBack() }
|
function goBack() { uni.navigateBack() }
|
||||||
|
|
||||||
function goDetail(item: User) {
|
function goDetail(item: User) {
|
||||||
uni.navigateTo({ url: `/pages/contact/detail?id=${item.id}` })
|
uni.navigateTo({ url: `/pages/contact/detail?userId=${item.id}` })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 高亮匹配文字
|
// 高亮匹配文字
|
||||||
|
|||||||
@@ -185,27 +185,27 @@ function handleClearCache() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
// 页面容器 - 浅色模式
|
// 页面容器 - 浅色模式 (与设计稿完全一致)
|
||||||
.settings-page {
|
.settings-page {
|
||||||
--bg-page: #f7f8fa;
|
--bg-page: #F5F7FA; // 设计稿: bg-[#F5F7FA]
|
||||||
--bg-surface: #ffffff;
|
--bg-surface: #ffffff; // 卡片背景
|
||||||
--text-primary: #1d1d1f;
|
--text-primary: #1f2937; // gray-800 (列表项文字)
|
||||||
--text-secondary: #6b7280;
|
--text-secondary: #9ca3af; // gray-400 (分组标题)
|
||||||
--text-tertiary: #9ca3af;
|
--text-tertiary: #9ca3af; // gray-400 (值/箭头)
|
||||||
--border-color: rgba(0, 0, 0, 0.05);
|
--border-color: #f9fafb; // gray-50
|
||||||
|
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: var(--bg-page);
|
background: var(--bg-page);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 深色模式 - Warm Stone
|
// 深色模式 - Warm Stone (与设计稿完全一致)
|
||||||
.settings-page.dark {
|
.settings-page.dark {
|
||||||
--bg-page: #1c1917;
|
--bg-page: #121212; // 设计稿: bg-[#121212]
|
||||||
--bg-surface: #292524;
|
--bg-surface: #1c1917; // warm-900 (卡片背景)
|
||||||
--text-primary: #f5f5f4;
|
--text-primary: #e7e5e4; // warm-200 (列表项文字)
|
||||||
--text-secondary: #e7e5e4;
|
--text-secondary: #78716c; // warm-500 (分组标题)
|
||||||
--text-tertiary: #78716c;
|
--text-tertiary: #9ca3af; // gray-400 (值/箭头)
|
||||||
--border-color: rgba(255, 255, 255, 0.1);
|
--border-color: rgba(41, 37, 36, 0.5); // warm-800/50
|
||||||
}
|
}
|
||||||
|
|
||||||
// 动画
|
// 动画
|
||||||
@@ -300,7 +300,11 @@ function handleClearCache() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
background: var(--border-color);
|
background: #f9fafb; // gray-50
|
||||||
|
|
||||||
|
.dark & {
|
||||||
|
background: #292524; // warm-800
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-box {
|
.icon-box {
|
||||||
|
|||||||
Reference in New Issue
Block a user