zbb 3 settimane fa
parent
commit
fdeb7b261e

+ 189 - 0
components/user-avatar/index.vue

@@ -0,0 +1,189 @@
+<template>
+	<view class="user-avatar" :class="customClass" :style="wrapperStyle">
+		<view
+			class="avatar-box"
+			:style="boxStyle"
+		>
+			<!-- 图片模式:加载正常 -->
+			<image
+				v-if="!imgError && src"
+				class="avatar-img"
+				:src="src"
+				:mode="mode"
+				@error="onImgError"
+			/>
+			<!-- 文字模式:加载失败或无图片 -->
+			<view v-else class="avatar-text" :style="textStyle">
+				<text>{{ displayText }}</text>
+			</view>
+		</view>
+		<!-- 徽章(可选) -->
+		<image
+			v-if="badgeSrc"
+			class="avatar-badge"
+			:src="badgeSrc"
+			:style="badgeStyle"
+		/>
+	</view>
+</template>
+
+<script setup>
+	import { ref, computed } from 'vue'
+
+	const props = defineProps({
+		// 头像图片地址
+		src: {
+			type: String,
+			default: ''
+		},
+		// 用户名称(取第一个字)
+		name: {
+			type: String,
+			default: ''
+		},
+		// 头像大小(rpx)
+		size: {
+			type: Number,
+			default: 80
+		},
+		// 字体大小倍数(相对size),默认0.4
+		fontRatio: {
+			type: Number,
+			default: 0.4
+		},
+		// 背景色
+		bgColor: {
+			type: String,
+			default: '#4080FF'
+		},
+		// 文字颜色
+		textColor: {
+			type: String,
+			default: '#FFFFFF'
+		},
+		// 图片裁剪模式
+		mode: {
+			type: String,
+			default: 'aspectFill'
+		},
+		// 圆角值(默认50%圆形)
+		radius: {
+			type: String,
+			default: '50%'
+		},
+		// 边框
+		border: {
+			type: String,
+			default: ''
+		},
+		// 徽章图片
+		badgeSrc: {
+			type: String,
+			default: ''
+		},
+		// 徽章大小(rpx)
+		badgeSize: {
+			type: Number,
+			default: 0
+		},
+		// 自定义样式类
+		customClass: {
+			type: String,
+			default: ''
+		}
+	})
+
+	// 图片是否加载失败
+	const imgError = ref(false)
+
+	// 显示的文字(名称第一个字)
+	const displayText = computed(() => {
+		if (!props.name) return '?'
+		// 取第一个字符
+		return props.name.charAt(0)
+	})
+
+	// 图片加载失败回调
+	const onImgError = () => {
+		imgError.value = true
+	}
+
+	// 外层容器样式
+	const wrapperStyle = computed(() => {
+		const sizePx = (props.size / 2) + 'px'
+		return {
+			width: sizePx,
+			height: sizePx
+		}
+	})
+
+	// 头像盒子样式
+	const boxStyle = computed(() => {
+		const sizePx = (props.size / 2) + 'px'
+		const style = {
+			width: sizePx,
+			height: sizePx,
+			borderRadius: props.radius
+		}
+		if (props.border) {
+			style.border = props.border
+		}
+		return style
+	})
+
+	// 文字样式
+	const textStyle = computed(() => {
+		const fontSize = (props.size * props.fontRatio / 2) + 'px'
+		return {
+			fontSize: fontSize,
+			backgroundColor: props.bgColor,
+			color: props.textColor
+		}
+	})
+
+	// 徽章样式
+	const badgeStyle = computed(() => {
+		if (!props.badgeSize) return {}
+		const sizePx = (props.badgeSize / 2) + 'px'
+		return {
+			width: sizePx,
+			height: sizePx
+		}
+	})
+</script>
+
+<style scoped>
+	.user-avatar {
+		position: relative;
+		flex-shrink: 0;
+	}
+
+	.avatar-box {
+		overflow: hidden;
+		width: 100%;
+		height: 100%;
+	}
+
+	.avatar-img {
+		width: 100%;
+		height: 100%;
+		display: block;
+	}
+
+	.avatar-text {
+		width: 100%;
+		height: 100%;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		font-weight: 600;
+	}
+
+	.avatar-badge {
+		position: absolute;
+		bottom: 0;
+		right: 0;
+		transform: translate(20%, 20%);
+		z-index: 1;
+	}
+</style>

