Files
jurong_circle_frontdesk/src/views/MyProfile.vue
2025-09-03 11:16:04 +08:00

744 lines
18 KiB
Vue

<template>
<div class="personal-center">
<!-- 用户信息区域 -->
<div class="user-info-section">
<div class="user-avatar">
<el-avatar
:size="76"
:src="avatarUrl"
class="avatar-img"
>
<el-icon><User /></el-icon>
</el-avatar>
<el-button
type="primary"
size="small"
@click="showAvatarUpload = true"
class="upload-btn"
>
更换头像
</el-button>
</div>
<div class="user-actions">
<template v-if="userStore.isAuthenticated">
<span class="username">{{ userStore.user?.username || '用户' }}</span>
<button class="logout-btn" @click="handleLogout">退出登录</button>
</template>
<template v-else>
<div class="auth-buttons">
<router-link to="/mylogin">
<button class="login-btn">立即登录</button>
</router-link>
<router-link to="/register">
<button class="register-btn">注册</button>
</router-link>
</div>
</template>
</div>
</div>
<!-- 功能入口区域 -->
<div class="function-section">
<div class="function-grid">
<router-link
v-for="(item, index) in functionItems"
:key="index"
:to="item.path"
class="function-item"
custom
v-slot="{ navigate }"
>
<div @click="navigate" class="function-item-content">
<div class="function-icon">
<img :src="item.image" :alt="item.text" class="function-image">
</div>
<div class="function-text">{{ item.text }}</div>
</div>
</router-link>
</div>
</div>
<!-- 余额和记录区域 -->
<div class="balance-section">
<div class="balance-card">
<div class="balance-item">
<span class="balance-label">我的融豆</span>
<div class="balance-content">
<img src='/imgs/profile/rongdou.png' alt="融豆" class="bean-image">
<div class="balance-value">{{ Math.abs(accountInfo.balance) }}</div>
</div>
</div>
<el-checkbox
v-model="accountInfo.is_distribute"
@change="handleDistributeChange"
class="distribute-checkbox"
:true-label="true"
:false-label="false"
>
默认自动匹配
</el-checkbox>
</div>
</div>
<!-- 我的订单 -->
<div class="order-section">
<div class="section-header">
<h3>我的订单</h3>
<router-link to="/orders">
<span class="view-all">查看全部 ></span>
</router-link>
</div>
</div>
<!-- 设置选项区域 -->
<div class="settings-section">
<div class="setting-item" v-for="(item, index) in settings" :key="index">
<router-link :to="item.path" class="setting-link"> <!-- 添加 class 便于样式控制 -->
<div class="setting-content"> <!-- 添加 class 便于样式控制 -->
<span class="setting-text">{{ item.text }}</span>
<span class="setting-arrow">></span>
</div>
</router-link>
</div>
</div>
<!-- 头像上传对话框 -->
<el-dialog
v-model="showAvatarUpload"
title="更换头像"
width="400px"
>
<el-upload
class="avatar-uploader"
action="#"
:show-file-list="false"
:before-upload="beforeAvatarUpload"
:http-request="uploadAvatar"
>
<img v-if="newAvatar" :src="newAvatar" class="avatar-preview" />
<el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
</el-upload>
<template #footer>
<span class="dialog-footer">
<el-button @click="showAvatarUpload = false">取消</el-button>
<el-button type="primary" @click="confirmAvatarUpload" :disabled="!newAvatar">
确定
</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script>
import { ref, onMounted } from 'vue';
import { useUserStore } from '@/stores/user';
import { useRouter } from 'vue-router';
import { ElMessage, ElMessageBox } from 'element-plus';
import { User, Plus } from '@element-plus/icons-vue';
import api from '@/utils/api';
import { getImageUrl } from '@/config';
export default {
setup() {
const userStore = useUserStore();
const router = useRouter();
const avatarUrl = ref('');
const newAvatar = ref('');
const uploadedAvatarData = ref(null);
const showAvatarUpload = ref(false);
const accountInfo = ref({ balance: '0.00', is_distribute: false });
const isLoading = ref(false);
const settings = ref([
{text:'账号安全',path:'/editpasswordpage'},
{text:'商户资料',path:'/editdetailspage'},
{text:'通知设置'},
{text:'积分获取规则'},
{text:'隐私协议'},
]);
const functionItems = ref([
{ image: "/imgs/mainpage/jiaoyijilu.png", text: "购物车", path: "/cart" },
{ image: "/imgs/mainpage/dingdanchaxun.png", text: "地址", path: "/address" },
{ image: "/imgs/mainpage/kefuzhongxin.png", text: "收藏", path: "" }
]);
// 加载账户信息
const loadAccountInfo = async () => {
try {
console.log(userStore.user,'userStore.user');
if (userStore.user?.id) {
const response = await api.get(`/user/profile`);
console.log(response.data);
if (response.data.success) {
accountInfo.value = {
...accountInfo.value,
...response.data.user,
balance: response.data.user?.balance || '0.00',
is_distribute: response.data.user?.is_distribute || false
};
// 确保加载头像
if (response.data.user?.avatar) {
// 使用getImageUrl处理头像路径
const processedAvatarUrl = getImageUrl(response.data.user.avatar);
avatarUrl.value = processedAvatarUrl;
// 更新store中的头像
if(userStore.user) {
userStore.setUser({
...userStore.user,
avatar: processedAvatarUrl
});
}
}
}
}
} catch (error) {
console.error('加载账户信息失败:', error);
}
};
// 处理默认自动匹配状态变化
const handleDistributeChange = async (value) => {
try {
const action = value ? '开启' : '关闭';
await ElMessageBox.confirm(
`确定要${action}默认自动匹配功能吗?`,
'确认操作',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
);
const response = await api.put(`/user/${userStore.user.id}/distribute`, {
is_distribute: value
});
if (response.data.success) {
ElMessage.success('默认自动匹配状态更新成功');
} else {
// 如果更新失败,恢复原状态
accountInfo.value.is_distribute = !value;
ElMessage.error(response.data.message || '默认自动匹配状态更新失败');
}
} catch (error) {
if (error === 'cancel') {
// 用户取消操作,恢复原状态
accountInfo.value.is_distribute = !value;
} else {
console.error('更新默认自动匹配状态失败:', error);
// 如果请求失败,恢复原状态
accountInfo.value.is_distribute = !value;
ElMessage.error('默认自动匹配状态更新失败');
}
}
};
// 头像上传前的验证
const beforeAvatarUpload = (file) => {
const isJPG = file.type === 'image/jpeg' || file.type === 'image/png';
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isJPG) {
ElMessage.error('头像只能是 JPG/PNG 格式!');
return false;
}
if (!isLt2M) {
ElMessage.error('头像大小不能超过 2MB!');
return false;
}
return true;
};
// 上传头像
const uploadAvatar = async (options) => {
try {
const formData = new FormData();
formData.append('file', options.file);
const response = await api.post('/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data',
'Authorization': `Bearer ${userStore.token}`
}
});
if (response.data.success) {
// 保存上传响应数据
uploadedAvatarData.value = response.data.data;
// 使用url作为显示地址
newAvatar.value = response.data.data.url;
ElMessage.success('头像上传成功');
} else {
ElMessage.error(response.data.message || '上传失败');
}
} catch (error) {
console.error('头像上传失败:', error);
ElMessage.error('头像上传失败');
}
}
// 确认更新头像
const confirmAvatarUpload = async () => {
try {
if (!newAvatar.value || !uploadedAvatarData.value) {
ElMessage.error('请先选择头像');
return;
}
// 使用path提交给后端
const avatarPath = uploadedAvatarData.value.path;
const response = await api.put('/user/profile', {
avatar: avatarPath
});
if (response.data.success) {
// 使用url作为显示地址
avatarUrl.value = uploadedAvatarData.value.url;
// 更新用户store中的头像信息
if(userStore.user) {
userStore.setUser({
...userStore.user,
avatar: uploadedAvatarData.value.url
});
}
showAvatarUpload.value = false;
newAvatar.value = '';
uploadedAvatarData.value = null;
ElMessage.success('头像更新成功');
} else {
ElMessage.error(response.data.message || '头像更新失败');
}
} catch (error) {
console.error('头像更新失败:', error);
if (error.response) {
ElMessage.error(error.response.data.message || '头像更新失败');
} else {
ElMessage.error('头像更新失败');
}
}
}
const handleLogout = async () => {
try {
await ElMessageBox.confirm('确定要退出登录吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
userStore.logout();
router.push('/mylogin');
ElMessage.success('已退出登录');
} catch {
// 用户取消
}
};
onMounted(() => {
// 初始化时从store中获取头像
if (userStore.user?.avatar) {
avatarUrl.value = userStore.user.avatar;
}
loadAccountInfo();
});
return {
avatarUrl,
newAvatar,
showAvatarUpload,
uploadedAvatarData,
accountInfo,
isLoading,
functionItems,
settings,
beforeAvatarUpload,
uploadAvatar,
confirmAvatarUpload,
handleLogout,
handleDistributeChange,
userStore,
getImageUrl
};
}
};
</script>
<style scoped>
.personal-center {
max-width: 600px;
margin: 0 auto;
padding: 20px;
font-family: 'PingFang SC', 'Helvetica Neue', Arial, sans-serif;
background-color: #f8f8f8;
min-height: 100vh;
background: linear-gradient(to bottom, #72c9ffae, #f3f3f3);
}
/* 用户信息区域 - 修改部分 */
.user-info-section {
display: flex;
align-items: center;
padding: 20px;
background-color: transparent;
border-radius: 12px;
color: #333;
margin-bottom: 20px;
position: relative;
}
.user-avatar {
position: relative;
width: 76px;
height: 76px;
margin-right: 15px;
flex-shrink: 0;
}
.avatar-img {
width: 76px;
height: 76px;
border-radius: 50%;
object-fit: cover;
border: 3px solid rgba(0, 0, 0, 0.1);
}
.upload-btn {
position: absolute;
bottom: -10px;
left: 50%;
transform: translateX(-50%);
font-size: 12px;
padding: 4px 8px;
border-radius: 12px;
}
.user-actions {
display: flex;
align-items: center;
flex-grow: 1;
min-width: 0;
}
.username {
font-size: 16px;
font-weight: 500;
color: #333;
margin-right: auto;
padding-left: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 60%;
}
.auth-buttons {
display: flex;
gap: 10px;
margin-left: auto;
}
.login-btn, .register-btn {
padding: 8px 16px;
border-radius: 20px;
font-weight: 500;
font-size: 14px;
cursor: pointer;
transition: all 0.3s ease;
background-color: transparent;
border: 1px solid #6a11cb;
color: #6a11cb;
}
.register-btn {
border: 1px solid #6a11cb;
color: #6a11cb;
}
.login-btn:hover, .register-btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.logout-btn {
padding: 8px 16px;
border-radius: 20px;
font-weight: 500;
font-size: 14px;
cursor: pointer;
transition: all 0.3s ease;
background-color: transparent;
border: 1px solid #ff4d4f;
color: #ff4d4f;
flex-shrink: 0;
margin-left: 10px;
}
.logout-btn:hover {
background-color: #fff2f0;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
/* 功能入口区域 */
.function-section {
border-radius: 12px;
padding: 15px 0;
margin-bottom: 20px;
}
.function-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 15px;
padding: 0 15px;
}
.function-item-content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 10px 0;
cursor: pointer;
transition: all 0.2s ease;
background-color: white;
border-radius: 12px;
height: 80px;
}
.function-item-content:hover {
transform: scale(1.05);
}
.function-icon {
width: 40px;
height: 40px;
margin-bottom: 8px;
display: flex;
align-items: center;
justify-content: center;
}
.function-image {
width: 30px;
height: 30px;
object-fit: contain;
}
.function-text {
font-size: 13px;
color: #333;
}
/* 余额和记录区域 */
.balance-section {
background-color: #2f89ff;
border-radius: 12px;
padding: 15px;
margin-bottom: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
color: white;
position: relative;
height: 85px;
}
.balance-card {
display: flex;
justify-content: space-between;
align-items: center;
height: 100%;
}
.balance-item {
display: flex;
flex-direction: column;
gap: 8px;
flex: 1;
}
.balance-label {
font-size: 14px;
color: rgba(255, 255, 255, 0.9);
letter-spacing: 0.5px;
font-weight: 400;
}
.distribute-checkbox {
color: white;
}
.distribute-checkbox :deep(.el-checkbox__label) {
color: rgba(255, 255, 255, 0.9);
font-size: 12px;
}
.distribute-checkbox :deep(.el-checkbox__input.is-checked .el-checkbox__inner) {
background-color: rgba(255, 255, 255, 0.9) !important;
border-color: rgba(255, 255, 255, 0.9) !important;
}
.distribute-checkbox :deep(.el-checkbox__input.is-checked .el-checkbox__inner::after) {
border-color: #2f89ff !important;
}
.distribute-checkbox :deep(.el-checkbox__inner) {
border-color: rgba(255, 255, 255, 0.6) !important;
background-color: transparent !important;
}
.distribute-checkbox :deep(.el-checkbox__input.is-checked) {
.el-checkbox__inner {
background-color: rgba(255, 255, 255, 0.9) !important;
border-color: rgba(255, 255, 255, 0.9) !important;
}
.el-checkbox__inner::after {
border-color: #2f89ff !important;
}
}
.balance-value {
font-size: 28px;
font-weight: 600;
color: white;
letter-spacing: 0.5px;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
/* 我的订单 */
.order-section {
background-color: white;
border-radius: 12px;
padding: 15px;
margin-bottom: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.section-header h3 {
font-size: 16px;
color: #333;
margin: 0;
}
.view-all {
font-size: 13px;
color: #999;
cursor: pointer;
}
/* 设置区域 */
.settings-section {
background-color: white;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
.setting-item {
display: block;
justify-content: space-between;
align-items: center;
padding: 0px;
border-bottom: 1px solid #f0f0f0;
cursor: pointer;
transition: background-color 0.2s ease;
}
.setting-link {
display: block; /* 转为块级元素,占满父容器宽度 */
width: 100%;
height: 100%;
padding: 15px; /* 将原有的 padding 移到这里,确保点击区域完整 */
text-decoration: none; /* 去除链接默认下划线 */
}
.setting-content {
display: flex; /* 使用 flex 布局,方便内容左右对齐 */
justify-content: space-between; /* 文字左对齐,箭头右对齐 */
align-items: center; /* 垂直居中 */
width: 100%; /* 占满父容器宽度 */
height: 100%; /* 占满父容器高度 */
}
.setting-item:last-child {
border-bottom: none;
}
.setting-item:hover {
background-color: #f9f9f9;
}
.setting-text {
font-size: 15px;
color: #333;
}
.setting-arrow {
color: #ccc;
font-size: 14px;
}
/* 头像上传对话框样式 */
.avatar-uploader {
text-align: center;
}
.avatar-uploader :deep(.el-upload) {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: 0.2s;
width: 178px;
height: 178px;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto;
}
.avatar-uploader :deep(.el-upload:hover) {
border-color: #409eff;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
}
.avatar-preview {
width: 178px;
height: 178px;
object-fit: cover;
}
.balance-content {
display: flex;
align-items: center;
gap: 8px; /* 控制图标和数值之间的间距 */
}
.bean-image {
width: 20px;
height: 20px;
flex-shrink: 0; /* 防止图片被压缩 */
}
.balance-value {
font-size: 28px;
font-weight: 600;
color: white;
letter-spacing: 0.5px;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
white-space: nowrap; /* 防止文本换行 */
}
</style>