修复一些问题
This commit is contained in:
@@ -133,7 +133,7 @@ function handleLogout() {
|
|||||||
background: rgba(0, 0, 0, 0.2);
|
background: rgba(0, 0, 0, 0.2);
|
||||||
backdrop-filter: blur(2px);
|
backdrop-filter: blur(2px);
|
||||||
-webkit-backdrop-filter: blur(2px);
|
-webkit-backdrop-filter: blur(2px);
|
||||||
z-index: 1000;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mask-enter {
|
.mask-enter {
|
||||||
@@ -156,7 +156,7 @@ function handleLogout() {
|
|||||||
width: 75%;
|
width: 75%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: var(--drawer-bg);
|
background: var(--drawer-bg);
|
||||||
z-index: 1001;
|
z-index: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
box-shadow: 16rpx 0 48rpx rgba(0, 0, 0, 0.1);
|
box-shadow: 16rpx 0 48rpx rgba(0, 0, 0, 0.1);
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ function switchTab(name: string) {
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
z-index: 10;
|
z-index: 1;
|
||||||
height: 168rpx;
|
height: 168rpx;
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
border-top: 1rpx solid rgba(0, 0, 0, 0.05);
|
border-top: 1rpx solid rgba(0, 0, 0, 0.05);
|
||||||
|
|||||||
@@ -171,7 +171,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted, onUnmounted, nextTick, getCurrentInstance } from 'vue'
|
import { ref, computed, onMounted, onUnmounted, nextTick, getCurrentInstance } from 'vue'
|
||||||
import { onLoad } from '@dcloudio/uni-app'
|
import { onLoad, onReady } from '@dcloudio/uni-app'
|
||||||
import { useAuthStore, useChatStore, useConversationStore } from '@/stores'
|
import { useAuthStore, useChatStore, useConversationStore } from '@/stores'
|
||||||
import { wsManager } from '@/api/websocket'
|
import { wsManager } from '@/api/websocket'
|
||||||
import * as messageApi from '@/api/modules/message'
|
import * as messageApi from '@/api/modules/message'
|
||||||
@@ -219,8 +219,12 @@ const instance = getCurrentInstance()
|
|||||||
// 状态定义
|
// 状态定义
|
||||||
const roomId = ref(''); const targetId = ref(''); const chatName = ref(''); const targetAvatar = ref('');
|
const roomId = ref(''); const targetId = ref(''); const chatName = ref(''); const targetAvatar = ref('');
|
||||||
const inputText = ref(''); const messages = ref<ChatMessage[]>([]); const scrollToId = ref('');
|
const inputText = ref(''); const messages = ref<ChatMessage[]>([]); const scrollToId = ref('');
|
||||||
const scrollTop = ref(0); // 增加 scrollTop 控制
|
const scrollTop = ref(0)
|
||||||
const scrollWithAnimation = ref(false); const loadingMore = ref(false); const hasMore = ref(true);
|
const scrollTopSeed = ref(0)
|
||||||
|
const scrollWithAnimation = ref(false)
|
||||||
|
const loadingMore = ref(false)
|
||||||
|
const hasMore = ref(true)
|
||||||
|
let scrollRetryTimers: ReturnType<typeof setTimeout>[] = []
|
||||||
const playingAudioId = ref<number | null>(null); const page = ref(1);
|
const playingAudioId = ref<number | null>(null); const page = ref(1);
|
||||||
const showFileConfirm = ref(false); const uploading = ref(false); const pendingFile = ref<{ path: string; name: string; size: number; type: 'image' | 'video' | 'file'; messageType: number }>({ path: '', name: '', size: 0, type: 'file', messageType: 8 });
|
const showFileConfirm = ref(false); const uploading = ref(false); const pendingFile = ref<{ path: string; name: string; size: number; type: 'image' | 'video' | 'file'; messageType: number }>({ path: '', name: '', size: 0, type: 'file', messageType: 8 });
|
||||||
const showMsgActions = ref(false); const selectedMessage = ref<ChatMessage | null>(null);
|
const showMsgActions = ref(false); const selectedMessage = ref<ChatMessage | null>(null);
|
||||||
@@ -248,42 +252,52 @@ onLoad(async (options: any) => {
|
|||||||
await loadGroupMembers()
|
await loadGroupMembers()
|
||||||
}
|
}
|
||||||
|
|
||||||
loadMessages();
|
loadMessages()
|
||||||
});
|
})
|
||||||
|
|
||||||
onUnmounted(() => { wsManager.offMessage(handleNewMessage); stopAudio(); });
|
onReady(() => {
|
||||||
|
scrollToBottom(false)
|
||||||
|
})
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
onUnmounted(() => {
|
||||||
// 1. 终极滚动修复:组合 scroll-into-view 和 scrollTop
|
clearScrollRetries()
|
||||||
// ----------------------------------------------------------------------
|
wsManager.offMessage(handleNewMessage)
|
||||||
function scrollToBottom(animated = true) {
|
stopAudio()
|
||||||
scrollWithAnimation.value = animated
|
})
|
||||||
|
|
||||||
// 第一招:使用 scroll-into-view 锚点
|
/** 清除滚动重试定时器,避免页面卸载后仍触发 */
|
||||||
// 先清空,确保下次设置能触发变化
|
function clearScrollRetries() {
|
||||||
scrollToId.value = ''
|
scrollRetryTimers.forEach(t => clearTimeout(t))
|
||||||
|
scrollRetryTimers = []
|
||||||
|
}
|
||||||
|
|
||||||
nextTick(() => {
|
/** 执行一次滚底:锚点 msg-bottom + scrollTop 递增 */
|
||||||
setTimeout(() => {
|
function applyScrollToBottom() {
|
||||||
// 尝试1:滚动到最后一条消息
|
|
||||||
if (messages.value.length > 0) {
|
|
||||||
scrollToId.value = `msg-${messages.value[messages.value.length - 1].id}`
|
|
||||||
} else {
|
|
||||||
scrollToId.value = 'msg-bottom'
|
scrollToId.value = 'msg-bottom'
|
||||||
}
|
|
||||||
|
|
||||||
console.log('🚀 触发滚动 ID:', scrollToId.value)
|
|
||||||
|
|
||||||
// 第二招:双保险,直接设置 scrollTop 到一个巨大值
|
|
||||||
// 某些情况下 view 没渲染出来 id 找不到,scrollTop 更可靠
|
|
||||||
uni.createSelectorQuery().in(instance).select('.message-feed').boundingClientRect((res) => {
|
uni.createSelectorQuery().in(instance).select('.message-feed').boundingClientRect((res) => {
|
||||||
if (res && res.height) {
|
if (res && res.height) {
|
||||||
console.log('📏 计算高度滚动:', res.height)
|
scrollTop.value = 0
|
||||||
scrollTop.value = res.height + 1000 // 加个 buffer 确保滚到底
|
nextTick(() => {
|
||||||
|
scrollTopSeed.value++
|
||||||
|
scrollTop.value = res.height + scrollTopSeed.value
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}).exec()
|
}).exec()
|
||||||
|
}
|
||||||
|
|
||||||
}, 150) // 延时稍微加大一点,等待渲染完全
|
/**
|
||||||
|
* 滚动到消息列表底部
|
||||||
|
* 组合 scroll-into-view 与 scrollTop,并在多个延迟点重试以应对图片/群成员渲染
|
||||||
|
*/
|
||||||
|
function scrollToBottom(animated = true, retries = [0, 100, 300]) {
|
||||||
|
scrollWithAnimation.value = animated
|
||||||
|
clearScrollRetries()
|
||||||
|
scrollToId.value = ''
|
||||||
|
retries.forEach((delay) => {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
nextTick(() => applyScrollToBottom())
|
||||||
|
}, delay)
|
||||||
|
scrollRetryTimers.push(timer)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -369,12 +383,10 @@ async function loadMessages() {
|
|||||||
const res = await messageApi.getMessages(roomId.value, 1, 50);
|
const res = await messageApi.getMessages(roomId.value, 1, 50);
|
||||||
const newMsgs = mapMessages((res.data || []).reverse());
|
const newMsgs = mapMessages((res.data || []).reverse());
|
||||||
|
|
||||||
if (newMsgs.length > cached.length || cached.length === 0) {
|
messages.value = newMsgs
|
||||||
messages.value = newMsgs;
|
chatStore.setRoomMessages(roomId.value, messages.value)
|
||||||
chatStore.setRoomMessages(roomId.value, messages.value);
|
scrollToBottom(false)
|
||||||
scrollToBottom(false);
|
hasMore.value = res.data.length >= 50
|
||||||
}
|
|
||||||
hasMore.value = res.data.length >= 50;
|
|
||||||
} catch (error) { console.error('加载消息失败:', error) }
|
} catch (error) { console.error('加载消息失败:', error) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -442,6 +454,7 @@ async function loadGroupMembers() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
groupMembersMap.value = memberMap
|
groupMembersMap.value = memberMap
|
||||||
|
scrollToBottom(false)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('加载群成员失败:', e)
|
console.error('加载群成员失败:', e)
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<wd-config-provider :theme="isDark ? 'dark' : 'light'">
|
<wd-config-provider :theme="isDark ? 'dark' : 'light'">
|
||||||
<view class="page-container" :class="{ dark: isDark }">
|
<view
|
||||||
|
class="page-container"
|
||||||
|
:class="{ dark: isDark }"
|
||||||
|
@touchstart="onDrawerSwipeStart"
|
||||||
|
@touchmove="onDrawerSwipeMove"
|
||||||
|
@touchend="onDrawerSwipeEnd"
|
||||||
|
>
|
||||||
|
|
||||||
<!-- 1. 顶部导航栏 (与设计稿完全一致) -->
|
<!-- 1. 顶部导航栏 (与设计稿完全一致) -->
|
||||||
<view class="custom-navbar">
|
<view class="custom-navbar">
|
||||||
@@ -184,11 +190,13 @@
|
|||||||
:z-index="9999"
|
:z-index="9999"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 侧边抽屉 -->
|
<!-- 侧边抽屉:独立层叠上下文,避免 z-index 过高遮挡全局弹窗 -->
|
||||||
|
<view class="drawer-layer">
|
||||||
<app-drawer
|
<app-drawer
|
||||||
v-model="showDrawer"
|
v-model="showDrawer"
|
||||||
@logout="logout"
|
@logout="logout"
|
||||||
/>
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
<wd-toast />
|
<wd-toast />
|
||||||
<wd-message-box :z-index="11000" />
|
<wd-message-box :z-index="11000" />
|
||||||
@@ -224,6 +232,12 @@ const refreshing = ref(false)
|
|||||||
const showConvActions = ref(false)
|
const showConvActions = ref(false)
|
||||||
const selectedConv = ref<Conversation | null>(null)
|
const selectedConv = ref<Conversation | null>(null)
|
||||||
|
|
||||||
|
/** 左边缘右滑打开抽屉的手势状态 */
|
||||||
|
let swipeStartX = 0
|
||||||
|
let swipeStartY = 0
|
||||||
|
let swipeFromEdge = false
|
||||||
|
let swipeTriggered = false
|
||||||
|
|
||||||
const user = computed(() => authStore.user)
|
const user = computed(() => authStore.user)
|
||||||
const conversations = computed(() => conversationStore.conversations)
|
const conversations = computed(() => conversationStore.conversations)
|
||||||
const loading = computed(() => conversationStore.loading)
|
const loading = computed(() => conversationStore.loading)
|
||||||
@@ -323,6 +337,32 @@ function onPlusMenuSelect(key: string) {
|
|||||||
|
|
||||||
function openDrawer() { showDrawer.value = true }
|
function openDrawer() { showDrawer.value = true }
|
||||||
|
|
||||||
|
/** 从左边缘右滑打开抽屉 */
|
||||||
|
function onDrawerSwipeStart(e: TouchEvent) {
|
||||||
|
if (showDrawer.value) return
|
||||||
|
const touch = e.touches[0]
|
||||||
|
swipeStartX = touch.clientX
|
||||||
|
swipeStartY = touch.clientY
|
||||||
|
swipeFromEdge = touch.clientX < 40
|
||||||
|
swipeTriggered = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDrawerSwipeMove(e: TouchEvent) {
|
||||||
|
if (!swipeFromEdge || showDrawer.value || swipeTriggered) return
|
||||||
|
const touch = e.touches[0]
|
||||||
|
const deltaX = touch.clientX - swipeStartX
|
||||||
|
const deltaY = Math.abs(touch.clientY - swipeStartY)
|
||||||
|
if (deltaX > 60 && deltaY < 30) {
|
||||||
|
swipeTriggered = true
|
||||||
|
openDrawer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDrawerSwipeEnd() {
|
||||||
|
swipeFromEdge = false
|
||||||
|
swipeTriggered = false
|
||||||
|
}
|
||||||
|
|
||||||
async function logout() {
|
async function logout() {
|
||||||
try {
|
try {
|
||||||
await messageBox.confirm({ title: '提示', msg: '确定退出?' })
|
await messageBox.confirm({ title: '提示', msg: '确定退出?' })
|
||||||
@@ -354,6 +394,12 @@ async function logout() {
|
|||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: var(--bg-page);
|
background: var(--bg-page);
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-layer {
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
@@ -401,7 +447,7 @@ async function logout() {
|
|||||||
.custom-navbar {
|
.custom-navbar {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 100;
|
z-index: 1;
|
||||||
// 确保没有任何边框
|
// 确保没有任何边框
|
||||||
border: none;
|
border: none;
|
||||||
|
|
||||||
|
|||||||
@@ -4,128 +4,196 @@
|
|||||||
<!-- 导航栏 -->
|
<!-- 导航栏 -->
|
||||||
<view class="nav-bar">
|
<view class="nav-bar">
|
||||||
<view class="nav-back" @click="goBack">
|
<view class="nav-back" @click="goBack">
|
||||||
|
<!-- #ifdef H5 || APP-PLUS -->
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
<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"/>
|
<path d="M15 18l-6-6 6-6"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
<!-- #endif -->
|
||||||
|
<!-- #ifdef MP-WEIXIN -->
|
||||||
|
<wd-icon name="arrow-left" size="44rpx" />
|
||||||
|
<!-- #endif -->
|
||||||
</view>
|
</view>
|
||||||
<text class="nav-title">{{ pageTitle }}</text>
|
<text class="nav-title">编辑资料</text>
|
||||||
<view class="nav-placeholder"></view>
|
<view class="nav-placeholder"></view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="content-body">
|
<view class="content-body">
|
||||||
<!-- 输入卡片 -->
|
<!-- 头像编辑区 -->
|
||||||
<view class="input-card animate-fade-in-up">
|
<view class="avatar-section animate-fade-in-up">
|
||||||
<view class="input-label">{{ inputLabel }}</view>
|
<view class="avatar-wrap" @click="chooseAvatar">
|
||||||
<textarea
|
<app-avatar :src="form.avatar" :name="form.name" :size="192" round />
|
||||||
v-if="field === 'desc'"
|
<view class="avatar-mask">
|
||||||
v-model="value"
|
<!-- #ifdef H5 || APP-PLUS -->
|
||||||
class="custom-input textarea"
|
<svg viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2">
|
||||||
:placeholder="placeholder"
|
<path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/>
|
||||||
:maxlength="maxLength"
|
<circle cx="12" cy="13" r="4"/>
|
||||||
placeholder-class="p-holder"
|
</svg>
|
||||||
/>
|
<!-- #endif -->
|
||||||
|
<!-- #ifdef MP-WEIXIN -->
|
||||||
|
<wd-icon name="camera" size="64rpx" color="#fff" />
|
||||||
|
<!-- #endif -->
|
||||||
|
</view>
|
||||||
|
<view class="avatar-badge">
|
||||||
|
<!-- #ifdef H5 || APP-PLUS -->
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2">
|
||||||
|
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
|
||||||
|
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
|
||||||
|
</svg>
|
||||||
|
<!-- #endif -->
|
||||||
|
<!-- #ifdef MP-WEIXIN -->
|
||||||
|
<wd-icon name="edit" size="24rpx" color="#fff" />
|
||||||
|
<!-- #endif -->
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<text class="avatar-hint">点击更换头像</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 基础信息 -->
|
||||||
|
<view class="info-card animate-fade-in-up" style="animation-delay: 80ms;">
|
||||||
|
<view class="field-row">
|
||||||
|
<text class="field-label">昵称</text>
|
||||||
<input
|
<input
|
||||||
v-else
|
v-model="form.name"
|
||||||
v-model="value"
|
class="field-input"
|
||||||
class="custom-input"
|
placeholder="请输入昵称"
|
||||||
:placeholder="placeholder"
|
maxlength="20"
|
||||||
:maxlength="maxLength"
|
|
||||||
placeholder-class="p-holder"
|
placeholder-class="p-holder"
|
||||||
/>
|
/>
|
||||||
<text v-if="maxLength" class="counter">{{ value.length }}/{{ maxLength }}</text>
|
</view>
|
||||||
|
<view class="field-row no-border">
|
||||||
|
<text class="field-label">地区</text>
|
||||||
|
<input
|
||||||
|
v-model="form.region"
|
||||||
|
class="field-input"
|
||||||
|
placeholder="请输入地区"
|
||||||
|
maxlength="50"
|
||||||
|
placeholder-class="p-holder"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 个性签名 -->
|
||||||
|
<view class="desc-card animate-fade-in-up" style="animation-delay: 120ms;">
|
||||||
|
<text class="desc-label">个性签名</text>
|
||||||
|
<textarea
|
||||||
|
v-model="form.desc"
|
||||||
|
class="desc-input"
|
||||||
|
placeholder="介绍一下自己吧..."
|
||||||
|
maxlength="100"
|
||||||
|
placeholder-class="p-holder"
|
||||||
|
/>
|
||||||
|
<text class="counter">{{ form.desc.length }}/100</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 保存按钮 -->
|
<!-- 保存按钮 -->
|
||||||
<view class="btn-wrap animate-fade-in-up" style="animation-delay: 100ms;">
|
<view class="btn-wrap animate-fade-in-up" style="animation-delay: 160ms;">
|
||||||
<view class="save-btn" @click="save">
|
<view class="save-btn" :class="{ disabled: saving }" @click="save">
|
||||||
保存
|
{{ saving ? '保存中...' : '保存修改' }}
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<wd-toast />
|
||||||
</view>
|
</view>
|
||||||
</wd-config-provider>
|
</wd-config-provider>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue'
|
import { reactive, ref, onMounted } from 'vue'
|
||||||
import { useTheme } from '@/composables/useTheme'
|
import { useTheme } from '@/composables/useTheme'
|
||||||
import { useAuthStore } from '@/stores'
|
import { useAuthStore } from '@/stores'
|
||||||
import * as userApi from '@/api/modules/user'
|
import * as userApi from '@/api/modules/user'
|
||||||
|
import * as attachmentApi from '@/api/modules/attachment'
|
||||||
|
import { useToast } from 'wot-design-uni'
|
||||||
|
import AppAvatar from '@/components/common/AppAvatar.vue'
|
||||||
|
|
||||||
const { isDark } = useTheme()
|
const { isDark } = useTheme()
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
|
const toast = useToast()
|
||||||
const saving = ref(false)
|
const saving = ref(false)
|
||||||
const field = ref('')
|
const avatarUploading = ref(false)
|
||||||
const value = ref('')
|
|
||||||
const placeholder = ref('')
|
|
||||||
const maxLength = ref(0)
|
|
||||||
const pageTitle = ref('编辑资料')
|
|
||||||
const inputLabel = ref('')
|
|
||||||
|
|
||||||
function goBack() { uni.navigateBack() }
|
/** 表单数据,与后端 User 字段对齐 */
|
||||||
|
const form = reactive({
|
||||||
onMounted(() => {
|
name: '',
|
||||||
const pages = getCurrentPages()
|
avatar: '',
|
||||||
const currentPage = pages[pages.length - 1]
|
desc: '',
|
||||||
const options = (currentPage as any).$page?.options || {}
|
region: ''
|
||||||
field.value = options.field || ''
|
|
||||||
value.value = decodeURIComponent(options.value || '')
|
|
||||||
|
|
||||||
switch (field.value) {
|
|
||||||
case 'name':
|
|
||||||
placeholder.value = '请输入昵称'
|
|
||||||
maxLength.value = 20
|
|
||||||
pageTitle.value = '修改昵称'
|
|
||||||
inputLabel.value = '昵称'
|
|
||||||
break
|
|
||||||
case 'desc':
|
|
||||||
placeholder.value = '请输入个性签名'
|
|
||||||
maxLength.value = 100
|
|
||||||
pageTitle.value = '修改签名'
|
|
||||||
inputLabel.value = '个性签名'
|
|
||||||
break
|
|
||||||
case 'region':
|
|
||||||
placeholder.value = '请输入地区'
|
|
||||||
maxLength.value = 50
|
|
||||||
pageTitle.value = '修改地区'
|
|
||||||
inputLabel.value = '地区'
|
|
||||||
break
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/** 从 store 初始化表单 */
|
||||||
|
function initForm() {
|
||||||
|
const u = authStore.user
|
||||||
|
if (!u) return
|
||||||
|
form.name = u.name || ''
|
||||||
|
form.avatar = u.avatar || ''
|
||||||
|
form.desc = u.desc || ''
|
||||||
|
form.region = u.region || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function goBack() {
|
||||||
|
uni.navigateBack()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
initForm()
|
||||||
|
})
|
||||||
|
|
||||||
|
/** 选择并上传头像,复用群头像上传模式 */
|
||||||
|
function chooseAvatar() {
|
||||||
|
if (avatarUploading.value) return
|
||||||
|
uni.chooseImage({
|
||||||
|
count: 1,
|
||||||
|
sizeType: ['compressed'],
|
||||||
|
sourceType: ['album', 'camera'],
|
||||||
|
success: async (res) => {
|
||||||
|
const filePath = res.tempFilePaths[0]
|
||||||
|
if (!filePath) return
|
||||||
|
avatarUploading.value = true
|
||||||
|
try {
|
||||||
|
toast.loading('上传中...')
|
||||||
|
const attachment = await attachmentApi.uploadAttachment(filePath, 'image')
|
||||||
|
form.avatar = attachment.file_url
|
||||||
|
toast.close()
|
||||||
|
toast.success('头像已更新')
|
||||||
|
} catch (e: any) {
|
||||||
|
toast.close()
|
||||||
|
toast.error(e.message || '头像上传失败')
|
||||||
|
} finally {
|
||||||
|
avatarUploading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 保存全部资料字段 */
|
||||||
async function save() {
|
async function save() {
|
||||||
if (!value.value.trim()) {
|
if (!form.name.trim()) {
|
||||||
uni.showToast({ title: '内容不能为空', icon: 'none' })
|
toast.error('昵称不能为空')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (saving.value) return
|
if (saving.value) return
|
||||||
saving.value = true
|
|
||||||
|
|
||||||
try {
|
|
||||||
const userId = authStore.user?.id
|
const userId = authStore.user?.id
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
uni.showToast({ title: '用户信息异常', icon: 'none' })
|
toast.error('用户信息异常')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
saving.value = true
|
||||||
// 构建更新数据
|
try {
|
||||||
const updates: Record<string, string> = {}
|
const updates = {
|
||||||
updates[field.value] = value.value.trim()
|
name: form.name.trim(),
|
||||||
|
avatar: form.avatar,
|
||||||
// 调用 API 更新用户信息
|
desc: form.desc.trim(),
|
||||||
const updatedUser = await userApi.updateUser({ id: userId, updates })
|
region: form.region.trim()
|
||||||
|
|
||||||
// 更新本地 store
|
|
||||||
if (authStore.user) {
|
|
||||||
const newUserInfo = { ...authStore.user, ...updates }
|
|
||||||
authStore.updateUserInfo(newUserInfo)
|
|
||||||
}
|
}
|
||||||
|
await userApi.updateUser({ id: userId, updates })
|
||||||
uni.showToast({ title: '保存成功', icon: 'success' })
|
const latest = await userApi.getMyInfo()
|
||||||
setTimeout(() => { uni.navigateBack() }, 1500)
|
authStore.updateUserInfo(latest)
|
||||||
} catch (error: any) {
|
toast.success('保存成功')
|
||||||
console.error('保存失败:', error)
|
setTimeout(() => uni.navigateBack(), 1200)
|
||||||
uni.showToast({ title: error.message || '保存失败', icon: 'none' })
|
} catch (e: any) {
|
||||||
|
console.error('保存失败:', e)
|
||||||
|
toast.error(e.message || '保存失败')
|
||||||
} finally {
|
} finally {
|
||||||
saving.value = false
|
saving.value = false
|
||||||
}
|
}
|
||||||
@@ -133,7 +201,6 @@ async function save() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
// 页面容器 - 浅色模式
|
|
||||||
.edit-page {
|
.edit-page {
|
||||||
--bg-page: #f7f8fa;
|
--bg-page: #f7f8fa;
|
||||||
--bg-surface: #ffffff;
|
--bg-surface: #ffffff;
|
||||||
@@ -141,12 +208,12 @@ async function save() {
|
|||||||
--text-secondary: #6b7280;
|
--text-secondary: #6b7280;
|
||||||
--text-tertiary: #9ca3af;
|
--text-tertiary: #9ca3af;
|
||||||
--color-brand: #4F46E5;
|
--color-brand: #4F46E5;
|
||||||
|
--border-color: rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: var(--bg-page);
|
background: var(--bg-page);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 深色模式 - Warm Stone
|
|
||||||
.edit-page.dark {
|
.edit-page.dark {
|
||||||
--bg-page: #1c1917;
|
--bg-page: #1c1917;
|
||||||
--bg-surface: #292524;
|
--bg-surface: #292524;
|
||||||
@@ -154,9 +221,9 @@ async function save() {
|
|||||||
--text-secondary: #e7e5e4;
|
--text-secondary: #e7e5e4;
|
||||||
--text-tertiary: #78716c;
|
--text-tertiary: #78716c;
|
||||||
--color-brand: #f97316;
|
--color-brand: #f97316;
|
||||||
|
--border-color: rgba(255, 255, 255, 0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 动画
|
|
||||||
@keyframes fadeInUp {
|
@keyframes fadeInUp {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
@@ -173,7 +240,6 @@ async function save() {
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 导航栏
|
|
||||||
.nav-bar {
|
.nav-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -219,47 +285,106 @@ async function save() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 内容
|
|
||||||
.content-body {
|
.content-body {
|
||||||
padding: 40rpx;
|
padding: 40rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 输入卡片
|
.avatar-section {
|
||||||
.input-card {
|
display: flex;
|
||||||
background: var(--bg-surface);
|
flex-direction: column;
|
||||||
border-radius: 24rpx;
|
align-items: center;
|
||||||
padding: 32rpx;
|
|
||||||
position: relative;
|
|
||||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.03);
|
|
||||||
margin-bottom: 48rpx;
|
margin-bottom: 48rpx;
|
||||||
|
|
||||||
.input-label {
|
.avatar-wrap {
|
||||||
font-size: 26rpx;
|
position: relative;
|
||||||
font-weight: 600;
|
width: 192rpx;
|
||||||
color: var(--text-secondary);
|
height: 192rpx;
|
||||||
margin-bottom: 16rpx;
|
border-radius: 50%;
|
||||||
|
overflow: visible;
|
||||||
|
|
||||||
|
.avatar-mask {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(0, 0, 0, 0.4);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 64rpx;
|
||||||
|
height: 64rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active .avatar-mask {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-badge {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 48rpx;
|
||||||
|
height: 48rpx;
|
||||||
|
background: var(--color-brand);
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: 4rpx solid var(--bg-page);
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 24rpx;
|
||||||
|
height: 24rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-hint {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
margin-top: 24rpx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-input {
|
.info-card {
|
||||||
width: 100%;
|
background: var(--bg-surface);
|
||||||
font-size: 32rpx;
|
border-radius: 24rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.03);
|
||||||
|
margin-bottom: 32rpx;
|
||||||
|
border: 1rpx solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 28rpx 32rpx;
|
||||||
|
border-bottom: 1rpx solid var(--border-color);
|
||||||
|
|
||||||
|
&.no-border {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-label {
|
||||||
|
width: 112rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-input {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 28rpx;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
padding: 16rpx 0;
|
|
||||||
border-bottom: 2rpx solid transparent;
|
|
||||||
transition: border-color 0.2s;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
border-bottom-color: var(--color-brand);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.textarea {
|
|
||||||
height: 200rpx;
|
|
||||||
line-height: 1.6;
|
|
||||||
resize: none;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,17 +392,45 @@ async function save() {
|
|||||||
color: var(--text-tertiary);
|
color: var(--text-tertiary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.counter {
|
.desc-card {
|
||||||
position: absolute;
|
background: var(--bg-surface);
|
||||||
bottom: 20rpx;
|
border-radius: 24rpx;
|
||||||
right: 32rpx;
|
padding: 32rpx;
|
||||||
|
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.03);
|
||||||
|
margin-bottom: 48rpx;
|
||||||
|
border: 1rpx solid var(--border-color);
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.desc-label {
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desc-input {
|
||||||
|
width: 100%;
|
||||||
|
height: 192rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: var(--text-primary);
|
||||||
|
line-height: 1.6;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.counter {
|
||||||
|
display: block;
|
||||||
|
text-align: right;
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
color: var(--text-tertiary);
|
color: var(--text-tertiary);
|
||||||
|
margin-top: 8rpx;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存按钮
|
|
||||||
.btn-wrap {
|
.btn-wrap {
|
||||||
margin-top: 40rpx;
|
margin-top: 16rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.save-btn {
|
.save-btn {
|
||||||
@@ -299,8 +452,12 @@ async function save() {
|
|||||||
box-shadow: 0 8rpx 24rpx rgba(249, 115, 22, 0.25);
|
box-shadow: 0 8rpx 24rpx rgba(249, 115, 22, 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&:active:not(.disabled) {
|
||||||
transform: scale(0.98);
|
transform: scale(0.98);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -6,7 +6,20 @@
|
|||||||
<view class="profile-header">
|
<view class="profile-header">
|
||||||
<view class="header-top">
|
<view class="header-top">
|
||||||
<text class="page-title">我的</text>
|
<text class="page-title">我的</text>
|
||||||
<view class="icon-btn" @click="uni.navigateTo({ url: '/pages/settings/index' })">
|
<view class="header-actions">
|
||||||
|
<view class="edit-btn" @click="goProfileEdit">
|
||||||
|
<!-- #ifdef H5 || APP-PLUS -->
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
|
||||||
|
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
|
||||||
|
</svg>
|
||||||
|
<!-- #endif -->
|
||||||
|
<!-- #ifdef MP-WEIXIN -->
|
||||||
|
<wd-icon name="edit" size="40rpx" />
|
||||||
|
<!-- #endif -->
|
||||||
|
<text class="edit-text">编辑资料</text>
|
||||||
|
</view>
|
||||||
|
<view class="icon-btn" @click="goSettings">
|
||||||
<!-- #ifdef H5 || APP-PLUS -->
|
<!-- #ifdef H5 || APP-PLUS -->
|
||||||
<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"/>
|
||||||
@@ -18,23 +31,18 @@
|
|||||||
<!-- #endif -->
|
<!-- #endif -->
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
<view class="user-hero animate-fade-in-up" @click="goProfileEdit">
|
<!-- 用户信息展示区,仅展示不可点击跳转 -->
|
||||||
|
<view class="user-hero animate-fade-in-up">
|
||||||
<view class="avatar-wrap">
|
<view class="avatar-wrap">
|
||||||
<app-avatar :src="userInfo.avatar" :name="userInfo.nickname" :size="160" round custom-style="border: 6rpx solid var(--bg-surface);" />
|
<app-avatar :src="user?.avatar" :name="user?.name" :size="160" round custom-style="border: 6rpx solid var(--bg-surface);" />
|
||||||
</view>
|
</view>
|
||||||
<view class="hero-info">
|
<view class="hero-info">
|
||||||
<text class="nickname">{{ userInfo.nickname || '未设置昵称' }}</text>
|
<text class="nickname">{{ user?.name || '未设置昵称' }}</text>
|
||||||
<text class="account">账号: {{ userInfo.account || '--' }}</text>
|
<text class="account">账号: {{ displayAccount }}</text>
|
||||||
|
<text v-if="user?.desc" class="desc">{{ user.desc }}</text>
|
||||||
</view>
|
</view>
|
||||||
<!-- #ifdef H5 || APP-PLUS -->
|
|
||||||
<svg class="chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
||||||
<polyline points="9 18 15 12 9 6"/>
|
|
||||||
</svg>
|
|
||||||
<!-- #endif -->
|
|
||||||
<!-- #ifdef MP-WEIXIN -->
|
|
||||||
<wd-icon name="arrow-right" size="32rpx" custom-class="chevron-mp" />
|
|
||||||
<!-- #endif -->
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 数据概览 -->
|
<!-- 数据概览 -->
|
||||||
@@ -128,26 +136,53 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<app-tab-bar current="profile" />
|
<!-- <app-tab-bar current="profile" />-->
|
||||||
</view>
|
</view>
|
||||||
</wd-config-provider>
|
</wd-config-provider>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
import { onShow } from '@dcloudio/uni-app'
|
||||||
import { useTheme } from '@/composables/useTheme'
|
import { useTheme } from '@/composables/useTheme'
|
||||||
import { useAuthStore } from '@/stores'
|
import { useAuthStore } from '@/stores'
|
||||||
|
import * as userApi from '@/api/modules/user'
|
||||||
import AppAvatar from '@/components/common/AppAvatar.vue'
|
import AppAvatar from '@/components/common/AppAvatar.vue'
|
||||||
import AppTabBar from '@/components/common/AppTabBar.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 || {})
|
|
||||||
function goProfileEdit() { uni.navigateTo({ url: '/pages/profile/edit' }) }
|
const user = computed(() => authStore.user)
|
||||||
|
|
||||||
|
/** 展示账号:优先手机号,其次邮箱 */
|
||||||
|
const displayAccount = computed(() => {
|
||||||
|
const u = user.value
|
||||||
|
if (!u) return '--'
|
||||||
|
return u.phone || u.email || '--'
|
||||||
|
})
|
||||||
|
|
||||||
|
/** 进入编辑页(仅通过编辑按钮触发) */
|
||||||
|
function goProfileEdit() {
|
||||||
|
uni.navigateTo({ url: '/pages/profile/edit' })
|
||||||
|
}
|
||||||
|
|
||||||
|
function goSettings() {
|
||||||
|
uni.navigateTo({ url: '/pages/settings/index' })
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 每次显示时拉取最新用户信息 */
|
||||||
|
onShow(async () => {
|
||||||
|
try {
|
||||||
|
const info = await userApi.getMyInfo()
|
||||||
|
authStore.updateUserInfo(info)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('刷新用户信息失败:', e)
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
// 页面容器 - 浅色模式
|
|
||||||
.page-container {
|
.page-container {
|
||||||
--bg-page: #f7f8fa;
|
--bg-page: #f7f8fa;
|
||||||
--bg-surface: #ffffff;
|
--bg-surface: #ffffff;
|
||||||
@@ -162,7 +197,6 @@ function goProfileEdit() { uni.navigateTo({ url: '/pages/profile/edit' }) }
|
|||||||
padding-bottom: 200rpx;
|
padding-bottom: 200rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 深色模式 - Warm Stone
|
|
||||||
.page-container.dark {
|
.page-container.dark {
|
||||||
--bg-page: #1c1917;
|
--bg-page: #1c1917;
|
||||||
--bg-surface: #292524;
|
--bg-surface: #292524;
|
||||||
@@ -173,7 +207,6 @@ function goProfileEdit() { uni.navigateTo({ url: '/pages/profile/edit' }) }
|
|||||||
--color-brand: #f97316;
|
--color-brand: #f97316;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 动画
|
|
||||||
@keyframes fadeInUp {
|
@keyframes fadeInUp {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
@@ -190,7 +223,6 @@ function goProfileEdit() { uni.navigateTo({ url: '/pages/profile/edit' }) }
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 头部
|
|
||||||
.profile-header {
|
.profile-header {
|
||||||
background: var(--bg-surface);
|
background: var(--bg-surface);
|
||||||
border-bottom-left-radius: 80rpx;
|
border-bottom-left-radius: 80rpx;
|
||||||
@@ -219,6 +251,39 @@ function goProfileEdit() { uni.navigateTo({ url: '/pages/profile/edit' }) }
|
|||||||
letter-spacing: -0.5rpx;
|
letter-spacing: -0.5rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8rpx;
|
||||||
|
padding: 12rpx 20rpx;
|
||||||
|
border-radius: 32rpx;
|
||||||
|
background: rgba(79, 70, 229, 0.08);
|
||||||
|
transition: all 0.15s;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 32rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
color: var(--color-brand);
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--color-brand);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
opacity: 0.8;
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.icon-btn {
|
.icon-btn {
|
||||||
width: 72rpx;
|
width: 72rpx;
|
||||||
height: 72rpx;
|
height: 72rpx;
|
||||||
@@ -245,21 +310,18 @@ function goProfileEdit() { uni.navigateTo({ url: '/pages/profile/edit' }) }
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 24rpx;
|
gap: 24rpx;
|
||||||
transition: all 0.15s;
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar-wrap {
|
.avatar-wrap {
|
||||||
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.1);
|
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.1);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-info {
|
.hero-info {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
.nickname {
|
.nickname {
|
||||||
font-size: 40rpx;
|
font-size: 40rpx;
|
||||||
@@ -272,16 +334,18 @@ function goProfileEdit() { uni.navigateTo({ url: '/pages/profile/edit' }) }
|
|||||||
font-size: 26rpx;
|
font-size: 26rpx;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.chevron {
|
.desc {
|
||||||
width: 32rpx;
|
font-size: 24rpx;
|
||||||
height: 32rpx;
|
|
||||||
color: var(--text-tertiary);
|
color: var(--text-tertiary);
|
||||||
|
margin-top: 8rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 数据统计
|
|
||||||
.stats-row {
|
.stats-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
@@ -308,7 +372,6 @@ function goProfileEdit() { uni.navigateTo({ url: '/pages/profile/edit' }) }
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 菜单
|
|
||||||
.menu-container {
|
.menu-container {
|
||||||
padding: 40rpx;
|
padding: 40rpx;
|
||||||
}
|
}
|
||||||
@@ -344,17 +407,9 @@ function goProfileEdit() { uni.navigateTo({ url: '/pages/profile/edit' }) }
|
|||||||
height: 40rpx;
|
height: 40rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.indigo {
|
&.indigo { background: #4F46E5; }
|
||||||
background: #4F46E5;
|
&.amber { background: #f59e0b; }
|
||||||
}
|
&.emerald { background: #10b981; }
|
||||||
|
|
||||||
&.amber {
|
|
||||||
background: #f59e0b;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.emerald {
|
|
||||||
background: #10b981;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-text {
|
.menu-text {
|
||||||
|
|||||||
Reference in New Issue
Block a user