骑手端app代码仓库创建

This commit is contained in:
admin
2026-01-06 21:22:12 +08:00
commit 34c63780a8
467 changed files with 65334 additions and 0 deletions

View File

@@ -0,0 +1,26 @@
<!-- 分类展示first-one 风格 -->
<template>
<view class="ss-flex-col">
<view class="goods-box" v-for="item in pagination.list" :key="item.id">
<s-goods-column
size="sl"
:data="item"
@click="sheep.$router.go('/pages/goods/index', { id: item.id })"
/>
</view>
</view>
</template>
<script setup>
import sheep from '@/sheep';
const props = defineProps({
pagination: Object,
});
</script>
<style lang="scss" scoped>
.goods-box {
width: 100%;
}
</style>

View File

@@ -0,0 +1,66 @@
<!-- 分类展示first-two 风格 -->
<template>
<view>
<view class="ss-flex flex-wrap">
<view class="goods-box" v-for="item in pagination?.list" :key="item.id">
<view @click="sheep.$router.go('/pages/goods/index', { id: item.id })">
<view class="goods-img">
<image class="goods-img" :src="item.picUrl" mode="aspectFit" />
</view>
<view class="goods-content">
<view class="goods-title ss-line-1 ss-m-b-28">{{ item.name }}</view>
<view class="goods-price">{{ fen2yuan(item.price) }}</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import sheep from '@/sheep';
import { fen2yuan } from '@/sheep/hooks/useGoods';
const props = defineProps({
pagination: Object,
});
</script>
<style lang="scss" scoped>
.goods-box {
width: calc((100% - 20rpx) / 2);
margin-bottom: 20rpx;
.goods-img {
width: 100%;
height: 246rpx;
border-radius: 10rpx 10rpx 0px 0px;
}
.goods-content {
width: 100%;
background: #ffffff;
box-shadow: 0px 0px 20rpx 4rpx rgba(199, 199, 199, 0.22);
padding: 20rpx 0 32rpx 16rpx;
box-sizing: border-box;
border-radius: 0 0 10rpx 10rpx;
.goods-title {
font-size: 26rpx;
font-weight: bold;
color: #333333;
}
.goods-price {
font-size: 24rpx;
font-family: OPPOSANS;
font-weight: 500;
color: #e1212b;
}
}
&:nth-child(2n + 1) {
margin-right: 20rpx;
}
}
</style>

View File

@@ -0,0 +1,97 @@
<!-- 分类展示second-one 风格 -->
<template>
<view>
<!-- 一级分类的名字 -->
<!-- <view class="title-box ss-flex ss-col-center ss-row-center ss-p-b-30">
<view class="title-line-left" />
<view class="title-text ss-p-x-20">{{ props.data[activeMenu].name }}</view>
<view class="title-line-right" />
</view> -->
<view class="title-box ss-flex ss-p-b-30">
<view class="theme-line"></view>
<view class="title-text">{{ props.data[activeMenu].name }}</view>
</view>
<!-- 二级分类的名字 -->
<view class="goods-item-box ss-flex ss-flex-wrap ss-p-b-20">
<view
class="goods-item"
v-for="item in props.data[activeMenu].children"
:key="item.id"
@tap="
sheep.$router.go('/pages/goods/list', {
categoryId: item.id,
})
"
>
<image class="goods-img" :src="item.picUrl" mode="aspectFill" />
<view class="ss-p-10">
<view class="goods-title ss-line-1">{{ item.name }}</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import sheep from '@/sheep';
const props = defineProps({
data: {
type: Object,
default: () => ({}),
},
activeMenu: [Number, String],
});
</script>
<style lang="scss" scoped>
.title-box {
font-weight: 800;
font-size: 32rpx;
color: #333333;
.title-line-left,
.title-line-right {
width: 15px;
height: 1px;
background: #d2d2d2;
}
}
.goods-item {
width: calc((100% - 20px) / 3);
margin-right: 10px;
margin-bottom: 10px;
&:nth-of-type(3n) {
margin-right: 0;
}
.goods-img {
width: calc((100vw - 140px) / 3);
height: calc((100vw - 140px) / 3);
}
.goods-title {
font-size: 26rpx;
font-weight: bold;
color: #333333;
line-height: 40rpx;
text-align: center;
}
.goods-price {
color: $red;
line-height: 40rpx;
}
}
.theme-line {
margin-right: 15rpx;
width: 8rpx;
height: 28rpx;
background: var(--ui-BG-Main);
border-radius: 8rpx 8rpx 8rpx 8rpx;
}
</style>

