|
|
@@ -1,750 +1,793 @@
|
|
|
<template>
|
|
|
- <div class="page-container">
|
|
|
- <el-card shadow="never" class="table-card">
|
|
|
- <template #header>
|
|
|
- <div class="card-header">
|
|
|
- <span class="title">订单列表</span>
|
|
|
- <div class="right-panel">
|
|
|
- <el-radio-group v-model="filters.service" size="default" @change="handleSearch">
|
|
|
- <el-radio-button label="">全部类型</el-radio-button>
|
|
|
- <el-radio-button v-for="item in serviceOptions" :key="item.id" :label="item.id">{{ item.name
|
|
|
- }}</el-radio-button>
|
|
|
- </el-radio-group>
|
|
|
- <el-input v-model="filters.content" placeholder="订单号/商户/宠主/手机号" class="search-input"
|
|
|
- prefix-icon="Search" clearable @clear="handleSearch" @keyup.enter="handleSearch" />
|
|
|
- <el-button type="primary" icon="Search" @click="handleSearch">查询</el-button>
|
|
|
- </div>
|
|
|
+ <div class="page-container">
|
|
|
+ <el-card shadow="never" class="table-card">
|
|
|
+ <template #header>
|
|
|
+ <div class="card-header">
|
|
|
+ <span class="title">订单列表</span>
|
|
|
+ <div class="right-panel">
|
|
|
+ <el-radio-group v-model="filters.service" size="default" @change="handleSearch">
|
|
|
+ <el-radio-button label="">全部类型</el-radio-button>
|
|
|
+ <el-radio-button v-for="item in serviceOptions" :key="item.id" :label="item.id">{{ item.name }}</el-radio-button>
|
|
|
+ </el-radio-group>
|
|
|
+ <el-input
|
|
|
+ v-model="filters.content"
|
|
|
+ placeholder="订单号/商户/宠主/手机号"
|
|
|
+ class="search-input"
|
|
|
+ prefix-icon="Search"
|
|
|
+ clearable
|
|
|
+ @clear="handleSearch"
|
|
|
+ @keyup.enter="handleSearch"
|
|
|
+ />
|
|
|
+ <el-button type="primary" icon="Search" @click="handleSearch">查询</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <el-tabs v-model="filters.status" class="status-tabs" @tab-change="handleStatusTabChange">
|
|
|
+ <el-tab-pane label="全部订单" name="" />
|
|
|
+ <el-tab-pane label="待派单" name="0" />
|
|
|
+ <el-tab-pane label="待接单" name="1" />
|
|
|
+ <el-tab-pane label="服务中" name="2" />
|
|
|
+ <el-tab-pane label="待商家确认" name="3" />
|
|
|
+ <el-tab-pane label="已完成" name="4" />
|
|
|
+ <el-tab-pane label="已取消" name="5" />
|
|
|
+ </el-tabs>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <el-table :data="tableData" style="width: 100%" v-loading="loading" :header-cell-style="{ background: '#f5f7fa' }">
|
|
|
+ <el-table-column prop="code" label="订单号" width="170" fixed="left" />
|
|
|
+
|
|
|
+ <el-table-column label="服务类型" width="190">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <div class="service-type-cell">
|
|
|
+ <el-tag>{{ getServiceName(row.service) }}</el-tag>
|
|
|
+ <el-tag v-if="getServiceModeTag(row)" class="sub-tag" type="warning" effect="plain">{{ getServiceModeTag(row) }}</el-tag>
|
|
|
+ <el-tag v-if="getServiceOrderTypeTag(row)" class="sub-tag" :type="getServiceOrderTypeTag(row).type" effect="dark">{{
|
|
|
+ getServiceOrderTypeTag(row).label
|
|
|
+ }}</el-tag>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column label="宠物信息" min-width="150">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <div class="pet-info">
|
|
|
+ <el-avatar :size="30" class="avatar-type">{{ row.petName?.charAt(0) }}</el-avatar>
|
|
|
+ <div class="pet-detail">
|
|
|
+ <div class="pet-name">
|
|
|
+ {{ row.petName }}
|
|
|
</div>
|
|
|
-
|
|
|
- <el-tabs v-model="filters.status" class="status-tabs" @tab-change="handleStatusTabChange">
|
|
|
- <el-tab-pane label="全部订单" name="" />
|
|
|
- <el-tab-pane label="待派单" name="0" />
|
|
|
- <el-tab-pane label="待接单" name="1" />
|
|
|
- <el-tab-pane label="服务中" name="2" />
|
|
|
- <el-tab-pane label="待商家确认" name="3" />
|
|
|
- <el-tab-pane label="已完成" name="4" />
|
|
|
- <el-tab-pane label="已取消" name="5" />
|
|
|
- </el-tabs>
|
|
|
- </template>
|
|
|
-
|
|
|
- <el-table :data="tableData" style="width: 100%" v-loading="loading"
|
|
|
- :header-cell-style="{ background: '#f5f7fa' }">
|
|
|
- <el-table-column prop="code" label="订单号" width="170" fixed="left" />
|
|
|
-
|
|
|
- <el-table-column label="服务类型" width="190">
|
|
|
- <template #default="{ row }">
|
|
|
- <div class="service-type-cell">
|
|
|
- <el-tag>{{ getServiceName(row.service) }}</el-tag>
|
|
|
- <el-tag v-if="getServiceModeTag(row)" class="sub-tag" type="warning" effect="plain">{{
|
|
|
- getServiceModeTag(row) }}</el-tag>
|
|
|
- <el-tag v-if="getServiceOrderTypeTag(row)" class="sub-tag"
|
|
|
- :type="getServiceOrderTypeTag(row).type" effect="dark">{{
|
|
|
- getServiceOrderTypeTag(row).label }}</el-tag>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
-
|
|
|
- <el-table-column label="宠物信息" min-width="150">
|
|
|
- <template #default="{ row }">
|
|
|
- <div class="pet-info">
|
|
|
- <el-avatar :size="30" class="avatar-type">{{ row.petName?.charAt(0) }}</el-avatar>
|
|
|
- <div class="pet-detail">
|
|
|
- <div class="pet-name">
|
|
|
- {{ row.petName }}
|
|
|
- </div>
|
|
|
- <div class="pet-breed">{{ row.petBreed }}</div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
-
|
|
|
- <el-table-column label="所属用户" width="120" prop="customerName">
|
|
|
- <template #default="{ row }">
|
|
|
- <span style="font-weight: 500">{{ row.customerName }}</span>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
-
|
|
|
- <el-table-column label="城市/区域" width="140">
|
|
|
- <template #default="{ row }">
|
|
|
- <div>{{ getCityDistrictText(row).city || '-' }}</div>
|
|
|
- <div class="sub-text">{{ getCityDistrictText(row).district || '-' }}</div>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
-
|
|
|
- <el-table-column label="商户/下单人" min-width="160">
|
|
|
- <template #default="{ row }">
|
|
|
- <div class="merchant-info">
|
|
|
- <div>{{ row.storeName }}</div>
|
|
|
- <div class="sub-text" v-if="row.placerUsername">{{ row.placerUsername }}</div>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
-
|
|
|
- <el-table-column prop="createTime" label="下单时间" width="165" sortable>
|
|
|
- <template #default="{ row }">
|
|
|
- <span class="time-text">{{ row.createTime }}</span>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
-
|
|
|
- <el-table-column label="订单状态" width="100">
|
|
|
- <template #default="{ row }">
|
|
|
- <div class="status-cell">
|
|
|
- <div class="status-dot" :class="getStatusClass(row.status)"></div>
|
|
|
- <span>{{ getStatusName(row.status) }}</span>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
-
|
|
|
- <el-table-column label="履约信息" width="140">
|
|
|
- <template #default="{ row }">
|
|
|
- <div v-if="row.fulfillerName" class="fulfiller-info">
|
|
|
- <span class="fulfiller-name">{{ row.fulfillerName }}</span>
|
|
|
- <span class="fulfiller-fee" v-if="row.price !== null && row.price !== undefined">¥{{
|
|
|
- row.price }}</span>
|
|
|
- </div>
|
|
|
- <span v-else class="text-gray">暂未指派</span>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
-
|
|
|
- <el-table-column label="操作" width="200" fixed="right">
|
|
|
- <template #default="{ row }">
|
|
|
- <div class="op-cell">
|
|
|
- <el-button link type="primary" size="small" @click="handleDetail(row)">详情</el-button>
|
|
|
- <el-button v-if="row.status === 0" link type="success" size="small"
|
|
|
- @click="openDispatchDialog(row)">派单</el-button>
|
|
|
- <el-button v-if="[1, 2].includes(row.status)" link type="warning" size="small"
|
|
|
- @click="openDispatchDialog(row)">重新派单</el-button>
|
|
|
- <el-button v-if="[0, 1].includes(row.status)" link type="danger" size="small"
|
|
|
- @click="handleCancel(row)">取消</el-button>
|
|
|
-
|
|
|
- <el-dropdown v-if="[2, 3, 4].includes(row.status)" trigger="click"
|
|
|
- @command="(cmd) => handleCommand(cmd, row)">
|
|
|
- <span class="el-dropdown-link">
|
|
|
- 更多<el-icon class="el-icon--right">
|
|
|
- <ArrowDown />
|
|
|
- </el-icon>
|
|
|
- </span>
|
|
|
- <template #dropdown>
|
|
|
- <el-dropdown-menu>
|
|
|
- <el-dropdown-item v-if="row.status === 3"
|
|
|
- command="complete">确认完成</el-dropdown-item>
|
|
|
- <el-dropdown-item v-if="[3, 4].includes(row.status)"
|
|
|
- command="care_summary">护理小结</el-dropdown-item>
|
|
|
- <el-dropdown-item command="reward">奖惩</el-dropdown-item>
|
|
|
- <el-dropdown-item command="remark">备注</el-dropdown-item>
|
|
|
- </el-dropdown-menu>
|
|
|
- </template>
|
|
|
- </el-dropdown>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- </el-table>
|
|
|
-
|
|
|
- <div class="pagination-container">
|
|
|
- <el-pagination v-model:current-page="pagination.current" v-model:page-size="pagination.size"
|
|
|
- :page-sizes="[10, 20, 50, 100]" layout="total, sizes, prev, pager, next, jumper"
|
|
|
- :total="pagination.total" @size-change="handleSizeChange" @current-change="handleCurrentChange" />
|
|
|
+ <div class="pet-breed">{{ row.petBreed }}</div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- </el-card>
|
|
|
-
|
|
|
- <!-- 组件 -->
|
|
|
- <OrderDetailDrawer v-model:visible="detailVisible" :order="currentOrder" @dispatch="openDispatchDialog"
|
|
|
- @cancel="handleCancel" @command="handleCommand" @care-summary="openCareSummary" />
|
|
|
-
|
|
|
- <DispatchDialog v-model:visible="dispatchDialogVisible" :order="currentDispatchOrder"
|
|
|
- @submit="handleDispatchSubmit" />
|
|
|
-
|
|
|
- <CareSummaryDrawer v-model:visible="careSummaryVisible" :order="careSummaryOrder" @submit="saveCareSummary" />
|
|
|
-
|
|
|
- <RewardDialog v-model:visible="rewardDialogVisible" :order="currentOperateRow" @submit="handleRewardSubmit" />
|
|
|
-
|
|
|
- <RemarkDialog v-model:visible="remarkDialogVisible" :order="currentOperateRow" @submit="handleRemarkSubmit" />
|
|
|
-
|
|
|
- </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column label="所属用户" width="120" prop="customerName">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span style="font-weight: 500">{{ row.customerName }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column label="城市/区域" width="140">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <div>{{ getCityDistrictText(row).city || '-' }}</div>
|
|
|
+ <div class="sub-text">{{ getCityDistrictText(row).district || '-' }}</div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column label="商户/下单人" min-width="160">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <div class="merchant-info">
|
|
|
+ <div>{{ row.storeName }}</div>
|
|
|
+ <div class="sub-text" v-if="row.placerUsername">{{ row.placerUsername }}</div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column prop="createTime" label="下单时间" width="165" sortable>
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span class="time-text">{{ row.createTime }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column prop="createTime" label="预约服务时间" width="165" sortable>
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span class="time-text">{{ row.serviceTime }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column label="订单状态" width="100">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <div class="status-cell">
|
|
|
+ <div class="status-dot" :class="getStatusClass(row.status)"></div>
|
|
|
+ <span>{{ getStatusName(row.status) }}</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column label="履约信息" width="140">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <div v-if="row.fulfillerName" class="fulfiller-info">
|
|
|
+ <span class="fulfiller-name">{{ row.fulfillerName }}</span>
|
|
|
+ <span class="fulfiller-fee" v-if="row.price !== null && row.price !== undefined">¥{{ row.price }}</span>
|
|
|
+ </div>
|
|
|
+ <span v-else class="text-gray">暂未指派</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column label="操作" width="200" fixed="right">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <div class="op-cell">
|
|
|
+ <el-button link type="primary" size="small" @click="handleDetail(row)">详情</el-button>
|
|
|
+ <el-button v-if="row.status === 0" link type="success" size="small" @click="openDispatchDialog(row)">派单</el-button>
|
|
|
+ <el-button v-if="[1, 2].includes(row.status)" link type="warning" size="small" @click="openDispatchDialog(row)">重新派单</el-button>
|
|
|
+ <el-button v-if="[0, 1].includes(row.status)" link type="danger" size="small" @click="handleCancel(row)">取消</el-button>
|
|
|
+
|
|
|
+ <el-dropdown v-if="[2, 3, 4].includes(row.status)" trigger="click" @command="(cmd) => handleCommand(cmd, row)">
|
|
|
+ <span class="el-dropdown-link">
|
|
|
+ 更多<el-icon class="el-icon--right">
|
|
|
+ <ArrowDown />
|
|
|
+ </el-icon>
|
|
|
+ </span>
|
|
|
+ <template #dropdown>
|
|
|
+ <el-dropdown-menu>
|
|
|
+ <el-dropdown-item v-if="row.status === 3" command="complete">确认完成</el-dropdown-item>
|
|
|
+ <el-dropdown-item v-if="[3, 4].includes(row.status)" command="care_summary">护理小结</el-dropdown-item>
|
|
|
+ <el-dropdown-item command="reward">奖惩</el-dropdown-item>
|
|
|
+ <el-dropdown-item command="remark">备注</el-dropdown-item>
|
|
|
+ </el-dropdown-menu>
|
|
|
+ </template>
|
|
|
+ </el-dropdown>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+
|
|
|
+ <div class="pagination-container">
|
|
|
+ <el-pagination
|
|
|
+ v-model:current-page="pagination.current"
|
|
|
+ v-model:page-size="pagination.size"
|
|
|
+ :page-sizes="[10, 20, 50, 100]"
|
|
|
+ layout="total, sizes, prev, pager, next, jumper"
|
|
|
+ :total="pagination.total"
|
|
|
+ @size-change="handleSizeChange"
|
|
|
+ @current-change="handleCurrentChange"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+ <!-- 组件 -->
|
|
|
+ <OrderDetailDrawer
|
|
|
+ v-model:visible="detailVisible"
|
|
|
+ :order="currentOrder"
|
|
|
+ @dispatch="openDispatchDialog"
|
|
|
+ @cancel="handleCancel"
|
|
|
+ @command="handleCommand"
|
|
|
+ @care-summary="openCareSummary"
|
|
|
+ />
|
|
|
+
|
|
|
+ <DispatchDialog v-model:visible="dispatchDialogVisible" :order="currentDispatchOrder" @submit="handleDispatchSubmit" />
|
|
|
+
|
|
|
+ <CareSummaryDrawer v-model:visible="careSummaryVisible" :order="careSummaryOrder" @submit="saveCareSummary" />
|
|
|
+
|
|
|
+ <RewardDialog v-model:visible="rewardDialogVisible" :order="currentOperateRow" @submit="handleRewardSubmit" />
|
|
|
+
|
|
|
+ <RemarkDialog v-model:visible="remarkDialogVisible" :order="currentOperateRow" @submit="handleRemarkSubmit" />
|
|
|
+ </div>
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
-import { ref, reactive, onMounted, nextTick } from 'vue'
|
|
|
-import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
-import OrderDetailDrawer from './components/OrderDetailDrawer.vue'
|
|
|
-import DispatchDialog from './components/DispatchDialog.vue'
|
|
|
-import CareSummaryDrawer from './components/CareSummaryDrawer.vue'
|
|
|
-import RewardDialog from './components/RewardDialog.vue'
|
|
|
-import RemarkDialog from './components/RemarkDialog.vue'
|
|
|
-import { listOnStore as listServiceOnStore } from '@/api/service/list/index'
|
|
|
-import { listSubOrder } from '@/api/order/subOrder/index'
|
|
|
-import { dispatchSubOrder } from '@/api/order/subOrder/index'
|
|
|
-import { getSubOrderInfo } from '@/api/order/subOrder/index'
|
|
|
-import { cancelSubOrder } from '@/api/order/subOrder/index'
|
|
|
-import { listOnStore as listAreaStationOnStore } from '@/api/system/areaStation'
|
|
|
-import { getStore } from '@/api/system/store'
|
|
|
-
|
|
|
-const loading = ref(false)
|
|
|
+import { ref, reactive, onMounted, nextTick } from 'vue';
|
|
|
+import { ElMessage, ElMessageBox } from 'element-plus';
|
|
|
+import OrderDetailDrawer from './components/OrderDetailDrawer.vue';
|
|
|
+import DispatchDialog from './components/DispatchDialog.vue';
|
|
|
+import CareSummaryDrawer from './components/CareSummaryDrawer.vue';
|
|
|
+import RewardDialog from './components/RewardDialog.vue';
|
|
|
+import RemarkDialog from './components/RemarkDialog.vue';
|
|
|
+import { listOnStore as listServiceOnStore } from '@/api/service/list/index';
|
|
|
+import { listSubOrder } from '@/api/order/subOrder/index';
|
|
|
+import { dispatchSubOrder } from '@/api/order/subOrder/index';
|
|
|
+import { getSubOrderInfo } from '@/api/order/subOrder/index';
|
|
|
+import { cancelSubOrder } from '@/api/order/subOrder/index';
|
|
|
+import { listOnStore as listAreaStationOnStore } from '@/api/system/areaStation';
|
|
|
+import { getStore } from '@/api/system/store';
|
|
|
+
|
|
|
+const loading = ref(false);
|
|
|
|
|
|
const filters = reactive({
|
|
|
- service: '',
|
|
|
- status: '',
|
|
|
- content: ''
|
|
|
-})
|
|
|
+ service: '',
|
|
|
+ status: '',
|
|
|
+ content: ''
|
|
|
+});
|
|
|
|
|
|
const pagination = reactive({
|
|
|
- current: 1,
|
|
|
- size: 10,
|
|
|
- total: 100
|
|
|
-})
|
|
|
+ current: 1,
|
|
|
+ size: 10,
|
|
|
+ total: 100
|
|
|
+});
|
|
|
|
|
|
-const tableData = ref([])
|
|
|
-const serviceOptions = ref([])
|
|
|
-const areaStationList = ref([])
|
|
|
-const areaStationMap = ref({})
|
|
|
-const storeMap = ref({})
|
|
|
+const tableData = ref([]);
|
|
|
+const serviceOptions = ref([]);
|
|
|
+const areaStationList = ref([]);
|
|
|
+const areaStationMap = ref({});
|
|
|
+const storeMap = ref({});
|
|
|
|
|
|
onMounted(() => {
|
|
|
- getServiceList()
|
|
|
- getAreaStationList()
|
|
|
- handleSearch()
|
|
|
-})
|
|
|
+ getServiceList();
|
|
|
+ getAreaStationList();
|
|
|
+ handleSearch();
|
|
|
+});
|
|
|
|
|
|
const getServiceList = () => {
|
|
|
- listServiceOnStore().then(res => {
|
|
|
- serviceOptions.value = res.data || []
|
|
|
- })
|
|
|
-}
|
|
|
+ listServiceOnStore().then((res) => {
|
|
|
+ serviceOptions.value = res.data || [];
|
|
|
+ });
|
|
|
+};
|
|
|
|
|
|
const getAreaStationList = () => {
|
|
|
- listAreaStationOnStore().then(res => {
|
|
|
- const list = res.data || []
|
|
|
- areaStationList.value = list
|
|
|
- const map = {}
|
|
|
- for (const item of list) {
|
|
|
- if (item && item.id !== undefined && item.id !== null) map[item.id] = item
|
|
|
- }
|
|
|
- areaStationMap.value = map
|
|
|
- })
|
|
|
-}
|
|
|
-
|
|
|
-const getCityDistrictText = (row) => {
|
|
|
- if (!row) return { city: '', district: '' }
|
|
|
- const map = areaStationMap.value || {}
|
|
|
- let stationId = row.site
|
|
|
- if (!map[stationId] && row.store) {
|
|
|
- const store = (storeMap.value || {})[row.store]
|
|
|
- if (store?.site) stationId = store.site
|
|
|
- }
|
|
|
- if (!stationId) return { city: '', district: '' }
|
|
|
-
|
|
|
- const station = map[stationId]
|
|
|
- if (!station) return { city: '', district: '' }
|
|
|
-
|
|
|
- const parent = station.parentId ? map[station.parentId] : undefined
|
|
|
- if (!parent) return { city: station.name || '', district: '' }
|
|
|
-
|
|
|
- if (parent.type === 0) return { city: parent.name || '', district: '' }
|
|
|
- if (parent.type === 1) {
|
|
|
- const city = parent.parentId ? map[parent.parentId] : undefined
|
|
|
- return { city: city?.name || '', district: parent.name || '' }
|
|
|
+ listAreaStationOnStore().then((res) => {
|
|
|
+ const list = res.data || [];
|
|
|
+ areaStationList.value = list;
|
|
|
+ const map = {};
|
|
|
+ for (const item of list) {
|
|
|
+ if (item && item.id !== undefined && item.id !== null) map[item.id] = item;
|
|
|
}
|
|
|
+ areaStationMap.value = map;
|
|
|
+ });
|
|
|
+};
|
|
|
|
|
|
- return { city: '', district: parent.name || '' }
|
|
|
-}
|
|
|
+const getCityDistrictText = (row) => {
|
|
|
+ if (!row) return { city: '', district: '' };
|
|
|
+ const map = areaStationMap.value || {};
|
|
|
+ let stationId = row.site;
|
|
|
+ if (!map[stationId] && row.store) {
|
|
|
+ const store = (storeMap.value || {})[row.store];
|
|
|
+ if (store?.site) stationId = store.site;
|
|
|
+ }
|
|
|
+ if (!stationId) return { city: '', district: '' };
|
|
|
+
|
|
|
+ const station = map[stationId];
|
|
|
+ if (!station) return { city: '', district: '' };
|
|
|
+
|
|
|
+ const parent = station.parentId ? map[station.parentId] : undefined;
|
|
|
+ if (!parent) return { city: station.name || '', district: '' };
|
|
|
+
|
|
|
+ if (parent.type === 0) return { city: parent.name || '', district: '' };
|
|
|
+ if (parent.type === 1) {
|
|
|
+ const city = parent.parentId ? map[parent.parentId] : undefined;
|
|
|
+ return { city: city?.name || '', district: parent.name || '' };
|
|
|
+ }
|
|
|
+
|
|
|
+ return { city: '', district: parent.name || '' };
|
|
|
+};
|
|
|
|
|
|
const handleStatusTabChange = async () => {
|
|
|
- pagination.current = 1
|
|
|
- await nextTick()
|
|
|
- handleSearch()
|
|
|
-}
|
|
|
+ pagination.current = 1;
|
|
|
+ await nextTick();
|
|
|
+ handleSearch();
|
|
|
+};
|
|
|
|
|
|
const handleSearch = () => {
|
|
|
- loading.value = true
|
|
|
- listSubOrder({
|
|
|
- pageNum: pagination.current,
|
|
|
- pageSize: pagination.size,
|
|
|
- service: filters.service !== '' ? filters.service : undefined,
|
|
|
- status: filters.status !== '' ? Number(filters.status) : undefined,
|
|
|
- content: filters.content || undefined
|
|
|
- }).then(res => {
|
|
|
- tableData.value = res.rows || []
|
|
|
- pagination.total = res.total || 0
|
|
|
- loadStoresForRows(tableData.value)
|
|
|
- loading.value = false
|
|
|
- }).catch(() => {
|
|
|
- loading.value = false
|
|
|
+ loading.value = true;
|
|
|
+ listSubOrder({
|
|
|
+ pageNum: pagination.current,
|
|
|
+ pageSize: pagination.size,
|
|
|
+ service: filters.service !== '' ? filters.service : undefined,
|
|
|
+ status: filters.status !== '' ? Number(filters.status) : undefined,
|
|
|
+ content: filters.content || undefined
|
|
|
+ })
|
|
|
+ .then((res) => {
|
|
|
+ tableData.value = res.rows || [];
|
|
|
+ pagination.total = res.total || 0;
|
|
|
+ loadStoresForRows(tableData.value);
|
|
|
+ loading.value = false;
|
|
|
})
|
|
|
-}
|
|
|
+ .catch(() => {
|
|
|
+ loading.value = false;
|
|
|
+ });
|
|
|
+};
|
|
|
|
|
|
const loadStoresForRows = async (rows) => {
|
|
|
- const map = storeMap.value || {}
|
|
|
- const ids = Array.from(new Set((rows || []).map(r => r?.store).filter(Boolean)))
|
|
|
- const missing = ids.filter(id => map[id] === undefined)
|
|
|
- if (missing.length === 0) return
|
|
|
- await Promise.all(missing.map(async (id) => {
|
|
|
- try {
|
|
|
- const res = await getStore(id)
|
|
|
- if (res?.data) map[id] = res.data
|
|
|
- } catch {
|
|
|
- map[id] = null
|
|
|
- }
|
|
|
- }))
|
|
|
- storeMap.value = { ...map }
|
|
|
-}
|
|
|
+ const map = storeMap.value || {};
|
|
|
+ const ids = Array.from(new Set((rows || []).map((r) => r?.store).filter(Boolean)));
|
|
|
+ const missing = ids.filter((id) => map[id] === undefined);
|
|
|
+ if (missing.length === 0) return;
|
|
|
+ await Promise.all(
|
|
|
+ missing.map(async (id) => {
|
|
|
+ try {
|
|
|
+ const res = await getStore(id);
|
|
|
+ if (res?.data) map[id] = res.data;
|
|
|
+ } catch {
|
|
|
+ map[id] = null;
|
|
|
+ }
|
|
|
+ })
|
|
|
+ );
|
|
|
+ storeMap.value = { ...map };
|
|
|
+};
|
|
|
|
|
|
const handleSizeChange = (val) => {
|
|
|
- pagination.size = val
|
|
|
- handleSearch()
|
|
|
-}
|
|
|
+ pagination.size = val;
|
|
|
+ handleSearch();
|
|
|
+};
|
|
|
const handleCurrentChange = (val) => {
|
|
|
- pagination.current = val
|
|
|
- handleSearch()
|
|
|
-}
|
|
|
+ pagination.current = val;
|
|
|
+ handleSearch();
|
|
|
+};
|
|
|
|
|
|
const getServiceName = (serviceId) => {
|
|
|
- const item = serviceOptions.value.find(i => i.id === serviceId)
|
|
|
- return item ? item.name : '未知服务'
|
|
|
-}
|
|
|
+ const item = serviceOptions.value.find((i) => i.id === serviceId);
|
|
|
+ return item ? item.name : '未知服务';
|
|
|
+};
|
|
|
|
|
|
const getServiceModeTag = (row) => {
|
|
|
- const t = row?.type
|
|
|
- if (t === 0 || t === '0' || t === 1 || t === '1') return '往返'
|
|
|
- return ''
|
|
|
-}
|
|
|
+ const t = row?.type;
|
|
|
+ if (t === 0 || t === '0' || t === 1 || t === '1') return '往返';
|
|
|
+ return '';
|
|
|
+};
|
|
|
|
|
|
const getServiceOrderTypeTag = (row) => {
|
|
|
- const t = row?.type
|
|
|
- if (t === 0 || t === '0') return { label: '接', type: 'primary' }
|
|
|
- if (t === 1 || t === '1') return { label: '送', type: 'success' }
|
|
|
- if (t === 2 || t === '2') return { label: '单程接', type: 'primary' }
|
|
|
- if (t === 3 || t === '3') return { label: '单程送', type: 'success' }
|
|
|
- return null
|
|
|
-}
|
|
|
+ const t = row?.type;
|
|
|
+ if (t === 0 || t === '0') return { label: '接', type: 'primary' };
|
|
|
+ if (t === 1 || t === '1') return { label: '送', type: 'success' };
|
|
|
+ if (t === 2 || t === '2') return { label: '单程接', type: 'primary' };
|
|
|
+ if (t === 3 || t === '3') return { label: '单程送', type: 'success' };
|
|
|
+ return null;
|
|
|
+};
|
|
|
|
|
|
const getStatusName = (status) => {
|
|
|
- const map = { 0: '待派单', 1: '待接单', 2: '服务中', 3: '待商家确认', 4: '已完成', 5: '已取消' }
|
|
|
- return map[status] || '未知'
|
|
|
-}
|
|
|
+ const map = { 0: '待派单', 1: '待接单', 2: '服务中', 3: '待商家确认', 4: '已完成', 5: '已取消' };
|
|
|
+ return map[status] || '未知';
|
|
|
+};
|
|
|
|
|
|
const getStatusClass = (status) => {
|
|
|
- const map = { 0: 'pending_dispatch', 1: 'pending_accept', 2: 'serving', 3: 'pending_confirm', 4: 'completed', 5: 'cancelled' }
|
|
|
- return map[status] || 'pending_dispatch'
|
|
|
-}
|
|
|
+ const map = { 0: 'pending_dispatch', 1: 'pending_accept', 2: 'serving', 3: 'pending_confirm', 4: 'completed', 5: 'cancelled' };
|
|
|
+ return map[status] || 'pending_dispatch';
|
|
|
+};
|
|
|
+
|
|
|
+const getFulfillerStatusText = (status) => {
|
|
|
+ const statusMap = {
|
|
|
+ resting: '休息',
|
|
|
+ busy: '接单中',
|
|
|
+ disabled: '禁用'
|
|
|
+ };
|
|
|
+ return statusMap[status] || status;
|
|
|
+};
|
|
|
+
|
|
|
+const getFulfillerStatusType = (status) => {
|
|
|
+ const typeMap = {
|
|
|
+ resting: 'info',
|
|
|
+ busy: 'success',
|
|
|
+ disabled: 'danger'
|
|
|
+ };
|
|
|
+ return typeMap[status] || 'info';
|
|
|
+};
|
|
|
|
|
|
// 弹窗状态管理
|
|
|
-const detailVisible = ref(false)
|
|
|
-const currentOrder = ref(null)
|
|
|
+const detailVisible = ref(false);
|
|
|
+const currentOrder = ref(null);
|
|
|
|
|
|
-const dispatchDialogVisible = ref(false)
|
|
|
-const currentDispatchOrder = ref(null)
|
|
|
+const dispatchDialogVisible = ref(false);
|
|
|
+const currentDispatchOrder = ref(null);
|
|
|
|
|
|
-const careSummaryVisible = ref(false)
|
|
|
-const careSummaryOrder = ref(null)
|
|
|
+const careSummaryVisible = ref(false);
|
|
|
+const careSummaryOrder = ref(null);
|
|
|
|
|
|
-const rewardDialogVisible = ref(false)
|
|
|
-const remarkDialogVisible = ref(false)
|
|
|
-const currentOperateRow = ref(null)
|
|
|
+const rewardDialogVisible = ref(false);
|
|
|
+const remarkDialogVisible = ref(false);
|
|
|
+const currentOperateRow = ref(null);
|
|
|
|
|
|
// 详情
|
|
|
const handleDetail = async (row) => {
|
|
|
- const typeName = getServiceName(row?.service)
|
|
|
- const isTransport = row?.mode === 1 || row?.mode === '1'
|
|
|
- const typeCode = isTransport ? 'transport' : (typeName?.includes('喂') || typeName?.includes('遛') ? 'feeding' : 'washing')
|
|
|
- currentOrder.value = {
|
|
|
- ...row,
|
|
|
- orderNo: row?.code || row?.orderCode || row?.orderNo || row?.orderNumber || row?.no || '',
|
|
|
- type: row?.typeCode || row?.type || typeCode,
|
|
|
- serviceItem: getServiceName(row?.service) || row?.serviceName || row?.service || '',
|
|
|
- userAvatar: 'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png',
|
|
|
- address: '某小区5号楼2单元101',
|
|
|
- groupBuyPackage: '',
|
|
|
- transportType: row.splitType || row.transportType,
|
|
|
+ const typeName = getServiceName(row?.service);
|
|
|
+ const isTransport = row?.mode === 1 || row?.mode === '1';
|
|
|
+ const typeCode = isTransport ? 'transport' : typeName?.includes('喂') || typeName?.includes('遛') ? 'feeding' : 'washing';
|
|
|
+ currentOrder.value = {
|
|
|
+ ...row,
|
|
|
+ orderNo: row?.code || row?.orderCode || row?.orderNo || row?.orderNumber || row?.no || '',
|
|
|
+ type: row?.typeCode || row?.type || typeCode,
|
|
|
+ serviceItem: getServiceName(row?.service) || row?.serviceName || row?.service || '',
|
|
|
+ userAvatar: 'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png',
|
|
|
+ address: '某小区5号楼2单元101',
|
|
|
+ groupBuyPackage: '',
|
|
|
+ transportType: row.splitType || row.transportType,
|
|
|
+ detail: {
|
|
|
+ ...row.detail,
|
|
|
+ pickTime: '2024-02-05 09:30',
|
|
|
+ pickAddr: row.detail?.pickAddr || '北京市朝阳区某小区5号楼2单元101',
|
|
|
+ pickContact: '李先生',
|
|
|
+ pickPhone: '13812345678',
|
|
|
+ dropTime: '2024-02-05 18:30',
|
|
|
+ dropAddr: row.detail?.dropAddr || '北京市朝阳区某小区5号楼2单元101',
|
|
|
+ dropContact: '李先生',
|
|
|
+ dropPhone: '13812345678',
|
|
|
+ packageName: row.detail?.packageName || '精细洗护套餐A',
|
|
|
+ petStatus: '胆小,需安抚',
|
|
|
+ area: '北京市朝阳区某小区5号楼2单元101'
|
|
|
+ },
|
|
|
+ petGender: 'male',
|
|
|
+ petAge: '2岁',
|
|
|
+ petWeight: '15kg',
|
|
|
+ petVaccine: '已接种',
|
|
|
+ petSterilized: true,
|
|
|
+ petCharacter: '活泼好动,喜欢球类玩具',
|
|
|
+ petHealth: '健康良好',
|
|
|
+ fulfillerAvatar: 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png',
|
|
|
+ fulfillerPhone: '13812345678',
|
|
|
+ fulfillerStation: '朝阳服务站',
|
|
|
+ orderLogs: [
|
|
|
+ { time: '2024-02-04 09:30', title: '订单创建', content: '商户提交订单', icon: 'Document' },
|
|
|
+ { time: '2024-02-04 10:00', title: '系统派单', content: '指派给 王大力', icon: 'Bicycle' },
|
|
|
+ { time: '2024-02-04 10:05', title: '接单成功', content: '履约者已确认接单', icon: 'CircleCheck' },
|
|
|
+ { time: '2024-02-04 13:55', title: '到达服务点', content: '履约者已打卡', icon: 'Location' }
|
|
|
+ ]
|
|
|
+ };
|
|
|
+
|
|
|
+ try {
|
|
|
+ const res = await getSubOrderInfo(row?.id);
|
|
|
+ const info = res?.data?.data || res?.data;
|
|
|
+ if (info) {
|
|
|
+ currentOrder.value = {
|
|
|
+ ...(currentOrder.value || {}),
|
|
|
+ id: info.id,
|
|
|
+ orderNo: info.code || currentOrder.value?.orderNo,
|
|
|
+ code: info.code,
|
|
|
+ subOrderType: info.type,
|
|
|
+ status: info.status ?? currentOrder.value?.status,
|
|
|
+ mode: info.mode ?? currentOrder.value?.mode,
|
|
|
+ type: info.mode === 1 || info.mode === '1' ? 'transport' : currentOrder.value?.type,
|
|
|
+ transportType: info.mode === 1 || info.mode === '1' ? 'round' : currentOrder.value?.transportType,
|
|
|
+ serviceTime: info.serviceTime || currentOrder.value?.serviceTime,
|
|
|
+ endServiceTime: info.endServiceTime || currentOrder.value?.endServiceTime,
|
|
|
+ contact: info.contact || currentOrder.value?.contact,
|
|
|
+ contactPhoneNumber: info.contactPhoneNumber || currentOrder.value?.contactPhoneNumber,
|
|
|
+ toAddress: info.toAddress || currentOrder.value?.toAddress,
|
|
|
+ address:
|
|
|
+ info.mode === 1 || info.mode === '1'
|
|
|
+ ? info.toAddress || currentOrder.value?.address
|
|
|
+ : info.address || info.toAddress || currentOrder.value?.address,
|
|
|
+ price: info.price !== undefined && info.price !== null ? Number(info.price) / 100 : currentOrder.value?.price,
|
|
|
+ fulfillerFee: info.price !== undefined && info.price !== null ? Number(info.price) / 100 : currentOrder.value?.fulfillerFee,
|
|
|
+ merchantName: info.storeName || currentOrder.value?.merchantName,
|
|
|
+ platformId: info.platformId ?? currentOrder.value?.platformId,
|
|
|
+ groupBuyPackage: info.groupPurchasePackageName || currentOrder.value?.groupBuyPackage,
|
|
|
+ fulfiller: info.fulfiller ?? currentOrder.value?.fulfiller,
|
|
|
detail: {
|
|
|
- ...row.detail,
|
|
|
- pickTime: '2024-02-05 09:30',
|
|
|
- pickAddr: row.detail?.pickAddr || '北京市朝阳区某小区5号楼2单元101',
|
|
|
- pickContact: '李先生',
|
|
|
- pickPhone: '13812345678',
|
|
|
- dropTime: '2024-02-05 18:30',
|
|
|
- dropAddr: row.detail?.dropAddr || '北京市朝阳区某小区5号楼2单元101',
|
|
|
- dropContact: '李先生',
|
|
|
- dropPhone: '13812345678',
|
|
|
- packageName: row.detail?.packageName || '精细洗护套餐A',
|
|
|
- petStatus: '胆小,需安抚',
|
|
|
- area: '北京市朝阳区某小区5号楼2单元101'
|
|
|
- },
|
|
|
- petGender: 'male',
|
|
|
- petAge: '2岁',
|
|
|
- petWeight: '15kg',
|
|
|
- petVaccine: '已接种',
|
|
|
- petSterilized: true,
|
|
|
- petCharacter: '活泼好动,喜欢球类玩具',
|
|
|
- petHealth: '健康良好',
|
|
|
- fulfillerAvatar: 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png',
|
|
|
- fulfillerPhone: '13812345678',
|
|
|
- fulfillerStation: '朝阳服务站',
|
|
|
- orderLogs: [
|
|
|
- { time: '2024-02-04 09:30', title: '订单创建', content: '商户提交订单', icon: 'Document' },
|
|
|
- { time: '2024-02-04 10:00', title: '系统派单', content: '指派给 王大力', icon: 'Bicycle' },
|
|
|
- { time: '2024-02-04 10:05', title: '接单成功', content: '履约者已确认接单', icon: 'CircleCheck' },
|
|
|
- { time: '2024-02-04 13:55', title: '到达服务点', content: '履约者已打卡', icon: 'Location' }
|
|
|
- ]
|
|
|
- }
|
|
|
-
|
|
|
- try {
|
|
|
- const res = await getSubOrderInfo(row?.id)
|
|
|
- const info = res?.data?.data || res?.data
|
|
|
- if (info) {
|
|
|
- currentOrder.value = {
|
|
|
- ...(currentOrder.value || {}),
|
|
|
- id: info.id,
|
|
|
- orderNo: info.code || currentOrder.value?.orderNo,
|
|
|
- code: info.code,
|
|
|
- subOrderType: info.type,
|
|
|
- status: info.status ?? currentOrder.value?.status,
|
|
|
- mode: info.mode ?? currentOrder.value?.mode,
|
|
|
- type: (info.mode === 1 || info.mode === '1') ? 'transport' : currentOrder.value?.type,
|
|
|
- transportType: (info.mode === 1 || info.mode === '1') ? 'round' : currentOrder.value?.transportType,
|
|
|
- serviceTime: info.serviceTime || currentOrder.value?.serviceTime,
|
|
|
- endServiceTime: info.endServiceTime || currentOrder.value?.endServiceTime,
|
|
|
- contact: info.contact || currentOrder.value?.contact,
|
|
|
- contactPhoneNumber: info.contactPhoneNumber || currentOrder.value?.contactPhoneNumber,
|
|
|
- toAddress: info.toAddress || currentOrder.value?.toAddress,
|
|
|
- address: info.toAddress || currentOrder.value?.address,
|
|
|
- price: (info.price !== undefined && info.price !== null) ? (Number(info.price) / 100) : currentOrder.value?.price,
|
|
|
- fulfillerFee: (info.price !== undefined && info.price !== null) ? (Number(info.price) / 100) : currentOrder.value?.fulfillerFee,
|
|
|
- merchantName: info.storeName || currentOrder.value?.merchantName,
|
|
|
- platformId: info.platformId ?? currentOrder.value?.platformId,
|
|
|
- groupBuyPackage: info.groupPurchasePackageName || currentOrder.value?.groupBuyPackage,
|
|
|
- fulfiller: info.fulfiller ?? currentOrder.value?.fulfiller,
|
|
|
- detail: {
|
|
|
- ...(currentOrder.value?.detail || {}),
|
|
|
- pickTime: info.serviceTime || currentOrder.value?.detail?.pickTime,
|
|
|
- pickAddr: info.fromAddress || currentOrder.value?.detail?.pickAddr,
|
|
|
- pickContact: info.contact || currentOrder.value?.detail?.pickContact,
|
|
|
- pickPhone: info.contactPhoneNumber || currentOrder.value?.detail?.pickPhone,
|
|
|
- dropTime: info.endServiceTime || currentOrder.value?.detail?.dropTime,
|
|
|
- dropAddr: info.toAddress || currentOrder.value?.detail?.dropAddr,
|
|
|
- dropContact: info.contact || currentOrder.value?.detail?.dropContact,
|
|
|
- dropPhone: info.contactPhoneNumber || currentOrder.value?.detail?.dropPhone,
|
|
|
- fromAddress: info.fromAddress || currentOrder.value?.detail?.fromAddress,
|
|
|
- toAddress: info.toAddress || currentOrder.value?.detail?.toAddress,
|
|
|
- area: info.toAddress || currentOrder.value?.detail?.area,
|
|
|
- fromCode: info.fromCode || currentOrder.value?.detail?.fromCode,
|
|
|
- toCode: info.toCode || currentOrder.value?.detail?.toCode
|
|
|
- }
|
|
|
- }
|
|
|
+ ...(currentOrder.value?.detail || {}),
|
|
|
+ pickTime: info.serviceTime || currentOrder.value?.detail?.pickTime,
|
|
|
+ pickAddr: info.fromAddress || currentOrder.value?.detail?.pickAddr,
|
|
|
+ pickContact: info.contact || currentOrder.value?.detail?.pickContact,
|
|
|
+ pickPhone: info.contactPhoneNumber || currentOrder.value?.detail?.pickPhone,
|
|
|
+ dropTime: info.endServiceTime || currentOrder.value?.detail?.dropTime,
|
|
|
+ dropAddr: info.toAddress || currentOrder.value?.detail?.dropAddr,
|
|
|
+ dropContact: info.contact || currentOrder.value?.detail?.dropContact,
|
|
|
+ dropPhone: info.contactPhoneNumber || currentOrder.value?.detail?.dropPhone,
|
|
|
+ fromAddress: info.fromAddress || currentOrder.value?.detail?.fromAddress,
|
|
|
+ toAddress: info.toAddress || currentOrder.value?.detail?.toAddress,
|
|
|
+ area: info.area || info.address || info.toAddress || currentOrder.value?.detail?.area,
|
|
|
+ packageName: info.packageName || info.servicePackageName || info.package || currentOrder.value?.detail?.packageName,
|
|
|
+ petStatus: info.petStatus || info.specialRequirement || info.requirement || currentOrder.value?.detail?.petStatus,
|
|
|
+ fromCode: info.fromCode || currentOrder.value?.detail?.fromCode,
|
|
|
+ toCode: info.toCode || currentOrder.value?.detail?.toCode
|
|
|
}
|
|
|
- } catch {
|
|
|
+ };
|
|
|
}
|
|
|
- detailVisible.value = true
|
|
|
-}
|
|
|
+ } catch {}
|
|
|
+ detailVisible.value = true;
|
|
|
+};
|
|
|
|
|
|
// 取消订单
|
|
|
const handleCancel = (row) => {
|
|
|
- ElMessageBox.confirm('确认取消该订单吗?', '提示', { type: 'warning' })
|
|
|
- .then(() => {
|
|
|
- cancelSubOrder({ orderId: row?.id }).then(() => {
|
|
|
- ElMessage.success('订单已取消')
|
|
|
- handleSearch()
|
|
|
- })
|
|
|
- })
|
|
|
-}
|
|
|
+ ElMessageBox.confirm('确认取消该订单吗?', '提示', { type: 'warning' }).then(() => {
|
|
|
+ cancelSubOrder({ orderId: row?.id }).then(() => {
|
|
|
+ ElMessage.success('订单已取消');
|
|
|
+ handleSearch();
|
|
|
+ });
|
|
|
+ });
|
|
|
+};
|
|
|
|
|
|
// 派单
|
|
|
const openDispatchDialog = (row) => {
|
|
|
- const typeName = getServiceName(row?.service)
|
|
|
- const isTransport = row?.mode === 1 || row?.mode === '1'
|
|
|
- const typeCode = isTransport ? 'transport' : (typeName?.includes('喂') || typeName?.includes('遛') ? 'feeding' : 'washing')
|
|
|
-
|
|
|
- const t = row?.subOrderType ?? row?.type
|
|
|
- const transportType = (t === 0 || t === '0' || t === 1 || t === '1') ? 'round' : ((t === 2 || t === '2') ? 'pick' : ((t === 3 || t === '3') ? 'drop' : (row?.splitType || row?.transportType)))
|
|
|
-
|
|
|
- const toAddress = row?.toAddress || ''
|
|
|
- const pickAddr = isTransport ? toAddress : ''
|
|
|
- const dropAddr = isTransport ? toAddress : ''
|
|
|
- const address = isTransport ? '' : toAddress
|
|
|
-
|
|
|
- const orderObj = {
|
|
|
- id: row.id,
|
|
|
- typeCode,
|
|
|
- transportType,
|
|
|
- time: row.serviceTime || row.appointTime || row.createTime,
|
|
|
- status: row.status,
|
|
|
- address,
|
|
|
- pickAddr,
|
|
|
- dropAddr,
|
|
|
- riderId: row.riderId || row.fulfiller || null
|
|
|
- }
|
|
|
- currentDispatchOrder.value = orderObj
|
|
|
- dispatchDialogVisible.value = true
|
|
|
-}
|
|
|
+ const typeName = getServiceName(row?.service);
|
|
|
+ const isTransport = row?.mode === 1 || row?.mode === '1';
|
|
|
+ const typeCode = isTransport ? 'transport' : typeName?.includes('喂') || typeName?.includes('遛') ? 'feeding' : 'washing';
|
|
|
+
|
|
|
+ const t = row?.subOrderType ?? row?.type;
|
|
|
+ const transportType =
|
|
|
+ t === 0 || t === '0' || t === 1 || t === '1'
|
|
|
+ ? 'round'
|
|
|
+ : t === 2 || t === '2'
|
|
|
+ ? 'pick'
|
|
|
+ : t === 3 || t === '3'
|
|
|
+ ? 'drop'
|
|
|
+ : row?.splitType || row?.transportType;
|
|
|
+
|
|
|
+ const toAddress = row?.toAddress || '';
|
|
|
+ const pickAddr = isTransport ? toAddress : '';
|
|
|
+ const dropAddr = isTransport ? toAddress : '';
|
|
|
+ const address = isTransport ? '' : toAddress;
|
|
|
+
|
|
|
+ const orderObj = {
|
|
|
+ id: row.id,
|
|
|
+ typeCode,
|
|
|
+ transportType,
|
|
|
+ time: row.serviceTime || row.appointTime || row.createTime,
|
|
|
+ status: row.status,
|
|
|
+ address,
|
|
|
+ pickAddr,
|
|
|
+ dropAddr,
|
|
|
+ riderId: row.riderId || row.fulfiller || null
|
|
|
+ };
|
|
|
+ currentDispatchOrder.value = orderObj;
|
|
|
+ dispatchDialogVisible.value = true;
|
|
|
+};
|
|
|
|
|
|
const handleDispatchSubmit = (payload) => {
|
|
|
- if (!currentDispatchOrder.value) return
|
|
|
- const priceFen = Math.round(Number(payload.fee || 0) * 100)
|
|
|
- dispatchSubOrder({
|
|
|
- orderId: currentDispatchOrder.value.id,
|
|
|
- fulfiller: payload.riderId,
|
|
|
- price: priceFen
|
|
|
- }).then(() => {
|
|
|
- ElMessage.success('派单成功')
|
|
|
- const row = tableData.value.find(r => r.id === currentDispatchOrder.value.id)
|
|
|
- if (row) {
|
|
|
- row.status = 1
|
|
|
- row.fulfillerName = payload.riderName || 'Unknown'
|
|
|
- row.price = payload.fee
|
|
|
- }
|
|
|
- handleSearch()
|
|
|
- })
|
|
|
-}
|
|
|
+ if (!currentDispatchOrder.value) return;
|
|
|
+ const priceFen = Math.round(Number(payload.fee || 0) * 100);
|
|
|
+ dispatchSubOrder({
|
|
|
+ orderId: currentDispatchOrder.value.id,
|
|
|
+ fulfiller: payload.riderId,
|
|
|
+ price: priceFen
|
|
|
+ }).then(() => {
|
|
|
+ ElMessage.success('派单成功');
|
|
|
+ const row = tableData.value.find((r) => r.id === currentDispatchOrder.value.id);
|
|
|
+ if (row) {
|
|
|
+ row.status = 1;
|
|
|
+ row.fulfillerName = payload.riderName || 'Unknown';
|
|
|
+ row.price = payload.fee;
|
|
|
+ }
|
|
|
+ handleSearch();
|
|
|
+ });
|
|
|
+};
|
|
|
|
|
|
// 护理小结
|
|
|
const openCareSummary = (row) => {
|
|
|
- careSummaryOrder.value = {
|
|
|
- ...row,
|
|
|
- petAge: '3岁',
|
|
|
- petGender: 'male',
|
|
|
- petTags: ['易过敏', '胆小'],
|
|
|
- petWeight: '30 kg',
|
|
|
- petPersonality: '活泼,超级粘人,喜欢玩球',
|
|
|
- homeTime: '2023-01-01',
|
|
|
- houseType: '电梯',
|
|
|
- entryMethod: '密码开门',
|
|
|
- entryDetail: '密码: 123456 (仅限服务期间使用)',
|
|
|
- healthStatus: '健康',
|
|
|
- vaccineImg: 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg',
|
|
|
- allergy: '海鲜'
|
|
|
- }
|
|
|
- careSummaryVisible.value = true
|
|
|
-}
|
|
|
+ careSummaryOrder.value = {
|
|
|
+ ...row,
|
|
|
+ petAge: '3岁',
|
|
|
+ petGender: 'male',
|
|
|
+ petTags: ['易过敏', '胆小'],
|
|
|
+ petWeight: '30 kg',
|
|
|
+ petPersonality: '活泼,超级粘人,喜欢玩球',
|
|
|
+ homeTime: '2023-01-01',
|
|
|
+ houseType: '电梯',
|
|
|
+ entryMethod: '密码开门',
|
|
|
+ entryDetail: '密码: 123456 (仅限服务期间使用)',
|
|
|
+ healthStatus: '健康',
|
|
|
+ vaccineImg: 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg',
|
|
|
+ allergy: '海鲜'
|
|
|
+ };
|
|
|
+ careSummaryVisible.value = true;
|
|
|
+};
|
|
|
|
|
|
const saveCareSummary = (text) => {
|
|
|
- if (careSummaryOrder.value) {
|
|
|
- careSummaryOrder.value.careSummary = text
|
|
|
- ElMessage.success('护理小结已保存')
|
|
|
- handleSearch()
|
|
|
- }
|
|
|
-}
|
|
|
+ if (careSummaryOrder.value) {
|
|
|
+ careSummaryOrder.value.careSummary = text;
|
|
|
+ ElMessage.success('护理小结已保存');
|
|
|
+ handleSearch();
|
|
|
+ }
|
|
|
+};
|
|
|
|
|
|
// 奖惩
|
|
|
const openRewardDialog = (row) => {
|
|
|
- currentOperateRow.value = row
|
|
|
- rewardDialogVisible.value = true
|
|
|
-}
|
|
|
+ currentOperateRow.value = row;
|
|
|
+ rewardDialogVisible.value = true;
|
|
|
+};
|
|
|
const handleRewardSubmit = (form) => {
|
|
|
- ElMessage.success(`操作成功:${form.type === 'reward' ? '奖励' : '惩罚'}已执行`)
|
|
|
-}
|
|
|
+ ElMessage.success(`操作成功:${form.type === 'reward' ? '奖励' : '惩罚'}已执行`);
|
|
|
+};
|
|
|
|
|
|
// 备注
|
|
|
const openRemarkDialog = (row) => {
|
|
|
- currentOperateRow.value = row
|
|
|
- remarkDialogVisible.value = true
|
|
|
-}
|
|
|
+ currentOperateRow.value = row;
|
|
|
+ remarkDialogVisible.value = true;
|
|
|
+};
|
|
|
const handleRemarkSubmit = (text) => {
|
|
|
- if (currentOperateRow.value) {
|
|
|
- currentOperateRow.value.remark = text
|
|
|
- ElMessage.success('备注已更新')
|
|
|
- }
|
|
|
-}
|
|
|
+ if (currentOperateRow.value) {
|
|
|
+ currentOperateRow.value.remark = text;
|
|
|
+ ElMessage.success('备注已更新');
|
|
|
+ }
|
|
|
+};
|
|
|
|
|
|
// 更多操作
|
|
|
const handleCommand = (cmd, row) => {
|
|
|
- if (cmd === 'reward') openRewardDialog(row)
|
|
|
- if (cmd === 'remark') openRemarkDialog(row)
|
|
|
- if (cmd === 'care_summary') openCareSummary(row)
|
|
|
- if (cmd === 'complete') {
|
|
|
- ElMessageBox.confirm('确认将该订单手动标记为完成吗?', '提示', { type: 'warning' })
|
|
|
- .then(() => {
|
|
|
- row.status = 4
|
|
|
- ElMessage.success('订单已标记完成')
|
|
|
- })
|
|
|
- }
|
|
|
- if (cmd === 'delete') {
|
|
|
- ElMessageBox.confirm('确认删除该订单吗?此操作不可恢复', '警告', { type: 'error' })
|
|
|
- .then(() => {
|
|
|
- tableData.value = tableData.value.filter(item => item.id !== row.id)
|
|
|
- ElMessage.success('订单已删除')
|
|
|
- })
|
|
|
- }
|
|
|
-}
|
|
|
+ if (cmd === 'reward') openRewardDialog(row);
|
|
|
+ if (cmd === 'remark') openRemarkDialog(row);
|
|
|
+ if (cmd === 'care_summary') openCareSummary(row);
|
|
|
+ if (cmd === 'complete') {
|
|
|
+ ElMessageBox.confirm('确认将该订单手动标记为完成吗?', '提示', { type: 'warning' }).then(() => {
|
|
|
+ row.status = 4;
|
|
|
+ ElMessage.success('订单已标记完成');
|
|
|
+ });
|
|
|
+ }
|
|
|
+ if (cmd === 'delete') {
|
|
|
+ ElMessageBox.confirm('确认删除该订单吗?此操作不可恢复', '警告', { type: 'error' }).then(() => {
|
|
|
+ tableData.value = tableData.value.filter((item) => item.id !== row.id);
|
|
|
+ ElMessage.success('订单已删除');
|
|
|
+ });
|
|
|
+ }
|
|
|
+};
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
.page-container {
|
|
|
- padding: 20px;
|
|
|
+ padding: 20px;
|
|
|
}
|
|
|
|
|
|
.card-header {
|
|
|
- display: flex;
|
|
|
- justify-content: space-between;
|
|
|
- align-items: center;
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
}
|
|
|
|
|
|
.title {
|
|
|
- font-weight: bold;
|
|
|
- font-size: 18px;
|
|
|
+ font-weight: bold;
|
|
|
+ font-size: 18px;
|
|
|
}
|
|
|
|
|
|
.right-panel {
|
|
|
- display: flex;
|
|
|
- gap: 10px;
|
|
|
- align-items: center;
|
|
|
+ display: flex;
|
|
|
+ gap: 10px;
|
|
|
+ align-items: center;
|
|
|
}
|
|
|
|
|
|
.search-input {
|
|
|
- width: 220px;
|
|
|
+ width: 220px;
|
|
|
}
|
|
|
|
|
|
.status-tabs {
|
|
|
- margin-top: 10px;
|
|
|
- margin-bottom: -10px;
|
|
|
+ margin-top: 10px;
|
|
|
+ margin-bottom: -10px;
|
|
|
}
|
|
|
|
|
|
.pagination-container {
|
|
|
- display: flex;
|
|
|
- justify-content: flex-end;
|
|
|
- margin-top: 20px;
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ margin-top: 20px;
|
|
|
}
|
|
|
|
|
|
/* Table Content Styles */
|
|
|
.service-type-cell {
|
|
|
- display: flex;
|
|
|
- flex-direction: row;
|
|
|
- gap: 4px;
|
|
|
- align-items: center;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ gap: 4px;
|
|
|
+ align-items: center;
|
|
|
}
|
|
|
|
|
|
.sub-tag {
|
|
|
- font-size: 11px;
|
|
|
- height: 20px;
|
|
|
- padding: 0 5px;
|
|
|
+ font-size: 11px;
|
|
|
+ height: 20px;
|
|
|
+ padding: 0 5px;
|
|
|
}
|
|
|
|
|
|
.pet-info {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- gap: 10px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 10px;
|
|
|
}
|
|
|
|
|
|
.pet-info .el-avatar {
|
|
|
- background: #e0eaff;
|
|
|
- color: #409eff;
|
|
|
- font-weight: bold;
|
|
|
- flex-shrink: 0;
|
|
|
+ background: #e0eaff;
|
|
|
+ color: #409eff;
|
|
|
+ font-weight: bold;
|
|
|
+ flex-shrink: 0;
|
|
|
}
|
|
|
|
|
|
.pet-info .avatar-feeding {
|
|
|
- background: #fdf6ec;
|
|
|
- color: #e6a23c;
|
|
|
+ background: #fdf6ec;
|
|
|
+ color: #e6a23c;
|
|
|
}
|
|
|
|
|
|
.pet-info .avatar-washing {
|
|
|
- background: #f0f9eb;
|
|
|
- color: #67c23a;
|
|
|
+ background: #f0f9eb;
|
|
|
+ color: #67c23a;
|
|
|
}
|
|
|
|
|
|
.pet-detail {
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- line-height: 1.4;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ line-height: 1.4;
|
|
|
}
|
|
|
|
|
|
.pet-name {
|
|
|
- font-weight: bold;
|
|
|
- font-size: 14px;
|
|
|
- color: #303133;
|
|
|
+ font-weight: bold;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #303133;
|
|
|
}
|
|
|
|
|
|
.pet-breed {
|
|
|
- color: #909399;
|
|
|
- font-weight: normal;
|
|
|
- font-size: 12px;
|
|
|
+ color: #909399;
|
|
|
+ font-weight: normal;
|
|
|
+ font-size: 12px;
|
|
|
}
|
|
|
|
|
|
.merchant-info {
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- line-height: 1.4;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ line-height: 1.4;
|
|
|
}
|
|
|
|
|
|
.sub-text {
|
|
|
- font-size: 12px;
|
|
|
- color: #999;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #999;
|
|
|
}
|
|
|
|
|
|
.text-gray {
|
|
|
- color: #ccc;
|
|
|
- font-style: italic;
|
|
|
+ color: #ccc;
|
|
|
+ font-style: italic;
|
|
|
}
|
|
|
|
|
|
.time-text {
|
|
|
- font-size: 13px;
|
|
|
- color: #606266;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #606266;
|
|
|
}
|
|
|
|
|
|
.status-cell {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
}
|
|
|
|
|
|
.status-dot {
|
|
|
- width: 6px;
|
|
|
- height: 6px;
|
|
|
- border-radius: 50%;
|
|
|
- margin-right: 6px;
|
|
|
- background-color: #909399;
|
|
|
+ width: 6px;
|
|
|
+ height: 6px;
|
|
|
+ border-radius: 50%;
|
|
|
+ margin-right: 6px;
|
|
|
+ background-color: #909399;
|
|
|
}
|
|
|
|
|
|
.status-dot.pending_dispatch {
|
|
|
- background-color: #f56c6c;
|
|
|
- box-shadow: 0 0 4px rgba(245, 108, 108, 0.4);
|
|
|
+ background-color: #f56c6c;
|
|
|
+ box-shadow: 0 0 4px rgba(245, 108, 108, 0.4);
|
|
|
}
|
|
|
|
|
|
.status-dot.pending_accept {
|
|
|
- background-color: #e6a23c;
|
|
|
+ background-color: #e6a23c;
|
|
|
}
|
|
|
|
|
|
.status-dot.serving {
|
|
|
- background-color: #409eff;
|
|
|
+ background-color: #409eff;
|
|
|
}
|
|
|
|
|
|
.status-dot.pending_confirm {
|
|
|
- background-color: #bf24e8;
|
|
|
+ background-color: #bf24e8;
|
|
|
}
|
|
|
|
|
|
.status-dot.completed {
|
|
|
- background-color: #67c23a;
|
|
|
+ background-color: #67c23a;
|
|
|
}
|
|
|
|
|
|
.status-dot.cancelled {
|
|
|
- background-color: #909399;
|
|
|
+ background-color: #909399;
|
|
|
}
|
|
|
|
|
|
.fulfiller-info {
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
}
|
|
|
|
|
|
.fulfiller-name {
|
|
|
- font-weight: 500;
|
|
|
- color: #333;
|
|
|
+ font-weight: 500;
|
|
|
+ color: #333;
|
|
|
}
|
|
|
|
|
|
.fulfiller-fee {
|
|
|
- font-size: 12px;
|
|
|
- color: #e6a23c;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #e6a23c;
|
|
|
}
|
|
|
|
|
|
.op-cell {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- gap: 8px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
}
|
|
|
|
|
|
.el-dropdown-link {
|
|
|
- cursor: pointer;
|
|
|
- color: #409eff;
|
|
|
- font-size: 12px;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- line-height: 1;
|
|
|
- height: 24px;
|
|
|
+ cursor: pointer;
|
|
|
+ color: #409eff;
|
|
|
+ font-size: 12px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ line-height: 1;
|
|
|
+ height: 24px;
|
|
|
}
|
|
|
</style>
|