mrr.sj.front/pages/home/home.vue

1664 lines
42 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="homePage">
<image class="page-bg"
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/0901dc51-ff5a-427f-aab1-993d80aced79.png"
mode="widthFix"></image>
<view class="header-section">
<view class="user-info" @tap="handleLoginRedirect">
<image mode="aspectFill" class="avatar" :src="currentAvatar"></image>
<view class="info-text">
<view class="name">{{ currentName }}</view>
<view class="desc">欢迎登录美融融商家版</view>
</view>
</view>
<view class="status-toggle" :class="{ 'is-closed': !isOpenStore }" @tap="toggleStatus"
v-if="isLogin && userInfo.id_type > 0">
<text class="status-text">{{ isOpenStore ? '营业中' : '休息中' }}</text>
<view class="toggle-dot"></view>
</view>
</view>
<view class="home-banner" @tap="goToGrowthPackage">
<image class="home-banner-img" src="@/static/images/home/banner.png" mode="aspectFill"></image>
</view>
<view class="setup-modal-mask" v-if="showSetupModal" catchtouchmove="true">
<view class="setup-modal-content">
<image class="modal-bg"
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/ac2cb4aa-a7c5-4776-98f4-b77e9c0cfa85.png">
</image>
<view class="modal-header">
<text>温馨提示</text>
</view>
<view class="modal-body">
<text class="modal-subtitle">发布服务请完善以下资料:</text>
<view class="missing-list">
<view class="missing-item" v-if="!hasBusinessTime">
<view class="item-left">
<image class="item-icon"
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/21b26b44-5e36-4291-8900-705a3d5bfa90.png">
</image>
<view class="item-texts">
<text class="item-title">营业时间未配置</text>
<text class="item-desc">请完善店铺的营业时间</text>
</view>
</view>
<view class="item-right orange" @tap="goToBusinessTime">去配置</view>
</view>
<view class="missing-item" v-if="!hasServiceSkill">
<view class="item-left">
<image class="item-icon"
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/21b26b44-5e36-4291-8900-705a3d5bfa90.png">
</image>
<view class="item-texts">
<text class="item-title">服务技能未配置</text>
<text class="item-desc">请完善服务技能</text>
</view>
</view>
<view class="item-right orange" @tap="goToServiceSkill">去配置</view>
</view>
<view class="missing-item" v-if="!isCertified">
<view class="item-left">
<image class="item-icon"
:src="isUnderReview ? 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/25c444be-a66e-45d6-b50d-922149ff059f.png' : 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/21b26b44-5e36-4291-8900-705a3d5bfa90.png'">
</image>
<view class="item-texts">
<!-- 只有在审核中时赋予蓝色样式 blue-text -->
<text class="item-title" :class="{ 'blue-text': isUnderReview }">{{ isUnderReview ?
'资质认证中' : '资质未认证' }}</text>
<text class="item-desc">请完成资质认证</text>
</view>
</view>
<!-- 只有在审核中时为灰色,其余未认证状态为浅绿色 light-green -->
<view class="item-right" :class="isUnderReview ? 'grey' : 'light-green'" @tap="goToAuth">
{{ isUnderReview ? '查看进度' : '去认证' }}
</view>
</view>
</view>
</view>
<view class="modal-footer">
<view class="btn-know" @tap="closeSetupModal">知道了</view>
</view>
</view>
</view>
<view class="settlement-guide card-box" v-if="isLogin && isUserInfoLoaded && !isCertified">
<view class="guide-header"
:style="{ backgroundImage: 'url(' + (isSettled ? 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/c3a6cdd8-0447-4b50-a8cc-7bab1eedb502.png' : 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/138ccbc3-486d-4287-b67f-f809de99e408.png') + ')' }">
<view class="guide-body">
<text class="guide-text">{{ isSettled ? '完成认证,开启您的接单之路' : '入驻美融融,生意更轻松' }}</text>
<view class="guide-btn" @tap="goToSettle">{{ isSettled ? '去认证' : '去入驻' }}</view>
</view>
</view>
</view>
<view class="quick-actions card-box" v-if="isLogin && isCertified">
<view class="action-item" v-for="(item, index) in quickActions" :key="index" @tap="handleQuickAction(item)">
<image class="action-icon" :src="item.icon"></image>
<text class="action-text">{{ item.name }}</text>
</view>
</view>
<view class="order-stats-basic card-box" v-if="isLogin && isCertified">
<view class="card-header">
<view class="header-left">
<text class="title">接单管理</text>
<text class="update-time">更新时间:{{ orderBasic.updateTime }}</text>
<image class="refresh-icon"
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/31c6eebc-5538-4c66-9b32-24dced170a99.png"
@tap="fetchOrderNum"></image>
</view>
<view class="header-right" @tap="goToOrderList">更多数据 <image class="more-right-icon"
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/ee119e24-1cb2-43df-86e6-61af8e9fd0ad.png">
</image>
</view>
</view>
<view class="stats-content" @tap="goToOrderList">
<view class="stat-item">
<text class="num">{{ orderBasic.waitStart }}</text>
<text class="label">待开始</text>
</view>
<view class="stat-item">
<text class="num">{{ orderBasic.waitFinish }}</text>
<text class="label">待完成</text>
</view>
<view class="stat-item">
<text class="num">{{ orderBasic.finished }}</text>
<text class="label">已取消</text>
</view>
</view>
</view>
<view class="order-stats-detail card-box" v-if="isLogin && isCertified">
<view class="card-header">
<view class="header-left">
<text class="title">当日实时数据</text>
<text class="update-time">更新时间:{{ orderDetail.updateTime }}</text>
<image class="refresh-icon"
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/31c6eebc-5538-4c66-9b32-24dced170a99.png"
@tap="fetchRealTimeData"></image>
</view>
</view>
<view class="detail-content">
<view class="score-dashboard">
<view class="ring-track"></view>
<view class="ring-progress" :style="{ 'background': progressGradient }"></view>
<view class="dot start-dot" v-if="displayScore > 0"></view>
<view class="dot end-dot" v-if="displayScore > 0"
:style="{ 'transform': `rotate(-${displayScoreDeg}deg) translateY(-67rpx)` }"></view>
<text class="score-num">{{ displayScore.toFixed(1) }}</text>
<image class="score-tag-img"
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/52005bc0-2896-407d-8826-f34539637d20.png"
mode="aspectFit"></image>
</view>
<view class="detail-grid">
<view class="grid-item">
<text class="label">访问人数</text>
<view class="val-box"><text class="val">{{ orderDetail.visitors }}</text> <text
class="unit">人</text></view>
<text class="yesterday">昨日{{ orderDetail.yVisitors }}人</text>
</view>
<view class="grid-item">
<text class="label">下单量</text>
<view class="val-box"><text class="val">{{ orderDetail.orders }}</text> <text
class="unit">单</text></view>
<text class="yesterday">昨日{{ orderDetail.yOrders }}单</text>
</view>
<view class="grid-item">
<text class="label">成交金额</text>
<view class="val-box">
<text class="val">{{ formattedAmount.val }}</text>
<text class="unit">{{ formattedAmount.unit }}</text>
</view>
<text class="yesterday">昨日{{ formattedYAmount.val }}{{ formattedYAmount.unit }}</text>
</view>
</view>
</view>
</view>
<view class="notice-bar card-box">
<image class="notice-icon"
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/75f624b8-a6ed-4787-9380-1a087062805b.png">
</image>
<swiper class="notice-swiper" vertical autoplay circular interval="3000">
<swiper-item v-for="(msg, index) in noticeList" :key="index" @tap="goToNotice(msg)">
<text class="notice-text line-1">{{ msg.title }}</text>
</swiper-item>
</swiper>
<view class="notice-btn" @tap="goToNoticeList">点击查看</view>
</view>
<view class="brands-section card-box">
<view class="card-header">
<view class="header-left"><text class="title" style="margin-left: 10rpx;">精选品牌</text></view>
<view class="header-right brand-more" @tap="goToBrandList">更多品牌 <image class="more-right-icon"
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/ee119e24-1cb2-43df-86e6-61af8e9fd0ad.png">
</image>
</view>
</view>
<view class="brand-grid">
<brand-card class="brand-card-wrap" v-for="(item, index) in brands" :key="index" :item="item"
:tagKeys="item.tagKeys"></brand-card>
</view>
<view class="home-empty" v-if="brands.length === 0">
<noData top="20rpx"></noData>
</view>
</view>
<view class="skills-section card-box">
<view class="card-header">
<view class="header-left"><text class="title">技能集市</text></view>
<view class="header-right" @tap="goToSkillList">更多技能 <image class="more-right-icon"
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/ee119e24-1cb2-43df-86e6-61af8e9fd0ad.png">
</image>
</view>
</view>
<view class="skill-list">
<skill-card v-for="(item, index) in skills" :key="index" :item="item"></skill-card>
</view>
<view class="home-empty" v-if="skills.length === 0">
<noData top="20rpx"></noData>
</view>
</view>
<view style="height: 120rpx;"></view>
<TabBar :selected="0"></TabBar>
</view>
</template>
<script>
// 全局变量:用于控制每次杀进程启动后,只在首页弹一次弹窗
let hasShownSetupModal = false;
import TabBar from "@/components/tabbar/tabbar.vue"
import projectTabs from "./components/project-tabs.vue";
import request from "@/utils/request";
import filters from "./components/filters.vue";
import CustomNavbar from "@/components/custom-navbar/custom-navbar.vue";
import BrandCard from "./components/brand-card.vue";
import SkillCard from "./components/skill-card.vue";
export default {
components: {
filters,
projectTabs,
CustomNavbar,
TabBar,
BrandCard,
SkillCard
},
data() {
return {
isLogin: false,
isUserInfoLoaded: false, // 新增:标识用户信息是否已经加载完毕
userInfo: {},
authDetails: null, // 用于存储获取到的真实入驻详情配置
sjId: null,
showSetupModal: false, // 弹窗开关控制
displayScore: 0,
animationTimer: null,
noticeList: [{
id: 0,
title: '',
url: ''
}],
quickActions: [{
name: '扫码验券',
icon: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/b128e423-e4b5-4ad6-bdf5-522de7a9bc68.png',
path: '/pages/shop/verify/verify-order'
},
{
name: '服务管理',
icon: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/a90f02a2-9d39-440a-ad20-a4337bcd1b38.png',
path: '/pages/artisan/service-list'
},
{
name: '工位管理',
icon: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/4c503603-6c2f-4a7a-9329-5e54b8ca769e.png',
path: '/pages/shop/workSpace/list'
},
{
name: '财务管理',
icon: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/728c659e-e395-4473-b83e-7e04539f7ec6.png',
path: '/pages/wallet/wallet'
}
],
orderBasic: {
updateTime: '--:--',
waitStart: 0,
waitFinish: 0,
finished: 0
},
orderDetail: {
updateTime: '--:--',
score: '0.0',
visitors: 0,
yVisitors: 0,
orders: 0,
yOrders: 0,
amount: '0.00',
yAmount: '0.00'
},
brands: [],
skills: []
};
},
computed: {
// 格式化今日成交金额
formattedAmount() {
return this.formatMoney(this.orderDetail.amount);
},
// 格式化昨日成交金额
formattedYAmount() {
return this.formatMoney(this.orderDetail.yAmount);
},
// 依靠后端实际返回的资料详情来判断是否真的提交过申请,而不是无脑信 id_type: -1
isUnderReview() {
if (!this.isLogin) return false;
// 只要拿到了具体的入驻详情数据对象(比如填了各种地址、执照等,内容肯定不为空),就说明填过表了
if (this.authDetails && Object.keys(this.authDetails).length > 0) {
// 如果后端是用特定的状态值判断,比如 this.authDetails.state === 0你可以自己加 && 后面
return true;
}
return false; // 啥也没返回,说明只是刚注册没填资料
},
// 判断是否具备接单的大前提条件
canOpenStore() {
return this.hasBusinessTime && this.hasServiceSkill && this.isCertified;
},
// 修复:使用 authDetails 里的真实接口返回字段判定服务技能
hasServiceSkill() {
// 检查 userInfo 中是否有值
const fromUser = this.userInfo && Array.isArray(this.userInfo.servers_kill) && this.userInfo.servers_kill
.length > 0;
// 检查 authDetails 中是否有值
const fromAuth = this.authDetails && Array.isArray(this.authDetails.servers_kill) && this.authDetails
.servers_kill.length > 0;
// 只要有一边配了,就算已配置
return this.isLogin && (fromUser || fromAuth);
},
// 状态显示完全由计算属性控制,去除了手动修改可能导致的冲突
isOpenStore() {
// 如果连开门的前提条件都没满足,强制显示“休息中”
if (!this.canOpenStore) {
return false;
}
// 满足条件后,听后端的 (使用 == 防止后端返回字符串 "1")
return this.userInfo.order_taking == 1;
},
// 状态管理判定
// 是否已入驻 (0: 未入驻, -1: 入驻第一步, >0: 已认证) 只要不等于0说明已经开启了入驻流程
isSettled() {
// 加上 typeof 判断,防止初始空对象时 undefined !== 0 造成的误判
if (!this.isLogin || typeof this.userInfo.id_type === 'undefined') return false;
return this.userInfo.id_type !== 0;
},
// 是否认证通过 (根据规则,> 0 说明认证通过了)
isCertified() {
if (!this.isLogin || typeof this.userInfo.id_type === 'undefined') return false;
return this.userInfo.id_type > 0;
},
// 优化兼容判定营业时间userInfo 和 authDetails 里只要有就算配置了)
hasBusinessTime() {
const timeFromUser = this.userInfo && this.userInfo.business_time;
const timeFromAuth = this.authDetails && this.authDetails.business_time;
return this.isLogin && !!(timeFromUser || timeFromAuth);
},
// 根据登录状态动态返回头像
currentAvatar() {
if (!this.isLogin || !this.userInfo.head_photo) {
return 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/4099a78b-d3bd-4cf5-8599-68dfe751af16.png'; // 默认未登录头像
}
return this.userInfo.head_photo;
},
// 根据登录状态动态返回昵称
currentName() {
if (!this.isLogin) return '未登录';
return this.userInfo.name || this.userInfo.nick_name || '手机用户';
},
displayScoreDeg() {
const score = this.displayScore || 0;
const percentage = Math.min(Math.max(score, 0), 100);
return (percentage / 100) * 360;
},
progressGradient() {
return `conic-gradient(
transparent 0deg,
transparent calc(360deg - ${this.displayScoreDeg}deg),
#FF4D4F calc(360deg - ${this.displayScoreDeg}deg),
#FF4D4F 360deg
)`;
}
},
onShow() {
this.refreshPageState();
},
onPullDownRefresh() {
// 1. 重新请求首页需要的所有数据
this.refreshPageState();
// 2. 停止下拉刷新的动画
setTimeout(() => {
uni.stopPullDownRefresh();
}, 500);
},
onLoad() {
uni.hideTabBar();
uni.$on('appShowRefresh', this.refreshPageState);
},
onUnload() {
uni.$off('appShowRefresh', this.refreshPageState);
},
watch: {
'orderDetail.score'() {
this.animateScore();
}
},
methods: {
refreshPageState() {
const hasToken = !!uni.getStorageSync("accessToken");
this.isLogin = hasToken;
if (hasToken) {
this.getUserInfo();
} else {
this.userInfo = {};
this.authDetails = null;
this.isUserInfoLoaded = false;
this.sjId = null;
}
// 通知、品牌、技能 这三个接口不强依赖登录,无论登没登录都获取
this.fetchNoticeList();
this.fetchBrandList();
this.fetchSkillList();
},
// 金额转换万单位处理
formatMoney(amount) {
let num = parseFloat(amount);
if (isNaN(num)) return {
val: '0',
unit: '元'
};
if (num >= 10000) {
// 达到一万及以上除以10000最多保留两位小数parseFloat会自动去掉末尾的0
let val = parseFloat((num / 10000).toFixed(2));
return {
val: val,
unit: '万元'
};
}
// 小于一万,原样返回,单位为元
return {
val: parseFloat(num.toFixed(2)),
unit: '元'
};
},
// 处理登录跳转
handleLoginRedirect() {
// 如果 isLogin 为 false未登录状态则跳转到登录页
if (!this.isLogin) {
uni.navigateTo({
url: '/pages/blogPopup/blogPopup'
});
}
},
goToGrowthPackage() {
uni.navigateTo({
url: '/pages/home/growth-package'
});
},
// 弹窗中的快捷跳转功能
closeSetupModal() {
this.showSetupModal = false;
},
goToBusinessTime() {
this.showSetupModal = false;
uni.navigateTo({
url: '/pages/shop/business-time'
});
},
goToServiceSkill() {
this.showSetupModal = false;
uni.navigateTo({
url: '/pages/shop/service-skills'
});
},
// 专门给【提示弹窗】点击用的 (查看进度 / 去认证)
goToAuth() {
// 1. 点击后先关闭提示弹窗
this.showSetupModal = false;
// 2. 根据咱们新的严谨判断,决定它是真提交了还是纯没填
if (this.isUnderReview) {
// === 查看进度 ===
uni.navigateTo({
url: '/pages/ruzhu/ruzhu?type=1'
});
} else {
// === 去认证 / 被驳回重新认证 ===
this.navigateToForm();
}
},
// 专门给【横幅】点击用的 (去入驻 / 去认证)
goToSettle() {
if (!this.isSettled) {
// 完全没入驻过(新手),从头开始
uni.navigateTo({
url: '/pages/ruzhu/ruzhu'
});
} else if (this.isUnderReview) {
// 如果在横幅里处于审核中,也送去进度页
uni.navigateTo({
url: '/pages/ruzhu/ruzhu?type=1'
});
} else {
// 已经开启入驻但没完成表单,继续去填表!
this.navigateToForm();
}
},
// 提取公共的【去填表】路由分发大脑
navigateToForm() {
if (this.isSettled) {
// 已经开启入驻(老手),直接空降到 ruzhu 的最后一个流程第3步门店信息
uni.navigateTo({
url: '/pages/ruzhu/ruzhu?step=3'
});
} else {
// 完全没入驻过(纯新手),老老实实从第 0 步开始
uni.navigateTo({
url: '/pages/ruzhu/ruzhu'
});
}
},
goToBrandList() {
if (!this.isLogin) {
uni.navigateTo({
url: '/pages/blogPopup/blogPopup'
});
return;
}
if (this.brands.length === 0) {
uni.showToast({
title: '暂无数据',
icon: 'none'
});
return;
}
uni.navigateTo({
url: '/pages/jingxuan/selected-brands'
});
},
goToSkillList() {
if (!this.isLogin) {
uni.navigateTo({
url: '/pages/blogPopup/blogPopup'
});
return;
}
if (this.skills.length === 0) {
uni.showToast({
title: '暂无数据',
icon: 'none'
});
return;
}
uni.navigateTo({
url: '/pages/jingxuan/selected-skills'
});
},
// 处理金刚区点击事件 (包含扫码验券核心逻辑)
handleQuickAction(item) {
if (item.name === '扫码验券') {
uni.navigateTo({
url: '/pages/shop/verify/verify-order',
fail: (err) => {
console.error('跳转失败:', err);
}
});
return;
} // 2. 通用跳转逻辑
else if (item.path) {
uni.navigateTo({
url: item.path,
fail: () => {
uni.showToast({
title: '页面开发中...',
icon: 'none'
});
}
});
}
},
// 格式化时间 (提取 HH:mm)
formatTime(dateTimeStr) {
if (!dateTimeStr) return '--:--';
const arr = dateTimeStr.split(' ');
if (arr.length > 1) {
return arr[1].substring(0, 5); // 截取 HH:mm
}
return dateTimeStr;
},
getUserInfo() {
this.isUserInfoLoaded = false;
return request.post("/sj/user/getUser").then((result) => {
if (result.code == 200 && result.data) {
this.userInfo = result.data;
this.isLogin = true;
this.isUserInfoLoaded = true; // 数据回来了,允许相关 UI 进行渲染
uni.setStorageSync("sjId", result.data.id);
this.sjId = result.data.id;
// 优化 1只有完全通过认证才去后台查单量和收益数据
if (this.isCertified) {
this.fetchOrderNum();
this.fetchRealTimeData();
}
// 优化 2只要开启了入驻流程哪怕只是在“去认证”阶段依然需要拉详情来判断有没有补全三大件
if (this.isSettled) {
this.fetchAuthDetails();
}
}
}).catch((err) => {
if (!uni.getStorageSync("accessToken")) {
this.isLogin = false;
this.userInfo = {};
this.authDetails = null;
this.sjId = null;
}
this.isUserInfoLoaded = false;
console.error("首页获取商家信息失败:", err);
});
},
// 获取入驻、修改资料审核详情(获取服务技能、营业时间等准确数据)
fetchAuthDetails() {
request.post("/sj/userSjAuth/details", {
type: 1, // 审核类型 1入驻申请
id_type: '1' // 平台商家
}).then(res => {
if (res.code == 200 && res.data) {
this.authDetails = res.data;
}
// 只有确保拉到了配置详情后,再来判断三大件有没有配全
// 如果没有配全,且是首次大退打开进入首页,拉起弹窗
if (!this.canOpenStore && !hasShownSetupModal) {
this.showSetupModal = true;
hasShownSetupModal = true;
}
});
},
fetchOrderNum() {
request.post("/sj/poster/orderNum").then(res => {
if (res.code == 200 && res.data) {
this.orderBasic = {
updateTime: this.formatTime(res.data.now_date),
waitStart: res.data.dfw || 0,
waitFinish: res.data.dwc || 0,
finished: res.data.yqx || 0
};
}
});
},
// 获取:首页实时数据
fetchRealTimeData() {
request.post("/sj/poster/realTimeData").then(res => {
if (res.code == 200 && res.data) {
const today = res.data.today || {};
const yesterday = res.data.yesterday || {};
this.orderDetail = {
updateTime: this.formatTime(res.data.now_date),
score: today.rating_num || 0,
visitors: today.visitor_num || 0,
yVisitors: yesterday.visitor_num || 0,
orders: today.place_order || 0,
yOrders: yesterday.place_order || 0,
amount: today.deal_money || 0,
yAmount: yesterday.deal_money || 0
};
}
});
},
// 获取:通知公告列表
fetchNoticeList() {
request.post('/sj/notice/list', {
page: 1,
limit: 5
}).then(res => {
if (res.code == 200 && res.data && res.data.list.length > 0) {
this.noticeList = res.data.list;
}
});
},
// 获取:品牌列表
fetchBrandList() {
request.post('/sj/brand/list', {
page: 1,
limit: 4,
poster_recommend: 1
}).then(res => {
if (res.code == 200 && res.data && res.data.list) {
this.brands = res.data.list.map(item => {
let tags = [];
if (item.franchise_state === 1) tags.push('franchise');
if (item.student_state === 1) tags.push('recruit');
if (tags.length === 0) tags.push('more');
return {
id: item.id,
name: item.name,
bgSrc: item.cover_img ||
'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/d02b2d8b-0e01-4c6d-bbee-c60dccf4867c.jpg',
avatar: item.logo_img ||
'https://dummyimage.com/100x100/ccc/fff.png&text=Avatar',
tagKeys: tags
};
});
} else {
this.brands = [];
}
}).catch(() => {
this.brands = [];
});
},
// 获取:技能集市列表
fetchSkillList() {
request.post('/sj/skill/list', {
page: 1,
limit: 4,
poster_recommend: 1
}).then(res => {
if (res.code == 200 && res.data && res.data.list) {
this.skills = res.data.list.map(item => {
return {
id: item.id,
title: item.name,
type: item.link_name || '平台课程',
views: item.browse_num || 0,
time: item.create_time ? item.create_time.split(' ')[0] : '',
coverSrc: item.cover_img ||
'https://dummyimage.com/200x150/ccc/fff.png&text=Video',
typeIcon: item.link_logo || '',
video: item.video || ''
};
});
} else {
this.skills = [];
}
}).catch(() => {
this.skills = [];
});
},
// 跳转:通知详情页
goToNotice(msg) {
if (!this.isLogin) {
uni.navigateTo({
url: '/pages/blogPopup/blogPopup'
});
return
}
if (msg.id) {
uni.navigateTo({
url: `/pages/notice/detail?id=${msg.id}`
});
}
},
// 跳转:通知公告列表页
goToNoticeList() {
if (!this.isLogin) {
uni.navigateTo({
url: '/pages/blogPopup/blogPopup'
});
return
}
uni.navigateTo({
url: '/pages/notice/notice_list'
});
},
// 跳转:订单列表页
goToOrderList() {
uni.switchTab({
url: '/pages/order/userorder-list'
});
},
// 营业中 / 休息中 按钮逻辑判断
toggleStatus() {
const isCurrentlyResting = !this.isOpenStore; // 以页面实际展示状态为准
// 强力拦截:在休息中且三大件没配全,想要营业时拦截弹窗
if (isCurrentlyResting && !this.canOpenStore) {
this.showSetupModal = true;
return;
}
// 准备切换的新状态
let newStatus = this.isOpenStore ? 2 : 1;
// 加入一个防连点的 Loading
uni.showLoading({
title: '切换中...',
mask: true
});
// 发起请求告诉后端
request.post("/sj/sjordertaking", {
order_taking: newStatus,
sjid: this.sjId
}).then((res) => {
uni.hideLoading();
// 因为上一次的严格判断误伤了你,这里回归你最原始的写法,直接假定成功
this.userInfo.order_taking = newStatus; // 强行改一下本地状态让按钮动过去
this.getUserInfo(); // 无论如何刷新一下真实数据
uni.showToast({
title: newStatus == 1 ? '已开启营业' : '已休息',
icon: 'none'
});
}).catch(() => {
// 只有在彻底断网报错的情况下,才会弹下面这个
uni.hideLoading();
uni.showToast({
title: '网络异常,切换失败',
icon: 'none'
});
});
},
animateScore() {
const target = parseFloat(this.orderDetail.score) || 0;
if (this.animationTimer) clearInterval(this.animationTimer);
const start = this.displayScore;
const change = target - start;
if (Math.abs(change) < 0.05) {
this.displayScore = target;
return;
}
const frames = 30;
const duration = 600;
let currentFrame = 0;
this.animationTimer = setInterval(() => {
currentFrame++;
const progress = currentFrame / frames;
const easeProgress = 1 - Math.pow(1 - progress, 3);
this.displayScore = start + change * easeProgress;
if (currentFrame >= frames) {
this.displayScore = target;
clearInterval(this.animationTimer);
}
}, duration / frames);
}
}
};
</script>
<style lang="scss">
page {
background: #f5f6f8;
}
/* ============ 提示弹窗样式 ============ */
.setup-modal-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
.setup-modal-content {
width: 630rpx;
height: 775rpx;
/* 根据要求绝对固定宽高 */
background: #FFFFFF;
border-radius: 32rpx;
position: relative;
display: flex;
flex-direction: column;
.modal-bg {
position: absolute;
top: 0;
left: 0;
width: 630rpx;
height: 265rpx;
z-index: 0;
}
.modal-header {
position: relative;
z-index: 1;
height: 150rpx;
display: flex;
align-items: flex-end;
justify-content: center;
padding-bottom: 20rpx;
text {
font-size: 36rpx;
font-weight: bold;
color: #333333;
letter-spacing: 2rpx;
}
}
.modal-body {
position: relative;
z-index: 1;
flex: 1;
padding: 24rpx 40rpx;
.modal-subtitle {
font-size: 28rpx;
color: #333333;
margin-bottom: 40rpx;
display: block;
}
.missing-list {
display: flex;
flex-direction: column;
gap: 46rpx;
/* 间距拉开显得不那么拥挤 */
.missing-item {
display: flex;
justify-content: space-between;
align-items: center;
.item-left {
display: flex;
align-items: flex-start;
.item-icon {
width: 32rpx;
height: 32rpx;
margin-right: 16rpx;
flex-shrink: 0;
/* 防止图标被挤压变形 */
margin-top: 6rpx;
}
.item-texts {
display: flex;
flex-direction: column;
.item-title {
font-size: 30rpx;
color: #333333;
font-weight: 500;
margin-bottom: 8rpx;
/* 增加蓝色字体样式 */
&.blue-text {
color: #2b85e4;
}
}
.item-desc {
font-size: 24rpx;
color: #999999;
}
}
}
.item-right {
font-size: 26rpx;
font-weight: 400;
margin-top: -45rpx;
&.orange {
color: #FF7B00;
}
&.grey {
color: #999999;
}
/* 增加浅绿色样式 */
&.light-green {
color: #5CAC81;
}
}
}
}
}
.modal-footer {
position: relative;
z-index: 1;
padding: 0 50rpx 50rpx;
.btn-know {
width: 100%;
height: 88rpx;
background: #FF4767;
border-radius: 44rpx;
color: #FFFFFF;
font-size: 32rpx;
font-weight: 500;
display: flex;
align-items: center;
justify-content: center;
}
}
}
}
/* ==================================== */
.homePage {
position: relative;
min-height: 100vh;
padding: 0 24rpx;
padding-top: 100rpx;
.page-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
z-index: 0;
}
.card-box {
position: relative;
z-index: 1;
background: #ffffff;
border-radius: 24rpx;
margin-bottom: 24rpx;
padding: 20rpx 30rpx 30rpx;
}
.more-right-icon {
width: 8rpx;
height: 14rpx;
margin-left: 6rpx;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
.header-left {
display: flex;
align-items: center;
.title {
margin-right: 20rpx;
font-family: PingFangSC, PingFang SC;
font-weight: 500;
font-size: 30rpx;
color: #101010;
line-height: 42rpx;
text-align: left;
font-style: normal;
}
.update-time {
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 22rpx;
color: #8E694D;
line-height: 28rpx;
text-align: left;
font-style: normal;
}
.refresh-icon {
width: 18rpx;
height: 18rpx;
margin-left: 10rpx;
}
}
.header-right {
font-weight: 400;
font-size: 24rpx;
color: #666666;
line-height: 28rpx;
}
.brand-more {
font-size: 24rpx;
}
}
.header-section {
position: relative;
z-index: 1;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
.user-info {
display: flex;
align-items: center;
.avatar {
width: 76rpx;
height: 76rpx;
border-radius: 50%;
margin-right: 20rpx;
}
.name {
height: 42rpx;
font-family: PingFangTC, PingFangTC;
font-weight: 500;
font-size: 34rpx;
color: #333333;
line-height: 42rpx;
}
.desc {
height: 28rpx;
font-family: PingFangTC, PingFangTC;
font-weight: 400;
font-size: 22rpx;
color: #858796;
line-height: 28rpx;
text-align: left;
font-style: normal;
}
}
.status-toggle {
position: relative;
width: 173rpx;
height: 62rpx;
display: flex;
align-items: center;
border-radius: 31rpx;
box-sizing: border-box;
overflow: hidden;
background-image: linear-gradient(316deg,
rgba(255, 161, 203, 1) 0,
rgba(251, 143, 180, 1) 49.381741%,
rgba(255, 130, 239, 1) 100%);
transition: all 0.3s ease;
&::before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: url("https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/269c1024-34ec-42af-8b20-1ce047c9851b.png") no-repeat center;
background-size: 100% 100%;
opacity: 0;
z-index: 1;
transition: opacity 0.3s ease;
}
.status-text {
position: absolute;
font-weight: 500;
font-size: 28rpx;
color: #FFFFFF;
line-height: 40rpx;
z-index: 3;
left: 22rpx;
transition: transform 0.3s cubic-bezier(0.25, 1, 0.5, 1);
}
.toggle-dot {
position: absolute;
top: 5rpx;
right: 5rpx;
width: 52rpx;
height: 52rpx;
background: #fff;
border-radius: 50%;
z-index: 3;
transition: transform 0.35s cubic-bezier(0.25, 1, 0.5, 1);
}
&.is-closed {
&::before {
opacity: 1;
}
.status-text {
transform: translateX(46rpx);
}
.toggle-dot {
transform: translate(-113rpx, 2rpx);
}
}
}
}
.home-banner {
position: relative;
z-index: 1;
width: 710rpx;
height: 266rpx;
margin-left: -4rpx;
margin-bottom: 24rpx;
border-radius: 24rpx;
overflow: hidden;
.home-banner-img {
display: block;
width: 100%;
height: 100%;
}
}
.settlement-guide {
padding: 0;
overflow: hidden;
.guide-header {
width: 100%;
height: 248rpx;
background-size: cover;
}
.guide-body {
padding: 90rpx 0 50rpx;
display: flex;
flex-direction: column;
align-items: center;
.guide-text {
margin-bottom: 20rpx;
font-weight: 400;
font-size: 28rpx;
color: #333333;
line-height: 40rpx;
}
.guide-btn {
width: 534rpx;
height: 70rpx;
border-radius: 35rpx;
border: 1rpx solid #FF4767;
color: #FF4767;
font-weight: 500;
font-size: 28rpx;
display: flex;
align-items: center;
justify-content: center;
}
}
}
.quick-actions {
display: flex;
justify-content: space-around;
padding: 30rpx 10rpx;
background: #F3F4FD;
.action-item {
display: flex;
flex-direction: column;
align-items: center;
.action-icon {
width: 60rpx;
height: 60rpx;
margin-bottom: 13rpx;
}
.action-text {
font-weight: 400;
font-size: 28rpx;
color: #101010;
line-height: 33rpx;
text-align: left;
font-style: normal;
}
}
}
.order-stats-basic {
background: linear-gradient(176deg, #F0F4FF 0%, #FFFFFF 100%);
border-radius: 20rpx;
border: 1rpx solid #FFFFFF;
.stats-content {
display: flex;
justify-content: space-around;
text-align: center;
.num {
display: block;
font-weight: 500;
font-size: 40rpx;
color: #333333;
line-height: 56rpx;
font-style: normal;
}
.label {
font-weight: 400;
font-size: 24rpx;
color: #666666;
line-height: 33rpx;
text-align: left;
font-style: normal;
}
}
}
.order-stats-detail {
.detail-content {
display: flex;
align-items: center;
.score-dashboard {
position: relative;
width: 140rpx;
height: 140rpx;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-right: 40rpx;
flex-shrink: 0;
.ring-track {
position: absolute;
width: 130rpx;
height: 130rpx;
top: 5rpx;
left: 5rpx;
background: url("https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/4189620a-fd4f-42eb-9977-817e521b4f12.png") no-repeat center;
background-size: contain;
z-index: 1;
}
.ring-progress {
position: absolute;
top: 2rpx;
left: 0;
width: 140rpx;
height: 140rpx;
border-radius: 50%;
mask: radial-gradient(transparent 62rpx, #000 65rpx);
-webkit-mask: radial-gradient(transparent 62rpx, #000 65rpx);
z-index: 2;
}
.dot {
position: absolute;
top: 50%;
left: 50%;
width: 7rpx;
height: 7rpx;
background: #FF4D4F;
border-radius: 50%;
z-index: 3;
margin-top: -3.5rpx;
margin-left: -3.5rpx;
}
.start-dot {
transform: translateY(-66rpx);
}
.end-dot {
transform-origin: center center;
margin-left: -4rpx;
margin-top: -2rpx;
}
.score-num {
position: absolute;
font-family: AlimamaShuHeiTi, sans-serif;
font-weight: bold;
font-size: 34rpx;
color: #333333;
z-index: 3;
}
.score-tag-img {
position: absolute;
bottom: -10rpx;
width: 85rpx;
height: 35rpx;
z-index: 4;
}
}
.detail-grid {
flex: 1;
display: flex;
justify-content: space-between;
.grid-item {
display: flex;
flex-direction: column;
align-items: center;
.label {
font-weight: 400;
font-size: 24rpx;
color: #888888;
line-height: 33rpx;
margin-bottom: 12rpx;
}
.val-box {
display: flex;
align-items: baseline;
margin-bottom: 6rpx;
.val {
font-weight: 500;
font-size: 40rpx;
color: #333333;
line-height: 56rpx;
}
.unit {
font-weight: 400;
font-size: 24rpx;
color: #888888;
line-height: 33rpx;
margin-left: 6rpx;
}
}
.yesterday {
margin-top: 6rpx;
font-weight: 400;
font-size: 24rpx;
color: #888888;
line-height: 33rpx;
text-align: center;
}
}
}
}
}
.notice-bar {
display: flex;
align-items: center;
padding: 13rpx 30rpx;
min-height: 66rpx;
box-sizing: border-box;
background: url("https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/3d9c6b4b-ef6f-4242-abd4-6194d13e02dc.png");
background-size: contain;
.notice-icon {
width: 25rpx;
height: 21rpx;
margin-right: 16rpx;
}
.notice-swiper {
flex: 1;
height: 40rpx;
line-height: 40rpx;
.notice-text {
font-weight: 400;
font-size: 26rpx;
color: #30353C;
}
}
.notice-btn {
// font-weight: 400;
font-size: 22rpx;
color: #8D4C1B;
padding: 6rpx 18rpx;
border-radius: 30rpx;
border: 1rpx solid #8d4c1b50;
margin-left: 10rpx;
margin-bottom: 4rpx;
margin-top: 4rpx;
}
}
.brands-section {
padding: 20rpx 20rpx;
.brand-grid {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
.brand-card-wrap {
width: 48.5%;
margin-bottom: 14rpx;
}
}
}
.home-empty {
height: 360rpx;
display: flex;
justify-content: center;
}
.skills-section {
.skill-list {
display: flex;
flex-direction: column;
}
.skill-item {
display: flex;
margin-bottom: 30rpx;
&:last-child {
margin-bottom: 0;
}
.cover-box {
position: relative;
width: 224rpx;
height: 126rpx;
border-radius: 16rpx;
overflow: hidden;
margin-right: 24rpx;
flex-shrink: 0;
.cover {
width: 100%;
height: 100%;
}
.play-icon {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 39rpx;
height: 39rpx;
}
}
.skill-info {
flex: 1;
display: flex;
min-width: 0;
flex-direction: column;
justify-content: space-between;
.skill-title {
display: block;
font-size: 28rpx;
color: #333;
font-weight: 400;
line-height: 40rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.skill-type {
display: flex;
align-items: center;
font-size: 22rpx;
margin-top: 10rpx;
.type-icon {
border: 1rpx solid #D6CAC0;
width: 26rpx;
height: 26rpx;
margin-right: 8rpx;
border-radius: 50%;
}
.type-text {
background: #f9d3d760;
border-radius: 7rpx;
font-weight: 400;
font-size: 24rpx;
color: #E55463;
line-height: 33rpx;
padding: 2rpx 10rpx 0 10rpx;
}
}
.skill-stats {
font-size: 22rpx;
color: #999;
margin-top: 10rpx;
display: flex;
align-items: center;
.view-icon {
width: 26rpx;
height: 22rpx;
margin-right: 8rpx;
margin-top: -2rpx;
}
}
}
}
}
.line-1 {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.line-2 {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
}
</style>