- 新增师傅端路由和页面配置,切换登录后默认跳转到师傅大厅 - 新增服务分类图标上传功能和增项服务管理模块 - 新增订单、申诉、结算等模型关联关系及后台控制器 - 新增师傅端TabBar组件和消息页面跳转逻辑 - 新增文件上传接口和服务计算逻辑 - 新增各类SVG图标和空状态提示
1075 行
26 KiB
Vue
1075 行
26 KiB
Vue
<template>
|
||
<view class="detail-page">
|
||
<!-- 统一顶部导航栏 -->
|
||
<view class="nav-bar">
|
||
<view class="nav-btn clickable-hover" @tap="goBack">
|
||
<image class="nav-icon" src="/static/icons/back.svg" mode="aspectFit" />
|
||
</view>
|
||
<text class="nav-title">订单详情</text>
|
||
<view class="nav-actions">
|
||
<view class="share-btn clickable-hover" @tap="shareOrder">
|
||
<image class="action-icon" src="/static/icons/master/share-action.svg" mode="aspectFit" />
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="page-body" v-if="order">
|
||
<!-- 1. 顶部师傅围观与抢占提示条(对标工奇兵实机标语) -->
|
||
<view class="rush-banner">
|
||
<view class="rush-left">
|
||
<text class="rush-bold">{{ order.view_count || 2 }}</text>
|
||
<text class="rush-txt">位师傅正在查看</text>
|
||
</view>
|
||
<view class="rush-right" v-if="order.price_type === 'quote'">
|
||
<text class="rush-txt">只剩</text>
|
||
<text class="rush-bold">{{ order.remaining_quote_slots !== undefined ? order.remaining_quote_slots : 2 }}</text>
|
||
<text class="rush-txt">个报价名额,尽快去报价</text>
|
||
</view>
|
||
<view class="rush-right" v-else>
|
||
<text class="rush-txt">请尽快抢单锁定工单</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 2. 业主信息卡片(对标工奇兵实机卡片1) -->
|
||
<view class="card">
|
||
<view class="card-head">
|
||
<text class="card-title">业主信息</text>
|
||
</view>
|
||
|
||
<view class="client-phone-row">
|
||
<image class="client-icon" src="/static/icons/master/user-avatar-small.svg" mode="aspectFit" />
|
||
<text class="client-phone">{{ order.mobile }}</text>
|
||
</view>
|
||
|
||
<view class="address-box">
|
||
<view class="addr-main">
|
||
<image class="addr-icon" src="/static/icons/master/order-loc.svg" mode="aspectFit" />
|
||
<text class="addr-text">{{ order.full_address }}</text>
|
||
</view>
|
||
<view class="nav-action clickable-hover" @tap="openLocationMap">
|
||
<image class="nav-arrow-ico" src="/static/icons/master/nav-arrow.svg" mode="aspectFit" />
|
||
<text class="nav-action-txt">查看导航</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 距离提示 -->
|
||
<view class="distance-notice">
|
||
<view class="dist-tag">受限</view>
|
||
<text class="dist-desc">距您居住地址直线距离约 {{ order.distance_km }} km,距您当前直线距离约 {{ order.distance_km }} km,距离信息仅供参考</text>
|
||
</view>
|
||
|
||
<view class="appoint-time-row">
|
||
<image class="time-ico" src="/static/icons/master/order-time.svg" mode="aspectFit" />
|
||
<text class="appoint-k">期望上门时间:</text>
|
||
<text class="appoint-v">{{ order.appoint_time_text }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 3. 商品与服务信息卡片(对标工奇兵实机卡片2) -->
|
||
<view class="card">
|
||
<view class="card-head">
|
||
<text class="card-title">商品信息</text>
|
||
</view>
|
||
|
||
<view class="goods-row">
|
||
<view class="goods-thumb-wrap clickable-hover" @tap="previewGoodsImage">
|
||
<image class="goods-thumb" :src="order.service_image || '/static/icons/cat-sofa.svg'" mode="aspectFill" />
|
||
<view class="view-large-badge">查看大图</view>
|
||
</view>
|
||
<view class="goods-info">
|
||
<text class="goods-name">{{ order.service_name }} ({{ order.goods_model }})</text>
|
||
<view class="goods-meta-row">
|
||
<text class="goods-qty">商品数量:{{ order.goods_num }}个</text>
|
||
</view>
|
||
<view class="service-type-row">
|
||
<text class="st-label">服务类型:</text>
|
||
<text class="st-pill">{{ order.service_type_tag || '维修' }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="remark-block">
|
||
<text class="rk-label">商品备注:</text>
|
||
<text class="rk-content">{{ order.remark || '无' }}</text>
|
||
</view>
|
||
|
||
<view class="remark-block">
|
||
<text class="rk-label">服务备注:</text>
|
||
<text class="rk-content">{{ order.remark || '接单后请与业主沟通具体施工细节' }}</text>
|
||
</view>
|
||
|
||
<!-- 安全提醒 -->
|
||
<view class="security-banner">
|
||
<text class="sec-text">禁止引导客户进行线下交易,避免影响您的订单推送优先级。</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 4. 订单基本信息卡片(对标工奇兵实机卡片3) -->
|
||
<view class="card">
|
||
<view class="card-head flex-between">
|
||
<text class="card-title">订单信息</text>
|
||
<view class="feedback-link clickable-hover" @tap="orderFeedback">
|
||
<text class="fb-txt">订单反馈 ›</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="info-row">
|
||
<text class="ik">订单编号:</text>
|
||
<text class="iv">{{ order.order_no }}</text>
|
||
<view class="copy-pill clickable-hover" @tap="copyOrderNo">
|
||
<text class="copy-txt">复制</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="info-row">
|
||
<text class="ik">下单时间:</text>
|
||
<text class="iv">{{ order.createtime_text }}</text>
|
||
</view>
|
||
|
||
<view class="info-row">
|
||
<text class="ik">订单类型:</text>
|
||
<text class="iv mode-highlight">{{ order.price_type_text }}</text>
|
||
</view>
|
||
|
||
<view class="info-row" v-if="order.price_type === 'custom'">
|
||
<text class="ik">用户出价:</text>
|
||
<text class="iv price-txt">¥{{ Number(order.user_price).toFixed(2) }}</text>
|
||
</view>
|
||
|
||
<view class="info-row" v-if="order.price_type === 'fixed'">
|
||
<text class="ik">一口价:</text>
|
||
<text class="iv price-txt">¥{{ Number(order.total_amount).toFixed(2) }}</text>
|
||
</view>
|
||
|
||
<view class="info-row" v-if="order.my_quote">
|
||
<text class="ik">我的报价:</text>
|
||
<text class="iv my-quote-txt">¥{{ Number(order.my_quote.amount).toFixed(2) }}({{ order.my_quote.status === 'accept' ? '已被采纳' : '等待客户选择' }})</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="bottom-placeholder"></view>
|
||
|
||
<!-- 5. 底部固定操作栏(对标工奇兵实机吸底栏) -->
|
||
<view class="bottom-bar" v-if="order">
|
||
<view class="bar-left">
|
||
<view class="icon-btn clickable-hover" @tap="contactService">
|
||
<image class="ib-ico" src="/static/icons/master/service-action.svg" mode="aspectFit" />
|
||
<text class="ib-txt">联系客服</text>
|
||
</view>
|
||
<view class="icon-btn clickable-hover" @tap="shareOrder">
|
||
<image class="ib-ico" src="/static/icons/master/share-action.svg" mode="aspectFit" />
|
||
<text class="ib-txt">转给朋友</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="bar-right">
|
||
<!-- 一口价单 -->
|
||
<view
|
||
class="primary-action-btn clickable-hover"
|
||
v-if="order.price_type === 'fixed' && order.can_grab"
|
||
@tap="openGrabConfirm"
|
||
>
|
||
<text class="pab-title">立即抢单</text>
|
||
<text class="pab-sub">¥{{ Number(order.total_amount).toFixed(2) }}</text>
|
||
</view>
|
||
|
||
<!-- 用户定价单 -->
|
||
<view
|
||
class="primary-action-btn clickable-hover custom"
|
||
v-else-if="order.price_type === 'custom' && order.can_grab"
|
||
@tap="openGrabConfirm"
|
||
>
|
||
<text class="pab-title">按此出价接单</text>
|
||
<text class="pab-sub">¥{{ Number(order.user_price).toFixed(2) }}</text>
|
||
</view>
|
||
|
||
<!-- 师傅竞价单 -->
|
||
<view
|
||
class="primary-action-btn clickable-hover quote"
|
||
v-else-if="order.price_type === 'quote' && order.can_quote"
|
||
@tap="openQuoteDrawer"
|
||
>
|
||
<text class="pab-title">立即报价</text>
|
||
<text class="pab-sub">(报价快,中选高)</text>
|
||
</view>
|
||
|
||
<!-- 已报价状态 -->
|
||
<view class="state-action-btn disabled" v-else-if="order.my_quote">
|
||
<text class="sab-txt">已报价 ¥{{ Number(order.my_quote.amount).toFixed(2) }}</text>
|
||
</view>
|
||
|
||
<!-- 已经属于我的工单 -->
|
||
<view class="state-action-btn success" v-else-if="order.is_my_order" @tap="goMyOrders">
|
||
<text class="sab-txt">前往工单履约 ›</text>
|
||
</view>
|
||
|
||
<!-- 其他已不可接状态 -->
|
||
<view class="state-action-btn disabled" v-else>
|
||
<text class="sab-txt">{{ order.status_text }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 6. 抢单防误点二次确认弹窗(100% 对标工奇兵实机图3) -->
|
||
<view class="confirm-mask" v-if="showGrabModal" @tap="showGrabModal = false">
|
||
<view class="confirm-dialog" @tap.stop>
|
||
<text class="cd-title">提示</text>
|
||
<text class="cd-content">请确定已经了解清楚订单服务要求、商品数量、服务地址等信息。您确定可以承揽,并能完成工作,如随意抢单,将影响您的订单推送优先级。</text>
|
||
<view class="cd-actions">
|
||
<view class="cd-btn cancel clickable-hover" @tap="showGrabModal = false">
|
||
<text class="cd-btn-txt cancel">再看看</text>
|
||
</view>
|
||
<view class="cd-btn ok clickable-hover" :class="{ disabled: grabbing }" @tap="confirmGrab">
|
||
<text class="cd-btn-txt ok">{{ grabbing ? '抢单中...' : '确定' }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 7. 师傅竞价报价抽屉/弹窗 -->
|
||
<view class="quote-mask" v-if="showQuoteModal" @tap="showQuoteModal = false">
|
||
<view class="quote-drawer" @tap.stop>
|
||
<view class="qd-head">
|
||
<text class="qd-title">提交竞价报价</text>
|
||
<image class="qd-close clickable-hover" src="/static/icons/close.svg" mode="aspectFit" @tap="showQuoteModal = false" />
|
||
</view>
|
||
|
||
<view class="qd-body">
|
||
<view class="qd-field">
|
||
<text class="qd-label">我的报价金额(元)</text>
|
||
<view class="price-input-wrap">
|
||
<text class="cny-sym">¥</text>
|
||
<input
|
||
class="price-input"
|
||
type="digit"
|
||
v-model="quoteForm.amount"
|
||
placeholder="请输入合理报价"
|
||
placeholder-style="color: #BBBBBB"
|
||
/>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="qd-field">
|
||
<text class="qd-label">有效时长(小时)</text>
|
||
<picker mode="selector" :range="quoteHourOptions" @change="onQuoteHourChange">
|
||
<view class="hour-picker-val">{{ quoteForm.valid_hours }} 小时内有效 ›</view>
|
||
</picker>
|
||
</view>
|
||
|
||
<view class="qd-field">
|
||
<text class="qd-label">服务承诺与说明</text>
|
||
<textarea
|
||
class="desc-textarea"
|
||
v-model="quoteForm.description"
|
||
placeholder="如:自带专业工具,现场不加价,质保90天"
|
||
placeholder-style="color: #BBBBBB"
|
||
maxlength="200"
|
||
/>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="qd-foot">
|
||
<view class="qd-submit-btn clickable-hover" :class="{ disabled: quoteSubmitting }" @tap="submitQuote">
|
||
<text class="qsb-txt">{{ quoteSubmitting ? '提交中...' : '确认提交报价' }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, reactive } from 'vue';
|
||
import { onLoad } from '@dcloudio/uni-app';
|
||
import api from '@/utils/api.js';
|
||
|
||
const orderId = ref(0);
|
||
const order = ref(null);
|
||
const loading = ref(false);
|
||
|
||
const showGrabModal = ref(false);
|
||
const grabbing = ref(false);
|
||
|
||
const showQuoteModal = ref(false);
|
||
const quoteSubmitting = ref(false);
|
||
const quoteHourOptions = ['12', '24', '48', '72'];
|
||
const quoteForm = reactive({
|
||
amount: '',
|
||
valid_hours: 24,
|
||
description: '自带专业工具,准时上门,工艺规范保障质量'
|
||
});
|
||
|
||
onLoad((query) => {
|
||
if (query && query.id) {
|
||
orderId.value = Number(query.id);
|
||
loadDetail();
|
||
}
|
||
});
|
||
|
||
async function loadDetail() {
|
||
loading.value = true;
|
||
try {
|
||
const res = await api.masterOrderDetail(orderId.value);
|
||
order.value = res;
|
||
} catch (e) {
|
||
uni.showToast({ title: e.msg || '加载订单失败', icon: 'none' });
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
}
|
||
|
||
function goBack() {
|
||
const pages = getCurrentPages();
|
||
if (pages.length > 1) {
|
||
uni.navigateBack();
|
||
} else {
|
||
uni.reLaunch({ url: '/pages/master/hall' });
|
||
}
|
||
}
|
||
|
||
function shareOrder() {
|
||
uni.showToast({ title: '已生成分享链接', icon: 'none' });
|
||
}
|
||
|
||
function contactService() {
|
||
uni.showModal({
|
||
title: '联系师傅客服',
|
||
content: '客服电话:400-800-6688(工作时间 08:00 - 22:00)',
|
||
confirmText: '立即拨打',
|
||
confirmColor: '#F0710D',
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
uni.makePhoneCall({ phoneNumber: '4008006688', fail: () => {} });
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
function copyOrderNo() {
|
||
if (!order.value) return;
|
||
uni.setClipboardData({
|
||
data: String(order.value.order_no),
|
||
success: () => {
|
||
uni.showToast({ title: '订单号已复制', icon: 'none' });
|
||
}
|
||
});
|
||
}
|
||
|
||
function openLocationMap() {
|
||
if (!order.value) return;
|
||
const lat = Number(order.value.lat) || 26.647;
|
||
const lng = Number(order.value.lng) || 106.630;
|
||
uni.openLocation({
|
||
latitude: lat,
|
||
longitude: lng,
|
||
name: order.value.full_address || '服务地址',
|
||
address: order.value.full_address,
|
||
fail: () => {
|
||
uni.showToast({ title: '无法唤起地图服务', icon: 'none' });
|
||
}
|
||
});
|
||
}
|
||
|
||
function previewGoodsImage() {
|
||
if (!order.value) return;
|
||
const imgs = order.value.goods_images && order.value.goods_images.length
|
||
? order.value.goods_images
|
||
: [order.value.service_image || '/static/icons/cat-sofa.svg'];
|
||
uni.previewImage({
|
||
urls: imgs,
|
||
current: 0
|
||
});
|
||
}
|
||
|
||
function orderFeedback() {
|
||
uni.showToast({ title: '感谢您的反馈,师傅客服将跟进', icon: 'none' });
|
||
}
|
||
|
||
function openGrabConfirm() {
|
||
showGrabModal.value = true;
|
||
}
|
||
|
||
async function confirmGrab() {
|
||
if (grabbing.value || !order.value) return;
|
||
grabbing.value = true;
|
||
try {
|
||
const res = await api.masterGrab({ order_id: order.value.id });
|
||
showGrabModal.value = false;
|
||
uni.showToast({ title: '抢单成功!', icon: 'success' });
|
||
setTimeout(() => {
|
||
uni.redirectTo({
|
||
url: `/pages/master/success?order_id=${order.value.id}&mobile=${res.user_mobile || ''}`
|
||
});
|
||
}, 600);
|
||
} catch (e) {
|
||
uni.showToast({ title: e.msg || '抢单失败,可能已被接单', icon: 'none' });
|
||
} finally {
|
||
grabbing.value = false;
|
||
}
|
||
}
|
||
|
||
function openQuoteDrawer() {
|
||
showQuoteModal.value = true;
|
||
}
|
||
|
||
function onQuoteHourChange(e) {
|
||
quoteForm.valid_hours = Number(quoteHourOptions[e.detail.value]);
|
||
}
|
||
|
||
async function submitQuote() {
|
||
const amt = Number(quoteForm.amount);
|
||
if (!amt || amt <= 0) {
|
||
uni.showToast({ title: '请输入有效的报价金额', icon: 'none' });
|
||
return;
|
||
}
|
||
if (quoteSubmitting.value || !order.value) return;
|
||
quoteSubmitting.value = true;
|
||
try {
|
||
await api.masterQuote({
|
||
order_id: order.value.id,
|
||
amount: amt,
|
||
valid_hours: quoteForm.valid_hours,
|
||
description: quoteForm.description
|
||
});
|
||
showQuoteModal.value = false;
|
||
uni.showToast({ title: '报价提交成功,等待客户选择', icon: 'success' });
|
||
setTimeout(() => {
|
||
loadDetail();
|
||
}, 800);
|
||
} catch (e) {
|
||
uni.showToast({ title: e.msg || '提交报价失败', icon: 'none' });
|
||
} finally {
|
||
quoteSubmitting.value = false;
|
||
}
|
||
}
|
||
|
||
function goMyOrders() {
|
||
uni.redirectTo({ url: '/pages/master/orders' });
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.detail-page {
|
||
min-height: 100vh;
|
||
background: #F4F5F7;
|
||
padding-bottom: 20rpx;
|
||
}
|
||
|
||
/* 顶部导航 */
|
||
.nav-bar {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 20rpx 24rpx;
|
||
height: 88rpx;
|
||
background: #FFFFFF;
|
||
box-sizing: border-box;
|
||
border-bottom: 1rpx solid #EBEBEB;
|
||
}
|
||
.nav-btn {
|
||
width: 60rpx;
|
||
height: 60rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
.nav-icon {
|
||
width: 36rpx;
|
||
height: 36rpx;
|
||
}
|
||
.nav-title {
|
||
font-size: 32rpx;
|
||
font-weight: 600;
|
||
color: #1F1F1F;
|
||
}
|
||
.nav-actions {
|
||
width: 60rpx;
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
}
|
||
.action-icon {
|
||
width: 38rpx;
|
||
height: 38rpx;
|
||
}
|
||
|
||
/* 顶部抢单提示条(对标实机) */
|
||
.rush-banner {
|
||
background: #FFF7ED;
|
||
border-bottom: 1rpx solid #FFEDD5;
|
||
padding: 16rpx 28rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
.rush-left, .rush-right {
|
||
display: flex;
|
||
align-items: baseline;
|
||
}
|
||
.rush-bold {
|
||
font-size: 28rpx;
|
||
font-weight: 700;
|
||
color: #EA580C;
|
||
margin: 0 4rpx;
|
||
}
|
||
.rush-txt {
|
||
font-size: 24rpx;
|
||
color: #9A3412;
|
||
}
|
||
|
||
.page-body {
|
||
padding: 20rpx 24rpx;
|
||
}
|
||
|
||
/* 通用卡片 */
|
||
.card {
|
||
background: #FFFFFF;
|
||
border-radius: 20rpx;
|
||
padding: 24rpx;
|
||
margin-bottom: 20rpx;
|
||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.03);
|
||
}
|
||
.card-head {
|
||
margin-bottom: 20rpx;
|
||
}
|
||
.card-title {
|
||
font-size: 30rpx;
|
||
font-weight: 700;
|
||
color: #1F1F1F;
|
||
}
|
||
.flex-between {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
/* 业主信息卡片 */
|
||
.client-phone-row {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 14rpx;
|
||
}
|
||
.client-icon {
|
||
width: 32rpx;
|
||
height: 32rpx;
|
||
margin-right: 12rpx;
|
||
}
|
||
.client-phone {
|
||
font-size: 30rpx;
|
||
font-weight: 600;
|
||
color: #1F1F1F;
|
||
}
|
||
|
||
.address-box {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: space-between;
|
||
padding-bottom: 16rpx;
|
||
border-bottom: 1rpx dashed #E5E7EB;
|
||
}
|
||
.addr-main {
|
||
flex: 1;
|
||
min-width: 0;
|
||
display: flex;
|
||
align-items: flex-start;
|
||
}
|
||
.addr-icon {
|
||
width: 28rpx;
|
||
height: 28rpx;
|
||
margin-top: 6rpx;
|
||
margin-right: 10rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
.addr-text {
|
||
font-size: 28rpx;
|
||
color: #374151;
|
||
line-height: 40rpx;
|
||
font-weight: 500;
|
||
}
|
||
.nav-action {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-left: 20rpx;
|
||
flex-shrink: 0;
|
||
padding: 6rpx 14rpx;
|
||
background: #FFF7ED;
|
||
border-radius: 20rpx;
|
||
}
|
||
.nav-arrow-ico {
|
||
width: 24rpx;
|
||
height: 24rpx;
|
||
margin-right: 6rpx;
|
||
}
|
||
.nav-action-txt {
|
||
font-size: 22rpx;
|
||
color: #EA580C;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.distance-notice {
|
||
margin-top: 16rpx;
|
||
display: flex;
|
||
align-items: flex-start;
|
||
}
|
||
.dist-tag {
|
||
background: #FFF1F2;
|
||
color: #E11D48;
|
||
font-size: 20rpx;
|
||
padding: 2rpx 10rpx;
|
||
border-radius: 6rpx;
|
||
margin-right: 10rpx;
|
||
flex-shrink: 0;
|
||
font-weight: 600;
|
||
}
|
||
.dist-desc {
|
||
font-size: 22rpx;
|
||
color: #6B7280;
|
||
line-height: 34rpx;
|
||
}
|
||
|
||
.appoint-time-row {
|
||
margin-top: 18rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
.time-ico {
|
||
width: 26rpx;
|
||
height: 26rpx;
|
||
margin-right: 10rpx;
|
||
}
|
||
.appoint-k {
|
||
font-size: 26rpx;
|
||
color: #4B5563;
|
||
}
|
||
.appoint-v {
|
||
font-size: 26rpx;
|
||
color: #1F1F1F;
|
||
font-weight: 600;
|
||
}
|
||
|
||
/* 商品信息卡片 */
|
||
.goods-row {
|
||
display: flex;
|
||
padding-bottom: 20rpx;
|
||
border-bottom: 1rpx solid #F3F4F6;
|
||
}
|
||
.goods-thumb-wrap {
|
||
position: relative;
|
||
width: 140rpx;
|
||
height: 140rpx;
|
||
border-radius: 12rpx;
|
||
overflow: hidden;
|
||
background: #F8F9FA;
|
||
flex-shrink: 0;
|
||
}
|
||
.goods-thumb {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
.view-large-badge {
|
||
position: absolute;
|
||
bottom: 0; left: 0; right: 0;
|
||
background: rgba(0, 0, 0, 0.55);
|
||
color: #FFFFFF;
|
||
font-size: 18rpx;
|
||
text-align: center;
|
||
padding: 4rpx 0;
|
||
}
|
||
.goods-info {
|
||
flex: 1;
|
||
min-width: 0;
|
||
margin-left: 20rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: space-between;
|
||
}
|
||
.goods-name {
|
||
font-size: 28rpx;
|
||
font-weight: 700;
|
||
color: #1F1F1F;
|
||
line-height: 38rpx;
|
||
}
|
||
.goods-qty {
|
||
font-size: 24rpx;
|
||
color: #6B7280;
|
||
}
|
||
.service-type-row {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
.st-label {
|
||
font-size: 24rpx;
|
||
color: #6B7280;
|
||
}
|
||
.st-pill {
|
||
font-size: 22rpx;
|
||
color: #EA580C;
|
||
background: #FFF7ED;
|
||
border: 1rpx solid #FFEDD5;
|
||
padding: 2rpx 12rpx;
|
||
border-radius: 8rpx;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.remark-block {
|
||
margin-top: 16rpx;
|
||
padding: 14rpx 18rpx;
|
||
background: #FFFBEB;
|
||
border-radius: 12rpx;
|
||
display: flex;
|
||
align-items: baseline;
|
||
}
|
||
.rk-label {
|
||
font-size: 24rpx;
|
||
color: #B45309;
|
||
font-weight: 600;
|
||
flex-shrink: 0;
|
||
}
|
||
.rk-content {
|
||
font-size: 24rpx;
|
||
color: #92400E;
|
||
line-height: 34rpx;
|
||
}
|
||
|
||
.security-banner {
|
||
margin-top: 20rpx;
|
||
padding: 14rpx 18rpx;
|
||
background: #F9FAFB;
|
||
border-radius: 10rpx;
|
||
}
|
||
.sec-text {
|
||
font-size: 22rpx;
|
||
color: #9CA3AF;
|
||
line-height: 32rpx;
|
||
}
|
||
|
||
/* 订单信息卡片 */
|
||
.feedback-link {
|
||
padding: 6rpx 12rpx;
|
||
}
|
||
.fb-txt {
|
||
font-size: 24rpx;
|
||
color: #6B7280;
|
||
}
|
||
.info-row {
|
||
display: flex;
|
||
align-items: center;
|
||
height: 64rpx;
|
||
}
|
||
.ik {
|
||
font-size: 26rpx;
|
||
color: #6B7280;
|
||
width: 150rpx;
|
||
}
|
||
.iv {
|
||
font-size: 26rpx;
|
||
color: #1F1F1F;
|
||
font-weight: 500;
|
||
}
|
||
.mode-highlight {
|
||
color: #F0710D;
|
||
font-weight: 600;
|
||
}
|
||
.price-txt {
|
||
font-size: 30rpx;
|
||
color: #EA580C;
|
||
font-weight: 700;
|
||
}
|
||
.my-quote-txt {
|
||
font-size: 26rpx;
|
||
color: #2563EB;
|
||
font-weight: 600;
|
||
}
|
||
.copy-pill {
|
||
margin-left: 16rpx;
|
||
padding: 2rpx 14rpx;
|
||
background: #F3F4F6;
|
||
border-radius: 8rpx;
|
||
}
|
||
.copy-txt {
|
||
font-size: 22rpx;
|
||
color: #374151;
|
||
}
|
||
|
||
/* 底部固定栏 */
|
||
.bottom-placeholder {
|
||
height: 120rpx;
|
||
padding-bottom: constant(safe-area-inset-bottom);
|
||
padding-bottom: env(safe-area-inset-bottom);
|
||
box-sizing: content-box;
|
||
}
|
||
.bottom-bar {
|
||
position: fixed;
|
||
bottom: 0; left: 0; right: 0;
|
||
background: #FFFFFF;
|
||
border-top: 1rpx solid #E5E7EB;
|
||
height: 110rpx;
|
||
padding: 0 24rpx;
|
||
padding-bottom: constant(safe-area-inset-bottom);
|
||
padding-bottom: env(safe-area-inset-bottom);
|
||
box-sizing: content-box;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.05);
|
||
z-index: 99;
|
||
}
|
||
.bar-left {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
.icon-btn {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-right: 32rpx;
|
||
}
|
||
.ib-ico {
|
||
width: 40rpx;
|
||
height: 40rpx;
|
||
}
|
||
.ib-txt {
|
||
font-size: 20rpx;
|
||
color: #6B7280;
|
||
margin-top: 4rpx;
|
||
}
|
||
|
||
.bar-right {
|
||
flex: 1;
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
}
|
||
.primary-action-btn {
|
||
height: 84rpx;
|
||
padding: 0 36rpx;
|
||
border-radius: 42rpx;
|
||
background: linear-gradient(135deg, #F98D25 0%, #ED6F0E 100%);
|
||
display: flex;
|
||
align-items: baseline;
|
||
justify-content: center;
|
||
box-shadow: 0 6rpx 20rpx rgba(237, 111, 14, 0.35);
|
||
}
|
||
.primary-action-btn.custom {
|
||
background: linear-gradient(135deg, #10B981 0%, #059669 100%);
|
||
box-shadow: 0 6rpx 20rpx rgba(16, 185, 129, 0.35);
|
||
}
|
||
.primary-action-btn.quote {
|
||
background: linear-gradient(135deg, #F97316 0%, #EA580C 100%);
|
||
}
|
||
.pab-title {
|
||
font-size: 30rpx;
|
||
font-weight: 700;
|
||
color: #FFFFFF;
|
||
}
|
||
.pab-sub {
|
||
font-size: 22rpx;
|
||
color: rgba(255, 255, 255, 0.9);
|
||
margin-left: 8rpx;
|
||
}
|
||
|
||
.state-action-btn {
|
||
height: 84rpx;
|
||
padding: 0 36rpx;
|
||
border-radius: 42rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
.state-action-btn.disabled {
|
||
background: #E5E7EB;
|
||
}
|
||
.state-action-btn.disabled .sab-txt {
|
||
color: #9CA3AF;
|
||
font-size: 28rpx;
|
||
font-weight: 600;
|
||
}
|
||
.state-action-btn.success {
|
||
background: #F0FDF4;
|
||
border: 1rpx solid #BBF7D0;
|
||
}
|
||
.state-action-btn.success .sab-txt {
|
||
color: #16A34A;
|
||
font-size: 28rpx;
|
||
font-weight: 600;
|
||
}
|
||
|
||
/* 抢单防误点二次确认弹窗(100% 对标工奇兵实机图3) */
|
||
.confirm-mask {
|
||
position: fixed;
|
||
top: 0; left: 0; right: 0; bottom: 0;
|
||
background: rgba(0, 0, 0, 0.5);
|
||
z-index: 999;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 0 60rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
.confirm-dialog {
|
||
width: 100%;
|
||
background: #FFFFFF;
|
||
border-radius: 28rpx;
|
||
padding: 40rpx 36rpx 32rpx;
|
||
box-sizing: border-box;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
box-shadow: 0 16rpx 48rpx rgba(0, 0, 0, 0.2);
|
||
}
|
||
.cd-title {
|
||
font-size: 34rpx;
|
||
font-weight: 700;
|
||
color: #1F1F1F;
|
||
}
|
||
.cd-content {
|
||
margin-top: 24rpx;
|
||
font-size: 28rpx;
|
||
line-height: 44rpx;
|
||
color: #4B5563;
|
||
text-align: justify;
|
||
}
|
||
.cd-actions {
|
||
width: 100%;
|
||
margin-top: 36rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
border-top: 1rpx solid #F3F4F6;
|
||
padding-top: 24rpx;
|
||
}
|
||
.cd-btn {
|
||
flex: 1;
|
||
height: 80rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
.cd-btn.cancel {
|
||
border-right: 1rpx solid #F3F4F6;
|
||
}
|
||
.cd-btn-txt.cancel {
|
||
font-size: 30rpx;
|
||
color: #6B7280;
|
||
}
|
||
.cd-btn-txt.ok {
|
||
font-size: 30rpx;
|
||
font-weight: 700;
|
||
color: #EA580C;
|
||
}
|
||
.cd-btn.disabled {
|
||
opacity: 0.6;
|
||
}
|
||
|
||
/* 报价抽屉 */
|
||
.quote-mask {
|
||
position: fixed;
|
||
top: 0; left: 0; right: 0; bottom: 0;
|
||
background: rgba(0, 0, 0, 0.55);
|
||
z-index: 999;
|
||
display: flex;
|
||
align-items: flex-end;
|
||
}
|
||
.quote-drawer {
|
||
width: 100%;
|
||
background: #FFFFFF;
|
||
border-radius: 32rpx 32rpx 0 0;
|
||
padding: 36rpx 36rpx constant(safe-area-inset-bottom);
|
||
padding: 36rpx 36rpx env(safe-area-inset-bottom);
|
||
box-sizing: border-box;
|
||
animation: slideUp 0.25s ease-out;
|
||
}
|
||
.qd-head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding-bottom: 24rpx;
|
||
border-bottom: 1rpx solid #F3F4F6;
|
||
}
|
||
.qd-title {
|
||
font-size: 32rpx;
|
||
font-weight: 700;
|
||
color: #1F1F1F;
|
||
}
|
||
.qd-close {
|
||
width: 36rpx;
|
||
height: 36rpx;
|
||
}
|
||
.qd-body {
|
||
padding: 24rpx 0;
|
||
}
|
||
.qd-field {
|
||
margin-bottom: 24rpx;
|
||
}
|
||
.qd-label {
|
||
display: block;
|
||
font-size: 26rpx;
|
||
font-weight: 600;
|
||
color: #374151;
|
||
margin-bottom: 12rpx;
|
||
}
|
||
.price-input-wrap {
|
||
display: flex;
|
||
align-items: center;
|
||
background: #F9FAFB;
|
||
border: 1rpx solid #E5E7EB;
|
||
border-radius: 14rpx;
|
||
height: 88rpx;
|
||
padding: 0 24rpx;
|
||
}
|
||
.cny-sym {
|
||
font-size: 36rpx;
|
||
font-weight: 700;
|
||
color: #EA580C;
|
||
margin-right: 12rpx;
|
||
}
|
||
.price-input {
|
||
flex: 1;
|
||
font-size: 32rpx;
|
||
color: #1F1F1F;
|
||
font-weight: 600;
|
||
}
|
||
.hour-picker-val {
|
||
height: 80rpx;
|
||
background: #F9FAFB;
|
||
border: 1rpx solid #E5E7EB;
|
||
border-radius: 14rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 0 24rpx;
|
||
font-size: 28rpx;
|
||
color: #1F1F1F;
|
||
}
|
||
.desc-textarea {
|
||
width: 100%;
|
||
height: 140rpx;
|
||
background: #F9FAFB;
|
||
border: 1rpx solid #E5E7EB;
|
||
border-radius: 14rpx;
|
||
padding: 16rpx 20rpx;
|
||
box-sizing: border-box;
|
||
font-size: 26rpx;
|
||
color: #1F1F1F;
|
||
}
|
||
.qd-foot {
|
||
padding-top: 12rpx;
|
||
}
|
||
.qd-submit-btn {
|
||
height: 88rpx;
|
||
border-radius: 44rpx;
|
||
background: linear-gradient(135deg, #F98D25 0%, #ED6F0E 100%);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
box-shadow: 0 8rpx 24rpx rgba(237, 111, 14, 0.35);
|
||
}
|
||
.qsb-txt {
|
||
font-size: 30rpx;
|
||
font-weight: 700;
|
||
color: #FFFFFF;
|
||
}
|
||
.qd-submit-btn.disabled {
|
||
opacity: 0.6;
|
||
}
|
||
</style>
|