480
pages/index/index.vue Normal file
View File

@@ -0,0 +1,480 @@
<!-- 接单页将原首页替换为接单页面含订单卡片顶部 tab底部批量操作栏 -->
<template>
<view class="receive-page">
<!-- 顶部区域状态 + tabs -->
<view class="top-area" :style="headerStyle">
<view class="top-bg"></view>
<view class="top-inner">
<view class="user-info">
<image class="user-avatar" :src="driverInfo.avatar || defaultAvatar" mode="cover" />
<view class="user-meta">
<!-- <text class="user-name">{{ driverInfo.nickName || '骑手姓名' }}</text> -->
<text class="user-status" @click="toggleOnline">{{ driverInfo.isOnline ? '在线中' : '离线' }}</text>
</view>
</view>
<view class="tabs">
<view :class="['tab', activeTab === 'pickup' ? 'active' : '']" @click="switchTab('pickup')">
<text>待取货</text>
<text class="count">({{ pickupCount }})</text>
</view>
<view :class="['tab', activeTab === 'delivering' ? 'active' : '']" @click="switchTab('delivering')">
<text>配送中</text>
<text class="count">({{ deliveringCount }})</text>
</view>
</view>
</view>
</view>
<!-- 订单列表 -->
<scroll-view class="order-list" scroll-y="true" :style="{ height: listHeight + 'px' }">
<view v-for="order in filteredOrders" :key="order.id" class="order-card">
<!-- 头部编号 -->
<view class="order-header">
<view class="order-badge">{{ order.type === 'pickup' ? '取' : '送' }}</view>
<view class="order-title">
<text class="shop-name">{{ order.shopName }}</text>
<text class="order-id">#{{ order.id }}</text>
</view>
<view class="order-status">{{ order.statusText }}</view>
</view>
<!-- 地址信息 -->
<view class="order-info">
<view class="address-row">
<view class="icon pickup"></view>
<view class="address-content">
<text class="address-title">{{ order.pickupAddress }}</text>
<text class="address-sub">商家已出餐 · {{ order.pickupNote || '' }}</text>
</view>
<view class="nav-icon" @click="openMap(order.pickupLat, order.pickupLng, order.pickupAddress)">导航</view>
</view>
<view class="address-row">
<view class="icon deliver"></view>
<view class="address-content">
<text class="address-title">{{ order.deliveryAddress }}</text>
<text class="address-sub">收货人{{ order.receiverName }} {{ order.receiverPhone ? ('尾号' + (order.receiverPhone + '').slice(-4)) : ''}}</text>
</view>
<view class="nav-icon" @click="openMap(order.deliveryLat, order.deliveryLng, order.deliveryAddress)">导航</view>
</view>
</view>
<!-- 备注 -->
<view class="order-note" v-if="order.note">
<text>顾客{{ order.note }}</text>
</view>
<!-- 操作区 -->
<view class="order-actions">
<view class="contact" @click="callPhone(order.receiverPhone)">
<text>联系</text>
</view>
<view class="confirm" @click="confirmArrive(order.id)">
<text>确认到店</text>
</view>
</view>
</view>
</scroll-view>
<!-- 底部批量操作栏 -->
<view class="bottom-bar">
<view class="batch-item" @click="scanQr">扫一扫取单</view>
<view class="batch-item" @click="openManualInput">输入用户单编码</view>
</view>
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
import sheep from '@/sheep';
// 驿站/骑手信息(从 store 获取或 mock
const driverInfo = ref({
isOnline: true,
nickName: '骑手张三',
avatar: ''
});
const defaultAvatar = 'https://huichibao.oss-cn-guangzhou.aliyuncs.com/1/material/348b8223-8d03-46aa-8836-6757e8beebd2.png';
// 页面状态
const activeTab = ref('pickup'); // 'pickup' | 'delivering'
const listHeight = ref(600);
// Mock 订单数据(真实项目应从后端接口拉取 / store
const orders = ref([
{
id: 1001,
type: 'pickup',
statusText: '待取货',
shopName: '取货点店铺名称',
pickupAddress: '广东省广州市天河区学院站荷光路118-121号',
pickupLat: 23.1,
pickupLng: 113.3,
pickupNote: '商家已出餐',
deliveryAddress: '广东省广州市天河区华景新城软件园区',
deliveryLat: 23.12,
deliveryLng: 113.31,
receiverName: '张先生',
receiverPhone: '13900001234',
note: '根据餐量提供餐具'
},
{
id: 1002,
type: 'pickup',
statusText: '待取货',
shopName: '乐易购(学院店)',
pickupAddress: '广东省广州市天河区学院站荷光路118--121号',
pickupLat: 23.11,
pickupLng: 113.32,
pickupNote: '',
deliveryAddress: '广东省广州市天河区某小区',
deliveryLat: 23.13,
deliveryLng: 113.33,
receiverName: '李女士',
receiverPhone: '13900005678',
note: ''
}
]);
// 计算各 tab 数量与过滤列表
const pickupCount = computed(() => orders.value.filter(o => o.type === 'pickup').length);
const deliveringCount = computed(() => orders.value.filter(o => o.type === 'delivering').length);
const filteredOrders = computed(() => {
if (activeTab.value === 'pickup') {
return orders.value.filter(o => o.type === 'pickup');
}
return orders.value.filter(o => o.type === 'delivering');
});
// 切换 tab
function switchTab(tab) {
activeTab.value = tab;
}
// 切换上线/下线(简单 UI 切换,建议接入后端)
function toggleOnline() {
driverInfo.value.isOnline = !driverInfo.value.isOnline;
sheep.$helper && sheep.$helper.toast && sheep.$helper.toast(driverInfo.value.isOnline ? '已上线' : '已下线');
}
// 确认到店(演示:改变订单状态)
function confirmArrive(orderId) {
const order = orders.value.find(o => o.id === orderId);
if (!order) return;
// 示例逻辑:到店后将类型改为 delivering
if (order.type === 'pickup') {
order.type = 'delivering';
order.statusText = '配送中';
sheep.$helper && sheep.$helper.toast && sheep.$helper.toast('已确认到店,开始配送');
}
}
// 拨打电话
function callPhone(phone) {
if (!phone) {
sheep.$helper && sheep.$helper.toast && sheep.$helper.toast('未找到联系电话');
return;
}
uni.makePhoneCall({
phoneNumber: phone
});
}
// 打开地图导航(使用 openLocation 打开经纬度或直接跳转小程序地图)
function openMap(lat, lng, name) {
if (!lat || !lng) {
sheep.$helper && sheep.$helper.toast && sheep.$helper.toast('无法获取坐标');
return;
}
uni.openLocation({
latitude: Number(lat),
longitude: Number(lng),
name: name || '',
scale: 18
});
}
// 底部操作(扫码、手动输入)
function scanQr() {
uni.scanCode({
onlyFromCamera: false,
success(res) {
const code = res.result || res.path || '';
sheep.$helper && sheep.$helper.toast && sheep.$helper.toast('已识别:' + code);
},
fail() {
sheep.$helper && sheep.$helper.toast && sheep.$helper.toast('扫码失败');
}
});
}
function openManualInput() {
uni.navigateTo({
url: '/pages/index/user' // 示例跳转,按需替换为真实手动输入页面
});
}
// 入口:计算列表高度适配底部栏
onMounted(() => {
try {
const sys = uni.getSystemInfoSync();
const windowHeight = sys.windowHeight || 667;
// 留出顶部和底部空间
listHeight.value = windowHeight - 200;
} catch (e) {
// ignore
}
});
// 顶部安全区处理:参考 pages/index/user.vue 的实现
import { onShow } from '@dcloudio/uni-app';
const headerStyle = ref({});
function setHeaderSafeArea() {
try {
const sys = uni.getSystemInfoSync();
const statusBarHeightPx = sys?.statusBarHeight || 0;
const windowWidth = sys?.windowWidth || 375;
// 将 px 转为 rpx rpx = px / windowWidth * 750
const statusBarHeightRpx = Math.round((statusBarHeightPx / windowWidth) * 750);
headerStyle.value = {
paddingTop: statusBarHeightPx + 'px',
'--statusbar': statusBarHeightRpx + 'rpx'
};
} catch (e) {
// ignore
}
}
onMounted(() => {
setHeaderSafeArea();
// 计算列表高度适配底部栏
try {
const sys = uni.getSystemInfoSync();
const windowHeight = sys.windowHeight || 667;
// 留出顶部和底部空间
listHeight.value = windowHeight - 200;
} catch (e) {
// ignore
}
});
onShow(() => {
// 每次页面显示时重新计算(兼容热更或状态变化)
setHeaderSafeArea();
});
</script>
<style scoped>
.receive-page {
background: #fff;
min-height: 100vh;
position: relative;
}
.top-area {
position: relative;
/* 兼容刘海屏安全区处理 */
padding-top: constant(safe-area-inset-top);
padding-top: env(safe-area-inset-top);
height: 220rpx;
}
.top-bg {
position: absolute;
left: 0;
right: 0;
top: 0;
/* 背景高度需要包含安全区高度 */
height: 270rpx;
height: calc(270rpx + constant(safe-area-inset-top));
height: calc(270rpx + env(safe-area-inset-top));
height: calc(270rpx + var(--statusbar, 0rpx));
background: #c292ee;
border-bottom-left-radius: 12rpx;
border-bottom-right-radius: 12rpx;
z-index: 0;
}
.top-inner {
position: absolute;
left: 0;
right: 0;
top: calc(var(--statusbar, 0rpx) + 30rpx);
z-index: 1;
padding: 0 30rpx;
display: flex;
flex-direction: column;
}
.user-info {
display: flex;
flex-direction: row;
align-items: center;
}
.user-avatar {
width: 110rpx;
height: 110rpx;
border-radius: 55rpx;
border: 4rpx solid rgba(255,255,255,0.6);
}
.user-meta {
margin-left: 20rpx;
}
.user-name {
font-size: 32rpx;
color: #fff;
font-weight: 700;
}
.user-status {
margin-top: 8rpx;
color: rgba(255,255,255,0.9);
font-size: 26rpx;
}
.tabs {
margin-top: 18rpx;
display: flex;
flex-direction: row;
gap: 20rpx;
}
.tab {
padding: 10rpx 20rpx;
background: rgba(255,255,255,0.12);
border-radius: 40rpx;
color: #fff;
display: flex;
align-items: center;
}
.tab.active {
background: #fff;
color: #6b3aa6;
}
.tab .count {
margin-left: 8rpx;
}
.order-list {
padding: 20rpx;
padding-right: 20rpx;
box-sizing: border-box;
background: #f7f7f7;
}
.order-card {
background: #fff;
border-radius: 12rpx;
padding: 20rpx;
margin-bottom: 18rpx;
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.06);
overflow: hidden;
box-sizing: border-box;
max-width: 100%;
}
.order-header {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
margin-bottom: 12rpx;
}
.order-badge {
width: 54rpx;
height: 54rpx;
border-radius: 27rpx;
background: #e6f7ff;
color: #1890ff;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
}
.order-title {
flex: 1;
margin-left: 12rpx;
}
.shop-name {
font-size: 28rpx;
font-weight: 700;
}
.order-id {
color: #999;
margin-left: 8rpx;
}
.order-status {
color: #ff7a45;
}
.order-info {
margin-top: 8rpx;
}
.address-row {
display: flex;
flex-direction: row;
align-items: center;
margin-top: 10rpx;
}
.icon {
width: 38rpx;
height: 38rpx;
border-radius: 19rpx;
background: #f2f2f2;
display: flex;
align-items: center;
justify-content: center;
margin-right: 10rpx;
font-weight: 700;
}
.icon.pickup { background: #87d6ff; color: #fff; }
.icon.deliver { background: #ffd591; color: #fff; }
.address-content { flex: 1; min-width: 0; }
.address-title {
display: block;
font-size: 26rpx;
font-weight: 600;
/* 支持长地址换行,防止撑开布局 */
white-space: normal;
word-break: break-word;
}
.address-sub { display: block; font-size: 22rpx; color: #888; margin-top: 6rpx; }
.nav-icon { color: #1e9fff; padding: 6rpx 10rpx; }
.order-note { background: #f6f6f6; padding: 12rpx; border-radius: 8rpx; margin-top: 12rpx; color: #666; }
.order-actions {
display: flex;
flex-direction: row;
margin-top: 12rpx;
gap: 12rpx;
}
.contact {
flex: 1;
background: #fff;
border: 1rpx solid #ddd;
padding: 14rpx;
text-align: center;
border-radius: 8rpx;
color: #333;
}
.confirm {
flex: 2;
background: #1e9fff;
padding: 14rpx;
text-align: center;
border-radius: 8rpx;
color: #fff;
}
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 110rpx;
background: rgba(255,255,255,0.98);
display: flex;
flex-direction: row;
justify-content: space-around;
align-items: center;
border-top: 1rpx solid #eee;
}
.batch-item {
background: #fff;
padding: 14rpx 20rpx;
border-radius: 40rpx;
border: 1rpx solid #ddd;
}
</style>

39
pages/index/login.vue Normal file
View File

@@ -0,0 +1,39 @@
<!-- 微信公众号的登录回调页 -->
<template>
<!-- 空登陆页 -->
<view />
</template>
<script setup>
import sheep from '@/sheep';
import { onLoad } from '@dcloudio/uni-app';
onLoad(async (options) => {
// #ifdef H5
// 将 search 参数赋值到 options 中,方便下面解析
new URLSearchParams(location.search).forEach((value, key) => {
options[key] = value;
});
// 执行登录 or 绑定,注意需要 await 绑定
const event = options.event;
const code = options.code;
const state = options.state;
if (event === 'login') { // 场景一:登录
await sheep.$platform.useProvider().login(code, state);
} else if (event === 'bind') { // 场景二:绑定
await sheep.$platform.useProvider().bind(code, state);
}
// 检测 H5 登录回调
let returnUrl = uni.getStorageSync('returnUrl');
if (returnUrl) {
uni.removeStorage({key:'returnUrl'});
location.replace(returnUrl);
} else {
uni.switchTab({
url: '/',
});
}
// #endif
});
</script>

463
pages/index/user.vue Normal file
View File

@@ -0,0 +1,463 @@
<template>
<view class="page">
<!-- header -->
<view class="header-wrap" :style="headerStyle">
<view class="header-bg"></view>
<view class="header-inner">
<image class="avatar" :src="user.avatar || defautAvatar"></image>
<view class="user-meta">
<view class="user-name">{{ user.nickName || '姓名(账号)' }}</view>
<view class="user-status" @click="handleStatusToggle">
{{ user.isOnline ? '在线' : '离线' }}<uni-icons style="margin-left:10rpx;" type="right" size="13" color="#fff"></uni-icons>
</view>
</view>
</view>
</view>
<!-- stats card -->
<view class="stats-card">
<view class="stats-row">
<view class="stats-item">
<text class="stats-title">今日预计收入</text>
<text class="stats-value"> {{ formatMoney(todayIncome) }} </text>
<view class="stats-link" @click="openAccount">
我的账户 <uni-icons type="right" size="13"></uni-icons>
</view>
</view>
<view class="stats-item">
<text class="stats-title">今日完成单量</text>
<text class="stats-value"> {{ todayOrders }} </text>
<view class="stats-link" @click="openOrders">
订单统计 <uni-icons type="right" size="13"></uni-icons>
</view>
</view>
</view>
</view>
<!-- shortcuts -->
<view class="shortcuts">
<view class="shortcut" @click="openAttendance">
<image class="shortcut-icon" src="/static/img/order1.png" mode="aspectFit" />
<text class="shortcut-text">考勤排班</text>
</view>
<view class="shortcut" @click="openSalary">
<image class="shortcut-icon" src="/static/img/order2.png" mode="aspectFit" />
<text class="shortcut-text">薪资助手</text>
</view>
<view class="shortcut" @click="openSetting">
<!-- <image class="shortcut-icon" src="/static/img/edit.png" mode="aspectFit" /> -->
<view class="shortcut-icon">
<uni-icons type="gear" size="43"></uni-icons>
</view>
<text class="shortcut-text">设置</text>
</view>
</view>
</view>
<s-auth-modal />
<su-popup type="center" :show="showStatusPopup" round="14" :showClose="false">
<view class="modal-box">
<view class="modal-body">
<text class="modal-title">{{ modalTitle }}</text>
<text v-if="modalMsg" class="modal-msg">{{ modalMsg }}</text>
</view>
<view class="modal-footer">
<view class="modal-btn cancel" @click="cancelConfirm">取消</view>
<view class="modal-btn confirm" @click="confirmAction">确认</view>
</view>
</view>
</su-popup>
</template>
<script setup>
import {
computed,
ref
} from 'vue';
import {
onShow,
onPageScroll,
onPullDownRefresh
} from '@dcloudio/uni-app';
import sheep from '@/sheep';
// 现有 store / 模板数据
const template = computed(() => sheep.$store('app').template.user);
const isLogin = computed(() => sheep.$store('user').isLogin);
const user = ref({});
const todayIncome = ref(0);
const todayOrders = ref(0);
const showBind = ref(false);
// 动态 header 内联样式,用于兼容不同平台的状态栏高度
const headerStyle = ref({});
const defautAvatar =
'https://huichibao.oss-cn-guangzhou.aliyuncs.com/1/material/348b8223-8d03-46aa-8836-6757e8beebd2.png';
// 格式化金额显示
function formatMoney(val) {
if (val == null) return '0';
return (Number(val) || 0).toFixed(1);
}
// 微信小程序的“手机号快速验证”
const getPhoneNumber = async (e) => {
if (e.detail.errMsg !== 'getPhoneNumber:ok') {
sheep.$helper.toast('快捷登录失败');
return;
}
let result = await sheep.$platform.useProvider().mobileLogin(e.detail);
if (result) {
showBind.value = false;
}
};
// 页面显示时拉取用户信息并填充统计数据(从 store 获取或使用占位)
onShow(async () => {
const data = await sheep.$store('user').getInfo();
if (data) {
user.value = data;
// 兼容后端字段名,优先使用 data.todayIncome / data.income / placeholder
todayIncome.value = data.todayIncome ?? data.income ?? 137.9;
todayOrders.value = data.todayOrders ?? data.orders ?? 39;
if (data?.status == 1) {
console.log("清空缓存");
uni.clearStorageSync();
}
}
// 兼容处理:读取原生状态栏高度并设置 header 的 padding-toppx与 CSS 变量 --statusbarrpx
try {
const sys = uni.getSystemInfoSync();
const statusBarHeightPx = sys?.statusBarHeight || 0;
const windowWidth = sys?.windowWidth || 375;
// 将 px 转为 rpx rpx = px / windowWidth * 750
const statusBarHeightRpx = Math.round((statusBarHeightPx / windowWidth) * 750);
headerStyle.value = {
paddingTop: statusBarHeightPx + 'px',
'--statusbar': statusBarHeightRpx + 'rpx'
};
} catch (e) {
// ignore
}
});
// 确认弹框状态(上线/下线/禁止)
const showStatusPopup = ref(false);
const confirmType = ref(''); // 'online' | 'offline' | 'forbidden'
const modalTitle = ref('');
const modalMsg = ref('');
// 判断用户是否被禁止接单(兼容多种字段)
function isUserForbidden() {
const u = user.value || {};
return !!(u.forbidden || u.isForbidden || u.forbid || u.forbidReceive || u.disableReceive || u.receive === false);
}
// 点击状态:根据当前状态弹不同的确认框
function handleStatusToggle() {
if (user.value.isOnline) {
confirmType.value = 'offline';
modalTitle.value = '确认下线?';
modalMsg.value = '下线需平台进行核准\n此时正常接单请留意核准信息';
showStatusPopup.value = true;
return;
}
// 如果被禁止接单
if (isUserForbidden()) {
confirmType.value = 'forbidden';
modalTitle.value = '您处于禁止接单状态';
modalMsg.value = '暂无法上线上线';
showStatusPopup.value = true;
return;
}
// 普通从离线 -> 上线
confirmType.value = 'online';
modalTitle.value = '确认上线?';
modalMsg.value = '';
showStatusPopup.value = true;
}
function cancelConfirm() {
showStatusPopup.value = false;
}
async function confirmAction() {
const type = confirmType.value;
showStatusPopup.value = false;
if (type === 'online') {
// TODO: 调用后端接口变更上线状态
user.value.isOnline = true;
sheep.$helper && sheep.$helper.toast && sheep.$helper.toast('已上线');
} else if (type === 'offline') {
user.value.isOnline = false;
sheep.$helper && sheep.$helper.toast && sheep.$helper.toast('已下线');
} else if (type === 'forbidden') {
// 仅展示信息,无操作
sheep.$helper && sheep.$helper.toast && sheep.$helper.toast('无法上线(禁止接单)');
}
// 可在此处调用 store 或 API 同步服务端状态,例如:
// await sheep.$store('user').setOnline(user.value.isOnline);
}
onPullDownRefresh(() => {
sheep.$store('user').updateUserData();
setTimeout(function() {
uni.stopPullDownRefresh();
}, 800);
});
onPageScroll(() => {});
// 跳转/交互方法(保留路由调用位置,用户可按需实现)
function goBack() {
uni.navigateBack();
}
function openAccount() {
uni.navigateTo({
url: '/pages/public/webview?type=account'
});
}
function openOrders() {
uni.navigateTo({
url: '/pages/index/order-list'
});
}
function openAttendance() {
uni.navigateTo({
url: '/pages/public/faq'
});
}
function openSalary() {
uni.navigateTo({
url: '/pages/public/richtext'
});
}
function openSetting() {
uni.navigateTo({
url: '/pages/public/setting'
});
}
</script>
<style scoped>
.page {
background: #ffffff;
min-height: 100vh;
}
/* header */
.header-wrap {
position: relative;
/* 适配刘海屏安全区:优先使用 constant/env兼容小程序与 iOS/Android 安全区 */
padding-top: constant(safe-area-inset-top);
padding-top: env(safe-area-inset-top);
}
.header-bg {
/* 背景放置为绝对定位以覆盖安全区(兼容 env/constant 回退与 JS 动态 --statusbar */
position: absolute;
top: 0;
left: 0;
right: 0;
/* 背景高度需要包含安全区高度,优先使用 env/constant最后一行为回退值 */
height: 250rpx;
height: calc(250rpx + constant(safe-area-inset-top));
height: calc(250rpx + env(safe-area-inset-top));
/* JS 动态变量回退(当 env/constant 不可用时,使用 --statusbar单位 rpx */
height: calc(250rpx + var(--statusbar, 0rpx));
background: #9ad6f0;
/* 浅蓝 */
border-bottom-left-radius: 24rpx;
border-bottom-right-radius: 24rpx;
z-index: 0;
}
.header-inner {
position: absolute;
left: 32rpx;
/* 使用 CSS 变量保证当我们通过 JS 设置 --statusbar 时,内容会相对下移(单位 rpx */
top: calc(var(--statusbar, 0rpx) + 40rpx);
z-index: 1;
flex-direction: row;
display: flex;
align-items: center;
}
.avatar {
width: 120rpx;
height: 120rpx;
border-radius: 60rpx;
border: 4rpx solid rgba(255, 255, 255, 0.6);
}
.user-meta {
margin-left: 20rpx;
}
.user-name {
font-size: 30rpx;
color: #fff;
font-weight: 600;
}
.user-status {
margin-top: 8rpx;
font-size: 26rpx;
color: rgba(255, 255, 255, 0.9);
}
.back {
position: absolute;
left: 18rpx;
top: calc(var(--statusbar, 0rpx) + 12rpx);
z-index: 2;
color: #fff;
font-size: 36rpx;
}
/* stats card */
.stats-card {
padding: 0 24rpx;
margin-top: 190rpx;
position: relative;
z-index: 3;
}
.stats-row {
background: #f6c98b;
/* 浅橙色 */
border-radius: 20rpx;
padding: 30rpx;
display: flex;
flex-direction: row;
justify-content: space-between;
}
.stats-item {
width: 48%;
}
.stats-title {
font-size: 24rpx;
color: #5b4a32;
}
.stats-value {
display: block;
margin-top: 12rpx;
font-size: 42rpx;
color: #222;
font-weight: 700;
}
.stats-link {
display: block;
margin-top: 12rpx;
font-size: 24rpx;
color: #7a5a3a;
}
/* shortcuts */
.shortcuts {
margin-top: 30rpx;
padding: 30rpx 40rpx;
display: flex;
flex-direction: row;
justify-content: space-between;
position: relative;
z-index: 3;
}
.shortcut {
flex: 1;
align-items: center;
display: flex;
flex-direction: column;
}
.shortcut-icon {
width: 80rpx;
height: 80rpx;
margin-bottom: 12rpx;
}
.shortcut-text {
font-size: 24rpx;
color: #333;
}
/* popup 原有样式 */
.popup {
padding: 80rpx 0 50rpx;
}
.tip-text {
margin-bottom: 30rpx;
font-weight: 400;
font-size: 24rpx;
color: #999999;
line-height: 44rpx;
text-align: center;
}
.bind-btn {
width: 630rpx;
height: 96rpx;
font-weight: 400;
font-size: 28rpx;
color: #FFFFFF;
line-height: 96rpx;
text-align: center;
font-style: normal;
background: #00B85B;
border-radius: 64rpx 64rpx 64rpx 64rpx;
}
/* 确认弹框样式su-popup 内部内容) */
.modal-box {
width: 640rpx;
background: #fff;
border-radius: 14rpx;
overflow: hidden;
}
.modal-body {
padding: 40rpx 30rpx;
text-align: center;
}
.modal-title {
display: block;
font-size: 30rpx;
color: #333;
margin-bottom: 10rpx;
}
.modal-msg {
display: block;
font-size: 24rpx;
color: #999;
line-height: 34rpx;
white-space: pre-line;
}
.modal-footer {
display: flex;
flex-direction: row;
border-top: 1rpx solid #eee;
}
.modal-btn {
flex: 1;
padding: 26rpx 0;
text-align: center;
font-size: 28rpx;
}
.modal-btn.cancel {
color: #666;
border-right: 1rpx solid #eee;
}
.modal-btn.confirm {
color: #1e9fff;
}
</style>