文件
yjyf/yjyf-app/pages/master/workbench.vue
YuanWen 361dffc7d8 feat: 新增师傅端功能及服务增项管理模块
- 新增师傅端路由和页面配置,切换登录后默认跳转到师傅大厅
- 新增服务分类图标上传功能和增项服务管理模块
- 新增订单、申诉、结算等模型关联关系及后台控制器
- 新增师傅端TabBar组件和消息页面跳转逻辑
- 新增文件上传接口和服务计算逻辑
- 新增各类SVG图标和空状态提示
2026-11-03 15:45:21 +08:00

308 行
12 KiB
Vue

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

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

<template>
<view class="page">
<!-- 1. 师傅顶部个人名片 -->
<view class="profile-hero">
<view class="hero-deco d1"></view>
<view class="hero-deco d2"></view>
<view class="user-block">
<view class="avatar-wrap">
<image class="avatar-img" src="/static/icons/user-avatar.svg" mode="aspectFit" />
</view>
<view class="user-meta">
<view class="name-row">
<text class="user-name">{{ info ? info.realname : '师傅' }}</text>
<view class="auth-tag">
<image class="auth-ico" src="/static/icons/shield.svg" mode="aspectFit" />
<text class="auth-txt">实名认证师傅</text>
</view>
</view>
<text class="user-level" v-if="info">
等级 Lv{{ info.level }} · 综合评分 {{ info.score }} 分 · 履约 {{ info.order_count }} 单
</text>
</view>
<view class="status-pill clickable-hover" :class="{ off: info && info.work_status === 'off' }" @tap="toggleWork">
<view class="status-dot"></view>
<text class="status-txt">{{ info && info.work_status === 'off' ? '暂停接单' : '听单中' }}</text>
</view>
</view>
<!-- 2. 资产工钱大卡(对标工奇兵待结算工钱) -->
<view class="assets-box">
<view class="asset-top">
<text class="asset-title">可提现工钱(元)</text>
<view class="bill-link clickable-hover" @tap="go('/pages/master/wallet')">
<text class="bill-txt">账单明细</text>
<image class="arrow-ico" src="/static/icons/arrow-right.svg" mode="aspectFit" />
</view>
</view>
<view class="asset-main">
<text class="asset-val">{{ info ? Number(info.money).toFixed(2) : '0.00' }}</text>
<view class="withdraw-btn clickable-hover" @tap="go('/pages/master/wallet')">
申请提现
</view>
</view>
<view class="asset-sub">
<text class="sub-item">今日收入 ¥{{ todayIncome }}</text>
<text class="sub-divider">|</text>
<text class="sub-item">累计收入 ¥{{ totalIncome }}</text>
</view>
</view>
</view>
<!-- 3. 核心功能菜单组(对标工奇兵功能列表) -->
<view class="menu-card">
<view class="menu-item clickable-hover" @tap="go('/pages/master/orders')">
<view class="mi-left">
<image class="mi-ico" src="/static/icons/master/menu-orders.svg" mode="aspectFit" />
<text class="mi-title">我的接单管理</text>
</view>
<view class="mi-right">
<text class="mi-badge" v-if="info && info.todo_count > 0">{{ info.todo_count }} 待办</text>
<image class="mi-arrow" src="/static/icons/arrow-right.svg" mode="aspectFit" />
</view>
</view>
<view class="menu-item clickable-hover" @tap="go('/pages/master/quote')">
<view class="mi-left">
<image class="mi-ico" src="/static/icons/master/menu-quote.svg" mode="aspectFit" />
<text class="mi-title">报价记录与管理</text>
</view>
<view class="mi-right">
<text class="mi-tip">查看出价状态</text>
<image class="mi-arrow" src="/static/icons/arrow-right.svg" mode="aspectFit" />
</view>
</view>
<view class="menu-item clickable-hover" @tap="go('/pages/master/credit')">
<view class="mi-left">
<image class="mi-ico" src="/static/icons/master/menu-credit.svg" mode="aspectFit" />
<text class="mi-title">服务信用与申诉</text>
</view>
<view class="mi-right">
<text class="mi-tip" v-if="credit">好评率 {{ credit.good_rate }}%</text>
<image class="mi-arrow" src="/static/icons/arrow-right.svg" mode="aspectFit" />
</view>
</view>
<view class="menu-item clickable-hover" @tap="callService">
<view class="mi-left">
<image class="mi-ico" src="/static/icons/master/menu-service.svg" mode="aspectFit" />
<text class="mi-title">联系平台客服</text>
</view>
<view class="mi-right">
<text class="mi-tip">4000000000</text>
<image class="mi-arrow" src="/static/icons/arrow-right.svg" mode="aspectFit" />
</view>
</view>
</view>
<!-- 4. 服务技能与品类 -->
<view class="card">
<view class="card-head">
<text class="card-title">已认证服务类目</text>
<text class="card-tip">{{ skills.length }} 项技能</text>
</view>
<view class="skills-box">
<text class="skill-tag" v-for="s in skills" :key="s.cat_id">{{ s.name }}</text>
<text class="skill-tag empty" v-if="!skills.length">暂无已认证类目</text>
</view>
</view>
<!-- 5. 身份切换通道(对标工奇兵我要下单找师傅) -->
<view class="switch-role-card clickable-hover" @tap="backToUser">
<view class="src-left">
<image class="src-ico" src="/static/icons/role-switch.svg" mode="aspectFit" />
<view class="src-text">
<text class="src-title">我要下单找师傅</text>
<text class="src-desc">切换至业主身份发布安装维修需求</text>
</view>
</view>
<image class="src-arrow" src="/static/icons/arrow-right-orange.svg" mode="aspectFit" />
</view>
<!-- 师傅端专属底部导航 (当前 Tab 3: 我的) -->
<YjyfMasterTabBar :current="3" />
</view>
</template>
<script setup>
import { ref } from 'vue';
import { onShow } from '@dcloudio/uni-app';
import api from '@/utils/api.js';
import { isLogin, toLogin } from '@/utils/request.js';
import { setRole } from '@/utils/role.js';
import YjyfMasterTabBar from '@/components/YjyfMasterTabBar.vue';
const info = ref(null);
const credit = ref(null);
const skills = ref([]);
const todayIncome = ref('0.00');
const totalIncome = ref('0.00');
function go(url) {
uni.navigateTo({ url });
}
function callService() {
uni.makePhoneCall({ phoneNumber: '4000000000' });
}
async function load() {
if (!isLogin()) { toLogin(); return; }
try {
const res = await api.masterInfo();
if (!res.applied || res.master.verify_status !== 'pass') {
uni.redirectTo({ url: '/pages/master/apply' });
return;
}
info.value = res.master;
skills.value = res.skills || [];
} catch (e) {}
try {
const inc = await api.masterIncome();
if (inc) {
todayIncome.value = Number(inc.today_income || 0).toFixed(2);
totalIncome.value = Number(inc.total_income || 0).toFixed(2);
}
} catch (e) {}
try {
credit.value = await api.masterCredit();
} catch (e) {}
}
async function toggleWork() {
if (!info.value) return;
const next = info.value.work_status === 'off' ? 'on' : 'off';
try {
await api.masterToggle({ work_status: next });
info.value.work_status = next;
uni.showToast({ title: next === 'on' ? '已开启接单听单' : '已暂停接单', icon: 'none' });
} catch (e) {}
}
function backToUser() {
api.switchRole({ role: 'user' }).then((res) => {
setRole('user');
uni.reLaunch({ url: res.redirect || '/pages/index/index' });
}).catch(() => {});
}
onShow(() => load());
</script>
<style scoped>
.page { min-height: 100vh; background: #F5F6F8; padding-bottom: 40rpx; }
/* 顶部名片 */
.profile-hero {
position: relative; overflow: hidden; padding: 36rpx 28rpx 30rpx;
background: linear-gradient(160deg, #FFB85C 0%, #F98D25 55%, #ED6F0E 100%);
}
.hero-deco { position: absolute; border-radius: 50%; background: rgba(255,255,255,0.13); }
.d1 { width: 320rpx; height: 320rpx; right: -120rpx; top: -130rpx; }
.d2 { width: 180rpx; height: 180rpx; left: -80rpx; bottom: -60rpx; }
.user-block { position: relative; display: flex; align-items: center; justify-content: space-between; }
.avatar-wrap {
width: 104rpx; height: 104rpx; border-radius: 52rpx; background: #fff;
display: flex; align-items: center; justify-content: center; flex-shrink: 0;
box-shadow: 0 4rpx 14rpx rgba(0,0,0,0.1);
}
.avatar-img { width: 72rpx; height: 72rpx; }
.user-meta { flex: 1; min-width: 0; margin-left: 20rpx; }
.name-row { display: flex; align-items: center; }
.user-name { font-size: 36rpx; font-weight: 700; color: #fff; }
.auth-tag {
margin-left: 12rpx; display: flex; align-items: center; padding: 2rpx 12rpx;
border-radius: 8rpx; background: rgba(255,255,255,0.22);
}
.auth-ico { width: 20rpx; height: 20rpx; margin-right: 6rpx; }
.auth-txt { font-size: 18rpx; color: #fff; font-weight: 600; }
.user-level { margin-top: 8rpx; font-size: 22rpx; color: rgba(255,255,255,0.92); }
.status-pill {
display: flex; align-items: center; padding: 0 22rpx; height: 54rpx;
border-radius: 27rpx; background: #fff; box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.08);
}
.status-pill.off { background: rgba(255,255,255,0.35); }
.status-dot { width: 12rpx; height: 12rpx; border-radius: 50%; background: #2FA84F; margin-right: 8rpx; }
.status-pill.off .status-dot { background: #E8331B; }
.status-txt { font-size: 22rpx; font-weight: 600; color: #E36B0C; }
.status-pill.off .status-txt { color: #fff; }
/* 资产大卡 */
.assets-box {
position: relative; margin-top: 30rpx; padding: 24rpx 24rpx 20rpx;
border-radius: 20rpx; background: rgba(255,255,255,0.18);
backdrop-filter: blur(10px);
}
.asset-top { display: flex; align-items: center; justify-content: space-between; }
.asset-title { font-size: 24rpx; color: rgba(255,255,255,0.9); }
.bill-link { display: flex; align-items: center; }
.bill-txt { font-size: 22rpx; color: #fff; font-weight: 500; }
.arrow-ico { width: 22rpx; height: 22rpx; margin-left: 4rpx; }
.asset-main { display: flex; align-items: baseline; justify-content: space-between; margin-top: 10rpx; }
.asset-val { font-size: 56rpx; font-weight: 700; color: #fff; line-height: 1.1; }
.withdraw-btn {
padding: 0 32rpx; height: 56rpx; line-height: 56rpx; border-radius: 28rpx;
background: #fff; color: #E36B0C; font-size: 24rpx; font-weight: 700;
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.08);
}
.asset-sub { display: flex; align-items: center; margin-top: 14rpx; padding-top: 14rpx; border-top: 1rpx solid rgba(255,255,255,0.22); }
.sub-item { font-size: 22rpx; color: rgba(255,255,255,0.9); }
.sub-divider { margin: 0 16rpx; font-size: 18rpx; color: rgba(255,255,255,0.5); }
/* 菜单组 */
.menu-card {
margin: 20rpx 24rpx 0; background: #fff; border-radius: 20rpx;
box-shadow: 0 6rpx 20rpx rgba(240,129,31,.06); overflow: hidden;
}
.menu-item {
display: flex; align-items: center; justify-content: space-between;
padding: 28rpx 24rpx; border-bottom: 1rpx solid #F5F5F5;
}
.menu-item:last-child { border-bottom: none; }
.mi-left { display: flex; align-items: center; }
.mi-ico { width: 40rpx; height: 40rpx; margin-right: 18rpx; flex-shrink: 0; }
.mi-title { font-size: 28rpx; font-weight: 600; color: #2C2C2E; }
.mi-right { display: flex; align-items: center; }
.mi-badge {
padding: 2rpx 14rpx; border-radius: 14rpx; background: #FDEEEB;
color: #E8331B; font-size: 20rpx; font-weight: 600; margin-right: 8rpx;
}
.mi-tip { font-size: 24rpx; color: #9A9A9A; margin-right: 8rpx; }
.mi-arrow { width: 24rpx; height: 24rpx; }
/* 类目卡片 */
.card {
margin: 20rpx 24rpx 0; padding: 24rpx; background: #fff; border-radius: 20rpx;
box-shadow: 0 6rpx 20rpx rgba(240,129,31,.06);
}
.card-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14rpx; }
.card-title { font-size: 28rpx; font-weight: 700; color: #1F1F1F; }
.card-tip { font-size: 22rpx; color: #9A9A9A; }
.skills-box { display: flex; flex-wrap: wrap; }
.skill-tag {
padding: 0 22rpx; line-height: 52rpx; margin: 0 12rpx 12rpx 0;
border-radius: 26rpx; background: #FFF3E6; color: #C4580A; font-size: 22rpx; font-weight: 500;
}
.skill-tag.empty { background: #F5F5F5; color: #9A9A9A; }
/* 切换业主角色卡片 */
.switch-role-card {
margin: 24rpx 24rpx 0; padding: 24rpx; background: #fff; border-radius: 20rpx;
display: flex; align-items: center; justify-content: space-between;
box-shadow: 0 6rpx 20rpx rgba(240,129,31,.06);
}
.src-left { display: flex; align-items: center; }
.src-ico { width: 44rpx; height: 44rpx; margin-right: 18rpx; flex-shrink: 0; }
.src-text { display: flex; flex-direction: column; }
.src-title { font-size: 28rpx; font-weight: 700; color: #1F1F1F; }
.src-desc { margin-top: 4rpx; font-size: 22rpx; color: #8E8E93; }
.src-arrow { width: 28rpx; height: 28rpx; }
</style>