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

1075 行
26 KiB
Vue

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

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

<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>