2099 lines
62 KiB
Vue
2099 lines
62 KiB
Vue
<template>
|
||
<div>
|
||
<el-drawer
|
||
:title="`订单明细 - ${orderInfo.orderId}`"
|
||
:visible.sync="dialogVisible"
|
||
size="60%"
|
||
append-to-body
|
||
:close-on-click-modal="true"
|
||
:close-on-press-escape="true"
|
||
class="order-detail-dialog"
|
||
top="5vh"
|
||
@close="handleClose"
|
||
>
|
||
<div class="order-detail-container">
|
||
<el-tabs v-model="activeName">
|
||
<el-tab-pane
|
||
label="基本信息"
|
||
style="height: 100%"
|
||
name="first"
|
||
></el-tab-pane>
|
||
<el-tab-pane
|
||
label="流程管理"
|
||
style="height: 100%"
|
||
name="third"
|
||
></el-tab-pane>
|
||
</el-tabs>
|
||
<!-- 左侧:订单基本信息 -->
|
||
<div class="detailleft" v-if="activeName == 'first'">
|
||
<el-row :gutter="20">
|
||
<el-row :gutter="20">
|
||
<el-col :span="12">
|
||
<el-card class="box-card" shadow="hover">
|
||
<div slot="header" class="clearfix">
|
||
<span>订单基本信息</span>
|
||
</div>
|
||
|
||
<el-descriptions :column="1" border size="small">
|
||
<el-descriptions-item label="订单号">
|
||
{{ info.orderId || "未设置" }}
|
||
</el-descriptions-item>
|
||
|
||
<el-descriptions-item label="服务名称">
|
||
{{ info.productName || "未设置" }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="订单总价">
|
||
¥{{ (info.totalPrice || 0).toFixed(2) }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="支付金额">
|
||
¥{{ (info.payPrice || 0).toFixed(2) }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="抵扣金额">
|
||
¥{{ (info.deduction || 0).toFixed(2) }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="创建时间">
|
||
{{ info.createdAt || "未设置" }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="支付时间">
|
||
{{ info.payTime || "未设置" }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="订单状态">
|
||
<el-tag
|
||
:type="getStatusTagType(info.status)"
|
||
size="small"
|
||
>
|
||
{{ getStatusLabel(info.status) }}
|
||
</el-tag>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="备注说明" v-if="info.mark">
|
||
{{ info.mark }}
|
||
</el-descriptions-item>
|
||
</el-descriptions>
|
||
|
||
<!-- 附件信息 -->
|
||
<div v-if="orderInfo.fileData" class="attachment-section">
|
||
<h4>订单附件</h4>
|
||
<div class="file-list">
|
||
<div
|
||
v-for="(file, index) in getFileList()"
|
||
:key="index"
|
||
class="file-item"
|
||
>
|
||
<el-image
|
||
v-if="isImage(file)"
|
||
:src="file"
|
||
:preview-src-list="getFileList()"
|
||
fit="cover"
|
||
class="file-image"
|
||
>
|
||
<div slot="error" class="image-slot">
|
||
<i class="el-icon-picture-outline"></i>
|
||
</div>
|
||
</el-image>
|
||
<div v-else class="file-icon">
|
||
<i class="el-icon-document"></i>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</el-card>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-card class="box-card" shadow="hover">
|
||
<div slot="header" class="clearfix">
|
||
<span>预约信息</span>
|
||
</div>
|
||
<el-descriptions :column="1" border size="small">
|
||
<el-descriptions-item label="联系姓名">
|
||
{{ info.adressjson.name }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="联系电话">
|
||
{{ info.adressjson.phone }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="详细位置">
|
||
{{ info.adressjson.info }}
|
||
</el-descriptions-item>
|
||
</el-descriptions>
|
||
<br />
|
||
<AddressSelector
|
||
:longitude="info.adressjson.longitude"
|
||
:latitude="info.adressjson.latitude"
|
||
:address="info.adressjson.addressName"
|
||
:disabled="true"
|
||
/>
|
||
</el-card>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row :gutter="20">
|
||
<!-- 中间:师傅基本信息 -->
|
||
<el-col :span="12">
|
||
<el-card class="box-card" shadow="hover">
|
||
<div slot="header" class="clearfix">
|
||
<span>师傅基本信息</span>
|
||
</div>
|
||
|
||
<el-descriptions :column="1" border size="small">
|
||
<el-descriptions-item label="师傅姓名">
|
||
<div v-if="loadingWorkerInfo" class="loading-info">
|
||
<i class="el-icon-loading"></i> 加载中...
|
||
</div>
|
||
<span v-else-if="workerInfo">{{
|
||
workerInfo.name || "未设置"
|
||
}}</span>
|
||
<span v-else>{{ orderInfo.workerName || "未分配" }}</span>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="师傅电话">
|
||
<div v-if="loadingWorkerInfo" class="loading-info">
|
||
<i class="el-icon-loading"></i> 加载中...
|
||
</div>
|
||
<span v-else-if="workerInfo">{{
|
||
workerInfo.phone || "未设置"
|
||
}}</span>
|
||
<span v-else>{{
|
||
orderInfo.workerPhone || "未设置"
|
||
}}</span>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="师傅等级">
|
||
<div v-if="loadingWorkerInfo" class="loading-info">
|
||
<i class="el-icon-loading"></i> 加载中...
|
||
</div>
|
||
<span v-else-if="workerInfo">{{
|
||
getWorkerLevelLabel(workerInfo.level)
|
||
}}</span>
|
||
<span v-else>{{
|
||
orderInfo.workerLevel || "未设置"
|
||
}}</span>
|
||
</el-descriptions-item>
|
||
<!-- <el-descriptions-item label="服务区域">-->
|
||
<!-- <div v-if="loadingWorkerInfo" class="loading-info">-->
|
||
<!-- <i class="el-icon-loading"></i> 加载中...-->
|
||
<!-- </div>-->
|
||
<!-- <span v-else-if="workerInfo">{{ getWorkerAreaLabel(workerInfo.serviceCityIds) }}</span>-->
|
||
<!-- <span v-else>{{ orderInfo.workerArea || '未设置' }}</span>-->
|
||
<!-- </el-descriptions-item>-->
|
||
<!-- <el-descriptions-item label="技能标签">-->
|
||
<!-- <div v-if="loadingWorkerInfo" class="loading-info">-->
|
||
<!-- <i class="el-icon-loading"></i> 加载中...-->
|
||
<!-- </div>-->
|
||
<!-- <div v-else-if="workerInfo && workerInfo.skillIds" class="skills-tags">-->
|
||
<!-- <el-tag -->
|
||
<!-- v-for="skill in getWorkerSkillLabels(workerInfo.skillIds)" -->
|
||
<!-- :key="skill" -->
|
||
<!-- size="mini" -->
|
||
<!-- type="info"-->
|
||
<!-- style="margin: 2px;"-->
|
||
<!-- >-->
|
||
<!-- {{ skill }}-->
|
||
<!-- </el-tag>-->
|
||
<!-- </div>-->
|
||
<!-- <div v-else-if="orderInfo.workerSkills && orderInfo.workerSkills.length > 0" class="skills-tags">-->
|
||
<!-- <el-tag -->
|
||
<!-- v-for="skill in orderInfo.workerSkills" -->
|
||
<!-- :key="skill" -->
|
||
<!-- size="mini" -->
|
||
<!-- type="info"-->
|
||
<!-- style="margin: 2px;"-->
|
||
<!-- >-->
|
||
<!-- {{ skill }}-->
|
||
<!-- </el-tag>-->
|
||
<!-- </div>-->
|
||
<!-- <span v-else>未设置</span>-->
|
||
<!-- </el-descriptions-item>-->
|
||
<el-descriptions-item label="接单时间">
|
||
{{ orderInfo.receiveTime || "未接单" }}
|
||
</el-descriptions-item>
|
||
<!-- <el-descriptions-item label="派单时间">-->
|
||
<!-- {{ orderInfo.dispatchTime || '未派单' }}-->
|
||
<!-- </el-descriptions-item>-->
|
||
</el-descriptions>
|
||
|
||
<!-- 师傅头像 -->
|
||
<div
|
||
v-if="workerInfo && workerInfo.avatar"
|
||
class="worker-avatar-section"
|
||
>
|
||
<h4>师傅头像</h4>
|
||
<div class="worker-avatar">
|
||
<el-image
|
||
:src="workerInfo.avatar"
|
||
fit="cover"
|
||
class="avatar-image"
|
||
>
|
||
<div slot="error" class="avatar-slot">
|
||
<i class="el-icon-user"></i>
|
||
</div>
|
||
</el-image>
|
||
</div>
|
||
</div>
|
||
<!-- 占位区域,确保卡片高度一致 -->
|
||
<div v-else class="placeholder-section">
|
||
<div class="placeholder-content">
|
||
<i v-if="loadingWorkerInfo" class="el-icon-loading"></i>
|
||
<i v-else class="el-icon-user-solid"></i>
|
||
<p v-if="loadingWorkerInfo">加载中...</p>
|
||
<p v-else>暂无师傅信息</p>
|
||
</div>
|
||
</div>
|
||
</el-card>
|
||
</el-col>
|
||
<!-- 中间:预约信息 -->
|
||
<el-col :span="12">
|
||
<el-card class="box-card" shadow="hover">
|
||
<div slot="header" class="clearfix">
|
||
<span>预约信息</span>
|
||
</div>
|
||
|
||
<el-descriptions :column="1" border size="small">
|
||
<el-descriptions-item label="预约数量">
|
||
{{ orderInfo.num || "0" }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="预约时间">
|
||
{{ orderInfo.appointmentTime || "未设置" }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="服务类型">
|
||
<template v-if="info.odertype == 1"> 拼团订单 </template>
|
||
<template v-if="info.odertype == 2">
|
||
一口价订单
|
||
</template>
|
||
<template v-if="info.odertype == 3"> 秒杀订单 </template>
|
||
<template v-if="info.odertype == 4"> 报价订单 </template>
|
||
<template v-if="info.odertype == 0"> 预约订单 </template>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="服务要求">
|
||
{{ orderInfo.serviceRequirement || "无特殊要求" }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="紧急程度">
|
||
<el-tag
|
||
:type="getUrgencyTagType(orderInfo.urgency)"
|
||
size="small"
|
||
>
|
||
{{ getUrgencyLabel(orderInfo.urgency) }}
|
||
</el-tag>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="预计时长">
|
||
{{ orderInfo.estimatedDuration || "未设置" }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="特殊说明">
|
||
{{ orderInfo.specialNote || "无" }}
|
||
</el-descriptions-item>
|
||
</el-descriptions>
|
||
</el-card>
|
||
</el-col>
|
||
</el-row>
|
||
</el-row>
|
||
</div>
|
||
<div class="detailright" v-if="activeName == 'third'">
|
||
<el-row :gutter="20" class="boxcontent">
|
||
<el-col :span="24">
|
||
<el-card class="box-card" shadow="hover">
|
||
<div>
|
||
<div
|
||
v-if="
|
||
orderInfo.receiveRecords &&
|
||
orderInfo.receiveRecords.length > 0
|
||
"
|
||
class="order-timeline"
|
||
>
|
||
<div class="timeline-container">
|
||
<div
|
||
v-for="(record, index) in orderInfo.receiveRecords"
|
||
:key="record.id || index"
|
||
class="timeline-item"
|
||
>
|
||
<!-- 时间轴节点 -->
|
||
<div
|
||
class="timeline-node"
|
||
:class="getTimelineNodeClass(record.title)"
|
||
>
|
||
<i :class="getTimelineIcon(record.title)"></i>
|
||
</div>
|
||
|
||
<!-- 时间轴内容 -->
|
||
<div class="timeline-content">
|
||
<div class="timeline-header">
|
||
<span class="timeline-title">{{
|
||
record.title || "未知操作"
|
||
}}</span>
|
||
<span class="timeline-time">{{
|
||
formatTimelineTime(record.createdAt)
|
||
}}</span>
|
||
</div>
|
||
<div class="timeline-body">
|
||
<div class="timeline-description">
|
||
{{ getContentDescription(record.content) }}
|
||
</div>
|
||
|
||
<!-- 显示报价数据 -->
|
||
<div
|
||
v-if="record.quote && isJsonString(record.quote)"
|
||
class="timeline-quote"
|
||
>
|
||
<div class="quote-header">
|
||
<i class="el-icon-money"></i>
|
||
<span>报价详情</span>
|
||
</div>
|
||
<div class="quote-content">
|
||
<pre>{{ formatJson(record.quote) }}</pre>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 显示图片 -->
|
||
<div
|
||
v-if="
|
||
record.contentImage &&
|
||
record.contentImage.length > 0
|
||
"
|
||
class="timeline-images"
|
||
>
|
||
<el-image
|
||
v-for="(img, imgIndex) in record.contentImage"
|
||
:key="imgIndex"
|
||
:src="img"
|
||
:preview-src-list="record.contentImage"
|
||
fit="cover"
|
||
class="timeline-image"
|
||
>
|
||
<div slot="error" class="image-slot">
|
||
<i class="el-icon-picture-outline"></i>
|
||
</div>
|
||
</el-image>
|
||
</div>
|
||
|
||
<!-- 订单流程操作按钮 - 只在最后一条日志显示 -->
|
||
<div
|
||
class="timeline-actions"
|
||
v-if="
|
||
index === orderInfo.receiveRecords.length - 1
|
||
"
|
||
>
|
||
<!-- 根据最后一条日志的type显示不同操作 -->
|
||
<el-button
|
||
v-if="record.type === 1"
|
||
type="primary"
|
||
size="small"
|
||
@click="handleDispatchFromTimeline(record)"
|
||
>
|
||
<i class="el-icon-s-promotion"></i>
|
||
派单
|
||
</el-button>
|
||
|
||
<!-- type为1.1时显示更换师傅和接单按钮 -->
|
||
<el-button
|
||
v-if="record.type === 1.1"
|
||
type="warning"
|
||
size="small"
|
||
@click="handleChangeWorker(record)"
|
||
style="margin-right: 8px"
|
||
>
|
||
<i class="el-icon-refresh"></i>
|
||
更换师傅
|
||
</el-button>
|
||
|
||
<el-button
|
||
v-if="record.type === 1.1"
|
||
type="success"
|
||
size="small"
|
||
@click="handleAcceptOrder(record)"
|
||
>
|
||
<i class="el-icon-check"></i>
|
||
接单
|
||
</el-button>
|
||
|
||
<el-button
|
||
v-if="record.type === 2"
|
||
type="success"
|
||
size="small"
|
||
@click="handleDeparture(record)"
|
||
>
|
||
<i class="el-icon-location"></i>
|
||
出发上门
|
||
</el-button>
|
||
|
||
<el-button
|
||
v-if="record.type === 3"
|
||
type="warning"
|
||
size="small"
|
||
@click="handleConfirmArrival(record)"
|
||
>
|
||
<i class="el-icon-location-information"></i>
|
||
确认到达
|
||
</el-button>
|
||
|
||
<!-- type为4时显示三个按钮:结束订单、开始服务、项目报价 -->
|
||
<div
|
||
v-if="record.type === 4"
|
||
class="timeline-actions-multiple"
|
||
>
|
||
<el-button
|
||
type="danger"
|
||
size="small"
|
||
@click="handleEndOrder(record)"
|
||
style="margin-right: 8px"
|
||
>
|
||
<i class="el-icon-close"></i>
|
||
结束订单
|
||
</el-button>
|
||
|
||
<el-button
|
||
type="success"
|
||
size="small"
|
||
@click="handleStartService(record)"
|
||
style="margin-right: 8px"
|
||
>
|
||
<i class="el-icon-video-play"></i>
|
||
开始服务
|
||
</el-button>
|
||
<el-button
|
||
type="primary"
|
||
size="small"
|
||
@click="handleProjectQuote(record)"
|
||
>
|
||
<i class="el-icon-money"></i>
|
||
项目报价
|
||
</el-button>
|
||
</div>
|
||
<el-button
|
||
type="primary"
|
||
size="small"
|
||
@click="handleProjectQuote(record)"
|
||
>
|
||
<i class="el-icon-money"></i>
|
||
项目报价
|
||
</el-button>
|
||
<el-button
|
||
v-if="record.type === 5"
|
||
type="success"
|
||
size="small"
|
||
@click="handleCompleteService(record)"
|
||
>
|
||
<i class="el-icon-circle-check"></i>
|
||
完成服务
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 连接线(除了最后一个项目) -->
|
||
<div
|
||
v-if="index < orderInfo.receiveRecords.length - 1"
|
||
class="timeline-connector"
|
||
></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div v-else class="no-logs">
|
||
<el-empty description="暂无接单记录" :image-size="100">
|
||
<el-button type="primary" @click="handleRefreshLogs"
|
||
>刷新数据</el-button
|
||
>
|
||
</el-empty>
|
||
</div>
|
||
</div>
|
||
</el-card>
|
||
</el-col>
|
||
</el-row>
|
||
<div class="fnbt">
|
||
<el-button
|
||
type="primary"
|
||
size="small"
|
||
@click="handleDispatchFromTimeline()"
|
||
>
|
||
<i class="el-icon-s-promotion"></i>
|
||
派单
|
||
</el-button>
|
||
|
||
<!-- type为1.1时显示更换师傅和接单按钮 -->
|
||
<el-button
|
||
type="warning"
|
||
size="small"
|
||
@click="handleChangeWorker()"
|
||
style="margin-right: 8px"
|
||
>
|
||
<i class="el-icon-refresh"></i>
|
||
更换师傅
|
||
</el-button>
|
||
|
||
<el-button type="success" size="small" @click="handleAcceptOrder()">
|
||
<i class="el-icon-check"></i>
|
||
接单
|
||
</el-button>
|
||
|
||
<el-button type="success" size="small" @click="handleDeparture()">
|
||
<i class="el-icon-location"></i>
|
||
出发上门
|
||
</el-button>
|
||
|
||
<el-button
|
||
type="warning"
|
||
size="small"
|
||
@click="handleConfirmArrival()"
|
||
>
|
||
<i class="el-icon-location-information"></i>
|
||
确认到达
|
||
</el-button>
|
||
<el-button
|
||
type="danger"
|
||
size="small"
|
||
@click="handleEndOrder()"
|
||
style="margin-right: 8px"
|
||
>
|
||
<i class="el-icon-close"></i>
|
||
结束订单
|
||
</el-button>
|
||
|
||
<el-button
|
||
type="success"
|
||
size="small"
|
||
@click="handleStartService()"
|
||
style="margin-right: 8px"
|
||
>
|
||
<i class="el-icon-video-play"></i>
|
||
开始服务
|
||
</el-button>
|
||
<el-button
|
||
type="primary"
|
||
size="small"
|
||
@click="handleProjectQuote()"
|
||
>
|
||
<i class="el-icon-money"></i>
|
||
项目报价
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</el-drawer>
|
||
|
||
<!-- 结束订单 - 上门费输入弹窗 -->
|
||
<el-dialog
|
||
title="结束订单"
|
||
:visible.sync="endOrderDialogVisible"
|
||
width="420px"
|
||
append-to-body
|
||
@close="resetEndOrderForm"
|
||
>
|
||
<el-form
|
||
ref="endOrderFormRef"
|
||
:model="endOrderForm"
|
||
:rules="endOrderRules"
|
||
label-width="90px"
|
||
>
|
||
<el-form-item label="上门费" prop="fee">
|
||
<el-input-number
|
||
v-model.number="endOrderForm.fee"
|
||
:min="0"
|
||
:step="1"
|
||
placeholder="请输入上门费"
|
||
style="width: 100%"
|
||
/>
|
||
</el-form-item>
|
||
</el-form>
|
||
<div slot="footer" class="dialog-footer">
|
||
<el-button @click="endOrderDialogVisible = false">取 消</el-button>
|
||
<el-button
|
||
type="primary"
|
||
:loading="endOrderSubmitting"
|
||
@click="submitEndOrder"
|
||
>确 认</el-button
|
||
>
|
||
</div>
|
||
</el-dialog>
|
||
|
||
<!-- 更换师傅弹窗 -->
|
||
<el-dialog
|
||
title="更换师傅"
|
||
:visible.sync="changeWorkerDialogVisible"
|
||
width="800px"
|
||
append-to-body
|
||
@close="selectedWorker = null"
|
||
>
|
||
<!-- 当前师傅信息 -->
|
||
<div v-if="orderInfo.workerName" class="current-worker-banner">
|
||
<el-alert
|
||
:title="`当前师傅: ${orderInfo.workerName}`"
|
||
type="info"
|
||
:closable="false"
|
||
show-icon
|
||
/>
|
||
</div>
|
||
|
||
<!-- 已选择新师傅提示 -->
|
||
<div v-if="selectedWorker" class="selected-worker-banner">
|
||
<el-alert
|
||
:title="`已选择新师傅: ${selectedWorker.name}`"
|
||
type="success"
|
||
:closable="false"
|
||
show-icon
|
||
/>
|
||
</div>
|
||
|
||
<div class="change-worker-content">
|
||
<!-- 订单信息 -->
|
||
<div class="order-info-section">
|
||
<h4>订单信息</h4>
|
||
<el-row :gutter="20">
|
||
<el-col :span="12">
|
||
<div class="info-item">
|
||
<label>订单号:</label>
|
||
<span>{{ orderInfo.orderId }}</span>
|
||
</div>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<div class="info-item">
|
||
<label>客户姓名:</label>
|
||
<span>{{ orderInfo.userName }}</span>
|
||
</div>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<div class="info-item">
|
||
<label>联系电话:</label>
|
||
<span>{{ orderInfo.userPhone }}</span>
|
||
</div>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<div class="info-item">
|
||
<label>服务地址:</label>
|
||
<span>{{ orderInfo.appointmentAddress || "未设置" }}</span>
|
||
</div>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<div class="info-item">
|
||
<label>服务项目:</label>
|
||
<span>{{ orderInfo.productName }}</span>
|
||
</div>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<div class="info-item">
|
||
<label>订单金额:</label>
|
||
<span class="amount-highlight"
|
||
>¥{{ (orderInfo.payPrice || 0).toFixed(2) }}</span
|
||
>
|
||
</div>
|
||
</el-col>
|
||
</el-row>
|
||
</div>
|
||
|
||
<!-- 选择新师傅 -->
|
||
<div class="worker-selection-section">
|
||
<h4>选择新师傅</h4>
|
||
|
||
<!-- 搜索条件 -->
|
||
<el-form
|
||
:model="workerQueryParams"
|
||
:inline="true"
|
||
style="margin-bottom: 15px"
|
||
>
|
||
<el-form-item label="昵称">
|
||
<el-input
|
||
v-model="workerQueryParams.name"
|
||
placeholder="请输入师傅昵称"
|
||
clearable
|
||
style="width: 150px"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="电话">
|
||
<el-input
|
||
v-model="workerQueryParams.phone"
|
||
placeholder="请输入联系电话"
|
||
clearable
|
||
style="width: 150px"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="状态">
|
||
<el-select
|
||
v-model="workerQueryParams.status"
|
||
placeholder="请选择状态"
|
||
clearable
|
||
style="width: 150px"
|
||
>
|
||
<el-option label="全部" value="" />
|
||
<el-option label="可用" value="1" />
|
||
<el-option label="不可用" value="0" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-button
|
||
type="primary"
|
||
@click="searchWorkers"
|
||
:loading="loadingWorkers"
|
||
>
|
||
<i class="el-icon-search"></i> 搜索
|
||
</el-button>
|
||
<el-button @click="resetWorkerSearch">
|
||
<i class="el-icon-refresh"></i> 重置
|
||
</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
|
||
<!-- 师傅列表 -->
|
||
<div class="worker-list">
|
||
<el-table
|
||
:data="availableWorkers"
|
||
v-loading="loadingWorkers"
|
||
style="width: 100%"
|
||
@row-click="selectWorker"
|
||
:row-class-name="getWorkerRowClass"
|
||
>
|
||
<el-table-column prop="name" label="师傅姓名" width="120">
|
||
<template slot-scope="scope">
|
||
<div class="worker-info">
|
||
<el-avatar
|
||
:size="32"
|
||
:src="scope.row.avatar"
|
||
:alt="scope.row.name"
|
||
>
|
||
<i class="el-icon-user"></i>
|
||
</el-avatar>
|
||
<span style="margin-left: 8px">{{ scope.row.name }}</span>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column
|
||
prop="phonenumber"
|
||
label="联系电话"
|
||
width="120"
|
||
/>
|
||
<el-table-column prop="level" label="等级" width="80">
|
||
<template slot-scope="scope">
|
||
<el-tag :type="getLevelTagType(scope.row.level)" size="mini">
|
||
{{ getWorkerLevelLabel(scope.row.level) }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column
|
||
prop="serviceCityIds"
|
||
label="服务区域"
|
||
width="150"
|
||
>
|
||
<template slot-scope="scope">
|
||
<span>{{
|
||
getWorkerAreaLabel(scope.row.serviceCityIds)
|
||
}}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="skillIds" label="技能标签" min-width="200">
|
||
<template slot-scope="scope">
|
||
<div class="skills-tags">
|
||
<el-tag
|
||
v-for="skill in getWorkerSkillLabels(scope.row.skillIds)"
|
||
:key="skill"
|
||
size="mini"
|
||
type="info"
|
||
style="margin: 2px"
|
||
>
|
||
{{ skill }}
|
||
</el-tag>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="100" align="center">
|
||
<template slot-scope="scope">
|
||
<el-button
|
||
type="primary"
|
||
size="mini"
|
||
@click.stop="selectWorker(scope.row)"
|
||
:disabled="scope.row.id === orderInfo.workerId"
|
||
>
|
||
{{
|
||
scope.row.id === orderInfo.workerId ? "当前师傅" : "选择"
|
||
}}
|
||
</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div slot="footer" class="dialog-footer">
|
||
<el-button @click="changeWorkerDialogVisible = false">取 消</el-button>
|
||
<el-button
|
||
type="primary"
|
||
@click="confirmChangeWorker"
|
||
:disabled="!selectedWorker"
|
||
>
|
||
确认更换
|
||
</el-button>
|
||
</div>
|
||
</el-dialog>
|
||
|
||
<!-- 开始服务 - 上传图片弹窗 -->
|
||
<el-dialog
|
||
title="开始服务"
|
||
:visible.sync="startServiceDialogVisible"
|
||
width="680px"
|
||
append-to-body
|
||
>
|
||
<el-form label-width="90px">
|
||
<el-form-item label="服务图片" required>
|
||
<ImageUpload
|
||
v-model="startServiceImages"
|
||
:limit="9"
|
||
:file-size="10"
|
||
/>
|
||
<div class="el-upload__tip" style="margin-top: 6px; color: #909399">
|
||
至少上传1张服务开始图片,支持 jpg/png/jpeg,单张 ≤ 10MB
|
||
</div>
|
||
</el-form-item>
|
||
</el-form>
|
||
<div slot="footer" class="dialog-footer">
|
||
<el-button @click="startServiceDialogVisible = false">取 消</el-button>
|
||
<el-button
|
||
type="primary"
|
||
:loading="startServiceSubmitting"
|
||
@click="submitStartService"
|
||
>完 成</el-button
|
||
>
|
||
</div>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import { getUsers, listUsers } from "@/api/system/users";
|
||
import { getOrder } from "@/api/system/Order";
|
||
import request from "@/utils/request";
|
||
import ImageUpload from "@/components/ImageUpload";
|
||
import AddressSelector from "@/components/AddressSelector";
|
||
import { js } from "js-beautify";
|
||
|
||
export default {
|
||
name: "OrderDetailDialog",
|
||
components: { ImageUpload, AddressSelector },
|
||
props: {
|
||
visible: {
|
||
type: Boolean,
|
||
default: false,
|
||
},
|
||
orderInfo: {
|
||
type: Object,
|
||
default: () => ({}),
|
||
},
|
||
},
|
||
data() {
|
||
return {
|
||
dialogVisible: false,
|
||
workerInfo: null, // 存储师傅信息
|
||
loadingWorkerInfo: false, // 加载师傅信息的状态
|
||
activeName: "first",
|
||
// 结束订单弹窗
|
||
endOrderDialogVisible: false,
|
||
endOrderSubmitting: false,
|
||
endOrderForm: { fee: 0 },
|
||
endOrderRules: {
|
||
fee: [{ required: true, message: "请输入上门费", trigger: "blur" }],
|
||
},
|
||
|
||
// 更换师傅相关数据
|
||
changeWorkerDialogVisible: false,
|
||
availableWorkers: [],
|
||
selectedWorker: null,
|
||
loadingWorkers: false,
|
||
workerQueryParams: {
|
||
pageNum: 1,
|
||
pageSize: 10,
|
||
name: "",
|
||
phone: "",
|
||
status: "",
|
||
},
|
||
|
||
// 开始服务弹窗
|
||
startServiceDialogVisible: false,
|
||
startServiceImages: [],
|
||
startServiceSubmitting: false,
|
||
info: {}, //订单详情
|
||
};
|
||
},
|
||
watch: {
|
||
visible: {
|
||
immediate: true,
|
||
handler(newVal) {
|
||
this.dialogVisible = newVal;
|
||
if (newVal && this.orderInfo.workerId) {
|
||
this.loadWorkerInfo();
|
||
}
|
||
},
|
||
},
|
||
orderInfo: {
|
||
immediate: true,
|
||
handler() {
|
||
this.getinfo();
|
||
},
|
||
},
|
||
"orderInfo.workerId": {
|
||
immediate: true,
|
||
handler(newWorkerId) {
|
||
if (newWorkerId && this.dialogVisible) {
|
||
this.loadWorkerInfo();
|
||
}
|
||
},
|
||
},
|
||
},
|
||
method() {},
|
||
methods: {
|
||
// 获取订单详情
|
||
getinfo() {
|
||
if (this.orderInfo.id) {
|
||
getOrder(this.orderInfo.id).then((r) => {
|
||
r.data.adressjson = JSON.parse(r.data.adressjson);
|
||
this.info = r.data;
|
||
});
|
||
}
|
||
},
|
||
/** 关闭对话框 */
|
||
handleClose() {
|
||
this.dialogVisible = false;
|
||
this.$emit("update:visible", false);
|
||
},
|
||
|
||
/** 加载师傅信息 */
|
||
async loadWorkerInfo() {
|
||
if (!this.orderInfo.workerId) {
|
||
this.workerInfo = null;
|
||
return;
|
||
}
|
||
|
||
try {
|
||
this.loadingWorkerInfo = true;
|
||
const response = await getUsers(this.orderInfo.workerId);
|
||
if (response.code === 200) {
|
||
this.workerInfo = response.data;
|
||
} else {
|
||
this.$message.error("获取师傅信息失败:" + response.msg);
|
||
this.workerInfo = null;
|
||
}
|
||
} catch (error) {
|
||
console.error("获取师傅信息失败:", error);
|
||
this.$message.error("获取师傅信息失败");
|
||
this.workerInfo = null;
|
||
} finally {
|
||
this.loadingWorkerInfo = false;
|
||
}
|
||
},
|
||
|
||
/** 获取状态标签类型 */
|
||
getStatusTagType(status) {
|
||
if (!status) return "info";
|
||
const statusMap = {
|
||
1: "warning", // 待支付
|
||
2: "primary", // 待服务
|
||
3: "success", // 服务中
|
||
4: "success", // 已完成
|
||
5: "danger", // 已取消
|
||
6: "info", // 已退款
|
||
7: "info", // 已结束
|
||
};
|
||
return statusMap[status] || "info";
|
||
},
|
||
|
||
/** 获取订单状态标签 */
|
||
getStatusLabel(status) {
|
||
if (!status) return "未知状态";
|
||
const statusMap = {
|
||
1: "待支付",
|
||
2: "待服务",
|
||
3: "服务中",
|
||
4: "已完成",
|
||
5: "已取消",
|
||
6: "已退款",
|
||
7: "已结束",
|
||
};
|
||
return statusMap[status] || `状态${status}`;
|
||
},
|
||
|
||
/** 获取紧急程度标签类型 */
|
||
getUrgencyTagType(urgency) {
|
||
if (!urgency) return "info";
|
||
const urgencyMap = {
|
||
1: "info", // 普通
|
||
2: "warning", // 紧急
|
||
3: "danger", // 特急
|
||
};
|
||
return urgencyMap[urgency] || "info";
|
||
},
|
||
|
||
/** 获取紧急程度标签 */
|
||
getUrgencyLabel(urgency) {
|
||
if (!urgency) return "普通";
|
||
const urgencyMap = {
|
||
1: "普通",
|
||
2: "紧急",
|
||
3: "特急",
|
||
};
|
||
return urgencyMap[urgency] || "普通";
|
||
},
|
||
|
||
/** 获取时间轴节点样式类 */
|
||
getTimelineNodeClass(title) {
|
||
if (!title) return "timeline-node-default";
|
||
if (title.includes("订单生成") || title.includes("创建"))
|
||
return "timeline-node-primary";
|
||
if (
|
||
title.includes("支付成功") ||
|
||
title.includes("接单") ||
|
||
title.includes("派单")
|
||
)
|
||
return "timeline-node-success";
|
||
if (title.includes("出发") || title.includes("到达"))
|
||
return "timeline-node-warning";
|
||
if (title.includes("开始服务")) return "timeline-node-info";
|
||
if (title.includes("服务完成") || title.includes("完成"))
|
||
return "timeline-node-danger";
|
||
return "timeline-node-default";
|
||
},
|
||
|
||
/** 获取时间轴图标 */
|
||
getTimelineIcon(title) {
|
||
if (!title) return "el-icon-info";
|
||
if (title.includes("订单生成") || title.includes("创建"))
|
||
return "el-icon-plus";
|
||
if (
|
||
title.includes("支付成功") ||
|
||
title.includes("接单") ||
|
||
title.includes("派单")
|
||
)
|
||
return "el-icon-check";
|
||
if (title.includes("出发") || title.includes("到达"))
|
||
return "el-icon-location";
|
||
if (title.includes("开始服务")) return "el-icon-video-play";
|
||
if (title.includes("服务完成") || title.includes("完成"))
|
||
return "el-icon-circle-check";
|
||
return "el-icon-info";
|
||
},
|
||
|
||
/** 格式化时间轴时间 */
|
||
formatTimelineTime(time) {
|
||
if (!time) return "未知时间";
|
||
const date = new Date(time);
|
||
return date.toLocaleString();
|
||
},
|
||
|
||
/** 获取内容描述 */
|
||
getContentDescription(content) {
|
||
if (!content) return "无描述信息";
|
||
|
||
try {
|
||
const contentData = JSON.parse(content);
|
||
if (Array.isArray(contentData)) {
|
||
return contentData[0]?.name || content;
|
||
} else if (typeof contentData === "object") {
|
||
return contentData.name || content;
|
||
}
|
||
} catch (e) {
|
||
// 如果解析失败,直接返回原始内容
|
||
}
|
||
|
||
return content;
|
||
},
|
||
|
||
/** 判断是否为JSON字符串 */
|
||
isJsonString(str) {
|
||
if (typeof str !== "string") return false;
|
||
try {
|
||
JSON.parse(str);
|
||
return true;
|
||
} catch (e) {
|
||
return false;
|
||
}
|
||
},
|
||
|
||
/** 格式化JSON显示 */
|
||
formatJson(jsonStr) {
|
||
try {
|
||
const obj = JSON.parse(jsonStr);
|
||
return JSON.stringify(obj, null, 2);
|
||
} catch (e) {
|
||
return jsonStr;
|
||
}
|
||
},
|
||
|
||
/** 获取文件列表 */
|
||
getFileList() {
|
||
if (!this.orderInfo.fileData) {
|
||
return [];
|
||
}
|
||
|
||
try {
|
||
let files;
|
||
if (typeof this.orderInfo.fileData === "string") {
|
||
try {
|
||
files = JSON.parse(this.orderInfo.fileData);
|
||
} catch (e) {
|
||
files = this.orderInfo.fileData.split(",").filter(Boolean);
|
||
}
|
||
} else if (Array.isArray(this.orderInfo.fileData)) {
|
||
files = this.orderInfo.fileData;
|
||
} else {
|
||
files = [];
|
||
}
|
||
|
||
return Array.isArray(files) ? files : [];
|
||
} catch (e) {
|
||
console.error("解析文件数据失败:", e);
|
||
return [];
|
||
}
|
||
},
|
||
|
||
/** 判断是否为图片文件 */
|
||
isImage(fileUrl) {
|
||
if (!fileUrl || typeof fileUrl !== "string") return false;
|
||
const imageExtensions = [
|
||
".jpg",
|
||
".jpeg",
|
||
".png",
|
||
".gif",
|
||
".bmp",
|
||
".webp",
|
||
];
|
||
const lowerUrl = fileUrl.toLowerCase();
|
||
return imageExtensions.some((ext) => lowerUrl.includes(ext));
|
||
},
|
||
|
||
/** 刷新日志 */
|
||
handleRefreshLogs() {
|
||
this.$emit("refresh-logs");
|
||
},
|
||
|
||
/** 导出日志 */
|
||
handleExportLogs() {
|
||
this.$emit("export-logs");
|
||
},
|
||
|
||
/** 派单操作 */
|
||
handleDispatchFromTimeline(record) {
|
||
this.$emit("dispatch-order", record);
|
||
},
|
||
|
||
/** 更换师傅操作 */
|
||
handleChangeWorker(record) {
|
||
console.log("更换师傅按钮被点击,记录:", record);
|
||
// 打开更换师傅弹窗
|
||
this.changeWorkerDialogVisible = true;
|
||
// 加载可用的师傅列表
|
||
this.loadAvailableWorkers();
|
||
},
|
||
|
||
/** 接单操作 */
|
||
async handleAcceptOrder(record) {
|
||
try {
|
||
console.log("接单按钮被点击,记录:", record);
|
||
|
||
// 构建接单参数
|
||
const params = {
|
||
orderId: this.orderInfo.orderId,
|
||
workerId: this.orderInfo.workerId || null,
|
||
};
|
||
|
||
console.log("接单参数:", params);
|
||
|
||
// 调用接单API
|
||
const response = await request({
|
||
url: "/system/Order/accept-order",
|
||
method: "post",
|
||
data: params,
|
||
});
|
||
|
||
if (response.code === 200) {
|
||
this.$message.success("接单成功!");
|
||
|
||
// 刷新订单信息
|
||
this.$emit("refresh-order");
|
||
|
||
// 关闭订单明细弹窗
|
||
this.handleClose();
|
||
} else {
|
||
this.$message.error("接单失败:" + response.msg);
|
||
}
|
||
} catch (error) {
|
||
console.error("接单失败:", error);
|
||
this.$message.error("接单失败:" + (error.message || "未知错误"));
|
||
}
|
||
},
|
||
|
||
/** 出发上门操作 */
|
||
handleDeparture(record) {
|
||
this.$emit("departure", record);
|
||
},
|
||
|
||
/** 确认到达操作 */
|
||
async handleConfirmArrival(record) {
|
||
try {
|
||
this.$modal
|
||
.confirm("确认要执行确认到达操作吗?")
|
||
.then(async () => {
|
||
// 调用确认到达API
|
||
const response = await request.post(
|
||
"/system/Order/confirm-arrival",
|
||
{
|
||
orderId: this.orderInfo.orderId,
|
||
}
|
||
);
|
||
|
||
if (response.code === 200) {
|
||
this.$message.success("确认到达成功!");
|
||
// 刷新订单信息
|
||
this.$emit("refresh-order");
|
||
// 关闭订单明细弹窗
|
||
this.handleClose();
|
||
} else {
|
||
this.$message.error("确认到达失败:" + response.msg);
|
||
}
|
||
})
|
||
.catch(() => {
|
||
// 用户取消操作
|
||
});
|
||
} catch (error) {
|
||
console.error("确认到达失败:", error);
|
||
this.$message.error("确认到达失败:" + (error.message || "未知错误"));
|
||
}
|
||
},
|
||
|
||
/** 结束订单操作 */
|
||
handleEndOrder(record) {
|
||
this.endOrderDialogVisible = true;
|
||
},
|
||
|
||
/** 开始服务操作 */
|
||
handleStartService(record) {
|
||
this.startServiceImages = [];
|
||
this.startServiceDialogVisible = true;
|
||
},
|
||
|
||
/** 提交开始服务 */
|
||
async submitStartService() {
|
||
if (!this.startServiceImages || this.startServiceImages.length === 0) {
|
||
this.$message.warning("请至少上传一张服务图片");
|
||
return;
|
||
}
|
||
try {
|
||
this.startServiceSubmitting = true;
|
||
const response = await request({
|
||
url: "/system/Order/start-service",
|
||
method: "post",
|
||
data: {
|
||
orderId: this.orderInfo.orderId,
|
||
image: this.startServiceImages,
|
||
},
|
||
});
|
||
if (response.code === 200) {
|
||
this.$message.success("开始服务成功!");
|
||
this.startServiceDialogVisible = false;
|
||
this.startServiceImages = [];
|
||
this.$emit("refresh-order");
|
||
} else {
|
||
this.$message.error("开始服务失败:" + (response.msg || "未知错误"));
|
||
}
|
||
} catch (error) {
|
||
this.$message.error("开始服务失败:" + (error.message || "网络错误"));
|
||
} finally {
|
||
this.startServiceSubmitting = false;
|
||
}
|
||
},
|
||
|
||
/** 项目报价操作 */
|
||
handleProjectQuote(record) {
|
||
this.$emit("project-quote", record);
|
||
},
|
||
|
||
/** 完成服务操作 */
|
||
handleCompleteService(record) {
|
||
this.$emit("complete-service", record);
|
||
},
|
||
|
||
/** 获取师傅等级标签 */
|
||
getWorkerLevelLabel(level) {
|
||
if (!level) return "未设置";
|
||
const levelMap = {
|
||
1: "初级",
|
||
2: "中级",
|
||
3: "高级",
|
||
4: "专家",
|
||
};
|
||
return levelMap[level] || `等级${level}`;
|
||
},
|
||
|
||
/** 获取师傅服务区域标签 */
|
||
getWorkerAreaLabel(city) {
|
||
if (!city) return "未设置";
|
||
try {
|
||
const cityData = JSON.parse(city);
|
||
if (Array.isArray(cityData)) {
|
||
return cityData.map((item) => item.name || item).join(", ");
|
||
} else if (typeof cityData === "object") {
|
||
return cityData.name || city;
|
||
}
|
||
} catch (e) {
|
||
// 如果解析失败,直接返回原始内容
|
||
}
|
||
return city;
|
||
},
|
||
|
||
/** 重置结束订单表单 */
|
||
resetEndOrderForm() {
|
||
this.endOrderForm = { fee: 0 };
|
||
this.endOrderSubmitting = false;
|
||
},
|
||
|
||
/** 提交结束订单 */
|
||
submitEndOrder() {
|
||
this.$refs.endOrderFormRef &&
|
||
this.$refs.endOrderFormRef.validate(async (valid) => {
|
||
if (!valid) return;
|
||
try {
|
||
this.endOrderSubmitting = true;
|
||
const response = await request({
|
||
url: "/system/Order/end-order",
|
||
method: "post",
|
||
data: {
|
||
orderId: this.orderInfo.orderId,
|
||
doorFee: this.endOrderForm.fee,
|
||
},
|
||
});
|
||
if (response.code === 200) {
|
||
this.$message.success("订单已结束");
|
||
this.$emit("refresh-order");
|
||
this.endOrderDialogVisible = false;
|
||
this.resetEndOrderForm();
|
||
this.handleClose();
|
||
} else {
|
||
this.$message.error(
|
||
"结束订单失败:" + (response.msg || "未知错误")
|
||
);
|
||
}
|
||
} catch (e) {
|
||
this.$message.error("结束订单失败:" + (e.message || "网络错误"));
|
||
} finally {
|
||
this.endOrderSubmitting = false;
|
||
}
|
||
});
|
||
},
|
||
|
||
/** 获取师傅技能标签 */
|
||
getWorkerSkillLabels(skillIds) {
|
||
if (!skillIds) return [];
|
||
try {
|
||
const skills = JSON.parse(skillIds);
|
||
if (Array.isArray(skills)) {
|
||
return skills.map((skill) => skill.name || skill).filter(Boolean);
|
||
}
|
||
} catch (e) {
|
||
// 如果解析失败,直接返回原始内容
|
||
}
|
||
return [];
|
||
},
|
||
|
||
/** 加载可用的师傅列表 */
|
||
async loadAvailableWorkers() {
|
||
try {
|
||
this.loadingWorkers = true;
|
||
|
||
// 构建查询参数
|
||
const params = {
|
||
pageNum: this.workerQueryParams.pageNum,
|
||
pageSize: this.workerQueryParams.pageSize,
|
||
};
|
||
|
||
// 只有当有值时才添加搜索条件
|
||
if (this.workerQueryParams.name) {
|
||
params.name = this.workerQueryParams.name;
|
||
}
|
||
if (this.workerQueryParams.phone) {
|
||
params.phone = this.workerQueryParams.phone;
|
||
}
|
||
if (this.workerQueryParams.status) {
|
||
params.status = this.workerQueryParams.status;
|
||
}
|
||
|
||
console.log("调用getUsers API,参数:", params);
|
||
|
||
// 调用获取可用师傅列表的API
|
||
const response = await listUsers(params);
|
||
|
||
if (response.code === 200) {
|
||
this.availableWorkers = response.rows || [];
|
||
console.log("获取师傅列表成功,数量:", this.availableWorkers.length);
|
||
} else {
|
||
this.$message.error("获取师傅列表失败:" + response.msg);
|
||
this.availableWorkers = [];
|
||
}
|
||
} catch (error) {
|
||
console.error("获取师傅列表失败:", error);
|
||
this.$message.error(
|
||
"获取师傅列表失败:" + (error.message || "未知错误")
|
||
);
|
||
this.availableWorkers = [];
|
||
} finally {
|
||
this.loadingWorkers = false;
|
||
}
|
||
},
|
||
|
||
/** 搜索师傅 */
|
||
searchWorkers() {
|
||
this.workerQueryParams.pageNum = 1;
|
||
this.loadAvailableWorkers();
|
||
},
|
||
|
||
/** 重置师傅搜索 */
|
||
resetWorkerSearch() {
|
||
this.workerQueryParams = {
|
||
pageNum: 1,
|
||
pageSize: 10,
|
||
name: "",
|
||
phone: "",
|
||
status: "",
|
||
};
|
||
this.loadAvailableWorkers();
|
||
},
|
||
|
||
/** 选择师傅 */
|
||
selectWorker(worker) {
|
||
this.selectedWorker = worker;
|
||
this.$message.success(`已选择师傅:${worker.name}`);
|
||
},
|
||
|
||
/** 确认更换师傅 */
|
||
confirmChangeWorker() {
|
||
if (!this.selectedWorker) {
|
||
this.$message.warning("请先选择要更换的师傅");
|
||
return;
|
||
}
|
||
|
||
if (this.selectedWorker.id === this.orderInfo.workerId) {
|
||
this.$message.warning("不能选择当前已分配的师傅");
|
||
return;
|
||
}
|
||
|
||
this.$modal
|
||
.confirm(`确认将订单更换给师傅 ${this.selectedWorker.name} 吗?`)
|
||
.then(() => {
|
||
// 这里调用更换师傅的API
|
||
this.processChangeWorker();
|
||
})
|
||
.catch(() => {
|
||
// 用户取消
|
||
});
|
||
},
|
||
|
||
/** 处理更换师傅 */
|
||
async processChangeWorker() {
|
||
try {
|
||
// 这里需要调用更换师傅的API
|
||
// 您需要根据实际情况实现这个API调用
|
||
this.$message.success("更换师傅成功!");
|
||
this.changeWorkerDialogVisible = false;
|
||
this.selectedWorker = null;
|
||
|
||
// 刷新师傅信息
|
||
if (this.orderInfo.workerId) {
|
||
this.loadWorkerInfo();
|
||
}
|
||
|
||
// 通知父组件刷新数据
|
||
this.$emit("refresh-order");
|
||
} catch (error) {
|
||
console.error("更换师傅失败:", error);
|
||
this.$message.error("更换师傅失败:" + (error.message || "未知错误"));
|
||
}
|
||
},
|
||
|
||
/** 获取等级标签类型 */
|
||
getLevelTagType(level) {
|
||
if (!level) return "info";
|
||
const levelMap = {
|
||
1: "info", // 初级
|
||
2: "success", // 中级
|
||
3: "warning", // 高级
|
||
4: "danger", // 专家
|
||
};
|
||
return levelMap[level] || "info";
|
||
},
|
||
|
||
/** 获取师傅行样式类 */
|
||
getWorkerRowClass({ row }) {
|
||
if (row.id === this.orderInfo.workerId) {
|
||
return "current-worker-row";
|
||
}
|
||
if (row.id === this.selectedWorker?.id) {
|
||
return "selected-worker-row";
|
||
}
|
||
return "";
|
||
},
|
||
},
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.detailleft {
|
||
height: calc(100% - 54px);
|
||
overflow-y: auto;
|
||
overflow-x: hidden;
|
||
}
|
||
.detailright {
|
||
height: calc(100% - 54px);
|
||
|
||
.boxcontent {
|
||
height: calc(100% - 60px);
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.fnbt {
|
||
display: flex;
|
||
height: 60px;
|
||
align-items: center;
|
||
background: #fff;
|
||
border-top: 1px solid #eee;
|
||
margin: 0px -20px -20px -20px;
|
||
box-shadow: 0px 0px 10px 5px rgba(0,0,0,.1);
|
||
padding: 0px 20px;
|
||
position: relative;
|
||
bottom: -20px;
|
||
justify-content: flex-end;
|
||
}
|
||
}
|
||
|
||
.order-detail-dialog {
|
||
.el-dialog__body {
|
||
padding: 20px 30px;
|
||
max-height: 80vh;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.el-dialog__footer {
|
||
padding: 15px 30px;
|
||
border-top: 1px solid #e4e7ed;
|
||
background: #f8f9fa;
|
||
}
|
||
}
|
||
|
||
.order-detail-container {
|
||
// 确保三个卡片在同一行时高度一致
|
||
height: 100%;
|
||
|
||
.el-col {
|
||
display: flex;
|
||
|
||
.el-card {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
margin-bottom: 20px;
|
||
min-height: 0; // 重要:允许flex子项收缩
|
||
|
||
.el-card__header {
|
||
background-color: #f5f7fa;
|
||
border-bottom: 1px solid #e4e7ed;
|
||
flex-shrink: 0; // 防止header收缩
|
||
|
||
.clearfix {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
|
||
span {
|
||
font-weight: 600;
|
||
color: #303133;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.header-actions {
|
||
.action-btn {
|
||
margin-left: 10px;
|
||
color: #409eff;
|
||
|
||
&:hover {
|
||
color: #1890ff;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.el-card__body {
|
||
padding: 20px;
|
||
flex: 1; // 占据剩余空间
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-height: 0; // 允许收缩
|
||
}
|
||
}
|
||
}
|
||
|
||
// 其他卡片样式
|
||
.el-row:not(:first-child) {
|
||
.el-card {
|
||
margin-bottom: 20px;
|
||
}
|
||
}
|
||
|
||
// 强制三个卡片高度一致
|
||
.el-row:first-child {
|
||
.el-col {
|
||
.el-card {
|
||
height: auto !important;
|
||
min-height: 400px; // 设置最小高度确保一致性
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.attachment-section {
|
||
margin-top: 20px;
|
||
padding-top: 20px;
|
||
border-top: 1px solid #e4e7ed;
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: flex-start;
|
||
min-height: 120px; // 确保最小高度
|
||
|
||
h4 {
|
||
margin: 0 0 15px 0;
|
||
color: #303133;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.file-list {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 12px;
|
||
|
||
.file-item {
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.file-image {
|
||
width: 60px;
|
||
height: 60px;
|
||
border-radius: 6px;
|
||
border: 2px solid #e4e7ed;
|
||
cursor: pointer;
|
||
transition: border-color 0.2s ease;
|
||
|
||
&:hover {
|
||
border-color: #409eff;
|
||
}
|
||
}
|
||
|
||
.file-icon {
|
||
width: 60px;
|
||
height: 60px;
|
||
border-radius: 6px;
|
||
font-size: 24px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: #f5f7fa;
|
||
border: 2px solid #e4e7ed;
|
||
color: #909399;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* 订单进度时间轴样式 */
|
||
.order-timeline {
|
||
padding: 20px 0;
|
||
}
|
||
|
||
.timeline-container {
|
||
position: relative;
|
||
}
|
||
|
||
.timeline-item {
|
||
position: relative;
|
||
display: flex;
|
||
align-items: flex-start;
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.timeline-item:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
/* 时间轴节点 */
|
||
.timeline-node {
|
||
width: 40px;
|
||
height: 40px;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-right: 20px;
|
||
flex-shrink: 0;
|
||
position: relative;
|
||
z-index: 2;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.timeline-node i {
|
||
font-size: 18px;
|
||
color: white;
|
||
}
|
||
|
||
.timeline-node-primary {
|
||
background: linear-gradient(135deg, #409eff, #36a3f7);
|
||
}
|
||
|
||
.timeline-node-success {
|
||
background: linear-gradient(135deg, #67c23a, #5daf34);
|
||
}
|
||
|
||
.timeline-node-warning {
|
||
background: linear-gradient(135deg, #e6a23c, #d49426);
|
||
}
|
||
|
||
.timeline-node-info {
|
||
background: linear-gradient(135deg, #909399, #7a7d83);
|
||
}
|
||
|
||
.timeline-node-danger {
|
||
background: linear-gradient(135deg, #f56c6c, #e64242);
|
||
}
|
||
|
||
.timeline-node-default {
|
||
background: linear-gradient(135deg, #909399, #7a7d83);
|
||
}
|
||
|
||
/* 时间轴内容 */
|
||
.timeline-content {
|
||
flex: 1;
|
||
background: white;
|
||
border-radius: 8px;
|
||
padding: 16px;
|
||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
||
border: 1px solid #ebeef5;
|
||
}
|
||
|
||
.timeline-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.timeline-title {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #303133;
|
||
}
|
||
|
||
.timeline-time {
|
||
font-size: 12px;
|
||
color: #909399;
|
||
background: #f5f7fa;
|
||
padding: 4px 8px;
|
||
border-radius: 12px;
|
||
}
|
||
|
||
.timeline-body {
|
||
color: #606266;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.timeline-description {
|
||
margin-bottom: 12px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
/* 时间轴图片 */
|
||
.timeline-images {
|
||
display: flex;
|
||
gap: 8px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.timeline-image {
|
||
width: 80px;
|
||
height: 80px;
|
||
border-radius: 6px;
|
||
border: 1px solid #ebeef5;
|
||
object-fit: cover;
|
||
}
|
||
|
||
/* 连接线 */
|
||
.timeline-connector {
|
||
position: absolute;
|
||
left: 20px;
|
||
top: 40px;
|
||
width: 2px;
|
||
height: 100%;
|
||
background: linear-gradient(to bottom, #e4e7ed, #c0c4cc);
|
||
z-index: 1;
|
||
}
|
||
|
||
/* 无记录状态 */
|
||
.no-logs {
|
||
text-align: center;
|
||
padding: 40px 20px;
|
||
}
|
||
|
||
/* 操作按钮 */
|
||
.timeline-actions {
|
||
margin-top: 12px;
|
||
padding-top: 12px;
|
||
border-top: 1px solid #f0f0f0;
|
||
}
|
||
|
||
.timeline-actions-multiple {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
}
|
||
|
||
/* 报价详情样式 */
|
||
.timeline-quote {
|
||
margin-top: 12px;
|
||
padding: 12px;
|
||
background: #f8f9fa;
|
||
border-radius: 6px;
|
||
border: 1px solid #e9ecef;
|
||
}
|
||
|
||
.quote-header {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 8px;
|
||
font-weight: 600;
|
||
color: #495057;
|
||
}
|
||
|
||
.quote-header i {
|
||
margin-right: 6px;
|
||
color: #28a745;
|
||
}
|
||
|
||
.quote-content {
|
||
background: white;
|
||
padding: 8px;
|
||
border-radius: 4px;
|
||
border: 1px solid #dee2e6;
|
||
}
|
||
|
||
.quote-content pre {
|
||
margin: 0;
|
||
font-size: 12px;
|
||
color: #6c757d;
|
||
white-space: pre-wrap;
|
||
word-break: break-word;
|
||
}
|
||
|
||
/* 技能标签样式 */
|
||
.skills-tags {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 4px;
|
||
}
|
||
|
||
/* 师傅头像样式 */
|
||
.worker-avatar-section {
|
||
margin-top: 20px;
|
||
padding-top: 20px;
|
||
border-top: 1px solid #e4e7ed;
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
min-height: 120px; // 确保最小高度
|
||
|
||
h4 {
|
||
margin: 0 0 15px 0;
|
||
color: #303133;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.worker-avatar {
|
||
display: flex;
|
||
justify-content: center;
|
||
|
||
.avatar-image {
|
||
width: 80px;
|
||
height: 80px;
|
||
border-radius: 50%;
|
||
border: 3px solid #e4e7ed;
|
||
cursor: pointer;
|
||
transition: border-color 0.2s ease;
|
||
|
||
&:hover {
|
||
border-color: #409eff;
|
||
}
|
||
}
|
||
|
||
.avatar-slot {
|
||
width: 80px;
|
||
height: 80px;
|
||
border-radius: 50%;
|
||
background: #f5f7fa;
|
||
border: 3px solid #e4e7ed;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 32px;
|
||
color: #909399;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* 占位区域样式 */
|
||
.placeholder-section {
|
||
margin-top: 20px;
|
||
padding-top: 20px;
|
||
border-top: 1px solid #e4e7ed;
|
||
flex: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-height: 120px; // 确保最小高度
|
||
|
||
.placeholder-content {
|
||
text-align: center;
|
||
color: #909399;
|
||
|
||
i {
|
||
font-size: 48px;
|
||
margin-bottom: 10px;
|
||
display: block;
|
||
}
|
||
|
||
p {
|
||
margin: 0;
|
||
font-size: 14px;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* 加载状态样式 */
|
||
.loading-info {
|
||
display: flex;
|
||
align-items: center;
|
||
color: #909399;
|
||
font-size: 14px;
|
||
|
||
i {
|
||
margin-right: 6px;
|
||
font-size: 16px;
|
||
}
|
||
}
|
||
|
||
/* 响应式设计 */
|
||
@media (max-width: 768px) {
|
||
.order-detail-container {
|
||
.el-col {
|
||
margin-bottom: 20px;
|
||
}
|
||
}
|
||
|
||
.timeline-item {
|
||
flex-direction: column;
|
||
align-items: center;
|
||
text-align: center;
|
||
}
|
||
|
||
.timeline-node {
|
||
margin-right: 0;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.timeline-content {
|
||
width: 100%;
|
||
text-align: left;
|
||
}
|
||
|
||
.timeline-header {
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
gap: 8px;
|
||
}
|
||
|
||
.timeline-connector {
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
}
|
||
}
|
||
|
||
/* 更换师傅弹窗样式 */
|
||
.current-worker-banner,
|
||
.selected-worker-banner {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.change-worker-content {
|
||
.order-info-section {
|
||
margin-bottom: 30px;
|
||
padding: 20px;
|
||
background: #f8f9fa;
|
||
border-radius: 8px;
|
||
border: 1px solid #e9ecef;
|
||
|
||
h4 {
|
||
margin: 0 0 15px 0;
|
||
color: #303133;
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.info-item {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 12px;
|
||
|
||
label {
|
||
font-weight: 600;
|
||
color: #606266;
|
||
width: 80px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
span {
|
||
color: #303133;
|
||
flex: 1;
|
||
}
|
||
|
||
.amount-highlight {
|
||
color: #f56c6c;
|
||
font-weight: 600;
|
||
font-size: 16px;
|
||
}
|
||
}
|
||
}
|
||
|
||
.worker-selection-section {
|
||
h4 {
|
||
margin: 0 0 15px 0;
|
||
color: #303133;
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.worker-list {
|
||
.worker-info {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.skills-tags {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 4px;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* 师傅行样式 */
|
||
.current-worker-row {
|
||
background-color: #f0f9ff !important;
|
||
|
||
.el-button {
|
||
background-color: #909399;
|
||
border-color: #909399;
|
||
color: white;
|
||
cursor: not-allowed;
|
||
}
|
||
}
|
||
|
||
.selected-worker-row {
|
||
background-color: #f0f9ff !important;
|
||
border: 2px solid #409eff;
|
||
}
|
||
</style>
|