文件
yjyf/yjyf-app/pages/mine/mine.vue
YuanWen 9ae2b976a8 feat: 重构首页与全局UI,新增服务选择抽屉与统一导航组件
新增统一导航栏组件YjyfNavBar,替换各页面原生导航
新增服务选择抽屉组件GoodsPickerDrawer,实现分类选品流程
新增tabbar图标矢量资源,优化底部导航样式
新增后端运力统计接口与多商品下单逻辑
修复页面跳转逻辑,统一使用switchTab/redirectTo
优化订单创建与评价页的UI组件化,提升一致性
2026-11-02 18:27:37 +08:00

222 行
9.9 KiB
Vue

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

<template>
<view class="page">
<!-- 统一规范顶部导航栏(安全距离自适应) -->
<YjyfNavBar theme="orange" title="个人中心" :placeholder="true" />
<view class="header">
<view class="deco d1"></view>
<view class="h-row">
<view class="avatar">
<image v-if="user && user.avatar" class="avatar-img" :src="user.avatar" mode="aspectFill"></image>
<text v-else class="avatar-ph">{{ avatarText }}</text>
</view>
<view class="h-info">
<text class="nick">{{ user ? user.nickname : '未登录' }}</text>
<view class="badges">
<text class="badge">{{ roleText }}</text>
<text class="badge light" v-if="master">评分 {{ master.score }}</text>
</view>
</view>
</view>
<view class="h-stats">
<view class="h-stat">
<text class="h-num">{{ user ? user.balance : 0 }}</text>
<text class="h-lab">余额</text>
</view>
<view class="h-stat">
<text class="h-num">{{ unread }}</text>
<text class="h-lab">未读消息</text>
</view>
<view class="h-stat">
<text class="h-num">{{ coupon }}</text>
<text class="h-lab">优惠券</text>
</view>
</view>
</view>
<view class="rolecard">
<view class="role-top">
<text class="role-title">{{ masterCard.title }}</text>
<text class="role-sub">{{ masterCard.desc }}</text>
</view>
<view class="role-btn" @tap="masterCard.action">{{ masterCard.btn }}</view>
</view>
<view class="rolecard">
<view class="role-top">
<text class="role-title">{{ merchantCard.title }}</text>
<text class="role-sub">{{ merchantCard.desc }}</text>
</view>
<view class="role-btn" @tap="merchantCard.action">{{ merchantCard.btn }}</view>
</view>
<view class="menu">
<view class="menu-item" @tap="go('/pages/order/list')">
<text class="menu-t">我的订单</text><text class="menu-a">›</text>
</view>
<view class="menu-item" @tap="go('/pages/address/list')">
<text class="menu-t">服务地址</text><text class="menu-a">›</text>
</view>
<view class="menu-item" @tap="go('/pages/coupon/coupon')">
<text class="menu-t">优惠券</text><text class="menu-a">›</text>
</view>
<view class="menu-item" @tap="go('/pages/message/message')">
<text class="menu-t">消息中心</text><text class="menu-a">›</text>
</view>
<view class="menu-item" @tap="callService">
<text class="menu-t">联系客服</text><text class="menu-a">›</text>
</view>
</view>
<view class="logout" @tap="logout">退出登录</view>
<YjyfTabBar :current="3" />
</view>
</template>
<script setup>
import { ref, computed } from 'vue';
import YjyfTabBar from '@/components/YjyfTabBar.vue';
import { onShow } from '@dcloudio/uni-app';
import api from '@/utils/api.js';
import { isLogin, toLogin, setUser, clearAuth } from '@/utils/request.js';
import { setRole } from '@/utils/role.js';
const user = ref(null);
const master = ref(null);
const merchant = ref(null);
const unread = ref(0);
const coupon = ref(0);
const avatarText = computed(() => (user.value && user.value.nickname ? user.value.nickname.slice(0, 1) : '我'));
const roleText = computed(() => {
if (master.value && master.value.verify_status === 'pass') return '认证师傅';
if (merchant.value && merchant.value.verify_status === 'pass') {
return merchant.value.is_owner === false ? '关联商家' : '认证商家';
}
return '个人用户';
});
const masterCard = computed(() => {
const m = master.value;
if (!m) {
return { title: '成为优匠优服师傅', desc: '实名认证 + 技能认证,接单赚收入', btn: '立即入驻', action: () => go('/pages/master/apply') };
}
if (m.verify_status === 'pending') {
return { title: '师傅资质审核中', desc: '平台将在 1-2 个工作日内完成审核', btn: '查看进度', action: () => go('/pages/master/apply') };
}
if (m.verify_status === 'reject') {
return { title: '资质未通过', desc: m.audit_msg || '请补充资料后重新提交', btn: '重新提交', action: () => go('/pages/master/apply') };
}
return { title: '切换为师傅身份', desc: '进入师傅工作台接单', btn: '切换师傅端', action: switchToMaster };
});
const merchantCard = computed(() => {
const m = merchant.value;
if (!m) {
return { title: '成为优匠优服商家', desc: '企业认证,支持批量下单 / 对账 / 开票', btn: '商家入驻', action: () => go('/pages/merchant/apply') };
}
if (m.verify_status === 'pending') {
return { title: '商家资质审核中', desc: '平台将在 1-2 个工作日内完成审核', btn: '查看进度', action: () => go('/pages/merchant/apply') };
}
if (m.verify_status === 'reject') {
return { title: '商家资质未通过', desc: m.audit_msg || '请补充资料后重新提交', btn: '重新提交', action: () => go('/pages/merchant/apply') };
}
if (m.is_owner === false) {
return { title: m.name || '关联商家账号', desc: '已绑定企业账号,订单 / 账单 / 开票数据与主账号同步', btn: '切换商家端', action: switchToMerchant };
}
return { title: m.name || '我的企业账号', desc: '进入商家工作台:批量下单 / 账单 / 开票', btn: '切换商家端', action: switchToMerchant };
});
function toast(t) { uni.showToast({ title: t, icon: 'none' }); }
function go(url) { uni.navigateTo({ url: url }); }
function backHome() {
const pages = getCurrentPages();
if (pages && pages.length > 1) {
uni.navigateBack();
} else {
uni.switchTab({ url: '/pages/index/index', fail: () => uni.redirectTo({ url: '/pages/index/index' }) });
}
}
function callService() { uni.makePhoneCall({ phoneNumber: '4000000000' }); }
async function load() {
if (!isLogin()) { toLogin(); return; }
try {
const res = await api.profile();
user.value = res;
master.value = res.master;
merchant.value = res.merchant;
unread.value = res.unread_count || 0;
coupon.value = res.coupon_count || 0;
setUser(res);
} catch (e) {}
}
async function switchToMerchant() {
try {
const res = await api.switchRole({ role: 'merchant' });
setRole('merchant');
uni.showToast({ title: '已切换为商家身份' });
setTimeout(() => uni.reLaunch({ url: res.redirect }), 500);
} catch (e) {}
}
async function switchToMaster() {
try {
const res = await api.switchRole({ role: 'master' });
setRole('master');
uni.showToast({ title: '已切换为师傅身份' });
setTimeout(() => uni.reLaunch({ url: res.redirect }), 500);
} catch (e) {}
}
function logout() {
uni.showModal({
title: '退出登录', content: '确认退出当前账号?',
success: (r) => {
if (!r.confirm) return;
clearAuth();
uni.reLaunch({ url: '/pages/login/login' });
}
});
}
onShow(() => load());
</script>
<style scoped>
.page { min-height: 100vh; background: #F5F6F8; padding-bottom: 60rpx; }
.header { position: relative; padding: 40rpx 32rpx 30rpx; overflow: hidden; background: linear-gradient(160deg, #FFB85C 0%, #F98D25 55%, #ED6F0E 100%); }
.deco { position: absolute; width: 320rpx; height: 320rpx; right: -110rpx; top: -130rpx; border-radius: 50%; background: rgba(255,255,255,.14); }
.h-top { position: relative; display: flex; align-items: center; margin-bottom: 16rpx; }
.home-btn { display: flex; align-items: center; height: 54rpx; padding: 0 22rpx 0 16rpx; border-radius: 27rpx; background: rgba(255,255,255,.24); }
.home-btn:active { background: rgba(255,255,255,.38); }
.home-ic { font-size: 34rpx; line-height: 34rpx; color: #fff; margin-top: -4rpx; margin-right: 8rpx; }
.home-tx { font-size: 24rpx; font-weight: 600; color: #fff; }
.h-row { position: relative; display: flex; align-items: center; }
.avatar { width: 120rpx; height: 120rpx; border-radius: 50%; background: rgba(255,255,255,.92); display: flex; align-items: center; justify-content: center; overflow: hidden; }
.avatar-img { width: 120rpx; height: 120rpx; }
.avatar-ph { font-size: 44rpx; font-weight: 700; color: #E88A2C; }
.h-info { flex: 1; min-width: 0; margin-left: 22rpx; display: flex; flex-direction: column; }
.nick { font-size: 36rpx; font-weight: 700; color: #fff; }
.badges { display: flex; margin-top: 12rpx; }
.badge { padding: 0 16rpx; line-height: 36rpx; border-radius: 18rpx; background: rgba(255,255,255,.24); color: #fff; font-size: 20rpx; margin-right: 10rpx; }
.badge.light { background: #FFF3DF; color: #C4580A; font-weight: 600; }
.h-stats { position: relative; display: flex; margin-top: 30rpx; padding-top: 24rpx; border-top: 1rpx solid rgba(255,255,255,.25); }
.h-stat { flex: 1; display: flex; flex-direction: column; align-items: center; }
.h-num { font-size: 34rpx; font-weight: 700; color: #fff; }
.h-lab { margin-top: 4rpx; font-size: 20rpx; color: rgba(255,255,255,.88); }
.rolecard { position: relative; margin: -20rpx 28rpx 0; padding: 26rpx 24rpx; background: #fff; border-radius: 22rpx; box-shadow: 0 10rpx 26rpx rgba(226,120,26,.14); display: flex; align-items: center; }
.role-top { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.role-title { font-size: 30rpx; font-weight: 700; color: #1F1F1F; }
.role-sub { margin-top: 6rpx; font-size: 22rpx; color: #9A9A9A; }
.role-btn { flex-shrink: 0; margin-left: 16rpx; padding: 0 26rpx; line-height: 66rpx; border-radius: 33rpx; color: #fff; font-size: 24rpx; font-weight: 600; background: linear-gradient(135deg,#FFA93F 0%,#F0710D 100%); box-shadow: 0 6rpx 16rpx rgba(240,113,13,.28); }
.menu { margin: 22rpx 28rpx 0; background: #fff; border-radius: 22rpx; overflow: hidden; box-shadow: 0 6rpx 20rpx rgba(240,129,31,.06); }
.menu-item { display: flex; align-items: center; justify-content: space-between; padding: 0 24rpx; height: 100rpx; border-bottom: 1rpx solid #F4F4F4; }
.menu-item:last-child { border-bottom: none; }
.menu-t { font-size: 28rpx; color: #333; }
.menu-a { font-size: 30rpx; color: #C4C4C4; }
.logout { margin: 30rpx 28rpx 0; height: 92rpx; display: flex; align-items: center; justify-content: center; background: #fff; border-radius: 46rpx; font-size: 28rpx; color: #999; }
</style>