+ 1 - 8
pages/index/components/card-preview.vue

@@ -6,7 +6,7 @@
 			<view class="user-card-header">
 				<view class="user-info">
 					<view class="avatar-wrapper">
-						<image class="avatar" src="/static/avatar-default.png" mode="aspectFill" />
+						<UserAvatar src="" name="赵建平" :size="120" />
 					</view>
 					<view class="user-details">
 						<view class="name-row">
@@ -227,13 +227,6 @@ const saveCard = () => {
 		.avatar-wrapper {
 			flex-shrink: 0;
 			margin-right: 24rpx;
-			
-			.avatar {
-				width: 120rpx;
-				height: 120rpx;
-				border-radius: 50%;
-				border: 4rpx solid rgba(255, 255, 255, 0.3);
-			}
 		}
 		
 		.user-details {

+ 8 - 26
pages/index/index.vue

@@ -24,22 +24,21 @@
 
 					<!-- 右上:头像 -->
 					<view class="avatar-wrapper">
-						<image class="avatar" :src="cardInfo.avatar || '/static/image/public/avatar-default.png'" />
-						<image class="badge-icon" src="/static/image/public/badge-icon.png" />
+						<UserAvatar :src="cardInfo.avatar" :name="cardInfo.nickName" :size="140" :badge-src="'/static/image/public/badge-icon.png'" :badge-size="64" />
 					</view>
 
 					<!-- 左下:联系方式 -->
 					<view class="user-contact">
 						<view class="contact-row" @click="makeCall">
-							<uni-icons type="phone" :size="18" color="#666666"></uni-icons>
+							<uni-icons style="margin-top: 4rpx;" type="phone" :size="18" color="#666666"></uni-icons>
 							<text class="contact-text">{{ cardInfo.phonenumber || '暂无电话' }}</text>
 						</view>
 						<view class="contact-row">
-							<uni-icons type="email" :size="18" color="#666666"></uni-icons>
+							<uni-icons style="margin-top: 4rpx;" type="email" :size="18" color="#666666"></uni-icons>
 							<text class="contact-text">{{ cardInfo.email || '暂无邮箱' }}</text>
 						</view>
 						<view class="contact-row">
-							<uni-icons type="location" :size="18" color="#666666"></uni-icons>
+							<uni-icons style="margin-top: 4rpx;" type="location" :size="18" color="#666666"></uni-icons>
 							<text class="contact-text">{{ cardInfo.companyAddress || '暂无地址' }}</text>
 						</view>
 					</view>
@@ -242,6 +241,7 @@
 
 <script setup>
 	import NavBar from '@/components/nav-bar/index.vue'
+	import UserAvatar from '@/components/user-avatar/index.vue'
 	import CardPreview from './components/card-preview.vue'
 	import {
 		ref,
@@ -433,26 +433,8 @@
 			.avatar-wrapper {
 				position: absolute;
 				top: 32rpx;
-				right: 40rpx;
-				width: 140rpx;
-				height: 140rpx;
-				border-radius: 50%;
-				box-shadow: 0rpx 0rpx 4rpx 0rpx rgba(21, 93, 252, 0.20);
-
-				.avatar {
-					width: 100%;
-					height: 100%;
-					border-radius: 50%;
-				}
-
-				.badge-icon {
-					width: 64rpx;
-					height: 64rpx;
-					position: absolute;
-					bottom: -10rpx;
-					right: -10rpx;
-					z-index: 1;
-				}
+				right: 36rpx;
+				z-index: 2;
 			}
 
 			.user-contact {
@@ -460,7 +442,7 @@
 
 				.contact-row {
 					display: flex;
-					align-items: center;
+					align-items: flex-start;
 					margin-bottom: 16rpx;
 
 					uni-icons {

+ 82 - 23
pages/mine/card.vue

@@ -21,8 +21,7 @@
 
 				<!-- 右上:头像 -->
 				<view class="avatar-wrapper">
-					<image class="avatar" :src="cardInfo.avatar || '/static/image/public/avatar-default.png'" />
-					<image class="badge-icon" src="/static/image/public/badge-icon.png" />
+					<UserAvatar :src="cardInfo.avatar" :name="cardInfo.nickName" :size="130" :badge-src="'/static/image/public/badge-icon.png'" :badge-size="56" />
 				</view>
 
 				<!-- 左下:联系方式 -->
@@ -100,6 +99,84 @@
 			<canvas id="qrPosterCanvas" type="2d" style="width: 600px; height: 700px;"></canvas>
 		</view>
 
+		<!-- 名片分享弹窗 -->
+		<uni-popup ref="cardPopup" type="center">
+			<view class="card-popup">
+				<!-- 顶部装饰条 -->
+				<view class="popup-decor">
+					<view class="decor-line"></view>
+				</view>
+				
+				<!-- 标题 -->
+				<view class="popup-title-box">
+					<text class="popup-title">我的名片</text>
+					<text class="popup-desc">分享给朋友或保存到相册</text>
+				</view>
+				
+				<!-- 名片卡片 -->
+				<view class="card-box">
+					<view class="user-card">
+						<!-- 背景图 -->
+						<image class="user-card-bg" src="/static/image/home/usecard-bg.png" mode="aspectFill" />
+					
+						<!-- 左上:姓名 + 职位 -->
+						<view class="user-header">
+							<view class="name-row">
+								<text class="user-name">{{ cardInfo.nickName || '用户' }}</text>
+								<text class="user-role">{{ cardInfo.postName || '职位' }}</text>
+							</view>
+							<text class="company-name">{{ cardInfo.companyName || '公司名称' }}</text>
+						</view>
+					
+						<!-- 右上:头像 -->
+						<view class="user-avatar-box">
+							<UserAvatar :src="cardInfo.avatar" :name="cardInfo.nickName" :size="120" :badge-src="'/static/image/public/badge-icon.png'" :badge-size="50" />
+						</view>
+					
+						<!-- 左下:联系方式 -->
+						<view class="user-contact">
+							<view class="contact-row" @click="makeCall">
+								<view class="contact-icon">
+									<uni-icons type="phone" :size="16" color="#4080FF"></uni-icons>
+								</view>
+								<text class="contact-text">{{ cardInfo.phonenumber || '暂无电话' }}</text>
+							</view>
+							<view class="contact-row">
+								<view class="contact-icon">
+									<uni-icons type="email" :size="16" color="#4080FF"></uni-icons>
+								</view>
+								<text class="contact-text">{{ cardInfo.email || '暂无邮箱' }}</text>
+							</view>
+							<view class="contact-row">
+								<view class="contact-icon">
+									<uni-icons type="location" :size="16" color="#4080FF"></uni-icons>
+								</view>
+								<text class="contact-text">{{ cardInfo.companyAddress || '暂无地址' }}</text>
+							</view>
+						</view>
+					</view>
+				</view>
+				
+				<!-- 操作按钮 -->
+				<view class="popup-btns">
+					<view class="btn btn-cancel" @click="closeCardPopup">
+						<text class="btn-text">取消</text>
+					</view>
+					<view class="btn btn-confirm" @click="shareCardConfirm">
+						<text class="btn-text">分享名片</text>
+					</view>
+				</view>
+				
+				<!-- 底部全宽按钮 -->
+				<view class="popup-footer">
+					<view class="btn btn-full" @click="shareCardConfirm">
+						<uni-icons type="paperplane" size="22" color="#ffffff"></uni-icons>
+						<text class="btn-text">立即分享名片给朋友</text>
+					</view>
+				</view>
+			</view>
+		</uni-popup>
+
 		<!-- 二维码弹窗 -->
 		<uni-popup ref="qrPopup" type="top">
 			<NavBar title="" color="#FFFFFF" :fixed="true" :bg="'transparent'"></NavBar>
@@ -141,6 +218,7 @@
 		onShareAppMessage
 	} from '@dcloudio/uni-app';
 	import NavBar from '@/components/nav-bar/index.vue'
+	import UserAvatar from '@/components/user-avatar/index.vue'
 	import {
 		useUserStore
 	} from '@/store/modules/user.js'
@@ -502,27 +580,8 @@
 		.avatar-wrapper {
 			position: absolute;
 			top: 32rpx;
-			right: 40rpx;
-			width: 140rpx;
-			height: 140rpx;
-			border-radius: 50%;
-			box-shadow: 0rpx 0rpx 4rpx 0rpx rgba(21, 93, 252, 0.20);
-
-			.avatar {
-				border-radius: 50%;
-				width: 100%;
-				height: 100%;
-			}
-
-			.badge-icon {
-				width: 64rpx;
-				height: 64rpx;
-				position: absolute;
-				bottom: 0;
-				right: 0;
-				transform: translate(50% 50%);
-				z-index: 1;
-			}
+			right: 36rpx;
+			z-index: 2;
 		}
 
 		.user-contact {

+ 311 - 328
pages/mine/index.vue

@@ -11,9 +11,8 @@
 			<!-- 用户信息 -->
 			<view class="user-info-section">
 				<view class="avatar-wrapper">
-					<image class="avatar" :src="cardInfo.avatar || '/static/image/public/avatar-default.png'"
-						mode="aspectFill" />
-					<image class="avatar-badge" src="/static/image/public/badge-icon.png" />
+					<UserAvatar :src="cardInfo.avatar" :name="cardInfo.nickName" :size="120"
+						:badge-src="'/static/image/public/badge-icon.png'" :badge-size="40" />
 				</view>
 				<view class="user-details">
 					<view class="name-row">
@@ -65,8 +64,8 @@
 						<text class="data-label">客户总数</text>
 					</view>
 					<view class="data-item highlight">
-						<text
-							class="data-value orange">{{ statisticsData.customerNewThisMonth.toLocaleString() }}</text>
+						<text class="data-value orange">{{ statisticsData.customerNewThisMonth.toLocaleString()
+							}}</text>
 						<text class="data-label">本月新增</text>
 					</view>
 					<view class="data-item">
@@ -88,8 +87,8 @@
 						<text class="data-label">商机总数</text>
 					</view>
 					<view class="data-item highlight">
-						<text
-							class="data-value orange">{{ statisticsData.opportunityNewThisMonth.toLocaleString() }}</text>
+						<text class="data-value orange">{{ statisticsData.opportunityNewThisMonth.toLocaleString()
+							}}</text>
 						<text class="data-label">本月新增</text>
 					</view>
 					<view class="data-item">
@@ -141,390 +140,374 @@
 </template>
 
 <script setup>
-	import {
-		ref,
-		onMounted
-	} from 'vue'
-	import NavBar from '@/components/nav-bar/index.vue'
-	import {
-		useUserStore
-	} from '@/store/modules/user.js'
-	import {
-		storeToRefs
-	} from 'pinia'
-
-	// 使用 Pinia 管理用户状态
-	const userStore = useUserStore()
-	const {
-		cardInfo,
-		companyInfo
-	} = storeToRefs(userStore)
-
-	// 统计数据
-	const statisticsData = ref({
-		clueTotal: 0,
-		clueNewThisMonth: 0,
-		clueMine: 0,
-		customerTotal: 0,
-		customerNewThisMonth: 0,
-		customerMine: 0,
-		opportunityTotal: 0,
-		opportunityNewThisMonth: 0,
-		opportunityMine: 0
-	})
-
-	onMounted(async () => {
-		// 如果没有数据,重新获取
-		if (!cardInfo.value.nickName) {
-			await userStore.queryCardInfo()
-		}
-		if (!companyInfo.value.name) {
-			await userStore.queryCompanyInfo()
-		}
-
-		// 加载统计数据
-		loadStatisticsData()
-	})
-
-	// 加载统计数据(后续对接真实 API)
-	const loadStatisticsData = () => {
-		// TODO: 调用统计 API 获取真实数据
-		statisticsData.value = {
-			clueTotal: 1266,
-			clueNewThisMonth: 65,
-			clueMine: 126,
-			customerTotal: 1088,
-			customerNewThisMonth: 123,
-			customerMine: 235,
-			opportunityTotal: 1366,
-			opportunityNewThisMonth: 63,
-			opportunityMine: 86
-		}
+import {
+	ref,
+	onMounted
+} from 'vue'
+import NavBar from '@/components/nav-bar/index.vue'
+import UserAvatar from '@/components/user-avatar/index.vue'
+import {
+	useUserStore
+} from '@/store/modules/user.js'
+import {
+	storeToRefs
+} from 'pinia'
+
+// 使用 Pinia 管理用户状态
+const userStore = useUserStore()
+const {
+	cardInfo,
+	companyInfo
+} = storeToRefs(userStore)
+
+// 统计数据
+const statisticsData = ref({
+	clueTotal: 0,
+	clueNewThisMonth: 0,
+	clueMine: 0,
+	customerTotal: 0,
+	customerNewThisMonth: 0,
+	customerMine: 0,
+	opportunityTotal: 0,
+	opportunityNewThisMonth: 0,
+	opportunityMine: 0
+})
+
+onMounted(async () => {
+	// 如果没有数据,重新获取
+	if (!cardInfo.value.nickName) {
+		await userStore.queryCardInfo()
 	}
-
-	const navigateTo = (page) => {
-		uni.showToast({
-			title: `功能开发中:${page}`,
-			icon: 'none'
-		})
+	if (!companyInfo.value.name) {
+		await userStore.queryCompanyInfo()
 	}
 
-	// 显示名片
-	const showCard = () => {
-		uni.navigateTo({
-			url: '/pages/mine/card'
-		})
+	// 加载统计数据
+	loadStatisticsData()
+})
+
+// 加载统计数据(后续对接真实 API)
+const loadStatisticsData = () => {
+	// TODO: 调用统计 API 获取真实数据
+	statisticsData.value = {
+		clueTotal: 1266,
+		clueNewThisMonth: 65,
+		clueMine: 126,
+		customerTotal: 1088,
+		customerNewThisMonth: 123,
+		customerMine: 235,
+		opportunityTotal: 1366,
+		opportunityNewThisMonth: 63,
+		opportunityMine: 86
 	}
+}
 
-	// 退出登录
-	const logout = () => {
-		uni.showModal({
-			title: '提示',
-			content: '确定要退出登录吗?',
-			success: (res) => {
-				if (res.confirm) {
-					console.log('用户确认退出登录')
-
-					// 1. 清除 token
-					uni.removeStorageSync('token')
-
-					// 2. 清除 store 中的用户数据
-					userStore.$reset()
-
-					// 3. 清除其他可能存储的数据
-					uni.removeStorageSync('userInfo')
-					uni.removeStorageSync('companyInfo')
-
-					console.log('已清除所有登录数据')
-
-					// 4. 跳转到登录页
-					uni.reLaunch({
-						url: '/pages/login/login'
-					})
+const navigateTo = (page) => {
+	uni.showToast({
+		title: `功能开发中:${page}`,
+		icon: 'none'
+	})
+}
 
-					uni.showToast({
-						title: '已退出登录',
-						icon: 'success'
-					})
-				}
+// 显示名片
+const showCard = () => {
+	uni.navigateTo({
+		url: '/pages/mine/card'
+	})
+}
+
+// 退出登录
+const logout = () => {
+	uni.showModal({
+		title: '提示',
+		content: '确定要退出登录吗?',
+		success: (res) => {
+			if (res.confirm) {
+				console.log('用户确认退出登录')
+
+				// 1. 清除 token
+				uni.removeStorageSync('token')
+
+				// 2. 清除 store 中的用户数据
+				userStore.$reset()
+
+				// 3. 清除其他可能存储的数据
+				uni.removeStorageSync('userInfo')
+				uni.removeStorageSync('companyInfo')
+
+				console.log('已清除所有登录数据')
+
+				// 4. 跳转到登录页
+				uni.reLaunch({
+					url: '/pages/login/login'
+				})
+
+				uni.showToast({
+					title: '已退出登录',
+					icon: 'success'
+				})
 			}
-		})
-	}
+		}
+	})
+}
 </script>
 
 <style lang="scss" scoped>
-	.mine-container {
-		background: #f5f6f8;
+.mine-container {
+	background: #f5f6f8;
+}
+
+.top-bg {
+	width: 750rpx;
+	height: 634rpx;
+	position: fixed;
+	top: 0;
+	left: 0;
+	z-index: 1;
+}
+
+// 顶部背景区域
+.header-bg {
+	padding: 0 40rpx;
+	padding-bottom: 200rpx;
+	position: relative;
+	overflow: hidden;
+
+	// 背景光效
+	.bg-light {
+		position: absolute;
+		border-radius: 50%;
+		background: radial-gradient(circle, rgba(255, 255, 255, 0.2) 0%, transparent 70%);
 	}
 
-	.top-bg {
-		width: 750rpx;
-		height: 634rpx;
-		position: fixed;
-		top: 0;
-		left: 0;
-		z-index: 1;
+	.bg-light-1 {
+		top: -150rpx;
+		right: -100rpx;
+		width: 500rpx;
+		height: 500rpx;
 	}
 
-	// 顶部背景区域
-	.header-bg {
-		padding: 0 40rpx;
-		padding-bottom: 200rpx;
+	.bg-light-2 {
+		top: 200rpx;
+		left: -200rpx;
+		width: 400rpx;
+		height: 400rpx;
+	}
+
+	// 顶部状态栏
+	.header-top {
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		margin-bottom: 60rpx;
 		position: relative;
-		overflow: hidden;
+		z-index: 10;
 
-		// 背景光效
-		.bg-light {
-			position: absolute;
-			border-radius: 50%;
-			background: radial-gradient(circle, rgba(255, 255, 255, 0.2) 0%, transparent 70%);
+		.header-left {
+			width: 100rpx;
 		}
 
-		.bg-light-1 {
-			top: -150rpx;
-			right: -100rpx;
-			width: 500rpx;
-			height: 500rpx;
-		}
+		.header-right {
+			display: flex;
+			align-items: center;
 
-		.bg-light-2 {
-			top: 200rpx;
-			left: -200rpx;
-			width: 400rpx;
-			height: 400rpx;
+			.more-btn,
+			.refresh-btn {
+				width: 64rpx;
+				height: 64rpx;
+				border-radius: 50%;
+				background: rgba(255, 255, 255, 0.15);
+				display: flex;
+				align-items: center;
+				justify-content: center;
+				margin-left: 16rpx;
+			}
 		}
+	}
 
-		// 顶部状态栏
-		.header-top {
-			display: flex;
-			justify-content: space-between;
-			align-items: center;
-			margin-bottom: 60rpx;
+	// 用户信息区域
+	.user-info-section {
+		display: flex;
+		align-items: center;
+		position: relative;
+		z-index: 10;
+
+		.avatar-wrapper {
 			position: relative;
-			z-index: 10;
+			margin-right: 24rpx;
+			flex-shrink: 0;
+		}
 
-			.header-left {
-				width: 100rpx;
-			}
+		.user-details {
+			flex: 1;
 
-			.header-right {
+			.name-row {
 				display: flex;
 				align-items: center;
+				margin-bottom: 12rpx;
 
-				.more-btn,
-				.refresh-btn {
-					width: 64rpx;
-					height: 64rpx;
-					border-radius: 50%;
-					background: rgba(255, 255, 255, 0.15);
-					display: flex;
-					align-items: center;
-					justify-content: center;
+				.user-name {
+					font-size: 36rpx;
+					font-weight: 600;
+					color: #ffffff;
+					text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
+				}
+
+				.user-role {
+					font-size: 22rpx;
+					color: #ffffff;
+					background: rgba(255, 255, 255, 0.25);
+					padding: 4rpx 16rpx;
+					border-radius: 20rpx;
 					margin-left: 16rpx;
+					font-weight: 500;
 				}
 			}
+
+			.company-name {
+				font-size: 24rpx;
+				color: rgba(255, 255, 255, 0.85);
+				display: block;
+				text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
+			}
 		}
 
-		// 用户信息区域
-		.user-info-section {
+		.qr-btn {
 			display: flex;
+			flex-direction: column;
 			align-items: center;
-			position: relative;
-			z-index: 10;
-
-			.avatar-wrapper {
-				position: relative;
-				margin-right: 24rpx;
-
-				.avatar {
-					width: 140rpx;
-					height: 140rpx;
-					border-radius: 50%;
-					border: 4rpx solid rgba(255, 255, 255, 0.4);
-					background: #ffffff;
-				}
-
-				.avatar-badge {
-					width: 64rpx;
-					height: 64rpx;
-					position: absolute;
-					bottom: 0;
-					right: 0;
-					transform: translate(50% 50%);
-					z-index: 1;
-				}
-			}
+			justify-content: center;
 
-			.user-details {
-				flex: 1;
-
-				.name-row {
-					display: flex;
-					align-items: center;
-					margin-bottom: 12rpx;
-
-					.user-name {
-						font-size: 36rpx;
-						font-weight: 600;
-						color: #ffffff;
-						text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
-					}
-
-					.user-role {
-						font-size: 22rpx;
-						color: #ffffff;
-						background: rgba(255, 255, 255, 0.25);
-						padding: 4rpx 16rpx;
-						border-radius: 20rpx;
-						margin-left: 16rpx;
-						font-weight: 500;
-					}
-				}
-
-				.company-name {
-					font-size: 24rpx;
-					color: rgba(255, 255, 255, 0.85);
-					display: block;
-					text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
-				}
+			.qr-icon {
+				width: 48rpx;
+				height: 48rpx;
 			}
 
-			.qr-btn {
-				display: flex;
-				flex-direction: column;
-				align-items: center;
-				justify-content: center;
-
-				.qr-icon {
-					width: 48rpx;
-					height: 48rpx;
-				}
-
-				.qr-text {
-					font-size: 20rpx;
-					color: #ffffff;
-					margin-top: 4rpx;
-				}
+			.qr-text {
+				font-size: 20rpx;
+				color: #ffffff;
+				margin-top: 4rpx;
 			}
 		}
 	}
+}
+
+// 内容区域
+.content-section {
+	margin-top: -160rpx;
+	position: relative;
+	z-index: 20;
+	padding: 0 40rpx;
+}
+
+// 数据卡片
+.data-card {
+	background: #ffffff;
+	margin-bottom: 24rpx;
+	padding: 32rpx;
+	border-radius: 24rpx;
+	box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
+
+	.card-header {
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		margin-bottom: 24rpx;
+	}
 
-	// 内容区域
-	.content-section {
-		margin-top: -160rpx;
-		position: relative;
-		z-index: 20;
-		padding: 0 40rpx;
+	.card-title {
+		font-size: 28rpx;
+		font-weight: 600;
+		color: #333333;
 	}
 
-	// 数据卡片
-	.data-card {
-		background: #ffffff;
-		margin-bottom: 24rpx;
-		padding: 32rpx;
-		border-radius: 24rpx;
-		box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
+	.data-row {
+		display: flex;
+		justify-content: space-between;
 
-		.card-header {
+		.data-item {
 			display: flex;
-			justify-content: space-between;
+			flex-direction: column;
 			align-items: center;
-			margin-bottom: 24rpx;
-		}
-
-		.card-title {
-			font-size: 28rpx;
-			font-weight: 600;
-			color: #333333;
-		}
-
-		.data-row {
-			display: flex;
-			justify-content: space-between;
-
-			.data-item {
-				display: flex;
-				flex-direction: column;
-				align-items: center;
-				flex: 1;
-				position: relative;
+			flex: 1;
+			position: relative;
 
-				.data-value {
-					font-size: 40rpx;
-					font-weight: 600;
-					color: #333333;
-					margin-bottom: 12rpx;
-					font-family: DIN, 'DIN Alternate', sans-serif;
-				}
+			.data-value {
+				font-size: 40rpx;
+				font-weight: 600;
+				color: #333333;
+				margin-bottom: 12rpx;
+				font-family: DIN, 'DIN Alternate', sans-serif;
+			}
 
-				.data-label {
-					font-size: 24rpx;
-					color: #999999;
-				}
+			.data-label {
+				font-size: 24rpx;
+				color: #999999;
+			}
 
-				&.highlight .data-value {
-					color: #ff6b35;
-				}
+			&.highlight .data-value {
+				color: #ff6b35;
+			}
 
-				&:not(:last-child)::after {
-					content: '';
-					position: absolute;
-					right: 0;
-					top: 50%;
-					transform: translateY(-50%);
-					width: 1rpx;
-					height: 60rpx;
-					background: #f0f0f0;
-				}
+			&:not(:last-child)::after {
+				content: '';
+				position: absolute;
+				right: 0;
+				top: 50%;
+				transform: translateY(-50%);
+				width: 1rpx;
+				height: 60rpx;
+				background: #f0f0f0;
 			}
 		}
 	}
-
-	// 功能菜单卡片
-	.menu-card {
-		background: #ffffff;
-		padding: 0 32rpx;
-		border-radius: 24rpx;
-		box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
-
-		.menu-item {
+}
+
+// 功能菜单卡片
+.menu-card {
+	background: #ffffff;
+	padding: 0 32rpx;
+	border-radius: 24rpx;
+	box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
+
+	.menu-item {
+		display: flex;
+		align-items: center;
+		padding: 32rpx 0;
+
+		.menu-icon-wrapper {
+			width: 64rpx;
+			height: 64rpx;
+			border-radius: 16rpx;
 			display: flex;
 			align-items: center;
-			padding: 32rpx 0;
+			justify-content: center;
+			margin-right: 20rpx;
 
-			.menu-icon-wrapper {
-				width: 64rpx;
-				height: 64rpx;
-				border-radius: 16rpx;
-				display: flex;
-				align-items: center;
-				justify-content: center;
-				margin-right: 20rpx;
-
-				&.icon-blue {
-					background: rgba(74, 144, 226, 0.1);
-				}
-
-				&.icon-red {
-					background: rgba(255, 77, 79, 0.1);
-				}
+			&.icon-blue {
+				background: rgba(74, 144, 226, 0.1);
 			}
 
-			.menu-text {
-				flex: 1;
-				font-size: 28rpx;
-				color: #333333;
+			&.icon-red {
+				background: rgba(255, 77, 79, 0.1);
 			}
 		}
 
-		.menu-divider {
-			height: 1rpx;
-			background: #f0f0f0;
+		.menu-text {
+			flex: 1;
+			font-size: 28rpx;
+			color: #333333;
 		}
 	}
 
-	// 底部占位
-	.bottom-spacer {
-		height: 40rpx;
+	.menu-divider {
+		height: 1rpx;
+		background: #f0f0f0;
 	}
+}
+
+// 底部占位
+.bottom-spacer {
+	height: 40rpx;
+}
 </style>

+ 4 - 22
pages/mine/userCard.vue

@@ -21,8 +21,7 @@
 
 				<!-- 右上:头像 -->
 				<view class="avatar-wrapper">
-					<image class="avatar" :src="cardInfo.avatar || '/static/image/public/avatar-default.png'" />
-					<image class="badge-icon" src="/static/image/public/badge-icon.png" />
+					<UserAvatar :src="cardInfo.avatar" :name="cardInfo.nickName" :size="130" :badge-src="'/static/image/public/badge-icon.png'" :badge-size="56" />
 				</view>
 
 				<!-- 左下:联系方式 -->
@@ -97,6 +96,7 @@
 		onMounted
 	} from 'vue'
 	import NavBar from '@/components/nav-bar/index.vue'
+	import UserAvatar from '@/components/user-avatar/index.vue'
 	import {
 		useUserStore
 	} from '@/store/modules/user.js'
@@ -310,26 +310,8 @@
 			.avatar-wrapper {
 				position: absolute;
 				top: 32rpx;
-				right: 40rpx;
-				width: 140rpx;
-				height: 140rpx;
-				border-radius: 50%;
-				box-shadow: 0rpx 0rpx 4rpx 0rpx rgba(21, 93, 252, 0.20);
-
-				.avatar {
-					width: 100%;
-					height: 100%;
-				}
-
-				.badge-icon {
-					width: 64rpx;
-					height: 64rpx;
-					position: absolute;
-					bottom: 0;
-					right: 0;
-					transform: translate(50% 50%);
-					z-index: 1;
-				}
+				right: 36rpx;
+				z-index: 2;
 			}
 
 			.user-contact {

+ 3 - 3
pages/todos/index.vue

@@ -3,7 +3,7 @@
 		<NavBar title="" color="#020202" :fixed="true" :bg="'#fff'">
 			<template #left>
 				<uni-search-bar placeholder="请输入" style="width: 70%" bgColor="#EEEEEE" clearButton="none"
-					cancelButton="none" @confirm="" />
+					cancelButton="none"/>
 			</template>
 			<template #bottom>
 				<view class="top-tabs">
@@ -58,7 +58,7 @@
 					</view>
 
 					<view class="row-user">
-						<image class="user-avatar" src="/static/avatar-default.png" mode="aspectFill" />
+						<UserAvatar src="" name="啊啊啊啊" :size="64" />
 						<text class="user-name">啊啊啊啊</text>
 					</view>
 
@@ -80,7 +80,7 @@
 						<uni-icons type="right" size="14" color="#cccccc"></uni-icons>
 					</view>
 					<view class="contact-item" v-for="(contact, cIndex) in company.contacts" :key="cIndex">
-						<image class="contact-avatar" src="/static/avatar-default.png" mode="aspectFill" />
+						<UserAvatar src="" name="" :size="48" />
 						<view class="contact-info">
 							<text class="contact-name">{{ contact.name }}</text>
 							<text class="contact-intent">{{ contact.intent }}</text>